-- | Provides pitch spelling.
module Music.Pitch.Common.Spell
(
        -- ** Spelling
        -- * About
        -- $semitonesAndSpellings

        -- * Spelling type
        Spelling,
        spell,
        spelled,

        -- ** Standard spellings
        modally,
        usingSharps,
        usingFlats,
) where

import           Data.AffineSpace
import           Data.VectorSpace
import           Control.Lens

import           Music.Pitch.Absolute
import           Music.Pitch.Alterable
import           Music.Pitch.Augmentable
import           Music.Pitch.Common.Interval
import           Music.Pitch.Common.Number
import           Music.Pitch.Common.Pitch
import           Music.Pitch.Common.Semitones
import           Music.Pitch.Literal

-- $semitonesAndSpellings
--
-- TODO document better
--
-- The `semitones` function retrieves the number of Semitones in a pitch, for example
--
-- > semitones :: Interval -> Semitones
-- > semitones major third = 4
--
-- Note that semitones is surjetive. We can define a non-deterministic function `spellings`
--
-- > spellings :: Semitones -> [Interval]
-- > spellings 4 = [majorThird, diminishedFourth]
--
-- /Law/
--
-- > map semitones (spellings a) = replicate n a    for all n > 0
--
-- /Lemma/
--
-- > map semitones (spellings a)

-- |
-- A spelling provide a way of notating a semitone interval such as 'tritone'.
--
-- Examples:
--
-- > spell usingSharps tritone   == _A4
-- > spell usingFlats  tritone   == d5
-- > spell modally     tone      == _M2
--
type Spelling = Semitones -> Number


-- |
-- Spell an interval using the given 'Spelling'.
--
spell :: HasSemitones a => Spelling -> a -> Interval
spell spelling x = let
  -- TODO use Steps etc to remove fromIntegral
  (octaves, steps) = semitones x `divMod` 12
  num  = fromIntegral (spelling steps)
  diff = fromIntegral steps - fromIntegral (diatonicToChromatic num)
  in (\a b -> (fromIntegral a, fromIntegral b)^.interval') diff num ^+^ _P8^*(fromIntegral octaves)
    where
      diatonicToChromatic = go
        where
          go 0 = 0
          go 1 = 2
          go 2 = 4
          go 3 = 5
          go 4 = 7
          go 5 = 9
          go 6 = 11

-- |
-- Flipped version of 'spell'. To be used infix, as in:
--
-- > d5 `spelled` usingSharps
--
spelled :: HasSemitones a => a -> Spelling -> Interval
spelled = flip spell

-- |
-- Spell using the most the most common accidentals. Double sharps and flats are not
-- preserved.
--
-- This spelling is particularly useful for modal music where the tonic is C.
--
-- > c cs d eb e f fs g gs a bb b
--
modally :: Spelling
modally = go
  where
    go 0  = 0
    go 1  = 0
    go 2  = 1
    go 3  = 2
    go 4  = 2
    go 5  = 3
    go 6  = 3
    go 7  = 4
    go 8  = 4
    go 9  = 5
    go 10 = 6
    go 11 = 6

-- |
-- Spell using sharps. Double sharps and flats are not preserved.
--
-- > c cs d ds e f fs g gs a as b
--
usingSharps :: Spelling
usingSharps = go
  where
    go 0  = 0
    go 1  = 0
    go 2  = 1
    go 3  = 1
    go 4  = 2
    go 5  = 3
    go 6  = 3
    go 7  = 4
    go 8  = 4
    go 9  = 5
    go 10 = 5
    go 11 = 6

-- |
-- Spell using flats. Double sharps and flats are not preserved.
--
-- > c db d eb e f gb g ab a bb b
--
usingFlats :: Spelling
usingFlats = go
  where
    go 0  = 0
    go 1  = 1
    go 2  = 1
    go 3  = 2
    go 4  = 2
    go 5  = 3
    go 6  = 4
    go 7  = 4
    go 8  = 5
    go 9  = 5
    go 10 = 6
    go 11 = 6