任天堂おしゃべりフラワーのしゃべりを網羅する?

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の瞬時値を捉えて、表示することは一応できていますね。実際の使用環境での設定で、トリガーが安定して捉えれればいいわけです。

コメント