
本教程详细介绍了如何使用java sound api从实时midi乐器获取输入流并进行处理。文章首先阐明了直接通过`sequencer`监听事件的局限性,进而提出并演示了通过实现自定义`receiver`接口来实时接收和处理midi消息的核心方法。此外,教程还探讨了如何在实时处理的同时,利用第二个`transmitter`实现midi数据的并行录制,并提供了完整的代码示例和注意事项,帮助开发者构建高效的midi应用。
在开发需要与实时MIDI乐器交互的应用时,例如自动乐谱显示器或MIDI控制器,获取并处理来自乐器的MIDI输入流是核心任务。Java Sound API提供了一套强大的工具来完成此项工作。本教程将深入探讨如何高效地从实时MIDI设备接收消息,并处理实时事件,同时兼顾并行录制的需求。
Java Sound API中的javax.sound.midi包是处理MIDI数据的核心。要从外部MIDI设备(如数字钢琴)接收数据,主要涉及以下几个关键接口:
当MIDI设备产生事件时,它通过其Transmitter发送MidiMessage。为了接收这些消息,我们需要创建一个Receiver并将其连接到设备的Transmitter。
在尝试实时监听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});这种方法在处理实时乐器输入时往往无法提供即时回调。
要实现真正的实时MIDI消息回调,最直接且有效的方法是创建一个自定义的Receiver实现。当MIDI设备发送消息时,这些消息会直接传递给连接到其Transmitter的Receiver。
首先,需要识别并打开你的MIDI输入设备。MidiSystem.getMidiDeviceInfo()方法可以列出所有可用的MIDI设备。
动态WEB网站中的PHP和MySQL详细反映实际程序的需求,仔细地探讨外部数据的验证(例如信用卡卡号的格式)、用户登录以及如何使用模板建立网页的标准外观。动态WEB网站中的PHP和MySQL的内容不仅仅是这些。书中还提到如何串联JavaScript与PHP让用户操作时更快、更方便。还有正确处理用户输入错误的方法,让网站看起来更专业。另外还引入大量来自PEAR外挂函数库的强大功能,对常用的、强大的包
508
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.");
}
// ... 后续代码将在此处添加
}
}创建一个内部类或独立类,实现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 方法)获取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.");
}
}
}通过实现自定义Receiver接口,Java开发者可以高效、实时地从MIDI乐器获取并处理MIDI输入流。结合Sequencer的并行录制能力,可以构建功能强大的MIDI应用,满足实时交互和数据存储的双重需求。理解Transmitter和Receiver的工作原理,并遵循良好的资源管理和线程安全实践,是开发健壮MIDI应用的关键。
以上就是Java MIDI实时输入流处理教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号