module Music.Parts.Instrument (
Instrument,
fullName,
shortName,
fromMidiProgram,
toMidiProgram,
fromMusicXmlSoundId,
toMusicXmlSoundId,
transposition,
transpositionString,
standardClef,
allowedClefs,
playableRange,
comfortableRange,
gmClef,
gmMidiChannel,
gmScoreOrder,
) where
import Control.Applicative
import Control.Lens (toListOf)
import Data.Aeson (ToJSON (..), FromJSON(..))
import qualified Data.Aeson
import Data.Default
import Data.Functor.Adjunction (unzipR)
import qualified Data.List
import Data.Map (Map)
import qualified Data.Maybe
import Data.Semigroup
import Data.Semigroup.Option.Instances
import Data.Set (Set)
import qualified Data.Set
import Data.Traversable (traverse)
import Data.Typeable
import Text.Numeral.Roman (toRoman)
import Music.Dynamics (Dynamics)
import Music.Parts.Internal.Data (InstrumentDef)
import qualified Music.Parts.Internal.Data as Data
import Music.Pitch
data Instrument
= StdInstrument Int
| OtherInstrument String
instance Show Instrument where
show x = Data.Maybe.fromMaybe "(unknown)" $ fullName x
instance Enum Instrument where
toEnum = StdInstrument
fromEnum (StdInstrument x) = x
fromEnum (OtherInstrument _) = error "Instrument.fromEnum used on unknown instrument"
instance Eq Instrument where
x == y = soundId x == soundId y
instance Ord Instrument where
compare x y = compare (scoreOrder x) (scoreOrder y)
instance Default Instrument where
def = StdInstrument 0
instance ToJSON Instrument where
toJSON (StdInstrument x) = Data.Aeson.object [("midi-instrument", toJSON x)]
toJSON (OtherInstrument x) = Data.Aeson.object [("instrument-id", toJSON x)]
instance FromJSON Instrument where
parseJSON (Data.Aeson.Object v) = do
mi <- v Data.Aeson..:? "midi-instrument"
ii <- v Data.Aeson..:? "instrument-id"
case (mi,ii) of
(Just mi,_) -> return $fromMidiProgram mi
(Nothing,Just ii) -> return $fromMusicXmlSoundId ii
_ -> empty
parseJSON _ = empty
fromMidiProgram :: Int -> Instrument
fromMidiProgram = StdInstrument
toMidiProgram :: Instrument -> Maybe Int
toMidiProgram = fmap pred . Data.Maybe.listToMaybe . Data._generalMidiProgram . fetchInstrumentDef
fromMusicXmlSoundId :: String -> Instrument
fromMusicXmlSoundId = OtherInstrument
toMusicXmlSoundId :: Instrument -> Maybe String
toMusicXmlSoundId = Just . soundId
soundId :: Instrument -> String
soundId = Data._soundId . fetchInstrumentDef
allowedClefs :: Instrument -> Set Clef
allowedClefs = Data.Set.fromList . Data._allowedClefs . fetchInstrumentDef
standardClef :: Instrument -> Maybe Clef
standardClef = Data.Maybe.listToMaybe . Data._standardClef . fetchInstrumentDef
data BracketType = Bracket | Brace | SubBracket
data StaffLayout = Staff Clef | Staves BracketType [StaffLayout]
pianoStaff :: StaffLayout
pianoStaff = Staves Brace [Staff trebleClef, Staff bassClef]
playableRange :: Instrument -> Ambitus Pitch
playableRange = Data.Maybe.fromMaybe (error "Missing comfortableRange for instrument") . Data._playableRange . fetchInstrumentDef
comfortableRange :: Instrument -> Ambitus Pitch
comfortableRange = Data.Maybe.fromMaybe (error "Missing comfortableRange for instrument") . Data._comfortableRange . fetchInstrumentDef
fullName :: Instrument -> Maybe String
fullName x = Data._sibeliusName (fetchInstrumentDef x) `first` Data._longName (fetchInstrumentDef x)
where
first (Just x) _ = Just x
first _ (Just x) = Just x
first Nothing Nothing = Nothing
shortName :: Instrument -> Maybe String
shortName = Data._shortName . fetchInstrumentDef
transposition :: Instrument -> Interval
transposition = Data._transposition . fetchInstrumentDef
where
transpositionString :: Instrument -> String
transpositionString x = pitchToPCString (c .+^ transposition x)
pitchToPCString :: Pitch -> String
pitchToPCString x = show (name x) ++ showA (accidental x)
where
showA 1 = "#"
showA 0 = ""
showA (1) = "b"
scoreOrder :: Instrument -> Double
scoreOrder = Data._scoreOrder . fetchInstrumentDef
fetchInstrumentDef :: Instrument -> InstrumentDef
fetchInstrumentDef (StdInstrument x) = Data.Maybe.fromMaybe (error "Bad instr") $ Data.getInstrumentDefByGeneralMidiProgram (x + 1)
fetchInstrumentDef (OtherInstrument x) = Data.Maybe.fromMaybe (error "Bad instr") $ Data.getInstrumentDefById x
gmClef :: Int -> Int
gmMidiChannel :: Int -> Int
gmScoreOrder :: Int -> Double
gmInstrName :: Int -> Maybe String
gmClef x = Data.Maybe.fromMaybe 0 $ fmap (go . Data._standardClef) $ Data.getInstrumentDefByGeneralMidiProgram (x + 1)
where
go cs | head cs == trebleClef = 0
| head cs == altoClef = 1
| head cs == bassClef = 2
| otherwise = error "gmClef: Unknown clef"
gmScoreOrder x = Data.Maybe.fromMaybe 0 $ fmap (Data._scoreOrder) $ Data.getInstrumentDefByGeneralMidiProgram (x + 1)
gmMidiChannel x = Data.Maybe.fromMaybe 0 $ (=<<) (Data._defaultMidiChannel) $ Data.getInstrumentDefByGeneralMidiProgram (x + 1)
gmInstrName x = (=<<) (Data._longName) $ Data.getInstrumentDefByGeneralMidiProgram (x + 1)