C++ Builder CE サウンドプログラミング

2.Windowsのサウンド出力のレベルをリアルタイム表示するプログラムを作る

まずはフォームのデザインから示します。珍しくFMXベースです。

縦長の棒二本は、TPaintBoxです。他にTTimerが一つ、TCheckBoxTButtonもそれぞれ一個づつ置きます。プログラム全体の制御はTimer1なるインスタンスが統括します。その割り込み周期は、25msで、秒40回サウンド出力をサンプリングして逐次左右のチャンネル別にPaintBox1とPaintBox2に表示するというだけのことです。サウンド出力を取ってくるのは、前回の関数の親戚の

float apeak[2];
hr = pMeterInfo->GetChannelsPeakValues(chcount,apeak);

を使います。画面表示と並行してWS2812Bタイプの円環状のLED(24素子)に出力レベルを”ピークレベルを適宜残しながら適当に”表示します。USB-RS232CのI/Fがつながれていなくてもおけです。エラーにはなりません。さて、全体のソースは、

//---------------------------------------------------------------------------
#include <fmx.h>
#pragma hdrstop

#include <mmdeviceapi.h>
#include <endpointvolume.h>


#include "Unit1.h"
#include "ws2812b.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"

#define TIMER_PERIOD 25

// "安全な"COMポインタの解放処理マクロ
#define SAFE_RELEASE(p) if(p){ p->Release(); p=NULL; }

IMMDeviceEnumerator *pEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioMeterInformation *pMeterInfo = NULL;
float apeak[6] = {0,0,0,0,0,0};
float last1=0;
float last2=0;

DWORD now,lasttime,lasttime2;

UINT    chcount;
ws2812b* aled;//= new ws2812b(NUMLEDS, leds);

#define NUMLEDS 24


float lastl=0;

float lasth=0;

CCRGB leds[NUMLEDS];
CCRGB tables[NUMLEDS];

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
// set size ?
	Form1->Width = 73;
	Form1->Height = 461;

HRESULT hr;
CoInitialize(NULL);
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
						NULL, CLSCTX_INPROC_SERVER,
						__uuidof(IMMDeviceEnumerator),
						(void**)&pEnumerator);
   if ( FAILED(hr) ) {
		ShowMessage("CoCreateInstance failed");
		return;
	}
hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
  if ( FAILED(hr) ) {
		ShowMessage("GetDefaultAudioEndpoint  failed");
		return;
	}
hr = pDevice->Activate(__uuidof(IAudioMeterInformation),
						CLSCTX_ALL,NULL,(void**)&pMeterInfo);
	  if ( FAILED(hr) ) {
		ShowMessage("device activate failed");
		return;
	}
		hr = pMeterInfo->GetMeteringChannelCount(&chcount);
	if( FAILED(hr)){
		ShowMessage("get ch count failed");
		return;

	}

	aled = new ws2812b(NUMLEDS, leds);
	aled->clear();

	int steps = 12;
	int i;
	float n;
	unsigned char r,g,b;
	char    obuf[80];

	for( i = 0 ; i < steps ; i++ ){
		n = (float)i/(float)(steps-1);
		r = (float)0*(1.0f-n) + (float)(0x20)*n;
		g = (float)(0x20)*(1.0f-n) + (float)(0x20)*n;
		b = (float)0*(1.0f-n)+ (float)0*n;
		tables[i].setRGB(r,g,b);
		tables[23-i].setRGB(r,g,b);

	}
	Timer1->Interval = TIMER_PERIOD;
	Timer1->Enabled = true;

}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
	SAFE_RELEASE(pEnumerator);
	SAFE_RELEASE(pDevice);
	SAFE_RELEASE(pMeterInfo);
	CoUninitialize();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
	HRESULT hr;
	int numl,numh;
	char    obuf[80];

	hr = pMeterInfo->GetChannelsPeakValues(chcount,apeak);
	if( FAILED(hr)){
		ShowMessage("get ch values failed");
		Timer1->Enabled = false;
	}

	numl = apeak[0]*NUMLEDS/2;
	numh = NUMLEDS-1-apeak[1]*NUMLEDS/2;
	for( int k = 0 ; k < NUMLEDS ; k++ ){
		if( k <= numl || k >= numh){
			if( k <= numl )
				leds[k] = tables[k];
			else
				leds[k] = tables[29-k];
		}
		else
			leds[k].setRGB(0,0,0);
	}
	if( numl == 0 )
		leds[0].setRGB(0,0,0);
	if( numh == NUMLEDS-1 )
		leds[NUMLEDS-1].setRGB(0,0,0);

	/* peak and hold */

	if( apeak[0] < lastl ){
		//leds[(int)(lastl*NUMLEDS/2)].setRGB(30,0,0);
		unsigned char r,g,b;
		r = tables[(int)(lastl*NUMLEDS/2)].r;
		g = tables[(int)(lastl*NUMLEDS/2)].g;
		b = tables[(int)(lastl*NUMLEDS/2)].b;
		leds[(int)(lastl*NUMLEDS/2)].setRGB(3*r,0,0);
		lastl = apeak[0];
		lastl -= 0.12;

	}
	else
		lastl = apeak[0];
	if( apeak[1] < lasth ){
		unsigned char r,g,b;
		r = tables[NUMLEDS-(int)(lasth*NUMLEDS/2)].r;
		g = tables[NUMLEDS-(int)(lasth*NUMLEDS/2)].g;
		b = tables[NUMLEDS-(int)(lasth*NUMLEDS/2)].b;
		leds[NUMLEDS-(int)(lasth*NUMLEDS/2)].setRGB(3*r,0,0);
		lasth = apeak[1];
		lasth -= 0.12;
	}
	else
		lasth = apeak[1];
	/* */
	aled->showleds();
	PaintBox1->Repaint();
	PaintBox2->Repaint();
}
//---------------------------------------------------------------------------

void __fastcall TForm1::PaintBox1Paint(TObject *Sender, TCanvas *Canvas)
{
	TAlphaColor t;
	TRectF var;
	float start;
	if( Dot->IsChecked )
		start = 360*(apeak[0]-0.01);
	else
		start = 0;
	for( float i = start ; i <360*apeak[0] ; i += 1.0 ){
		t = HSLtoRGB(i/360,0.75,0.5);
		PaintBox1->Canvas->Fill->Color = t;
		var = TRectF(0,360-i,this->Width,360-i-1);
		PaintBox1->Canvas->FillRect(var,0,0,AllCorners,1.0);
	}
	if( last1 > apeak[0] ) {
		t = HSLtoRGB(0,0.75,0.5);
		PaintBox1->Canvas->Fill->Color = t;
		var = TRectF(0,360-last1*360-2,this->Width,360-last1*360+2);
		PaintBox1->Canvas->FillRect(var,0,0,AllCorners,1.0);
		last1 -= 0.01;
	}
	else
		last1 = apeak[0];
	//last1 = apeak[0];


}

//---------------------------------------------------------------------------
void __fastcall TForm1::PaintBox2Paint(TObject *Sender, TCanvas *Canvas)
{
//	static float lastvalue = 0.0;
	TAlphaColor t;
	TRectF var;
	float start;
	if( Dot->IsChecked )
		start = 360*(apeak[1]-0.01);
	else
		start = 0;

	for( float i = start ; i <360*apeak[1] ; i += 1.0 ){
		t = HSLtoRGB(i/360,0.75,0.5);
		PaintBox2->Canvas->Fill->Color = t;
		//var = TRectF(i,0,i+1,this->Height);
		var = TRectF(0,360-i,this->Width,360-i-1);
		PaintBox2->Canvas->FillRect(var,0,0,AllCorners,1.0);
	}
	//plot previous peak if any
	if( last2 > apeak[1] ) {
		t = HSLtoRGB(0,0.75,0.5);
		PaintBox2->Canvas->Fill->Color = t;
		//var = TRectF(last2*360-2,0,last2*360+2,this->Height);
        var = TRectF(0,360-last2*360-2,this->Width,360-last2*360+2);
		PaintBox2->Canvas->FillRect(var,0,0,AllCorners,1.0);
		last2 -= 0.01;
	}
	else
		last2 = apeak[1];
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
	aled->clear();
    Application->Terminate();
}
//---------------------------------------------------------------------------

これに加えて、WS2812Bの回で取り上げたSerialPort.cpp,SerialPort.h,ws2812b.cpp,ws2812b.hをプロジェクトに加えてからBuildします。起動してみた時の動画を以下に示します。

音源は、BGMerさんの作品で、

これを使わせていただいています。ダウンロードリンクは、https://bgmer.net/wp-content/uploads/2022/05/294_BPM88.mp3です。さて動画の話に戻ると、サウンド出力レベルの記録動画と別テイクのLED発光の動画を合成しています。なるべく頭を合わせたつもりですが、少しだけズレているかもしれません。また前回消費電流が0mAとなっていましたが、今回のピーク時の電流値は60~70mAなので、それなりに消費してますが、まだ余裕はあるかもしれません。5V電源はI/Fからのを使っています。電圧は安定して5V程度ですね。

コメント