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

如何实现MusicXML转按小节拆分声部的ABC记谱格式

背景

我最近尝试作曲,但完全是新手,没有乐理基础。因此我想训练LLaMA来辅助我为多乐器乐曲续写提供建议。然而乐谱是二维的(时间和乐器数量),而注意力机制是一维的,所以采用便于注意力机制解析的记谱方式至关重要。

问题

我希望将MusicXML转换为ABC格式,且满足一个额外条件:每个声部按小节拆分,以便大语言模型更易解析。以下是我设想的示例格式:

X:1
T: Three Bar Tune
M:4/4
L:1/4
K:C
%%MIDI program 1 0
%%MIDI program 2 40
%%MIDI program 3 42
V:1
[V:1 "Piano"] C G E G |]
V:2 clef=treble
[V:2 "Violin"] E B G B |]
V:3 clef=bass
[V:3 "Cello"] G, D B, D |]

V:1
[V:1 "Piano"] D A F A |]
V:2
[V:2 "Violin"] F C A C |]
V:3
[V:3 "Cello"] A, E C E |]

V:1
[V:1 "Piano"] E B G B |]
V:2
[V:2 "Violin"] G D B D |]
V:3
[V:3 "Cello"] B, F D F |]

V:1
[V:1 "Piano"] F C A C |]
V:2
[V:2 "Violin"] A, E C E |]
V:3
[V:3 "Cello"] C G E G |]

请问有没有办法生成这种格式?

我尝试过midi2abc,但它功能有限,且由于MusicXML和ABC记谱法的表达性较强,而MIDI存在局限性,因此MIDI并非理想的中间格式。

我还尝试过自己编写程序,但音乐记谱体系非常复杂。参考过ABC记谱标准v2.1的规范。

import xml.etree.ElementTree as ET
from music21 import converter, pitch


def get_note_name(pitch_obj):
    abc_note_names = ['C', '^C', 'D', '^D', 'E', 'F', '^F', 'G', '^G', 'A', '^A', 'B']
    note_name = abc_note_names[pitch_obj.pitchClass]
    octave = pitch_obj.octave - 1

    if octave == -1:
        return note_name + ','
    elif octave == 0:
        return note_name
    elif octave == 1:
        return note_name.lower()
    elif octave == 2:
        return note_name.lower() + "'"
    else:
        return note_name.lower() + "'" * (octave - 1)


def musicxml_to_abc(musicxml_file_path):
    # Load the MusicXML file
    score = converter.parse(musicxml_file_path)
    tree = ET.parse(musicxml_file_path)
    root = tree.getroot()
    time_signature = '4/4'

    # Find the time signature
    for attributes in root.iter('attributes'):
        for time in attributes.iter('time'):
            beats = time.find('beats').text
            beat_type = time.find('beat-type').text
            time_signature = f"{beats}/{beat_type}"
            break

    abc = f"X:1
T: One Bar Tune
M:{time_signature}
L:1/4
K:C
"

    voice_names = {}
    voices = {}
    for part in root.iter('part'):
        part_id = part.attrib['id']
        for score_part in root.find('part-list').iter('score-part'):
            if score_part.attrib['id'] == part_id:
                part_name = score_part.find('part-name').text
                voice_names[part_id] = part_name
                voices[part_id] = []

        for measure in part.iter('measure'):
            measure_notes = ''
            for note in measure.iter('note'):
                pitch_elem = note.find('pitch')
                if note.find('rest') is not None or pitch_elem is None:
                    measure_notes += 'z '
                else:
                    step = pitch_elem.find('step').text
                    alter = pitch_elem.find('alter')
                    octave = int(pitch_elem.find('octave').text)
                    if alter is not None:
                        alter = int(alter.text)
                    else:
                        alter = 0

                    pitch_obj = pitch.Pitch(step=step, octave=octave, accidental=alter)
                    note_name = get_note_name(pitch_obj)
                    measure_notes += note_name + ' '

            voices[part_id].append(measure_notes)

    voice_counter = 1
    for part_id, voice_name in voice_names.items():
        abc += f"%%MIDI program {voice_counter} 0
"
        abc += f'V:{voice_counter} clef=treble
'
        abc += f'[V:{voice_counter} "{voice_name}"] '
        voice_counter += 1

    for measure_num in range(len(voices[next(iter(voices))])):
        for voice_num, (part_id, voice_measures) in enumerate(voices.items(), start=1):
            abc += f"
V:{voice_num}
"
            abc += f"[V:{voice_num}] {voice_measures[measure_num]} |]
"

    return abc


if __name__ == "__main__":
    musicxml_file_path = 'path/to/musicxml'
    abc_output = musicxml_to_abc(musicxml_file_path)

    print(abc_output)

内容的提问来源于stack exchange,提问作者Coen Hacking

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 22:34:59