diff --git a/nukedopl3.d b/nukedopl3.d new file mode 100644 index 0000000..f401784 --- /dev/null +++ b/nukedopl3.d @@ -0,0 +1,2624 @@ +// Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT) +// +// 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. +// +// +// Nuked OPL3 emulator. +// Thanks: +// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): +// Feedback and Rhythm part calculation information. +// forums.submarine.org.uk(carbon14, opl3): +// Tremolo and phase generator calculation information. +// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): +// OPL2 ROMs. +// +// version: 1.7.4 +/++ + OPL3 (1990's midi chip) emulator. + + License: + GPL2 + Authors: + Originally written in C by Alexey Khokholov, ported to D by ketmar, very slightly modified by me. ++/ +module arsd.nukedopl3 /*is aliced*/; +nothrow @trusted @nogc: + +public: +enum OPL_WRITEBUF_SIZE = 1024; +enum OPL_WRITEBUF_DELAY = 2; + + +// ////////////////////////////////////////////////////////////////////////// // +version(genmidi_dumper) import iv.strex; + + +// ////////////////////////////////////////////////////////////////////////// // +private: +enum OPL_RATE = 49716; + +struct OPL3Slot { + OPL3Channel* channel; + OPL3Chip* chip; + short out_; + short fbmod; + short* mod; + short prout; + short eg_rout; + short eg_out; + ubyte eg_inc; + ubyte eg_gen; + ubyte eg_rate; + ubyte eg_ksl; + ubyte *trem; + ubyte reg_vib; + ubyte reg_type; + ubyte reg_ksr; + ubyte reg_mult; + ubyte reg_ksl; + ubyte reg_tl; + ubyte reg_ar; + ubyte reg_dr; + ubyte reg_sl; + ubyte reg_rr; + ubyte reg_wf; + ubyte key; + uint pg_phase; + uint timer; +} + +struct OPL3Channel { + OPL3Slot*[2] slots; + OPL3Channel* pair; + OPL3Chip* chip; + short*[4] out_; + ubyte chtype; + ushort f_num; + ubyte block; + ubyte fb; + ubyte con; + ubyte alg; + ubyte ksv; + ushort cha, chb; +} + +struct OPL3WriteBuf { + ulong time; + ushort reg; + ubyte data; +} + +/// +public struct OPL3Chip { +private: + OPL3Channel[18] channel; + OPL3Slot[36] slot; + ushort timer; + ubyte newm; + ubyte nts; + ubyte rhy; + ubyte vibpos; + ubyte vibshift; + ubyte tremolo; + ubyte tremolopos; + ubyte tremoloshift; + uint noise; + short zeromod; + int[2] mixbuff; + //OPL3L + int rateratio; + int samplecnt; + short[2] oldsamples; + short[2] samples; + + ulong writebuf_samplecnt; + uint writebuf_cur; + uint writebuf_last; + ulong writebuf_lasttime; + OPL3WriteBuf[OPL_WRITEBUF_SIZE] writebuf; +} + + +private: +enum RSM_FRAC = 10; + +// Channel types + +enum { + ch_2op = 0, + ch_4op = 1, + ch_4op2 = 2, + ch_drum = 3 +} + +// Envelope key types + +enum { + egk_norm = 0x01, + egk_drum = 0x02 +} + + +// +// logsin table +// + +static immutable ushort[256] logsinrom = [ + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +]; + +// +// exp table +// + +static immutable ushort[256] exprom = [ + 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, + 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, + 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, + 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, + 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, + 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, + 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, + 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, + 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, + 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, + 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, + 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, + 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, + 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, + 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, + 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, + 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, + 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, + 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, + 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, + 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, + 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, + 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, + 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, + 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, + 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, + 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, + 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, + 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, + 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, + 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, + 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa +]; + +// +// freq mult table multiplied by 2 +// +// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +// + +static immutable ubyte[16] mt = [ + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 +]; + +// +// ksl table +// + +static immutable ubyte[16] kslrom = [ + 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 +]; + +static immutable ubyte[4] kslshift = [ + 8, 1, 2, 0 +]; + +// +// envelope generator constants +// + +static immutable ubyte[8][4][3] eg_incstep = [ + [ + [ 0, 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 1, 0, 1, 0, 1, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 1, 1 ], + [ 0, 1, 1, 1, 1, 1, 1, 1 ] + ], + [ + [ 1, 1, 1, 1, 1, 1, 1, 1 ], + [ 2, 2, 1, 1, 1, 1, 1, 1 ], + [ 2, 2, 1, 1, 2, 2, 1, 1 ], + [ 2, 2, 2, 2, 2, 2, 1, 1 ] + ] +]; + +static immutable ubyte[16] eg_incdesc = [ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2 +]; + +static immutable byte[16] eg_incsh = [ + 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2 +]; + +// +// address decoding +// + +static immutable byte[0x20] ad_slot = [ + 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +]; + +static immutable ubyte[18] ch_slot = [ + 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 +]; + +// +// Envelope generator +// + +alias envelope_sinfunc = short function (ushort phase, ushort envelope) nothrow @trusted @nogc; +alias envelope_genfunc = void function (OPL3Slot *slott) nothrow @trusted @nogc; + +private short OPL3_EnvelopeCalcExp (uint level) { + if (level > 0x1fff) level = 0x1fff; + return cast(short)(((exprom.ptr[(level&0xff)^0xff]|0x400)<<1)>>(level>>8)); +} + +private short OPL3_EnvelopeCalcSin0 (ushort phase, ushort envelope) { + ushort out_ = 0; + ushort neg = 0; + phase &= 0x3ff; + if (phase&0x200) neg = ushort.max; + if (phase&0x100) out_ = logsinrom.ptr[(phase&0xff)^0xff]; else out_ = logsinrom.ptr[phase&0xff]; + return OPL3_EnvelopeCalcExp(out_+(envelope<<3))^neg; +} + +private short OPL3_EnvelopeCalcSin1 (ushort phase, ushort envelope) { + ushort out_ = 0; + phase &= 0x3ff; + if (phase&0x200) out_ = 0x1000; + else if (phase&0x100) out_ = logsinrom.ptr[(phase&0xff)^0xff]; + else out_ = logsinrom.ptr[phase&0xff]; + return OPL3_EnvelopeCalcExp(out_+(envelope<<3)); +} + +private short OPL3_EnvelopeCalcSin2 (ushort phase, ushort envelope) { + ushort out_ = 0; + phase &= 0x3ff; + if (phase&0x100) out_ = logsinrom.ptr[(phase&0xff)^0xff]; else out_ = logsinrom.ptr[phase&0xff]; + return OPL3_EnvelopeCalcExp(out_+(envelope<<3)); +} + +private short OPL3_EnvelopeCalcSin3 (ushort phase, ushort envelope) { + ushort out_ = 0; + phase &= 0x3ff; + if (phase&0x100) out_ = 0x1000; else out_ = logsinrom.ptr[phase&0xff]; + return OPL3_EnvelopeCalcExp(out_+(envelope<<3)); +} + +private short OPL3_EnvelopeCalcSin4 (ushort phase, ushort envelope) { + ushort out_ = 0; + ushort neg = 0; + phase &= 0x3ff; + if ((phase&0x300) == 0x100) neg = ushort.max; + if (phase&0x200) out_ = 0x1000; + else if (phase&0x80) out_ = logsinrom.ptr[((phase^0xff)<<1)&0xff]; + else out_ = logsinrom.ptr[(phase<<1)&0xff]; + return OPL3_EnvelopeCalcExp(out_+(envelope<<3))^neg; +} + +private short OPL3_EnvelopeCalcSin5 (ushort phase, ushort envelope) { + ushort out_ = 0; + phase &= 0x3ff; + if (phase&0x200) out_ = 0x1000; + else if (phase&0x80) out_ = logsinrom.ptr[((phase^0xff)<<1)&0xff]; + else out_ = logsinrom.ptr[(phase<<1)&0xff]; + return OPL3_EnvelopeCalcExp(out_+(envelope<<3)); +} + +private short OPL3_EnvelopeCalcSin6 (ushort phase, ushort envelope) { + ushort neg = 0; + phase &= 0x3ff; + if (phase&0x200) neg = ushort.max; + return OPL3_EnvelopeCalcExp(envelope<<3)^neg; +} + +private short OPL3_EnvelopeCalcSin7 (ushort phase, ushort envelope) { + ushort out_ = 0; + ushort neg = 0; + phase &= 0x3ff; + if (phase&0x200) { + neg = ushort.max; + phase = (phase&0x1ff)^0x1ff; + } + out_ = cast(ushort)(phase<<3); + return OPL3_EnvelopeCalcExp(out_+(envelope<<3))^neg; +} + +static immutable envelope_sinfunc[8] envelope_sin = [ + &OPL3_EnvelopeCalcSin0, + &OPL3_EnvelopeCalcSin1, + &OPL3_EnvelopeCalcSin2, + &OPL3_EnvelopeCalcSin3, + &OPL3_EnvelopeCalcSin4, + &OPL3_EnvelopeCalcSin5, + &OPL3_EnvelopeCalcSin6, + &OPL3_EnvelopeCalcSin7 +]; + +static immutable envelope_genfunc[5] envelope_gen = [ + &OPL3_EnvelopeGenOff, + &OPL3_EnvelopeGenAttack, + &OPL3_EnvelopeGenDecay, + &OPL3_EnvelopeGenSustain, + &OPL3_EnvelopeGenRelease +]; + +alias envelope_gen_num = int; +enum /*envelope_gen_num*/:int { + envelope_gen_num_off = 0, + envelope_gen_num_attack = 1, + envelope_gen_num_decay = 2, + envelope_gen_num_sustain = 3, + envelope_gen_num_release = 4 +} + +private ubyte OPL3_EnvelopeCalcRate (OPL3Slot* slot, ubyte reg_rate) { + if (reg_rate == 0x00) return 0x00; + ubyte rate = cast(ubyte)((reg_rate<<2)+(slot.reg_ksr ? slot.channel.ksv : (slot.channel.ksv>>2))); + if (rate > 0x3c) rate = 0x3c; + return rate; +} + +private void OPL3_EnvelopeUpdateKSL (OPL3Slot* slot) { + short ksl = (kslrom.ptr[slot.channel.f_num>>6]<<2)-((0x08-slot.channel.block)<<5); + if (ksl < 0) ksl = 0; + slot.eg_ksl = cast(ubyte)ksl; +} + +private void OPL3_EnvelopeUpdateRate (OPL3Slot* slot) { + switch (slot.eg_gen) { + case envelope_gen_num_off: + case envelope_gen_num_attack: + slot.eg_rate = OPL3_EnvelopeCalcRate(slot, slot.reg_ar); + break; + case envelope_gen_num_decay: + slot.eg_rate = OPL3_EnvelopeCalcRate(slot, slot.reg_dr); + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + slot.eg_rate = OPL3_EnvelopeCalcRate(slot, slot.reg_rr); + break; + default: break; + } +} + +private void OPL3_EnvelopeGenOff (OPL3Slot* slot) { + slot.eg_rout = 0x1ff; +} + +private void OPL3_EnvelopeGenAttack (OPL3Slot* slot) { + if (slot.eg_rout == 0x00) { + slot.eg_gen = envelope_gen_num_decay; + OPL3_EnvelopeUpdateRate(slot); + } else { + slot.eg_rout += ((~cast(uint)slot.eg_rout)*slot.eg_inc)>>3; + if (slot.eg_rout < 0x00) slot.eg_rout = 0x00; + } +} + +private void OPL3_EnvelopeGenDecay (OPL3Slot* slot) { + if (slot.eg_rout >= slot.reg_sl<<4) { + slot.eg_gen = envelope_gen_num_sustain; + OPL3_EnvelopeUpdateRate(slot); + } else { + slot.eg_rout += slot.eg_inc; + } +} + +private void OPL3_EnvelopeGenSustain (OPL3Slot* slot) { + if (!slot.reg_type) OPL3_EnvelopeGenRelease(slot); +} + +private void OPL3_EnvelopeGenRelease (OPL3Slot* slot) { + if (slot.eg_rout >= 0x1ff) { + slot.eg_gen = envelope_gen_num_off; + slot.eg_rout = 0x1ff; + OPL3_EnvelopeUpdateRate(slot); + } else { + slot.eg_rout += slot.eg_inc; + } +} + +private void OPL3_EnvelopeCalc (OPL3Slot* slot) { + ubyte rate_h, rate_l; + ubyte inc = 0; + rate_h = slot.eg_rate>>2; + rate_l = slot.eg_rate&3; + if (eg_incsh.ptr[rate_h] > 0) { + if ((slot.chip.timer&((1<> eg_incsh.ptr[rate_h])&0x07]; + } + } else { + inc = cast(ubyte)(eg_incstep.ptr[eg_incdesc.ptr[rate_h]].ptr[rate_l].ptr[slot.chip.timer&0x07]<<(-cast(int)(eg_incsh.ptr[rate_h]))); + } + slot.eg_inc = inc; + slot.eg_out = cast(short)(slot.eg_rout+(slot.reg_tl<<2)+(slot.eg_ksl>>kslshift.ptr[slot.reg_ksl])+*slot.trem); + envelope_gen[slot.eg_gen](slot); +} + +private void OPL3_EnvelopeKeyOn (OPL3Slot* slot, ubyte type) { + if (!slot.key) { + slot.eg_gen = envelope_gen_num_attack; + OPL3_EnvelopeUpdateRate(slot); + if ((slot.eg_rate>>2) == 0x0f) { + slot.eg_gen = envelope_gen_num_decay; + OPL3_EnvelopeUpdateRate(slot); + slot.eg_rout = 0x00; + } + slot.pg_phase = 0x00; + } + slot.key |= type; +} + +private void OPL3_EnvelopeKeyOff (OPL3Slot* slot, ubyte type) { + if (slot.key) { + slot.key &= (~cast(uint)type); + if (!slot.key) { + slot.eg_gen = envelope_gen_num_release; + OPL3_EnvelopeUpdateRate(slot); + } + } +} + +// +// Phase Generator +// + +private void OPL3_PhaseGenerate (OPL3Slot* slot) { + ushort f_num; + uint basefreq; + + f_num = slot.channel.f_num; + if (slot.reg_vib) { + byte range; + ubyte vibpos; + + range = (f_num>>7)&7; + vibpos = slot.chip.vibpos; + + if (!(vibpos&3)) range = 0; + else if (vibpos&1) range >>= 1; + range >>= slot.chip.vibshift; + + if (vibpos&4) range = cast(byte) -cast(int)(range); + f_num += range; + } + basefreq = (f_num<>1; + slot.pg_phase += (basefreq*mt.ptr[slot.reg_mult])>>1; +} + +// +// Noise Generator +// + +private void OPL3_NoiseGenerate (OPL3Chip* chip) { + if (chip.noise&0x01) chip.noise ^= 0x800302; + chip.noise >>= 1; +} + +// +// Slot +// + +private void OPL3_SlotWrite20 (OPL3Slot* slot, ubyte data) { + slot.trem = ((data>>7)&0x01 ? &slot.chip.tremolo : cast(ubyte*)&slot.chip.zeromod); + slot.reg_vib = (data>>6)&0x01; + slot.reg_type = (data>>5)&0x01; + slot.reg_ksr = (data>>4)&0x01; + slot.reg_mult = data&0x0f; + OPL3_EnvelopeUpdateRate(slot); +} + +private void OPL3_SlotWrite40 (OPL3Slot* slot, ubyte data) { + slot.reg_ksl = (data>>6)&0x03; + slot.reg_tl = data&0x3f; + OPL3_EnvelopeUpdateKSL(slot); +} + +private void OPL3_SlotWrite60 (OPL3Slot* slot, ubyte data) { + slot.reg_ar = (data>>4)&0x0f; + slot.reg_dr = data&0x0f; + OPL3_EnvelopeUpdateRate(slot); +} + +private void OPL3_SlotWrite80 (OPL3Slot* slot, ubyte data) { + slot.reg_sl = (data>>4)&0x0f; + if (slot.reg_sl == 0x0f) slot.reg_sl = 0x1f; + slot.reg_rr = data&0x0f; + OPL3_EnvelopeUpdateRate(slot); +} + +private void OPL3_SlotWriteE0 (OPL3Slot* slot, ubyte data) { + slot.reg_wf = data&0x07; + if (slot.chip.newm == 0x00) slot.reg_wf &= 0x03; +} + +private void OPL3_SlotGeneratePhase (OPL3Slot* slot, ushort phase) { + slot.out_ = envelope_sin[slot.reg_wf](phase, slot.eg_out); +} + +private void OPL3_SlotGenerate (OPL3Slot* slot) { + OPL3_SlotGeneratePhase(slot, cast(ushort)(cast(ushort)(slot.pg_phase>>9)+*slot.mod)); +} + +private void OPL3_SlotGenerateZM (OPL3Slot* slot) { + OPL3_SlotGeneratePhase(slot, cast(ushort)(slot.pg_phase>>9)); +} + +private void OPL3_SlotCalcFB (OPL3Slot* slot) { + slot.fbmod = (slot.channel.fb != 0x00 ? cast(short)((slot.prout+slot.out_)>>(0x09-slot.channel.fb)) : 0); + slot.prout = slot.out_; +} + +// +// Channel +// + +private void OPL3_ChannelUpdateRhythm (OPL3Chip* chip, ubyte data) { + OPL3Channel* channel6; + OPL3Channel* channel7; + OPL3Channel* channel8; + ubyte chnum; + + chip.rhy = data&0x3f; + if (chip.rhy&0x20) { + channel6 = &chip.channel.ptr[6]; + channel7 = &chip.channel.ptr[7]; + channel8 = &chip.channel.ptr[8]; + channel6.out_.ptr[0] = &channel6.slots.ptr[1].out_; + channel6.out_.ptr[1] = &channel6.slots.ptr[1].out_; + channel6.out_.ptr[2] = &chip.zeromod; + channel6.out_.ptr[3] = &chip.zeromod; + channel7.out_.ptr[0] = &channel7.slots.ptr[0].out_; + channel7.out_.ptr[1] = &channel7.slots.ptr[0].out_; + channel7.out_.ptr[2] = &channel7.slots.ptr[1].out_; + channel7.out_.ptr[3] = &channel7.slots.ptr[1].out_; + channel8.out_.ptr[0] = &channel8.slots.ptr[0].out_; + channel8.out_.ptr[1] = &channel8.slots.ptr[0].out_; + channel8.out_.ptr[2] = &channel8.slots.ptr[1].out_; + channel8.out_.ptr[3] = &channel8.slots.ptr[1].out_; + for (chnum = 6; chnum < 9; ++chnum) chip.channel.ptr[chnum].chtype = ch_drum; + OPL3_ChannelSetupAlg(channel6); + //hh + if (chip.rhy&0x01) { + OPL3_EnvelopeKeyOn(channel7.slots.ptr[0], egk_drum); + } else { + OPL3_EnvelopeKeyOff(channel7.slots.ptr[0], egk_drum); + } + //tc + if (chip.rhy&0x02) { + OPL3_EnvelopeKeyOn(channel8.slots.ptr[1], egk_drum); + } else { + OPL3_EnvelopeKeyOff(channel8.slots.ptr[1], egk_drum); + } + //tom + if (chip.rhy&0x04) { + OPL3_EnvelopeKeyOn(channel8.slots.ptr[0], egk_drum); + } else { + OPL3_EnvelopeKeyOff(channel8.slots.ptr[0], egk_drum); + } + //sd + if (chip.rhy&0x08) { + OPL3_EnvelopeKeyOn(channel7.slots.ptr[1], egk_drum); + } else { + OPL3_EnvelopeKeyOff(channel7.slots.ptr[1], egk_drum); + } + //bd + if (chip.rhy&0x10) { + OPL3_EnvelopeKeyOn(channel6.slots.ptr[0], egk_drum); + OPL3_EnvelopeKeyOn(channel6.slots.ptr[1], egk_drum); + } else { + OPL3_EnvelopeKeyOff(channel6.slots.ptr[0], egk_drum); + OPL3_EnvelopeKeyOff(channel6.slots.ptr[1], egk_drum); + } + } else { + for (chnum = 6; chnum < 9; ++chnum) { + chip.channel.ptr[chnum].chtype = ch_2op; + OPL3_ChannelSetupAlg(&chip.channel.ptr[chnum]); + OPL3_EnvelopeKeyOff(chip.channel.ptr[chnum].slots.ptr[0], egk_drum); + OPL3_EnvelopeKeyOff(chip.channel.ptr[chnum].slots.ptr[1], egk_drum); + } + } +} + +private void OPL3_ChannelWriteA0 (OPL3Channel* channel, ubyte data) { + if (channel.chip.newm && channel.chtype == ch_4op2) return; + channel.f_num = (channel.f_num&0x300)|data; + channel.ksv = cast(ubyte)((channel.block<<1)|((channel.f_num>>(0x09-channel.chip.nts))&0x01)); + OPL3_EnvelopeUpdateKSL(channel.slots.ptr[0]); + OPL3_EnvelopeUpdateKSL(channel.slots.ptr[1]); + OPL3_EnvelopeUpdateRate(channel.slots.ptr[0]); + OPL3_EnvelopeUpdateRate(channel.slots.ptr[1]); + if (channel.chip.newm && channel.chtype == ch_4op) { + channel.pair.f_num = channel.f_num; + channel.pair.ksv = channel.ksv; + OPL3_EnvelopeUpdateKSL(channel.pair.slots.ptr[0]); + OPL3_EnvelopeUpdateKSL(channel.pair.slots.ptr[1]); + OPL3_EnvelopeUpdateRate(channel.pair.slots.ptr[0]); + OPL3_EnvelopeUpdateRate(channel.pair.slots.ptr[1]); + } +} + +private void OPL3_ChannelWriteB0 (OPL3Channel* channel, ubyte data) { + if (channel.chip.newm && channel.chtype == ch_4op2) return; + channel.f_num = (channel.f_num&0xff)|((data&0x03)<<8); + channel.block = (data>>2)&0x07; + channel.ksv = cast(ubyte)((channel.block<<1)|((channel.f_num>>(0x09-channel.chip.nts))&0x01)); + OPL3_EnvelopeUpdateKSL(channel.slots.ptr[0]); + OPL3_EnvelopeUpdateKSL(channel.slots.ptr[1]); + OPL3_EnvelopeUpdateRate(channel.slots.ptr[0]); + OPL3_EnvelopeUpdateRate(channel.slots.ptr[1]); + if (channel.chip.newm && channel.chtype == ch_4op) { + channel.pair.f_num = channel.f_num; + channel.pair.block = channel.block; + channel.pair.ksv = channel.ksv; + OPL3_EnvelopeUpdateKSL(channel.pair.slots.ptr[0]); + OPL3_EnvelopeUpdateKSL(channel.pair.slots.ptr[1]); + OPL3_EnvelopeUpdateRate(channel.pair.slots.ptr[0]); + OPL3_EnvelopeUpdateRate(channel.pair.slots.ptr[1]); + } +} + +private void OPL3_ChannelSetupAlg (OPL3Channel* channel) { + if (channel.chtype == ch_drum) { + final switch (channel.alg&0x01) { + case 0x00: + channel.slots.ptr[0].mod = &channel.slots.ptr[0].fbmod; + channel.slots.ptr[1].mod = &channel.slots.ptr[0].out_; + break; + case 0x01: + channel.slots.ptr[0].mod = &channel.slots.ptr[0].fbmod; + channel.slots.ptr[1].mod = &channel.chip.zeromod; + break; + } + return; + } + if (channel.alg&0x08) return; + if (channel.alg&0x04) { + channel.pair.out_.ptr[0] = &channel.chip.zeromod; + channel.pair.out_.ptr[1] = &channel.chip.zeromod; + channel.pair.out_.ptr[2] = &channel.chip.zeromod; + channel.pair.out_.ptr[3] = &channel.chip.zeromod; + final switch (channel.alg&0x03) { + case 0x00: + channel.pair.slots.ptr[0].mod = &channel.pair.slots.ptr[0].fbmod; + channel.pair.slots.ptr[1].mod = &channel.pair.slots.ptr[0].out_; + channel.slots.ptr[0].mod = &channel.pair.slots.ptr[1].out_; + channel.slots.ptr[1].mod = &channel.slots.ptr[0].out_; + channel.out_.ptr[0] = &channel.slots.ptr[1].out_; + channel.out_.ptr[1] = &channel.chip.zeromod; + channel.out_.ptr[2] = &channel.chip.zeromod; + channel.out_.ptr[3] = &channel.chip.zeromod; + break; + case 0x01: + channel.pair.slots.ptr[0].mod = &channel.pair.slots.ptr[0].fbmod; + channel.pair.slots.ptr[1].mod = &channel.pair.slots.ptr[0].out_; + channel.slots.ptr[0].mod = &channel.chip.zeromod; + channel.slots.ptr[1].mod = &channel.slots.ptr[0].out_; + channel.out_.ptr[0] = &channel.pair.slots.ptr[1].out_; + channel.out_.ptr[1] = &channel.slots.ptr[1].out_; + channel.out_.ptr[2] = &channel.chip.zeromod; + channel.out_.ptr[3] = &channel.chip.zeromod; + break; + case 0x02: + channel.pair.slots.ptr[0].mod = &channel.pair.slots.ptr[0].fbmod; + channel.pair.slots.ptr[1].mod = &channel.chip.zeromod; + channel.slots.ptr[0].mod = &channel.pair.slots.ptr[1].out_; + channel.slots.ptr[1].mod = &channel.slots.ptr[0].out_; + channel.out_.ptr[0] = &channel.pair.slots.ptr[0].out_; + channel.out_.ptr[1] = &channel.slots.ptr[1].out_; + channel.out_.ptr[2] = &channel.chip.zeromod; + channel.out_.ptr[3] = &channel.chip.zeromod; + break; + case 0x03: + channel.pair.slots.ptr[0].mod = &channel.pair.slots.ptr[0].fbmod; + channel.pair.slots.ptr[1].mod = &channel.chip.zeromod; + channel.slots.ptr[0].mod = &channel.pair.slots.ptr[1].out_; + channel.slots.ptr[1].mod = &channel.chip.zeromod; + channel.out_.ptr[0] = &channel.pair.slots.ptr[0].out_; + channel.out_.ptr[1] = &channel.slots.ptr[0].out_; + channel.out_.ptr[2] = &channel.slots.ptr[1].out_; + channel.out_.ptr[3] = &channel.chip.zeromod; + break; + } + } else { + final switch (channel.alg&0x01) { + case 0x00: + channel.slots.ptr[0].mod = &channel.slots.ptr[0].fbmod; + channel.slots.ptr[1].mod = &channel.slots.ptr[0].out_; + channel.out_.ptr[0] = &channel.slots.ptr[1].out_; + channel.out_.ptr[1] = &channel.chip.zeromod; + channel.out_.ptr[2] = &channel.chip.zeromod; + channel.out_.ptr[3] = &channel.chip.zeromod; + break; + case 0x01: + channel.slots.ptr[0].mod = &channel.slots.ptr[0].fbmod; + channel.slots.ptr[1].mod = &channel.chip.zeromod; + channel.out_.ptr[0] = &channel.slots.ptr[0].out_; + channel.out_.ptr[1] = &channel.slots.ptr[1].out_; + channel.out_.ptr[2] = &channel.chip.zeromod; + channel.out_.ptr[3] = &channel.chip.zeromod; + break; + } + } +} + +private void OPL3_ChannelWriteC0 (OPL3Channel* channel, ubyte data) { + channel.fb = (data&0x0e)>>1; + channel.con = data&0x01; + channel.alg = channel.con; + if (channel.chip.newm) { + if (channel.chtype == ch_4op) { + channel.pair.alg = cast(ubyte)(0x04|(channel.con<<1)|(channel.pair.con)); + channel.alg = 0x08; + OPL3_ChannelSetupAlg(channel.pair); + } else if (channel.chtype == ch_4op2) { + channel.alg = cast(ubyte)(0x04|(channel.pair.con<<1)|(channel.con)); + channel.pair.alg = 0x08; + OPL3_ChannelSetupAlg(channel); + } else { + OPL3_ChannelSetupAlg(channel); + } + } else { + OPL3_ChannelSetupAlg(channel); + } + if (channel.chip.newm) { + channel.cha = ((data>>4)&0x01 ? ushort.max : 0); + channel.chb = ((data>>5)&0x01 ? ushort.max : 0); + } else { + channel.cha = channel.chb = ushort.max; + } +} + +private void OPL3_ChannelKeyOn (OPL3Channel* channel) { + if (channel.chip.newm) { + if (channel.chtype == ch_4op) { + OPL3_EnvelopeKeyOn(channel.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOn(channel.slots.ptr[1], egk_norm); + OPL3_EnvelopeKeyOn(channel.pair.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOn(channel.pair.slots.ptr[1], egk_norm); + } else if (channel.chtype == ch_2op || channel.chtype == ch_drum) { + OPL3_EnvelopeKeyOn(channel.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOn(channel.slots.ptr[1], egk_norm); + } + } else { + OPL3_EnvelopeKeyOn(channel.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOn(channel.slots.ptr[1], egk_norm); + } +} + +private void OPL3_ChannelKeyOff (OPL3Channel* channel) { + if (channel.chip.newm) { + if (channel.chtype == ch_4op) { + OPL3_EnvelopeKeyOff(channel.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOff(channel.slots.ptr[1], egk_norm); + OPL3_EnvelopeKeyOff(channel.pair.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOff(channel.pair.slots.ptr[1], egk_norm); + } else if (channel.chtype == ch_2op || channel.chtype == ch_drum) { + OPL3_EnvelopeKeyOff(channel.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOff(channel.slots.ptr[1], egk_norm); + } + } else { + OPL3_EnvelopeKeyOff(channel.slots.ptr[0], egk_norm); + OPL3_EnvelopeKeyOff(channel.slots.ptr[1], egk_norm); + } +} + +private void OPL3_ChannelSet4Op (OPL3Chip* chip, ubyte data) { + ubyte bit; + ubyte chnum; + for (bit = 0; bit < 6; ++bit) { + chnum = bit; + if (bit >= 3) chnum += 9-3; + if ((data>>bit)&0x01) { + chip.channel.ptr[chnum].chtype = ch_4op; + chip.channel.ptr[chnum+3].chtype = ch_4op2; + } else { + chip.channel.ptr[chnum].chtype = ch_2op; + chip.channel.ptr[chnum+3].chtype = ch_2op; + } + } +} + +private short OPL3_ClipSample (int sample) pure { + pragma(inline, true); + if (sample > 32767) sample = 32767; + else if (sample < -32768) sample = -32768; + return cast(short)sample; +} + +private void OPL3_GenerateRhythm1 (OPL3Chip* chip) { + OPL3Channel* channel6; + OPL3Channel* channel7; + OPL3Channel* channel8; + ushort phase14; + ushort phase17; + ushort phase; + ushort phasebit; + + channel6 = &chip.channel.ptr[6]; + channel7 = &chip.channel.ptr[7]; + channel8 = &chip.channel.ptr[8]; + OPL3_SlotGenerate(channel6.slots.ptr[0]); + phase14 = (channel7.slots.ptr[0].pg_phase>>9)&0x3ff; + phase17 = (channel8.slots.ptr[1].pg_phase>>9)&0x3ff; + phase = 0x00; + //hh tc phase bit + phasebit = ((phase14&0x08)|(((phase14>>5)^phase14)&0x04)|(((phase17>>2)^phase17)&0x08)) ? 0x01 : 0x00; + //hh + phase = cast(ushort)((phasebit<<9)|(0x34<<((phasebit^(chip.noise&0x01))<<1))); + OPL3_SlotGeneratePhase(channel7.slots.ptr[0], phase); + //tt + OPL3_SlotGenerateZM(channel8.slots.ptr[0]); +} + +private void OPL3_GenerateRhythm2 (OPL3Chip* chip) { + OPL3Channel* channel6; + OPL3Channel* channel7; + OPL3Channel* channel8; + ushort phase14; + ushort phase17; + ushort phase; + ushort phasebit; + + channel6 = &chip.channel.ptr[6]; + channel7 = &chip.channel.ptr[7]; + channel8 = &chip.channel.ptr[8]; + OPL3_SlotGenerate(channel6.slots.ptr[1]); + phase14 = (channel7.slots.ptr[0].pg_phase>>9)&0x3ff; + phase17 = (channel8.slots.ptr[1].pg_phase>>9)&0x3ff; + phase = 0x00; + //hh tc phase bit + phasebit = ((phase14&0x08)|(((phase14>>5)^phase14)&0x04)|(((phase17>>2)^phase17)&0x08)) ? 0x01 : 0x00; + //sd + phase = (0x100<<((phase14>>8)&0x01))^((chip.noise&0x01)<<8); + OPL3_SlotGeneratePhase(channel7.slots.ptr[1], phase); + //tc + phase = cast(ushort)(0x100|(phasebit<<9)); + OPL3_SlotGeneratePhase(channel8.slots.ptr[1], phase); +} + + +// ////////////////////////////////////////////////////////////////////////// // +/// OPL3_Generate +public void generate (ref OPL3Chip chip, short* buf) { + ubyte ii; + ubyte jj; + short accm; + + buf[1] = OPL3_ClipSample(chip.mixbuff.ptr[1]); + + for (ii = 0; ii < 12; ++ii) { + OPL3_SlotCalcFB(&chip.slot.ptr[ii]); + OPL3_PhaseGenerate(&chip.slot.ptr[ii]); + OPL3_EnvelopeCalc(&chip.slot.ptr[ii]); + OPL3_SlotGenerate(&chip.slot.ptr[ii]); + } + + for (ii = 12; ii < 15; ++ii) { + OPL3_SlotCalcFB(&chip.slot.ptr[ii]); + OPL3_PhaseGenerate(&chip.slot.ptr[ii]); + OPL3_EnvelopeCalc(&chip.slot.ptr[ii]); + } + + if (chip.rhy&0x20) { + OPL3_GenerateRhythm1(&chip); + } else { + OPL3_SlotGenerate(&chip.slot.ptr[12]); + OPL3_SlotGenerate(&chip.slot.ptr[13]); + OPL3_SlotGenerate(&chip.slot.ptr[14]); + } + + chip.mixbuff.ptr[0] = 0; + for (ii = 0; ii < 18; ++ii) { + accm = 0; + for (jj = 0; jj < 4; ++jj) accm += *chip.channel.ptr[ii].out_.ptr[jj]; + chip.mixbuff.ptr[0] += cast(short)(accm&chip.channel.ptr[ii].cha); + } + + for (ii = 15; ii < 18; ++ii) { + OPL3_SlotCalcFB(&chip.slot.ptr[ii]); + OPL3_PhaseGenerate(&chip.slot.ptr[ii]); + OPL3_EnvelopeCalc(&chip.slot.ptr[ii]); + } + + if (chip.rhy&0x20) { + OPL3_GenerateRhythm2(&chip); + } else { + OPL3_SlotGenerate(&chip.slot.ptr[15]); + OPL3_SlotGenerate(&chip.slot.ptr[16]); + OPL3_SlotGenerate(&chip.slot.ptr[17]); + } + + buf[0] = OPL3_ClipSample(chip.mixbuff.ptr[0]); + + for (ii = 18; ii < 33; ++ii) { + OPL3_SlotCalcFB(&chip.slot.ptr[ii]); + OPL3_PhaseGenerate(&chip.slot.ptr[ii]); + OPL3_EnvelopeCalc(&chip.slot.ptr[ii]); + OPL3_SlotGenerate(&chip.slot.ptr[ii]); + } + + chip.mixbuff.ptr[1] = 0; + for (ii = 0; ii < 18; ++ii) { + accm = 0; + for (jj = 0; jj < 4; jj++) accm += *chip.channel.ptr[ii].out_.ptr[jj]; + chip.mixbuff.ptr[1] += cast(short)(accm&chip.channel.ptr[ii].chb); + } + + for (ii = 33; ii < 36; ++ii) { + OPL3_SlotCalcFB(&chip.slot.ptr[ii]); + OPL3_PhaseGenerate(&chip.slot.ptr[ii]); + OPL3_EnvelopeCalc(&chip.slot.ptr[ii]); + OPL3_SlotGenerate(&chip.slot.ptr[ii]); + } + + OPL3_NoiseGenerate(&chip); + + if ((chip.timer&0x3f) == 0x3f) chip.tremolopos = (chip.tremolopos+1)%210; + chip.tremolo = (chip.tremolopos < 105 ? chip.tremolopos>>chip.tremoloshift : cast(ubyte)((210-chip.tremolopos)>>chip.tremoloshift)); + if ((chip.timer&0x3ff) == 0x3ff) chip.vibpos = (chip.vibpos+1)&7; + + ++chip.timer; + + while (chip.writebuf.ptr[chip.writebuf_cur].time <= chip.writebuf_samplecnt) { + if (!(chip.writebuf.ptr[chip.writebuf_cur].reg&0x200)) break; + chip.writebuf.ptr[chip.writebuf_cur].reg &= 0x1ff; + chip.writeReg(chip.writebuf.ptr[chip.writebuf_cur].reg, chip.writebuf.ptr[chip.writebuf_cur].data); + chip.writebuf_cur = (chip.writebuf_cur+1)%OPL_WRITEBUF_SIZE; + } + ++chip.writebuf_samplecnt; +} + + +/// OPL3_GenerateResampled +public void generateResampled (ref OPL3Chip chip, short* buf) { + while (chip.samplecnt >= chip.rateratio) { + chip.oldsamples.ptr[0] = chip.samples.ptr[0]; + chip.oldsamples.ptr[1] = chip.samples.ptr[1]; + chip.generate(chip.samples.ptr); + chip.samplecnt -= chip.rateratio; + } + buf[0] = cast(short)((chip.oldsamples.ptr[0]*(chip.rateratio-chip.samplecnt)+chip.samples.ptr[0]*chip.samplecnt)/chip.rateratio); + buf[1] = cast(short)((chip.oldsamples.ptr[1]*(chip.rateratio-chip.samplecnt)+chip.samples.ptr[1]*chip.samplecnt)/chip.rateratio); + chip.samplecnt += 1<>8)&0x01; + ubyte regm = reg&0xff; + switch (regm&0xf0) { + case 0x00: + if (high) { + switch (regm&0x0f) { + case 0x04: + OPL3_ChannelSet4Op(&chip, v); + break; + case 0x05: + chip.newm = v&0x01; + break; + default: break; + } + } else { + switch (regm&0x0f) { + case 0x08: + chip.nts = (v>>6)&0x01; + break; + default: break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot.ptr[regm&0x1f] >= 0) OPL3_SlotWrite20(&chip.slot.ptr[18*high+ad_slot.ptr[regm&0x1f]], v); + break; + case 0x40: + case 0x50: + if (ad_slot.ptr[regm&0x1f] >= 0) OPL3_SlotWrite40(&chip.slot.ptr[18*high+ad_slot.ptr[regm&0x1f]], v); + break; + case 0x60: + case 0x70: + if (ad_slot.ptr[regm&0x1f] >= 0) OPL3_SlotWrite60(&chip.slot.ptr[18*high+ad_slot.ptr[regm&0x1f]], v); + break; + case 0x80: + case 0x90: + if (ad_slot.ptr[regm&0x1f] >= 0) OPL3_SlotWrite80(&chip.slot.ptr[18*high+ad_slot.ptr[regm&0x1f]], v); + break; + case 0xe0: + case 0xf0: + if (ad_slot.ptr[regm&0x1f] >= 0) OPL3_SlotWriteE0(&chip.slot.ptr[18*high+ad_slot.ptr[regm&0x1f]], v); + break; + case 0xa0: + if ((regm&0x0f) < 9) OPL3_ChannelWriteA0(&chip.channel.ptr[9*high+(regm&0x0f)], v); + break; + case 0xb0: + if (regm == 0xbd && !high) { + chip.tremoloshift = (((v>>7)^1)<<1)+2; + chip.vibshift = ((v>>6)&0x01)^1; + OPL3_ChannelUpdateRhythm(&chip, v); + } else if ((regm&0x0f) < 9) { + OPL3_ChannelWriteB0(&chip.channel.ptr[9*high+(regm&0x0f)], v); + if (v&0x20) OPL3_ChannelKeyOn(&chip.channel.ptr[9*high+(regm&0x0f)]); else OPL3_ChannelKeyOff(&chip.channel.ptr[9*high+(regm&0x0f)]); + } + break; + case 0xc0: + if ((regm&0x0f) < 9) OPL3_ChannelWriteC0(&chip.channel.ptr[9*high+(regm&0x0f)], v); + break; + default: break; + } +} + + +/// OPL3_WriteRegBuffered +public void writeRegBuffered (ref OPL3Chip chip, ushort reg, ubyte v) { + ulong time1, time2; + + if (chip.writebuf.ptr[chip.writebuf_last].reg&0x200) { + chip.writeReg(chip.writebuf.ptr[chip.writebuf_last].reg&0x1ff, chip.writebuf.ptr[chip.writebuf_last].data); + chip.writebuf_cur = (chip.writebuf_last+1)%OPL_WRITEBUF_SIZE; + chip.writebuf_samplecnt = chip.writebuf.ptr[chip.writebuf_last].time; + } + + chip.writebuf.ptr[chip.writebuf_last].reg = reg|0x200; + chip.writebuf.ptr[chip.writebuf_last].data = v; + time1 = chip.writebuf_lasttime+OPL_WRITEBUF_DELAY; + time2 = chip.writebuf_samplecnt; + + if (time1 < time2) time1 = time2; + + chip.writebuf.ptr[chip.writebuf_last].time = time1; + chip.writebuf_lasttime = time1; + chip.writebuf_last = (chip.writebuf_last+1)%OPL_WRITEBUF_SIZE; +} + + +/// OPL3_GenerateStream; outputs STEREO stream +public void generateStream (ref OPL3Chip chip, short[] smpbuf) { + auto sndptr = smpbuf.ptr; + foreach (immutable _; 0..smpbuf.length/2) { + chip.generateResampled(sndptr); + sndptr += 2; + } +} + + +// ////////////////////////////////////////////////////////////////////////// // +// simple DooM MUS / Midi player +public final class OPLPlayer { +private: + static immutable ubyte[128] opl_voltable = [ + 0x00, 0x01, 0x03, 0x05, 0x06, 0x08, 0x0a, 0x0b, + 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, 0x16, 0x17, + 0x19, 0x1a, 0x1b, 0x1d, 0x1e, 0x20, 0x21, 0x22, + 0x24, 0x25, 0x27, 0x29, 0x2b, 0x2d, 0x2f, 0x31, + 0x32, 0x34, 0x36, 0x37, 0x39, 0x3b, 0x3c, 0x3d, + 0x3f, 0x40, 0x42, 0x43, 0x44, 0x45, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x5b, 0x5c, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x60, 0x61, 0x62, 0x63, 0x63, 0x64, 0x65, + 0x65, 0x66, 0x67, 0x67, 0x68, 0x69, 0x69, 0x6a, + 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, + 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, + 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, + 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, + 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f + ]; + + static immutable ushort[284+384] opl_freqtable = [ + 0x0133, 0x0133, 0x0134, 0x0134, 0x0135, 0x0136, 0x0136, 0x0137, + 0x0137, 0x0138, 0x0138, 0x0139, 0x0139, 0x013a, 0x013b, 0x013b, + 0x013c, 0x013c, 0x013d, 0x013d, 0x013e, 0x013f, 0x013f, 0x0140, + 0x0140, 0x0141, 0x0142, 0x0142, 0x0143, 0x0143, 0x0144, 0x0144, + 0x0145, 0x0146, 0x0146, 0x0147, 0x0147, 0x0148, 0x0149, 0x0149, + 0x014a, 0x014a, 0x014b, 0x014c, 0x014c, 0x014d, 0x014d, 0x014e, + 0x014f, 0x014f, 0x0150, 0x0150, 0x0151, 0x0152, 0x0152, 0x0153, + 0x0153, 0x0154, 0x0155, 0x0155, 0x0156, 0x0157, 0x0157, 0x0158, + 0x0158, 0x0159, 0x015a, 0x015a, 0x015b, 0x015b, 0x015c, 0x015d, + 0x015d, 0x015e, 0x015f, 0x015f, 0x0160, 0x0161, 0x0161, 0x0162, + 0x0162, 0x0163, 0x0164, 0x0164, 0x0165, 0x0166, 0x0166, 0x0167, + 0x0168, 0x0168, 0x0169, 0x016a, 0x016a, 0x016b, 0x016c, 0x016c, + 0x016d, 0x016e, 0x016e, 0x016f, 0x0170, 0x0170, 0x0171, 0x0172, + 0x0172, 0x0173, 0x0174, 0x0174, 0x0175, 0x0176, 0x0176, 0x0177, + 0x0178, 0x0178, 0x0179, 0x017a, 0x017a, 0x017b, 0x017c, 0x017c, + 0x017d, 0x017e, 0x017e, 0x017f, 0x0180, 0x0181, 0x0181, 0x0182, + 0x0183, 0x0183, 0x0184, 0x0185, 0x0185, 0x0186, 0x0187, 0x0188, + 0x0188, 0x0189, 0x018a, 0x018a, 0x018b, 0x018c, 0x018d, 0x018d, + 0x018e, 0x018f, 0x018f, 0x0190, 0x0191, 0x0192, 0x0192, 0x0193, + 0x0194, 0x0194, 0x0195, 0x0196, 0x0197, 0x0197, 0x0198, 0x0199, + 0x019a, 0x019a, 0x019b, 0x019c, 0x019d, 0x019d, 0x019e, 0x019f, + 0x01a0, 0x01a0, 0x01a1, 0x01a2, 0x01a3, 0x01a3, 0x01a4, 0x01a5, + 0x01a6, 0x01a6, 0x01a7, 0x01a8, 0x01a9, 0x01a9, 0x01aa, 0x01ab, + 0x01ac, 0x01ad, 0x01ad, 0x01ae, 0x01af, 0x01b0, 0x01b0, 0x01b1, + 0x01b2, 0x01b3, 0x01b4, 0x01b4, 0x01b5, 0x01b6, 0x01b7, 0x01b8, + 0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bc, 0x01bd, 0x01be, + 0x01bf, 0x01c0, 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x01c4, 0x01c4, + 0x01c5, 0x01c6, 0x01c7, 0x01c8, 0x01c9, 0x01c9, 0x01ca, 0x01cb, + 0x01cc, 0x01cd, 0x01ce, 0x01ce, 0x01cf, 0x01d0, 0x01d1, 0x01d2, + 0x01d3, 0x01d3, 0x01d4, 0x01d5, 0x01d6, 0x01d7, 0x01d8, 0x01d8, + 0x01d9, 0x01da, 0x01db, 0x01dc, 0x01dd, 0x01de, 0x01de, 0x01df, + 0x01e0, 0x01e1, 0x01e2, 0x01e3, 0x01e4, 0x01e5, 0x01e5, 0x01e6, + 0x01e7, 0x01e8, 0x01e9, 0x01ea, 0x01eb, 0x01ec, 0x01ed, 0x01ed, + 0x01ee, 0x01ef, 0x01f0, 0x01f1, 0x01f2, 0x01f3, 0x01f4, 0x01f5, + 0x01f6, 0x01f6, 0x01f7, 0x01f8, 0x01f9, 0x01fa, 0x01fb, 0x01fc, + 0x01fd, 0x01fe, 0x01ff, 0x0200, 0x0201, 0x0201, 0x0202, 0x0203, + 0x0204, 0x0205, 0x0206, 0x0207, 0x0208, 0x0209, 0x020a, 0x020b, + 0x020c, 0x020d, 0x020e, 0x020f, 0x0210, 0x0210, 0x0211, 0x0212, + 0x0213, 0x0214, 0x0215, 0x0216, 0x0217, 0x0218, 0x0219, 0x021a, + 0x021b, 0x021c, 0x021d, 0x021e, 0x021f, 0x0220, 0x0221, 0x0222, + 0x0223, 0x0224, 0x0225, 0x0226, 0x0227, 0x0228, 0x0229, 0x022a, + 0x022b, 0x022c, 0x022d, 0x022e, 0x022f, 0x0230, 0x0231, 0x0232, + 0x0233, 0x0234, 0x0235, 0x0236, 0x0237, 0x0238, 0x0239, 0x023a, + 0x023b, 0x023c, 0x023d, 0x023e, 0x023f, 0x0240, 0x0241, 0x0242, + 0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, + 0x024c, 0x024d, 0x024e, 0x024f, 0x0250, 0x0251, 0x0252, 0x0253, + 0x0254, 0x0256, 0x0257, 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, + 0x025d, 0x025e, 0x025f, 0x0260, 0x0262, 0x0263, 0x0264, 0x0265, + 0x0266, 0x0267, 0x0268, 0x0269, 0x026a, 0x026c, 0x026d, 0x026e, + 0x026f, 0x0270, 0x0271, 0x0272, 0x0273, 0x0275, 0x0276, 0x0277, + 0x0278, 0x0279, 0x027a, 0x027b, 0x027d, 0x027e, 0x027f, 0x0280, + 0x0281, 0x0282, 0x0284, 0x0285, 0x0286, 0x0287, 0x0288, 0x0289, + 0x028b, 0x028c, 0x028d, 0x028e, 0x028f, 0x0290, 0x0292, 0x0293, + 0x0294, 0x0295, 0x0296, 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, + 0x029e, 0x029f, 0x02a0, 0x02a1, 0x02a2, 0x02a4, 0x02a5, 0x02a6, + 0x02a7, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ae, 0x02af, 0x02b0, + 0x02b1, 0x02b2, 0x02b4, 0x02b5, 0x02b6, 0x02b7, 0x02b9, 0x02ba, + 0x02bb, 0x02bd, 0x02be, 0x02bf, 0x02c0, 0x02c2, 0x02c3, 0x02c4, + 0x02c5, 0x02c7, 0x02c8, 0x02c9, 0x02cb, 0x02cc, 0x02cd, 0x02ce, + 0x02d0, 0x02d1, 0x02d2, 0x02d4, 0x02d5, 0x02d6, 0x02d8, 0x02d9, + 0x02da, 0x02dc, 0x02dd, 0x02de, 0x02e0, 0x02e1, 0x02e2, 0x02e4, + 0x02e5, 0x02e6, 0x02e8, 0x02e9, 0x02ea, 0x02ec, 0x02ed, 0x02ee, + 0x02f0, 0x02f1, 0x02f2, 0x02f4, 0x02f5, 0x02f6, 0x02f8, 0x02f9, + 0x02fb, 0x02fc, 0x02fd, 0x02ff, 0x0300, 0x0302, 0x0303, 0x0304, + 0x0306, 0x0307, 0x0309, 0x030a, 0x030b, 0x030d, 0x030e, 0x0310, + 0x0311, 0x0312, 0x0314, 0x0315, 0x0317, 0x0318, 0x031a, 0x031b, + 0x031c, 0x031e, 0x031f, 0x0321, 0x0322, 0x0324, 0x0325, 0x0327, + 0x0328, 0x0329, 0x032b, 0x032c, 0x032e, 0x032f, 0x0331, 0x0332, + 0x0334, 0x0335, 0x0337, 0x0338, 0x033a, 0x033b, 0x033d, 0x033e, + 0x0340, 0x0341, 0x0343, 0x0344, 0x0346, 0x0347, 0x0349, 0x034a, + 0x034c, 0x034d, 0x034f, 0x0350, 0x0352, 0x0353, 0x0355, 0x0357, + 0x0358, 0x035a, 0x035b, 0x035d, 0x035e, 0x0360, 0x0361, 0x0363, + 0x0365, 0x0366, 0x0368, 0x0369, 0x036b, 0x036c, 0x036e, 0x0370, + 0x0371, 0x0373, 0x0374, 0x0376, 0x0378, 0x0379, 0x037b, 0x037c, + 0x037e, 0x0380, 0x0381, 0x0383, 0x0384, 0x0386, 0x0388, 0x0389, + 0x038b, 0x038d, 0x038e, 0x0390, 0x0392, 0x0393, 0x0395, 0x0397, + 0x0398, 0x039a, 0x039c, 0x039d, 0x039f, 0x03a1, 0x03a2, 0x03a4, + 0x03a6, 0x03a7, 0x03a9, 0x03ab, 0x03ac, 0x03ae, 0x03b0, 0x03b1, + 0x03b3, 0x03b5, 0x03b7, 0x03b8, 0x03ba, 0x03bc, 0x03bd, 0x03bf, + 0x03c1, 0x03c3, 0x03c4, 0x03c6, 0x03c8, 0x03ca, 0x03cb, 0x03cd, + 0x03cf, 0x03d1, 0x03d2, 0x03d4, 0x03d6, 0x03d8, 0x03da, 0x03db, + 0x03dd, 0x03df, 0x03e1, 0x03e3, 0x03e4, 0x03e6, 0x03e8, 0x03ea, + 0x03ec, 0x03ed, 0x03ef, 0x03f1, 0x03f3, 0x03f5, 0x03f6, 0x03f8, + 0x03fa, 0x03fc, 0x03fe, 0x036c + ]; + +private: + // GenMidi lump structure + static align(1) struct GenMidi { + align(1): + public: + static align(1) struct Operator { + align(1): + ubyte mult; /* Tremolo / vibrato / sustain / KSR / multi */ + ubyte attack; /* Attack rate / decay rate */ + ubyte sustain; /* Sustain level / release rate */ + ubyte wave; /* Waveform select */ + ubyte ksl; /* Key scale level */ + ubyte level; /* Output level */ + ubyte feedback; /* Feedback for modulator, unused for carrier */ + } + + static align(1) struct Voice { + align(1): + Operator mod; /* modulator */ + Operator car; /* carrier */ + /* Base note offset. This is used to offset the MIDI note values. + Several of the GENMIDI instruments have a base note offset of -12, + causing all notes to be offset down by one octave. */ + short offset; + } + + static align(1) struct Patch { + align(1): + public: + enum Flag : ushort { + Fixed = 0x01, + DualVoice = 0x04, + } + public: + /* bit 0: Fixed pitch - Instrument always plays the same note. + Most MIDI instruments play a note that is specified in the MIDI "key on" event, + but some (most notably percussion instruments) always play the same fixed note. + bit 1: Unknown - used in instrument #65 of the Doom GENMIDI lump. + bit 2: Double voice - Play two voices simultaneously. This is used even on an OPL2 chip. + If this is not set, only the first voice is played. If it is set, the fine tuning + field (see below) can be used to adjust the pitch of the second voice relative to + the first. + */ + version(genmidi_dumper) { + ushort flags; + } else { + ubyte flags; + } + /* Fine tuning - This normally has a value of 128, but can be adjusted to adjust the tuning of + the instrument. This field only applies to the second voice of double-voice instruments; + for single voice instruments it has no effect. The offset values are similar to MIDI pitch + bends; for example, a value of 82 (hex) in this field is equivalent to a MIDI pitch bend of +256. + */ + ubyte finetune; + /* Note number used for fixed pitch instruments */ + ubyte note; + Voice[2] voice; + version(genmidi_dumper) { + // no name in this mode + } else { + string name; // patch name + } + } + + public: + //char[8] header; + Patch[175] patch; + version(genmidi_dumper) { + char[32][175] namestrs; // patch names + @property const(char)[] name (usize patchidx) const pure nothrow @safe @nogc { + const(char)[] res = namestrs[patchidx][]; + foreach (immutable idx, immutable char ch; res) if (ch == 0) return res[0..idx]; + return res; + } + } + + public: + version(genmidi_dumper) { + void dump (VFile fo) { + fo.writeln("static immutable GenMidi mGenMidi = GenMidi(["); + foreach (immutable idx, const ref Patch pt; patch[]) { + fo.write(" GenMidi.Patch("); + fo.writef("0x%02x,", pt.flags); + fo.writef("%3u,", pt.finetune); + fo.writef("%3u,[", pt.note); + // voices + foreach (immutable vidx, const ref v; pt.voice[]) { + fo.write("GenMidi.Voice("); + fo.write("GenMidi.Operator("); + fo.writef("%3u,", v.mod.mult); + fo.writef("%3u,", v.mod.attack); + fo.writef("%3u,", v.mod.sustain); + fo.writef("%3u,", v.mod.wave); + fo.writef("%3u,", v.mod.ksl); + fo.writef("%3u,", v.mod.level); + fo.writef("%3u),", v.mod.feedback); + fo.write("GenMidi.Operator("); + fo.writef("%3u,", v.car.mult); + fo.writef("%3u,", v.car.attack); + fo.writef("%3u,", v.car.sustain); + fo.writef("%3u,", v.car.wave); + fo.writef("%3u,", v.car.ksl); + fo.writef("%3u,", v.car.level); + fo.writef("%3u),", v.car.feedback); + fo.writef("%4d),", v.offset); + } + fo.write("],", name(idx).quote); + fo.writeln("),"); + } + fo.writeln("]);"); + } + } + } + + version(genmidi_dumper) { + } else { + //mixin(import("zgenmidi.d")); + static immutable GenMidi mGenMidi = GenMidi([ + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,240,243, 1, 64, 20, 10),GenMidi.Operator( 48,241,244, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Acoustic Grand Piano"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,240,243, 0, 64, 18, 10),GenMidi.Operator( 48,241,244, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Bright Acoustic Piano"), + GenMidi.Patch(0x04,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,225,243, 1, 64, 14, 8),GenMidi.Operator( 48,241,244, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 17,232, 21, 0, 0, 0, 1),GenMidi.Operator( 18,247, 20, 0, 0, 0, 0), 0),],"Electric Grand Piano"), + GenMidi.Patch(0x04,130, 0,[GenMidi.Voice(GenMidi.Operator( 16,241, 83, 1, 64, 15, 6),GenMidi.Operator( 16,209,244, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 17,241, 83, 0, 64, 15, 6),GenMidi.Operator( 17,209,244, 0, 0, 0, 0), 0),],"Honky-tonk Piano"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 33,241, 81, 0, 64, 38, 6),GenMidi.Operator( 49,210,229, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Rhodes Paino"), + GenMidi.Patch(0x04,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,241,230, 0, 64, 17, 6),GenMidi.Operator(176,241,229, 0, 64, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 18,242,121, 0, 64, 3, 9),GenMidi.Operator( 16,241,153, 0, 64, 0, 0), 0),],"Chorused Piano"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,242, 1, 2,128, 7, 6),GenMidi.Operator( 48,193,244, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Harpsichord"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(144,161, 98, 1,128, 14, 12),GenMidi.Operator( 16,145,167, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Clavinet"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 40,242,100, 1, 64, 15, 8),GenMidi.Operator( 49,242,228, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Celesta"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 19,145, 17, 0, 0, 14, 9),GenMidi.Operator( 20,125, 52, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Glockenspiel"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(178,246, 65, 0, 0, 15, 0),GenMidi.Operator(144,210,146, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Music Box"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(240,241,243, 0, 0, 2, 1),GenMidi.Operator(242,241,244, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Vibraphone"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(128,121, 21, 0, 0, 0, 1),GenMidi.Operator(131,248,117, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Marimba"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 20,246,147, 0, 0, 31, 8),GenMidi.Operator( 16,246, 83, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Xylophone"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(129,182, 19, 1,128, 25, 10),GenMidi.Operator( 2,255, 19, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Tubular-bell"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,145, 17, 0, 64, 7, 8),GenMidi.Operator( 17, 82, 83, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Dulcimer"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(160,177, 22, 0,128, 8, 7),GenMidi.Operator( 97,209, 23, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Hammond Organ"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,241, 5, 1, 0, 0, 7),GenMidi.Operator(148,244, 54, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Percussive Organ"), + GenMidi.Patch(0x04,138, 0,[GenMidi.Voice(GenMidi.Operator(226,242, 23, 0,128, 30, 0),GenMidi.Operator( 96,255, 7, 1,128, 0, 0), -12),GenMidi.Voice(GenMidi.Operator(224,242, 23, 1,128, 30, 0),GenMidi.Operator(160,255, 7, 0,128, 0, 0), 0),],"Rock Organ"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48, 48, 4, 0,128, 18, 9),GenMidi.Operator( 49, 84, 20, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 49, 84, 20, 2,128, 18, 9),GenMidi.Operator( 48,253, 68, 0,128, 0, 0), 0),],"Church Organ"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 0,128, 23, 0, 64, 9, 6),GenMidi.Operator(129, 96, 23, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Reed Organ"), + GenMidi.Patch(0x04,125, 0,[GenMidi.Voice(GenMidi.Operator( 32,162, 21, 0, 64, 8, 10),GenMidi.Operator( 49, 65, 38, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 32,130, 21, 0, 64, 10, 10),GenMidi.Operator( 49, 70, 38, 1, 0, 0, 0), 0),],"Accordion"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(176, 96, 52, 0, 0, 12, 8),GenMidi.Operator(178, 66, 22, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(176, 96, 52, 0, 0, 12, 8),GenMidi.Operator(178, 66, 22, 0,128, 0, 0), 12),],"Harmonica"), + GenMidi.Patch(0x04,129, 0,[GenMidi.Voice(GenMidi.Operator( 32,240, 5, 1,128, 18, 8),GenMidi.Operator( 49, 82, 5, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 32,240, 5, 1,128, 18, 0),GenMidi.Operator( 49, 82, 5, 2, 0, 0, 0), 0),],"Tango Accordion"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,241,245, 0,128, 13, 0),GenMidi.Operator( 32,241,246, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Acoustic Guitar (nylon)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,225,228, 1, 0, 13, 10),GenMidi.Operator( 48,242,227, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Acoustic Guitar (steel)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 0,241, 31, 2, 0, 33, 10),GenMidi.Operator( 0,244,136, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Electric Guitar (jazz)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 16,234, 50, 1,128, 7, 2),GenMidi.Operator( 16,210,231, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Electric Guitar (clean)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,224,244, 0,128, 18, 0),GenMidi.Operator( 48,242,245, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Electric Guitar (muted)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 0,241,255, 0, 0, 16, 10),GenMidi.Operator( 81,240,255, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0, 0, 0, 0, 0, 0),GenMidi.Operator( 0, 0, 0, 0, 0, 0, 0), 0),],"Overdriven Guitar"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 16,241,255, 0, 0, 13, 12),GenMidi.Operator( 81,240,255, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0, 0, 0, 0, 0, 0),GenMidi.Operator( 0, 0, 0, 0, 0, 0, 0), 0),],"Distortion Guitar"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 16,161,151, 2, 64, 3, 0),GenMidi.Operator( 17,225,231, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Guitar Harmonics"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,196, 32, 0, 0, 14, 0),GenMidi.Operator(176,195,246, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0, 0, 0, 0, 0, 0),GenMidi.Operator( 0, 0, 0, 0, 0, 0, 0), 0),],"Acoustic Bass"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,240,255, 0,128, 22, 10),GenMidi.Operator( 49,241,248, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Electric Bass (finger)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,224, 20, 0,128, 15, 8),GenMidi.Operator( 48,225,214, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Electric Bass (pick)"), + GenMidi.Patch(0x04,126, 0,[GenMidi.Voice(GenMidi.Operator(225, 81, 69, 1, 64, 13, 0),GenMidi.Operator(160,145, 70, 1, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator(161, 81, 69, 1, 64, 13, 0),GenMidi.Operator(160,129, 70, 1, 0, 0, 0), 0),],"Fretless Bass"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,240,231, 2, 0, 0, 0),GenMidi.Operator( 49,241, 71, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 16,245,231, 1, 0, 13, 13),GenMidi.Operator( 16,246,231, 2, 0, 0, 0), 0),],"* Slap Bass 1"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,240,229, 0,128, 16, 8),GenMidi.Operator( 49,241,245, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Slap Bass 2"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,244,245, 1, 0, 10, 10),GenMidi.Operator( 48,243,246, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Synth Bass 1"), + GenMidi.Patch(0x04,118, 0,[GenMidi.Voice(GenMidi.Operator( 48,131, 70, 1, 0, 21, 10),GenMidi.Operator( 49,210, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 48,131, 70, 1, 0, 21, 10),GenMidi.Operator( 49,210, 23, 0, 0, 0, 0), 0),],"Synth Bass 2"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 96, 80, 69, 1, 0, 23, 6),GenMidi.Operator(161, 97, 70, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Violin"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(240, 96, 68, 0,128, 15, 2),GenMidi.Operator(113, 65, 21, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Viola"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(176,208, 20, 2, 0, 15, 6),GenMidi.Operator( 97, 98, 23, 1,128, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Cello"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(240,177, 17, 2,128, 10, 6),GenMidi.Operator( 32,160, 21, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Contrabass"), + GenMidi.Patch(0x04,139, 0,[GenMidi.Voice(GenMidi.Operator(240,195, 1, 2,128, 9, 6),GenMidi.Operator( 97,131, 5, 0, 64, 0, 0), -12),GenMidi.Voice(GenMidi.Operator(112,179, 1, 2,128, 9, 6),GenMidi.Operator( 96,147, 5, 1, 64, 0, 0), 0),],"Tremolo Strings"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,248,249, 2,128, 23, 14),GenMidi.Operator( 32,118,230, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Pizzicato Strings"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 49,241, 53, 0, 0, 36, 0),GenMidi.Operator( 32,243,179, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Orchestral Harp"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 0,170,200, 0, 0, 4, 10),GenMidi.Operator( 16,210,179, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Timpani"), + GenMidi.Patch(0x04,120, 0,[GenMidi.Voice(GenMidi.Operator( 96,192, 4, 1, 64, 17, 4),GenMidi.Operator(177, 85, 4, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(160,144, 4, 1, 64, 18, 6),GenMidi.Operator( 49, 85, 4, 1,128, 0, 0), 0),],"String Ensemble 1"), + GenMidi.Patch(0x04,133, 0,[GenMidi.Voice(GenMidi.Operator( 32,144, 5, 1, 64, 17, 4),GenMidi.Operator(161, 53, 5, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(160,144, 5, 1, 64, 18, 6),GenMidi.Operator( 33, 53, 5, 1,128, 0, 0), 0),],"String Ensemble 2"), + GenMidi.Patch(0x04,123, 0,[GenMidi.Voice(GenMidi.Operator(161,105, 5, 2,128, 19, 10),GenMidi.Operator(241,102, 2, 2, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator(161,105, 5, 2,128, 19, 10),GenMidi.Operator(241,102, 2, 2, 0, 0, 0), -12),],"Synth Strings 1"), + GenMidi.Patch(0x04,132, 0,[GenMidi.Voice(GenMidi.Operator( 33, 17, 3, 0, 64, 13, 0),GenMidi.Operator( 32, 49, 4, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 17, 51, 2,128, 2, 8),GenMidi.Operator( 0, 49, 54, 1,128, 0, 0), 0),],"Synth Strings 2"), + GenMidi.Patch(0x04,138, 0,[GenMidi.Voice(GenMidi.Operator( 96,144, 84, 0, 64, 22, 0),GenMidi.Operator( 96,112, 4, 0, 64, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 32,144, 84, 0,128, 18, 0),GenMidi.Operator( 96,112, 4, 0,192, 0, 0), 0),],"Choir Aahs"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(160,177,183, 0,128, 25, 0),GenMidi.Operator(160,114,133, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 18,102,240, 0,192, 6, 12),GenMidi.Operator( 81,174,182, 0,192, 0, 0), -12),],"Voice Oohs"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(176, 96, 84, 0, 64, 26, 0),GenMidi.Operator(176, 48,116, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Synth Voice"), + GenMidi.Patch(0x04,128, 0,[GenMidi.Voice(GenMidi.Operator( 16, 48, 67, 0,128, 16, 2),GenMidi.Operator( 16,100, 20, 0, 0, 0, 0), -24),GenMidi.Voice(GenMidi.Operator(144, 80, 66, 0,128, 15, 2),GenMidi.Operator( 17, 84, 69, 0, 0, 0, 0), -12),],"Orchestra Hit"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,128, 21, 1,128, 14, 10),GenMidi.Operator( 48, 81, 54, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Trumpet"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(176,113, 31, 0, 0, 26, 14),GenMidi.Operator( 32,114, 59, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Trombone"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,128, 70, 0, 0, 22, 12),GenMidi.Operator( 32,146, 86, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0,128, 0, 0),GenMidi.Operator( 0, 0,240, 0,128, 0, 0), 0),],"Tuba"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(128,128,230, 1,128, 13, 12),GenMidi.Operator(144, 81,246, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Muted Trumpet"), + GenMidi.Patch(0x04,129, 0,[GenMidi.Voice(GenMidi.Operator( 32,112,184, 0, 0, 34, 14),GenMidi.Operator( 32, 97,150, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 32,112,184, 0, 0, 35, 14),GenMidi.Operator( 32, 97,150, 0,128, 0, 0), 0),],"French Horn"), + GenMidi.Patch(0x04,131, 0,[GenMidi.Voice(GenMidi.Operator( 32, 96, 21, 1,128, 14, 10),GenMidi.Operator( 48, 81, 54, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 48,112, 23, 1,128, 18, 14),GenMidi.Operator( 48, 97, 54, 1, 0, 0, 0), 0),],"Brass Section"), + GenMidi.Patch(0x04,134, 0,[GenMidi.Voice(GenMidi.Operator( 32,145,166, 2, 64, 13, 12),GenMidi.Operator( 32,129,151, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 32,145,166, 2,128, 12, 12),GenMidi.Operator( 32,145,151, 1, 0, 0, 0), 0),],"Synth Brass 1"), + GenMidi.Patch(0x04,134, 0,[GenMidi.Voice(GenMidi.Operator( 48,129,166, 2, 64, 16, 12),GenMidi.Operator( 48, 97,151, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 48,129,166, 2, 64, 10, 10),GenMidi.Operator( 48, 97,151, 1, 0, 0, 0), 0),],"Synth Bass 2"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(160, 96, 5, 0,128, 22, 6),GenMidi.Operator(177, 82, 22, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Soprano Sax"), + GenMidi.Patch(0x02,128, 0,[GenMidi.Voice(GenMidi.Operator(160,112, 6, 1,128, 9, 6),GenMidi.Operator(176, 98, 22, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Alto Sax"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(160,152, 11, 0, 64, 10, 10),GenMidi.Operator(176,115, 11, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Tenor Sax"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(160,144, 11, 1,128, 5, 10),GenMidi.Operator(176, 99, 27, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Baritone Sax"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(112,112, 22, 0,128, 16, 6),GenMidi.Operator(162, 92, 8, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Oboe"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,200, 7, 0, 64, 15, 10),GenMidi.Operator( 49,115, 7, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"English Horn"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,144, 25, 0,128, 17, 10),GenMidi.Operator( 49, 97, 27, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Bassoon"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,165, 23, 0,128, 13, 8),GenMidi.Operator(176, 99, 23, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Clarinet"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(240,110,143, 0,128, 0, 14),GenMidi.Operator(112, 53, 42, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Piccolo"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(160, 80,136, 0,128, 19, 8),GenMidi.Operator( 96, 85, 42, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Flute"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,101, 23, 0, 0, 10, 11),GenMidi.Operator(160,116, 39, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Recorder"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(176, 36, 39, 1,128, 4, 9),GenMidi.Operator(176, 69, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 23,240, 2, 0, 0, 14),GenMidi.Operator( 0, 37,240, 0, 0, 0, 0), 0),],"Pan Flute"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(225, 87, 4, 0,128, 45, 14),GenMidi.Operator( 96, 87, 55, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Bottle Blow"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(241, 87, 52, 3, 0, 40, 14),GenMidi.Operator(225,103, 93, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Shakuhachi"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(208, 49, 15, 0,192, 7, 11),GenMidi.Operator(112, 50, 5, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Whistle"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(176, 81, 5, 0,192, 7, 11),GenMidi.Operator( 48, 66, 41, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Ocarina"), + GenMidi.Patch(0x04,130, 0,[GenMidi.Voice(GenMidi.Operator( 34, 81, 91, 1, 64, 18, 0),GenMidi.Operator( 48, 96, 37, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 34,145, 91, 1, 64, 13, 0),GenMidi.Operator( 48,240, 37, 1, 0, 0, 0), 0),],"Lead 1 (square)"), + GenMidi.Patch(0x04,127, 0,[GenMidi.Voice(GenMidi.Operator( 32,193,155, 1, 64, 3, 8),GenMidi.Operator( 49,192,101, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 96,177,171, 1, 64, 1, 8),GenMidi.Operator( 49,241, 5, 0, 0, 0, 0), 0),],"Lead 2 (sawtooth)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(240, 87, 51, 3, 0, 40, 14),GenMidi.Operator(224,103, 7, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Lead 3 (calliope)"), + GenMidi.Patch(0x04,130, 0,[GenMidi.Voice(GenMidi.Operator(224, 87, 4, 3, 0, 35, 14),GenMidi.Operator(224,103, 77, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(224,247, 4, 3, 0, 35, 14),GenMidi.Operator(224,135, 77, 0, 0, 0, 0), 0),],"Lead 4 (chiffer)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(161,120, 11, 1, 64, 2, 8),GenMidi.Operator( 48,241, 43, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Lead 5 (charang)"), + GenMidi.Patch(0x04,122, 0,[GenMidi.Voice(GenMidi.Operator( 96,128, 85, 0, 0, 33, 8),GenMidi.Operator(224,242, 20, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 32,144, 85, 0, 0, 33, 8),GenMidi.Operator(160,162, 20, 0, 0, 0, 0), 0),],"Lead 6 (voice)"), + GenMidi.Patch(0x04,125, 0,[GenMidi.Voice(GenMidi.Operator( 32,193,149, 1, 64, 3, 10),GenMidi.Operator(176,112, 99, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(160,145,149, 1, 64, 9, 10),GenMidi.Operator( 49, 97, 99, 1, 0, 0, 0), -5),],"Lead 7 (5th sawtooth)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 36, 81, 7, 1, 64, 0, 9),GenMidi.Operator(160,253, 41, 2, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Lead 8 (bass & lead)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 36, 81, 7, 1, 64, 0, 9),GenMidi.Operator(160,253, 41, 2, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* Lead 8 (bass & lead)"), + GenMidi.Patch(0x04,130, 0,[GenMidi.Voice(GenMidi.Operator(128, 50, 5, 0,192, 0, 9),GenMidi.Operator( 96, 51, 5, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 64, 50, 5, 0, 64, 0, 9),GenMidi.Operator(224, 51, 5, 0, 0, 0, 0), 0),],"Pad 2 (warm)"), + GenMidi.Patch(0x04,130, 0,[GenMidi.Voice(GenMidi.Operator(160,161,165, 2,128, 15, 12),GenMidi.Operator(160,161,150, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(160,161,165, 2,128, 15, 12),GenMidi.Operator(160,161,150, 1, 0, 0, 0), 0),],"Pad 3 (polysynth)"), + GenMidi.Patch(0x04,139, 0,[GenMidi.Voice(GenMidi.Operator(224,240, 5, 0, 64, 4, 1),GenMidi.Operator( 96,129, 84, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(224,240, 5, 1, 64, 4, 1),GenMidi.Operator( 96,113, 84, 0,128, 0, 0), 0),],"Pad 4 (choir)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(128,161, 51, 0,128, 10, 7),GenMidi.Operator(224, 82, 84, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Pad 5 (bowed glass)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(129,128, 82, 1,128, 29, 14),GenMidi.Operator( 64, 35, 83, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Pad 6 (metal)"), + GenMidi.Patch(0x04,126, 0,[GenMidi.Voice(GenMidi.Operator(225, 81, 69, 1, 64, 13, 0),GenMidi.Operator(160,145, 70, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(161, 81, 69, 1, 64, 13, 0),GenMidi.Operator(160,129, 70, 1, 0, 0, 0), 0),],"Pad 7 (halo)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(225, 17, 82, 1,128, 12, 8),GenMidi.Operator(224,128,115, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Pad 8 (sweep)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,114, 71, 0, 64, 0, 11),GenMidi.Operator(131,248, 25, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"FX 1 (rain)"), + GenMidi.Patch(0x04,136, 0,[GenMidi.Voice(GenMidi.Operator( 0,133, 2, 1,192, 18, 10),GenMidi.Operator(193, 69, 18, 1, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 34, 69, 3, 0,192, 18, 10),GenMidi.Operator(227, 53, 53, 2, 0, 0, 0), -5),],"FX 2 (soundtrack)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 4,246,116, 0,192, 0, 0),GenMidi.Operator( 2,163, 36, 0, 0, 0, 0), -24),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* FX 3 (crystal)"), + GenMidi.Patch(0x04,126, 0,[GenMidi.Voice(GenMidi.Operator(144,192,210, 0,128, 14, 0),GenMidi.Operator( 48,209,210, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(144,208,210, 0,128, 14, 0),GenMidi.Operator( 48,241,210, 0, 0, 0, 0), 0),],"FX 4 (atmosphere)"), + GenMidi.Patch(0x04,116, 0,[GenMidi.Voice(GenMidi.Operator(208,144,243, 0, 0, 18, 0),GenMidi.Operator(192,194,243, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator(208,144,243, 0, 0, 18, 0),GenMidi.Operator(192,194,242, 0,128, 0, 0), 0),],"FX 5 (brightness)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(224, 19, 82, 1, 0, 26, 0),GenMidi.Operator(241, 51, 19, 2,128, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"FX 6 (goblin)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(224, 69,186, 0, 0, 26, 0),GenMidi.Operator(240, 50,145, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"FX 7 (echo drops)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 16, 88, 2, 1, 0, 24, 10),GenMidi.Operator( 2, 66,114, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"* FX 8 (star-theme)"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32, 99,179, 0, 0, 8, 2),GenMidi.Operator( 36, 99,179, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Sitar"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,119, 18, 0, 0, 13, 4),GenMidi.Operator( 16,243,244, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0,249,250, 2, 0, 10, 15),GenMidi.Operator( 0,249,250, 3, 64, 0, 0), 0),],"Banjo"), + GenMidi.Patch(0x04,128, 0,[GenMidi.Voice(GenMidi.Operator( 0,249, 51, 0,128, 0, 0),GenMidi.Operator( 0,244,115, 2,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 7,249,172, 2, 0, 26, 0),GenMidi.Operator( 15,249, 41, 2, 0, 0, 0), 0),],"Shamisen"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,242, 83, 1, 0, 33, 8),GenMidi.Operator( 34,145,228, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Koto"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 3,241, 57, 3, 64, 15, 6),GenMidi.Operator( 21,214,116, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Kalimba"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,137, 21, 1, 64, 2, 10),GenMidi.Operator( 33,107, 7, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Bag Pipe"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48,161, 3, 0, 0, 31, 14),GenMidi.Operator( 33, 82, 38, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Fiddle"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 48, 64, 19, 0, 0, 19, 8),GenMidi.Operator( 48, 97, 22, 1,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Shanai"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 19,161, 50, 0, 0, 0, 1),GenMidi.Operator( 18,178,114, 1,128, 0, 0), -7),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Tinkle Bell"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(149,231, 1, 0,128, 1, 4),GenMidi.Operator( 22,150,103, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Agogo"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 3,240, 4, 1, 64, 9, 6),GenMidi.Operator( 32,130, 5, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Steel Drums"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 19,248,209, 0, 64, 4, 6),GenMidi.Operator( 18,245,120, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Woodblock"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 16,167,236, 0, 0, 11, 0),GenMidi.Operator( 16,213,245, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Taiko Drum"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 32,168,200, 0, 0, 11, 0),GenMidi.Operator( 1,214,183, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Melodic Tom"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 0,248,196, 0, 0, 11, 0),GenMidi.Operator( 0,211,183, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Synth Drum"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 12, 65, 49, 0,128, 15, 14),GenMidi.Operator( 16, 33, 29, 3,128, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Reverse Cymbal"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 50, 52,179, 1, 0, 33, 14),GenMidi.Operator( 49, 84,247, 3, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Guitar Fret Noise"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(209, 55, 4, 0,128, 45, 14),GenMidi.Operator( 80, 55, 52, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Breath Noise"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 2, 62, 1, 2, 0, 0, 14),GenMidi.Operator( 8, 20,243, 2, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Seashore"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(245,235, 3, 0,192, 20, 7),GenMidi.Operator(246, 69,104, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Bird Tweet"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator(240,218,113, 1, 0, 0, 8),GenMidi.Operator(202,176, 23, 1,192, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Telephone Ring"), + GenMidi.Patch(0x01,128, 17,[GenMidi.Voice(GenMidi.Operator(240, 30, 17, 1, 0, 0, 8),GenMidi.Operator(226, 33, 17, 1,192, 0, 0), -24),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Helicopter"), + GenMidi.Patch(0x01,128, 65,[GenMidi.Voice(GenMidi.Operator(239, 83, 0, 2,128, 6, 14),GenMidi.Operator(239, 16, 2, 3,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Applause"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 12,240,240, 2, 0, 0, 14),GenMidi.Operator( 4,246,230, 0, 0, 0, 0), -12),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Gun Shot"), + GenMidi.Patch(0x01,128, 38,[GenMidi.Voice(GenMidi.Operator( 0,249, 87, 2, 0, 0, 0),GenMidi.Operator( 0,251, 70, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Acoustic Bass Drum"), + GenMidi.Patch(0x01,128, 25,[GenMidi.Voice(GenMidi.Operator( 0,250, 71, 0, 0, 0, 6),GenMidi.Operator( 0,249, 6, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Acoustic Bass Drum"), + GenMidi.Patch(0x01,128, 83,[GenMidi.Voice(GenMidi.Operator( 2,253,103, 0,128, 0, 6),GenMidi.Operator( 3,247,120, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Slide Stick"), + GenMidi.Patch(0x01,128, 32,[GenMidi.Voice(GenMidi.Operator( 15,247, 20, 2, 0, 5, 14),GenMidi.Operator( 0,249, 71, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Acoustic Snare"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator(225,136,251, 3, 0, 0, 15),GenMidi.Operator(255,166,168, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Hand Clap"), + GenMidi.Patch(0x05,128, 36,[GenMidi.Voice(GenMidi.Operator( 6,170,255, 0, 0, 0, 14),GenMidi.Operator( 0,247,250, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 63, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 42),],"Electric Snare"), + GenMidi.Patch(0x01,128, 15,[GenMidi.Voice(GenMidi.Operator( 2,245,108, 0, 0, 0, 7),GenMidi.Operator( 3,247, 56, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Floor Tom"), + GenMidi.Patch(0x01,128, 88,[GenMidi.Voice(GenMidi.Operator( 12,152, 94, 2, 0, 0, 15),GenMidi.Operator( 15,251, 6, 3, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Closed High-Hat"), + GenMidi.Patch(0x01,128, 19,[GenMidi.Voice(GenMidi.Operator( 2,245,120, 0, 0, 0, 7),GenMidi.Operator( 0,247, 55, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High Floor Tom"), + GenMidi.Patch(0x01,128, 88,[GenMidi.Voice(GenMidi.Operator( 12,120, 94, 2, 0, 0, 15),GenMidi.Operator( 10,138, 43, 3,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Pedal High Hat"), + GenMidi.Patch(0x01,128, 21,[GenMidi.Voice(GenMidi.Operator( 2,245, 55, 0, 0, 0, 3),GenMidi.Operator( 2,247, 55, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Tom"), + GenMidi.Patch(0x01,128, 79,[GenMidi.Voice(GenMidi.Operator( 0,199, 1, 2, 64, 5, 14),GenMidi.Operator( 11,249, 51, 2, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Open High Hat"), + GenMidi.Patch(0x01,128, 26,[GenMidi.Voice(GenMidi.Operator( 2,245, 55, 0, 0, 0, 3),GenMidi.Operator( 2,247, 55, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low-Mid Tom"), + GenMidi.Patch(0x01,128, 28,[GenMidi.Voice(GenMidi.Operator( 2,245, 55, 0, 0, 0, 3),GenMidi.Operator( 2,247, 55, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High-Mid Tom"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator( 4,194,230, 0, 0, 16, 14),GenMidi.Operator( 0,232, 67, 3, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Crash Cymbal 1"), + GenMidi.Patch(0x01,128, 32,[GenMidi.Voice(GenMidi.Operator( 2,245, 55, 0, 0, 0, 3),GenMidi.Operator( 2,247, 55, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High Tom"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator( 3,253, 18, 2,128, 0, 10),GenMidi.Operator( 2,253, 5, 2,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Ride Cymbal 1"), + GenMidi.Patch(0x01,128, 96,[GenMidi.Voice(GenMidi.Operator( 0,228,133, 0,128, 0, 14),GenMidi.Operator(192,215, 52, 2,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Chinses Cymbal"), + GenMidi.Patch(0x01,128, 72,[GenMidi.Voice(GenMidi.Operator( 4,226,230, 0,128, 16, 14),GenMidi.Operator( 1,184, 68, 1, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Ride Bell"), + GenMidi.Patch(0x01,128, 79,[GenMidi.Voice(GenMidi.Operator( 2,118,119, 2,128, 7, 15),GenMidi.Operator( 1,152,103, 3, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Tambourine"), + GenMidi.Patch(0x01,128, 69,[GenMidi.Voice(GenMidi.Operator( 4,246,112, 2,128, 1, 14),GenMidi.Operator( 7,198,163, 3, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Splash Cymbal"), + GenMidi.Patch(0x01,128, 71,[GenMidi.Voice(GenMidi.Operator( 0,253,103, 0, 0, 0, 6),GenMidi.Operator( 1,246,152, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Cowbell"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator( 4,194,230, 0, 0, 16, 14),GenMidi.Operator( 0,232, 67, 3, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Crash Cymbal 2"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 1,249,181, 0, 0, 7, 11),GenMidi.Operator(191,212, 80, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Vibraslap"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator( 3,253, 18, 2,128, 0, 10),GenMidi.Operator( 2,253, 5, 2,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Ride Cymbal 2"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator( 0,251, 86, 2, 0, 0, 4),GenMidi.Operator( 0,250, 38, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High Bongo"), + GenMidi.Patch(0x01,128, 54,[GenMidi.Voice(GenMidi.Operator( 0,251, 86, 2, 0, 0, 4),GenMidi.Operator( 0,250, 38, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Bango"), + GenMidi.Patch(0x01,128, 72,[GenMidi.Voice(GenMidi.Operator( 0,251, 86, 2,128, 0, 0),GenMidi.Operator( 0,247, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Mute High Conga"), + GenMidi.Patch(0x01,128, 67,[GenMidi.Voice(GenMidi.Operator( 0,251, 86, 2,128, 0, 0),GenMidi.Operator( 0,247, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Open High Conga"), + GenMidi.Patch(0x01,128, 60,[GenMidi.Voice(GenMidi.Operator( 0,251, 86, 2,128, 0, 0),GenMidi.Operator( 0,247, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Conga"), + GenMidi.Patch(0x01,128, 55,[GenMidi.Voice(GenMidi.Operator( 3,251, 86, 0,128, 1, 0),GenMidi.Operator( 0,247, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High Timbale"), + GenMidi.Patch(0x01,128, 48,[GenMidi.Voice(GenMidi.Operator( 3,251, 86, 0,128, 1, 0),GenMidi.Operator( 0,247, 23, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Timbale"), + GenMidi.Patch(0x01,128, 77,[GenMidi.Voice(GenMidi.Operator( 1,253,103, 3, 0, 0, 8),GenMidi.Operator( 1,246,152, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High Agogo"), + GenMidi.Patch(0x01,128, 72,[GenMidi.Voice(GenMidi.Operator( 1,253,103, 3, 0, 0, 8),GenMidi.Operator( 1,246,152, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Agogo"), + GenMidi.Patch(0x01,128, 88,[GenMidi.Voice(GenMidi.Operator( 12,120, 94, 2, 0, 0, 15),GenMidi.Operator( 10,138, 43, 3,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Cabasa"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 0, 90,214, 2, 0, 14, 10),GenMidi.Operator(191,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Maracas"), + GenMidi.Patch(0x01,128, 49,[GenMidi.Voice(GenMidi.Operator( 0,249,199, 1, 0, 7, 10),GenMidi.Operator(128,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Short Whistle"), + GenMidi.Patch(0x01,128, 49,[GenMidi.Voice(GenMidi.Operator( 0,249,199, 1, 0, 7, 10),GenMidi.Operator(128,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Long Whistle"), + GenMidi.Patch(0x01,128, 49,[GenMidi.Voice(GenMidi.Operator( 0,249,199, 1, 0, 7, 10),GenMidi.Operator(128,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Short Guiro"), + GenMidi.Patch(0x01,128, 49,[GenMidi.Voice(GenMidi.Operator( 0,249,199, 1, 0, 7, 10),GenMidi.Operator(128,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Long Guiro"), + GenMidi.Patch(0x01,128, 73,[GenMidi.Voice(GenMidi.Operator( 19,248,209, 1, 64, 4, 6),GenMidi.Operator( 18,245,120, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Claves"), + GenMidi.Patch(0x01,128, 68,[GenMidi.Voice(GenMidi.Operator( 19,248,209, 1, 64, 4, 6),GenMidi.Operator( 18,245,120, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"High Wood Block"), + GenMidi.Patch(0x01,128, 61,[GenMidi.Voice(GenMidi.Operator( 19,248,209, 1, 64, 4, 6),GenMidi.Operator( 18,245,120, 0, 0, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Low Wood Block"), + GenMidi.Patch(0x00,128, 0,[GenMidi.Voice(GenMidi.Operator( 1, 94,220, 1, 0, 11, 10),GenMidi.Operator(191,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Mute Cuica"), + GenMidi.Patch(0x01,128, 49,[GenMidi.Voice(GenMidi.Operator( 0,249,199, 1, 0, 7, 10),GenMidi.Operator(128,255,255, 0,192, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Open Cuica"), + GenMidi.Patch(0x01,128, 90,[GenMidi.Voice(GenMidi.Operator(197,242, 96, 0, 64, 15, 8),GenMidi.Operator(212,244,122, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Mute Triangle"), + GenMidi.Patch(0x01,128, 90,[GenMidi.Voice(GenMidi.Operator(133,242, 96, 1, 64, 15, 8),GenMidi.Operator(148,242,183, 0,128, 0, 0), 0),GenMidi.Voice(GenMidi.Operator( 0, 0,240, 0, 0, 0, 0),GenMidi.Operator( 0, 0,240, 0, 0, 0, 0), 0),],"Open Triangle"), + ]); + } + +private: + static struct SynthMidiChannel { + ubyte volume; + ushort volume_t; + ubyte pan; + ubyte reg_pan; + ubyte pitch; + const(GenMidi.Patch)* patch; + bool drum; + } + + static struct SynthVoice { + ubyte bank; + + ubyte op_base; + ubyte ch_base; + + uint freq; + + ubyte[2] tl; + ubyte additive; + + bool voice_dual; + const(GenMidi.Voice)* voice_data; + const(GenMidi.Patch)* patch; + + SynthMidiChannel* chan; + + ubyte velocity; + ubyte key; + ubyte note; + + int finetune; + + ubyte pan; + } + + static align(1) struct MidiHeader { + align(1): + char[4] header; + uint length; + ushort format; + ushort count; + ushort time; + ubyte[0] data; + } + + static align(1) struct MidiTrack { + align(1): + char[4] header; + uint length; + ubyte[0] data; + } + + static struct Track { + const(ubyte)* data; + const(ubyte)* pointer; + uint length; + uint time; + ubyte lastevent; + bool finish; + uint num; + } + + static align(1) struct MusHeader { + align(1): + char[4] header; + ushort length; + ushort offset; + } + +private: + // config + uint mSampleRate; + bool mOPL2Mode; + bool mStereo; + + // genmidi lump + version(genmidi_dumper) { + GenMidi mGenMidi; + bool mGenMidiLoaded; + } + + // OPL3 emulator + OPL3Chip chip; + + SynthMidiChannel[16] mSynthMidiChannels; + SynthVoice[18] mSynthVoices; + uint mSynthVoiceNum; + SynthVoice*[18] mSynthVoicesAllocated; + uint mSynthVoicesAllocatedNum; + SynthVoice*[18] mSynthVoicesFree; + uint mSynthVoicesFreeNum; + + Track[] mMidiTracks; + uint mMidiCount; + uint mMidiTimebase; + uint mMidiCallrate; + uint mMidiTimer; + uint mMidiTimechange; + uint mMidiRate; + uint mMidiFinished; + ubyte[16] mMidiChannels; + ubyte mMidiChannelcnt; + + const(ubyte)* mMusData; + const(ubyte)* mMusPointer; + ushort mMusLength; + uint mMusTimer; + uint mMusTimeend; + ubyte[16] mMusChanVelo; + + uint mSongTempo; + bool mPlayerActive; + bool mPlayLooped; + + enum DataFormat { + Unknown, + Midi, + Mus, + } + DataFormat mDataFormat = DataFormat.Unknown; + + uint mOPLCounter; + + ubyte[] songdata; + +private: + static ushort MISC_Read16LE (ushort n) pure nothrow @trusted @nogc { + const(ubyte)* m = cast(const(ubyte)*)&n; + return cast(ushort)(m[0]|(m[1]<<8)); + } + + static ushort MISC_Read16BE (ushort n) pure nothrow @trusted @nogc { + const(ubyte)* m = cast(const(ubyte)*)&n; + return cast(ushort)(m[1]|(m[0]<<8)); + } + + static uint MISC_Read32LE (uint n) pure nothrow @trusted @nogc { + const(ubyte)* m = cast(const(ubyte)*)&n; + return m[0]|(m[1]<<8)|(m[2]<<16)|(m[3]<<24); + } + + static uint MISC_Read32BE (uint n) pure nothrow @trusted @nogc { + const(ubyte)* m = cast(const(ubyte)*)&n; + return m[3]|(m[2]<<8)|(m[1]<<16)|(m[0]<<24); + } + + // ////////////////////////////////////////////////////////////////////// // + // synth + enum SynthCmd : ubyte { + NoteOff, + NoteOn, + PitchBend, + Patch, + Control, + } + + enum SynthCtl : ubyte { + Bank, + Modulation, + Volume, + Pan, + Expression, + Reverb, + Chorus, + Sustain, + Soft, + AllNoteOff, + MonoMode, + PolyMode, + Reset, + } + + void SynthResetVoice (ref SynthVoice voice) nothrow @trusted @nogc { + voice.freq = 0; + voice.voice_dual = false; + voice.voice_data = null; + voice.patch = null; + voice.chan = null; + voice.velocity = 0; + voice.key = 0; + voice.note = 0; + voice.pan = 0x30; + voice.finetune = 0; + voice.tl.ptr[0] = 0x3f; + voice.tl.ptr[1] = 0x3f; + } + + void SynthResetChip (ref OPL3Chip chip) nothrow @safe @nogc { + for (ushort i = 0x40; i < 0x56; ++i) chip.writeReg(i, 0x3f); + for (ushort i = 0x60; i < 0xf6; ++i) chip.writeReg(i, 0x00); + for (ushort i = 0x01; i < 0x40; ++i) chip.writeReg(i, 0x00); + + chip.writeReg(0x01, 0x20); + + if (!mOPL2Mode) { + chip.writeReg(0x105, 0x01); + for (ushort i = 0x140; i < 0x156; ++i) chip.writeReg(i, 0x3f); + for (ushort i = 0x160; i < 0x1f6; ++i) chip.writeReg(i, 0x00); + for (ushort i = 0x101; i < 0x140; ++i) chip.writeReg(i, 0x00); + chip.writeReg(0x105, 0x01); + } else { + chip.writeReg(0x105, 0x00); + } + } + + void SynthResetMidi (ref SynthMidiChannel channel) nothrow @trusted @nogc { + channel.volume = 100; + channel.volume_t = opl_voltable.ptr[channel.volume]+1; + channel.pan = 64; + channel.reg_pan = 0x30; + channel.pitch = 64; + channel.patch = &mGenMidi.patch.ptr[0]; + channel.drum = false; + if (&channel is &mSynthMidiChannels.ptr[15]) channel.drum = true; + } + + void SynthInit () nothrow @trusted @nogc { + static immutable ubyte[9] opl_slotoffset = [0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12]; + + for (uint i = 0; i < 18; ++i) { + mSynthVoices.ptr[i].bank = cast(ubyte)(i/9); + mSynthVoices.ptr[i].op_base = opl_slotoffset.ptr[i%9]; + mSynthVoices.ptr[i].ch_base = i%9; + SynthResetVoice(mSynthVoices.ptr[i]); + } + + for (uint i = 0; i < 16; ++i) SynthResetMidi(mSynthMidiChannels.ptr[i]); + + SynthResetChip(chip); + + mSynthVoiceNum = (mOPL2Mode ? 9 : 18); + + for (ubyte i = 0; i < mSynthVoiceNum; i++) mSynthVoicesFree.ptr[i] = &mSynthVoices.ptr[i]; + + mSynthVoicesAllocatedNum = 0; + mSynthVoicesFreeNum = mSynthVoiceNum; + } + + void SynthWriteReg (uint bank, ushort reg, ubyte data) nothrow @trusted @nogc { + reg |= bank<<8; + chip.writeReg(reg, data); + } + + void SynthVoiceOff (SynthVoice* voice) nothrow @trusted @nogc { + SynthWriteReg(voice.bank, cast(ushort)(0xb0+voice.ch_base), cast(ubyte)(voice.freq>>8)); + voice.freq = 0; + for (uint i = 0; i < mSynthVoicesAllocatedNum; ++i) { + if (mSynthVoicesAllocated.ptr[i] is voice) { + for (uint j = i; j < mSynthVoicesAllocatedNum-1; ++j) { + mSynthVoicesAllocated.ptr[j] = mSynthVoicesAllocated.ptr[j+1]; + } + break; + } + } + --mSynthVoicesAllocatedNum; + mSynthVoicesFree.ptr[mSynthVoicesFreeNum++] = voice; + } + + void SynthVoiceFreq (SynthVoice* voice) nothrow @trusted @nogc { + int freq = voice.chan.pitch+voice.finetune+32*voice.note; + uint block = 0; + + if (freq < 0) { + freq = 0; + } else if (freq >= 284) { + freq -= 284; + block = freq/384; + if (block > 7) block = 7; + freq %= 384; + freq += 284; + } + + freq = (block<<10)|opl_freqtable.ptr[freq]; + + SynthWriteReg(voice.bank, 0xa0+voice.ch_base, freq&0xff); + SynthWriteReg(voice.bank, cast(ushort)(0xb0+voice.ch_base), cast(ubyte)((freq>>8)|0x20)); + + voice.freq = freq; + } + + void SynthVoiceVolume (SynthVoice* voice) nothrow @trusted @nogc { + ubyte volume = cast(ubyte)(0x3f-(voice.chan.volume_t*opl_voltable.ptr[voice.velocity])/256); + if ((voice.tl.ptr[0]&0x3f) != volume) { + voice.tl.ptr[0] = (voice.tl.ptr[0]&0xc0)|volume; + SynthWriteReg(voice.bank, 0x43+voice.op_base, voice.tl.ptr[0]); + if (voice.additive) { + ubyte volume2 = cast(ubyte)(0x3f-voice.additive); + if (volume2 < volume) volume2 = volume; + volume2 |= voice.tl.ptr[1]&0xc0; + if (volume2 != voice.tl.ptr[1]) { + voice.tl.ptr[1] = volume2; + SynthWriteReg(voice.bank, 0x40+voice.op_base, voice.tl.ptr[1]); + } + } + } + } + + ubyte SynthOperatorSetup (uint bank, uint base, const(GenMidi.Operator)* op, bool volume) nothrow @trusted @nogc { + ubyte tl = op.ksl; + if (volume) tl |= 0x3f; else tl |= op.level; + SynthWriteReg(bank, cast(ushort)(0x40+base), tl); + SynthWriteReg(bank, cast(ushort)(0x20+base), op.mult); + SynthWriteReg(bank, cast(ushort)(0x60+base), op.attack); + SynthWriteReg(bank, cast(ushort)(0x80+base), op.sustain); + SynthWriteReg(bank, cast(ushort)(0xE0+base), op.wave); + return tl; + } + + void SynthVoiceOn (SynthMidiChannel* channel, const(GenMidi.Patch)* patch, bool dual, ubyte key, ubyte velocity) nothrow @trusted @nogc { + SynthVoice* voice; + const(GenMidi.Voice)* voice_data; + uint bank; + uint base; + int note; + + if (mSynthVoicesFreeNum == 0) return; + + voice = mSynthVoicesFree.ptr[0]; + + --mSynthVoicesFreeNum; + + for (uint i = 0; i < mSynthVoicesFreeNum; ++i) mSynthVoicesFree.ptr[i] = mSynthVoicesFree.ptr[i+1]; + + mSynthVoicesAllocated.ptr[mSynthVoicesAllocatedNum++] = voice; + + voice.chan = channel; + voice.key = key; + voice.velocity = velocity; + voice.patch = patch; + voice.voice_dual = dual; + + if (dual) { + voice_data = &patch.voice.ptr[1]; + voice.finetune = cast(int)(patch.finetune>>1)-64; + } else { + voice_data = &patch.voice.ptr[0]; + voice.finetune = 0; + } + + voice.pan = channel.reg_pan; + + if (voice.voice_data != voice_data) { + voice.voice_data = voice_data; + bank = voice.bank; + base = voice.op_base; + + voice.tl.ptr[0] = SynthOperatorSetup(bank, base+3, &voice_data.car, true); + + if (voice_data.mod.feedback&1) { + voice.additive = cast(ubyte)(0x3f-voice_data.mod.level); + voice.tl.ptr[1] = SynthOperatorSetup(bank, base, &voice_data.mod, true); + } else { + voice.additive = 0; + voice.tl.ptr[1] = SynthOperatorSetup(bank, base, &voice_data.mod, false); + } + } + + SynthWriteReg(voice.bank, 0xc0+voice.ch_base, voice_data.mod.feedback|voice.pan); + + if (MISC_Read16LE(patch.flags)&GenMidi.Patch.Flag.Fixed) { + note = patch.note; + } else { + if (channel.drum) { + note = 60; + } else { + note = key; + note += cast(short)MISC_Read16LE(cast(ushort)voice_data.offset); + while (note < 0) note += 12; + while (note > 95) note -= 12; + } + } + voice.note = cast(ubyte)note; + + SynthVoiceVolume(voice); + SynthVoiceFreq(voice); + } + + void SynthKillVoice () nothrow @trusted @nogc { + SynthVoice* voice; + if (mSynthVoicesFreeNum > 0) return; + voice = mSynthVoicesAllocated.ptr[0]; + for (uint i = 0; i < mSynthVoicesAllocatedNum; i++) { + if (mSynthVoicesAllocated.ptr[i].voice_dual || mSynthVoicesAllocated.ptr[i].chan >= voice.chan) { + voice = mSynthVoicesAllocated.ptr[i]; + } + } + SynthVoiceOff(voice); + } + + void SynthNoteOff (SynthMidiChannel* channel, ubyte note) nothrow @trusted @nogc { + for (uint i = 0; i < mSynthVoicesAllocatedNum; ) { + if (mSynthVoicesAllocated.ptr[i].chan is channel && mSynthVoicesAllocated.ptr[i].key == note) { + SynthVoiceOff(mSynthVoicesAllocated.ptr[i]); + } else { + ++i; + } + } + } + + void SynthNoteOn (SynthMidiChannel* channel, ubyte note, ubyte velo) nothrow @trusted @nogc { + const(GenMidi.Patch)* patch; + + if (velo == 0) { + SynthNoteOff(channel, note); + return; + } + + if (channel.drum) { + if (note < 35 || note > 81) return; + patch = &mGenMidi.patch.ptr[note-35+128]; + } else { + patch = channel.patch; + } + + SynthKillVoice(); + + SynthVoiceOn(channel, patch, false, note, velo); + + if (mSynthVoicesFreeNum > 0 && MISC_Read16LE(patch.flags)&GenMidi.Patch.Flag.DualVoice) { + SynthVoiceOn(channel, patch, true, note, velo); + } + } + + void SynthPitchBend (SynthMidiChannel* channel, ubyte pitch) nothrow @trusted @nogc { + SynthVoice*[18] mSynthChannelVoices; + SynthVoice*[18] mSynthOtherVoices; + + uint cnt1 = 0; + uint cnt2 = 0; + + channel.pitch = pitch; + + for (uint i = 0; i < mSynthVoicesAllocatedNum; ++i) { + if (mSynthVoicesAllocated.ptr[i].chan is channel) { + SynthVoiceFreq(mSynthVoicesAllocated.ptr[i]); + mSynthChannelVoices.ptr[cnt1++] = mSynthVoicesAllocated.ptr[i]; + } else { + mSynthOtherVoices.ptr[cnt2++] = mSynthVoicesAllocated.ptr[i]; + } + } + + for (uint i = 0; i < cnt2; ++i) mSynthVoicesAllocated.ptr[i] = mSynthOtherVoices.ptr[i]; + for (uint i = 0; i < cnt1; ++i) mSynthVoicesAllocated.ptr[i+cnt2] = mSynthChannelVoices.ptr[i]; + } + + void SynthUpdatePatch (SynthMidiChannel* channel, ubyte patch) nothrow @trusted @nogc { + if (patch >= mGenMidi.patch.length) patch = 0; + channel.patch = &mGenMidi.patch.ptr[patch]; + } + + void SynthUpdatePan (SynthMidiChannel* channel, ubyte pan) nothrow @trusted @nogc { + ubyte new_pan = 0x30; + if (pan <= 48) new_pan = 0x20; else if (pan >= 96) new_pan = 0x10; + channel.pan = pan; + if (channel.reg_pan != new_pan) { + channel.reg_pan = new_pan; + for (uint i = 0; i < mSynthVoicesAllocatedNum; ++i) { + if (mSynthVoicesAllocated.ptr[i].chan is channel) { + mSynthVoicesAllocated.ptr[i].pan = new_pan; + SynthWriteReg(mSynthVoicesAllocated.ptr[i].bank, + 0xc0+mSynthVoicesAllocated.ptr[i].ch_base, + mSynthVoicesAllocated.ptr[i].voice_data.mod.feedback|new_pan); + } + } + } + } + + void SynthUpdateVolume (SynthMidiChannel* channel, ubyte volume) nothrow @trusted @nogc { + if (volume&0x80) volume = 0x7f; + if (channel.volume != volume) { + channel.volume = volume; + channel.volume_t = opl_voltable.ptr[channel.volume]+1; + for (uint i = 0; i < mSynthVoicesAllocatedNum; ++i) { + if (mSynthVoicesAllocated.ptr[i].chan is channel) { + SynthVoiceVolume(mSynthVoicesAllocated.ptr[i]); + } + } + } + } + + void SynthNoteOffAll (SynthMidiChannel* channel) nothrow @trusted @nogc { + for (uint i = 0; i < mSynthVoicesAllocatedNum; ) { + if (mSynthVoicesAllocated.ptr[i].chan is channel) { + SynthVoiceOff(mSynthVoicesAllocated.ptr[i]); + } else { + ++i; + } + } + } + + void SynthEventReset (SynthMidiChannel* channel) nothrow @trusted @nogc { + SynthNoteOffAll(channel); + channel.reg_pan = 0x30; + channel.pan = 64; + channel.pitch = 64; + } + + void SynthReset () nothrow @trusted @nogc { + for (ubyte i = 0; i < 16; ++i) { + SynthNoteOffAll(&mSynthMidiChannels.ptr[i]); + SynthResetMidi(mSynthMidiChannels.ptr[i]); + } + } + + void SynthWrite (ubyte command, ubyte data1, ubyte data2) nothrow @trusted @nogc { + SynthMidiChannel* channel = &mSynthMidiChannels.ptr[command&0x0f]; + command >>= 4; + switch (command) { + case SynthCmd.NoteOff: SynthNoteOff(channel, data1); break; + case SynthCmd.NoteOn: SynthNoteOn(channel, data1, data2); break; + case SynthCmd.PitchBend: SynthPitchBend(channel, data2); break; + case SynthCmd.Patch: SynthUpdatePatch(channel, data1); break; + case SynthCmd.Control: + switch (data1) { + case SynthCtl.Volume: SynthUpdateVolume(channel, data2); break; + case SynthCtl.Pan: SynthUpdatePan(channel, data2); break; + case SynthCtl.AllNoteOff: SynthNoteOffAll(channel); break; + case SynthCtl.Reset: SynthEventReset(channel); break; + default: break; + } + break; + default: break; + } + } + + // ////////////////////////////////////////////////////////////////////// // + // MIDI + uint MIDI_ReadDelay (const(ubyte)** data) nothrow @trusted @nogc { + const(ubyte)* dn = *data; + uint delay = 0; + do { + delay = (delay<<7)|((*dn)&0x7f); + } while (*dn++&0x80); + *data = dn; + return delay; + } + + bool MIDI_LoadSong () nothrow @trusted { + enum midh = "MThd"; + enum mtrkh = "MTrk"; + + import core.stdc.string : memcmp; + + if (songdata.length <= MidiHeader.sizeof) return false; + + if (memcmp(songdata.ptr, "RIFF".ptr, 4) == 0) + songdata = songdata[0x14 .. $]; + + const(MidiHeader)* mid = cast(const(MidiHeader)*)songdata.ptr; + + if (memcmp(mid.header.ptr, midh.ptr, 4) != 0 || MISC_Read32BE(mid.length) != 6) return false; + + mMidiCount = MISC_Read16BE(mid.count); + const(ubyte)[] midi_data = mid.data.ptr[0..songdata.length-MidiHeader.sizeof]; + mMidiTimebase = MISC_Read16BE(mid.time); + + // if (mMidiTracks !is null) delete mMidiTracks; + + mMidiTracks = new Track[](mMidiCount); + + uint trknum = 0; + while (trknum < mMidiCount) { + if (midi_data.length < 8) { /*delete mMidiTracks;*/ return false; } // out of data + const(MidiTrack)* track = cast(const(MidiTrack)*)midi_data.ptr; + uint datasize = MISC_Read32BE(track.length); + if (midi_data.length-8 < datasize) { /*delete mMidiTracks;*/ return false; } // out of data + if (memcmp(track.header.ptr, mtrkh.ptr, 4) != 0) { + // not a track, skip this chunk + midi_data = midi_data[datasize+8..$]; + } else { + // track + mMidiTracks[trknum].length = datasize; + mMidiTracks[trknum].data = track.data.ptr; + mMidiTracks[trknum].num = trknum++; + // move to next chunk + midi_data = midi_data[datasize+8..$]; + } + } + // check if we have all tracks + if (trknum != mMidiCount) { /*delete mMidiTracks;*/ return false; } // out of tracks + + mDataFormat = DataFormat.Midi; + + return true; + } + + bool MIDI_StartSong () nothrow @trusted @nogc { + if (mDataFormat != DataFormat.Midi || mPlayerActive) return false; + + for (uint i = 0; i < mMidiCount; ++i) { + mMidiTracks[i].pointer = mMidiTracks[i].data; + mMidiTracks[i].time = MIDI_ReadDelay(&mMidiTracks[i].pointer); + mMidiTracks[i].lastevent = 0x80; + mMidiTracks[i].finish = 0; + } + + for (uint i = 0; i < 16; ++i) mMidiChannels.ptr[i] = 0xff; + + mMidiChannelcnt = 0; + + mMidiRate = 1000000/(500000/mMidiTimebase); + mMidiCallrate = mSongTempo; + mMidiTimer = 0; + mMidiTimechange = 0; + mMidiFinished = 0; + + mPlayerActive = true; + + return true; + } + + void MIDI_StopSong () nothrow @trusted @nogc { + if (mDataFormat != DataFormat.Midi || !mPlayerActive) return; + mPlayerActive = false; + for (uint i = 0; i < 16; ++i) { + SynthWrite(cast(ubyte)((SynthCmd.Control<<4)|i), SynthCtl.AllNoteOff, 0); + } + SynthReset(); + } + + Track* MIDI_NextTrack () nothrow @trusted @nogc { + Track* mintrack = &mMidiTracks[0]; + for (uint i = 1; i < mMidiCount; i++) { + if ((mMidiTracks[i].time < mintrack.time && !mMidiTracks[i].finish) || mintrack.finish) { + mintrack = &mMidiTracks[i]; + } + } + return mintrack; + } + + ubyte MIDI_GetChannel (ubyte chan) nothrow @trusted @nogc { + if (chan == 9) return 15; + if (mMidiChannels.ptr[chan] == 0xff) mMidiChannels.ptr[chan] = mMidiChannelcnt++; + return mMidiChannels.ptr[chan]; + } + + void MIDI_Command (const(ubyte)** datap, ubyte evnt) nothrow @trusted @nogc { + ubyte chan; + const(ubyte)* data; + ubyte v1, v2; + + data = *datap; + chan = MIDI_GetChannel(evnt&0x0f); + switch (evnt&0xf0) { + case 0x80: + v1 = *data++; + v2 = *data++; + SynthWrite((SynthCmd.NoteOff<<4)|chan, v1, 0); + break; + case 0x90: + v1 = *data++; + v2 = *data++; + SynthWrite((SynthCmd.NoteOn<<4)|chan, v1, v2); + break; + case 0xa0: + data += 2; + break; + case 0xb0: + v1 = *data++; + v2 = *data++; + switch (v1) { + case 0x00: case 0x20: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Bank, v2); break; + case 0x01: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Modulation, v2); break; + case 0x07: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Volume, v2); break; + case 0x0a: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Pan, v2); break; + case 0x0b: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Expression, v2); break; + case 0x40: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Sustain, v2); break; + case 0x43: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Soft, v2); break; + case 0x5b: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Reverb, v2); break; + case 0x5d: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Chorus, v2); break; + case 0x78: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.AllNoteOff, v2); break; + case 0x79: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Reset, v2); break; + case 0x7b: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.AllNoteOff, v2); break; + case 0x7e: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.MonoMode, v2); break; + case 0x7f: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.PolyMode, v2); break; + default: break; + } + break; + case 0xc0: + v1 = *data++; + SynthWrite((SynthCmd.Patch<<4)|chan, v1, 0); + break; + case 0xd0: + data += 1; + break; + case 0xe0: + v1 = *data++; + v2 = *data++; + SynthWrite((SynthCmd.PitchBend<<4)|chan, v1, v2); + break; + default: break; + } + *datap = data; + } + + void MIDI_FinishTrack (Track* trck) nothrow @trusted @nogc { + if (trck.finish) return; + trck.finish = true; + ++mMidiFinished; + } + + void MIDI_AdvanceTrack (Track* trck) nothrow @trusted @nogc { + ubyte evnt; + ubyte meta; + ubyte length; + uint tempo; + const(ubyte)* data; + + evnt = *trck.pointer++; + + if (!(evnt&0x80)) { + evnt = trck.lastevent; + --trck.pointer; + } + + switch (evnt) { + case 0xf0: + case 0xf7: + length = cast(ubyte)MIDI_ReadDelay(&trck.pointer); + trck.pointer += length; + break; + case 0xff: + meta = *trck.pointer++; + length = cast(ubyte)MIDI_ReadDelay(&trck.pointer); + data = trck.pointer; + trck.pointer += length; + switch (meta) { + case 0x2f: + MIDI_FinishTrack(trck); + break; + case 0x51: + if (length == 0x03) { + tempo = (data[0]<<16)|(data[1]<<8)|data[2]; + mMidiTimechange += (mMidiTimer*mMidiRate)/mMidiCallrate; + mMidiTimer = 0; + mMidiRate = 1000000/(tempo/mMidiTimebase); + } + break; + default: break; + } + break; + default: + MIDI_Command(&trck.pointer,evnt); + break; + } + + trck.lastevent = evnt; + if (trck.pointer >= trck.data+trck.length) MIDI_FinishTrack(trck); + } + + void MIDI_Callback () nothrow @trusted @nogc { + Track* trck; + + if (mDataFormat != DataFormat.Midi || !mPlayerActive) return; + + for (;;) { + trck = MIDI_NextTrack(); + if (trck.finish || trck.time > mMidiTimechange+(mMidiTimer*mMidiRate)/mMidiCallrate) break; + MIDI_AdvanceTrack(trck); + if (!trck.finish) trck.time += MIDI_ReadDelay(&trck.pointer); + } + + ++mMidiTimer; + + if (mMidiFinished == mMidiCount) { + if (!mPlayLooped) MIDI_StopSong(); + for (uint i = 0; i < mMidiCount; i++) { + mMidiTracks[i].pointer = mMidiTracks[i].data; + mMidiTracks[i].time = MIDI_ReadDelay(&mMidiTracks[i].pointer); + mMidiTracks[i].lastevent = 0x80; + mMidiTracks[i].finish = 0; + } + + for (uint i = 0; i < 16; i++) mMidiChannels.ptr[i] = 0xff; + + mMidiChannelcnt = 0; + + mMidiRate = 1000000/(500000/mMidiTimebase); + mMidiTimer = 0; + mMidiTimechange = 0; + mMidiFinished = 0; + + SynthReset(); + } + } + + // ////////////////////////////////////////////////////////////////////// // + // MUS + void MUS_Callback () nothrow @trusted @nogc { + if (mDataFormat != DataFormat.Mus || !mPlayerActive) return; + while (mMusTimer == mMusTimeend) { + ubyte cmd; + ubyte evnt; + ubyte chan; + ubyte data1; + ubyte data2; + + cmd = *mMusPointer++; + chan = cmd&0x0f; + evnt = (cmd>>4)&7; + + switch (evnt) { + case 0x00: + data1 = *mMusPointer++; + SynthWrite((SynthCmd.NoteOff<<4)|chan, data1, 0); + break; + case 0x01: + data1 = *mMusPointer++; + if (data1&0x80) { + data1 &= 0x7f; + mMusChanVelo.ptr[chan] = *mMusPointer++; + } + SynthWrite((SynthCmd.NoteOn<<4)|chan, data1, mMusChanVelo.ptr[chan]); + break; + case 0x02: + data1 = *mMusPointer++; + SynthWrite((SynthCmd.PitchBend<<4)|chan, (data1&1)<<6, data1>>1); + break; + case 0x03: + case 0x04: + data1 = *mMusPointer++; + data2 = 0; + if (evnt == 0x04) data2 = *mMusPointer++; + switch (data1) { + case 0x00: SynthWrite((SynthCmd.Patch<<4)|chan, data2, 0); break; + case 0x01: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Bank, data2); break; + case 0x02: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Modulation, data2); break; + case 0x03: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Volume, data2); break; + case 0x04: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Pan, data2); break; + case 0x05: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Expression, data2); break; + case 0x06: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Reverb, data2); break; + case 0x07: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Chorus, data2); break; + case 0x08: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Sustain, data2); break; + case 0x09: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Soft, data2); break; + case 0x0a: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.AllNoteOff, data2); break; + case 0x0b: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.AllNoteOff, data2); break; + case 0x0c: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.MonoMode, data2); break; + case 0x0d: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.PolyMode, data2); break; + case 0x0e: SynthWrite((SynthCmd.Control<<4)|chan, SynthCtl.Reset, data2); break; + case 0x0f: break; + default: break; + } + break; + case 0x05: + break; + case 0x06: + if (!mPlayLooped) { + MUS_StopSong(); + return; + } + mMusPointer = mMusData; + cmd = 0; + SynthReset(); + break; + case 0x07: + ++mMusPointer; + break; + default: break; + } + + if (cmd&0x80) { + mMusTimeend += MIDI_ReadDelay(&mMusPointer); + break; + } + } + ++mMusTimer; + } + + bool MUS_LoadSong () nothrow @trusted @nogc { + enum mush = "MUS\x1a"; + import core.stdc.string : memcmp; + if (songdata.length <= MusHeader.sizeof) return false; + const(MusHeader)* mus = cast(const(MusHeader)*)songdata.ptr; + if (memcmp(mus.header.ptr, mush.ptr, 4) != 0) return false; + mMusLength = MISC_Read16LE(mus.length); + uint musofs = MISC_Read16LE(mus.offset); + if (musofs >= songdata.length) return false; + if (songdata.length-musofs < mMusLength) return false; + mMusData = &(cast(const(ubyte)*)songdata.ptr)[musofs]; + mDataFormat = DataFormat.Mus; + return true; + } + + bool MUS_StartSong () nothrow @trusted @nogc { + if (mDataFormat != DataFormat.Mus || mPlayerActive) return true; + mMusPointer = mMusData; + mMusTimer = 0; + mMusTimeend = 0; + mPlayerActive = true; + return false; + } + + void MUS_StopSong () nothrow @trusted @nogc { + if (mDataFormat != DataFormat.Mus || !mPlayerActive) return; + mPlayerActive = false; + for (uint i = 0; i < 16; i++) { + SynthWrite(cast(ubyte)((SynthCmd.Control<<4)|i), SynthCtl.AllNoteOff, 0); + } + SynthReset(); + } + + void PlayerInit () nothrow @trusted @nogc { + mSongTempo = DefaultTempo; + mPlayerActive = false; + mDataFormat = DataFormat.Unknown; + mPlayLooped = false; + mMidiTracks = null; + } + + version(genmidi_dumper) static immutable string genmidiData = import("GENMIDI.lmp"); + + version(genmidi_dumper) bool loadGenMIDI (const(void)* data) nothrow @trusted @nogc { + import core.stdc.string : memcmp, memcpy; + static immutable string genmidi_head = "#OPL_II#"; + if (memcmp(data, data, 8) != 0) return false; + memcpy(&mGenMidi, (cast(const(ubyte)*)data)+8, GenMidi.sizeof); + mGenMidiLoaded = true; + return true; + } + + version(genmidi_dumper) public void dumpGenMidi (VFile fo) { mGenMidi.dump(fo); } + +public: + enum DefaultTempo = 140; + +public: + this (int asamplerate=48000, bool aopl3mode=true, bool astereo=true) nothrow @trusted @nogc { + version(genmidi_dumper) mGenMidiLoaded = false; + songdata = null; + sendConfig(asamplerate, aopl3mode, astereo); + SynthInit(); + PlayerInit(); + mOPLCounter = 0; + version(genmidi_dumper) loadGenMIDI(genmidiData.ptr); + } + + private void sendConfig (int asamplerate, bool aopl3mode, bool astereo) nothrow @safe @nogc { + if (asamplerate < 4096) asamplerate = 4096; + if (asamplerate > 96000) asamplerate = 96000; + mSampleRate = asamplerate; + chip.reset(mSampleRate); + mOPL2Mode = !aopl3mode; + mStereo = astereo; + SynthInit(); + } + + bool load (const(void)[] data) { + import core.stdc.string : memcpy; + stop(); // just in case + mDataFormat = DataFormat.Unknown; + //delete songdata; + version(genmidi_dumper) if (!mGenMidiLoaded) return false; + // just in case + scope(failure) { + mDataFormat = DataFormat.Unknown; + //delete songdata; + } + if (data.length == 0) return false; + songdata.length = data.length; + memcpy(songdata.ptr, data.ptr, data.length); + if (MUS_LoadSong() || MIDI_LoadSong()) return true; + mDataFormat = DataFormat.Unknown; + //delete songdata; + return false; + } + + @property void tempo (uint atempo) pure nothrow @safe @nogc { + if (atempo < 1) atempo = 1; else if (atempo > 255) atempo = 255; + mSongTempo = atempo; + } + + @property uint tempo () const pure nothrow @safe @nogc { return mSongTempo; } + + @property void looped (bool loop) pure nothrow @safe @nogc { mPlayLooped = loop; } + @property bool looped () const pure nothrow @safe @nogc { return mPlayLooped; } + + @property bool loaded () const pure nothrow @safe @nogc { return (mDataFormat != DataFormat.Unknown); } + + @property bool playing () const pure nothrow @safe @nogc { return mPlayerActive; } + + @property void stereo (bool v) pure nothrow @safe @nogc { mStereo = v; } + @property bool stereo () const pure nothrow @safe @nogc { return mStereo; } + + // returns `false` if song cannot be started (or if it is already playing) + bool play () nothrow @safe @nogc { + bool res = false; + final switch (mDataFormat) { + case DataFormat.Unknown: break; + case DataFormat.Midi: res = MIDI_StartSong(); break; + case DataFormat.Mus: res = MUS_StartSong(); break; + } + return res; + } + + void stop () nothrow @safe @nogc { + final switch (mDataFormat) { + case DataFormat.Unknown: break; + case DataFormat.Midi: MIDI_StopSong(); break; + case DataFormat.Mus: MUS_StopSong(); break; + } + } + + // return number of generated *frames* + // returns 0 if song is complete (and player is not looped) + uint generate (short[] buffer) nothrow @trusted @nogc { + if (mDataFormat == DataFormat.Unknown) return 0; + if (buffer.length > uint.max/64) buffer = buffer[0..uint.max/64]; + uint length = cast(uint)buffer.length; + if (mStereo) length /= 2; + if (length < 1) return 0; // oops + short[2] accm = void; + uint i = 0; + while (i < length) { + if (!mPlayerActive) break; + while (mOPLCounter >= mSampleRate) { + if (mPlayerActive) { + final switch (mDataFormat) { + case DataFormat.Unknown: assert(0, "the thing that should not be"); + case DataFormat.Midi: MIDI_Callback(); break; + case DataFormat.Mus: MUS_Callback(); break; + } + } + mOPLCounter -= mSampleRate; + } + mOPLCounter += mSongTempo; + chip.generateResampled(accm.ptr); + if (mStereo) { + buffer.ptr[i*2] = accm.ptr[0]; + buffer.ptr[i*2+1] = accm.ptr[1]; + } else { + buffer.ptr[i] = (accm.ptr[0]+accm.ptr[1])/2; + } + ++i; + } + return i; + } +} diff --git a/simpleaudio.d b/simpleaudio.d index 467edc7..faf1911 100644 --- a/simpleaudio.d +++ b/simpleaudio.d @@ -204,21 +204,30 @@ interface SampleController { object. +/ void stop(); + /++ + Reports the current stream position, in seconds, if available (NaN if not). + +/ + float position(); } -class DummySample : SampleController { +private class DummySample : SampleController { void pause() {} void resume() {} void stop() {} + float position() { return float.init; } } -class SampleControlFlags : SampleController { +private class SampleControlFlags : SampleController { void pause() { paused = true; } void resume() { paused = false; } void stop() { stopped = true; } bool paused; bool stopped; + + float position() { return currentPosition; } + + float currentPosition = 0.0; } /++ @@ -246,6 +255,8 @@ struct AudioOutputThread { History: Parameter `default` added on Nov 8, 2020. + + The sample rate parameter was not correctly applied to the device on Linux until December 24, 2020. +/ this(bool enable, int SampleRate = 44100, int channels = 2, string device = "default") { if(enable) { @@ -305,6 +316,12 @@ struct AudioOutputThread { else static assert(0); } + SampleController playEmulatedOpl3Midi()(string filename) { + if(impl) + return impl.playEmulatedOpl3Midi(filename); + return new DummySample; + } + SampleController playOgg()(string filename, bool loop = false) { if(impl) return impl.playOgg(filename, loop); @@ -485,10 +502,60 @@ final class AudioPcmOutThreadImplementation : Thread { } /++ - Requires vorbis.d to be compiled in (module arsd.vorbis) + Plays the given midi files with the nuked opl3 emulator. + + Requires nukedopl3.d (module [arsd.nukedopl3]) to be compiled in, which is GPL. + History: + Added December 24, 2020. + License: + If you use this function, you are opting into the GPL version 2 or later. + Authors: + Based on ketmar's code. + +/ + SampleController playEmulatedOpl3Midi()(string filename, bool loop = false) { + import arsd.nukedopl3; + auto scf = new SampleControlFlags; + + import std.file; + auto bytes = cast(ubyte[]) std.file.read(filename); + auto player = new OPLPlayer(this.SampleRate, true, channels == 2); + player.looped = loop; + player.load(bytes); + player.play(); + + addChannel( + delegate bool(short[] buffer) { + if(scf.paused) { + buffer[] = 0; + return true; + } + + if(!player.playing) + return false; + + auto pos = player.generate(buffer[]); + scf.currentPosition += cast(float) buffer.length / SampleRate/ channels; + if(pos == 0) + return false; + return !scf.stopped; + } + ); + + return scf; + } + + /++ + Requires vorbis.d to be compiled in (module arsd.vorbis) + + Returns: + An implementation of [SampleController] which lets you pause, etc., the file. + + Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playWav], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible. History: Automatic resampling support added Nov 7, 2020. + + Return value changed from `void` to a sample control object on December 23, 2020. +/ SampleController playOgg()(string filename, bool loop = false) { import arsd.vorbis; @@ -522,10 +589,13 @@ final class AudioPcmOutThreadImplementation : Thread { if(got == 0) { if(loop) { v.seekStart(); + scf.currentPosition = 0; return true; } return false; + } else { + scf.currentPosition += cast(float) got / v.sampleRate; } return !scf.stopped; } @@ -558,12 +628,20 @@ final class AudioPcmOutThreadImplementation : Thread { } /++ - Requires mp3.d to be compiled in (module arsd.mp3) which is LGPL licensed. + Requires mp3.d to be compiled in (module [arsd.mp3]) which is LGPL licensed. + That LGPL license will extend to your code. + + Returns: + An implementation of [SampleController] which lets you pause, etc., the file. + + Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playOgg] and [playWav], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible. History: Automatic resampling support added Nov 7, 2020. + + Return value changed from `void` to a sample control object on December 23, 2020. +/ - SampleControlFlags playMp3()(string filename) { + SampleController playMp3()(string filename) { import arsd.mp3; import std.stdio; @@ -597,10 +675,14 @@ final class AudioPcmOutThreadImplementation : Thread { if(next.length >= buffer.length) { buffer[] = next[0 .. buffer.length]; next = next[buffer.length .. $]; + + scf.currentPosition += cast(float) buffer.length / mp3.sampleRate / mp3.channels; } else { buffer[0 .. next.length] = next[]; buffer = buffer[next.length .. $]; + scf.currentPosition += cast(float) next.length / mp3.sampleRate / mp3.channels; + next = next[$..$]; if(buffer.length) { @@ -680,8 +762,14 @@ final class AudioPcmOutThreadImplementation : Thread { Also requires the resampler to be compiled in at this time, but that may change in the future, I was just lazy. + Returns: + An implementation of [SampleController] which lets you pause, etc., the file. + + Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playOgg], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible. History: Added Nov 8, 2020. + + Return value changed from `void` to a sample control object on December 23, 2020. +/ SampleController playWav()(string filename) { auto scf = new SampleControlFlags; @@ -984,7 +1072,7 @@ final class AudioPcmOutThreadImplementation : Thread { assert(!err); } - AudioOutput ao = AudioOutput(0); + AudioOutput ao = AudioOutput(device, SampleRate, channels); this.ao = &ao; scope(exit) this.ao = null; auto omg = this; @@ -1610,7 +1698,7 @@ version(Posix) { private sigaction_t oldSigIntr; void setSigIntHandler() { sigaction_t n; - n.sa_handler = &interruptSignalHandler; + n.sa_handler = &interruptSignalHandlerSAudio; n.sa_mask = cast(sigset_t) 0; n.sa_flags = 0; sigaction(SIGINT, &n, &oldSigIntr); @@ -1623,7 +1711,7 @@ version(Posix) { private extern(C) - void interruptSignalHandler(int sigNumber) nothrow { + void interruptSignalHandlerSAudio(int sigNumber) nothrow { interrupted = true; } } @@ -3728,6 +3816,8 @@ abstract class ResamplingContext { resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $]; } } + + scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels; } else if(outputChannels == 2) { foreach(idx, ref s; buffer) { if(resamplerDataLeft.dataOut.length == 0) { @@ -3749,6 +3839,8 @@ abstract class ResamplingContext { } } } + + scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels; } else assert(0); return !scflags.stopped;