
# patScale.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>

"""

import random

from   MMA.harmony import harmonize
from   MMA.notelen import getNoteLen
import gbl
from   MMA.common import *
from   MMA.pat import PC, seqBump


class Scale(PC):
    """ Pattern class for a Scale track. """

    vtype = 'SCALE'

    lastNote = -1
    lastChord = None
    lastStype = None
    lastDirect = 1
    lastRange = 0
    sOffset = 0
    notes = None
    dirfact = 1

    def getPgroup(self, ev):
        """ Get group for scale patterns.

            Fields - start, length, volume
        """


        if len(ev) != 3:
            error("There must be at exactly 3 items in each group "
                  "in a Scale 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 Scale definition")


        return a

    def setScaletype(self, ln):
        """ Set scale type. """

        ln = self.lnExpand(ln, "ScaleType")
        tmp = []

        for n in ln:
            n = n.upper()
            if not n in ( 'CHROMATIC', 'AUTO'):
                error("Unknown %s ScaleType. Only Chromatic and Auto are valid" % self.name)
            tmp.append(n)

        self.scaleType = seqBump(tmp)

        if gbl.debug:
            print "Set %s ScaleType to " % self.name,
            printList(ln)


    def restart(self):
        self.ssvoice = -1
        self.lastNote = -1
        self.lastChord = None
        self.lastStype = None
        self.lastDirect = 1
        self.lastRange = 0
        self.sOffset = 0
        self.notes = None
        self.dirfact = 1

    def trackBar(self, pattern, ctable):
        """ Do a scale bar.

            Called from self.bar()
        """

        sc = self.seq
        direct = self.direction[sc]
        unify = self.unify[sc]

        # If the range or direction has changed, we just start
        # with a new scale.


        t = self.chordRange[sc]
        if t != self.lastRange:
            self.lastRange = t
            self.lastChord = None

        if self.lastDirect != direct:
            self.lastDirect = direct
            self.lastChord = None

        for p in pattern:

            tb = self.getChordInPos(p.offset, ctable)

            if tb.scaleZ:
                continue


            thisChord = tb.chord.tonic + tb.chord.chordType
            stype = self.scaleType[sc]

            if thisChord != self.lastChord or stype != self.lastStype:
                self.lastChord = thisChord
                self.lastStype = stype

                if stype == 'CHROMATIC':
                    notelist = [ tb.chord.rootNote + x for x in range(0,12)]
                
                else:
                    notelist = list(tb.chord.scaleList)

                """ Get the current scale and append enuf copies
                together for RANGE setting. If Range happens
                to be 0 or 1 we end up with a single copy.
                """

                ln=self.chordRange[sc]    # RANGE 1...x (def. == 1)

                o=0
                self.notes = []

                while ln >= 1:
                    for a in notelist:
                        self.notes.append(a+o)
                    o+=12
                    ln-=1

                if ln>0 and ln<1:  # for fractional scale lengths
                    ln = int(len(notelist) * ln)
                    if ln < 2:   # important, must be at least 2 notes in a scale
                        ln=2
                    for a in notelist[:ln]:
                        self.notes.append(a+o)

                if direct == 'DOWN':
                    self.dirfact = -1
                    if self.lastNote == -1:
                        self.sOffset = len(self.notes)-1
                else:
                    self.sOffset = 0

                if self.lastNote > -1:
                    if self.lastNote in self.notes:
                        self.sOffset = self.notes.index(self.lastNote)

                    else:
                        self.sOffset=len(self.notes)-1
                        for     i, a in enumerate(self.notes):
                            if a>self.lastNote:
                                self.sOffset = i
                                break


            # Keep offset into note list in range

            # only > end of list if BOTH or UP

            if self.sOffset >= len(self.notes):
                if direct == 'BOTH':
                    self.dirfact = -1
                    self.sOffset = len(self.notes)-2
                else:        ## UP
                    self.sOffset = 0

            # only < start of list if DOWN or BOTH

            elif self.sOffset < 0:
                if direct == 'BOTH':
                    self.dirfact = 1
                    self.sOffset = 1
                else:        ## DOWN
                    self.sOffset = len(self.notes)-1

            if direct == 'RANDOM':
                note = random.choice(self.notes)
            else:
                note = self.notes[self.sOffset]
                self.sOffset += self.dirfact

            self.lastNote = note

            if not self.harmonyOnly[sc]:
                self.sendNote(
                    p.offset,
                    self.getDur(p.duration),
                    self.adjustNote(note),
                    self.adjustVolume(p.vol, p.offset))


            if self.harmony[sc]:
                ch = self.getChordInPos(p.offset, ctable).chord.noteList
                h = harmonize(self.harmony[sc], note, ch)
                for n in h:
                    self.sendNote(
                        p.offset,
                        self.getDur(p.duration),
                        self.adjustNote(n),
                        self.adjustVolume(self.harmonyVolume[sc] * p.vol, -1) )


