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

JFugue中ParserListenerAdapter的onChordParsed方法未触发求助

解决JFugue读取MIDI和弦时onChordParsed未触发的问题

核心原因

JFugue的onChordParsed方法仅针对JFugue音乐字符串(Music String)中的显式和弦语法(比如Cmaj、F#dim这类直接声明的和弦标记)触发。而MIDI文件中的“和弦”本质是多个同时触发的NoteOn事件,JFugue默认会将它们作为独立音符逐个解析,不会自动合并为Chord对象,因此onChordParsed不会被调用。

解决方案:手动识别同时发声的音符

要从MIDI中提取和弦,需要自行跟踪音符的起始时间,将时间戳一致(或时间差在极小阈值内)的音符组合成和弦。以下是修改后的监听类实现:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jfugue.parser.ParserListenerAdapter;
import org.jfugue.theory.Chord;
import org.jfugue.theory.Note;

public class MusicStringParserListener extends ParserListenerAdapter {
    private List<String> tokenList = new ArrayList<>();
    // 缓存当前待组合的音符:key为起始时间,value为该时间点的所有音符
    private Map<Long, List<Note>> pendingNotes = new HashMap<>();
    // 时间阈值:小于该值的音符视为同时发声(单位:微秒,可根据MIDI精度调整)
    private static final long TIME_THRESHOLD = 10000; // 对应10ms

    @Override
    public void onNoteParsed(Note note) {
        long noteStartTime = note.getPosition();
        boolean merged = false;

        // 检查是否有时间接近的已存在音符组
        for (Map.Entry<Long, List<Note>> entry : pendingNotes.entrySet()) {
            if (Math.abs(noteStartTime - entry.getKey()) <= TIME_THRESHOLD) {
                entry.getValue().add(note);
                merged = true;
                break;
            }
        }

        // 未找到接近的时间组则新建
        if (!merged) {
            List<Note> newNoteGroup = new ArrayList<>();
            newNoteGroup.add(note);
            pendingNotes.put(noteStartTime, newNoteGroup);
        }
    }

    @Override
    public void onTrackParsed(long trackId) {
        // 轨道解析完成后,统一处理所有缓存的音符组
        for (List<Note> noteGroup : pendingNotes.values()) {
            if (noteGroup.size() == 1) {
                // 单音,按原有逻辑处理
                Note note = noteGroup.get(0);
                tokenList.add(note.getToneString() + note.getOctave() + note.getDuration());
            } else {
                // 多音符组合成和弦,执行自定义处理
                Chord chord = new Chord(noteGroup.toArray(new Note[0]));
                System.out.println("识别到和弦:" + chord);
                tokenList.add(chord.getSymbol());
            }
        }
        pendingNotes.clear(); // 清空缓存,准备解析下一个轨道
    }

    public List<String> getTokenList() {
        return tokenList;
    }
}

关键逻辑说明

  1. 用note.getPosition()获取音符的起始时间(单位:微秒),通过pendingNotes缓存同一时间窗口内的所有音符。
  2. 设置TIME_THRESHOLD是为了兼容MIDI编辑中可能出现的极微小时间差,避免把本该是和弦的音符拆成单音。
  3. 在onTrackParsed(轨道解析完成时)统一处理缓存的音符组,区分单音与和弦并分别处理;如果需要实时处理,也可以监听onNoteOffParsed事件,当一个时间组的所有音符都结束时再做处理。

额外注意事项

  • 确保MIDI文件中的和弦确实是同时触发的音符,若音符间时间差超过阈值,会被识别为单音序列。
  • JFugue的Chord类支持直接通过Note数组创建,可通过chord.getSymbol()获取和弦名称,或遍历内部音符做自定义处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 22:10:30