找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 15220|回复: 18

【文件结构】MIDI文件的结构和完整的读取方法

[复制链接]
发表于 2014-7-19 06:15:11 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
原帖:http://www.0xaa55.com/thread-717-1-1.html
转载请注明出处。

MIDI文件是一种多媒体音乐文件。不同于别的音乐文件,MIDI文件只记录曲谱。播放的时候,软件会读取MIDI文件的内容,然后把曲谱发送给声卡,由声卡模拟发音。相对于留声机原理的音乐文件(MP3、OGG、AMR、WAV、FLAC、AAC等),这种记录曲谱的文件更小,更容易创作。缺点是因为声音是软件模拟发出的,音质不能得到保证,而且不同的声卡有不同的发音效果,因此MIDI文件的播放效果不能得到保证。不过为此,微软提供了软波表合成器,使用微软的软波表可以实现MIDI文件效果的统一。

著名的游戏《恶魔之星III》(俗称“雷电3”)的背景音乐就是用MIDI音乐播放的效果。微软DirectX的组件DirectMusic就能用于播放MIDI音乐。

MIDI文件大体上分两个部分:文件头和音轨。
文件头由14个字节组成。开头是文件标识“MThd”,之后是一个Big-Endian存储的DWORD,值通常为6。这个值的意义是“文件头的信息所占字节数”,也就是文件头接下来6个字节的字节数(就是6了嘛)。之后的6个字节,其实是3个Big-Endian存储的WORD,分别是类型字,音轨数,每四分音符的Tick数。类型字有三个值:0表示这个文件只有一个音轨,1表示这个文件有多个音轨,播放的时候要同步播放所有音轨,2表示这个文件虽然有多个音轨,但是播放的时候必须一个音轨一个音轨的播放。
用一个结构体来描述MIDI文件头的结构:
  1. typedef struct
  2. {
  3.     DWORD   dwFlag;             //MThd标识
  4.     DWORD   dwRestLen;          //剩余部分长度
  5.     WORD    wType;              //类型
  6.     WORD    wNbTracks;          //音轨数
  7.     WORD    wTicksPerCrotchet;  //每四分音符的Tick数
  8. }MIDIHeader;
复制代码
以上就是文件头了。然后就是文件的重要部分:文件内容了。
文件内容由多个音轨组成,每个音轨的开头都是这样的结构:4字节的音轨标识"MTrk",4字节以Big-Endian存储的音轨字节数。如下所示:
  1. typedef struct
  2. {
  3.     DWORD   dwFlag;     //为0x6B72544D,即"MTrk"
  4.     DWORD   dwTrackLen; //音轨长度(除去音轨头部以外的字节数)(Big-Endian)
  5. }MIDITrack;
复制代码
其中dwTrackLen的值,是整个音轨的字节数减去音轨头部的8个字节。
每个音轨,除去音轨头部以外,剩下的就是主要的文件内容了。在介绍文件内容以前,我觉得有必要科普一下MIDI文件所用的“动态字节”是怎么回事。
首先这个动态字节存在的意义是为了减少一个数字的存储空间。1个字节是8位,我们拿出其中的低7位存储数字,那么我们能存储的范围是0-127,如果我们要存储的数字在这个范围内的话,我们就把最高位设置为0。而如果我们要存储的数字超出了0-127这个范围,那么我们就把最高位设置为1,然后记录下高7位,剩下的留给下一个字节。假设我们要存储111这个数字,因为它的值在0-127范围内,我们可以只用一个字节存储:01101111b。而假设我要存储333这个数字,把它转换成二进制的时候是101001101b,超出了7位能存储的范围,那么我们先提取出它的高7位和低7位:0000010和1001101.然后我们用这样的两个字节存储:10000010b和01001101b.当我们读取动态字节的时候,我们先读取一个字节,记录它的低7位,然后判断它的最高位来判断是否需要继续读取下一个字节。
以下VB代码用于展示读取动态字节的方法。调用的时候,以文件号1打开文件,然后用Seek #1定位文件指针,最后调用它来读取动态字节。
  1. '读取动态字节
  2. Function ReadDynamicBytes(ValueOut As Long) As Long '返回读取的字节数
  3. Dim OneByte As Byte
  4. ValueOut = 0
  5. For ReadDynamicBytes = 1 To 4 '最多读取4个字节
  6.     Get #1, , OneByte '读取一个字节
  7.     ValueOut = (ValueOut * &H80&) Or (OneByte And &H7F) '记录这个字节的低7位,同时左移以读取的数据让出位置。
  8.     If (OneByte And &H80) = 0 Then Exit For '这个字节的最高位是0,没有后续字节,停止读取。
  9. Next
  10. End Function
