JavaScript API
==============
Noisemaker includes a vanilla JavaScript port that runs in modern browsers. The JavaScript implementation strives to maintain visual parity with the Python version, sharing the same presets, algorithms, and RNG behavior.
Overview
--------
The JavaScript port is a complete reimplementation of Noisemaker's core library in vanilla JavaScript (ES modules). The final library runs entirely in the browser without dependencies.
Key Features
~~~~~~~~~~~~
* **Full feature parity** with Python implementation
* **Deterministic output** via controlled RNG seeding
* **Shared preset DSL** - same presets work in both Python and JS
* **Cross-language testing** - JS tests run against Python reference
* **Browser-native** - no build step required, pure ES modules
Installation
------------
For Browser Use
~~~~~~~~~~~~~~~
Include the ES modules directly in your HTML:
.. code-block:: html
Using the Prebuilt Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~
If you prefer a single-file build (no import map or bundler required), download
``noisemaker.bundle.js`` from the latest GitHub release or build it locally with
``npm run bundle``. The script registers a ``Noisemaker`` global that mirrors the
module exports.
.. code-block:: html
For Development/Testing
~~~~~~~~~~~~~~~~~~~~~~~
The JavaScript port includes a Node-based CLI for testing and development:
.. code-block:: bash
cd js/
npm install
npm test # Run cross-language parity tests
Command-Line Rendering (Experimental)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The JavaScript build includes a Node-powered CLI for rendering without a browser:
.. code-block:: bash
npx noisemaker-js generate basic --filename output.png --width 512 --height 512 --seed 123
Additional options: ``--time``, ``--speed``, ``--with-alpha``, ``--debug``
Core Modules
------------
The JavaScript library is organized into ES modules mirroring the Python structure:
Tensor Operations
~~~~~~~~~~~~~~~~~
**tensor.js** - Tensor wrapper for image data
.. code-block:: javascript
import { Tensor } from './noisemaker/tensor.js';
// Create from array
const data = new Float32Array(512 * 512 * 3);
const tensor = Tensor.fromArray(data, [512, 512, 3]);
// Read back data
const pixels = tensor.read();
Noise Generation
~~~~~~~~~~~~~~~~
**simplex.js** - 3D OpenSimplex noise
.. code-block:: javascript
import { simplex } from './noisemaker/simplex.js';
const noise = simplex(x, y, z, seed);
**value.js** - Value noise and tensor operations
.. code-block:: javascript
import { basic, multires } from './noisemaker/value.js';
const tensor = basic({ seed: 42, shape: [256, 256, 3] });
**generators.js** - High-level noise generation
.. code-block:: javascript
import { multires } from './noisemaker/generators.js';
const result = multires(preset, {
seed: 42,
shape: [512, 512, 3],
time: 0.0,
speed: 1.0
});
Effects and Composition
~~~~~~~~~~~~~~~~~~~~~~~
**effects.js** - Image post-processing effects
.. code-block:: javascript
import { posterize, bloom, aberration } from './noisemaker/effects.js';
let tensor = generators.basic({ seed: 42, shape: [512, 512, 3] });
tensor = posterize(tensor, shape, time, speed, { levels: 5 });
tensor = bloom(tensor, shape, time, speed, { alpha: 0.5 });
**effectsRegistry.js** - Effect metadata and registration
.. code-block:: javascript
import { register, EFFECT_METADATA } from './noisemaker/effectsRegistry.js';
function myEffect(tensor, shape, time, speed, amount = 1.0) {
// Custom effect implementation
return tensor;
}
register('myEffect', myEffect, { amount: 1.0 });
console.log(EFFECT_METADATA.myEffect); // => { amount: 1.0 }
**composer.js** - Preset composition system
.. code-block:: javascript
import { Preset } from './noisemaker/composer.js';
const preset = new Preset('acid', presets);
const tensor = preset.render({
seed: 42,
shape: [512, 512, 3],
time: 0.0,
speed: 1.0
});
**presets.js** - Preset loading and DSL evaluation
.. code-block:: javascript
import { Preset, PRESETS } from './noisemaker/presets.js';
const preset = Preset('acid');
const allPresets = PRESETS();
Constants and Enums
~~~~~~~~~~~~~~~~~~~
**constants.js** - All enumerations from Python
.. code-block:: javascript
import {
DistanceMetric,
PointDistribution,
ValueMask,
ColorSpace,
InterpolationType
} from './noisemaker/constants.js';
const metric = DistanceMetric.euclidean;
const distrib = PointDistribution.random;
**masks.js** - Predefined mask patterns
.. code-block:: javascript
import { Masks, mask_values } from './noisemaker/masks.js';
const chessMask = Masks[ValueMask.chess]; // [[0,1],[1,0]]
**palettes.js** - Color palette definitions
.. code-block:: javascript
import { PALETTES } from './noisemaker/palettes.js';
const palette = PALETTES['rainbow'];
Utilities
~~~~~~~~~
**rng.js** - Deterministic random number generation
.. code-block:: javascript
import * as rng from './noisemaker/rng.js';
rng.setSeed(42);
const value = rng.random(); // [0.0, 1.0)
const int = rng.randomInt(0, 9); // [0, 9]
**util.js** - Helper functions
.. code-block:: javascript
import { save, shape } from './noisemaker/util.js';
// Save tensor to canvas
await save(tensor, canvas);
// Get shape from canvas
const shape = shapeFromCanvas(canvas);
**oklab.js** - OKLab color space conversion
.. code-block:: javascript
import { rgbToOklab, oklabToRgb } from './noisemaker/oklab.js';
const oklab = rgbToOklab([r, g, b]);
const rgb = oklabToRgb([L, a, b]);
**points.js** - Point cloud generation
.. code-block:: javascript
import { pointCloud, rand, squareGrid } from './noisemaker/points.js';
const [xPoints, yPoints] = pointCloud(freq, PointDistribution.random);
**glyphs.js** - Font rendering (browser Canvas API)
.. code-block:: javascript
import { loadGlyphs } from './noisemaker/glyphs.js';
const glyphs = loadGlyphs([height, width]);
Cross-Language Parity
---------------------
Testing Approach
~~~~~~~~~~~~~~~~
The JavaScript test suite runs against the Python reference implementation:
.. code-block:: bash
cd js/
npm test
Each test:
1. Generates output in JavaScript with a specific seed
2. Invokes Python subprocess with identical parameters
3. Compares outputs pixel-by-pixel
4. **Any difference is a test failure** - no fixtures or approximations
This ensures the JavaScript port produces **identical** output to Python.
Parity Requirements
~~~~~~~~~~~~~~~~~~~
From ``js/doc/PY_JS_PARITY_SPEC.md``:
* **RNG behavior must match exactly** - same seed produces same random sequence
* **Never simulate weighted randomness** by repeating values; use explicit probability checks
* **Float precision differences** are not acceptable - results must be bit-identical where possible
* **Do not modify Python reference** to make JS tests pass
* **Do not skip or weaken tests** to hide parity issues
Shared Preset DSL
~~~~~~~~~~~~~~~~~
Both implementations use the same preset file:
.. code-block:: text
/share/dsl/presets.dsl # Shared by Python and JavaScript
This ensures presets behave identically across languages.
Development Guidelines
----------------------
From ``js/doc/VANILLA_JS_PORT_SPEC.md``:
When In Doubt
~~~~~~~~~~~~~
**Refer to the Python version and do what it does.** The Python version is the baseline reference implementation.
Code Style
~~~~~~~~~~
* Use ES modules (``import``/``export``)
* Document functions with JSDoc where helpful
* Match Python naming conventions (snake_case for functions)
* Use ``async``/``await`` for asynchronous operations
Testing
~~~~~~~
* Run ``npm test`` before committing
* Add parity tests for new features
* Never modify Python to make JS pass
API Differences from Python
----------------------------
The JavaScript API maintains functional parity but has some necessary differences:
Async Operations
~~~~~~~~~~~~~~~~
Some operations in JavaScript are asynchronous:
.. code-block:: javascript
// Python (synchronous)
tensor = preset.render(seed=42, shape=[512, 512, 3])
// JavaScript (asynchronous)
const tensor = await preset.render({ seed: 42, shape: [512, 512, 3] });
Object vs. Keyword Arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
JavaScript uses object destructuring instead of Python's kwargs:
.. code-block:: javascript
// Python
result = multires(preset, seed=42, shape=[512, 512, 3], time=0.0, speed=1.0)
// JavaScript
const result = await multires(preset, {
seed: 42,
shape: [512, 512, 3],
time: 0.0,
speed: 1.0
});
Canvas Output
~~~~~~~~~~~~~
JavaScript renders directly to HTML5 Canvas:
.. code-block:: javascript
const canvas = document.getElementById('output');
await preset.render({ seed: 42, shape: [512, 512, 3], canvas: canvas });
Quick Reference: Python ↔ JavaScript
-------------------------------------
Core Functions
~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 40 60
* - Python
- JavaScript
* - ``from noisemaker.presets import Preset``
- ``import { Preset } from './noisemaker/presets.js';``
* - ``preset = Preset('acid')``
- ``const preset = Preset('acid');``
* - ``tensor = preset.render(seed=42, shape=[512, 512, 3])``
- ``const tensor = await preset.render({ seed: 42, shape: [512, 512, 3] });``
* - ``from noisemaker.generators import multires``
- ``import { multires } from './noisemaker/generators.js';``
* - ``tensor = multires(preset, seed=42, shape=[512, 512, 3])``
- ``const tensor = await multires(preset, { seed: 42, shape: [512, 512, 3] });``
* - ``from noisemaker.effects import bloom, posterize``
- ``import { bloom, posterize } from './noisemaker/effects.js';``
* - ``tensor = bloom(tensor, shape, time, speed, alpha=0.5)``
- ``const tensor = await bloom(tensor, shape, time, speed, { alpha: 0.5 });``
Random Number Generation
~~~~~~~~~~~~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 40 60
* - Python
- JavaScript
* - ``import noisemaker.rng as rng``
- ``import * as rng from './noisemaker/rng.js';``
* - ``rng.set_seed(42)``
- ``rng.setSeed(42);``
* - ``value = rng.random()``
- ``const value = rng.random();``
* - ``value = rng.random_int(0, 9)``
- ``const value = rng.randomInt(0, 9);``
* - ``item = rng.random_member([1, 2, 3])``
- ``const item = rng.randomMember([1, 2, 3]);``
Constants and Enums
~~~~~~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 40 60
* - Python
- JavaScript
* - ``from noisemaker.constants import DistanceMetric``
- ``import { DistanceMetric } from './noisemaker/constants.js';``
* - ``metric = DistanceMetric.euclidean``
- ``const metric = DistanceMetric.euclidean;``
* - ``from noisemaker.masks import Masks``
- ``import { Masks } from './noisemaker/masks.js';``
* - ``mask = Masks[ValueMask.chess]``
- ``const mask = Masks[ValueMask.chess];``
* - ``from noisemaker.palettes import PALETTES``
- ``import { PALETTES } from './noisemaker/palettes.js';``
Noise Functions
~~~~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 40 60
* - Python
- JavaScript
* - ``from noisemaker.simplex import simplex``
- ``import { simplex } from './noisemaker/simplex.js';``
* - ``value = simplex(x, y, z, seed)``
- ``const value = simplex(x, y, z, seed);``
* - ``from noisemaker.value import basic``
- ``import { basic } from './noisemaker/value.js';``
* - ``tensor = basic(seed=42, shape=[256, 256, 3])``
- ``const tensor = await basic({ seed: 42, shape: [256, 256, 3] });``
Color Spaces
~~~~~~~~~~~~
.. list-table::
:header-rows: 1
:widths: 40 60
* - Python
- JavaScript
* - ``from noisemaker.oklab import rgb_to_oklab``
- ``import { rgbToOklab } from './noisemaker/oklab.js';``
* - ``oklab = rgb_to_oklab([r, g, b])``
- ``const oklab = rgbToOklab([r, g, b]);``
* - ``from noisemaker.oklab import oklab_to_rgb``
- ``import { oklabToRgb } from './noisemaker/oklab.js';``
* - ``rgb = oklab_to_rgb([L, a, b])``
- ``const rgb = oklabToRgb([L, a, b]);``
Examples
--------
Basic Noise Generation
~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: javascript
import { Preset } from './js/noisemaker/presets.js';
async function generate() {
const preset = Preset('basic');
const canvas = document.getElementById('canvas');
await preset.render({
seed: Date.now(),
shape: [512, 512, 3],
canvas: canvas
});
}
Animated Noise
~~~~~~~~~~~~~~
.. code-block:: javascript
import { Preset } from './js/noisemaker/presets.js';
async function animate() {
const preset = Preset('funky-glyphs');
const canvas = document.getElementById('canvas');
let time = 0;
function frame() {
preset.render({
seed: 42,
shape: [512, 512, 3],
time: time,
speed: 0.05,
canvas: canvas
}).then(() => {
time += 0.016; // ~60fps
requestAnimationFrame(frame);
});
}
requestAnimationFrame(frame);
}
Custom Effect Pipeline
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: javascript
import { multires } from './js/noisemaker/generators.js';
import { posterize, bloom, aberration } from './js/noisemaker/effects.js';
import { save } from './js/noisemaker/util.js';
async function customPipeline() {
const shape = [512, 512, 3];
// Generate base noise
let tensor = await multires(null, {
seed: 42,
shape: shape,
octaves: 8,
freq: 4
});
// Apply effects
tensor = await posterize(tensor, shape, 0, 1, { levels: 5 });
tensor = await bloom(tensor, shape, 0, 1, { alpha: 0.5 });
tensor = await aberration(tensor, shape, 0, 1, { displacement: 0.05 });
// Save to canvas
const canvas = document.getElementById('output');
await save(tensor, canvas);
}
Further Reading
---------------
* `JavaScript README `_
* `Vanilla JS Port Specification `_
* `Python/JS Parity Requirements `_
* `Browser Demos `_