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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 04:54:53