Midi Sequencer in Python

(work in progress)

I’ve always thought it would be cool to be able to compose music in code. There are some languages out there that do this, but I was more interested in direct midi programming than in a DSL for composing music. Enter mido.

mido is a midi library for Python that wraps python-rtmidi in a friendlier API. It can do a lot of things, but I was primarily interested in sending notes to a synth in Bitwig Studio. But playing notes was more a side effect of wanting to explore some ideas around an API for composing music.

I started by defining a Note class.

Python
import mido

class Note:

    # http://bradthemad.org/guitar/tempo_explanation.php
    LENGTHS = {
        'w': 240,
        'h': 120,
        'q': 60,
        'e': 30,
        's': 15,
        't': 7.5,
        'dq': 90,
        'de': 45,
        'ds': 22.5,
        'tq': 40,
        'te': 20,
        'ts': 10
    }

    # what is the equivalent of '1' numeric duration value
    DURATION_MULTIPLIER = LENGTHS['q']

    def __init__(self, value:int, duration, multiplier=1, velocity:int=64):
        self.value = value
        self.duration = duration
        self.multiplier = multiplier
        self.velocity = velocity

    # Converts a note length value like dotted eighth to a value in seconds based on default or provided bpm tempo. 
    # Length abbrs: w, h, q, e, s, t, dq, de, ds, tq, te, ts (whole/half/quarter/eight/sixteenth/32nd, dotted/triplet)
    def seconds(self, tempo:int):
        if  type(self.duration) == str:
            if self.duration in Note.LENGTHS:
                len = Note.LENGTHS[self.duration]
                return len * self.multiplier / tempo
            else:
                return 0
        else:
            return self.duration * Note.DURATION_MULTIPLIER / tempo
        
    # Returns mido "note_on" message
    def get_on(self):
        print(f"On {self.value} {self.duration}")
        return mido.Message('note_on', note=self.value, velocity=self.velocity)

    # Returns mido "note_off" message
    def get_off(self):
        print(f"Off {self.value} {self.duration}")
        return mido.Message('note_off', note=self.value)