第10回 簡易サウンドプレイヤーの作成

いやー、やっとアプリらしいものを作れるようになりました。
記念すべき10回目はサウンドプレイヤーです。
クラスとしては、BSimpleGameSound、BFileGameSoundが登場します。
BSimpleGameSoundは再生タイミング重視の小さいサウンドファイル再生用のクラス、
BFileGameSoundはタイミングを重視しない大きいサウンドファイル再生用のクラスです。
R4.5になってから追加されたこれらのクラスで、音楽再生が随分と楽になりました。
今回のサンプルでは、ファイルの再生用には主にBFileGameSoundを使い、
最初のミキサーの初期化にBSImpleGameSoundを使用しています。


今回のサンプルソースです。

// sample08.cpp

#include <Application.h>        // BApplicationを使うために必要
#include <Window.h>             // BWindowを使うために必要
#include <View.h>               // BViewを使うために必要
#include <Alert.h>			  // BAlertを使うために必要
#include <StringView.h>		  // BStringViewを使うために必要
#include <Entry.h>			  // entry_ref構造体を使うために必要
#include <FileGameSound.h>	  // BFileGameSoundを使うために必要
#include <SimpleGameSound.h>   // BSimpleGameSoundを使うために必要
#include <MediaDefs.h>          // エンディアン情報のために必要

// クラスの前方宣言
class MyApp;
class MyWindow;
class MyView;

class MyApp : public BApplication
{
public:
	MyApp();
private:
	MyWindow* m_win;
};

class MyWindow : public BWindow
{
public:
	MyWindow(BRect frame,const char* title,window_type type,uint32 flags);
	virtual bool QuitRequested();	// 終了が要求されたときに呼ばれる関数
private:
	MyView* m_view;
};
 
class MyView : public BView
{
public:
	MyView(BRect frame,const char *name,uint32 resizingMode,uint32 flags);
	~MyView();
	virtual void MessageReceived(BMessage* message); 
	virtual void MouseDown(BPoint point); // マウスがクリックされると呼ばれる関数
private:
	void AutoFitStringView(const char* string);  // BStringViewを適切にリサイズする
	BStringView* m_string_view; // 表示する文字列

	void PlaySound(const entry_ref* ref);	 // ファイルの再生
	BFileGameSound* m_sound;    // 再生するサウンド
};

MyApp::MyApp()
      :BApplication("application/x-vnd.big56-MyApp")
{
	m_win = new MyWindow(BRect(100,100,300,150),"簡易音楽再生プレイヤー",B_TITLED_WINDOW,0);
	m_win->Show();
}

MyWindow::MyWindow(BRect frame,const char* title,window_type type,uint32 flags)
		:BWindow(frame,title,type,flags)
{
	frame.OffsetTo(0,0);  // 与えられたBRectの左上が(0,0)になるようにする

	m_view = new MyView(frame,"theView",B_FOLLOW_ALL_SIDES,B_WILL_DRAW);
	AddChild(m_view);	
}

bool MyWindow::QuitRequested()
{
// アプリケーションを終了する
	be_app->Quit();
	return true;
}

MyView::MyView(BRect frame,const char* title,uint32 resizingMode,uint32 flags)
	  :BView(frame,title,resizingMode,flags)
{
	m_sound = NULL; // 最初は音楽は再生されていない

	gs_audio_format init_format;  // ミックスする音のフォーマット指定
	init_format.frame_rate = 44100.0f;			     // サンプリング周波数は44.1KHz
	init_format.channel_count = 2;			     // ステレオサウンド
	init_format.format = gs_audio_format::B_GS_S16; // 量子化ビットは16ビット
	init_format.byte_order = B_MEDIA_HOST_ENDIAN;  // エンディアンの指定
	init_format.buffer_size = 0;				     // バッファサイズは0

	// サウンドミキサーの初期化
	BSimpleGameSound* init_sound = new BSimpleGameSound(NULL,2,&init_format);
	delete init_sound;
	
	m_string_view = new BStringView(BRect(10,10,150,30),"thStringView","ファイルをドロップしてください");
	AutoFitStringView("ファイルをドロップしてください");
	
	AddChild(m_string_view);
}

MyView::~MyView()
{
	delete m_sound; // サウンド再生時に終了したら、サウンドの解放
}

