khodowany 7cad6db980
ci / checks (push) Successful in 47m22s
Sizing
2026-09-30 02:49:54 -05:00
2026-09-27 11:03:43 -05:00
2026-09-29 22:30:41 -05:00
2026-09-25 09:35:09 -05:00
2026-09-30 02:49:54 -05:00
2026-09-30 02:49:54 -05:00
2026-09-27 11:03:43 -05:00
2026-09-04 19:30:11 -05:00
2026-09-25 11:37:00 -05:00
2026-09-27 11:03:43 -05:00
2026-09-30 02:49:54 -05:00
2026-09-15 00:06:02 -05:00
2026-09-11 00:02:36 -05:00
2026-09-25 09:35:09 -05:00
2026-09-27 11:03:43 -05:00
2026-09-04 19:30:11 -05:00
2026-09-24 19:44:05 -05:00
2026-09-24 19:44:05 -05:00
2026-09-30 02:18:31 -05:00
2026-09-27 11:03:43 -05:00
2026-09-30 02:49:54 -05:00

Product Vamp

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.

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.

Pico controls:

  • Tap: next menu item or next score page.
  • Double tap: previous menu item or previous score page.
  • Tap and hold: select the highlighted setting, song, or part, or return from score view to the previous menu.

Build

Configure and build the native development target:

python3 modulus.py configure
python3 modulus.py build

Release verification:

python3 modulus.py configure --target x86_64_gcc12_release
python3 modulus.py build --target x86_64_gcc12_release

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.

Configure/build read public packages anonymously from the khodo Conan registry, even when the project cache contains an expired login. configure --update refreshes both the exported Modulus tooling and Conan dependencies. --no-remote uses only locally cached packages. Publishing is the only operation that needs credentials. CMake checks the required Sonus and Luma APIs during configuration and reports stale dependencies early.

ARM builds use the versioned GCC 12 cross compilers. The selected compiler executables also identify internal Conan binaries, excluding old packages built with an unversioned compiler that could resolve to GCC 13. The first corrected build rebuilds missing internal packages; third-party caches remain reusable.

modulus.py is an unchanged copy of Modulus's canonical bootstrap. The project-specific build image lives in package/Dockerfile.build; tooling.docker declares its additional inputs and permits Docker socket access only for build/package commands that assemble the Zero image.

Refresh clangd compile databases, including the Pico firmware database, with:

python3 modulus.py lsp

When running directly outside the generated Modulus Docker image, set PICO_SDK_PATH before refreshing the Pico database.

Editor settings live in modulus.json under tooling.editor. Modulus generates the ignored .clangd, .clang-format, and pyrightconfig.json files; edit the JSON settings rather than these outputs. The clangd configuration routes Zero/shared code to the native compile database and Pico code to its firmware database.

Modulus also generates .nvim.lua for Neovim 0.11+ with exrc enabled. Review and trust it when Neovim asks; it preserves the existing LSP capabilities/keybindings and runs clangd 19 in the build container for this repo. Run python3 modulus.py lsp once to prepare both databases and the shared read-only header cache. Other editors can use python3 modulus.py clangd as their language-server command. Startup never downloads or builds anything, and never falls back to host clangd.

tooling.lsp.clangd configures the server executable, arguments, trusted compiler paths, and additional header roots. Product Vamp adds the bare-metal ARM compiler and /opt/pico-sdk; standard-library and SDK definitions open from an image-keyed cache under $XDG_CACHE_HOME/modulus/clangd (default ~/.cache/modulus/clangd). Set tooling.editor.nvim.enabled to false for a hand-managed editor setup. Existing unowned .nvim.lua files are preserved.

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:

PICO_SDK_PATH=/path/to/pico-sdk python3 modulus.py package --update

The package command creates:

build/packages/product-vamp-arm_64_gcc12_release.tar.gz
build/packages/product-vamp-arm_64_gcc12_release.tar.gz.sha256

