Supposing I already have a working synth player, I'm trying to play a MIDI file. It looks as though this is quite involved.
https://www.juce.com/doc/classMidiFile <-- I can see how to use this to extract a const MidiMessageSequence* for each channel.
Looking at it from the other end, my synth's getNextAudioBlock looks like the demo's: https://github.com/julianstorer/JUCE/blob/master/examples/Demo/Source/Demos/AudioSynthesiserDemo.cpp#L206
i.e.
MidiMessageCollector midiCollector;
void prepareToPlay (int /*samplesPerBlockExpected*/, double sampleRate) override {
midiCollector.reset (sampleRate);
synth.setCurrentPlaybackSampleRate (sampleRate);
}
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override {
bufferToFill.clearActiveBufferRegion();
MidiBuffer incomingMidi;
midiCollector.removeNextBlockOfMessages (incomingMidi, bufferToFill.numSamples);
keyboardState.processNextMidiBuffer (incomingMidi, 0, bufferToFill.numSamples, true);
synth.renderNextBlock (*bufferToFill.buffer, incomingMidi, 0, bufferToFill.numSamples);
}
I think I want to ignore MidiMessageCollector as that is for real-time collection.
So it looks like somehow I need to extract a MidiBuffer from my MidiMessageSequence
http://www.juce.com/forum/topic/play-midi-file-midimessagesequence-midibuffer says I need to do it myself:
ScopedPointer<MidiFile> midiFile = new MidiFile();
Array<int> currMidiEvent;
double midiTimeElapsed;
bool midiIsPlaying = false;
void setMidiFile(String file) {
FileInputStream fileStream(file);
midiFile->readFrom(fileStream);
midiFile->convertTimestampTicksToSeconds();
currMidiEvent.clear(); // last used event in each channel
for (int i = 0; i < midiFile->getNumTracks(); i++)
currMidiEvent.add(0);
midiTimeElapsed = 0.0;
midiIsPlaying = true;
}
void getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill) override {
bufferToFill.clearActiveBufferRegion();
MidiBuffer incomingMidi;
midiCollector.removeNextBlockOfMessages(incomingMidi, bufferToFill.numSamples);
while (midiIsPlaying) {
double sampleRate = 48000.f, // fix this later
startTime = midiTimeElapsed,
timeslice = bufferToFill.numSamples / sampleRate;
for (int t = 0; t < midiFile->getNumTracks(); t++) {
const MidiMessageSequence* track = midiFile->getTrack(t);
// read in events for this track
while (true) {
if (currMidiEvent[t] >= track->getNumEvents())
break;
MidiMessage& m = track->getEventPointer(currMidiEvent[t])->message;
double timeOffset = m.getTimeStamp() - startTime;
if (timeOffset > timeslice)
break;
int sampleOffset = (int)(sampleRate * timeOffset);
//DBG(sampleOffset); <-- seems ok!
incomingMidi.addEvent(m, sampleOffset); // <-- is something wrong here?
currMidiEvent.set(t, currMidiEvent[t] + 1);
}
}
midiTimeElapsed = startTime + timeslice;
}
const int startSample = 0;
const bool injectIndirectEvents = true;
keyboardState.processNextMidiBuffer(incomingMidi, startSample, bufferToFill.numSamples, injectIndirectEvents);
synth.renderNextBlock(*bufferToFill.buffer, incomingMidi, startSample, bufferToFill.numSamples);
}
I can't see why no notes are playing... everything seems to check out.
π
PS EDIT: looks like I should be using MidiBuffer::addEvents so I can add a block of events in one go. So I think my strategy should be to flatten all channels into a single MidiBuffer upon load. But https://www.juce.com/doc/classMidiBuffer#a9607fef1521aa115337f012cff244950 confuses me:
i.e. events in the source buffer whose timestamp is greater than or equal to (startSample + numSamples) will be ignored.
But MidiMessage doesn't store timestamps as samples; either it is midi ticks or seconds.
