Skip to content

Commit

Permalink
Singlesub (#42)
Browse files Browse the repository at this point in the history
* Allows for the updating of a single subgraph

Co-authored-by: Mike Solomon <mike@wavr.so>
  • Loading branch information
Mike Solomon and Mike Solomon authored Mar 15, 2022
1 parent dc97925 commit 5d54433
Show file tree
Hide file tree
Showing 24 changed files with 674 additions and 287 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.1] - 2022-03-15

- Adds updating for single subgraph

## [0.7.0] - 2022-02-27

- Changes representation of audio parameters to better mirror the API.
Expand Down
12 changes: 5 additions & 7 deletions cheatsheet/Branching.purs
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,20 @@ import WAGS.WebAPI (BrowserAudioBuffer)

type World = { myBuffer :: BrowserAudioBuffer }

type MyGraph1
=
type MyGraph1 =
( speaker :: AU.TSpeaker /\ { gain :: Unit }
, gain :: AU.TGain /\ { osc :: Unit }
, osc :: AU.TSinOsc /\ {}
)

type MyGraph2
=
type MyGraph2 =
( speaker :: AU.TSpeaker /\ { gain :: Unit }
, gain :: AU.TGain /\ { buf :: Unit }
, buf :: AU.TLoopBuf /\ {}
)

initialFrame :: IxWAG RunAudio RunEngine Frame0 Unit () MyGraph1 Number
initialFrame = ipatch { microphone: empty, mediaElement: empty } $> 42.0
initialFrame = ipatch { microphone: empty, mediaElement: empty, subgraphs: {}, tumults: {} } $> 42.0

branch1
:: forall proof
Expand All @@ -45,7 +43,7 @@ branch1 =
if e.time % 2.0 < 1.0 then
Right $ ichange { osc: 330.0 } $> a
else
Left $ icont branch2 (ipatch { microphone: empty, mediaElement: empty } :*> ichange { buf: e.world.myBuffer } $> "hello")
Left $ icont branch2 (ipatch { microphone: empty, mediaElement: empty, subgraphs: {}, tumults: {} } :*> ichange { buf: e.world.myBuffer } $> "hello")

branch2
:: forall proof
Expand All @@ -64,7 +62,7 @@ branch2 =
}
$> a
else
Left $ icont branch1 (ipatch { microphone: empty, mediaElement: empty } $> 42.0)
Left $ icont branch1 (ipatch { microphone: empty, mediaElement: empty, subgraphs: {}, tumults: {} } $> 42.0)

piece :: Scene (BehavingScene Unit World ()) RunAudio RunEngine Frame0 Unit
piece = const initialFrame @!> branch1
2 changes: 1 addition & 1 deletion cheatsheet/CreateScene.purs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type MyGraph
)

initialFrame :: IxWAG RunAudio RunEngine Frame0 Unit () MyGraph Unit
initialFrame = ipatch { microphone: empty, mediaElement: empty }
initialFrame = ipatch { microphone: empty, mediaElement: empty, subgraphs: {}, tumults: {} }

piece :: Scene (BehavingScene Unit Unit ()) RunAudio RunEngine Frame0 Unit
piece =
Expand Down
2 changes: 1 addition & 1 deletion examples/kitchen-sink/KitchenSink/TLP/Allpass.purs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ doAllpass =
else
Left
$ icont doLowpass Ix.do
ipatch { microphone: Nothing, mediaElement: Nothing }
ipatch { microphone: Nothing, mediaElement: Nothing, subgraphs: {}, tumults: {} }
ichange
{ lowpass: 300.0
, buf: myBuffer
Expand Down
2 changes: 1 addition & 1 deletion examples/kitchen-sink/KitchenSink/TLP/Convolver.purs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ doConvolver =
Right (ipure lsig)
else
Left
$ icont doAllpass (ipatch { microphone: Nothing, mediaElement: Nothing } :*> ichange (ksAllpassCreate world) $> lsig)
$ icont doAllpass (ipatch { microphone: Nothing, mediaElement: Nothing, subgraphs: {}, tumults: {} } :*> ichange (ksAllpassCreate world) $> lsig)
2 changes: 1 addition & 1 deletion examples/kitchen-sink/KitchenSink/TLP/SawtoothOsc.purs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ doSawtoothOsc =
ipure lsig
else
Left
$ icont doConvolver (ipatch { microphone: Nothing, mediaElement: Nothing } :*> ichange { buf: { buffer: myBuffer, onOff: _on }, convolver: reverb } $> lsig)
$ icont doConvolver (ipatch { microphone: Nothing, mediaElement: Nothing, subgraphs: {}, tumults: {} } :*> ichange { buf: { buffer: myBuffer, onOff: _on }, convolver: reverb } $> lsig)
2 changes: 1 addition & 1 deletion examples/kitchen-sink/KitchenSink/TLP/TriangleOsc.purs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ doTriangleOsc =
Left
$ icont doSquareOsc
if lsig.iteration `mod` 2 == 0 then Ix.do
ipatch { microphone: Nothing, mediaElement: Nothing }
ipatch { microphone: Nothing, mediaElement: Nothing, subgraphs: {}, tumults: {} }
ichange { squareOsc: 220.0 }
ipure lsig
else Ix.do
Expand Down
244 changes: 244 additions & 0 deletions examples/patching/Patching.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
module WAGS.Example.Patching where

import Prelude

import Control.Alt ((<|>))
import Control.Apply.Indexed ((:*>))
import Control.Comonad (extract)
import Control.Comonad.Cofree (Cofree, deferCofree)
import Control.Comonad.Cofree.Class (unwrapCofree)
import Data.Foldable (for_)
import Data.Identity (Identity(..))
import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap)
import Data.Tuple (Tuple(..))
import Data.Tuple.Nested (type (/\))
import Data.Typelevel.Num (class Lt, class Pred, D0, D40, d39, pred)
import Data.Vec as V
import Effect (Effect)
import Effect.Aff.Class (class MonadAff)
import Effect.Class (class MonadEffect)
import FRP.Event (subscribe)
import FRP.Event.Mouse (down, up)
import Halogen as H
import Halogen.Aff (awaitBody, runHalogenAff)
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.VDom.Driver (runUI)
import Type.Proxy (Proxy(..))
import WAGS.Change (ichange')
import WAGS.Control.Functions.Graph (iloop, (@!>))
import WAGS.Control.Functions.Subgraph as SG
import WAGS.Control.Indexed (IxWAG)
import WAGS.Control.Types (Frame0, Scene, SubScene)
import WAGS.Create.Optionals (gain, input, playBuf, subgraphSingleSetter)
import WAGS.Graph.AudioUnit (TGain, TSinOsc, TSpeaker, TSubgraph)
import WAGS.Graph.Parameter (_off, _on)
import WAGS.Interpret (class AudioInterpret, AsSubgraph(..), close, context, decodeAudioDataFromUri, makeFFIAudioSnapshot)
import WAGS.Patch (PatchedSubgraphInput(..), ipatch)
import WAGS.Run (RunAudio, RunEngine, TriggeredRun, TriggeredScene(..), runNoLoop)
import WAGS.WebAPI (AudioContext, BrowserAudioBuffer)

type World = { atar :: BrowserAudioBuffer }

newtype SGWorld = SGWorld Boolean

vec :: V.Vec D40 Unit
vec = V.fill (const unit)

newtype TriggerSG = TriggerSG
( forall proof
. IxWAG RunAudio RunEngine proof Unit FullGraph FullGraph Unit
)

unTriggerSG
:: TriggerSG
-> forall proof
. IxWAG RunAudio RunEngine proof Unit FullGraph FullGraph Unit
unTriggerSG (TriggerSG ta) = ta

class CofreeSubgraph0 (n :: Type) where
cofreeSubgraph0 :: n -> Cofree Identity TriggerSG

instance CofreeSubgraph0 D0 where
cofreeSubgraph0 n = deferCofree
( \_ -> Tuple
( TriggerSG
( ichange' (Proxy :: _ "sg") (subgraphSingleSetter n (SGWorld true)) *> ichange' (Proxy :: _ "sg2") (subgraphSingleSetter n (SGWorld true))
)
)
( Identity $ deferCofree
( \_ -> Tuple
( TriggerSG
( ichange' (Proxy :: _ "sg") (subgraphSingleSetter n (SGWorld false)) *> ichange' (Proxy :: _ "sg2") (subgraphSingleSetter n (SGWorld true))
)
)
(Identity triggers0)
)
)
)
else instance
( Pred n n'
, Lt n D40
, CofreeSubgraph0 n'
) =>
CofreeSubgraph0 n where
cofreeSubgraph0 n = deferCofree
( \_ -> Tuple
( TriggerSG
( ichange' (Proxy :: _ "sg") (subgraphSingleSetter n (SGWorld true)) *> ichange' (Proxy :: _ "sg2") (subgraphSingleSetter n (SGWorld true))
)
)
( Identity $ deferCofree
( \_ -> Tuple
( TriggerSG
( ichange' (Proxy :: _ "sg") (subgraphSingleSetter n (SGWorld false)) *> ichange' (Proxy :: _ "sg2") (subgraphSingleSetter n (SGWorld false))
)
)
(Identity (cofreeSubgraph0 (pred n)))
)
)
)

---

triggers0 :: Cofree Identity TriggerSG
triggers0 = cofreeSubgraph0 d39

subPiece0
:: forall audio engine
. AudioInterpret audio engine
=> Int
-> BrowserAudioBuffer
-> SubScene "buffy" () SGWorld audio engine Frame0 Unit
subPiece0 _ atar = mempty # SG.loopUsingScene \(SGWorld oo) _ ->
{ control: unit
, scene:
{ buffy: playBuf
{ onOff: if oo then _on else _off
}
atar
}
}

subPiece1
:: forall audio engine
. AudioInterpret audio engine
=> Int
-> SubScene "gnn" (beep :: Unit) SGWorld audio engine Frame0 Unit
subPiece1 i = mempty # SG.loopUsingScene \(SGWorld oo) _ ->
{ control: unit
, scene:
{ gnn: gain
(if oo then 0.2 else 0.0)
(input (Proxy :: _ "beep"))
}
}

type FullGraph =
( speaker :: TSpeaker /\ { gn :: Unit }
, gn :: TGain /\ { sg :: Unit, sg2 :: Unit }
, sg :: TSubgraph D40 "buffy" () SGWorld /\ {}
, sg2 ::
TSubgraph D40 "gnn" (beep :: Unit) SGWorld
/\ { beep :: Unit }
, beep :: TSinOsc /\ {}
)

createFrame :: BrowserAudioBuffer -> IxWAG RunAudio RunEngine Frame0 Unit () FullGraph Unit
createFrame atar =
ipatch
{ microphone: Nothing
, mediaElement: Nothing
, subgraphs:
{ sg: PatchedSubgraphInput
{ controls: vec
, scenes: AsSubgraph (\i _ -> subPiece0 i atar)
, envs: (const $ const $ SGWorld false)
}
, sg2: PatchedSubgraphInput
{ controls: vec
, scenes: AsSubgraph (\i _ -> subPiece1 i)
, envs: (const $ const $ SGWorld false)
}
}
, tumults: {}
} :*> ichange' (Proxy :: _ "gn") 1.0
:*> ichange' (Proxy :: _ "beep") { freq: 550.0, onOff: _on }

piece :: Scene (TriggeredScene Unit World ()) RunAudio RunEngine Frame0 Unit
piece = (\(TriggeredScene env) -> createFrame env.world.atar $> { triggers0 }) @!> iloop \_ acc ->
(unTriggerSG $ extract acc.triggers0) $> acc { triggers0 = unwrap $ unwrapCofree acc.triggers0 }

main :: Effect Unit
main =
runHalogenAff do
body <- awaitBody
runUI component unit body

type State =
{ unsubscribe :: Effect Unit
, audioCtx :: Maybe AudioContext
, freqz :: Array String
}

data Action
= StartAudio
| StopAudio

component :: forall query input output m. MonadEffect m => MonadAff m => H.Component query input output m
component =
H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
}

initialState :: forall input. input -> State
initialState _ =
{ unsubscribe: pure unit
, audioCtx: Nothing
, freqz: []
}

render :: forall m. State -> H.ComponentHTML Action () m
render { freqz } = do
HH.div_
$
[ HH.h1_
[ HH.text "Patching test" ]
, HH.button
[ HE.onClick \_ -> StartAudio ]
[ HH.text "Start audio" ]
, HH.button
[ HE.onClick \_ -> StopAudio ]
[ HH.text "Stop audio" ]
]
<> map (\freq -> HH.p [] [ HH.text freq ]) freqz

handleAction :: forall output m. MonadEffect m => MonadAff m => Action -> H.HalogenM State Action () output m Unit
handleAction = case _ of
StartAudio -> do
audioCtx <- H.liftEffect context
ffiAudio <- H.liftEffect $ makeFFIAudioSnapshot audioCtx
atar <-
H.liftAff $ decodeAudioDataFromUri
audioCtx
"https://freesound.org/data/previews/100/100981_1234256-lq.mp3"
unsubscribe <-
H.liftEffect
$ subscribe
( runNoLoop
((pure unit) <|> (up $> unit) <|> (down $> unit))
(pure { atar })
{}
(ffiAudio)
piece
)
(\(_ :: TriggeredRun Unit ()) -> pure unit)
H.modify_ _ { unsubscribe = unsubscribe, audioCtx = Just audioCtx }
StopAudio -> do
{ unsubscribe, audioCtx } <- H.get
H.liftEffect unsubscribe
for_ audioCtx (H.liftEffect <<< close)
H.modify_ _ { unsubscribe = pure unit, audioCtx = Nothing }
6 changes: 6 additions & 0 deletions examples/patching/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<script src="index.js"></script>
<body>
</body>
</html>
12 changes: 5 additions & 7 deletions examples/skip-machine/SkipMachine.purs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ vol = 1.4 :: Number

