Skip to content

FM Synth 0.1.3: LFOs and scenes reach the AMP envelope and VOIC/CHRD; LFO pitch depth as on other machines - #884

Closed
jankolkmeier wants to merge 9 commits into
repeat98:mainfrom
jankolkmeier:synth-lfo-fixes
Closed

jankolkmeier wants to merge 9 commits into
repeat98:mainfrom
jankolkmeier:synth-lfo-fixes

Conversation

@jankolkmeier

@jankolkmeier jankolkmeier commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Related to #883. FM Synth 0.1.2-experimental → 0.1.3-experimental. Engine by Tim Hastie (@timhastie), changes by Jan Kolkmeier (@jankolkmeier). This is not an author update, so it needs owner review. Draft: it needs an owner approval and the release builds; see "Needs the owner".

What changes for players

  • LFOs and scene locks on AMP ATK, HOLD and REL now shape FM Synth's envelope. Before, only the knob and p-locks did.
  • An LFO on PTCH now moves the pitch as far as on FLEX and STATIC tracks (full DEP ≈ ±1 octave). Before, it moved five times further. The PTCH knob, p-locks and scenes are unchanged: they stay in whole semitones.
  • Scene locks now reach VOIC and CHRD.
  • VOIC, CHRD and RATO jump between scenes instead of interpolating. This is a deliberate difference from the stock morph, which interpolates every scene value. These three are a voice count, a chord shape and a ratio table position, so a crossfade would pass through unrelated counts, chords and ratios in between.
    • Which value: below the fader's midpoint scene B's value applies; from 64 on, scene A's.
    • No lock: a scene that doesn't lock the value, or is switched off, gives the knob value with p-lock, as the stock morph treats it at that end.
    • When: VOIC and CHRD apply at the next note start, as before. RATO applies at once (the engine reads it every frame), and an LFO on RATO still adds to it.
  • Known change: a saved project with an LFO on an FM track's PTCH swings one fifth as far as before.

How

All in upstream/synth/poly.s, Modwerk's copy of the octatrick engine. No new hook sites, pokes or tables; one 64-byte per-track snapshot (po_amp).

  1. Envelope. sy_ison overrides the DSP voice record's ATK/HOLD/REL (0 / INF / INF) every frame. The engine read those values from the live lane, which carries Part values and locks but not the LFO and scene-morph contributions, which are written to the voice record.
    • po_amp_snap keeps the record's values per track while they're still the frame builder's: in sy_cold at a START, and in sy_ison before the override.
    • po_amp_at serves the five readers (sy_cold, po_mono_env ×2, po_frame ×2, po_st_note). Until a track has a snapshot it falls back to the lane.
  2. Pitch. sy_word stretches the PTCH word ×5, because a synth PTCH unit is a semitone, and the LFO's contribution was stretched with it.
    • po_lfo3_depth, the existing detour at the LFO engine's depth read (both copies), now also divides the depth by 5 on a synth track when the LFO's PMTR is PTCH.
    • Full DEP counts as 0x8000, matching stock's +256.
  3. VOIC/CHRD readers (sy_warm, po_st_note, the held-chord path) now read the snapshot.
  4. Jumps.
    • po_scene_jump picks VOIC/CHRD from the morph's working copy (0x80000ed4 + 64 t, [A, B] per slot; MIDI.md App. C), the fader (0x460d16c8) and the scene-off flags. The flags are 0x80000006 for A and 0x80000007 for B, read from the morph at 0x4000cc6c.
    • po_ratio_word takes the morph's share out of the RATO word, (eA·xf + eB·(127 − xf)) << 8 / 127, and puts the nearer scene's value in. The LFO share stays.

Other files:

  • sdk/machines/octatrack/module-adapters/machine-ports.json: poly.s hash.
  • machine-recipes.json: the synth recipe recaptured for the new image (--capture-stock-recipe; 38 copies, hashes only).
  • sdk/imports/synth-fixes-0.1.3.json: the full delta from the original import, as synth-fixes-273-264.json was for 0.1.2. sdk/tests/test_synth_release.py now reads it.
  • Catalog pin; release notes in the module folder and in src/community/module-changelogs/synth.json.
  • README, and a new TESTING.md. The 0.1.2 report moved to evidence/release-0.1.2/TESTING.md.
  • evidence/lfo-regressions.json: sanitized measurements and the hardware report, per build.

