From 2678f0c510cf6814698f8c773e7808946047d5d0 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Mon, 16 Apr 2018 04:44:44 +0200 Subject: transform the note map into a fixed array --- src/adlmidi_midiplay.cpp | 86 ++++++++++++++++++++++++++---------------------- src/adlmidi_private.hpp | 71 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index ed7f44a..965dc3c 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -967,10 +967,10 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if((opl.m_musicMode == OPL3::MODE_RSXX) && (velocity != 0)) { // Check if this is just a note after-touch - MIDIchannel::activenoteiterator i = Ch[channel].activenotes.find(note); - if(i != Ch[channel].activenotes.end()) + MIDIchannel::activenoteiterator i = Ch[channel].activenotes_find(note); + if(i) { - i->second.vol = velocity; + i->vol = velocity; NoteUpdate(channel, i, Upd_Volume); return false; } @@ -1196,11 +1196,11 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) // Allocate active note for MIDI channel std::pair - ir = Ch[channel].activenotes.insert(std::make_pair(note, MIDIchannel::NoteInfo())); - ir.first->second.vol = velocity; - ir.first->second.tone = tone; - ir.first->second.midiins = midiins; - ir.first->second.insmeta = meta; + ir = Ch[channel].activenotes_insert(note); + ir.first->vol = velocity; + ir.first->tone = tone; + ir.first->midiins = midiins; + ir.first->insmeta = meta; for(unsigned ccount = 0; ccount < 2; ++ccount) { @@ -1208,7 +1208,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if(c < 0) continue; uint16_t chipChan = static_cast(adlchannel[ccount]); - ir.first->second.phys[chipChan] = voices[ccount]; + ir.first->phys[chipChan] = voices[ccount]; } NoteUpdate(channel, ir.first, Upd_All | Upd_Patch); return true; @@ -1224,13 +1224,13 @@ void MIDIplay::realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t at { channel = channel % 16; MIDIchannel::activenoteiterator - i = Ch[channel].activenotes.find(note); - if(i == Ch[channel].activenotes.end()) + i = Ch[channel].activenotes_find(note); + if(!i) { // Ignore touch if note is not active return; } - i->second.vol = 127 - atVal; + i->vol = 127 - atVal; NoteUpdate(channel, i, Upd_Volume); } @@ -1239,12 +1239,10 @@ void MIDIplay::realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal) // TODO: Verify, is this correct action? channel = channel % 16; for(MIDIchannel::activenoteiterator - i = Ch[channel].activenotes.begin(); - i != Ch[channel].activenotes.end(); - ++i) + i = Ch[channel].activenotes_begin(); i; ++i) { // Set this pressure to all active notes on the channel - i->second.vol = 127 - atVal; + i->vol = 127 - atVal; } NoteUpdate_All(channel, Upd_Volume); @@ -1436,7 +1434,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, unsigned props_mask, int32_t select_adlchn) { - MIDIchannel::NoteInfo &info = i->second; + MIDIchannel::NoteInfo &info = *i; const int16_t tone = info.tone; const uint8_t vol = info.vol; const int midiins = info.midiins; @@ -1444,7 +1442,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, const adlinsdata &ains = opl.GetAdlMetaIns(insmeta); AdlChannel::Location my_loc; my_loc.MidCh = MidCh; - my_loc.note = i->first; + my_loc.note = info.note; for(MIDIchannel::NoteInfo::PhysMap::iterator jnext = info.phys.begin(); @@ -1640,7 +1638,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } if(info.phys.empty()) - Ch[MidCh].activenotes.erase(i); + Ch[MidCh].activenotes_erase(i); } #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -2150,10 +2148,10 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not else s -= (j->second.kon_time_until_neglible / 2); - MIDIchannel::activenotemap_t::const_iterator - k = Ch[j->first.MidCh].activenotes.find(j->first.note); + MIDIchannel::activenoteiterator + k = const_cast(Ch[j->first.MidCh]).activenotes_find(j->first.note); - if(k != Ch[j->first.MidCh].activenotes.end()) + if(k) { // Same instrument = good if(j->second.ins == ins) @@ -2166,7 +2164,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not } // Percussion is inferior to melody - s += 50 * (int64_t)(k->second.midiins / 128); + s += 50 * (int64_t)(k->midiins / 128); /* if(k->second.midiins >= 25 && k->second.midiins < 40 @@ -2225,7 +2223,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo // Collision: Kill old note, // UNLESS we're going to do arpeggio MIDIchannel::activenoteiterator i - (Ch[j->first.MidCh].activenotes.find(j->first.note)); + (Ch[j->first.MidCh].activenotes_find(j->first.note)); // Check if we can do arpeggio. if((j->second.vibdelay < 70 @@ -2284,17 +2282,17 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator { hooks.onNote(hooks.onNote_userData, (int)from_channel, - i->second.tone, - i->second.midiins, 0, 0.0); + i->tone, + i->midiins, 0, 0.0); hooks.onNote(hooks.onNote_userData, (int)c, - i->second.tone, - i->second.midiins, - i->second.vol, 0.0); + i->tone, + i->midiins, + i->vol, 0.0); } - i->second.phys.erase(static_cast(from_channel)); - i->second.phys[cs] = j->second.ins; + i->phys.erase(static_cast(from_channel)); + i->phys[cs] = j->second.ins; ch[cs].users.insert(*j); ch[from_channel].users.erase(j); return; @@ -2402,9 +2400,7 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB) void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask) { for(MIDIchannel::activenoteiterator - i = Ch[MidCh].activenotes.begin(); - i != Ch[MidCh].activenotes.end(); - ) + i = Ch[MidCh].activenotes_begin(); i;) { MIDIchannel::activenoteiterator j(i++); NoteUpdate(MidCh, j, props_mask); @@ -2414,9 +2410,9 @@ void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask) void MIDIplay::NoteOff(uint16_t MidCh, uint8_t note) { MIDIchannel::activenoteiterator - i = Ch[MidCh].activenotes.find(note); + i = Ch[MidCh].activenotes_find(note); - if(i != Ch[MidCh].activenotes.end()) + if(i) NoteUpdate(MidCh, i, Upd_Off); } @@ -2425,7 +2421,7 @@ void MIDIplay::UpdateVibrato(double amount) { for(size_t a = 0, b = Ch.size(); a < b; ++a) { - if(Ch[a].vibrato && !Ch[a].activenotes.empty()) + if(Ch[a].vibrato && !Ch[a].activenotes_empty()) { NoteUpdate_All(static_cast(a), Upd_Pitch); Ch[a].vibpos += amount * Ch[a].vibspeed; @@ -2447,7 +2443,17 @@ uint64_t MIDIplay::ChooseDevice(const std::string &name) size_t n = devices.size() * 16; devices.insert(std::make_pair(name, n)); - Ch.resize(n + 16); + + size_t channelsBefore = Ch.size(); + size_t channels = n + 16; + Ch.resize(channels); + + for(size_t ch = channelsBefore; ch < channels; ++ch) { + for(unsigned i = 0; i < 128; ++i) { + Ch[ch].activenotes[i].note = i; + Ch[ch].activenotes[i].active = false; + } + } return n; } @@ -2504,7 +2510,7 @@ retry_arpeggio: { NoteUpdate( i->first.MidCh, - Ch[ i->first.MidCh ].activenotes.find(i->first.note), + Ch[ i->first.MidCh ].activenotes_find(i->first.note), Upd_Off, static_cast(c)); goto retry_arpeggio; @@ -2512,7 +2518,7 @@ retry_arpeggio: NoteUpdate( i->first.MidCh, - Ch[ i->first.MidCh ].activenotes.find(i->first.note), + Ch[ i->first.MidCh ].activenotes_find(i->first.note), Upd_Pitch | Upd_Volume | Upd_Pan, static_cast(c)); } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 271bc5e..0ecdfff 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -580,6 +580,8 @@ public: bool is_xg_percussion; struct NoteInfo { + uint8_t note; + bool active; // Current pressure uint8_t vol; char ____padding[1]; @@ -610,10 +612,73 @@ public: // List of OPL3 channels it is currently occupying. std::map phys; }; - typedef std::map activenotemap_t; - typedef activenotemap_t::iterator activenoteiterator; char ____padding2[5]; - activenotemap_t activenotes; + NoteInfo activenotes[128]; + + struct activenoteiterator + { + explicit activenoteiterator(NoteInfo *info = 0) + : ptr(info) {} + activenoteiterator &operator++() + { + if(ptr->note == 127) + ptr = 0; + else + for(++ptr; ptr && !ptr->active;) + ptr = (ptr->note == 127) ? 0 : (ptr + 1); + return *this; + }; + activenoteiterator operator++(int) + { + activenoteiterator pos = *this; + ++*this; + return pos; + } + NoteInfo &operator*() const + { return *ptr; } + NoteInfo *operator->() const + { return ptr; } + bool operator==(activenoteiterator other) const + { return ptr == other.ptr; } + bool operator!=(activenoteiterator other) const + { return ptr != other.ptr; } + operator NoteInfo *() const + { return ptr; } + private: + NoteInfo *ptr; + }; + + activenoteiterator activenotes_begin() + { + activenoteiterator it(activenotes); + return (it->active) ? it : ++it; + } + + activenoteiterator activenotes_find(uint8_t note) + { + return activenoteiterator( + activenotes[note].active ? &activenotes[note] : 0); + } + + std::pair activenotes_insert(uint8_t note) + { + NoteInfo &info = activenotes[note]; + bool inserted = !info.active; + if(inserted) info.active = true; + return std::pair(activenoteiterator(&info), inserted); + } + + void activenotes_erase(activenoteiterator pos) + { + if(pos) + pos->active = false; + } + + bool activenotes_empty() + { + return !activenotes_begin(); + } + void reset() { resetAllControllers(); -- cgit v1.2.3 From 09bb3bc4d31ac925a13f51f8fc4944330da158bb Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Mon, 16 Apr 2018 23:22:49 +0300 Subject: `NoteInfo` and it's parent `MIDIchannel` no more contains dynamic stuff Everything now are a simple types and fixed arrays. --- src/adlmidi_midiplay.cpp | 57 ++++++++++++++++++++++++++---------------------- src/adlmidi_private.hpp | 13 ++++++++--- 2 files changed, 41 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 965dc3c..0636921 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1093,10 +1093,11 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) //uint16_t i[2] = { ains.adlno1, ains.adlno2 }; bool pseudo_4op = ains->flags & adlinsdata::Flag_Pseudo4op; + size_t chans_count = (pseudo_4op || (ains->adlno1 != ains->adlno2)) ? 2 : 1; MIDIchannel::NoteInfo::Phys voices[2] = { - {ains->adlno1, false}, - {ains->adlno2, pseudo_4op} + {0, ains->adlno1, false}, + {0, ains->adlno2, pseudo_4op} }; if((opl.AdlPercussionMode == 1) && PercussionMap[midiins & 0xFF]) @@ -1112,9 +1113,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) } // Allocate AdLib channel (the physical sound channel for the note) - int32_t adlchannel[2] = { -1, -1 }; + int32_t adlchannel[MIDIchannel::NoteInfo::MaxNumPhysChans] = { -1, -1 }; - for(uint32_t ccount = 0; ccount < 2; ++ccount) + for(uint32_t ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { if(ccount == 1) { @@ -1201,14 +1202,15 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) ir.first->tone = tone; ir.first->midiins = midiins; ir.first->insmeta = meta; + ir.first->chip_channels_max = chans_count; - for(unsigned ccount = 0; ccount < 2; ++ccount) + for(size_t ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { int32_t c = adlchannel[ccount]; if(c < 0) continue; - uint16_t chipChan = static_cast(adlchannel[ccount]); - ir.first->phys[chipChan] = voices[ccount]; + voices[ccount].chip_chan = static_cast(c); + ir.first->chip_channels[ccount] = voices[ccount]; } NoteUpdate(channel, ir.first, Upd_All | Upd_Patch); return true; @@ -1444,14 +1446,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, my_loc.MidCh = MidCh; my_loc.note = info.note; - for(MIDIchannel::NoteInfo::PhysMap::iterator - jnext = info.phys.begin(); - jnext != info.phys.end(); - ) + for(size_t ccount = 0; ccount < info.chip_channels_max; ccount++) { - MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++); - uint16_t c = j->first; - const MIDIchannel::NoteInfo::Phys &ins = j->second; + const MIDIchannel::NoteInfo::Phys &ins = info.chip_channels[ccount]; + uint16_t c = ins.chip_chan; if(select_adlchn >= 0 && c != select_adlchn) continue; @@ -1466,14 +1464,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } - for(MIDIchannel::NoteInfo::PhysMap::iterator - jnext = info.phys.begin(); - jnext != info.phys.end(); - ) + for(size_t ccount = 0; ccount < info.chip_channels_max; ccount++) { - MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++); - uint16_t c = j->first; - const MIDIchannel::NoteInfo::Phys &ins = j->second; + const MIDIchannel::NoteInfo::Phys &ins = info.chip_channels[ccount]; + uint16_t c = ins.chip_chan; if(select_adlchn >= 0 && c != select_adlchn) continue; @@ -1512,7 +1506,9 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0); } - info.phys.erase(j); + //info.phys.erase(j); + if(ccount == (info.chip_channels_max - 1)) + info.chip_channels_max = 0; continue; } @@ -1637,7 +1633,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } - if(info.phys.empty()) + if(info.chip_channels_max == 0) Ch[MidCh].activenotes_erase(i); } @@ -2251,7 +2247,9 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo opl.NoteOff(c); } -void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator j, MIDIplay::MIDIchannel::activenoteiterator i) +void MIDIplay::KillOrEvacuate(size_t from_channel, + AdlChannel::users_t::iterator j, + MIDIplay::MIDIchannel::activenoteiterator i) { // Before killing the note, check if it can be // evacuated to another channel as an arpeggio @@ -2291,8 +2289,15 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator i->vol, 0.0); } - i->phys.erase(static_cast(from_channel)); - i->phys[cs] = j->second.ins; + for(size_t cchan = 0; cchan < i->chip_channels_max; cchan++) + { + MIDIchannel::NoteInfo::Phys &chan = i->chip_channels[cchan]; + if(chan.chip_chan == static_cast(from_channel)) + { + chan = j->second.ins; + chan.chip_chan = cs; + } + } ch[cs].users.insert(*j); ch[from_channel].users.erase(j); return; diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 08c22c0..6391f2a 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -618,8 +618,14 @@ public: size_t midiins; // Index to physical adlib data structure, adlins[] size_t insmeta; + enum + { + MaxNumPhysChans = 2 + }; struct Phys { + //! Destinition chip channel + uint16_t chip_chan; //! ins, inde to adl[] size_t insId; //! Is this voice must be detunable? @@ -634,9 +640,10 @@ public: return !operator==(oth); } }; - typedef std::map PhysMap; - // List of OPL3 channels it is currently occupying. - std::map phys; + //! List of OPL3 channels it is currently occupying. + Phys chip_channels[MaxNumPhysChans]; + //! Count of used channels. + size_t chip_channels_max; }; char ____padding2[5]; NoteInfo activenotes[128]; -- cgit v1.2.3 From 985f08d9ab40ffa6367261281660e6c3e5b0720f Mon Sep 17 00:00:00 2001 From: Wohlstand Date: Wed, 18 Apr 2018 11:45:07 +0300 Subject: Updated DMXOPL3 bank by @sneakernets Taken this state: https://github.com/sneakernets/DMXOPL/blob/f7ac28019cc2ea33cb0bbe03a1899391917f5c85/GENMIDI(GS).wopl --- src/adldata.cpp | 155 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/adldata.cpp b/src/adldata.cpp index 1556f4d..871c87e 100644 --- a/src/adldata.cpp +++ b/src/adldata.cpp @@ -4,7 +4,7 @@ * FROM A NUMBER OF SOURCES, MOSTLY PC GAMES. * PREPROCESSED, CONVERTED, AND POSTPROCESSED OFF-SCREEN. */ -const adldata adl[4423] = +const adldata adl[4430] = { // ,---------+-------- Wave select settings // | ,-------ч-+------ Sustain/release rates // | | ,-----ч-ч-+---- Attack/decay rates @@ -4357,13 +4357,20 @@ const adldata adl[4423] = { 0x2F7F602,0x0F8F802, 0x00,0x88, 0xE, +12 }, { 0x05476C1,0x30892C5, 0x80,0x08, 0x0, +0 }, { 0x05477C1,0x30892C5, 0x00,0x08, 0xA, -2 }, - { 0x005C604,0x005C604, 0x08,0x00, 0x1, +0 }, - { 0x509F902,0x057AB07, 0x03,0x07, 0xC, +12 }, + { 0x007C604,0x007C604, 0x08,0x08, 0x1, +0 }, + { 0x201F302,0x057AB09, 0x03,0x07, 0xC, +12 }, { 0x254F307,0x307F905, 0x04,0x08, 0x6, -5 }, { 0x254F307,0x207F905, 0x04,0x08, 0x8, +0 }, - { 0x509F912,0x057AB07, 0x03,0x07, 0xC, +12 }, + { 0x006C604,0x007C604, 0x08,0x08, 0x1, +0 }, + { 0x201F312,0x057AB09, 0x03,0x07, 0xC, +12 }, { 0x254D307,0x3288905, 0x04,0x03, 0xA, -5 }, - { 0x509F902,0x057AB07, 0x03,0x07, 0x0, +12 }, + { 0x0015500,0x007C716, 0x0C,0x00, 0x0, +0 }, + { 0x201F312,0x057AB09, 0x00,0x07, 0xC, +12 }, + { 0x0015500,0x007C718, 0x0C,0x00, 0x0, +0 }, + { 0x001F312,0x047BB05, 0x03,0x07, 0xC, +12 }, + { 0x0015500,0x007C71B, 0x0C,0x00, 0x0, +0 }, + { 0x201F312,0x047BB09, 0x03,0x07, 0xC, +12 }, + { 0x0015500,0x007C71F, 0x0C,0x00, 0x0, +0 }, { 0x210F509,0x605FE05, 0x8A,0x8A, 0xE, +12 }, { 0x400F509,0x605FE05, 0x07,0x8A, 0xA, +12 }, { 0x2E1F11E,0x3F3F318, 0x04,0x00, 0x8, +0 }, @@ -8921,76 +8928,76 @@ const struct adlinsdata adlins[4549] = {4336,4335, 25, 1, 33, 33,0.000000 }, {4337,4338, 61, 1, 40, 40,0.000000 }, {4339,4340, 37, 1, 53, 53,0.000000 }, - {4341,4342, 15, 1, 320, 320,0.000000 }, + {4341,4342, 15, 1, 80, 80,0.000000 }, {4343,4344, 48, 1, 73, 73,-1.906250 }, - {4341,4345, 19, 1, 320, 320,0.000000 }, - {4346,4346, 48, 0, 53, 53,0.000000 }, - {4341,4342, 22, 1, 353, 353,0.000000 }, - {4341,4342, 24, 1, 360, 360,0.000000 }, - {4341,4347, 27, 1, 393, 393,0.000000 }, - {4341,4342, 31, 1, 380, 380,0.000000 }, - {4348,4349, 60, 1, 246, 246,0.031250 }, - {4350,4350, 70, 0, 340, 340,0.000000 }, - {4351,4352, 80, 1, 106, 106,0.125000 }, - {4353,4353, 58, 0, 73, 73,0.000000 }, - {4354,4355, 31, 1, 313, 313,0.000000 }, - {4356,4356, 61, 0, 253, 253,0.000000 }, - {4357,4358, 41, 1, 100, 100,0.000000 }, - {4359,4360, 35, 1, 160, 160,0.000000 }, - {4361,4362, 29, 1, 40, 40,0.000000 }, - {4363,4364, 41, 1, 166, 166,0.000000 }, - {4363,4364, 37, 1, 160, 160,0.000000 }, - {4365,4366, 54, 1, 80, 80,0.000000 }, - {4365,4367, 48, 1, 80, 80,0.000000 }, - {4368,4369, 77, 1, 53, 53,0.000000 }, - {4370,4371, 72, 1, 46, 46,0.000000 }, - {4372,4372, 40, 0, 140, 140,0.000000 }, - {4373,4373, 38, 0, 73, 73,0.000000 }, - {4374,4374, 36, 0, 533, 533,0.000000 }, - {4375,4376, 60, 1, 26, 26,0.000000 }, - {4376,4377, 60, 1, 26, 26,0.000000 }, - {4378,4378, 73, 0, 60, 60,0.000000 }, - {4379,4380, 68, 1, 40, 40,0.000000 }, - {4381,4382, 18, 1, 60, 60,0.000000 }, - {4383,4384, 18, 1, 106, 106,0.000000 }, - {4385,4385, 90, 0, 80, 80,0.000000 }, - {4386,4386, 90, 0, 306, 306,0.000000 }, - {4387,4388, 64, 1, 233, 233,0.031250 }, - {4389,4390, 80, 1, 140, 140,0.031250 }, - {4391,4392, 64, 1, 606, 606,0.000000 }, - {4393,4393, 67, 0, 20, 20,0.000000 }, - {4394,4395, 50, 1, 53, 53,0.000000 }, - {4396,4396, 36, 0, 66, 66,0.000000 }, - {4397,4397, 0, 0, 40000, 20,0.000000 }, - {4398,4398, 0, 0, 40000, 0,0.000000 }, - {4399,4399, 0, 0, 360, 360,0.000000 }, - {4400,4400, 0, 0, 586, 586,0.000000 }, - {4401,4401, 0, 0, 40000, 0,0.000000 }, - {4402,4402, 0, 0, 40000, 0,0.000000 }, - {4403,4403, 0, 0, 40000, 0,0.000000 }, - {4404,4404, 0, 0, 40000, 6,0.000000 }, + {4345,4346, 19, 1, 120, 120,0.000000 }, + {4347,4347, 48, 0, 53, 53,0.000000 }, + {4348,4349, 15, 1, 33, 33,0.000000 }, + {4350,4351, 12, 1, 33, 33,0.000000 }, + {4352,4353, 11, 1, 33, 33,0.000000 }, + {4354,4353, 8, 1, 40, 40,0.000000 }, + {4355,4356, 60, 1, 246, 246,0.031250 }, + {4357,4357, 70, 0, 340, 340,0.000000 }, + {4358,4359, 80, 1, 106, 106,0.125000 }, + {4360,4360, 58, 0, 73, 73,0.000000 }, + {4361,4362, 31, 1, 313, 313,0.000000 }, + {4363,4363, 61, 0, 253, 253,0.000000 }, + {4364,4365, 41, 1, 100, 100,0.000000 }, + {4366,4367, 35, 1, 160, 160,0.000000 }, + {4368,4369, 29, 1, 40, 40,0.000000 }, + {4370,4371, 41, 1, 166, 166,0.000000 }, + {4370,4371, 37, 1, 160, 160,0.000000 }, + {4372,4373, 54, 1, 80, 80,0.000000 }, + {4372,4374, 48, 1, 80, 80,0.000000 }, + {4375,4376, 77, 1, 53, 53,0.000000 }, + {4377,4378, 72, 1, 46, 46,0.000000 }, + {4379,4379, 40, 0, 140, 140,0.000000 }, + {4380,4380, 38, 0, 73, 73,0.000000 }, + {4381,4381, 36, 0, 533, 533,0.000000 }, + {4382,4383, 60, 1, 26, 26,0.000000 }, + {4383,4384, 60, 1, 26, 26,0.000000 }, + {4385,4385, 73, 0, 60, 60,0.000000 }, + {4386,4387, 68, 1, 40, 40,0.000000 }, + {4388,4389, 18, 1, 60, 60,0.000000 }, + {4390,4391, 18, 1, 106, 106,0.000000 }, + {4392,4392, 90, 0, 80, 80,0.000000 }, + {4393,4393, 90, 0, 306, 306,0.000000 }, + {4394,4395, 64, 1, 233, 233,0.031250 }, + {4396,4397, 80, 1, 140, 140,0.031250 }, + {4398,4399, 64, 1, 606, 606,0.000000 }, + {4400,4400, 67, 0, 20, 20,0.000000 }, + {4401,4402, 50, 1, 53, 53,0.000000 }, + {4403,4403, 36, 0, 66, 66,0.000000 }, + {4404,4404, 0, 0, 40000, 20,0.000000 }, {4405,4405, 0, 0, 40000, 0,0.000000 }, - {4406,4406, 0, 0, 146, 146,0.000000 }, - {4406,4406, 73, 0, 886, 886,0.000000 }, - {4407,4407, 0, 0, 40, 0,0.000000 }, - {4408,4408, 0, 0, 486, 0,0.000000 }, - {4409,4409, 0, 0, 1226, 1226,0.000000 }, - {4410,4410, 0, 0, 1480, 1480,0.000000 }, - {4411,4411, 0, 0, 46, 46,0.000000 }, - {4412,4412, 0, 0, 126, 126,0.000000 }, - {4412,4412, 12, 0, 106, 106,0.000000 }, - {4413,4413, 0, 0, 160, 160,0.000000 }, - {4413,4413, 1, 0, 153, 153,0.000000 }, - {4414,4414, 0, 0, 20, 20,0.000000 }, - {4414,4414, 23, 0, 26, 26,0.000000 }, - {4415,4415, 0, 0, 140, 140,0.000000 }, - {4416,4416, 0, 0, 486, 486,0.000000 }, - {4417,4417, 0, 0, 40000, 13,0.000000 }, - {4418,4418, 0, 0, 40000, 0,0.000000 }, - {4419,4419, 0, 0, 1226, 1226,0.000000 }, - {4420,4420, 0, 0, 766, 766,0.000000 }, - {4421,4421, 0, 0, 93, 93,0.000000 }, - {4422,4422, 0, 2, 40000, 0,0.000000 }, + {4406,4406, 0, 0, 360, 360,0.000000 }, + {4407,4407, 0, 0, 586, 586,0.000000 }, + {4408,4408, 0, 0, 40000, 0,0.000000 }, + {4409,4409, 0, 0, 40000, 0,0.000000 }, + {4410,4410, 0, 0, 40000, 0,0.000000 }, + {4411,4411, 0, 0, 40000, 6,0.000000 }, + {4412,4412, 0, 0, 40000, 0,0.000000 }, + {4413,4413, 0, 0, 146, 146,0.000000 }, + {4413,4413, 73, 0, 886, 886,0.000000 }, + {4414,4414, 0, 0, 40, 0,0.000000 }, + {4415,4415, 0, 0, 486, 0,0.000000 }, + {4416,4416, 0, 0, 1226, 1226,0.000000 }, + {4417,4417, 0, 0, 1480, 1480,0.000000 }, + {4418,4418, 0, 0, 46, 46,0.000000 }, + {4419,4419, 0, 0, 126, 126,0.000000 }, + {4419,4419, 12, 0, 106, 106,0.000000 }, + {4420,4420, 0, 0, 160, 160,0.000000 }, + {4420,4420, 1, 0, 153, 153,0.000000 }, + {4421,4421, 0, 0, 20, 20,0.000000 }, + {4421,4421, 23, 0, 26, 26,0.000000 }, + {4422,4422, 0, 0, 140, 140,0.000000 }, + {4423,4423, 0, 0, 486, 486,0.000000 }, + {4424,4424, 0, 0, 40000, 13,0.000000 }, + {4425,4425, 0, 0, 40000, 0,0.000000 }, + {4426,4426, 0, 0, 1226, 1226,0.000000 }, + {4427,4427, 0, 0, 766, 766,0.000000 }, + {4428,4428, 0, 0, 93, 93,0.000000 }, + {4429,4429, 0, 2, 40000, 0,0.000000 }, }; @@ -10487,6 +10494,6 @@ const AdlBankSetup adlbanksetup[74] = {3, 0, 0, 0, 0}, //Bank 69, TMB (Blood) {3, 0, 0, 0, 0}, //Bank 70, TMB (Lee) {3, 0, 0, 0, 0}, //Bank 71, TMB (Nam) - {1, 0, 0, 0, 0}, //Bank 72, WOPL (DMXOPL3 bank by Sneakernets) + {0, 0, 0, 0, 0}, //Bank 72, WOPL (DMXOPL3 bank by Sneakernets) {1, 0, 0, 0, 0} //Bank 73, EA (Cartooners) }; -- cgit v1.2.3 From 26388a7cd07a365e4256b0308c3aa87772accd41 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Wed, 18 Apr 2018 16:18:37 +0200 Subject: reformulated phys code into original map semantics --- src/adlmidi_midiplay.cpp | 50 ++++++++++++++++-------------------- src/adlmidi_private.hpp | 67 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 965dc3c..ea40d50 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1093,10 +1093,10 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) //uint16_t i[2] = { ains.adlno1, ains.adlno2 }; bool pseudo_4op = ains->flags & adlinsdata::Flag_Pseudo4op; - MIDIchannel::NoteInfo::Phys voices[2] = + MIDIchannel::NoteInfo::Phys voices[MIDIchannel::NoteInfo::MaxNumPhysChans] = { - {ains->adlno1, false}, - {ains->adlno2, pseudo_4op} + {0, ains->adlno1, false}, + {0, ains->adlno2, pseudo_4op} }; if((opl.AdlPercussionMode == 1) && PercussionMap[midiins & 0xFF]) @@ -1112,9 +1112,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) } // Allocate AdLib channel (the physical sound channel for the note) - int32_t adlchannel[2] = { -1, -1 }; + int32_t adlchannel[MIDIchannel::NoteInfo::MaxNumPhysChans] = { -1, -1 }; - for(uint32_t ccount = 0; ccount < 2; ++ccount) + for(uint32_t ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { if(ccount == 1) { @@ -1201,14 +1201,15 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) ir.first->tone = tone; ir.first->midiins = midiins; ir.first->insmeta = meta; + ir.first->chip_channels_count = 0; - for(unsigned ccount = 0; ccount < 2; ++ccount) + for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { int32_t c = adlchannel[ccount]; if(c < 0) continue; uint16_t chipChan = static_cast(adlchannel[ccount]); - ir.first->phys[chipChan] = voices[ccount]; + ir.first->phys_ensure_find_or_create(chipChan)->assign(voices[ccount]); } NoteUpdate(channel, ir.first, Upd_All | Upd_Patch); return true; @@ -1444,14 +1445,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, my_loc.MidCh = MidCh; my_loc.note = info.note; - for(MIDIchannel::NoteInfo::PhysMap::iterator - jnext = info.phys.begin(); - jnext != info.phys.end(); - ) + for(unsigned ccount = 0, ctotal = info.chip_channels_count; ccount < ctotal; ccount++) { - MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++); - uint16_t c = j->first; - const MIDIchannel::NoteInfo::Phys &ins = j->second; + const MIDIchannel::NoteInfo::Phys &ins = info.chip_channels[ccount]; + uint16_t c = ins.chip_chan; if(select_adlchn >= 0 && c != select_adlchn) continue; @@ -1466,14 +1463,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } - for(MIDIchannel::NoteInfo::PhysMap::iterator - jnext = info.phys.begin(); - jnext != info.phys.end(); - ) + for(unsigned ccount = 0; ccount < info.chip_channels_count; ccount++) { - MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++); - uint16_t c = j->first; - const MIDIchannel::NoteInfo::Phys &ins = j->second; + const MIDIchannel::NoteInfo::Phys &ins = info.chip_channels[ccount]; + uint16_t c = ins.chip_chan; if(select_adlchn >= 0 && c != select_adlchn) continue; @@ -1512,7 +1505,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0); } - info.phys.erase(j); + info.phys_erase_at(&ins); // decrements channel count + --ccount; // adjusts index accordingly continue; } @@ -1637,7 +1631,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } - if(info.phys.empty()) + if(info.chip_channels_count == 0) Ch[MidCh].activenotes_erase(i); } @@ -2223,7 +2217,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo // Collision: Kill old note, // UNLESS we're going to do arpeggio MIDIchannel::activenoteiterator i - (Ch[j->first.MidCh].activenotes_find(j->first.note)); + (Ch[j->first.MidCh].activenotes_ensure_find(j->first.note)); // Check if we can do arpeggio. if((j->second.vibdelay < 70 @@ -2291,8 +2285,8 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator i->vol, 0.0); } - i->phys.erase(static_cast(from_channel)); - i->phys[cs] = j->second.ins; + i->phys_erase(static_cast(from_channel)); + i->phys_ensure_find_or_create(cs)->assign(j->second.ins); ch[cs].users.insert(*j); ch[from_channel].users.erase(j); return; @@ -2510,7 +2504,7 @@ retry_arpeggio: { NoteUpdate( i->first.MidCh, - Ch[ i->first.MidCh ].activenotes_find(i->first.note), + Ch[ i->first.MidCh ].activenotes_ensure_find(i->first.note), Upd_Off, static_cast(c)); goto retry_arpeggio; @@ -2518,7 +2512,7 @@ retry_arpeggio: NoteUpdate( i->first.MidCh, - Ch[ i->first.MidCh ].activenotes_find(i->first.note), + Ch[ i->first.MidCh ].activenotes_ensure_find(i->first.note), Upd_Pitch | Upd_Volume | Upd_Pan, static_cast(c)); } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 08c22c0..69a833c 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -87,6 +87,7 @@ typedef int32_t ssize_t; #include #include #include +#include #if !(defined(__APPLE__) && defined(__GLIBCXX__)) #include //PRId32, PRIu32, etc. #else @@ -618,13 +619,25 @@ public: size_t midiins; // Index to physical adlib data structure, adlins[] size_t insmeta; + enum + { + MaxNumPhysChans = 2, + MaxNumPhysItemCount = MaxNumPhysChans, + }; struct Phys { + //! Destination chip channel + uint16_t chip_chan; //! ins, inde to adl[] size_t insId; //! Is this voice must be detunable? bool pseudo4op; + void assign(const Phys &oth) + { + insId = oth.insId; + pseudo4op = oth.pseudo4op; + } bool operator==(const Phys &oth) const { return (insId == oth.insId) && (pseudo4op == oth.pseudo4op); @@ -634,9 +647,50 @@ public: return !operator==(oth); } }; - typedef std::map PhysMap; - // List of OPL3 channels it is currently occupying. - std::map phys; + //! List of OPL3 channels it is currently occupying. + Phys chip_channels[MaxNumPhysItemCount]; + //! Count of used channels. + unsigned chip_channels_count; + // + Phys *phys_find(unsigned chip_chan) + { + Phys *ph = NULL; + for(unsigned i = 0; i < chip_channels_count && !ph; ++i) + if(chip_channels[i].chip_chan == chip_chan) + ph = &chip_channels[i]; + return ph; + } + Phys *phys_find_or_create(unsigned chip_chan) + { + Phys *ph = phys_find(chip_chan); + if(!ph) { + if(chip_channels_count < MaxNumPhysItemCount) { + ph = &chip_channels[chip_channels_count++]; + ph->chip_chan = chip_chan; + } + } + return ph; + } + Phys *phys_ensure_find_or_create(unsigned chip_chan) + { + Phys *ph = phys_find_or_create(chip_chan); + assert(ph); + return ph; + } + void phys_erase_at(const Phys *ph) + { + unsigned pos = ph - chip_channels; + assert(pos < chip_channels_count); + for(unsigned i = pos + 1; i < chip_channels_count; ++i) + chip_channels[i - 1] = chip_channels[i]; + --chip_channels_count; + } + void phys_erase(unsigned chip_chan) + { + Phys *ph = phys_find(chip_chan); + if(ph) + phys_erase_at(ph); + } }; char ____padding2[5]; NoteInfo activenotes[128]; @@ -686,6 +740,13 @@ public: activenotes[note].active ? &activenotes[note] : 0); } + activenoteiterator activenotes_ensure_find(uint8_t note) + { + activenoteiterator it = activenotes_find(note); + assert(it); + return it; + } + std::pair activenotes_insert(uint8_t note) { NoteInfo &info = activenotes[note]; -- cgit v1.2.3 From 558cd429dbef266d94ef789efa6514fc9d531b95 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Thu, 19 Apr 2018 19:05:42 +0200 Subject: hard-RT: elimination of the user map --- src/adlmidi_midiplay.cpp | 159 +++++++++++++++++++++++------------------------ src/adlmidi_private.hpp | 105 ++++++++++++++++++++++++++----- 2 files changed, 169 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 80f36ce..d984fe1 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -117,18 +117,18 @@ inline bool isXgPercChannel(uint8_t msb, uint8_t lsb) void MIDIplay::AdlChannel::AddAge(int64_t ms) { - if(users.empty()) + if(users_empty()) koff_time_until_neglible = std::max(int64_t(koff_time_until_neglible - ms), static_cast(-0x1FFFFFFFl)); else { koff_time_until_neglible = 0; - for(users_t::iterator i = users.begin(); i != users.end(); ++i) + for(LocationData *i = users_first; i; i = i->next) { - i->second.kon_time_until_neglible = - std::max(i->second.kon_time_until_neglible - ms, static_cast(-0x1FFFFFFFl)); - i->second.vibdelay += ms; + i->kon_time_until_neglible = + std::max(i->kon_time_until_neglible - ms, static_cast(-0x1FFFFFFFl)); + i->vibdelay += ms; } } } @@ -1455,11 +1455,13 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(props_mask & Upd_Patch) { opl.Patch(c, ins.insId); - AdlChannel::LocationData &d = ch[c].users[my_loc]; - d.sustained = false; // inserts if necessary - d.vibdelay = 0; - d.kon_time_until_neglible = ains.ms_sound_kon; - d.ins = ins; + AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc); + if(d) { // inserts if necessary + d->sustained = false; + d->vibdelay = 0; + d->kon_time_until_neglible = ains.ms_sound_kon; + d->ins = ins; + } } } @@ -1475,15 +1477,15 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, { if(Ch[MidCh].sustain == 0) { - AdlChannel::users_t::iterator k = ch[c].users.find(my_loc); + AdlChannel::LocationData *k = ch[c].users_find(my_loc); - if(k != ch[c].users.end()) - ch[c].users.erase(k); + if(k) + ch[c].users_erase(k); if(hooks.onNote) hooks.onNote(hooks.onNote_userData, c, tone, midiins, 0, 0.0); - if(ch[c].users.empty()) + if(ch[c].users_empty()) { opl.NoteOff(c); if(props_mask & Upd_Mute) // Mute the note @@ -1499,8 +1501,9 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, { // Sustain: Forget about the note, but don't key it off. // Also will avoid overwriting it very soon. - AdlChannel::LocationData &d = ch[c].users[my_loc]; - d.sustained = true; // note: not erased! + AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc); + if(d) + d->sustained = true; // note: not erased! if(hooks.onNote) hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0); } @@ -1602,10 +1605,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(props_mask & Upd_Pitch) { - AdlChannel::LocationData &d = ch[c].users[my_loc]; + AdlChannel::LocationData *d = ch[c].users_find(my_loc); // Don't bend a sustained note - if(!d.sustained) + if(!d || !d->sustained) { double bend = Ch[MidCh].bend + opl.GetAdlIns(ins.insId).finetune; double phase = 0.0; @@ -1615,7 +1618,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, phase = ains.voice2_fine_tune;//0.125; // Detune the note slightly (this is what Doom does) } - if(Ch[MidCh].vibrato && d.vibdelay >= Ch[MidCh].vibdelay) + if(Ch[MidCh].vibrato && d->vibdelay >= Ch[MidCh].vibdelay) bend += Ch[MidCh].vibrato * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos); #ifdef ADLMIDI_USE_DOSBOX_OPL @@ -2130,30 +2133,27 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not // Same midi-instrument = some stability //if(c == MidCh) s += 4; - for(AdlChannel::users_t::const_iterator - j = ch[c].users.begin(); - j != ch[c].users.end(); - ++j) + for (AdlChannel::LocationData *j = ch[c].users_first; j; j = j->next) { s -= 4000; - if(!j->second.sustained) - s -= j->second.kon_time_until_neglible; + if(!j->sustained) + s -= j->kon_time_until_neglible; else - s -= (j->second.kon_time_until_neglible / 2); + s -= (j->kon_time_until_neglible / 2); MIDIchannel::activenoteiterator - k = const_cast(Ch[j->first.MidCh]).activenotes_find(j->first.note); + k = const_cast(Ch[j->loc.MidCh]).activenotes_find(j->loc.note); if(k) { // Same instrument = good - if(j->second.ins == ins) + if(j->ins == ins) { s += 300; // Arpeggio candidate = even better - if(j->second.vibdelay < 70 - || j->second.kon_time_until_neglible > 20000) + if(j->vibdelay < 70 + || j->kon_time_until_neglible > 20000) s += 0; } @@ -2181,14 +2181,11 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not if(opl.four_op_category[c2] != opl.four_op_category[c]) continue; - for(AdlChannel::users_t::const_iterator - m = ch[c2].users.begin(); - m != ch[c2].users.end(); - ++m) + for(AdlChannel::LocationData *m = ch[c2].users_first; m; m = m->next) { - if(m->second.sustained) continue; - if(m->second.vibdelay >= 200) continue; - if(m->second.ins != j->second.ins) continue; + if(m->sustained) continue; + if(m->vibdelay >= 200) continue; + if(m->ins != j->ins) continue; n_evacuation_stations += 1; } } @@ -2202,27 +2199,25 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::Not void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins) { - if(ch[c].users.empty()) return; // Nothing to do + if(ch[c].users_empty()) return; // Nothing to do //bool doing_arpeggio = false; - for(AdlChannel::users_t::iterator - jnext = ch[c].users.begin(); - jnext != ch[c].users.end(); - ) + for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;) { - AdlChannel::users_t::iterator j(jnext++); + AdlChannel::LocationData *j = jnext; + jnext = jnext->next; - if(!j->second.sustained) + if(!j->sustained) { // Collision: Kill old note, // UNLESS we're going to do arpeggio MIDIchannel::activenoteiterator i - (Ch[j->first.MidCh].activenotes_ensure_find(j->first.note)); + (Ch[j->loc.MidCh].activenotes_ensure_find(j->loc.note)); // Check if we can do arpeggio. - if((j->second.vibdelay < 70 - || j->second.kon_time_until_neglible > 20000) - && j->second.ins == ins) + if((j->vibdelay < 70 + || j->kon_time_until_neglible > 20000) + && j->ins == ins) { // Do arpeggio together with this note. //doing_arpeggio = true; @@ -2241,12 +2236,12 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo // Keyoff the channel so that it can be retriggered, // unless the new note will be introduced as just an arpeggio. - if(ch[c].users.empty()) + if(ch[c].users_empty()) opl.NoteOff(c); } void MIDIplay::KillOrEvacuate(size_t from_channel, - AdlChannel::users_t::iterator j, + AdlChannel::LocationData *j, MIDIplay::MIDIchannel::activenoteiterator i) { // Before killing the note, check if it can be @@ -2265,14 +2260,15 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, if(opl.four_op_category[c] != opl.four_op_category[from_channel]) continue; - for(AdlChannel::users_t::iterator - m = ch[c].users.begin(); - m != ch[c].users.end(); - ++m) + AdlChannel &adlch = ch[c]; + if(adlch.users_size == AdlChannel::users_max) + continue; // no room for more arpeggio on channel + + for(AdlChannel::LocationData *m = adlch.users_first; m; m = m->next) { - if(m->second.vibdelay >= 200 - && m->second.kon_time_until_neglible < 10000) continue; - if(m->second.ins != j->second.ins) + if(m->vibdelay >= 200 + && m->kon_time_until_neglible < 10000) continue; + if(m->ins != j->ins) continue; if(hooks.onNote) { @@ -2288,9 +2284,10 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, } i->phys_erase(static_cast(from_channel)); - i->phys_ensure_find_or_create(cs)->assign(j->second.ins); - ch[cs].users.insert(*j); - ch[from_channel].users.erase(j); + i->phys_ensure_find_or_create(cs)->assign(j->ins); + if(!ch[cs].users_insert(*j)) + assert(false); + ch[from_channel].users_erase(j); return; } } @@ -2303,7 +2300,7 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, ins );*/ // Kill it - NoteUpdate(j->first.MidCh, + NoteUpdate(j->loc.MidCh, i, Upd_Off, static_cast(from_channel)); @@ -2330,27 +2327,25 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn) for(unsigned c = first; c < last; ++c) { - if(ch[c].users.empty()) continue; // Nothing to do + if(ch[c].users_empty()) continue; // Nothing to do - for(AdlChannel::users_t::iterator - jnext = ch[c].users.begin(); - jnext != ch[c].users.end(); - ) + for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;) { - AdlChannel::users_t::iterator j(jnext++); + AdlChannel::LocationData *j = jnext; + jnext = jnext->next; - if((MidCh < 0 || j->first.MidCh == MidCh) - && j->second.sustained) + if((MidCh < 0 || j->loc.MidCh == MidCh) + && j->sustained) { int midiins = '?'; if(hooks.onNote) - hooks.onNote(hooks.onNote_userData, (int)c, j->first.note, midiins, 0, 0.0); - ch[c].users.erase(j); + hooks.onNote(hooks.onNote_userData, (int)c, j->loc.note, midiins, 0, 0.0); + ch[c].users_erase(j); } } // Keyoff the channel, if there are no users left. - if(ch[c].users.empty()) + if(ch[c].users_empty()) opl.NoteOff(c); } } @@ -2485,11 +2480,11 @@ retry_arpeggio: if(c > uint32_t(std::numeric_limits::max())) break; - size_t n_users = ch[c].users.size(); + size_t n_users = ch[c].users_size; if(n_users > 1) { - AdlChannel::users_t::const_iterator i = ch[c].users.begin(); + AdlChannel::LocationData *i = ch[c].users_first; size_t rate_reduction = 3; if(n_users >= 3) @@ -2498,23 +2493,25 @@ retry_arpeggio: if(n_users >= 4) rate_reduction = 1; - std::advance(i, (arpeggio_counter / rate_reduction) % n_users); + for(unsigned count = (arpeggio_counter / rate_reduction) % n_users, + n = 0; n < count; ++n) + i = i->next; - if(i->second.sustained == false) + if(i->sustained == false) { - if(i->second.kon_time_until_neglible <= 0l) + if(i->kon_time_until_neglible <= 0l) { NoteUpdate( - i->first.MidCh, - Ch[ i->first.MidCh ].activenotes_ensure_find(i->first.note), + i->loc.MidCh, + Ch[ i->loc.MidCh ].activenotes_ensure_find(i->loc.note), Upd_Off, static_cast(c)); goto retry_arpeggio; } NoteUpdate( - i->first.MidCh, - Ch[ i->first.MidCh ].activenotes_ensure_find(i->first.note), + i->loc.MidCh, + Ch[ i->loc.MidCh ].activenotes_ensure_find(i->loc.note), Upd_Pitch | Upd_Volume | Upd_Pan, static_cast(c)); } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 69a833c..f6eea5a 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -803,36 +803,113 @@ public: // Additional information about OPL3 channels struct AdlChannel { - // For collisions struct Location { uint16_t MidCh; uint8_t note; - bool operator==(const Location &b) const - { - return MidCh == b.MidCh && note == b.note; - } - bool operator< (const Location &b) const - { - return MidCh < b.MidCh || (MidCh == b.MidCh && note < b.note); - } - char ____padding[1]; + bool operator==(const Location &l) const + { return MidCh == l.MidCh && note == l.note; } + bool operator!=(const Location &l) const + { return !operator==(l); } }; struct LocationData { + LocationData *prev, *next; + Location loc; bool sustained; char ____padding[7]; MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[] int64_t kon_time_until_neglible; int64_t vibdelay; }; - typedef std::map users_t; - users_t users; // If the channel is keyoff'd int64_t koff_time_until_neglible; + + enum { users_max = 128 }; + LocationData *users_first, *users_free_cells; + LocationData users_cells[users_max]; + unsigned users_size; + + bool users_empty() const + { return !users_first; } + LocationData *users_find(Location loc) + { + LocationData *user = NULL; + for(LocationData *curr = users_first; !user && curr; curr = curr->next) + if(curr->loc == loc) + user = curr; + return user; + } + LocationData *users_allocate() + { + // remove free cells front + LocationData *user = users_free_cells; + if(!user) + return NULL; + users_free_cells = user->next; + users_free_cells->prev = NULL; + // add to users front + if(users_first) + users_first->prev = user; + user->prev = NULL; + user->next = users_first; + users_first = user; + ++users_size; + return user; + } + LocationData *users_find_or_create(Location loc) + { + LocationData *user = users_find(loc); + if(!user) { + user = users_allocate(); + if(!user) + return NULL; + LocationData *prev = user->prev, *next = user->next; + *user = LocationData(); + user->prev = prev; user->next = next; + user->loc = loc; + } + return user; + } + LocationData *users_insert(const LocationData &x) + { + LocationData *user = users_find(x.loc); + if(!user) + { + user = users_allocate(); + if(!user) + return NULL; + LocationData *prev = user->prev, *next = user->next; + *user = x; + user->prev = prev; user->next = next; + } + return user; + } + void users_erase(LocationData *user) + { + if(user->prev) + user->prev->next = user->next; + if(user->next) + user->next->prev = user->prev; + if(user == users_first) + users_first = user->next; + user->prev = NULL; + user->next = users_free_cells; + users_free_cells = user; + --users_size; + } + // For channel allocation: - AdlChannel(): users(), koff_time_until_neglible(0) { } + AdlChannel(): koff_time_until_neglible(0), users_first(NULL), users_size(0) + { + users_free_cells = users_cells; + for(size_t i = 0; i < users_max; ++i) + { + users_cells[i].prev = (i > 0) ? &users_cells[i - 1] : NULL; + users_cells[i].next = (i + 1 < users_max) ? &users_cells[i + 1] : NULL; + } + } void AddAge(int64_t ms); }; @@ -1230,7 +1307,7 @@ private: void KillOrEvacuate( size_t from_channel, - AdlChannel::users_t::iterator j, + AdlChannel::LocationData *j, MIDIchannel::activenoteiterator i); void Panic(); void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1); -- cgit v1.2.3 From cfacf50fc1422d88b169d02cfaf48dd8529e5a76 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 20 Apr 2018 00:28:09 +0300 Subject: Put some another functions into "ADLMIDI_DISABLE_MIDI_SEQUENCER" zone Those functions are dead out of disabled MIDI Sequencer code, so, there are must NOT appear in the library when MIDI Sequencer is disabled. --- src/adlmidi_load.cpp | 3 +++ src/adlmidi_midiplay.cpp | 2 +- src/adlmidi_private.hpp | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 77e6f69..f87b547 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -32,6 +32,7 @@ # endif//XMI #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes) { uint64_t result = 0; @@ -54,6 +55,8 @@ uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes) return result; } +#endif + bool MIDIplay::LoadBank(const std::string &filename) { fileReader file; diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index d984fe1..702ebea 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -760,6 +760,7 @@ void MIDIplay::applySetup() ch.resize(opl.NumChannels); } +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) { uint64_t result = 0; @@ -792,7 +793,6 @@ uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok) return result; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER double MIDIplay::Tick(double s, double granularity) { s *= tempoMultiplier; diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index f6eea5a..3532126 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -1167,7 +1167,21 @@ public: Setup m_setup; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + /** + * @brief Utility function to read Big-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ static uint64_t ReadBEint(const void *buffer, size_t nbytes); + + /** + * @brief Utility function to read Little-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ static uint64_t ReadLEint(const void *buffer, size_t nbytes); /** @@ -1184,6 +1198,7 @@ public: * @return Unsigned integer that conains parsed variable-length value */ uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok); +#endif bool LoadBank(const std::string &filename); bool LoadBank(const void *data, size_t size); -- cgit v1.2.3 From 58e055fc0a43d583a4638b7bd6a5e20c536fd49c Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 20 Apr 2018 00:44:39 +0300 Subject: Fix a small warning, found on attempt to build OPL3 Bank Editor --- src/chips/opl_chip_base.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/chips/opl_chip_base.cpp b/src/chips/opl_chip_base.cpp index 4a7c4f5..670a998 100644 --- a/src/chips/opl_chip_base.cpp +++ b/src/chips/opl_chip_base.cpp @@ -26,7 +26,7 @@ int OPLChipBase::generate32(int32_t *output, size_t frames) enum { maxFramesAtOnce = 256 }; int16_t temp[2 * maxFramesAtOnce]; for(size_t left = frames; left > 0;) { - size_t count = (left < maxFramesAtOnce) ? left : maxFramesAtOnce; + size_t count = (left < static_cast(maxFramesAtOnce)) ? left : static_cast(maxFramesAtOnce); generate(temp, count); for(size_t i = 0; i < 2 * count; ++i) output[i] = temp[i]; @@ -41,7 +41,7 @@ int OPLChipBase::generateAndMix32(int32_t *output, size_t frames) enum { maxFramesAtOnce = 256 }; int16_t temp[2 * maxFramesAtOnce]; for(size_t left = frames; left > 0;) { - size_t count = (left < maxFramesAtOnce) ? left : maxFramesAtOnce; + size_t count = (left < static_cast(maxFramesAtOnce)) ? left : static_cast(maxFramesAtOnce); generate(temp, count); for(size_t i = 0; i < 2 * count; ++i) output[i] += temp[i]; -- cgit v1.2.3 From f1ea9ba9fee23d80b8c662ba9f4762c6ba0746ad Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 20 Apr 2018 02:00:06 +0300 Subject: Use the same WOPL_File parser from OPL3 Bank Editor in libADLMIDI TODO: port this into Gen-ADLDATA too! No more need to update same file parser twice. Just, copy-paste the same WOPL parser between of OPL3 Bank Editor and libADLMIDI. --- src/adlmidi_load.cpp | 300 +++++++++----------------- src/wopl/wopl_file.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/wopl/wopl_file.h | 282 +++++++++++++++++++++++++ 3 files changed, 960 insertions(+), 204 deletions(-) create mode 100644 src/wopl/wopl_file.c create mode 100644 src/wopl/wopl_file.h (limited to 'src') diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index f87b547..703f34a 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -22,6 +22,7 @@ */ #include "adlmidi_private.hpp" +#include "wopl/wopl_file.h" #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER # ifndef ADLMIDI_DISABLE_MUS_SUPPORT @@ -72,44 +73,6 @@ bool MIDIplay::LoadBank(const void *data, size_t size) } - -/* WOPL-needed misc functions */ -static uint16_t toUint16LE(const uint8_t *arr) -{ - uint16_t num = arr[0]; - num |= ((arr[1] << 8) & 0xFF00); - return num; -} - -static uint16_t toUint16BE(const uint8_t *arr) -{ - uint16_t num = arr[1]; - num |= ((arr[0] << 8) & 0xFF00); - return num; -} - -static int16_t toSint16BE(const uint8_t *arr) -{ - int16_t num = *reinterpret_cast(&arr[0]); - num *= 1 << 8; - num |= arr[1]; - return num; -} - -static const char *wopl3_magic = "WOPL3-BANK\0"; -static const uint16_t wopl_latest_version = 3; - -#define WOPL_INST_SIZE_V2 62 -#define WOPL_INST_SIZE_V3 66 - -enum WOPL_InstrumentFlags -{ - WOPL_Flags_NONE = 0, - WOPL_Flag_Enable4OP = 0x01, - WOPL_Flag_Pseudo4OP = 0x02, - WOPL_Flag_NoSound = 0x04 -}; - struct WOPL_Inst { bool fourOps; @@ -120,27 +83,13 @@ struct WOPL_Inst uint16_t ms_sound_koff; }; -static bool readInstrument(MIDIplay::fileReader &file, WOPL_Inst &ins, uint16_t &version, bool isPercussion = false) +static void cvt_WOPLI_to_FMIns(WOPL_Inst &ins, WOPLInstrument &in) { - uint8_t idata[WOPL_INST_SIZE_V3]; - if(version >= 3) - { - if(file.read(idata, 1, WOPL_INST_SIZE_V3) != WOPL_INST_SIZE_V3) - return false; - } - else - { - if(file.read(idata, 1, WOPL_INST_SIZE_V2) != WOPL_INST_SIZE_V2) - return false; - } - - //strncpy(ins.name, char_p(idata), 32); - ins.op[0].finetune = (int8_t)toSint16BE(idata + 32); - ins.op[1].finetune = (int8_t)toSint16BE(idata + 34); - //ins.velocity_offset = int8_t(idata[36]); + ins.op[0].finetune = in.note_offset1; + ins.op[1].finetune = in.note_offset2; ins.adlins.voice2_fine_tune = 0.0; - int8_t voice2_fine_tune = int8_t(idata[37]); + int8_t voice2_fine_tune = in.second_voice_detune; if(voice2_fine_tune != 0) { if(voice2_fine_tune == 1) @@ -151,202 +100,145 @@ static bool readInstrument(MIDIplay::fileReader &file, WOPL_Inst &ins, uint16_t ins.adlins.voice2_fine_tune = ((voice2_fine_tune * 15.625) / 1000.0); } - ins.adlins.tone = isPercussion ? idata[38] : 0; + ins.adlins.tone = in.percussion_key_number; - uint8_t flags = idata[39]; - ins.adlins.flags = (flags & WOPL_Flag_Enable4OP) && (flags & WOPL_Flag_Pseudo4OP) ? adlinsdata::Flag_Pseudo4op : 0; - ins.adlins.flags|= (flags & WOPL_Flag_NoSound) ? adlinsdata::Flag_NoSound : 0; - ins.fourOps = (flags & WOPL_Flag_Enable4OP) || (flags & WOPL_Flag_Pseudo4OP); + ins.adlins.flags = (in.inst_flags & WOPL_Ins_4op) && (in.inst_flags & WOPL_Ins_Pseudo4op) ? adlinsdata::Flag_Pseudo4op : 0; + ins.adlins.flags|= (in.inst_flags & WOPL_Ins_IsBlank) ? adlinsdata::Flag_NoSound : 0; + ins.fourOps = (in.inst_flags & WOPL_Ins_4op) || (in.inst_flags & WOPL_Ins_Pseudo4op); - ins.op[0].feedconn = (idata[40]); - ins.op[1].feedconn = (idata[41]); + ins.op[0].feedconn = in.fb_conn1_C0; + ins.op[1].feedconn = in.fb_conn2_C0; for(size_t op = 0, slt = 0; op < 4; op++, slt++) { - size_t off = 42 + size_t(op) * 5; - // ins.setAVEKM(op, idata[off + 0]);//AVEKM - // ins.setAtDec(op, idata[off + 2]);//AtDec - // ins.setSusRel(op, idata[off + 3]);//SusRel - // ins.setWaveForm(op, idata[off + 4]);//WaveForm - // ins.setKSLL(op, idata[off + 1]);//KSLL ins.op[slt].carrier_E862 = - ((static_cast(idata[off + 4]) << 24) & 0xFF000000) //WaveForm - | ((static_cast(idata[off + 3]) << 16) & 0x00FF0000) //SusRel - | ((static_cast(idata[off + 2]) << 8) & 0x0000FF00) //AtDec - | ((static_cast(idata[off + 0]) << 0) & 0x000000FF); //AVEKM - ins.op[slt].carrier_40 = idata[off + 1];//KSLL + ((static_cast(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm + | ((static_cast(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel + | ((static_cast(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec + | ((static_cast(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM + ins.op[slt].carrier_40 = in.operators[op].ksl_l_40;//KSLL op++; - off = 42 + size_t(op) * 5; ins.op[slt].modulator_E862 = - ((static_cast(idata[off + 4]) << 24) & 0xFF000000) //WaveForm - | ((static_cast(idata[off + 3]) << 16) & 0x00FF0000) //SusRel - | ((static_cast(idata[off + 2]) << 8) & 0x0000FF00) //AtDec - | ((static_cast(idata[off + 0]) << 0) & 0x000000FF); //AVEKM - ins.op[slt].modulator_40 = idata[off + 1];//KSLL + ((static_cast(in.operators[op].waveform_E0) << 24) & 0xFF000000) //WaveForm + | ((static_cast(in.operators[op].susrel_80) << 16) & 0x00FF0000) //SusRel + | ((static_cast(in.operators[op].atdec_60) << 8) & 0x0000FF00) //AtDec + | ((static_cast(in.operators[op].avekf_20) << 0) & 0x000000FF); //AVEKM + ins.op[slt].modulator_40 = in.operators[op].ksl_l_40;//KSLL } - if(version >= 3) - { - ins.ms_sound_kon = toUint16BE(idata + 62); - ins.ms_sound_koff = toUint16BE(idata + 64); - } - else - { - ins.ms_sound_kon = 1000; - ins.ms_sound_koff = 500; - } - - return true; + ins.ms_sound_kon = in.delay_on_ms; + ins.ms_sound_koff = in.delay_off_ms; } bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) { + int err = 0; + WOPLFile *wopl = NULL; + char *raw_file_data = NULL; size_t fsize; - ADL_UNUSED(fsize); if(!fr.isValid()) { errorStringOut = "Custom bank: Invalid data stream!"; return false; } - char magic[32]; - std::memset(magic, 0, 32); - - uint16_t version = 0; - - uint16_t count_melodic_banks = 1; - uint16_t count_percusive_banks = 1; - - if(fr.read(magic, 1, 11) != 11) - { - errorStringOut = "Custom bank: Can't read magic number!"; - return false; - } - - if(std::strncmp(magic, wopl3_magic, 11) != 0) + // Read complete bank file into the memory + fr.seek(0, SEEK_END); + fsize = fr.tell(); + fr.seek(0, SEEK_SET); + // Allocate necessary memory block + raw_file_data = (char*)malloc(fsize); + if(!raw_file_data) { - errorStringOut = "Custom bank: Invalid magic number!"; + errorStringOut = "Custom bank: Out of memory before of read!"; return false; } + fr.read(raw_file_data, 1, fsize); - uint8_t version_raw[2]; - if(fr.read(version_raw, 1, 2) != 2) - { - errorStringOut = "Custom bank: Can't read version!"; - return false; - } + // Parse bank file from the memory + wopl = WOPL_LoadBankFromMem((void*)raw_file_data, fsize, &err); + //Free the buffer no more needed + free(raw_file_data); - version = toUint16LE(version_raw); - if(version > wopl_latest_version) + // Check for any erros + if(!wopl) { - errorStringOut = "Custom bank: Unsupported WOPL version!"; - return false; - } - - uint8_t head[6]; - std::memset(head, 0, 6); - if(fr.read(head, 1, 6) != 6) - { - errorStringOut = "Custom bank: Can't read header!"; - return false; - } - - count_melodic_banks = toUint16BE(head); - count_percusive_banks = toUint16BE(head + 2); - - if((count_melodic_banks < 1) || (count_percusive_banks < 1)) - { - errorStringOut = "Custom bank: Too few banks in this file!"; - return false; + switch(err) + { + case WOPL_ERR_BAD_MAGIC: + errorStringOut = "Custom bank: Invalid magic!"; + return false; + case WOPL_ERR_UNEXPECTED_ENDING: + errorStringOut = "Custom bank: Unexpected ending!"; + return false; + case WOPL_ERR_INVALID_BANKS_COUNT: + errorStringOut = "Custom bank: Invalid banks count!"; + return false; + case WOPL_ERR_NEWER_VERSION: + errorStringOut = "Custom bank: Version is newer than supported by this library!"; + return false; + case WOPL_ERR_OUT_OF_MEMORY: + errorStringOut = "Custom bank: Out of memory!"; + return false; + default: + errorStringOut = "Custom bank: Unknown error!"; + return false; + } } - /*UNUSED YET*/ - bool default_deep_vibrato = ((head[4]>>0) & 0x01); - bool default_deep_tremolo = ((head[4]>>1) & 0x01); - - //5'th byte reserved for Deep-Tremolo and Deep-Vibrato flags - m_setup.HighTremoloMode = default_deep_tremolo; - m_setup.HighVibratoMode = default_deep_vibrato; - //6'th byte reserved for ADLMIDI's default volume model - m_setup.VolumeModel = (int)head[5]; + m_setup.HighTremoloMode = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0; + m_setup.HighVibratoMode = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0; + m_setup.VolumeModel = wopl->volume_model; + /* TODO: Avoid memory reallocation in nearest future! */ opl.dynamic_melodic_banks.clear(); opl.dynamic_percussion_banks.clear(); opl.setEmbeddedBank(m_setup.AdlBank); - if(version >= 2)//Read bank meta-entries + OPL3::BankMap *slots_banks[2] = { &opl.dynamic_melodic_banks, &opl.dynamic_percussion_banks}; + uint16_t slots_counts[2] = {wopl->banks_count_melodic, wopl->banks_count_percussion}; + WOPLBank *slots_src_ins[2] = { wopl->banks_melodic, wopl->banks_percussive }; + + for(int ss = 0; ss < 2; ss++) { - for(uint16_t i = 0; i < count_melodic_banks; i++) + for(int i = 0; i < slots_counts[ss]; i++) { - uint8_t bank_meta[34]; - if(fr.read(bank_meta, 1, 34) != 34) - { - errorStringOut = "Custom bank: Fail to read melodic bank meta-data!"; - return false; - } - uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]); - size_t offset = opl.dynamic_melodic_banks.size(); - opl.dynamic_melodic_banks[bank] = offset; - //strncpy(bankMeta.name, char_p(bank_meta), 32); - } + uint16_t bank = (slots_src_ins[ss][i].bank_midi_msb * 256) + slots_src_ins[ss][i].bank_midi_lsb; + size_t offset = slots_banks[ss]->size(); + (*slots_banks[ss])[bank] = offset; - for(uint16_t i = 0; i < count_percusive_banks; i++) - { - uint8_t bank_meta[34]; - if(fr.read(bank_meta, 1, 34) != 34) + for(int j = 0; j < 128; j++) { - errorStringOut = "Custom bank: Fail to read percussion bank meta-data!"; - return false; + WOPL_Inst ins; + std::memset(&ins, 0, sizeof(WOPL_Inst)); + WOPLInstrument &inIns = slots_src_ins[ss][i].ins[j]; + + cvt_WOPLI_to_FMIns(ins, inIns); + + ins.adlins.ms_sound_kon = ins.ms_sound_kon; + ins.adlins.ms_sound_koff = ins.ms_sound_koff; + ins.adlins.adlno1 = static_cast(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); + opl.dynamic_instruments.push_back(ins.op[0]); + ins.adlins.adlno2 = ins.adlins.adlno1; + if(ins.fourOps) + { + ins.adlins.adlno2 = static_cast(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); + opl.dynamic_instruments.push_back(ins.op[1]); + } + opl.dynamic_metainstruments.push_back(ins.adlins); } - uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]); - size_t offset = opl.dynamic_percussion_banks.size(); - opl.dynamic_percussion_banks[bank] = offset; - //strncpy(bankMeta.name, char_p(bank_meta), 32); - } - } - - uint16_t total = 128 * count_melodic_banks; - bool readPercussion = false; - -tryAgain: - for(uint16_t i = 0; i < total; i++) - { - WOPL_Inst ins; - std::memset(&ins, 0, sizeof(WOPL_Inst)); - if(!readInstrument(fr, ins, version, readPercussion)) - { - opl.setEmbeddedBank(m_setup.AdlBank); - errorStringOut = "Custom bank: Fail to read instrument!"; - return false; - } - ins.adlins.ms_sound_kon = ins.ms_sound_kon; - ins.adlins.ms_sound_koff = ins.ms_sound_koff; - ins.adlins.adlno1 = static_cast(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); - opl.dynamic_instruments.push_back(ins.op[0]); - ins.adlins.adlno2 = ins.adlins.adlno1; - if(ins.fourOps) - { - ins.adlins.adlno2 = static_cast(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); - opl.dynamic_instruments.push_back(ins.op[1]); } - opl.dynamic_metainstruments.push_back(ins.adlins); - } - - if(!readPercussion) - { - total = 128 * count_percusive_banks; - readPercussion = true; - goto tryAgain; } opl.AdlBank = ~0u; // Use dynamic banks! //Percussion offset is count of instruments multipled to count of melodic banks - opl.dynamic_percussion_offset = 128 * count_melodic_banks; - + opl.dynamic_percussion_offset = 128 * wopl->banks_count_melodic; applySetup(); + WOPL_Free(wopl); + return true; } diff --git a/src/wopl/wopl_file.c b/src/wopl/wopl_file.c new file mode 100644 index 0000000..1d4ceeb --- /dev/null +++ b/src/wopl/wopl_file.c @@ -0,0 +1,582 @@ +/* + * Wohlstand's OPL3 Bank File - a bank format to store OPL3 timbre data and setup + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "wopl_file.h" +#include +#include + +static const char *wopl3_magic = "WOPL3-BANK\0"; +static const char *wopli_magic = "WOPL3-INST\0"; + +static const uint16_t wopl_latest_version = 3; + +#define WOPL_INST_SIZE_V2 62 +#define WOPL_INST_SIZE_V3 66 + +static uint16_t toUint16LE(const uint8_t *arr) +{ + uint16_t num = arr[0]; + num |= ((arr[1] << 8) & 0xFF00); + return num; +} + +static uint16_t toUint16BE(const uint8_t *arr) +{ + uint16_t num = arr[1]; + num |= ((arr[0] << 8) & 0xFF00); + return num; +} + +static int16_t toSint16BE(const uint8_t *arr) +{ + int16_t num = *(const int8_t *)(&arr[0]); + num *= 1 << 8; + num |= arr[1]; + return num; +} + +static void fromUint16LE(uint16_t in, uint8_t *arr) +{ + arr[0] = in & 0x00FF; + arr[1] = (in >> 8) & 0x00FF; +} + +static void fromUint16BE(uint16_t in, uint8_t *arr) +{ + arr[1] = in & 0x00FF; + arr[0] = (in >> 8) & 0x00FF; +} + +static void fromSint16BE(int16_t in, uint8_t *arr) +{ + arr[1] = in & 0x00FF; + arr[0] = (in >> 8) & 0x00FF; +} + + +WOPLFile *WOPL_Init(uint16_t melodic_banks, uint16_t percussive_banks) +{ + WOPLFile *file = NULL; + if(melodic_banks == 0) + return NULL; + if(percussive_banks == 0) + return NULL; + file = (WOPLFile*)calloc(1, sizeof(WOPLFile)); + if(!file) + return NULL; + file->banks_count_melodic = melodic_banks; + file->banks_count_percussion = percussive_banks; + file->banks_melodic = (WOPLBank*)calloc(1, sizeof(WOPLBank) * melodic_banks ); + file->banks_percussive = (WOPLBank*)calloc(1, sizeof(WOPLBank) * percussive_banks ); + return file; +} + +void WOPL_Free(WOPLFile *file) +{ + if(file) + { + if(file->banks_melodic) + free(file->banks_melodic); + if(file->banks_percussive) + free(file->banks_percussive); + free(file); + } +} + +int WOPL_BanksCmp(const WOPLFile *bank1, const WOPLFile *bank2) +{ + int res = 1; + + res &= (bank1->version == bank2->version); + res &= (bank1->opl_flags == bank2->opl_flags); + res &= (bank1->volume_model == bank2->volume_model); + res &= (bank1->banks_count_melodic == bank2->banks_count_melodic); + res &= (bank1->banks_count_percussion == bank2->banks_count_percussion); + + if(res) + { + int i; + for(i = 0; i < bank1->banks_count_melodic; i++) + res &= (memcmp(&bank1->banks_melodic[i], &bank2->banks_melodic[i], sizeof(WOPLBank)) == 0); + if(res) + { + for(i = 0; i < bank1->banks_count_percussion; i++) + res &= (memcmp(&bank1->banks_percussive[i], &bank2->banks_percussive[i], sizeof(WOPLBank)) == 0); + } + } + + return res; +} + +static void WOPL_parseInstrument(WOPLInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays) +{ + int l; + strncpy(ins->inst_name, (const char*)cursor, 32); + ins->note_offset1 = toSint16BE(cursor + 32); + ins->note_offset2 = toSint16BE(cursor + 34); + ins->midi_velocity_offset = (int8_t)cursor[36]; + ins->second_voice_detune = (int8_t)cursor[37]; + ins->percussion_key_number = cursor[38]; + ins->inst_flags = cursor[39]; + ins->fb_conn1_C0 = cursor[40]; + ins->fb_conn2_C0 = cursor[41]; + for(l = 0; l < 4; l++) + { + size_t off = 42 + (size_t)(l) * 5; + ins->operators[l].avekf_20 = cursor[off + 0]; + ins->operators[l].ksl_l_40 = cursor[off + 1]; + ins->operators[l].atdec_60 = cursor[off + 2]; + ins->operators[l].susrel_80 = cursor[off + 3]; + ins->operators[l].waveform_E0 = cursor[off + 4]; + } + if((version >= 3) && has_sounding_delays) + { + ins->delay_on_ms = toUint16BE(cursor + 62); + ins->delay_off_ms = toUint16BE(cursor + 64); + } +} + +static void WOPL_writeInstrument(WOPLInstrument *ins, uint8_t *cursor, uint16_t version, uint8_t has_sounding_delays) +{ + int l; + strncpy((char*)cursor, ins->inst_name, 32); + fromSint16BE(ins->note_offset1, cursor + 32); + fromSint16BE(ins->note_offset2, cursor + 34); + cursor[36] = (uint8_t)ins->midi_velocity_offset; + cursor[37] = (uint8_t)ins->second_voice_detune; + cursor[38] = ins->percussion_key_number; + cursor[39] = ins->inst_flags; + cursor[40] = ins->fb_conn1_C0; + cursor[41] = ins->fb_conn2_C0; + for(l = 0; l < 4; l++) + { + size_t off = 42 + (size_t)(l) * 5; + cursor[off + 0] = ins->operators[l].avekf_20; + cursor[off + 1] = ins->operators[l].ksl_l_40; + cursor[off + 2] = ins->operators[l].atdec_60; + cursor[off + 3] = ins->operators[l].susrel_80; + cursor[off + 4] = ins->operators[l].waveform_E0; + } + if((version >= 3) && has_sounding_delays) + { + fromUint16BE(ins->delay_on_ms, cursor + 62); + fromUint16BE(ins->delay_off_ms, cursor + 64); + } +} + +WOPLFile *WOPL_LoadBankFromMem(void *mem, size_t length, int *error) +{ + WOPLFile *outFile = NULL; + uint16_t i = 0, j = 0, k = 0; + uint16_t version = 0; + uint16_t count_melodic_banks = 1; + uint16_t count_percusive_banks = 1; + uint8_t *cursor = (uint8_t *)mem; + + WOPLBank *bankslots[2]; + uint16_t bankslots_sizes[2]; + +#define SET_ERROR(err) \ +{\ + WOPL_Free(outFile);\ + if(error)\ + {\ + *error = err;\ + }\ +} + +#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } + + if(!cursor) + { + SET_ERROR(WOPL_ERR_NULL_POINTER); + return NULL; + } + + {/* Magic number */ + if(length < 11) + { + SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING); + return NULL; + } + if(memcmp(cursor, wopl3_magic, 11) != 0) + { + SET_ERROR(WOPL_ERR_BAD_MAGIC); + return NULL; + } + GO_FORWARD(11); + } + + {/* Version code */ + if(length < 2) + { + SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING); + return NULL; + } + version = toUint16LE(cursor); + if(version > wopl_latest_version) + { + SET_ERROR(WOPL_ERR_NEWER_VERSION); + return NULL; + } + GO_FORWARD(2); + } + + {/* Header of WOPL */ + uint8_t head[6]; + if(length < 6) + { + SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING); + return NULL; + } + memcpy(head, cursor, 6); + count_melodic_banks = toUint16BE(head); + count_percusive_banks = toUint16BE(head + 2); + GO_FORWARD(6); + + outFile = WOPL_Init(count_melodic_banks, count_percusive_banks); + if(!outFile) + { + SET_ERROR(WOPL_ERR_OUT_OF_MEMORY); + return NULL; + } + + outFile->version = version; + outFile->opl_flags = head[4]; + outFile->volume_model = head[5]; + } + + bankslots_sizes[0] = count_melodic_banks; + bankslots[0] = outFile->banks_melodic; + bankslots_sizes[1] = count_percusive_banks; + bankslots[1] = outFile->banks_percussive; + + if(version >= 2) /* Bank names and LSB/MSB titles */ + { + for(i = 0; i < 2; i++) + { + for(j = 0; j < bankslots_sizes[i]; j++) + { + if(length < 34) + { + SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING); + return NULL; + } + strncpy(bankslots[i][j].bank_name, (const char*)cursor, 32); + bankslots[i][j].bank_midi_lsb = cursor[32]; + bankslots[i][j].bank_midi_msb = cursor[33]; + GO_FORWARD(34); + } + } + } + + {/* Read instruments data */ + uint16_t insSize = 0; + if(version > 2) + insSize = WOPL_INST_SIZE_V3; + else + insSize = WOPL_INST_SIZE_V2; + for(i = 0; i < 2; i++) + { + if(length < (insSize * 128) * (size_t)bankslots_sizes[i]) + { + SET_ERROR(WOPL_ERR_UNEXPECTED_ENDING); + return NULL; + } + + for(j = 0; j < bankslots_sizes[i]; j++) + { + for(k = 0; k < 128; k++) + { + WOPLInstrument *ins = &bankslots[i][j].ins[k]; + WOPL_parseInstrument(ins, cursor, version, 1); + GO_FORWARD(insSize); + } + } + } + } + +#undef GO_FORWARD +#undef SET_ERROR + + return outFile; +} + +int WOPL_LoadInstFromMem(WOPIFile *file, void *mem, size_t length) +{ + uint16_t version = 0; + uint8_t *cursor = (uint8_t *)mem; + uint16_t ins_size; + + if(!cursor) + return WOPL_ERR_NULL_POINTER; + +#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } + + {/* Magic number */ + if(length < 11) + return WOPL_ERR_UNEXPECTED_ENDING; + if(memcmp(cursor, wopli_magic, 11) != 0) + return WOPL_ERR_BAD_MAGIC; + GO_FORWARD(11); + } + + {/* Version code */ + if(length < 2) + return WOPL_ERR_UNEXPECTED_ENDING; + version = toUint16LE(cursor); + if(version > wopl_latest_version) + return WOPL_ERR_NEWER_VERSION; + GO_FORWARD(2); + } + + {/* is drum flag */ + if(length < 1) + return WOPL_ERR_UNEXPECTED_ENDING; + file->is_drum = *cursor; + GO_FORWARD(1); + } + + if(version > 2) + /* Skip sounding delays are not part of single-instrument file + * two sizes of uint16_t will be subtracted */ + ins_size = WOPL_INST_SIZE_V3 - (sizeof(uint16_t) * 2); + else + ins_size = WOPL_INST_SIZE_V2; + + if(length < ins_size) + return WOPL_ERR_UNEXPECTED_ENDING; + + WOPL_parseInstrument(&file->inst, cursor, version, 0); + GO_FORWARD(ins_size); + + return WOPL_ERR_OK; +#undef GO_FORWARD +} + +size_t WOPL_CalculateBankFileSize(WOPLFile *file, uint16_t version) +{ + size_t final_size = 0; + size_t ins_size = 0; + + if(version == 0) + version = wopl_latest_version; + + if(!file) + return 0; + final_size += 11 + 2 + 2 + 2 + 1 + 1; + /* + * Magic number, + * Version, + * Count of melodic banks, + * Count of percussive banks, + * Chip specific flags + * Volume Model + */ + + if(version >= 2) + { + /* Melodic banks meta-data */ + final_size += (32 + 1 + 1) * file->banks_count_melodic; + /* Percussive banks meta-data */ + final_size += (32 + 1 + 1) * file->banks_count_percussion; + } + + if(version >= 3) + ins_size = WOPL_INST_SIZE_V3; + else + ins_size = WOPL_INST_SIZE_V2; + /* Melodic instruments */ + final_size += (ins_size * 128) * file->banks_count_melodic; + /* Percusive instruments */ + final_size += (ins_size * 128) * file->banks_count_percussion; + + return final_size; +} + +size_t WOPL_CalculateInstFileSize(WOPIFile *file, uint16_t version) +{ + size_t final_size = 0; + size_t ins_size = 0; + + if(version == 0) + version = wopl_latest_version; + + if(!file) + return 0; + final_size += 11 + 2 + 1; + /* + * Magic number, + * version, + * is percussive instrument + */ + + if(version >= 3) + ins_size = WOPL_INST_SIZE_V3; + else + ins_size = WOPL_INST_SIZE_V2; + final_size += ins_size * 128; + + return final_size; +} + +int WOPL_SaveBankToMem(WOPLFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm) +{ + uint8_t *cursor = (uint8_t *)dest_mem; + uint16_t ins_size = 0; + uint16_t i, j, k; + uint16_t banks_melodic = force_gm ? 1 : file->banks_count_melodic; + uint16_t banks_percusive = force_gm ? 1 : file->banks_count_percussion; + + WOPLBank *bankslots[2]; + uint16_t bankslots_sizes[2]; + + if(version == 0) + version = wopl_latest_version; + +#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } + + if(length < 11) + return WOPL_ERR_UNEXPECTED_ENDING; + memcpy(cursor, wopl3_magic, 11); + GO_FORWARD(11); + + if(length < 2) + return WOPL_ERR_UNEXPECTED_ENDING; + fromUint16LE(version, cursor); + GO_FORWARD(2); + + if(length < 2) + return WOPL_ERR_UNEXPECTED_ENDING; + fromUint16BE(banks_melodic, cursor); + GO_FORWARD(2); + + if(length < 2) + return WOPL_ERR_UNEXPECTED_ENDING; + fromUint16BE(banks_percusive, cursor); + GO_FORWARD(2); + + if(length < 2) + return WOPL_ERR_UNEXPECTED_ENDING; + cursor[0] = file->opl_flags; + cursor[1] = file->volume_model; + GO_FORWARD(2); + + bankslots[0] = file->banks_melodic; + bankslots_sizes[0] = banks_melodic; + bankslots[1] = file->banks_percussive; + bankslots_sizes[1] = banks_percusive; + + if(version >= 2) + { + for(i = 0; i < 2; i++) + { + for(j = 0; j < bankslots_sizes[i]; j++) + { + if(length < 34) + return WOPL_ERR_UNEXPECTED_ENDING; + strncpy((char*)cursor, bankslots[i][j].bank_name, 32); + cursor[32] = bankslots[i][j].bank_midi_lsb; + cursor[33] = bankslots[i][j].bank_midi_msb; + GO_FORWARD(34); + } + } + } + + {/* Write instruments data */ + if(version >= 3) + ins_size = WOPL_INST_SIZE_V3; + else + ins_size = WOPL_INST_SIZE_V2; + for(i = 0; i < 2; i++) + { + if(length < (ins_size * 128) * (size_t)bankslots_sizes[i]) + return WOPL_ERR_UNEXPECTED_ENDING; + + for(j = 0; j < bankslots_sizes[i]; j++) + { + for(k = 0; k < 128; k++) + { + WOPLInstrument *ins = &bankslots[i][j].ins[k]; + WOPL_writeInstrument(ins, cursor, version, 1); + GO_FORWARD(ins_size); + } + } + } + } + + return WOPL_ERR_OK; +#undef GO_FORWARD +} + +int WOPL_SaveInstToMem(WOPIFile *file, void *dest_mem, size_t length, uint16_t version) +{ + uint8_t *cursor = (uint8_t *)dest_mem; + uint16_t ins_size; + + if(!cursor) + return WOPL_ERR_NULL_POINTER; + + if(version == 0) + version = wopl_latest_version; + +#define GO_FORWARD(bytes) { cursor += bytes; length -= bytes; } + + {/* Magic number */ + if(length < 11) + return WOPL_ERR_UNEXPECTED_ENDING; + memcpy(cursor, wopli_magic, 11); + GO_FORWARD(11); + } + + {/* Version code */ + if(length < 2) + return WOPL_ERR_UNEXPECTED_ENDING; + fromUint16LE(version, cursor); + GO_FORWARD(2); + } + + {/* is drum flag */ + if(length < 1) + return WOPL_ERR_UNEXPECTED_ENDING; + *cursor = file->is_drum; + GO_FORWARD(1); + } + + if(version > 2) + /* Skip sounding delays are not part of single-instrument file + * two sizes of uint16_t will be subtracted */ + ins_size = WOPL_INST_SIZE_V3 - (sizeof(uint16_t) * 2); + else + ins_size = WOPL_INST_SIZE_V2; + + if(length < ins_size) + return WOPL_ERR_UNEXPECTED_ENDING; + + WOPL_writeInstrument(&file->inst, cursor, version, 0); + GO_FORWARD(ins_size); + + return WOPL_ERR_OK; +#undef GO_FORWARD +} diff --git a/src/wopl/wopl_file.h b/src/wopl/wopl_file.h new file mode 100644 index 0000000..bf88a50 --- /dev/null +++ b/src/wopl/wopl_file.h @@ -0,0 +1,282 @@ +/* + * Wohlstand's OPL3 Bank File - a bank format to store OPL3 timbre data and setup + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef WOPL_FILE_H +#define WOPL_FILE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Global OPL flags */ +typedef enum +{ + /* Enable Deep-Tremolo flag */ + WOPL_FLAG_DEEP_TREMOLO = 0x01, + /* Enable Deep-Vibrato flag */ + WOPL_FLAG_DEEP_VIBRATO = 0x02 +} WOPLFileFlags; + +/* Volume scaling model implemented in the libADLMIDI */ +typedef enum +{ + WOPL_VM_Generic = 0, + WOPL_VM_Native, + WOPL_VM_DMX, + WOPL_VM_Apogee, + WOPL_VM_Win9x +} WOPL_VolumeModel; + +typedef enum +{ + /* Is two-operator single-voice instrument (no flags) */ + WOPL_Ins_2op = 0x00, + /* Is true four-operator instrument */ + WOPL_Ins_4op = 0x01, + /* Is pseudo four-operator (two 2-operator voices) instrument */ + WOPL_Ins_Pseudo4op = 0x02, + /* Is a blank instrument entry */ + WOPL_Ins_IsBlank = 0x04, + + /* RythmMode flags mask */ + WOPL_RythmModeMask = 0x38, + + /* Mask of the flags range */ + WOPL_Ins_ALL_MASK = 0x07 +} WOPL_InstrumentFlags; + +typedef enum +{ + /* RythmMode: BassDrum */ + WOPL_RM_BassDrum = 0x08, + /* RythmMode: Snare */ + WOPL_RM_Snare = 0x10, + /* RythmMode: TomTom */ + WOPL_RM_TomTom = 0x18, + /* RythmMode: Cymbell */ + WOPL_RM_Cymball = 0x20, + /* RythmMode: HiHat */ + WOPL_RM_HiHat = 0x28 +} WOPL_RythmMode; + +/* Error codes */ +typedef enum +{ + WOPL_ERR_OK = 0, + /* Magic number is not maching */ + WOPL_ERR_BAD_MAGIC, + /* Too short file */ + WOPL_ERR_UNEXPECTED_ENDING, + /* Zero banks count */ + WOPL_ERR_INVALID_BANKS_COUNT, + /* Version of file is newer than supported by current version of library */ + WOPL_ERR_NEWER_VERSION, + /* Out of memory */ + WOPL_ERR_OUT_OF_MEMORY, + /* Given null pointer memory data */ + WOPL_ERR_NULL_POINTER +} WOPL_ErrorCodes; + +/* Operator indeces inside of Instrument Entry */ +#define WOPL_OP_CARRIER1 0 +#define WOPL_OP_MODULATOR1 1 +#define WOPL_OP_CARRIER2 2 +#define WOPL_OP_MODULATOR2 3 + +/* OPL3 Oerators data */ +typedef struct +{ + /* AM/Vib/Env/Ksr/FMult characteristics */ + uint8_t avekf_20; + /* Key Scale Level / Total level register data */ + uint8_t ksl_l_40; + /* Attack / Decay */ + uint8_t atdec_60; + /* Systain and Release register data */ + uint8_t susrel_80; + /* Wave form */ + uint8_t waveform_E0; +} WOPLOperator; + +/* Instrument entry */ +typedef struct +{ + /* Title of the instrument */ + char inst_name[34]; + /* MIDI note key (half-tone) offset for an instrument (or a first voice in pseudo-4-op mode) */ + int16_t note_offset1; + /* MIDI note key (half-tone) offset for a second voice in pseudo-4-op mode */ + int16_t note_offset2; + /* MIDI note velocity offset (taken from Apogee TMB format) */ + int8_t midi_velocity_offset; + /* Second voice detune level (taken from DMX OP2) */ + int8_t second_voice_detune; + /* Percussion MIDI base tone number at which this drum will be played */ + uint8_t percussion_key_number; + /* Enum WOPL_InstrumentFlags */ + uint8_t inst_flags; + /* Feedback&Connection register for first and second operators */ + uint8_t fb_conn1_C0; + /* Feedback&Connection register for third and fourth operators */ + uint8_t fb_conn2_C0; + /* Operators register data */ + WOPLOperator operators[4]; + /* Millisecond delay of sounding while key is on */ + uint16_t delay_on_ms; + /* Millisecond delay of sounding after key off */ + uint16_t delay_off_ms; +} WOPLInstrument; + +/* Bank entry */ +typedef struct +{ + /* Name of bank */ + char bank_name[33]; + /* MIDI Bank LSB code */ + uint8_t bank_midi_lsb; + /* MIDI Bank MSB code */ + uint8_t bank_midi_msb; + /* Instruments data of this bank */ + WOPLInstrument ins[128]; +} WOPLBank; + +/* Instrument data file */ +typedef struct +{ + /* Version of instrument file */ + uint16_t version; + /* Is this a percussion instrument */ + uint8_t is_drum; + /* Instrument data */ + WOPLInstrument inst; +} WOPIFile; + +/* Bank data file */ +typedef struct +{ + /* Version of bank file */ + uint16_t version; + /* Count of melodic banks in this file */ + uint16_t banks_count_melodic; + /* Count of percussion banks in this file */ + uint16_t banks_count_percussion; + /* Enum WOPLFileFlags */ + uint8_t opl_flags; + /* Enum WOPL_VolumeModel */ + uint8_t volume_model; + /* dynamically allocated data Melodic banks array */ + WOPLBank *banks_melodic; + /* dynamically allocated data Percussive banks array */ + WOPLBank *banks_percussive; +} WOPLFile; + + +/** + * @brief Initialize blank WOPL data structure with allocated bank data + * @param melodic_banks Count of melodic banks + * @param percussive_banks Count of percussive banks + * @return pointer to heap-allocated WOPL data structure or NULL when out of memory or incorrectly given banks counts + */ +extern WOPLFile *WOPL_Init(uint16_t melodic_banks, uint16_t percussive_banks); + +/** + * @brief Clean up WOPL data file (all allocated bank arrays will be fried too) + * @param file pointer to heap-allocated WOPL data structure + */ +extern void WOPL_Free(WOPLFile *file); + +/** + * @brief Compare two bank entries + * @param bank1 First bank + * @param bank2 Second bank + * @return 1 if banks are equal or 0 if there are different + */ +extern int WOPL_BanksCmp(const WOPLFile *bank1, const WOPLFile *bank2); + + +/** + * @brief Load WOPL bank file from the memory. + * WOPL data structure will be allocated. (don't forget to clear it with WOPL_Free() after use!) + * @param mem Pointer to memory block contains raw WOPL bank file data + * @param length Length of given memory block + * @param error pointer to integer to return an error code. Pass NULL if you don't want to use error codes. + * @return Heap-allocated WOPL file data structure or NULL if any error has occouped + */ +extern WOPLFile *WOPL_LoadBankFromMem(void *mem, size_t length, int *error); + +/** + * @brief Load WOPI instrument file from the memory. + * You must allocate WOPIFile structure by yourself and give the pointer to it. + * @param file Pointer to destinition WOPIFile structure to fill it with parsed data. + * @param mem Pointer to memory block contains raw WOPI instrument file data + * @param length Length of given memory block + * @return 0 if no errors occouped, or an error code of WOPL_ErrorCodes enumeration + */ +extern int WOPL_LoadInstFromMem(WOPIFile *file, void *mem, size_t length); + +/** + * @brief Calculate the size of the output memory block + * @param file Heap-allocated WOPL file data structure + * @param version Destinition version of the file + * @return Size of the raw WOPL file data + */ +extern size_t WOPL_CalculateBankFileSize(WOPLFile *file, uint16_t version); + +/** + * @brief Calculate the size of the output memory block + * @param file Pointer to WOPI file data structure + * @param version Destinition version of the file + * @return Size of the raw WOPI file data + */ +extern size_t WOPL_CalculateInstFileSize(WOPIFile *file, uint16_t version); + +/** + * @brief Write raw WOPL into given memory block + * @param file Heap-allocated WOPL file data structure + * @param dest_mem Destinition memory block pointer + * @param length Length of destinition memory block + * @param version Wanted WOPL version + * @param force_gm Force GM set in saved bank file + * @return Error code or 0 on success + */ +extern int WOPL_SaveBankToMem(WOPLFile *file, void *dest_mem, size_t length, uint16_t version, uint16_t force_gm); + +/** + * @brief Write raw WOPI into given memory block + * @param file Pointer to WOPI file data structure + * @param dest_mem Destinition memory block pointer + * @param length Length of destinition memory block + * @param version Wanted WOPI version + * @return Error code or 0 on success + */ +extern int WOPL_SaveInstToMem(WOPIFile *file, void *dest_mem, size_t length, uint16_t version); + +#ifdef __cplusplus +} +#endif + +#endif /* WOPL_FILE_H */ -- cgit v1.2.3 From 9e126007c0e0ffacece64efb436490403dee1493 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Fri, 20 Apr 2018 05:38:46 +0200 Subject: fix implementation defined behavior --- src/wopl/wopl_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/wopl/wopl_file.c b/src/wopl/wopl_file.c index 1d4ceeb..af141f2 100644 --- a/src/wopl/wopl_file.c +++ b/src/wopl/wopl_file.c @@ -71,7 +71,7 @@ static void fromUint16BE(uint16_t in, uint8_t *arr) static void fromSint16BE(int16_t in, uint8_t *arr) { arr[1] = in & 0x00FF; - arr[0] = (in >> 8) & 0x00FF; + arr[0] = ((uint16_t)in >> 8) & 0x00FF; } -- cgit v1.2.3 From bae786e1a6176db4733d4bc495b32aaeb26b451c Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Sat, 21 Apr 2018 10:21:52 +0200 Subject: wopl robustness fix --- src/wopl/wopl_file.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/wopl/wopl_file.c b/src/wopl/wopl_file.c index af141f2..25b75be 100644 --- a/src/wopl/wopl_file.c +++ b/src/wopl/wopl_file.c @@ -133,6 +133,7 @@ static void WOPL_parseInstrument(WOPLInstrument *ins, uint8_t *cursor, uint16_t { int l; strncpy(ins->inst_name, (const char*)cursor, 32); + ins->inst_name[32] = '\0'; ins->note_offset1 = toSint16BE(cursor + 32); ins->note_offset2 = toSint16BE(cursor + 34); ins->midi_velocity_offset = (int8_t)cursor[36]; @@ -284,6 +285,7 @@ WOPLFile *WOPL_LoadBankFromMem(void *mem, size_t length, int *error) return NULL; } strncpy(bankslots[i][j].bank_name, (const char*)cursor, 32); + bankslots[i][j].bank_name[32] = '\0'; bankslots[i][j].bank_midi_lsb = cursor[32]; bankslots[i][j].bank_midi_msb = cursor[33]; GO_FORWARD(34); -- cgit v1.2.3 From 85ebe5214c784f0ac00a0f12684be520a7650ea4 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Sat, 21 Apr 2018 15:53:29 +0200 Subject: wopl API: give names to struct and enum types --- src/wopl/wopl_file.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/wopl/wopl_file.h b/src/wopl/wopl_file.h index bf88a50..f5d816b 100644 --- a/src/wopl/wopl_file.h +++ b/src/wopl/wopl_file.h @@ -33,7 +33,7 @@ extern "C" { #endif /* Global OPL flags */ -typedef enum +typedef enum WOPLFileFlags { /* Enable Deep-Tremolo flag */ WOPL_FLAG_DEEP_TREMOLO = 0x01, @@ -42,7 +42,7 @@ typedef enum } WOPLFileFlags; /* Volume scaling model implemented in the libADLMIDI */ -typedef enum +typedef enum WOPL_VolumeModel { WOPL_VM_Generic = 0, WOPL_VM_Native, @@ -51,7 +51,7 @@ typedef enum WOPL_VM_Win9x } WOPL_VolumeModel; -typedef enum +typedef enum WOPL_InstrumentFlags { /* Is two-operator single-voice instrument (no flags) */ WOPL_Ins_2op = 0x00, @@ -69,7 +69,7 @@ typedef enum WOPL_Ins_ALL_MASK = 0x07 } WOPL_InstrumentFlags; -typedef enum +typedef enum WOPL_RythmMode { /* RythmMode: BassDrum */ WOPL_RM_BassDrum = 0x08, @@ -84,7 +84,7 @@ typedef enum } WOPL_RythmMode; /* Error codes */ -typedef enum +typedef enum WOPL_ErrorCodes { WOPL_ERR_OK = 0, /* Magic number is not maching */ @@ -108,7 +108,7 @@ typedef enum #define WOPL_OP_MODULATOR2 3 /* OPL3 Oerators data */ -typedef struct +typedef struct WOPLOperator { /* AM/Vib/Env/Ksr/FMult characteristics */ uint8_t avekf_20; @@ -123,7 +123,7 @@ typedef struct } WOPLOperator; /* Instrument entry */ -typedef struct +typedef struct WOPLInstrument { /* Title of the instrument */ char inst_name[34]; @@ -152,7 +152,7 @@ typedef struct } WOPLInstrument; /* Bank entry */ -typedef struct +typedef struct WOPLBank { /* Name of bank */ char bank_name[33]; @@ -165,7 +165,7 @@ typedef struct } WOPLBank; /* Instrument data file */ -typedef struct +typedef struct WOPIFile { /* Version of instrument file */ uint16_t version; @@ -176,7 +176,7 @@ typedef struct } WOPIFile; /* Bank data file */ -typedef struct +typedef struct WOPLFile { /* Version of bank file */ uint16_t version; -- cgit v1.2.3