{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} ------------------------------------------------------------------------------------- -- | -- Copyright : (c) Hans Hoglund 2012 -- -- License : BSD-style -- -- Maintainer : hans@hanshoglund.se -- Stability : experimental -- Portability : portable -- -- Generic equal temperament pitch. -- -- Use the type-level numbers from @type-unary@ to construct an temperement dividing -- the octave in any number of steps. Common cases such as 6-TET (whole-tones), -- 12-TET (half-tones) and 24-TET (quarter-tones) are provided for convenience. -- ------------------------------------------------------------------------------------- module Music.Pitch.Equal ( -- * Equal temperament Equal, toEqual, fromEqual, equalToRatio, size, cast, -- ** Synonyms Equal6, Equal12, Equal17, Equal24, Equal36, -- ** Extra type-level naturals N20, N30, N17, N24, N36, ) where import Data.Maybe import Data.Either import Data.Semigroup import Data.VectorSpace import Data.AffineSpace import Control.Monad import Control.Applicative import Music.Pitch.Absolute import TypeUnary.Nat -- Based on Data.Fixed newtype Equal a = Equal { getEqual :: Int } deriving instance {-IsNat a =>-} Eq (Equal a) deriving instance {-IsNat a =>-} Ord (Equal a) instance {-IsNat a =>-} Show (Equal a) where show (Equal a) = show a -- showsPrec d (Equal x) = showParen (d > app_prec) $ -- showString "Equal " . showsPrec (app_prec+1) x -- where app_prec = 10 instance IsNat a => Num (Equal a) where Equal a + Equal b = Equal (a + b) Equal a * Equal b = Equal (a * b) negate (Equal a) = Equal (negate a) abs (Equal a) = Equal (abs a) signum (Equal a) = Equal (signum a) fromInteger = toEqual . fromIntegral instance IsNat a => Semigroup (Equal a) where (<>) = (+) instance IsNat a => Monoid (Equal a) where mempty = 0 mappend = (+) instance IsNat a => AdditiveGroup (Equal a) where zeroV = 0 (^+^) = (+) negateV = negate instance IsNat a => VectorSpace (Equal a) where type Scalar (Equal a) = Equal a (*^) = (*) -- Convenience to avoid ScopedTypeVariables etc getSize :: IsNat a => Equal a -> Nat a getSize _ = nat -- | Size of this type (value not evaluated). -- -- >>> size (undefined :: Equal N2) -- 2 -- -- >>> size (undefined :: Equal N12) -- 12 -- size :: IsNat a => Equal a -> Int size = natToZ . getSize -- TODO I got this part wrong -- -- This type implements limited values (useful for interval *steps*) -- An ET-interval is just an int, with a type-level size (divMod is "separate") -- -- | Create an equal-temperament value. -- toEqual :: IsNat a => Int -> Maybe (Equal a) -- toEqual = checkSize . Equal -- -- -- | Unsafely create an equal-temperament value. -- unsafeToEqual :: IsNat a => Int -> Equal a -- unsafeToEqual n = case toEqual n of -- Nothing -> error $ "Bad equal: " ++ show n -- Just x -> x -- -- checkSize :: IsNat a => Equal a -> Maybe (Equal a) -- checkSize x = if 0 <= fromEqual x && fromEqual x < size x then Just x else Nothing -- -- | Create an equal-temperament value. toEqual :: IsNat a => Int -> Equal a toEqual = Equal -- | Extract an equal-temperament value. fromEqual :: IsNat a => Equal a -> Int fromEqual = getEqual -- | Convert an equal-temeperament value to a frequency ratio. -- -- >>> equalToRatio (7 :: Equal12) -- 1.4983070768766815 -- -- >>> equalToRatio (4 :: Equal12) -- 1.2599210498948732 -- equalToRatio :: IsNat a => Equal a -> Double equalToRatio x = 2**(realToFrac (fromEqual x) / realToFrac (size x)) -- | Safely cast a tempered value to another size. -- -- >>> cast (1 :: Equal12) :: Equal24 -- 2 :: Equal24 -- -- >>> cast (8 :: Equal12) :: Equal6 -- 4 :: Equal6 -- -- >>> (2 :: Equal12) + cast (2 :: Equal24) -- 3 :: Equal12 -- cast :: (IsNat a, IsNat b) => Equal a -> Equal b cast = cast' undefined cast' :: (IsNat a, IsNat b) => Equal b -> Equal a -> Equal b cast' bDummy aDummy@(Equal a) = Equal $ (a * size bDummy) `div` size aDummy type Equal6 = Equal N6 type Equal12 = Equal N12 type Equal17 = Equal N17 type Equal24 = Equal N24 type Equal36 = Equal N36 type N20 = N10 :*: N2 type N30 = N10 :*: N3 type N17 = N10 :+: N7 type N24 = N20 :+: N4 type N36 = N30 :+: N6