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

QQ登录

只需一步,快速开始

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

【C语言】Midi文件播放器(可跨平台)

  [复制链接]

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24221 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
发表于 2015-8-24 00:49:50 | 显示全部楼层 |阅读模式

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

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

×
来源:http://www.0xaa55.com/thread-1489-1-1.html
转载请保留出处。

因为读写文件、发送Midi指令的函数都需要用户来亲自完成(实现接口)因此,可以实现跨平台。
不过目前我只写了个Windows平台的播放实例。DOS的以后会写出能在DOSBox播放Midi音乐的。
另外我这个的播放效果比mciSendCommand(或mciSendString)播放Midi文件的效果好很多。mciSendXXXX这玩意儿播放Midi存在跳音符的问题(比如某些鼓点没打上、节奏出现问题等),而我的这个Midi解析库则解决了这个问题。

midifile.h:
  1. //=============================================================================
  2. //作者:0xAA55
  3. //论坛:http://www.0xaa55.com/
  4. //版权所有(C) 2013-2015 技术宅的结界
  5. //请保留原作者信息,否则视为侵权。
  6. //-----------------------------------------------------------------------------
  7. #ifndef _MIDI_Parser_
  8. #define        _MIDI_Parser_

  9. #include<stdint.h>

  10. //=============================================================================
  11. //宏,可以用于调整调用约定、符号导入导出规则等
  12. //-----------------------------------------------------------------------------
  13. //MIDIFILE的函数调用约定
  14. #ifndef Midi_c
  15. #define Midi_c _cdecl
  16. #endif // !Midi_c

  17. //MIDIFILE的回调函数调用约定
  18. #ifndef Midi_cb
  19. #define Midi_cb _cdecl
  20. #endif // !Midi_cb

  21. //MIDIFILE的符号导出规则
  22. #ifndef Midi_x
  23. #ifdef __cplusplus
  24. #define Midi_x extern"C"
  25. #else // !__cplusplus
  26. #define Midi_x extern
  27. #endif // !__cplusplus
  28. #endif // !Midi_x

  29. //MIDIFILE的导出函数
  30. #define Midi_fn(fn,rt) Midi_x rt Midi_c fn

  31. //用户自定义数据的格式
  32. #ifndef Midi_UserData_t
  33. #define Midi_UserData_t void
  34. #endif

  35. #ifndef Midi_UserData_p
  36. #define Midi_UserData_p Midi_UserData_t*
  37. #endif

  38. //为了防止出现fpos_t未定义的问题,这里定义一次。
  39. typedef __int64 fpos_t;

  40. //Midi时间刻度
  41. #ifndef MidiTime_t
  42. #define MidiTime_t double
  43. #endif

  44. #ifndef MidiTime_p
  45. #define MidiTime_p MidiTime_t*
  46. #endif
  47. //-----------------------------------------------------------------------------




  48. //=============================================================================
  49. //错误代码
  50. //-----------------------------------------------------------------------------
  51. typedef enum
  52. {
  53.         ME_OK = 0,                //无错误
  54.         ME_OutOfMem,        //内存不足
  55.         ME_TellFail,        //取得文件指针失败
  56.         ME_SeekFail,        //设置文件指针失败
  57.         ME_ReadFail,        //读取失败
  58.         ME_BadFile                //文件格式错误
  59. }MidiErrCode_t, * MidiErrCode_p;




  60. //=============================================================================
  61. //用户回调函数,用户必须提供实现的函数
  62. //-----------------------------------------------------------------------------
  63. //读取文件:要求成功返回非零,失败返回0
  64. typedef int(Midi_cb* ReadFile_f)
  65. (
  66.         void* pBuf,                //缓冲区
  67.         size_t        cb,                        //要读取的字节数
  68.         Midi_UserData_p pUserData        //用户数据
  69.         );
  70. //-----------------------------------------------------------------------------
  71. //取得文件指针:要求成功返回非零,失败返回0
  72. typedef int(Midi_cb* GetFilePtr_f)
  73. (
  74.         fpos_t* pPosition,        //取得位置
  75.         Midi_UserData_p pUserData        //用户数据
  76.         );
  77. //-----------------------------------------------------------------------------
  78. //设置文件指针:要求成功返回非零,失败返回0
  79. typedef int(Midi_cb* SetFilePtr_f)
  80. (
  81.         fpos_t        Position,        //新的位置
  82.         Midi_UserData_p pUserData        //用户数据
  83.         );
  84. //-----------------------------------------------------------------------------
  85. //发送MIDI命令
  86. typedef void(Midi_cb* SendMidiMsg_f)
  87. (
  88.         uint32_t TickCount,        //当前命令时钟
  89.         uint16_t TrackNo,        //当前音轨号
  90.         uint8_t        Command,        //命令
  91.         uint8_t        Param1,                //参数1
  92.         uint8_t        Param2,                //参数2
  93.         Midi_UserData_p pUserData        //用户数据
  94.         );
  95. //-----------------------------------------------------------------------------
  96. //错误处理
  97. typedef void(Midi_cb* Error_f)(MidiErrCode_t);
  98. //-----------------------------------------------------------------------------


  99. //=============================================================================
  100. //MIDI解析器的音轨数据
  101. //-----------------------------------------------------------------------------
  102. typedef struct
  103. {
  104.         fpos_t        StartPos;        //音轨开始的文件偏移
  105.         fpos_t        TrackSize;        //音轨大小
  106.         fpos_t        NextNotePos;//音轨下个音符的文件偏移
  107.         uint32_t LastNoteTick;//上个音符的时钟
  108.         uint32_t TotalTicks;//这个音轨的总时钟数
  109.         uint8_t EndOfTrack;        //是否到达了当前轨道的末尾
  110.         uint8_t        LastCommand;//上个命令
  111.         uint8_t        LastParam1;        //上个命令的参数1
  112.         uint8_t        LastParam2;        //上个命令的参数2
  113. }TrackData_t, * TrackData_p;


  114. //=============================================================================
  115. //MIDI解析器结构体
  116. //-----------------------------------------------------------------------------
  117. typedef struct
  118. {
  119.         //=========================================================================
  120.         //用户需要先填写如下的参数,才能使用MidiFile接口
  121.         ReadFile_f                pfnReadFile;                //读取文件回调
  122.         GetFilePtr_f        pfnGetFilePtr;                //设置文件指针回调
  123.         SetFilePtr_f        pfnSetFilePtr;                //设置文件指针回调
  124.         SendMidiMsg_f        pfnSendMidiMsg;                //发送Midi指令回调
  125.         Error_f                        pfnOnError;                        //错误处理回调
  126.         Midi_UserData_p        pUserData;                        //用户自定义数据
  127.         //-------------------------------------------------------------------------

  128.         uint16_t                wType;              //Midi文件类型
  129.         uint16_t                wNbTracks;          //音轨数
  130.         uint16_t                wTicksPerCrotchet;  //每四分音符的Tick数
  131.         TrackData_p                pTrackData;                        //音轨数据
  132.         uint32_t                Velocity;                        //播放速度,一个四分音符的微秒数
  133.         uint32_t                TotalTicks;                        //整个Midi文件的时钟数(长度)
  134.         uint8_t                        EndOfFile;                        //是否已经更新到了结尾

  135.         MidiErrCode_t        LastErr;                        //错误代码
  136. }MidiParser_t, * MidiParser_p;

  137. //MIDI文件的类型
  138. #define MIDIT_SingleTrack   0   /*单音轨*/
  139. #define MIDIT_MultiSync     1   /*同步多音轨*/
  140. #define MIDIT_MultiAsync    2   /*异步多音轨*/

  141. //=============================================================================
  142. //MidiParserStart:
  143. //分析MIDI文件,为播放做准备。失败返回零,成功返回非零
  144. //-----------------------------------------------------------------------------
  145. Midi_fn(MidiParserStart, int)
  146. (
  147.         MidiParser_p pParser
  148.         );

  149. //=============================================================================
  150. //MidiParserUpdate:
  151. //更新播放状态到当前时间
  152. //-----------------------------------------------------------------------------
  153. Midi_fn(MidiParserUpdate, int)
  154. (
  155.         MidiParser_p        pParser,
  156.         MidiTime_t                dbMilliSeconds,
  157.         uint8_t                        uSilent
  158.         );

  159. //=============================================================================
  160. //MidiParserReset:
  161. //重置Midi解析器状态
  162. //-----------------------------------------------------------------------------
  163. Midi_fn(MidiParserReset, void)
  164. (
  165.         MidiParser_p pParser
  166.         );

  167. //=============================================================================
  168. //MidiParserParseToEnd:
  169. //分析整个文件
  170. //-----------------------------------------------------------------------------
  171. Midi_fn(MidiParserParseToEnd, int)
  172. (
  173.         MidiParser_p pParser
  174.         );

  175. //=============================================================================
  176. //MidiParserShutdown:
  177. //关闭Midi解析器
  178. //-----------------------------------------------------------------------------
  179. Midi_fn(MidiParserShutdown, void)
  180. (
  181.         MidiParser_p pParser
  182.         );

  183. /******************************************************************************
  184. 注释

  185. 1、播放
  186. ·用户需要自己编写读取文件、定位文件指针的函数、发送Midi字节的函数(按照本头文
  187. 件所描述)的规格进行编写。然后按照说明填写MidiParser_t结构体的成员。
  188. ·填写完成后,调用MidiParserStart,初始化播放器。
  189. ·随后,不断调用MidiParserUpdate(并提供播放时间)进行Midi文件的播放。播放时间
  190. 的数值只能向后增长。
  191. ·播放完最后一个音符后,MidiParser_t的成员EndOfFile将会被置1。如需重新播放,请
  192. 调用MidiParserReset,然后重新计时并重复上一个步骤。
  193. ·不需要播放的时候,请调用MidiParserShutdown完成最后的处理。
  194. ·请注意,如果你在Windows使用mciSendCommand播放Midi音乐,你会遇到跳音符的问题。
  195. 而本库所使用的算法比Windows的mciSendCommand好,因此没有这个问题。请使用本库。

  196. 2、定位到指定播放位置(慎用:可能导致播放乐曲的效果偏离作曲者的设定)
  197. ·调用MidiParserReset重置播放状态。
  198. ·随后,调用MidiParserUpdate并提供你要定位的时间值(如果设置Silent参数的话,
  199. MidiParserUpdate将不会发送Midi字节)。

  200. 3、本程序也可用于Midi文件的解析器——获取每个Midi指令、时间、轨道等信息。

  201. 注:本程序对于格式有问题的Midi文件并不能做到很好的解析——要求:
  202. ·每个Midi音轨的最后一个音符必须是元数据——音轨结束标识

  203. ******************************************************************************/


  204. #endif // !_MIDI_Parser_
