MIDI Sync Utility Functions

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Code: Select all

inline float TempoToSecondsPerBeat(float tempo)
{
    assert(tempo > 0.0f);

    return 60.0f / tempo;
}

inline float TempoToSamplesPerBeat(float tempo, float sampleRate)
{
    assert(tempo > 0.0f);
    assert(sampleRate >= 0.0f); 

    return TempoToSecondsPerBeat(tempo) * sampleRate;
}
A couple of simple, no-brainer functions for converting tempo to seconds/beats.

We can build on this:

Code: Select all

enum NoteType
{
    None,
    WholeNote,
    DottedHalfNote,
    HalfNote,
    HalfNoteTriplet,
    DottedQuarterNote,
    QuarterNote,
    QuarterNoteTriplet,
    DottedEigthNote,
    EigthNote,
    EigthNoteTriplet,
    DottedSixteenthNote,
    SixteenthNote,
    SixteenthNoteTriplet,
    DottedThirtySecondNote,
    ThirtySecondNote,
    ThirtySecondNoteTriplet,

    NoteTypeCount
};
Just about every note length we'd be interested in (could be expanded to include sixty-fourth notes).

Code: Select all

// Assumes 4/4 time signature. Could be modified to take other
// time signatures into account.
inline float CalculateMidiSyncScaler(int barCount, int noteType)
{
    static const float Scalers[NoteTypeCount] =
    {
        0.0f,
        4.0f,
        3.0f,
        2.0f,
        1.333333f,
        1.5f,
        1.0f,
        0.666666f,
        0.75f,
        0.5f,
        0.333333f,
        0.375f,
        0.25f,
        0.1666666f,
        0.1875f,
        0.125f,
        0.083333f        
    };

    assert(barCount >= 0);
    assert((noteType >= 0) && (noteType < NoteTypeCount));

    return barCount * Scalers[WholeNote] + Scalers[noteType];
}
The Scaler array corresponds to the note types.

Usage:

Code: Select all

// Say we're setting the delay time for a delay effect. MIDI sync
// is set to quarter-note resolution:

// The tempo is given to us earlier with a call to getTimeInfo().
float seconds = TempoToSecondsPerBeat(tempo);

// Calculate MIDI sync scaler for quarter-note resolution.
float scaler = CalculateMidiSyncScaler(0, QuarterNote);

// Scale delay time to MIDI sync resolution.
float delayTime = seconds * scaler;

// Finally, set the delay time.
delay.SetDelayTime(delayTime);

Post

I was thinking about adapting the above code to work with any time signature. For example, if the time signature is 3/4, one bar would be considered three quarter notes.

However, in testing a few plugins, I've found that the time signature is largely ignored; 4/4 is assumed.

So if you set the time sig to 3/4 and set, say, your delay sync resolution to 1 bar, the delay will sync to ever four quarter notes instead of three.

Admittedly, I haven't tested very many plugins, just a handful so far.

Thoughts?

Post

if the plugin claims it's trying to aim for the length of a bar, that would have to be a bug wouldn't it?

i'd try using fractions rather than typing all the decimal strings out.

my code for example does the following sillyness:

Code: Select all

    case 2: //tempo
    {
     data /= 8;
     long mode = (data+1) % 3; //triplet, normal, dotted
     float length = powf(2.0f, 1.0f + floor((data-17) / 3)) / 16.0f; 
     if (data < 17) length *= 0.5f;
     if (mode == 0) length *= (2.0f/3.0f); else if (mode == 2) length *= (3.0f/2.0f);
     float r = (15.0f / length);
     S->L1.setHz(Tempo / r);
    }    
uses only note length, i don't bother syncing to bars or anything like that.

Post

My synth ignores time-signature of the song, and claims a "whole note" (which is 4/4) instead of one bar to side-step that issue. Actually syncing to "one bar" opens a can of worms, 'cos then you might also have to deal with time-signature changes and what not. Dealing with tempo-changes is IMHO enough.

