第11回 カーブかフォークかSliderか


今回はBSliderについてです。
前回、BFileGameSoundに音量とパンとサンプリングレート変更機能があると説明しましたが、
BSliderを使って、それらをリアルタイムに設定出来るサウンドプレイヤーの作成をします。
BSliderをドラッグしてぐりぐりやると、音が早くなったり、音の位置を変えられたり出来るわけです。

BSliderはただ使うにしても、今までのコントロールとは違い、カスタマイズ出来る項目が非常に多いです。
このコントロールはボリュームなどのパラメータを調整するための、いわゆる「つまみ」の役目を果たすので、
メディアOSって方向性を決めたときに、かなり気合いを入れて作ったんじゃないかと思います。

あと、これはBeのサンプルコードのgameplayをかなり参考にしています。
ファイルの読み込み方がgameplayとは異なりますが、そちらの方も参考になされてはいかがでしょうか?(ただし英語です)

// sample09.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>          // エンディアン情報のために必要 
#include <Slider.h>             // BSliderを使うために必要

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

// メッセージ定数
const uint32 VOLUME_CHANGED = 'voch';
const uint32 RATE_CHANGED = 'rach';
const uint32 PAN_CHANGED = 'pach';

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);
	virtual void AttachedToWindow();
private:
	void AutoFitStringView(const char* string);  // BStringViewを適切にリサイズする
	void PlaySound(const entry_ref* ref);	 // ファイルの再生
	void SetSamplingRate(float rate);   // サンプリングレートの変更
	void SliderValueInit();		      // スライダーの値を初期値に設定

	BSlider* m_volume_slider;  // ボリューム調整スライダー
	BSlider* m_pan_slider;       // パン調整スライダー 
	BSlider* m_rate_slider;      // サンプリングレート調整スライダー

	BStringView* m_string_view; // 表示する文字列
	BFileGameSound* m_sound;    // 再生するサウンド
};

MyApp::MyApp()
      :BApplication("application/x-vnd.big56-MyApp")
{
	m_win = new MyWindow(BRect(100,100,330,250),"簡易音楽再生プレイヤー",B_TITLED_WINDOW,B_ASYNCHRONOUS_CONTROLS);
	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),"theStringView","ファイルをドロップしてください");
	AutoFitStringView("ファイルをドロップしてください");
	AddChild(m_string_view);

	// ボリュームスライダー作成
	m_volume_slider = new BSlider(BRect(160,40,220,150),"theVolumeSlider",
							"ボリューム",new BMessage(VOLUME_CHANGED),
							0,100,B_VERTICAL);
	m_volume_slider->SetModificationMessage(new BMessage(VOLUME_CHANGED));
	m_volume_slider->SetLimitLabels("Min","Max");    // 両端のラベル設定
	AddChild(m_volume_slider);

 	// パンスライダー作成
	m_pan_slider = new BSlider(BRect(10,40,150,60),"thePanSlider",
							"パン",new BMessage(PAN_CHANGED),
							-100,100,B_TRIANGLE_THUMB);
	m_pan_slider->SetModificationMessage(new BMessage(PAN_CHANGED));
	m_pan_slider->SetLimitLabels("左","右");          // 両端のラベル設定
	m_pan_slider->SetHashMarks(B_HASH_MARKS_BOTH); // マークをつける
	m_pan_slider->SetHashMarkCount(5);			// マークの数は5つ
	AddChild(m_pan_slider);

	// サンプリングレートスライダー作成
	m_rate_slider = new BSlider(BRect(10,100,150,120),"theRateSlider",
							"曲の早さ",new BMessage(RATE_CHANGED),
							50,200);
	m_rate_slider->SetModificationMessage(new BMessage(RATE_CHANGED));
	m_rate_slider->SetLimitLabels("1/2","×2");           // 両端のラベル設定
	m_rate_slider->SetHashMarks(B_HASH_MARKS_BOTTOM); // マークをつける
	m_rate_slider->SetHashMarkCount(4);                 // マークの数は4つ
	AddChild(m_rate_slider);

	SliderValueInit(); // スライダーの値の初期化


}

MyView::~MyView()
{
}

// スライダーのメッセージ先の設定
void MyView::AttachedToWindow()
{
	m_volume_slider->SetTarget(this);
	m_pan_slider->SetTarget(this);
	m_rate_slider->SetTarget(this);
}