复制代码
midifile.c:
  1. //=============================================================================
  2. //作者:0xAA55
  3. //论坛:http://www.0xaa55.com/
  4. //版权所有(C) 2013-2015 技术宅的结界
  5. //请保留原作者信息,否则视为侵权。
  6. //-----------------------------------------------------------------------------
  7. #include"midifile.h"

  8. #include<stdio.h>
  9. #include<malloc.h>
  10. #include<memory.h>

  11. #pragma pack(push,1)
  12. //MIDI文件头的结构体
  13. typedef struct
  14. {
  15.         uint32_t        dwFlag;             //MThd标识
  16.         uint32_t        dwRestLen;          //剩余部分长度
  17.         uint16_t        wType;              //类型
  18.         uint16_t        wNbTracks;          //音轨数
  19.         uint16_t        wTicksPerCrotchet;  //每四分音符的Tick数
  20. }MIDIHeader, * MIDIHeaderP;
  21. #pragma pack(pop,1)

  22. //MIDI文件中出现过的标识
  23. #define MIDI_MThd   0x6468544D
  24. #define MIDI_MTrk   0x6B72544D

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

  29. //用于读取文件的宏。要使用它,函数必须要有MidiParser_p pParser参数
  30. #define ErrC(e) {pParser->LastErr=(e);pParser->pfnOnError((e));goto BadRet;}
  31. #define Tell(x) if(!pParser->pfnGetFilePtr((x),pParser->pUserData))\
  32.         ErrC(ME_TellFail)
  33. #define Seek(x) if(!pParser->pfnSetFilePtr((x),pParser->pUserData))\
  34.         ErrC(ME_SeekFail)
  35. #define Read(x) if(!pParser->pfnReadFile(&(x),sizeof((x)),pParser->pUserData))\
  36.         ErrC(ME_ReadFail)
  37. #define Skip(x) {fpos_t __CurPos;Tell(&__CurPos);__CurPos+=(x);Seek(__CurPos);}

  38. //=============================================================================
  39. //ReadDynamicByte:
  40. //读取动态字节,最多读取4字节。返回读取的字节数
  41. //-----------------------------------------------------------------------------
  42. static size_t ReadDynamicByte(MidiParser_p pParser, uint32_t* pOut)
  43. {
  44.         size_t BytesRead;//已读取的字节数
  45.         uint32_t uVal = 0;
  46.         for (BytesRead = 1; BytesRead <= 4; BytesRead++)//最多读取4字节
  47.         {
  48.                 uint8_t Value;
  49.                 Read(Value);//读取一个字节
  50.                 uVal = (uVal << 7) | (Value & 0x7F);//新读入的是低位
  51.                 if (!(Value & 0x80))//如果没有后续字节
  52.                         break;//就停止读取。
  53.         }
  54.         *pOut = uVal;
  55.         return BytesRead;//返回读取的字节数
  56. BadRet:
  57.         *pOut = uVal;
  58.         return 0;
  59. }

  60. //=============================================================================
  61. //GetCommandNbParams:
  62. //取得MIDI命令的参数个数
  63. //-----------------------------------------------------------------------------
  64. static uint8_t GetCommandNbParams(uint8_t bEvent)
  65. {
  66.         if (bEvent <= 0x7F)return 0;//上个命令的参数
  67.         else if (bEvent <= 0x8F)return 2;//两个参数
  68.         else if (bEvent <= 0x9F)return 2;//两个参数
  69.         else if (bEvent <= 0xAF)return 2;//两个参数
  70.         else if (bEvent <= 0xBF)return 2;//两个参数
  71.         else if (bEvent <= 0xCF)return 1;//一个参数
  72.         else if (bEvent <= 0xDF)return 1;//一个参数
  73.         else if (bEvent <= 0xEF)return 2;//两个参数
  74.         else if (bEvent <= 0xFF)return 0;//机器码
  75.         else return 2;//元数据
  76. }

  77. //=============================================================================
  78. //DefOnError:
  79. //默认的错误提示函数
  80. //-----------------------------------------------------------------------------
  81. static void Midi_cb DefOnError(MidiErrCode_t Err)
  82. {
  83.         //什么也不做
  84. }

  85. //=============================================================================
  86. //MidiParserStart:
  87. //分析MIDI文件,为播放做准备。失败返回零,成功返回非零
  88. //-----------------------------------------------------------------------------
  89. Midi_fn(MidiParserStart, int)
  90. (
  91.         MidiParser_p pParser
  92.         )
  93. {
  94.         MIDIHeader midiHeader;
  95.         size_t i;

  96.         pParser->wType = 0;
  97.         pParser->wNbTracks = 0;
  98.         pParser->wTicksPerCrotchet = 0;
  99.         pParser->pTrackData = NULL;
  100.         pParser->Velocity = 0;
  101.         pParser->TotalTicks = 0;
  102.         pParser->EndOfFile = 0;
  103.         pParser->LastErr = ME_OK;

  104.         if (!pParser->pfnOnError)
  105.                 pParser->pfnOnError = DefOnError;

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

  109.         //读取MIDI文件头
  110.         Read(midiHeader);

  111.         //检查文件格式
  112.         if (midiHeader.dwFlag != MIDI_MThd)//标识必须是"MThd"
  113.                 ErrC(ME_BadFile);

  114.         //转换为Little-Endian
  115.         midiHeader.dwRestLen = BSwapD(midiHeader.dwRestLen);
  116.         midiHeader.wType = BSwapW(midiHeader.wType);
  117.         midiHeader.wNbTracks = BSwapW(midiHeader.wNbTracks);
  118.         midiHeader.wTicksPerCrotchet = BSwapW(midiHeader.wTicksPerCrotchet);

  119.         //分析器数据
  120.         pParser->wType = midiHeader.wType;
  121.         pParser->wNbTracks = midiHeader.wNbTracks;
  122.         pParser->wTicksPerCrotchet = midiHeader.wTicksPerCrotchet;

  123.         //跳转到MIDI音轨的位置
  124.         Seek(8 + midiHeader.dwRestLen);

  125.         //轨道信息
  126.         i = sizeof(TrackData_t) * pParser->wNbTracks;//轨道信息总大小
  127.         pParser->pTrackData = (TrackData_p)malloc(i);//分配内存
  128.         if (!pParser->pTrackData)
  129.                 ErrC(ME_OutOfMem);
  130.         memset(pParser->pTrackData, 0, i);//清零

  131.         //准备读取各个音轨
  132.         for (i = 0; i < midiHeader.wNbTracks; i++)
  133.         {
  134.                 uint32_t dwTrackFlag;
  135.                 uint32_t dwTrackLen;

  136.                 uint8_t LastEvent = 0;//上一个事件
  137.                 uint8_t EndOfTrack;//是否读取到了音轨的结束位置

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

  141.                 fpos_t TrackStartPos;

  142.                 //轨道标记
  143.                 Read(dwTrackFlag);
  144.                 if (dwTrackFlag != MIDI_MTrk)
  145.                         ErrC(ME_BadFile);

  146.                 //轨道长度
  147.                 Read(dwTrackLen);
  148.                 dwTrackLen = BSwapD(dwTrackLen);//转换Big-Endian

  149.                 Tell(&TrackStartPos);//记录当前位置

  150.                 pParser->pTrackData[i].StartPos = TrackStartPos;
  151.                 pParser->pTrackData[i].TrackSize = dwTrackLen;
  152.                 pParser->pTrackData[i].NextNotePos = TrackStartPos;
  153.                 pParser->pTrackData[i].LastNoteTick = 0;
  154.                 pParser->pTrackData[i].EndOfTrack = 0;

  155.                 //音轨的重要内容是事件数据
  156.                 for (EndOfTrack = 0; !EndOfTrack;)//循环读取事件
  157.                 {
  158.                         uint32_t dwDelay;
  159.                         uint8_t bEvent;

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

  163.                         //读取延时
  164.                         if (!ReadDynamicByte(pParser, &dwDelay))
  165.                                 ErrC(ME_BadFile);

  166.                         //统计当前音轨总时钟数
  167.                         pParser->pTrackData[i].TotalTicks += dwDelay;

  168.                         //读取事件号
  169.                         Read(bEvent);

  170.                         //分析事件
  171.                         if (bEvent <= 0x7F)
  172.                         {//0到0x7F:和上一个事件的事件号相同,而读取的这个字节就是上一个事件
  173.                         //号的参数的第一个字节
  174.                                 Skip(GetCommandNbParams(LastEvent) - 1);
  175.                                 bEvent = LastEvent;
  176.                         }
  177.                         else if (bEvent <= 0xEF)
  178.                         {//基本的MIDI命令
  179.                                 Skip(GetCommandNbParams(bEvent));
  180.                         }
  181.                         else if (bEvent == 0xF0)
  182.                         {//0xF0:系统码
  183.                                 uint8_t bSysCode = 0;
  184.                                 do
  185.                                 {
  186.                                         Read(bSysCode);
  187.                                 } while (bSysCode != 0xF7);
  188.                         }
  189.                         else if (bEvent == 0xFF)
  190.                         {//元事件
  191.                                 uint8_t bType, Bytes;
  192.                                 fpos_t CurrentPos;

  193.                                 Read(bType);//元数据类型
  194.                                 Read(Bytes);//元数据字节数

  195.                                 Tell(&CurrentPos);//记录元数据读取的位置

  196.                                 switch (bType)
  197.                                 {
  198.                                 case 0x2F://音轨结束标识
  199.                                         EndOfTrack = 1;
  200.                                         break;
  201.                                 case 0x51://速度
  202.                                 {
  203.                                         uint8_t bVelocity1, bVelocity2, bVelocity3;

  204.                                         //速度是24位整数,一个四分音符的微秒数。
  205.                                         Read(bVelocity1);
  206.                                         Read(bVelocity2);
  207.                                         Read(bVelocity3);

  208.                                         if (!pParser->Velocity)
  209.                                                 pParser->Velocity =
  210.                                                 ((uint32_t)bVelocity3 |
  211.                                                         ((uint32_t)bVelocity2 << 8) |
  212.                                                         ((uint32_t)bVelocity1 << 16));
  213.                                 }
  214.                                 break;
  215.                                 default:
  216.                                         break;
  217.                                 }

  218.                                 Seek(CurrentPos + Bytes);//读取完成后正确跳到下一个事件的位置。
  219.                         }
  220.                         else//其它事件,未知事件
  221.                         {
  222.                                 ErrC(ME_BadFile);
  223.                         }
  224.                         LastEvent = bEvent;
  225.                 }//回去继续读取事件。

  226.                 //如果是异步多音轨的话,记录整个Midi文件的播放时长
  227.                 if (pParser->wType == MIDIT_MultiAsync)
  228.                 {
  229.                         //异步多音轨,同级总时长
  230.                         pParser->TotalTicks += pParser->pTrackData[i].TotalTicks;
  231.                 }
  232.                 else//否则(单音轨、同步多音轨)以时长最长的音轨为整个音乐的长度。
  233.                 {
  234.                         //同步多音轨或单音轨,找到时长最长的音轨。
  235.                         if (pParser->pTrackData[i].TotalTicks > pParser->TotalTicks)
  236.                                 pParser->TotalTicks = pParser->pTrackData[i].TotalTicks;
  237.                 }
  238.                 Seek(TrackStartPos + dwTrackLen);//转到下一个音轨
  239.         }

  240.         return 1;
  241. BadRet:
  242.         MidiParserShutdown(pParser);
  243.         return 0;
  244. }

  245. //=============================================================================
  246. //MidiParserUpdate:
  247. //更新播放状态到当前时间
  248. //-----------------------------------------------------------------------------
  249. Midi_fn(MidiParserUpdate, int)
  250. (
  251.         MidiParser_p        pParser,
  252.         MidiTime_t                dbMilliSeconds,
  253.         uint8_t                        uSilent
  254.         )
  255. {
  256.         uint16_t CurTrack;
  257.         uint32_t CurTick;

  258.         if (dbMilliSeconds < 0)
  259.                 dbMilliSeconds = 0;

  260.         //当前时钟数
  261.         CurTick = (uint32_t)(dbMilliSeconds * 1000.0 * pParser->wTicksPerCrotchet / pParser->Velocity);

  262.         //检查是否播放到了结尾
  263.         if (CurTick >= pParser->TotalTicks)
  264.         {
  265.                 pParser->EndOfFile = 1;
  266.         }

  267.         for (CurTrack = 0; CurTrack < pParser->wNbTracks; CurTrack++)
  268.         {
  269.                 TrackData_p pData = &(pParser->pTrackData[CurTrack]);

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

  273.                 //异步多音轨单独处理
  274.                 if (pParser->wType == MIDIT_MultiAsync)
  275.                 {
  276.                         if (CurTick >= pData->TotalTicks)
  277.                         {
  278.                                 CurTick -= pData->TotalTicks;
  279.                                 continue;
  280.                         }
  281.                 }
  282.                 for (; !pData->EndOfTrack; )
  283.                 {
  284.                         uint32_t dwDelay;
  285.                         uint8_t bEvent;

  286.                         //定位到当前轨道的下一个音符的位置
  287.                         Seek(pData->NextNotePos);

  288.                         //读取延时
  289.                         if (!ReadDynamicByte(pParser, &dwDelay))
  290.                                 ErrC(ME_BadFile);

  291.                         if (CurTick >= pData->LastNoteTick + dwDelay)
  292.                         {
  293.                                 pData->LastNoteTick += dwDelay;
  294.                                 //读取事件号
  295.                                 Read(bEvent);

  296.                                 //分析事件
  297.                                 if (bEvent <= 0x7F)
  298.                                 {        //0到0x7F:和上一个事件的事件号相同,而读取的这个字节就是上一
  299.                                         //个事件号的参数的第一个字节
  300.                                         uint8_t NbParams = GetCommandNbParams(pData->LastCommand);
  301.                                         if (NbParams == 1)
  302.                                         {
  303.                                                 pData->LastParam1 = bEvent;
  304.                                                 if (!uSilent)
  305.                                                 {
  306.                                                         pParser->pfnSendMidiMsg(pData->LastNoteTick,
  307.                                                                 CurTrack, pData->LastCommand, bEvent, 0,
  308.                                                                 pParser->pUserData);
  309.                                                 }
  310.                                         }
  311.                                         else if (NbParams == 2)
  312.                                         {
  313.                                                 uint8_t bParam2;
  314.                                                 Read(bParam2);
  315.                                                 pData->LastParam1 = bEvent;
  316.                                                 pData->LastParam2 = bParam2;
  317.                                                 if (!uSilent)
  318.                                                 {
  319.                                                         pParser->pfnSendMidiMsg(pData->LastNoteTick,
  320.                                                                 CurTrack, pData->LastCommand, bEvent, bParam2,
  321.                                                                 pParser->pUserData);
  322.                                                 }
  323.                                         }
  324.                                 }
  325.                                 else if (bEvent <= 0xEF)
  326.                                 {
  327.                                         uint8_t NbParams = GetCommandNbParams(bEvent);
  328.                                         pData->LastCommand = bEvent;
  329.                                         if (NbParams == 1)
  330.                                         {
  331.                                                 uint8_t bParam1;
  332.                                                 Read(bParam1);
  333.                                                 pData->LastParam1 = bParam1;
  334.                                                 if (!uSilent)
  335.                                                 {
  336.                                                         pParser->pfnSendMidiMsg(pData->LastNoteTick,
  337.                                                                 CurTrack, bEvent, bParam1, 0, pParser->pUserData);
  338.                                                 }
  339.                                         }
  340.                                         else if (NbParams == 2)
  341.                                         {
  342.                                                 uint8_t bParam1, bParam2;
  343.                                                 Read(bParam1);
  344.                                                 Read(bParam2);
  345.                                                 pData->LastParam1 = bParam1;
  346.                                                 pData->LastParam2 = bParam2;
  347.                                                 if (!uSilent)
  348.                                                 {
  349.                                                         pParser->pfnSendMidiMsg(pData->LastNoteTick,
  350.                                                                 CurTrack, bEvent, bParam1, bParam2,
  351.                                                                 pParser->pUserData);
  352.                                                 }
  353.                                         }
  354.                                 }
  355.                                 else if (bEvent == 0xF0)
  356.                                 {//0xF0:系统码
  357.                                         uint8_t bSysCode = 0;
  358.                                         do
  359.                                         {
  360.                                                 Read(bSysCode);
  361.                                         } while (bSysCode != 0xF7);
  362.                                 }
  363.                                 else if (bEvent == 0xFF)
  364.                                 {//元事件
  365.                                         uint8_t bType, Bytes;
  366.                                         fpos_t CurrentPos;

  367.                                         Read(bType);//元数据类型
  368.                                         Read(Bytes);//元数据字节数

  369.                                         Tell(&CurrentPos);//记录元数据读取的位置

  370.                                         switch (bType)
  371.                                         {
  372.                                         case 0x2F://音轨结束标识
  373.                                                 pData->EndOfTrack = 1;
  374.                                                 break;
  375.                                         }

  376.                                         Seek(CurrentPos + Bytes);//读取完成后正确跳到下一个事件的位置
  377.                                 }
  378.                                 else//其它事件,未知事件
  379.                                 {
  380.                                         ErrC(ME_BadFile);
  381.                                 }
  382.                                 Tell(&(pData->NextNotePos));
  383.                                 if (pData->NextNotePos >= pData->StartPos + pData->TrackSize)
  384.                                         pData->EndOfTrack = 1;
  385.                         }
  386.                         else
  387.                                 break;
  388.                 }

  389.                 //异步多音轨单独处理
  390.                 if (pParser->wType == MIDIT_MultiAsync)
  391.                 {
  392.                         //防止同步播放其它音轨
  393.                         if (CurTick < pData->TotalTicks)
  394.                                 break;
  395.                 }
  396.         }

  397.         return 1;
  398. BadRet:
  399.         MidiParserShutdown(pParser);
  400.         return 0;
  401. }

  402. //=============================================================================
  403. //MidiParserParseToEnd:
  404. //分析整个文件
  405. //-----------------------------------------------------------------------------
  406. Midi_fn(MidiParserParseToEnd, int)
  407. (
  408.         MidiParser_p pParser
  409.         )
  410. {
  411.         return MidiParserUpdate(pParser, (double)(pParser->TotalTicks) * pParser->Velocity / pParser->wTicksPerCrotchet * 0.001, 0);
  412. }

  413. //=============================================================================
  414. //MidiParserReset:
  415. //重置Midi解析器状态
  416. //-----------------------------------------------------------------------------
  417. Midi_fn(MidiParserReset, void)
  418. (
  419.         MidiParser_p pParser
  420.         )
  421. {
  422.         uint16_t CurTrack;

  423.         for (CurTrack = 0; CurTrack < pParser->wNbTracks; CurTrack++)
  424.         {
  425.                 pParser->pTrackData[CurTrack].NextNotePos =
  426.                         pParser->pTrackData[CurTrack].StartPos;
  427.                 pParser->pTrackData[CurTrack].LastNoteTick = 0;
  428.                 pParser->pTrackData[CurTrack].EndOfTrack = 0;
  429.         }

  430.         pParser->EndOfFile = 0;
  431. }

  432. //=============================================================================
  433. //MidiParserShutdown:
  434. //关闭Midi解析器
  435. //-----------------------------------------------------------------------------
  436. Midi_fn(MidiParserShutdown, void)
  437. (
  438.         MidiParser_p pParser
  439.         )
  440. {
  441.         if (pParser)
  442.         {
  443.                 free(pParser->pTrackData);
  444.                 pParser->pTrackData = NULL;

  445.                 pParser->wType = 0;
  446.                 pParser->wNbTracks = 0;
  447.                 pParser->wTicksPerCrotchet = 0;
  448.                 pParser->Velocity = 0;
  449.                 pParser->TotalTicks = 0;
  450.                 pParser->EndOfFile = 0;
  451.         }
  452. }

  453. #undef ErrC
  454. #undef Tell
  455. #undef Seek
  456. #undef Read
