首页 > Java > java教程 > 正文

Java MIDI实时输入流处理教程

心靈之曲
发布: 2025-12-03 20:19:02
原创
985人浏览过

Java MIDI实时输入流处理教程

本教程详细介绍了如何使用java sound api从实时midi乐器获取输入流并进行处理。文章首先阐明了直接通过`sequencer`监听事件的局限性,进而提出并演示了通过实现自定义`receiver`接口来实时接收和处理midi消息的核心方法。此外,教程还探讨了如何在实时处理的同时,利用第二个`transmitter`实现midi数据的并行录制,并提供了完整的代码示例和注意事项,帮助开发者构建高效的midi应用。

Java Sound API实时MIDI输入处理详解

在开发需要与实时MIDI乐器交互的应用时,例如自动乐谱显示器或MIDI控制器,获取并处理来自乐器的MIDI输入流是核心任务。Java Sound API提供了一套强大的工具来完成此项工作。本教程将深入探讨如何高效地从实时MIDI设备接收消息,并处理实时事件,同时兼顾并行录制的需求。

理解MIDI输入与Java Sound API

Java Sound API中的javax.sound.midi包是处理MIDI数据的核心。要从外部MIDI设备(如数字钢琴)接收数据,主要涉及以下几个关键接口:

  • MidiDevice: 代表一个MIDI设备,可以是输入设备(如键盘)或输出设备(如合成器)。
  • Transmitter: 用于从一个MidiDevice发送MIDI消息。
  • Receiver: 用于接收MIDI消息。

当MIDI设备产生事件时,它通过其Transmitter发送MidiMessage。为了接收这些消息,我们需要创建一个Receiver并将其连接到设备的Transmitter。

实时MIDI事件监听的挑战

在尝试实时监听MIDI输入时,开发者可能会自然地想到使用Sequencer并为其添加ControllerEventListener。然而,Sequencer的主要职责是播放和录制MIDI序列,其内置的事件监听器通常更侧重于序列内部的控制事件,而不是直接从外部设备传入的原始、实时MIDI消息。

立即学习Java免费学习笔记(深入)”;

例如,以下代码片段尝试通过Sequencer监听控制事件,但在实时录制过程中可能无法如预期般工作:

sequencer.addControllerEventListener(new ControllerEventListener() {
    @Override
    public void controlChange(ShortMessage event) {
        System.out.println("listener works");
    }
}, new int[] {127});
登录后复制

这种方法在处理实时乐器输入时往往无法提供即时回调。

解决方案:实现自定义Receiver

要实现真正的实时MIDI消息回调,最直接且有效的方法是创建一个自定义的Receiver实现。当MIDI设备发送消息时,这些消息会直接传递给连接到其Transmitter的Receiver。

1. 查找并打开MIDI输入设备

首先,需要识别并打开你的MIDI输入设备。MidiSystem.getMidiDeviceInfo()方法可以列出所有可用的MIDI设备。

动态WEB网站中的PHP和MySQL:直观的QuickPro指南第2版
动态WEB网站中的PHP和MySQL:直观的QuickPro指南第2版

动态WEB网站中的PHP和MySQL详细反映实际程序的需求,仔细地探讨外部数据的验证(例如信用卡卡号的格式)、用户登录以及如何使用模板建立网页的标准外观。动态WEB网站中的PHP和MySQL的内容不仅仅是这些。书中还提到如何串联JavaScript与PHP让用户操作时更快、更方便。还有正确处理用户输入错误的方法,让网站看起来更专业。另外还引入大量来自PEAR外挂函数库的强大功能,对常用的、强大的包

动态WEB网站中的PHP和MySQL:直观的QuickPro指南第2版 508
查看详情 动态WEB网站中的PHP和MySQL:直观的QuickPro指南第2版
import javax.sound.midi.*;
import java.io.File;
import java.io.IOException;
import javax.swing.Timer;

public class MidiInputTutorial {

    public static void main(String[] args) throws Exception {
        // 1. 列出所有MIDI设备信息
        Info[] infos = MidiSystem.getMidiDeviceInfo();
        System.out.println("Available MIDI Devices:");
        for (int i = 0; i < infos.length; i++) {
            System.out.println(i + ": " + infos[i].getName() + " - " + infos[i].getDescription());
        }

        // 假设你的钢琴是列表中的某个设备,这里我们选择索引1作为示例
        // 实际应用中可能需要更智能的设备选择逻辑
        MidiDevice inputDevice = MidiSystem.getMidiDevice(infos[1]); // 请根据实际情况调整索引

        // 2. 打开MIDI输入设备
        if (!inputDevice.isOpen()) {
            inputDevice.open();
            System.out.println("MIDI Input Device '" + inputDevice.getDeviceInfo().getName() + "' opened.");
        } else {
            System.out.println("MIDI Input Device '" + inputDevice.getDeviceInfo().getName() + "' is already open.");
        }

        // ... 后续代码将在此处添加
    }
}
登录后复制

2. 实现自定义Receiver

创建一个内部类或独立类,实现Receiver接口。send(MidiMessage message, long timeStamp)方法是核心,每当接收到MIDI消息时,该方法就会被调用。

// ... (在 MidiInputTutorial 类内部)

    private static class MyRealtimeReceiver implements Receiver {
        @Override
        public void send(MidiMessage message, long timeStamp) {
            // 在这里处理实时MIDI消息
            if (message instanceof ShortMessage) {
                ShortMessage sm = (ShortMessage) message;
                // 仅处理NOTE_ON消息作为示例
                if (sm.getCommand() == ShortMessage.NOTE_ON) {
                    int key = sm.getData1();
                    int velocity = sm.getData2();
                    System.out.println("实时接收: Note ON - Key: " + key + ", Velocity: " + velocity + ", Timestamp: " + timeStamp);
                    // 实际应用中,你可能需要将此处理逻辑放入单独的线程,以避免阻塞MIDI输入流
                } else if (sm.getCommand() == ShortMessage.NOTE_OFF) {
                    int key = sm.getData1();
                    int velocity = sm.getData2(); // Note_Off的velocity通常为0,但有些设备可能发送非零值
                    System.out.println("实时接收: Note OFF - Key: " + key + ", Velocity: " + velocity + ", Timestamp: " + timeStamp);
                }
                // 也可以处理其他类型的ShortMessage,如Control Change (sm.getCommand() == ShortMessage.CONTROL_CHANGE)
            } else if (message instanceof SysexMessage) {
                // 处理系统独占消息
                // System.out.println("实时接收: Sysex Message");
            } else if (message instanceof MetaMessage) {
                // 处理Meta消息 (通常在序列中出现,实时输入较少)
                // System.out.println("实时接收: Meta Message");
            }
        }

        @Override
        public void close() {
            System.out.println("MyRealtimeReceiver closed.");
        }
    }

// ... (回到 main 方法)
登录后复制

3. 连接Transmitter和Receiver

获取MIDI输入设备的Transmitter,然后将你的自定义Receiver设置为其接收器。

// ... (在 main 方法中,在 inputDevice.open() 之后)

        // 3. 获取输入设备的Transmitter
        Transmitter transmitter = inputDevice.getTransmitter();

        // 4. 创建并设置自定义Receiver
        Receiver myRealtimeReceiver = new MyRealtimeReceiver();
        transmitter.setReceiver(myRealtimeReceiver);

        System.out.println("Custom Receiver is now listening for MIDI input...");

        // 为了让程序保持运行并接收事件,可以添加一个简单的延迟或计时器
        // 实际应用中,你可能需要一个主循环或事件驱动的UI
        new Timer(20000, e -> { // 运行20秒后退出
            System.out.println("\nStopping MIDI input and exiting...");
            transmitter.close(); // 关闭transmitter
            myRealtimeReceiver.close(); // 关闭自定义receiver
            inputDevice.close(); // 关闭输入设备
            System.exit(0);
        }).start();

        // 阻止主线程立即退出
        Thread.sleep(21000); // 确保计时器有时间执行
    } // main 方法结束
} // MidiInputTutorial 类结束
登录后复制

通过这种方式,MyRealtimeReceiver的send方法将在每次MIDI消息从乐器发出时被调用,从而实现真正的实时回调。

进阶:实时处理与并行录制

在某些场景下,你可能不仅需要实时处理MIDI事件,还需要将这些事件同时录制到一个Sequence中,以便后续保存或分析。这可以通过使用Sequencer的录制功能来实现,但需要从MIDI输入设备获取第二个Transmitter并将其连接到Sequencer的Receiver。

重要提示: 一个MidiDevice可以有多个Transmitter,每个Transmitter可以连接到不同的Receiver。

// ... (在 main 方法中,在 inputDevice.open() 之后)

        // ... (设置 myRealtimeReceiver 的代码)

        // 5. 设置Sequencer进行并行录制
        Sequencer sequencer = MidiSystem.getSequencer();
        sequencer.open();

        // 获取输入设备的第二个Transmitter用于录制
        Transmitter recordingTransmitter = inputDevice.getTransmitter();
        Receiver sequencerReceiver = sequencer.getReceiver();
        recordingTransmitter.setReceiver(sequencerReceiver);

        Sequence seq = new Sequence(Sequence.PPQ, 24); // 创建一个新的MIDI序列
        Track currentTrack = seq.createTrack(); // 创建一个轨道

        sequencer.setSequence(seq);
        sequencer.setTickPosition(0);
        sequencer.recordEnable(currentTrack, -1); // 启用录制到指定轨道
        sequencer.startRecording(); // 开始录制

        System.out.println("Sequencer is now recording MIDI input in parallel...");

        // 计时器结束时停止录制并保存文件
        new Timer(10000, e -> { // 录制10秒
            System.out.println("\nStopping recording...");
            sequencer.stopRecording();

            try {
                // 将录制的数据写入MIDI文件
                MidiSystem.write(sequencer.getSequence(), 0, new File("recorded_midi.mid"));
                System.out.println("Recorded MIDI saved to recorded_midi.mid");
            } catch (IOException e1) {
                e1.printStackTrace();
            }

            // 关闭所有资源
            recordingTransmitter.close();
            sequencerReceiver.close();
            sequencer.close();
            transmitter.close();
            myRealtimeReceiver.close();
            inputDevice.close();
            System.exit(0);
        }).start();

        // 阻止主线程立即退出
        Thread.sleep(11000); // 确保计时器有时间执行
    } // main 方法结束
} // MidiInputTutorial 类结束
登录后复制

完整代码示例 (整合实时处理与并行录制):

import javax.sound.midi.*;
import javax.sound.midi.MidiDevice.Info;
import javax.swing.Timer;
import java.io.File;
import java.io.IOException;

public class MidiInputTutorial {

    public static void main(String[] args) throws Exception {

        // 1. 列出所有MIDI设备信息
        Info[] infos = MidiSystem.getMidiDeviceInfo();
        System.out.println("Available MIDI Devices:");
        if (infos.length == 0) {
            System.err.println("No MIDI devices found. Please ensure your MIDI device is connected and drivers are installed.");
            return;
        }
        for (int i = 0; i < infos.length; i++) {
            System.out.println(i + ": " + infos[i].getName() + " - " + infos[i].getDescription());
        }

        // 假设你的钢琴是列表中的某个设备,这里我们选择索引1作为示例
        // 实际应用中可能需要更智能的设备选择逻辑,例如通过名称匹配
        MidiDevice inputDevice = null;
        int deviceIndex = 1; // 示例索引,请根据实际情况调整
        if (deviceIndex >= 0 && deviceIndex < infos.length) {
            inputDevice = MidiSystem.getMidiDevice(infos[deviceIndex]);
        } else {
            System.err.println("Invalid device index. Please choose a valid index from the list.");
            return;
        }

        // 2. 打开MIDI输入设备
        if (!inputDevice.isOpen()) {
            inputDevice.open();
            System.out.println("MIDI Input Device '" + inputDevice.getDeviceInfo().getName() + "' opened.");
        } else {
            System.out.println("MIDI Input Device '" + inputDevice.getDeviceInfo().getName() + "' is already open.");
        }

        // 3. 设置自定义Receiver进行实时处理
        Transmitter realtimeTransmitter = inputDevice.getTransmitter();
        MyRealtimeReceiver myRealtimeReceiver = new MyRealtimeReceiver();
        realtimeTransmitter.setReceiver(myRealtimeReceiver);
        System.out.println("Custom Receiver is now listening for real-time MIDI input...");

        // 4. 设置Sequencer进行并行录制
        Sequencer sequencer = MidiSystem.getSequencer();
        sequencer.open();

        // 获取输入设备的第二个Transmitter用于录制
        Transmitter recordingTransmitter = inputDevice.getTransmitter();
        Receiver sequencerReceiver = sequencer.getReceiver();
        recordingTransmitter.setReceiver(sequencerReceiver);

        Sequence seq = new Sequence(Sequence.PPQ, 24); // 创建一个新的MIDI序列
        Track currentTrack = seq.createTrack(); // 创建一个轨道

        sequencer.setSequence(seq);
        sequencer.setTickPosition(0);
        sequencer.recordEnable(currentTrack, -1); // 启用录制到指定轨道
        sequencer.startRecording(); // 开始录制
        System.out.println("Sequencer is now recording MIDI input in parallel...");

        // 计时器:10秒后停止录制,20秒后停止实时监听并退出
        new Timer(10000, e -> {
            System.out.println("\nStopping recording...");
            sequencer.stopRecording();

            try {
                MidiSystem.write(sequencer.getSequence(), 0, new File("recorded_midi.mid"));
                System.out.println("Recorded MIDI saved to recorded_midi.mid");
            } catch (IOException e1) {
                e1.printStackTrace();
            }

            // 关闭Sequencer相关的资源
            recordingTransmitter.close();
            sequencerReceiver.close();
            sequencer.close();
        }).start();

        new Timer(20000, e -> {
            System.out.println("\nStopping real-time MIDI input and exiting...");
            // 关闭实时监听相关的资源
            realtimeTransmitter.close();
            myRealtimeReceiver.close();
            inputDevice.close(); // 关闭输入设备
            System.exit(0);
        }).start();

        // 阻止主线程立即退出,等待计时器完成
        Thread.sleep(21000);
    }

