Example #1
0
def trackDefPattern(name, ln):
    """ Define a pattern for a track.

    Use the type-name for all defines.... check the track
    names and if it has a '-' in it, we use only the
    part BEFORE the '-'. So DRUM-Snare becomes DRUM.
    """

    ln = ln[:]

    name = name.split("-")[0]

    trackAlloc(name, 1)

    if ln:
        pattern = ln.pop(0).upper()
    else:
        error("Define is expecting a pattern name")

    if pattern in ("z", "Z", "-"):
        error("Pattern name '%s' is reserved" % pattern)

    if pattern.startswith("_"):
        error("Names with a leading underscore are reserved")

    if not ln:
        error("No pattern list given for '%s %s'" % (name, pattern))

    ln = " ".join(ln)
    gbl.tnames[name].definePattern(pattern, ln)
Example #2
0
def trackDefPattern(name, ln):
    """ Define a pattern for a track.

    Use the type-name for all defines.... check the track
    names and if it has a '-' in it, we use only the
    part BEFORE the '-'. So DRUM-Snare becomes DRUM.
    """

    ln = ln[:]

    name = name.split('-')[0]

    trackAlloc(name, 1)

    if ln:
        pattern = ln.pop(0).upper()
    else:
        error("Define is expecting a pattern name")

    if pattern in ('z', 'Z', '-'):
        error("Pattern name '%s' is reserved" % pattern)

    if pattern.startswith('_'):
        error("Names with a leading underscore are reserved")

    if not ln:
        error("No pattern list given for '%s %s'" % (name, pattern))

    ln = ' '.join(ln)
    gbl.tnames[name].definePattern(pattern, ln)
Example #3
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 #4
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 #5
0
File: midiIn.py Project: rcook/mma
def midiinc(ln):
    """ Include a MIDI file into MMA generated files. """

    filename = ''
    doLyric = 0
    doText = 0
    channels = []
    transpose = None
    stripSilence = -1
    report = 0
    istart = 0          # istart/end are in ticks
    iend = 0xffffff     # but are set in options in Beats
    verbose = 0
    octAdjust = 0
    velAdjust = 100
    ignorePC = 1
    stretch = None

    notopt, ln = opt2pair(ln)

    if notopt:
        error("MidiInc: Expecting cmd=opt pairs, not '%s'." % ' '.join(notopt))

    for cmd, opt in ln:
        cmd = cmd.upper()

        if cmd == 'FILE':
            filename = MMA.file.fixfname(opt)

        elif cmd == 'VOLUME':
            velAdjust = stoi(opt)

        elif cmd == 'OCTAVE':
            octAdjust = stoi(opt)
            if octAdjust < -4 or octAdjust > 4:
                error("MidiInc: 'Octave' adjustment must be -4 to 4, not %s" % opt)
            octAdjust *= 12

        elif cmd == 'TRANSPOSE':
            transpose = stoi(opt)
            if transpose < -24 or transpose > 24:
                error("MidiInc: 'Transpose' must be -24 to 24, not %s" % opt)

        elif cmd == 'START':
            if opt[-1].upper() == 'M':   # measures
                istart = int(stof(opt[:-1]) * gbl.barLen)
            elif opt[-1].upper() == 'T':  # ticks
                istart = int(stof(opt[:-1]))
            else:  # must be digits, stof() catches errors
                istart = int((stof(opt)-1) * gbl.BperQ)

            if istart < 0:
                error("MidiInc: 'Start' must be > 0.")

        elif cmd == 'END':
            if opt[-1].upper() == 'M':
                iend = int((stof(opt[:-1])-1) * gbl.barLen)
            elif opt[-1].upper() == 'T':
                iend = int(stof(opt[:-1]))
            else:
                iend = int((stof(opt)-1) * gbl.BperQ)

            if iend < 0:
                error("MidiInc: 'End' must be > 0.")

        elif cmd == 'TEXT':
            opt = opt.upper()
            if opt in ("ON", '1'):
                doText = 1
            elif opt in ("OFF", '0'):
                doText = 0
            else:
                error("MidiInc: 'Text' expecting 'ON' or 'OFF'")

        elif cmd == 'LYRIC':
            opt = opt.upper()
            if opt in ("ON", '1'):
                doLyric = 1
            elif opt in ("OFF", '0'):
                doLyric = 0
            else:
                error("MidiInc: 'Lyric' expecting 'ON' or 'OFF'")

        elif cmd == "REPORT":
            opt = opt.upper()
            if opt in ("ON", '1'):
                report = 1
            elif opt in ("OFF", '0'):
                report = 0
            else:
                error("MidiInc: 'Report' expecting 'ON' or 'OFF'")

        elif cmd == "VERBOSE":
            opt = opt.upper()
            if opt in ("ON", '1'):
                verbose = 1
            elif opt in ("OFF", '0'):
                verbose = 0
            else:
                error("MidiInc: 'Verbose' expecting 'ON' or 'OFF'")

        elif cmd == "STRIPSILENCE":
            opt = opt.upper()
            if opt in ("OFF", '0'):
                stripSilence = 0
            elif opt == "ON":  # this is the default
                stripSilence = -1
            else:
                stripSilence = stoi(opt, "MIdiInc StripSilence= expecting "
                                    "'value', 'On' or 'Off', not %s" % opt)

        elif cmd == "IGNOREPC":
            opt = opt.upper()
            if opt in ("TRUE", "ON", "1"):   # default
                ignorePC = 1
            elif opt in ("FALSE", "OFF", "0"):  # use program change in imported
                ignorePC = 0
            else:
                error("MIdiInc: 'IncludePC' expecting 'True' or 'False', not %s" % opt)

        elif cmd == "STRETCH":
            v = stof(opt)
            if v < 1 or v > 500:
                error("MidiInc: 'Stretch' range of 1 to 500, not %s." % opt)
            stretch = v/100.

        # If none of the above matched a CMD we assume that it is
        # a trackname. Keep this as the last test!

        else:
            trackAlloc(cmd, 0)
            if not cmd in gbl.tnames:
                error("MidiInc: %s is not a valid MMA track" % cmd)

            opt = opt.split(',')
            riffmode = 0
            printriff = 0
            ch = None
            for o in opt:
                o = o.upper()
                if o == 'RIFF':
                    riffmode = 1
                elif o == 'SEQUENCE':
                    riffmode = 2
                elif o == 'PRINT':
                    printriff = 1
                    if not riffmode:
                        riffmode = 1
                else:
                    if ch is not None:
                        error("MidiInc: Only one channel assignment per track.")
                    ch = stoi(o)

            if ch < 1 or ch > 16:
                error("MidiInc: MIDI channel for import must be 1..16, not %s" % ch)

            channels.append((cmd, ch-1, riffmode, printriff))

    # If transpose was NOT set, use the global transpose value
    # Note special riff value as well. Need to double adjust since
    # the riff import will do its own adjustment.
    # this needs to be done BEFORE reading the midi file
    if transpose is None:
        transpose = gbl.transpose
        riffTranspose = -gbl.transpose
    else:
        riffTranspose = 0
    octAdjust += transpose    # this takes care of octave and transpose

    mf = MidiData()
    mf.octaveAdjust = octAdjust
    mf.velocityAdjust = velAdjust
    mf.ignorePC = ignorePC

    try:
        mf.readFile(filename)
    except RuntimeError as e:
        error("MidiInc: %s" % e)

    if mf.beatDivision != gbl.BperQ:
        warning("MIDI file '%s' tick/beat of %s differs from MMA's "
                "%s. Will try to compensate" % 
                (filename, mf.beatDivision, gbl.BperQ))
        mf.adjustBeats( gbl.BperQ / float(mf.beatDivision))

    if report or verbose:  # try to be helpful
        print("MIDI File %s successfully read." % filename)
        print("Total Text events: %s" % len(mf.textEvents))
        print("Total Lyric events: %s" % len(mf.lyricEvents))
        print('\n')

        for ch in sorted(mf.events.keys()):
            if not mf.events[ch]:
                continue

            if verbose and not report:   # in verbose mode only list info for tracks we're using
                doit = 0
                for z in channels:
                    if z[1] == ch:
                        doit = 1
                        break
                
                if not doit:
                    continue

            fnote = fevent = 0xffffff
            ncount = 0
            for ev in mf.events[ch]:
                delta = ev[0]
                if delta < fevent:
                    fevent = delta
                if ev[1] >> 4 == 0x9:
                    if delta < fnote:
                        fnote = delta
                    if ev[3]:
                        ncount += 1
            msg = ["Channel %2s: First event %-8s" % (ch+1, fevent)]
            if ncount:
                msg.append("First Note %-8s Total Notes %-4s" % (fnote, ncount))
            print(' '.join(msg))

        if report:
            print("\nNo data generated!")
            sys.exit(0)
        
    if not channels:
        if doLyric or doText:
            warning("MidiInc: no import channels specified, "
                    "only text or lyrics imported")
        else:
            error("MidiInc: A channel to import and a destination "
                  "track must be specified")

    if (istart >= iend) or (istart < 0) or (iend < 0):
        error("MidiInc: Range invalid, start=%s, end=%s" % (istart, iend))

    if gbl.debug:
        print("MidiInc: file=%s, Volume=%s, Octave=%s, Transpose=%s, Lyric=%s, " 
            "Text=%s, Range=%s..%s StripSilence=%s Verbose=%s" 
            % (filename, velAdjust, octAdjust, transpose, doLyric, doText,
               istart, iend, stripSilence, verbose))
        msg = []
        for t, ch, riffmode, riffprint in channels:
            o = ''
            if riffmode == 1:
                o = ',riff'
            elif riffmode == 2:
                o = ',sequence'
            elif printriff:
                o += ',print'
            msg.append("MidiInc: Channel %s-->%s%s" % (ch+1, t, o))
        print(' '.join(msg))

    if stretch:
        if verbose:
            print("Applying stretch to all events. Deltas will be multiplied by %s" % stretch)

        for tr in mf.events:
            for e in mf.events[tr]:
                e[0] = int(e[0] * stretch)   # e[0] is the offset

        for e in mf.textEvents:
            e[0] = int(e[0] * stretch)

        for e in mf.lyricEvents:
            e[0] = int(e[0] * stretch)

    # Midi file parsed, add selected events to mma data

    if stripSilence == 0:
        if verbose:
            print("Firstnote offset was %s. Being reset to start of file by StripSilence=Off." 
                % mf.firstNote)
        mf.firstNote = 0

    if verbose:
        print("First note offset: %s" % mf.firstNote)

    if doText:
        inst = 0
        disc = 0
        if verbose:
            print("Scanning %s textevents." % len(mf.textEvents))
        for tm, tx in mf.textEvents:
            delta = tm-mf.firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addText(gbl.tickOffset + delta, tx)
                inst += 1
            else:
                disc += 1

        if gbl.debug:
            print("MidiInc text events: %s inserted, %s out of range." % (inst, disc))

    if doLyric:
        inst = 0
        disc = 0
        if verbose:
            print("Scanning %s LyricEvents." % len(mf.lyricEvents))
        for tm, tx in mf.lyricEvents:
            delta = tm-mf.firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addLyric(gbl.tickOffset + delta, tx)
                inst += 1
            else:
                disc += 1
        if gbl.debug:
            print("MidiInc lyric events: %s inserted, %s out of range." % (inst, disc))

    for n, c, riffmode, printriff in channels:
        if not len(mf.events[c]):
            warning("No data to assign from imported channel %s to track %s" % (c+1, n))

    inst = 0
    disc = 0

    for tr, ch, riffmode, printriff in channels:
        onNotes = []
        if gbl.tnames[tr].disable:   # skip if disabled track

            if verbose:
                print("Skipping import of channel %s since track %s is disabled." 
                    % (ch, tr))
            continue

        t = gbl.tnames[tr]
        if not t.channel:
            t.setChannel()

        if riffmode:
            riff = []
            if t.vtype not in ('MELODY', 'SOLO'):
                error("MidiInc: Riff only works on Melody/Solo tracks, not '%s'." % t.name)

        t.clearPending()
        if t.voice[0] != t.ssvoice:
            gbl.mtrks[t.channel].addProgChange(gbl.tickOffset, t.voice[0], t.ssvoice)

        channel = t.channel
        track = gbl.mtrks[channel]

        if verbose:
                print("Parsing imported file. Channel=%s Track=%s MIDI Channel=%s" 
                    % (ch, tr, channel))
                if len(mf.events[ch]):
                    print(" Total events: %s; Event range: %s %s; Start/End Range: %s %s" 
                    % (len(mf.events[ch]), mf.events[ch][0][0], 
                       mf.events[ch][-1][0], istart, iend))
                else:
                    print("No events in Channel %s" % ch)

        
        # If we're processing midi voice changes (ignorePC=off) and there
        # are events BEFORE the first note, w eneed to insert
        # them before the notes. We put them all at the current midi offset.
        if ignorePC==0:
            for ev in mf.events[ch]:
                if ev[0] > mf.firstNote:
                    break
                if ev[1] >> 4 == 0xc:
                    track.addToTrack(gbl.tickOffset,
                                     packBytes(ev[1] | channel-1, *ev[2:]))
                    inst += 1
                    disc -= 1

        for ev in mf.events[ch]:
            delta = ev[0]-mf.firstNote
            
            if delta >= istart and delta <= iend:
                if riffmode:
                    offset = delta-istart
                    x = ev[1] >> 4
                    if x != 0x09 and x != 0x08:  # skip non note events
                        continue
                    pitch = ev[2]
                    velocity = ev[3]
                    if x == 0x8:
                        velocity = 0
                    riff.append([offset, pitch, velocity])

                else:
                    offset = gbl.tickOffset + (delta-istart)
                    # add note on/off, key pressure, etc.
                    track.addToTrack(offset, packBytes(ev[1] | channel-1, *ev[2:]))

                    # Track on/off events to avoid stuck midi notes.
                    x = ev[1] >> 4
                    if x == 0x09 and ev[3] and ev[2] not in onNotes:
                        onNotes.append(ev[2])  # append this pitch to onNotes
                    if x == 0x09 and not ev[3] or x == 0x08 and ev[2] in onNotes:
                        onNotes.remove(ev[2])  # remove this as being ON
                        
                inst += 1
            else:
                disc += 1

        if onNotes:
            for x in onNotes:
                track.addToTrack(offset, packBytes(0x90 | channel-1, [x,0]))
            warning("MidiINC: Stuck MIDI note(s) '%s' turned off in %s." % 
                    (', '.join([str(x) for x in onNotes]), tr))

        if riffmode:
            evlist = createRiff(riff, tr, riffTranspose)

            if riffmode == 2:
                txt = []
            for a in sorted(evlist):
                if printriff and riffmode == 1:
                    print("%s Riff %s" % (tr, evlist[a]))
                elif riffmode == 2:   # sequence mode, create sequence line and push into input
                    txt.append("{%s}" % evlist[a])
                else:   # riffmode==1, printriff=0 - just add to the riff stack
                    gbl.tnames[tr].setRiff(evlist[a])

            if riffmode == 2 and txt:
                if printriff:
                    print("%s Sequence %s" % (tr, ' '.join(txt)))
                else:
                    MMA.sequence.trackSequence(tr, txt)

    if gbl.debug:
            print("MidiInc events: %s inserted, %s out of range." % (inst, disc))
