neingeist
/
arduinisten
Archived
1
0
Fork 0

add midi fnord

master
entropia 15 years ago
parent 8ed4510a4e
commit 5d93efa09c

@ -0,0 +1,25 @@
import sys
sys.path.append("midi")
from MidiOutStream import MidiOutStream
from MidiInFile import MidiInFile
"""
This prints all note on events on midi channel 0
"""
class Transposer(MidiOutStream):
"Transposes all notes by 1 octave"
def note_on(self, channel=0, note=0x40, velocity=0x40):
if channel == 0:
print channel, note, velocity, self.rel_time()
event_handler = Transposer()
in_file = 'nevergoingtoU.mid'
midi_in = MidiInFile(event_handler, in_file)
midi_in.read()

@ -0,0 +1,217 @@
# -*- coding: ISO-8859-1 -*-
from struct import pack, unpack
"""
This module contains functions for reading and writing the special data types
that a midi file contains.
"""
"""
nibbles are four bits. A byte consists of two nibles.
hiBits==0xF0, loBits==0x0F Especially used for setting
channel and event in 1. byte of musical midi events
"""
def getNibbles(byte):
"""
Returns hi and lo bits in a byte as a tuple
>>> getNibbles(142)
(8, 14)
Asserts byte value in byte range
>>> getNibbles(256)
Traceback (most recent call last):
...
ValueError: Byte value out of range 0-255: 256
"""
if not 0 <= byte <= 255:
raise ValueError('Byte value out of range 0-255: %s' % byte)
return (byte >> 4 & 0xF, byte & 0xF)
def setNibbles(hiNibble, loNibble):
"""
Returns byte with value set according to hi and lo bits
Asserts hiNibble and loNibble in range(16)
>>> setNibbles(8, 14)
142
>>> setNibbles(8, 16)
Traceback (most recent call last):
...
ValueError: Nible value out of range 0-15: (8, 16)
"""
if not (0 <= hiNibble <= 15) or not (0 <= loNibble <= 15):
raise ValueError('Nible value out of range 0-15: (%s, %s)' % (hiNibble, loNibble))
return (hiNibble << 4) + loNibble
def readBew(value):
"""
Reads string as big endian word, (asserts len(value) in [1,2,4])
>>> readBew('aáâã')
1642193635L
>>> readBew('')
25057
"""
return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0]
def writeBew(value, length):
"""
Write int as big endian formatted string, (asserts length in [1,2,4])
Difficult to print the result in doctest, so I do a simple roundabout test.
>>> readBew(writeBew(25057, 2))
25057
>>> readBew(writeBew(1642193635L, 4))
1642193635L
"""
return pack('>%s' % {1:'B', 2:'H', 4:'L'}[length], value)
"""
Variable Length Data (varlen) is a data format sprayed liberally throughout
a midi file. It can be anywhere from 1 to 4 bytes long.
If the 8'th bit is set in a byte another byte follows. The value is stored
in the lowest 7 bits of each byte. So max value is 4x7 bits = 28 bits.
"""
def readVar(value):
"""
Converts varlength format to integer. Just pass it 0 or more chars that
might be a varlen and it will only use the relevant chars.
use varLen(readVar(value)) to see how many bytes the integer value takes.
asserts len(value) >= 0
>>> readVar('€@')
64
>>> readVar('áâãa')
205042145
"""
sum = 0
for byte in unpack('%sB' % len(value), value):
sum = (sum << 7) + (byte & 0x7F)
if not 0x80 & byte: break # stop after last byte
return sum
def varLen(value):
"""
Returns the the number of bytes an integer will be when
converted to varlength
"""
if value <= 127:
return 1
elif value <= 16383:
return 2
elif value <= 2097151:
return 3
else:
return 4
def writeVar(value):
"Converts an integer to varlength format"
sevens = to_n_bits(value, varLen(value))
for i in range(len(sevens)-1):
sevens[i] = sevens[i] | 0x80
return fromBytes(sevens)
def to_n_bits(value, length=1, nbits=7):
"returns the integer value as a sequence of nbits bytes"
bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)]
bytes.reverse()
return bytes
def toBytes(value):
"Turns a string into a list of byte values"
return unpack('%sB' % len(value), value)
def fromBytes(value):
"Turns a list of bytes into a string"
if not value:
return ''
return pack('%sB' % len(value), *value)
if __name__ == '__main__':
# print to7bits(0, 3)
# print to7bits(127, 3)
# print to7bits(255, 3)
# print to7bits(65536, 3)
# simple test cases
# print 'getHiLoHex', getNibbles(16)
# print 'setHiLoHex', setNibbles(1,0)
#
# print 'readBew', readBew('aáâã')
# print 'writeBew', writeBew(1642193635, 4)
#
# print 'varLen', varLen(1)
#
print 'readVar', readVar('€@')
print 'writeVar', writeVar(8192)
print 'readVar', readVar('áâãa')
print 'writeVar', writeVar(205058401)
#
# vartest = '\x82\xF7\x80\x00'
# print 'toBytes', toBytes(vartest)
# print 'fromBytes', fromBytes([48, 49, 50,])
# instr = '\xFF\xFF\xFF\x00'
# print 'readVar', readVar(instr)
# inst2 = 268435455
# print inst2
# print writeVar(inst2)
# print writeVar(readVar(instr))
s1 = 0x00000000
print '%08X -' % s1, '00', writeVar(s1)
s2 = 0x00000040
print '%08X -' % s2, '40', writeVar(s2)
s3 = 0x0000007F
print '%08X -' % s3, '7F', writeVar(s3)
s4 = 0x00000080
print '%08X -' % s4, '81 00', writeVar(s4)
s5 = 0x00002000
print '%08X -' % s5, 'C0 00', writeVar(s5)
s6 = 0x00003FFF
print '%08X -' % s6, 'FF 7F', writeVar(s6)
s7 = 0x00004000
print '%08X -' % s7, '81 80 00', writeVar(s7)
s8 = 0x00100000
print '%08X -' % s8, 'C0 80 00', writeVar(s8)
s9 = 0x001FFFFF
print '%08X -' % s9, 'FF FF 7F', writeVar(s9)
s10 = 0x00200000
print '%08X -' % s10, '81 80 80 00', writeVar(s10)
s11 = 0x08000000
print '%08X -' % s11, 'C0 80 80 00', writeVar(s11)
s12 = 0x0FFFFFFF
print '%08X -' % s12, 'FF FF FF 7F', writeVar(s12)