复制代码
entry.c:
游客,如果您要查看本帖隐藏内容请回复
20150823235729.png
有趣的是,这个CMD窗口显示的内容很有节奏感233(它会随着音乐跳动)
这东西除了可以做成Midi播放器以外,还可以做成解析器,用来分析一个Midi音乐(偷谱)
BIN: MIDIPlayBin.7z (46.88 KB, 下载次数: 56)
SRC:
游客,如果您要查看本帖隐藏内容请回复

BIN的用法:将一个midi文件拖到它的图标上,松开鼠标就开始播放了。
20150825172233.png

本帖被以下淘专辑推荐:

回复

使用道具 举报

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24221 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
 楼主| 发表于 2015-8-25 17:48:05 | 显示全部楼层
已更新,修复了循环播放破坏内存的问题。
回复 赞! 1 靠! 0

使用道具 举报

0

主题

2

回帖

11

积分

用户组: 初·技术宅

UID
966
精华
0
威望
1 点
宅币
7 个
贡献
0 次
宅之契约
0 份
在线时间
1 小时
注册时间
2015-7-18
发表于 2015-8-24 07:35:06 | 显示全部楼层
test~~~~~~~~~~~~~~~~~~~~~~~~~~~~
回复

使用道具 举报

0

主题

14

回帖