复制代码
理论上动态字节可用于存储大数,不过特别大的数在MIDI文件里用不着。我们顶多读取4字节(28位整数)

讲完了动态字节,接下来就应该讲文件内容了。文件的内容都是“事件”,这些“事件”是一个接一个存储的。一个事件都有固定的结构:延迟,事件号,事件参数。
其中“延迟”是动态字节,用于表示上一个事件到这一个事件之间的延迟量。这个延迟量的单位是Tick。一个Tick有多长时间取决于MIDI文件的曲速。
事件号的值在0x80到0xFF之间的时候表示的是具体的值,若读取到的这个值在0到0x7F之间,则表示这个事件的事件号和上一个事件相同,而读取到的值是它的参数。
有关事件号的资料在网络上查找也大同小异。但是都讲得不够清楚。我专门写了一个C语言程序用于解释事件号。看源码便知。
  1. //=============================================================================
  2. //作者:0xAA55
  3. //论坛:http://www.0xaa55.com/
  4. //版权所有(C) 2013-2014 技术宅的结界
  5. //请保留原作者信息,否则视为侵权。
  6. //-----------------------------------------------------------------------------
  7. #include<stdio.h>
  8. #include<malloc.h>

  9. //统一类型长度
  10. typedef signed int      MIDIInt,*MIDIIntP;
  11. typedef signed char     MIDIInt8,*MIDIInt8P;
  12. typedef signed short    MIDIInt16,*MIDIInt16P;
  13. typedef signed long     MIDIInt32,*MIDIInt32P;
  14. typedef unsigned int    MIDIUInt,*MIDIUIntP;
  15. typedef unsigned char   MIDIUInt8,*MIDIUInt8P;
  16. typedef unsigned short  MIDIUInt16,*MIDIUInt16P;
  17. typedef unsigned long   MIDIUInt32,*MIDIUInt32P;

  18. typedef MIDIUInt8       BYTE;
  19. typedef MIDIUInt16      WORD;
  20. typedef MIDIUInt32      DWORD;

  21. //MIDI文件头的结构体
  22. typedef struct
  23. {
  24.     DWORD   dwFlag;             //MThd标识
  25.     DWORD   dwRestLen;          //剩余部分长度
  26.     WORD    wType;              //类型
  27.     WORD    wNbTracks;          //音轨数
  28.     WORD    wTicksPerCrotchet;  //每四分音符的Tick数
  29. }MIDIHeader,*MIDIHeaderP;

  30. //MIDI文件中出现过的标识
  31. #define MIDI_MThd   0x6468544D
  32. #define MIDI_MTrk   0x6B72544D

  33. //MIDI文件头的“类型”
  34. #define MIDIT_SingleTrack   0   /*单音轨*/
  35. #define MIDIT_MultiSync     1   /*同步多音轨*/
  36. #define MIDIT_MultiAsync    2   /*异步多音轨*/

  37. //各种长度的Big-Endian到Little-Endian的转换
  38. #define BSwapW(x)   ((((x) & 0xFF)<<8)|(((x) & 0xFF00)>>8))
  39. #define BSwap24(x)  ((((x) & 0xFF)<<16)|((x) & 0xFF00)|(((x) & 0xFF0000)>>16))
  40. #define BSwapD(x)   ((((x) & 0xFF)<<24)|(((x) & 0xFF00)<<8)|(((x) & 0xFF0000)>>8)|(((x) & 0xFF000000)>>24))

  41. //将音符字节转换成字符串的函数
  42. char*NoteToString(BYTE bNote);

  43. //读取字符串然后打印
  44. size_t ReadStringAndPrint(FILE*,size_t);

  45. //=============================================================================
  46. //ReadDynamicBytes:
  47. //读取动态字节,最多读取4字节。返回读取的字节数
  48. //-----------------------------------------------------------------------------
  49. size_t ReadDynamicBytes(FILE*fp,DWORD*pOut)
  50. {
  51.     size_t bBytesRead;//已读取的字节数
  52.     *pOut=0;
  53.     for(bBytesRead=1;bBytesRead<=4;bBytesRead++)//最多读取4字节
  54.     {
  55.         int iValue=fgetc(fp);
  56.         if(iValue==EOF)
  57.             return 0;
  58.         *pOut=(*pOut<<7)|(iValue & 0x7F);//新读入的是低位
  59.         if(!(iValue & 0x80))//如果没有后续字节
  60.             break;//就停止读取。
  61.     }
  62.     return bBytesRead;//返回读取的字节数
  63. }

  64. //=============================================================================
  65. //ParseMIDI:
  66. //分析MIDI文件。失败返回零,成功返回非零
  67. //-----------------------------------------------------------------------------
  68. int ParseMIDI(char*pszFileName)
  69. {
  70. #   define READERR {fprintf(stderr,"读取文件%s遇到错误\n",pszFileName);goto BadReturn;}
  71. #   define FMTERR {fprintf(stderr,"%s不是一个正常的MIDI文件\n",pszFileName);goto BadReturn;}
  72. #   define READ(x) if(fread(&(x),1,sizeof(x),fp)!=sizeof(x))READERR

  73.     FILE*fp;
  74.     MIDIHeader midiHeader;
  75.     size_t i;

  76.     //打开文件
  77.     fp=fopen(pszFileName,"rb");
  78.     if(!fp)
  79.         READERR;

  80.     //MIDI文件头就是一个MIDIHeader结构体。
  81.     //但是要注意其中的数值都是Big-Endian存储的
  82.     //需要进行转换

  83.     //读取MIDI文件头
  84.     READ(midiHeader);
  85.    
  86.     //检查文件格式
  87.     if(midiHeader.dwFlag!=MIDI_MThd)//标识必须是"MThd"
  88.         FMTERR;

  89.     //转换为Little-Endian
  90.     midiHeader.dwRestLen=           BSwapD(midiHeader.dwRestLen);
  91.     midiHeader.wType=               BSwapW(midiHeader.wType);
  92.     midiHeader.wNbTracks=           BSwapW(midiHeader.wNbTracks);
  93.     midiHeader.wTicksPerCrotchet=   BSwapW(midiHeader.wTicksPerCrotchet);

  94.     //分析文件头
  95.     switch(midiHeader.wType)
  96.     {
  97.     case MIDIT_SingleTrack:
  98.         fputs("类型:单音轨\n",stdout);
  99.         break;
  100.     case MIDIT_MultiSync:
  101.         fputs("类型:同步多音轨\n",stdout);
  102.         break;
  103.     case MIDIT_MultiAsync:
  104.         fputs("类型:异步多音轨\n",stdout);
  105.         break;
  106.     default:
  107.         fprintf(stdout,"类型:未知(0x%04X)\n",midiHeader.wType);
  108.         break;
  109.     }

  110.     //打印音轨数等信息
  111.     fprintf(stdout,
  112.         "音轨数:%u\n"
  113.         "每四分音符时钟数:%u\n",
  114.         midiHeader.wNbTracks,
  115.         midiHeader.wTicksPerCrotchet);

  116.     //正确跳转到MIDI音轨的位置,体现midiHeader.dwRestLen的作用……
  117.     fseek(fp,8+midiHeader.dwRestLen,SEEK_SET);

  118.     //准备读取各个音轨
  119.     for(i=0;i<midiHeader.wNbTracks;i++)
  120.     {
  121.         DWORD dwTrackFlag;
  122.         DWORD dwTrackLen;

  123.         //每个音轨的开头都是一个dwTrackFlag和一个dwTrackLen
  124.         //dwTrackFlag的值必须是MIDI_MTrk
  125.         //dwTrackLen标记了下一个音轨的位置

  126.         size_t TrackStartPos;

  127.         fputs("读取音轨\n",stdout);

  128.         READ(dwTrackFlag);  if(dwTrackFlag!=MIDI_MTrk)FMTERR;//检查文件格式
  129.         READ(dwTrackLen);   TrackStartPos=ftell(fp);//记录当前位置

  130.         //转换Big-Endian
  131.         dwTrackLen=BSwapD(dwTrackLen);

  132.         //显示每个音轨的字节数
  133.         fprintf(stdout,"音轨大小:%u 字节\n",dwTrackLen);

  134.         //音轨的重要内容是事件数据
  135.         for(;;)//循环读取事件
  136.         {
  137.             DWORD dwDelay;
  138.             BYTE bEvent;

  139.             //每个事件的构成都是:
  140.             //延时,事件号,参数
  141.             //其中的延时是动态字节,参数大小随事件号而定

  142.             //上一个事件号
  143.             BYTE bLastEvent;

  144.             //读取延时
  145.             if(!ReadDynamicBytes(fp,&dwDelay))
  146.                 READERR;

  147.             //读取事件号
  148.             READ(bEvent);

  149.             //每读取一个事件,打印这个事件的时间
  150.             fprintf(stdout,"时间:%u,",dwDelay);

  151.             //分析事件
  152. ProcEvent:  if(bEvent <= 0x7F)
  153.             {//0到0x7F:和上一个事件的事件号相同,而读取的这个字节就是上一个事件号的参数的第一个字节
  154.                 fseek(fp,-1,SEEK_CUR);//回退一个字节
  155.                 bEvent=bLastEvent;
  156.                 goto ProcEvent;
  157.             }
  158.             else if(bEvent <= 0x8F)
  159.             {//0x80到0x8F:松开音符
  160.                 BYTE bNote,bVel;

  161.                 READ(bNote);//音符
  162.                 READ(bVel); //力度

  163.                 fprintf(stdout,"(通道%u)松开音符:%s, 力度:%u\n",
  164.                     bEvent & 0xF,//低4位表示通道数
  165.                     NoteToString(bNote),//音高
  166.                     bVel);//力度
  167.             }
  168.             else if(bEvent <= 0x9F)
  169.             {//0x90到0x9F:按下音符
  170.                 BYTE bNote,bVel;

  171.                 READ(bNote);//音符
  172.                 READ(bVel); //力度

  173.                 fprintf(stdout,"(通道%u)按下音符:%s, 力度:%u\n",
  174.                     bEvent & 0xF,//低4位表示通道数
  175.                     NoteToString(bNote),//音高
  176.                     bVel);//力度
  177.             }
  178.             else if(bEvent <= 0xAF)
  179.             {//0xA0到0xAF:触后音符
  180.                 BYTE bNote,bVel;

  181.                 READ(bNote);//音符
  182.                 READ(bVel); //力度

  183.                 fprintf(stdout,"(通道%u)触后音符:%s, 力度:%u\n",
  184.                     bEvent & 0xF,//低4位表示通道数
  185.                     NoteToString(bNote),//音高
  186.                     bVel);//力度
  187.             }
  188.             else if(bEvent <= 0xBF)
  189.             {//0xB0到0xBF:控制器
  190.                 BYTE bReg,bVal;

  191.                 READ(bReg);//寄存器号
  192.                 READ(bVal);//寄存器值

  193.                 fprintf(stdout,"(通道%u)控制器:%u,值=%u\n",
  194.                     bEvent & 0xF,//低4位表示通道数
  195.                     bReg,//寄存器
  196.                     bVal);//值
  197.             }
  198.             else if(bEvent <= 0xCF)
  199.             {//0xC0到0xCF:改变乐器
  200.                 BYTE bInstrument;

  201.                 READ(bInstrument);//乐器号
  202.                
  203.                 fprintf(stdout,"(通道%u)改变乐器:%u\n",
  204.                     bEvent & 0xF,//低4位表示通道数
  205.                     bInstrument);//乐器号
  206.             }
  207.             else if(bEvent <= 0xDF)
  208.             {//0xD0到0xDF:触后通道
  209.                 BYTE bChannel;

  210.                 READ(bChannel);//通道号
  211.                
  212.                 fprintf(stdout,"(通道%u)触后通道:%u\n",
  213.                     bEvent & 0xF,//低4位表示通道数
  214.                     bChannel);//通道号
  215.             }
  216.             else if(bEvent <= 0xEF)
  217.             {//0xE0到0xEF:滑音
  218.                 WORD wPitch;//滑音参数是Little-Endian的WORD

  219.                 READ(wPitch);
  220.                
  221.                 fprintf(stdout,"(通道%u)滑音:%u\n",
  222.                     bEvent & 0xF,//低4位表示通道数
  223.                     wPitch);//滑音
  224.             }
  225.             else if(bEvent == 0xF0)
  226.             {//0xF0:系统码
  227.                 BYTE bSysCode=0;

  228.                 fputs("系统码:",stdout);
  229.                 for(;;)
  230.                 {
  231.                     READ(bSysCode);
  232.                     if(bSysCode!=0xF7)
  233.                         fprintf(stdout,"0x%02X,",bSysCode);
  234.                     else
  235.                     {
  236.                         fprintf(stdout,"0x%02X\n",bSysCode);
  237.                         break;//读取到0xF7结束
  238.                     }
  239.                 }
  240.             }
  241.             else if(bEvent == 0xFF)
  242.             {//元事件
  243.                 BYTE bType,bBytes;
  244.                 size_t CurrentPos;
  245.                
  246.                 READ(bType);//元数据类型
  247.                 READ(bBytes);//元数据字节数

  248.                 CurrentPos=ftell(fp);//记录元数据读取的位置

  249.                 fputs("元数据 - ",stdout);

  250.                 switch(bType)
  251.                 {
  252.                 case 0x00://设置轨道音序
  253.                     {
  254.                         WORD wTrackSeq;
  255.                         READ(wTrackSeq);
  256.                         wTrackSeq=BSwapW(wTrackSeq);
  257.                         fprintf(stdout,"设置轨道音序:0x%04X\n",wTrackSeq);
  258.                     }
  259.                     break;
  260.                 case 0x01://歌曲备注
  261.                     fputs("备注:",stdout);
  262.                     ReadStringAndPrint(fp,bBytes);
  263.                     fputc('\n',stdout);
  264.                     break;
  265.                 case 0x02://版权
  266.                     fputs("版权:",stdout);
  267.                     ReadStringAndPrint(fp,bBytes);
  268.                     fputc('\n',stdout);
  269.                     break;
  270.                 case 0x03://歌曲标题
  271.                     fputs("标题:",stdout);
  272.                     ReadStringAndPrint(fp,bBytes);
  273.                     fputc('\n',stdout);
  274.                     break;
  275.                 case 0x04://乐器名称
  276.                     fputs("乐器名:",stdout);
  277.                     ReadStringAndPrint(fp,bBytes);
  278.                     fputc('\n',stdout);
  279.                     break;
  280.                 case 0x05://歌词
  281.                     fputs("歌词:",stdout);
  282.                     ReadStringAndPrint(fp,bBytes);
  283.                     fputc('\n',stdout);
  284.                     break;
  285.                 case 0x06://标记
  286.                     fputs("标记:",stdout);
  287.                     ReadStringAndPrint(fp,bBytes);
  288.                     fputc('\n',stdout);
  289.                     break;
  290.                 case 0x07://开始点
  291.                     fputs("开始点:",stdout);
  292.                     ReadStringAndPrint(fp,bBytes);
  293.                     fputc('\n',stdout);
  294.                     break;
  295.                 case 0x21://音轨开始标识
  296.                     fputs("音轨开始标识.\n",stdout);
  297.                     break;
  298.                 case 0x2F://音轨结束标识
  299.                     fputs("音轨结束标识.\n",stdout);
  300.                     goto EndOfTrack;
  301.                 case 0x51://速度
  302.                     {
  303.                         BYTE bVelocity1,bVelocity2,bVelocity3;

  304.                         //速度是24位整数,一个四分音符的微秒数。
  305.                         READ(bVelocity1);
  306.                         READ(bVelocity2);
  307.                         READ(bVelocity3);

  308.                         //而且是Big-Endian
  309.                         fprintf(stdout,"速度: 每四分音符%u微秒.\n",
  310.                             bVelocity3|(bVelocity2<<8)|(bVelocity1<<16));
  311.                     }
  312.                     break;
  313.                 case 0x58://节拍
  314.                     {
  315.                         BYTE bBeatNumerator,bBeatDenominator,bMetronomeTimer,bNb32ndNotesInCrotchet;

  316.                         READ(bBeatNumerator);//分子
  317.                         READ(bBeatDenominator);//分母
  318.                         READ(bMetronomeTimer);//节拍器时钟
  319.                         READ(bNb32ndNotesInCrotchet);//每四分音符有多少三十二分音符

  320.                         fprintf(stdout,"节拍:%u/%u. 节拍器时钟:%u. 每四分音符有%u个三十二分音符.\n",
  321.                             bBeatNumerator,bBeatDenominator,bMetronomeTimer,bNb32ndNotesInCrotchet);
  322.                     }
  323.                     break;
  324.                 case 0x59://调号
  325.                     {
  326.                         MIDIInt8 bTunePitch;
  327.                         BYTE bTuneType;

  328.                         READ(bTunePitch);//升降号
  329.                         READ(bTuneType);//大调、小调

  330.                         fprintf(stdout,"升降号:%d, ",bTunePitch);
  331.                         switch(bTuneType)
  332.                         {
  333.                         case 0://大调
  334.                             fputs("大调\n",stdout);
  335.                             break;
  336.                         case 1://小调
  337.                             fputs("小调\n",stdout);
  338.                             break;
  339.                         default://不知道
  340.                             fprintf(stdout,"未知调(0x%02X).\n",bTuneType);
  341.                             break;
  342.                         }
  343.                     }
  344.                     break;
  345.                 case 0x7F://音序特定信息
  346.                     fputs("音序特定信息:",stdout);
  347.                     ReadStringAndPrint(fp,bBytes);
  348.                     fputc('\n',stdout);
  349.                     break;
  350.                 default:
  351.                     fprintf(stdout,"未知元数据类型(0x%02X)\n",bType);
  352.                     break;
  353.                 }

  354.                 fseek(fp,CurrentPos+bBytes,SEEK_SET);//读取完成后正确跳到下一个事件的位置。
  355.             }
  356.             else//其它事件,未知事件
  357.             {
  358.                 fprintf(stdout,"未知事件:0x%08X. 停止当前音轨的分析。\n",bEvent);
  359.                 break;
  360.             }

  361.             //记录上一个事件号
  362.             bLastEvent=bEvent;
  363.         }//回去继续读取事件。

  364. EndOfTrack:
  365.         fseek(fp,TrackStartPos+dwTrackLen,SEEK_SET);//转到下一个音轨
  366.     }

  367.     fputs("MIDI文件读取结束。\n",stdout);

  368.     fclose(fp);
  369.     return 1;
  370. BadReturn:
  371.     fclose(fp);
  372.     return 0;

  373. #   undef READ
  374. #   undef READERR
  375. }

  376. //=============================================================================
  377. //NoteToString:
  378. //将音符音高转换为字符串
  379. //其实每次返回的都是同一缓冲区,只是每次都改写了缓冲区的值。
  380. //-----------------------------------------------------------------------------
  381. char*NoteToString(BYTE bNote)
  382. {
  383.     static char szBuf[5];//顶多5字符
  384.     switch(bNote % 12)
  385.     {
  386.     case 0:
  387.         sprintf(szBuf,"C%d",(int)(bNote / 12)-2);
  388.         break;
  389.     case 1:
  390.         sprintf(szBuf,"C%d#",(int)(bNote / 12)-2);
  391.         break;
  392.     case 2:
  393.         sprintf(szBuf,"D%d",(int)(bNote / 12)-2);
  394.         break;
  395.     case 3:
  396.         sprintf(szBuf,"D%d#",(int)(bNote / 12)-2);
  397.         break;
  398.     case 4:
  399.         sprintf(szBuf,"E%d",(int)(bNote / 12)-2);
  400.         break;
  401.     case 5:
  402.         sprintf(szBuf,"F%d",(int)(bNote / 12)-2);
  403.         break;
  404.     case 6:
  405.         sprintf(szBuf,"F%d#",(int)(bNote / 12)-2);
  406.         break;
  407.     case 7:
  408.         sprintf(szBuf,"G%d",(int)(bNote / 12)-2);
  409.         break;
  410.     case 8:
  411.         sprintf(szBuf,"G%d#",(int)(bNote / 12)-2);
  412.         break;
  413.     case 9:
  414.         sprintf(szBuf,"A%d",(int)(bNote / 12)-2);
  415.         break;
  416.     case 10:
  417.         sprintf(szBuf,"A%d#",(int)(bNote / 12)-2);
  418.         break;
  419.     case 11:
  420.         sprintf(szBuf,"B%d",(int)(bNote / 12)-2);
  421.         break;
  422.     }
  423.     return szBuf;
  424. }

  425. //=============================================================================
  426. //ReadStringAndPrint
  427. //读取字符串然后打印。返回读取的字节数
  428. //-----------------------------------------------------------------------------
  429. size_t ReadStringAndPrint(FILE*fp,size_t szLength)
  430. {
  431.     char*pBuf;
  432.     pBuf=(char*)malloc(szLength+1);
  433.     if(pBuf)//分配内存一次读取
  434.     {
  435.         size_t Ret=fread(pBuf,1,szLength,fp);
  436.         pBuf[szLength]=0;//结尾的\0
  437.         fputs(pBuf,stdout);
  438.         free(pBuf);
  439.         return Ret;
  440.     }
  441.     else
  442.     {
  443.         fputs("无法将字符串读入内存。\n",stderr);
  444.         return 0;
  445.     }
  446. }

  447. //=============================================================================
  448. //main:
  449. //入口
  450. //-----------------------------------------------------------------------------
  451. int main(int argc,char**argv)
  452. {
  453.     if(argc<2)
  454.     {
  455.         fprintf(stderr,
  456.             "Usage:\n"
  457.             "%s midifile.mid\n",argv[0]?argv[0]:"MIDIFile");
  458.         return 1;
  459.     }

  460.     ParseMIDI(argv[1]);
  461.     return 0;
  462. }
