diff options
author | Vitaly Novichkov <admin@wohlnet.ru> | 2018-08-06 01:56:50 +0300 |
---|---|---|
committer | Vitaly Novichkov <admin@wohlnet.ru> | 2018-08-06 01:56:50 +0300 |
commit | 1ab34e88a326c396fbb42c503eb4ffa56fa0a148 (patch) | |
tree | 4f2d6ada0ab0bd1d64947a71c5492a29be7c8dbf /src/midi_sequencer_impl.hpp | |
parent | 0a003c8bc12514a5586f2f0e40559fcbf6607883 (diff) | |
parent | 0e2807a9d4c8c900e85c9b33ba96c69a82c58a68 (diff) | |
download | libADLMIDI-1ab34e88a326c396fbb42c503eb4ffa56fa0a148.tar.gz libADLMIDI-1ab34e88a326c396fbb42c503eb4ffa56fa0a148.tar.bz2 libADLMIDI-1ab34e88a326c396fbb42c503eb4ffa56fa0a148.zip |
Merge branch 'master' into stable
Diffstat (limited to 'src/midi_sequencer_impl.hpp')
-rw-r--r-- | src/midi_sequencer_impl.hpp | 1372 |
1 files changed, 1046 insertions, 326 deletions
diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index ce70c3e..0fe6192 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -173,8 +173,15 @@ void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) controllers.reserve(events.size()); controllers.push_back(events[i]); } - else if((events[i].type == MidiEvent::T_SPECIAL) - && ((events[i].subtype == MidiEvent::ST_MARKER) || (events[i].subtype == MidiEvent::ST_DEVICESWITCH))) + else if((events[i].type == MidiEvent::T_SPECIAL) && ( + (events[i].subtype == MidiEvent::ST_MARKER) || + (events[i].subtype == MidiEvent::ST_DEVICESWITCH) || + (events[i].subtype == MidiEvent::ST_LOOPSTART) || + (events[i].subtype == MidiEvent::ST_LOOPEND) || + (events[i].subtype == MidiEvent::ST_LOOPSTACK_BEGIN) || + (events[i].subtype == MidiEvent::ST_LOOPSTACK_END) || + (events[i].subtype == MidiEvent::ST_LOOPSTACK_BREAK) + )) { if(metas.capacity() == 0) metas.reserve(events.size()); @@ -262,6 +269,7 @@ void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) BW_MidiSequencer::BW_MidiSequencer() : m_interface(NULL), m_format(Format_MIDI), + m_smfFormat(0), m_loopEnabled(false), m_fullSongTimeLength(0.0), m_postSongWaitDelay(1.0), @@ -269,10 +277,13 @@ BW_MidiSequencer::BW_MidiSequencer() : m_loopEndTime(-1.0), m_tempoMultiplier(1.0), m_atEnd(false), - m_loopStart(false), - m_loopEnd(false), - m_invalidLoop(false) -{} + m_trackSolo(~(size_t)0), + m_triggerHandler(NULL), + m_triggerUserData(NULL) +{ + m_loop.reset(); + m_loop.invalidLoop = false; +} BW_MidiSequencer::~BW_MidiSequencer() {} @@ -307,6 +318,31 @@ BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat() return m_format; } +size_t BW_MidiSequencer::getTrackCount() const +{ + return m_trackData.size(); +} + +bool BW_MidiSequencer::setTrackEnabled(size_t track, bool enable) +{ + size_t trackCount = m_trackData.size(); + if(track >= trackCount) + return false; + m_trackDisable[track] = !enable; + return true; +} + +void BW_MidiSequencer::setSoloTrack(size_t track) +{ + m_trackSolo = track; +} + +void BW_MidiSequencer::setTriggerHandler(TriggerHandler handler, void *userData) +{ + m_triggerHandler = handler; + m_triggerUserData = userData; +} + const std::vector<BW_MidiSequencer::CmfInstrument> BW_MidiSequencer::getRawCmfInstruments() { return m_cmfInstruments; @@ -357,21 +393,39 @@ double BW_MidiSequencer::getTempoMultiplier() return m_tempoMultiplier; } -bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > &trackData) + +void BW_MidiSequencer::buildSmfSetupReset(size_t trackCount) { m_fullSongTimeLength = 0.0; m_loopStartTime = -1.0; m_loopEndTime = -1.0; + m_trackDisable.clear(); + m_trackSolo = ~(size_t)0; m_musTitle.clear(); m_musCopyright.clear(); m_musTrackTitles.clear(); m_musMarkers.clear(); m_trackData.clear(); - const size_t trackCount = trackData.size(); m_trackData.resize(trackCount, MidiTrackQueue()); + m_trackDisable.resize(trackCount); + + m_loop.reset(); + m_loop.invalidLoop = false; + + m_currentPosition.track.clear(); + m_currentPosition.track.resize(trackCount); +} + +bool BW_MidiSequencer::buildSmfTrackData(const std::vector<std::vector<uint8_t> > &trackData) +{ + const size_t trackCount = trackData.size(); + buildSmfSetupReset(trackCount); + + bool gotGlobalLoopStart = false, + gotGlobalLoopEnd = false, + gotStackLoopStart = false, + gotLoopEventInThisRow = false; - m_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 @@ -381,17 +435,14 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & //! Cache for error message strign char error[150]; - m_currentPosition.track.clear(); - m_currentPosition.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; + //! Tempo change events list + std::vector<MidiEvent> temposList; /* * TODO: Make this be safer for memory in case of broken input data @@ -458,26 +509,26 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & if(event.subtype == MidiEvent::ST_TEMPOCHANGE) { event.absPosition = abs_position; - tempos.push_back(event); + temposList.push_back(event); } - else if(!m_invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART)) + else if(!m_loop.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) - m_invalidLoop = true; + if(gotGlobalLoopStart || gotLoopEventInThisRow) + m_loop.invalidLoop = true; else { - gotLoopStart = true; + gotGlobalLoopStart = true; loopStartTicks = abs_position; } //In this row we got loop event, register this! gotLoopEventInThisRow = true; } - else if(!m_invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) + else if(!m_loop.invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) { /* * loopEnd is invalid when: @@ -485,16 +536,71 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & * - starts together with loopStart * - appars more than one time in same MIDI file */ - if(gotLoopEnd || gotLoopEventInThisRow) - m_invalidLoop = true; + if(gotGlobalLoopEnd || gotLoopEventInThisRow) + { + m_loop.invalidLoop = true; + if(m_interface->onDebugMessage) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "== Invalid loop detected! %s %s ==", + (gotGlobalLoopEnd ? "[Caught more than 1 loopEnd!]" : ""), + (gotLoopEventInThisRow ? "[loopEnd in same row as loopStart!]" : "") + ); + } + } else { - gotLoopEnd = true; + gotGlobalLoopEnd = true; loopEndTicks = abs_position; } - //In this row we got loop event, register this! + // In this row we got loop event, register this! gotLoopEventInThisRow = true; } + else if(!m_loop.invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTACK_BEGIN)) + { + if(!gotStackLoopStart) + { + if(!gotGlobalLoopStart) + loopStartTicks = abs_position; + gotStackLoopStart = true; + } + + m_loop.stackUp(); + if(m_loop.stackLevel >= static_cast<int>(m_loop.stack.size())) + { + LoopStackEntry e; + e.loops = event.data[0]; + e.infinity = (event.data[0] == 0); + e.start = abs_position; + e.end = abs_position; + m_loop.stack.push_back(e); + } + } + else if(!m_loop.invalidLoop && + ((event.subtype == MidiEvent::ST_LOOPSTACK_END) || + (event.subtype == MidiEvent::ST_LOOPSTACK_BREAK)) + ) + { + if(m_loop.stackLevel <= -1) + { + m_loop.invalidLoop = true; // Caught loop end without of loop start! + if(m_interface->onDebugMessage) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "== Invalid loop detected! [Caught loop end without of loop start] ==" + ); + } + } + else + { + if(loopEndTicks < abs_position) + loopEndTicks = abs_position; + m_loop.getCurStack().end = abs_position; + m_loop.stackDown(); + } + } } if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! @@ -527,16 +633,35 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & m_currentPosition.track[tk].pos = m_trackData[tk].begin(); } - if(gotLoopStart && !gotLoopEnd) + if(gotGlobalLoopStart && !gotGlobalLoopEnd) { - gotLoopEnd = true; + gotGlobalLoopEnd = true; loopEndTicks = ticksSongLength; } //loopStart must be located before loopEnd! if(loopStartTicks >= loopEndTicks) - m_invalidLoop = true; + { + m_loop.invalidLoop = true; + if(m_interface->onDebugMessage && (gotGlobalLoopStart || gotGlobalLoopEnd)) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "== Invalid loop detected! [loopEnd is going before loopStart] ==" + ); + } + } + + buildTimeLine(temposList, loopStartTicks, loopEndTicks); + + return true; +} +void BW_MidiSequencer::buildTimeLine(const std::vector<MidiEvent> &tempos, + uint64_t loopStartTicks, + uint64_t loopEndTicks) +{ + const size_t trackCount = m_trackData.size(); /********************************************************************************/ //Calculate time basing on collected tempo events /********************************************************************************/ @@ -580,7 +705,7 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & do { TempoChangePoint tempoMarker; - MidiEvent &tempoPoint = tempos[tempo_change_index]; + const MidiEvent &tempoPoint = tempos[tempo_change_index]; tempoMarker.absPos = tempoPoint.absPosition; tempoMarker.tempo = m_invDeltaTicks * fraction<uint64_t>(readBEint(tempoPoint.data.data(), tempoPoint.data.size())); points.push_back(tempoMarker); @@ -642,7 +767,7 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & } //Capture loop points time positions - if(!m_invalidLoop) + if(!m_loop.invalidLoop) { // Set loop points times if(loopStartTicks == pos.absPos) @@ -669,6 +794,8 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & m_trackBeginPosition = m_currentPosition; //Initial loop position will begin at begin of track until passing of the loop point m_loopBeginPosition = m_currentPosition; + //Set lowest level of the loop stack + m_loop.stackLevel = -1; /********************************************************************************/ //Resolve "hell of all times" of too short drum notes: @@ -788,7 +915,6 @@ bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > & } #endif - return true; } bool BW_MidiSequencer::processEvents(bool isSeek) @@ -798,9 +924,14 @@ bool BW_MidiSequencer::processEvents(bool isSeek) if(m_atEnd) return false;//No more events in the queue - m_loopEnd = false; + m_loop.caughtEnd = false; const size_t TrackCount = m_currentPosition.track.size(); - const Position RowBeginPosition(m_currentPosition); + const Position rowBeginPosition(m_currentPosition); + bool doLoopJump = false; + unsigned caughLoopStart = 0; + unsigned caughLoopStackStart = 0; + unsigned caughLoopStackEnds = 0; + unsigned caughLoopStackBreaks = 0; #ifdef DEBUG_TIME_CALCULATION double maxTime = 0.0; @@ -823,14 +954,41 @@ bool BW_MidiSequencer::processEvents(bool isSeek) { const MidiEvent &evt = track.pos->events[i]; #ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) - CurrentPositionNew.began = true; + if(!m_currentPosition.began && (evt.type == MidiEvent::T_NOTEON)) + m_currentPosition.began = true; #endif if(isSeek && (evt.type == MidiEvent::T_NOTEON)) continue; handleEvent(tk, evt, track.lastHandledEvent); - if(m_loopEnd) + + if(m_loop.caughtStart) + { + caughLoopStart++; + m_loop.caughtStart = false; + } + + if(m_loop.caughtStackStart) + { + caughLoopStackStart++; + m_loop.caughtStackStart = false; + } + + if(m_loop.caughtStackBreak) + { + caughLoopStackBreaks++; + m_loop.caughtStackBreak = false; + } + + if(m_loop.caughtEnd || m_loop.isStackEnd()) + { + if(m_loop.caughtStackEnd) + { + m_loop.caughtStackEnd = false; + caughLoopStackEnds++; + } + doLoopJump = true; break;//Stop event handling on catching loopEnd event! + } } #ifdef DEBUG_TIME_CALCULATION @@ -843,6 +1001,9 @@ bool BW_MidiSequencer::processEvents(bool isSeek) track.delay += track.pos->delay; track.pos++; } + + if(doLoopJump) + break; } } @@ -875,21 +1036,81 @@ bool BW_MidiSequencer::processEvents(bool isSeek) fraction<uint64_t> t = shortest * m_tempo; #ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) + if(m_currentPosition.began) #endif m_currentPosition.wait += t.value(); //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); - if(m_loopStart) + if(caughLoopStart > 0) + m_loopBeginPosition = rowBeginPosition; + + if(caughLoopStackStart > 0) + { + while(caughLoopStackStart > 0) + { + m_loop.stackUp(); + LoopStackEntry &s = m_loop.getCurStack(); + s.startPosition = rowBeginPosition; + caughLoopStackStart--; + } + return true; + } + + if(caughLoopStackBreaks > 0) { - m_loopBeginPosition = RowBeginPosition; - m_loopStart = false; + while(caughLoopStackBreaks > 0) + { + LoopStackEntry &s = m_loop.getCurStack(); + s.loops = 0; + s.infinity = false; + // Quit the loop + m_loop.stackDown(); + caughLoopStackBreaks--; + } } - if(shortest_no || m_loopEnd) + if(caughLoopStackEnds > 0) + { + while(caughLoopStackEnds > 0) + { + LoopStackEntry &s = m_loop.getCurStack(); + if(s.infinity) + { + m_currentPosition = s.startPosition; + m_loop.skipStackStart = true; + return true; + } + else + if(s.loops >= 0) + { + s.loops--; + if(s.loops > 0) + { + m_currentPosition = s.startPosition; + m_loop.skipStackStart = true; + return true; + } + else + { + // Quit the loop + m_loop.stackDown(); + } + } + else + { + // Quit the loop + m_loop.stackDown(); + } + caughLoopStackEnds--; + } + + return true; + } + + if(shortest_no || m_loop.caughtEnd) { //Loop if song end or loop end point has reached - m_loopEnd = false; + m_loop.caughtEnd = false; shortest = 0; if(!m_loopEnabled) { @@ -1026,6 +1247,46 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c evt.data.clear();//Data is not needed return evt; } + + if(data.substr(0, 10) == "loopstart=") + { + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_LOOPSTACK_BEGIN; + uint8_t loops = static_cast<uint8_t>(atoi(data.substr(10).c_str())); + evt.data.clear(); + evt.data.push_back(loops); + + if(m_interface->onDebugMessage) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "Stack Marker Loop Start at %d to %d level with %d loops", + m_loop.stackLevel, + m_loop.stackLevel + 1, + loops + ); + } + return evt; + } + + if(data.substr(0, 8) == "loopend=") + { + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_LOOPSTACK_END; + evt.data.clear(); + + if(m_interface->onDebugMessage) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "Stack Marker Loop %s at %d to %d level", + (evt.subtype == MidiEvent::ST_LOOPSTACK_END ? "End" : "Break"), + m_loop.stackLevel, + m_loop.stackLevel - 1 + ); + } + return evt; + } } if(evtype == MidiEvent::ST_ENDTRACK) @@ -1095,13 +1356,67 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c 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)) + } + else + if(evType == MidiEvent::T_CTRLCHANGE) { - //Change event type to custom Loop Start event and clear data - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear(); + //111'th loopStart controller (RPG Maker and others) + if((m_format == Format_MIDI) && (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(); + } + + if(m_format == Format_XMIDI) + { + switch(evt.data[0]) + { + case 116: // For Loop Controller + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_LOOPSTACK_BEGIN; + evt.data[0] = evt.data[1]; + evt.data.pop_back(); + + if(m_interface->onDebugMessage) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "Stack XMI Loop Start at %d to %d level with %d loops", + m_loop.stackLevel, + m_loop.stackLevel + 1, + evt.data[0] + ); + } + break; + + case 117: // Next/Break Loop Controller + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = evt.data[1] < 64 ? + MidiEvent::ST_LOOPSTACK_BREAK : + MidiEvent::ST_LOOPSTACK_END; + evt.data.clear(); + + if(m_interface->onDebugMessage) + { + m_interface->onDebugMessage( + m_interface->onDebugMessage_userData, + "Stack XMI Loop %s at %d to %d level", + (evt.subtype == MidiEvent::ST_LOOPSTACK_END ? "End" : "Break"), + m_loop.stackLevel, + m_loop.stackLevel - 1 + ); + } + break; + + case 119: // Callback Trigger + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_CALLBACK_TRIGGER; + evt.data.assign(1, evt.data[1]); + break; + } + } } return evt; @@ -1122,6 +1437,20 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEvent &evt, int32_t &status) { + if(track == 0 && m_smfFormat < 2 && evt.type == MidiEvent::T_SPECIAL && + (evt.subtype == MidiEvent::ST_TEMPOCHANGE || evt.subtype == MidiEvent::ST_TIMESIGNATURE)) + { + /* never reject track 0 timing events on SMF format != 2 + note: multi-track XMI convert to format 2 SMF */ + } + else + { + if(m_trackSolo != ~(size_t)0 && track != m_trackSolo) + return; + if(m_trackDisable[track]) + return; + } + if(m_interface->onEvent) { m_interface->onEvent(m_interface->onEvent_userData, @@ -1148,7 +1477,7 @@ void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEve // 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); + const char *data(length ? (const char *)evt.data.data() : ""); if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track { @@ -1171,9 +1500,9 @@ void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEve if(evtype == MidiEvent::ST_DEVICESWITCH) { if(m_interface->onDebugMessage) - m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Switching another device: %s", data.c_str()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Switching another device: %s", data); if(m_interface->rt_deviceSwitch) - m_interface->rt_deviceSwitch(m_interface->rtUserData, track, data.c_str(), data.size()); + m_interface->rt_deviceSwitch(m_interface->rtUserData, track, data, length); return; } @@ -1181,21 +1510,58 @@ void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEve // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); //Turn on Loop handling when loop is enabled - if(m_loopEnabled && !m_invalidLoop) + if(m_loopEnabled && !m_loop.invalidLoop) { if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec MIDI loop Start point { - m_loopStart = true; + m_loop.caughtStart = true; return; } if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec MIDI loop End point { - m_loopEnd = true; + m_loop.caughtEnd = true; + return; + } + + if(evtype == MidiEvent::ST_LOOPSTACK_BEGIN) + { + if(m_loop.skipStackStart) + { + m_loop.skipStackStart = false; + return; + } + LoopStackEntry &s = m_loop.stack[(m_loop.stackLevel + 1)]; + s.loops = (int)data[0]; + s.infinity = (data[0] == 0); + m_loop.caughtStackStart = true; + return; + } + + if(evtype == MidiEvent::ST_LOOPSTACK_END) + { + m_loop.caughtStackEnd = true; + return; + } + + if(evtype == MidiEvent::ST_LOOPSTACK_BREAK) + { + m_loop.caughtStackBreak = true; return; } } + if(evtype == MidiEvent::ST_CALLBACK_TRIGGER) + { +#if 0 /* Print all callback triggers events */ + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Callback Trigger: %02X", evt.data[0]); +#endif + if(m_triggerHandler) + m_triggerHandler(m_triggerUserData, data[0], track); + return; + } + if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib { if(m_interface->rt_rawOPL) @@ -1346,7 +1712,7 @@ double BW_MidiSequencer::seek(double seconds, const double granularity) * * TODO: Detect & set loopStart position on load time to don't break loop while seeking */ - m_loopStart = false; + m_loop.caughtStart = false; while((m_currentPosition.absTimePosition < seconds) && (m_currentPosition.absTimePosition < m_fullSongTimeLength)) @@ -1404,9 +1770,10 @@ double BW_MidiSequencer::getLoopEnd() void BW_MidiSequencer::rewind() { m_currentPosition = m_trackBeginPosition; - m_atEnd = false; - m_loopStart = true; - m_loopEnd = false; + m_atEnd = false; + + m_loop.reset(); + m_loop.caughtStart = true; } void BW_MidiSequencer::setTempo(double tempo) @@ -1451,13 +1818,69 @@ public: } }; +/** + * @brief Detect the EA-MUS file format + * @param head Header part + * @param fr Context with opened file data + * @return true if given file was identified as EA-MUS + */ +static bool detectRSXX(const char *head, FileAndMemReader &fr) +{ + char headerBuf[7] = ""; + bool ret = false; + + // Try to identify RSXX format + if(head[0] == 0x7D) + { + fr.seek(0x6D, FileAndMemReader::SET); + fr.read(headerBuf, 1, 6); + if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) + ret = true; + } + + fr.seek(0, FileAndMemReader::SET); + return ret; +} + +/** + * @brief Detect the Id-software Music File format + * @param head Header part + * @param fr Context with opened file data + * @return true if given file was identified as IMF + */ +static bool detectIMF(const char *head, FileAndMemReader &fr) +{ + uint8_t raw[4]; + size_t end = static_cast<size_t>(head[0]) + 256 * static_cast<size_t>(head[1]); + + if(!end || (end & 3)) + return false; + + size_t backup_pos = fr.tell(); + int64_t sum1 = 0, sum2 = 0; + fr.seek(2, FileAndMemReader::SET); + + for(unsigned n = 0; n < 42; ++n) + { + if(fr.read(raw, 1, 4) != 4) + break; + int64_t value1 = raw[0]; + value1 += raw[1] << 8; + sum1 += value1; + int64_t value2 = raw[2]; + value2 += raw[3] << 8; + sum2 += value2; + } + + fr.seek(static_cast<long>(backup_pos), FileAndMemReader::SET); + + return (sum1 > sum2); +} + bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr) { size_t fsize; BW_MidiSequencer_UNUSED(fsize); - std::vector<std::vector<uint8_t> > rawTrackData; - //! Temp buffer for conversion - BufferGuard<uint8_t> cvt_buf; m_parsingErrorsString.clear(); assert(m_interface);// MIDI output interface must be defined! @@ -1472,21 +1895,15 @@ bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr) } m_atEnd = false; - m_loopStart = true; - m_invalidLoop = false; + m_loop.fullReset(); + m_loop.caughtStart = true; m_format = Format_MIDI; - - bool is_GMF = false; // GMD/MUS files (ScummVM) - bool is_IMF = false; // IMF - bool is_CMF = false; // Creative Music format (CMF/CTMF) - bool is_RSXX = false; // RSXX, such as Cartooners + m_smfFormat = 0; const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 char headerBuf[headerSize] = ""; - size_t DeltaTicks = 192, TrackCount = 1; -riffskip: fsize = fr.read(headerBuf, 1, headerSize); if(fsize < headerSize) { @@ -1494,239 +1911,449 @@ riffskip: return false; } + + if(std::memcmp(headerBuf, "MThd\0\0\0\6", 8) == 0) + { + fr.seek(0, FileAndMemReader::SET); + return parseSMF(fr); + } + if(std::memcmp(headerBuf, "RIFF", 4) == 0) { - fr.seek(6l, FileAndMemReader::CUR); - goto riffskip; + fr.seek(0, FileAndMemReader::SET); + return parseRMI(fr); } if(std::memcmp(headerBuf, "GMF\x1", 4) == 0) { - // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast<long>(headerSize), FileAndMemReader::CUR); - is_GMF = true; + fr.seek(0, FileAndMemReader::SET); + return parseGMF(fr); } + #ifndef BWMIDI_DISABLE_MUS_SUPPORT - else if(std::memcmp(headerBuf, "MUS\x1A", 4) == 0) + if(std::memcmp(headerBuf, "MUS\x1A", 4) == 0) { - // MUS/DMX files (Doom) - size_t mus_len = fr.fileSize(); fr.seek(0, FileAndMemReader::SET); - uint8_t *mus = (uint8_t *)malloc(mus_len); - if(!mus) - { - m_errorString = "Out of memory!"; - return false; - } - fsize = fr.read(mus, 1, mus_len); - if(fsize < mus_len) - { - fr.close(); - m_errorString = "Failed to read MUS file data!\n"; - return false; - } + return parseMUS(fr); + } +#endif - //Close source stream - fr.close(); +#ifndef BWMIDI_DISABLE_XMI_SUPPORT + if((std::memcmp(headerBuf, "FORM", 4) == 0) && (std::memcmp(headerBuf + 8, "XDIR", 4) == 0)) + { + fr.seek(0, FileAndMemReader::SET); + return parseXMI(fr); + } +#endif - uint8_t *mid = NULL; - uint32_t mid_len = 0; - int m2mret = Convert_mus2midi(mus, static_cast<uint32_t>(mus_len), - &mid, &mid_len, 0); - if(mus) - free(mus); - if(m2mret < 0) - { - m_errorString = "Invalid MUS/DMX data format!"; - return false; - } - cvt_buf.set(mid); - //Open converted MIDI file - fr.openData(mid, static_cast<size_t>(mid_len)); - //Re-Read header again! - goto riffskip; + if(std::memcmp(headerBuf, "CTMF", 4) == 0) + { + fr.seek(0, FileAndMemReader::SET); + return parseCMF(fr); } -#endif //BWMIDI_DISABLE_MUS_SUPPORT -#ifndef BWMIDI_DISABLE_XMI_SUPPORT - else if(std::memcmp(headerBuf, "FORM", 4) == 0) + if(detectIMF(headerBuf, fr)) + { + fr.seek(0, FileAndMemReader::SET); + return parseIMF(fr); + } + + if(detectRSXX(headerBuf, fr)) + { + fr.seek(0, FileAndMemReader::SET); + return parseRSXX(fr); + } + + m_errorString = "Unknown or unsupported file format"; + return false; +} + + +bool BW_MidiSequencer::parseIMF(FileAndMemReader &fr) +{ + const size_t deltaTicks = 1; + const size_t trackCount = 1; + const uint32_t imfTempo = 1428; + size_t imfEnd = 0; + uint64_t abs_position = 0; + uint8_t imfRaw[4]; + + MidiTrackRow evtPos; + MidiEvent event; + + std::vector<MidiEvent> temposList; + + m_format = Format_IMF; + + buildSmfSetupReset(trackCount); + + m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(deltaTicks)); + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(deltaTicks) * 2); + + fr.seek(0, FileAndMemReader::SET); + if(fr.read(imfRaw, 1, 2) != 2) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + imfEnd = static_cast<size_t>(imfRaw[0]) + 256 * static_cast<size_t>(imfRaw[1]); + + // Define the playing tempo + event.type = MidiEvent::T_SPECIAL; + event.subtype = MidiEvent::ST_TEMPOCHANGE; + event.absPosition = 0; + event.data.resize(4); + event.data[0] = static_cast<uint8_t>((imfTempo >> 24) & 0xFF); + event.data[1] = static_cast<uint8_t>((imfTempo >> 16) & 0xFF); + event.data[2] = static_cast<uint8_t>((imfTempo >> 8) & 0xFF); + event.data[3] = static_cast<uint8_t>((imfTempo & 0xFF)); + evtPos.events.push_back(event); + temposList.push_back(event); + + // Define the draft for IMF events + event.type = MidiEvent::T_SPECIAL; + event.subtype = MidiEvent::ST_RAWOPL; + event.absPosition = 0; + event.data.resize(2); + + fr.seek(2, FileAndMemReader::SET); + while(fr.tell() < imfEnd && !fr.eof()) { - if(std::memcmp(headerBuf + 8, "XDIR", 4) != 0) + if(fr.read(imfRaw, 1, 4) != 4) + break; + + event.data[0] = imfRaw[0]; // port index + event.data[1] = imfRaw[1]; // port value + event.absPosition = abs_position; + event.isValid = true; + + evtPos.events.push_back(event); + evtPos.delay = static_cast<uint64_t>(imfRaw[2]) + 256 * static_cast<uint64_t>(imfRaw[3]); + + if(evtPos.delay > 0) { - fr.close(); - m_errorString = fr.fileName() + ": Invalid format\n"; - return false; + evtPos.absPos = abs_position; + abs_position += evtPos.delay; + m_trackData[0].push_back(evtPos); + evtPos.clear(); } + } - size_t mus_len = fr.fileSize(); - fr.seek(0, FileAndMemReader::SET); + if(m_trackData[0].size() > 0) + m_currentPosition.track[0].pos = m_trackData[0].begin(); + + buildTimeLine(temposList); + + return true; +} - uint8_t *mus = (uint8_t*)malloc(mus_len); - if(!mus) +bool BW_MidiSequencer::parseRSXX(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, trackCount = 1; + std::vector<std::vector<uint8_t> > rawTrackData; + + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + // Try to identify RSXX format + if(headerBuf[0] == 0x7D) + { + fr.seek(0x6D, FileAndMemReader::SET); + fr.read(headerBuf, 1, 6); + if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) { - m_errorString = "Out of memory!"; - return false; + m_format = Format_RSXX; + fr.seek(0x7D, FileAndMemReader::SET); + trackCount = 1; + deltaTicks = 60; } - fsize = fr.read(mus, 1, mus_len); - if(fsize < mus_len) + else { - fr.close(); - m_errorString = "Failed to read XMI file data!\n"; + m_errorString = "Invalid RSXX header!\n"; return false; } + } - //Close source stream - fr.close(); + rawTrackData.clear(); + rawTrackData.resize(trackCount, std::vector<uint8_t>()); + m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(deltaTicks)); + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(deltaTicks)); + + size_t totalGotten = 0; + + for(size_t tk = 0; tk < trackCount; ++tk) + { + // Read track header + size_t trackLength; - uint8_t *mid = NULL; - uint32_t mid_len = 0; - int m2mret = Convert_xmi2midi(mus, static_cast<uint32_t>(mus_len), - &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION); - if(mus) free(mus); - if(m2mret < 0) + size_t pos = fr.tell(); + fr.seek(0, FileAndMemReader::END); + trackLength = fr.tell() - pos; + fr.seek(static_cast<long>(pos), FileAndMemReader::SET); + + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, trackLength); + if(fsize < trackLength) { - m_errorString = "Invalid XMI data format!"; + m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n"; return false; } - cvt_buf.set(mid); - //Open converted MIDI file - fr.openData(mid, static_cast<size_t>(mid_len)); - //Re-Read header again! - goto riffskip; - } -#endif //BWMIDI_DISABLE_XMI_SUPPORT - - else if(std::memcmp(headerBuf, "CTMF", 4) == 0) - { - // Creative Music Format (CMF). - // When playing CTMF files, use the following commandline: - // adlmidi song8.ctmf -p -v 1 1 0 - // i.e. enable percussion mode, deeper vibrato, and use only 1 card. - is_CMF = true; - m_format = Format_CMF; - //unsigned version = ReadLEint(HeaderBuf+4, 2); - uint64_t ins_start = readLEint(headerBuf + 6, 2); - uint64_t mus_start = readLEint(headerBuf + 8, 2); - //unsigned deltas = ReadLEint(HeaderBuf+10, 2); - uint64_t ticks = readLEint(headerBuf + 12, 2); - // Read title, author, remarks start offsets in file - fsize = fr.read(headerBuf, 1, 6); - if(fsize < 6) + totalGotten += fsize; + + //Finalize raw track data with a zero + rawTrackData[tk].push_back(0); + } + + for(size_t tk = 0; tk < trackCount; ++tk) + totalGotten += rawTrackData[tk].size(); + + if(totalGotten == 0) + { + m_errorString = fr.fileName() + ": Empty track data"; + return false; + } + + // Build new MIDI events table + if(!buildSmfTrackData(rawTrackData)) + { + m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; + return false; + } + + m_smfFormat = 0; + m_loop.stackLevel = -1; + + return true; +} + +bool BW_MidiSequencer::parseCMF(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, trackCount = 1; + std::vector<std::vector<uint8_t> > rawTrackData; + + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + if(std::memcmp(headerBuf, "CTMF", 4) != 0) + { + m_errorString = fr.fileName() + ": Invalid format, CTMF signature is not found!\n"; + return false; + } + + m_format = Format_CMF; + + //unsigned version = ReadLEint(HeaderBuf+4, 2); + uint64_t ins_start = readLEint(headerBuf + 6, 2); + uint64_t mus_start = readLEint(headerBuf + 8, 2); + //unsigned deltas = ReadLEint(HeaderBuf+10, 2); + uint64_t ticks = readLEint(headerBuf + 12, 2); + // Read title, author, remarks start offsets in file + fsize = fr.read(headerBuf, 1, 6); + if(fsize < 6) + { + fr.close(); + m_errorString = "Unexpected file ending on attempt to read CTMF header!"; + return false; + } + + //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; + fr.seek(16, FileAndMemReader::CUR); // Skip the channels-in-use table + fsize = fr.read(headerBuf, 1, 4); + if(fsize < 4) + { + fr.close(); + m_errorString = "Unexpected file ending on attempt to read CMF instruments block header!"; + return false; + } + + uint64_t ins_count = readLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + fr.seek(static_cast<long>(ins_start), FileAndMemReader::SET); + + m_cmfInstruments.reserve(static_cast<size_t>(ins_count)); + for(uint64_t i = 0; i < ins_count; ++i) + { + CmfInstrument inst; + fsize = fr.read(inst.data, 1, 16); + if(fsize < 16) { fr.close(); - m_errorString = "Unexpected file ending on attempt to read CTMF header!"; + m_errorString = "Unexpected file ending on attempt to read CMF instruments raw data!"; return false; } + m_cmfInstruments.push_back(inst); + } - //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; - fr.seek(16, FileAndMemReader::CUR); // Skip the channels-in-use table - fsize = fr.read(headerBuf, 1, 4); - if(fsize < 4) + fr.seeku(mus_start, FileAndMemReader::SET); + trackCount = 1; + deltaTicks = (size_t)ticks; + + rawTrackData.clear(); + rawTrackData.resize(trackCount, std::vector<uint8_t>()); + m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(deltaTicks)); + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(deltaTicks)); + + size_t totalGotten = 0; + + for(size_t tk = 0; tk < trackCount; ++tk) + { + // Read track header + size_t trackLength; + size_t pos = fr.tell(); + fr.seek(0, FileAndMemReader::END); + trackLength = fr.tell() - pos; + fr.seek(static_cast<long>(pos), FileAndMemReader::SET); + + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, trackLength); + if(fsize < trackLength) { - fr.close(); - m_errorString = "Unexpected file ending on attempt to read CMF instruments block header!"; + m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n"; return false; } + totalGotten += fsize; + } - uint64_t ins_count = readLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); - fr.seek(static_cast<long>(ins_start), FileAndMemReader::SET); + for(size_t tk = 0; tk < trackCount; ++tk) + totalGotten += rawTrackData[tk].size(); - m_cmfInstruments.reserve(static_cast<size_t>(ins_count)); - for(uint64_t i = 0; i < ins_count; ++i) - { - CmfInstrument inst; - fsize = fr.read(inst.data, 1, 16); - if(fsize < 16) - { - fr.close(); - m_errorString = "Unexpected file ending on attempt to read CMF instruments raw data!"; - return false; - } - m_cmfInstruments.push_back(inst); - } + if(totalGotten == 0) + { + m_errorString = fr.fileName() + ": Empty track data"; + return false; + } - fr.seeku(mus_start, FileAndMemReader::SET); - TrackCount = 1; - DeltaTicks = (size_t)ticks; + // Build new MIDI events table + if(!buildSmfTrackData(rawTrackData)) + { + m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; + return false; } - else + + return true; +} + +bool BW_MidiSequencer::parseGMF(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, trackCount = 1; + std::vector<std::vector<uint8_t> > rawTrackData; + + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) { - // Try to identify RSXX format - if(headerBuf[0] == 0x7D) - { - fr.seek(0x6D, FileAndMemReader::SET); - fr.read(headerBuf, 1, 6); - if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) - { - is_RSXX = true; - m_format = Format_RSXX; - fr.seek(0x7D, FileAndMemReader::SET); - TrackCount = 1; - DeltaTicks = 60; - } - } + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + if(std::memcmp(headerBuf, "GMF\x1", 4) != 0) + { + m_errorString = fr.fileName() + ": Invalid format, GMF\\x1 signature is not found!\n"; + return false; + } + + fr.seek(7 - static_cast<long>(headerSize), FileAndMemReader::CUR); + + rawTrackData.clear(); + rawTrackData.resize(trackCount, std::vector<uint8_t>()); + m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(deltaTicks)); + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(deltaTicks) * 2); + static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; + size_t totalGotten = 0; - // Try parsing as an IMF file - if(!is_RSXX) + for(size_t tk = 0; tk < trackCount; ++tk) + { + // Read track header + size_t trackLength; + size_t pos = fr.tell(); + fr.seek(0, FileAndMemReader::END); + trackLength = fr.tell() - pos; + fr.seek(static_cast<long>(pos), FileAndMemReader::SET); + + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, trackLength); + if(fsize < trackLength) { - do - { - uint8_t raw[4]; - size_t end = static_cast<size_t>(headerBuf[0]) + 256 * static_cast<size_t>(headerBuf[1]); + m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n"; + return false; + } + totalGotten += fsize; + // Note: GMF does include the track end tag. + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + } - if(!end || (end & 3)) - break; + for(size_t tk = 0; tk < trackCount; ++tk) + totalGotten += rawTrackData[tk].size(); - size_t backup_pos = fr.tell(); - int64_t sum1 = 0, sum2 = 0; - fr.seek(2, FileAndMemReader::SET); + if(totalGotten == 0) + { + m_errorString = fr.fileName() + ": Empty track data"; + return false; + } - for(unsigned n = 0; n < 42; ++n) - { - if(fr.read(raw, 1, 4) != 4) - break; - int64_t value1 = raw[0]; - value1 += raw[1] << 8; - sum1 += value1; - int64_t value2 = raw[2]; - value2 += raw[3] << 8; - sum2 += value2; - } + // Build new MIDI events table + if(!buildSmfTrackData(rawTrackData)) + { + m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; + return false; + } - fr.seek(static_cast<long>(backup_pos), FileAndMemReader::SET); + return true; +} - if(sum1 > sum2) - { - is_IMF = true; - m_format = Format_IMF; - DeltaTicks = 1; - } - } while(false); - } +bool BW_MidiSequencer::parseSMF(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, TrackCount = 1; + unsigned smfFormat = 0; + std::vector<std::vector<uint8_t> > rawTrackData; - if(!is_IMF && !is_RSXX) - { - if(std::memcmp(headerBuf, "MThd\0\0\0\6", 8) != 0) - { - fr.close(); - m_errorString = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; - return false; - } + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } - /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ - TrackCount = (size_t)readBEint(headerBuf + 10, 2); - DeltaTicks = (size_t)readBEint(headerBuf + 12, 2); - } + if(std::memcmp(headerBuf, "MThd\0\0\0\6", 8) != 0) + { + m_errorString = fr.fileName() + ": Invalid format, MThd signature is not found!\n"; + return false; } + smfFormat = static_cast<unsigned>(readBEint(headerBuf + 8, 2)); + TrackCount = static_cast<size_t>(readBEint(headerBuf + 10, 2)); + deltaTicks = static_cast<size_t>(readBEint(headerBuf + 12, 2)); + + if(smfFormat > 2) + smfFormat = 1; + rawTrackData.clear(); rawTrackData.resize(TrackCount, std::vector<uint8_t>()); - m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks)); - if(is_CMF || is_RSXX) - m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks)); - else - m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2); - static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; + m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(deltaTicks)); + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(deltaTicks) * 2); + size_t totalGotten = 0; for(size_t tk = 0; tk < TrackCount; ++tk) @@ -1734,87 +2361,24 @@ riffskip: // Read track header size_t trackLength; - if(is_IMF) + fsize = fr.read(headerBuf, 1, 8); + if((fsize < 8) || (std::memcmp(headerBuf, "MTrk", 4) != 0)) { - //std::fprintf(stderr, "Reading IMF file...\n"); - size_t end = static_cast<size_t>(headerBuf[0]) + 256 * static_cast<size_t>(headerBuf[1]); - unsigned IMF_tempo = 1428; - static const unsigned char imf_tempo[] = {0x0,//Zero delay! - MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, - static_cast<uint8_t>(IMF_tempo >> 24), - static_cast<uint8_t>(IMF_tempo >> 16), - static_cast<uint8_t>(IMF_tempo >> 8), - static_cast<uint8_t>(IMF_tempo) - }; - rawTrackData[tk].insert(rawTrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); - rawTrackData[tk].push_back(0x00); - fr.seek(2, FileAndMemReader::SET); - - while(fr.tell() < end && !fr.eof()) - { - uint8_t special_event_buf[5]; - uint8_t raw[4]; - special_event_buf[0] = MidiEvent::T_SPECIAL; - special_event_buf[1] = MidiEvent::ST_RAWOPL; - special_event_buf[2] = 0x02; - if(fr.read(raw, 1, 4) != 4) - break; - special_event_buf[3] = raw[0]; // port index - special_event_buf[4] = raw[1]; // port value - uint32_t delay = static_cast<uint32_t>(raw[2]); - delay += 256 * static_cast<uint32_t>(raw[3]); - totalGotten += 4; - //if(special_event_buf[3] <= 8) continue; - //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); - rawTrackData[tk].insert(rawTrackData[tk].end(), special_event_buf, special_event_buf + 5); - //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); - if(delay >> 14) - rawTrackData[tk].push_back(static_cast<uint8_t>(0x80 | ((delay >> 14) & 0x7F))); - if(delay >> 7) - rawTrackData[tk].push_back(static_cast<uint8_t>(0x80 | ((delay >> 7) & 0x7F))); - rawTrackData[tk].push_back(static_cast<uint8_t>(((delay >> 0) & 0x7F))); - } - - rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + m_errorString = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; + return false; } - else - { - // Take the rest of the file - if(is_GMF || is_CMF || is_RSXX) - { - size_t pos = fr.tell(); - fr.seek(0, FileAndMemReader::END); - trackLength = fr.tell() - pos; - fr.seek(static_cast<long>(pos), FileAndMemReader::SET); - } - else - { - fsize = fr.read(headerBuf, 1, 8); - if((fsize < 8) || (std::memcmp(headerBuf, "MTrk", 4) != 0)) - { - fr.close(); - m_errorString = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; - return false; - } - trackLength = (size_t)readBEint(headerBuf + 4, 4); - } - - // Read track data - rawTrackData[tk].resize(trackLength); - fsize = fr.read(&rawTrackData[tk][0], 1, trackLength); - if(fsize < trackLength) - { - fr.close(); - m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n"; - return false; - } - totalGotten += fsize; + trackLength = (size_t)readBEint(headerBuf + 4, 4); - if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. - rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); - if(is_RSXX)//Finalize raw track data with a zero - rawTrackData[tk].push_back(0); + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, trackLength); + if(fsize < trackLength) + { + m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n"; + return false; } + + totalGotten += fsize; } for(size_t tk = 0; tk < TrackCount; ++tk) @@ -1827,11 +2391,167 @@ riffskip: } // Build new MIDI events table - if(!buildTrackData(rawTrackData)) + if(!buildSmfTrackData(rawTrackData)) { m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; return false; } + m_smfFormat = smfFormat; + m_loop.stackLevel = -1; + return true; } + +bool BW_MidiSequencer::parseRMI(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + + size_t fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + if(std::memcmp(headerBuf, "RIFF", 4) != 0) + { + m_errorString = fr.fileName() + ": Invalid format, RIFF signature is not found!\n"; + return false; + } + + m_format = Format_MIDI; + + fr.seek(6l, FileAndMemReader::CUR); + return parseSMF(fr); +} + +#ifndef BWMIDI_DISABLE_MUS_SUPPORT +bool BW_MidiSequencer::parseMUS(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + BufferGuard<uint8_t> cvt_buf; + + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + if(std::memcmp(headerBuf, "MUS\x1A", 4) != 0) + { + m_errorString = fr.fileName() + ": Invalid format, MUS\\x1A signature is not found!\n"; + return false; + } + + size_t mus_len = fr.fileSize(); + + fr.seek(0, FileAndMemReader::SET); + uint8_t *mus = (uint8_t *)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fsize = fr.read(mus, 1, mus_len); + if(fsize < mus_len) + { + m_errorString = "Failed to read MUS file data!\n"; + return false; + } + + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_mus2midi(mus, static_cast<uint32_t>(mus_len), + &mid, &mid_len, 0); + if(mus) + free(mus); + + if(m2mret < 0) + { + m_errorString = "Invalid MUS/DMX data format!"; + return false; + } + cvt_buf.set(mid); + + //Open converted MIDI file + fr.openData(mid, static_cast<size_t>(mid_len)); + + return parseSMF(fr); +} +#endif //BWMIDI_DISABLE_MUS_SUPPORT + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT +bool BW_MidiSequencer::parseXMI(FileAndMemReader &fr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + BufferGuard<uint8_t> cvt_buf; + + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } + + if(std::memcmp(headerBuf, "FORM", 4) != 0) + { + m_errorString = fr.fileName() + ": Invalid format, FORM signature is not found!\n"; + return false; + } + + if(std::memcmp(headerBuf + 8, "XDIR", 4) != 0) + { + m_errorString = fr.fileName() + ": Invalid format\n"; + return false; + } + + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + + uint8_t *mus = (uint8_t*)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fsize = fr.read(mus, 1, mus_len); + if(fsize < mus_len) + { + m_errorString = "Failed to read XMI file data!\n"; + return false; + } + + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_xmi2midi(mus, static_cast<uint32_t>(mus_len), + &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION); + if(mus) + free(mus); + if(m2mret < 0) + { + m_errorString = "Invalid XMI data format!"; + return false; + } + + cvt_buf.set(mid); + //Open converted MIDI file + fr.openData(mid, static_cast<size_t>(mid_len)); + //Set format as XMIDI + m_format = Format_XMIDI; + + return parseSMF(fr); +} +#endif |