Files
product-vamp/TECHNICAL_GUIDE.md
T
khodowany a0e13434e4
ci / checks (push) Successful in 51m14s
Rhythm Patterns
2026-10-07 00:22:19 -05:00

82 KiB
Raw Blame History

Product Vamp Technical Guide

Repository Shape

Product Vamp follows the Modulus product layout used by the khodo repos:

  • include/vamp_zero and src/vamp_zero: Pi Zero C++23 application.
  • include/vamp_pico and src/vamp_pico: Pi Pico 2 W C++17 firmware.
  • include/vamp_protocol and src/vamp_protocol: shared route and page-command contracts used by both devices.
  • cmake/pico.cmake and cmake/zero.cmake: device-specific build definitions selected by the root CMakeLists.txt.
  • include/vamp_style.hpp and src/vamp_style.cpp: shared visual palette. include/vamp_pico/artwork.hpp defines 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 under tests/.
  • 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/health
  • GET /api/remote/display
  • POST /api/remote/title (registered title image ID)
  • POST /api/remote/heartbeat
  • POST /api/remote/next
  • POST /api/remote/previous
  • POST /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), followed by three held ending measures, 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 171 arrangement bars: the eight-bar intro, five 32-bar passes, and three written held-ending measures.

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 the title_image content identifier for Pico artwork.
  • performance: normalized source song ID, exact score duration, count-in length and metronome beat/accent positions. Each presentation page additionally stores first_measure, measure_count, start_beat and end_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. chord_families.py classifies actual shapes as drop-two, close-position, compact-open, compact-doubled or three-note shells, ranked by versioned policy. Melody adaptation also permits strings 2–4 when the exact source pitch cannot sit above the chord on string 1. Within the selected span tier, its lowest feasible string number and foreground position precede chord size. A primary grip with the melody on string 2 beats a four-fret grip with it on string 1. Short monophonic melody notes retain their movement-based placement; Electric Solo is unchanged. These electric roles use 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.

Acoustic families are transposable degree orders for triads, tetrads and extended chords, with root/fifth alternatives on both bass strings. Slots resolve to the written third/suspension, sixth/seventh, fifth and extension, including alterations and omissions. They are not per-song grip tables. Only harmonically and physically valid realizations enter selection; unavailable bass/inversion variants are discarded. Coverage checks exercise 35 common qualities in all 12 keys. Four-fret fallbacks support previously unavailable acoustic Bb9, BbM9, Bb7#9 and Bb6add9; for example, Bb9 uses fifth-bass frets 8/10/7/9 on strings 5/4/3/2. Acoustic Bbm9 and Bm9 still have no grip meeting all constraints. Unsupported chords fail with source measure, beat and chord rather than silently omitting defining tones.

The active voicing selector compares complete paths with dynamic programming over concrete grips. The leading cost minimizes four-fret grip selections: every span of one, two or three positions has zero cost in this tier. Existing criteria then apply within the tier. Electric accompaniment prioritizes chord size and shared family rank, then combined grip effort, hand-center travel, register and position costs, followed by missing optional tones and register tie-breaking. Acoustic prefers declared degree-order families on either bass string, then a combined travel/register/position cost, optional-tone coverage and register tie-breaking. Stable role-specific keys resolve equal costs. Since accompaniment transitions have travel costs but no hard travel cutoff, four-fret candidates are safely removed from a span when any primary grip exists; this exact dominance reduction keeps the repeat search small. Electric placement retains distinct alternatives where future held/tied notes can constrain feasibility, and does not charge tied continuations as fresh grip choices. The remaining acoustic placement helper uses the same leading fallback tier.

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 family, bass/string family availability, selected positions, bass degree and movement, plus fret_span, span_group and span_candidates (primary/fallback counts before dominance reduction). These are diagnostic sidecar fields; the score/runtime format is unchanged.

comping_rhythm.py selects recurring source-only figures from version 6 of composition/accompaniment.json. The normal acoustic cycle has two two-bar slots; electric has four. Each template and anticipation mode is evaluated at every occurrence of its slot before retrieval limits apply. Up to eight diverse candidates per slot enter bounded complete-cycle comparison. Distinct slots differ in attack/duration relationships, not just IDs, bass/upper assignments or anticipation flags. Repetition is not penalized. The source melody and harmonic rhythm affect selection; title, iteration, generated solo and tempo do not. groove_cycles reports slot identities, anticipation modes, aggregate cost, compared cycles and any reduction caused by vocabulary or form length.

