How to convert wedges to time in musicxml

39 views
Skip to first unread message

TwoSheds deCosta

unread,
Jun 14, 2024, 3:36:56 PMJun 14
to music21
I am developing a singing opera synthesizer (www.turingoperaworkshop.com) but have run into a major blocker: wedges! All of the input to my app is musicxml format, but I can't find any docs anywhere for interpreting how wedges translate into time. For each wedge in a file, I simply need to be able to calculate start and stop time, either absolute or relative to other elements. I'm using Python.

Any insight you could give would be most helpful!
Cheers!
Richard deCosta
Composer, Drummer, Piscator

Bernhard Wagner

unread,
Jun 15, 2024, 4:26:57 AMJun 15
to music21
from music21 import stream, note, dynamics

# Create a sample score for demonstration
score = stream.Score()
part = stream.Part()
measure1 = stream.Measure()
measure2 = stream.Measure()

# Add notes to the measures
n1 = note.Note('C4', quarterLength=1.0)  # Note where the wedge starts
n2 = note.Note('D4', quarterLength=1.0)
n3 = note.Note('E4', quarterLength=1.0)
n4 = note.Note('F4', quarterLength=1.0)  # Note where the wedge ends

measure1.append([n1, n2])
measure2.append([n3, n4])

part.append([measure1, measure2])
score.append(part)

# Create a wedge
wedge = dynamics.DynamicWedge(type='crescendo', endStyle='diminuendo')
wedge.addSpannedElements([n1, n4])
part.insert(n1.offset, wedge)

# Function to find and calculate start and stop times of all wedges in the score
def find_wedges(score):
    wedges_info = []
    for element in score.recurse().getElementsByClass(dynamics.DynamicWedge):
        spanned_elements = element.getSpannedElements()
        if spanned_elements:
            start_element = spanned_elements[0]
            end_element = spanned_elements[-1]
            start_time = start_element.offset
            stop_time = end_element.offset + end_element.quarterLength
            wedges_info.append((start_time, stop_time))
    return wedges_info

# Find wedges and calculate their start and stop times
wedges_info = find_wedges(score)
for start_time, stop_time in wedges_info:
    print(f"Wedge start time: {start_time}, Wedge stop time: {stop_time}")

TwoSheds deCosta

unread,
Jun 19, 2024, 3:28:54 AMJun 19
to music21
Do text dynamics (pp, ff, etc.) work the same way?

TwoSheds deCosta

unread,
Jun 20, 2024, 12:48:45 PM (13 days ago) Jun 20
to music21
from music21 import converter, dynamics, note
import matplotlib.pyplot as plt
import json

class MusicXMLDynamicsParser:
def __init__(self, score_path, start_dynamic=0.3, end_dynamic=3.5):
self.score = converter.parse(score_path)
self.start_dynamic = start_dynamic
self.end_dynamic = end_dynamic
self.notes_list = []
self.energy_envelope = []

def process_notes(self):
for n in self.score.recurse().notes:
self.notes_list.append({
'start_time': n.offset,
'end_time': n.offset + n.duration.quarterLength,
'type': 'general',
'language': 'en',
'pitch': n.pitch.midi,
'phone': ['aa']
})

def process_wedges(self):
for element in self.score.recurse().getElementsByClass(dynamics.DynamicWedge):
spanned_elements = element.getSpannedElements()
if spanned_elements:
start_element = spanned_elements[0]
end_element = spanned_elements[-1]
start_time = start_element.offset
end_time = end_element.offset + end_element.quarterLength
duration = end_time - start_time

if isinstance(element, dynamics.Crescendo):
start_dynamic = self.start_dynamic
end_dynamic = self.end_dynamic
elif isinstance(element, dynamics.Diminuendo):
start_dynamic = self.end_dynamic
end_dynamic = self.start_dynamic

num_values = 20 # Number of values to span the dynamic change
hop_time = duration / num_values
dynamic_values = [
start_dynamic + i * (end_dynamic - start_dynamic) / (num_values - 1)
for i in range(num_values)
]

self.energy_envelope.append({
'hop_time': hop_time,
'start_time': start_time,
'values': dynamic_values
})
print(f"Processing {'crescendo' if isinstance(element, dynamics.Crescendo) else 'diminuendo'} wedge")
print(f"Start time: {start_time}, End time: {end_time}, Duration: {duration}")
print(f"Dynamic values: {dynamic_values}")

def create_aces_file(self, output_path):
self.process_notes()
self.process_wedges()

aces_data = {
'version': 1.1,
'notes': self.notes_list,
'piece_params': {
'energy': {
'envelope': self.energy_envelope,
'user': []
},
'air': {
'envelope': [],
'user': []
},
'falsetto': {
'envelope': [],
'user': []
},
'pitch': {
'user': []
},
'tension': {
'envelope': [],
'user': []
}
}
}

with open(output_path, 'w') as f:
json.dump(aces_data, f, indent=4)

class ACESVisualizer:
def __init__(self, aces_file):
with open(aces_file, 'r') as f:
self.aces_data = json.load(f)

def plot_energy(self):
energy_data = self.aces_data['piece_params']['energy']['envelope']
for segment in energy_data:
times = [segment['start_time'] + i * segment['hop_time'] for i in range(len(segment['values']))]
plt.plot(times, segment['values'], 'r-')

plt.title('Energy Envelope')
plt.xlabel('Time (s)')
plt.ylabel('Energy')
plt.grid(True)
plt.show()

# Paths
score_path = 'minimalDynamicsTest2.musicxml'
output_path = '/minimalDynamicsTest2.aces'

# Create ACES file
parser = MusicXMLDynamicsParser(score_path)
parser.create_aces_file(output_path)

# Visualize the energy envelope
visualizer = ACESVisualizer(output_path)
visualizer.plot_energy()


I think I ALMOST have it, but the timing of the dynamics still isn't quite right. Any suggestions?
Reply all
Reply to author
Forward
0 new messages