0xAA55 发表于 2015-8-24 00:49:50

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

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

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

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

#include<stdint.h>

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

//MIDIFILE的回调函数调用约定
#ifndef Midi_cb
#define Midi_cb _cdecl
#endif // !Midi_cb

//MIDIFILE的符号导出规则
#ifndef Midi_x
#ifdef __cplusplus
#define Midi_x extern"C"
#else // !__cplusplus
#define Midi_x extern
#endif // !__cplusplus
#endif // !Midi_x

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

//用户自定义数据的格式
#ifndef Midi_UserData_t
#define Midi_UserData_t void
#endif

#ifndef Midi_UserData_p
#define Midi_UserData_p Midi_UserData_t*
#endif

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

//Midi时间刻度
#ifndef MidiTime_t
#define MidiTime_t double
#endif

#ifndef MidiTime_p
#define MidiTime_p MidiTime_t*
#endif
//-----------------------------------------------------------------------------




//=============================================================================
//错误代码
//-----------------------------------------------------------------------------
typedef enum
{
        ME_OK = 0,                //无错误
        ME_OutOfMem,        //内存不足
        ME_TellFail,        //取得文件指针失败
        ME_SeekFail,        //设置文件指针失败
        ME_ReadFail,        //读取失败
        ME_BadFile                //文件格式错误
}MidiErrCode_t, * MidiErrCode_p;




//=============================================================================
//用户回调函数,用户必须提供实现的函数
//-----------------------------------------------------------------------------
//读取文件:要求成功返回非零,失败返回0
typedef int(Midi_cb* ReadFile_f)
(
        void* pBuf,                //缓冲区
        size_t        cb,                        //要读取的字节数
        Midi_UserData_p pUserData        //用户数据
        );
//-----------------------------------------------------------------------------
//取得文件指针:要求成功返回非零,失败返回0
typedef int(Midi_cb* GetFilePtr_f)
(
        fpos_t* pPosition,        //取得位置
        Midi_UserData_p pUserData        //用户数据
        );
//-----------------------------------------------------------------------------
//设置文件指针:要求成功返回非零,失败返回0
typedef int(Midi_cb* SetFilePtr_f)
(
        fpos_t        Position,        //新的位置
        Midi_UserData_p pUserData        //用户数据
        );
//-----------------------------------------------------------------------------
//发送MIDI命令
typedef void(Midi_cb* SendMidiMsg_f)
(
        uint32_t TickCount,        //当前命令时钟
        uint16_t TrackNo,        //当前音轨号
        uint8_t        Command,        //命令
        uint8_t        Param1,                //参数1
        uint8_t        Param2,                //参数2
        Midi_UserData_p pUserData        //用户数据
        );
//-----------------------------------------------------------------------------
//错误处理
typedef void(Midi_cb* Error_f)(MidiErrCode_t);
//-----------------------------------------------------------------------------


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


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

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

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

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

//=============================================================================
//MidiParserStart:
//分析MIDI文件,为播放做准备。失败返回零,成功返回非零
//-----------------------------------------------------------------------------
Midi_fn(MidiParserStart, int)
(
        MidiParser_p pParser
        );

//=============================================================================
//MidiParserUpdate:
//更新播放状态到当前时间
//-----------------------------------------------------------------------------
Midi_fn(MidiParserUpdate, int)
(
        MidiParser_p        pParser,
        MidiTime_t                dbMilliSeconds,
        uint8_t                        uSilent
        );

//=============================================================================
//MidiParserReset:
//重置Midi解析器状态
//-----------------------------------------------------------------------------
Midi_fn(MidiParserReset, void)
(
        MidiParser_p pParser
        );

//=============================================================================
//MidiParserParseToEnd:
//分析整个文件
//-----------------------------------------------------------------------------
Midi_fn(MidiParserParseToEnd, int)
(
        MidiParser_p pParser
        );

