第22章 声音与音乐(1)

22.1 Windows和多媒体

22.1.1 Windows中支持多媒体功能的API

(1)底层接口:如波形音频输入、输出函数waveIn和waveOut前缀开头

MIDI输出设备midiOut函数

(2)高层接口:

  ①以mci为前缀的7个函数。mci本身有两种,一种是向MCI发送消息。一种是向MCI发送文本字符串。

  ②MessageBeep和PlaySound等函数。

22.1.2 TESTMCI程序

效果图

/*---------------------------------------------------------
   TESTMCI.C —— MCI Command String Tester
                 (c)Charles Petzold,1998
---------------------------------------------------------*/

#include <Windows.h>
#include "resource.h"

#pragma comment(lib,"WINMM.lib")  //须用到WINMM.DLL的导入表

#define ID_TIMER 1
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szAppName[] = TEXT("TestMci");

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    if (-1==DialogBox(hInstance,szAppName,NULL,DlgProc))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
    }

    return 0;
}

BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND hwndEdit;
    int iCharBeg, iCharEnd,iLineBeg,iLineEnd,iLine,iLength,iChar;
    TCHAR szCommand[1024], szReturn[1024], szError[1024],szBuffer[24];
    MCIERROR error;
    RECT rect;

    switch (message)
    {
    case WM_INITDIALOG:
        //将窗口置于屏幕中心
        GetWindowRect(hwnd, &rect);
        SetWindowPos(hwnd, NULL,
            (GetSystemMetrics(SM_CXSCREEN) - rect.right + rect.left) / 2,
            (GetSystemMetrics(SM_CYSCREEN) - rect.bottom + rect.top) / 2,
            0, 0, SWP_NOZORDER | SWP_NOSIZE);

        hwndEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
        SetFocus(hwndEdit);
        return FALSE;  //手动设置焦点,应返回FALSE。否则会设在第1个拥有WS_TABSTOP控件上

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            //计算文本框中被选中的开始和结束的行数
            SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&iCharBeg, (LPARAM)&iCharEnd);//开始位置和结束位置

            iLineBeg = SendMessage(hwndEdit, EM_LINEFROMCHAR, iCharBeg, 0);
            iLineEnd = SendMessage(hwndEdit, EM_LINEFROMCHAR, iCharEnd, 0);

            //遍历所有选中的行(如果没有选中的,则默认会读取最后一行文本)
            for (iLine = iLineBeg; iLine<=iLineEnd; iLine++)
            {
                //每次循环获取第iLine行文本,并在最末尾加上\0结束,如果是空行,则跳过。

                //缓冲区第1个字为缓冲区字符个数(含\0),EM_GETLINE消息要求的
                *(WORD*)szCommand = sizeof(szCommand) / sizeof(TCHAR);
                iLength = SendMessage(hwndEdit, EM_GETLINE, iLine, (LPARAM)szCommand);
                szCommand[iLength] = ‘\0‘;

                if (iLength == 0) continue; //跳过空行

                //发送MCI命令
                /*
                MCIERROR mciSendString(// 可用mciGetErrorString获得错误的文本描述
                                LPCTSTR lpszCommand, // 指向以null结尾的命令字符串:”命令 设备[ 参数]”
                                LPTSTR lpszReturnString,// 指向接收返回信息的缓冲区,为NULL时不返回信息
                                UINT cchReturn, // 上述缓冲区的大小
                                HANDLE hwndCallback);//在命令串中含notify时,它指定一个回调窗口的句柄,一般为NULL

                */
                error = mciSendString(szCommand, szReturn, sizeof(szReturn) / sizeof(TCHAR), hwnd);

                //显示返回的消息文本
                SetDlgItemText(hwnd, IDC_RETURN_STRING, szReturn);

                //显示错误信息
                mciGetErrorString(error, szError, sizeof(szError) / sizeof(TCHAR));
                SetDlgItemText(hwnd, IDC_ERROR_STRING, szError);
            }

            //将插入符(Caret)放在选定行中最后一行的后面
            iChar = SendMessage(hwndEdit, EM_LINEINDEX, iLineEnd, 0); //EM_LINEINDEX获取第iLineEnd行
                                                                      //第1个字符的位置索引

            iChar += SendMessage(hwndEdit, EM_LINELENGTH, iCharEnd, 0); //获取指定位置的字符(iCharEnd)所在行的字符个数
                                                                        //iChar指定该行最后一个字符的后面。
            SendMessage(hwndEdit, EM_SETSEL, iChar, iChar); //选择该行最后一个字符

            //插入回车换行符
            SendMessage(hwndEdit, EM_REPLACESEL, FALSE, (LPARAM)TEXT("\r\n"));

            SetFocus(hwndEdit);
            return TRUE;

        case IDCANCEL:
            EndDialog(hwnd, 0);
            return TRUE;

        case IDC_MAIN_EDIT: //编辑框消息
            if (HIWORD(wParam) == EN_ERRSPACE) //请求的空间无法得到满足时
            {
                MessageBox(hwnd, TEXT("Error control out of space."),
                    szAppName, MB_OK | MB_ICONINFORMATION);
                return TRUE;
            }
            break;
        }
        break;

        /*
         lParam = (LONG) lDevID;//
         wParam = (WPARAM) wFlags:
         1、MCI_NOTIFY_ABORTED      2、MCI_NOTIFY_FAILURE
         3、MCI_NOTIFY_SUCCESSFUL   4、MCI_NOTIFY_SUPERSEDED
        */
    case MM_MCINOTIFY:
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_MESSAGE), TRUE);
        wsprintf(szBuffer, TEXT("Device ID = %i"), lParam);
        SetDlgItemText(hwnd, IDC_NOTIFY_ID, szBuffer);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ID), TRUE);

        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUCCESSFUL), wParam & MCI_NOTIFY_SUCCESSFUL);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUPERSEDED), wParam & MCI_NOTIFY_SUPERSEDED);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ABORTED), wParam & MCI_NOTIFY_ABORTED);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_FAILURE), wParam & MCI_NOTIFY_FAILURE);

        SetTimer(hwnd, ID_TIMER, 5000, NULL);
        return TRUE;

    case WM_TIMER:
        KillTimer(hwnd, ID_TIMER);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_MESSAGE), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ID), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUCCESSFUL), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_SUPERSEDED), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_ABORTED), FALSE);
        EnableWindow(GetDlgItem(hwnd, IDC_NOTIFY_FAILURE), FALSE);
        return TRUE;

    case WM_SYSCOMMAND:
        switch (LOWORD(wParam))
        {
        case SC_CLOSE:
            EndDialog(hwnd, 0);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 TestMci.rc 使用
//
#define IDC_MAIN_EDIT                   1001
#define IDC_NOTIFY_ID                   1002
#define IDC_NOTIFY_MESSAGE              1003
#define IDC_NOTIFY_SUCCESSFUL           1004
#define IDC_NOTIFY_ABORTED              1005
#define IDC_NOTIFY_FAILURE              1006
#define IDC_NOTIFY_SUPERSEDED           1007
#define IDC_ERROR_STRING                1008
#define IDC_RETURN_STRING               1009

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1006
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//TestMci.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

TESTMCI DIALOGEX 0, 0, 279, 290
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "MCI Tester"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,61,266,50,14
    PUSHBUTTON      "Close",IDCANCEL,146,266,50,14
    EDITTEXT        IDC_MAIN_EDIT,16,15,248,101,ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL
    LTEXT           "Return String:",IDC_STATIC,19,118,46,8
    EDITTEXT        IDC_ERROR_STRING,144,130,120,67,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_GROUP | NOT WS_TABSTOP
    LTEXT           "Error String:",IDC_STATIC,145,119,40,8
    EDITTEXT        IDC_RETURN_STRING,18,129,120,66,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_GROUP | NOT WS_TABSTOP
    GROUPBOX        "MM_MCINOTIFY message",IDC_STATIC,18,199,247,62
    LTEXT           "",IDC_NOTIFY_ID,30,214,228,8
    LTEXT           "MCI_NOTIFY_SUCCESSFUL",IDC_NOTIFY_SUCCESSFUL,34,230,88,8,WS_DISABLED
    LTEXT           "MCI_NOTIFY_ABORTED",IDC_NOTIFY_ABORTED,163,230,78,8,WS_DISABLED
    LTEXT           "MCI_NOTIFY_FAILURE",IDC_NOTIFY_FAILURE,163,244,74,8,WS_DISABLED
    LTEXT           "MCI_NOTIFY_SUPERSEDED",IDC_NOTIFY_SUPERSEDED,34,244,89,8,WS_DISABLED
END

/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    "TESTMCI", DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 272
        TOPMARGIN, 7
        BOTTOMMARGIN, 283
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

22.2 波形音频

22.2.1 声音与波形

(1)正弦波

振幅:指声音强度

频率:指音调(人类耳朵从低音调到高音调的区间范围为20Hz~20 000Hz)

(2)人类对频率的感知是和频率的对数成正比的——频率翻倍定义为八度

(3)傅里叶级数和正弦波谐波:任何周期性的波形,都可以分解为多个正弦波,它们的频率关系是整数倍数。基波的频率代表正弦波的频率,基波也被称为一次谐波,还有二次、三次等谐波。

22.2.2 采样率和采样的大小

(1)采样率:指每秒对波形采集的样本数量,必须是声音最高频率的2倍。这被称为“奈奎斯将频率”。如人耳能听到高达20kHz的声音,为了获取人类可以听到的整个音频范围,采样率为40kHz(是声音最高频率的2倍),但考虑到经过低通滤波后有衰减效应,所以提高10%左右,即44kHz。一般取44.1kHz。

(2)采样大小:单位是位(bit)——用于表示声音的强度,决定了最小的声音和最大声音之间的差别。

以分贝来表示——dB = 20*log(A1/A2),其中A1和A2是两个声音的振幅。

  ①采样大小为1位,即A1/A2 =1,则动态范围dB=0,因为只有一个振幅。

  ②采样大小为8位,即A1/A2 =256,动态范围dB= 20*log(256),即48分贝。其范围相当于一个安静的房间与一台运行着的电动机之间的区别。

  ③采样大小为16位时,产生的动态范围为dB=20*log(65536),即96分贝,这个范围非常接近听力最低阈值和产生痛感阈值之间的区别。注意,立体声是双声道的,每个声道采样大小为16位

(3)计算未压缩音频所需占用的空间:声音长度*采样率*采样大小/8 *声道数 (单位:字节)

22.2.3 用软件生成正弦波——WaveOutXXX波形输出设备函数的使用

22.2.3.1 程序结构

(1)本程序生成20Hz~5000Hz的正弦波

(2)在脉冲编码时,采样率是一个常数,本例采用11025Hz,设要生成的正弦波的频率为X,则正弦波的每个周期内采集的样本数量为11025/x。由于每个周期的样本数量可能是小数,这样每个周期末尾都不连续。所以不能用此方法来生成正弦波。

(3)正确的方法:——相位角。第1个样本为0度,递增值为正弦波频率*2π/采样率。如用11025Hz的采样率生成1000Hz的正弦波,则每个周期大约11个样本,用这11个样本的正弦值来表示每个样本的位数(振幅)。

22.2.3.2程序用到函数或结构体

(1)WAVEFORMATEX结构体


字段


含义


WORD  wFormatTag


指定格式类型; 默认 WAVE_FORMAT_PCM = 1


WORD  nChannels


指出波形数据的通道数; 单声道为 1, 立体声为 2


DWORD nSamplesPerSec


指定样本采样率(每秒的样本数)


DWORD nAvgBytesPerSec


指定数据传输的平均速率(每秒的字节数)

PCM编码:其值等于nSamplesPerSec*nBlockAlign


WORD  nBlockAlign


指定块对齐, 块对齐是数据的最小单位。

PCM编码:其值等于nChannels*wBitsPerSample/8(单位字节),即每个样本占用的字节总数,16 位立体声 PCM 的块对齐是 4 字节(每个样本2字节, 2个通道)


WORD  wBitsPerSample


采样大小(字节),等于8位或16位


WORD  cbSize


附加信息大小; PCM 格式没这个字段,可设为0。(PCM:脉冲编码调制)

(2)waveOutOpen函数介绍


参数


含义


LPHWAVEOUT phwo


指向接收波形音频输出装置的句柄。如果fdwFlags设定为WAVE_FORMAT_QUERY时,该参数可为NULL


UINT uDeviceID


①将要被打开的波形音频输出装置的ID,该值是个索引值,范围从0至系统中波形输出设备的数量减1.

②波形输出设备数量可用WaveOutGetNumDevs查询

如果该值设为WAVE_MAPPER(-1),则会自动选择,一般会选择控制面板的多媒体程序的【音频】选项卡中【首选设备】中指定的设备,如果无法满足需求,会自动选择另一个设备。


LPWAVEFORMATEX pwfx


指向一个WAVEFORMATEX结构体的指针


DWORD dwCallback


指明接收波形输出消息的窗口句柄或回调函数。注意:回调函数是在中断时间内访问的, 必须在 DLL 中。要访问的数据都必须是在固定的数据段中。


DWORD dwCallbackInstance


如果dwCallBack指明了使用回调函数,则该参数可以指定要传递给回调函数额外的数据。


DWORD fdwFlags


CALLBACK_WINDOW、CALLBACK_FUNCTION:指定第4个参数的类型。

CALLBACK_EVENT:指定dwCallback为事件句柄

CALLBACK_THREAD:指定dwCallback为线程ID

WAVE_ALLOWSYNC:当是同步设备时必须指定

WAVE_FORMAT_QUERY:检查设备是否可以打开,而不实际去打开它。

【返回值】

  ①MMSYSERR_NOERROR     = 0;  {成功时}

  ②MMSYSERR_BADDEVICEID = 2;  {设备ID超界}

  ③MMSYSERR_ALLOCATED   = 4;  {指定的资源已被分配}

  ④MMSYSERR_NODRIVER    = 6;  {没有安装驱动程序}

  ⑤MMSYSERR_NOMEM       = 7;  {不能分配或锁定内存}

  ⑥WAVERR_BADFORMAT     = 32; {设备不支持请求的波形格式}

说明:

  ①函数调用后立即返回

  ②如果选择窗口句柄为接收波形输出消息,则会收到WM_WOM_OPEN消息,wParam为波形输出设备句柄,lParam为保留值(无用)

(3)WaveOutPrepareHeader函数及WAVEHDR结构体

  ①WaveOutPrepareHeader函数:准备波形数据,可避免WAVEHDR结构体和缓冲区从内存被交换到磁盘去。

  ②WAVEHDR结构体


字段


含义


LPSTR  lpData;


存储声音数据的缓冲区地址,要在堆里的,不能是函数的局部变量,MM_WOM_DONE消息的处理函数中还要用到。


DWORD  dwBufferLength


上述缓冲区的大小


DWORD  dwBytesRecorded


当设备用于录音时,标志已经录入的数据长度


DWORD  dwUser


给应用程序用的


DWORD  dwFlags;


波形数据的缓冲区的属性

WHDR_BEGINLOOP:指明该缓冲区是每次循环的第一个缓冲区。

WHDR_ENDLOOP:指明该缓冲区是每次循环的最后一个缓冲区。

WHDR_DONE:指明缓冲区数据播放完毕后,被设备驱动程序设为该值。


DWORD  dwLoops


循环播放的次数


struct wavehdr_tag * lpNext


保留值


DWORD  reserved


保留值

(4)waveOutWrite函数——向指定的波输形输出设备传入数据,真正开始播放声音

播放完后发送一条:MM_WOM_DONE消息,wParam为波形输出设备的句柄,lParam为指向WAVEHDR结构的指针。

(5)waveOutReset函数——停止声音播放,然后将当前位置置0,所有等待播放的数据被标志为Done,并发送MM_WOM_DONE消息

  ①waveOutReset不是立即返回的函数,要等待驻留在波形输出设备中的数据处理完才返回。

  ②在调用到返回之间的这段时间内,禁止任何线程对处理reset中的hWaveOut调用,否则会造成死锁。

(6)waveOutClose函数——发送MM_WOM_CLOSE消息

(7)waveOutUnprepareHeader——清除由 waveOutPrepareHeader 完成的准备。

//SineWave.c

#include <Windows.h>
#include <math.h>
#include "resource.h"

#pragma comment(lib,"WINMM.lib")

#define SAMPLE_RATE      11025
#define FREQ_MIN         20
#define FREQ_MAX         5000
#define FREQ_INIT         440
#define PI                 3.1415926
#define OUT_BUFFER_SIZE  4096

TCHAR szAppName[] = TEXT("SineWave");

BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int nCmdShow)
{
    if (-1==DialogBox(hInstance,szAppName,NULL,DlgProc))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
            szAppName, MB_ICONERROR);
    }
    return 0;
}

