VocalEasel/mma/MMA/patSolo.py

636 lines
18 KiB
Python
Raw Normal View History

2006-11-10 08:07:56 +00:00
# patSolo.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
"""
2009-05-17 22:34:44 +00:00
import MMA.notelen
import MMA.translate
import MMA.harmony
import MMA.volume
import MMA.alloc
2006-11-10 08:07:56 +00:00
import gbl
from MMA.common import *
from MMA.pat import PC
2009-05-17 22:34:44 +00:00
2006-11-10 08:07:56 +00:00
class NoteList:
2007-04-29 06:47:40 +00:00
def __init__(self, length):
self.dur = length
self.velocity = []
self.nl = []
2006-11-10 08:07:56 +00:00
##############################
class Melody(PC):
2007-04-29 06:47:40 +00:00
""" The melody and solo tracks are identical, expect that
the solo tracks DO NOT get saved in grooves and are only
initialized once.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
vtype = 'MELODY'
drumType = None
endTilde = []
drumTone = 38
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def setDrumType(self):
""" Set this track to be a drum track. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if self.channel:
error("You cannot change a track to DRUM once it has been used")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.drumType = 1
self.setChannel('10')
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def definePattern(self, name, ln):
error("Melody/solo patterns cannot be defined")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def restart(self):
self.ssvoice = -1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def setTone(self, ln):
""" A solo track can have a tone, if it is DRUMTYPE."""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not self.drumType:
error("You must set a Solo track to DrumType before setting Tone")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if len(ln) > 1:
error("Only 1 value permitted for Drum Tone in Solo tracks")
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
self.drumTone = MMA.translate.dtable.get(ln[0])
2009-05-17 22:34:44 +00:00
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def getLine(self, pat, ctable):
""" Extract a melodyline for solo/melody tracks.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
This is only called from trackbar(), but it's nicer
to isolate it here.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
RETURNS: notes structure. This is a dictionary. Each key represents
an offset in MIDI ticks in the current bar. The data for
each entry is an array of notes, a duration and velocity:
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
notes[offset].dur - duration in ticks
notes[offset].velocity[] - velocity for notes
notes[offset].defaultVel - default velocity for this offset
notes[offset].nl[] - list of notes (if the only note value
is None this is a rest placeholder)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
"""
sc = self.seq
barEnd = gbl.BperQ*gbl.QperBar
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
acc=keySig.getAcc()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# list of notename to midivalues
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
midiNotes = {'c':0, 'd':2, 'e':4, 'f':5, 'g':7, 'a':9, 'b':11, 'r':None }
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" The initial string is in the format "1ab;4c;;4r;". The trailing
';' is important and needed. If we don't have this requirement
we can't tell if the last note is a repeat of the previous. For
example, if we have coded "2a;2a;" as "2a;;" and we didn't
have the 'must end with ;' rule, we end up with "2a;" and
then we make this into 2 notes...or do we? Easiest just to
insist that all bars end with a ";".
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not pat.endswith(';'):
error("All Solo strings must end with a ';'")
""" Take our list of note/value pairs and decode into
a list of midi values. Quite ugly.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.swingMode:
2009-05-17 22:34:44 +00:00
len8 = MMA.notelen.getNoteLen('8')
len81 = MMA.notelen.getNoteLen('81')
len82 = MMA.notelen.getNoteLen('82')
2007-04-29 06:47:40 +00:00
onBeats = [ x * gbl.BperQ for x in range(gbl.QperBar)]
offBeats = [ (x * gbl.BperQ + len8) for x in range(gbl.QperBar)]
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
length = MMA.notelen.getNoteLen('4') # default note length
2007-04-29 06:47:40 +00:00
lastc = '' # last parsed note
velocity = 90 # intial/default velocity for solo notes
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
notes={} # A dict of NoteList, keys == offset
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
if self.drumType:
isdrum = 1
lastc = str(self.drumTone)
else:
isdrum = None
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
pat = pat.replace(' ', '').split(';')[:-1]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# set initial offset into bar
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if pat[0].startswith("~"):
pat[0]=pat[0][1:]
if not self.endTilde or self.endTilde[1] != gbl.tickOffset:
error("Previous line did not end with '~'")
else:
offset = self.endTilde[0]
else:
offset = 0
lastOffset = None
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Strip off trailing ~
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if pat[-1].endswith("~"):
self.endTilde = [1, gbl.tickOffset + (gbl.BperQ * gbl.QperBar) ]
pat[-1]=pat[-1][:-1]
else:
self.endTilde = []
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Begin parse loop
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for a in pat:
if a == '<>':
continue
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if offset >= barEnd:
error("Attempt to start Solo note '%s' after end of bar" % a)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# strip out all '<volume>' setting and adjust velocity
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
a, vls = pextract(a, "<", ">")
if vls:
if len(vls) > 1:
error("Only 1 volume string is permitted per note-set")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
vls = vls[0].upper().strip()
if not vls in MMA.volume.vols:
error("%s string Expecting a valid volume, not '%s'" % \
(self.name, vls))
velocity *= MMA.volume.vols[vls]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" Split the chord chunk into a note length and notes. Each
part of this is optional and defaults to the previously
parsed value.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
i = 0
while i < len(a):
if not a[i] in '1234568.+':
break
else:
i+=1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if i:
2009-05-17 22:34:44 +00:00
l=MMA.notelen.getNoteLen(a[0:i])
2007-04-29 06:47:40 +00:00
c=a[i:]
else:
l=length
c=a
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not c:
c=lastc
if not c:
error("You must specify the first note in a solo line")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
length = l # set defaults for next loop
lastc = c
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" Convert the note part into a series of midi values
Notes can be a single note, or a series of notes. And
each note can be a letter a-g (or r), a '#,&,n' plus
a series of '+'s or '-'s. Drum solos must have each
note separated by ','s: "Snare1,Kick1,44".
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if isdrum:
c=c.split(',')
else:
c=list(c)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
while c:
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Parse off note name or 'r' for a rest
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
name = c.pop(0)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if name == 'r' and (offset in notes or c):
error("You cannot combine a rest with a note in a chord for solos")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not isdrum:
if not name in midiNotes:
error("%s encountered illegal note name '%s'"
% (self.name, name))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
v = midiNotes[ name ]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Parse out a "#', '&' or 'n' accidental.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if c and c[0]=='#':
c.pop(0)
acc[name] = 1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif c and c[0]=='&':
c.pop(0)
acc[name] = -1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
elif c and c[0]=='n':
c.pop(0)
acc[name] = 0
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if v != None:
v += acc[name]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Parse out +/- (or series) for octave
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if c and c[0] == '+':
while c and c[0] == '+':
c.pop(0)
v += 12
elif c and c[0] == '-':
while c and c[0] == '-':
c.pop(0)
v -= 12
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else:
if not name: # just for leading '.'s
continue
if name == 'r':
v = midiNotes[ name ]
elif name == '*':
v = self.drumTone
else:
2009-05-17 22:34:44 +00:00
v = int(MMA.translate.dtable.get(name))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" Swingmode -- This tests for successive 8ths on/off beat
If found, the first is converted to 'long' 8th, the 2nd to a 'short'
and the offset for the 2nd is adjusted to comp. for the 'long'.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.swingMode and l==len8 and \
offset in offBeats and \
lastOffset in onBeats and \
lastOffset in notes:
if notes[lastOffset].dur == len8:
offset = lastOffset + len81
notes[lastOffset].dur = len81
l=len82
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# create a new note[] entry for this offset
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not offset in notes:
notes[offset] = NoteList(l)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# add note event to note[] array
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
notes[offset].nl.append(v)
notes[offset].velocity.append(self.adjustVolume(velocity, offset))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
notes[offset].defaultVel = velocity # needed for addHarmony()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
lastOffset = offset
offset += l
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if offset <= barEnd:
if self.endTilde:
error("Tilde at end of bar has no effect")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else:
if self.endTilde:
self.endTilde[0]=offset-barEnd
else:
warning("%s, end of last note overlaps end of bar by %2.3f "
"beat(s)." % (self.name, (offset-barEnd)/float(gbl.BperQ)))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return notes
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def addHarmony(self, notes, ctable):
""" Add harmony to solo notes. """
2009-05-17 22:34:44 +00:00
2007-04-29 06:47:40 +00:00
sc=self.seq
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
harmony = self.harmony[sc]
harmOnly = self.harmonyOnly[sc]
for offset in notes:
nn = notes[offset]
if len(nn.nl) == 1 and nn.nl[0] != None:
tb = self.getChordInPos(offset, ctable)
if tb.chordZ:
continue
2006-11-10 08:07:56 +00:00
2009-05-17 22:34:44 +00:00
h = MMA.harmony.harmonize(harmony, nn.nl[0], tb.chord.bnoteList)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" If harmonyonly set then drop note, substitute harmony,
else append harmony notes to chord.
"""
if harmOnly:
nn.nl = h
nn.velocity = []
off=0
else:
nn.nl.extend(h)
off=1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Create velocites for harmony note(s)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for i in range(off,len(nn.nl)):
nn.velocity.append(self.adjustVolume(nn.defaultVel *
self.harmonyVolume[sc], offset))
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return notes
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def trackBar(self, pat, ctable):
""" Do the solo/melody line. Called from self.bar() """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
notes = self.getLine(pat, ctable)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if self.harmony[self.seq] and not self.drumType:
self.addHarmony(notes, ctable)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
sc=self.seq
unify = self.unify[sc]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
rptr = self.mallet
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for offset in sorted(notes.keys()):
nn=notes[offset]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for n,v in zip(nn.nl, nn.velocity):
if n == None: # skip rests
continue
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not self.drumType: # octave, transpose
n = self.adjustNote(n)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.sendNote( offset, self.getDur(nn.dur), n, v)
2006-11-10 08:07:56 +00:00
class Solo(Melody):
2007-04-29 06:47:40 +00:00
""" Pattern class for a solo track. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
vtype = 'SOLO'
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Grooves are not saved/restored for solo tracks.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def restoreGroove(self, gname):
self.setSeqSize()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def saveGroove(self, gname):
pass
2006-11-10 08:07:56 +00:00
##################################
""" Keysignature. This is only used in the solo/melody tracks so it
probably makes sense to have the parse routine here as well. To
2007-04-29 06:47:40 +00:00
contain everything in one location we make a single instance class
of the whole mess.
2006-11-10 08:07:56 +00:00
"""
class KeySig:
2007-04-29 06:47:40 +00:00
def __init__(self):
self.kSig = 0
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
majKy = { "C" : 0, "G" : 1, "D" : 2,
"A" : 3, "E" : 4, "B" : 5,
"F#": 6, "C#": 7, "F" : -1,
"Bb": -2, "Eb": -3, "Ab": -4,
"Db": -5, "Gb": -6, "Cb": -7 }
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
minKy = { "A" : 0, "E" : 1, "B" : 2,
"F#": 3, "C#": 4, "G#": 5,
"D#": 6, "A#": 7, "D" : -1,
"G" : -2, "C" : -3, "F" : -4,
"Bb": -5, "Eb": -6, "Ab": -7 }
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def set(self,ln):
""" Set the keysignature. Used by solo tracks."""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
mi = 0
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if len(ln) < 1 or len(ln) > 2:
error("KeySig only takes 1 or 2 arguments")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if len(ln) == 2:
l=ln[1][0:3].upper()
if l == 'MIN':
mi=1
elif l == 'MAJ':
mi=0
else:
error("KeySig 2nd arg must be 'Major' or 'Minor', not '%s'" % ln[1])
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
l=ln[0]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
t=l[0].upper() + l[1:]
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if mi and t in self.minKy:
self.kSig = self.minKy[t]
elif not mi and t in self.majKy:
self.kSig = self.majKy[t]
elif l[0] in "ABCDEFG":
error("There is no key signature name: '%s'" % l)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else:
c=l[0]
f=l[1].upper()
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not f in ("B", "&", "#"):
error("2nd char in KeySig must be 'b' or '#', not '%s'" % f)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not c in "01234567":
error("1st char in KeySig must be digit 0..7, not '%s'" % c)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
self.kSig = int(c)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if f in ('B', '&'):
self.kSig = -self.kSig
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not c in "01234567":
error("1st char in KeySig must be digit 0..7, not '%s'" % c)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
# Set the midi meta track with the keysig. This doen't do anything
# in the playback, but other programs may use it.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
n = self.kSig
if n < 0:
n = 256 + n
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
gbl.mtrks[0].addKeySig(gbl.tickOffset, n, mi)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.debug:
n = self.kSig
if n >= 0:
f = "Sharps"
else:
f = "Flats"
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
print "KeySig set to %s %s" % (abs(n), f)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
def getAcc(self):
""" The solo parser needs to know which notes are accidentals.
This is simple with a keysig table. There is an entry for each note,
either -1,0,1 corresponding to flat,natural,sharp. We populate
the table for each bar from the keysig value. As we process
the bar data we update the table. There is one flaw here---in
real music an accidental for a note in a give octave does not
effect the following same-named notes in different octaves.
In this routine IT DOES.
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
NOTE: This is recreated for each bar of music for each solo/melody track.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
acc = {'a':0, 'b':0, 'c':0, 'd':0, 'e':0, 'f':0, 'g':0 }
ks=self.kSig
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if ks < 0:
for a in range( abs(ks) ):
acc[ ['b','e','a','d','g','c','f'][a] ] = -1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
else:
for a in range(ks):
acc[ ['f','c','g','d','a','e','b'][a] ] = 1
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return acc
2006-11-10 08:07:56 +00:00
keySig=KeySig() # single instance
2007-04-29 06:47:40 +00:00
2006-11-10 08:07:56 +00:00
#######################
""" When solos are included in a chord/data line they are
assigned to the tracks listed in this list. Users can
change the tracks with the setAutoSolo command.
"""
autoSoloTracks = [ 'SOLO', 'SOLO-1', 'SOLO-2', 'SOLO-3' ]
def setAutoSolo(ln):
2007-04-29 06:47:40 +00:00
""" Set the order and names of tracks to use when assigning
automatic solos (specified on chord lines in {}s).
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
global autoSoloTracks
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if not len(ln):
error("You must specify at least one track for autosolos")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
autoSoloTracks = []
for n in ln:
n=n.upper()
MMA.alloc.trackAlloc(n, 1)
if gbl.tnames[n].vtype not in ('MELODY', 'SOLO'):
error("All autotracks must be Melody or Solo tracks, not %s" % gbl.tnames[n].vtype)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
autoSoloTracks.append(n)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.debug:
print "AutoSolo track names:",
for a in autoSoloTracks:
print a,
print
2006-11-10 08:07:56 +00:00
###############
2007-04-29 06:47:40 +00:00
2006-11-10 08:07:56 +00:00
def extractSolo(ln, rptcount):
2007-04-29 06:47:40 +00:00
""" Parser calls this to extract solo strings. """
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
a = ln.count('{')
b = ln.count('}')
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if a != b:
error("Mismatched {}s for solo found in chord line")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if a:
if rptcount > 1:
error("Bars with both repeat count and solos are not permitted")
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
ln, solo = pextract(ln, '{', '}')
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if len(solo) > len(autoSoloTracks):
error("Too many melody/solo riffs in chord line. %s used, "
"only %s defined" % (len(solo), len(autoSoloTracks)) )
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
firstSolo = solo[0][:] # save for autoharmony tracks
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" We have the solo information. Now we loop though each "solo" and:
1. Ensure or Create a MMA track for the solo
2. Push the solo data into a Riff for the given track.
"""
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
for s, trk in zip(solo, autoSoloTracks):
MMA.alloc.trackAlloc(trk, 1)
gbl.tnames[trk].setRiff( s.strip() )
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
""" After all the solo data is interpreted and sent to the
correct track, we check any leftover tracks. If any of these
tracks are empty of data AND are harmonyonly the note
data from the first track is interpeted again for that
track. Tricky: the max() is needed since harmonyonly can
have different setting for each bar...this way
the copy is done if ANY bar in the seq has harmonyonly set.
2006-11-10 08:07:56 +00:00
"""
2007-04-29 06:47:40 +00:00
for t in autoSoloTracks[1:]:
2009-05-17 22:34:44 +00:00
if t in gbl.tnames and gbl.tnames[t].riff == [] \
2007-04-29 06:47:40 +00:00
and max(gbl.tnames[t].harmonyOnly):
gbl.tnames[t].setRiff( firstSolo[:] )
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
if gbl.debug:
print "%s duplicated to %s for HarmonyOnly." % (trk, t)
2006-11-10 08:07:56 +00:00
2007-04-29 06:47:40 +00:00
return ln
2006-11-10 08:07:56 +00:00