如何在Java中读取MIDI设备数据并转换为音符信息
解析MIDI键盘的字节数组并转换为音符名称
一、这些字节数组是什么?
你收到的是MIDI协议消息的字节缓冲区,其中:
- 前几个非零字节是实际的MIDI控制消息,后面的大量
0是缓冲区的填充数据,无实际意义,可直接忽略。 - MIDI消息由状态字节和数据字节组成:
- 状态字节最高位为1(对应Java中byte类型的负数,因byte是有符号类型,范围-128~127),用来标识消息类型(比如Note On、Note Off、控制器消息等)。
- 数据字节最高位为0(正数或0),是消息的具体参数(比如音符编号、按键力度)。
- 举个例子:你数组里的
-112(转成无符号int为0x90)是通道1的Note On消息,后面的47是音符编号,75是按键力度。
二、Java中转换为D4、C#5这类音符名称的方法
核心步骤
- 过滤缓冲区中的空字节(
0),提取有效MIDI消息。 - 识别Note On/Off类型的消息(状态字节范围
0x80~0x8F是Note Off,0x90~0x9F是Note On)。 - 将MIDI音符编号(0~127)映射为对应的音名+八度。
示例代码
修改你原有的onSend方法,加入解析逻辑:
import android.util.Log; // 音名映射数组,对应MIDI编号0~11的音名 private static final String[] NOTE_NAMES = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; @Override public void onSend(byte[] bytes, int offset, int count, long timestamp) { // 遍历有效数据范围(offset到offset+count) for (int i = offset; i < offset + count; ) { int status = bytes[i] & 0xFF; // 跳过空字节 if (status == 0) { i++; continue; } // 提取消息类型(高4位) int messageType = status & 0xF0; // 处理Note On和Note Off消息 if (messageType == 0x80 || messageType == 0x90) { // 消息需要2个数据字节,检查是否有足够数据 if (i + 2 >= offset + count) { break; } // 转换为无符号int,避免有符号byte的问题 int noteNumber = bytes[i+1] & 0xFF; int velocity = bytes[i+2] & 0xFF; // 计算八度和音名 int octave = (noteNumber / 12) - 1; // MIDI编号60对应C4,60/12=5,5-1=4 int noteIndex = noteNumber % 12; String noteName = NOTE_NAMES[noteIndex] + octave; // 区分Note On/Off(Note On力度为0时视为Off) String action = (messageType == 0x90 && velocity > 0) ? "按下" : "松开"; Log.d("MIDI音符", action + ": " + noteName + " | 力度: " + velocity); // 跳过当前3字节的消息 i += 3; } else { // 其他类型消息(如控制器、程序变更),这里简单跳过1字节,可按需扩展 i++; } } }
关键说明
bytes[i] & 0xFF:将有符号byte转换为无符号int,正确表示MIDI协议中0~255的数值范围。- 八度计算:MIDI编号60是音乐中的中央C(C4),通过
(noteNumber / 12) - 1对齐通用的音符命名规则。 - Note On力度为0:部分MIDI设备会用力度0的Note On消息代替Note Off,代码中做了兼容处理。
内容的提问来源于stack exchange,提问作者M. Feyz
相关产品推荐
相关产品推荐

