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

Codexのセッションを続ける

前の記事

のプログラムでキャプチャーした音声のファイルへの保存ができるようで実際にはできないのを修正します。一からセッション繰り返さなくても、”継続”というか”Resume”できるようですね。まずはCodexを起動して、セッションを再開すればよいですね。

左側の”チャット”の下をクリック。

上図右上にあるように、“waveで書き込めるようにして”というリクエストをすると、しばらくごちゃごちゃして、うまくパッチが当たらないので1から作り直すとかいいつつ、

WAV書き込み対応を追加しました。
更新済みZIP: [MicCaptureCppBuilder.zip](C:\\Users\\jakeb\\Documents\\Codex\\2026-06-24\\wi\\outputs\\MicCaptureCppBuilder.zip)
展開済みフォルダ: [MicCaptureCppBuilder](C:\\Users\\jakeb\\Documents\\Codex\\2026-06-24\\wi\\outputs\\MicCaptureCppBuilder)
変更点は、録音開始時に mic_capture.wav を作成し、録音データをPCMとして書き込み、停止時にWAVヘッダのサイズを確定する処理です。出力先は実行ファイルと同じフォルダで、形式は 44.1kHz / 16bit / mono です。

README.mdドキュメント・MDプレビューを開く次で開く

3 件のファイルを編集+327-17変更を確認元に戻すレビューする

outputs/MicCaptureCppBuilder/MainForm.houtputs/MicCaptureCppBuilder/MainForm.h+13-0
outputs/MicCaptureCppBuilder/MainForm.cppoutputs/MicCaptureCppBuilder/MainForm.cpp+296-0
outputs/MicCaptureCppBuilder/README.mdoutputs/MicCaptureCppBuilder/README.md

相変わらずプロジェクトファイルは使えませんけど、再構成してbuildはできました。実行すると、

“Start”をクリック、レベルモニターしながら”Stop”をクリック。収録風景は、

できたwaveは、

出典が明らかでないということで、Codex君が作ったソースを開示するにあたっても許諾は不要ということで、以下に示しますね。MainForm.hは、

#ifndef MainFormH
#define MainFormH

#include <System.Classes.hpp>
#include <Vcl.ComCtrls.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.StdCtrls.hpp>
#include <System.SyncObjs.hpp>
#include <mmsystem.h>

class TForm1 : public TForm
{
__published:
    TButton *ButtonStart;
    TButton *ButtonStop;
    TProgressBar *ProgressBarLevel;
    TLabel *LabelLevel;
    TLabel *LabelStatus;
    void __fastcall ButtonStartClick(TObject *Sender);
    void __fastcall ButtonStopClick(TObject *Sender);
    void __fastcall FormClose(TObject *Sender, TCloseAction &Action);

private:
    static const int SampleRate = 44100;
    static const int Channels = 1;
    static const int BitsPerSample = 16;
    static const int BufferMs = 50;
    static const int BufferCount = 4;
    static const int BufferBytes = SampleRate * Channels * (BitsPerSample / 8) * BufferMs / 1000;

    HWAVEIN FWaveIn;
    WAVEHDR FHeaders[BufferCount];
    BYTE FBuffers[BufferCount][BufferBytes];
    bool FRecording;
    DWORD FLastUiTick;
    TFileStream *FWaveFile;
    TCriticalSection *FFileLock;
    DWORD FWaveDataBytes;
    String FWaveFileName;

    void StartCapture();
    void StopCapture();
    void HandleWaveData(WAVEHDR *Header);
    void QueueLevel(int Level);
    void SetStatus(const String &Text);
    bool OpenWaveFile();
    void WriteWaveHeaderPlaceholder();
    void WriteWaveData(const void *Data, int Bytes);
    void CloseWaveFile();
    void WriteFourCC(const char *Text);
    void WriteWord(WORD Value);
    void WriteDWord(DWORD Value);

    static void CALLBACK WaveInProc(HWAVEIN WaveIn, UINT Msg, DWORD_PTR Instance,
        DWORD_PTR Param1, DWORD_PTR Param2);

public:
    __fastcall TForm1(TComponent* Owner);
    __fastcall ~TForm1();
};

extern PACKAGE TForm1 *Form1;

#endif

MainForm.cppは、

#include <vcl.h>
#pragma hdrstop

#include "MainForm.h"

#include <System.SysUtils.hpp>
#include <algorithm>
#include <cstdlib>

