概要

オーディオの低レベルAPIを使用してメモリ上に作成した正弦波データを再生します。

正弦波とは三角関数のsinで表せる波形で以下のような形をしています。
y 0 π SVGの代替画像
本ソフトでは16bitの分解能としていますので最大振幅を±32767としており波形は以下の式で表せます。
\omega =2 \pi f
y=32767 \sin (\omega t)
周波数成分は基本波成分のみで混じりのない純音ですので澄んだ音です。
ラジオボックスにより周波数の切り替えおよびプッシュボタンで再生と停止ができます。
メモリ上には1周期の正弦波データを作成しており、ダブルバッファにより音が途切れないようにしています。
バッファリング処理が煩雑になるので簡単なC++のクラスを作成しています。
正弦波の周波数は配列freq_tblで定義しています。この配列の値を変えれば好きな周波数を設定できます。
Waveフォーマットは48kHz 16bit モノラルで設定しています。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit
サウンドの設定がステレオ以外の場合、音が出ない場合があります。

プログラムソースの概要

OUT_WAVEクラス

バッファが1個の場合、デバイスへデータを送信し終わった後にバッファのデータを作成しそのバッファを再生することになり、音が途切れる可能性があります。バッファを2個用意すれば、バッファーデーターをデバイスに送信している途中にもう一方のバッファのデータを作成できるので音が途切れません。
オーディオ低レベル出力関数のダブルバッファリング等の処理をカプセル化するために作成しています。
バッファ投入後に発生するメッセージを受け取るコールバック関数をfriend関数として定義しており、WOM_DONEメッセージ受信時に空いているバッファにwaveデータを投入します。WOM_DONEメッセージはWaveOutデバイスの再生を停止させるwaveOutReset APIでも発生するのでwaveOutWrite APIとwaveOutReset APIを区別できるようにAPI実行前にメンバ変数resetを設定しておき、WOM_DONEメッセージ発生時にメンバー変数resetの値により再度再生するかどうかを決定します。

openメンバー関数

WAVEFORMATEX構造体を希望のサンプルレート・ビット数・チャンネル数で初期化しwaveOutOpen APIでWaveOutデバイスを開きハンドルをhWaveOutメンバーに保存します。
Waveデータのバッファ等を管理するための情報を格納しているWAVEHDR構造体を必要なバッファ数を確保します。
Waveデータのバッファをバッファ数分確保し、WAVEHDR構造体のlpDataメンバーにバッファアドレスを設定します。
バッファ数はbufCountの設定しています。

sinMakeメンバー関数

使用するバッファ全部について1周期の正弦波のwaveデータを作成します。
正弦波はsin関数で作成します。Waveの最大振幅は、32767にしています。
再生するバイト数をWAVEHDR構造体のdwBufferLengthメンバーに設定します。
WAVEHDR構造体のdwFlagsメンバーに連続再生を指定します。
ループ再生の回数は、1周期を連続再生した場合において約0.4秒になるように回数を計算しWAVEHDR構造体のdwLoopsメンバーにに設定します。
WOM_DONEメッセージはループ再生を指定しているときは、バッファデータごとに発生するのではなく、ループの最後の再生データをデバイスに送信し終えた後に発生します。本プログラムの場合、約0.4秒置きに発生することになります。

playメンバー関数

バッファ内の正弦波データの連続再生を開始します。
バッファ1回分のデーターの投入が終わるとコールバック関数aveOutProcが呼び出されます。
resetメンバー変数がfalseの場合、継続して再生を行う必要があるので、waveOutPrepareHeaderによりバッファの情報をデバイスに登録しwaveOutWrite APIによりwaveデーターを投入します。
複数のバッファがある場合は、全部のバッファに上記の処理を行います。
waveOutWrite APIは再生が終了した時点ではなくデバイスにデータを送信し終えたときに終わります。

stopメンバー関数

resetメンバーにtrueをセットし、再生中にWOM_DONEメッセージ受信時にバッファの再生を行わないように設定します。
waveOutReset APIを実行し、再生を停止します。

closeメンバー関数

stopメンバー関数を実行し再生を停止します。
waveOutUnprepareHeader APIによりwaveOutPrepareHeader APIにより登録したバッファをクリーンアップします。
openメンバー関数により確保したバッファ等を解放します。

_tWinMain

