📄 22. 声音与音乐.txt
字号:
// Message generated when a buffer is finished
case MM_WOM_DONE:
if (bShutOff)
{
waveOutClose (hWaveOut) ;
return TRUE ;
}
// Fill and send out a new buffer
FillBuffer (((PWAVEHDR) lParam)->lpData, iFreq) ;
waveOutWrite (hWaveOut, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ;
return TRUE ;
case MM_WOM_CLOSE:
waveOutUnprepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
waveOutUnprepareHeader (hWaveOut, pWaveHdr2, sizeof (WAVEHDR)) ;
free (pWaveHdr1) ;
free (pWaveHdr2) ;
free (pBuffer1) ;
free (pBuffer2) ;
hWaveOut = NULL ;
SetDlgItemText (hwnd, IDC_ONOFF, TEXT ("Turn On")) ;
if (bClosing)
EndDialog (hwnd, 0) ;
return TRUE ;
case WM_SYSCOMMAND:
switch (wParam)
{
case SC_CLOSE:
if (hWaveOut != NULL)
{
bShutOff = TRUE ;
bClosing = TRUE ;
waveOutReset (hWaveOut) ;
}
else
EndDialog (hwnd, 0) ;
return TRUE ;
}
break ;
}
return FALSE ;
}
SINEWAVE.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Dialog
SINEWAVE DIALOG DISCARDABLE 100, 100, 200, 50
STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Sine Wave Generator"
FONT 8, "MS Sans Serif"
BEGIN
SCROLLBAR IDC_SCROLL,8,8,150,12
RTEXT "440",IDC_TEXT,160,10,20,8
LTEXT "Hz",IDC_STATIC,182,10,12,8
PUSHBUTTON "Turn On",IDC_ONOFF,80,28,40,14
END
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by SineWave.rc
#define IDC_STATIC -1
#define IDC_SCROLL 1000
#define IDC_TEXT 1001
#define IDC_ONOFF 1002
注意,FillBuffer例程中用到的OUT_BUFFER_SIZE、SAMPLE_RATE和PI标识符在程序的顶部定义。FillBuffer的iFreq参数是需要的频率,单位是Hz。还要注意,sin函数的结果调整到了0到254的范围之间。对于每个样本,sin函数的fAngle参数都增加一个值,该值的大小是2π弧度乘以需要的频率再除以取样频率。
SINEWAVE的窗口包含三个控件:一个用于选择频率的水平滚动条,一个用于显示目前所选频率的静态文字区域,以及一个标记为「Turn On」的按钮。按下此按钮后,您将从连结声卡的扩音器中听到正弦波的声音,同时按钮上的文字将变成「Turn Off」。用键盘或者鼠标移动滚动条可以改变频率。要关闭声音,可以再次按下按钮。
SINEWAVE程序代码初始化滚动条,以便频率在WM_INITDIALOG消息处理期间最低是20Hz,最高是5000Hz。初始化时,滚动条设定为440 Hz。用音乐术语来说就是中音上面的A,它在管弦乐队演奏时用来调音。DlgProc在接收WM_HSCROLL消息处理期间改变静态变量iFreq。注意,Page Left和Page Right将导致DlgProc增加或者减少一个八度音阶。
当DlgProc从按钮收到一个WM_COMMAND消息时,它首先配置4个内存块-2个用于WAVEHDR结构,我们马上讨论。另两个用于缓冲区储存波形数据,我们将这两个缓冲区称为pBuffer1和pBuffer2。
通过呼叫waveOutOpen函数,SINEWAVE打开波形声音设备以便输出,waveOutOpen函数使用下面的参数:
waveOutOpen (&hWaveOut, wDeviceID, &waveformat, dwCallBack,
dwCallBackData, dwFlags) ;
将第一个参数设定为指向HWAVEOUT(handle to waveform audio output:波形声音输出句柄)型态的变量。从函数传回时,此变量将设定为一个句柄,后面的波形输出呼叫中将使用该句柄。
waveOutOpen的第二个参数是设备ID。它允许函数可以在安装多个声卡的机器上使用。参数的范围在0到系统所安装的波形输出设备数之间。呼叫waveOutGetNumDevs可以获得波形输出设备数,而呼叫waveOutGetDevCaps可以找出每个波形输出设备。如果想消除设备问号,那么您可以用常数WAVE_MAPPER(定义为-1)来选择设备,该设备在「控制台」的「多媒体」中「音效」页面卷标里的「喜欢使用的设备」中指定。另外,如果首选设备不能满足您的需要,而其它设备可以,那么系统将选择其它设备。
第三个参数是指向WAVEFORMATEX结构的指针(后面将详细介绍)。第四个参数是窗口句柄或指向动态链接库中callback函数的指标,用来表示接收波形输出消息的窗口或者callback函数。使用callback函数时,可在第五个参数中指定程序定义的数据。dwFlags参数可设为CALLBACK_WINDOW或CALLBACK_FUNCTION,以表示第四个参数的型态。您也可用WAVE_FORMAT_QUERY标记来检查能否打开设备(实际上并不打开它)。还有其它几个标记可用。
waveOutOpen的第三个参数定义为指向WAVEFORMATEX型态结构的指针,此结构在MMSYSTEM.H中定义如下:
typedef struct waveformat_tag
{
WORD wFormatTag ; // waveform format = WAVE_FORMAT_PCM
WORD nChannels ; // number of channels = 1 or 2
DWORD nSamplesPerSec ; // sample rate
DWORD nAvgBytesPerSec ; // bytes per second
WORD nBlockAlign ; // block alignment
WORD wBitsPerSample ; // bits per samples = 8 or 16
WORD cbSize ; // 0 for PCM
}
WAVEFORMATEX, * PWAVEFORMATEX ;
您可用此结构指定取样频率(nSamplesPerSec)和取样精确度(nBitsPerSample),以及选择单声道或立体声(nChannels)。结构中有些信息看起来是多余的,但该结构也可用于非PCM的取样方式。在非PCM取样方式下,此结构的最后一个字段设定为非0值,并带有其它信息。
对于PCM取样方式,nBlockAlign字段设定为nChannels乘以wBitsPerSample再除以8所得到的数值,它表示每次取样的总字节数。nAvgBytesPerSec字段设定为nSamplesPerSec和nBlockAlign的乘积。
SINEWAVE初始化WAVEFORMATEX结构的字段,并呼叫waveOutOpen函数:
waveOutOpen ( &hWaveOut, WAVE_MAPPER, &waveformat,
(DWORD) hwnd, 0, CALLBACK_WINDOW)
如果呼叫成功,则waveOutOpen函数传回MMSYSERR_NOERROR(定义为0),否则传回非0的错误代码。如果waveOutOpen的传回值非0,则SINEWAVE清除窗口,并显示一个标识错误的消息框。
现在设备打开了,SINEWAVE继续初始化两个WAVEHDR结构的字段,这两个结构用于在API中传递缓冲。WAVEHDR定义如下:
typedef struct wavehdr_tag
{
LPSTR lpData; // pointer to data buffer
DWORD dwBufferLength; // length of data buffer
DWORD dwBytesRecorded; // used for recorded
DWORD dwUser; // for program use
DWORD dwFlags; // flags
DWORD dwLoops; // number of repetitions
struct wavehdr_tag FAR *lpNext; // reserved
DWORD reserved; // reserved
}
WAVEHDR, *PWAVEHDR ;
SINEWAVE将lpData字段设定为包含数据的缓冲区地址,dwBufferLength字段设定为此缓冲区的大小,dwLoops字段设定为1,其它字段都设定为0或NULL。如果要重复循环播放声音,可设定dwFlags和dwLoops字段。
SINEWAVE下一步为两个信息表头呼叫waveOutPrepareHeader函数,以防止结构和缓冲区与磁盘发生数据交换。
到此为止,所有的这些准备都是响应单击开启声音的按钮。但在程序的消息队列里已经有一个消息在等待响应。因为我们已经在函数waveOutOpen中指定要用一个窗口消息处理程序来接收波形输出消息,所以waveOutOpen函数向程序的消息队列发送了MM_WOM_OPEN消息,wParam消息参数设定为波形输出句柄。要处理MM_WOM_OPEN消息,SINEWAVE呼叫FillBuffer函数两次,并用正弦波形数据填充pBuffer缓冲区。然后SINEWAVE把两个WAVEHDR结构传送给waveOutWrite,此函数将数据传送到波形输出硬件,才真正开始播放声音。
当波形硬件播放完waveOutWrite函数传送来的数据后,就向窗口发送MM_WOM_DONE消息,其中wParam参数是波形输出句柄,lParam是指向WAVEHDR结构的指针。SINEWAVE在处理此消息时,将计算缓冲区的新数据,并呼叫waveOutWrite来重新提交缓冲区。
编写SINEWAVE程序时也可以只用一个WAVEHDR结构和一个缓冲区。不过,这样在播放完数据后将会有很短暂的停顿,以等待程序处理MM_WOM_DONE消息来提交新的缓冲区。SINEWAVE使用的「双缓冲」技术避免了声音的不连续。
当使用者单击「Turn Off」按钮关闭声音时,DlgProc接收到另一个WM_COMMAND消息。对此消息,DlgProc把bShutOff变量设定为TRUE,并呼叫waveOutReset函数。此函数停止处理声音并发送一条MM_WOM_DONE消息。bShutOff为TRUE时,SINEWAVE透过呼叫waveOutClose来处理MM_WOM_DONE,从而产生一条MM_WOM_CLOSE消息。处理MM_WOM_CLOSE通常包括清除程序。SINEWAVE为两个WAVEHDR结构而呼叫waveOutUnprepareHeader、释放所有的内存块并把按钮上的文字改回「Turn On」。
如果硬件继续播放缓冲区的声音数据,那么它自己呼叫waveOutClose就没有作用。您必须先呼叫waveOutReset来停止播放并产生MM_WOM_DONE消息。当wParam是SC_CLOSE时,DlgProc也处理WM_SYSCOMMAND消息,这是因为使用者从系统菜单中选择了「Close」。如果波形声音继续播放,DlgProc则呼叫waveOutReset。无论如何,最后总要呼叫EndDialog来结束程序。
数位录音机
Windows提供了一个称为「录音程序」来录制和播放数字声音。程序22-3所示的程序(RECORD1)不如「录音程序」完善,因为它不含有任何文件I/O,也不允许声音编辑。然而,这个程序显示了使用低阶波形声音API来录制和回放声音的基本方法。
程序22-3 RECORD1
RECORD1.C
/*---------------------------------------------------------------------------
RECORD1.C -- Waveform Audio Recorder
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#define INP_BUFFER_SIZE 16384
BOOL CALLBACK DlgProc (HWND, UINT, WPARAM, LPARAM) ;
TCHAR szAppName [] = TEXT ("Record1") ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -