add midi fnord
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('aá')
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -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')
|
Binary file not shown.
@ -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()
|
||||||
|
|
Binary file not shown.
@ -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)
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -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)
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -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()
|
Binary file not shown.
@ -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()
|
Binary file not shown.
@ -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
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -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
|
Binary file not shown.
Binary file not shown.
@ -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.
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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 @@
|
|||||||
|
0.1.4
|
Binary file not shown.
@ -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()
|
Reference in New Issue