@ -0,0 +1,287 @@
# -*- coding: ISO-8859-1 -*-
# std library
from struct import unpack
# custom
from DataTypeConverters import readBew, readVar, varLen, toBytes
# uhh I don't really like this, but there are so many constants to
# import otherwise
from constants import *
class EventDispatcher:
def __init__(self, outstream):
"""
The event dispatcher generates events on the outstream.
"""
# internal values, don't mess with 'em directly
self.outstream = outstream
# public flags
# A note_on with a velocity of 0x00 is actually the same as a
# note_off with a velocity of 0x40. When
# "convert_zero_velocity" is set, the zero velocity note_on's
# automatically gets converted into note_off's. This is a less
# suprising behaviour for those that are not into the intimate
# details of the midi spec.
self.convert_zero_velocity = 1
# If dispatch_continuos_controllers is true, continuos
# controllers gets dispatched to their defined handlers. Else
# they just trigger the "continuous_controller" event handler.
self.dispatch_continuos_controllers = 1 # NOT IMPLEMENTED YET
# If dispatch_meta_events is true, meta events get's dispatched
# to their defined events. Else they all they trigger the
# "meta_event" handler.
self.dispatch_meta_events = 1
def header(self, format, nTracks, division):
"Triggers the header event"
self.outstream.header(format, nTracks, division)
def start_of_track(self, current_track):
"Triggers the start of track event"
# I do this twice so that users can overwrite the
# start_of_track event handler without worrying whether the
# track number is updated correctly.
self.outstream.set_current_track(current_track)
self.outstream.start_of_track(current_track)
def sysex_event(self, data):
"Dispatcher for sysex events"
self.outstream.sysex_event(data)
def eof(self):
"End of file!"
self.outstream.eof()
def update_time(self, new_time=0, relative=1):
"Updates relative/absolute time."
self.outstream.update_time(new_time, relative)
def reset_time(self):
"Updates relative/absolute time."
self.outstream.reset_time()
# Event dispatchers for similar types of events
def channel_messages(self, hi_nible, channel, data):
"Dispatches channel messages"
stream = self.outstream
data = toBytes(data)
if (NOTE_ON & 0xF0) == hi_nible:
note, velocity = data
# note_on with velocity 0x00 are same as note
# off with velocity 0x40 according to spec!
if velocity==0 and self.convert_zero_velocity:
stream.note_off(channel, note, 0x40)
else:
stream.note_on(channel, note, velocity)
elif (NOTE_OFF & 0xF0) == hi_nible:
note, velocity = data
stream.note_off(channel, note, velocity)
elif (AFTERTOUCH & 0xF0) == hi_nible:
note, velocity = data
stream.aftertouch(channel, note, velocity)
elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible:
controller, value = data
# A lot of the cc's are defined, so we trigger those directly
if self.dispatch_continuos_controllers:
self.continuous_controllers(channel, controller, value)
else:
stream.continuous_controller(channel, controller, value)
elif (PATCH_CHANGE & 0xF0) == hi_nible:
program = data[0]
stream.patch_change(channel, program)
elif (CHANNEL_PRESSURE & 0xF0) == hi_nible:
pressure = data[0]
stream.channel_pressure(channel, pressure)
elif (PITCH_BEND & 0xF0) == hi_nible:
hibyte, lobyte = data
value = (hibyte<<7) + lobyte
stream.pitch_bend(channel, value)
else:
raise ValueError, 'Illegal channel message!'
def continuous_controllers(self, channel, controller, value):
"Dispatches channel messages"
stream = self.outstream
# I am not really shure if I ought to dispatch continuous controllers
# There's so many of them that it can clutter up the OutStream
# classes.
# So I just trigger the default event handler
stream.continuous_controller(channel, controller, value)
def system_commons(self, common_type, common_data):
"Dispatches system common messages"
stream = self.outstream
# MTC Midi time code Quarter value
if common_type == MTC:
data = readBew(common_data)
msg_type = (data & 0x07) >> 4
values = (data & 0x0F)
stream.midi_time_code(msg_type, values)
elif common_type == SONG_POSITION_POINTER:
hibyte, lobyte = toBytes(common_data)
value = (hibyte<<7) + lobyte
stream.song_position_pointer(value)
elif common_type == SONG_SELECT:
data = readBew(common_data)
stream.song_select(data)
elif common_type == TUNING_REQUEST:
# no data then
stream.tuning_request(time=None)
def meta_event(self, meta_type, data):
"Dispatches meta events"
stream = self.outstream
# SEQUENCE_NUMBER = 0x00 (00 02 ss ss (seq-number))
if meta_type == SEQUENCE_NUMBER:
number = readBew(data)
stream.sequence_number(number)
# TEXT = 0x01 (01 len text...)
elif meta_type == TEXT:
stream.text(data)
# COPYRIGHT = 0x02 (02 len text...)
elif meta_type == COPYRIGHT:
stream.copyright(data)
# SEQUENCE_NAME = 0x03 (03 len text...)
elif meta_type == SEQUENCE_NAME:
stream.sequence_name(data)
# INSTRUMENT_NAME = 0x04 (04 len text...)
elif meta_type == INSTRUMENT_NAME:
stream.instrument_name(data)
# LYRIC = 0x05 (05 len text...)
elif meta_type == LYRIC:
stream.lyric(data)
# MARKER = 0x06 (06 len text...)
elif meta_type == MARKER:
stream.marker(data)
# CUEPOINT = 0x07 (07 len text...)
elif meta_type == CUEPOINT:
stream.cuepoint(data)
# PROGRAM_NAME = 0x08 (05 len text...)
elif meta_type == PROGRAM_NAME:
stream.program_name(data)
# DEVICE_NAME = 0x09 (09 len text...)
elif meta_type == DEVICE_NAME:
stream.device_name(data)
# MIDI_CH_PREFIX = 0x20 (20 01 channel)
elif meta_type == MIDI_CH_PREFIX:
channel = readBew(data)
stream.midi_ch_prefix(channel)
# MIDI_PORT = 0x21 (21 01 port (legacy stuff))
elif meta_type == MIDI_PORT:
port = readBew(data)
stream.midi_port(port)
# END_OFF_TRACK = 0x2F (2F 00)
elif meta_type == END_OF_TRACK:
stream.end_of_track()
# TEMPO = 0x51 (51 03 tt tt tt (tempo in us/quarternote))
elif meta_type == TEMPO:
b1, b2, b3 = toBytes(data)
# uses 3 bytes to represent time between quarter
# notes in microseconds
stream.tempo((b1<<16) + (b2<<8) + b3)
# SMTP_OFFSET = 0x54 (54 05 hh mm ss ff xx)
elif meta_type == SMTP_OFFSET:
hour, minute, second, frame, framePart = toBytes(data)
stream.smtp_offset(
hour, minute, second, frame, framePart)
# TIME_SIGNATURE = 0x58 (58 04 nn dd cc bb)
elif meta_type == TIME_SIGNATURE:
nn, dd, cc, bb = toBytes(data)
stream.time_signature(nn, dd, cc, bb)
# KEY_SIGNATURE = 0x59 (59 02 sf mi)
elif meta_type == KEY_SIGNATURE:
sf, mi = toBytes(data)
stream.key_signature(sf, mi)
# SPECIFIC = 0x7F (Sequencer specific event)
elif meta_type == SPECIFIC:
meta_data = toBytes(data)
stream.sequencer_specific(meta_data)
# Handles any undefined meta events
else: # undefined meta type
meta_data = toBytes(data)
stream.meta_event(meta_type, meta_data)
if __name__ == '__main__':
from MidiToText import MidiToText
outstream = MidiToText()
dispatcher = EventDispatcher(outstream)
dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')

@ -0,0 +1,192 @@
# -*- coding: ISO-8859-1 -*-
# std library
from struct import unpack
# uhh I don't really like this, but there are so many constants to
# import otherwise
from constants import *
from EventDispatcher import EventDispatcher
class MidiFileParser:
"""
The MidiFileParser is the lowest level parser that see the data as
midi data. It generates events that gets triggered on the outstream.
"""
def __init__(self, raw_in, outstream):
"""
raw_data is the raw content of a midi file as a string.
"""
# internal values, don't mess with 'em directly
self.raw_in = raw_in
self.dispatch = EventDispatcher(outstream)
# Used to keep track of stuff
self._running_status = None
def parseMThdChunk(self):
"Parses the header chunk"
raw_in = self.raw_in
header_chunk_type = raw_in.nextSlice(4)
header_chunk_zise = raw_in.readBew(4)
# check if it is a proper midi file
if header_chunk_type != 'MThd':
raise TypeError, "It is not a valid midi file!"
# Header values are at fixed locations, so no reason to be clever
self.format = raw_in.readBew(2)
self.nTracks = raw_in.readBew(2)
self.division = raw_in.readBew(2)
# Theoretically a header larger than 6 bytes can exist
# but no one has seen one in the wild
# But correctly ignore unknown data if it is though
if header_chunk_zise > 6:
raw_in.moveCursor(header_chunk_zise-6)
# call the header event handler on the stream
self.dispatch.header(self.format, self.nTracks, self.division)
def parseMTrkChunk(self):
"Parses a track chunk. This is the most important part of the parser."
# set time to 0 at start of a track
self.dispatch.reset_time()
dispatch = self.dispatch
raw_in = self.raw_in
# Trigger event at the start of a track
dispatch.start_of_track(self._current_track)
# position cursor after track header
raw_in.moveCursor(4)
# unsigned long is 4 bytes
tracklength = raw_in.readBew(4)
track_endposition = raw_in.getCursor() + tracklength # absolute position!
while raw_in.getCursor() < track_endposition:
# find relative time of the event
time = raw_in.readVarLen()
dispatch.update_time(time)
# be aware of running status!!!!
peak_ahead = raw_in.readBew(move_cursor=0)
if (peak_ahead & 0x80):
# the status byte has the high bit set, so it
# was not running data but proper status byte
status = self._running_status = raw_in.readBew()
else:
# use that darn running status
status = self._running_status
# could it be illegal data ?? Do we need to test for that?
# I need more example midi files to be shure.
# Also, while I am almost certain that no realtime
# messages will pop up in a midi file, I might need to
# change my mind later.
# we need to look at nibbles here
hi_nible, lo_nible = status & 0xF0, status & 0x0F
# match up with events
# Is it a meta_event ??
# these only exists in midi files, not in transmitted midi data
# In transmitted data META_EVENT (0xFF) is a system reset
if status == META_EVENT:
meta_type = raw_in.readBew()
meta_length = raw_in.readVarLen()
meta_data = raw_in.nextSlice(meta_length)
dispatch.meta_event(meta_type, meta_data)
# Is it a sysex_event ??
elif status == SYSTEM_EXCLUSIVE:
# ignore sysex events
sysex_length = raw_in.readVarLen()
# don't read sysex terminator
sysex_data = raw_in.nextSlice(sysex_length-1)
# only read last data byte if it is a sysex terminator
# It should allways be there, but better safe than sorry
if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:
eo_sysex = raw_in.readBew()
dispatch.sysex_event(sysex_data)
# the sysex code has not been properly tested, and might be fishy!
# is it a system common event?
elif hi_nible == 0xF0: # Hi bits are set then
data_sizes = {
MTC:1,
SONG_POSITION_POINTER:2,
SONG_SELECT:1,
}
data_size = data_sizes.get(hi_nible, 0)
common_data = raw_in.nextSlice(data_size)
common_type = lo_nible
dispatch.system_common(common_type, common_data)
# Oh! Then it must be a midi event (channel voice message)
else:
data_sizes = {
PATCH_CHANGE:1,
CHANNEL_PRESSURE:1,
NOTE_OFF:2,
NOTE_ON:2,
AFTERTOUCH:2,
CONTINUOUS_CONTROLLER:2,
PITCH_BEND:2,
}
data_size = data_sizes.get(hi_nible, 0)
channel_data = raw_in.nextSlice(data_size)
event_type, channel = hi_nible, lo_nible
dispatch.channel_messages(event_type, channel, channel_data)
def parseMTrkChunks(self):
"Parses all track chunks."
for t in range(self.nTracks):
self._current_track = t
self.parseMTrkChunk() # this is where it's at!
self.dispatch.eof()
if __name__ == '__main__':
# get data
test_file = 'test/midifiles/minimal.mid'
test_file = 'test/midifiles/cubase-minimal.mid'
test_file = 'test/midifiles/Lola.mid'
# f = open(test_file, 'rb')
# raw_data = f.read()
# f.close()
#
#
# # do parsing
from MidiToText import MidiToText
from RawInstreamFile import RawInstreamFile
midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText())
midi_in.parseMThdChunk()
midi_in.parseMTrkChunks()