复制代码
BIN: MIDIFile.exe (48 KB, 下载次数: 65, 售价: 1 个宅币)
SRC: MIDIFile.7z (21.83 KB, 下载次数: 41, 售价: 10 个宅币)
回复

使用道具 举报

发表于 2017-2-21 18:53:56 | 显示全部楼层
好东西,好好学习
回复 赞! 靠!

使用道具 举报

发表于 2017-2-22 10:51:36 | 显示全部楼层
这个资料在十几年前应该很有用。。。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2017-2-22 12:55:42 | 显示全部楼层
tx7790 发表于 2017-2-22 10:51
这个资料在十几年前应该很有用。。。

现在依然有用,尤其是嵌入式开发,这个技术根本就没过时
回复 赞! 靠!

使用道具 举报

发表于 2017-7-6 18:15:27 | 显示全部楼层
谢谢楼主分享!!
回复 赞! 靠!

使用道具 举报

发表于 2017-8-21 00:24:55 | 显示全部楼层
学习了…感谢楼主!
回复 赞! 靠!

使用道具 举报

发表于 2017-10-25 01:52:21 | 显示全部楼层
谢谢楼主分享
回复 赞! 靠!

使用道具 举报

发表于 2017-10-25 02:27:32 | 显示全部楼层
正好在写这方面的程序
回复 赞! 靠!

