/* _______ ____ __ ___ ___ * \ _ \ \ / \ / \ \ / / ' ' ' * | | \ \ | | || | \/ | . . * | | | | | | || ||\ /| | * | | | | | | || || \/ | | ' ' ' * | | | | | | || || | | . . * | |_/ / \ \__// || | | * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque * / \ * / . \ * sequence.c - The sequence (SEQU) signal type. / / \ \ * | < / \_ * By entheh. | \/ /\ / * \_ / > / * | \ / / * | ' / * \__/ */ #include #include #include #include "dumb.h" #define SIGTYPE_SEQUENCE DUMB_ID('S','E','Q','U') /* We have 256 intervals per semitone, 12 * 256 per octave 2 ** (1 / (12 * 256)) = 1.000225659305069791926712241547647863626 pow(DUMB_PITCH_BASE, x) = 1.5 x = log2(1.5) / log2(DUMB_PITCH_BASE) x = log2(1.5) * 12 * 256 x = 1797.004802 cf. x = 7 * 256 = 1792 so, for the perfect fifth temperament, use an interval of 1797. */ /* Sequencing format * ----------------- * * NOTE: A LOT OF THIS IS NOW REDUNDANT. PLEASE REFER TO duhspecs.txt. * * When a signal is initiated, it claims a reference number. If any other * currently playing signal has the same number, that signal becomes * anonymous and inaccessible; that is, if multiple signals were initiated * with the same reference, the reference belongs to the most recent. * * Signals can be stopped, or have their pitch, volume or parameters changed, * using the reference number. A signal may stop prematurely if it runs out * of data, in which case the reference number becomes void, and operations * on it will be ignored. Such a situation will not flag any kind of warning, * since it may be the result of inaccuracies when resampling. * * The sequence consists of a series of commands. All commands begin with a * long int, which is the time to wait after the last command (or the * beginning of the sequence) before executing this command. 65536 represents * one second. A time of -1 (or in fact any negative time) terminates the * sequence, but any currently playing signals will continue until they run * out. Make sure no non-terminating signals are playing when the sequence * ends! * * The time, if nonnegative, is followed by one byte indicating the type of * command. This byte can have the following values: * * SEQUENCE_START_SIGNAL * unsigned char ref; - Reference. Need more than 256? Use two sequences, * and get your brain seen to. * int sig; - The index of the signal to initiate. * long pos; - The position at which to start. 65536 represents one second. * unsigned short volume; - Volume. 65535 represents the maximum volume, so * you will want to go lower than this if you are * playing more than one signal at once. * signed short pitch; - Pitch. 0 represents a frequency of 65536 Hz. Scale * is logarithmic. Add 256 to increase pitch by one * semitone in the even temperament, or add 12*256 to * increase pitch by one octave in any temperament * (i.e. double the frequency). * * SEQUENCE_SET_VOLUME * unsigned char ref; * unsigned short volume; * * SEQUENCE_SET_PITCH * unsigned char ref; * signed short pitch; * * SEQUENCE_SET_PARAMETER * unsigned char ref; * unsigned char id; * long value; * - see the description of the set_parameter function pointer for * information on 'id' and 'value'. * * SEQUENCE_STOP_SIGNAL * unsigned char ref; */ #define SEQUENCE_START_SIGNAL 0 #define SEQUENCE_SET_VOLUME 1 #define SEQUENCE_SET_PITCH 2 #define SEQUENCE_SET_PARAMETER 3 #define SEQUENCE_STOP_SIGNAL 4 typedef struct SEQUENCE_PLAYING { struct SEQUENCE_PLAYING *next; DUH_SIGNAL_SAMPINFO *sampinfo; int ref; int pitch; int volume; } SEQUENCE_PLAYING; typedef struct SEQUENCE_SAMPINFO { DUH *duh; int n_channels; unsigned char *signal; long time_left; int sub_time_left; SEQUENCE_PLAYING *playing; } SEQUENCE_SAMPINFO; #define sequence_c(signal) ((int)*((*(signal))++)) static int sequence_w(unsigned char **signal) { int v = (*signal)[0] | ((*signal)[1] << 8); *signal += 2; return v; } static long sequence_l(unsigned char **signal) { long v = (*signal)[0] | ((*signal)[1] << 8) | ((*signal)[2] << 16) | ((*signal)[3] << 24); *signal += 4; return v; } static long sequence_cl(unsigned char **signal) { long v = sequence_c(signal); if (v & 0x80) { v &= 0x7F; v |= sequence_c(signal) << 7; if (v & 0x4000) { v &= 0x3FFF; v |= sequence_c(signal) << 14; if (v & 0x200000) { v &= 0x1FFFFF; v |= sequence_c(signal) << 21; if (v & 0x10000000) { v &= 0x0FFFFFFF; v |= sequence_c(signal) << 28; } } } } return v; } static void *sequence_load_signal(DUH *duh, DUMBFILE *file) { long size; unsigned char *signal; (void)duh; size = dumbfile_igetl(file); if (dumbfile_error(file) || size <= 0) return NULL; signal = malloc(size); if (!signal) return NULL; if (dumbfile_getnc((char *)signal, size, file) < size) { free(signal); return NULL; } return signal; } static long render( SEQUENCE_SAMPINFO *sampinfo, float volume, float delta, long pos, long size, sample_t **samples ) { sample_t **splptr; SEQUENCE_PLAYING **playing_p = &sampinfo->playing; long max_size = 0; long part_size; int n; long i; for (n = 0; n < sampinfo->n_channels; n++) memset(samples[n] + pos, 0, size * sizeof(sample_t)); splptr = malloc(sampinfo->n_channels * sizeof(*splptr)); if (!splptr) return 0; splptr[0] = malloc(sampinfo->n_channels * size * sizeof(sample_t)); if (!splptr[0]) { free(splptr); return 0; } for (n = 1; n < sampinfo->n_channels; n++) splptr[n] = splptr[n - 1] + size; while (*playing_p) { SEQUENCE_PLAYING *playing = *playing_p; part_size = duh_signal_render_samples( playing->sampinfo, volume * (float)playing->volume * (1.0f / 65535.0f), (float)(pow(DUMB_PITCH_BASE, playing->pitch) * delta), size, splptr ); for (n = 0; n < sampinfo->n_channels; n++) for (i = 0; i < part_size; i++) samples[n][pos+i] += splptr[n][i]; if (part_size > max_size) max_size = part_size; if (part_size < size) { *playing_p = playing->next; duh_signal_end_samples(playing->sampinfo); free(playing); } else playing_p = &playing->next; } free(splptr[0]); free(splptr); return max_size; } /* 'offset' is added to the position at which the signal should start. It is * currently assumed to be positive, and is currently only used when seeking * forwards in the sequence. */ static void sequence_command(SEQUENCE_SAMPINFO *sampinfo, long offset) { int command = sequence_c(&sampinfo->signal); if (command == SEQUENCE_START_SIGNAL) { int ref = sequence_c(&sampinfo->signal); int sig = sequence_cl(&sampinfo->signal); long pos = sequence_cl(&sampinfo->signal); int volume = sequence_w(&sampinfo->signal); int pitch = (int)(signed short)sequence_w(&sampinfo->signal); SEQUENCE_PLAYING *playing = sampinfo->playing; while (playing) { if (playing->ref == ref) playing->ref = -1; playing = playing->next; } playing = malloc(sizeof(SEQUENCE_PLAYING)); if (playing) { playing->sampinfo = duh_signal_start_samples(sampinfo->duh, sig, sampinfo->n_channels, pos + offset); if (playing->sampinfo) { playing->ref = ref; playing->pitch = pitch; playing->volume = volume; playing->next = sampinfo->playing; sampinfo->playing = playing; } else free(playing); } } else if (command == SEQUENCE_SET_VOLUME) { int ref = sequence_c(&sampinfo->signal); int volume = sequence_w(&sampinfo->signal); SEQUENCE_PLAYING *playing = sampinfo->playing; while (playing) { if (playing->ref == ref) { playing->volume = volume; break; } playing = playing->next; } } else if (command == SEQUENCE_SET_PITCH) { int ref = sequence_c(&sampinfo->signal); int pitch = (int)(signed short)sequence_w(&sampinfo->signal); SEQUENCE_PLAYING *playing = sampinfo->playing; while (playing) { if (playing->ref == ref) { playing->pitch = pitch; break; } playing = playing->next; } } else if (command == SEQUENCE_SET_PARAMETER) { int ref = sequence_c(&sampinfo->signal); unsigned char id = sequence_c(&sampinfo->signal); long value = sequence_l(&sampinfo->signal); SEQUENCE_PLAYING *playing = sampinfo->playing; while (playing) { if (playing->ref == ref) { duh_signal_set_parameter(playing->sampinfo, id, value); break; } playing = playing->next; } } else if (command == SEQUENCE_STOP_SIGNAL) { int ref = sequence_c(&sampinfo->signal); SEQUENCE_PLAYING **playing_p = &sampinfo->playing; while (*playing_p) { SEQUENCE_PLAYING *playing = *playing_p; if (playing->ref == ref) { duh_signal_end_samples(playing->sampinfo); *playing_p = playing->next; free(playing); break; } playing_p = &playing->next; } } else { TRACE("Error in sequence: unknown command %d.\n", command); sampinfo->signal = NULL; } } static void *sequence_start_samples(DUH *duh, void *signal, int n_channels, long pos) { SEQUENCE_SAMPINFO *sampinfo; long time = sequence_cl((unsigned char **)&signal); if (time < 0) return NULL; sampinfo = malloc(sizeof(SEQUENCE_SAMPINFO)); if (!sampinfo) return NULL; sampinfo->duh = duh; sampinfo->n_channels = n_channels; sampinfo->signal = signal; sampinfo->playing = NULL; /* Seek to 'pos'. */ while (time < pos) { pos -= time; sequence_command(sampinfo, pos); time = sequence_cl(&sampinfo->signal); if (time < 0) { sampinfo->signal = NULL; return sampinfo; } } sampinfo->time_left = time - pos; sampinfo->sub_time_left = 0; return sampinfo; } static long sequence_render_samples( void *sampinfo, float volume, float delta, long size, sample_t **samples ) { #define sampinfo ((SEQUENCE_SAMPINFO *)sampinfo) long pos = 0; int dt = (int)(delta * 65536.0f + 0.5f); long todo; LONG_LONG t; while (sampinfo->signal) { todo = (long)((((LONG_LONG)sampinfo->time_left << 16) | sampinfo->sub_time_left) / dt); if (todo >= size) break; if (todo) { render(sampinfo, volume, delta, pos, todo, samples); pos += todo; size -= todo; todo = (long)((((LONG_LONG)sampinfo->time_left << 16) | sampinfo->sub_time_left) / dt); t = sampinfo->sub_time_left - (LONG_LONG)todo * dt; sampinfo->sub_time_left = (long)t & 65535; sampinfo->time_left += (long)(t >> 16); } sequence_command(sampinfo, 0); todo = sequence_cl(&sampinfo->signal); if (todo >= 0) sampinfo->time_left += todo; else sampinfo->signal = NULL; } if (sampinfo->signal) { render(sampinfo, volume, delta, pos, size, samples); pos += size; t = sampinfo->sub_time_left - (LONG_LONG)size * dt; sampinfo->sub_time_left = (long)t & 65535; sampinfo->time_left += (long)(t >> 16); } else pos += render(sampinfo, volume, delta, pos, size, samples); return pos; /** WARNING - remove this... */ #if 0 float size_unified = size * delta; int n; sample_t **samples2 = malloc(sampinfo->n_channels * sizeof(*samples2)); memcpy(samples2, samples, sampinfo->n_channels * sizeof(*samples2)); while (sampinfo->signal && sampinfo->time < size_unified) { { long sz = (long)(sampinfo->time / delta); if (sz) render(sampinfo, volume, delta, sz, samples2); for (n = 0; n < sampinfo->n_channels; n++) samples2[n] += sz; size -= sz; pos += sz; sampinfo->time -= sz * delta; } sequence_command(sampinfo, 0); { long time = sequence_cl(&sampinfo->signal); if (time >= 0) sampinfo->time += (float)time; else sampinfo->signal = NULL; } size_unified = size * delta; } if (sampinfo->signal) { render(sampinfo, volume, delta, size, samples2); sampinfo->time -= size_unified; pos += size; } else pos += render(sampinfo, volume, delta, size, samples2); free(samples2); return pos; #endif #undef sampinfo } static void sequence_end_samples(void *sampinfo) { SEQUENCE_PLAYING *playing = ((SEQUENCE_SAMPINFO *)sampinfo)->playing; while (playing) { SEQUENCE_PLAYING *next = playing->next; duh_signal_end_samples(playing->sampinfo); free(playing); playing = next; } free(sampinfo); } static void sequence_unload_signal(void *signal) { free(signal); } static DUH_SIGTYPE_DESC sigtype_sequence = { SIGTYPE_SEQUENCE, &sequence_load_signal, &sequence_start_samples, NULL, &sequence_render_samples, &sequence_end_samples, &sequence_unload_signal }; void dumb_register_sigtype_sequence(void) { dumb_register_sigtype(&sigtype_sequence); }