Generation

This commit is contained in:
khodowany
2026-09-30 01:38:16 -05:00
parent b1a7b44d7a
commit deaa0468a7
20 changed files with 417 additions and 198 deletions
+11 -5
View File
@@ -128,23 +128,27 @@ Acoustic accompaniment uses the planned foundation. Electric accompaniment secti
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 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 1-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.
`guitar.py` searches standard tuning E2 A2 D3 G3 B3 E4, frets 0-20 inclusive, with at most four frets between the lowest and highest positions, including fret 0. Open strings participate in span validation and proximity scoring: 0/2/3 and 0/4 are valid, while 0/5 and 0/11/12 are not. Exact melody tones are never dropped or replaced. Simultaneous tones occupy distinct strings, and sustained/tied tones retain their assignments. Acoustic grips use one bass on string 5 or 6 and two or three consecutive strings within 2-4, with zero or one unused string between bass and upper voices. Both rhythmic voices must fit one complete grip, even when only one attacks. Each continuous acoustic harmony span keeps the same bass pitch, upper voicing, and string/fret positions. Root-bass and fifth-bass candidates compete using the same complete-grip cost; fifth choices are not merely fallbacks. The fifth follows the chord's actual degree, including diminished or augmented fifths, and is unavailable if omitted from the chord. Explicit slash basses and inversions override this choice. All required chord tones remain mandatory, including the root when required in an upper voice; there is no alternating-bass substitution.
`guitar.py` searches standard tuning E2 A2 D3 G3 B3 E4, frets 1-20 inclusive, with at most four frets between the lowest and highest positions. Open strings are excluded from chords and single-note melodies alike. A pitch that only an open string could produce fails generation rather than being octave-shifted or omitted. Exact melody tones are never dropped or replaced. Simultaneous tones occupy distinct strings, and sustained/tied tones retain their assignments. Acoustic grips use one bass on string 5 or 6 and two or three consecutive strings within 2-4, with zero or one unused string between bass and upper voices. Both rhythmic voices must fit one complete grip, even when only one attacks. Each continuous acoustic harmony span keeps the same bass pitch, upper voicing, and string/fret positions. Root-bass and fifth-bass candidates compete equally; fifth choices are not merely fallbacks. Explicit acoustic slash basses and inversions override the default bass choices. There is no alternating-bass substitution.
Shared `ChordRequirements` separate mandatory pitch classes, allowed pitch classes, root-or-fifth alternatives, and bass constraints. Every complete generated guitar grip must contain the root, the fifth, or both, with no preference for the root itself. This rule applies equally to acoustic accompaniment, electric accompaniment, and electric melody chords, not isolated bass/upper attacks or unharmonized melody notes. The fifth follows the chord's actual degree, including diminished or augmented fifths. Explicitly removed degrees are never restored, and a harmony removing both root and fifth cannot generate a guitar chord. Defining thirds/suspensions, sixths/sevenths, alterations, and required extensions remain mandatory; an altered fifth cannot be omitted merely because the root is present.
Acoustic spans follow the harmony timeline, including changes during silence. Barline crossings, rests, repeated identical harmonies, and section labels do not reset the grip while accompaniment continues. Changed chord kinds, roots, degrees, basses, or inversions start a new span. N.C. and non-accompaniment passages end a span. Returning to a chord after a different harmony starts a new decision, not a song-wide fixed fingering. A span with attacks is placed once as a complete grip, then each bass or upper attack receives its corresponding subset; simultaneous attacks receive both. If no complete grip satisfies the existing harmonic and fretboard constraints, generation fails with passage context without replacing published assets. Span state is build-only and does not add runtime fields.
Electric chord blocks use strings 1-3 or 1-4, including string 1. Rootless voicings and octave doublings are allowed as fallbacks; the third or suspension, sixth/seventh, alterations, and named extensions remain required. An unaltered fifth in extended chords and unaltered intermediate ninth/eleventh extensions may be omitted. Exact melody pitches, including non-chord tones, remain unchanged. Root preference never restores a degree explicitly subtracted from the harmony.
Electric chord blocks use strings 1-3 or 1-4, including string 1. Rootless voicings containing the fifth and octave doublings are ordinary candidates. An unaltered fifth may be omitted when the root is present, and unaltered intermediate ninth/eleventh extensions may be omitted. Exact melody pitches, including non-chord tones, remain unchanged.
`melody.py` treats the full tied duration as one note when testing whether it exceeds one quarter-note beat. Qualifying notes receive a chord at their initial attack and at each subsequent changed harmony, even if a resulting fragment is less than one beat. Changed harmonies break ties and reattack the melody; repeated identical harmonies do not. N.C. releases the added voices while preserving the melody's sustain. Added tones through unchanged harmony retain their own ties. The quarter-note cap on electric stabs does not apply to these melody chords. If no valid chord fits, the exact melody remains with a diagnostic; it is not octave-wrapped or omitted.
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.
Placement uses the ordered `VoicingCost` components: electric string shortfall from four, melody covered, number of tones above the melody, physical effort, acoustic simplicity, and missing optional pitch classes. These costs accumulate over the whole passage, so movement cannot outweigh the earlier electric 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 penalties: movement takes priority over simplicity, and a simpler three-note grip can beat an equally nearby but more awkward four-note grip.
`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.
Physical effort for generated electric chords is `movement + highest_fret + 2 * max(0, highest_fret - 12) + fret_span`. Exact, unharmonized melody placement retains its previous cost without the `highest_fret` term. Their movement metric remains the Manhattan distance between doubled fret/string position centers, with the exact small-grid distance transform used for independent attacks. Acoustic effort is `movement + highest_fret + 2 * max(0, highest_fret - 12)`, followed by a separate simplicity cost of `3 * fret_span + 2 * (distinct_frets - 1)`. Acoustic movement sums fret changes on retained strings plus one unit per added or released string; an unchanged position costs zero. Exact candidate-to-candidate dynamic programming retains all acoustic grips, including different grips with equal centers. This favors C7 at `5:10, 3:9, 2:11` after Gm7 at `5:10, 3:10, 2:11`, without a song-specific shape dictionary.
Generated chord height contributes one point per fret throughout the allowed range. The shared `PREFERRED_MAX_FRET` remains 12, above which each excess fret adds two more points. Lower positions compete with movement and span costs rather than winning unconditionally; higher grips remain available when they reduce movement enough. 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 or finger-assignment model. 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.
@@ -180,6 +184,8 @@ Generated guitar tones carry `tablature: {string, fret}`; their staff is marked
Every part uses two music systems per page; only an odd final system occupies a row alone. Pagination reserves two rows below the header, keeping a fixed gap and the footer clear. Both systems on a page share a uniform scale when their measured height exceeds a row; notation, TAB, text, and anchors scale together without changing musical content. Guitar layout starts with 10-pixel notation spacing, 10.5-pixel TAB spacing, 12-pixel fret labels, and a 14-pixel gap between systems. The unscaled TAB spacing accommodates Petaluma numerals up to 9.75 pixels tall. The layout audit accounts for each system's scale when verifying pitch and TAB alignment. Existing `.pages/*.png` preview files are the exact packaged page textures.
Line breaks occur only at measure boundaries. Each line fits as many complete measures as readable spacing allows; it never fills remaining space with a partial measure. A complete measure exceeding the available width or height fails with source and measure context instead of being split or squeezed to fit. Authored ties, beams, tuplets, and lyric spans can continue across whole-measure line breaks. Column measurement and fit trials reuse a score index and bounded column cache. Page placement then re-spaces columns for the final height-fitting scale, keeping every notation/TAB staff at the same physical left and right margins without stretching glyphs. Non-final lines are justified; a short final line retains natural note spacing while staff lines extend to the same right margin. Layout audits enforce these margins as well as musical coverage.
`audit.py` checks the final layout against the musical model: timeline and fragment coverage, exact fragment durations, actual glyph bounds, pitch positions, visible note primitives, required accidentals, ties, lyrics, harmonies, tempos and spans. It rejects out-of-page geometry and collisions among unrelated noteheads, accidentals and same-lane text. Staff, ledger, stem, beam and tie contacts are intentional. This is not a proof of publication-quality engraving for every possible MusicXML document; notation changes need targeted semantic, geometry and reviewed image tests. The writer's XML round-trip audit is retained separately in the test suite.
Bundles preserve the complete `music` object. Presentation identifies the `Vamp Python` engraver; its style fingerprint includes the style settings, both font files, font-specific metadata and the glyph-name mapping. Scores, playback manifests, and the catalog have no numbered schema labels. Required fields, types, relationships, dimensions, and checksums determine whether generated data is usable. Incompatible data is rejected, never migrated or guessed.