- From: Joseph Berkovitz <joe@noteflight.com>
- Date: Mon, 2 Feb 2015 11:57:53 -0500
- To: Steven Yi <stevenyi@gmail.com>
- Cc: public-audio@w3.org
- Message-Id: <C5B62F29-5456-4FAF-9DC1-9F5D9B4F26AD@noteflight.com>
Thanks Steven. Could you please post your comment about jitter and the Brandt/Dannenberg reference to the issue thread? Those are valuable points. Best, …Joe On Feb 2, 2015, at 11:49 AM, Steven Yi <stevenyi@gmail.com> wrote: > Hi Joe, > > Thank you for filing that issue, I have subscribed to it and will try > to add some notes to it. Regarding #5, I should have noted that I > think the "Two Clocks" design with ahead of time scheduling of a > partial set of events is completely valid for a number of realtime > uses cases. (I believe it's the same as Brandt and Dannenberg's > "Forward Synchronous Model", as discussed in [1]). However, I think > the design also best works when some expectations can be made about > how bounded the jitter can be, which with the JS Main thread seems > very difficult. > > Thanks! > steven > > > [1] - "Time in Distributed Real-Time Systems", Eli Brandt and Roger B. > Dannenberg. 1999. Available at: > http://www.cs.cmu.edu/~rbd/papers/synchronous99/synchronous99.pdf > > > > On Mon, Feb 2, 2015 at 11:09 AM, Joseph Berkovitz <joe@noteflight.com> wrote: >> Hi Steven, >> >> Many points here worth responding to, but I will just reference your #5 >> since it was also raised by some other people at the WAC and I think it is >> an important issue for both realtime and offline audio rendering. >> >> Please see the newly filed >> https://github.com/WebAudio/web-audio-api/issues/473 >> >> …Joe >> >> >> >> On Jan 30, 2015, at 7:57 PM, Steven Yi <stevenyi@gmail.com> wrote: >> >> Hello All, >> >> First, it was a great pleasure to be at the Web Audio conference. I >> enjoyed the sessions and gigs and getting to the meet the other >> members of community that I did. Cheers to IRCAM and Mozilla for the >> lovely conference! >> >> That said, I have some comments and questions about the Web Audio API >> and specification. (Note: these comments are in reference to the 06 >> January 2015 draft, found at >> http://webaudio.github.io/web-audio-api/.) >> >> #1 - The specification is not clear to me when a node become live. I >> assume it is when a node is connected to the active part of the audio >> graph that is "live" and processing. Since node creation and graph >> assembly is done in the JS Main thread, it seems that the following >> from "3.3 Example: Mixer with Send Busses", it possible that nodes >> might get attached across buffers in the audio thread: >> >> compressor = context.createDynamicsCompressor(); >> >> // Send1 effect >> reverb = context.createConvolver(); >> // Convolver impulse response may be set here or later >> >> // Send2 effect >> delay = context.createDelay(); >> >> // Connect final compressor to final destination >> compressor.connect(context.destination); >> >> // Connect sends 1 & 2 through effects to main mixer >> s1 = context.createGain(); >> reverb.connect(s1); >> s1.connect(compressor); >> >> s2 = context.createGain(); >> delay.connect(s2); >> s2.connect(compressor); >> >> For example, could it be the case that "s1.connect(compresor)" above >> happens just before buffer n starts to generate, and >> "s2.connect(compressor)" happens such that it starts in when buffer n >> + 1 is generating? >> >> If this is the case, would connecting the compressor to the >> context.destination at the end of the example, rather than the >> beginning, guarantee that the graph of nodes connected to the >> compressor are started at the same time? If so, then maybe this >> aspect of node graph creation could be clarified and the example in >> 3.3 updated so that the sub-graph of nodes is clearly formed before >> attaching to the active audio-graph. >> >> #2 - Following from #1, what would happen if one is dynamically >> altering a graph to remove an intermediary node? For example, lets >> say one has a graph like: >> >> gain = contxt.createGainNode(); >> compressor = context.createDynamicsCompressor(); >> reverb = context.createConvolver(); >> gain.connect(reverb); >> reverb.connect(compressor); >> compressor.connect(context.destination); >> >> and later the user decides to remove the reverb with something like: >> >> reverb.disconnect(); >> // gain.disconnect(); >> gain.connect(compressor); >> >> (Assuming the above uses a gain node as a stable node for other nodes >> to attach to.) My question is: when does connect and disconnect >> happen? Does it happen at block boundaries? I assume it must or a >> graph can get in a bad state if the graph changes while a block is >> being processed. >> >> Also, without the gain.disconnect(), will there be a hidden reference >> to the reverb from gain? (I guess a "connection" reference according >> to 2.3.3). If so, this seems like it could be a source of a memory >> leak (assuming that the above object references to reverb are all >> cleared from the JS main thread side). >> >> #3 - In "2.3.2 Methods", for an AudioNode to connect to another audio >> node, it is not clear whether fan-out/fan-in is supported. The >> documentation for connecting to AudioParams explicitly states that >> this is supported. Should the first connect() method documentation be >> clarified for this when connecting to nodes? >> >> #4 - Also in regards to 2.3.2, the API of disconnect() seems odd as it >> does not mirror connect(). connect() is given an argument of what node >> or audioParam to connect to. disconnect() however does not have a >> target argument. It's not clear then what this disconnects from. For >> example, if I connect a node to two different nodes and also to >> another node's parameter, then call disconnect, what happens? As it >> is now, it doesn't seem possible then to create a GUI editor where one >> could connect the output of a node to multiple other nodes/params, >> then click and disconnect a single connection. >> >> #5 - In the music systems I've seen, event processing is done within >> the audio-thread. This generally happens for each buffer, something >> like: >> >> 1. Process incoming messages >> 2. Process a priority queue of pending events >> 3. Handle audio input >> 4. Run processing graph for one block >> 5. Handle audio output >> >> I'm familiar with this from Csound and SuperCollider's engines, as >> well as the design in my own software synthesizer Pink. (Chuck's >> design follow the same basic pattern above, but on a sample-by-sample >> basis.) >> >> As it is today, the Web Audio API does not have any kind of reified >> event object. One can schedule some things like automations via >> param's setXXXatTime() methods and have that run within the time of >> the audio engine, but there is nothing built-in for events in the Web >> Audio API. >> >> Now, I have no issues with the Web Audio API not having a concrete >> event system, and think it should not have one, as people have >> different notions and needs out of what is encoded in an event. >> However, I think that there should be a way to create one's own event >> system, one that is clocked to the same audio system clock (i.e. run >> within the audio thread). >> >> I was a bit concerned when at the conference there was mention of "A >> Tale of Two Clocks". The design of trying to reference two clocks can >> not, by definition, allow for a queue of events to be processed >> synchronously with audio. If one formalizes events processing >> functions and audio processing functions as functions of time, by >> having two clocks you get two different variables, ta and tb, which >> are not equivalent unless the clocks are proven to advance at the same >> exact rate (i.e. ta0 == tb0, ta1 == tb1, ... tan == tbn). However, >> the JS Main thread and audio thread are not run at the same rate, so >> we can at best implement some kind of approximation, but it can not be >> a formally correct solution. >> >> Event processing in a thread other than the audio thread has problems. >> One mentioned at the conference was what to do with offline rendering, >> where the clock of an audio engine runs faster than realtime, and may >> advance faster or slower in terms of wall-clock time while rendering, >> depending on how heavy the processing needs of the graph is. Second, >> I seemed to remember hearing a problem during one of the concerts when >> I turned off my phone's screen and I continued to hear audio but all >> events stopped, then a number of events fired all at once when I >> turned my screen back on. The piece used an event scheduling system >> that ran in the JS Main thread. I assume this situation is similar to >> what could happen with backgrounded tabs, but I'm not quite sure about >> all this. Either way, I think there are real problems here that need >> to be addressed. >> >> This also leads to a bigger question: with Web Audio, if I run the >> same project twice that uses an event system to reify graph >> modifications in time (as events in audio engines are mostly used for, >> i.e. alloc this graph of nodes and add to the live audio graph), will >> I get the same result? Assuming to use only referentially transparent >> nodes (i.e. no random calculations), I believe the only way to >> guarantee this is if the event system is processed as part of the >> audio thread. >> >> Now, what can a user do with Web Audio to create their own Event >> system that is in sync with the audio thread? Currently, there is the >> ScriptProcessorNode. Of course, the design of ScriptProcessorNode is >> deeply flawed for all the reasons discussed at the conference >> (Security, Inefficient due to context switching, potential for >> breakups, etc.). However, what it does do is allow for one to process >> events in sync with the audio thread, allowing to build formally >> correct audio systems where one processes event time according to the >> same time as is used by the audio nodes. Additionally, according to >> those events, one can dynamically modify the graph (i.e. add new >> instances of a sub-graph of nodes to the live graph, representing a >> "note"), via reference to other nodes and the audio context. So while >> flawed in terms of performance and security, it does allow one to >> build correct systems that generate consistent output. >> >> My concern is that there was discussion of not only deprecating >> ScriptProcessorNode, but removing it altogether. I would have no >> problems with this, except that from reading the current specification >> for AudioWorker, I do not see how it would be possible to create an >> event system with it. While one can pass messages to and from an >> AudioWorker, one has no access to the AudioContext. In that regards, >> one can not say, within an AudioWorker, create new nodes and attach to >> the context.destination. I am not very familiar with transferables and >> what can be passed between the AudioWork and the JS Main thread via >> postMessage, but I assume AudioNodes can not be made transferable. >> >> At this point, I'm questioning what can be done. It seems >> AudioWorker's design is not meant for event processing (fair enough), >> and ScriptProcessor can only do this by accident and not design. Is >> there any solution to this problem with the Web Audio API moving >> forward? For example, would this group be willing to consider >> extending the API for non-audio nodes? (Processing nodes?). If >> processing nodes could be added that has a larger context than what is >> proposed for AudioWorkGlobalContext--say, has access to the >> AudioContext, and can modify the audio node graph dynamically--I could >> see it as a solution to allow building higher level constructs like an >> event system. >> >> #6 - For the AudioWorker specification, I think it would be useful to >> have clarification on when postMessage is processed. In 2.11.1.2, it >> has a link to "the algorithm defined by the Worker Specification". >> That in turn mentions: >> >> "The postMessage() method on DedicatedWorkerGlobalScope objects must >> act as if, when invoked, it immediately invoked the method of the same >> name on the port, with the same arguments, and returned the same >> return value." >> >> If it meant to be processed immediately, then this can cause problems >> if the AudioWorker is already part of a live graph and values mutate >> while an audio worker is processing a block. I think it would be good >> to have clarification on this, perhaps with a recommendation that in >> onaudioprocess functions, one should make a local copy of a value of a >> mutable value and use that for the duration of onaudioprocess to get a >> consistent result for the block. >> >> #7 - Related to #6, I noticed in "2.11.3.1 A Bitcrusher Node", the >> example uses a phaser variable that is scoped to the AudioWorker. I >> assume this would then be on the heap. This is perhaps more of general >> JS question, but I normally see in block-based audio programming that >> for a process() function, one generally copies any state variables of >> a node/ugen/etc. to local variables, runs the audio for-loop with >> local variable, then saves the state for the next run. This is done >> for performance (better locality, stack vs. heap access, better >> compiler optimizations, etc.). I don't know much about JavaScript >> implementations; can anyone comment if these kinds of optimizations >> are effective in JS? If so, the example might benefit from rewriting >> and give some guidance. (i.e. phase and lastDataValue are copied to a >> local var before the for-loop, and saved again after the for-loop, in >> onaudioprocess). >> >> Thanks! >> steven >> >> >> >> . . . . . ...Joe >> >> Joe Berkovitz >> President >> >> Noteflight LLC >> Boston, Mass. >> phone: +1 978 314 6271 >> www.noteflight.com >> "Your music, everywhere" >> . . . . . ...Joe Joe Berkovitz President Noteflight LLC Boston, Mass. phone: +1 978 314 6271 www.noteflight.com "Your music, everywhere"
Received on Monday, 2 February 2015 16:58:30 UTC