From c2d5573367790b4b41dd245da1859ee0c9e96dd2 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Tue, 4 Jun 2019 14:53:52 +0200 Subject: [PATCH] WIP MIDIplay v2 --- CMakeLists.txt | 4 + src/adlmidi.cpp | 4 +- src/adlmidi_midiplay.cpp | 352 ++++--------------------- src/adlmidi_midiplay.hpp | 475 +--------------------------------- src/midiplay/chip_voice.cpp | 43 +++ src/midiplay/chip_voice.hpp | 120 +++++++++ src/midiplay/cpp_extras.cpp | 252 ++++++++++++++++++ src/midiplay/event_hooks.hpp | 48 ++++ src/midiplay/midi_channel.hpp | 342 ++++++++++++++++++++++++ src/midiplay/player.hpp | 23 ++ src/midiplay/setup.hpp | 56 ++++ 11 files changed, 949 insertions(+), 770 deletions(-) create mode 100644 src/midiplay/chip_voice.cpp create mode 100644 src/midiplay/chip_voice.hpp create mode 100644 src/midiplay/cpp_extras.cpp create mode 100644 src/midiplay/event_hooks.hpp create mode 100644 src/midiplay/midi_channel.hpp create mode 100644 src/midiplay/player.hpp create mode 100644 src/midiplay/setup.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 86a4b4ed..66a1af1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,8 @@ set(libADLMIDI_SOURCES ${libADLMIDI_SOURCE_DIR}/src/adlmidi_midiplay.cpp ${libADLMIDI_SOURCE_DIR}/src/adlmidi_opl3.cpp ${libADLMIDI_SOURCE_DIR}/src/adlmidi_private.cpp + ${libADLMIDI_SOURCE_DIR}/src/midiplay/chip_voice.cpp + ${libADLMIDI_SOURCE_DIR}/src/midiplay/cpp_extras.cpp ${libADLMIDI_SOURCE_DIR}/src/wopl/wopl_file.c ) @@ -225,6 +227,7 @@ if(libADLMIDI_STATIC OR WITH_VLC_PLUGIN) add_library(ADLMIDI_static STATIC ${libADLMIDI_SOURCES}) set_target_properties(ADLMIDI_static PROPERTIES OUTPUT_NAME ADLMIDI) target_include_directories(ADLMIDI_static PUBLIC ${libADLMIDI_SOURCE_DIR}/include) + target_include_directories(ADLMIDI_static PRIVATE ${libADLMIDI_SOURCE_DIR}/src) set_legacy_standard(ADLMIDI_static) set_visibility_hidden(ADLMIDI_static) handle_options(ADLMIDI_static) @@ -238,6 +241,7 @@ if(libADLMIDI_SHARED) add_library(ADLMIDI_shared SHARED ${libADLMIDI_SOURCES}) set_target_properties(ADLMIDI_shared PROPERTIES OUTPUT_NAME ADLMIDI) target_include_directories(ADLMIDI_shared PUBLIC ${libADLMIDI_SOURCE_DIR}/include) + target_include_directories(ADLMIDI_shared PRIVATE ${libADLMIDI_SOURCE_DIR}/src) set_legacy_standard(ADLMIDI_shared) set_visibility_hidden(ADLMIDI_shared) handle_options(ADLMIDI_shared) diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 72cf601a..eb0e70d5 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -1254,7 +1254,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, MidiPlayer *player = GET_MIDI_PLAYER(device); assert(player); - MidiPlayer::Setup &setup = player->m_setup; + MIDIsetup &setup = player->m_setup; ssize_t gotten_len = 0; ssize_t n_periodCountStereo = 512; @@ -1362,7 +1362,7 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC MidiPlayer *player = GET_MIDI_PLAYER(device); assert(player); - MidiPlayer::Setup &setup = player->m_setup; + MIDIsetup &setup = player->m_setup; ssize_t gotten_len = 0; ssize_t n_periodCountStereo = 512; diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index b8a8cf78..31c4f058 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -86,28 +86,6 @@ inline bool isXgPercChannel(uint8_t msb, uint8_t lsb) return (msb == 0x7E || msb == 0x7F) && (lsb == 0); } -void MIDIplay::AdlChannel::addAge(int64_t us) -{ - const int64_t neg = 1000 * static_cast(-0x1FFFFFFFl); - if(users.empty()) - { - koff_time_until_neglible_us = std::max(koff_time_until_neglible_us - us, neg); - if(koff_time_until_neglible_us < 0) - koff_time_until_neglible_us = 0; - } - else - { - koff_time_until_neglible_us = 0; - for(users_iterator i = users.begin(); !i.is_end(); ++i) - { - LocationData &d = i->value; - if(!d.fixed_sustain) - d.kon_time_until_neglible_us = std::max(d.kon_time_until_neglible_us - us, neg); - d.vibdelay_us += us; - } - } -} - MIDIplay::MIDIplay(unsigned long sampleRate): m_cmfPercussionMode(false), m_masterVolume(MasterVolumeDefault), @@ -237,7 +215,7 @@ void MIDIplay::TickIterators(double s) Synth &synth = *m_synth; for(uint32_t c = 0, n = synth.m_numChannels; c < n; ++c) { - AdlChannel &ch = m_chipChannels[c]; + ChipChannel &ch = m_chipChannels[c]; ch.addAge(static_cast(s * 1e6)); } @@ -753,14 +731,14 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) case 64: // Enable/disable sustain m_midiChannels[channel].sustain = (value >= 64); if(!m_midiChannels[channel].sustain) - killSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_Pedal); + killSustainingNotes(channel, -1, ChipChannel::LocationData::Sustain_Pedal); break; case 66: // Enable/disable sostenuto if(value >= 64) //Find notes and mark them as sostenutoed markSostenutoNotes(channel); else - killSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_Sostenuto); + killSustainingNotes(channel, -1, ChipChannel::LocationData::Sustain_Sostenuto); break; case 67: // Enable/disable soft-pedal @@ -782,7 +760,7 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) m_midiChannels[channel].resetAllControllers(); noteUpdateAll(channel, Upd_Pan + Upd_Volume + Upd_Pitch); // Kill all sustained notes - killSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_ANY); + killSustainingNotes(channel, -1, ChipChannel::LocationData::Sustain_ANY); break; case 120: // All sounds off @@ -1118,7 +1096,7 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) void MIDIplay::realTime_panic() { panic(); - killSustainingNotes(-1, -1, AdlChannel::LocationData::Sustain_ANY); + killSustainingNotes(-1, -1, ChipChannel::LocationData::Sustain_ANY); } void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length) @@ -1164,7 +1142,7 @@ void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) #endif void MIDIplay::noteUpdate(size_t midCh, - MIDIplay::MIDIchannel::notes_iterator i, + MIDIchannel::notes_iterator i, unsigned props_mask, int32_t select_adlchn) { @@ -1175,7 +1153,7 @@ void MIDIplay::noteUpdate(size_t midCh, const uint8_t vol = info.vol; const int midiins = static_cast(info.midiins); const adlinsdata2 &ains = *info.ains; - AdlChannel::Location my_loc; + ChipChannel::Location my_loc; my_loc.MidCh = static_cast(midCh); my_loc.note = info.note; @@ -1196,11 +1174,11 @@ void MIDIplay::noteUpdate(size_t midCh, if(props_mask & Upd_Patch) { synth.setPatch(c, ins.ains); - AdlChannel::users_iterator i = m_chipChannels[c].find_or_create_user(my_loc); + ChipChannel::users_iterator i = m_chipChannels[c].find_or_create_user(my_loc); if(!i.is_end()) // inserts if necessary { - AdlChannel::LocationData &d = i->value; - d.sustained = AdlChannel::LocationData::Sustain_None; + ChipChannel::LocationData &d = i->value; + d.sustained = ChipChannel::LocationData::Sustain_None; d.vibdelay_us = 0; d.fixed_sustain = (ains.ms_sound_kon == static_cast(adlNoteOnMaxTime)); d.kon_time_until_neglible_us = 1000 * ains.ms_sound_kon; @@ -1222,8 +1200,8 @@ void MIDIplay::noteUpdate(size_t midCh, { if(!m_midiChannels[midCh].sustain) { - AdlChannel::users_iterator k = m_chipChannels[c].find_user(my_loc); - bool do_erase_user = (!k.is_end() && ((k->value.sustained & AdlChannel::LocationData::Sustain_Sostenuto) == 0)); + ChipChannel::users_iterator k = m_chipChannels[c].find_user(my_loc); + bool do_erase_user = (!k.is_end() && ((k->value.sustained & ChipChannel::LocationData::Sustain_Sostenuto) == 0)); if(do_erase_user) m_chipChannels[c].users.erase(k); @@ -1248,9 +1226,9 @@ void MIDIplay::noteUpdate(size_t midCh, { // Sustain: Forget about the note, but don't key it off. // Also will avoid overwriting it very soon. - AdlChannel::users_iterator d = m_chipChannels[c].find_or_create_user(my_loc); + ChipChannel::users_iterator d = m_chipChannels[c].find_or_create_user(my_loc); if(!d.is_end()) - d->value.sustained |= AdlChannel::LocationData::Sustain_Pedal; // note: not erased! + d->value.sustained |= ChipChannel::LocationData::Sustain_Pedal; // note: not erased! if(hooks.onNote) hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, -1, 0.0); } @@ -1352,10 +1330,10 @@ void MIDIplay::noteUpdate(size_t midCh, if(props_mask & Upd_Pitch) { - AdlChannel::users_iterator d = m_chipChannels[c].find_user(my_loc); + ChipChannel::users_iterator d = m_chipChannels[c].find_user(my_loc); // Don't bend a sustained note - if(d.is_end() || (d->value.sustained == AdlChannel::LocationData::Sustain_None)) + if(d.is_end() || (d->value.sustained == ChipChannel::LocationData::Sustain_None)) { MIDIchannel &chan = m_midiChannels[midCh]; double midibend = chan.bend * chan.bendsense; @@ -1410,7 +1388,7 @@ void MIDIplay::setErrorString(const std::string &err) int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins) const { Synth &synth = *m_synth; - const AdlChannel &chan = m_chipChannels[c]; + const ChipChannel &chan = m_chipChannels[c]; int64_t koff_ms = chan.koff_time_until_neglible_us / 1000; int64_t s = -koff_ms; @@ -1425,12 +1403,12 @@ int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::Note } // Same midi-instrument = some stability - for(AdlChannel::const_users_iterator j = chan.users.begin(); !j.is_end(); ++j) + for(ChipChannel::const_users_iterator j = chan.users.begin(); !j.is_end(); ++j) { - const AdlChannel::LocationData &jd = j->value; + const ChipChannel::LocationData &jd = j->value; int64_t kon_ms = jd.kon_time_until_neglible_us / 1000; - s -= (jd.sustained == AdlChannel::LocationData::Sustain_None) ? + s -= (jd.sustained == ChipChannel::LocationData::Sustain_None) ? (4000000 + kon_ms) : (500000 + (kon_ms / 2)); MIDIchannel::notes_iterator @@ -1474,10 +1452,10 @@ int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::Note if(synth.m_channelCategory[c2] != synth.m_channelCategory[c]) continue; - for(AdlChannel::const_users_iterator m = m_chipChannels[c2].users.begin(); !m.is_end(); ++m) + for(ChipChannel::const_users_iterator m = m_chipChannels[c2].users.begin(); !m.is_end(); ++m) { - const AdlChannel::LocationData &md = m->value; - if(md.sustained != AdlChannel::LocationData::Sustain_None) continue; + const ChipChannel::LocationData &md = m->value; + if(md.sustained != ChipChannel::LocationData::Sustain_None) continue; if(md.vibdelay_us >= 200000) continue; if(md.ins != jd.ins) continue; n_evacuation_stations += 1; @@ -1498,13 +1476,13 @@ void MIDIplay::prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInf Synth &synth = *m_synth; //bool doing_arpeggio = false; - for(AdlChannel::users_iterator jnext = m_chipChannels[c].users.begin(); !jnext.is_end();) + for(ChipChannel::users_iterator jnext = m_chipChannels[c].users.begin(); !jnext.is_end();) { - AdlChannel::users_iterator j = jnext; - AdlChannel::LocationData &jd = jnext->value; + ChipChannel::users_iterator j = jnext; + ChipChannel::LocationData &jd = jnext->value; ++jnext; - if(jd.sustained == AdlChannel::LocationData::Sustain_None) + if(jd.sustained == ChipChannel::LocationData::Sustain_None) { // Collision: Kill old note, // UNLESS we're going to do arpeggio @@ -1529,7 +1507,7 @@ void MIDIplay::prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInf // Kill all sustained notes on this channel // Don't keep them for arpeggio, because arpeggio requires // an intact "activenotes" record. This is a design flaw. - killSustainingNotes(-1, static_cast(c), AdlChannel::LocationData::Sustain_ANY); + killSustainingNotes(-1, static_cast(c), ChipChannel::LocationData::Sustain_ANY); // Keyoff the channel so that it can be retriggered, // unless the new note will be introduced as just an arpeggio. @@ -1538,12 +1516,12 @@ void MIDIplay::prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInf } void MIDIplay::killOrEvacuate(size_t from_channel, - AdlChannel::users_iterator j, - MIDIplay::MIDIchannel::notes_iterator i) + ChipChannel::users_iterator j, + MIDIchannel::notes_iterator i) { Synth &synth = *m_synth; uint32_t maxChannels = ADL_MAX_CHIPS * 18; - AdlChannel::LocationData &jd = j->value; + ChipChannel::LocationData &jd = j->value; MIDIchannel::NoteInfo &info = i->value; // Before killing the note, check if it can be @@ -1562,7 +1540,7 @@ void MIDIplay::killOrEvacuate(size_t from_channel, if(synth.m_channelCategory[c] != synth.m_channelCategory[from_channel]) continue; - AdlChannel &adlch = m_chipChannels[c]; + ChipChannel &adlch = m_chipChannels[c]; if(adlch.users.size() == adlch.users.capacity()) continue; // no room for more arpeggio on channel @@ -1570,9 +1548,9 @@ void MIDIplay::killOrEvacuate(size_t from_channel, continue; // channel already has this note playing (sustained) // avoid introducing a duplicate location. - for(AdlChannel::users_iterator m = adlch.users.begin(); !m.is_end(); ++m) + for(ChipChannel::users_iterator m = adlch.users.begin(); !m.is_end(); ++m) { - AdlChannel::LocationData &mv = m->value; + ChipChannel::LocationData &mv = m->value; if(mv.vibdelay_us >= 200000 && mv.kon_time_until_neglible_us < 10000000) continue; @@ -1638,10 +1616,10 @@ void MIDIplay::killSustainingNotes(int32_t midCh, int32_t this_adlchn, uint32_t if(m_chipChannels[c].users.empty()) continue; // Nothing to do - for(AdlChannel::users_iterator jnext = m_chipChannels[c].users.begin(); !jnext.is_end();) + for(ChipChannel::users_iterator jnext = m_chipChannels[c].users.begin(); !jnext.is_end();) { - AdlChannel::users_iterator j = jnext; - AdlChannel::LocationData &jd = j->value; + ChipChannel::users_iterator j = jnext; + ChipChannel::LocationData &jd = j->value; ++jnext; if((midCh < 0 || jd.loc.MidCh == midCh) @@ -1651,7 +1629,7 @@ void MIDIplay::killSustainingNotes(int32_t midCh, int32_t this_adlchn, uint32_t if(hooks.onNote) hooks.onNote(hooks.onNote_userData, (int)c, jd.loc.note, midiins, 0, 0.0); jd.sustained &= ~sustain_type; - if(jd.sustained == AdlChannel::LocationData::Sustain_None) + if(jd.sustained == ChipChannel::LocationData::Sustain_None) m_chipChannels[c].users.erase(j);//Remove only when note is clean from any holders } } @@ -1671,13 +1649,13 @@ void MIDIplay::markSostenutoNotes(int32_t midCh) if(m_chipChannels[c].users.empty()) continue; // Nothing to do - for(AdlChannel::users_iterator jnext = m_chipChannels[c].users.begin(); !jnext.is_end();) + for(ChipChannel::users_iterator jnext = m_chipChannels[c].users.begin(); !jnext.is_end();) { - AdlChannel::users_iterator j = jnext; - AdlChannel::LocationData &jd = j->value; + ChipChannel::users_iterator j = jnext; + ChipChannel::LocationData &jd = j->value; ++jnext; - if((jd.loc.MidCh == midCh) && (jd.sustained == AdlChannel::LocationData::Sustain_None)) - jd.sustained |= AdlChannel::LocationData::Sustain_Sostenuto; + if((jd.loc.MidCh == midCh) && (jd.sustained == ChipChannel::LocationData::Sustain_None)) + jd.sustained |= ChipChannel::LocationData::Sustain_Sostenuto; } } } @@ -1816,7 +1794,7 @@ void MIDIplay::updateArpeggio(double) // amount = amount of time passed if(n_users > 1) { - AdlChannel::users_iterator i = m_chipChannels[c].users.begin(); + ChipChannel::users_iterator i = m_chipChannels[c].users.begin(); size_t rate_reduction = 3; if(n_users >= 3) @@ -1829,8 +1807,8 @@ void MIDIplay::updateArpeggio(double) // amount = amount of time passed n = 0; n < count; ++n) ++i; - AdlChannel::LocationData &d = i->value; - if(d.sustained == AdlChannel::LocationData::Sustain_None) + ChipChannel::LocationData &d = i->value; + if(d.sustained == ChipChannel::LocationData::Sustain_None) { if(d.kon_time_until_neglible_us <= 0) { @@ -1896,13 +1874,13 @@ void MIDIplay::describeChannels(char *str, char *attr, size_t size) uint32_t index = 0; while(index < numChannels && index < size - 1) { - const AdlChannel &adlChannel = m_chipChannels[index]; + const ChipChannel &adlChannel = m_chipChannels[index]; - AdlChannel::const_users_iterator loc = adlChannel.users.begin(); - AdlChannel::const_users_iterator locnext(loc); + ChipChannel::const_users_iterator loc = adlChannel.users.begin(); + ChipChannel::const_users_iterator locnext(loc); if(!loc.is_end()) ++locnext; - if(loc.is_end()) // off + if(loc.is_end()) // off { str[index] = '-'; } @@ -1938,233 +1916,3 @@ void MIDIplay::describeChannels(char *str, char *attr, size_t size) str[index] = 0; attr[index] = 0; } - -#ifndef ADLMIDI_DISABLE_CPP_EXTRAS - -struct AdlInstrumentTester::Impl -{ - uint32_t cur_gm; - uint32_t ins_idx; - std::vector adl_ins_list; - Synth *opl; - MIDIplay *play; -}; - -ADLMIDI_EXPORT AdlInstrumentTester::AdlInstrumentTester(ADL_MIDIPlayer *device) - : P(new Impl) -{ -#ifndef DISABLE_EMBEDDED_BANKS - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - P->cur_gm = 0; - P->ins_idx = 0; - P->play = play; - P->opl = play ? play->m_synth.get() : NULL; -#else - ADL_UNUSED(device); -#endif -} - -ADLMIDI_EXPORT AdlInstrumentTester::~AdlInstrumentTester() -{ - delete P; -} - -ADLMIDI_EXPORT void AdlInstrumentTester::FindAdlList() -{ -#ifndef DISABLE_EMBEDDED_BANKS - const unsigned NumBanks = (unsigned)adl_getBanksCount(); - std::set adl_ins_set; - for(unsigned bankno = 0; bankno < NumBanks; ++bankno) - adl_ins_set.insert(banks[bankno][P->cur_gm]); - P->adl_ins_list.assign(adl_ins_set.begin(), adl_ins_set.end()); - P->ins_idx = 0; - NextAdl(0); - P->opl->silenceAll(); -#endif -} - - - -ADLMIDI_EXPORT void AdlInstrumentTester::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127 -{ -#ifndef DISABLE_EMBEDDED_BANKS - Synth *opl = P->opl; - if(opl->m_volumeScale == Synth::VOLUME_NATIVE) - opl->touchNote(c, static_cast(volume * 127 / (127 * 127 * 127) / 2)); - else - { - // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A) - opl->touchNote(c, static_cast(volume > 8725 ? static_cast(std::log((double)volume) * 11.541561 + (0.5 - 104.22845)) : 0)); - // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A) - //Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0); - } -#else - ADL_UNUSED(c); - ADL_UNUSED(volume); -#endif -} - -ADLMIDI_EXPORT void AdlInstrumentTester::DoNote(int note) -{ -#ifndef DISABLE_EMBEDDED_BANKS - MIDIplay *play = P->play; - Synth *opl = P->opl; - if(P->adl_ins_list.empty()) FindAdlList(); - const unsigned meta = P->adl_ins_list[P->ins_idx]; - const adlinsdata2 ains = adlinsdata2::from_adldata(::adlins[meta]); - - int tone = (P->cur_gm & 128) ? (P->cur_gm & 127) : (note + 50); - if(ains.tone) - { - /*if(ains.tone < 20) - tone += ains.tone; - else */ - if(ains.tone < 128) - tone = ains.tone; - else - tone -= ains.tone - 128; - } - double hertz = 172.00093 * std::exp(0.057762265 * (tone + 0.0)); - int32_t adlchannel[2] = { 0, 3 }; - if((ains.flags & (adlinsdata::Flag_Pseudo4op|adlinsdata::Flag_Real4op)) == 0) - { - adlchannel[1] = -1; - adlchannel[0] = 6; // single-op - if(play->hooks.onDebugMessage) - { - play->hooks.onDebugMessage(play->hooks.onDebugMessage_userData, - "noteon at %d for %g Hz\n", adlchannel[0], hertz); - } - } - else - { - if(play->hooks.onDebugMessage) - { - play->hooks.onDebugMessage(play->hooks.onDebugMessage_userData, - "noteon at %d and %d for %g Hz\n", adlchannel[0], adlchannel[1], hertz); - } - } - - opl->noteOff(0); - opl->noteOff(3); - opl->noteOff(6); - for(unsigned c = 0; c < 2; ++c) - { - if(adlchannel[c] < 0) continue; - opl->setPatch(static_cast(adlchannel[c]), ains.adl[c]); - opl->touchNote(static_cast(adlchannel[c]), 63); - opl->setPan(static_cast(adlchannel[c]), 0x30); - opl->noteOn(static_cast(adlchannel[c]), static_cast(adlchannel[1]), hertz); - } -#else - ADL_UNUSED(note); -#endif -} - -ADLMIDI_EXPORT void AdlInstrumentTester::NextGM(int offset) -{ -#ifndef DISABLE_EMBEDDED_BANKS - P->cur_gm = (P->cur_gm + 256 + (uint32_t)offset) & 0xFF; - FindAdlList(); -#else - ADL_UNUSED(offset); -#endif -} - -ADLMIDI_EXPORT void AdlInstrumentTester::NextAdl(int offset) -{ -#ifndef DISABLE_EMBEDDED_BANKS - //Synth *opl = P->opl; - if(P->adl_ins_list.empty()) FindAdlList(); - const unsigned NumBanks = (unsigned)adl_getBanksCount(); - P->ins_idx = (uint32_t)((int32_t)P->ins_idx + (int32_t)P->adl_ins_list.size() + offset) % (int32_t)P->adl_ins_list.size(); - -#if 0 - UI.Color(15); - std::fflush(stderr); - std::printf("SELECTED G%c%d\t%s\n", - cur_gm < 128 ? 'M' : 'P', cur_gm < 128 ? cur_gm + 1 : cur_gm - 128, - "<-> select GM, ^v select ins, qwe play note"); - std::fflush(stdout); - UI.Color(7); - std::fflush(stderr); -#endif - - for(size_t a = 0, n = P->adl_ins_list.size(); a < n; ++a) - { - const unsigned i = P->adl_ins_list[a]; - const adlinsdata2 ains = adlinsdata2::from_adldata(::adlins[i]); - - char ToneIndication[8] = " "; - if(ains.tone) - { - /*if(ains.tone < 20) - snprintf(ToneIndication, 8, "+%-2d", ains.tone); - else*/ - if(ains.tone < 128) - snprintf(ToneIndication, 8, "=%-2d", ains.tone); - else - snprintf(ToneIndication, 8, "-%-2d", ains.tone - 128); - } - std::printf("%s%s%s%u\t", - ToneIndication, - (ains.flags & (adlinsdata::Flag_Pseudo4op|adlinsdata::Flag_Real4op)) ? "[2]" : " ", - (P->ins_idx == a) ? "->" : "\t", - i - ); - - for(unsigned bankno = 0; bankno < NumBanks; ++bankno) - if(banks[bankno][P->cur_gm] == i) - std::printf(" %u", bankno); - - std::printf("\n"); - } -#else - ADL_UNUSED(offset); -#endif -} - -ADLMIDI_EXPORT bool AdlInstrumentTester::HandleInputChar(char ch) -{ -#ifndef DISABLE_EMBEDDED_BANKS - static const char notes[] = "zsxdcvgbhnjmq2w3er5t6y7ui9o0p"; - // c'd'ef'g'a'bC'D'EF'G'A'Bc'd'e - switch(ch) - { - case '/': - case 'H': - case 'A': - NextAdl(-1); - break; - case '*': - case 'P': - case 'B': - NextAdl(+1); - break; - case '-': - case 'K': - case 'D': - NextGM(-1); - break; - case '+': - case 'M': - case 'C': - NextGM(+1); - break; - case 3: -#if !((!defined(__WIN32__) || defined(__CYGWIN__)) && !defined(__DJGPP__)) - case 27: -#endif - return false; - default: - const char *p = std::strchr(notes, ch); - if(p && *p) - DoNote((int)(p - notes) - 12); - } -#else - ADL_UNUSED(ch); -#endif - return true; -} - -#endif /* ADLMIDI_DISABLE_CPP_EXTRAS */ diff --git a/src/adlmidi_midiplay.hpp b/src/adlmidi_midiplay.hpp index 1067252f..9bd5489f 100644 --- a/src/adlmidi_midiplay.hpp +++ b/src/adlmidi_midiplay.hpp @@ -27,30 +27,11 @@ #include "adldata.hh" #include "adlmidi_private.hpp" #include "adlmidi_ptr.hpp" -#include "structures/pl_list.hpp" - -/** - * @brief Hooks of the internal events - */ -struct MIDIEventHooks -{ - MIDIEventHooks() : - onNote(NULL), - onNote_userData(NULL), - onDebugMessage(NULL), - onDebugMessage_userData(NULL) - {} - - //! Note on/off hooks - typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); - NoteHook onNote; - void *onNote_userData; - - //! Library internal debug messages - typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); - DebugMessageHook onDebugMessage; - void *onDebugMessage_userData; -}; +#include "midiplay/event_hooks.hpp" +#include "midiplay/midi_channel.hpp" +#include "midiplay/chip_voice.hpp" +#include "midiplay/setup.hpp" +#include "midiplay/player.hpp" class MIDIplay { @@ -64,413 +45,6 @@ class MIDIplay void partialReset(); void resetMIDI(); - /**********************Internal structures and classes**********************/ - - /** - * @brief Persistent settings for each MIDI channel - */ - struct MIDIchannel - { - //! LSB Bank number - uint8_t bank_lsb, - //! MSB Bank number - bank_msb; - //! Current patch number - uint8_t patch; - //! Volume level - uint8_t volume, - //! Expression level - expression; - //! Panning level - uint8_t panning, - //! Vibrato level - vibrato, - //! Channel aftertouch level - aftertouch; - //! Portamento time - uint16_t portamento; - //! Is Pedal sustain active - bool sustain; - //! Is Soft pedal active - bool softPedal; - //! Is portamento enabled - bool portamentoEnable; - //! Source note number used by portamento - int8_t portamentoSource; // note number or -1 - //! Portamento rate - double portamentoRate; - //! Per note Aftertouch values - uint8_t noteAftertouch[128]; - //! Is note aftertouch has any non-zero value - bool noteAfterTouchInUse; - //! Reserved - char _padding[6]; - //! Pitch bend value - int bend; - //! Pitch bend sensitivity - double bendsense; - //! Pitch bend sensitivity LSB value - int bendsense_lsb, - //! Pitch bend sensitivity MSB value - bendsense_msb; - //! Vibrato position value - double vibpos, - //! Vibrato speed value - vibspeed, - //! Vibrato depth value - vibdepth; - //! Vibrato delay time - int64_t vibdelay_us; - //! Last LSB part of RPN value received - uint8_t lastlrpn, - //! Last MSB poart of RPN value received - lastmrpn; - //! Interpret RPN value as NRPN - bool nrpn; - //! Brightness level - uint8_t brightness; - - //! Is melodic channel turned into percussion - bool is_xg_percussion; - - /** - * @brief Per-Note information - */ - struct NoteInfo - { - //! Note number - uint8_t note; - //! Current pressure - uint8_t vol; - //! Note vibrato (a part of Note Aftertouch feature) - uint8_t vibrato; - //! Tone selected on noteon: - int16_t noteTone; - //! Current tone (!= noteTone if gliding note) - double currentTone; - //! Gliding rate - double glideRate; - //! Patch selected on noteon; index to bank.ins[] - size_t midiins; - //! Is note the percussion instrument - bool isPercussion; - //! Note that plays missing instrument. Doesn't using any chip channels - bool isBlank; - //! Whether releasing and on extended life time defined by TTL - bool isOnExtendedLifeTime; - //! Time-to-live until release (short percussion note fix) - double ttl; - //! Patch selected - const adlinsdata2 *ains; - enum - { - MaxNumPhysChans = 2, - MaxNumPhysItemCount = MaxNumPhysChans - }; - - struct FindPredicate - { - explicit FindPredicate(unsigned note) - : note(note) {} - bool operator()(const NoteInfo &ni) const - { return ni.note == note; } - unsigned note; - }; - - /** - * @brief Reference to currently using chip channel - */ - struct Phys - { - //! Destination chip channel - uint16_t chip_chan; - //! ins, inde to adl[] - adldata ains; - //! Is this voice must be detunable? - bool pseudo4op; - - void assign(const Phys &oth) - { - ains = oth.ains; - pseudo4op = oth.pseudo4op; - } - bool operator==(const Phys &oth) const - { - return (ains == oth.ains) && (pseudo4op == oth.pseudo4op); - } - bool operator!=(const Phys &oth) const - { - return !operator==(oth); - } - }; - - //! 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(uint16_t 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(uint16_t chip_chan) - { - Phys *ph = phys_find_or_create(chip_chan); - assert(ph); - return ph; - } - void phys_erase_at(const Phys *ph) - { - intptr_t pos = ph - chip_channels; - assert(pos < static_cast(chip_channels_count)); - for(intptr_t i = pos + 1; i < static_cast(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); - } - }; - - //! Reserved - char _padding2[5]; - //! Count of gliding notes in this channel - unsigned gliding_note_count; - //! Count of notes having a TTL countdown in this channel - unsigned extended_note_count; - - //! Active notes in the channel - pl_list activenotes; - typedef pl_list::iterator notes_iterator; - typedef pl_list::const_iterator const_notes_iterator; - - notes_iterator find_activenote(unsigned note) - { - return activenotes.find_if(NoteInfo::FindPredicate(note)); - } - - notes_iterator ensure_find_activenote(unsigned note) - { - notes_iterator it = find_activenote(note); - assert(!it.is_end()); - return it; - } - - notes_iterator find_or_create_activenote(unsigned note) - { - notes_iterator it = find_activenote(note); - if(!it.is_end()) - cleanupNote(it); - else - { - NoteInfo ni; - ni.note = note; - it = activenotes.insert(activenotes.end(), ni); - } - return it; - } - - notes_iterator ensure_find_or_create_activenote(unsigned note) - { - notes_iterator it = find_or_create_activenote(note); - assert(!it.is_end()); - return it; - } - - /** - * @brief Reset channel into initial state - */ - void reset() - { - resetAllControllers(); - patch = 0; - vibpos = 0; - bank_lsb = 0; - bank_msb = 0; - lastlrpn = 0; - lastmrpn = 0; - nrpn = false; - is_xg_percussion = false; - } - - /** - * @brief Reset all MIDI controllers into initial state - */ - void resetAllControllers() - { - bend = 0; - bendsense_msb = 2; - bendsense_lsb = 0; - updateBendSensitivity(); - volume = 100; - expression = 127; - sustain = false; - softPedal = false; - vibrato = 0; - aftertouch = 0; - std::memset(noteAftertouch, 0, 128); - noteAfterTouchInUse = false; - vibspeed = 2 * 3.141592653 * 5.0; - vibdepth = 0.5 / 127; - vibdelay_us = 0; - panning = 64; - portamento = 0; - portamentoEnable = false; - portamentoSource = -1; - portamentoRate = HUGE_VAL; - brightness = 127; - } - - /** - * @brief Has channel vibrato to process - * @return - */ - bool hasVibrato() - { - return (vibrato > 0) || (aftertouch > 0) || noteAfterTouchInUse; - } - - /** - * @brief Commit pitch bend sensitivity value from MSB and LSB - */ - void updateBendSensitivity() - { - int cent = bendsense_msb * 128 + bendsense_lsb; - bendsense = cent * (1.0 / (128 * 8192)); - } - - /** - * @brief Clean up the state of the active note before removal - */ - void cleanupNote(notes_iterator i) - { - NoteInfo &info = i->value; - if(info.glideRate != HUGE_VAL) - --gliding_note_count; - if(info.ttl > 0) - --extended_note_count; - } - - MIDIchannel() - : activenotes(128) - { - gliding_note_count = 0; - extended_note_count = 0; - reset(); - } - }; - - /** - * @brief Additional information about OPL3 channels - */ - struct AdlChannel - { - struct Location - { - uint16_t MidCh; - uint8_t note; - bool operator==(const Location &l) const - { return MidCh == l.MidCh && note == l.note; } - bool operator!=(const Location &l) const - { return !operator==(l); } - }; - struct LocationData - { - Location loc; - enum { - Sustain_None = 0x00, - Sustain_Pedal = 0x01, - Sustain_Sostenuto = 0x02, - Sustain_ANY = Sustain_Pedal | Sustain_Sostenuto - }; - uint32_t sustained; - char _padding[6]; - MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[] - //! Has fixed sustain, don't iterate "on" timeout - bool fixed_sustain; - //! Timeout until note will be allowed to be killed by channel manager while it is on - int64_t kon_time_until_neglible_us; - int64_t vibdelay_us; - - struct FindPredicate - { - explicit FindPredicate(Location loc) - : loc(loc) {} - bool operator()(const LocationData &ld) const - { return ld.loc == loc; } - Location loc; - }; - }; - - //! Time left until sounding will be muted after key off - int64_t koff_time_until_neglible_us; - - //! Recently passed instrument, improves a goodness of released but busy channel when matching - MIDIchannel::NoteInfo::Phys recent_ins; - - pl_list users; - typedef pl_list::iterator users_iterator; - typedef pl_list::const_iterator const_users_iterator; - - users_iterator find_user(const Location &loc) - { - return users.find_if(LocationData::FindPredicate(loc)); - } - - users_iterator find_or_create_user(const Location &loc) - { - users_iterator it = find_user(loc); - if(it.is_end() && users.size() != users.capacity()) - { - LocationData ld; - ld.loc = loc; - it = users.insert(users.end(), ld); - } - return it; - } - - // For channel allocation: - AdlChannel(): koff_time_until_neglible_us(0), users(128) - { - std::memset(&recent_ins, 0, sizeof(MIDIchannel::NoteInfo::Phys)); - } - - AdlChannel(const AdlChannel &oth): koff_time_until_neglible_us(oth.koff_time_until_neglible_us), users(oth.users) - { - } - - AdlChannel &operator=(const AdlChannel &oth) - { - koff_time_until_neglible_us = oth.koff_time_until_neglible_us; - users = oth.users; - return *this; - } - - /** - * @brief Increases age of active note in microseconds time - * @param us Amount time in microseconds - */ - void addAge(int64_t us); - }; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** * @brief MIDI files player sequencer @@ -488,37 +62,6 @@ class MIDIplay void initSequencerInterface(); #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - struct Setup - { - int emulator; - bool runAtPcmRate; - unsigned int bankId; - int numFourOps; - unsigned int numChips; - int deepTremoloMode; - int deepVibratoMode; - int rhythmMode; - bool logarithmicVolumes; - int volumeScaleModel; - //unsigned int SkipForward; - int scaleModulators; - bool fullRangeBrightnessCC74; - - double delay; - double carry; - - /* The lag between visual content and audio content equals */ - /* the sum of these two buffers. */ - double mindelay; - double maxdelay; - - /* For internal usage */ - ssize_t tick_skip_samples_delay; /* Skip tick processing after samples count. */ - /* For internal usage */ - - unsigned long PCM_RATE; - }; - /** * @brief MIDI Marker entry */ @@ -570,7 +113,7 @@ class MIDIplay char _padding[7]; //! Chip channels map - std::vector m_chipChannels; + std::vector m_chipChannels; //! Counter of arpeggio processing size_t m_arpeggioCounter; @@ -601,7 +144,7 @@ class MIDIplay int32_t m_outBuf[1024]; //! Synthesizer setup - Setup m_setup; + MIDIsetup m_setup; /** * @brief Load custom bank from file @@ -922,7 +465,7 @@ class MIDIplay */ void killOrEvacuate( size_t from_channel, - AdlChannel::users_iterator j, + ChipChannel::users_iterator j, MIDIchannel::notes_iterator i); /** @@ -938,7 +481,7 @@ class MIDIplay */ void killSustainingNotes(int32_t midCh = -1, int32_t this_adlchn = -1, - uint32_t sustain_type = AdlChannel::LocationData::Sustain_ANY); + uint32_t sustain_type = ChipChannel::LocationData::Sustain_ANY); /** * @brief Find active notes and mark them as sostenuto-sustained * @param MidCh MIDI channel, -1 - all MIDI channels diff --git a/src/midiplay/chip_voice.cpp b/src/midiplay/chip_voice.cpp new file mode 100644 index 00000000..f835c508 --- /dev/null +++ b/src/midiplay/chip_voice.cpp @@ -0,0 +1,43 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "chip_voice.hpp" +#include + +void ChipChannel::addAge(int64_t us) +{ + const int64_t neg = 1000 * static_cast(-0x1FFFFFFFl); + if(users.empty()) + { + koff_time_until_neglible_us = std::max(koff_time_until_neglible_us - us, neg); + if(koff_time_until_neglible_us < 0) + koff_time_until_neglible_us = 0; + } + else + { + koff_time_until_neglible_us = 0; + for(users_iterator i = users.begin(); !i.is_end(); ++i) + { + LocationData &d = i->value; + if(!d.fixed_sustain) + d.kon_time_until_neglible_us = std::max(d.kon_time_until_neglible_us - us, neg); + d.vibdelay_us += us; + } + } +} diff --git a/src/midiplay/chip_voice.hpp b/src/midiplay/chip_voice.hpp new file mode 100644 index 00000000..1cd6987b --- /dev/null +++ b/src/midiplay/chip_voice.hpp @@ -0,0 +1,120 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPLMIDI_MIDIPLAY_PLAYER_HPP +#define OPLMIDI_MIDIPLAY_PLAYER_HPP + +#pragma message("TODO: get rid of include") +#include "midi_channel.hpp" +#include "structures/pl_list.hpp" + +/** + * @brief Additional information about OPL3 channels + */ +struct ChipChannel +{ + struct Location + { + uint16_t MidCh; + uint8_t note; + bool operator==(const Location &l) const + { return MidCh == l.MidCh && note == l.note; } + bool operator!=(const Location &l) const + { return !operator==(l); } + }; + struct LocationData + { + Location loc; + enum { + Sustain_None = 0x00, + Sustain_Pedal = 0x01, + Sustain_Sostenuto = 0x02, + Sustain_ANY = Sustain_Pedal | Sustain_Sostenuto + }; + uint32_t sustained; + char _padding[6]; + MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[] + //! Has fixed sustain, don't iterate "on" timeout + bool fixed_sustain; + //! Timeout until note will be allowed to be killed by channel manager while it is on + int64_t kon_time_until_neglible_us; + int64_t vibdelay_us; + + struct FindPredicate + { + explicit FindPredicate(Location loc) + : loc(loc) {} + bool operator()(const LocationData &ld) const + { return ld.loc == loc; } + Location loc; + }; + }; + + //! Time left until sounding will be muted after key off + int64_t koff_time_until_neglible_us; + + //! Recently passed instrument, improves a goodness of released but busy channel when matching + MIDIchannel::NoteInfo::Phys recent_ins; + + pl_list users; + typedef pl_list::iterator users_iterator; + typedef pl_list::const_iterator const_users_iterator; + + users_iterator find_user(const Location &loc) + { + return users.find_if(LocationData::FindPredicate(loc)); + } + + users_iterator find_or_create_user(const Location &loc) + { + users_iterator it = find_user(loc); + if(it.is_end() && users.size() != users.capacity()) + { + LocationData ld; + ld.loc = loc; + it = users.insert(users.end(), ld); + } + return it; + } + + // For channel allocation: + ChipChannel(): koff_time_until_neglible_us(0), users(128) + { + std::memset(&recent_ins, 0, sizeof(MIDIchannel::NoteInfo::Phys)); + } + + ChipChannel(const ChipChannel &oth): koff_time_until_neglible_us(oth.koff_time_until_neglible_us), users(oth.users) + { + } + + ChipChannel &operator=(const ChipChannel &oth) + { + koff_time_until_neglible_us = oth.koff_time_until_neglible_us; + users = oth.users; + return *this; + } + + /** + * @brief Increases age of active note in microseconds time + * @param us Amount time in microseconds + */ + void addAge(int64_t us); +}; + +#endif // OPLMIDI_MIDIPLAY_PLAYER_HPP diff --git a/src/midiplay/cpp_extras.cpp b/src/midiplay/cpp_extras.cpp new file mode 100644 index 00000000..038cd49b --- /dev/null +++ b/src/midiplay/cpp_extras.cpp @@ -0,0 +1,252 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "adlmidi_private.hpp" +#include "adlmidi_midiplay.hpp" +#include "adlmidi_opl3.hpp" + +#ifndef ADLMIDI_DISABLE_CPP_EXTRAS + +struct AdlInstrumentTester::Impl +{ + uint32_t cur_gm; + uint32_t ins_idx; + std::vector adl_ins_list; + Synth *opl; + MIDIplay *play; +}; + +ADLMIDI_EXPORT AdlInstrumentTester::AdlInstrumentTester(ADL_MIDIPlayer *device) + : P(new Impl) +{ +#ifndef DISABLE_EMBEDDED_BANKS + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + P->cur_gm = 0; + P->ins_idx = 0; + P->play = play; + P->opl = play ? play->m_synth.get() : NULL; +#else + ADL_UNUSED(device); +#endif +} + +ADLMIDI_EXPORT AdlInstrumentTester::~AdlInstrumentTester() +{ + delete P; +} + +ADLMIDI_EXPORT void AdlInstrumentTester::FindAdlList() +{ +#ifndef DISABLE_EMBEDDED_BANKS + const unsigned NumBanks = (unsigned)adl_getBanksCount(); + std::set adl_ins_set; + for(unsigned bankno = 0; bankno < NumBanks; ++bankno) + adl_ins_set.insert(banks[bankno][P->cur_gm]); + P->adl_ins_list.assign(adl_ins_set.begin(), adl_ins_set.end()); + P->ins_idx = 0; + NextAdl(0); + P->opl->silenceAll(); +#endif +} + + + +ADLMIDI_EXPORT void AdlInstrumentTester::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127 +{ +#ifndef DISABLE_EMBEDDED_BANKS + Synth *opl = P->opl; + if(opl->m_volumeScale == Synth::VOLUME_NATIVE) + opl->touchNote(c, static_cast(volume * 127 / (127 * 127 * 127) / 2)); + else + { + // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A) + opl->touchNote(c, static_cast(volume > 8725 ? static_cast(std::log((double)volume) * 11.541561 + (0.5 - 104.22845)) : 0)); + // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A) + //Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0); + } +#else + ADL_UNUSED(c); + ADL_UNUSED(volume); +#endif +} + +ADLMIDI_EXPORT void AdlInstrumentTester::DoNote(int note) +{ +#ifndef DISABLE_EMBEDDED_BANKS + MIDIplay *play = P->play; + Synth *opl = P->opl; + if(P->adl_ins_list.empty()) FindAdlList(); + const unsigned meta = P->adl_ins_list[P->ins_idx]; + const adlinsdata2 ains = adlinsdata2::from_adldata(::adlins[meta]); + + int tone = (P->cur_gm & 128) ? (P->cur_gm & 127) : (note + 50); + if(ains.tone) + { + /*if(ains.tone < 20) + tone += ains.tone; + else */ + if(ains.tone < 128) + tone = ains.tone; + else + tone -= ains.tone - 128; + } + double hertz = 172.00093 * std::exp(0.057762265 * (tone + 0.0)); + int32_t adlchannel[2] = { 0, 3 }; + if((ains.flags & (adlinsdata::Flag_Pseudo4op|adlinsdata::Flag_Real4op)) == 0) + { + adlchannel[1] = -1; + adlchannel[0] = 6; // single-op + if(play->hooks.onDebugMessage) + { + play->hooks.onDebugMessage(play->hooks.onDebugMessage_userData, + "noteon at %d for %g Hz\n", adlchannel[0], hertz); + } + } + else + { + if(play->hooks.onDebugMessage) + { + play->hooks.onDebugMessage(play->hooks.onDebugMessage_userData, + "noteon at %d and %d for %g Hz\n", adlchannel[0], adlchannel[1], hertz); + } + } + + opl->noteOff(0); + opl->noteOff(3); + opl->noteOff(6); + for(unsigned c = 0; c < 2; ++c) + { + if(adlchannel[c] < 0) continue; + opl->setPatch(static_cast(adlchannel[c]), ains.adl[c]); + opl->touchNote(static_cast(adlchannel[c]), 63); + opl->setPan(static_cast(adlchannel[c]), 0x30); + opl->noteOn(static_cast(adlchannel[c]), static_cast(adlchannel[1]), hertz); + } +#else + ADL_UNUSED(note); +#endif +} + +ADLMIDI_EXPORT void AdlInstrumentTester::NextGM(int offset) +{ +#ifndef DISABLE_EMBEDDED_BANKS + P->cur_gm = (P->cur_gm + 256 + (uint32_t)offset) & 0xFF; + FindAdlList(); +#else + ADL_UNUSED(offset); +#endif +} + +ADLMIDI_EXPORT void AdlInstrumentTester::NextAdl(int offset) +{ +#ifndef DISABLE_EMBEDDED_BANKS + //Synth *opl = P->opl; + if(P->adl_ins_list.empty()) FindAdlList(); + const unsigned NumBanks = (unsigned)adl_getBanksCount(); + P->ins_idx = (uint32_t)((int32_t)P->ins_idx + (int32_t)P->adl_ins_list.size() + offset) % (int32_t)P->adl_ins_list.size(); + +#if 0 + UI.Color(15); + std::fflush(stderr); + std::printf("SELECTED G%c%d\t%s\n", + cur_gm < 128 ? 'M' : 'P', cur_gm < 128 ? cur_gm + 1 : cur_gm - 128, + "<-> select GM, ^v select ins, qwe play note"); + std::fflush(stdout); + UI.Color(7); + std::fflush(stderr); +#endif + + for(size_t a = 0, n = P->adl_ins_list.size(); a < n; ++a) + { + const unsigned i = P->adl_ins_list[a]; + const adlinsdata2 ains = adlinsdata2::from_adldata(::adlins[i]); + + char ToneIndication[8] = " "; + if(ains.tone) + { + /*if(ains.tone < 20) + snprintf(ToneIndication, 8, "+%-2d", ains.tone); + else*/ + if(ains.tone < 128) + snprintf(ToneIndication, 8, "=%-2d", ains.tone); + else + snprintf(ToneIndication, 8, "-%-2d", ains.tone - 128); + } + std::printf("%s%s%s%u\t", + ToneIndication, + (ains.flags & (adlinsdata::Flag_Pseudo4op|adlinsdata::Flag_Real4op)) ? "[2]" : " ", + (P->ins_idx == a) ? "->" : "\t", + i + ); + + for(unsigned bankno = 0; bankno < NumBanks; ++bankno) + if(banks[bankno][P->cur_gm] == i) + std::printf(" %u", bankno); + + std::printf("\n"); + } +#else + ADL_UNUSED(offset); +#endif +} + +ADLMIDI_EXPORT bool AdlInstrumentTester::HandleInputChar(char ch) +{ +#ifndef DISABLE_EMBEDDED_BANKS + static const char notes[] = "zsxdcvgbhnjmq2w3er5t6y7ui9o0p"; + // c'd'ef'g'a'bC'D'EF'G'A'Bc'd'e + switch(ch) + { + case '/': + case 'H': + case 'A': + NextAdl(-1); + break; + case '*': + case 'P': + case 'B': + NextAdl(+1); + break; + case '-': + case 'K': + case 'D': + NextGM(-1); + break; + case '+': + case 'M': + case 'C': + NextGM(+1); + break; + case 3: +#if !((!defined(__WIN32__) || defined(__CYGWIN__)) && !defined(__DJGPP__)) + case 27: +#endif + return false; + default: + const char *p = std::strchr(notes, ch); + if(p && *p) + DoNote((int)(p - notes) - 12); + } +#else + ADL_UNUSED(ch); +#endif + return true; +} + +#endif /* ADLMIDI_DISABLE_CPP_EXTRAS */ diff --git a/src/midiplay/event_hooks.hpp b/src/midiplay/event_hooks.hpp new file mode 100644 index 00000000..9083e8a0 --- /dev/null +++ b/src/midiplay/event_hooks.hpp @@ -0,0 +1,48 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPLMIDI_MIDIPLAY_EVENT_HOOKS_HPP +#define OPLMIDI_MIDIPLAY_EVENT_HOOKS_HPP + +#include + +/** + * @brief Hooks of the internal events + */ +struct MIDIEventHooks +{ + MIDIEventHooks() : + onNote(NULL), + onNote_userData(NULL), + onDebugMessage(NULL), + onDebugMessage_userData(NULL) + {} + + //! Note on/off hooks + typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); + NoteHook onNote; + void *onNote_userData; + + //! Library internal debug messages + typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); + DebugMessageHook onDebugMessage; + void *onDebugMessage_userData; +}; + +#endif // OPLMIDI_MIDIPLAY_EVENT_HOOKS_HPP diff --git a/src/midiplay/midi_channel.hpp b/src/midiplay/midi_channel.hpp new file mode 100644 index 00000000..50c5dd67 --- /dev/null +++ b/src/midiplay/midi_channel.hpp @@ -0,0 +1,342 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPLMIDI_MIDIPLAY_MIDI_CHANNEL_HPP +#define OPLMIDI_MIDIPLAY_MIDI_CHANNEL_HPP + +#include "adldata.hh" +#include "structures/pl_list.hpp" +#include +#include +#include +#include + +/** + * @brief Persistent settings for each MIDI channel + */ +struct MIDIchannel +{ + //! LSB Bank number + uint8_t bank_lsb, + //! MSB Bank number + bank_msb; + //! Current patch number + uint8_t patch; + //! Volume level + uint8_t volume, + //! Expression level + expression; + //! Panning level + uint8_t panning, + //! Vibrato level + vibrato, + //! Channel aftertouch level + aftertouch; + //! Portamento time + uint16_t portamento; + //! Is Pedal sustain active + bool sustain; + //! Is Soft pedal active + bool softPedal; + //! Is portamento enabled + bool portamentoEnable; + //! Source note number used by portamento + int8_t portamentoSource; // note number or -1 + //! Portamento rate + double portamentoRate; + //! Per note Aftertouch values + uint8_t noteAftertouch[128]; + //! Is note aftertouch has any non-zero value + bool noteAfterTouchInUse; + //! Reserved + char _padding[6]; + //! Pitch bend value + int bend; + //! Pitch bend sensitivity + double bendsense; + //! Pitch bend sensitivity LSB value + int bendsense_lsb, + //! Pitch bend sensitivity MSB value + bendsense_msb; + //! Vibrato position value + double vibpos, + //! Vibrato speed value + vibspeed, + //! Vibrato depth value + vibdepth; + //! Vibrato delay time + int64_t vibdelay_us; + //! Last LSB part of RPN value received + uint8_t lastlrpn, + //! Last MSB poart of RPN value received + lastmrpn; + //! Interpret RPN value as NRPN + bool nrpn; + //! Brightness level + uint8_t brightness; + + //! Is melodic channel turned into percussion + bool is_xg_percussion; + + /** + * @brief Per-Note information + */ + struct NoteInfo + { + //! Note number + uint8_t note; + //! Current pressure + uint8_t vol; + //! Note vibrato (a part of Note Aftertouch feature) + uint8_t vibrato; + //! Tone selected on noteon: + int16_t noteTone; + //! Current tone (!= noteTone if gliding note) + double currentTone; + //! Gliding rate + double glideRate; + //! Patch selected on noteon; index to bank.ins[] + size_t midiins; + //! Is note the percussion instrument + bool isPercussion; + //! Note that plays missing instrument. Doesn't using any chip channels + bool isBlank; + //! Whether releasing and on extended life time defined by TTL + bool isOnExtendedLifeTime; + //! Time-to-live until release (short percussion note fix) + double ttl; + //! Patch selected + const adlinsdata2 *ains; + enum + { + MaxNumPhysChans = 2, + MaxNumPhysItemCount = MaxNumPhysChans + }; + + struct FindPredicate + { + explicit FindPredicate(unsigned note) + : note(note) {} + bool operator()(const NoteInfo &ni) const + { return ni.note == note; } + unsigned note; + }; + + /** + * @brief Reference to currently using chip channel + */ + struct Phys + { + //! Destination chip channel + uint16_t chip_chan; + //! ins, inde to adl[] + adldata ains; + //! Is this voice must be detunable? + bool pseudo4op; + + void assign(const Phys &oth) + { + ains = oth.ains; + pseudo4op = oth.pseudo4op; + } + bool operator==(const Phys &oth) const + { + return (ains == oth.ains) && (pseudo4op == oth.pseudo4op); + } + bool operator!=(const Phys &oth) const + { + return !operator==(oth); + } + }; + + //! 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(uint16_t 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(uint16_t chip_chan) + { + Phys *ph = phys_find_or_create(chip_chan); + assert(ph); + return ph; + } + void phys_erase_at(const Phys *ph) + { + intptr_t pos = ph - chip_channels; + assert(pos < static_cast(chip_channels_count)); + for(intptr_t i = pos + 1; i < static_cast(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); + } + }; + + //! Reserved + char _padding2[5]; + //! Count of gliding notes in this channel + unsigned gliding_note_count; + //! Count of notes having a TTL countdown in this channel + unsigned extended_note_count; + + //! Active notes in the channel + pl_list activenotes; + typedef pl_list::iterator notes_iterator; + typedef pl_list::const_iterator const_notes_iterator; + + notes_iterator find_activenote(unsigned note) + { + return activenotes.find_if(NoteInfo::FindPredicate(note)); + } + + notes_iterator ensure_find_activenote(unsigned note) + { + notes_iterator it = find_activenote(note); + assert(!it.is_end()); + return it; + } + + notes_iterator find_or_create_activenote(unsigned note) + { + notes_iterator it = find_activenote(note); + if(!it.is_end()) + cleanupNote(it); + else + { + NoteInfo ni; + ni.note = note; + it = activenotes.insert(activenotes.end(), ni); + } + return it; + } + + notes_iterator ensure_find_or_create_activenote(unsigned note) + { + notes_iterator it = find_or_create_activenote(note); + assert(!it.is_end()); + return it; + } + + /** + * @brief Reset channel into initial state + */ + void reset() + { + resetAllControllers(); + patch = 0; + vibpos = 0; + bank_lsb = 0; + bank_msb = 0; + lastlrpn = 0; + lastmrpn = 0; + nrpn = false; + is_xg_percussion = false; + } + + /** + * @brief Reset all MIDI controllers into initial state + */ + void resetAllControllers() + { + bend = 0; + bendsense_msb = 2; + bendsense_lsb = 0; + updateBendSensitivity(); + volume = 100; + expression = 127; + sustain = false; + softPedal = false; + vibrato = 0; + aftertouch = 0; + std::memset(noteAftertouch, 0, 128); + noteAfterTouchInUse = false; + vibspeed = 2 * 3.141592653 * 5.0; + vibdepth = 0.5 / 127; + vibdelay_us = 0; + panning = 64; + portamento = 0; + portamentoEnable = false; + portamentoSource = -1; + portamentoRate = HUGE_VAL; + brightness = 127; + } + + /** + * @brief Has channel vibrato to process + * @return + */ + bool hasVibrato() + { + return (vibrato > 0) || (aftertouch > 0) || noteAfterTouchInUse; + } + + /** + * @brief Commit pitch bend sensitivity value from MSB and LSB + */ + void updateBendSensitivity() + { + int cent = bendsense_msb * 128 + bendsense_lsb; + bendsense = cent * (1.0 / (128 * 8192)); + } + + /** + * @brief Clean up the state of the active note before removal + */ + void cleanupNote(notes_iterator i) + { + NoteInfo &info = i->value; + if(info.glideRate != HUGE_VAL) + --gliding_note_count; + if(info.ttl > 0) + --extended_note_count; + } + + MIDIchannel() + : activenotes(128) + { + gliding_note_count = 0; + extended_note_count = 0; + reset(); + } +}; + +#endif // OPLMIDI_MIDIPLAY_MIDI_CHANNEL_HPP diff --git a/src/midiplay/player.hpp b/src/midiplay/player.hpp new file mode 100644 index 00000000..d031250e --- /dev/null +++ b/src/midiplay/player.hpp @@ -0,0 +1,23 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPLMIDI_MIDIPLAY_PLAYER_HPP +#define OPLMIDI_MIDIPLAY_PLAYER_HPP + +#endif // OPLMIDI_MIDIPLAY_PLAYER_HPP diff --git a/src/midiplay/setup.hpp b/src/midiplay/setup.hpp new file mode 100644 index 00000000..a2026ad4 --- /dev/null +++ b/src/midiplay/setup.hpp @@ -0,0 +1,56 @@ +/* + * libADLMIDI is a free Software MIDI synthesizer library with OPL3 emulation + * + * ADLMIDI Library API: Copyright (c) 2015-2019 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef OPLMIDI_MIDIPLAY_SETUP_HPP +#define OPLMIDI_MIDIPLAY_SETUP_HPP + +#include + +struct MIDIsetup +{ + int emulator; + bool runAtPcmRate; + unsigned int bankId; + int numFourOps; + unsigned int numChips; + int deepTremoloMode; + int deepVibratoMode; + int rhythmMode; + bool logarithmicVolumes; + int volumeScaleModel; + //unsigned int SkipForward; + int scaleModulators; + bool fullRangeBrightnessCC74; + + double delay; + double carry; + + /* The lag between visual content and audio content equals */ + /* the sum of these two buffers. */ + double mindelay; + double maxdelay; + + /* For internal usage */ + ssize_t tick_skip_samples_delay; /* Skip tick processing after samples count. */ + /* For internal usage */ + + unsigned long PCM_RATE; +}; + +#endif // OPLMIDI_MIDIPLAY_SETUP_HPP