63 KiB
Product Vamp Technical Guide
Repository Shape
Product Vamp follows the Modulus product layout used by the khodo repos:
include/vamp_zeroandsrc/vamp_zero: Pi Zero C++23 application.include/vamp_picoandsrc/vamp_pico: Pi Pico 2 W C++17 firmware.include/vamp_protocolandsrc/vamp_protocol: shared route and page-command contracts used by both devices.cmake/pico.cmakeandcmake/zero.cmake: device-specific build definitions selected by the rootCMakeLists.txt.include/vamp_style.hppandsrc/vamp_style.cpp: shared visual palette.include/vamp_pico/artwork.hppdefines the Pico coverage-image and sprite interfaces; fixed artwork is generated in the build directory.data: original songs, paired training solos, the frozen solo model, and harmonic/accompaniment policies. Expected-output fixtures live undertests/.generator: build-time Python source analysis, independent role generation, authored solo reuse, learned prediction, guitar placement, engraving, recording validation, Pico artwork, and catalog construction.package: generic bundle inputs, scripts, config, fonts, metronome samples and backing recordings, and device install files.
Zero Runtime
The Zero process can run headless for local/API quick checks or with Luma for the Waveshare Zero-DISP-7A display.
runtime.headless controls display startup and audio.enabled controls audio startup. The unused runtime.hardware setting has been removed; older copies of that field are ignored like other unknown configuration fields.
The API is plain HTTP on the private AP:
GET /api/healthGET /api/remote/displayPOST /api/remote/title(registered title image ID)POST /api/remote/heartbeatPOST /api/remote/nextPOST /api/remote/previousPOST /api/remote/select
There is no browser remote, HTTP font route, or full-catalog status dump. control owns serialized device actions; direct menu actions carry the displayed input revision and catalog identity so intervening remote navigation invalidates them. Pico display responses include only focused-item/settings values and the selected title artwork ID.
Page commands use /api/remote/next and /api/remote/previous; the old /api/page/* routes are removed.
The AP setup pins NetworkManager to 2.4 GHz channel 6 with WPA2/RSN and CCMP ciphers so the Pico W can join using CYW43_AUTH_WPA2_AES_PSK.
On Raspberry Pi OS Lite, the installed display service owns tty1, conflicts with getty@tty1.service, and starts the Zero container through product_vamp_start_zero. The wrapper switches to the display VT when chvt is available and runs vamp_zero inside the container through setsid --wait --ctty with /dev/tty1 as stdin, giving raylib's DRM backend a real virtual terminal while keeping Docker attached to the long-running display process. SSH remains the normal admin path and tty2 is enabled as the fallback local console.
At startup the Zero validates the packaged catalog.db application identifier and required table structure, opens it read-only through Noto, and loads menu metadata. It never creates or migrates a database, scans score JSON, or repairs metadata. The main menu contains Performance and Settings. Settings holds Backing, Metronome, and Tempo; its values apply for the current session. Song -> Part -> Score navigation offers Acoustic, Electric, and Vocal without key selection. Musical interpretation, spacing, beaming, ties, accidentals, and pagination happen on the build host. The Zero displays engraved PNGs and retains at most two page textures.
Runtime catalogs contain immutable metadata shared by navigation, the HTTP service, and the UI. Song groups reference canonical records by index. SQLite retains complete bundles, while cached measure/page counts let menu queries avoid reading musical JSON. Python constructs the complete database in one transaction before deployment; metadata and embedded JSON are published together after an integrity check. The songs table uses strict column types, primary-key and path uniqueness constraints, and JSON validity checks. Menu rows are ordered by title, composer, part, and ID with binary collation, using the matching songs_menu index; C++ groups the ordered rows in one pass without sorting them again. Noto provides read-only connections, bound score lookups, application-ID checks, and quick integrity checks. Product-specific schema and asset-path validation remain in the application. There are no custom rows, runtime imports, metadata-repair triggers, or persisted page-state tables. Selecting a score loads only that bundle asynchronously, discarding stale results after navigation; the view keeps at most two page textures.
--once (also runtime.exit_after_startup) is a headless readiness check: it checks database integrity and validates the catalog and referenced page assets, checks runtime-directory writes and the configured server bind, and exits without opening audio/display devices or publishing a readiness heartbeat. Page checks include containment, checksums, CPU-only PNG decoding, and exact 976x484 dimensions. The configured heartbeat destination must be absent or a regular file, never a directory, symlink, or special file. Runtime writes and readiness probes use exclusive sibling temporary files; readiness leaves an existing heartbeat unchanged and removes its probes. --health-check instead probes the running service's read-only /api/health endpoint using the configured timeout. A stopped/unresponsive service or an invalid response fails the check; it does not create or repair files. --check-config remains configuration-only.
Pico networking progresses without blocking touch sampling. Navigation commands use a bounded 16-entry FIFO ahead of background status polls; a full queue reports busy. Commands are not automatically retried because a timed-out request may already have executed. Background polls wait one second after completion before trying again.
The Zero menus use direct taps: release on a row to select it, tap switches to toggle them, and use the tempo minus/plus buttons to adjust BPM. Drags, canceled touches, multiple fingers, and stale menu actions do not activate items. Back clears tempo editing; returning to Settings restores the last setting's focus. Long song lists have previous/next focus buttons.
In score view, Zero and Pico share one gesture state machine: a second release within 220 ms of the first release produces previous, a hold of at least 700 ms produces select once, and other taps produce next. A late second release preserves both single taps; holding consumes a pending first tap. Zero schedules pending-tap deadlines without polling and discards canceled or obsolete timer callbacks.
Score Generation
The build runs ./modulus.py python -m generator against data/ and writes build-only assets under the CMake binary tree. Every song produces acoustic, electric, and vocal parts in its authored musical key. Source counts are discovered, not fixed in code. Generator-only optional chord markers are consumed before publishing.
Songs live directly in data/songs/. Each source produces acoustic, electric, and vocal parts under songs/, with matching page directories. Adding, renaming, or removing a source requires no registry edit. Regeneration replaces the output tree and catalog. The two current songs generate six parts.
The pipeline is Soundslice/MuseScore -> uncompressed .xml -> musical model and rhythm plans -> part generation -> semantic JSON, supplied WAVs, engraved pages, and Pico title artwork -> packaged SQLite catalog and assets. Export MusicXML 3.1 or 4.0, score-partwise, with one part and one staff. The musical model retains independent voices and per-tone durations/ties. Packaged music describes each generated part, not an archival copy of the XML.
Musical Data
data/songs/ contains authoritative source scores. data/training/manifest.json pairs each authored solo with a source and a slot (1 or 2), and declares its pitch convention. Both slots train one pooled model. Slot 1 maps to acoustic playback and slot 2 to electric playback; slot and instrument names are not model features. The four XML files retain their notes, harmony, timing, ties, rests, Autumn Leaves pickup and All of Me's full-bar opening. Explanatory labels such as Lead in, b9 and 6 were removed; they were not model features. Written chord extensions remain intact.
data/models/solo.json is an explicitly trained, versioned statistical model. Its short-context counts, rhythm and register statistics are learned from the paired examples. Provenance records input hashes and the training version. This is supervised use of the supplied solos, not independence from them and not a neural network pretrained on an external corpus.
data/composition/ contains shared guitar/difficulty policy, sparse accompaniment figures and explicit harmonic-treatment validation rules. The exhaustive rhythm/interval catalogs, 21-pattern scale catalog, fixed maps, old solo grammar and handwritten phrase scorer have been removed. The renderer can still display existing scale annotations as part of its compatible score schema; new solos do not create them.
MusicXML Contract
| Area | Accepted notation |
|---|---|
| Pitch | Exact rational letter/alteration/octave spelling, including microtones, double/triple accidentals, and courtesy accidentals. B-sharp and C-flat retain the correct octave. |
| Rhythm | All fourteen MusicXML types, maxima through 1024th; up to sixteen augmentation dots; exact positive rational metric durations and separately stored written values. Time modifications must reconcile the two. |
| Chords and rests | Independent voice lanes, simultaneous chord tones, shorter additional chord tones, rests, full-measure rests, hidden events, forward spacers, and rhythm slashes. |
| Ties and tuplets | Per-pitch ties, including partial chord ties; nested and cross-bar tuplet spans. Nested tuplets require explicit tuplet-actual and tuplet-normal numbers. |
| Small notes | Grace notes with zero metric duration, cue notes, and measured single/paired tremolos. |
| Staff state | G/F/C, percussion, and no-clef staves; octave clefs and octave-shift spans; major/minor/modal keys or custom seven-letter alterations; timed key, clef, and meter changes. Authored TAB and technical fingering instructions are rejected; TAB is generated from pitched guitar parts. |
| Meter | Simple, compound, additive, mixed-denominator, and unmetered timelines. A partial measure must be marked implicit="yes"; an initial implicit measure is the arrangement pickup, including a full measure padded with leading rests. |
| Harmony | Structured roots, kinds, inversions, slash basses, and degree additions/alterations/subtractions. Harmony events have their own offsets, including inside held notes. Electric melody generation intentionally reattacks eligible long notes at changed harmonies; import preserves the authored notes. |
| Text | Title, composer, part name, multiple lyric verses with syllabification, elisions, and extenders, rehearsal labels at measure starts, and Vamp directives. |
| Tempo | Metronome directions and positive quarter-note BPM from <sound tempo="...">, either within a direction or directly in a measure. Playback/navigation attributes on sound are rejected. |
| Reading order | All measures are written out. Repeat bars, numbered endings, navigation jumps, and pass-specific ties are rejected. Ordinary barlines and contiguous ties remain supported. |
Multiple staves/parts, D.C./D.S./coda/fine navigation, slurs, dynamics, articulations, and expressive ornaments are outside the product contract and rejected. Unknown musical elements also fail. Serialized times and alterations use reduced signed-64-bit numerators and positive denominators; pitch octaves are -4 through 16, clef/octave shifts span at most three octaves, and meter counts are 1-1024 with denominator units 1-4096. These resource bounds produce diagnostics, not clamping or truncation.
Layout/exporter information such as page defaults, credits, print layout, font/color/position attributes, and source pagination does not determine the small-screen layout. Engraving is regenerated. Beam groups and written note values are retained; unrelated engraving metadata does not authorize ignoring musical descendants. defusedxml==0.7.1 forbids XML entities and external access. Ordinary MusicXML DOCTYPE declarations are allowed without fetching their DTDs.
Chord labels are generated from structured harmony using M, m, aug, dim, and sus: for example CM, Cm7, CM9, Caug, Cdim7, Csus, Csus2, and Cm7b5. CmM7 is a minor triad with a major seventh, corresponding to MusicXML's major-minor kind. Ninths, elevenths, and thirteenths include their underlying seventh; Cmadd9 does not. Slash basses, degree alterations, added tones, and omissions remain semantic, not display-only. Older recognized spellings such as -7, maj7, and + are accepted but rendered canonically.
Standard MusicXML kinds and explicit degrees remain authoritative. For Soundslice compatibility, a text-only <kind text="mM7">other</kind> (also m7b5, 7b9, and other recognized chord spellings) is normalized into a standard kind and degrees. Complete symbols such as (Fm7) or (Bb7) supply their own root; an exporter placeholder C is accepted only for text-only other harmonies. A conflicting non-placeholder root, conflicting bass, contradictory structured chord, or unknown spelling is an error with source/measure context. Raw exporter text is not retained as an override that could become stale during transposition. This compatibility path does not require editing the XML or weakening structured-harmony validation.
Vamp Directives
Musical genres and player-style presets are removed. There is no default genre, alias table, generated style metadata, or genre selection. The strict importer rejects every obsolete vamp:style miscellaneous-field hint, including an empty value, instead of discarding it. The XML writer does not emit that field. Keys, modes, keyless notation, and local changes remain preserved; there is no transposition reference or vamp:tonic property. Visual styling, font attributes, engraving Style, presentation fingerprints, and ordinary <bar-style> notation are unrelated and remain supported.
Parenthesized chord symbols mark optional turnarounds, including Soundslice's text-only (Fm7) and (Bb7) exports. Alternatively, place staff text vamp:optional-chord at the same onset as exactly one harmony; this text may be hidden. Earlier arrangement sections retain those chords and their parentheses; the final vocal pass omits them. The normalized harmony stores parenthesized display metadata separately from the generator-only chord_optional flag. Orphaned/duplicate markers and unknown vamp: properties are errors; consumed directives never enter runtime JSON.
Linear Reading Order
There is no repeat analyzer, expansion pass, or repeat/ending engraver. Performance writes an eight-bar intro and five full passes (vocal, guitar melody, acoustic solo, electric solo, vocal) explicitly onto successive pages; neither display nor playback jumps backward.
Repeat signs, numbered endings, D.C./D.S./coda navigation, and time-only ties fail import rather than being ignored or expanded. Write the intended notes and rests explicitly, including any alternate ending. Legacy repeat fields in generated musical documents are rejected; regenerate assets instead of retaining a compatibility interpreter. Full leading-rest measures remain full measures, not inferred pickups. Missing titles fall back to readable, spaced filename titles and missing composer credits remain empty, as with the current Soundslice exports.
An explicitly marked opening pickup is excluded from the repeated song body. At each vocal or electric melody entry, it is right-aligned within the preceding arrangement bar. Outgoing music continues until the pickup's first sounding note, then releases as the incoming melody takes over; pickup rests do not erase earlier accompaniment. Lyrics, ties into the following downbeat, tuplets, and octave spans retain their timing. Pickup harmony changes also apply to the accompanying parts at their relocated offsets. All parts share the same bar count, and a pickup longer than its destination bar fails generation. Autumn Leaves uses a four-beat padded pickup and a 32-bar body, yielding 168 arrangement bars, including the eight-bar intro.
Ordinary ties still link adjacent matching tones across measures and systems. Tuplets, independent voices, local staff changes, and manual page navigation are unchanged. The recording’s three-bar sustain tail has no added score section or metronome clicks.
Runtime Schema
A newly generated bundle has three top-level fields:
music: metadata and a single staff containing measures, independent voice lanes, timed harmonies/attributes/tempos, instruments, tuplets, and octave spans.presentation: engraver name, style checksum, fixed width/height, ordered relative PNG paths with SHA-256 checksums, and thetitle_imagecontent identifier for Pico artwork.performance: normalized source song ID, exact score duration, count-in length and metronome beat/accent positions. Each presentation page additionally storesfirst_measure,measure_count,start_beatandend_beat, derived from its actual engraved systems. These ranges must cover every measure once, without gaps.
There are no authored-key/selected-key metadata pairs or musical genre/style fields. The score's key metadata describes its actual music, and menus display that key alongside its part name. Instrument normalization transforms explicit key cancellations with the same interval as the notes. Presentation's style_sha256 identifies visual engraving settings and fonts, not a musical genre.
Measure durations, event onsets/durations, harmony offsets, span times, and pitch alterations use { "numerator": N, "denominator": D }. Times are in quarter-note units and alterations in semitones. There is no tick grid, display-duration surrogate, or runtime flattening into one voice. Written note types, dots, and time modifications remain distinct from metric duration. Every event owns a voice; every chord tone has its own ID, written duration, duration, and reciprocal tie links. Tuplets reference events and belong to one voice.
Shared Part Generation
production.generate_role accepts immutable canonical source analysis, a role, production data and settings. Canonical musical identity excludes display titles and filenames. Accompaniment, source melody, vocals and each solo have their own caches and can run in independent worker processes. No role reads another generated role. Assembly applies source-defined participation windows, including guitar-role exclusions and returning pickups, without changing another role's decisions.
parts.py retains exact source notes, rests, lyrics and ties. melody.py harmonizes eligible sustained electric source-melody notes using their actual chord, preserving source pitches while splitting and reattacking held notes at harmony changes. Guitar notation applies its octave transposition once. comping.Canonical independently chooses compact chord voicings and rhythm from the source form and shared policy, then reuses that part at the same source-form position throughout the arrangement. There is no generated-solo response or reuse of a generated melody's grips. Sparse bars and deliberate releases are legal.
Electric accompaniment shares the electric melody's guitar.chord_positions(..., profile='electric') families: strings 1–3 or 1–4, with a preference for four-note grips. It uses the same written chord requirements without taking responsibility for the slash bass. Acoustic accompaniment uses acoustic_voicing.candidates: exactly one bass on string 5/6 and one upper note each on strings 4/3/2. Without an explicit slash bass or inversion, its bass is a root or written fifth (including an altered fifth); plain major/minor triads also permit the third. Plain triads retain all three chord members. Explicit bass/inversion requirements override this choice. Unsupported grips fail with the source measure, beat and chord rather than weakening the layout or harmonic requirements.
The active voicing selector compares complete paths with dynamic programming over concrete grips. Electric costs prioritize the shared melody family's string count, then grip effort plus hand-center travel, missing optional tones and register preference. Acoustic costs prioritize travel of the bass and all three upper voices, then optional-tone coverage and register preference. Stable role-specific keys resolve equal costs. Silent spans retain the preceding hand position. A continuous accompaniment repeat includes the last-to-first transition. Source-harmony variants, such as relocated pickup harmony, are solved between fixed canonical neighbors without mutating a shared previous-grip state; request order cannot change them. Per-span composition reports include candidate counts, selected string/fret positions, bass degree and movement.
comping_rhythm.py constructs whole-measure rhythms from version 2 of composition/accompaniment.json. Normal full-measure ranges are 0–2 electric chord attacks, 1–2 acoustic bass attacks and 1–3 acoustic upper-chord attacks. Bass starts on beat one whenever harmony is available there. An absent harmony or N.C. remains silent. Candidates mix half-beat stabs, quarters, longer holds and alternating durations. Exact chord offsets constrain sounding intervals; short partial bars have bounded gestures. Rapid harmonic changes can authorize one extra attack per layer, recorded as an exception, without assigning a fresh density budget to every chord.
The original head supplies entrances, moving-note intervals and space; the actual progression supplies harmonic support requirements and release boundaries. Candidates retain distinct attack counts and articulation families before the cutoff. Acoustic bass/upper candidates are paired and scored for combined crowding, counting coincident attacks as one action. A bounded dynamic program selects the form's complete rhythm path using source-context costs and preferences against immediate whole-bar repetition. This chooses from retained candidates, not every possible rhythm. Compatible sustained neighbors with identical harmony and grip can then join across a barline without reattack; the upper layer retains at least one new attack per bar and bass always reattacks. Changing only tempo leaves these musical decisions unchanged. Tempo remains in the concrete asset cache key so score metadata refreshes correctly.
Acoustic bass and upper events retain independent voice lanes through assembly and notation, so either layer may sustain under another attack. Validation also checks the combined sounding grip across those voices. The product build audits canonical accompaniment reuse across arrangement iterations, allowing only source-derived changes in harmony, pickups and role handoffs. The .roles sidecars report per-measure families, attack counts, overlaps, ties and exceptions. Their selection_cost records local rhythm-candidate contributions before cross-bar joining; the form selector also applies the documented transition cost. Solos retain their existing learned source-melody/harmony conditioning and registered exact performances.
Monophonic solos use strings 1–6 and frets 1–12, with no open strings or acoustic/electric string restrictions. Placement compares complete paths through legal positions, accounting for hand-region changes, travel time and string crossings. Ties retain their position. Chord placement retains its separate policy: frets 1–20 with a sounding anchor at fret 12 or below and the existing inclusive three-fret span. Acoustic accompaniment additionally keeps its bass within frets 1–12 and requires an independent anchor in its upper three-note cluster, preserving the option of separate bass/upper attacks. These costs approximate physical feasibility; they do not assign fingers or guarantee comfortable playing.
Recording Playback and Page Timing
Vamp uses only supplied PCM16 WAVs. package/assets/sounds/beat1.wav and beat2.wav provide measure accents and ordinary beats. Backing files are package/assets/backing/<normalized_source_id>_<bpm>.wav. generator/performance.py checks containment, format, decoded length and checksums, then copies the recordings byte for byte into the staged output. A catalog resources row stores their paths, checksums, sample rates, channels, frame counts, song IDs and tempos. No note-event audio compiler, pitch synthesizer, release envelope or generated audition audio remains. Pitch mathematics remains for notation, TAB, composition and exact MusicXML comparisons.
Settings exposes audio.backing_enabled, audio.metronome_enabled and audio.tempo_bpm; both toggles default off and tempo defaults to 120. The fixed tempo set is 60 through 120 in steps of 10. Backing restricts it to the intersection available for all catalog songs. Turning it on snaps to the nearest available tempo, with ties going slower; an empty intersection makes it unavailable. Obsolete playback and configurable tempo-range settings are rejected. Recordings are never stretched or trimmed.
The existing recordings contain one bar of count-in, 168 score bars, then three held bars and decay. Backing-only plays the complete file. Both toggles play the recorded count-in alone, then backing plus sample clicks during the score; no clicks play over the held ending. Metronome-only supplies its own one-bar count-in and completes at score end. Both off enables manual page navigation. Opening a part with either toggle on starts automatic mode. Next/previous are ignored during automatic mode; hold exits, and natural completion returns to the selected part menu. The final page remains displayed through a recording tail.
Preparation runs on a worker: checksums and samples are loaded before publishing a Sonus recording transport. Sonus streams backing audio through miniaudio’s background resource manager and mixes preloaded stereo clicks on the same rendered-frame clock. Callback work uses preallocated buffers, and resource retirement occurs on control threads. Stream failures stop progress and surface an error. Absolute rational beat positions are rounded to frames at 48 kHz once per position, avoiding accumulated rounding drift. The UI polls that audio clock for page boundaries and Count-in/Playing/Sustain status; wall-clock timers never determine musical progress.
Page metadata comes from actual system pagination and is preserved and revalidated in engraving caches. The UI prepares the current and next page before playback starts and retains a two-texture cache. Session and catalog identities reject stale preparation, page updates and completion after cancellation. Missing or corrupt pages/audio leave a visible error and permit hold/back. --once validates packaged recording checksums and all score pages without opening audio/display hardware.
Authored Repertoire and Learned Solos
The production decision is explicit:
- Look up the canonical source music and solo slot in the training manifest. A registered pair uses its authored solo exactly, with deterministic guitar placement and normal notation/export. This is library reuse, not a claim that a model reproduced it. An incompatible arrangement window, invalid tie, unsupported harmonic relationship or unplayable note fails rather than clipping, transposing or replacing it. Authored chromatic/color notes pass the same contextual harmonic validation as predictions; there is no authored exemption.
- For an unregistered pair, load the frozen pooled model and call
solo.composer.compose → decoder.Decoder → placement → validate → notation/export. This path does not read authored notes or train during an ordinary build.generate_role(..., settings={'predict': True})explicitly exercises prediction even for a registered song.
The small conditional model backs off from up to four recent joint pitch/rhythm events through source-melody features, current/future chord quality, metrical phase and global evidence. Pitch relationships are relative; there are no song-name conditions or per-song prediction tables. All examples contribute to the same distribution. Source intervals, sounding state and chord-relative source pitches influence prediction. Register comes from pooled training evidence, not an acoustic/electric octave formula.
Prediction uses constrained autoregressive sampling with a stable seed derived from canonical musical source content, slot/role and relevant configuration. The two slots draw from the same learned distribution; the seed permits different interpretations without teaching separate instrument styles. It scores valid joint continuations by their learned likelihood, then draws deterministically from the normalized probabilities. This avoids the mostly-silent maximum-likelihood mode observed during testing. At most four deterministic attempts are allowed if a sample cannot finish or contains no sounding notes; this is not a beam search or a claim of global phrase optimization.
Binary durations and complete triplet groups remain exact. Contiguous silence is one event, matching training tokenization; the decoder cannot repeatedly concatenate rest tokens to invent a long silent passage. Selected holds survive barline notation splits. Generic chromatic approaches/enclosures include their exact target as complete validated actions. Their event score is identical to emitting the constituent events separately, and equivalent actions are deduplicated before sampling. The report's negative log likelihood is diagnostic, not a ranking of complete solos or a model of listener preference.
Fixed scale maps no longer constrain prediction. Brief ordinary movement uses the source key and written chord tones; chord quality, alterations, omissions and slash-bass semantics remain authoritative. Long nonchord events require a supported color or resolution relationship. Approaches resolve to their specified pitch and octave; anticipation validates the upcoming harmony. Missing harmony does not authorize arbitrary pitches. Search failure is explicit and never invokes the old composer.
solo.recognition identifies these relationships automatically in a complete sounded line, using the versioned policies and solo.expressions validator. It checks actual preparation, pitch/register, duration, future harmony and resolution; annotations, filenames, authored status and source IDs grant no permission. A bounded deterministic search finds mutually consistent coverage of exposed events, without changing notes. Malformed explicit contracts remain errors. Tied notation fragments are merged before harmonic classification. Unsupported notes, missing harmony, expired resolutions and exhausted recognition budgets fail explicitly. Users do not need to supply explanations or tags.
Prediction proposes stable and resolving colors, anticipated colors, delayed approaches with real space or intervening valid motion, and chromatic passing notes between valid endpoints, alongside the existing complete enclosures and connections. These are joint actions containing their required destinations. Disabling expressions removes these options. Prepared suspensions are recognized and validated but do not yet have a dedicated prediction action. The model still selects from its small learned distribution; validation establishes supported musical relationships, not aesthetic quality.
Training checks the complete source-form harmonic context. In the repeated performance form, an unlabeled pickup inherits the final source-form chord; an explicit pickup chord or N.C. takes precedence. This is the same arrangement context used for playback, not an invented first chord or a rule tied to either song name.
The model has only four examples from two songs. Its four-event context does not establish long-range thematic memory, nor does training prove transfer to another standard. Phrase report boundaries describe audible breaths after selection; they are not another composer or an assertion of phrase-wide theme planning. This initial implementation can still repeat local patterns or choose weak continuations on unfamiliar harmony. Exact authored playback and novel model output are reported separately.
Train explicitly:
./modulus.py python -m generator.train --leave-one-song-out
This writes the pooled model to data/models/solo.json and diagnostics to build/training/report.json. Omit --leave-one-song-out to train without held-out likelihood diagnostics. Ordinary asset builds never run training.
Register an additional pair and retrain without changing Python:
./modulus.py python -m generator.train --add-source /path/to/new-song.xml --add-solo /path/to/new-song-solo1.xml --slot 1
./modulus.py python -m generator.train --add-source data/songs/new-song.xml --add-solo /path/to/new-song-solo2.xml --slot 2
Files are copied into data/songs and data/training without overwriting different contents. Validation checks the exact measure/harmony correspondence, contextual harmonic validity, guitar-written convention and fret range before publishing the manifest. An existing source without a registered slot uses model prediction for that slot. Use --pitch-convention encoded only for files whose own transposition describes sounding pitch. Updating existing examples means editing their XML/manifest deliberately and rerunning training. The report under build/training/report.json records automatically recognized harmonic treatments, teacher-forced likelihood and optional leave-one-song-out likelihood; none is a listening assessment or unassisted-generation accuracy.
Production input traces distinguish authored reuse from learned prediction. Caches include source, role, relevant data and code; authored cache keys include the selected XML's content and convention. Build and package inputs include the model, manifest and authored repertoire. Changes to another solo do not change an already registered part's notes. Updating the pooled model can legitimately change predictions for any unregistered part. CMake defaults to two composition processes (VAMP_COMPOSITION_JOBS, 1–16) and caches each role under build/build_common/composition_cache. Direct generator runs use --composition-jobs and opt into persistent caching with --composition-cache; single-role builds use --cache-dir.
The generated solo_xml/*-solo1.xml and *-solo2.xml exports use local measure numbers, preserve pickups, and store whole-arrangement positions in manifest.solo. Exact reuse means complete sounding pitches, onsets, releases, repeated attacks and silence agree. XML whitespace, note splitting, measure labels and exporter-specific annotation containers need not be identical. Optional ./modulus.py python -m tests.solo_compare compares these completed exports; it never feeds a production decision. Its --generated-dir is the directory containing manifest.solo, and --reference/--form-start use the manifest's SONG/ROLE keys. --require-exact fails if any requested reference differs in complete sounding events or rests. Training and authored reuse are the explicitly permitted dependencies on the supplied music.
Migration inventory:
| Kept or added | Consumer and reason |
|---|---|
data/songs/ (two songs) |
Normal source importer and immutable arrangement analysis. |
data/training/ (four XMLs + manifest) |
Explicit trainer and registered authored playback. All slots are pooled for learning. Annotation-only cleanup preserves the music and learned counts. |
data/models/solo.json |
Frozen prediction model; reproducible by generator.train. |
data/composition/{roles,harmony,lead_ins}.json |
Physical/difficulty settings and generic harmonic validity. Fixed-map permissions removed. |
data/composition/accompaniment.json |
Version 2: independently authored duration families, per-layer ranges and bounded retrieval settings, consumed by comping_rhythm.py. Replaces the old per-chord figure list; no reference-derived material. |
generator/harmonic_time.py |
Exact chord/key spans retained from the removed scale subsystem. |
| Solo event, expression, placement, notation and export helpers | Retained physical/timing safeguards shared by authored and predicted music. |
Removed: data/{scales,sequences,rhythms}, old composition/solo.json and mixed rhythms.json, catalog import/cache code, old solo construction/phrase/selection/scoring/sequence modules, unused accompaniment catalog planner, three evaluation-song excerpts, and tests specific to those retired algorithms. Shared importer, timing, guitar, playback, engraving, arrangement and independence tests remain, with new pooled-learning, registration and exact-repertoire tests. No docs/ or audition/ directory is required. Temporary comparisons and diagnostics belong under /tmp/; old comparison pages, diagnostic reports, formatter-debug scripts and the copied verification bundle have been removed from build/. Product builds, caches and toolchains remain. The installed build/sonus_arm dependency remains because the configured ARM build uses it; its unused separate compilation directory has been removed.
Verification on October 5, 2026: unittest discover -s tests -p 'test_*.py' passed 361 tests in 78.62 seconds. These include all four approved solos without annotations or authored-role privileges, source-form pickup context, invalid resolutions, passing endpoints, anticipated colors, tied-event partition invariance and active prediction construction. The normal six-part asset build passed --check, including playback, engraving, TAB and catalog validation. With two composition workers, newly generated solo results, cached accompaniment/melody and a warm engraving cache, it took 5.10 seconds; performance-part generation occupied 2.37 seconds. Each registered solo took 0.36–0.40 seconds. The subsequent cmake --build build/build_x86_debug --target vamp_generated_assets also passed. These measurements describe registered playback, not novel prediction; the disposable diagnostic reports have since been removed.
Voicing-only verification on October 6, 2026, before the rhythm update: the suite passed 363 tests in 120.66 seconds, including 11 new voicing tests. Exhaustive small cases verify that the movement selector beats greedy selection and includes repeat/neighbor boundaries. That change retained the prior accompaniment onset/duration/rest sequences; source melody, vocal and both solo role documents were unchanged. Its six-score production rebuild passed --check in 11.54 seconds with two composition and two engraving workers: 2.95 seconds generating parts and 8.19 seconds engraving, with two unchanged score cache hits. This checks timing, harmonic/physical constraints and rendering, not a claim of ergonomic comfort from listening.
Rhythm verification on October 6, 2026: unittest discover -s tests -p 'test_*.py' passed all 375 tests in 83.57 seconds. Coverage includes attack ranges, bass downbeats, mixed durations, harmonic-change budgets, overlapping voices, tied joins, 3/4 and 5/4, melody-conditioned rhythm costs, tempo invariance, canonical reuse, and serial/parallel role identity. A synthetic learned-model test also confirms that changing original melody rhythm or upcoming harmony changes solo duration probabilities with a fixed seed. Both songs' solo, electric melody and vocal role documents remain exactly equal to the saved pre-rhythm baseline; no model retraining was performed.
The ordinary six-score rebuild with --check --export-solo-xml --composition-jobs 2 --jobs 2 passed in 13.39 seconds: 4.44 seconds generating parts and 8.48 seconds engraving. All role composition results were newly generated; the warm engraving namespace reused two unchanged vocal scores. The subsequent cmake --build build/build_x86_debug --target vamp_generated_assets passed with all role/score caches reused. Actual selected acoustic upper rhythms use one or two attacks in these songs; three-attack alternatives are available and tested but are not forced. All of Me electric has three zero-attack source/harmony contexts; Autumn Leaves electric uses one or two attacks. Neither song required a density exception. Reuse audits checked 103/62 acoustic/electric repeated bars for Autumn Leaves and 102/62 for All of Me, with only source-defined pickups and final-harmony exceptions. Sample acoustic notation/TAB pages were visually inspected. Supplied audio recordings remain unchanged; no ear-based audition of the new accompaniment was performed. Disposable logs, profiles and comparisons are under /tmp/, outside product assets.
The optional exact comparison confirmed all four exports: 137/141 attacks for All of Me and 121/115 for Autumn Leaves, with no pitch, onset, release or rest differences. All four now pass contextual harmony validation as well. Removing 69 explanatory annotations left the complete event streams and learned musical count tables identical to the pre-edit baseline; only model provenance hashes changed. This verifies authored reuse rather than learned reproduction. Commands used the ordinary asset generator with --export-solo-xml --check and python -m tests.solo_compare with four explicit --reference pairs, --reference-pitch guitar-written and --require-exact. No ear-based audition or claim of transfer quality was made.
Forced prediction, without reading the training XMLs, completed all four source/role combinations with valid harmony. These were composition-only runs on the training-source songs, not held-out transfer tests. Their notes intentionally differ from registered playback and were not published as replacements. The expanded treatment candidates initially took 99.03–120.33 seconds per role with one worker and no concrete-result cache. Removing repeated validation of identical resolution endpoints reduced the isolated All of Me acoustic check from 104.30 to 60.13 seconds, with an identical complete output hash. Six additional contexts retained all 32,566 ordered action candidates exactly. These expanded prediction choices remain slower than the earlier 21.81–27.16-second decoder; registered playback is unaffected. Training the pooled 2,860-context model took 0.57 seconds. Prediction remains the slower path, chiefly candidate construction and event-likelihood evaluation; further profiling is needed before expanding its choices again.
Engraving Boundary
generator/engraving/ is a custom Python engraver, not a wrapper around MuseScore or another notation engine. generator.engraving.layout.layout_score(document, style, fonts) produces immutable layout pages with exact source intervals, notation anchors and drawing primitives. generator.engraving.raster.rasterize_page(page, fonts) draws one page at four-times resolution and downsamples it. generator.engraving.pipeline.engrave_tree(root, fonts) is the asset-pipeline entry point. fonts.Fonts, types.Style, and types.EngravingError live in their respective engraving modules; page constants and check_page live in validation. Package initializers do not re-export these APIs. generator/musicxml_writer.py remains a round-trip/export utility and is not involved in engraving.
Engraving supports a bounded process pool (VAMP_ENGRAVING_JOBS, default 2) and a build-only content-addressed page cache (VAMP_ENGRAVING_CACHE_DIR, default build/build_common/engraving_cache). Keys include the complete musical document, engraving implementation, fonts/metadata, style, Python and rendering dependency versions. Page timing is included in cached manifests and checked against exact measure boundaries; corrupt coverage is rebuilt. Recording files are copied independently of the page cache. Cache hits are copied into the current staged output and checked for checksums, PNG decoding, dimensions, blank pages and clipped ink. Missing or corrupt entries are regenerated. All semantic and asset validation, database construction and transactional publication remain in place. The cache is not installed and can be deleted at any time when builds are idle. CI saves it after the native build and reuses it for ARM packaging; generation prints stage timing and per-worker progress instead of remaining silent during engraving.
Reuse has two levels. A full-score hit restores complete page images for the same musical document and metadata. A notation-body group shares system layout and body pixels among documents with identical staff content, while retaining each document's title, subtitle, pagination, footer, and complete semantic bundle. Body keys include the rendering context and actual page-band geometry; incompatible placement cannot reuse those pixels. Each header/footer is composed separately, and pages whose drawing primitives cannot be separated safely use full-page rasterization. This reuse never merges score IDs, catalog records, or authored music.
notation.py interprets staff state, exact pitch positions and shared accidental state; notes.py and attributes.py construct symbols; spans.py handles beams, ties, tuplets, octave shifts and lyric continuations; layout.py handles spacing and pagination. fonts.py reads bundled OTF outlines and SMuFL metadata, caching metrics and outlines for one generation run. raster.py uses FontTools, aggdraw and Pillow only as low-level font and drawing libraries. Modulus owns the pinned optional assets dependency group. Configure prepares its build-only environment; CMake and ./modulus.py python use the exported runner to import shared build utilities without shadowing the Modulus package. Standard glyph names and their upstream notice are vendored alongside the fonts; no system music fonts are consulted.
Voices retain separate stems and collision offsets; chord tones retain their own written durations and per-tone ties. Pitched and percussion material use their own staff positions. Unknown exact microtonal alterations use a rational semitone annotation rather than a rounded accidental; an unavailable explicit SMuFL glyph is an error. The broader model/engraver supports this notation, while automatic guitar generation enforces the source contracts above.
Generated guitar tones carry tablature: {string, fret}; their staff is marked tablature: true. engraving/tablature.py draws a six-line TAB view below standard notation, sharing onset columns, barlines, and page breaks. Both views belong to one system and never duplicate the semantic timeline or playback events. Standard notation carries rhythm, rests, and tuplets; TAB carries positions, solo slashes, and tied continuations without rest glyphs. Strings and frets are validated against sounding pitch, and the layout audit checks each visible TAB numeral's position and alignment.
Every part uses two music systems per page; only an odd final system occupies a row alone. A compact title/composer/part header reserves the same height on every page, wrapping credits if necessary. Pagination fits the two systems using their individual heights and distributes extra whitespace around them while retaining minimum system and header/footer clearances. One uniform scale applies throughout the entire part, reducing the ordinary notation size only when required by the tallest pair. Notation, TAB, text, and anchors scale together without changing musical content. Guitar layout starts with 10-pixel notation spacing, 10.5-pixel TAB spacing, 12-pixel fret labels, and a 14-pixel minimum gap between systems. The unscaled TAB spacing accommodates Petaluma numerals up to 9.75 pixels tall. Tempo markings consume neither ink nor spacing; their semantic data is preserved. Existing .pages/*.png preview files are the exact packaged page textures.
Line breaks occur only at measure boundaries. Each line fits as many complete measures as readable spacing allows; it never fills remaining space with a partial measure. A complete measure exceeding the available width or height fails with source and measure context instead of being split or squeezed to fit. Authored ties, beams, tuplets, and lyric spans can continue across whole-measure line breaks. Column measurement and fit trials reuse a score index and bounded column cache. Page placement then re-spaces columns for the final height-fitting scale, keeping every notation/TAB staff at the same physical left and right margins without stretching glyphs. Non-final lines are justified; a short final line retains natural note spacing while staff lines extend to the same right margin. Layout audits enforce these margins as well as musical coverage.
audit.py checks the final layout against the musical model: timeline and fragment coverage, exact fragment durations, actual glyph bounds, pitch positions, visible note primitives, required accidentals, ties, lyrics, harmonies and spans. It enforces consistent notation size throughout a part and header/footer clearance, and rejects out-of-page geometry and collisions among unrelated noteheads, accidentals and same-lane text. Staff, ledger, stem, beam and tie contacts are intentional. This is not a proof of publication-quality engraving for every possible MusicXML document; notation changes need targeted semantic, geometry and reviewed image tests. The writer's XML round-trip audit is retained separately in the test suite.
Bundles preserve the complete music object. Presentation identifies the Vamp Python engraver; its style fingerprint includes the style settings, both font files, font-specific metadata and the glyph-name mapping. Scores, performance metadata, and the catalog have no numbered schema labels. Required fields, types, relationships, dimensions, and checksums determine whether generated data is usable. Incompatible data is rejected, never migrated or guessed.
Pages are 976x484 pixels with nominal 10-pixel guitar or 12-pixel vocal staff spacing, 28-pixel horizontal margins and 18-pixel vertical margins. The two acoustic rest lanes sit closer to the staff, with collision offsets preserving their separation. Systems and pages contain complete measures; tied notes and notation spans may continue across their boundaries. Presentation fragments preserve exact timing and never change the semantic JSON. A measure that cannot fit at readable spacing fails with source and measure context; the uniform part-wide scaling described above fits pairs of complete systems vertically. Blank pages, incorrect dimensions, and ink reaching the page edge are rejected. Runtime letterboxing preserves aspect ratio. Luma's load_texture_uncached() lets Product Vamp retain at most two textures without a hidden strong asset-cache reference. The UI reports missing/corrupt assets instead of falling back to the removed renderer. Runtime validates bundle metadata, paths, dimensions, and page checksums; the build validates musical semantics.
Validation And Publication
Generation validates sources and complete staged output before replacing the previous asset directory. Replacement is always complete; deleted sources cannot leave stale JSON, pages, audio, or Pico title assets. CMake tracks the input list as well as file contents, so additions and removals regenerate the catalog. Runtime packages contain catalog.db with embedded semantic JSON and title-image identifiers, PNG pages, supplied WAV recordings, Pico title images, and the OTF UI text font, not MusicXML, MuseScore, or Python dependencies. Python test discovery runs model/import/export tests; the normal asset build additionally qualifies and audits every catalog score.
The page cache uses <renderer fingerprint>/fullscore/<score fingerprint>/ and <renderer fingerprint>/notation/<band fingerprint>/ directories, without a manual cache revision. ./modulus.py python -m generator --renderer-fingerprint reports the same identity used by rendering and CI: source bytes, fonts, visual style, Python implementation/version, and rendering-library versions. Python compiler/build timestamps are excluded. CI keys append generator code, the palette, source songs, composition policies, the frozen model, the training manifest, and authored solo XMLs. Earlier snapshots are restored only within the same renderer directory; only that directory is saved after a successful build.
cold namespace means that renderer's persistent directory does not yet exist. A renderer or environment change starts a new namespace; deleting a namespace has the same effect. It does not disable reuse within the current run: shared notation is still laid out once per group and its body pixels can be reused as other titles are composed, even without a persistent cache. Conversely, a warm directory does not guarantee that a particular score is cached. Progress reports full-score hits, body hits, newly rasterized body bands, composed pages, missing/rejected entries, and phase timings. Worker phase timings are summed and may exceed wall time. Obsolete namespaces are not migrated and can be removed when builds are idle.
Diagnostics identify the file, part, measure, and unsupported element or invalid relationship. Examples include part P1, measure 4: unsupported musical element <slur>, tie stop has no contiguous matching start, and optional-chord marker must identify exactly one harmony.
The six compact source files were replaced, not retained as a second authoring format. Installation replaces the entire catalog, rather than retaining or migrating rows from older releases. Legacy data must be authored as supported MusicXML and regenerated; the runtime never upgrades old databases.
References: MusicXML durations, tuplets, degree alterations, and SMuFL accidentals.
Generator checks compare the generated performance summary against tests/fixtures/performance_summary.json. The summary stores compatibility invariants such as section layout, chord-label placement, absence of uncomposed solo slashes, and vocal timing without committing the generated score files themselves. Refresh that file only when an intentional arrangement-logic change should become the new baseline.
Pico Runtime
The Pico firmware uses the Pico SDK C++ stack and the pico2_w board definition. It initializes the ST7789T3 display over SPI, reads the CST816D touch controller over I2C, joins the Zero AP, polls /api/remote/display, and sends HTTP POST commands for gestures.
The display is rotated 90 degrees clockwise into a 320x240 landscape UI. A small connection indicator in the header uses the shared palette:
- Amber (
retrying): connecting, offline, or a remote request failed. - Green (
connected): connected to the Zero API. - Red (
danger): Wi-Fi initialization failed.
The body shows the focused menu item, song title, or setting and its current value. In score view it shows the active title, part, and page count. A separate status label displays connecting, offline, error, or busy when applicable. Both devices and score pages use the dark palette in generator/palette.json. Touch controls are tap for next, double tap for previous, and tap-and-hold for select. Holding Tempo enters or leaves editing; taps then increase BPM and double taps decrease it. All visible text uses Petaluma Script, matching score-page text and the Zero UI.
generator.pico_artwork shapes text on the build host with the bundled PetalumaScript.otf and Pillow's Raqm support. Fixed menu, part, connection/status, and settings labels, digits, and / become four-bit coverage sprites. The Pico CMake rule writes generated/pico/static.p4 and embeds it through generated/pico/artwork.S. The Zero does not link this sprite atlas; Luma renders its dynamic UI text from the packaged OTF.
Score generation renders titles as 296x64 four-bit coverage images under pico/titles/<sha256>.mask. Each file is 9,480 bytes: an eight-byte VPTI header with little-endian width/height, followed by packed coverage. Identical image bytes share an asset without merging score identities. The generated presentation.title_image identifier is also stored in catalog metadata; no separate lookup manifest is required. These assets are generated and validated before staged output is published. Remote status includes title_image=<sha256>; POST /api/remote/title with {"id":"<sha256>"} serves validated bytes only for catalog-registered titles. Pico fetches a changed title, validates its fixed format and bounds, rejects stale responses, and retains the selected image. Part labels and page counts use fixed sprites without reshaping or refetching title text. Commands take priority over image requests, and failed title requests display UNAVAILABLE until a retry succeeds.
Pico performs coverage blending and sprite placement only: no OTF parsing, font shaping, glyph rasterization, or general text renderer runs in firmware. Missing glyphs and invalid source text are build errors. Font or generator changes rebuild both static firmware sprites and title artwork. Direct generation uses ./modulus.py python -m generator.pico_artwork --static-output <path> for firmware sprites and ./modulus.py python -m generator.pico_artwork --assets <generated_assets> for title assets. Python, Pillow/Raqm, and FontTools remain build-only dependencies.
The root CMakeLists.txt is also the Pico firmware entrypoint when configured with -DVAMP_PICO_FIRMWARE_PROJECT=ON. That mode returns before the Zero/Linux targets are declared, so the Pico build can use the embedded ARM toolchain cleanly.
Build defaults can be overridden at package time with CMake cache variables:
VAMP_PICO_WIFI_SSIDVAMP_PICO_WIFI_PASSPHRASEVAMP_ZERO_HOSTVAMP_ZERO_PORT
Packaging
Modulus packages the CMake install stage. Product Vamp uses install_prefix . so the registry artifact is a top-level multi-device bundle rather than a root filesystem overlay.
The ARM64 package target builds:
zero/product_vamp_zero_linux_arm64.docker.tarpico/product_vamp_pico2w.uf2manifest.json
Building the full package requires Docker/buildx, the ARM embedded GCC toolchain, and the Pico SDK. CI passes PICO_SDK_PATH directly. The generated Modulus Docker image also carries the pinned Pico SDK at /opt/pico-sdk for local package builds. The Zero Docker runtime targets Debian trixie to match current Raspberry Pi OS and the ARM64 dependency binaries, and includes util-linux for the setsid --wait --ctty launch path.
Generated score assets are created during CMake build/package and copied into the Zero image context. Static font assets remain tracked under package/assets/fonts.
generator/catalog.py uses Python's standard-library SQLite module to construct catalog.db after musical, recording, and engraving validation. The database carries application ID 0x56414D50, complete score bundles, and menu metadata in a strict songs table with a songs_menu index; a strict resources table contains the supplied-audio manifest. Runtime validates the table's column names/types, non-null requirements, and primary key directly. Sorted insertion and the absence of wall-clock timestamps make repeated builds reproducible with the same toolchain. It is closed and integrity-checked before publication, without journal/WAL sidecars. Individual JSON files remain available in the build tree for inspection, but both CMake installation and Docker image assembly exclude them.
The default database.path is /usr/share/product_vamp/catalog.db; development configuration points at the native generated_assets database. Stored source paths are logical, asset-relative locations, not build-host filenames. Runtime resolves them against runtime.assets_path and rejects traversal and escaping symlinks. The JSON file itself need not exist: its parent identifies the score's page directory. Normal startup checks catalog compatibility; --once additionally runs SQLite's integrity probe and checks all page/audio files. Missing, corrupt, or incompatible catalogs fail rather than creating an empty library. --ensure-database is no longer a supported command.
Every run of zero/install_zero.sh is a clean installation. It stages configuration/scripts, verifies the manifest's Docker tag against the incoming archive's image digest, and probes that image before stopping Product Vamp. It then clears the owned /etc/product_vamp, /var/lib/product_vamp, /run/product_vamp, and obsolete host /usr/share/product_vamp trees, including old SQLite sidecars. The installed container uses assets from its image and no longer mounts a writable state database. Startup reads the verified image ID from /etc/product_vamp/image_id, rather than depending on a mutable Docker tag. The installer also removes the fixed legacy hyphenated Product Vamp paths, units, containers, and network profile after preflight, while rejecting symlinked destinations. It replaces its scripts and systemd units, removes their old overrides, recreates only the product_vamp_control NetworkManager profile, and requires a successful service health check before announcing success.
Configuration, AP credentials, and all prior catalog data are intentionally reset to bundle defaults. There is no automatic backup or migration. Installation through the control AP can disconnect the session; use the local console or independent networking. Preflight failure leaves existing application files untouched and restores the previous image tag; replacement failure stops Product Vamp and reports the error. Rerunning the installer is the recovery path. Ordinary boots/restarts do not run cleanup. Unrelated networks, OS data, containers, and system logs remain untouched. Cleanup rejects symlink/mount targets, never follows paths from old configuration, and removes only identified obsolete Product Vamp images not used by another container.
CI builds stable target-specific archives with python3 modulus.py package, then python3 modulus.py publish uses Modulus's artifact publisher to upload exactly the configured targets and their checksums. Gitea requires a registry slot, so publishing replaces latest and removes obsolete remote entries. There is no application release number, artifact version argument, or versioned install directory. The manifest, runtime heartbeat file, and --build-info retain the Git commit for diagnostics. The health endpoint reports readiness; remote display responses contain device navigation state. Conan references, compiler/dependency pins, and MusicXML protocol identifiers remain external tool requirements.
Shared Protocol
The Pico firmware does not link Zero-side Linux application libraries such as Ligo, Luma, or Noto. Those remain Zero-side dependencies: Ligo serves the HTTP API, Luma renders the Zero display, and Noto owns SQLite persistence. Cross-device code should stay in vamp_protocol and vamp_style: plain C++17 with no OS, allocator-heavy, or Zero-only dependencies.
Luma owns the application shell, UI control tree, texture loading, and UI-thread texture lifetime. Product Vamp's page cache uses the uncached loading API; there is no score-specific Raylib font cache or handwritten notation renderer.
LSP
tooling.editor.clangd in modulus.json generates the ignored .clangd, pointing native Zero and shared protocol code at build/build_x86_debug, and Pico code at build/build_pico_debug. Refresh both compile databases with:
python3 modulus.py lsp
The tooling.lsp section in modulus.json owns the Pico CMake configure. It unsets host compiler variables before configuring the Pico SDK project so clangd sees the embedded ARM toolchain. When running directly outside the generated Modulus Docker image, set PICO_SDK_PATH before refreshing the Pico database.