The tarball contains the Zero Docker archive, Pi install scripts, generated catalog database, pages, audio, fonts, Pico title artwork, and Pico UF2 firmware. Complete score JSON is stored inside catalog.db; individual JSON files remain build intermediates and are not deployed. Builds replace the same target-specific archive without a release label. The manifest and vamp-zero --build-info identify the source commit.

Run sudo zero/install-zero.sh from an extracted bundle on the Zero. Every installation resets all Product Vamp data and settings, including the control Wi-Fi profile, to the new bundle defaults. No previous database rows or configuration are migrated. Unrelated OS data and networks are untouched. Installing through the control Wi-Fi can disconnect your session; use a local console or an independent connection.

The installer probes the incoming image before removing existing application data. A failed replacement leaves Product Vamp stopped and reports an error; rerun the installer after fixing the cause. Normal service restarts do not perform installation cleanup. No automatic backup of the previous installation is retained.

Publishing the rolling generic package requires credentials only for upload:

PACKAGE_REGISTRY_USER=<user> \
PACKAGE_REGISTRY_TOKEN=<token> \
python3 package/scripts/publish_latest_generic_package.py

Artifacts are published to the khodo generic package registry's required latest slot. Publish the updated Modulus tooling before running Product Vamp CI so packaging and publishing agree on the stable filenames. CI checks this contract immediately after configure using a tiny fixture archive, without compiling or contacting the registry. A failure here means the exported Modulus packager needs updating; rerun the full workflow after publishing Modulus, not just the upload step.

Score Assets

Uncompressed MusicXML 3.1/4.0 scores exported from Soundslice or MuseScore live in data/. Generated score JSON is not committed.

data/songs supplies the authored music. data/rhythms/<meter>/<series>/ supplies the accompaniment rhythm library for 2/4, 3/4, 4/4, and 5/4. All three series are active: the original quarter/eighth patterns plus series 3 with half, dotted-half, and whole notes/rests. Rhythm snippets are build inputs, not separate catalog entries or runtime files. Generation loads the library once and shares it across parts.

data/scales is reserved for unfinished scale sources. These files are not parsed, validated, generated, or packaged, and editing them does not trigger asset regeneration.

To inspect generated assets without building the full package, run:

python3 -m generator --data-dir data --output-dir /tmp/product-vamp-generated --static-assets-dir package/assets --check

The --check path compares generation invariants against data/expected/performance_summary.json. The pinned build-only dependencies are in generator/requirements.txt. Configure/build automatically refresh the project build image when this file changes; direct builds must install it in their Python environment.

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

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.

The build-time pipeline is Soundslice/MuseScore -> .xml -> musical model and rhythm plans -> 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.

Accompaniment uses deterministic four-bar phrase cycles: a two-bar motif repeats with a one-beat variation in the fourth bar when a suitable candidate exists. Acoustic has independently planned bass and upper-chord rhythms, including simultaneous attacks, sharing one fixed playable grip until the harmony changes. Its bass pitch, upper voicing, and TAB positions stay constant across rests, barlines, repeated chord symbols, and section labels while accompaniment continues. The bass can be the root or the chord's fifth, whichever makes the complete grip easier; explicit slash basses and inversions remain authoritative. The rhythms combine into one notated voice with no ties: each attack releases by the next attack or barline, simultaneous tones share a stem and duration, and rests appear only when the entire part is silent. Held notes are not reattacked. Electric accompaniment targets about two attacks per 4/4 bar, scaled for other meters, favoring gaps around the melody and acoustic rhythms. Its notes retain the selected rhythm's duration without a one-beat cap, stopping at chord changes or barlines and reusing the corresponding melody passage's chord shape when available. Both parts remain tie-free; clipped binary durations that need ties shorten to one conventional note followed by silence. Rhythm snippets supply attacks and rests; tied continuations do not create accompaniment attacks. Structured harmony supplies chord tones. N.C. and absent harmony remain silent. Solo slashes and the seven-section form retain their roles. Unsupported accompaniment meters and mid-measure meter changes fail explicitly.

