技术宅的结界

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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 3551|回复: 44
收起左侧

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

[复制链接]

995

主题

2213

帖子

5万

积分

用户组: 管理员

一只技术宅

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

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

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

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

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

midifile.h:
[C] 纯文本查看 复制代码
//=============================================================================
//作者: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
);

//=============================================================================
//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:
[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[i].StartPos=TrackStartPos;
		pParser->pTrackData[i].TrackSize=dwTrackLen;
		pParser->pTrackData[i].NextNotePos=TrackStartPos;
		pParser->pTrackData[i].LastNoteTick=0;
		pParser->pTrackData[i].EndOfTrack=0;

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

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

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

			//统计当前音轨总时钟数
			pParser->pTrackData[i].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=
							(bVelocity3|
							(bVelocity2<<8)|
							(bVelocity1<<16))/pParser->wTicksPerCrotchet;
					}
					break;
				default:
					break;
				}

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

		//如果是异步多音轨的话,记录整个Midi文件的播放时长
		if(pParser->wType==MIDIT_MultiAsync)
		{
			//异步多音轨,同级总时长
			pParser->TotalTicks+=pParser->pTrackData[i].TotalTicks;
		}
		else//否则(单音轨、同步多音轨)以时长最长的音轨为整个音乐的长度。
		{
			//同步多音轨或单音轨,找到时长最长的音轨。
			if(pParser->pTrackData[i].TotalTicks>pParser->TotalTicks)
				pParser->TotalTicks=pParser->pTrackData[i].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->Velocity);

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

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

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

		//异步多音轨单独处理
		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;
}

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

	for(CurTrack=0;CurTrack<pParser->wNbTracks;CurTrack++)
	{
		pParser->pTrackData[CurTrack].NextNotePos=
			pParser->pTrackData[CurTrack].StartPos;
		pParser->pTrackData[CurTrack].LastNoteTick=0;
		pParser->pTrackData[CurTrack].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 Read
entry.c:
游客,如果您要查看本帖隐藏内容请回复
20150823235729.png
有趣的是,这个CMD窗口显示的内容很有节奏感233(它会随着音乐跳动)
这东西除了可以做成Midi播放器以外,还可以做成解析器,用来分析一个Midi音乐(偷谱)
BIN: MIDIPlayBin.7z (46.88 KB, 下载次数: 25)

本帖被以下淘专辑推荐:

0

主题

2

帖子

11

积分

用户组: 初·技术宅

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

使用道具 举报

0

主题

14

帖子

92

积分

用户组: 小·技术宅

UID
726
精华
0
威望
1 点
宅币
76 个
贡献
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  我以为不通过系统就能发声了呢




995

主题

2213

帖子

5万

积分

用户组: 管理员

一只技术宅

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

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

5

主题

22

帖子

109

积分

用户组: 小·技术宅

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

995

主题

2213

帖子

5万

积分

用户组: 管理员

一只技术宅

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

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

995

主题

2213

帖子

5万

积分

用户组: 管理员

一只技术宅

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

微软波表,也就是MIDI_MAPPER

995

主题

2213

帖子

5万

积分

用户组: 管理员

一只技术宅

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

5

主题

22

帖子

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文件

995

主题

2213

帖子

5万

积分

用户组: 管理员

一只技术宅

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

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

5

主题

22

帖子

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

主题

27

帖子

81

积分

用户组: 小·技术宅

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

1

主题

31

帖子

72

积分

用户组: 小·技术宅

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

帖子

54

积分

用户组: 小·技术宅

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

1

主题

9

帖子

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, 2018-10-21 07:33 , Processed in 0.146702 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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