@@ -5,7 +5,7 @@ from typing import NamedTuple
|
||||
|
||||
from generator.transposition import mod12
|
||||
from generator import harmony
|
||||
from generator.model import NATURAL
|
||||
from generator.model import NATURAL, alteration
|
||||
|
||||
STANDARD_TUNING = ((6, 40), (5, 45), (4, 50), (3, 55), (2, 59), (1, 64))
|
||||
WRITTEN_GUITAR_OCTAVE_OFFSET = 12
|
||||
@@ -50,10 +50,10 @@ def find_playable_voicing(symbol: str | dict) -> list[TabPosition]:
|
||||
if len(voicing) > 4 and voicing.get(degree) == 0:
|
||||
del voicing[degree]
|
||||
root = definition.get('root', {'letter': 'c', 'alter': 0})
|
||||
root_pc = NATURAL[root['letter']] + root['alter']
|
||||
root_pc = NATURAL[root['letter']] + alteration(root['alter'])
|
||||
pitch_classes = tuple(dict.fromkeys((root_pc + harmony.degree_semitones(d, a)) % 12 for d, a in voicing.items()))
|
||||
bass = definition.get('bass')
|
||||
bass_pc = (NATURAL[bass['letter']] + bass['alter']) % 12 if bass else None
|
||||
bass_pc = (NATURAL[bass['letter']] + alteration(bass['alter'])) % 12 if bass else None
|
||||
if bass_pc is not None and bass_pc not in pitch_classes:
|
||||
pitch_classes += (bass_pc,)
|
||||
if not pitch_classes:
|
||||
@@ -88,13 +88,13 @@ def pitches_for_voicing(symbol: str | dict) -> list[dict[str, int | str]]:
|
||||
spellings = {}
|
||||
for degree, alter in harmony.degrees(definition).items():
|
||||
p = shift_pitch(root, degree - 1, harmony.degree_semitones(degree, alter))
|
||||
spellings[(NATURAL[p['letter']] + p['alter']) % 12] = p
|
||||
spellings[(NATURAL[p['letter']] + alteration(p['alter'])) % 12] = p
|
||||
if bass := definition.get('bass'):
|
||||
spellings[(NATURAL[bass['letter']] + bass['alter']) % 12] = bass
|
||||
spellings[(NATURAL[bass['letter']] + alteration(bass['alter'])) % 12] = bass
|
||||
result = []
|
||||
for position in find_playable_voicing(definition):
|
||||
midi = position.midi + WRITTEN_GUITAR_OCTAVE_OFFSET
|
||||
p = spellings[midi % 12].copy()
|
||||
p['octave'] = (midi - NATURAL[p['letter']] - p['alter']) // 12 - 1
|
||||
p['octave'] = int((midi - NATURAL[p['letter']] - alteration(p['alter'])) // 12 - 1)
|
||||
result.append(p)
|
||||
return result
|
||||
|
||||
@@ -10,7 +10,7 @@ from generator.arrange.fretboard import pitches_for_voicing
|
||||
from generator.model import beat_lengths, fraction, key_label, make_event, meter_label, rational
|
||||
from generator.musicxml import load_musicxml
|
||||
from generator.transposition import concert_document, shift_pitch
|
||||
from generator.score import copy_source_voice, make_arranged_document, make_measure, measure_notes, reidentify, slugify, source_staff, write_json
|
||||
from generator.score import copy_source_voices, make_arranged_document, make_measure, measure_notes, reidentify, slugify, source_staff, write_json
|
||||
|
||||
PART_TYPES = ('acoustic', 'electric', 'vocal')
|
||||
FINAL_SECTION_INDEX = 6
|
||||
@@ -85,6 +85,11 @@ def generated_voice(measure: dict, role: str, previous: dict | None) -> list[dic
|
||||
def arrange_document(document: dict, part_type: str) -> dict:
|
||||
if part_type not in PART_TYPES:
|
||||
raise ValueError(f'unknown arrangement part: {part_type}')
|
||||
for measure in source_staff(document)['measures']:
|
||||
specialized_staff = any(state.get('clef', {}).get('sign') in ('TAB', 'percussion') for state in [measure, *measure.get('attributes', [])])
|
||||
specialized_notes = any('unpitched' in pitch or 'tablature' in pitch for note in measure_notes(measure) for pitch in note['pitches'])
|
||||
if specialized_staff or specialized_notes:
|
||||
raise ValueError(f'{measure["id"]}: automatic performance arranging requires a pitched non-TAB lead; percussion/TAB conversion is not qualified')
|
||||
document = concert_document(document)
|
||||
staff = source_staff(document)
|
||||
source = staff['measures']
|
||||
@@ -104,11 +109,14 @@ def arrange_document(document: dict, part_type: str) -> dict:
|
||||
m['harmonies'] = [h for h in m['harmonies'] if section.section_index != FINAL_SECTION_INDEX or not h.get('chord_optional')]
|
||||
for h in m['harmonies']:
|
||||
h.pop('chord_optional', None)
|
||||
notes = copy_source_voice(m, strip_lyrics=section.role == 'melody')['notes'] if section.role in ('vocal', 'melody') else generated_voice(m, section.role, previous[si])
|
||||
source_voices = copy_source_voices(m, strip_lyrics=section.role == 'melody') if section.role in ('vocal', 'melody') else None
|
||||
notes = [n for v in source_voices for n in v['notes']] if source_voices else generated_voice(m, section.role, previous[si])
|
||||
if section.role == 'melody':
|
||||
for note in notes:
|
||||
note['pitches'] = [shift_pitch(p, 7, 12) for p in note['pitches']]
|
||||
result = make_measure(f'measure-{len(output) + len(segment) + 1}', m, notes, section_label=section.label if not segment else '')
|
||||
result = make_measure(f'measure-{len(output) + len(segment) + 1}', m, [] if source_voices else notes, section_label=section.label if not segment else '')
|
||||
if source_voices:
|
||||
result['voices'] = source_voices
|
||||
if part_type != 'vocal':
|
||||
result['clef'] = {'sign': 'G', 'line': 2, 'octave_change': 0}
|
||||
result['transpose'] = {'diatonic': 0, 'chromatic': 0, 'octave_change': -1}
|
||||
@@ -130,12 +138,13 @@ def arrange_document(document: dict, part_type: str) -> dict:
|
||||
pad = fraction(destination['duration']) - fraction(pickup['duration'])
|
||||
if pad < 0:
|
||||
raise ValueError('pickup is longer than the preceding arrangement measure')
|
||||
notes = copy_source_voice(pickup, strip_lyrics=False)['notes']
|
||||
for n in notes:
|
||||
n['onset'] = rational(fraction(n['onset']) + pad)
|
||||
if pad:
|
||||
notes.insert(0, make_event('pickup-rest', Fraction(), pad, 'rest'))
|
||||
destination['voices'][0]['notes'] = notes
|
||||
voices = copy_source_voices(pickup, strip_lyrics=False)
|
||||
for voice in voices:
|
||||
for n in voice['notes']:
|
||||
n['onset'] = rational(fraction(n['onset']) + pad)
|
||||
if pad:
|
||||
voice['notes'].insert(0, make_event(f'pickup-rest-{voice["id"]}', Fraction(), pad, 'rest') | {'voice': voice['id']})
|
||||
destination['voices'] = voices
|
||||
identity_segment = [destination, *segment]
|
||||
if pickup['harmonies']:
|
||||
destination['harmonies'] = [h for h in destination['harmonies'] if fraction(h['onset']) < pad]
|
||||
|
||||
Reference in New Issue
Block a user