DirectSound 蜂鸣声实现
由于 x64 Windows 移除了 Beep 函数,这是一个使用 DirectSound 实现 Beep 函数的方案。
引言
MSDN 文档声明 Win32 Beep()
函数在 64 位版本的 Windows 上不再受支持。但是,在不需要创建和播放声音文件的情况下,可能需要以简单的方式产生不同频率的音频。特别是,我编写此代码是为了实现 QBasic 的 PLAY
命令,以播放我手头的一些旧的 QBasic 歌曲。
背景
原始的 Beep 函数直接控制硬件计时器,进而直接用方波驱动扬声器,正如 Larry Osterman 所描述的那样。我们将尝试模拟这种行为,因为方波是最容易即时生成的波形,尽管代码可以很容易地修改为生成正弦波。我选择不这样做,因为代码完全可以在没有浮点运算的情况下工作,而生成正弦波要么涉及浮点运算,要么使用查找表使代码变得更加复杂。
Using the Code
代码完全包含在头文件 DSBeeper.h 中。只需构造一个 DSBeeper
对象,然后调用其 Beep()
函数
DSBeeper beeper;
beeper.Beep(750, 300);
构造函数初始化 DirectSound,当对象超出范围时,析构函数取消初始化。请注意,构造函数还使用特定的线程模型调用 CoInitializeEx()
。您可能需要修改构造函数和析构函数以适应您的特定应用程序。
实现
设置 DirectSound 的代码没什么意思,因为它只是遵循 Microsoft 建议的样板。但是,在不使用浮点运算的情况下合成正确频率的方波才是令人感兴趣的部分。
给定频率(以赫兹为单位),其倒数是波长(以秒为单位)。对于方波,波形是恒定的,但每半个周期切换一次符号。音频设备通常每秒播放声音 44100 次,因此一个半周期中的样本数是 44100 / (2 * 频率)。对于高频(让我们极端一点,使用 32kHz),半周期将小于一个样本!显然,我们无法播放这样的波。
可以使用 IDirectSoundBuffer
设备来控制播放频率。使用它,我们可以生成一个频率不正确的波形,但以调整后的速率播放它,使其听起来像正确的频率。上面计算的半个周期被向下舍入,受整数算术规则的约束。这可能会导致频率上限的误差超过 30%。要正确调整播放频率,我们必须将其乘以校正因子,即舍入的半个周期除以未舍入的值。计算半个周期的代码是
INT32 half_period = SAMPLING_RATE * NUM_CHANNELS / (2*dwFreq);
现在,对播放频率的调整是
DWORD play_freq;
m_lpDSBuffer->GetFrequency(&play_freq);
play_freq = MulDiv(play_freq, 2*dwFreq*half_period,
SAMPLING_RATE * NUM_CHANNELS);
m_lpDSBuffer->SetFrequency(play_freq);
我们使用 MulDiv
函数将前两个参数相乘,然后除以最后一个参数,中间精度为 64 位(这在 x86 处理器上的汇编中非常高效)。这并非绝对必要,因为分子最大值的计算不超过 32 位整数的限制(对于我尝试的几个例子),安全起见更好。请注意,这并不会比使用浮点比例因子差多少,因为结果精确到整数的 1 位,而当您必须将频率设置为整数时,您只能要求这么多。
QBPlayer - 演示项目
这实际上是两个项目合并成一个,因为演示项目做的事情并不简单,但它使演示更好地说明了代码的功能。演示项目是 QBasic PLAY
命令的实现,它允许您使用 PC 扬声器生成音乐。这用于制作游戏(例如,在示例程序 GORILLA.BAS
中),许多人可能仍然有旧的歌曲文件。演示项目解析一个命令字符串,指示要播放的音符,或如何修改播放器状态,并生成相应的音调。
如果您只想听听声音,请运行程序 QBPlayer_ds.exe,输入“C”然后按 Enter 键。您应该听到一个音符播放。输入 CTRL-Z 并按 Enter 键退出。对于那些运行 32 位 Windows 版本的人,您可以将输出与 PC 扬声器版本 (QBplayer.exe) 进行比较。
代码使用 Visual Studio 2005、DirectX SDK 2009 年 11 月以及来自 Windows Server 2003 R2 的 Platform SDK 的头文件进行编译。代码与 Visual C++ 6.0 MSVCRT.LIB 链接,以避免重新分发问题和代码大小问题。
历史
2009-01-21 - 初始修订