在C语言中直接读取MIDI文件并不简单,因为MIDI文件是一种包含音乐事件(如音符的开始和结束、控制信号等)的二进制格式,而不是像文本文件那样容易解析。不过,你可以通过以下步骤来实现:
- 了解MIDI文件格式:
- MIDI文件有多种格式,最常见的是SMF (Standard MIDI File) 格式,也被称为Type 0、Type 1或Type 2。
- 你需要了解MIDI文件的各个部分,包括文件头、轨道头和轨道事件等。
- 编写解析器:
- 编写一个C程序来读取MIDI文件的二进制内容。
- 首先,你需要读取文件头以获取文件的整体信息(如文件类型、轨道数等)。
- 然后,逐个读取轨道头,了解每个轨道的详细信息。
- 接下来,解析轨道中的MIDI事件,这通常涉及解析时间戳和事件数据。
- 处理MIDI事件:
- 对于每个MIDI事件,你需要解析事件数据以了解它是什么类型的事件(如音符开始、音符结束、控制更改等)。
- 根据事件类型,你可以执行相应的操作,如播放音符、更改音量或发送其他MIDI消息。
- 使用库:
- 考虑到MIDI文件的复杂性,你可能希望使用现有的库来帮助解析和处理MIDI文件。
- 例如,你可以查找支持C语言的MIDI库,如
libsmf
或libmidi
(请注意,这些库可能不存在或已过时,因为MIDI文件处理在C语言中并不常见)。
- 集成到项目中:
- 一旦你能够解析和处理MIDI文件,你可以将其集成到你的C项目中。
- 这可能涉及将MIDI事件转换为音频输出(如使用MIDI输出设备或软件合成器)或将MIDI数据转换为其他格式(如MIDI转乐谱)。
- 测试和调试:
- 编写测试用例来验证你的MIDI解析器是否能够正确处理各种MIDI文件。
- 使用调试工具来查找和修复任何错误或问题。
请注意,直接处理MIDI文件需要深入了解MIDI规范和相关文件格式。如果你不熟悉这些概念,可能需要花费一些时间来学习它们。此外,由于MIDI文件的复杂性,编写一个健壮且可靠的MIDI解析器可能是一个相当复杂的任务。
如果你只是想在C语言项目中播放MIDI文件,而不是解析和处理MIDI文件的内部细节,那么一个更简单的方法是使用外部MIDI播放器或库来播放MIDI文件,并通过命令行或API接口与你的C程序进行交互。
请先看百度百科:MIDI文件格式
编写 mid_head.c 读取 midi 文件头部:
#include <stdio.h>
#include <stdint.h>
typedef struct {
char ctag[4]; // chunk_tag: MThd
int32_t chunk_size;
// 指定Midi的格式: 00 00单音轨; 00 01多音轨,且同步; 00 02多音轨,但不同步
uint16_t geshi;
uint16_t tracks; // 轨道数:=实际音轨数字 +1个全局音轨
// 指定基本时间格式类型: 类型1:定义一个四分音符的tick数;
// 类型2:定义每秒中SMTPE帧的数量及每个SMTPE帧的ticks
uint16_t ticks;
char ttag[4]; // track_tag: MTrk
uint8_t t_id;
uint16_t track_size;
} MidiHeader;
uint16_t swapUint16(uint16_t shortValue){
return ((shortValue & 0x00FF ) <<8) | ((shortValue & 0xFF00)>>8);
}
int32_t swapInt32(int32_t intValue){
int32_t temp = 0;
temp = ((intValue & 0x000000FF) <<24) +
((intValue & 0x0000FF00) <<8) +
((intValue & 0x00FF0000) >>8) +
((intValue & 0xFF000000) >>24);
return temp;
}
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}
const char *f1 = argv[1]; // filename
FILE *file = fopen(f1, "rb");
if (!file) {
perror("Error opening file");
return -1;
}
MidiHeader hd;
if (fread(&hd, sizeof(MidiHeader), 1, file) != 1) {
fclose(file);
perror("Error reading file head");
return -1;
}
// 打印读取到的数据,验证读取成功
printf("Chunk tag: %s\n", hd.ctag);
printf("Chunk Size: %04d\n", swapInt32(hd.chunk_size));
printf("geshi:%d, tracks:%d, ticks:%d\n",swapUint16(hd.geshi),swapUint16(hd.tracks),swapUint16(hd.ticks));
printf("Track tag: %s\n", hd.ttag);
printf("track id: %x, track Size: %d\n", hd.t_id, swapUint16(hd.track_size));
if (fseek(file, swapUint16(hd.track_size)-2, 1) !=0) {
fclose(file);
perror("Error fseek file ");
return -1;
}
char t1tag[5];
if (fread(&t1tag, sizeof(char), 4, file) != 4) {
fclose(file);
perror("Error reading file head");
return -1;
}
printf("track1 tag: %s\n", t1tag);
uint32_t track1_size;
if (fread(&track1_size, sizeof(uint32_t), 1, file) != 1) {
fclose(file);
perror("Error reading file head");
return -1;
}
printf("track1 size: %d\n", swapInt32(track1_size));
fclose(file);
return 0;
}
where gcc
D:\Strawberry\c\bin\gcc.exe
编译 gcc mid_head.c -o mid_head.exe
运行 mid_head happy_birthday.mid
mid_head happy_birthday.mid
Chunk tag: MThd
Chunk Size: 0006
geshi:1, tracks:2, ticks:1024
Track tag: MTrk
Track id: 0, Track Size: 20
Track1 tag: MTrk
track1 size: 247
为了对单个几十MB的.mid 文件采样数据,读取.mid 文件头部 4080 bytes
Unix 命令 head -c 4080 sample1.mid > temp1.mid
运行 strings temp1.mid