Package
ci / checks (push) Successful in 48m34s

This commit is contained in:
khodowany
2026-09-30 19:46:37 -05:00
parent 2a1aa2c67a
commit 09656a9d60
6621 changed files with 698 additions and 774 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build/*
build/build_tools/* build/build_tools/*
!build/build_tools/docker/ !build/build_tools/docker/
build/build_tools/docker/* build/build_tools/docker/*
!build/build_tools/docker/format-requirements.txt !build/build_tools/docker/format_requirements.txt
dist dist
node_modules node_modules
*.log *.log
+15 -15
View File
@@ -88,44 +88,44 @@ jobs:
python3 \ python3 \
python3-venv python3-venv
python3 -m venv build/build_common/ci_python python3 -m venv build/build_common/ci_python
build/build_common/ci_python/bin/python -m pip install --no-cache-dir conan==2.26.2 -r generator/requirements.txt build/build_common/ci_python/bin/python -m pip install --no-cache-dir conan==2.26.2
echo "$PWD/build/build_common/ci_python/bin" >> "$GITHUB_PATH" echo "$PWD/build/build_common/ci_python/bin" >> "$GITHUB_PATH"
- name: Restore Conan cache - name: Restore Conan cache
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: build/build_common/conan_cache path: build/build_common/conan_cache
key: product-vamp-conan-${{ runner.os }}-${{ hashFiles('conanfile.py', 'modulus.json', 'conan/profiles/*') }} key: product_vamp_conan_${{ runner.os }}_${{ hashFiles('conanfile.py', 'modulus.json', 'conan/profiles/*') }}
restore-keys: product-vamp-conan-${{ runner.os }}- restore-keys: product_vamp_conan_${{ runner.os }}_
- name: Configure native release - name: Configure native release
run: python3 modulus.py configure --target x86_64_gcc12_release --update run: python3 modulus.py configure --target x86_64_gcc12_release --update
- name: Check package tooling - name: Check package tooling
run: python3 -m unittest discover -s tests -p 'test_publish_package.py' run: python3 modulus.py python -m unittest discover -s tests -p 'test_publish_package.py'
- name: Check code style - name: Check code style
run: | run: |
python3 -m pip install --no-cache-dir -r build/build_tools/docker/format-requirements.txt python3 -m pip install --no-cache-dir -r build/build_tools/docker/format_requirements.txt
python3 modulus.py format --check python3 modulus.py format --check
- name: Identify engraving renderer - name: Identify engraving renderer
id: renderer id: renderer
run: echo "fingerprint=$(python3 -m generator --renderer-fingerprint)" >> "$GITHUB_OUTPUT" run: echo "fingerprint=$(python3 modulus.py python -m generator --renderer-fingerprint)" >> "$GITHUB_OUTPUT"
- name: Restore engraving cache - name: Restore engraving cache
id: engraving-cache id: engraving_cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
with: with:
path: build/build_common/engraving-cache/${{ steps.renderer.outputs.fingerprint }} path: build/build_common/engraving_cache/${{ steps.renderer.outputs.fingerprint }}
key: product-vamp-pages-${{ steps.renderer.outputs.fingerprint }}-${{ hashFiles('generator/**/*.py', 'generator/*.py', 'data/songs/*.xml', 'data/rhythms/**/*.xml', 'data/scales/**/*.xml') }} key: product_vamp_pages_${{ steps.renderer.outputs.fingerprint }}_${{ hashFiles('generator/**/*.py', 'generator/*.py', 'generator/palette.json', 'data/songs/*.xml', 'data/rhythms/**/*.xml', 'data/scales/**/*.xml') }}
restore-keys: product-vamp-pages-${{ steps.renderer.outputs.fingerprint }}- restore-keys: product_vamp_pages_${{ steps.renderer.outputs.fingerprint }}_
- name: Build native release - name: Build native release
run: python3 modulus.py build --target x86_64_gcc12_release run: python3 modulus.py build --target x86_64_gcc12_release
- name: Save engraving cache - name: Save engraving cache
if: steps.engraving-cache.outputs.cache-hit != 'true' if: steps.engraving_cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4 uses: actions/cache/save@v4
with: with:
path: build/build_common/engraving-cache/${{ steps.renderer.outputs.fingerprint }} path: build/build_common/engraving_cache/${{ steps.renderer.outputs.fingerprint }}
key: ${{ steps.engraving-cache.outputs.cache-primary-key }} key: ${{ steps.engraving_cache.outputs.cache-primary-key }}
- name: Test native release - name: Test native release
run: python3 modulus.py test --target x86_64_gcc12_release run: python3 modulus.py test --target x86_64_gcc12_release
- name: Test asset generation - name: Test asset generation
run: python3 -m unittest discover -s tests -p 'test_*.py' run: python3 modulus.py python -m unittest discover -s tests -p 'test_*.py'
- name: Fetch Pico SDK - name: Fetch Pico SDK
if: ${{ gitea.event_name == 'workflow_dispatch' || (gitea.event_name == 'push' && gitea.ref == 'refs/heads/master') }} if: ${{ gitea.event_name == 'workflow_dispatch' || (gitea.event_name == 'push' && gitea.ref == 'refs/heads/master') }}
env: env:
@@ -150,4 +150,4 @@ jobs:
env: env:
PACKAGE_REGISTRY_USER: ${{ secrets.CONAN_USER }} PACKAGE_REGISTRY_USER: ${{ secrets.CONAN_USER }}
PACKAGE_REGISTRY_TOKEN: ${{ secrets.CONAN_TOKEN }} PACKAGE_REGISTRY_TOKEN: ${{ secrets.CONAN_TOKEN }}
run: python3 package/scripts/publish_latest_generic_package.py run: python3 modulus.py publish
+1 -1
View File
@@ -7,6 +7,6 @@ if(VAMP_PICO_FIRMWARE_PROJECT)
return() return()
endif() endif()
project(product-vamp LANGUAGES CXX) project(product_vamp LANGUAGES CXX)
include(cmake/zero.cmake) include(cmake/zero.cmake)
+16 -14
View File
@@ -36,7 +36,7 @@ python3 modulus.py configure --target x86_64_gcc12_release
python3 modulus.py build --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. `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. 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.
@@ -69,13 +69,13 @@ PICO_SDK_PATH=/path/to/pico-sdk python3 modulus.py package --update
The package command creates: The package command creates:
```text ```text
build/packages/product-vamp-arm_64_gcc12_release.tar.gz build/packages/product_vamp_arm_64_gcc12_release.tar.gz
build/packages/product-vamp-arm_64_gcc12_release.tar.gz.sha256 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. 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. 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. 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.
@@ -84,7 +84,7 @@ Publishing the rolling generic package requires credentials only for upload:
```sh ```sh
PACKAGE_REGISTRY_USER=<user> \ PACKAGE_REGISTRY_USER=<user> \
PACKAGE_REGISTRY_TOKEN=<token> \ PACKAGE_REGISTRY_TOKEN=<token> \
python3 package/scripts/publish_latest_generic_package.py python3 modulus.py publish
``` ```
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. 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.
@@ -95,22 +95,22 @@ Uncompressed MusicXML 3.1/4.0 scores exported from Soundslice or MuseScore live
`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/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` supplies the 21 C-centered modes of major, melodic minor, and harmonic minor. The Python build transposes these patterns internally to label the acoustic and electric solo passages; it never transposes the songs or stores scale assignments in song XML. Scale changes appear above the existing solo chord symbols, with detailed build-only reports in `generated-assets/analysis/`. Editing a scale regenerates assets, and invalid patterns fail before replacing the previous output. `data/scales` supplies the 21 C-centered modes of major, melodic minor, and harmonic minor. The Python build transposes these patterns internally to label the acoustic and electric solo passages; it never transposes the songs or stores scale assignments in song XML. Scale changes appear above the existing solo chord symbols, with detailed build-only reports in `generated_assets/analysis/`. Editing a scale regenerates assets, and invalid patterns fail before replacing the previous output.
To inspect generated assets without building the full package, run: To inspect generated assets without building the full package, run:
```sh ```sh
python3 -m generator --data-dir data --output-dir /tmp/product-vamp-generated --static-assets-dir package/assets --check ./modulus.py python -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 `--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. 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. 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 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. 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. 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 voicing until the harmony changes within an arrangement iteration. Bass, upper voices, and TAB positions stay constant across rests, barlines, and repeated chord symbols. Any defined chord tone can serve as the bass; 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. 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 voicing until the harmony changes within an arrangement iteration. Bass, upper voices, and TAB positions stay constant across rests, barlines, and repeated chord symbols. Any defined chord tone can serve as the bass; 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.
@@ -132,7 +132,9 @@ Use American English in project-owned identifiers, filenames, messages, and docu
See [Score Generation](TECHNICAL_GUIDE.md#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. See [Score Generation](TECHNICAL_GUIDE.md#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. 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.
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. 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.
@@ -141,10 +143,10 @@ Pico artwork generation requires Pillow's Raqm layout support to preserve kernin
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: 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:
```sh ```sh
python3 -m pip install -r generator/requirements.txt ./modulus.py configure
``` ```
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. To inspect the fixed artwork independently, run `./modulus.py python -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 ## License
+6 -4
View File
@@ -13,10 +13,12 @@ python3 modulus.py package
Copy the tarball to the Zero, verify its `.sha256`, unpack it anywhere, and run: Copy the tarball to the Zero, verify its `.sha256`, unpack it anywhere, and run:
```sh ```sh
sudo zero/install-zero.sh sudo zero/install_zero.sh
``` ```
The installer loads `zero/product-vamp-zero-linux-arm64.docker.tar`, installs `/etc/product-vamp/vamp.json` if missing, configures NetworkManager AP mode, and enables the systemd units. The installer loads `zero/product_vamp_zero_linux_arm64.docker.tar`, installs `/etc/product_vamp/vamp.json` if missing, configures NetworkManager AP mode, and enables the systemd units.
Project-owned install paths, services, executables, and archives use underscores. The checkout and Git URL remain `product-vamp`. On upgrades, the installer validates and probes the incoming image before removing the old hyphenated Product Vamp paths, services, and network profile. Unrelated services, networks, and data are preserved; symlinked installation paths are rejected.
On reinstalls, the installer skips apt entirely when the required OS packages are already present. If packages are missing, the Zero needs upstream internet before the installer can finish provisioning Docker, NetworkManager, and SQLite. On reinstalls, the installer skips apt entirely when the required OS packages are already present. If packages are missing, the Zero needs upstream internet before the installer can finish provisioning Docker, NetworkManager, and SQLite.
@@ -29,7 +31,7 @@ Defaults:
- Zero IP: `192.168.42.1` - Zero IP: `192.168.42.1`
- API: `http://192.168.42.1:8080` - API: `http://192.168.42.1:8080`
For API-only quick checks, set `runtime.headless` to `true` or run `vamp-zero --once` from a development build. For API-only quick checks, set `runtime.headless` to `true` or run `vamp_zero --once` from a development build.
Score inputs are tracked under `data/songs/`. The build/package step generates acoustic, electric, and vocal parts, engraved pages, audio, and the read-only catalog database. Installation replaces these assets together; no database migration or runtime score import is needed. Score inputs are tracked under `data/songs/`. The build/package step generates acoustic, electric, and vocal parts, engraved pages, audio, and the read-only catalog database. Installation replaces these assets together; no database migration or runtime score import is needed.
@@ -38,7 +40,7 @@ Score inputs are tracked under `data/songs/`. The build/package step generates a
Hold BOOTSEL while plugging the Pico into USB, then copy: Hold BOOTSEL while plugging the Pico into USB, then copy:
```text ```text
pico/product-vamp-pico2w.uf2 pico/product_vamp_pico2w.uf2
``` ```
to the mounted `RPI-RP2` drive. to the mounted `RPI-RP2` drive.
+13 -13
View File
@@ -35,7 +35,7 @@ Page commands use `/api/remote/next` and `/api/remote/previous`; the old `/api/p
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`. 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. 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 Playback, 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. 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 Playback, 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.
@@ -51,7 +51,7 @@ In score view, Zero and Pico share one gesture state machine: a second release w
## Score Generation ## Score Generation
The build runs `python3 -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. 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. 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.
@@ -132,7 +132,7 @@ Generated accompaniment has no ties. Acoustic attacks release at the earliest of
Acoustic accompaniment uses the planned foundation. Electric accompaniment sections in songs use the response plan; song electric melody sections are raised one octave before harmonization. Song sections remain Rhythm Lead-in, Vocal Melody, Guitar Melody, Acoustic Solo, Electric Solo, Vocal Melody, and Ending, with each part retaining its melody, accompaniment, rest, or solo-slash role. Optional turnarounds are removed before planning the final ending. The generated phrase cycles contain explicit events in reading order, not runtime loops or repeat signs. Acoustic accompaniment uses the planned foundation. Electric accompaniment sections in songs use the response plan; song electric melody sections are raised one octave before harmonization. Song sections remain Rhythm Lead-in, Vocal Melody, Guitar Melody, Acoustic Solo, Electric Solo, Vocal Melody, and Ending, with each part retaining its melody, accompaniment, rest, or solo-slash role. Optional turnarounds are removed before planning the final ending. The generated phrase cycles contain explicit events in reading order, not runtime loops or repeat signs.
Each source is parsed, validated, and normalized once for all three parts. The batch generator computes shared accompaniment plans before building those parts. Public `arrange_document` and `generate_performance_scores` calls require a keyword `rhythms` argument holding a `generator.rhythm.Library`; internal part builders receive precomputed plans. Builders treat the prepared source and plans as read-only and own their generated notes and spans. Source-path checks reject colliding song slugs before writing parts: for example, `one-two.xml` and `one_two.xml` would both produce `one-two-acoustic.json` and cannot coexist. Each source is parsed, validated, and normalized once for all three parts. The batch generator computes shared accompaniment plans before building those parts. Public `arrange_document` and `generate_performance_scores` calls require a keyword `rhythms` argument holding a `generator.rhythm.Library`; internal part builders receive precomputed plans. Builders treat the prepared source and plans as read-only and own their generated notes and spans. Source-path checks reject colliding song slugs before writing parts: for example, `one-two.xml` and `one_two.xml` would both produce `one_two_acoustic.json` and cannot coexist.
All three parts use octave-transposing notation: written notes sound one octave lower. This includes the vocal part for the singer's range. Unmarked source notation uses that convention by default. Vocal melodies retain their written pitches and octave numbers. Song electric melody sections add twelve semitones to both written and sounding pitch; the guitar's octave-transposing convention still applies. Written C4 sounds C3 (A-string fret 3), and written C#6 sounds C#5, within the 1-20 fret range. Generated accompaniment is notated using the same convention. All three parts use octave-transposing notation: written notes sound one octave lower. This includes the vocal part for the singer's range. Unmarked source notation uses that convention by default. Vocal melodies retain their written pitches and octave numbers. Song electric melody sections add twelve semitones to both written and sounding pitch; the guitar's octave-transposing convention still applies. Written C4 sounds C3 (A-string fret 3), and written C#6 sounds C#5, within the 1-20 fret range. Generated accompaniment is notated using the same convention.
@@ -164,7 +164,7 @@ There is no finger assignment, barre logic, or predefined chord-shape dictionary
All musical compilation and tone synthesis run in Python (`generator/playback.py`). Each transformed score receives a sorted timeline of independent tone events with rational quarter-note `onset` and `end` positions. Ties become one continuous attack. Rests and spacers remain silence, including trailing silence. Harmony labels are not auto-accompaniment: only pitches actually present in the selected arrangement sound. Cue notes are silent, hidden pitched notes sound, and pitchless rhythm slashes are silent. All musical compilation and tone synthesis run in Python (`generator/playback.py`). Each transformed score receives a sorted timeline of independent tone events with rational quarter-note `onset` and `end` positions. Ties become one continuous attack. Rests and spacers remain silence, including trailing silence. Harmony labels are not auto-accompaniment: only pitches actually present in the selected arrangement sound. Cue notes are silent, hidden pitched notes sound, and pitchless rhythm slashes are silent.
The `soft-struck` profile generates shared mono 48 kHz PCM16 WAVs with a short attack/decay and a seamless sustain loop, plus a shared 50 ms release envelope. Exact sounding pitches, including supported microtones, determine samples. Instrument transposition is applied once from the generated musical model, without role- or filename-dependent octave heuristics. Samples are shared across scores and voices by content identity. There are no SoundFonts, bundled instrument samples, oscillators, or MusicXML parsing at runtime. The `soft_struck` profile generates shared mono 48 kHz PCM16 WAVs with a short attack/decay and a seamless sustain loop, plus a shared 50 ms release envelope. Exact sounding pitches, including supported microtones, determine samples. Instrument transposition is applied once from the generated musical model, without role- or filename-dependent octave heuristics. Samples are shared across scores and voices by content identity. There are no SoundFonts, bundled instrument samples, oscillators, or MusicXML parsing at runtime.
Grace groups honor explicit steal-time-previous/following or make-time; unspecified groups share 25 percent of the following event. Conflicting grace modes, trailing grace groups, unsupported unpitched sound, tied measured tremolo, and incompatible paired tremolo make playback explicitly unavailable while retaining the notation. Measured tremolo uses written subdivision and tuplet ratios. The tone range is 20-18,000 Hz. Asset validation checks containment, SHA-256, PCM format/length, loop endpoints, envelope shape, event ordering, references, and rational timing. Generation remains transactional if validation fails. Grace groups honor explicit steal-time-previous/following or make-time; unspecified groups share 25 percent of the following event. Conflicting grace modes, trailing grace groups, unsupported unpitched sound, tied measured tremolo, and incompatible paired tremolo make playback explicitly unavailable while retaining the notation. Measured tremolo uses written subdivision and tuplet ratios. The tone range is 20-18,000 Hz. Asset validation checks containment, SHA-256, PCM format/length, loop endpoints, envelope shape, event ordering, references, and rational timing. Generation remains transactional if validation fails.
@@ -186,17 +186,17 @@ Selection first preserves every required pitch. Recognized ii-V pairs, resolving
Only the soloing part receives compact `scale_spans`, with absolute rational start/end offsets, labels, and matched status. `N.C.` and solo boundaries stop annotations. Accompaniment, melody, pitches, TAB, and playback are unchanged; no solo notes are synthesized. Original melody notes are not constraints on these slash-only solo passages. Unmatched chords print `No exact scale match` and emit a source/measure/beat warning without changing the harmony. Invalid scale data or missing tonal metadata instead fail generation before publication. Only the soloing part receives compact `scale_spans`, with absolute rational start/end offsets, labels, and matched status. `N.C.` and solo boundaries stop annotations. Accompaniment, melody, pitches, TAB, and playback are unchanged; no solo notes are synthesized. Original melody notes are not constraints on these slash-only solo passages. Unmatched chords print `No exact scale match` and emit a source/measure/beat warning without changing the harmony. Invalid scale data or missing tonal metadata instead fail generation before publication.
Scale labels appear above chord symbols at collection changes and system starts. Detailed parent collections, source and arranged positions, chord-root modes, degrees, chord-tone targets, and selection reasons are written to `generated-assets/analysis/<song>.txt`. These reports are published transactionally with the build output but excluded from both installation and Docker runtime assets. The C++ runtime only displays the generated pages; it does not load the scale library or run harmonic analysis. Scale labels appear above chord symbols at collection changes and system starts. Detailed parent collections, source and arranged positions, chord-root modes, degrees, chord-tone targets, and selection reasons are written to `generated_assets/analysis/<song>.txt`. These reports are published transactionally with the build output but excluded from both installation and Docker runtime assets. The C++ runtime only displays the generated pages; it does not load the scale library or run harmonic analysis.
### Engraving Boundary ### 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. `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. Playback is deliberately excluded from page keys and is always rebuilt independently. 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. 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. Playback is deliberately excluded from page keys and is always rebuilt independently. 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. 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. All dependencies are pinned in `generator/requirements.txt` and remain build-only. Standard glyph names and their upstream notice are vendored alongside the fonts; no system music fonts are consulted. `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. 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.
@@ -216,7 +216,7 @@ Pages are 976x484 pixels with 10-pixel guitar or 12-pixel vocal staff spacing, 2
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, generated PCM WAVs, 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. 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, generated PCM WAVs, 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. `python3 -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 all generator and active XML inputs, including rhythms, and restore earlier snapshots only within the same renderer directory; only that directory is saved after a successful build. 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 all generator and active XML inputs, including rhythms, and restore earlier snapshots 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. `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.
@@ -244,7 +244,7 @@ The body shows the focused menu item, song title, or setting and its current val
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. 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 `python3 -m generator.pico_artwork --static-output <path>` for firmware sprites and `python3 -m generator.pico_artwork --assets <generated-assets>` for title assets. Python, Pillow/Raqm, and FontTools remain build-only dependencies. 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. 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.
@@ -261,8 +261,8 @@ Modulus packages the CMake install stage. Product Vamp uses `install_prefix` `.`
The ARM64 package target builds: The ARM64 package target builds:
- `zero/product-vamp-zero-linux-arm64.docker.tar` - `zero/product_vamp_zero_linux_arm64.docker.tar`
- `pico/product-vamp-pico2w.uf2` - `pico/product_vamp_pico2w.uf2`
- `manifest.json` - `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. 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.
@@ -271,9 +271,9 @@ Generated score assets are created during CMake build/package and copied into th
`generator/catalog.py` uses Python's standard-library SQLite module to construct `catalog.db` after musical, playback, and engraving validation. The database carries application ID `0x56414D50`, complete score bundles, and menu metadata. Runtime validates both required strict tables, their column names/types, non-null requirements, and primary keys 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. `generator/catalog.py` uses Python's standard-library SQLite module to construct `catalog.db` after musical, playback, and engraving validation. The database carries application ID `0x56414D50`, complete score bundles, and menu metadata. Runtime validates both required strict tables, their column names/types, non-null requirements, and primary keys 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. 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 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. 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. 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.
+12
View File
@@ -0,0 +1,12 @@
include_guard(GLOBAL)
if(NOT MODULUS_PYTHON_RUNNER OR NOT MODULUS_PYTHON_ROOT)
message(FATAL_ERROR "Modulus build Python is not configured. Run ./modulus.py configure first.")
endif()
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(VAMP_PYTHON_COMMAND ${Python3_EXECUTABLE} -u ${MODULUS_PYTHON_RUNNER} ${MODULUS_PYTHON_ROOT})
file(GLOB VAMP_SHARED_PYTHON_INPUTS CONFIGURE_DEPENDS
${MODULUS_PYTHON_ROOT}/modulus/*.py
${MODULUS_PYTHON_ROOT}/modulus/*_requirements.txt
)
list(APPEND VAMP_SHARED_PYTHON_INPUTS ${MODULUS_PYTHON_REQUIREMENTS})
+11 -9
View File
@@ -29,7 +29,7 @@ endif()
include(${PICO_SDK_PATH}/pico_sdk_init.cmake) include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
project(product-vamp-pico LANGUAGES C CXX ASM) project(product_vamp_pico LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
@@ -37,6 +37,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
pico_sdk_init() pico_sdk_init()
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pico_artwork.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pico_artwork.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/theme.cmake)
set(VAMP_MODULUS_INCLUDE_DIR "" CACHE PATH "Path to the Modulus public include directory") set(VAMP_MODULUS_INCLUDE_DIR "" CACHE PATH "Path to the Modulus public include directory")
set(VAMP_RESOLVED_MODULUS_INCLUDE_DIR "${VAMP_MODULUS_INCLUDE_DIR}") set(VAMP_RESOLVED_MODULUS_INCLUDE_DIR "${VAMP_MODULUS_INCLUDE_DIR}")
@@ -74,7 +75,7 @@ if(NOT EXISTS "${VAMP_RESOLVED_MODULUS_INCLUDE_DIR}/modulus/types.hpp")
message(FATAL_ERROR "VAMP_MODULUS_INCLUDE_DIR must contain modulus/types.hpp: ${VAMP_RESOLVED_MODULUS_INCLUDE_DIR}") message(FATAL_ERROR "VAMP_MODULUS_INCLUDE_DIR must contain modulus/types.hpp: ${VAMP_RESOLVED_MODULUS_INCLUDE_DIR}")
endif() endif()
add_executable(product-vamp-pico2w add_executable(product_vamp_pico2w
src/vamp_style.cpp src/vamp_style.cpp
src/vamp_pico/display.cpp src/vamp_pico/display.cpp
src/vamp_pico/http_client.cpp src/vamp_pico/http_client.cpp
@@ -86,25 +87,26 @@ add_executable(product-vamp-pico2w
src/vamp_protocol/page_control.cpp src/vamp_protocol/page_control.cpp
src/vamp_protocol/gesture.cpp src/vamp_protocol/gesture.cpp
) )
vamp_add_pico_artwork(product-vamp-pico2w) vamp_add_pico_artwork(product_vamp_pico2w)
target_include_directories(product-vamp-pico2w PRIVATE vamp_add_theme(product_vamp_pico2w)
target_include_directories(product_vamp_pico2w PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
${VAMP_RESOLVED_MODULUS_INCLUDE_DIR} ${VAMP_RESOLVED_MODULUS_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include/vamp_pico/capi ${CMAKE_CURRENT_SOURCE_DIR}/include/vamp_pico/capi
) )
target_compile_definitions(product-vamp-pico2w PRIVATE target_compile_definitions(product_vamp_pico2w PRIVATE
VAMP_PICO_WIFI_SSID="${VAMP_PICO_WIFI_SSID}" VAMP_PICO_WIFI_SSID="${VAMP_PICO_WIFI_SSID}"
VAMP_PICO_WIFI_PASSPHRASE="${VAMP_PICO_WIFI_PASSPHRASE}" VAMP_PICO_WIFI_PASSPHRASE="${VAMP_PICO_WIFI_PASSPHRASE}"
VAMP_ZERO_HOST="${VAMP_ZERO_HOST}" VAMP_ZERO_HOST="${VAMP_ZERO_HOST}"
VAMP_ZERO_PORT=${VAMP_ZERO_PORT} VAMP_ZERO_PORT=${VAMP_ZERO_PORT}
) )
target_link_libraries(product-vamp-pico2w PRIVATE target_link_libraries(product_vamp_pico2w PRIVATE
pico_stdlib pico_stdlib
hardware_i2c hardware_i2c
hardware_spi hardware_spi
pico_cyw43_arch_lwip_threadsafe_background pico_cyw43_arch_lwip_threadsafe_background
) )
pico_enable_stdio_usb(product-vamp-pico2w 1) pico_enable_stdio_usb(product_vamp_pico2w 1)
pico_enable_stdio_uart(product-vamp-pico2w 0) pico_enable_stdio_uart(product_vamp_pico2w 0)
pico_add_extra_outputs(product-vamp-pico2w) pico_add_extra_outputs(product_vamp_pico2w)
+8 -4
View File
@@ -1,11 +1,11 @@
find_package(Python3 COMPONENTS Interpreter REQUIRED) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_python.cmake)
execute_process( execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import PIL.features; import fontTools.ttLib; assert PIL.features.check_feature('raqm')" COMMAND ${Python3_EXECUTABLE} -c "import PIL.features; import fontTools.ttLib; assert PIL.features.check_feature('raqm')"
RESULT_VARIABLE VAMP_PILLOW_STATUS RESULT_VARIABLE VAMP_PILLOW_STATUS
ERROR_QUIET ERROR_QUIET
) )
if(NOT VAMP_PILLOW_STATUS EQUAL 0) if(NOT VAMP_PILLOW_STATUS EQUAL 0)
message(FATAL_ERROR "Pico artwork generation requires Pillow with Raqm and fontTools. Install libfribidi0 on Debian/Ubuntu, then run: ${Python3_EXECUTABLE} -m pip install -r ${CMAKE_CURRENT_SOURCE_DIR}/generator/requirements.txt") message(FATAL_ERROR "Pico artwork requires Pillow with Raqm and fontTools. Install libfribidi0 and configure the Modulus assets Python group.")
endif() endif()
# Zero uses packaged font files; only the Pico embeds this fixed sprite atlas. # Zero uses packaged font files; only the Pico embeds this fixed sprite atlas.
@@ -14,11 +14,15 @@ function(vamp_add_pico_artwork target)
set(VAMP_PICO_ARTWORK_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/generated/pico/artwork.S) set(VAMP_PICO_ARTWORK_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/generated/pico/artwork.S)
add_custom_command( add_custom_command(
OUTPUT ${VAMP_PICO_ARTWORK_BINARY} OUTPUT ${VAMP_PICO_ARTWORK_BINARY}
COMMAND ${Python3_EXECUTABLE} -m generator.pico_artwork --static-output ${VAMP_PICO_ARTWORK_BINARY} COMMAND ${VAMP_PYTHON_COMMAND} -m generator.pico_artwork --static-output ${VAMP_PICO_ARTWORK_BINARY}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/generator/pico_artwork.py ${CMAKE_CURRENT_SOURCE_DIR}/generator/pico_artwork.py
${CMAKE_CURRENT_SOURCE_DIR}/generator/requirements.txt ${VAMP_SHARED_PYTHON_INPUTS}
${CMAKE_CURRENT_SOURCE_DIR}/generator/palette.json
${CMAKE_CURRENT_SOURCE_DIR}/generator/theme.py
${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_python.cmake
${CMAKE_CURRENT_SOURCE_DIR}/cmake/theme.cmake
${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/petaluma/PetalumaScript.otf ${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/petaluma/PetalumaScript.otf
VERBATIM VERBATIM
) )
+14
View File
@@ -0,0 +1,14 @@
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_python.cmake)
function(vamp_add_theme target)
set(header ${CMAKE_CURRENT_BINARY_DIR}/generated/vamp_style/palette.hpp)
add_custom_command(
OUTPUT ${header}
COMMAND ${VAMP_PYTHON_COMMAND} -m generator.theme --header ${header}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS generator/theme.py generator/palette.json ${VAMP_SHARED_PYTHON_INPUTS}
VERBATIM
)
target_sources(${target} PRIVATE ${header})
target_include_directories(${target} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/generated)
endfunction()
+1 -1
View File
@@ -7,4 +7,4 @@ set(VAMP_PICO_WIFI_SSID "Vamp Control" CACHE STRING "Default Pico Wi-Fi SSID")
set(VAMP_PICO_WIFI_PASSPHRASE "vamp-control" CACHE STRING "Default Pico Wi-Fi passphrase") set(VAMP_PICO_WIFI_PASSPHRASE "vamp-control" CACHE STRING "Default Pico Wi-Fi passphrase")
set(VAMP_ZERO_HOST "192.168.42.1" CACHE STRING "Default Zero control host for Pico firmware") set(VAMP_ZERO_HOST "192.168.42.1" CACHE STRING "Default Zero control host for Pico firmware")
set(VAMP_ZERO_PORT "8080" CACHE STRING "Default Zero control port for Pico firmware") set(VAMP_ZERO_PORT "8080" CACHE STRING "Default Zero control port for Pico firmware")
set(VAMP_ZERO_IMAGE_TAG "product-vamp-zero:dev" CACHE STRING "Docker image tag embedded in package metadata") set(VAMP_ZERO_IMAGE_TAG "product_vamp_zero:dev" CACHE STRING "Docker image tag embedded in package metadata")
+62 -50
View File
@@ -8,7 +8,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)
find_package(modulus CONFIG REQUIRED COMPONENTS boost_json openssl raylib) find_package(modulus CONFIG REQUIRED COMPONENTS boost_json openssl raylib)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(Python3 COMPONENTS Interpreter REQUIRED) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_python.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/theme.cmake)
find_package(ligo CONFIG REQUIRED) find_package(ligo CONFIG REQUIRED)
find_package(noto CONFIG REQUIRED) find_package(noto CONFIG REQUIRED)
find_package(luma CONFIG REQUIRED) find_package(luma CONFIG REQUIRED)
@@ -51,12 +52,13 @@ set(VAMP_STYLE_SOURCES
src/vamp_style.cpp src/vamp_style.cpp
) )
add_library(vamp-style STATIC ${VAMP_STYLE_SOURCES}) add_library(vamp_style STATIC ${VAMP_STYLE_SOURCES})
target_include_directories(vamp-style PUBLIC vamp_add_theme(vamp_style)
target_include_directories(vamp_style PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
) )
target_compile_features(vamp-style PUBLIC cxx_std_17) target_compile_features(vamp_style PUBLIC cxx_std_17)
target_link_libraries(vamp-style PUBLIC target_link_libraries(vamp_style PUBLIC
modulus::modulus modulus::modulus
) )
@@ -65,12 +67,12 @@ set(VAMP_PROTOCOL_SOURCES
src/vamp_protocol/page_control.cpp src/vamp_protocol/page_control.cpp
) )
add_library(vamp-protocol STATIC ${VAMP_PROTOCOL_SOURCES}) add_library(vamp_protocol STATIC ${VAMP_PROTOCOL_SOURCES})
target_include_directories(vamp-protocol PUBLIC target_include_directories(vamp_protocol PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
) )
target_compile_features(vamp-protocol PUBLIC cxx_std_17) target_compile_features(vamp_protocol PUBLIC cxx_std_17)
target_link_libraries(vamp-protocol PUBLIC target_link_libraries(vamp_protocol PUBLIC
modulus::modulus modulus::modulus
) )
@@ -97,18 +99,18 @@ set(VAMP_CORE_SOURCES
src/vamp_zero/ui.cpp src/vamp_zero/ui.cpp
) )
add_library(vamp-zero-core STATIC ${VAMP_CORE_SOURCES}) add_library(vamp_zero_core STATIC ${VAMP_CORE_SOURCES})
target_include_directories(vamp-zero-core PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated) target_include_directories(vamp_zero_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
target_include_directories(vamp-zero-core PUBLIC target_include_directories(vamp_zero_core PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
) )
target_compile_features(vamp-zero-core PUBLIC cxx_std_23) target_compile_features(vamp_zero_core PUBLIC cxx_std_23)
target_compile_definitions(vamp-zero-core PUBLIC target_compile_definitions(vamp_zero_core PUBLIC
VAMP_GIT_SHA="${VAMP_GIT_SHA}" VAMP_GIT_SHA="${VAMP_GIT_SHA}"
) )
target_link_libraries(vamp-zero-core PUBLIC target_link_libraries(vamp_zero_core PUBLIC
vamp-style vamp_style
vamp-protocol vamp_protocol
modulus::modulus modulus::modulus
modulus::boost_json modulus::boost_json
modulus::openssl modulus::openssl
@@ -120,9 +122,9 @@ target_link_libraries(vamp-zero-core PUBLIC
PRIVATE modulus::raylib PRIVATE modulus::raylib
) )
add_executable(vamp-zero src/vamp_zero/main.cpp) add_executable(vamp_zero src/vamp_zero/main.cpp)
target_compile_features(vamp-zero PRIVATE cxx_std_23) target_compile_features(vamp_zero PRIVATE cxx_std_23)
target_link_libraries(vamp-zero PRIVATE vamp-zero-core) target_link_libraries(vamp_zero PRIVATE vamp_zero_core)
if(BUILD_TESTING) if(BUILD_TESTING)
add_subdirectory(tests) add_subdirectory(tests)
@@ -137,26 +139,29 @@ file(GLOB_RECURSE VAMP_GENERATOR_INPUTS CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/data/expected/*.json ${CMAKE_CURRENT_SOURCE_DIR}/data/expected/*.json
${CMAKE_CURRENT_SOURCE_DIR}/generator/*.py ${CMAKE_CURRENT_SOURCE_DIR}/generator/*.py
${CMAKE_CURRENT_SOURCE_DIR}/generator/**/*.py ${CMAKE_CURRENT_SOURCE_DIR}/generator/**/*.py
${CMAKE_CURRENT_SOURCE_DIR}/generator/requirements.txt ${VAMP_SHARED_PYTHON_INPUTS}
${CMAKE_CURRENT_SOURCE_DIR}/generator/palette.json
${CMAKE_CURRENT_SOURCE_DIR}/generator/theme.py
${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_python.cmake
${CMAKE_CURRENT_SOURCE_DIR}/cmake/theme.cmake
${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/* ${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/*
${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/**/* ${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/**/*
) )
set(VAMP_GENERATED_ASSETS_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated-assets) set(VAMP_GENERATED_ASSETS_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated_assets)
set(VAMP_GENERATED_ASSETS_STAMP ${CMAKE_CURRENT_BINARY_DIR}/generated-assets.stamp) set(VAMP_GENERATED_ASSETS_STAMP ${CMAKE_CURRENT_BINARY_DIR}/generated_assets.stamp)
set(VAMP_ENGRAVING_JOBS 2 CACHE STRING "Maximum parallel Python engraving workers (1-16)") set(VAMP_ENGRAVING_JOBS 2 CACHE STRING "Maximum parallel Python engraving workers (1-16)")
set(VAMP_ENGRAVING_CACHE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/build/build_common/engraving-cache" CACHE PATH "Shared build-only page cache; empty disables caching") set(VAMP_ENGRAVING_CACHE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/build/build_common/engraving_cache" CACHE PATH "Shared build-only page cache; empty disables caching")
set(VAMP_ENGRAVING_CACHE_ARGS) set(VAMP_ENGRAVING_CACHE_ARGS)
if(VAMP_ENGRAVING_CACHE_DIR) if(VAMP_ENGRAVING_CACHE_DIR)
list(APPEND VAMP_ENGRAVING_CACHE_ARGS --engraving-cache "${VAMP_ENGRAVING_CACHE_DIR}") list(APPEND VAMP_ENGRAVING_CACHE_ARGS --engraving-cache "${VAMP_ENGRAVING_CACHE_DIR}")
endif() endif()
string(JOIN "\n" VAMP_GENERATOR_INPUT_MANIFEST ${VAMP_GENERATOR_INPUTS}) string(JOIN "\n" VAMP_GENERATOR_INPUT_MANIFEST ${VAMP_GENERATOR_INPUTS})
file(CONFIGURE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated-assets.inputs file(CONFIGURE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated_assets.inputs
CONTENT "@VAMP_GENERATOR_INPUT_MANIFEST@\n" @ONLY) CONTENT "@VAMP_GENERATOR_INPUT_MANIFEST@\n" @ONLY)
add_custom_command( add_custom_command(
OUTPUT ${VAMP_GENERATED_ASSETS_STAMP} OUTPUT ${VAMP_GENERATED_ASSETS_STAMP}
BYPRODUCTS ${VAMP_GENERATED_ASSETS_DIR}/catalog.db BYPRODUCTS ${VAMP_GENERATED_ASSETS_DIR}/catalog.db
COMMAND ${Python3_EXECUTABLE} COMMAND ${VAMP_PYTHON_COMMAND}
-u
-m generator -m generator
--data-dir ${CMAKE_CURRENT_SOURCE_DIR}/data --data-dir ${CMAKE_CURRENT_SOURCE_DIR}/data
--output-dir ${VAMP_GENERATED_ASSETS_DIR} --output-dir ${VAMP_GENERATED_ASSETS_DIR}
@@ -167,30 +172,30 @@ add_custom_command(
${VAMP_ENGRAVING_CACHE_ARGS} ${VAMP_ENGRAVING_CACHE_ARGS}
COMMAND ${CMAKE_COMMAND} -E touch ${VAMP_GENERATED_ASSETS_STAMP} COMMAND ${CMAKE_COMMAND} -E touch ${VAMP_GENERATED_ASSETS_STAMP}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${VAMP_GENERATOR_INPUTS} ${CMAKE_CURRENT_BINARY_DIR}/generated-assets.inputs DEPENDS ${VAMP_GENERATOR_INPUTS} ${CMAKE_CURRENT_BINARY_DIR}/generated_assets.inputs
USES_TERMINAL USES_TERMINAL
VERBATIM VERBATIM
) )
add_custom_target(vamp-generated-assets ALL DEPENDS ${VAMP_GENERATED_ASSETS_STAMP}) add_custom_target(vamp_generated_assets ALL DEPENDS ${VAMP_GENERATED_ASSETS_STAMP})
if(BUILD_TESTING) if(BUILD_TESTING)
add_dependencies(product-vamp-test vamp-generated-assets) add_dependencies(product_vamp_test vamp_generated_assets)
endif() endif()
install(TARGETS vamp-zero install(TARGETS vamp_zero
RUNTIME DESTINATION zero/bin RUNTIME DESTINATION zero/bin
) )
install(FILES package/config/vamp.example.json DESTINATION zero/config) install(FILES package/config/vamp.example.json DESTINATION zero/config)
install(DIRECTORY ${VAMP_GENERATED_ASSETS_DIR}/ DESTINATION zero/share/product-vamp install(DIRECTORY ${VAMP_GENERATED_ASSETS_DIR}/ DESTINATION zero/share/product_vamp
PATTERN "*.json" EXCLUDE PATTERN "analysis" EXCLUDE) PATTERN "*.json" EXCLUDE PATTERN "analysis" EXCLUDE)
install(PROGRAMS install(PROGRAMS
package/zero/install-zero.sh package/zero/install_zero.sh
package/zero/start-ap.sh package/zero/start_ap.sh
package/zero/start-zero.sh package/zero/start_zero.sh
DESTINATION zero DESTINATION zero
) )
install(FILES install(FILES
package/zero/product-vamp-ap.service package/zero/product_vamp_ap.service
package/zero/product-vamp-zero.service package/zero/product_vamp_zero.service
DESTINATION zero/systemd DESTINATION zero/systemd
) )
install(FILES package/pico/wiring.md DESTINATION pico) install(FILES package/pico/wiring.md DESTINATION pico)
@@ -203,23 +208,23 @@ endif()
if(VAMP_BUILD_ZERO_IMAGE) if(VAMP_BUILD_ZERO_IMAGE)
find_program(DOCKER_EXECUTABLE docker REQUIRED) find_program(DOCKER_EXECUTABLE docker REQUIRED)
set(VAMP_ZERO_IMAGE_CONTEXT ${CMAKE_CURRENT_BINARY_DIR}/zero-image-context) set(VAMP_ZERO_IMAGE_CONTEXT ${CMAKE_CURRENT_BINARY_DIR}/zero_image_context)
set(VAMP_ZERO_IMAGE_TAR ${CMAKE_CURRENT_BINARY_DIR}/product-vamp-zero-linux-arm64.docker.tar) set(VAMP_ZERO_IMAGE_TAR ${CMAKE_CURRENT_BINARY_DIR}/product_vamp_zero_linux_arm64.docker.tar)
add_custom_command( add_custom_command(
OUTPUT ${VAMP_ZERO_IMAGE_TAR} OUTPUT ${VAMP_ZERO_IMAGE_TAR}
COMMAND ${CMAKE_COMMAND} -E rm -rf ${VAMP_ZERO_IMAGE_CONTEXT} COMMAND ${CMAKE_COMMAND} -E rm -rf ${VAMP_ZERO_IMAGE_CONTEXT}
COMMAND ${CMAKE_COMMAND} -E make_directory ${VAMP_ZERO_IMAGE_CONTEXT}/usr/bin COMMAND ${CMAKE_COMMAND} -E make_directory ${VAMP_ZERO_IMAGE_CONTEXT}/usr/bin
COMMAND ${CMAKE_COMMAND} -E make_directory ${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product-vamp COMMAND ${CMAKE_COMMAND} -E make_directory ${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product_vamp
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:vamp-zero> ${VAMP_ZERO_IMAGE_CONTEXT}/usr/bin/vamp-zero COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:vamp_zero> ${VAMP_ZERO_IMAGE_CONTEXT}/usr/bin/vamp_zero
COMMAND ${CMAKE_COMMAND} -E copy package/zero/Dockerfile ${VAMP_ZERO_IMAGE_CONTEXT}/Dockerfile COMMAND ${CMAKE_COMMAND} -E copy package/zero/Dockerfile ${VAMP_ZERO_IMAGE_CONTEXT}/Dockerfile
COMMAND ${CMAKE_COMMAND} -DSOURCE=${VAMP_GENERATED_ASSETS_DIR} -DDESTINATION=${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product-vamp -P cmake/runtime_assets.cmake COMMAND ${CMAKE_COMMAND} -DSOURCE=${VAMP_GENERATED_ASSETS_DIR} -DDESTINATION=${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product_vamp -P cmake/runtime_assets.cmake
COMMAND ${CMAKE_COMMAND} -E make_directory ${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product-vamp/config COMMAND ${CMAKE_COMMAND} -E make_directory ${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product_vamp/config
COMMAND ${CMAKE_COMMAND} -E copy package/config/vamp.example.json ${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product-vamp/config/vamp.example.json COMMAND ${CMAKE_COMMAND} -E copy package/config/vamp.example.json ${VAMP_ZERO_IMAGE_CONTEXT}/usr/share/product_vamp/config/vamp.example.json
COMMAND ${BASH_EXECUTABLE} package/scripts/build_zero_image.sh ${VAMP_ZERO_IMAGE_CONTEXT} ${VAMP_ZERO_IMAGE_TAR} ${VAMP_ZERO_IMAGE_TAG} COMMAND ${BASH_EXECUTABLE} package/scripts/build_zero_image.sh ${VAMP_ZERO_IMAGE_CONTEXT} ${VAMP_ZERO_IMAGE_TAR} ${VAMP_ZERO_IMAGE_TAG}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS DEPENDS
vamp-zero vamp_zero
package/scripts/build_zero_image.sh package/scripts/build_zero_image.sh
package/zero/Dockerfile package/zero/Dockerfile
package/config/vamp.example.json package/config/vamp.example.json
@@ -227,19 +232,23 @@ if(VAMP_BUILD_ZERO_IMAGE)
${VAMP_GENERATED_ASSETS_STAMP} ${VAMP_GENERATED_ASSETS_STAMP}
VERBATIM VERBATIM
) )
add_custom_target(vamp-zero-image ALL DEPENDS ${VAMP_ZERO_IMAGE_TAR}) add_custom_target(vamp_zero_image ALL DEPENDS ${VAMP_ZERO_IMAGE_TAR})
install(FILES ${VAMP_ZERO_IMAGE_TAR} DESTINATION zero) install(FILES ${VAMP_ZERO_IMAGE_TAR} DESTINATION zero)
endif() endif()
if(VAMP_BUILD_PICO_FIRMWARE) if(VAMP_BUILD_PICO_FIRMWARE)
set(VAMP_PICO_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/pico-build) set(VAMP_PICO_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/pico_build)
set(VAMP_PICO_UF2 ${CMAKE_CURRENT_BINARY_DIR}/product-vamp-pico2w.uf2) set(VAMP_PICO_UF2 ${CMAKE_CURRENT_BINARY_DIR}/product_vamp_pico2w.uf2)
file(GLOB VAMP_PICO_SOURCES CONFIGURE_DEPENDS file(GLOB VAMP_PICO_SOURCES CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
${CMAKE_CURRENT_SOURCE_DIR}/cmake/pico.cmake ${CMAKE_CURRENT_SOURCE_DIR}/cmake/pico.cmake
${CMAKE_CURRENT_SOURCE_DIR}/cmake/pico_artwork.cmake ${CMAKE_CURRENT_SOURCE_DIR}/cmake/pico_artwork.cmake
${CMAKE_CURRENT_SOURCE_DIR}/generator/pico_artwork.py ${CMAKE_CURRENT_SOURCE_DIR}/generator/pico_artwork.py
${CMAKE_CURRENT_SOURCE_DIR}/generator/requirements.txt ${VAMP_SHARED_PYTHON_INPUTS}
${CMAKE_CURRENT_SOURCE_DIR}/generator/palette.json
${CMAKE_CURRENT_SOURCE_DIR}/generator/theme.py
${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_python.cmake
${CMAKE_CURRENT_SOURCE_DIR}/cmake/theme.cmake
${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/petaluma/PetalumaScript.otf ${CMAKE_CURRENT_SOURCE_DIR}/package/assets/fonts/petaluma/PetalumaScript.otf
${CMAKE_CURRENT_SOURCE_DIR}/cmake/vamp_options.cmake ${CMAKE_CURRENT_SOURCE_DIR}/cmake/vamp_options.cmake
${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp
@@ -265,12 +274,15 @@ if(VAMP_BUILD_PICO_FIRMWARE)
"${VAMP_ZERO_HOST}" "${VAMP_ZERO_HOST}"
"${VAMP_ZERO_PORT}" "${VAMP_ZERO_PORT}"
"${modulus_INCLUDE_DIR}" "${modulus_INCLUDE_DIR}"
"${Python3_EXECUTABLE}"
"${MODULUS_PYTHON_RUNNER}"
"${MODULUS_PYTHON_ROOT}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS DEPENDS
package/scripts/build_pico_firmware.sh package/scripts/build_pico_firmware.sh
${VAMP_PICO_SOURCES} ${VAMP_PICO_SOURCES}
VERBATIM VERBATIM
) )
add_custom_target(vamp-pico-firmware ALL DEPENDS ${VAMP_PICO_UF2}) add_custom_target(vamp_pico_firmware ALL DEPENDS ${VAMP_PICO_UF2})
install(FILES ${VAMP_PICO_UF2} DESTINATION pico) install(FILES ${VAMP_PICO_UF2} DESTINATION pico)
endif() endif()
+3 -2
View File
@@ -3,7 +3,7 @@ import conan.tools.build
import conan.tools.cmake import conan.tools.cmake
class product_vamp_conan(conan.ConanFile): class product_vamp_conan(conan.ConanFile):
name = "product-vamp" name = "product_vamp"
version = "latest" version = "latest"
description = "Two-device sheet-music display and guitar remote" description = "Two-device sheet-music display and guitar remote"
license = "MIT" license = "MIT"
@@ -36,8 +36,8 @@ class product_vamp_conan(conan.ConanFile):
"tests/fixtures/*", "tests/fixtures/*",
"tests/fixtures/**/*", "tests/fixtures/**/*",
"generator/*.py", "generator/*.py",
"generator/palette.json",
"generator/**/*.py", "generator/**/*.py",
"generator/requirements.txt",
"include/*.hpp", "include/*.hpp",
"include/vamp_style/*.hpp", "include/vamp_style/*.hpp",
"src/vamp_pico/*.S.in", "src/vamp_pico/*.S.in",
@@ -89,6 +89,7 @@ class product_vamp_conan(conan.ConanFile):
if hasattr(toolchain, "user_presets_path"): if hasattr(toolchain, "user_presets_path"):
toolchain.user_presets_path = False toolchain.user_presets_path = False
toolchain.variables["BUILD_TESTING"] = bool(self.options.with_tests) toolchain.variables["BUILD_TESTING"] = bool(self.options.with_tests)
toolchain.variables.update(self.python_requires["modulus"].module.python_cmake_variables(self))
toolchain.generate() toolchain.generate()
def build(self): def build(self):
cmake = conan.tools.cmake.CMake(self) cmake = conan.tools.cmake.CMake(self)
+12 -12
View File
@@ -1,6 +1,6 @@
{ {
"autumn-leaves-acoustic.json": { "autumn_leaves_acoustic.json": {
"title": "autumn-leaves", "title": "autumn_leaves",
"composer": "", "composer": "",
"key_signature": "D minor", "key_signature": "D minor",
"part_type": "acoustic", "part_type": "acoustic",
@@ -2071,8 +2071,8 @@
} }
] ]
}, },
"autumn-leaves-electric.json": { "autumn_leaves_electric.json": {
"title": "autumn-leaves", "title": "autumn_leaves",
"composer": "", "composer": "",
"key_signature": "D minor", "key_signature": "D minor",
"part_type": "electric", "part_type": "electric",
@@ -4143,8 +4143,8 @@
} }
] ]
}, },
"autumn-leaves-vocal.json": { "autumn_leaves_vocal.json": {
"title": "autumn-leaves", "title": "autumn_leaves",
"composer": "", "composer": "",
"key_signature": "D minor", "key_signature": "D minor",
"part_type": "vocal", "part_type": "vocal",
@@ -6856,8 +6856,8 @@
} }
] ]
}, },
"it-could-happen-to-you-acoustic.json": { "it_could_happen_to_you_acoustic.json": {
"title": "it-could-happen-to-you", "title": "it_could_happen_to_you",
"composer": "", "composer": "",
"key_signature": "Eb", "key_signature": "Eb",
"part_type": "acoustic", "part_type": "acoustic",
@@ -9154,8 +9154,8 @@
} }
] ]
}, },
"it-could-happen-to-you-electric.json": { "it_could_happen_to_you_electric.json": {
"title": "it-could-happen-to-you", "title": "it_could_happen_to_you",
"composer": "", "composer": "",
"key_signature": "Eb", "key_signature": "Eb",
"part_type": "electric", "part_type": "electric",
@@ -11452,8 +11452,8 @@
} }
] ]
}, },
"it-could-happen-to-you-vocal.json": { "it_could_happen_to_you_vocal.json": {
"title": "it-could-happen-to-you", "title": "it_could_happen_to_you",
"composer": "", "composer": "",
"key_signature": "Eb", "key_signature": "Eb",
"part_type": "vocal", "part_type": "vocal",

Some files were not shown because too many files have changed in this diff Show More