diff options
Diffstat (limited to 'src/adlmidi_midiplay.cpp')
-rw-r--r-- | src/adlmidi_midiplay.cpp | 1854 |
1 files changed, 484 insertions, 1370 deletions
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 89c1a73..190a521 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -93,6 +93,8 @@ static const uint8_t PercussionMap[256] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +enum { MasterVolumeDefault = 127 }; + inline bool isXgPercChannel(uint8_t msb, uint8_t lsb) { return (msb == 0x7E || msb == 0x7F) && (lsb == 0); @@ -115,583 +117,14 @@ void MIDIplay::AdlChannel::AddAge(int64_t ms) } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -MIDIplay::MidiEvent::MidiEvent() : - type(T_UNKNOWN), - subtype(T_UNKNOWN), - channel(0), - isValid(1), - absPosition(0) -{} - - -MIDIplay::MidiTrackRow::MidiTrackRow() : - time(0.0), - delay(0), - absPos(0), - timeDelay(0.0) -{} - -void MIDIplay::MidiTrackRow::reset() -{ - time = 0.0; - delay = 0; - absPos = 0; - timeDelay = 0.0; - events.clear(); -} - -void MIDIplay::MidiTrackRow::sortEvents(bool *noteStates) -{ - typedef std::vector<MidiEvent> EvtArr; - EvtArr metas; - EvtArr noteOffs; - EvtArr controllers; - EvtArr anyOther; - - metas.reserve(events.size()); - noteOffs.reserve(events.size()); - controllers.reserve(events.size()); - anyOther.reserve(events.size()); - - for(size_t i = 0; i < events.size(); i++) - { - if(events[i].type == MidiEvent::T_NOTEOFF) - noteOffs.push_back(events[i]); - else if((events[i].type == MidiEvent::T_CTRLCHANGE) - || (events[i].type == MidiEvent::T_PATCHCHANGE) - || (events[i].type == MidiEvent::T_WHEEL) - || (events[i].type == MidiEvent::T_CHANAFTTOUCH)) - { - controllers.push_back(events[i]); - } - else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER)) - metas.push_back(events[i]); - else - anyOther.push_back(events[i]); - } - - /* - * If Note-Off and it's Note-On is on the same row - move this damned note off down! - */ - if(noteStates) - { - std::set<size_t> markAsOn; - for(size_t i = 0; i < anyOther.size(); i++) - { - const MidiEvent e = anyOther[i]; - if(e.type == MidiEvent::T_NOTEON) - { - const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F); - //Check, was previously note is on or off - bool wasOn = noteStates[note_i]; - markAsOn.insert(note_i); - // Detect zero-length notes are following previously pressed note - int noteOffsOnSameNote = 0; - for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();) - { - //If note was off, and note-off on same row with note-on - move it down! - if( - ((*j).channel == e.channel) && - ((*j).data[0] == e.data[0]) - ) - { - //If note is already off OR more than one note-off on same row and same note - if(!wasOn || (noteOffsOnSameNote != 0)) - { - anyOther.push_back(*j); - j = noteOffs.erase(j); - markAsOn.erase(note_i); - continue; - } - else - { - //When same row has many note-offs on same row - //that means a zero-length note follows previous note - //it must be shuted down - noteOffsOnSameNote++; - } - } - j++; - } - } - } - - //Mark other notes as released - for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++) - { - size_t note_i = (j->channel * 255) + (j->data[0] & 0x7F); - noteStates[note_i] = false; - } - - for(std::set<size_t>::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) - noteStates[*j] = true; - } - /***********************************************************************************/ - - events.clear(); - events.insert(events.end(), noteOffs.begin(), noteOffs.end()); - events.insert(events.end(), metas.begin(), metas.end()); - events.insert(events.end(), controllers.begin(), controllers.end()); - events.insert(events.end(), anyOther.begin(), anyOther.end()); -} -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::buildTrackData() -{ - fullSongTimeLength = 0.0; - loopStartTime = -1.0; - loopEndTime = -1.0; - musTitle.clear(); - musCopyright.clear(); - musTrackTitles.clear(); - musMarkers.clear(); - caugh_missing_instruments.clear(); - caugh_missing_banks_melodic.clear(); - caugh_missing_banks_percussion.clear(); - trackDataNew.clear(); - const size_t trackCount = TrackData.size(); - trackDataNew.resize(trackCount, MidiTrackQueue()); - - invalidLoop = false; - bool gotLoopStart = false, gotLoopEnd = false, gotLoopEventInThisRow = false; - //! Tick position of loop start tag - uint64_t loopStartTicks = 0; - //! Tick position of loop end tag - uint64_t loopEndTicks = 0; - //! Full length of song in ticks - uint64_t ticksSongLength = 0; - //! Cache for error message strign - char error[150]; - - CurrentPositionNew.track.clear(); - CurrentPositionNew.track.resize(trackCount); - - //! Caches note on/off states. - bool noteStates[16 * 255]; - /* This is required to carefully detect zero-length notes * - * and avoid a move of "note-off" event over "note-on" while sort. * - * Otherwise, after sort those notes will play infinite sound */ - - //Tempo change events - std::vector<MidiEvent> tempos; - - /* - * TODO: Make this be safer for memory in case of broken input data - * which may cause going away of available track data (and then give a crash!) - * - * POST: Check this more carefully for possible vulnuabilities are can crash this - */ - for(size_t tk = 0; tk < trackCount; ++tk) - { - uint64_t abs_position = 0; - int status = 0; - MidiEvent event; - bool ok = false; - uint8_t *end = TrackData[tk].data() + TrackData[tk].size(); - uint8_t *trackPtr = TrackData[tk].data(); - std::memset(noteStates, 0, sizeof(noteStates)); - - //Time delay that follows the first event in the track - { - MidiTrackRow evtPos; - if(opl.m_musicMode == OPL3::MODE_RSXX) - ok = true; - else - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); - if(!ok) - { - int len = snprintf(error, 150, "buildTrackData: Can't read variable-length value at begin of track %d.\n", (int)tk); - if((len > 0) && (len < 150)) - errorString += std::string(error, (size_t)len); - return false; - } - - //HACK: Begin every track with "Reset all controllers" event to avoid controllers state break came from end of song - for(uint8_t chan = 0; chan < 16; chan++) - { - MidiEvent event; - event.type = MidiEvent::T_CTRLCHANGE; - event.channel = chan; - event.data.push_back(121); - event.data.push_back(0); - evtPos.events.push_back(event); - } - - evtPos.absPos = abs_position; - abs_position += evtPos.delay; - trackDataNew[tk].push_back(evtPos); - } - - MidiTrackRow evtPos; - do - { - event = parseEvent(&trackPtr, end, status); - if(!event.isValid) - { - int len = snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); - if((len > 0) && (len < 150)) - errorString += std::string(error, (size_t)len); - return false; - } - - evtPos.events.push_back(event); - if(event.type == MidiEvent::T_SPECIAL) - { - if(event.subtype == MidiEvent::ST_TEMPOCHANGE) - { - event.absPosition = abs_position; - tempos.push_back(event); - } - else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART)) - { - /* - * loopStart is invalid when: - * - starts together with loopEnd - * - appears more than one time in same MIDI file - */ - if(gotLoopStart || gotLoopEventInThisRow) - invalidLoop = true; - else - { - gotLoopStart = true; - loopStartTicks = abs_position; - } - //In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) - { - /* - * loopEnd is invalid when: - * - starts before loopStart - * - starts together with loopStart - * - appars more than one time in same MIDI file - */ - if(gotLoopEnd || gotLoopEventInThisRow) - invalidLoop = true; - else - { - gotLoopEnd = true; - loopEndTicks = abs_position; - } - //In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - } - - if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! - { - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); - if(!ok) - { - /* End of track has been reached! However, there is no EOT event presented */ - event.type = MidiEvent::T_SPECIAL; - event.subtype = MidiEvent::ST_ENDTRACK; - } - } - - if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK)) - { - evtPos.absPos = abs_position; - abs_position += evtPos.delay; - evtPos.sortEvents(noteStates); - trackDataNew[tk].push_back(evtPos); - evtPos.reset(); - gotLoopEventInThisRow = false; - } - } - while((trackPtr <= end) && (event.subtype != MidiEvent::ST_ENDTRACK)); - - if(ticksSongLength < abs_position) - ticksSongLength = abs_position; - //Set the chain of events begin - if(trackDataNew[tk].size() > 0) - CurrentPositionNew.track[tk].pos = trackDataNew[tk].begin(); - } - - if(gotLoopStart && !gotLoopEnd) - { - gotLoopEnd = true; - loopEndTicks = ticksSongLength; - } - - //loopStart must be located before loopEnd! - if(loopStartTicks >= loopEndTicks) - invalidLoop = true; - - /********************************************************************************/ - //Calculate time basing on collected tempo events - /********************************************************************************/ - for(size_t tk = 0; tk < trackCount; ++tk) - { - fraction<uint64_t> currentTempo = Tempo; - double time = 0.0; - uint64_t abs_position = 0; - size_t tempo_change_index = 0; - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "\n============Track %" PRIuPTR "=============\n", tk); - std::fflush(stdout); -#endif - - MidiTrackRow *posPrev = &(*(track.begin()));//First element - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { -#ifdef DEBUG_TIME_CALCULATION - bool tempoChanged = false; -#endif - MidiTrackRow &pos = *it; - if((posPrev != &pos) && //Skip first event - (!tempos.empty()) && //Only when in-track tempo events are available - (tempo_change_index < tempos.size()) - ) - { - // If tempo event is going between of current and previous event - if(tempos[tempo_change_index].absPosition <= pos.absPos) - { - //Stop points: begin point and tempo change points are before end point - std::vector<TempoChangePoint> points; - fraction<uint64_t> t; - TempoChangePoint firstPoint = {posPrev->absPos, currentTempo}; - points.push_back(firstPoint); - - //Collect tempo change points between previous and current events - do - { - TempoChangePoint tempoMarker; - MidiEvent &tempoPoint = tempos[tempo_change_index]; - tempoMarker.absPos = tempoPoint.absPosition; - tempoMarker.tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); - points.push_back(tempoMarker); - tempo_change_index++; - } - while((tempo_change_index < tempos.size()) && - (tempos[tempo_change_index].absPosition <= pos.absPos)); - - // Re-calculate time delay of previous event - time -= posPrev->timeDelay; - posPrev->timeDelay = 0.0; - - for(size_t i = 0, j = 1; j < points.size(); i++, j++) - { - /* If one or more tempo events are appears between of two events, - * calculate delays between each tempo point, begin and end */ - uint64_t midDelay = 0; - //Delay between points - midDelay = points[j].absPos - points[i].absPos; - //Time delay between points - t = midDelay * currentTempo; - posPrev->timeDelay += t.value(); - - //Apply next tempo - currentTempo = points[j].tempo; -#ifdef DEBUG_TIME_CALCULATION - tempoChanged = true; -#endif - } - //Then calculate time between last tempo change point and end point - TempoChangePoint tailTempo = points.back(); - uint64_t postDelay = pos.absPos - tailTempo.absPos; - t = postDelay * currentTempo; - posPrev->timeDelay += t.value(); - - //Store Common time delay - posPrev->time = time; - time += posPrev->timeDelay; - } - } - - fraction<uint64_t> t = pos.delay * currentTempo; - pos.timeDelay = t.value(); - pos.time = time; - time += pos.timeDelay; - - //Capture markers after time value calculation - for(size_t i = 0; i < pos.events.size(); i++) - { - MidiEvent &e = pos.events[i]; - if((e.type == MidiEvent::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) - { - MIDI_MarkerEntry marker; - marker.label = std::string((char *)e.data.data(), e.data.size()); - marker.pos_ticks = pos.absPos; - marker.pos_time = pos.time; - musMarkers.push_back(marker); - } - } - - //Capture loop points time positions - if(!invalidLoop) - { - // Set loop points times - if(loopStartTicks == pos.absPos) - loopStartTime = pos.time; - else if(loopEndTicks == pos.absPos) - loopEndTime = pos.time; - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); - std::fflush(stdout); -#endif - - abs_position += pos.delay; - posPrev = &pos; - } - - if(time > fullSongTimeLength) - fullSongTimeLength = time; - } - - fullSongTimeLength += postSongWaitDelay; - //Set begin of the music - trackBeginPositionNew = CurrentPositionNew; - //Initial loop position will begin at begin of track until passing of the loop point - LoopBeginPositionNew = CurrentPositionNew; - - /********************************************************************************/ - //Resolve "hell of all times" of too short drum notes: - //move too short percussion note-offs far far away as possible - /********************************************************************************/ -#if 1 //Use this to record WAVEs for comparison before/after implementing of this - if(opl.m_musicMode == OPL3::MODE_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF - { - //! Minimal real time in seconds -#define DRUM_NOTE_MIN_TIME 0.03 - //! Minimal ticks count -#define DRUM_NOTE_MIN_TICKS 15 - struct NoteState - { - double delay; - uint64_t delayTicks; - bool isOn; - char ___pad[7]; - } drNotes[255]; - uint16_t banks[16]; - - for(size_t tk = 0; tk < trackCount; ++tk) - { - std::memset(drNotes, 0, sizeof(drNotes)); - std::memset(banks, 0, sizeof(banks)); - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { - MidiTrackRow &pos = *it; - - for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) - { - MidiEvent *et = &pos.events[(size_t)e]; - - /* Set MSB/LSB bank */ - if(et->type == MidiEvent::T_CTRLCHANGE) - { - uint8_t ctrlno = et->data[0]; - uint8_t value = et->data[1]; - switch(ctrlno) - { - case 0: // Set bank msb (GM bank) - banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); - break; - case 32: // Set bank lsb (XG bank) - banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); - break; - } - continue; - } - - bool percussion = (et->channel == 9) || - banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) - banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) - if(!percussion) - continue; - - if(et->type == MidiEvent::T_NOTEON) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - ns.isOn = true; - ns.delay = 0.0; - ns.delayTicks = 0; - } - else if(et->type == MidiEvent::T_NOTEOFF) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - if(ns.isOn) - { - ns.isOn = false; - if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short - { - //Move it into next event position if that possible - for(MidiTrackQueue::iterator itNext = it; - itNext != track.end(); - itNext++) - { - MidiTrackRow &posN = *itNext; - if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) - { - //Put note-off into begin of next event list - posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); - //Renive this event from a current row - pos.events.erase(pos.events.begin() + (int)e); - e--; - break; - } - ns.delay += posN.timeDelay; - ns.delayTicks += posN.delay; - } - } - ns.delay = 0.0; - ns.delayTicks = 0; - } - } - } - - //Append time delays to sustaining notes - for(size_t no = 0; no < 128; no++) - { - NoteState &ns = drNotes[no]; - if(ns.isOn) - { - ns.delay += pos.timeDelay; - ns.delayTicks += pos.delay; - } - } - } - } -#undef DRUM_NOTE_MIN_TIME -#undef DRUM_NOTE_MIN_TICKS - } -#endif - - return true; -} -#endif - - MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), - m_arpeggioCounter(0), - m_audioTickCounter(0) -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - , fullSongTimeLength(0.0), - postSongWaitDelay(1.0), - loopStartTime(-1.0), - loopEndTime(-1.0), - tempoMultiplier(1.0), - atEnd(false), - loopStart(false), - loopEnd(false), - invalidLoop(false) + m_masterVolume(MasterVolumeDefault), + m_sysExDeviceId(0), + m_synthMode(Mode_XG), + m_arpeggioCounter(0) +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) + , m_audioTickCounter(0) #endif { devices.clear(); @@ -712,13 +145,15 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.LogarithmicVolumes = false; m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO; //m_setup.SkipForward = 0; - m_setup.loopingIsEnabled = false; m_setup.ScaleModulators = -1; m_setup.fullRangeBrightnessCC74 = false; m_setup.delay = 0.0; m_setup.carry = 0.0; m_setup.tick_skip_samples_delay = 0; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + initSequencerInterface(); +#endif applySetup(); ChooseDevice("none"); realTime_ResetState(); @@ -726,6 +161,8 @@ MIDIplay::MIDIplay(unsigned long sampleRate): void MIDIplay::applySetup() { + opl.m_musicMode = OPL3::MODE_MIDI; + m_setup.tick_skip_samples_delay = 0; opl.runAtPcmRate = m_setup.runAtPcmRate; @@ -745,10 +182,12 @@ void MIDIplay::applySetup() opl.ScaleModulators = m_setup.ScaleModulators < 0 ? opl.dynamic_bank_setup.scaleModulators : (m_setup.ScaleModulators != 0); + if(m_setup.LogarithmicVolumes) opl.ChangeVolumeRangesModel(ADLMIDI_VolumeModel_NativeOPL3); - opl.m_musicMode = OPL3::MODE_MIDI; - opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(m_setup.VolumeModel)); + else + opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(m_setup.VolumeModel)); + if(m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model opl.m_volumeScale = (OPL3::VolumesScale)opl.dynamic_bank_setup.volumeModel; @@ -764,191 +203,16 @@ void MIDIplay::applySetup() m_arpeggioCounter = 0; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) -{ - uint64_t result = 0; - for(;;) - { - uint8_t byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - return result; -} - -uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok) -{ - uint64_t result = 0; - ok = false; - - for(;;) - { - if(*ptr >= end) - return 2; - unsigned char byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - - ok = true; - return result; -} - -double MIDIplay::Tick(double s, double granularity) +void MIDIplay::TickIterators(double s) { - s *= tempoMultiplier; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew()) - break; - if(CurrentPositionNew.wait <= 0.0) - antiFreezeCounter--; - } - - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - for(uint16_t c = 0; c < opl.NumChannels; ++c) ch[c].AddAge(static_cast<int64_t>(s * 1000.0)); - UpdateVibrato(s); UpdateArpeggio(s); - - if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value! - return 0.0; - - return CurrentPositionNew.wait; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - -void MIDIplay::TickIteratos(double s) -{ - for(uint16_t c = 0; c < opl.NumChannels; ++c) - ch[c].AddAge(static_cast<int64_t>(s * 1000.0)); - UpdateVibrato(s); - UpdateArpeggio(s); -} - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -void MIDIplay::seek(double seconds) -{ - if(seconds < 0.0) - return;//Seeking negative position is forbidden! :-P - const double granularity = m_setup.mindelay, - granualityHalf = granularity * 0.5, - s = seconds;//m_setup.delay < m_setup.maxdelay ? m_setup.delay : m_setup.maxdelay; - - /* Attempt to go away out of song end must rewind position to begin */ - if(seconds > fullSongTimeLength) - { - rewind(); - return; - } - - bool loopFlagState = m_setup.loopingIsEnabled; - // Turn loop pooints off because it causes wrong position rememberin on a quick seek - m_setup.loopingIsEnabled = false; - - /* - * Seeking search is similar to regular ticking, except of next things: - * - We don't processsing arpeggio and vibrato - * - To keep correctness of the state after seek, begin every search from begin - * - All sustaining notes must be killed - * - Ignore Note-On events - */ - rewind(); - - /* - * Set "loop Start" to false to prevent overwrite of loopStart position with - * seek destinition position - * - * TODO: Detect & set loopStart position on load time to don't break loop while seeking - */ - loopStart = false; - - while((CurrentPositionNew.absTimePosition < seconds) && - (CurrentPositionNew.absTimePosition < fullSongTimeLength)) - { - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - double dstWait = CurrentPositionNew.wait + granualityHalf; - while((CurrentPositionNew.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew(true)) - break; - //Avoid freeze because of no waiting increasing in more than 10000 cycles - if(CurrentPositionNew.wait <= dstWait) - antiFreezeCounter--; - else - { - dstWait = CurrentPositionNew.wait + granualityHalf; - antiFreezeCounter = 10000; - } - } - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - } - - if(CurrentPositionNew.wait < 0.0) - CurrentPositionNew.wait = 0.0; - - m_setup.loopingIsEnabled = loopFlagState; - m_setup.delay = CurrentPositionNew.wait; - m_setup.carry = 0.0; -} - -double MIDIplay::tell() -{ - return CurrentPositionNew.absTimePosition; -} - -double MIDIplay::timeLength() -{ - return fullSongTimeLength; -} - -double MIDIplay::getLoopStart() -{ - return loopStartTime; -} - -double MIDIplay::getLoopEnd() -{ - return loopEndTime; -} - -void MIDIplay::rewind() -{ - Panic(); - KillSustainingNotes(-1, -1); - CurrentPositionNew = trackBeginPositionNew; - atEnd = false; - loopStart = true; - loopEnd = false; - //invalidLoop = false;//No more needed here as this flag is set on load time -} - -void MIDIplay::setTempo(double tempo) -{ - tempoMultiplier = tempo; +#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) + UpdateGlide(s); +#endif } -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ void MIDIplay::realTime_ResetState() { @@ -961,9 +225,12 @@ void MIDIplay::realTime_ResetState() chan.lastlrpn = 0; chan.lastmrpn = 0; chan.nrpn = false; + if((m_synthMode & Mode_GS) != 0)// Reset custom drum channels on GS + chan.is_xg_percussion = false; NoteUpdate_All(uint16_t(ch), Upd_All); NoteUpdate_All(uint16_t(ch), Upd_Off); } + m_masterVolume = MasterVolumeDefault; } bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) @@ -996,32 +263,40 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) MIDIchannel &midiChan = Ch[channel]; size_t midiins = midiChan.patch; - bool isPercussion = (channel % 16 == 9); - bool isXgPercussion = false; + bool isPercussion = (channel % 16 == 9) || midiChan.is_xg_percussion; uint16_t bank = 0; if(midiChan.bank_msb || midiChan.bank_lsb) { - bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.bank_lsb); - //0x7E00 - XG SFX1/SFX2 channel (16128 signed decimal) - //0x7F00 - XG Percussion channel (16256 signed decimal) - if(bank == 0x7E00 || bank == 0x7F00) - { - //Let XG SFX1/SFX2 bank will have LSB==1 (128...255 range in WOPN file) - //Let XG Percussion bank will use (0...127 range in WOPN file) - bank = (uint16_t)midiins + ((bank == 0x7E00) ? 128 : 0); // MIDI instrument defines the patch - midiins = note; // Percussion instrument - isXgPercussion = true; - isPercussion = false; - } + if((m_synthMode & Mode_GS) != 0) //in GS mode ignore LSB + bank = (uint16_t(midiChan.bank_msb) * 256); + else + bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.bank_lsb); } if(isPercussion) { - bank = (uint16_t)midiins; // MIDI instrument defines the patch + // == XG bank numbers == + // 0x7E00 - XG "SFX Kits" SFX1/SFX2 channel (16128 signed decimal) + // 0x7F00 - XG "Drum Kits" Percussion channel (16256 signed decimal) + + // MIDI instrument defines the patch: + if((m_synthMode & Mode_XG) != 0) + { + // Let XG SFX1/SFX2 bank will go in 128...255 range of LSB in WOPN file) + // Let XG Percussion bank will use (0...127 LSB range in WOPN file) + + // Choose: SFX or Drum Kits + bank = (uint16_t)midiins + ((bank == 0x7E00) ? 128 : 0); + } + else + { + bank = (uint16_t)midiins; + } midiins = note; // Percussion instrument } - if(isPercussion || isXgPercussion) + + if(isPercussion) bank += OPL3::PercussionTag; const adlinsdata2 *ains = &OPL3::emptyInstrument; @@ -1038,9 +313,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) ains = &bnk->ins[midiins]; else if(hooks.onDebugMessage) { - std::set<uint16_t> &missing = (isPercussion || isXgPercussion) ? + std::set<uint16_t> &missing = (isPercussion) ? caugh_missing_banks_percussion : caugh_missing_banks_melodic; - const char *text = (isPercussion || isXgPercussion) ? + const char *text = (isPercussion) ? "percussion" : "melodic"; if(missing.insert(bank).second) hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing %s MIDI bank %i (patch %i)", channel, text, bank, midiins); @@ -1057,25 +332,15 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) ains = &bnk->ins[midiins]; } - /* - if(MidCh%16 == 9 || (midiins != 32 && midiins != 46 && midiins != 48 && midiins != 50)) - break; // HACK - if(midiins == 46) vol = (vol*7)/10; // HACK - if(midiins == 48 || midiins == 50) vol /= 4; // HACK - */ - //if(midiins == 56) vol = vol*6/10; // HACK - //int meta = banks[opl.AdlBank][midiins]; - int16_t tone = note; - - if(!isPercussion && !isXgPercussion && (bank > 0)) // For non-zero banks + if(!isPercussion && (bank > 0)) // For non-zero banks { if(ains->flags & adlinsdata::Flag_NoSound) { if(hooks.onDebugMessage) { if(caugh_missing_instruments.insert(static_cast<uint8_t>(midiins)).second) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Caugh a blank instrument %i (offset %i) in the MIDI bank %u", channel, Ch[channel].patch, midiins, bank); + hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Caught a blank instrument %i (offset %i) in the MIDI bank %u", channel, Ch[channel].patch, midiins, bank); } bank = 0; midiins = midiChan.patch; @@ -1204,16 +469,37 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) //if(hooks.onDebugMessage) // hooks.onDebugMessage(hooks.onDebugMessage_userData, "i1=%d:%d, i2=%d:%d", i[0],adlchannel[0], i[1],adlchannel[1]); + if(midiChan.softPedal) // Apply Soft Pedal level reducing + velocity = static_cast<uint8_t>(std::floor(static_cast<float>(velocity) * 0.8f)); + // Allocate active note for MIDI channel std::pair<MIDIchannel::activenoteiterator, bool> ir = midiChan.activenotes_insert(note); ir.first->vol = velocity; ir.first->vibrato = midiChan.noteAftertouch[note]; - ir.first->tone = tone; + ir.first->noteTone = tone; + ir.first->currentTone = tone; + ir.first->glideRate = HUGE_VAL; ir.first->midiins = midiins; ir.first->ains = ains; ir.first->chip_channels_count = 0; + int8_t currentPortamentoSource = midiChan.portamentoSource; + double currentPortamentoRate = midiChan.portamentoRate; + bool portamentoEnable = + midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion; + // Record the last note on MIDI channel as source of portamento + midiChan.portamentoSource = static_cast<int8_t>(note); + // midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; + + // Enable gliding on portamento note + if (portamentoEnable && currentPortamentoSource >= 0) + { + ir.first->currentTone = currentPortamentoSource; + ir.first->glideRate = currentPortamentoRate; + ++midiChan.gliding_note_count; + } + for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { int32_t c = adlchannel[ccount]; @@ -1222,6 +508,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) uint16_t chipChan = static_cast<uint16_t>(adlchannel[ccount]); ir.first->phys_ensure_find_or_create(chipChan)->assign(voices[ccount]); } + NoteUpdate(channel, ir.first, Upd_All | Upd_Patch); return true; } @@ -1271,27 +558,29 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) case 0: // Set bank msb (GM bank) Ch[channel].bank_msb = value; - Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb); + if((m_synthMode & Mode_GS) == 0)// Don't use XG drums on GS synth mode + Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb); break; case 32: // Set bank lsb (XG bank) Ch[channel].bank_lsb = value; - Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb); + if((m_synthMode & Mode_GS) == 0)// Don't use XG drums on GS synth mode + Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb); break; case 5: // Set portamento msb Ch[channel].portamento = static_cast<uint16_t>((Ch[channel].portamento & 0x7F) | (value << 7)); - //UpdatePortamento(MidCh); + UpdatePortamento(channel); break; case 37: // Set portamento lsb Ch[channel].portamento = (Ch[channel].portamento & 0x3F80) | (value); - //UpdatePortamento(MidCh); + UpdatePortamento(channel); break; case 65: // Enable/disable portamento - // value >= 64 ? enabled : disabled - //UpdatePortamento(MidCh); + Ch[channel].portamentoEnable = value >= 64; + UpdatePortamento(channel); break; case 7: // Change volume @@ -1305,8 +594,20 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) break; case 64: // Enable/disable sustain - Ch[channel].sustain = value; - if(!value) KillSustainingNotes(channel); + Ch[channel].sustain = (value >= 64); + if(!Ch[channel].sustain) + KillSustainingNotes(channel, -1, AdlChannel::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); + break; + + case 67: // Enable/disable soft-pedal + Ch[channel].softPedal = (value >= 64); break; case 11: // Change expression (another volume factor) @@ -1324,14 +625,13 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) case 121: // Reset all controllers Ch[channel].resetAllControllers(); - //UpdatePortamento(MidCh); NoteUpdate_All(channel, Upd_Pan + Upd_Volume + Upd_Pitch); // Kill all sustained notes - KillSustainingNotes(channel); + KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_ANY); break; case 120: // All sounds off - NoteUpdate_All(channel, Upt_OffMute); + NoteUpdate_All(channel, Upd_OffMute); break; case 123: // All notes off @@ -1433,19 +733,270 @@ void MIDIplay::realTime_BankChange(uint8_t channel, uint16_t bank) Ch[channel].bank_msb = uint8_t((bank >> 8) & 0xFF); } +void MIDIplay::setDeviceId(uint8_t id) +{ + m_sysExDeviceId = id; +} + +bool MIDIplay::realTime_SysEx(const uint8_t *msg, size_t size) +{ + if(size < 4 || msg[0] != 0xF0 || msg[size - 1] != 0xF7) + return false; + + unsigned manufacturer = msg[1]; + unsigned dev = msg[2]; + msg += 3; + size -= 4; + + switch(manufacturer) + { + default: + break; + case Manufacturer_UniversalNonRealtime: + case Manufacturer_UniversalRealtime: + return doUniversalSysEx( + dev, manufacturer == Manufacturer_UniversalRealtime, msg, size); + case Manufacturer_Roland: + return doRolandSysEx(dev, msg, size); + case Manufacturer_Yamaha: + return doYamahaSysEx(dev, msg, size); + } + + return false; +} + +bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, size_t size) +{ + bool devicematch = dev == 0x7F || dev == m_sysExDeviceId; + if(size < 2 || !devicematch) + return false; + + unsigned address = + (((unsigned)data[0] & 0x7F) << 8) | + (((unsigned)data[1] & 0x7F)); + data += 2; + size -= 2; + + switch(((unsigned)realtime << 16) | address) + { + case (0 << 16) | 0x0901: // GM System On + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System On"); + m_synthMode = Mode_GM; + realTime_ResetState(); + return true; + case (0 << 16) | 0x0902: // GM System Off + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System Off"); + m_synthMode = Mode_XG;//TODO: TEMPORARY, make something RIGHT + realTime_ResetState(); + return true; + case (1 << 16) | 0x0401: // MIDI Master Volume + if(size != 2) + break; + unsigned volume = + (((unsigned)data[0] & 0x7F)) | + (((unsigned)data[1] & 0x7F) << 7); + m_masterVolume = volume >> 7; + for(size_t ch = 0; ch < Ch.size(); ch++) + NoteUpdate_All(uint16_t(ch), Upd_Volume); + return true; + } + + return false; +} + +bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) +{ + bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId; + if(size < 6 || !devicematch) + return false; + + unsigned model = data[0] & 0x7F; + unsigned mode = data[1] & 0x7F; + unsigned checksum = data[size - 1] & 0x7F; + data += 2; + size -= 3; + +#if !defined(ADLMIDI_SKIP_ROLAND_CHECKSUM) + { + unsigned checkvalue = 0; + for(size_t i = 0; i < size; ++i) + checkvalue += data[i] & 0x7F; + checkvalue = (128 - (checkvalue & 127)) & 127; + if(checkvalue != checksum) + { + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught invalid roland SysEx message!"); + return false; + } + } +#endif + + unsigned address = + (((unsigned)data[0] & 0x7F) << 16) | + (((unsigned)data[1] & 0x7F) << 8) | + (((unsigned)data[2] & 0x7F)); + unsigned target_channel = 0; + + /* F0 41 10 42 12 40 00 7F 00 41 F7 */ + + if((address & 0xFFF0FF) == 0x401015) // Turn channel 1 into percussion + { + address = 0x401015; + target_channel = data[1] & 0x0F; + } + + data += 3; + size -= 3; + + if(mode != RolandMode_Send) // don't have MIDI-Out reply ability + return false; + + // Mode Set + // F0 {41 10 42 12} {40 00 7F} {00 41} F7 + + // Custom drum channels + // F0 {41 10 42 12} {40 1<ch> 15} {<state> <sum>} F7 + + switch((model << 24) | address) + { + case (RolandModel_GS << 24) | 0x00007F: // System Mode Set + { + if(size != 1 || (dev & 0xF0) != 0x10) + break; + unsigned mode = data[0] & 0x7F; + ADL_UNUSED(mode);//TODO: Hook this correctly! + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland System Mode Set: %02X", mode); + m_synthMode = Mode_GS; + realTime_ResetState(); + return true; + } + case (RolandModel_GS << 24) | 0x40007F: // Mode Set + { + if(size != 1 || (dev & 0xF0) != 0x10) + break; + unsigned value = data[0] & 0x7F; + ADL_UNUSED(value);//TODO: Hook this correctly! + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland Mode Set: %02X", value); + m_synthMode = Mode_GS; + realTime_ResetState(); + return true; + } + case (RolandModel_GS << 24) | 0x401015: // Percussion channel + { + if(size != 1 || (dev & 0xF0) != 0x10) + break; + if(Ch.size() < 16) + break; + unsigned value = data[0] & 0x7F; + const uint8_t channels_map[16] = + { + 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15 + }; + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, + "SysEx: Caught Roland Percussion set: %02X on channel %u (from %X)", + value, channels_map[target_channel], target_channel); + Ch[channels_map[target_channel]].is_xg_percussion = ((value == 0x01)) || ((value == 0x02)); + return true; + } + } + + return false; +} + +bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) +{ + bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId; + if(size < 1 || !devicematch) + return false; + + unsigned model = data[0] & 0x7F; + ++data; + --size; + + switch((model << 8) | (dev & 0xF0)) + { + case (YamahaModel_XG << 8) | 0x10: // parameter change + { + if(size < 3) + break; + + unsigned address = + (((unsigned)data[0] & 0x7F) << 16) | + (((unsigned)data[1] & 0x7F) << 8) | + (((unsigned)data[2] & 0x7F)); + data += 3; + size -= 3; + + switch(address) + { + case 0x00007E: // XG System On + if(size != 1) + break; + unsigned value = data[0] & 0x7F; + ADL_UNUSED(value);//TODO: Hook this correctly! + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Yamaha XG System On: %02X", value); + m_synthMode = Mode_XG; + realTime_ResetState(); + return true; + } + + break; + } + } + + return false; +} + void MIDIplay::realTime_panic() { Panic(); - KillSustainingNotes(-1, -1); + KillSustainingNotes(-1, -1, AdlChannel::LocationData::Sustain_ANY); +} + +void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length) +{ + const std::string indata(data, length); + current_device[track] = ChooseDevice(indata); } -void MIDIplay::AudioTick(uint32_t chipId, uint32_t /*rate*/) +uint64_t MIDIplay::realTime_currentDevice(size_t track) +{ + return current_device[track]; +} + +void MIDIplay::realTime_rawOPL(uint8_t reg, uint8_t value) +{ + if((reg & 0xF0) == 0xC0) + value |= 0x30; + //std::printf("OPL poke %02X, %02X\n", reg, value); + //std::fflush(stdout); + opl.Poke(0, reg, value); +} + +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) +void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) { if(chipId != 0) // do first chip ticks only return; - /*uint32_t tickNumber = */m_audioTickCounter++; + uint32_t tickNumber = m_audioTickCounter++; + double timeDelta = 1.0 / rate; + + enum { portamentoInterval = 32 }; // for efficiency, set rate limit on pitch updates + + if(tickNumber % portamentoInterval == 0) + { + double portamentoDelta = timeDelta * portamentoInterval; + UpdateGlide(portamentoDelta); + } } +#endif void MIDIplay::NoteUpdate(uint16_t MidCh, MIDIplay::MIDIchannel::activenoteiterator i, @@ -1453,7 +1004,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, int32_t select_adlchn) { MIDIchannel::NoteInfo &info = *i; - const int16_t tone = info.tone; + const int16_t noteTone = info.noteTone; + const double currentTone = info.currentTone; const uint8_t vol = info.vol; const int midiins = static_cast<int>(info.midiins); const adlinsdata2 &ains = *info.ains; @@ -1474,7 +1026,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc); if(d) // inserts if necessary { - d->sustained = false; + d->sustained = AdlChannel::LocationData::Sustain_None; d->vibdelay = 0; d->fixed_sustain = (ains.ms_sound_kon == static_cast<uint16_t>(adlNoteOnMaxTime)); d->kon_time_until_neglible = ains.ms_sound_kon; @@ -1493,17 +1045,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(props_mask & Upd_Off) // note off { - if(Ch[MidCh].sustain == 0) + if(!Ch[MidCh].sustain) { AdlChannel::LocationData *k = ch[c].users_find(my_loc); - - if(k) + bool do_erase_user = (k && ((k->sustained & AdlChannel::LocationData::Sustain_Sostenuto) == 0)); + if(do_erase_user) ch[c].users_erase(k); if(hooks.onNote) - hooks.onNote(hooks.onNote_userData, c, tone, midiins, 0, 0.0); + hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, 0, 0.0); - if(ch[c].users_empty()) + if(do_erase_user && ch[c].users_empty()) { opl.NoteOff(c); if(props_mask & Upd_Mute) // Mute the note @@ -1523,9 +1075,9 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, // Also will avoid overwriting it very soon. AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc); if(d) - d->sustained = true; // note: not erased! + d->sustained |= AdlChannel::LocationData::Sustain_Pedal; // note: not erased! if(hooks.onNote) - hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0); + hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, -1, 0.0); } info.phys_erase_at(&ins); // decrements channel count @@ -1553,10 +1105,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, switch(opl.m_volumeScale) { - + default: case OPL3::VOLUME_Generic: { - volume = vol * Ch[MidCh].volume * Ch[MidCh].expression; + volume = vol * m_masterVolume * Ch[MidCh].volume * Ch[MidCh].expression; /* If the channel has arpeggio, the effective volume of * *this* instrument is actually lower due to timesharing. @@ -1567,64 +1119,60 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, */ //volume = (int)(volume * std::sqrt( (double) ch[c].users.size() )); - // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A) - volume = volume > 8725 ? static_cast<uint32_t>(std::log(static_cast<double>(volume)) * 11.541561 + (0.5 - 104.22845)) : 0; - // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A) - //opl.Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0); - - opl.Touch_Real(c, volume, brightness); - //opl.Touch(c, volume); + // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A) + volume = volume > (8725 * 127) ? static_cast<uint32_t>(std::log(static_cast<double>(volume)) * 11.541560327111707 - 1.601379199767093e+02) : 0; + // The incorrect formula below: SOLVE(V=127^4 * (2^(A/63)-1), A) + //opl.Touch_Real(c, volume>(11210*127) ? 91.61112 * std::log((4.8819E-7/127)*volume + 1.0)+0.5 : 0); } break; case OPL3::VOLUME_NATIVE: { volume = vol * Ch[MidCh].volume * Ch[MidCh].expression; - volume = volume * 127 / (127 * 127 * 127) / 2; - opl.Touch_Real(c, volume, brightness); + // volume = volume * m_masterVolume / (127 * 127 * 127) / 2; + volume = (volume * m_masterVolume) / 4096766; } break; case OPL3::VOLUME_DMX: { - volume = 2 * ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129) + 1; + volume = 2 * (Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 16129) + 1; //volume = 2 * (Ch[MidCh].volume) + 1; volume = (DMX_volume_mapping_table[(vol < 128) ? vol : 127] * volume) >> 9; - opl.Touch_Real(c, volume, brightness); } break; case OPL3::VOLUME_APOGEE: { - volume = ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129); + volume = (Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 16129); volume = ((64 * (vol + 0x80)) * volume) >> 15; //volume = ((63 * (vol + 0x80)) * Ch[MidCh].volume) >> 15; - opl.Touch_Real(c, volume, brightness); } break; case OPL3::VOLUME_9X: { - //volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * 127 / 16129 /*2048383*/) >> 2)]; - volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 2048383) >> 2)]; + //volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * m_masterVolume / 16129 /*2048383*/) >> 2)]; + volume = 63 - W9X_volume_mapping_table[((vol * Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 2048383) >> 2)]; //volume = W9X_volume_mapping_table[vol >> 2] + volume; - opl.Touch_Real(c, volume, brightness); } break; } + opl.Touch_Real(c, volume, brightness); + /* DEBUG ONLY!!! - static uint32_t max = 0; + static uint32_t max = 0; - if(volume == 0) - max = 0; + if(volume == 0) + max = 0; - if(volume > max) - max = volume; + if(volume > max) + max = volume; - printf("%d\n", max); - fflush(stdout); - */ + printf("%d\n", max); + fflush(stdout); + */ } if(props_mask & Upd_Pitch) @@ -1632,7 +1180,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, AdlChannel::LocationData *d = ch[c].users_find(my_loc); // Don't bend a sustained note - if(!d || !d->sustained) + if(!d || (d->sustained == AdlChannel::LocationData::Sustain_None)) { double midibend = Ch[MidCh].bend * Ch[MidCh].bendsense; double bend = midibend + ins.ains.finetune; @@ -1648,355 +1196,22 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(vibrato && (!d || d->vibdelay >= Ch[MidCh].vibdelay)) bend += static_cast<double>(vibrato) * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos); -#ifdef ADLMIDI_USE_DOSBOX_OPL -# define BEND_COEFFICIENT 172.00093 -#else -# define BEND_COEFFICIENT 172.4387 -#endif - opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (static_cast<double>(tone) + bend + phase))); +#define BEND_COEFFICIENT 172.4387 + opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase))); #undef BEND_COEFFICIENT if(hooks.onNote) - hooks.onNote(hooks.onNote_userData, c, tone, midiins, vol, midibend); + hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, vol, midibend); } } } if(info.chip_channels_count == 0) - Ch[MidCh].activenotes_erase(i); -} - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::ProcessEventsNew(bool isSeek) -{ - if(CurrentPositionNew.track.size() == 0) - atEnd = true;//No MIDI track data to play - if(atEnd) - return false;//No more events in the queue - - loopEnd = false; - const size_t TrackCount = CurrentPositionNew.track.size(); - const PositionNew RowBeginPosition(CurrentPositionNew); - -#ifdef DEBUG_TIME_CALCULATION - double maxTime = 0.0; -#endif - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (track.delay <= 0)) - { - //Check is an end of track has been reached - if(track.pos == trackDataNew[tk].end()) - { - track.status = -1; - break; - } - - // Handle event - for(size_t i = 0; i < track.pos->events.size(); i++) - { - const MidiEvent &evt = track.pos->events[i]; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) - CurrentPositionNew.began = true; -#endif - if(isSeek && (evt.type == MidiEvent::T_NOTEON)) - continue; - HandleEvent(tk, evt, track.status); - if(loopEnd) - break;//Stop event handling on catching loopEnd event! - } - -#ifdef DEBUG_TIME_CALCULATION - if(maxTime < track.pos->time) - maxTime = track.pos->time; -#endif - // Read next event time (unless the track just ended) - if(track.status >= 0) - { - track.delay += track.pos->delay; - track.pos++; - } - } - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); - std::fflush(stdout); -#endif - - // Find shortest delay from all track - uint64_t shortest = 0; - bool shortest_no = true; - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (shortest_no || track.delay < shortest)) - { - shortest = track.delay; - shortest_no = false; - } - } - - //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); - - // Schedule the next playevent to be processed after that delay - for(size_t tk = 0; tk < TrackCount; ++tk) - CurrentPositionNew.track[tk].delay -= shortest; - - fraction<uint64_t> t = shortest * Tempo; - -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait += t.value(); - - //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); - if(loopStart) - { - LoopBeginPositionNew = RowBeginPosition; - loopStart = false; - } - - if(shortest_no || loopEnd) - { - //Loop if song end or loop end point has reached - loopEnd = false; - shortest = 0; - if(!m_setup.loopingIsEnabled) - { - atEnd = true; //Don't handle events anymore - CurrentPositionNew.wait += postSongWaitDelay;//One second delay until stop playing - return true;//We have caugh end here! - } - CurrentPositionNew = LoopBeginPositionNew; - } - - return true;//Has events in queue -} - -MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t **pptr, uint8_t *end, int &status) -{ - uint8_t *&ptr = *pptr; - MIDIplay::MidiEvent evt; - - if(ptr + 1 > end) - { - //When track doesn't ends on the middle of event data, it's must be fine - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_ENDTRACK; - return evt; - } - - unsigned char byte = *(ptr++); - bool ok = false; - - if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx - { - uint64_t length = ReadVarLenEx(pptr, end, ok); - if(!ok || (ptr + length > end)) - { - errorString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - ptr += (size_t)length; - return evt; - } - - if(byte == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = *(ptr++); - uint64_t length = ReadVarLenEx(pptr, end, ok); - if(!ok || (ptr + length > end)) - { - errorString += "parseEvent: Can't read Special event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - std::string data(length ? (const char *)ptr : 0, (size_t)length); - ptr += (size_t)length; - - evt.type = byte; - evt.subtype = evtype; - evt.data.insert(evt.data.begin(), data.begin(), data.end()); - -#if 0 /* Print all tempo events */ - if(evt.subtype == MidiEvent::ST_TEMPOCHANGE) - { - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]); - } -#endif - - /* TODO: Store those meta-strings separately and give ability to read them - * by external functions (to display song title and copyright in the player) */ - if(evt.subtype == MidiEvent::ST_COPYRIGHT) - { - if(musCopyright.empty()) - { - musCopyright = std::string((const char *)evt.data.data(), evt.data.size()); - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music copyright: %s", musCopyright.c_str()); - } - else if(hooks.onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Extra copyright event: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_SQTRKTITLE) - { - if(musTitle.empty()) - { - musTitle = std::string((const char *)evt.data.data(), evt.data.size()); - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music title: %s", musTitle.c_str()); - } - else if(hooks.onDebugMessage) - { - //TODO: Store track titles and associate them with each track and make API to retreive them - std::string str((const char *)evt.data.data(), evt.data.size()); - musTrackTitles.push_back(str); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Track title: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_INSTRTITLE) - { - if(hooks.onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Instrument: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_MARKER) - { - //To lower - for(size_t i = 0; i < data.size(); i++) - { - if(data[i] <= 'Z' && data[i] >= 'A') - data[i] = data[i] - ('Z' - 'z'); - } - - if(data == "loopstart") - { - //Return a custom Loop Start event instead of Marker - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear();//Data is not needed - return evt; - } - - if(data == "loopend") - { - //Return a custom Loop End event instead of Marker - evt.subtype = MidiEvent::ST_LOOPEND; - evt.data.clear();//Data is not needed - return evt; - } - } - - if(evtype == MidiEvent::ST_ENDTRACK) - status = -1;//Finalize track - - return evt; - } - - // Any normal event (80..EF) - if(byte < 0x80) - { - byte = static_cast<uint8_t>(status | 0x80); - ptr--; - } - - //Sys Com Song Select(Song #) [0-127] - if(byte == MidiEvent::T_SYSCOMSNGSEL) - { - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - return evt; - } - - //Sys Com Song Position Pntr [LSB, MSB] - if(byte == MidiEvent::T_SYSCOMSPOSPTR) - { - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - return evt; - } - - uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; - status = byte; - evt.channel = midCh; - evt.type = evType; - - switch(evType) { - case MidiEvent::T_NOTEOFF://2 byte length - case MidiEvent::T_NOTEON: - case MidiEvent::T_NOTETOUCH: - case MidiEvent::T_CTRLCHANGE: - case MidiEvent::T_WHEEL: - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - - /* TODO: Implement conversion of RSXX's note volumes out of synthesizer */ - /*if((opl.m_musicMode == OPL3::MODE_RSXX) && (evType == MidiEvent::T_NOTEON) && (evt.data[1] != 0)) - { - //NOT WORKING YET - evt.type = MidiEvent::T_NOTETOUCH; - } - else */if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) - { - evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! - } //111'th loopStart controller (RPG Maker and others) - else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) - { - //Change event type to custom Loop Start event and clear data - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear(); - } - - return evt; - case MidiEvent::T_PATCHCHANGE://1 byte length - case MidiEvent::T_CHANAFTTOUCH: - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.data.push_back(*(ptr++)); - return evt; + if(i->glideRate != HUGE_VAL) + --Ch[MidCh].gliding_note_count; + Ch[MidCh].activenotes_erase(i); } - - return evt; } -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ const std::string &MIDIplay::getErrorString() { @@ -2008,168 +1223,9 @@ void MIDIplay::setErrorString(const std::string &err) errorStringOut = err; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &status) -{ - if(hooks.onEvent) - { - hooks.onEvent(hooks.onEvent_userData, - evt.type, - evt.subtype, - evt.channel, - evt.data.data(), - evt.data.size()); - } - - if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx - { - //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); - //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); - return; - } - - if(evt.type == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = evt.subtype; - uint64_t length = (uint64_t)evt.data.size(); - std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); - - if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track - { - status = -1; - return; - } - - if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change - { - Tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(evt.data.data(), evt.data.size())); - return; - } - - if(evtype == MidiEvent::ST_MARKER)//Meta event - { - //Do nothing! :-P - return; - } - - if(evtype == MidiEvent::ST_DEVICESWITCH) - { - current_device[tk] = ChooseDevice(data); - return; - } - - //if(evtype >= 1 && evtype <= 6) - // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); - - //Turn on Loop handling when loop is enabled - if(m_setup.loopingIsEnabled && !invalidLoop) - { - if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - loopStart = true; - return; - } - - if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - loopEnd = true; - return; - } - } - - if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - uint8_t i = static_cast<uint8_t>(data[0]), v = static_cast<uint8_t>(data[1]); - if((i & 0xF0) == 0xC0) - v |= 0x30; - //std::printf("OPL poke %02X, %02X\n", i, v); - //std::fflush(stdout); - opl.Poke(0, i, v); - return; - } - - return; - } - - // Any normal event (80..EF) - // if(evt.type < 0x80) - // { - // byte = static_cast<uint8_t>(CurrentPosition.track[tk].status | 0x80); - // CurrentPosition.track[tk].ptr--; - // } - - if(evt.type == MidiEvent::T_SYSCOMSNGSEL || - evt.type == MidiEvent::T_SYSCOMSPOSPTR) - return; - - /*UI.PrintLn("@%X Track %u: %02X %02X", - CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, - TrackData[tk][CurrentPosition.track[tk].ptr]);*/ - uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; - midCh += (uint8_t)current_device[tk]; - status = evt.type; - - switch(evt.type) - { - case MidiEvent::T_NOTEOFF: // Note off - { - uint8_t note = evt.data[0]; - realTime_NoteOff(midCh, note); - break; - } - - case MidiEvent::T_NOTEON: // Note on - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - /*if(*/ realTime_NoteOn(midCh, note, vol); /*)*/ - //CurrentPosition.began = true; - break; - } - - case MidiEvent::T_NOTETOUCH: // Note touch - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - realTime_NoteAfterTouch(midCh, note, vol); - break; - } - - case MidiEvent::T_CTRLCHANGE: // Controller change - { - uint8_t ctrlno = evt.data[0]; - uint8_t value = evt.data[1]; - realTime_Controller(midCh, ctrlno, value); - break; - } - - case MidiEvent::T_PATCHCHANGE: // Patch change - realTime_PatchChange(midCh, evt.data[0]); - break; - - case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch - { - // TODO: Verify, is this correct action? - uint8_t vol = evt.data[0]; - realTime_ChannelAfterTouch(midCh, vol); - break; - } - - case MidiEvent::T_WHEEL: // Wheel/pitch bend - { - uint8_t a = evt.data[0]; - uint8_t b = evt.data[1]; - realTime_PitchBend(midCh, b, a); - break; - } - } -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t) const { - int64_t s = -ch[c].koff_time_until_neglible; + int64_t s = (opl.m_musicMode != OPL3::MODE_CMF) ? -ch[c].koff_time_until_neglible : 0; // Same midi-instrument = some stability //if(c == MidCh) s += 4; @@ -2177,7 +1233,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteI { s -= 4000; - if(!j->sustained) + if(j->sustained == AdlChannel::LocationData::Sustain_None) s -= j->kon_time_until_neglible; else s -= (j->kon_time_until_neglible / 2); @@ -2223,7 +1279,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteI for(AdlChannel::LocationData *m = ch[c2].users_first; m; m = m->next) { - if(m->sustained) continue; + if(m->sustained != AdlChannel::LocationData::Sustain_None) continue; if(m->vibdelay >= 200) continue; if(m->ins != j->ins) continue; n_evacuation_stations += 1; @@ -2247,7 +1303,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo AdlChannel::LocationData *j = jnext; jnext = jnext->next; - if(!j->sustained) + if(j->sustained == AdlChannel::LocationData::Sustain_None) { // Collision: Kill old note, // UNLESS we're going to do arpeggio @@ -2272,7 +1328,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo // 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<int32_t>(c)); + KillSustainingNotes(-1, static_cast<int32_t>(c), AdlChannel::LocationData::Sustain_ANY); // Keyoff the channel so that it can be retriggered, // unless the new note will be introduced as just an arpeggio. @@ -2314,11 +1370,11 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, { hooks.onNote(hooks.onNote_userData, (int)from_channel, - i->tone, + i->noteTone, static_cast<int>(i->midiins), 0, 0.0); hooks.onNote(hooks.onNote_userData, (int)c, - i->tone, + i->noteTone, static_cast<int>(i->midiins), i->vol, 0.0); } @@ -2355,7 +1411,7 @@ void MIDIplay::Panic() } } -void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn) +void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn, uint8_t sustain_type) { uint32_t first = 0, last = opl.NumChannels; @@ -2365,9 +1421,10 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn) last = first + 1; } - for(unsigned c = first; c < last; ++c) + for(uint32_t 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::LocationData *jnext = ch[c].users_first; jnext;) { @@ -2375,12 +1432,14 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn) jnext = jnext->next; if((MidCh < 0 || j->loc.MidCh == MidCh) - && j->sustained) + && ((j->sustained & sustain_type) != 0)) { int midiins = '?'; if(hooks.onNote) hooks.onNote(hooks.onNote_userData, (int)c, j->loc.note, midiins, 0, 0.0); - ch[c].users_erase(j); + j->sustained &= ~sustain_type; + if((j->sustained == AdlChannel::LocationData::Sustain_None)) + ch[c].users_erase(j);//Remove only when note is clean from any holders } } @@ -2390,6 +1449,24 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn) } } +void MIDIplay::MarkSostenutoNotes(int32_t MidCh) +{ + uint32_t first = 0, last = opl.NumChannels; + for(uint32_t c = first; c < last; ++c) + { + if(ch[c].users_empty()) + continue; // Nothing to do + + for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;) + { + AdlChannel::LocationData *j = jnext; + jnext = jnext->next; + if((j->loc.MidCh == MidCh) && (j->sustained == AdlChannel::LocationData::Sustain_None)) + j->sustained |= AdlChannel::LocationData::Sustain_Sostenuto; + } + } +} + void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB) { bool nrpn = Ch[MidCh].nrpn; @@ -2405,17 +1482,26 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB) Ch[MidCh].bendsense_lsb = value; Ch[MidCh].updateBendSensitivity(); break; - case 0x0108 + 1*0x10000 + 1*0x20000: // Vibrato speed - if(value == 64) Ch[MidCh].vibspeed = 1.0; - else if(value < 100) Ch[MidCh].vibspeed = 1.0 / (1.6e-2 * (value ? value : 1)); - else Ch[MidCh].vibspeed = 1.0 / (0.051153846 * value - 3.4965385); - Ch[MidCh].vibspeed *= 2 * 3.141592653 * 5.0; + case 0x0108 + 1*0x10000 + 1*0x20000: + if((m_synthMode & Mode_XG) != 0) // Vibrato speed + { + if(value == 64) Ch[MidCh].vibspeed = 1.0; + else if(value < 100) Ch[MidCh].vibspeed = 1.0 / (1.6e-2 * (value ? value : 1)); + else Ch[MidCh].vibspeed = 1.0 / (0.051153846 * value - 3.4965385); + Ch[MidCh].vibspeed *= 2 * 3.141592653 * 5.0; + } break; - case 0x0109 + 1*0x10000 + 1*0x20000: // Vibrato depth - Ch[MidCh].vibdepth = ((value - 64) * 0.15) * 0.01; + case 0x0109 + 1*0x10000 + 1*0x20000: + if((m_synthMode & Mode_XG) != 0) // Vibrato depth + { + Ch[MidCh].vibdepth = ((value - 64) * 0.15) * 0.01; + } break; - case 0x010A + 1*0x10000 + 1*0x20000: // Vibrato delay in millisecons - Ch[MidCh].vibdelay = value ? int64_t(0.2092 * std::exp(0.0795 * (double)value)) : 0; + case 0x010A + 1*0x10000 + 1*0x20000: + if((m_synthMode & Mode_XG) != 0) // Vibrato delay in millisecons + { + Ch[MidCh].vibdelay = value ? int64_t(0.2092 * std::exp(0.0795 * (double)value)) : 0; + } break; default:/* UI.PrintLn("%s %04X <- %d (%cSB) (ch %u)", "NRPN"+!nrpn, addr, value, "LM"[MSB], MidCh);*/ @@ -2423,15 +1509,14 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB) } } -//void MIDIplay::UpdatePortamento(unsigned MidCh) -//{ -// // mt = 2^(portamento/2048) * (1.0 / 5000.0) -// /* -// double mt = std::exp(0.00033845077 * Ch[MidCh].portamento); -// NoteUpdate_All(MidCh, Upd_Pitch); -// */ -// //UI.PrintLn("Portamento %u: %u (unimplemented)", MidCh, Ch[MidCh].portamento); -//} +void MIDIplay::UpdatePortamento(unsigned MidCh) +{ + double rate = HUGE_VAL; + uint16_t midival = Ch[MidCh].portamento; + if(Ch[MidCh].portamentoEnable && midival > 0) + rate = 350.0 * std::pow(2.0, -0.062 * (1.0 / 128) * midival); + Ch[MidCh].portamentoRate = rate; +} void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask) { @@ -2467,9 +1552,6 @@ void MIDIplay::UpdateVibrato(double amount) } } - - - uint64_t MIDIplay::ChooseDevice(const std::string &name) { std::map<std::string, uint64_t>::iterator i = devices.find(name); @@ -2532,7 +1614,7 @@ retry_arpeggio: n = 0; n < count; ++n) i = i->next; - if(i->sustained == false) + if(i->sustained == AdlChannel::LocationData::Sustain_None) { if(i->kon_time_until_neglible <= 0l) { @@ -2554,6 +1636,38 @@ retry_arpeggio: } } +void MIDIplay::UpdateGlide(double amount) +{ + size_t num_channels = Ch.size(); + + for(size_t channel = 0; channel < num_channels; ++channel) + { + MIDIchannel &midiChan = Ch[channel]; + if(midiChan.gliding_note_count == 0) + continue; + + for(MIDIchannel::activenoteiterator it = midiChan.activenotes_begin(); + it; ++it) + { + double finalTone = it->noteTone; + double previousTone = it->currentTone; + + bool directionUp = previousTone < finalTone; + double toneIncr = amount * (directionUp ? +it->glideRate : -it->glideRate); + + double currentTone = previousTone + toneIncr; + bool glideFinished = !(directionUp ? (currentTone < finalTone) : (currentTone > finalTone)); + currentTone = glideFinished ? finalTone : currentTone; + + if(currentTone != previousTone) + { + it->currentTone = currentTone; + NoteUpdate(static_cast<uint16_t>(channel), it, Upd_Pitch); + } + } + } +} + #ifndef ADLMIDI_DISABLE_CPP_EXTRAS |