概要

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

DTMFとはのDual-Tone Multi-Frequency略であり高音と低音を2つ組み合わせる方法です。
高音と低音が各4つの周波数がありますので16通りの表現ができます。
高音(Hz)
1209133614771633
低音(Hz)697123A
770456B
852789C
941*0#D
本ソフトでは正弦波を2つ加算しております。
正弦波とは三角関数のsinで表せる波形で以下のような形をしています。
y 0 π SVGの代替画像
本ソフトでは16bitの分解能としており2つの信号を加算するので1個の信号は最大振幅を±16383としており波形は以下の式で表せます。



y 0 SVGの代替画像
上図の青線が低音、緑線が高音、赤線が合成音です。
プッシュボタンを押した時に、0.2秒発音するだけですが、正弦波出力のクラスを流用している(正弦波出力(低レベルAPI))ので少し複雑なコードになっています。
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クラス

オーディオ低レベル出力関数の処理をカプセル化するために作成しています。

open2メンバー関数

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

dtmfMakeメンバー関数

使用するバッファ全部について1周期の正弦波のwaveデータを作成します。
正弦波はsin関数で作成します。Waveの最大振幅は、各正弦波ごとに16383にしています。
再生するバイト数をWAVEHDR構造体のdwBufferLengthメンバーに設定します。
WAVEHDR構造体のdwFlagsメンバーに再生を指定します。
ループ再生の回数は、1回に設定します。

ownPlayメンバー関数

バッファ内のデータの1回再生します。
waveOutPrepareHeaderによりバッファの情報をデバイスに登録しwaveOutWrite APIによりwaveデーターを投入します。
複数のバッファがある場合は、全部のバッファに上記の処理を行います。
waveOutWrite APIは再生が終了した時点ではなくデバイスにデータを送信し終えたときに終わります。

stopメンバー関数

resetメンバーにtrueをセットします。
waveOutReset APIを実行し、再生を停止します。

closeメンバー関数

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

_tWinMain

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

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

メインダイアログボックスのプロシージャーです。
各プッシュボタンのID番号はWM_USER+キャラクタコードとしていますのでWM_USERを引けばそのままキャラクターコードとして扱えます。

case WM_COMMAND:

ダイアログボックスの子ウィンドウ(ダイアログボックスに張り付けられているエディットボックスやプッシュボタン等)から発生したメッセージが届きます。
ダイアログボックスプロシージャーの第3引数であるWPARAMの下位16bitがコントロールのID番号、その上位16bitには通知内容が格納されています。
LOWORDマクロによりWPARAMの下位16bitを取り出し、switchステートメントによりコントロールごとに処理を振り分けます
case IDCANCEL:
ESCキーまたは閉じるボタンをクリックするとこのメッセージが発生します。
OUT_WAVE::close関数によりWaveOutデバイスを閉じます。
EndDialog APIを呼び出し、ダイアログボックスを終了させます。 このAPIの第2引数は、ダイアログボックスが終了し、DialogBox APIが返す値となります。
default:
wParamの下位ワードを取り出してIDを取得します。
isDtmfCharマクロによりプッシュボタンのIDかどうかを確認します。
OUT_WAVE::stop関数により再生をとめます。
Char2Dtmf関数によりキャラクターコードから高音と低音の周波数を取得します。
OUT_WAVE::dtmfMake関数によりバッファ上にDTMFのwaveデータを作成します。
OUT_WAVE::ownPlay関数によりバッファ上のwaveデータの再生を開始します。

isDtmfCharマクロ

resource.hて定義しており、 引数のキャラクターコードがDTMFである場合は1、それ以外の場合は0を返します。
本プログラムでは、WM_COMMANDメッセージを使用するコントロールが限られるため少し楽をして、文字コードが#~Dの間であればDTMFとみなしてます。

Char2Dtmf関数

DTMFのキャラクターコードに対する高音と低音の周波数を返します。
文字列strの最初要素から順番にキャラクターコードと比較し一致した場合、その文字列の要素位置をnとし、nを4で割ったときの余りを高音のインデックス、nを4で割った値を低音のインデックスとしてテーブルを参照し周波数を取得します。
一致するキャラクタコードがない場合は-1を返します。

プログラムソース

dtmf.cpp

//	DTMF出力

#include <windows.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);

//	キャラクターコードに対応するDTMF信号の周波数を取得
void Char2Dtmf(int c, int* f1, int* f2);

class OUT_WAVE{
	int bufCount;	//	再生するバイト数
	int sz;			//	バッファ長
	HWAVEOUT hWaveOut;
	WAVEFORMATEX wfe;
	WAVEHDR* whdr;
	short** lpWave;	//	バッファ
public:
	bool reset;
	void open2(int rate, int sec){	//	wave出力デバイスを開く
		bufCount = 1;	//	使用するバッファ数
		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)0, (DWORD_PTR)this, CALLBACK_FUNCTION);
		sz = wfe.nAvgBytesPerSec*sec/1000;
		whdr =new WAVEHDR[bufCount];
		lpWave = (short**)new DWORD_PTR[bufCount];
		for (int n = 0; n < bufCount; n++){
			lpWave[n] = new short[wfe.nAvgBytesPerSec*sec];
			whdr[n].lpData = (LPSTR)lpWave[n];
		}
	}
	void dtmfMake(int freq1,int freq2){	//	DTMFデータ作成
		double qstep1 = 2 * M_PI *freq1 / wfe.nSamplesPerSec;
		double qstep2 = 2 * M_PI *freq2 / wfe.nSamplesPerSec;
		double q1 = 0;
		double q2 = 0;
		int n;
		for (n = 0; n < sz; n++){
			int d;
			d = int(16383 * sin(q1)) + int(16383 * sin(q2));
			for (int i = 0; i < bufCount; i++)
				lpWave[i][n] = d;
			q1 += qstep1;
			q2 += qstep2;
		}
		for (int i = 0; i < bufCount; i++){
			whdr[i].dwBufferLength = n * 2;	//	バッファバイト数
			whdr[i].dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
			whdr[i].dwLoops = 0;
			whdr[i].dwUser = 0;
		}
	}
	void ownPlay(void){	//	ワンショット再生
		reset = true;
		for (int i = 0; i < bufCount; i++){
			whdr[i].dwLoops = 0;
			whdr[i].dwFlags = 0;
			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.open2( 48000 , 200 );
	DialogBox(hInstance, TEXT("DLG1"), 0, (DLGPROC)DlgProc1);
	wav.close();
	return 0;
}

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

LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	int freq1;
	int freq2;
	switch (msg) {

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDCANCEL:
			EndDialog(hDlg, FALSE);
			return FALSE;
		default:
			if (isDtmfChar(LOWORD(wParam) - WM_USER) ){
				wav.stop();
				Char2Dtmf(LOWORD(wParam) - WM_USER,&freq1,&freq2);
				wav.dtmfMake(freq1,freq2);
				wav.ownPlay();
			}
			return FALSE;
		}
	default:
		return FALSE;
	}
	return TRUE;
}

//	キャラクターコードに対応するDTMF信号の周波数を取得

void Char2Dtmf(int c, int* f1, int* f2){
	int low[] = { 697, 770, 852, 941 };
	int hi[] = { 1209, 1336, 1477, 1633 };
	char* str = "123A456B789C*0#D";

	int n = 0;
	while (str[n]){
		if (str[n] == c){
			int hin = n % 4;
			int lown = n / 4;
			*f1 = low[lown];
			*f2 = hi[hin];
			return;
		}
		++n;
	}
	*f1 = -1;
	*f2 = -1;
}

resource.h

#define IDC_1_BUTTON (WM_USER+0x31)
#define IDC_2_BUTTON (WM_USER+0x32)
#define IDC_3_BUTTON (WM_USER+0x33)
#define IDC_A_BUTTON (WM_USER+0x41)
#define IDC_4_BUTTON (WM_USER+0x34)
#define IDC_5_BUTTON (WM_USER+0x35)
#define IDC_6_BUTTON (WM_USER+0x36)
#define IDC_B_BUTTON (WM_USER+0x42)
#define IDC_7_BUTTON (WM_USER+0x37)
#define IDC_8_BUTTON (WM_USER+0x38)
#define IDC_9_BUTTON (WM_USER+0x39)
#define IDC_C_BUTTON (WM_USER+0x43)
#define IDC_ASTERRISK_BUTTON (WM_USER+0x2A)
#define IDC_0_BUTTON (WM_USER+0x30)
#define IDC_SHARP_BUTTON (WM_USER+0x23)
#define IDC_D_BUTTON (WM_USER+0x44)

#define isDtmfChar(c) ( (_T('#')<=c && c<=_T('D')) ? 1 : 0 )

resource.rc




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

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