Noisemaker Composer¶
Noisemaker Composer is a high-level interface for creating generative art with procedural noise.
Use the Composer DSL to define procedural art presets. This domain-specific language provides declarative syntax for composing noise generators and image effects. It supports randomization, inheritance, and reusable configurations.
Note
JavaScript Compatibility: Python and JavaScript share the Composer DSL. 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 is share/dsl/presets.dsl. The noisemaker.dsl module parses it. Both the reference Python implementation and the JavaScript port use that file. 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 inherit from each other. To inherit from a built-in preset, include that preset 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 does this preset inherit from? (
layers)What are the meaningful variables? (
settings)Which parameters control noise generation? (
generator)Which effects should each octave receive? (
octaves)Which effects should follow layer flattening? (
postandfinal)
Grammar¶
The DSL follows a strict grammar for parsing and validation. The parser converts programs into an abstract syntax tree (AST). The evaluator checks programs against a whitelist of operations and surfaces. It 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).Lists, dictionaries, and argument lists allow trailing commas.
Enumresolves to JavaScript enum objects exported fromconstants.js.The parser skips whitespace and comments 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.
The composer applies parent presets in order. Later presets override earlier ones. The current preset’s settings override all parents.
layers: ["basic", "voronoi", "grain"]
settings¶
Type: Dictionary
Purpose: Define variables for use throughout the preset as 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¶
Call effects 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
Check enum names: Enum references must exactly match defined enums
Check parameter names: Effect parameters must match the effect’s signature
Check setting references: They 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 has three layers:
DSL Layer (
noisemaker.dsl)This layer parses and evaluates the Composer DSL from
share/dsl/presets.dsl. Both Python and JavaScript use the same DSL file for consistency across platforms.Preset Layer (
noisemaker.presets,noisemaker.composer)This layer loads preset definitions and provides the
Presetclass for rendering. It handles preset inheritance (layering), resolves settings, and applies effects.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. You can 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. This shared source helps 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