note to self: enable HTML escape characters in Article Titles

Although my days of touring in shoddy vans with smelly dudes playing loud things and yelling at nice people are (likely) over, I'm still a musician at heart. However, as most of my time lately has been dedicated to becoming an excellent software engineer as fast as humanly possible, I figured it would make a hell of a lot more sense for me to teach my computer to write music for me, rather than having to do it all myself.

Before I go all nuts figuring out how to teach TensorFlow to write Death Metal, I figured I'd start simple and focus on figuring out how to turn code into notes. Happily, it turns out this is a relatively simple task in this magic awesome future-world we're living in:

  1. Install Python
  2. Install the python-rtmidi library
  3. Start up GarageBand (or any other MIDI-compatible sequencer)
  4. That's pretty much it

Having written a grand total of around 20 lines of Python code prior to this experiment, I can safely say that the code for this project is intensely hackadellic. Fortunately, the resulting randomly-generated tune is a lot more pleasing to the ears than the sounds my guitar made when I was first learning to play. Of course, I gave the computer a few advantages:

  1. I restricted the available notes to the (C-minor?) scale, which basically made it impossible for the program to play anything too awful (see the MAJOR_SCALE variable & Scale class)
  2. I further restricted the available notes by creating a Chord and related set of CHORD_OPTIONS, allowing only standard I, III, IV, V, VI, and VII chords
  3. I defined a basic Arpeggio, which added considerably to the feels
  4. I kept the output to a single channel, and routed the same resulting notes to four different instruments to create both a pleasing backing pad and a main melodic lead

While the result is not particularly inspired, I am quite excited by the possibilities. Next up, I'll be doing a bit of design and refactoring, as well as switching to the significantly more capable Reaper (which I used extensively back in my recording days). This is way too much fun!

Music: Code:
import random
import time
import itertools
import rtmidi


class MidiPlayer(object):
    "this is a midi player"

    note_on_event = 0x90
    note_off_event = 0x80

    def __init__(self):
        self.midiout = rtmidi.MidiOut()
        self.midiout.open_virtual_port("My cool virtual output")

    def __del__(self):
        del self.midiout

    def play_note(self, note, duration_seconds=1, velocity=127):
        "this plays a note"
        if self.__can_play__():
            self.__play_note__(note, velocity)
            time.sleep(duration_seconds)

    def play_chord(self, chord, velocity=127):
        "this plays a chord"
        if self.__can_play__():
            internotedelay = 0.0125
            for note in chord:
                self.__play_note__(note, velocity)
                time.sleep(internotedelay)


    def play_arpeggio(self, arpeggio, repeats=0):
        "this plays an arpeggio"
        if self.__can_play__():

            initialvelocity = 127
            for _ in itertools.repeat(None, repeats + 1):
                for part in arpeggio.pattern:
                    note = arpeggio.chord[part - 1]
                    self.__play_note__(note, initialvelocity)
                    time.sleep(arpeggio.delay)
                    self.__stop_note__(note)
                    initialvelocity -= 15
                initialvelocity = 100

    def __can_play__(self):
        return self.midiout.is_port_open()

    def __play_note__(self, note, velocity):
        self.midiout.send_message([self.note_on_event, note, velocity])

    def __stop_note__(self, note):
        self.midiout.send_message([self.note_off_event, note, 0])


class Scale(object):
    "this is a scale"

    items = []
    length = 0

    def __init__(self, items):
        self.items = items
        self.length = len(items)

    def __getitem__(self, key):
        octave = key / self.length
        return self.items[key % self.length] + 12 * octave

    def __len__(self):
        return len(self.items)


class Chord(tuple):
    "this is a chord"

    def __new__(cls, notes):
        return super(Chord, cls).__new__(cls, tuple(notes))

    def __add__(self, num):
        return tuple(num + x for x in self)

    def __radd__(self, num):
        return tuple(num + x for x in self)

class Arpeggio(object):
    "this is an arpeggio"

    def __init__(self, chord, pattern, delay):
        self.chord = chord
        self.pattern = pattern
        self.delay = delay


MAJOR_SCALE = Scale([
    0, 2, 3, 5, 7, 8, 11, 12, 14, 15
])


if __name__ == "__main__":

    MYPLAYER = MidiPlayer()

    while True:
        CHORD_OPTIONS = (1, 3, 4, 5, 6, 7, 8)
        i = random.randint(0, len(CHORD_OPTIONS) - 1)
        SCALENOTE = CHORD_OPTIONS[i] - 1
        MY_CHORD = Chord(
            [
                MAJOR_SCALE[SCALENOTE],
                MAJOR_SCALE[SCALENOTE + 2],
                MAJOR_SCALE[SCALENOTE + 4]
            ]
        )
        MY_ARPEGGIO = Arpeggio(48 + MY_CHORD, (1, 2, 3, 2, 3, 2), 0.3)
        MYPLAYER.play_arpeggio(MY_ARPEGGIO, 1)
References: