VocalEasel/mma/MMA/midiIn.py

519 lines
14 KiB
Python
Raw Normal View History

2006-11-10 08:07:56 +00:00
# midiIn.py
"""
This module is an integeral part of the program
MMA - Musical Midi Accompaniment.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2007-04-29 06:47:40 +00:00
Bob van der Poel <bob@mellowood.ca>
2006-11-10 08:07:56 +00:00
"""
import MMA.midiM
from MMA.alloc import trackAlloc
import gbl
from MMA.common import *
import os
# The following 2 variables are global. A bit ugly :)
midifile = '' # The imported MIDI file (data) as a long string
offset = 0 # Current pointer into the MIDI file
""" Helper functions
It might be better to have these
2007-04-29 06:47:40 +00:00
functions setup in midiM.py ... but it's easier just
now to have it here. The main problem is that we are
reading from a buffer and don't know how many bytes to
pass back and forth.
2006-11-10 08:07:56 +00:00
"""
def mvarlen():
2007-04-29 06:47:40 +00:00
""" Convert variable length midi value to int. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
x=0L
for i in range(4):
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
try:
byte=ord(midifile[offset])
offset += 1
except:
error("Invalid MIDI file include (varlen->int)")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if byte < 0x80:
x = ( x << 7 ) + byte
break
else:
x = ( x << 7 ) + ( byte & 0x7f )
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return int(x)
2006-11-10 08:07:56 +00:00
def chars(count):
2007-04-29 06:47:40 +00:00
""" Return 'count' chars from file (updates global pointer). """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
bytes=midifile[offset:offset+count]
offset+=count
return bytes
2006-11-10 08:07:56 +00:00
def m1i():
2007-04-29 06:47:40 +00:00
""" Get 1 byte (updates global pointer). """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
try:
byte = midifile[offset]
offset += 1
except:
error("Invalid MIDI file include (byte, offset=%s)" % offset)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return ord(byte)
2006-11-10 08:07:56 +00:00
def m32i():
2007-04-29 06:47:40 +00:00
""" Convert 4 bytes to integer. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
x = 0L
for i in range(4):
try:
byte = midifile[offset]
offset += 1
except:
error("Invalid MIDI file include (i32->int, offset=%s)" % offset)
x = (x << 8) + ord(byte)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return int(x)
2006-11-10 08:07:56 +00:00
def m16i():
2007-04-29 06:47:40 +00:00
""" Convert 2 bytes to integer. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
x = 0L
for i in range(2):
try:
byte = midifile[offset]
offset += 1
except:
error("Invalid MIDI file include (i16->int, offset=%s)" % offset)
x = (x << 8) + ord(byte)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return int(x)
2006-11-10 08:07:56 +00:00
######################################################
## Main function, called from parser.
def midiinc(ln):
2007-04-29 06:47:40 +00:00
""" Include a MIDI file into MMA generated files. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global midifile, offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
filename = ''
doLyric = 0
doText = 0
volAdjust = 100
octAdjust = 0
transpose = None
channels = []
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# These are the start/end points for the included file. They are in
# beats, but are adjusted after the file is opened to ticks.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
istart=0
iend = 0xffffff
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for a in ln:
cmd, opt = a.split('=')
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
cmd=cmd.upper()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if cmd == 'FILE':
filename = os.path.expanduser(opt)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'VOLUME':
volAdjust = stoi(opt)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'OCTAVE':
octAdjust = stoi(opt)
if octAdjust < -4 or octAdjust > 4:
error("Octave adjustment must be -4 to 4, not %s" % opt)
octAdjust *= 12
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'TRANSPOSE':
transpose = stoi(opt)
if transpose < -24 or transpose > 24:
error("Tranpose must be -24 to 24, not %s" % opt)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'START':
istart = stof(opt)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'END':
iend = stof(opt)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'TEXT':
opt=opt.upper()
if opt in ("ON", 1):
doText=1
elif opt in ("OFF", 0):
doText=0
else:
error("MidiInc Text= expecting 'ON' or 'OFF'")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif cmd == 'LYRIC' and opt != '0':
opt=opt.upper()
if opt in ("ON", 1):
doLyric=1
elif opt in ("OFF", 0):
doLyric=0
else:
error("MidiInc Lyric= expecting 'ON' or 'OFF'")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# make sure this is last option ... it has to be a TRACKNAME=CHANNEL-NUMBER
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else:
trackAlloc(cmd, 0)
if not cmd in gbl.tnames:
error("%s is not a valid MMA track" % cmd)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
ch = stoi(opt)
if ch < 1 or ch > 16:
error("MIDI channel for import must be 1..16, not %s" % ch)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
channels.append( (cmd, ch-1))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not channels:
if doLyric or doText:
warning("MidiInc: no import channels specified, only text or lyrics imported")
else:
error("MidiInc: A channel to import and a destination track must be specified")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if (istart >= iend) or (istart < 0) or (iend < 0):
error("MidiInc range invalid: start=%s, end=%s" % (istart, iend))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.debug:
print "MidiInc: file=%s, Volume=%s, Octave=%s, Transpose=%s, Lyric=%s, Text=%s, Range=%s..%s"\
% (filename, volAdjust, octAdjust, transpose, doLyric, doText, istart, iend)
for t, ch in channels:
print "MidiInc: Channel %s --> Track %s" % (ch+1, t)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# If transpose was NOT set, use the global transpose value
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if transpose == None:
transpose = gbl.transpose
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
octAdjust += transpose # this takes care of octave and transpose
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
try:
inpath = file(filename, "rb")
except:
error("Unable to open MIDI file %s for reading" % filename)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
midifile=inpath.read()
inpath.close()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Create our storage:
# A dic with the channels 0-15 as keys for the midi note events
# 2 lists for lyrics and text events. These have tuples for (time, text)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
events={}
for c in range(0,16):
events[c]=[]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
textEvs=[]
lyricEvs=[]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Ensure this is valid header
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
hd=midifile[0:4]
if hd != 'MThd':
error("Expecting 'HThd', %s not a standard midi file" % filename)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
offset = 4
a = m32i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if a != 6:
error("Expecting a 32 bit value of 6 in header")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
format=m16i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if format not in (0,1):
error("MIDI file format %s not recognized" % format)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
ntracks=m16i()
beatDivision=m16i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if beatDivision != gbl.BperQ:
warning("MIDI file '%s' tick/beat of %s differs from MMA's "
"%s. Will try to compensate" %
(filename, beatDivision, gbl.BperQ))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Adjust start/end to the file's tick
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
istart *= beatDivision
iend *= beatDivision
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
midievents={}
firstNote = 0xffffff
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for tr in range(ntracks):
tm=0
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
hdr = midifile[offset:offset+4]
offset+=4
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if hdr != 'MTrk':
error("Malformed MIDI file in track header")
trlen = m32i() # track length, not used?
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
lastevent = None
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" Parse the midi file. We have to parse off each event, even
though many will just be thrown away. You can't just skip around
in a midi file :) In the future we might decide to include meta
stuff, etc. Or, we may not :) For now, we keep:
- note on
- note off
- key pressure
- control change
- program change
- channel pressure
- pitch blend
- text event
- lyric event
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
while 1:
tm += mvarlen() # adjust total offset by delta
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
ev=m1i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if ev < 0x80:
if not lastevent:
error("Illegal running status in %s at %s" % (midifile, offset))
offset -= 1
ev=lastevent
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
sValue = ev>>4 # Shift MSBs to get a 4 bit value
channel = ev & 0x0f
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if sValue == 0x8: # note off event
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
note=m1i()
vel=m1i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if octAdjust and channel != 10:
note += octAdjust
if note < 0 or note > 127:
continue
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
events[channel].append([tm, ev & 0xf0, chr(note)+chr(vel)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0x9: # note on event
if tm < firstNote:
firstNote = tm
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
note=m1i()
vel=m1i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if octAdjust and channel != 10:
note += octAdjust
if note < 0 or note > 127:
continue
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if volAdjust != 100:
vel = int( (vel*volAdjust)/100)
if vel<0: vel=1
if vel>127: vel=127
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
events[ev & 0xf].append([tm, ev & 0xf0, chr(note)+chr(vel)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0xa: # key pressure
events[ev & 0xf].append([tm, ev & 0xf0, chars(2)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0xb: # control change
events[ev & 0xf].append([tm, ev & 0xf0, chars(2)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0xc: # program change
events[ev & 0xf].append([tm, ev & 0xf0, chars(1)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0xd: # channel pressure
events[ev & 0xf].append([tm, ev & 0xf0, chars(1)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0xe: # pitch blend
events[ev & 0xf].append([tm, ev & 0xf0, chars(2)])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif sValue == 0xf: # system, mostly ignored
if ev == 0xff: # meta events
a=m1i()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if a == 0x00: # sequence number
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x01: # text (could be lyrics)
textEvs.append((tm, chars(mvarlen())))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x02: # copyright
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x03: # seq/track name
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x04: # instrument name
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x05: # lyric
lyricEvs.append((tm, chars(mvarlen())))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x06: # marker
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x07: # cue point
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x21: # midi port
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x2f: # end of track
l=mvarlen()
offset += l
break
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x51: #tempo
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x54: # SMPTE offset
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x58: # time sig
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif a == 0x59: # key sig
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else: # probably 0x7f, proprietary event
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif ev == 0xf0: # system exclusive
l=mvarlen()
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif ev == 0xf2: # song position pointer, 2 bytes
offset += 2
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif ev == 0xf3: # song select, 1 byte
offset += 1
else: # all others are single byte commands
pass
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if ev >= 0x80 and ev <= 0xef:
lastevent = ev
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Midi file parsed, add selected events to mma data
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
beatad = gbl.BperQ / float(beatDivision)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if doText:
inst=0
disc=0
for tm,tx in textEvs:
delta = tm-firstNote
if delta >= istart and delta <= iend:
gbl.mtrks[0].addText(gbl.tickOffset + int(delta * beatad), tx)
inst+=1
else:
disc+=1
if gbl.debug:
print"MidiInc text events: %s inserted, %s out of range." % (inst, disc)
if doLyric:
inst=0
disc=0
for tm, tx in lyricEvs:
delta = tm-firstNote
if delta >= istart and delta <= iend:
gbl.mtrks[0].addLyric(gbl.tickOffset + int(delta * beatad), tx)
inst+=1
else:
disc+=1
if gbl.debug:
print"MidiInc lyric events: %s inserted, %s out of range." % (inst, disc)
for n,c in channels:
if not len(events[c]):
warning("No data to assign from imported channel %s to track %s" % (c+1, n))
inst=0
disc=0
for tr, ch in channels:
t=gbl.tnames[tr]
if not t.channel:
t.setChannel()
t.clearPending()
if t.voice[0] != t.ssvoice:
gbl.mtrks[t.channel].addProgChange( gbl.tickOffset, t.voice[0])
channel = t.channel
track = gbl.mtrks[channel]
for ev in events[ch]:
delta = ev[0]-firstNote
if delta >= istart and delta <= iend:
track.addToTrack( gbl.tickOffset + int(delta * beatad),
chr(ev[1] | channel-1) + ev[2] )
inst+=1
else:
disc+=1
if gbl.debug:
print"MidiInc events: %s inserted, %s out of range." % (inst, disc)
2006-11-10 08:07:56 +00:00