#pragma package(smart_init)
#pragma resource "*.dfm"
#pragma comment(lib, "winmm.lib")

TForm1 *Form1;

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner),
      FWaveIn(NULL),
      FRecording(false),
      FLastUiTick(0),
      FWaveFile(NULL),
      FFileLock(new TCriticalSection()),
      FWaveDataBytes(0)
{
    ZeroMemory(FHeaders, sizeof(FHeaders));
    ButtonStop->Enabled = false;
    ProgressBarLevel->Min = 0;
    ProgressBarLevel->Max = 100;
    SetStatus(L"Stopped");
}

__fastcall TForm1::~TForm1()
{
    StopCapture();
    CloseWaveFile();
    delete FFileLock;
}

void __fastcall TForm1::ButtonStartClick(TObject *Sender)
{
    StartCapture();
}

void __fastcall TForm1::ButtonStopClick(TObject *Sender)
{
    StopCapture();
}

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    StopCapture();
}

void TForm1::StartCapture()
{
    if (FRecording) {
        return;
    }

    WAVEFORMATEX Format;
    ZeroMemory(&Format, sizeof(Format));
    Format.wFormatTag = WAVE_FORMAT_PCM;
    Format.nChannels = Channels;
    Format.nSamplesPerSec = SampleRate;
    Format.wBitsPerSample = BitsPerSample;
    Format.nBlockAlign = Format.nChannels * Format.wBitsPerSample / 8;
    Format.nAvgBytesPerSec = Format.nSamplesPerSec * Format.nBlockAlign;

    MMRESULT Result = waveInOpen(&FWaveIn, WAVE_MAPPER, &Format,
        reinterpret_cast<DWORD_PTR>(&TForm1::WaveInProc),
        reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);

    if (Result != MMSYSERR_NOERROR) {
        MessageDlg(L"Could not open the microphone. Check Windows microphone permission and input device.",
            mtError, TMsgDlgButtons() << mbOK, 0);
        return;
    }

    if (!OpenWaveFile()) {
        waveInClose(FWaveIn);
        FWaveIn = NULL;
        MessageDlg(L"Could not create mic_capture.wav.", mtError,
            TMsgDlgButtons() << mbOK, 0);
        return;
    }

    for (int i = 0; i < BufferCount; ++i) {
        ZeroMemory(&FHeaders[i], sizeof(WAVEHDR));
        FHeaders[i].lpData = reinterpret_cast<LPSTR>(FBuffers[i]);
        FHeaders[i].dwBufferLength = BufferBytes;

        Result = waveInPrepareHeader(FWaveIn, &FHeaders[i], sizeof(WAVEHDR));
        if (Result != MMSYSERR_NOERROR) {
            StopCapture();
            MessageDlg(L"Could not prepare recording buffer.", mtError,
                TMsgDlgButtons() << mbOK, 0);
            return;
        }

        Result = waveInAddBuffer(FWaveIn, &FHeaders[i], sizeof(WAVEHDR));
        if (Result != MMSYSERR_NOERROR) {
            StopCapture();
            MessageDlg(L"Could not add recording buffer.", mtError,
                TMsgDlgButtons() << mbOK, 0);
            return;
        }
    }

    FRecording = true;
    FLastUiTick = 0;

    Result = waveInStart(FWaveIn);
    if (Result != MMSYSERR_NOERROR) {
        StopCapture();
        MessageDlg(L"Could not start recording.", mtError,
            TMsgDlgButtons() << mbOK, 0);
        return;
    }

    ButtonStart->Enabled = false;
    ButtonStop->Enabled = true;
    SetStatus(L"Recording: " + FWaveFileName);
}

void TForm1::StopCapture()
{
    if (!FWaveIn) {
        CloseWaveFile();
        return;
    }

    FRecording = false;
    waveInStop(FWaveIn);
    waveInReset(FWaveIn);

    for (int i = 0; i < BufferCount; ++i) {
        if (FHeaders[i].dwFlags & WHDR_PREPARED) {
            waveInUnprepareHeader(FWaveIn, &FHeaders[i], sizeof(WAVEHDR));
        }
    }

    waveInClose(FWaveIn);
    FWaveIn = NULL;
    CloseWaveFile();

    ButtonStart->Enabled = true;
    ButtonStop->Enabled = false;
    ProgressBarLevel->Position = 0;
    LabelLevel->Caption = L"0%";
    SetStatus(L"Stopped. Saved: " + FWaveFileName);
}

