Tom Murphy
FARM 2017
(Thanks, Rohan!)
DAG
play $ 0.1 ~* sinOsc (freq_ 440)
sinOsc
represents a binary that's run by the server
play $
> :i sinOsc
sinOsc :: Args '["freq"] '["phase"] a => a -> SDBody a Signal
All valid:
sinOsc (freq_ 440)
sinOsc (freq_ 440, phase_ 1)
sinOsc (phase_ 1, freq_ 440)
Not valid:
sinOsc (phase_ 1)
(freq_ 440, phase_ 1)
HCons (ua 440 :: UA "freq") (HCons (ua 1 :: UA "phase") HNil)
(Note: HNil isn't too elegant!)
type family Args (required :: [Symbol])
(optional :: [Symbol])
args
:: Constraint where
Args required optional args =
( Subset required (UAsArgs args)
, Subset (UAsArgs args) (SetUnion required optional)
, FromUA args
)
So does:
sinOsc (freq_ 440, phase_ 1)
...satisfying:
sinOsc :: Args '["freq"] '["phase"] a => a -> SDBody a Signal
...mean there's a big file like:
freq_ :: ToSig s as => s -> UA "freq" as
phase_ :: ToSig s as => s -> UA "phase" as
-- etc...
somewhere?
(UA
== UGen argument)
Here's why.
Any kind of sum type on the args...
data Arg =
Freq
| Phase
| -- ...
...is out because the set of arguments is an open set
Not only is it possible to write new UGens We want to encourage it!
One record per UGen...
data SinOsc = SinOsc
freq :: x
, phase :: x
}
...are out at least until -XOverloadedRecordFields
is finished
(And also because of SDBody
problem we'll see later)
Also downside: typing
{-# LANGUAGE DuplicateRecordFields #-}
data UGen =
SinOsc { freq = x, phase = x }
| Foo { freq = x }
Two problems:
sinOsc (freq_ 440)
I feel the same way!
Name cluttering:
etc.
"An identifier consists of a letter followed by zero or more letters, digits, underscores, and single quotes." - Haskell 2010 Report
Another contender:
sinOsc (freq'' 440, phase'' 1)
foo 440 0.5 1.2 3.5 12
setDoorLocks :: Bool -> Bool -> IO ()
setDoorLocks letPeopleIn letDinosaursOut =
"Hmm, what order do they go in again?"
Can lead to actual deafness
someUGen 1 440
1 = freq 440 = amp
(Also :i
!)
(0.75::Float) ~* sinOsc (freq_ (440::Float), phase_ (1::Float))
vs.
0.75 ~* sinOsc (freq_ 440, phase_ 1)
(We'll see this again later!)
length (L.nub "vivid") == 3
Example: "Synth def body"
These should behave differently
do let x = whiteNoise
x ~- x
do x <- whiteNoise
x ~- x
GHC -O2 vs -O0
sinOsc
represents a binary that's run by the server
sinOsc (freq_ whiteNoise)
do w <- whiteNoise
sinOsc (freq_ w)
SynthDef(\freq, {
s = SinOsc.ar(freq: freq);
Out.ar(0, s)
})
An open set of addressable-by-string identifiers... Hmm...
(V::V "amp")
or
V @"amp"
shorter version of
(Proxy::Proxy "amp")
x :: SynthDef '["freq"]
x = sd (440 ::I "freq") $ do
s0 <- sinOsc (freq_ (V::V "freq"), phase_ 0.5)
s1 <- 0.1 ~* s0
out 0 [s1,s1]
synth ::
(VividAction m, Subset (InnerVars params) args) =>
SynthDef args -> params -> m (Synth args)
set ::
(VividAction m, Subset (InnerVars params) sdArgs) =>
Synth sdArgs -> params -> m ()
See it in action
class (Monad m , MonadIO m) => VividAction (m :: * -> *) where
callOSC :: OSC -> m ()
newNodeId :: m NodeId
wait :: Real n => n -> m ()
getTime :: m Timestamp
fork :: m () -> m ()
defineSD :: SynthDef a -> m ()
-- And several others...
instance VividAction IO
instance VividAction Scheduled
instance VividAction NRT
Therefore: