hsc3-lang-0.13: Sound/SC3/Lang/Control/Event.hs
-- | An 'Event' is a ('Key','Value') map.
module Sound.SC3.Lang.Control.Event where
import qualified Data.Map as M
import Data.Maybe
import qualified Sound.OpenSoundControl as O
import qualified Sound.SC3.Server as S
import qualified Sound.SC3.Lang.Control.Duration as D
import qualified Sound.SC3.Lang.Control.Instrument as I
import qualified Sound.SC3.Lang.Control.Pitch as P
-- | The type of the /key/ at an 'Event'.
type Key = String
-- | The type of the /value/ at an 'Event'.
type Value = Double
-- | The /type/ of an 'Event'.
data Type = E_s_new | E_n_set | E_rest deriving (Eq,Show)
-- | An 'Event' has a 'Type', possibly an integer identifier, possibly
-- an 'I.Instrument' and a map of ('Key','Value') pairs.
data Event = Event {e_type :: Type
,e_id :: Maybe Int
,e_instrument :: Maybe I.Instrument
,e_map :: M.Map Key Value}
deriving (Eq,Show)
-- | The /default/ empty event.
defaultEvent :: Event
defaultEvent =
Event {e_type = E_s_new
,e_id = Nothing
,e_instrument = Nothing
,e_map = M.empty}
-- | Lookup /k/ in /e/.
--
-- > lookup_m "k" defaultEvent == Nothing
lookup_m :: Key -> Event -> Maybe Value
lookup_m k e = M.lookup k (e_map e)
-- | Variant of 'lookup_m' with a default value /v/.
--
-- > lookup_v 1 "k" defaultEvent == 1
lookup_v :: Value -> Key -> Event -> Value
lookup_v v k e = fromMaybe v (lookup_m k e)
-- | Variant of 'lookup_v' with a transformation function.
--
-- > lookup_t 1 negate "k" defaultEvent == 1
-- > lookup_t 1 negate "k" (insert "k" 1 defaultEvent) == -1
lookup_t :: t -> (Value -> t) -> Key -> Event -> t
lookup_t v f k e =
case lookup_m k e of
Nothing -> v
Just v' -> f v'
-- | Lookup 'Pitch' model parameters at /e/ and construct a 'Pitch'
-- value.
pitch :: Event -> P.Pitch Double
pitch e =
let get_r v k = lookup_v v k e
get_m v k = lookup_t v const k e
in P.Pitch {P.mtranspose = get_r 0 "mtranspose"
,P.gtranspose = get_r 0 "gtranspose"
,P.ctranspose = get_r 0 "ctranspose"
,P.octave = get_r 5 "octave"
,P.root = get_r 0 "root"
,P.degree = get_r 0 "degree"
,P.scale = [0, 2, 4, 5, 7, 9, 11]
,P.stepsPerOctave = get_r 12 "stepsPerOctave"
,P.detune = get_r 0 "detune"
,P.harmonic = get_r 1 "harmonic"
,P.freq_f = get_m P.default_freq_f "freq"
,P.midinote_f = get_m P.default_midinote_f "midinote"
,P.note_f = get_m P.default_note_f "note"}
-- | Lookup 'D.Duration' model parameters at an 'Event' and construct a
-- 'D.Duration' value.
duration :: Event -> D.Duration Double
duration e =
let get_r v k = lookup_v v k e
get_m v k = lookup_t v const k e
get_o k = lookup_m k e
in D.Duration {D.tempo = get_r 60 "tempo"
,D.dur = get_r 1 "dur"
,D.stretch = get_r 1 "stretch"
,D.legato = get_r 0.8 "legato"
,D.sustain_f = get_m D.default_sustain_f "sustain"
,D.delta_f = get_m D.default_delta_f "delta"
,D.lag = get_r 0.1 "lag"
,D.fwd' = get_o "fwd'"}
-- | Insert (/k/,/v/) into /e/.
--
-- > lookup_m "k" (insert "k" 1 defaultEvent) == Just 1
insert :: Key -> Value -> Event -> Event
insert k v e = e {e_map = M.insert k v (e_map e)}
-- | Lookup /db/ field of 'Event', the default value is @-20db@.
db :: Event -> Value
db = lookup_v (-20) "db"
-- | Function to convert from decibels to linear amplitude.
dbAmp' :: Floating a => a -> a
dbAmp' a = 10 ** (a * 0.05)
-- | The linear amplitude of the amplitude model at /e/.
--
-- > amp (event [("db",-20)]) == 0.1
amp :: Event -> Value
amp e = lookup_v (dbAmp' (db e)) "amp" e
-- | The /fwd/ value of the duration model at /e/.
--
-- > fwd (event [("dur",1),("stretch",2)]) == 2
fwd :: Event -> Double
fwd = D.fwd . duration
-- | The /latency/ to compensate for when sending messages based on
-- the event. Defaults to @0.1@.
latency :: Event -> Double
latency = lookup_v 0.1 "latency"
-- | List of 'Key's used in pitch, duration and amplitude models.
--
-- > ("degree" `elem` model_keys) == True
model_keys :: [Key]
model_keys =
["amp","db"
,"delta","dur","legato","fwd'","stretch","sustain","tempo"
,"ctranspose","degree","freq","midinote","mtranspose","note","octave"
,"rest"]
-- | List of reserved 'Key's used in pitch, duration and amplitude
-- models. These are keys that may be provided explicitly, but if not
-- will be calculated implicitly.
--
-- > ("freq" `elem` reserved) == True
reserved :: [Key]
reserved = ["freq","midinote","note"
,"delta","sustain"
,"amp"]
-- | If 'Key' is 'reserved' then 'Nothing', else 'id'.
parameters' :: (Key,Value) -> Maybe (Key,Value)
parameters' (k,v) =
if k `elem` reserved
then Nothing
else Just (k,v)
-- | Extract non-'reserved' 'Keys' from 'Event'.
parameters :: Event -> [(Key,Value)]
parameters = mapMaybe parameters' . M.toList . e_map
-- | 'Value' editor for 'Key' at 'Event', with default value in case
-- 'Key' is not present.
edit_v :: Key -> Value -> (Value -> Value) -> Event -> Event
edit_v k v f e =
case lookup_m k e of
Just n -> insert k (f n) e
Nothing -> insert k (f v) e
-- | Variant of 'edit_v' with no default value.
edit :: Key -> (Value -> Value) -> Event -> Event
edit k f e =
case lookup_m k e of
Just n -> insert k (f n) e
Nothing -> e
-- | Basic 'Event' constructor function with 'e_map' given as a list.
from_list :: Type -> Maybe Int -> Maybe I.Instrument -> [(Key,Value)] -> Event
from_list t n i l =
Event {e_type = t
,e_id = n
,e_instrument = i
,e_map = M.fromList l}
-- | Construct an 'Event' from a list of (/key/,/value/) pairs.
--
-- > lookup_m "k" (event [("k",1)]) == Just 1
event :: [(Key,Value)] -> Event
event l =
Event {e_type = E_s_new
,e_id = Nothing
,e_instrument = Nothing
,e_map = M.fromList l}
-- | Extract 'I.Instrument' name from 'Event', or @default@.
instrument_name :: Event -> String
instrument_name e =
case e_instrument e of
Nothing -> "default"
Just (I.InstrumentDef s _) -> S.synthdefName s
Just (I.InstrumentName s _) -> s
-- | Extract 'I.Instrument' definition from 'Event' if present.
instrument_def :: Event -> Maybe S.Synthdef
instrument_def e =
case e_instrument e of
Nothing -> Nothing
Just (I.InstrumentDef s _) -> Just s
Just (I.InstrumentName _ _) -> Nothing
-- | 'I.send_release' of 'I.Instrument' at 'Event'.
instrument_send_release :: Event -> Bool
instrument_send_release e =
case e_instrument e of
Nothing -> True
Just i -> I.send_release i
-- | Merge two sorted sequence of (/location/,/value/) pairs.
--
-- > let m = f_merge (zip [0,2..6] ['a'..]) (zip [0,3,6] ['A'..])
-- > in m == [(0,'a'),(0,'A'),(2,'b'),(3,'B'),(4,'c'),(6,'d'),(6,'C')]
f_merge :: Ord a => [(a,t)] -> [(a,t)] -> [(a,t)]
f_merge p q =
case (p,q) of
([],_) -> q
(_,[]) -> p
((t0,e0):r0,(t1,e1):r1) ->
if t0 <= t1
then (t0,e0) : f_merge r0 q
else (t1,e1) : f_merge p r1
-- | Times are /hosc/ (NTP) times.
type Time = O.Time
-- | Merge two time-stamped 'Event' sequences. Note that this uses
-- 'fwd' to calculate start times.
merge' :: (Time,[Event]) -> (Time,[Event]) -> [(Time,Event)]
merge' (pt,p) (qt,q) =
let p_st = map (+ pt) (0 : scanl1 (+) (map fwd p))
q_st = map (+ qt) (0 : scanl1 (+) (map fwd q))
in f_merge (zip p_st p) (zip q_st q)
-- | Insert /fwd/ 'Key's into a time-stamped 'Event' sequence.
add_fwd :: [(Time,Event)] -> [Event]
add_fwd e =
case e of
(t0,e0):(t1,e1):e' ->
insert "fwd'" (t1 - t0) e0 : add_fwd ((t1,e1):e')
_ -> map snd e
-- | Composition of 'add_fwd' and 'merge''.
merge :: (Time,[Event]) -> (Time,[Event]) -> [Event]
merge p q = add_fwd (merge' p q)
-- | Does 'Event' have a non-zero @rest@ key.
is_rest :: Event -> Bool
is_rest e =
case lookup_m "rest" e of
Just r -> r > 0
Nothing -> False
-- | Generate @SC3@ 'O.Bundle' messages describing 'Event'. Consults the
-- 'instrument_send_release' in relation to gate command.
to_sc3_bundle :: Time -> Int -> Event -> Maybe (O.Bundle,O.Bundle)
to_sc3_bundle t j e =
let s = instrument_name e
sr = instrument_send_release e
p = pitch e
d = duration e
rt = D.sustain d {- rt = release time -}
f = P.detunedFreq p
pr = ("freq",f) : ("midinote",P.midinote p) : ("note",P.note p) :
("delta",D.delta d) : ("sustain",rt) :
("amp",amp e) :
parameters e
i = fromMaybe j (e_id e)
t' = t + latency e
in if is_rest e || isNaN f
then Nothing
else let m_on = case e_type e of
E_s_new -> [S.s_new s i S.AddToTail 1 pr]
E_n_set -> [S.n_set i pr]
E_rest -> []
m_off = if not sr
then []
else case e_type e of
E_s_new -> [S.n_set i [("gate",0)]]
E_n_set -> [S.n_set i [("gate",0)]]
E_rest -> []
in Just (O.Bundle t' m_on
,O.Bundle (t' + rt) m_off)
{-
-- | The frequency of the 'pitch' of /e/.
--
-- > freq (event [("degree",5)]) == 440
-- > freq (event [("midinote",69)]) == 440
freq :: Event -> Double
freq = P.detunedFreq . pitch
-- | The /sustain/ value of the duration model at /e/.
--
-- > sustain (event [("dur",1),("legato",0.5)]) == 0.5
sustain :: Event -> Double
sustain = D.sustain . duration
-}