Meter-specific full patterns declare meter and, for 5/4, grouping: [3, 2] or [2, 3]; meter: partial marks the bounded one-bar pickup vocabulary. The loader checks nominal lengths, grouping, quarter/eighth arithmetic, lane overlap and distinct acoustic/electric onset patterns. Each role has 24 distinct attack/duration figures per selectable pool: 4/4 (11 families), 3/4 (12 families), and each 5/4 grouping (12 families). The 4/4 pool removes one duplicate acoustic figure, develops another previously duplicated answer, and adds three electric variants. Patterns are independently authored from general principles, with research URLs and provenance in the resource, rather than transcriptions. The counts describe available choices, not required musical activity.

grouping_for is a pure source-only decision. It honors written 3+2 or 2+3, otherwise compares actual chord changes at the secondary boundary, then melody entrances following at least an eighth-note rest, then other melody attacks; equal evidence defaults to 3+2. An unchanged chord marking is not a change. The selection stays fixed through a continuous meter section, and each role derives it independently from the same source. Fills cannot borrow another grouping. No accents or playback swing changes are inferred. groove_cycles additionally reports grouping, its evidence and the eligible pattern count. Equal-duration compound meters (such as 6/8) do not borrow the 3/4 vocabulary; unsupported comping meters fail explicitly.

Accompaniment dependency keys and choice streams include only full-pattern meters and partial lengths present in that source, plus the applicable instrument policy. Adding unused-meter patterns therefore does not reseed an existing song; unrelated role policies remain excluded. Research prose is excluded from musical seeds. This schema migration changes the accompaniment choice-key domain once; it does not promise identical previous 4/4 tie-break winners. Source melodies, registered solos, notation format and playback count-in are unchanged.

The cycle begins at the first full source-body measure. A partial-form entry first fits the incoming harmony, then slices the sounded figure instead of restarting it; this prevents a previously clipped chord hold from reappearing as a new attack. An odd final bar uses the appropriate half-pattern. Meter changes restart a compatible cycle. Unsupported durations fail explicitly, and limited vocabularies report a shorter cycle. Optional final-harmony changes rebind the same selected pattern. Canonical grips are fixed wherever the chord and interval start remain unchanged, so a new turnaround cannot move neighboring unaffected voicings.

Fills replace only a two-bar figure's ending bar, using a same-family variation that improves the existing source-fit objective. Eligible boundaries are source section endings, form endings, or a melody breath of at least one quarter-note beat at the block end. Carried events from the opening bar are protected. The largest improvements are retained with an eight-measure minimum separation around the whole cyclic form, equally for both roles; no fill quota applies. Fills do not change cycle phase and recur at identical source positions. Reports record the base slot, selected fill, source index and reason.

Normal acoustic patterns use 1–3 attack positions per full bar. Separate bass and upper events last quarters/eighths and may overlap; paired attacks can be short or sustained. Events cannot overlap within the same lane. First attacks of new harmonic spans include bass (first_attack: bass_per_harmony), including mid-bar changes; continued harmony does not reset at barlines or computational boundaries. Fitting can pair bass with an upper attack without changing its onset, with independent releases where necessary. A paired attack counts once in rhythm evaluation. The completed validator follows sounding tie chains; placement validates all simultaneously sounding strings, fret spans and anchors. The two lanes are merged into one notated part with tied fragments preserving their true durations.

Electric normally uses 0–2 attack positions per bar, with stabs, sustains and space. Direct events release at changed harmony. An eligible upper/electric template can anticipate a chord by half a beat when it continues into that chord; the grip comes from the upcoming harmony and releases by its following change. Acoustic anticipations also include bass on the new chord's first attack. Conflicting lane overlaps reject the candidate. Missing harmony remains silent. Protected participation limits retain the chosen figure, shortening only its affected releases and recording exceptions. Cross-bar sounding notes become ties, never automatic merging of repeated attacks.

All six production roles receive reuse audits. Musical comparisons include rests, pitch durations, ties and TAB while ignoring regenerated event IDs and global measure numbers. Source-defined pickup/participation changes and effective final-turnaround harmony are reported separately. The final three-bar hold remains instrumental.

