@@ -15,7 +15,7 @@ def staff_attributes(index: ScoreIndex, state: dict, time: Fraction, fonts: Font
|
||||
lines = state['staff_details']['lines']
|
||||
if 'clef' in fields and clef['sign'] != 'none':
|
||||
sign = clef['sign']
|
||||
name = {'G': 'gClef', 'F': 'fClef', 'C': 'cClef', 'percussion': 'unpitchedPercussionClef1', 'TAB': '6stringTabClef' if lines != 4 else '4stringTabClef'}[sign]
|
||||
name = {'G': 'gClef', 'F': 'fClef', 'C': 'cClef', 'percussion': 'unpitchedPercussionClef1'}[sign]
|
||||
y = -space * (clef.get('line', 2) - 1) if sign in ('G', 'F', 'C') else -space * (lines - 1) / 2
|
||||
glyph = fonts.glyph(name, x, y, space, 'clef', references)
|
||||
result.append(glyph)
|
||||
@@ -23,7 +23,7 @@ def staff_attributes(index: ScoreIndex, state: dict, time: Fraction, fonts: Font
|
||||
label = str(abs(octave) * 7 + 1)
|
||||
result.append(fonts.text(label, (glyph.box.left + glyph.box.right) / 2, glyph.box.top - 2 if octave > 0 else glyph.box.bottom + 14, 14, 'clef-octave', references, True))
|
||||
x = bounds(result).right + space
|
||||
if 'key' in fields and clef['sign'] not in ('percussion', 'TAB'):
|
||||
if 'key' in fields and clef['sign'] != 'percussion':
|
||||
key = state['key']
|
||||
steps = key.get('steps')
|
||||
if steps is None:
|
||||
|
||||
@@ -34,7 +34,7 @@ def audit_layout(document: dict, layout: LayoutDocument, fonts: Fonts) -> None:
|
||||
expected = index.pitch_position(index.tones[anchor.source][1], anchor.time)
|
||||
if abs(anchor.y - (baseline - expected * layout.style.space / 2)) > 0.01:
|
||||
raise EngravingError(f'{anchor.source}: displayed staff position changed pitch')
|
||||
role = 'tab' if anchor.fragment.kind == 'tone' and index.state(anchor.time)['clef']['sign'] == 'TAB' else 'head' if anchor.fragment.kind == 'tone' else anchor.fragment.kind
|
||||
role = 'head' if anchor.fragment.kind == 'tone' else anchor.fragment.kind
|
||||
candidates = [p for p in system.primitives if p.role == role and anchor.source in p.sources and abs(p.points[0][0] - anchor.x) < 0.01]
|
||||
if not candidates:
|
||||
raise EngravingError(f'{anchor.source}: visible anchor has no corresponding drawing primitive')
|
||||
@@ -43,6 +43,15 @@ def audit_layout(document: dict, layout: LayoutDocument, fonts: Fonts) -> None:
|
||||
expected_glyph = head_glyph({'type': anchor.fragment.written_type}, tone.get('notehead', event.value.get('notehead')))
|
||||
if candidates[0].value != expected_glyph:
|
||||
raise EngravingError(f'{anchor.source}: notehead changed written duration or shape')
|
||||
if index.staff.get('tablature') and anchor.fragment.kind == 'tone':
|
||||
tone = index.tones[anchor.source][1]
|
||||
tab = tone['tablature']
|
||||
tab_baseline = max(p.points[0][1] for p in system.primitives if p.role == 'tab-staff')
|
||||
expected_y = tab_baseline - (6 - tab['string']) * layout.style.space + 5
|
||||
expected_x = min(a.x for a in system.anchors if a.event == anchor.event and a.time == anchor.time)
|
||||
tabs = [p for p in system.primitives if p.role == 'tab' and anchor.source in p.sources]
|
||||
if len(tabs) != 1 or tabs[0].value != str(tab['fret']) or abs(tabs[0].points[0][1] - expected_y) > 0.01 or abs(tabs[0].points[0][0] - expected_x) > 0.01:
|
||||
raise EngravingError(f'{anchor.source}: tablature changed position or rhythmic alignment')
|
||||
for fragment in system.fragments:
|
||||
fragments.setdefault(fragment.source, []).append(fragment)
|
||||
# Staff, ledger, stem, beam and tie contacts are intentional. Distinct
|
||||
@@ -50,7 +59,7 @@ def audit_layout(document: dict, layout: LayoutDocument, fonts: Fonts) -> None:
|
||||
checked = [p for p in system.primitives if p.role in ('head', 'tab', 'accidental', 'harmony', 'lyric')]
|
||||
for i, item in enumerate(checked):
|
||||
for other in checked[i + 1:]:
|
||||
if set(item.sources) & set(other.sources):
|
||||
if set(item.sources) & set(other.sources) and (item.role != 'tab' or other.role != 'tab'):
|
||||
continue
|
||||
if item.role != other.role and {item.role, other.role} - {'head', 'tab', 'accidental'}:
|
||||
continue
|
||||
|
||||
@@ -5,6 +5,7 @@ from fractions import Fraction
|
||||
import math
|
||||
|
||||
from generator import harmony
|
||||
from generator.engraving import tablature
|
||||
from generator.model import fraction, validate_document
|
||||
from generator.engraving.attributes import barline, staff_attributes, tempo
|
||||
from generator.engraving.fonts import Fonts
|
||||
@@ -36,6 +37,7 @@ def column(index: ScoreIndex, time: Fraction, start: Fraction, end: Fraction, fo
|
||||
grace_width = max((p.box.right for p in drawn), default=grace_width) + 9
|
||||
occupied.extend(p for p in drawn if p.role in ('head', 'tab', 'rest', 'slash', 'accidental', 'dot'))
|
||||
items.extend(drawn)
|
||||
items.extend(tablature.column(index, event, placed, fonts, style))
|
||||
anchors.extend(placed)
|
||||
fragments.extend(pieces)
|
||||
if event.start == time and not event.value.get('hidden'):
|
||||
@@ -112,11 +114,12 @@ def make_system(index: ScoreIndex, start: Fraction, end: Fraction, fonts: Fonts,
|
||||
width = available
|
||||
right = width - 5
|
||||
last_measure = next(m for a, b, m in index.measures if a < end <= b)
|
||||
tab_items = [p for p in items if p.role.startswith('tab')]
|
||||
items = [p for p in items if not p.role.startswith('tab')]
|
||||
items = draw_beams(index, anchors, items, start, end, content_left, right, style)
|
||||
items = draw_spans(index, anchors, items, start, end, content_left, right, fonts, style)
|
||||
items = place_text_lanes(items, fonts, style)
|
||||
items = draw_lyric_spans(index, items, anchors, start, end, content_left, right, fonts)
|
||||
# Staff lines are interrupted behind TAB numerals, not painted through them.
|
||||
changes = [(0, state['staff_details']['lines'])]
|
||||
for time, x in placements:
|
||||
lines = index.state(time)['staff_details']['lines']
|
||||
@@ -127,12 +130,7 @@ def make_system(index: ScoreIndex, start: Fraction, end: Fraction, fonts: Fonts,
|
||||
x2 = changes[i + 1][0] if i + 1 < len(changes) else width - 1
|
||||
for number in range(lines):
|
||||
y = -number * style.space
|
||||
cuts = sorted((max(x1, p.box.left - 2), min(x2, p.box.right + 2)) for p in items if p.role == 'tab' and p.box.top <= y <= p.box.bottom and p.box.left < x2 and p.box.right > x1)
|
||||
cursor = x1
|
||||
for a, b in cuts + [(x2, x2)]:
|
||||
if a > cursor:
|
||||
staff_lines.append(line('staff', (), ((cursor, y), (a, y)), style.space * 0.13))
|
||||
cursor = max(cursor, b)
|
||||
staff_lines.append(line('staff', (), ((x1, y), (x2, y)), style.space * 0.13))
|
||||
items = staff_lines + items
|
||||
if end in index.boundaries:
|
||||
items.extend(barline(last_measure.get('barlines', {}).get('right', 'regular'), width - 1, -style.space * (index.state(end, before=True)['staff_details']['lines'] - 1), last_measure['id']))
|
||||
@@ -140,6 +138,7 @@ def make_system(index: ScoreIndex, start: Fraction, end: Fraction, fonts: Fonts,
|
||||
items.extend(barline(measure.get('barlines', {}).get('left', 'none'), 0, -style.space * (state['staff_details']['lines'] - 1), measure['id']))
|
||||
else:
|
||||
items.append(fonts.text(str(measure['number']) + ' (cont.)', 0, min(p.box.top for p in items) - 6, 12, 'continuation', (measure['id'],)))
|
||||
items = tablature.staff(index, items + tab_items, start, end, content_left, width - 1, fonts, style)
|
||||
return System(start, end, tuple(items), tuple(anchors), tuple(fragments), bounds(items))
|
||||
|
||||
def paginate(systems: list[System], document: dict, fonts: Fonts, style: Style) -> tuple[Page, ...]:
|
||||
|
||||
@@ -70,13 +70,6 @@ class ScoreIndex:
|
||||
def pitch_position(self, tone: dict, time: Fraction) -> float:
|
||||
state = self.state(time)
|
||||
clef = state['clef']
|
||||
if clef['sign'] == 'TAB':
|
||||
if 'tablature' not in tone:
|
||||
raise EngravingError(f'{tone["id"]}: TAB notes require explicit string and fret')
|
||||
string = tone['tablature']['string']
|
||||
if not 1 <= string <= state['staff_details']['lines']:
|
||||
raise EngravingError(f'{tone["id"]}: TAB string is outside the staff')
|
||||
return 2 * (state['staff_details']['lines'] - string)
|
||||
pitch = tone.get('unpitched', tone)
|
||||
sign = clef['sign']
|
||||
reference = {'G': 4 * 7 + 4, 'F': 3 * 7 + 3, 'C': 4 * 7}.get(sign, 4 * 7 + 4)
|
||||
@@ -101,7 +94,7 @@ class ScoreIndex:
|
||||
if event.value.get('hidden') or tone.get('print', {}).get('print-object') == 'no' or hidden_head:
|
||||
result[tone['id']] = False
|
||||
continue
|
||||
if 'unpitched' in tone or state['clef']['sign'] == 'TAB':
|
||||
if 'unpitched' in tone:
|
||||
result[tone['id']] = False
|
||||
continue
|
||||
identity = (tone['letter'], tone['octave'])
|
||||
|
||||
@@ -95,9 +95,7 @@ def draw_event(index: ScoreIndex, event: Event, start: Fraction, end: Fraction,
|
||||
fragments.append(fragment)
|
||||
if not displayed:
|
||||
continue
|
||||
if state['clef']['sign'] == 'TAB':
|
||||
head = fonts.text(str(tone['tablature']['fret']), x, y + 5, tone_size * 1.5, 'tab', sources)
|
||||
elif written['type'] in ('long', 'maxima') and tone.get('notehead', {}).get('name', 'normal') == 'normal':
|
||||
if written['type'] in ('long', 'maxima') and tone.get('notehead', {}).get('name', 'normal') == 'normal':
|
||||
width = tone_size * (2.8 if written['type'] == 'maxima' else 1.4)
|
||||
head = line('head', sources, ((x, y - tone_size / 2), (x + width, y - tone_size / 2), (x + width, y + tone_size / 2), (x, y + tone_size / 2), (x, y - tone_size / 2)), 2)
|
||||
else:
|
||||
@@ -105,15 +103,14 @@ def draw_event(index: ScoreIndex, event: Event, start: Fraction, end: Fraction,
|
||||
group_items.append(head)
|
||||
if tone.get('notehead', {}).get('parentheses') == 'yes':
|
||||
group_items.extend((fonts.text('(', head.box.left - 6, y + 5, 18, 'head-parentheses', sources), fonts.text(')', head.box.right + 2, y + 5, 18, 'head-parentheses', sources)))
|
||||
if state['clef']['sign'] != 'TAB':
|
||||
ledger = -2
|
||||
while position <= ledger:
|
||||
group_items.append(line('ledger', sources, ((head.box.left - 5, -ledger * style.space / 2), (head.box.right + 5, -ledger * style.space / 2)), 1.8))
|
||||
ledger -= 2
|
||||
ledger = lines * 2
|
||||
while position >= ledger:
|
||||
group_items.append(line('ledger', sources, ((head.box.left - 5, -ledger * style.space / 2), (head.box.right + 5, -ledger * style.space / 2)), 1.8))
|
||||
ledger += 2
|
||||
ledger = -2
|
||||
while position <= ledger:
|
||||
group_items.append(line('ledger', sources, ((head.box.left - 5, -ledger * style.space / 2), (head.box.right + 5, -ledger * style.space / 2)), 1.8))
|
||||
ledger -= 2
|
||||
ledger = lines * 2
|
||||
while position >= ledger:
|
||||
group_items.append(line('ledger', sources, ((head.box.left - 5, -ledger * style.space / 2), (head.box.right + 5, -ledger * style.space / 2)), 1.8))
|
||||
ledger += 2
|
||||
if 'alter' in tone and index.accidentals[tone['id']] and onset == event.start:
|
||||
accidental_items = accidental(fonts, alteration(tone['alter']), tone.get('accidental_display', {}), tone_size, sources)
|
||||
box = bounds(accidental_items)
|
||||
|
||||
@@ -241,8 +241,12 @@ def draw_lyric_spans(index: ScoreIndex, items: list[Primitive], anchors: list[An
|
||||
extend = next((s['attributes'].get('type', 'start') for s in verse['syllables'] if s['kind'] == 'extend'), None)
|
||||
if syllabic not in ('begin', 'middle') and extend not in ('start', 'continue'):
|
||||
continue
|
||||
if 'extend_to' in verse and extend != 'start':
|
||||
continue
|
||||
target = following[0].start if following else max(e.end for e in index.events if e.voice == voice)
|
||||
if target < start or event.start >= end:
|
||||
if 'extend_to' in verse:
|
||||
target = fraction(verse['extend_to'])
|
||||
if target <= start or event.start >= end:
|
||||
continue
|
||||
identifier = event.value['id'] + ':lyric:' + number
|
||||
next_id = following[0].value['id'] + ':lyric:' + number if following else ''
|
||||
@@ -251,6 +255,9 @@ def draw_lyric_spans(index: ScoreIndex, items: list[Primitive], anchors: list[An
|
||||
x2 = b.box.left - 5 if b else right
|
||||
if b is None and following and following[0].value['id'] in positions:
|
||||
x2 = positions[following[0].value['id']].x
|
||||
if 'extend_to' in verse:
|
||||
endpoints = [a.x for a in anchors if a.time >= target]
|
||||
x2 = min(endpoints) - 5 if endpoints else right
|
||||
y = lyric_baseline + index.lyric_lanes.index((voice, number)) * 23
|
||||
if x2 <= x1:
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from generator.model import fraction
|
||||
from generator.engraving.notes import rest_glyph
|
||||
from generator.engraving.types import bounds, curve, line
|
||||
|
||||
def column(index, event, anchors, fonts, style):
|
||||
if not index.staff.get('tablature') or not anchors:
|
||||
return []
|
||||
x = min(a.x for a in anchors)
|
||||
sources = (event.value['id'],)
|
||||
if not event.value['pitches']:
|
||||
anchor = anchors[0]
|
||||
glyph = 'noteheadSlashHorizontalEnds' if anchor.fragment.kind == 'slash' else rest_glyph(anchor.fragment.written_type)
|
||||
return [fonts.glyph(glyph, x, -2 * style.space, style.space, 'tab-' + anchor.fragment.kind, sources)]
|
||||
result = []
|
||||
for anchor in anchors:
|
||||
tone = index.tones[anchor.source][1]
|
||||
tab = tone['tablature']
|
||||
y = -(6 - tab['string']) * style.space
|
||||
result.append(fonts.text(str(tab['fret']), x, y + 5, style.space * 1.2, 'tab', (event.value['id'], tone['id'])))
|
||||
return result
|
||||
|
||||
def staff(index, items, start, end, left, right, fonts, style):
|
||||
notation = [p for p in items if not p.role.startswith('tab')]
|
||||
numbers = [p for p in items if p.role.startswith('tab')]
|
||||
if not index.staff.get('tablature'):
|
||||
return items
|
||||
offset = bounds(notation).bottom + 32 + 5 * style.space
|
||||
numbers = [p.moved(0, offset) for p in numbers]
|
||||
drawing = list(numbers)
|
||||
drawing.append(fonts.text('TAB', 0, offset - 2 * style.space, 15, 'tab-label', ()))
|
||||
for string in range(1, 7):
|
||||
y = offset - (6 - string) * style.space
|
||||
cuts = sorted((max(0, p.box.left - 2), min(right, p.box.right + 2)) for p in numbers if p.box.top <= y <= p.box.bottom)
|
||||
cursor = 0
|
||||
for a, b in cuts + [(right, right)]:
|
||||
if a > cursor:
|
||||
drawing.append(line('tab-staff', (), ((cursor, y), (a, y)), style.space * 0.13))
|
||||
cursor = max(cursor, b)
|
||||
for bar in (p for p in notation if p.role == 'barline'):
|
||||
drawing.append(line('tab-barline', bar.sources, tuple((x, offset + y * 5 / 4) 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 + 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
|
||||
y = offset - (6 - tone['tablature']['string']) * style.space + 8
|
||||
if x2 > x1:
|
||||
drawing.append(curve('tab-tie', (identifier,), ((x1, y), (x1 + (x2 - x1) / 3, y + 5), (x2 - (x2 - x1) / 3, y + 5), (x2, y))))
|
||||
return notation + drawing
|
||||
Reference in New Issue
Block a user