# Audio Graph
In addition to audio units, it is possible to use an audio graph to construct sound with feedback.
A feedback loop is created when one uses the processed output of an audio node as an input to itself. One classic physical feedback loop is echo between two walls: the delayed audio bounces back and forth, causing really interesting and surprising effects.
Because audio functions like gain
consume other audio functions like sinOsc
, there is no way to create a loop by composing these functions. Instead, to create a feedback loop, we need to use the graph
function to create an audio graph.
An audio graph is a row with three keys: accumulators
, processors
and generators
. generators
can be any function that creates audio (including graph
itself). processors
are unary audio operators like filters and convolution. All of the audio functions that do this, like highpass
and waveShaper
, have graph analogues with g'
prepended, i.e. g'highpass
and g'waveShaper
. aggregators
are n-ary audio operators like g'add
, g'mul
and g'gain
(gain is just addition composed with multiplication of a constant, and the special gain
function does this in an efficient way).
The audio graph must respect certain rules: it must be fully connected, it must have a unique terminal node, it must have at least one generator, it must have no orphan nodes, it must not have duplicate edges between nodes, etc. Violating any of these rules will result in a type error at compile-time.
The graph structure is represented using incoming edges, so processors have only one incoming edge whereas accumulators have an arbitrary number of incoming edges, as we see below. Play it and you'll hear an echo effect!
pwf :: Array (Tuple Number Number)
pwf =
join
$ map
( \i ->
map
( \(Tuple f s) ->
Tuple (f + 0.11 * toNumber i) s
)
[ Tuple 0.0 0.0, Tuple 0.02 0.7, Tuple 0.06 0.2 ]
)
(range 0 400)
kr = 20.0 / 1000.0 :: Number -- the control rate in seconds, or 50 Hz
epwf = evalPiecewise kr
initialOnset = { onset: Nothing } :: { onset :: Maybe Number }
scene ::
forall a.
Mouse ->
{ onset :: Maybe Number | a } ->
Number ->
Behavior (IAudioUnit D2 { onset :: Maybe Number | a })
scene mouse acc@{ onset } time = f time <$> click
where
f s cl =
IAudioUnit
( dup1
( (gain' 0.2 $ sinOsc (110.0 + (3.0 * sin (0.5 * rad))))
+ (gain' 0.1 (gainT' (epwf pwf s) $ sinOsc 440.0))
+ (gain' 0.1 $ sinOsc (220.0 + (if cl then (50.0 + maybe 0.0 (\t -> 10.0 * (s - t)) stTime) else 0.0)))
+ ( graph
{ aggregators:
{ out: Tuple g'add (SLProxy :: SLProxy ("combine" :/ SNil))
, combine: Tuple g'add (SLProxy :: SLProxy ("gain" :/ "mic" :/ SNil))
, gain: Tuple (g'gain 0.9) (SLProxy :: SLProxy ("del" :/ SNil))
}
, processors:
{ del: Tuple (g'delay 0.2) (SProxy :: SProxy "filt")
, filt: Tuple (g'bandpass 440.0 1.0) (SProxy :: SProxy "combine")
}
, generators:
{ mic: microphone
}
}
)
) \mono ->
speaker
$ ( (panner (-0.5) (merger (mono +> mono +> empty)))
:| (gain' 0.5 $ (play "forest"))
: Nil
)
)
(acc { onset = stTime })
where
rad = pi * s
stTime = case Tuple onset cl of
(Tuple Nothing true) -> Just s
(Tuple (Just y) true) -> Just y
(Tuple _ false) -> Nothing
click :: Behavior Boolean
click = map (not <<< isEmpty) $ buttons mouse