Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NoiseGenerator Node #2416

Open
g200kg opened this issue Jan 21, 2016 · 72 comments
Open

Add NoiseGenerator Node #2416

g200kg opened this issue Jan 21, 2016 · 72 comments
Projects

Comments

@g200kg
Copy link

g200kg commented Jan 21, 2016

current possible choice may be:

  1. Javascript code with ScriptProcessor -- ScriptProcessor is deprecaed.
  2. AudioWorker -- Not yet sure, but It seems slightly elaborated for these simple function.
  3. BufferSourceNode with pre-rendered buffer -- may be ok but wastetfull?
    I think it is a bit cumbersome relative to other basic waveform.

hopefully, white / pink switchable is great.

@lcrespom
Copy link

I agree, I consider this lack an important shortcoming of the spec.

@rtoy
Copy link
Member

rtoy commented Jan 21, 2016

I don't have any real objections to a white noise generator.

@hoch
Copy link
Member

hoch commented Jan 21, 2016

Are we okay with the disparity from different vendors? If not, this should be defined clearly in the spec with formula or algorithm. I believe there are several ways of creating noise (some are complex), but I am not sure what needs to be specified: how do we define a 'spec-compliant' noise generator? SNR? Distribution?

Also if we are too strict about the noise generation process, I can already imagine the situation like: 'the noise generation in the spec is boring, I will just do it in my AudioWorker'.

In my view, it goes both ways - would love to hear what others think.

@lcrespom
Copy link

