DirectSound 钢琴(2)

写在前面:为什么我们喜欢底层?

市场化的游戏越来越强调成本经济效益,如果不是暴雪那样的大型公司(开发过魔兽争霸、星际争霸……),基本上不会花重金聘用职业游戏音乐创作配乐团队的,甚至对于一个小游戏而言,直接来个 PlaySound 播放背景音乐就草草了事了。很多人喜欢用高层,一来方便,而来开发效率高。但是为什么很多高手们关注喜欢底层?那是因为越贴近底层的程序,自由度越高。以播放声音来说,计算机播放音乐实际上是:读取文件到内存(如果文件很大,是不是会消耗很多)->验证文件的合理性并获取文件信息和编码参数->开始解码->解码数据输送到声卡->二进制信号控制声卡实现播放。我曾经包含了一个 40MB 的音乐文件,就一个 int main 里面调用 PlaySound,使用 SND_FILENAME 方法,没有其他任何语句,结果这个程序就有 40.3MB 的大小。其实用 DirectSound 的话,完全可以分配一个 3K 的内存播放即可了。

1.DirectSound 编程基础(DirectSound 是音频底层,只能用来处理 PCM、.wav 类似的文件,对于MIDI 等不支持):

<1>综述:DirectSound 是微软设计给游戏开发人员的 DirectX 组件之一,因为游戏引擎通常需要高性能的流畅打击感,几乎完美逼真的音质,因此游戏编程经常直接调用底层硬件来实现最好最直接的效果,Direct 就是这个含义,详情可参考维基百科。DirectX 提供的都是基于 com 的组件,这些组件包含高层的:Direct3D、DirectShow、DirectMusic 等,由于游戏市场的需要,微软加大了对 Direct3D 的研发,其它组件更新频率下降,在游戏程序员的眼中那些都黯然失色。由于 DirectX 有许多发行版,各个版本都不尽相同,所以有的时候你可能看不到 DirectMusic、DirectShow、DirectPlayer、DirectDraw,那是因为这些组件被合并为了统一的名字(XAudio = DirectSound + DirectMusic,Direct2D = DirectDraw + DirectShow……),通过接口中的参数确定是否使用高级功能,就是应用层功能。至于应用层和底层的区别,就不再赘述。游戏开发尽可能直接调用 API,摈弃那些过分冗余繁杂庞大的 MFC 之类的框架,何况 MFC 还有 bug.

<2>功能:DirectSound 中的示例程序展示了功能包含:设备选择,播放、暂停声音,获取播放状态,获取或设定播放进度,改变声音音频、采样率,设定播放方式(循环播放),声音从左声道到右声道,从右声道到左声道,设定 3D 音效,使用渲染特技,设定听者物理空间,对声音和运动进行算法叠加模拟多普勒效应,录音、截取一段声音,声音加工的后处理。

2.DirectSound 模拟钢琴演奏

<1>说明

机制:按键响应

情景假定:演奏者自始至终脚踩延音踏板,休止符就是制动踏板。额外假定重复相同按键会实现 2 部钢琴演奏声音的混音效果而非前者声音被重写掩盖

<2>DirectSound 功能总图

DirectSound 钢琴(2)_第1张图片

<3>DirectSound 流程(仅给出核心代码)

/* 建议初始化为 NULL / nullptr */
LPDIRECTSOUND8 g_lpds;			// 设备对象,必须唯一。这里使用用户默认设备,如需指定设备,必须枚举设备。
LPDIRECTSOUNDBUFFER g_lpdsbuf;		// 缓冲区对象,建议唯一,多个试过也行,不清楚存在主缓冲区、辅助缓冲区区别,但是 COM 接口的确有些不一样。
LPDIRECTSOUNDBUFFER8 g_lpdsbuf8[NUM];	// 缓冲区对象,用以装载要演奏的钢琴 88 个音,我一般装 26 个,因为字母键盘只有 26 个。

// 初始化条件:播放声音的窗口句柄,在该窗口上设定 DirectSound 的协作级别

if (DirectSoundCreate8(NULL, &g_lpDS, NULL) != DS_OK)
	return FALSE;
if (g_lpDS->SetCooperativeLevel(hWnd, DSSCL_NORMAL) != DS_OK)
	return FALSE;

// 紧接着就是创建辅助缓存,都是一个套路,不过至今让我困惑的是,为何 dsbd 必须是全局的才能播放?
DSBUFFERDESC dsbd;
if (FAILED(g_lpDS->CreateSoundBuffer(&dsbd, &dsbuf, NULL)))	// 主缓存
	return FALSE;
// 可以用循环进行下面一步,转换出最多低于 128 个接口
// 我修改过 VS2010 的堆栈保留提交大小,后发现最多只能创建 127 个 DirectSoundBuffer8 接口,第 128 个一定失败,不了解原因,
// 此外微软设置 DirectSound 貌似最多支持 8 个 wav 文件同时播放(这样的文件比我的要大),这似乎和硬件有关,高版本的 DirectSound 支持几路不了解了
if (FAILED(dsbuf->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*)&g_lpDSBuf8[g_nCnt])))
	return FALSE;

// 此处为解析 wav 文件格式,参考我的文件格式里面的链接,用 c 语言的文件读写 fopen, fread, fwrite 即可,步骤不重要略过只给出思路

// 此处的目的是将 wav 文件中的数据写入,如果 wav 文件很大,我们没必要一口气读取它,而是像循环队列一样地将它由播放的当前点覆盖写入,设置循环播放标记即可
LPVOID pDSLockedBuffer;
DWORD dwDSLockedBufferSize;
while (g_lpDSBuf[g_nCnt]->Lock(0, g_pNoteCtx[cNote - 'A'].wavHdr.dwDataSize, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, NULL, 0L))
	g_lpDSBuf[g_nCnt]->Restore();
memcpy(pDSLockedBuffer, g_pNoteCtx[cNote - 'A'].pBuffer, g_pNoteCtx[cNote - 'A'].wavHdr.dwDataSize);
if (FAILED(g_lpDSBuf[g_nCnt]->Unlock(pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0)))
	return FALSE;

// 播放很简单,写入后即可
g_lpDSBuf[g_nCnt]->Play(NULL, NULL, NULL);

// 最后别忘记释放,统统 Release 即可
g_lpDSBuf[g_nCnt]->Release();
g_lpDS->Release();

 

注意:后续作品代码会变得很核心!可能就不会公布了!

你可能感兴趣的:(DirectSound,钢琴)