如何在Sequencer API中监听NOTE_ON事件且不破坏音效?
问题:监听MIDI NOTE_ON事件同时保证音效流畅
使用Java的Sequencer API时,无法直接监听NOTE_ON事件。参考《Head First Java》中的方案,我尝试注册ControllerEventListener来监听与目标NOTE_ON事件同步的虚拟CONTROL_CHANGE事件(事件号设为127),但这种方式会导致音效过渡失去流畅性。你可以运行附带的代码对比开启/关闭监听逻辑时的音效差异,现在需要一个能在不影响音效的前提下监听NOTE_ON事件的方案。
代码示例
App入口类
public class App { public static void main(String[] args) { MidiPlayer player = new MidiPlayer(); player.playRegularPiano(); } }
MidiPlayer实现类
import static javax.sound.midi.ShortMessage.*; @SuppressWarnings("unused") public class MidiPlayer { private static final int KEYBOARD_CHANNEL = 1; private static final int PERCUSSION_CHANNEL = 9; private static final int MAGIC_NUMBER_THAT_MEANS_SEQUENCE_IS_FINISHED = 47; private static final int HOOK_EVENT_NUM = 127; // 自定义的非保留事件号 public void playRegularPiano() { playInstrument(KEYBOARD_CHANNEL, Keyboards.REGULAR_PIANO); } // 其他playX()方法 @SneakyThrows private void playInstrument(int channel, int instrument) { Sequencer sequencer = MidiSystem.getSequencer(); sequencer.open(); // // sequencer.addControllerEventListener(event -> // System.out.println("la-la"), new int[]{HOOK_EVENT_NUM}); Sequence sequence = getSequence(channel, instrument); sequencer.setSequence(sequence); MetaEventListener listener = getClosingListener(sequencer); sequencer.addMetaEventListener(listener); sequencer.start(); } private Sequence getSequence(int channel, int instrument) throws InvalidMidiDataException { Sequence sequence = new Sequence(Sequence.PPQ, 4); MidiEventBuilder builder = new MidiEventBuilder(); Track track = sequence.createTrack(); ShortMessage firstMessage, secondMessage; MidiEvent instrumentPick, noteOn, noteOff, hookEvent; instrumentPick = builder.withCommand(PROGRAM_CHANGE) .withChannel(channel) .withFirstByte(instrument) .withSecondByte(80) .withTick(0) .build(); track.add(instrumentPick); for (int i = 0; i < 8; i++) { noteOn = builder.withCommand(NOTE_ON) .withChannel(channel) .withFirstByte(54 - i) .withSecondByte(80) .withTick(i + 1) .build(); track.add(noteOn); // // hookEvent = builder.withCommand(CONTROL_CHANGE) // .withChannel(channel) // .withFirstByte(HOOK_EVENT_NUM) // .withSecondByte(0) // .withTick(i + 1) // .build(); // track.add(hookEvent); noteOff = builder.withCommand(NOTE_OFF) .withChannel(channel) .withFirstByte(54 - i) .withSecondByte(80) .withTick(i + 1 + 20) .build(); track.add(noteOff); } for (int i = 8; i < 25; i++) { noteOn = builder.withCommand(NOTE_ON) .withChannel(channel) .withFirstByte(54 - (7 - (i - 7))) .withSecondByte(100) .withTick(i + 1) .build(); track.add(noteOn); // // hookEvent = builder.withCommand(CONTROL_CHANGE) // .withChannel(channel) // .withFirstByte(HOOK_EVENT_NUM) // .withSecondByte(0) // .withTick(i + 1) // .build(); // track.add(hookEvent); noteOff = builder.withCommand(NOTE_OFF) .withChannel(channel) .withFirstByte(54 - (7 - (i - 7))) .withSecondByte(100) .withTick(i + 1 + 20) .build(); track.add(noteOff); } return sequence; } private MetaEventListener getClosingListener(Sequencer sequencer) { return (metaMessage) -> { if (metaMessage.getType() == MAGIC_NUMBER_THAT_MEANS_SEQUENCE_IS_FINISHED) { sequencer.close(); } }; } private static class Keyboards { private static final int REGULAR_PIANO = 1; // 其他乐器常量 } private static class MidiEventBuilder { private int command; private int channel; private int firstByte; private int secondByte; private int tick; private MidiEventBuilder withCommand(int command) { this.command = command; return this; } private MidiEventBuilder withChannel(int channel) { this.channel = channel; return this; } private MidiEventBuilder withFirstByte(int firstByte) { this.firstByte = firstByte; return this; } private MidiEventBuilder withSecondByte(int secondByte) { this.secondByte = secondByte; return this; } private MidiEventBuilder withTick(int tick) { this.tick = tick; return this; } private MidiEvent build() throws InvalidMidiDataException { ShortMessage message = new ShortMessage(command, channel, firstByte, secondByte); return new MidiEvent(message, tick); } } }
内容的提问来源于stack exchange,提问作者Sergey Zolotarev
相关产品推荐
相关产品推荐

