1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
# parseCL.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>
This module parses a chordline and sets the timing offsets for
each "tab".
"""
from . import gbl
from MMA.common import *
import MMA.truncate
# This table is passed to the track classes. It has
# an instance for each chord in the current bar.
class CTable:
name = None # Chord name (used by plectrum track)
chord = None # A pointer to the chordNotes structures
lastchord = None # chord from previous ctable. Used by trigger()
chStart = None # where in the bar the chord starts (in ticks, 0..)
chEnd = None # where it ends (in ticks)
chordZ = None # set if chord is tacet
arpeggioZ = None # set if arpeggio is tacet
walkZ = None # set if walking bass is tacet
drumZ = None # set if drums are tacet
bassZ = None # set if bass is tacet
scaleZ = None # set if scale track is tacet
ariaZ = None # set if aria track is tacet
plectrumZ = None # set if plectrum is tacet
lastChord = None # tracks last chord for "/ /" data lines.
chordTabs = [] # initialized by MMA.main call to setTime()
def setChordTabs(l):
""" Set the tab positions for chord parsing.
This table is set up on each TIME change. Need to
do it even if the TIME is NOT changed since the table
positions might. Cheap to do, so not worth worrying.
"""
global chordTabs
chordTabs = tuple(( int((x-1) * gbl.BperQ) for x in l ))
def parseChordLine(l):
""" Parse a line of chord symbols and determine start/end points. """
global lastChord
ctable = [] # an entry for each chord in the bar
quarter = gbl.BperQ # ticks in a quarter note (== 1 beat)
if MMA.truncate.length:
endTime = MMA.truncate.length
else:
endTime = (quarter * gbl.QperBar) # number of ticks in bar
p = 0 # our beat counter --- points to tab 0, 1, etc in chordTabs
for ll in l:
if '@' in ll: # we have something like "Cm@3.2"
ch, beat = ll.split('@', 1)
beat = stof(beat, "Expecting a value after the @ in '%s'" % ll)
if beat < 1:
error("Beat after @ must be 1 or greater, not '%s'." % beat)
if beat >= gbl.QperBar + 1:
error("Beat after @ must be less than %s, not '%s'." % (gbl.QperBar + 1, beat))
# tick offset for this chord
beat = int((beat - 1) * quarter)
# need to set p to next spot in chordTabs[].
for p, i in enumerate(chordTabs):
if i > beat:
break
else:
ch = ll
if p > len(chordTabs)-1:
error("Too many chords specified in line. Max is %s. "
"For more chords use @ notation or change TIME TABS."
% (len(chordTabs)))
beat = chordTabs[p]
p += 1 # for next beat
if ch in '-/': # handle continuation chords
if not ctable:
if lastChord:
ch = lastChord
else:
error("No previous chord for '/' or '-' at line start")
else: # '/' other than at start just increment the beat counter
continue
if ctable:
if ctable[-1].name == ch: # skip duplicate chords
continue
if ctable[-1].chStart >= beat:
error("Chord positions out of order")
else: # first entry
if beat != 0:
error("The first chord must be at beat 1, not %s." % ((beat / quarter) + 1))
ctab = CTable()
ctab.name = ch
ctab.chStart = beat
if ctable:
ctab.lastchord = ctable[-1].name
else:
ctab.lastchord = lastChord
# If the chord we just extracted has a 'z' in it then we do the
# following ugly stuff to figure out which tracks to mute. 'ch'
# will be a chord name or 'z' when this is done.
if 'z' in ch:
if ch.count('z')>1:
error("Only one 'z' is permitted in the mute shortcut. '%s' is "
"illegal." % ch)
if lastChord and 'z' in lastChord: # strip off z stuff from prior chord
lastChord = lastChord.split('z')[0]
c, r = ch.split('z', 1) # get chord and track list
if not c: # no chord specified
c = 'z' # dummy chord name to keep chordnotes() happy
if r == '!': # 'z!' is fine, mute all
r = 'DCAWBSRP'
elif not r: # 'z' is fine, mute all tracks except Drum
r = 'CBAWSRP'
else:
if lastChord:
c = lastChord
if 'z' in c:
c = c.split('z')[0] # we only want the chord
else:
error("Unable to find a chord for '%s'. Try specifing the "
"chord name." % ch)
if not c:
error("To use this shortcut to mute individual tracks "
"the chord must be included. "
"Use CHORDz[DCAWBSRP] not '%s'" % ch)
else: # illegal CHORD + 'z' or 'z!'
if r == '!':
error("'%s' is illegal. 'z!' mutes all tracks "
"so you can't include the chord." % ch)
elif not r:
error("'%s' is illegal. This notation is a shortcut, You must specify tracks "
"if you use a chord." % ch)
ch = c # this will be 'z' or the chord part
# Got a chord (real name or 'z') and tracks to mute
for v in r:
if v == 'C':
ctab.chordZ = 1
elif v == 'B':
ctab.bassZ = 1
elif v == 'A':
ctab.arpeggioZ = 1
elif v == 'W':
ctab.walkZ = 1
elif v == 'D':
ctab.drumZ = 1
elif v == 'S':
ctab.scaleZ = 1
elif v == 'R':
ctab.ariaZ = 1
elif v == 'P':
ctab.plectrumZ = 1
else:
error("Unknown track '%s' for muting in '%s'" % (v, ch))
ctab.chord = MMA.chords.ChordNotes(ch) # Derive chord notes (or mute)
ctable.append(ctab)
# Finished each chord in data line, test
# to see that all chords are in range.
if ctable[-1].chStart >= endTime:
error("Maximum offset for chord '%s' must be less than %s, not '%s'." %
(ctable[-1].name, endTime / quarter + 1, ctable[-1].chStart / quarter + 1))
# Fix up some pointers.
for i, v in enumerate(ctable[:-1]): # set end range for each chord
ctable[i].chEnd = ctable[i + 1].chStart
ctable[-1].chEnd = endTime # set end of range for last chord
lastChord = ctable[-1].name # remember chord at end of this bar for next
return ctable
|