94

积分

用户组: 小·技术宅

UID
726
精华
0
威望
1 点
宅币
78 个
贡献
0 次
宅之契约
0 份
在线时间
8 小时
注册时间
2015-3-12
发表于 2015-8-24 10:18:13 | 显示全部楼层
这是调用哪个音色库播放的?可以改吗?
回复 赞! 靠!

使用道具 举报

0

主题

2

回帖

11

积分

用户组: 初·技术宅

UID
966
精华
0
威望
1 点
宅币
7 个
贡献
0 次
宅之契约
0 份
在线时间
1 小时
注册时间
2015-7-18
发表于 2015-8-24 10:41:54 | 显示全部楼层
还是使用了midiOutShortMsg  我以为不通过系统就能发声了呢




回复 赞! 靠!

使用道具 举报

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24221 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
 楼主| 发表于 2015-8-24 13:22:28 | 显示全部楼层
gcyun 发表于 2015-8-24 10:41
还是使用了midiOutShortMsg  我以为不通过系统就能发声了呢

你现实点吧。做是可以做,但是需要翻看各种兼容声卡驱动的源码。我做这个东西的下一步就是为了实现DOS下驱动SoundBlaster16播放Midi。文件解析和Midi命令发送的代码是分开的。此外它还可以做成一个查看Midi文件曲谱的工具(需要自己解析Midi指令,论坛有Midi指令解析的实例,自己看)
回复 赞! 靠!

