diff options
Diffstat (limited to 'engines/sci/sfx/softseq')
29 files changed, 10216 insertions, 0 deletions
diff --git a/engines/sci/sfx/softseq/Makefile.am b/engines/sci/sfx/softseq/Makefile.am new file mode 100644 index 0000000000..af7f8ca2c9 --- /dev/null +++ b/engines/sci/sfx/softseq/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libscisoftseq.a +INCLUDES = -I$(top_srcdir)/src/include @EXTRA_INCLUDES@ +libscisoftseq_a_SOURCES = softsequencers.c pcspeaker.c SN76496.c fmopl.c opl2.c amiga.c \ + fluidsynth.c +EXTRA_DIST = fmopl.h diff --git a/engines/sci/sfx/softseq/SN76496.c b/engines/sci/sfx/softseq/SN76496.c new file mode 100644 index 0000000000..cb32b89f0c --- /dev/null +++ b/engines/sci/sfx/softseq/SN76496.c @@ -0,0 +1,248 @@ +/*************************************************************************** + SN76496.c Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ +/* Tandy/PCJr sequencer for FreeSCI */ + +#include "../softseq.h" +#include <sci_midi.h> + +#define FREQUENCY 44100 +#define CHANNELS_NR 3 +#define VOLUME_SHIFT 3 + +static int global_volume = 100; /* Base volume */ +static int volumes[CHANNELS_NR] = { 100, 100, 100 }; +static int notes[CHANNELS_NR] = {0, 0, 0}; /* Current halftone, or 0 if off */ +static int freq_count[CHANNELS_NR] = {0, 0, 0}; +static int channel_assigner = 0; +static int channels_assigned = 0; +static int chan_nrs[CHANNELS_NR] = {-1, -1, -1}; + +extern sfx_softseq_t sfx_softseq_pcspeaker; +/* Forward-declare the sequencer we are defining here */ + + +static int +SN76496_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +SN76496_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, + int patch2_len) +{ + return SFX_OK; +} + +static void +SN76496_exit(sfx_softseq_t *self) +{ +} + +static void +SN76496_event(sfx_softseq_t *self, byte command, int argc, byte *argv) +{ + int i; + int chan = -1; +#if 0 + fprintf(stderr, "Note [%02x : %02x %02x]\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + if ((command & 0xe0) == 0x80) { + int chan_nr = command & 0xf; + + /* First, test for channel having been assigned already */ + if (channels_assigned & (1 << chan_nr)) { + /* Already assigned this channel number: */ + for (i = 0; i < CHANNELS_NR; i++) + if (chan_nrs[i] == chan_nr) { + chan = i; + break; + } + } else { + /* Assign new channel round-robin */ + + /* Mark channel as unused: */ + if (chan_nrs[channel_assigner] >= 0) + channels_assigned &= ~(1 << chan_nrs[channel_assigner]); + + /* Remember channel: */ + chan_nrs[channel_assigner] = chan_nr; + /* Mark channel as used */ + channels_assigned |= (1 << chan_nrs[channel_assigner]); + + /* Save channel for use later in this call: */ + chan = channel_assigner; + /* Round-ropin iterate channel assigner: */ + channel_assigner = (channel_assigner + 1) % CHANNELS_NR; + } + } +#if 0 + fprintf(stderr, " --> %d [%04x], {%d,%d,%d}@%d\n", chan, + channels_assigned, chan_nrs[0],chan_nrs[1],chan_nrs[2],channel_assigner); +#endif + + switch (command & 0xf0) { + + case 0x80: + if (argv[0] == notes[chan]) + notes[chan] = 0; + break; + + case 0x90: + if (!argv[1]) { + if (argv[chan] == notes[chan]) + notes[chan] = 0; + } else { + notes[chan] = argv[0]; + volumes[chan] = argv[1]; + } + break; + + case 0xb0: + if (argv[1] == SCI_MIDI_CHANNEL_NOTES_OFF) + notes[chan] = 0; + break; + + + default: +#if DEBUG + fprintf(stderr, "[SFX:PCM-PC] Unused MIDI command %02x %02x %02x\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + break; /* ignore */ + } +} + +#define BASE_NOTE 129 /* A10 */ +#define BASE_OCTAVE 10 /* A10, as I said */ + +static int +freq_table[12] = { /* A4 is 440Hz, halftone map is x |-> ** 2^(x/12) */ + 28160, /* A10 */ + 29834, + 31608, + 33488, + 35479, + 37589, + 39824, + 42192, + 44701, + 47359, + 50175, + 53159 +}; + +static inline int +get_freq(int note) +{ + int halftone_delta = note - BASE_NOTE; + int oct_diff = ((halftone_delta + BASE_OCTAVE * 12) / 12) - BASE_OCTAVE; + int halftone_index = (halftone_delta + (12*100)) % 12 ; + int freq = (!note)? 0 : freq_table[halftone_index] / (1 << (-oct_diff)); + + return freq; +} + + +void +SN76496_poll(sfx_softseq_t *self, byte *dest, int len) +{ + gint16 *buf = (gint16 *) dest; + int i; + int chan; + int freq[CHANNELS_NR]; + + for (chan = 0; chan < CHANNELS_NR; chan++) + freq[chan] = get_freq(notes[chan]); + + for (i = 0; i < len; i++) { + int result = 0; + + for (chan = 0; chan < CHANNELS_NR; chan++) + if (notes[chan]) { + int volume = (global_volume * volumes[chan]) + >> VOLUME_SHIFT; + + freq_count[chan] += freq[chan]; + while (freq_count[chan] >= (FREQUENCY << 1)) + freq_count[chan] -= (FREQUENCY << 1); + + if (freq_count[chan] - freq[chan] < 0) { + /* Unclean rising edge */ + int l = volume << 1; + result += -volume + (l*freq_count[chan])/freq[chan]; + } else if (freq_count[chan] >= FREQUENCY + && freq_count[chan] - freq[chan] < FREQUENCY) { + /* Unclean falling edge */ + int l = volume << 1; + result += volume - (l*(freq_count[chan] - FREQUENCY))/freq[chan]; + } else { + if (freq_count[chan] < FREQUENCY) + result += volume; + else + result += -volume; + } + } + buf[i] = result; + } + +} + +void +SN76496_allstop(sfx_softseq_t *self) +{ + int i; + for (i = 0; i < CHANNELS_NR; i++) + notes[i] = 0; +} + +void +SN76496_volume(sfx_softseq_t *self, int new_volume) +{ + global_volume = new_volume; +} + + +sfx_softseq_t sfx_softseq_SN76496 = { + "SN76496", + "0.1", + SN76496_set_option, + SN76496_init, + SN76496_exit, + SN76496_volume, + SN76496_event, + SN76496_poll, + SN76496_allstop, + NULL, + SFX_SEQ_PATCHFILE_NONE, + SFX_SEQ_PATCHFILE_NONE, + 0x10, /* Tandy/PCJr channels */ + 0, /* No rhythm channel */ + 3, /* # of voices */ + {FREQUENCY, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/amiga.c b/engines/sci/sfx/softseq/amiga.c new file mode 100644 index 0000000000..12240897d7 --- /dev/null +++ b/engines/sci/sfx/softseq/amiga.c @@ -0,0 +1,658 @@ +/*************************************************************************** + amiga.c Copyright (C) 2007 Walter van Niftrik + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#include "resource.h" +#include "sci_memory.h" +#include "../softseq.h" + +#define FREQUENCY 44100 +#define CHANNELS_NR 10 +#define HW_CHANNELS_NR 16 + +/* Samplerate of the instrument bank */ +#define BASE_FREQ 20000 + +/* Instrument looping flag */ +#define MODE_LOOP 1 << 0 +/* Instrument pitch changes flag */ +#define MODE_PITCH 1 << 1 + +#define PAN_LEFT 91 +#define PAN_RIGHT 164 + +/* #define DEBUG */ + +typedef struct envelope { + /* Phase period length in samples */ + int length; + /* Velocity delta per period */ + int delta; + /* Target velocity */ + int target; +} envelope_t; + +/* Fast decay envelope */ +static envelope_t env_decay = {FREQUENCY / (32 * 64), 1, 0}; + +typedef struct instrument { + char name[30]; + int mode; + /* Size of non-looping part in bytes */ + int size; + /* Starting offset and size of loop in bytes */ + int loop_size; + /* Transpose value in semitones */ + int transpose; + /* Envelope */ + envelope_t envelope[4]; + sbyte *samples; + sbyte *loop; +} instrument_t; + +typedef struct bank { + char name[30]; + int size; + instrument_t *instruments[256]; +} bank_t; + +typedef struct channel { + int instrument; + int note; + int note_velocity; + int velocity; + int envelope; + /* Number of samples till next envelope event */ + int envelope_samples; + int decay; + int looping; + int hw_channel; + frac_t offset; + frac_t rate; +} channel_t; + +typedef struct hw_channel { + int instrument; + int volume; + int pan; +} hw_channel_t; + +/* Instrument bank */ +static bank_t bank; +/* Internal channels */ +static channel_t channels[CHANNELS_NR]; +/* External channels */ +static hw_channel_t hw_channels[HW_CHANNELS_NR]; +/* Overall volume */ +static int volume = 127; + +/* Frequencies for every note */ +static int freq_table[] = { + 58, 62, 65, 69, 73, 78, 82, 87, + 92, 98, 104, 110, 117, 124, 131, 139, + 147, 156, 165, 175, 185, 196, 208, 220, + 234, 248, 262, 278, 294, 312, 331, 350, + 371, 393, 417, 441, 468, 496, 525, 556, + 589, 625, 662, 701, 743, 787, 834, 883, + 936, 992, 1051, 1113, 1179, 1250, 1324, 1403, + 1486, 1574, 1668, 1767, 1872, 1984, 2102, 2227, + 2359, 2500, 2648, 2806, 2973, 3149, 3337, 3535, + 3745, 3968, 4204, 4454, 4719, 5000, 5297, 5612, + 5946, 6299, 6674, 7071, 7491, 7937, 8408, 8908, + 9438, 10000, 10594, 11224, 11892, 12599, 13348, 14142, + 14983, 15874, 16817, 17817, 18877, 20000, 21189, 22449, + 23784, 25198, 26696, 28284, 29966, 31748, 33635, 35635, + 37754, 40000, 42378, 44898, 47568, 50396, 53393, 56568, + 59932, 63496, 67271, 71271, 75509, 80000, 84757, 89796 +}; + +static void +set_envelope(channel_t *channel, envelope_t *envelope, int phase) +{ + channel->envelope = phase; + channel->envelope_samples = envelope[phase].length; + + if (phase == 0) + channel->velocity = channel->note_velocity / 2; + else + channel->velocity = envelope[phase - 1].target; +} + +static inline int +interpolate(sbyte *samples, frac_t offset) +{ + int x = frac_to_int(offset); + int diff = (samples[x + 1] - samples[x]) << 8; + + return (samples[x] << 8) + frac_to_int(diff * (offset & FRAC_LO_MASK)); +} + +static void +play_instrument(gint16 *dest, channel_t *channel, int count) +{ + int index = 0; + int vol = hw_channels[channel->hw_channel].volume; + instrument_t *instrument = bank.instruments[channel->instrument]; + + while (1) { + /* Available source samples until end of segment */ + frac_t lin_avail; + int seg_end, rem, i, amount; + sbyte *samples; + + if (channel->looping) { + samples = instrument->loop; + seg_end = instrument->loop_size; + } + else { + samples = instrument->samples; + seg_end = instrument->size; + } + + lin_avail = int_to_frac(seg_end) - channel->offset; + + rem = count - index; + + /* Amount of destination samples that we will compute this iteration */ + amount = lin_avail / channel->rate; + + if (lin_avail % channel->rate) + amount++; + + if (amount > rem) + amount = rem; + + /* Stop at next envelope event */ + if ((channel->envelope_samples != -1) && (amount > channel->envelope_samples)) + amount = channel->envelope_samples; + + for (i = 0; i < amount; i++) { + dest[index++] = interpolate(samples, channel->offset) * channel->velocity / 64 * channel->note_velocity * vol / (127 * 127); + channel->offset += channel->rate; + } + + if (channel->envelope_samples != -1) + channel->envelope_samples -= amount; + + if (channel->envelope_samples == 0) { + envelope_t *envelope; + int delta, target, velocity; + + if (channel->decay) + envelope = &env_decay; + else + envelope = &instrument->envelope[channel->envelope]; + + delta = envelope->delta; + target = envelope->target; + velocity = channel->velocity - envelope->delta; + + /* Check whether we have reached the velocity target for the current phase */ + if ((delta >= 0 && velocity <= target) || (delta < 0 && velocity >= target)) { + channel->velocity = target; + + /* Stop note after velocity has dropped to 0 */ + if (target == 0) { + channel->note = -1; + break; + } else + switch (channel->envelope) { + case 0: + case 2: + /* Go to next phase */ + set_envelope(channel, instrument->envelope, channel->envelope + 1); + break; + case 1: + case 3: + /* Stop envelope */ + channel->envelope_samples = -1; + break; + } + } else { + /* We haven't reached the target yet */ + channel->envelope_samples = envelope->length; + channel->velocity = velocity; + } + } + + if (index == count) + break; + + if (frac_to_int(channel->offset) >= seg_end) { + if (instrument->mode & MODE_LOOP) { + /* Loop the samples */ + channel->offset -= int_to_frac(seg_end); + channel->looping = 1; + } else { + /* All samples have been played */ + channel->note = -1; + break; + } + } + } +} + +static void +change_instrument(int channel, int instrument) +{ +#ifdef DEBUG + if (bank.instruments[instrument]) + sciprintf("[sfx:seq:amiga] Setting channel %i to \"%s\" (%i)\n", channel, bank.instruments[instrument]->name, instrument); + else + sciprintf("[sfx:seq:amiga] Warning: instrument %i does not exist (channel %i)\n", instrument, channel); +#endif + hw_channels[channel].instrument = instrument; +} + +static void +stop_channel(int ch) +{ + int i; + + /* Start decay phase for note on this hw channel, if any */ + for (i = 0; i < CHANNELS_NR; i++) + if (channels[i].note != -1 && channels[i].hw_channel == ch && !channels[i].decay) { + /* Trigger fast decay envelope */ + channels[i].decay = 1; + channels[i].envelope_samples = env_decay.length; + break; + } +} + +static void +stop_note(int ch, int note) +{ + int channel; + instrument_t *instrument; + + for (channel = 0; channel < CHANNELS_NR; channel++) + if (channels[channel].note == note && channels[channel].hw_channel == ch && !channels[channel].decay) + break; + + if (channel == CHANNELS_NR) { +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: cannot stop note %i on channel %i\n", note, ch); +#endif + return; + } + + instrument = bank.instruments[channels[channel].instrument]; + + /* Start the envelope phases for note-off if looping is on and envelope is enabled */ + if ((instrument->mode & MODE_LOOP) && (instrument->envelope[0].length != 0)) + set_envelope(&channels[channel], instrument->envelope, 2); +} + +static void +start_note(int ch, int note, int velocity) +{ + instrument_t *instrument; + int channel; + + if (hw_channels[ch].instrument < 0 || hw_channels[ch].instrument > 255) { + sciprintf("[sfx:seq:amiga] Error: invalid instrument %i on channel %i\n", hw_channels[ch].instrument, ch); + return; + } + + instrument = bank.instruments[hw_channels[ch].instrument]; + + if (!instrument) { + sciprintf("[sfx:seq:amiga] Error: instrument %i does not exist\n", hw_channels[ch].instrument); + return; + } + + for (channel = 0; channel < CHANNELS_NR; channel++) + if (channels[channel].note == -1) + break; + + if (channel == CHANNELS_NR) { + sciprintf("[sfx:seq:amiga] Warning: could not find a free channel\n"); + return; + } + + stop_channel(ch); + + if (instrument->mode & MODE_PITCH) { + int fnote = note + instrument->transpose; + + if (fnote < 0 || fnote > 127) { + sciprintf("[sfx:seq:amiga] Error: illegal note %i\n", fnote); + return; + } + + /* Compute rate for note */ + channels[channel].rate = double_to_frac(freq_table[fnote] / (double) FREQUENCY); + } + else + channels[channel].rate = double_to_frac(BASE_FREQ / (double) FREQUENCY); + + channels[channel].instrument = hw_channels[ch].instrument; + channels[channel].note = note; + channels[channel].note_velocity = velocity; + + if ((instrument->mode & MODE_LOOP) && (instrument->envelope[0].length != 0)) + set_envelope(&channels[channel], instrument->envelope, 0); + else { + channels[channel].velocity = 64; + channels[channel].envelope_samples = -1; + } + + channels[channel].offset = 0; + channels[channel].hw_channel = ch; + channels[channel].decay = 0; + channels[channel].looping = 0; +} + +static gint16 read_int16(byte *data) +{ + return (data[0] << 8) | data[1]; +} + +static gint32 read_int32(byte *data) +{ + return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; +} + +static instrument_t *read_instrument(FILE *file, int *id) +{ + instrument_t *instrument; + byte header[61]; + int size; + int seg_size[3]; + int loop_offset; + int i; + + if (fread(header, 1, 61, file) < 61) { + sciprintf("[sfx:seq:amiga] Error: failed to read instrument header\n"); + return NULL; + } + + instrument = (instrument_t *) sci_malloc(sizeof(instrument_t)); + + seg_size[0] = read_int16(header + 35) * 2; + seg_size[1] = read_int16(header + 41) * 2; + seg_size[2] = read_int16(header + 47) * 2; + + instrument->mode = header[33]; + instrument->transpose = (gint8) header[34]; + for (i = 0; i < 4; i++) { + int length = (gint8) header[49 + i]; + + if (length == 0 && i > 0) + length = 256; + + instrument->envelope[i].length = length * FREQUENCY / 60; + instrument->envelope[i].delta = (gint8) header[53 + i]; + instrument->envelope[i].target = header[57 + i]; + } + /* Final target must be 0 */ + instrument->envelope[3].target = 0; + + loop_offset = read_int32(header + 37) & ~1; + size = seg_size[0] + seg_size[1] + seg_size[2]; + + *id = read_int16(header); + + strncpy(instrument->name, (char *) header + 2, 29); + instrument->name[29] = 0; +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Reading instrument %i: \"%s\" (%i bytes)\n", + *id, instrument->name, size); + sciprintf(" Mode: %02x\n", instrument->mode); + sciprintf(" Looping: %s\n", instrument->mode & MODE_LOOP ? "on" : "off"); + sciprintf(" Pitch changes: %s\n", instrument->mode & MODE_PITCH ? "on" : "off"); + sciprintf(" Segment sizes: %i %i %i\n", seg_size[0], seg_size[1], seg_size[2]); + sciprintf(" Segment offsets: 0 %i %i\n", loop_offset, read_int32(header + 43)); +#endif + instrument->samples = (sbyte *) sci_malloc(size + 1); + if (fread(instrument->samples, 1, size, file) < size) { + sciprintf("[sfx:seq:amiga] Error: failed to read instrument samples\n"); + return NULL; + } + + if (instrument->mode & MODE_LOOP) { + if (loop_offset + seg_size[1] > size) { +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: looping samples extend %i bytes past end of sample block\n", + loop_offset + seg_size[1] - size); +#endif + seg_size[1] = size - loop_offset; + } + + if (seg_size[1] < 0) { + sciprintf("[sfx:seq:amiga] Error: invalid looping point\n"); + return NULL; + } + + instrument->size = seg_size[0]; + instrument->loop_size = seg_size[1]; + + instrument->loop = (sbyte*)sci_malloc(instrument->loop_size + 1); + memcpy(instrument->loop, instrument->samples + loop_offset, instrument->loop_size); + + instrument->samples[instrument->size] = instrument->loop[0]; + instrument->loop[instrument->loop_size] = instrument->loop[0]; + } else { + instrument->size = size; + instrument->samples[instrument->size] = 0; + } + + return instrument; +} + +static int +ami_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +ami_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, int patch2_len) +{ + FILE *file; + byte header[40]; + int i; + + file = sci_fopen("bank.001", "rb"); + + if (!file) { + sciprintf("[sfx:seq:amiga] Error: file bank.001 not found\n"); + return SFX_ERROR; + } + + if (fread(header, 1, 40, file) < 40) { + sciprintf("[sfx:seq:amiga] Error: failed to read header of file bank.001\n"); + fclose(file); + return SFX_ERROR; + } + + for (i = 0; i < 256; i++) + bank.instruments[i] = NULL; + + for (i = 0; i < CHANNELS_NR; i++) { + channels[i].note = -1; + } + + for (i = 0; i < HW_CHANNELS_NR; i++) { + hw_channels[i].instrument = -1; + hw_channels[i].volume = 127; + hw_channels[i].pan = (i % 4 == 0 || i % 4 == 3 ? PAN_LEFT : PAN_RIGHT); + } + + bank.size = read_int16(header + 38); + strncpy(bank.name, (char *) header + 8, 29); + bank.name[29] = 0; +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Reading %i instruments from bank \"%s\"\n", bank.size, bank.name); +#endif + + for (i = 0; i < bank.size; i++) { + int id; + instrument_t *instrument = read_instrument(file, &id); + + if (!instrument) { + sciprintf("[sfx:seq:amiga] Error: failed to read bank.001\n"); + fclose(file); + return SFX_ERROR; + } + + if (id < 0 || id > 255) { + sciprintf("[sfx:seq:amiga] Error: instrument ID out of bounds\n"); + return SFX_ERROR; + } + + bank.instruments[id] = instrument; + } + + fclose(file); + + return SFX_OK; +} + +static void +ami_exit(sfx_softseq_t *self) +{ + int i; + + for (i = 0; i < bank.size; i++) { + if (bank.instruments[i]) { + sci_free(bank.instruments[i]->samples); + sci_free(bank.instruments[i]); + } + } +} + +static void +ami_event(sfx_softseq_t *self, byte command, int argc, byte *argv) +{ + int channel, oper; + + channel = command & 0x0f; + oper = command & 0xf0; + + if (channel >= HW_CHANNELS_NR) { +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: received event for non-existing channel %i\n", channel); +#endif + return; + } + + switch(oper) { + case 0x90: + if (argv[1] > 0) + start_note(channel, argv[0], argv[1]); + else + stop_note(channel, argv[0]); + break; + case 0xb0: + switch (argv[0]) { + case 0x07: + hw_channels[channel].volume = argv[1]; + break; + case 0x0a: +#ifdef DEBUG + sciprintf("[sfx:seq:amiga] Warning: ignoring pan 0x%02x event for channel %i\n", argv[1], channel); +#endif + break; + case 0x7b: + stop_channel(channel); + break; + default: + sciprintf("[sfx:seq:amiga] Warning: unknown control event 0x%02x\n", argv[0]); + } + break; + case 0xc0: + change_instrument(channel, argv[0]); + break; + default: + sciprintf("[sfx:seq:amiga] Warning: unknown event %02x\n", command); + } +} + +void +ami_poll(sfx_softseq_t *self, byte *dest, int len) +{ + int i, j; + gint16 *buf = (gint16 *) dest; + gint16 *buffers = (gint16*)malloc(len * 2 * CHANNELS_NR); + + memset(buffers, 0, len * 2 * CHANNELS_NR); + memset(dest, 0, len * 4); + + /* Generate samples for all notes */ + for (i = 0; i < CHANNELS_NR; i++) + if (channels[i].note >= 0) + play_instrument(buffers + i * len, &channels[i], len); + + for (j = 0; j < len; j++) { + int mixedl = 0, mixedr = 0; + + /* Mix and pan */ + for (i = 0; i < CHANNELS_NR; i++) { + mixedl += buffers[i * len + j] * (256 - hw_channels[channels[i].hw_channel].pan); + mixedr += buffers[i * len + j] * hw_channels[channels[i].hw_channel].pan; + } + + /* Adjust volume */ + buf[2 * j] = mixedl * volume >> 16; + buf[2 * j + 1] = mixedr *volume >> 16; + } +} + +void +ami_volume(sfx_softseq_t *self, int new_volume) +{ + volume = new_volume; +} + +void +ami_allstop(sfx_softseq_t *self) +{ + int i; + for (i = 0; i < HW_CHANNELS_NR; i++) + stop_channel(i); +} + +sfx_softseq_t sfx_softseq_amiga = { + "amiga", + "0.1", + ami_set_option, + ami_init, + ami_exit, + ami_volume, + ami_event, + ami_poll, + ami_allstop, + NULL, + SFX_SEQ_PATCHFILE_NONE, + SFX_SEQ_PATCHFILE_NONE, + 0x40, + 0, /* No rhythm channel (9) */ + HW_CHANNELS_NR, /* # of voices */ + {FREQUENCY, SFX_PCM_STEREO_LR, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/fluidsynth.c b/engines/sci/sfx/softseq/fluidsynth.c new file mode 100644 index 0000000000..a44d75e5bd --- /dev/null +++ b/engines/sci/sfx/softseq/fluidsynth.c @@ -0,0 +1,262 @@ +/*************************************************************************** + fluidsynth.c Copyright (C) 2008 Walter van Niftrik + + This program may be modified and copied freely according to the terms of + the GNU general public license (GPL), as long as the above copyright + notice and the licensing information contained herein are preserved. + + Please refer to www.gnu.org for licensing details. + + This work is provided AS IS, without warranty of any kind, expressed or + implied, including but not limited to the warranties of merchantibility, + noninfringement, and fitness for a specific purpose. The author will not + be held liable for any damage caused by this work or derivatives of it. + + By using this source code, you agree to the licensing terms as stated + above. + + + Please contact the maintainer for bug reports or inquiries. + + Current Maintainer: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_FLUIDSYNTH_H + +#include <fluidsynth.h> + +#include "../softseq.h" +#include "../sequencer.h" +#include "../device.h" +#include "resource.h" + +static sfx_sequencer_t *gmseq; +static fluid_settings_t* settings; +static fluid_synth_t* synth; +static guint8 status; +static char *soundfont = "/etc/midi/8MBGMSFX.SF2"; +static int rpn[16]; + +#define SAMPLE_RATE 44100 +#define CHANNELS SFX_PCM_STEREO_LR + +/* MIDI writer */ + +static int +fluidsynth_midi_init(struct _midi_writer *self) +{ + return SFX_OK; +} + +static int +fluidsynth_midi_set_option(struct _midi_writer *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +fluidsynth_midi_write(struct _midi_writer *self, unsigned char *buf, int len) +{ + if (buf[0] == 0xf0) + sciprintf("FluidSynth: Skipping sysex message.\n"); + else if (len == 2) { + guint8 command, channel; + + command = buf[0] & 0xf0; + channel = buf[0] & 0x0f; + + switch(command) { + case 0xc0: + fluid_synth_program_change(synth, channel, buf[1]); + break; + default: + printf("FluidSynth: MIDI command [%02x %02x] not supported\n", buf[0], buf[1]); + } + } + else if (len == 3) { + guint8 command, channel; + + command = buf[0] & 0xf0; + channel = buf[0] & 0x0f; + + switch(command) { + case 0x80: + fluid_synth_noteoff(synth, channel, buf[1]); + break; + case 0x90: + fluid_synth_noteon(synth, channel, buf[1], buf[2]); + break; + case 0xb0: + switch (buf[1]) { + case 0x06: + /* Data Entry Slider - course */ + if (rpn[channel] == 0) + fluid_synth_pitch_wheel_sens(synth, channel, buf[2]); + else + sciprintf("FluidSynth: RPN %i not supported\n", rpn[channel]); + case 0x64: + /* Registered Parameter Number (RPN) - fine */ + rpn[channel] &= ~0x7f; + rpn[channel] |= buf[2] & 0x7f; + break; + case 0x65: + /* Registered Parameter Number (RPN) - course */ + rpn[channel] &= ~0x3f80; + rpn[channel] |= (buf[2] & 0x7f) << 7; + break; + default: + fluid_synth_cc(synth, channel, buf[1], buf[2]); + } + break; + case 0xe0: + fluid_synth_pitch_bend(synth, channel, (buf[2] << 7) | buf[1]); + break; + default: + sciprintf("FluidSynth: MIDI command [%02x %02x %02x] not supported\n", buf[0], buf[1], buf[2]); + } + } + else + sciprintf("FluidSynth: Skipping invalid message of %i bytes.\n", len); + + return SFX_OK; +} + +static void +fluidsynth_midi_delay(struct _midi_writer *self, int ticks) +{ +} + +static void +fluidsynth_midi_reset_timer(struct _midi_writer *self) +{ +} + +static void +fluidsynth_midi_close(struct _midi_writer *self) +{ +} + +static midi_writer_t midi_writer_fluidsynth = { + "fluidsynth", + fluidsynth_midi_init, + fluidsynth_midi_set_option, + fluidsynth_midi_write, + fluidsynth_midi_delay, + NULL, + fluidsynth_midi_reset_timer, + fluidsynth_midi_close +}; + +/* Software sequencer */ + +static void +fluidsynth_poll(sfx_softseq_t *self, byte *dest, int count) +{ + fluid_synth_write_s16(synth, count, dest, 0, 2, dest + 2, 0, 2); +} + +static int +fluidsynth_init(sfx_softseq_t *self, byte *data_ptr, int data_length, + byte *data2_ptr, int data2_length) +{ + int sfont_id; + double min, max; + + if (0) { + sciprintf("FluidSynth ERROR: Mono sound output not supported.\n"); + return SFX_ERROR; + } + + gmseq = sfx_find_sequencer("General MIDI"); + if (!gmseq) { + sciprintf("FluidSynth ERROR: Unable to find General MIDI sequencer.\n"); + return SFX_ERROR; + } + + settings = new_fluid_settings(); + + fluid_settings_getnum_range(settings, "synth.sample-rate", &min, &max); + if (SAMPLE_RATE < min || SAMPLE_RATE > max) { + sciprintf("FluidSynth ERROR: Sample rate '%i' not supported. Valid " + "range is (%i-%i).\n", SAMPLE_RATE, (int) min, (int) max); + delete_fluid_settings(settings); + return SFX_ERROR; + } + + fluid_settings_setnum(settings, "synth.sample-rate", SAMPLE_RATE); + fluid_settings_setnum(settings, "synth.gain", 0.5f); + + synth = new_fluid_synth(settings); + + if ((sfont_id = fluid_synth_sfload(synth, soundfont, 1)) < 0) { + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return SFX_ERROR; + } + + gmseq->open(data_length, data_ptr, data2_length, data2_ptr, + &midi_writer_fluidsynth); + + return SFX_OK; +} + +static void +fluidsynth_exit(sfx_softseq_t *self) +{ + delete_fluid_synth(synth); + delete_fluid_settings(settings); +} + +static void +fluidsynth_allstop(sfx_softseq_t *self) +{ + if (gmseq->allstop) + gmseq->allstop(); +} + +static void +fluidsynth_volume(sfx_softseq_t *self, int volume) +{ + if (gmseq->volume) + gmseq->volume(volume); +} + +static int +fluidsynth_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static void +fluidsynth_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + gmseq->event(cmd, argc, argv); +} + +sfx_softseq_t sfx_softseq_fluidsynth = { + "fluidsynth", + "0.1", + fluidsynth_set_option, + fluidsynth_init, + fluidsynth_exit, + fluidsynth_volume, + fluidsynth_event, + fluidsynth_poll, + fluidsynth_allstop, + NULL, + 004, /* patch.004 */ + 001, /* patch.001 */ + 0x01, /* playflag */ + 1, /* do play channel 9 */ + 32, /* Max polypgony */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; + +#endif /* HAVE_FLUIDSYNTH_H */ diff --git a/engines/sci/sfx/softseq/fmopl.c b/engines/sci/sfx/softseq/fmopl.c new file mode 100644 index 0000000000..6ea777c667 --- /dev/null +++ b/engines/sci/sfx/softseq/fmopl.c @@ -0,0 +1,1144 @@ +/*************************************************************************** + fmopl.c Copyright (C) 1999-2000 Tatsuyuki Satoh + 2001-2004 The ScummVM project + 2002 Solomon Peachy + 2004 Walter van Niftrik + + This program may be modified and copied freely according to the terms of + the GNU general public license (GPL), as long as the above copyright + notice and the licensing information contained herein are preserved. + + Please refer to www.gnu.org for licensing details. + + This work is provided AS IS, without warranty of any kind, expressed or + implied, including but not limited to the warranties of merchantibility, + noninfringement, and fitness for a specific purpose. The author will not + be held liable for any damage caused by this work or derivatives of it. + + By using this source code, you agree to the licensing terms as stated + above. + + LGPL licensed version of MAMEs fmopl (V0.37a modified) by + Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#ifndef INLINE +# ifdef _MSC_VER +# define INLINE __inline +# else +# define INLINE inline +# endif +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> + +#include "fmopl.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<<OPL_OUTSB) +#define OPL_MINOUT (-0x8000<<OPL_OUTSB) + +/* -------------------- quality selection --------------------- */ + +/* sinwave entries */ +/* used static memory = SIN_ENT * 4 (byte) */ +#define SIN_ENT 2048 + +/* output level entries (envelope,sinwave) */ +/* envelope counter lower bits */ +int ENV_BITS; +/* envelope output entries */ +int EG_ENT; + +/* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */ +/* used static memory = EG_ENT*4 (byte) */ +int EG_OFF; /* OFF */ +int EG_DED; +int EG_DST; /* DECAY START */ +int EG_AED; +#define EG_AST 0 /* ATTACK START */ + +#define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */ + +/* LFO table entries */ +#define VIB_ENT 512 +#define VIB_SHIFT (32-9) +#define AMS_ENT 512 +#define AMS_SHIFT (32-9) + +#define VIB_RATE 256 + +/* -------------------- local defines , macros --------------------- */ + +/* register number to channel number , slot offset */ +#define SLOT1 0 +#define SLOT2 1 + +/* envelope phase */ +#define ENV_MOD_RR 0x00 +#define ENV_MOD_DR 0x01 +#define ENV_MOD_AR 0x02 + +/* -------------------- tables --------------------- */ +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, +#ifdef TWELVE_VOICE + 18,20,22,19,21,23,-1,-1 +#else + -1,-1,-1,-1,-1,-1,-1,-1 +#endif +}; + +static guint32 KSL_TABLE[8 * 16]; + +static const double KSL_TABLE_SEED[8 * 16] = { + /* OCT 0 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + /* OCT 1 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.750, 1.125, 1.500, + 1.875, 2.250, 2.625, 3.000, + /* OCT 2 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 1.125, 1.875, 2.625, + 3.000, 3.750, 4.125, 4.500, + 4.875, 5.250, 5.625, 6.000, + /* OCT 3 */ + 0.000, 0.000, 0.000, 1.875, + 3.000, 4.125, 4.875, 5.625, + 6.000, 6.750, 7.125, 7.500, + 7.875, 8.250, 8.625, 9.000, + /* OCT 4 */ + 0.000, 0.000, 3.000, 4.875, + 6.000, 7.125, 7.875, 8.625, + 9.000, 9.750, 10.125, 10.500, + 10.875, 11.250, 11.625, 12.000, + /* OCT 5 */ + 0.000, 3.000, 6.000, 7.875, + 9.000, 10.125, 10.875, 11.625, + 12.000, 12.750, 13.125, 13.500, + 13.875, 14.250, 14.625, 15.000, + /* OCT 6 */ + 0.000, 6.000, 9.000, 10.875, + 12.000, 13.125, 13.875, 14.625, + 15.000, 15.750, 16.125, 16.500, + 16.875, 17.250, 17.625, 18.000, + /* OCT 7 */ + 0.000, 9.000, 12.000, 13.875, + 15.000, 16.125, 16.875, 17.625, + 18.000, 18.750, 19.125, 19.500, + 19.875, 20.250, 20.625, 21.000 +}; + +/* sustain lebel table (3db per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ + +static int SL_TABLE[16]; + +static const guint32 SL_TABLE_SEED[16] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31 +}; + +#define TL_MAX (EG_ENT * 2) /* limit(tl + ksr + envelope) + sinwave */ +/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */ +/* TL_TABLE[ 0 to TL_MAX ] : plus section */ +/* TL_TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */ +static int *TL_TABLE; + +/* pointers to TL_TABLE with sinwave output offset */ +static int **SIN_TABLE; + +/* LFO table */ +static int *AMS_TABLE; +static int *VIB_TABLE; + +/* envelope output curve table */ +/* attack + decay + OFF */ +//static int ENV_CURVE[2*EG_ENT+1]; +static int ENV_CURVE[2 * 4096 + 1]; // to keep it static ... + +/* multiple table */ +#define ML(a) (int)(a * 2) +static const guint32 MUL_TABLE[16]= { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */ + ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00), + ML(8.00), ML(9.00), ML(10.00), ML(10.00),ML(12.00),ML(12.00),ML(15.00),ML(15.00) +}; +#undef ML + +/* dummy attack / decay rate ( when rate == 0 ) */ +static int RATE_0[16]= +{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +/* -------------------- static state --------------------- */ + +/* lock level of common table */ +static int num_lock = 0; + +/* work table */ +static void *cur_chip = NULL; /* current chip point */ +/* currenct chip state */ +/* static OPLSAMPLE *bufL,*bufR; */ +static OPL_CH *S_CH; +static OPL_CH *E_CH; +OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2; + +static int outd[1]; +static int ams; +static int vib; +int *ams_table; +int *vib_table; +static int amsIncr; +static int vibIncr; +static int feedback2; /* connect for SLOT 2 */ + +/* --------------------- rebuild tables ------------------- */ + +#define SC_KSL(mydb) ((guint32) (mydb / (EG_STEP / 2))) +#define SC_SL(db) (int)(db * ((3 / EG_STEP) * (1 << ENV_BITS))) + EG_DST + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM) { + int i; + + ENV_BITS = ENV_BITS_PARAM; + EG_ENT = EG_ENT_PARAM; + EG_OFF = ((2 * EG_ENT)<<ENV_BITS); /* OFF */ + EG_DED = EG_OFF; + EG_DST = (EG_ENT << ENV_BITS); /* DECAY START */ + EG_AED = EG_DST; + //EG_STEP = (96.0/EG_ENT); + + for (i = 0; i < (int)(sizeof(KSL_TABLE_SEED) / sizeof(double)); i++) + KSL_TABLE[i] = SC_KSL(KSL_TABLE_SEED[i]); + + for (i = 0; i < (int)(sizeof(SL_TABLE_SEED) / sizeof(guint32)); i++) + SL_TABLE[i] = SC_SL(SL_TABLE_SEED[i]); +} + +#undef SC_KSL +#undef SC_SL + +/* --------------------- subroutines --------------------- */ + +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); +} + +/* ----- key on ----- */ +INLINE void OPL_KEYON(OPL_SLOT *SLOT) { + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} +/* ----- key off ----- */ +INLINE void OPL_KEYOFF(OPL_SLOT *SLOT) { + if( SLOT->evm > ENV_MOD_RR) { + /* set envelope counter from envleope output */ + SLOT->evm = ENV_MOD_RR; + if( !(SLOT->evc & EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST; + SLOT->evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +INLINE guint32 OPL_CALC_SLOT(OPL_SLOT *SLOT) { + /* calcrate envelope generator */ + if((SLOT->evc += SLOT->evs) >= SLOT->eve) { + switch( SLOT->evm ){ + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) { + SLOT->evs = 0; + } else { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF + 1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm(OPL_CH *CH) { + int *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +INLINE void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* 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_TABLE[v & 0x0f]; + SLOT->KSR = (v & 0x10) ? 0 : 2; + SLOT->eg_typ = (v & 0x20) >> 5; + SLOT->vib = (v & 0x40); + SLOT->ams = (v & 0x80); + 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 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ + + if(!(OPL->mode & 0x80)) { /* not CSM latch total level */ + 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]; + int ar = v >> 4; + int dr = v & 0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_AR) + SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->evs = SLOT->evsd; +} + +/* 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]; + int sl = v >> 4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_RR) + SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con) / (0x1000000 / SIN_ENT)) & (SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +INLINE void OPL_CALC_CH(OPL_CH *CH) { + guint32 env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (guint32)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH->FB) { + int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + *CH->connect1 += OP_OUT(SLOT, env_out, 0); + } + }else { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (guint32)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT, env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +INLINE void OPL_CALC_RH(OPL_CH *CH) { + guint32 env_tam, env_sd, env_top, env_hh; + int whitenoise = (int)((rand()&1) * (WHITE_NOISE_db / EG_STEP)); + int tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH[6].FB) { + int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + feedback2 = OP_OUT(SLOT, env_out, 0); + } + }else { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib / VIB_RATE); + else + SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam =OPL_CALC_SLOT(SLOT8_1); + env_top = OPL_CALC_SLOT(SLOT8_2); + env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) + SLOT7_1->Cnt += (2 * SLOT7_1->Incr * vib / VIB_RATE); + else + SLOT7_1->Cnt += 2 * SLOT7_1->Incr; + if(SLOT7_2->vib) + SLOT7_2->Cnt += ((CH[7].fc * 8) * vib / VIB_RATE); + else + SLOT7_2->Cnt += (CH[7].fc * 8); + if(SLOT8_1->vib) + SLOT8_1->Cnt += (SLOT8_1->Incr * vib / VIB_RATE); + else + SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) + SLOT8_2->Cnt += ((CH[8].fc * 48) * vib / VIB_RATE); + else + SLOT8_2->Cnt += (CH[8].fc * 48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if(env_sd < (guint32)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; + /* TAM */ + if(env_tam < (guint32)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; + /* TOP-CY */ + if(env_top < (guint32)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; + /* HH */ + if(env_hh < (guint32)(EG_ENT-1)) + outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0; i < 4; i++) + OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4; i <= 60; i++){ + rate = OPL->freqbase; /* frequency rate */ + if(i < 60) + rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT << ENV_BITS); + OPL->AR_TABLE[i] = (int)(rate / ARRATE); + OPL->DR_TABLE[i] = (int)(rate / DRRATE); + } + for (i = 60; i < 75; i++) { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable(void) { + int s,t; + double rate; + int i,j; + double pom; + + /* allocate dynamic tables */ + if((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) + return 0; + if((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { + free(TL_TABLE); + return 0; + } + if((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + if((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0; t < EG_ENT - 1 ; t++){ + rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; + } + /* fill volume off area */ + for (t = EG_ENT - 1; t < TL_MAX; t++){ + TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; + for (s = 1;s <= SIN_ENT / 4; s++){ + pom = sin(2 * PI * s / SIN_ENT); /* sin */ + pom = 20 * log10(1 / pom); /* decibel */ + j = (int)(pom / EG_STEP); /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; + } + for (s = 0;s < SIN_ENT; s++) { + SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; + SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; + } + + /* envelope counter -> envelope output table */ + for (i=0; i < EG_ENT; i++) { + /* ATTACK curve */ + pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; + /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; + } + /* off */ + ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; + /* make LFO ams table */ + for (i=0; i < AMS_ENT; i++) { + pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ + AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ + AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i=0; i < VIB_ENT; i++) { + /* 100cent = 1seminote = 6% ?? */ + pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ + VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ + VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ + } + return 1; +} + +static void OPLCloseTable(void) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + free(VIB_TABLE); +} + +/* CSM Key Controll */ +INLINE void CSMKeyControll(OPL_CH *CH) { + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) { + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); + /* make fnumber -> increment counter table */ + for( fn=0; fn < 1024; fn++) { + OPL->FN_TABLE[fn] = (guint32)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); + } + /* LFO freq.table */ + OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); + OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); +} + +/* ---------- write a OPL registers ---------- */ +void OPLWriteReg(FM_OPL *OPL, int r, int v) { + OPL_CH *CH; + int slot; + guint32 block_fnum; + + switch(r & 0xe0) { + case 0x00: /* 00-1f:controll */ + switch(r & 0x1f) { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) { + OPL->wavesel = v & 0x20; + if(!OPL->wavesel) { + /* preset compatible mode */ + int c; + for(c=0; c<OPL->max_ch; c++) { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v) * 4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v) * 16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v & 0x80) { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL, 0x7f); + } + else { /* set IRQ mask ,timer enable*/ + guint8 st1 = v & 1; + guint8 st2 = (v >> 1) & 1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL, v & 0x78); + OPL_STATUSMASK_SET(OPL,((~v) & 0x78) | 0x01); + /* timer 2 */ + if(OPL->st[1] != st2) { + double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) { + double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); + } + } + return; + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + guint8 rkey = OPL->rythm ^ v; + OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; + OPL->rythm = v & 0x3f; + if(OPL->rythm & 0x20) { + /* BD key on/off */ + if(rkey & 0x10) { + if(v & 0x10) { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } + else { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey & 0x08) { + if(v & 0x08) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey & 0x04) { + if(v & 0x04) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey & 0x02) { + if(v & 0x02) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey & 0x01) { + if(v & 0x01) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + } + /* keyon,block,fnum */ + if((r & 0x0f) >= ADLIB_VOICES) + return; + CH = &OPL->P_CH[r & 0x0f]; + if(!(r&0x10)) { /* a0-a8 */ + block_fnum = (CH->block_fnum & 0x1f00) | v; + } + else { /* b0-b8 */ + int keyon = (v >> 5) & 1; + block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); + if(CH->keyon != keyon) { + if((CH->keyon=keyon)) { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } + else { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) { + int blockRv = 7 - (block_fnum >> 10); + int fnum = block_fnum & 0x3ff; + CH->block_fnum = block_fnum; + CH->ksl_base = KSL_TABLE[block_fnum >> 6]; + CH->fc = OPL->FN_TABLE[fnum] >> blockRv; + CH->kcode = CH->block_fnum >> 9; + if((OPL->mode & 0x40) && CH->block_fnum & 0x100) + CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if((r & 0x0f) >= ADLIB_VOICES) + return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v >> 1) & 7; + CH->FB = feedback ? (8 + 1) - feedback : 0; + CH->CON = v & 1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r & 0x1f]; + if(slot == -1) + return; + CH = &OPL->P_CH[slot / 2]; + if(OPL->wavesel) { + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) +{ + num_lock++; + if(num_lock>1) + return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if(!OPLOpenTable()) { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) { + if(num_lock) + num_lock--; + if(num_lock) + return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, gint16 *buffer, int length, int interleave) { + int i; + int data; + gint16 *buf = buffer; + guint32 amsCnt = OPL->amsCnt; + guint32 vibCnt = OPL->vibCnt; + guint8 rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + + if((void *)OPL != cur_chip){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for(i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH; CH < R_CH; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(S_CH); +#ifdef TWELVE_VOICE + for(CH=&S_CH[9]; CH < &S_CH[ADLIB_VOICES]; CH++) + OPL_CALC_CH(CH); +#endif + /* limit check */ + data = Limit(outd[0], OPL_MAXOUT, OPL_MINOUT); + /* store to sound buffer */ + buf[i << interleave] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +} + +/* ---------- reset a chip ---------- */ +void OPLResetChip(FM_OPL *OPL) { + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL, 0x7f); + /* reset with register write */ + OPLWriteReg(OPL, 0x01,0); /* wabesel 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 parameter */ + for(c = 0; c < OPL->max_ch ;c++ ) { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0; s < 2; s++ ) { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF + 1; + CH->SLOT[s].evs = 0; + } + } +} + +/* ---------- Create a virtual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL *OPLCreate(int type, int clock, int rate) { + char *ptr; + FM_OPL *OPL; + int state_size; +#ifdef TWELVE_VOICE + int max_ch = 12; +#else + int max_ch = 9; /* normaly 9 channels */ +#endif + + if( OPL_LockTable() == -1) + return NULL; + /* allocate OPL state space */ + state_size = sizeof(FM_OPL); + state_size += sizeof(OPL_CH) * max_ch; + + /* allocate memory block */ + ptr = (char *)calloc(state_size, 1); + if(ptr == NULL) + return NULL; + + /* clear */ + memset(ptr, 0, state_size); + OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); + OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; + + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + + /* init grobal tables */ + OPL_initalize(OPL); + + /* reset chip */ + OPLResetChip(OPL); + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) { + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ + +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} + +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +/* ---------- YM3812 I/O interface ---------- */ +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; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) { + if(!(a & 1)) { /* status port */ + return OPL->status & (OPL->statusmask | 0x80); + } + /* data port */ + switch(OPL->address) { + case 0x05: /* KeyBoard IN */ + fprintf(stderr, "OPL:read unmapped KEYBOARD port\n"); + return 0; + case 0x19: /* I/O DATA */ + fprintf(stderr, "OPL:read unmapped I/O port\n"); + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + } + return 0; +} + +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->TimerHandler) + (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); + return OPL->status >> 7; +} diff --git a/engines/sci/sfx/softseq/fmopl.h b/engines/sci/sfx/softseq/fmopl.h new file mode 100644 index 0000000000..5bb1cd80d7 --- /dev/null +++ b/engines/sci/sfx/softseq/fmopl.h @@ -0,0 +1,168 @@ +/*************************************************************************** + fmopl.h Copyright (C) 1999-2000 Tatsuyuki Satoh + 2001-2004 The ScummVM project + 2002 Solomon Peachy + 2004 Walter van Niftrik + + This program may be modified and copied freely according to the terms of + the GNU general public license (GPL), as long as the above copyright + notice and the licensing information contained herein are preserved. + + Please refer to www.gnu.org for licensing details. + + This work is provided AS IS, without warranty of any kind, expressed or + implied, including but not limited to the warranties of merchantibility, + noninfringement, and fitness for a specific purpose. The author will not + be held liable for any damage caused by this work or derivatives of it. + + By using this source code, you agree to the licensing terms as stated + above. + + LGPL licensed version of MAMEs fmopl (V0.37a modified) by + Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + +***************************************************************************/ + +#ifndef FMOPL_H_ +#define FMOPL_H_ + +#include <scitypes.h> + +#define TWELVE_VOICE 1 +#ifdef TWELVE_VOICE +#define ADLIB_VOICES 12 +#else +#define ADLIB_VOICES 9 +#endif + +enum { + FMOPL_ENV_BITS_HQ = 16, + FMOPL_ENV_BITS_LQ = 8, + FMOPL_EG_ENT_HQ = 4096, + FMOPL_EG_ENT_LQ = 128 +}; + + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + int TL; /* total level :TL << 8 */ + int TLL; /* adjusted now TL */ + guint8 KSR; /* key scale rate :(shift down bit) */ + int *AR; /* attack rate :&AR_TABLE[AR<<2] */ + int *DR; /* decay rate :&DR_TABLE[DR<<2] */ + int SL; /* sustain level :SL_TABLE[SL] */ + int *RR; /* release rate :&DR_TABLE[RR<<2] */ + guint8 ksl; /* keyscale level :(shift down bits) */ + guint8 ksr; /* key scale rate :kcode>>KSR */ + guint32 mul; /* multiple :ML_TABLE[ML] */ + guint32 Cnt; /* frequency count */ + guint32 Incr; /* frequency step */ + + /* envelope generator state */ + guint8 eg_typ;/* envelope type flag */ + guint8 evm; /* envelope phase */ + int evc; /* envelope counter */ + int eve; /* envelope counter end point */ + int evs; /* envelope counter step */ + int evsa; /* envelope step for AR :AR[ksr] */ + int evsd; /* envelope step for DR :DR[ksr] */ + int evsr; /* envelope step for RR :RR[ksr] */ + + /* LFO */ + guint8 ams; /* ams flag */ + guint8 vib; /* vibrate flag */ + /* wave selector */ + int **wavetable; +} OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + guint8 CON; /* connection type */ + guint8 FB; /* feed back :(shift down bit)*/ + int *connect1; /* slot1 output pointer */ + int *connect2; /* slot2 output pointer */ + int op1_out[2]; /* slot1 output for selfeedback */ + + /* phase generator state */ + guint32 block_fnum; /* block+fnum */ + guint8 kcode; /* key code : KeyScaleCode */ + guint32 fc; /* Freq. Increment base */ + guint32 ksl_base; /* KeyScaleLevel Base step */ + guint8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + guint8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + guint8 address; /* address register */ + guint8 status; /* status flag */ + guint8 statusmask; /* status mask */ + guint32 mode; /* Reg.08 : CSM , notesel,etc. */ + + /* Timer */ + int T[2]; /* timer counter */ + guint8 st[2]; /* timer enable */ + + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + + /* Rythm sention */ + guint8 rythm; /* Rythm mode , key flag */ + + /* time tables */ + int AR_TABLE[75]; /* atttack rate tables */ + int DR_TABLE[75]; /* decay rate tables */ + guint32 FN_TABLE[1024];/* fnumber -> increment counter */ + + /* LFO */ + int *ams_table; + int *vib_table; + int amsCnt; + int amsIncr; + int vibCnt; + int vibIncr; + + /* wave selector enable flag */ + guint8 wavesel; + + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL, int a, int v); +unsigned char OPLRead(FM_OPL *OPL, int a); +int OPLTimerOver(FM_OPL *OPL, int c); +void OPLWriteReg(FM_OPL *OPL, int r, int v); +void YM3812UpdateOne(FM_OPL *OPL, gint16 *buffer, int length, int interleave); +#endif diff --git a/engines/sci/sfx/softseq/mt32.cpp b/engines/sci/sfx/softseq/mt32.cpp new file mode 100644 index 0000000000..6b37673c9e --- /dev/null +++ b/engines/sci/sfx/softseq/mt32.cpp @@ -0,0 +1,228 @@ +/*************************************************************************** + Copyright (C) 2008 Walter van Niftrik + + + This program may be modified and copied freely according to the terms of + the GNU general public license (GPL), as long as the above copyright + notice and the licensing information contained herein are preserved. + + Please refer to www.gnu.org for licensing details. + + This work is provided AS IS, without warranty of any kind, expressed or + implied, including but not limited to the warranties of merchantability, + noninfringement, and fitness for a specific purpose. The author will not + be held liable for any damage caused by this work or derivatives of it. + + By using this source code, you agree to the licensing terms as stated + above. + + + Please contact the maintainer for bug reports or inquiries. + + Current Maintainer: + + Walter van Niftrik <w.f.b.w.v.niftrik@stud.tue.nl> + +***************************************************************************/ + +extern "C" { +#include "../softseq.h" +#include "../sequencer.h" +#include "../device.h" +#include "resource.h" +#include "sci_memory.h" +} + +#include "mt32/mt32emu.h" + +/* FIXME */ +#define FREESCI_GAMEDIR ".freesci" + +#define SAMPLE_RATE 32000 +#define CHANNELS SFX_PCM_STEREO_LR +#define STEREO 1 + +static MT32Emu::Synth *synth; +static sfx_sequencer_t *mt32seq; +static int initializing; + +/* MIDI writer */ + +static int +mt32_midi_init(struct _midi_writer *self) +{ + return SFX_OK; +} + +static int +mt32_midi_set_option(struct _midi_writer *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +mt32_midi_write(struct _midi_writer *self, unsigned char *buf, int len) +{ + if (buf[0] == 0xf0) + synth->playSysex(buf, len); + else if (len < 4) { + MT32Emu::Bit32u msg; + msg = buf[0]; + if (len > 0) + msg |= buf[1] << 8; + if (len > 1) + msg |= buf[2] << 16; + synth->playMsg(msg); + } + else + sciprintf("MT32EMU: Skipping non-sysex message of more than 3 bytes.\n"); + + return SFX_OK; +} + +static void +mt32_midi_delay(struct _midi_writer *self, int ticks) +{ +} + +static void +mt32_midi_reset_timer(struct _midi_writer *self) +{ +} + +static void +mt32_midi_close(struct _midi_writer *self) +{ +} + +static midi_writer_t midi_writer_mt32 = { + "mt32", + mt32_midi_init, + mt32_midi_set_option, + mt32_midi_write, + mt32_midi_delay, + NULL, + mt32_midi_reset_timer, + mt32_midi_close +}; + +/* Software sequencer */ + +static void printDebug(void *userData, const char *fmt, va_list list) +{ + if (initializing) { + vprintf(fmt, list); + printf("\n"); + } +} + +static void +mt32_poll(sfx_softseq_t *self, byte *dest, int count) +{ + synth->render((MT32Emu::Bit16s *) dest, count); +} + +static int +mt32_init(sfx_softseq_t *self, byte *data_ptr, int data_length, byte *data2_ptr, + int data2_length) +{ + MT32Emu::SynthProperties prop; + char *home = sci_get_homedir(); + char *romdir; + + if (!home) { + sciprintf("MT32EMU: Couldn't determine home directory.\n"); + return SFX_ERROR; + } + + romdir = (char *) sci_malloc(sizeof(home) + 2 * sizeof(G_DIR_SEPARATOR_S) + + sizeof(FREESCI_GAMEDIR) + 1); + + strcpy(romdir, home); + strcat(romdir, G_DIR_SEPARATOR_S); + strcat(romdir, FREESCI_GAMEDIR); + strcat(romdir, G_DIR_SEPARATOR_S); + + mt32seq = sfx_find_sequencer("MT32"); + if (!mt32seq) { + sciprintf("MT32EMU: Unable to find MT32 sequencer.\n"); + return SFX_ERROR; + } + + prop.sampleRate = SAMPLE_RATE; + prop.useReverb = true; + prop.useDefaultReverb = true; + prop.baseDir = romdir; + prop.userData = NULL; + prop.report = NULL; + prop.printDebug = printDebug; + prop.openFile = NULL; + prop.closeFile = NULL; + + initializing = 1; + synth = new MT32Emu::Synth(); + if (!synth->open(prop)) + return SFX_ERROR; + initializing = 0; + + mt32seq->open(data_length, data_ptr, data2_length, data2_ptr, + &midi_writer_mt32); + + free(romdir); + + return SFX_OK; +} + +static void +mt32_exit(sfx_softseq_t *self) +{ + synth->close(); + delete synth; + + mt32seq->close(); +} + +static void +mt32_allstop(sfx_softseq_t *self) +{ + if (mt32seq->allstop) + mt32seq->allstop(); +} + +static void +mt32_volume(sfx_softseq_t *self, int volume) +{ + if (mt32seq->volume) + mt32seq->volume(volume / 2); /* High volume causes clipping. */ +} + +static int +mt32_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static void +mt32_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + mt32seq->event(cmd, argc, argv); +} + +sfx_softseq_t sfx_softseq_mt32 = { + "mt32emu", + "0.1", + mt32_set_option, + mt32_init, + mt32_exit, + mt32_volume, + mt32_event, + mt32_poll, + mt32_allstop, + NULL, + 001, /* patch.001 */ + SFX_SEQ_PATCHFILE_NONE, + 0x01, /* playflag */ + 1, /* do play channel 9 */ + 32, /* Max polypgony */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/mt32/Makefile.am b/engines/sci/sfx/softseq/mt32/Makefile.am new file mode 100644 index 0000000000..5d2ffa31c8 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/Makefile.am @@ -0,0 +1,5 @@ +noinst_HEADERS = freeverb.h mt32emu.h part.h partialManager.h synth.h \ +i386.h mt32_file.h partial.h structures.h tables.h +noinst_LIBRARIES = libmt32emu.a +libmt32emu_a_SOURCES = freeverb.cpp mt32_file.cpp partial.cpp synth.cpp \ +i386.cpp part.cpp partialManager.cpp tables.cpp diff --git a/engines/sci/sfx/softseq/mt32/freeverb.cpp b/engines/sci/sfx/softseq/mt32/freeverb.cpp new file mode 100644 index 0000000000..1c3aab0494 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/freeverb.cpp @@ -0,0 +1,310 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Comb filter implementation +// +// Written by +// http://www.dreampoint.co.uk +// This code is public domain + +#include "freeverb.h" + +comb::comb() { + filterstore = 0; + bufidx = 0; +} + +void comb::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void comb::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void comb::setdamp(float val) { + damp1 = val; + damp2 = 1 - val; +} + +float comb::getdamp() { + return damp1; +} + +void comb::setfeedback(float val) { + feedback = val; +} + +float comb::getfeedback() { + return feedback; +} + +// Allpass filter implementation + +allpass::allpass() { + bufidx = 0; +} + +void allpass::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void allpass::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void allpass::setfeedback(float val) { + feedback = val; +} + +float allpass::getfeedback() { + return feedback; +} + +// Reverb model implementation + +revmodel::revmodel() { + // Tie the components to their buffers + combL[0].setbuffer(bufcombL1,combtuningL1); + combR[0].setbuffer(bufcombR1,combtuningR1); + combL[1].setbuffer(bufcombL2,combtuningL2); + combR[1].setbuffer(bufcombR2,combtuningR2); + combL[2].setbuffer(bufcombL3,combtuningL3); + combR[2].setbuffer(bufcombR3,combtuningR3); + combL[3].setbuffer(bufcombL4,combtuningL4); + combR[3].setbuffer(bufcombR4,combtuningR4); + combL[4].setbuffer(bufcombL5,combtuningL5); + combR[4].setbuffer(bufcombR5,combtuningR5); + combL[5].setbuffer(bufcombL6,combtuningL6); + combR[5].setbuffer(bufcombR6,combtuningR6); + combL[6].setbuffer(bufcombL7,combtuningL7); + combR[6].setbuffer(bufcombR7,combtuningR7); + combL[7].setbuffer(bufcombL8,combtuningL8); + combR[7].setbuffer(bufcombR8,combtuningR8); + allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); + allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); + allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); + allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); + allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); + allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); + allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); + allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + + // Set default values + allpassL[0].setfeedback(0.5f); + allpassR[0].setfeedback(0.5f); + allpassL[1].setfeedback(0.5f); + allpassR[1].setfeedback(0.5f); + allpassL[2].setfeedback(0.5f); + allpassR[2].setfeedback(0.5f); + allpassL[3].setfeedback(0.5f); + allpassR[3].setfeedback(0.5f); + setwet(initialwet); + setroomsize(initialroom); + setdry(initialdry); + setdamp(initialdamp); + setwidth(initialwidth); + setmode(initialmode); + + // Buffer will be full of rubbish - so we MUST mute them + mute(); +} + +void revmodel::mute() { + int i; + + if (getmode() >= freezemode) + return; + + for (i = 0; i < numcombs; i++) { + combL[i].mute(); + combR[i].mute(); + } + + for (i = 0; i < numallpasses; i++) { + allpassL[i].mute(); + allpassR[i].mute(); + } +} + +void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output REPLACING anything already there + *outputL = outL * wet1 + outR * wet2 + *inputL * dry; + *outputR = outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output MIXING with anything already there + *outputL += outL * wet1 + outR * wet2 + *inputL * dry; + *outputR += outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::update() { + // Recalculate internal values after parameter change + + int i; + + wet1 = wet * (width / 2 + 0.5f); + wet2 = wet * ((1 - width) / 2); + + if (mode >= freezemode) { + roomsize1 = 1; + damp1 = 0; + gain = muted; + } else { + roomsize1 = roomsize; + damp1 = damp; + gain = fixedgain; + } + + for (i = 0; i < numcombs; i++) { + combL[i].setfeedback(roomsize1); + combR[i].setfeedback(roomsize1); + } + + for (i = 0; i < numcombs; i++) { + combL[i].setdamp(damp1); + combR[i].setdamp(damp1); + } +} + +// The following get/set functions are not inlined, because +// speed is never an issue when calling them, and also +// because as you develop the reverb model, you may +// wish to take dynamic action when they are called. + +void revmodel::setroomsize(float value) { + roomsize = (value * scaleroom) + offsetroom; + update(); +} + +float revmodel::getroomsize() { + return (roomsize - offsetroom) / scaleroom; +} + +void revmodel::setdamp(float value) { + damp = value * scaledamp; + update(); +} + +float revmodel::getdamp() { + return damp / scaledamp; +} + +void revmodel::setwet(float value) { + wet = value * scalewet; + update(); +} + +float revmodel::getwet() { + return wet / scalewet; +} + +void revmodel::setdry(float value) { + dry = value * scaledry; +} + +float revmodel::getdry() { + return dry / scaledry; +} + +void revmodel::setwidth(float value) { + width = value; + update(); +} + +float revmodel::getwidth() { + return width; +} + +void revmodel::setmode(float value) { + mode = value; + update(); +} + +float revmodel::getmode() { + if (mode >= freezemode) + return 1; + else + return 0; +} diff --git a/engines/sci/sfx/softseq/mt32/freeverb.h b/engines/sci/sfx/softseq/mt32/freeverb.h new file mode 100644 index 0000000000..53c5307c5a --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/freeverb.h @@ -0,0 +1,239 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Macro for killing denormalled numbers +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// Based on IS_DENORMAL macro by Jon Watte +// This code is public domain + +#ifndef FREEVERB_H +#define FREEVERB_H + +#define undenormalise(sample) if (((*(unsigned int*)&sample) & 0x7f800000) == 0) sample = 0.0f + +// Comb filter class declaration + +class comb { +public: + comb(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setdamp(float val); + float getdamp(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float filterstore; + float damp1; + float damp2; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float comb::process(float input) { + float output; + + output = buffer[bufidx]; + undenormalise(output); + + filterstore = (output * damp2) + (filterstore * damp1); + undenormalise(filterstore); + + buffer[bufidx] = input + (filterstore * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + +// Allpass filter declaration + +class allpass { +public: + allpass(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float allpass::process(float input) { + float output; + float bufout; + + bufout = buffer[bufidx]; + undenormalise(bufout); + + output = -input + bufout; + buffer[bufidx] = input + (bufout * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + + +// Reverb model tuning values + +const int numcombs = 8; +const int numallpasses = 4; +const float muted = 0; +const float fixedgain = 0.015f; +const float scalewet = 3; +const float scaledry = 2; +const float scaledamp = 0.4f; +const float scaleroom = 0.28f; +const float offsetroom = 0.7f; +const float initialroom = 0.5f; +const float initialdamp = 0.5f; +const float initialwet = 1 / scalewet; +const float initialdry = 0; +const float initialwidth = 1; +const float initialmode = 0; +const float freezemode = 0.5f; +const int stereospread = 23; + +// These values assume 44.1KHz sample rate +// they will probably be OK for 48KHz sample rate +// but would need scaling for 96KHz (or other) sample rates. +// The values were obtained by listening tests. +const int combtuningL1 = 1116; +const int combtuningR1 = 1116 + stereospread; +const int combtuningL2 = 1188; +const int combtuningR2 = 1188 + stereospread; +const int combtuningL3 = 1277; +const int combtuningR3 = 1277 + stereospread; +const int combtuningL4 = 1356; +const int combtuningR4 = 1356 + stereospread; +const int combtuningL5 = 1422; +const int combtuningR5 = 1422 + stereospread; +const int combtuningL6 = 1491; +const int combtuningR6 = 1491 + stereospread; +const int combtuningL7 = 1557; +const int combtuningR7 = 1557 + stereospread; +const int combtuningL8 = 1617; +const int combtuningR8 = 1617 + stereospread; +const int allpasstuningL1 = 556; +const int allpasstuningR1 = 556 + stereospread; +const int allpasstuningL2 = 441; +const int allpasstuningR2 = 441 + stereospread; +const int allpasstuningL3 = 341; +const int allpasstuningR3 = 341 + stereospread; +const int allpasstuningL4 = 225; +const int allpasstuningR4 = 225 + stereospread; + + +// Reverb model declaration + +class revmodel { +public: + revmodel(); + void mute(); + void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void setroomsize(float value); + float getroomsize(); + void setdamp(float value); + float getdamp(); + void setwet(float value); + float getwet(); + void setdry(float value); + float getdry(); + void setwidth(float value); + float getwidth(); + void setmode(float value); + float getmode(); +private: + void update(); + + float gain; + float roomsize, roomsize1; + float damp, damp1; + float wet, wet1, wet2; + float dry; + float width; + float mode; + + // The following are all declared inline + // to remove the need for dynamic allocation + // with its subsequent error-checking messiness + + // Comb filters + comb combL[numcombs]; + comb combR[numcombs]; + + // Allpass filters + allpass allpassL[numallpasses]; + allpass allpassR[numallpasses]; + + // Buffers for the combs + float bufcombL1[combtuningL1]; + float bufcombR1[combtuningR1]; + float bufcombL2[combtuningL2]; + float bufcombR2[combtuningR2]; + float bufcombL3[combtuningL3]; + float bufcombR3[combtuningR3]; + float bufcombL4[combtuningL4]; + float bufcombR4[combtuningR4]; + float bufcombL5[combtuningL5]; + float bufcombR5[combtuningR5]; + float bufcombL6[combtuningL6]; + float bufcombR6[combtuningR6]; + float bufcombL7[combtuningL7]; + float bufcombR7[combtuningR7]; + float bufcombL8[combtuningL8]; + float bufcombR8[combtuningR8]; + + // Buffers for the allpasses + float bufallpassL1[allpasstuningL1]; + float bufallpassR1[allpasstuningR1]; + float bufallpassL2[allpasstuningL2]; + float bufallpassR2[allpasstuningR2]; + float bufallpassL3[allpasstuningL3]; + float bufallpassR3[allpasstuningR3]; + float bufallpassL4[allpasstuningL4]; + float bufallpassR4[allpasstuningR4]; +}; + +#endif diff --git a/engines/sci/sfx/softseq/mt32/i386.cpp b/engines/sci/sfx/softseq/mt32/i386.cpp new file mode 100644 index 0000000000..f092189d76 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/i386.cpp @@ -0,0 +1,849 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "mt32emu.h" + +#ifdef MT32EMU_HAVE_X86 + +namespace MT32Emu { + +#ifndef _MSC_VER + +#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value)) +#define cpuid_flag (1 << 21) + +static inline bool atti386_DetectCPUID() { + unsigned int result; + + // Is there a cpuid? + result = cpuid_flag; // set test + eflag(result); + if (!(result & cpuid_flag)) + return false; + + result = 0; // clear test + eflag(result); + if (result & cpuid_flag) + return false; + + return true; +} + +static inline bool atti386_DetectSIMD() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + /* check cpuid */ + __asm__ __volatile__( + "pushl %%ebx \n" \ + "movl $1, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "ecx", "edx"); + + if (result & (1 << 25)) + return true; + + return false; +} + +static inline bool atti386_Detect3DNow() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + // get cpuid + __asm__ __volatile__( + "pushl %%ebx \n" \ + "movl $0x80000001, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "ecx", "edx"); + + if (result & 0x80000000) + return true; + + return false; +} + + +static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) { + __asm__ __volatile__ ( + "pushl %1 \n" \ + "pushl %2 \n" \ + "movss 0(%0), %%xmm1 \n" \ + "movups 0(%1), %%xmm2 \n" \ + "movlps 0(%2), %%xmm3 \n" \ + " \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + " \n" \ + "addl $16, %1 \n" \ + "addl $8, %2 \n" \ + " \n" \ + "movups 0(%1), %%xmm2 \n" \ + " \n" \ + "movlps 0(%2), %%xmm3 \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + "movss %%xmm1, 0(%0) \n" \ + "popl %2 \n" \ + "popl %1 \n" \ + : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr) + : "memory" +#ifdef __SSE__ + , "xmm1", "xmm2", "xmm3" +#endif + ); + + return *output; +} + +static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) { + float tmp; + + __asm__ __volatile__ ( + "movq %0, %%mm1 \n" \ + " \n" \ + "movl %1, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + " \n" \ + "movl %2, %%eax; \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + " \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%eax \n" \ + " \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%edi \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "movd %%mm1, %0 \n" \ + "femms \n" \ + : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp) + : "eax", "edi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); + + return output; +} + +static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %1, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm3 \n" \ + "por %%mm2, %%mm3 \n" \ + "movl %2, %%esi \n" \ + "movl %3, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm3, %%mm1 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%edi) \n" \ + " \n" \ + "addl $8, %%esi \n" \ + "addl $8, %%edi \n" \ + " \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd) + : "eax", "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) { + __asm__ __volatile__( + "movl %4, %%ecx \n" \ + "shrl $1, %%ecx \n" \ + "addl $4, %%ecx \n" \ + "pushl %%ecx \n" \ + " \n" \ + "movl %0, %%esi \n" \ + "movups 0(%%esi), %%xmm1 \n" \ + " \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "xorl %%eax, %%eax \n" \ + "movw 0(%1), %%ax \n" \ + "cwde \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm1 \n" \ + "psrlq $32, %%mm1 \n" \ + "movw 0(%1), %%ax \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm2 \n" \ + "por %%mm2, %%mm1 \n" \ + " \n" \ + "decl %%ecx \n" \ + "jnz 1b \n" \ + " \n" \ + "popl %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %3, %%edi \n" \ + "incl %%esi \n" \ + "2: \n" \ + "decl %%ecx \n" \ + "jnz 2b \n" \ + : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len) + : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory"); +} + +static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%edi), %%mm1 \n" \ + "movq 0(%%esi), %%mm2 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq %%mm1, %%mm3 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "paddw %%mm3, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %2, %%ax \n" \ + "movd %%eax, %%mm1 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm1 \n" \ + "por %%mm2, %%mm1 \n" \ + "movl %3, %%edi \n" \ + "movl %4, %%esi \n" \ + "pushl %%ebx \n" \ + "1: \n" \ + "movw 0(%%esi), %%bx \n" \ + "addl $2, %%esi \n" \ + "movw 0(%%esi), %%dx \n" \ + "addl $2, %%esi \n" \ + "" \ + "movw %%dx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%dx, %%ax \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm2 \n" \ + "movw %%bx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%bx, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "por %%mm3, %%mm2 \n" \ + "" \ + "pmulhw %%mm1, %%mm2 \n" \ + "movq %%mm2, 0(%%edi) \n" \ + "addl $8, %%edi \n" \ + "" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + "popl %%ebx \n" \ + : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) + : "eax", "ecx", "edx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +#endif + +bool DetectSIMD() { +#ifdef _MSC_VER + bool found_simd; + __asm { + pushfd + pop eax // get EFLAGS into eax + mov ebx,eax // keep a copy + xor eax,0x200000 + // toggle CPUID bit + + push eax + popfd // set new EFLAGS + pushfd + pop eax // EFLAGS back into eax + + xor eax,ebx + // have we changed the ID bit? + + je NO_SIMD + // No, no CPUID instruction + + // we could toggle the + // ID bit so CPUID is present + mov eax,1 + + cpuid // get processor features + test edx,1<<25 // check the SIMD bit + jz NO_SIMD + mov found_simd,1 + jmp DONE + NO_SIMD: + mov found_simd,0 + DONE: + } + return found_simd; +#else + return atti386_DetectSIMD(); +#endif +} + +bool Detect3DNow() { +#ifdef _MSC_VER + bool found3D = false; + __asm { + pushfd + pop eax + mov edx, eax + xor eax, 00200000h + push eax + popfd + pushfd + pop eax + xor eax, edx + jz NO_3DNOW + + mov eax, 80000000h + cpuid + + cmp eax, 80000000h + jbe NO_3DNOW + + mov eax, 80000001h + cpuid + test edx, 80000000h + jz NO_3DNOW + mov found3D, 1 +NO_3DNOW: + + } + return found3D; +#else + return atti386_Detect3DNow(); +#endif +} + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + +#ifdef _MSC_VER + __asm { + + movss xmm1, output + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + add coef_ptr, 16 + add hist1_ptr, 8 + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + movss output, xmm1 + } +#else + output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); +#endif + return output; +} + +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + // I find it very sad that 3DNow requires twice as many instructions as Intel's SSE + // Intel does have the upper hand here. +#ifdef _MSC_VER + float tmp; + __asm { + movq mm1, output + mov ebx, coef_ptr + movq mm2, [ebx] + + mov eax, hist1_ptr; + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + + movd DWORD PTR [eax+4], mm3 + + add ebx, 8 + add eax, 8 + + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + movd DWORD PTR [eax+4], mm3 + + movd output, mm1 + + femms + } +#else + output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); +#endif + return output; +} + +#if MT32EMU_USE_MMX > 0 + +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) { + int tmplen = len >> 1; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx,tmplen + mov ax, leftvol + shl eax,16 + mov ax, rightvol + movd mm1, eax + movd mm2, eax + psllq mm1, 32 + por mm1, mm2 + mov edi, partialBuf + mov esi, mixedBuf +mmxloop1: + mov bx, [esi] + add esi,2 + mov dx, [esi] + add esi,2 + + mov ax, dx + shl eax, 16 + mov ax, dx + movd mm2,eax + psllq mm2, 32 + mov ax, bx + shl eax, 16 + mov ax, bx + movd mm3,eax + por mm2,mm3 + + pmulhw mm2, mm1 + movq [edi], mm2 + add edi, 8 + + dec ecx + cmp ecx,0 + jg mmxloop1 + emms + } +#else + atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf); +#endif + return tmplen << 1; +} + +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop1: + movq mm1, [edi] + movq mm2, [esi] + paddw mm1,mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop1 + emms + } +#else + atti386_mixBuffers(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + + +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop2: + movq mm1, [esi] + movq mm2, [edi] + movq mm3, mm1 + pmulhw mm1, mm2 + paddw mm1,mm3 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop2 + emms + } +#else + atti386_mixBuffersRingMix(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop3: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop3 + emms + } +#else + atti386_mixBuffersRing(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { + int tmplen = (len >> 1); + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov ax,volume + shl eax,16 + mov ax,volume + movd mm3,eax + movd mm2,eax + psllq mm3, 32 + por mm3,mm2 + mov esi, useBuf + mov edi, stream +mixloop4: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm3 + paddw mm1,mm2 + movq [edi], mm1 + + add esi,8 + add edi,8 + + dec ecx + cmp ecx,0 + jg mixloop4 + emms + } +#else + atti386_produceOutput1(tmplen, volume, useBuf, stream); +#endif + return tmplen << 1; +} + +#endif + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/i386.h b/engines/sci/sfx/softseq/mt32/i386.h new file mode 100644 index 0000000000..e8644411cd --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/i386.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_I386_H +#define MT32EMU_I386_H + +namespace MT32Emu { +#ifdef MT32EMU_HAVE_X86 + +// Function that detects the availablity of SSE SIMD instructions +bool DetectSIMD(); +// Function that detects the availablity of 3DNow instructions +bool Detect3DNow(); + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr); + +#if MT32EMU_USE_MMX > 0 +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf); +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len); +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume); +#endif + +#endif + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/mt32_file.cpp b/engines/sci/sfx/softseq/mt32/mt32_file.cpp new file mode 100644 index 0000000000..86cb29fd49 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/mt32_file.cpp @@ -0,0 +1,112 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdio.h> + +#include "mt32emu.h" + +namespace MT32Emu { + + bool ANSIFile::open(const char *filename, OpenMode mode) { + const char *fmode; + if (mode == OpenMode_read) { + fmode = "rb"; + } else { + fmode = "wb"; + } + fp = fopen(filename, fmode); + return (fp != NULL); + } + + void ANSIFile::close() { + fclose(fp); + } + + size_t ANSIFile::read(void *in, size_t size) { + return fread(in, 1, size, fp); + } + + bool ANSIFile::readLine(char *in, size_t size) { + return fgets(in, (int)size, fp) != NULL; + } + + bool ANSIFile::readBit8u(Bit8u *in) { + int c = fgetc(fp); + if (c == EOF) + return false; + *in = (Bit8u)c; + return true; + } + + bool File::readBit16u(Bit16u *in) { + Bit8u b[2]; + if (read(&b[0], 2) != 2) + return false; + *in = ((b[0] << 8) | b[1]); + return true; + } + + bool File::readBit32u(Bit32u *in) { + Bit8u b[4]; + if (read(&b[0], 4) != 4) + return false; + *in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + return true; + } + + size_t ANSIFile::write(const void *out, size_t size) { + return fwrite(out, 1, size, fp); + } + + bool ANSIFile::writeBit8u(Bit8u out) { + return fputc(out, fp) != EOF; + } + + bool File::writeBit16u(Bit16u out) { + if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x00FF))) { + return false; + } + return true; + } + + bool File::writeBit32u(Bit32u out) { + if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x000000FF))) { + return false; + } + return true; + } + + bool ANSIFile::isEOF() { + return feof(fp) != 0; + } +} diff --git a/engines/sci/sfx/softseq/mt32/mt32_file.h b/engines/sci/sfx/softseq/mt32/mt32_file.h new file mode 100644 index 0000000000..5f05c9e9ae --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/mt32_file.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_FILE_H +#define MT32EMU_FILE_H + +#include <stdio.h> + +namespace MT32Emu { + +class File { +public: + enum OpenMode { + OpenMode_read = 0, + OpenMode_write = 1 + }; + virtual ~File() {} + virtual void close() = 0; + virtual size_t read(void *in, size_t size) = 0; + virtual bool readLine(char *in, size_t size) = 0; + virtual bool readBit8u(Bit8u *in) = 0; + virtual bool readBit16u(Bit16u *in); + virtual bool readBit32u(Bit32u *in); + virtual size_t write(const void *out, size_t size) = 0; + virtual bool writeBit8u(Bit8u out) = 0; + // Note: May write a single byte to the file before failing + virtual bool writeBit16u(Bit16u out); + // Note: May write some (<4) bytes to the file before failing + virtual bool writeBit32u(Bit32u out); + virtual bool isEOF() = 0; +}; + +class ANSIFile: public File { +private: + FILE *fp; +public: + bool open(const char *filename, OpenMode mode); + void close(); + size_t read(void *in, size_t size); + bool readLine(char *in, size_t size); + bool readBit8u(Bit8u *in); + size_t write(const void *out, size_t size); + bool writeBit8u(Bit8u out); + bool isEOF(); +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/mt32emu.h b/engines/sci/sfx/softseq/mt32/mt32emu.h new file mode 100644 index 0000000000..0aa4df7488 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/mt32emu.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_MT32EMU_H +#define MT32EMU_MT32EMU_H + +// Debugging +// Show the instruments played +#define MT32EMU_MONITOR_INSTRUMENTS 1 +// Shows number of partials MT-32 is playing, and on which parts +#define MT32EMU_MONITOR_PARTIALS 0 +// Determines how the waveform cache file is handled (must be regenerated after sampling rate change) +#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache +//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generage but don't save cache +//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache +//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache + +// Configuration +// The maximum number of partials playing simultaneously +#define MT32EMU_MAX_PARTIALS 32 +// The maximum number of notes playing simultaneously per part. +// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. +#define MT32EMU_MAX_POLY 32 +// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow. +#define MT32EMU_ACCURATENOTES 0 + +#if (defined (_MSC_VER) && defined(_M_IX86)) +#define MT32EMU_HAVE_X86 +#elif defined(__GNUC__) +#if __GNUC__ >= 3 && defined(__i386__) +#define MT32EMU_HAVE_X86 +#endif +#endif + +#ifdef MT32EMU_HAVE_X86 +#define MT32EMU_USE_MMX 1 +#else +#define MT32EMU_USE_MMX 0 +#endif + +#include "freeverb.h" + +#include "structures.h" +#include "i386.h" +#include "mt32_file.h" +#include "tables.h" +#include "partial.h" +#include "partialManager.h" +#include "part.h" +#include "synth.h" + +#endif diff --git a/engines/sci/sfx/softseq/mt32/part.cpp b/engines/sci/sfx/softseq/mt32/part.cpp new file mode 100644 index 0000000000..b3d71bccca --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/part.cpp @@ -0,0 +1,632 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +namespace MT32Emu { + +static const Bit8u PartialStruct[13] = { + 0, 0, 2, 2, 1, 3, + 3, 0, 3, 0, 2, 1, 3 }; + +static const Bit8u PartialMixStruct[13] = { + 0, 1, 0, 1, 1, 0, + 1, 3, 3, 2, 2, 2, 2 }; + +static const float floatKeyfollow[17] = { + -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f, + 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f, + 5.0f/4.0f, 3.0f/2.0f, 2.0f, + 1.0009765625f, 1.0048828125f +}; + +//FIXME:KG: Put this dpoly stuff somewhere better +bool dpoly::isActive() const { + return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; +} + +Bit32u dpoly::getAge() const { + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL) { + return partials[i]->age; + } + } + return 0; +} + +RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) { + strcpy(name, "Rhythm"); + rhythmTemp = &synth->mt32ram.rhythmSettings[0]; + refresh(); +} + +Part::Part(Synth *useSynth, unsigned int usePartNum) { + this->synth = useSynth; + this->partNum = usePartNum; + patchCache[0].dirty = true; + holdpedal = false; + patchTemp = &synth->mt32ram.patchSettings[partNum]; + if (usePartNum == 8) { + // Nasty hack for rhythm + timbreTemp = NULL; + } else { + sprintf(name, "Part %d", partNum + 1); + timbreTemp = &synth->mt32ram.timbreSettings[partNum]; + } + currentInstr[0] = 0; + currentInstr[10] = 0; + expression = 127; + volumeMult = 0; + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + bend = 0.0f; + memset(polyTable,0,sizeof(polyTable)); + memset(patchCache, 0, sizeof(patchCache)); +} + +void Part::setHoldPedal(bool pedalval) { + if (holdpedal && !pedalval) { + holdpedal = false; + stopPedalHold(); + } else { + holdpedal = pedalval; + } +} + +void RhythmPart::setBend(unsigned int midiBend) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); + return; +} + +void Part::setBend(unsigned int midiBend) { + // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0 + if (midiBend <= 0x2000) { + bend = ((signed int)midiBend - 0x2000) / (float)0x2000; + } else { + bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF; + } + // Loop through all partials to update their bend + for (int i = 0; i < MT32EMU_MAX_POLY; i++) { + for (int j = 0; j < 4; j++) { + if (polyTable[i].partials[j] != NULL) { + polyTable[i].partials[j]->setBend(bend); + } + } + } +} + +void RhythmPart::setModulation(unsigned int midiModulation) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation); +} + +void Part::setModulation(unsigned int midiModulation) { + // Just a bloody guess, as always, before I get things figured out + for (int t = 0; t < 4; t++) { + if (patchCache[t].playPartial) { + int newrate = (patchCache[t].modsense * midiModulation) >> 7; + //patchCache[t].lfoperiod = lfotable[newrate]; + patchCache[t].lfodepth = newrate; + //FIXME:KG: timbreTemp->partial[t].lfo.depth = + } + } +} + +void RhythmPart::refresh() { + updateVolume(); + // (Re-)cache all the mapped timbres ahead of time + for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) { + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) // 94 on MT-32 + continue; + Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14... + // FIXME:KG: Panning cache should be backed up to partials using it, too + if (pan < 7) { + drumPan[drumNum].leftvol = pan * 4681; + drumPan[drumNum].rightvol = 32767; + } else { + drumPan[drumNum].rightvol = (14 - pan) * 4681; + drumPan[drumNum].leftvol = 32767; + } + PatchCache *cache = drumCache[drumNum]; + backupCacheToPartials(cache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = true; + cache[t].pitchShift = 0.0f; + cache[t].benderRange = 0.0f; + cache[t].pansetptr = &drumPan[drumNum]; + cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } + } +} + +void Part::refresh() { + updateVolume(); + backupCacheToPartials(patchCache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].dirty = true; + patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f; + patchCache[t].benderRange = patchTemp->patch.benderRange; + patchCache[t].pansetptr = &volumesetting; + patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0; + } + memcpy(currentInstr, timbreTemp->common.name, 10); +} + +const char *Part::getCurrentInstr() const { + return ¤tInstr[0]; +} + +void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { + for (int m = 0; m < 85; m++) { + if (rhythmTemp[m].timbre == absTimbreNum - 128) + drumCache[m][0].dirty = true; + } +} + +void Part::refreshTimbre(unsigned int absTimbreNum) { + if (getAbsTimbreNum() == absTimbreNum) { + memcpy(currentInstr, timbreTemp->common.name, 10); + patchCache[0].dirty = true; + } +} + +int Part::fixBiaslevel(int srcpnt, int *dir) { + int noteat = srcpnt & 0x3F; + int outnote; + if (srcpnt < 64) + *dir = 0; + else + *dir = 1; + outnote = 33 + noteat; + //synth->printDebug("Bias note %d, dir %d", outnote, *dir); + + return outnote; +} + +int Part::fixKeyfollow(int srckey) { + if (srckey>=0 && srckey<=16) { + int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; + return keyfix[srckey]; + } else { + //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); + return 256; + } +} + +void Part::abortPoly(dpoly *poly) { + if (!poly->isPlaying) { + return; + } + for (int i = 0; i < 4; i++) { + Partial *partial = poly->partials[i]; + if (partial != NULL) { + partial->deactivate(); + } + } + poly->isPlaying = false; +} + +void Part::setPatch(const PatchParam *patch) { + patchTemp->patch = *patch; +} + +void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { + synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name); +} + +void Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int RhythmPart::getAbsTimbreNum() const { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name); + return 0; +} + +unsigned int Part::getAbsTimbreNum() const { + return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; +} + +void RhythmPart::setProgram(unsigned int patchNum) { + synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum); +} + +void Part::setProgram(unsigned int patchNum) { + setPatch(&synth->mt32ram.patches[patchNum]); + setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre); + + refresh(); + + allSoundOff(); //FIXME:KG: Is this correct? +} + +void Part::backupCacheToPartials(PatchCache cache[4]) { + // check if any partials are still playing with the old patch cache + // if so then duplicate the cached data from the part to the partial so that + // we can change the part's cache without affecting the partial. + // We delay this until now to avoid a copy operation with every note played + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL && partial->patchCache == &cache[i]) { + partial->cachebackup = cache[i]; + partial->patchCache = &partial->cachebackup; + } + } + } +} + +void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { + backupCacheToPartials(cache); + int partialCount = 0; + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 1) { + cache[t].playPartial = true; + partialCount++; + } else { + cache[t].playPartial = false; + continue; + } + + // Calculate and cache common parameters + + cache[t].pcm = timbre->partial[t].wg.pcmwave; + cache[t].useBender = (timbre->partial[t].wg.bender == 1); + + switch (t) { + case 0: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 0; + cache[t].structurePair = 1; + break; + case 1: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 1; + cache[t].structurePair = 0; + break; + case 2: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 0; + cache[t].structurePair = 3; + break; + case 3: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 1; + cache[t].structurePair = 2; + break; + default: + break; + } + + cache[t].waveform = timbre->partial[t].wg.waveform; + cache[t].pulsewidth = timbre->partial[t].wg.pulsewid; + cache[t].pwsens = timbre->partial[t].wg.pwvelo; + if (timbre->partial[t].wg.keyfollow > 16) { + synth->printDebug("Bad keyfollow value in timbre!"); + cache[t].pitchKeyfollow = 1.0f; + } else { + cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow]; + } + + cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f; + cache[t].pitchEnv = timbre->partial[t].env; + cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f); + cache[t].pitchsustain = cache[t].pitchEnv.level[3]; + + // Calculate and cache TVA envelope stuff + cache[t].ampEnv = timbre->partial[t].tva; + cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); + + cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]); + cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1; + cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]); + cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2; + cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf; + + // Calculate and cache filter stuff + cache[t].filtEnv = timbre->partial[t].tvf; + cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow); + cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27); + cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir); + cache[t].tvfblevel = cache[t].filtEnv.biaslevel; + cache[t].filtsustain = cache[t].filtEnv.envlevel[3]; + + // Calculate and cache LFO stuff + cache[t].lfodepth = timbre->partial[t].lfo.depth; + cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate]; + cache[t].lforate = timbre->partial[t].lfo.rate; + cache[t].modsense = timbre->partial[t].lfo.modsense; + } + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = false; + cache[t].partialCount = partialCount; + cache[t].sustain = (timbre->common.nosustain == 0); + } + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform); + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): Recached timbre", name, currentInstr); + for (int i = 0; i < 4; i++) { + synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); + } +#endif +} + +const char *Part::getName() const { + return name; +} + +void Part::updateVolume() { + volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127]; +} + +int Part::getVolume() const { + // FIXME: Use the mappings for this in the control ROM + return patchTemp->outlevel * 127 / 100; +} + +void Part::setVolume(int midiVolume) { + // FIXME: Use the mappings for this in the control ROM + patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127); + updateVolume(); + synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume); +} + +void Part::setExpression(int midiExpression) { + expression = midiExpression; + updateVolume(); +} + +void RhythmPart::setPan(unsigned int midiPan) +{ + // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan); +} + +void Part::setPan(unsigned int midiPan) { + // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100% + // (But this makes the range somewhat skewed) + // Check against the real thing + // NOTE: Panning is inverted compared to GM. + if (midiPan < 64) { + volumesetting.leftvol = (Bit16s)(midiPan * 512); + volumesetting.rightvol = 32767; + } else if (midiPan == 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + } else { + volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520); + volumesetting.leftvol = 32767; + } + patchTemp->panpot = (Bit8u)(midiPan * 14 / 127); + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void RhythmPart::playNote(unsigned int key, int vel) { + if (key < 24 || key > 108)/*> 87 on MT-32)*/ { + synth->printDebug("%s: Attempted to play invalid key %d", name, key); + return; + } + int drumNum = key - 24; + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) { // 94 on MT-32 + synth->printDebug("%s: Attempted to play unmapped key %d", name, key); + return; + } + int absTimbreNum = drumTimbreNum + 128; + TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre; + memcpy(currentInstr, timbre->common.name, 10); +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key); +#endif + if (drumCache[drumNum][0].dirty) { + cacheTimbre(drumCache[drumNum], timbre); + } + playPoly(drumCache[drumNum], key, MIDDLEC, vel); +} + +void Part::playNote(unsigned int key, int vel) { + int freqNum = key; + if (freqNum < 12) { + synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); + freqNum += 12; + } else if (freqNum > 108) { + synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); + while (freqNum > 108) { + freqNum -= 12; + } + } + // POLY1 mode, Single Assign + // Haven't found any software that uses any of the other poly modes + // FIXME:KG: Should this also apply to rhythm? + for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].key == key)) { + //AbortPoly(&polyTable[i]); + stopNote(key); + break; + } + } +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key); +#endif + if (patchCache[0].dirty) { + cacheTimbre(patchCache, timbreTemp); + } + playPoly(patchCache, key, freqNum, vel); +} + +void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) { + unsigned int needPartials = cache[0].partialCount; + unsigned int freePartials = synth->partialManager->getFreePartialCount(); + + if (freePartials < needPartials) { + if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount()); + return; + } + } + // Find free poly + int m; + for (m = 0; m < MT32EMU_MAX_POLY; m++) { + if (!polyTable[m].isActive()) { + break; + } + } + if (m == MT32EMU_MAX_POLY) { + synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel); + return; + } + + dpoly *tpoly = &polyTable[m]; + + tpoly->isPlaying = true; + tpoly->key = key; + tpoly->isDecay = false; + tpoly->freqnum = freqNum; + tpoly->vel = vel; + tpoly->pedalhold = false; + + bool allnull = true; + for (int x = 0; x < 4; x++) { + if (cache[x].playPartial) { + tpoly->partials[x] = synth->partialManager->allocPartial(partNum); + allnull = false; + } else { + tpoly->partials[x] = NULL; + } + } + + if (allnull) + synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); + + tpoly->sustain = cache[0].sustain; + tpoly->volumeptr = &volumeMult; + + for (int x = 0; x < 4; x++) { + if (tpoly->partials[x] != NULL) { + tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]); + tpoly->partials[x]->setBend(bend); + } + } +} + +static void startDecayPoly(dpoly *tpoly) { + if (tpoly->isDecay) { + return; + } + tpoly->isDecay = true; + + for (int t = 0; t < 4; t++) { + Partial *partial = tpoly->partials[t]; + if (partial == NULL) + continue; + partial->startDecayAll(); + } + tpoly->isPlaying = false; +} + +void Part::allNotesOff() { + // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B) + // should treat the hold pedal as usual. + // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal. + // The latter controller is not implemented on the MT-32 (according to the docs). + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } +} + +void Part::allSoundOff() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + startDecayPoly(tpoly); + } + } +} + +void Part::stopPedalHold() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly; + tpoly = &polyTable[q]; + if (tpoly->isActive() && tpoly->pedalhold) + stopNote(tpoly->key); + } +} + +void Part::stopNote(unsigned int key) { + // Non-sustaining instruments ignore stop commands. + // They die away eventually anyway + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); +#endif + + if (key != 255) { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying && tpoly->key == key) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } + return; + } + + // Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first + // This is simplest + int oldest = -1; + Bit32u oldage = 0; + + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + + if (tpoly->isPlaying && !tpoly->isDecay) { + if (tpoly->getAge() >= oldage) { + oldage = tpoly->getAge(); + oldest = q; + } + } + } + + if (oldest != -1) { + startDecayPoly(&polyTable[oldest]); + } +} + +} diff --git a/engines/sci/sfx/softseq/mt32/part.h b/engines/sci/sfx/softseq/mt32/part.h new file mode 100644 index 0000000000..54c4999653 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/part.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PART_H +#define MT32EMU_PART_H + +namespace MT32Emu { + +class PartialManager; +class Synth; + +class Part { +private: + // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) + MemParams::PatchTemp *patchTemp; + TimbreParam *timbreTemp; + + // 0=Part 1, .. 7=Part 8, 8=Rhythm + unsigned int partNum; + + bool holdpedal; + + StereoVolume volumesetting; + + PatchCache patchCache[4]; + + float bend; // -1.0 .. +1.0 + + dpoly polyTable[MT32EMU_MAX_POLY]; + + void abortPoly(dpoly *poly); + + static int fixKeyfollow(int srckey); + static int fixBiaslevel(int srcpnt, int *dir); + + void setPatch(const PatchParam *patch); + +protected: + Synth *synth; + char name[8]; // "Part 1".."Part 8", "Rhythm" + char currentInstr[11]; + int expression; + Bit32u volumeMult; + + void updateVolume(); + void backupCacheToPartials(PatchCache cache[4]); + void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre); + void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel); + const char *getName() const; + +public: + Part(Synth *synth, unsigned int usePartNum); + virtual ~Part() {} + virtual void playNote(unsigned int key, int vel); + void stopNote(unsigned int key); + void allNotesOff(); + void allSoundOff(); + int getVolume() const; + void setVolume(int midiVolume); + void setExpression(int midiExpression); + virtual void setPan(unsigned int midiPan); + virtual void setBend(unsigned int midiBend); + virtual void setModulation(unsigned int midiModulation); + virtual void setProgram(unsigned int midiProgram); + void setHoldPedal(bool pedalval); + void stopPedalHold(); + virtual void refresh(); + virtual void refreshTimbre(unsigned int absTimbreNum); + virtual void setTimbre(TimbreParam *timbre); + virtual unsigned int getAbsTimbreNum() const; + const char *getCurrentInstr() const; +}; + +class RhythmPart: public Part { + // Pointer to the area of the MT-32's memory dedicated to rhythm + const MemParams::RhythmTemp *rhythmTemp; + + // This caches the timbres/settings in use by the rhythm part + PatchCache drumCache[85][4]; + StereoVolume drumPan[85]; +public: + RhythmPart(Synth *synth, unsigned int usePartNum); + void refresh(); + void refreshTimbre(unsigned int timbreNum); + void setTimbre(TimbreParam *timbre); + void playNote(unsigned int key, int vel); + unsigned int getAbsTimbreNum() const; + void setPan(unsigned int midiPan); + void setBend(unsigned int midiBend); + void setModulation(unsigned int midiModulation); + void setProgram(unsigned int patchNum); +}; + +} +#endif diff --git a/engines/sci/sfx/softseq/mt32/partial.cpp b/engines/sci/sfx/softseq/mt32/partial.cpp new file mode 100644 index 0000000000..9d32282a82 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partial.cpp @@ -0,0 +1,960 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <math.h> +#include <string.h> + +#include "mt32emu.h" + +#ifdef MACOSX +// Older versions of Mac OS X didn't supply a powf function. To ensure +// binary compatibility, we force using pow instead of powf (the only +// potential drawback is that it might be a little bit slower). +#define powf pow +#endif + +#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y))) +#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y))) +#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point) +#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point)) + +using namespace MT32Emu; + +Partial::Partial(Synth *useSynth) { + this->synth = useSynth; + ownerPart = -1; + poly = NULL; + pair = NULL; +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + noteLookupStorage.waveforms[i] = new Bit16s[65536]; + } + noteLookup = ¬eLookupStorage; +#endif +} + +Partial::~Partial() { +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + delete[] noteLookupStorage.waveforms[i]; + } + delete[] noteLookupStorage.wavTable; +#endif +} + +int Partial::getOwnerPart() const { + return ownerPart; +} + +bool Partial::isActive() { + return ownerPart > -1; +} + +const dpoly *Partial::getDpoly() const { + return this->poly; +} + +void Partial::activate(int part) { + // This just marks the partial as being assigned to a part + ownerPart = part; +} + +void Partial::deactivate() { + ownerPart = -1; + if (poly != NULL) { + for (int i = 0; i < 4; i++) { + if (poly->partials[i] == this) { + poly->partials[i] = NULL; + break; + } + } + if (pair != NULL) { + pair->pair = NULL; + } + } +} + +void Partial::initKeyFollow(int key) { + // Setup partial keyfollow + // Note follow relative to middle C + + // Calculate keyfollow for pitch +#if 1 + float rel = key == -1 ? 0.0f : (key - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift; + //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)? + //int newKey = (int)(rel * patchCache->pitchKeyfollow); + //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift; +#else + float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch; +#endif +#if MT32EMU_ACCURATENOTES == 1 + noteVal = newPitch; + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, patchCache->pitch, patchCache->pitchKeyfollow, patchCache->pitchShift, newPitch); +#else + float newPitchInt; + float newPitchFract = modff(newPitch, &newPitchInt); + if (newPitchFract > 0.5f) { + newPitchInt += 1.0f; + newPitchFract -= 1.0f; + } + noteVal = (int)newPitchInt; + fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f); + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, patchCache->pitch, patchCache->pitchKeyfollow, patchCache->pitchShift, newPitch, noteVal, fineShift); +#endif + // FIXME:KG: Raise/lower by octaves until in the supported range. + while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108? + noteVal -= 12; + while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12? + noteVal += 12; + // Calculate keyfollow for filter + int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; + if (keyfollow > 108) + keyfollow = 108; + else if (keyfollow < -108) + keyfollow = -108; + filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108]; + realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108]; +} + +int Partial::getKey() const { + if (poly == NULL) { + return -1; + } else { + return poly->key; + } +} + +void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) { + if (usePoly == NULL || useCache == NULL) { + synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK"); + return; + } + patchCache = useCache; + poly = usePoly; + mixType = patchCache->structureMix; + structurePosition = patchCache->structurePosition; + + play = true; + initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal +#if MT32EMU_ACCURATENOTES == 0 + noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE]; +#else + Tables::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL); +#endif + keyLookup = &synth->tables.keyLookups[poly->freqnum - 12]; + + if (patchCache->PCMPartial) { + pcmNum = patchCache->pcm; + if (synth->controlROMMap->pcmCount > 128) { + // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter. + if (patchCache->waveform > 1) { + pcmNum += 128; + } + } + pcmWave = &synth->pcmWaves[pcmNum]; + } else { + pcmWave = NULL; + } + + lfoPos = 0; + pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[patchCache->pwsens][poly->vel]; + if (pulsewidth > 100) { + pulsewidth = 100; + } else if (pulsewidth < 0) { + pulsewidth = 0; + } + + for (int e = 0; e < 3; e++) { + envs[e].envpos = 0; + envs[e].envstat = -1; + envs[e].envbase = 0; + envs[e].envdist = 0; + envs[e].envsize = 0; + envs[e].sustaining = false; + envs[e].decaying = false; + envs[e].prevlevel = 0; + envs[e].counter = 0; + envs[e].count = 0; + } + ampEnvVal = 0; + pitchEnvVal = 0; + pitchSustain = false; + loopPos = 0; + partialOff.pcmoffset = partialOff.pcmplace = 0; + pair = pairPartial; + useNoisePair = pairPartial == NULL && (mixType == 1 || mixType == 2); + age = 0; + alreadyOutputed = false; + memset(history,0,sizeof(history)); +} + +Bit16s *Partial::generateSamples(long length) { + if (!isActive() || alreadyOutputed) { + return NULL; + } + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::generateSamples()!"); + return NULL; + } + + alreadyOutputed = true; + + // Generate samples + + Bit16s *partialBuf = &myBuffer[0]; + Bit32u volume = *poly->volumeptr; + while (length--) { + Bit32s envval; + Bit32s sample = 0; + if (!envs[EnvelopeType_amp].sustaining) { + if (envs[EnvelopeType_amp].count <= 0) { + Bit32u ampval = getAmpEnvelope(); + if (!play) { + deactivate(); + break; + } + if (ampval > 100) { + ampval = 100; + } + + ampval = synth->tables.volumeMult[ampval]; + ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8); + //if (envs[EnvelopeType_amp].sustaining) + ampEnvVal = ampval; + } + --envs[EnvelopeType_amp].count; + } + + unsigned int lfoShift = 0x1000; + if (pitchSustain) { + // Calculate LFO position + // LFO does not kick in completely until pitch envelope sustains + if (patchCache->lfodepth > 0) { + lfoPos++; + if (lfoPos >= patchCache->lfoperiod) + lfoPos = 0; + int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); + int lfoatr = synth->tables.sintable[lfoatm]; + lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr]; + } + } else { + // Calculate Pitch envelope + envval = getPitchEnvelope(); + int pd = patchCache->pitchEnv.depth; + pitchEnvVal = synth->tables.pitchEnvVal[pd][envval]; + } + + int delta; + + // Wrap positions or end if necessary + if (patchCache->PCMPartial) { + // PCM partial + + delta = noteLookup->wavTable[pcmNum]; + int len = pcmWave->len; + if (partialOff.pcmplace >= len) { + if (pcmWave->loop) { + //partialOff.pcmplace = partialOff.pcmoffset = 0; + partialOff.pcmplace %= len; + } else { + play = false; + deactivate(); + break; + } + } + } else { + // Synthesis partial + delta = 0x10000; + partialOff.pcmplace %= (Bit16u)noteLookup->div2; + } + + // Build delta for position of next sample + // Fix delta code + Bit32u tdelta = delta; +#if MT32EMU_ACCURATENOTES == 0 + tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12); +#endif + tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12); + delta = (int)tdelta; + + // Get waveform - either PCM or synthesized sawtooth or square + if (ampEnvVal > 0) { + if (patchCache->PCMPartial) { + // Render PCM sample + int ra, rb, dist; + Bit32u taddr; + Bit32u pcmAddr = pcmWave->addr; + if (delta < 0x10000) { + // Linear sound interpolation + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr]; + taddr++; + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + rb = synth->pcmROMData[pcmAddr]; + } else { + rb = 0; + } + } else { + rb = synth->pcmROMData[taddr]; + } + dist = rb - ra; + sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); + } else { + // Sound decimation + // The right way to do it is to use a lowpass filter on the waveform before selecting + // a point. This is too slow. The following approximates this as fast as possible + int idelta = delta >> 16; + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr++]; + for (int ix = 0; ix < idelta - 1; ix++) { + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + taddr = pcmAddr; + } else { + // Behave as if all subsequent samples were 0 + break; + } + } + ra += synth->pcmROMData[taddr++]; + } + sample = ra / idelta; + } + } else { + // Render synthesised sample + int toff = partialOff.pcmplace; + int minorplace = partialOff.pcmoffset >> 14; + Bit32s filterInput; + Bit32s filtval = getFiltEnvelope(); + + //synth->printDebug("Filtval: %d", filtval); + + if ((patchCache->waveform & 1) == 0) { + // Square waveform. Made by combining two pregenerated bandlimited + // sawtooth waveforms + Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0]; + int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7); + Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0]; + Bit16s pa = noteLookup->waveforms[0][ofsA]; + Bit16s pb = noteLookup->waveforms[0][ofsB]; + filterInput = pa - pb; + // Non-bandlimited squarewave + /* + ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8); + if (toff < ofs) + sample = 1 * WGAMP; + else + sample = -1 * WGAMP; + */ + } else { + // Sawtooth. Made by combining the full cosine and half cosine according + // to how it looks on the MT-32. What it really does it takes the + // square wave and multiplies it by a full cosine + int waveoff = (toff << 2) + minorplace; + if (toff < noteLookup->sawTable[pulsewidth]) + filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + else + filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; + // This is the correct way + // Seems slow to me (though bandlimited) -- doesn't seem to + // sound any better though + /* + //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; + + Bit32u ofs = toff % (noteLookup->div2 >> 1); + + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9); + ofs3 = ofs3 % (noteLookup->div2 >> 1); + + pa = noteLookup->waveforms[0][ofs]; + pb = noteLookup->waveforms[0][ofs3]; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2; + */ + } + + //Very exact filter + if (filtval > ((FILTERGRAN * 15) / 16)) + filtval = ((FILTERGRAN * 15) / 16); + sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]); + if (sample < -32768) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample); + sample = -32768; + } + else if (sample > 32767) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample); + sample = 32767; + } + } + } + + // Add calculated delta to our waveform offset + Bit32u absOff = ((partialOff.pcmplace << 16) | partialOff.pcmoffset); + absOff += delta; + partialOff.pcmplace = (Bit16u)((absOff & 0xFFFF0000) >> 16); + partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF); + + // Put volume envelope over generated sample + sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9); + sample = FIXEDPOINT_SMULT(sample, volume, 7); + envs[EnvelopeType_amp].envpos++; + envs[EnvelopeType_pitch].envpos++; + envs[EnvelopeType_filt].envpos++; + + *partialBuf++ = (Bit16s)sample; + } + // We may have deactivated and broken out of the loop before the end of the buffer, + // if so then fill the remainder with 0s. + if (++length > 0) + memset(partialBuf, 0, length * 2); + return &myBuffer[0]; +} + +void Partial::setBend(float factor) { + if (!patchCache->useBender || factor == 0.0f) { + bendShift = 4096; + return; + } + // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB. + // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs. + float bendSemitones = factor * patchCache->benderRange; // -24 .. 24 + float mult = powf(2.0f, bendSemitones / 12.0f); + synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", factor, patchCache->benderRange, bendSemitones, mult); + bendShift = (int)(mult * 4096.0f); +} + +Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return buf2; + if (buf2 == NULL) + return buf1; + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffers(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + *buf1 = *buf1 + *buf2; + buf1++, buf2++; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return NULL; + if (buf2 == NULL) { + Bit16s *outBuf = buf1; + while (len--) { + if (*buf1 < -8192) + *buf1 = -8192; + else if (*buf1 > 8192) + *buf1 = 8192; + buf1++; + } + return outBuf; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffersRingMix(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a = (a * b) + a; + if (a>1.0) + a = 1.0; + if (a<-1.0) + a = -1.0; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i]; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) { + return NULL; + } + if (buf2 == NULL) { + return NULL; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_USE_MMX >= 1 + // FIXME:KG: Not really checked as working + int donelen = i386_mixBuffersRing(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a *= b; + if (a>1.0) + a = 1.0; + if (a<-1.0) + a = -1.0; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + } + return outBuf; +} + +void Partial::mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len) { + if (buf2 == NULL) { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = 0; + } + } else if (buf1 == NULL) { + while (len--) { + *outBuf++ = 0; + *outBuf++ = *buf2++; + } + } else { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = *buf2++; + } + } +} + +bool Partial::produceOutput(Bit16s *partialBuf, long length) { + if (!isActive() || alreadyOutputed) + return false; + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::produceOutput()!"); + return false; + } + + Bit16s *pairBuf = NULL; + // Check for dependant partial + if (pair != NULL) { + if (!pair->alreadyOutputed) { + // Note: pair may have become NULL after this + pairBuf = pair->generateSamples(length); + } + } else if (useNoisePair) { + // Generate noise for pairless ring mix + pairBuf = synth->tables.noiseBuf; + } + + Bit16s *myBuf = generateSamples(length); + + if (myBuf == NULL && pairBuf == NULL) + return false; + + Bit16s *p1buf, *p2buf; + + if (structurePosition == 0 || pairBuf == NULL) { + p1buf = myBuf; + p2buf = pairBuf; + } else { + p2buf = myBuf; + p1buf = pairBuf; + } + + //synth->printDebug("mixType: %d", mixType); + + Bit16s *mixedBuf; + switch (mixType) { + case 0: + // Standard sound mix + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + + case 1: + // Ring modulation with sound mix + mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); + break; + + case 2: + // Ring modulation alone + mixedBuf = mixBuffersRing(p1buf, p2buf, length); + break; + + case 3: + // Stereo mixing. One partial to one speaker channel, one to another. + // FIXME:KG: Surely we should be multiplying by the left/right volumes here? + mixBuffersStereo(p1buf, p2buf, partialBuf, length); + return true; + + default: + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + } + + if (mixedBuf == NULL) + return false; + + Bit16s leftvol, rightvol; + leftvol = patchCache->pansetptr->leftvol; + rightvol = patchCache->pansetptr->rightvol; + +#if MT32EMU_USE_MMX >= 2 + // FIXME:KG: This appears to introduce crackle + int donelen = i386_partialProductOutput(length, leftvol, rightvol, partialBuf, mixedBuf); + length -= donelen; + mixedBuf += donelen; + partialBuf += donelen * 2; +#endif + while (length--) { + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)leftvol) >> 15); + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 15); + mixedBuf++; + } + return true; +} + +Bit32s Partial::getFiltEnvelope() { + int reshigh; + + int cutoff, depth; + + EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; + + if (tStat->decaying) { + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + if (tStat->envpos >= tStat->envsize) + reshigh = 0; + } else { + if (tStat->envstat==4) { + reshigh = patchCache->filtsustain; + if (!poly->sustain) { + startDecay(EnvelopeType_filt, reshigh); + } + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat==-1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->filtEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat == 3) { + tStat->envsize = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + } else { + Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat]; + if (tStat->envstat > 1) { + int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 8; + } + + tStat->envsize++; + tStat->envdist = patchCache->filtEnv.envlevel[tStat->envstat] - tStat->envbase; + } + + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + + } + tStat->prevlevel = reshigh; + } + + cutoff = patchCache->filtEnv.cutoff; + + //if (patchCache->waveform==1) reshigh = (reshigh * 3) >> 2; + + depth = patchCache->filtEnv.envdepth; + + //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7; + depth = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8); + + int bias = patchCache->tvfbias; + int dist; + + if (bias != 0) { + //FIXME:KG: Is this really based on pitch (as now), or key pressed? + //synth->printDebug("Cutoff before %d", cutoff); + if (patchCache->tvfdir == 0) { + if (noteVal < bias) { + dist = bias - noteVal; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + dist = noteVal - bias; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + + } + //synth->printDebug("Cutoff after %d", cutoff); + } + + depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8; + reshigh = (reshigh * depth) >> 7; + + Bit32s tmp; + + cutoff *= filtVal; + cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct? + + reshigh *= filtVal; + reshigh /= realVal; //FIXME:KG: As above for cutoff + + if (patchCache->waveform == 1) { + reshigh = (reshigh * 65) / 100; + } + + if (cutoff > 100) + cutoff = 100; + else if (cutoff < 0) + cutoff = 0; + if (reshigh > 100) + reshigh = 100; + else if (reshigh < 0) + reshigh = 0; + tmp = noteLookup->nfiltTable[cutoff][reshigh]; + //tmp *= keyfollow; + //tmp /= realfollow; + + //synth->printDebug("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256); + return tmp; +} + +bool Partial::shouldReverb() { + if (!isActive()) + return false; + return patchCache->reverb; +} + +Bit32u Partial::getAmpEnvelope() { + Bit32s tc; + + EnvelopeStatus *tStat = &envs[EnvelopeType_amp]; + + if (!play) + return 0; + + if (tStat->decaying) { + tc = tStat->envbase; + tc += (tStat->envdist * tStat->envpos) / tStat->envsize; + if (tc < 0) + tc = 0; + if ((tStat->envpos >= tStat->envsize) || (tc == 0)) { + play = false; + // Don't have to worry about prevlevel storage or anything, this partial's about to die + return 0; + } + } else { + if ((tStat->envstat == -1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat == -1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat == 4) { + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampEnv.envlevel[3]; + if (!poly->sustain) + startDecay(EnvelopeType_amp, tc); + else + tStat->sustaining = true; + goto PastCalc; + } + Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envdist = targetLevel - tStat->envbase; + Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat]; + if (targetLevel == 0) { + tStat->envsize = synth->tables.envDecayTime[envTime]; + } else { + int envLevelDelta = abs(tStat->envdist); + if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) { + envTime = synth->tables.envDeltaMaxTime[envLevelDelta]; + } + tStat->envsize = synth->tables.envTime[envTime]; + } + + // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L) + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + + switch (tStat->envstat) { + case 0: + //Spot for velocity time follow + //Only used for first attack + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8); + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 1: + case 2: + case 3: + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + default: + synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat); + break; + } + + tStat->envsize++; + + if (tStat->envdist != 0) { + tStat->counter = abs(tStat->envsize / tStat->envdist); + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } else { + tStat->counter = 0; + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + tStat->count = tStat->counter; +PastCalc: + tc = (tc * (Bit32s)patchCache->ampEnv.level) / 100; + } + + // Prevlevel storage is bottle neck + tStat->prevlevel = tc; + + //Bias level crap stuff now + + for (int i = 0; i < 2; i++) { + if (patchCache->ampblevel[i]!=0) { + int bias = patchCache->ampbias[i]; + if (patchCache->ampdir[i]==0) { + // < Bias + if (noteVal < bias) { + int dist = bias - noteVal; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + int dist = noteVal - bias; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } + } + } + if (tc < 0) { + synth->printDebug("*** ERROR: tc < 0 (%d) at getAmpEnvelope()", tc); + tc = 0; + } + return (Bit32u)tc; +} + +Bit32s Partial::getPitchEnvelope() { + EnvelopeStatus *tStat = &envs[EnvelopeType_pitch]; + + Bit32s tc; + pitchSustain = false; + if (tStat->decaying) { + if (tStat->envpos >= tStat->envsize) + tc = patchCache->pitchEnv.level[4]; + else { + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + } else { + if (tStat->envstat==3) { + tc = patchCache->pitchsustain; + if (poly->sustain) + pitchSustain = true; + else + startDecay(EnvelopeType_pitch, tc); + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + tStat->envstat++; + + tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; + + Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat]; + int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; + + tStat->envpos = 0; + tStat->envsize++; + tStat->envdist = patchCache->pitchEnv.level[tStat->envstat + 1] - tStat->envbase; + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + tStat->prevlevel = tc; + } + return tc; +} + +void Partial::startDecayAll() { + startDecay(EnvelopeType_amp, envs[EnvelopeType_amp].prevlevel); + startDecay(EnvelopeType_filt, envs[EnvelopeType_filt].prevlevel); + startDecay(EnvelopeType_pitch, envs[EnvelopeType_pitch].prevlevel); + pitchSustain = false; +} + +void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { + EnvelopeStatus *tStat = &envs[envnum]; + + tStat->sustaining = false; + tStat->decaying = true; + tStat->envpos = 0; + tStat->envbase = startval; + + switch (envnum) { + case EnvelopeType_amp: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_filt: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_pitch: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8); + tStat->envdist = patchCache->pitchEnv.level[4] - startval; + break; + default: + break; + } + tStat->envsize++; +} diff --git a/engines/sci/sfx/softseq/mt32/partial.h b/engines/sci/sfx/softseq/mt32/partial.h new file mode 100644 index 0000000000..93d8bcd985 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partial.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIAL_H +#define MT32EMU_PARTIAL_H + +namespace MT32Emu { + +class Synth; +struct NoteLookup; + +enum EnvelopeType { + EnvelopeType_amp = 0, + EnvelopeType_filt = 1, + EnvelopeType_pitch = 2 +}; + +struct EnvelopeStatus { + Bit32s envpos; + Bit32s envstat; + Bit32s envbase; + Bit32s envdist; + Bit32s envsize; + + bool sustaining; + bool decaying; + Bit32s prevlevel; + + Bit32s counter; + Bit32s count; +}; + +// Class definition of MT-32 partials. 32 in all. +class Partial { +private: + Synth *synth; + + int ownerPart; // -1 if unassigned + int mixType; + int structurePosition; // 0 or 1 of a structure pair + bool useNoisePair; + + Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; + + // Keyfollowed note value +#if MT32EMU_ACCURATENOTES == 1 + NoteLookup noteLookupStorage; + float noteVal; +#else + int noteVal; + int fineShift; +#endif + const NoteLookup *noteLookup; // LUTs for this noteVal + const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key + + // Keyfollowed filter values + int realVal; + int filtVal; + + // Only used for PCM partials + int pcmNum; + PCMWaveEntry *pcmWave; + + int pulsewidth; + + Bit32u lfoPos; + soundaddr partialOff; + + Bit32u ampEnvVal; + Bit32u pitchEnvVal; + + float history[32]; + + bool pitchSustain; + + int loopPos; + + dpoly *poly; + + int bendShift; + + Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len); + void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len); + + Bit32s getFiltEnvelope(); + Bit32u getAmpEnvelope(); + Bit32s getPitchEnvelope(); + + void initKeyFollow(int freqNum); + +public: + const PatchCache *patchCache; + EnvelopeStatus envs[3]; + bool play; + + PatchCache cachebackup; + + Partial *pair; + bool alreadyOutputed; + Bit32u age; + + Partial(Synth *synth); + ~Partial(); + + int getOwnerPart() const; + int getKey() const; + const dpoly *getDpoly() const; + bool isActive(); + void activate(int part); + void deactivate(void); + void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial); + void startDecay(EnvelopeType envnum, Bit32s startval); + void startDecayAll(); + void setBend(float factor); + bool shouldReverb(); + + // Returns true only if data written to buffer + // This function (unlike the one below it) returns processed stereo samples + // made from combining this single partial with its pair, if it has one. + bool produceOutput(Bit16s * partialBuf, long length); + + // This function produces mono sample output using the partial's private internal buffer + Bit16s *generateSamples(long length); +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/partialManager.cpp b/engines/sci/sfx/softseq/mt32/partialManager.cpp new file mode 100644 index 0000000000..3d3b6302db --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partialManager.cpp @@ -0,0 +1,272 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <string.h> + +#include "mt32emu.h" + +using namespace MT32Emu; + +PartialManager::PartialManager(Synth *useSynth) { + this->synth = useSynth; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i] = new Partial(synth); +} + +PartialManager::~PartialManager(void) { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + delete partialTable[i]; +} + +void PartialManager::getPerPartPartialUsage(int usage[9]) { + memset(usage, 0, 9 * sizeof (int)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive()) + usage[partialTable[i]->getOwnerPart()]++; + } +} + +void PartialManager::clearAlreadyOutputed() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->alreadyOutputed = false; +} + +void PartialManager::ageAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->age++; +} + +bool PartialManager::shouldReverb(int i) { + return partialTable[i]->shouldReverb(); +} + +bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { + return partialTable[i]->produceOutput(buffer, bufferLength); +} + +void PartialManager::deactivateAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + partialTable[i]->deactivate(); + } +} + +unsigned int PartialManager::setReserve(Bit8u *rset) { + unsigned int pr = 0; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < rset[x]; y++) { + partialReserveTable[pr] = x; + pr++; + } + } + return pr; +} + +Partial *PartialManager::allocPartial(int partNum) { + Partial *outPartial = NULL; + + // Use the first inactive partial reserved for the specified part (if there are any) + // Otherwise, use the last inactive partial, if any + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialTable[i]->isActive()) { + outPartial = partialTable[i]; + if (partialReserveTable[i] == partNum) + break; + } + } + if (outPartial != NULL) { + outPartial->activate(partNum); + outPartial->age = 0; + } + return outPartial; +} + +unsigned int PartialManager::getFreePartialCount(void) { + int count = 0; + memset(partialPart, 0, sizeof(partialPart)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialTable[i]->isActive()) + count++; + else + partialPart[partialTable[i]->getOwnerPart()]++; + } + return count; +} + +/* +bool PartialManager::freePartials(unsigned int needed, int partNum) { + int i; + int myPartPrior = (int)mt32ram.system.reserveSettings[partNum]; + if (myPartPrior<partialPart[partNum]) { + //This can have more parts, must kill off those with less priority + int most, mostPart; + while (needed > 0) { + int selectPart = -1; + //Find the worst offender with more partials than allocated and kill them + most = -1; + mostPart = -1; + int diff; + + for (i=0;i<9;i++) { + diff = partialPart[i] - (int)mt32ram.system.reserveSettings[i]; + + if (diff>0) { + if (diff>most) { + most = diff; + mostPart = i; + } + } + } + selectPart = mostPart; + if (selectPart == -1) { + // All parts are within the allocated limits, you suck + // Look for first partial not of this part that's decaying perhaps? + return false; + } + bool found; + int oldest; + int oldnum; + while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == selectPart) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --partialPart[selectPart]; + --needed; + } + + } + return true; + + } else { + //This part has reached its max, must kill off its own + bool found; + int oldest; + int oldnum; + while (needed > 0) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == partNum) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --needed; + } + // Couldn't free enough partials, sorry + if (needed>0) return false; + return true; + } + +} +*/ +bool PartialManager::freePartials(unsigned int needed, int partNum) { + if (needed == 0) { + return true; + } + // Reclaim partials reserved for this part + // Kill those that are already decaying first + /* + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum) { + if (partialTable[i]->ownerPart != partNum) { + if (partialTable[i]->partCache->envs[AMPENV].decaying) { + partialTable[i]->isActive = false; + --needed; + if (needed == 0) + return true; + } + } + } + }*/ + // Then kill those with the lowest part priority -- oldest at the moment + while (needed > 0) { + Bit32u prior = 0; + int priornum = -1; + + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { + /* + if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) { + prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart]; + priornum = i; + }*/ + if (partialTable[i]->age >= prior) { + prior = partialTable[i]->age; + priornum = i; + } + } + } + if (priornum != -1) { + partialTable[priornum]->deactivate(); + --needed; + } else { + break; + } + } + + // Kill off the oldest partials within this part + while (needed > 0) { + Bit32u oldest = 0; + int oldlist = -1; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) { + if (partialTable[i]->age >= oldest) { + oldest = partialTable[i]->age; + oldlist = i; + } + } + } + if (oldlist != -1) { + partialTable[oldlist]->deactivate(); + --needed; + } else { + break; + } + } + return needed == 0; +} + +const Partial *PartialManager::getPartial(unsigned int partialNum) const { + if (partialNum > MT32EMU_MAX_PARTIALS - 1) + return NULL; + return partialTable[partialNum]; +} diff --git a/engines/sci/sfx/softseq/mt32/partialManager.h b/engines/sci/sfx/softseq/mt32/partialManager.h new file mode 100644 index 0000000000..b10f93ff02 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/partialManager.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIALMANAGER_H +#define MT32EMU_PARTIALMANAGER_H + +namespace MT32Emu { + +class Synth; + +class PartialManager { +private: + Synth *synth; // Only used for sending debug output + + Partial *partialTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialPart[9]; // The count of partials played per part + +public: + + PartialManager(Synth *synth); + ~PartialManager(); + Partial *allocPartial(int partNum); + unsigned int getFreePartialCount(void); + bool freePartials(unsigned int needed, int partNum); + unsigned int setReserve(Bit8u *rset); + void deactivateAll(); + void ageAll(); + bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength); + bool shouldReverb(int i); + void clearAlreadyOutputed(); + void getPerPartPartialUsage(int usage[9]); + const Partial *getPartial(unsigned int partialNum) const; +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/structures.h b/engines/sci/sfx/softseq/mt32/structures.h new file mode 100644 index 0000000000..ef58c1d20f --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/structures.h @@ -0,0 +1,284 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_STRUCTURES_H +#define MT32EMU_STRUCTURES_H + +namespace MT32Emu { + +const unsigned int MAX_SAMPLE_OUTPUT = 4096; + +// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output +#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) + +#ifdef _MSC_VER +#define MT32EMU_ALIGN_PACKED __declspec(align(1)) +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; +#else +#define MT32EMU_ALIGN_PACKED __attribute__((packed)) +typedef unsigned long long Bit64u; +typedef signed long long Bit64s; +#endif + +typedef unsigned int Bit32u; +typedef signed int Bit32s; +typedef unsigned short int Bit16u; +typedef signed short int Bit16s; +typedef unsigned char Bit8u; +typedef signed char Bit8s; + +// The following structures represent the MT-32's memory +// Since sysex allows this memory to be written to in blocks of bytes, +// we keep this packed so that we can copy data into the various +// banks directly +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(push, 1) +#else +#pragma pack(1) +#endif + +struct TimbreParam { + struct commonParam { + char name[10]; + Bit8u pstruct12; // 1&2 0-12 (1-13) + Bit8u pstruct34; // #3&4 0-12 (1-13) + Bit8u pmute; // 0-15 (0000-1111) + Bit8u nosustain; // 0-1(Normal, No sustain) + } MT32EMU_ALIGN_PACKED common; + + struct partialParam { + struct wgParam { + Bit8u coarse; // 0-96 (C1,C#1-C9) + Bit8u fine; // 0-100 (-50 to +50 (cents?)) + Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) + Bit8u bender; // 0,1 (ON/OFF) + Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2) + Bit8u pcmwave; // 0-127 (1-128) + Bit8u pulsewid; // 0-100 + Bit8u pwvelo; // 0-14 (-7 - +7) + } MT32EMU_ALIGN_PACKED wg; + + struct envParam { + Bit8u depth; // 0-10 + Bit8u sensitivity; // 1-100 + Bit8u timekeyfollow; // 0-4 + Bit8u time[4]; // 1-100 + Bit8u level[5]; // 1-100 (-50 - +50) + } MT32EMU_ALIGN_PACKED env; + + struct lfoParam { + Bit8u rate; // 0-100 + Bit8u depth; // 0-100 + Bit8u modsense; // 0-100 + } MT32EMU_ALIGN_PACKED lfo; + + struct tvfParam { + Bit8u cutoff; // 0-100 + Bit8u resonance; // 0-30 + Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) + Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel; // 0-14 (-7 - +7) + Bit8u envdepth; // 0-100 + Bit8u envsense; // 0-100 + Bit8u envdkf; // DEPTH KEY FOLL0W 0-4 + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tvf; + + struct tvaParam { + Bit8u level; // 0-100 + Bit8u velosens; // 0-100 + Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel1; // 0-12 (-12 - 0) + Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel2; // 0-12 (-12 - 0) + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envvkf; // VELOS KEY FOLL0W 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tva; + } MT32EMU_ALIGN_PACKED partial[4]; +} MT32EMU_ALIGN_PACKED; + +struct PatchParam { + Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) + Bit8u timbreNum; // TIMBRE NUMBER 0-63 + Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) + Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents) + Bit8u benderRange; // BENDER RANGE 0-24 + Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + Bit8u dummy; // (DUMMY) +} MT32EMU_ALIGN_PACKED; + +struct MemParams { + // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8. + // The LAPC-I documentation specified an additional area for rhythm at the end, + // where all parameters but fine tune, assign mode and output level are ignored + struct PatchTemp { + PatchParam patch; + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u dummyv[6]; + } MT32EMU_ALIGN_PACKED; + + PatchTemp patchSettings[9]; + + struct RhythmTemp { + Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + } MT32EMU_ALIGN_PACKED; + + RhythmTemp rhythmSettings[85]; + + TimbreParam timbreSettings[8]; + + PatchParam patches[128]; + + // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above + struct PaddedTimbre { + TimbreParam timbre; + Bit8u padding[10]; + } MT32EMU_ALIGN_PACKED; + + PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm + + struct SystemArea { + Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz + Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) + Bit8u reverbTime; // REVERB TIME 0-7 (1-8) + Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8) + Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 + Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) + Bit8u masterVol; // MASTER VOLUME 0-100 + } MT32EMU_ALIGN_PACKED; + + SystemArea system; +}; + +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(pop) +#else +#pragma pack() +#endif + +struct PCMWaveEntry { + Bit32u addr; + Bit32u len; + double tune; + bool loop; +}; + +struct soundaddr { + Bit16u pcmplace; + Bit16u pcmoffset; +}; + +struct StereoVolume { + Bit16s leftvol; + Bit16s rightvol; +}; + +// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings +struct PatchCache { + bool playPartial; + bool PCMPartial; + int pcm; + char waveform; + int pulsewidth; + int pwsens; + + float pitch; + + int lfodepth; + int lforate; + Bit32u lfoperiod; + int modsense; + + float pitchKeyfollow; + + int filtkeyfollow; + + int tvfbias; + int tvfblevel; + int tvfdir; + + int ampbias[2]; + int ampblevel[2]; + int ampdir[2]; + + int ampdepth; + int amplevel; + + bool useBender; + float benderRange; // 0.0, 1.0, .., 24.0 (semitones) + + TimbreParam::partialParam::envParam pitchEnv; + TimbreParam::partialParam::tvaParam ampEnv; + TimbreParam::partialParam::tvfParam filtEnv; + + Bit32s pitchsustain; + Bit32s filtsustain; + + Bit32u structureMix; + int structurePosition; + int structurePair; + + // The following fields are actually common to all partials in the timbre + bool dirty; + Bit32u partialCount; + bool sustain; + float pitchShift; + bool reverb; + const StereoVolume *pansetptr; +}; + +class Partial; // Forward reference for class defined in partial.h + +struct dpoly { + bool isPlaying; + + unsigned int key; + int freqnum; + int vel; + + bool isDecay; + + const Bit32u *volumeptr; + + Partial *partials[4]; + + bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal + bool sustain; + + bool isActive() const; + Bit32u getAge() const; +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/synth.cpp b/engines/sci/sfx/softseq/mt32/synth.cpp new file mode 100644 index 0000000000..5ae196dc63 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/synth.cpp @@ -0,0 +1,1199 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <math.h> +#include <string.h> +#include <stdlib.h> + +#include "mt32emu.h" + +#ifdef MACOSX +// Older versions of Mac OS X didn't supply a powf function. To ensure +// binary compatibility, we force using pow instead of powf (the only +// potential drawback is that it might be a little bit slower). +#define powf pow +#endif + +namespace MT32Emu { + +const int MAX_SYSEX_SIZE = 512; + +const ControlROMMap ControlROMMaps[5] = { + // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog + {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57D0, 0x57E2}, // MT-32 revision 0 + {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57E2, 0x57F4}, // MT-32 revision 0 + {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1 + {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod + {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L + // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) +}; + +float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) { + float *hist2_ptr; + float output,new_hist; + + hist2_ptr = hist1_ptr + 1; // next history + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + output = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + hist1_ptr++; + hist2_ptr++; + + // i = 1 + output = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + + return(output); +} + +Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { + for (unsigned int i = 0; i < len; i++) { + checksum = checksum + data[i]; + } + checksum = checksum & 0x7f; + if (checksum) + checksum = 0x80 - checksum; + return checksum; +} + +Synth::Synth() { + isOpen = false; + reverbModel = NULL; + partialManager = NULL; + memset(parts, 0, sizeof(parts)); +} + +Synth::~Synth() { + close(); // Make sure we're closed and everything is freed +} + +int Synth::report(ReportType type, const void *data) { + if (myProp.report != NULL) { + return myProp.report(myProp.userData, type, data); + } + return 0; +} + +void Synth::printDebug(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (myProp.printDebug != NULL) { + myProp.printDebug(myProp.userData, fmt, ap); + } else { + vprintf(fmt, ap); + printf("\n"); + } + va_end(ap); +} + +void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { + // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters + if (reverbModel != NULL) + delete reverbModel; + reverbModel = new revmodel(); + + switch (newRevMode) { + case 0: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.75f); + break; + case 1: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.5f); + break; + case 2: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.1f); + break; + case 3: + reverbModel->setroomsize(1.0f); + reverbModel->setdamp(.75f); + break; + default: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.5f); + break; + } + reverbModel->setdry(1); + reverbModel->setwet((float)newRevLevel / 8.0f); + reverbModel->setwidth((float)newRevTime / 8.0f); +} + +File *Synth::openFile(const char *filename, File::OpenMode mode) { + if (myProp.openFile != NULL) { + return myProp.openFile(myProp.userData, filename, mode); + } + char pathBuf[2048]; + if (myProp.baseDir != NULL) { + strcpy(&pathBuf[0], myProp.baseDir); + strcat(&pathBuf[0], filename); + filename = pathBuf; + } + ANSIFile *file = new ANSIFile(); + if (!file->open(filename, mode)) { + delete file; + return NULL; + } + return file; +} + +void Synth::closeFile(File *file) { + if (myProp.closeFile != NULL) { + myProp.closeFile(myProp.userData, file); + } else { + file->close(); + delete file; + } +} + +bool Synth::loadPreset(File *file) { + bool inSys = false; + Bit8u sysexBuf[MAX_SYSEX_SIZE]; + Bit16u syslen = 0; + bool rc = true; + for (;;) { + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + sysexBuf[syslen] = c; + if (inSys) { + syslen++; + if (c == 0xF7) { + playSysex(&sysexBuf[0], syslen); + inSys = false; + syslen = 0; + } else if (syslen == MAX_SYSEX_SIZE) { + printDebug("MAX_SYSEX_SIZE (%d) exceeded while processing preset, ignoring message", MAX_SYSEX_SIZE); + inSys = false; + syslen = 0; + } + } else if (c == 0xF0) { + syslen++; + inSys = true; + } + } + return rc; +} + +bool Synth::loadControlROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE); + + closeFile(file); + if (!rc) + return rc; + + // Control ROM successfully loaded, now check whether it's a known type + controlROMMap = NULL; + for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) { + if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { + controlROMMap = &ControlROMMaps[i]; + return true; + } + } + return false; +} + +bool Synth::loadPCMROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = true; + int i; + for (i = 0; i < pcmROMSize; i++) { + Bit8u s; + if (!file->readBit8u(&s)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } else { + printDebug("PCM ROM file has an odd number of bytes! Ignoring last"); + } + break; + } + + short e; + int bit; + int u; + int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; + + e = 0; + for (u = 0; u < 15; u++) { + if (order[u] < 8) + bit = (s >> (7 - order[u])) & 0x1; + else + bit = (c >> (7 - (order[u] - 8))) & 0x1; + e = e | (short)(bit << (15 - u)); + } + + /* + //Bit16s e = ( ((s & 0x7f) << 4) | ((c & 0x40) << 6) | ((s & 0x80) << 6) | ((c & 0x3f))) << 2; + if (e<0) + e = -32767 - e; + int ut = abs(e); + int dif = 0x7fff - ut; + x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000)); + e = (int)((float)e * (x/3200)); + */ + + // File is companded (dB?), convert to linear PCM + // MINDB = -96 + // MAXDB = -15 + float testval; + testval = (float)((~e) & 0x7fff); + testval = -(testval / 400.00f); + //testval = -(testval / 341.32291666666666666666666666667); + float vol = powf(8, testval / 20) * 32767.0f; + + if (e > 0) + vol = -vol; + + pcmROMData[i] = (Bit16s)vol; + } + if (i != pcmROMSize) { + printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i); + rc = false; + } + closeFile(file); + return rc; +} + +bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; + for (int i = 0; i < count; i++) { + int rAddr = tps[i].pos * 0x800; + int rLenExp = (tps[i].len & 0x70) >> 4; + int rLen = 0x800 << rLenExp; + bool rLoop = (tps[i].len & 0x80) != 0; + //Bit8u rFlag = tps[i].len & 0x0F; + Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB; + // The number below is confirmed to a reasonable degree of accuracy on CM-32L + double STANDARDFREQ = 442.0; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.0)); + //printDebug("%f,%d,%d", pTune, tps[i].pitchCoarse, tps[i].pitchFine); + if (rAddr + rLen > pcmROMSize) { + printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); + return false; + } + pcmWaves[i].addr = rAddr; + pcmWaves[i].len = rLen; + pcmWaves[i].loop = rLoop; + pcmWaves[i].tune = rTune; + } + return false; +} + +bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) { + if (memLen < sizeof(TimbreParam::commonParam)) { + return false; + } + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + memcpy(&timbre->common, mem, 14); + unsigned int memPos = 14; + char drumname[11]; + strncpy(drumname, timbre->common.name, 10); + drumname[10] = 0; + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 0x1) { + if (memPos + 58 >= memLen) { + return false; + } + memcpy(&timbre->partial[t], mem + memPos, 58); + memPos += 58; + } + } + return true; +} + +bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) { + const Bit8u *drumMap = &controlROMData[mapAddress]; + int timbreNum = 192; + for (Bit16u i = 0; i < count * 2; i += 2) { + Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; + /* + // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is) + if (address >= CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + */ + if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address); + return false; + } + } + return true; +} + +bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) { + for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + address = address + offset; + TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; + memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); + } + return true; +} + +bool Synth::open(SynthProperties &useProp) { + if (isOpen) + return false; + + myProp = useProp; + if (useProp.baseDir != NULL) { + myProp.baseDir = new char[strlen(useProp.baseDir) + 1]; + strcpy(myProp.baseDir, useProp.baseDir); + } + + // This is to help detect bugs + memset(&mt32ram, '?', sizeof(mt32ram)); + + printDebug("Loading Control ROM"); + if (!loadControlROM("CM32L_CONTROL.ROM")) { + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, NULL); + return false; + } + } + + // 512KB PCM ROM for MT-32, etc. + // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 + // Note that the size below is given in samples (16-bit), not bytes + pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; + pcmROMData = new Bit16s[pcmROMSize]; + + printDebug("Loading PCM ROM"); + if (!loadPCMROM("CM32L_PCM.ROM")) { + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, NULL); + return false; + } + } + + printDebug("Initialising Timbre Bank A"); + if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) { + return false; + } + + printDebug("Initialising Timbre Bank B"); + if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) { + return false; + } + + printDebug("Initialising Timbre Bank R"); + if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) { + return false; + } + + printDebug("Initialising Timbre Bank M"); + // CM-64 seems to initialise all bytes in this bank to 0. + memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64); + + partialManager = new PartialManager(this); + + pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount]; + + printDebug("Initialising PCM List"); + initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount); + + printDebug("Initialising Rhythm Temp"); + memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4); + + printDebug("Initialising Patches"); + for (Bit8u i = 0; i < 128; i++) { + PatchParam *patch = &mt32ram.patches[i]; + patch->timbreGroup = i / 64; + patch->timbreNum = i % 64; + patch->keyShift = 24; + patch->fineTune = 50; + patch->benderRange = 12; + patch->assignMode = 0; + patch->reverbSwitch = 1; + patch->dummy = 0; + } + + printDebug("Initialising System"); + // The MT-32 manual claims that "Standard pitch" is 442Hz. + mt32ram.system.masterTune = 0x4A; // Confirmed on CM-64 + mt32ram.system.reverbMode = 0; // Confirmed + mt32ram.system.reverbTime = 5; // Confirmed + mt32ram.system.reverbLevel = 3; // Confirmed + memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed + for (Bit8u i = 0; i < 9; i++) { + // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9} + // An alternative configuration can be selected by holding "Master Volume" + // and pressing "PART button 1" on the real MT-32's frontpanel. + // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9} + mt32ram.system.chanAssign[i] = i + 1; + } + mt32ram.system.masterVol = 100; // Confirmed + if (!refreshSystem()) + return false; + + for (int i = 0; i < 8; i++) { + mt32ram.patchSettings[i].outlevel = 80; + mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i]; + memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); + parts[i] = new Part(this, i); + parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]); + } + parts[8] = new RhythmPart(this, 8); + + // For resetting mt32 mid-execution + mt32default = mt32ram; + + iirFilter = &iir_filter_normal; + +#ifdef MT32EMU_HAVE_X86 + bool availableSSE = DetectSIMD(); + bool available3DNow = Detect3DNow(); + + if (availableSSE) + report(ReportType_availableSSE, NULL); + if (available3DNow) + report(ReportType_available3DNow, NULL); + + if (available3DNow) { + printDebug("Detected and using SIMD (AMD 3DNow) extensions"); + iirFilter = &iir_filter_3dnow; + report(ReportType_using3DNow, NULL); + } else if (availableSSE) { + printDebug("Detected and using SIMD (Intel SSE) extensions"); + iirFilter = &iir_filter_sse; + report(ReportType_usingSSE, NULL); + } +#endif + + isOpen = true; + isEnabled = false; + + printDebug("*** Initialisation complete ***"); + return true; +} + +void Synth::close(void) { + if (!isOpen) + return; + + tables.freeNotes(); + if (partialManager != NULL) { + delete partialManager; + partialManager = NULL; + } + + if (reverbModel != NULL) { + delete reverbModel; + reverbModel = NULL; + } + + for (int i = 0; i < 9; i++) { + if (parts[i] != NULL) { + delete parts[i]; + parts[i] = NULL; + } + } + if (myProp.baseDir != NULL) { + delete myProp.baseDir; + myProp.baseDir = NULL; + } + + delete[] pcmWaves; + delete[] pcmROMData; + isOpen = false; +} + +void Synth::playMsg(Bit32u msg) { + // FIXME: Implement active sensing + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); + unsigned char chan = (unsigned char) (msg & 0x00000F); + unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + isEnabled = true; + + //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); + + char part = chantable[chan]; + if (part < 0 || part > 8) { + printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity); + return; + } + playMsgOnPart(part, code, note, velocity); +} + +void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { + Bit32u bend; + + //printDebug("Synth::playMsg(0x%02x)",msg); + switch (code) { + case 0x8: + //printDebug("Note OFF - Part %d", part); + // The MT-32 ignores velocity for note off + parts[part]->stopNote(note); + break; + case 0x9: + //printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity); + if (velocity == 0) { + // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40 + parts[part]->stopNote(note); + } else { + parts[part]->playNote(note, velocity); + } + break; + case 0xB: // Control change + switch (note) { + case 0x01: // Modulation + //printDebug("Modulation: %d", velocity); + parts[part]->setModulation(velocity); + break; + case 0x07: // Set volume + //printDebug("Volume set: %d", velocity); + parts[part]->setVolume(velocity); + break; + case 0x0A: // Pan + //printDebug("Pan set: %d", velocity); + parts[part]->setPan(velocity); + break; + case 0x0B: + //printDebug("Expression set: %d", velocity); + parts[part]->setExpression(velocity); + break; + case 0x40: // Hold (sustain) pedal + //printDebug("Hold pedal set: %d", velocity); + parts[part]->setHoldPedal(velocity>=64); + break; + + case 0x79: // Reset all controllers + //printDebug("Reset all controllers"); + //FIXME: Check for accuracy against real thing + parts[part]->setVolume(100); + parts[part]->setExpression(127); + parts[part]->setPan(64); + parts[part]->setBend(0x2000); + parts[part]->setHoldPedal(false); + break; + + case 0x7B: // All notes off + //printDebug("All notes off"); + parts[part]->allNotesOff(); + break; + + default: + printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); + break; + } + + break; + case 0xC: // Program change + //printDebug("Program change %01x", note); + parts[part]->setProgram(note); + break; + case 0xE: // Pitch bender + bend = (velocity << 7) | (note); + //printDebug("Pitch bender %02x", bend); + parts[part]->setBend(bend); + break; + default: + printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); + break; + } + + //midiOutShortMsg(m_out, msg); +} + +void Synth::playSysex(const Bit8u *sysex, Bit32u len) { + if (len < 2) { + printDebug("playSysex: Message is too short for sysex (%d bytes)", len); + } + if (sysex[0] != 0xF0) { + printDebug("playSysex: Message lacks start-of-sysex (0xF0)"); + return; + } + // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len. + Bit32u endPos; + for (endPos = 1; endPos < len; endPos++) + { + if (sysex[endPos] == 0xF7) + break; + } + if (endPos == len) { + printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); + return; + } + playSysexWithoutFraming(sysex + 1, endPos - 1); +} + +void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) { + if (len < 4) { + printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); + return; + } + if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) { + printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + if (sysex[2] == SYSEX_MDL_D50) { + printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + else if (sysex[2] != SYSEX_MDL_MT32) { + printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4); +} + +void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len) { + if (device > 0x10) { + // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels + printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device); + return; + } + if (len < 4) { + printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); + return; + } + unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); + if (checksum != sysex[len - 1]) { + printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); + return; + } + len -= 1; // Exclude checksum + switch (command) { + case SYSEX_CMD_DT1: + writeSysex(device, sysex, len); + break; + case SYSEX_CMD_RQ1: + readSysex(device, sysex, len); + break; + default: + printDebug("playSysexWithoutFraming: Unsupported command %02x", command); + return; + } +} + +void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) { +} + +const MemoryRegion memoryRegions[8] = { + {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9}, + {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85}, + {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8}, + {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128}, + {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64}, + {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1}, + {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1}, + {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1} +}; + +const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion); + +void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); + addr = MT32EMU_MEMADDR(addr); + sysex += 3; + len -= 3; + //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr)); + // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + + // Process channel-specific sysex by converting it to device-global + if (device < 0x10) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(MemParams::PatchTemp); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MT32EMU_MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) { + addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < MT32EMU_MEMADDR(0x030000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(TimbreParam); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; + } else { + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + return; + } + } + + // Process device-global sysex (possibly converted from channel-specific sysex above) + for (;;) { + // Find the appropriate memory region + int regionNum; + const MemoryRegion *region = NULL; // Initialised to please compiler + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex); + break; + } + } + if (regionNum == NUM_REGIONS) { + printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len); + break; + } + Bit32u next = region->next(addr, len); + if (next == 0) { + break; + } + addr += next; + sysex += next; + len -= next; + } +} + +void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) { + int regionNum; + const MemoryRegion *region = NULL; + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + readMemoryRegion(region, addr, len, data); + break; + } + } +} + +void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) { + unsigned int first = region->firstTouched(addr); + //unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + len = region->getClampedLen(addr, len); + + unsigned int m; + + switch(region->type) { + case MR_PatchTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m]; + break; + case MR_RhythmTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m]; + break; + case MR_TimbreTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m]; + break; + case MR_Patches: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m]; + break; + case MR_Timbres: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m]; + break; + case MR_System: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.system)[m + off]; + break; + default: + for (m = 0; m < len; m += 2) { + data[m] = 0xff; + if (m + 1 < len) { + data[m+1] = (Bit8u)region->type; + } + } + // TODO: Don't care about the others ATM + break; + } + +} + +void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) { + unsigned int first = region->firstTouched(addr); + unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + switch (region->type) { + case MR_PatchTemp: + for (unsigned int m = 0; m < len; m++) { + ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m]; + } + //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); + + for (unsigned int i = first; i <= last; i++) { + int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; + char timbreName[11]; + memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10); + timbreName[10] = 0; + printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel); + if (parts[i] != NULL) { + if (i != 8) { + // Note: Confirmed on CM-64 that we definitely *should* update the timbre here, + // but only in the case that the sysex actually writes to those values + if (i == first && off > 2) { + printDebug(" (Not updating timbre, since those values weren't touched)"); + } else { + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); + } + } + parts[i]->refresh(); + } + } + break; + case MR_RhythmTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + int timbreNum = mt32ram.rhythmSettings[i].timbre; + char timbreName[11]; + if (timbreNum < 94) { + memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10); + timbreName[10] = 0; + } else { + strcpy(timbreName, "[None]"); + } + printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); + } + if (parts[8] != NULL) { + parts[8]->refresh(); + } + break; + case MR_TimbreTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName); + if (parts[i] != NULL) { + parts[i]->refresh(); + } + } + break; + case MR_Patches: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + PatchParam *patch = &mt32ram.patches[i]; + int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); + instrumentName[10] = 0; + Bit8u *n = (Bit8u *)patch; + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); + // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using + // based on a timbre match (but many patches could have the same timbre!) + // If this refresh is really correct, we should store the patch number in use by each part. + /* + for (int part = 0; part < 8; part++) { + if (parts[part] != NULL) { + int partPatchAbsTimbreNum = mt32ram.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum; + if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) { + parts[part]->setPatch(patch); + parts[part]->RefreshPatch(); + } + } + } + */ + } + break; + case MR_Timbres: + // Timbres + first += 128; + last += 128; + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, off, off + len, i, instrumentName); + // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)... + // Does the real MT-32 automatically do this? + for (unsigned int part = 0; part < 9; part++) { + if (parts[part] != NULL) { + parts[part]->refreshTimbre(i); + } + } + } + break; + case MR_System: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.system)[m + off] = data[m]; + + report(ReportType_devReconfig, NULL); + + printDebug("WRITE-SYSTEM:"); + refreshSystem(); + break; + case MR_Display: + char buf[MAX_SYSEX_SIZE]; + memcpy(&buf, &data[0], len); + buf[len] = 0; + printDebug("WRITE-LCD: %s", buf); + report(ReportType_lcdMessage, buf); + break; + case MR_Reset: + printDebug("RESET"); + report(ReportType_devReset, NULL); + partialManager->deactivateAll(); + mt32ram = mt32default; + for (int i = 0; i < 9; i++) { + parts[i]->refresh(); + } + isEnabled = false; + break; + } +} + +bool Synth::refreshSystem() { + memset(chantable, -1, sizeof(chantable)); + + for (unsigned int i = 0; i < 9; i++) { + //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]); + if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { + parts[i]->allSoundOff(); + } else { + chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; + } + } + //FIXME:KG: This is just an educated guess. + // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here) + // The MT-32 documentation claims a range of 432.1Hz-457.6Hz + masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f)); + printDebug(" Master Tune: %f", masterTune); + printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + report(ReportType_newReverbMode, &mt32ram.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + + if (myProp.useDefaultReverb) { + initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + } else { + initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel); + } + + Bit8u *rset = mt32ram.system.reserveSettings; + printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + int pr = partialManager->setReserve(rset); + if (pr != 32) + printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); + rset = mt32ram.system.chanAssign; + printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + printDebug(" Master volume: %d", mt32ram.system.masterVol); + masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100); + if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) { + report(ReportType_errorSampleRate, NULL); + return false; + } + return true; +} + +bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) { + // Sysex header + if (!file->writeBit8u(0xF0)) + return false; + if (!file->writeBit8u(0x41)) + return false; + if (!file->writeBit8u(0x10)) + return false; + if (!file->writeBit8u(0x16)) + return false; + if (!file->writeBit8u(0x12)) + return false; + + char lsb = (char)(address & 0x7f); + char isb = (char)((address >> 7) & 0x7f); + char msb = (char)(((address >> 14) & 0x7f) | 0x08); + + //Address + if (!file->writeBit8u(msb)) + return false; + if (!file->writeBit8u(isb)) + return false; + if (!file->writeBit8u(lsb)) + return false; + + //Data + if (file->write(timbre, 246) != 246) + return false; + + //Checksum + unsigned char checksum = calcSysexChecksum((const Bit8u *)timbre, 246, msb + isb + lsb); + if (!file->writeBit8u(checksum)) + return false; + + //End of sysex + if (!file->writeBit8u(0xF7)) + return false; + return true; +} + +int Synth::dumpTimbres(const char *filename, int start, int len) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) + return -1; + + for (int timbreNum = start; timbreNum < start + len; timbreNum++) { + int useaddr = (timbreNum - start) * 256; + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + if (!dumpTimbre(file, timbre, useaddr)) + break; + } + closeFile(file); + return 0; +} + +void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { +#if MT32EMU_USE_MMX > 2 + //FIXME:KG: This appears to introduce crackle + int donelen = i386_produceOutput1(useBuf, stream, len, volume); + len -= donelen; + stream += donelen * 2; + useBuf += donelen * 2; +#endif + int end = len * 2; + while (end--) { + *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15); + stream++; + } +} + +void Synth::render(Bit16s *stream, Bit32u len) { + memset(stream, 0, len * sizeof (Bit16s) * 2); + if (!isEnabled) + return; + while (len > 0) { + Bit32u thisLen = len > MAX_SAMPLE_OUTPUT ? MAX_SAMPLE_OUTPUT : len; + doRender(stream, thisLen); + len -= thisLen; + stream += 2 * thisLen; + } +} + +void Synth::doRender(Bit16s *stream, Bit32u len) { + partialManager->ageAll(); + + if (myProp.useReverb) { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + Bit32u m = 0; + for (unsigned int i = 0; i < len; i++) { + sndbufl[i] = (float)stream[m] / 32767.0f; + m++; + sndbufr[i] = (float)stream[m] / 32767.0f; + m++; + } + reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); + m=0; + for (unsigned int i = 0; i < len; i++) { + stream[m] = (Bit16s)(outbufl[i] * 32767.0f); + m++; + stream[m] = (Bit16s)(outbufr[i] * 32767.0f); + m++; + } + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + } else { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + + partialManager->clearAlreadyOutputed(); + +#if MT32EMU_MONITOR_PARTIALS == 1 + samplepos += len; + if (samplepos > myProp.SampleRate * 5) { + samplepos = 0; + int partialUsage[9]; + partialManager->GetPerPartPartialUsage(partialUsage); + printDebug("1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7]); + printDebug("Rhythm: %02d TOTAL: %02d", partialUsage[8], MT32EMU_MAX_PARTIALS - partialManager->GetFreePartialCount()); + } +#endif +} + +const Partial *Synth::getPartial(unsigned int partialNum) const { + return partialManager->getPartial(partialNum); +} + +const Part *Synth::getPart(unsigned int partNum) const { + if (partNum > 8) + return NULL; + return parts[partNum]; +} + +} diff --git a/engines/sci/sfx/softseq/mt32/synth.h b/engines/sci/sfx/softseq/mt32/synth.h new file mode 100644 index 0000000000..9d57c8d3cd --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/synth.h @@ -0,0 +1,300 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_SYNTH_H +#define MT32EMU_SYNTH_H + +#include <stdarg.h> + +class revmodel; + +namespace MT32Emu { + +class File; +class TableInitialiser; +class Partial; +class PartialManager; +class Part; + +enum ReportType { + // Errors + ReportType_errorControlROM = 1, + ReportType_errorPCMROM, + ReportType_errorSampleRate, + + // Progress + ReportType_progressInit, + + // HW spec + ReportType_availableSSE, + ReportType_available3DNow, + ReportType_usingSSE, + ReportType_using3DNow, + + // General info + ReportType_lcdMessage, + ReportType_devReset, + ReportType_devReconfig, + ReportType_newReverbMode, + ReportType_newReverbTime, + ReportType_newReverbLevel +}; + +struct SynthProperties { + // Sample rate to use in mixing + int sampleRate; + + // Flag to activate reverb. True = use reverb, False = no reverb + bool useReverb; + // True to use software set reverb settings, False to set reverb settings in + // following parameters + bool useDefaultReverb; + // When not using the default settings, this specifies one of the 4 reverb types + // 1 = Room 2 = Hall 3 = Plate 4 = Tap + unsigned char reverbType; + // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbTime; + // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbLevel; + // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) + // Not used if "openFile" is set. May be NULL in any case. + char *baseDir; + // This is used as the first argument to all callbacks + void *userData; + // Callback for reporting various errors and information. May be NULL + int (*report)(void *userData, ReportType type, const void *reportData); + // Callback for debug messages, in vprintf() format + void (*printDebug)(void *userData, const char *fmt, va_list list); + // Callback for providing an implementation of File, opened and ready for use + // May be NULL, in which case a default implementation will be used. + File *(*openFile)(void *userData, const char *filename, File::OpenMode mode); + // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. + void (*closeFile)(void *userData, File *file); +}; + +// This is the specification of the Callback routine used when calling the RecalcWaveforms +// function +typedef void (*recalcStatusCallback)(int percDone); + +// This external function recreates the base waveform file (waveforms.raw) using a specifed +// sampling rate. The callback routine provides interactivity to let the user know what +// percentage is complete in regenerating the waveforms. When a NULL pointer is used as the +// callback routine, no status is reported. +bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); + +typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr); + +const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; + +const Bit8u SYSEX_MDL_MT32 = 0x16; +const Bit8u SYSEX_MDL_D50 = 0x14; + +const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 +const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 +const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data +const Bit8u SYSEX_CMD_RQD = 0x41; // Request data +const Bit8u SYSEX_CMD_DAT = 0x42; // Data set +const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge +const Bit8u SYSEX_CMD_EOD = 0x45; // End of data +const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error +const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection + +const unsigned int CONTROL_ROM_SIZE = 64 * 1024; + +struct ControlROMPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; + Bit16u pcmCount; + Bit16u timbreAMap; + Bit16u timbreAOffset; + Bit16u timbreBMap; + Bit16u timbreBOffset; + Bit16u timbreRMap; + Bit16u timbreRCount; + Bit16u rhythmSettings; + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; + Bit16u panSettings; + Bit16u programSettings; +}; + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } +}; + + +class Synth { +friend class Part; +friend class RhythmPart; +friend class Partial; +friend class Tables; +private: + bool isEnabled; + + iir_filter_type iirFilter; + + PCMWaveEntry *pcmWaves; // Array + + const ControlROMMap *controlROMMap; + Bit8u controlROMData[CONTROL_ROM_SIZE]; + Bit16s *pcmROMData; + int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM + + Bit8s chantable[32]; + + #if MT32EMU_MONITOR_PARTIALS == 1 + static Bit32s samplepos = 0; + #endif + + Tables tables; + + MemParams mt32ram, mt32default; + + revmodel *reverbModel; + + float masterTune; + Bit16u masterVolume; + + bool isOpen; + + PartialManager *partialManager; + Part *parts[9]; + + Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2]; + float sndbufl[MAX_SAMPLE_OUTPUT]; + float sndbufr[MAX_SAMPLE_OUTPUT]; + float outbufl[MAX_SAMPLE_OUTPUT]; + float outbufr[MAX_SAMPLE_OUTPUT]; + + SynthProperties myProp; + + bool loadPreset(File *file); + void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel); + void doRender(Bit16s * stream, Bit32u len); + + void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); + void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); + + bool loadControlROM(const char *filename); + bool loadPCMROM(const char *filename); + bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr); + int dumpTimbres(const char *filename, int start, int len); + + bool initPCMList(Bit16u mapAddress, Bit16u count); + bool initRhythmTimbres(Bit16u mapAddress, Bit16u count); + bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre); + bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); + bool refreshSystem(); + +protected: + int report(ReportType type, const void *reportData); + File *openFile(const char *filename, File::OpenMode mode); + void closeFile(File *file); + void printDebug(const char *fmt, ...); + +public: + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + + Synth(); + ~Synth(); + + // Used to initialise the MT-32. Must be called before any other function. + // Returns true if initialization was sucessful, otherwise returns false. + bool open(SynthProperties &useProp); + + // Closes the MT-32 and deallocates any memory used by the synthesizer + void close(void); + + // Sends a 4-byte MIDI message to the MT-32 for immediate playback + void playMsg(Bit32u msg); + void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); + + // Sends a string of Sysex commands to the MT-32 for immediate interpretation + // The length is in bytes + void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); + void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); + void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + + // This callback routine is used to have the MT-32 generate samples to the specified + // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo, + // one sample is 4 bytes) + void render(Bit16s * stream, Bit32u len); + + const Partial *getPartial(unsigned int partialNum) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/mt32/tables.cpp b/engines/sci/sfx/softseq/mt32/tables.cpp new file mode 100644 index 0000000000..4591ea22e3 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/tables.cpp @@ -0,0 +1,749 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +#ifdef MACOSX +// Older versions of Mac OS X didn't supply a powf function. To ensure +// binary compatibility, we force using pow instead of powf (the only +// potential drawback is that it might be a little bit slower). +#define powf pow +#endif + +#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) + +namespace MT32Emu { + +//Amplitude time velocity follow exponential coefficients +static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; + +// These are division constants for the TVF depth key follow +static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; + +//Envelope time keyfollow exponential coefficients +static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; +static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; + +// Begin filter stuff + +// Pre-warp the coefficients of a numerator or denominator. +// Note that a0 is assumed to be 1, so there is no wrapping +// of it. +static void prewarp(double *a1, double *a2, double fc, double fs) { + double wp; + + wp = 2.0 * fs * tan(DOUBLE_PI * fc / fs); + + *a2 = *a2 / (wp * wp); + *a1 = *a1 / wp; +} + +// Transform the numerator and denominator coefficients +// of s-domain biquad section into corresponding +// z-domain coefficients. +// +// Store the 4 IIR coefficients in array pointed by coef +// in following order: +// beta1, beta2 (denominator) +// alpha1, alpha2 (numerator) +// +// Arguments: +// a0-a2 - s-domain numerator coefficients +// b0-b2 - s-domain denominator coefficients +// k - filter gain factor. initially set to 1 +// and modified by each biquad section in such +// a way, as to make it the coefficient by +// which to multiply the overall filter gain +// in order to achieve a desired overall filter gain, +// specified in initial value of k. +// fs - sampling rate (Hz) +// coef - array of z-domain coefficients to be filled in. +// +// Return: +// On return, set coef z-domain coefficients +static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) { + double ad, bd; + + // alpha (Numerator in s-domain) + ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0; + // beta (Denominator in s-domain) + bd = 4. * b2 * fs * fs + 2. * b1* fs + b0; + + // update gain constant for this section + *k *= ad/bd; + + // Denominator + *coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1 + *coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2 + + // Nominator + *coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1 + *coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2 +} + +// a0-a2: numerator coefficients +// b0-b2: denominator coefficients +// fc: Filter cutoff frequency +// fs: sampling rate +// k: overall gain factor +// coef: pointer to 4 iir coefficients +static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) { + // Calculate a1 and a2 and overwrite the original values + prewarp(a1, a2, fc, fs); + prewarp(b1, b2, fc, fs); + bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef); +} + +static void initFilter(float fs, float fc, float *icoeff, float Q) { + float *coef; + double a0, a1, a2, b0, b1, b2; + + double k = 1.5; // Set overall filter gain factor + coef = icoeff + 1; // Skip k, or gain + + // Section 1 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 0.765367 / Q; // Divide by resonance or Q + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + coef += 4; // Point to next filter section + + // Section 2 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 1.847759 / Q; + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + + icoeff[0] = (float)k; +} + +void Tables::initFiltCoeff(float samplerate) { + for (int j = 0; j < FILTERGRAN; j++) { + for (int res = 0; res < 31; res++) { + float tres = resonanceFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres); + } + } +} + +void Tables::initEnvelopes(float samplerate) { + for (int lf = 0; lf <= 100; lf++) { + float elf = (float)lf; + + // General envelope + // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter, + // when all other times were 0 and all levels were 100. Note that variations occur depending on the level + // delta of the section, which we're not fully emulating. + float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f; + int samples = (int)(seconds * samplerate); + envTime[lf] = samples; + + // Cap on envelope times depending on the level delta + if (elf == 0) { + envDeltaMaxTime[lf] = 63; + } else { + float cap = 11.0f * (float)log(elf) + 64; + if (cap > 100.0f) { + cap = 100.0f; + } + envDeltaMaxTime[lf] = (int)cap; + } + + + // This (approximately) represents the time durations when the target level is 0. + // Not sure why this is a special case, but it's seen to be from the real thing. + seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f; + envDecayTime[lf] = (int)(seconds * samplerate); + + // I am certain of this: Verified by hand LFO log + lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f)); + } +} + +void Tables::initMT32ConstantTables(Synth *synth) { + int lf; + synth->printDebug("Initialising Pitch Tables"); + for (lf = -108; lf <= 108; lf++) { + tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + //synth->printDebug("KT %d = %d", f, keytable[f+108]); + } + + for (int res = 0; res < 31; res++) { + resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f; + } + + int period = 65536; + + for (int ang = 0; ang < period; ang++) { + int halfang = (period / 2); + int angval = ang % halfang; + float tval = (((float)angval / (float)halfang) - 0.5f) * 2; + if (ang >= halfang) + tval = -tval; + sintable[ang] = (Bit16s)(tval * 50.0f) + 50; + } + + int velt, dep; + float tempdep; + for (velt = 0; velt < 128; velt++) { + for (dep = 0; dep < 5; dep++) { + if (dep > 0) { + float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]); + tempdep = 256.0f * ff; + envTimeVelfollowMult[dep][velt] = (int)tempdep; + //if ((velt % 16) == 0) { + // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); + //} + } else + envTimeVelfollowMult[dep][velt] = 256; + } + + for (dep = -7; dep < 8; dep++) { + float fldep = (float)abs(dep) / 7.0f; + fldep = powf(fldep,2.5f); + if (dep < 0) + fldep = fldep * -1.0f; + pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); + } + } + + for (dep = 0; dep <= 100; dep++) { + for (velt = 0; velt < 128; velt++) { + float fdep = (float)dep * 0.000347013f; // Another MT-32 constant + float fv = ((float)velt - 64.0f)/7.26f; + float flogdep = powf(10, fdep * fv); + float fbase; + + if (velt > 64) + synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0); + else { + //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); + fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f)); + synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0); + } + //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); + } + } + + for (lf = 0; lf < 128; lf++) { + float veloFract = lf / 127.0f; + for (int velsens = 0; velsens <= 100; velsens++) { + float sensFract = (velsens - 50) / 50.0f; + if (velsens < 50) { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8); + } else { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8); + } + } + } + + for (lf = 0; lf <= 100; lf++) { + // Converts the 0-100 range used by the MT-32 to volume multiplier + volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7); + } + + for (lf = 0; lf <= 100; lf++) { + float mv = lf / 100.0f; + float pt = mv - 0.5f; + if (pt < 0) + pt = 0; + + // Original (CC version) + //pwFactor[lf] = (int)(pt * 210.04f) + 128; + + // Approximation from sample comparison + pwFactor[lf] = (int)(pt * 179.0f) + 128; + } + + for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { + int myRand; + myRand = rand(); + //myRand = ((myRand - 16383) * 7168) >> 16; + // This one is slower but works with all values of RAND_MAX + myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2)); + //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason + noiseBuf[i] = (Bit16s)myRand; + } + + float tdist; + float padjtable[51]; + for (lf = 0; lf <= 50; lf++) { + if (lf == 0) + padjtable[lf] = 7; + else if (lf == 1) + padjtable[lf] = 6; + else if (lf == 2) + padjtable[lf] = 5; + else if (lf == 3) + padjtable[lf] = 4; + else if (lf == 4) + padjtable[lf] = 4 - (0.333333f); + else if (lf == 5) + padjtable[lf] = 4 - (0.333333f * 2); + else if (lf == 6) + padjtable[lf] = 3; + else if ((lf > 6) && (lf <= 12)) { + tdist = (lf-6.0f) / 6.0f; + padjtable[lf] = 3.0f - tdist; + } else if ((lf > 12) && (lf <= 25)) { + tdist = (lf - 12.0f) / 13.0f; + padjtable[lf] = 2.0f - tdist; + } else { + tdist = (lf - 25.0f) / 25.0f; + padjtable[lf] = 1.0f - tdist; + } + //synth->printDebug("lf %d = padj %f", lf, padjtable[lf]); + } + + float lfp, depf, finalval, tlf; + int depat, pval, depti; + for (lf = 0; lf <= 10; lf++) { + // I believe the depth is cubed or something + + for (depat = 0; depat <= 100; depat++) { + if (lf > 0) { + depti = abs(depat - 50); + tlf = (float)lf - padjtable[depti]; + if (tlf < 0) + tlf = 0; + lfp = (float)exp(0.713619942f * tlf) / 407.4945111f; + + if (depat < 50) + finalval = 4096.0f * powf(2, -lfp); + else + finalval = 4096.0f * powf(2, lfp); + pval = (int)finalval; + + pitchEnvVal[lf][depat] = pval; + //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, tlf, lfp); + } else { + pitchEnvVal[lf][depat] = 4096; + //synth->printDebug("lf %d depat %d pval 4096", lf, depat); + } + } + } + for (lf = 0; lf <= 100; lf++) { + // It's linear - verified on MT-32 - one of the few things linear + lfp = ((float)lf * 0.1904f) / 310.55f; + + for (depat = 0; depat <= 100; depat++) { + depf = ((float)depat - 50.0f) / 50.0f; + //finalval = pow(2, lfp * depf * .5); + finalval = 4096.0f + (4096.0f * lfp * depf); + + pval = (int)finalval; + + lfoShift[lf][depat] = pval; + + //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval); + } + } + + for (lf = 0; lf <= 12; lf++) { + for (int distval = 0; distval < 128; distval++) { + float amplog, dval; + if (lf == 0) { + amplog = 0; + dval = 1; + tvaBiasMult[lf][distval] = 256; + } else { + /* + amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; + dval = ((128.0f - (float)distval) / 128.0f); + amplog = exp(amplog); + dval = powf(amplog, dval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0); + */ + // Lets assume for a second it's linear + + // Distance of full volume reduction + amplog = (float)(12.0f / (float)lf) * 24.0f; + if (distval > amplog) { + tvaBiasMult[lf][distval] = 0; + } else { + dval = (amplog - (float)distval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, tvaBiasMult[lf][distval],amplog); + } + } + + for (lf = 0; lf <= 14; lf++) { + for (int distval = 0; distval < 128; distval++) { + float filval = fabsf((float)((lf - 7) * 12) / 7.0f); + float amplog, dval; + if (lf == 7) { + amplog = 0; + dval = 1; + tvfBiasMult[lf][distval] = 256; + } else { + //amplog = pow(1.431817011, filval) / FLOAT_PI; + amplog = powf(1.531817011f, filval) / FLOAT_PI; + dval = (128.0f - (float)distval) / 128.0f; + amplog = (float)exp(amplog); + dval = powf(amplog,dval)/amplog; + if (lf < 8) { + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } else { + dval = powf(dval, 0.3333333f); + if (dval < 0.01f) + dval = 0.01f; + dval = 1 / dval; + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, tvfBiasMult[lf][distval],amplog); + } + } +} + +// Per-note table initialisation follows + +static void initSaw(NoteLookup *noteLookup, Bit32s div2) { + int tmpdiv = div2 << 16; + for (int rsaw = 0; rsaw <= 100; rsaw++) { + float fsaw; + if (rsaw < 50) + fsaw = 50.0f; + else + fsaw = (float)rsaw; + + //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 + float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; + noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; + //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]); + } +} + +static void initDep(KeyLookup *keyLookup, float f) { + for (int dep = 0; dep < 5; dep++) { + if (dep == 0) { + keyLookup->envDepthMult[dep] = 256; + keyLookup->envTimeMult[dep] = 256; + } else { + float depfac = 3000.0f; + float ff, tempdep; + depfac = (float)depexp[dep]; + + ff = (f - (float)MIDDLEC) / depfac; + tempdep = powf(2, ff) * 256.0f; + keyLookup->envDepthMult[dep] = (int)tempdep; + + ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); + keyLookup->envTimeMult[dep] = (int)(ff * 256.0f); + } + } + //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]); +} + +Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) { + Bit32s x = (Bit32s)(input * ampVal); + if (x < -ampVal - 1) { + synth->printDebug("%s==%d<-WGAMP-1!", n, x); + x = (Bit32s)(-ampVal - 1); + } else if (x > ampVal) { + synth->printDebug("%s==%d>WGAMP!", n, x); + x = (Bit32s)ampVal; + } + return (Bit16s)x; +} + +File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) { + int iDiv2 = (int)div2; + noteLookup->waveformSize[0] = iDiv2 << 1; + noteLookup->waveformSize[1] = iDiv2 << 1; + noteLookup->waveformSize[2] = iDiv2 << 2; + for (int i = 0; i < 3; i++) { + if (noteLookup->waveforms[i] == NULL) { + noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]]; + } + } + if (file != NULL) { + for (int i = 0; i < 3 && file != NULL; i++) { + size_t len = noteLookup->waveformSize[i]; + for (unsigned int j = 0; j < len; j++) { + if (!file->readBit16u((Bit16u *)¬eLookup->waveforms[i][j])) { + synth->printDebug("Error reading wave file cache!"); + file->close(); + file = NULL; + break; + } + } + } + } + if (file == NULL) { + double sd = DOUBLE_PI / div2; + + for (int fa = 0; fa < (iDiv2 << 1); fa++) { + // sa ranges from 0 to 2PI + double sa = fa * sd; + + // Calculate a sample for the bandlimited sawtooth wave + double saw = 0.0; + int sincs = iDiv2 >> 1; + double sinus = 1.0; + for (int sincNum = 1; sincNum <= sincs; sincNum++) { + saw += sin(sinus * sa) / sinus; + sinus++; + } + + // This works pretty well + // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude + noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84); + noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0)); + noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI)); + noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI)); + } + } + return file; +} + +static void initFiltTable(NoteLookup *noteLookup, float freq, float rate) { + for (int tr = 0; tr <= 200; tr++) { + float ftr = (float)tr; + + // Verified exact on MT-32 + if (tr > 100) + ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f); + + // I think this is the one + float brsq = powf(10.0f, (tr / 50.0f) - 1.0f); + noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16); + + float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f; + noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16); + } +} + +static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { + for (int cf = 0; cf <= 100; cf++) { + float cfmult = (float)cf; + + for (int tf = 0;tf <= 100; tf++) { + float tfadd = (float)tf; + + //float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f; + //float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f)); + + float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f); + + noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN); + if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16)) + noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16); + } + } +} + +File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) { + float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); + float div2 = rate * 2.0f / freq; + noteLookup->div2 = (int)div2; + + if (noteLookup->div2 == 0) + noteLookup->div2 = 1; + + initSaw(noteLookup, noteLookup->div2); + + //synth->printDebug("Note %f; freq=%f, div=%f", note, freq, rate / freq); + file = initWave(synth, noteLookup, (const float)WGAMP, div2, file); + + // Create the pitch tables + if (noteLookup->wavTable == NULL) + noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount]; + double rateMult = 32000.0 / rate; + double tuner = freq * 65536.0f; + for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) { + noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); + } + + initFiltTable(noteLookup, freq, rate); + initNFiltTable(noteLookup, freq, rate); + return file; +} + +bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) { + const char *NoteNames[12] = { + "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " + }; + char filename[64]; + int intRate = (int)rate; + char version[4] = {0, 0, 0, 5}; + sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, masterTune); + + File *file = NULL; + char header[20]; + strncpy(header, "MT32WAVE", 8); + int pos = 8; + // Version... + for (int i = 0; i < 4; i++) + header[pos++] = version[i]; + header[pos++] = (char)((intRate >> 24) & 0xFF); + header[pos++] = (char)((intRate >> 16) & 0xFF); + header[pos++] = (char)((intRate >> 8) & 0xFF); + header[pos++] = (char)(intRate & 0xFF); + int intTuning = (int)masterTune; + header[pos++] = (char)((intTuning >> 8) & 0xFF); + header[pos++] = (char)(intTuning & 0xFF); + header[pos++] = 0; + header[pos] = (char)((masterTune - intTuning) * 10); +#if MT32EMU_WAVECACHEMODE < 2 + bool reading = false; + file = synth->openFile(filename, File::OpenMode_read); + if (file != NULL) { + char fileHeader[20]; + if (file->read(fileHeader, 20) == 20) { + if (memcmp(fileHeader, header, 20) == 0) { + Bit16u endianCheck; + if (file->readBit16u(&endianCheck)) { + if (endianCheck == 1) { + reading = true; + } else { + synth->printDebug("Endian check in %s does not match expected", filename); + } + } else { + synth->printDebug("Unable to read endian check in %s", filename); + } + } else { + synth->printDebug("Header of %s does not match expected", filename); + } + } else { + synth->printDebug("Error reading 16 bytes of %s", filename); + } + if (!reading) { + file->close(); + file = NULL; + } + } else { + synth->printDebug("Unable to open %s for reading", filename); + } +#endif + + float progress = 0.0f; + bool abort = false; + synth->report(ReportType_progressInit, &progress); + for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) { + synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2); + NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; + file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); + progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; + abort = synth->report(ReportType_progressInit, &progress) != 0; + if (abort) + break; + } + +#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2 + if (file == NULL) { + file = synth->openFile(filename, File::OpenMode_write); + if (file != NULL) { + if (file->write(header, 20) == 20 && file->writeBit16u(1)) { + for (int f = 0; f < NUM_NOTES; f++) { + for (int i = 0; i < 3 && file != NULL; i++) { + int len = noteLookups[f].waveformSize[i]; + for (int j = 0; j < len; j++) { + if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) { + synth->printDebug("Error writing waveform cache file"); + file->close(); + file = NULL; + break; + } + } + } + } + } else { + synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename); + } + } else { + synth->printDebug("Unable to open %s for writing - won't be created", filename); + } + } +#endif + + if (file != NULL) + synth->closeFile(file); + return !abort; +} + +void Tables::freeNotes() { + for (int t = 0; t < 3; t++) { + for (int m = 0; m < NUM_NOTES; m++) { + if (noteLookups[m].waveforms[t] != NULL) { + delete[] noteLookups[m].waveforms[t]; + noteLookups[m].waveforms[t] = NULL; + noteLookups[m].waveformSize[t] = 0; + } + if (noteLookups[m].wavTable != NULL) { + delete[] noteLookups[m].wavTable; + noteLookups[m].wavTable = NULL; + } + } + } + initialisedMasterTune = 0.0f; +} + +Tables::Tables() { + initialisedSampleRate = 0.0f; + initialisedMasterTune = 0.0f; + memset(¬eLookups, 0, sizeof(noteLookups)); +} + +bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) { + if (sampleRate <= 0.0f) { + synth->printDebug("Bad sampleRate (%d <= 0.0f)", sampleRate); + return false; + } + if (initialisedSampleRate == 0.0f) { + initMT32ConstantTables(synth); + } + if (initialisedSampleRate != sampleRate) { + initFiltCoeff(sampleRate); + initEnvelopes(sampleRate); + for (int key = 12; key <= 108; key++) { + initDep(&keyLookups[key - 12], (float)key); + } + } + if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { + freeNotes(); + if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { + return false; + } + initialisedSampleRate = sampleRate; + initialisedMasterTune = masterTune; + } + return true; +} + +} diff --git a/engines/sci/sfx/softseq/mt32/tables.h b/engines/sci/sfx/softseq/mt32/tables.h new file mode 100644 index 0000000000..d9af5114b2 --- /dev/null +++ b/engines/sci/sfx/softseq/mt32/tables.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2003-2005 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_TABLES_H +#define MT32EMU_TABLES_H + +namespace MT32Emu { + +// Mathematical constants +const double DOUBLE_PI = 3.1415926535897932384626433832795; +const double DOUBLE_LN = 2.3025850929940456840179914546844; +const float FLOAT_PI = 3.1415926535897932384626433832795f; +const float FLOAT_LN = 2.3025850929940456840179914546844f; + +// Filter settings +const int FILTERGRAN = 512; + +// Amplitude of waveform generator +// FIXME: This value is the amplitude possible whilst avoiding +// overdriven values immediately after filtering when playing +// back SQ3MT.MID. Needs to be checked. +const int WGAMP = 12382; + +const int MIDDLEC = 60; +const int MIDDLEA = 69; // By this I mean "A above middle C" + +// FIXME:KG: may only need to do 12 to 108 +// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow +// and adjustment for timbre pitch, so the results can be outside that range. +// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range, +// or something else altogether? +const int LOWEST_NOTE = 12; +const int HIGHEST_NOTE = 127; +const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT + +class Synth; + +struct NoteLookup { + Bit32u div2; + Bit32u *wavTable; + Bit32s sawTable[101]; + int filtTable[2][201]; + int nfiltTable[101][101]; + Bit16s *waveforms[3]; + Bit32u waveformSize[3]; +}; + +struct KeyLookup { + Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed + Bit32s envDepthMult[5]; +}; + +class Tables { + float initialisedSampleRate; + float initialisedMasterTune; + void initMT32ConstantTables(Synth *synth); + static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input); + static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file); + bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning); + void initEnvelopes(float sampleRate); + void initFiltCoeff(float samplerate); +public: + // Constant LUTs + Bit32s tvfKeyfollowMult[217]; + Bit32s tvfVelfollowMult[128][101]; + Bit32s tvfBiasMult[15][128]; + Bit32u tvaVelfollowMult[128][101]; + Bit32s tvaBiasMult[13][128]; + Bit16s noiseBuf[MAX_SAMPLE_OUTPUT]; + Bit16s sintable[65536]; + Bit32s pitchEnvVal[16][101]; + Bit32s envTimeVelfollowMult[5][128]; + Bit32s pwVelfollowAdd[15][128]; + float resonanceFactor[31]; + Bit32u lfoShift[101][101]; + Bit32s pwFactor[101]; + Bit32s volumeMult[101]; + + // LUTs varying with sample rate + Bit32u envTime[101]; + Bit32u envDeltaMaxTime[101]; + Bit32u envDecayTime[101]; + Bit32u lfoPeriod[101]; + float filtCoeff[FILTERGRAN][31][8]; + + // Various LUTs for each note and key + NoteLookup noteLookups[NUM_NOTES]; + KeyLookup keyLookups[97]; + + Tables(); + bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune); + File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file); + void freeNotes(); +}; + +} + +#endif diff --git a/engines/sci/sfx/softseq/opl2.c b/engines/sci/sfx/softseq/opl2.c new file mode 100644 index 0000000000..ed2f3001c8 --- /dev/null +++ b/engines/sci/sfx/softseq/opl2.c @@ -0,0 +1,718 @@ +/*************************************************************************** + opl2.c Copyright (C) 2002/04 Solomon Peachy, Christoph Reichenbach + + This program may be modified and copied freely according to the terms of + the GNU general public license (GPL), as long as the above copyright + notice and the licensing information contained herein are preserved. + + Please refer to www.gnu.org for licensing details. + + This work is provided AS IS, without warranty of any kind, expressed or + implied, including but not limited to the warranties of merchantibility, + noninfringement, and fitness for a specific purpose. The author will not + be held liable for any damage caused by this work or derivatives of it. + + By using this source code, you agree to the licensing terms as stated + above. + +***************************************************************************/ + +#include <resource.h> +#include <sfx_iterator.h> +#include "../softseq.h" +#include "../adlib.h" +#include <math.h> + +#include "fmopl.h" + +#ifdef _DREAMCAST +#define SAMPLE_RATE 22050 +#define CHANNELS SFX_PCM_MONO +#define STEREO 0 +#else +#define SAMPLE_RATE 44100 +#define CHANNELS SFX_PCM_STEREO_LR +#define STEREO 1 +#endif + + +/* local function declarations */ + +static void opl2_allstop(sfx_softseq_t *self); + +//#define DEBUG_ADLIB + +/* portions shamelessly lifted from claudio's XMP */ +/* other portions lifted from sound/opl3.c in the Linux kernel */ + +#define ADLIB_LEFT 0 +#define ADLIB_RIGHT 1 + +/* #define OPL_INTERNAL_FREQ 3600000 */ +#define OPL_INTERNAL_FREQ 3579545 + +static int ready = 0; +static int pcmout_stereo = STEREO; + +static int register_base[11] = { + 0x20, 0x23, 0x40, 0x43, + 0x60, 0x63, 0x80, 0x83, + 0xe0, 0xe3, 0xc0 +}; + +static int register_offset[12] = { + /* Channel 1 2 3 4 5 6 7 8 9 */ + /* Operator 1 */ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12, 0x18, 0x19, 0x1A + +}; + +static int ym3812_note[13] = { + 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, + 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287, + 0x2ae +}; + +static guint8 sci_adlib_vol_base[16] = { + 0x00, 0x11, 0x15, 0x19, 0x1D, 0x22, 0x26, 0x2A, + 0x2E, 0x23, 0x37, 0x3B, 0x3F, 0x3F, 0x3F, 0x3F +}; +static guint8 sci_adlib_vol_tables[16][64]; + +/* ripped out of the linux kernel, of all places. */ +static gint8 fm_volume_table[128] = { + -64, -48, -40, -35, -32, -29, -27, -26, + -24, -23, -21, -20, -19, -18, -18, -17, + -16, -15, -15, -14, -13, -13, -12, -12, + -11, -11, -10, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -6, -6, -6, + -5, -5, -5, -5, -4, -4, -4, -4, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -1, -1, -1, -1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8 +}; + +/* back to your regularly scheduled definitions */ + +static guint8 instr[MIDI_CHANNELS]; +static guint16 pitch[MIDI_CHANNELS]; +static guint8 vol[MIDI_CHANNELS]; +static guint8 pan[MIDI_CHANNELS]; +static int free_voices = ADLIB_VOICES; +static guint8 oper_note[ADLIB_VOICES]; +static guint8 oper_chn[ADLIB_VOICES]; + +static FM_OPL *ym3812_L = NULL; +static FM_OPL *ym3812_R = NULL; + +static guint8 adlib_reg_L[256]; +static guint8 adlib_reg_R[256]; +static guint8 adlib_master; + + +/* initialise note/operator lists, etc. */ +void adlibemu_init_lists(void) +{ + int i; + + int j; + + for (i = 0 ; i < 16 ; i++) { + for (j = 0; j < 64 ; j++) { + sci_adlib_vol_tables[i][j] = ((guint16)sci_adlib_vol_base[i]) * j / 63; + } + } + + for (i = 0; i < MIDI_CHANNELS ; i++) { + pitch[i] = 8192; /* center the pitch wheel */ + } + + free_voices = ADLIB_VOICES; + + memset(instr, 0, sizeof(instr)); + memset(vol, 0x7f, sizeof(vol)); + memset(pan, 0x3f, sizeof(pan)); + memset(adlib_reg_L, 0, sizeof(adlib_reg_L)); + memset(adlib_reg_R, 0, sizeof(adlib_reg_R)); + memset(oper_chn, 0xff, sizeof(oper_chn)); + memset(oper_note, 0xff, sizeof(oper_note)); + adlib_master=12; +} + +/* more shamelessly lifted from xmp and adplug. And altered. :) */ + +static inline int opl_write_L (int a, int v) +{ + adlib_reg_L[a] = v; + OPLWrite (ym3812_L, 0x388, a); + return OPLWrite (ym3812_L, 0x389, v); +} + +static inline int opl_write_R (int a, int v) +{ + adlib_reg_R[a] = v; + OPLWrite (ym3812_R, 0x388, a); + return OPLWrite (ym3812_R, 0x389, v); +} + +static inline int opl_write(int a, int v) +{ + opl_write_L(a,v); + return opl_write_R(a,v); +} + +/* +static inline guint8 opl_read (int a) +{ + OPLWrite (ym3812_L, 0x388, a); + return OPLRead (ym3812_L, 0x389); +} +*/ + +void synth_setpatch (int voice, guint8 *data) +{ + int i; + + opl_write(0xBD, 0); + + for (i = 0; i < 10; i++) + opl_write(register_base[i] + register_offset[voice], data[i]); + + opl_write(register_base[10] + voice, data[10]); + + /* mute voice after patch change */ + opl_write_L(0xb0 + voice, adlib_reg_L[0xb0+voice] & 0xdf); + opl_write_R(0xb0 + voice, adlib_reg_R[0xb0+voice] & 0xdf); + +#ifdef DEBUG_ADLIB + for (i = 0; i < 10; i++) + printf("%02x ", adlib_reg_L[register_base[i]+register_offset[voice]]); + printf("%02x ", adlib_reg_L[register_base[10]+voice]); +#endif + +} + +void synth_setvolume_L (int voice, int volume) +{ + gint8 level1, level2; + + level1 = ~adlib_reg_L[register_base[2]+register_offset[voice]] & 0x3f; + level2 = ~adlib_reg_L[register_base[3]+register_offset[voice]] & 0x3f; + + if (level1) { + level1 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level2) { + level2 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level1 > 0x3f) + level1 = 0x3f; + if (level1 < 0) + level1 = 0; + + if (level2 > 0x3f) + level2 = 0x3f; + if (level2 < 0) + level2 = 0; + + /* algorithm-dependent; we may need to set both operators. */ + if (adlib_reg_L[register_base[10]+voice] & 1) + opl_write_L(register_base[2]+register_offset[voice], + (guint8)((~level1 &0x3f) | + (adlib_reg_L[register_base[2]+register_offset[voice]]&0xc0))); + + opl_write_L(register_base[3]+register_offset[voice], + (guint8)((~level2 &0x3f) | + (adlib_reg_L[register_base[3]+register_offset[voice]]&0xc0))); + +} + +void synth_setvolume_R (int voice, int volume) +{ + gint8 level1, level2; + + level1 = ~adlib_reg_R[register_base[2]+register_offset[voice]] & 0x3f; + level2 = ~adlib_reg_R[register_base[3]+register_offset[voice]] & 0x3f; + + if (level1) { + level1 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level2) { + level2 += sci_adlib_vol_tables[adlib_master][volume>>1]; + } + + if (level1 > 0x3f) + level1 = 0x3f; + if (level1 < 0) + level1 = 0; + + if (level2 > 0x3f) + level2 = 0x3f; + if (level2 < 0) + level2 = 0; + + /* now for the other side. */ + if (adlib_reg_R[register_base[10]+voice] & 1) + opl_write_R(register_base[2]+register_offset[voice], + (guint8)((~level1 &0x3f) | + (adlib_reg_R[register_base[2]+register_offset[voice]]&0xc0))); + + opl_write_R(register_base[3]+register_offset[voice], + (guint8)((~level2 &0x3f) | + (adlib_reg_R[register_base[3]+register_offset[voice]]&0xc0))); + +} + +void synth_setnote (int voice, int note, int bend) +{ + int n, fre, oct; + float delta; + + delta = 0; + + n = note % 12; + + if (bend < 8192) + bend = 8192-bend; + delta = pow(2, (float) (bend%8192)/8192.0); + + if (bend > 8192) + fre = ym3812_note[n]*delta; else + fre = ym3812_note[n]/delta; + + oct = note / 12 - 1; + + if (oct < 0) + oct = 0; + + opl_write(0xa0 + voice, fre & 0xff); + opl_write(0xb0 + voice, + 0x20 | ((oct << 2) & 0x1c) | ((fre >> 8) & 0x03)); +#ifdef DEBUG_ADLIB + printf("-- %02x %02x\n", adlib_reg_L[0xa0+voice], adlib_reg_L[0xb0+voice]); +#endif + +} + + +/* back to your regularly scheduled driver */ + +int adlibemu_stop_note(int chn, int note, int velocity) +{ + int i, op=255; + + // sciprintf("Note off %d %d %d\n", chn, note, velocity); + + for (i=0;i<ADLIB_VOICES && op==255;i++) { + if (oper_chn[i] == chn) + if (oper_note[i] == note) + op=i; + } + + if (op==255) { +#ifdef DEBUG_ADLIB + printf ("ADLIB: can't stop note: C%02x N%02x V%02x\n", chn, note, velocity); + printf ("C "); + for (i = 0; i < ADLIB_VOICES ; i++ ) { + printf ("%02x ", oper_chn[i]); + } + printf ("\n"); + printf ("N "); + for (i = 0; i < ADLIB_VOICES ; i++ ) { + printf ("%02x ", oper_note[i]); + } + printf ("\n"); +#endif + return -1; /* that note isn't playing.. */ + } + + opl_write_L(0xb0+op,(adlib_reg_L[0xb0+op] & 0xdf)); + opl_write_R(0xb0+op,(adlib_reg_R[0xb0+op] & 0xdf)); + + oper_chn[op] = 255; + oper_note[op] = 255; + + free_voices++; + +#ifdef DEBUG_ADLIB + printf("stop voice %d (%d rem): C%02x N%02x V%02x\n", op, free_voices, chn, note, velocity); +#endif + + return 0; +} + +int adlibemu_start_note(int chn, int note, int velocity) +{ + int op, volume_L, volume_R, inst = 0; + + // sciprintf("Note on %d %d %d\n", chn, note, velocity); + + if (velocity == 0) { + return adlibemu_stop_note(chn, note, velocity); + } + + if (free_voices <= 0) { + printf("ADLIB: All voices full\n"); /* XXX implement overflow code */ + return -1; + } + + for (op = 0; op < ADLIB_VOICES ; op++) + if (oper_chn[op] == 255) + break; + + if (op == ADLIB_VOICES) { + printf("ADLIB: WTF? We couldn't find a voice yet it we have %d left.\n", free_voices); + return -1; + } + + /* Scale channel volume */ + volume_L = velocity * vol[chn] / 127; + volume_R = velocity * vol[chn] / 127; + + /* Apply a pan */ + if (pcmout_stereo) { + if (pan[chn] > 0x3f) /* pan right; so we scale the left down. */ + volume_L = volume_L / 0x3f * (0x3f - (pan[chn] - 0x3f)); + else if (pan[chn] < 0x3f) /* pan left; so we scale the right down.*/ + volume_R = volume_R / 0x3f * (0x3f - (0x3f-pan[chn])); + } + inst = instr[chn]; + + synth_setpatch(op, adlib_sbi[inst]); + synth_setvolume_L(op, volume_L); + synth_setvolume_R(op, volume_R); + synth_setnote(op, note, pitch[chn]); + + oper_chn[op] = chn; + oper_note[op] = note; + free_voices--; + +#ifdef DEBUG_ADLIB + printf("play voice %d (%d rem): C%02x N%02x V%02x/%02x-%02x P%02x (%02x/%02x)\n", op, free_voices, chn, note, velocity, volume_L, volume_R, inst, + adlib_reg_L[register_base[2]+register_offset[op]] & 0x3f, + adlib_reg_L[register_base[3]+register_offset[op]] & 0x3f); +#endif + + return 0; +} + +static +void adlibemu_update_pitch(int chn, int note, int newpitch) +{ + int i; + int matched = 0; + + pitch[chn] = newpitch; + + for (i=0;i<ADLIB_VOICES;i++) { + if (oper_chn[i] == chn) + { + matched++; + synth_setnote(i, oper_note[i], newpitch); + } + } + +// printf("Matched %d notes on channel %d.\n", matched, chn); +} + +void test_adlib (void) { + + int voice = 0; +#if 0 + guint8 data[] = { 0x25, 0x21, 0x48, 0x48, 0xf0, 0xf2, 0xf0, 0xa5, 0x00, 0x00, 0x06 }; +#else + guint8 *data = adlib_sbi[0x0a]; +#endif + +#if 1 + opl_write(register_base[0]+register_offset[voice], data[0]); + opl_write(register_base[1]+register_offset[voice], data[1]); + opl_write(register_base[2]+register_offset[voice], data[2]); + opl_write(register_base[3]+register_offset[voice], data[3]); + opl_write(register_base[4]+register_offset[voice], data[4]); + opl_write(register_base[5]+register_offset[voice], data[5]); + opl_write(register_base[6]+register_offset[voice], data[6]); + opl_write(register_base[7]+register_offset[voice], data[7]); + opl_write(register_base[8]+register_offset[voice], data[8]); + opl_write(register_base[9]+register_offset[voice], data[9]); + opl_write(register_base[10]+register_offset[voice], data[10]); +#else + synth_setpatch(voice, data); +#endif + +#if 0 + opl_write(0xA0 + voice, 0x57); + opl_write(0xB0 + voice, 0x2d); +#else + synth_setvolume_L(voice, 0x50); + synth_setvolume_R(voice, 0x50); + synth_setnote(voice, 0x30, 0); +#endif + + /* + instr[0x0e] = 0x0a; + instr[0x03] = 0x26; + + adlibemu_start_note(0x0e, 0x30, 0x40); + sleep(1); + adlibemu_start_note(0x03, 0x48, 0x40); + sleep(1); + */ +} + + +/* count is # of FRAMES, not bytes. + We assume 16-bit stereo frames (ie 4 bytes) +*/ +static void +opl2_poll (sfx_softseq_t *self, byte *dest, int count) +{ + gint16 *buffer = (gint16 *) dest; + gint16 *ptr = buffer; + + if (!ready) { + fprintf(stderr, "synth_mixer(): !ready \n"); + exit(1); + } + + if (!buffer) { + fprintf(stderr, "synth_mixer(): !buffer \n"); + exit(1); + } + +#if 0 + { + static unsigned long remaining_delta = 0; + int samples; + int remaining = count; + + while (remaining > 0) { + samples = remaining_delta * pcmout_sample_rate / 1000000; + samples = sci_min(samples, remaining); + if (samples) { + YM3812UpdateOne(ADLIB_LEFT, ptr, samples, 1); + YM3812UpdateOne(ADLIB_RIGHT, ptr+1, samples, 1); + } + if (remaining > samples) { + remaining_delta = (remaining - samples) * 1000000 / pcmout_sample_rate; + } else { + song->play_next_note(); + remaining_delta = song->get_next_delta(); + song->advance(); + } + remaining -= samples; + } + } +#endif + + if (pcmout_stereo) { + YM3812UpdateOne (ym3812_L, ptr, count, 1); + YM3812UpdateOne (ym3812_R, ptr+1, count, 1); + } else { + YM3812UpdateOne (ym3812_L, ptr, count, 0); + } +} + +static int +opl2_init(sfx_softseq_t *self, byte *data_ptr, int data_length, byte *data2_ptr, + int data2_length) +{ + int i; + + /* load up the patch.003 file, parse out the instruments */ + if (data_length < 1344) { + sciprintf ("[sfx:seq:opl2] Invalid patch.003: Expected %d, got %d\n", 1344, data_length); + return -1; + } + + for (i = 0; i < 48; i++) + make_sbi((adlib_def *)(data_ptr+(28 * i)), adlib_sbi[i]); + + if (data_length > 1344) + for (i = 48; i < 96; i++) + make_sbi((adlib_def *)(data_ptr+2+(28 * i)), adlib_sbi[i]); + + OPLBuildTables(FMOPL_ENV_BITS_HQ, FMOPL_EG_ENT_HQ); + + if (!(ym3812_L = OPLCreate (OPL_TYPE_YM3812, OPL_INTERNAL_FREQ, SAMPLE_RATE)) || + !(ym3812_R = OPLCreate (OPL_TYPE_YM3812, OPL_INTERNAL_FREQ, SAMPLE_RATE))) { + sciprintf ("[sfx:seq:opl2] Failure: Emulator init failed!\n"); + return SFX_ERROR; + } + + ready = 1; + + opl2_allstop(self); + return SFX_OK; +} + + +static void +opl2_exit(sfx_softseq_t *self) +{ + FM_OPL *opl = ym3812_L; + ym3812_L = NULL; + OPLDestroy(opl); + opl = ym3812_R; + ym3812_R = NULL; + OPLDestroy(opl); + + // XXX deregister with pcm layer. +} + +static void +opl2_allstop(sfx_softseq_t *self) +{ + // printf("AdlibEmu: Reset\n"); + if (! ready) + return; + + adlibemu_init_lists(); + + OPLResetChip (ym3812_L); + OPLResetChip (ym3812_R); + + opl_write(0x01, 0x20); + opl_write(0xBD, 0xc0); + +#ifdef DEBUG_ADLIB + printf("ADLIB: reset complete\n"); +#endif + // test_adlib(); +} + +int midi_adlibemu_reverb(short param) +{ + printf("ADLIB: reverb NYI %04x \n", param); + return 0; +} + +int midi_adlibemu_event(guint8 command, guint8 note, guint8 velocity, guint32 delta) +{ + guint8 channel, oper; + + channel = command & 0x0f; + oper = command & 0xf0; + + switch (oper) { + case 0x80: + return adlibemu_stop_note(channel, note, velocity); + case 0x90: /* noteon and noteoff */ + return adlibemu_start_note(channel, note, velocity); + case 0xe0: /* Pitch bend */ + { + int bend = (note & 0x7f) | ((velocity & 0x7f) << 7); +// printf("Bend to %d\n", bend); + adlibemu_update_pitch(channel, note, bend); + } + case 0xb0: /* CC changes. */ + switch (note) { + case 0x07: + vol[channel] = velocity; + break; + case 0x0a: + pan[channel] = velocity; + break; + case 0x4b: + break; + case 0x7b: { /* all notes off */ + int i = 0; + for (i=0;i<ADLIB_VOICES;i++) + if (oper_chn[i] == channel) + adlibemu_stop_note(channel, oper_note[i], 0); + break; + } + default: + ; /* XXXX ignore everything else for now */ + } + return 0; + case 0xd0: /* aftertouch */ + /* XXX Aftertouch in the OPL thing? */ + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + return 0; + } + + return 0; +} + +int midi_adlibemu_event2(guint8 command, guint8 param, guint32 delta) +{ + guint8 channel; + guint8 oper; + + channel = command & 0x0f; + oper = command & 0xf0; + switch (oper) { + case 0xc0: /* change instrument */ +#ifdef DEBUG_ADLIB + printf("ADLIB: Selecting instrument %d on channel %d\n", param, channel); +#endif + instr[channel] = param; + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + } + + return 0; +} + +static void +opl2_volume(sfx_softseq_t *self, int volume) +{ + guint8 i; + + i = (guint8)volume * 15 / 100; + + adlib_master=i; + +#ifdef DEBUG_ADLIB + printf("ADLIB: master volume set to %d\n", adlib_master); +#endif +} + +int +opl2_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +void +opl2_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + if (argc == 1) + midi_adlibemu_event2(cmd, argv[0], 0); + else if (argc == 2) + midi_adlibemu_event(cmd, argv[0], argv[1], 0); +} + +/* the driver struct */ + +sfx_softseq_t sfx_softseq_opl2 = { + "adlibemu", + "0.1", + opl2_set_option, + opl2_init, + opl2_exit, + opl2_volume, + opl2_event, + opl2_poll, + opl2_allstop, + NULL, + 3, /* use patch.003 */ + SFX_SEQ_PATCHFILE_NONE, + 0x4, /* Play flags */ + 0, /* No rhythm channel (9) */ + ADLIB_VOICES, /* # of voices */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/pcspeaker.c b/engines/sci/sfx/softseq/pcspeaker.c new file mode 100644 index 0000000000..771c8f0da5 --- /dev/null +++ b/engines/sci/sfx/softseq/pcspeaker.c @@ -0,0 +1,184 @@ +/*************************************************************************** + soft-pcspeaker.c Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ +/* PC speaker software sequencer for FreeSCI */ + +#include "../softseq.h" +#include <sci_midi.h> + +#define FREQUENCY 94020 + +static int volume = 0x0600; +static int note = 0; /* Current halftone, or 0 if off */ +static int freq_count = 0; + +extern sfx_softseq_t sfx_softseq_pcspeaker; +/* Forward-declare the sequencer we are defining here */ + + +static int +sps_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +static int +sps_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, + int patch2_len) +{ + return SFX_OK; +} + +static void +sps_exit(sfx_softseq_t *self) +{ +} + +static void +sps_event(sfx_softseq_t *self, byte command, int argc, byte *argv) +{ +#if 0 + fprintf(stderr, "Note [%02x : %02x %02x]\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + + switch (command & 0xf0) { + + case 0x80: + if (argv[0] == note) + note = 0; + break; + + case 0x90: + if (!argv[1]) { + if (argv[0] == note) + note = 0; + } else + note = argv[0]; + /* Ignore velocity otherwise; we just use the global one */ + break; + + case 0xb0: + if (argv[1] == SCI_MIDI_CHANNEL_NOTES_OFF) + note = 0; + break; + + default: +#if DEBUG + fprintf(stderr, "[SFX:PCM-PC] Unused MIDI command %02x %02x %02x\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); +#endif + break; /* ignore */ + } +} + +#define BASE_NOTE 129 /* A10 */ +#define BASE_OCTAVE 10 /* A10, as I said */ + +static int +freq_table[12] = { /* A4 is 440Hz, halftone map is x |-> ** 2^(x/12) */ + 28160, /* A10 */ + 29834, + 31608, + 33488, + 35479, + 37589, + 39824, + 42192, + 44701, + 47359, + 50175, + 53159 +}; + + +void +sps_poll(sfx_softseq_t *self, byte *dest, int len) +{ + int halftone_delta = note - BASE_NOTE; + int oct_diff = ((halftone_delta + BASE_OCTAVE * 12) / 12) - BASE_OCTAVE; + int halftone_index = (halftone_delta + (12*100)) % 12 ; + int freq = (!note)? 0 : freq_table[halftone_index] / (1 << (-oct_diff)); + gint16 *buf = (gint16 *) dest; + + int i; + for (i = 0; i < len; i++) { + if (note) { + freq_count += freq; + while (freq_count >= (FREQUENCY << 1)) + freq_count -= (FREQUENCY << 1); + + if (freq_count - freq < 0) { + /* Unclean rising edge */ + int l = volume << 1; + buf[i] = -volume + (l*freq_count)/freq; + } else if (freq_count >= FREQUENCY + && freq_count - freq < FREQUENCY) { + /* Unclean falling edge */ + int l = volume << 1; + buf[i] = volume - (l*(freq_count - FREQUENCY))/freq; + } else { + if (freq_count < FREQUENCY) + buf[i] = volume; + else + buf[i] = -volume; + } + } else + buf[i] = 0; + } + +} + +void +sps_volume(sfx_softseq_t *self, int new_volume) +{ + volume = new_volume << 4; +} + +void +sps_allstop(sfx_softseq_t *self) +{ + note = 0; +} + +sfx_softseq_t sfx_softseq_pcspeaker = { + "pc-speaker", + "0.3", + sps_set_option, + sps_init, + sps_exit, + sps_volume, + sps_event, + sps_poll, + sps_allstop, + NULL, + SFX_SEQ_PATCHFILE_NONE, + SFX_SEQ_PATCHFILE_NONE, + 0x20, /* PC speaker channel only */ + 0, /* No rhythm channel */ + 1, /* # of voices */ + {FREQUENCY, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_NATIVE} +}; diff --git a/engines/sci/sfx/softseq/softsequencers.c b/engines/sci/sfx/softseq/softsequencers.c new file mode 100644 index 0000000000..d0f544190e --- /dev/null +++ b/engines/sci/sfx/softseq/softsequencers.c @@ -0,0 +1,71 @@ +/*************************************************************************** + softsequencers.c Copyright (C) 2004 Christoph Reichenbach + + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public Licence as + published by the Free Software Foundaton; either version 2 of the + Licence, or (at your option) any later version. + + It is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantibility or fitness for a particular purpose. See the + GNU General Public Licence for more details. + + You should have received a copy of the GNU General Public Licence + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + + Please contact the maintainer for any program-related bug reports or + inquiries. + + Current Maintainer: + + Christoph Reichenbach (CR) <jameson@linuxgames.com> + +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "../softseq.h" + +extern sfx_softseq_t sfx_softseq_opl2; +extern sfx_softseq_t sfx_softseq_SN76496; +extern sfx_softseq_t sfx_softseq_pcspeaker; +extern sfx_softseq_t sfx_softseq_amiga; +extern sfx_softseq_t sfx_softseq_mt32; +extern sfx_softseq_t sfx_softseq_fluidsynth; + +static sfx_softseq_t *sw_sequencers[] = { + &sfx_softseq_opl2, +/* &sfx_softseq_mt32, */ + &sfx_softseq_SN76496, + &sfx_softseq_pcspeaker, + &sfx_softseq_amiga, +#ifdef HAVE_FLUIDSYNTH_H + &sfx_softseq_fluidsynth, +#endif + NULL +}; + + +sfx_softseq_t * +sfx_find_softseq(char *name) +{ + if (!name) + return sw_sequencers[0]; + else { + int i = 0; + while (sw_sequencers[i]) + if (!strcmp(sw_sequencers[i]->name, name)) + return sw_sequencers[i]; + else + ++i; + + return NULL; /* Not found */ + } +} |