fadno-1.1.9.1: 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.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 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 :: forall a . Integral a => PPQ -> Iso' a Rational
ratioPPQ p = iso toRat toInt where
ppq4 = ppqDiv p * (4 :: a)
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
-- | StrongAccent TODO implement after fixing fadno-xml #7
| Tenuto
| DetachedLegato
| Staccatissimo
| Spiccato
| Scoop
| Plop
| Doit
| Falloff
| BreathMark
| Caesura
| Stress
| Unstress
| SoftAccent
| OtherArticulation String
deriving (Eq,Show,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
-- | Adapts musicxml Beams where beams are labeled "1" for eighth beam etc,
-- where instead it is a list implying the first element is eighth etc.
data Beam
= BeamBegin
| BeamContinue
| BeamEnd
| BeamForwardHook
| BeamBackwardHook
deriving (Eq,Bounded,Enum,Ord,Show)
class HasBeams a where beams :: Lens' a [Beam]
instance HasBeams [Beam] where beams = ($)
class HasVoice a where voice :: Lens' a (Maybe String)
-- | 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.
type Note' p d = Noted (Note p d)
data Noted n = Noted {
_nNote :: n
, _nTie :: Maybe Tie
, _nSlur :: Maybe Slur
, _nArticulation :: Maybe Articulation
, _nBeams :: [Beam]
, _nVoice :: Maybe String
} deriving (Eq,Generic,Functor,Foldable,Traversable)
makeLenses ''Noted
instance HasNote (Note' p d) p d where
note = nNote
fromNote = note' . view note
instance HasTie (Noted n) where tie = nTie
instance HasSlur (Noted n) where slur = nSlur
instance HasArticulation (Noted n) where articulation = nArticulation
instance HasBeams (Noted n) where beams = nBeams
instance HasVoice (Noted n) where voice = nVoice
instance (Show n) => Show (Noted n) where
show n = mshows n (show (view nNote n))
[mshow tie "tie"
,mshow slur "slur"
,mshow articulation "articulation"
,const $ case view beams n of
[] -> ""
bs -> " & beams .= " ++ show bs
,mshow voice "voice"
]
-- | Note smart ctor, used in 'Show'.
note' :: Note p d -> Note' p d
note' = noted
noted :: n -> Noted n
noted n = Noted n Nothing 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"