You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解析music21 stream识别不同声部的同时演奏音符

Finding Simultaneous Notes Across Parts in music21

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:

  • OffsetIterator automatically 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 Note and Chord objects 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_offset ensures we align notes correctly even if parts start at different times.
  • Using defaultdict simplifies 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:50:36