Open nick-thompson opened 7 months ago
Hi @nick-thompson! Happy to help with this one. Are you intending for the limiter to be applied to the runtime, or as an addition to the editor's default constant?
Awesome, thanks @mark-mxwl! I was imagining it would go right here: https://github.com/elemaudio/website/blob/main/src/components/playground/runtime.js#L46-L49. Basically whatever the user exports from their playground module, we evaluate it and then pass the results through some el.limit()
style function. The gotcha is that there is no el.limit
in the elementary standard library so this task will include writing a limiter or using the existing el.compress
with a suitable ratio. How's that sound?
Sounds good @nick-thompson! I think dialing in el.compress
as a limiter would work just fine (though a dedicated limiter module would be really cool to have at some point). I'll have a PR up sometime this weekend. 🙌
Great, thanks!
Hey @nick-thompson! I was hoping to make this one easy on you and just knock it out. However, I'm running into an issue. I'm still new to your library, so I may be overlooking the obvious, but here's where I'm at. Looking at el.compress
, it accepts an ElemNode
for the sidechain
and xn
parameters. From what I can gather, userOutput
returns an object of the type ElemNode
, which represents an audio signal within the context of this library. However, when userOutput
is passed to the compressor, it throws the following error:
Invariant Violation: Whoops, expecting a Node type here! Got: object
at invariant (webpack-internal:///./node_modules/invariant/browser.js:38:15)
at resolve (webpack-internal:///./node_modules/@elemaudio/core/dist/index.js:1121:3)
at Object.env (webpack-internal:///./node_modules/@elemaudio/core/dist/index.js:1259:59)
at Object.compress (webpack-internal:///./node_modules/@elemaudio/core/dist/index.js:1572:20)
at Runtime.runUserCode (webpack-internal:///./src/components/playground/runtime.js:41:74)
at async eval (webpack-internal:///./src/components/playground/index.js:99:19)
While the Playground editor itself accepts ElemNode
as valid input for el.compress
, the behavior is different in the runtime--even when userOutput
is an identical obj.
Any guidance you could offer would be greatly appreciated!
Hey @mark-mxwl, mind sharing the code you wrote? Without looking at it, my only guess is this: userOutput
there is intended to support either an array of output signals (for stereo or multi-channel rendering), or a single output signal (for mono rendering). If you get an array you'll have to unpack it and apply the limiter to both channels:
const limit = (xn) => el.compress(10, 100, -48, 4, xn, xn);
const userOutput = render();
const stats = Array.isArray(userOutput)
? await this.core.render(...userOutput.map(limit))
: await this.core.render(limit(userOutput), limit(userOutput));
Something like that, with more sensible parameters for the atk/rel/thresh/ratio.
The error you're seeing there just suggests that one of the arguments you've passed to el.compress
(likely the sidechain argument because we see el.env in the callstack) is not of type ElemNode
, so it's not valid type for the graph construction. It might be helpful to drop a debugger or a console log there to inspect the type before you pass it in.
@nick-thompson, really appreciate the help! Funny, but the code I had was nearly identical, just with the limiter dialed in as so:
const userOutput = render();
const limit = (n) => el.compress(1, 10, -3, 20, n, n);
const stats = Array.isArray(userOutput)
? await this.core.render(...userOutput.map(limit))
: await this.core.render(limit(userOutput), limit(userOutput));
If you log the user output from both the Playground UI and runtime.js, you return the same ElemNode
object. But running an isNode
check inside the Playground returns true for type NodeRepr_t
, and false in the runtime. See below:
True when logging from Playground; false when logging from runtime.js:
Object { symbol: "__ELEM_NODE__", hash: 520300429, kind: "sin", props: {}, children: {…} }
children: Object { hd: {…}, tl: 0 }
hash: 520300429
kind: "sin"
props: Object { }
symbol: "__ELEM_NODE__"
<prototype>: Object { … }
Ah, I got it. Just updated @elemaudio/core
and @elemaudio/web-renderer
to 3.2.x. The problem was the isNode
function itself: In the lib. version I was using, isNode
was checking for a .TAG value of 4 for symbol
, when userOutput.symbol
is, of course, a string. The isNode
func. in the updated library reflects this with a .TAG value of 1, for string. 🙌
I'll get a PR up in a bit.
Add a default limiter to the Playground which ideally stays out of the way but which would clamp anything going over 0dB. Mostly as a safety/panic mechanism to catch unexpected loud noises while the user is working