VocalEasel/mma/MMA/grooves.py
Matthias Neeracher fb48baaf09 Updated to MMA 1.4
2009-05-17 22:34:44 +00:00

449 lines
11 KiB
Python

# grooves.py
"""
This module is an integeral part of the program
MMA - Musical Midi Accompaniment.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Bob van der Poel <bob@mellowood.ca>
"""
""" Groove storage. Each entry in glist{} has a keyname
of a saved groove.
lastGroove and currentGroove are used by macros
"""
import os
import MMA.midi
import MMA.notelen
import MMA.auto
import MMA.volume
import MMA.parse
import MMA.seqrnd
import MMA.docs
import gbl
from MMA.common import *
glist = {}
aliaslist = {}
lastGroove = '' # name of the last groove (used by macros)
currentGroove = '' # name of current groove (used by macros)
""" groovesList[] holds a list of grooves to use when the
groove command is used with several args. ie, when
we use either:
Groove 2 groove1 groove2 groove3
or
Groove groove1 groove2 groove3
in both cases, the names of the grooves are stored in groovesList[]
"""
groovesList = None
groovesCount = 0
def grooveDefine(ln):
""" Define a groove.
Current settings are assigned to a groove name.
"""
if not len(ln):
error("Use: DefGroove Name")
slot=ln[0].upper()
# Slot names can't contain a '/' (reserved) or be an integer (used in groove select).
if '/' in slot:
error("The '/' is not permitted in a groove name")
if slot.isdigit():
error("Invalid slot name '%s'. Cannot be only digits" % slot)
if slot in aliaslist:
error("Can't define groove name %s, already defined as an alias for %s." \
% (slot, aliaslist[slot]))
grooveDefineDo(slot)
if gbl.debug:
print "Groove settings saved to '%s'." % slot
if gbl.makeGrvDefs:
MMA.auto.updateGrooveList(slot)
if len(ln) > 1:
MMA.docs.docDefine(ln)
def grooveDefineDo(slot):
for n in gbl.tnames.values():
n.saveGroove(slot)
glist[slot] = {
'SEQSIZE': gbl.seqSize,
'SEQRNDWT': MMA.seqrnd.seqRndWeight[:],
'QPERBAR': gbl.QperBar,
'SEQRND': MMA.seqrnd.seqRnd[:],
'TIMESIG': MMA.midi.timeSig.get(),
'81': MMA.notelen.noteLenTable['81'],
'82': MMA.notelen.noteLenTable['82'],
'SWINGMODE': gbl.swingMode ,
'SWINGSKEW': gbl.swingSkew,
'VRATIO': (MMA.volume.vTRatio, MMA.volume.vMRatio)}
def grooveAlias(ln):
""" Create an alias name for an existing groove. """
global aliaslist
if len(ln) != 2:
error("GrooveAlias needs exactly 2 args: GrooveName AliasName.")
a = ln[0].upper()
g = ln[1].upper()
if not g in glist:
error("GrooveAlias: Groove %s has not been defined." % ln[0])
aliaslist[a]=g
def groove(ln):
""" Select a previously defined groove. """
global groovesList, groovesCount, lastGroove, currentGroove
if not ln:
error("Groove: needs agrument(s)")
tmpList =[]
if ln[0].isdigit():
wh=stoi(ln[0])
if wh<1:
error("Groove selection must be > 0, not '%s'" % wh)
ln=ln[1:]
else:
wh = None
for slot in ln:
slot = slot.upper()
if slot == "/":
if len(tmpList):
slot=tmpList[-1]
else:
error("A previous groove name is needed before a '/'")
if not slot in glist: # convert alias to real groove name
for a,r in aliaslist.iteritems():
if slot == a:
slot = r
break
if not slot in glist:
if gbl.debug:
print "Groove '%s' not defined. Trying auto-load from libraries" \
% slot
l=MMA.auto.loadGrooveDir(slot) # name of the lib file with groove
if l:
if gbl.debug:
print "Attempting to load groove '%s' from '%s'." % (slot, l)
reportFutureVols()
MMA.parse.usefile([os.path.join(gbl.autoLib, l)])
if not slot in glist:
error("Groove '%s' not found. Have libraries changed "
"since last 'mma -g' run?" % slot)
else:
error("Groove '%s' could not be found in memory or library files" % slot )
tmpList.append(slot)
if not len(tmpList):
error("Use: Groove [selection] Name [...]")
""" If the first arg to list was an int() (ie: 3 groove1 groove2 grooveFoo)
we select from the list. After the selection, we reset the list to be
just the selected entry. This was, if there are multiple groove names without
a leading int() we process the list as groove list changing with each bar.
"""
if wh:
wh = (wh-1) % len(tmpList)
tmpList=tmpList[wh:wh+1]
slot=tmpList[0]
grooveDo(slot)
groovesCount = 0
if len(tmpList)==1:
groovesList=None
else:
groovesList=tmpList
lastGroove = currentGroove
currentGroove = slot
if lastGroove == '':
lastGroove = slot
if gbl.debug:
print "Groove settings restored from '%s'." % slot
def grooveDo(slot):
""" This is separate from groove() so we can call it from
usefile() with a qualified name. """
reportFutureVols()
oldSeqSize = gbl.seqSize
g=glist[slot]
gbl.seqSize = g['SEQSIZE']
MMA.seqrnd.seqRndWeight = g['SEQRNDWT']
gbl.QperBar = g['QPERBAR']
MMA.seqrnd.seqRnd = g['SEQRND']
MMA.midi.timeSig.set( *g['TIMESIG']) # passing tuple as 2 args.
MMA.notelen.noteLenTable['81'] = g['81']
MMA.notelen.noteLenTable['82'] = g['82']
gbl.swingMode = g['SWINGMODE']
gbl.swingSkew = g['SWINGSKEW']
MMA.volume.vTRatio, MMA.volume.vMRatio = g['VRATIO']
for n in gbl.tnames.values():
n.restoreGroove(slot)
""" This is important! Tracks NOT overwritten by saved grooves way
have the wrong sequence length. I don't see any easy way to hit
just the unchanged/unrestored tracks so we do them all.
Only done if a change in seqsize ... doesn't take long to be safe.
"""
if oldSeqSize != gbl.seqSize:
for a in gbl.tnames.values():
a.setSeqSize()
MMA.seqrnd.seqRndWeight = seqBump(MMA.seqrnd.seqRndWeight)
gbl.seqCount = 0
def reportFutureVols():
""" Print warning for pending track cresendos.
We need a seperate func here since the groove() may
parse a new file, which will clear out data before
getting to grooveDo().
Note that the test is for more that one trailing future volume.
This is deliberate ... a construct like:
Chord Cresc ff 1
..somechord
Groove NEW
will leave a future volume on the stack.
"""
volerrs=[]
for n in gbl.tnames.values():
if len(n.futureVols)>1:
volerrs.append(n.name)
n.futureVols = [] # don't want leftover future vols a track level!
if volerrs:
volerrs.sort()
warning("Pending (de)Cresc in %s." % ', '.join(volerrs))
def grooveClear(ln):
""" Delete all previously loaded grooves from memory."""
global groovesList, groovesCount, glist, lastGroove, currentGroove, aliaslist
if ln:
error("GrooveClear does not have any arguments.")
groovesList = {}
aliaslist = {}
groovesCount = 0
try:
a= glist[gmagic]
except:
a=None
glist={}
if a:
glist[gmagic]=a
lastGroove = ''
currentGroove = ''
if gbl.debug:
print "All grooves deleted."
def nextGroove():
""" Handle groove lists. Called from parse().
If there is more than 1 entry in the groove list,
advance (circle). We don't have to qualify grooves
since they were verified when this list was created.
groovesList==None if there is only one groove (or none).
"""
global lastGroove, currentGroove, groovesCount
if groovesList:
groovesCount += 1
if groovesCount > len(groovesList)-1:
groovesCount = 0
slot = groovesList[groovesCount]
if slot != currentGroove:
grooveDo(slot)
lastGroove = currentGroove
currentGroove = slot
if gbl.debug:
print "Groove (list) setting restored from '%s'." % slot
def trackGroove(name, ln):
""" Select a previously defined groove for a single track. """
if len(ln) != 1:
error("Use: %s Groove Name" % name)
slot = ln[0].upper()
if not slot in glist: # convert alias to real groove name
for a,r in aliaslist.iteritems():
if slot == a:
slot = r
break
if not slot in glist:
error("Groove '%s' not defined" % slot)
g=gbl.tnames[name]
g.restoreGroove(slot)
if g.sequence == [None] * len(g.sequence):
warning("'%s' Track Groove has no sequence. Track name error?" % name)
g.setSeqSize()
if gbl.debug:
print "%s Groove settings restored from '%s'." % (name, slot)
def getAlias(al):
""" This is used by the library doc printer to get a list aliases. """
al=al.upper()
l=[]
for a,r in aliaslist.iteritems():
if al == r:
l.append(a.title())
return ', '.join(l)
def allgrooves(ln):
""" Apply a command to all currently defined grooves. """
if not ln:
error("OverRide: requires a directive.")
origSlot = MMA.parse.gmagic # save the current groove
grooveDefineDo(origSlot)
action = ln[0].upper()
if len(ln)>1:
trAction = ln[1].upper()
else:
trAction = ''
sfuncs = MMA.parse.simpleFuncs
tfuncs = MMA.parse.trackFuncs
counter = 0
for g in glist:
grooveDo(g)
if action in sfuncs:
sfuncs[action](ln[1:])
counter += 1
continue
if len(ln) < 2:
error("AllGrooves: No command for assumed trackname %s." % action)
name=action
if not name in gbl.tnames:
continue
if trAction in tfuncs:
tfuncs[trAction](name, ln[2:])
counter += 1
else:
error ("AllGrooves: Not a command: '%s'" % ' '.join(ln))
grooveDefineDo(g) # store the change!!!
grooveDo(origSlot) # restore groove
if not counter:
warning("No tracks affected with '%s'" % ' '.join(ln))
else:
if gbl.debug:
print "AllGrooves: %s tracks modified." % counter