Playback
ci / checks (push) Failing after 23m37s

This commit is contained in:
khodowany
2026-10-05 23:41:11 -05:00
parent ffdbe48258
commit 82349cf897
104 changed files with 4095 additions and 4354 deletions
+29 -22
View File
@@ -5,7 +5,7 @@ Product Vamp is a two-device sheet-music appliance.
- Raspberry Pi Zero 2 W hosts the control Wi-Fi AP, runs the display/control service in Docker, and drives a Waveshare Zero-DISP-7A 1024x600 touch display.
- Raspberry Pi Pico 2 W runs UF2 firmware for a Waveshare 2-inch capacitive touch LCD and sends menu/page commands over the Zero-hosted AP.
Python builds the complete SQLite catalog with the score assets. The Zero opens that packaged catalog read-only through Noto, without creating a database, migrating schemas, or importing JSON at startup. Runtime navigation and rendering use Ligo for the HTTP API, Luma for touch UI rendering, and Modulus for shared build/runtime utilities.
Python builds the complete SQLite catalog with the score assets. The Zero opens that packaged catalog read-only through Noto, without creating a database, migrating schemas, or importing JSON at startup. Runtime services use Ligo for the HTTP API, Luma for touch UI rendering, Sonus for audio playback, and Modulus for shared build/runtime utilities.
The catalog stores shared, immutable menu metadata. Complete score bundles stay in SQLite and are loaded individually when selected; snapshots and Pico heartbeats do not copy the catalog's musical JSON.
@@ -14,6 +14,7 @@ Device controls:
On the Zero, tap menu rows and switches directly; Tempo has minus/plus buttons. The main menu offers **Performance** and **Settings**.
On the Pico (and on the Zero while viewing a score):
- Tap: next menu item or next score page.
- Double tap: previous menu item or previous score page.
- Tap and hold: select the focused item, enter/leave tempo editing, or return from a score to its part menu.
@@ -34,6 +35,8 @@ Release verification:
```sh
python3 modulus.py configure --target x86_64_gcc12_release
python3 modulus.py build --target x86_64_gcc12_release
python3 modulus.py test --target x86_64_gcc12_release
python3 modulus.py python -m unittest discover -s tests -p 'test_*.py'
```
`python3 modulus.py run` uses `package/config/vamp.development.json` for a headless readiness check against native build assets. `vamp_zero --once` checks the packaged database and referenced page/audio assets, checks runtime writes and the server bind, then exits without opening audio or display hardware. Missing or incompatible catalogs require rebuilding or reinstalling; they are never repaired at runtime. `--check-config` only validates configuration; `--health-check` probes the running service's `/api/health` endpoint without modifying files.
@@ -60,7 +63,7 @@ Modulus also generates `.nvim.lua` for Neovim 0.11+ with `exrc` enabled. Review
## Package
Product packaging targets Raspberry Pi OS/Debian trixie on ARM64 and optionally builds Pico firmware. Set `PICO_SDK_PATH` when building the firmware artifact:
Product packaging targets Raspberry Pi OS/Debian trixie on ARM64. The configured package builds both the Zero Docker image and Pico firmware. The build container includes the Pico SDK; when building directly outside that container, set `PICO_SDK_PATH`:
```sh
PICO_SDK_PATH=/path/to/pico-sdk python3 modulus.py package --update
@@ -91,19 +94,21 @@ Artifacts are published to the khodo generic package registry's required `latest
## Score Assets
Uncompressed MusicXML 3.1/4.0 scores exported from Soundslice or MuseScore live in `data/`. Generated score JSON is not committed.
Uncompressed MusicXML 3.1/4.0 source scores exported from Soundslice or MuseScore live in `data/songs/`. Generated score JSON is not committed.
`data/songs` supplies the original melody and harmony. The 21 scale patterns, 2,954 relative interval paths, meter-specific rhythm catalogs, and general rules in `data/composition` supply musical vocabulary. Production never reads reference solos, fitted parameters, or previous music.
`data/songs/` supplies the original melody and harmony. `data/composition/` holds guitar/difficulty settings, sparse accompaniment figures, and harmonic-validation policies. `data/training/manifest.json` registers authored solos and their source/slot pairings; `data/models/solo.json` holds the frozen pooled prediction model. The former scale, interval, and rhythm catalogs and handwritten phrase composer have been removed.
Six roles generate independently: acoustic accompaniment/solo; electric accompaniment/melody/solo; vocal melody. A process pool and per-role caches support parallel and incremental builds. Canonical accompaniment repeats at corresponding source-form positions and does not react to generated solos. Original vocal and electric head content remain unchanged.
Six roles generate independently: acoustic accompaniment/solo; electric accompaniment/melody/solo; vocal melody. A process pool and per-role caches support parallel and incremental builds. Each role uses the source and its own inputs without reading another generated role. Canonical accompaniment repeats at corresponding source-form positions and does not react to generated solos. Vocal melody preserves the authored notes and lyrics. Electric melody preserves source pitches and harmonizes eligible sustained notes, reattacking them at chord changes.
The solo composer creates complete source-related melodic phrases before selection. A realized theme develops through returns, answers, register changes, arpeggio motion and complete scale routes. Phrase destinations, rhythm, rests and holds are coordinated. Both guitars use strings 1–6 and frets 1–12 without open strings. Colors, lead-ins, anticipations and suspensions need explicit harmonic evidence. No dynamics, accents or humanization are added.
**Registered Solo 1 is notated exactly in the acoustic part and Solo 2 exactly in the electric part.** Unregistered song/slot pairs use deterministic prediction from one pooled statistical model trained on both slots. There is no learned acoustic/electric style distinction. Prediction uses up to four recent pitch/rhythm events, source-melody features, harmony, and metrical position; it does not plan complete themes or guarantee long-range phrase development. The current corpus is four authored solos from Autumn Leaves and All of Me.
`--export-solo-xml` (CMake `VAMP_EXPORT_SOLO_XML=ON`) writes `generated_assets/solo_xml/*-solo1.xml` for acoustic and `*-solo2.xml` for electric, with actual notes and TAB. These exports do not recompose music. `python -m generator.compose --song ... --role acoustic_solo --data-dir data --output ... --solo-xml ...` builds one independent role.
Training, authored score reuse, and prediction share contextual harmony validation based on actual notes, timing, and chords. Explanatory labels and justification tags are not required. Unsupported relationships fail with a diagnostic rather than silently changing notes. Both solo roles use strings 1–6 and frets 1–12 without open strings. No dynamics, accents, velocity shaping, or random timing are added.
Optional comparison lives in `python -m tests.solo_compare`, outside production. Supply `--generated-dir`, explicit `--reference ROLE=PATH`, `--form-start ROLE=MEASURE`, `--reference-pitch guitar-written`, and `--output build/solo_comparison/review`; add `--assets` and `--baseline-dir` for consistent audio comparisons. References are analysis-only. The supplied targets sound one octave below encoded pitches; All of Me aligns at measure 1 and Autumn Leaves at measure 2, preserving the lead-in. Closeness is reported using actual notes and timing; matching statistics or technical validity do not establish musical preference.
`--export-solo-xml` (CMake `VAMP_EXPORT_SOLO_XML=ON`) writes `solo_xml/*-solo1.xml` for acoustic and `*-solo2.xml` for electric under the generated asset directory, with actual notes and TAB. These exports do not recompose music. `./modulus.py python -m generator.compose --song ... --role acoustic_solo --data-dir data --output ... --solo-xml ...` builds one independent role. To exercise prediction for a registered song, pass `--settings` with a JSON file containing `{"predict": true}`.
See [Independent Musical Generation](TECHNICAL_GUIDE.md#independent-musical-generation) for the active pipeline and limitations. Keep just one compact baseline and the latest comparison under `build/solo_comparison/`.
Optional comparison runs through `./modulus.py python -m tests.solo_compare`. Point `--generated-dir` at the exported `solo_xml/` directory and supply explicit `--reference SONG/ROLE=PATH`, `--form-start SONG/ROLE=MEASURE`, `--reference-pitch guitar-written`, and `--output build/solo_comparison/review`. Keys match `manifest.solo`, such as `all_of_me/acoustic`. Add `--require-exact` to fail on sounding-note or rest differences, or `--baseline-dir` to compare an earlier MusicXML export. Comparison results never feed production; registered authored playback and explicit training use the paired solos separately. The supplied solos sound one octave below encoded pitches; All of Me aligns at measure 1 and Autumn Leaves at measure 2, preserving the lead-in. Matching notes or technical validity do not establish musical preference.
See [Shared Part Generation](TECHNICAL_GUIDE.md#shared-part-generation) and [Authored Repertoire and Learned Solos](TECHNICAL_GUIDE.md#authored-repertoire-and-learned-solos) for the active pipeline and limitations. Keep just one compact baseline and the latest comparison under `build/solo_comparison/`.
To inspect generated assets without building the full package, run:
@@ -114,36 +119,38 @@ To inspect generated assets without building the full package, run:
The `--check` path compares generation invariants against `tests/fixtures/performance_summary.json`.
Modulus owns the pinned build-only dependencies in its optional `assets` group, selected by `tooling.python.groups` in `modulus.json`. Configure prepares `build/build_common/python`; `./modulus.py python` and CMake use that environment and the exported Modulus modules. Builds never install Python dependencies. After dependency pins change, run configure again. C++-only libraries do not opt in, and Python is not packaged for the Pi.
CMake builds engrave with two Python workers and share a disposable page cache under `build/build_common/engraving_cache`. Native and ARM packaging reuse unchanged pages; CI also retains this cache between runs. Cached musical roles are reused independently; music, playback, and catalog validation still run for every generation. Set `VAMP_ENGRAVING_JOBS` (1-16) to adjust concurrency, or set `VAMP_ENGRAVING_CACHE_DIR` to an empty CMake value to disable caching. Direct generator runs can opt in with `--jobs 2 --engraving-cache build/build_common/engraving_cache --progress`.
CMake builds use two composition workers and two engraving workers, with separate disposable caches under `build/build_common/composition_cache` and `build/build_common/engraving_cache`. Native and ARM packaging reuse unchanged roles and pages; CI also retains the page cache between runs. Music, recording, and catalog validation still run for every generation. Set `VAMP_COMPOSITION_JOBS` or `VAMP_ENGRAVING_JOBS` (1-16) to adjust concurrency, or set `VAMP_ENGRAVING_CACHE_DIR` to an empty CMake value to disable persistent page caching. Direct generator runs default to two composition workers and one engraving worker, without persistent caches; use `--composition-cache`, `--engraving-cache`, `--composition-jobs`, `--jobs`, and `--progress` as needed.
Identical notation bodies share layout and body rasterization even when titles or score identities differ; each score retains its own header, pages, music, and catalog identity. A `cold namespace` means no cache directory exists for the current renderer fingerprint. It does not mean that every score must be rendered independently: repeated bodies can still be reused within that build, including with persistent caching disabled. Progress separates full-score hits, body hits, newly rasterized bodies, and composed pages.
Solos use one pooled model trained on the paired scores in `data/training`. Both solo slots contribute equally; there is no learned acoustic/electric style distinction. **Registered Solo 1 is played exactly by acoustic and Solo 2 exactly by electric.** Unregistered song/slot pairs use deterministic model prediction. Exact registered playback is explicit authored reuse, not a claim of model memorization. Only Autumn Leaves and All of Me are included initially.
Training and playback use the same contextual harmony validator as predictions. It recognizes supported colors, passing notes, approaches and anticipations from the actual notes, durations and chords. No explanatory score labels or hand-authored justification tags are required. The four approved solos pass this validation; their explanatory labels have been removed without changing their music. Unsupported relationships still produce a diagnostic rather than silently changing a note.
Add a source and its authored solo without changing generator code:
Add a source and its authored solo, then retrain the pooled model, without changing generator code:
```sh
./modulus.py python -m generator.train --add-source /path/to/song.xml --add-solo /path/to/solo1.xml --slot 1
./modulus.py python -m generator.train --add-source data/songs/song.xml --add-solo /path/to/solo2.xml --slot 2
```
To retrain existing pairs, run `./modulus.py python -m generator.train --leave-one-song-out`. Ordinary builds use the saved model; they never retrain automatically. The four-example model is a starting point and does not establish musical quality on unfamiliar songs. [Training and playback details](TECHNICAL_GUIDE.md#authored-repertoire-and-learned-solos) cover pairing, timing, pitch convention, data migration and limitations.
To retrain existing pairs, run `./modulus.py python -m generator.train`; add `--leave-one-song-out` for held-out likelihood diagnostics. Training writes `data/models/solo.json` and `build/training/report.json`. Ordinary builds use registered authored music or the saved model and never retrain automatically. Exact registered playback verifies authored reuse; it does not demonstrate model memorization or musical quality on unfamiliar songs. [Training and playback details](TECHNICAL_GUIDE.md#authored-repertoire-and-learned-solos) cover pairing, timing, pitch convention, data migration, and limitations.
The build-time pipeline is Soundslice/MuseScore -> `.xml` -> musical model and independent parts -> part generation -> JSON, engraved PNG pages, generated_tone audio, Pico title artwork, and a read-only catalog database. Sources in `data/songs/` retain the seven-section performance form. Songs generate Acoustic, Electric, and Vocal parts in their authored key, without key variants or a key-selection menu. Performance measure numbers run continuously across the complete arrangement, starting at 1 in the lead-in; they do not restart with a new section or chorus. Pickups occupy the preceding measure. Standalone comparison XMLs use local measure numbers and retain the original arrangement numbers in `manifest.solo`.
The build-time pipeline is MusicXML -> source analysis -> independent roles -> assembled parts -> semantic JSON, supplied recordings, engraved PNG pages, Pico title artwork, and a read-only catalog database. Each source produces the six-section performance form as Acoustic, Electric, and Vocal parts in its authored key, without key variants or a key-selection menu. Performance measure numbers run continuously across the complete arrangement, starting at 1 in the lead-in; they do not restart with a new section or chorus. Pickups occupy the preceding measure. Standalone comparison XMLs use local measure numbers and retain the original arrangement numbers in `manifest.solo`.
All generated monophonic guitar material uses frets 1–12 without open strings. Accompaniment uses all legal compact positions; a chord can extend to fret 20 while retaining at least one sounding anchor at fret 12 or below and the existing three-fret span restriction. Electric source-melody harmonization retains its existing placement policy. Exact source pitches and ties are preserved; unplayable or unsupported input fails explicitly rather than being silently octave-folded.
All generated monophonic guitar material uses frets 1–12 without open strings. Accompaniment uses all legal compact positions; a chord can extend to fret 20 while retaining at least one sounding anchor at fret 12 or below and the existing three-fret span restriction. Electric source-melody harmonization retains its separate placement and chord-change reattack policy. Import preserves exact source pitches and ties; unplayable or unsupported input fails explicitly rather than being silently octave-folded.
There are no genre presets or runtime composition. The complete arranged score is played in reading order. The source-defined form and continuous whole-arrangement measure numbering remain unchanged.
All parts use two music systems per page, with a single system only when the score ends on an odd system. Lines break only between measures and share consistent staff/TAB widths. A measure that cannot fit at readable spacing fails with a diagnostic instead of being split. Tall notation is scaled uniformly to fit the two rows; guitar notation and TAB stay together and aligned. TAB displays frets and ties without duplicate rests; standard notation retains the complete rhythm. The generated `.pages/*.png` files are the same page images displayed by the product.
Electric source-melody harmonization retains its existing chord and exact-pitch behavior. Accompaniment uses compact source-derived voicings and sparse rhythm figures, reused at corresponding form positions. Neither accompaniment reads a generated solo or the other guitar's realization. Solos use all six strings; fixed scale maps and the former handwritten solo search/catalogs have been removed. New predictions retain chord, color and resolution checks. No dynamics, accents, velocity shaping or random timing are added.
Write all music in reading order: repeat bars, numbered endings, navigation jumps, and pass-specific ties are rejected. The six-section performance arrangements remain fully written out. Chord labels use `M`, `m`, `aug`, `dim`, `sus`, and `mM7`, including explicit major triads such as `CM`. Recognized Soundslice text-only harmonies are normalized into structured chords; parenthesized chords mark optional turnarounds omitted from the final vocal pass.
Write all music in reading order: repeat bars, numbered endings, navigation jumps, and pass-specific ties are rejected. The seven-section performance arrangements remain fully written out. Chord labels use `M`, `m`, `aug`, `dim`, `sus`, and `mM7`, including explicit major triads such as `CM`. Recognized Soundslice text-only harmonies are normalized into structured chords; parenthesized chords mark optional turnarounds omitted from the final arrangement ending.
Settings provides independent **Metronome** and **Backing** toggles, both off by default, plus **Tempo**. Song -> Part -> Score navigation stays the same. With both toggles off, pages turn manually. With either enabled, opening a part starts a one-bar count-in and an automatic performance; tap/double-tap page commands are ignored, and hold exits to the part menu. Natural completion also returns there.
The Settings menu's **Playback** toggle plays the selected part once at the menu BPM, suppresses the metronome, and returns to the part menu after natural completion. Manual page turns do not seek or restart audio. Playback is off by default. Python generates shared PCM tones and exact beat timelines; C++/Sonus only loads, schedules, and mixes them. Audio sample sustain loops hold individual notes for their written duration; they never repeat musical passages. No SoundFonts or runtime synthesizer are required. The main menu contains Performance and Settings. Settings holds Playback, Metronome, and Tempo; its values apply for the current session. Navigation is Song -> Part -> Score.
A performance is an eight-bar intro followed by five complete passes: Vocal Melody, Guitar Melody, Acoustic Solo, Electric Solo, and Vocal Melody. There is no separate Ending section. The current 32-bar songs contain 168 numbered bars. Optional turnaround chords are omitted from the final vocal pass.
Audio comes exclusively from supplied WAV files: `package/assets/sounds/beat1.wav` accents each measure's first beat, and `beat2.wav` plays its other beats. Backing tracks belong in `package/assets/backing/<song_id>_<bpm>.wav`, for example `all_of_me_120.wav`. The normalized song ID comes from the source filename. Files are copied unchanged; the app never synthesizes notes or stretches recordings.
Tempo choices are 60, 70, 80, 90, 100, 110, and 120 BPM. With Backing on, choices are the tempos available for every catalog song; currently that is 120 BPM. Enabling Backing selects the closest available tempo, preferring the slower choice on a tie. If no tempo is shared, Backing is unavailable. Add recordings at the other tempos and rebuild to enable them.
Backing recordings include one bar of count-in, the complete score, and the held ending with decay. The recorded count-in plays alone. When both toggles are on, metronome clicks start at the score and stop at its end, leaving the last three held bars and decay unclicked. Metronome-only mode supplies its own count-in and stops at score end. Pages follow the rendered audio clock, using each engraved page's actual measure range; the final page stays visible through the backing tail.
Code style is managed by Modulus, using compact two-space formatting. Python imports modules by their full names and qualifies imported symbols; only `from __future__ import annotations` is exempt, and imports have no aliases. Run `./modulus.py format` to format C++ and Python, or `./modulus.py format --check` to check formatting and prohibited aliases. The project-local Neovim integration routes Conform formatting through the same container tools, including format on save and existing LazyVim toggles. Run configure first when the image or exported tooling needs updating. Use `tooling.editor.clang_format` for C++ overrides and `tooling.format.exclude` for project-relative exclusion globs; do not edit generated `.clang-format` files.
@@ -153,7 +160,7 @@ See [Score Generation](TECHNICAL_GUIDE.md#score-generation) for supported MusicX
Build-time engraving is implemented in Python in `generator/engraving`, using the bundled Petaluma OTF fonts and SMuFL metadata. MuseScore is an optional authoring tool, not a build dependency or rendering backend. Configure installs the optional Modulus asset dependencies. Exact musical timing is retained through layout, with line breaks between complete measures. Missing glyphs, unrepresentable page geometry, and failed notation audits stop generation before replacing existing assets.
`generator/palette.json` supplies the dark gig theme for Zero, Pico, score pages, and previews. CMake generates the shared C++ colors from the same palette used by Python. Palette changes invalidate the engraving cache without changing music, spacing, or pagination.
`generator/palette.json` preserves the sheet music’s existing dark palette across Zero, Pico, score pages, and previews. Menus use the same Petaluma Script lettering, quiet rules, tune/composer hierarchy and restrained accent colors for a jazz Real Book feel. Performance status shows Count-in, Playing or Sustain, BPM, page progress and a hold-to-exit hint. CMake generates the shared C++ colors from the same palette used by Python. Palette changes invalidate the engraving cache without changing music, spacing, or pagination.
Petaluma is from Steinberg under the SIL Open Font License. Petaluma supplies music glyphs, and Petaluma Script supplies all visible text on score pages and both device interfaces. The Zero uses packaged `PetalumaScript.otf` for dynamic UI text; the Pico displays prerendered Petaluma Script artwork. Runtime packages retain `OFL.txt`; the music font and engraver stay build-only.