Example #1
0
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))
Example #2
0
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))
Example #3
0
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")
Example #4
0
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")
Example #5
0
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))
Example #7
0
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