aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/midi_sequencer.h13
-rw-r--r--src/midi_sequencer.hpp36
-rw-r--r--src/midi_sequencer_impl.hpp53
3 files changed, 102 insertions, 0 deletions
diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h
index 3f4b750..4c6693f 100644
--- a/src/midi_sequencer.h
+++ b/src/midi_sequencer.h
@@ -45,6 +45,19 @@ typedef struct BW_MidiRtInterface
/*! User data which will be passed through On-Event hook */
void *onEvent_userData;
+ /*! PCM render */
+ typedef void (*PcmRender)(void *userdata, uint8_t *stream, size_t length);
+ /*! PCM render hook which catches passing of loop start point */
+ PcmRender onPcmRender;
+ /*! User data which will be passed through On-PCM-render hook */
+ void *onPcmRender_userData;
+
+ //! Sample rate
+ uint32_t pcmSampleRate;
+
+ //! Size of one sample in bytes
+ uint32_t pcmFrameSize;
+
/*! Library internal debug messages */
typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...);
/*! Debug message hook */
diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp
index 1eb5873..6832492 100644
--- a/src/midi_sequencer.hpp
+++ b/src/midi_sequencer.hpp
@@ -511,6 +511,34 @@ private:
//! Common error string
std::string m_errorString;
+ struct SequencerTime
+ {
+ //! Time buffer
+ double timeRest;
+ //! Sample rate
+ uint32_t sampleRate;
+ //! Size of one frame in bytes
+ uint32_t frameSize;
+ //! Minimum possible delay, granuality
+ double minDelay;
+ //! Last delay
+ double delay;
+
+ void init()
+ {
+ sampleRate = 44100;
+ frameSize = 2;
+ reset();
+ }
+
+ void reset()
+ {
+ timeRest = 0.0;
+ minDelay = 1.0 / static_cast<double>(sampleRate);
+ delay = 0.0;
+ }
+ } m_time;
+
public:
BW_MidiSequencer();
virtual ~BW_MidiSequencer();
@@ -522,6 +550,14 @@ public:
void setInterface(const BW_MidiRtInterface *intrf);
/**
+ * @brief Runs ticking in a sync with audio streaming. Use this together with onPcmRender hook to easily play MIDI.
+ * @param stream pointer to the output PCM stream
+ * @param length length of the buffer in bytes
+ * @return Count of recorded data in bytes
+ */
+ int playStream(uint8_t *stream, size_t length);
+
+ /**
* @brief Returns file format type of currently loaded file
* @return File format type enumeration
*/
diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp
index ef4a3de..02a4156 100644
--- a/src/midi_sequencer_impl.hpp
+++ b/src/midi_sequencer_impl.hpp
@@ -315,6 +315,7 @@ BW_MidiSequencer::BW_MidiSequencer() :
{
m_loop.reset();
m_loop.invalidLoop = false;
+ m_time.init();
}
BW_MidiSequencer::~BW_MidiSequencer()
@@ -342,9 +343,56 @@ void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *intrf)
//System Exclusive hook is REQUIRED
assert(intrf->rt_systemExclusive);
+ if(intrf->pcmSampleRate != 0 && intrf->pcmFrameSize != 0)
+ {
+ m_time.sampleRate = intrf->pcmSampleRate;
+ m_time.frameSize = intrf->pcmFrameSize;
+ m_time.reset();
+ }
+
m_interface = intrf;
}
+int BW_MidiSequencer::playStream(uint8_t *stream, size_t length)
+{
+ int count = 0;
+ size_t samples = static_cast<size_t>(length / static_cast<size_t>(m_time.frameSize));
+ size_t left = samples;
+ size_t periodSize = 0;
+ uint8_t *stream_pos = stream;
+
+ assert(m_interface->onPcmRender);
+
+ while(left > 0)
+ {
+ const double leftDelay = left / double(m_time.sampleRate);
+ const double maxDelay = m_time.timeRest < leftDelay ? m_time.timeRest : leftDelay;
+ if((positionAtEnd()) && (m_time.delay <= 0.0))
+ break;//Stop to fetch samples at reaching the song end with disabled loop
+
+ m_time.timeRest -= maxDelay;
+ periodSize = static_cast<size_t>(static_cast<double>(m_time.sampleRate) * maxDelay);
+
+ if(stream)
+ {
+ size_t generateSize = periodSize > left ? static_cast<size_t>(left) : static_cast<size_t>(periodSize);
+ m_interface->onPcmRender(m_interface->onPcmRender_userData, stream_pos, generateSize * m_time.frameSize);
+ stream_pos += generateSize * m_time.frameSize;
+ count += generateSize;
+ left -= generateSize;
+ assert(left <= samples);
+ }
+
+ if(m_time.timeRest <= 0.0)
+ {
+ m_time.delay = Tick(m_time.delay, m_time.minDelay);
+ m_time.timeRest += m_time.delay;
+ }
+ }
+
+ return count * static_cast<int>(m_time.frameSize);
+}
+
BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat()
{
return m_format;
@@ -449,6 +497,7 @@ void BW_MidiSequencer::buildSmfSetupReset(size_t trackCount)
m_loop.reset();
m_loop.invalidLoop = false;
+ m_time.reset();
m_currentPosition.began = false;
m_currentPosition.absTimePosition = 0.0;
@@ -1894,6 +1943,9 @@ double BW_MidiSequencer::seek(double seconds, const double granularity)
if(m_currentPosition.wait < 0.0)
m_currentPosition.wait = 0.0;
+ m_time.reset();
+ m_time.delay = m_currentPosition.wait;
+
m_loopEnabled = loopFlagState;
return m_currentPosition.wait;
}
@@ -1925,6 +1977,7 @@ void BW_MidiSequencer::rewind()
m_loop.reset();
m_loop.caughtStart = true;
+ m_time.reset();
}
void BW_MidiSequencer::setTempo(double tempo)