Example #6
0
def midiinc(ln):
    """ Include a MIDI file into MMA generated files. """

    global midifile, offset

    filename = ''
    doLyric = 0
    doText = 0
    volAdjust = 100
    octAdjust = 0
    transpose = None
    channels = []

    # These are the start/end points for the included file. They are in
    # beats, but are adjusted after the file is opened to ticks.

    istart = 0
    iend = 0xffffff

    for a in ln:
        cmd, opt = a.split('=')

        cmd = cmd.upper()

        if cmd == 'FILE':
            filename = os.path.expanduser(opt)

        elif cmd == 'VOLUME':
            volAdjust = stoi(opt)

        elif cmd == 'OCTAVE':
            octAdjust = stoi(opt)
            if octAdjust < -4 or octAdjust > 4:
                error("Octave adjustment must be -4 to 4, not %s" % opt)
            octAdjust *= 12

        elif cmd == 'TRANSPOSE':
            transpose = stoi(opt)
            if transpose < -24 or transpose > 24:
                error("Tranpose must be -24 to 24, not %s" % opt)

        elif cmd == 'START':
            istart = stof(opt)

        elif cmd == 'END':
            iend = stof(opt)

        elif cmd == 'TEXT':
            opt = opt.upper()
            if opt in ("ON", 1):
                doText = 1
            elif opt in ("OFF", 0):
                doText = 0
            else:
                error("MidiInc Text= expecting 'ON' or 'OFF'")

        elif cmd == 'LYRIC' and opt != '0':
            opt = opt.upper()
            if opt in ("ON", 1):
                doLyric = 1
            elif opt in ("OFF", 0):
                doLyric = 0
            else:
                error("MidiInc Lyric= expecting 'ON' or 'OFF'")

        # make sure this is last option ... it has to be a TRACKNAME=CHANNEL-NUMBER

        else:
            trackAlloc(cmd, 0)
            if not cmd in gbl.tnames:
                error("%s is not a valid MMA track" % cmd)

            ch = stoi(opt)
            if ch < 1 or ch > 16:
                error("MIDI channel for import must be 1..16, not %s" % ch)

            channels.append((cmd, ch - 1))

    if not channels:
        if doLyric or doText:
            warning(
                "MidiInc: no import channels specified, only text or lyrics imported"
            )
        else:
            error(
                "MidiInc: A channel to import and a destination track must be specified"
            )

    if (istart >= iend) or (istart < 0) or (iend < 0):
        error("MidiInc range invalid: start=%s, end=%s" % (istart, iend))

    if gbl.debug:
        print "MidiInc: file=%s, Volume=%s, Octave=%s, Transpose=%s, Lyric=%s, Text=%s, Range=%s..%s"\
            % (filename, volAdjust, octAdjust, transpose, doLyric, doText, istart, iend)
        for t, ch in channels:
            print "MidiInc: Channel %s --> Track %s" % (ch + 1, t)

    # If transpose was NOT set, use the global transpose value

    if transpose == None:
        transpose = gbl.transpose

    octAdjust += transpose  # this takes care of octave and transpose

    try:
        inpath = file(filename, "rb")
    except:
        error("Unable to open MIDI file %s for reading" % filename)

    midifile = inpath.read()
    inpath.close()

    # Create our storage:
    #    A dic with the channels 0-15 as keys for the midi note events
    #    2 lists for lyrics and text events. These have tuples for (time, text)

    events = {}
    for c in range(0, 16):
        events[c] = []

    textEvs = []
    lyricEvs = []

    # Ensure this is valid header

    hd = midifile[0:4]
    if hd != 'MThd':
        error("Expecting 'HThd', %s not a standard midi file" % filename)

    offset = 4
    a = m32i()

    if a != 6:
        error("Expecting a 32 bit value of 6 in header")

    format = m16i()

    if format not in (0, 1):
        error("MIDI file format %s not recognized" % format)

    ntracks = m16i()
    beatDivision = m16i()

    if beatDivision != gbl.BperQ:
        warning("MIDI file '%s' tick/beat of %s differs from MMA's "
                "%s. Will try to compensate" %
                (filename, beatDivision, gbl.BperQ))

    # Adjust start/end to the file's tick

    istart *= beatDivision
    iend *= beatDivision

    midievents = {}
    firstNote = 0xffffff

    for tr in range(ntracks):
        tm = 0

        hdr = midifile[offset:offset + 4]
        offset += 4

        if hdr != 'MTrk':
            error("Malformed MIDI file in track header")
        trlen = m32i()  # track length, not used?

        lastevent = None
        """ Parse the midi file. We have to parse off each event, even
            though many will just be thrown away. You can't just skip around
            in a midi file :) In the future we might decide to include meta
            stuff, etc. Or, we may not :) For now, we keep:
                - note on
                - note off
                 - key pressure
                - control change
                - program change
                - channel pressure
                - pitch blend
                - text event
                - lyric event
        """

        while 1:
            tm += mvarlen()  # adjust total offset by delta

            ev = m1i()

            if ev < 0x80:
                if not lastevent:
                    error("Illegal running status in %s at %s" %
                          (midifile, offset))
                offset -= 1
                ev = lastevent

            sValue = ev >> 4  # Shift MSBs to get a 4 bit value
            channel = ev & 0x0f

            if sValue == 0x8:  # note off event

                note = m1i()
                vel = m1i()

                if octAdjust and channel != 10:
                    note += octAdjust
                    if note < 0 or note > 127:
                        continue

                events[channel].append([tm, ev & 0xf0, chr(note) + chr(vel)])

            elif sValue == 0x9:  # note on event
                if tm < firstNote:
                    firstNote = tm

                note = m1i()
                vel = m1i()

                if octAdjust and channel != 10:
                    note += octAdjust
                    if note < 0 or note > 127:
                        continue

                if volAdjust != 100:
                    vel = int((vel * volAdjust) / 100)
                    if vel < 0: vel = 1
                    if vel > 127: vel = 127

                events[ev & 0xf].append([tm, ev & 0xf0, chr(note) + chr(vel)])

            elif sValue == 0xa:  # key pressure
                events[ev & 0xf].append([tm, ev & 0xf0, chars(2)])

            elif sValue == 0xb:  # control change
                events[ev & 0xf].append([tm, ev & 0xf0, chars(2)])

            elif sValue == 0xc:  # program change
                events[ev & 0xf].append([tm, ev & 0xf0, chars(1)])

            elif sValue == 0xd:  # channel pressure
                events[ev & 0xf].append([tm, ev & 0xf0, chars(1)])

            elif sValue == 0xe:  # pitch blend
                events[ev & 0xf].append([tm, ev & 0xf0, chars(2)])

            elif sValue == 0xf:  # system, mostly ignored
                if ev == 0xff:  # meta events
                    a = m1i()

                    if a == 0x00:  # sequence number
                        l = mvarlen()
                        offset += l

                    elif a == 0x01:  # text (could be lyrics)
                        textEvs.append((tm, chars(mvarlen())))

                    elif a == 0x02:  # copyright
                        l = mvarlen()
                        offset += l

                    elif a == 0x03:  # seq/track name
                        l = mvarlen()
                        offset += l

                    elif a == 0x04:  # instrument name
                        l = mvarlen()
                        offset += l

                    elif a == 0x05:  # lyric
                        lyricEvs.append((tm, chars(mvarlen())))

                    elif a == 0x06:  # marker
                        l = mvarlen()
                        offset += l

                    elif a == 0x07:  # cue point
                        l = mvarlen()
                        offset += l

                    elif a == 0x21:  # midi port
                        l = mvarlen()
                        offset += l

                    elif a == 0x2f:  # end of track
                        l = mvarlen()
                        offset += l
                        break

                    elif a == 0x51:  #tempo
                        l = mvarlen()
                        offset += l

                    elif a == 0x54:  # SMPTE offset
                        l = mvarlen()
                        offset += l

                    elif a == 0x58:  # time sig
                        l = mvarlen()
                        offset += l

                    elif a == 0x59:  # key sig
                        l = mvarlen()
                        offset += l

                    else:  # probably 0x7f, proprietary event
                        l = mvarlen()
                        offset += l

                elif ev == 0xf0:  # system exclusive
                    l = mvarlen()
                    offset += l

                elif ev == 0xf2:  # song position pointer, 2 bytes
                    offset += 2

                elif ev == 0xf3:  # song select, 1 byte
                    offset += 1

                else:  # all others are single byte commands
                    pass

            if ev >= 0x80 and ev <= 0xef:
                lastevent = ev

    # Midi file parsed, add selected events to mma data

    beatad = gbl.BperQ / float(beatDivision)

    if doText:
        inst = 0
        disc = 0
        for tm, tx in textEvs:
            delta = tm - firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addText(gbl.tickOffset + int(delta * beatad), tx)
                inst += 1
            else:
                disc += 1
        if gbl.debug:
            print "MidiInc text events: %s inserted, %s out of range." % (inst,
                                                                          disc)

    if doLyric:
        inst = 0
        disc = 0
        for tm, tx in lyricEvs:
            delta = tm - firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addLyric(gbl.tickOffset + int(delta * beatad), tx)
                inst += 1
            else:
                disc += 1
        if gbl.debug:
            print "MidiInc lyric events: %s inserted, %s out of range." % (
                inst, disc)

    for n, c in channels:
        if not len(events[c]):
            warning("No data to assign from imported channel %s to track %s" %
                    (c + 1, n))

    inst = 0
    disc = 0
    for tr, ch in channels:
        t = gbl.tnames[tr]
        if not t.channel:
            t.setChannel()

        t.clearPending()
        if t.voice[0] != t.ssvoice:
            gbl.mtrks[t.channel].addProgChange(gbl.tickOffset, t.voice[0])

        channel = t.channel
        track = gbl.mtrks[channel]

        for ev in events[ch]:
            delta = ev[0] - firstNote
            if delta >= istart and delta <= iend:
                track.addToTrack(gbl.tickOffset + int(delta * beatad),
                                 chr(ev[1] | channel - 1) + ev[2])
                inst += 1
            else:
                disc += 1

    if gbl.debug:
        print "MidiInc events: %s inserted, %s out of range." % (inst, disc)
