Midibuffer timing problems


#1

Hi all,

I am having a problem with midibuffer. I populate a buffer from some data. The when I spit out the timing information to the console, it clearly shows that the durations are all the same – intentional with my test data. However, when played back, the actual events playback wrong. Often the third or fourth event is 4 times longer than it should be and there will be over lapping events. I’m able to see this because it is also reflected when I generate a midi file from the data.

Below is where the buffer is constructed, the output to the console will consistently produce the expected results, for example:

adding note at start: 296572 end: 338908 duration: 42336

With 42336 being the expected duration in samples and is consistent throughout the buffer.
Here’s what the midi output looks like and what playback sounds like with the consistent duration from above:

Code:

double msPerTick = 60000 / (_tempo * _ppq);

// start and end times in samples
double startTime = std::floor(((msPerTick*starttick) * 0.001) * _samplerate);
double endTime = std::floor(((msPerTick*durationTicks) * 0.001) * _samplerate + startTime);

if(notes.size() != vels.size()) {
    return false;
}

for(int i = 0; i < notes.size(); i++) {
    
    MidiMessage note_on = MidiMessage::noteOn(1, notes[i], (uint8)vels[i]);
    note_on.setTimeStamp(startTime);
    seq.addEvent(note_on, startTime);
    
    MidiMessage note_off = MidiMessage::noteOff(1, notes[i]);
    note_off.setTimeStamp(endTime);
    seq.addEvent(note_off, endTime);
    
    std::cout << "adding note at start: " << startTime << " end: " << endTime << " duration: " << (endTime - startTime) << "\n";
    
}

#2

have a look at this:


#3

Thanks,

But playback is coming from the MidiBuffer itself being sent to a synth via a midicollector. To test for this problem: before playback, I copy the midibuffer into a sequence and output the midi file. So the midi file is unrelated to playback. What is represented in the MidiFile is what I am hearing produced at playback. Yet, the data being sent to the Buffer is not what I am seeing output: they should all be half notes!


#4

This is continuing to be a problem I can’t find a solution to.
It seems clear that I input the same data with each run.
However, each test iteration creates entirely different midi buffers.
Not sure where to look anymore.

Here’s some example output. The first image is the correct output and occurs maybe 10% of the time. The following, also the above, are all variations produced with identical data.


#5

When results are different every time then it usually means either some uninitialised junk somewhere, or a race condition, but it doesn’t really sound like either of those. Are you adding the same notes to the sequence multiple times or not closing the file after you’ve written it, or using a dangling pointer when writing it, or something like that?

Also, did you remember to call updateMatchedPairs() ?


#6

Again:

I’ve tracked the problem down to the MidiBuffer.

The file is for testing. It is an accurate representation of the midibuffer contents. Any usage of the buffer (playback, data to a flat file, midifile) produces the same results you see above.

However, I am going to assume that the comment can apply in principle to a MidiBuffer as well.

With that:

I call clear(start, numsamples) as the buffer is traversed in the timer callback – following the pattern set in one of the tutorials. There are times where playback is interrupted and AllNotesOff is called, there I call clear() on the buffer.

All of that should result in an empty buffer. Correct?


#7

Timer callback? You’re not trying to use a Timer, are you?


#8
Timer callback? You're not trying to use a Timer, are you?

For playback, I am.

I should step back and lay out the process here.

I have an application that loads a shared library that interfaces with JUCE.
That app populates the MidiBuffer as I add notes (chords really).
The buffer is then iterated through on playback using a hirestimer, yes.
The buffer’s data is sent to an AudioSource with a midicollector.
To test, I also output to the console and a midi file before playback.

I can verify the audio sent to the synth and the midi file are identical. The data sent to the buffer differs and is the “correct” data.

I’m not multithreading. So I can’t imagine there is a race condition. And the data reaches the shared library in the correct order, as far as I can tell.

All of this process, I have garnered from the tutorials and the forums.
Does this make sense?


#9

no. Post your code. no one knows what the problem is based on paragraphs of text. we need actual code to help you


#10

The only relevant code is in the first message.


#11

If you’re using a HighResolutionTimer then yes, you are multithreading. That’s what makes it high res.

And if you’re using an audio callback to render the midi then any kind of timer is entirely the wrong approach - your audio process callback already knows exactly how many samples it has rendered and can just send the midi notes at exactly the right time, so adding a whole intermediate layer of playback, timers, threads and message collectors (which really only intended for batching up live events which don’t have proper timestamps) will just screw up your original note times.


#12

Thanks, useful.
This implies to me the timer isn’t halting before I call clear on the buffer and begin writing to it.

What would be the correct approach to:

Play an AudioSource – Synthesizer – from a generated midi sequence from another program
Return position information to the calling program


#13

Are you creating or processing your Midi data within the processBlock method?


#14

Adamski, I am not. The posted method above is receiving data from another program – JUCE is compiled in a static library. Midi is being stored in the buffer until playback is called. Playback is handled by the timer callback below:

void SwingTheoryAudio::hiResTimerCallback() {
double currentTime = (Time::getMillisecondCounterHiRes() * 0.001) - startTime;
int currentSampleNumber = (int)(currentTime*_samplerate);

MidiBuffer::Iterator it(seq);
MidiMessage message;
int samplenumber;

while(it.getNextEvent(message, samplenumber)) {
    if(samplenumber > currentSampleNumber) {
        break;
    }
    
    synthSource.midiCollector.addMessageToQueue(message);
}

seq.clear(previousSampleNumber, currentSampleNumber - previousSampleNumber);
previousSampleNumber = currentSampleNumber;

}

Process block looks like this:

void STSynthSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) {
    bufferToFill.clearActiveBufferRegion();

    MidiBuffer incoming;
    midiCollector.removeNextBlockOfMessages(incoming, bufferToFill.numSamples);
}

#15

Jules and all,
Let me pull back from this and ask a question:

I have a program, C++ and QT, without any audio or midi support. I am planning to use JUCE for that. I am sending note numbers and velocities to a static library. What methodology do I use to play those back accurately? How can do I store the incoming midi data for eventual playback? How does that data get to the processBlock?