From deaa0468a70bcfa3f6ed3d3406533aac96760613 Mon Sep 17 00:00:00 2001 From: khodowany Date: Wed, 30 Sep 2026 01:38:16 -0500 Subject: [PATCH] Generation --- README.md | 6 +- TECHNICAL_GUIDE.md | 16 ++- .../harmonic-minor-scale-modes/0007.xml | 5 +- generator/engraving/audit.py | 7 ++ generator/engraving/layout.py | 59 +++++---- generator/engraving/notation.py | 1 + generator/engraving/pipeline.py | 8 +- generator/engraving/spans.py | 2 +- generator/engraving/tablature.py | 30 +++-- generator/guitar.py | 102 +++++++++------- tests/fixtures/engraving-python.png | Bin 25768 -> 25749 bytes tests/fixtures/notation-python.png | Bin 56081 -> 56104 bytes tests/test_acoustic.py | 2 +- tests/test_acoustic_voicing.py | 53 +++++--- tests/test_engraving.py | 115 ++++++++++++++---- tests/test_guitar.py | 98 +++++++++++---- tests/test_guitar_parts.py | 15 ++- tests/test_music_model.py | 2 +- tests/test_score_bundle.py | 5 + tests/test_voicing.py | 89 ++++++++------ 20 files changed, 417 insertions(+), 198 deletions(-) diff --git a/README.md b/README.md index 608bcac..8f83bcd 100644 --- a/README.md +++ b/README.md @@ -111,11 +111,11 @@ Accompaniment uses deterministic four-bar phrase cycles: a two-bar motif repeats There are no musical genre presets, genre defaults, or genre metadata. Obsolete `vamp:style` hints are rejected; visual styles, fonts, colors, and barline notation remain supported. Phrase cycles are written into the generated score and played once in reading order. -Guitar parts share a theory-based voicing and proximity search over standard tuning, frets 0-20, and a maximum four-fret span including open strings. Open strings can accompany frets 1-4, but not distant positions such as 11 or 12. Acoustic uses a bass on string 5 or 6 and two or three consecutive upper strings within 2-4, with zero or one unused string between them. Electric chords use strings 1-3 or 1-4 and may omit the root. Electric melody notes longer than a quarter note, including full tied runs, receive chords when playable; chord changes create fresh attacks even for short remaining fragments. Electric melodies are raised one octave; vocal registers stay unchanged. All parts still sound one octave below their generated notation, and explicit MusicXML transposition is honored once. Unplayable melody harmonizations remain single notes with a diagnostic; unplayable accompaniment fails generation. +Guitar parts share a theory-based voicing and proximity search over standard tuning, frets 1-20, and a maximum four-fret span. Open strings are excluded from chords and single-note melodies. Acoustic uses a bass on string 5 or 6 and two or three consecutive upper strings within 2-4, with zero or one unused string between them. Electric chords use strings 1-3 or 1-4 and may omit the root. Electric melody notes longer than a quarter note, including full tied runs, receive chords when playable; chord changes create fresh attacks even for short remaining fragments. Electric melodies are raised one octave; vocal registers stay unchanged. All parts still sound one octave below their generated notation, and explicit MusicXML transposition is honored once. Unplayable melody harmonizations remain single notes with a diagnostic; unplayable accompaniment or exact melody pitches fail generation. -All parts use two music systems per page, with a single system only when the score ends on an odd system. Tall notation is scaled uniformly to fit the two rows; guitar notation and TAB stay together and aligned. TAB displays frets and ties without duplicate rests; standard notation retains the complete rhythm. The generated `.pages/*.png` files are the same page images displayed by the product. +All parts use two music systems per page, with a single system only when the score ends on an odd system. Lines break only between measures and share consistent staff/TAB widths. A measure that cannot fit at readable spacing fails with a diagnostic instead of being split. Tall notation is scaled uniformly to fit the two rows; guitar notation and TAB stay together and aligned. TAB displays frets and ties without duplicate rests; standard notation retains the complete rhythm. The generated `.pages/*.png` files are the same page images displayed by the product. -Electric melody prioritizes four-string chords, then melody on top, then root inclusion. Compact shapes, nearby positions, and fret comfort are preferred before optional chord tones; rootless voicings remain fallbacks, not equally desirable defaults. Electric accompaniment shares the fullness/root priorities. Acoustic instead favors simple grips, allowing bass plus two upper notes when that is easier than a four-note shape. Defining chord tones remain mandatory. All guitar placement uses a fret comfort threshold: frets 0-12 inclusive have no height penalty, and each fret above 12 adds two effort points based on the grip's highest fret. Frets 13-20 remain available; the soft penalty does not override musical priorities or force a less convenient move to a lower position. Exact melody pitches and rhythms remain unchanged. +Electric melody prioritizes four-string chords, then melody on top. Every generated guitar chord requires the root, the fifth, or both; neither is preferred over the other. Acoustic favors simple grips, allowing bass plus two upper notes when that is easier than a four-note shape. Defining chord tones remain mandatory. Acoustic and electric chord scoring balances movement and span with a lower-position preference: the highest fret adds one effort point per fret, plus two more per fret above 12. Frets 13-20 remain available when movement savings justify them. Exact melody pitches and rhythms remain unchanged, and unharmonized melody placement retains its existing proximity scoring. Write all music in reading order: repeat bars, numbered endings, navigation jumps, and pass-specific ties are rejected. The seven-section performance arrangements remain fully written out. Chord labels use `M`, `m`, `aug`, `dim`, `sus`, and `mM7`, including explicit major triads such as `CM`. Recognized Soundslice text-only harmonies are normalized into structured chords; parenthesized chords mark optional turnarounds omitted from the final arrangement ending. diff --git a/TECHNICAL_GUIDE.md b/TECHNICAL_GUIDE.md index 9220a0b..6ed28ee 100644 --- a/TECHNICAL_GUIDE.md +++ b/TECHNICAL_GUIDE.md @@ -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. diff --git a/data/scales/harmonic-minor-scale-modes/0007.xml b/data/scales/harmonic-minor-scale-modes/0007.xml index 38bf29f..e879557 100644 --- a/data/scales/harmonic-minor-scale-modes/0007.xml +++ b/data/scales/harmonic-minor-scale-modes/0007.xml @@ -94,12 +94,13 @@ - A + B + -2 4 4 quarter - natural + flat-flat diff --git a/generator/engraving/audit.py b/generator/engraving/audit.py index 81b0ba3..e3baa2e 100644 --- a/generator/engraving/audit.py +++ b/generator/engraving/audit.py @@ -33,6 +33,13 @@ def audit_layout(document: dict, layout: generator.engraving.types.LayoutDocumen for source in item.sources: primitives.setdefault(source, []).append(item) for system in page.systems: + for role in ('staff', 'tab-staff'): + lines = [p for p in system.primitives if p.role == role] + if lines: + left = min(x for p in lines for x, _ in p.points) + right = max(x for p in lines for x, _ in p.points) + if abs(left - layout.style.margin_x - 1) > 0.01 or abs(right - (page.width - layout.style.margin_x - 1)) > 0.01: + raise generator.engraving.types.EngravingError('music systems must have consistent rendered staff widths') if system.start != previous_end or system.end <= system.start: raise generator.engraving.types.EngravingError('layout systems do not partition the score timeline') previous_end = system.end diff --git a/generator/engraving/layout.py b/generator/engraving/layout.py index 2965eed..8d3e4a4 100644 --- a/generator/engraving/layout.py +++ b/generator/engraving/layout.py @@ -78,7 +78,7 @@ def column_metrics(index, time, start, end, fonts, style, columns): columns.popitem(last=False) return result -def make_system(index: generator.engraving.notation.ScoreIndex, start: fractions.Fraction, end: fractions.Fraction, fonts: generator.engraving.fonts.Fonts, style: generator.engraving.types.Style, justify: bool = False, *, columns=None) -> generator.engraving.types.System: +def make_system(index: generator.engraving.notation.ScoreIndex, start: fractions.Fraction, end: fractions.Fraction, fonts: generator.engraving.fonts.Fonts, style: generator.engraving.types.Style, justify: bool = False, *, columns=None, staff_width: float | None = None) -> generator.engraving.types.System: items, anchors, fragments = [], [], [] state = index.state(start) measure = index.measure_at(start)[2] @@ -118,7 +118,7 @@ def make_system(index: generator.engraving.notation.ScoreIndex, start: fractions cursor = x + max(style.space * 1.4, box.right) + spring groups.append((item_start, len(items), anchor_start, len(anchors))) width = cursor + 6 - available = style.width - style.margin_x * 2 + available = staff_width + 1 if staff_width is not None else style.width - style.margin_x * 2 if justify and width < available: stretch = (available - width) / max(1, len(times)) for i, (first_item, last_item, first_anchor, last_anchor) in enumerate(groups): @@ -126,6 +126,10 @@ def make_system(index: generator.engraving.notation.ScoreIndex, start: fractions anchors[first_anchor:last_anchor] = [a.moved(i * stretch, 0) for a in anchors[first_anchor:last_anchor]] placements = [(time, x + i * stretch) for i, (time, x) in enumerate(placements)] width = available + if staff_width is not None: + if width > available + 0.01: + raise generator.engraving.types.EngravingError(f'at musical offset {start}: content exceeds the final staff width') + width = available right = width - 5 last_measure = index.measure_at(end, before=True)[2] tab_items = [p for p in items if p.role.startswith('tab')] @@ -155,8 +159,22 @@ def make_system(index: generator.engraving.notation.ScoreIndex, start: fractions items = generator.engraving.tablature.staff(index, items + tab_items, start, end, content_left, width - 1, fonts, style) return generator.engraving.types.System(start, end, tuple(items), tuple(anchors), tuple(fragments), generator.engraving.types.bounds(items)) -def paginate(systems: list[generator.engraving.types.System], document: dict, fonts: generator.engraving.fonts.Fonts, style: generator.engraving.types.Style) -> tuple[generator.engraving.types.Page, ...]: +def fit_page_systems(systems, index, row_height, fonts, style): + scale = min(1, row_height / max(s.box.bottom - s.box.top for s in systems)) + columns = collections.OrderedDict() + # Re-space columns at the final physical width; scale glyphs uniformly, not horizontally. + for _ in range(8): + width = (style.width - 2 * style.margin_x - 2) / scale + fitted = [make_system(index, s.start, s.end, fonts, style, justify=s.end < index.end, columns=columns, staff_width=width) for s in systems] + height = max(s.box.bottom - s.box.top for s in fitted) + if height * scale <= row_height + 0.001: + return [s.scaled(scale) for s in fitted] + scale = row_height / height + raise generator.engraving.types.EngravingError(f'at musical offset {systems[0].start}: page spacing did not converge') + +def paginate(systems: list[generator.engraving.types.System], document: dict, fonts: generator.engraving.fonts.Fonts, style: generator.engraving.types.Style, *, index=None) -> tuple[generator.engraving.types.Page, ...]: pages = [] + index = index or generator.engraving.notation.ScoreIndex(document) guitar = document['staves'][0].get('tablature', False) header_gap = 14 if guitar else 24 @@ -183,31 +201,29 @@ def paginate(systems: list[generator.engraving.types.System], document: dict, fo row_height = (style.height - style.margin_y - 24 - top - style.system_gap) / 2 if row_height <= 0: raise generator.engraving.types.EngravingError('page header leaves no room for two music systems') - pair = systems[first:first + 2] - scale = min(1, row_height / max(s.box.bottom - s.box.top for s in pair)) + pair = fit_page_systems(systems[first:first + 2], index, row_height, fonts, style) placed = [] for row, system in enumerate(pair): - system = system.scaled(scale) dx, dy = style.margin_x + 1, top + row * (row_height + style.system_gap) - system.box.top placed.append(dataclasses.replace(system, primitives=tuple(p.moved(dx, dy) for p in system.primitives), anchors=tuple(a.moved(dx, dy) for a in system.anchors), box=system.box.moved(dx, dy))) footer = fonts.text(str(number), style.width / 2, style.height - style.margin_y, 12, 'page-number', (), True) pages.append(generator.engraving.types.Page(number, style.width, style.height, style.supersampling, tuple(placed), tuple(header + [p for s in placed for p in s.primitives] + [footer]))) return tuple(pages) -def layout_systems(document: dict, style: generator.engraving.types.Style, fonts: generator.engraving.fonts.Fonts) -> tuple[generator.engraving.types.System, ...]: - generator.model.validate_document(document) - index = generator.engraving.notation.ScoreIndex(document) +def layout_systems(document: dict, style: generator.engraving.types.Style, fonts: generator.engraving.fonts.Fonts, *, index=None) -> tuple[generator.engraving.types.System, ...]: + if index is None: + generator.model.validate_document(document) + index = generator.engraving.notation.ScoreIndex(document) if not index.measures: raise generator.engraving.types.EngravingError('score has no measures') systems = [] start = fractions.Fraction() - available = style.width - style.margin_x * 2 + available = style.width - style.margin_x * 2 - 2 while start < index.end: columns = collections.OrderedDict() candidates = index.boundary_times[bisect.bisect_right(index.boundary_times, start):] selected = None - # Reserve the same title/credit and footer bands when considering a split, - # so a wide pitch range can break vertically as well as horizontally. + # Reserve title/credit and footer bands before fitting complete measures. height_limit = style.height - style.margin_y * 2 - 110 for end in candidates: trial = make_system(index, start, end, fonts, style, columns=columns) @@ -215,22 +231,17 @@ def layout_systems(document: dict, style: generator.engraving.types.Style, fonts break selected = trial if selected is None: - measure_end = candidates[0] - for end in index.column_times[bisect.bisect_right(index.column_times, start):bisect.bisect_left(index.column_times, measure_end)]: - trial = make_system(index, start, end, fonts, style, columns=columns) - if trial.box.right - trial.box.left <= available and trial.box.bottom - trial.box.top <= height_limit: - selected = trial - else: - break - if selected is None: - raise generator.engraving.types.EngravingError(f'at musical offset {start}: irreducible simultaneous content exceeds the page width or height') - systems.append(make_system(index, start, selected.end, fonts, style, justify=True, columns=columns) if selected.end < index.end else selected) + measure = index.measure_at(start)[2] + raise generator.engraving.types.EngravingError(f'measure {measure["number"]} ({measure["id"]}): complete measure exceeds the page width or height at readable spacing') + systems.append(selected) start = selected.end return tuple(systems) def layout_score(document: dict, style: generator.engraving.types.Style, fonts: generator.engraving.fonts.Fonts) -> generator.engraving.types.LayoutDocument: + generator.model.validate_document(document) style = generator.engraving.types.score_style(document, style) - systems = layout_systems(document, style, fonts) - layout = generator.engraving.types.LayoutDocument(paginate(systems, document, fonts, style), style, fonts.fingerprint(style)) + index = generator.engraving.notation.ScoreIndex(document) + systems = layout_systems(document, style, fonts, index=index) + layout = generator.engraving.types.LayoutDocument(paginate(systems, document, fonts, style, index=index), style, fonts.fingerprint(style)) generator.engraving.audit.audit_layout(document, layout, fonts) return layout diff --git a/generator/engraving/notation.py b/generator/engraving/notation.py index ffcb334..5633912 100644 --- a/generator/engraving/notation.py +++ b/generator/engraving/notation.py @@ -60,6 +60,7 @@ class ScoreIndex: time = end self.end = time self.measure_starts = [item[0] for item in self.measures] + self.measure_starts_by_id = {measure['id']: start for start, _, measure in self.measures} self.events.sort(key=lambda e: (e.start, not bool(e.value.get('grace')), e.lane)) self.event_starts = [] self.event_max_ends = [] diff --git a/generator/engraving/pipeline.py b/generator/engraving/pipeline.py index 008a213..c99c119 100644 --- a/generator/engraving/pipeline.py +++ b/generator/engraving/pipeline.py @@ -17,6 +17,7 @@ import generator.engraving.audit import generator.engraving.cache import generator.engraving.fonts import generator.engraving.layout +import generator.engraving.notation import generator.engraving.raster import generator.engraving.types import generator.engraving.validation @@ -111,6 +112,7 @@ def engrave_group(notation, paths, root, font_path, cache_dir, fingerprint, prog family = generator.engraving.fonts.Fonts(font_path) style = generator.engraving.types.Style() systems = None + index = None scores = {} audited = set() result = GroupResult() @@ -132,10 +134,12 @@ def engrave_group(notation, paths, root, font_path, cache_dir, fingerprint, prog else: if systems is None: started = time.monotonic() - systems = generator.engraving.layout.layout_systems(document, style, family) + generator.model.validate_document(document) + index = generator.engraving.notation.ScoreIndex(document) + systems = generator.engraving.layout.layout_systems(document, style, family, index=index) result.timings['layout'] += time.monotonic() - started started = time.monotonic() - pages_layout = generator.engraving.layout.paginate(systems, document, family, style) + pages_layout = generator.engraving.layout.paginate(systems, document, family, style, index=index) placement = tuple((page.width, page.height, tuple((system.start, system.end, system.box) for system in page.systems)) for page in pages_layout) if placement not in audited: layout = generator.engraving.types.LayoutDocument(pages_layout, style, fingerprint) diff --git a/generator/engraving/spans.py b/generator/engraving/spans.py index 838f232..b7416dc 100644 --- a/generator/engraving/spans.py +++ b/generator/engraving/spans.py @@ -17,7 +17,7 @@ def beam_groups(index: generator.engraving.notation.ScoreIndex) -> tuple[tuple[s for event in (e for e in index.events if e.voice == voice): note = event.value primary = next((b['type'] for b in note.get('beams', []) if b['level'] == 1), None) - measure_start = next(start for start, _, measure in index.measures if measure['id'] == event.measure['id']) + measure_start = index.measure_starts_by_id[event.measure['id']] beats = generator.model.beat_lengths(index.state(event.start)['meter']) offset = event.start - measure_start beat = 0 diff --git a/generator/engraving/tablature.py b/generator/engraving/tablature.py index 4a4ff67..bb4366d 100644 --- a/generator/engraving/tablature.py +++ b/generator/engraving/tablature.py @@ -1,6 +1,5 @@ from __future__ import annotations -import generator.engraving.notes import generator.engraving.types import generator.model @@ -40,19 +39,24 @@ def staff(index, items, start, end, left, right, fonts, style): for bar in (p for p in notation if p.role == 'barline'): drawing.append(generator.engraving.types.line('tab-barline', bar.sources, tuple((x, offset + y * 5 * style.tab_space / (4 * style.space)) for x, y in bar.points), bar.thickness)) by_source = {source: p for p in numbers if p.role == 'tab' for source in p.sources[1:]} - for identifier, (event, tone) in index.tones.items(): - target = tone.get('tie_to') - finish = event.start + generator.model.fraction(tone['duration']) - if not target and not (event.start < start < finish or event.start < end < finish): - continue - if target: - finish = index.tones[target][0].start - if finish < start or event.start >= end: - continue - first, last = by_source.get(identifier), by_source.get(target) - x1 = first.box.right + 2 if first else left - x2 = last.box.left - 2 if last else right + + def tie(identifier, tone, x1, x2): y = offset - (6 - tone['tablature']['string']) * style.tab_space + 6 if x2 > x1: drawing.append(generator.engraving.types.curve('tab-tie', (identifier,), ((x1, y), (x1 + (x2 - x1) / 3, y + 5), (x2 - (x2 - x1) / 3, y + 5), (x2, y)))) + + for identifier, (event, tone) in index.tones.items(): + first = by_source.get(identifier) + finish = event.start + generator.model.fraction(tone['duration']) + if first and event.start < start < finish: + tie(identifier, tone, left, first.box.left - 2) + if first and finish > end: + tie(identifier, tone, first.box.right + 2, right) + elif target := tone.get('tie_to'): + destination = index.tones[target][0].start + if event.start >= end or destination < start: + continue + last = by_source.get(target) + if first or last: + tie(identifier, tone, first.box.right + 2 if first else left, last.box.left - 2 if last else right) return notation + drawing diff --git a/generator/guitar.py b/generator/guitar.py index 42f40da..128ec7d 100644 --- a/generator/guitar.py +++ b/generator/guitar.py @@ -13,7 +13,7 @@ import generator.model import generator.pitch STANDARD_TUNING = ((6, 40), (5, 45), (4, 50), (3, 55), (2, 59), (1, 64)) -MIN_FRET = 0 +MIN_FRET = 1 MAX_FRET = 20 PREFERRED_MAX_FRET = 12 MAX_FRET_SPAN = 4 @@ -30,8 +30,8 @@ class VoicingCost(typing.NamedTuple): string_shortfall: int = 0 covered_melody: int = 0 covering_tones: int = 0 - missing_root: int = 0 effort: int = 0 + simplicity: int = 0 missing_tones: int = 0 def plus(self, other: VoicingCost) -> VoicingCost: return VoicingCost(*(a + b for a, b in zip(self, other))) @@ -40,17 +40,18 @@ class VoicingCost(typing.NamedTuple): return self._replace(effort=self.effort + distance) @functools.lru_cache(maxsize=8192) -def voicing_cost(positions: tuple[TabPosition, ...], profile: str = '', allowed: tuple[int, ...] = (), root: int | None = None, melody: tuple[int, ...] = ()) -> VoicingCost: +def voicing_cost(positions: tuple[TabPosition, ...], profile: str = '', allowed: tuple[int, ...] = (), melody: tuple[int, ...] = ()) -> VoicingCost: frets = {p.fret for p in positions} classes = {p.midi % 12 for p in positions} above = sum(p.midi > max(melody) for p in positions) if melody else 0 span = compactness(positions) - effort = 2 * max(0, max(frets, default=0) - PREFERRED_MAX_FRET) + span - if profile == 'acoustic': - effort += 2 * span + 2 * max(0, len(frets) - 1) - return VoicingCost(max(0, 4 - len(positions)) if profile == 'electric' else 0, - int(bool(above)), above, int(profile == 'electric' and root is not None and root not in classes), - effort, len(set(allowed) - classes)) + effort = 2 * max(0, max(frets, default=0) - PREFERRED_MAX_FRET) + if profile in ('acoustic', 'electric'): + effort += max(frets, default=0) + simplicity = 3 * span + 2 * max(0, len(frets) - 1) if profile == 'acoustic' else 0 + if profile != 'acoustic': + effort += span + return VoicingCost(max(0, 4 - len(positions)) if profile == 'electric' else 0, int(bool(above)), above, effort, simplicity, len(set(allowed) - classes)) def within_fret_span(positions) -> bool: frets = [position.fret for position in positions] @@ -70,15 +71,27 @@ def validate_tuning(state: dict, location: str) -> None: if tunings and (len(tunings) != 6 or {t['line']: generator.model.pitch_number(t) for t in tunings} != expected): raise ValueError(f'{location}: guitar generation requires standard six-string tuning') -def chord_requirements(definition: dict, profile: str = '') -> tuple[tuple[int, ...], tuple[int, ...], int | None]: +@dataclasses.dataclass(frozen=True) +class ChordRequirements: + required: tuple[int, ...] = () + allowed: tuple[int, ...] = () + foundation: tuple[int, ...] = () + basses: tuple[int, ...] = () + def covers(self, classes: set[int]) -> bool: + return set(self.required) <= classes and bool(set(self.foundation) & classes) + +def chord_requirements(definition: dict, profile: str = '') -> ChordRequirements: degrees = generator.harmony.degrees(definition) root = definition['root'] root_pc = generator.model.NATURAL[root['letter']] + generator.model.alteration(root['alter']) explicit = {d['value'] for d in definition.get('degrees', []) if d['type'] != 'subtract'} - optional = {d for d, a in degrees.items() if not a and d not in explicit and (d == 5 and len(degrees) > 3 or d in (9, 11) and d < max(degrees))} + optional = {d for d, a in degrees.items() if not a and d not in explicit and d in (9, 11) and d < max(degrees)} classes = {d: (root_pc + generator.harmony.degree_semitones(d, a)) % 12 for d, a in degrees.items()} - required = {value for d, value in classes.items() if d not in optional and not (profile == 'electric' and d == 1)} + required = {value for d, value in classes.items() if d not in optional and (d not in (1, 5) or degrees[d])} allowed = set(classes.values()) + foundation = {value for d, value in classes.items() if d in (1, 5)} + if not foundation: + raise ValueError('generated guitar chords require a root or fifth in the harmony') bass = definition.get('bass') bass_pc = (generator.model.NATURAL[bass['letter']] + generator.model.alteration(bass['alter'])) % 12 if bass else None if bass_pc is None and 'inversion' in definition: @@ -92,20 +105,19 @@ def chord_requirements(definition: dict, profile: str = '') -> tuple[tuple[int, if bass_pc is not None: allowed.add(bass_pc) required.add(bass_pc) - if profile == 'acoustic' and bass_pc is None: - bass_pc = root_pc % 12 - return tuple(sorted(required)), tuple(sorted(allowed)), bass_pc + basses = {bass_pc} if bass_pc is not None else foundation if profile == 'acoustic' else set() + return ChordRequirements(tuple(sorted(required)), tuple(sorted(allowed)), tuple(sorted(foundation)), tuple(sorted(basses))) @functools.lru_cache(maxsize=256) -def chord_positions(required: tuple[int, ...], allowed: tuple[int, ...], bass: int | None, prune: bool = True, profile: str = '', melody: tuple[int, ...] = (), root: int | None = None) -> tuple[tuple[TabPosition, ...], ...]: +def chord_positions(requirements: ChordRequirements, prune: bool = True, profile: str = '', melody: tuple[int, ...] = ()) -> tuple[tuple[TabPosition, ...], ...]: result = [] - options = [tuple(TabPosition(string, fret, open_midi + fret) for fret in range(MIN_FRET, MAX_FRET + 1) if (open_midi + fret) % 12 in allowed or open_midi + fret in melody) for string, open_midi in STANDARD_TUNING] + options = [tuple(TabPosition(string, fret, open_midi + fret) for fret in range(MIN_FRET, MAX_FRET + 1) if (open_midi + fret) % 12 in requirements.allowed or open_midi + fret in melody) for string, open_midi in STANDARD_TUNING] def visit(index, selected, covered): - if len(set(required) - covered) > len(options) - index: + if len(set(requirements.required) - covered) > len(options) - index: return if index == len(options): - if selected and set(required) <= covered and (bass is None or min(selected, key=lambda p: p.midi).midi % 12 == bass): + if selected and requirements.covers(covered) and (not requirements.basses or min(selected, key=lambda p: p.midi).midi % 12 in requirements.basses): result.append(tuple(selected)) return visit(index + 1, selected, covered) @@ -117,21 +129,21 @@ def chord_positions(required: tuple[int, ...], allowed: tuple[int, ...], bass: i by_string = {string: options[i] for i, (string, _) in enumerate(STANDARD_TUNING)} for strings in ACOUSTIC_STRINGS if profile == 'acoustic' else ELECTRIC_STRINGS: for selected in itertools.product(*(by_string[s] for s in strings)): - if not within_fret_span(selected) or not set(required) <= {p.midi % 12 for p in selected} or not set(melody) <= {p.midi for p in selected}: + if not within_fret_span(selected) or not requirements.covers({p.midi % 12 for p in selected}) or not set(melody) <= {p.midi for p in selected}: continue - if bass is not None and (selected[0].midi % 12 != bass or selected[0].midi != min(p.midi for p in selected)): + if requirements.basses and (selected[0].midi % 12 not in requirements.basses or selected[0].midi != min(p.midi for p in selected)): continue result.append(selected) else: visit(0, (), set()) - if not prune: + if not prune or profile == 'acoustic': return tuple(sorted(result)) # Independent attacks with equal centers have identical movement costs. # Held strings require the full candidate set instead of this reduction. best = {} for positions in result: center = position_center(positions) - rank = (voicing_cost(positions, profile, allowed, root, melody), positions) + rank = (voicing_cost(positions, profile, requirements.allowed, melody), positions) if center not in best or rank < best[center][0]: best[center] = (rank, positions) return tuple(sorted(item[1] for item in best.values())) @@ -157,6 +169,14 @@ def movement(previous: tuple[TabPosition, ...], current: tuple[TabPosition, ...] return 0 return sum(abs(a - b) for a, b in zip(position_center(previous), position_center(current))) +@functools.lru_cache(maxsize=16384) +def acoustic_movement(previous: tuple[TabPosition, ...], current: tuple[TabPosition, ...]) -> int: + if not previous or not current: + return 0 + before = {p.string_number: p.fret for p in previous} + after = {p.string_number: p.fret for p in current} + return sum(abs(before[s] - after[s]) for s in before.keys() & after.keys()) + len(before.keys() ^ after.keys()) + @functools.lru_cache(maxsize=4096) def position_center(positions: tuple[TabPosition, ...]) -> tuple[int, int]: frets = [p.fret for p in positions] or [0] @@ -236,16 +256,11 @@ class EventPlacement: added: tuple[TabPosition, ...] = () def acoustic_candidates(span: generator.acoustic.HarmonySpan): - required, allowed, bass = chord_requirements(span.harmony, 'acoustic') - basses = {bass} - degrees = generator.harmony.degrees(span.harmony) - if 'bass' not in span.harmony and 'inversion' not in span.harmony and 5 in degrees: - basses.add((bass + generator.harmony.degree_semitones(5, degrees[5])) % 12) - shapes = sorted({shape for value in sorted(basses) for shape in chord_positions(required, allowed, value, profile='acoustic')}) - for shape in shapes: + requirements = chord_requirements(span.harmony, 'acoustic') + for shape in chord_positions(requirements, profile='acoustic'): active = tuple((str(p.string_number), p) for p in shape) choices = tuple(EventPlacement(note, (), tuple(p for p in shape if (p.string_number >= 5) == (note['_guitar_lane'] == 'bass'))) for note in span.notes) - yield AttackCandidate(active, choices, shape, voicing_cost(shape, 'acoustic', allowed)) + yield AttackCandidate(active, choices, shape, voicing_cost(shape, 'acoustic', requirements.allowed)) def continued_sources(notes): result = {p['tie_from'] for n in notes for p in n['pitches'] if 'tie_from' in p} @@ -270,16 +285,10 @@ def attack_candidates(notes: list[dict], tones: dict[str, GuitarTone], active: d pending.append((tone['id'], options)) if fallback and chord and chord.get('_harmonize'): chords, chord, profile = [], None, '' - requirements = chord_requirements(chord['_harmony'], profile) if chord else ((), (), None) - root = None - if chord and 1 in generator.harmony.degrees(chord['_harmony']): - pitch = chord['_harmony']['root'] - value = int((generator.model.NATURAL[pitch['letter']] + generator.model.alteration(pitch['alter'])) % 12) - if value in requirements[1]: - root = value + requirements = chord_requirements(chord['_harmony'], profile) if chord else ChordRequirements() melody = tuple(sorted({p.midi for _, options in pending for p in options})) if profile == 'electric' else () fresh = has_attack(notes) - candidates = chord_positions(*requirements, prune=not (preserve_strings or held or pending or profile), profile=profile, melody=melody, root=root) if chord else [()] + candidates = chord_positions(requirements, prune=not (preserve_strings or held or pending or profile), profile=profile, melody=melody) if chord else [()] for exact in extend_positions(held, sorted(pending, key=lambda p: len(p[1]))): exact_positions = dict(exact) for positions in candidates: @@ -291,7 +300,7 @@ def attack_candidates(notes: list[dict], tones: dict[str, GuitarTone], active: d compatible = True for note in notes: added = tuple(p for p in positions if p not in exact_positions.values() and note in chords) - if any(p.midi % 12 not in requirements[1] for p in added): + if any(p.midi % 12 not in requirements.allowed for p in added): compatible = False break if '_continue_from' in note: @@ -303,7 +312,7 @@ def attack_candidates(notes: list[dict], tones: dict[str, GuitarTone], active: d choices.append(EventPlacement(note, tuple((p, exact_positions[p['id']]) for p in note['pitches']), added)) if not compatible: continue - cost = voicing_cost(positions, profile, requirements[1], root, melody) if fresh else VoicingCost() + cost = voicing_cost(positions, profile, requirements.allowed, melody) if fresh else VoicingCost() yield AttackCandidate(tuple(sorted(assignment.items())), tuple(choices), positions, cost) else: shape = tuple(p for _, p in exact) + positions @@ -311,7 +320,7 @@ def attack_candidates(notes: list[dict], tones: dict[str, GuitarTone], active: d continue assignment = tuple(sorted((*exact, *((f'{chord["id"]}-s{p.string_number}', p) for p in positions)))) if chord else exact choices = tuple(EventPlacement(n, tuple((p, exact_positions[p['id']]) for p in n['pitches']), positions if n is chord else ()) for n in notes) - cost = voicing_cost(shape, profile, requirements[1], root, melody) if fresh else VoicingCost() + cost = voicing_cost(shape, profile, requirements.allowed, melody) if fresh else VoicingCost() yield AttackCandidate(assignment, choices, shape, cost) def guitar_events(document: dict) -> tuple[list, dict[str, GuitarTone]]: @@ -373,6 +382,13 @@ def advance_attacks(candidates, states: dict) -> dict: result[candidate.active] = Placement(cost, candidate.active, previous, candidate.choices) return result +def advance_acoustic(candidates, states: dict) -> dict: + result = {} + for candidate in candidates: + cost, _, previous = min((p.cost.moved(acoustic_movement(tuple(position for _, position in p.active), candidate.shape)), i, p) for i, p in enumerate(states.values())) + result[candidate.active] = Placement(cost.plus(candidate.cost), candidate.active, previous, candidate.choices) + return result + def advance_positions(notes: list[dict], onset: fractions.Fraction, tones: dict[str, GuitarTone], states: dict, preserve_strings: bool, fallback: bool = False) -> dict: continued = continued_sources(notes) fresh = has_attack(notes) @@ -405,7 +421,7 @@ def place_guitar(document: dict) -> None: states = {(): Placement(VoicingCost(), (), None, ())} if document['staves'][0].get('arrangement_part_type') == 'acoustic': for span in generator.acoustic.harmony_spans(document): - states = advance_attacks(acoustic_candidates(span), states) + states = advance_acoustic(acoustic_candidates(span), states) if not states: raise ValueError(f'{span.location}: no acoustic grip within frets {MIN_FRET}-{MAX_FRET} and four-fret span for {span.harmony.get("symbol", span.harmony["kind"])}') else: diff --git a/tests/fixtures/engraving-python.png b/tests/fixtures/engraving-python.png index 47a74c8aa324601a10a4137cc9a6a3a6c9f7c62d..a3213dde3f23071c6bb8b6d40bc140544a91fb4d 100644 GIT binary patch literal 25749 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℘}#F^asuDrRrV<#*tc&VF|v@N12uyt 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 literal 25768 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 diff --git a/tests/fixtures/notation-python.png b/tests/fixtures/notation-python.png index dbccc6415b71af353b2207a01c863db19450f5d5..7b2ac1da28e585d5915fd82b777504a2defff343 100644 GIT binary patch literal 56104 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 F{|h#uWUK%H diff --git a/tests/test_acoustic.py b/tests/test_acoustic.py index cdff696..cd990bf 100644 --- a/tests/test_acoustic.py +++ b/tests/test_acoustic.py @@ -27,7 +27,7 @@ def acoustic_document(bars): for measure, (bass, upper) in zip(staff['measures'], bars): measure['transpose']['octave_change'] = -1 measure['voices'] = [] - for lane, attacks, positions in (('bass', bass, (('c', 5, 3),)), ('upper', upper, (('e', 4, 2), ('g', 3, 0)))): + for lane, attacks, positions in (('bass', bass, (('d', 5, 5),)), ('upper', upper, (('f', 4, 3), ('a', 3, 2)))): notes, cursor = [], fractions.Fraction() for onset, duration in attacks: onset, duration = fractions.Fraction(onset), fractions.Fraction(duration) diff --git a/tests/test_acoustic_voicing.py b/tests/test_acoustic_voicing.py index fe707ae..908c91c 100644 --- a/tests/test_acoustic_voicing.py +++ b/tests/test_acoustic_voicing.py @@ -52,18 +52,15 @@ class AcousticVoicingTests(unittest.TestCase): self.assertLessEqual(len(basses), 1) self.assertLessEqual(len(uppers), 1) grip = tuple(sorted({p for n in span.notes for p in shape(n)}, reverse=True)) - required, allowed, bass = generator.guitar.chord_requirements(span.harmony, 'acoustic') - bass_classes = {bass} - degrees = generator.harmony.degrees(span.harmony) - if 'bass' not in span.harmony and 'inversion' not in span.harmony and 5 in degrees: - bass_classes.add((bass + generator.harmony.degree_semitones(5, degrees[5])) % 12) + requirements = generator.guitar.chord_requirements(span.harmony, 'acoustic') if basses and uppers: self.assertIn(tuple(p.string_number for p in grip), generator.guitar.ACOUSTIC_STRINGS) - self.assertTrue(set(required) <= {p.midi % 12 for p in grip} <= set(allowed)) + self.assertTrue(requirements.covers({p.midi % 12 for p in grip})) + self.assertTrue({p.midi % 12 for p in grip} <= set(requirements.allowed)) if basses: - self.assertIn(min(p.midi for p in grip) % 12, bass_classes) + self.assertIn(min(p.midi for p in grip) % 12, requirements.basses) self.assertTrue(generator.guitar.within_fret_span(grip)) - self.assertTrue(all(0 <= p.fret <= 20 for p in grip)) + self.assertTrue(all(1 <= p.fret <= 20 for p in grip)) for note in span.notes: self.assertFalse(any(k.startswith('_') for k in note)) self.assertTrue(all('tie_from' not in p and 'tie_to' not in p for p in note['pitches'])) @@ -78,16 +75,16 @@ class AcousticVoicingTests(unittest.TestCase): generator.acoustic.combine(document) self.assertEqual(tests.test_acoustic.sounding(document), before) generator.model.validate_document(document) - def test_simpler_fifth_bass_can_win_without_alternating(self): + def test_fifth_bass_can_win_without_alternating(self): document = planned_document([[(0, 'Csus')], []]) spans = self.place(document) self.assertEqual(len(spans), 1) bass = [p for n in spans[0].notes for p in shape(n) if p.string_number >= 5] self.assertTrue(bass) self.assertEqual({p.midi % 12 for p in bass}, {7}) - root_shapes = generator.guitar.chord_positions(*generator.guitar.chord_requirements(spans[0].harmony, 'acoustic'), profile='acoustic') + root_shapes = generator.guitar.chord_positions(generator.guitar.chord_requirements(generator.harmony.parse_symbol('Csus/C'), 'acoustic'), profile='acoustic') selected = tuple(sorted({p for n in spans[0].notes for p in shape(n)}, reverse=True)) - self.assertLess(generator.guitar.voicing_cost(selected, 'acoustic'), min(generator.guitar.voicing_cost(s, 'acoustic') for s in root_shapes)) + self.assertLessEqual(generator.guitar.voicing_cost(selected, 'acoustic'), min(generator.guitar.voicing_cost(s, 'acoustic') for s in root_shapes)) def test_bass_options_follow_defined_fifth_and_explicit_basses(self): inversion = generator.harmony.parse_symbol('Cdim7') | {'inversion': 0} for chord, expected in (('C7', {0, 7}), ('Cdim7', {0, 6}), ('Caug', {0, 8}), ('Cno5', {0}), ('C7/E', {4}), (inversion, {0})): @@ -95,8 +92,8 @@ class AcousticVoicingTests(unittest.TestCase): span = generator.acoustic.harmony_spans(planned_document([[(0, chord)]]))[0] candidates = list(generator.guitar.acoustic_candidates(span)) self.assertEqual({min(p.midi for p in c.shape) % 12 for c in candidates}, expected) - required, allowed, _ = generator.guitar.chord_requirements(span.harmony, 'acoustic') - self.assertTrue(all(set(required) <= {p.midi % 12 for p in c.shape} <= set(allowed) for c in candidates)) + requirements = generator.guitar.chord_requirements(span.harmony, 'acoustic') + self.assertTrue(all(requirements.covers({p.midi % 12 for p in c.shape}) for c in candidates)) def test_same_grip_survives_repeated_symbols_rest_bars_and_sections(self): attacks = [([(0, 1), (2, 1)], [(0, 1), (3, 1)]), ([], []), ([(2, 1)], [(1, 1), (3, 1)])] document = planned_document([[(0, 'C7')], [], [(0, 'C7'), (2, 'C7')]], attacks) @@ -118,7 +115,9 @@ class AcousticVoicingTests(unittest.TestCase): document = planned_document([changes], [([(i, 1) for i in range(4)], [(i, 1) for i in range(4)])]) spans = self.place(document) self.assertEqual(len(spans), 4) - self.assertEqual([min(p.midi for n in span.notes for p in shape(n)) % 12 for span in spans], [0, 4, 7, 6]) + basses = [min(p.midi for n in span.notes for p in shape(n)) % 12 for span in spans] + self.assertIn(basses[0], (0, 7)) + self.assertEqual(basses[1:], [4, 7, 6]) def test_non_accompaniment_passage_ends_the_span(self): document = planned_document([[(0, 'C7')], [], []]) measure = document['staves'][0]['measures'][1] @@ -159,14 +158,34 @@ class AcousticVoicingTests(unittest.TestCase): candidates = list(generator.guitar.acoustic_candidates(span)) expected = {} for candidate in candidates: - expected[candidate.active] = min((previous.cost.moved(generator.guitar.movement(tuple(p for _, p in previous.active), candidate.shape)).plus(candidate.cost), index) for index, previous in enumerate(states.values())) - actual = generator.guitar.advance_attacks(candidates, states) + expected[candidate.active] = min((previous.cost.moved(generator.guitar.acoustic_movement(tuple(p for _, p in previous.active), candidate.shape)).plus(candidate.cost), index) for index, previous in enumerate(states.values())) + actual = generator.guitar.advance_acoustic(candidates, states) predecessors = {id(p): i for i, p in enumerate(states.values())} self.assertEqual({key: (p.cost, predecessors[id(p.previous)]) for key, p in actual.items()}, expected) states = actual def test_impossible_grip_fails_contextually_without_partial_materialization(self): document = planned_document([[(0, 'C7')], [(0, 'C7b9#11')]]) before = copy.deepcopy(document) - with self.assertRaisesRegex(ValueError, 'measure 2, event .*no acoustic grip.*0-20'): + with self.assertRaisesRegex(ValueError, 'measure 2, event .*no acoustic grip.*1-20'): generator.guitar.place_guitar(document) self.assertEqual(document, before) + def test_g_minor_to_c_seventh_preserves_positions_with_fifth_bass(self): + tuning = dict(generator.guitar.STANDARD_TUNING) + previous = tuple(generator.guitar.TabPosition(s, f, tuning[s] + f) for s, f in ((5, 10), (3, 10), (2, 11))) + active = tuple((str(p.string_number), p) for p in previous) + states = {active: generator.guitar.Placement(generator.guitar.VoicingCost(), active, None, ())} + span = generator.acoustic.harmony_spans(planned_document([[(0, 'C7')]]))[0] + candidates = list(generator.guitar.acoustic_candidates(span)) + result = generator.guitar.advance_acoustic(candidates, states) + best = min(result.values(), key=lambda p: (p.cost, p.active)) + chosen = tuple(p for _, p in best.active) + self.assertEqual([(p.string_number, p.fret) for p in chosen], [(5, 10), (3, 9), (2, 11)]) + self.assertEqual(generator.guitar.acoustic_movement(previous, chosen), 1) + self.assertEqual(generator.guitar.acoustic_movement(chosen, previous), 1) + self.assertEqual(generator.guitar.acoustic_movement(previous, previous), 0) + self.assertEqual(generator.guitar.acoustic_movement((), chosen), 0) + def test_acoustic_candidates_are_not_pruned_by_grip_center(self): + requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7'), 'acoustic') + candidates = generator.guitar.chord_positions(requirements, profile='acoustic') + self.assertEqual(candidates, generator.guitar.chord_positions(requirements, profile='acoustic', prune=False)) + self.assertGreater(len(candidates), len({generator.guitar.position_center(p) for p in candidates})) diff --git a/tests/test_engraving.py b/tests/test_engraving.py index 110f7aa..4419578 100644 --- a/tests/test_engraving.py +++ b/tests/test_engraving.py @@ -2,6 +2,7 @@ import collections import copy import dataclasses import fractions +import math import pathlib import unittest import unittest.mock @@ -50,6 +51,12 @@ class EngravingTests(unittest.TestCase): self.assertIn(len(layout.pages[-1].systems), (1, 2)) for page in layout.pages: self.assertEqual(len({system.scale for system in page.systems}), 1) + for system in page.systems: + for role in ('staff', 'tab-staff'): + lines = [p for p in system.primitives if p.role == role] + if lines: + self.assertAlmostEqual(min(x for p in lines for x, _ in p.points), layout.style.margin_x + 1) + self.assertAlmostEqual(max(x for p in lines for x, _ in p.points), page.width - layout.style.margin_x - 1) for first, second in zip(page.systems, page.systems[1:]): self.assertGreaterEqual(second.box.top - first.box.bottom, layout.style.system_gap - 0.001) def test_guitar_pages_always_pair_systems_including_tall_passages(self): @@ -60,7 +67,7 @@ class EngravingTests(unittest.TestCase): self.assert_paired_pages(layout) self.assertEqual(layout.style.space, 10) self.assertEqual(layout.style.tab_font_size, 12) - wide = tests.test_musicxml.xml_note('E', 2, 'half', octave=3) + tests.test_musicxml.xml_note('C', 2, 'half', octave=7) + wide = tests.test_musicxml.xml_note('F', 2, 'half', octave=3) + tests.test_musicxml.xml_note('C', 2, 'half', octave=7) document = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([wide] * 32)) for measure in document['staves'][0]['measures']: measure['transpose']['octave_change'] = -1 @@ -104,8 +111,8 @@ class EngravingTests(unittest.TestCase): self.assertIsNone(PIL.ImageChops.difference(actual, expected.convert('RGB')).getbbox()) def test_aligned_bands_match_full_pages_with_spans_tab_and_sampling_phases(self): tied = tests.test_musicxml.xml_note('E', 2, 'half', extra='') + tests.test_musicxml.xml_note('E', 2, 'half', extra='') - dense = ''.join(tests.test_musicxml.xml_note('G', 1, '64th') for _ in range(64)) - source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(dense, divisions=16)) + content = ''.join(tests.test_musicxml.xml_note('G', 1, 'eighth') for _ in range(8)) + source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 8, divisions=2)) documents = [generator.musicxml.load_musicxml(pathlib.Path('tests/fixtures/notation.xml')), tests.test_musicxml.parse(tests.test_musicxml.xml_score(tied)), tests.arrangement_fixtures.section_document(source, 'electric', rhythms=tests.rhythm_fixtures.library())] @@ -238,20 +245,85 @@ class EngravingTests(unittest.TestCase): self.assertEqual([p.value for p in self.primitives(self.layout(source), 'accidental')], ['accidentalSharp', 'accidentalNatural']) conflict = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note(alter=1, extra='a') + '4' + tests.test_musicxml.xml_note(extra='b'))) self.assertEqual(len(self.primitives(self.layout(conflict), 'accidental')), 2) - def test_dense_measure_splits_exactly_without_modifying_music(self): + def test_line_breaks_only_use_complete_measures_even_when_part_would_fit(self): + content = ''.join(tests.test_musicxml.xml_note(duration=1, kind='quarter') for _ in range(4)) + source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 3)) + index = generator.engraving.notation.ScoreIndex(source) + style = generator.engraving.types.Style() + trial = generator.engraving.layout.make_system(index, fractions.Fraction(), fractions.Fraction(5), self.fonts, style) + style = dataclasses.replace(style, width=math.ceil(trial.box.right - trial.box.left + 2 * style.margin_x + 3)) + layout = self.layout(source, style) + self.assertEqual(layout.pages[0].systems[0].end, 4) + self.assertEqual(layout.pages[0].systems[1].start, 4) + self.assertTrue(all(s.start in index.boundaries and s.end in index.boundaries for p in layout.pages for s in p.systems)) + self.assertFalse(self.primitives(layout, 'continuation')) + self.assertEqual(len(self.primitives(layout, 'barline')), 3) + self.assert_paired_pages(layout) + def test_final_short_line_extends_staff_without_stretching_notes(self): + source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note(duration=2, kind='half') * 2)) + style = generator.engraving.types.Style() + natural = generator.engraving.layout.layout_systems(source, style, self.fonts)[0] + layout = self.layout(source, style) + final = layout.pages[0].systems[0] + for before, after in zip(natural.anchors, final.anchors): + self.assertAlmostEqual(after.x, style.margin_x + 1 + before.x * final.scale) + self.assert_paired_pages(layout) + def test_beamed_tuplet_can_continue_across_an_ordinary_line_break(self): + modification = '32' + notes = [tests.test_musicxml.xml_note('G', 1, 'eighth', extra=modification + f'{action}') for action in ('begin', *(['continue'] * 22), 'end')] + source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([''.join(notes[:12]), ''.join(notes[12:])], divisions=3)) + events = tests.test_musicxml.notes(source) + tests.test_musicxml.notes(source, 1) + source['staves'][0]['tuplets'] = [{'id': 'across-line', 'voice': '1', 'start': events[0]['id'], 'end': events[-1]['id'], 'note_count': 3, 'space_count': 2, 'depth': 0, 'bracket': 'yes'}] + for event in events: + event['tuplet_ids'] = ['across-line'] + layout = self.layout(source, generator.engraving.types.Style(width=800)) + systems = [s for p in layout.pages for s in p.systems] + self.assertGreater(len(systems), 1) + for system in systems: + self.assertTrue(any(p.role == 'beam' for p in system.primitives)) + self.assertTrue(any(p.role == 'tuplet-number' and 'across-line' in p.sources for p in system.primitives)) + self.assertEqual(len(self.primitives(layout, 'barline')), 2) + def test_accidental_memory_and_grace_groups_follow_the_measure_not_the_line(self): + grace = 'D416th' + content = ''.join(grace + tests.test_musicxml.xml_note(duration=1, kind='quarter', alter=1) for _ in range(4)) + source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 2)) + layout = self.layout(source, generator.engraving.types.Style(width=700)) + systems = [s for p in layout.pages for s in p.systems] + self.assertGreater(len(systems), 1) + self.assertEqual([p.value for p in self.primitives(layout, 'accidental')], ['accidentalSharp', 'accidentalSharp']) + events = tests.test_musicxml.notes(source) + tests.test_musicxml.notes(source, 1) + for grace, principal in zip(events[::2], events[1::2]): + for system in systems: + identifiers = {a.event for a in system.anchors} + self.assertEqual(grace['id'] in identifiers, principal['id'] in identifiers) + def test_tab_ties_enter_and_leave_whole_measure_systems(self): + bars = [] + for ties in ('', '', ''): + content = tests.test_musicxml.xml_note('E', octave=5, extra='held' + ties) + '4' + content += ''.join(tests.test_musicxml.xml_note('G', 1, 'quarter', octave=5, extra='moving') for _ in range(4)) + bars.append(content) + source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars(bars)) + for measure in source['staves'][0]['measures']: + measure['transpose']['octave_change'] = -1 + generator.guitar.place_guitar(source) + style = generator.engraving.types.score_style(source, generator.engraving.types.Style()) + index = generator.engraving.notation.ScoreIndex(source) + for i, (start, end) in enumerate(((0, 4), (4, 8), (8, 12))): + tone = source['staves'][0]['measures'][i]['voices'][0]['notes'][0]['pitches'][0] + held = tone['id'] + system = generator.engraving.layout.make_system(index, fractions.Fraction(start), fractions.Fraction(end), self.fonts, style) + number = next(p for p in system.primitives if p.role == 'tab' and held in p.sources) + ties = [p for p in system.primitives if p.role == 'tab-tie' and {held, tone.get('tie_from')} & set(p.sources)] + self.assertEqual(sum(p.points[-1][0] < number.box.left for p in ties), int(start > 0)) + self.assertEqual(sum(p.points[0][0] > number.box.right for p in ties), int(end < 12)) + def test_dense_measure_fails_contextually_without_modifying_music(self): content = ''.join(tests.test_musicxml.xml_note('G', 1, '64th', extra='fast') for _ in range(64)) content += '64' + tests.test_musicxml.xml_note('C', 64, 'whole', extra='held') source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(content, divisions=16)) - layout = self.layout(source) - systems = [s for p in layout.pages for s in p.systems] - self.assertGreater(len(systems), 1) - held = source['staves'][0]['measures'][0]['voices'][1]['notes'][0]['pitches'][0]['id'] - pieces = [f for s in systems for f in s.fragments if f.source == held] - self.assertEqual(sum(f.length() for f in pieces), 4) - self.assertGreater(len(pieces), 1) - self.assertTrue(self.primitives(layout, 'tie')) - for page in layout.pages: - self.assertEqual(generator.engraving.raster.rasterize_page(page, self.fonts).size, (976, 484)) + before = copy.deepcopy(source) + with self.assertRaisesRegex(generator.engraving.types.EngravingError, 'measure 1 .*complete measure exceeds.*readable spacing'): + self.layout(source) + self.assertEqual(source, before) def test_hidden_pitch_does_not_suppress_a_visible_accidental(self): source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note(duration=2, kind='half', alter=1) + tests.test_musicxml.xml_note(duration=2, kind='half', alter=1))) tests.test_musicxml.notes(source)[0]['hidden'] = True @@ -263,11 +335,10 @@ class EngravingTests(unittest.TestCase): index = generator.engraving.notation.ScoreIndex(source) self.assertEqual(index.pitch_position(tests.test_musicxml.notes(source)[0]['pitches'][0], fractions.Fraction()), original - 7) self.assertTrue(any(p.value == '8va' for p in self.primitives(self.layout(source), 'octave-shift'))) - def test_wide_pitch_range_can_break_inside_a_measure_vertically(self): + def test_excessive_pitch_range_fails_instead_of_splitting_the_measure(self): source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note(duration=2, kind='half', octave=10) + tests.test_musicxml.xml_note(duration=2, kind='half', octave=-1))) - layout = self.layout(source) - self.assertEqual([len(page.systems) for page in layout.pages], [2]) - self.assertEqual(len(self.primitives(layout, 'head')), 2) + with self.assertRaisesRegex(generator.engraving.types.EngravingError, 'measure 1 .*complete measure exceeds.*height'): + self.layout(source) def test_cue_grace_and_tremolo_are_drawn(self): grace = 'D416th' source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(grace + tests.test_musicxml.xml_note(duration=4, kind='whole', prefix=''))) @@ -283,8 +354,8 @@ class EngravingTests(unittest.TestCase): tab = tests.arrangement_fixtures.section_document(tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('C', octave=6))), 'electric', rhythms=tests.rhythm_fixtures.library()) self.assertEqual(self.primitives(self.layout(tab), 'tab')[0].value, '20') def test_generated_tab_shares_columns_and_systems_without_duplicating_music(self): - content = ''.join(tests.test_musicxml.xml_note('G', 1, '64th') for _ in range(64)) - source = tests.arrangement_fixtures.section_document(tests.test_musicxml.parse(tests.test_musicxml.xml_score(content, divisions=16)), 'electric', rhythms=tests.rhythm_fixtures.library()) + content = ''.join(tests.test_musicxml.xml_note('G', 1, 'eighth') for _ in range(8)) + source = tests.arrangement_fixtures.section_document(tests.test_musicxml.parse(tests.test_accompaniment.xml_bars([content] * 8, divisions=2)), 'electric', rhythms=tests.rhythm_fixtures.library()) layout = self.layout(source) systems = [s for page in layout.pages for s in page.systems] self.assertGreater(len(systems), 1) @@ -364,8 +435,8 @@ class EngravingTests(unittest.TestCase): self.assertEqual([p.value for p in self.primitives(layout, 'clef')], ['gClef', 'cClef']) def test_lyric_extender_continues_on_systems_without_syllables(self): lyrics = 'Ah' - content = ''.join(tests.test_musicxml.xml_note('G', 1, '64th', extra=lyrics if i == 0 else '') for i in range(64)) - source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(content, divisions=16)) + bars = [''.join(tests.test_musicxml.xml_note('G', 1, 'quarter', extra=lyrics if bar == i == 0 else '') for i in range(4)) for bar in range(8)] + source = tests.test_musicxml.parse(tests.test_accompaniment.xml_bars(bars)) layout = self.layout(source) systems = [s for p in layout.pages for s in p.systems] self.assertGreater(len(systems), 1) diff --git a/tests/test_guitar.py b/tests/test_guitar.py index bad4ee2..dc704af 100644 --- a/tests/test_guitar.py +++ b/tests/test_guitar.py @@ -21,46 +21,100 @@ import tests.test_musicxml class GuitarTests(unittest.TestCase): def test_fret_boundaries_and_span(self): - self.assertIn(generator.guitar.TabPosition(6, 0, 40), generator.guitar.positions_for_pitch(fractions.Fraction(40))) + self.assertFalse(generator.guitar.positions_for_pitch(fractions.Fraction(40))) + self.assertIn(generator.guitar.TabPosition(6, 1, 41), generator.guitar.positions_for_pitch(fractions.Fraction(41))) self.assertIn(generator.guitar.TabPosition(1, 20, 84), generator.guitar.positions_for_pitch(fractions.Fraction(84))) self.assertFalse(generator.guitar.positions_for_pitch(fractions.Fraction(85))) self.assertFalse(generator.guitar.positions_for_pitch(fractions.Fraction(129, 2))) self.assertTrue(generator.guitar.within_fret_span((generator.guitar.TabPosition(4, 11, 61), generator.guitar.TabPosition(3, 12, 67), generator.guitar.TabPosition(2, 14, 73)))) self.assertFalse(generator.guitar.within_fret_span((generator.guitar.TabPosition(6, 0, 40), generator.guitar.TabPosition(1, 20, 84)))) self.assertFalse(generator.guitar.within_fret_span((generator.guitar.TabPosition(4, 10, 60), generator.guitar.TabPosition(1, 20, 84)))) - def test_open_strings_count_in_span_and_proximity(self): - for frets, valid in (((0, 2, 3), True), ((0, 4), True), ((0, 5), False), ((12, 0, 11), False), ((0, 0), True)): + def test_fretted_positions_keep_span_and_proximity_scoring(self): + for frets, valid in (((1, 2, 3), True), ((1, 5), True), ((1, 6), False), ((12, 1, 11), False), ((3, 3), True)): with self.subTest(frets=frets): positions = tuple(generator.guitar.TabPosition(6 - i, fret, 0) for i, fret in enumerate(frets)) self.assertEqual(generator.guitar.within_fret_span(positions), valid) self.assertEqual(generator.guitar.compactness(positions), max(frets) - min(frets)) self.assertEqual(generator.guitar.position_center(positions)[0], min(frets) + max(frets)) self.assertTrue(generator.guitar.within_fret_span(())) - def test_held_open_string_cannot_mix_with_high_frets(self): - held = tests.test_musicxml.xml_note('E', octave=2) - high = tests.test_musicxml.xml_note('C', 2, 'half', octave=6, extra='2') - document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(held + '42' + high)) - with self.assertRaisesRegex(ValueError, 'no guitar placement'): + def test_low_open_e_fails_without_octave_fitting_or_mutation(self): + document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', octave=2))) + before = copy.deepcopy(document) + with self.assertRaisesRegex(ValueError, 'measure-1, event .*pitch 40.*1-20'): generator.guitar.place_guitar(document) + self.assertEqual(document, before) + def test_open_string_pitch_uses_a_fretted_alternative(self): + for _, midi in generator.guitar.STANDARD_TUNING[1:]: + positions = generator.guitar.positions_for_pitch(fractions.Fraction(midi)) + self.assertTrue(positions) + self.assertTrue(all(p.fret >= 1 and p.midi == midi for p in positions)) + document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', octave=4))) + generator.guitar.place_guitar(document) + tone = tests.test_musicxml.notes(document)[0]['pitches'][0] + self.assertGreater(tone['tablature']['fret'], 0) + self.assertEqual(generator.model.pitch_number(tone), 64) + def test_explicit_open_tab_is_rejected_without_substitution(self): + document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', octave=4))) + tests.test_musicxml.notes(document)[0]['pitches'][0]['tablature'] = {'string': 1, 'fret': 0} + before = copy.deepcopy(document) + with self.assertRaisesRegex(ValueError, 'event .*1-20'): + generator.guitar.place_guitar(document) + self.assertEqual(document, before) + document['staves'][0]['tablature'] = True + with self.assertRaisesRegex(ValueError, 'invalid generated tablature'): + generator.model.validate_document(document) def test_theory_controls_omissions_and_bass(self): for symbol in ('CM', 'G7b9', 'FM7#11/A', 'CmM7', 'Cdim7', 'C13', 'Csus', 'Cmadd9'): with self.subTest(symbol=symbol): - required, allowed, bass = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol)) - candidates = generator.guitar.chord_positions(required, allowed, bass) + requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol)) + candidates = generator.guitar.chord_positions(requirements) self.assertTrue(candidates) for candidate in candidates: covered = {p.midi % 12 for p in candidate} - self.assertTrue(set(required) <= covered <= set(allowed)) + self.assertTrue(requirements.covers(covered)) + self.assertTrue(covered <= set(requirements.allowed)) self.assertEqual(len(candidate), len({p.string_number for p in candidate})) self.assertTrue(generator.guitar.within_fret_span(candidate)) - if bass is not None: - self.assertEqual(min(p.midi for p in candidate) % 12, bass) - self.assertIn(2, generator.guitar.chord_requirements(generator.harmony.parse_symbol('Cmadd9'))[0]) - self.assertNotIn(7, generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7'))[0]) - self.assertIn(7, generator.guitar.chord_requirements(generator.harmony.parse_symbol('CM'))[0]) - self.assertEqual(generator.guitar.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 0})[2], 0) - self.assertEqual(generator.guitar.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 1})[2], 4) - self.assertTrue(any(p.fret > 12 for c in generator.guitar.chord_positions(*generator.guitar.chord_requirements(generator.harmony.parse_symbol('CM'))) for p in c)) + self.assertTrue(all(1 <= p.fret <= 20 for p in candidate)) + if requirements.basses: + self.assertIn(min(p.midi for p in candidate) % 12, requirements.basses) + self.assertIn(2, generator.guitar.chord_requirements(generator.harmony.parse_symbol('Cmadd9')).required) + self.assertNotIn(7, generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7')).required) + self.assertEqual(generator.guitar.chord_requirements(generator.harmony.parse_symbol('CM')).foundation, (0, 7)) + self.assertEqual(generator.guitar.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 0}).basses, (0,)) + self.assertEqual(generator.guitar.chord_requirements({**generator.harmony.parse_symbol('CM'), 'inversion': 1}).basses, (4,)) + self.assertTrue(any(p.fret > 12 for c in generator.guitar.chord_positions(generator.guitar.chord_requirements(generator.harmony.parse_symbol('CM'))) for p in c)) + def test_root_or_fifth_is_required_for_every_generated_profile(self): + for profile in ('', 'acoustic', 'electric'): + requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol('C7'), profile) + for classes in ({0, 4, 10}, {4, 7, 10}, {0, 4, 7, 10}): + self.assertTrue(requirements.covers(classes)) + self.assertFalse(requirements.covers({4, 10})) + candidates = generator.guitar.chord_positions(requirements, prune=False, profile=profile) + self.assertTrue(candidates) + self.assertTrue(all({p.midi % 12 for p in shape} & {0, 7} for shape in candidates)) + self.assertEqual({frozenset({p.midi % 12 for p in shape} & {0, 7}) for shape in candidates}, {frozenset({0}), frozenset({7}), frozenset({0, 7})}) + def test_altered_and_removed_fifths_keep_their_semantics(self): + for symbol, fifth in (('Cdim7', 6), ('Caug7', 8)): + requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol), 'electric') + self.assertEqual(requirements.foundation, (0, fifth)) + self.assertIn(fifth, requirements.required) + self.assertNotIn(7, requirements.allowed) + for omitted, remaining in ((1, 7), (5, 0)): + chord = generator.harmony.parse_symbol('C7') + chord['degrees'].append({'value': omitted, 'alter': 0, 'type': 'subtract'}) + for profile in ('acoustic', 'electric'): + requirements = generator.guitar.chord_requirements(chord, profile) + self.assertEqual(requirements.foundation, (remaining,)) + self.assertTrue(all(remaining in {p.midi % 12 for p in c} for c in generator.guitar.chord_positions(requirements, profile=profile))) + chord['degrees'].append({'value': 1, 'alter': 0, 'type': 'subtract'}) + with self.assertRaisesRegex(ValueError, 'root or fifth'): + generator.guitar.chord_requirements(chord) + def test_explicit_degree_chord_uses_the_same_root_or_fifth_rule(self): + standard = generator.harmony.parse_symbol('C7') + explicit = {**standard, 'kind': 'other', 'degrees': [{'value': d, 'alter': a, 'type': 'add'} for d, a in generator.harmony.degrees(standard).items()]} + for profile in ('', 'acoustic', 'electric'): + self.assertEqual(generator.guitar.chord_requirements(standard, profile), generator.guitar.chord_requirements(explicit, profile)) def test_melody_placement_preserves_notes_timing_and_sound(self): source = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note('E', 2, 'half', octave=4) + tests.test_musicxml.xml_note('F', 2, 'half', octave=4))) before = copy.deepcopy(source) @@ -161,7 +215,7 @@ class GuitarTests(unittest.TestCase): generator.guitar.place_guitar(document) self.assertEqual(before, document) def test_source_contract_and_unplayable_music_fail(self): - cases = [(tests.test_musicxml.xml_note(octave=7, alter=1), '', '0-20'), (tests.test_musicxml.xml_note(alter='0.5'), '', '0-20'), + cases = [(tests.test_musicxml.xml_note(octave=7, alter=1), '', '1-20'), (tests.test_musicxml.xml_note(alter='0.5'), '', '1-20'), (tests.test_musicxml.xml_note(), '1', 'capo')] for content, attributes, error in cases: with self.subTest(error=error), self.assertRaisesRegex(ValueError, error): @@ -187,13 +241,13 @@ class GuitarTests(unittest.TestCase): output.mkdir() (output / 'last-good').write_text('preserved') args = argparse.Namespace(data_dir=root / 'data', output_dir=output, static_assets_dir=pathlib.Path('package/assets'), check=False) - with unittest.mock.patch('generator.rhythm.load_library', return_value=tests.rhythm_fixtures.library()), self.assertRaisesRegex(ValueError, '0001.xml, part electric:.*0-20'): + with unittest.mock.patch('generator.rhythm.load_library', return_value=tests.rhythm_fixtures.library()), self.assertRaisesRegex(ValueError, '0001.xml, part electric:.*1-20'): generator.__main__.generate_assets(args) self.assertEqual([p.name for p in output.iterdir()], ['last-good']) self.assertEqual((output / 'last-good').read_text(), 'preserved') harmony = 'Cdominant9-1add111add' (root / 'data/songs/0001.xml').write_text(tests.test_musicxml.xml_score(harmony + tests.test_musicxml.xml_note())) - with unittest.mock.patch('generator.rhythm.load_library', return_value=tests.rhythm_fixtures.library()), self.assertRaisesRegex(ValueError, '0001.xml, part acoustic:.*measure 1.*no acoustic grip.*0-20'): + with unittest.mock.patch('generator.rhythm.load_library', return_value=tests.rhythm_fixtures.library()), self.assertRaisesRegex(ValueError, '0001.xml, part acoustic:.*measure 1.*no acoustic grip.*1-20'): generator.__main__.generate_assets(args) self.assertEqual([p.name for p in output.iterdir()], ['last-good']) self.assertEqual((output / 'last-good').read_text(), 'preserved') diff --git a/tests/test_guitar_parts.py b/tests/test_guitar_parts.py index 0050f9f..d00f22a 100644 --- a/tests/test_guitar_parts.py +++ b/tests/test_guitar_parts.py @@ -27,17 +27,19 @@ class GuitarPartTests(unittest.TestCase): for profile in ('acoustic', 'electric'): with self.subTest(symbol=symbol, profile=profile): definition = generator.harmony.parse_symbol(symbol) - required, allowed, bass = generator.guitar.chord_requirements(definition, profile) - candidates = generator.guitar.chord_positions(required, allowed, bass, profile=profile) + requirements = generator.guitar.chord_requirements(definition, profile) + candidates = generator.guitar.chord_positions(requirements, profile=profile) self.assertTrue(candidates) for candidate in candidates: strings = tuple(p.string_number for p in candidate) self.assertIn(strings, generator.guitar.ACOUSTIC_STRINGS if profile == 'acoustic' else generator.guitar.ELECTRIC_STRINGS) - self.assertTrue(set(required) <= {p.midi % 12 for p in candidate} <= set(allowed)) + self.assertTrue(requirements.covers({p.midi % 12 for p in candidate})) + self.assertTrue({p.midi % 12 for p in candidate} <= set(requirements.allowed)) self.assertTrue(generator.guitar.within_fret_span(candidate)) + self.assertTrue(all(1 <= p.fret <= 20 for p in candidate)) chord = generator.harmony.parse_symbol('C7') - self.assertNotIn(0, generator.guitar.chord_requirements(chord, 'electric')[0]) - candidates = generator.guitar.chord_positions(*generator.guitar.chord_requirements(chord, 'electric'), profile='electric') + self.assertNotIn(0, generator.guitar.chord_requirements(chord, 'electric').required) + candidates = generator.guitar.chord_positions(generator.guitar.chord_requirements(chord, 'electric'), profile='electric') self.assertTrue(any(all(p.midi % 12 != 0 for p in c) for c in candidates)) def test_acoustic_combines_independent_rhythms_with_a_fixed_grip(self): result = self.section(tests.test_parts.xml_harmony() + tests.test_musicxml.xml_note(), 'acoustic') @@ -47,7 +49,8 @@ class GuitarPartTests(unittest.TestCase): bass = [(n['onset'], p) for n in notes for p in n['pitches'] if p['tablature']['string'] >= 5 and 'tie_from' not in p] upper = {generator.model.fraction(n['onset']) for n in notes for p in n['pitches'] if p['tablature']['string'] <= 4 and 'tie_from' not in p} self.assertNotEqual({generator.model.fraction(onset) for onset, _ in bass}, upper) - self.assertEqual([generator.model.pitch_number(p) % 12 for _, p in bass], [7, 7]) + self.assertEqual(len(bass), 2) + self.assertIn([generator.model.pitch_number(p) % 12 for _, p in bass], ([0, 0], [7, 7])) self.assertEqual(bass[0][1]['tablature'], bass[1][1]['tablature']) upper_shapes = {tuple((p['tablature']['string'], p['tablature']['fret']) for p in n['pitches'] if p['tablature']['string'] <= 4) for n in notes} self.assertEqual(len(upper_shapes - {()}), 1) diff --git a/tests/test_music_model.py b/tests/test_music_model.py index 3b0a680..e765fc0 100644 --- a/tests/test_music_model.py +++ b/tests/test_music_model.py @@ -38,7 +38,7 @@ class MusicModelTests(unittest.TestCase): self.assertEqual(set(generator.harmony.pitch_classes(generator.harmony.parse_symbol(label))), expected, label) for label, essentials in [('G7b9', {11, 5, 8}), ('C13', {4, 10, 9})]: definition = generator.harmony.parse_symbol(label) - pitches = generator.guitar.chord_pitches(definition, generator.guitar.chord_positions(*generator.guitar.chord_requirements(definition))[0]) + pitches = generator.guitar.chord_pitches(definition, generator.guitar.chord_positions(generator.guitar.chord_requirements(definition))[0]) self.assertTrue(essentials.issubset({generator.model.pitch_number(p) % 12 for p in pitches}), label) with self.assertRaises(ValueError): generator.harmony.parse_symbol('Cunrecognized') diff --git a/tests/test_score_bundle.py b/tests/test_score_bundle.py index c3c3902..1c46ba1 100644 --- a/tests/test_score_bundle.py +++ b/tests/test_score_bundle.py @@ -64,6 +64,11 @@ class ScoreBundleTests(unittest.TestCase): with self.assertRaisesRegex(ValueError, 'write the music out'): generator.__main__.generate_assets(args) self.assertEqual(original, {p.relative_to(output): p.read_bytes() for p in output.rglob('*') if p.is_file()}) + dense = ''.join(tests.test_musicxml.xml_note('G', 1, '64th') for _ in range(64)) + source.write_text(tests.test_musicxml.xml_score(dense, divisions=16)) + with self.assertRaisesRegex(generator.engraving.types.EngravingError, r'songs/fixture-electric.json: measure 1 .*complete measure exceeds'): + generator.__main__.generate_assets(args) + self.assertEqual(original, {p.relative_to(output): p.read_bytes() for p in output.rglob('*') if p.is_file()}) source.write_text(tests.test_musicxml.xml_score(tests.test_musicxml.xml_note(extra='other'))) with self.assertRaisesRegex(generator.engraving.types.EngravingError, 'unavailableGlyph'): generator.__main__.generate_assets(args) diff --git a/tests/test_voicing.py b/tests/test_voicing.py index 7e505d3..abed34a 100644 --- a/tests/test_voicing.py +++ b/tests/test_voicing.py @@ -17,10 +17,9 @@ def grip(frets, strings=(4, 3, 2, 1)): def ranked(symbol, melody=(), profile='electric', prune=False): definition = generator.harmony.parse_symbol(symbol) - required, allowed, bass = generator.guitar.chord_requirements(definition, profile) - root = int((generator.model.NATURAL[definition['root']['letter']] + generator.model.alteration(definition['root']['alter'])) % 12) - candidates = generator.guitar.chord_positions(required, allowed, bass, prune=prune, profile=profile, melody=melody, root=root) - return sorted(candidates, key=lambda shape: (generator.guitar.voicing_cost(shape, profile, allowed, root, melody), shape)) + requirements = generator.guitar.chord_requirements(definition, profile) + candidates = generator.guitar.chord_positions(requirements, prune=prune, profile=profile, melody=melody) + return sorted(candidates, key=lambda shape: (generator.guitar.voicing_cost(shape, profile, requirements.allowed, melody), shape)) class VoicingTests(unittest.TestCase): def test_requested_shapes_win_without_a_shape_dictionary(self): @@ -28,34 +27,37 @@ class VoicingTests(unittest.TestCase): with self.subTest(symbol=symbol): self.assertEqual(ranked(symbol, (melody,))[0], grip(expected)) def test_four_strings_precede_melody_height_and_root_presence(self): - candidates = ranked('CM7', (64,)) - four = next(c for c in candidates if len(c) == 4 and max(p.midi for p in c) > 64) - three = next(c for c in candidates if len(c) == 3 and max(p.midi for p in c) == 64) + candidates = ranked('C#M7', (65,)) + four = next(c for c in candidates if len(c) == 4 and max(p.midi for p in c) > 65) + three = next(c for c in candidates if len(c) == 3 and max(p.midi for p in c) == 65) self.assertLess(candidates.index(four), candidates.index(three)) candidates = ranked('FM7', (69,)) rootless = next(c for c in candidates if len(c) == 4 and 5 not in {p.midi % 12 for p in c}) rooted = next(c for c in candidates if len(c) == 3 and 5 in {p.midi % 12 for p in c}) self.assertLess(candidates.index(rootless), candidates.index(rooted)) def test_melody_on_top_precedes_root_among_four_string_shapes(self): - candidates = ranked('C7b9', (64,)) + candidates = ranked('C#M7', (65,)) best = candidates[0] self.assertEqual(len(best), 4) - self.assertEqual(max(p.midi for p in best), 64) - self.assertNotIn(0, {p.midi % 12 for p in best}) - self.assertTrue(any(0 in {p.midi % 12 for p in c} and max(p.midi for p in c) > 64 for c in candidates)) - def test_root_precedes_physical_cost_without_covering_the_melody(self): - self.assertEqual(ranked('CM7', (67,))[0], grip((2, 4, 1, 3))) - rooted = generator.guitar.voicing_cost(grip((2, 4, 1, 3)), 'electric', (0, 4, 7, 11), 0, (67,)) - rootless = generator.guitar.voicing_cost(grip((5, 4, 5, 3)), 'electric', (0, 4, 7, 11), 0, (67,)) - self.assertGreater(rooted.effort, rootless.effort) + self.assertEqual(max(p.midi for p in best), 65) + self.assertNotIn(1, {p.midi % 12 for p in best}) + self.assertTrue(any(1 in {p.midi % 12 for p in c} and max(p.midi for p in c) > 65 for c in candidates)) + def test_lower_position_can_balance_a_wider_span_without_covering_melody(self): + candidates = ranked('CM7', (67,)) + self.assertEqual(candidates[0], grip((2, 4, 1, 3))) + self.assertIn(grip((5, 4, 5, 3)), candidates) + rooted = generator.guitar.voicing_cost(grip((2, 4, 1, 3)), 'electric', (0, 4, 7, 11), (67,)) + rootless = generator.guitar.voicing_cost(grip((5, 4, 5, 3)), 'electric', (0, 4, 7, 11), (67,)) + self.assertEqual(rooted.effort, rootless.effort) self.assertLess(rooted, rootless) def test_essential_extensions_are_never_relaxed_to_add_the_root(self): for symbol in ('C7b9', 'FM7#11', 'C13', 'Cdim7'): definition = generator.harmony.parse_symbol(symbol) - required, allowed, _ = generator.guitar.chord_requirements(definition, 'electric') + requirements = generator.guitar.chord_requirements(definition, 'electric') for candidate in ranked(symbol): classes = {p.midi % 12 for p in candidate} - self.assertTrue(set(required) <= classes <= set(allowed)) + self.assertTrue(requirements.covers(classes)) + self.assertTrue(classes <= set(requirements.allowed)) def test_explicitly_removed_root_is_not_restored_or_penalized(self): document = tests.test_musicxml.parse(tests.test_musicxml.xml_score(tests.test_parts.xml_harmony(kind='major-seventh') + tests.test_musicxml.xml_note('E', octave=5))) measure = document['staves'][0]['measures'][0] @@ -67,34 +69,49 @@ class VoicingTests(unittest.TestCase): _, tones = generator.guitar.guitar_events(document) candidates = list(generator.guitar.attack_candidates([note], tones, {}, {}, False)) self.assertTrue(candidates) - self.assertTrue(all(c.cost.missing_root == 0 and all(p.midi % 12 != 0 for p in c.shape) for c in candidates)) - def test_frets_zero_through_twelve_have_no_height_cost(self): - for profile in ('', 'electric', 'acoustic'): - for fret in range(13): + self.assertTrue(all(all(p.midi % 12 != 0 for p in c.shape) and any(p.midi % 12 == 7 for p in c.shape) for c in candidates)) + def test_chord_height_cost_increases_throughout_the_fretboard(self): + for profile in ('electric', 'acoustic'): + for fret in range(1, 21): with self.subTest(profile=profile, fret=fret): - self.assertEqual(generator.guitar.voicing_cost(grip((fret,) * 4), profile).effort, 0) + self.assertEqual(generator.guitar.voicing_cost(grip((fret,) * 4), profile).effort, fret + 2 * max(0, fret - 12)) self.assertEqual(generator.guitar.voicing_cost((), profile).effort, 0) - def test_height_cost_only_counts_frets_above_twelve(self): - for profile in ('', 'electric', 'acoustic'): - for fret in range(13, 21): - with self.subTest(profile=profile, fret=fret): - self.assertEqual(generator.guitar.voicing_cost(grip((fret,) * 4), profile).effort, 2 * (fret - 12)) + def test_exact_melody_height_scoring_remains_unchanged(self): + for fret in range(1, 21): + self.assertEqual(generator.guitar.voicing_cost(grip((fret,) * 4)).effort, 2 * max(0, fret - 12)) crossing = grip((11, 12, 13, 14)) self.assertEqual(generator.guitar.voicing_cost(crossing).effort, 7) - self.assertEqual(generator.guitar.voicing_cost(crossing, 'acoustic').effort, 19) + self.assertEqual(generator.guitar.voicing_cost(crossing, 'electric').effort, 21) + self.assertEqual(generator.guitar.voicing_cost(crossing, 'acoustic').effort, 18) + self.assertEqual(generator.guitar.voicing_cost(crossing, 'acoustic').simplicity, 15) def test_span_and_acoustic_simplicity_costs_remain_unchanged(self): - shape = grip((0, 2, 3, 4)) + shape = grip((1, 3, 4, 5)) self.assertEqual(generator.guitar.voicing_cost(shape).effort, 4) - self.assertEqual(generator.guitar.voicing_cost(shape, 'acoustic').effort, 18) + self.assertEqual(generator.guitar.voicing_cost(shape, 'electric').effort, 9) + self.assertEqual(generator.guitar.voicing_cost(shape, 'acoustic').effort, 5) + self.assertEqual(generator.guitar.voicing_cost(shape, 'acoustic').simplicity, 18) for profile in ('', 'electric', 'acoustic'): - self.assertLess(generator.guitar.voicing_cost(grip((8, 8, 8, 8)), profile), generator.guitar.voicing_cost(grip((2, 4, 1, 3)), profile)) + self.assertLess(generator.guitar.voicing_cost(grip((4, 4, 4, 4)), profile), generator.guitar.voicing_cost(grip((2, 4, 1, 3)), profile)) + def test_lower_acoustic_half_diminished_and_dominant_grips_win(self): + high = [grip((12, 12, 12, 11), (6, 4, 3, 2)), grip((12, 11, 12, 11), (6, 4, 3, 2))] + low = [grip((7, 8, 7, 8), (5, 4, 3, 2)), grip((7, 8, 6, 8), (5, 4, 3, 2))] + for symbol, upper, lower in zip(('Em7b5', 'A7b9'), high, low): + requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol(symbol), 'acoustic') + candidates = generator.guitar.chord_positions(requirements, profile='acoustic') + self.assertIn(upper, candidates) + self.assertIn(lower, candidates) + self.assertEqual({p.midi % 12 for p in upper}, {p.midi % 12 for p in lower}) + self.assertEqual(min(p.midi for p in upper), min(p.midi for p in lower)) + self.assertLess(generator.guitar.voicing_cost(lower, 'acoustic', requirements.allowed), generator.guitar.voicing_cost(upper, 'acoustic', requirements.allowed)) + costs = [sum(generator.guitar.voicing_cost(s, 'acoustic').effort for s in pair) + generator.guitar.acoustic_movement(*pair) for pair in (low, high)] + self.assertEqual(costs, [17, 25]) def test_acoustic_simpler_three_note_shape_can_beat_four_notes(self): - candidates = ranked('C6', profile='acoustic') + candidates = ranked('Csus', profile='acoustic') best = candidates[0] self.assertEqual(len(best), 3) self.assertTrue(any(len(c) == 4 for c in candidates)) - required, _, _ = generator.guitar.chord_requirements(generator.harmony.parse_symbol('C6'), 'acoustic') - self.assertTrue(set(required) <= {p.midi % 12 for p in best}) + requirements = generator.guitar.chord_requirements(generator.harmony.parse_symbol('Csus'), 'acoustic') + self.assertTrue(requirements.covers({p.midi % 12 for p in best})) def test_nearby_position_beats_lower_frets_within_comfort_range(self): low, nearby, previous = (grip((f,) * 4) for f in (3, 8, 9)) for profile in ('', 'electric', 'acoustic'): @@ -104,7 +121,7 @@ class VoicingTests(unittest.TestCase): for profile in ('', 'electric', 'acoustic'): self.assertLess(generator.guitar.voicing_cost(high, profile), generator.guitar.voicing_cost(low, profile).moved(generator.guitar.movement(high, low))) def test_candidate_pruning_uses_full_cost_and_preserves_each_center(self): - for symbol, profile, melody in (('FM7', 'electric', (69,)), ('C7', 'acoustic', ()), ('Gm7', 'electric', (67,))): + for symbol, profile, melody in (('FM7', 'electric', (69,)), ('Gm7', 'electric', (67,))): best = {} for shape in ranked(symbol, melody, profile): best.setdefault(generator.guitar.position_center(shape), shape)