- From: Ehsan Akhgari <ehsan.akhgari@gmail.com>
- Date: Thu, 16 May 2013 11:51:21 -0400
- To: Srikumar Karaikudi Subramanian <srikumarks@gmail.com>
- Cc: "Robert O'Callahan" <robert@ocallahan.org>, Chris Rogers <crogers@google.com>, "public-audio@w3.org" <public-audio@w3.org>
- Message-ID: <CANTur_5GQiwZtuROwB1=rt0P7mYRF4PpsS24s+QDRUBaKW8qPQ@mail.gmail.com>
On Thu, May 16, 2013 at 10:16 AM, Srikumar Karaikudi Subramanian <
srikumarks@gmail.com> wrote:
> To recap, here are what I understand to be the core issues with passing
> buffers to nodes of various types, including buffer source nodes, wave
> shaper nodes, convolver nodes, etc. - (please fix if you see any
> misunderstanding or omissions) -
>
> 1. Race conditions should not be possible. Setting a buffer parameter
> of a node and subsequent modification of that buffer should not affect the
> behaviour of the node in order for the system to not be racy (note: whether
> to allow that modification is an orthogonal issue). Current opinion (Robert
> O' Callahan and Ehsan Akhgari) is that the ideal behaviour in this case is
> that subsequent modification of the buffer should fail dramatically instead
> of subtly.
> 2. Backward compatibility needs to be maintained as much as possible -
> current styles of usage shouldn't break.
>
> The current standing suggestion is to neuter ArrayBuffer and Float32Arrays
> passed to nodes, to prevent them from being modified further. The advantage
> of this is that race conditions are avoided, and it can be arranged such
> that current interfaces aren't broken (by, for example, having getters
> return copies). However, buffers can no longer be shared between nodes. The
> disadvantage is that different nodes now MUST use different copies of a
> buffer, which increases demand on memory and decreases programming
> convenience.
>
> Another possibility -
>
> The proper solution to resolving race conditions due to data structures
> shared between threads is to use immutable data structures. The only binary
> data structure in Javascript that is immutable is the Blob (afaik). If the
> various nodes that currently use Float32Array buffer attributes are spec'd
> to accept Blob objects instead, perhaps with mime type
> "audio/x-raw;format=float32", we solve all race conditions. Data stored in
> a Blob object can now be safely shared between nodes. Implementations are
> free to choose whether to keep internal copies in nodes or not.
>
> However, current code would break. A Blob object "b" can be created from a
> Float32Array "arr" like this -
>
> var b = new Blob([arr], {type: "audio/x-raw;format=float32"});
>
> If a Float32Array attribute -- ex: wave shaper node's 'curve' attribute --
> is implemented to a) accept Blob buffers and b) when a Float32Array is
> assigned to it, it is first converted into a Blob, which is subsequently
> returned by the attribute's getter, we get compatibility with current code
> base.
>
Would that not break code which expects to get a Float32Array out of the
getter? Also, there are two other complications with that solution:
* I'm not sure if it's current possible to express attributes with one type
in the setter and another type in the getter.
* I don't see how that avoid copying in the common case of just setting the
property to a Float32Array. Blobs are not magically immutable, they store
a copy of the array that you pass them.
> The following kinds of code will work correctly without race conditions -
>
> var shaper1 = context.createWaveShaper(...);
> var shaper2 = context.createWaveShaper(...);
> var buffer = new Float32Buffer(..); // fill up with something.
>
> shaper1.curve = buffer; // First internal copy made.
> shaper2.curve = buffer; // Second internal copy made.
> shaper1.curve = shaper2.curve = buffer; // One internal copy made in
> shaper2 which is shared with shaper1.
>
> var b = new Blob([buffer], {type: "audio/x-raw;format=float32"}); // Copy
> of buffer made. buffer is free to be GC'd if no other use for it.
> shaper1.curve = shaper2.curve = b; // Shared immutable buffer. No race
> conditions.
> shaper1.curve = b; // Shared immutable buffer.
> shaper2.curve = b; // Shared immutable buffer.
> shaper3.curve = b.slice(64, 128, b.type); // Shared slice of immutable
> buffer. Also no race condition.
>
>
> The following kind of code will fail dramatically -
>
> var shaper = context.createWaveShaper(...);
> var buf = new Float32Buffer(100);
> shaper.curve = buf; // Internal copy made.
> for (var i = 0; i < 100; ++i) {
> shaper.curve[i] = something; // Array-style indexing into Blob will fail.
> }
>
>
Well, it seems like you're breaking backwards compatibility in a much worse
way. As far as I know, there is no other API on the web platform which
returns a different type of object from the setter.
> The following kind of code is probably irrelevant since I don't expect
> anyone (yet) to have set a buffer without filling it first -
>
> var shaper = context.createWaveShaper(...);
> var buf = new Float32Buffer(100);
> shaper.curve = buf; // Internal copy made.
> for (var i = 0; i < 100; ++i) {
> buff[i] = something; // Modifications to buff have no effect.
> }
>
>
Well, you would be surprised to see the types of mistakes like the above
that browser vendors need to deal with. :-) The fact that this code will
work without throwing and does not do what the author intends is a
problem. Basically, if shaper was a regular JS object, then this code
_would_ do what the author intended...
I'd like to wait for crogers to chime in at this point, I think we've
enumerated the possible alternatives here pretty well!
--
Ehsan
<http://ehsanakhgari.org/>
Received on Thursday, 16 May 2013 15:52:38 UTC