midi stuff?

Will Ware wware at world.std.com
Sun Dec 26 02:24:35 EST 1999


I (wware at world.std.com) wrote:
> Is there any Python code floating around for generating MIDI
> files, starting from any sort of human-readable score?

The following works on a RedHat 6.1 system.

##################################################################

#!/usr/bin/env python
import os, sys, string

badNoteError = 'bad note error'

class Midi:
    def __init__(self, outf=sys.stdout):
        self.quarterNoteTime = 256
        self.outf = outf
    def dumpByte(self,b):
        self.outf.write(chr(b & 0xFF))
    def dumpInt16(self,i):
        self.dumpByte(i >> 8)
        self.dumpByte(i)
    def dumpInt(self,i):
        self.dumpInt16(i >> 16)
        self.dumpInt16(i)
    def dumpVariable(self,v):
        for b in self.variable(v):
            self.dumpByte(b)
    def variable(self,i,toobig=0):
        if i < 0x80:
            if toobig:
                return [0x80 + i,]
            else:
                return [i,]
        else:
            lowSeven = i & 0x7f
            return (self.variable(i >> 7, 1) +
                    self.variable(i & 0x7f, toobig))

class MidiFile(Midi):
    tracks = [ ]
    def register(self,track):
        self.tracks.append(track)
        return self.quarterNoteTime, self.outf
    def dump(self):
        # dump the header
        self.outf.write('MThd')
        self.dumpInt(6)   # six bytes follow
        self.dumpInt16(1)
        self.dumpInt16(len(self.tracks))
        self.dumpInt16(self.quarterNoteTime)
        for t in self.tracks:
            t.dump()
        self.outf.close()

class Track(Midi):
    def __init__(self,channel,owner):
        self.data = [ ]
        self.channel = channel - 1
        self.quarterNoteTime, self.outf = owner.register(self)
        self.currentNoteLength = 1.
        self.currentOctave = 60
        self.pendingRest = 0.
    def dump(self):
        self.outf.write('MTrk')
        self.dumpInt(len(self.data))
        for b in self.data:
            self.dumpByte(b)
    def event(self, time, bytes):
        self.data = self.data + self.variable(time) + bytes
    def note(self, pitch, duration):
        self.event(int(self.pendingRest * self.quarterNoteTime),
                   [0x90 + self.channel, pitch, 127])
        self.pendingRest = 0
        self.event(int(duration * self.quarterNoteTime),
                   [0x80 + self.channel, pitch, 127])
    def parseNote(self, str):
        pitchOffset = 0
        rest = 0
        while str:
            c = str[0]
            str = str[1:]
            if c == '+':    # octave up
                self.currentOctave = self.currentOctave + 12
            elif c == '-':  # octave down
                self.currentOctave = self.currentOctave - 12
            elif c == '#':  # sharp
                pitchOffset = pitchOffset + 1
            elif c == '@':  # flat
                pitchOffset = pitchOffset - 1
            # numbers are translated to note lengths, measured
            # in multiples of a quarter note
            elif (c >= '0' and c <= '9') or c == '.':
                while (len(str) > 0 and
                       (str[0] == '.' or 
                        (str[0] >= '0' and str[0] <= '9'))):
                    c = c + str[0]
                    str = str[1:]
                # this number is a note length in quarter notes
                self.currentNoteLength = eval(c)
            # here are note names (A-G), it would be nice if
            # the key got picked up automatically here
            elif c >= 'a' and c <= 'g':
                pitchOffset = pitchOffset + \
                              {'c': 0, 'd': 2, 'e': 4, 'f': 5,
                               'g': 7, 'a': 9, 'b': 11}[c]
            elif c >= 'A' and c <= 'G':
                pitchOffset = pitchOffset + \
                              {'C': 0, 'D': 2, 'E': 4, 'F': 5,
                               'G': 7, 'A': 9, 'B': 11}[c]
            # rests
            elif c == 'r' or c == 'R':
                rest = 1
            else:
                raise badNoteError
        if rest:
            self.pendingRest = self.pendingRest + self.currentNoteLength
        else:
            self.note(self.currentOctave + pitchOffset,
                      self.currentNoteLength)
    def parseVoice(self, str):
        for s in string.split(str):
            self.parseNote(s)

mf = MidiFile(open('foo.mid', 'w'))
t1 = Track(1, mf)
t2 = Track(2, mf)
# the first few notes of the Crab Canon
t1.parseVoice('c2 e@ g a@ b-3 g+1 f#2 f e e at 3 d1 d@ c b- a.5 g c1+ f e at 2 d c')
t2.parseVoice('''
c+ e@ g c+ b-.5 c+ d e@ f e@ d c d g- d+ f e@ d c b- a b c+ e@ d c
b-
''')

mf.dump()
# playmidi loses last notes sometimes, xplaymidi doesn't, I dunno why
os.system('xplaymidi foo.mid')

############################################################
-- 
 - - - - - - - - - - - - - - - - - - - - - - - -
Resistance is futile. Capacitance is efficacious.
Will Ware	email:    wware @ world.std.com



More information about the Python-list mailing list