I also sync to song-position if desired.. VST time-info can give you song-position in quarter notes, then you can sync time-division-scaled phase to that, and scale phase-increment of LFOS with current tempo, and you're more or less in sync.

My two cents is that if you want tight sync for something like a sequencer or LFO, accept that you need to use doubles. Otherwise you're gonna have lots of fun keeping it from drifting.

I'd also just write the divisions as divisions, even if you have a static array, 'cos your compiler can work a static division into a single number anyway, and you'll get as much precision as your data-type allows.

Post

Ok, after reading both of your responses, I've modified the code:

Code: Select all

enum NoteDuration
{
    WholeNote,
    HalfNote,
    QuarterNote,
    EigthNote,
    SixteenthNote,
    ThirtySecondNote,
    SixtyFourthNote,

    NoteDurationCount
};

enum NoteMode
{
    Triplet,
    Normal,
    Dotted,

    NoteModeCount
};

Code: Select all

inline double CalculateMidiSyncScaler(int noteDuration, 
                                      int noteMode)
{
    static const double DurationScalers[NoteDurationCount] =
    {
        4.0,        // Whole note
        2.0,        // Half note
        1.0,        // Quarter note
        0.5,        // Eigth note
        0.25,       // Sixteenth note
        0.125,      // Thirty-second note
        0.0625      // Sixty-fourth note
    };

    static const double ModeScalers[NoteModeCount] =
    {
        2.0 / 3.0,  // Triplet
        1.0,        // Normal
        1.5         // Dotted
    };

    assert((noteDuration >= 0) && 
           (noteDuration < NoteDurationCount));
    assert((noteMode >= 0) && 
           (noteMode < NoteModeCount));

    return DurationScalers[noteDuration] * ModeScalers[noteMode];}
If a longer length is needed beyond a whole note (4/4), the return value can be scaled up.

I stuck with the static array approach because it's easier for me to wrap my head around. But I did promote the types up to double.

Post

mystran wrote:My synth ignores time-signature of the song, and claims a "whole note" (which is 4/4) instead of one bar to side-step that issue. Actually syncing to "one bar" opens a can of worms, 'cos then you might also have to deal with time-signature changes and what not. Dealing with tempo-changes is IMHO enough.
I think I'm still confused. For example, if the time signature is 6/8, then a "beat" is a dotted quarter-note. If the tempo is 60bpm, that's 60 dotted quarter notes per minute, i.e. one dotted quarter note per second. We'd have 90 quarter notes in a minute.

If the time signature is 4/4, then a beat is a quarter note. At 60bpm, that's 60 quarter notes per minute, or one quarter note per second.

Notice that in both examples the tempo hasn't changed, 60bpm. But the number of quarter notes per minute does change.

So if the MIDI sync resolution is set to 1/4 (a quarter note), the length of the note in time will be determined not only by the tempo but also by the time signature. Correct?

I'm trying to arrive at an undersanding that will allow me to get synchronization correct without having to bother with the time signature, but I'm having trouble wrapping my head around it.

Post

it really depends upon what you're trying to sync, if you mentioned that i'd put my mind to work on the problem..

i would think the best method is to find some fundamental unit of time and treat all other units as fractions of that fundamental one. for example you could make a beat = "1", then define all times in your sequencer based upon fractions of that beat. that way you're only needing to worry about one scaling value.

probably better to use fractions in code though, rather than 1.5 you might as well go for '3/2' since the compiler should (in theory) optimize them away anyway.

Post

aciddose wrote:it really depends upon what you're trying to sync, if you mentioned that i'd put my mind to work on the problem..
Let's take delay time.
i would think the best method is to find some fundamental unit of time and treat all other units as fractions of that fundamental one. for example you could make a beat = "1", then define all times in your sequencer based upon fractions of that beat. that way you're only needing to worry about one scaling value.
Good idea. Let 1/1 equal one beat.

For delay time in seconds we can calculate it as the following:

delayTime = 60.0 / tempo * scaler;

Where tempo and scaler must be greater than zero.

So if the tempo is 60bpm and the scale is 1/1, the delay time is 1 second.

Post

Leslie Sanford wrote: So if the MIDI sync resolution is set to 1/4 (a quarter note), the length of the note in time will be determined not only by the tempo but also by the time signature. Correct?
I don't have a MIDI sync resolution anywhere. I only synchronize with VST time-info. Tempo is strictly speaking defined as "quarter notes per minute" so there is no note-length change if the signature changes, but tempo stays as it was. You should forget about "beats" in the musical sense, since that only will make the problem impossible to solve, because different people might interpret the concept of beats differently, and in some musical contexts you essentially have unequal beats (say 7/8 signature that's essentially written as 4+3 or the other way around, could be interpreted to have two beats of different lengths).

If you have BPM of 130, then you almost definitely want 1/1 (as shown to the user) equal exactly 60*4/130. No more, no less, even if the time signature was 13/12.

For my purposes, the correct phase for a song-synced LFO (or sequence, once I get that implemented) is always just song-position (in quarter notes) modulo LFO time (in quarter notes internally). Since song-position and LFO time are both relative to tempo, change in tempo doesn't affect phase relative to logical song-position. For my tempo-synced LFOs it's essentially the same except phase=0 aligns to note-on, so the LFO time is essentially just scaled by tempo and phase reset at note-on.

In case of delays, all I do is scale the nominal delay by current tempo (still not sure what's the best thing to do for tempo-change in this case).

I haven't attempted other types of synchronization so your mileage may vary.
I'm trying to arrive at an undersanding that will allow me to get synchronization correct without having to bother with the time signature, but I'm having trouble wrapping my head around it.
How to correctly synchronize depends on what you want to synchronize to. Without having a knowledge of that, it's hard to suggest any particular synchronization strategy.

Post

mystran wrote: I don't have a MIDI sync resolution anywhere. I only synchronize with VST time-info. Tempo is strictly speaking defined as "quarter notes per minute" so there is no note-length change if the signature changes, but tempo stays as it was.
Here was the source of my confusion. Thanks for clearing this up.

All right, I'm back on track. Thanks for your patience. :)

FWIW, here's what I've arrived at as an example. I have a table of synchronization scalers for my delay with a corresponding table of text labels for the UI:

Code: Select all

const double Delay::SyncScalers[] =
{
    CalculateSyncScaler(SixteenthNote, Triplet),
    CalculateSyncScaler(SixteenthNote, Normal),
    CalculateSyncScaler(SixteenthNote, Dotted),
    CalculateSyncScaler(EigthNote, Triplet),
    CalculateSyncScaler(EigthNote, Normal),
    CalculateSyncScaler(EigthNote, Dotted),
    CalculateSyncScaler(QuarterNote, Normal)
};

const char *Delay::SyncLabels[] =
{
    "1/16T",
    "1/16",
    "1/16D",
    "1/8T",
    "1/8",
    "1/8D",
    "1/4"
};
The CalculateSyncScaler function is based on the one I posted earlier.

For each block, if the tempo has changed, I update the delay time:

Code: Select all

double scaler = SyncScalers[syncResolution];
double time = TempoToSecondsPerBeat(tempo / scaler);

SetDelayTime(time);
Things work as expected. Testing it in Cantabile using its metronome gives expected results.

Whew. I usually, when I try to nail down my understanding of a concept, I wind up confusing myself first. But things are clear now. :)

Post

