diff options
Diffstat (limited to 'src/adlmidi_midiplay.cpp')
-rw-r--r-- | src/adlmidi_midiplay.cpp | 1116 |
1 files changed, 610 insertions, 506 deletions
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index f536f4e..577e8b8 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -23,7 +23,6 @@ #include "adlmidi_private.hpp" - // Mapping from MIDI volume level to OPL level value. static const uint32_t DMX_volume_mapping_table[] = @@ -132,27 +131,29 @@ void MIDIplay::AdlChannel::AddAge(int64_t ms) MIDIplay::MidiEvent::MidiEvent() : type(T_UNKNOWN), subtype(T_UNKNOWN), - channel(0) + channel(0), + isValid(1), + absPosition(0) {} -MIDIplay::MidiTrackPos::MidiTrackPos() : +MIDIplay::MidiTrackRow::MidiTrackRow() : time(0.0), delay(0), - timeDelay(0.0), - next(NULL) + absPos(0), + timeDelay(0.0) {} -void MIDIplay::MidiTrackPos::reset() +void MIDIplay::MidiTrackRow::reset() { time = 0.0; delay = 0; + absPos = 0; timeDelay = 0.0; events.clear(); - next = NULL; } -void MIDIplay::MidiTrackPos::sortEvents() +void MIDIplay::MidiTrackRow::sortEvents() { std::vector<MidiEvent> metas; std::vector<MidiEvent> noteOffs; @@ -172,7 +173,7 @@ void MIDIplay::MidiTrackPos::sortEvents() controllers.push_back(events[i]); } else - if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_META)) + if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER)) metas.push_back(events[i]); else anyOther.push_back(events[i]); @@ -185,83 +186,316 @@ void MIDIplay::MidiTrackPos::sortEvents() events.insert(events.end(), anyOther.begin(), anyOther.end()); } -void MIDIplay::buildTrackData() +bool MIDIplay::buildTrackData() { + fullSongTimeLength = 0.0; + loopStartTime = -1.0; + loopEndTime = -1.0; + musTitle.clear(); trackDataNew.clear(); - trackDataNewStatus.clear(); const size_t trackCount = TrackData.size(); - trackDataNew.resize(trackCount, std::vector<MidiTrackPos>()); - trackDataNewStatus.resize(trackCount, 0); + 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); - /* TODO: Based on tempo changes, make accurate seconds time marking. - * Current way is inaccurate, because of tempo change at different track - * will cause time desynchronization between tracks. - * Also, seconds calculation is incorrect */ + //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!) + */ for(size_t tk = 0; tk < trackCount; ++tk) { - fraction<uint64_t> currentTempo = Tempo; - double time = 0.0; + uint64_t abs_position = 0; int status = 0; - std::vector<MidiTrackPos> posEvents; MidiEvent event; + bool ok = false; + uint8_t *end = TrackData[tk].data() + TrackData[tk].size(); uint8_t *trackPtr = TrackData[tk].data(); //Time delay that follows the first event in the track { - MidiTrackPos evtPos; - evtPos.delay = ReadVarLen(&trackPtr); - fraction<uint64_t> t = evtPos.delay * currentTempo; + MidiTrackRow evtPos; + evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + if(!ok) + { + int len = std::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; + } CurrentPositionNew.wait = evtPos.delay; - evtPos.timeDelay = t.value(); - time += evtPos.timeDelay; + evtPos.absPos = abs_position; + abs_position += evtPos.delay; trackDataNew[tk].push_back(evtPos); } - MidiTrackPos evtPos; + MidiTrackRow evtPos; do { - event = parseEvent(&trackPtr, status); + event = parseEvent(&trackPtr, end, status); + if(!event.isValid) + { + int len = std::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 && event.subtype == MidiEvent::ST_TEMPOCHANGE) - currentTempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(event.data.data(), event.data.size())); - evtPos.delay = ReadVarLen(&trackPtr); + 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) + { + int len = std::snprintf(error, 150, "buildTrackData: Can't read variable-length value in the track %d.\n", (int)tk); + if((len > 0) && (len < 150)) + errorString += std::string(error, (size_t)len); + return false; + } + } + if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK)) { - fraction<uint64_t> t = evtPos.delay * currentTempo; - evtPos.timeDelay = t.value() ; - evtPos.time = time; - time += evtPos.timeDelay; + evtPos.absPos = abs_position; + abs_position += evtPos.delay; evtPos.sortEvents(); trackDataNew[tk].push_back(evtPos); evtPos.reset(); + gotLoopEventInThisRow = false; } - } while(event.subtype != MidiEvent::ST_ENDTRACK); - - // Build the chain of events - for(size_t i = 0, j = 1; i < trackDataNew[tk].size() && j < trackDataNew[tk].size(); i++, j++) - trackDataNew[tk][i].next = &(trackDataNew[tk][j]); + } 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][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; + uint8_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; + + 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; + trackBeginPositionNew = CurrentPositionNew; + + return true; } MIDIplay::MIDIplay(): cmf_percussion_mode(false), - config(NULL), + fullSongTimeLength(0.0), + postSongWaitDelay(1.0), + loopStartTime(-1.0), + loopEndTime(-1.0), + tempoMultiplier(1.0), trackStart(false), atEnd(false), loopStart(false), loopEnd(false), - loopStart_passed(false), - invalidLoop(false), - loopStart_hit(false) + invalidLoop(false) { devices.clear(); + + m_setup.AdlBank = 0; + m_setup.NumFourOps = 7; + m_setup.NumCards = 2; + m_setup.HighTremoloMode = false; + m_setup.HighVibratoMode = false; + m_setup.AdlPercussionMode = false; + m_setup.LogarithmicVolumes = false; + m_setup.SkipForward = 0; + m_setup.loopingIsEnabled = false; + m_setup.ScaleModulators = false; + m_setup.delay = 0.0; + m_setup.carry = 0.0; + m_setup.stored_samples = 0; + m_setup.backup_samples_size = 0; + + opl.NumCards = m_setup.NumCards; + opl.AdlBank = m_setup.AdlBank; + opl.NumFourOps = m_setup.NumFourOps; + opl.LogarithmicVolumes = m_setup.LogarithmicVolumes; + opl.HighTremoloMode = m_setup.HighTremoloMode; + opl.HighVibratoMode = m_setup.HighVibratoMode; + opl.AdlPercussionMode = m_setup.AdlPercussionMode; + opl.ScaleModulators = m_setup.ScaleModulators; } uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) @@ -277,24 +511,33 @@ uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) return result; } -uint64_t MIDIplay::ReadVarLen(size_t tk) +uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok) { uint64_t result = 0; + ok = false; + for(;;) { - uint8_t byte = TrackData[tk][CurrentPosition.track[tk].ptr++]; + if(*ptr >= end) + return 2; + unsigned char byte = *((*ptr)++); result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; + if(!(byte & 0x80)) break; } + + ok = true; return result; } double MIDIplay::Tick(double s, double granularity) { - //if(CurrentPositionNew.began) + 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)) @@ -316,42 +559,100 @@ double MIDIplay::Tick(double s, double granularity) UpdateVibrato(s); UpdateArpeggio(s); + if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value! + return 0.0; + return CurrentPositionNew.wait; -// if(CurrentPosition.began) -// CurrentPosition.wait -= s; +} -// int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing -// while((CurrentPosition.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) -// { -// //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); -// if(!ProcessEvents()) -// break; -// if(CurrentPosition.wait <= 0.0) -// antiFreezeCounter--; -// } +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; + + bool loopFlagState = m_setup.loopingIsEnabled; + 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(); + 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; -// if(antiFreezeCounter <= 0) -// CurrentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events -// with zero delay are been detected */ + m_setup.loopingIsEnabled = loopFlagState; + m_setup.delay = CurrentPositionNew.wait; + m_setup.carry = 0.0; +} + +double MIDIplay::tell() +{ + return CurrentPositionNew.absTimePosition; +} -// for(uint16_t c = 0; c < opl.NumChannels; ++c) -// ch[c].AddAge(static_cast<int64_t>(s * 1000.0)); +double MIDIplay::timeLength() +{ + return fullSongTimeLength; +} -// UpdateVibrato(s); -// UpdateArpeggio(s); +double MIDIplay::getLoopStart() +{ + return loopStartTime; +} -// return CurrentPosition.wait; +double MIDIplay::getLoopEnd() +{ + return loopEndTime; } void MIDIplay::rewind() { - CurrentPosition = trackBeginPosition; + Panic(); + KillSustainingNotes(-1, -1); + CurrentPositionNew = trackBeginPositionNew; trackStart = true; atEnd = false; loopStart = true; - loopStart_passed = false; invalidLoop = false; - loopStart_hit = false; +} + +void MIDIplay::setTempo(double tempo) +{ + tempoMultiplier = tempo; } void MIDIplay::realTime_ResetState() @@ -562,10 +863,8 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) for(unsigned ccount = 0; ccount < 2; ++ccount) { int32_t c = adlchannel[ccount]; - if(c < 0) continue; - ir.first->second.phys[ static_cast<uint16_t>(adlchannel[ccount]) ] = i[ccount]; } NoteUpdate(channel, ir.first, Upd_All | Upd_Patch); @@ -976,153 +1275,77 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } -bool MIDIplay::ProcessEvents() +bool MIDIplay::ProcessEventsNew(bool isSeek) { - if(TrackData.size() == 0) + 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 = TrackData.size(); - const Position RowBeginPosition(CurrentPosition); - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - if(CurrentPosition.track[tk].status >= 0 - && CurrentPosition.track[tk].delay <= 0) - { - // Handle event - HandleEvent(tk); - - // Read next event time (unless the track just ended) - if(CurrentPosition.track[tk].ptr >= TrackData[tk].size()) - CurrentPosition.track[tk].status = -1; - - if(CurrentPosition.track[tk].status >= 0) - CurrentPosition.track[tk].delay += ReadVarLen(tk); - } - } - - // Find shortest delay from all track - uint64_t shortest = 0; - bool shortest_no = true; - - for(size_t tk = 0; tk < TrackCount; ++tk) - if((CurrentPosition.track[tk].status >= 0) && (shortest_no || CurrentPosition.track[tk].delay < shortest)) - { - shortest = CurrentPosition.track[tk].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) - CurrentPosition.track[tk].delay -= shortest; - - fraction<uint64_t> t = shortest * Tempo; - - if(CurrentPosition.began) - CurrentPosition.wait += t.valuel(); - - //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); - /* - if(CurrentPosition.track[0].ptr > 8119) - loopEnd = true; - // ^HACK: CHRONO TRIGGER LOOP - */ - - if(loopStart_hit && (loopStart || loopEnd)) //Avoid invalid loops - { - invalidLoop = true; - loopStart = false; - loopEnd = false; - LoopBeginPosition = trackBeginPosition; - } - else - loopStart_hit = false; - - if(loopStart) - { - if(trackStart) - { - trackBeginPosition = RowBeginPosition; - trackStart = false; - atEnd = false; - } - LoopBeginPosition = RowBeginPosition; - loopStart = false; - loopStart_hit = true; - } - - if(shortest_no || loopEnd) - { - //Loop if song end or loop end point has reached - loopEnd = false; - shortest = 0; - if(opl._parent->loopingIsEnabled == 0) - { - atEnd = true; //Don't handle events anymore - CurrentPosition.wait += 1.0;//One second delay until stop playing - return true;//We have caugh end here! - } - CurrentPosition = LoopBeginPosition; - } - - return true;//Has events in queue -} - -bool MIDIplay::ProcessEventsNew() -{ - if(TrackData.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 = TrackData.size(); + 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) { - if(CurrentPositionNew.track[tk].status >= 0 - && CurrentPositionNew.track[tk].delay <= 0) + PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; + if((track.status >= 0) && (track.delay <= 0)) { // Handle event - for(size_t i = 0; i < CurrentPositionNew.track[tk].pos->events.size(); i++) + for(size_t i = 0; i < track.pos->events.size(); i++) { - MidiEvent &evt = CurrentPositionNew.track[tk].pos->events[i]; - HandleEvent(tk, evt, CurrentPositionNew.track[tk].status); + 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! } - std::fprintf(stdout, "Time: %f\r", CurrentPositionNew.track[tk].pos->time); - std::fflush(stdout); + #ifdef DEBUG_TIME_CALCULATION + if(maxTime < track.pos->time) + maxTime = track.pos->time; + #endif // Read next event time (unless the track just ended) - if(CurrentPositionNew.track[tk].pos->next == NULL/* >= TrackData[tk].size()*/) - CurrentPositionNew.track[tk].status = -1; + if(track.pos == trackDataNew[tk].end()) + track.status = -1; - if(CurrentPositionNew.track[tk].status >= 0) - CurrentPositionNew.track[tk].delay += CurrentPositionNew.track[tk].pos->delay; - - if(CurrentPositionNew.track[tk].status >= 0) - CurrentPositionNew.track[tk].pos = CurrentPositionNew.track[tk].pos->next; + 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) - if((CurrentPositionNew.track[tk].status >= 0) && (shortest_no || CurrentPositionNew.track[tk].delay < shortest)) + { + PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; + if((track.status >= 0) && (shortest_no || track.delay < shortest)) { - shortest = CurrentPositionNew.track[tk].delay; + shortest = track.delay; shortest_no = false; } + } //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); @@ -1132,37 +1355,17 @@ bool MIDIplay::ProcessEventsNew() fraction<uint64_t> t = shortest * Tempo; - //if(CurrentPositionNew.began) - CurrentPositionNew.wait += t.valuel(); - - //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); - /* - if(CurrentPosition.track[0].ptr > 8119) - loopEnd = true; - // ^HACK: CHRONO TRIGGER LOOP - */ + #ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) + #endif + CurrentPositionNew.wait += t.value(); - if(loopStart_hit && (loopStart || loopEnd)) //Avoid invalid loops - { - invalidLoop = true; - loopStart = false; - loopEnd = false; - LoopBeginPositionNew = trackBeginPositionNew; - } - else - loopStart_hit = false; + //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); if(loopStart) { - if(trackStart) - { - trackBeginPositionNew = RowBeginPosition; - trackStart = false; - atEnd = false; - } LoopBeginPositionNew = RowBeginPosition; loopStart = false; - loopStart_hit = true; } if(shortest_no || loopEnd) @@ -1170,10 +1373,10 @@ bool MIDIplay::ProcessEventsNew() //Loop if song end or loop end point has reached loopEnd = false; shortest = 0; - if(opl._parent->loopingIsEnabled == 0) + if(!m_setup.loopingIsEnabled) { atEnd = true; //Don't handle events anymore - CurrentPositionNew.wait += 1.0;//One second delay until stop playing + CurrentPositionNew.wait += postSongWaitDelay;//One second delay until stop playing return true;//We have caugh end here! } CurrentPositionNew = LoopBeginPositionNew; @@ -1182,24 +1385,45 @@ bool MIDIplay::ProcessEventsNew() return true;//Has events in queue } -MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t**pptr, int &status) +MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t**pptr, uint8_t *end, int &status) { uint8_t *&ptr = *pptr; MIDIplay::MidiEvent evt; + + if(ptr + 1 > end) + { + errorString += "parseEvent: Can't read event type byte - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + unsigned char byte = *(ptr++); + bool ok = false; - if(byte == 0xF7 || byte == 0xF0) // Ignore SysEx + if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx { - uint64_t length = ReadVarLen(pptr); + 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 == 0xFF) + if(byte == MidiEvent::T_SPECIAL) { // Special event FF uint8_t evtype = *(ptr++); - uint64_t length = ReadVarLen(pptr); + 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; @@ -1207,8 +1431,62 @@ MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t**pptr, int &status) evt.subtype = evtype; evt.data.insert(evt.data.begin(), data.begin(), data.end()); + /* 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) + { + //TODO: Implement own field for this + //TODO: Implement API call to retreive this + //TODO: Implement a hook to catch this + std::string str((const char*)evt.data.data(), evt.data.size()); + std::fprintf(stdout, "Copyright: %s\n", str.c_str()); + std::fflush(stdout); + } + else + if(evt.subtype == MidiEvent::ST_SQTRKTITLE) + { + //TODO: Implement API call to retreive this + //TODO: Implement a hook to catch this + if(musTitle.empty()) + musTitle = std::string((const char*)evt.data.data(), evt.data.size()); + } + else + if(evt.subtype == MidiEvent::ST_INSTRTITLE) + { + //TODO: Implement a hook to catch this + std::string str((const char*)evt.data.data(), evt.data.size()); + std::fprintf(stdout, "Instrument: %s\n", str.c_str()); + std::fflush(stdout); + } + 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; + status = -1;//Finalize track return evt; } @@ -1220,313 +1498,151 @@ MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t**pptr, int &status) ptr--; } - if(byte == 0xF3) + //Sys Com Song Select(Song #) [0-127] + if(byte == MidiEvent::T_SYSCOMSNGSEL) { - ptr += 1; - return evt; - } - - if(byte == 0xF2) - { - ptr += 2; + 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; } - uint8_t MidCh = byte & 0x0F, EvType = byte >> 4; - status = byte; - evt.channel = MidCh; - evt.type = EvType; - - switch(EvType) - { - case 0x8: // Note off - case 0x9: // Note on - case 0xA: // Note touch - case 0xB: // Controller change - case 0xE: // Wheel/pitch bend + //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; } - case 0xC: // Patch change - case 0xD: // Channel after-touch - { - evt.data.push_back(*(ptr++)); - return evt; - } - } - - return evt; -} - -void MIDIplay::HandleEvent(size_t tk) -{ - unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++]; - - if(byte == 0xF7 || byte == 0xF0) // Ignore SysEx - { - uint64_t length = ReadVarLen(tk); - //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); - CurrentPosition.track[tk].ptr += (size_t)length; - //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); - return; - } + uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; + status = byte; + evt.channel = midCh; + evt.type = evType; - if(byte == 0xFF) + switch(evType) { - // Special event FF - uint8_t evtype = TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint64_t length = ReadVarLen(tk); - std::string data(length ? (const char *) &TrackData[tk][CurrentPosition.track[tk].ptr] : 0, (size_t)length); - CurrentPosition.track[tk].ptr += (size_t)length; - - if(evtype == 0x2F)//End Of Track - { - CurrentPosition.track[tk].status = -1; - return; - } - - if(evtype == 0x51)//Tempo change + 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) { - Tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(data.data(), data.size())); - return; + errorString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; } - if(evtype == 6)//Meta event - { - //Turn on/off Loop handling when loop is disabled - if(opl._parent->loopingIsEnabled != 0) - { - /* Move this away from events handler */ - 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") && (!invalidLoop)) - { - loopStart = true; - loopStart_passed = true; - } - - if((data == "loopend") && (!invalidLoop)) - { - if((loopStart_passed) && (!loopStart)) - loopEnd = true; - else - invalidLoop = true; - } - } - } - - if(evtype == 9) - current_device[tk] = ChooseDevice(data); - - //if(evtype >= 1 && evtype <= 6) - // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); - if(evtype == 0xE3) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + //111'th loopStart controller (RPG Maker and others) + if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) { - 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.PokeN(0, i, v); + //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; - } - - // Any normal event (80..EF) - if(byte < 0x80) - { - byte = static_cast<uint8_t>(CurrentPosition.track[tk].status | 0x80); - CurrentPosition.track[tk].ptr--; - } - - if(byte == 0xF3) - { - CurrentPosition.track[tk].ptr += 1; - return; - } - - if(byte == 0xF2) - { - CurrentPosition.track[tk].ptr += 2; - 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 = byte & 0x0F, EvType = byte >> 4; - MidCh += (uint8_t)current_device[tk]; - CurrentPosition.track[tk].status = byte; - - switch(EvType) - { - case 0x8: // Note off - { - uint8_t note = TrackData[tk][CurrentPosition.track[tk].ptr++]; - /*uint8_t vol=*/TrackData[tk][CurrentPosition.track[tk].ptr++]; - //if(MidCh != 9) note -= 12; // HACK - realTime_NoteOff(MidCh, note); - break; - } - case 0x9: // Note on - { - uint8_t note = TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t vol = TrackData[tk][CurrentPosition.track[tk].ptr++]; - //if(MidCh != 9) note -= 12; // HACK - if(realTime_NoteOn(MidCh, note, vol)) - CurrentPosition.began = true; - break; - } - - case 0xA: // Note touch - { - uint8_t note = TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t vol = TrackData[tk][CurrentPosition.track[tk].ptr++]; - realTime_NoteAfterTouch(MidCh, note, vol); - break; - } - - case 0xB: // Controller change - { - uint8_t ctrlno = TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t value = TrackData[tk][CurrentPosition.track[tk].ptr++]; - - if((opl._parent->loopingIsEnabled != 0) && (ctrlno == 111) && !invalidLoop) + return evt; + case MidiEvent::T_PATCHCHANGE://1 byte length + case MidiEvent::T_CHANAFTTOUCH: + if(ptr + 1 > end) { - loopStart = true; - loopStart_passed = true; - break; + errorString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; } - - realTime_Controller(MidCh, ctrlno, value); - break; - } - - case 0xC: // Patch change - realTime_PatchChange(MidCh, TrackData[tk][CurrentPosition.track[tk].ptr++]); - break; - - case 0xD: // Channel after-touch - { - // TODO: Verify, is this correct action? - uint8_t vol = TrackData[tk][CurrentPosition.track[tk].ptr++]; - realTime_ChannelAfterTouch(MidCh, vol); - break; + evt.data.push_back(*(ptr++)); + return evt; } - case 0xE: // Wheel/pitch bend - { - uint8_t a = TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t b = TrackData[tk][CurrentPosition.track[tk].ptr++]; - realTime_PitchBend(MidCh, b, a); - break; - } - } + return evt; } + void MIDIplay::HandleEvent(size_t tk, MIDIplay::MidiEvent &evt, int &status) { - if(evt.type == 0xF7 || evt.type == 0xF0) // Ignore SysEx + 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 == 0xFF) + 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 == 0x2F)//End Of Track + if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track { status = -1; return; } - if(evtype == 0x51)//Tempo change + if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change { Tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(evt.data.data(), evt.data.size())); return; } - if(evtype == 6)//Meta event + if(evtype == MidiEvent::ST_MARKER)//Meta event { - //Turn on/off Loop handling when loop is disabled - if(opl._parent->loopingIsEnabled != 0) - { - /* Move this away from events handler */ - 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") && (!invalidLoop)) - { - loopStart = true; - loopStart_passed = true; - } - - if((data == "loopend") && (!invalidLoop)) - { - if((loopStart_passed) && (!loopStart)) - loopEnd = true; - else - invalidLoop = true; - } - } + //Do nothing! :-P + return; } - if(evtype == 9) + 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()); - if(evtype == 0xE1) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + //Turn on Loop handling when loop is enabled + if(m_setup.loopingIsEnabled && !invalidLoop) { - if(!invalidLoop) + if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib { loopStart = true; - loopStart_passed = true; + return; } - } - if(evtype == 0xE2) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - if(!invalidLoop) + if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib { - if((loopStart_passed) && (!loopStart)) - loopEnd = true; - else - invalidLoop = true; + loopEnd = true; + return; } } - if(evtype == 0xE3) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + 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.PokeN(0, i, v); + return; } return; @@ -1539,86 +1655,68 @@ void MIDIplay::HandleEvent(size_t tk, MIDIplay::MidiEvent &evt, int &status) // CurrentPosition.track[tk].ptr--; // } - if(evt.type == 0xF3) - { - //CurrentPosition.track[tk].ptr += 1; - return; - } - - if(evt.type == 0xF2) - { - //CurrentPosition.track[tk].ptr += 2; + 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]; + uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; + midCh += (uint8_t)current_device[tk]; status = evt.type; switch(evt.type) { - case 0x8: // Note off + case MidiEvent::T_NOTEOFF: // Note off { uint8_t note = evt.data[0]; - /*uint8_t vol=*/ //TrackData[tk][CurrentPosition.track[tk].ptr++]; - //if(MidCh != 9) note -= 12; // HACK - realTime_NoteOff(MidCh, note); + realTime_NoteOff(midCh, note); break; } - case 0x9: // Note on + + case MidiEvent::T_NOTEON: // Note on { - uint8_t note = evt.data[0];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t vol = evt.data[1];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - //if(MidCh != 9) note -= 12; // HACK - if(realTime_NoteOn(MidCh, note, vol)) - CurrentPosition.began = true; + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + /*if(*/ realTime_NoteOn(midCh, note, vol); /*)*/ + //CurrentPosition.began = true; break; } - case 0xA: // Note touch + case MidiEvent::T_NOTETOUCH: // Note touch { - uint8_t note = evt.data[0];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t vol = evt.data[1];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - realTime_NoteAfterTouch(MidCh, note, vol); + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + realTime_NoteAfterTouch(midCh, note, vol); break; } - case 0xB: // Controller change + case MidiEvent::T_CTRLCHANGE: // Controller change { - uint8_t ctrlno = evt.data[0];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t value = evt.data[1];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - - if((opl._parent->loopingIsEnabled != 0) && (ctrlno == 111) && !invalidLoop) - { - loopStart = true; - loopStart_passed = true; - break; - } - - realTime_Controller(MidCh, ctrlno, value); + uint8_t ctrlno = evt.data[0]; + uint8_t value = evt.data[1]; + realTime_Controller(midCh, ctrlno, value); break; } - case 0xC: // Patch change - realTime_PatchChange(MidCh, evt.data[0] /*TrackData[tk][CurrentPosition.track[tk].ptr++]*/); + case MidiEvent::T_PATCHCHANGE: // Patch change + realTime_PatchChange(midCh, evt.data[0]); break; - case 0xD: // Channel after-touch + case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch { // TODO: Verify, is this correct action? - uint8_t vol = evt.data[0];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - realTime_ChannelAfterTouch(MidCh, vol); + uint8_t vol = evt.data[0]; + realTime_ChannelAfterTouch(midCh, vol); break; } - case 0xE: // Wheel/pitch bend + case MidiEvent::T_WHEEL: // Wheel/pitch bend { - uint8_t a = evt.data[0];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - uint8_t b = evt.data[1];//TrackData[tk][CurrentPosition.track[tk].ptr++]; - realTime_PitchBend(MidCh, b, a); + uint8_t a = evt.data[0]; + uint8_t b = evt.data[1]; + realTime_PitchBend(midCh, b, a); break; } } @@ -1688,11 +1786,8 @@ long MIDIplay::CalculateAdlChannelGoodness(unsigned c, uint16_t ins, uint16_t) c ++m) { if(m->second.sustained) continue; - if(m->second.vibdelay >= 200) continue; - if(m->second.ins != j->second.ins) continue; - n_evacuation_stations += 1; } } @@ -1813,6 +1908,15 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator static_cast<int32_t>(from_channel)); } +void MIDIplay::Panic() +{ + for(uint8_t chan = 0; chan < Ch.size(); chan++) + { + for(uint8_t note = 0; note < 128; note++) + realTime_NoteOff(chan, note); + } +} + void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn) { uint32_t first = 0, last = opl.NumChannels; |