void FillBuffer(PBYTE pBuffer, int iFreq)
{
    static double fAngle;
    int i;

    //用相位角来生成正弦波声音数据,声音强度(振幅)从0-254
    for  (i= 0; i<OUT_BUFFER_SIZE; i++)
    {
        pBuffer[i] = (BYTE)(127+127*sin(fAngle));

        //相位增量为:正弦波频率*2π/采样率
        fAngle += 2 * PI * iFreq / SAMPLE_RATE;

        if (fAngle>2 * PI)
            fAngle -= 2 * PI; //注意这里不能重置为0,因为要让声音连续。
    }
}

BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static BOOL bShutOff, bClosing;
    static HWND hwndScroll;
    static int iFreq = FREQ_INIT;
    static PBYTE  pBuffer1, pBuffer2; //用双缓冲播放声音
    static PWAVEHDR pWaveHdr1, pWaveHdr2;
    static WAVEFORMATEX waveformat;
    int iDummy;

    static HWAVEOUT hWaveOut;

    RECT  rect;
    switch (message)
    {
    case WM_INITDIALOG:

        //将窗口置于屏幕中央
        GetWindowRect(hwnd, &rect);
        SetWindowPos(hwnd, NULL, (GetSystemMetrics(SM_CXSCREEN) - rect.right + rect.left) / 2,
                                 (GetSystemMetrics(SM_CYSCREEN) - rect.bottom + rect.top) / 2,
                                 rect.right - rect.left, rect.bottom - rect.top, SWP_SHOWWINDOW);

        hwndScroll = GetDlgItem(hwnd, IDC_SCROLL);
        SetScrollRange(hwndScroll, SB_CTL, FREQ_MIN, FREQ_MAX, FALSE);
        SetScrollPos(hwndScroll, SB_CTL, FREQ_INIT, TRUE);
        SetDlgItemInt(hwnd, IDC_TEXT, FREQ_INIT, FALSE);

        //SendMessage(hwnd, DM_SETDEFID, (WPARAM)IDC_SCROLL, 0); //测试用的,改变对话框回车键默认行为
        return TRUE;

    case WM_HSCROLL:
        switch (LOWORD(wParam))
        {
        case SB_LINELEFT:       iFreq -= 1; break;
        case SB_LINERIGHT:        iFreq += 1; break;
        case SB_PAGELEFT:       iFreq /= 2; break;//降低1个八度
        case SB_PAGERIGHT:      iFreq *= 2; break;//升高1个八度
        case SB_THUMBTRACK:     iFreq = HIWORD(wParam);break;
        case SB_TOP:
            GetScrollRange(hwndScroll, SB_CTL, &iFreq, &iDummy); //将最小值放入iFreq,最大放入iDummy
            break;
        case SB_BOTTOM:
            GetScrollRange(hwndScroll, SB_CTL, &iDummy, &iFreq); //将最大值放入iFreq
            break;
        }

        iFreq = max(FREQ_MIN, min(FREQ_MAX, iFreq));

        SetScrollPos(hwndScroll, SB_CTL, iFreq, TRUE);
        SetDlgItemInt(hwnd, IDC_TEXT, iFreq, FALSE);

        return TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDC_ONOFF:

            //如果是要打开波形音频,则hWaveOut==NULL
            if (hWaveOut == NULL)
            {
                //为2个声音头结构和声音数据分配内存
                pWaveHdr1 = malloc(sizeof(WAVEHDR));
                pWaveHdr2 = malloc(sizeof(WAVEHDR));
                pBuffer1  = malloc(OUT_BUFFER_SIZE);
                pBuffer2  = malloc(OUT_BUFFER_SIZE);

                if (!pWaveHdr1 || !pWaveHdr2 || !pBuffer1 ||!pBuffer2)
                {
                    if (!pWaveHdr1) free(pWaveHdr1);
                    if (!pWaveHdr2) free(pWaveHdr2);
                    if (!pBuffer1)  free(pBuffer1);
                    if (!pBuffer2)  free(pBuffer2);

                    MessageBeep(MB_ICONEXCLAMATION);
                    MessageBox(hwnd, TEXT("Error allocating memory!"),
                                szAppName, MB_ICONEXCLAMATION | MB_OK);
                    return TRUE;
                }

                //关闭按钮按下标志
                bShutOff = FALSE;

                //打开波形输出设备
                waveformat.wFormatTag        = WAVE_FORMAT_PCM;
                waveformat.nChannels        = 1;//单声道
                waveformat.nSamplesPerSec   = SAMPLE_RATE;
                waveformat.nAvgBytesPerSec    = SAMPLE_RATE;//nSamplesPerSec*nBlockAlign
                waveformat.nBlockAlign        = 1; //每个样本所占的字节数,nChannels*wBitsPerSample/8
                waveformat.wBitsPerSample    = 8; //每个样本大小(位数)(单位:位)
                waveformat.cbSize            = 0;

                //自动选择波形输出设备,并打开。消息发送指定的窗口
                if (MMSYSERR_NOERROR != waveOutOpen(&hWaveOut , WAVE_MAPPER , &waveformat,
                                                    (DWORD)hwnd, 0, CALLBACK_WINDOW ))
                {
                    free(pWaveHdr1);
                    free(pWaveHdr2);
                    free(pBuffer1);
                    free(pBuffer2);

                    hWaveOut = NULL;
                    MessageBeep(MB_ICONEXCLAMATION);
                    MessageBox(hwnd, TEXT("Error opening waveform audio device!"),
                        szAppName, MB_ICONEXCLAMATION | MB_OK);
                    return TRUE;
                }

                //创建波形头结构并做好输出准备
                pWaveHdr1->lpData            = pBuffer1;       //指向声音数据
                pWaveHdr1->dwBufferLength    = OUT_BUFFER_SIZE;//声音数据的大小
                pWaveHdr1->dwBytesRecorded  = 0;
                pWaveHdr1->dwUser            = 0;
                pWaveHdr1->dwFlags            = 0;
                pWaveHdr1->dwLoops            = 1;
                pWaveHdr1->lpNext            = NULL;
                pWaveHdr1->reserved            = 0;

                //防止WAVEHDR和缓冲区从内存被交换到磁盘去
                waveOutPrepareHeader(hWaveOut, pWaveHdr1, sizeof(WAVEHDR));

                pWaveHdr2->lpData            = pBuffer2;       //指向声音数据
                pWaveHdr2->dwBufferLength    = OUT_BUFFER_SIZE;//声音数据的大小
                pWaveHdr2->dwBytesRecorded    = 0;
                pWaveHdr2->dwUser            = 0;
                pWaveHdr2->dwFlags            = 0;
                pWaveHdr2->dwLoops            = 1;
                pWaveHdr2->lpNext            = NULL;
                pWaveHdr2->reserved            = 0;

                //防止WAVEHDR和缓冲区从内存被交换到磁盘去
                waveOutPrepareHeader(hWaveOut, pWaveHdr2, sizeof(WAVEHDR));
            }
            else  //如果要关闭波形文件,则输出设备复位
            {
                bShutOff = TRUE;
                waveOutReset(hWaveOut);//停止播放
            }
            return TRUE;
        }
        break;

    case MM_WOM_OPEN: //调用waveOutOpen函数后,收到该消息
        SetDlgItemText(hwnd, IDC_ONOFF, TEXT("Turn Off"));

        //将两个缓冲区的数据写入波形输出设备中
        FillBuffer(pBuffer1, iFreq);
        waveOutWrite(hWaveOut, pWaveHdr1, sizeof(WAVEHDR)); //真正的播放开始

        FillBuffer(pBuffer2, iFreq);  //双缓冲技术,可以保证发出的声音是连续的。
        waveOutWrite(hWaveOut, pWaveHdr2, sizeof(WAVEHDR));
        return TRUE;

    case MM_WOM_DONE:  //当缓冲区数据播放完毕或waveOutReset时,收到该消息
        if (bShutOff)
        {
            waveOutClose(hWaveOut); //按下Turn Off按钮
            return TRUE;
        }

        //如果缓冲区的数据播放完毕,则
        //生成新的声音数据,并发送给波输形输出设备
        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));

        if (pWaveHdr1) free(pWaveHdr1);
        if (pWaveHdr2) free(pWaveHdr2);
        if (pBuffer1)  free(pBuffer1);
        if (pBuffer2)  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;
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 SineWave.rc 使用
//
#define IDC_SCROLL                      1001
#define IDC_TEXT                        1002
#define IDC_ONOFF                       1003

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1004
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//SineWave.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