USB package: 26,660 bytes (+500 against 0.1.2), module id e8c3e4fa unchanged, strict stock-free scan passes. Before any change, the published 0.1.2 package rebuilt byte for byte. Compatibility is unchanged: same sites, events and conflicts.

Evidence

Emulator. The adapted ot_emu (scripts/octatrack-emulator/build.py) running base cd39c494, with packages installed over emulated USB. T1 is FM SYNTH with INDX 0 (a sine), trigs on 1/5/9/13, LFO1 from the project file; T1's stem was measured.

Case 0.1.2 0.1.3
Square → PTCH, DEP 10, constant +1 +10.0 st +2.0 st
Square → PTCH, DEP 10, moving n/a ±2.0 st
Square → REL (30), DEP 60, constant +1 25 ms per note τ ≈ 0.45 s (REL 90 law)
Square → REL (30), DEP 60, moving n/a alternates REL 90 / REL 0
Part VOIC 3 + CHRD, no scenes chord bit-identical to 0.1.2

The sample jumps at each trig on held notes are identical in both versions: the existing retrigger dip.

Hardware (reported by @jankolkmeier, not verified). Octatrack MKII, OS 1.40C, base cd39c494, over USB, beside Sidechain Compressor 0.1.2, Preview Vol 0.1.2 and the draft Deja Vu. The build each result comes from is listed in evidence/lfo-regressions.json.

  • LFOs on ATK, HOLD, REL and PTCH behave as on FLEX.
  • Scenes on ATK/HOLD/REL work.
  • Scene locks and p-locks on VOIC/CHRD work.
  • VOIC, CHRD and RATO switch once at the midpoint; a scene without the lock plays the knob value.
  • An LFO on RATO steps around a scene-locked RATO.
  • No clicks, stuck notes or hangs; a power-cycle restore plays.
  • An A/B against a FLEX track matches stock for CHROMATIC keys under a PTCH scene lock, CHROMATIC keys while stopped with a PTCH LFO, and the PTCH knob on a sounding p-locked note.

Not tested:

  • chip worst-case cycles and complete memory bounds;
  • MKI, maximum simultaneous FM tracks, MIDI note input;
  • the scene-off flags on hardware.

Needs the owner

@repeat98, everything except your approval is now in the PR:

  • Release builds:
    • compiled packages from the pinned toolchain (build-modules-isolated.sh + modules:import);
    • the USB package 0.1.3-experimental (ff99f9c2…, the build tested on the MKII);
    • the native comparison: 154 selections, 54 matches, 100 matching refusals, 0 mismatches.
  • Fresh LCD captures of 0.1.3: the same plan as 0.1.2, pixel-identical screens, provenance updated.
  • Release tests (synth-release.test.ts, test_synth_release.py) for 0.1.3.

CI stops only at sdk/synth-build-approval.json, which only you can give. With this record, both release tests pass locally (tried with a stand-in that was never committed):

{
  "kind": "owner-approved-update",
  "id": "synth",
  "version": "0.1.3-experimental",
  "sourceSha256": "e5c05ab5272f83e7142d6bc00e40a1aa3de7d899a5fa2336332ff2eb5dac9fbe",
  "approvedBy": "repeat98",
  "approvedOn": "<date>",
  "waived": ["current-build-hardware", "chip-worst-case-cycles", "complete-memory-bounds"],
  "ownerStatement": "<your words>",
  "reason": "<why>"
}

No release-documentation waiver is needed any more, because the captures are fresh. Two other points:

  • chooser-metadata.json: module:verify asks to commit a change to it that only moves POLY8's entry within the list, with the same content. I left it out, since the beta base hashes that file. Your call.
  • Tim's FM Synth 0.1.5: scale quantizer on MIDI tracks (the author's 2.13 quantizer) #555 (0.1.4/0.1.5) touches the same files and is on hold. If you'd rather fold these changes into his line, say so; the same code is unchanged at his v2.15.

