module Music.Theory.Gamelan where import Data.Char {- base -} import Data.Function {- base -} import Data.List {- base -} import Data.Maybe {- base -} import Data.Ratio {- base -} import Text.Printf {- base -} import qualified Music.Theory.Clef as T {- hmt -} import qualified Music.Theory.Enum as T {- hmt -} import qualified Music.Theory.Pitch as T {- hmt -} import qualified Music.Theory.Tuning as T {- hmt -} import qualified Music.Theory.Tuning.ET as T {- hmt-diagrams -} -- | 'fromJust' with error message. fromJust_err :: String -> Maybe a -> a fromJust_err err = fromMaybe (error err) -- | 'approxRational' of 0.01. near_rat :: Double -> Rational near_rat = flip approxRational 0.01 -- * Gamelan -- | Enumeration of gamelan instrument families. data Instrument_Family = Bonang | Gender | Gong | Saron deriving (Enum,Bounded,Eq,Ord,Show,Read) -- | Universe instrument_family_set :: [Instrument_Family] instrument_family_set = T.enum_univ -- | Enumeration of Gamelan instruments. data Instrument_Name = Bonang_Barung -- ^ Bonang Barung (horizontal gong, middle) | Bonang_Panerus -- ^ Bonang Panerus (horizontal gong, high) | Gender_Barung -- ^ Gender Barung (key&resonator, middle) | Gender_Panerus -- ^ Gender Panembung (key&resonator, high) | Gender_Panembung -- ^ Gender Panembung, Slenthem (key&resonator, low) | Gong_Ageng -- ^ Gong Ageng (hanging gong, low) | Gong_Suwukan -- ^ Gong Suwukan (hanging gong, middle) | Kempul -- ^ Kempul (hanging gong, middle) | Kempyang -- ^ Kempyang (horizontal gong, high) | Kenong -- ^ Kenong (horizontal gong, low) | Ketuk -- ^ Ketuk (horizontal gong, middle) | Saron_Barung -- ^ Saron Barung, Saron (key, middle) | Saron_Demung -- ^ Saron Demung, Demung (key, low) | Saron_Panerus -- ^ Saron Panerus, Peking (key, high) deriving (Enum,Bounded,Eq,Ord,Show,Read) instrument_family :: Instrument_Name -> Maybe Instrument_Family instrument_family nm = case nm of Bonang_Barung -> Just Bonang Bonang_Panerus -> Just Bonang Gender_Barung -> Just Gender Gender_Panerus -> Just Gender Gender_Panembung -> Just Gender Gong_Ageng -> Just Gong Gong_Suwukan -> Just Gong Kempul -> Just Gong Kempyang -> Nothing Kenong -> Nothing Ketuk -> Nothing Saron_Barung -> Just Saron Saron_Demung -> Just Saron Saron_Panerus -> Just Saron instrument_name_pp :: Instrument_Name -> String instrument_name_pp = let f c = if c == '_' then ' ' else c in map f . show -- | 'Clef' appropriate for 'Instrument_Name'. instrument_name_clef :: Integral i => Instrument_Name -> T.Clef i instrument_name_clef nm = case nm of Bonang_Barung -> T.Clef T.Treble 0 Bonang_Panerus -> T.Clef T.Treble 1 Gender_Barung -> T.Clef T.Treble 0 Gender_Panerus -> T.Clef T.Treble 1 Gender_Panembung -> T.Clef T.Bass 0 Gong_Ageng -> T.Clef T.Bass 0 Gong_Suwukan -> T.Clef T.Bass 0 Kempul -> T.Clef T.Bass 0 Kempyang -> T.Clef T.Treble 1 Kenong -> T.Clef T.Treble 0 Ketuk -> T.Clef T.Alto 0 Saron_Barung -> T.Clef T.Treble 0 Saron_Demung -> T.Clef T.Treble 0 Saron_Panerus -> T.Clef T.Treble 1 instrument_name_clef_plain :: Integral i => Instrument_Name -> T.Clef i instrument_name_clef_plain = T.clef_zero . instrument_name_clef -- | Enumeration of Gamelan scales. data Scale = Pelog | Slendro deriving (Enum,Eq,Ord,Show,Read) type Octave = Integer type Degree = Integer type Frequency = Double type Annotation = String data Pitch = Pitch {pitch_octave :: Octave ,pitch_degree :: Degree} deriving (Eq,Ord,Show) pitch_pp_ascii :: Pitch -> String pitch_pp_ascii (Pitch o d) = let d' = intToDigit (fromIntegral d) o' = if o < 0 then genericReplicate (abs o) '-' else genericReplicate o '+' in o' ++ [d'] pitch_pp_duple :: Pitch -> String pitch_pp_duple (Pitch o d) = printf "(%d,%d)" o d data Note = Note {note_scale :: Scale ,note_pitch :: Pitch} deriving (Eq,Ord,Show) note_degree :: Note -> Degree note_degree = pitch_degree . note_pitch data Tone = Tone {tone_instrument_name :: Instrument_Name ,tone_note :: Maybe Note ,tone_frequency :: Maybe Frequency ,tone_annotation :: Maybe Annotation} deriving (Eq,Show) tone_frequency_err :: Tone -> Frequency tone_frequency_err = fromJust_err "tone_frequency" . tone_frequency -- | Orderable if frequency is given. instance Ord Tone where compare = tone_compare_frequency -- | Constructor for 'Tone' without /frequency/ or /annotation/. plain_tone :: Instrument_Name -> Scale -> Octave -> Degree -> Tone plain_tone nm sc o d = Tone nm (Just (Note sc (Pitch o d))) Nothing Nothing -- | Tones are considered /equivalent/ if they have the same -- 'Instrument_Name' and 'Note'. tone_equivalent :: Tone -> Tone -> Bool tone_equivalent p q = let Tone nm nt _ _ = p Tone nm' nt' _ _ = q in nm == nm' && nt == nt' tone_24et_pitch :: Tone -> Maybe T.Pitch tone_24et_pitch = let f i = let (_,pt,_,_,_) = T.nearest_24et_tone i in pt in fmap f . tone_frequency tone_24et_pitch' :: Tone -> T.Pitch tone_24et_pitch' = fromJust_err "tone_24et_pitch" . tone_24et_pitch tone_24et_pitch_detune :: Tone -> Maybe T.Pitch_Detune tone_24et_pitch_detune = fmap T.nearest_pitch_detune_24et . tone_frequency tone_24et_pitch_detune' :: Tone -> T.Pitch_Detune tone_24et_pitch_detune' = fromJust_err "tone_24et_pitch_detune" . tone_24et_pitch_detune tone_fmidi :: Tone -> Double tone_fmidi = T.cps_to_fmidi . tone_frequency_err -- | Fractional (rational) 24-et midi note number of 'Tone'. tone_24et_fmidi :: Tone -> Rational tone_24et_fmidi = near_rat . T.pitch_to_fmidi . tone_24et_pitch' tone_12et_pitch :: Tone -> Maybe T.Pitch tone_12et_pitch = let f i = let (_,pt,_,_,_) = T.nearest_12et_tone i in pt in fmap f . tone_frequency tone_12et_pitch' :: Tone -> T.Pitch tone_12et_pitch' = fromJust_err "tone_12et_pitch" . tone_12et_pitch tone_12et_pitch_detune :: Tone -> Maybe T.Pitch_Detune tone_12et_pitch_detune = fmap T.nearest_pitch_detune_12et . tone_frequency tone_12et_pitch_detune' :: Tone -> T.Pitch_Detune tone_12et_pitch_detune' = fromJust_err "tone_12et_pitch_detune" . tone_12et_pitch_detune -- | Fractional (rational) 24-et midi note number of 'Tone'. tone_12et_fmidi :: Tone -> Rational tone_12et_fmidi = near_rat . T.pitch_to_fmidi . tone_12et_pitch' tone_family :: Tone -> Maybe Instrument_Family tone_family = instrument_family . tone_instrument_name tone_family_err :: Tone -> Instrument_Family tone_family_err = fromJust_err "tone_family" . tone_family tone_in_family :: Instrument_Family -> Tone -> Bool tone_in_family c t = tone_family t == Just c select_tones :: Instrument_Family -> [Tone] -> [Maybe Tone] select_tones c = let f t = if tone_family t == Just c then Just t else Nothing in map f -- | Specify subset as list of families and scales. type Tone_Subset = ([Instrument_Family],[Scale]) -- | Extract subset of 'Tone_Set'. tone_subset :: Tone_Subset -> Tone_Set -> Tone_Set tone_subset (fm,sc) = let f t = fromJust_err "tone_subset" (tone_family t) `elem` fm && fromJust_err "tone_subset" (tone_scale t) `elem` sc in filter f data Instrument = Instrument {instrument_name :: Instrument_Name ,instrument_scale :: Maybe Scale ,instrument_pitches :: Maybe [Pitch] ,instrument_frequencies :: Maybe [Frequency]} deriving (Eq,Show) type Tone_Set = [Tone] type Tone_Group = [Tone_Set] type Gamelan = [Instrument] tone_scale :: Tone -> Maybe Scale tone_scale = fmap note_scale . tone_note tone_pitch :: Tone -> Maybe Pitch tone_pitch = fmap note_pitch . tone_note tone_degree :: Tone -> Maybe Degree tone_degree = fmap pitch_degree . tone_pitch tone_degree' :: Tone -> Degree tone_degree' = fromJust_err "tone_degree" . tone_degree tone_octave :: Tone -> Maybe Octave tone_octave = fmap pitch_octave . tone_pitch tone_class :: Tone -> (Instrument_Name,Maybe Scale) tone_class t = (tone_instrument_name t,tone_scale t) instrument_class :: Instrument -> (Instrument_Name,Maybe Scale) instrument_class i = (instrument_name i,instrument_scale i) tone_class_p :: (Instrument_Name, Scale) -> Tone -> Bool tone_class_p (nm,sc) t = tone_instrument_name t == nm && tone_scale t == Just sc tone_family_class_p :: (Instrument_Family,Scale) -> Tone -> Bool tone_family_class_p (fm,sc) t = instrument_family (tone_instrument_name t) == Just fm && tone_scale t == Just sc -- | Given a 'Tone_Set', find those 'Tone's that are within 'T.Cents' of 'Frequency'. tone_set_near_frequency :: Tone_Set -> T.Cents -> Frequency -> Tone_Set tone_set_near_frequency t k n = let near i = abs (T.cps_difference_cents i n) <= k near_t i = maybe False near (tone_frequency i) in filter near_t t -- | Compare 'Tone's by frequency. 'Tone's without frequency compare -- as if at frequency @0@. tone_compare_frequency :: Tone -> Tone -> Ordering tone_compare_frequency = compare `on` (maybe 0 id . tone_frequency) -- | If all /f/ of /a/ are 'Just' /b/, then 'Just' /[b]/, else -- 'Nothing'. map_maybe_uniform :: (a -> Maybe b) -> [a] -> Maybe [b] map_maybe_uniform f x = let x' = map f x in if any isNothing x' then Nothing else Just (catMaybes x') instrument :: Tone_Set -> Instrument instrument c = let sf = fmap note_scale . tone_note pf = fmap note_pitch . tone_note pm = map_maybe_uniform pf c fm = map_maybe_uniform tone_frequency c (p,f) = case (pm,fm) of (Just i,Just j) -> let (i',j') = unzip (sort (zip i j)) in (Just i',Just j') _ -> (pm,fm) in case c of t:_ -> Instrument (tone_instrument_name t) (sf t) p f [] -> undefined instruments :: Tone_Set -> [Instrument] instruments c = let c' = sortBy (compare `on` tone_instrument_name) c c'' = groupBy ((==) `on` tone_class) c' in map instrument c'' instrument_gamut :: Instrument -> Maybe (Pitch,Pitch) instrument_gamut = let f p = (head p,last p) in fmap f . instrument_pitches scale_degrees :: Scale -> [Degree] scale_degrees s = case s of Pelog -> [1..7] Slendro -> [1,2,3,5,6] -- > degree_index Slendro 4 == Nothing -- > degree_index Pelog 4 == Just 3 degree_index :: Scale -> Degree -> Maybe Int degree_index s d = findIndex (== d) (scale_degrees s) -- * Tone set tone_set_gamut :: Tone_Set -> Maybe (Pitch,Pitch) tone_set_gamut g = case mapMaybe (fmap note_pitch . tone_note) g of [] -> Nothing p -> Just (minimum p,maximum p) tone_set_instrument :: Tone_Set -> (Instrument_Name,Maybe Scale) -> Tone_Set tone_set_instrument db (i,s) = let f t = tone_class t == (i,s) in filter f db