W3C home > Mailing lists > Public > public-audio@w3.org > April to June 2016

Re: Comments on AudioWorklet examples

From: Hongchan Choi <hongchan@google.com>
Date: Wed, 01 Jun 2016 18:27:16 +0000
Message-ID: <CAGJqXNsnvr3j9zvFBQVOZZwfGcukikoctF81AS9jOSd10NywAA@mail.gmail.com>
To: Joe Berkovitz <joe@noteflight.com>
Cc: Audio Working Group <public-audio@w3.org>
> But with AudioWorkletNodes, we can't take this nice behavior for granted.
Because of the synchronous nature of Node creation (you don't wait for a
Promise), the Processor won't exist yet at the exact moment the Node is
created.

I want the construction of AudioWorkletNode to be synchronous. This must be
ensured by design. We only need the promise for importing a script, not
instantiating of a node. In that sense, `created()` method might not be
necessary after all.

I am doing more research on ES6 class pattern, and I believe there are some
more features coming up soon (const/static) that might be beneficial for
our use case. I think we should explore both directions: the implicit/terse
way and the explicit/verbose way.

On Wed, Jun 1, 2016 at 11:18 AM Joe Berkovitz <joe@noteflight.com> wrote:

> .            .       .    .  . ...Joe
>
> Joe Berkovitz
> President
> Noteflight LLC
>
> +1 978 314 6271
>
> 49R Day Street
> Somerville MA 02144
> USA
>
> "Bring music to life"
> www.noteflight.com
>
> On Wed, Jun 1, 2016 at 1:42 PM, Hongchan Choi <hongchan@google.com> wrote:
>
>> It took me a day to think about questions you raised. I think we have an
>> important decision to make. Please find my responses inline:
>>
>>
>> Hi Hongchan,
>>>
>>> Thanks for responding! I will attempt some stunted responses from my
>>> phone since I'm traveling today, to keep the discussion going.
>>>
>>> Based on our previous discussion, I'd expect that there needs to be a
>>>> subclass constructor that explicitly copies these values out of the
>>>> AudioNodeOptions into the new node's AudioParams, like this:
>>>>
>>>> class BitcrusherNode extends AudioWorkletNode {
>>>>   constructor(ctx, options) {
>>>>     super(ctx, 'Bitcrusher', options);
>>>>     if (options.hasOwnProperty('bitDepth') {
>>>>       this.bitDepth.value = options.bitDepth;
>>>>     }
>>>>     // ...initialize other params, initial state up front, possibly
>>>> utilizing postMessage()...
>>>> }
>>>>
>>>
>>> Just to confirm: so this is a node definition, not the processor?
>>> Initializing these values from the main thread is a definitely better idea,
>>> but the pattern here is quite involved and verbose. I think we can start
>>> from this idea and try to simplify the syntax to find a sweet spot. I
>>> really need to think about having two definitions for both threads. Very
>>> explicit, but that also makes us diverge from Worklet infrastructure which
>>> I am a bit afraid of.
>>>
>>>
>>> Sorry, I don't see where this diverges from Worklets -- what am I
>>> missing?
>>>
>>
>> Worklet spec does not specify (nor expose) main thread interface. The
>> class definition is solely for off-main-thread processing. Now I am
>> realizing this is the biggest difference between AudioWorklet and other
>> worklet variants. Let's say you can define the node representation as shown
>> above, how can the browser match the corresponding processor object? That
>> part seems also magical to me. In that case, we might have to introduce a
>> new method that runs within global scope to define a `name` and to match
>> the processor accordingly. Is that okay?
>>
>
> The only magic is in the second argument to the AudioWorkletNode
> constructor (DOMString name), that determines which registered Processor is
> to be used on the audio side.
>
> The only thing new I've done here is say: it's useful to extend
> AudioWorkletNode into a subclass (here, that's BitcrusherNode). The
> subclass calls the superclass constructor, which has the same signature it
> always had: [Constructor(AudioContext audioContext, DOMString name,
> optional AudioNodeOptions options)]. The "name" argument is just filled in
> by the subclass, so that the person instantiating the subclass doesn't need
> to know it. In my above reworked example, it's 'Bitcrusher', just as it was
> in your original example where you call new AudioWorkletNode(ctx,
> 'Bitcrusher', {...}).
>
>
>>
>> The reason I made this a subclass is because otherwise I did not see how
>>> to make use of the custom AudioNodeOptions entries that your example
>>> employed  to initialize the bitcrusher AudioParams. The options are
>>> constructor args, so unless there is some behavior in AudioWorkletNode that
>>> automagically initializes AudioParams with matching names, I don't see how
>>> else it can happen.
>>>
>>
>>> If you do envision this init behavior then please make it explicit.
>>> Would it apply only to AudioParams? (I assume so because nothing else about
>>> a Processor can be declared)
>>>
>>> Of course one could leave the Params out of the ctor options argument
>>> and just initialize them after creating the AudioWorkletNode.
>>>
>>
>> One thing I also want to avoid is over-complicating the definition of
>> AudioWorkletNode. Your idea makes perfect sense to me, but I think it is
>> too burdensome to developers having to define a node and a processor only
>> for the initialization of AudioParams. Beyond the parameter initialization,
>> what else can we do? You can define a new method, but it is just a wrapper
>> function of postMessage(). I agree that being explicit always win at the
>> end, so this is something we have to decide.
>>
>
> I want to make way for the group to chime in here. Personally [not as
> chair] I think it's best if everything about initialization is explicit.
>
>>
>>
>>> The only opportunity we have to discover parameter descriptors is before
>>>> the success of the promise from the script import for a node type.
>>>>
>>>   So *maybe* it works like this: after the script is imported for an
>>>> AudioWorkletProcessor, the Processor gets instantiated once prior to the
>>>> success of the Promise and its paramDescriptors getter is examined; the
>>>> result determines the set of parameters exposed by subsequent nodes of that
>>>> type. The Processor instance is then thrown away; its only purpose was to
>>>> figure out what params exist for this node type.
>>>>
>>>
>>> Can you elaborate on this a bit more? I am not sure if I am following
>>> you.
>>>
>>>
>>> Along the lines of my previous response, I was asking myself how the API
>>> can know what parameters exist for a Node whose Processor doesn't exist
>>> yet. Yet it is the Processor that exposes the param descriptors.
>>>
>>
>>> The only way I see for this to happen is for a Processor to be created
>>> as part it's registration, solely for the purpose of interrogating its
>>> param descriptors.
>>>
>>
>> I am still not sure about what is being asked, but let me try; you cannot
>> know the parameters of a node before its instantiation. Isn't this how a
>> regular audio node works? After the construction and when created() method
>> gets called, the node reference and AudioParams within will be accessible.
>> Or are you asking we implement the new feature for AudioWorkletNode? I can
>> see the benefit of figuring out parameters from a node that hasn't been
>> created, but I believe it is slightly off-topic.
>>
>
> I'm not saying one needs to know the parameters of a Node *before* its
> instantiation.
>
> I'm saying one needs to know the parameters of a Node *immediately
> following* its instantiation. You can do this with a regular node:
>
>     var g = new GainNode(ctx);
>     g.gain.value = 1.5;     // g.gain exists right away!
>
> But with AudioWorkletNodes, we can't take this nice behavior for granted.
> Because of the synchronous nature of Node creation (you don't wait for a
> Promise), the Processor won't exist yet at the exact moment the Node is
> created. We want to be able to do this:
>
>    var bc = new AudioWorkletNode(ctx, 'Bitcrusher');  // or, new
> BitcrusherNode(ctx) in my version
>    bc.bitDepth.value = 1;   // bc.bitDepth is there right away!
>
> It seems to me that bc.bitDepth can only exist as an AudioParam at that
> point if we already know the parameter descriptors declared by the
> Processor registered for 'Bitcrusher'. And no one on the main thread side
> knows exactly when the AudioWorkletProcessor will be created. That is why I
> think this information needs to be made available to the main thread side
> somehow, as a byproduct of registering the AudioWorkletProcessor on the
> audio side.
>
>
Received on Wednesday, 1 June 2016 18:27:58 UTC

This archive was generated by hypermail 2.3.1 : Wednesday, 1 June 2016 18:27:58 UTC