-- | Absolute pitch representation.
module Music.Pitch.Absolute
  (
  -- * Absolute pitch representation
  Hertz(..),
  -- FreqRatio(..),
  -- Octaves,
  Cents,
  Fifths,
  
  -- * HasFrequency class
  HasFrequency(..),
  -- octaves,
  fifths,
  cents,
  ) where

import           Control.Applicative
import           Control.Monad
import           Data.AdditiveGroup
import           Data.AffineSpace
import           Data.Either
import           Data.Maybe
import           Data.Semigroup
import           Data.VectorSpace

import           Music.Pitch.Literal

{-|
Absolute frequency in Hertz.
-}
newtype Hertz = Hertz { getHertz :: Double }
    deriving (Read, Eq, Enum, Num, Ord, Fractional, Floating, Real, RealFrac)

{-|
Number of pure octaves.

Octaves are a logarithmic representation of frequency such that

> f * (2/1) = frequency (octaves f + 1)
-}
newtype Octaves = Octaves { getOctaves :: Hertz }
  deriving (Read, Show, Eq, Enum, Num, Ord, Fractional, Floating, Real, RealFrac)

{-|
Number of pure fifths.

Fifths are a logarithmic representation of frequency.

> f * (3/2) = frequency (fifths f + 1)

-}
newtype Fifths = Fifths { getFifths :: Hertz }
  deriving (Read, Show, Eq, Enum, Num, Ord, Fractional, Floating, Real, RealFrac)

{-|
Number of cents.

Cents are a logarithmic representation of frequency such that

> f * (2/1) = frequency (cents f + 1200)

-}
newtype Cents = Cents { getCents :: Hertz }
  deriving (Read, Show, Eq, Enum, Num, Ord, Fractional, Floating, Real, RealFrac)


instance Show Hertz where show h = (show (getHertz h)) ++ " Hz"

instance Semigroup Hertz    where (<>) = (*)
instance Semigroup Octaves  where (<>) = (+)
instance Semigroup Fifths   where (<>) = (+)
instance Semigroup Cents    where (<>) = (+)

instance Monoid Hertz       where mempty  = 1 ; mappend = (*)
instance Monoid Octaves     where mempty  = 0 ; mappend = (+)
instance Monoid Fifths      where mempty  = 0 ; mappend = (+)
instance Monoid Cents       where mempty  = 0 ; mappend = (+)

instance AffineSpace Hertz where
  type Diff Hertz = Double
  (.-.) f1 f2 = (getHertz f1) / (getHertz f2)
  (.+^) f x = Hertz $ (getHertz f) * x

class HasFrequency a where
  frequency :: a -> Hertz

instance HasFrequency Hertz where
  frequency = id

instance HasFrequency Octaves where
  frequency (Octaves f) = (2/1) ** f

instance HasFrequency Fifths where
  frequency (Fifths f)  =  (3/2) ** f

instance HasFrequency Cents where
  frequency (Cents f)   =  (2/1) ** (f / 1200)

-- | Convert a frequency to octaves.
octaves :: HasFrequency a => a -> Octaves
octaves a = Octaves $ logBase (2/1) (frequency a)

-- | Convert a frequency to fifths.
fifths :: HasFrequency a => a -> Fifths
fifths a = Fifths $ logBase (3/2) (frequency a)

-- | Convert a frequency to cents.
cents :: HasFrequency a => a -> Cents
cents a = Cents $ logBase (2/1) (frequency a) * 1200