ok, i am just not understanding the problem with my code for some reason. Here’s a pic of Logic’s data being recorded at the same time as when I use my little midi recorder. You can see the SMPTE times of all of the events, the lengths, and also the SMPTE FPS, which is 50. I set it to 50, so that there are 20ms per frame. that makes it nice and easy to convert a SMPTE time of, say, 00:00:00:25.46 into milliseconds by multiplying 25.46 * 20 = 509.2ms, which makes sense. If you’re doing 50FPS, and the time stamp says you’re at frame 25.46 out of 50, then you’re roughly around 500ms.
Ok, so here is my code and the output from that code that corresponds with the midi shown in the picture above:
class MidiRecorder : public Timer, public MidiInputCallback {
public:
MidiRecorder() {
// double msPerQuarterNote = (60000.f / 96.f); //96bpm
// double ticksPerQuarterNote = 960;
msPerTick = (60000.f / 96.f) / 960.f;
saveTimerIsRunning = false;
enableAllMidiInputs();
}
void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override {
if( !saveTimerIsRunning ) {
//startTimer(5 * 1000 * 60 );
startTime = Time::getMillisecondCounterHiRes();
startTimer(10000);//10 seconds till File:Save dialog pops up
saveTimerIsRunning = true;
DBG( "msPerTick: " + String(msPerTick));
}
mms.addEvent(message);
mms.getEventPointer(mms.getNumEvents()-1)->message.setTimeStamp(Time::getMillisecondCounterHiRes() - startTime);
DBG( "event Time: "
+ String(mms.getEventTime(mms.getNumEvents()-1))
+ " "
+ mms.getEventPointer(mms.getNumEvents()-1)->message.getDescription());
}
void MidiRecorder::timerCallback() {
stopTimer();
MidiFile mf;
mf.setTicksPerQuarterNote(960);
int microsecondsPerQuarter = 60.0*Time::getHighResolutionTicksPerSecond()/96; //96 is the BPM
MidiMessage tempoEvent = MidiMessage::tempoMetaEvent(microsecondsPerQuarter);
tempoEvent.setTimeStamp( startTime );
mms.addEvent(tempoEvent); //tempo is 96bpm
mms.updateMatchedPairs();
mms.sort();
mf.addTrack(mms);
... code to save the MidiFile to disk;
}
private:
MidiMessageSequence mms;
double startTime;
double msPerTick;
bool saveTimerIsRunning = false;
};
Here’s the output I get from the DBG() in handleIncomingMidiMessage:
msPerTick: 0.651042
event Time: 0.131516 Note on C1 Velocity 90 Channel 1
event Time: 526.812 Note off C1 Velocity 0 Channel 1
event Time: 540.458 Note on D1 Velocity 71 Channel 1
event Time: 1047.9 Note off D1 Velocity 0 Channel 1
event Time: 1054.01 Note on E1 Velocity 75 Channel 1
event Time: 1588.53 Note off E1 Velocity 0 Channel 1
event Time: 1593.19 Note on F1 Velocity 67 Channel 1
event Time: 2154.84 Note on G1 Velocity 67 Channel 1
event Time: 2156.5 Note off F1 Velocity 0 Channel 1
event Time: 2695.46 Note off G1 Velocity 0 Channel 1
Let’s do some math for the durations between noteOns and noteOffs since the data is in milliseconds:
C1: 526.680ms
D1: 507.442ms
E1: 534.52ms
F1: 563.31ms
G1: 540.62ms
Now, let’s look at the durations provided by logic in the picture. Again, 50FPS, so we multiply the event duration * 20ms to get the duration of the event in ms.
C1: 525.4ms
D1: 506.2ms
E1: 531.6ms
F1: 563.0ms
G1: 540.2ms
Ok cool, it’s not too far off from the info spit out by the DBG() messages.
Now, when I write the midi data into a Midi File, that’s not what gets written! I end up with these values instead:
multiplying them by 20ms, we get:
C1: 342.4ms
D1: 328.6ms
E1: 346.6ms
F1: 365.2ms
G1: 349.2ms
The difference between what gets calculated and what gets written by JUCE is as follows:
C1 difference: 65.16%
D1 difference: 64.91%
E1 difference: 65.19%
F1 difference: 64.86%
G1 difference: 64.64%
So, somewhere in the timerCallback() method the timeStamp for each midiMessage is being reduced by ~65%.
What happens if I change the TicksPerQuarterNote to be 960 * 0.65?
Here’s midi recorded by logic (note the length/info column on the right)
and here’s the midi file generated by my timerCallback(). note the length/info column on the right!!!
@jules @ed95 or any other JUCE devs, could you comment on this odd behavior when writing MidiFiles to disk?