void MyView::MessageReceived(BMessage* message)
{
	entry_ref ref; // ファイルを表す構造体
	
	switch(message->what)
	{
	// ファイルがドラッグされたときに、ビューはB_SIMPLE_DATAというメッセージを受け取る
	// その時、"refs"というキーで、ファイルを示すentry_refという構造体がメッセージに含まれる
	// entry_refのnameというフィールドにファイル名(フルパスではない)が入っている
	case B_SIMPLE_DATA:
		// entry_ref構造体が正常に取得できたら処理を開始
		if (message->FindRef("refs",&ref) == B_OK)		
		{
			AutoFitStringView(ref.name);
			m_string_view->SetText(ref.name);
			PlaySound(&ref);	// サウンドの再生
		}
		break;
	default: 
		BView::MessageReceived(message);
		break;
	}
}

// 与えられた文字列に対して、m_string_viewを適切な長さに設定する
void MyView::AutoFitStringView(const char* string)
{
	BFont font(be_plain_font);	// be_plain_fontはユーザーがシステム設定しているフォント
	font_height f_height;

	float width,height; // 文字の幅と高さ

	// StringWidthで文字列の幅の長さを取得出来る
	width = font.StringWidth(string);  

	// 文字の高さはまずfont_height構造体を取得する必要がある
	font.GetHeight(&f_height);   

	// アセントどデセントを足した値がフォントの高さ	
	height = f_height.ascent + f_height.descent;  

	m_string_view->ResizeTo(width,height);
}

// 音楽を再生する
// 引数は、再生するファイルのentry_ref構造体
void MyView::PlaySound(const entry_ref* ref)
{
	// あらかじめ音楽が再生されていたら、現在のメモリを解放
	// そうでない場合は、m_soundはNULLだが、
	// NULLをdeleteしても安全なのである。
	delete m_sound;

	m_sound = new BFileGameSound(ref);
	if (m_sound->InitCheck() == B_OK) // サウンドクラスが適切に初期化されたか?
	{
		m_sound->StartPlaying();
	}
	else
	{
		delete m_sound;
		m_sound = NULL;
	}
}

// ビュー上でマウスがクリックされた時の処理
// 音楽が再生されていたら音楽を止める
void MyView::MouseDown(BPoint point)
{
	if (m_sound != NULL)
	{
		m_sound->StopPlaying();	
		delete m_sound;
		m_sound = NULL;
	}
}

int main(int argc,char** argv)
{
 	MyApp app;
	app.Run();
	return 0;
}

今回の実行結果です。

実行結果

前回と変わりません。しかし、音楽ファイルをドロップすれば音が聞けます。
この画面から、メディアOSらしさを感じ取っていただければ幸いです。

サウンドの再生中にウィンドウの中をクリックすると、音が止まります。
音楽以外のファイルをドロップした場合は、ファイル名が表示されるだけです。


このアプリは、サウンドファイルが再生されているかどうかを
メンバ変数のm_soundのポインタがNULLかどうかで判定しています。
MyViewのコンストラクタでm_soundをNULLに初期化しています。

m_sound = NULL; // 最初は音楽は再生されていない

そして、後のMouseDown()関数の中で、m_soundがNULLかどうかを調べて、
ファイルの再生を止めています。

// ビュー上でマウスがクリックされた時の処理
// 音楽が再生されていたら音楽を止める
void MyView::MouseDown(BPoint point)
{
	if (m_sound != NULL)
	{
		m_sound->StopPlaying();	
		delete m_sound;
		m_sound = NULL;
	}
}

MouseDown()関数は、マウスがクリックされるとBeOSのシステムが呼ぶ関数で、
受け取った引数には、マウスがクリックされた座標が入っています。
今回のサウンドプレイヤーでは、どの部分をクリックしても、
サウンドを止めるという動作は変わらないので、この引数を使用していません。


次にサウンドミキサーの初期化です。
サウンドミキサーとは、システムが音楽を再生するときに音を合成しているものです。
BeOSではサウンドを複数ならしても、システムが適切に音を合成してくれます。
さすが、メディアOSと言いたいところですが、音を合成する際に、
どのような周波数や量子化ビットで合成するかを指定する必要があります。

BSimpleGameSoundやBFileGameSoundなどを使用して、音を再生する時の、
サウンドミキサーの設定は、最初に読み込まれたサウンドファイルのフォーマットに依存します。
これだと、最初にへなちょこなサウンドファイル(モノラルだったり、サンプリング周波数が低かったりするもの)を読み込むと
その後に読み込んだ高音質なサウンドファイルもすべてへなちょこになります!
これはうれしくないので、最初にサウンドミキサーの設定を決めるためのダミーのBSimpleGameSoundを用意して
そのインスタンスを生成した直後に、解放するということを行っています。

その時のコードを見てみましょう。

gs_audio_format init_format;  // ミックスする音のフォーマット指定
	init_format.frame_rate = 44100.0f;			     // サンプリング周波数は44.1KHz
	init_format.channel_count = 2;			     // ステレオサウンド
	init_format.format = gs_audio_format::B_GS_S16; // 量子化ビットは16ビット
	init_format.byte_order = B_MEDIA_HOST_ENDIAN;  // エンディアンの指定
	init_format.buffer_size = 0;				     // バッファサイズは0

	// サウンドミキサーの初期化
	BSimpleGameSound* init_sound = new BSimpleGameSound(NULL,2,&init_format);
	delete init_sound;

このコードでは、一般的な音楽CDのフォーマットに合わせています。
ここで、gs_audio_format構造体というものが初登場しています。
この構造体の持つフィールドは次の表の様になっています。

フィールド名変数の型説明
frame_ratefloatサンプリング周波数
channel_countuint32チャンネル数(モノラルだと1、ステレオだと2)
formatuint32音楽フォーマット、詳しくは後述
byte_orderuint32リトルエンディアンかビッグエンディアンか
buffer_sizesize_tバッファサイズ

frame_rateはサンプリング周波数の指定です。
サンプリング周波数とは1秒間に何回波形の値をとるかということを示します。
例えば、44.1KHzと言ったら、1秒間に44100回、元の波形の値をサンプリングするということです。

formatに関しては、次の定数を指定することが出来ます。

定数名説明
B_GS_U8符号なし8ビット量子化
B_GS_U16符号あり16ビット量子化
B_GS_S32符号あり32ビット量子化
B_GS_F符号あり浮動小数点形式で格納

これらは、ある地点の音楽波形の量(ゲイン)がサウンドファイル内でどの形で格納されているかを示すものです。
量子化とは、ゲインの値を格納するのに何ビット使うかということです。
例えば、8ビット量子化と言ったら、ゲインの値は256段階に分けられて格納され、
16ビット量子化と言ったら65536段階に分けられて格納されます。

byte_orderフィールドには、再生する音楽のエンディアンを指定します。
エンディアンとは、変数のメモリ上での並びに関するものです。
あまり詳しくは説明しませんが、ビッグエンディアンは人間から見て素直な並び方、
リトルエンディアンは1バイトごとに逆順に並んだ並び方のことを言います。
知識としては、PowerPC版がビッグエンディアン、Intel版がリトルエンディアンということを覚えていれば十分です。
ここで指定している、B_MEDIA_HOST_ENDIANとは、MediaDefs.hで定義されている変数で、
Mac上でコンパイルするか、Intel上でコンパイルするかによって値の変わる変数です。
byte_orderフィールドにはこれを指定しておけば間違いないと思います。

buffer_sizeはバッファサイズの指定をしますが、ダミーのインスタンスを生成するために、0を指定しています。

さて、フォーマットを指定済みのgs_audio_format構造体を作成したら、
いよいよダミーのBSimpleGameSoundのインスタンスを生成します。
BSimpleGameSoundのコンストラクタは次の様になっています。

BSimpleGameSound(
	const void * inData,
	size_t inFrameCount,
	const gs_audio_format * format,
	BGameSoundDevice * device = NULL);

inDataは、再生するサウンドデータ配列へのポインタで、今回はダミーなのでNULLを指定しています。
inFrameCountはオーディオのフレーム数の指定をします。
fotmatは先程作成したgs_audio_format構造体へのポインタを指定します。
deviceはサウンドデバイスの指定で、NULLを指定するとデフォルトのデバイスを使用します。
現在は絶対にNULLで無くてはならないとのことです。将来的に複数サウンドデバイスを取り付けて、
ユーザーに選択出来るようにする予定なのでしょう。

以上の様にダミーを作って、ミキサーのフォーマットを決めると、
Beメニューの[Preference] - [Media]の画面が次の様になります。

Media画面

