mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 19:23:59 +00:00
2235 lines
63 KiB
Python
2235 lines
63 KiB
Python
|
|
# pat.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 <bob@mellowood.ca>
|
|
|
|
"""
|
|
|
|
|
|
import copy
|
|
import random
|
|
import math
|
|
|
|
import MMA.notelen
|
|
import MMA.translate
|
|
import MMA.midi
|
|
import MMA.midiC
|
|
import MMA.mdefine
|
|
import MMA.volume
|
|
import MMA.alloc
|
|
import MMA.seqrnd
|
|
|
|
import gbl
|
|
from MMA.common import *
|
|
|
|
|
|
pats = {} # Storage for all pattern defines
|
|
|
|
def getIncDec(v):
|
|
""" See if 'v' starts with -/+. Strip and return. """
|
|
|
|
if v.startswith('-'):
|
|
incr = -1
|
|
v = v[1:]
|
|
elif v.startswith('+'):
|
|
incr = 1
|
|
v = v[1:]
|
|
else:
|
|
incr = 0
|
|
|
|
return incr, v
|
|
|
|
def getIncDecValue(orig, inc, new):
|
|
""" Set value based on original, increment and new values. """
|
|
|
|
if inc == -1:
|
|
return orig - new
|
|
elif inc == 1:
|
|
return orig + new
|
|
else:
|
|
return new
|
|
|
|
def getRndPair(l, trk, min, max):
|
|
""" Parse off a pair (or single) value for random settings (rvolume, etc.). """
|
|
|
|
if not l:
|
|
error("%s: expecting a value or value pair." % trk)
|
|
|
|
if l.count(',')>1:
|
|
error("%s: Random pairs can only have one ','!" % trk)
|
|
|
|
if l.count(',') == 1:
|
|
n1, n2 = l.split(',')
|
|
try:
|
|
n1 = int(n1)
|
|
n2 = int(n2)
|
|
except:
|
|
error("%s: Expecting integers, not '%s' or '%s'." % (trk, n1, n2))
|
|
else:
|
|
n1 = l.strip()
|
|
try:
|
|
n1 = int(n1)
|
|
except:
|
|
error("%s: Expecting integer, not '%s'." % trk)
|
|
n2 = n1 * -1
|
|
|
|
if n2 < n1:
|
|
n1, n2 = n2, n1
|
|
|
|
if n1 < min or n1 > max or n2 < min or n2 > max:
|
|
error("%s: Max range is %s to %s." % (trk, min, max) )
|
|
|
|
return (n1, n2)
|
|
|
|
|
|
|
|
|
|
class PC:
|
|
""" Pattern class.
|
|
|
|
Define classes for processing drum, chord, arp, and chord track.
|
|
These are mostly the same, so we create a base class and derive
|
|
the others from it.
|
|
|
|
We have a class for each track type. They are all derived
|
|
from the base PC class. Some classes have their own __init__().
|
|
They call back here after doing their own special stuff.
|
|
|
|
"""
|
|
|
|
def __init__(self, nm):
|
|
|
|
self.inited = 0
|
|
self.name = nm
|
|
self.channel = 0
|
|
self.grooves = {}
|
|
self.saveVols = {}
|
|
self.ssvoice = -1 # Track the voice set for the track
|
|
self.smidiVoice = () # Track MIDIVoice cmds to avoid dups
|
|
self.midiSent = 0 # if set, MIDICLEAR invoked.
|
|
|
|
|
|
""" Midi commands like Pan, Glis, etc. are stacked until musical
|
|
data is actually written to the track. Each item in
|
|
the midiPending list is a name (PAN, GLIS, etc), timeoffset, value.
|
|
"""
|
|
|
|
self.midiPending = []
|
|
|
|
self.riff = []
|
|
|
|
self.disable = 0
|
|
|
|
self.clearSequence()
|
|
|
|
self.nextVolume = None
|
|
|
|
self.inited = 1
|
|
|
|
if gbl.muteTracks and nm not in gbl.muteTracks:
|
|
self.disable = 1
|
|
|
|
##########################################
|
|
## These are called from process() to set options
|
|
|
|
def setCompress(self, ln):
|
|
""" set/unset the compress flag. """
|
|
|
|
ln = lnExpand(ln, '%s Compress' % self.name)
|
|
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
|
|
n = stoi(n, "Argument for %s Compress must be a value" \
|
|
% self.name)
|
|
|
|
if n < 0 or n > 5:
|
|
error("Compress %s out-of-range; must be 0 to 5" % n)
|
|
|
|
if n and self.vtype=='CHORD' and self.voicing.mode:
|
|
vwarn = 1
|
|
|
|
tmp.append(n)
|
|
|
|
self.compress = seqBump(tmp)
|
|
|
|
if self.vtype not in ("CHORD", "ARPEGGIO"):
|
|
warning ("Compress is ignored in %s tracks" % self.vtype)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Compress to:" % self.name,
|
|
printList(self.compress)
|
|
|
|
|
|
def setRange(self, ln):
|
|
""" set range. """
|
|
|
|
ln = lnExpand(ln, '%s Range' % self.name)
|
|
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
|
|
n = stof(n)
|
|
if n == 0:
|
|
n=1
|
|
if n <= 0 or n >= 6:
|
|
error("Range %s out-of-range; must be between 0..6, not %s" % (self.name, n))
|
|
|
|
tmp.append(n)
|
|
|
|
self.chordRange = seqBump(tmp)
|
|
|
|
if self.vtype not in ("SCALE", "ARPEGGIO", "ARIA"):
|
|
warning ("Range ignored in '%s' tracks" % self.vtype)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Range to:" % self.name,
|
|
printList(self.chordRange)
|
|
|
|
|
|
def setVoicing(self, ln):
|
|
""" set the Voicing Mode options.
|
|
|
|
|
|
This is a stub. The real code is in patChord.py (settings are
|
|
only valid for that type). """
|
|
|
|
error("Voicing is not supported for %s tracks" % self.vtype)
|
|
|
|
|
|
|
|
def setForceOut(self):
|
|
""" Set the force output flag. This does 2 things: assigns
|
|
a midi channel and sends the voicing setting to the track.
|
|
"""
|
|
|
|
if not self.channel:
|
|
self.setChannel()
|
|
self.clearPending()
|
|
|
|
self.insertVoice()
|
|
|
|
|
|
def setDupRoot(self, ln):
|
|
""" set/unset root duplication.
|
|
|
|
This is a stub. Only valid for CHORDs and that is where the code is."""
|
|
|
|
|
|
warning("RootDup has no effect in %s tracks" % self.vtype)
|
|
|
|
|
|
def setChordLimit(self, ln):
|
|
""" set/unset the chordLimit flag. """
|
|
|
|
n = stoi(ln, "Argument for %s ChordLimit must be a value" % self.name)
|
|
|
|
if n < 0 or n > 8:
|
|
error("ChordLimit %s out-of-range; must be 0 to 8" % n)
|
|
|
|
self.chordLimit = n
|
|
|
|
if self.vtype not in ("CHORD", "ARPEGGIO"):
|
|
warning ("Limit is ignored in %s tracks" % self.vtype)
|
|
|
|
if gbl.debug:
|
|
print "Set %s ChordLimit to %s" % (self.name, n)
|
|
|
|
|
|
def setChannel(self, ln=None):
|
|
""" Set the midi-channel number for a track.
|
|
|
|
- Checks for channel duplication
|
|
- Auto assigns channel number if ln==''
|
|
|
|
|
|
If no track number was passed, then we try to
|
|
auto-alloc a track. First, we see if a preference
|
|
was set via MidiChPref. If these is no preference,
|
|
or if the preferred channel is already allocated
|
|
we go though the list, top to bottom, to find
|
|
an available channel.
|
|
"""
|
|
|
|
if not ln:
|
|
try:
|
|
c=gbl.midiChPrefs[self.name]
|
|
except:
|
|
c=0
|
|
|
|
if not c or gbl.midiAvail[c]:
|
|
c=-1
|
|
for a in range(16, 0, -1):
|
|
if a!=10 and not gbl.midiAvail[a]:
|
|
c=a
|
|
break
|
|
|
|
if c < 0:
|
|
error("No MIDI channel is available for %s,\n"
|
|
"Try CHShare or Delete unused tracks" % self.name)
|
|
|
|
else:
|
|
c = stoi(ln, "%s Channel assignment expecting Value, not %s" %
|
|
(self.name, ln))
|
|
|
|
if c<0 or c>16:
|
|
error("%s Channel must be 0..16, not %s" % (self.name, ln))
|
|
|
|
if c == 10:
|
|
if self.vtype == 'DRUM':
|
|
pass
|
|
elif self.vtype in ('SOLO', 'MELODY') and self.drumType:
|
|
pass
|
|
else:
|
|
error("Channel 10 is reserved for DRUM, not %s" % self.name)
|
|
|
|
if self.vtype == 'DRUM' and c != 10:
|
|
error("DRUM tracks must be assigned to channel 10")
|
|
|
|
# Disable the channel.
|
|
|
|
if c == 0:
|
|
if gbl.midiAvail[self.channel]:
|
|
gbl.midiAvail[self.channel] -= 1
|
|
s="%s channel disabled" % self.name
|
|
if gbl.midiAvail[self.channel]:
|
|
s+=" Other tracks are still using channel %s" % self.channel
|
|
else:
|
|
s+=" Channel %s available" % self.channel
|
|
warning(s)
|
|
self.channel = 0
|
|
self.disable = 1
|
|
return
|
|
|
|
|
|
if c != 10:
|
|
for a, tr in gbl.tnames.items():
|
|
if a == self.name: # okay to reassign same number
|
|
continue
|
|
|
|
if tr.channel == c:
|
|
error("Channel %s is assigned to %s" % (c, tr.name ) )
|
|
|
|
self.channel = c
|
|
if not self.name in gbl.midiAssigns[c]:
|
|
gbl.midiAssigns[c].append(self.name)
|
|
|
|
gbl.midiAvail[c]+=1
|
|
|
|
if not c in gbl.mtrks:
|
|
gbl.mtrks[c]=MMA.midi.Mtrk(c)
|
|
offset=0
|
|
if gbl.debug:
|
|
print "MIDI channel %s buffer created" % c
|
|
else:
|
|
offset = gbl.tickOffset
|
|
|
|
if c != 10:
|
|
f=0
|
|
for a, i in enumerate(self.midiPending):
|
|
if i[0]=='TNAME':
|
|
f=1
|
|
if not f:
|
|
self.midiPending.append(('TNAME', 0, self.name.title() ))
|
|
|
|
if gbl.debug:
|
|
print "MIDI Channel %s assigned to %s" % (self.channel, self.name)
|
|
|
|
|
|
def setChShare(self, ln):
|
|
""" Share midi-channel setting. """
|
|
|
|
if self.channel: # If channel already assigned, ignore
|
|
warning("Channel for %s has previously been assigned "
|
|
"(can't ChShare)" % self.name)
|
|
return
|
|
|
|
""" Get name of track to share with and make sure it exists.
|
|
If not, trackAlloc() will create the track. Do some
|
|
sanity checks and ensure that the shared track has
|
|
a channel assigned.
|
|
"""
|
|
|
|
sc = ln.upper()
|
|
|
|
MMA.alloc.trackAlloc(sc, 1)
|
|
|
|
if not sc in gbl.tnames:
|
|
error("Channel '%s' does not exist. No such name" % sc)
|
|
|
|
if sc == self.name:
|
|
error("%s can't share MIDI channel with itself" % sc)
|
|
|
|
|
|
if not gbl.tnames[sc].channel:
|
|
gbl.tnames[sc].setChannel()
|
|
|
|
schannel = gbl.tnames[sc].channel
|
|
|
|
if not schannel:
|
|
error("CHShare attempted to assign MIDI channel for %s, but "
|
|
"none avaiable" % self.name)
|
|
|
|
|
|
""" Actually do the assignment. Also copy voice/octave from
|
|
base track to this one ... it's going to use that voice anyway?
|
|
"""
|
|
|
|
self.channel = schannel
|
|
|
|
self.voice = gbl.tnames[sc].voice[:]
|
|
self.octave = gbl.tnames[sc].octave[:]
|
|
|
|
# Update the avail. lists
|
|
|
|
gbl.midiAssigns[self.channel].append(self.name)
|
|
gbl.midiAvail[self.channel]+=1
|
|
|
|
|
|
def setChannelVolume(self, v):
|
|
""" LowLevel MIDI command. Set Channel Volume. """
|
|
|
|
self.midiPending.append(( "CVOLUME", gbl.tickOffset, v) )
|
|
|
|
if gbl.debug:
|
|
print "Set %s MIDIChannelVolume to %s" % (self.name, v)
|
|
|
|
def setMidiCresc(self, v1, v2, count):
|
|
""" Low level MIDI (de)cresc channel volume."""
|
|
|
|
t = v2-v1 # total number of steps
|
|
step = (count * gbl.BperQ * gbl.QperBar) / abs(t) # step rate
|
|
p=gbl.tickOffset
|
|
if v2<v1:
|
|
dir=-1
|
|
else:
|
|
dir=1
|
|
for v in range(v1,v2+dir,dir):
|
|
self.midiPending.append(("CVOLUME", int(p), v))
|
|
p+=step
|
|
|
|
if gbl.debug:
|
|
print "%s MIDIChannelVolume: Added %s changes" % (self.name, t)
|
|
|
|
|
|
def setTname(self, n):
|
|
""" Set the track name.
|
|
|
|
This is stacked and only gets output if track generates MIDI.
|
|
It is a handy way to override MMA's track naming.
|
|
"""
|
|
|
|
self.midiPending.append(('TNAME', 0, n ))
|
|
if gbl.debug:
|
|
print "Set %s track name for MIDI to %s" % (self.name, n)
|
|
|
|
def setPan(self, ln):
|
|
""" Set MIDI Pan for this track. Parse sends 1 or 3 args. """
|
|
|
|
if len(ln)==3:
|
|
beats = stof(ln[2])
|
|
if beats < 1:
|
|
error("Beat value must be positive count, not '%s'." % beats)
|
|
initPan = stoi(ln[0], "Expecting integer value 0..127")
|
|
newPan = stoi(ln[1], "Expecting integer value 0..127")
|
|
else:
|
|
beats = 0
|
|
initPan = 0
|
|
newPan = stoi(ln[0], "Expecting integer value 0..127")
|
|
|
|
ticks = beats * gbl.BperQ # convert beats to midi ticks
|
|
|
|
if initPan<0 or initPan>127:
|
|
error("Initial MidiPAN value must be 0..127")
|
|
|
|
if newPan<0 or newPan>127:
|
|
error("Final MidiPAN value must be 0..127")
|
|
|
|
if newPan<initPan:
|
|
span = initPan-newPan
|
|
changer=-1
|
|
else:
|
|
span = newPan-initPan
|
|
changer=1
|
|
|
|
if span>0:
|
|
step = ticks/span
|
|
else:
|
|
beats=0
|
|
|
|
if beats:
|
|
v=initPan
|
|
off=gbl.tickOffset
|
|
for a in range(span+1):
|
|
self.midiPending.append( ("PAN", int(off), v))
|
|
off+=step
|
|
v+=changer
|
|
|
|
else:
|
|
self.midiPending.append( ("PAN", gbl.tickOffset, newPan))
|
|
|
|
if gbl.debug:
|
|
if beats:
|
|
print "Set %s MIDIPan from %s to %s over %s beats." % \
|
|
(self.name, initPan, newPan, beats)
|
|
else:
|
|
print "Set %s MIDIPan to %s" % (self.name, newPan)
|
|
|
|
|
|
|
|
def setGlis(self, ln):
|
|
""" Set MIDI Glis for this track. """
|
|
|
|
v = stoi(ln, "Expecting integer for Portamento")
|
|
|
|
if v<0 or v>127:
|
|
error("Value for Portamento must be 0..127")
|
|
|
|
self.midiPending.append( ("GLIS", gbl.tickOffset, v))
|
|
|
|
if gbl.debug:
|
|
print "Set %s MIDIPortamento to %s" % (self.name, v)
|
|
|
|
def setMidiText(self, ln):
|
|
""" Set/queue midi text. """
|
|
|
|
self.midiPending.append( ("MIDITEXT", gbl.tickOffset, ln))
|
|
|
|
if gbl.debug:
|
|
print "Set %s MIDIText '%s'." % ln
|
|
|
|
def setMidiCue(self, ln):
|
|
""" Set/queue midi cue. """
|
|
|
|
self.midiPending.append( ("MIDICUE", gbl.tickOffset, ln))
|
|
|
|
if gbl.debug:
|
|
print "Set %s MIDICue '%s'." % ln
|
|
|
|
def setStrum(self, ln):
|
|
""" Set Strum time. """
|
|
|
|
ln = lnExpand(ln, '%s Strum' % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
if ',' in n:
|
|
a,b = n.split(',', 1)
|
|
a = stoi(a, "Argument for %s Strum must be an integer" % self.name)
|
|
b = stoi(b, "Argument for %s Strum must be an integer" % self.name)
|
|
else:
|
|
a = stoi(n, "Argument for %s Strum must be an integer" % self.name)
|
|
b = a
|
|
|
|
if a>b:
|
|
xx=a
|
|
a=b
|
|
b=xx
|
|
|
|
if a < -300 or a > 300 or b < -300 or b > 300:
|
|
error("STRUM: %s out-of-range. All values must be -300..300" % n)
|
|
|
|
if a == 0 and b == 0:
|
|
tmp.append(None)
|
|
else:
|
|
tmp.append((a,b))
|
|
|
|
self.strum = seqBump(tmp)
|
|
|
|
if self.vtype == "DRUM":
|
|
warning("Strum has no effect in %s tracks" % self.name)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Strum to %s" % (self.name, self.strum)
|
|
|
|
|
|
def getStrum(self, sc):
|
|
""" Returns the strum factor. Note that if strum==(0,0) a 0 is returned."""
|
|
|
|
if not self.strum[sc]:
|
|
return 0
|
|
else:
|
|
return random.randint(self.strum[sc][0], self.strum[sc][1])
|
|
|
|
def setTone(self, ln):
|
|
""" Set Tone. Error trap, only drum tracks have tone. """
|
|
|
|
error("Tone command not supported for %s track" % self.name)
|
|
|
|
|
|
def setOn(self):
|
|
""" Turn ON track. """
|
|
|
|
if gbl.muteTracks and self.name not in gbl.muteTracks:
|
|
warning("Attempt to enable muted track %s ignored." % self.name)
|
|
|
|
else:
|
|
self.disable = 0
|
|
self.ssvoice = -1
|
|
|
|
if gbl.debug:
|
|
print "%s Enabled" % self.name
|
|
|
|
|
|
def setOff(self):
|
|
""" Turn OFF track. """
|
|
|
|
self.disable = 1
|
|
|
|
if gbl.debug:
|
|
print "%s Disabled" % self.name
|
|
|
|
|
|
|
|
def setRVolume(self, ln):
|
|
""" Set the volume randomizer for a track. """
|
|
|
|
msg = "%s Rvolume" % self.name
|
|
ln = lnExpand(ln, msg)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n1, n2 = getRndPair(n, msg, -100, 100)
|
|
tmp.append( [ n1/100. , n2/100. ] )
|
|
|
|
self.rVolume = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "%s:" % msg,
|
|
for n1, n2 in self.rVolume:
|
|
n1 = int(n1 * 100)
|
|
n2 = int(n2 * 100)
|
|
if abs(n1) == n2:
|
|
print "%s" % n2,
|
|
else:
|
|
print "%s,%s" % (n1, n2),
|
|
print
|
|
|
|
|
|
def setRSkip(self, ln):
|
|
""" Set the note random skip factor for a track. """
|
|
|
|
msg = "%s RSkip" % self.name
|
|
ln = lnExpand(ln, msg)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = stoi(n, "%s: Expecting integer" % msg)
|
|
|
|
if n < 0 or n > 99:
|
|
error("%s: arg must be 0..99" % msg)
|
|
|
|
tmp.append(n/100.)
|
|
|
|
self.rSkip = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "%s set to:" % msg,
|
|
for n in self.rSkip:
|
|
print int(n * 100),
|
|
print
|
|
|
|
|
|
def setRTime(self, ln):
|
|
""" Set the timing randomizer for a track. """
|
|
|
|
msg = "%s RTime" % self.name
|
|
ln = lnExpand(ln, msg)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n1, n2 = getRndPair(n, msg, -100, 100)
|
|
tmp.append([n1, n2])
|
|
|
|
self.rTime = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "%s:" % msg,
|
|
for n1, n2 in self.rVolume:
|
|
if abs(n1) == n2:
|
|
print "%s" % n2,
|
|
else:
|
|
print "%s,%s" % (n1, n2),
|
|
print
|
|
|
|
def setRnd(self, arg):
|
|
""" Enable random pattern selection from sequence."""
|
|
|
|
if arg in ("ON", "1"):
|
|
self.seqRnd = 1
|
|
|
|
elif arg in ("OFF", "0"):
|
|
self.seqRnd = 0
|
|
|
|
else:
|
|
error("SeqRnd: '%s' is not a valid option" % arg)
|
|
|
|
if gbl.debug:
|
|
if self.seqRnd:
|
|
a="On"
|
|
else:
|
|
a="Off"
|
|
print "%s SeqRnd: %s" % (self.name, a)
|
|
|
|
|
|
def setRndWeight(self, ln):
|
|
""" Set weighting factors for seqrnd. """
|
|
|
|
ln = lnExpand(ln, "%s SeqRndWeight" % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = stoi(n)
|
|
if n < 0: error("SeqRndWeight: Values must be 0 or greater")
|
|
tmp.append(n)
|
|
|
|
self.seqRndWeight = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "%s SeqRndWeight:" % self.name,
|
|
printList(self.seqRndWeight)
|
|
|
|
|
|
def setDirection(self, ln):
|
|
""" Set scale direction. """
|
|
|
|
ln = lnExpand(ln, "%s Direction" % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = n.upper()
|
|
if not n in ('UP', 'DOWN', 'BOTH', 'RANDOM'):
|
|
error("Unknown %s Direction '%s'" % (self.name, n) )
|
|
tmp.append(n)
|
|
|
|
self.direction = seqBump(tmp)
|
|
|
|
if self.vtype == 'SCALE':
|
|
self.lastChord = None
|
|
self.lastNote = -1
|
|
|
|
|
|
if gbl.debug:
|
|
print "Set %s Direction to:" % self.name,
|
|
printList(self.direction)
|
|
|
|
|
|
def setScaletype(self, ln):
|
|
""" Set scale type.
|
|
|
|
This is a error stub. The real code is in the permitted track code.
|
|
"""
|
|
|
|
warning("ScaleType has no effect in %s tracks") % self.vtype
|
|
|
|
|
|
def setInvert(self, ln):
|
|
""" Set inversion for track.
|
|
|
|
This can be applied to any track,
|
|
but has no effect in drum tracks. It inverts the chord
|
|
by one rotation for each value.
|
|
"""
|
|
|
|
ln = lnExpand(ln, "%s Invert" % self.name)
|
|
|
|
vwarn = 0
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = stoi(n, "Argument for %s Invert must be an integer" % self.name)
|
|
|
|
if n and self.vtype=='CHORD' and self.voicing.mode:
|
|
vwarn = 1
|
|
|
|
tmp.append(n)
|
|
|
|
self.invert = seqBump(tmp)
|
|
|
|
if self.vtype not in ("CHORD", "ARPEGGIO"):
|
|
warning ("Invert is ignored in %s tracks" % self.vtype)
|
|
|
|
if vwarn:
|
|
warning("Setting both Voicing Mode and Invert is not a good idea")
|
|
|
|
if gbl.debug:
|
|
print "Set %s Invert to:" % self.name,
|
|
printList(self.invert)
|
|
|
|
|
|
def setOctave(self, ln):
|
|
""" Set the octave for a track. Use 0-10, -x, +x. """
|
|
|
|
ln = lnExpand(ln, '%s Octave' % self.name)
|
|
ln = seqBump(ln) # Needed for +/- values to complete
|
|
tmp = []
|
|
i = 0
|
|
for n in ln:
|
|
incr, n = getIncDec(n)
|
|
n = stoi(n, "Argument for %s Octave must be an integer" % self.name)
|
|
n = getIncDecValue(self.octave[i]/12, incr, n)
|
|
|
|
if n < 0 or n > 10:
|
|
error("Octave %s out-of-range; must be 0..10" % n)
|
|
tmp.append( n * 12 )
|
|
i+=1
|
|
|
|
|
|
self.octave = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Octave to:" % self.name,
|
|
for i in self.octave:
|
|
print i/12,
|
|
print
|
|
|
|
|
|
def setSpan(self, start, end):
|
|
""" Set span.
|
|
|
|
Note: The start/end parm has been verified in parser.
|
|
|
|
"""
|
|
|
|
if self.vtype == 'DRUM':
|
|
warning("Span has no effect in Drum tracks")
|
|
|
|
self.spanStart = start
|
|
self.spanEnd = end
|
|
|
|
if gbl.debug:
|
|
print "Set %s Span to %s...%s" % (self.name, self.spanStart, self.spanEnd)
|
|
|
|
|
|
def setHarmony(self, ln):
|
|
""" Set the harmony. """
|
|
|
|
ln = lnExpand(ln, '%s Harmony' % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = n.upper()
|
|
if n in ( '-', '-0', 'NONE'):
|
|
n = None
|
|
|
|
tmp.append(n)
|
|
|
|
self.harmony = seqBump(tmp)
|
|
|
|
if self.vtype in ( 'CHORD', 'DRUM' ):
|
|
warning("Harmony setting for %s track ignored" % self.vtype)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Harmony to:" % self.name,
|
|
printList(self.harmony)
|
|
|
|
|
|
def setHarmonyOnly(self, ln):
|
|
""" Set the harmony only. """
|
|
|
|
|
|
ln = lnExpand(ln, '%s HarmonyOnly' % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = n.upper()
|
|
if n in ('-', '0'):
|
|
n = None
|
|
|
|
tmp.append(n)
|
|
|
|
self.harmony = seqBump(tmp)
|
|
self.harmonyOnly = seqBump(tmp)
|
|
|
|
if self.vtype in ( 'CHORD', 'DRUM'):
|
|
warning("HarmonyOnly setting for %s track ignored" % self.vtype)
|
|
|
|
if gbl.debug:
|
|
print "Set %s HarmonyOnly to:" % self.name,
|
|
printList(self.harmonyOnly)
|
|
|
|
|
|
def setHarmonyVolume(self, ln):
|
|
""" Set harmony volume adjustment. """
|
|
|
|
ln = lnExpand(ln, '%s HarmonyOnly' % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
v=stoi(n)
|
|
|
|
if v<0:
|
|
error("HarmonyVolume adjustment must be positive integer")
|
|
tmp.append(v/100.)
|
|
|
|
self.harmonyVolume = seqBump(tmp)
|
|
|
|
if self.vtype in ( 'CHORD', 'DRUM' ):
|
|
warning("HarmonyVolume adjustment for %s track ignored" % self.vtype)
|
|
|
|
if gbl.debug:
|
|
print "Set %s HarmonyVolume to:" % self.name,
|
|
printList(self.harmonyVolume)
|
|
|
|
|
|
def setSeqSize(self):
|
|
""" Expand existing pattern list.
|
|
|
|
Track functions my have their own and do a callback.
|
|
"""
|
|
|
|
self.sequence = seqBump(self.sequence)
|
|
if self.midiVoice:
|
|
self.midiVoice = seqBump(self.midiVoice)
|
|
if self.midiSeq:
|
|
self.midiSeq = seqBump(self.midiSeq)
|
|
self.invert = seqBump(self.invert)
|
|
self.artic = seqBump(self.artic)
|
|
self.volume = seqBump(self.volume)
|
|
self.voice = seqBump(self.voice)
|
|
self.rVolume = seqBump(self.rVolume)
|
|
self.rSkip = seqBump(self.rSkip)
|
|
self.rTime = seqBump(self.rTime)
|
|
self.seqRndWeight = seqBump(self.seqRndWeight)
|
|
self.strum = seqBump(self.strum)
|
|
self.octave = seqBump(self.octave)
|
|
self.harmony = seqBump(self.harmony)
|
|
self.harmonyOnly = seqBump(self.harmonyOnly)
|
|
self.harmonyVolume = seqBump(self.harmonyVolume)
|
|
self.direction = seqBump(self.direction)
|
|
self.scaleType = seqBump(self.scaleType)
|
|
self.compress = seqBump(self.compress)
|
|
self.chordRange = seqBump(self.chordRange)
|
|
self.dupRoot = seqBump(self.dupRoot)
|
|
self.unify = seqBump(self.unify)
|
|
self.accent = seqBump(self.accent)
|
|
|
|
|
|
def setVoice(self, ln):
|
|
""" Set the voice for a track.
|
|
|
|
Note, this just sets flags, the voice is set in bar().
|
|
ln[] is not nesc. set to the correct length.
|
|
|
|
the voice can be gm-string, user-def-string, or value.
|
|
A value can be xx.yy.vv, yy.vv or vv
|
|
"""
|
|
|
|
ln = lnExpand(ln, '%s Voice' % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n = MMA.translate.vtable.get(n)
|
|
voc=MMA.midiC.instToValue(n)
|
|
|
|
if voc < 0 and n[0].isalpha():
|
|
error("Voice '%s' is not defined." % n)
|
|
if voc < 0: # not a valid name, assume vv.msb(ctrl0).lsb(ctrl32) value
|
|
nn = n.split('.')
|
|
if len(nn) > 3 or len(nn) < 1:
|
|
error("Expecting a voice value Prog.MSB.LSB, not '%s'" % n)
|
|
voc = 0
|
|
if len(nn) > 2:
|
|
i = stoi(nn[2])
|
|
if i<0 or i>127:
|
|
error("LSB must be 0..127, not '%s'" % i)
|
|
voc = i << 16
|
|
|
|
if len(nn) > 1:
|
|
i = stoi(nn[1])
|
|
if i<0 or i>127:
|
|
error("MSB must be 0..127, not '%s'" % i)
|
|
voc += i << 8
|
|
|
|
i = stoi(nn[0])
|
|
if i<0 or i>127:
|
|
error("Program must be 0..127, not '%s'" % i)
|
|
voc += i
|
|
|
|
tmp.append( voc )
|
|
|
|
self.voice = seqBump(tmp)
|
|
|
|
if self.channel and len(gbl.midiAssigns[self.channel])>1:
|
|
a=''
|
|
for n in gbl.midiAssigns[self.channel]:
|
|
if n != self.name:
|
|
a += ' %s' % n
|
|
warning("Track %s is shared with %s,\n"
|
|
" changing voice may create conflict" % (a,self.name))
|
|
|
|
if gbl.debug:
|
|
print "Set %s Voice to:" % self.name,
|
|
for i in self.voice:
|
|
print MMA.midiC.valueToInst(i),
|
|
print
|
|
|
|
|
|
def setMidiClear(self, ln):
|
|
""" Set MIDIclear sequences. """
|
|
|
|
|
|
if ln[0] in 'zZ-':
|
|
self.midiClear = None
|
|
else:
|
|
self.midiClear = MMA.mdefine.mdef.get(ln[0])
|
|
|
|
if gbl.debug:
|
|
print "%s MIDIClear: %s" % (self.name, self.midiSeqFmt(self.midiClear))
|
|
|
|
|
|
def doMidiClear(self):
|
|
""" Reset MIDI settings. """
|
|
|
|
if self.midiSent:
|
|
if not self.midiClear:
|
|
warning("%s: Midi data has been inserted with MIDIVoice/Seq "
|
|
"but no MIDIClear data is present" % self.name)
|
|
|
|
else:
|
|
for i in self.midiClear:
|
|
gbl.mtrks[self.channel].addCtl(gbl.tickOffset, i[1])
|
|
|
|
self.midiSent = 0
|
|
|
|
def doChannelReset(self):
|
|
""" Reset the midi voicing to 'sane'. Called when track ended. """
|
|
|
|
if self.ssvoice > 127:
|
|
gbl.mtrks[self.channel].addProgChange( gbl.tickOffset, 0, self.ssvoice)
|
|
|
|
def setMidiSeq(self, ln):
|
|
""" Set a midi sequence for a track.
|
|
|
|
This is sent for every bar. Syntax is:
|
|
<beat> <ctrl> hh .. ; ...
|
|
|
|
or a single '-' to disable.
|
|
"""
|
|
|
|
""" lnExpand() works here! The midi data has been converted to
|
|
pseudo-macros already in the parser. """
|
|
|
|
ln = lnExpand(ln, "%s MidiSeq" % self.name)
|
|
|
|
seq = []
|
|
for a in ln:
|
|
if a in 'zZ-':
|
|
seq.append(None)
|
|
else:
|
|
seq.append(MMA.mdefine.mdef.get(a.upper()))
|
|
|
|
if seq.count(None) == len(seq):
|
|
self.midiSeq = []
|
|
else:
|
|
self.midiSeq = seqBump( seq )
|
|
|
|
if gbl.debug:
|
|
print "%s MIDISeq:" % self.name,
|
|
for l in seq:
|
|
print '{ %s }' % self.midiSeqFmt(l),
|
|
|
|
|
|
|
|
def setMidiVoice(self, ln):
|
|
""" Set a MIDI sequence for a track.
|
|
|
|
This is sent whenever we send a VOICE. Syntax is:
|
|
<beat> <ctrl> hh .. ; ...
|
|
|
|
or a single '-' to disable.
|
|
"""
|
|
|
|
""" lnExpand() works here! The midi data has been converted to
|
|
pseudo-macros already in the parser. """
|
|
|
|
ln = lnExpand(ln, '%s MIDIVoice' % self.name)
|
|
|
|
seq = []
|
|
for a in ln:
|
|
if a in 'zZ':
|
|
seq.append(None)
|
|
else:
|
|
seq.append(MMA.mdefine.mdef.get(a.upper()))
|
|
|
|
if seq.count(None) == len(seq):
|
|
self.midiVoice = []
|
|
else:
|
|
self.midiVoice = seqBump( seq )
|
|
|
|
|
|
if gbl.debug:
|
|
print "%s MIDIVoice:" % self.name,
|
|
for l in seq:
|
|
print '{ %s }' % self.midiSeqFmt(l),
|
|
print
|
|
|
|
|
|
def midiSeqFmt(self, lst):
|
|
""" Used by setMidiVoice/Clear/Seq for debugging format. """
|
|
|
|
if lst == None:
|
|
return ''
|
|
ret=''
|
|
for i in lst:
|
|
ret += "%s %s 0x%02x ; " % (i[0],
|
|
MMA.midiC.valueToCtrl(ord(i[1][0])),
|
|
ord(i[1][1]))
|
|
return ret.rstrip("; ")
|
|
|
|
|
|
def setVolume(self, ln):
|
|
""" Set the volume for a pattern.
|
|
ln - list of volume names (pp, mf, etc)
|
|
ln[] not nesc. correct length
|
|
"""
|
|
|
|
ln = lnExpand(ln, '%s Volume' % self.name)
|
|
tmp = [None] * len(ln)
|
|
self.futureVols = [] # clear off dangling (de)cresc for voice.
|
|
|
|
for i,n in enumerate(ln):
|
|
a = MMA.volume.calcVolume(n, self.volume[i])
|
|
|
|
if self.vtype == 'DRUM':
|
|
a=MMA.translate.drumVolTable.get(self.toneList[i], a)
|
|
else:
|
|
a=MMA.translate.voiceVolTable.get(self.voice[i], a)
|
|
tmp[i] = a
|
|
|
|
self.volume = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Volume to:" % self.name,
|
|
for a in self.volume:
|
|
print int(a * 100),
|
|
print
|
|
|
|
|
|
def setCresc(self, dir, ln):
|
|
""" Set Crescendo for a track. """
|
|
|
|
if len(ln) == 3:
|
|
self.setVolume([ln[0]])
|
|
ln=ln[1:]
|
|
|
|
if len(ln) != 2:
|
|
error("Cresc expecting 2 or 3 args.")
|
|
|
|
vol = self.volume[0]
|
|
|
|
if self.volume.count(vol) != len(self.volume):
|
|
warning("(De)Crescendo being used with track with variable sequence volumes")
|
|
|
|
self.futureVols = MMA.volume.fvolume(dir, vol, ln)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Cresc to:" % self.name,
|
|
for a in self.futureVols:
|
|
print int(a*100),
|
|
print
|
|
|
|
|
|
def setSwell(self, ln):
|
|
""" Set a swell (cresc<>decresc) for track. """
|
|
|
|
if len(ln) == 3: # 3 args, 1st is intial setting
|
|
self.setVolume([ln[0]])
|
|
ln=ln[1:]
|
|
|
|
if len(ln) != 2:
|
|
error("%s Swell expecting 2 or 3 args." % self.name)
|
|
|
|
if self.volume.count(self.volume[0]) != len(self.volume):
|
|
warning("%s Swell being used with track with variable sequence volumes." \
|
|
% self.name)
|
|
|
|
count = stoi(ln[1])
|
|
if count < 2:
|
|
error("%s Swell bar count must be 2 or greater." % self.name)
|
|
|
|
count += 1
|
|
c = int(count/2)
|
|
if count % 2: # even number of bars (we bumped count)
|
|
offset = 1
|
|
c+=1
|
|
else: # odd number
|
|
offset = 0
|
|
c=str(c)
|
|
|
|
self.futureVols = MMA.volume.fvolume(0 ,self.volume[0],
|
|
[ ln[0], c ] )
|
|
|
|
self.futureVols.extend(MMA.volume.fvolume(0, self.futureVols[-1],
|
|
[str(int(self.volume[0]*100)), c ] )[offset:] )
|
|
|
|
if gbl.debug:
|
|
print "Set %s Swell to:" % self.name,
|
|
for a in self.futureVols:
|
|
print int(a*100),
|
|
print
|
|
|
|
|
|
def setMallet(self, ln):
|
|
""" Mallet (repeat) settngs. """
|
|
|
|
if self.vtype == "PLECTRUM":
|
|
warning("%s: Mallet has no effect, ignored." % self.name)
|
|
return
|
|
|
|
for l in ln:
|
|
try:
|
|
mode, val = l.upper().split('=')
|
|
except:
|
|
error("Each Mallet option must contain a '=', not '%s'" % l)
|
|
|
|
if mode == 'RATE':
|
|
if val == '0' or val.upper() == 'NONE':
|
|
self.mallet = 0
|
|
else:
|
|
self.mallet = MMA.notelen.getNoteLen(val)
|
|
|
|
elif mode == 'DECAY':
|
|
val = stof(val, "Mallet Decay must be a value, not '%s'" % val)
|
|
|
|
if val < -50 or val > 50:
|
|
error("Mallet Decay rate must be -50..+50")
|
|
|
|
self.malletDecay = val/100
|
|
|
|
if gbl.debug:
|
|
print "%s Mallet Rate:%s Decay:%s" % \
|
|
(self.name, self.mallet, self.malletDecay)
|
|
|
|
|
|
def setAccent(self, ln):
|
|
""" Set the accent values. This is a list of lists, a list for each seq. """
|
|
|
|
tmp = []
|
|
|
|
|
|
""" 2 ways to call this:
|
|
|
|
Track Accent 1 20 3 -10 -- fix up by adding {} around entire option list,
|
|
Track Accent {1 20 3 -10} -- okay
|
|
Track Accent {1 20} / {/} {3 20} -- okay.
|
|
|
|
At this point ln is a string. We extract each set of {}s and build a
|
|
new ln[].
|
|
|
|
Note that the "/" can or not have {}s.
|
|
"""
|
|
|
|
ln = ' '.join(ln)
|
|
if not ln.startswith('{'):
|
|
ln='{' + ln +"}"
|
|
|
|
l=[]
|
|
while ln:
|
|
if ln[0] == "/": # / not delimited with {}
|
|
ln = "{/}" + ln[1:]
|
|
|
|
a,b = pextract(ln, "{", "}", 1)
|
|
ln=a.strip()
|
|
if len(b)==1 and b[0]=='/': # convert ['/'] to '/' for lnExpand()
|
|
l.append('/')
|
|
else:
|
|
l.append(b[0].split())
|
|
|
|
ln = lnExpand(l, '%s Accent' % self.name)
|
|
|
|
for l in ln:
|
|
tt=[]
|
|
if len(l)/2*2 != len(l):
|
|
error("Use: %s Accent Beat Percentage [...]" % self.name)
|
|
|
|
for b, v in zip(l[::2], l[1::2]):
|
|
b=self.setBarOffset( b )
|
|
v=stoi(v, "Beat offset must be a value, not '%s'" % v)
|
|
if v < -100 or v > 100:
|
|
error("Velocity adjustment (as percentage) must "
|
|
"be -100..100, not '%s'" % v)
|
|
|
|
tt.append( (b, v/100. ) )
|
|
tmp.append(tt)
|
|
|
|
self.accent = seqBump( tmp )
|
|
|
|
if gbl.debug:
|
|
print "%s Accent:" % self.name,
|
|
for s in self.accent:
|
|
print "{",
|
|
for b,v in s:
|
|
print '%s %s' % (1+(b/float(gbl.BperQ)), int(v*100)),
|
|
print "}",
|
|
print
|
|
|
|
|
|
def setArtic(self, ln):
|
|
""" Set the note articuation value. """
|
|
|
|
ln = lnExpand(ln, '%s Articulate' % self.name)
|
|
ln = seqBump(ln)
|
|
tmp = []
|
|
incr = 0
|
|
i = 0
|
|
|
|
for n in ln:
|
|
incr, n = getIncDec(n)
|
|
a = stoi(n, "Expecting value in articulation setting")
|
|
a = getIncDecValue(self.artic[i], incr, a)
|
|
|
|
if a < 1 or a > 500:
|
|
error("%s: Articulation setting must be 1..500, not %s" % (self.name,a))
|
|
|
|
if a > 200:
|
|
warning("%s: Articulation '%s' is a large value." % (self.name, a))
|
|
|
|
tmp.append(a)
|
|
i += 1
|
|
|
|
self.artic = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Articulate to:" % self.name,
|
|
printList(self.artic)
|
|
|
|
|
|
def setUnify(self, ln):
|
|
""" Set unify. """
|
|
|
|
ln = lnExpand(ln, "%s Unify" % self.name)
|
|
tmp = []
|
|
|
|
for n in ln:
|
|
n=n.upper()
|
|
if n in ( 'ON', '1'):
|
|
tmp.append(1)
|
|
elif n in( 'OFF', '0'):
|
|
tmp.append(0)
|
|
else:
|
|
error("Unify accepts ON | OFF | 0 | 1")
|
|
|
|
self.unify = seqBump(tmp)
|
|
|
|
if gbl.debug:
|
|
print "Set %s Unify to:" % self.name,
|
|
printList(self.unify)
|
|
|
|
|
|
def copySettings(self, cp):
|
|
""" Copy the voicing from a 2nd voice to the current one. """
|
|
|
|
if not cp in gbl.tnames:
|
|
error("CopySettings does not know track '%s'" % cp)
|
|
|
|
cp=gbl.tnames[cp]
|
|
|
|
if cp.vtype != self.vtype:
|
|
error("Tracks must be of same type for copy ... "
|
|
"%s and %s aren't" % (self.name, cp.name))
|
|
|
|
self.volume = cp.volume[:]
|
|
self.rVolume = copy.deepcopy(cp.rVolume)
|
|
self.accent = cp.accent[:]
|
|
self.rSkip = cp.rSkip[:]
|
|
self.rTime = copy.deepcopy(cp.rTime)
|
|
self.strum = cp.strum[:]
|
|
self.octave = cp.octave[:]
|
|
self.harmony = cp.harmony[:]
|
|
self.harmonyOnly = cp.harmonyOnly[:]
|
|
self.harmonyVolume = cp.harmonyVolume[:]
|
|
self.direction = cp.direction[:]
|
|
self.scaleType = cp.scaleType[:]
|
|
self.voice = cp.voice[:]
|
|
self.invert = cp.invert[:]
|
|
self.artic = cp.artic[:]
|
|
self.compress = cp.compress[:]
|
|
|
|
self.riff = cp.riff[:]
|
|
|
|
if self.vtype == 'DRUM':
|
|
self.toneList = cp.toneList[:]
|
|
|
|
|
|
if gbl.debug:
|
|
print "Settings from %s copied to %s" % (cp.name, self.name)
|
|
|
|
|
|
|
|
##################################################
|
|
## Save/restore grooves
|
|
|
|
def saveGroove(self, gname):
|
|
""" Define a groove.
|
|
|
|
Called by the 'DefGroove Name'. This is called for
|
|
each track.
|
|
|
|
If 'gname' is already defined it is overwritten.
|
|
|
|
Note that tracks may have their own function for local/specific variables.
|
|
They call here first to create storage, then do their own.
|
|
"""
|
|
|
|
self.grooves[gname] = {
|
|
'ACCENT': self.accent[:],
|
|
'ARTIC': self.artic[:],
|
|
'COMPRESS': self.compress[:],
|
|
'DIR': self.direction[:],
|
|
'DUPROOT': self.dupRoot[:],
|
|
'HARMONY': self.harmony[:],
|
|
'HARMONYO': self.harmonyOnly[:],
|
|
'HARMONYV': self.harmonyVolume[:],
|
|
'INVERT': self.invert[:],
|
|
'LIMIT': self.chordLimit,
|
|
'RANGE': self.chordRange[:],
|
|
'OCTAVE': self.octave[:],
|
|
'RSKIP': self.rSkip[:],
|
|
'RTIME': copy.deepcopy(self.rTime),
|
|
'RVOLUME': copy.deepcopy(self.rVolume),
|
|
'SCALE': self.scaleType[:],
|
|
'SEQ': self.sequence[:],
|
|
'SEQRND': self.seqRnd,
|
|
'SEQRNDWT': self.seqRndWeight[:],
|
|
'STRUM': self.strum[:],
|
|
'VOICE': self.voice[:],
|
|
'VOLUME': self.volume[:],
|
|
'UNIFY': self.unify[:],
|
|
'MIDISEQ': self.midiSeq[:],
|
|
'MIDIVOICE': self.midiVoice[:],
|
|
'MIDICLEAR': self.midiClear[:],
|
|
'SPAN': (self.spanStart, self.spanEnd),
|
|
'MALLET': (self.mallet, self.malletDecay),
|
|
}
|
|
|
|
|
|
def restoreGroove(self, gname):
|
|
""" Restore a defined groove.
|
|
|
|
Some tracks will override to restore their own variables. They
|
|
then call back to this to finish the job.
|
|
"""
|
|
|
|
self.doMidiClear()
|
|
|
|
g = self.grooves[gname]
|
|
|
|
self.sequence = g['SEQ']
|
|
self.volume = g['VOLUME']
|
|
self.accent = g['ACCENT']
|
|
self.rTime = g['RTIME']
|
|
self.rVolume = g['RVOLUME']
|
|
self.rSkip = g['RSKIP']
|
|
self.strum = g['STRUM']
|
|
self.octave = g['OCTAVE']
|
|
self.voice = g['VOICE']
|
|
self.harmonyOnly= g['HARMONYO']
|
|
self.harmony = g['HARMONY']
|
|
self.harmonyVolume = g['HARMONYV']
|
|
self.direction = g['DIR']
|
|
self.scaleType = g['SCALE']
|
|
self.invert = g['INVERT']
|
|
self.artic = g['ARTIC']
|
|
self.seqRnd = g['SEQRND' ]
|
|
self.seqRndWeight = g['SEQRNDWT']
|
|
self.compress = g['COMPRESS']
|
|
self.chordRange = g['RANGE']
|
|
self.dupRoot = g['DUPROOT']
|
|
self.chordLimit = g['LIMIT']
|
|
self.unify = g['UNIFY']
|
|
self.midiClear = g['MIDICLEAR']
|
|
self.midiSeq = g['MIDISEQ']
|
|
self.midiVoice = g['MIDIVOICE']
|
|
self.spanStart, self.spanEnd = g['SPAN']
|
|
self.mallet, self.malletDecay = g['MALLET']
|
|
|
|
|
|
""" It's quite possible that the track was created after
|
|
the groove was saved. This means that the data restored
|
|
was just the default stuff inserted when the track
|
|
was created ... which is fine, but the sequence size
|
|
isn't necs. right. We can probably test any list, and octave[]
|
|
is as good as any.
|
|
"""
|
|
|
|
if len(self.octave) != gbl.seqSize:
|
|
self.setSeqSize()
|
|
|
|
####################################
|
|
## Sequence functions
|
|
|
|
def setSequence(self, ln):
|
|
""" Set the sequence for a track.
|
|
|
|
The ln passed from the parser should be a list of existing
|
|
patterns, plus the special 'patterns' Z, z, -, and *. Remember
|
|
that the parser has already converted {} patterns to a special
|
|
pattern line _1.
|
|
|
|
First we expand ln to the proper length. lnExpand() also
|
|
duplicates '/' to the previous pattern.
|
|
|
|
Then we step though ln:
|
|
|
|
- convert 'z', 'Z' and '-' to empty patterns.
|
|
|
|
- duplicate the existing pattern for '*'
|
|
|
|
- copy the defined pattern for everything else.
|
|
There's a bit of Python reference trickery here.
|
|
Eg, if we have the line:
|
|
|
|
Bass Sequence B1 B2
|
|
|
|
the sequence is set with pointers to the existing
|
|
patterns defined for B1 and B2. Now, if we later change
|
|
the definitions for B1 or B2, the stored pointer DOEN'T
|
|
change. So, changing pattern definitions has NO EFFECT.
|
|
|
|
"""
|
|
|
|
|
|
ln = lnExpand(ln, '%s Sequence' % self.name)
|
|
tmp = [None] * len(ln)
|
|
|
|
for i, n in enumerate(ln):
|
|
n=n.upper()
|
|
|
|
if n in ('Z', '-'):
|
|
tmp[i] = None
|
|
|
|
elif n == '*':
|
|
tmp[i] = self.sequence[i]
|
|
|
|
else:
|
|
p= (self.vtype, n)
|
|
if not p in pats:
|
|
error("Track %s does not have pattern '%s'" % p )
|
|
tmp[i] = pats[p]
|
|
|
|
self.sequence = seqBump(tmp)
|
|
|
|
if gbl.seqshow:
|
|
print "%s sequence set:" % self.name,
|
|
for a in ln:
|
|
if a in "Zz-":
|
|
print "-",
|
|
else:
|
|
print a,
|
|
print
|
|
|
|
|
|
def clearSequence(self):
|
|
""" Clear sequence for track.
|
|
|
|
This is also called from __init__() to set the initial defaults for each track.
|
|
|
|
"""
|
|
|
|
if self.vtype != 'SOLO' or not self.inited:
|
|
self.artic = [90]
|
|
self.sequence = [None]
|
|
self.seqRnd = 0
|
|
self.seqRndWeight = [1]
|
|
self.scaleType = ['AUTO']
|
|
self.rVolume = [[0,0]]
|
|
self.rSkip = [0]
|
|
self.rTime = [[0,0]]
|
|
self.octave = [4 * 12]
|
|
self.voice = [0]
|
|
self.chordRange = [1]
|
|
self.harmony = [None]
|
|
self.harmonyOnly = [None]
|
|
self.harmonyVolume = [.8]
|
|
self.strum = [None]
|
|
self.volume = [MMA.volume.vols['M'] ]
|
|
self.compress = [0]
|
|
self.dupRoot = [0]
|
|
self.chordLimit = 0
|
|
self.invert = [0]
|
|
self.lastChord = []
|
|
self.accent = [ [] ]
|
|
self.unify = [0]
|
|
self.midiClear = []
|
|
self.midiSeq = []
|
|
self.midiVoice = []
|
|
self.spanStart = 0
|
|
self.spanEnd = 127
|
|
self.mallet = 0
|
|
self.malletDecay = 0
|
|
self.futureVols = []
|
|
self.direction = ['BOTH']
|
|
|
|
# for midinote (see midinote.py)
|
|
self.transpose = 0 # transpose off=0, on=1
|
|
self.useticks = 1 # offsets beats=0, ticks=1 defaults to ticks
|
|
self.tickdur = 1 # duration notes=0, ticks=1 defaults to ticks
|
|
self.articulate = 0 # honor articulate, defaults to Off
|
|
self.tadjust = 0 # time adjustment factor
|
|
self.vadjust = 1 # volume adjustment factor (percentage)
|
|
self.oadjust = 0 # octave (12 pitches) adjustment
|
|
|
|
if self.riff:
|
|
if len(self.riff) > 1:
|
|
warning("%s sequence clear deleting %s riffs" % (self.name, len(self.riff)))
|
|
else:
|
|
warning("%s sequence clear deleting unused riff" % self.name )
|
|
|
|
self.riff = []
|
|
|
|
self.setSeqSize()
|
|
|
|
|
|
############################
|
|
### Pattern functions
|
|
############################
|
|
|
|
|
|
def definePattern(self, name, ln):
|
|
""" Define a Pattern.
|
|
|
|
All patterns are stored in pats{}. The keys for this
|
|
are tuples -- (track type, pattern name).
|
|
|
|
"""
|
|
|
|
name = name.upper()
|
|
slot = (self.vtype,name)
|
|
|
|
# This is just for the debug code
|
|
|
|
if name.startswith('_'):
|
|
redef = "dynamic define"
|
|
elif slot in pats:
|
|
redef = name + ' redefined'
|
|
else:
|
|
redef = name + ' created'
|
|
|
|
ln = ln.rstrip('; ') # Delete optional trailing ';' & WS
|
|
pats[slot] = self.defPatRiff(ln)
|
|
|
|
if gbl.pshow:
|
|
print "%s pattern %s:" % (self.name.title(), redef )
|
|
self.printPattern(pats[slot])
|
|
|
|
|
|
def dupRiff(self, ln):
|
|
""" Duplicate an existing set of riffs from one solo track to another."""
|
|
|
|
if not self.riff:
|
|
error("%s DupRiff: No data to copy." % self.name)
|
|
|
|
for t in ln:
|
|
t=t.upper()
|
|
|
|
if not t in gbl.tnames:
|
|
error("%s DupRiff: Destination track %s does not exist." \
|
|
% (self.name, t))
|
|
|
|
tr = gbl.tnames[t]
|
|
|
|
if self.vtype != tr.vtype:
|
|
error("%s DupRiff: Can't copy to %s, incompatible types (%s != %s)." \
|
|
% (self.name,t, self.vtype, tr.vtype))
|
|
|
|
if tr.riff:
|
|
error("%s DupRiff: Destination track %s has pending data." \
|
|
% (self.name, tr.name))
|
|
|
|
tr.riff = copy.deepcopy(self.riff)
|
|
|
|
if gbl.debug:
|
|
print "%s DupRiff copied to %s." % (self.name, tr.name)
|
|
|
|
|
|
def setRiff(self, ln):
|
|
""" Define and set a Riff. """
|
|
|
|
solo = self.vtype in ("MELODY", "SOLO")
|
|
|
|
if solo:
|
|
self.riff.append(ln)
|
|
else:
|
|
ln = ln.rstrip('; ')
|
|
if len(ln) == 1 and (ln[0] in ('Z','z','-')):
|
|
self.riff.append([])
|
|
else:
|
|
self.riff.append(self.defPatRiff(ln))
|
|
|
|
if gbl.pshow:
|
|
print "%s Riff:" % self.name,
|
|
if solo:
|
|
print self.riff[-1]
|
|
else:
|
|
self.printPattern(self.riff[-1])
|
|
|
|
|
|
def defPatRiff(self, ln):
|
|
""" Worker function to define pattern. Shared by definePattern()
|
|
and setRiff().
|
|
"""
|
|
|
|
def mulPatRiff(oldpat, fact):
|
|
""" Multiply a pattern. """
|
|
|
|
fact = stoi(fact, "The multiplier arg must be an integer not '%s'" % fact)
|
|
|
|
if fact<1 or fact >100:
|
|
error("The multiplier arg must be in the range 2 to 100")
|
|
|
|
|
|
""" Make N copies of pattern, adjusted so that the new copy has
|
|
all note lengths and start times adjusted.
|
|
eg: [[1, 2, 66], [3, 2, 88]] * 2
|
|
becomes [[1,4,66], [2,4,88], [3,4,66], [4,4,88]].
|
|
"""
|
|
|
|
new = []
|
|
add = 0
|
|
step = (gbl.BperQ * gbl.QperBar)/fact
|
|
|
|
for n in range(fact):
|
|
orig = copy.deepcopy(oldpat)
|
|
for z in orig:
|
|
z.offset = (z.offset / fact) + add
|
|
z.duration /= fact
|
|
if z.duration < 1:
|
|
z.duration = 1
|
|
|
|
new.append(z)
|
|
add += step
|
|
|
|
return tuple( new )
|
|
|
|
|
|
def shiftPatRiff(oldpat, fact):
|
|
|
|
fact = stof(fact, "The shift arg must be a value, not '%s'" % fact)
|
|
|
|
# Adjust all the beat offsets
|
|
|
|
new = copy.deepcopy(oldpat)
|
|
max = gbl.BperQ * (gbl.QperBar)
|
|
for n in new:
|
|
n.offset += fact * gbl.BperQ
|
|
if n.offset < 0 or n.offset > max:
|
|
error("Pattern shift with factor %f has resulted in an "
|
|
"illegal offset" % fact )
|
|
|
|
return tuple( new )
|
|
|
|
def patsort(c1, c2):
|
|
""" Sort a pattern tuple. """
|
|
|
|
if c1.offset < c2.offset: return -1
|
|
if c1.offset == c2.offset: return 0
|
|
else: return 1
|
|
|
|
|
|
### Start of main function...
|
|
|
|
# Convert the string to list...
|
|
# "1 2 3; 4 5 6" ---> [ [1,2,3], [4,5,6] ]
|
|
|
|
p = []
|
|
ln = ln.upper().split(';')
|
|
for l in ln:
|
|
p.append(l.split())
|
|
|
|
plist=[]
|
|
|
|
|
|
for ev in p:
|
|
more=[]
|
|
for i,e in enumerate(ev):
|
|
if e.upper() in ('SHIFT', '*'):
|
|
if i == 0:
|
|
error("Pattern definition can't start with SHIFT or *")
|
|
more = ev[i:]
|
|
ev=ev[:i]
|
|
break
|
|
|
|
if len(ev) == 1:
|
|
nm = (self.vtype, ev[0])
|
|
|
|
if nm in pats:
|
|
if nm[0].startswith('_'):
|
|
error("You can't use a pattern name beginning with an underscore")
|
|
pt = pats[nm]
|
|
|
|
else:
|
|
error("%s is not an existing %s pattern" % (nm[1], nm[0].title()) )
|
|
|
|
else:
|
|
pt = [self.getPgroup(ev)]
|
|
|
|
while more:
|
|
cmd = more.pop(0)
|
|
if cmd not in ('SHIFT', '*'):
|
|
error("Expecting SHIFT or *, not '%s'" % cmd)
|
|
|
|
if not more:
|
|
error("Expecting factor after %s" % cmd)
|
|
if cmd == 'SHIFT':
|
|
pt = shiftPatRiff(pt, more.pop(0))
|
|
elif cmd == '*':
|
|
pt = mulPatRiff(pt, more.pop(0))
|
|
|
|
plist.extend(pt)
|
|
|
|
|
|
plist.sort(patsort)
|
|
|
|
if MMA.swing.mode:
|
|
plist = MMA.swing.pattern(plist, self.vtype)
|
|
|
|
return plist
|
|
|
|
|
|
def printPattern(self, pat):
|
|
""" Print a pattern. Used by debugging code."""
|
|
|
|
print self.formatPattern(pat)
|
|
|
|
def formatPattern(self, pat):
|
|
pp=[]
|
|
if not pat:
|
|
return ' z '
|
|
|
|
for p in pat:
|
|
s=[]
|
|
s.append("%.2f %.0f" % (1+(p.offset/float(gbl.BperQ)),
|
|
p.duration))
|
|
|
|
if self.vtype == 'CHORD':
|
|
for a in p.vol:
|
|
s.append( "%.0f" % a)
|
|
|
|
elif self.vtype == 'BASS':
|
|
f=str(p.noteoffset+1)
|
|
|
|
if p.accidental == 1:
|
|
f+="#"
|
|
elif p.accidental == -1:
|
|
f+="b"
|
|
|
|
if p.addoctave > 0:
|
|
f+="+" * (p.addoctave/12)
|
|
elif p.addoctave < 0:
|
|
f+="-" * (p.addoctave/-12)
|
|
|
|
s.append( "%s %.0f" % (f, p.vol ) )
|
|
|
|
elif self.vtype in ('ARPEGGIO', 'SCALE', 'DRUM', 'WALK'):
|
|
s.append( "%.0f " % p.vol )
|
|
|
|
pp.append(' '.join(s))
|
|
|
|
return "; ".join(pp)
|
|
|
|
|
|
def insertVoice(self):
|
|
""" Called from bar() and setForceOut(). Adds voice stuff to track."""
|
|
|
|
sc = gbl.seqCount
|
|
|
|
""" 1st pass for MIDIVOICE. There's a separate slot for
|
|
each bar in the sequence, plus the data can be sent
|
|
before or after 'voice' commands. This first loop
|
|
sends MIDIVOICE data with an offset of 0. Note, we
|
|
don't set the value for 'self.smidiVoice' until we
|
|
do this again, later. All this is needed since some
|
|
MIDIVOICE commands NEED to be sent BEFORE voice selection,
|
|
and others AFTER.
|
|
"""
|
|
|
|
if self.midiVoice:
|
|
v = self.midiVoice[sc]
|
|
if v and v != self.smidiVoice:
|
|
for i in v:
|
|
if not i[0]:
|
|
gbl.mtrks[self.channel].addCtl(gbl.tickOffset, i[1])
|
|
|
|
# Set the voice in the midi track if not previously done.
|
|
|
|
v=self.voice[sc]
|
|
if v != self.ssvoice:
|
|
|
|
gbl.mtrks[self.channel].addProgChange( gbl.tickOffset, v, self.ssvoice)
|
|
self.ssvoice = v
|
|
|
|
# Mark ssvoice also in shared tracks
|
|
|
|
for a in gbl.midiAssigns[self.channel]:
|
|
try:
|
|
gbl.tnames[a].ssvoice = v
|
|
except KeyError:
|
|
pass
|
|
|
|
if gbl.debug:
|
|
print "%s Voice '%s' inserted at %s" % \
|
|
(self.name, MMA.midiC.valueToInst(v), gbl.tickOffset )
|
|
|
|
""" Our 2nd stab at MIDIVOICE. This time any sequences
|
|
with offsets >0 are sent. AND the smidiVoice and midiSent
|
|
variables are set.
|
|
"""
|
|
|
|
if self.midiVoice:
|
|
v = self.midiVoice[sc]
|
|
if v and v != self.smidiVoice:
|
|
for i in v:
|
|
if i[0]:
|
|
gbl.mtrks[self.channel].addCtl(gbl.tickOffset, i[1])
|
|
self.smidiVoice = v
|
|
self.midiSent = 1 # used by MIDICLEAR
|
|
|
|
|
|
#########################
|
|
## Music processing
|
|
#########################
|
|
|
|
|
|
def bar(self, ctable):
|
|
""" Process a bar of music for this track. """
|
|
|
|
# Future vol == de(cresc). Done if track is on or off!
|
|
|
|
if self.futureVols:
|
|
self.volume = seqBump([self.futureVols.pop(0)])
|
|
if self.futureVols:
|
|
self.nextVolume = self.futureVols[0]
|
|
else:
|
|
self.nextVolume = None
|
|
|
|
# If track is off don't do anything else.
|
|
|
|
if self.disable:
|
|
if self.riff:
|
|
self.riff.pop(0)
|
|
return
|
|
|
|
|
|
""" Decide which seq to use. This is either the current
|
|
seqCount, or if SeqRnd has been set for the track
|
|
it is a random pattern in the sequence.
|
|
|
|
The class variable self.seq is set to the sequence to use.
|
|
"""
|
|
|
|
if self.seqRnd:
|
|
self.seq = MMA.seqrnd.getrndseq(self.seqRndWeight)
|
|
else:
|
|
self.seq = gbl.seqCount
|
|
|
|
sc = self.seq
|
|
|
|
""" Get pattern for this sequence. Either a Riff or a Pattern. """
|
|
|
|
if self.riff:
|
|
pattern = self.riff.pop(0)
|
|
else:
|
|
pattern = self.sequence[sc]
|
|
if not pattern:
|
|
return
|
|
|
|
""" MIDI Channel assignment. If no channel is assigned try
|
|
to find an unused number and assign that.
|
|
"""
|
|
|
|
if not self.channel:
|
|
self.setChannel()
|
|
|
|
# We are ready to create musical data. 1st do pending midi commands.
|
|
|
|
self.clearPending()
|
|
|
|
self.insertVoice()
|
|
|
|
# Do MIDISeq for this voice
|
|
|
|
if self.midiSeq:
|
|
l = self.midiSeq[sc]
|
|
if l:
|
|
for i in l:
|
|
gbl.mtrks[self.channel].addCtl( getOffset(i[0]), i[1] )
|
|
self.midiSent = 1
|
|
|
|
self.trackBar(pattern, ctable)
|
|
|
|
|
|
|
|
def clearPending(self):
|
|
|
|
while self.midiPending:
|
|
c, off, v = self.midiPending.pop(0)
|
|
|
|
if c == 'TNAME':
|
|
gbl.mtrks[self.channel].addTrkName(off, v)
|
|
if gbl.debug:
|
|
print "%s Track name inserted at offset %s" % \
|
|
(self.name, off)
|
|
|
|
elif c == 'GLIS':
|
|
gbl.mtrks[self.channel].addGlis(off, v)
|
|
if gbl.debug:
|
|
print "%s Glis at offset %s set to %s" % \
|
|
(self.name, off, ord(chr(v)))
|
|
|
|
elif c == 'PAN':
|
|
gbl.mtrks[self.channel].addPan(off, v)
|
|
if gbl.debug:
|
|
print "%s Pan at offset %s set to %s" % \
|
|
(self.name, off, v)
|
|
|
|
elif c == 'CVOLUME':
|
|
gbl.mtrks[self.channel].addChannelVol(off, v)
|
|
if gbl.debug:
|
|
print "%s ChannelVolume at offset %s set to %s" % \
|
|
(self.name, off, v)
|
|
|
|
elif c == 'MIDITEXT':
|
|
gbl.mtrks[self.channel].addText(off, v)
|
|
if gbl.debug:
|
|
print "%s MidiText inserted at %s." % (self.name, off)
|
|
|
|
elif c == 'MIDICUE':
|
|
gbl.mtrks[self.channel].addCuePoint(off, v)
|
|
if gbl.debug:
|
|
print "%s MidiCue inserted at %s." % (self.name, off)
|
|
|
|
else:
|
|
error("Unknown midi command pending. Call Bob")
|
|
|
|
|
|
|
|
def getChordInPos( self, offset, ctabs):
|
|
""" Compare an offset to a list of ctables and return
|
|
the table entry active for the given beat.
|
|
|
|
The chord start/end offsets generated by parseChordLine() will be in
|
|
the range 0... (BperQ*QperBar). So, negative offset in patterns need
|
|
to be reset to 0; and a hit at the end of the bar could be missed if we
|
|
don't assume that anything out-of-range is in the last chord. Sort of
|
|
ugly, but it's quick and it works.
|
|
|
|
Returns a CTable structure.
|
|
"""
|
|
|
|
for c in ctabs:
|
|
if offset < c.chEnd:
|
|
break
|
|
|
|
return c
|
|
|
|
|
|
|
|
|
|
|
|
def adjustVolume(self, v, beat):
|
|
""" Adjust a note volume based on the track and global volume
|
|
setting.
|
|
"""
|
|
|
|
if not v:
|
|
return 0
|
|
|
|
sc = self.seq
|
|
|
|
if self.rSkip[sc] and random.random() < self.rSkip[sc]:
|
|
return 0
|
|
|
|
a1 = self.volume[sc]
|
|
if not a1:
|
|
return 0
|
|
|
|
if self.nextVolume: # inter-bar cresc adjust
|
|
bt=beat
|
|
if bt<1: # might have negative offsets, cres code ignores
|
|
bt=0
|
|
a1 += (self.nextVolume - a1) * bt / (gbl.BperQ * gbl.QperBar)
|
|
|
|
a1 *= MMA.volume.vTRatio
|
|
|
|
a2 = MMA.volume.volume
|
|
|
|
if not a2:
|
|
return 0
|
|
|
|
if MMA.volume.nextVolume: # inter-bar cresc adjust
|
|
bt=beat
|
|
if bt<1: # might have negative offsets, cres code ignores
|
|
bt=0
|
|
a2 += (MMA.volume.nextVolume - a2) * bt / (gbl.BperQ * gbl.QperBar)
|
|
|
|
a2 *= MMA.volume.vMRatio
|
|
|
|
v *= ( a1 + a2 )
|
|
|
|
for b,a in self.accent[sc]:
|
|
if b==beat:
|
|
v += (v * a)
|
|
|
|
# take .rVolume % of current volume, add/sub result to current
|
|
|
|
if self.rVolume[sc]:
|
|
a1 = int(v * self.rVolume[sc][0])
|
|
a2 = int(v * self.rVolume[sc][1])
|
|
if a1 or a2:
|
|
v += random.randrange(a1, a2)
|
|
|
|
if v > 127:
|
|
v = 127
|
|
elif v < 1:
|
|
v = 1
|
|
|
|
return int(v)
|
|
|
|
|
|
def adjustNote(self, n):
|
|
""" Adjust a note for a given octave/transposition.
|
|
Ensure that the note is in range.
|
|
"""
|
|
|
|
n += self.octave[self.seq] + gbl.transpose
|
|
|
|
while n < 0 or n < self.spanStart:
|
|
n += 12
|
|
while n > 127 or n > self.spanEnd:
|
|
n -= 12
|
|
|
|
return n
|
|
|
|
|
|
def setBarOffset(self, v):
|
|
""" Convert a string into a valid bar offset in midi ticks. """
|
|
|
|
m=v.find('-')
|
|
p=v.find('+')
|
|
|
|
if m>-1 and p>-1:
|
|
if m>p:
|
|
sp = p
|
|
sign = 1
|
|
else:
|
|
sp = m
|
|
sign = -1
|
|
|
|
elif m >- 1:
|
|
sp = m
|
|
sign = -1
|
|
|
|
elif p >- 1:
|
|
sp = p
|
|
sign = 1
|
|
|
|
else:
|
|
sp = None
|
|
|
|
if sp:
|
|
note = v[sp+1:]
|
|
v = v[:sp]
|
|
else:
|
|
note = None
|
|
|
|
v=stof(v, "Value for %s bar offset must be integer/float" % self.name)
|
|
v = (v-1) * gbl.BperQ
|
|
|
|
if note:
|
|
v += MMA.notelen.getNoteLen(note) * sign
|
|
|
|
if v < 0:
|
|
if v<-gbl.BperQ:
|
|
error("Defining %s Pattern, bar offset must be 0 or greater" %
|
|
self.name)
|
|
else:
|
|
warning("Offset in '%s' is '%s ticks' before bar start!" % (self.name, -v))
|
|
|
|
if v >= gbl.QperBar * gbl.BperQ:
|
|
error("Defining %s Pattern, bar offset must be less than %s" %
|
|
(self.name, gbl.QperBar + 1))
|
|
|
|
|
|
return int(v)
|
|
|
|
|
|
def getDur(self, d):
|
|
""" Return the adjusted duration for a note.
|
|
|
|
The adjustment makes notes longer or shorter. Valid
|
|
adjustments are 1 to 200.
|
|
"""
|
|
|
|
d = (d * self.artic[self.seq]) / 100
|
|
if not d:
|
|
d = 1 # force a value if we end with 0.
|
|
|
|
return d
|
|
|
|
|
|
def sendNote( self, offset, duration, note, velocity):
|
|
""" Send a note to the MIDI machine. This is called from all
|
|
track classes and handles niceties like mallet-repeat.
|
|
"""
|
|
|
|
if not velocity:
|
|
return
|
|
|
|
sc = self.seq
|
|
|
|
rptr = self.mallet
|
|
if rptr and duration > rptr:
|
|
ll = self.getDur(rptr)
|
|
offs = 0
|
|
vel = velocity
|
|
count =0
|
|
|
|
for q in range(duration/rptr):
|
|
gbl.mtrks[self.channel].addPairToTrack(
|
|
offset + offs,
|
|
self.rTime[sc][0], self.rTime[sc][1],
|
|
ll,
|
|
note,
|
|
vel,
|
|
None )
|
|
|
|
offs += rptr
|
|
if self.malletDecay:
|
|
vel = int( vel + (vel * self.malletDecay) )
|
|
if vel < 1:
|
|
vel = 1
|
|
if vel > 255:
|
|
vel=255
|
|
count+=1
|
|
|
|
else:
|
|
gbl.mtrks[self.channel].addPairToTrack(
|
|
offset,
|
|
self.rTime[sc][0], self.rTime[sc][1],
|
|
duration,
|
|
note,
|
|
velocity,
|
|
self.unify[sc] )
|
|
|