首页
论坛
课程
招聘
[原创]在VC中播放mid音乐的最简单方法
2005-12-14 20:38 14137

[原创]在VC中播放mid音乐的最简单方法

2005-12-14 20:38
14137
前一段时间看到有人发布XM音乐的播放方法,主要用于在注册机加一个小巧的背景音乐.
但由于XM音乐比较少,比较小巧的就更少了,况且还依赖minifmod库.

所以考虑到以下因素,同样推荐在注册机加入MIDI音乐:
1.MIDI音乐文件使用更广泛,更容易获得,制作和编辑.
2.DirectMusic附带4MB音色库的Roland软波表,音质也不比XM差。
3.MIDI数据比XM数据更容易被压缩(MIDI文件压缩比在1:8左右).
4.不需要外部编程库,系统自带播放内核,添加的代码量极少.

这里给出一个最简单的MIDI音乐的播放方法,即使用DirectMusic高层库.
虽然利用MCI的高层也可以播放,但它似乎不支持内存载入,
而且无法避免与其他MIDI音乐的同时播放的冲突.
估计都有装了DirectX吧,播放的通用性应该没有问题.
但编译这个程序还需要装上DirectX SDK.
// Tiny MIDI player [By Dwing]
#pragma comment(lib,"ole32.lib")
#pragma comment(lib,"dxguid.lib")
#pragma comment(linker,"/ENTRY:Entry")
#include<dmusici.h>

unsigned char mididata[]={0x4D,0x54,0x68,0x64,......}; // 这里加入mid文件的数据.

IDirectMusicPerformance*    performance=0;
IDirectMusicSegment*        segment    =0;
IDirectMusicLoader*         loader     =0;

bool PlayMIDI(unsigned char* data,unsigned int size)
{
    DMUS_OBJECTDESC desc={0};
    desc.dwSize     =sizeof(desc);
    desc.guidClass  =CLSID_DirectMusicSegment;
    desc.pbMemData  =data;
    desc.llMemLength=size;
    desc.dwValidData=DMUS_OBJ_CLASS|DMUS_OBJ_MEMORY;
    if(FAILED(CoInitialize(0))) return false;
    if(FAILED(CoCreateInstance(CLSID_DirectMusicPerformance,0,CLSCTX_INPROC,
        IID_IDirectMusicPerformance,(void**)&performance))) return false;
    if(FAILED(CoCreateInstance(CLSID_DirectMusicLoader,0,CLSCTX_INPROC,
        IID_IDirectMusicLoader,(void**)&loader))) return false;
    if(FAILED(loader->GetObject(&desc,IID_IDirectMusicSegment,(void**)&segment))) return false;
    if(FAILED(performance->Init(0,0,0))) return false;
    if(FAILED(performance->AddPort(0))) return false;
    if(FAILED(segment->SetParam(GUID_StandardMIDIFile,-1,0,0,performance))) return false;
    if(FAILED(segment->SetParam(GUID_Download,-1,0,0,performance))) return false;
    if(FAILED(segment->SetRepeats(DMUS_SEG_REPEAT_INFINITE))) return false;
    if(FAILED(performance->PlaySegment(segment,0,0,0))) return false;
    return true;
}

void StopMIDI()
{
    if(segment)
    {
        if(performance)
        {
            performance->Stop(segment,0,0,0);
            segment->SetParam(GUID_Unload,-1,0,0,(void**)performance);
        }
        segment->Release();
        segment=0;
    }
    if(loader)
    {
        loader->Release();
        loader=0;
    }
    if(performance)
    {
        performance->CloseDown();
        performance->Release();
        performance=0;
    }
    CoUninitialize();
}

void Entry()
{
    if(PlayMIDI(mididata,sizeof(mididata)))
    {
        MessageBox(0,"Playing...","MIDI",0);
        StopMIDI();
    }
    else
        MessageBox(0,"Error!","MIDI",0);
    ExitProcess(0);
}