void CALLBACK TForm1::WaveInProc(HWAVEIN WaveIn, UINT Msg, DWORD_PTR Instance,
    DWORD_PTR Param1, DWORD_PTR Param2)
{
    if (Msg != WIM_DATA || Instance == 0) {
        return;
    }

    TForm1 *Form = reinterpret_cast<TForm1*>(Instance);
    Form->HandleWaveData(reinterpret_cast<WAVEHDR*>(Param1));
}

void TForm1::HandleWaveData(WAVEHDR *Header)
{
    if (!Header || Header->dwBytesRecorded == 0) {
        return;
    }

    short *Samples = reinterpret_cast<short*>(Header->lpData);
    const int SampleCount = Header->dwBytesRecorded / sizeof(short);

    int Peak = 0;
    for (int i = 0; i < SampleCount; ++i) {
        Peak = std::max(Peak, std::abs(static_cast<int>(Samples[i])));
    }

    const int Level = std::min(100, Peak * 100 / 32767);
    QueueLevel(Level);
    WriteWaveData(Header->lpData, Header->dwBytesRecorded);

    if (FRecording && FWaveIn) {
        waveInAddBuffer(FWaveIn, Header, sizeof(WAVEHDR));
    }
}

void TForm1::QueueLevel(int Level)
{
    const DWORD Now = GetTickCount();
    if (Now - FLastUiTick < 50) {
        return;
    }
    FLastUiTick = Now;

    TThread::Queue(NULL, [this, Level]() {
        if (ComponentState.Contains(csDestroying)) {
            return;
        }

        ProgressBarLevel->Position = Level;
        LabelLevel->Caption = String(Level) + L"%";
    });
}

void TForm1::SetStatus(const String &Text)
{
    LabelStatus->Caption = Text;
}

bool TForm1::OpenWaveFile()
{
    CloseWaveFile();

    FWaveFileName = IncludeTrailingPathDelimiter(ExtractFilePath(Application->ExeName)) +
        L"mic_capture.wav";
    FWaveDataBytes = 0;

    try {
        FWaveFile = new TFileStream(FWaveFileName, fmCreate);
        WriteWaveHeaderPlaceholder();
    }
    catch (...) {
        CloseWaveFile();
        return false;
    }

    return true;
}

void TForm1::WriteWaveHeaderPlaceholder()
{
    WriteFourCC("RIFF");
    WriteDWord(0);
    WriteFourCC("WAVE");
    WriteFourCC("fmt ");
    WriteDWord(16);
    WriteWord(WAVE_FORMAT_PCM);
    WriteWord(Channels);
    WriteDWord(SampleRate);
    WriteDWord(SampleRate * Channels * (BitsPerSample / 8));
    WriteWord(Channels * (BitsPerSample / 8));
    WriteWord(BitsPerSample);
    WriteFourCC("data");
    WriteDWord(0);
}

void TForm1::WriteWaveData(const void *Data, int Bytes)
{
    if (!FWaveFile || !Data || Bytes <= 0) {
        return;
    }

    FFileLock->Acquire();
    try {
        if (FWaveFile) {
            FWaveFile->WriteBuffer(Data, Bytes);
            FWaveDataBytes += Bytes;
        }
    }
    __finally {
        FFileLock->Release();
    }
}

void TForm1::CloseWaveFile()
{
    FFileLock->Acquire();
    try {
        if (FWaveFile) {
            FWaveFile->Position = 4;
            WriteDWord(36 + FWaveDataBytes);
            FWaveFile->Position = 40;
            WriteDWord(FWaveDataBytes);
            delete FWaveFile;
            FWaveFile = NULL;
        }
    }
    __finally {
        FFileLock->Release();
    }
}

void TForm1::WriteFourCC(const char *Text)
{
    FWaveFile->WriteBuffer(Text, 4);
}

void TForm1::WriteWord(WORD Value)
{
    FWaveFile->WriteBuffer(&Value, sizeof(Value));
}

void TForm1::WriteDWord(DWORD Value)
{
    FWaveFile->WriteBuffer(&Value, sizeof(Value));
}

フォームのデザインは、実行画面とMainForm.hの内容から適当に作って下さい。

Codex君も使えそうですけど、ここから本来の目的の”Vox”とかは無理かもしれません。

コメント