aboutsummaryrefslogtreecommitdiff
path: root/src/adlmidi_midiplay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/adlmidi_midiplay.cpp')
-rw-r--r--src/adlmidi_midiplay.cpp1292
1 files changed, 32 insertions, 1260 deletions
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp
index 2f186a6..5b7f0d9 100644
--- a/src/adlmidi_midiplay.cpp
+++ b/src/adlmidi_midiplay.cpp
@@ -115,586 +115,12 @@ 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)
#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
, m_audioTickCounter(0)
#endif
-#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)
-#endif
{
devices.clear();
@@ -714,13 +140,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();
@@ -766,79 +194,7 @@ 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)
-{
- 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 !defined(ADLMIDI_AUDIO_TICK_HANDLER)
- UpdateGlide(s);
-#endif
-
- 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)
+void MIDIplay::TickIterators(double s)
{
for(uint16_t c = 0; c < opl.NumChannels; ++c)
ch[c].AddAge(static_cast<int64_t>(s * 1000.0));
@@ -849,115 +205,6 @@ void MIDIplay::TickIteratos(double s)
#endif
}
-#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;
-}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
-
void MIDIplay::realTime_ResetState()
{
for(size_t ch = 0; ch < Ch.size(); ch++)
@@ -1465,6 +712,26 @@ void MIDIplay::realTime_panic()
KillSustainingNotes(-1, -1);
}
+void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length)
+{
+ const std::string indata(data, length);
+ current_device[track] = ChooseDevice(indata);
+}
+
+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)
{
@@ -1652,17 +919,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
}
/* 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)
@@ -1707,339 +974,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
}
}
-#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;
- }
-
- return evt;
-}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
-
const std::string &MIDIplay::getErrorString()
{
return errorStringOut;
@@ -2050,165 +984,6 @@ 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;
@@ -2508,9 +1283,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);