The current verified baseline is API v13: 64 logical buses (12 input, 8 output, 44 aux). Pin the distingNT_API submodule and verify the target firmware/API from official Expert Sleepers sources before each release; do not assume this baseline will remain current.
<working_method>
- Inspect the real repository before designing: current branch/status, API submodule commit, build targets, tests, release workflow, docs, and existing parameter indices.
- Define behavior as observable contracts. For instruments, specify voice ownership, allocation/stealing, gate thresholds, pitch conversion, retrigger behavior, sustain/panic handling, and reconfiguration behavior before changing DSP.
- Keep the public control surface small. Expose dedicated inputs only when a signal is inherently audio-rate or per-voice. Prefer the disting NT mapping system for ordinary modulation rather than adding redundant CV input parameters.
- Implement with fixed memory and bounded audio-thread work. Avoid allocation, blocking I/O, logging, and unbounded scans in
step(). - Add native behavioral tests while implementing. Exercise state transitions and discontinuities, not only steady-state audio.
- Update player documentation in the same development slice as the behavior it describes.
- Before a release, run the fact-check, automated verification, ARM inspection, and release checklist in production-workflow.md.
- Treat emulator/native checks, CI, and physical disting NT acceptance as separate evidence. Never report one as proof of another. </working_method>
<design_rules>
- Use
numFramesBy4 * 4; convert host bus numbers to buffer indices exactly once at the boundary. - Treat bus parameter value
0asNonewhen the parameter allows it. Never subtract one before validating the value. - Respect Add/Replace output mode. State whether a voltage ceiling applies to this plugin's contribution or to the final shared bus.
- Clamp all externally derived indices, voltages, notes, frequencies, and counts before using them. Extremely out-of-range CV must not crash or produce invalid oscillator increments.
- Preserve parameter indices in released plugins. Append new parameters to the underlying parameter array; page arrays may present those appended parameters on the correct UI page.
- When a parameter changes the legal range of another parameter, mutate the instance-owned definition, call
NT_updateParameterDefinition(), and synchronize the value with the callback-safe host setter. Guard recursive updates. - Declare derived bus usage to the host when one routing parameter implies additional buses. Runtime reads and routing metadata must use the same derivation.
- Reset complete musical state on the supported panic messages, including MIDI realtime Stop (
0xFC), System Reset (0xFF), and channel-mode All Sound Off/All Notes Off (CC 120/123) when the plugin consumes them. </design_rules>
<memory_budgeting> When object sections, loader errors, or plug-in memory pressure matter, read memory-budgeting.md.
- Treat pool sizes and loader placement as dated firmware facts, not API guarantees; inspect the pinned firmware and the real ARM object.
- Put persistent per-instance data in memory requested by
calculateRequirements()rather than static DTC allocations. - Use
NT_globals.workBufferonly for non-persistent scratch that fitsworkBufferSizeBytes; it resides in DTC. - Do not assume cold plug-in code can run from flash or that proposed relocation changes exist until the target firmware proves they do. </memory_budgeting>
<polyphonic_cv_gate> For a polyphonic gate/CV instrument, read and apply polyphonic-cv-gate.md. Its core model is:
- A gate group owns one gate bus and
Countconsecutive pitch buses immediately after it: pitch voiceireadsgateBus + i + 1in host numbering. - Validate the highest derived pitch bus against
kNT_lastBus, the configured voice ceiling, and all other groups' reservations. - Allocate fixed, non-overlapping voice partitions in group order. Reserve the remainder for MIDI; do not let MIDI steal a CV-owned slot or one CV group borrow another group's slot.
- Use Schmitt hysteresis, explicit sample-and-hold semantics, fresh-edge activation after count increases, and short transition fades for removal/reassignment.
- Retrigger an audible release tail in place when logical voice identity is unchanged; stealing/reassignment should fade the old sound before resetting phase/filter/envelope state. </polyphonic_cv_gate>
<documentation_and_fact_check> Documentation is part of implementation, not release cleanup.
- Keep the public guide player-focused: purpose, installation, controls, defaults, ranges, routing, ownership, S&H behavior, panic behavior, compatibility, and practical examples. Keep build/developer commands in developer docs.
- Audit every numeric or behavioral claim against code and tests. Audit firmware, API, installation, and hardware claims against current primary sources.
- Label ambiguous pitch anchors with both MIDI note number and frequency; do not rely on octave-name conventions.
- Distinguish user envelope Release from internal safety fades, a plugin's own output limiting from the final Add-mode bus, and MIDI channel messages from system/realtime messages.
- Record unresolved or time-sensitive claims instead of smoothing over uncertainty.
Use the claim inventory and release gate in production-workflow.md. </documentation_and_fact_check>
- Native unit/behavior tests, including worst-case voice counts and state transitions.
- Strict compile warnings and available static analysis.
- ARM hardware build with
-fPICand no exceptions/RTTI. - Object inspection: ELF32 little-endian ARM relocatable object, exported
pluginEntry, section/size review, and an understood undefined-symbol set. - Documentation fact-check against the release commit.
- nt_emu and/or physical hardware testing appropriate to the change, reported separately.
- Clean repository status and a reviewed release diff.
Use testing.md for desktop/emulator setup and production-workflow.md for the release gate.
<release_process>
- Use semantic version intent: breaking change = major, user-visible compatible feature = minor, compatible fix/reliability change = patch. A documentation-only correction normally does not need a version tag.
- Require explicit user approval at the production tag/release boundary.
- Use templates/release.yaml: Linux runner, pinned major action versions, recursive submodules, ARM toolchain, hardware build plus inspection, exact
programs/plug-ins/<name>.oarchive layout, downloadable workflow artifact, and tag-only GitHub Release creation. - After pushing the tag, watch the hosted workflow to a terminal result. Then inspect the live GitHub Release, download the published archive, verify its digest and contents, and inspect the shipped
.orather than trusting only the workspace build. </release_process>
<reference_guides>
- production-workflow.md — design through release, including fact-check and post-publish verification
- memory-budgeting.md — dated loader placement, pool,
calculateRequirements(), andworkBufferfacts - polyphonic-cv-gate.md — reusable gate/CV voice architecture and click-safe transitions
- reference.md — API v13 structures and callbacks
- examples.md — DSP and API patterns
- quick-start.md — project setup and first build
- testing.md — native, nt_emu, and hardware testing
- GUID-CONVENTION.md — GUID allocation rules
- templates/ — source, Makefile, and GitHub Actions templates </reference_guides>
<final_polish> Before handoff or publication:
- Fix warnings rather than suppressing them unless the exception is documented.
- Run
clang-tidywith the actual compile database when available andcppcheck --enable=warning,style,performance,portability --std=c++17 --suppress=missingIncludeSystem .when appropriate. - Inspect
arm-none-eabi-size -A,arm-none-eabi-nm -S --size-sort, andarm-none-eabi-readelf -h. Do not encode a timeless fixed plugin-size limit: firmware memory availability changes, including an increase in firmware 1.16. - Confirm immutable tables and metadata are actually const, enum strings exactly match their ranges, hot callbacks allocate nothing, and every array/index path is bounded.
- Review the user-facing diff for stale defaults, renamed controls, changed behavior, missing compatibility notes, or developer-only clutter.
- Check
git status; preserve completed work in the repository's normal durable flow. </final_polish>
<success_criteria>
- The requested behavior is covered by deterministic tests and works in the relevant live environment.
- ARM output is a valid relocatable plugin object exporting
pluginEntry. - Routing metadata matches runtime bus access, including derived polyphonic buses.
- Voice transitions and configuration changes do not introduce avoidable clicks, stuck notes, or cross-owner stealing.
- Panic clears every voice and controller state the plugin owns.
- Player docs match code, tests, packaged defaults, and current official platform facts.
- The release archive has the exact MicroSD layout and the published artifact has been independently rechecked.
- The repository is clean, committed, and pushed unless the user explicitly requests otherwise. </success_criteria>