type World = { hamlet :: BrowserAudioBuffer }

type SceneType
=
type SceneType =
( speaker :: TSpeaker /\ { buf :: Unit }
, buf :: TPlayBuf /\ {}
)
Expand Down Expand Up @@ -88,7 +87,7 @@ cf nea len = f nea 0.0
f ct x n = let hit = n + 0.04 > x in mkCofree (if hit then Just (hnea ct) else Nothing) (f (if hit then (rotate ct) else ct) (if hit then x + section else x))

piece :: Scene (BehavingScene Unit World ()) RunAudio RunEngine Frame0 Unit
piece = (\e@(BehavingScene { world: { hamlet } }) -> ipatch { microphone: empty, mediaElement: empty } :*> myChange e (cf order (bufferDuration hamlet))) @!> iloop myChange
piece = (\e@(BehavingScene { world: { hamlet } }) -> ipatch { microphone: empty, mediaElement: empty, subgraphs: {}, tumults: {} } :*> myChange e (cf order (bufferDuration hamlet))) @!> iloop myChange

easingAlgorithm :: Cofree ((->) Int) Int
easingAlgorithm =
Expand All @@ -103,8 +102,7 @@ main =
body <- awaitBody
runUI component unit body

type State
=
type State =
{ unsubscribe :: Effect Unit
, audioCtx :: Maybe AudioContext
, freqz :: Array String
Expand Down Expand Up @@ -158,8 +156,8 @@ handleAction = case _ of
unsubscribe <-
H.liftEffect
$ subscribe
(run (pure unit) (pure { hamlet }) { easingAlgorithm } (ffiAudio) piece)
(\(_ :: BehavingRun Unit ()) -> pure unit)
(run (pure unit) (pure { hamlet }) { easingAlgorithm } (ffiAudio) piece)
(\(_ :: BehavingRun Unit ()) -> pure unit)
H.modify_ _ { unsubscribe = unsubscribe, audioCtx = Just audioCtx }
Freqz freqz -> H.modify_ _ { freqz = freqz }
StopAudio -> do
Expand Down
Loading

0 comments on commit 5d54433

Please sign in to comment.