@ -0,0 +1,55 @@
# -*- coding: ISO-8859-1 -*-
from RawInstreamFile import RawInstreamFile
from MidiFileParser import MidiFileParser
class MidiInFile:
"""
Parses a midi file, and triggers the midi events on the outStream
object.
Get example data from a minimal midi file, generated with cubase.
>>> test_file = 'C:/Documents and Settings/maxm/Desktop/temp/midi/src/midi/tests/midifiles/minimal-cubase-type0.mid'
Do parsing, and generate events with MidiToText,
so we can see what a minimal midi file contains
>>> from MidiToText import MidiToText
>>> midi_in = MidiInFile(MidiToText(), test_file)
>>> midi_in.read()
format: 0, nTracks: 1, division: 480
----------------------------------
<BLANKLINE>
Start - track #0
sequence_name: Type 0
tempo: 500000
time_signature: 4 2 24 8
note_on - ch:00, note:48, vel:64 time:0
note_off - ch:00, note:48, vel:40 time:480
End of track
<BLANKLINE>
End of file
"""
def __init__(self, outStream, infile):
# these could also have been mixins, would that be better? Nah!
self.raw_in = RawInstreamFile(infile)
self.parser = MidiFileParser(self.raw_in, outStream)
def read(self):
"Start parsing the file"
p = self.parser
p.parseMThdChunk()
p.parseMTrkChunks()
def setData(self, data=''):
"Sets the data from a plain string"
self.raw_in.setData(data)

@ -0,0 +1,52 @@
# -*- coding: ISO-8859-1 -*-
from MidiOutStream import MidiOutStream
class MidiInStream:
"""
Takes midi events from the midi input and calls the apropriate
method in the eventhandler object
"""
def __init__(self, midiOutStream, device):
"""
Sets a default output stream, and sets the device from where
the input comes
"""
if midiOutStream is None:
self.midiOutStream = MidiOutStream()
else:
self.midiOutStream = midiOutStream
def close(self):
"""
Stop the MidiInstream
"""
def read(self, time=0):
"""
Start the MidiInstream.
"time" sets timer to specific start value.
"""
def resetTimer(self, time=0):
"""
Resets the timer, probably a good idea if there is some kind
of looping going on
"""

@ -0,0 +1,448 @@
# -*- coding: ISO-8859-1 -*-
from MidiOutStream import MidiOutStream
from RawOutstreamFile import RawOutstreamFile
from constants import *
from DataTypeConverters import fromBytes, writeVar
class MidiOutFile(MidiOutStream):
"""
MidiOutFile is an eventhandler that subclasses MidiOutStream.
"""
def __init__(self, raw_out=''):
self.raw_out = RawOutstreamFile(raw_out)
MidiOutStream.__init__(self)
def write(self):
self.raw_out.write()
def event_slice(self, slc):
"""
Writes the slice of an event to the current track. Correctly
inserting a varlen timestamp too.
"""
trk = self._current_track_buffer
trk.writeVarLen(self.rel_time())
trk.writeSlice(slc)
#####################
## Midi events
def note_on(self, channel=0, note=0x40, velocity=0x40):
"""
channel: 0-15
note, velocity: 0-127
"""
slc = fromBytes([NOTE_ON + channel, note, velocity])
self.event_slice(slc)
def note_off(self, channel=0, note=0x40, velocity=0x40):
"""
channel: 0-15
note, velocity: 0-127
"""
slc = fromBytes([NOTE_OFF + channel, note, velocity])
self.event_slice(slc)
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
"""
channel: 0-15
note, velocity: 0-127
"""
slc = fromBytes([AFTERTOUCH + channel, note, velocity])
self.event_slice(slc)
def continuous_controller(self, channel, controller, value):
"""
channel: 0-15
controller, value: 0-127
"""
slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])
self.event_slice(slc)
# These should probably be implemented
# http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums
def patch_change(self, channel, patch):
"""
channel: 0-15
patch: 0-127
"""
slc = fromBytes([PATCH_CHANGE + channel, patch])
self.event_slice(slc)
def channel_pressure(self, channel, pressure):
"""
channel: 0-15
pressure: 0-127
"""
slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])
self.event_slice(slc)
def pitch_bend(self, channel, value):
"""
channel: 0-15
value: 0-16383
"""
msb = (value>>7) & 0xFF
lsb = value & 0xFF
slc = fromBytes([PITCH_BEND + channel, msb, lsb])
self.event_slice(slc)
#####################
## System Exclusive
# def sysex_slice(sysex_type, data):
# ""
# sysex_len = writeVar(len(data)+1)
# self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE)
#
def system_exclusive(self, data):
"""
data: list of values in range(128)
"""
sysex_len = writeVar(len(data)+1)
self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))
#####################
## Common events
def midi_time_code(self, msg_type, values):
"""
msg_type: 0-7
values: 0-15
"""
value = (msg_type<<4) + values
self.event_slice(fromBytes([MIDI_TIME_CODE, value]))
def song_position_pointer(self, value):
"""
value: 0-16383
"""
lsb = (value & 0x7F)
msb = (value >> 7) & 0x7F
self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))
def song_select(self, songNumber):
"""
songNumber: 0-127
"""
self.event_slice(fromBytes([SONG_SELECT, songNumber]))
def tuning_request(self):
"""
No values passed
"""
self.event_slice(chr(TUNING_REQUEST))
#########################
# header does not really belong here. But anyhoo!!!
def header(self, format=0, nTracks=1, division=96):
"""
format: type of midi file in [0,1,2]
nTracks: number of tracks. 1 track for type 0 file
division: timing division ie. 96 ppq.
"""
raw = self.raw_out
raw.writeSlice('MThd')
bew = raw.writeBew
bew(6, 4) # header size
bew(format, 2)
bew(nTracks, 2)
bew(division, 2)
def eof(self):
"""
End of file. No more events to be processed.
"""
# just write the file then.
self.write()
#####################
## meta events
def meta_slice(self, meta_type, data_slice):
"Writes a meta event"
slc = fromBytes([META_EVENT, meta_type]) + \
writeVar(len(data_slice)) + data_slice
self.event_slice(slc)
def meta_event(self, meta_type, data):
"""
Handles any undefined meta events
"""
self.meta_slice(meta_type, fromBytes(data))
def start_of_track(self, n_track=0):
"""
n_track: number of track
"""
self._current_track_buffer = RawOutstreamFile()
self.reset_time()
self._current_track += 1
def end_of_track(self):
"""
Writes the track to the buffer.
"""
raw = self.raw_out
raw.writeSlice(TRACK_HEADER)
track_data = self._current_track_buffer.getvalue()
# wee need to know size of track data.
eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])
raw.writeBew(len(track_data)+len(eot_slice), 4)
# then write
raw.writeSlice(track_data)
raw.writeSlice(eot_slice)
def sequence_number(self, value):
"""
value: 0-65535
"""
self.meta_slice(meta_type, writeBew(value, 2))
def text(self, text):
"""
Text event
text: string
"""
self.meta_slice(TEXT, text)
def copyright(self, text):
"""
Copyright notice
text: string
"""
self.meta_slice(COPYRIGHT, text)
def sequence_name(self, text):
"""
Sequence/track name
text: string
"""
self.meta_slice(SEQUENCE_NAME, text)
def instrument_name(self, text):
"""
text: string
"""
self.meta_slice(INSTRUMENT_NAME, text)
def lyric(self, text):
"""
text: string
"""
self.meta_slice(LYRIC, text)
def marker(self, text):
"""
text: string
"""
self.meta_slice(MARKER, text)
def cuepoint(self, text):
"""
text: string
"""
self.meta_slice(CUEPOINT, text)
def midi_ch_prefix(self, channel):
"""
channel: midi channel for subsequent data
(deprecated in the spec)
"""
self.meta_slice(MIDI_CH_PREFIX, chr(channel))
def midi_port(self, value):
"""
value: Midi port (deprecated in the spec)
"""
self.meta_slice(MIDI_CH_PREFIX, chr(value))
def tempo(self, value):
"""
value: 0-2097151
tempo in us/quarternote
(to calculate value from bpm: int(60,000,000.00 / BPM))
"""
hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)
self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))
def smtp_offset(self, hour, minute, second, frame, framePart):
"""
hour,
minute,
second: 3 bytes specifying the hour (0-23), minutes (0-59) and
seconds (0-59), respectively. The hour should be
encoded with the SMPTE format, just as it is in MIDI
Time Code.
frame: A byte specifying the number of frames per second (one
of : 24, 25, 29, 30).
framePart: A byte specifying the number of fractional frames,
in 100ths of a frame (even in SMPTE-based tracks
using a different frame subdivision, defined in the
MThd chunk).
"""
self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))
def time_signature(self, nn, dd, cc, bb):
"""
nn: Numerator of the signature as notated on sheet music
dd: Denominator of the signature as notated on sheet music
The denominator is a negative power of 2: 2 = quarter
note, 3 = eighth, etc.
cc: The number of MIDI clocks in a metronome click
bb: The number of notated 32nd notes in a MIDI quarter note
(24 MIDI clocks)
"""
self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))
def key_signature(self, sf, mi):
"""
sf: is a byte specifying the number of flats (-ve) or sharps
(+ve) that identifies the key signature (-7 = 7 flats, -1
= 1 flat, 0 = key of C, 1 = 1 sharp, etc).
mi: is a byte specifying a major (0) or minor (1) key.
"""
self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))
def sequencer_specific(self, data):
"""
data: The data as byte values
"""
self.meta_slice(SEQUENCER_SPECIFIC, data)
# #####################
# ## realtime events
# These are of no use in a midi file, so they are ignored!!!
# def timing_clock(self):
# def song_start(self):
# def song_stop(self):
# def song_continue(self):
# def active_sensing(self):
# def system_reset(self):
if __name__ == '__main__':
out_file = 'test/midifiles/midiout.mid'
midi = MidiOutFile(out_file)
#format: 0, nTracks: 1, division: 480
#----------------------------------
#
#Start - track #0
#sequence_name: Type 0
#tempo: 500000
#time_signature: 4 2 24 8
#note_on - ch:00, note:48, vel:64 time:0
#note_off - ch:00, note:48, vel:40 time:480
#End of track
#
#End of file
midi.header(0, 1, 480)
midi.start_of_track()
midi.sequence_name('Type 0')
midi.tempo(750000)
midi.time_signature(4, 2, 24, 8)
ch = 0
for i in range(127):
midi.note_on(ch, i, 0x64)
midi.update_time(96)
midi.note_off(ch, i, 0x40)
midi.update_time(0)
midi.update_time(0)
midi.end_of_track()
midi.eof() # currently optional, should it do the write instead of write??
midi.write()

@ -0,0 +1,471 @@
# -*- coding: ISO-8859-1 -*-
class MidiOutStream:
"""
MidiOutstream is Basically an eventhandler. It is the most central
class in the Midi library. You use it both for writing events to
an output stream, and as an event handler for an input stream.
This makes it extremely easy to take input from one stream and
send it to another. Ie. if you want to read a Midi file, do some
processing, and send it to a midiport.
All time values are in absolute values from the opening of a
stream. To calculate time values, please use the MidiTime and
MidiDeltaTime classes.
"""
def __init__(self):
# the time is rather global, so it needs to be stored
# here. Otherwise there would be no really simple way to
# calculate it. The alternative would be to have each event
# handler do it. That sucks even worse!
self._absolute_time = 0
self._relative_time = 0
self._current_track = 0
self._running_status = None
# time handling event handlers. They should be overwritten with care
def update_time(self, new_time=0, relative=1):
"""
Updates the time, if relative is true, new_time is relative,
else it's absolute.
"""
if relative:
self._relative_time = new_time
self._absolute_time += new_time
else:
self._relative_time = new_time - self._absolute_time
self._absolute_time = new_time
def reset_time(self):
"""
reset time to 0
"""
self._relative_time = 0
self._absolute_time = 0
def rel_time(self):
"Returns the relative time"
return self._relative_time
def abs_time(self):
"Returns the absolute time"
return self._absolute_time
# running status methods
def reset_run_stat(self):
"Invalidates the running status"
self._running_status = None
def set_run_stat(self, new_status):
"Set the new running status"
self._running_status = new_status
def get_run_stat(self):
"Set the new running status"
return self._running_status
# track handling event handlers
def set_current_track(self, new_track):
"Sets the current track number"
self._current_track = new_track
def get_current_track(self):
"Returns the current track number"
return self._current_track
#####################
## Midi events
def channel_message(self, message_type, channel, data):
"""The default event handler for channel messages"""
pass
def note_on(self, channel=0, note=0x40, velocity=0x40):
"""
channel: 0-15
note, velocity: 0-127
"""
pass
def note_off(self, channel=0, note=0x40, velocity=0x40):
"""
channel: 0-15
note, velocity: 0-127
"""
pass
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
"""
channel: 0-15
note, velocity: 0-127
"""
pass
def continuous_controller(self, channel, controller, value):
"""
channel: 0-15
controller, value: 0-127
"""
pass
def patch_change(self, channel, patch):
"""
channel: 0-15
patch: 0-127
"""
pass
def channel_pressure(self, channel, pressure):
"""
channel: 0-15
pressure: 0-127
"""
pass
def pitch_bend(self, channel, value):
"""
channel: 0-15
value: 0-16383
"""
pass
#####################
## System Exclusive
def system_exclusive(self, data):
"""
data: list of values in range(128)
"""
pass
#####################
## Common events
def song_position_pointer(self, value):
"""
value: 0-16383
"""
pass
def song_select(self, songNumber):
"""
songNumber: 0-127
"""
pass
def tuning_request(self):
"""
No values passed
"""
pass
def midi_time_code(self, msg_type, values):
"""
msg_type: 0-7
values: 0-15
"""
pass
#########################
# header does not really belong here. But anyhoo!!!
def header(self, format=0, nTracks=1, division=96):
"""
format: type of midi file in [1,2]
nTracks: number of tracks
division: timing division
"""
pass
def eof(self):
"""
End of file. No more events to be processed.
"""
pass
#####################
## meta events
def meta_event(self, meta_type, data):
"""
Handles any undefined meta events
"""
pass
def start_of_track(self, n_track=0):
"""
n_track: number of track
"""
pass
def end_of_track(self):
"""
n_track: number of track
"""
pass
def sequence_number(self, value):
"""
value: 0-16383
"""
pass
def text(self, text):
"""
Text event
text: string
"""
pass
def copyright(self, text):
"""
Copyright notice
text: string
"""
pass
def sequence_name(self, text):
"""
Sequence/track name
text: string
"""
pass
def instrument_name(self, text):
"""
text: string
"""
pass
def lyric(self, text):
"""
text: string
"""
pass
def marker(self, text):
"""
text: string
"""
pass
def cuepoint(self, text):
"""
text: string
"""
pass
def midi_ch_prefix(self, channel):
"""
channel: midi channel for subsequent data (deprecated in the spec)
"""
pass
def midi_port(self, value):
"""
value: Midi port (deprecated in the spec)
"""
pass
def tempo(self, value):
"""
value: 0-2097151
tempo in us/quarternote
(to calculate value from bpm: int(60,000,000.00 / BPM))
"""
pass
def smtp_offset(self, hour, minute, second, frame, framePart):
"""
hour,
minute,
second: 3 bytes specifying the hour (0-23), minutes (0-59) and
seconds (0-59), respectively. The hour should be
encoded with the SMPTE format, just as it is in MIDI
Time Code.
frame: A byte specifying the number of frames per second (one
of : 24, 25, 29, 30).
framePart: A byte specifying the number of fractional frames,
in 100ths of a frame (even in SMPTE-based tracks
using a different frame subdivision, defined in the
MThd chunk).
"""
pass
def time_signature(self, nn, dd, cc, bb):
"""
nn: Numerator of the signature as notated on sheet music
dd: Denominator of the signature as notated on sheet music
The denominator is a negative power of 2: 2 = quarter
note, 3 = eighth, etc.
cc: The number of MIDI clocks in a metronome click
bb: The number of notated 32nd notes in a MIDI quarter note
(24 MIDI clocks)
"""
pass
def key_signature(self, sf, mi):
"""
sf: is a byte specifying the number of flats (-ve) or sharps
(+ve) that identifies the key signature (-7 = 7 flats, -1
= 1 flat, 0 = key of C, 1 = 1 sharp, etc).
mi: is a byte specifying a major (0) or minor (1) key.
"""
pass
def sequencer_specific(self, data):
"""
data: The data as byte values
"""
pass
#####################
## realtime events
def timing_clock(self):
"""
No values passed
"""
pass
def song_start(self):
"""
No values passed
"""
pass
def song_stop(self):
"""
No values passed
"""
pass
def song_continue(self):
"""
No values passed
"""
pass
def active_sensing(self):
"""
No values passed
"""
pass
def system_reset(self):
"""
No values passed
"""
pass
if __name__ == '__main__':
midiOut = MidiOutStream()
midiOut.update_time(0,0)
midiOut.note_on(0, 63, 127)
midiOut.note_off(0, 63, 127)

