module Music.Parts.Internal.Data (
SoundId,
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,
_generalMidiProgram :: [Int],
_generalMidiPercussionNote :: [Int],
_defaultMidiChannel :: Maybe Int,
_scoreOrder :: Double,
_allowedClefs :: [Clef],
_standardClef :: [Clef],
_transposition :: Interval,
_playableRange :: Maybe (Ambitus Pitch),
_comfortableRange :: Maybe (Ambitus Pitch),
_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
defs = System.IO.Unsafe.unsafePerformIO getInstrumentData
getInstrumentDefByGeneralMidiProgram :: Int -> Maybe InstrumentDef
getInstrumentDefByGeneralMidiProgram a = Data.List.find (\x -> a `elem` _generalMidiProgram x) defs
where
defs = System.IO.Unsafe.unsafePerformIO getInstrumentData
getInstrumentDefByGeneralMidiPercussionNote :: Int -> Maybe InstrumentDef
getInstrumentDefByGeneralMidiPercussionNote a = Data.List.find (\x -> a `elem` _generalMidiPercussionNote x) defs
where
defs = System.IO.Unsafe.unsafePerformIO getInstrumentData
pitchFromSPN :: String -> Maybe Pitch
pitchFromSPN x = fmap (\on -> (.+^ _P8^*(on4))) (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)
readClef :: String -> Maybe Clef
readClef = go where
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)
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)
<*> v .! 8
<*> v .! 9
<*> v .! 10
<*> v .! 11
<*> v .! 12
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