
# 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("AllGrooves: requires arguments.")
    
    origSlot = MMA.parse.gmagic    # save the current groove
    grooveDefineDo(origSlot)

    action = ln[0].upper()   # either a command or a trackname

    if len(ln)>1:
        trAction = ln[1].upper()   
    else:
        trAction = ''

    sfuncs = MMA.parse.simpleFuncs
    tfuncs = MMA.parse.trackFuncs

    counter = 0

    for g in glist:   # do command for each groove in memory
        grooveDo(g)     # active existing groove

        if action in sfuncs:        # test for non-track command and exe.
            sfuncs[action](ln[1:])
            counter += 1

        else:                       # not a non-track, see if track command
            if not trAction:
                error("AllGrooves: No command for assumed trackname %s." % action)

            name = action  # remember 'action' is ln[0]. Using 'name' just makes it clearer
            if not name in gbl.tnames:  # skip command if track doesn't exist
                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 original state
    
    if not counter:
        warning("No tracks affected with '%s'" % ' '.join(ln))
        
    else:
        if gbl.debug:
            print "AllGrooves: %s tracks modified." % counter