左から二つめのSoundAppというのが今回作成したアプリケーションですが、
44.1KHz 16bitと書かれているのがわかると思います。
これで、このアプリケーションはオーディオミキサーに接続が終了し、
音楽を再生したときに44.1KHz 16bitで合成されるようになりました。


さて、次に音楽を実際に再生するコードの紹介です。

// 音楽を再生する
// 引数は、再生するファイルのentry_ref構造体
void MyView::PlaySound(const entry_ref* ref)
{
	// あらかじめ音楽が再生されていたら、現在のメモリを解放
	// そうでない場合は、m_soundはNULLだが、
	// NULLをdeleteしても安全なのである。
	delete m_sound;

	m_sound = new BFileGameSound(ref);
	if (m_sound->InitCheck() == B_OK) // サウンドクラスが適切に初期化されたか?
	{
		m_sound->StartPlaying();
	}
	else
	{
		delete m_sound;
		m_sound = NULL;
	}
}

ここで特筆すべきことは、これだけのコードでWAVやAIFFは言うに及ばずMP3やMPEG、AVIのサウンドまで再生出来ることです!
さすが、メディアOS。凄い、凄すぎるぜメディアOS!

サウンドを再生する場合には、
インスタンスでファイルを指定→InitCheck()で初期化のチェック→StartPlaying()関数で再生
という流れになります。

ファイルの指定方法はentry_ref構造体を使う方法以外にも、直接パス名を渡す方法があります。

BFileGameSound(
	const entry_ref * file,
	bool looping = true,
	BGameSoundDevice * device = NULL);

BFileGameSound(
	const char * file,
	bool looping = true,
	BGameSoundDevice * device = NULL);

BFileGameSoundのコンストラクタはこの2バージョンあり、第1引数でentry_ref構造体か、パス名を指定します。
loopingは、その名の通り再生が終了したらループするかどうかです。デフォルトでは、trueになっています。
deviceは先程説明したものと同じです。

次にInitCheck()関数で初期化のチェックをしていますが、ここでサウンドファイル以外のものを読み込んだら
エラーとしてはねられるようになります。
この関数の戻り値の表を次に示します。

説明
B_OKOKっす
B_ERRORサウンド再生のための準備が出来なかった
B_NO_MEMORYメモリー不足

この、インスタンス生成後、InitCheck()関数で初期化チェックというパターンは
他のBeOSのクラスでも見られることなので、覚えておいてもいいでしょう。

StartPlaying()関数で音の再生を開始します。
この関数を、サウンド再生中に呼び出すと、そのサウンドをまた最初から再生し直します。
もし、同じサウンドを何回も合成して再生したいのなら、Clone()というメンバ関数によって、
インスタンスの複製が出来る(戻り値が複製へのポインタ)ので、それを利用するのもいいでしょう。


参考までにBSimpleGameSoundのコンストラクタは次の様になっています。

BSimpleGameSound(
	const entry_ref * inFile,
	BGameSoundDevice * device = NULL);

BSimpleGameSound(
	const char * inFile,
	BGameSoundDevice * device = NULL);

ループするかしないかの、引数が無くなっています。
BSimpleGameSoundの場合、ループはSetIsLooping()関数で指定します。
なぜ、BFileGameSoundとBSimpleGameSoundで別々の仕様なのかは不明ですが、
おそらくサウンドのループを実現している実装が異なるからだと思います。

後のファイル再生のコードに関しては、BFileGameSoundと全く同じです。
InitCheck()で初期化チェック、StartPlaying()で再生開始です。


さて、メインのコード紹介は終わりましたが、BFileGameSoundとBSimpleGameSoundの他の面白い機能の紹介をしたいと思います。

まずは、音量(ゲイン)の設定です。

status_t SetGain(
	float pan,
	bigtime_t duration = 0);

	float Gain();

SetGain()関数で、ゲインの設定をして、Gain()関数で現在のゲインの取得をします。
第1引数、gainには0.0〜1.0までの値を指定することが出来ます。
1.0が最大音量、0.0は音無しです。
durationは、設定したゲインの効果を何マイクロ秒後に出すか?という設定です。
0を指定すると、ゲインの効果は直ちに現れます。
戻り値は、B_OKでOK、B_ERRORで、エラーを示します。

次に音の位置(パン)の設定です。
パンの設定によって、ステレオスピーカーの環境では、音を左に寄らせたり、右に寄らせたり出来ます。

status_t SetPan(
	float gain,
	bigtime_t duration = 0);

	float Pan();

