C++ Builder CE サウンドプログラミング post-triggerによる非同期Mic入力録音の試み その2
前記事
の続編です。callback関数でmic levelの瞬時値(相当)を得ようとする試みです。まずフォーム、

TProgressBarを1個だけ配置。Unit1.hは、
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ComCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE で管理されるコンポーネント
TProgressBar *ProgressBar1;
void __fastcall FormCreate(TObject *Sender);
void __fastcall FormDestroy(TObject *Sender);
private: // ユーザー宣言
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endifというように、Formのイベントの

OnCreateとOnDestroyをフックしつつ、Unit1.cppは、
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <mmsystem.h>
#include <math.h>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
#pragma comment(lib, "winmm.lib")
static const int SAMPLE_RATE = 16000;
static const int CHANNELS = 1;
static const int BITS = 16;
static const int BUFFER_SAMPLES = 4096;
HWAVEIN hWaveIn = NULL;
WAVEHDR hdr1, hdr2;
short *buf1 = nullptr;
short *buf2 = nullptr;
// Forward declarations
void ProcessMicLevel(const short *data, DWORD bytes);
// ----- CALLBACK FUNCTION -----
// Called by Windows when a buffer is filled with PCM data
void CALLBACK WaveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR dwParam1, DWORD_PTR)
{
if (msg == MM_WIM_DATA)
{
WAVEHDR *hdr = reinterpret_cast<WAVEHDR*>(dwParam1);
// process PCM buffer → RMS/peak level
ProcessMicLevel((short*)hdr->lpData, hdr->dwBytesRecorded);
// Re queue buffer to continue capturing
waveInAddBuffer(hWaveIn, hdr, sizeof(WAVEHDR));
}
}
// ----- CALCULATE LEVEL -----
// Computes RMS level: 0.0 → 1.0
void ProcessMicLevel(const short *data, DWORD bytes)
{
int samples = bytes / sizeof(short);
if (samples <= 0) return;
double sum = 0;
short peak = 0;
for (int i = 0; i < samples; i++) {
short v = data[i];
sum += (double)v * v;
if (abs(v) > peak) peak = abs(v);
}
double rms = sqrt(sum / samples) / 32768.0; // normalized 0–1
double level = rms * 100.0;
// Thread-safe UI update
TThread::Synchronize(nullptr, [&](){
if (Form1->ProgressBar1) {
Form1->ProgressBar1->Position = (int)level;
}
});
}
// ----- START MICROPHONE -----
bool StartMic()
{
WAVEFORMATEX fmt = {};
fmt.wFormatTag = WAVE_FORMAT_PCM; // from Windows audio API [cite:3]
fmt.nChannels = CHANNELS;
fmt.nSamplesPerSec = SAMPLE_RATE;
fmt.wBitsPerSample = BITS;
fmt.nBlockAlign = (fmt.wBitsPerSample / 8) * fmt.nChannels;
fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
if (waveInOpen(&hWaveIn, WAVE_MAPPER, &fmt,
(DWORD_PTR)WaveInProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
return false;
buf1 = new short[BUFFER_SAMPLES];
buf2 = new short[BUFFER_SAMPLES];
ZeroMemory(&hdr1, sizeof(WAVEHDR));
ZeroMemory(&hdr2, sizeof(WAVEHDR));
hdr1.lpData = (LPSTR)buf1;
hdr1.dwBufferLength = BUFFER_SAMPLES * sizeof(short);
hdr2.lpData = (LPSTR)buf2;
hdr2.dwBufferLength = BUFFER_SAMPLES * sizeof(short);
// waveInPrepareHeader confirmed in Windows audio API documentation [cite:3]
waveInPrepareHeader(hWaveIn, &hdr1, sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn, &hdr2, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &hdr1, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &hdr2, sizeof(WAVEHDR));
waveInStart(hWaveIn);
return true;
}
// ----- STOP MICROPHONE -----
void StopMic()
{
if (!hWaveIn) return;
waveInStop(hWaveIn);
waveInReset(hWaveIn);
waveInUnprepareHeader(hWaveIn, &hdr1, sizeof(WAVEHDR));
waveInUnprepareHeader(hWaveIn, &hdr2, sizeof(WAVEHDR));
waveInClose(hWaveIn);
hWaveIn = NULL;
delete[] buf1;
delete[] buf2;
buf1 = buf2 = nullptr;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
StartMic();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
StopMic();
}
//---------------------------------------------------------------------------動かすと、
のように一応動きます。バーの変化がギクシャクしているのは、平滑処理とかをしているためですかね。ま、トリガーが安定して取れればいいので、WinMM APIでmic levelの瞬時値を捉えて、表示することは一応できていますね。実際の使用環境での設定で、トリガーが安定して捉えれればいいわけです。



コメント