How to execute tempo changes in Mido?

130 views Asked by At

I'm attempting to create a simple Python script using Mido to execute the writing of a midi file that has tempo changes embedded. Right now I think I'm getting stuck on how to operate "delta time" when iterating over the different tempo changes. I'll post the code I've currently got. The issue I keep running into is that the first tempo change occurs at the correct time (referring to the first tempo CHANGE of 98 bpm, not the INITIAL tempo of 98b bpm, but all of the tempo changes after that) but all of the changes after that are too late and spread out. Any help is greatly appreciated:


import mido
from mido import MidiFile, MidiTrack, MetaMessage

def create_tempo_map_midi(positions, tempos, sample_rate=44100, output_midi_file='tempo_map.mid'):
    midi = MidiFile()
    track = MidiTrack()
    midi.tracks.append(track)
    ticks_per_beat = 480

    current_ticks = 0  # Track the cumulative ticks

    for position, tempo in zip(positions, tempos):
        bpm_to_microseconds = 60_000_000 / tempo  # Convert BPM to microseconds per beat
        time_seconds = position / sample_rate

        # Calculate the ticks directly without using mido.second2tick
        time_ticks = int(time_seconds * ticks_per_beat * tempo / 60)

        # Ensure that the calculated time_ticks is non-negative
        delta_ticks = max(0, time_ticks - current_ticks)

        # Add the tempo change event to the track
        track.append(MetaMessage('set_tempo', tempo=int(bpm_to_microseconds), time=delta_ticks))

        # Update current_ticks
        current_ticks = time_ticks

    # Save the MIDI file
    midi.save(output_midi_file)

if __name__ == "__main__":
    tempo_values = [98.0, 98.0, 101.5467, 103.3155, 105.0865, 106.8571, 108.6168, 110.3756, 112.1227, 113.8698, 115.6076, 117.3423, 119.0782, 120.8079, 122.5382, 124.2676, 126.0, 156.0, 156.0, 152.5883, 149.1766, 145.7649, 142.3532, 138.9415, 135.5298, 132.1181, 128.7064, 125.2947, 121.883, 118.4713, 115.0596, 111.6479, 108.2362, 104.8245, 98.0]
    pos_values = [0, 1404000, 1417500, 1430528, 1443333, 1455922, 1468303, 1480483, 1492469, 1504268, 1515886, 1527329, 1538603, 1549713, 1560664, 1571460, 1592242, 1592745, 8309514, 8317994, 8326664, 8335532, 8344608, 8353901, 8363422, 8373183, 8383196, 8393475, 8404034, 8414888, 8426055, 8437553, 8449402, 8461625, 8474246]
    create_tempo_map_midi(pos_values, tempo_values)

I've tried multiple variation of how to calculate and apply "delta_ticks". I'd expect the tempos to line up properly, but they don't.

1

There are 1 answers

1
aMike On

I'm not sure if this will answer your question. I just want to point out that there's a wonderful little book called "The Technology of Computer Music", written by Max Matthews (and other famous people you may recognize) in 1969, reprinted in 1974. (It's available as a PDF scan on several sites.)

On pages 87-90 he shows an example of an accelerando. It's all arcane Fortran-style field inputs needed for punched cards, but the math and equations are easily used today.

Here is his example re-done in Python:

'''
The Technology of Computer Music, Max Matthews, 1969, pages 87-90

duration = P4 * 60 / bpm sec
start time = computed start of last note + 
                  (start of this note - start of previous note) * 60 / bpm

See example in Fig.43 page 88.
'''                  

import math

bpm_score  = [60,60,60,60, 60,75,90,105, 120,120,120,120, 30,30,30]
note_start = [0,1,2,3,4,5,6,7,8,9,10,11,12,13]

# notes have same lengths, same durations
NOTE_DUR = 0.8

def main():
    start_time = 0              # start time of current note
    prev_note_start = 0
    for beat in range(len(note_start)):
        bpm = bpm_score[beat]
        dur = NOTE_DUR * 60 / bpm
        print("beat", beat, "bpm", bpm, "start", start_time, "dur", dur)
        start_time += (note_start[beat] - prev_note_start) * 60 / bpm
        prev_note_start = start_time

main()

And the output (edited for nicer columns):

beat  0 bpm  60 start  0     dur 0.8
beat  1 bpm  60 start  0.0   dur 0.8
beat  2 bpm  60 start  1.0   dur 0.8
beat  3 bpm  60 start  2.0   dur 0.8
beat  4 bpm  60 start  3.0   dur 0.8
beat  5 bpm  75 start  4.0   dur 0.64
beat  6 bpm  90 start  4.8   dur 0.533
beat  7 bpm 105 start  5.6   dur 0.457
beat  8 bpm 120 start  6.4   dur 0.4
beat  9 bpm 120 start  7.2   dur 0.4
beat 10 bpm 120 start  8.1   dur 0.4
beat 11 bpm 120 start  9.05  dur 0.4
beat 12 bpm  30 start 10.025 dur 1.6
beat 13 bpm  30 start 13.975 dur 1.6

I'm not sure how to use this with your problem, though. Perhaps it will help with the timing and spacing of the BPM markers? I hope at least it shows you something useful.

That was a good question -- I had fun working up the example.

Good luck!