mid文件的数据可用一个小工具自己导出成数组格式.
执行PlayMIDI()后音乐的播放会有约1~2秒的延迟,估计不会有太大影响的.
如果在程序退出前不需要停止播放音乐,可以不调用StopMIDI(),程序会更小.
PlayMIDI()函数用VC的最小代码编译,只有200多字节!

【看雪培训】《Adroid高级研修班》2022年夏季班招生中!

收藏
点赞0
打赏
分享
最新回复 (9)
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
Immlep 活跃值 11 2005-12-15 00:17
2
0
好文章。。
雪    币: 43812
活跃值: 活跃值 (164068)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
linhanshi 活跃值 2005-12-15 00:28
3
0
雪    币: 454
活跃值: 活跃值 (22)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
prince 活跃值 16 2005-12-15 13:04
4
0
恩,是个好的方法,我以前写的注册机是把MIDI数据写到磁盘上再播放,这个方法省了读写磁盘文件的步骤,方便了一些.但是手写那个MIDI的二进制数据也是个非常麻烦的事情.推荐流行时代Sen写的文章,将二进制数据写入资源,然后在内存中释放,这样就可以把手写MIDI数据的步骤也省略掉了,强烈推荐:

原文如下:

近来看到prince发贴甚多,想我自论坛创建来就没写什么东西,因为本人才疏学浅。今天突然感觉心中不快,写篇小儿文章让大家见笑。文中尚有很多愚钝之辞,还望各位兄弟指出。
很久以前love和小子就写了一个文件补丁工具,他们的是asm版本,我今天的是C的版本,如果没有兴趣,可以就此打住。

我们写程序的时候经常要生成或者是创建新的文件,这个是怎么做到的呢?老的CIH病毒是这么写的:
BYTE byExe[] = {
  0x4d, 0x87 ......
  ..... .....
}
这个如果要生成的文件很大的时候这个就会很难写,而且写出来的源文件会非常的庞大。这个时候,我们可以用载入资源的方式来解决。

用到的相关API:
FineResource:查找一个资源。我们是把相关要生成的文件载入,就是用这个函数来确定其资源的位置。
SizeofResource:获得资源的尺寸。
LoadResource:装载资源,装入到内存中。
LockResource:锁定资源,在内存中锁定。

好了,现在在VC的工程中载入这个文件吧。首先,我们把***.exe或者***.mid该成***.bin二进制文件,在资源文件上点击右键,选择Import(导入)。这里我们为自定义资源类型,即Custom Resource Type,Resource type为读者兴趣随便填写,这里用MyRes,资源名称用IDR_MyRes。