@ -0,0 +1,179 @@
# -*- coding: ISO-8859-1 -*-
from MidiOutStream import MidiOutStream
class MidiToText(MidiOutStream):
"""
This class renders a midi file as text. It is mostly used for debugging
"""
#############################
# channel events
def channel_message(self, message_type, channel, data):
"""The default event handler for channel messages"""
print 'message_type:%X, channel:%X, data size:%X' % (message_type, channel, len(data))
def note_on(self, channel=0, note=0x40, velocity=0x40):
print 'note_on - ch:%02X, note:%02X, vel:%02X time:%s' % (channel, note, velocity, self.rel_time())
def note_off(self, channel=0, note=0x40, velocity=0x40):
print 'note_off - ch:%02X, note:%02X, vel:%02X time:%s' % (channel, note, velocity, self.rel_time())
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
print 'aftertouch', channel, note, velocity
def continuous_controller(self, channel, controller, value):
print 'controller - ch: %02X, cont: #%02X, value: %02X' % (channel, controller, value)
def patch_change(self, channel, patch):
print 'patch_change - ch:%02X, patch:%02X' % (channel, patch)
def channel_pressure(self, channel, pressure):
print 'channel_pressure', channel, pressure
def pitch_bend(self, channel, value):
print 'pitch_bend ch:%s, value:%s' % (channel, value)
#####################
## Common events
def system_exclusive(self, data):
print 'system_exclusive - data size: %s' % len(date)
def song_position_pointer(self, value):
print 'song_position_pointer: %s' % value
def song_select(self, songNumber):
print 'song_select: %s' % songNumber
def tuning_request(self):
print 'tuning_request'
def midi_time_code(self, msg_type, values):
print 'midi_time_code - msg_type: %s, values: %s' % (msg_type, values)
#########################
# header does not really belong here. But anyhoo!!!
def header(self, format=0, nTracks=1, division=96):
print 'format: %s, nTracks: %s, division: %s' % (format, nTracks, division)
print '----------------------------------'
print ''
def eof(self):
print 'End of file'
def start_of_track(self, n_track=0):
print 'Start - track #%s' % n_track
def end_of_track(self):
print 'End of track'
print ''
###############
# sysex event
def sysex_event(self, data):
print 'sysex_event - datasize: %X' % len(data)
#####################
## meta events
def meta_event(self, meta_type, data):
print 'undefined_meta_event:', meta_type, len(data)
def sequence_number(self, value):
print 'sequence_number', number
def text(self, text):
print 'text', text
def copyright(self, text):
print 'copyright', text
def sequence_name(self, text):
print 'sequence_name:', text
def instrument_name(self, text):
print 'instrument_name:', text
def lyric(self, text):
print 'lyric', text
def marker(self, text):
print 'marker', text
def cuepoint(self, text):
print 'cuepoint', text
def midi_ch_prefix(self, channel):
print 'midi_ch_prefix', channel
def midi_port(self, value):
print 'midi_port:', value
def tempo(self, value):
print 'tempo:', value
def smtp_offset(self, hour, minute, second, frame, framePart):
print 'smtp_offset', hour, minute, second, frame, framePart
def time_signature(self, nn, dd, cc, bb):
print 'time_signature:', nn, dd, cc, bb
def key_signature(self, sf, mi):
print 'key_signature', sf, mi
def sequencer_specific(self, data):
print 'sequencer_specific', len(data)
if __name__ == '__main__':
# get data
test_file = 'test/midifiles/minimal.mid'
f = open(test_file, 'rb')
# do parsing
from MidiInFile import MidiInFile
midiIn = MidiInFile(MidiToText(), f)
midiIn.read()
f.close()

@ -0,0 +1,108 @@
# -*- coding: ISO-8859-1 -*-
# standard library imports
from types import StringType
from struct import unpack
# custom import
from DataTypeConverters import readBew, readVar, varLen
class RawInstreamFile:
"""
It parses and reads data from an input file. It takes care of big
endianess, and keeps track of the cursor position. The midi parser
only reads from this object. Never directly from the file.
"""
def __init__(self, infile=''):
"""
If 'file' is a string we assume it is a path and read from
that file.
If it is a file descriptor we read from the file, but we don't
close it.
Midi files are usually pretty small, so it should be safe to
copy them into memory.
"""
if infile:
if isinstance(infile, StringType):
infile = open(infile, 'rb')
self.data = infile.read()
infile.close()
else:
# don't close the f
self.data = infile.read()
else:
self.data = ''
# start at beginning ;-)
self.cursor = 0
# setting up data manually
def setData(self, data=''):
"Sets the data from a string."
self.data = data
# cursor operations
def setCursor(self, position=0):
"Sets the absolute position if the cursor"
self.cursor = position
def getCursor(self):
"Returns the value of the cursor"
return self.cursor
def moveCursor(self, relative_position=0):
"Moves the cursor to a new relative position"
self.cursor += relative_position
# native data reading functions
def nextSlice(self, length, move_cursor=1):
"Reads the next text slice from the raw data, with length"
c = self.cursor
slc = self.data[c:c+length]
if move_cursor:
self.moveCursor(length)
return slc
def readBew(self, n_bytes=1, move_cursor=1):
"""
Reads n bytes of date from the current cursor position.
Moves cursor if move_cursor is true
"""
return readBew(self.nextSlice(n_bytes, move_cursor))
def readVarLen(self):
"""
Reads a variable length value from the current cursor position.
Moves cursor if move_cursor is true
"""
MAX_VARLEN = 4 # Max value varlen can be
var = readVar(self.nextSlice(MAX_VARLEN, 0))
# only move cursor the actual bytes in varlen
self.moveCursor(varLen(var))
return var
if __name__ == '__main__':
test_file = 'test/midifiles/minimal.mid'
fis = RawInstreamFile(test_file)
print fis.nextSlice(len(fis.data))
test_file = 'test/midifiles/cubase-minimal.mid'
cubase_minimal = open(test_file, 'rb')
fis2 = RawInstreamFile(cubase_minimal)
print fis2.nextSlice(len(fis2.data))
cubase_minimal.close()

