def getPgroup(self, ev): """ Get group for chord pattern. Tuples: [start, length, volume (,volume ...) ] """ if len(ev) < 3: error("There must be at least 3 items in each group " "of a chord pattern definition, not <%s>" % ' '.join(ev)) a = struct() a.offset = self.setBarOffset(ev[0]) a.duration = getNoteLen(ev[1]) vv = ev[2:] if len(vv) > 8: error("Only 8 volumes are permitted in Chord definition, not %s" % len(vv)) a.vol = [0] * 8 for i, v in enumerate(vv): v = stoi(v, "Expecting integer in volume list for Chord definition") a.vol[i] = v for i in range(i + 1, 8): # force remaining volumes a.vol[i] = v return a
def sysfun(self, func, arg): if func == 'NOTELEN': return "%sT" % getNoteLen(arg) elif func == 'ENV': return safeEnv(arg) else: error("Unknown system function %s" % func)
def getPgroup(self, ev): """ Get group for bass pattern. Fields - start, length, note, volume """ if len(ev) != 4: error("There must be n groups of 4 in a pattern definition, " "not <%s>" % ' '.join(ev)) a = struct() a.offset = self.setBarOffset(ev[0]) a.duration = getNoteLen(ev[1]) offset = ev[2] n = offset[0] if n in "1234567": a.noteoffset = int(n) - 1 else: error("Note offset in Bass must be '1'...'7', not '%s'" % n) n = offset[1:2] if n == "#": a.accidental = 1 ptr = 2 elif n == 'b' or n == 'b' or n == '&': a.accidental = -1 ptr = 2 else: a.accidental = 0 ptr = 1 a.addoctave = 0 for n in ev[2][ptr:]: if n == '+': a.addoctave += 12 elif n == '-': a.addoctave -= 12 else: error( "Only '- + # b &' are permitted after a noteoffset, not '%s'" % n) a.vol = stoi(ev[3], "Note volume in Bass definition not int") return a
def getPgroup(self, ev): """ Get group for apreggio pattern. Fields - start, length, volume """ a = struct() if len(ev) != 3: error("There must be exactly 3 items in each group " "for apreggio define, not '%s'" % ' '.join(ev)) a.offset = self.setBarOffset(ev[0]) a.duration = getNoteLen(ev[1]) a.vol = stoi(ev[2], "Type error in Arpeggio definition") return a
def getPgroup(self, ev): """ Get group for a drum pattern. Fields - start, length, volume """ if len(ev) != 3: error("There must be at exactly 3 items in each " "group of a drum define, not <%s>" % ' '.join(ev) ) a = struct() a.offset = self.setBarOffset(ev[0]) a.duration = getNoteLen(ev[1]) a.vol = stoi(ev[2], "Type error in Drum volume") return a
def getPgroup(self, ev): """ Get group for walking bass pattern. Fields - start, length, volume """ if len(ev) != 3: error("There must be at exactly 3 items in each group in " "a Walking Bass definition, not <%s>" % ' '.join(ev)) a = struct() a.offset = self.setBarOffset(ev[0]) a.duration = getNoteLen(ev[1]) a.vol = stoi(ev[2], "Type error in Walking Bass definition") return a
def getPgroup(self, ev): """ Get group for aria pattern. Fields - start, length, velocity """ if len(ev) != 3: error("There must be n groups of 3 in a pattern definition, " "not <%s>" % ' '.join(ev)) a = struct() a.offset = self.setBarOffset(ev[0]) a.duration = getNoteLen(ev[1]) a.vol = stoi(ev[2], "Note volume in Aria definition not int") return a
def sysvar(self, s): """ Create an internal macro. """ # Simple/global system values if s == 'CHORDADJUST': return ' '.join([ "%s=%s" % (a, MMA.chords.cdAdjust[a]) for a in sorted(MMA.chords.cdAdjust) ]) elif s == 'KEYSIG': return keySig.getKeysig() elif s == 'TIME': return str(gbl.QperBar) elif s == 'CTABS': return ','.join([ str((float(x) / gbl.BperQ) + 1) for x in MMA.parseCL.chordTabs ]) elif s == 'TIMESIG': return timeSig.getAscii() elif s == 'TEMPO': return str(gbl.tempo) elif s == 'OFFSET': return str(gbl.tickOffset) elif s == 'VOLUME': return str(int(MMA.volume.volume * 100)) # INT() is important elif s == 'VOLUMERATIO': return str((MMA.volume.vTRatio * 100)) elif s == 'LASTVOLUME': return str(int(MMA.volume.lastVolume * 100)) elif s == 'GROOVE': return MMA.grooves.currentGroove elif s == 'GROOVELIST': return ' '.join( sorted([ x for x in MMA.grooves.glist.keys() if type(x) == type('') ])) elif s == 'TRACKLIST': return ' '.join(sorted(gbl.tnames.keys())) elif s == 'LASTGROOVE': return MMA.grooves.lastGroove elif s == 'PLUGINS': from MMA.regplug import simplePlugs # to avoid circular import error return ' '.join(simplePlugs) elif s == 'SEQ': return str(gbl.seqCount) elif s == 'SEQRND': if MMA.seqrnd.seqRnd[0] == 0: return "Off" if MMA.seqrnd.seqRnd[0] == 1: return "On" return ' '.join(MMA.seqrnd.seqRnd[1:]) elif s == 'SEQSIZE': return str(gbl.seqSize) elif s == 'SWINGMODE': return MMA.swing.settings() elif s == 'TICKPOS': return str(gbl.tickOffset) elif s == 'TRANSPOSE': return str(gbl.transpose) elif s == 'STACKVALUE': if not self.pushstack: error("Empty push/pull variable stack") return self.pushstack.pop() elif s == 'DEBUG': return "Debug=%s Filenames=%s Patterns=%s " \ "Sequence=%s Runtime=%s Warnings=%s Expand=%s " \ "Roman=%s Plectrum=%s Groove=%s" % \ (gbl.debug, gbl.showFilenames, gbl.pshow, gbl.seqshow, gbl.showrun, int(not gbl.noWarn), gbl.showExpand, gbl.rmShow, gbl.plecShow, gbl.gvShow) elif s == 'LASTDEBUG': return "Debug=%s Filenames=%s Patterns=%s " \ "Sequence=%s Runtime=%s Warnings=%s Expand=%s " \ "Roman=%s Plectrum=%s Groove=%s" % \ (gbl.Ldebug, gbl.LshowFilenames, gbl.Lpshow, gbl.Lseqshow, gbl.Lshowrun, int(not gbl.LnoWarn), gbl.LshowExpand, gbl.LrmShow, gbl.LplecShow, gbl.LgvShow) elif s == 'VEXPAND': if self.expandMode: return "On" else: return "Off" elif s == "MIDIPLAYER": return "%s Background=%s Delay=%s." % \ (' '.join(MMA.player.midiPlayer), MMA.player.inBackGround, MMA.player.waitTime) elif s == "MIDISPLIT": return ' '.join([str(x) for x in MMA.midi.splitChannels]) elif s.startswith("NOTELEN(") and s.endswith(")"): return "%sT" % getNoteLen(s[8:-1]) elif s == 'SEQRNDWEIGHT': return ' '.join([str(x) for x in MMA.seqrnd.seqRndWeight]) elif s == 'AUTOLIBPATH': return ' '.join(MMA.paths.libDirs) elif s == 'LIBPATH': return ' '.join(MMA.paths.libPath) elif s == 'MMAPATH': return gbl.MMAdir elif s == 'INCPATH': return ' '.join(MMA.paths.incPath) elif s == 'VOICETR': return MMA.translate.vtable.retlist() elif s == 'TONETR': return MMA.translate.dtable.retlist() elif s == 'OUTPATH': return gbl.outPath elif s == 'BARNUM': return str(gbl.barNum + 1) elif s == 'LINENUM': return str(gbl.lineno) elif s == 'LYRIC': return lyric.setting() # Track vars ... these are in format TRACKNAME_VAR a = s.rfind('_') if a == -1: error("Unknown system variable $_%s" % s) tname = s[:a] func = s[a + 1:] try: t = gbl.tnames[tname] except KeyError: error("System variable $_%s refers to nonexistent track." % s) if func == 'ACCENT': r = [] for s in t.accent: r.append("{") for b, v in s: r.append('%g' % (b / float(gbl.BperQ) + 1)) r.append(str(int(v * 100))) r.append("}") return ' '.join(r) elif func == 'ARTICULATE': return ' '.join([str(x) for x in t.artic]) elif func == 'CHORDS': r = [] for l in t.chord: r.append('{' + ' '.join(l) + '}') return ' '.join(r) elif func == 'CHANNEL': return str(t.channel) elif func == 'COMPRESS': return ' '.join([str(x) for x in t.compress]) elif func == 'DELAY': return ' '.join([str(x) for x in t.delay]) elif func == 'DIRECTION': if t.vtype == 'ARIA': return ' '.join([str(x) for x in t.selectDir]) else: return ' '.join([str(x) for x in t.direction]) elif func == 'DUPROOT': if t.vtype != "CHORD": error("Only CHORD tracks have DUPROOT") return t.getDupRootSetting() elif func == 'FRETNOISE': return t.getFretNoiseOptions() elif func == 'HARMONY': return ' '.join([str(x) for x in t.harmony]) elif func == 'HARMONYONLY': return ' '.join([str(x) for x in t.harmonyOnly]) elif func == 'HARMONYVOLUME': return ' '.join([str(int(i * 100)) for i in t.harmonyVolume]) elif func == 'INVERT': return ' '.join([str(x) for x in t.invert]) elif func == 'LIMIT': return str(t.chordLimit) elif func == 'MALLET': if t.vtype not in ("SOLO", "MELODY"): error("Mallet only valid in SOLO and MELODY tracks") return "Mallet Rate=%i Decay=%i" % (t.mallet, t.malletDecay * 100) elif func == 'MIDINOTE': return MMA.midinote.mopts(t) elif func == 'MIDIVOLUME': return "%s" % t.cVolume elif func == 'OCTAVE': return ' '.join([str(i // 12) for i in t.octave]) elif func == 'MOCTAVE': return ' '.join([str((i // 12) - 1) for i in t.octave]) elif func == 'ORNAMENT': return MMA.ornament.getOrnOpts(t) elif func == 'PLUGINS': from MMA.regplug import trackPlugs # avoids circular import return ' '.join(trackPlugs) elif func == 'RANGE': return ' '.join([str(x) for x in t.chordRange]) elif func == 'RSKIP': m = '' if t.rSkipBeats: m = "Beats=%s " % ','.join( ['%g' % (x / float(gbl.BperQ) + 1) for x in t.rSkipBeats]) m += ' '.join([str(int(i * 100)) for i in t.rSkip]) return m elif func == 'RDURATION': tmp = [] for a1, a2 in t.rDuration: a1 = int(a1 * 100) a2 = int(a2 * 100) if a1 == a2: tmp.append('%s' % abs(a1)) else: tmp.append('%s,%s' % (a1, a2)) return ' '.join(tmp) elif func == 'RTIME': tmp = [] for a1, a2 in t.rTime: if a1 == a2: tmp.append('%s' % abs(a1)) else: tmp.append('%s,%s' % (a1, a2)) return ' '.join(tmp) elif func == 'RVOLUME': tmp = [] for a1, a2 in t.rVolume: a1 = int(a1 * 100) a2 = int(a2 * 100) if a1 == a2: tmp.append('%s' % abs(a1)) else: tmp.append('%s,%s' % (a1, a2)) return ' '.join(tmp) elif func == 'RPITCH': return MMA.rpitch.getOpts(t) elif func == 'SEQUENCE': tmp = [] for a in range(gbl.seqSize): tmp.append('{' + t.formatPattern(t.sequence[a]) + '}') return ' '.join(tmp) elif func == 'SEQRND': if t.seqRnd: return 'On' else: return 'Off' elif func == 'SEQRNDWEIGHT': return ' '.join([str(x) for x in t.seqRndWeight]) elif func == 'SPAN': return "%s %s" % (t.spanStart, t.spanEnd) elif func == 'STICKY': if t.sticky: return "True" else: return "False" elif func == 'STRUM': r = [] for v in t.strum: if v is None: r.append("0") else: a, b = v if a == b: r.append("%s" % a) else: r.append("%s,%s" % (a, b)) return ' '.join(r) elif func == 'STRUMADD': return ' '.join([str(x) for x in t.strumAdd]) elif func == 'TRIGGER': return MMA.trigger.getTriggerOptions(t) elif func == 'TONE': if t.vtype != "DRUM": error("Only DRUM tracks have TONE") return ' '.join([MMA.midiC.valueToDrum(a) for a in t.toneList]) elif func == 'UNIFY': return ' '.join([str(x) for x in t.unify]) elif func == 'VOICE': return ' '.join([MMA.midiC.valueToInst(a) for a in t.voice]) elif func == 'VOICING': if t.vtype != 'CHORD': error("Only CHORD tracks have VOICING") t = t.voicing return "Mode=%s Range=%s Center=%s RMove=%s Move=%s Dir=%s" % \ (t.mode, t.range, t.center, t.random, t.bcount, t.dir) elif func == 'VOLUME': return ' '.join([str(int(a * 100)) for a in t.volume]) else: error("Unknown system track variable %s" % s)
def getLine(self, pat, ctable): """ Extract a melodyline for solo/melody tracks. This is only called from trackbar(), but it's nicer to isolate it here. RETURNS: notes structure. This is a dictionary. Each key represents an offset in MIDI ticks in the current bar. The data for each entry is an array of notes, a duration and velocity: notes[offset].dur - duration in ticks notes[offset].velocity[] - velocity for notes notes[offset].defaultVel - default velocity for this offset notes[offset].nl[] - list of notes (if the only note value is None this is a rest placeholder) """ sc = self.seq barEnd = gbl.BperQ * gbl.QperBar acc = keySig.getAcc() # list of notename to midivalues midiNotes = { 'c': 0, 'd': 2, 'e': 4, 'f': 5, 'g': 7, 'a': 9, 'b': 11, 'r': None } """ The initial string is in the format "1ab;4c;;4r;". The trailing ';' is important and needed. If we don't have this requirement we can't tell if the last note is a repeat of the previous. For example, if we have coded "2a;2a;" as "2a;;" and we didn't have the 'must end with ;' rule, we end up with "2a;" and then we make this into 2 notes...or do we? Easiest just to insist that all bars end with a ";". """ if not pat.endswith(';'): error("All Solo strings must end with a ';'") """ Take our list of note/value pairs and decode into a list of midi values. Quite ugly. """ if gbl.swingMode: len8 = getNoteLen('8') len81 = getNoteLen('81') len82 = getNoteLen('82') onBeats = [x * gbl.BperQ for x in range(gbl.QperBar)] offBeats = [(x * gbl.BperQ + len8) for x in range(gbl.QperBar)] length = getNoteLen('4') # default note length lastc = '' # last parsed note velocity = 90 # intial/default velocity for solo notes notes = {} # A dict of NoteList, keys == offset if self.drumType: isdrum = 1 lastc = str(self.drumTone) else: isdrum = None pat = pat.replace(' ', '').split(';')[:-1] # set initial offset into bar if pat[0].startswith("~"): pat[0] = pat[0][1:] if not self.endTilde or self.endTilde[1] != gbl.tickOffset: error("Previous line did not end with '~'") else: offset = self.endTilde[0] else: offset = 0 lastOffset = None # Strip off trailing ~ if pat[-1].endswith("~"): self.endTilde = [1, gbl.tickOffset + (gbl.BperQ * gbl.QperBar)] pat[-1] = pat[-1][:-1] else: self.endTilde = [] # Begin parse loop for a in pat: if a == '<>': continue if offset >= barEnd: error("Attempt to start Solo note '%s' after end of bar" % a) # strip out all '<volume>' setting and adjust velocity a, vls = pextract(a, "<", ">") if vls: if len(vls) > 1: error("Only 1 volume string is permitted per note-set") vls = vls[0].upper().strip() if not vls in MMA.volume.vols: error("%s string Expecting a valid volume, not '%s'" % \ (self.name, vls)) velocity *= MMA.volume.vols[vls] """ Split the chord chunk into a note length and notes. Each part of this is optional and defaults to the previously parsed value. """ i = 0 while i < len(a): if not a[i] in '1234568.+': break else: i += 1 if i: l = getNoteLen(a[0:i]) c = a[i:] else: l = length c = a if not c: c = lastc if not c: error("You must specify the first note in a solo line") length = l # set defaults for next loop lastc = c """ Convert the note part into a series of midi values Notes can be a single note, or a series of notes. And each note can be a letter a-g (or r), a '#,&,n' plus a series of '+'s or '-'s. Drum solos must have each note separated by ','s: "Snare1,Kick1,44". """ if isdrum: c = c.split(',') else: c = list(c) while c: # Parse off note name or 'r' for a rest name = c.pop(0) if name == 'r' and (offset in notes or c): error( "You cannot combine a rest with a note in a chord for solos" ) if not isdrum: if not name in midiNotes: error("%s encountered illegal note name '%s'" % (self.name, name)) v = midiNotes[name] # Parse out a "#', '&' or 'n' accidental. if c and c[0] == '#': c.pop(0) acc[name] = 1 elif c and c[0] == '&': c.pop(0) acc[name] = -1 elif c and c[0] == 'n': c.pop(0) acc[name] = 0 if v != None: v += acc[name] # Parse out +/- (or series) for octave if c and c[0] == '+': while c and c[0] == '+': c.pop(0) v += 12 elif c and c[0] == '-': while c and c[0] == '-': c.pop(0) v -= 12 else: if not name: # just for leading '.'s continue if name == 'r': v = midiNotes[name] elif name == '*': v = self.drumTone else: v = MMA.translate.dtable.get(name) """ Swingmode -- This tests for successive 8ths on/off beat If found, the first is converted to 'long' 8th, the 2nd to a 'short' and the offset for the 2nd is adjusted to comp. for the 'long'. """ if gbl.swingMode and l==len8 and \ offset in offBeats and \ lastOffset in onBeats and \ lastOffset in notes: if notes[lastOffset].dur == len8: offset = lastOffset + len81 notes[lastOffset].dur = len81 l = len82 # create a new note[] entry for this offset if not offset in notes: notes[offset] = NoteList(l) # add note event to note[] array notes[offset].nl.append(v) notes[offset].velocity.append( self.adjustVolume(velocity, offset)) notes[offset].defaultVel = velocity # needed for addHarmony() lastOffset = offset offset += l if offset <= barEnd: if self.endTilde: error("Tilde at end of bar has no effect") else: if self.endTilde: self.endTilde[0] = offset - barEnd else: warning("%s, end of last note overlaps end of bar by %2.3f " "beat(s)." % (self.name, (offset - barEnd) / float(gbl.BperQ))) return notes