Windowsから最初に_tWinMain関数が呼び出されます。
OUT_WAVE::open関数により48kHz モノラルでWaveOutデバイスを開きます。
DialogBox APIよりメインダイアログボックスを作成します。 OUT_WAVE::close関数によりWaveOutデバイスを閉じます。

DlgProc1ダイアログボックスのプロシージャー

メインダイアログボックスのプロシージャーです。

case WM_INITDIALOG:

ダイアログボックスの初期化時に呼び出されます。
グローバル変数freq_tblのメモリ上のバイト数から要素数を計算し変数maxに格納します。
ラジオボックスのテキストを設定するために以下の処理をfreq_tblの要素数行います。

freq_tblから1個の要素を取り出し_stprintf_s関数により文字列化しSetWindowText APIによりラジオボックスのテキストを設定します。
freq_tblの要素とスタティック変数freqと比較し同一の場合は、そのラジオボックスにBM_SETCHECKメッセージを送信しチェックを入れます。

EnableWindow APIによりSTOPプッシュボタンを無効にします。
ラジオボタンやプッシュボタンのウィンドウハンドルを取得するためにGetDlgItem APIを使用します。

case WM_COMMAND:

ダイアログボックスの子ウィンドウ(ダイアログボックスに張り付けられているエディットボックスやプッシュボタン等)から発生したメッセージが届きます。
ダイアログボックスプロシージャーの第3引数であるWPARAMの下位16bitがコントロールのID番号、その上位16bitには通知内容が格納されています。
LOWORDマクロによりWPARAMの下位16bitを取り出し、switchステートメントによりコントロールごとに処理を振り分けます
case IDC_PLAY_BUTTON:
発音(P)プッシュボタンのID番号がIDC_PLAY_BUTTONです。
発音(P)プッシュボタンをクリックするとこのメッセージが発生します。
EnableWindow APIにより発音(P)プッシュボタンを無効、消音(S)プッシュボタンを有効にします。
OUT_WAVE::sinMake関数によりバッファ上に1周期の正弦波waveデータを作成します。
OUT_WAVE::play関数によりバッファ上のwaveデータの再生を開始します。
case IDC_STOP_BUTTON:
消音(S)プッシュボタンのID番号がIDC_STOP_BUTTONです。
消音(S)プッシュボタンをクリックするとこのメッセージが発生します。
EnableWindow APIにより発音(P)プッシュボタンを有効、消音(S)プッシュボタンを無効にします。
OUT_WAVE::stop関数によりバッファ上のwaveデータの再生を停止します。
case IDOK:
終了(E)プッシュボタンのID番号がIDOKです。 終了(E)プッシュボタンをクリックするとこのメッセージが発生します。 OUT_WAVE::close関数によりWaveOutデバイスを閉じます。
EndDialog APIを呼び出し、ダイアログボックスを終了させます。 このAPIの第2引数は、ダイアログボックスが終了し、DialogBox APIが返す値となります。
case IDCANCEL:
ESCキーまたは閉じるボタンをクリックするとこのメッセージが発生します。
OUT_WAVE::close関数によりWaveOutデバイスを閉じます。
EndDialog APIを呼び出し、ダイアログボックスを終了させます。 このAPIの第2引数は、ダイアログボックスが終了し、DialogBox APIが返す値となります。
default:
wParamの下位ワードを取り出してラジオボタンのIDの場合以下の処理を行います。

wParamの上位ワードを取り出してBN_CLICKEDメッセージの場合、該当するラジオボタンの周波数を取得しスタティック変数freqに格納します。
BN_CLICKEDメッセージ以外の場合でOUT_WAVE::reset変数がfalseの場合は、再生中ですのでOUT_WAVE::stop関数により再生を止め、 OUT_WAVE::sinMake関数によりバッファ上に1周期の正弦波waveデータを作成します。
OUT_WAVE::play関数によりバッファ上のwaveデータの再生を開始します。

プログラムソース

sinwave.cpp

//       正弦波出力

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <mmsystem.h>
#include <math.h>
#include "resource.h"

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

#ifndef M_PI
        #define M_PI 3.14159265358979323846
#endif

LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

typedef void* VOID_PTR;

//      ラジオボックスの文字列および周波数の設定
int freq_tbl[]={
10,
22,
33,
47,
68,
100,
220,
330,
470,
680,
1000,
2200,
3300,
4700,
6800,
10000,
22000,
};


class OUT_WAVE{
        int bufCount;   //      再生するバイト数
        int sz;                 //      バッファ長
        HWAVEOUT hWaveOut;
        WAVEFORMATEX wfe;
        WAVEHDR* whdr;
        short** lpWave; //      バッファ
        friend void CALLBACK WaveOutProc(HWAVEOUT hWaveOut, UINT msg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2){
                switch (msg){
                case WOM_OPEN:
                        break;
                case WOM_CLOSE:
                        break;
                case WOM_DONE:{ //      バッファ再生終了
                                OUT_WAVE& wav = *(OUT_WAVE*)dwInstance;
                                if (wav.reset == false){
                                        for (int i = 0; i < wav.bufCount; i++){
                                                if ((wav.whdr[i].dwFlags & WHDR_INQUEUE) == 0){     //      バッファ0が空
                                                        waveOutWrite(hWaveOut, &wav.whdr[i], sizeof(WAVEHDR));
                                                }
                                        }
                                }
                                break;
                        }
                }
        }
public:
        bool reset;
        void open(int rate, int sec){   //      wave出力デバイスを開く
                bufCount = 2;   //      使用するバッファ数
                reset = true;
                wfe.wFormatTag = WAVE_FORMAT_PCM;
                wfe.nChannels = 1;      //      モノラル    
                wfe.wBitsPerSample = 16;        //      分解能16bit
                wfe.nBlockAlign = wfe.nChannels*wfe.wBitsPerSample / 8;
                wfe.nSamplesPerSec = rate;      //      サンプルレート
                wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign;
                waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfe, (DWORD_PTR)WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
                sz = wfe.nAvgBytesPerSec*sec;
                whdr =new WAVEHDR[bufCount];
                lpWave = (short**)new VOID_PTR[bufCount];
                for (int n = 0; n < bufCount; n++){
                        lpWave[n] = new short[wfe.nAvgBytesPerSec*sec];
                        whdr[n].lpData = (LPSTR)lpWave[n];
                }
        }
        void sinMake(int freq){ //      1周期の正弦波データ作成
                double qstep = 2 * M_PI *freq / wfe.nSamplesPerSec;
                double rq = 2;
                double q = 0;
                int n;
                for (n = 0; n < sz; n++){
                        int d;
                        d = int(32767 * sin(q));
                        for (int i = 0; i < bufCount; i++)
                                lpWave[i][n] = d;
                        q += qstep;
                        if (rq < 0 && 0 <= q){
                                break;
                        }
                        rq = q;
                }
                for (int i = 0; i < bufCount; i++){
                        whdr[i].dwBufferLength = n * 2; //      バッファバイト数
                        whdr[i].dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
                        whdr[i].dwLoops = 20000 * freq / wfe.nSamplesPerSec;
                        whdr[i].dwUser = 0;
                }
        }
        void play(void){        //      連続再生    
                reset = false;
                for (int i = 0; i < bufCount; i++){
                        waveOutPrepareHeader(hWaveOut, &whdr[i], sizeof(WAVEHDR));
                        waveOutWrite(hWaveOut, &whdr[i], sizeof(WAVEHDR));
                }
        }
        void stop(void){        //      停止
                reset = true;
                waveOutReset(hWaveOut);
        }
        void close(void){       //      wave出力デバイスを閉じる
                stop();
                for (int n = 0; n < bufCount; n++){
                        waveOutUnprepareHeader(hWaveOut, &whdr[n], sizeof(WAVEHDR));
                        delete[]lpWave[n];
                }
                delete[]whdr;
                delete[]lpWave;
        }
};

OUT_WAVE wav;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst, TCHAR* lpszCmdLine, int nCmdShow){
        wav.open( 48000 , 1 );
        DialogBox(hInstance, TEXT("DLG1"), 0, (DLGPROC)DlgProc1);
        wav.close();
        return 0;
}

//      ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
        static unsigned freq = 1000;
        static int max=0;
        int n;
        TCHAR buf[16];
        switch (msg) {
        case WM_INITDIALOG:
                max = sizeof(::freq_tbl) / sizeof(::freq_tbl[0]);
                for (n = 0; n < max; n++){
                        _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("%dHz"), ::freq_tbl[n]);
                        SetWindowText(GetDlgItem(hDlg, IDC_RADIOBOX + n), buf);
                        if (freq == ::freq_tbl[n]){
                                SendMessage(GetDlgItem(hDlg, IDC_RADIOBOX+n), BM_SETCHECK, BST_CHECKED, 0);
                        }
                }
                EnableWindow(GetDlgItem(hDlg, IDC_STOP_BUTTON), FALSE);
                return TRUE;
        case WM_COMMAND:
                switch (LOWORD(wParam)) {
                case IDC_PLAY_BUTTON:
                        EnableWindow(GetDlgItem(hDlg, IDC_PLAY_BUTTON), FALSE);
                        EnableWindow(GetDlgItem(hDlg, IDC_STOP_BUTTON), TRUE);
                        wav.sinMake(freq);
                        wav.play();
                        return FALSE;
                case IDC_STOP_BUTTON:
                        EnableWindow(GetDlgItem(hDlg, IDC_PLAY_BUTTON), TRUE);
                        EnableWindow(GetDlgItem(hDlg, IDC_STOP_BUTTON), FALSE);
                        wav.stop();
                        return FALSE;
                case IDOK:
                        EndDialog(hDlg, TRUE);
                        return TRUE;
                case IDCANCEL:
                        EndDialog(hDlg, FALSE);
                        return FALSE;
                default:
                        if (IDC_RADIOBOX <= LOWORD(wParam) && LOWORD(wParam)<(IDC_RADIOBOX+max)){
                                if (HIWORD(wParam) == BN_CLICKED){
                                        freq = ::freq_tbl[LOWORD(wParam) - IDC_RADIOBOX];
                                }
                                if (wav.reset == false){
                                        wav.stop();
                                        wav.sinMake(freq);
                                        wav.play();
                                }
                        }
                        return FALSE;
                }
        default:
                return FALSE;
        }
        return TRUE;
}

resource.h

#define  IDC_RADIOBOX    100

#define IDC_PLAY_BUTTON         200
#define IDC_STOP_BUTTON         201
#define IDC_GROUPBOX104         202

resource.rc

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

dlg1 DIALOG DISCARDABLE 0, 0, 291, 123
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT
CAPTION "正弦波出力"
FONT 9, "MS 明朝"
{
 CONTROL "周波数", IDC_GROUPBOX104, "BUTTON", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 12, 12, 267, 78

 CONTROL "", IDC_RADIOBOX + 0, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 19, 26, 48, 14
 CONTROL "", IDC_RADIOBOX + 1, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 68, 26, 48, 14
 CONTROL "", IDC_RADIOBOX + 2, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 117, 26, 48, 14
 CONTROL "", IDC_RADIOBOX + 3, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 166, 26, 48, 14
 CONTROL "", IDC_RADIOBOX + 4, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 215, 26, 48, 14
 CONTROL "", IDC_RADIOBOX + 5, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 19, 40, 48, 14
 CONTROL "", IDC_RADIOBOX + 6, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 68, 40, 48, 14
 CONTROL "", IDC_RADIOBOX + 7, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 117, 40, 48, 14
 CONTROL "", IDC_RADIOBOX + 8, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 166, 40, 48, 14
 CONTROL "", IDC_RADIOBOX + 9, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 215, 40, 48, 14
 CONTROL "", IDC_RADIOBOX + 10, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 19, 54, 48, 14
 CONTROL "", IDC_RADIOBOX + 11, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 68, 54, 48, 14
 CONTROL "", IDC_RADIOBOX + 12, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 117, 54, 48, 14
 CONTROL "", IDC_RADIOBOX + 13, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 166, 54, 48, 14
 CONTROL "", IDC_RADIOBOX + 14, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 215, 54, 48, 14
 CONTROL "", IDC_RADIOBOX + 15, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 19, 68, 48, 14
 CONTROL "", IDC_RADIOBOX + 16, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 68, 68, 48, 14
 
 CONTROL "発音(&P)", IDC_PLAY_BUTTON, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 37, 102, 54, 14
 CONTROL "消音(&S)", IDC_STOP_BUTTON, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 98, 102, 54, 14
 CONTROL "終了(&E)", IDCANCEL, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 159, 102, 54, 14
}

ソースファイルと実行ファイルのダウンロード

ダウンロード sinwave.zip(48.7kByte)
ZIPファイルに含まれるファイル
sinwave.cpp
resource.h
resource.rc
sinwave.exe