AFAIK white noise is just a sequence of random samples. Different browsers will have different algorithms for random number generation, but unless they are flawed (see http://hackaday.com/2015/12/28/v8-javascript-fixes-horrible-random-number-generator/), the difference should not be audible.

Other than that, of course there should be no reuse of previous sequences of random samples: each new sample should be freshly generated by the RNG.

For example, here is how I currently do noise on a ScriptProcessorNode:

processAudio(evt) {
    for (let channel = 0; channel < evt.outputBuffer.numberOfChannels; channel++) {
        const out = evt.outputBuffer.getChannelData(channel);
        for (let sample = 0; sample < out.length; sample++)
            out[sample] = this.playing ? this.gain * (Math.random() * 2 - 1) : 0;
    }
}

By the way, if you see something wrong, please let me know.

@rtoy
Copy link
Member

rtoy commented Jan 21, 2016

To me white noise has always been random samples, as lcrespom shows.

Questions that come to my mind:

  • What is the actual noise distribution? Additive white Gaussian noise?
  • Should browsers produce the exact same sequence?
    • Maybe. Useful if you critically depend on it, but for audio, perhaps not.
    • This probably means the random number generator needs to be specified. (Fortunately, there are lots of well-documented and tested generators out there.)
  • Should the NoiseSource node allow you to select a seed?
    • Probably so that you can have uncorrelated noise sources.
  • What about pink noise? How should that be specified?

And while we're dreaming, a constant source node would be kind of nice to have, even though it's pretty trivial do to, but perhaps a bit heavy-weight to use an AudioBufferSource node to do it.

@hughrawlinson
Copy link
Contributor

White noise would be a series of random samples, vendors using their browser implementations standard RNG is probably fine now that v8 has fixed that huge issue.

Probably shouldn't call a node 'NoiseGenerator' if it can only do one type of noise though. It'd be cool to have the full colour spectrum to choose from, but then we're getting into the "is this really necessary" territory. Playing "angel's advocate", the algorithms to generate the other kinds of noise are very straightforward, all of the noises in the "technical definitions" section of that article can be scaled with bitwise multiplication in the frequency domain, and it'd be useful to have functions for generating Ndb/octave decay and equal loudness curves.

@hughrawlinson
Copy link
Contributor

(not to mention all of the wonderful uses of noise as an AudioParam)

@hoch
Copy link
Member

hoch commented Jan 21, 2016

Yeap. I would say if we decide to make this happen, we need to cover as
many bases as we can - along with the solid specification.
On Thu, Jan 21, 2016 at 11:53 AM Hugh Rawlinson notifications@github.com
wrote:

White noise would be a series of random samples, vendors using their
browser implementations standard RNG is probably fine now that v8 has fixed
that huge issue.

Probably shouldn't call a node 'NoiseGenerator' if it can only do one type
of noise though. It'd be cool to have the full colour spectrum
https://en.wikipedia.org/wiki/Colors_of_noise to choose from, but then
we're getting into the "is this really necessary" territory. Playing
"angel's advocate", the algorithms to generate the other kinds of noise are
very straightforward, all of the noises in the "technical definitions"
section of that article can be scaled with bitwise multiplication in the
frequency domain, and it'd be useful to have functions for generating
Ndb/octave decay and equal loudness curves.


Reply to this email directly or view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/705#issuecomment-173689784
.

@lcrespom
Copy link

@hughrawlinson noise as an AudioParam should allow for Sample & Hold, so the user can specify the duration of each random number. That is how old synths do their noise LFO wave.
I also have source code for such node, I can post it if you think it is useful for the discussion.

@g200kg
Copy link
Author

g200kg commented Jan 21, 2016

About white noise, I think the first thing need to be decided is Gaussian or not.

These are not so different audibly, but have different loudness (if same peak amplitude), and have different behavior especially when used as S&H source.

Because Gaussian is more match to natural phenomena, white noise in analog synths are Gaussian. it can be generated by a box-muller's method or some approximation.

IMO (as a user), not need the exactly same number sequence but need exactly same distribution(sigma).
About pink noise, need just filtered by exactly same -3dB/Oct characteristic filter.

@rtoy
Copy link
Member

rtoy commented Jan 22, 2016

On Thu, Jan 21, 2016 at 11:53 AM, Hugh Rawlinson notifications@github.com
wrote:

White noise would be a series of random samples, vendors using their
browser implementations standard RNG is probably fine now that v8 has fixed
that huge issue.

Probably shouldn't call a node 'NoiseGenerator' if it can only do one type
of noise though. It'd be cool to have the full colour spectrum
https://en.wikipedia.org/wiki/Colors_of_noise to choose from, but then
we're getting into the "is this really necessary" territory. Playing
"angel's advocate", the algorithms to generate the other kinds of noise are
very straightforward, all of the noises in the "technical definitions"
section of that article can be scaled with bitwise multiplication in the
frequency domain, and it'd be useful to have functions for generating
Ndb/octave decay and equal loudness curves.

​Not exactly sure what "bitwise multiplication in the frequency domain"
means, but directly modifying the frequency domain signal (and the inverse
transforming to get the time domain signal) isn't usually bandlimited.
Extra care needed; or just do time-domain filtering​ (but designing the
filters probably isn't so easy).


Reply to this email directly or view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/705#issuecomment-173689784
.

Ray

@cwilso
Copy link
Contributor

cwilso commented Jan 22, 2016

Note that it's important for noise generators to NOT have the same sequence
of values, or you may have unpleasant interactions between two noise nodes
in different parts of the graph. This is why a single generated-noise
buffer isn't a good idea (unless you randomize the start time in the buffer
source node).

On Fri, Jan 22, 2016 at 9:20 AM, rtoy notifications@github.com wrote:

On Thu, Jan 21, 2016 at 11:53 AM, Hugh Rawlinson <notifications@github.com

wrote:

White noise would be a series of random samples, vendors using their
browser implementations standard RNG is probably fine now that v8 has
fixed
that huge issue.

Probably shouldn't call a node 'NoiseGenerator' if it can only do one
type
of noise though. It'd be cool to have the full colour spectrum
https://en.wikipedia.org/wiki/Colors_of_noise to choose from, but then
we're getting into the "is this really necessary" territory. Playing
"angel's advocate", the algorithms to generate the other kinds of noise
are
very straightforward, all of the noises in the "technical definitions"
section of that article can be scaled with bitwise multiplication in the
frequency domain, and it'd be useful to have functions for generating
Ndb/octave decay and equal loudness curves.

​Not exactly sure what "bitwise multiplication in the frequency domain"
means, but directly modifying the frequency domain signal (and the inverse
transforming to get the time domain signal) isn't usually bandlimited.
Extra care needed; or just do time-domain filtering​ (but designing the
filters probably isn't so easy).


Reply to this email directly or view it on GitHub
<
https://github.com/WebAudio/web-audio-api/issues/705#issuecomment-173689784

.

Ray


Reply to this email directly or view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/705#issuecomment-173983175
.

@rtoy
Copy link
Member

rtoy commented Jan 22, 2016

Since WebAudio provides both biquad and IIR filters, the noise generator doesn't need to provide all the different kinds of colored noise. The user can do the appropriate filtering, so the noise generator only needs to provide white noise (with some appropriate sample distribution).

@g200kg
Copy link
Author

g200kg commented Jan 22, 2016

The pink noise is a bit trouble because that needs a -3dB/Oct filter. The easiest way is FIR filter using convolver.

@rtoy
Copy link
Member

rtoy commented Jan 22, 2016

I think this is a good reason just to spec a white noise generator and let the user color it however he wants to color it.

@notthetup
Copy link
Contributor

I believe we should be able to create a 3dB/Oct IIR filter using the new IIRFilterNode.

@hoch
Copy link
Member

hoch commented Jan 22, 2016

Yeap. That's what Ray was talking about. Still, I think the variable sample
rate for the noise is useful for sample & hold.
On Fri, Jan 22, 2016 at 3:08 PM Chinmay Pendharkar notifications@github.com
wrote:

I believe we should be able to create a 3dB/Oct IIR filter
http://www.firstpr.com.au/dsp/pink-noise/ using the new IIRFilterNode
https://webaudio.github.io/web-audio-api/#idl-def-IIRFilterNode.


Reply to this email directly or view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/705#issuecomment-174082644
.

@g200kg
Copy link
Author

g200kg commented Jan 24, 2016

Sounds great,
Hopefully, the -3dB filter would be "built-in", rather than external combination of three IIRFilterNode.

@rtoy
Copy link
Member

rtoy commented Jan 24, 2016

Why do you need three IIRFilterNodes? One should be enough.

@g200kg
Copy link
Author

g200kg commented Jan 24, 2016

Thank you. I have misunderstood. The introduced document use summing three 1st order IIR but we can do that with one high-order IIRFilterNode, right?

@notthetup
Copy link
Contributor

Yup.

an equiripple approximation to the ideal pinking filter can be realized by
alternating real poles with real zeros. a simple 3rd order solution that i
obtained is:

pole zero


0.99572754 0.98443604
0.94790649 0.83392334
0.53567505 0.07568359

This would be a single 3rd order IIRFilter.

@hoch
Copy link
Member

hoch commented Jan 26, 2016

What I would love to see is:

  1. White noise generation with two distribution model: gaussian and uniform
  2. A variable rate multiplier based on the current sample rate (possibly as float AudioParam)

The coloring is a bit tricky to be specified except for widely-accepted noise types and also it easily can be done with IIR or convolver.

@mdjp
Copy link
Member

mdjp commented Jan 29, 2016

This is being moved to V.next - although the group can see the benefit of this and it would be a welcome feature, it can be solved with the audio worker for V1 and effort shoudl really be concentrated there rather on new nodes.

@joeberkovitz joeberkovitz changed the title Is there any chance of adding NoiseGenerator Node? Add NoiseGenerator Node Nov 7, 2017
@svgeesus
Copy link
Contributor

This is where we would benefit from encapsulation/layering. I could wrap a Gaussian noise generator and a particular filter, and expose that as one composite "pink noise" node.

@padenot
Copy link
Member

padenot commented Jun 25, 2019

F2F resolution:

  • Support a new node that outputs two noise types: uniform distribution in [-1,1], Gaussian distribution in [-1,1].
  • Authors will be able to create their own noise type by filtering, with WaveShaperNode, AudioWorkletNode, IIRFilterNode, BiquadFilterNode, etc.

@meshula
Copy link

meshula commented Jul 2, 2019

For LabSound, (a fork of webkit's WebAudio for use outside of browsers, http://labsound.io) - we implemented a simple NoiseNode in our extended node API. https://github.com/LabSound/LabSound/blob/master/include/LabSound/extended/NoiseNode.h We used the white/brown/pink generator from here, http://noisehack.com/generate-noise-web-audio-api/ as it met our needs, and is very fast to execute in a callback.

@rtoy
Copy link
Member

rtoy commented Aug 8, 2019

Rough proposal:

enum NoiseType {
  "uniform",  // in the range [-1 ,1]
  "gaussian" // or "normal"?  Mean = 0, standard deviation = 1
  // Anything else is currently out-of-scope
}

[Constructor (BaseAudioContext context, optional NoiseGeneratorOptions options)]
interface NoiseGeneratorNode : AudioScheduledSourceNode {
  attribute NoiseType type;
  // Maybe the following aren't necessary, but it's kind of useful to
  // inspect the node to know how to create a duplicate if desired.
  readonly attribute unsigned long long sequenceNumber;
  readonly attribute unsigned long long initialSeed;
  // Maybe we don't want to expose the running state of the RNG?
  readonly attribute unsigned long long state;
}

dictionary NoiseGeneratorOptions : AudioNodeOptions {
  NoiseType type = "uniform";
  // Initial seed for the RNG.  If not specified, browsers should
  // choose a "random" value.  Or maybe it should be a specified fixed
  // value?
  unsigned long long seed;
  // Selects one of the 2^64 possible independent sequences, each of
  // length 2^64.  Then each node with the same initial seed but
  // different sequenceNumber is guaranteed to produce a different
  // sequence from every other node.
  required unsigned long long sequenceNumber;
};

The proposed RNG to use is the minimal C (reference) implementation: https://github.com/imneme/pcg-c-basic. This is small and fast and passes all the tests for randomness. It is specifically not crypto safe.

If the seed is not given, the browser initializes it as desired, with the only requirement is that it is not a constant. (Or maybe we should specify a default seed?)

For a given seed and sequenceNumber, the every browser must produce exactly the same sequence of numbers for a uniform source. For Gaussian, browsers may very depending on the algorithm used to generate the Gaussian.

@rtoy
Copy link
Member

rtoy commented Aug 8, 2019

Hmm. Need to describe the algorithm for going from a 32-bit random integer to a single-precision float. That's not given in the references above.

@meshula
Copy link

meshula commented Aug 8, 2019

Without interpretation, I'm going to drop the spectral content of pcg-c-basic here. Used the ranged generator from 0-65535, normalized to -1.0 to 1.0, 4096 samples as input. ----
image
image

@rtoy
Copy link
Member

rtoy commented Oct 27, 2020

Previously in https://github.com/WebAudio/web-audio-api-v2/issues/8#issuecomment-519621343, the node had

readonly attribute unsigned long long state;

This allowed you to examine the internal state of the generator. You could get the internal state, but it wasn't useful because we didn't provide a method for you to update the internal state of a generator. So I removed it.

This means you couldn't inspect a noise node and create a new node that would start off with exactly the same values as the original node. But that wasn't really feasible anyway unless you stopped the context to get a stable value of the state and initialized the new node with that state. (But you couldn't create the node with that state; you only have the seed which is used with the sequence number to create the state.)

Is that important to anyone?

And then, do you want to node to do S/H of the values? That's not currently part of the proposed spec. We can add that.

@rtoy
Copy link
Member

rtoy commented Oct 27, 2020

@guest271314 Your example in https://github.com/WebAudio/web-audio-api-v2/issues/8#issuecomment-710193331 doesn't generate white noise AFAICT. Looks like it randomly selects one of the oscillator types and then a random frequency. That's not white noise, which is what we want here.

@guest271314

This comment has been minimized.

@guest271314

This comment has been minimized.

@rtoy
Copy link
Member

rtoy commented Oct 28, 2020

White noise is a random process with a constant power spectral density.

@guest271314

This comment has been minimized.

@guest271314

This comment was marked as off-topic.

@guest271314

This comment has been minimized.

@guest271314

This comment has been minimized.

@rtoy

This comment has been minimized.

@rtoy
Copy link
Member

rtoy commented Nov 3, 2020

Oh, I didn't know Javascript has BigInt now. And Chrome Beta and Firefox Developer support it. This would make for a nicer API. But it looks like WebIDL doesn't currently support BigInt, so perhaps we're stuck with using arrays to specify the seed.

@rtoy
Copy link
Member

rtoy commented Nov 3, 2020

Anyway, here is a proposal assuming we had BigInt:

enum NoiseType {
  "uniform",
  "gaussian" // or "normal"?
};

interface NoiseGeneratorNode {
  constructor (BaseAudioContext context, NoiseGeneratorOptions options);
  // Different sequenceNumbers with the same seed produces independent
  // sequences of random numbers.
  readonly attribute unsigned long sequenceNumber;
  // The 64-bit seed used to initialize this generator.
  readonly attribute BigInt seed;
};

dictionary NoiseGeneratorOptions {
  NoiseType type = "uniform";
  required unsigned long sequenceNumber;
  // 64-bit seed.  If not given, the browser creates a random initial
  // seed in an unspecified manner.
  BigInt seed;
};

@domenic
Copy link
Contributor

domenic commented Nov 4, 2020

Some naive questions trying to help out on the BigInt side. I tried to answer these by reading upthread but I couldn't find them easily, probably because this is mostly domain experts talking and goes above my head :).

  • Do you definitely need 64 bits for an initial seed? It's not clear to me why that's better than 32 or 53.

  • Why is it preferable to get your 64 bits in integer form, instead of in double form? I.e. why not take a double as input, and just use its bit pattern (which is 64 full bits)? You could even reinterpret those bits as an 64-bit integer if it made calculations easier (example below)

  • If you go with BigInt, what is your plan for handling values outside the desired range? (Throwing seems perfectly reasonable, FWIW.) Is your desired range ~ [-2^63, 2^63] or [0, 2^64]? (I know there should be +/-1s in some of those endpoints... I always forget which.)

To be clear, I am completely ready to believe this is best done with 64-bit ints, and am excited about BigInt getting its first web platform use case. I just want to make sure we have all our reasoning lined up, so that we can set a nice precedent for the future.

Code to reinterpret a 64-bit float as a 64-bit int
const buffer = new ArrayBuffer(8);
const intView = new BigInt64Array(buffer);
const floatView = new Float64Array(buffer);

floatView[0] = Math.PI;
console.log(intView[0]);

@guest271314

This comment has been minimized.

@rtoy
Copy link
Member

rtoy commented Nov 4, 2020

The algorithms used by the pcg generator specify a 64-bit seed. Using only 32 bits would restrict the the set of possible sequences, or make the sequences less random. I don't know how to analyze such things. (Emails to the author have been unanswered.) 53 bits might be enough, but again I don't know. (We previously allowed you to examine the state of the generator of 64 bits, and all of those are needed exactly as is.)

Yes, using a double and treating it as a 64-bit integer could work too. But there's a huge range of values that are NaNs that would be hard to get. To get the full range, you'd basically do the opposite of your code to reinterpret doubles to 64-bit ints. This would be a rather unnatural interface.

For BigInts, we'd probably just take the low 64 bits to get the seed we need.

BigInts provide a reasonably natural interface that reflects the pcg describes.

Without BigInts, I think the best interface would be an array of 2 32-bit integers (or any other integer array that has 64 bits total).

In conclusion, BigInts provide a natural interface, but it's not strictly required. An array of integers works fine. Using fewer than 64 bits has unknown effects on the quality of the generated output.

@rtoy

This comment has been minimized.

@rtoy
Copy link
Member

rtoy commented Nov 6, 2020

Since whatwg/webidl#525 has been merged, perhaps we should use BigInt's for the interface. It may take some time before browsers get around to supporting this WebIDL feature though.

Not sure how we want to deal with that.

@rtoy
Copy link
Member

rtoy commented Nov 19, 2020

Teleconf: @padenot will ask SpiderMonkey folks about exposing this on the web.

@rtoy is perfectly happy with using arrays. We can add support for bigint later if desired.

@rtoy
Copy link
Member

rtoy commented Jan 21, 2021

Teleconf: Generally agreed not use bigints in the interface; an array is good enough.

We can add it later if desired.

@rtoy
Copy link
Member

rtoy commented May 12, 2021

F2F meeting: Agreed that the API is correct except BigInt should not be used. Michel Buffa will look at some Faust code and possibly propose some additional noise types if they are recognized as good methods for generating such noise.

@rtoy
Copy link
Member

rtoy commented May 13, 2021

Here's the updated API using arrays:

enum NoiseType {
  "uniform",
  "gaussian"  // Or "Gaussian" because it's a name?
};

interface NoiseGeneratorNode : AudioScheduledSourceNode {
  [RaisesException] constructor(BaseAudioContext context,
                                optional NoiseGeneratorOptions options = {});
  attribute NoiseType type;
  readonly attribute unsigned long sequenceNumber;
  [RaisesException] void getSeed(Uint32Array seed);
};

dictionary NoiseGeneratorOptions {
  NoiseType type = "uniform";
  unsigned long sequenceNumber = 1;
  sequence<unsigned long> initialSeed;
};

The RNG to use is the minimal C (reference) implementation: https://github.com/imneme/pcg-c-basic. This is small and fast and passes all the tests for randomness. It is specifically not crypto safe.

initialSeed is a sequence with a length of at least two.

The 64-bit seed, rngSeed, for the underlying generator is computed from initialSeed by

rngSeed = (initialSeed[0] << 32) + initialSeed[1]

That is, the first element of initialSeed is the high 32-bits of the seed and the second element is the low 32-bits of the seed. Any other elements are ignored.

getSeed must throw an error if the length of the array seed is less than two.

The array seed is filled according to

seed[0] = (rngSeed >> 32) & 0xffffffff;
seed[1] = rngSeed & 0xffffffff;

If initialSeed is not provided, the UA must generate a random seed in a UA-specific manner such that creating multiple NoiseGeneratorNodes will all have a different seed. Thus:

let n1 = new NoiseGeneratorNode(context, {sequenceNumber: 1});
let n2 = new NoiseGeneratorNode(context, {sequenceNumber: 2});
let s1 = new Uint32Array(2);
let s2 = new Uint32Array(2);
n1.getSeed(s1);
n2.getSeed(s2);

// Must return false
s1.every((val, index) => val === s2[index])

Conversely, two NoiseGeneratorNodes with the same sequenceNumber and initialSeed must produce exactly the same output.

For any given seed and sequence number, every UA must produce exactly the sequence of numbers when the type is "uniform". For "gaussian", this is not required; UAs are free to choose any appropriate algorithm to generate Gaussian noise.

@mdjp mdjp transferred this issue from WebAudio/web-audio-api-v2 Sep 29, 2021
@mdjp mdjp added the P1 WG charter deliverables; "need to have" label Sep 29, 2021
@mdjp mdjp added this to Untriaged in v.next via automation Sep 29, 2021
@mdjp mdjp moved this from Untriaged to In discussion in v.next Sep 29, 2021
@hoch hoch added tpac21-audionode-v2 P2 Significant developers/implementers interest; "want to have" and removed P1 WG charter deliverables; "need to have" labels Oct 27, 2021
@hoch hoch removed the P2 Significant developers/implementers interest; "want to have" label Nov 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
v.next
In discussion
Development

No branches or pull requests