//=============================================================================
//MidiParserShutdown:
//关闭Midi解析器
//-----------------------------------------------------------------------------
Midi_fn(MidiParserShutdown, void)
(
        MidiParser_p pParser
        );

/******************************************************************************
注释

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

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

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

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

******************************************************************************/


#endif // !_MIDI_Parser_midifile.c://=============================================================================
//作者:0xAA55
//论坛:http://www.0xaa55.com/
//版权所有(C) 2013-2015 技术宅的结界
//请保留原作者信息,否则视为侵权。
//-----------------------------------------------------------------------------
#include"midifile.h"

#include<stdio.h>
#include<malloc.h>
#include<memory.h>

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

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

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

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

//=============================================================================
//ReadDynamicByte:
//读取动态字节,最多读取4字节。返回读取的字节数
//-----------------------------------------------------------------------------
static size_t ReadDynamicByte(MidiParser_p pParser, uint32_t* pOut)
{
        size_t BytesRead;//已读取的字节数
        uint32_t uVal = 0;
        for (BytesRead = 1; BytesRead <= 4; BytesRead++)//最多读取4字节
        {
                uint8_t Value;
                Read(Value);//读取一个字节
                uVal = (uVal << 7) | (Value & 0x7F);//新读入的是低位
                if (!(Value & 0x80))//如果没有后续字节
                        break;//就停止读取。
        }
        *pOut = uVal;
        return BytesRead;//返回读取的字节数
BadRet:
        *pOut = uVal;
        return 0;
}

//=============================================================================
//GetCommandNbParams:
//取得MIDI命令的参数个数
//-----------------------------------------------------------------------------
static uint8_t GetCommandNbParams(uint8_t bEvent)
{
        if (bEvent <= 0x7F)return 0;//上个命令的参数
        else if (bEvent <= 0x8F)return 2;//两个参数
        else if (bEvent <= 0x9F)return 2;//两个参数
        else if (bEvent <= 0xAF)return 2;//两个参数
        else if (bEvent <= 0xBF)return 2;//两个参数
        else if (bEvent <= 0xCF)return 1;//一个参数
        else if (bEvent <= 0xDF)return 1;//一个参数
        else if (bEvent <= 0xEF)return 2;//两个参数
        else if (bEvent <= 0xFF)return 0;//机器码
        else return 2;//元数据
}

//=============================================================================
//DefOnError:
//默认的错误提示函数
//-----------------------------------------------------------------------------
static void Midi_cb DefOnError(MidiErrCode_t Err)
{
        //什么也不做
}