void MyView::MessageReceived(BMessage* message)
{
	entry_ref ref; // ファイルを表す構造体
	int32 value;    // スライダーの値
	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);
			SliderValueInit();
		}
		break;
	case VOLUME_CHANGED:	// ボリューム変更
		if (m_sound != NULL)
		{
			message->FindInt32("be:value",&value);
			m_sound->SetGain(value / 100.0f);	// ゲイン変更
		}
		break;
	case RATE_CHANGED:      // 曲の早さ変更
		if (m_sound != NULL)
		{
			message->FindInt32("be:value",&value);
			SetSamplingRate(value / 100.0f);   // サンプリングレート変更
		}
		break;
	case PAN_CHANGED:       // パン変更
		if (m_sound != NULL)
		{
			message->FindInt32("be:value",&value);
			m_sound->SetPan(value / 100.0f);  //  パンの変更
		}
		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しても安全なのでOKである。
	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)
{
	if (m_sound != NULL)
	{
		m_sound->StopPlaying();	
		delete m_sound;
		m_sound = NULL;
	}
}

// サンプリングレート変更
void MyView::SetSamplingRate(float rate_times)
{
	gs_attribute attr;
	attr.attribute = B_GS_SAMPLING_RATE;  // サンプリングレートの変更
	attr.duration  = 0;					// 変更は直ちに行う
									// 元のサンプリングレートに、指定された値を掛ける
	attr.value     = m_sound->Format().frame_rate * rate_times; 
	attr.flags     = 0;                      // その他のフラグはなし

	m_sound->SetAttributes(&attr,1);
}

// スライダーの値の初期化
void MyView::SliderValueInit()
{
	m_volume_slider->SetValue(100);
	m_pan_slider->SetValue(0);
	m_rate_slider->SetValue(100);
}

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

今回のサンプルの実行結果は次のようになります。

実行結果

前回のサウンドプレイヤーにスライダーが3つついて、ボリュームとパンと曲の早さが調整出来るようになりました。
スライダーをドラッグしたら直ちに音に変化が現れるので、いろいろやってみるといいでしょう。


まず、最初の注意点は、ウィンドウを作成するときのフラグについてです。

m_win = new MyWindow(BRect(100,100,330,250),"簡易音楽再生プレイヤー",B_TITLED_WINDOW,B_ASYNCHRONOUS_CONTROLS);

