hmt-0.16: Music/Theory/Gamelan.hs
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