//=============================================================================
//MidiParserStart:
//分析MIDI文件,为播放做准备。失败返回零,成功返回非零
//-----------------------------------------------------------------------------
Midi_fn(MidiParserStart, int)
(
        MidiParser_p pParser
        )
{
        MIDIHeader midiHeader;
        size_t i;

        pParser->wType = 0;
        pParser->wNbTracks = 0;
        pParser->wTicksPerCrotchet = 0;
        pParser->pTrackData = NULL;
        pParser->Velocity = 0;
        pParser->TotalTicks = 0;
        pParser->EndOfFile = 0;
        pParser->LastErr = ME_OK;

        if (!pParser->pfnOnError)
                pParser->pfnOnError = DefOnError;

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

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

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

        //转换为Little-Endian
        midiHeader.dwRestLen = BSwapD(midiHeader.dwRestLen);
        midiHeader.wType = BSwapW(midiHeader.wType);
        midiHeader.wNbTracks = BSwapW(midiHeader.wNbTracks);
        midiHeader.wTicksPerCrotchet = BSwapW(midiHeader.wTicksPerCrotchet);

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

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

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

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

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

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

                fpos_t TrackStartPos;

                //轨道标记
                Read(dwTrackFlag);
                if (dwTrackFlag != MIDI_MTrk)
                        ErrC(ME_BadFile);

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

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

                pParser->pTrackData.StartPos = TrackStartPos;
                pParser->pTrackData.TrackSize = dwTrackLen;
                pParser->pTrackData.NextNotePos = TrackStartPos;
                pParser->pTrackData.LastNoteTick = 0;
                pParser->pTrackData.EndOfTrack = 0;

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

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

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

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

                        //读取事件号
                        Read(bEvent);

                        //分析事件
                        if (bEvent <= 0x7F)
                        {//0到0x7F:和上一个事件的事件号相同,而读取的这个字节就是上一个事件
                        //号的参数的第一个字节
                                Skip(GetCommandNbParams(LastEvent) - 1);
                                bEvent = LastEvent;
                        }
                        else if (bEvent <= 0xEF)
                        {//基本的MIDI命令
                                Skip(GetCommandNbParams(bEvent));
                        }
                        else if (bEvent == 0xF0)
                        {//0xF0:系统码
                                uint8_t bSysCode = 0;
                                do
                                {
                                        Read(bSysCode);
                                } while (bSysCode != 0xF7);
                        }
                        else if (bEvent == 0xFF)
                        {//元事件
                                uint8_t bType, Bytes;
                                fpos_t CurrentPos;

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

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

                                switch (bType)
                                {
                                case 0x2F://音轨结束标识
                                        EndOfTrack = 1;
                                        break;
                                case 0x51://速度
                                {
                                        uint8_t bVelocity1, bVelocity2, bVelocity3;

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

                                        if (!pParser->Velocity)
                                                pParser->Velocity =
                                                ((uint32_t)bVelocity3 |
                                                        ((uint32_t)bVelocity2 << 8) |
                                                        ((uint32_t)bVelocity1 << 16));
                                }
                                break;
                                default:
                                        break;
                                }

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

                //如果是异步多音轨的话,记录整个Midi文件的播放时长
                if (pParser->wType == MIDIT_MultiAsync)
                {
                        //异步多音轨,同级总时长
                        pParser->TotalTicks += pParser->pTrackData.TotalTicks;
                }
                else//否则(单音轨、同步多音轨)以时长最长的音轨为整个音乐的长度。
                {
                        //同步多音轨或单音轨,找到时长最长的音轨。
                        if (pParser->pTrackData.TotalTicks > pParser->TotalTicks)
                                pParser->TotalTicks = pParser->pTrackData.TotalTicks;
                }
                Seek(TrackStartPos + dwTrackLen);//转到下一个音轨
        }

        return 1;
BadRet:
        MidiParserShutdown(pParser);
        return 0;
}