最後にB_ASYNCHRONOUS_CONTROLSを指定しています。
これは、スライダーがマウスをドラッグしたときに、メッセージを発行するために絶対必要なものです。
これをつけないと、スライダーをマウスでドラッグしても、マウスボタンを放した時にしかメッセージを送信しません。
今回のアプリケーションだとボリューム変更がマウスを放したときにしか行われなくなってしまいます。
今回のサンプル作成の時に、これで30分程はまりました(^^;
まぁ、それでも特に不都合があるってわけじゃないんですが、出来ればマウスをドラッグした瞬間に
ボリュームなどのパラメータは変化して欲しいですよね。
実際、このフラグを外して実行してみればどのような感じかわかると思います。

次に、スライダーの作成です。
今回のサンプルでは、

という3通りの方法でスライダーを作成しています。
縦向きでつまみが三角というのも当然できます。

まずは縦向きでつまみが四角のボリュームスライダーの作成です。

// ボリュームスライダー作成
	m_volume_slider = new BSlider(BRect(160,40,220,150),"theVolumeSlider",
				"ボリューム",new BMessage(VOLUME_CHANGED),
				0,100,B_VERTICAL);

まずは、BSliderクラスのコンストラクタについて解説する必要があるでしょう。

BSlider(BRect frame,
	const char *name,
	const char *label,
	BMessage *message,
	int32 minValue,
	int32 maxValue,
	thumb_style thumbType = B_BLOCK_THUMB,
	uint32 resizingMode = B_FOLLOW_LEFT |B_FOLLOW_TOP,
	uint32 flags = B_NAVIGABLE | B_WILL_DRAW |B_FRAME_EVENTS);

BSlider(BRect frame,
	const char *name,
	const char *label,
	BMessage *message,
	int32 minValue,
	int32 maxValue,
	orientation posture,
	thumb_style thumbType = B_BLOCK_THUMB,
	uint32 resizingMode = B_FOLLOW_LEFT |B_FOLLOW_TOP,
	uint32 flags = B_NAVIGABLE | B_WILL_DRAW |B_FRAME_EVENTS);

コンストラクタは2種類ありますが、違っているのはスライダーの向きを指定するpostureだけです。
スライダーの向きを指定しないバージョンのコンストラクタでは、横向きのスライダーが作成されます。
コンストラクタの設定項目は、

となっています。

スライダーの大きさのframeに関しては、左上の点と右上のX座標のみ考慮するようです。
縦の長さが小さいからといって、スライダーのグラフィックが途中で切られるようなことはありません。

この中で、スライダの向き、postureには次の値を設定できます。

説明
B_HORIZONTAL水平のスライダー
B_VERTICAL垂直のスライダー

スライダーにつくツマミの形、thumbTypeについては次の値が設定出来ます。

説明
B_BLOCK_THUMB四角のつまみ
B_TRIANGLE_THUMB三角のつまみ

BSliderを構築した後、SetModificationMessage()というメンバ関数を使用しています。

m_volume_slider->SetModificationMessage(new BMessage(VOLUME_CHANGED));

SetModificationMessage()関数と、ModificationMessage()関数の宣言は次のようになっています。

virtual void SetModificationMessage(BMessage *message);
BMessage*    ModificationMessage() const;

SetModificationMessage()関数は、マウスが放されたときでなく、マウスのドラッグ中に送信するBMessageを指定します。
ここでは、コンストラクタと同じメッセージ、VOLUME_CHANGEDを指定しています。
これで、マウスがドラッグされると直ちにメッセージが送信され、ボリュームが変更されます。

ModificationMessage()関数はSetModificationMessage()関数で設定した、BMessageを返す関数です。
今回のサンプルでは使用していません。

次に、スライダーの最小値と最大値に表示される文字列の設定をしています。

m_volume_slider->SetLimitLabels("Min","Max");    // 両端のラベル設定

SetLimitLabels()関数と、MinLimitLabel()関数、MaxLimitLabel()関数の宣言です。

virtual	void 	SetLimitLabels(const char *minLabel,
		const char *maxLabel);
const char*	MinLimitLabel() const;
const char*	MaxLimitLabel() const;							

SetLimitLabels()関数では、第1引数minLabelに最小値の文字列を、第2引数maxLabelに最大値の文字列を指定します。
MinLimitLabel()関数では最小値の文字列、MaxLimitLabel()関数では最大値の文字列が返されます。

最後に作成したスライダーを、ビューの子供に登録して終わりです。

AddChild(m_volume_slider);

他のスライダーの作成方法を見てみましょう。

 	// パンスライダー作成
	m_pan_slider = new BSlider(BRect(10,40,150,60),"thePanSlider",
							"パン",new BMessage(PAN_CHANGED),
							-100,100,B_TRIANGLE_THUMB);
	m_pan_slider->SetModificationMessage(new BMessage(PAN_CHANGED));
	m_pan_slider->SetLimitLabels("左","右");          // 両端のラベル設定
	m_pan_slider->SetHashMarks(B_HASH_MARKS_BOTH); // マークをつける
	m_pan_slider->SetHashMarkCount(5);			// マークの数は5つ
	AddChild(m_pan_slider);

	// サンプリングレートスライダー作成
	m_rate_slider = new BSlider(BRect(10,100,150,120),"theRateSlider",
							"曲の早さ",new BMessage(RATE_CHANGED),
							50,200);
	m_rate_slider->SetModificationMessage(new BMessage(RATE_CHANGED));
	m_rate_slider->SetLimitLabels("1/2","×2");           // 両端のラベル設定
	m_rate_slider->SetHashMarks(B_HASH_MARKS_BOTTOM); // マークをつける
	m_rate_slider->SetHashMarkCount(4);                 // マークの数は4つ
	AddChild(m_rate_slider);

これら二つのスライダーには、SetHashMarks()関数とSetHashCount()関数というのが加わっています。

HashMarkとはいわゆる、スライダーの区切りのことです。
SetHashMarks()関数で、スライダーの区切りの配置する場所を設定します。
virtual void       SetHashMarks(hash_mark_location where);
hash_mark_location HashMarks() const;

ここで、SetHashMarks()関数に設定出来る値は次の通りです。

説明
B_HASH_MARKS_NONE区切りはつけない
B_HASH_MARKS_TOPスライダーの上側につける(水平のスライダーのみ)
B_HASH_MARKS_BOTTOMスライダーの下側につける(水平のスライダーのみ)
B_HASH_MARKS_LEFTスライダーの左側につける(垂直のスライダーのみ)
B_HASH_MARKS_RIGHTスライダーの右側につける(垂直のスライダーのみ)
B_HASH_MARKS_BOTHスライダーの両側につける

HashMarks()関数は、現在のスライダーの区切りの配置がどこかを返す関数です。

次にSetHashMarkCount()関数でスライダーの区切りの個数を設定します。

virtual void SetHashMarkCount(int32 count);
int32 	    HashMarkCount() const;

コンストラクタの宣言を見ていただければわかるかと思いますが、引数に区切りの個数をint32型で渡すだけです。
HashMarkCount()関数は、区切りの個数を返す関数です。

コンストラクタの最後でスライダーを初期値に設定するための関数、SliderValueInit()関数を呼んでいます。
なぜ別関数でやったかというと、サウンドファイルを読み込み直したときに、スライダーの位置も元に戻したいからです。

// スライダーの値の初期化
void MyView::SliderValueInit()
{
	m_volume_slider->SetValue(100);
	m_pan_slider->SetValue(0);
	m_rate_slider->SetValue(100);
}

スライダーのSetValue()という関数は、スライダーの現在の値を設定します。
スライダーのつまみの位置も、SetValue()で変更した値に応じて、変更されます。


これでスライダーの構築は終わりましたが、まだ実際にボリューム等を変更する個所が残っています。
MyView::MessageReceived()関数内のコードを次に示します。

case VOLUME_CHANGED:	// ボリューム変更
	if (m_sound != NULL)
	{
		message->FindInt32("be:value",&value);
		m_sound->SetGain(value / 100.0f);	// ゲイン変更
	}
	break;
case RATE_CHANGED:      // 曲の早さ変更
	if (m_sound != NULL)
	{
		message->FindInt32("be:value",&value);
		SetSamplingRate(value / 100.0f);   // サンプリングレート変更
	}
	break;
case PAN_CHANGED:       // パン変更
	if (m_sound != NULL)
	{
		message->FindInt32("be:value",&value);
		m_sound->SetPan(value / 100.0f);  //  パンの変更
	}
	break;

スライダーからメッセージが送信された場合、そのメッセージには"be:value"というキーで
その時のスライダーの値が入っています。
BMessage::FindInt32()関数でその値を取出し、それを100で割った数字で、ボリューム等の変更をしています。
なぜ、こんなことをしているかというと、スライダーの値というのはint32型(符号あり32ビット型整数)しか格納出来ないからです。

SetGain()関数とSatPan()関数の説明は第10回を参考にしていただくとして、
サンプリングレートの変更方法についてちょっと捕捉説明します。

// サンプリングレート変更
void MyView::SetSamplingRate(float rate_times)
{
	gs_attribute attr;
	attr.attribute = B_GS_SAMPLING_RATE;  // サンプリングレートの変更
	attr.duration  = 0;					// 変更は直ちに行う
									// 元のサンプリングレートに、指定された値を掛ける
	attr.value     = m_sound->Format().frame_rate * rate_times; 
	attr.flags     = 0;                      // その他のフラグはなし

	m_sound->SetAttributes(&attr,1);
}

gs_attribute構造体の説明については、第10回で説明したので省略しますが、
valueの値を設定する方法についてちょっと説明します。
BFileGameSound(BSimpleGameSound)のFormat()関数で、現在のサウンドのgs_audio_format構造体が取得できます。
その、frame_rateフィールドに現在のサンプリングレートが入っているので、そこに指定された値を掛けることによって、
倍速などの再生を実現しています。

なぜ、サンプリングレートを倍にすると、速度も倍になるかというと、
BFileGameSoundがサウンドを再生する際に、実際には1秒のデータを0.5秒で、サウンドミキサーに送るからです。


さて、BSliderいかがでしたでしょうか?
サンプルもだんだん肥大化してきて、サンプルだけで、8KBを超えるようになりました。
次回は、このサンプルにさらに機能を追加するか、それとももうちょっとコンパクトに別のものにするか考え中です。

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

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