MidiMessage Meta Tempo Event Inconsistencies Amongst Hosts

Hello everyone.

My plugin creates Tempo Change messages and writes them out to a MIDI file.

void foo(double ppq, double bpm)
    auto secondsPerQuarterNote = 60 / bpm;
    auto microsecondsPerQuarterNote = juce::roundToInt(secondsPerQuarterNote * 1'000'000);
    auto msg = juce::MidiMessage::tempoMetaEvent(microsecondsPerQuarterNote);
    msg.setTimeStamp(juce::roundToInt(ppq * 960));

For certain hosts, this works as expected. Cubase and REAPER, for example, produce the correct bpm using this code.

In Digital Performer specifically, the Tempo written into the file is several multiples of the intended bpm.

JUCE 7.0.5
Digital Performer 11
Test Machine 1: Macbook Pro (Intel CPU) with OS 12
Test Machine 2: Mac Studio (Apple CPU) with OS 13

Any ideas what causes this and how to correct it? Must I detect the host and write different code specifically for each host, or is there some truly universal way to do this that I am missing?

Thanks in advance for any tips.

Using a python script (Mido - MIDI Objects for Python — Mido 1.2.10 documentation) to examine the MIDI messages, we found that juce::MidiMessage::timeSignatureMetaEvent writes out a message with 1 clock per tick and 96 thirty-second notes per beat, where 24 clocks per tick and 8 thirty-second notes per beat was expected. I can’t find any parameters to alter these settings, so now we are resorting to directly overwriting the raw midi message byte by byte.

@reuk: Is this a host-specific problem with juce::MidiMessage::timeSignatureMetaEvent() or am I missing something?

Well, for a start the timeStamp you set with

msg.setTimeStamp(juce::roundToInt(ppq * 960));

is not a (integral) part of the MidiMessage. It’s used for timing, i.e when this message should be played in relation to the other messages. By the content you put there (ppq and 9600) it looks as if you think it has something to do with the actual tempo event. But I might be wrong, perhaps your intention is that the message should appear at that precise moment in time (ppq * 9600)…

Since you’re talking of writing the message to a midi file (as opposed to streaming it directly to a host), do you set the timeFormat properly when you write the midi file?

Excerpt from juce_MidiFile.h:

/** Sets the time format to use when this file is written to a stream.

    If this is called, the file will be written as bars/beats using the
    specified resolution, rather than SMPTE absolute times, as would be
    used if setSmpteTimeFormat() had been called instead.

    @param ticksPerQuarterNote  e.g. 96, 960
    @see setSmpteTimeFormat
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept;

@oxxyyd: Thanks for taking the time to reply. Appreciate that!

Don’t be distracted by setTimestamp() as that has nothing to do with the BPM, it is merely the location where the Meta Tempo Change event is inserted into the sequence.

As for the time format of the MidiFile object, I should have included that information in the original post. I used setTicksPerQuarterNote(960) for that purpose. I experimented with different values, but to no avail.

The only solution we found was to write the raw MIDI message in bytes. I will reply back with the solution once my coding partner pushes his changes, so all in the community can benefit from our discovery.

The topic and your first post is about TempoMidiEvents. The second post is about TimeSignatureMetaEvents. Are you having problems with both?

Right. Missing some context. The tempo changes (as viewed from the host application DP) displayed high multiples of the intended value. 120bpm showing up as 360bpm. The problem turned out to be caused by the juce::MidiMessage::metaTimeSignatureEvent setting a weird ticks per clock and thirty-second notes per beat value. The juce method takes only numerator and denominator as inputs. No obvious way to explicitly set the ticks per clock and thirty-second notes per beat values. The bogus values (1 and 96) replaced with correct values (24 and 8) by modifying the raw MIDI message for the Time Signature resulted in correct BPM values in the MIDI file. Again, bear in mind, this only happens with DP/AU and not with Cubase/VST3 or Reaper/AU or Reaper/VST3.

This was our solution:

uint8 clocks_per_click = 24;
uint8 notated_32nd_notes_per_beat = 8
auto data = msgInput.getRawData();
juce::MidiMessage msgOutput(
    data[0], data[1], data[2], data[3], data[4],
    clocks_per_click, notated_32nd_notes_per_beat);

I should reiterate that this is only necessary for Digital Performer 11 Audio Units (and perhaps other Audio Units hosts that we did not test.)