mystran wrote:My synth ignores time-signature of the song, and claims a "whole note" (which is 4/4) instead of one bar to side-step that issue. Actually syncing to "one bar" opens a can of worms, 'cos then you might also have to deal with time-signature changes and what not. Dealing with tempo-changes is IMHO enough.
(sorry for the double reply, couldn't leave well enough alone)

Beats Per Minute

The Wikipedia article shows that beats per minute can be indicated by the beat duration in musical notation followed the number of beats per minute. A typical example is given with: quarter note = 120. It also says:

"In compound time signatures the beat consists of three note durations (so there are 3 quavers (eighth notes) per beat in a 6/8 time signature), so a dotted form of the next note duration up is used. The most common compound signatures: 6/8, 9/8, and 12/8, therefore use a dotted crotchet (dotted quarter note) to indicate their BPM."

This kinda gets back to my point about some time signatures representing a beat with a length other than a quarter note. With identical tempos, a quarter note will have a shorter timespan with a time signature of 6/8 than it would be at 4/4.

However, in the VST world, there's no indication of beat duration (we can infer it from the time signature, but should we?). So it would appear that we should always assume quarter note length for the beat.

Post

Leslie Sanford wrote: The Wikipedia article shows that beats per minute can be indicated by the beat duration in musical notation followed the number of beats per minute. A typical example is given with: quarter note = 120. It also says:
The trouble is, traditional musical concepts are not strictly defined in a sense that can be expressed mathematically. Also, even the Wikipedia article doesn't address cases with prime nominators, which unfortunate you sometimes have to deal with in real-world music.
This kinda gets back to my point about some time signatures representing a beat with a length other than a quarter note. With identical tempos, a quarter note will have a shorter timespan with a time signature of 6/8 than it would be at 4/4.
Except it isn't actually that easy either. If we had a musical piece where the signature of every other bar was 4/4 and every other bar 6/8, you would generally expect a quarter note to still have the same duration. This might seem esoteric at first, but changing signatures are actually not all that uncommon in classical music, not to mention progressive rock..

As I said above, musical concepts are not always strictly defined in a sensible way.
However, in the VST world, there's no indication of beat duration (we can infer it from the time signature, but should we?). So it would appear that we should always assume quarter note length for the beat.
Well, one could say it's defined in VST, but it's not stated explicitly. Tempo is defined just as BPM, but when you look at other stuff you'll see:
VstTimeInfo::ppqPos : At tempo 120, 1 quarter makes 1/2 second, so 2.0 ppq translates to 48000 samples at 48kHz sample rate. .25 ppq is one sixteenth note then. if you need something like 480ppq, you simply multiply ppq by that scaler.
And then description of ppqPos simply says:
double VstTimeInfo::ppqPos
Musical Position, in Quarter Note (1.0 equals 1 Quarter Note).
Together those only make sense if tempo is always in quarter notes.

But, now as I started actually investigating how such signatures are actually handled by hosts, I get the following results:

In FLStudio, changing the signature will actually keep the tempo relative to the beat division (140Bpm at 7/8 is 140 eights per minute), and ppqPos as reported by VstTimeInfo doesn't seem to follow the documentation.. instead ppqPos now becomes the position in eights (or /5 or /13 if you use a stupid signature). I'd quite confidently say this is a "bug" or "mis-feature" or whatever (see below). It also doesn't follow the Wikipedia definition either, so go figure.

In Live (I got lite of 6) changing the signature has absolutely no effect on what is reported to a plugin (which is what I think should happen based on the documentation). My Live only seems to support divisions by power-of-2 but tweaking the signature has no effect on anything, and time is indeed reported in quarter notes.

EnergyXT demo I've got I can't figure out if there's some way to modify signature (it seems like if you add a sequencer it says 1/1 but it doesn't seem possible to modify that... but then again I don't know how to use that program and the 1/1 might not even be a signature, it's just in a place where you might expect one). Since I can't tweak stuff, tempo seems quarter notes.

Savihost doesn't seem to have any concept of signatures at all, just a generic BPM tempo.

Currently that's all the hosts I've installed. I thought I had some demos/lite-versions of other stuff but must have uninstalled 'em (I think I have some lite/whatever of some Cakewalk stuff on CD that came with my MIDI keyboard, but haven't installed it obviously.. can try it another day if you want).

