music-parts-1.9.0: src/Music/Parts/Internal/Data.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Music.Parts.Internal.Data (
SoundId,
-- InstrumentTopCategory(..),
InstrumentDef(..),
getInstrumentDefById,
getInstrumentDefByGeneralMidiProgram,
getInstrumentDefByGeneralMidiPercussionNote,
) where
import Control.Monad.Plus
import Data.Map (Map)
import Control.Applicative
import Control.Lens (toListOf, (^.))
import Data.AffineSpace
import qualified Data.ByteString.Lazy
import qualified Data.ByteString.Lazy.Char8
import Data.Csv (FromField (..), FromRecord (..),
(.!))
import qualified Data.Csv
import qualified Data.List
import Data.Traversable (traverse)
import Data.VectorSpace
import qualified System.IO.Unsafe
import Music.Pitch
import Music.Pitch.Ambitus
import Music.Pitch.Clef
#ifndef GHCI
#define GET_DATA_FILE Paths_music_parts.getDataFileName
import qualified Paths_music_parts
#else
#define GET_DATA_FILE (return . ("../music-parts/"++))
#endif
type SoundId = String
#ifndef GHCI
instance Num Clef where
fromInteger 0 = trebleClef
fromInteger 1 = altoClef
fromInteger 2 = bassClef
#endif
data InstrumentTopCategory
= Woodwind
| Brass
| Keyboard
| Fretted
| Percussion
| Vocal
| Strings
| Other
deriving (Show)
data InstrumentDef = InstrumentDef {
_soundId :: SoundId, -- ID
_generalMidiProgram :: [Int], -- GM Program
_generalMidiPercussionNote :: [Int], -- GM Percussion Note
_defaultMidiChannel :: Maybe Int, -- Default MIDI Channel
_scoreOrder :: Double, -- Score Order
_allowedClefs :: [Clef], -- Allowed Clefs
_standardClef :: [Clef], -- Standard Clef (1 elem for single staff, more otherwise, never empty)
_transposition :: Interval, -- Transposition
_playableRange :: Maybe (Ambitus Pitch), -- Playable Range
_comfortableRange :: Maybe (Ambitus Pitch), -- Comfortable Range
_longName :: Maybe String,
_shortName :: Maybe String,
_sibeliusName :: Maybe String
}
deriving (Show)
getInstrumentDefById :: String -> Maybe InstrumentDef
getInstrumentDefById a = Data.List.find (\x -> _soundId x == a) defs
where
-- Safe as this file never change
defs = System.IO.Unsafe.unsafePerformIO getInstrumentData
getInstrumentDefByGeneralMidiProgram :: Int -> Maybe InstrumentDef
getInstrumentDefByGeneralMidiProgram a = Data.List.find (\x -> a `elem` _generalMidiProgram x) defs
where
-- Safe as this file never change
defs = System.IO.Unsafe.unsafePerformIO getInstrumentData
getInstrumentDefByGeneralMidiPercussionNote :: Int -> Maybe InstrumentDef
getInstrumentDefByGeneralMidiPercussionNote a = Data.List.find (\x -> a `elem` _generalMidiPercussionNote x) defs
where
-- Safe as this file never change
defs = System.IO.Unsafe.unsafePerformIO getInstrumentData
-- TODO move
pitchFromSPN :: String -> Maybe Pitch
pitchFromSPN x = fmap (\on -> (.+^ _P8^*(on-4))) (safeRead octS) <*> pc pcS
where
pc "C" = Just c
pc "D" = Just d
pc "E" = Just e
pc "F" = Just f
pc "G" = Just g
pc "A" = Just a
pc "B" = Just b
pc "Cs" = Just cs
pc "Ds" = Just ds
pc "Es" = Just es
pc "Fs" = Just fs
pc "Gs" = Just gs
pc "As" = Just as
pc "Bs" = Just bs
pc "Cb" = Just cb
pc "Db" = Just db
pc "Eb" = Just eb
pc "Fb" = Just fb
pc "Gb" = Just gb
pc "Ab" = Just ab
pc "Bb" = Just bb
pc _ = Nothing
pcS = init x
octS = pure $ last x
safeRead x = Just (read x) -- TODO catch exception
readClef :: String -> Maybe Clef
readClef = go where
-- go "french" = Just trebleClef
go "treble" = Just trebleClef
go "sop" = Just sopranoClef
go "mez" = Just mezzoSopranoClef
go "alto" = Just altoClef
go "ten" = Just tenorClef
go "bar" = Just baritoneClef
go "bass" = Just bassClef
go "perc" = Just percClef
go _ = Nothing
percClef = Clef (PercClef, 0, 0) -- TODO move
{-
Can't stop these instances from being reexported (https://www.haskell.org/onlinereport/modules.html)
so they are transitively exported by all modules depending on the Suite!
Drats!
-}
instance FromField [Int] where
parseField v = fmap (mcatMaybes . map safeRead) $ fmap (splitBy ',') $ parseField v
instance FromField Pitch where
parseField v = mcatMaybes $ fmap pitchFromSPN $ parseField v
instance FromField (Maybe (Ambitus Pitch)) where
parseField v = fmap (listToAmbitus . mcatMaybes . map pitchFromSPN) $ fmap (splitBy '-') $ parseField v
where
listToAmbitus [a,b] = Just $ (a,b)^.ambitus
listToAmbitus _ = Nothing
instance FromField Clef where
parseField v = mcatMaybes $ fmap readClef $ parseField v
instance FromField [Clef] where
parseField v = fmap (mcatMaybes . map readClef) $ fmap (splitBy ',') $ parseField v
instance FromRecord InstrumentDef where
parseRecord v = InstrumentDef
<$> v .! 0
<*> v .! 1
<*> v .! 2
<*> v .! 3
<*> v .! 4
<*> v .! 5
<*> v .! 6
<*> fmap (.-.(c::Pitch)) (v .! 7) -- sounding - written, i.e. -P8 for double bass
<*> v .! 8
<*> v .! 9
<*> v .! 10
<*> v .! 11
<*> v .! 12
{-
Don't edit data files!
Original here
https://docs.google.com/spreadsheets/d/1I7lCGd8u4ggqqa_ATMVb87V10Vc8J8TP9w-vXu0M18o/edit#gid=0
-}
getInstrumentData' :: IO [Map String String]
getInstrumentData' = do
fp <- GET_DATA_FILE "data/instruments.csv"
d <- Data.ByteString.Lazy.readFile fp
return $ case Data.Csv.decodeByName d of
Left e -> error $ "Could not read data/instruments.csv "++show e
Right (_header, x) -> toListOf traverse x
getInstrumentData :: IO [InstrumentDef]
getInstrumentData = do
fp <- GET_DATA_FILE "data/instruments.csv"
d <- Data.ByteString.Lazy.readFile fp
return $ case Data.Csv.decode Data.Csv.HasHeader d of
Left e -> error $ "Could not read data/instruments.csv "++show e
Right (x) -> toListOf traverse x
splitBy :: Eq a => a -> [a] -> [[a]]
splitBy _ [] = []
splitBy x xs = splitBy1 x xs
where
splitBy1 delimiter = foldr f [[]]
where f c l@(x:xs) | c == delimiter = []:l
| otherwise = (c:x):xs