diff options
author | Max Horn | 2004-12-25 18:34:44 +0000 |
---|---|---|
committer | Max Horn | 2004-12-25 18:34:44 +0000 |
commit | fec3df209601b812034fafed53ef74b7ee732512 (patch) | |
tree | 14572de096e66b3720faf67dbd9d3cf3f3926422 /sound | |
parent | 0d2fa6ecf02d5745db90d78c78e546b3fe62d373 (diff) | |
download | scummvm-rg350-fec3df209601b812034fafed53ef74b7ee732512.tar.gz scummvm-rg350-fec3df209601b812034fafed53ef74b7ee732512.tar.bz2 scummvm-rg350-fec3df209601b812034fafed53ef74b7ee732512.zip |
Moved the softsynth midi drivers into a sound/softsynth; amongst other things, this fixes bug #1083058
svn-id: r16316
Diffstat (limited to 'sound')
27 files changed, 9193 insertions, 3 deletions
diff --git a/sound/mididrv.cpp b/sound/mididrv.cpp index 9872cf65d0..11fa0d3b15 100644 --- a/sound/mididrv.cpp +++ b/sound/mididrv.cpp @@ -137,3 +137,54 @@ int MidiDriver::detectMusicDriver(int midiFlags) { return musicDriver; } + +MidiDriver *MidiDriver::createMidi(int midiDriver) { + switch(midiDriver) { + case MD_NULL: return MidiDriver_NULL_create(); + + // In the case of Adlib, we won't specify anything. + // IMuse is designed to set up its own Adlib driver + // if need be, and we only have to specify a native + // driver. + case MD_ADLIB: return NULL; + +#ifdef USE_MT32EMU + case MD_MT32: return MidiDriver_MT32_create(g_engine->_mixer); +#endif + + case MD_TOWNS: return MidiDriver_YM2612_create(g_engine->_mixer); + + // Right now PC Speaker and PCjr are handled + // outside the MidiDriver architecture, so + // don't create anything for now. + case MD_PCSPK: + case MD_PCJR: return NULL; +#if defined(__PALM_OS__) + case MD_YPA1: return MidiDriver_YamahaPa1_create(); +#ifndef DISABLE_TAPWAVE + case MD_ZODIAC: return MidiDriver_Zodiac_create(); +#endif +#endif +#if defined(WIN32) && !defined(_WIN32_WCE) + case MD_WINDOWS: return MidiDriver_WIN_create(); +#endif +#if defined(__MORPHOS__) + case MD_ETUDE: return MidiDriver_ETUDE_create(); +#endif +#if defined(UNIX) && !defined(__BEOS__) && !defined(MACOSX) + case MD_SEQ: return MidiDriver_SEQ_create(); +#endif +#if (defined(MACOSX) || defined(macintosh)) && !defined(__PALM_OS__) + case MD_QTMUSIC: return MidiDriver_QT_create(); +#endif +#if defined(MACOSX) + case MD_COREAUDIO: return MidiDriver_CORE_create(); +#endif +#if defined(UNIX) && defined(USE_ALSA) + case MD_ALSA: return MidiDriver_ALSA_create(); +#endif + } + + error("Invalid midi driver selected"); + return NULL; +} diff --git a/sound/module.mk b/sound/module.mk index 12b5da5868..e5e409d416 100644 --- a/sound/module.mk +++ b/sound/module.mk @@ -14,11 +14,14 @@ MODULE_OBJS := \ sound/rate.o \ sound/voc.o \ sound/vorbis.o \ - sound/flac.o -# sound/resample.o \ + sound/flac.o \ + sound/softsynth/adlib.o \ + sound/softsynth/ym2612.o \ + sound/softsynth/mt32.o \ MODULE_DIRS += \ - sound + sound \ + sound/softsynth # Include common rules include $(srcdir)/common.rules diff --git a/sound/softsynth/.cvsignore b/sound/softsynth/.cvsignore new file mode 100644 index 0000000000..39a06683b7 --- /dev/null +++ b/sound/softsynth/.cvsignore @@ -0,0 +1 @@ +.deps diff --git a/sound/softsynth/adlib.cpp b/sound/softsynth/adlib.cpp new file mode 100644 index 0000000000..97967af89f --- /dev/null +++ b/sound/softsynth/adlib.cpp @@ -0,0 +1,1509 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001-2004 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + */ + +#include "sound/softsynth/emumidi.h" +#include "common/util.h" +#include "sound/fmopl.h" + +#ifdef DEBUG_ADLIB +static int tick; +#endif + +class MidiDriver_ADLIB; +struct AdlibVoice; + +struct InstrumentExtra { + byte a, b, c, d, e, f, g, h; +}; + +struct AdlibInstrument { + byte flags_1; + byte oplvl_1; + byte atdec_1; + byte sustrel_1; + byte waveform_1; + byte flags_2; + byte oplvl_2; + byte atdec_2; + byte sustrel_2; + byte waveform_2; + byte feedback; + byte flags_a; + InstrumentExtra extra_a; + byte flags_b; + InstrumentExtra extra_b; + byte duration; + + AdlibInstrument() { memset(this, 0, sizeof(AdlibInstrument)); } +}; + +class AdlibPart : public MidiChannel { + friend class MidiDriver_ADLIB; + +protected: +// AdlibPart *_prev, *_next; + AdlibVoice *_voice; + int16 _pitchbend; + byte _pitchbend_factor; + int8 _transpose_eff; + byte _vol_eff; + int8 _detune_eff; + byte _modwheel; + bool _pedal; + byte _program; + byte _pri_eff; + AdlibInstrument _part_instr; + +protected: + MidiDriver_ADLIB *_owner; + bool _allocated; + byte _channel; + + void init(MidiDriver_ADLIB *owner, byte channel); + void allocate() { _allocated = true; } + +public: + AdlibPart() { + _voice = 0; + _pitchbend = 0; + _pitchbend_factor = 2; + _transpose_eff = 0; + _vol_eff = 0; + _detune_eff = 0; + _modwheel = 0; + _pedal = 0; + _program = 0; + _pri_eff = 0; + + _owner = 0; + _allocated = false; + _channel = 0; + } + + MidiDriver *device(); + byte getNumber() { return _channel; } + void release() { _allocated = false; } + + void send (uint32 b); + + // Regular messages + void noteOff(byte note); + void noteOn(byte note, byte velocity); + void programChange(byte program); + void pitchBend(int16 bend); + + // Control Change messages + void controlChange(byte control, byte value); + void modulationWheel(byte value); + void volume(byte value); + void panPosition(byte value) { return; } // Not supported + void pitchBendFactor(byte value); + void detune(byte value); + void priority(byte value); + void sustain(bool value); + void effectLevel(byte value) { return; } // Not supported + void chorusLevel(byte value) { return; } // Not supported + void allNotesOff(); + + // SysEx messages + void sysEx_customInstrument(uint32 type, byte *instr); +}; + +// FYI (Jamieson630) +// It is assumed that any invocation to AdlibPercussionChannel +// will be done through the MidiChannel base class as opposed to the +// AdlibPart base class. If this were NOT the case, all the functions +// listed below would need to be virtual in AdlibPart as well as MidiChannel. +class AdlibPercussionChannel : public AdlibPart { + friend class MidiDriver_ADLIB; + +protected: + void init(MidiDriver_ADLIB *owner, byte channel); + +public: + void noteOff(byte note); + void noteOn(byte note, byte velocity); + void programChange(byte program) { } + void pitchBend(int16 bend) { } + + // Control Change messages + void controlChange(byte control, byte value) { } + void modulationWheel(byte value) { } + void pitchBendFactor(byte value) { } + void detune(byte value) { } + void priority(byte value) { } + void sustain(bool value) { } + + // SysEx messages + void sysEx_customInstrument(uint32 type, byte *instr) { } +}; + +struct Struct10 { + byte active; + int16 cur_val; + int16 count; + uint16 max_value; + int16 start_value; + byte loop; + byte table_a[4]; + byte table_b[4]; + int8 unk3; + int8 modwheel; + int8 modwheel_last; + uint16 speed_lo_max; + uint16 num_steps; + int16 speed_hi; + int8 direction; + uint16 speed_lo; + uint16 speed_lo_counter; +}; + +struct Struct11 { + int16 modify_val; + byte param, flag0x40, flag0x10; + Struct10 *s10; +}; + +struct AdlibVoice { + AdlibPart *_part; + AdlibVoice *_next, *_prev; + byte _waitforpedal; + byte _note; + byte _channel; + byte _twochan; + byte _vol_1, _vol_2; + int16 _duration; + + Struct10 _s10a; + Struct11 _s11a; + Struct10 _s10b; + Struct11 _s11b; + + AdlibVoice() { memset(this, 0, sizeof(AdlibVoice)); } +}; + +struct AdlibSetParams { + byte a, b, c, d; +}; + +static const byte channel_mappings[9] = { + 0, 1, 2, 8, + 9, 10, 16, 17, + 18 +}; + +static const byte channel_mappings_2[9] = { + 3, 4, 5, 11, + 12, 13, 19, 20, + 21 +}; + +static const AdlibSetParams adlib_setparam_table[] = { + {0x40, 0, 63, 63}, // level + {0xE0, 2, 0, 0}, // unused + {0x40, 6, 192, 0}, // level key scaling + {0x20, 0, 15, 0}, // modulator frequency multiple + {0x60, 4, 240, 15}, // attack rate + {0x60, 0, 15, 15}, // decay rate + {0x80, 4, 240, 15}, // sustain level + {0x80, 0, 15, 15}, // release rate + {0xE0, 0, 3, 0}, // waveform select + {0x20, 7, 128, 0}, // amp mod + {0x20, 6, 64, 0}, // vib + {0x20, 5, 32, 0}, // eg typ + {0x20, 4, 16, 0}, // ksr + {0xC0, 0, 1, 0}, // decay alg + {0xC0, 1, 14, 0} // feedback +}; + +const byte param_table_1[16] = { + 29, 28, 27, 0, + 3, 4, 7, 8, + 13, 16, 17, 20, + 21, 30, 31, 0 +}; + +const uint16 maxval_table[16] = { + 0x2FF, 0x1F, 0x7, 0x3F, + 0x0F, 0x0F, 0x0F, 0x3, + 0x3F, 0x0F, 0x0F, 0x0F, + 0x3, 0x3E, 0x1F, 0 +}; + +static const uint16 num_steps_table[] = { + 1, 2, 4, 5, + 6, 7, 8, 9, + 10, 12, 14, 16, + 18, 21, 24, 30, + 36, 50, 64, 82, + 100, 136, 160, 192, + 240, 276, 340, 460, + 600, 860, 1200, 1600 +}; + +static const byte note_to_f_num[] = { + 90, 91, 92, 92, 93, 94, 94, 95, + 96, 96, 97, 98, 98, 99, 100, 101, + 101, 102, 103, 104, 104, 105, 106, 107, + 107, 108, 109, 110, 111, 111, 112, 113, + 114, 115, 115, 116, 117, 118, 119, 120, + 121, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, + 143, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 157, 158, 159, 160, + 161, 162, 163, 165, 166, 167, 168, 169, + 171, 172, 173, 174, 176, 177, 178, 180, + 181, 182, 184, 185, 186, 188, 189, 190, + 192, 193, 194, 196, 197, 199, 200, 202, + 203, 205, 206, 208, 209, 211, 212, 214, + 215, 217, 218, 220, 222, 223, 225, 226, + 228, 230, 231, 233, 235, 236, 238, 240, + 242, 243, 245, 247, 249, 251, 252, 254, +}; + +static const byte map_gm_to_fm[128][30] = { + // 0x00 +{ 0xC2, 0xC5, 0x2B, 0x99, 0x58, 0xC2, 0x1F, 0x1E, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 }, +{ 0x22, 0x53, 0x0E, 0x8A, 0x30, 0x14, 0x06, 0x1D, 0x7A, 0x5C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x06, 0x00, 0x1C, 0x79, 0x40, 0x02, 0x00, 0x4B, 0x79, 0x58, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x89, 0x2A, 0x89, 0x49, 0xC2, 0x16, 0x1C, 0xB8, 0x7C, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x23 }, +{ 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x20, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x84, 0x40, 0x3B, 0x5A, 0x6F, 0x81, 0x0E, 0x3B, 0x5A, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0x7F, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x8C, 0x80, 0x05, 0xEA, 0x59, 0x82, 0x0A, 0x3C, 0xAA, 0x64, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x85, 0x40, 0x0D, 0xEC, 0x71, 0x84, 0x58, 0x3E, 0xCB, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x8A, 0xC0, 0x0C, 0xDC, 0x50, 0x88, 0x58, 0x3D, 0xDA, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC9, 0x40, 0x2B, 0x78, 0x42, 0xC2, 0x04, 0x4C, 0x8A, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1A }, +{ 0x2A, 0x0E, 0x17, 0x89, 0x28, 0x22, 0x0C, 0x1B, 0x09, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE7, 0x9B, 0x08, 0x08, 0x26, 0xE2, 0x06, 0x0A, 0x08, 0x70, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC5, 0x05, 0x00, 0xFC, 0x40, 0x84, 0x00, 0x00, 0xDC, 0x50, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x86, 0x40, 0x5D, 0x5A, 0x41, 0x81, 0x00, 0x0B, 0x5A, 0x7F, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x10 +{ 0xED, 0x00, 0x7B, 0xC8, 0x40, 0xE1, 0x99, 0x4A, 0xE9, 0x7E, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE8, 0x4F, 0x3A, 0xD7, 0x7C, 0xE2, 0x97, 0x49, 0xF9, 0x7D, 0x05, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x10, 0x2F, 0xF7, 0x7D, 0xF3, 0x45, 0x8F, 0xC7, 0x62, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x01, 0x8C, 0x9F, 0xDA, 0x70, 0xE4, 0x50, 0x9F, 0xDA, 0x6A, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x08, 0xD5, 0x9D, 0xA5, 0x45, 0xE2, 0x3F, 0x9F, 0xD6, 0x49, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE5, 0x0F, 0x7D, 0xB8, 0x2E, 0xA2, 0x0F, 0x7C, 0xC7, 0x61, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0x62, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x88, 0x9C, 0x50, 0x64, 0xE2, 0x18, 0x70, 0xC4, 0x7C, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 }, +{ 0x42, 0x55, 0x3E, 0xEB, 0x24, 0xD4, 0x08, 0x0D, 0xA9, 0x71, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x18 }, +{ 0xC2, 0x00, 0x2B, 0x17, 0x51, 0xC2, 0x1E, 0x4D, 0x97, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x19 }, +{ 0xC6, 0x01, 0x2D, 0xA7, 0x44, 0xC2, 0x06, 0x0E, 0xA7, 0x79, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x0C, 0x06, 0x06, 0x55, 0xC2, 0x3F, 0x09, 0x86, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0A }, +{ 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0x59, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0x7F, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0B }, + // 0x20 +{ 0xC2, 0x40, 0x3C, 0x96, 0x58, 0xC4, 0xDE, 0x0E, 0xC7, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x20 }, +{ 0x31, 0x13, 0x2D, 0xD7, 0x3C, 0xE2, 0x18, 0x2E, 0xB8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x22, 0x86, 0x0D, 0xD7, 0x50, 0xE4, 0x18, 0x5E, 0xB8, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 }, +{ 0xF2, 0x0A, 0x0D, 0xD7, 0x40, 0xE4, 0x1F, 0x5E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x09, 0x4B, 0xD6, 0x48, 0xE4, 0x1F, 0x1C, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x28 }, +{ 0x62, 0x11, 0x0C, 0xE6, 0x3C, 0xE4, 0x1F, 0x0C, 0xC8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x12, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x7D, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x13, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x5D, 0xB8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xA2, 0x40, 0x5D, 0xBA, 0x3F, 0xE2, 0x00, 0x8F, 0xD8, 0x79, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x40, 0x3D, 0xDA, 0x3B, 0xE1, 0x00, 0x7E, 0xD8, 0x7A, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x00, 0x6D, 0xFA, 0x5D, 0xE2, 0x00, 0x8F, 0xC8, 0x79, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x4E, 0xDB, 0x4A, 0xE3, 0x18, 0x6F, 0xE9, 0x7E, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x4E, 0xDB, 0x66, 0xE2, 0x00, 0x7F, 0xE9, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x02, 0x0F, 0x66, 0xAA, 0x51, 0x02, 0x64, 0x29, 0xF9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x16, 0x4A, 0x04, 0xBA, 0x39, 0xC2, 0x58, 0x2D, 0xCA, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x02, 0x00, 0x01, 0x7A, 0x79, 0x02, 0x3F, 0x28, 0xEA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, + // 0x30 +{ 0x62, 0x53, 0x9C, 0xBA, 0x31, 0x62, 0x5B, 0xAD, 0xC9, 0x55, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x40, 0x6E, 0xDA, 0x49, 0xE2, 0x13, 0x8F, 0xF9, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x40, 0x8F, 0xFA, 0x50, 0xF2, 0x04, 0x7F, 0xFA, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x3D, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE6, 0x80, 0x9C, 0x99, 0x42, 0xE2, 0x04, 0x7D, 0x78, 0x60, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC3, 0x3F, 0x4B, 0xE9, 0x7E, 0xC1, 0x3F, 0x9B, 0xF9, 0x7F, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x00, 0x8F, 0xFB, 0x50, 0xF6, 0x47, 0x8F, 0xE9, 0x68, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x00, 0xAF, 0x88, 0x58, 0xF2, 0x54, 0x6E, 0xC9, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x84, 0x4E, 0x78, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x02, 0x9F, 0xB8, 0x48, 0x22, 0x89, 0x9F, 0xE8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0x7D, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x40 +{ 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0x7D, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x0F, 0x90, 0xF8, 0x78, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x00, 0x8E, 0xC9, 0x3D, 0xE6, 0x00, 0x7E, 0xD8, 0x68, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x00, 0x5F, 0xF9, 0x48, 0xE6, 0x98, 0x8F, 0xF8, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x0C, 0x6E, 0xD8, 0x3D, 0x2A, 0x06, 0x7D, 0xD8, 0x58, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x7E, 0x89, 0x38, 0xE6, 0x84, 0x80, 0xF8, 0x68, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x80, 0x6C, 0xD9, 0x30, 0xE2, 0x00, 0x8D, 0xC8, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x80, 0x88, 0x48, 0x40, 0xE2, 0x0A, 0x7D, 0xA8, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x77, 0xC5, 0x54, 0xE2, 0x00, 0x9E, 0xD7, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x80, 0x86, 0xB9, 0x64, 0xE2, 0x05, 0x9F, 0xD7, 0x78, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x00, 0x68, 0x68, 0x56, 0xE2, 0x08, 0x9B, 0xB3, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0xA6, 0x87, 0x41, 0xE2, 0x0A, 0x7E, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x80, 0x9A, 0xB8, 0x48, 0xE2, 0x00, 0x9E, 0xF9, 0x60, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x80, 0x8E, 0x64, 0x68, 0xE2, 0x28, 0x6F, 0x73, 0x7C, 0x01, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x50 +{ 0xE8, 0x00, 0x7D, 0x99, 0x54, 0xE6, 0x80, 0x80, 0xF8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE6, 0x00, 0x9F, 0xB9, 0x6D, 0xE1, 0x00, 0x8F, 0xC8, 0x7D, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x09, 0x68, 0x4A, 0xE2, 0x2B, 0x9E, 0xF3, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC4, 0x00, 0x99, 0xE8, 0x3B, 0xE2, 0x25, 0x6F, 0x93, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE6, 0x00, 0x6F, 0xDA, 0x69, 0xE2, 0x05, 0x2F, 0xD8, 0x6A, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE3, 0x00, 0x0F, 0xF7, 0x7D, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x3C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE8, 0x40, 0x0D, 0x89, 0x7D, 0xE2, 0x17, 0x7E, 0xD9, 0x7C, 0x07, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0xDF, 0x8A, 0x56, 0xE2, 0x5E, 0xCF, 0xBA, 0x7E, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE2, 0x00, 0x0B, 0x68, 0x60, 0xE2, 0x01, 0x9E, 0xB8, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEA, 0x00, 0xAE, 0xAB, 0x49, 0xE2, 0x00, 0xAE, 0xBA, 0x6C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xEB, 0x80, 0x8C, 0xCB, 0x3A, 0xE2, 0x86, 0xAF, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE5, 0x40, 0xDB, 0x3B, 0x3C, 0xE2, 0x80, 0xBE, 0xCA, 0x71, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE4, 0x00, 0x9E, 0xAA, 0x3D, 0xE1, 0x43, 0x0F, 0xBA, 0x7E, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE7, 0x40, 0xEC, 0xCA, 0x44, 0xE2, 0x03, 0xBF, 0xBA, 0x66, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x60 +{ 0xEA, 0x00, 0x68, 0xB8, 0x48, 0xE2, 0x0A, 0x8E, 0xB8, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x61, 0x00, 0xBE, 0x99, 0x7E, 0xE3, 0x40, 0xCF, 0xCA, 0x7D, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xCD, 0x00, 0x0B, 0x00, 0x48, 0xC2, 0x58, 0x0C, 0x00, 0x7C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x1C }, +{ 0xE2, 0x00, 0x0E, 0x00, 0x52, 0xE2, 0x58, 0x5F, 0xD0, 0x7D, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xCC, 0x00, 0x7D, 0xDA, 0x40, 0xC2, 0x00, 0x5E, 0x9B, 0x58, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE9, 0xC0, 0xEE, 0xD8, 0x43, 0xE2, 0x05, 0xDD, 0xAA, 0x70, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xDA, 0x00, 0x8F, 0xAC, 0x4A, 0x22, 0x05, 0x8D, 0x8A, 0x75, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x62, 0x8A, 0xCB, 0x7A, 0x74, 0xE6, 0x56, 0xAF, 0xDB, 0x70, 0x02, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xC2, 0x41, 0xAC, 0x5B, 0x5B, 0xC2, 0x80, 0x0D, 0xCB, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x12 }, +{ 0x75, 0x00, 0x0E, 0xCB, 0x5A, 0xE2, 0x1E, 0x0A, 0xC9, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, +{ 0x41, 0x00, 0x0E, 0xEA, 0x53, 0xC2, 0x00, 0x08, 0xCA, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0xC1, 0x40, 0x0C, 0x59, 0x6A, 0xC2, 0x80, 0x3C, 0xAB, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D }, +{ 0x4B, 0x00, 0x0A, 0xF5, 0x61, 0xC2, 0x19, 0x0C, 0xE9, 0x7C, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0x62, 0x00, 0x7F, 0xD8, 0x54, 0xEA, 0x00, 0x8F, 0xD8, 0x7D, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, + // 0x70 +{ 0xCF, 0x40, 0x09, 0xEA, 0x54, 0xC4, 0x00, 0x0C, 0xDB, 0x64, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x40, 0x0C, 0xAA, 0x54, 0xC4, 0x00, 0x18, 0xF9, 0x64, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xC9, 0x0E, 0x88, 0xD9, 0x3E, 0xC2, 0x08, 0x1A, 0xEA, 0x6C, 0x0C, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x03, 0x00, 0x15, 0x00, 0x64, 0x02, 0x00, 0x08, 0x00, 0x7C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x01, 0x00, 0x47, 0xD7, 0x6C, 0x01, 0x3F, 0x0C, 0xFB, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x00, 0x00, 0x36, 0x67, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x02, 0x00, 0x36, 0x68, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0xCB, 0x00, 0xAF, 0x00, 0x7E, 0xC0, 0x00, 0xC0, 0x06, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0F }, +{ 0x05, 0x0D, 0x80, 0xA6, 0x7F, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x0F, 0x00, 0x90, 0xFA, 0x68, 0x06, 0x00, 0xA7, 0x39, 0x54, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0xC9, 0x15, 0xDD, 0xFF, 0x7C, 0x00, 0x00, 0xE7, 0xFC, 0x6C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x38 }, +{ 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x07, 0x80, 0x0B, 0xC8, 0x65, 0x02, 0x3F, 0x0C, 0xEA, 0x7C, 0x0F, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x08, 0x00, 0x0B, 0x3C, 0x7C, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 } +}; + +static byte gm_percussion_to_fm[39][30] = { +{ 0x1A, 0x3F, 0x15, 0x05, 0x7C, 0x02, 0x21, 0x2B, 0xE4, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0x11, 0x12, 0x04, 0x07, 0x7C, 0x02, 0x23, 0x0B, 0xE5, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x0A, 0x3F, 0x0B, 0x01, 0x7C, 0x1F, 0x1C, 0x46, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x01 }, +{ 0x00, 0x3F, 0x0F, 0x00, 0x7C, 0x10, 0x12, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x0F, 0x3F, 0x0B, 0x00, 0x7C, 0x1F, 0x0F, 0x19, 0xD0, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x00, 0x3F, 0x1F, 0x00, 0x7E, 0x1F, 0x16, 0x07, 0x00, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x1F, 0x4A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x7F, 0x08, 0xFF, 0x7E, 0x00, 0xC7, 0x2D, 0xF7, 0x73, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x43, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x7F, 0x08, 0xCF, 0x7E, 0x00, 0x45, 0x2A, 0xF8, 0x4B, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C }, +{ 0x12, 0x3F, 0x06, 0x17, 0x7C, 0x03, 0x27, 0x0B, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x7F, 0x08, 0xCD, 0x7E, 0x00, 0x40, 0x1A, 0x69, 0x63, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0C }, +{ 0x13, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x17, 0x0A, 0xD9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x15, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x3F, 0x2B, 0xFB, 0x7E, 0xC0, 0x1E, 0x1A, 0xCA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, +{ 0x17, 0x3F, 0x04, 0x09, 0x7C, 0x03, 0x22, 0x0D, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x3F, 0x0F, 0x5E, 0x7C, 0xC6, 0x13, 0x00, 0xCA, 0x7F, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0xCF, 0x3F, 0x7E, 0x9D, 0x7C, 0xC8, 0xC0, 0x0A, 0xBA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x06 }, +{ 0xCF, 0x3F, 0x4D, 0x9F, 0x7C, 0xC6, 0x00, 0x08, 0xDA, 0x5B, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0xCF, 0x3F, 0x5D, 0xAA, 0x7A, 0xC0, 0xA4, 0x67, 0x99, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x3F, 0x4A, 0xFD, 0x7C, 0xCF, 0x00, 0x59, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x0F, 0x18, 0x0A, 0xFA, 0x57, 0x06, 0x07, 0x06, 0x39, 0x7C, 0x0A, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x3F, 0x2B, 0xFC, 0x7C, 0xCC, 0xC6, 0x0B, 0xEA, 0x7F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x10 }, +{ 0x05, 0x1A, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x0C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0x04, 0x19, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x2C, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x04 }, +{ 0x04, 0x0A, 0x04, 0x00, 0x6C, 0x01, 0x07, 0x0D, 0xFA, 0x74, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x07 }, +{ 0x15, 0x14, 0x05, 0x00, 0x7D, 0x01, 0x07, 0x5C, 0xE9, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x10, 0x10, 0x05, 0x08, 0x7C, 0x01, 0x08, 0x0D, 0xEA, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x05 }, +{ 0x11, 0x00, 0x06, 0x87, 0x7F, 0x02, 0x40, 0x09, 0x59, 0x68, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 }, +{ 0x13, 0x26, 0x04, 0x6A, 0x7F, 0x01, 0x00, 0x08, 0x5A, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x08 }, +{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC4, 0x00, 0x18, 0xF9, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC3, 0x00, 0x18, 0xF8, 0x54, 0x04, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xCB, 0x3F, 0x8F, 0x00, 0x7E, 0xC5, 0x00, 0x98, 0xD6, 0x5F, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x0D }, +{ 0x0C, 0x18, 0x87, 0xB3, 0x7F, 0x19, 0x10, 0x55, 0x75, 0x7C, 0x0E, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x05, 0x11, 0x15, 0x00, 0x64, 0x02, 0x08, 0x08, 0x00, 0x5C, 0x09, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0x04, 0x08, 0x15, 0x00, 0x48, 0x01, 0x08, 0x08, 0x00, 0x60, 0x08, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x02 }, +{ 0xDA, 0x00, 0x53, 0x30, 0x68, 0x07, 0x1E, 0x49, 0xC4, 0x7E, 0x03, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 }, +{ 0x1C, 0x00, 0x07, 0xBC, 0x6C, 0x0C, 0x14, 0x0B, 0x6A, 0x7E, 0x0B, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x03 }, +{ 0x0A, 0x0E, 0x7F, 0x00, 0x7D, 0x13, 0x20, 0x28, 0x03, 0x7C, 0x06, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x00 } +}; + +static const byte gm_percussion_lookup[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0xFF, 0xFF, 0x17, 0x18, 0x19, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x21, 0x22, 0x23, 0xFF, 0xFF, + 0x24, 0x25, 0xFF, 0xFF, 0xFF, 0x26, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +static byte lookup_table[64][32]; +static const byte volume_table[] = { + 0, 4, 7, 11, + 13, 16, 18, 20, + 22, 24, 26, 27, + 29, 30, 31, 33, + 34, 35, 36, 37, + 38, 39, 40, 41, + 42, 43, 44, 44, + 45, 46, 47, 47, + 48, 49, 49, 50, + 51, 51, 52, 53, + 53, 54, 54, 55, + 55, 56, 56, 57, + 57, 58, 58, 59, + 59, 60, 60, 60, + 61, 61, 62, 62, + 62, 63, 63, 63 +}; + +static int lookup_volume(int a, int b) { + if (b == 0) + return 0; + + if (b == 31) + return a; + + if (a < -63 || a > 63) { + return b * (a + 1) >> 5; + } + + if (b < 0) { + if (a < 0) { + return lookup_table[-a][-b]; + } else { + return -lookup_table[a][-b]; + } + } else { + if (a < 0) { + return -lookup_table[-a][b]; + } else { + return lookup_table[a][b]; + } + } +} + +static void create_lookup_table() { + int i, j; + int sum; + + for (i = 0; i < 64; i++) { + sum = i; + for (j = 0; j < 32; j++) { + lookup_table[i][j] = sum >> 5; + sum += i; + } + } + for (i = 0; i < 64; i++) + lookup_table[i][0] = 0; +} + +//////////////////////////////////////// +// +// Adlib MIDI driver +// +//////////////////////////////////////// + +class MidiDriver_ADLIB : public MidiDriver_Emulated { + friend class AdlibPart; + friend class AdlibPercussionChannel; + +public: + MidiDriver_ADLIB(SoundMixer *mixer); + + int open(); + void close(); + void send(uint32 b); + void send(byte channel, uint32 b); // Supports higher than channel 15 + uint32 property(int prop, uint32 param); + + void setPitchBendRange(byte channel, uint range); + void sysEx_customInstrument(byte channel, uint32 type, byte *instr); + + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported + + + // AudioStream API + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + +private: + bool _game_SmallHeader; + + FM_OPL *_opl; + byte *_adlib_reg_cache; + + int _adlib_timer_counter; + + uint16 channel_table_2[9]; + int _voice_index; + int _timer_p; + int _timer_q; + uint16 curnote_table[9]; + AdlibVoice _voices[9]; + AdlibPart _parts[32]; + AdlibPercussionChannel _percussion; + + void generateSamples(int16 *buf, int len); + void onTimer(); + void part_key_on(AdlibPart *part, AdlibInstrument *instr, byte note, byte velocity); + void part_key_off(AdlibPart *part, byte note); + + void adlib_key_off(int chan); + void adlib_note_on(int chan, byte note, int mod); + void adlib_note_on_ex(int chan, byte note, int mod); + int adlib_read_param(int chan, byte data); + void adlib_setup_channel(int chan, AdlibInstrument * instr, byte vol_1, byte vol_2); + byte adlib_read(byte port) { + return _adlib_reg_cache[port]; + } + void adlib_set_param(int channel, byte param, int value); + void adlib_key_onoff(int channel); + void adlib_write(byte port, byte value); + void adlib_playnote(int channel, int note); + + AdlibVoice *allocate_voice(byte pri); + + void mc_off(AdlibVoice * voice); + + static void link_mc(AdlibPart *part, AdlibVoice *voice); + void mc_inc_stuff(AdlibVoice *voice, Struct10 * s10, Struct11 * s11); + void mc_init_stuff(AdlibVoice *voice, Struct10 * s10, Struct11 * s11, byte flags, + InstrumentExtra * ie); + + void struct10_init(Struct10 * s10, InstrumentExtra * ie); + static byte struct10_ontimer(Struct10 * s10, Struct11 * s11); + static void struct10_setup(Struct10 * s10); + static int random_nr(int a); + void mc_key_on(AdlibVoice *voice, AdlibInstrument *instr, byte note, byte velocity); +}; + +// MidiChannel method implementations + +void AdlibPart::init(MidiDriver_ADLIB *owner, byte channel) { + _owner = owner; + _channel = channel; + _pri_eff = 127; +} + +MidiDriver *AdlibPart::device() { + return _owner; +} + +void AdlibPart::send(uint32 b) { + _owner->send(_channel, b); +} + +void AdlibPart::noteOff(byte note) { +#ifdef DEBUG_ADLIB + debug(6, "%10d: noteOff(%d)", tick, note); +#endif + _owner->part_key_off(this, note); +} + +void AdlibPart::noteOn(byte note, byte velocity) { +#ifdef DEBUG_ADLIB + debug(6, "%10d: noteOn(%d,%d)", tick, note, velocity); +#endif + _owner->part_key_on(this, &_part_instr, note, velocity); +} + +void AdlibPart::programChange(byte program) { + if (program > 127) + return; + + uint i; + uint count = 0; + for (i = 0; i < ARRAYSIZE(map_gm_to_fm[0]); ++i) + count += map_gm_to_fm[program][i]; + if (!count) + warning("No Adlib instrument defined for GM program %d", (int) program); + _program = program; + memcpy(&_part_instr, &map_gm_to_fm[program], sizeof(AdlibInstrument)); +} + +void AdlibPart::pitchBend(int16 bend) { + AdlibVoice *voice; + + _pitchbend = bend; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, + (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + } +} + +void AdlibPart::controlChange(byte control, byte value) { + switch (control) { + case 1: modulationWheel(value); break; + case 7: volume(value); break; + case 10: break; // Pan position. Not supported. + case 16: pitchBendFactor(value); break; + case 17: detune(value); break; + case 18: priority(value); break; + case 64: sustain(value > 0); break; + case 91: break; // Effects level. Not supported. + case 93: break; // Chorus level. Not supported. + case 119: break; // Unknown, used in Simon the Sorcerer 2 + case 121: break; // Unknown, used in Simon the Sorcerer 1 + case 123: allNotesOff(); break; + default: + warning("Adlib: Unknown control change message %d", (int) control); + } +} + +void AdlibPart::modulationWheel(byte value) { + AdlibVoice *voice; + + _modwheel = value; + for (voice = _voice; voice; voice = voice->_next) { + if (voice->_s10a.active && voice->_s11a.flag0x40) + voice->_s10a.modwheel = _modwheel >> 2; + if (voice->_s10b.active && voice->_s11b.flag0x40) + voice->_s10b.modwheel = _modwheel >> 2; + } +} + +void AdlibPart::volume(byte value) { + AdlibVoice *voice; + + _vol_eff = value; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_set_param(voice->_channel, 0, volume_table[lookup_table[voice->_vol_2][_vol_eff >> 2]]); + if (voice->_twochan) { + _owner->adlib_set_param(voice->_channel, 13, volume_table[lookup_table[voice->_vol_1][_vol_eff >> 2]]); + } + } +} + +void AdlibPart::pitchBendFactor(byte value) { + AdlibVoice *voice; + + _pitchbend_factor = value; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, + (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + } +} + +void AdlibPart::detune(byte value) { + AdlibVoice *voice; + + _detune_eff = value; + for (voice = _voice; voice; voice = voice->_next) { + _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, + (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + } +} + +void AdlibPart::priority(byte value) { + _pri_eff = value; +} + +void AdlibPart::sustain(bool value) { + AdlibVoice *voice; + + _pedal = value; + if (!value) { + for (voice = _voice; voice; voice = voice->_next) { + if (voice->_waitforpedal) + _owner->mc_off(voice); + } + } +} + +void AdlibPart::allNotesOff() { + while (_voice) + _owner->mc_off(_voice); +} + +void AdlibPart::sysEx_customInstrument(uint32 type, byte *instr) { + if (type == 'ADL ') { + AdlibInstrument *i = &_part_instr; + memcpy(i, instr, sizeof(AdlibInstrument)); + } +} + +// MidiChannel method implementations for percussion + +void AdlibPercussionChannel::init(MidiDriver_ADLIB *owner, byte channel) { + AdlibPart::init(owner, channel); + _pri_eff = 0; + _vol_eff = 127; +} + +void AdlibPercussionChannel::noteOff(byte note) { + // Jamieson630: Unless I run into a specific instrument that + // may require a key off, I'm going to ignore this message. + // The rationale is that a percussion instrument should + // fade out of its own accord, and the Adlib instrument + // definitions used should follow this rule. Since + // percussion voices are allocated at the lowest priority + // anyway, we know that "hanging" percussion sounds will + // not prevent later musical instruments (or even other + // percussion sounds) from playing. +/* + _owner->part_key_off(this, note); +*/ +} + +void AdlibPercussionChannel::noteOn(byte note, byte velocity) { + byte key = gm_percussion_lookup[note]; + if (key == 0xFF) { + debug(2, "No FM map for GM percussion key %d", (int) note); + return; + } + _owner->part_key_on(this, (AdlibInstrument *) &gm_percussion_to_fm[key], note, velocity); +} + +// MidiDriver method implementations + +MidiDriver_ADLIB::MidiDriver_ADLIB(SoundMixer *mixer) + : MidiDriver_Emulated(mixer) { + uint i; + + _game_SmallHeader = false; + + _adlib_reg_cache = 0; + + _adlib_timer_counter = 0; + _voice_index = 0; + for (i = 0; i < ARRAYSIZE(curnote_table); ++i) { + curnote_table[i] = 0; + } + + for (i = 0; i < ARRAYSIZE(_parts); ++i) { + _parts[i].init(this, i + ((i >= 9) ? 1 : 0)); + } + _percussion.init(this, 9); + _timer_p = 0xD69; + _timer_q = 0x411B; +} + +int MidiDriver_ADLIB::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + int i; + AdlibVoice *voice; + + for (i = 0, voice = _voices; i != ARRAYSIZE(_voices); i++, voice++) { + voice->_channel = i; + voice->_s11a.s10 = &voice->_s10b; + voice->_s11b.s10 = &voice->_s10a; + } + + _adlib_reg_cache = (byte *)calloc(256, 1); + + _opl = makeAdlibOPL(getRate()); + + adlib_write(1, 0x20); + adlib_write(8, 0x40); + adlib_write(0xBD, 0x00); + create_lookup_table(); + + _mixer->setupPremix(this); + + return 0; +} + +void MidiDriver_ADLIB::close() { + if (!_isOpen) + return; + _isOpen = false; + + // Detach the premix callback handler + _mixer->setupPremix(0); + + uint i; + for (i = 0; i < ARRAYSIZE(_voices); ++i) { + if (_voices[i]._part) + mc_off(&_voices[i]); + } + + // Turn off the OPL emulation +// YM3812Shutdown(); + + free(_adlib_reg_cache); +} + +void MidiDriver_ADLIB::send(uint32 b) { + send(b & 0xF, b & 0xFFFFFFF0); +} + +void MidiDriver_ADLIB::send(byte chan, uint32 b) { + //byte param3 = (byte) ((b >> 24) & 0xFF); + byte param2 = (byte) ((b >> 16) & 0xFF); + byte param1 = (byte) ((b >> 8) & 0xFF); + byte cmd = (byte) (b & 0xF0); + + AdlibPart *part; + if (chan == 9) + part = &_percussion; + else + part = &_parts[chan]; + + switch (cmd) { + case 0x80:// Note Off + part->noteOff(param1); + break; + case 0x90: // Note On + part->noteOn(param1, param2); + break; + case 0xA0: // Aftertouch + break; // Not supported. + case 0xB0: // Control Change + part->controlChange(param1, param2); + break; + case 0xC0: // Program Change + part->programChange(param1); + break; + case 0xD0: // Channel Pressure + break; // Not supported. + case 0xE0: // Pitch Bend + part->pitchBend((param1 | (param2 << 7)) - 0x2000); + break; + case 0xF0: // SysEx + // We should never get here! SysEx information has to be + // sent via high-level semantic methods. + warning("MidiDriver_ADLIB: Receiving SysEx command on a send() call"); + break; + + default: + warning("MidiDriver_ADLIB: Unknown send() command 0x%02X", cmd); + } +} + +uint32 MidiDriver_ADLIB::property(int prop, uint32 param) { + switch (prop) { + case PROP_OLD_ADLIB: // Older games used a different operator volume algorithm + _game_SmallHeader = (param > 0); + if (_game_SmallHeader) { + _timer_p = 473; + _timer_q = 1000; + } else { + _timer_p = 0xD69; + _timer_q = 0x411B; + } + return 1; + } + + return 0; +} + +void MidiDriver_ADLIB::setPitchBendRange(byte channel, uint range) { + AdlibVoice *voice; + AdlibPart *part = &_parts[channel]; + + part->_pitchbend_factor = range; + for (voice = part->_voice; voice; voice = voice->_next) { + adlib_note_on(voice->_channel, voice->_note + part->_transpose_eff, + (part->_pitchbend * part->_pitchbend_factor >> 6) + part->_detune_eff); + } +} + +void MidiDriver_ADLIB::sysEx_customInstrument(byte channel, uint32 type, byte *instr) { + _parts[channel].sysEx_customInstrument(type, instr); +} + +MidiChannel *MidiDriver_ADLIB::allocateChannel() { + AdlibPart *part; + uint i; + + for (i = 0; i < ARRAYSIZE(_parts); ++i) { + part = &_parts[i]; + if (!part->_allocated) { + part->allocate(); + return part; + } + } + return NULL; +} + +MidiDriver *MidiDriver_ADLIB_create(SoundMixer *mixer) { + return new MidiDriver_ADLIB(mixer); +} + +// All the code brought over from IMuseAdlib + +void MidiDriver_ADLIB::adlib_write(byte port, byte value) { + if (_adlib_reg_cache[port] == value) + return; +#ifdef DEBUG_ADLIB + debug(6, "%10d: adlib_write[%x] = %x", tick, port, value); +#endif + _adlib_reg_cache[port] = value; + + OPLWriteReg(_opl, port, value); +} + +void MidiDriver_ADLIB::generateSamples(int16 *data, int len) { + memset(data, 0, sizeof(int16) * len); + YM3812UpdateOne(_opl, data, len); +} + +void MidiDriver_ADLIB::onTimer() { + AdlibVoice *voice; + int i; + + _adlib_timer_counter += _timer_p; + while (_adlib_timer_counter >= _timer_q) { + _adlib_timer_counter -= _timer_q; +#ifdef DEBUG_ADLIB + tick++; +#endif + voice = _voices; + for (i = 0; i != ARRAYSIZE(_voices); i++, voice++) { + if (!voice->_part) + continue; + if (voice->_duration && (voice->_duration -= 0x11) <= 0) { + mc_off(voice); + return; + } + if (voice->_s10a.active) { + mc_inc_stuff(voice, &voice->_s10a, &voice->_s11a); + } + if (voice->_s10b.active) { + mc_inc_stuff(voice, &voice->_s10b, &voice->_s11b); + } + } + } +} + +void MidiDriver_ADLIB::mc_off(AdlibVoice *voice) { + AdlibVoice *tmp; + + adlib_key_off(voice->_channel); + + tmp = voice->_prev; + + if (voice->_next) + voice->_next->_prev = tmp; + if (tmp) + tmp->_next = voice->_next; + else + voice->_part->_voice = voice->_next; + voice->_part = NULL; +} + +void MidiDriver_ADLIB::mc_inc_stuff(AdlibVoice *voice, Struct10 *s10, Struct11 *s11) { + byte code; + AdlibPart *part = voice->_part; + + code = struct10_ontimer(s10, s11); + + if (code & 1) { + switch (s11->param) { + case 0: + voice->_vol_2 = s10->start_value + s11->modify_val; + if (!_game_SmallHeader) { + adlib_set_param(voice->_channel, 0, + volume_table[lookup_table[voice->_vol_2] + [part->_vol_eff >> 2]]); + } else { + adlib_set_param(voice->_channel, 0, voice->_vol_2); + } + break; + case 13: + voice->_vol_1 = s10->start_value + s11->modify_val; + if (voice->_twochan && !_game_SmallHeader) { + adlib_set_param(voice->_channel, 13, + volume_table[lookup_table[voice->_vol_1] + [part->_vol_eff >> 2]]); + } else { + adlib_set_param(voice->_channel, 13, voice->_vol_1); + } + break; + case 30: + s11->s10->modwheel = (char)s11->modify_val; + break; + case 31: + s11->s10->unk3 = (char)s11->modify_val; + break; + default: + adlib_set_param(voice->_channel, s11->param, + s10->start_value + s11->modify_val); + break; + } + } + + if (code & 2 && s11->flag0x10) + adlib_key_onoff(voice->_channel); +} + +void MidiDriver_ADLIB::adlib_key_off(int chan){ + byte port = chan + 0xB0; + adlib_write(port, adlib_read(port) & ~0x20); +} + +byte MidiDriver_ADLIB::struct10_ontimer(Struct10 *s10, Struct11 *s11) { + byte result = 0; + int i; + + if (s10->count && (s10->count -= 17) <= 0) { + s10->active = 0; + return 0; + } + + i = s10->cur_val + s10->speed_hi; + s10->speed_lo_counter += s10->speed_lo; + if (s10->speed_lo_counter >= s10->speed_lo_max) { + s10->speed_lo_counter -= s10->speed_lo_max; + i += s10->direction; + } + if (s10->cur_val != i || s10->modwheel != s10->modwheel_last) { + s10->cur_val = i; + s10->modwheel_last = s10->modwheel; + i = lookup_volume(i, s10->modwheel_last); + if (i != s11->modify_val) { + s11->modify_val = i; + result = 1; + } + } + + if (!--s10->num_steps) { + s10->active++; + if (s10->active > 4) { + if (s10->loop) { + s10->active = 1; + result |= 2; + struct10_setup(s10); + } else { + s10->active = 0; + } + } else { + struct10_setup(s10); + } + } + + return result; +} + +void MidiDriver_ADLIB::adlib_set_param(int channel, byte param, int value) { + const AdlibSetParams *as; + byte port; + + assert(channel >= 0 && channel < 9); + + if (param <= 12) { + port = channel_mappings_2[channel]; + } else if (param <= 25) { + param -= 13; + port = channel_mappings[channel]; + } else if (param <= 27) { + param -= 13; + port = channel; + } else if (param == 28 || param == 29) { + if (param == 28) + value -= 15; + else + value -= 383; + value <<= 4; + channel_table_2[channel] = value; + adlib_playnote(channel, curnote_table[channel] + value); + return; + } else { + return; + } + + as = &adlib_setparam_table[param]; + if (as->d) + value = as->d - value; + port += as->a; + adlib_write(port, (adlib_read(port) & ~as->c) | (((byte)value) << as->b)); +} + +void MidiDriver_ADLIB::adlib_key_onoff(int channel) { + byte val; + byte port = channel + 0xB0; + assert(channel >= 0 && channel < 9); + + val = adlib_read(port); + adlib_write(port, val & ~0x20); + adlib_write(port, val | 0x20); +} + +void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) { + int b, c, d, e, f, g, h; + byte t; + + b = s10->unk3; + f = s10->active - 1; + + t = s10->table_a[f]; + e = num_steps_table[lookup_table[t & 0x7F][b]]; + if (t & 0x80) { + e = random_nr(e); + } + if (e == 0) + e++; + + s10->num_steps = s10->speed_lo_max = e; + + if (f != 2) { + c = s10->max_value; + g = s10->start_value; + t = s10->table_b[f]; + d = lookup_volume(c, (t & 0x7F) - 31); + if (t & 0x80) { + d = random_nr(d); + } + if (d + g > c) { + h = c - g; + } else { + h = d; + if (d + g < 0) + h = -g; + } + h -= s10->cur_val; + } else { + h = 0; + } + + s10->speed_hi = h / e; + if (h < 0) { + h = -h; + s10->direction = -1; + } else { + s10->direction = 1; + } + + s10->speed_lo = h % e; + s10->speed_lo_counter = 0; +} + +void MidiDriver_ADLIB::adlib_playnote(int channel, int note) { + byte old, oct, notex; + int note2; + int i; + + note2 = (note >> 7) - 4; + note2 = (note2 < 128) ? note2 : 0; + + oct = (note2 / 12); + if (oct > 7) + oct = 7 << 2; + else + oct <<= 2; + notex = note2 % 12 + 3; + + old = adlib_read(channel + 0xB0); + if (old & 0x20) { + old &= ~0x20; + if (oct > old) { + if (notex < 6) { + notex += 12; + oct -= 4; + } + } else if (oct < old) { + if (notex > 11) { + notex -= 12; + oct += 4; + } + } + } + + i = (notex << 3) + ((note >> 4) & 0x7); + adlib_write(channel + 0xA0, note_to_f_num[i]); + adlib_write(channel + 0xB0, oct | 0x20); +} + +// TODO: Replace this with RandomSource? But if so, please note that this +// function will be called with negative parameters - getRandomNumber(-1) will +// crash ScummVM, random_nr(-1) won't. + +int MidiDriver_ADLIB::random_nr(int a) { + static byte _rand_seed = 1; + if (_rand_seed & 1) { + _rand_seed >>= 1; + _rand_seed ^= 0xB8; + } else { + _rand_seed >>= 1; + } + return _rand_seed * a >> 8; +} + +void MidiDriver_ADLIB::part_key_off(AdlibPart *part, byte note) { + AdlibVoice *voice; + + for (voice = part->_voice; voice; voice = voice->_next) { + if (voice->_note == note) { + if (part->_pedal) + voice->_waitforpedal = true; + else + mc_off(voice); + } + } +} + +void MidiDriver_ADLIB::part_key_on(AdlibPart *part, AdlibInstrument *instr, byte note, byte velocity) { + AdlibVoice *voice; + + voice = allocate_voice(part->_pri_eff); + if (!voice) + return; + + link_mc(part, voice); + mc_key_on(voice, instr, note, velocity); +} + +AdlibVoice *MidiDriver_ADLIB::allocate_voice(byte pri) { + AdlibVoice *ac, *best = NULL; + int i; + + for (i = 0; i < 9; i++) { + if (++_voice_index >= 9) + _voice_index = 0; + ac = &_voices[_voice_index]; + if (!ac->_part) + return ac; + if (!ac->_next) { + if (ac->_part->_pri_eff <= pri) { + pri = ac->_part->_pri_eff; + best = ac; + } + } + } + + /* V3 games don't have note priorities, first comes wins. */ + if (_game_SmallHeader) + return NULL; + + if (best) + mc_off(best); + return best; +} + +void MidiDriver_ADLIB::link_mc(AdlibPart *part, AdlibVoice *voice) { + voice->_part = part; + voice->_next = (AdlibVoice *)part->_voice; + part->_voice = voice; + voice->_prev = NULL; + + if (voice->_next) + voice->_next->_prev = voice; +} + +void MidiDriver_ADLIB::mc_key_on(AdlibVoice *voice, AdlibInstrument *instr, byte note, byte velocity) { + AdlibPart *part = voice->_part; + int c; + byte vol_1, vol_2; + + voice->_twochan = instr->feedback & 1; + voice->_note = note; + voice->_waitforpedal = false; + voice->_duration = instr->duration; + if (voice->_duration != 0) + voice->_duration *= 63; + + if (!_game_SmallHeader) + vol_1 = (instr->oplvl_1 & 0x3F) + lookup_table[velocity >> 1][instr->waveform_1 >> 2]; + else + vol_1 = 0x3f - (instr->oplvl_1 & 0x3F); + if (vol_1 > 0x3F) + vol_1 = 0x3F; + voice->_vol_1 = vol_1; + + if (!_game_SmallHeader) + vol_2 = (instr->oplvl_2 & 0x3F) + lookup_table[velocity >> 1][instr->waveform_2 >> 2]; + else + vol_2 = 0x3f - (instr->oplvl_2 & 0x3F); + if (vol_2 > 0x3F) + vol_2 = 0x3F; + voice->_vol_2 = vol_2; + + c = part->_vol_eff >> 2; + + if (!_game_SmallHeader) { + vol_2 = volume_table[lookup_table[vol_2][c]]; + if (voice->_twochan) + vol_1 = volume_table[lookup_table[vol_1][c]]; + } + + adlib_setup_channel(voice->_channel, instr, vol_1, vol_2); + adlib_note_on_ex(voice->_channel, part->_transpose_eff + note, part->_detune_eff + (part->_pitchbend * part->_pitchbend_factor >> 6)); + + if (instr->flags_a & 0x80) { + mc_init_stuff(voice, &voice->_s10a, &voice->_s11a, instr->flags_a, &instr->extra_a); + } else { + voice->_s10a.active = 0; + } + + if (instr->flags_b & 0x80) { + mc_init_stuff(voice, &voice->_s10b, &voice->_s11b, instr->flags_b, &instr->extra_b); + } else { + voice->_s10b.active = 0; + } +} + +void MidiDriver_ADLIB::adlib_setup_channel(int chan, AdlibInstrument *instr, byte vol_1, byte vol_2) { + byte port; + + assert(chan >= 0 && chan < 9); + + port = channel_mappings[chan]; + adlib_write(port + 0x20, instr->flags_1); + adlib_write(port + 0x40, (instr->oplvl_1 | 0x3F) - vol_1 ); + adlib_write(port + 0x60, 0xff & (~instr->atdec_1)); + adlib_write(port + 0x80, 0xff & (~instr->sustrel_1)); + adlib_write(port + 0xE0, instr->waveform_1); + + port = channel_mappings_2[chan]; + adlib_write(port + 0x20, instr->flags_2); + adlib_write(port + 0x40, (instr->oplvl_2 | 0x3F) - vol_2 ); + adlib_write(port + 0x60, 0xff & (~instr->atdec_2)); + adlib_write(port + 0x80, 0xff & (~instr->sustrel_2)); + adlib_write(port + 0xE0, instr->waveform_2); + + adlib_write((byte)chan + 0xC0, instr->feedback); +} + +void MidiDriver_ADLIB::adlib_note_on_ex(int chan, byte note, int mod) +{ + int code; + assert(chan >= 0 && chan < 9); + code = (note << 7) + mod; + curnote_table[chan] = code; + channel_table_2[chan] = 0; + adlib_playnote(chan, code); +} + +void MidiDriver_ADLIB::mc_init_stuff(AdlibVoice *voice, Struct10 * s10, + Struct11 * s11, byte flags, InstrumentExtra * ie) { + AdlibPart *part = voice->_part; + s11->modify_val = 0; + s11->flag0x40 = flags & 0x40; + s10->loop = flags & 0x20; + s11->flag0x10 = flags & 0x10; + s11->param = param_table_1[flags & 0xF]; + s10->max_value = maxval_table[flags & 0xF]; + s10->unk3 = 31; + if (s11->flag0x40) { + s10->modwheel = part->_modwheel >> 2; + } else { + s10->modwheel = 31; + } + + switch (s11->param) { + case 0: + s10->start_value = voice->_vol_2; + break; + case 13: + s10->start_value = voice->_vol_1; + break; + case 30: + s10->start_value = 31; + s11->s10->modwheel = 0; + break; + case 31: + s10->start_value = 0; + s11->s10->unk3 = 0; + break; + default: + s10->start_value = adlib_read_param(voice->_channel, s11->param); + } + + struct10_init(s10, ie); +} + +void MidiDriver_ADLIB::struct10_init(Struct10 *s10, InstrumentExtra *ie) { + s10->active = 1; + if (!_game_SmallHeader) { + s10->cur_val = 0; + } else { + s10->cur_val = s10->start_value; + s10->start_value = 0; + } + s10->modwheel_last = 31; + s10->count = ie->a; + if (s10->count) + s10->count *= 63; + s10->table_a[0] = ie->b; + s10->table_a[1] = ie->d; + s10->table_a[2] = ie->f; + s10->table_a[3] = ie->g; + + s10->table_b[0] = ie->c; + s10->table_b[1] = ie->e; + s10->table_b[2] = 0; + s10->table_b[3] = ie->h; + + struct10_setup(s10); +} + +int MidiDriver_ADLIB::adlib_read_param(int chan, byte param) { + const AdlibSetParams *as; + byte val; + byte port; + + assert(chan >= 0 && chan < 9); + + if (param <= 12) { + port = channel_mappings_2[chan]; + } else if (param <= 25) { + param -= 13; + port = channel_mappings[chan]; + } else if (param <= 27) { + param -= 13; + port = chan; + } else if (param == 28) { + return 0xF; + } else if (param == 29) { + return 0x17F; + } else { + return 0; + } + + as = &adlib_setparam_table[param]; + val = adlib_read(port + as->a); + val &= as->c; + val >>= as->b; + if (as->d) + val = as->d - val; + + return val; +} + +void MidiDriver_ADLIB::adlib_note_on(int chan, byte note, int mod) { + int code; + assert(chan >= 0 && chan < 9); + code = (note << 7) + mod; + curnote_table[chan] = code; + adlib_playnote(chan, (int16) channel_table_2[chan] + code); +} diff --git a/sound/softsynth/emumidi.h b/sound/softsynth/emumidi.h new file mode 100644 index 0000000000..34cd4b0983 --- /dev/null +++ b/sound/softsynth/emumidi.h @@ -0,0 +1,107 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001-2004 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + */ + +#include "stdafx.h" +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/mixer.h" + +#define FIXP_SHIFT 16 + +class MidiDriver_Emulated : public AudioStream, public MidiDriver { +protected: + bool _isOpen; + SoundMixer *_mixer; + +private: + Timer::TimerProc _timerProc; + void *_timerParam; + + int _nextTick; + int _samplesPerTick; + +protected: + virtual void generateSamples(int16 *buf, int len) = 0; + virtual void onTimer() {} + + int _baseFreq; + +public: + MidiDriver_Emulated(SoundMixer *mixer) : _mixer(mixer) { + _isOpen = false; + + _timerProc = 0; + _timerParam = 0; + + _nextTick = 0; + _samplesPerTick = 0; + + _baseFreq = 250; + } + + int open() { + _isOpen = true; + + int d = getRate() / _baseFreq; + int r = getRate() % _baseFreq; + + // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ + // but less prone to arithmetic overflow. + + _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq; + return 0; + } + + void setTimerCallback(void *timer_param, Timer::TimerProc timer_proc) { + _timerProc = timer_proc; + _timerParam = timer_param; + } + + uint32 getBaseTempo() { return 1000000 / _baseFreq; } + + + // AudioStream API + int readBuffer(int16 *data, const int numSamples) { + const int stereoFactor = isStereo() ? 2 : 1; + int len = numSamples / stereoFactor; + int step; + + do { + step = len; + if (step > (_nextTick >> FIXP_SHIFT)) + step = (_nextTick >> FIXP_SHIFT); + + generateSamples(data, step); + + _nextTick -= step << FIXP_SHIFT; + if (!(_nextTick >> FIXP_SHIFT)) { + if (_timerProc) + (*_timerProc)(_timerParam); + onTimer(); + _nextTick += _samplesPerTick; + } + data += step * stereoFactor; + len -= step; + } while (len); + + return numSamples; + } + bool endOfData() const { return false; } +}; diff --git a/sound/softsynth/mt32.cpp b/sound/softsynth/mt32.cpp new file mode 100644 index 0000000000..4789e46fc7 --- /dev/null +++ b/sound/softsynth/mt32.cpp @@ -0,0 +1,486 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001-2004 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + */ + +#include "stdafx.h" +#include "common/scummsys.h" + +#ifdef USE_MT32EMU + +#include "sound/softsynth/mt32/mt32emu.h" + +#include "sound/softsynth/emumidi.h" +#include "sound/mpu401.h" + +#include "common/util.h" +#include "common/file.h" +#include "common/config-manager.h" + +#include "graphics/font.h" +#include "graphics/surface.h" + +class MidiChannel_MT32 : public MidiChannel_MPU401 { + void effectLevel(byte value) { } + void chorusLevel(byte value) { } +}; + +class MidiDriver_MT32 : public MidiDriver_Emulated { +private: + PlayingSoundHandle _handle; + MidiChannel_MT32 _midiChannels[16]; + uint16 _channelMask; + MT32Emu::Synth *_synth; + + int _outputRate; + +protected: + void generateSamples(int16 *buf, int len); + +public: + bool _initialising; + + MidiDriver_MT32(SoundMixer *mixer); + virtual ~MidiDriver_MT32(); + + int open(); + void close(); + void send(uint32 b); + void setPitchBendRange (byte channel, uint range); + void sysEx(byte *msg, uint16 length); + + uint32 property(int prop, uint32 param); + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel(); + + // AudioStream API + bool isStereo() const { return true; } + int getRate() const { return _outputRate; } +}; + +typedef File SFile; + +class MT32File: public MT32Emu::File { + SFile file; +public: + bool open(const char *filename, OpenMode mode) { + SFile::AccessMode accessMode = mode == OpenMode_read ? SFile::kFileReadMode : SFile::kFileWriteMode; + return file.open(filename, accessMode); + } + void close() { + return file.close(); + } + size_t read(void *in, size_t size) { + return file.read(in, size); + } + bool readLine(char *in, size_t size) { + return file.gets(in, size) != NULL; + } + bool readBit8u(MT32Emu::Bit8u *in) { + byte b = file.readByte(); + if (file.eof()) + return false; + *in = b; + return true; + } + size_t write(const void *in, size_t size) { + return file.write(in, size); + } + bool writeBit8u(MT32Emu::Bit8u out) { + file.writeByte(out); + return !file.ioFailed(); + } + bool isEOF() { + return file.eof(); + } +}; + +static int eatSystemEvents() { + OSystem::Event event; + while (g_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_QUIT: + return 1; + default: + break; + } + } + return 0; +} + +static void drawProgress(float progress) { + Graphics::Surface surf; + uint32 borderColor = 0x2; + uint32 fillColor = 0x4; + surf.w = g_system->getWidth() / 7 * 5; + surf.h = Graphics::g_scummfont.getFontHeight(); + int x = g_system->getWidth() / 7; + int y = g_system->getHeight() / 2 - surf.h / 2; + surf.pitch = surf.w; + surf.bytesPerPixel = 1; + surf.pixels = calloc(surf.w, surf.h); + Common::Rect r(surf.w, surf.h); + surf.frameRect(r, borderColor); + r.grow(-1); + r.right = r.left + (uint16)(r.width() * progress); + surf.fillRect(r, fillColor); + g_system->copyRectToScreen((byte *)surf.pixels, surf.pitch, x, y, surf.w, surf.h); + g_system->updateScreen(); + free(surf.pixels); +} + +static void drawMessage(int offset, const Common::String &text) { + const Graphics::Font &font(Graphics::g_scummfont); + Graphics::Surface surf; + uint32 color = 0x2; + surf.w = g_system->getWidth(); + surf.h = font.getFontHeight(); + surf.pitch = surf.w; + surf.bytesPerPixel = 1; + surf.pixels = calloc(surf.w, surf.h); + font.drawString(&surf, text, 0, 0, surf.w, color, Graphics::kTextAlignCenter); + int y = g_system->getHeight() / 2 - font.getFontHeight() / 2 + offset * (font.getFontHeight() + 1); + g_system->copyRectToScreen((byte *)surf.pixels, surf.pitch, 0, y, surf.w, surf.h); + g_system->updateScreen(); + free(surf.pixels); +} + +static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) { + MT32File *file = new MT32File(); + if (!file->open(filename, mode)) { + delete file; + return NULL; + } + return file; +} + +static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) { + char buf[512]; + if (((MidiDriver_MT32 *)userData)->_initialising) { + vsprintf(buf, fmt, list); + buf[70] = 0; // Truncate to a reasonable length + drawMessage(1, buf); + } + //vdebug(0, fmt, list); // FIXME: Use a higher debug level +} + +static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) { + switch(type) { + case MT32Emu::ReportType_lcdMessage: + g_system->displayMessageOnOSD((const char *)reportData); + break; + case MT32Emu::ReportType_errorControlROM: + error("Failed to load MT32_CONTROL.ROM"); + break; + case MT32Emu::ReportType_errorPCMROM: + error("Failed to load MT32_PCM.ROM"); + break; + case MT32Emu::ReportType_progressInit: + if (((MidiDriver_MT32 *)userData)->_initialising) { + drawProgress(*((const float *)reportData)); + return eatSystemEvents(); + } + break; + default: + break; + } + return 0; +} + +//////////////////////////////////////// +// +// MidiDriver_MT32 +// +//////////////////////////////////////// + +MidiDriver_MT32::MidiDriver_MT32(SoundMixer *mixer) : MidiDriver_Emulated(mixer) { + _channelMask = 0xFFFF; // Permit all 16 channels by default + uint i; + for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { + _midiChannels[i].init(this, i); + } + _synth = NULL; + // A higher baseFreq reduces the length used in generateSamples(), + // and means that the timer callback will be called more often. + // That results in more accurate timing. + _baseFreq = 10000; + // Unfortunately bugs in the emulator cause inaccurate tuning + // at rates other than 32KHz, thus we produce data at 32KHz and + // rely on SoundMixer to convert. + _outputRate = 32000; //_mixer->getOutputRate(); + _initialising = false; +} + +MidiDriver_MT32::~MidiDriver_MT32() { + if (_synth != NULL) + delete _synth; +} + +int MidiDriver_MT32::open() { + MT32Emu::SynthProperties prop; + + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + memset(&prop, 0, sizeof(prop)); + prop.sampleRate = getRate(); + prop.useReverb = true; + prop.useDefaultReverb = false; + prop.reverbType = 0; + prop.reverbTime = 5; + prop.reverbLevel = 3; + prop.userData = this; + prop.printDebug = MT32_PrintDebug; + prop.report = MT32_Report; + prop.openFile = MT32_OpenFile; + _synth = new MT32Emu::Synth(); + _initialising = true; + const byte dummy_palette[] = { + 0, 0, 0, 0, + 0, 0, 171, 0, + 0, 171, 0, 0, + 0, 171, 171, 0, + 171, 0, 0, 0 + }; + + g_system->setPalette(dummy_palette, 0, 5); + drawMessage(-1, "Initialising MT-32 Emulator"); + if (!_synth->open(prop)) + return MERR_DEVICE_NOT_AVAILABLE; + _initialising = false; + g_system->clearScreen(); + g_system->updateScreen(); + _mixer->playInputStream(&_handle, this, false, -1, 255, 0, false, true); + return 0; +} + +void MidiDriver_MT32::send(uint32 b) { + _synth->playMsg(b); +} + +void MidiDriver_MT32::setPitchBendRange(byte channel, uint range) { + if (range > 24) { + printf("setPitchBendRange() called with range > 24: %d", range); + } + byte benderRangeSysex[9]; + benderRangeSysex[0] = 0x41; // Roland + benderRangeSysex[1] = channel; + benderRangeSysex[2] = 0x16; // MT-32 + benderRangeSysex[3] = 0x12; // Write + benderRangeSysex[4] = 0x00; + benderRangeSysex[5] = 0x00; + benderRangeSysex[6] = 0x04; + benderRangeSysex[7] = (byte)range; + benderRangeSysex[8] = MT32Emu::Synth::calcSysexChecksum(&benderRangeSysex[4], 4, 0); + sysEx(benderRangeSysex, 9); +} + +void MidiDriver_MT32::sysEx(byte *msg, uint16 length) { + if (msg[0] == 0xf0) { + _synth->playSysex(msg, length); + } else { + _synth->playSysexWithoutFraming(msg, length); + } +} + +void MidiDriver_MT32::close() { + if (!_isOpen) + return; + _isOpen = false; + + // Detach the player callback handler + setTimerCallback(NULL, NULL); + // Detach the mixer callback handler + _mixer->stopHandle(_handle); + + _synth->close(); + delete _synth; + _synth = NULL; +} + +void MidiDriver_MT32::generateSamples(int16 *data, int len) { + _synth->render(data, len); +} + +uint32 MidiDriver_MT32::property(int prop, uint32 param) { + switch (prop) { + case PROP_CHANNEL_MASK: + _channelMask = param & 0xFFFF; + return 1; + } + + return 0; +} + +MidiChannel *MidiDriver_MT32::allocateChannel() { + MidiChannel_MT32 *chan; + uint i; + + for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { + if (i == 9 || !(_channelMask & (1 << i))) + continue; + chan = &_midiChannels[i]; + if (chan->allocate()) { + return chan; + } + } + return NULL; +} + +MidiChannel *MidiDriver_MT32::getPercussionChannel() { + return &_midiChannels[9]; +} + +// This code should be used when calling the timer callback from the mixer thread is undesirable. +// Note that it results in less accurate timing. +#if 0 +class MidiEvent_MT32 { +public: + MidiEvent_MT32 *_next; + uint32 _msg; // 0xFFFFFFFF indicates a sysex message + byte *_data; + uint32 _len; + + MidiEvent_MT32(uint32 msg, byte *data, uint32 len) { + _msg = msg; + if (len > 0) { + _data = new byte[len]; + memcpy(_data, data, len); + } + _len = len; + _next = NULL; + } + + MidiEvent_MT32() { + if (_len > 0) + delete _data; + } +}; + +class MidiDriver_ThreadedMT32 : public MidiDriver_MT32 { +private: + OSystem::MutexRef _eventMutex; + MidiEvent_MT32 *_events; + Timer::TimerProc _timer_proc; + + void pushMidiEvent(MidiEvent_MT32 *event); + MidiEvent_MT32 *popMidiEvent(); + +protected: + void send(uint32 b); + void sysEx(byte *msg, uint16 length); + +public: + MidiDriver_ThreadedMT32(SoundMixer *mixer); + virtual ~MidiDriver_ThreadedMT32(); + + void onTimer(); + void close(); + void setTimerCallback(void *timer_param, Timer::TimerProc timer_proc); +}; + + +MidiDriver_ThreadedMT32::MidiDriver_ThreadedMT32(SoundMixer *mixer) : MidiDriver_MT32(mixer) { + _eventMutex = g_system->createMutex(); + _events = NULL; + _timer_proc = NULL; +} + +MidiDriver_ThreadedMT32::~MidiDriver_ThreadedMT32() { + g_system->deleteMutex(_eventMutex); +} + +void MidiDriver_ThreadedMT32::close() { + MidiDriver_MT32::close(); + while ((popMidiEvent() != NULL)) { + // Just eat any leftover events + } +} + +void MidiDriver_ThreadedMT32::setTimerCallback(void *timer_param, Timer::TimerProc timer_proc) { + if (!_timer_proc || !timer_proc) { + if (_timer_proc) + g_timer->removeTimerProc(_timer_proc); + _timer_proc = timer_proc; + if (timer_proc) + g_timer->installTimerProc(timer_proc, getBaseTempo(), timer_param); + } +} + +void MidiDriver_ThreadedMT32::pushMidiEvent(MidiEvent_MT32 *event) { + g_system->lockMutex(_eventMutex); + if (_events == NULL) { + _events = event; + } else { + MidiEvent_MT32 *last = _events; + while (last->_next != NULL) + last = last->_next; + last->_next = event; + } + g_system->unlockMutex(_eventMutex); +} + +MidiEvent_MT32 *MidiDriver_ThreadedMT32::popMidiEvent() { + MidiEvent_MT32 *event; + g_system->lockMutex(_eventMutex); + event = _events; + if (event != NULL) + _events = event->_next; + g_system->unlockMutex(_eventMutex); + return event; +} + +void MidiDriver_ThreadedMT32::send(uint32 b) { + MidiEvent_MT32 *event = new MidiEvent_MT32(b, NULL, 0); + pushMidiEvent(event); +} + +void MidiDriver_ThreadedMT32::sysEx(byte *msg, uint16 length) { + MidiEvent_MT32 *event = new MidiEvent_MT32(0xFFFFFFFF, msg, length); + pushMidiEvent(event); +} + +void MidiDriver_ThreadedMT32::onTimer() { + MidiEvent_MT32 *event; + while ((event = popMidiEvent()) != NULL) { + if (event->_msg == 0xFFFFFFFF) { + MidiDriver_MT32::sysEx(event->_data, event->_len); + } else { + MidiDriver_MT32::send(event->_msg); + } + delete event; + } +} +#endif + +//////////////////////////////////////// +// +// MidiDriver_MT32 factory +// +//////////////////////////////////////// + +MidiDriver *MidiDriver_MT32_create(SoundMixer *mixer) { + // HACK: It will stay here until engine plugin loader overhaul + if (ConfMan.hasKey("extrapath")) + File::addDefaultDirectory(ConfMan.get("extrapath")); + return new MidiDriver_MT32(mixer); +} + +#endif diff --git a/sound/softsynth/mt32/.cvsignore b/sound/softsynth/mt32/.cvsignore new file mode 100644 index 0000000000..39a06683b7 --- /dev/null +++ b/sound/softsynth/mt32/.cvsignore @@ -0,0 +1 @@ +.deps diff --git a/sound/softsynth/mt32/freeverb.cpp b/sound/softsynth/mt32/freeverb.cpp new file mode 100644 index 0000000000..0c366fe62f --- /dev/null +++ b/sound/softsynth/mt32/freeverb.cpp @@ -0,0 +1,312 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004 The ScummVM project + * Copyright (C) 2000 Jezar at Dreampoint + * + * This code is public domain + * + * Parts of this code are: + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + * + */ + +// Comb filter implementation +// +// Written by +// http://www.dreampoint.co.uk +// This code is public domain + +#include "stdafx.h" +#include "sound/softsynth/mt32/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/sound/softsynth/mt32/freeverb.h b/sound/softsynth/mt32/freeverb.h new file mode 100644 index 0000000000..d618f79670 --- /dev/null +++ b/sound/softsynth/mt32/freeverb.h @@ -0,0 +1,240 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004 The ScummVM project + * Copyright (C) 2000 Jezar at Dreampoint + * + * This code is public domain + * + * Parts of this code are: + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + * + */ + +// 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/sound/softsynth/mt32/i386.cpp b/sound/softsynth/mt32/i386.cpp new file mode 100644 index 0000000000..e2e4b0f790 --- /dev/null +++ b/sound/softsynth/mt32/i386.cpp @@ -0,0 +1,817 @@ +/* Copyright (c) 2003-2004 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__( + "movl $1, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + : "=r"(result) : : "eax", "ebx", "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__( + "movl $0x80000001, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + : "=r"(result) : : "eax", "ebx", "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) + : "xmm1", "xmm2", "xmm3", "memory"); + + 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, %%ebx \n" \ + "movq 0(%%ebx), %%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, %%ebx \n" \ + "movq 0(%%ebx), %%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, %%ebx \n" \ + "addl $8, %%eax \n" \ + " \n" \ + "movq 0(%%ebx), %%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, %%ebx \n" \ + "movq 0(%%ebx), %%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", "ebx", "mm1", "mm2", "mm3", "memory"); + + 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", "mm1", "mm2", "mm3", "memory"); +} + +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", "mm1", "mm2", "memory"); +} + +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", "mm1", "mm2", "mm3", "memory"); +} + +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", "mm1", "mm2", "memory"); +} + +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" \ + "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" \ + : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) + : "eax", "ebx", "ecx", "edx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); +} + +#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, int revLevel) { + 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 + output *= ResonInv[revLevel]; + return output; +} + +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { + 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 + output *= ResonInv[revLevel]; + 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/sound/softsynth/mt32/i386.h b/sound/softsynth/mt32/i386.h new file mode 100644 index 0000000000..c26fcb4110 --- /dev/null +++ b/sound/softsynth/mt32/i386.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2003-2004 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, int revLevel); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel); + +#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/sound/softsynth/mt32/module.mk b/sound/softsynth/mt32/module.mk new file mode 100644 index 0000000000..2e48efa9d6 --- /dev/null +++ b/sound/softsynth/mt32/module.mk @@ -0,0 +1,17 @@ +MODULE := sound/softsynth/mt32 + +MODULE_OBJS := \ + sound/softsynth/mt32/mt32_file.o \ + sound/softsynth/mt32/i386.o \ + sound/softsynth/mt32/part.o \ + sound/softsynth/mt32/partial.o \ + sound/softsynth/mt32/partialManager.o \ + sound/softsynth/mt32/synth.o \ + sound/softsynth/mt32/tables.o \ + sound/softsynth/mt32/freeverb.o + +MODULE_DIRS += \ + sound/softsynth/mt32 + +# Include common rules +include $(srcdir)/common.rules diff --git a/sound/softsynth/mt32/mt32_file.cpp b/sound/softsynth/mt32/mt32_file.cpp new file mode 100644 index 0000000000..8cf4e14a7d --- /dev/null +++ b/sound/softsynth/mt32/mt32_file.cpp @@ -0,0 +1,112 @@ +/* Copyright (c) 2003-2004 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/sound/softsynth/mt32/mt32_file.h b/sound/softsynth/mt32/mt32_file.h new file mode 100644 index 0000000000..7ebb6449b6 --- /dev/null +++ b/sound/softsynth/mt32/mt32_file.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2003-2004 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(unsigned char out); + bool isEOF(); +}; + +} + +#endif diff --git a/sound/softsynth/mt32/mt32emu.h b/sound/softsynth/mt32/mt32emu.h new file mode 100644 index 0000000000..9fffa721f5 --- /dev/null +++ b/sound/softsynth/mt32/mt32emu.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2004 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/sound/softsynth/mt32/part.cpp b/sound/softsynth/mt32/part.cpp new file mode 100644 index 0000000000..5aee1202b2 --- /dev/null +++ b/sound/softsynth/mt32/part.cpp @@ -0,0 +1,603 @@ +/* Copyright (c) 2003-2004 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; + if (usePartNum == 8) { + // Nasty hack for rhythm + patchTemp = NULL; + timbreTemp = NULL; + } else { + sprintf(name, "Part %d", partNum + 1); + patchTemp = &synth->mt32ram.patchSettings[partNum]; + timbreTemp = &synth->mt32ram.timbreSettings[partNum]; + } + currentInstr[0] = 0; + currentInstr[10] = 0; + volume = voltable[102]; //FIXME:KG: Original was just volume=102; I assume this is intended + 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) + stopPedalHold(); + 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() { + // (Re-)cache all the mapped timbres ahead of time + for (unsigned int drumNum = 0; drumNum < 64; drumNum++) { + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 94) + 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 + // FIXME:KG: If I don't have left/right mixed up here, it's pure luck + if (pan < 7) { + drumPan[drumNum].leftvol = 32767; + drumPan[drumNum].rightvol = pan * 4681; + } else { + drumPan[drumNum].rightvol = 32767; + drumPan[drumNum].leftvol = (14 - pan) * 4681; + } + 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() { + 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); +} + +void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { + for (int m = 0; m < 64; 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 Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int RhythmPart::getAbsTimbreNum() const { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm"); + 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); +#if 0 + // Immediately stop all partials on this part (this is apparently *not* the correct behaviour) + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + AbortPoly(poly); + } +#endif + + refresh(); + + allStop(); //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; + for (int i = 0; i < 4; i++) + cache[t].ampEnv.envlevel[i] = (char)((float)cache[t].ampEnv.envlevel[i] * 1.27f); + cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); + float tvelo = ((float)timbre->partial[t].tva.velosens / 100.0f); + float velo = fabs(tvelo-0.5f) * 2.0f; + velo *= 63.0f; + cache[t].ampEnv.velosens = (char)velo; + if (tvelo<0.5f) + cache[t].ampenvdir = 1; + else + cache[t].ampenvdir = 0; + + 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; + cache[t].ampsustain = cache[t].ampEnv.envlevel[3]; + cache[t].amplevel = cache[t].ampEnv.level; + + // Calculate and cache filter stuff + cache[t].filtEnv = timbre->partial[t].tvf; + cache[t].tvfdepth = cache[t].filtEnv.envdkf; + 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 = lfotable[(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::setVolume(int vol) { + volume = voltable[vol]; +} + +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) + if (midiPan < 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = (Bit16s)(midiPan * 512); + } else if (midiPan == 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + } else { + volumesetting.rightvol = 32767; + volumesetting.leftvol = (Bit16s)((127 - midiPan) * 520); + } + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void RhythmPart::playNote(unsigned int key, int vel) { + if (key < 24 || key > 87) { + synth->printDebug("%s: Attempted to play invalid key %d", name, key); + return; + } + int drumNum = key - 24; + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 94) { + 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 Vol %d", name, currentInstr, drumNum, absTimbreNum, vel, key, volume); +#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 Vol %d", name, currentInstr, vel, key, volume); +#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 = &volume; + + 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::allStop() { + 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/sound/softsynth/mt32/part.h b/sound/softsynth/mt32/part.h new file mode 100644 index 0000000000..1214ec52f9 --- /dev/null +++ b/sound/softsynth/mt32/part.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2003-2004 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]; + Bit32u volume; + 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 void playNote(unsigned int key, int vel); + void stopNote(unsigned int key); + void allStop(); + void setVolume(int vol); + virtual void setPan(unsigned int midiPan); + virtual void setBend(unsigned int midiBend); + virtual void setModulation(unsigned int midiModulation); + virtual void setProgram(unsigned int patchNum); + void setHoldPedal(bool pedalval); + void stopPedalHold(); + virtual void refresh(); + virtual void refreshTimbre(unsigned int absTimbreNum); + void setTimbre(TimbreParam *timbre); + virtual unsigned int getAbsTimbreNum() 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[64][4]; + StereoVolume drumPan[64]; +public: + RhythmPart(Synth *synth, unsigned int usePartNum); + void refreshTimbre(unsigned int timbreNum); + void refresh(); + 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/sound/softsynth/mt32/partial.cpp b/sound/softsynth/mt32/partial.cpp new file mode 100644 index 0000000000..32ff9e9af7 --- /dev/null +++ b/sound/softsynth/mt32/partial.cpp @@ -0,0 +1,912 @@ +/* Copyright (c) 2003-2004 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" + +#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]; + } +#endif +} + +int Partial::getOwnerPart() { + return ownerPart; +} + +bool Partial::isActive() { + return ownerPart > -1; +} + +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; + if (keyfollow < -108) + keyfollow = -108; + filtVal = keytable[keyfollow + 108]; + realVal = keytable[(key - MIDDLEC) + 108]; +} + +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 = ¬eLookups[noteVal - LOWEST_NOTE]; +#else + TableInitialiser::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->PCMList, NULL); +#endif + + lfoPos = 0; + pulsewidth = patchCache->pulsewidth + pwveltable[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]; + 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 > 127) { + ampval = 127; + } + + ampval = voltable[ampval]; + int tmpvel; + if (patchCache->ampenvdir == 1) + tmpvel = 127 - poly->vel; + else + tmpvel = poly->vel; + ampval = (ampval * ampveltable[tmpvel][(int)patchCache->ampEnv.velosens]) >> 8; + //if (envs[EnvelopeType_amp].sustaining) + ampEnvVal = ampval; + } + --envs[EnvelopeType_amp].count; + } + + int lfoat = 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 = sintable[lfoatm]; + lfoat = lfoptable[patchCache->lfodepth][lfoatr]; + } + } else { + // Calculate Pitch envelope + envval = getPitchEnvelope(); + int pd = patchCache->pitchEnv.depth; + pitchEnvVal = penvtable[pd][envval]; + } + + int delta; + // These two are only for PCM partials, obviously + PCMWaveEntry *pcmWave = NULL; // Initialise to please compiler + Bit32u pcmAddr = 0; // Initialise to please compiler + + // Wrap positions or end if necessary + if (patchCache->PCMPartial) { + // PCM partial + int len; + pcmWave = &synth->PCMList[patchCache->pcm]; + + delta = noteLookup->wavTable[patchCache->pcm]; + pcmAddr = pcmWave->addr; + 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 = 0x10707; + partialOff.pcmplace %= (Bit16u)(noteLookup->div << 1); + } + + // Build delta for position of next sample + // Fix delta code + Bit64u tdelta = (Bit64u)delta; +#if MT32EMU_ACCURATENOTES == 0 + tdelta = (tdelta * fineShift) >> 12; +#endif + tdelta = (tdelta * pitchEnvVal) >> 12; + tdelta = (tdelta * lfoat) >> 12; + tdelta = (tdelta * bendShift) >> 12; + delta = (int)tdelta; + Bit32u volume = *poly->volumeptr; + + // Get waveform - either PCM or synthesized sawtooth or square + if (ampEnvVal > 0) { + if (patchCache->PCMPartial) { + // Render PCM sample + int ra, rb, dist; + Bit32u taddr; + if (delta < 0x10000) { + // Linear sound interpolation + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->romfile[taddr]; + taddr++; + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + rb = synth->romfile[pcmAddr]; + } else { + rb = 0; + } + } else { + rb = synth->romfile[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->romfile[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->romfile[taddr++]; + } + sample = ra / idelta; + } + } else { + // Render synthesised sample + Bit32u div = noteLookup->div; + int wf = patchCache->waveform; + int toff = partialOff.pcmplace; + int minorplace = partialOff.pcmoffset >> 14; + + Bit32s filtval = getFiltEnvelope(); + + //synth->printDebug("Filtval: %d", filtval); + + if (wf==0) { + // Square waveform. Made by combining two pregenerated bandlimited + // sawtooth waveforms + // Pulse width is not yet correct + if (div == 0) { + synth->printDebug("ERROR: div=0 generating square wave, this should never happen!"); + div = 1; + } + Bit32u ofsA = toff % div; + Bit32u ofsB = toff + FIXEDPOINT_UMULT(div, pulsetable[pulsewidth], 8); + ofsB = ofsB % div; + Bit16s pa = noteLookup->waveforms[0][(ofsA << 2) + minorplace]; + Bit16s pb = noteLookup->waveforms[0][(ofsB << 2) + minorplace]; + sample = (pa - pb) * 4; + // Non-bandlimited squarewave + /* + ofs = ((div << 1) * pulsetable[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]) + sample = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + else + sample = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; + sample = sample * 4; + // 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 % div; + + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(div, pulsetable[patchCache->pulsewidth], 8); + ofs3 = ofs3 % div; + + pa = noteLookup->waveforms[0][ofs]; + pb = noteLookup->waveforms[0][ofs3]; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / WGAMP; + sample = sample *4; + */ + } + + //Very exact filter + if (filtval > ((FILTERGRAN * 15) / 16)) + filtval = ((FILTERGRAN * 15) / 16); + sample = (Bit32s)floor((synth->iirFilter)((float)sample, &history[0], filtcoeff[filtval][(int)patchCache->filtEnv.resonance], patchCache->filtEnv.resonance)); + } + } + + // 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 = smallnoise; + } + + 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) >> 16); + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 16); + mixedBuf++; + } + return true; +} + +Bit32s Partial::getFiltEnvelope() { + int reshigh; + + int cutoff,depth,keyfollow, realfollow; + + EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; + + keyfollow = filtVal; + realfollow = realVal; + + 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 = lasttimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + } else { + tStat->envsize = (envtimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]] * noteLookup->timekeyTable[(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 = (depth * filveltable[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 = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; + } + } else { + // > Bias + if (noteVal > bias) { + dist = noteVal - bias; + cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; + } + + } + //synth->printDebug("Cutoff after %d", cutoff); + } + + depth = (depth * noteLookup->fildepTable[patchCache->tvfdepth]) >> 8; + reshigh = (reshigh * depth) >> 7; + + Bit32s tmp; + + cutoff *= keyfollow; + cutoff /= realfollow; + + reshigh *= keyfollow; + reshigh /= realfollow; + + 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 = (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; + + switch(tStat->envstat) { + case 0: + //Spot for velocity time follow + //Only used for first attack + tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * veltkeytable[(int)patchCache->ampEnv.envvkf][poly->vel]) >> 8; + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 3: + // Final attack envelope uses same time table as the decay + //tStat->envsize = decaytimetable[patchCache->ampEnv.envtime[tStat->envstat]]; + tStat->envsize = lasttimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]]; + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 4: + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampsustain; + if (!poly->sustain) + startDecay(EnvelopeType_amp, tc); + else + tStat->sustaining = true; + + goto PastCalc; + default: + //Spot for timekey follow + //Only used in subsquent envelope parameters, including the decay + tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * noteLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; + + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + } + + tStat->envsize++; + tStat->envdist = patchCache->ampEnv.envlevel[tStat->envstat] - tStat->envbase; + + 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->amplevel) >> 7; + } + + // 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 = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; + } + } else { + // > Bias + if (noteVal > bias) { + int dist = noteVal - bias; + tc = (tc * ampbiastable[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]; + tStat->envsize = (envtimetable[(int)patchCache->pitchEnv.time[tStat->envstat]] * noteLookup->timekeyTable[(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 = (decaytimetable[(int)patchCache->ampEnv.envtime[4]] * noteLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; + tStat->envdist = -startval; + break; + case EnvelopeType_filt: + tStat->envsize = (decaytimetable[(int)patchCache->filtEnv.envtime[4]] * noteLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; + tStat->envdist = -startval; + break; + case EnvelopeType_pitch: + tStat->envsize = (decaytimetable[(int)patchCache->pitchEnv.time[3]] * noteLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8 ; + tStat->envdist = patchCache->pitchEnv.level[4] - startval; + break; + default: + break; + } + tStat->envsize++; +} diff --git a/sound/softsynth/mt32/partial.h b/sound/softsynth/mt32/partial.h new file mode 100644 index 0000000000..0834a64c15 --- /dev/null +++ b/sound/softsynth/mt32/partial.h @@ -0,0 +1,142 @@ +/* Copyright (c) 2003-2004 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]; + + bool play; + + // Keyfollowed note value +#if MT32EMU_ACCURATENOTES == 1 + NoteLookup noteLookupStorage; + float noteVal; +#else + int noteVal; + int fineShift; +#endif + const NoteLookup *noteLookup; // LUTs for this noteVal + + // Keyfollowed filter values + int realVal; + int filtVal; + + EnvelopeStatus envs[3]; + + 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; + PatchCache cachebackup; + + Partial *pair; + bool alreadyOutputed; + Bit32u age; + + Partial(Synth *synth); + ~Partial(); + + int getOwnerPart(); + 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/sound/softsynth/mt32/partialManager.cpp b/sound/softsynth/mt32/partialManager.cpp new file mode 100644 index 0000000000..776276edda --- /dev/null +++ b/sound/softsynth/mt32/partialManager.cpp @@ -0,0 +1,266 @@ +/* Copyright (c) 2003-2004 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 <memory.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; +} diff --git a/sound/softsynth/mt32/partialManager.h b/sound/softsynth/mt32/partialManager.h new file mode 100644 index 0000000000..ee145d4c9f --- /dev/null +++ b/sound/softsynth/mt32/partialManager.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2003-2004 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]); +}; + +} + +#endif diff --git a/sound/softsynth/mt32/structures.h b/sound/softsynth/mt32/structures.h new file mode 100644 index 0000000000..b04d98b0fd --- /dev/null +++ b/sound/softsynth/mt32/structures.h @@ -0,0 +1,282 @@ +/* Copyright (c) 2003-2004 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; + +#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; // 0-1 (SQU/SAW) + 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 { + struct PatchTemp { + PatchParam patch; + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u dummyv[6]; + } MT32EMU_ALIGN_PACKED patchSettings[8]; + + 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 rhythmSettings[86]; // FIXME: Was 64, but let's support the next model... + + TimbreParam MT32EMU_ALIGN_PACKED timbreSettings[8]; + + PatchParam MT32EMU_ALIGN_PACKED patches[128]; + + struct PaddedTimbre { + TimbreParam timbre; + Bit8u padding[10]; + } MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 30]; // 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 system; +}; + +struct MemBanks { + Bit8u pTemp[8][sizeof(MemParams::PatchTemp)]; + Bit8u rTemp[86][sizeof(MemParams::RhythmTemp)]; + Bit8u tTemp[8][sizeof(TimbreParam)]; + Bit8u patchBank[128][sizeof(PatchParam)]; + Bit8u timbreBank[64 + 64 + 64 + 30][sizeof(MemParams::PaddedTimbre)]; + Bit8u systemBank[sizeof(MemParams::SystemArea)]; + // System memory 0x100000 + // Display 0x200000 + // Reset 0x7F0000 +}; + +#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 ampenvdir; + int amplevel; + int tvfdepth; + + bool useBender; + float benderRange; // 0.0, 1.0, .., 24.0 (semitones) + + TimbreParam::partialParam::envParam pitchEnv; + TimbreParam::partialParam::tvaParam ampEnv; + TimbreParam::partialParam::tvfParam filtEnv; + + Bit32s ampsustain; + 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/sound/softsynth/mt32/synth.cpp b/sound/softsynth/mt32/synth.cpp new file mode 100644 index 0000000000..1ba543b581 --- /dev/null +++ b/sound/softsynth/mt32/synth.cpp @@ -0,0 +1,1048 @@ +/* Copyright (c) 2003-2004 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 <errno.h> + +#include "mt32emu.h" + +namespace MT32Emu { + +const int MAX_SYSEX_SIZE = 512; + +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { + 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; + + output *= ResonInv[revLevel]; + + 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(noteLookups, 0, sizeof(noteLookups)); + 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, sizeof(controlROMData)) == sizeof(controlROMData)); + + closeFile(file); + return rc; +} + +bool Synth::loadPCMROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = true; + for (int i = 0; ; 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("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 encoded in dB, convert to 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; + + romfile[i] = (Bit16s)vol; + + } + closeFile(file); + return rc; +} + +struct ControlROMPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +void Synth::initPCMList() { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[0x3000]; + for (int i = 0; i < 128; 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; + //FIXME:KG: Pick a number, any number. The one below sounded best to me in listening tests, but needs to be confirmed. + double STANDARDFREQ = 432.1; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4096.0 - 9.0 / 12.0)); + //printDebug("%f,%d,%d", pTune, tps[i].pitchCoarse, tps[i].pitchFine); + PCMList[i].addr = rAddr; + PCMList[i].len = rLen; + PCMList[i].loop = rLoop; + PCMList[i].tune = rTune; + } +} + +void Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem) { + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + memcpy(&timbre->common, mem, 14); + mem += 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) { + memcpy(&timbre->partial[t], mem, 58); + mem += 58; + } + } +} + +void Synth::initRhythmTimbres() { + const Bit8u *drumMap = &controlROMData[0x3200]; + int timbreNum = 192; + for (Bit16u i = 0; i < 60; i += 2) { + Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; + initRhythmTimbre(timbreNum++, &controlROMData[address]); + } +} + +void Synth::initTimbres(Bit16u mapAddress, int startTimbre) { + for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + address = address + (mapAddress - 0x8000); + TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; + memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); + } +} + +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)); + for (int i = 128; i < 192; i++) { + // If something sets a patch to point to an uninitialised memory timbre, don't play anything + mt32ram.timbres[i].timbre.common.pmute = 0; + } + + printDebug("Loading Control ROM"); + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, &errno); + return false; + } + + printDebug("Loading PCM ROM"); + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, &errno); + return false; + } + + partialManager = new PartialManager(this); + + printDebug("Initialising PCM List"); + initPCMList(); + + printDebug("Initialising Timbre Bank A"); + initTimbres(0x8000, 0); + + printDebug("Initialising Timbre Bank B"); + initTimbres(0xC000, 64); + + printDebug("Initialising Timbre Bank R"); + initRhythmTimbres(); + + printDebug("Initialising Rhythm Temp"); + memcpy(mt32ram.rhythmSettings, &controlROMData[0x741C], 344); + + 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"); + //FIXME: Confirm that these are all correct + // The MT-32 manual claims that "Standard pitch" is 442Hz. + // I assume they mean this is the MT-32 default pitch, and not concert pitch, + // since the latter has been internationally defined as 440Hz for decades. + // Regardless, I'm setting the default masterTune to 440Hz + mt32ram.system.masterTune = 0x40; + mt32ram.system.reverbMode = 0; + mt32ram.system.reverbTime = 5; + mt32ram.system.reverbLevel = 3; + memcpy(mt32ram.system.reserveSettings, &controlROMData[0x57E5], 9); + 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; + if (!refreshSystem()) + return false; + + for (int i = 0; i < 8; i++) { + mt32ram.patchSettings[i].outlevel = 80; + mt32ram.patchSettings[i].panpot = controlROMData[0x5800 + i]; + memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); + parts[i] = new Part(this, i); + parts[i]->setProgram(controlROMData[0x57EE + 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; + + TableInitialiser::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; + } + isOpen = false; +} + +void Synth::playMsg(Bit32u msg) { + unsigned char code = (unsigned char)((msg & 0xf0) >> 4); + unsigned char chan = (unsigned char)(msg & 0xf); + unsigned char note = (unsigned char)((msg & 0xff00) >> 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 + //if (part!=3) return; + //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]->setVolume(velocity); + break; + case 0x40: // Hold pedal + //printDebug("Hold pedal set: %d", velocity); + parts[part]->setHoldPedal(velocity>=64); + break; + + case 0x79: // Reset all controllers + printDebug("Reset all controllers (NYI)"); + break; + + case 0x7B: // All notes off + //printDebug("All notes off"); + parts[part]->allStop(); + 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 < 3) { + printDebug("playSysex: Message is too short for sysex (%d bytes)", len); + } + if (sysex[0] != 0xf0) { + printDebug("playSysex: Message lacks start-of-sysex (0xf0)"); + return; + } + if (sysex[len - 1] != 0xf7) { + printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); + return; + } + playSysexWithoutFraming(sysex + 1, len - 2); +} + +void Synth::playSysexWithoutFraming(const Bit8u * sysex, Bit32u len) { + if (len < 4) { + printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); + return; + } + if (sysex[0] != 0x41) { + 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] == 0x14) { + printDebug("playSysexWithoutFraming: Header is intended for Roland 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] != 0x16) { + printDebug("playSysexWithoutFraming: Header not intended for MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + if (sysex[3] != 0x12) { + printDebug("playSysexWithoutFraming: Unsupported command %02x", sysex[3]); + return; + } + playSysexWithoutHeader(sysex[1], sysex + 4, len - 4); +} + +// MEMADDR() converts from sysex-padded, SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that int code and output +#define MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) + +#define NUMTOUCHED(x,y) (((x) + sizeof(y) - 1) / sizeof(y)) + +void Synth::playSysexWithoutHeader(unsigned char device, 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 + Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); + addr = MEMADDR(addr); + sysex += 3; + len -= 3; + //printDebug("Sysex addr: 0x%06x", SYSEXMEMADDR(addr)); + // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + if (device < 0x10) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, SYSEXMEMADDR(addr)); + if (/*addr >= MEMADDR(0x000000) && */addr < 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 += MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MEMADDR(0x020000)) { + addr += MEMADDR(0x030110) - MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < 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 += MEMADDR(0x040000) - MEMADDR(0x020000) + offset; + } else { + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, SYSEXMEMADDR(addr)); + return; + } + } + if (addr >= MEMADDR(0x030000) && addr < MEMADDR(0x030110)) { + int off = addr - MEMADDR(0x030000); + if (off + len > sizeof(mt32ram.patchSettings)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstPart = off / sizeof(MemParams::PatchTemp); + off %= sizeof(MemParams::PatchTemp); + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.patchSettings[firstPart])[off + m] = sysex[m]; + //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); + + int lastPart = firstPart + NUMTOUCHED(off + len, MemParams::PatchTemp) - 1; + for (int i = firstPart; i <= lastPart; 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)", firstPart, lastPart, off, off + len, i, absTimbreNum, timbreName); + if (parts[i] != NULL) { + if (i == firstPart && off > 2) { + printDebug(" (Not updating timbre, since those values weren't touched)"); + } else { + // Not sure whether we should do this at all, really. + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); + } + parts[i]->refresh(); + } + } + } else if (addr >= MEMADDR(0x030110) && addr < MEMADDR(0x040000)) { + int off = addr - MEMADDR(0x030110); + if (off + len > sizeof(mt32ram.rhythmSettings)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstDrum = off / sizeof(MemParams::RhythmTemp); + off %= sizeof(MemParams::RhythmTemp); + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.rhythmSettings[firstDrum])[off + m] = sysex[m]; + int lastDrum = firstDrum + NUMTOUCHED(off + len, MemParams::RhythmTemp) - 1; + for (int i = firstDrum; i <= lastDrum; 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)", firstDrum, lastDrum, 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(); + } + } else if (addr >= MEMADDR(0x040000) && addr < MEMADDR(0x050000)) { + int off = addr - MEMADDR(0x040000); + if (off + len > sizeof(mt32ram.timbreSettings)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstPart = off / sizeof(TimbreParam); + off %= sizeof(TimbreParam); + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbreSettings[firstPart])[off + m] = sysex[m]; + int lastPart = firstPart + NUMTOUCHED(off + len, TimbreParam) - 1; + for (int i = firstPart; i <= lastPart; 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)", firstPart, lastPart, off, off + len, i, instrumentName); + if (parts[i] != NULL) { + parts[i]->refresh(); + } + } + } + else if (addr >= MEMADDR(0x050000) && addr < MEMADDR(0x060000)) { + int off = addr - MEMADDR(0x050000); + if (off + len > sizeof(mt32ram.patches)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstPatch = off / sizeof(PatchParam); + off %= sizeof(PatchParam); + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.patches[firstPatch])[off + m] = sysex[m]; + int lastPatch = firstPatch + NUMTOUCHED(off + len, PatchParam) - 1; + for (int i = firstPatch; i <= lastPatch; 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", firstPatch, lastPatch, 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(); + } + } + } + */ + } + } else if (addr >= MEMADDR(0x080000) && addr < MEMADDR(0x090000)) { + // Timbres + int off = addr - MEMADDR(0x080000); + if (off + len > sizeof(MemParams::PaddedTimbre) * 64) { + // You can only write to one group at a time + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + unsigned int firstTimbre = off / sizeof (MemParams::PaddedTimbre); + off %= sizeof (MemParams::PaddedTimbre); + firstTimbre += 128; + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbres[firstTimbre])[off + m] = sysex[m]; + unsigned int lastTimbre = firstTimbre + NUMTOUCHED(len + off, MemParams::PaddedTimbre) - 1; + for (unsigned int i = firstTimbre; i <= lastTimbre; 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\"", firstTimbre, lastTimbre, 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); + } + } + } + } else if (addr >= MEMADDR(0x100000) && addr < MEMADDR(0x200000)) { + int off = addr - MEMADDR(0x100000); + if (off + len > sizeof(mt32ram.system)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.system)[m + off] = sysex[m]; + + report(ReportType_devReconfig, NULL); + + printDebug("WRITE-SYSTEM:"); + refreshSystem(); + } else if (addr == MEMADDR(0x200000)) { + char buf[MAX_SYSEX_SIZE]; + if (len > MAX_SYSEX_SIZE - 1) { + printDebug("WRITE-LCD sysex length (%d) exceeded MAX_SYSEX_SIZE (%d) - 1; truncating", len, MAX_SYSEX_SIZE); + len = MAX_SYSEX_SIZE - 1; + } + memcpy(&buf, &sysex[0], len); + buf[len] = 0; + printDebug("WRITE-LCD: %s", buf); + report(ReportType_lcdMessage, buf); + } else if (addr >= MEMADDR(0x7f0000)) { + printDebug("Reset"); + report(ReportType_devReset, NULL); + partialManager->deactivateAll(); + mt32ram = mt32default; + for (int i = 0; i < 9; i++) { + parts[i]->refresh(); + } + isEnabled = false; + } else { + printDebug("Sysex write to unrecognised address %06x", SYSEXMEMADDR(addr)); + } +} + +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]->allStop(); + } 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 * 327); + if (!TableInitialiser::initMT32Tables(this, PCMList, (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) { + Bit32u m; + + partialManager->ageAll(); + + if (myProp.useReverb) { + bool hasOutput = false; + 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); + hasOutput = true; + } + } + } + // No point in doing reverb on a mute buffer... + if (hasOutput) { + 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 +} + +} diff --git a/sound/softsynth/mt32/synth.h b/sound/softsynth/mt32/synth.h new file mode 100644 index 0000000000..1501593cbd --- /dev/null +++ b/sound/softsynth/mt32/synth.h @@ -0,0 +1,200 @@ +/* Copyright (c) 2003-2004 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 { + +const int ROMSIZE = 512 * 1024; +const int PCMSIZE = ROMSIZE / 2; +const int GRAN = 512; + +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, int revLevel); + +class Synth { +friend class Part; +friend class RhythmPart; +friend class Partial; +friend class TableInitialiser; +private: + bool isEnabled; + + iir_filter_type iirFilter; + + PCMWaveEntry PCMList[128]; + + Bit8u controlROMData[64 * 1024]; + Bit16s romfile[PCMSIZE + GRAN]; + Bit8s chantable[32]; + + #if MT32EMU_MONITOR_PARTIALS == 1 + static Bit32s samplepos = 0; + #endif + + 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 playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); + void playSysexWithoutHeader(unsigned char channel, const Bit8u *sysex, Bit32u len); + + 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); + + void initPCMList(); + void initRhythmTimbres(); + void initTimbres(Bit16u mapAddress, int startTimbre); + void initRhythmTimbre(int drumNum, const Bit8u *mem); + 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); + + // 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); + + // 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); +}; + +} + +#endif diff --git a/sound/softsynth/mt32/tables.cpp b/sound/softsynth/mt32/tables.cpp new file mode 100644 index 0000000000..4a17165733 --- /dev/null +++ b/sound/softsynth/mt32/tables.cpp @@ -0,0 +1,729 @@ +/* Copyright (c) 2003-2004 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" + +#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) + +namespace MT32Emu { + +//Amplitude time velocity follow exponential coefficients +const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +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}; + +static float initialisedSampleRate = 0.0f; +static float initialisedMasterTune = 0.0f; + +Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; + +// Some optimization stuff +Bit32s keytable[217]; +Bit16s sintable[65536]; +Bit32u lfotable[101]; +Bit32s penvtable[16][101]; +Bit32s filveltable[128][101]; +Bit32s veltkeytable[5][128]; +Bit32s pulsetable[101]; +Bit32s pulseoffset[101]; +Bit32s ampbiastable[13][128]; +Bit32s fbiastable[15][128]; +float filtcoeff[FILTERGRAN][31][8]; +Bit32s finetable[201]; +Bit32u lfoptable[101][101]; +Bit32s ampveltable[128][64]; +Bit32s pwveltable[15][128]; +Bit32s envtimetable[101]; +Bit32s decaytimetable[101]; +Bit32s lasttimetable[101]; +Bit32s voltable[128]; +float ResonFactor[31]; +float ResonInv[31]; + +NoteLookup noteLookups[NUM_NOTES]; + +// 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; +} + +static void initFiltCoeff(float samplerate) { + for (int j = 0; j < FILTERGRAN; j++) { + for (int res = 0; res < 31; res++) { + float tres = ResonFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtcoeff[j][res], tres); + } + } +} + +static void initEnvelopes(float samplerate) { + for (int lf = 0; lf <= 100; lf++) { + float elf = (float)lf; + + // General envelope + float logtime = elf * 0.088362939f; + envtimetable[lf] = (int)((exp(logtime)/312.12) * (float)samplerate); + + // Decay envelope -- shorter for some reason + // This is also the timing for the envelope right before the + // amp and filter envelope sustains + + lasttimetable[lf] = decaytimetable[lf] = (int)((exp(logtime)/(312.12*2)) * (float)samplerate); + //lasttimetable[lf] = (int)((exp(logtime)/(312.12*6)) * (float)samplerate); + + float mv = (float)lf / 100.0f; + float pt = mv - 0.5f; + if (pt < 0) + pt = 0; + + pulsetable[lf] = (int)(pt * 215.04f) + 128; + + // I am certain of this: Verified by hand LFO log + lfotable[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f,(float)lf) * 0.021236044f)); + + //LOG(LOG_ERROR|LOG_MISC,"lf %d = lfo %d pulsetable %d", lf, lfotable[lf], pulsetable[lf]); + } +} + +void TableInitialiser::initMT32ConstantTables(Synth *synth) { + if (initialisedSampleRate > 0.0f) { + return; + } + int lf; + synth->printDebug("Initialising Pitch Tables"); + for (lf = -108; lf <= 108; lf++) { + keytable[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + //synth->printDebug("KT %d = %d", f, keytable[f+108]); + } + + int res; + float fres; + for (res = 0; res < 31; res++) { + fres = (float)res / 30.0f; + ResonFactor[res] = (powf(2.0f, logf(powf(fres, 16.0f))) * 2.5f) + 1.0f; + ResonInv[res] = 1 / ResonFactor[res]; + } + + 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; + veltkeytable[dep][velt] = (int)tempdep; + //if ((velt % 16) == 0) { + // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); + //} + } else + veltkeytable[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; + pwveltable[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) + filveltable[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)); + filveltable[velt][dep] = (int)(fbase * 256.0); + } + //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); + } + } + + for (lf = 0; lf <= 200; lf++) { + //FIXME:KG: I'm fairly sure this is wrong... lf=100 should yield no fine-tuning (4096)? + finetable[lf] = (int)((powf(2.0f, (((float)lf / 200.0f) - 1.0f) / 12.0f)) * 4096.0f); + + // FIXME:KG: This now gives a range of -1 .. 1 semitone. Should be correct, but check + //finetable[lf] = (int)((powf(2.0f, (((float)lf / 100.0f) - 1.0f) / 12.0f)) * 4096.0f); + } + + float lff; + for (lf = 0; lf < 128; lf++) { + for (velt = 0; velt < 64; velt++) { + lff = 1 - (powf(((128.0f - (float)lf) / 64.0f), 0.25f) * ((float)velt / 96)); + ampveltable[lf][velt] = (int)(lff * 256.0); + //synth->printDebug("Ampveltable: %d, %d = %d", lf, velt, ampveltable[lf][velt]); + } + } + + for (lf = 0; lf < 128; lf++) { + // Converts MIDI velocity to volume. + voltable[lf] = FIXEDPOINT_MAKE(powf((float)lf / 127.0f, FLOAT_LN), 7); + } + for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { + int myRand; + myRand = rand(); + int origRand = myRand; + //myRand = ((myRand - 16383) * WGAMP) >> 16; + // This one is slower but works with all values of RAND_MAX + myRand = (int)((origRand - RAND_MAX / 2) / (float)RAND_MAX * (WGAMP / 2)); + //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason + smallnoise[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 = expf(0.713619942f * tlf) / 407.4945111f; + + if (depat < 50) + finalval = 4096.0f * powf(2, -lfp); + else + finalval = 4096.0f * powf(2, lfp); + pval = (int)finalval; + + penvtable[lf][depat] = pval; + //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, tlf, lfp); + } else { + penvtable[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; + + lfoptable[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; + ampbiastable[lf][distval] = 256; + } else { + amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; + dval = ((128.0f - (float)distval) / 128.0f); + amplog = expf(amplog); + dval = powf(amplog, dval) / amplog; + ampbiastable[lf][distval] = (int)(dval * 256.0); + } + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, ampbiastable[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; + fbiastable[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 = expf(amplog); + dval = powf(amplog,dval)/amplog; + if (lf < 8) { + fbiastable[lf][distval] = (int)(dval * 256.0f); + } else { + dval = powf(dval, 0.3333333f); + if (dval < 0.01f) + dval = 0.01f; + dval = 1 / dval; + fbiastable[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, fbiastable[lf][distval],amplog); + } + } +} + +// Per-note table initialisation follows + +static void initSaw(NoteLookup *noteLookup, Bit32s div) { + for (int rsaw = 0; rsaw <= 100; rsaw++) { + float fsaw; + if (rsaw < 50) + fsaw = 50.0f; + else + fsaw = (float)rsaw; + int tmpdiv = div << 17; + + //(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(NoteLookup *noteLookup, float f) { + for (int dep = 0; dep < 5; dep++) { + if (dep == 0) { + noteLookup->fildepTable[dep] = 256; + noteLookup->timekeyTable[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; + noteLookup->fildepTable[dep] = (int)tempdep; + + ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); + noteLookup->timekeyTable[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]); +} + +File *TableInitialiser::initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div, File *file) { + int iDiv = (int)div; + noteLookup->waveformSize[0] = iDiv << 2; + noteLookup->waveformSize[1] = iDiv << 2; + noteLookup->waveformSize[2] = iDiv << 3; + 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 / (div * 2.0); + + for (int fa = 0; fa < (iDiv << 2); fa++) { + double sa = fa * sd; + +#if 0 + //FIXME:KG: Credit Timo Strunk (bastardo on #scummvm) for help with this! + double saw = 0.5 * DOUBLE_PI - sa / 2; +#else + double saw = 0.0; + for (int sinus = 1; sinus < div; sinus++) { + double fsinus = (double)sinus; + saw += sin(fsinus * sa) / fsinus; + } +#endif + + // This works pretty well + noteLookup->waveforms[0][fa] = (Bit16s)(saw * -ampsize / 2); + noteLookup->waveforms[1][fa] = (Bit16s)(cos(sa / 2.0) * -ampsize); + noteLookup->waveforms[2][fa * 2] = (Bit16s)(cos(sa - DOUBLE_PI) * -ampsize); + noteLookup->waveforms[2][fa * 2 + 1] = (Bit16s)(cos((sa + (sd / 2)) - DOUBLE_PI) * -ampsize); + } + } + 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 - 0); + if (tfadd < 0) + tfadd = 0; + + float freqsum = expf((cfmult + tfadd) / 30.0f) / 4.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 *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry pcmWaves[128], File *file) { + float ampsize = WGAMP; + float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); + float div = rate / freq; + noteLookup->div = (int)div; + + if (noteLookup->div == 0) + noteLookup->div = 1; + + initSaw(noteLookup, noteLookup->div); + initDep(noteLookup, note); + + //synth->printDebug("Note %f; freq=%f, div=%f", note, freq, rate / freq); + file = initWave(synth, noteLookup, ampsize, div, file); + + // Create the pitch tables + + double rateMult = 32000.0 / rate; + double tuner = freq * 65536.0f; + for (int pc = 0; pc < 128; pc++) { + noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); + } + + initFiltTable(noteLookup, freq, rate); + initNFiltTable(noteLookup, freq, rate); + return file; +} + +bool TableInitialiser::initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], 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, 3}; + 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) - 1); + 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 TableInitialiser::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; + } + } + } + initialisedMasterTune = 0.0f; +} + +bool TableInitialiser::initMT32Tables(Synth *synth, PCMWaveEntry pcmWaves[128], 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); + } + if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { + freeNotes(); + if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { + return false; + } + initialisedSampleRate = sampleRate; + initialisedMasterTune = masterTune; + } + return true; +} + +} diff --git a/sound/softsynth/mt32/tables.h b/sound/softsynth/mt32/tables.h new file mode 100644 index 0000000000..0c85796f4f --- /dev/null +++ b/sound/softsynth/mt32/tables.h @@ -0,0 +1,101 @@ +/* Copyright (c) 2003-2004 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; + +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. Do 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 + +// Amplitude of waveform generator +const int WGAMP = 7168; // 8192? + +class Synth; + +extern Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; + +// Some optimization stuff +extern Bit32s keytable[217]; +extern Bit16s sintable[65536]; +extern Bit32u lfotable[101]; +extern Bit32s penvtable[16][101]; +extern Bit32s filveltable[128][101]; +extern Bit32s veltkeytable[5][128]; +extern Bit32s pulsetable[101]; +extern Bit32s ampbiastable[13][128]; +extern Bit32s fbiastable[15][128]; +extern float filtcoeff[FILTERGRAN][31][8]; +extern Bit32s finetable[201]; +extern Bit32u lfoptable[101][101]; +extern Bit32s ampveltable[128][64]; +extern Bit32s pwveltable[15][128]; +extern Bit32s envtimetable[101]; +extern Bit32s decaytimetable[101]; +extern Bit32s lasttimetable[101]; +extern Bit32s voltable[128]; +extern float ResonInv[31]; + +struct NoteLookup { + Bit32u div; + Bit32u wavTable[128]; + Bit32s sawTable[101]; + Bit32s fildepTable[5]; + Bit32s timekeyTable[5]; + int filtTable[2][201]; + int nfiltTable[101][101]; + Bit16s *waveforms[3]; + Bit32u waveformSize[3]; +}; + +extern NoteLookup noteLookups[NUM_NOTES]; + +class TableInitialiser { + static void initMT32ConstantTables(Synth *synth); + static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div, File *file); + static bool initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float rate, float tuning); +public: + static bool initMT32Tables(Synth *synth, PCMWaveEntry pcmWaves[128], float sampleRate, float masterTune); + static File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry pcmWaves[128], File *file); + static void freeNotes(); +}; + +} + +#endif diff --git a/sound/softsynth/ym2612.cpp b/sound/softsynth/ym2612.cpp new file mode 100644 index 0000000000..b9ad9b35ca --- /dev/null +++ b/sound/softsynth/ym2612.cpp @@ -0,0 +1,907 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001-2004 The ScummVM project + * + * YM2612 tone generation code written by Tomoaki Hayasaka. + * Used under the terms of the GNU General Public License. + * Adpated to ScummVM by Jamieson Christian. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + */ + +#include "sound/softsynth/emumidi.h" + +#include <math.h> + + +//////////////////////////////////////// +// +// Miscellaneous +// +//////////////////////////////////////// + +static int *sintbl = 0; +static int *powtbl = 0; +static int *frequencyTable = 0; +static int *keycodeTable = 0; +static int *keyscaleTable = 0; +static int *attackOut = 0; + +//////////////////////////////////////// +// +// Class declarations +// +//////////////////////////////////////// + +class Operator2612; +class Voice2612; +class MidiChannel_YM2612; +class MidiDriver_YM2612; + +class Operator2612 { +protected: + Voice2612 *_owner; + enum State { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing }; + State _state; + int32 _currentLevel; + int _frequency; + uint32 _phase; + int _lastOutput; + int _feedbackLevel; + int _detune; + int _multiple; + int32 _totalLevel; + int _keyScale; + int _velocity; + int _specifiedTotalLevel; + int _specifiedAttackRate; + int _specifiedDecayRate; + int _specifiedSustainLevel; + int _specifiedSustainRate; + int _specifiedReleaseRate; + int _tickCount; + int _attackTime; + int32 _decayRate; + int32 _sustainLevel; + int32 _sustainRate; + int32 _releaseRate; + +public: + Operator2612 (Voice2612 *owner); + ~Operator2612(); + void feedbackLevel(int level); + void setInstrument(byte const *instrument); + void velocity(int velo); + void keyOn(); + void keyOff(); + void frequency(int freq); + void nextTick(const int *phaseShift, int *outbuf, int buflen); + bool inUse() { return (_state != _s_ready); } +}; + +class Voice2612 { +public: + Voice2612 *next; + uint16 _rate; + +protected: + Operator2612 *_opr[4]; + int _velocity; + int _control7; + int _note; + int _frequencyOffs; + int _frequency; + int _algorithm; + + int *_buffer; + int _buflen; + +public: + Voice2612(); + ~Voice2612(); + void setControlParameter(int control, int value); + void setInstrument(byte const *instrument); + void velocity(int velo); + void nextTick(int *outbuf, int buflen); + void noteOn(int n, int onVelo); + bool noteOff(int note); + void pitchBend(int value); + void recalculateFrequency(); +}; + +class MidiChannel_YM2612 : public MidiChannel { +protected: + uint16 _rate; + Voice2612 *_voices; + Voice2612 *_next_voice; + +public: + void removeAllVoices(); + void nextTick(int *outbuf, int buflen); + void rate(uint16 r); + +public: + MidiChannel_YM2612(); + virtual ~MidiChannel_YM2612(); + + // MidiChannel interface + MidiDriver *device() { return 0; } + byte getNumber() { return 0; } + void release() { } + void send(uint32 b) { } + void noteOff(byte note); + void noteOn(byte note, byte onVelo); + void programChange(byte program) { } + void pitchBend(int16 value); + void controlChange(byte control, byte value); + void pitchBendFactor(byte value) { } + void sysEx_customInstrument(uint32 type, byte *instr); +}; + +class MidiDriver_YM2612 : public MidiDriver_Emulated { +protected: + MidiChannel_YM2612 *_channel[16]; + + int _next_voice; + int _volume; + +protected: + static void createLookupTables(); + void nextTick(int16 *buf1, int buflen); + int volume(int val = -1) { if (val >= 0) _volume = val; return _volume; } + void rate(uint16 r); + + void generateSamples(int16 *buf, int len); + +public: + MidiDriver_YM2612(SoundMixer *mixer); + virtual ~MidiDriver_YM2612(); + + int open(); + void close(); + void send(uint32 b); + void send(byte channel, uint32 b); // Supports higher than channel 15 + uint32 property(int prop, uint32 param) { return 0; } + + void setPitchBendRange(byte channel, uint range) { } + void sysEx(byte *msg, uint16 length); + + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + + // AudioStream API + bool isStereo() const { return true; } + int getRate() const { return _mixer->getOutputRate(); } +}; + +//////////////////////////////////////// +// +// Operator2612 implementation +// +//////////////////////////////////////// + +Operator2612::Operator2612 (Voice2612 *owner) : + _owner (owner), + _state (_s_ready), + _currentLevel ((int32)0x7f << 15), + _phase (0), + _lastOutput (0), + _feedbackLevel (0), + _detune (0), + _multiple (1), + _keyScale (0), + _specifiedTotalLevel (127), + _specifiedAttackRate (0), + _specifiedDecayRate (0), + _specifiedSustainRate (0), + _specifiedReleaseRate (15) { + velocity(0); +} + +Operator2612::~Operator2612() +{ } + +void Operator2612::velocity(int velo) { + _velocity = velo; + _totalLevel = ((int32)_specifiedTotalLevel << 15) + + ((int32)(127-_velocity) << 13); + _sustainLevel = ((int32)_specifiedSustainLevel << 17); +} + +void Operator2612::feedbackLevel(int level) { + _feedbackLevel = level; +} + +void Operator2612::setInstrument(byte const *instrument) { + _detune = (instrument[8] >> 4) & 7; + _multiple = instrument[8] & 15; + _specifiedTotalLevel = instrument[12] & 127; + _keyScale = (instrument[16] >> 6) & 3; + _specifiedAttackRate = instrument[16] & 31; + _specifiedDecayRate = instrument[20] & 31; + _specifiedSustainRate = instrument[24] & 31; + _specifiedSustainLevel = (instrument[28] >> 4) & 15; + _specifiedReleaseRate = instrument[28] & 15; + _state = _s_ready; // 本物ではどうなのかな? + velocity(_velocity); +} + +void Operator2612::keyOn() { + _state = _s_attacking; + _tickCount = 0; + _phase = 0; // どうも、実際こうらしい + _currentLevel = ((int32)0x7f << 15); // これも、実際こうらしい +} + +void Operator2612::keyOff() { + if (_state != _s_ready) + _state = _s_releasing; +} + +void Operator2612::frequency(int freq) { + double value; // Use for intermediate computations to avoid int64 arithmetic + int r; + + _frequency = freq / _owner->_rate; + + r = _specifiedAttackRate; + if (r != 0) { + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + if (r >= 64) + r = 63; // するべきなんだろうとは思うんだけど (赤p.207) + } + + r = 63 - r; + if (_specifiedTotalLevel >= 128) + value = 0; + else { + value = powtbl[(r&3) << 7]; + value *= 1 << (r >> 2); + value *= 41; // r == 20 のとき、0-96[db] が 10.01[ms] == 41.00096 + value /= 1 << (15 + 5); + value *= 127 - _specifiedTotalLevel; + value /= 127; + } + _attackTime = (int32) value; // 1 秒 == (1 << 12) + if (_attackTime > 0) + _attackTime = (1 << (12+10)) / (_owner->_rate * _attackTime); + + r = _specifiedDecayRate; + if (r != 0) { + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + if (r >= 64) + r = 63; + } + value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; + _decayRate = (int32) value / _owner->_rate; + + r = _specifiedSustainRate; + if (r != 0) { + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + if (r >= 64) + r = 63; + } + value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; + _sustainRate = (int32) value / _owner->_rate; + + r = _specifiedReleaseRate; + if (r != 0) { + r = r * 2 + 1; // (Translated) I cannot know whether the timing is a good choice or not + r = r * 2 + (keyscaleTable[freq/262205] >> (3-_keyScale)); + // KS による補正はあるらしい。赤p.206 では記述されてないけど。 + if (r >= 64) + r = 63; + } + value = (double) powtbl[(r&3) << 7] * (0x10 << (r>>2)) / 31; + _releaseRate = (int32) value / _owner->_rate; +} + +void Operator2612::nextTick(const int *phasebuf, int *outbuf, int buflen) { + if (_state == _s_ready) + return; + if (_state == _s_attacking && _attackTime <= 0) { + _currentLevel = 0; + _state = _s_decaying; + } + + int32 levelIncrement = 0; + int32 target = 0; + State next_state = _s_ready; + const int32 zero_level = ((int32)0x7f << 15); + const int phaseIncrement = (_multiple > 0) ? (_frequency * _multiple) : (_frequency / 2); + + int32 output = _lastOutput; + int32 level = _currentLevel + _totalLevel; + + while (buflen) { + switch (_state) { + case _s_ready: + return; + break; + case _s_attacking: + next_state = _s_attacking; + break; + case _s_decaying: + levelIncrement = _decayRate; + target = _sustainLevel + _totalLevel; + next_state = _s_sustaining; + break; + case _s_sustaining: + levelIncrement = _sustainRate; + target = zero_level + _totalLevel; + next_state = _s_ready; + break; + case _s_releasing: + levelIncrement = _releaseRate; + target = zero_level + _totalLevel; + next_state = _s_ready; + break; + } + + bool switching = false; + do { + if (next_state == _s_attacking) { + // Attack phase + ++_tickCount; + int i = (int) (_tickCount * _attackTime); + if (i >= 1024) { + level = _totalLevel; + _state = _s_decaying; + switching = true; + } else { + level = (attackOut[i] << (31 - 8 - 16)) + _totalLevel; + } + } else { + // Decay, Sustain and Release phases + level += levelIncrement; + if (level >= target) { + level = target; + _state = next_state; + switching = true; + } + } + + if (level < zero_level) { + int phaseShift = *phasebuf >> 2; // 正しい変調量は? 3 じゃ小さすぎで 2 じゃ大きいような。 + if (_feedbackLevel) + phaseShift += (output << (_feedbackLevel - 1)) / 1024; + output = sintbl[((_phase >> 7) + phaseShift) & 0x7ff]; + output >>= (level >> 18); // 正しい減衰量は? + // Here is the original code, which requires 64-bit ints +// output *= powtbl[511 - ((level>>25)&511)]; +// output >>= 16; +// output >>= 1; + // And here's our 32-bit trick for doing it. (Props to Fingolfin!) + // Result varies from original code by max of 1. +// int powVal = powtbl[511 - ((level>>9)&511)]; +// int outputHI = output / 256; +// int powHI = powVal / 256; +// output = (outputHI * powHI) / 2 + (outputHI * (powVal % 256) + powHI * (output % 256)) / 512; + // And here's the even faster code. + // Result varies from original code by max of 8. + output = ((output >> 4) * (powtbl[511-((level>>9)&511)] >> 3)) / 1024; + + _phase += phaseIncrement; + _phase &= 0x3ffff; + } else + output = 0; + + *outbuf += output; + --buflen; + ++phasebuf; + ++outbuf; + } while (buflen && !switching); + } + _lastOutput = output; + _currentLevel = level - _totalLevel; +} + +//////////////////////////////////////// +// +// Voice2612 implementation +// +//////////////////////////////////////// + +Voice2612::Voice2612() { + next = 0; + _control7 = 127; + _note = 40; + _frequency = 440; + _frequencyOffs = 0x2000; + _algorithm = 7; + + _buffer = 0; + _buflen = 0; + + int i; + for (i = 0; i < ARRAYSIZE(_opr); ++i) + _opr[i] = new Operator2612 (this); + velocity(0); +} + +Voice2612::~Voice2612() { + int i; + for (i = 0; i < ARRAYSIZE(_opr); ++i) + delete _opr[i]; + free(_buffer); +} + +void Voice2612::velocity(int velo) { + _velocity = velo; +#if 0 + int v = (velo * _control7) >> 7; // これだと精度良くないですね +#else + int v = velo + (_control7 - 127) * 4; +#endif + bool iscarrier[8][4] = { + { false, false, false, true, }, //0 + { false, false, false, true, }, //1 + { false, false, false, true, }, //2 + { false, false, false, true, }, //3 + { false, true, false, true, }, //4 + { false, true, true, true, }, //5 + { false, true, true, true, }, //6 + { true, true, true, true, }, //7 + }; + int opr; + for (opr = 0; opr < 4; opr++) + if (iscarrier[_algorithm][opr]) + _opr[opr]->velocity(v); + else + _opr[opr]->velocity(127); +} + +void Voice2612::setControlParameter(int control, int value) { + switch (control) { + case 7: + _control7 = value; + velocity(_velocity); + break; + case 123: + // All notes off + noteOff(_note); + }; +} + +void Voice2612::setInstrument(byte const *instrument) { + if (instrument == NULL) + return; + + _algorithm = instrument[32] & 7; + _opr[0]->feedbackLevel((instrument[32] >> 3) & 7); + _opr[1]->feedbackLevel(0); + _opr[2]->feedbackLevel(0); + _opr[3]->feedbackLevel(0); + _opr[0]->setInstrument(instrument + 0); + _opr[1]->setInstrument(instrument + 2); + _opr[2]->setInstrument(instrument + 1); + _opr[3]->setInstrument(instrument + 3); +} + +void Voice2612::nextTick(int *outbuf, int buflen) { + if (_velocity == 0) + return; + + if (_buflen < buflen) { + free(_buffer); + _buflen = buflen; + _buffer = (int *) malloc(sizeof(int) * buflen * 2); + } + + int *buf1 = _buffer; + int *buf2 = _buffer + buflen; + memset(_buffer, 0, sizeof(int) * buflen * 2); + + switch (_algorithm) { + case 0: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, buf1, buflen); + memset (buf2, 0, sizeof (int) * buflen); + _opr[2]->nextTick(buf1, buf2, buflen); + _opr[3]->nextTick(buf2, outbuf, buflen); + break; + case 1: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf1, buf2, buflen); + _opr[2]->nextTick(buf2, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 2: + _opr[1]->nextTick(buf1, buf2, buflen); + _opr[2]->nextTick(buf2, buf1, buflen); + memset(buf2, 0, sizeof(int) * buflen); + _opr[0]->nextTick(buf2, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 3: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, buf1, buflen); + memset(buf2, 0, sizeof(int) * buflen); + _opr[2]->nextTick(buf2, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 4: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, outbuf, buflen); + _opr[2]->nextTick(buf1, buf1, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 5: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, outbuf, buflen); + _opr[2]->nextTick(buf2, outbuf, buflen); + _opr[3]->nextTick(buf2, outbuf, buflen); + break; + case 6: + _opr[0]->nextTick(buf1, buf2, buflen); + _opr[1]->nextTick(buf2, outbuf, buflen); + _opr[2]->nextTick(buf1, outbuf, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + case 7: + _opr[0]->nextTick(buf1, outbuf, buflen); + _opr[1]->nextTick(buf1, outbuf, buflen); + _opr[2]->nextTick(buf1, outbuf, buflen); + _opr[3]->nextTick(buf1, outbuf, buflen); + break; + }; +} + +void Voice2612::noteOn(int n, int onVelo) { + _note = n; + velocity(onVelo); + recalculateFrequency(); + int i; + for (i = 0; i < ARRAYSIZE(_opr); i++) + _opr[i]->keyOn(); +} + +bool Voice2612::noteOff(int note) { + if (_note != note) + return false; + int i; + for (i = 0; i < ARRAYSIZE(_opr); i++) + _opr[i]->keyOff(); + return true; +} + +void Voice2612::pitchBend(int value) { + _frequencyOffs = value; + recalculateFrequency(); +} + +void Voice2612::recalculateFrequency() { + // MIDI とも違うし.... + // どういう仕様なんだろうか? + // と思ったら、なんと、これ (↓) が正解らしい。 + int32 basefreq = frequencyTable[_note]; + int cfreq = frequencyTable[_note - (_note % 12)]; + int oct = _note / 12; + int fnum = (int) (((double)basefreq * (1 << 13)) / cfreq); // OPL の fnum と同じようなもの。 + fnum += _frequencyOffs - 0x2000; + if (fnum < 0x2000) { + fnum += 0x2000; + oct--; + } + if (fnum >= 0x4000) { + fnum -= 0x2000; + oct++; + } + + // _frequency は最終的にバイアス 256*1024 倍 + _frequency = (int) ((frequencyTable[oct*12] * (double)fnum) / 8); + + int i; + for (i = 0; i < ARRAYSIZE(_opr); i++) + _opr[i]->frequency(_frequency); +} + +//////////////////////////////////////// +// +// MidiChannel_YM2612 +// +//////////////////////////////////////// + +MidiChannel_YM2612::MidiChannel_YM2612() { + _voices = 0; + _next_voice = 0; +} + +MidiChannel_YM2612::~MidiChannel_YM2612() { + removeAllVoices(); +} + +void MidiChannel_YM2612::removeAllVoices() { + if (!_voices) + return; + Voice2612 *last, *voice = _voices; + for (; voice; voice = last) { + last = voice->next; + delete voice; + } + _voices = _next_voice = 0; +} + +void MidiChannel_YM2612::noteOn(byte note, byte onVelo) { + if (!_voices) + return; + _next_voice = _next_voice ? _next_voice : _voices; + _next_voice->noteOn(note, onVelo); + _next_voice = _next_voice->next; +} + +void MidiChannel_YM2612::noteOff(byte note) { + if (!_voices) + return; + if (_next_voice == _voices) + _next_voice = 0; + Voice2612 *voice = _next_voice; + do { + if (!voice) + voice = _voices; + if (voice->noteOff(note)) { + _next_voice = voice; + break; + } + voice = voice->next; + } while (voice != _next_voice); +} + +void MidiChannel_YM2612::controlChange(byte control, byte value) { + // いいのかこれで? + if (control == 121) { + // Reset controller + removeAllVoices(); + } else { + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->setControlParameter(control, value); + } +} + +void MidiChannel_YM2612::sysEx_customInstrument(uint32 type, byte *fmInst) { + if (type != 'EUP ') + return; + Voice2612 *voice = new Voice2612; + voice->next = _voices; + _voices = voice; + voice->_rate = _rate; + voice->setInstrument(fmInst); +} + +void MidiChannel_YM2612::pitchBend(int16 value) { + // いいのかこれで? + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->pitchBend(value); +} + +void MidiChannel_YM2612::nextTick(int *outbuf, int buflen) { + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->nextTick(outbuf, buflen); +} + +void MidiChannel_YM2612::rate(uint16 r) { + _rate = r; + Voice2612 *voice = _voices; + for (; voice; voice = voice->next) + voice->_rate = r; +} + +//////////////////////////////////////// +// +// MidiDriver_YM2612 +// +//////////////////////////////////////// + +MidiDriver_YM2612::MidiDriver_YM2612(SoundMixer *mixer) + : MidiDriver_Emulated(mixer) { + _next_voice = 0; + + createLookupTables(); + _volume = 256; + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + _channel[i] = new MidiChannel_YM2612; + rate(getRate()); +} + +MidiDriver_YM2612::~MidiDriver_YM2612() { + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + delete _channel[i]; + delete sintbl; + delete powtbl; + delete frequencyTable; + delete keycodeTable; + delete keyscaleTable; + delete attackOut; + sintbl = powtbl = frequencyTable = keycodeTable = keyscaleTable = attackOut = 0; +} + +int MidiDriver_YM2612::open() { + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + _mixer->setupPremix(this); + return 0; +} + +void MidiDriver_YM2612::close() { + if (!_isOpen) + return; + _isOpen = false; + + // Detach the premix callback handler + _mixer->setupPremix(0); +} + +void MidiDriver_YM2612::send(uint32 b) { + send(b & 0xF, b & 0xFFFFFFF0); +} + +void MidiDriver_YM2612::send(byte chan, uint32 b) { + //byte param3 = (byte) ((b >> 24) & 0xFF); + byte param2 = (byte) ((b >> 16) & 0xFF); + byte param1 = (byte) ((b >> 8) & 0xFF); + byte cmd = (byte) (b & 0xF0); + if (chan > ARRAYSIZE(_channel)) + return; + + switch (cmd) { + case 0x80:// Note Off + _channel[chan]->noteOff(param1); + break; + case 0x90: // Note On + _channel[chan]->noteOn(param1, param2); + break; + case 0xA0: // Aftertouch + break; // Not supported. + case 0xB0: // Control Change + _channel[chan]->controlChange(param1, param2); + break; + case 0xC0: // Program Change + _channel[chan]->programChange(param1); + break; + case 0xD0: // Channel Pressure + break; // Not supported. + case 0xE0: // Pitch Bend + _channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000); + break; + case 0xF0: // SysEx + // We should never get here! SysEx information has to be + // sent via high-level semantic methods. + warning("MidiDriver_YM2612: Receiving SysEx command on a send() call"); + break; + + default: + warning("MidiDriver_YM2612: Unknown send() command 0x%02X", cmd); + } +} + +void MidiDriver_YM2612::sysEx(byte *msg, uint16 length) { + if (msg[0] != 0x7C || msg[1] >= ARRAYSIZE(_channel)) + return; + _channel[msg[1]]->sysEx_customInstrument('EUP ', &msg[2]); +} + +void MidiDriver_YM2612::generateSamples(int16 *data, int len) { + memset(data, 0, 2 * sizeof(int16) * len); + nextTick(data, len); +} + +void MidiDriver_YM2612::nextTick(int16 *buf1, int buflen) { + int *buf0 = (int *)buf1; + + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + _channel[i]->nextTick(buf0, buflen); + + for (i = 0; i < buflen; ++i) + buf1[i*2+1] = buf1[i*2] = ((buf0[i] * volume()) >> 10) & 0xffff; +} + +void MidiDriver_YM2612::rate(uint16 r) +{ + int i; + for (i = 0; i < ARRAYSIZE(_channel); i++) + _channel[i]->rate(r); +} + +void MidiDriver_YM2612::createLookupTables() { + { + int i; + sintbl = new int [2048]; + for (i = 0; i < 2048; i++) + sintbl[i] = (int)(0xffff * sin(i/2048.0*2.0*PI)); + } + + { + int i; + powtbl = new int [1025]; + for (i = 0; i <= 1024; i++) + powtbl[i] = (int)(0x10000 * pow(2.0, (i-512)/512.0)); + } + + { + int i; + int block; + + static int fnum[] = { + 0x026a, 0x028f, 0x02b6, 0x02df, + 0x030b, 0x0339, 0x036a, 0x039e, + 0x03d5, 0x0410, 0x044e, 0x048f, + }; + + // (int)(880.0 * 256.0 * pow(2.0, (note-0x51)/12.0)); // バイアス 256 倍 + // 0x45 が 440Hz (a4)、0x51 が 880Hz (a5) らしい + frequencyTable = new int [120]; + for (block = -1; block < 9; block++) { + for (i = 0; i < 12; i++) { + double freq = fnum[i] * (166400.0 / 3) * pow(2.0, block-21); + frequencyTable[(block+1)*12+i] = (int)(256.0 * freq); + } + } + + keycodeTable = new int [120]; + // detune 量の計算や KS による rate 変換に使うんじゃないかな + for (block = -1; block < 9; block++) { + for (i = 0; i < 12; i++) { + // see p.204 + int f8 = (fnum[i] >> 7) & 1; + int f9 = (fnum[i] >> 8) & 1; + int f10 = (fnum[i] >> 9) & 1; + int f11 = (fnum[i] >> 10) & 1; + int n4 = f11; + int n3 = f11&(f10|f9|f8) | (~f11&f10&f9&f8); + int note = n4*2 + n3; + // see p.207 + keycodeTable[(block+1)*12+i] = block*4 + note; + } + } + } + + { + int freq; + keyscaleTable = new int [8192]; + keyscaleTable[0] = 0; + for (freq = 1; freq < 8192; freq++) { + keyscaleTable[freq] = (int)(log((double)freq) / 9.03 * 32.0) - 1; + // 8368[Hz] (o9c) で 32くらい。9.03 =:= ln 8368 + } + } + + { + int i; + attackOut = new int [1024]; + for (i = 0; i < 1024; i++) + attackOut[i] = (int)(((0x7fff+0x03a5)*30.0) / (30.0+i)) - 0x03a5; + } +} + +//////////////////////////////////////// +// +// MidiDriver_YM2612 factory +// +//////////////////////////////////////// + +MidiDriver *MidiDriver_YM2612_create(SoundMixer *mixer) { + return new MidiDriver_YM2612(mixer); +} |