Example #7
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)
Example #8
0
def midiinc(ln):
    """ Include a MIDI file into MMA generated files. """

    global midifile, offset, octAdjust, volAdjust, firstNote, istart, iend, ignorePC

    filename = ''
    doLyric = 0
    doText = 0
    channels = []
    transpose = None
    stripSilence = 1
    report = 0

    notopt, ln = opt2pair(ln)

    if notopt:
        error("MidiInc expecting cmd=opt pairs, not '%s'." % ' '.join(notopt) )
        
    for cmd, opt in ln:
        cmd=cmd.upper()

        if cmd == 'FILE':
            filename = MMA.file.fixfname(opt)

        elif cmd == 'VOLUME':
            volAdjust = stoi(opt)

        elif cmd == 'OCTAVE':
            octAdjust = stoi(opt)
            if octAdjust < -4 or octAdjust > 4:
                error("Octave adjustment must be -4 to 4, not %s" % opt)
            octAdjust *= 12

        elif cmd == 'TRANSPOSE':
            transpose = stoi(opt)
            if transpose < -24 or transpose > 24:
                error("Transpose must be -24 to 24, not %s" % opt)

        elif cmd == 'START':
            istart = stof(opt)

        elif cmd == 'END':
            iend = stof(opt)

        elif cmd == 'TEXT':
            opt=opt.upper()
            if opt in ("ON", '1'):
                doText=1
            elif opt in ("OFF", '0'):
                doText=0
            else:
                error("MidiInc Text= expecting 'ON' or 'OFF'")

        elif cmd == 'LYRIC':
            opt=opt.upper()
            if opt in ("ON", '1'):
                doLyric=1
            elif opt in ("OFF", '0'):
                doLyric=0
            else:
                error("MidiInc Lyric: expecting 'ON' or 'OFF'")
        
        elif cmd == "REPORT":
            opt=opt.upper()
            if opt in ("ON", '1'):
                report=1
            elif opt in ("OFF", '0'):
                report=0
            else:
                error("MidiInc Report: expecting 'ON' or 'OFF'")
        

        elif cmd == "STRIPSILENCE":
            opt=opt.upper()
            if opt in ("OFF", '0'):
                stripSilence = 0
            elif opt == "ON": # this is the default
                stripSilence = -1
            else:
                stripSilence = stoi(opt,
                     "MIdiInc StripSilence= expecting 'value', 'On' or 'Off', "
                      "not %s" % opt)

        elif cmd == "INCLUDEPC":
            opt=op.upper()
            if opt in ("TRUE", "ON", "1"):   # default
                ignorePC=1
            elif opt in ("FALSE", "OFF", "0"):  # use program change in imported
                ignorePC=0
            else:
                error ("MIdiInc IncludePC= expecting 'True' or 'False', not %s" % opt)

        # If none of the above matched a CMD we assume that it is
        # a trackname. Keep this as the last test!

        else:
            trackAlloc(cmd, 0)
            if not cmd in gbl.tnames:
                error("MidiInc: %s is not a valid MMA track" % cmd)

            opt = opt.split(',')
            riffmode=0
            printriff=0
            ch = None
            for o in opt:
                o=o.upper()
                if o == 'RIFF':
                    riffmode = 1
                elif o == 'PRINT':
                    printriff = 1
                    riffmode = 1
                else:
                    if ch != None:
                        error("MidiInc: Only one channel assignment per track.")
                    ch = stoi(o)
                
            if ch < 1 or ch > 16:
                error("MidiInc: MIDI channel for import must be 1..16, not %s" % ch)

            channels.append( (cmd, ch-1, riffmode, printriff))


    if report:  # don't bother with channel tests, etc.
        gbl.noWarn=1
        events, textEvs, lyricEvs = readMidi(filename)

        print "MIDI File %s successfully read." % filename
        print "Total Text events: %s"  % len(textEvs)
        print "Total Lyric events: %s" % len(lyricEvs)
        if beatDivision != gbl.BperQ:
            s = "(MMA uses %s, MidiInc will compensate)" % gbl.BperQ
        else: 
            s=''
        print "Ticks per quarter note: %s %s" % (beatDivision, s)
        print

        for ch in sorted(events.keys()):
            if not events[ch]:
                continue

            fnote = fevent = 0xffffff
            ncount = 0
            for ev in events[ch]:
                delta = ev[0]
                if delta < fevent:
                    fevent = delta
                if ev[1]>>4 == 0x9:
                    if delta < fnote:
                        fnote = delta
                    if ord(ev[2][1]):
                        ncount +=1
            print "Channel %2s: First event %-8s" % (ch+1, fevent),
            if ncount:
                print "First Note %-8s Total Notes %-4s" %  (fnote, ncount)
            else:
                print
        print


        print "No data generated!"
        sys.exit(0)




    if not channels:
        if doLyric or doText:
            warning("MidiInc: no import channels specified, "
                    "only text or lyrics imported")
        else:
            error("MidiInc: A channel to import and a destination "
                  "track must be specified")

    if (istart >= iend) or (istart < 0) or (iend < 0):
         error("MidiInc range invalid: start=%s, end=%s" % (istart, iend))

    if gbl.debug:
        print "MidiInc: file=%s, Volume=%s, Octave=%s, Transpose=%s, Lyric=%s, " \
            "Text=%s, Range=%s..%s StripSilence=%s" \
            % (filename, volAdjust, octAdjust, transpose, doLyric, doText, \
                   istart, iend, stripSilence)
        for t, ch, riffmode, riffprint in channels:
            o=''
            if riffmode:
                o=',riff'
            elif printriff:
                o=',riff,print'
            print "MidiInc: Channel %s-->%s%s" % (ch+1, t, o),
        print

    # If transpose was NOT set, use the global transpose value
    # Note special riff value as well. Need to double adjust since
    # the riff import will do its own adjustment.

    if transpose == None:
        transpose = gbl.transpose
        riffTranspose = -gbl.transpose
    else:
        riffTranspose = 0

    octAdjust += transpose    # this takes care of octave and transpose

    events, textEvs, lyricEvs = readMidi(filename)


    # Midi file parsed, add selected events to mma data

    beatad = gbl.BperQ / float(beatDivision)
    if not stripSilence:
        firstNote = 0
    elif stripSilence > 0:
        firstNote = stripSilence

    if doText:
        inst=0
        disc=0
        for tm,tx in textEvs:
            delta = tm-firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addText(gbl.tickOffset + int(delta * beatad), tx)
                inst+=1
            else:
                disc+=1
        if gbl.debug:
            print"MidiInc text events: %s inserted, %s out of range." % (inst, disc)

    if doLyric:
        inst=0
        disc=0
        for tm, tx in lyricEvs:
            delta = tm-firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addLyric(gbl.tickOffset + int(delta * beatad), tx)
                inst+=1
            else:
                disc+=1
        if gbl.debug:
            print"MidiInc lyric events: %s inserted, %s out of range." % (inst, disc)


    for n,c, riffmode, printriff in channels:
        if not len(events[c]):
            warning("No data to assign from imported channel %s to track %s" % (c+1, n))

    inst=0
    disc=0

    for tr, ch, riffmode, printriff in channels:

        if gbl.tnames[tr].disable:   # skip if disabled track 
            continue

        t=gbl.tnames[tr]
        if not t.channel:
            t.setChannel()

        if riffmode:
            riff = []
            if t.vtype not in ('MELODY', 'SOLO'):
                error("MidiInc Riff only works on Melody/Solo tracks, not '%s'." % t.name)

        t.clearPending()
        if t.voice[0] != t.ssvoice:
            gbl.mtrks[t.channel].addProgChange( gbl.tickOffset, t.voice[0], t.ssvoice)

        channel = t.channel
        track = gbl.mtrks[channel]

        for ev in events[ch]:
            delta = ev[0]-firstNote

            if delta >= istart and delta <= iend:
                if riffmode:
                    offset = int(delta * beatad) 
                    x=ev[1]>>4
                    if x != 0x09 and x != 0x08:
                        continue
                    pitch=ord(ev[2][0])
                    velocity=ord(ev[2][1])
                    if x == 0x8:
                        velocity = 0
                    riff.append([offset, pitch, velocity]) 
                else:
                    offset = gbl.tickOffset + int(delta * beatad)
                    track.addToTrack( offset, chr(ev[1] | channel-1) + ev[2] )
                inst+=1
            else:
                disc+=1

        if riffmode:
            createRiff(riff, tr, printriff, riffTranspose, beatad)

    if gbl.debug:
            print"MidiInc events: %s inserted, %s out of range." % (inst, disc)
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 #10
0
def midiinc(ln):
    """ Include a MIDI file into MMA generated files. """

    global midifile, offset, octAdjust, volAdjust, firstNote, ignorePC, verbose

    filename = ''
    doLyric = 0
    doText = 0
    channels = []
    transpose = None
    stripSilence = -1
    report = 0
    istart = 0          # istart/end are in ticks
    iend = 0xffffff     # but are set in options in Beats
    verbose = 0
    octAdjust = 0
    volAdjust = 100
    firstNote = 0
    ignorePC = 1
    stretch = None


    notopt, ln = opt2pair(ln)

    if notopt:
        error("MidiInc: Expecting cmd=opt pairs, not '%s'." % ' '.join(notopt) )
        
    for cmd, opt in ln:
        cmd=cmd.upper()

        if cmd == 'FILE':
            filename = MMA.file.fixfname(opt)

        elif cmd == 'VOLUME':
            volAdjust = stoi(opt)

        elif cmd == 'OCTAVE':
            octAdjust = stoi(opt)
            if octAdjust < -4 or octAdjust > 4:
                error("MidiInc: 'Octave' adjustment must be -4 to 4, not %s" % opt)
            octAdjust *= 12

        elif cmd == 'TRANSPOSE':
            transpose = stoi(opt)
            if transpose < -24 or transpose > 24:
                error("MidiInc: 'Transpose' must be -24 to 24, not %s" % opt)

        elif cmd == 'START':
            if opt[-1].upper() == 'M':   # measures
                istart = int( (stof(opt[:-1])-1) * gbl.BperQ * gbl.QperBar)
            elif opt[-1].upper() == 'T':  # ticks
                istart = int(stof(opt[:-1]))
            else:                         # must be digits, stof() catches errors
                istart = int((stof(opt)-1) * gbl.BperQ)

            if istart < 0:
                error("MidiInc: 'Start' must be > 0.")


        elif cmd == 'END':
            if opt[-1].upper() == 'M':
                iend = int( (stof(opt[:-1])-1) * gbl.BperQ * gbl.QperBar)
            elif opt[-1].upper() == 'T':
                istart = int(stof(opt[:-1]))
            else:
                iend = int((stof(opt)-1) * gbl.BperQ)
            
            if iend < 0:
                error("MidiInc: 'End' must be > 0.")
            
        elif cmd == 'TEXT':
            opt=opt.upper()
            if opt in ("ON", '1'):
                doText=1
            elif opt in ("OFF", '0'):
                doText=0
            else:
                error("MidiInc: 'Text' expecting 'ON' or 'OFF'")

        elif cmd == 'LYRIC':
            opt=opt.upper()
            if opt in ("ON", '1'):
                doLyric=1
            elif opt in ("OFF", '0'):
                doLyric=0
            else:
                error("MidiInc: 'Lyric' expecting 'ON' or 'OFF'")
        
        elif cmd == "REPORT":
            opt=opt.upper()
            if opt in ("ON", '1'):
                report=1
            elif opt in ("OFF", '0'):
                report=0
            else:
                error("MidiInc: 'Report' expecting 'ON' or 'OFF'")
        
        elif cmd == "VERBOSE":
            opt=opt.upper()
            if opt in ("ON", '1'):
                verbose=1
            elif opt in ("OFF", '0'):
                verbose=0
            else:
                error("MidiInc: 'Verbose' expecting 'ON' or 'OFF'")
        
        
        elif cmd == "STRIPSILENCE":
            opt=opt.upper()
            if opt in ("OFF", '0'):
                stripSilence = 0
            elif opt == "ON": # this is the default
                stripSilence = -1
            else:
                stripSilence = stoi(opt,
                     "MIdiInc StripSilence= expecting 'value', 'On' or 'Off', "
                      "not %s" % opt)

        elif cmd == "IGNOREPC":
            opt=op.upper()
            if opt in ("TRUE", "ON", "1"):   # default
                ignorePC=1
            elif opt in ("FALSE", "OFF", "0"):  # use program change in imported
                ignorePC=0
            else:
                error ("MIdiInc: 'IncludePC' expecting 'True' or 'False', not %s" % opt)

        elif cmd == "STRETCH":
            v = stof(opt)
            if v < 1 or v > 500:
                error("MidiInc: 'Stretch' range of 1 to 500, not %s." % opt)
            stretch = v/100.

        # If none of the above matched a CMD we assume that it is
        # a trackname. Keep this as the last test!

        else:
            trackAlloc(cmd, 0)
            if not cmd in gbl.tnames:
                error("MidiInc: %s is not a valid MMA track" % cmd)

            opt = opt.split(',')
            riffmode=0
            printriff=0
            ch = None
            for o in opt:
                o=o.upper()
                if o == 'RIFF':
                    riffmode = 1
                elif o == 'SEQUENCE':
                    riffmode = 2
                elif o == 'PRINT':
                    printriff = 1
                    if not riffmode:
                        riffmode = 1
                else:
                    if ch != None:
                        error("MidiInc: Only one channel assignment per track.")
                    ch = stoi(o)
                
            if ch < 1 or ch > 16:
                error("MidiInc: MIDI channel for import must be 1..16, not %s" % ch)

            channels.append( (cmd, ch-1, riffmode, printriff))

    events, textEvs, lyricEvs = readMidi(filename)

    if report or verbose:  # try to be helpful
        gbl.noWarn=1

        print "MIDI File %s successfully read." % filename
        print "Total Text events: %s"  % len(textEvs)
        print "Total Lyric events: %s" % len(lyricEvs)
        print
        
        for ch in sorted(events.keys()):
            if not events[ch]:
                continue

            if verbose and not report:   # in verbose mode only list info for tracks we're using
                doit = 0
                for z in channels:
                    if z[1] == ch:
                        doit = 1
                        break

                if not doit:
                    continue

            fnote = fevent = 0xffffff
            ncount = 0
            for ev in events[ch]:
                delta = ev[0]
                if delta < fevent:
                    fevent = delta
                if ev[1]>>4 == 0x9:
                    if delta < fnote:
                        fnote = delta
                    if ord(ev[2][1]):
                        ncount +=1
            print "Channel %2s: First event %-8s" % (ch+1, fevent),
            if ncount:
                print "First Note %-8s Total Notes %-4s" %  (fnote, ncount)
            else:
                print

        if report:
            print "\nNo data generated!"
            sys.exit(0)


    if not channels:
        if doLyric or doText:
            warning("MidiInc: no import channels specified, "
                    "only text or lyrics imported")
        else:
            error("MidiInc: A channel to import and a destination "
                  "track must be specified")

    if (istart >= iend) or (istart < 0) or (iend < 0):
         error("MidiInc: Range invalid, start=%s, end=%s" % (istart, iend))

    if gbl.debug:
        print "MidiInc: file=%s, Volume=%s, Octave=%s, Transpose=%s, Lyric=%s, " \
            "Text=%s, Range=%s..%s StripSilence=%s Verbose=%s" \
            % (filename, volAdjust, octAdjust, transpose, doLyric, doText, \
                   istart, iend, stripSilence, verbose)
        for t, ch, riffmode, riffprint in channels:
            o=''
            if riffmode==1:
                o=',riff'
            elif riffmode == 2:
                o=',sequence'
            elif printriff:
                o+=',print'
            print "MidiInc: Channel %s-->%s%s" % (ch+1, t, o),
        print

    # If transpose was NOT set, use the global transpose value
    # Note special riff value as well. Need to double adjust since
    # the riff import will do its own adjustment.

    if transpose == None:
        transpose = gbl.transpose
        riffTranspose = -gbl.transpose
    else:
        riffTranspose = 0

    octAdjust += transpose    # this takes care of octave and transpose

    if stretch:
        if verbose:
            print "Applying stretch to all events. Deltas will be multiplied by", stretch
        for tr in events:
            for e in events[tr]:
                e[0] = int(e[0] * stretch)

        for e in textEvs:
            e[0] = int(e[0] * stretch)


        for e in lyricEvs:
            e[0] = int(e[0] * stretch)

    # Midi file parsed, add selected events to mma data

    if stripSilence == 0:

        if verbose:
            print "Firstnote offset was %s. Being reset to start of file by stripSilence." \
                % firstNote
        firstNote = 0
    
    if verbose:
        print "First note offset: %s" % firstNote

    if doText:
        inst=0
        disc=0
        if verbose:
            print "Scanning %s textevents." % len(textEvs)
        for tm,tx in textEvs:
            delta = tm-firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addText(gbl.tickOffset + delta, tx)
                inst+=1
            else:
                disc+=1
        if gbl.debug:
            print"MidiInc text events: %s inserted, %s out of range." % (inst, disc)

    if doLyric:
        inst=0
        disc=0
        if verbose:
            print "Scanning %s LyricEvents." % len(lyricEvs)
        for tm, tx in lyricEvs:
            delta = tm-firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addLyric(gbl.tickOffset + delta, tx)
                inst+=1
            else:
                disc+=1
        if gbl.debug:
            print"MidiInc lyric events: %s inserted, %s out of range." % (inst, disc)


    for n,c, riffmode, printriff in channels:
        if not len(events[c]):
            warning("No data to assign from imported channel %s to track %s" % (c+1, n))

    inst=0
    disc=0

    for tr, ch, riffmode, printriff in channels:

        if gbl.tnames[tr].disable:   # skip if disabled track 
            if verbose:
                print "Skipping import of channel %s since track %s is disabled." \
                    % (ch, tr)
            continue

        t=gbl.tnames[tr]
        if not t.channel:
            t.setChannel()

        if riffmode:
            riff = []
            if t.vtype not in ('MELODY', 'SOLO'):
                error("MidiInc: Riff only works on Melody/Solo tracks, not '%s'." % t.name)

        t.clearPending()
        if t.voice[0] != t.ssvoice:
            gbl.mtrks[t.channel].addProgChange( gbl.tickOffset, t.voice[0], t.ssvoice)

        channel = t.channel
        track = gbl.mtrks[channel]
        
        if verbose:
                print "Parsing imported file. Channel=%s Track=%s MIDI Channel=%s" \
                    % (ch, tr, channel)
                print " Total events: %s; Event range: %s %s; Start/End Range: %s %s" \
                    % (len(events[ch]),events[ch][0][0], events[ch][-1][0], istart, iend)

        for ev in events[ch]:
            delta = ev[0]-firstNote
            
            if delta >= istart and delta  <= iend:
                if riffmode:
                    offset = delta-istart
                    x=ev[1]>>4
                    if x != 0x09 and x != 0x08:
                        continue
                    pitch=ord(ev[2][0])
                    velocity=ord(ev[2][1])
                    if x == 0x8:
                        velocity = 0
                    riff.append([offset, pitch, velocity]) 
                else:
                    offset = gbl.tickOffset + (delta-istart)
                    track.addToTrack( offset, chr(ev[1] | channel-1) + ev[2] )
                inst+=1
            else:
                disc+=1

        if riffmode:
            evlist = createRiff(riff, tr, riffTranspose)
            
            if riffmode == 2:
                txt = []
            for a in sorted(evlist):
                if printriff and riffmode == 1:
                    print "%s Riff %s" % (tr, evlist[a])
                elif riffmode == 2:   # sequence mode, create sequence line and push into input
                    txt.append("{%s}" % evlist[a])
                else:   # riffmode==1, printriff=0 - just add to the riff stack
                    gbl.tnames[tr].setRiff(evlist[a])

            if riffmode == 2 and txt:
                if printriff:
                    print "%s Sequence %s" % (tr, ' '.join(txt))
                else:
                    MMA.sequence.trackSequence(tr, txt)

    if gbl.debug:
            print"MidiInc events: %s inserted, %s out of range." % (inst, disc)
