音声の入力
概要
マイクなどから入力された音声を取得して波形として表示してみました。
HSPの場合、プラグインがあるのですが、今回はAPIを直接叩いてやってみました。
今回は波形の取得までで再生や保存は行なっていません。
モジュール化したサンプル
と言っても実用性無視の適当仕様です。
モジュール名も決めていない感じです。
そのまま使ってもらっても構いませんが、参考程度とお考え下さい。
下に書いてあるソースはサンプル付きのモジュールと言いますか、モジュール付きのサンプルです。
PCにマイクを繋いで実行して下さい。あとはウィンドウ内に「START」というボタンが現れるので、
そのボタンを押せば音声の入力開始です。
取得した波形が描画されるので眺めて楽しんで下さい。
サンプリングレート、量子化ビット数、チャンネル数、が選べますが分かる方だけ触っていただければいいと思います。
;
;[ Infomation ]
; Name : 音声入力テスト
; SubName :
; Version :
; copyright :
;
;[ Update history ]
;yyyy/mm/dd : ver : comment
;2013/07/15 : 0.1 : とりあえず完成。
;
;[ comment ]
;STARTボタンを押すと、マイク入力されている音声を波形で表示します。
;
;サンプリングレート:0より大きい整数値。
;量子化ビット数:8または16
;チャンネル数:1~2
;
;マルチバッファリングを実装。バッファの数は2個。
;波形の左側がバッファ1個目、右側が2個目です。
;
#include "winmm.as"
;#ifndef __MODULE_NAME__
;#define global __MODULE_NAME__
#module
;---------------------------------------------------------------------------------------------------
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; 定数の定義
;
#define WAVE_MAPPER $FFFFFFFF ;ユーザーが選択している優先デバイス
#define CALLBACK_WINDOW $00010000
; 音声データ フォーマット
#define WAVE_FORMAT_PCM $00000001 ;PCM
; WAVE関連
#define MMSYSERR_NOERROR $00000000 ;成功
#define MMSYSERR_ALLOCATED $00000004 ;エラー : 指定されたリソースはすでに割り当てられています。
#define MMSYSERR_BADDEVICEID $00000002 ;エラー : 指定されたデバイス識別子は範囲外です。
#define MMSYSERR_NODRIVER $00000006 ;エラー : デバイスドライバが存在しません。
#define MMSYSERR_NOMEM $00000007 ;エラー : メモリを割り当てられないか、またはロックできません。
#define WAVERR_BADFORMAT $00000020 ;エラー : サポートされていないウェーブフォームオーディオ形式でオープンしようとしました。
#define MMSYSERR_HANDLEBUSY $0000000C ;エラー : 別のスレッドがハンドルを使用中である
#define MMSYSERR_INVALHANDLE $00000005 ;エラー : デバイスハンドルが無効である
#define WAVERR_UNPREPARED $00000022 ;エラー : バッファが準備されていない
#define WAVERR_STILLPLAYING $00000021 ;エラー : キュー内にバッファがまだ残っている
; イベント
#define MM_WIM_CLOSE $000003BF
#define MM_WIM_DATA $000003C0
#define MM_WIM_OPEN $000003BE
#define MM_WOM_CLOSE $000003BC
#define MM_WOM_DONE $000003BD
#define MM_WOM_OPEN $000003BB
;-----------------------------------
;
; USER
;
;-----------------------------------
; WAVE
#define WAVE_BUFCNT 2 //多重バッファ数
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; モジュール初期化
;
;[ Infomation ]
; ModSoundIni
;
; return : 0
;
;[ comment ]
;最初に必ず実行して下さい。
;
#deffunc ModSoundIni
// イベントの設定
oncmd gosub *On_MM_WIM_OPEN, MM_WIM_OPEN // ウェーブフォームオーディオ入力デバイスが開いた
oncmd gosub *On_MM_WIM_DATA, MM_WIM_DATA // 入力に対してバッファが一杯になるか、入力が停止された
oncmd gosub *On_MM_WIM_CLOSE, MM_WIM_CLOSE // ウェーブフォームオーディオ入力デバイスが閉じられた
// 録音の状態
// 1 : 録音中
// 2 : クローズ処理中
stateRecorder = 0
return
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; WAVEFORMATEX構造体をセット
;
;[ Infomation ]
; ModSoundSetWaveformatex p1
; p1=0~(0) :
;
; return : 0
;
;[ comment ]
;
#deffunc ModSoundSetWaveformatex int wFormatTag, int nChannels, int nSamplesPerSec, int wBitsPerSample
// チャンネル数が2より大きい等の場合は、WAVEFORMATEXTENSIBLE 構造体が必要です。
// 変数宣言
dim wfe, 5 // WAVEFORMATEX構造体
// WAVEFORMATEX構造体を用意
;typedef struct {
; WORD wFormatTag; // フォーマット
; WORD nChannels; // チャンネル数
; DWORD nSamplesPerSec; // サンプル レート [Hz]
; DWORD nAvgBytesPerSec; // 平均データ転送レート (単位 バイト/秒)
; WORD nBlockAlign; // ブロック アラインメント (単位 バイト) 指定フォーマットの最小単位のデータサイズ
; WORD wBitsPerSample; // 1 サンプルあたりのビット数(コンテナサイズ)
; WORD cbSize; // 拡張サイズ(通常は0)
;} WAVEFORMATEX;
;
wfe(0) = WAVE_FORMAT_PCM + ( nChannels <<16 ) //wFormatTag + ( nChannels<<16 )
wfe(1) = nSamplesPerSec //nSamplesPerSec
wfe(2) = nSamplesPerSec * nChannels * wBitsPerSample / 8 //nAvgBytesPerSec
wfe(3) = ( nChannels * wBitsPerSample / 8 ) + ( wBitsPerSample<<16 ) //nBlockAlign + (wBitsPerSample<<16)
wfe(4) = 0 //cbSize
// 普通は64KByteぐらいが使われているらしい。
// なおバッファの数は最低2個以上。安全を考えると3つ以上。
// 多いほどいいがコストもかかるので状況による。最適値はない。(人によっては4個つぐらいとのこと)
bufsize = nSamplesPerSec
return
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; 音声入力を開始
;
;[ Infomation ]
; ModSoundStartProc
;
; return : 0
;
;[ comment ]
;実行前にModSoundSetWaveformatex命令を実行して下さい。
;
#deffunc ModSoundStartProc
// 変数宣言
sdim bWave, bufsize, WAVE_BUFCNT // 音声データ格納用バッファ
dim whdr, 8, WAVE_BUFCNT // WAVEHDR構造体
hWaveIn = 0 // オープンしているウェーブフォームオーディオ入力デバイスを識別するハンドルが入る。
// WAVEHDR構造体を用意
;typedef struct {
; LPSTR lpData; // ウェーブフォームバッファへのポインタ
; DWORD dwBufferLength; // バッファのサイズ(バイト)
; DWORD dwBytesRecorded; // 入力バッファとして使用する場合に指定したバッファ内のデータ要領
; DWORD dwUser;
; DWORD dwFlags;
; DWORD dwLoops; // ループ再生させる回数
; struct wavehdr_tag * lpNext;
; DWORD reserved;
;} WAVEHDR;
;
; 波形データを収めるバッファのサイズ
; 1秒
repeat WAVE_BUFCNT
whdr(0, cnt) = varptr(bWave(cnt))
whdr(1, cnt) = bufsize
whdr(4, cnt) = $00000004|$00000008 //WHDR_BEGINLOOP|WHDR_ENDLOOP
whdr(5, cnt) = 1
loop
// 入力デバイスを開く
waveInOpen varptr(hWaveIn), WAVE_MAPPER, varptr(wfe), hwnd, 0, CALLBACK_WINDOW
// 現在の状態を入力中に設定
stateRecorder = 1
return
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; 音声入力を終了
;
;[ Infomation ]
; ModSoundStopProc
;
; return : 0
;
;[ comment ]
;音声入力を終了します。
;
;安全のため、プログラム終了する際にもこの命令を実行するようにして下さい。
;
#deffunc ModSoundStopProc
// 再生中ではない場合は、何もしない
if (stateRecorder & 1)=0 : return
// 終了処理中のフラグを立てる
stateRecorder |= 2
// 提供されたウェーブフォームオーディオ入力デバイスでの入力を停止
waveInReset hWaveIn
return
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; 音声入力の現在の状態を取得
;
;[ Infomation ]
; ModSoundGetState()
;
; return : 0
;
;[ comment ]
;音声入力の現在の状態を返します。
; 0:停止中
; 1:再生中
; 2:停止・終了処理中
;
#defcfunc ModSoundGetState
return stateRecorder
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; 音声入力結果を表示
;
;[ Infomation ]
; ModSoundTestDraw int cty, int amp
;
; cty : 中心座標y
; amp : 振幅
; return : 0
;
;[ comment ]
;取得値を波形の形で表示します。
;
#deffunc ModSoundTestDraw int cty, int amp
if stateRecorder = 1 {
wb = WAVE_BUFCNT -1 : if wb = 0 : wb = 1
// コンテナサイズ
ct = ( wfe(3) & 0xFFFF0000 )>>16
// チャンネル数
ch = ( wfe(0) & 0xFFFF0000 )>>16
// とにかく今のバッファの内容をそのまま描画
cx = 0 : cy = cty
dt = double(ginfo_winx) / bufsize
// 軸を描画
color 127, 127, 127
repeat ch
cx = 0 : cy = cty + amp*2*cnt
line cx, cy, ginfo_winx, cy
color 255
pos cx, cy : mes "" + (cnt+1) + " CH"
loop
// 1データの横幅。⊿t。
// 8 Bit, 1chの時にウィンドウ幅いっぱいにバッファのデータを表示するようにしています。
// 波形は上から1ch、2ch。左がバッファ1個目、右が2個目。
dt = double(ginfo_winx) / ( bufsize * WAVE_BUFCNT * ch )
// 波形を描画
color 0,0,0
repeat WAVE_BUFCNT
bcnt = cnt
// 8 Bit
if ct = 8 {
repeat ch // チャンネル
c = cnt
cx = 0 : cy = cty + amp*2*c
y = cy : x = ginfo_winx / WAVE_BUFCNT * bcnt // 波形の継ぎ目の部分(見た目キレイに鳴るようにつないでいるだけです。)
repeat bufsize / ch
b = cnt
// bWaveからデータを取り出す。
// 8Bitでデータ1個分。
// 2chの場合データの並びは、ステレオなのでデータは「左右左右…(1ch,2ch,1ch,2ch,…)」と入っている。
val = peek(bWave(bcnt), b*ch+c) ;1 byte
val -= 128 ;128 = 0 dB、最小値0、最大値255
x0 = x : y0 = y
x = dt*b*ch + cx + ginfo_winx / WAVE_BUFCNT * bcnt
y = cy + val
line x0, y0, x, y
loop // bufsize / ch
loop // ch
}
// 16 Bit
if ct = 16 {
repeat ch // チャンネル
c = cnt
cx = 0 : cy = cty + amp*2*c
y = cy : x = ginfo_winx / WAVE_BUFCNT * bcnt // 波形の継ぎ目の部分(見た目キレイに鳴るようにつないでいるだけです。)
repeat bufsize / (ct / 8) / ch
b = cnt
// bWaveからデータを取り出す。
// 16 Bitでデータ1個分。
// 2chの場合データの並びは、ステレオなのでデータは「左右左右…(1ch,2ch,1ch,2ch,…)」と入っている。
val = wpeek(bWave(bcnt), b*2*ch + c*2) ;2 byte(short型)
// 取得値はushort型として扱われているため、符号情報を復元してshort型をint型に変換したようにする
if val & 0x8000 : val |= 0xffff0000
// 振幅を8Bitの時と同じにするため調整
val = val * 128 / 32767
x0 = x : y0 = y
x = dt*b*ch + cx + ginfo_winx / WAVE_BUFCNT * bcnt
y = cy + val
line x0, y0, x, y
loop // bufsize / (ct / 8)
loop // ch
}
loop // WAVE_BUFCNT
}
return
;///////////////////////////////////////////////////////////////////////////////////////////////////
;
; イベント処理
;
;-----------------------------------
;
; ウェーブフォームオーディオ入力デバイスが開いた
;
;-----------------------------------
*On_MM_WIM_OPEN
// 入力のために、ウェーブフォームオーディオデータブロックを初期化
repeat WAVE_BUFCNT
waveInPrepareHeader hWaveIn, varptr(whdr(0, cnt)), 32
loop
// 入力デバイスのバッファキューに、準備したバッファを追加
repeat WAVE_BUFCNT
waveInAddBuffer hWaveIn, varptr(whdr(0, cnt)), 32
if stat : dialog "waveInAddBuffer エラー(" + stat + ")" : stop
loop
// 入力デバイスのキューに入っているバッファに音声の入力を開始
waveInStart hWaveIn
if stat : dialog "waveInStart エラー(" + stat + ")" : stop
return
;-----------------------------------
;
; 入力に対してバッファが一杯になるか、入力が停止された
;
;-----------------------------------
; バッファの入力が終了するごとに呼ばれる
; WPARAM : 入力終了した hWaveIn ウェーブフォームオーディオ入力デバイスのハンドル
; LPARAM : 入力終了した LPWAVEHDR WAVEHDR構造体のポインタ
;
*On_MM_WIM_DATA
wp = wparam
if stateRecorder = 3 {
// 入力が停止された
// 録音を終了
stateRecorder = 2 // 終了処理中。
// 入力用に準備済みのバッファを解放
// バッファ開放の準備
repeat WAVE_BUFCNT
waveInUnprepareHeader wp, varptr(whdr(0, cnt)), 32
r = stat
if r = MMSYSERR_HANDLEBUSY : dialog "WaveInUnprepareHeader エラー(" + r + ")\n別のスレッドがハンドルを使用中である\n(" + cnt + ")"
if r = MMSYSERR_INVALHANDLE : dialog "WaveInUnprepareHeader エラー(" + r + ")\nデバイスハンドルが無効である\n(" + cnt + ")" + "\nデバイスハンドル : " + wp + " --- " + hWaveIn
if r = MMSYSERR_NODRIVER : dialog "WaveInUnprepareHeader エラー(" + r + ")\nデバイスドライバがない\n(" + cnt + ")"
if r = MMSYSERR_NOMEM : dialog "WaveInUnprepareHeader エラー(" + r + ")\nメモリを割り当てられない、またはロックできない\n(" + cnt + ")"
if r = WAVERR_STILLPLAYING : dialog "WaveInUnprepareHeader エラー(" + r + ")\nキュー内にバッファがまだ残っている\n(" + cnt + ")"
loop
// ウェーブフォームオーディオ入力デバイスを閉じる
waveInClose wp
r = stat
if r = MMSYSERR_INVALHANDLE : dialog "waveInClose エラー(" + r + ")\nデバイスハンドルが無効である\n(" + cnt + ")"
if r = MMSYSERR_NODRIVER : dialog "waveInClose エラー(" + r + ")\nデバイスドライバがない\n(" + cnt + ")"
if r = MMSYSERR_NOMEM : dialog "waveInClose エラー(" + r + ")\nメモリを割り当てられない、またはロックできない\n(" + cnt + ")"
if r = WAVERR_STILLPLAYING : dialog "waveInClose エラー(" + r + ")\nキュー内にバッファがまだ残っている\n(" + cnt + ")"
// 終了処理完了
// 終了処理中にMM_WIM_DATAが発生した時の対策としてこのようにしています。
stateRecorder = 0
return
}
if stateRecorder = 1 {
// 入力に対してバッファが一杯になった
// 入力キューを追加
// 使い終わったバッファを再びキューに入れる。
// バッファが1個だけだとこの間入力が止まってしまうので、2個以上でぐるぐるまわす必要がある。
waveInAddBuffer wparam, lparam, 32
r = stat
if r = MMSYSERR_HANDLEBUSY : dialog "waveInAddBuffer エラー(" + r + ")\n別のスレッドがハンドルを使用中である"
if r = MMSYSERR_INVALHANDLE : dialog "waveInAddBuffer エラー(" + r + ")\nデバイスハンドルが無効である"
if r = MMSYSERR_NODRIVER : dialog "waveInAddBuffer エラー(" + r + ")\nデバイスドライバがない"
if r = MMSYSERR_NOMEM : dialog "waveInAddBuffer エラー(" + r + ")\nメモリを割り当てられない、またはロックできない"
if r = WAVERR_UNPREPARED : dialog "waveInAddBuffer エラー(" + r + ")\nバッファが準備されていない"
}
return
;-----------------------------------
;
; ウェーブフォームオーディオ入力デバイスが閉じられた
;
;-----------------------------------
;WPARAM には入力デバイスのハンドル
;LPARAM には 0 が格納されている
;
*On_MM_WIM_CLOSE
dim bWave // 音声データ格納用バッファ を開放
return
;---------------------------------------------------------------------------------------------------
#global
;#endif ;__MODULE_NAME__
;-------------------------------------------------------------------------------
;
; ウィンドウ作成
;
;-------------------------------------------------------------------------------
; 再生・停止フラグ
; 1 = 再生
flgPlay = 0
screen 0, 1000
ModSoundIni
onexit *ExitProc
;-----------------------------------
;
; GUI
;
;-----------------------------------
#define LINEHEIGHT 25 ; 1行の高さ
inputSmpPSecValue = 44100
inputBitPSmpValue = 8 ; 8,16,32,... 8の倍数
inputChannelValue = 1 ; 1~2。3以上は扱いが少し変わる。
y = 20
pos 10, y + LINEHEIGHT * 0 : mes "サンプリングレート:" : pos 180, y + LINEHEIGHT * 0 : input inputSmpPSecValue : pos 250, y + LINEHEIGHT * 0 : mes "[Hz]"
pos 10, y + LINEHEIGHT * 1 : mes "量子化ビット数:" : pos 180, y + LINEHEIGHT * 1 : input inputBitPSmpValue : pos 250, y + LINEHEIGHT * 1 : mes "[Bit]"
pos 10, y + LINEHEIGHT * 2 : mes "チャンネル数:" : pos 180, y + LINEHEIGHT * 2 : input inputChannelValue : pos 250, y + LINEHEIGHT * 2 : mes "[ch]"
; 再生・停止ボタン
pos 350, 20
button gosub "- START -", *StartStop
buttonStartStopID = stat
;-----------------------------------
;
; メインループ
;
;-----------------------------------
*main
redraw 1 : await 16 : redraw 0 : color 255, 255, 255
boxf 0, 96, ginfo_winx, ginfo_winy : color : pos 0,0
;
; 生波形描画
;
if flgPlay = 1 {
ModSoundTestDraw 192, 96
}
goto *main
;-------------------------------------------------------------------------------
;
; サブルーチン
;
;-------------------------------------------------------------------------------
;-----------------------------------
;
; 終了処理
;
;-----------------------------------
*ExitProc
; 終了処理
ModSoundStopProc
end
;-----------------------------------
;
; 再生・停止
;
;-----------------------------------
*StartStop
if flgPlay=0 {
if inputChannelValue < 1 : dialog "チャンネル数が小さすぎます。(1~2)" : return
if inputChannelValue > 2 : dialog "チャンネル数が大きすぎます。(1~2)" : return
if (inputBitPSmpValue ! 8) and (inputBitPSmpValue ! 16) : dialog "コンテナサイズは8か16にしてください。" : return
if inputSmpPSecValue<=0 : dialog "サンプリングレートは0以下は設定出来ません。" : return
; 停止→再生
ModSoundSetWaveformatex $00000001, inputChannelValue, inputSmpPSecValue, inputBitPSmpValue
ModSoundStartProc
flgPlay = 1
objprm buttonStartStopID, "> STOP <"
} else {
; 再生→停止
ModSoundStopProc
flgPlay = 0
objprm buttonStartStopID, "- START -"
}
return
解説
細かい解説はすごくめんどくさいので、既存のわかりやすいサイトをご紹介。
Win32 API入門 - 音声の入力 http://wisdom.sakura.ne.jp/system/winapi/media/mm7.html
Cactus Software http://www.cactussoft.co.jp/Sarbo/chpWave.html
どちらも詳しくて、情報がまとまって手に入る良いサイトでした。
今回作ったサンプルは、ダブルバッファリングでバッファがいっぱいになっても取得が止まらないようにしています。
詳しくは上記のサイトを見るか、Google先生に聞いてみて下さい。
Google先生に聞くと他にもいろんなサンプルが見つかるんですが、サンプルによって実装がさまざまで悩むと思います。
実際に今回書いたサンプルも上で紹介したサイト記載のサンプルとは少し違います。しかし私が色々調べた結果がこれなのですよ。なので…あっているかどうか非常に不安です。(´・ω・`)
いやでもwaveInCloseで無効にしたハンドルでwaveInUnprepareHeaderするとは何事かと…。
ライセンス
NYSLです。
サンプルもモジュールも好きに使って下さい。