aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/midi_sequencer.hpp37
-rw-r--r--src/midi_sequencer_impl.hpp156
2 files changed, 188 insertions, 5 deletions
diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp
index e89ecca..bba33e3 100644
--- a/src/midi_sequencer.hpp
+++ b/src/midi_sequencer.hpp
@@ -384,6 +384,9 @@ private:
//! Is song at end
bool m_atEnd;
+ //! Set the number of loops limit. Lesser than 0 - loop infinite
+ int m_loopCount;
+
/**
* @brief Loop stack entry
*/
@@ -427,6 +430,15 @@ private:
//! Are loop points invalid?
bool invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/
+ //! Is look got temporarily broken because of post-end seek?
+ bool temporaryBroken;
+
+ //! How much times the loop should start repeat? For example, if you want to loop song twice, set value 1
+ int loopsCount;
+
+ //! how many loops left until finish the song
+ int loopsLeft;
+
//! Stack of nested loops
std::vector<LoopStackEntry> stack;
//! Current level on the loop stack (<0 - out of loop, 0++ - the index in the loop stack)
@@ -443,12 +455,15 @@ private:
caughtStackEnd = false;
caughtStackBreak = false;
skipStackStart = false;
+ loopsLeft = loopsCount;
}
void fullReset()
{
+ loopsCount = -1;
reset();
invalidLoop = false;
+ temporaryBroken = false;
stack.clear();
stackLevel = -1;
}
@@ -495,6 +510,8 @@ private:
std::vector<bool> m_trackDisable;
//! Index of solo track, or max for disabled
size_t m_trackSolo;
+ //! MIDI channel disable (exception for extra port-prefix-based channels)
+ bool m_channelDisable[16];
/**
* @brief Handler of callback trigger events
@@ -581,6 +598,14 @@ public:
bool setTrackEnabled(size_t track, bool enable);
/**
+ * @brief Disable/enable a channel is sounding
+ * @param channel Channel number from 0 to 15
+ * @param enable Enable the channel playback
+ * @return true on success, false if there was no such channel
+ */
+ bool setChannelEnabled(size_t channel, bool enable);
+
+ /**
* @brief Enables or disables solo on a track
* @param track Identifier of solo track, or max to disable
*/
@@ -618,6 +643,18 @@ public:
void setLoopEnabled(bool enabled);
/**
+ * @brief Get the number of loops set
+ * @return number of loops or -1 if loop infinite
+ */
+ int getLoopsCount();
+
+ /**
+ * @brief How many times song should loop
+ * @param loops count or -1 to loop infinite
+ */
+ void setLoopsCount(int loops);
+
+ /**
* @brief Switch loop hooks-only mode on/off
* @param enabled Don't loop: trigger hooks only without loop
*/
diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp
index f13768f..dac8b12 100644
--- a/src/midi_sequencer_impl.hpp
+++ b/src/midi_sequencer_impl.hpp
@@ -314,6 +314,7 @@ BW_MidiSequencer::BW_MidiSequencer() :
m_loopEndTime(-1.0),
m_tempoMultiplier(1.0),
m_atEnd(false),
+ m_loopCount(-1),
m_trackSolo(~static_cast<size_t>(0)),
m_triggerHandler(NULL),
m_triggerUserData(NULL)
@@ -417,6 +418,33 @@ bool BW_MidiSequencer::setTrackEnabled(size_t track, bool enable)
return true;
}
+bool BW_MidiSequencer::setChannelEnabled(size_t channel, bool enable)
+{
+ if(channel >= 16)
+ return false;
+
+ if(!enable && m_channelDisable[channel] != !enable)
+ {
+ uint8_t ch = static_cast<uint8_t>(channel);
+
+ // Releae all pedals
+ m_interface->rt_controllerChange(m_interface->rtUserData, ch, 64, 0);
+ m_interface->rt_controllerChange(m_interface->rtUserData, ch, 66, 0);
+
+ // Release all notes on the channel now
+ for(int i = 0; i < 127; ++i)
+ {
+ if(m_interface->rt_noteOff)
+ m_interface->rt_noteOff(m_interface->rtUserData, ch, i);
+ if(m_interface->rt_noteOffVel)
+ m_interface->rt_noteOffVel(m_interface->rtUserData, ch, i, 0);
+ }
+ }
+
+ m_channelDisable[channel] = !enable;
+ return true;
+}
+
void BW_MidiSequencer::setSoloTrack(size_t track)
{
m_trackSolo = track;
@@ -448,6 +476,18 @@ void BW_MidiSequencer::setLoopEnabled(bool enabled)
m_loopEnabled = enabled;
}
+int BW_MidiSequencer::getLoopsCount()
+{
+ return m_loopCount >= 0 ? (m_loopCount + 1) : m_loopCount;
+}
+
+void BW_MidiSequencer::setLoopsCount(int loops)
+{
+ if(loops >= 1)
+ loops -= 1; // Internally, loops count has the 0 base
+ m_loopCount = loops;
+}
+
void BW_MidiSequencer::setLoopHooksOnly(bool enabled)
{
m_loopHooksOnly = enabled;
@@ -491,6 +531,7 @@ void BW_MidiSequencer::buildSmfSetupReset(size_t trackCount)
m_loopEndTime = -1.0;
m_loopFormat = Loop_Default;
m_trackDisable.clear();
+ std::memset(m_channelDisable, 0, sizeof(m_channelDisable));
m_trackSolo = ~(size_t)0;
m_musTitle.clear();
m_musCopyright.clear();
@@ -903,6 +944,85 @@ void BW_MidiSequencer::buildTimeLine(const std::vector<MidiEvent> &tempos,
// Set lowest level of the loop stack
m_loop.stackLevel = -1;
+ // Set the count of loops
+ m_loop.loopsCount = m_loopCount;
+ m_loop.loopsLeft = m_loopCount;
+
+ /********************************************************************************/
+ // Find and set proper loop points
+ /********************************************************************************/
+ if(!m_loop.invalidLoop && !m_currentPosition.track.empty())
+ {
+ unsigned caughLoopStart = 0;
+ bool scanDone = false;
+ const size_t trackCount = m_currentPosition.track.size();
+ Position rowPosition(m_currentPosition);
+
+ while(!scanDone)
+ {
+ const Position rowBeginPosition(rowPosition);
+
+ for(size_t tk = 0; tk < trackCount; ++tk)
+ {
+ Position::TrackInfo &track = rowPosition.track[tk];
+ if((track.lastHandledEvent >= 0) && (track.delay <= 0))
+ {
+ // Check is an end of track has been reached
+ if(track.pos == m_trackData[tk].end())
+ {
+ track.lastHandledEvent = -1;
+ continue;
+ }
+
+ for(size_t i = 0; i < track.pos->events.size(); i++)
+ {
+ const MidiEvent &evt = track.pos->events[i];
+ if(evt.type == MidiEvent::T_SPECIAL && evt.subtype == MidiEvent::ST_LOOPSTART)
+ {
+ caughLoopStart++;
+ scanDone = true;
+ break;
+ }
+ }
+
+ if(track.lastHandledEvent >= 0)
+ {
+ track.delay += track.pos->delay;
+ track.pos++;
+ }
+ }
+ }
+
+ // Find a shortest delay from all track
+ uint64_t shortestDelay = 0;
+ bool shortestDelayNotFound = true;
+
+ for(size_t tk = 0; tk < trackCount; ++tk)
+ {
+ Position::TrackInfo &track = rowPosition.track[tk];
+ if((track.lastHandledEvent >= 0) && (shortestDelayNotFound || track.delay < shortestDelay))
+ {
+ shortestDelay = track.delay;
+ shortestDelayNotFound = false;
+ }
+ }
+
+ // Schedule the next playevent to be processed after that delay
+ for(size_t tk = 0; tk < trackCount; ++tk)
+ rowPosition.track[tk].delay -= shortestDelay;
+
+ if(caughLoopStart > 0)
+ {
+ m_loopBeginPosition = rowBeginPosition;
+ m_loopBeginPosition.absTimePosition = m_loopStartTime;
+ scanDone = true;
+ }
+
+ if(shortestDelayNotFound)
+ break;
+ }
+ }
+
/********************************************************************************/
// Resolve "hell of all times" of too short drum notes:
// move too short percussion note-offs far far away as possible
@@ -1154,7 +1274,7 @@ bool BW_MidiSequencer::processEvents(bool isSeek)
#endif
m_currentPosition.wait += t.value();
- if(caughLoopStart > 0)
+ if(caughLoopStart > 0 && m_loopBeginPosition.absTimePosition <= 0.0)
m_loopBeginPosition = rowBeginPosition;
if(caughLoopStackStart > 0)
@@ -1238,13 +1358,24 @@ bool BW_MidiSequencer::processEvents(bool isSeek)
m_loop.caughtEnd = false;
shortestDelay = 0;
- if(!m_loopEnabled || m_loopHooksOnly)
+ if(!m_loopEnabled || (shortestDelayNotFound && m_loop.loopsCount >= 0 && m_loop.loopsLeft < 1) || m_loopHooksOnly)
{
m_atEnd = true; // Don't handle events anymore
m_currentPosition.wait += m_postSongWaitDelay; // One second delay until stop playing
return true; // We have caugh end here!
}
- m_currentPosition = m_loopBeginPosition;
+
+ if(m_loop.temporaryBroken)
+ {
+ m_currentPosition = m_trackBeginPosition;
+ m_loop.temporaryBroken = false;
+ }
+ else if(m_loop.loopsCount < 0 || m_loop.loopsLeft >= 1)
+ {
+ m_currentPosition = m_loopBeginPosition;
+ if(m_loop.loopsCount >= 1)
+ m_loop.loopsLeft--;
+ }
}
return true; // Has events in queue
@@ -1797,6 +1928,8 @@ void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEve
{
case MidiEvent::T_NOTEOFF: // Note off
{
+ if(midCh < 16 && m_channelDisable[midCh])
+ break; // Disabled channel
uint8_t note = evt.data[0];
uint8_t vol = evt.data[1];
if(m_interface->rt_noteOff)
@@ -1808,6 +1941,8 @@ void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEve
case MidiEvent::T_NOTEON: // Note on
{
+ if(midCh < 16 && m_channelDisable[midCh])
+ break; // Disabled channel
uint8_t note = evt.data[0];
uint8_t vol = evt.data[1];
m_interface->rt_noteOn(m_interface->rtUserData, static_cast<uint8_t>(midCh), note, vol);
@@ -1897,7 +2032,7 @@ double BW_MidiSequencer::seek(double seconds, const double granularity)
/* Attempt to go away out of song end must rewind position to begin */
if(seconds > m_fullSongTimeLength)
{
- rewind();
+ this->rewind();
return 0.0;
}
@@ -1912,7 +2047,7 @@ double BW_MidiSequencer::seek(double seconds, const double granularity)
* - All sustaining notes must be killed
* - Ignore Note-On events
*/
- rewind();
+ this->rewind();
/*
* Set "loop Start" to false to prevent overwrite of loopStart position with
@@ -1922,6 +2057,8 @@ double BW_MidiSequencer::seek(double seconds, const double granularity)
*/
m_loop.caughtStart = false;
+ m_loop.temporaryBroken = (seconds >= m_loopEndTime);
+
while((m_currentPosition.absTimePosition < seconds) &&
(m_currentPosition.absTimePosition < m_fullSongTimeLength))
{
@@ -1951,6 +2088,13 @@ double BW_MidiSequencer::seek(double seconds, const double granularity)
if(m_currentPosition.wait < 0.0)
m_currentPosition.wait = 0.0;
+ if(m_atEnd)
+ {
+ this->rewind();
+ m_loopEnabled = loopFlagState;
+ return 0.0;
+ }
+
m_time.reset();
m_time.delay = m_currentPosition.wait;
@@ -1983,8 +2127,10 @@ void BW_MidiSequencer::rewind()
m_currentPosition = m_trackBeginPosition;
m_atEnd = false;
+ m_loop.loopsCount = m_loopCount;
m_loop.reset();
m_loop.caughtStart = true;
+ m_loop.temporaryBroken = false;
m_time.reset();
}