使用道具 举报

5

主题

17

回帖

109

积分

用户组: 小·技术宅

UID
594
精华
0
威望
0 点
宅币
87 个
贡献
0 次
宅之契约
0 份
在线时间
7 小时
注册时间
2014-12-14
发表于 2015-8-24 22:22:35 | 显示全部楼层
xp系统下窗口一闪而过。
回复 赞! 靠!

使用道具 举报

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24221 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
 楼主| 发表于 2015-8-25 01:10:58 | 显示全部楼层
W·Y 发表于 2015-8-24 22:22
xp系统下窗口一闪而过。

要么你没提供Midi文件,要么你提供的文件是错误的。
回复 赞! 靠!

使用道具 举报

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24221 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
 楼主| 发表于 2015-8-25 17:19:46 | 显示全部楼层
宇宙卐之王 发表于 2015-8-24 10:18
这是调用哪个音色库播放的?可以改吗?

微软波表,也就是MIDI_MAPPER
回复 赞! 靠!

使用道具 举报

5

主题

17

回帖

109

积分

用户组: 小·技术宅

UID
594
精华
0
威望
0 点
宅币
87 个
贡献
0 次
宅之契约
0 份
在线时间
7 小时
注册时间
2014-12-14
发表于 2015-8-28 05:36:34 | 显示全部楼层
0xAA55 发表于 2015-8-24 13:22
你现实点吧。做是可以做,但是需要翻看各种兼容声卡驱动的源码。我做这个东西的下一步就是为了实现DOS下 ...