//=============================================================================
//MidiParserUpdate:
//更新播放状态到当前时间
//-----------------------------------------------------------------------------
Midi_fn(MidiParserUpdate, int)
(
        MidiParser_p        pParser,
        MidiTime_t                dbMilliSeconds,
        uint8_t                        uSilent
        )
{
        uint16_t CurTrack;
        uint32_t CurTick;

        if (dbMilliSeconds < 0)
                dbMilliSeconds = 0;

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

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

        for (CurTrack = 0; CurTrack < pParser->wNbTracks; CurTrack++)
        {
                TrackData_p pData = &(pParser->pTrackData);

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

                //异步多音轨单独处理
                if (pParser->wType == MIDIT_MultiAsync)
                {
                        if (CurTick >= pData->TotalTicks)
                        {
                                CurTick -= pData->TotalTicks;
                                continue;
                        }
                }
                for (; !pData->EndOfTrack; )
                {
                        uint32_t dwDelay;
                        uint8_t bEvent;

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

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

                        if (CurTick >= pData->LastNoteTick + dwDelay)
                        {
                                pData->LastNoteTick += dwDelay;
                                //读取事件号
                                Read(bEvent);

                                //分析事件
                                if (bEvent <= 0x7F)
                                {        //0到0x7F:和上一个事件的事件号相同,而读取的这个字节就是上一
                                        //个事件号的参数的第一个字节
                                        uint8_t NbParams = GetCommandNbParams(pData->LastCommand);
                                        if (NbParams == 1)
                                        {
                                                pData->LastParam1 = bEvent;
                                                if (!uSilent)
                                                {
                                                        pParser->pfnSendMidiMsg(pData->LastNoteTick,
                                                                CurTrack, pData->LastCommand, bEvent, 0,
                                                                pParser->pUserData);
                                                }
                                        }
                                        else if (NbParams == 2)
                                        {
                                                uint8_t bParam2;
                                                Read(bParam2);
                                                pData->LastParam1 = bEvent;
                                                pData->LastParam2 = bParam2;
                                                if (!uSilent)
                                                {
                                                        pParser->pfnSendMidiMsg(pData->LastNoteTick,
                                                                CurTrack, pData->LastCommand, bEvent, bParam2,
                                                                pParser->pUserData);
                                                }
                                        }
                                }
                                else if (bEvent <= 0xEF)
                                {
                                        uint8_t NbParams = GetCommandNbParams(bEvent);
                                        pData->LastCommand = bEvent;
                                        if (NbParams == 1)
                                        {
                                                uint8_t bParam1;
                                                Read(bParam1);
                                                pData->LastParam1 = bParam1;
                                                if (!uSilent)
                                                {
                                                        pParser->pfnSendMidiMsg(pData->LastNoteTick,
                                                                CurTrack, bEvent, bParam1, 0, pParser->pUserData);
                                                }
                                        }
                                        else if (NbParams == 2)
                                        {
                                                uint8_t bParam1, bParam2;
                                                Read(bParam1);
                                                Read(bParam2);
                                                pData->LastParam1 = bParam1;
                                                pData->LastParam2 = bParam2;
                                                if (!uSilent)
                                                {
                                                        pParser->pfnSendMidiMsg(pData->LastNoteTick,
                                                                CurTrack, bEvent, bParam1, bParam2,
                                                                pParser->pUserData);
                                                }
                                        }
                                }
                                else if (bEvent == 0xF0)
                                {//0xF0:系统码
                                        uint8_t bSysCode = 0;
                                        do
                                        {
                                                Read(bSysCode);
                                        } while (bSysCode != 0xF7);
                                }
                                else if (bEvent == 0xFF)
                                {//元事件
                                        uint8_t bType, Bytes;
                                        fpos_t CurrentPos;

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

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

                                        switch (bType)
                                        {
                                        case 0x2F://音轨结束标识
                                                pData->EndOfTrack = 1;
                                                break;
                                        }

                                        Seek(CurrentPos + Bytes);//读取完成后正确跳到下一个事件的位置
                                }
                                else//其它事件,未知事件
                                {
                                        ErrC(ME_BadFile);
                                }
                                Tell(&(pData->NextNotePos));
                                if (pData->NextNotePos >= pData->StartPos + pData->TrackSize)
                                        pData->EndOfTrack = 1;
                        }
                        else
                                break;
                }

                //异步多音轨单独处理
                if (pParser->wType == MIDIT_MultiAsync)
                {
                        //防止同步播放其它音轨
                        if (CurTick < pData->TotalTicks)
                                break;
                }
        }

        return 1;
BadRet:
        MidiParserShutdown(pParser);
        return 0;
}

//=============================================================================
//MidiParserParseToEnd:
//分析整个文件
//-----------------------------------------------------------------------------
Midi_fn(MidiParserParseToEnd, int)
(
        MidiParser_p pParser
        )
{
        return MidiParserUpdate(pParser, (double)(pParser->TotalTicks) * pParser->Velocity / pParser->wTicksPerCrotchet * 0.001, 0);
}

//=============================================================================
//MidiParserReset:
//重置Midi解析器状态
//-----------------------------------------------------------------------------
Midi_fn(MidiParserReset, void)
(
        MidiParser_p pParser
        )
{
        uint16_t CurTrack;

        for (CurTrack = 0; CurTrack < pParser->wNbTracks; CurTrack++)
        {
                pParser->pTrackData.NextNotePos =
                        pParser->pTrackData.StartPos;
                pParser->pTrackData.LastNoteTick = 0;
                pParser->pTrackData.EndOfTrack = 0;
        }

        pParser->EndOfFile = 0;
}