好了,现在就可以写一个函数进行文件的生成了:
BOOL Create()
{
    HRSRC hResInfo;
    HGLOBAL hResData;
    DWORD dwSize, dwWritten;
    HANDLE hFile;
    //开始所需的资源
    hResInfo = FineResource(NULL,MAKEINTRESOURCE(IDR_MyRes),"MyRes");
    if(hResInfo == NULL)
        return FALSE;
    //获得资源尺寸
    dwSize = SizeofResource(NULL,hResInfo);
    //装载
    hResData = LoadResource(NULL,hResInfo);
    if(hResData == NULL)
        return FALSE;
    //最重要的地方,写文件
    hFile = CreateFile("c:\\MyResG.exe",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
    if(hFile == NULL)
        return FALSE;
    WriteFile(hFile,(LPCVOID)LockResource(hResData),dwSize,&dwWritten,NULL);
    CloseHandle(hFile);
    return TRUE;
}
用这个函数就可以把资源文件的东西拖出来了,哈哈。完工
雪    币: 201
活跃值: 活跃值 (34)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
dwing 活跃值 1 2005-12-15 16:26
5
0
最初由 prince 发布
恩,是个好的方法,我以前写的注册机是把MIDI数据写到磁盘上再播放,这个方法省了读写磁盘文件的步骤,方便了一些.但是手

写那个MIDI的二进制数据也是个非常麻烦的事情.推荐流行时代Sen写的文章,将二进制数据写入资源,然后在内存中释放,这样就可

以把手写MIDI数据的步骤也省略掉了,强烈推荐:
原文如下:
........

原来那些数据需要手写-_-!汗~~~
写个小程序来自动帮我们写吧!
[COLOR=blue]
#include<stdio.h>
void main()
{
    unsigned char c;
    unsigned int  n=0;
    FILE *fin =fopen("data.bin","rb");
    FILE *fout=fopen("data.h"  ,"wb");
    if(!fin||!fout) {printf("not found data.bin!\n");return;}
    fprintf(fout,"unsigned char *data[]={\r\n");
    while(1)
    {
        c=fgetc(fin);
        if(feof(fin)) break;
        fprintf(fout,"0x%02X,",c);
        if(++n%16==0) fprintf(fout,"\r\n");
    }
    fseek(fout,-1,SEEK_CUR);
    fprintf(fout,"\r\n};\r\n");
    fclose(fout);
    fclose(fin);
}

当然使用资源也是一个好办法,但比起直接使用数组缺点如下:
1.需要调用FineResource,SizeofResource,LoadResource,LockResource等函数,代码量多一些.
2.资源目录表和资源名称(如果使用字符串格式)需要一些额外的数据,且不能被加壳工具压缩.
3.使用资源就非常明显让别人轻易提取出该资源的数据.
4.便于某些无资源编译器或不方便编辑资源的编译器编译(如Dev C++).

还有另一个方法可以直接包含文件,而不是一堆十六进制数据.
但需要有nasm汇编器(把nasmw.exe放入VC编译程序目录中).
工程中添加文件mididata.asm,内容:
section .data
global _mididata
_mididata incbin "midi.mid"
;包含文件的文件名,与此文件同目录
设置mididata.asm的编译属性:
Custom Build/Commands: nasmw -f win32 -o $(OutDir)\$(InputName).obj $(InputPath)
Custom Build/Outputs:  $(OutDir)\$(InputName).obj
然后只需要在需要mididata的C++文件中加入下面一行即可:
extern "C" unsigned char mididata[12345]; //大小需要手动输入
哪个方法更方便自己决定吧......
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jjwangjun 活跃值 2005-12-15 16:57
6
0
以上都不错,只是我不太懂,强烈支持
雪    币: 454
活跃值: 活跃值 (22)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
prince 活跃值 16 2005-12-15 18:03
7
0
最初由 dwing 发布

当然使用资源也是一个好办法,但比起直接使用数组缺点如下:
1.需要调用FineResource,SizeofResource,LoadResource,LockResource等函数,代码量多一些.
2.资源目录表和资源名称(如果使用字符串格式)需要一些额外的数据,且不能被加壳工具压缩.
3.使用资源就非常明显让别人轻易提取出该资源的数据.
4.便于某些无资源编译器或不方便编辑资源的编译器编译(如Dev C++).

还有另一个方法可以直接包含文件,而不是一堆十六进制数据.
但需要有nasm汇编器(把nasmw.exe放入VC编译程序目录中).
工程中添加文件mididata.asm,内容:
section .data
global _mididata
_mididata incbin "midi.mid" ;包含文件的文件名,与此文件同目录
设置mididata.asm的编译属性:
Custom Build/Commands: nasmw -f win32 -o $(OutDir)\$(InputName).obj $(InputPath)
Custom Build/Outputs: $(OutDir)\$(InputName).obj
然后只需要在需要mididata的C++文件中加入下面一行即可:
extern "C" unsigned char mididata[12345]; //大小需要手动输入
哪个方法更方便自己决定吧......
........


呵呵,学习了!
雪    币: 203
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hyzhang 活跃值 2005-12-15 19:14
8
0
谁来转成asm的播放函数造福大家。
雪    币: 201
活跃值: 活跃值 (34)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
dwing 活跃值 1 2005-12-15 20:08
9
0
最初由 hyzhang 发布
谁来转成asm的播放函数造福大家。


如果MASM32中包含COM库和DirectX库的话就可以转换.
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Sen 活跃值 1 2005-12-24 00:34
10
0
哦hoho,prince把老文都搬出来了
游客
登录 | 注册 方可回帖
返回