music-sibelius-1.3.1: src/Music/Sibelius.hs
{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, NoMonomorphismRestriction,
ConstraintKinds, FlexibleContexts #-}
module Music.Sibelius where
import Data.Aeson
import Control.Applicative
import Data.Aeson.Types(parse, Parser)
import Music.Prelude.StringQuartet
import qualified Music.Score as Score
import Data.ByteString.Lazy(ByteString)
import qualified Data.ByteString.Lazy as B
import qualified Data.HashMap.Strict as HashMap
setTitle :: String -> Score a -> Score a
setTitle = setMeta "title"
setComposer :: String -> Score a -> Score a
setComposer = setMeta "composer"
setInformation :: String -> Score a -> Score a
setInformation = setMeta "information"
setMeta :: String -> String -> Score a -> Score a
setMeta _ _ = id
data SibScore = SibScore {
scoreTitle :: String,
scoreComposer :: String,
scoreInformation :: String,
scoreStaffHeight :: Double,
scoreTransposing :: Bool,
scoreStaves :: [SibStaff],
scoreSystemStaff :: ()
}
deriving (Eq, Ord, Show)
instance FromJSON SibScore where
parseJSON (Object v) = SibScore
<$> v .: "title"
<*> v .: "composer"
<*> v .: "information"
<*> v .: "staffHeight"
<*> v .: "transposing"
<*> v .: "staves"
-- TODO
<*> return ()
data SibStaff = SibStaff {
staffBars :: [SibBar],
staffName :: String,
staffShortName :: String
}
deriving (Eq, Ord, Show)
instance FromJSON SibStaff where
parseJSON (Object v) = SibStaff
<$> v .: "bars"
<*> v .: "name"
<*> v .: "shortName"
data SibBar = SibBar {
barElements :: [SibElement]
}
deriving (Eq, Ord, Show)
instance FromJSON SibBar where
parseJSON (Object v) = SibBar
<$> v .: "elements"
data SibElement
= SibElementText SibText
| SibElementClef SibClef
| SibElementSlur SibSlur
| SibElementCrescendoLine SibCrescendoLine
| SibElementDiminuendoLine SibDiminuendoLine
| SibElementTimeSignature SibTimeSignature
| SibElementKeySignature SibKeySignature
| SibElementTuplet SibTuplet
| SibElementChord SibChord
deriving (Eq, Ord, Show)
instance FromJSON SibElement where
parseJSON x@(Object v) = case HashMap.lookup "type" v of
-- TODO
Just "text" -> error "JsElementText"
Just "clef" -> error "SibElementClef"
Just "slur" -> error "SibElementSlur"
Just "cresc" -> error "SibElementCrescendoLine"
Just "dim" -> error "SibElementDiminuendoLine"
Just "time" -> error "SibElementTimeSignature"
Just "key" -> error "SibElementKeySignature"
Just "tuplet" -> error "SibElementTuplet"
Just "chord" -> SibElementChord <$> parseJSON x
_ -> mempty
data SibText = SibText {
textVoice :: Int,
textPosition :: Int,
textText :: String,
textStyle :: Int
}
deriving (Eq, Ord, Show)
instance FromJSON SibText where
parseJSON = error "Not implemented (instance FromJSON SibText)"
data SibClef = SibClef {
clefVoice :: Int,
clefPosition :: Int,
clefStyle :: Int
}
deriving (Eq, Ord, Show)
instance FromJSON SibClef where
parseJSON = error "Not implemented (instance FromJSON SibClef)"
data SibSlur = SibSlur {
slurVoice :: Int,
slurPosition :: Int,
slurDuration :: Int,
slurStyle :: Int
}
deriving (Eq, Ord, Show)
instance FromJSON SibSlur where
parseJSON = error "Not implemented (instance FromJSON SibSlur)"
data SibCrescendoLine = SibCrescendoLine {
crescVoice :: Int,
crescPosition :: Int,
crescDuration :: Int,
crescStyle :: Int
}
deriving (Eq, Ord, Show)
instance FromJSON SibCrescendoLine where
parseJSON = error "Not implemented (instance FromJSON SibCrescendoLine)"
data SibDiminuendoLine = SibDiminuendoLine {
dimVoice :: Int,
dimPosition :: Int,
dimDuration :: Int,
dimStyle :: Int
}
deriving (Eq, Ord, Show)
instance FromJSON SibDiminuendoLine where
parseJSON = error "Not implemented (instance FromJSON SibDiminuendoLine)"
data SibTimeSignature = SibTimeSignature {
timeVoice :: Int,
timePosition :: Int,
timeValue :: Rational,
timeIsCommon :: Bool,
timeIsAllaBreve :: Bool
}
deriving (Eq, Ord, Show)
instance FromJSON SibTimeSignature where
parseJSON = error "Not implemented (instance FromJSON SibTimeSignature)"
data SibKeySignature = SibKeySignature {
keyVoice :: Int,
keyPosition :: Int,
keyMajor :: Bool,
keySharps :: Int,
keyIsOpen :: Bool
}
deriving (Eq, Ord, Show)
instance FromJSON SibKeySignature where
parseJSON = error "Not implemented (instance FromJSON SibKeySignature)"
data SibTuplet = SibTuplet {
tupletVoice :: Int,
tupletPosition :: Int,
tupletDuration :: Int,
tupletPlayedDuration :: Int,
tupletValue :: Rational
}
deriving (Eq, Ord, Show)
instance FromJSON SibTuplet where
parseJSON (Object v) = SibTuplet
<$> v .: "voice"
<*> v .: "position"
<*> v .: "duration"
<*> v .: "playedDuration"
<*> (v .: "value" >>= \[x,y] -> return $ x / y) -- TODO unsafe
data SibArticulation
= UpBow
| DownBow
| Plus
| Harmonic
| Marcato
| Accent
| Tenuto
| Wedge
| Staccatissimo
| Staccato
deriving (Eq, Ord, Show, Enum)
readSibArticulation :: String -> Maybe SibArticulation
readSibArticulation = go
where
go "upbow" = Just UpBow
go "downBow" = Just DownBow
go "plus" = Just Plus
go "harmonic" = Just Harmonic
go "marcato" = Just Marcato
go "accent" = Just Accent
go "tenuto" = Just Tenuto
go "wedge" = Just Wedge
go "staccatissimo" = Just Staccatissimo
go "staccato" = Just Staccato
go _ = Nothing
data SibChord = SibChord {
chordPosition :: Int,
chordDuration :: Int,
chordVoice :: Int,
chordArticulations :: [SibArticulation], -- TODO
chordSingleTremolos :: Int,
chordDoubleTremolos :: Int,
chordAcciaccatura :: Bool,
chordAppoggiatura :: Bool,
chordNotes :: [SibNote]
}
deriving (Eq, Ord, Show)
instance FromJSON SibChord where
parseJSON (Object v) = SibChord
<$> v .: "position"
<*> v .: "duration"
<*> v .: "voice"
<*> doThing (v .: "articulations")
<*> v .: "singleTremolos"
<*> v .: "doubleTremolos"
<*> v .: "acciaccatura"
<*> v .: "appoggiatura"
<*> v .: "notes"
doThing = (=<<) (sequence . fmap (returnMaybe readSibArticulation))
data SibNote = SibNote {
notePitch :: Int,
noteDiatonicPitch :: Int,
noteAccidental :: Int,
noteTied :: Bool,
noteStyle :: Int
}
deriving (Eq, Ord, Show)
instance FromJSON SibNote where
parseJSON (Object v) = SibNote
<$> v .: "pitch"
<*> v .: "diatonicPitch"
<*> v .: "accidental"
<*> v .: "tied"
<*> v .: "style"
fromSib :: IsSibelius a => SibScore -> Score a
fromSib (SibScore title composer info staffH transp staves systemStaff) =
foldr (</>) mempty $ fmap fromSibStaff staves
-- TODO meta information
fromSibStaff :: IsSibelius a => SibStaff -> Score a
fromSibStaff (SibStaff bars name shortName) =
removeRests $ scat $ fmap fromSibBar bars
-- TODO bar length hardcoded
-- TODO meta information
fromSibBar :: IsSibelius a => SibBar -> Score (Maybe a)
fromSibBar (SibBar elems) =
fmap Just (pcat $ fmap fromSibElem elems) <> return Nothing^*1
fromSibElem :: IsSibelius a => SibElement -> Score a
fromSibElem = go where
go (SibElementChord chord) = fromSibChord chord
-- TODO tuplet, key/time signature, line and text support
fromSibChord :: IsSibelius a => SibChord -> Score a
fromSibChord (SibChord pos dur voice ar strem dtrem acci appo notes) =
setTime $ setDur $ every setArt ar $ tremolo strem $ pcat $ fmap fromSibNote notes
where
setTime = delay (fromIntegral pos / 1024)
setDur = stretch (fromIntegral dur / 1024)
setArt Marcato = marcato
setArt Accent = accent
setArt Tenuto = tenuto
setArt Staccato = staccato
setArt a = error $ "fromSibChord: Unsupported articulation" ++ show a
-- TODO tremolo and appogiatura/acciaccatura support
fromSibNote :: IsSibelius a => SibNote -> Score a
fromSibNote (SibNote pitch di acc tied style) =
(if tied then fmap beginTie else id)
$ modifyPitches (+ (fromIntegral pitch - 60)) def
where
def = c
readSib :: IsSibelius a => FilePath -> IO (Score a)
readSib path = fmap (either (\x -> error $ "Could not read score" ++ x) id) $ readSibEither path
readSibMaybe :: IsSibelius a => FilePath -> IO (Maybe (Score a))
readSibMaybe path = fmap (either (const Nothing) Just) $ readSibEither path
readSibEither :: IsSibelius a => FilePath -> IO (Either String (Score a))
readSibEither path = do
json <- B.readFile path
return $ fmap fromSib $ eitherDecode' json
type IsSibelius a = (
IsPitch a,
HasPart' a,
Enum (Part a),
HasPitch a,
Num (Score.Pitch a),
HasTremolo a,
HasArticulation a,
Tiable a
)
main = do
result <- readSibEither "test.json"
case result of
Left e -> putStrLn $ "Error: " ++ e
Right x -> do
writeMidi "test.mid" $ asScore $ f x
openXml $ f x
-- openLy $ f x
-- let score = fromSib $ fromJust $ decode' json
-- openLy score
where
f = id
-- f = retrograde
-- f x = stretch (1/4) $ times 2 x |> times 2 (stretch 2 x)
fromJust (Just x) = x
returnMaybe :: MonadPlus m => (a -> Maybe b) -> a -> m b
returnMaybe f = mmapMaybe f . return
every :: (a -> b -> b) -> [a] -> b -> b
every f x = foldr (.) id (fmap f x)