Checklist

  • New or changed module: category guide (machines.md) walked; TESTING.md lists what is not tested; npm run module:doctor -- synth: blocked at the owner approval.
  • Builds, installs and works alone; no requires, no hooks into another module; uiClaims unchanged.
  • Greater semantic version and exact catalog pin.
  • Source and media properly licensed; authors and licence texts preserved (MIT, Tim Hastie; octabam wrapper, Sam Banks).
  • No Elektron firmware, extracted routines/tables or images. The recipe holds addresses, lengths and SHA-256 only.
  • Screenshots: UI unchanged; the 0.1.2 captures are retained, pending the owner's waiver.
  • TESTING.md records commands, results, source and limitations; emulator and hardware evidence are separate.
  • resources.impact / tests.qualification: unchanged figures; +64 B state, +500 B package; worst-case cycles not re-measured.
  • Attributed hardware evidence verified by the reviewer: reported results only.
  • Rebased onto current main; npm run pr:ready says Ready: merged with main (71600295); pr:ready stops at the owner approval.

Validation commands and results:

python3 -B sdk/runtime/loader/build.py --octabam-machine synth --stock <OS 1.40C> -o …   # package builds, strict stock-free scan: pass
python3 -B -m unittest sdk/tests/test_synth_release.py   # 2 of 3 pass; the third needs the 0.1.3 approval
npm run pr:ready                                          # stops: "synth: owner-approved update does not cover this module version"

What the guides lacked or got wrong

  • Interactive ot_emu play needs extra steps that I found no guide stating for the interactive mode:
    • --mount for the project load;
    • CLOCK RECEIVE cleared (0x80000028 bit 0; --internal-clock only acts with --sequencer);
    • frame on before PLAY.
  • USB installs need the adapted emulator. On the upstream emulator, runtime installs are refused as BUSY (unknown task state). The adapter's README says so; docs/OCTATRACK_DRIVING.md doesn't.

Both go into a separate Docs: PR.

🤖 Generated with Claude Code

jankolkmeier and others added 3 commits October 24, 2026 23:40
…on FLEX; VOIC/CHRD/RATO scenes jump

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@repeat98

Copy link
Copy Markdown
Owner

Thanks Jan, that's a really thorough one. Two things first. @timhastie FM Synth is your module, so I'd like your OK before this goes in. Jan found that LFOs and scenes don't reach AMP ATK/HOLD/REL or VOIC/CHRD on FM tracks, and that an LFO on PTCH swings five times as far as on FLEX (#883). The fix is all in poly.s. Are you fine with it as is, or would you rather take it into your 2.18 re-cut? Jan: my #895 also claims 0.1.3-experimental. I'd like to merge that first, so this one becomes 0.1.4-experimental after merging main. Once Tim's OK is in I'll look at the approval and the release builds.

jankolkmeier and others added 5 commits October 25, 2026 09:39
…dules-isolated.sh at 34206f4, modules:import --development)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for 0.1.3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@timhastie

Copy link
Copy Markdown
Contributor

Thanks Jan, good finds. I'd rather not merge this as it is, because my line has moved on: scene locks on VOIC and CHRD have been in it since 2.17, and there the crossfader deliberately sweeps through every voice count and chord shape rather than jumping at the midpoint. I'll fold your two other fixes into my re-cut, credited to you: LFOs and scene locks on AMP ATK / HOLD / REL shaping FM's volume envelope, and the PTCH LFO depth matching FLEX. When my re-cut is up, #884 can be closed in its favour. Thanks again!

@jankolkmeier

Copy link
Copy Markdown
Contributor Author

Thanks for checking it out, and happy to help! I (or let's be honest: claude) did not realize you had a further advanced version of this elsewhere. Glad I did still find some things to improve, though! I also just realized an hour ago, sweeping through VOIC and CHRD is probably the better call, as demonstrated by innerkop :)

@timhastie

Copy link
Copy Markdown
Contributor

Thanks Jan! I'll tag you when the re-cut with your two fixes is up.

@repeat98

Copy link
Copy Markdown
Owner

Thanks again for these, Jan. As Tim said, his line has moved on, and he's folding your two fixes into his re-cut with credit: LFOs and scene locks on AMP ATK/HOLD/REL shaping FM's envelope, and the PTCH LFO depth matching FLEX. So I'm closing this one in favour of that. Nothing is lost; your changes reach FM Synth through Tim's release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants