+17
-25
@@ -9,8 +9,8 @@ Product Vamp follows the Modulus product layout used by the khodo repos:
|
||||
- `include/vamp_protocol` and `src/vamp_protocol`: shared route and page-command contracts used by both devices.
|
||||
- `cmake/pico.cmake` and `cmake/zero.cmake`: device-specific build definitions selected by the root `CMakeLists.txt`.
|
||||
- `include/vamp_style.hpp` and `src/vamp_style.cpp`: shared visual palette. `include/vamp_pico/artwork.hpp` defines the Pico coverage-image and sprite interfaces; fixed artwork is generated in the build directory.
|
||||
- `data`: authored MusicXML for performance songs, practice exercises, and accompaniment rhythms, plus the generator regression summary.
|
||||
- `generator`: build-time Python rhythm planning, part generation, guitar placement, scat lyrics, engraving, audio compilation, Pico artwork, and catalog construction.
|
||||
- `data`: authored MusicXML for performance songs and accompaniment rhythms, plus the generator regression summary.
|
||||
- `generator`: build-time Python rhythm planning, part generation, guitar placement, engraving, audio compilation, Pico artwork, and catalog construction.
|
||||
- `package`: generic bundle inputs, scripts, config, static font assets, and device install files.
|
||||
|
||||
## Zero Runtime
|
||||
@@ -36,11 +36,9 @@ The AP setup pins NetworkManager to 2.4 GHz channel 6 with WPA2/RSN and CCMP cip
|
||||
|
||||
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. Performance navigation is Song -> Part -> Score; practice is Exercise -> Part -> Score. Both offer 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 Songs, Playback, Metronome, and Tempo. Song -> Part -> Score navigation offers Acoustic, Electric, and Vocal without key selection. Musical interpretation, spacing, beaming, ties, accidentals, and pagination happen on the build host. The Zero displays engraved PNGs and retains at most two page textures.
|
||||
|
||||
Runtime catalogs contain immutable metadata shared by navigation, the HTTP service, and the UI. Song groups reference canonical records by index; practice is a flat, numerically ordered exercise list. SQLite retains complete bundles, while cached measure/page counts let menu queries avoid reading musical JSON. Python constructs the complete database before deployment; metadata and embedded JSON are published together. There are no custom rows, runtime imports, metadata-repair triggers, or persisted page-state tables. Selecting a score loads only that bundle asynchronously, discarding stale results after navigation; the view keeps at most two page textures.
|
||||
|
||||
Practice exercise IDs are the four-digit source stems, including `0000` when present. Part IDs append the part name, such as `0001-electric`. Duplicate titles and identical placeholder music remain independent exercises. The catalog stores exercise identity and part metadata without practice categories, lesson groups, or key variants. Every exercise must have all three parts. Older catalogs must be rebuilt or reinstalled, not migrated.
|
||||
Runtime catalogs contain immutable metadata shared by navigation, the HTTP service, and the UI. Song groups reference canonical records by index. SQLite retains complete bundles, while cached measure/page counts let menu queries avoid reading musical JSON. Python constructs the complete database in one transaction before deployment; metadata and embedded JSON are published together after an integrity check. The single `songs` table uses strict column types, primary-key and path uniqueness constraints, and JSON validity checks. Menu rows are ordered by title, composer, part, and ID with binary collation, using the matching `songs_menu` index; C++ groups the ordered rows in one pass without sorting them again. Noto provides read-only connections, bound score lookups, application-ID checks, and quick integrity checks. Product-specific schema and asset-path validation remain in the application. There are no custom rows, runtime imports, metadata-repair triggers, or persisted page-state tables. Selecting a score loads only that bundle asynchronously, discarding stale results after navigation; the view keeps at most two page textures.
|
||||
|
||||
`--once` (also `runtime.exit_after_startup`) is a headless readiness check: it checks database integrity and validates the catalog and referenced page assets, checks runtime-directory writes and the configured server bind, and exits without opening audio/display devices or publishing a readiness heartbeat. Page checks include containment, checksums, CPU-only PNG decoding, and exact 976x484 dimensions. The configured heartbeat destination must be absent or a regular file, never a directory, symlink, or special file. Runtime writes and readiness probes use exclusive sibling temporary files; readiness leaves an existing heartbeat unchanged and removes its probes. `--health-check` instead probes the running service's read-only `/api/health` endpoint using the configured timeout. A stopped/unresponsive service or an invalid response fails the check; it does not create or repair files. `--check-config` remains configuration-only.
|
||||
|
||||
@@ -50,11 +48,11 @@ Zero and Pico share one gesture state machine: a second release within 220 ms of
|
||||
|
||||
## Score Generation
|
||||
|
||||
The build runs `python3 -m generator` against `data/` and writes build-only assets under the CMake binary tree. Every song and exercise 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 `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.
|
||||
|
||||
Practice sources live directly in `data/practice/` with four-digit ASCII filenames, currently `0001.xml` through `0366.xml`; `0000.xml` is also valid. For example, `data/practice/0001.xml` generates `practice/0001/{acoustic,electric,vocal}.json` and corresponding page directories. Songs stay in `data/songs/`. The filename number determines exercise identity and ordering; MusicXML titles determine labels, falling back to the number. Root-level XML, other practice filenames, and nested practice directories are rejected. Adding, renaming, or removing a source requires no registry edit. Regeneration replaces the output tree and catalog, removing old three-digit paths and IDs without aliases or migration. The current 366 exercises produce 1,098 practice parts, plus six performance 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.
|
||||
|
||||
The pipeline is Soundslice/MuseScore -> uncompressed `.xml` -> musical model and rhythm plans -> part generation -> semantic JSON, audio, engraved pages, and Pico title artwork -> packaged SQLite catalog and assets. Export MusicXML 3.1 or 4.0, `score-partwise`, with one part and one staff. The musical model retains independent voices and per-tone durations/ties; automatic practice generation additionally requires a single monophonic melody and optional structured harmonies. Packaged music describes each generated part, not an archival copy of the XML.
|
||||
The pipeline is Soundslice/MuseScore -> uncompressed `.xml` -> musical model and rhythm plans -> part generation -> semantic JSON, audio, engraved pages, and Pico title artwork -> packaged SQLite catalog and assets. Export MusicXML 3.1 or 4.0, `score-partwise`, with one part and one staff. The musical model retains independent voices and per-tone durations/ties. Packaged music describes each generated part, not an archival copy of the XML.
|
||||
|
||||
### Rhythm Sources
|
||||
|
||||
@@ -102,7 +100,7 @@ Parenthesized chord symbols mark optional turnarounds, including Soundslice's te
|
||||
|
||||
### Linear Reading Order
|
||||
|
||||
There is no repeat analyzer, expansion pass, or repeat/ending engraver. Practice reads the source once in authored order. Performance retains the seven-section arranger, writing every section explicitly onto successive pages; neither display nor playback jumps backward.
|
||||
There is no repeat analyzer, expansion pass, or repeat/ending engraver. Performance retains the seven-section arranger, writing every section explicitly onto successive pages; neither display nor playback jumps backward.
|
||||
|
||||
Repeat signs, numbered endings, D.C./D.S./coda navigation, and `time-only` ties fail import rather than being ignored or expanded. Write the intended notes and rests explicitly, including any alternate ending. Legacy repeat fields in generated musical documents are rejected; regenerate assets instead of retaining a compatibility interpreter. Full leading-rest measures remain full measures, not inferred pickups. Missing titles fall back to filenames and missing composer credits remain empty, as with the current Soundslice exports.
|
||||
|
||||
@@ -122,17 +120,15 @@ Measure durations, event onsets/durations, harmony offsets, span times, and pitc
|
||||
|
||||
### Shared Part Generation
|
||||
|
||||
`parts.py` owns shared source validation, melody-copying, measure construction, rests, and solo slashes. `rhythm.py` loads snippets; `accompaniment.py` plans phrase cycles and realizes independent bass/upper voices or electric stabs. Before guitar placement, `acoustic.py` shortens generated bass/upper attacks so they cannot overlap and groups them into continuous harmony spans; afterward it combines them into one notated voice. `guitar.py` selects one complete grip per acoustic span using its shared candidate generation and placement search. `melody.py` identifies long tied melody runs and splits them at harmony changes before guitar placement. `arrange/generate.py` supplies the existing seven-section song form; `practice/generate.py` supplies a single source pass. `pitch.py` resolves source instrument transposition and the shared written-octave convention. There is no generic twelve-key transposer or separate practice fretboard solver.
|
||||
`parts.py` owns shared source validation, melody-copying, measure construction, rests, and solo slashes. `rhythm.py` loads snippets; `accompaniment.py` plans phrase cycles and realizes independent bass/upper voices or electric stabs. Before guitar placement, `acoustic.py` shortens generated bass/upper attacks so they cannot overlap and groups them into continuous harmony spans; afterward it combines them into one notated voice. `guitar.py` selects one complete grip per acoustic span using its shared candidate generation and placement search. `melody.py` identifies long tied melody runs and splits them at harmony changes before guitar placement. `arrange/generate.py` supplies the existing seven-section song form. `pitch.py` resolves source instrument transposition and the shared written-octave convention. There is no generic twelve-key transposer.
|
||||
|
||||
Generated accompaniment has no ties. Acoustic attacks release at the earliest of their planned end, the next combined bass/upper attack, or the barline. Simultaneous bass/upper attacks use the shorter planned duration for the entire grip. If a binary duration would need ties, it shortens to the longest conventional single note that fits, followed by silence; no extra attack or artificial tuplet is introduced. Rhythm-snippet continuations are silent, and the planner does not count them as acoustic coverage. After placement, consolidation preserves the prepared timing and TAB assignments, combines simultaneous tones under one stem, and writes rests only for complete silence. A silent full bar has one measure rest. Old lane stem directions and beam groups are removed in favor of single-voice engraving. Authored melody ties, solo slashes, and electric melody harmonization remain separate from this build-time accompaniment policy.
|
||||
|
||||
Practice acoustic accompaniment is derived from structured harmony roots, kinds, degrees, inversions, and slash basses using the same generation rules as songs. A missing harmony or N.C. produces rests, not guessed chords. Electric retains the melody's register and spelling while adding playable chords to long notes. Vocal retains authored notes and receives scat lyrics. Song vocal lyrics are unchanged. Practice optional-turnaround harmonies are played once, without creating extra sections.
|
||||
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, Guitar Solo, Guitar 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 in songs and practice 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, Guitar Solo, Guitar 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`, `practice_document`, `generate_performance_scores`, and `generate_practice_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, for both songs and practice, 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. Practice and 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. For practice, written C4 sounds C3 (A-string fret 3), and written C#6 sounds C#5, within the 0-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 0-20 fret range. Generated accompaniment is notated using the same convention.
|
||||
|
||||
`pitch.py` resolves explicit MusicXML instrument transposition once before part generation. Explicit zero transposition is also authoritative. Octave-clef exports already encode sounding pitch in their note values, so they do not receive another implicit octave subtraction. Generated parts carry an explicit `transpose.octave_change: -1`; guitar placement and audio read that shared state. Internal concert-pitch normalization and written-pitch conversion cancel for ordinary source notation except for the intentional song electric melody octave increase. There is no automatic octave fitting, vocal-specific range shift, or key transposition.
|
||||
|
||||
@@ -146,19 +142,15 @@ Electric chord blocks use strings 1-3 or 1-4, including string 1. Rootless voici
|
||||
|
||||
Placement uses the ordered `VoicingCost` components: electric string shortfall from four, melody covered, number of tones above the melody, missing root, physical effort, and missing optional pitch classes. These costs accumulate over the whole passage, so movement cannot outweigh the earlier musical priorities. Melody height compares sounding pitch; unison doubling does not cover it. Electric accompaniment uses the same ranking without the melody-height terms. Acoustic does not receive electric fullness/root penalties: a simpler three-note grip can beat a more awkward four-note grip while satisfying its hard harmonic requirements.
|
||||
|
||||
`electric.py` links song accompaniment attacks to harmonized melody events using authored source positions, not displayed measure numbers or a song-wide chord dictionary. References stay inside the corresponding source harmony span and must match its structured chord. Within a span, an attack uses the latest available melody grip at or before its source position, or the first later grip if it precedes all melody chords. This allows different shapes for the same chord in different passages. After guitar placement and melody tie resolution, linked stabs copy the actual melody pitches and TAB positions while retaining their own IDs, timing, and written durations; melody ties and markings are not copied. A melody that remains a single note provides no grip. Passages without a matching playable three- or four-string melody chord, including altered endings without a corresponding reference, retain independently generated electric shapes. No reference metadata reaches runtime JSON, and practice electric remains the melody part without an added accompaniment section.
|
||||
`electric.py` links song accompaniment attacks to harmonized melody events using authored source positions, not displayed measure numbers or a song-wide chord dictionary. References stay inside the corresponding source harmony span and must match its structured chord. Within a span, an attack uses the latest available melody grip at or before its source position, or the first later grip if it precedes all melody chords. This allows different shapes for the same chord in different passages. After guitar placement and melody tie resolution, linked stabs copy the actual melody pitches and TAB positions while retaining their own IDs, timing, and written durations; melody ties and markings are not copied. A melody that remains a single note provides no grip. Passages without a matching playable three- or four-string melody chord, including altered endings without a corresponding reference, retain independently generated electric shapes. No reference metadata reaches runtime JSON.
|
||||
|
||||
Physical effort is `movement + 2 * max(0, highest_fret - 12) + fret_span` for electric and exact melody placement, and `movement + 2 * max(0, highest_fret - 12) + 3 * fret_span + 2 * (distinct_frets - 1)` for acoustic. The shared `PREFERRED_MAX_FRET` is 12: height alone adds no cost within frets 0-12 inclusive, then adds two points per excess fret through the hard maximum of 20. This is a soft contribution to effort, not a range priority overriding musical correctness, grip simplicity, or movement. Movement is the Manhattan distance between doubled fret/string position centers. Open strings count in every fret measurement. Acoustic shape costs are charged once per harmony span, independent of rhythm density, and transitions compare complete grips rather than whichever lane last sounded. Pure melody tie continuations add no fresh shape or movement cost. There is no automatic octave fitting. Candidate pruning and the exact small-grid distance transform share this ranking; held notes keep their occupied strings and all compatible candidate states. Stable position ordering resolves equal costs.
|
||||
|
||||
There is no finger assignment, barre logic, or predefined chord-shape dictionary. These are proximity and fretboard-feasibility constraints, not a guarantee of comfortable fingering. Nonstandard tuning, capos, microtonal frets, and unplayable passages fail with source/part/event context; there is no slash fallback for a failed chord. Existing assets remain untouched after failure.
|
||||
|
||||
### Practice Scat
|
||||
|
||||
`scat.py` generates one lyric verse from the authored melody without changing notes or rhythm. Every untied attack receives one syllable: `doo` for a duration at least the local beat, `dat` for a shorter phrase ending, `dit` for a short offbeat, and `ba` for another short attack. Consecutive matching short syllables alternate with `da`. Phrase boundaries come from gaps/rests, section labels, and the score end, not every barline. Compound/additive meters and pickups determine beat placement; unmetered music uses duration and phrase endings without inventing accents. Grace attacks receive short syllables; cue notes do not. Tied chains receive one syllable with an extender ending at the chain's actual end, including across pages. There is no randomness or vocal range/octave adaptation.
|
||||
|
||||
### Playback Boundary
|
||||
|
||||
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 or exercise 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.
|
||||
|
||||
@@ -168,7 +160,7 @@ When grace groups steal from both ends of one donor, their adjustments accumulat
|
||||
|
||||
At runtime the shared on-demand score loader serves both display and audio. A background worker validates and loads only the selected score's samples, converts absolute beat positions to rounded sample frames at the menu BPM, and prepares a Sonus sequence. Pitch does not change with BPM, and rounding absolute positions avoids cumulative timing drift. The I/O loop publishes the prepared sequence and polls completion without doing disk I/O or musical interpretation. Sonus mixes preallocated voices from immutable PCM and retires them on control threads; the callback never synthesizes tones or loads files. Limits are one million events, 512 distinct samples, 64 MiB decoded samples per selection, 64 simultaneous voices including releases, and 24 hours at the selected BPM. Exceeding them reports an error rather than dropping notes.
|
||||
|
||||
`audio.playback_enabled` defaults to false. The obsolete `track_enabled` setting is rejected with a diagnostic naming its replacement. Playback suppresses the metronome without changing its stored toggle. Authored tempo marks remain notation; menu BPM is constant throughout a session. Selecting a practice or performance part starts a new session. Manual page changes never restart or seek. Natural completion, including trailing silence/release, returns to the corresponding part menu with its selection preserved. Leaving the score cancels playback; stale results cannot close a newer score. Errors remain visible rather than being mistaken for completion.
|
||||
`audio.playback_enabled` defaults to false. The obsolete `track_enabled` setting is rejected with a diagnostic naming its replacement. Playback suppresses the metronome without changing its stored toggle. Authored tempo marks remain notation; menu BPM is constant throughout a session. Selecting a part starts a new session. Manual page changes never restart or seek. Natural completion, including trailing silence/release, returns to the corresponding part menu with its selection preserved. Leaving the score cancels playback; stale results cannot close a newer score. Errors remain visible rather than being mistaken for completion.
|
||||
|
||||
`--once` additionally validates referenced audio without opening display/audio hardware. Runtime packages contain WAVs alongside pages and the SQLite catalog; the source XML and Python dependencies remain build-only.
|
||||
|
||||
@@ -178,11 +170,11 @@ At runtime the shared on-demand score loader serves both display and audio. A ba
|
||||
|
||||
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 exercise 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.
|
||||
|
||||
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 and practice generation enforce 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.
|
||||
|
||||
Generated guitar tones carry `tablature: {string, fret}`; their staff is marked `tablature: true`. `engraving/tablature.py` draws a six-line TAB view below standard notation, sharing onset columns, barlines, and page breaks. Both views belong to one system and never duplicate the semantic timeline or playback events. Standard notation carries rhythm, rests, and tuplets; TAB carries positions, solo slashes, and tied continuations without rest glyphs. Strings and frets are validated against sounding pitch, and the layout audit checks each visible TAB numeral's position and alignment.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user