SINEWAVE DIALOGEX 0, 0, 247, 62
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Sine Wave Generator"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "Turn On",IDC_ONOFF,106,44,50,14
    SCROLLBAR       IDC_SCROLL,15,12,189,11,WS_TABSTOP
    RTEXT           "440",IDC_TEXT,202,13,25,8
    LTEXT           "Hz",IDC_STATIC,229,13,9,8,WS_TABSTOP
END

/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    "SINEWAVE", DIALOG
    BEGIN
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
时间: 2024-10-30 04:47:19

第22章 声音与音乐(1)的相关文章

第22章 声音与音乐(3)

22.2.7 波形音频文件格式——WAV音频格式 偏移量 字节 数据 0000 4 字符串——“RIFF”(资源交换文件格式),文件由很多数据“块”组成,每一块数据由块的名称和块长组成.名称由4个ASCII字符组成.块长不包含块的名称和块长这个字段本身所需的8个字节,也就是说是“块”的实际长度. 0004 4 波形数据块的大小(文件大小-8)即从偏移0008开始到文件结束的大小 0008 4 字符串——“WAVE” 000C 4 字符串——“fmt ”(注意:fmt后面有一空格) 0010 4

第22章 声音与音乐(2)

22.2.4 数字录音机——WaveInXXX波形输入设备函数的使用 (1)录音常用的API ①waveInOpen(打开一个音频输入设备) ②waveInPrepareHeader(为一个即将在waveInAddBuffer中调用的输入缓冲区准备头部) ③waveInAddBuffer(添加一个输入用的数据缓冲区) ④waveInStart(开始录音) ⑤waveInReset(重置输入设备以便重新录音或关闭录音) ⑥waveInClose(关闭音频输入设备)等几个. (2)windows w