It would be interesting if someone with Cubase tested how that thing works, but I'd expect it reports ppqPos and tempo in quarter notes like the documentation says (or in case of tempo, can be assumed to mean).

So the result with Valo is that in FL song-mode LFOs will mis-sync to a new beat division of ppqPos (making a mess) but delay and stuff like that is unaffected by signature. Testing other plugins I have installed, none of them seems to try to sync to song-position, and as such are unaffected by signature changes (since reported tempo stays constant). When they report divisions, they all act like if tempo was in quarter notes though.

Now the selection of plugins I tried in FL includes demos of Image-Lines own plugins, say Poizone/Toxic for an example, and when they give a division (for arp/gate/whatever) on GUI they seem to act like if tempo was always in quarter notes, which further suggests that the position/tempo counting of FL is a mis-feature.

I'd still keep my position that you should assume tempo is always in quarter notes, since most everybody else seems to do this anyway.

As for the case of FL, I guess I'm going to investigate, and maybe check IL support (I'm a paying customer, I want my 13/37 time-signature to work properly god dammit!) whether they wanna argue that it isn't a bug. :D

Post

mystran wrote: As for the case of FL, I guess I'm going to investigate, and maybe check IL support (I'm a paying customer, I want my 13/37 time-signature to work properly god dammit!) whether they wanna argue that it isn't a bug. :D
HAHAHAHA, only after posting I realized the two random primes (honestly) I picked up would actually come out as something funny. Oh well.. just an offtopic remark.

Post

mystran wrote: Well, one could say it's defined in VST, but it's not stated explicitly. Tempo is defined just as BPM, but when you look at other stuff you'll see:
VstTimeInfo::ppqPos : At tempo 120, 1 quarter makes 1/2 second, so 2.0 ppq translates to 48000 samples at 48kHz sample rate. .25 ppq is one sixteenth note then. if you need something like 480ppq, you simply multiply ppq by that scaler.
And then description of ppqPos simply says:
double VstTimeInfo::ppqPos
Musical Position, in Quarter Note (1.0 equals 1 Quarter Note).
Together those only make sense if tempo is always in quarter notes.
You've convinced me. :) Thanks for your input on this. I appreciate it.

Post

mystran wrote:HAHAHAHA, only after posting I realized the two random primes (honestly) I picked up would actually come out as something funny. Oh well.. just an offtopic remark.
And off-MIDI, too.

MIDI Clock relies on solely on "BPM", i.e., "beats per minute" values; there are 24 clock ticks per "beat", and a beat is defined as a "quarter note" there... which is not really defined. There's no signature concept here.

MIDI Ticks (rarely met in real life) are sent every 10 milliseconds. Obviously no signature concept here, either.

MIDI Time Code relies on "frames", which specify the offset from the start point in SMPTE "frames", where a frame can be 1/24, 1/25, 1/30 drop, or 1/30 second. No signature concept here, either.

Standard MIDI files may contain a signature; this, however, is a bit restricted. Quoting from the "Web site on the Language of Gods":
FF 58 04 nn dd cc bb

Time signature is expressed as 4 numbers. nn and dd represent the "numerator" and "denominator" of the signature as notated on sheet music. The denominator is a negative power of 2: 2 = quarter note, 3 = eighth, etc.

The cc expresses the number of MIDI clocks in a metronome click.

The bb parameter expresses the number of notated 32nd notes in a MIDI quarter note (24 MIDI clocks). This event allows a program to relate what MIDI thinks of as a quarter, to something entirely different.

NOTE: If there are no time signature events in a MIDI file, then the time signature is assumed to be 4/4.
I.e., the only prime number you can put into a MIDI file as time signature denominator is 2.
"Until you spread your wings, you'll have no idea how far you can walk." Image

Post Reply

Return to “DSP and Plugin Development”