# 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