Example #11
0
def midiinc(ln):
    """ Include a MIDI file into MMA generated files. """

    global midifile, offset, octAdjust, volAdjust, firstNote, ignorePC, verbose

    filename = ''
    doLyric = 0
    doText = 0
    channels = []
    transpose = None
    stripSilence = -1
    report = 0
    istart = 0  # istart/end are in ticks
    iend = 0xffffff  # but are set in options in Beats
    verbose = 0
    octAdjust = 0
    volAdjust = 100
    firstNote = 0
    ignorePC = 1
    stretch = None

    notopt, ln = opt2pair(ln)

    if notopt:
        error("MidiInc: Expecting cmd=opt pairs, not '%s'." % ' '.join(notopt))

    for cmd, opt in ln:
        cmd = cmd.upper()

        if cmd == 'FILE':
            filename = MMA.file.fixfname(opt)

        elif cmd == 'VOLUME':
            volAdjust = stoi(opt)

        elif cmd == 'OCTAVE':
            octAdjust = stoi(opt)
            if octAdjust < -4 or octAdjust > 4:
                error("MidiInc: 'Octave' adjustment must be -4 to 4, not %s" %
                      opt)
            octAdjust *= 12

        elif cmd == 'TRANSPOSE':
            transpose = stoi(opt)
            if transpose < -24 or transpose > 24:
                error("MidiInc: 'Transpose' must be -24 to 24, not %s" % opt)

        elif cmd == 'START':
            if opt[-1].upper() == 'M':  # measures
                istart = int((stof(opt[:-1]) - 1) * gbl.BperQ * gbl.QperBar)
            elif opt[-1].upper() == 'T':  # ticks
                istart = int(stof(opt[:-1]))
            else:  # must be digits, stof() catches errors
                istart = int((stof(opt) - 1) * gbl.BperQ)

            if istart < 0:
                error("MidiInc: 'Start' must be > 0.")

        elif cmd == 'END':
            if opt[-1].upper() == 'M':
                iend = int((stof(opt[:-1]) - 1) * gbl.BperQ * gbl.QperBar)
            elif opt[-1].upper() == 'T':
                istart = int(stof(opt[:-1]))
            else:
                iend = int((stof(opt) - 1) * gbl.BperQ)

            if iend < 0:
                error("MidiInc: 'End' must be > 0.")

        elif cmd == 'TEXT':
            opt = opt.upper()
            if opt in ("ON", '1'):
                doText = 1
            elif opt in ("OFF", '0'):
                doText = 0
            else:
                error("MidiInc: 'Text' expecting 'ON' or 'OFF'")

        elif cmd == 'LYRIC':
            opt = opt.upper()
            if opt in ("ON", '1'):
                doLyric = 1
            elif opt in ("OFF", '0'):
                doLyric = 0
            else:
                error("MidiInc: 'Lyric' expecting 'ON' or 'OFF'")

        elif cmd == "REPORT":
            opt = opt.upper()
            if opt in ("ON", '1'):
                report = 1
            elif opt in ("OFF", '0'):
                report = 0
            else:
                error("MidiInc: 'Report' expecting 'ON' or 'OFF'")

        elif cmd == "VERBOSE":
            opt = opt.upper()
            if opt in ("ON", '1'):
                verbose = 1
            elif opt in ("OFF", '0'):
                verbose = 0
            else:
                error("MidiInc: 'Verbose' expecting 'ON' or 'OFF'")

        elif cmd == "STRIPSILENCE":
            opt = opt.upper()
            if opt in ("OFF", '0'):
                stripSilence = 0
            elif opt == "ON":  # this is the default
                stripSilence = -1
            else:
                stripSilence = stoi(
                    opt,
                    "MIdiInc StripSilence= expecting 'value', 'On' or 'Off', "
                    "not %s" % opt)

        elif cmd == "IGNOREPC":
            opt = op.upper()
            if opt in ("TRUE", "ON", "1"):  # default
                ignorePC = 1
            elif opt in ("FALSE", "OFF",
                         "0"):  # use program change in imported
                ignorePC = 0
            else:
                error(
                    "MIdiInc: 'IncludePC' expecting 'True' or 'False', not %s"
                    % opt)

        elif cmd == "STRETCH":
            v = stof(opt)
            if v < 1 or v > 500:
                error("MidiInc: 'Stretch' range of 1 to 500, not %s." % opt)
            stretch = v / 100.

        # If none of the above matched a CMD we assume that it is
        # a trackname. Keep this as the last test!

        else:
            trackAlloc(cmd, 0)
            if not cmd in gbl.tnames:
                error("MidiInc: %s is not a valid MMA track" % cmd)

            opt = opt.split(',')
            riffmode = 0
            printriff = 0
            ch = None
            for o in opt:
                o = o.upper()
                if o == 'RIFF':
                    riffmode = 1
                elif o == 'SEQUENCE':
                    riffmode = 2
                elif o == 'PRINT':
                    printriff = 1
                    if not riffmode:
                        riffmode = 1
                else:
                    if ch != None:
                        error(
                            "MidiInc: Only one channel assignment per track.")
                    ch = stoi(o)

            if ch < 1 or ch > 16:
                error(
                    "MidiInc: MIDI channel for import must be 1..16, not %s" %
                    ch)

            channels.append((cmd, ch - 1, riffmode, printriff))

    events, textEvs, lyricEvs = readMidi(filename)

    if report or verbose:  # try to be helpful
        gbl.noWarn = 1

        print "MIDI File %s successfully read." % filename
        print "Total Text events: %s" % len(textEvs)
        print "Total Lyric events: %s" % len(lyricEvs)
        print

        for ch in sorted(events.keys()):
            if not events[ch]:
                continue

            if verbose and not report:  # in verbose mode only list info for tracks we're using
                doit = 0
                for z in channels:
                    if z[1] == ch:
                        doit = 1
                        break

                if not doit:
                    continue

            fnote = fevent = 0xffffff
            ncount = 0
            for ev in events[ch]:
                delta = ev[0]
                if delta < fevent:
                    fevent = delta
                if ev[1] >> 4 == 0x9:
                    if delta < fnote:
                        fnote = delta
                    if ord(ev[2][1]):
                        ncount += 1
            print "Channel %2s: First event %-8s" % (ch + 1, fevent),
            if ncount:
                print "First Note %-8s Total Notes %-4s" % (fnote, ncount)
            else:
                print

        if report:
            print "\nNo data generated!"
            sys.exit(0)

    if not channels:
        if doLyric or doText:
            warning("MidiInc: no import channels specified, "
                    "only text or lyrics imported")
        else:
            error("MidiInc: A channel to import and a destination "
                  "track must be specified")

    if (istart >= iend) or (istart < 0) or (iend < 0):
        error("MidiInc: Range invalid, start=%s, end=%s" % (istart, iend))

    if gbl.debug:
        print "MidiInc: file=%s, Volume=%s, Octave=%s, Transpose=%s, Lyric=%s, " \
            "Text=%s, Range=%s..%s StripSilence=%s Verbose=%s" \
            % (filename, volAdjust, octAdjust, transpose, doLyric, doText, \
                   istart, iend, stripSilence, verbose)
        for t, ch, riffmode, riffprint in channels:
            o = ''
            if riffmode == 1:
                o = ',riff'
            elif riffmode == 2:
                o = ',sequence'
            elif printriff:
                o += ',print'
            print "MidiInc: Channel %s-->%s%s" % (ch + 1, t, o),
        print

    # If transpose was NOT set, use the global transpose value
    # Note special riff value as well. Need to double adjust since
    # the riff import will do its own adjustment.

    if transpose == None:
        transpose = gbl.transpose
        riffTranspose = -gbl.transpose
    else:
        riffTranspose = 0

    octAdjust += transpose  # this takes care of octave and transpose

    if stretch:
        if verbose:
            print "Applying stretch to all events. Deltas will be multiplied by", stretch
        for tr in events:
            for e in events[tr]:
                e[0] = int(e[0] * stretch)

        for e in textEvs:
            e[0] = int(e[0] * stretch)

        for e in lyricEvs:
            e[0] = int(e[0] * stretch)

    # Midi file parsed, add selected events to mma data

    if stripSilence == 0:

        if verbose:
            print "Firstnote offset was %s. Being reset to start of file by stripSilence." \
                % firstNote
        firstNote = 0

    if verbose:
        print "First note offset: %s" % firstNote

    if doText:
        inst = 0
        disc = 0
        if verbose:
            print "Scanning %s textevents." % len(textEvs)
        for tm, tx in textEvs:
            delta = tm - firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addText(gbl.tickOffset + delta, tx)
                inst += 1
            else:
                disc += 1
        if gbl.debug:
            print "MidiInc text events: %s inserted, %s out of range." % (inst,
                                                                          disc)

    if doLyric:
        inst = 0
        disc = 0
        if verbose:
            print "Scanning %s LyricEvents." % len(lyricEvs)
        for tm, tx in lyricEvs:
            delta = tm - firstNote
            if delta >= istart and delta <= iend:
                gbl.mtrks[0].addLyric(gbl.tickOffset + delta, tx)
                inst += 1
            else:
                disc += 1
        if gbl.debug:
            print "MidiInc lyric events: %s inserted, %s out of range." % (
                inst, disc)

    for n, c, riffmode, printriff in channels:
        if not len(events[c]):
            warning("No data to assign from imported channel %s to track %s" %
                    (c + 1, n))

    inst = 0
    disc = 0

    for tr, ch, riffmode, printriff in channels:

        if gbl.tnames[tr].disable:  # skip if disabled track
            if verbose:
                print "Skipping import of channel %s since track %s is disabled." \
                    % (ch, tr)
            continue

        t = gbl.tnames[tr]
        if not t.channel:
            t.setChannel()

        if riffmode:
            riff = []
            if t.vtype not in ('MELODY', 'SOLO'):
                error(
                    "MidiInc: Riff only works on Melody/Solo tracks, not '%s'."
                    % t.name)

        t.clearPending()
        if t.voice[0] != t.ssvoice:
            gbl.mtrks[t.channel].addProgChange(gbl.tickOffset, t.voice[0],
                                               t.ssvoice)

        channel = t.channel
        track = gbl.mtrks[channel]

        if verbose:
            print "Parsing imported file. Channel=%s Track=%s MIDI Channel=%s" \
                % (ch, tr, channel)
            print " Total events: %s; Event range: %s %s; Start/End Range: %s %s" \
                % (len(events[ch]),events[ch][0][0], events[ch][-1][0], istart, iend)

        for ev in events[ch]:
            delta = ev[0] - firstNote

            if delta >= istart and delta <= iend:
                if riffmode:
                    offset = delta - istart
                    x = ev[1] >> 4
                    if x != 0x09 and x != 0x08:
                        continue
                    pitch = ord(ev[2][0])
                    velocity = ord(ev[2][1])
                    if x == 0x8:
                        velocity = 0
                    riff.append([offset, pitch, velocity])
                else:
                    offset = gbl.tickOffset + (delta - istart)
                    track.addToTrack(offset, chr(ev[1] | channel - 1) + ev[2])
                inst += 1
            else:
                disc += 1

        if riffmode:
            evlist = createRiff(riff, tr, riffTranspose)

            if riffmode == 2:
                txt = []
            for a in sorted(evlist):
                if printriff and riffmode == 1:
                    print "%s Riff %s" % (tr, evlist[a])
                elif riffmode == 2:  # sequence mode, create sequence line and push into input
                    txt.append("{%s}" % evlist[a])
                else:  # riffmode==1, printriff=0 - just add to the riff stack
                    gbl.tnames[tr].setRiff(evlist[a])

            if riffmode == 2 and txt:
                if printriff:
                    print "%s Sequence %s" % (tr, ' '.join(txt))
                else:
                    MMA.sequence.trackSequence(tr, txt)

    if gbl.debug:
        print "MidiInc events: %s inserted, %s out of range." % (inst, disc)
Example #12
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