summaryrefslogtreecommitdiff
path: root/src/player
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2017-05-26 21:51:04 -0700
committerVito Caputo <vcaputo@pengaru.com>2017-05-26 22:48:09 -0700
commit78f8fce7f286fd0c71774e2567404ed51f24fef3 (patch)
treef3de4987f7a9fc1bc03331e97b65a851b041051a /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.c1094
-rw-r--r--src/player/effects.c2070
-rw-r--r--src/player/equalizer.c255
-rw-r--r--src/player/filters.c116
-rw-r--r--src/player/fmopl.c2396
-rw-r--r--src/player/fmpatches.c179
-rw-r--r--src/player/mixer.c1555
-rw-r--r--src/player/mixutil.c258
-rw-r--r--src/player/opl-util.c134
-rw-r--r--src/player/snd_fm.c385
-rw-r--r--src/player/sndmix.c1259
-rw-r--r--src/player/tables.c443
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",
+};
+
© All Rights Reserved