AI编写的MIDI解码程序,PHP语言版本1
PHP MIDI 文件解码器(读取 .mid,解析MThd/MTrk、VLQ、事件)
说明:PHP 7.4+ 可用,仅解析 MIDI 1.0 标准文件,支持读取文件头、多音轨、VLQ变长整数解码、MIDI事件、Meta事件。
不做音频渲染,只解析MIDI二进制指令,刚好适配你电鼓MIDI分析需求。
<?php
class MidiDecoder
{
public string $raw;
public int $ptr = 0;
public array $header = [];
public array $tracks = [];
public function loadFile(string $filePath): bool
{
if (!is_file($filePath)) return false;
$this->raw = file_get_contents($filePath);
$this->ptr = 0;
return $this->parseHeader();
}
// 读取N字节,大端
protected function readBig(int $bytes): int
{
$bin = substr($this->raw, $this->ptr, $bytes);
$this->ptr += $bytes;
$unpack = unpack('N', str_pad($bin, 4, chr(0), STR_PAD_LEFT));
return $unpack[1];
}
// VLQ 变长整数解码(MIDI核心编码)
protected function readVLQ(): int
{
$val = 0;
while (true) {
$b = ord($this->raw[$this->ptr++]);
$val = ($val << 7) | ($b & 0x7F);
if (($b & 0x80) === 0) break;
}
return $val;
}
// 解析文件头 MThd
protected function parseHeader(): bool
{
$id = substr($this->raw, $this->ptr, 4);
$this->ptr += 4;
if ($id !== 'MThd') return false;
$len = $this->readBig(4);
$fmt = $this->readBig(2);
$trackCount = $this->readBig(2);
$tpqn = $this->readBig(2);
$this->header = [
'format' => $fmt,
'track_count' => $trackCount,
'tpqn' => $tpqn
];
// 解析全部音轨 MTrk
for ($t = 0; $t < $trackCount; $t++) {
$this->parseTrack();
}
return true;
}
// 解析单条MTrk音轨
protected function parseTrack()
{
$id = substr($this->raw, $this->ptr, 4);
$this->ptr +=4;
$trackLen = $this->readBig(4);
$trackEnd = $this->ptr + $trackLen;
$events = [];
$runningStatus = 0;
while ($this->ptr < $trackEnd) {
$delta = $this->readVLQ();
$byte = ord($this->raw[$this->ptr]);
// Running Status复用
if (($byte & 0x80) === 0) {
$status = $runningStatus;
} else {
$status = $byte;
$runningStatus = $byte;
$this->ptr++;
}
$type = ($status >> 4) & 0x0F;
$ch = $status & 0x0F;
if ($status === 0xFF) {
// Meta事件 FF
$metaType = ord($this->raw[$this->ptr++]);
$metaLen = $this->readVLQ();
$metaData = substr($this->raw, $this->ptr, $metaLen);
$this->ptr += $metaLen;
$events[] = [
'delta' => $delta,
'type' => 'meta',
'meta_type' => $metaType,
'data' => $metaData
];
} elseif ($status === 0xF0 || $status === 0xF7) {
// SysEx系统独占事件
$sysexLen = $this->readVLQ();
$data = substr($this->raw, $this->ptr, $sysexLen);
$this->ptr += $sysexLen;
$events[] = [
'delta' => $delta,
'type' => 'sysex',
'status' => $status,
'data' => $data
];
} else {
// MIDI通道事件 NoteOn / NoteOff / CC / ProgramChange
switch ($type) {
case 0x9: // Note On
$note = ord($this->raw[$this->ptr++]);
$vel = ord($this->raw[$this->ptr++]);
$events[] = [
'delta' => $delta,
'type' => 'note_on',
'ch' => $ch,
'note' => $note,
'vel' => $vel
];
break;
case 0x8: // Note Off
$note = ord($this->raw[$this->ptr++]);
$vel = ord($this->raw[$this->ptr++]);
$events[] = [
'delta' => $delta,
'type' => 'note_off',
'ch' => $ch,
'note' => $note,
'vel' => $vel
];
break;
case 0xB: // CC控制器
$ccNum = ord($this->raw[$this->ptr++]);
$val = ord($this->raw[$this->ptr++]);
$events[] = [
'delta' => $delta,
'type' => 'cc',
'ch' => $ch,
'cc' => $ccNum,
'value' => $val
];
break;
case 0xC: // Program Change 音色切换
$prog = ord($this->raw[$this->ptr++]);
$events[] = [
'delta' => $delta,
'type' => 'program',
'ch' => $ch,
'program' => $prog
];
break;
case 0xE: // 弯音
$b1 = ord($this->raw[$this->ptr++]);
$b2 = ord($this->raw[$this->ptr++]);
$pitch = ($b2 <<7) | $b1;
$events[] = [
'delta' => $delta,
'type' => 'pitch',
'ch' => $ch,
'value' => $pitch
];
break;
default:
// 其他未实现事件,跳过字节
$events[] = ['delta'=>$delta, 'type'=>'unknown','status'=>$status];
break;
}
}
}
$this->tracks[] = $events;
}
// 打印解析结果
public function dump()
{
echo "==== MIDI HEADER ====\n";
print_r($this->header);
echo "\n==== TRACK COUNT: {$this->header['track_count']} ====\n";
foreach ($this->tracks as $tkIdx => $events) {
echo "\n--- Track #$tkIdx , total events: ".count($events)."\n";
foreach ($events as $e) {
echo "delta:{$e['delta']} | ";
echo match($e['type']) {
'note_on' => "NOTE_ON ch{$e['ch']} note{$e['note']} vel{$e['vel']}",
'note_off' => "NOTE_OFF ch{$e['ch']} note{$e['note']} vel{$e['vel']}",
'cc' => "CC ch{$e['ch']} cc{$e['cc']} val{$e['value']}",
'program' => "PROGRAM ch{$e['ch']} prog{$e['program']}",
'meta' => "META type{$e['meta_type']} len=".strlen($e['data']),
'sysex' => "SYSEX",
default => "UNKNOWN"
};
echo "\n";
}
}
}
}
// ========= 使用示例 =========
$decoder = new MidiDecoder();
$midFile = './test.mid'; // 替换成你的midi文件路径
if ($decoder->loadFile($midFile)) {
$decoder->dump();
} else {
echo "解析失败,文件不存在或者不是合法MIDI";
}
?>功能说明
- VLQ 变长整数解码(MIDI最核心部分)
- 解析 MThd 文件头:MIDI格式、轨道数量、TPQN
- 解析 MTrk 音轨块,支持 Running Status 运行状态压缩
支持事件类型:
- NoteOn / NoteOff(鼓音符,通道10打击乐)
- CC控制器、ProgramChange音色切换、弯音
- Meta元事件(BPM、拍号、文本、轨道结束标记)
- SysEx系统消息
- 输出每条事件的
delta tick
针对你的电鼓MIDI补充
MIDI打击乐固定在 通道9(程序里ch=9,对应MIDI文档叫通道10)
鼓键映射参考:
36=底鼓Kick,38=军鼓Snare,42=闭踩镲HH Closed,46开踩镲,49叮叮镲Ride,51吊镲Crash
使用要点
- 保存为
midi_decode.php,把 test.mid 放到同目录 - 命令行执行:
php midi_decode.php - 大MIDI文件可以把dump打印关掉,改成存入数组做后续分析
可扩展方向(你可以按需加)
- 把tick换算成真实时间(依靠meta事件里的BPM)
- 过滤只输出打击乐通道ch=9事件(只提取鼓点,DTX谱分析)
- 导出为JSON,存入数据库
版权属于:Joyber
本文链接:https://blog.qqvbc.com/default/1559.html
转载时须注明出处及本声明