{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies               #-}

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : portable
--
-- Absolute pitch representation.
--
-- The canonical pitch representation is frequency in Hertz (Hz).
--
-- For conversion, see 'HasFrequency'.
--
-------------------------------------------------------------------------------------

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, Show, Eq, Enum, Num, Ord, Fractional, Floating, Real, RealFrac)

{-
-- |
-- A ratio between two different (Hertz) frequencies.
--
newtype FreqRatio = FreqRatio { getFreqRatio :: Double }
  deriving (Read, Show, 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 octaves.
--
-- 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)

-- |
-- 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)


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 AdditiveGroup FreqRatio where
  zeroV   = 1
  (^+^)   = (*)
  negateV f = 1 / f

instance VectorSpace FreqRatio where
  type Scalar FreqRatio = Double
  (*^) x f = FreqRatio ((getFreqRatio f) ** x)

instance AffineSpace Hertz where
  type Diff Hertz = FreqRatio
  (.-.) f1 f2 = FreqRatio $ (getHertz f1) / (getHertz f2)
  (.+^) p f = Hertz $ (getHertz p) * (getFreqRatio f)
-}
instance AdditiveGroup Hertz where
  zeroV   = 1
  (^+^)   = (*)
  negateV f = 1 / f

instance VectorSpace Hertz where
  type Scalar Hertz = Hertz
  (*^) x f = Hertz ((getHertz f) ** getHertz x)

instance AffineSpace Hertz where
  type Diff Hertz = Hertz
  (.-.) = (-)
  (.+^) = (+)

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)

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

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

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

--
-- For convenience. TODO problematic for these reasons:
--
--  * Confusing as _P8+m2 is not the same as _P8<>m2
--  * Does not work with HasPitch (up, down...) etc as we do not have an affine/vector space pair
--
-- Can we fix this with newtype wrappers?
--
instance IsInterval Hertz where
  fromInterval (IntervalL (o,d,c)) = (2**fromIntegral o) * (r !! fromIntegral c)
    where
      r = [
        1/1,
        (8*2)/15, 9/8,      (2*3)/5, 5/4,     (2*2)/3,
        10/7,
        3/2,      (4*2)/5,  5/3,     (2*8)/9, 15/8
        ]