Bagaag

A midi sequencer in Python

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.

 1import mido
 2
 3class Note:
 4
 5    # http://bradthemad.org/guitar/tempo_explanation.php
 6    LENGTHS = {
 7        'w': 240,
 8        'h': 120,
 9        'q': 60,
10        'e': 30,
11        's': 15,
12        't': 7.5,
13        'dq': 90,
14        'de': 45,
15        'ds': 22.5,
16        'tq': 40,
17        'te': 20,
18        'ts': 10
19    }
20
21    # what is the equivalent of '1' numeric duration value
22    DURATION_MULTIPLIER = LENGTHS['q']
23
24    def __init__(self, value:int, duration, multiplier=1, velocity:int=64):
25        self.value = value
26        self.duration = duration
27        self.multiplier = multiplier
28        self.velocity = velocity
29
30    # Converts a note length value like dotted eighth to a value in seconds based on default or provided bpm tempo. 
31    # Length abbrs: w, h, q, e, s, t, dq, de, ds, tq, te, ts (whole/half/quarter/eight/sixteenth/32nd, dotted/triplet)
32    def seconds(self, tempo:int):
33        if  type(self.duration) == str:
34            if self.duration in Note.LENGTHS:
35                len = Note.LENGTHS[self.duration]
36                return len * self.multiplier / tempo
37            else:
38                return 0
39        else:
40            return self.duration * Note.DURATION_MULTIPLIER / tempo
41        
42    # Returns mido "note_on" message
43    def get_on(self):
44        print(f"On {self.value} {self.duration}")
45        return mido.Message('note_on', note=self.value, velocity=self.velocity)
46
47    # Returns mido "note_off" message
48    def get_off(self):
49        print(f"Off {self.value} {self.duration}")
50        return mido.Message('note_off', note=self.value)

#Python