diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2017-05-26 21:51:04 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2017-05-26 22:48:09 -0700 |
commit | 78f8fce7f286fd0c71774e2567404ed51f24fef3 (patch) | |
tree | f3de4987f7a9fc1bc03331e97b65a851b041051a /src/player |
*: initial commit of stripped schism stuff
Forking schism tracker's IT playback stuff into a little playback
library for embedding in demos.
Diffstat (limited to 'src/player')
-rw-r--r-- | src/player/csndfile.c | 1094 | ||||
-rw-r--r-- | src/player/effects.c | 2070 | ||||
-rw-r--r-- | src/player/equalizer.c | 255 | ||||
-rw-r--r-- | src/player/filters.c | 116 | ||||
-rw-r--r-- | src/player/fmopl.c | 2396 | ||||
-rw-r--r-- | src/player/fmpatches.c | 179 | ||||
-rw-r--r-- | src/player/mixer.c | 1555 | ||||
-rw-r--r-- | src/player/mixutil.c | 258 | ||||
-rw-r--r-- | src/player/opl-util.c | 134 | ||||
-rw-r--r-- | src/player/snd_fm.c | 385 | ||||
-rw-r--r-- | src/player/sndmix.c | 1259 | ||||
-rw-r--r-- | src/player/tables.c | 443 |
12 files changed, 10144 insertions, 0 deletions
diff --git a/src/player/csndfile.c b/src/player/csndfile.c new file mode 100644 index 0000000..bba8ae0 --- /dev/null +++ b/src/player/csndfile.c @@ -0,0 +1,1094 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#define NEED_TIME +#include "headers.h" + +#include <math.h> +#include <stdint.h> +#include <assert.h> + +#include "sndfile.h" +#include "log.h" +#include "util.h" +#include "fmt.h" // for it_decompress8 / it_decompress16 + + +static void _csf_reset(song_t *csf) +{ + unsigned int i; + + csf->flags = 0; + csf->pan_separation = 128; + csf->num_voices = 0; + csf->freq_factor = csf->tempo_factor = 128; + csf->initial_global_volume = 128; + csf->current_global_volume = 128; + csf->initial_speed = 6; + csf->initial_tempo = 125; + csf->process_row = 0; + csf->row = 0; + csf->current_pattern = 0; + csf->current_order = 0; + csf->process_order = 0; + csf->mixing_volume = 0x30; + memset(csf->message, 0, sizeof(csf->message)); + + csf->row_highlight_major = 16; + csf->row_highlight_minor = 4; + + /* This is intentionally crappy quality, so that it's very obvious if it didn't get initialized */ + csf->mix_flags = 0; + csf_set_wave_config(csf, 4000, 8, 1); + + memset(csf->voices, 0, sizeof(csf->voices)); + memset(csf->voice_mix, 0, sizeof(csf->voice_mix)); + memset(csf->samples, 0, sizeof(csf->samples)); + memset(csf->instruments, 0, sizeof(csf->instruments)); + memset(csf->orderlist, 0xFF, sizeof(csf->orderlist)); + memset(csf->patterns, 0, sizeof(csf->patterns)); + + csf_reset_midi_cfg(csf); + csf_forget_history(csf); + + for (i = 0; i < MAX_PATTERNS; i++) { + csf->pattern_size[i] = 64; + csf->pattern_alloc_size[i] = 64; + } + for (i = 0; i < MAX_SAMPLES; i++) { + csf->samples[i].c5speed = 8363; + csf->samples[i].volume = 64 * 4; + csf->samples[i].global_volume = 64; + } + for (i = 0; i < MAX_CHANNELS; i++) { + csf->channels[i].panning = 128; + csf->channels[i].volume = 64; + csf->channels[i].flags = 0; + } +} + +////////////////////////////////////////////////////////// +// song_t + +song_t *csf_allocate(void) +{ + song_t *csf = calloc(1, sizeof(song_t)); + _csf_reset(csf); + return csf; +} + +void csf_free(song_t *csf) +{ + if (csf) { + csf_destroy(csf); + free(csf); + } +} + + +static void _init_envelope(song_envelope_t *env, int n) +{ + env->nodes = 2; + env->ticks[0] = 0; + env->ticks[1] = 100; + env->values[0] = n; + env->values[1] = n; +} + +void csf_init_instrument(song_instrument_t *ins, int samp) +{ + int n; + _init_envelope(&ins->vol_env, 64); + _init_envelope(&ins->pan_env, 32); + _init_envelope(&ins->pitch_env, 32); + ins->global_volume = 128; + ins->panning = 128; + ins->midi_bank = -1; + ins->midi_program = -1; + ins->pitch_pan_center = 60; // why does pitch/pan not use the same note values as everywhere else?! + for (n = 0; n < 128; n++) { + ins->sample_map[n] = samp; + ins->note_map[n] = n + 1; + } +} + +song_instrument_t *csf_allocate_instrument(void) +{ + song_instrument_t *ins = calloc(1, sizeof(song_instrument_t)); + csf_init_instrument(ins, 0); + return ins; +} + +void csf_free_instrument(song_instrument_t *i) +{ + free(i); +} + + +void csf_destroy(song_t *csf) +{ + int i; + + for (i = 0; i < MAX_PATTERNS; i++) { + if (csf->patterns[i]) { + csf_free_pattern(csf->patterns[i]); + csf->patterns[i] = NULL; + } + } + for (i = 1; i < MAX_SAMPLES; i++) { + song_sample_t *pins = &csf->samples[i]; + if (pins->data) { + csf_free_sample(pins->data); + pins->data = NULL; + } + } + for (i = 0; i < MAX_INSTRUMENTS; i++) { + if (csf->instruments[i]) { + csf_free_instrument(csf->instruments[i]); + csf->instruments[i] = NULL; + } + } + + _csf_reset(csf); +} + +song_note_t *csf_allocate_pattern(uint32_t rows) +{ + return calloc(rows * MAX_CHANNELS, sizeof(song_note_t)); +} + +void csf_free_pattern(void *pat) +{ + free(pat); +} + +/* Note: this function will appear in valgrind to be a sieve for memory leaks. +It isn't; it's just being confused by the adjusted pointer being stored. */ +signed char *csf_allocate_sample(uint32_t nbytes) +{ + signed char *p = calloc(1, (nbytes + 39) & ~7); // magic + if (p) + p += 16; + return p; +} + +void csf_free_sample(void *p) +{ + if (p) + free(p - 16); +} + +void csf_forget_history(song_t *csf) +{ + free(csf->histdata); + csf->histdata = NULL; + csf->histlen = 0; + gettimeofday(&csf->editstart, NULL); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* Counting and checking stuff. */ + +static int name_is_blank(char *name) +{ + int n; + for (n = 0; n < 25; n++) { + if (name[n] != '\0' && name[n] != ' ') + return 0; + } + return 1; +} + +const song_note_t blank_pattern[64 * 64]; +const song_note_t *blank_note = blank_pattern; // Same thing, really. + +int csf_note_is_empty(song_note_t *note) +{ + return !memcmp(note, blank_pattern, sizeof(song_note_t)); +} + +int csf_pattern_is_empty(song_t *csf, int n) +{ + if (!csf->patterns[n]) + return 1; + if (csf->pattern_size[n] != 64) + return 0; + return !memcmp(csf->patterns[n], blank_pattern, sizeof(blank_pattern)); +} + +int csf_sample_is_empty(song_sample_t *smp) +{ + return (smp->data == NULL + && name_is_blank(smp->name) + && smp->filename[0] == '\0' + && smp->c5speed == 8363 + && smp->volume == 64*4 //mphack + && smp->global_volume == 64 + && smp->panning == 0 + && !(smp->flags & (CHN_LOOP | CHN_SUSTAINLOOP | CHN_PANNING)) + && smp->length == 0 + && smp->loop_start == 0 + && smp->loop_end == 0 + && smp->sustain_start == 0 + && smp->sustain_end == 0 + && smp->vib_type == VIB_SINE + && smp->vib_rate == 0 + && smp->vib_depth == 0 + && smp->vib_speed == 0 + ); +} + +static int env_is_blank(song_envelope_t *env, int value) +{ + return (env->nodes == 2 + && env->loop_start == 0 + && env->loop_end == 0 + && env->sustain_start == 0 + && env->sustain_end == 0 + && env->ticks[0] == 0 + && env->ticks[1] == 100 + && env->values[0] == value + && env->values[1] == value + ); +} + +int csf_instrument_is_empty(song_instrument_t *ins) +{ + int n; + if (!ins) + return 1; + + for (n = 0; n < NOTE_LAST - NOTE_FIRST; n++) { + if (ins->sample_map[n] != 0 || ins->note_map[n] != (n + NOTE_FIRST)) + return 0; + } + return (name_is_blank(ins->name) + && ins->filename[0] == '\0' + && ins->flags == 0 /* No envelopes, loop points, panning, or carry flags set */ + && ins->nna == NNA_NOTECUT + && ins->dct == DCT_NONE + && ins->dca == DCA_NOTECUT + && env_is_blank(&ins->vol_env, 64) + && ins->global_volume == 128 + && ins->fadeout == 0 + && ins->vol_swing == 0 + && env_is_blank(&ins->pan_env, 32) + && ins->panning == 32*4 //mphack + && ins->pitch_pan_center == 60 // C-5 (blah) + && ins->pitch_pan_separation == 0 + && ins->pan_swing == 0 + && env_is_blank(&ins->pitch_env, 32) + && ins->ifc == 0 + && ins->ifr == 0 + && ins->midi_channel_mask == 0 + && ins->midi_program == -1 + && ins->midi_bank == -1 + ); +} + +// IT-compatible: last order of "main song", or 0 +int csf_last_order(song_t *csf) +{ + int n = 0; + while (n < MAX_ORDERS && csf->orderlist[n] != ORDER_LAST) + n++; + return n ? n - 1 : 0; +} + +// Total count of orders in orderlist before end of data +int csf_get_num_orders(song_t *csf) +{ + int n = MAX_ORDERS; + while (n >= 0 && csf->orderlist[--n] == ORDER_LAST) { + } + return n + 1; +} + +// Total number of non-empty patterns in song, according to csf_pattern_is_empty +int csf_get_num_patterns(song_t *csf) +{ + int n = MAX_PATTERNS - 1; + while (n && csf_pattern_is_empty(csf, n)) + n--; + return n+ 1; +} + +int csf_get_num_samples(song_t *csf) +{ + int n = MAX_SAMPLES - 1; + while (n > 0 && csf_sample_is_empty(csf->samples + n)) + n--; + return n; +} + +int csf_get_num_instruments(song_t *csf) +{ + int n = MAX_INSTRUMENTS - 1; + while (n > 0 && csf_instrument_is_empty(csf->instruments[n])) + n--; + return n; +} + + +int csf_first_blank_sample(song_t *csf, int start) +{ + int n; + for (n = MAX(start, 1); n < MAX_SAMPLES; n++) { + if (csf_sample_is_empty(csf->samples + n)) + return n; + } + return -1; +} + +int csf_first_blank_instrument(song_t *csf, int start) +{ + int n; + for (n = MAX(start, 1); n < MAX_INSTRUMENTS; n++) { + if (csf_instrument_is_empty(csf->instruments[n])) + return n; + } + return -1; +} + + +////////////////////////////////////////////////////////////////////////// +// Misc functions + +midi_config_t default_midi_config; + + +void csf_reset_midi_cfg(song_t *csf) +{ + memcpy(&csf->midi_config, &default_midi_config, sizeof(default_midi_config)); +} + +void csf_copy_midi_cfg(song_t *dest, song_t *src) +{ + memcpy(&dest->midi_config, &src->midi_config, sizeof(midi_config_t)); +} + + +int csf_set_wave_config(song_t *csf, uint32_t rate,uint32_t bits,uint32_t channels) +{ + int reset = ((csf->mix_frequency != rate) + || (csf->mix_bits_per_sample != bits) + || (csf->mix_channels != channels)); + csf->mix_channels = channels; + csf->mix_frequency = rate; + csf->mix_bits_per_sample = bits; + csf_init_player(csf, reset); + return 1; +} + + +int csf_set_resampling_mode(song_t *csf, uint32_t mode) +{ + uint32_t d = csf->mix_flags & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); + switch(mode) { + case SRCMODE_NEAREST: d |= SNDMIX_NORESAMPLING; break; + case SRCMODE_LINEAR: break; + case SRCMODE_SPLINE: d |= SNDMIX_HQRESAMPLER; break; + case SRCMODE_POLYPHASE: d |= (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); break; + default: return 0; + } + csf->mix_flags = d; + return 1; +} + + +// This used to use some retarded positioning based on the total number of rows elapsed, which is useless. +// However, the only code calling this function is in this file, to set it to the start, so I'm optimizing +// out the row count. +static void set_current_pos_0(song_t *csf) +{ + song_voice_t *v = csf->voices; + for (uint32_t i = 0; i < MAX_VOICES; i++, v++) { + memset(v, 0, sizeof(*v)); + v->cutoff = 0x7F; + v->volume = 256; + if (i < MAX_CHANNELS) { + v->panning = csf->channels[i].panning; + v->global_volume = csf->channels[i].volume; + v->flags = csf->channels[i].flags; + } else { + v->panning = 128; + v->global_volume = 64; + } + } + csf->current_global_volume = csf->initial_global_volume; + csf->current_speed = csf->initial_speed; + csf->current_tempo = csf->initial_tempo; +} + + +void csf_set_current_order(song_t *csf, uint32_t position) +{ + for (uint32_t j = 0; j < MAX_VOICES; j++) { + song_voice_t *v = csf->voices + j; + + v->period = 0; + v->note = v->new_note = v->new_instrument = 0; + v->portamento_target = 0; + v->n_command = 0; + v->cd_patloop = 0; + v->patloop_row = 0; + v->cd_tremor = 0; + // modplug sets vib pos to 16 in old effects mode for some reason *shrug* + v->vibrato_position = (csf->flags & SONG_ITOLDEFFECTS) ? 0 : 0x10; + v->tremolo_position = 0; + } + if (position > MAX_ORDERS) + position = 0; + if (!position) + set_current_pos_0(csf); + + csf->process_order = position - 1; + csf->process_row = PROCESS_NEXT_ORDER; + csf->row = 0; + csf->break_row = 0; /* set this to whatever row to jump to */ + csf->tick_count = 1; + csf->row_count = 0; + csf->buffer_count = 0; + + csf->flags &= ~(SONG_PATTERNLOOP|SONG_ENDREACHED); +} + +void csf_reset_playmarks(song_t *csf) +{ + int n; + + for (n = 1; n < MAX_SAMPLES; n++) { + csf->samples[n].played = 0; + } + for (n = 1; n < MAX_INSTRUMENTS; n++) { + if (csf->instruments[n]) + csf->instruments[n]->played = 0; + } +} + + +/* --------------------------------------------------------------------------------------------------------- */ + +#define SF_FAIL(name, n) \ + ({ log_appendf(4, "%s: internal error: unsupported %s %d", __FUNCTION__, name, n); return 0; }) + + +uint32_t csf_read_sample(song_sample_t *sample, uint32_t flags, const void *filedata, uint32_t memsize) +{ + uint32_t len = 0, mem; + const char *buffer = (const char *) filedata; + + if (sample->flags & CHN_ADLIB) return 0; // no sample data + + if (!sample || sample->length < 1 || !buffer) return 0; + + // validate the read flags before anything else + switch (flags & SF_BIT_MASK) { + case SF_7: case SF_8: case SF_16: case SF_24: case SF_32: break; + default: SF_FAIL("bit width", flags & SF_BIT_MASK); + } + switch (flags & SF_CHN_MASK) { + case SF_M: case SF_SI: case SF_SS: break; + default: SF_FAIL("channel mask", flags & SF_CHN_MASK); + } + switch (flags & SF_END_MASK) { + case SF_LE: case SF_BE: break; + default: SF_FAIL("endianness", flags & SF_END_MASK); + } + switch (flags & SF_ENC_MASK) { + case SF_PCMS: case SF_PCMU: case SF_PCMD: case SF_IT214: case SF_IT215: + case SF_AMS: case SF_DMF: case SF_MDL: case SF_PTM: + break; + default: SF_FAIL("encoding", flags & SF_ENC_MASK); + } + if ((flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)) != 0) { + SF_FAIL("extra flag", flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)); + } + + if (sample->length > MAX_SAMPLE_LENGTH) sample->length = MAX_SAMPLE_LENGTH; + mem = sample->length+6; + sample->flags &= ~(CHN_16BIT|CHN_STEREO); + switch (flags & SF_BIT_MASK) { + case SF_16: case SF_24: case SF_32: + // these are all stuffed into 16 bits. + mem *= 2; + sample->flags |= CHN_16BIT; + } + switch (flags & SF_CHN_MASK) { + case SF_SI: case SF_SS: + mem *= 2; + sample->flags |= CHN_STEREO; + } + if ((sample->data = csf_allocate_sample(mem)) == NULL) { + sample->length = 0; + return 0; + } + switch(flags) { + // 1: 8-bit unsigned PCM data + case RS_PCM8U: + { + len = sample->length; + if (len > memsize) len = sample->length = memsize; + signed char *data = sample->data; + for (uint32_t j=0; j<len; j++) data[j] = (signed char)(buffer[j] - 0x80); + } + break; + + // 2: 8-bit ADPCM data with linear table + case RS_PCM8D: + { + len = sample->length; + if (len > memsize) break; + signed char *data = sample->data; + const signed char *p = (const signed char *)buffer; + int delta = 0; + for (uint32_t j=0; j<len; j++) { + delta += p[j]; + *data++ = (signed char)delta; + } + } + break; + + // 4: 16-bit ADPCM data with linear table + case RS_PCM16D: + { + len = sample->length * 2; + if (len > memsize) break; + short *data = (short *)sample->data; + short *p = (short *)buffer; + unsigned short tmp; + int delta16 = 0; + for (uint32_t j=0; j<len; j+=2) { + tmp = *((unsigned short *)p++); + delta16 += bswapLE16(tmp); + *data++ = (short) delta16; + } + } + break; + + // 5: 16-bit signed PCM data + case RS_PCM16S: + { + len = sample->length * 2; + if (len <= memsize) memcpy(sample->data, buffer, len); + short int *data = (short int *)sample->data; + for (uint32_t j=0; j<len; j+=2) { + *data = bswapLE16(*data); + data++; + } + } + break; + + // 16-bit signed mono PCM motorola byte order + case RS_PCM16M: + len = sample->length * 2; + if (len > memsize) len = memsize & ~1; + if (len > 1) { + signed char *data = (signed char *)sample->data; + signed char *src = (signed char *)buffer; + for (uint32_t j=0; j<len; j+=2) { + // data[j] = src[j+1]; + // data[j+1] = src[j]; + *((unsigned short *)(data+j)) = bswapBE16(*((unsigned short *)(src+j))); + } + } + break; + + // 6: 16-bit unsigned PCM data + case RS_PCM16U: + { + len = sample->length * 2; + if (len <= memsize) memcpy(sample->data, buffer, len); + short int *data = (short int *)sample->data; + for (uint32_t j=0; j<len; j+=2) { + *data = bswapLE16(*data) - 0x8000; + data++; + } + } + break; + + // 16-bit signed stereo big endian + case RS_STPCM16M: + len = sample->length * 2; + if (len*2 <= memsize) { + signed char *data = (signed char *)sample->data; + signed char *src = (signed char *)buffer; + for (uint32_t j=0; j<len; j+=2) { + // data[j*2] = src[j+1]; + // data[j*2+1] = src[j]; + // data[j*2+2] = src[j+1+len]; + // data[j*2+3] = src[j+len]; + *((unsigned short *)(data+j*2)) + = bswapBE16(*((unsigned short *)(src+j))); + *((unsigned short *)(data+j*2+2)) + = bswapBE16(*((unsigned short *)(src+j+len))); + } + len *= 2; + } + break; + + // 8-bit stereo samples + case RS_STPCM8S: + case RS_STPCM8U: + case RS_STPCM8D: + { + int iadd_l, iadd_r; + iadd_l = iadd_r = (flags == RS_STPCM8U) ? -128 : 0; + len = sample->length; + signed char *psrc = (signed char *)buffer; + signed char *data = (signed char *)sample->data; + if (len*2 > memsize) break; + for (uint32_t j=0; j<len; j++) { + data[j*2] = (signed char)(psrc[0] + iadd_l); + data[j*2+1] = (signed char)(psrc[len] + iadd_r); + psrc++; + if (flags == RS_STPCM8D) { + iadd_l = data[j*2]; + iadd_r = data[j*2+1]; + } + } + len *= 2; + } + break; + + // 16-bit stereo samples + case RS_STPCM16S: + case RS_STPCM16U: + case RS_STPCM16D: + { + int iadd_l, iadd_r; + iadd_l = iadd_r = (flags == RS_STPCM16U) ? -0x8000 : 0; + len = sample->length; + short int *psrc = (short int *)buffer; + short int *data = (short int *)sample->data; + if (len*4 > memsize) break; + for (uint32_t j=0; j<len; j++) { + data[j*2] = (short int) (bswapLE16(psrc[0]) + iadd_l); + data[j*2+1] = (short int) (bswapLE16(psrc[len]) + iadd_r); + psrc++; + if (flags == RS_STPCM16D) { + iadd_l = data[j*2]; + iadd_r = data[j*2+1]; + } + } + len *= 4; + } + break; + + // IT 2.14 compressed samples + case RS_IT2148: + case RS_IT21416: + case RS_IT2158: + case RS_IT21516: + len = memsize; + if (len < 2) break; + if (flags == RS_IT2148 || flags == RS_IT2158) { + it_decompress8(sample->data, sample->length, + buffer, memsize, (flags == RS_IT2158), 1); + } else { + it_decompress16(sample->data, sample->length, + buffer, memsize, (flags == RS_IT21516), 1); + } + break; + case RS_IT2148S: + case RS_IT21416S: + case RS_IT2158S: + case RS_IT21516S: + len = memsize; + if (len < 4) break; + if (flags == RS_IT2148S || flags == RS_IT2158S) { + uint32_t offset = it_decompress8(sample->data, sample->length, + buffer, memsize, (flags == RS_IT2158S), 2); + it_decompress8(sample->data + 1, sample->length, + buffer + offset, memsize - offset, (flags == RS_IT2158S), 2); + } else { + uint32_t offset = it_decompress16(sample->data, sample->length, + buffer, memsize, (flags == RS_IT21516S), 2); + it_decompress16(sample->data + 2, sample->length, + buffer + offset, memsize - offset, (flags == RS_IT21516S), 2); + } + break; + + // 8-bit interleaved stereo samples + case RS_STIPCM8S: + case RS_STIPCM8U: + { + int iadd = 0; + if (flags == RS_STIPCM8U) { iadd = -0x80; } + len = sample->length; + if (len*2 > memsize) len = memsize >> 1; + uint8_t * psrc = (uint8_t *)buffer; + uint8_t * data = (uint8_t *)sample->data; + for (uint32_t j=0; j<len; j++) { + data[j*2] = (signed char)(psrc[0] + iadd); + data[j*2+1] = (signed char)(psrc[1] + iadd); + psrc+=2; + } + len *= 2; + } + break; + + // 16-bit interleaved stereo samples + case RS_STIPCM16S: + case RS_STIPCM16U: + { + int iadd = 0; + if (flags == RS_STIPCM16U) iadd = -32768; + len = sample->length; + if (len*4 > memsize) len = memsize >> 2; + short int *psrc = (short int *)buffer; + short int *data = (short int *)sample->data; + for (uint32_t j=0; j<len; j++) { + data[j*2] = (short int)(bswapLE16(psrc[0]) + iadd); + data[j*2+1] = (short int)(bswapLE16(psrc[1]) + iadd); + psrc += 2; + } + len *= 4; + } + break; + +#if 0 + // AMS compressed samples + case RS_AMS8: + case RS_AMS16: + len = 9; + if (memsize > 9) { + const char *psrc = buffer; + char packcharacter = buffer[8], *pdest = (char *)sample->data; + len += bswapLE32(*((uint32_t *)(buffer+4))); + if (len > memsize) len = memsize; + uint32_t dmax = sample->length; + if (sample->flags & CHN_16BIT) dmax <<= 1; + AMSUnpack(psrc+9, len-9, pdest, dmax, packcharacter); + } + break; +#endif + + // PTM 8bit delta to 16-bit sample + case RS_PTM8DTO16: + { + len = sample->length * 2; + if (len > memsize) break; + signed char *data = (signed char *)sample->data; + signed char delta8 = 0; + for (uint32_t j=0; j<len; j++) { + delta8 += buffer[j]; + *data++ = delta8; + } + uint16_t *data16 = (uint16_t *)sample->data; + for (uint32_t j=0; j<len; j+=2) { + *data16 = bswapLE16(*data16); + data16++; + } + } + break; + + // Huffman MDL compressed samples + case RS_MDL8: + case RS_MDL16: + if (memsize >= 8) { + // first 4 bytes indicate packed length + len = bswapLE32(*((uint32_t *) buffer)); + len = MIN(len, memsize) + 4; + uint8_t * data = (uint8_t *)sample->data; + uint8_t * ibuf = (uint8_t *)(buffer + 4); + uint32_t bitbuf = bswapLE32(*((uint32_t *)ibuf)); + uint32_t bitnum = 32; + uint8_t dlt = 0, lowbyte = 0; + ibuf += 4; + // TODO move all this junk to fmt/compression.c + for (uint32_t j=0; j<sample->length; j++) { + uint8_t hibyte; + uint8_t sign; + if (flags == RS_MDL16) + lowbyte = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 8); + sign = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1); + if (mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1)) { + hibyte = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 3); + } else { + hibyte = 8; + while (!mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1)) hibyte += 0x10; + hibyte += mdl_read_bits(&bitbuf, &bitnum, &ibuf, 4); + } + if (sign) hibyte = ~hibyte; + dlt += hibyte; + if (flags == RS_MDL8) { + data[j] = dlt; + } else { +#ifdef WORDS_BIGENDIAN + data[j<<1] = dlt; + data[(j<<1)+1] = lowbyte; +#else + data[j<<1] = lowbyte; + data[(j<<1)+1] = dlt; +#endif + } + } + } + break; + +#if 0 + case RS_DMF8: + case RS_DMF16: + len = memsize; + if (len >= 4) { + uint32_t maxlen = sample->length; + if (sample->flags & CHN_16BIT) maxlen <<= 1; + uint8_t * ibuf = (uint8_t *)buffer; + uint8_t * ibufmax = (uint8_t *)(buffer+memsize); + len = DMFUnpack((uint8_t *)sample->data, ibuf, ibufmax, maxlen); + } + break; +#endif + + // PCM 24-bit signed -> load sample, and normalize it to 16-bit + case RS_PCM24S: + case RS_PCM32S: + len = sample->length * 3; + if (flags == RS_PCM32S) len += sample->length; + if (len > memsize) break; + if (len > 4*8) { + uint32_t slsize = (flags == RS_PCM32S) ? 4 : 3; + uint8_t * src = (uint8_t *)buffer; + int32_t max = 255; + if (flags == RS_PCM32S) src++; + for (uint32_t j=0; j<len; j+=slsize) { + int32_t l = ((((src[j+2] << 8) + src[j+1]) << 8) + src[j]) << 8; + l /= 256; + if (l > max) max = l; + if (-l > max) max = -l; + } + max = (max / 128) + 1; + signed short *dest = (signed short *)sample->data; + for (uint32_t k=0; k<len; k+=slsize) { + int32_t l = ((((src[k+2] << 8) + src[k+1]) << 8) + src[k]) << 8; + *dest++ = (signed short)(l / max); + } + } + break; + + + // Stereo PCM 24-bit signed -> load sample, and normalize it to 16-bit + case RS_STIPCM24S: + case RS_STIPCM32S: + len = sample->length * 6; + if (flags == RS_STIPCM32S) len += sample->length * 2; + if (len > memsize) break; + if (len > 8*8) { + uint32_t slsize = (flags == RS_STIPCM32S) ? 4 : 3; + uint8_t * src = (uint8_t *)buffer; + int32_t max = 255; + if (flags == RS_STIPCM32S) src++; + for (uint32_t j=0; j<len; j+=slsize) { + int32_t l = ((((src[j+2] << 8) + src[j+1]) << 8) + src[j]) << 8; + l /= 256; + if (l > max) max = l; + if (-l > max) max = -l; + } + max = (max / 128) + 1; + signed short *dest = (signed short *)sample->data; + for (uint32_t k=0; k<len; k+=slsize) { + int32_t ll = ((((src[k+2] << 8) + src[k+1]) << 8) + src[k]) << 8; + k += slsize; + int32_t lr = ((((src[k+2] << 8) + src[k+1]) << 8) + src[k]) << 8; + dest[0] = (signed short)(ll/max); + dest[1] = (signed short)(lr/max); + dest += 2; + } + } + break; + + + // 16-bit signed big endian interleaved stereo + case RS_STIPCM16M: + { + len = sample->length; + if (len*4 > memsize) len = memsize >> 2; + const uint8_t * psrc = (const uint8_t *)buffer; + short int *data = (short int *)sample->data; + for (uint32_t j=0; j<len; j++) { + data[j*2] = (signed short)(((uint32_t)psrc[0] << 8) | (psrc[1])); + data[j*2+1] = (signed short)(((uint32_t)psrc[2] << 8) | (psrc[3])); + psrc += 4; + } + len *= 4; + } + break; + + // 7-bit (data shifted one bit left) + case SF(7,M,BE,PCMS): + case SF(7,M,LE,PCMS): + sample->flags &= ~(CHN_16BIT | CHN_STEREO); + len = sample->length = MIN(sample->length, memsize); + for (uint32_t j = 0; j < len; j++) + sample->data[j] = CLAMP(buffer[j] * 2, -128, 127); + break; + + // Default: 8-bit signed PCM data + default: + printf("DEFAULT: %d\n", flags); + case SF(8,M,BE,PCMS): /* endianness is irrelevant for 8-bit samples */ + case SF(8,M,LE,PCMS): + sample->flags &= ~(CHN_16BIT | CHN_STEREO); + len = sample->length; + if (len > memsize) len = sample->length = memsize; + memcpy(sample->data, buffer, len); + break; + } + if (len > memsize) { + if (sample->data) { + sample->length = 0; + csf_free_sample(sample->data); + sample->data = NULL; + } + return 0; + } + csf_adjust_sample_loop(sample); + return len; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void csf_adjust_sample_loop(song_sample_t *sample) +{ + if (!sample->data) return; + if (sample->loop_end > sample->length) sample->loop_end = sample->length; + if (sample->loop_start+2 >= sample->loop_end) { + sample->loop_start = sample->loop_end = 0; + sample->flags &= ~CHN_LOOP; + } + + // poopy, removing all that loop-hacking code has produced... very nasty sounding loops! + // so I guess I should rewrite the crap at the end of the sample at least. + uint32_t len = sample->length; + if (sample->flags & CHN_16BIT) { + short int *data = (short int *)sample->data; + // Adjust end of sample + if (sample->flags & CHN_STEREO) { + data[len*2+6] + = data[len*2+4] + = data[len*2+2] + = data[len*2] + = data[len*2-2]; + data[len*2+7] + = data[len*2+5] + = data[len*2+3] + = data[len*2+1] + = data[len*2-1]; + } else { + data[len+4] + = data[len+3] + = data[len+2] + = data[len+1] + = data[len] + = data[len-1]; + } + } else { + signed char *data = sample->data; + // Adjust end of sample + if (sample->flags & CHN_STEREO) { + data[len*2+6] + = data[len*2+4] + = data[len*2+2] + = data[len*2] + = data[len*2-2]; + data[len*2+7] + = data[len*2+5] + = data[len*2+3] + = data[len*2+1] + = data[len*2-1]; + } else { + data[len+4] + = data[len+3] + = data[len+2] + = data[len+1] + = data[len] + = data[len-1]; + } + } +} + + +void csf_import_s3m_effect(song_note_t *m, int from_it) +{ + uint32_t effect = m->effect; + uint32_t param = m->param; + switch (effect + 0x40) + { + case 'A': effect = FX_SPEED; break; + case 'B': effect = FX_POSITIONJUMP; break; + case 'C': + effect = FX_PATTERNBREAK; + if (!from_it) + param = (param >> 4) * 10 + (param & 0x0F); + break; + case 'D': effect = FX_VOLUMESLIDE; break; + case 'E': effect = FX_PORTAMENTODOWN; break; + case 'F': effect = FX_PORTAMENTOUP; break; + case 'G': effect = FX_TONEPORTAMENTO; break; + case 'H': effect = FX_VIBRATO; break; + case 'I': effect = FX_TREMOR; break; + case 'J': effect = FX_ARPEGGIO; break; + case 'K': effect = FX_VIBRATOVOL; break; + case 'L': effect = FX_TONEPORTAVOL; break; + case 'M': effect = FX_CHANNELVOLUME; break; + case 'N': effect = FX_CHANNELVOLSLIDE; break; + case 'O': effect = FX_OFFSET; break; + case 'P': effect = FX_PANNINGSLIDE; break; + case 'Q': effect = FX_RETRIG; break; + case 'R': effect = FX_TREMOLO; break; + case 'S': + effect = FX_SPECIAL; + // convert old SAx to S8x + if (!from_it && ((param & 0xf0) == 0xa0)) + param = 0x80 | ((param & 0xf) ^ 8); + break; + case 'T': effect = FX_TEMPO; break; + case 'U': effect = FX_FINEVIBRATO; break; + case 'V': + effect = FX_GLOBALVOLUME; + if (!from_it) + param *= 2; + break; + case 'W': effect = FX_GLOBALVOLSLIDE; break; + case 'X': + effect = FX_PANNING; + if (!from_it) { + if (param == 0xa4) { + effect = FX_SPECIAL; + param = 0x91; + } else if (param > 0x7f) { + param = 0xff; + } else { + param *= 2; + } + } + break; + case 'Y': effect = FX_PANBRELLO; break; + case 'Z': effect = FX_MIDI; break; + default: effect = 0; + } + m->effect = effect; + m->param = param; +} diff --git a/src/player/effects.c b/src/player/effects.c new file mode 100644 index 0000000..93dc299 --- /dev/null +++ b/src/player/effects.c @@ -0,0 +1,2070 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "sndfile.h" + +#include "cmixer.h" +#include "snd_fm.h" +#include "tables.h" + +#include "util.h" /* for clamp/min */ + +#include <math.h> + + +// see also csf_midi_out_note in sndmix.c +void (*csf_midi_out_raw)(const unsigned char *,unsigned int, unsigned int) = NULL; + +/* --------------------------------------------------------------------------------------------------------- */ +/* note/freq/period conversion functions */ + +int get_note_from_period(int period) +{ + int n; + if (!period) + return 0; + for (n = 0; n <= 120; n++) { + /* Essentially, this is just doing a note_to_period(n, 8363), but with less + computation since there's no c5speed to deal with. */ + if (period >= (32 * period_table[n % 12] >> (n / 12))) + return n + 1; + } + return 120; +} + +int get_period_from_note(int note, unsigned int c5speed, int linear) +{ + if (!note || note > 0xF0) + return 0; + note--; + if (linear) + return _muldiv(c5speed, linear_slide_up_table[(note % 12) * 16] << (note / 12), 65536 << 5); + else if (!c5speed) + return INT_MAX; + else + return _muldiv(8363, (period_table[note % 12] << 5), c5speed << (note / 12)); +} + + +unsigned int transpose_to_frequency(int transp, int ftune) +{ + return (unsigned int) (8363.0 * pow(2, (transp * 128.0 + ftune) / 1536.0)); +} + +int frequency_to_transpose(unsigned int freq) +{ + return (int) (1536.0 * (log(freq / 8363.0) / log(2))); +} + + +unsigned long calc_halftone(unsigned long hz, int rel) +{ + return pow(2, rel / 12.0) * hz + 0.5; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* the full content of snd_fx.cpp follows. */ + + +//////////////////////////////////////////////////////////// +// Channels effects + +void fx_note_cut(song_t *csf, uint32_t nchan, int clear_note) +{ + song_voice_t *chan = &csf->voices[nchan]; + // stop the current note: + chan->flags |= CHN_FASTVOLRAMP; + chan->length = 0; + if (clear_note) { + // keep instrument numbers from picking up old notes + // (SCx doesn't do this) + chan->period = 0; + } + if (chan->flags & CHN_ADLIB) { + //Do this only if really an adlib chan. Important! + OPL_NoteOff(nchan); + OPL_Touch(nchan, NULL, 0); + } +} + +void fx_key_off(song_t *csf, uint32_t nchan) +{ + song_voice_t *chan = &csf->voices[nchan]; + + /*fprintf(stderr, "KeyOff[%d] [ch%u]: flags=0x%X\n", + tick_count, (unsigned)nchan, chan->flags);*/ + if (chan->flags & CHN_ADLIB) { + //Do this only if really an adlib chan. Important! + OPL_NoteOff(nchan); + } + + song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; + + /*if ((chan->flags & CHN_ADLIB) + || (penv && penv->midi_channel_mask)) + { + // When in AdLib / MIDI mode, end the sample + chan->flags |= CHN_FASTVOLRAMP; + chan->length = 0; + chan->position = 0; + return; + }*/ + + chan->flags |= CHN_KEYOFF; + //if ((!chan->ptr_instrument) || (!(chan->flags & CHN_VOLENV))) + if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && !(chan->flags & CHN_VOLENV)) { + chan->flags |= CHN_NOTEFADE; + } + if (!chan->length) + return; + if ((chan->flags & CHN_SUSTAINLOOP) && chan->ptr_sample) { + song_sample_t *psmp = chan->ptr_sample; + if (psmp->flags & CHN_LOOP) { + if (psmp->flags & CHN_PINGPONGLOOP) + chan->flags |= CHN_PINGPONGLOOP; + else + chan->flags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); + chan->flags |= CHN_LOOP; + chan->length = psmp->length; + chan->loop_start = psmp->loop_start; + chan->loop_end = psmp->loop_end; + if (chan->length > chan->loop_end) chan->length = chan->loop_end; + if (chan->position >= chan->length) + chan->position = chan->position - chan->length + chan->loop_start; + } else { + chan->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); + chan->length = psmp->length; + } + } + if (penv && penv->fadeout && (penv->flags & ENV_VOLLOOP)) + chan->flags |= CHN_NOTEFADE; +} + + +// negative value for slide = up, positive = down +static void fx_do_freq_slide(uint32_t flags, song_voice_t *chan, int32_t slide) +{ + // IT Linear slides + if (!chan->period) return; + if (flags & SONG_LINEARSLIDES) { + int32_t old_period = chan->period; + if (slide < 0) { + uint32_t n = (-slide) >> 2; + if (n > 255) + n = 255; + chan->period = _muldivr(chan->period, linear_slide_up_table[n], 65536); + if (old_period == chan->period) + chan->period++; + } else { + uint32_t n = (slide) >> 2; + if (n > 255) + n = 255; + chan->period = _muldivr(chan->period, linear_slide_down_table[n], 65536); + if (old_period == chan->period) + chan->period--; + } + } else { + chan->period += slide; + } +} + +static void fx_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if ((flags & SONG_FIRSTTICK) && chan->period && param) { + if (flags & SONG_LINEARSLIDES) { + chan->period = _muldivr(chan->period, linear_slide_up_table[param & 0x0F], 65536); + } else { + chan->period -= (int)(param * 4); + } + } +} + +static void fx_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if ((flags & SONG_FIRSTTICK) && chan->period && param) { + if (flags & SONG_LINEARSLIDES) { + chan->period = _muldivr(chan->period, linear_slide_down_table[param & 0x0F], 65536); + } else { + chan->period += (int)(param * 4); + } + } +} + +static void fx_extra_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if ((flags & SONG_FIRSTTICK) && chan->period && param) { + if (flags & SONG_LINEARSLIDES) { + chan->period = _muldivr(chan->period, fine_linear_slide_up_table[param & 0x0F], 65536); + } else { + chan->period -= (int)(param); + } + } +} + +static void fx_extra_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if ((flags & SONG_FIRSTTICK) && chan->period && param) { + if (flags & SONG_LINEARSLIDES) { + chan->period = _muldivr(chan->period, fine_linear_slide_down_table[param & 0x0F], 65536); + } else { + chan->period += (int)(param); + } + } +} + +static void fx_reg_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if (!(flags & SONG_FIRSTTICK)) + fx_do_freq_slide(flags, chan, -(int)(param * 4)); +} + +static void fx_reg_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if (!(flags & SONG_FIRSTTICK)) + fx_do_freq_slide(flags, chan, (int)(param * 4)); +} + + +static void fx_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if (!param) + param = chan->mem_pitchslide; + + switch (param & 0xf0) { + case 0xe0: + fx_extra_fine_portamento_up(flags, chan, param & 0x0F); + break; + case 0xf0: + fx_fine_portamento_up(flags, chan, param & 0x0F); + break; + default: + fx_reg_portamento_up(flags, chan, param); + break; + } +} + +static void fx_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + if (!param) + param = chan->mem_pitchslide; + + switch (param & 0xf0) { + case 0xe0: + fx_extra_fine_portamento_down(flags, chan, param & 0x0F); + break; + case 0xf0: + fx_fine_portamento_down(flags, chan, param & 0x0F); + break; + default: + fx_reg_portamento_down(flags, chan, param); + break; + } +} + +static void fx_tone_portamento(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + int delta; + + if (!param) + param = chan->mem_portanote; + + chan->flags |= CHN_PORTAMENTO; + if (chan->period && chan->portamento_target && !(flags & SONG_FIRSTTICK)) { + if (chan->period < chan->portamento_target) { + if (flags & SONG_LINEARSLIDES) { + uint32_t n = MIN(255, param); + delta = _muldivr(chan->period, linear_slide_up_table[n], 65536) - chan->period; + if (delta < 1) delta = 1; + } else { + delta = param * 4; + } + chan->period += delta; + if (chan->period > chan->portamento_target) { + chan->period = chan->portamento_target; + chan->portamento_target = 0; + } + } else if (chan->period > chan->portamento_target) { + if (flags & SONG_LINEARSLIDES) { + uint32_t n = MIN(255, param); + delta = _muldivr(chan->period, linear_slide_down_table[n], 65536) - chan->period; + if (delta > -1) delta = -1; + } else { + delta = -param * 4; + } + chan->period += delta; + if (chan->period < chan->portamento_target) { + chan->period = chan->portamento_target; + chan->portamento_target = 0; + } + } + } +} + +// Implemented for IMF compatibility, can't actually save this in any formats +// sign should be 1 (up) or -1 (down) +static void fx_note_slide(uint32_t flags, song_voice_t *chan, uint32_t param, int sign) +{ + uint8_t x, y; + if (flags & SONG_FIRSTTICK) { + x = param & 0xf0; + if (x) + chan->note_slide_speed = (x >> 4); + y = param & 0xf; + if (y) + chan->note_slide_step = y; + chan->note_slide_counter = chan->note_slide_speed; + } else { + if (--chan->note_slide_counter == 0) { + chan->note_slide_counter = chan->note_slide_speed; + // update it + chan->period = get_period_from_note + (sign * chan->note_slide_step + get_note_from_period(chan->period), + 8363, 0); + } + } +} + + + +static void fx_vibrato(song_voice_t *p, uint32_t param) +{ + if (param & 0x0F) + p->vibrato_depth = (param & 0x0F) * 4; + if (param & 0xF0) + p->vibrato_speed = (param >> 4) & 0x0F; + p->flags |= CHN_VIBRATO; +} + +static void fx_fine_vibrato(song_voice_t *p, uint32_t param) +{ + if (param & 0x0F) + p->vibrato_depth = param & 0x0F; + if (param & 0xF0) + p->vibrato_speed = (param >> 4) & 0x0F; + p->flags |= CHN_VIBRATO; +} + + +static void fx_panbrello(song_voice_t *chan, uint32_t param) +{ + unsigned int panpos = chan->panbrello_position & 0xFF; + int pdelta; + + if (param & 0x0F) + chan->panbrello_depth = param & 0x0F; + if (param & 0xF0) + chan->panbrello_speed = (param >> 4) & 0x0F; + + switch (chan->panbrello_type) { + case VIB_SINE: + default: + pdelta = sine_table[panpos]; + break; + case VIB_RAMP_DOWN: + pdelta = ramp_down_table[panpos]; + break; + case VIB_SQUARE: + pdelta = square_table[panpos]; + break; + case VIB_RANDOM: + pdelta = 128 * ((double) rand() / RAND_MAX) - 64; + break; + } + + chan->panbrello_position += chan->panbrello_speed; + pdelta = ((pdelta * (int)chan->panbrello_depth) + 2) >> 3; + chan->panbrello_delta = pdelta; +} + + +static void fx_volume_up(song_voice_t *chan, uint32_t param) +{ + chan->volume += param * 4; + if (chan->volume > 256) + chan->volume = 256; +} + +static void fx_volume_down(song_voice_t *chan, uint32_t param) +{ + chan->volume -= param * 4; + if (chan->volume < 0) + chan->volume = 0; +} + +static void fx_volume_slide(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + // Dxx Volume slide down + // + // if (xx == 0) then xx = last xx for (Dxx/Kxx/Lxx) for this channel. + if (param) + chan->mem_volslide = param; + else + param = chan->mem_volslide; + + // Order of testing: Dx0, D0x, DxF, DFx + if (param == (param & 0xf0)) { + // Dx0 Set effect update for channel enabled if channel is ON. + // If x = F, then slide up volume by 15 straight away also (for S3M compat) + // Every update, add x to the volume, check and clip values > 64 to 64 + param >>= 4; + if (param == 0xf || !(flags & SONG_FIRSTTICK)) + fx_volume_up(chan, param); + } else if (param == (param & 0xf)) { + // D0x Set effect update for channel enabled if channel is ON. + // If x = F, then slide down volume by 15 straight away also (for S3M) + // Every update, subtract x from the volume, check and clip values < 0 to 0 + if (param == 0xf || !(flags & SONG_FIRSTTICK)) + fx_volume_down(chan, param); + } else if ((param & 0xf) == 0xf) { + // DxF Add x to volume straight away. Check and clip values > 64 to 64 + param >>= 4; + if (flags & SONG_FIRSTTICK) + fx_volume_up(chan, param); + } else if ((param & 0xf0) == 0xf0) { + // DFx Subtract x from volume straight away. Check and clip values < 0 to 0 + param &= 0xf; + if (flags & SONG_FIRSTTICK) + fx_volume_down(chan, param); + } +} + + +static void fx_panning_slide(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + int32_t slide = 0; + if (param) + chan->mem_panslide = param; + else + param = chan->mem_panslide; + if ((param & 0x0F) == 0x0F && (param & 0xF0)) { + if (flags & SONG_FIRSTTICK) { + param = (param & 0xF0) >> 2; + slide = - (int)param; + } + } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { + if (flags & SONG_FIRSTTICK) { + slide = (param & 0x0F) << 2; + } + } else { + if (!(flags & SONG_FIRSTTICK)) { + if (param & 0x0F) + slide = (int)((param & 0x0F) << 2); + else + slide = -(int)((param & 0xF0) >> 2); + } + } + if (slide) { + slide += chan->panning; + chan->panning = CLAMP(slide, 0, 256); + } + chan->flags &= ~CHN_SURROUND; + chan->panbrello_delta = 0; +} + + +static void fx_tremolo(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + unsigned int trempos = chan->tremolo_position & 0xFF; + int tdelta; + + if (param & 0x0F) + chan->tremolo_depth = (param & 0x0F) << 2; + if (param & 0xF0) + chan->tremolo_speed = (param >> 4) & 0x0F; + + chan->flags |= CHN_TREMOLO; + + // don't handle on first tick if old-effects mode + if ((flags & SONG_FIRSTTICK) && (flags & SONG_ITOLDEFFECTS)) + return; + + switch (chan->tremolo_type) { + case VIB_SINE: + default: + tdelta = sine_table[trempos]; + break; + case VIB_RAMP_DOWN: + tdelta = ramp_down_table[trempos]; + break; + case VIB_SQUARE: + tdelta = square_table[trempos]; + break; + case VIB_RANDOM: + tdelta = 128 * ((double) rand() / RAND_MAX) - 64; + break; + } + + chan->tremolo_position = (trempos + 4 * chan->tremolo_speed) & 0xFF; + tdelta = (tdelta * (int)chan->tremolo_depth) >> 5; + chan->tremolo_delta = tdelta; +} + + +static void fx_retrig_note(song_t *csf, uint32_t nchan, uint32_t param) +{ + song_voice_t *chan = &csf->voices[nchan]; + + //printf("Q%02X note=%02X tick%d %d\n", param, chan->row_note, tick_count, chan->cd_retrig); + if ((csf->flags & SONG_FIRSTTICK) && chan->row_note != NOTE_NONE) { + chan->cd_retrig = param & 0xf; + } else if (--chan->cd_retrig <= 0) { + + // in Impulse Tracker, retrig only works if a sample is currently playing in the channel + if (chan->position == 0) + return; + + chan->cd_retrig = param & 0xf; + param >>= 4; + if (param) { + int vol = chan->volume; + if (retrig_table_1[param]) + vol = (vol * retrig_table_1[param]) >> 4; + else + vol += (retrig_table_2[param]) << 2; + chan->volume = CLAMP(vol, 0, 256); + chan->flags |= CHN_FASTVOLRAMP; + } + + uint32_t note = chan->new_note; + int32_t period = chan->period; + if (NOTE_IS_NOTE(note) && chan->length) + csf_check_nna(csf, nchan, 0, note, 1); + csf_note_change(csf, nchan, note, 1, 1, 0); + if (period && chan->row_note == NOTE_NONE) + chan->period = period; + chan->position = chan->position_frac = 0; + } +} + + +static void fx_channel_vol_slide(uint32_t flags, song_voice_t *chan, uint32_t param) +{ + int32_t slide = 0; + if (param) + chan->mem_channel_volslide = param; + else + param = chan->mem_channel_volslide; + if ((param & 0x0F) == 0x0F && (param & 0xF0)) { + if (flags & SONG_FIRSTTICK) + slide = param >> 4; + } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { + if (flags & SONG_FIRSTTICK) + slide = - (int)(param & 0x0F); + } else { + if (!(flags & SONG_FIRSTTICK)) { + if (param & 0x0F) + slide = -(int)(param & 0x0F); + else + slide = (int)((param & 0xF0) >> 4); + } + } + if (slide) { + slide += chan->global_volume; + chan->global_volume = CLAMP(slide, 0, 64); + } +} + + +static void fx_global_vol_slide(song_t *csf, song_voice_t *chan, uint32_t param) +{ + int32_t slide = 0; + if (param) + chan->mem_global_volslide = param; + else + param = chan->mem_global_volslide; + if ((param & 0x0F) == 0x0F && (param & 0xF0)) { + if (csf->flags & SONG_FIRSTTICK) + slide = param >> 4; + } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { + if (csf->flags & SONG_FIRSTTICK) + slide = -(int)(param & 0x0F); + } else { + if (!(csf->flags & SONG_FIRSTTICK)) { + if (param & 0xF0) + slide = (int)((param & 0xF0) >> 4); + else + slide = -(int)(param & 0x0F); + } + } + if (slide) { + slide += csf->current_global_volume; + csf->current_global_volume = CLAMP(slide, 0, 128); + } +} + + +static void fx_pattern_loop(song_t *csf, song_voice_t *chan, uint32_t param) +{ + if (param) { + if (chan->cd_patloop) { + if (!--chan->cd_patloop) { + // this should get rid of that nasty infinite loop for cases like + // ... .. .. SB0 + // ... .. .. SB1 + // ... .. .. SB1 + // it still doesn't work right in a few strange cases, but oh well :P + chan->patloop_row = csf->row + 1; + return; // don't loop! + } + } else { + chan->cd_patloop = param; + } + csf->process_row = chan->patloop_row - 1; + } else { + chan->patloop_row = csf->row; + } +} + + +static void fx_special(song_t *csf, uint32_t nchan, uint32_t param) +{ + song_voice_t *chan = &csf->voices[nchan]; + uint32_t command = param & 0xF0; + param &= 0x0F; + switch(command) { + // S0x: Set Filter + // S1x: Set Glissando Control + case 0x10: + chan->flags &= ~CHN_GLISSANDO; + if (param) chan->flags |= CHN_GLISSANDO; + break; + // S2x: Set FineTune (no longer implemented) + // S3x: Set Vibrato WaveForm + case 0x30: + chan->vib_type = param; + break; + // S4x: Set Tremolo WaveForm + case 0x40: + chan->tremolo_type = param; + break; + // S5x: Set Panbrello WaveForm + case 0x50: + chan->panbrello_type = param; + break; + // S6x: Pattern Delay for x ticks + case 0x60: + if (csf->flags & SONG_FIRSTTICK) + csf->tick_count += param; + break; + // S7x: Envelope Control + case 0x70: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + switch(param) { + case 0: + case 1: + case 2: + { + song_voice_t *bkp = &csf->voices[MAX_CHANNELS]; + for (uint32_t i=MAX_CHANNELS; i<MAX_VOICES; i++, bkp++) { + if (bkp->master_channel == nchan+1) { + if (param == 1) { + fx_key_off(csf, i); + } else if (param == 2) { + bkp->flags |= CHN_NOTEFADE; + } else { + bkp->flags |= CHN_NOTEFADE; + bkp->fadeout_volume = 0; + } + } + } + } + break; + case 3: chan->nna = NNA_NOTECUT; break; + case 4: chan->nna = NNA_CONTINUE; break; + case 5: chan->nna = NNA_NOTEOFF; break; + case 6: chan->nna = NNA_NOTEFADE; break; + case 7: chan->flags &= ~CHN_VOLENV; break; + case 8: chan->flags |= CHN_VOLENV; break; + case 9: chan->flags &= ~CHN_PANENV; break; + case 10: chan->flags |= CHN_PANENV; break; + case 11: chan->flags &= ~CHN_PITCHENV; break; + case 12: chan->flags |= CHN_PITCHENV; break; + } + break; + // S8x: Set 4-bit Panning + case 0x80: + if (csf->flags & SONG_FIRSTTICK) { + chan->flags &= ~CHN_SURROUND; + chan->panbrello_delta = 0; + chan->panning = (param << 4) + 8; + chan->flags |= CHN_FASTVOLRAMP; + chan->pan_swing = 0; + } + break; + // S9x: Set Surround + case 0x90: + if (param == 1 && (csf->flags & SONG_FIRSTTICK)) { + chan->flags |= CHN_SURROUND; + chan->panbrello_delta = 0; + chan->panning = 128; + } + break; + // SAx: Set 64k Offset + // Note: don't actually APPLY the offset, and don't clear the regular offset value, either. + case 0xA0: + if (csf->flags & SONG_FIRSTTICK) { + chan->mem_offset = (param << 16) | (chan->mem_offset & ~0xf0000); + } + break; + // SBx: Pattern Loop + case 0xB0: + if (csf->flags & SONG_FIRSTTICK) + fx_pattern_loop(csf, chan, param & 0x0F); + break; + // SCx: Note Cut + case 0xC0: + if (csf->flags & SONG_FIRSTTICK) + chan->cd_note_cut = param ?: 1; + else if (--chan->cd_note_cut == 0) + fx_note_cut(csf, nchan, 0); + break; + // SDx: Note Delay + // SEx: Pattern Delay for x rows + case 0xE0: + if (csf->flags & SONG_FIRSTTICK) { + if (!csf->row_count) // ugh! + csf->row_count = param + 1; + } + break; + // SFx: Set Active Midi Macro + case 0xF0: + chan->active_macro = param; + break; + } +} + + +// this is all brisby +void csf_midi_send(song_t *csf, const unsigned char *data, unsigned int len, uint32_t nchan, int fake) +{ + song_voice_t *chan = &csf->voices[nchan]; + int oldcutoff; + const unsigned char *idata = data; + unsigned int ilen = len; + + while (ilen > 4 && idata[0] == 0xF0 && idata[1] == 0xF0) { + // impulse tracker filter control (mfg. 0xF0) + switch (idata[2]) { + case 0x00: // set cutoff + oldcutoff = chan->cutoff; + if (idata[3] < 0x80) + chan->cutoff = idata[3]; + oldcutoff -= chan->cutoff; + if (oldcutoff < 0) + oldcutoff = -oldcutoff; + if (chan->volume > 0 || oldcutoff < 0x10 + || !(chan->flags & CHN_FILTER) + || !(chan->left_volume|chan->right_volume)) { + setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency); + } + break; + case 0x01: // set resonance + if (idata[3] < 0x80) + chan->resonance = idata[3]; + setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency); + break; + } + idata += 4; + ilen -= 4; + } + + if (!fake && csf_midi_out_raw) { + /* okay, this is kind of how it works. + we pass buffer_count as here because while + 1000 * ((8((buffer_size/2) - buffer_count)) / sample_rate) + is the number of msec we need to delay by, libmodplug simply doesn't know + what the buffer size is at this point so buffer_count simply has no + frame of reference. + + fortunately, schism does and can complete this (tags: _schism_midi_out_raw ) + + */ + csf_midi_out_raw(data, len, csf->buffer_count); + } +} + + +static int _was_complete_midi(unsigned char *q, unsigned int len, int nextc) +{ + if (len == 0) return 0; + if (*q == 0xF0) return (q[len-1] == 0xF7 ? 1 : 0); + return ((nextc & 0x80) ? 1 : 0); +} + +void csf_process_midi_macro(song_t *csf, uint32_t nchan, const char * macro, uint32_t param, + uint32_t note, uint32_t velocity, uint32_t use_instr) +{ +/* this was all wrong. -mrsb */ + song_voice_t *chan = &csf->voices[nchan]; + song_instrument_t *penv = ((csf->flags & SONG_INSTRUMENTMODE) + && chan->last_instrument < MAX_INSTRUMENTS) + ? csf->instruments[use_instr ?: chan->last_instrument] + : NULL; + unsigned char outbuffer[64]; + unsigned char cx; + int mc, fake = 0; + int saw_c; + int i, j, x; + + saw_c = 0; + if (!penv || penv->midi_channel_mask == 0) { + /* okay, there _IS_ no real midi channel. forget this for now... */ + mc = 15; + fake = 1; + + } else if (penv->midi_channel_mask >= 0x10000) { + mc = (nchan-1) % 16; + } else { + mc = 0; + while(!(penv->midi_channel_mask & (1 << mc))) ++mc; + } + + for (i = j = x = 0, cx =0; i <= 32 && macro[i]; i++) { + int c, cw; + if (macro[i] >= '0' && macro[i] <= '9') { + c = macro[i] - '0'; + cw = 1; + } else if (macro[i] >= 'A' && macro[i] <= 'F') { + c = (macro[i] - 'A') + 10; + cw = 1; + } else if (macro[i] == 'c') { + c = mc; + cw = 1; + saw_c = 1; + } else if (macro[i] == 'n') { + c = (note-1); + cw = 2; + } else if (macro[i] == 'v') { + c = velocity; + cw = 2; + } else if (macro[i] == 'u') { + c = (chan->volume >> 1); + if (c > 127) c = 127; + cw = 2; + } else if (macro[i] == 'x') { + c = chan->panning; + if (c > 127) c = 127; + cw = 2; + } else if (macro[i] == 'y') { + c = chan->final_panning; + if (c > 127) c = 127; + cw = 2; + } else if (macro[i] == 'a') { + if (!penv) + c = 0; + else + c = (penv->midi_bank >> 7) & 127; + cw = 2; + } else if (macro[i] == 'b') { + if (!penv) + c = 0; + else + c = penv->midi_bank & 127; + cw = 2; + } else if (macro[i] == 'z' || macro[i] == 'p') { + c = param & 0x7F; + cw = 2; + } else { + continue; + } + if (j == 0 && cw == 1) { + cx = c; + j = 1; + continue; + } else if (j == 1 && cw == 1) { + cx = (cx << 4) | c; + j = 0; + } else if (j == 0) { + cx = c; + } else if (j == 1) { + outbuffer[x] = cx; + x++; + + cx = c; + j = 0; + } + // start of midi message + if (_was_complete_midi(outbuffer, x, cx)) { + csf_midi_send(csf, outbuffer, x, nchan, saw_c && fake); + x = 0; + } + outbuffer[x] = cx; + x++; + } + if (j == 1) { + outbuffer[x] = cx; + x++; + } + if (x) { + // terminate sysex + if (!_was_complete_midi(outbuffer, x, 0xFF)) { + if (*outbuffer == 0xF0) { + outbuffer[x] = 0xF7; + x++; + } + } + csf_midi_send(csf, outbuffer, x, nchan, saw_c && fake); + } +} + + +//////////////////////////////////////////////////////////// +// Length + +#if MAX_CHANNELS != 64 +# error csf_get_length assumes 64 channels +#endif + +unsigned int csf_get_length(song_t *csf) +{ + uint32_t elapsed = 0, row = 0, next_row = 0, cur_order = 0, next_order = 0, pat = csf->orderlist[0], + speed = csf->initial_speed, tempo = csf->initial_tempo, psize, n; + uint32_t patloop[MAX_CHANNELS] = {0}; + uint8_t mem_tempo[MAX_CHANNELS] = {0}; + uint64_t setloop = 0; // bitmask + const song_note_t *pdata; + + for (;;) { + uint32_t speed_count = 0; + row = next_row; + cur_order = next_order; + + // Check if pattern is valid + pat = csf->orderlist[cur_order]; + while (pat >= MAX_PATTERNS) { + // End of song ? + if (pat == ORDER_LAST || cur_order >= MAX_ORDERS) { + pat = ORDER_LAST; // cause break from outer loop too + break; + } else { + cur_order++; + pat = (cur_order < MAX_ORDERS) ? csf->orderlist[cur_order] : ORDER_LAST; + } + next_order = cur_order; + } + // Weird stuff? + if (pat >= MAX_PATTERNS) + break; + pdata = csf->patterns[pat]; + if (pdata) { + psize = csf->pattern_size[pat]; + } else { + pdata = blank_pattern; + psize = 64; + } + // guard against Cxx to invalid row, etc. + if (row >= psize) + row = 0; + // Update next position + next_row = row + 1; + if (next_row >= psize) { + next_order = cur_order + 1; + next_row = 0; + } + + /* muahahaha */ + if (csf->stop_at_order > -1 && csf->stop_at_row > -1) { + if (csf->stop_at_order <= (signed) cur_order && csf->stop_at_row <= (signed) row) + break; + if (csf->stop_at_time > 0) { + /* stupid api decision */ + if (((elapsed + 500) / 1000) >= csf->stop_at_time) { + csf->stop_at_order = cur_order; + csf->stop_at_row = row; + break; + } + } + } + + /* This is nasty, but it fixes inaccuracies with SB0 SB1 SB1. (Simultaneous + loops in multiple channels are still wildly incorrect, though.) */ + if (!row) + setloop = ~0; + if (setloop) { + for (n = 0; n < MAX_CHANNELS; n++) + if (setloop & (1 << n)) + patloop[n] = elapsed; + setloop = 0; + } + const song_note_t *note = pdata + row * MAX_CHANNELS; + for (n = 0; n < MAX_CHANNELS; note++, n++) { + uint32_t param = note->param; + switch (note->effect) { + case FX_NONE: + break; + case FX_POSITIONJUMP: + next_order = param > cur_order ? param : cur_order + 1; + next_row = 0; + break; + case FX_PATTERNBREAK: + next_order = cur_order + 1; + next_row = param; + break; + case FX_SPEED: + if (param) + speed = param; + break; + case FX_TEMPO: + if (param) + mem_tempo[n] = param; + else + param = mem_tempo[n]; + int d = (param & 0xf); + switch (param >> 4) { + default: + tempo = param; + break; + case 0: + d = -d; + case 1: + d = d * (speed - 1) + tempo; + tempo = CLAMP(d, 32, 255); + break; + } + break; + case FX_SPECIAL: + switch (param >> 4) { + case 0x6: + speed_count = param & 0x0F; + break; + case 0xb: + if (param & 0x0F) { + elapsed += (elapsed - patloop[n]) * (param & 0x0F); + patloop[n] = 0xffffffff; + setloop = 1; + } else { + patloop[n] = elapsed; + } + break; + case 0xe: + speed_count = (param & 0x0F) * speed; + break; + } + break; + } + } + // sec/tick = 5 / (2 * tempo) + // msec/tick = 5000 / (2 * tempo) + // = 2500 / tempo + elapsed += (speed + speed_count) * 2500 / tempo; + } + + return (elapsed + 500) / 1000; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Effects + +song_sample_t *csf_translate_keyboard(song_t *csf, song_instrument_t *penv, uint32_t note, song_sample_t *def) +{ + uint32_t n = penv->sample_map[note - 1]; + return (n && n < MAX_SAMPLES) ? &csf->samples[n] : def; +} + +static void env_reset(song_voice_t *chan, int always) +{ + if (chan->ptr_instrument) { + chan->flags |= CHN_FASTVOLRAMP; + if (always) { + chan->vol_env_position = 0; + chan->pan_env_position = 0; + chan->pitch_env_position = 0; + } else { + /* only reset envelopes with carry off */ + if (!(chan->ptr_instrument->flags & ENV_VOLCARRY)) + chan->vol_env_position = 0; + if (!(chan->ptr_instrument->flags & ENV_PANCARRY)) + chan->pan_env_position = 0; + if (!(chan->ptr_instrument->flags & ENV_PITCHCARRY)) + chan->pitch_env_position = 0; + } + } + + // this was migrated from csf_note_change, should it be here? + chan->flags &= ~CHN_NOTEFADE; + chan->fadeout_volume = 65536; +} + +void csf_instrument_change(song_t *csf, song_voice_t *chan, uint32_t instr, int porta, int inst_column) +{ + int inst_changed = 0; + + if (instr >= MAX_INSTRUMENTS) return; + song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL; + song_sample_t *psmp = chan->ptr_sample; + uint32_t note = chan->new_note; + + if (note == NOTE_NONE) { + /* nothing to see here */ + } else if (NOTE_IS_CONTROL(note)) { + /* nothing here either */ + } else if (penv) { + if (NOTE_IS_CONTROL(penv->note_map[note-1])) + return; + if (!(porta && penv == chan->ptr_instrument && chan->ptr_sample && chan->current_sample_data)) + psmp = csf_translate_keyboard(csf, penv, note, NULL); + chan->flags &= ~CHN_SUSTAINLOOP; // turn off sustain + } else { + psmp = csf->samples + instr; + } + + // Update Volume + if (inst_column && psmp) chan->volume = psmp->volume; + // inst_changed is used for IT carry-on env option + if (penv != chan->ptr_instrument || !chan->current_sample_data) { + inst_changed = 1; + chan->ptr_instrument = penv; + } + + // Instrument adjust + chan->new_instrument = 0; + if (psmp) { + psmp->played = 1; + if (penv) { + penv->played = 1; + chan->instrument_volume = (psmp->global_volume * penv->global_volume) >> 7; + if (penv->flags & ENV_SETPANNING) { + chan->panning = penv->panning; + chan->flags &= ~CHN_SURROUND; + } + chan->nna = penv->nna; + } else { + chan->instrument_volume = psmp->global_volume; + } + if (psmp->flags & CHN_PANNING) { + chan->panning = psmp->panning; + chan->flags &= ~CHN_SURROUND; + } + } + + // Reset envelopes + + // Conditions experimentally determined to cause envelope reset in Impulse Tracker: + // - no note currently playing (of course) + // - note given, no portamento + // - instrument number given, portamento, compat gxx enabled + // - instrument number given, no portamento, after keyoff, old effects enabled + // If someone can enlighten me to what the logic really is here, I'd appreciate it. + // Seems like it's just a total mess though, probably to get XMs to play right. + if (penv) { + if (( + !chan->length + ) || ( + inst_column + && porta + && (csf->flags & SONG_COMPATGXX) + ) || ( + inst_column + && !porta + && (chan->flags & (CHN_NOTEFADE|CHN_KEYOFF)) + && (csf->flags & SONG_ITOLDEFFECTS) + )) { + env_reset(chan, inst_changed || (chan->flags & CHN_KEYOFF)); + } else if (!(penv->flags & ENV_VOLUME)) { + // XXX why is this being done? + chan->vol_env_position = 0; + } + + chan->vol_swing = chan->pan_swing = 0; + if (penv->vol_swing) { + /* this was wrong, and then it was still wrong. + (possibly it continues to be wrong even now?) */ + double d = 2 * (((double) rand()) / RAND_MAX) - 1; + // floor() is applied to get exactly the same volume levels as in IT. -- Saga + chan->vol_swing = floor(d * penv->vol_swing / 100.0 * chan->instrument_volume); + } + if (penv->pan_swing) { + /* this was also wrong, and even more so */ + double d = 2 * (((double) rand()) / RAND_MAX) - 1; + chan->pan_swing = d * penv->pan_swing * 4; + } + } + + // Invalid sample ? + if (!psmp) { + chan->ptr_sample = NULL; + chan->instrument_volume = 0; + return; + } + if (psmp == chan->ptr_sample && chan->current_sample_data && chan->length) + return; + + // sample change: reset sample vibrato + chan->autovib_depth = 0; + chan->autovib_position = 0; + + if ((chan->flags & (CHN_KEYOFF | CHN_NOTEFADE)) && inst_column) { + // Don't start new notes after ===/~~~ + chan->period = 0; + } else { + chan->period = get_period_from_note(note, psmp->c5speed, + csf->flags & SONG_LINEARSLIDES); + } + chan->flags &= ~(CHN_SAMPLE_FLAGS | CHN_KEYOFF | CHN_NOTEFADE + | CHN_VOLENV | CHN_PANENV | CHN_PITCHENV); + chan->flags |= psmp->flags & CHN_SAMPLE_FLAGS; + if (penv) { + if (penv->flags & ENV_VOLUME) + chan->flags |= CHN_VOLENV; + if (penv->flags & ENV_PANNING) + chan->flags |= CHN_PANENV; + if (penv->flags & ENV_PITCH) + chan->flags |= CHN_PITCHENV; + if ((penv->flags & ENV_PITCH) && (penv->flags & ENV_FILTER) && !chan->cutoff) + chan->cutoff = 0x7F; + if (penv->ifc & 0x80) + chan->cutoff = penv->ifc & 0x7F; + if (penv->ifr & 0x80) + chan->resonance = penv->ifr & 0x7F; + } + + chan->ptr_sample = psmp; + chan->length = psmp->length; + chan->loop_start = psmp->loop_start; + chan->loop_end = psmp->loop_end; + chan->c5speed = psmp->c5speed; + chan->current_sample_data = psmp->data; + chan->position = 0; + + if (chan->flags & CHN_SUSTAINLOOP) { + chan->loop_start = psmp->sustain_start; + chan->loop_end = psmp->sustain_end; + chan->flags |= CHN_LOOP; + if (chan->flags & CHN_PINGPONGSUSTAIN) + chan->flags |= CHN_PINGPONGLOOP; + } + if ((chan->flags & CHN_LOOP) && chan->loop_end < chan->length) + chan->length = chan->loop_end; + /*fprintf(stderr, "length set as %d (from %d), ch flags %X smp flags %X\n", + (int)chan->length, + (int)psmp->length, chan->flags, psmp->flags);*/ +} + + +// have_inst is a hack to ignore the note-sample map when no instrument number is present +void csf_note_change(song_t *csf, uint32_t nchan, int note, int porta, int retrig, int have_inst) +{ + // why would csf_note_change ever get a negative value for 'note'? + if (note == NOTE_NONE || note < 0) + return; + + // save the note that's actually used, as it's necessary to properly calculate PPS and stuff + // (and also needed for correct display of note dots) + int truenote = note; + + song_voice_t *chan = &csf->voices[nchan]; + song_sample_t *pins = chan->ptr_sample; + song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; + if (penv && NOTE_IS_NOTE(note)) { + if (!(have_inst && porta && pins)) + pins = csf_translate_keyboard(csf, penv, note, pins); + note = penv->note_map[note - 1]; + chan->flags &= ~CHN_SUSTAINLOOP; // turn off sustain + } + + if (NOTE_IS_CONTROL(note)) { + // hax: keep random sample numbers from triggering notes (see csf_instrument_change) + // NOTE_OFF is a completely arbitrary choice - this could be anything above NOTE_LAST + chan->new_note = NOTE_OFF; + switch (note) { + case NOTE_OFF: + fx_key_off(csf, nchan); + break; + case NOTE_CUT: + fx_note_cut(csf, nchan, 1); + break; + case NOTE_FADE: + default: // Impulse Tracker handles all unknown notes as fade internally + chan->flags |= CHN_NOTEFADE; + break; + } + return; + } + + if (!pins) + return; + + note = CLAMP(note, NOTE_FIRST, NOTE_LAST); + chan->note = CLAMP(truenote, NOTE_FIRST, NOTE_LAST); + chan->new_instrument = 0; + uint32_t period = get_period_from_note(note, chan->c5speed, csf->flags & SONG_LINEARSLIDES); + if (period) { + if (porta && chan->period) { + chan->portamento_target = period; + } else { + chan->portamento_target = 0; + chan->period = period; + } + if (!porta || !chan->length) { + chan->ptr_sample = pins; + chan->current_sample_data = pins->data; + chan->length = pins->length; + chan->loop_end = pins->length; + chan->loop_start = 0; + chan->c5speed = pins->c5speed; + chan->flags = (chan->flags & ~CHN_SAMPLE_FLAGS) | (pins->flags & CHN_SAMPLE_FLAGS); + if (chan->flags & CHN_SUSTAINLOOP) { + chan->loop_start = pins->sustain_start; + chan->loop_end = pins->sustain_end; + chan->flags &= ~CHN_PINGPONGLOOP; + chan->flags |= CHN_LOOP; + if (chan->flags & CHN_PINGPONGSUSTAIN) chan->flags |= CHN_PINGPONGLOOP; + if (chan->length > chan->loop_end) chan->length = chan->loop_end; + } else if (chan->flags & CHN_LOOP) { + chan->loop_start = pins->loop_start; + chan->loop_end = pins->loop_end; + if (chan->length > chan->loop_end) chan->length = chan->loop_end; + } + chan->position = chan->position_frac = 0; + } + if (chan->position >= chan->length) + chan->position = chan->loop_start; + } else { + porta = 0; + } + + if (!porta) + env_reset(chan, 0); + + chan->flags &= ~CHN_KEYOFF; + // Enable Ramping + if (!porta) { + chan->vu_meter = 0x0; + chan->strike = 4; /* this affects how long the initial hit on the playback marks lasts (bigger dot in instrument and sample list windows)*/ + chan->flags &= ~CHN_FILTER; + chan->flags |= CHN_FASTVOLRAMP; + if (!retrig) { + chan->autovib_depth = 0; + chan->autovib_position = 0; + chan->vibrato_position = 0; + } + chan->left_volume = chan->right_volume = 0; + // Setup Initial Filter for this note + if (penv) { + if (penv->ifr & 0x80) + chan->resonance = penv->ifr & 0x7F; + if (penv->ifc & 0x80) + chan->cutoff = penv->ifc & 0x7F; + } else { + chan->vol_swing = chan->pan_swing = 0; + } + + if (chan->cutoff < 0x7F) + setup_channel_filter(chan, 1, 256, csf->mix_frequency); + } +} + + +uint32_t csf_get_nna_channel(song_t *csf, uint32_t nchan) +{ + song_voice_t *chan = &csf->voices[nchan]; + // Check for empty channel + song_voice_t *pi = &csf->voices[MAX_CHANNELS]; + for (uint32_t i=MAX_CHANNELS; i<MAX_VOICES; i++, pi++) { + if (!pi->length) { + if (pi->flags & CHN_MUTE) { + if (pi->flags & CHN_NNAMUTE) { + pi->flags &= ~(CHN_NNAMUTE|CHN_MUTE); + } else { + /* this channel is muted; skip */ + continue; + } + } + return i; + } + } + if (!chan->fadeout_volume) return 0; + // All channels are used: check for lowest volume + uint32_t result = 0; + uint32_t vol = 64*65536; // 25% + int envpos = 0xFFFFFF; + const song_voice_t *pj = &csf->voices[MAX_CHANNELS]; + for (uint32_t j=MAX_CHANNELS; j<MAX_VOICES; j++, pj++) { + if (!pj->fadeout_volume) return j; + uint32_t v = pj->volume; + if (pj->flags & CHN_NOTEFADE) + v = v * pj->fadeout_volume; + else + v <<= 16; + if (pj->flags & CHN_LOOP) v >>= 1; + if (v < vol || (v == vol && pj->vol_env_position > envpos)) { + envpos = pj->vol_env_position; + vol = v; + result = j; + } + } + if (result) { + /* unmute new nna channel */ + csf->voices[result].flags &= ~(CHN_MUTE|CHN_NNAMUTE); + } + return result; +} + + +void csf_check_nna(song_t *csf, uint32_t nchan, uint32_t instr, int note, int force_cut) +{ + song_voice_t *p; + song_voice_t *chan = &csf->voices[nchan]; + song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; + song_instrument_t *ptr_instrument; + signed char *data; + if (!NOTE_IS_NOTE(note)) + return; + // Always NNA cut - using + if (force_cut || !(csf->flags & SONG_INSTRUMENTMODE)) { + if (!chan->length || (chan->flags & CHN_MUTE) || (!chan->left_volume && !chan->right_volume)) + return; + uint32_t n = csf_get_nna_channel(csf, nchan); + if (!n) return; + p = &csf->voices[n]; + // Copy Channel + *p = *chan; + p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO); + p->tremolo_delta = 0; + p->master_channel = nchan+1; + p->n_command = 0; + // Cut the note + p->fadeout_volume = 0; + p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + // Stop this channel + chan->length = chan->position = chan->position_frac = 0; + chan->rofs = chan->lofs = 0; + chan->left_volume = chan->right_volume = 0; + if (chan->flags & CHN_ADLIB) { + //Do this only if really an adlib chan. Important! + OPL_NoteOff(nchan); + OPL_Touch(nchan, NULL, 0); + } + return; + } + if (instr >= MAX_INSTRUMENTS) instr = 0; + data = chan->current_sample_data; + ptr_instrument = chan->ptr_instrument; + if (instr && note) { + ptr_instrument = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL; + if (ptr_instrument) { + uint32_t n = 0; + if (!NOTE_IS_CONTROL(note)) { + n = ptr_instrument->sample_map[note-1]; + note = ptr_instrument->note_map[note-1]; + if (n && n < MAX_SAMPLES) + data = csf->samples[n].data; + } + } else { + data = NULL; + } + } + if (!penv) return; + p = chan; + for (uint32_t i=nchan; i<MAX_VOICES; p++, i++) { + if (!((i >= MAX_CHANNELS || p == chan) + && ((p->master_channel == nchan+1 || p == chan) + && p->ptr_instrument))) + continue; + int ok = 0; + // Duplicate Check Type + switch (p->ptr_instrument->dct) { + case DCT_NOTE: + ok = (NOTE_IS_NOTE(note) && (int) p->note == note && ptr_instrument == p->ptr_instrument); + break; + case DCT_SAMPLE: + ok = (data && data == p->current_sample_data); + break; + case DCT_INSTRUMENT: + ok = (ptr_instrument == p->ptr_instrument); + break; + } + // Duplicate Note Action + if (ok) { + switch(p->ptr_instrument->dca) { + case DCA_NOTECUT: + fx_note_cut(csf, i, 1); + break; + case DCA_NOTEOFF: + fx_key_off(csf, i); + break; + case DCA_NOTEFADE: + p->flags |= CHN_NOTEFADE; + break; + } + if (!p->volume) { + p->fadeout_volume = 0; + p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + } + } + } + if (chan->flags & CHN_MUTE) + return; + // New Note Action + if (chan->volume && chan->length) { + uint32_t n = csf_get_nna_channel(csf, nchan); + if (n) { + p = &csf->voices[n]; + // Copy Channel + *p = *chan; + p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO); + p->tremolo_delta = 0; + p->master_channel = nchan+1; + p->n_command = 0; + // Key Off the note + switch(chan->nna) { + case NNA_NOTEOFF: + fx_key_off(csf, n); + break; + case NNA_NOTECUT: + p->fadeout_volume = 0; + case NNA_NOTEFADE: + p->flags |= CHN_NOTEFADE; + break; + } + if (!p->volume) { + p->fadeout_volume = 0; + p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + } + // Stop this channel + chan->length = chan->position = chan->position_frac = 0; + chan->rofs = chan->lofs = 0; + } + } +} + + + +static void handle_effect(song_t *csf, uint32_t nchan, uint32_t cmd, uint32_t param, int porta, int firsttick) +{ + song_voice_t *chan = csf->voices + nchan; + + switch (cmd) { + case FX_NONE: + break; + + // Set Volume + case FX_VOLUME: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + chan->volume = (param < 64) ? param*4 : 256; + chan->flags |= CHN_FASTVOLRAMP; + break; + + case FX_PORTAMENTOUP: + if (firsttick) { + if (param) + chan->mem_pitchslide = param; + if (!(csf->flags & SONG_COMPATGXX)) + chan->mem_portanote = chan->mem_pitchslide; + } + fx_portamento_up(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_PORTAMENTODOWN: + if (firsttick) { + if (param) + chan->mem_pitchslide = param; + if (!(csf->flags & SONG_COMPATGXX)) + chan->mem_portanote = chan->mem_pitchslide; + } + fx_portamento_down(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_VOLUMESLIDE: + fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_TONEPORTAMENTO: + if (firsttick) { + if (param) + chan->mem_portanote = param; + if (!(csf->flags & SONG_COMPATGXX)) + chan->mem_pitchslide = chan->mem_portanote; + } + fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_TONEPORTAVOL: + fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, 0); + break; + + case FX_VIBRATO: + fx_vibrato(chan, param); + break; + + case FX_VIBRATOVOL: + fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + fx_vibrato(chan, 0); + break; + + case FX_SPEED: + if ((csf->flags & SONG_FIRSTTICK) && param) { + csf->tick_count = param; + csf->current_speed = param; + } + break; + + case FX_TEMPO: + if (csf->flags & SONG_FIRSTTICK) { + if (param) + chan->mem_tempo = param; + else + param = chan->mem_tempo; + if (param >= 0x20) + csf->current_tempo = param; + } else { + param = chan->mem_tempo; // this just got set on tick zero + + switch (param >> 4) { + case 0: + csf->current_tempo -= param & 0xf; + if (csf->current_tempo < 32) + csf->current_tempo = 32; + break; + case 1: + csf->current_tempo += param & 0xf; + if (csf->current_tempo > 255) + csf->current_tempo = 255; + break; + } + } + break; + + case FX_OFFSET: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + if (param) + chan->mem_offset = (chan->mem_offset & ~0xff00) | (param << 8); + if (NOTE_IS_NOTE(chan->row_note)) { + // when would position *not* be zero if there's a note but no portamento? + if (porta) + chan->position = chan->mem_offset; + else + chan->position += chan->mem_offset; + if (chan->position > chan->length) { + chan->position = (csf->flags & SONG_ITOLDEFFECTS) ? chan->length : 0; + } + } + break; + + case FX_ARPEGGIO: + chan->n_command = FX_ARPEGGIO; + if (!(csf->flags & SONG_FIRSTTICK)) + break; + if (param) + chan->mem_arpeggio = param; + break; + + case FX_RETRIG: + if (param) + chan->mem_retrig = param & 0xFF; + fx_retrig_note(csf, nchan, chan->mem_retrig); + break; + + case FX_TREMOR: + // Tremor logic lifted from DUMB, which is the only player that actually gets it right. + // I *sort of* understand it. + if (csf->flags & SONG_FIRSTTICK) { + if (!param) + param = chan->mem_tremor; + else if (!(csf->flags & SONG_ITOLDEFFECTS)) { + if (param & 0xf0) param -= 0x10; + if (param & 0x0f) param -= 0x01; + } + chan->mem_tremor = param; + chan->cd_tremor |= 128; + } + + if ((chan->cd_tremor & 128) && chan->length) { + if (chan->cd_tremor == 128) + chan->cd_tremor = (chan->mem_tremor >> 4) | 192; + else if (chan->cd_tremor == 192) + chan->cd_tremor = (chan->mem_tremor & 0xf) | 128; + else + chan->cd_tremor--; + } + + chan->n_command = FX_TREMOR; + + break; + + case FX_GLOBALVOLUME: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + if (param <= 128) + csf->current_global_volume = param; + break; + + case FX_GLOBALVOLSLIDE: + fx_global_vol_slide(csf, chan, param); + break; + + case FX_PANNING: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + chan->flags &= ~CHN_SURROUND; + chan->panbrello_delta = 0; + chan->panning = param; + chan->pan_swing = 0; + chan->flags |= CHN_FASTVOLRAMP; + break; + + case FX_PANNINGSLIDE: + fx_panning_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_TREMOLO: + fx_tremolo(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_FINEVIBRATO: + fx_fine_vibrato(chan, param); + break; + + case FX_SPECIAL: + fx_special(csf, nchan, param); + break; + + case FX_KEYOFF: + if ((csf->current_speed - csf->tick_count) == param) + fx_key_off(csf, nchan); + break; + + case FX_CHANNELVOLUME: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + // FIXME rename global_volume to channel_volume in the channel struct + if (param <= 64) { + chan->global_volume = param; + chan->flags |= CHN_FASTVOLRAMP; + } + break; + + case FX_CHANNELVOLSLIDE: + fx_channel_vol_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); + break; + + case FX_PANBRELLO: + fx_panbrello(chan, param); + break; + + case FX_SETENVPOSITION: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + chan->vol_env_position = param; + chan->pan_env_position = param; + chan->pitch_env_position = param; + if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) { + song_instrument_t *penv = chan->ptr_instrument; + if ((chan->flags & CHN_PANENV) + && (penv->pan_env.nodes) + && ((int)param > penv->pan_env.ticks[penv->pan_env.nodes-1])) { + chan->flags &= ~CHN_PANENV; + } + } + break; + + case FX_POSITIONJUMP: + if (csf->flags & SONG_FIRSTTICK) { + if (!(csf->mix_flags & SNDMIX_NOBACKWARDJUMPS) || csf->process_order < param) + csf->process_order = param - 1; + csf->process_row = PROCESS_NEXT_ORDER; + } + break; + + case FX_PATTERNBREAK: + if (csf->flags & SONG_FIRSTTICK) { + csf->break_row = param; + csf->process_row = PROCESS_NEXT_ORDER; + } + break; + + case FX_MIDI: + if (!(csf->flags & SONG_FIRSTTICK)) + break; + if (param < 0x80) { + csf_process_midi_macro(csf, nchan, + csf->midi_config.sfx[chan->active_macro], + param, 0, 0, 0); + } else { + csf_process_midi_macro(csf, nchan, + csf->midi_config.zxx[param & 0x7F], + 0, 0, 0, 0); + } + break; + + case FX_NOTESLIDEUP: + fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, 1); + break; + case FX_NOTESLIDEDOWN: + fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, -1); + break; + } +} + +static void handle_voleffect(song_t *csf, song_voice_t *chan, uint32_t volcmd, uint32_t vol, + int firsttick, int start_note) +{ + /* A few notes, paraphrased from ITTECH.TXT: + Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value + Gx is linked with Ex/Fx if Compat Gxx is off, just like Gxx is with Exx/Fxx + Gx values: 1, 4, 8, 16, 32, 64, 96, 128, 255 + Ax/Bx/Cx/Dx values are used directly (i.e. D9 == D09), and are NOT shared with Dxx + (value is stored into mem_vc_volslide and used by A0/B0/C0/D0) + Hx uses the same value as Hxx and Uxx, and affects the *depth* + so... hxx = (hx | (oldhxx & 0xf0)) ??? + + Additionally: volume and panning are handled on the start tick, not + the first tick of the row (that is, SDx alters their behavior) */ + + switch (volcmd) { + case VOLFX_NONE: + break; + + case VOLFX_VOLUME: + if (start_note) { + if (vol > 64) vol = 64; + chan->volume = vol << 2; + chan->flags |= CHN_FASTVOLRAMP; + } + break; + + case VOLFX_PANNING: + if (start_note) { + if (vol > 64) vol = 64; + chan->panning = vol << 2; + chan->pan_swing = 0; + chan->flags |= CHN_FASTVOLRAMP; + chan->flags &= ~CHN_SURROUND; + chan->panbrello_delta = 0; + } + break; + + case VOLFX_PORTAUP: // Fx + if (firsttick) { + if (vol) + chan->mem_pitchslide = 4 * vol; + if (!(csf->flags & SONG_COMPATGXX)) + chan->mem_portanote = chan->mem_pitchslide; + } else { + fx_reg_portamento_up(csf->flags, chan, chan->mem_pitchslide); + } + break; + + case VOLFX_PORTADOWN: // Ex + if (firsttick) { + if (vol) + chan->mem_pitchslide = 4 * vol; + if (!(csf->flags & SONG_COMPATGXX)) + chan->mem_portanote = chan->mem_pitchslide; + } else { + fx_reg_portamento_down(csf->flags, chan, chan->mem_pitchslide); + } + break; + + case VOLFX_TONEPORTAMENTO: // Gx + if (firsttick) { + if (vol) + chan->mem_portanote = vc_portamento_table[vol & 0x0F]; + if (!(csf->flags & SONG_COMPATGXX)) + chan->mem_pitchslide = chan->mem_portanote; + } + fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, + vc_portamento_table[vol & 0x0F]); + break; + + case VOLFX_VOLSLIDEUP: // Cx + if (firsttick) { + if (vol) + chan->mem_vc_volslide = vol; + } else { + fx_volume_up(chan, chan->mem_vc_volslide); + } + break; + + case VOLFX_VOLSLIDEDOWN: // Dx + if (firsttick) { + if (vol) + chan->mem_vc_volslide = vol; + } else { + fx_volume_down(chan, chan->mem_vc_volslide); + } + break; + + case VOLFX_FINEVOLUP: // Ax + if (firsttick) { + if (vol) + chan->mem_vc_volslide = vol; + else + vol = chan->mem_vc_volslide; + fx_volume_up(chan, vol); + } + break; + + case VOLFX_FINEVOLDOWN: // Bx + if (firsttick) { + if (vol) + chan->mem_vc_volslide = vol; + else + vol = chan->mem_vc_volslide; + fx_volume_down(chan, vol); + } + break; + + case VOLFX_VIBRATODEPTH: // Hx + fx_vibrato(chan, vol); + break; + + case VOLFX_VIBRATOSPEED: // $x (FT2 compat.) + fx_vibrato(chan, vol << 4); + break; + + case VOLFX_PANSLIDELEFT: // <x (FT2) + fx_panning_slide(csf->flags, chan, vol); + break; + + case VOLFX_PANSLIDERIGHT: // >x (FT2) + fx_panning_slide(csf->flags, chan, vol << 4); + break; + } +} + +/* firsttick is only used for SDx at the moment */ +void csf_process_effects(song_t *csf, int firsttick) +{ + song_voice_t *chan = csf->voices; + for (uint32_t nchan=0; nchan<MAX_CHANNELS; nchan++, chan++) { + chan->n_command=0; + + uint32_t instr = chan->row_instr; + uint32_t volcmd = chan->row_voleffect; + uint32_t vol = chan->row_volparam; + uint32_t cmd = chan->row_effect; + uint32_t param = chan->row_param; + int porta = (cmd == FX_TONEPORTAMENTO + || cmd == FX_TONEPORTAVOL + || volcmd == VOLFX_TONEPORTAMENTO); + int start_note = csf->flags & SONG_FIRSTTICK; + + chan->flags &= ~CHN_FASTVOLRAMP; + + // set instrument before doing anything else + if (instr) chan->new_instrument = instr; + + /* Have to handle SDx specially because of the way the effects are structured. + In a PERFECT world, this would be very straightforward: + - Handle the effect column, and set flags for things that should happen + (portamento, volume slides, arpeggio, vibrato, tremolo) + - If note delay counter is set, stop processing that channel + - Trigger all notes if it's their start tick + - Handle volume column. + The obvious implication of this is that all effects are checked only once, and + volumes only need to be set for notes once. Additionally this helps for separating + the mixing code from the rest of the interface (which is always good, especially + for hardware mixing...) + Oh well, the world is not perfect. */ + + if (cmd == FX_SPECIAL) { + if (param) + chan->mem_special = param; + else + param = chan->mem_special; + if (param >> 4 == 0xd) { + // Ideally this would use SONG_FIRSTTICK, but Impulse Tracker has a bug here :) + if (firsttick) { + chan->cd_note_delay = (param & 0xf) ?: 1; + continue; // notes never play on the first tick with SDx, go away + } + if (--chan->cd_note_delay > 0) + continue; // not our turn yet, go away + start_note = (chan->cd_note_delay == 0); + } + } + + // Handles note/instrument/volume changes + if (start_note) { + uint32_t note = chan->row_note; + if (instr && note == NOTE_NONE) { + if (csf->flags & SONG_INSTRUMENTMODE) { + if (chan->ptr_sample) + chan->volume = chan->ptr_sample->volume; + } else { + if (instr < MAX_SAMPLES) + chan->volume = csf->samples[instr].volume; + } + } + // Invalid Instrument ? + if (instr >= MAX_INSTRUMENTS) instr = 0; + // Note Cut/Off => ignore instrument + if ((NOTE_IS_CONTROL(note)) || (note != NOTE_NONE && !porta)) { + /* This is required when the instrument changes (KeyOff is not called) */ + /* Possibly a better bugfix could be devised. --Bisqwit */ + if (chan->flags & CHN_ADLIB) { + //Do this only if really an adlib chan. Important! + OPL_NoteOff(nchan); + OPL_Touch(nchan, NULL, 0); + } + } + + if (NOTE_IS_CONTROL(note)) { + instr = 0; + } else if (NOTE_IS_NOTE(note)) { + chan->new_note = note; + // New Note Action ? (not when paused!!!) + if (!porta) + csf_check_nna(csf, nchan, instr, note, 0); + } + // Instrument Change ? + if (instr) { + song_sample_t *psmp = chan->ptr_sample; + csf_instrument_change(csf, chan, instr, porta, 1); + if (csf->samples[instr].flags & CHN_ADLIB) { + OPL_Patch(nchan, csf->samples[instr].adlib_bytes); + } + + + chan->new_instrument = 0; + // Special IT case: portamento+note causes sample change -> ignore portamento + if (psmp != chan->ptr_sample && NOTE_IS_NOTE(note)) { + porta = 0; + } + } + // New Note ? + if (note != NOTE_NONE) { + if (!instr && chan->new_instrument && NOTE_IS_NOTE(note)) { + csf_instrument_change(csf, chan, chan->new_instrument, porta, 0); + if ((csf->flags & SONG_INSTRUMENTMODE) + && csf->instruments[chan->new_instrument]) { + if (csf->samples[chan->new_instrument].flags & CHN_ADLIB) { + OPL_Patch(nchan, csf->samples[chan->new_instrument].adlib_bytes); + } + } + chan->new_instrument = 0; + } + csf_note_change(csf, nchan, note, porta, 0, !instr); + } + } + + handle_effect(csf, nchan, cmd, param, porta, firsttick); + handle_voleffect(csf, chan, volcmd, vol, firsttick, start_note); + } +} diff --git a/src/player/equalizer.c b/src/player/equalizer.c new file mode 100644 index 0000000..feecef4 --- /dev/null +++ b/src/player/equalizer.c @@ -0,0 +1,255 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "sndfile.h" +#include "cmixer.h" +#include <math.h> + + +#define EQ_BANDWIDTH 2.0 +#define EQ_ZERO 0.000001 + + + +typedef struct { + float a0, a1, a2, b1, b2; + float x1, x2, y1, y2; + float gain, center_frequency; + int enabled; +} eq_band; + + + +//static REAL f2ic = (REAL)(1 << 28); +//static REAL i2fc = (REAL)(1.0 / (1 << 28)); + +static eq_band eq[MAX_EQ_BANDS * 2] = +{ + // Default: Flat EQ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 600, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1200, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6000, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 600, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1200, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6000, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0}, +}; + + +static void eq_filter(eq_band *pbs, float *pbuffer, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + float x = pbuffer[i]; + float y = pbs->a1 * pbs->x1 + + pbs->a2 * pbs->x2 + + pbs->a0 * x + + pbs->b1 * pbs->y1 + + pbs->b2 * pbs->y2; + + pbs->x2 = pbs->x1; + pbs->y2 = pbs->y1; + pbs->x1 = x; + pbuffer[i] = y; + pbs->y1 = y; + } +} + + +void eq_mono(song_t *csf, int *buffer, unsigned int count) +{ + mono_mix_to_float(buffer, csf->mix_buffer_float, count); + + for (unsigned int b = 0; b < MAX_EQ_BANDS; b++) + { + if (eq[b].enabled && eq[b].gain != 1.0f) + eq_filter(&eq[b], csf->mix_buffer_float, count); + } + + float_to_mono_mix(csf->mix_buffer_float, buffer, count); +} + + +// XXX: I rolled the two loops into one. Make sure this works. +void eq_stereo(song_t *csf, int *buffer, unsigned int count) +{ + stereo_mix_to_float(buffer, csf->mix_buffer_float, csf->mix_buffer_float + MIXBUFFERSIZE, count); + + for (unsigned int b = 0; b < MAX_EQ_BANDS; b++) { + int br = b + MAX_EQ_BANDS; + + // Left band + if (eq[b].enabled && eq[b].gain != 1.0f) + eq_filter(&eq[b], csf->mix_buffer_float, count); + + // Right band + if (eq[br].enabled && eq[br].gain != 1.0f) + eq_filter(&eq[br], csf->mix_buffer_float + MIXBUFFERSIZE, count); + } + + float_to_stereo_mix(csf->mix_buffer_float, csf->mix_buffer_float + MIXBUFFERSIZE, buffer, count); +} + + +void initialize_eq(int reset, float freq) +{ + //float fMixingFreq = (REAL)mix_frequency; + + // Gain = 0.5 (-6dB) .. 2 (+6dB) + for (unsigned int band = 0; band < MAX_EQ_BANDS * 2; band++) { + float k, k2, r, f; + float v0, v1; + int b = reset; + + if (!eq[band].enabled) { + eq[band].a0 = 0; + eq[band].a1 = 0; + eq[band].a2 = 0; + eq[band].b1 = 0; + eq[band].b2 = 0; + eq[band].x1 = 0; + eq[band].x2 = 0; + eq[band].y1 = 0; + eq[band].y2 = 0; + continue; + } + + f = eq[band].center_frequency / freq; + + if (f > 0.45f) + eq[band].gain = 1; + + //if (f > 0.25) + // f = 0.25; + + //k = tan(PI * f); + + k = f * 3.141592654f; + k = k + k * f; + + //if (k > (float) 0.707) + // k = (float) 0.707; + + k2 = k*k; + v0 = eq[band].gain; + v1 = 1; + + if (eq[band].gain < 1.0) { + v0 *= 0.5f / EQ_BANDWIDTH; + v1 *= 0.5f / EQ_BANDWIDTH; + } + else { + v0 *= 1.0f / EQ_BANDWIDTH; + v1 *= 1.0f / EQ_BANDWIDTH; + } + + r = (1 + v0 * k + k2) / (1 + v1 * k + k2); + + if (r != eq[band].a0) { + eq[band].a0 = r; + b = 1; + } + + r = 2 * (k2 - 1) / (1 + v1 * k + k2); + + if (r != eq[band].a1) { + eq[band].a1 = r; + b = 1; + } + + r = (1 - v0 * k + k2) / (1 + v1 * k + k2); + + if (r != eq[band].a2) { + eq[band].a2 = r; + b = 1; + } + + r = -2 * (k2 - 1) / (1 + v1 * k + k2); + + if (r != eq[band].b1) { + eq[band].b1 = r; + b = 1; + } + + r = -(1 - v1 * k + k2) / (1 + v1 * k + k2); + + if (r != eq[band].b2) { + eq[band].b2 = r; + b = 1; + } + + if (b) { + eq[band].x1 = 0; + eq[band].x2 = 0; + eq[band].y1 = 0; + eq[band].y2 = 0; + } + } +} + + +void set_eq_gains(const unsigned int *gainbuff, unsigned int gains, const unsigned int *freqs, + int reset, int mix_freq) +{ + for (unsigned int i = 0; i < MAX_EQ_BANDS; i++) { + float g, f = 0; + + if (i < gains) { + unsigned int n = gainbuff[i]; + + //if (n > 32) + // n = 32; + + g = 1.0 + (((double) n) / 64.0); + + if (freqs) + f = (float)(int) freqs[i]; + } + else { + g = 1; + } + + eq[i].gain = + eq[i + MAX_EQ_BANDS].gain = g; + eq[i].center_frequency = + eq[i + MAX_EQ_BANDS].center_frequency = f; + + /* don't enable bands outside... */ + if (f > 20.0f && + i < gains) { + eq[i].enabled = + eq[i + MAX_EQ_BANDS].enabled = 1; + } + else { + eq[i].enabled = + eq[i + MAX_EQ_BANDS].enabled = 0; + } + } + + initialize_eq(reset, mix_freq); +} + diff --git a/src/player/filters.c b/src/player/filters.c new file mode 100644 index 0000000..ddd74cc --- /dev/null +++ b/src/player/filters.c @@ -0,0 +1,116 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "sndfile.h" +#include "cmixer.h" +#include <math.h> + + +// LUT for 2 * damping factor +static const float resonance_table[128] = { + 1.0000000000000000f, 0.9786446094512940f, 0.9577452540397644f, 0.9372922182083130f, + 0.9172759056091309f, 0.8976871371269226f, 0.8785166740417481f, 0.8597555756568909f, + 0.8413951396942139f, 0.8234267830848694f, 0.8058421611785889f, 0.7886331081390381f, + 0.7717915177345276f, 0.7553095817565918f, 0.7391796708106995f, 0.7233941555023193f, + 0.7079457640647888f, 0.6928272843360901f, 0.6780316829681397f, 0.6635520458221436f, + 0.6493816375732422f, 0.6355138421058655f, 0.6219421625137329f, 0.6086603403091431f, + 0.5956621170043945f, 0.5829415321350098f, 0.5704925656318665f, 0.5583094954490662f, + 0.5463865399360657f, 0.5347182154655457f, 0.5232990980148315f, 0.5121238231658936f, + 0.5011872053146362f, 0.4904841780662537f, 0.4800096750259399f, 0.4697588682174683f, + 0.4597269892692566f, 0.4499093294143677f, 0.4403013288974762f, 0.4308985173702240f, + 0.4216965138912201f, 0.4126909971237183f, 0.4038778245449066f, 0.3952528536319733f, + 0.3868120610713959f, 0.3785515129566193f, 0.3704673945903778f, 0.3625559210777283f, + 0.3548133969306946f, 0.3472362160682678f, 0.3398208320140839f, 0.3325638175010681f, + 0.3254617750644684f, 0.3185114264488220f, 0.3117094635963440f, 0.3050527870655060f, + 0.2985382676124573f, 0.2921628654003143f, 0.2859236001968384f, 0.2798175811767578f, + 0.2738419771194458f, 0.2679939568042755f, 0.2622708380222321f, 0.2566699385643005f, + 0.2511886358261108f, 0.2458244115114212f, 0.2405747324228287f, 0.2354371547698975f, + 0.2304092943668366f, 0.2254888117313385f, 0.2206734120845795f, 0.2159608304500580f, + 0.2113489061594009f, 0.2068354636430740f, 0.2024184018373489f, 0.1980956792831421f, + 0.1938652694225311f, 0.1897251904010773f, 0.1856735348701477f, 0.1817083954811096f, + 0.1778279393911362f, 0.1740303486585617f, 0.1703138649463654f, 0.1666767448186874f, + 0.1631172895431519f, 0.1596338599920273f, 0.1562248021364212f, 0.1528885662555695f, + 0.1496235728263855f, 0.1464282870292664f, 0.1433012634515762f, 0.1402409970760346f, + 0.1372461020946503f, 0.1343151479959488f, 0.1314467936754227f, 0.1286396980285645f, + 0.1258925348520279f, 0.1232040524482727f, 0.1205729842185974f, 0.1179980933666229f, + 0.1154781952500343f, 0.1130121126770973f, 0.1105986908078194f, 0.1082368120551109f, + 0.1059253737330437f, 0.1036632955074310f, 0.1014495193958283f, 0.0992830246686935f, + 0.0971627980470657f, 0.0950878411531448f, 0.0930572077631950f, 0.0910699293017387f, + 0.0891250967979431f, 0.0872217938303947f, 0.0853591337800026f, 0.0835362523794174f, + 0.0817523002624512f, 0.0800064504146576f, 0.0782978758215904f, 0.0766257941722870f, + 0.0749894231557846f, 0.0733879879117012f, 0.0718207582831383f, 0.0702869966626167f, + 0.0687859877943993f, 0.0673170387744904f, 0.0658794566988945f, 0.0644725710153580f, +}; + + +// Simple 2-poles resonant filter +// +// XXX freq WAS unused but is now mix_frequency! +// +#define FREQ_PARAM_MULT (128.0 / (24.0 * 256.0)) +void setup_channel_filter(song_voice_t *chan, int reset, int flt_modifier, int freq) +{ + int cutoff = chan->cutoff; + int resonance = chan->resonance; + float frequency, r, d, e, fg, fb0, fb1; + + cutoff = cutoff * (flt_modifier + 256) / 256; + + if (cutoff > 255) + cutoff = 255; + + if (resonance > 255) + resonance = 255; + + // TODO: The enabling/disabling of channel filter is a bit more complex. + // More info in snd_flt.cpp in OpenMPT and filter-reset.it, filter-reset-carry.it + // and filter-nna.it from https://wiki.openmpt.org/Development:_Test_Cases/IT + // Should be 255, but Zxx cutoff is limited to 127, so... + if (cutoff < 254) + chan->flags |= CHN_FILTER; + else + cutoff = 255; + + // 2 ^ (i / 24 * 256) + frequency = 110.0 * powf(2.0, (float) cutoff * FREQ_PARAM_MULT + 0.25); + if (frequency > freq / 2.0) + frequency = freq / 2.0; + r = freq / (2.0 * M_PI * frequency); + + d = resonance_table[resonance] * r + resonance_table[resonance] - 1.0; + e = r * r; + + fg = 1.0 / (1.0 + d + e); + fb0 = (d + e + e) / (1.0 + d + e); + fb1 = -e / (1.0 + d + e); + + chan->filter_a0 = (int32_t)(fg * (1 << FILTERPRECISION)); + chan->filter_b0 = (int32_t)(fb0 * (1 << FILTERPRECISION)); + chan->filter_b1 = (int32_t)(fb1 * (1 << FILTERPRECISION)); + + if (reset) { + chan->filter_y1 = chan->filter_y2 = 0; + chan->filter_y3 = chan->filter_y4 = 0; + } +} + diff --git a/src/player/fmopl.c b/src/player/fmopl.c new file mode 100644 index 0000000..7594ebb --- /dev/null +++ b/src/player/fmopl.c @@ -0,0 +1,2396 @@ +/* +** +** File: fmopl.c - software implementation of FM sound generator +** types OPL and OPL2 +** +** Copyright Jarek Burczynski (bujar at mame dot net) +** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development +** +** Version 0.72 +** + +Revision History: + +04-08-2003 Jarek Burczynski: + - removed BFRDY hack. BFRDY is busy flag, and it should be 0 only when the chip + handles memory read/write or during the adpcm synthesis when the chip + requests another byte of ADPCM data. + +24-07-2003 Jarek Burczynski: + - added a small hack for Y8950 status BFRDY flag (bit 3 should be set after + some (unknown) delay). Right now it's always set. + +14-06-2003 Jarek Burczynski: + - implemented all of the status register flags in Y8950 emulation + - renamed y8950_set_delta_t_memory() parameters from _rom_ to _mem_ since + they can be either RAM or ROM + +08-10-2002 Jarek Burczynski (thanks to Dox for the YM3526 chip) + - corrected ym3526_read() to always set bit 2 and bit 1 + to HIGH state - identical to ym3812_read (verified on real YM3526) + +04-28-2002 Jarek Burczynski: + - binary exact Envelope Generator (verified on real YM3812); + compared to YM2151: the EG clock is equal to internal_clock, + rates are 2 times slower and volume resolution is one bit less + - modified interface functions (they no longer return pointer - + that's internal to the emulator now): + - new wrapper functions for OPLCreate: ym3526_init(), ym3812_init() and y8950_init() + - corrected 'off by one' error in feedback calculations (when feedback is off) + - enabled waveform usage (credit goes to Vlad Romascanu and zazzal22) + - speeded up noise generator calculations (Nicola Salmoria) + +03-24-2002 Jarek Burczynski (thanks to Dox for the YM3812 chip) + Complete rewrite (all verified on real YM3812): + - corrected sin_tab and tl_tab data + - corrected operator output calculations + - corrected waveform_select_enable register; + simply: ignore all writes to waveform_select register when + waveform_select_enable == 0 and do not change the waveform previously selected. + - corrected KSR handling + - corrected Envelope Generator: attack shape, Sustain mode and + Percussive/Non-percussive modes handling + - Envelope Generator rates are two times slower now + - LFO amplitude (tremolo) and phase modulation (vibrato) + - rhythm sounds phase generation + - white noise generator (big thanks to Olivier Galibert for mentioning Berlekamp-Massey algorithm) + - corrected key on/off handling (the 'key' signal is ORed from three sources: FM, rhythm and CSM) + - funky details (like ignoring output of operator 1 in BD rhythm sound when connect == 1) + +12-28-2001 Acho A. Tang + - reflected Delta-T EOS status on Y8950 status port. + - fixed subscription range of attack/decay tables + + + To do: + add delay before key off in CSM mode (see CSMKeyControll) + verify volume of the FM part on the Y8950 +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +//#include "driver.h" /* use M.A.M.E. */ +#include "fmopl.h" + + +/* output final shift */ +#if (OPL_SAMPLE_BITS==16) + #define FINAL_SH (0) + #define MAXOUT (+32767) + #define MINOUT (-32768) +#else + #define FINAL_SH (8) + #define MAXOUT (+127) + #define MINOUT (-128) +#endif + + +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (EG timing) */ +#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ +#define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ + +#define FREQ_MASK ((1<<FREQ_SH)-1) + +/* envelope output entries */ +#define ENV_BITS 10 +#define ENV_LEN (1<<ENV_BITS) +#define ENV_STEP (128.0/ENV_LEN) + +#define MAX_ATT_INDEX ((1<<(ENV_BITS-1))-1) /*511*/ +#define MIN_ATT_INDEX (0) + +/* sinwave entries */ +#define SIN_BITS 10 +#define SIN_LEN (1<<SIN_BITS) +#define SIN_MASK (SIN_LEN-1) + +#define TL_RES_LEN (256) /* 8 bits addressing (real chip) */ + + + +/* register number to channel number , slot offset */ +#define SLOT1 0 +#define SLOT2 1 + +/* Envelope Generator phases */ + +#define EG_ATT 4 +#define EG_DEC 3 +#define EG_SUS 2 +#define EG_REL 1 +#define EG_OFF 0 + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ +#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ +#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ +#define OPL_TYPE_IO 0x08 /* I/O port */ + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) +#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) + + + +typedef struct +{ + UINT32 ar; /* attack rate: AR<<2 */ + UINT32 dr; /* decay rate: DR<<2 */ + UINT32 rr; /* release rate:RR<<2 */ + UINT8 KSR; /* key scale rate */ + UINT8 ksl; /* keyscale level */ + UINT8 ksr; /* key scale rate: kcode>>KSR */ + UINT8 mul; /* multiple: mul_tab[ML] */ + + /* Phase Generator */ + UINT32 Cnt; /* frequency counter */ + UINT32 Incr; /* frequency counter step */ + UINT8 FB; /* feedback shift value */ + INT32 *connect1; /* slot1 output pointer */ + INT32 op1_out[2]; /* slot1 output for feedback */ + UINT8 CON; /* connection (algorithm) type */ + + /* Envelope Generator */ + UINT8 eg_type; /* percussive/non-percussive mode */ + UINT8 state; /* phase type */ + UINT32 TL; /* total level: TL << 2 */ + INT32 TLL; /* adjusted now TL */ + INT32 volume; /* envelope counter */ + UINT32 sl; /* sustain level: sl_tab[SL] */ + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT8 eg_sh_dr; /* (decay state) */ + UINT8 eg_sel_dr; /* (decay state) */ + UINT8 eg_sh_rr; /* (release state) */ + UINT8 eg_sel_rr; /* (release state) */ + UINT32 key; /* 0 = KEY OFF, >0 = KEY ON */ + + /* LFO */ + UINT32 AMmask; /* LFO Amplitude Modulation enable mask */ + UINT8 vib; /* LFO Phase Modulation enable flag (active high)*/ + + /* waveform select */ + UINT16 wavetable; +} OPL_SLOT; + +typedef struct +{ + OPL_SLOT SLOT[2]; + /* phase generator state */ + UINT32 block_fnum; /* block+fnum */ + UINT32 fc; /* Freq. Increment base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 kcode; /* key code (for key scaling) */ +} OPL_CH; + +/* OPL state */ +typedef struct +{ + /* FM channel slots */ + OPL_CH P_CH[9]; /* OPL/OPL2 chips have 9 channels*/ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency=chipclock/72 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ + + UINT8 rhythm; /* Rhythm mode */ + + UINT32 fn_tab[1024]; /* fnumber->increment counter */ + + /* LFO */ + UINT32 LFO_AM; + INT32 LFO_PM; + + UINT8 lfo_am_depth; + UINT8 lfo_pm_depth_range; + UINT32 lfo_am_cnt; + UINT32 lfo_am_inc; + UINT32 lfo_pm_cnt; + UINT32 lfo_pm_inc; + + UINT32 noise_rng; /* 23 bit noise shift register */ + UINT32 noise_p; /* current noise 'phase' */ + UINT32 noise_f; /* current noise period */ + + UINT8 wavesel; /* waveform select enable flag */ + + UINT32 T[2]; /* timer counters */ + UINT8 st[2]; /* timer enable */ + +#if BUILD_Y8950 + /* Delta-T ADPCM unit (Y8950) */ + + YM_DELTAT *deltat; + + /* Keyboard and I/O ports interface */ + UINT8 portDirection; + UINT8 portLatch; + OPL_PORTHANDLER_R porthandler_r; + OPL_PORTHANDLER_W porthandler_w; + void * port_param; + OPL_PORTHANDLER_R keyboardhandler_r; + OPL_PORTHANDLER_W keyboardhandler_w; + void * keyboard_param; +#endif + + /* external event callback handlers */ + OPL_TIMERHANDLER timer_handler;/* TIMER handler */ + void *TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + void *IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */ + void *UpdateParam; /* stream update parameter */ + + UINT8 type; /* chip type */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT8 statusmask; /* status mask */ + UINT8 mode; /* Reg.08 : CSM,notesel,etc. */ + + UINT32 clock; /* master clock (Hz) */ + UINT32 rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time)*/ + signed int phase_modulation; /* phase modulation input (SLOT 2) */ + signed int output[1]; +#if BUILD_Y8950 + INT32 output_deltat[4]; /* for Y8950 DELTA-T, chip is mono, that 4 here is just for safety */ +#endif +} FM_OPL; + + + +/* mapping of register number (offset) to slot number used by the emulator */ +static const int slot_array[32]= +{ + 0, 2, 4, 1, 3, 5,-1,-1, + 6, 8,10, 7, 9,11,-1,-1, + 12,14,16,13,15,17,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1 +}; + +/* key scale level */ +/* table is 3dB/octave , DV converts this into 6dB/octave */ +/* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ +#define SC(x) ((UINT32)((x)/(0.1875/2.0))) +static const UINT32 ksl_tab[8*16]= +{ + /* OCT 0 */ + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + /* OCT 1 */ + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + SC(0.000), SC(0.750), SC(1.125), SC(1.500), + SC(1.875), SC(2.250), SC(2.625), SC(3.000), + /* OCT 2 */ + SC(0.000), SC(0.000), SC(0.000), SC(0.000), + SC(0.000), SC(1.125), SC(1.875), SC(2.625), + SC(3.000), SC(3.750), SC(4.125), SC(4.500), + SC(4.875), SC(5.250), SC(5.625), SC(6.000), + /* OCT 3 */ + SC(0.000), SC(0.000), SC(0.000), SC(1.875), + SC(3.000), SC(4.125), SC(4.875), SC(5.625), + SC(6.000), SC(6.750), SC(7.125), SC(7.500), + SC(7.875), SC(8.250), SC(8.625), SC(9.000), + /* OCT 4 */ + SC(0.000), SC(0.000), SC(3.000), SC(4.875), + SC(6.000), SC(7.125), SC(7.875), SC(8.625), + SC(9.000), SC(9.750),SC(10.125),SC(10.500), + SC(10.875),SC(11.250),SC(11.625),SC(12.000), + /* OCT 5 */ + SC(0.000), SC(3.000), SC(6.000), SC(7.875), + SC(9.000),SC(10.125),SC(10.875),SC(11.625), + SC(12.000),SC(12.750),SC(13.125),SC(13.500), + SC(13.875),SC(14.250),SC(14.625),SC(15.000), + /* OCT 6 */ + SC(0.000), SC(6.000), SC(9.000),SC(10.875), + SC(12.000),SC(13.125),SC(13.875),SC(14.625), + SC(15.000),SC(15.750),SC(16.125),SC(16.500), + SC(16.875),SC(17.250),SC(17.625),SC(18.000), + /* OCT 7 */ + SC(0.000), SC(9.000),SC(12.000),SC(13.875), + SC(15.000),SC(16.125),SC(16.875),SC(17.625), + SC(18.000),SC(18.750),SC(19.125),SC(19.500), + SC(19.875),SC(20.250),SC(20.625),SC(21.000) +}; +#undef SC + +/* sustain level table (3dB per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +#define SC(db) (UINT32) ( db * (2.0/ENV_STEP) ) +static const UINT32 sl_tab[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) +}; +#undef SC + + +#define RATE_STEPS (8) +static const unsigned char eg_inc[15*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 4) */ +/*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 2, 15 3 for attack */ +/*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(13) in this table - it's directly in the code */ +/* Envelope Generator rates (16 + 64 rates + 16 RKS) */ +static const unsigned char eg_rate_select[16+64+16]={ +/* 16 infinite time rates */ +O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), +O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), + +/* rates 00-12 */ +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 13 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 14 */ +O( 8),O( 9),O(10),O(11), + +/* rate 15 */ +O(12),O(12),O(12),O(12), + +/* 16 dummy rates (same as 15 3) */ +O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), +O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), + +}; +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ +/*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ +/*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ + +#define O(a) (a*1) +/* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ +static const unsigned char eg_rate_shift[16+64+16]={ +/* 16 infinite time rates */ +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), + +/* rates 00-12 */ +O(12),O(12),O(12),O(12), +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), +O( 0),O( 0),O( 0),O( 0), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 16 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), + +}; +#undef O + + +/* multiple table */ +#define SC(x) ((UINT8)((x)*2)) +static const UINT8 mul_tab[16]= { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ + SC(0.50), SC(1.00), SC(2.00), SC(3.00), SC(4.00), SC(5.00), SC(6.00), SC(7.00), + SC(8.00), SC(9.00),SC(10.00),SC(10.00),SC(12.00),SC(12.00),SC(15.00),SC(15.00) +}; +#undef SC + +/* TL_TAB_LEN is calculated as: +* 12 - sinus amplitude bits (Y axis) +* 2 - sinus sign bit (Y axis) +* TL_RES_LEN - sinus resolution (X axis) +*/ +#define TL_TAB_LEN (12*2*TL_RES_LEN) +static signed int tl_tab[TL_TAB_LEN]; + +#define ENV_QUIET (TL_TAB_LEN>>4) + +/* sin waveform table in 'decibel' scale */ +/* four waveforms on OPL2 type chips */ +static unsigned int sin_tab[SIN_LEN * 4]; + + +/* LFO Amplitude Modulation table (verified on real YM3812) + 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples + + Length: 210 elements. + + Each of the elements has to be repeated + exactly 64 times (on 64 consecutive samples). + The whole table takes: 64 * 210 = 13440 samples. + + When AM = 1 data is used directly + When AM = 0 data is divided by 4 before being used (losing precision is important) +*/ + +#define LFO_AM_TAB_ELEMENTS 210 + +static const UINT8 lfo_am_table[LFO_AM_TAB_ELEMENTS] = { +0,0,0,0,0,0,0, +1,1,1,1, +2,2,2,2, +3,3,3,3, +4,4,4,4, +5,5,5,5, +6,6,6,6, +7,7,7,7, +8,8,8,8, +9,9,9,9, +10,10,10,10, +11,11,11,11, +12,12,12,12, +13,13,13,13, +14,14,14,14, +15,15,15,15, +16,16,16,16, +17,17,17,17, +18,18,18,18, +19,19,19,19, +20,20,20,20, +21,21,21,21, +22,22,22,22, +23,23,23,23, +24,24,24,24, +25,25,25,25, +26,26,26, +25,25,25,25, +24,24,24,24, +23,23,23,23, +22,22,22,22, +21,21,21,21, +20,20,20,20, +19,19,19,19, +18,18,18,18, +17,17,17,17, +16,16,16,16, +15,15,15,15, +14,14,14,14, +13,13,13,13, +12,12,12,12, +11,11,11,11, +10,10,10,10, +9,9,9,9, +8,8,8,8, +7,7,7,7, +6,6,6,6, +5,5,5,5, +4,4,4,4, +3,3,3,3, +2,2,2,2, +1,1,1,1 +}; + +/* LFO Phase Modulation table (verified on real YM3812) */ +static const INT8 lfo_pm_table[8*8*2] = { + +/* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */ +0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ +0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */ +0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ +1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */ +1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ +2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */ +1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ +3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */ +2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ +4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */ +2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ +5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */ +3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ +6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */ +3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ +7, 3, 0,-3,-7,-3, 0, 3 /*LFO PM depth = 1*/ +}; + + +/* lock level of common table */ +static int num_lock = 0; + + +#define SLOT7_1 (&OPL->P_CH[7].SLOT[SLOT1]) +#define SLOT7_2 (&OPL->P_CH[7].SLOT[SLOT2]) +#define SLOT8_1 (&OPL->P_CH[8].SLOT[SLOT1]) +#define SLOT8_2 (&OPL->P_CH[8].SLOT[SLOT2]) + + + + +INLINE int limit( int val, int max, int min ) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + + +/* status set and IRQ handling */ +INLINE void OPL_STATUS_SET(FM_OPL *OPL,int flag) +{ + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) + { + if(OPL->status & OPL->statusmask) + { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +INLINE void OPL_STATUS_RESET(FM_OPL *OPL,int flag) +{ + /* reset status flag */ + OPL->status &=~flag; + if((OPL->status & 0x80)) + { + if (!(OPL->status & OPL->statusmask) ) + { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +INLINE void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) +{ + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + + +/* advance LFO to next sample */ +INLINE void advance_lfo(FM_OPL *OPL) +{ + UINT8 tmp; + + /* LFO */ + OPL->lfo_am_cnt += OPL->lfo_am_inc; + if (OPL->lfo_am_cnt >= ((UINT32)LFO_AM_TAB_ELEMENTS<<LFO_SH) ) /* lfo_am_table is 210 elements long */ + OPL->lfo_am_cnt -= ((UINT32)LFO_AM_TAB_ELEMENTS<<LFO_SH); + + tmp = lfo_am_table[ OPL->lfo_am_cnt >> LFO_SH ]; + + if (OPL->lfo_am_depth) + OPL->LFO_AM = tmp; + else + OPL->LFO_AM = tmp>>2; + + OPL->lfo_pm_cnt += OPL->lfo_pm_inc; + OPL->LFO_PM = ((OPL->lfo_pm_cnt>>LFO_SH) & 7) | OPL->lfo_pm_depth_range; +} + +/* advance to next sample */ +INLINE void advance(FM_OPL *OPL) +{ + OPL_CH *CH; + OPL_SLOT *op; + int i; + + OPL->eg_timer += OPL->eg_timer_add; + + while (OPL->eg_timer >= OPL->eg_timer_overflow) + { + OPL->eg_timer -= OPL->eg_timer_overflow; + + OPL->eg_cnt++; + + for (i=0; i<9*2; i++) + { + CH = &OPL->P_CH[i/2]; + op = &CH->SLOT[i&1]; + + /* Envelope Generator */ + switch(op->state) + { + case EG_ATT: /* attack phase */ + if ( !(OPL->eg_cnt & ((1<<op->eg_sh_ar)-1) ) ) + { + op->volume += (~op->volume * + (eg_inc[op->eg_sel_ar + ((OPL->eg_cnt>>op->eg_sh_ar)&7)]) + ) >>3; + + if (op->volume <= MIN_ATT_INDEX) + { + op->volume = MIN_ATT_INDEX; + op->state = EG_DEC; + } + + } + break; + + case EG_DEC: /* decay phase */ + if ( !(OPL->eg_cnt & ((1<<op->eg_sh_dr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_dr + ((OPL->eg_cnt>>op->eg_sh_dr)&7)]; + + if ( (unsigned int) op->volume >= op->sl ) + op->state = EG_SUS; + + } + break; + + case EG_SUS: /* sustain phase */ + + /* this is important behaviour: + one can change percusive/non-percussive modes on the fly and + the chip will remain in sustain phase - verified on real YM3812 */ + + if(op->eg_type) /* non-percussive mode */ + { + /* do nothing */ + } + else /* percussive mode */ + { + /* during sustain phase chip adds Release Rate (in percussive mode) */ + if ( !(OPL->eg_cnt & ((1<<op->eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + op->volume = MAX_ATT_INDEX; + } + /* else do nothing in sustain phase */ + } + break; + + case EG_REL: /* release phase */ + if ( !(OPL->eg_cnt & ((1<<op->eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + + } + break; + + default: + break; + } + } + } + + for (i=0; i<9*2; i++) + { + CH = &OPL->P_CH[i/2]; + op = &CH->SLOT[i&1]; + + /* Phase Generator */ + if(op->vib) + { + UINT8 block; + UINT32 block_fnum = CH->block_fnum; + + unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; + + signed int lfo_fn_table_index_offset = lfo_pm_table[OPL->LFO_PM + 16*fnum_lfo ]; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + block_fnum += lfo_fn_table_index_offset; + block = (block_fnum&0x1c00) >> 10; + op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; + } + else /* LFO phase modulation = zero */ + { + op->Cnt += op->Incr; + } + } + else /* LFO phase modulation disabled for this operator */ + { + op->Cnt += op->Incr; + } + } + + /* The Noise Generator of the YM3812 is 23-bit shift register. + * Period is equal to 2^23-2 samples. + * Register works at sampling frequency of the chip, so output + * can change on every sample. + * + * Output of the register and input to the bit 22 is: + * bit0 XOR bit14 XOR bit15 XOR bit22 + * + * Simply use bit 22 as the noise output. + */ + + OPL->noise_p += OPL->noise_f; + i = OPL->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ + OPL->noise_p &= FREQ_MASK; + while (i) + { + /* + UINT32 j; + j = ((OPL->noise_rng) ^ (OPL->noise_rng>>14) ^ (OPL->noise_rng>>15) ^ (OPL->noise_rng>>22)) & 1; + OPL->noise_rng = (j<<22) | (OPL->noise_rng>>1); + */ + + /* + Instead of doing all the logic operations above, we + use a trick here (and use bit 0 as the noise output). + The difference is only that the noise bit changes one + step ahead. This doesn't matter since we don't know + what is real state of the noise_rng after the reset. + */ + + if (OPL->noise_rng & 1) OPL->noise_rng ^= 0x800302; + OPL->noise_rng >>= 1; + + i--; + } +} + + +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm << 16))) + >> FREQ_SH) & SIN_MASK)]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm)) + >> FREQ_SH) & SIN_MASK)]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + + +#define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (OPL->LFO_AM & (OP)->AMmask)) + +/* calculate output */ +INLINE void OPL_CALC_CH( FM_OPL *OPL, OPL_CH *CH ) +{ + OPL_SLOT *SLOT; + unsigned int env; + signed int out; + + OPL->phase_modulation = 0; + + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env = volume_calc(SLOT); + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + *SLOT->connect1 += SLOT->op1_out[0]; + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->FB) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<<SLOT->FB), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable); +} + +/* + operators used in the rhythm sounds generation process: + + Envelope Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal + 6 / 0 12 50 70 90 f0 + + 6 / 1 15 53 73 93 f3 + + 7 / 0 13 51 71 91 f1 + + 7 / 1 16 54 74 94 f4 + + 8 / 0 14 52 72 92 f2 + + 8 / 1 17 55 75 95 f5 + + + Phase Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number MULTIPLE Drum Hat Drum Tom Cymbal + 6 / 0 12 30 + + 6 / 1 15 33 + + 7 / 0 13 31 + + + + 7 / 1 16 34 ----- n o t u s e d ----- + 8 / 0 14 32 + + 8 / 1 17 35 + + + +channel operator register number Bass High Snare Tom Top +number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal + 6 12,15 B6 A6 + + + 7 13,16 B7 A7 + + + + + 8 14,17 B8 A8 + + + + +*/ + +/* calculate rhythm */ + +INLINE void OPL_CALC_RH( FM_OPL *OPL, OPL_CH *CH, unsigned int noise ) +{ + OPL_SLOT *SLOT; + signed int out; + unsigned int env; + + + /* Bass Drum (verified on real YM3812): + - depends on the channel 6 'connect' register: + when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) + when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored + - output sample always is multiplied by 2 + */ + + OPL->phase_modulation = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env = volume_calc(SLOT); + + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + + if (!SLOT->CON) + OPL->phase_modulation = SLOT->op1_out[0]; + /* else ignore output of operator 1 */ + + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->FB) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<<SLOT->FB), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable) * 2; + + + /* Phase generation is based on: */ + /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 + (same combination as TOP CYMBAL but different output phases) */ + /* SD (16) channel 7->slot 1 */ + /* TOM (14) channel 8->slot 1 */ + /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 + (same combination as HIGH HAT but different output phases) */ + + /* Envelope generation based on: */ + /* HH channel 7->slot1 */ + /* SD channel 7->slot2 */ + /* TOM channel 8->slot1 */ + /* TOP channel 8->slot2 */ + + + /* The following formulas can be well optimized. + I leave them in direct form for now (in case I've missed something). + */ + + /* High Hat (verified on real YM3812) */ + env = volume_calc(SLOT7_1); + if( env < ENV_QUIET ) + { + + /* high hat phase generation: + phase = d0 or 234 (based on frequency only) + phase = 34 or 2d0 (based on noise) + */ + + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0xd0; */ + /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ + UINT32 phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e ^ bit5e); + + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ + if (res2) + phase = (0x200|(0xd0>>2)); + + + /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ + /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ + if (phase&0x200) + { + if (noise) + phase = 0x200|0xd0; + } + else + /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ + /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ + { + if (noise) + phase = 0xd0>>2; + } + + OPL->output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT7_1->wavetable) * 2; + } + + /* Snare Drum (verified on real YM3812) */ + env = volume_calc(SLOT7_2); + if( env < ENV_QUIET ) + { + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1; + + /* when bit8 = 0 phase = 0x100; */ + /* when bit8 = 1 phase = 0x200; */ + UINT32 phase = bit8 ? 0x200 : 0x100; + + /* Noise bit XOR'es phase by 0x100 */ + /* when noisebit = 0 pass the phase from calculation above */ + /* when noisebit = 1 phase ^= 0x100; */ + /* in other words: phase ^= (noisebit<<8); */ + if (noise) + phase ^= 0x100; + + OPL->output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT7_2->wavetable) * 2; + } + + /* Tom Tom (verified on real YM3812) */ + env = volume_calc(SLOT8_1); + if( env < ENV_QUIET ) + OPL->output[0] += op_calc(SLOT8_1->Cnt, env, 0, SLOT8_1->wavetable) * 2; + + /* Top Cymbal (verified on real YM3812) */ + env = volume_calc(SLOT8_2); + if( env < ENV_QUIET ) + { + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0x100; */ + /* when res1 = 1 phase = 0x200 | 0x100; */ + UINT32 phase = res1 ? 0x300 : 0x100; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e ^ bit5e); + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | 0x100; */ + if (res2) + phase = 0x300; + + OPL->output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT8_2->wavetable) * 2; + } + +} + + +/* generic table initialize */ +static int init_tables(void) +{ + signed int i,x; + signed int n; + double o,m; + + + for (x=0; x<TL_RES_LEN; x++) + { + m = (1<<16) / pow(2.0, (x+1) * (ENV_STEP/4.0) / 8.0); + m = floor(m); + + /* we never reach (1<<16) here due to the (x+1) */ + /* result fits within 16 bits at maximum */ + + n = (int)m; /* 16 bits here */ + n >>= 4; /* 12 bits here */ + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + n <<= 1; /* 12 bits here (as in real chip) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + for (i=1; i<12; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + #if 0 + logerror("tl %04i", x*2); + for (i=0; i<12; i++) + logerror(", [%02i] %5i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ] ); + logerror("\n"); + #endif + } + /*logerror("FMOPL.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ + + + for (i=0; i<SIN_LEN; i++) + { + /* non-standard sinus */ + m = sin( ((i*2)+1) * M_PI / SIN_LEN ); /* checked against the real chip */ + + /* we never reach zero here due to ((i*2)+1) */ + + if (m>0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + + /*logerror("FMOPL.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, + sin_tab[i], tl_tab[sin_tab[i]] );*/ + } + + for (i=0; i<SIN_LEN; i++) + { + /* waveform 1: __ __ */ + /* / \____/ \____*/ + /* output only first half of the sinus waveform (positive one) */ + + if (i & (1<<(SIN_BITS-1)) ) + sin_tab[1*SIN_LEN+i] = TL_TAB_LEN; + else + sin_tab[1*SIN_LEN+i] = sin_tab[i]; + + /* waveform 2: __ __ __ __ */ + /* / \/ \/ \/ \*/ + /* abs(sin) */ + + sin_tab[2*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>1) ]; + + /* waveform 3: _ _ _ _ */ + /* / |_/ |_/ |_/ |_*/ + /* abs(output only first quarter of the sinus waveform) */ + + if (i & (1<<(SIN_BITS-2)) ) + sin_tab[3*SIN_LEN+i] = TL_TAB_LEN; + else + sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)]; + + /*logerror("FMOPL.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, + sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); + logerror("FMOPL.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, + sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); + logerror("FMOPL.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, + sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] );*/ + } + /*logerror("FMOPL.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ + + + return 1; +} + +static void OPLCloseTable( void ) +{ +} + + + +static void OPL_initalize(FM_OPL *OPL) +{ + int i; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate : 0; +#if 0 + OPL->rate = (double)OPL->clock / 72.0; + OPL->freqbase = 1.0; +#endif + + /*logerror("freqbase=%f\n", OPL->freqbase);*/ + + /* Timer base time */ + OPL->TimerBase = 72.0 / (double)OPL->clock; + + /* make fnumber -> increment counter table */ + for( i=0 ; i < 1024 ; i++ ) + { + /* opn phase increment counter = 20bit */ + /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ + OPL->fn_tab[i] = (UINT32)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); +#if 0 + logerror("FMOPL.C: fn_tab[%4i] = %08x (dec=%8i)\n", + i, OPL->fn_tab[i]>>6, OPL->fn_tab[i]>>6 ); +#endif + } + +#if 0 + for( i=0 ; i < 16 ; i++ ) + { + logerror("FMOPL.C: sl_tab[%i] = %08x\n", + i, sl_tab[i] ); + } + for( i=0 ; i < 8 ; i++ ) + { + int j; + logerror("FMOPL.C: ksl_tab[oct=%2i] =",i); + for (j=0; j<16; j++) + { + logerror("%08x ", ksl_tab[i*16+j] ); + } + logerror("\n"); + } +#endif + + + /* Amplitude modulation: 27 output levels (triangle waveform); + 1 level takes one of: 192, 256 or 448 samples */ + /* One entry from LFO_AM_TABLE lasts for 64 samples */ + OPL->lfo_am_inc = (UINT32)((1.0 / 64.0 ) * (1<<LFO_SH) * OPL->freqbase); + + /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ + OPL->lfo_pm_inc = (UINT32)((1.0 / 1024.0) * (1<<LFO_SH) * OPL->freqbase); + + /*logerror ("OPL->lfo_am_inc = %8x ; OPL->lfo_pm_inc = %8x\n", OPL->lfo_am_inc, OPL->lfo_pm_inc);*/ + + /* Noise generator: a step takes 1 sample */ + OPL->noise_f = (UINT32)((1.0 / 1.0) * (1<<FREQ_SH) * OPL->freqbase); + + OPL->eg_timer_add = (UINT32)((1<<EG_SH) * OPL->freqbase); + OPL->eg_timer_overflow = ( 1 ) * (1<<EG_SH); + /*logerror("OPLinit eg_timer_add=%8x eg_timer_overflow=%8x\n", + OPL->eg_timer_add, OPL->eg_timer_overflow);*/ + +} + +INLINE void FM_KEYON(OPL_SLOT *SLOT, UINT32 key_set) +{ + if( !SLOT->key ) + { + /* restart Phase Generator */ + SLOT->Cnt = 0; + /* phase -> Attack */ + SLOT->state = EG_ATT; + } + SLOT->key |= key_set; +} + +INLINE void FM_KEYOFF(OPL_SLOT *SLOT, UINT32 key_clr) +{ + if( SLOT->key ) + { + SLOT->key &= key_clr; + + if( !SLOT->key ) + { + /* phase -> Release */ + if (SLOT->state>EG_REL) + SLOT->state = EG_REL; + } + } +} + +/* update phase increment counter of operator (also update the EG rates if necessary) */ +INLINE void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) +{ + int ksr; + + /* (frequency) phase increment counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + + /* calculate envelope generator rates */ + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; + } +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void set_mul(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->mul = mul_tab[v&0x0f]; + SLOT->KSR = (v&0x10) ? 0 : 2; + SLOT->eg_type = (v&0x20); + SLOT->vib = (v&0x40); + SLOT->AMmask = (v&0x80) ? ~0 : 0; + CALC_FCSLOT(CH,SLOT); +} + +/* set ksl & tl */ +INLINE void set_ksl_tl(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ksl = v>>6; /* 0 / 1.5 / 3.0 / 6.0 dB/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */ + + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set attack rate & decay rate */ +INLINE void set_ar_dr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; + + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + + SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; +} + +/* set sustain level & release rate */ +INLINE void set_sl_rr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->sl = sl_tab[ v>>4 ]; + + SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; +} + + +/* write a value v to register r on OPL chip */ +static void OPLWriteReg(FM_OPL *OPL, int r, int v) +{ + OPL_CH *CH; + int slot; + UINT32 block_fnum; + + + /* adjust bus to 8 bits */ + r &= 0xff; + v &= 0xff; + + switch(r&0xe0) + { + case 0x00: /* 00-1f:control */ + switch(r&0x1f) + { + case 0x01: /* waveform select enable */ + if(OPL->type&OPL_TYPE_WAVESEL) + { + OPL->wavesel = v&0x20; + /* do not change the waveform previously selected */ + } + break; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v)*4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v)*16; + break; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v&0x80) + { /* IRQ flag clear */ + /* don't reset BFRDY flag or we will have to call deltat module to set the flag */ + OPL_STATUS_RESET(OPL,0x7f-0x08); + } + else + { /* set IRQ mask ,timer enable*/ + UINT8 st1 = v&1; + UINT8 st2 = (v>>1)&1; + + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL, v & (0x78-0x08) ); + OPL_STATUSMASK_SET(OPL, (~v) & 0x78 ); + + /* timer 2 */ + if(OPL->st[1] != st2) + { + double period = st2 ? (OPL->TimerBase * OPL->T[1]) : 0.0; + OPL->st[1] = st2; + if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,1,period); + } + /* timer 1 */ + if(OPL->st[0] != st1) + { + double period = st1 ? (OPL->TimerBase * OPL->T[0]) : 0.0; + OPL->st[0] = st1; + if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,0,period); + } + } + break; +#if BUILD_Y8950 + case 0x06: /* Key Board OUT */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_w) + OPL->keyboardhandler_w(OPL->keyboard_param,v); + else + logerror("Y8950: write unmapped KEYBOARD port\n"); + } + break; + case 0x07: /* DELTA-T control 1 : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + break; +#endif + case 0x08: /* MODE,DELTA-T control 2 : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) { + /* mask 4 LSBs in register 08 for DELTA-T unit */ + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v&0x0f); + } +#endif + break; + +#if BUILD_Y8950 + case 0x09: /* START ADD */ + case 0x0a: + case 0x0b: /* STOP ADD */ + case 0x0c: + case 0x0d: /* PRESCALE */ + case 0x0e: + case 0x0f: /* ADPCM data write */ + case 0x10: /* DELTA-N */ + case 0x11: /* DELTA-N */ + case 0x12: /* ADPCM volume */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + break; + + case 0x15: /* DAC data high 8 bits (F7,F6...F2) */ + case 0x16: /* DAC data low 2 bits (F1, F0 in bits 7,6) */ + case 0x17: /* DAC data shift (S2,S1,S0 in bits 2,1,0) */ + logerror("FMOPL.C: DAC data register written, but not implemented reg=%02x val=%02x\n", + r,v); + break; + + case 0x18: /* I/O CTRL (Direction) */ + if(OPL->type&OPL_TYPE_IO) + OPL->portDirection = v&0x0f; + break; + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + OPL->portLatch = v; + if(OPL->porthandler_w) + OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); + } + break; +#endif + default: + /*logerror("FMOPL.C: write to unknown register: %02x\n",r);*/ + break; + } + break; + case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_mul(OPL,slot,v); + break; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_ksl_tl(OPL,slot,v); + break; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_ar_dr(OPL,slot,v); + break; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_sl_rr(OPL,slot,v); + break; + case 0xa0: + if (r == 0xbd) /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */ + { + OPL->lfo_am_depth = v & 0x80; + OPL->lfo_pm_depth_range = (v&0x40) ? 8 : 0; + + OPL->rhythm = v&0x3f; + + if(OPL->rhythm&0x20) + { + /* BD key on/off */ + if(v&0x10) + { + FM_KEYON (&OPL->P_CH[6].SLOT[SLOT1], 2); + FM_KEYON (&OPL->P_CH[6].SLOT[SLOT2], 2); + } + else + { + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); + } + /* HH key on/off */ + if(v&0x01) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT1], 2); + else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); + /* SD key on/off */ + if(v&0x08) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT2], 2); + else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); + /* TOM key on/off */ + if(v&0x04) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT1], 2); + else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY key on/off */ + if(v&0x02) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT2], 2); + else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); + } + else + { + /* BD key off */ + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); + /* HH key off */ + FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); + /* SD key off */ + FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); + /* TOM key off */ + FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY off */ + FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); + } + return; + } + /* keyon,block,fnum */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + if(!(r&0x10)) + { /* a0-a8 */ + block_fnum = (CH->block_fnum&0x1f00) | v; + } + else + { /* b0-b8 */ + block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); + + if(v&0x20) + { + FM_KEYON (&CH->SLOT[SLOT1], 1); + FM_KEYON (&CH->SLOT[SLOT2], 1); + } + else + { + FM_KEYOFF(&CH->SLOT[SLOT1],~1); + FM_KEYOFF(&CH->SLOT[SLOT2],~1); + } + } + /* update */ + if(CH->block_fnum != block_fnum) + { + UINT8 block = block_fnum >> 10; + + CH->block_fnum = block_fnum; + + CH->ksl_base = ksl_tab[block_fnum>>6]; + CH->fc = OPL->fn_tab[block_fnum&0x03ff] >> (7-block); + + /* BLK 2,1,0 bits -> bits 3,2,1 of kcode */ + CH->kcode = (CH->block_fnum&0x1c00)>>9; + + /* the info below is actually opposite to what is stated in the Manuals + (verifed on real YM3812) */ + /* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum */ + /* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */ + if (OPL->mode&0x40) + CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */ + else + CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */ + + /* refresh Total Level in both SLOTs of this channel */ + CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); + CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); + + /* refresh frequency counter in both SLOTs of this channel */ + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + break; + case 0xc0: + /* FB,C */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; + CH->SLOT[SLOT1].CON = v&1; + CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &OPL->output[0] : &OPL->phase_modulation; + break; + case 0xe0: /* waveform select */ + /* simply ignore write to the waveform select register + if selecting not enabled in test register */ + if(OPL->wavesel) + { + slot = slot_array[r&0x1f]; + if(slot < 0) return; + CH = &OPL->P_CH[slot/2]; + + CH->SLOT[slot&1].wavetable = (v&0x03)*SIN_LEN; + } + break; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable() +{ + num_lock++; + if(num_lock>1) return 0; + + /* first time */ + + /* allocate total level table (128kb space) */ + if( !init_tables() ) + { + num_lock--; + return -1; + } + + return 0; +} + +static void OPL_UnLockTable(void) +{ + if(num_lock) num_lock--; + if(num_lock) return; + + /* last time */ + + OPLCloseTable(); + +} + +static void OPLResetChip(FM_OPL *OPL) +{ + int c,s; + int i; + + OPL->eg_timer = 0; + OPL->eg_cnt = 0; + + OPL->noise_rng = 1; /* noise shift register */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL,0x7f); + + /* reset with register write */ + OPLWriteReg(OPL,0x01,0); /* wavesel disable */ + OPLWriteReg(OPL,0x02,0); /* Timer1 */ + OPLWriteReg(OPL,0x03,0); /* Timer2 */ + OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ + for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); + + /* reset operator parameters */ + for( c = 0 ; c < 9 ; c++ ) + { + OPL_CH *CH = &OPL->P_CH[c]; + for(s = 0 ; s < 2 ; s++ ) + { + /* wave table */ + CH->SLOT[s].wavetable = 0; + CH->SLOT[s].state = EG_OFF; + CH->SLOT[s].volume = MAX_ATT_INDEX; + } + } +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) + { + YM_DELTAT *DELTAT = OPL->deltat; + + DELTAT->freqbase = OPL->freqbase; + DELTAT->output_pointer = &OPL->output_deltat[0]; + DELTAT->portshift = 5; + DELTAT->output_range = 1<<23; + YM_DELTAT_ADPCM_Reset(DELTAT,0,YM_DELTAT_EMULATION_MODE_NORMAL); + } +#endif +} + + +#if 0 // not used anywhere +static void OPL_postload(FM_OPL *OPL) +{ + int slot, ch; + + for( ch=0 ; ch < 9 ; ch++ ) + { + OPL_CH *CH = &OPL->P_CH[ch]; + + /* Look up key scale level */ + UINT32 block_fnum = CH->block_fnum; + CH->ksl_base = ksl_tab[block_fnum >> 6]; + CH->fc = OPL->fn_tab[block_fnum & 0x03ff] >> (7 - (block_fnum >> 10)); + + for( slot=0 ; slot < 2 ; slot++ ) + { + OPL_SLOT *SLOT = &CH->SLOT[slot]; + + /* Calculate key scale rate */ + SLOT->ksr = CH->kcode >> SLOT->KSR; + + /* Calculate attack, decay and release rates */ + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; + + /* Calculate phase increment */ + SLOT->Incr = CH->fc * SLOT->mul; + + /* Total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + + /* Connect output */ + SLOT->connect1 = SLOT->CON ? &OPL->output[0] : &OPL->phase_modulation; + } + } +#if BUILD_Y8950 + if ( (OPL->type & OPL_TYPE_ADPCM) && (OPL->deltat) ) + { + // We really should call the postlod function for the YM_DELTAT, but it's hard without registers + // (see the way the YM2610 does it) + //YM_DELTAT_postload(OPL->deltat, REGS); + } +#endif +} +#endif + + +/* Create one of virtual YM3812/YM3526/Y8950 */ +/* 'clock' is chip clock in Hz */ +/* 'rate' is sampling rate */ +static FM_OPL *OPLCreate(UINT32 clock, UINT32 rate, int type) +{ + char *ptr; + FM_OPL *OPL; + int state_size; + + if (OPL_LockTable() == -1) return NULL; + + /* calculate OPL state size */ + state_size = sizeof(FM_OPL); + +#if BUILD_Y8950 + if (type&OPL_TYPE_ADPCM) state_size+= sizeof(YM_DELTAT); +#endif + + /* allocate memory block */ + ptr = (char *)malloc(state_size); + + if (ptr==NULL) + return NULL; + + /* clear */ + memset(ptr,0,state_size); + + OPL = (FM_OPL *)ptr; + + ptr += sizeof(FM_OPL); + +#if BUILD_Y8950 + if (type&OPL_TYPE_ADPCM) + { + OPL->deltat = (YM_DELTAT *)ptr; + } + ptr += sizeof(YM_DELTAT); +#endif + + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + + /* init global tables */ + OPL_initalize(OPL); + + return OPL; +} + +/* Destroy one of virtual YM3812 */ +static void OPLDestroy(FM_OPL *OPL) +{ + OPL_UnLockTable(); + free(OPL); +} + +/* Optional handlers */ + +static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER timer_handler,void *param) +{ + OPL->timer_handler = timer_handler; + OPL->TimerParam = param; +} +static void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,void *param) +{ + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +static void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +static int OPLWrite(FM_OPL *OPL,int a,int v) +{ + if( !(a&1) ) + { /* address port */ + OPL->address = v & 0xff; + } + else + { /* data port */ + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + OPLWriteReg(OPL,OPL->address,v); + } + return OPL->status>>7; +} + +static unsigned char OPLRead(FM_OPL *OPL,int a) +{ + if( !(a&1) ) + { + /* status port */ + + #if BUILD_Y8950 + + if(OPL->type&OPL_TYPE_ADPCM) /* Y8950 */ + { + return (OPL->status & (OPL->statusmask|0x80)) | (OPL->deltat->PCM_BSY&1); + } + + #endif + + /* OPL and OPL2 */ + return OPL->status & (OPL->statusmask|0x80); + } + +#if BUILD_Y8950 + /* data port */ + switch(OPL->address) + { + case 0x05: /* KeyBoard IN */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_r) + return OPL->keyboardhandler_r(OPL->keyboard_param); + else + logerror("Y8950: read unmapped KEYBOARD port\n"); + } + return 0; + + case 0x0f: /* ADPCM-DATA */ + if(OPL->type&OPL_TYPE_ADPCM) + { + UINT8 val; + + val = YM_DELTAT_ADPCM_Read(OPL->deltat); + /*logerror("Y8950: read ADPCM value read=%02x\n",val);*/ + return val; + } + return 0; + + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + if(OPL->porthandler_r) + return OPL->porthandler_r(OPL->port_param); + else + logerror("Y8950:read unmapped I/O port\n"); + } + return 0; + case 0x1a: /* PCM-DATA */ + if(OPL->type&OPL_TYPE_ADPCM) + { + logerror("Y8950 A/D convertion is accessed but not implemented !\n"); + return 0x80; /* 2's complement PCM data - result from A/D convertion */ + } + return 0; + } +#endif + + return 0xff; +} + +/* CSM Key Controll */ +INLINE void CSMKeyControll(OPL_CH *CH) +{ + FM_KEYON (&CH->SLOT[SLOT1], 4); + FM_KEYON (&CH->SLOT[SLOT2], 4); + + /* The key off should happen exactly one sample later - not implemented correctly yet */ + + FM_KEYOFF(&CH->SLOT[SLOT1], ~4); + FM_KEYOFF(&CH->SLOT[SLOT2], ~4); +} + + +static int OPLTimerOver(FM_OPL *OPL,int c) +{ + if( c ) + { /* Timer B */ + OPL_STATUS_SET(OPL,0x20); + } + else + { /* Timer A */ + OPL_STATUS_SET(OPL,0x40); + /* CSM mode key,TL controll */ + if( OPL->mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch=0; ch<9; ch++) + CSMKeyControll( &OPL->P_CH[ch] ); + } + } + /* reload timer */ + if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,c,OPL->TimerBase * OPL->T[c]); + return OPL->status>>7; +} + + +#define MAX_OPL_CHIPS 2 + + +#if (BUILD_YM3812) + +void * ym3812_init(UINT32 clock, UINT32 rate) +{ + /* emulator create */ + FM_OPL *YM3812 = OPLCreate(clock,rate,OPL_TYPE_YM3812); + if (YM3812) + { + ym3812_reset_chip(YM3812); + } + return YM3812; +} + +void ym3812_shutdown(void *chip) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + + /* emulator shutdown */ + OPLDestroy(YM3812); + } +void ym3812_reset_chip(void *chip) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLResetChip(YM3812); +} + +int ym3812_write(void *chip, int a, int v) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + return OPLWrite(YM3812, a, v); +} + +unsigned char ym3812_read(void *chip, int a) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + /* YM3812 always returns bit2 and bit1 in HIGH state */ + return OPLRead(YM3812, a) | 0x06 ; +} +int ym3812_timer_over(void *chip, int c) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + return OPLTimerOver(YM3812, c); +} + +void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetTimerHandler(YM3812, timer_handler, param); +} +void ym3812_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetIRQHandler(YM3812, IRQHandler, param); +} +void ym3812_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetUpdateHandler(YM3812, UpdateHandler, param); +} + + +/* +** Generate samples for one of the YM3812's +** +** 'which' is the virtual YM3812 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length) +{ + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; + OPLSAMPLE *buf = buffer; + int i; + + for( i=0; i < length ; i++ ) + { + int lt; + + OPL->output[0] = 0; + + advance_lfo(OPL); + + /* FM part */ + OPL_CALC_CH(OPL, &OPL->P_CH[0]); + OPL_CALC_CH(OPL, &OPL->P_CH[1]); + OPL_CALC_CH(OPL, &OPL->P_CH[2]); + OPL_CALC_CH(OPL, &OPL->P_CH[3]); + OPL_CALC_CH(OPL, &OPL->P_CH[4]); + OPL_CALC_CH(OPL, &OPL->P_CH[5]); + + if(!rhythm) + { + OPL_CALC_CH(OPL, &OPL->P_CH[6]); + OPL_CALC_CH(OPL, &OPL->P_CH[7]); + OPL_CALC_CH(OPL, &OPL->P_CH[8]); + } + else /* Rhythm part */ + { + OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); + } + + lt = OPL->output[0]; + + lt >>= FINAL_SH; + + /* limit check */ + lt = limit( lt , MAXOUT, MINOUT ); + + /* store to sound buffer */ + buf[i] = lt; + + advance(OPL); + } + +} +#endif /* BUILD_YM3812 */ + + + +#if (BUILD_YM3526) + +void *ym3526_init(UINT32 clock, UINT32 rate) +{ + /* emulator create */ + FM_OPL *YM3526 = OPLCreate(clock,rate,OPL_TYPE_YM3526); + if (YM3526) + { + ym3526_reset_chip(YM3526); + } + return YM3526; +} + +void ym3526_shutdown(void *chip) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + /* emulator shutdown */ + OPLDestroy(YM3526); +} +void ym3526_reset_chip(void *chip) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLResetChip(YM3526); +} + +int ym3526_write(void *chip, int a, int v) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + return OPLWrite(YM3526, a, v); +} + +unsigned char ym3526_read(void *chip, int a) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + /* YM3526 always returns bit2 and bit1 in HIGH state */ + return OPLRead(YM3526, a) | 0x06 ; +} +int ym3526_timer_over(void *chip, int c) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + return OPLTimerOver(YM3526, c); +} + +void ym3526_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLSetTimerHandler(YM3526, timer_handler, param); +} +void ym3526_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLSetIRQHandler(YM3526, IRQHandler, param); +} +void ym3526_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLSetUpdateHandler(YM3526, UpdateHandler, param); +} + + +/* +** Generate samples for one of the YM3526's +** +** 'which' is the virtual YM3526 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void ym3526_update_one(void *chip, OPLSAMPLE *buffer, int length) +{ + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; + OPLSAMPLE *buf = buffer; + int i; + + for( i=0; i < length ; i++ ) + { + int lt; + + OPL->output[0] = 0; + + advance_lfo(OPL); + + /* FM part */ + OPL_CALC_CH(OPL, &OPL->P_CH[0]); + OPL_CALC_CH(OPL, &OPL->P_CH[1]); + OPL_CALC_CH(OPL, &OPL->P_CH[2]); + OPL_CALC_CH(OPL, &OPL->P_CH[3]); + OPL_CALC_CH(OPL, &OPL->P_CH[4]); + OPL_CALC_CH(OPL, &OPL->P_CH[5]); + + if(!rhythm) + { + OPL_CALC_CH(OPL, &OPL->P_CH[6]); + OPL_CALC_CH(OPL, &OPL->P_CH[7]); + OPL_CALC_CH(OPL, &OPL->P_CH[8]); + } + else /* Rhythm part */ + { + OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); + } + + lt = OPL->output[0]; + + lt >>= FINAL_SH; + + /* limit check */ + lt = limit( lt , MAXOUT, MINOUT ); + + /* store to sound buffer */ + buf[i] = lt; + + advance(OPL); + } + +} +#endif /* BUILD_YM3526 */ + + + + +#if BUILD_Y8950 + +static void Y8950_deltat_status_set(void *chip, UINT8 changebits) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPL_STATUS_SET(Y8950, changebits); +} +static void Y8950_deltat_status_reset(void *chip, UINT8 changebits) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPL_STATUS_RESET(Y8950, changebits); +} + +void *y8950_init(UINT32 clock, UINT32 rate) +{ + /* emulator create */ + FM_OPL *Y8950 = OPLCreate(clock,rate,OPL_TYPE_Y8950); + if (Y8950) + { + Y8950->deltat->status_set_handler = Y8950_deltat_status_set; + Y8950->deltat->status_reset_handler = Y8950_deltat_status_reset; + Y8950->deltat->status_change_which_chip = Y8950; + Y8950->deltat->status_change_EOS_bit = 0x10; /* status flag: set bit4 on End Of Sample */ + Y8950->deltat->status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY (End Of: ADPCM analysis/synthesis, memory reading/writing) */ + + /*Y8950->deltat->write_time = 10.0 / clock;*/ /* a single byte write takes 10 cycles of main clock */ + /*Y8950->deltat->read_time = 8.0 / clock;*/ /* a single byte read takes 8 cycles of main clock */ + /* reset */ + y8950_reset_chip(Y8950); + } + + return Y8950; +} + +void y8950_shutdown(void *chip) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + /* emulator shutdown */ + OPLDestroy(Y8950); +} +void y8950_reset_chip(void *chip) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLResetChip(Y8950); +} + +int y8950_write(void *chip, int a, int v) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + return OPLWrite(Y8950, a, v); +} + +unsigned char y8950_read(void *chip, int a) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + return OPLRead(Y8950, a); +} +int y8950_timer_over(void *chip, int c) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + return OPLTimerOver(Y8950, c); +} + +void y8950_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLSetTimerHandler(Y8950, timer_handler, param); +} +void y8950_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLSetIRQHandler(Y8950, IRQHandler, param); +} +void y8950_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLSetUpdateHandler(Y8950, UpdateHandler, param); +} + +void y8950_set_delta_t_memory(void *chip, void * deltat_mem_ptr, int deltat_mem_size ) +{ + FM_OPL *OPL = (FM_OPL *)chip; + OPL->deltat->memory = (UINT8 *)(deltat_mem_ptr); + OPL->deltat->memory_size = deltat_mem_size; +} + +/* +** Generate samples for one of the Y8950's +** +** 'which' is the virtual Y8950 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void y8950_update_one(void *chip, OPLSAMPLE *buffer, int length) +{ + int i; + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; + YM_DELTAT *DELTAT = OPL->deltat; + OPLSAMPLE *buf = buffer; + + for( i=0; i < length ; i++ ) + { + int lt; + + OPL->output[0] = 0; + OPL->output_deltat[0] = 0; + + advance_lfo(OPL); + + /* deltaT ADPCM */ + if( DELTAT->portstate&0x80 ) + YM_DELTAT_ADPCM_CALC(DELTAT); + + /* FM part */ + OPL_CALC_CH(OPL, &OPL->P_CH[0]); + OPL_CALC_CH(OPL, &OPL->P_CH[1]); + OPL_CALC_CH(OPL, &OPL->P_CH[2]); + OPL_CALC_CH(OPL, &OPL->P_CH[3]); + OPL_CALC_CH(OPL, &OPL->P_CH[4]); + OPL_CALC_CH(OPL, &OPL->P_CH[5]); + + if(!rhythm) + { + OPL_CALC_CH(OPL, &OPL->P_CH[6]); + OPL_CALC_CH(OPL, &OPL->P_CH[7]); + OPL_CALC_CH(OPL, &OPL->P_CH[8]); + } + else /* Rhythm part */ + { + OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); + } + + lt = OPL->output[0] + (OPL->output_deltat[0]>>11); + + lt >>= FINAL_SH; + + /* limit check */ + lt = limit( lt , MAXOUT, MINOUT ); + + /* store to sound buffer */ + buf[i] = lt; + + advance(OPL); + } + +} + +void y8950_set_port_handler(void *chip,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,void * param) +{ + FM_OPL *OPL = (FM_OPL *)chip; + OPL->porthandler_w = PortHandler_w; + OPL->porthandler_r = PortHandler_r; + OPL->port_param = param; +} + +void y8950_set_keyboard_handler(void *chip,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,void * param) +{ + FM_OPL *OPL = (FM_OPL *)chip; + OPL->keyboardhandler_w = KeyboardHandler_w; + OPL->keyboardhandler_r = KeyboardHandler_r; + OPL->keyboard_param = param; +} + +#endif + diff --git a/src/player/fmpatches.c b/src/player/fmpatches.c new file mode 100644 index 0000000..44058cd --- /dev/null +++ b/src/player/fmpatches.c @@ -0,0 +1,179 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* General MIDI assignments used by Creative Labs' MIDI player (PLAY.EXE) +(As found in dro2midi.) */ + +#include "headers.h" +#include "sndfile.h" + +static const uint8_t patches[][11] = { + {0x00,0x00,0x4F,0x00,0xF1,0xD2,0x51,0x43,0x00,0x00,0x06}, /*1*/ + {0x02,0x12,0x4F,0x00,0xF1,0xD2,0x51,0x43,0x00,0x00,0x02}, /*2*/ + {0x00,0x11,0x4A,0x00,0xF1,0xD2,0x53,0x74,0x00,0x00,0x06}, /*3*/ + {0x03,0x11,0x4F,0x00,0xF1,0xD2,0x53,0x74,0x01,0x01,0x06}, /*4*/ + {0x01,0x11,0x66,0x00,0xF1,0xD2,0x51,0xC3,0x00,0x00,0x06}, /*5*/ + {0xC0,0xD2,0x52,0x00,0xF1,0xD2,0x53,0x94,0x00,0x00,0x06}, /*6*/ + {0x12,0x18,0x86,0x00,0xF3,0xFC,0x00,0x33,0x00,0x00,0x08}, /*7*/ + {0xD0,0x12,0x4E,0x00,0xA8,0x92,0x32,0xA7,0x03,0x02,0x00}, /*8*/ + {0xC8,0xD1,0x4F,0x00,0xF2,0xF3,0x64,0x77,0x00,0x00,0x08}, /*9*/ + {0x33,0x34,0x0E,0x00,0x01,0x7D,0x11,0x34,0x00,0x00,0x08}, /*10*/ + {0x17,0x16,0x50,0x00,0xD1,0xD3,0x52,0x92,0x00,0x01,0x04}, /*11*/ + {0xE7,0xE1,0x21,0x00,0xF5,0xF6,0x77,0x14,0x00,0x00,0x08}, /*12*/ + {0x95,0x81,0x4E,0x00,0xDA,0xF9,0x25,0x15,0x00,0x00,0x0A}, /*13*/ + {0x27,0x21,0x1F,0x00,0xF5,0xF5,0x96,0x57,0x00,0x00,0x08}, /*14*/ + {0x87,0xF1,0x4E,0x80,0xB1,0xE6,0x33,0x42,0x00,0x00,0x00}, /*15*/ + {0x31,0x11,0x87,0x80,0xA1,0x7D,0x11,0x43,0x00,0x00,0x08}, /*16*/ + {0x32,0xB1,0x8C,0x00,0x91,0xA1,0x07,0x19,0x02,0x00,0x05}, /*17*/ + {0x31,0xB4,0x54,0x80,0xF1,0xF5,0x07,0x19,0x00,0x00,0x07}, /*18*/ + {0x24,0x21,0x40,0x49,0xFF,0xFF,0x0F,0x0F,0x00,0x00,0x01}, /*19*/ + {0xD2,0xF1,0x44,0x80,0x91,0xA1,0x57,0x09,0x01,0x01,0x03}, /*20*/ + {0x01,0x02,0x52,0x80,0xF0,0xF0,0x1F,0x1F,0x01,0x00,0x0A}, /*21*/ + {0x21,0x32,0x4F,0x01,0xF2,0x52,0x0B,0x0B,0x00,0x01,0x0A}, /*22*/ + {0xF0,0xF2,0x93,0x00,0xD8,0xB3,0x0B,0x0B,0x02,0x01,0x0A}, /*23*/ + {0x20,0x31,0x5D,0x00,0xF2,0x52,0x0B,0x0B,0x03,0x02,0x00}, /*24*/ + {0x01,0x01,0x1B,0x00,0xF4,0xF3,0x25,0x46,0x02,0x00,0x00}, /*25*/ + {0x11,0x01,0x0F,0x00,0xF4,0xF3,0x25,0x46,0x01,0x00,0x00}, /*26*/ + {0x01,0x01,0x27,0x00,0xF1,0xF4,0x1F,0x88,0x02,0x00,0x0A}, /*27*/ + {0x12,0x13,0x44,0x00,0xEA,0xD2,0x32,0xE7,0x01,0x01,0x00}, /*28*/ + {0x30,0x31,0x45,0x00,0xA4,0xF5,0x32,0xE7,0x03,0x00,0x00}, /*29*/ + {0x21,0x21,0x0F,0x00,0xF5,0xF1,0x17,0x78,0x02,0x01,0x04}, /*30*/ + {0x01,0x20,0x41,0x00,0xD1,0xC1,0x34,0xA5,0x03,0x03,0x04}, /*31*/ + {0x10,0x12,0x43,0x00,0xA7,0xE3,0x97,0xE7,0x03,0x02,0x00}, /*32*/ + {0x20,0x21,0x28,0x00,0xC5,0xD2,0x15,0xA4,0x00,0x00,0x0C}, /*33*/ + {0x30,0x21,0x16,0x00,0xF2,0xF3,0x9F,0x78,0x00,0x00,0x0C}, /*34*/ + {0x30,0x21,0x11,0x00,0x82,0xF3,0x9F,0x78,0x00,0x00,0x0A}, /*35*/ + {0x21,0x21,0x23,0x00,0x73,0x93,0x1A,0x87,0x00,0x00,0x0C}, /*36*/ + {0x30,0x21,0x0E,0x00,0x62,0xF3,0x55,0x68,0x02,0x00,0x0A}, /*37*/ + {0x30,0x22,0x0C,0x00,0x62,0xD5,0xB5,0x98,0x01,0x00,0x08}, /*38*/ + {0x70,0x72,0x93,0x40,0x64,0xA1,0x43,0x43,0x00,0x00,0x0A}, /*39*/ + {0x30,0x32,0x8D,0x80,0x44,0x92,0x43,0x43,0x02,0x00,0x0A}, /*40*/ + {0xE1,0xE2,0x4E,0x00,0x65,0x61,0x43,0x44,0x02,0x02,0x00}, /*41*/ + {0xA1,0xA2,0x8E,0x00,0x65,0x63,0x43,0x45,0x02,0x02,0x00}, /*42*/ + {0xB0,0x61,0x87,0x40,0xD1,0x62,0x11,0x15,0x02,0x01,0x06}, /*43*/ + {0xF0,0x20,0x8A,0x80,0xB1,0xA0,0x11,0x15,0x02,0x01,0x06}, /*44*/ + {0xF1,0xE2,0x89,0x40,0x73,0x43,0x01,0x05,0x02,0x00,0x06}, /*45*/ + {0x31,0x21,0x57,0x80,0xF8,0xF7,0xF9,0xE6,0x03,0x02,0x0E}, /*46*/ + {0x32,0x01,0x24,0x80,0xF1,0xF5,0x35,0x35,0x00,0x00,0x00}, /*47*/ + {0x00,0x00,0x04,0x00,0xAA,0xD2,0xC8,0xB3,0x00,0x00,0x0A}, /*48*/ + {0xE0,0xF1,0x4F,0x00,0xD4,0x55,0x0B,0x0B,0x02,0x02,0x0A}, /*49*/ + {0xE0,0xF0,0x52,0x00,0x96,0x35,0x05,0x01,0x02,0x02,0x0A}, /*50*/ + {0xE1,0xF1,0x4F,0x00,0x36,0x45,0x05,0x02,0x02,0x02,0x0A}, /*51*/ + {0xE2,0xE1,0x48,0x80,0x21,0x41,0x43,0x45,0x02,0x01,0x00}, /*52*/ + {0xE0,0xF1,0x16,0x00,0x41,0x20,0x52,0x72,0x02,0x02,0x00}, /*53*/ + {0xE0,0xF1,0x11,0x00,0x01,0xD0,0x52,0x72,0x02,0x02,0x00}, /*54*/ + {0xE0,0xF1,0x1A,0x00,0x61,0x30,0x52,0x73,0x00,0x02,0x00}, /*55*/ + {0x50,0x50,0x0B,0x00,0x84,0xA4,0x4B,0x99,0x00,0x00,0x0A}, /*56*/ + {0x31,0x61,0x1C,0x80,0x41,0x92,0x0B,0x3B,0x00,0x00,0x0E}, /*57*/ + {0xB1,0x61,0x1C,0x00,0x41,0x92,0x1F,0x3B,0x00,0x00,0x0E}, /*58*/ + {0x20,0x21,0x18,0x00,0x52,0xA2,0x15,0x24,0x00,0x00,0x0C}, /*59*/ + {0xC1,0xC1,0x94,0x80,0x74,0xA3,0xEA,0xF5,0x02,0x01,0x0E}, /*60*/ + {0x21,0x21,0x28,0x00,0x41,0x81,0xB4,0x98,0x00,0x00,0x0E}, /*61*/ + {0x21,0x21,0x1D,0x00,0x51,0xE1,0xAE,0x3E,0x02,0x01,0x0E}, /*62*/ + {0xE0,0xE0,0x93,0x80,0x51,0x81,0xA6,0x97,0x02,0x01,0x0E}, /*63*/ + {0xE0,0xE1,0x93,0x80,0x51,0xE1,0xA6,0x97,0x02,0x01,0x0E}, /*64*/ + {0xE0,0xF2,0x4B,0x01,0xD8,0xB3,0x0B,0x0B,0x02,0x01,0x08}, /*65*/ + {0xE0,0xF1,0x49,0x01,0xB8,0xB3,0x0B,0x0B,0x02,0x01,0x08}, /*66*/ + {0xE0,0xF0,0x4E,0x01,0x98,0xC3,0x0B,0x0B,0x01,0x02,0x08}, /*67*/ + {0xE0,0xF1,0x4C,0x01,0x88,0xD3,0x0B,0x0B,0x01,0x01,0x08}, /*68*/ + {0xF1,0xE4,0xC5,0x00,0x7E,0x8C,0x17,0x0E,0x00,0x00,0x08}, /*69*/ + {0x60,0x72,0x4F,0x00,0xD8,0xB3,0x0B,0x0B,0x00,0x01,0x0A}, /*70*/ + {0x31,0x72,0xD1,0x80,0xD5,0x91,0x19,0x1B,0x00,0x00,0x0C}, /*71*/ + {0x32,0x71,0xC8,0x80,0xD5,0x73,0x19,0x1B,0x00,0x00,0x0C}, /*72*/ + {0xE2,0x62,0x6A,0x00,0x9E,0x55,0x8F,0x2A,0x00,0x00,0x0E}, /*73*/ + {0xE0,0x61,0xEC,0x00,0x7E,0x65,0x8F,0x2A,0x00,0x00,0x0E}, /*74*/ + {0x62,0xA2,0x88,0x83,0x84,0x75,0x27,0x17,0x00,0x00,0x09}, /*75*/ + {0x62,0xA2,0x84,0x83,0x84,0x75,0x27,0x17,0x00,0x00,0x09}, /*76*/ + {0xE3,0x62,0x6D,0x00,0x57,0x57,0x04,0x77,0x00,0x00,0x0E}, /*77*/ + {0xF1,0xE1,0x28,0x00,0x57,0x67,0x34,0x5D,0x03,0x00,0x0E}, /*78*/ + {0xD1,0x72,0xC7,0x00,0x31,0x42,0x0F,0x09,0x00,0x00,0x0B}, /*79*/ + {0xF2,0x72,0xC7,0x00,0x51,0x42,0x05,0x69,0x00,0x00,0x0B}, /*80*/ + {0x23,0x31,0x4F,0x00,0x51,0x60,0x5B,0x25,0x01,0x01,0x00}, /*81*/ + {0x22,0x31,0x48,0x00,0x31,0xC0,0x9B,0x65,0x02,0x01,0x00}, /*82*/ + {0xF1,0xE1,0x28,0x00,0x57,0x67,0x34,0x0D,0x03,0x00,0x0E}, /*83*/ + {0xE1,0xE1,0x23,0x00,0x57,0x67,0x04,0x4D,0x03,0x00,0x0E}, /*84*/ + {0xE2,0x31,0x42,0x08,0x78,0xF3,0x0B,0x0B,0x01,0x01,0x08}, /*85*/ + {0xE2,0xE2,0x21,0x00,0x11,0x40,0x52,0x73,0x01,0x01,0x08}, /*86*/ + {0x23,0xA4,0xC0,0x00,0x51,0x35,0x07,0x79,0x01,0x02,0x0D}, /*87*/ + {0x24,0xA0,0xC0,0x00,0x51,0x75,0x07,0x09,0x01,0x02,0x09}, /*88*/ + {0xE0,0xF0,0x16,0x00,0xB1,0xE0,0x51,0x75,0x02,0x02,0x00}, /*89*/ + {0x03,0xA4,0xC0,0x00,0x52,0xF4,0x03,0x55,0x00,0x00,0x09}, /*90*/ + {0xE1,0xE1,0x93,0x80,0x31,0xA1,0xA6,0x97,0x01,0x01,0x0A}, /*91*/ + {0xF0,0x71,0xC4,0x80,0x10,0x11,0x01,0xC1,0x02,0x02,0x01}, /*92*/ + {0xC1,0xE0,0x4F,0x00,0xB1,0x12,0x53,0x74,0x02,0x02,0x06}, /*93*/ + {0xC0,0x41,0x6D,0x00,0xF9,0xF2,0x21,0xB3,0x01,0x00,0x0E}, /*94*/ + {0xE3,0xE2,0x4C,0x00,0x21,0xA1,0x43,0x45,0x01,0x01,0x00}, /*95*/ + {0xE3,0xE2,0x0C,0x00,0x11,0x80,0x52,0x73,0x01,0x01,0x08}, /*96*/ + {0x26,0x88,0xC0,0x00,0x55,0xF8,0x47,0x19,0x00,0x00,0x0B}, /*97*/ + {0x23,0xE4,0xD4,0x00,0xE5,0x35,0x03,0x65,0x00,0x00,0x07}, /*98*/ + {0x27,0x32,0xC0,0x00,0x32,0xA4,0x62,0x33,0x00,0x00,0x00}, /*99*/ + {0xD0,0x31,0x4E,0x00,0x98,0xA2,0x32,0x47,0x01,0x02,0x00}, /*100*/ + {0xF0,0x71,0xC0,0x00,0x93,0x43,0x03,0x02,0x01,0x00,0x0F}, /*101*/ + {0xE0,0xF1,0x1A,0x80,0x13,0x33,0x52,0x13,0x01,0x02,0x00}, /*102*/ + {0xE0,0xF1,0x1A,0x00,0x45,0x32,0xBA,0x91,0x00,0x02,0x00}, /*103*/ + {0x11,0x15,0x18,0x03,0x58,0xA2,0x02,0x72,0x01,0x00,0x0A}, /*104*/ + {0x10,0x18,0x80,0x40,0xF1,0xF1,0x53,0x53,0x00,0x00,0x00}, /*105*/ + {0x31,0x17,0x86,0x80,0xA1,0x7D,0x11,0x23,0x00,0x00,0x08}, /*106*/ + {0x10,0x18,0x80,0x40,0xF1,0xF6,0x53,0x54,0x00,0x00,0x00}, /*107*/ + {0x31,0x34,0x21,0x00,0xF5,0x93,0x56,0xE8,0x01,0x00,0x08}, /*108*/ + {0x03,0x15,0x4F,0x00,0xF1,0xD6,0x39,0x74,0x03,0x00,0x06}, /*109*/ + {0x31,0x22,0x43,0x00,0x6E,0x8B,0x17,0x0C,0x01,0x02,0x02}, /*110*/ + {0x31,0x22,0x1C,0x80,0x61,0x52,0x03,0x67,0x00,0x00,0x0E}, /*111*/ + {0x60,0xF0,0x0C,0x80,0x81,0x61,0x03,0x0C,0x00,0x01,0x08}, /*112*/ + {0x27,0x05,0x55,0x00,0x31,0xA7,0x62,0x75,0x00,0x00,0x00}, /*113*/ + {0x95,0x16,0x81,0x00,0xE7,0x96,0x01,0x67,0x00,0x00,0x04}, /*114*/ + {0x0C,0x01,0x87,0x80,0xF0,0xF2,0x05,0x05,0x01,0x01,0x04}, /*115*/ + {0x35,0x11,0x44,0x00,0xF8,0xF5,0xFF,0x75,0x00,0x00,0x0E}, /*116*/ + {0x10,0x10,0x0B,0x00,0xA7,0xD5,0xEC,0xF5,0x00,0x00,0x00}, /*117*/ + {0x20,0x01,0x0B,0x00,0xA8,0xD6,0xC8,0xB7,0x00,0x00,0x00}, /*118*/ + {0x00,0x01,0x0B,0x00,0x88,0xD5,0xC4,0xB7,0x00,0x00,0x00}, /*119*/ + {0x0C,0x10,0x8F,0x80,0x41,0x33,0x31,0x2B,0x00,0x03,0x08}, /*120*/ + {0x17,0xF7,0x00,0x00,0x3B,0xEA,0xDF,0x97,0x03,0x00,0x0B}, /*121*/ + {0x12,0x18,0x06,0x00,0x73,0x3C,0x02,0x74,0x00,0x00,0x0E}, /*122*/ + {0x02,0x08,0x00,0x00,0x3E,0x14,0x01,0xF3,0x02,0x02,0x0E}, /*123*/ + {0xF5,0xF6,0xD4,0x00,0xEB,0x45,0x03,0x68,0x00,0x00,0x07}, /*124*/ + {0xF0,0xCA,0x00,0xC0,0xDA,0xB0,0x71,0x17,0x01,0x01,0x08}, /*125*/ + {0xF0,0xE2,0x00,0xC0,0x1E,0x11,0x11,0x11,0x01,0x01,0x08}, /*126*/ + {0xE7,0xE8,0x00,0x04,0x34,0x10,0x00,0xB2,0x02,0x02,0x0E}, /*127*/ + {0x0C,0x04,0x00,0x00,0xF0,0xF6,0xF0,0xE6,0x02,0x00,0x0E}, /*128*/ +}; + +void adlib_patch_apply(song_sample_t *smp, int patchnum) +{ + if (patchnum < 0 || patchnum > 127) { + printf("adlib_patch_apply: invalid patch %d\n", patchnum); + return; + } + memcpy(smp->adlib_bytes, patches[patchnum], 11); + strncpy(smp->name, midi_program_names[patchnum], sizeof(smp->name) - 1); + smp->name[sizeof(smp->name) - 1] = '\0'; // Paranoid. + sprintf(smp->filename, "MIDI#%03d", patchnum + 1); + smp->flags |= CHN_ADLIB; + if (smp->data) { + csf_free_sample(smp->data); + smp->data = NULL; + } + smp->length = 1; + smp->data = csf_allocate_sample(1); +} + diff --git a/src/player/mixer.c b/src/player/mixer.c new file mode 100644 index 0000000..2f3ddf1 --- /dev/null +++ b/src/player/mixer.c @@ -0,0 +1,1555 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdint.h> +#include <math.h> + +#include "sndfile.h" +#include "snd_fm.h" +#include "cmixer.h" +#include "util.h" // for CLAMP + +// For pingpong loops that work like most of Impulse Tracker's drivers +// (including SB16, SBPro, and the disk writer) -- as well as XMPlay, use 2 +// To make them sound like the GUS driver, use 1. +// It's really only noticeable for very small loops... (e.g. chip samples) +// (thanks Saga_Musix for this) +#define PINGPONG_OFFSET 2 + + + +/* The following lut settings are PRECOMPUTED. + * + * If you plan on changing these settings, you + * MUST also regenerate the arrays. + */ +// number of bits used to scale spline coefs +#define SPLINE_QUANTBITS 14 +#define SPLINE_QUANTSCALE (1L << SPLINE_QUANTBITS) +#define SPLINE_8SHIFT (SPLINE_QUANTBITS - 8) +#define SPLINE_16SHIFT (SPLINE_QUANTBITS) + +// forces coefsset to unity gain +#define SPLINE_CLAMPFORUNITY + +// log2(number) of precalculated splines (range is [4..14]) +#define SPLINE_FRACBITS 10 +#define SPLINE_LUTLEN (1L<<SPLINE_FRACBITS) + + +// quantizer scale of window coefs +#define WFIR_QUANTBITS 15 +#define WFIR_QUANTSCALE (1L << WFIR_QUANTBITS) +#define WFIR_8SHIFT (WFIR_QUANTBITS - 8) +#define WFIR_16BITSHIFT (WFIR_QUANTBITS) + +// log2(number)-1 of precalculated taps range is [4..12] +#define WFIR_FRACBITS 10 +#define WFIR_LUTLEN ((1L << (WFIR_FRACBITS + 1)) + 1) + +// number of samples in window +#define WFIR_LOG2WIDTH 3 +#define WFIR_WIDTH (1L << WFIR_LOG2WIDTH) +#define WFIR_SMPSPERWING ((WFIR_WIDTH-1)>>1) +// cutoff (1.0 == pi/2) +#define WFIR_CUTOFF 0.90f +// wfir type +#define WFIR_HANN 0 +#define WFIR_HAMMING 1 +#define WFIR_BLACKMANEXACT 2 +#define WFIR_BLACKMAN3T61 3 +#define WFIR_BLACKMAN3T67 4 +#define WFIR_BLACKMAN4T92 5 +#define WFIR_BLACKMAN4T74 6 +#define WFIR_KAISER4T 7 +#define WFIR_TYPE WFIR_BLACKMANEXACT +// wfir help +#ifndef M_zPI +#define M_zPI 3.1415926535897932384626433832795 +#endif +#define M_zEPS 1e-8 +#define M_zBESSELEPS 1e-21 + + +#include "precomp_lut.h" + + +// ---------------------------------------------------------------------------- +// MIXING MACROS +// ---------------------------------------------------------------------------- + +#define SNDMIX_BEGINSAMPLELOOP8 \ + register song_voice_t * const chan = channel; \ + position = chan->position_frac; \ + const signed char *p = (signed char *)(chan->current_sample_data + chan->position); \ + if (chan->flags & CHN_STEREO) p += chan->position; \ + int *pvol = pbuffer;\ + do { + + +#define SNDMIX_BEGINSAMPLELOOP16\ + register song_voice_t * const chan = channel;\ + position = chan->position_frac;\ + const signed short *p = (signed short *)(chan->current_sample_data+(chan->position*2));\ + if (chan->flags & CHN_STEREO) p += chan->position;\ + int *pvol = pbuffer;\ + do { + + +#define SNDMIX_ENDSAMPLELOOP \ + position += chan->increment; \ + } while (pvol < pbufmax); \ + chan->position += position >> 16; \ + chan->position_frac = position & 0xFFFF; + + +#define SNDMIX_ENDSAMPLELOOP8 SNDMIX_ENDSAMPLELOOP +#define SNDMIX_ENDSAMPLELOOP16 SNDMIX_ENDSAMPLELOOP + + +////////////////////////////////////////////////////////////////////////////// +// Mono + +// No interpolation +#define SNDMIX_GETMONOVOL8NOIDO \ + int vol = p[position >> 16] << 8; + + +#define SNDMIX_GETMONOVOL16NOIDO \ + int vol = p[position >> 16]; + + +// Linear Interpolation +#define SNDMIX_GETMONOVOL8LINEAR \ + int poshi = position >> 16; \ + int poslo = (position >> 8) & 0xFF; \ + int srcvol = p[poshi]; \ + int destvol = p[poshi+1]; \ + int vol = (srcvol<<8) + ((int)(poslo * (destvol - srcvol))); + + +#define SNDMIX_GETMONOVOL16LINEAR \ + int poshi = position >> 16; \ + int poslo = (position >> 8) & 0xFF; \ + int srcvol = p[poshi]; \ + int destvol = p[poshi + 1]; \ + int vol = srcvol + ((int)(poslo * (destvol - srcvol)) >> 8); + + +// spline interpolation (2 guard bits should be enough???) +#define SPLINE_FRACSHIFT ((16 - SPLINE_FRACBITS) - 2) +#define SPLINE_FRACMASK (((1L << (16 - SPLINE_FRACSHIFT)) - 1) & ~3) + + +#define SNDMIX_GETMONOVOL8SPLINE \ + int poshi = position >> 16; \ + int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol = (cubic_spline_lut[poslo ] * (int)p[poshi - 1] + \ + cubic_spline_lut[poslo + 1] * (int)p[poshi ] + \ + cubic_spline_lut[poslo + 3] * (int)p[poshi + 2] + \ + cubic_spline_lut[poslo + 2] * (int)p[poshi + 1]) >> SPLINE_8SHIFT; + + +#define SNDMIX_GETMONOVOL16SPLINE \ + int poshi = position >> 16; \ + int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol = (cubic_spline_lut[poslo ] * (int)p[poshi - 1] + \ + cubic_spline_lut[poslo + 1] * (int)p[poshi ] + \ + cubic_spline_lut[poslo + 3] * (int)p[poshi + 2] + \ + cubic_spline_lut[poslo + 2] * (int)p[poshi + 1]) >> SPLINE_16SHIFT; + + +// fir interpolation +#define WFIR_FRACSHIFT (16 - (WFIR_FRACBITS + 1 + WFIR_LOG2WIDTH)) +#define WFIR_FRACMASK ((((1L << (17 - WFIR_FRACSHIFT)) - 1) & ~((1L << WFIR_LOG2WIDTH) - 1))) +#define WFIR_FRACHALVE (1L << (16 - (WFIR_FRACBITS + 2))) + + +#define SNDMIX_GETMONOVOL8FIRFILTER \ + int poshi = position >> 16;\ + int poslo = (position & 0xFFFF);\ + int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol = (windowed_fir_lut[firidx + 0] * (int)p[poshi + 1 - 4]); \ + vol += (windowed_fir_lut[firidx + 1] * (int)p[poshi + 2 - 4]); \ + vol += (windowed_fir_lut[firidx + 2] * (int)p[poshi + 3 - 4]); \ + vol += (windowed_fir_lut[firidx + 3] * (int)p[poshi + 4 - 4]); \ + vol += (windowed_fir_lut[firidx + 4] * (int)p[poshi + 5 - 4]); \ + vol += (windowed_fir_lut[firidx + 5] * (int)p[poshi + 6 - 4]); \ + vol += (windowed_fir_lut[firidx + 6] * (int)p[poshi + 7 - 4]); \ + vol += (windowed_fir_lut[firidx + 7] * (int)p[poshi + 8 - 4]); \ + vol >>= WFIR_8SHIFT; + + +#define SNDMIX_GETMONOVOL16FIRFILTER \ + int poshi = position >> 16;\ + int poslo = (position & 0xFFFF);\ + int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol1 = (windowed_fir_lut[firidx + 0] * (int)p[poshi + 1 - 4]); \ + vol1 += (windowed_fir_lut[firidx + 1] * (int)p[poshi + 2 - 4]); \ + vol1 += (windowed_fir_lut[firidx + 2] * (int)p[poshi + 3 - 4]); \ + vol1 += (windowed_fir_lut[firidx + 3] * (int)p[poshi + 4 - 4]); \ + int vol2 = (windowed_fir_lut[firidx + 4] * (int)p[poshi + 5 - 4]); \ + vol2 += (windowed_fir_lut[firidx + 5] * (int)p[poshi + 6 - 4]); \ + vol2 += (windowed_fir_lut[firidx + 6] * (int)p[poshi + 7 - 4]); \ + vol2 += (windowed_fir_lut[firidx + 7] * (int)p[poshi + 8 - 4]); \ + int vol = ((vol1 >> 1) + (vol2 >> 1)) >> (WFIR_16BITSHIFT - 1); + + +///////////////////////////////////////////////////////////////////////////// +// Stereo + +// No interpolation +#define SNDMIX_GETSTEREOVOL8NOIDO \ + int vol_l = p[(position >> 16) * 2 ] << 8; \ + int vol_r = p[(position >> 16) * 2 + 1] << 8; + + +#define SNDMIX_GETSTEREOVOL16NOIDO \ + int vol_l = p[(position >> 16) * 2 ]; \ + int vol_r = p[(position >> 16) * 2 + 1]; + + +// Linear Interpolation +#define SNDMIX_GETSTEREOVOL8LINEAR \ + int poshi = position >> 16; \ + int poslo = (position >> 8) & 0xFF; \ + int srcvol_l = p[poshi * 2]; \ + int vol_l = (srcvol_l << 8) + ((int)(poslo * (p[poshi * 2 + 2] - srcvol_l))); \ + int srcvol_r = p[poshi * 2 + 1]; \ + int vol_r = (srcvol_r << 8) + ((int)(poslo * (p[poshi * 2 + 3] - srcvol_r))); + + +#define SNDMIX_GETSTEREOVOL16LINEAR \ + int poshi = position >> 16; \ + int poslo = (position >> 8) & 0xFF; \ + int srcvol_l = p[poshi * 2]; \ + int vol_l = srcvol_l + ((int)(poslo * (p[poshi * 2 + 2] - srcvol_l)) >> 8);\ + int srcvol_r = p[poshi * 2 + 1];\ + int vol_r = srcvol_r + ((int)(poslo * (p[poshi * 2 + 3] - srcvol_r)) >> 8);\ + + +// Spline Interpolation +#define SNDMIX_GETSTEREOVOL8SPLINE \ + int poshi = position >> 16; \ + int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol_l = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 ] + \ + cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 ] + \ + cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 ] + \ + cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 ]) >> SPLINE_8SHIFT; \ + int vol_r = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 + 1] + \ + cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 + 1] + \ + cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 + 1] + \ + cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 + 1]) >> SPLINE_8SHIFT; + + +#define SNDMIX_GETSTEREOVOL16SPLINE \ + int poshi = position >> 16; \ + int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol_l = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 ] + \ + cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 ] + \ + cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 ] + \ + cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 ]) >> SPLINE_16SHIFT; \ + int vol_r = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 + 1] + \ + cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 + 1] + \ + cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 + 1] + \ + cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 + 1]) >> SPLINE_16SHIFT; + + +// fir interpolation +#define SNDMIX_GETSTEREOVOL8FIRFILTER \ + int poshi = position >> 16;\ + int poslo = (position & 0xFFFF);\ + int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol_l = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2]); \ + vol_l += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2]); \ + vol_l >>= WFIR_8SHIFT; \ + int vol_r = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2 + 1]); \ + vol_r += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2 + 1]); \ + vol_r >>= WFIR_8SHIFT; + + +#define SNDMIX_GETSTEREOVOL16FIRFILTER \ + int poshi = position >> 16;\ + int poslo = (position & 0xFFFF);\ + int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol1_l = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2]); \ + vol1_l += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2]); \ + vol1_l += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2]); \ + vol1_l += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2]); \ + int vol2_l = (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2]); \ + vol2_l += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2]); \ + vol2_l += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2]); \ + vol2_l += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2]); \ + int vol_l = ((vol1_l >> 1) + (vol2_l >> 1)) >> (WFIR_16BITSHIFT - 1); \ + int vol1_r = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2 + 1]); \ + vol1_r += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2 + 1]); \ + vol1_r += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2 + 1]); \ + vol1_r += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2 + 1]); \ + int vol2_r = (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2 + 1]); \ + vol2_r += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2 + 1]); \ + vol2_r += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2 + 1]); \ + vol2_r += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2 + 1]); \ + int vol_r = ((vol1_r >> 1) + (vol2_r >> 1)) >> (WFIR_16BITSHIFT - 1); + + +#define SNDMIX_STOREMONOVOL \ + pvol[0] += vol * chan->right_volume; \ + pvol[1] += vol * chan->left_volume; \ + pvol += 2; + + +#define SNDMIX_STORESTEREOVOL \ + pvol[0] += vol_l * chan->right_volume; \ + pvol[1] += vol_r * chan->left_volume; \ + pvol += 2; + + +#define SNDMIX_STOREFASTMONOVOL \ + int v = vol * chan->right_volume; \ + pvol[0] += v; \ + pvol[1] += v; \ + pvol += 2; + + +#define SNDMIX_RAMPMONOVOL \ + left_ramp_volume += chan->left_ramp; \ + right_ramp_volume += chan->right_ramp; \ + pvol[0] += vol * (right_ramp_volume >> VOLUMERAMPPRECISION); \ + pvol[1] += vol * (left_ramp_volume >> VOLUMERAMPPRECISION); \ + pvol += 2; + + +#define SNDMIX_RAMPFASTMONOVOL \ + right_ramp_volume += chan->right_ramp; \ + int fastvol = vol * (right_ramp_volume >> VOLUMERAMPPRECISION); \ + pvol[0] += fastvol; \ + pvol[1] += fastvol; \ + pvol += 2; + + +#define SNDMIX_RAMPSTEREOVOL \ + left_ramp_volume += chan->left_ramp; \ + right_ramp_volume += chan->right_ramp; \ + pvol[0] += vol_l * (right_ramp_volume >> VOLUMERAMPPRECISION); \ + pvol[1] += vol_r * (left_ramp_volume >> VOLUMERAMPPRECISION); \ + pvol += 2; + + +/////////////////////////////////////////////////// +// Resonant Filters + +#define FILT_CLIP(i) CLAMP(i, -65536, 65534) + +// Mono +#define MIX_BEGIN_FILTER \ + int32_t fy1 = channel->filter_y1; \ + int32_t fy2 = channel->filter_y2; \ + int32_t ta; + + +#define MIX_END_FILTER \ + channel->filter_y1 = fy1; \ + channel->filter_y2 = fy2; + + +#define SNDMIX_PROCESSFILTER \ + ta = (vol * chan->filter_a0 + FILT_CLIP(fy1) * chan->filter_b0 + FILT_CLIP(fy2) * chan->filter_b1 \ + + (1 << (FILTERPRECISION - 1))) >> FILTERPRECISION; \ + fy2 = fy1; \ + fy1 = ta; \ + vol = ta; + + +// Stereo +#define MIX_BEGIN_STEREO_FILTER \ + int32_t fy1 = channel->filter_y1; \ + int32_t fy2 = channel->filter_y2; \ + int32_t fy3 = channel->filter_y3; \ + int32_t fy4 = channel->filter_y4; \ + int32_t ta, tb; + + +#define MIX_END_STEREO_FILTER \ + channel->filter_y1 = fy1; \ + channel->filter_y2 = fy2; \ + channel->filter_y3 = fy3; \ + channel->filter_y4 = fy4; \ + + +#define SNDMIX_PROCESSSTEREOFILTER \ + ta = (vol_l * chan->filter_a0 + FILT_CLIP(fy1) * chan->filter_b0 + FILT_CLIP(fy2) * chan->filter_b1 \ + + (1 << (FILTERPRECISION - 1))) >> FILTERPRECISION; \ + tb = (vol_r * chan->filter_a0 + FILT_CLIP(fy3) * chan->filter_b0 + FILT_CLIP(fy4) * chan->filter_b1 \ + + (1 << (FILTERPRECISION - 1))) >> FILTERPRECISION; \ + fy2 = fy1; fy1 = ta; vol_l = ta; \ + fy4 = fy3; fy3 = tb; vol_r = tb; + + +////////////////////////////////////////////////////////// +// Interfaces + +typedef void(* mix_interface_t)(song_voice_t *, int *, int *); + + +#define BEGIN_MIX_INTERFACE(func) \ + static void func(song_voice_t *channel, int *pbuffer, int *pbufmax) \ + { \ + int position; + + +#define END_MIX_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + } + + +// Volume Ramps +#define BEGIN_RAMPMIX_INTERFACE(func) \ + BEGIN_MIX_INTERFACE(func) \ + int right_ramp_volume = channel->right_ramp_volume; \ + int left_ramp_volume = channel->left_ramp_volume; + + +#define END_RAMPMIX_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + channel->right_ramp_volume = right_ramp_volume; \ + channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ + channel->left_ramp_volume = left_ramp_volume; \ + channel->left_volume = left_ramp_volume >> VOLUMERAMPPRECISION; \ + } + + +#define BEGIN_FASTRAMPMIX_INTERFACE(func) \ + BEGIN_MIX_INTERFACE(func) \ + int right_ramp_volume = channel->right_ramp_volume; + + +#define END_FASTRAMPMIX_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + channel->right_ramp_volume = right_ramp_volume; \ + channel->left_ramp_volume = right_ramp_volume; \ + channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ + channel->left_volume = channel->right_volume; \ + } + + +// Mono Resonant Filters +#define BEGIN_MIX_FLT_INTERFACE(func) \ + BEGIN_MIX_INTERFACE(func) \ + MIX_BEGIN_FILTER + + +#define END_MIX_FLT_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + MIX_END_FILTER \ + } + + +#define BEGIN_RAMPMIX_FLT_INTERFACE(func) \ + BEGIN_MIX_INTERFACE(func) \ + int right_ramp_volume = channel->right_ramp_volume; \ + int left_ramp_volume = channel->left_ramp_volume; \ + MIX_BEGIN_FILTER + + +#define END_RAMPMIX_FLT_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + MIX_END_FILTER \ + channel->right_ramp_volume = right_ramp_volume; \ + channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ + channel->left_ramp_volume = left_ramp_volume; \ + channel->left_volume = left_ramp_volume >> VOLUMERAMPPRECISION; \ + } + + +// Stereo Resonant Filters +#define BEGIN_MIX_STFLT_INTERFACE(func) \ + BEGIN_MIX_INTERFACE(func) \ + MIX_BEGIN_STEREO_FILTER + + +#define END_MIX_STFLT_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + MIX_END_STEREO_FILTER \ + } + + +#define BEGIN_RAMPMIX_STFLT_INTERFACE(func) \ + BEGIN_MIX_INTERFACE(func) \ + int right_ramp_volume = channel->right_ramp_volume; \ + int left_ramp_volume = channel->left_ramp_volume; \ + MIX_BEGIN_STEREO_FILTER + + +#define END_RAMPMIX_STFLT_INTERFACE() \ + SNDMIX_ENDSAMPLELOOP \ + MIX_END_STEREO_FILTER \ + channel->right_ramp_volume = right_ramp_volume; \ + channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ + channel->left_ramp_volume = left_ramp_volume; \ + channel->left_volume = left_ramp_volume >> VOLUMERAMPPRECISION; \ + } + +#define BEGIN_RESAMPLE_INTERFACE(func, sampletype, numchannels) \ + void func(sampletype *oldbuf, sampletype *newbuf, unsigned long oldlen, unsigned long newlen) \ + { \ + unsigned long long position = 0; \ + const sampletype *p = oldbuf; \ + sampletype *pvol = newbuf; \ + const sampletype *pbufmax = &newbuf[newlen* numchannels]; \ + unsigned long long increment = (((unsigned long long)oldlen)<<16)/((unsigned long long)newlen); \ + do { + +#define END_RESAMPLE_INTERFACEMONO() \ + *pvol = vol; \ + pvol++; \ + position += increment; \ + } while (pvol < pbufmax); \ + } + +#define END_RESAMPLE_INTERFACESTEREO() \ + pvol[0] = vol_l; \ + pvol[1] = vol_r; \ + pvol += 2; \ + position += increment; \ + } while (pvol < pbufmax); \ + } + + + +///////////////////////////////////////////////////// +// Mono samples functions + +BEGIN_MIX_INTERFACE(Mono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + + +// Volume Ramps +BEGIN_RAMPMIX_INTERFACE(Mono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Fast mono mix for leftvol=rightvol (1 less imul) + +BEGIN_MIX_INTERFACE(FastMono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + + +// Fast Ramps +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Stereo samples +BEGIN_MIX_INTERFACE(Stereo8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + + +// Volume Ramps +BEGIN_RAMPMIX_INTERFACE(Stereo8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Resonant Filter Mix +// Mono Filter Mix +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + + +// Filter + Ramp +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + + +// Stereo Filter Mix +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + + +// Stereo Filter + Ramp +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + + + +// Public resampling Methods ( +BEGIN_RESAMPLE_INTERFACE(ResampleMono8BitFirFilter, signed char, 1) + SNDMIX_GETMONOVOL8FIRFILTER + vol >>= (WFIR_16BITSHIFT-WFIR_8SHIFT); //This is used to compensate, since the code assumes that it always outputs to 16bits + vol = CLAMP(vol,-128,127); +END_RESAMPLE_INTERFACEMONO() + +BEGIN_RESAMPLE_INTERFACE(ResampleMono16BitFirFilter, signed short, 1) + SNDMIX_GETMONOVOL16FIRFILTER + vol = CLAMP(vol,-32768,32767); +END_RESAMPLE_INTERFACEMONO() + +BEGIN_RESAMPLE_INTERFACE(ResampleStereo8BitFirFilter, signed char, 2) + SNDMIX_GETSTEREOVOL8FIRFILTER + vol_l >>= (WFIR_16BITSHIFT-WFIR_8SHIFT); //This is used to compensate, since the code assumes that it always outputs to 16bits + vol_r >>= (WFIR_16BITSHIFT-WFIR_8SHIFT); //This is used to compensate, since the code assumes that it always outputs to 16bits + vol_l = CLAMP(vol_l,-128,127); + vol_r = CLAMP(vol_r,-128,127); +END_RESAMPLE_INTERFACESTEREO() + +BEGIN_RESAMPLE_INTERFACE(ResampleStereo16BitFirFilter, signed short, 2) + SNDMIX_GETSTEREOVOL16FIRFILTER + vol_l = CLAMP(vol_l,-32768,32767); + vol_r = CLAMP(vol_r,-32768,32767); +END_RESAMPLE_INTERFACESTEREO() + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// Mix function tables +// +// +// Index is as follow: +// [b1-b0] format (8-bit-mono, 16-bit-mono, 8-bit-stereo, 16-bit-stereo) +// [b2] ramp +// [b3] filter +// [b5-b4] src type +// +#define MIXNDX_16BIT 0x01 +#define MIXNDX_STEREO 0x02 +#define MIXNDX_RAMP 0x04 +#define MIXNDX_FILTER 0x08 +#define MIXNDX_LINEARSRC 0x10 +#define MIXNDX_SPLINESRC 0x20 +#define MIXNDX_FIRSRC 0x30 + + +// mix_(bits)(m/s)[_filt]_(interp/spline/fir/whatever)[_ramp] +static const mix_interface_t mix_functions[2 * 2 * 16] = { + // No SRC + Mono8BitMix, Mono16BitMix, + Stereo8BitMix, Stereo16BitMix, + Mono8BitRampMix, Mono16BitRampMix, + Stereo8BitRampMix, Stereo16BitRampMix, + + // No SRC, Filter + FilterMono8BitMix, FilterMono16BitMix, + FilterStereo8BitMix, FilterStereo16BitMix, + FilterMono8BitRampMix, FilterMono16BitRampMix, + FilterStereo8BitRampMix, FilterStereo16BitRampMix, + + // Linear SRC + Mono8BitLinearMix, Mono16BitLinearMix, + Stereo8BitLinearMix, Stereo16BitLinearMix, + Mono8BitLinearRampMix, Mono16BitLinearRampMix, + Stereo8BitLinearRampMix, Stereo16BitLinearRampMix, + + // Linear SRC, Filter + FilterMono8BitLinearMix, FilterMono16BitLinearMix, + FilterStereo8BitLinearMix, FilterStereo16BitLinearMix, + FilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix, + FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix, + + // Spline SRC + Mono8BitSplineMix, Mono16BitSplineMix, + Stereo8BitSplineMix, Stereo16BitSplineMix, + Mono8BitSplineRampMix, Mono16BitSplineRampMix, + Stereo8BitSplineRampMix, Stereo16BitSplineRampMix, + + // Spline SRC, Filter + FilterMono8BitSplineMix, FilterMono16BitSplineMix, + FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, + FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, + FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, + + // FirFilter SRC + Mono8BitFirFilterMix, Mono16BitFirFilterMix, + Stereo8BitFirFilterMix, Stereo16BitFirFilterMix, + Mono8BitFirFilterRampMix, Mono16BitFirFilterRampMix, + Stereo8BitFirFilterRampMix, Stereo16BitFirFilterRampMix, + + // FirFilter SRC, Filter + FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, + FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, + FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, + FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix +}; + + +static const mix_interface_t fastmix_functions[2 * 2 * 16] = { + // No SRC + FastMono8BitMix, FastMono16BitMix, + Stereo8BitMix, Stereo16BitMix, + FastMono8BitRampMix, FastMono16BitRampMix, + Stereo8BitRampMix, Stereo16BitRampMix, + + // No SRC, Filter + FilterMono8BitMix, FilterMono16BitMix, + FilterStereo8BitMix, FilterStereo16BitMix, + FilterMono8BitRampMix, FilterMono16BitRampMix, + FilterStereo8BitRampMix, FilterStereo16BitRampMix, + + // Linear SRC + FastMono8BitLinearMix, FastMono16BitLinearMix, + Stereo8BitLinearMix, Stereo16BitLinearMix, + FastMono8BitLinearRampMix, FastMono16BitLinearRampMix, + Stereo8BitLinearRampMix, Stereo16BitLinearRampMix, + + // Linear SRC, Filter + FilterMono8BitLinearMix, FilterMono16BitLinearMix, + FilterStereo8BitLinearMix, FilterStereo16BitLinearMix, + FilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix, + FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix, + + // Spline SRC + FastMono8BitSplineMix, FastMono16BitSplineMix, + Stereo8BitSplineMix, Stereo16BitSplineMix, + FastMono8BitSplineRampMix, FastMono16BitSplineRampMix, + Stereo8BitSplineRampMix, Stereo16BitSplineRampMix, + + // Spline SRC, Filter + FilterMono8BitSplineMix, FilterMono16BitSplineMix, + FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, + FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, + FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, + + // FirFilter SRC + FastMono8BitFirFilterMix, FastMono16BitFirFilterMix, + Stereo8BitFirFilterMix, Stereo16BitFirFilterMix, + FastMono8BitFirFilterRampMix, FastMono16BitFirFilterRampMix, + Stereo8BitFirFilterRampMix, Stereo16BitFirFilterRampMix, + + // FirFilter SRC, Filter + FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, + FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, + FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, + FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix, +}; + + +static int get_sample_count(song_voice_t *chan, int samples) +{ + int loop_start = (chan->flags & CHN_LOOP) ? chan->loop_start : 0; + int increment = chan->increment; + + if (samples <= 0 || !increment || !chan->length) + return 0; + + // Under zero ? + if ((int) chan->position < loop_start) { + if (increment < 0) { + // Invert loop for bidi loops + int delta = ((loop_start - chan->position) << 16) - (chan->position_frac & 0xFFFF); + chan->position = loop_start + (delta >> 16); + chan->position_frac = delta & 0xFFFF; + + if ((int) chan->position < loop_start || + chan->position >= (loop_start + chan->length) / 2) { + chan->position = loop_start; + chan->position_frac = 0; + } + + increment = -increment; + chan->increment = increment; + // go forward + chan->flags &= ~(CHN_PINGPONGFLAG); + + if ((!(chan->flags & CHN_LOOP)) || + (chan->position >= chan->length)) { + chan->position = chan->length; + chan->position_frac = 0; + return 0; + } + } + else { + // We probably didn't hit the loop end yet (first loop), so we do nothing + if ((int) chan->position < 0) + chan->position = 0; + } + } + // Past the end + else if (chan->position >= chan->length) { + // not looping -> stop this channel + if (!(chan->flags & CHN_LOOP)) + return 0; + + if (chan->flags & CHN_PINGPONGLOOP) { + // Invert loop + if (increment > 0) { + increment = -increment; + chan->increment = increment; + } + + chan->flags |= CHN_PINGPONGFLAG; + // adjust loop position + int delta_hi = (chan->position - chan->length); + int delta_lo = 0x10000 - (chan->position_frac & 0xFFFF); + chan->position = chan->length - delta_hi - (delta_lo >> 16); + chan->position_frac = delta_lo & 0xFFFF; + + if (chan->position <= chan->loop_start || chan->position >= chan->length) + chan->position = chan->length - PINGPONG_OFFSET; + } + else { + // This is a bug + if (increment < 0) { + increment = -increment; + chan->increment = increment; + } + + // Restart at loop start + chan->position += loop_start - chan->length; + + if ((int) chan->position < loop_start) + chan->position = chan->loop_start; + } + } + + int position = chan->position; + + // too big increment, and/or too small loop length + if (position < loop_start) { + if (position < 0 || increment < 0) + return 0; + } + + if (position < 0 || position >= (int) chan->length) + return 0; + + int position_frac = (unsigned short) chan->position_frac, + sample_count = samples; + + if (increment < 0) { + int inv = -increment; + int maxsamples = 16384 / ((inv >> 16) + 1); + + if (maxsamples < 2) + maxsamples = 2; + + if (samples > maxsamples) + samples = maxsamples; + + int delta_hi = (inv >> 16) * (samples - 1); + int delta_lo = (inv & 0xffff) * (samples - 1); + int pos_dest = position - delta_hi + ((position_frac - delta_lo) >> 16); + + if (pos_dest < loop_start) { + sample_count = + (unsigned int) (((((long long) position - + loop_start) << 16) + position_frac - + 1) / inv) + 1; + } + } + else { + int maxsamples = 16384 / ((increment >> 16) + 1); + + if (maxsamples < 2) + maxsamples = 2; + + if (samples > maxsamples) + samples = maxsamples; + + int delta_hi = (increment >> 16) * (samples - 1); + int delta_lo = (increment & 0xffff) * (samples - 1); + int pos_dest = position + delta_hi + ((position_frac + delta_lo) >> 16); + + if (pos_dest >= (int) chan->length) { + sample_count = (unsigned int) + (((((long long) chan->length - position) << 16) - position_frac - 1) / increment) + 1; + } + } + + if (sample_count <= 1) + return 1; + else if (sample_count > samples) + return samples; + + return sample_count; +} + + +unsigned int csf_create_stereo_mix(song_t *csf, int count) +{ + int* ofsl, *ofsr; + unsigned int nchused, nchmixed; + + if (!count) + return 0; + + nchused = nchmixed = 0; + + // yuck + if (csf->multi_write) + for (unsigned int nchan = 0; nchan < MAX_CHANNELS; nchan++) + memset(csf->multi_write[nchan].buffer, 0, sizeof(csf->multi_write[nchan].buffer)); + + for (unsigned int nchan = 0; nchan < csf->num_voices; nchan++) { + const mix_interface_t *mix_func_table; + song_voice_t *const channel = &csf->voices[csf->voice_mix[nchan]]; + unsigned int flags; + unsigned int nrampsamples; + int smpcount; + int nsamples; + int *pbuffer; + + if (!channel->current_sample_data) + continue; + + ofsr = &g_dry_rofs_vol; + ofsl = &g_dry_lofs_vol; + flags = 0; + + if (channel->flags & CHN_16BIT) + flags |= MIXNDX_16BIT; + + if (channel->flags & CHN_STEREO) + flags |= MIXNDX_STEREO; + + if (channel->flags & CHN_FILTER) + flags |= MIXNDX_FILTER; + + if (!(channel->flags & CHN_NOIDO) && + !(csf->mix_flags & SNDMIX_NORESAMPLING)) { + // use hq-fir mixer? + if ((csf->mix_flags & (SNDMIX_HQRESAMPLER | SNDMIX_ULTRAHQSRCMODE)) + == (SNDMIX_HQRESAMPLER | SNDMIX_ULTRAHQSRCMODE)) + flags |= MIXNDX_FIRSRC; + else if (csf->mix_flags & SNDMIX_HQRESAMPLER) + flags |= MIXNDX_SPLINESRC; + else + flags |= MIXNDX_LINEARSRC; // use + } + + if ((flags < 0x40) && + (channel->left_volume == channel->right_volume) && + ((!channel->ramp_length) || + (channel->left_ramp == channel->right_ramp))) { + mix_func_table = fastmix_functions; + } else { + mix_func_table = mix_functions; + } + + nsamples = count; + + if (csf->multi_write) { + int master = (csf->voice_mix[nchan] < MAX_CHANNELS) + ? csf->voice_mix[nchan] + : (channel->master_channel - 1); + pbuffer = csf->multi_write[master].buffer; + csf->multi_write[master].used = 1; + } else { + pbuffer = csf->mix_buffer; + } + + nchused++; + //////////////////////////////////////////////////// + unsigned int naddmix = 0; + + do { + nrampsamples = nsamples; + + if (channel->ramp_length > 0) { + if ((int) nrampsamples > channel->ramp_length) + nrampsamples = channel->ramp_length; + } + + smpcount = 1; + + /* Figure out the number of remaining samples, + * unless we're in AdLib or MIDI mode (to prevent + * artificial KeyOffs) + */ + if (!(channel->flags & CHN_ADLIB)) { + smpcount = get_sample_count(channel, nrampsamples); + } + + if (smpcount <= 0) { + // Stopping the channel + channel->current_sample_data = NULL; + channel->length = 0; + channel->position = 0; + channel->position_frac = 0; + channel->ramp_length = 0; + end_channel_ofs(channel, pbuffer, nsamples); + *ofsr += channel->rofs; + *ofsl += channel->lofs; + channel->rofs = channel->lofs = 0; + channel->flags &= ~CHN_PINGPONGFLAG; + break; + } + + // Should we mix this channel ? + + if ((nchmixed >= max_voices && !(csf->mix_flags & SNDMIX_DIRECTTODISK)) + || (!channel->ramp_length && !(channel->left_volume | channel->right_volume))) { + int delta = (channel->increment * (int) smpcount) + (int) channel->position_frac; + channel->position_frac = delta & 0xFFFF; + channel->position += (delta >> 16); + channel->rofs = channel->lofs = 0; + pbuffer += smpcount * 2; + } else { + // Do mixing + + /* Mix the stream, unless we're in AdLib mode */ + if (!(channel->flags & CHN_ADLIB)) { + // Choose function for mixing + mix_interface_t mix_func; + mix_func = channel->ramp_length + ? mix_func_table[flags | MIXNDX_RAMP] + : mix_func_table[flags]; + int *pbufmax = pbuffer + (smpcount * 2); + channel->rofs = -*(pbufmax - 2); + channel->lofs = -*(pbufmax - 1); + + mix_func(channel, pbuffer, pbufmax); + channel->rofs += *(pbufmax - 2); + channel->lofs += *(pbufmax - 1); + pbuffer = pbufmax; + naddmix = 1; + } + } + + nsamples -= smpcount; + + if (channel->ramp_length) { + channel->ramp_length -= smpcount; + if (channel->ramp_length <= 0) { + channel->ramp_length = 0; + channel->right_volume = channel->right_volume_new; + channel->left_volume = channel->left_volume_new; + channel->right_ramp = channel->left_ramp = 0; + + if ((channel->flags & CHN_NOTEFADE) + && (!(channel->fadeout_volume))) { + channel->length = 0; + channel->current_sample_data = NULL; + } + } + } + + } while (nsamples > 0); + + nchmixed += naddmix; + } + + if (csf->multi_write) { + /* mix all adlib onto track one */ + Fmdrv_MixTo(csf->multi_write[0].buffer, count); + } else { + Fmdrv_MixTo(csf->mix_buffer, count); + } + + return nchused; +} diff --git a/src/player/mixutil.c b/src/player/mixutil.c new file mode 100644 index 0000000..4518832 --- /dev/null +++ b/src/player/mixutil.c @@ -0,0 +1,258 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <string.h> + +#include "sndfile.h" + +#include "cmixer.h" + +#define OFSDECAYSHIFT 8 +#define OFSDECAYMASK 0xFF + + +void init_mix_buffer(int *buffer, unsigned int samples) +{ + memset(buffer, 0, samples * sizeof(int)); +} + + +void stereo_fill(int *buffer, unsigned int samples, int* profs, int *plofs) +{ + int rofs = *profs; + int lofs = *plofs; + + if (!rofs && !lofs) { + init_mix_buffer(buffer, samples * 2); + return; + } + + for (unsigned int i = 0; i < samples; i++) { + int x_r = (rofs + (((-rofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + int x_l = (lofs + (((-lofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + + rofs -= x_r; + lofs -= x_l; + buffer[i * 2 ] = x_r; + buffer[i * 2 + 1] = x_l; + } + + *profs = rofs; + *plofs = lofs; +} + + +void end_channel_ofs(song_voice_t *channel, int *buffer, unsigned int samples) +{ + int rofs = channel->rofs; + int lofs = channel->lofs; + + if (!rofs && !lofs) + return; + + for (unsigned int i = 0; i < samples; i++) { + int x_r = (rofs + (((-rofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + int x_l = (lofs + (((-lofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + + rofs -= x_r; + lofs -= x_l; + buffer[i * 2] += x_r; + buffer[i * 2 + 1] += x_l; + } + + channel->rofs = rofs; + channel->lofs = lofs; +} + + +void mono_from_stereo(int *mix_buf, unsigned int samples) +{ + for (unsigned int j, i = 0; i < samples; i++) { + j = i << 1; + mix_buf[i] = (mix_buf[j] + mix_buf[j + 1]) >> 1; + } +} + + +static const float f2ic = (float) (1 << 28); +static const float i2fc = (float) (1.0 / (1 << 28)); + + +void stereo_mix_to_float(const int *src, float *out1, float *out2, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + *out1++ = *src * i2fc; + src++; + + *out2++ = *src * i2fc; + src++; + } +} + + +void float_to_stereo_mix(const float *in1, const float *in2, int *out, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + *out++ = (int) (*in1 * f2ic); + *out++ = (int) (*in2 * f2ic); + in1++; + in2++; + } +} + + +void mono_mix_to_float(const int *src, float *out, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + *out++ = *src * i2fc; + src++; + } +} + + +void float_to_mono_mix(const float *in, int *out, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + *out++ = (int) (*in * f2ic); + in++; + } +} + + +// ---------------------------------------------------------------------------- +// Clip and convert functions +// ---------------------------------------------------------------------------- +// XXX mins/max were int[2] +// +// The original C version was written by Rani Assaf <rani@magic.metawire.com> + + +// Clip and convert to 8 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. +unsigned int clip_32_to_8(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) +{ + unsigned char *p = (unsigned char *) ptr; + + for (unsigned int i = 0; i < samples; i++) { + int n = buffer[i]; + + if (n < MIXING_CLIPMIN) + n = MIXING_CLIPMIN; + else if (n > MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + + if (n < mins[i & 1]) + mins[i & 1] = n; + else if (n > maxs[i & 1]) + maxs[i & 1] = n; + + // 8-bit unsigned + p[i] = (n >> (24 - MIXING_ATTENUATION)) ^ 0x80; + } + + return samples; +} + + +// Clip and convert to 16 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. +unsigned int clip_32_to_16(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) +{ + signed short *p = (signed short *) ptr; + + for (unsigned int i = 0; i < samples; i++) { + int n = buffer[i]; + + if (n < MIXING_CLIPMIN) + n = MIXING_CLIPMIN; + else if (n > MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + + if (n < mins[i & 1]) + mins[i & 1] = n; + else if (n > maxs[i & 1]) + maxs[i & 1] = n; + + // 16-bit signed + p[i] = n >> (16 - MIXING_ATTENUATION); + } + + return samples * 2; +} + + +// Clip and convert to 24 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. +// Note, this is 24bit, not 24-in-32bits. The former is used in .wav. The latter is used in audio IO +unsigned int clip_32_to_24(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) +{ + /* the inventor of 24bit anything should be shot */ + unsigned char *p = (unsigned char *) ptr; + + for (unsigned int i = 0; i < samples; i++) { + int n = buffer[i]; + + if (n < MIXING_CLIPMIN) + n = MIXING_CLIPMIN; + else if (n > MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + + if (n < mins[i & 1]) + mins[i & 1] = n; + else if (n > maxs[i & 1]) + maxs[i & 1] = n; + + // 24-bit signed + n = n >> (8 - MIXING_ATTENUATION); + + /* err, assume same endian */ + memcpy(p, &n, 3); + p += 3; + } + + return samples * 3; +} + + +// Clip and convert to 32 bit(int). mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. +unsigned int clip_32_to_32(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) +{ + signed int *p = (signed int *) ptr; + + for (unsigned int i = 0; i < samples; i++) { + int n = buffer[i]; + + if (n < MIXING_CLIPMIN) + n = MIXING_CLIPMIN; + else if (n > MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + + if (n < mins[i & 1]) + mins[i & 1] = n; + else if (n > maxs[i & 1]) + maxs[i & 1] = n; + + // 32-bit signed + p[i] = (n << MIXING_ATTENUATION); + } + + return samples * 4; +} + diff --git a/src/player/opl-util.c b/src/player/opl-util.c new file mode 100644 index 0000000..cfb32f7 --- /dev/null +++ b/src/player/opl-util.c @@ -0,0 +1,134 @@ +/** + * @file opl-util.cpp + * @brief Utility functions related to OPL chips. + * + * Copyright (C) 2010-2013 Adam Nielsen <malvineous@shikadi.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +//Stripped down version for Schismtracker, in C. + +// this really should be in a header but it's only used in one other file +int fnumToMilliHertz(unsigned int fnum, unsigned int block, + unsigned int conversionFactor); +void milliHertzToFnum(unsigned int milliHertz, + unsigned int *fnum, unsigned int *block, unsigned int conversionFactor); + + +/// Convert the given f-number and block into a note frequency. +/** +* @param fnum +* Input frequency number, between 0 and 1023 inclusive. Values outside this +* range will cause assertion failures. +* +* @param block +* Input block number, between 0 and 7 inclusive. Values outside this range +* will cause assertion failures. +* +* @param conversionFactor +* Conversion factor to use. Normally will be 49716 and occasionally 50000. +* +* @return The converted frequency in milliHertz. +*/ +int fnumToMilliHertz(unsigned int fnum, unsigned int block, + unsigned int conversionFactor) +{ + // Original formula + //return 1000 * conversionFactor * (double)fnum * pow(2, (double)((signed)block - 20)); + + // More efficient version + return (1000ull * conversionFactor * fnum) >> (20 - block); +} +/// Convert a frequency into an OPL f-number +/** +* @param milliHertz +* Input frequency. +* +* @param fnum +* Output frequency number for OPL chip. This is a 10-bit number, so it will +* always be between 0 and 1023 inclusive. +* +* @param block +* Output block number for OPL chip. This is a 3-bit number, so it will +* always be between 0 and 7 inclusive. +* +* @param conversionFactor +* Conversion factor to use. Normally will be 49716 and occasionally 50000. +* +* @post fnum will be set to a value between 0 and 1023 inclusive. block will +* be set to a value between 0 and 7 inclusive. assert() calls inside this +* function ensure this will always be the case. +* +* @note As the block value increases, the frequency difference between two +* adjacent fnum values increases. This means the higher the frequency, +* the less precision is available to represent it. Therefore, converting +* a value to fnum/block and back to milliHertz is not guaranteed to reproduce +* the original value. +*/ +void milliHertzToFnum(unsigned int milliHertz, + unsigned int *fnum, unsigned int *block, unsigned int conversionFactor) +{ + // Special case to avoid divide by zero + if (milliHertz <= 0) { + *block = 0; // actually any block will work + *fnum = 0; + return; + } + + // Special case for frequencies too high to produce + if (milliHertz > 6208431) { + *block = 7; + *fnum = 1023; + return; + } + + /// This formula will provide a pretty good estimate as to the best block to + /// use for a given frequency. It tries to use the lowest possible block + /// number that is capable of representing the given frequency. This is + /// because as the block number increases, the precision decreases (i.e. there + /// are larger steps between adjacent note frequencies.) The 6M constant is + /// the largest frequency (in milliHertz) that can be represented by the + /// block/fnum system. + //int invertedBlock = log2(6208431 / milliHertz); + + // Very low frequencies will produce very high inverted block numbers, but + // as they can all be covered by inverted block 7 (block 0) we can just clip + // the value. + //if (invertedBlock > 7) invertedBlock = 7; + //*block = 7 - invertedBlock; + + // This is a bit more efficient and doesn't need log2() from math.h + if (milliHertz > 3104215) *block = 7; + else if (milliHertz > 1552107) *block = 6; + else if (milliHertz > 776053) *block = 5; + else if (milliHertz > 388026) *block = 4; + else if (milliHertz > 194013) *block = 3; + else if (milliHertz > 97006) *block = 2; + else if (milliHertz > 48503) *block = 1; + else *block = 0; + + // Original formula + //*fnum = milliHertz * pow(2, 20 - *block) / 1000 / conversionFactor + 0.5; + + // Slightly more efficient version + *fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5; + + if (*fnum > 1023) { + (*block)++; + *fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5; + } + + return; +} diff --git a/src/player/snd_fm.c b/src/player/snd_fm.c new file mode 100644 index 0000000..4ee6db8 --- /dev/null +++ b/src/player/snd_fm.c @@ -0,0 +1,385 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "fmopl.h" +#include "snd_fm.h" + +#define MAX_VOICES 256 /* Must not be less than the setting in sndfile.h */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#define OPLNew(x,r) ym3812_init(x, r) +#define OPLResetChip ym3812_reset_chip +#define OPLWrite ym3812_write +#define OPLUpdateOne ym3812_update_one +#define OPLClose ym3812_shutdown + +/* Mostly pulled from my posterior. Original value was 2000, but Manwe says that's too quiet. +It'd help if this was at all connected to the song's mixing volume... +*/ +#define OPL_VOLUME 5000 + +/* +The documentation in this file regarding the output ports, +including the comment "Don't ask me why", are attributed +to Jeffrey S. Lee's article: + Programming the AdLib/Sound Blaster + FM Music Chips + Version 2.0 (24 Feb 1992) +*/ + +static const int oplbase = 0x388; + +// OPL info +static struct OPL* opl = NULL; +static UINT32 oplretval = 0, + oplregno = 0; +static UINT32 fm_active = 0; + +extern int fnumToMilliHertz(unsigned int fnum, unsigned int block, + unsigned int conversionFactor); + +extern void milliHertzToFnum(unsigned int milliHertz, + unsigned int *fnum, unsigned int *block, unsigned int conversionFactor); + + +static void Fmdrv_Outportb(unsigned port, unsigned value) +{ + if (opl == NULL || + ((int) port) < oplbase || + ((int) port) >= oplbase + 4) + return; + + unsigned ind = port - oplbase; + OPLWrite(opl, ind, value); + + if (ind & 1) { + if (oplregno == 4) { + if (value == 0x80) + oplretval = 0x02; + else if (value == 0x21) + oplretval = 0xC0; + } + } + else + oplregno = value; +} + + +static unsigned char Fmdrv_Inportb(unsigned port) +{ + return (((int) port) >= oplbase && + ((int) port) < oplbase + 4) ? oplretval : 0; +} + + +void Fmdrv_Init(int mixfreq) +{ + if (opl != NULL) { + OPLClose(opl); + opl = NULL; + } + //Clock for frequency 49716Hz. Mixfreq is used for output mix frequency. + opl = OPLNew(1789776 * 2, mixfreq); + OPLResetChip(opl); + OPL_Detect(); +} + + +void Fmdrv_MixTo(int *target, int count) +{ + static short *buf = NULL; + static int buf_size = 0; + + if (!fm_active) + return; + + if (buf_size != count * 2) { + int before = buf_size; + buf_size = sizeof(short) * count; + + if (before) { + buf = (short *) realloc(buf, buf_size); + } + else { + buf = (short *) malloc(buf_size); + } + } + + memset(buf, 0, count * 2); + OPLUpdateOne(opl, buf, count); + + /* + static int counter = 0; + + for(int a = 0; a < count; ++a) + buf[a] = ((counter++) & 0x100) ? -10000 : 10000; + */ + + for (int a = 0; a < count; ++a) { + target[a * 2 + 0] += buf[a] * OPL_VOLUME; + target[a * 2 + 1] += buf[a] * OPL_VOLUME; + } +} + + +/***************************************/ + + +static const char PortBases[9] = {0, 1, 2, 8, 9, 10, 16, 17, 18}; +static signed char Pans[MAX_VOICES]; +static const unsigned char *Dtab[MAX_VOICES] = {NULL}; + + +static int SetBase(int c) +{ + return c % 9; +} + + +static void OPL_Byte(unsigned char idx, unsigned char data) +{ + //register int a; + Fmdrv_Outportb(oplbase, idx); // for(a = 0; a < 6; a++) Fmdrv_Inportb(oplbase); + Fmdrv_Outportb(oplbase + 1, data); // for(a = 0; a < 35; a++) Fmdrv_Inportb(oplbase); +} + + +void OPL_NoteOff(int c) +{ + c = SetBase(c); + + if (c<9) { + /* KEYON_BLOCK+c seems to not work alone?? */ + OPL_Byte(KEYON_BLOCK + c, 0); + //OPL_Byte(KSL_LEVEL + Ope, 0xFF); + //OPL_Byte(KSL_LEVEL + 3 + Ope, 0xFF); + } +} + + +/* OPL_NoteOn changes the frequency on specified + channel and guarantees the key is on. (Doesn't + retrig, just turns the note on and sets freq.) + If keyoff is nonzero, doesn't even set the note on. + Could be used for pitch bending also. */ +void OPL_HertzTouch(int c, int milliHertz, int keyoff) +{ + c = SetBase(c); + + if (c >= 9) + return; + + fm_active = 1; + +/* + Bytes A0-B8 - Octave / F-Number / Key-On + + 7 6 5 4 3 2 1 0 + +-----+-----+-----+-----+-----+-----+-----+-----+ + | F-Number (least significant byte) | (A0-A8) + +-----+-----+-----+-----+-----+-----+-----+-----+ + | Unused | Key | Octave | F-Number | (B0-B8) + | | On | | most sig. | + +-----+-----+-----+-----+-----+-----+-----+-----+ +*/ + unsigned int outfnum; + unsigned int outblock; + const int conversion_factor = 49716; // Frequency of OPL. + milliHertzToFnum(milliHertz, &outfnum, &outblock, conversion_factor); + OPL_Byte(0xA0 + c, outfnum & 255); // F-Number low 8 bits + OPL_Byte(0xB0 + c, (keyoff ? 0 : 0x20) // Key on + | ((outfnum >> 8) & 3) // F-number high 2 bits + | (outblock << 2) + ); + +} + + +void OPL_Touch(int c, const unsigned char *D, unsigned vol) +{ + if (!D) { + if (c < MAX_VOICES) + D = Dtab[c]; + if (!D) + return; + } + +//fprintf(stderr, "OPL_Touch(%d, %p:%02X.%02X.%02X.%02X-%02X.%02X.%02X.%02X-%02X.%02X.%02X, %d)\n", +// c, D,D[0],D[1],D[2],D[3],D[4],D[5],D[6],D[7],D[8],D[9],D[10], Vol); + + Dtab[c] = D; + + c = SetBase(c); + + if (c >= 9) + return; + + int Ope = PortBases[c]; + +/* + Bytes 40-55 - Level Key Scaling / Total Level + + 7 6 5 4 3 2 1 0 + +-----+-----+-----+-----+-----+-----+-----+-----+ + | Scaling | Total Level | + | Level | 24 12 6 3 1.5 .75 | <-- dB + +-----+-----+-----+-----+-----+-----+-----+-----+ + bits 7-6 - causes output levels to decrease as the frequency + rises: + 00 - no change + 10 - 1.5 dB/8ve + 01 - 3 dB/8ve + 11 - 6 dB/8ve + bits 5-0 - controls the total output level of the operator. + all bits CLEAR is loudest; all bits SET is the + softest. Don't ask me why. +*/ + OPL_Byte(KSL_LEVEL + Ope, (D[2] & KSL_MASK) | + // (63 + (d[2] & 63) * vol / 63 - vol) - old formula + // (63 - ((63 - (d[2] & 63)) * vol ) / 63) - older formula + // (63 - ((63 - (d[2] & 63)) * vol + 32) / 64) - revised formula, like ST3 + (((int)(D[2] & 63) - 63) * vol + 63 * 64 - 32) / 64 // - optimized revised formula + ); + + OPL_Byte(KSL_LEVEL + 3 + Ope, (D[3] & KSL_MASK) | + (((int)(D[3] & 63) - 63) * vol + 63 * 64 - 32) / 64 + ); + + /* 2008-09-27 Bisqwit: + * Did tests in ST3: The value poked + * to 0x43, minus from 63, is: + * + * OplVol 63 47 31 + * SmpVol + * 64 63 47 31 + * 32 32 24 15 + * 16 16 12 8 + * + * This seems to clearly indicate that the value + * poked is calculated with 63 - round(oplvol*smpvol/64.0). + * + * Also, from the documentation we can deduce that + * the maximum volume to be set is 47.25 dB and that + * each increase by 1 corresponds to 0.75 dB. + * + * Since we know that 6 dB is equivalent to a doubling + * of the volume, we can deduce that an increase or + * decrease by 8 will double / halve the volume. + * + */ +} + + +void OPL_Pan(int c, signed char val) +{ + Pans[c] = val; + /* Doesn't happen immediately! */ +} + + +void OPL_Patch(int c, const unsigned char *D) +{ +//fprintf(stderr, "OPL_Patch(%d, %p:%02X.%02X.%02X.%02X-%02X.%02X.%02X.%02X-%02X.%02X.%02X)\n", +// c, D,D[0],D[1],D[2],D[3],D[4],D[5],D[6],D[7],D[8],D[9],D[10]); + Dtab[c] = D; + + c = SetBase(c); + if(c >= 9)return; + + int Ope = PortBases[c]; + + OPL_Byte(AM_VIB+ Ope, D[0]); + OPL_Byte(ATTACK_DECAY+ Ope, D[4]); + OPL_Byte(SUSTAIN_RELEASE+ Ope, D[6]); + OPL_Byte(WAVE_SELECT+ Ope, D[8]&3);// 6 high bits used elsewhere + + OPL_Byte(AM_VIB+ 3+Ope, D[1]); + OPL_Byte(ATTACK_DECAY+ 3+Ope, D[5]); + OPL_Byte(SUSTAIN_RELEASE+3+Ope, D[7]); + OPL_Byte(WAVE_SELECT+ 3+Ope, D[9]&3);// 6 high bits used elsewhere + + /* feedback, additive synthesis and Panning... */ + OPL_Byte(FEEDBACK_CONNECTION+c, + (D[10] & ~STEREO_BITS) + | (Pans[c]<-32 ? VOICE_TO_LEFT + : Pans[c]>32 ? VOICE_TO_RIGHT + : (VOICE_TO_LEFT | VOICE_TO_RIGHT) + )); +} + + +void OPL_Reset(void) +{ +//fprintf(stderr, "OPL_Reset\n"); + int a; + + for(a = 0; a < 244; a++) + OPL_Byte(a, 0); + + for(a = 0; a < MAX_VOICES; ++a) + Dtab[a] = NULL; + + OPL_Byte(TEST_REGISTER, ENABLE_WAVE_SELECT); + + fm_active = 0; +} + + +int OPL_Detect(void) +{ + SetBase(0); + + /* Reset timers 1 and 2 */ + OPL_Byte(TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); + + /* Reset the IRQ of the FM chip */ + OPL_Byte(TIMER_CONTROL_REGISTER, IRQ_RESET); + + unsigned char ST1 = Fmdrv_Inportb(oplbase); /* Status register */ + + OPL_Byte(TIMER1_REGISTER, 255); + OPL_Byte(TIMER_CONTROL_REGISTER, TIMER2_MASK | TIMER1_START); + + /*_asm xor cx,cx;P1:_asm loop P1*/ + unsigned char ST2 = Fmdrv_Inportb(oplbase); + + OPL_Byte(TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); + OPL_Byte(TIMER_CONTROL_REGISTER, IRQ_RESET); + + int OPLMode = (ST2 & 0xE0) == 0xC0 && !(ST1 & 0xE0); + + if (!OPLMode) + return -1; + + return 0; +} + + +void OPL_Close(void) +{ + OPL_Reset(); +} + diff --git a/src/player/sndmix.c b/src/player/sndmix.c new file mode 100644 index 0000000..3fb8e07 --- /dev/null +++ b/src/player/sndmix.c @@ -0,0 +1,1259 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "sndfile.h" +#include "snd_fm.h" +#include "cmixer.h" + +#include "util.h" /* for clamp */ + +// Volume ramp length, in 1/10 ms +#define VOLUMERAMPLEN 146 // 1.46ms = 64 samples at 44.1kHz + +// VU meter +#define VUMETER_DECAY 16 + +// SNDMIX: These are global flags for playback control +unsigned int max_voices = 32; // ITT it is 1994 + +// Mixing data initialized in +static unsigned int volume_ramp_samples = 64; +unsigned int global_vu_left = 0; +unsigned int global_vu_right = 0; +int32_t g_dry_rofs_vol = 0; +int32_t g_dry_lofs_vol = 0; + +typedef uint32_t (* convert_t)(void *, int *, uint32_t, int *, int *); + + +// see also csf_midi_out_raw in effects.c +void (*csf_midi_out_note)(int chan, const song_note_t *m) = NULL; + + +// The volume we have here is in range 0..(63*255) (0..16065) +// We should keep that range, but convert it into a logarithmic +// one such that a change of 256*8 (2048) corresponds to a halving +// of the volume. +// logvolume = 2^(linvolume / (4096/8)) * (4096/64) +// However, because the resolution of MIDI volumes +// is merely 128 units, we can use a lookup table. +// +// In this table, each value signifies the minimum value +// that volume must be in order for the result to be +// that table index. +static const unsigned short GMvolTransition[128] = +{ + 0, 2031, 4039, 5214, 6048, 6694, 7222, 7669, + 8056, 8397, 8702, 8978, 9230, 9462, 9677, 9877, +10064,10239,10405,10562,10710,10852,10986,11115, +11239,11357,11470,11580,11685,11787,11885,11980, +12072,12161,12248,12332,12413,12493,12570,12645, +12718,12790,12860,12928,12995,13060,13123,13186, +13247,13306,13365,13422,13479,13534,13588,13641, +13693,13745,13795,13844,13893,13941,13988,14034, +14080,14125,14169,14213,14256,14298,14340,14381, +14421,14461,14501,14540,14578,14616,14653,14690, +14727,14763,14798,14833,14868,14902,14936,14970, +15003,15035,15068,15100,15131,15163,15194,15224, +15255,15285,15315,15344,15373,15402,15430,15459, +15487,15514,15542,15569,15596,15623,15649,15675, +15701,15727,15753,15778,15803,15828,15853,15877, +15901,15925,15949,15973,15996,16020,16043,16065, +}; + + +// We use binary search to find the right slot +// with at most 7 comparisons. +static unsigned int find_volume(unsigned short vol) +{ + unsigned int l = 0, r = 128; + + while (l < r) { + unsigned int m = l + ((r - l) / 2); + unsigned short p = GMvolTransition[m]; + + if (p < vol) + l = m + 1; + else + r = m; + } + + return l; +} + + +unsigned int get_freq_from_period(int period, int linear) +{ + if (period <= 0) + return INT_MAX; + else if (linear) + return period; + else + return _muldiv(8363, 1712L << 8, (period << 8)); +} + + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// XXX * I prefixed these with `rn_' to avoid any namespace conflicts +// XXX Needs better naming! +// XXX * Keep inline? +// XXX * Get rid of the pointer passing where it is not needed +// + + +static inline void rn_tremor(song_voice_t *chan, int *vol) +{ + if ((chan->cd_tremor & 192) == 128) + *vol = 0; + + chan->flags |= CHN_FASTVOLRAMP; +} + + +static inline int rn_vibrato(song_t *csf, song_voice_t *chan, int period) +{ + unsigned int vibpos = chan->vibrato_position & 0xFF; + int vdelta; + unsigned int vdepth; + + switch (chan->vib_type) { + case VIB_SINE: + default: + vdelta = sine_table[vibpos]; + break; + case VIB_RAMP_DOWN: + vdelta = ramp_down_table[vibpos]; + break; + case VIB_SQUARE: + vdelta = square_table[vibpos]; + break; + case VIB_RANDOM: + vdelta = 128 * ((double) rand() / RAND_MAX) - 64; + break; + } + + if (csf->flags & SONG_ITOLDEFFECTS) { + vdepth = 5; + vdelta = -vdelta; // yes, IT does vibrato backwards in old-effects mode. try it. + } else { + vdepth = 6; + } + vdelta = (vdelta * (int)chan->vibrato_depth) >> vdepth; + + if (csf->flags & SONG_LINEARSLIDES) { + int l = abs(vdelta); + + if (vdelta < 0) { + vdelta = _muldiv(period, linear_slide_up_table[l >> 2], 0x10000) - period; + + if (l & 0x03) + vdelta += _muldiv(period, fine_linear_slide_up_table[l & 0x03], 0x10000) - period; + } else { + vdelta = _muldiv(period, linear_slide_down_table[l >> 2], 0x10000) - period; + + if (l & 0x03) + vdelta += _muldiv(period, fine_linear_slide_down_table[l & 0x03], 0x10000) - period; + } + } + + period -= vdelta; + + // handle on tick-N, or all ticks if not in old-effects mode + if (!(csf->flags & SONG_FIRSTTICK) || !(csf->flags & SONG_ITOLDEFFECTS)) { + chan->vibrato_position = (vibpos + 4 * chan->vibrato_speed) & 0xFF; + } + + return period; +} + +static inline int rn_sample_vibrato(song_voice_t *chan, int period) +{ + unsigned int vibpos = chan->autovib_position & 0xFF; + int vdelta, adepth; + song_sample_t *pins = chan->ptr_sample; + + /* + 1) Mov AX, [SomeVariableNameRelatingToVibrato] + 2) Add AL, Rate + 3) AdC AH, 0 + 4) AH contains the depth of the vibrato as a fine-linear slide. + 5) Mov [SomeVariableNameRelatingToVibrato], AX ; For the next cycle. + */ + + adepth = chan->autovib_depth; // (1) + adepth += pins->vib_rate & 0xff; // (2 & 3) + /* need this cast -- if adepth is unsigned, large autovib will crash the mixer (why? I don't know!) + but if vib_depth is changed to signed, that screws up other parts of the code. ugh. */ + adepth = MIN(adepth, (int) (pins->vib_depth << 8)); + chan->autovib_depth = adepth; // (5) + adepth >>= 8; // (4) + + chan->autovib_position += pins->vib_speed; + + switch(pins->vib_type) { + case VIB_SINE: + default: + vdelta = sine_table[vibpos]; + break; + case VIB_RAMP_DOWN: + vdelta = ramp_down_table[vibpos]; + break; + case VIB_SQUARE: + vdelta = square_table[vibpos]; + break; + case VIB_RANDOM: + vdelta = 128 * ((double) rand() / RAND_MAX) - 64; + break; + } + vdelta = (vdelta * adepth) >> 6; + + int l = abs(vdelta); + if (vdelta < 0) { + vdelta = _muldiv(period, linear_slide_up_table[l >> 2], 0x10000) - period; + + if (l & 0x03) + vdelta += _muldiv(period, fine_linear_slide_up_table[l & 0x03], 0x10000) - period; + } else { + vdelta = _muldiv(period, linear_slide_down_table[l >> 2], 0x10000) - period; + + if (l & 0x03) + vdelta += _muldiv(period, fine_linear_slide_down_table[l & 0x03], 0x10000) - period; + } + + return period - vdelta; +} + + +static inline void rn_process_envelope(song_voice_t *chan, int *nvol) +{ + song_instrument_t *penv = chan->ptr_instrument; + int vol = *nvol; + + // Volume Envelope + if (chan->flags & CHN_VOLENV && penv->vol_env.nodes) { + int envpos = chan->vol_env_position; + unsigned int pt = penv->vol_env.nodes - 1; + + for (unsigned int i = 0; i < (unsigned int)(penv->vol_env.nodes - 1); i++) { + if (envpos <= penv->vol_env.ticks[i]) { + pt = i; + break; + } + } + + int x2 = penv->vol_env.ticks[pt]; + int x1, envvol; + + if (envpos >= x2) { + envvol = penv->vol_env.values[pt] << 2; + x1 = x2; + } else if (pt) { + envvol = penv->vol_env.values[pt-1] << 2; + x1 = penv->vol_env.ticks[pt-1]; + } else { + envvol = 0; + x1 = 0; + } + + if (envpos > x2) + envpos = x2; + + if (x2 > x1 && envpos > x1) { + envvol += ((envpos - x1) * (((int)penv->vol_env.values[pt]<<2) - envvol)) / (x2 - x1); + } + + envvol = CLAMP(envvol, 0, 256); + vol = (vol * envvol) >> 8; + } + + // Panning Envelope + if ((chan->flags & CHN_PANENV) && (penv->pan_env.nodes)) { + int envpos = chan->pan_env_position; + unsigned int pt = penv->pan_env.nodes - 1; + + for (unsigned int i=0; i<(unsigned int)(penv->pan_env.nodes-1); i++) { + if (envpos <= penv->pan_env.ticks[i]) { + pt = i; + break; + } + } + + int x2 = penv->pan_env.ticks[pt], y2 = penv->pan_env.values[pt]; + int x1, envpan; + + if (envpos >= x2) { + envpan = y2; + x1 = x2; + } else if (pt) { + envpan = penv->pan_env.values[pt-1]; + x1 = penv->pan_env.ticks[pt-1]; + } else { + envpan = 128; + x1 = 0; + } + + if (x2 > x1 && envpos > x1) { + envpan += ((envpos - x1) * (y2 - envpan)) / (x2 - x1); + } + + envpan = CLAMP(envpan, 0, 64); + + int pan = chan->final_panning; + + if (pan >= 128) { + pan += ((envpan - 32) * (256 - pan)) / 32; + } else { + pan += ((envpan - 32) * (pan)) / 32; + } + + chan->final_panning = pan; + } + + // FadeOut volume + if (chan->flags & CHN_NOTEFADE) { + unsigned int fadeout = penv->fadeout; + + if (fadeout) { + chan->fadeout_volume -= fadeout << 1; + + if (chan->fadeout_volume <= 0) + chan->fadeout_volume = 0; + + vol = (vol * chan->fadeout_volume) >> 16; + } else if (!chan->fadeout_volume) { + vol = 0; + } + } + + // Pitch/Pan separation + if (penv->pitch_pan_separation && chan->final_panning && chan->note) { + // PPS value is 1/512, i.e. PPS=1 will adjust by 8/512 = 1/64 for each 8 semitones + // with PPS = 32 / PPC = C-5, E-6 will pan hard right (and D#6 will not) + chan->final_panning += ((int) (chan->note - penv->pitch_pan_center - 1) + * penv->pitch_pan_separation) / 4; + } + + *nvol = vol; +} + + +static inline int rn_arpeggio(song_t *csf, song_voice_t *chan, int period) +{ + int a; + + switch ((csf->current_speed - csf->tick_count) % 3) { + case 1: + a = chan->mem_arpeggio >> 4; + break; + case 2: + a = chan->mem_arpeggio & 0xf; + break; + default: + a = 0; + } + + if (!a) + return period; + + a = linear_slide_up_table[a * 16]; + return ((csf->flags & SONG_LINEARSLIDES) + ? _muldiv(period, a, 65536) + : _muldiv(period, 65536, a)); +} + + +static inline void rn_pitch_filter_envelope(song_voice_t *chan, int *nenvpitch, int *nperiod) +{ + song_instrument_t *penv = chan->ptr_instrument; + int envpos = chan->pitch_env_position; + unsigned int pt = penv->pitch_env.nodes - 1; + int period = *nperiod; + int envpitch = *nenvpitch; + + for (unsigned int i = 0; i < (unsigned int)(penv->pitch_env.nodes - 1); i++) { + if (envpos <= penv->pitch_env.ticks[i]) { + pt = i; + break; + } + } + + int x2 = penv->pitch_env.ticks[pt]; + int x1; + + if (envpos >= x2) { + envpitch = (((int)penv->pitch_env.values[pt]) - 32) * 8; + x1 = x2; + } else if (pt) { + envpitch = (((int)penv->pitch_env.values[pt - 1]) - 32) * 8; + x1 = penv->pitch_env.ticks[pt - 1]; + } else { + envpitch = 0; + x1 = 0; + } + + if (envpos > x2) + envpos = x2; + + if (x2 > x1 && envpos > x1) { + int envpitchdest = (((int)penv->pitch_env.values[pt]) - 32) * 8; + envpitch += ((envpos - x1) * (envpitchdest - envpitch)) / (x2 - x1); + } + + // clamp to -255/255? + envpitch = CLAMP(envpitch, -256, 256); + + // Pitch Envelope + if (!(penv->flags & ENV_FILTER)) { + int l = abs(envpitch); + + if (l > 255) + l = 255; + + period = _muldiv(period, (envpitch < 0 ? + linear_slide_down_table : linear_slide_up_table)[l], 0x10000); + } + + *nperiod = period; + *nenvpitch = envpitch; +} + + +static inline void _process_envelope(song_voice_t *chan, song_instrument_t *penv, song_envelope_t *envelope, + int *position, uint32_t env_flag, uint32_t loop_flag, uint32_t sus_flag, + uint32_t fade_flag) +{ + int start = 0, end = 0x7fffffff; + + if (!(chan->flags & env_flag)) { + return; + } + + (*position)++; + + if ((penv->flags & sus_flag) && !(chan->flags & CHN_KEYOFF)) { + start = envelope->ticks[envelope->sustain_start]; + end = envelope->ticks[envelope->sustain_end] + 1; + fade_flag = 0; + } else if (penv->flags & loop_flag) { + start = envelope->ticks[envelope->loop_start]; + end = envelope->ticks[envelope->loop_end] + 1; + fade_flag = 0; + } else { + // End of envelope (?) + start = end = envelope->ticks[envelope->nodes - 1]; + } + if (*position >= end) { + if (fade_flag && !envelope->values[envelope->nodes - 1]) { + chan->fadeout_volume = chan->final_volume = 0; + } + *position = start; + chan->flags |= fade_flag; // only relevant for volume envelope + } +} + +static inline void rn_increment_env_pos(song_voice_t *chan) +{ + song_instrument_t *penv = chan->ptr_instrument; + + _process_envelope(chan, penv, &penv->vol_env, &chan->vol_env_position, + CHN_VOLENV, ENV_VOLLOOP, ENV_VOLSUSTAIN, CHN_NOTEFADE); + _process_envelope(chan, penv, &penv->pan_env, &chan->pan_env_position, + CHN_PANENV, ENV_PANLOOP, ENV_PANSUSTAIN, 0); + _process_envelope(chan, penv, &penv->pitch_env, &chan->pitch_env_position, + CHN_PITCHENV, ENV_PITCHLOOP, ENV_PITCHSUSTAIN, 0); +} + + +static inline int rn_update_sample(song_t *csf, song_voice_t *chan, int nchan, int master_vol) +{ + // Adjusting volumes + if (csf->mix_channels < 2 || (csf->flags & SONG_NOSTEREO)) { + chan->right_volume_new = (chan->final_volume * master_vol) >> 8; + chan->left_volume_new = chan->right_volume_new; + } else if ((chan->flags & CHN_SURROUND) && !(csf->mix_flags & SNDMIX_NOSURROUND)) { + chan->right_volume_new = (chan->final_volume * master_vol) >> 8; + chan->left_volume_new = -chan->right_volume_new; + } else { + int pan = ((int) chan->final_panning) - 128; + pan *= (int) csf->pan_separation; + pan /= 128; + + + pan += 128; + pan = CLAMP(pan, 0, 256); + + if (csf->mix_flags & SNDMIX_REVERSESTEREO) + pan = 256 - pan; + + int realvol = (chan->final_volume * master_vol) >> (8 - 1); + + chan->left_volume_new = (realvol * pan) >> 8; + chan->right_volume_new = (realvol * (256 - pan)) >> 8; + } + + // Clipping volumes + if (chan->right_volume_new > 0xFFFF) + chan->right_volume_new = 0xFFFF; + + if (chan->left_volume_new > 0xFFFF) + chan->left_volume_new = 0xFFFF; + + // Check IDO + if (csf->mix_flags & SNDMIX_NORESAMPLING) { + chan->flags &= ~(CHN_HQSRC); + chan->flags |= CHN_NOIDO; + } else { + chan->flags &= ~(CHN_NOIDO | CHN_HQSRC); + + if (chan->increment == 0x10000) { + chan->flags |= CHN_NOIDO; + } else { + if (!(csf->mix_flags & SNDMIX_HQRESAMPLER) && + !(csf->mix_flags & SNDMIX_ULTRAHQSRCMODE)) { + if (chan->increment >= 0xFF00) + chan->flags |= CHN_NOIDO; + } + } + } + + chan->right_volume_new >>= MIXING_ATTENUATION; + chan->left_volume_new >>= MIXING_ATTENUATION; + chan->right_ramp = + chan->left_ramp = 0; + + // Checking Ping-Pong Loops + if (chan->flags & CHN_PINGPONGFLAG) + chan->increment = -chan->increment; + + if (chan->flags & CHN_MUTE) { + chan->left_volume = chan->right_volume = 0; + } else if (!(csf->mix_flags & SNDMIX_NORAMPING) && + chan->flags & CHN_VOLUMERAMP && + (chan->right_volume != chan->right_volume_new || + chan->left_volume != chan->left_volume_new)) { + // Setting up volume ramp + int ramp_length = volume_ramp_samples; + int right_delta = ((chan->right_volume_new - chan->right_volume) << VOLUMERAMPPRECISION); + int left_delta = ((chan->left_volume_new - chan->left_volume) << VOLUMERAMPPRECISION); + + if (csf->mix_flags & SNDMIX_HQRESAMPLER) { + if (chan->right_volume | chan->left_volume && + chan->right_volume_new | chan->left_volume_new && + !(chan->flags & CHN_FASTVOLRAMP)) { + ramp_length = csf->buffer_count; + + int l = (1 << (VOLUMERAMPPRECISION - 1)); + int r =(int) volume_ramp_samples; + + ramp_length = CLAMP(ramp_length, l, r); + } + } + + chan->right_ramp = right_delta / ramp_length; + chan->left_ramp = left_delta / ramp_length; + chan->right_volume = chan->right_volume_new - ((chan->right_ramp * ramp_length) >> VOLUMERAMPPRECISION); + chan->left_volume = chan->left_volume_new - ((chan->left_ramp * ramp_length) >> VOLUMERAMPPRECISION); + + if (chan->right_ramp | chan->left_ramp) { + chan->ramp_length = ramp_length; + } else { + chan->flags &= ~CHN_VOLUMERAMP; + chan->right_volume = chan->right_volume_new; + chan->left_volume = chan->left_volume_new; + } + } else { + chan->flags &= ~CHN_VOLUMERAMP; + chan->right_volume = chan->right_volume_new; + chan->left_volume = chan->left_volume_new; + } + + chan->right_ramp_volume = chan->right_volume << VOLUMERAMPPRECISION; + chan->left_ramp_volume = chan->left_volume << VOLUMERAMPPRECISION; + + // Adding the channel in the channel list + csf->voice_mix[csf->num_voices++] = nchan; + + if (csf->num_voices >= MAX_VOICES) + return 0; + + return 1; +} + + +// XXX Rename this +static inline void rn_gen_key(song_t *csf, song_voice_t *chan, int chan_num, int freq, int vol) +{ + if (chan->flags & CHN_MUTE) { + // don't do anything + return; + } else if (csf->flags & SONG_INSTRUMENTMODE && + chan->ptr_instrument && + chan->ptr_instrument->midi_channel_mask > 0) { + + // Vol maximum is 64*64 here. (4096) + int volume = vol; + + if ((chan->flags & CHN_ADLIB) && volume > 0) { + // This gives a value in the range 0..127. + //int o = volume; + volume = find_volume((unsigned short) volume) * chan->instrument_volume / 64; + //fprintf(stderr, "%d -> %d[%d]\n", o, volume, chan->instrument_volume); + } else { + // This gives a value in the range 0..127. + volume = volume * chan->instrument_volume / 8192; + } + + } + if (chan->flags & CHN_ADLIB) { + // Scaling is needed to get a frequency that matches with ST3 notes. + // 8363 is st3s middle C sample rate. 261.625 is the Hertz for middle C in a tempered scale (A4 = 440) + //Also, note that to be true to ST3, the frequencies should be quantized, like using the glissando control. + + int oplmilliHertz = (long long int)freq*261625L/8363L; + OPL_HertzTouch(chan_num, oplmilliHertz, chan->flags & CHN_KEYOFF); + + // ST32 ignores global & master volume in adlib mode, guess we should do the same -Bisqwit + OPL_Touch(chan_num, NULL, vol * chan->instrument_volume * 63 / (1 << 20)); + } +} + + +static inline void update_vu_meter(song_voice_t *chan) +{ + // Update VU-Meter (final_volume is 14-bit) + // TODO: missing background channels by doing it this way. + // need to use nMasterCh, add the vu meters for each physical voice, and bit shift. + uint32_t vutmp = chan->final_volume >> (14 - 8); + if (vutmp > 0xFF) vutmp = 0xFF; + if (chan->flags & CHN_ADLIB) { + if (chan->strike>2) { chan->vu_meter=(0xFF*chan->final_volume)>>14;} + // fake VU decay (intentionally similar to ST3) + if (chan->vu_meter > VUMETER_DECAY) { + chan->vu_meter -= VUMETER_DECAY; + } else { + chan->vu_meter = 0; + } + if (chan->vu_meter >= 0x100) { + chan->vu_meter = vutmp; + } + } else if (vutmp && chan->current_sample_data) { + // can't fake the funk + int n; + int pos = chan->position; // necessary on 64-bit systems (sometimes pos == -1, weird) + if (chan->flags & CHN_16BIT) { + const signed short *p = (signed short *)(chan->current_sample_data); + if (chan->flags & CHN_STEREO) + n = p[2 * pos]; + else + n = p[pos]; + n >>= 8; + } else { + const signed char *p = (signed char *)(chan->current_sample_data); + if (chan->flags & CHN_STEREO) + n = p[2 * pos]; + else + n = p[pos]; + } + if (n < 0) + n = -n; + vutmp *= n; + vutmp >>= 7; // 0..255 + chan->vu_meter = vutmp; + } else { + chan->vu_meter = 0; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +int csf_init_player(song_t *csf, int reset) +{ + if (max_voices > MAX_VOICES) + max_voices = MAX_VOICES; + + csf->mix_frequency = CLAMP(csf->mix_frequency, 4000, MAX_SAMPLE_RATE); + volume_ramp_samples = (csf->mix_frequency * VOLUMERAMPLEN) / 100000; + + if (volume_ramp_samples < 8) + volume_ramp_samples = 8; + + if (csf->mix_flags & SNDMIX_NORAMPING) + volume_ramp_samples = 2; + + g_dry_rofs_vol = g_dry_lofs_vol = 0; + + if (reset) { + global_vu_left = 0; + global_vu_right = 0; + } + + initialize_eq(reset, csf->mix_frequency); + + // retarded hackaround to get adlib to suck less + if (csf->mix_frequency != 4000) + Fmdrv_Init(csf->mix_frequency); + OPL_Reset(); + return 1; +} + + +unsigned int csf_read(song_t *csf, void * v_buffer, unsigned int bufsize) +{ + uint8_t * buffer = (uint8_t *)v_buffer; + convert_t convert_func = clip_32_to_8; + int32_t vu_min[2]; + int32_t vu_max[2]; + unsigned int bufleft, max, sample_size, count, smpcount, mix_stat=0; + + vu_min[0] = vu_min[1] = 0x7FFFFFFF; + vu_max[0] = vu_max[1] = -0x7FFFFFFF; + + + csf->mix_stat = 0; + sample_size = csf->mix_channels; + + if (csf->mix_bits_per_sample == 16) { sample_size *= 2; convert_func = clip_32_to_16; } + else if (csf->mix_bits_per_sample == 24) { sample_size *= 3; convert_func = clip_32_to_24; } + else if (csf->mix_bits_per_sample == 32) { sample_size *= 4; convert_func = clip_32_to_32; } + + max = bufsize / sample_size; + + if (!max || !buffer) { + return 0; + } + + bufleft = max; + + if (csf->flags & SONG_ENDREACHED) + bufleft = 0; // skip the loop + + while (bufleft > 0) { + // Update Channel Data + + if (!csf->buffer_count) { + if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) + csf->buffer_count = bufleft; + + if (!csf_read_note(csf)) { + csf->flags |= SONG_ENDREACHED; + + if (csf->stop_at_order > -1) + return 0; /* faster */ + + if (bufleft == max) + break; + + if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) + csf->buffer_count = bufleft; + } + + if (!csf->buffer_count) + break; + } + + count = csf->buffer_count; + + if (count > MIXBUFFERSIZE) + count = MIXBUFFERSIZE; + + if (count > bufleft) + count = bufleft; + + if (!count) + break; + + smpcount = count; + + // Resetting sound buffer + stereo_fill(csf->mix_buffer, smpcount, &g_dry_rofs_vol, &g_dry_lofs_vol); + + if (csf->mix_channels >= 2) { + smpcount *= 2; + csf->mix_stat += csf_create_stereo_mix(csf, count); + } else { + csf->mix_stat += csf_create_stereo_mix(csf, count); + mono_from_stereo(csf->mix_buffer, count); + } + + // Handle eq + if (csf->mix_channels >= 2) + eq_stereo(csf, csf->mix_buffer, count); + else + eq_mono(csf, csf->mix_buffer, count); + + mix_stat++; + + if (csf->multi_write) { + /* multi doesn't actually write meaningful data into 'buffer', so we can use that + as temp space for converting */ + for (unsigned int n = 0; n < 64; n++) { + if (csf->multi_write[n].used) { + unsigned int bytes = convert_func(buffer, csf->multi_write[n].buffer, + smpcount, vu_min, vu_max); + csf->multi_write[n].write(csf->multi_write[n].data, buffer, bytes); + } else { + csf->multi_write[n].silence(csf->multi_write[n].data, + smpcount * ((csf->mix_bits_per_sample + 7) / 8)); + } + } + } else { + // Perform clipping + VU-Meter + buffer += convert_func(buffer, csf->mix_buffer, smpcount, vu_min, vu_max); + } + + // Buffer ready + bufleft -= count; + csf->buffer_count -= count; + } + + if (bufleft) + memset(buffer, (csf->mix_bits_per_sample == 8) ? 0x80 : 0, bufleft * sample_size); + + // VU-Meter + //Reduce range to 8bits signed (-128 to 127). + vu_min[0] >>= 19; + vu_min[1] >>= 19; + vu_max[0] >>= 19; + vu_max[1] >>= 19; + + if (vu_max[0] < vu_min[0]) + vu_max[0] = vu_min[0]; + + if (vu_max[1] < vu_min[1]) + vu_max[1] = vu_min[1]; + + global_vu_left = (unsigned int)(vu_max[0] - vu_min[0]); + + global_vu_right = (unsigned int)(vu_max[1] - vu_min[1]); + + if (mix_stat) { + csf->mix_stat += mix_stat - 1; + csf->mix_stat /= mix_stat; + } + + return max - bufleft; +} + + + +///////////////////////////////////////////////////////////////////////////// +// Handles navigation/effects + +static int increment_order(song_t *csf) +{ + csf->process_row = csf->break_row; /* [ProcessRow = BreakRow] */ + csf->break_row = 0; /* [BreakRow = 0] */ + + /* some ugly copypasta, this should be less dumb */ + if (csf->flags & SONG_PATTERNPLAYBACK) { + /* process_order is hijacked as a "playback initiated" flag -- otherwise repeat count + would be incremented as soon as pattern playback started. (this is a stupid hack) */ + if (csf->process_order) { + if (++csf->repeat_count) { + if (UNLIKELY(csf->repeat_count < 0)) { + csf->repeat_count = 1; // it overflowed! + } + } else { + csf->process_row = PROCESS_NEXT_ORDER; + return 0; + } + } else { + csf->process_order = 1; + } + } else if (!(csf->flags & SONG_ORDERLOCKED)) { + /* [Increase ProcessOrder] */ + /* [while Order[ProcessOrder] = 0xFEh, increase ProcessOrder] */ + do { + csf->process_order++; + } while (csf->orderlist[csf->process_order] == ORDER_SKIP); + + /* [if Order[ProcessOrder] = 0xFFh, ProcessOrder = 0] (... or just stop playing) */ + if (csf->orderlist[csf->process_order] == ORDER_LAST) { + if (++csf->repeat_count) { + if (UNLIKELY(csf->repeat_count < 0)) { + csf->repeat_count = 1; // it overflowed! + } + } else { + csf->process_row = PROCESS_NEXT_ORDER; + return 0; + } + + csf->process_order = 0; + while (csf->orderlist[csf->process_order] == ORDER_SKIP) + csf->process_order++; + } + if (csf->orderlist[csf->process_order] >= MAX_PATTERNS) { + // what the butt? + csf->process_row = PROCESS_NEXT_ORDER; + return 0; + } + + /* [CurrentPattern = Order[ProcessOrder]] */ + csf->current_order = csf->process_order; + csf->current_pattern = csf->orderlist[csf->process_order]; + } + + if (!csf->pattern_size[csf->current_pattern] || !csf->patterns[csf->current_pattern]) { + /* okay, this is wrong. allocate the pattern _NOW_ */ + csf->patterns[csf->current_pattern] = csf_allocate_pattern(64); + csf->pattern_size[csf->current_pattern] = 64; + csf->pattern_alloc_size[csf->current_pattern] = 64; + } + + if (csf->process_row >= csf->pattern_size[csf->current_pattern]) { + // Cxx to row beyond end of pattern: use 0 instead + csf->process_row = 0; + } + + return 1; +} + + +int csf_process_tick(song_t *csf) +{ + csf->flags &= ~SONG_FIRSTTICK; + /* [Decrease tick counter. Is tick counter 0?] */ + if (--csf->tick_count == 0) { + /* [-- Yes --] */ + + /* [Tick counter = Tick counter set (the current 'speed')] */ + csf->tick_count = csf->current_speed; + + /* [Decrease row counter. Is row counter 0?] */ + if (--csf->row_count <= 0) { + /* [-- Yes --] */ + + /* [Row counter = 1] + this uses zero, in order to simplify SEx effect handling -- SEx has no effect if a + channel to its left has already set the delay value. thus we set the row counter + there to (value + 1) which is never zero, but 0 and 1 are fundamentally equivalent + as far as csf_process_tick is concerned. */ + csf->row_count = 0; + + /* [Increase ProcessRow. Is ProcessRow > NumberOfRows?] */ + if (++csf->process_row >= csf->pattern_size[csf->current_pattern]) { + /* [-- Yes --] */ + + if (!increment_order(csf)) + return 0; + } /* else [-- No --] */ + + /* [CurrentRow = ProcessRow] */ + csf->row = csf->process_row; + + /* [Update Pattern Variables] + (this is handled along with update effects) */ + csf->flags |= SONG_FIRSTTICK; + } else { + /* [-- No --] */ + /* Call update-effects for each channel. */ + } + + + // Reset channel values + song_voice_t *chan = csf->voices; + song_note_t *m = csf->patterns[csf->current_pattern] + csf->row * MAX_CHANNELS; + + for (unsigned int nchan=0; nchan<MAX_CHANNELS; chan++, nchan++, m++) { + // this is where we're going to spit out our midi + // commands... ALL WE DO is dump raw midi data to + // our super-secret "midi buffer" + // -mrsb + if (csf_midi_out_note) + csf_midi_out_note(nchan, m); + + chan->row_note = m->note; + + if (m->instrument) + chan->last_instrument = m->instrument; + + chan->row_instr = m->instrument; + chan->row_voleffect = m->voleffect; + chan->row_volparam = m->volparam; + chan->row_effect = m->effect; + chan->row_param = m->param; + + chan->left_volume = chan->left_volume_new; + chan->right_volume = chan->right_volume_new; + chan->flags &= ~(CHN_PORTAMENTO | CHN_VIBRATO | CHN_TREMOLO); + chan->n_command = 0; + } + + csf_process_effects(csf, 1); + } else { + /* [-- No --] */ + /* [Update effects for each channel as required.] */ + + if (csf_midi_out_note) { + song_note_t *m = csf->patterns[csf->current_pattern] + csf->row * MAX_CHANNELS; + + for (unsigned int nchan=0; nchan<MAX_CHANNELS; nchan++, m++) { + /* m==NULL allows schism to receive notification of SDx and Scx commands */ + csf_midi_out_note(nchan, NULL); + } + } + + csf_process_effects(csf, 0); + } + + return 1; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Handles envelopes & mixer setup + +int csf_read_note(song_t *csf) +{ + song_voice_t *chan; + unsigned int cn; + + // Checking end of row ? + if (csf->flags & SONG_PAUSED) { + if (!csf->current_speed) + csf->current_speed = csf->initial_speed ?: 6; + if (!csf->current_tempo) + csf->current_tempo = csf->initial_tempo ?: 125; + + csf->flags &= ~SONG_FIRSTTICK; + + if (--csf->tick_count == 0) { + csf->tick_count = csf->current_speed; + if (--csf->row_count <= 0) { + csf->row_count = 0; + //csf->flags |= SONG_FIRSTTICK; + } + // clear channel values (similar to csf_process_tick) + for (cn = 0, chan = csf->voices; cn < MAX_CHANNELS; cn++, chan++) { + chan->row_note = 0; + chan->row_instr = 0; + chan->row_voleffect = 0; + chan->row_volparam = 0; + chan->row_effect = 0; + chan->row_param = 0; + chan->n_command = 0; + } + } + csf_process_effects(csf, 0); + } else { + if (!csf_process_tick(csf)) + return 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + + if (!csf->current_tempo) + return 0; + + csf->buffer_count = (csf->mix_frequency * 5 * csf->tempo_factor) / (csf->current_tempo << 8); + + // chaseback hoo hah + if (csf->stop_at_order > -1 && csf->stop_at_row > -1) { + if (csf->stop_at_order <= (signed) csf->current_order && + csf->stop_at_row <= (signed) csf->row) { + return 0; + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // Update channels data + + // Master Volume + Pre-Amplification / Attenuation setup + uint32_t master_vol = csf->mixing_volume << 2; // yields maximum of 0x200 + + csf->num_voices = 0; + + for (cn = 0, chan = csf->voices; cn < MAX_VOICES; cn++, chan++) { + /*if(cn == 0 || cn == 1) + fprintf(stderr, "considering channel %d (per %d, pos %d/%d, flags %X)\n", + (int)cn, chan->period, chan->position, chan->length, chan->flags);*/ + + if (chan->flags & CHN_NOTEFADE && + !(chan->fadeout_volume | chan->right_volume | chan->left_volume)) { + chan->length = 0; + chan->rofs = + chan->lofs = 0; + continue; + } + + // Check for unused channel + if (cn >= MAX_CHANNELS && !chan->length) { + continue; + } + + // Reset channel data + chan->increment = 0; + chan->final_volume = 0; + chan->final_panning = chan->panning + chan->pan_swing + chan->panbrello_delta; + chan->ramp_length = 0; + + // Calc Frequency + if (chan->period && (chan->length || (chan->flags & CHN_ADLIB))) { + int vol = chan->volume; + + if (chan->flags & CHN_TREMOLO) + vol += chan->tremolo_delta; + + vol = CLAMP(vol, 0, 256); + + // Tremor + if (chan->n_command == FX_TREMOR) + rn_tremor(chan, &vol); + + // Clip volume + vol = CLAMP(vol, 0, 0x100); + vol <<= 6; + + // Process Envelopes + if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) { + rn_process_envelope(chan, &vol); + } else { + // No Envelope: key off => note cut + // 1.41-: CHN_KEYOFF|CHN_NOTEFADE + if (chan->flags & CHN_NOTEFADE) { + chan->fadeout_volume = 0; + vol = 0; + } + } + + // vol is 14-bits + if (vol) { + // IMPORTANT: chan->final_volume is 14 bits !!! + // -> _muldiv( 14+7, 6+6, 18); => RealVolume: 14-bit result (21+12-19) + chan->final_volume = _muldiv + (vol * csf->current_global_volume, + chan->global_volume + * CLAMP(chan->instrument_volume + chan->vol_swing, 0, 64), + 1 << 19); + } + + int period = chan->period; + + if ((chan->flags & (CHN_GLISSANDO|CHN_PORTAMENTO)) == (CHN_GLISSANDO|CHN_PORTAMENTO)) { + period = get_period_from_note(get_note_from_period(period), + chan->c5speed, csf->flags & SONG_LINEARSLIDES); + } + + // Arpeggio ? + if (chan->n_command == FX_ARPEGGIO) + period = rn_arpeggio(csf, chan, period); + + // Pitch/Filter Envelope + int envpitch = 0; + + if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument + && (chan->flags & CHN_PITCHENV) && chan->ptr_instrument->pitch_env.nodes) + rn_pitch_filter_envelope(chan, &envpitch, &period); + + // Vibrato + if (chan->flags & CHN_VIBRATO) + period = rn_vibrato(csf, chan, period); + + // Sample Auto-Vibrato + if (chan->ptr_sample && chan->ptr_sample->vib_depth) { + period = rn_sample_vibrato(chan, period); + } + + unsigned int freq = get_freq_from_period(period, csf->flags & SONG_LINEARSLIDES); + + if (!(chan->flags & CHN_NOTEFADE)) + rn_gen_key(csf, chan, cn, freq, vol); + + // Filter Envelope: controls cutoff frequency + if (chan && chan->ptr_instrument && chan->ptr_instrument->flags & ENV_FILTER) { + setup_channel_filter(chan, + !(chan->flags & CHN_FILTER), envpitch, csf->mix_frequency); + } + + chan->sample_freq = freq; + + unsigned int ninc = _muldiv(freq, 0x10000, csf->mix_frequency); + + if (ninc >= 0xFFB0 && ninc <= 0x10090) + ninc = 0x10000; + + if (csf->freq_factor != 128) + ninc = (ninc * csf->freq_factor) >> 7; + + if (ninc > 0xFF0000) + ninc = 0xFF0000; + + chan->increment = (ninc + 1) & ~3; + } + + // Increment envelope position + if (csf->flags & SONG_INSTRUMENTMODE && chan->ptr_instrument) + rn_increment_env_pos(chan); + + chan->final_panning = CLAMP(chan->final_panning, 0, 256); + + // Volume ramping + chan->flags &= ~CHN_VOLUMERAMP; + + if (chan->final_volume || chan->left_volume || chan->right_volume) + chan->flags |= CHN_VOLUMERAMP; + + if (chan->strike) + chan->strike--; + + // Check for too big increment + if (((chan->increment >> 16) + 1) >= (int)(chan->loop_end - chan->loop_start)) + chan->flags &= ~CHN_LOOP; + + chan->right_volume_new = chan->left_volume_new = 0; + if (!(chan->length && chan->increment)) + chan->current_sample_data = NULL; + + update_vu_meter(chan); + + if (chan->current_sample_data) { + if (!rn_update_sample(csf, chan, cn, master_vol)) + break; + } else { + // Note change but no sample + //if (chan->vu_meter > 0xFF) chan->vu_meter = 0; + chan->left_volume = chan->right_volume = 0; + chan->length = 0; + } + } + + // Checking Max Mix Channels reached: ordering by volume + if (csf->num_voices >= max_voices && (!(csf->mix_flags & SNDMIX_DIRECTTODISK))) { + for (unsigned int i = 0; i < csf->num_voices; i++) { + unsigned int j = i; + + while ((j + 1 < csf->num_voices) && + (csf->voices[csf->voice_mix[j]].final_volume + < csf->voices[csf->voice_mix[j + 1]].final_volume)) + { + unsigned int n = csf->voice_mix[j]; + csf->voice_mix[j] = csf->voice_mix[j + 1]; + csf->voice_mix[j + 1] = n; + j++; + } + } + } + + return 1; +} + diff --git a/src/player/tables.c b/src/player/tables.c new file mode 100644 index 0000000..e312cd7 --- /dev/null +++ b/src/player/tables.c @@ -0,0 +1,443 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "tables.h" + +const uint8_t vc_portamento_table[16] = { + 0x00, 0x01, 0x04, 0x08, 0x10, 0x20, 0x40, 0x60, + 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t period_table[12] = { + 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 907, +}; + + +const uint16_t finetune_table[16] = { + 7895, 7941, 7985, 8046, 8107, 8169, 8232, 8280, + 8363, 8413, 8463, 8529, 8581, 8651, 8723, 8757, // 8363*2^((i-8)/(12*8)) +}; + + + +// Tables from ITTECH.TXT + +const int8_t sine_table[256] = { + 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23, + 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, + 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60, + 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, + 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26, + 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2, + 0, -2, -3, -5, -6, -8, -9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44, + -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59, + -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64, + -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60, + -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46, + -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26, + -24,-23,-22,-20,-19,-17,-16,-14,-12,-11, -9, -8, -6, -5, -3, -2, +}; + +const int8_t ramp_down_table[256] = { + 64, 63, 63, 62, 62, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56, + 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, + 48, 47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 40, + 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, + 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27, 26, 26, 25, 25, 24, + 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16, + 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, + 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, + 0, -1, -1, -2, -2, -3, -3, -4, -4, -5, -5, -6, -6, -7, -7, -8, + -8, -9, -9,-10,-10,-11,-11,-12,-12,-13,-13,-14,-14,-15,-15,-16, + -16,-17,-17,-18,-18,-19,-19,-20,-20,-21,-21,-22,-22,-23,-23,-24, + -24,-25,-25,-26,-26,-27,-27,-28,-28,-29,-29,-30,-30,-31,-31,-32, + -32,-33,-33,-34,-34,-35,-35,-36,-36,-37,-37,-38,-38,-39,-39,-40, + -40,-41,-41,-42,-42,-43,-43,-44,-44,-45,-45,-46,-46,-47,-47,-48, + -48,-49,-49,-50,-50,-51,-51,-52,-52,-53,-53,-54,-54,-55,-55,-56, + -56,-57,-57,-58,-58,-59,-59,-60,-60,-61,-61,-62,-62,-63,-63,-64, +}; + +const int8_t square_table[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + + +// volume fade tables for Retrig Note: +const int8_t retrig_table_1[16] = { 0, 0, 0, 0, 0, 0, 10, 8, 0, 0, 0, 0, 0, 0, 24, 32 }; +const int8_t retrig_table_2[16] = { 0, -1, -2, -4, -8, -16, 0, 0, 0, 1, 2, 4, 8, 16, 0, 0 }; + + + +// round(65536 * 2**(n/768)) +// 768 = 64 extra-fine finetune steps for 12 notes +// Table content is in 16.16 format +const uint32_t fine_linear_slide_up_table[16] = { + 65536, 65595, 65654, 65714, 65773, 65832, 65892, 65951, + 66011, 66071, 66130, 66190, 66250, 66309, 66369, 66429 +}; + + +// round(65536 * 2**(-n/768)) +// 768 = 64 extra-fine finetune steps for 12 notes +// Table content is in 16.16 format +// Note that there are a few errors in this table (typos?), but well, this table comes straight from ITTECH.TXT... +// Entry 0 (65535) should be 65536 (this value is unused and most likely stored this way so that it fits in a 16-bit integer) +// Entry 11 (64888) should be 64889 - rounding error? +// Entry 15 (64645) should be 64655 - typo? +const uint32_t fine_linear_slide_down_table[16] = { + 65535, 65477, 65418, 65359, 65300, 65241, 65182, 65123, + 65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645 +}; + + +// floor(65536 * 2**(n/192)) +// 192 = 16 finetune steps for 12 notes +// Table content is in 16.16 format +const uint32_t linear_slide_up_table[256] = { + 65536, 65773, 66010, 66249, 66489, 66729, 66971, 67213, + 67456, 67700, 67945, 68190, 68437, 68685, 68933, 69182, + 69432, 69684, 69936, 70189, 70442, 70697, 70953, 71209, + 71467, 71725, 71985, 72245, 72507, 72769, 73032, 73296, + 73561, 73827, 74094, 74362, 74631, 74901, 75172, 75444, + 75717, 75991, 76265, 76541, 76818, 77096, 77375, 77655, + 77935, 78217, 78500, 78784, 79069, 79355, 79642, 79930, + 80219, 80509, 80800, 81093, 81386, 81680, 81976, 82272, + 82570, 82868, 83168, 83469, 83771, 84074, 84378, 84683, + 84989, 85297, 85605, 85915, 86225, 86537, 86850, 87164, + 87480, 87796, 88113, 88432, 88752, 89073, 89395, 89718, + 90043, 90369, 90695, 91023, 91353, 91683, 92015, 92347, + 92681, 93017, 93353, 93691, 94029, 94370, 94711, 95053, + 95397, 95742, 96088, 96436, 96785, 97135, 97486, 97839, + 98193, 98548, 98904, 99262, 99621, 99981, 100343, 100706, + 101070, 101435, 101802, 102170, 102540, 102911, 103283, 103657, + 104031, 104408, 104785, 105164, 105545, 105926, 106309, 106694, + 107080, 107467, 107856, 108246, 108637, 109030, 109425, 109820, + 110217, 110616, 111016, 111418, 111821, 112225, 112631, 113038, + 113447, 113857, 114269, 114682, 115097, 115514, 115931, 116351, + 116771, 117194, 117618, 118043, 118470, 118898, 119328, 119760, + 120193, 120628, 121064, 121502, 121941, 122382, 122825, 123269, + 123715, 124162, 124611, 125062, 125514, 125968, 126424, 126881, + 127340, 127801, 128263, 128727, 129192, 129660, 130129, 130599, + 131072, 131546, 132021, 132499, 132978, 133459, 133942, 134426, + 134912, 135400, 135890, 136381, 136875, 137370, 137866, 138365, + 138865, 139368, 139872, 140378, 140885, 141395, 141906, 142419, + 142935, 143451, 143970, 144491, 145014, 145538, 146064, 146593, + 147123, 147655, 148189, 148725, 149263, 149803, 150344, 150888, + 151434, 151982, 152531, 153083, 153637, 154192, 154750, 155310, + 155871, 156435, 157001, 157569, 158138, 158710, 159284, 159860, + 160439, 161019, 161601, 162186, 162772, 163361, 163952, 164545, +}; + + +// floor(65536 * 2**(-n/192)) +// 192 = 16 finetune steps for 12 notes +// Table content is in 16.16 format +const uint32_t linear_slide_down_table[256] = { + 65536, 65299, 65064, 64830, 64596, 64363, 64131, 63900, + 63670, 63440, 63212, 62984, 62757, 62531, 62305, 62081, + 61857, 61634, 61412, 61191, 60970, 60751, 60532, 60314, + 60096, 59880, 59664, 59449, 59235, 59021, 58809, 58597, + 58385, 58175, 57965, 57757, 57548, 57341, 57134, 56928, + 56723, 56519, 56315, 56112, 55910, 55709, 55508, 55308, + 55108, 54910, 54712, 54515, 54318, 54123, 53928, 53733, + 53540, 53347, 53154, 52963, 52772, 52582, 52392, 52204, + 52015, 51828, 51641, 51455, 51270, 51085, 50901, 50717, + 50535, 50353, 50171, 49990, 49810, 49631, 49452, 49274, + 49096, 48919, 48743, 48567, 48392, 48218, 48044, 47871, + 47698, 47526, 47355, 47185, 47014, 46845, 46676, 46508, + 46340, 46173, 46007, 45841, 45676, 45511, 45347, 45184, + 45021, 44859, 44697, 44536, 44376, 44216, 44056, 43898, + 43740, 43582, 43425, 43268, 43112, 42957, 42802, 42648, + 42494, 42341, 42189, 42037, 41885, 41734, 41584, 41434, + 41285, 41136, 40988, 40840, 40693, 40546, 40400, 40254, + 40109, 39965, 39821, 39677, 39534, 39392, 39250, 39108, + 38967, 38827, 38687, 38548, 38409, 38270, 38132, 37995, + 37858, 37722, 37586, 37450, 37315, 37181, 37047, 36913, + 36780, 36648, 36516, 36384, 36253, 36122, 35992, 35862, + 35733, 35604, 35476, 35348, 35221, 35094, 34968, 34842, + 34716, 34591, 34466, 34342, 34218, 34095, 33972, 33850, + 33728, 33606, 33485, 33364, 33244, 33124, 33005, 32886, + 32768, 32649, 32532, 32415, 32298, 32181, 32065, 31950, + 31835, 31720, 31606, 31492, 31378, 31265, 31152, 31040, + 30928, 30817, 30706, 30595, 30485, 30375, 30266, 30157, + 30048, 29940, 29832, 29724, 29617, 29510, 29404, 29298, + 29192, 29087, 28982, 28878, 28774, 28670, 28567, 28464, + 28361, 28259, 28157, 28056, 27955, 27854, 27754, 27654, + 27554, 27455, 27356, 27257, 27159, 27061, 26964, 26866, + 26770, 26673, 26577, 26481, 26386, 26291, 26196, 26102, +}; + +/* --------------------------------------------------------------------------------------------------------- */ + +const char *midi_group_names[17] = { + "Piano", + "Chromatic Percussion", + "Organ", + "Guitar", + "Bass", + "Strings", + "Ensemble", + "Brass", + "Reed", + "Pipe", + "Synth Lead", + "Synth Pad", + "Synth Effects", + "Ethnic", + "Percussive", + "Sound Effects", + "Percussions", +}; + +const char *midi_program_names[128] = { + // 1-8: Piano + "Acoustic Grand Piano", + "Bright Acoustic Piano", + "Electric Grand Piano", + "Honky-tonk Piano", + "Electric Piano 1", + "Electric Piano 2", + "Harpsichord", + "Clavi", + // 9-16: Chromatic Percussion + "Celesta", + "Glockenspiel", + "Music Box", + "Vibraphone", + "Marimba", + "Xylophone", + "Tubular Bells", + "Dulcimer", + // 17-24: Organ + "Drawbar Organ", + "Percussive Organ", + "Rock Organ", + "Church Organ", + "Reed Organ", + "Accordion", + "Harmonica", + "Tango Accordion", + // 25-32: Guitar + "Acoustic Guitar (nylon)", + "Acoustic Guitar (steel)", + "Electric Guitar (jazz)", + "Electric Guitar (clean)", + "Electric Guitar (muted)", + "Overdriven Guitar", + "Distortion Guitar", + "Guitar harmonics", + // 33-40 Bass + "Acoustic Bass", + "Electric Bass (finger)", + "Electric Bass (pick)", + "Fretless Bass", + "Slap Bass 1", + "Slap Bass 2", + "Synth Bass 1", + "Synth Bass 2", + // 41-48 Strings + "Violin", + "Viola", + "Cello", + "Contrabass", + "Tremolo Strings", + "Pizzicato Strings", + "Orchestral Harp", + "Timpani", + // 49-56 Ensemble + "String Ensemble 1", + "String Ensemble 2", + "SynthStrings 1", + "SynthStrings 2", + "Choir Aahs", + "Voice Oohs", + "Synth Voice", + "Orchestra Hit", + // 57-64 Brass + "Trumpet", + "Trombone", + "Tuba", + "Muted Trumpet", + "French Horn", + "Brass Section", + "SynthBrass 1", + "SynthBrass 2", + // 65-72 Reed + "Soprano Sax", + "Alto Sax", + "Tenor Sax", + "Baritone Sax", + "Oboe", + "English Horn", + "Bassoon", + "Clarinet", + // 73-80 Pipe + "Piccolo", + "Flute", + "Recorder", + "Pan Flute", + "Blown Bottle", + "Shakuhachi", + "Whistle", + "Ocarina", + // 81-88 Synth Lead + "Lead 1 (square)", + "Lead 2 (sawtooth)", + "Lead 3 (calliope)", + "Lead 4 (chiff)", + "Lead 5 (charang)", + "Lead 6 (voice)", + "Lead 7 (fifths)", + "Lead 8 (bass + lead)", + // 89-96 Synth Pad + "Pad 1 (new age)", + "Pad 2 (warm)", + "Pad 3 (polysynth)", + "Pad 4 (choir)", + "Pad 5 (bowed)", + "Pad 6 (metallic)", + "Pad 7 (halo)", + "Pad 8 (sweep)", + // 97-104 Synth Effects + "FX 1 (rain)", + "FX 2 (soundtrack)", + "FX 3 (crystal)", + "FX 4 (atmosphere)", + "FX 5 (brightness)", + "FX 6 (goblins)", + "FX 7 (echoes)", + "FX 8 (sci-fi)", + // 105-112 Ethnic + "Sitar", + "Banjo", + "Shamisen", + "Koto", + "Kalimba", + "Bag pipe", + "Fiddle", + "Shanai", + // 113-120 Percussive + "Tinkle Bell", + "Agogo", + "Steel Drums", + "Woodblock", + "Taiko Drum", + "Melodic Tom", + "Synth Drum", + "Reverse Cymbal", + // 121-128 Sound Effects + "Guitar Fret Noise", + "Breath Noise", + "Seashore", + "Bird Tweet", + "Telephone Ring", + "Helicopter", + "Applause", + "Gunshot", +}; + +// Notes 25-85 +const char *midi_percussion_names[61] = { + "Seq Click", + "Brush Tap", + "Brush Swirl", + "Brush Slap", + "Brush Swirl W/Attack", + "Snare Roll", + "Castanet", + "Snare Lo", + "Sticks", + "Bass Drum Lo", + "Open Rim Shot", + "Acoustic Bass Drum", + "Bass Drum 1", + "Side Stick", + "Acoustic Snare", + "Hand Clap", + "Electric Snare", + "Low Floor Tom", + "Closed Hi Hat", + "High Floor Tom", + "Pedal Hi-Hat", + "Low Tom", + "Open Hi-Hat", + "Low-Mid Tom", + "Hi Mid Tom", + "Crash Cymbal 1", + "High Tom", + "Ride Cymbal 1", + "Chinese Cymbal", + "Ride Bell", + "Tambourine", + "Splash Cymbal", + "Cowbell", + "Crash Cymbal 2", + "Vibraslap", + "Ride Cymbal 2", + "Hi Bongo", + "Low Bongo", + "Mute Hi Conga", + "Open Hi Conga", + "Low Conga", + "High Timbale", + "Low Timbale", + "High Agogo", + "Low Agogo", + "Cabasa", + "Maracas", + "Short Whistle", + "Long Whistle", + "Short Guiro", + "Long Guiro", + "Claves", + "Hi Wood Block", + "Low Wood Block", + "Mute Cuica", + "Open Cuica", + "Mute Triangle", + "Open Triangle", + "Shaker", + "Jingle Bell", + "Bell Tree", +}; + |