aboutsummaryrefslogtreecommitdiff
path: root/src/midi_sequencer_impl.hpp
diff options
context:
space:
mode:
authorVitaly Novichkov <admin@wohlnet.ru>2018-08-06 01:56:50 +0300
committerVitaly Novichkov <admin@wohlnet.ru>2018-08-06 01:56:50 +0300
commit1ab34e88a326c396fbb42c503eb4ffa56fa0a148 (patch)
tree4f2d6ada0ab0bd1d64947a71c5492a29be7c8dbf /src/midi_sequencer_impl.hpp
parent0a003c8bc12514a5586f2f0e40559fcbf6607883 (diff)
parent0e2807a9d4c8c900e85c9b33ba96c69a82c58a68 (diff)
downloadlibADLMIDI-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.hpp1372
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