-- | Semitones and enharmonic equivalence.
module Music.Pitch.Common.Semitones
(
        -- * Types
        -- ** Octaves
        Octaves,

        -- ** Semitones
        Semitones,
        HasSemitones(..),
        semitone,
        tone,
        ditone,
        tritone,
        isSemitone,
        isTone,
        isTritone,

        -- * Enharmonic equivalence
        (=:=),
        (/:=),
) where

import Music.Pitch.Common.Types

-- |
-- Class of intervals that has a number of 'Octaves'.
--
class HasOctaves a where
  -- |
  -- Returns the number of octaves spanned by an interval.
  --
  -- The number of octaves is negative if and only if the interval is
  -- negative.
  --
  -- Examples:
  --
  -- > octaves (perfect unison)  =  0
  -- > octaves (d5 ^* 4)         =  2
  -- > octaves (-_P8)            =  -1
  --
  octaves :: a -> Octaves

instance HasOctaves Octaves where { octaves = id }


{-
-- |
-- Class of intervals that has a number of 'Steps'.
--
class HasSteps a where
  -- |
  -- The number of steps is always in the range /0 ≤ x < 12/.
  --
  -- Examples:
  --
  -- > octaves (perfect unison)  =  0
  -- > octaves (d5 ^* 4)         =  2
  -- > octaves (-m7)             =  -1
  --
  steps :: a -> Steps
-}

-- |
-- Class of intervals that can be converted to a number of 'Semitones'.
--
class HasSemitones a where

  -- |
  -- Returns the number of semitones spanned by an interval.
  --
  -- The number of semitones is negative if and only if the interval is
  -- negative.
  --
  -- >>> semitones (perfect unison)
  -- 0
  -- >>> semitones tritone
  -- 6
  -- >>> semitones d5
  -- 6
  -- >>> semitones (-_P8)
  -- -12
  --
  semitones :: a -> Semitones

instance HasSemitones ChromaticSteps where { semitones = id }


semitone, tone, ditone, tritone :: Semitones

-- | Precisely one semitone.
semitone = 1

-- | Precisely one whole tone, or two semitones.
tone     = 2

-- | Precisely two whole tones, or four semitones.
ditone   = 4

-- | Precisely three whole tones, or six semitones.
tritone  = 6


isTone, isSemitone, isTritone :: HasSemitones a => a -> Bool

-- | Returns true iff the given interval spans one semitone.
isSemitone  = (== semitone) . abs . semitones

-- | Returns true iff the given interval spans one whole tone (two semitones).
isTone      = (== tone)     . abs . semitones

-- | Returns true iff the given interval spans three whole tones (six semitones).
isTritone   = (== tritone)  . abs . semitones



infix 4 =:=
infix 4 /:=

-- |
-- Enharmonic equivalence.
--
-- >>> asInterval _A2 == m3
-- False
-- >>> asInterval _A2 =:= m3
-- True
--
(=:=) :: HasSemitones a => a -> a -> Bool
a =:= b = semitones a == semitones b

-- |
-- Enharmonic non-equivalence.
--
-- >>> asInterval _A2 /= m3
-- True
-- >>> asInterval _A2 /:= m3
-- False
--
(/:=) :: HasSemitones a => a -> a -> Bool
a /:= b = semitones a /= semitones b