# GNU Solfege - eartraining for GNOME
# Copyright (C) 2000, 2001, 2002  Tom Cato Amundsen
#
# 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

import types, random

class InvalidNotenameException:
    def __init__(self, n):
        self.m_notename = n
    def __str__(self):
        return self.m_notename

class MusicalPitch:
    LOWEST_STEPS = -28
    HIGHEST_STEPS = 47
    def __init__(self, n=None, a=None, o=None, null_value=0):
        """Will accept five types of arguments:
        0.   n is None, set to "c"
        1.   n is a MusicalPitch. This is necessary to make clones of this
             object.
        2.   n is a string, like cis  Des  g'' Will ignore argument a and o
        3.   n is an integer representing the tone.
        4.   three integers

             c,,,, is lowest: m_octave_i == -4, steps() == -28
             g'''''' is highest: m_octave_i = 6, steps() == 46

        """
        if n is None:
            self.m_notename_i = self.m_accidental_i = self.m_octave_i = 0
        elif isinstance(n, MusicalPitch):
            self.m_notename_i = n.m_notename_i
            self.m_accidental_i = n.m_accidental_i
            self.m_octave_i = n.m_octave_i
        elif type(n) == types.IntType and a is None and o is None:
            self.set_from_midiint(n)
        elif type(n) == types.StringType:
            self.set_from_notename(n)
        else:
            assert type(n) == type(a) == type(o) == types.IntType
            self.m_notename_i = n
            # 0 is natural, 1 is sharp etc.
            self.m_accidental_i = a
            # 0 is c     1 is c'    -1 is c, 
            self.m_octave_i = o
    def transpose_by_musicalpitch(self, P):
        """Silly function used by mpd/parser.py and company
        (d') transposes up one major second.
        """
        tra = P.semitone_pitch() - 60
        old_p = self.semitone_pitch()
        self.m_notename_i = self.m_notename_i + P.m_notename_i
        self.m_accidental_i = self.m_accidental_i + P.m_accidental_i
        if self.m_notename_i > 6:
            self.m_notename_i = self.m_notename_i - 7
            self.m_octave_i = self.m_octave_i + 1
        self.m_octave_i = self.m_octave_i + P.m_octave_i - 1
        if self.semitone_pitch()-old_p < tra:
            self.m_accidental_i = self.m_accidental_i + 1
        elif self.semitone_pitch()-old_p > tra:
            self.m_accidental_i = self.m_accidental_i - 1
        return self
    def steps(self):
        """0 == lille c
        """
        return self.m_notename_i + self.m_octave_i * 7
    def semitone_pitch(self):
        return [0, 2, 4, 5, 7, 9, 11][self.m_notename_i] + \
               self.m_accidental_i + self.m_octave_i * 12 + 48
    def set_from_midiint(self, midiint):
        self.m_octave_i = (midiint-48)/12
        self.m_notename_i = {0:0, 1:0, 2:1, 3:1, 4:2, 5:3, 6:3, 7:4, 8:4,
                             9:5, 10:5, 11:6}[midiint % 12]
        self.m_accidental_i = midiint-(self.m_octave_i+4)*12 \
                              -[0, 2, 4, 5, 7, 9, 11][self.m_notename_i]
    def set_from_notename(self, notename):
        tmp = notename
        self.m_accidental_i = self.m_octave_i = 0
        while notename[-1] in ["'", ","]:
            if notename[-1] == "'":
                self.m_octave_i = self.m_octave_i + 1
            elif notename[-1] == ",":
                self.m_octave_i = self.m_octave_i - 1
            notename = notename[:-1]
        if notename[:2] == 'es':
            notename = 'ees' + notename[2:]
        if notename[:2] == 'as':
            notename = 'aes' + notename[2:]
        while notename[-2:] == 'es':
            self.m_accidental_i = self.m_accidental_i -1
            notename = notename[:-2]
        while notename[-2:] == 'is':
            self.m_accidental_i = self.m_accidental_i + 1
            notename = notename[:-2]
        try:
            self.m_notename_i = ['c', 'd', 'e', 'f', 'g', 'a', 'b'].index(notename)
        except:
           raise InvalidNotenameException(tmp)
    def randomize(self, lowest, highest):
        """
        lowest and highest can be an integer, string or a MusicalPitch instance
        """
        assert type(lowest) == type(highest)
        if type(lowest) == types.StringType:
            lowest = MusicalPitch(lowest).semitone_pitch()
        if type(highest) == types.StringType:
            highest = MusicalPitch(highest).semitone_pitch()
        self.set_from_midiint(random.randint(int(lowest), int(highest)))
        return self
    def __add__(self, i):
        """
        MusicalPitch + integer = MusicalPitch
        MusicalPitch + Intervall = MusicalPitch
        """
        if type(i) == type(0):
            v = self.semitone_pitch()
            assert 0 <= v + i < 128
            return MusicalPitch(v+i)
        elif i.__class__.__name__ == 'Intervall':#isinstance(i, intervall.Intervall):
            r = MusicalPitch(self)
            _p = r.semitone_pitch()
            r.m_notename_i = r.m_notename_i + i.m_intervall * i.m_dir
            r.m_octave_i = r.m_octave_i + r.m_notename_i / 7 + i.m_octave * i.m_dir
            r.m_notename_i = r.m_notename_i % 7
            _diff = r.semitone_pitch() - _p
            r.m_accidental_i = r.m_accidental_i + (i.get_intvalue() - _diff)
            # to avoid notenames like ciscisciscis :
            if r.m_accidental_i > 2:
               r.m_accidental_i = r.m_accidental_i - 2
               r.m_notename_i = r.m_notename_i + 1
               if r.m_notename_i == 7:
                   r.m_notename_i = 0
                   r.m_octave_i = r.m_octave_i + 1
            if r.m_accidental_i < -2:
               r.m_accidental_i = r.m_accidental_i + 2
               r.m_notename_i = r.m_notename_i - 1
               if r.m_notename_i == -1:
                   r.m_notename_i = 6
                   r.m_octave_i = r.m_octave_i - 1
            return r
        else:
            raise "mpd.MusicalPitch.__add__"
    def __sub__(self, i):
        """
        MusicalPitch - MusicalPitch = integer
        MusicalPitch - integer = MusicalPitch
        """
        if isinstance(i, MusicalPitch):
            return self.semitone_pitch() - i.semitone_pitch()
        assert type(i) == types.IntType
        v = self.semitone_pitch()
        assert 0 <= v - i < 128
        return MusicalPitch(v-i)
    def __int__(self):
        return self.semitone_pitch()
    def __cmp__(self, B):
        if (self is None or self is None):
            return -1
        diff = self - B
        if diff < 0:
            return -1
        elif diff > 0:
            return 1
        else:
            return 0
    def __str__(self):
        return "(MusicalPitch %s)" % self.str()
    def notename(self):
        s = ['c', 'd', 'e', 'f', 'g', 'a', 'b'][self.m_notename_i]
        assert -3 < self.m_accidental_i < 3, self.m_accidental_i
        s = s + ['eses', 'es', '', 'is', 'isis'][self.m_accidental_i+2]
        return s
    def str(self):
        s = self.notename()
        if self.m_octave_i < 0:
            s = s + ',' * (-self.m_octave_i)
        if self.m_octave_i > 0:
            s = s + "'" * self.m_octave_i
        return s


if __name__ == "__main__":
    a = MusicalPitch("g")
    b = MusicalPitch("f")
    r = a - b
    print "%s - %s =" % (a, b), r
    r = a - 2
    print "%s - 2 =" % a, r
    r = a + 3
    print "%s + 3 =" % a, r
    print "a < b", a < b
    print "a > b", a > b
