It’s possible to get an audio worklet running nicely in Observable, but it takes a few workarounds. Here’s how I did it:
This might be a little nicer if the Observable editor somehow supported cells containing an embedded script, instead of a bare string.
Also, the trick of returning a Promise that never resolves to prevent downstream cells from running early seems a bit obscure. Maybe there’s a more elegant way to do it?
Edit: republished after I found a better way to convert a string to a URL.
Yeah we need a better way for embedded scripts. I had a similar situation popup when making a Service Worker. I tried wrapping the raw script in a function, then toString the function and chopping the function framing off with substring. You can keep syntax highlighting then. See Offline embedded notebooks / Tom Larkworthy / Observable does it for a service worker. Not ideal though, and very fragile.
Mike Bostock suggested using the invalidation promise instead of a never resolved one which is much less dangerous, and seems to fit our use case of holding up a dataflow indefinitely.
The nice thing with the invalidation promise is that it will resolve if their is an upstream dependant change or code change, but of course you can keep returning it to hold off downstream indefinitely. So it kinda perfectly fits the gap we are plugging, and you can’t really accidentally cause a permenant resource leak.
I’m not sure how I would do that. The tricky bit is how to avoid yielding a new value for running when volume changes in a way that doesn’t change the value of running.
I tried this:
running = {
if (this) {
console.log("not changing running");
return invalidation; // no changes once the previous value is true
}
let result = volume > 0;
console.log("changing running to $result");
return result;
}
However, it seems that returning invalidation causes the downstream audioContext to be closed without creating a new one. This isn’t quite the same as not yielding anything at all.
here is how I replace the mutable with a yield (Audio Worklet Example / Tom Larkworthy / Observable) , with this construction you do not need to return invalidation promises, though you need to listen to the input change without going through dataflow so the cell does not reevaluate (ie. addEventListener to the viewof not the data stream)
I used to use mutables a fair amount but now I think I prefer this pattern as it avoid the ‘global’ though it does require more background knowledge to understand.
Interesting. The behavior is a little different, though. It works when you run the demo as-is, but for development, if you change the worklet code then it stops playing (due to invalidation cleanup) and doesn’t automatically restart. You have to wiggle the volume control to get it to go again.
So I fixed the example to provide the same behaviour by only awaiting for the input slider IF the context is not running. Thus if the cell is recomputed it skips the await.