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

import MMA.notelen 
import MMA.harmony

import gbl
from   MMA.common import *
from   MMA.pat import PC

class Arpeggio(PC):
    """ Pattern class for an arpeggio track. """

    vtype         = 'ARPEGGIO'
    arpOffset     = -1
    arpDirection  = 1
    lastRange     = 999
    lastDirection = 999
    

    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  = MMA.notelen.getNoteLen(ev[1])
        a.vol       = stoi(ev[2], "Type error in Arpeggio definition")

        return a


    def restart(self):
        self.ssvoice = -1
        self.arpOffset=-1
        self.arpDirection=1
        lastRange     = 999
        lastDirection = 999

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

        Called from self.bar()

        """

        sc = self.seq

        direct = self.direction[sc]
        if direct != self.lastDirection:
            self.arpOffset=-1
            self.arpDirection=1
            self.lastDirection = direct

        range = self.chordRange[sc]
        if range != self.lastRange:
            self.arpOffset=-1
            self.arpDirection=1
            self.lastRange = range

        for p in pattern:
            tb = self.getChordInPos(p.offset, ctable)

            if tb.arpeggioZ:
                continue

            if direct == 'DOWN':
                self.arpDirection = -1

            if self.chordLimit:
                tb.chord.limit(self.chordLimit)

            if self.compress[sc]:
                tb.chord.compress()

            if self.invert[sc]:
                tb.chord.invert(self.invert[sc])

            # This should be optimized, it recreates the chord for every pattern.
            # Problem is that one would need to check all the LIMIT, COMPRESS, etc
            # settings each for each bar as well, so it's probably just as easy to
            # leave it as is. Besides, this works.

            ln = range
            o = 0
            ourChord = []
            while ln >= 1:
                for a in tb.chord.noteList:
                    ourChord.append(a+o)
                ln -= 1
                o += 12

            if ln > 0 and ln < 1:      # for fractional  lengths
                ln = int(tb.chord.noteListLen * ln)
                if ln < 2:   # important, min of 2 notes in arp.
                    ln=2
                for a in tb.chord.noteList[:ln]:
                    ourChord.append(a+o)

            if direct == 'BOTH':
                if self.arpOffset < 0:
                    self.arpOffset = 1
                    self.arpDirection = 1
                elif self.arpOffset >= len(ourChord):
                    self.arpOffset = len(ourChord)-2
                    self.arpDirection = -1

            elif direct == 'UP':
                if self.arpOffset >= len(ourChord) or self.arpOffset < 0:
                    self.arpOffset = 0
                    self.arpDirection = 1

            elif direct == 'DOWN':
                if self.arpOffset < 0 or self.arpOffset >= len(ourChord):
                    self.arpOffset = len(ourChord)-1
                    self.arpDirection = -1

            if direct == 'RANDOM':
                note  = random.choice(ourChord)
            else:
                note = ourChord[self.arpOffset]

            self.arpOffset += self.arpDirection

            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]:
                h = MMA.harmony.harmonize(self.harmony[sc], note, ourChord)
                
                strumOffset = self.getStrum(sc)
            
                for n in h:
                    self.sendNote(
                        p.offset + strumOffset,
                        self.getDur(p.duration),
                        self.adjustNote(n),
                        self.adjustVolume(p.vol * self.harmonyVolume[sc], -1) )
                    
                    strumOffset += self.getStrum(sc)

            tb.chord.reset()    # important, other tracks chord object