{-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- | -- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style -- -- Maintainer : hans@hanshoglund.se -- Stability : experimental -- Portability : non-portable (TF,GNTD) -- ------------------------------------------------------------------------------------- module Music.Score.Export.Backend ( HasOrdPart, HasDynamic3, HasDynamicNotation, HasArticulation3, HasArticulationNotation, HasBackend(..), HasBackendScore(..), HasBackendNote(..), export ) where import Music.Dynamics.Literal import Music.Pitch.Literal import qualified Codec.Midi as Midi import Control.Comonad (Comonad (..), extract) import Control.Applicative import Data.Colour.Names as Color import Data.Foldable (Foldable) import qualified Data.Foldable import Data.Functor.Couple import Data.Maybe import Data.Monoid.Average import Data.Ratio import Data.Traversable (Traversable, sequenceA) import qualified Data.Music.Lilypond as Lilypond import qualified Data.Music.MusicXml.Simple as MusicXml import Music.Score.Internal.Export hiding (MVoice) import System.Process import Music.Score.Internal.Quantize import qualified Text.Pretty as Pretty import qualified Data.List import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates) import Music.Score.Export.DynamicNotation import Music.Score.Export.ArticulationNotation import Data.Semigroup import Data.Semigroup.Instances import Data.Functor.Context import Music.Time import Music.Score.Dynamics import Music.Score.Articulation import Music.Score.Part type HasDynamic3 a a' a'' = ( HasDynamic' a, HasDynamic' a'', HasDynamic a a', HasDynamic a' a'', HasDynamic a a'' ) type HasDynamicNotation a b c = ( HasDynamic3 a b c, Dynamic b ~ Ctxt (Dynamic a), Dynamic c ~ DynamicNotation, Real (Dynamic a), Part (SetDynamic (Dynamic a) a) ~ Part (SetDynamic DynamicNotation b) ) type HasOrdPart a = (HasPart' a, Ord (Part a)) type HasArticulation3 c d e = ( HasArticulation' c, HasArticulation c d, HasArticulation d e, HasArticulation c e ) type HasArticulationNotation a b c = ( HasArticulation3 a b c, Articulation b ~ Ctxt (Articulation a), Articulation c ~ ArticulationNotation, -- TODO generalize -- Articulated (Articulation a), Real (Separation (Articulation a)), Real (Accentuation (Articulation a)) Articulation a ~ (Average Double, Average Double) ) -- | -- This class defines types and functions for exporting music. It provides the -- primitive types and methods used to implement 'export'. -- -- The backend type @b@ is just a type level tag to identify a specific backend. -- It is typically defined as an empty data declaration. -- -- The actual conversion is handled by the subclasses 'HasBackendScore' and -- 'HasBackendNote', which converts the time structure, and the contained music -- respectively. Thus structure and content are handled separately. -- -- It is often necessary to alter the events based on their surrounding context: for -- examples the beginning and end of spanners and beams depend on surrounding notes. -- The 'BackendContext' type allow 'HasBackendScore' instances to provide context for -- 'HasBackendNote' instances. -- -- @ -- data NoteList -- -- instance HasBackend NoteList where -- type BackendScore NoteList = [] -- type BackendContext NoteList = Identity -- type BackendNote NoteList = [(Sum Int, Int)] -- type BackendMusic NoteList = [(Sum Int, Int)] -- finalizeExport _ = concat -- -- instance HasBackendScore NoteList [a] a where -- exportScore _ = fmap Identity -- -- instance HasBackendNote NoteList a => HasBackendNote NoteList [a] where -- exportNote b ps = mconcat $ map (exportNote b) $ sequenceA ps -- -- instance HasBackendNote NoteList Int where -- exportNote _ (Identity p) = [(mempty ,p)] -- -- instance HasBackendNote NoteList a => HasBackendNote NoteList (DynamicT (Sum Int) a) where -- exportNote b (Identity (DynamicT (d,ps))) = set (mapped._1) d $ exportNote b (Identity ps) -- -- @ -- -- class Functor (BackendScore b) => HasBackend b where -- | External music representation type BackendMusic b :: * -- | Notes, chords and rests, with output handled by 'HasBackendNote' type BackendNote b :: * -- | Score, voice and time structure, with output handled by 'HasBackendScore' type BackendScore b :: * -> * -- | This type may be used to pass context from 'exportScore' to 'exportNote'. -- If the note export is not context-sensitive, 'Identity' can be used. type BackendContext b :: * -> * finalizeExport :: b -> BackendScore b (BackendNote b) -> BackendMusic b -- | -- A class for musical container types with an external representation. -- -- The first type parameter is simply a token representing the external format, -- and the second parameter is the type being represented. In a sense, the first -- parameter saves us from defining a separate class for each external representation, -- so rather than having `HasMidiScore`, `HasMusicXmlScore` and so on, we have -- 'HasBackendScore' 'Midi', 'HasBackendScore' 'MusicXml' and so on. -- class (HasBackend b) => HasBackendScore b s where -- | Type of events in this score type. -- This is generally just the parameterized type in a container, so we have -- -- @ -- BackendScoreEvent (Score a) ~ a -- BackendScoreEvent (Voice a) ~ a -- @ -- -- and so on. -- -- It is defined as a type function so that instances can put constraints on the -- saturated type, rather than being parametric over all note types. -- type BackendScoreEvent b s :: * exportScore :: b -> s -> BackendScore b (BackendContext b (BackendScoreEvent b s)) -- | -- A class for musical event types with an external representation. -- -- The first type parameter is simply a token representing the external format, -- and the second parameter is the type being represented. In a sense, the first -- parameter saves us from defining a separate class for each external representation, -- so rather than having `HasMidiNote`, `HasMusicXmlNote` and so on, we have -- 'HasBackendNote' 'Midi', 'HasBackendNote' 'MusicXml' and so on. -- class (HasBackend b) => HasBackendNote b a where exportNote :: b -> BackendContext b a -> BackendNote b exportChord :: b -> BackendContext b [a] -> BackendNote b exportChord = error "Not implemented: exportChord" -- | -- This is the primitive music export function. -- -- Backend developers are encouraged to provide wrappers on the form 'toX', 'writeX' etc. -- export :: (HasBackendScore b s, HasBackendNote b (BackendScoreEvent b s)) => b -> s -> BackendMusic b export b = finalizeExport b . fmap (exportNote b) . exportScore b