{-# 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