Named activity, melody-support, harmonic-support and articulation contributions evaluate complete figures. Cycle selection aggregates them over all occurrences, retaining family diversity before the final bounded comparison. groove_cycles.cost is the selected base cycle's aggregate cost before optional fills. Reports identify the base slot, realized pattern, projected events, continuations, anticipation target times and source-context exceptions. Tempo affects metadata/cache identity but never musical choice.

Acoustic events use temporary bass/upper notation lanes for projection; then acoustic.combine converts their sounding intervals to one rhythmic voice before assembly. It splits notes at attack/release boundaries and ties only continuing pitches; repeated attacks stay separate. Staff and TAB share the result, with no competing rests or opposing layer stems. This preserves exact sounding durations instead of truncating overlaps. The older truncating prepare helper serves only the noncanonical legacy/test path. 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 pattern IDs, exact projected layer events, continuations, anticipation targets and exceptions. Their selection_cost records the complete figure’s local contributions, repeated on each projected bar for context; the cycle selector aggregates these by the number of measures in each occurrence, without an anti-repetition transition cost. Accompaniment dependency keys contain only that instrument's policy; electric melody consumes the shared electric voicing families. 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, a sounding anchor on frets 1–12, primary spans of up to three consecutive positions and fallback spans of exactly four. The span is inclusive: 7–9 is three positions, 7–10 is four. Five positions are always invalid. The anchor must remain sounding, including after shorter notes release. 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.

Both accompaniment roles add register_weight * abs(mean sounding MIDI - center) and high_position_weight * max(0, mean fret - comfortable_max_fret) to their existing grip effort and movement tier, once per active harmonic span. The three role policies (acoustic_accompaniment, electric_accompaniment, electric_melody) use weights 1 and 1 and threshold 9; their register centers are 53, 60 and 60 respectively. All arithmetic is rational. Electric melody adds the same costs only to harmonized attacks, below compact-span, melody-string, foreground, chord-size and family priorities. Exact melody pitches are never transposed to meet the register center; monophonic placement is unchanged. Scoring and scored-pruning caches explicitly include the normalized preference tuple. Tied continuations incur no second chord cost. Existing travel costs and source-form joins remain in place; high positions can still win when avoiding a costly shift. The ending uses its own accompaniment role's local cost and incoming grip.

Each selected accompaniment span reports voicing_cost contributions for movement, base grip effort, weighted register, weighted position and their combined effort. These describe the balanced tier, not the higher span/family priorities; repeat-join travel is assigned to the first canonical span. Both accompaniment roles keep voicing settings in concrete cache dependencies but exclude them from rhythmic choice seeds, including settings overrides. Acoustic rhythm selection can change under its new bass-first policy, but changing voicing weights alone never reshuffles rhythm. Each role still composes independently.

Recurring-groove verification on October 6, 2026: the full Python regression command passed 423 tests in 149.85 seconds. After the final partial-entry grip fix and one additional real-evaluator fill test, the focused accompaniment/arrangement command passed 59 tests in 21.64 seconds. The native test target passed in 30.58 seconds. The initial native rebuild took 42 seconds, including 7 seconds for independently generated roles and 33.62 seconds of engraving; accompaniment roles took 0.8–1.6 seconds each. The following ARM build reused all role/score caches and took 11 seconds. All four solo documents, both electric melodies and both vocals exactly matched the prechange baseline; native/ARM score assets matched byte for byte. The chosen base cycles differed by song and retained all two/four requested slots. Autumn Leaves selected the authored overlapping-quarter acoustic pattern. No optional fill improved these production winners, so no fills were forced; independently authored tests verify real source-fit selection of a fill. An acoustic score/TAB page was visually checked for overlap ties in a single part. Recordings were unchanged and no listening assessment was performed. Baseline snapshots stayed under /tmp/.

Shared-preference and bass-first verification on October 6, 2026: python3 modulus.py python -m unittest discover -s tests -p 'test_*.py' passed 408 tests in 61.35 seconds, and the native test target passed all 124 cases. Both product builds passed asset checks; 139 native/ARM score and page files matched byte for byte. All 273 acoustic measures containing new attacks started with bass, including 69 delayed entrances. Tied carries did not trigger new attacks. Both solos, vocals, electric accompaniment rhythms, and electric foreground melody pitches/timing/TAB matched the saved baseline exactly. Some All of Me melody support chords changed; Autumn Leaves' complete electric melody document did not. All of Me's canonical electric accompaniment mean fret dropped from 7.26 to 4.13 and movement cost from 57 to 48; Autumn Leaves stayed at 2.95 and 46. Recordings were unchanged. These are score checks and page inspections, not a listening assessment.