SetPan()関数でパンの設定、Pan()関数で現在のパンの取得が出来ます。
第1引数のpanには、-1.0〜1.0の値が指定出来ます。
-1.0が完全に左に寄った状態、1.0が完全に右に寄った状態です。
durationはSetGain()関数と同じく、パンの効果を何マイクロ秒後に出すか?ということです。
戻り値もSetGain()関数と同じです。
小刻みにパンを左から右、または右から左に変えることによって、音に関して面白い効果を出すことが出来そうです。

最後にサウンド属性の全般の設定です。
これは、gs_attribute構造体というものを使用して、統一的に行うことが出来ます。
まず、属性変更の関数、SetAttribute()関数と、属性取得関数GetAttribute()関数を見てみましょう。

status_t SetAttributes(
	gs_attribute * inAttributes,
	size_t inAttributeCount);

status_t	GetAttributes(
	gs_attribute * outAttributes,
	size_t inAttributeCount);

SetAttribute()関数の第1引数inAttributeには、gs_attribute構造体の配列へのポインタ、
inAttributeCountは、inAttributeの配列の個数を示します。
第2引数はsizeof(gs_attribute)などの、構造体のサイズ指定で無いことに注意してください。

GetAttribute()関数は、第1引数outAttributeに、gs_attribute構造体の配列を受け取るためのポインタ、
第2引数には、同じく配列の個数を示します。

次にgs_attribute構造体の説明です。

フィールド名説明
attributeどの属性か
duration変更した属性の効果を何マイクロ秒後に出すか
value変更する属性の値
flagsその他のフラグ

この中でflagsに関しては、現在はBeBookを見るかぎり
常に0を指定しておけば大丈夫だと思います。

attributeに指定出来る値は次の通りです。

説明
B_GS_MAIN_GAINメインゲイン
B_GS_CD_THROUGH_GAINCDを通したゲイン(?)
B_GS_GAINゲイン
B_GS_PANパン
B_GS_SAMPLING_RATEサンプリング周波数
B_GS_LOOPINGループ

ここで、特筆すべきはB_GS_SAMPLING_RATEです。
サンプリングレートを変更することにより、いわゆる倍速再生などの効果が出せます。
例えば、もともと44.1KHzのファイルを、サンプリング周波数22.05KHzに設定すると、
半分の速度で再生することが出来ます(音が低くなる)。
逆に88.2KHzに設定すると、倍速で再生出来ます(音が高くなる)。
これを使用しても面白い効果が出せそうですね。


さて、記念すべき10回目はいかがでしたでしょうか?
実際の再生コードよりもミキサーの初期化の方に説明が集中してしまいましたが、
ここの部分のコードは多分常に同じになると思いますので、そんなに難しくないでしょう。
第5回と同様に異なったフォーマットのファイルでもわずかなコードで同じように読み込めてしまう
ということがおわかりいただけたでしょうか?
ここらへんから、BeOSでのプログラミングが楽しくなっていくのではないでしょうか。

[<<前へ] [次へ>>] [戻る]

big56 big56@anet.ne.jp
生命保険の切り替えはココ 過払い金の回収ならこちら 海外旅行保険の加入はコチラ!
[PR] | ヒーリング会社案内 作成se 転職川口栃木荻窪池袋中国SEO対策消費者金融車 買取テンプレート沖縄旅行免許合宿二輪引越しプレゼントゴルフ会員権留学レーシックマッサージFXアフィリエイトFXホームページ制作デイトレードハワイ旅行タイバンコクハワイ レンタカーベスト ハワイ ホテル レーツバリ島Hawaii hotelsHawaii Activitiesbhhrハワイホテルテキスト広告
【運営会社「パラダイムシフト」サービス】 ハワイ現地オプショナルツアーリラックマ) - ビジネスクラス航空券 - 格安航空券(1) - 格安航空券(2) - 海外ホテル - 韓国旅行 - タイムシェア - ホテル 予約
無料ホームページ - 携帯ホームページ - 無料ホームページ作成 - レンタルサーバー - ブログ - ヴィラ - ハワイ コンドミニアム - バリ島 ホテル - プーケット ホテル - 旅行 口コミ - 旅行情報 - 国際電話 - ホノルルマラソン - 掲示板監視 - 風評被害 - ホテル比較 - ノースウェスト航空 - ファイナルチェッカー