@@ -1,21 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fractions import Fraction
|
||||
from math import lcm
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree
|
||||
import fractions
|
||||
import math
|
||||
import pathlib
|
||||
import xml.etree.ElementTree
|
||||
|
||||
from generator import harmony
|
||||
from generator.model import alteration, fraction, validate_document
|
||||
import generator.harmony
|
||||
import generator.model
|
||||
|
||||
def element(parent, tag, content=None, **attributes):
|
||||
node = ElementTree.SubElement(parent, tag, {k.replace('_', '-'): str(v) for k, v in attributes.items() if v is not None})
|
||||
node = xml.etree.ElementTree.SubElement(parent, tag, {k.replace('_', '-'): str(v) for k, v in attributes.items() if v is not None})
|
||||
if content is not None:
|
||||
node.text = str(content)
|
||||
return node
|
||||
|
||||
def decimal(value) -> str:
|
||||
value = alteration(value) if isinstance(value, dict) else Fraction(value)
|
||||
value = generator.model.alteration(value) if isinstance(value, dict) else fractions.Fraction(value)
|
||||
denominator = value.denominator
|
||||
for prime in (2, 5):
|
||||
while denominator % prime == 0:
|
||||
@@ -37,7 +37,7 @@ def write_attributes(parent, state: dict, divisions=None) -> None:
|
||||
if 'cancel' in key:
|
||||
element(item, 'cancel', key['cancel'])
|
||||
if key.get('custom'):
|
||||
steps = key.get('steps', [{'letter': letter, 'alter': amount} for letter, amount in zip('cdefgab', key['alterations']) if alteration(amount)])
|
||||
steps = key.get('steps', [{'letter': letter, 'alter': amount} for letter, amount in zip('cdefgab', key['alterations']) if generator.model.alteration(amount)])
|
||||
for step in steps:
|
||||
element(item, 'key-step', step['letter'].upper())
|
||||
element(item, 'key-alter', decimal(step['alter']))
|
||||
@@ -85,7 +85,7 @@ def write_harmony(parent, value: dict, divisions: int) -> None:
|
||||
item = element(node, 'root')
|
||||
element(item, 'root-step', root['letter'].upper())
|
||||
element(item, 'root-alter', decimal(root['alter']))
|
||||
element(node, 'kind', value['kind'], text=harmony.symbol(value) if value.get('parenthesized') else None)
|
||||
element(node, 'kind', value['kind'], text=generator.harmony.symbol(value) if value.get('parenthesized') else None)
|
||||
if 'inversion' in value:
|
||||
element(node, 'inversion', value['inversion'])
|
||||
if 'bass' in value:
|
||||
@@ -94,20 +94,20 @@ def write_harmony(parent, value: dict, divisions: int) -> None:
|
||||
element(item, 'bass-alter', decimal(value['bass']['alter']))
|
||||
for degree in value['degrees']:
|
||||
item = element(node, 'degree')
|
||||
amount = alteration(degree['alter'])
|
||||
amount = generator.model.alteration(degree['alter'])
|
||||
if degree['type'] == 'alter':
|
||||
amount -= harmony.KINDS[value['kind']].get(degree['value'], 0)
|
||||
amount -= generator.harmony.KINDS[value['kind']].get(degree['value'], 0)
|
||||
elif degree['type'] == 'add' and (degree['value'] - 1) % 7 == 6:
|
||||
amount += 1
|
||||
element(item, 'degree-value', degree['value'])
|
||||
element(item, 'degree-alter', decimal(amount))
|
||||
element(item, 'degree-type', degree['type'])
|
||||
element(node, 'offset', int(fraction(value['onset']) * divisions))
|
||||
element(node, 'offset', int(generator.model.fraction(value['onset']) * divisions))
|
||||
|
||||
def write_note(parent, event: dict, voice: str, divisions: int, tuplets: list[dict]) -> None:
|
||||
if event['kind'] == 'spacer':
|
||||
item = element(parent, 'forward')
|
||||
element(item, 'duration', int(fraction(event['duration']) * divisions))
|
||||
element(item, 'duration', int(generator.model.fraction(event['duration']) * divisions))
|
||||
element(item, 'voice', voice)
|
||||
return
|
||||
pitches = event['pitches'] or [None]
|
||||
@@ -117,7 +117,7 @@ def write_note(parent, event: dict, voice: str, divisions: int, tuplets: list[di
|
||||
printing.update(pitch.get('print', {}))
|
||||
node = element(parent, 'note', **printing)
|
||||
if grace := event.get('grace'):
|
||||
element(node, 'grace', **{k: ('yes' if v else 'no') if isinstance(v, bool) else decimal(fraction(v) * divisions if k == 'make-time' else v) for k, v in grace.items()})
|
||||
element(node, 'grace', **{k: ('yes' if v else 'no') if isinstance(v, bool) else decimal(generator.model.fraction(v) * divisions if k == 'make-time' else v) for k, v in grace.items()})
|
||||
if (pitch or event).get('cue', event.get('cue')):
|
||||
element(node, 'cue')
|
||||
if index:
|
||||
@@ -137,7 +137,7 @@ def write_note(parent, event: dict, voice: str, divisions: int, tuplets: list[di
|
||||
element(item, 'alter', decimal(pitch['alter']) if pitch else '0')
|
||||
element(item, 'octave', pitch['octave'] if pitch else 4)
|
||||
if not event.get('grace'):
|
||||
element(node, 'duration', int(fraction(pitch['duration'] if pitch else event['duration']) * divisions))
|
||||
element(node, 'duration', int(generator.model.fraction(pitch['duration'] if pitch else event['duration']) * divisions))
|
||||
for field, action in (('tie_from', 'stop'), ('tie_to', 'start')):
|
||||
if pitch and pitch.get(field):
|
||||
element(node, 'tie', type=action)
|
||||
@@ -151,7 +151,7 @@ def write_note(parent, event: dict, voice: str, divisions: int, tuplets: list[di
|
||||
if pitch and (display := pitch.get('accidental_display')):
|
||||
element(node, 'accidental', display['name'], **{k: v for k, v in display.items() if k != 'name'})
|
||||
if 'ratio' in written:
|
||||
ratio = fraction(written['ratio'])
|
||||
ratio = generator.model.fraction(written['ratio'])
|
||||
item = element(node, 'time-modification')
|
||||
element(item, 'actual-notes', ratio.denominator)
|
||||
element(item, 'normal-notes', ratio.numerator)
|
||||
@@ -204,15 +204,13 @@ def write_barline(bar, measure, location):
|
||||
node = element(bar, 'barline', location=location)
|
||||
element(node, 'bar-style', measure.get('barlines', {}).get(location, 'regular'))
|
||||
|
||||
def musicxml_tree(document: dict) -> ElementTree.ElementTree:
|
||||
validate_document(document)
|
||||
root = ElementTree.Element('score-partwise', {'version': '4.0'})
|
||||
def musicxml_tree(document: dict) -> xml.etree.ElementTree.ElementTree:
|
||||
generator.model.validate_document(document)
|
||||
root = xml.etree.ElementTree.Element('score-partwise', {'version': '4.0'})
|
||||
metadata, staff = document['metadata'], document['staves'][0]
|
||||
element(element(root, 'work'), 'work-title', metadata['title'])
|
||||
identification = element(root, 'identification')
|
||||
element(identification, 'creator', metadata.get('composer', ''), type='composer')
|
||||
miscellaneous = element(identification, 'miscellaneous')
|
||||
element(miscellaneous, 'miscellaneous-field', metadata.get('style', 'Medium Swing'), name='vamp:style')
|
||||
defaults = element(root, 'defaults')
|
||||
element(defaults, 'music-font', font_family='Petaluma')
|
||||
element(defaults, 'word-font', font_family='Petaluma Script')
|
||||
@@ -240,23 +238,23 @@ def musicxml_tree(document: dict) -> ElementTree.ElementTree:
|
||||
for tag, value in instrument['midi'].items():
|
||||
element(item, tag, value)
|
||||
part = element(root, 'part', id='P1')
|
||||
absolute = Fraction()
|
||||
absolute = fractions.Fraction()
|
||||
previous_state = {}
|
||||
for index, measure in enumerate(staff['measures']):
|
||||
bar = element(part, 'measure', number=index + 1, implicit='yes' if measure['implicit'] else 'no')
|
||||
write_barline(bar, measure, 'left')
|
||||
times = [fraction(measure['duration'])]
|
||||
times.extend(fraction(n[field]) for v in measure['voices'] for n in v['notes'] for field in ('onset', 'duration'))
|
||||
times.extend(fraction(p['duration']) for v in measure['voices'] for n in v['notes'] for p in n['pitches'])
|
||||
times.extend(fraction(item['onset']) for field in ('attributes', 'harmonies', 'tempos') for item in measure.get(field, []))
|
||||
times.extend(fraction(s[field]) - absolute for s in staff.get('octave_shifts', []) for field in ('start', 'end'))
|
||||
divisions = lcm(*(t.denominator for t in times))
|
||||
times = [generator.model.fraction(measure['duration'])]
|
||||
times.extend(generator.model.fraction(n[field]) for v in measure['voices'] for n in v['notes'] for field in ('onset', 'duration'))
|
||||
times.extend(generator.model.fraction(p['duration']) for v in measure['voices'] for n in v['notes'] for p in n['pitches'])
|
||||
times.extend(generator.model.fraction(item['onset']) for field in ('attributes', 'harmonies', 'tempos') for item in measure.get(field, []))
|
||||
times.extend(generator.model.fraction(s[field]) - absolute for s in staff.get('octave_shifts', []) for field in ('start', 'end'))
|
||||
divisions = math.lcm(*(t.denominator for t in times))
|
||||
state = {k: measure[k] for k in ('key', 'meter', 'clef', 'transpose', 'staff_details') if k in measure}
|
||||
write_attributes(bar, {k: v for k, v in state.items() if previous_state.get(k) != v}, divisions)
|
||||
previous_state.update(state)
|
||||
cursor = Fraction()
|
||||
cursor = fractions.Fraction()
|
||||
for change in measure.get('attributes', []):
|
||||
onset = fraction(change['onset'])
|
||||
onset = generator.model.fraction(change['onset'])
|
||||
if onset != cursor:
|
||||
element(element(bar, 'forward' if onset > cursor else 'backup'), 'duration', int(abs(onset - cursor) * divisions))
|
||||
write_attributes(bar, change)
|
||||
@@ -269,7 +267,7 @@ def musicxml_tree(document: dict) -> ElementTree.ElementTree:
|
||||
if value.get('chord_optional'):
|
||||
direction = element(bar, 'direction', print_object='no')
|
||||
element(element(direction, 'direction-type'), 'words', 'vamp:optional-chord')
|
||||
element(direction, 'offset', int(fraction(value['onset']) * divisions))
|
||||
element(direction, 'offset', int(generator.model.fraction(value['onset']) * divisions))
|
||||
if measure.get('section_label'):
|
||||
element(element(element(bar, 'direction'), 'direction-type'), 'rehearsal', measure['section_label'], halign='left')
|
||||
for tempo in measure.get('tempos', []):
|
||||
@@ -279,35 +277,35 @@ def musicxml_tree(document: dict) -> ElementTree.ElementTree:
|
||||
for _ in range(tempo['written'].get('dots', 0)):
|
||||
element(metronome, 'beat-unit-dot')
|
||||
element(metronome, 'per-minute', decimal(tempo['bpm']))
|
||||
element(direction, 'offset', int(fraction(tempo['onset']) * divisions))
|
||||
element(direction, 'offset', int(generator.model.fraction(tempo['onset']) * divisions))
|
||||
for si, span in enumerate(staff.get('octave_shifts', [])):
|
||||
for field in ('start', 'end'):
|
||||
onset = fraction(span[field]) - absolute
|
||||
inside = 0 <= onset < fraction(measure['duration']) if field == 'start' else 0 < onset <= fraction(measure['duration'])
|
||||
onset = generator.model.fraction(span[field]) - absolute
|
||||
inside = 0 <= onset < generator.model.fraction(measure['duration']) if field == 'start' else 0 < onset <= generator.model.fraction(measure['duration'])
|
||||
if not inside:
|
||||
continue
|
||||
direction = element(bar, 'direction')
|
||||
element(element(direction, 'direction-type'), 'octave-shift', type='stop' if field == 'end' else 'up' if span['octaves'] > 0 else 'down', size=abs(span['octaves']) * 7 + 1, number=si + 1)
|
||||
element(direction, 'offset', int(onset * divisions))
|
||||
cursor = Fraction()
|
||||
cursor = fractions.Fraction()
|
||||
for voice in measure['voices']:
|
||||
if cursor:
|
||||
element(element(bar, 'backup'), 'duration', int(cursor * divisions))
|
||||
cursor = Fraction()
|
||||
cursor = fractions.Fraction()
|
||||
for event in voice['notes']:
|
||||
onset = fraction(event['onset'])
|
||||
onset = generator.model.fraction(event['onset'])
|
||||
if onset > cursor:
|
||||
element(element(bar, 'forward'), 'duration', int((onset - cursor) * divisions))
|
||||
write_note(bar, event, voice['id'], divisions, staff.get('tuplets', []))
|
||||
cursor = onset + fraction(event['duration'])
|
||||
if cursor < fraction(measure['duration']):
|
||||
element(element(bar, 'forward'), 'duration', int((fraction(measure['duration']) - cursor) * divisions))
|
||||
cursor = fraction(measure['duration'])
|
||||
cursor = onset + generator.model.fraction(event['duration'])
|
||||
if cursor < generator.model.fraction(measure['duration']):
|
||||
element(element(bar, 'forward'), 'duration', int((generator.model.fraction(measure['duration']) - cursor) * divisions))
|
||||
cursor = generator.model.fraction(measure['duration'])
|
||||
write_barline(bar, measure, 'right')
|
||||
absolute += fraction(measure['duration'])
|
||||
return ElementTree.ElementTree(root)
|
||||
absolute += generator.model.fraction(measure['duration'])
|
||||
return xml.etree.ElementTree.ElementTree(root)
|
||||
|
||||
def write_musicxml(path: Path, document: dict) -> None:
|
||||
def write_musicxml(path: pathlib.Path, document: dict) -> None:
|
||||
tree = musicxml_tree(document)
|
||||
ElementTree.indent(tree, space=' ')
|
||||
xml.etree.ElementTree.indent(tree, space=' ')
|
||||
tree.write(path, encoding='utf-8', xml_declaration=True)
|
||||
|
||||
Reference in New Issue
Block a user