Reference
Execution Model
The editor runs plain JavaScript in the browser. TypeScript syntax is not supported here.
Your code receives eight globals: ctx, LineCell, TargetCell, previous, start, target, diatonic, and chromatic.
The code must return either a LineCell(...) builder, a TargetCell(...) builder, or a built cell object.
You can write a single expression, or a multiline function body. In multiline mode, end with return ....
Available Inputs
ctx is the current harmonic context used by the preview. It changes when you change chord, scale, the selected line start, or the selected target note. Major chords infer Lydian by default, and minor-seventh chords infer Dorian by default.
ctx.startNote is a degree token like 1, b3, or #4.
ctx.targetNote is the destination degree token for TargetCell(ctx).
Degree inputs can be numbers or strings: 1, 3, 9, 11, 13, b3, #4, ^5, _7.
For readable relative target specs, use helpers such as diatonic(1, target), chromatic(1, target), or diatonic(-1, start). If you omit the reference, it defaults to the previous note in the current builder.
Allowed degrees are 1-7, 9, 11, and 13, with optional accidentals and octave marks.
LineCell
LineCell(start, ctx) starts a line from a note degree.
LineCell(ctx) is also allowed and starts from ctx.startNote.
.note(input) appends an explicit note.
.option(optionA, optionB, ...) or .option([optionA, optionB, ...]) appends a body choice. Each option can be a concrete note or a helper-built relative target spec.
.diatonic(n, reference?) moves by scale steps. By default it counts from previous; you can also pass start.
.chromatic(n, reference?) moves by semitones. By default it counts from previous; you can also pass start.
.start() appends the original start note again.
.leading(target?) inserts one or more local approach-note variants before the next resolution target. The target can be explicit, or omitted so the current resolve heuristic picks it first. These variants appear in the Variations section.
.resolve() with no arguments uses the default resolve heuristic.
.resolve(target) sets one explicit cadence target.
.resolve(targetA, targetB, ...) or .resolve([targetA, targetB, ...]) sets ordered resolution variants from strongest to weakest. The Resolutions panel shows them in that order.
.build() is optional. The editor will call it automatically if you return the builder itself.
TargetCell
TargetCell(ctx) uses ctx.targetNote as the fixed destination note.
TargetCell(target, ctx) is still allowed when you want to override the context target explicitly.
.note(input) appends an explicit note to the route.
.option(optionA, optionB, ...) or .option([optionA, optionB, ...]) appends a body choice. This is the way to say things like “chromatic upper neighbor or diatonic upper neighbor of the target”.
.diatonic(n, reference?) defaults to previous. On the first move, previous is the target itself. You can also pass start or target.
.chromatic(n, reference?) works the same way: default previous, optional explicit start or target.
.start() appends the original first route note again.
.target() appends the fixed target note into the body.
.leading() appends one or more local approach-note variants into the fixed target. These show up in the Variations section.
.resolve(target?) changes the fixed target if you need a different destination.
Example: TargetCell(ctx).diatonic(-1).chromatic(1, target).start().leading().
Variation example: TargetCell(2, ctx).diatonic(-1).option(diatonic(1, target), chromatic(1, target)).start().leading().target().
Target lines have one fixed destination, so the Resolutions section stays fixed, but the Variations section can still show body choices like .leading().
Resolution Algorithm
Explicit resolve always wins. If you call .resolve(3) or pass multiple targets into .resolve(...), the app uses exactly those targets in the order you wrote them.
Implicit resolve is local only. The engine builds a small candidate pool around the last body note: diatonic step up and down, diatonic third up and down, one chromatic semitone above and below, plus the nearest chord tone found inside that same local pool.
Candidates are rejected if they need more than one accidental, or if they are both melodically far away and structurally far away at the same time. In practice that removes wide octave jumps and strange over-altered spellings.
Scoring then prefers, in order: local semitone or step motion, in-scale notes, chord tones, nearest chord tones, continuation of an existing scalar or tertian pattern, stable color tones such as the major 6 and available 9, simple spelling, and fresh notes that were not already used in the line.
Notes outside the active scale are still allowed, but they are penalized as color changes. That especially pushes dominant-like notes such as b7/#6 down inside tonic major, and raised sevenths down inside modal minor unless you explicitly ask for them.
The Resolutions panel is just that ordered candidate list, and the breakdown panel shows the exact score contributions for each candidate.
Leading Algorithm
There is one public leading method: .leading(...).
For LineCell.leading(target?), the builder first decides what note it is leading into. If you pass a target, it uses that. If not, it asks the implicit resolve heuristic for the best local destination. Then it builds a small ordered set of approach notes: the best direct lead note, plus upper and lower local approach alternatives. Source-note duplicates and target-note duplicates are removed.
For TargetCell.leading(), the destination is already fixed by ctx.targetNote or by .resolve(...). The builder prefers an unused local approach into that target. If the current note already sits a semitone away, it first tries the opposite diatonic side so the approach does not collapse into a repeated note. After that it adds upper and lower local neighbours and removes duplicates or notes that are already present in the body.
In both builders, .leading() creates body variations, not separate resolution targets. That is why it changes the Variations section. Only .resolve(...) changes the Resolutions section.
Verify And Add
Verify compiles the JavaScript and checks that it builds against the current harmony.
After a successful verify, the main preview updates immediately as a draft.
The main cell preview and permutation picker use only the cell body. Resolve notes appear only in the resolutions list.
Add Cell saves the verified code to local browser storage and adds it to the library list.
If you change title or code after verifying, run Verify again before saving.
Future naming note: the DSL is reserved under the name Warble.