是你压缩包下自带的midi文件
回复 赞! 靠!

使用道具 举报

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24221 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
 楼主| 发表于 2015-8-28 17:55:44 | 显示全部楼层
W·Y 发表于 2015-8-28 05:36
是你压缩包下自带的midi文件

忘了设置xp兼容了。你自己用VS编译一下(平台工具集选XP)就可以运行了。
回复 赞! 靠!

使用道具 举报

5

主题

17

回帖

109

积分

用户组: 小·技术宅

UID
594
精华
0
威望
0 点
宅币
87 个
贡献
0 次
宅之契约
0 份
在线时间
7 小时
注册时间
2014-12-14
发表于 2015-8-29 11:18:16 | 显示全部楼层
0xAA55 发表于 2015-8-28 17:55
忘了设置xp兼容了。你自己用VS编译一下(平台工具集选XP)就可以运行了。 ...

原来如此。。
回复 赞! 靠!

使用道具 举报

0

主题

7

回帖

21

积分

用户组: 初·技术宅

UID
1135
精华
0
威望
1 点
宅币
12 个
贡献
0 次
宅之契约
0 份
在线时间
1 小时
注册时间
2015-9-19
发表于 2015-9-19 08:35:16 | 显示全部楼层
good mass
回复

使用道具 举报

