-- | Continous controller event and Ctl systems for external control interfaces. module Sound.Sc3.Ugen.Event where import Data.List {- base -} import Data.List.Split {- split -} import Sound.Sc3.Common.Math {- hsc3 -} import Sound.Sc3.Common.Rate {- hsc3 -} import Sound.Sc3.Ugen.Bindings.Composite {- hsc3 -} import Sound.Sc3.Ugen.Bindings.Db {- hsc3 -} import Sound.Sc3.Ugen.Types {- hsc3 -} import Sound.Sc3.Ugen.Util {- hsc3 -} -- * Cc Event {- | (v, w, x, y, z, o, rx, ry, p, px, _) v = voice, w = gate, z = force/pressure, o = orientation/angle, r = radius, p = pitch -} type CcEvent t = (Int, t, t, t, t, t, t, t, t, t, t) -- | Translate list to Event. cc_event_from_list :: Num t => Int -> [t] -> CcEvent t cc_event_from_list v l = case l of [w, x, y, z, o, rx, ry, p, px, py] -> (v, w, x, y, z, o, rx, ry, p, px, py) _ -> error "cc_event_from_list?" {- | (ccEventAddr, ccEventIncr, ccEventZero) ccEventAddr = k0 = index of control bus zero for event system, ccEventIncr = stp = voice index increment, ccEventZero = c0 = offset for event voices at current server -} type CcEventMeta t = (t, t, t) ccEventMetaDefault :: Num n => CcEventMeta n ccEventMetaDefault = (13000, 10, 0) ccEventMetaControls :: CcEventMeta Int -> CcEventMeta Ugen ccEventMetaControls (p, q, r) = let k nm i = control kr nm (fromIntegral i) in (k "ccEventAddr" p, k "ccEventIncr" q, k "ccEventZero" r) -- | c = event number (zero indexed) ccEventAddr :: (Ugen, Ugen, Ugen) -> Int -> CcEvent Ugen ccEventAddr (k0, stp, c0) c = let u = in' 10 kr (k0 + ((c0 + fromIntegral c) * stp)) in cc_event_from_list c (mceChannels u) -- | c0 = index of voice (channel) zero for event set, n = number of voices (channels) ccEventVoicerAddr :: CcEventMeta Ugen -> Int -> (CcEvent Ugen -> Ugen) -> Ugen ccEventVoicerAddr m n f = mce (map (\c -> f (ccEventAddr m c)) [0 .. n - 1]) -- | 'eventVoicerAddr' with default (addr, inct, zero). ccEventVoicer :: Int -> (CcEvent Ugen -> Ugen) -> Ugen ccEventVoicer = ccEventVoicerAddr ccEventMetaDefault -- | Synonym for ccEventVoicer. voicer :: Int -> (CcEvent Ugen -> Ugen) -> Ugen voicer = ccEventVoicer -- | 'eventVoicerAddr' with 'control' inputs for /eventAddr/, /eventIncr/ and /eventZero/. ccEventVoicerParam :: Int -> (CcEvent Ugen -> Ugen) -> Ugen ccEventVoicerParam = ccEventVoicerAddr (ccEventMetaControls ccEventMetaDefault) {- | Given /w|g/ and /p/ fields of an 'CcEvent' derive a 'gateReset' from g and a trigger derived from monitoring /w|g/ and /p/ for changed values. -} ccEventGateReset :: Ugen -> Ugen -> (Ugen, Ugen) ccEventGateReset g p = let tr = changed p 0.01 + changed g 0.01 in (gateReset g tr, tr) -- * Ctl -- | Sequence of 8 continous controller inputs in range (0-1). type Ctl8 = (Ugen, Ugen, Ugen, Ugen, Ugen, Ugen, Ugen, Ugen) -- | k0 = index of control bus zero ctl8At :: Int -> Ctl8 ctl8At k0 = let u = in' 8 kr (constant k0) in case mceChannels u of [cc0, cc1, cc2, cc3, cc4, cc5, cc6, cc7] -> (cc0, cc1, cc2, cc3, cc4, cc5, cc6, cc7) _ -> error "ctl8At?" -- | 'ctlVoicerAddr' with 'control' inputs for /CtlAddr/ and /CtlZero/. ctl8Voicer :: Int -> (Int -> Ctl8 -> Ugen) -> Ugen ctl8Voicer n f = mce (map (\c -> f c (ctl8At (11000 + (8 * c)))) [0 .. n - 1]) -- | Sequence of 16 continous controller inputs arranged as two Ctl8 sequences. type Ctl16 = (Ctl8, Ctl8) -- | 'ctl16VoicerAddr' with 'control' inputs for /CtlAddr/ and /CtlZero/. ctl16Voicer :: Int -> (Int -> Ctl16 -> Ugen) -> Ugen ctl16Voicer n f = mce (map (\c -> let i = 11000 + (16 * c) in f c (ctl8At i, ctl8At (i + 8))) [0 .. n - 1]) -- * Names -- | Control Specificier. (name,default,(minValue,maxValue,warpName)) type ControlSpec t = (String, t, (t, t, String)) control_spec_name :: ControlSpec t -> String control_spec_name (name, _, _) = name -- | Comma separated, no spaces. control_spec_parse :: String -> ControlSpec Double control_spec_parse str = case splitOn "," str of [cnmdef, lhs, rhs, wrp] -> case splitOn ":" cnmdef of [cnm, def] -> (cnm, read def, (read lhs, read rhs, wrp)) _ -> error ("control_spec_parse: " ++ cnmdef) _ -> error ("control_spec_parse: " ++ str) {- | Semicolon separated, no spaces. >>> control_spec_seq_parse "freq:220,110,440,exp;amp:0.1,0,1,amp;pan:0,-1,1,lin" [("freq",220.0,(110.0,440.0,"exp")),("amp",0.1,(0.0,1.0,"amp")),("pan",0.0,(-1.0,1.0,"lin"))] -} control_spec_seq_parse :: String -> [ControlSpec Double] control_spec_seq_parse = map control_spec_parse . splitOn ";" -- | Comma separated, 6 decimal places, no spaces. control_spec_print :: ControlSpec Double -> String control_spec_print (cnm, def, (lhs, rhs, wrp)) = intercalate "," [concat [cnm, ":", double_pp 6 def], double_pp 6 lhs, double_pp 6 rhs, wrp] {- | Semicolon separated, no spaces. >>> control_spec_seq_print (control_spec_seq_parse "freq:220,220,440,exp;amp:0.1,0,1,amp;pan:0,-1,1,lin") "freq:220.0,220.0,440.0,exp;amp:0.1,0.0,1.0,amp;pan:0.0,-1.0,1.0,lin" -} control_spec_seq_print :: [ControlSpec Double] -> String control_spec_seq_print = intercalate ";" . map control_spec_print control_spec_to_control :: ControlSpec Double -> Control control_spec_to_control (cnm, def, (lhs, rhs, wrp)) = let grp = if last cnm `elem` "[]" then Just Control_Range else Nothing in Control ControlRate Nothing cnm def False (Just (Control_Meta lhs rhs wrp 0 "" grp)) emptyBrackets {- | See SCClassLibrary/Common/Control/Spec:ControlSpec.initClass "ControlSpec defines the range and curve of a control" This list adds default values. -} sc3_control_spec :: Fractional t => [ControlSpec t] sc3_control_spec = [ ("amp", 0.1, (0, 1, "amp")) , ("beats", 1, (0, 20, "lin")) , ("bipolar", 0, (-1, 1, "lin")) , ("boostcut", 0, (-20, 20, "lin")) , ("db", -12, (-180, 0, "db")) , ("delay", 0.01, (0.0001, 1, "exp")) , ("detune", 0, (-20, 20, "lin")) , ("freq", 440, (20, 20000, "exp")) , ("lofreq", 20, (0.1, 100, "exp")) , ("midfreq", 440, (25, 4200, "exp")) , ("midi", 64, (0, 127, "lin")) , ("midinote", 64, (0, 127, "lin")) , ("midivelocity", 64, (1, 127, "lin")) , ("pan", 0, (-1, 1, "lin")) , ("phase", 0, (0, 6.28318, "lin")) , ("rate", 1, (0.125, 8, "exp")) , ("rq", 0.1, (0.001, 2, "exp")) , ("unipolar", 0, (0, 1, "lin")) , ("widefreq", 440, (0.1, 20000, "exp")) ] {- | See Kyma X Revealed, p.403 "The following EventValue names are associated with initial ranges other than (0,1). EventValue names are not case-sensitive." This list adds curve specifiers as strings and default values. >>> Data.List.intersect (map control_spec_name sc3_control_spec) (map control_spec_name kyma_event_value_ranges) ["beats","boostcut","freq","rate"] >>> let f i = filter ((== i) . control_spec_name) >>> let c (p,q) = (f p sc3_control_spec, f q kyma_event_value_ranges) >>> c ("lofreq","freqlow") ([("lofreq",20.0,(0.1,100.0,"exp"))],[("freqlow",120.0,(0.0,1000.0,"exp"))]) >>> c ("midfreq","freqmid") ([("midfreq",440.0,(25.0,4200.0,"exp"))],[("freqmid",1200.0,(1000.0,8000.0,"exp"))]) >>> find ((==) "freqhigh" . control_spec_name) kyma_event_value_ranges Just ("freqhigh",12000.0,(8000.0,24000.0,"exp")) -} kyma_event_value_ranges :: Fractional t => [ControlSpec t] kyma_event_value_ranges = [ ("angle", 0, (-0.5, 1.5, "lin")) , ("beats", 1, (1, 16, "lin")) , ("boostcut", 0, (-12, 12, "lin")) , ("bpm", 60, (0, 2000, "lin")) , ("centervalue", 0, (-1, 1, "lin")) , ("coef", 0, (-1, 1, "lin")) , ("cutoff", 440, (0, 10000, "exp")) , ("cycles", 1, (0, 100, "lin")) , ("dcoffset", 0, (-1, 1, "lin")) , ("direction", 0, (-1, 1, "lin")) , ("distance", 0, (-2, 2, "lin")) , ("fmntshift", 1, (0.75, 1.25, "lin")) , ("freq", 440, (0, 10000, "exp")) , ("freqhigh", 12000, (8000, 24000, "exp")) -- sampleRate / 2 , ("freqjitter", 0, (0, 1, "lin")) , ("freqlow", 120, (0, 1000, "exp")) , ("freqmid", 1200, (1000, 8000, "exp")) , ("gain", 0.1, (0, 10, "amp")) , ("gaindb", -12, (-128, 128, "lin")) , ("interval", 0, (-24, 24, "lin")) , ("keynumber", 64, (0, 127, "lin")) , ("logfreq", 20, (0, 127, "lin")) , ("looplength", 0, (-1, 1, "lin")) , ("offset", 0, (-1, 1, "lin")) , ("onduration", 0.1, (0, 30, "lin")) , ("panner", 0, (-0.5, 1.5, "lin")) , ("pitch", 64, (0, 127, "lin")) , ("q", 0.1, (0, 10, "lin")) , ("radius", 1, (-2, 2, "lin")) , ("rate", 1, (0, 2, "lin")) , ("ratio", 1, (0, 100, "lin")) , ("scale", 0, (-2, 2, "lin")) , ("smallInterval", 0, (0, 12, "lin")) , ("steps", 1, (1, 128, "lin")) , ("swing", 0, (0, 0.5, "lin")) , ("threshdb", -12, (-60, 0, "lin")) , ("timeconstant", 1, (0.0001, 5, "lin")) , ("timeindex", 0, (-1, 1, "lin")) , ("tune", 0, (-1, 1, "lin")) , ("upinterval", 0, (0, 24, "lin")) ]