//=============================================================================
//MidiParserShutdown:
//关闭Midi解析器
//-----------------------------------------------------------------------------
Midi_fn(MidiParserShutdown, void)
(
        MidiParser_p pParser
        )
{
        if (pParser)
        {
                free(pParser->pTrackData);
                pParser->pTrackData = NULL;

                pParser->wType = 0;
                pParser->wNbTracks = 0;
                pParser->wTicksPerCrotchet = 0;
                pParser->Velocity = 0;
                pParser->TotalTicks = 0;
                pParser->EndOfFile = 0;
        }
}

#undef ErrC
#undef Tell
#undef Seek
#undef Readentry.c:**** Hidden Message *****
有趣的是,这个CMD窗口显示的内容很有节奏感233(它会随着音乐跳动)
这东西除了可以做成Midi播放器以外,还可以做成解析器,用来分析一个Midi音乐(偷谱)
BIN:
SRC:**** Hidden Message *****
BIN的用法:将一个midi文件拖到它的图标上,松开鼠标就开始播放了。

0xAA55 发表于 2015-8-25 17:48:05

已更新,修复了循环播放破坏内存的问题。

gcyun 发表于 2015-8-24 07:35:06

test~~~~~~~~~~~~~~~~~~~~~~~~~~~~

宇宙卐之王 发表于 2015-8-24 10:18:13

这是调用哪个音色库播放的?可以改吗?

gcyun 发表于 2015-8-24 10:41:54

还是使用了midiOutShortMsg我以为不通过系统就能发声了呢




0xAA55 发表于 2015-8-24 13:22:28

gcyun 发表于 2015-8-24 10:41
还是使用了midiOutShortMsg我以为不通过系统就能发声了呢

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

W·Y 发表于 2015-8-24 22:22:35

xp系统下窗口一闪而过。

0xAA55 发表于 2015-8-25 01:10:58

W·Y 发表于 2015-8-24 22:22
xp系统下窗口一闪而过。

要么你没提供Midi文件,要么你提供的文件是错误的。

0xAA55 发表于 2015-8-25 17:19:46

宇宙卐之王 发表于 2015-8-24 10:18
这是调用哪个音色库播放的?可以改吗?

微软波表,也就是MIDI_MAPPER

W·Y 发表于 2015-8-28 05:36:34

0xAA55 发表于 2015-8-24 13:22
你现实点吧。做是可以做,但是需要翻看各种兼容声卡驱动的源码。我做这个东西的下一步就是为了实现DOS下 ...

是你压缩包下自带的midi文件

0xAA55 发表于 2015-8-28 17:55:44

W·Y 发表于 2015-8-28 05:36
是你压缩包下自带的midi文件

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

W·Y 发表于 2015-8-29 11:18:16

0xAA55 发表于 2015-8-28 17:55
忘了设置xp兼容了。你自己用VS编译一下(平台工具集选XP)就可以运行了。 ...

原来如此。。

EVERIN 发表于 2015-9-19 08:35:16

good mass

Jerry 发表于 2016-1-16 18:34:43

了解一下Midi文件播放

9998887776 发表于 2016-1-17 21:43:55

谢谢分享,来一个试试。。。

老麦 发表于 2016-2-21 15:02:53

新手学习,感恩

joeyten2000 发表于 2016-5-24 08:53:28

剛好想研究midi
參考一下,謝謝

jimrunning 发表于 2016-11-9 18:17:55

马一个 期待一下自己写出一份midi文件自动转midi massage的程序

jasonchen 发表于 2016-11-17 10:28:24

支持    !!

脑残后的天然呆 发表于 2016-12-19 19:50:14

棒棒哒,0xAA55写的详细又好,人最好啦
页: [1] 2 3 4
查看完整版本: 【C语言】Midi文件播放器(可跨平台)