0

主题

29

回帖

98

积分

用户组: 小·技术宅

UID
294
精华
0
威望
2 点
宅币
65 个
贡献
0 次
宅之契约
0 份
在线时间
8 小时
注册时间
2014-5-16
发表于 2016-1-16 18:34:43 | 显示全部楼层
了解一下Midi文件播放
回复 赞! 靠!

使用道具 举报

1

主题

25

回帖

67

积分

用户组: 小·技术宅

UID
1414
精华
0
威望
0 点
宅币
41 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-1-15
发表于 2016-1-17 21:43:55 | 显示全部楼层
谢谢分享,来一个试试。。。
回复 赞! 靠!

使用道具 举报

0

主题

9

回帖

25

积分

用户组: 初·技术宅

UID
1501
精华
0
威望
1 点
宅币
14 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-2-20
发表于 2016-2-21 15:02:53 | 显示全部楼层
新手学习,感恩
回复 赞! 靠!

使用道具 举报

0

主题

1

回帖

10

积分

用户组: 初·技术宅

UID
1717
精华
0
威望
1 点
宅币
7 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-5-24
发表于 2016-5-24 08:53:28 | 显示全部楼层
剛好想研究midi
參考一下,謝謝
回复 赞! 靠!

使用道具 举报

0

主题

1

回帖

8

积分

用户组: 初·技术宅

UID
2052
精华
0
威望
1 点
宅币
5 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-11-9
发表于 2016-11-9 18:17:55 | 显示全部楼层
马一个 期待一下自己写出一份midi文件自动转midi massage的程序
回复 赞! 靠!

使用道具 举报

0

主题

24

回帖

56

积分

用户组: 小·技术宅

UID
2067
精华
0
威望
1 点
宅币
29 个
贡献
1 次
宅之契约
0 份
在线时间
0 小时
注册时间
2016-11-17
发表于 2016-11-17 10:28:24 | 显示全部楼层
支持    !!
回复 赞! 靠!

使用道具 举报

1

主题

8

回帖

14

积分

用户组: 初·技术宅

UID
2129
精华
0
威望
0 点
宅币
5 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2016-12-19
发表于 2016-12-19 19:50:14 | 显示全部楼层
棒棒哒,0xAA55写的详细又好,人最好啦
回复 赞! 靠!

使用道具 举报

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

GMT+8, 2024-4-18 13:37 , Processed in 0.053684 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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