    private static class MyRealtimeReceiver implements Receiver {
        @Override
        public void send(MidiMessage message, long timeStamp) {
            if (message instanceof ShortMessage) {
                ShortMessage sm = (ShortMessage) message;
                if (sm.getCommand() == ShortMessage.NOTE_ON) {
                    int key = sm.getData1();
                    int velocity = sm.getData2();
                    System.out.println("实时接收: Note ON - Key: " + key + ", Velocity: " + velocity + ", Timestamp: " + timeStamp);
                    // 建议:对于复杂的实时处理,在此处将事件放入队列,由另一个线程异步处理
                } else if (sm.getCommand() == ShortMessage.NOTE_OFF) {
                    int key = sm.getData1();
                    int velocity = sm.getData2();
                    System.out.println("实时接收: Note OFF - Key: " + key + ", Velocity: " + velocity + ", Timestamp: " + timeStamp);
                }
            }
        }

        @Override
        public void close() {
            System.out.println("MyRealtimeReceiver closed.");
        }
    }
}
登录后复制

注意事项与最佳实践

  1. 设备选择: 在实际应用中,不应硬编码设备索引。通常通过遍历MidiSystem.getMidiDeviceInfo()返回的Info数组,根据getName()或getDescription()匹配特定的设备名称来选择正确的输入设备。
  2. 线程安全: Receiver.send()方法是在MIDI事件线程中调用的。如果你的实时处理逻辑(例如更新UI、执行复杂计算)耗时较长,可能会阻塞MIDI事件流,导致延迟或丢帧。建议将耗时的处理任务放入一个单独的线程池或使用事件队列进行异步处理。
  3. 资源管理: 务必在程序退出或不再需要时关闭所有打开的MIDI资源,包括MidiDevice、Transmitter、Receiver和Sequencer。否则可能导致资源泄露或设备被占用。
  4. 错误处理: 捕获MidiUnavailableException等异常,以优雅地处理MIDI设备不可用或访问失败的情况。
  5. MIDI消息类型: MidiMessage有多种子类型,如ShortMessage(音符、控制改变等)、SysexMessage(系统独占消息)和MetaMessage(元消息)。在Receiver.send()方法中,你需要根据MidiMessage的实际类型进行向下转型和处理。

总结

通过实现自定义Receiver接口,Java开发者可以高效、实时地从MIDI乐器获取并处理MIDI输入流。结合Sequencer的并行录制能力,可以构建功能强大的MIDI应用,满足实时交互和数据存储的双重需求。理解Transmitter和Receiver的工作原理,并遵循良好的资源管理和线程安全实践,是开发健壮MIDI应用的关键。

以上就是Java MIDI实时输入流处理教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号