@ -0,0 +1,69 @@
# -*- coding: ISO-8859-1 -*-
# standard library imports
import sys
from types import StringType
from struct import unpack
from cStringIO import StringIO
# custom import
from DataTypeConverters import writeBew, writeVar, fromBytes
class RawOutstreamFile:
"""
Writes a midi file to disk.
"""
def __init__(self, outfile=''):
self.buffer = StringIO()
self.outfile = outfile
# native data reading functions
def writeSlice(self, str_slice):
"Writes the next text slice to the raw data"
self.buffer.write(str_slice)
def writeBew(self, value, length=1):
"Writes a value to the file as big endian word"
self.writeSlice(writeBew(value, length))
def writeVarLen(self, value):
"Writes a variable length word to the file"
var = self.writeSlice(writeVar(value))
def write(self):
"Writes to disc"
if self.outfile:
if isinstance(self.outfile, StringType):
outfile = open(self.outfile, 'wb')
outfile.write(self.getvalue())
outfile.close()
else:
self.outfile.write(self.getvalue())
else:
sys.stdout.write(self.getvalue())
def getvalue(self):
return self.buffer.getvalue()
if __name__ == '__main__':
out_file = 'test/midifiles/midiout.mid'
out_file = ''
rawOut = RawOutstreamFile(out_file)
rawOut.writeSlice('MThd')
rawOut.writeBew(6, 4)
rawOut.writeBew(1, 2)
rawOut.writeBew(2, 2)
rawOut.writeBew(15360, 2)
rawOut.write()

@ -0,0 +1,6 @@
# -*- coding: ISO-8859-1 -*-
#import MidiOutStream
#import MidiInStream
#import MidiInFile
#import MidiToText

@ -0,0 +1,45 @@
------------------------------------------------------------------------
r409 | maxm | 2006-01-05 16:37:29 +0100 (to, 05 jan 2006) | 1 line
Made RawOutstreamFile.py write to std out if no outfile is given.
------------------------------------------------------------------------
r403 | maxm | 2006-01-05 13:34:11 +0100 (to, 05 jan 2006) | 1 line
------------------------------------------------------------------------
r402 | maxm | 2006-01-05 13:33:56 +0100 (to, 05 jan 2006) | 1 line
- Fixed minor bugs, added coding headers
------------------------------------------------------------------------
r401 | maxm | 2006-01-01 23:09:17 +0100 (s_, 01 jan 2006) | 1 line
Fixed sysex dispathcer bug.
------------------------------------------------------------------------
r268 | maxm | 2005-02-04 12:26:59 +0100 (fr, 04 feb 2005) | 1 line
------------------------------------------------------------------------
r128 | maxm | 2004-12-18 14:05:27 +0100 (l_, 18 dec 2004) | 1 line
Fixed bug when using relative time
------------------------------------------------------------------------
r15 | maxm | 2004-03-09 15:01:41 +0100 (ti, 09 mar 2004) | 1 line
made a copy to meta folder
------------------------------------------------------------------------
r13 | maxm | 2004-03-09 09:17:23 +0100 (ti, 09 mar 2004) | 1 line
Deleted .pyc files
------------------------------------------------------------------------
r12 | maxm | 2004-03-09 09:15:54 +0100 (ti, 09 mar 2004) | 1 line
Removed file/folder
------------------------------------------------------------------------
r3 | maxm | 2004-03-08 23:16:25 +0100 (ma, 08 mar 2004) | 1 line
Adde midi
------------------------------------------------------------------------
r1 | maxm | 2004-03-08 22:49:23 +0100 (ma, 08 mar 2004) | 1 line
Initial Import
------------------------------------------------------------------------

@ -0,0 +1,210 @@
# -*- coding: ISO-8859-1 -*-
###################################################
## Definitions of the different midi events
###################################################
## Midi channel events (The most usual events)
## also called "Channel Voice Messages"
NOTE_OFF = 0x80
# 1000cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
NOTE_ON = 0x90
# 1001cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
AFTERTOUCH = 0xA0
# 1010cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)
CONTINUOUS_CONTROLLER = 0xB0 # see Channel Mode Messages!!!
# 1011cccc 0ccccccc 0vvvvvvv (channel, controller, value)
PATCH_CHANGE = 0xC0
# 1100cccc 0ppppppp (channel, program)
CHANNEL_PRESSURE = 0xD0
# 1101cccc 0ppppppp (channel, pressure)
PITCH_BEND = 0xE0
# 1110cccc 0vvvvvvv 0wwwwwww (channel, value-lo, value-hi)
###################################################
## Channel Mode Messages (Continuous Controller)
## They share a status byte.
## The controller makes the difference here
# High resolution continuous controllers (MSB)
BANK_SELECT = 0x00
MODULATION_WHEEL = 0x01
BREATH_CONTROLLER = 0x02
FOOT_CONTROLLER = 0x04
PORTAMENTO_TIME = 0x05
DATA_ENTRY = 0x06
CHANNEL_VOLUME = 0x07
BALANCE = 0x08
PAN = 0x0A
EXPRESSION_CONTROLLER = 0x0B
EFFECT_CONTROL_1 = 0x0C
EFFECT_CONTROL_2 = 0x0D
GEN_PURPOSE_CONTROLLER_1 = 0x10
GEN_PURPOSE_CONTROLLER_2 = 0x11
GEN_PURPOSE_CONTROLLER_3 = 0x12
GEN_PURPOSE_CONTROLLER_4 = 0x13
# High resolution continuous controllers (LSB)
BANK_SELECT = 0x20
MODULATION_WHEEL = 0x21
BREATH_CONTROLLER = 0x22
FOOT_CONTROLLER = 0x24
PORTAMENTO_TIME = 0x25
DATA_ENTRY = 0x26
CHANNEL_VOLUME = 0x27
BALANCE = 0x28
PAN = 0x2A
EXPRESSION_CONTROLLER = 0x2B
EFFECT_CONTROL_1 = 0x2C
EFFECT_CONTROL_2 = 0x2D
GENERAL_PURPOSE_CONTROLLER_1 = 0x30
GENERAL_PURPOSE_CONTROLLER_2 = 0x31
GENERAL_PURPOSE_CONTROLLER_3 = 0x32
GENERAL_PURPOSE_CONTROLLER_4 = 0x33
# Switches
SUSTAIN_ONOFF = 0x40
PORTAMENTO_ONOFF = 0x41
SOSTENUTO_ONOFF = 0x42
SOFT_PEDAL_ONOFF = 0x43
LEGATO_ONOFF = 0x44
HOLD_2_ONOFF = 0x45
# Low resolution continuous controllers
SOUND_CONTROLLER_1 = 0x46 # (TG: Sound Variation; FX: Exciter On/Off)
SOUND_CONTROLLER_2 = 0x47 # (TG: Harmonic Content; FX: Compressor On/Off)
SOUND_CONTROLLER_3 = 0x48 # (TG: Release Time; FX: Distortion On/Off)
SOUND_CONTROLLER_4 = 0x49 # (TG: Attack Time; FX: EQ On/Off)
SOUND_CONTROLLER_5 = 0x4A # (TG: Brightness; FX: Expander On/Off)75 SOUND_CONTROLLER_6 (TG: Undefined; FX: Reverb OnOff)
SOUND_CONTROLLER_7 = 0x4C # (TG: Undefined; FX: Delay OnOff)
SOUND_CONTROLLER_8 = 0x4D # (TG: Undefined; FX: Pitch Transpose OnOff)
SOUND_CONTROLLER_9 = 0x4E # (TG: Undefined; FX: Flange/Chorus OnOff)
SOUND_CONTROLLER_10 = 0x4F # (TG: Undefined; FX: Special Effects OnOff)
GENERAL_PURPOSE_CONTROLLER_5 = 0x50
GENERAL_PURPOSE_CONTROLLER_6 = 0x51
GENERAL_PURPOSE_CONTROLLER_7 = 0x52
GENERAL_PURPOSE_CONTROLLER_8 = 0x53
PORTAMENTO_CONTROL = 0x54 # (PTC) (0vvvvvvv is the source Note number) (Detail)
EFFECTS_1 = 0x5B # (Ext. Effects Depth)
EFFECTS_2 = 0x5C # (Tremelo Depth)
EFFECTS_3 = 0x5D # (Chorus Depth)
EFFECTS_4 = 0x5E # (Celeste Depth)
EFFECTS_5 = 0x5F # (Phaser Depth)
DATA_INCREMENT = 0x60 # (0vvvvvvv is n/a; use 0)
DATA_DECREMENT = 0x61 # (0vvvvvvv is n/a; use 0)
NON_REGISTERED_PARAMETER_NUMBER = 0x62 # (LSB)
NON_REGISTERED_PARAMETER_NUMBER = 0x63 # (MSB)
REGISTERED_PARAMETER_NUMBER = 0x64 # (LSB)
REGISTERED_PARAMETER_NUMBER = 0x65 # (MSB)
# Channel Mode messages - (Detail)
ALL_SOUND_OFF = 0x78
RESET_ALL_CONTROLLERS = 0x79
LOCAL_CONTROL_ONOFF = 0x7A
ALL_NOTES_OFF = 0x7B
OMNI_MODE_OFF = 0x7C # (also causes ANO)
OMNI_MODE_ON = 0x7D # (also causes ANO)
MONO_MODE_ON = 0x7E # (Poly Off; also causes ANO)
POLY_MODE_ON = 0x7F # (Mono Off; also causes ANO)
###################################################
## System Common Messages, for all channels
SYSTEM_EXCLUSIVE = 0xF0
# 11110000 0iiiiiii 0ddddddd ... 11110111
MTC = 0xF1 # MIDI Time Code Quarter Frame
# 11110001
SONG_POSITION_POINTER = 0xF2
# 11110010 0vvvvvvv 0wwwwwww (lo-position, hi-position)
SONG_SELECT = 0xF3
# 11110011 0sssssss (songnumber)
#UNDEFINED = 0xF4
## 11110100
#UNDEFINED = 0xF5
## 11110101
TUNING_REQUEST = 0xF6
# 11110110
END_OFF_EXCLUSIVE = 0xF7 # terminator
# 11110111 # End of system exclusive
###################################################
## Midifile meta-events
SEQUENCE_NUMBER = 0x00 # 00 02 ss ss (seq-number)
TEXT = 0x01 # 01 len text...
COPYRIGHT = 0x02 # 02 len text...
SEQUENCE_NAME = 0x03 # 03 len text...
INSTRUMENT_NAME = 0x04 # 04 len text...
LYRIC = 0x05 # 05 len text...
MARKER = 0x06 # 06 len text...
CUEPOINT = 0x07 # 07 len text...
PROGRAM_NAME = 0x08 # 08 len text...
DEVICE_NAME = 0x09 # 09 len text...
MIDI_CH_PREFIX = 0x20 # MIDI channel prefix assignment (unofficial)
MIDI_PORT = 0x21 # 21 01 port, legacy stuff but still used
END_OF_TRACK = 0x2F # 2f 00
TEMPO = 0x51 # 51 03 tt tt tt (tempo in us/quarternote)
SMTP_OFFSET = 0x54 # 54 05 hh mm ss ff xx
TIME_SIGNATURE = 0x58 # 58 04 nn dd cc bb
KEY_SIGNATURE = 0x59 # ??? len text...
SPECIFIC = 0x7F # Sequencer specific event
FILE_HEADER = 'MThd'
TRACK_HEADER = 'MTrk'
###################################################
## System Realtime messages
## I don't supose these are to be found in midi files?!
TIMING_CLOCK = 0xF8
# undefined = 0xF9
SONG_START = 0xFA
SONG_CONTINUE = 0xFB
SONG_STOP = 0xFC
# undefined = 0xFD
ACTIVE_SENSING = 0xFE
SYSTEM_RESET = 0xFF
###################################################
## META EVENT, it is used only in midi files.
## In transmitted data it means system reset!!!
META_EVENT = 0xFF
# 11111111
###################################################
## Helper functions
def is_status(byte):
return (byte & 0x80) == 0x80 # 1000 0000

