Noisemaker Composer¶
Noisemaker Composer is a high-level interface for creating generative art with procedural noise.
Presets are authored with the Composer DSL, a domain-specific language for defining procedural art presets. It provides a structured, declarative syntax for composing noise generators and image effects while supporting randomization, inheritance, and reusable configurations.
Note
JavaScript Compatibility: The Composer DSL is shared between Python and JavaScript implementations. See JavaScript API for using presets in the browser.
Note
Two DSLs: Noisemaker has two distinct domain-specific languages:
Composer DSL (this document) — A declarative DSL for CPU-based compositions using Python/JavaScript effects. Presets describe what to generate via layered configurations.
Polymorphic DSL — An imperative DSL for GPU-based shader compositions. See Polymorphic DSL for details.
Overview¶
The Composer DSL allows you to define presets as JSON-like objects with specialized syntax extensions. Each preset can:
Layer (inherit from) other presets
Define reusable settings with random values
Configure noise generation parameters
Apply effects per-octave, post-processing, and final passes
Reference other presets inline
The canonical preset library lives in share/dsl/presets.dsl and is parsed by noisemaker.dsl. Both the reference Python implementation and the JavaScript port consume that file, so any change to the DSL immediately applies to both environments.
Custom Presets¶
You can create your own presets in a separate DSL file and use them with the CLI:
# Use a custom presets file
noisemaker generate my-preset --presets ~/my-presets.dsl -o custom.png
Custom preset files follow the same DSL syntax as the built-in presets.dsl. You can reference the built-in file as a template. Custom presets can layer on top of each other but cannot inherit from the built-in presets unless they are included in the same file.
For programmatic usage in Python:
from noisemaker.presets import set_presets_path, Preset
# Set custom presets file
set_presets_path('/path/to/my-presets.dsl')
# Now use presets from the custom file
preset = Preset('my-custom-preset')
preset.render(seed=42, shape=[1024, 1024, 3], filename='output.png')
Philosophy¶
“Composer” presets expose Noisemaker’s lower-level generator and effect APIs. The DSL keeps the data model compact while remaining expressive enough to describe the same building blocks the original Python dictionaries exposed.
At a high level, each preset answers five key questions:
Which presets are being built on? (
layers)What are the meaningful variables? (
settings)What are the noise generation parameters? (
generator)Which effects should be applied to each octave? (
octaves)Which effects should be applied after flattening layers? (
postandfinal)
Grammar¶
The DSL follows a strict grammar for parsing and validation. Programs are parsed into an abstract syntax tree (AST) and evaluated against a whitelist of operations and surfaces—the evaluator never invokes eval or Function.
Program ::= Chain ('.' 'out' '(' OutputRef? ')' )?
Chain ::= Expr ('.' Call)*
Expr ::= Ident '(' ArgList? ')'
Call ::= Ident '(' ArgList? ')'
ArgList ::= Arg (',' Arg)* ','?
Arg ::= NumberExpr | String | Boolean | Color
| Ident | Enum | OutputRef | SourceRef | Null
| List | Dict
NumberExpr ::= Primary
| NumberExpr ( '+' | '-' | '*' | '/' ) NumberExpr
| NumberExpr '?' NumberExpr ':' NumberExpr
Primary ::= Number | Boolean | Null | 'Math.PI'
| Ident | Enum | Call | MemberExpr
| '(' NumberExpr ')'
Call ::= (Ident | MemberExpr) '(' ArgList? ')'
MemberExpr ::= Ident '.' Ident
List ::= '[' ArgList? ']'
Dict ::= '{' (DictEntry (',' DictEntry)* ','?)? '}'
DictEntry ::= (String | Ident) ':' Arg
Enum ::= Ident '.' Ident
OutputRef ::= 'o' Digit
Ident ::= Letter ( Letter | Digit | '_' )*
Number ::= Digit+ ('.' Digit+)?
String ::= '"' [^"\n]* '"'
Digit ::= '0'…'9'
Letter ::= 'A'…'Z' | 'a'…'z'
Boolean ::= 'true' | 'false'
Null ::= 'null'
Color ::= '#' HexDigit HexDigit HexDigit
(HexDigit HexDigit HexDigit)?
HexDigit ::= Digit | 'A'…'F' | 'a'…'f'
Grammar Notes¶
Floats may omit the leading zero (e.g.,
.5).Trailing commas in lists, dictionaries, and argument lists are allowed.
Enumresolves to JavaScript enum objects exported fromconstants.js.Whitespace and comments are skipped between tokens.
Single-line comments use
//and run until the newline.Block comments use
/* … */and must be terminated before end of file.
Naming Conventions¶
Preset names use kebab-case (e.g.,
"acid-wash").Preset setting keys use snake_case (e.g.,
bloom_alpha).Function wrappers and argument keys use snake_case (e.g.,
random_int).Enum wrapper members use snake_case (e.g.,
ColorSpace.rgb).Underlying implementations:
Python enums and functions use
snake_case.JavaScript enums and functions use
camelCase.
Preset Keys¶
Each preset is a dictionary that can contain the following top-level keys:
layers¶
Type: Array of strings
Purpose: Inherit settings, generators, and effects from parent presets.
Parent presets are applied in order, with later presets overriding earlier ones. The current preset’s settings override all parents.
layers: ["basic", "voronoi", "grain"]
settings¶
Type: Dictionary
Purpose: Define variables that can be referenced throughout the preset using settings.key_name.
Settings support literals, enum references, helper functions, arithmetic expressions, and conditionals.
settings: {
freq: random_int(10, 15),
octaves: 8,
reindex_range: 1.25 + random() * 1.25,
color_space: ColorSpace.rgb,
}
This example from the acid preset shows typical settings usage.
generator¶
Type: Dictionary
Purpose: Configure noise generation parameters passed to noisemaker.generators.multires.
generator: {
freq: settings.freq,
octaves: settings.octaves,
lattice_drift: 1.0,
}
All keys must be valid generator parameters. Common ones include freq, octaves, ridges, distrib, color_space, hue_range, lattice_drift, corners, and spline_order.
octaves¶
Type: Array of effect calls
Purpose: Effects applied to each octave of noise during generation.
octaves: [
derivative(alpha: 0.333),
]
post¶
Type: Array of effect calls and/or preset references
Purpose: Effects applied after noise octaves are combined.
post: [
bloom(alpha: 0.25),
saturation(amount: 1.5),
]
final¶
Type: Array of effect calls and/or preset references
Purpose: Final effects applied after all post-processing.
final: [
aberration(displacement: 0.01),
]
unique¶
Type: Boolean
Purpose: Mark preset as unique (not for general layering). Defaults to false.
unique: true
Data Types¶
The DSL supports the following data types:
Numbers¶
Integers and floats, including arithmetic expressions:
freq: 5
alpha: 0.5 + random() * 0.25
computed: settings.base * 2 + 1
Strings¶
Double-quoted strings (no escape sequences):
palette_name: "viridis"
Booleans¶
Keywords true and false:
ridges: true
inverse: false
Null¶
Keyword null:
mask: null
Arrays¶
Lists of values:
freq: [4, 8]
layers: ["basic", "grain"]
options: [1, 2, 3]
Dictionaries¶
Key-value pairs:
settings: {
key1: value1,
key2: value2,
}
Enums¶
Access enum members using dot notation:
color_space: ColorSpace.rgb
mask: ValueMask.chess
dist_metric: DistanceMetric.euclidean
Available enums include: ColorSpace, ValueDistribution, ValueMask, DistanceMetric, VoronoiDiagramType, PointDistribution, InterpolationType, OctaveBlending, WormBehavior, and more.
Expressions¶
Arithmetic¶
Standard operators: +, -, *, /
reindex_range: 1.25 + random() * 1.25
double_freq: settings.freq * 2
Conditional (Ternary)¶
JavaScript-style ternary:
voronoi_inverse: coin_flip() ? true : false
freq: random() < 0.5 ? 4 : 8
Comparison and Logic¶
Comparison operators: <, >, <=, >=, ==, !=
Logical operators: && (and), || (or)
use_effect: random() < 0.75
Settings References¶
Access previously defined settings:
settings: {
freq: random_int(8, 12),
reflect_range: 7.5 + random() * 3.5,
}
Helper Functions¶
The DSL provides built-in helper functions for randomization and utilities:
random()¶
Returns a random float between 0.0 and 1.0.
bloom_alpha: 0.025 + random() * 0.0125
random_int(min, max)¶
Returns a random integer between min and max (inclusive).
freq: random_int(10, 15)
voronoi_sdf_sides: random_int(2, 8)
coin_flip()¶
Returns a random boolean (true or false).
voronoi_inverse: coin_flip()
random_member(collection)¶
Returns a random member from a single iterable. The iterable can be an enum (for example,
DistanceMetric) or any collection that yields members. Passing scalars directly raises an
error.
dist_metric: random_member(DistanceMetric.all())
voronoi_diagram_type: random_member([
VoronoiDiagramType.range,
VoronoiDiagramType.color_range,
VoronoiDiagramType.regions,
])
enum_range(start, end)¶
Returns a list of enum members from start to end (inclusive). Both arguments must be
members of the same enum.
values: enum_range(InterpolationType.constant, InterpolationType.bicubic)
// [InterpolationType.constant, InterpolationType.linear,
// InterpolationType.cosine, InterpolationType.bicubic]
stash(key, value)¶
Stores a value for later retrieval within the same evaluation context.
temp: stash("my_key", 42)
retrieved: stash("my_key")
mask_freq(mask, repeat)¶
Returns the appropriate frequency for a given mask and repeat value.
freq: mask_freq(ValueMask.chess, 8)
preset(name)¶
Inline another preset’s post/final effects.
post: [
bloom(alpha: 0.25),
preset("grain"),
]
Enum Helper Methods¶
Enums provide helper methods to filter and retrieve specific members:
EnumType.all()¶
Returns all enum members.
all_colors: ColorSpace.all()
Specific Enum Methods¶
Different enums provide specialized filter methods:
ColorSpace:
ColorSpace.color_members()- Color spaces only
DistanceMetric:
DistanceMetric.absolute_members()- Absolute metricsDistanceMetric.all()- All metrics
ValueMask:
ValueMask.procedural_members()- Procedural masksValueMask.grid_members()- Grid-based masksValueMask.glyph_members()- Glyph/character masksValueMask.nonprocedural_members()- Non-procedural masksValueMask.rgb_members()- RGB-based masks
PointDistribution:
PointDistribution.circular_members()- Circular distributionsPointDistribution.grid_members()- Grid-based distributions
WormBehavior:
WormBehavior.all()- All worm behaviors
Example usage:
dist_metric: random_member(DistanceMetric.all())
color_space: random_member(ColorSpace.color_members())
mask: random_member(ValueMask.grid_members())
Effect Calls¶
Effects are called with named parameters using colon syntax:
effect_name(param1: value1, param2: value2)
Examples from actual presets:
octaves: [
derivative(alpha: 0.333),
]
post: [
bloom(alpha: settings.bloom_alpha),
saturation(amount: 1.5),
]
final: [
aberration(displacement: 0.01),
]
Complete Example¶
Here’s the acid preset from the standard library (one of many presets in share/dsl/presets.dsl):
{
"acid": {
layers: ["basic", "reindex-post", "normalize"],
settings: {
color_space: ColorSpace.rgb,
freq: random_int(10, 15),
octaves: 8,
reindex_range: 1.25 + random() * 1.25,
},
},
// ... hundreds of other presets ...
}
This preset demonstrates:
Layering: Inherits from
basic(noise generation),reindex-post(color reindexing effect), andnormalize(value normalization)Settings: Defines randomized frequency (10-15), fixed octave count (8), and randomized reindex range
Enums: Uses
ColorSpace.rgbfor RGB color spaceRandomization: Combines
random_int()andrandom()for varied output
Another example, acid-droplets, shows a more complex preset:
{
"acid-droplets": {
layers: [
"multires",
"reflect-octaves",
"density-map",
"random-hue",
"bloom",
"shadow",
"saturation"
],
settings: {
freq: random_int(8, 12),
hue_range: 0,
lattice_drift: 1.0,
mask: ValueMask.sparse,
mask_static: true,
palette_on: false,
reflect_range: 7.5 + random() * 3.5,
},
},
}
This demonstrates extensive layering of multiple effect presets to create a complex composition.
Naming Conventions¶
The DSL follows these naming conventions:
Preset names:
kebab-case(e.g.,"my-awesome-preset")Setting keys:
snake_case(e.g.,base_freq,bloom_alpha)Function names:
snake_case(e.g.,random_int,coin_flip)Enum types:
PascalCase(e.g.,ColorSpace,ValueMask)Enum members:
snake_case(e.g.,ColorSpace.rgb,ValueMask.chess)
Best Practices¶
Use settings for reusable values
settings: { bloom_alpha: 0.025 + random() * 0.0125, }, final: [ bloom(alpha: settings.bloom_alpha), ]
Layer presets for composition
layers: ["basic", "reindex-post", "normalize"]
Use descriptive setting names
reflect_range: 7.5 + random() * 3.5 // Good rr: 7.5 + random() * 3.5 // Bad
Reference the canonical library
Study existing presets in
share/dsl/presets.dslfor patterns and techniques.
Debugging¶
When a preset doesn’t parse or evaluate correctly:
Check syntax: Ensure all braces, brackets, and parentheses are balanced
Verify enum names: Enum references must exactly match defined enums
Check parameter names: Effect parameters must match the effect’s signature
Look for typos: Setting references must exactly match defined keys
Test incrementally: Build complex presets step-by-step
The Python and JavaScript parsers provide error messages with line/column information when syntax errors occur.
Using Presets¶
Note
JavaScript examples assume an ES module environment (for example, <script type="module">) so that await can be used at the top level.
Basic Usage¶
Python¶
from noisemaker.composer import Preset
preset = Preset('acid')
preset.render(seed=1, shape=[1024, 1024, 3], filename='acid.png')
JavaScript¶
import { PRESETS } from './js/noisemaker/presets.js';
import { Preset } from './js/noisemaker/composer.js';
const presets = PRESETS();
const preset = new Preset('acid', presets);
await preset.render(1, {
width: 1024,
height: 1024,
canvas: document.getElementById('output')
});
Working with Arrays¶
Python¶
from noisemaker.composer import Preset
preset = Preset('voronoi')
tensor = preset.render(seed=42, shape=[512, 512, 3])
array = tensor.numpy()
JavaScript¶
import { PRESETS } from './js/noisemaker/presets.js';
import { Preset } from './js/noisemaker/composer.js';
const presets = PRESETS();
const preset = new Preset('voronoi', presets);
const tensor = await preset.render(42, { width: 512, height: 512 });
const array = tensor.read();
Override Settings¶
Python¶
from noisemaker.composer import Preset
preset = Preset('acid', settings={'freq': 20, 'octaves': 12})
preset.render(seed=1, shape=[1024, 1024, 3], filename='custom.png')
JavaScript¶
import { PRESETS } from './js/noisemaker/presets.js';
import { Preset } from './js/noisemaker/composer.js';
const presets = PRESETS();
const preset = new Preset('acid', presets, { freq: 20, octaves: 12 });
await preset.render(1, { width: 1024, height: 1024 });
List Available Presets¶
Python¶
from noisemaker.presets import PRESETS
presets = PRESETS()
print(list(presets.keys()))
JavaScript¶
import { PRESETS } from './js/noisemaker/presets.js';
const presets = PRESETS();
console.log(Object.keys(presets));
Architecture Overview¶
The Noisemaker Composer system is built on three layers:
DSL Layer (
noisemaker.dsl)Parses and evaluates the Composer DSL from
share/dsl/presets.dsl. The same DSL file is used by both Python and JavaScript implementations, ensuring cross-platform consistency.Preset Layer (
noisemaker.presets,noisemaker.composer)Loads preset definitions and provides the
Presetclass for rendering. Handles preset inheritance (layering), settings resolution, and effect application.Generator/Effect Layer (
noisemaker.generators,noisemaker.effects)Low-level TensorFlow operations for generating procedural noise and applying image effects.
The DSL provides a declarative interface to these lower-level APIs, making it easy to compose complex generative art without writing imperative code.
Cross-Platform Compatibility¶
Both the Python and JavaScript implementations:
Parse the same DSL file (
share/dsl/presets.dsl)Use identical tokenizer, parser, and evaluator logic
Produce deterministic output given the same seed
Support the same set of helper functions and enums
Any change to the DSL immediately applies to both environments, making it easy to maintain consistency across platforms.
See Also¶
Noisemaker API - Low-level generator and effect APIs
Noisemaker CLI - Command-line interface documentation
noisemaker.presets- Preset loading and evaluationnoisemaker.dsl- DSL parser and evaluator modulesnoisemaker.composer- Composer class and rendering API