def parse(inpath): """ Process a mma input file. """ global beginData, lastChord gbl.inpath = inpath curline = None while 1: curline = inpath.read() if curline is None: # eof, exit parser break l = macros.expand(curline) if not l: continue """ Handle BEGIN and END here. This is outside of the Repeat/End and variable expand loops so SHOULD be pretty bullet proof. Note that the beginData stuff is global to this module ... the Include/Use directives check to make sure we're not doing that inside a Begin/End. beginData[] is a list which we append to as more Begins are encountered. The placement here is pretty deliberate. Variable expand comes later so you can't macroize BEGIN ... I think this makes sense. The tests for 'begin', 'end' and the appending of the current begin[] stuff have to be here, in this order. """ action = l[0].upper() # 1st arg in line if action == "BEGIN": if not l[1:]: error("Use: Begin STUFF") beginPoints.append(len(beginData)) beginData.extend(l[1:]) continue if action == "END": if len(l) > 1: error("No arguments permitted for END") if not beginData: error("No 'BEGIN' for 'END'") beginData = beginData[: beginPoints.pop(-1)] continue if beginData: l = beginData + l action = l[0].upper() if gbl.showExpand and action != "REPEAT": print(l) # If the command is in the simple function table, jump & loop. if action in simpleFuncs: simpleFuncs[action](l[1:]) continue """ We have several possibilities ... 1. The command is a valid assigned track name, 2. The command is a valid track name, but needs to be dynamically allocated, 3. It's really a chord action """ if not action in gbl.tnames: # no track allocated? trackAlloc(action, 0) # Try to create. Always returns. if action in gbl.tnames: # BASS/DRUM/APEGGIO/CHORD name = action if len(l) < 2: error("Expecting argument after '%s'" % name) action = l[1].upper() # Got trackname and action if action in trackFuncs: # perfect, execute trackFuncs[action](name, l[2:]) continue elif action in simpleFuncs: # opps, not track func error("%s is not a track function. Use global form." % action) else: # opps, not any kind of func error("%s is not a %s track function." % (action, name)) ### Gotta be a chord data line! """ A data line can have an optional bar number at the start of the line. Makes debugging input easier. The next block strips leading integers off the line. Note that a line number on a line by itself it okay. """ if action.isdigit(): # isdigit() matches '1', '1234' but not '1a'! gbl.barLabel = l[0] l = l[1:] if not l: # ignore empty lines continue else: gbl.barLabel = "" """ A bar can have an optional repeat count. This must be at the end of bar in the form '* xx'. """ if len(l) > 1 and l[-2] == "*": rptcount = stoi(l[-1], "Expecting integer after '*'") l = l[:-2] else: rptcount = 1 """ Extract solo(s) from line ... this is anything in {}s. The solo data is pushed into RIFFs and discarded from the current line. """ l = " ".join(l) l = MMA.patSolo.extractSolo(l, rptcount) """ Set lyrics from [stuff] in the current line. NOTE: lyric.extract() inserts previously created data from LYRICS SET and inserts the chord names if that flag is active. """ l, lyrics = lyric.extract(l, rptcount) l = l.split() """ At this point we have only chord info. A number of sanity checks are made: 1. Make sure there is some chord data, 2. Ensure the correct number of chords. """ if not l: error("Expecting music (chord) data. Even lines with\n" " lyrics or solos still need a chord") """ We now have a chord line. It'll look something like: ['Cm', '/', 'z', 'F#@4.5'] or ['/' 'C@3' ] For each bar we create a list of CTables, one for each chord in the line. Each entry has the start/end (in beats), chordname, etc. """ ctable = parseChordLine(l) # parse the chord line # Create MIDI data for the bar for rpt in range(rptcount): # for each bar in the repeat count ( Cm * 3) """ Handle global (de)cresc by popping a new volume off stack. """ if MMA.volume.futureVol: MMA.volume.volume = MMA.volume.futureVol.pop(0) if MMA.volume.futureVol: MMA.volume.nextVolume = MMA.volume.futureVol[0] else: MMA.volume.nextVolume = None """ Set up for rnd seq. This may set the current seq point. If return is >=0 then we're doing track rnd. """ rsq, seqlist = MMA.seqrnd.setseq() """ Process each track. It is important that the track classes are written so that the ctable passed to them IS NOT MODIFIED. This applies especially to chords. If the track class changes the chord, then the function called MUST restore it before returning!!! """ for a in gbl.tnames.values(): if rsq >= 0: seqSave = gbl.seqCount if a.name in seqlist: # for seqrnd with tracklist gbl.seqCount = rsq a.bar(ctable) # process entire bar! if rsq >= 0: # for track rnd gbl.seqCount = seqSave # Adjust counters """ After processsing each bar we update a dictionary of bar pointers. This table is used when the MIDI data is written when -b or -B is set to limit output. """ if MMA.truncate.length: nextOffset = MMA.truncate.length MMA.truncate.countDown() else: nextOffset = gbl.barLen # barPtrs is used by the -B/b options to strip unwanted sections. gbl.barPtrs[gbl.barNum + 1] = [gbl.barLabel, gbl.tickOffset, gbl.tickOffset + nextOffset - 1] gbl.totTime += float(nextOffset / gbl.BperQ) / gbl.tempo gbl.tickOffset += nextOffset if gbl.printProcessed: if gbl.barLabel: gbl.barLabels.append(gbl.barLabel) else: gbl.barLabels.append("?") gbl.barNum += 1 gbl.seqCount = (gbl.seqCount + 1) % gbl.seqSize if gbl.barNum > gbl.maxBars: error("Capacity exceeded. Maxbar setting is %s. Use -m option" % gbl.maxBars) MMA.grooves.nextGroove() # using groove list? Advance. # Enabled with the -r command line option if gbl.showrun: if lyrics: # we print lyric as a list ly = lyrics # with the []s else: ly = "" # no lyric print("%3d: %s %s" % (gbl.barNum, " ".join(l), ly))
def trackSequence(name, ln): """ Define a sequence for a track. The format for a sequence: TrackName Seq1 [Seq2 ... ] Note, that SeqX can be a predefined seq or { seqdef } The {} is dynamically interpreted into a def. """ if not ln: error("Use: %s Sequence NAME [...]" % name) ln = ' '.join(ln) self = gbl.tnames[name] # this is the pattern class if self.vtype == "SOLO": warning("Sequences for SOLO tracks are not saved in Grooves.") """ Before we do extraction of {} stuff make sure we have matching {}s. Count the number of { and } and if they don't match read more lines and append. If we get to the EOF then we're screwed and we error out. Only trick is to make sure we do macro expansion! This code lets one have long sequence lines without bothering with '\' continuations. """ oLine = gbl.lineno # in case we error out, report start line while ln.count('{') != ln.count('}'): l = gbl.inpath.read() if l is None: # reached eof, error gbl.lineno = oLine error("%s Sequence {}s do not match" % name) l = ' '.join(macros.expand(l)) if l[-1] != '}' and l[-1] != ';': error("%s: Expecting multiple sequence lines to end in ';'" % name) ln += ' ' + l """ Extract out any {} definitions and assign them to new define variables (__1, __99, etc) and melt them back into the string. """ ids = 1 while 1: sp = ln.find("{") if sp < 0: break ln, s = pextract(ln, "{", "}", onlyone=True) if not s: error("Did not find matching '}' for '{'") pn = "_%s" % ids ids += 1 trk = name.split('-')[0] trackAlloc(trk, 1) """ We need to mung the plectrum classes. Problem is that we define all patterns in the base class (plectrum-banjo is created in PLECTRUM) which is fine, but the def depends on the number of strings in the instrument (set by the tuning option). So, we save the tuning for the base class, copy the real tuning, and restore it. NOTE: at this point the base and current tracks have been initialized. """ if trk == 'PLECTRUM' and name != trk: z = gbl.tnames[trk]._tuning[:] gbl.tnames[trk]._tuning = gbl.tnames[name]._tuning else: z = None gbl.tnames[trk].definePattern(pn, s[0]) # 'trk' is a base class! if z: gbl.tnames[trk]._tuning = z ln = ln[:sp] + ' ' + pn + ' ' + ln[sp:] ln = ln.split() """ We now have a sequence we can save for the track. All the {} defs have been converted to special defines (_1, _2, etc.). 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 DOESN'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 MMA.debug.seqshow: msg = ["%s sequence set:" % self.name] for a in ln: if a in "Zz-": msg.append("-") else: msg.append(a) dPrint(' '.join(msg))
def repeat(ln): """ Repeat/RepeatEnd/RepeatEnding. Read input until a RepeatEnd is found. The entire chunk is pushed back into the input stream the correct number of times. This accounts for endings and nested repeats. """ def repeatChunk(): q = [] qnum = [] nesting = 0 while 1: l = gbl.inpath.read() if not l: error("EOF encountered processing Repeat") act = l[0].upper() if act == "REPEAT": nesting += 1 elif act in ("REPEATEND", "ENDREPEAT") and nesting: nesting -= 1 elif act == "REPEATENDING" and nesting: pass elif act in ("REPEATEND", "ENDREPEAT", "REPEATENDING"): return (q, qnum, act, l[1:]) q.append(l) qnum.append(gbl.lineno) stack = [] stacknum = [] main = [] mainnum = [] ending = 0 if ln: error("REPEAT takes no arguments") main, mainnum, act, l = repeatChunk() while 1: if act in ("REPEATEND", "ENDREPEAT"): if l: l = macros.expand(l) if len(l) == 2 and l[0].upper() == "NOWARN": l = l[1:] warn = 0 else: warn = 1 if len(l) != 1: error("%s: Use [NoWarn] Count" % act) count = stoi(l[0], "%s takes an integer arg" % act) if count == 2 and warn: warning("%s count of 2 duplicates default. Did you mean 3 or more?" % act) elif count == 1 and warn: warning("%s count of 1 means NO REPEAT" % act) elif count == 0 and warn: warning("%s count of 0, Skipping entire repeated section" % act) elif count < 0: error("%s count must be 0 or greater" % act) elif count > 10 and warn: warning("%s is a large value for %s" % (count, act)) else: count = 2 if not ending: count += 1 for c in range(count - 1): stack.extend(main) stacknum.extend(mainnum) gbl.inpath.push(stack, stacknum) break elif act == "REPEATENDING": ending = 1 if l: l = macros.expand(l) if len(l) == 2 and l[0].upper() == "NOWARN": warn = 0 l = l[1:] else: warn = 1 if len(l) != 1: error("REPEATENDING: Use [NoWarn] Count") count = stoi(l[0], "RepeatEnding takes an integer arg") if count < 0: error("RepeatEnding count must be postive, not '%s'" % count) elif count == 0 and warn: warning("RepeatEnding count of 0, skipping section") elif count == 1 and warn: warning("RepeatEnding count of 1 duplicates default") elif count > 10 and warn: warning("%s is a large value for RepeatEnding" % count) else: count = 1 rpt, rptnum, act, l = repeatChunk() for c in range(count): stack.extend(main) stacknum.extend(mainnum) stack.extend(rpt) stacknum.extend(rptnum) else: error("Unexpected line in REPEAT")
def repeat(ln): """ Repeat/RepeatEnd/RepeatEnding. Read input until a RepeatEnd is found. The entire chunk is pushed back into the input stream the correct number of times. This accounts for endings and nested repeats. """ def repeatChunk(): q = [] qnum = [] nesting = 0 while 1: l = gbl.inpath.read() if not l: error("EOF encountered processing Repeat") act = l[0].upper() if act == 'REPEAT': nesting += 1 elif act in ('REPEATEND', 'ENDREPEAT') and nesting: nesting -= 1 elif act == 'REPEATENDING' and nesting: pass elif act in ('REPEATEND', 'ENDREPEAT', 'REPEATENDING'): return (q, qnum, act, l[1:]) q.append(l) qnum.append(gbl.lineno) stack = [] stacknum = [] main = [] mainnum = [] ending = 0 if ln: error("REPEAT takes no arguments") main, mainnum, act, l = repeatChunk() while 1: if act in ('REPEATEND', 'ENDREPEAT'): if l: l = macros.expand(l) if len(l) == 2: l = [x.upper() for x in l] if 'NOWARN' in l: l.remove('NOWARN') warn = 0 else: warn = 1 if len(l) != 1: error("%s: Use [NoWarn] Count" % act) count = stoi(l[0], "%s takes an integer arg" % act) if count == 2 and warn: warning( "%s count of 2 duplicates default. Did you mean 3 or more?" % act) elif count == 1 and warn: warning("%s count of 1 means NO REPEAT" % act) elif count == 0 and warn: warning("%s count of 0, Skipping entire repeated section" % act) elif count < 0: error("%s count must be 0 or greater" % act) elif count > 10 and warn: warning("%s is a large value for %s" % (count, act)) else: count = 2 if not ending: count += 1 for c in range(count - 1): stack.extend(main) stacknum.extend(mainnum) gbl.inpath.push(stack, stacknum) break elif act == 'REPEATENDING': ending = 1 if l: l = macros.expand(l) if len(l) == 2: l = [x.upper() for x in l] if 'NOWARN' in l: l.remove('NOWARN') warn = 0 else: warn = 1 if len(l) != 1: error("REPEATENDING: Use [NoWarn] Count") count = stoi(l[0], "RepeatEnding takes an integer arg") if count < 0: error("RepeatEnding count must be postive, not '%s'" % count) elif count == 0 and warn: warning("RepeatEnding count of 0, skipping section") elif count == 1 and warn: warning("RepeatEnding count of 1 duplicates default") elif count > 10 and warn: warning("%s is a large value for RepeatEnding" % count) else: count = 1 rpt, rptnum, act, l = repeatChunk() for c in range(count): stack.extend(main) stacknum.extend(mainnum) stack.extend(rpt) stacknum.extend(rptnum) else: error("Unexpected line in REPEAT")
def parse(inpath): """ Process a mma input file. """ global beginData, lastChord gbl.inpath = inpath curline = None while 1: MMA.after.check() curline = inpath.read() if curline is None: # eof, exit parser break l = macros.expand(curline) if not l: continue """ Handle BEGIN and END here. This is outside of the Repeat/End and variable expand loops so SHOULD be pretty bullet proof. Note that the beginData stuff is global to this module ... the Include/Use directives check to make sure we're not doing that inside a Begin/End. beginData[] is a list which we append to as more Begins are encountered. The placement here is pretty deliberate. Variable expand comes later so you can't macroize BEGIN ... I think this makes sense. The tests for 'begin', 'end' and the appending of the current begin[] stuff have to be here, in this order. """ action = l[0].upper() # 1st arg in line if action == 'BEGIN': if not l[1:]: error("Use: Begin STUFF") beginPoints.append(len(beginData)) beginData.extend(l[1:]) continue if action == 'END': if len(l) > 1: error("No arguments permitted for END") if not beginData: error("No 'BEGIN' for 'END'") beginData = beginData[:beginPoints.pop(-1)] continue if beginData: l = beginData + l action = l[0].upper() if MMA.debug.showExpand and action != 'REPEAT': dPrint(l) if action in simpleFuncs: simpleFuncs[action](l[1:]) continue """ We have several possibilities ... 1. The command is a valid assigned track name, 2. The command is a valid track name, but needs to be dynamically allocated, 3. It's really a chord action """ if not action in gbl.tnames: # no track allocated? trackAlloc(action, 0) # Try to create. Always returns. if action in gbl.tnames: # BASS/DRUM/APEGGIO/CHORD name = action if len(l) < 2: error("Expecting argument after '%s'" % name) action = l[1].upper() # Got trackname and action if action in trackFuncs: # perfect, execute trackFuncs[action](name, l[2:]) continue elif action in simpleFuncs: # opps, not track func error("%s is not a track function. Use global form." % action) else: # opps, not any kind of func error("%s is not a %s track function." % (action, name)) ### Gotta be a chord data line! """ A data line can have an optional bar number at the start of the line. Makes debugging input easier. The next block strips leading integers off the line. Note that a line number on a line by itself it okay. """ if action.isdigit(): # isdigit() matches '1', '1234' but not '1a'! gbl.barLabel = l[0].lstrip('0') l = l[1:] if not l: # ignore empty lines continue else: gbl.barLabel = '' ## A bar can have an optional repeat count. This must ## be at the end of bar in the form '* xx'. if len(l) > 1 and l[-2] == '*': rptcount = stoi(l[-1], "Expecting integer after '*'") l = l[:-2] else: rptcount = 1 # Dataplugins all start with '@'. Code is in the plugin code. # A data plugin modifies the existing data line and returns it. if l[0].startswith('@'): p = l[0].upper() if p not in dataFuncs: error("Unknown data plugin '%s' called." % p) l = dataFuncs[p](l[1:]) # Extract solo(s) from line ... this is anything in {}s. # The solo data is pushed into RIFFs and discarded from # the current line. l = ' '.join(l) l = MMA.patSolo.extractSolo(l, rptcount) # set lyrics from [stuff] in the current line. # NOTE: lyric.extract() inserts previously created # data from LYRICS SET and inserts the chord names # if that flag is active. l, lyrics = MMA.lyric.lyric.extract(l, rptcount) l = l.split() # At this point we have only chord info. A number # of sanity checks are made: # 1. Make sure there is some chord data, # 2. Ensure the correct number of chords. if not l: error("Expecting music (chord) data. Even lines with " "lyrics or solos still need a chord. If you " "don't want a chord use 'z'.") # We now have a chord line. It'll look something like: # # ['Cm', '/', 'z', 'F#@4.5'] or ['/' 'C@3' ] # # For each bar we create a list of CTables, one for each # chord in the line. Each entry has the start/end (in beats), chordname, etc. # ctable = parseChordLine(l) # parse the chord line # Create MIDI data for the bar for rpt in range( rptcount): # for each bar in the repeat count ( Cm * 3) """ Handle global (de)cresc by popping a new volume off stack. """ if MMA.volume.futureVol: MMA.volume.volume = MMA.volume.futureVol.pop(0) if MMA.volume.futureVol: MMA.volume.nextVolume = MMA.volume.futureVol[0] else: MMA.volume.nextVolume = None # Set up for rnd seq. This may set the current seq point. # If return is >=0 then we're doing track rnd. rsq, seqlist = MMA.seqrnd.setseq() """ Process each track. It is important that the track classes are written so that the ctable passed to them IS NOT MODIFIED. This applies especially to chords. If the track class changes the chord, then the function called MUST restore it before returning!!! """ for a in gbl.tnames.values(): if rsq >= 0: seqSave = gbl.seqCount if a.name in seqlist: # for seqrnd with tracklist gbl.seqCount = rsq a.bar(ctable) # process entire bar! if rsq >= 0: # for track rnd gbl.seqCount = seqSave # Adjust counters """ After processsing each bar we update a dictionary of bar pointers. This table is used when the MIDI data is written when -b or -B is set to limit output. """ if MMA.truncate.length: nextOffset = MMA.truncate.length MMA.truncate.countDown() else: nextOffset = gbl.barLen # barPtrs is used by the -B/b options to strip unwanted sections. gbl.barPtrs[gbl.barNum + 1] = [ gbl.barLabel, gbl.tickOffset, gbl.tickOffset + nextOffset - 1 ] gbl.totTime += float(nextOffset / gbl.BperQ) / gbl.tempo gbl.tickOffset += nextOffset if gbl.printProcessed: if gbl.barLabel: gbl.barLabels.append(gbl.barLabel) else: gbl.barLabels.append('?') gbl.barNum += 1 gbl.seqCount = (gbl.seqCount + 1) % gbl.seqSize if gbl.barNum > gbl.maxBars: error( "Capacity exceeded. Maxbar setting is %s. Use -m option" % gbl.maxBars) MMA.grooves.nextGroove() # using groove list? Advance. # Enabled with the -r command line option if MMA.debug.showrun: if lyrics: # we print lyric as a list ly = lyrics # with the []s else: ly = '' # no lyric dPrint("%3d: %s %s" % (gbl.barNum, ' '.join(l), ly)) # if repeat count is set with dupchord we push # the chord back and get lyric.extract to add the # chord to the midi file again. A real lyric is # just ignored ... 2 reasons: the lyric is mangled and # and it makes sense to only have it once! if rptcount > 1 and MMA.lyric.lyric.dupchords: _, lyrics = MMA.lyric.lyric.extract(' '.join(l), 0) # The barNum and other pointers have been incremented # and a bar of data has been processed. If we are repeating # due to a "*" we do a AGAIN test. Without a rpt this would # be done at the start of a data line. if rptcount > 1 and MMA.after.needed(): MMA.after.check(recurse=True)
def trackSequence(name, ln): """ Define a sequence for a track. The format for a sequence: TrackName Seq1 [Seq2 ... ] Note, that SeqX can be a predefined seq or { seqdef } The {} is dynamically interpreted into a def. """ if not ln: error("Use: %s Sequence NAME [...]" % name) ln = ' '.join(ln) self = gbl.tnames[name] # this is the pattern class if self.vtype == "SOLO": warning("Sequences for SOLO tracks are not saved in Grooves.") """ Before we do extraction of {} stuff make sure we have matching {}s. Count the number of { and } and if they don't match read more lines and append. If we get to the EOF then we're screwed and we error out. Only trick is to make sure we do macro expansion! This code lets one have long sequence lines without bothering with '\' continuations. """ oLine = gbl.lineno # in case we error out, report start line while ln.count('{') != ln.count('}'): l = gbl.inpath.read() if l is None: # reached eof, error gbl.lineno = oLine error("%s Sequence {}s do not match" % name) l = ' '.join(macros.expand(l)) if l[-1] != '}' and l[-1] != ';': error("%s: Expecting multiple sequence lines to end in ';'" % name) ln += ' ' + l """ Extract out any {} definitions and assign them to new define variables (__1, __99, etc) and melt them back into the string. """ ids = 1 while 1: sp = ln.find("{") if sp < 0: break ln, s = pextract(ln, "{", "}", onlyone=True) if not s: error("Did not find matching '}' for '{'") pn = "_%s" % ids ids += 1 trk = name.split('-')[0] trackAlloc(trk, 1) """ We need to mung the plectrum classes. Problem is that we define all patterns in the base class (plectrum-banjo is created in PLECTRUM) which is fine, but the def depends on the number of strings in the instrument (set by the tuning option). So, we save the tuning for the base class, copy the real tuning, and restore it. NOTE: at this point the base and current tracks have been initialized. """ if trk == 'PLECTRUM' and name != trk: z = gbl.tnames[trk]._tuning[:] gbl.tnames[trk]._tuning = gbl.tnames[name]._tuning else: z = None gbl.tnames[trk].definePattern(pn, s[0]) # 'trk' is a base class! if z: gbl.tnames[trk]._tuning = z ln = ln[:sp] + ' ' + pn + ' ' + ln[sp:] ln = ln.split() """ We now have a sequence we can save for the track. All the {} defs have been converted to special defines (_1, _2, etc.). 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 DOESN'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: msg = ["%s sequence set:" % self.name] for a in ln: if a in "Zz-": msg.append("-") else: msg.append(a) print(' '.join(msg))
def parse(inpath): """ Process a mma input file. """ global beginData, lastChord gbl.inpath = inpath curline = None while 1: curline = inpath.read() if curline == None: # eof, exit parser break l = macros.expand(curline) """ Handle BEGIN and END here. This is outside of the Repeat/End and variable expand loops so SHOULD be pretty bullet proof. Note that the beginData stuff is global to this module ... the Include/Use directives check to make sure we're not doing that inside a Begin/End. beginData[] is a list which we append to as more Begins are encountered. The placement here is pretty deliberate. Variable expand comes later so you can't macroize BEGIN ... I think this makes sense. The tests for 'begin', 'end' and the appending of the current begin[] stuff have to be here, in this order. """ action = l[0].upper() # 1st arg in line if action == 'BEGIN': if not l[1:]: error("Use: Begin STUFF") beginPoints.append(len(beginData)) beginData.extend(l[1:]) continue if action == 'END': if len(l) > 1: error("No arguments permitted for END") if not beginData: error("No 'BEGIN' for 'END'") beginData = beginData[:beginPoints.pop(-1)] continue if beginData: l = beginData + l action = l[0].upper() if gbl.showExpand and action != 'REPEAT': print l # If the command is in the simple function table, jump & loop. if action in simpleFuncs: simpleFuncs[action](l[1:]) continue """ We have several possibilities ... 1. The command is a valid assigned track name, 2. The command is a valid track name, but needs to be dynamically allocated, 3. It's really a chord action """ if not action in gbl.tnames: trackAlloc(action, 0) # ensure that track is allocated if action in gbl.tnames: # BASS/DRUM/APEGGIO/CHORD name = action if len(l) < 2: error("Expecting argument after '%s'" % name) action = l[1].upper() if action in trackFuncs: trackFuncs[action](name, l[2:]) else: error("Don't know '%s'" % curline) continue ### Gotta be a chord data line! """ A data line can have an optional bar number at the start of the line. Makes debugging input easier. The next block strips leading integers off the line. Note that a line number on a line by itself it okay. """ if action.isdigit(): # isdigit() matches '1', '1234' but not '1a'! barLabel = l[0] l = l[1:] if not l: # ignore empty lines continue else: barLabel = '' """ A bar can have an optional repeat count. This must be at the end of bar in the form '* xx'. """ if len(l) > 1 and l[-2] == '*': rptcount = stoi(l[-1], "Expecting integer after '*'") l = l[:-2] else: rptcount = 1 """ Extract solo(s) from line ... this is anything in {}s. The solo data is pushed into RIFFs and discarded from the current line. """ l = ' '.join(l) l = MMA.patSolo.extractSolo(l, rptcount) """ Set lyrics from [stuff] in the current line. NOTE: lyric.extract() inserts previously created data from LYRICS SET and inserts the chord names if that flag is active. """ l, lyrics = lyric.extract(l, rptcount) l = l.split() """ At this point we have only chord info. A number of sanity checks are made: 1. Make sure there is some chord data, 2. Ensure the correct number of chords. """ if not l: error("Expecting music (chord) data. Even lines with\n" " lyrics or solos still need a chord") """ We now have a chord line. It'll look something like: ['Cm', '/', 'z', 'F#@4.5'] or ['/' 'C@3' ] For each bar we create a list of CTables, one for each chord in the line. Each entry has the start/end (in beats), chordname, etc. """ ctable = parseChordLine(l) # parse the chord line # Create MIDI data for the bar for rpt in range( rptcount): # for each bar in the repeat count ( Cm * 3) """ Handle global (de)cresc by popping a new volume off stack. """ if MMA.volume.futureVol: MMA.volume.volume = MMA.volume.futureVol.pop(0) if MMA.volume.futureVol: MMA.volume.nextVolume = MMA.volume.futureVol[0] else: MMA.volume.nextVolume = None """ Set up for rnd seq. This may set the current seq point. If return is >=0 then we're doing track rnd. """ rsq, seqlist = MMA.seqrnd.setseq() """ Process each track. It is important that the track classes are written so that the ctable passed to them IS NOT MODIFIED. This applies especially to chords. If the track class changes the chord, then the function called MUST restore it before returning!!! """ for a in gbl.tnames.values(): if rsq >= 0: seqSave = gbl.seqCount if a.name in seqlist: # for seqrnd with tracklist gbl.seqCount = rsq a.bar(ctable) ## process entire bar! if rsq >= 0: # for track rnd gbl.seqCount = seqSave # Adjust counters """ After processsing each bar we update a dictionary of bar pointers. This table is used when the MIDI data is written when -b or -B is set to limit output. """ if MMA.truncate.length: nextOffset = MMA.truncate.length MMA.truncate.countDown() else: nextOffset = gbl.QperBar * gbl.BperQ gbl.barPtrs[gbl.barNum + 1] = [ barLabel, gbl.tickOffset, gbl.tickOffset + nextOffset - 1 ] gbl.totTime += float(nextOffset / gbl.BperQ) / gbl.tempo gbl.tickOffset += nextOffset gbl.barNum += 1 gbl.seqCount = (gbl.seqCount + 1) % gbl.seqSize if gbl.barNum > gbl.maxBars: error( "Capacity exceeded. Maxbar setting is %s. Use -m option" % gbl.maxBars) MMA.grooves.nextGroove() # using groove list? Advance. # Enabled with the -r command line option if gbl.showrun: print "%3d:" % gbl.barNum, for c in l: print c, if lyrics: print lyrics, print