@ -0,0 +1,29 @@
from MidiOutFile import MidiOutFile
"""
This is an example of the smallest possible type 0 midi file, where
all the midi events are in the same track.
"""
out_file = 'midiout/minimal_type0.mid'
midi = MidiOutFile(out_file)
# non optional midi framework
midi.header()
midi.start_of_track()
# musical events
midi.update_time(0)
midi.note_on(channel=0, note=0x40)
midi.update_time(192)
midi.note_off(channel=0, note=0x40)
# non optional midi framework
midi.update_time(0)
midi.end_of_track()
midi.eof()

@ -0,0 +1,23 @@
from MidiOutStream import MidiOutStream
from MidiInFile import MidiInFile
"""
This prints all note on events on midi channel 0
"""
class Transposer(MidiOutStream):
"Transposes all notes by 1 octave"
def note_on(self, channel=0, note=0x40, velocity=0x40):
if channel == 0:
print channel, note, velocity, self.rel_time()
event_handler = Transposer()
in_file = 'midiout/minimal_type0.mid'
midi_in = MidiInFile(event_handler, in_file)
midi_in.read()

@ -0,0 +1,28 @@
from MidiToText import MidiToText
"""
This is an example that uses the MidiToText eventhandler. When an
event is triggered on it, it prints the event to the console.
"""
midi = MidiToText()
# non optional midi framework
midi.header()
midi.start_of_track()
# musical events
midi.update_time(0)
midi.note_on(channel=0, note=0x40)
midi.update_time(192)
midi.note_off(channel=0, note=0x40)
# non optional midi framework
midi.update_time(0)
midi.end_of_track() # not optional!
midi.eof()

@ -0,0 +1,19 @@
"""
This is an example that uses the MidiToText eventhandler. When an
event is triggered on it, it prints the event to the console.
It gets the events from the MidiInFile.
So it prints all the events from the infile to the console. great for
debugging :-s
"""
# get data
test_file = 'test/midifiles/minimal-cubase-type0.mid'
# do parsing
from MidiInFile import MidiInFile
from MidiToText import MidiToText # the event handler
midiIn = MidiInFile(MidiToText(), test_file)
midiIn.read()

@ -0,0 +1,40 @@
from MidiOutFile import MidiOutFile
from MidiInFile import MidiInFile
"""
This is an example of the smallest possible type 0 midi file, where
all the midi events are in the same track.
"""
class Transposer(MidiOutFile):
"Transposes all notes by 1 octave"
def _transp(self, ch, note):
if ch != 9: # not the drums!
note += 12
if note > 127:
note = 127
return note
def note_on(self, channel=0, note=0x40, velocity=0x40):
note = self._transp(channel, note)
MidiOutFile.note_on(self, channel, note, velocity)
def note_off(self, channel=0, note=0x40, velocity=0x40):
note = self._transp(channel, note)
MidiOutFile.note_off(self, channel, note, velocity)
out_file = 'midiout/transposed.mid'
midi_out = Transposer(out_file)
#in_file = 'midiout/minimal_type0.mid'
#in_file = 'test/midifiles/Lola.mid'
in_file = 'test/midifiles/tennessee_waltz.mid'
midi_in = MidiInFile(midi_out, in_file)
midi_in.read()

@ -0,0 +1,76 @@
class EventDispatcherBase:
def __init__(self, outstream):
"""
The event dispatcher generates events on the outstream. This
is the base implementation. It is more like an interface for
how the EventDispatcher. It has the methods that are used by
the Midi Parser.
"""
# internal values, don't mess with 'em directly
self.outstream = outstream
def eof(self):
"End of file!"
self.outstream.eof()
def update_time(self, new_time=0, relative=1):
"Updates relative/absolute time."
self.outstream.update_time(new_time, relative)
# 'official' midi events
def header(self, format, nTracks, division):
"Triggers the header event"
self.outstream.header(format, nTracks, division)
def start_of_track(self, current_track):
"Triggers the start of track event"
# I do this twice so that users can overwrite the
# start_of_track event handler without worrying whether the
# track number is updated correctly.
self.outstream.set_current_track(current_track)
self.outstream.start_of_track(current_track)
# Event dispatchers for midi events
def channel_messages(self, hi_nible, channel, data):
"Dispatches channel messages"
self.outstream.channel_message(hi_nible, channel, data)
def continuous_controllers(self, channel, controller, value):
"Dispatches channel messages"
self.outstream.continuous_controller(channel, controller, value)
def system_commons(self, common_type, common_data):
"Dispatches system common messages"
self.outstream.system_common(common_type, common_data)
def meta_event(self, meta_type, data):
"Dispatches meta events"
self.outstream.meta_event(meta_type, data)
def sysex_events(self, data):
"Dispatcher for sysex events"
self.outstream.sysex_event(data)
if __name__ == '__main__':
from MidiToText import MidiToText
from constants import NOTE_ON
outstream = MidiToText()
dispatcher = EventDispatcherBase(outstream)
dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')

