fadno-1.1.3: src/Fadno/Notation.hs
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Fadno.Notation where
import GHC.Generics
import Data.Semigroup
import Data.String
import Data.Default
import Fadno.Note
import Control.Lens hiding (pre)
import Data.Typeable
import Data.Ratio
import Data.Sequence (Seq,fromList)
import Data.Foldable
import Test.HUnit
import Data.List
import Data.Maybe
-- valid time sig denoms
data Quanta = Q2|Q4|Q8|Q16|Q32|Q64
deriving (Eq,Show,Ord,Enum,Bounded,Typeable)
qToInt :: Quanta -> Int
qToInt = (2^) . succ . fromEnum
qFromInt :: Integral i => i -> Maybe Quanta
qFromInt = fmap toEnum . (`elemIndex` [2,4,8,16,32,64])
data TimeSignature = TimeSignature { _tsLength :: Int, _tsUnit :: Quanta }
deriving (Eq,Ord)
instance Show TimeSignature where
show (TimeSignature l u) = show l ++ "/:" ++ show u
makeLenses ''TimeSignature
class HasTimeSignature a where timeSignature :: Lens' a (Maybe TimeSignature)
(/:) :: Int -> Quanta -> TimeSignature
(/:) = TimeSignature
-- PPQ: valid midi divisions, named after equivalent Quantum
-- as in, "1 means ..."; PQ4 is "1 means quarter note"
data PPQ = PQ4|PQ8|PQ16|PQ32|PQ64|PQ128|PQ256
deriving (Eq,Show,Ord,Enum,Bounded)
-- convert to midi division value
ppqDiv :: Integral a => PPQ -> a
ppqDiv = (2^) . fromEnum
-- Compute duration of TS
tsToRatio :: TimeSignature -> Rational
tsToRatio (TimeSignature n d) = fromIntegral n % fromIntegral (qToInt d)
-- Derive TS from duration, with 1 denominator implying Q4
tsFromRatio :: Rational -> Maybe TimeSignature
tsFromRatio r = toTs (if d == 1 then 4 else (if n == 1 then 2 else 1))
where toTs m = (fromIntegral (n * m) /:) <$> qFromInt (d * m)
d = denominator r
n = numerator r
tsFromRatio' :: TimeSignature -> Rational -> Maybe TimeSignature
tsFromRatio' (TimeSignature _ src) = fmap adjust . tsFromRatio where
adjust t@(TimeSignature n d) | src <= d = t
| otherwise = (n * m) /: src
where qd = qToInt d
qs = qToInt src
m = qs `div` qd
-- | Duration iso, from Integral to Rational, given PPQ
ratioPPQ :: Integral a => PPQ -> Iso' a Rational
ratioPPQ p = iso toRat toInt where
ppq4 = ppqDiv p * 4
toRat i = fromIntegral i % fromIntegral ppq4
toInt r = truncate (r * toRational ppq4)
-- | Adapt a type to its HasXXX "Maybe Lens'"
adaptHas :: Lens' a (Maybe a)
adaptHas f s = fromMaybe s <$> f (Just s)
-- | Adapt a non-Maybe lens to the HasXXX "Maybe Lens'"
adaptHasLens :: Lens' s a -> Lens' s (Maybe a)
adaptHasLens l f s = fmap (maybe s (\a -> set l a s)) (f (Just (view l s)))
-- | Adapt a type that does NOT support the HasXXX feature.
adaptHasNot :: Lens' s (Maybe a)
adaptHasNot f s = fmap (const s) (f Nothing)
-- | Tied notes.
data Tie = TStart | TStop | TBoth
deriving (Eq,Bounded,Enum,Ord,Show)
makeLenses ''Tie
class HasTie a where tie :: Lens' a (Maybe Tie)
instance HasTie Tie where tie = adaptHas
instance HasTie (Note p d) where tie = adaptHasNot
-- | Slurred notes.
data Slur = SStart | SStop
deriving (Eq,Bounded,Enum,Ord,Show)
makeLenses ''Slur
class HasSlur a where slur :: Lens' a (Maybe Slur)
instance HasSlur Slur where slur = adaptHas
-- | Note articulations.
data Articulation = Staccato | Accent
deriving (Eq,Show,Bounded,Enum,Ord)
class HasArticulation a where articulation :: Lens' a (Maybe Articulation)
instance HasArticulation Articulation where articulation = adaptHas
-- | Bar rehearsal mark.
newtype RehearsalMark = RehearsalMark { _rehearsalText :: String }
deriving (Eq,Ord,IsString,Generic,Semigroup,Monoid,Default)
makeLenses ''RehearsalMark
instance Show RehearsalMark where show = show . _rehearsalText
class HasRehearsalMark a where rehearsalMark :: Lens' a (Maybe RehearsalMark)
instance HasRehearsalMark RehearsalMark where rehearsalMark = adaptHas
-- | Musical direction.
newtype Direction = Direction { _directionText :: String }
deriving (Eq,Ord,IsString,Generic,Semigroup,Monoid,Default)
makeLenses ''Direction
instance Show Direction where show = show . _directionText
class HasDirection a where direction :: Lens' a (Maybe Direction)
instance HasDirection Direction where direction = adaptHas
-- | Barline.
data Barline = Double | Final
deriving (Eq,Show,Ord,Generic)
class HasBarline a where barline :: Lens' a (Maybe Barline)
instance HasBarline Barline where barline = adaptHas
data Repeats = RStart | REnd | RBoth
deriving (Eq,Show,Ord,Generic)
class HasRepeats a where repeats :: Lens' a (Maybe Repeats)
instance HasRepeats Repeats where repeats = adaptHas
data Clef = TrebleClef | BassClef | AltoClef | PercClef
deriving (Eq,Show,Ord,Generic)
makeLenses ''Clef
class HasClef a where clef :: Lens' a (Maybe Clef)
instance HasClef Clef where clef = adaptHas
-- | Part identifier, prefers 'Num' or 'IsString' values.
newtype Part a = Part { _partIdx :: a }
deriving (Eq,Generic,Ord,Functor,Bounded,Foldable,Traversable,Real,Num,IsString)
makeLenses ''Part
instance (Show a) => Show (Part a) where show = show._partIdx
class HasPart a b | a -> b where part :: Lens' a (Maybe (Part b))
-- | Lensy show of a Maybe field, given a 'Getter' and its name.
mshow :: (Show a) => Getter s (Maybe a) -> String -> s -> String
mshow l n = maybe "" (\v -> " & " ++ n ++ " ?~ " ++ show v) . view l
-- | 'concatMap' show functions with a prelude.
mshows :: s -> String -> [s -> String] -> String
mshows s pre = (pre ++) . concatMap ($ s)
-- Example types.
-- | Note with notations.
data Note' p d = Note' {
_nNote :: Note p d
, _nTie :: Maybe Tie
, _nSlur :: Maybe Slur
, _nArticulation :: Maybe Articulation
} deriving (Eq,Generic)
makeLenses ''Note'
instance HasNote (Note' p d) p d where
note = nNote
fromNote = note' . view note
instance HasTie (Note' p d) where tie = nTie
instance HasSlur (Note' p d) where slur = nSlur
instance HasArticulation (Note' p d) where articulation = nArticulation
instance (Show p, Show d) => Show (Note' p d) where
show n = mshows n ("note' (" ++ show (view nNote n) ++ ")")
[mshow tie "tie"
,mshow slur "slur"
,mshow articulation "articulation"
]
-- | Note smart ctor, used in 'Show'.
note' :: Note p d -> Note' p d
note' n = Note' n Nothing Nothing Nothing
testNote :: Note' [Int] Int
testNote = note' ([60]|:2) & tie ?~ TStart & articulation ?~ Accent
-- | Bar as list of notes, with notations.
data Bar n = Bar {
_bNotes :: Seq n
, _bRehearsalMark :: Maybe RehearsalMark
, _bDirection :: Maybe Direction
, _bBarline :: Maybe Barline
, _bRepeats :: Maybe Repeats
, _bTimeSignature :: Maybe TimeSignature
, _bClef :: Maybe Clef
} deriving (Eq,Generic,Functor,Foldable,Traversable)
makeLenses ''Bar
instance Default (Bar n) where def = bar []
instance Snoc (Bar n) (Bar n) n n where
_Snoc = prism (\(b,n) -> over bNotes (review _Snoc . (,n)) b) $
\b -> case firstOf _Snoc (view bNotes b) of
Nothing -> Left (def :: Bar n)
(Just (as,a)) -> Right (set bNotes as b,a)
instance Cons (Bar n) (Bar n) n n where
_Cons = prism (\(n,b) -> over bNotes (review _Cons . (n,)) b) $
\b -> case firstOf _Cons (view bNotes b) of
Nothing -> Left (def :: Bar n)
(Just (a,as)) -> Right (a,set bNotes as b)
instance HasRehearsalMark (Bar n) where rehearsalMark = bRehearsalMark
instance HasDirection (Bar n) where direction = bDirection
instance HasBarline (Bar n) where barline = bBarline
instance HasTimeSignature (Bar n) where timeSignature = bTimeSignature
instance HasClef (Bar n) where clef = bClef
instance HasRepeats (Bar n) where repeats = bRepeats
instance (Show n) => Show (Bar n) where
show b = mshows b ("bar " ++ show (toList $ view bNotes b))
[mshow rehearsalMark "rehearsalMark"
,mshow direction "direction"
,mshow barline "barline"
,mshow repeats "repeat"
,mshow timeSignature "timeSignature"
,mshow clef "clef"
]
instance Semigroup (Bar n) where
a <> b = over bNotes (<> view bNotes b) a
instance Monoid (Bar n) where
mempty = def
mappend = (<>)
-- | Bar smart ctor, used in 'Show'.
bar :: [n] -> Bar n
bar ns = Bar (fromList ns) Nothing Nothing Nothing Nothing Nothing Nothing
testBar :: Bar (Note [Int] Int)
testBar = bar [[60]|:2,[62]|:1] & timeSignature ?~ TimeSignature 4 Q4 & direction ?~ "Softly"