-- |  The Interval module implements diatonic intervals.

module Music.Diatonic.Interval (
    Interval(
        Unison,Min2nd,Maj2nd,Min3rd,Maj3rd,Perf4th,
        Perf5th,Min6th,Maj6th,Min7th,Maj7th
      ), 
      compound, octave, min9th, maj9th, perf11th, min13th, maj13th,
      augment, diminish, 
      steps, semitones
  ) where


import Music.Diatonic.Quality
import Music.Diatonic.Equivalence



-- | Use these constructors to create 'Interval's. To alter them, use the 'diminish' or 'augment' functions.
data Interval = Unison | Min2nd | Maj2nd | Min3rd | Maj3rd | Perf4th
              | Perf5th | Min6th | Maj6th | Min7th | Maj7th 
              | Aug Interval | Dim Interval | Compound Interval
              deriving (Eq)


instance Qual Interval where
  quality i | i `elem` [Maj2nd, Maj3rd, Maj6th, Maj7th] = Major
  quality i | i `elem` [Min2nd, Min3rd, Min6th, Min7th] = Minor
  quality i | i `elem` [Unison, Perf4th, Perf5th] = Perfect
  quality (Aug i) = Augmented 
  quality (Dim i) = Diminished 
  quality (Compound i) = quality i 


instance Show Interval where
  show i = showq i ++ shown
    where shown = show . (+ 1) . steps $ i
          showq (Compound i) = showq i
          showq (Aug i@(Aug _)) = "A" ++ showq i
          showq (Dim i@(Dim _)) = "d" ++ showq i
          showq (Aug i) = "A" ++ (tail . showq $ i)
          showq (Dim i) = "d" ++ (tail . showq $ i)
          showq i = case quality i of
                      Major -> "M"
                      Minor -> "m"
                      Perfect -> "P"


instance Equiv Interval where 
  equiv i1 i2 = ((semitones i1 `mod` 12 == (semitones i2 `mod` 12)) && ((steps i1 `mod` 7) == (steps i2 `mod` 7)))



-- | Augments an 'Interval' by a semitone. The interval type remains the same.
augment :: Interval -> Interval
augment Min2nd = Maj2nd ; augment Min3rd = Maj3rd
augment Min6th = Maj6th ; augment Min7th = Maj7th
augment (Compound i) = compound . augment $ i
augment (Dim i) = i
augment i = Aug i


-- | Diminishes an 'Interval' by a semitone. The interval type remains the same.
diminish :: Interval -> Interval
diminish Maj2nd = Min2nd ; diminish Maj3rd = Min3rd
diminish Maj6th = Min6th ; diminish Maj7th = Min7th
diminish (Compound i) = compound . diminish $ i
diminish (Aug i) = i
diminish i = Dim i


-- | Returns the number of scale steps in an 'Interval'.
steps :: Interval -> Int
steps Unison  = 0 ;  steps Min2nd  = 1  ; steps Maj2nd  = 1   ; steps Min3rd = 2 
steps Maj3rd  = 2 ;  steps Perf4th = 3  ; steps Perf5th = 4   ; steps Min6th = 5 
steps Maj6th  = 5 ;  steps Min7th  = 6  ; steps Maj7th  = 6   ;
steps (Aug i) = steps i
steps (Dim i) = steps i
steps (Compound i) = 7 + steps i


-- | Returns the number of semitones in an 'Interval'.
semitones :: Interval -> Int
semitones Unison = 0   ; semitones Min2nd  = 1  ; semitones Maj2nd  = 2   ; semitones Min3rd = 3 
semitones Maj3rd = 4   ; semitones Perf4th = 5  ; semitones Perf5th = 7   ; semitones Min6th = 8 
semitones Maj6th = 9   ; semitones Min7th  = 10 ; semitones Maj7th  = 11  ;
semitones (Aug i) = semitones i + 1
semitones (Dim i) = semitones i - 1
semitones (Compound i) = 12 + semitones i


-- | Creates compound interval (adds an 'octave') to the specified 'Interval'
compound :: Interval -> Interval
compound = Compound 


octave :: Interval
octave = compound Unison


min9th :: Interval
min9th = compound Min2nd


maj9th :: Interval
maj9th = compound Maj2nd


perf11th :: Interval
perf11th = compound Perf4th


min13th :: Interval
min13th = compound Min6th


maj13th :: Interval
maj13th = compound Maj6th