thi-ng / umbrella

⛱ Broadly scoped ecosystem & mono-repository of 199 TypeScript projects (and ~180 examples) for general purpose, functional, data driven development
https://thi.ng
Apache License 2.0
3.41k stars 151 forks source link

[@thi.ng/colored-noise] white() returns rnd.norm is not a function #363

Closed logue closed 2 years ago

logue commented 2 years ago

It's pseudo code

import { blue, green, pink, red, violet, white } from '@thi.ng/colored-noise';
import { take } from '@thi.ng/transducers';

/**
 * Noise source
 *
 * @param duration - 
 */
function getNoise(duration: number): number[] {
  return [
    ...take<number>(
      duration,
      white()
    ),
   ];
}

An error similar to the following is output.

white.js:11 Uncaught TypeError: rnd.norm is not a function

It did not reproduce with other noises (such as blue, green, pink, red, violet).

postspectacular commented 2 years ago

Hey @logue - would it be possible for you to upload a more reproducible demo somewhere (codepen, codesandbox etc.)? Looking at the source, I can't see anything wrong with it, nor can't I reproduce your bug in the Node REPL...

https://github.com/thi-ng/umbrella/blob/2065c927cfe52fe72534fcc0ee74260c760d9966/packages/colored-noise/src/white.ts#L1-L15

$ node
Welcome to Node.js v18.9.0.
Type ".help" for more information.
>
> n=await import("@thi.ng/colored-noise"); tx=await import("@thi.ng/transducers"); null
null
> [...tx.take(10, n.white())]
[
  0.7961650819890886,
  0.15082163331974208,
  0.7858454158415085,
  0.8696593274664548,
  -0.9464416381763412,
  -0.5561235385412959,
  0.6976864107642693,
  -0.5725925349106373,
  0.49551732892693945,
  -0.5796380675511346
]
> 
logue commented 2 years ago

The code I'm actually using is below. I am using this library for generating biased random numbers to generate plosives for impulse responses.

https://github.com/logue/Reverb.js/blob/master/src/Reverb.ts#L366-L385

(In this code, the white parameter is this.options.peaks instead of this.options.scale.)

It seems that only white() has different specifications from other noise generation processes (no scale), so it is divided with an if statement, but it is not enough or it is difficult to understand the specifications of INorm in the first place. .

postspectacular commented 2 years ago

Interesting project! 😍

...but I think you're misunderstanding the parameters though: white() only has a scale/amplitude param, but no internal buffer size parameter (aka the 1st argument n for the other colored noise fns). That is because white() really is just a generator function wrapping a PRNG instance (like the default: SYSTEM and not needing any additional internal state/buffer as the other colors do...

As to your confusion about the INorm interface, maybe this will help to clarify:

https://github.com/thi-ng/umbrella/blob/2065c927cfe52fe72534fcc0ee74260c760d9966/packages/random/src/api.ts#L3-L14

That interface describes a subset of functionality provided by the larger IRandom interface (see lines below) and the various implementations in the thi.ng/random package (the above SYSTEM is just one of many). So whereas the .float(10) method returns values in the [0..10) range, .norm(10) yields values in the [-10..10) interval.

Hope that makes more sense now?

logue commented 2 years ago

For the time being, in the case of white, I understand that the specifications are different from other colored noises.

Ultimately, the process of dynamically switching with the switch statement is having trouble filling this gap.

white:

(scale?: number, rnd?: INorm) => Generator<number, void, unknown>

other colord noise:

(n?: number, scale?: number, rnd?: INorm) => Generator<number, void, unknown>

It's a palliative, but I dealt with it as follows:

        this.noise === white
          ? white(this.options.scale, this.options.randomAlgorithm)
          : this.noise(
              this.options.peaks,
              this.options.scale,
              this.options.randomAlgorithm
            )
postspectacular commented 2 years ago

Understood & if uniform args are required, then I'd propose to replace the current function args with a config object/interface, e.g.:

export interface ColoredNoiseOpts {
    numBins: number;
    scale: number;
    rnd: IRandom;
}

Then we can update the generators like so and you can get rid of your conditional:

export function* red(opts: Partial<ColoredNoiseOpts>) {
    const { numBins, scale, rnd } = {
        numBins: 2,
        scale: 1,
        rnd: SYSTEM,
        ...opts
    };
    // ....
}
postspectacular commented 2 years ago

Hi @logue - just wanted to let you know that this update is released now as v1.0.0

https://github.com/thi-ng/umbrella/blob/develop/packages/colored-noise/CHANGELOG.md#100-2022-11-23

logue commented 2 years ago

Thanks :)