4.20日第13次作业.,20章战略管理,21章业务流程管理和重组,22章知识管理,高项,29-田哲琦

4.20日第13次作业.,20章战略管理,21章业务流程管理和重组,22章知识管理,高项,29-田哲琦 20章.战略管理1.企业战略的特点有哪些?P420-421 答:1).全局性 2).长远性 3).抗争性 4).纲领性 2.企业战略决策的特点有哪些?P421答:1).决策的对象是复杂的,很难把握住它的结构,并且是没有先例的,对其处理上也是没有经验可循. 2).面对的问题常常是突发性的.难以预料的.所依靠的是来自外部的关于未来如何变化的很少的情报. 3).决策的性质直接涉及到企业的前途. 4)

第22章 CSS渐变效果

第 22章 CSS3渐变效果学习要点:1.线性渐变2.径向渐变 本章主要探讨 HTML5中 CSS3背景渐变功能,主要有两种渐变方式:线性渐变和径向(放射性)渐变. 一.线性渐变 CSS3提供了linear-gradient属性实现背景颜色的渐变功能.在以前,这种效果必须采用图片才能实现的.首先,我们看一下它的样式表,如下:linear-gradient(方位,起始色,末尾色) 方位: 可选参数,渐变的方位.可以使用的值有:to top.to topright.to right.to botto

4.11日第11次作业,21章法律法规与标准,22章职业道德规范

4.11日第11次作业,21章法律法规与标准,22章职业道德规范 21章:法律法规和标准规范 22章:职业道德规范 1.中国标准划分为哪四个层次?要求最低的是哪个?P498 答:<中华人民共和国标准化法>将标准划分为4个层次:即国家标准.行业标准.地方标准和企业标准.其中国标最低,企业标准最高. 2.国家标准的制订程序包括哪些?P499中间 答:国家标准的制定有一套正常程序,每一个过程都要按部就班地完成,这个过程分为前期准备.立项.起草.征求意见.审查.批准.出版.复审和废止9个阶段. 3.I

第22章 变易算法

  第22章 变易算法  Modifying  sequence operations    22.1 元素复制copycopy  Copy range of elements (function template)      22.2 反向复制copy_backwardcopy_backward  Copy range of elements backwards (function template)      22.3 元素交换swapswap  Exchange values of two

设计模式之第22章-组合模式(Java实现)

设计模式之第22章-组合模式(Java实现) “鱼哥,有没有什么模式是用来处理树形的“部分与整体”的层次结构的啊.”“当然”“没有?”“有啊.别急,一会人就到了.” 组合模式之自我介绍 “请问你是?怎么什么都不说就直接上来了.”“本式行不更名坐不改姓,就是组合模式来着,此次受作者之邀来讲讲我的前世今生来着.”“哦,你就是组合模式啊,久仰久仰.”“失敬失敬.”恩,首先我先说下定义:Compose objects into tree structure to represent part-whole

第22章 职责链模式(Chain of Responsibility)

原文 第22章 职责链模式(Chain of Responsibility) 职责链模式 导读:职责链模式是一个既简单又复杂的设计模式,刚开始学习这个设计模式的时候光示例都看了好几遍.就为了理清里面的逻辑走向.是个值得慢慢品味的设计模式 概述:   使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止. 结构图:          代码举例:公司请假   1 2 3 4 5 6 7 8 9 10 11 12

MySQL必知应会-第22章-使用视图

第22章-使用视图 本章将介绍视图究竟是什么,它们怎样工作,何时使用它们.我们还将看到如何利用视图简化前面章节中执行的某些SQL操作. 22.1 视图 需要MySQL 5 MySQL 5添加了对视图的支持.因此,本章内容适用于MySQL 5及以后的版本.视图是虚拟的表.与包含数据的表不一样,视图只包含使用时动态检索数据的查询.理解视图的最好方法是看一个例子.第15章中用下面的SELECT语句从3个表中检索数据: 此查询用来检索订购了某个特定产品的客户.任何需要这个数据的人都必须理解相关表的结构,