With no concurrent build, isolated cold role generation measured 0.56–0.57 seconds for acoustic accompaniment, 0.35–0.37 for electric accompaniment, and 0.26–0.29 for electric melody; cache hits took 0.02–0.04 seconds and matched clean regeneration for both songs. The normal two-worker native asset stage took about 14 seconds (4 composing, 9 engraving, 1 other); ARM reused the caches in about 4 seconds. Application compilation is separate from these asset-stage timings.

Lower-position verification on October 6, 2026: all 400 Python tests and 124 native test cases passed. Native and ARM builds passed asset checks; all 139 score/page files matched byte for byte. Against the saved production output, acoustic rhythms and all other five role documents were identical for both songs. Mean fret across canonical active acoustic grips changed from 8.90 to 4.50 in Autumn Leaves and 5.19 to 4.29 in All of Me; movement costs rose from 144 to 190 and 128 to 140 respectively. In the requested six-chord passage, five suggested grips won; Em7b5 instead chose its valid fifth-bass shape on strings 6/4/3/2 at frets 6/5/7/5. Tests also verify that the complete suggested sequence beats the previous higher sequence with fixed neighbors, and that sufficiently costly travel can still retain a higher grip. These are score/selection facts, not an ear-based audition.

Isolated uncached acoustic role generation took 0.61–0.62 seconds per song, versus 0.56–0.58 seconds before this change; cache hits took about 0.048 seconds and matched clean output. With two composition and engraving workers, the native asset rebuild took approximately 11 seconds (4 composing, 6 engraving, 1 other); ARM reused caches and took approximately 6 seconds. These are individual local measurements, not a statistical benchmark. Supplied recordings remain byte-identical.

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 main-form bars, three held bars and decay. arrange/ending.py now writes that existing tail as measures 169–171, without extending or transforming the recordings. Both accompaniment roles choose a valid closing voicing using their own outgoing position and hold it with ties; melody/solo roles are silent there and the vocal has three full rests. The chord is the final effective harmony after omitting optional turnarounds, including carried harmony. Missing/N.C. final harmony fails accompaniment generation explicitly. The original final bar is untouched.

Score timing includes sustain_start, an exact measure boundary, as well as the total written duration. Ending measures carry ending_sustain; Python and C++ validate a contiguous tail and consistent timing. Older scores without the new timing field retain their previous full-duration click behavior. Both toggles play the recorded count-in alone, then backing plus sample clicks until sustain_start. No clicks sound over the ending. Metronome-only supplies its own count-in and completes after the three silent held bars. Backing-only plays the complete file and decay. Both off enables manual navigation; hold exits and natural completion returns to the part menu. The final page remains visible through decay.

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 system and measure 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. presentation.pages partitions the music into normal two-system pages. presentation.playback_pages holds one view per system: that system and its successor, or a final single top system. Both reuse the same system breaks, horizontal geometry, and part-wide scale, fitted against every adjacent pair. Each descriptor carries system measure ranges, exact beat boundaries, and page-coordinate rectangles for its measure labels. Normal systems partition the score; overlapping playback views must match that canonical partition. Bundles missing playback metadata require regeneration.

Measure numbers are centered over their starting barlines (the staff’s left edge at system start), in a reserved lane above notation and below harmony/section labels. The display outlines the current label using the page texture’s letterbox transform; count-in, preparation, exit, and playback error clear it. Measure updates publish even when the line stays unchanged. Normal catalog page counts remain unchanged; the loader reads the separate playback count from bundle metadata. During automatic playback, navigation bounds, the header’s Line counter, and Pico’s page/pages fields refer to overlapping views. Manual browsing uses the normal sequence. 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 both normal and playback score pages without opening audio/display hardware.

Authored Repertoire and Learned Solos

The production decision is explicit:

  1. 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.
  2. 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.

Solos are published inside the arranged score JSON and engraved pages, without separate solo MusicXML exports or comparison manifests. Exact-repertoire tests compare generated sounding pitches, onsets, releases, repeated attacks, measure timing and silence directly with the authored XMLs. Tied notation fragments are merged into sounding events for this check, and generated TAB is checked against the guitar constraints. Training and authored reuse remain 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 3: independently authored per-instrument duration families, layer ranges, bounded retrieval and generic chord-degree/geometry families. Consumers: comping_rhythm.py, comping.py, chord_families.py and guitar.py. Migrates the v2 shared-duration schema; no parallel legacy data or 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. Desktop and ARM builds resolve Sonus through Conan; separate local Sonus installations and temporary CMake dependency overrides are unnecessary. Refresh both with python3 modulus.py configure --update --target x86_64_gcc12_debug --target arm_64_gcc12_release when dependency packages change. The obsolete playback-test assets are superseded by the current performance-test fixtures.

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 --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.

Chord-family and unified-acoustic verification on October 6, 2026: unittest discover -s tests -p 'test_*.py' passed 386 tests in 81.67 seconds. New cases cover 35 chord qualities in three keys for both instruments, discarded bass variants and explicit unsupported combinations, the six suggested acoustic grips, highest feasible electric melody strings, scoped policy dependencies, and one-voice tie preservation including repeated attacks and cross-bar ties. Quarter/eighth acoustic attack durations and available consecutive-eighth patterns are tested. The acoustic sounding events in both complete arrangements contain only quarter/eighth durations, even where notation splits them into ties.

The full checked rebuild with two composition and two engraving workers took 13.76 seconds: 4.68 seconds for all parts and 8.63 seconds engraving. All role caches missed after the code change; two unchanged vocal engravings were reused. Serial composition-only generation into a disposable directory also passed in 5.58 seconds. The subsequent CMake asset build passed and reproduced every role document from warm caches. Both registered solos and vocal documents are exactly unchanged from the saved prechange baseline; the electric melody's original pitches/onsets/durations also match. Reuse audits retain the same 103/62 and 102/62 repeated-bar counts. Acoustic upper rhythms now select one to three attacks in Autumn Leaves and one or two in All of Me. Acoustic and electric melody pages were visually inspected; this is notation/constraint verification, not an ear-based audition. Only product artifacts and normal caches were written under build/; diagnostic logs and baseline comparisons remain under /tmp/.

Four-fret fallback verification on October 6, 2026: unittest discover -s tests -p 'test_*.py' passed 392 tests in 85.65 seconds. This includes primary-tier precedence over family, arbitrary movement savings and melody-string height; a complete held-note path that needs an earlier four-fret option; tied-fragment cost invariance; and anchor release checks. The first full run exposed one obsolete C5 rejection expectation, now replaced by a test verifying its legal four-fret voicings contain only C/G. Coverage tests check 35 qualities in 12 keys for each instrument: 418/420 acoustic contexts and 420/420 electric contexts have a legal grip. Acoustic Bbm9/Bm9 remain explicitly unsupported; this is measured coverage, not a guarantee for every written extension or slash chord.

The checked six-score rebuild with --composition-jobs 2 --jobs 2 passed in 9.21 seconds (4.58 seconds composing all 12 roles, 3.90 seconds processing six cached engravings). All composition caches missed after the code change. Every resulting role document and accompaniment rhythm report exactly matches the saved baseline: current production chords already have primary grips, so none needed fallback substitutions. New sidecar span diagnostics identify the tiers and available candidates. The subsequent cmake --build build/build_x86_debug --target vamp_generated_assets passed with all role caches reused and identical per-role musical hashes. Acoustic and electric score/TAB pages were visually checked; supplied recordings are unchanged. Formatter and diff checks passed. Temporary verification files remain under /tmp/.

Earlier exact-repertoire verification confirmed all four authored solos: 137/141 attacks for All of Me and 121/115 for Autumn Leaves, with no pitch, onset, release or rest differences. All four also passed contextual harmony validation. 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. These checks now compare generated score data directly with the training inputs instead of producing standalone XML exports. 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.

Normal pages use two music systems; only an odd final system occupies a row alone. Playback pages use overlapping adjacent pairs and always finish with the last system alone on top. 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 adjacent pair, including transition pages. 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_SSID
  • VAMP_PICO_WIFI_PASSPHRASE
  • VAMP_ZERO_HOST
  • VAMP_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.tar
  • pico/product_vamp_pico2w.uf2
  • manifest.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.