# midinote.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 Bob van der Poel This module does all the midinote stuff. """ import MMA.notelen import MMA.midiC import gbl from MMA.common import * def parse(name, ln): """ Called from parser for a This figures the right routine.""" if not len(ln): error("MidiNote: Needs arguments") trk=gbl.tnames[name] # parse out the cmd=value options pairs ln, opts = opt2pair(ln, toupper=0) for o,v in opts: o=o.upper() v=v.upper() if o == 'TRANSPOSE': if v in ('0', 'OFF'): trk.transpose = 0 elif v in ('1', 'ON'): trk.transpose = gbl.transpose else: error("MIDINote: TRANSPOSE expecting ON or OFF, not %s." % v) elif o == 'OFFSETS': if v == 'BEATS': trk.useticks = 0 elif v == 'TICKS': trk.useticks = 1 else: error("MIDINote: OFFSETS expecting BEATS or TICKS, not %s." % v) elif o == 'DURATION': if v == 'NOTES': trk.tickdur = 0 elif v == 'TICKS': trk.tickdur = 1 else: error("MIDINote: DURATION expecting NOTES or TICKS, not %s." % v) elif o == 'ARTICULATE': if v in ('1,' 'ON'): trk.articulate=1 elif v in ('0', 'OFF'): trk.articulate=0 else: error("MIDINote: ARTICULATE expecting ON or OFF, not %s." % v) elif o == 'OCTAVE': trk.oadjust = stoi(v) if trk.oadjust < -4 or trk.oadjust > 4: error("MIDINote: Octave adjustment must be -4..4, not '%s'." % trk.oadjust) elif o == 'VOLUME': trk.vadjust = stof(v)/100 if trk.vadjust <= 0: error("MIDINote: Volume %% adjustment must be > 0, not '%s'." % trk.vadjust) elif o == 'ADJUST': trk.tadjust = stoi(v) else: error("MIDINote: unknown option pair %s=%s." % (o,v)) # end of option pairs. if ln: # process rest of the stuff ... real midi events. trk.setForceOut() a=ln[0].upper() if a[0] in (".0123456789"): insertNote(trk, ln) elif a == "NOTE": insertNote(trk, ln[1:]) elif a == "PB": insertPB(trk, ln[1:]) elif a == "PBR": insertPBrange(trk, ln[1:]) elif a == "CTRL": insertControl(trk, ln[1:]) elif a == "CHAT": insertChTouch(trk, ln[1:]) elif a == "CHATR": insertChTouchRange(trk, ln[1:]) else: error("MidiNote: Unknown command '%s'." % a) if gbl.debug: if opts: print "MIDINOTE: %s" % mopts(trk) def mopts(trk): """ Return options string to macro and setoption-debug. """ if trk.useticks: a1='Ticks' else: a1='Beats' if trk.tickdur: a2='Ticks'; else: a2='Notes' if trk.transpose: a3='On' else: a3='Off' if trk.articulate: a4='On' else: a4='Off' return "Offsets=%s Duration=%s Transpose=%s Articulate=%s Adjust=%s Volume=%s Octave=%s" \ % (a1, a2, a3, a4, trk.tadjust, trk.vadjust * 100, trk.oadjust) def getoffset(trk, v): """ Convert a string (value) to an offset. Convert to beats if nesc.""" offset = stof(v) if trk.useticks: if offset != int(offset): error("MidiNote: Offset set to ticks, float '%s' given. Integer must be used." % offset) offset += trk.tadjust else: if offset < 1: warning("MidiNote: Offset %s generates notes before start of current bar." % offset) if offset >= gbl.QperBar + 1: warning("MidiNote: Offset %s is past end of current bar." % offset) offset = (offset-1) * gbl.BperQ return int(offset) # conversion to int is nescessary def note2val(trk, acctable, orig): """ Convert a note name to a value. In this case the OCTAVE setting is used! """ t = list(orig) n = t.pop(0) try: val = {'c':0, 'd':2, 'e':4, 'f':5, 'g':7, 'a':9, 'b':11 }[n] except: error("MidiNote: Expecting valid note name, not '%s'." % orig) val += trk.octave[gbl.seqCount] # add in current octave # Modify the note with either the keysignature or given accidental. if t: # override modifier if #,& or n if t[0] == '#': acctable[n] = 1 t.pop(0) elif t[0] == '&': acctable[n] = -1 t.pop(0) elif t[0] == 'n': acctable[n] = 0 t.pop(0) val += acctable[n] # adjust for octave - or + while t and (t[0] == '-' or t[0] == '+'): if t[0]=='+': val+=12 else: val-=12 t.pop(0) if t: #anything left? Error. error("MidiNote: Unknown note specifier '%s' in '%s'." % (''.join(t), orig)) return val def insertNote(trk, ln): """ Insert specified (raw) MIDI notes into track. """ if len (ln) != 4: error("Use: %s MidiNote: " % trk.name) acctable = MMA.keysig.keySig.accList # keysig modifier, use for chord offset=getoffset(trk, ln[0]) # Set a flag if this is a drum track. if trk.vtype == 'DRUM': isdrum = 1 elif trk.vtype in ('MELODY', 'SOLO') and trk.drumType: isdrum = 1 else: isdrum = 0 notes = [] for n in ln[1].split(','): if n[0] in '0123456789': n = stoi(n) else: if isdrum: if n == '*': if trk.vtype in ('MELODY', 'SOLO'): n = trk.drumTone else: n = trk.toneList[gbl.seqCount] else: n = MMA.midiC.drumToValue(n) if n < 0: error("MidiNote: unknown drum tone '%s' in %s." % (n, trk.name) ) else: n = note2val(trk, acctable, n) if n < 0 or n >127: error("MidiNote: Notes must be in the range 0...127, not %s" % n) if trk.transpose and not isdrum: n += gbl.transpose while n < 0: n += 12 while n > 127: n -= 12 if trk.oadjust and not isdrum: n += (trk.oadjust * 12) while n < 0: n += 12 while n > 127: n -= 12 notes.append(n) velocity = stoi(ln[2]) if velocity < 1 or velocity > 127: error("MidiNote: Note velocity must be in the range 1...127, not %s" % velocity) velocity *= trk.vadjust if velocity < 1: velocity = 1 elif velocity > 127: velocity = 127 velocity = int(velocity) # trk.adjust can be a float if trk.tickdur: duration = stoi(ln[3]) else: duration = MMA.notelen.getNoteLen(ln[3]) if trk.articulate: duration = (duration * trk.artic[gbl.seqCount]) / 100 if duration < 1: duration = 1 channel = trk.channel track = gbl.mtrks[channel] for n in notes: onEvent = chr(0x90 | channel-1) + chr(n) + chr(velocity) offEvent = onEvent[:-1] + chr(0) track.addToTrack(gbl.tickOffset + offset, onEvent) track.addToTrack(gbl.tickOffset + offset + duration, offEvent) if gbl.debug: print "MidiNote Note %s: inserted note %s at offset %s." % (trk.name, notes, offset) def insertPB(trk, ln): """ Insert a pitch controller event. """ if len(ln) != 2: error("MidiNote: PB expecting 2 arguments.") offset = getoffset(trk, ln[0]) v = stoi(ln[1]) if v<-8191 or v>8192: error("MidiNote: PB value must be -8191..+8192, not '%s'." % v) v+=8191 # convert to 0..16383, max 14 bit value channel = trk.channel track = gbl.mtrks[channel] track.addToTrack(gbl.tickOffset + offset, chr(0xe0 | channel-1) + chr(v%128) + chr(v/128)) if gbl.debug: print "MidiNote PB %s: inserted bend %s at offset %s." % (trk.name, v-8191, offset) def insertPBrange(trk, ln): """ Insert a range of PB events. """ if len(ln) != 3: error("MidiNote: PBR expecting 3 arguments .") count = stoi(ln[0]) try: s1,s2 = ln[1].split(',') except: error("MidiNote PBR: event range must be 'v1,v2', not '%s'." % ln[1]) s1 = getoffset(trk, s1) s2 = getoffset(trk, s2) tinc = (s2-s1)/float(count) try: v1,v2 = ln[2].split(',') except: error("MidiNote PBR: pitch blend range must be 'v1,v2', not '%s'." % ln[2]) v1 = stoi(v1) v2 = stoi(v2) if v1<-8191 or v1>8192 or v2<-8191 or v2>8192: error("MidiNote: PBR values must be -8191..+8192, not '%s'." % ln[2]) v1+=8191 # convert to 0..16383, max 14 bit value v2+=8191 vinc = (v2-v1)/float(count) channel = trk.channel track = gbl.mtrks[channel] ev = chr(0xe0 | channel-1) offset = s1 bend = v1 for i in range(count+1): v = int(bend) track.addToTrack(gbl.tickOffset + int(offset), ev + chr(v%128) + chr(v/128)) offset += tinc bend += vinc if gbl.debug: print "MidiNote PBR %s: inserted bends %s to %s at offsets %s to %s." % \ (trk.name, v1-8191, v2-8191, s1, s2) def insertControl(trk, ln): """ Insert a controller event. """ if len(ln) != 3: error("MidiNote: Controller expecting 3 arguments.") offset = getoffset(trk, ln[0]) v=MMA.midiC.ctrlToValue(ln[1]) if v < 0: v=stoi(ln[1]) if v < 0 or v > 0x7f: error("MidiNote: Controller values must be 0x00 to 0x7f, not '%s'." % ln[1]) d=stoi(ln[2]) if d < 0 or d > 0x7f: error("MidiNote: Control Datum value must be 0x00 to 0x7f, not '%s'." % ln[2]) channel = trk.channel track = gbl.mtrks[channel] # bypass the addctl() defined in midi.py just to keep all the calls in this # module similar. We should have add**() command in midi.py for the above stuff # and redo this.??? track.addToTrack(gbl.tickOffset + offset, chr(0xb0 | channel-1) + chr(v) + chr(d) ) if gbl.debug: print "MidiNote Ctrl %s: inserted Controller %s value %s at offset %s." % \ (trk.name, v, d, offset) def insertChTouch(trk, ln): """ Insert a channel aftertouch) event. """ if len(ln) != 2: error("MidiNote: ChAT expecting 2 arguments.") offset = getoffset(trk, ln[0]) v = stoi(ln[1]) if v<0 or v>127: error("MidiNote: ChAT value must be 0 .. 127, not '%s'." % v) channel = trk.channel track = gbl.mtrks[channel] track.addToTrack(gbl.tickOffset + offset, chr(0xd0 | channel-1) + chr(v) ) if gbl.debug: print "MidiNote ChAT %s: inserted channel aftertouch %s at offset %s." % \ (trk.name, v, offset) def insertChTouchRange(trk, ln): """ Insert a range of channel aftertouch events. """ if len(ln) != 3: error("MidiNote: ChATR expecting 3 arguments .") count = stoi(ln[0]) try: s1,s2 = ln[1].split(',') except: error("MidiNote ChATR: event range must be 'v1,v2', not '%s'." % ln[1]) s1 = getoffset(trk, s1) s2 = getoffset(trk, s2) tinc = (s2-s1)/float(count) try: v1,v2 = ln[2].split(',') except: error("MidiNote ChATR: range must be 'v1,v2', not '%s'." % ln[2]) v1 = stoi(v1) v2 = stoi(v2) if v1<01 or v1>127 or v2<0 or v2>127: error("MidiNote: ChATR values must be 0.. 127, not '%s'." % ln[2]) vinc = (v2-v1)/float(count) channel = trk.channel track = gbl.mtrks[channel] ev = chr(0xd0 | channel-1) offset = s1 bend = v1 for i in range(count+1): v = int(bend) track.addToTrack(gbl.tickOffset + int(offset), ev + chr(v) ) offset += tinc bend += vinc if gbl.debug: print "MidiNote ChATR %s: inserted events %s to %s at offsets %s to %s." % \ (trk.name, v1, v2, s1, s2)