{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE NoMonomorphismRestriction  #-}
{-# LANGUAGE StandaloneDeriving         #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012-2014
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Provides a representation of harmonics. 
--
-------------------------------------------------------------------------------------

module Music.Score.Harmonics (
        -- * Harmonics
        HasHarmonic(..),
        HarmonicT(..),
        harmonic,
        artificial,
  ) where

import           Control.Applicative
import           Control.Comonad
import           Control.Lens            hiding (transform)
import           Data.Foldable
import           Data.Foldable
import           Data.Functor.Couple
import           Data.Ratio
import           Data.Semigroup
import           Data.Typeable
import           Data.Word

import           Music.Dynamics.Literal
import           Music.Pitch.Alterable
import           Music.Pitch.Augmentable
import           Music.Pitch.Literal
import           Music.Score.Part
import           Music.Score.Phrases
import           Music.Time


-- 0 for none, positive for natural, negative for artificial
class HasHarmonic a where
  setNatural :: Bool -> a -> a
  setHarmonic :: Int -> a -> a

-- (isNatural, overtone series index where 0 is fundamental)
newtype HarmonicT a = HarmonicT { getHarmonicT :: Couple (Any, Sum Int) a }
  deriving (
    Eq, Show, Ord, Functor, Foldable, Typeable, 
    Applicative, Monad, Comonad
    )

instance HasHarmonic a => HasHarmonic (b, a) where
  setNatural b = fmap (setNatural b)
  setHarmonic n = fmap (setHarmonic n)

instance HasHarmonic a => HasHarmonic (Couple b a) where
  setNatural b = fmap (setNatural b)
  setHarmonic n = fmap (setHarmonic n)

instance HasHarmonic a => HasHarmonic [a] where
  setNatural b = fmap (setNatural b)
  setHarmonic n = fmap (setHarmonic n)

instance HasHarmonic a => HasHarmonic (Score a) where
  setNatural b = fmap (setNatural b)
  setHarmonic n = fmap (setHarmonic n)

instance Wrapped (HarmonicT a) where
  type Unwrapped (HarmonicT a) = Couple (Any, Sum Int) a
  _Wrapped' = iso getHarmonicT HarmonicT

instance Rewrapped (HarmonicT a) (HarmonicT b)

instance HasHarmonic (HarmonicT a) where
  setNatural  b = over (_Wrapped'._Wrapped') $ \((_,n),x)   -> ((Any b,n),x)
  setHarmonic n = over (_Wrapped'._Wrapped') $ \((nat,_),x) -> ((nat,Sum n),x)

-- Lifted instances
deriving instance Num a => Num (HarmonicT a)
deriving instance Fractional a => Fractional (HarmonicT a)
deriving instance Floating a => Floating (HarmonicT a)
deriving instance Enum a => Enum (HarmonicT a)
deriving instance Bounded a => Bounded (HarmonicT a)
deriving instance (Num a, Ord a, Real a) => Real (HarmonicT a)
deriving instance (Real a, Enum a, Integral a) => Integral (HarmonicT a)

-- |
-- Make all notes natural harmonics on the given overtone (1 for octave, 2 for fifth etc).
-- Sounding pitch is unaffected, but notated output is transposed automatically.
--
harmonic :: HasHarmonic a => Int -> a -> a
harmonic n = setNatural True . setHarmonic n
-- TODO verify this can actually be played

-- |
-- Make all notes natural harmonics on the given overtone (1 for octave, 2 for fifth etc).
-- Sounding pitch is unaffected, but notated output is transposed automatically.
--
artificial :: HasHarmonic a => a -> a
artificial =  setNatural False . setHarmonic 3