如何解析music21 stream识别不同声部的同时演奏音符
Great question! When working with music21 to identify when notes from different voices/instruments play together (like the accented A in the treble clef and B in the bass clef in your 3rd measure), the core idea is to group musical elements by their global time offset (position relative to the start of the entire piece) and check for groups that contain notes from multiple distinct parts.
Here are two reliable approaches to implement this:
Approach 1: Using OffsetIterator (Most Efficient)
music21's OffsetIterator does the heavy lifting of grouping all elements in your score by their global offset. This is the cleanest way to find simultaneous events:
from music21 import converter, note, chord from music21.stream.iterator import OffsetIterator # Load your score (replace with your file path or existing piece object) piece = converter.parse("your_score.mxl") # Iterate through all time offsets in the score for offset, elements_at_offset in OffsetIterator(piece).items(): # Collect note/chord details with their part information simultaneous_notes = [] for elem in elements_at_offset: # Get the parent part of the element part = elem.getContextByClass("Part") part_label = part.partName if part.partName else f"Part {part.id}" # Handle single notes if isinstance(elem, note.Note): simultaneous_notes.append((part_label, elem.nameWithOctave)) # Handle chords (extract individual pitches) elif isinstance(elem, chord.Chord): for pitch in elem.pitches: simultaneous_notes.append((part_label, pitch.nameWithOctave)) # Only keep groups with notes from 2+ different parts unique_parts = {entry[0] for entry in simultaneous_notes} if len(unique_parts) >= 2: print(f"Simultaneous notes at global offset {offset}:") for part_label, note_name in simultaneous_notes: print(f"- {part_label}: {note_name}") print("---")
Key Details for This Approach:
OffsetIteratorautomatically calculates global offsets, so you don't have to manually adjust for part-specific starting offsets (e.g., a part that enters later in the score).- We check for both
NoteandChordobjects to cover all pitched elements. getContextByClass("Part")retrieves the parent part of each note, so you can track which voice/instrument each note belongs to.
Approach 2: Manual Collection & Grouping
If you prefer a more explicit workflow, you can collect all notes first, then group them by their global offset:
from music21 import converter from collections import defaultdict piece = converter.parse("your_score.mxl") # Step 1: Collect all notes with their part and global offset all_note_data = [] for part_idx, part in enumerate(piece.parts): part_label = part.partName if part.partName else f"Part {part_idx + 1}" # Flatten the part to access all nested notes for note in part.flat.notes: # Calculate global offset (part's start offset + note's local offset) global_offset = part.offset + note.offset all_note_data.append({ "part": part_label, "note": note.nameWithOctave, "offset": global_offset }) # Step 2: Group notes by their global offset offset_groups = defaultdict(list) for entry in all_note_data: offset_groups[entry["offset"]].append(entry) # Step 3: Filter groups with notes from multiple parts for offset, notes in offset_groups.items(): unique_parts = {entry["part"] for entry in notes} if len(unique_parts) >= 2: print(f"Simultaneous notes at global offset {offset}:") for entry in notes: print(f"- {entry['part']}: {entry['note']}") print("---")
Key Details for This Approach:
- Calculating
global_offsetensures we align notes correctly even if parts start at different times. - Using
defaultdictsimplifies grouping notes by their offset value.
Bonus: Mapping to Measure Numbers
If you want to tie these simultaneous notes to specific measures (like your 3rd measure example), you can access the measureNumber attribute of each note:
# Add this to either approach when processing a note measure_num = note.measureNumber print(f"Measure {measure_num}, Offset {offset}: ...")
内容的提问来源于stack exchange,提问作者Derek