@ -0,0 +1,182 @@
from MidiOutStream import MidiOutStream
class MidiOutPassThrough(MidiOutStream):
"""
This class i mainly used for testing the event dispatcher. The
methods just returns the passed parameters as a tupple.
"""
#####################
## Midi channel events
def note_on(self, channel, note, velocity, time=None):
return channel, note, velocity, time
def note_off(self, channel, note, velocity, time=None):
return channel, note, velocity, time
def aftertouch(self, channel, note, velocity, time=None):
return channel, note, velocity, time
def continuous_controller(self, channel, controller, value, time=None):
return channel, controller, value, time
def patch_change(self, channel, patch, time=None):
return channel, patch, time
def channel_pressure(self, channel, pressure, time=None):
return channel, pressure, time
#####################
## defined continuous controller events
# def cc_
#####################
## Common events
def system_exclusive(self, data, time=None):
return data, time
def song_position_pointer(self, hiPos, loPos, time=None):
return hiPos, loPos, time
def song_select(self, songNumber, time=None):
return songNumber, time
def tuning_request(self, time=None):
return time
#########################
# header does not really belong here. But anyhoo!!!
def header(self, format, nTracks, division):
return format, nTracks, division
def eof(self):
return 'eof'
#####################
## meta events
def start_of_track(self, n_track=0):
return n_track
def end_of_track(self, n_track=0, time=None):
return n_track, time
def sequence_number(self, hiVal, loVal, time=None):
return hiVal, loVal, time
def text(self, text, time=None):
return text, time
def copyright(self, text, time=None):
return text, time
def sequence_name(self, text, time=None):
return text, time
def instrument_name(self, text, time=None):
return text, time
def lyric(self, text, time=None):
return text, time
def marker(self, text, time=None):
return text, time
def cuepoint(self, text, time=None):
return text, time
def midi_port(self, value, time=None):
return value, time
def tempo(self, value, time=None):
return value, time
def smtp_offset(self, hour, minute, second, frame, framePart, time=None):
return hour, minute, second, frame, framePart, time
def time_signature(self, nn, dd, cc, bb, time=None):
return nn, dd, cc, bb, time
def key_signature(self, sf, mi, time=None):
return sf, mi, time
def sequencer_specific(self, data, time=None):
return data, time
#####################
## realtime events
def timing_clock(self, time=None):
return time
def song_start(self, time=None):
return time
def song_stop(self, time=None):
return time
def song_continue(self, time=None):
return time
def active_sensing(self, time=None):
return time
def system_reset(self, time=None):
return time
if __name__ == '__main__':
midiOut = MidiOutStream()
midiOut.note_on(0, 63, 127, 0)
midiOut.note_off(0, 63, 127, 384)

@ -0,0 +1,135 @@
class MidiOutStreamBase:
"""
MidiOutStreamBase is Basically an eventhandler. It is the most central
class in the Midi library. You use it both for writing events to
an output stream, and as an event handler for an input stream.
This makes it extremely easy to take input from one stream and
send it to another. Ie. if you want to read a Midi file, do some
processing, and send it to a midiport.
All time values are in absolute values from the opening of a
stream. To calculate time values, please use the MidiTime and
MidiDeltaTime classes.
"""
def __init__(self):
# the time is rather global, so it needs to be stored
# here. Otherwise there would be no really simple way to
# calculate it. The alternative would be to have each event
# handler do it. That sucks even worse!
self._absolute_time = 0
self._relative_time = 0
self._current_track = 0
# time handling event handlers. They should overwritten with care
def update_time(self, new_time=0, relative=1):
"""
Updates the time, if relative is true, new_time is relative,
else it's absolute.
"""
if relative:
self._relative_time = new_time
self._absolute_time += new_time
else:
self._absolute_time = new_time
self._relative_time = new_time - self._absolute_time
def rel_time(self):
"Returns the relative time"
return self._relative_time
def abs_time(self):
"Returns the absolute time"
return self._absolute_time
# track handling event handlers
def set_current_track(self, new_track):
"Sets the current track number"
self._current_track = new_track
def get_current_track(self):
"Returns the current track number"
return self._current_track
#####################
## Midi events
def channel_message(self, message_type, channel, data):
"""The default event handler for channel messages"""
pass
#####################
## Common events
def system_exclusive(self, data):
"""The default event handler for system_exclusive messages"""
pass
def system_common(self, common_type, common_data):
"""The default event handler for system common messages"""
pass
#########################
# header does not really belong here. But anyhoo!!!
def header(self, format, nTracks, division):
"""
format: type of midi file in [1,2]
nTracks: number of tracks
division: timing division
"""
pass
def start_of_track(self, n_track=0):
"""
n_track: number of track
"""
pass
def eof(self):
"""
End of file. No more events to be processed.
"""
pass
#####################
## meta events
def meta_event(self, meta_type, data, time):
"""The default event handler for meta_events"""
pass
if __name__ == '__main__':
midiOut = MidiOutStreamBase()
midiOut.update_time(0,0)
midiOut.note_on(0, 63, 127)
midiOut.note_off(0, 63, 127)

@ -0,0 +1 @@
Stuff that I am just playing around with

@ -0,0 +1,50 @@
This is the documentation for the midi package
==============================================
The modules follows the following naming convention:
MidiIn<StreamType>.py
---------------------
The MidiIn modules reads midi content for a specific type of stream. Ie. a file or a midi port. It then generates events and triggers them on a MidiOutStream.
MidiOut<StreamType>.py
----------------------
The MidiOut modules are event handlers, that reacts to events generated by a a Midi in module.
MidiInBase.py
---------------
The base class for input streams.
MidiOutBase.py
----------------
The base class for the output streams.
Internal modules
================
DataTypeConverters.py
---------------------
A collection of functions that converts the special data types used in midi files to and from strings.
constants.py
------------
A collection of constants from the midi spec.

@ -0,0 +1,26 @@
MThd | Format=1 | # of Tracks=2 | Division=15360
Track #0 ******************************************
Time Event
1: 1: 0 |Time Sig | 4/4 | MIDI-clocks\click=24 | 32nds\quarter=8
|Tempo | BPM=120 | micros\quarter=500000
101: 1: 0 |End of track|
Track #1 ******************************************
Time Event
1: 1: 0 |Track Name | len=7 |
0x53 0x79 0x6E 0x74 0x68 0x20 0x31 <Synth 1>
|Instrument | len=7 |
0x53 0x79 0x6E 0x74 0x68 0x20 0x31 <Synth 1>
|On Note | chan= 1 | pitch=C 1 | vol=127
2: 1: 0 |Off Note | chan= 1 | pitch=c 1 | vol=0
101: 1: 0 |End of track|

@ -0,0 +1,54 @@
4D 54 68 64 MThd
00 00 00 06 len: 6
00 01 Type: 1
00 02 tracks: 2
3C 00 tempo: 15360
4D 54 72 6B MTrk
00 00 00 16 len: 22
00 time
FF 58 04 Time Signature
04 02 18 08 4/4 24 8
00 time
FF 51 03 TEMPO:
07 A1 20 500.000 mySec
82 F7 80 00 time ## oh bugger, it's buggy.
FF 2F 00 End Of Track
4D 54 72 6B MTrk
00 00 00 2C len: 44
00 time
FF 03 Sequence/Track Name
07 len: 7
53 79 6E 74
68 20 31 'Synth 1'
00 time
FF 04 Instrument
07 len: 7
53 79 6E 74
68 20 31 'Synth 1'
00 time
FF 21 01 Midi port
04 Port #5
00 time
90 24 7F Note ON
83 E0 00 Note OFF
00 time
80 24 00 Note OFF
00 82 F3 A0
00
FF 2F 00 End Of Track

@ -0,0 +1,7 @@
These files are used for testing the midi package
=================================================
minimal.mid
-----------
A minimal working midifile. Plays a one bar "middle C" at 120 bpm. The absolute simplest file I could get to play in midi devices.

@ -0,0 +1,3 @@
Embarrasingly empty.
Why don't you write some tests?

@ -0,0 +1,23 @@
import sys
sys.path.append("midi")
from MidiOutStream import MidiOutStream
from MidiInFile import MidiInFile
class ArduinoConverter(MidiOutStream):
"Convert MIDI stream to some weird format I can actually use"
def note_on(self, channel=0, note=0x40, velocity=0x40):
if self.get_current_track() == 3 and channel == 0:
print note, self.rel_time()
def sysex_event(self, data):
"dummy"
pass
# get data
test_file = 'nevergoingtoU.mid'
# do parsing
c = ArduinoConverter()
midiIn = MidiInFile(c, test_file)
midiIn.read()