# 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

"""
The Intervall class is a class that is used to do math with
intervalls and musical pitches. You use this class if you
need to know the difference between a augmented second and
a minor third. If you don't require this you can probably
ise the simpler function utils.int_to_intervallname

>>> from musicalpitch import MusicalPitch
>>> a=MusicalPitch("c'")
>>> b=Intervall("m2")
>>> (a+b).str()
"des'"
>>> for t in ('d', 'm', 'M', 'a'):
...     for n in ('2', '3', '6', '7', '9', '10'):
...         i = Intervall(t+n)
...         assert i.get_short_name() == t+n
"""
import string, re

class Intervall:
    """
    The intervall is internally a intervall less than octave
    pluss n octaves. The data variables:
      m_dir
      m_octave
      m_intervall
      m_mod
    should NOT be touched by anyone, except MusicalPitch.__add__
    """
    def __init__(self, iname=None):
        self.m_dir = 1 # value as to be 1 or -1 for initialised obj
        self.m_octave = 0 # 0, 1, 2, 3 etc
        self.m_intervall = 0 # 0:unison, 1:seond, ... 6: septim
        # unison:              dim   perfect   aug
        # second:  -2:dim -1:minor 0:major   1:aug
        # third:      dim    minor   major     aug
        # fourth:              dim   perfect   aug
        # fifth:               dim   perfect   aug
        # sixth:      dim    minor   major     aug
        # seventh:    dim    minor   major     aug
        self.m_mod = 0
        if iname:
            self.set_from_string(iname)
    def errorcheck(self):
        assert 0 <= self.m_intervall <= 6
        assert -2 <= self.m_mod <= 1 # should be increased to -3 <= x <= 2
        assert self.m_octave >= 0
        assert self.m_dir in (-1, 1)
    def _set(self, dir, intervall, mod, octave):
        self.m_dir = dir
        self.m_intervall = intervall
        self.m_mod = mod
        self.m_octave = octave
        if __debug__:
            self.errorcheck()
        return self
    def set_from_int(self, i):
        """It returns self to allow chaining: set_from_int(4).pretty_name()
        """
        if i < 0:
            self.m_dir = -1
        else:
            self.m_dir = 1
        self.m_octave = abs(i) / 12
        self.m_mod, self.m_intervall = (
               (0, 0),          # unison
               (-1, 1), (0, 1), # second
               (-1, 2), (0, 2), # third
               (0, 3),          # fourth
               (-1, 4),         # dim 5, tritonus
               (0, 4),          # fifth
               (-1, 5), (0, 5), # sixth
               (-1, 6), (0, 6))[abs(i) % 12] # seventh
        return self
    def set_from_string(self, s):
        """
        second  m2 M2
        third   m3 M3
        fourth  4
        fifth   d5 5
        sixth   m6 M6
        seventh m7 M7
        octave  8
        none    m9 M9
        decim   m10 M10
        """
        s = string.strip(s)
        if s[0] == "-":
            self.m_dir = -1
            s = s[1:]
        else:
            self.m_dir = 1
        n, i = re.match("(m|M|d|a*)(\d*)", s).groups()
        if int(i) <= 7:
            self.m_intervall = int(i) - 1
            self.m_octave = 0
        else:
            self.m_intervall = int(i) - 8
            self.m_octave = 1
        if self.m_intervall in (1, 2, 5, 6):
            self.m_mod = {'d': -2, 'm': -1, 'M': 0, 'a': 1}[n]
        elif self.m_intervall in (4, 5):
            self.m_mod = {'d': -1, 'p': 0, '': 0, 'a': 1}[n]
    def get_intvalue(self):
        if __debug__:
            self.errorcheck()
        return ([0, 2, 4, 5, 7, 9, 11][self.m_intervall] + self.m_octave * 12 + self.m_mod) * self.m_dir
    def __str__(self):
        if __debug__:
            self.errorcheck()
        s = "(Intervall %i %imod %io" % (self.m_intervall, self.m_mod, self.m_octave)
        if self.m_dir == -1:
            s = s + " down)"
        else:
            s = s + " up)"
        return s
    def get_short_name(self):
        """
        DON'T USE THIS FUNCTION ANYWHERE!!!!!!!!
        The format for the names will change without notice, so the
        return value of this function is *ONLY* human readable.
        DON'T LET ANY CODE DEPEND ON THE FORMAT OF THE RETURN VALUE
        """
        n = ""
        if self.m_octave == 1 and self.m_intervall in (1, 2):
            n = n + {-2:'d', -1:'m', 0:'M', 1:'a'}[self.m_mod]
            n = n + {1: '9', 2:'10'}[self.m_intervall]
            if self.m_dir == -1:
                n = n + " down"
            return n
        elif self.m_octave == 1 and self.m_intervall == 0:
            n = n + {-1:'d', 0:'', 1:'a'}[self.m_mod]
            n = n + "8"
            if self.m_dir == -1:
                n = n + " down"
            return n
        elif self.m_intervall in (1, 2, 5, 6):
            n = n + {-2:'d', -1:'m', 0:'M', 1:'a'}[self.m_mod]
            n = n + {1:'2', 2:'3', 5:'6', 6:'7'}[self.m_intervall]
        elif self.m_intervall in (0, 3, 4, 7):
            n = n + {-1:'d', 0:'', 1:'a'}[self.m_mod]
            n = n + {0:'1', 3:'4', 4:'5', 7:'8'}[self.m_intervall]
        if self.m_octave > 0:
            n = n + "+%i octave" % self.m_octave
        if self.m_dir == -1:
            n = n + " down"
        return n

def _test():
    import doctest
    return doctest.testmod(intervall)

if __name__=="__main__":
    _test()
    import sys
    sys.exit()

if __name__=="__main__":
    import musicalpitch
    print "testing the intervall module"
    i = Intervall()
    for iv, intervall, modifier, octave, dir in (
          (5, 3, 0, 0, 1), (1, 1, -1, 0, 1), (2, 1, 0, 0, 1),
          (-1, 1, -1, 0, -1), (-2, 1, 0, 0, -1)):
        i.set_from_int(iv)
        print "set_from_int(%i)" % iv
        if i.m_intervall == intervall:
            print "intervall:%i, " % intervall,
        else:
            print "intervall: failed:%i!=%i, " % (i.m_intervall, intervall),
        if i.m_mod == modifier:
            print "mod:%i, " % modifier,
        else:
            print "mod: failed:%i=!%i, " % (i.m_mod, modifier),
        if i.m_octave == octave:
            print "octave:%i, " % octave,
        else:
            print "octave: failed:%i=!%i, " % (i.m_octave, octave),
        if i.m_dir == dir:
            print "dir:%i, " % dir,
        else:
            print "dir: failed:%i=!%i, " % (i.m_dir, dir),
        print
    for old, new, i in (("c'", "d'", 2), ("e'", "fis'", 2), ("e", "b,", -5),
           ("e", "bes,", -6), ("e", "ais,", -6)):
        n = musicalpitch.MusicalPitch(old) + Intervall().set_from_int(i)
        if n.str() == new:
            print "ok: %s + %i == %s" % (old, i, new)
        else:
            print "%s + %i == %s failed:" % (old, i, new), n.str()
    for x in ('m2', 'M2', 'm3', 'M3', '4', 'd5', '5', 'm6', 'M6',
              'm7', 'M7', '8', 'm9', 'M9', 'm10', 'M10'):
        i = Intervall()
        i.set_from_string(x)
        print x, i