There are no musical genre presets, genre defaults, or genre metadata. Obsolete vamp:style hints are rejected; visual styles, fonts, colors, and barline notation remain supported. Phrase cycles are written into the generated score and played once in reading order.

Guitar parts share a theory-based voicing and proximity search over standard tuning, frets 1-20, and a maximum four-fret span. Open strings are excluded from chords and single-note melodies. Acoustic uses a bass on string 5 or 6 and two or three consecutive upper strings within 2-4, with zero or one unused string between them. Electric chords use strings 1-3 or 1-4 and may omit the root. Electric melody notes longer than a quarter note, including full tied runs, receive chords when playable; chord changes create fresh attacks even for short remaining fragments. Electric melodies are raised one octave; vocal registers stay unchanged. All parts still sound one octave below their generated notation, and explicit MusicXML transposition is honored once. Unplayable melody harmonizations remain single notes with a diagnostic; unplayable accompaniment or exact melody pitches fail generation.

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 melody prioritizes four-string chords, then melody on top. Every generated guitar chord requires the root, the fifth, or both; neither is preferred over the other. Acoustic favors simple grips, allowing bass plus two upper notes when that is easier than a four-note shape. Defining chord tones remain mandatory. Acoustic and electric chords share a combined effort score for shape difficulty, fret height, and movement, so a compact mid-neck grip can beat a stretched lower one. Shape difficulty adds three points per fret of span and two per additional distinct fret. Height adds one point per fret, plus two more per fret above 12. Acoustic transitions weight per-string movement by two; electric retains its doubled-center movement metric. Frets 13-20 remain available when the overall tradeoff justifies them. Exact melody pitches and rhythms remain unchanged, and unharmonized melody placement retains its existing proximity scoring.

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.

The main 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 Songs, Playback, Metronome, and Tempo. Navigation is Song -> Part -> Score.

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.

Use American English in project-owned identifiers, filenames, messages, and documentation, such as catalog, color, and canceled. Preserve authored music and external API names.

See Score Generation for supported MusicXML notation, Vamp properties, schema details, and diagnostics. There is no compact-JSON source format or legacy runtime adapter. Unsupported notation fails generation before existing assets are replaced.

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. The managed image and CI install the pinned Python requirements automatically; direct host builds use pip install -r generator/requirements.txt. Exact musical timing is retained through layout, and dense measures can continue across systems. Missing glyphs, unrepresentable page geometry, and failed notation audits stop generation before replacing existing assets.

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.

Pico artwork generation requires Pillow's Raqm layout support to preserve kerning. On Debian/Ubuntu, install libfribidi0 alongside the pinned Python requirements. The build image and CI install both. See Pillow's installation requirements.

Pico firmware embeds a small set of fixed labels, part names, digits, and the page separator, generated as four-bit coverage sprites under generated/pico/static.p4. Score generation prerenders titles into content-addressed pico/titles/*.mask assets referenced by the catalog and served by the Zero when the selection changes. Pico blends those pixels and composes page counts from digit sprites; it does not load fonts, shape text, or rasterize glyphs. All Pico text shaping and artwork generation run on the build host. The build image and CI install the pinned dependencies automatically. For a direct build, first run:

python3 -m pip install -r generator/requirements.txt

To inspect the fixed artwork independently, run python3 -m generator.pico_artwork --static-output /tmp/product-vamp-pico/static.p4. Changing the source OTF or artwork generator rebuilds the firmware sprites and generated title assets. These generated files are not authored music and are not checked in.

License

Product Vamp is released under the MIT License. Bundled third-party fonts keep their own licenses next to the font files.

S
Description
No description provided
Readme MIT 123 MiB
Languages
Python 70.6%
C++ 26.7%
CMake 1.6%
Shell 1%
C 0.1%