music-score-1.7: src/Music/Time/Meta.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
-------------------------------------------------------------------------------------
-- |
-- Copyright : (c) Hans Hoglund 2012
--
-- License : BSD-style
--
-- Maintainer : hans@hanshoglund.se
-- Stability : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Provides meta-information.
--
-- Each score supports an unlimited number of 'Reactive' meta-values.
--
-- This is more or less based on Diagrams styles, which is in turn based
-- on XMonad.
--
-------------------------------------------------------------------------------------
module Music.Time.Meta (
-- * Attributes
IsAttribute,
Attribute,
-- ** Creating attributes
wrapAttr,
wrapTAttr,
unwrapAttr,
unwrapTAttr,
-- * Meta-data
Meta,
-- ** Creating meta-data
toMeta,
fromMeta,
-- ** The HasMeta class
HasMeta(..),
applyMeta,
) where
-- import Control.Applicative
-- import Control.Arrow
import Control.Lens hiding (transform)
import Control.Monad.Plus
import Data.Foldable (Foldable)
import qualified Data.Foldable as F
import qualified Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
-- import Data.Monoid.WithSemigroup
import Data.Semigroup
import Data.Set (Set)
import qualified Data.Set as Set
import Data.String
-- import Data.Traversable (Traversable)
-- import qualified Data.Traversable as T
import Data.Typeable
import Data.Void
-- import Music.Score.Part
import Music.Time.Internal.Util
import Music.Time.Reverse
import Music.Time.Split
import Music.Time.Transform
type IsAttribute a = (Typeable a, Monoid a, Semigroup a)
-- | An existential wrapper type to hold attributes.
data Attribute :: * where
Attribute :: IsAttribute a => a -> Attribute
TAttribute :: (Transformable a, IsAttribute a) => a -> Attribute
wrapAttr :: IsAttribute a => a -> Attribute
wrapAttr = Attribute
unwrapAttr :: IsAttribute a => Attribute -> Maybe a
unwrapAttr (Attribute a) = cast a
wrapTAttr :: (Transformable a, IsAttribute a) => a -> Attribute
wrapTAttr = TAttribute
unwrapTAttr :: (Transformable a, IsAttribute a) => Attribute -> Maybe a
unwrapTAttr (TAttribute a) = cast a
instance Semigroup Attribute where
(Attribute a1) <> a2 = case unwrapAttr a2 of
-- Nothing -> a2
Nothing -> error "Attribute.(<>) mismatch"
Just a2' -> Attribute (a1 <> a2')
(TAttribute a1) <> a2 = case unwrapTAttr a2 of
-- Nothing -> a2
Nothing -> error "Attribute.(<>) mismatch"
Just a2' -> TAttribute (a1 <> a2')
instance Transformable Attribute where
transform _ (Attribute a) = Attribute a
transform s (TAttribute a) = TAttribute (transform s a)
instance Splittable Attribute where
split _ x = (x,x)
instance Reversible Attribute where
rev = id
-- Meta is Transformable because the contents of the map is transformable
newtype Meta = Meta (Map String Attribute)
deriving (Transformable, Reversible, Splittable)
instance Semigroup Meta where
Meta s1 <> Meta s2 = Meta $ Map.unionWith (<>) s1 s2
-- | The empty meta contains no attributes; composition of metas is
-- a union of attributes; if the two metas have attributes of the
-- same type they are combined according to their semigroup
-- structure.
instance Monoid Meta where
mempty = Meta Map.empty
mappend = (<>)
instance HasMeta Meta where
meta = ($)
--
-- TODO
-- Temporarily disabling part specific meta-events
-- The API still works, but all parts are merged together
--
toMeta :: forall a b . ({-HasPart' a, -}IsAttribute b, Transformable b) => Maybe a -> b -> Meta
toMeta partId a = Meta $ Map.singleton key $ wrapTAttr a
where
key = ty ++ pt
pt = ""
-- pt = show $ fmap getPart partId
ty = show $ typeOf (undefined :: b)
fromMeta :: forall a b . ({-HasPart' a, -}IsAttribute b, Transformable b) => Maybe a -> Meta -> Maybe b
fromMeta partId (Meta s) = (unwrapTAttr =<<) $ Map.lookup key s
-- Note: unwrapAttr should never fail
where
key = ty ++ pt
pt = ""
-- pt = show $ fmap getPart partId
ty = show . typeOf $ (undefined :: b)
-- | Type class for things which have meta-information.
class HasMeta a where
-- | Apply meta-information by combining it (on the left) with the
-- existing meta-information.
meta :: Lens' a Meta
applyMeta :: HasMeta a => Meta -> a -> a
applyMeta m = (meta <>~ m)