使用道具 举报

发表于 2017-11-5 19:13:50 | 显示全部楼层
挺好的,这个文件挺有用的
回复 赞! 靠!

使用道具 举报

发表于 2017-11-7 07:50:28 | 显示全部楼层
感谢源代码。收藏
回复 赞! 靠!

使用道具 举报

发表于 2017-12-1 15:46:38 | 显示全部楼层
在做音乐方面的,希望可以用到
回复 赞! 靠!

使用道具 举报

发表于 2017-12-4 10:42:41 | 显示全部楼层
mac上跑的有问题,
回复 赞! 靠!

使用道具 举报

发表于 2018-1-3 14:59:16 | 显示全部楼层

挺好的,这个文件挺有用的
回复 赞! 靠!

使用道具 举报

发表于 2018-2-25 22:19:45 | 显示全部楼层
谢谢楼主分享
回复 赞! 靠!

使用道具 举报

发表于 2018-4-28 23:12:04 | 显示全部楼层
跑这个程序应该出什么结果?文件名应该在什么地方修改?
回复 赞! 靠!

使用道具 举报

发表于 2019-9-9 21:53:46 | 显示全部楼层
你这个程序有问题,元事件长度用ReadDynamicBytes函数读取的
回复 赞! 靠!

使用道具 举报

发表于 2021-3-11 03:21:46 | 显示全部楼层
谢谢大佬的教程 收藏起来观摩一波
回复 赞! 靠!

使用道具 举报

发表于 2022-11-7 15:01:25 | 显示全部楼层
搞嵌入式开发入门,对这个完全不懂
回复 赞! 靠!

使用道具 举报

发表于 2022-11-23 23:08:56 | 显示全部楼层
学习一下,给小孩编个小程序
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-22 10:50 , Processed in 0.043583 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表