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; } }
关键逻辑说明
- 用
note.getPosition()获取音符的起始时间(单位:微秒),通过pendingNotes缓存同一时间窗口内的所有音符。 - 设置
TIME_THRESHOLD是为了兼容MIDI编辑中可能出现的极微小时间差,避免把本该是和弦的音符拆成单音。 - 在
onTrackParsed(轨道解析完成时)统一处理缓存的音符组,区分单音与和弦并分别处理;如果需要实时处理,也可以监听onNoteOffParsed事件,当一个时间组的所有音符都结束时再做处理。
额外注意事项
- 确保MIDI文件中的和弦确实是同时触发的音符,若音符间时间差超过阈值,会被识别为单音序列。
- JFugue的
Chord类支持直接通过Note数组创建,可通过chord.getSymbol()获取和弦名称,或遍历内部音符做自定义处理。
内容的提问来源于stack exchange,提问作者BabaNew
相关产品推荐
相关产品推荐

