読者です 読者をやめる 読者になる 読者になる

音ゲーって楽しいよね!

どうも∠KOH(KacKOH(かっこう))です。読みにくいHNでごめんね。

 

今回は僕の所属しているサークルB4UTでのちょっとした企画(以下参照)に参加しようと思い書きはじめました。

www51.atwiki.jp

 

 ** ** **

 

さてさて。

皆さん音ゲーって知ってますよね。音楽ゲーム。音感ゲームっても言うのかな。知らない人はggってみよう。

一般的によく知られているので言うと太鼓の達人とかああいうやつです。ちょっとお手軽なやつだとスクフェスとかデレステとかああいうのも含まれます。

 

で。

今回は「今からでも遅くない! 音ゲーを始めてみませんか?」ってお話です。

えっ、始めるのが怖い? バッカお前……俺がついてるだろ!

 

なんでこんな話をしようと思ったのかと言うと、自身もつい1年半前に音ゲーを始めた(比較的)歴の浅い音ゲーマーだからです。(2016年12月現在大学3年で音ゲー始めたのは大学2年の5月)

 

 ** ** **

 

まずは僕が音ゲーを始めたきっかけをつらつらと。

そもそも僕は某県の田舎出身なのでゲームセンターは身近なものではなく、「こわいところ」「ふりょうがいそう」「たばこくさい」「なんかやだ」といった印象しかありませんでした。

で、大学に入って東京に出て来てからもその価値観は拭えず、大学1年の頃はおうちにこもってパソコンを見ながらオタクスマイルをしていたわけです。今もやってるけど

 

そんな僕に転機が訪れます。

大学2年の4月、高校同期に連れられて秋葉原タイトーステーションにふらっと立ち入ったんですね。まあでもそれまでゲーセンなんて碌に行ったこともない訳ですから特にやりたいゲームがあるわけでもなく各フロアをウロウロしてたわけです。

するとこんなものがありまして↓

p.eagate.573.jp

QMAっていうクイズゲームですね。

高校時代にクイズ研究会なる組織に所属していた僕はこのゲームにちょっと惹かれました。友達も一緒だったので4人対戦をしてみることに。そして……

 

「このゲーム………………おもしろいやんけ!」

 

見事アーケードゲーム(以下ACゲームと略)に陥落します。当時は筐体に100円を入れるのさえ新鮮でした。おもしろいけどお金がどんどん無くなる不安を覚えたのも、もう懐かしい話です。

 

んでんで。

ACゲームのプレーデータを保存するには専用のカードが必要になるらしいことを知った僕はe-AMUSEMENT PASS――通称eパス』を購入します。実は交通系カードでも代用可能なんですが当時はそんなこと知りませんでしたので。

そんなこんなでACゲームデビューした僕ですがやっぱりゲームセンターはちょっとこわいところと言う気がしていて。

後日初めて独りでゲーセンを訪れた時はビビりまくりでした。本当はそんなにこわいところでもないのにね。小心者とでも笑ってください。

でも空きコマに大学を抜け出してゲーセン行ったりとかめっちゃ大学生っぽくて楽しかったです(今では日常となってしまったので息をするようにクズムーブメントしてますが)

 

ところで先ほど購入したeパス。なんとなく流れで買ったこいつが僕を音ゲーの道へと引きずり込みます。

それは学校帰りに友人と寄ったゲーセンにて。

 

友人A「最近これにハマってるんだよね」

友人B「最高におもしろい」

  僕「これは……」

p.eagate.573.jp

そう、jubeatです。4x4の光るパネルという独特なインタフェースを持つコイツに友人たちはハマっていたのです。

実は何を隠そう、eパスを使うとKONAMI音ゲーは初回無料(ゲーセンによるかも)でプレーできちゃうんですよね。すばらしい。

ということでちょっと恥ずかしがりつつも挑戦。そしてチュートリアルが始まりリズムに合わせてパネルをタッチするのですが……

 

>> B A D <<

……アレ?

 

全 く リ ズ ム に 合 っ て な い !

音ゲーでは普通、叩くごとに上手く叩けているかの判定が出るのですが、全く良い判定が出ません。僕に限らず、おそらく初めて触れる人だと本当に判定が合わなくて乾いた笑いが出ます。ともかくチュートリアルで良い判定が出ただけで僕より才能あります。

 

恐れ慄きつつも1曲目。jubeatはアニソンやJPOPも結構収録されてるのでそのラインナップにまず驚きます。マンウィズのGet Off of My Way入ってるとかマジで? って感じ。

まあでも1曲目は無難(?)にonly my railganを選曲。オタクだから仕方ないね。

知ってる曲に合わせて叩く。やっていることはそれだけなのですがこれがすごい楽しいんです。音楽聴きながらついリズムを取ったり口ずさんだりしちゃうことが結構あると思うんですけど、アレにゲーム性が加わる。そう考えるとなんか面白そうじゃないですか?

 

そして曲が終わりリザルト画面。ギリギリクリア! 嬉しい!

音ゲーは自分の腕前を点数化して示してくれます。これってテストとかスポーツとかに近いのかなあと思います。練習すれば上手くなって結果に反映される。記録を更新したら嬉しいに決まってますよね。

 

こんな感じで音ゲーデビューした僕はその魅力にどっぷりと浸かり、今ではjubeatだけでなく他の音ゲーもたくさんやってます。お金がたりないよぅ。

 

  ** ** **

 

そんな感じで音ゲーにハマった僕ですが、音ゲーが楽しいのは勿論のこととして他にも副次的な楽しみ(もしかしたらこっちの方が主かも)があるんです。

 

まず交友関係が確実に広がります音ゲーのお話が出来る人ってだけで音ゲーマーは喜びます。これは本当。

あ、音ゲーを始めたらtwitterとかも始めるといいです。嬉しかったリザルトを貼ったりする人が多いので、そういった人をフォローしていくと自然と仲良くなったりとかとか。

僕自身いつの間にか仙台やら神戸やらに知り合いが増えてびっくりしてます。これからもどうぞよろしく。

 

ふたつめは外出頻度が増えること。

これは引きこもりがちだった僕が毎日のようにゲームセンターへ行って帰りにラーメンを食べていることからも自明ですね。目的が出来れば外に出ます。

 

みっつめは新しい音楽と出会えること。

JPOPとかJROCKとか洋楽とかアニソンとかボカロとか、そういったジャンルとはまた違った音楽に出会えます。

僕も音ゲーを始める前までボーカルの無い曲の良さがあまり分からなかった(意外とこういう人多いのでは)のですが、今となってはガンガン鳴ってる曲のカッコ良さに痺れてます。少なくとも、音楽にもっと興味が湧いてくるのは事実です。

折角だし色んな音楽の良さを享受できたら最高じゃないですか(語彙力のNASA)。

 

あとはライバルと競い合うこと。

副次的と言うか、独りで黙々とやるより実力の近い人と競い合いながらやると楽しさが倍増しますよ、ってことです。なんだかんだ人間は勝った負けたが好きな動物なのでライバルと競い合う(公式のアプリでもそういった機能があります)のは本当に楽しい。

上達も早まりますしいいこと尽くしです。

 

などなど。

 

 ** ** **

 

はい。

ということで駄文でしたがこの辺りで〆たいなあと思います。

 

おそらくなんですが、音ゲー始めたての頃ってゲーセンに居る他の音ゲーマーが上手過ぎて萎縮しちゃうと思います。上手い人の後に並ぶとかやだなー、とか。僕はそうでした。

もしかしたら意地の悪いヤツが居るかもしれませんが、少なくとも僕は遭遇したことがありません。もっともそういう人たちはSNSで炎上してるようなことが多いので超少数でしょう。

普通の音ゲーマーは新規に始める人をそれなりに歓迎していると思います。twitterでアドバイスくれる人もいっぱいいますし。

 

あ、でもマナーは守りましょう。順番は守るとか台パンしないとか。そのくらいです。

 

では、最後にもう一度。

音ゲーは本当に楽しいです。押しつけがましくてもこの楽しさを知って欲しい。

 

今からでも遅くない! 音ゲーを始めてみませんか?

 

(∠KOH)

Rhythmboxで1曲ループを実現する −6−

今回Rhythmboxを手探ってみて感じたことは大きく2つです。

 

その1。

C言語gtk+がメインで読みやすい!

 

その2。

関数名や変数名がわかりやすい!

 

……とにかく読みやすいコードを書くのが大事なんだなあ、と漠然と思いました。

grepで引っ掛け易い関数名や変数名のおかげで後から弄るのが簡単でした。

しかしながら他者の書いたコードというのはその人の癖やこだわりみたいなものが入ると途端に読みづらくなりますね。(少し前に言及した文字列で状態管理してるヤツとか)

スタンダードな書き方というのを身につけたいものです。

 

以上、『大規模ソフトウェアを手探る』の考察でした。

Rhythmboxで1曲ループを実現する −5−

はい、ということで前回の続きです。

 

早速ですが、前回扱ったrb_shell_player_get_playback_state関数が使われているrb_shell_player_sync_control_state関数をいじります。

 

static void
rb_shell_player_sync_control_state (RBShellPlayer *player)
{
	gboolean shuffle, repeat;
	GAction *action;
	rb_debug ("syncing control state");

	if (!rb_shell_player_get_playback_state (player, &shuffle,
						 &repeat))
		return;


	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-shuffle");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));
}

 ここはボタンの状態とそのフラグを同期させる処理を行う部分です。このままだとシャッフルか通常ループにした対応していないので、ここに1曲ループに関する記述を加えます。

 

また、シャッフルや通常ループがonの時、1曲ループはoffになり、1曲ループがonの時はその他はoffになっている方が自然です。

これも対応させましょう。

static void
rb_shell_player_sync_control_state (RBShellPlayer *player)
{
	gboolean shuffle, repeat, repeat1;
	GAction *action;
	rb_debug ("syncing control state");

	if (!rb_shell_player_get_playback_state_loop1 (player, &repeat1))
	{
		if (!rb_shell_player_get_playback_state (player, &shuffle,&repeat))
		{
			return;
		}
		action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
						     "play-shuffle");
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));
	
		action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
						     "play-repeat");
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));

		repeat1 = 0;
		action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat1");
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat1));

		return;
	}

	shuffle = 0;
	repeat = 0;
	loop1_flag = repeat1;

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-shuffle");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));

	action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
					     "play-repeat1");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat1));
}

 

こんな感じです。

 

何をしているかというと、

①現在の状態を調べる。それが"linear1"か"linear-loop1"のいずれかであればif文を素通り。別の状態であれば元々の処理(+1曲ループボタンをoffにする処理)を行って関数を抜ける。

②"linear1"か"linear-loop1"ならシャッフルボタンと通常ループボタンをoffにし、かつ、1曲ループボタンをフラグに合わせて関数を抜ける。

と言った具合です。

 

ふぅ。

 

これで大きな部分は終わりです。細かいところを直していきます。

まずは念の為loop1_flagを初期化。

前回state_to_play_order_loop1を書いた次の行あたりに、

gboolean loop1_flag = FALSE;

と一行書きましょう。

 

次。

play_repeat_action_cbとplay_shuffle_action_cb内でloop1_flagをFALSEにします。

static void
play_repeat_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;
	rb_debug ("repeat changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	repeat = !repeat;
	loop1_flag = FALSE;
	
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

static void
play_shuffle_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;

	if (player->priv->syncing_state)
		return;

	rb_debug ("shuffle changed");

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	shuffle = !shuffle;
	loop1_flag = FALSE;
	
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

更にもうひとつ。

rb_shell_player_init関数内で、

(前略)

    rb_shell_player_add_play_order (player, "linear", N_("Linear"),
					RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
    rb_shell_player_add_play_order (player, "linear-loop", N_("Linear looping"),
					RB_TYPE_LINEAR_PLAY_ORDER_LOOP, FALSE);
    rb_shell_player_add_play_order (player, "linear1", N_("Linear1"),
					RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
    rb_shell_player_add_play_order (player, "linear-loop1", N_("Linear looping one"),
					RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
    rb_shell_player_add_play_order (player, "shuffle", N_("Shuffle"),
					RB_TYPE_SHUFFLE_PLAY_ORDER, FALSE);
(後略)

 

他と対応するように青字を追加します。

これでrb-shell-player.cに対する変更は終わりです。

 

最後の最後。

rb-shell-player.hで関数を宣言するのとrb-shell.cにボタンの構築に関する記述をして実装完了です。長かった……。

 

ではrb-shell-player.h内で

(前略)
gboolean                rb_shell_player_get_playback_state(RBShellPlayer *player,
							   gboolean *shuffle,
							   gboolean *repeat);

gboolean                rb_shell_player_get_playback_state_loop1(RBShellPlayer *player,
							   	 gboolean *repeat1);
(後略)

 青字部分を宣言します。

 

そしてrb-shell.c内のconstruct_load_ui関数にて、

static void
construct_load_ui (RBShell *shell)
{

(中略)

	/* this seems a bit unnecessary */
	gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, "shuffle-button")),
						g_variant_new_boolean (TRUE));
	gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, "repeat-button")),
						g_variant_new_boolean (TRUE));
	gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, "repeat1-button")),
						g_variant_new_boolean (TRUE));
(後略)

 

……はい。

これで完成です!!

やったあ!!

 

次回はまとめとちょっとしたおまけです。

Rhythmboxで1曲ループを実現する −4−

今回はフラグとボタンを同期させます。

これが終われば実装は終了です。

ちょっと長いので記事を2回に分けて書こうと思います。

 

見ていくのはrhythmbox-3.0.2/shell/rb-shell-player.h、rhythmbox-3.0.2/shell/rb-shell-player.c、rhythmbox-3.0.2/shell/rb-shell.cの3つです。

 

とりあえずrb-shell-player.cを見ていきましょう。

 

まず気になるのがrb_shell_player_constructed関数です。名前からしてプレイヤーを構成するための重要な関数であることがわかります。

static void
rb_shell_player_constructed (GObject *object)
{
	RBApplication *app;
	RBShellPlayer *player;
	GAction *action;

	GActionEntry actions[] = {
		{ "play", play_action_cb },
		{ "play-previous", play_previous_action_cb },
		{ "play-next", play_next_action_cb },
		{ "play-repeat", play_repeat_action_cb, "b", "false" },
		{ "play-shuffle", play_shuffle_action_cb, "b", "false" },
		{ "volume-up", play_volume_up_action_cb },
		{ "volume-down", play_volume_down_action_cb }
	};

	RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);

	player = RB_SHELL_PLAYER (object);

	app = RB_APPLICATION (g_application_get_default ());
	g_action_map_add_action_entries (G_ACTION_MAP (app),
					 actions,
					 G_N_ELEMENTS (actions),
					 player);

	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>r", "app.play-repeat", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>u", "app.play-shuffle", g_variant_new_boolean (TRUE));

	(後略)

 

ここでは以前書き換えたuiファイルにおけるボタンのaction_nameプロパティに言及しているようです。そこでapp.play-repeat1に対する記述を書き加えます。

static void
rb_shell_player_constructed (GObject *object)
{
	RBApplication *app;
	RBShellPlayer *player;
	GAction *action;

	GActionEntry actions[] = {
		{ "play", play_action_cb },
		{ "play-previous", play_previous_action_cb },
		{ "play-next", play_next_action_cb },
		{ "play-repeat", play_repeat_action_cb, "b", "false" },
		{ "play-repeat1", play_repeat1_action_cb, "b", "false" },
		{ "play-shuffle", play_shuffle_action_cb, "b", "false" },
		{ "volume-up", play_volume_up_action_cb },
		{ "volume-down", play_volume_down_action_cb }
	};

	RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);

	player = RB_SHELL_PLAYER (object);

	app = RB_APPLICATION (g_application_get_default ());
	g_action_map_add_action_entries (G_ACTION_MAP (app),
					 actions,
					 G_N_ELEMENTS (actions),
					 player);

	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>r", "app.play-repeat", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>1", "app.play-repeat1", g_variant_new_boolean (TRUE));
	gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>u", "app.play-shuffle", g_variant_new_boolean (TRUE));

	(後略)

 

これで1曲ループボタンが押された時にplay_repeat1_action_cbという関数を呼び出すことが出来るようになりました。(ついでにCtrl+1をショートカットに割り当てています)

しかしplay_repeat1_action_cb関数はまだ作成していないので書き加えていきます。

一から書くのは骨が折れるのでplay_repeat_action_cb関数を雛形にしちゃいましょう。

static void
play_repeat_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean shuffle = FALSE;
	gboolean repeat = FALSE;
	rb_debug ("repeat changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state (player, &shuffle, &repeat);

	repeat = !repeat;
	neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

見た限りではボタンの状態を取得してそれを反転させる操作をしています。ここで少々問題になるのが太字で示した部分です。

これらの定義を見てみます。

 

[rb_shell_player_get_playback_state]

gboolean
rb_shell_player_get_playback_state (RBShellPlayer *player,
				    gboolean *shuffle,
				    gboolean *repeat)
{
	int i, j;
	char *play_order;

	play_order = g_settings_get_string (player->priv->settings, "play-order");
	for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
		for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
			if (!strcmp (play_order, state_to_play_order[i][j]))
				goto found;

	g_free (play_order);
	return FALSE;

found:
	if (shuffle != NULL) {
		*shuffle = i > 0;
	}
	if (repeat != NULL) {
		*repeat = j > 0;
	}
	g_free (play_order);
	return TRUE;
}

 

[state_to_play_order]

static const char* const state_to_play_order[2][2] =
	{{"linear",	"linear-loop"},
	 {"shuffle",	"random-by-age-and-rating"}};

 

rb_shell_player_get_playback_state関数はシャッフルと通常ループの状態をフラグとして得ます。返り値は現在の状態がstate_to_play_orderに登録されているものであればTRUE、そうでなければFALSEです。

state_to_play_orderは見ての通りフラグと文字列とを対応付ける配列です。(なぜわざわざ文字列を使うのかは疑問ですが……)

 

さて、この関数と配列はもちろん1曲ループには対応していませんので同じようなものを1曲ループ用に書き加えねばなりません。大変。

 

とはいえ雛形として元の関数を利用すれば意外に楽ちんです。まずはそれぞれをコピーしてその直後にペーストしましょう。

コピペした方を改変していきます。

 

[rb_play_repeat1_action_cb]

static void
play_repeat1_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
	RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
	const char *neworder;
	gboolean repeat1 = FALSE;
	rb_debug ("repeat1 changed");

	if (player->priv->syncing_state)
		return;

	rb_shell_player_get_playback_state_loop1 (player, &repeat1);

	repeat1 = !repeat1;
	loop1_flag = repeat1;
	neworder = state_to_play_order_loop1[repeat1 ? 1 : 0];
	g_settings_set_string (player->priv->settings, "play-order", neworder);
}

 

[rb_shell_player_get_playback_state_loop1]

gboolean
rb_shell_player_get_playback_state_loop1 (RBShellPlayer *player,
				    gboolean *repeat1)
{
	int i;
	char *play_order;

	play_order = g_settings_get_string (player->priv->settings, "play-order");
	for (i = 0; i < 2; i++)
		if (!strcmp (play_order, state_to_play_order_loop1[i]))
			goto found;

	g_free (play_order);
	return FALSE;

found:
	if (repeat1 != NULL) {
		*repeat1 = i > 0;
	}
	g_free (play_order);
	return TRUE;
}

 

[state_to_play_order_loop1]

static const char* const state_to_play_order_loop1[2] =
	{"linear1",	"linear-loop1"};

 

青字の部分が変更点です。"linear1"というのは"linear"と同じ動作をするのですが念の為分けて置きました。蛇足かも?

 

ここまできたらもう一息です。

続きは次回。

Rhythmboxで1曲ループを実現する −3−

今回は1曲ループのための関数を実装します。

 

簡単に説明すると、

リニア再生(順番通りに再生するヤツ)の関数内で次の曲/前の曲へ飛ぶとき、代わりに今再生している曲へ飛ばしちゃえばよくない?

ってことです。

 

編集するファイルはrhythmbox-3.0.2/shell/rb-play-order.hとrhythmbox-3.0.2/shell/rb-play-order-linear.cの2つです。

 

まずはrb-play-order.hで1曲ループを切り替えるためのフラグを宣言します。

ここは簡単で、

RBShellPlayer *      rb_play_order_get_player        (RBPlayOrder *porder);
RBSource *           rb_play_order_get_source        (RBPlayOrder *porder);
RhythmDB *           rb_play_order_get_db            (RBPlayOrder *porder);
RhythmDBQueryModel * rb_play_order_get_query_model   (RBPlayOrder *porder);
gboolean             rb_play_order_model_not_empty   (RBPlayOrder *porder);

gboolean             rb_play_order_player_is_playing (RBPlayOrder *porder);

gboolean loop1_flag;

 

関数宣言をしている箇所の後に青字部分を追加するだけです。

書き加えたら保存して閉じてOKです。

 

続いてrb-play-order-linear.c。

開いたらまずrb_linear_play_order_get_next関数とrb_linear_play_order_get_previous関数を探しましょう。この2つの関数は名前の通りそれぞれ次曲/前曲を取得する関数です。

static RhythmDBEntry *
rb_linear_play_order_get_next (RBPlayOrder *porder)
{
	RhythmDBQueryModel *model;
	RhythmDBEntry *entry;

	g_return_val_if_fail (porder != NULL, NULL);
	g_return_val_if_fail (RB_IS_LINEAR_PLAY_ORDER (porder), NULL);

	model = rb_play_order_get_query_model (porder);
	if (model == NULL)
		return NULL;

        entry = rb_play_order_get_playing_entry (porder);
	if (entry != NULL) {
		RhythmDBEntry *next;
		next = rhythmdb_query_model_get_next_from_entry (model, entry);
		rhythmdb_entry_unref (entry);
		return next;
	} else {
		GtkTreeIter iter;
		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
			return NULL;
		return rhythmdb_query_model_iter_to_entry (model, &iter);
	}
}

 

太字部分が実際に次曲の情報を取得している部分です。entryが現在の曲情報、nextが次曲の情報であるので1曲ループにするにはずばり、

 

next = entry;

 

とすれば良いですね!(すごく……雑です……)

これを先ほど宣言したフラグと合わせて書き換えると、

static RhythmDBEntry *
rb_linear_play_order_get_next (RBPlayOrder *porder)
{
	RhythmDBQueryModel *model;
	RhythmDBEntry *entry;

	g_return_val_if_fail (porder != NULL, NULL);
	g_return_val_if_fail (RB_IS_LINEAR_PLAY_ORDER (porder), NULL);

	model = rb_play_order_get_query_model (porder);
	if (model == NULL)
		return NULL;

        entry = rb_play_order_get_playing_entry (porder);
	if (entry != NULL) {
		RhythmDBEntry *next;
		if (loop1_flag == TRUE)
		{
			next = entry;
		}
		else
		{
			next = rhythmdb_query_model_get_next_from_entry (model, entry);
			rhythmdb_entry_unref (entry);
		}
		return next;
	} else {
		GtkTreeIter iter;
		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
			return NULL;
		return rhythmdb_query_model_iter_to_entry (model, &iter);
	}
}

 

フラグがTRUEなら1曲ループにする、という実装です。(Cではbooleanが扱えませんがgtk+ではgboolean型が定義されておりbooleanを扱うことが可能です)ということで同じような変更をrb_linear_play_order_get_previousにも行います。

static RhythmDBEntry *
rb_linear_play_order_get_previous (RBPlayOrder *porder)
{
	RhythmDBQueryModel *model;
	RhythmDBEntry *entry, *prev;

	g_return_val_if_fail (porder != NULL, NULL);
	g_return_val_if_fail (RB_IS_LINEAR_PLAY_ORDER (porder), NULL);

	model = rb_play_order_get_query_model (porder);
	if (model == NULL)
		return NULL;

        entry = rb_play_order_get_playing_entry (porder);
	if (entry == NULL)
		return NULL;

	if (loop1_flag == TRUE)
	{
		prev = entry;
	}
	else
	{
		prev = rhythmdb_query_model_get_previous_from_entry (model, entry);
		rhythmdb_entry_unref (entry);
	}

	return prev;
}

 

これで1曲ループ関数の実装は終了です。(まだフラグの値を操作していないのでこのままでは動かないことに注意!)

 \

次回はボタンとフラグを同期する操作へ入っていきます。

Rhythmboxで1曲ループを実現する −2−

今度こそ中身を見ていきます。

 

それでは早速Rhythmboxのソースをダウンロードして解凍しましょう。

今回扱うRhythmboxのバージョンは3.0.2です。しかし後述する改変を行えば3.4.1でも問題なく動作しますのでご安心を。

 

解凍するとrhythmbox-3.0.2というディレクトリが出来ると思います。

まず手始めに1曲ループ用のボタンを配置します。

 バックアップを取っておくと◎

 

rhythmbox-3.0.2/data/ui/main-toolbar.uiテキストエディタで開きます。

ここで記述されているのは、

f:id:kackoh:20161020122010p:plain

ここらへんのUIです(適当)

 

実際に中身を見るとまずこんな記述があると思います。

<object class="GtkImage" id="image1">
  <property name="visible">True</property>
  <property name="pixel_size">24</property>
  <property name="icon_name">media-playlist-shuffle-symbolic</property>
</object>

 

ここではgtk+を用いてアイコン画像を作っています。注目して頂きたいのは4行目のmedia-playlist-shuffle-symbolicという部分です。

当初、なんらかの画像ファイルを指定したりしてアイコンにしているのかな? と思ったのですが、どうやらgtk+に組み込まれたアイコンデータがあるようです。

末尾が-symbolicとなっているものは基本的にこういったアイコンデータを指しているみたいですね。なるほどなるほど。

 

また、1行目ではGtkImageクラスのオブジェクトにIDとしてimage1を振っています。続けて見ていくと同じようなものがimage1〜image5まで存在していることが確認できると思うので、ここにimage6として1曲ループボタン(のアイコン)を定義します。

(前略)
<object class="GtkImage" id="image5">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="xpad">8</property>
  <property name="ypad">9</property>
  <property name="pixel_size">24</property>
  <property name="icon_name">media-skip-backward-symbolic</property>
</object>
<object class="GtkImage" id="image6">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <property name="pixel_size">24</property>
  <property name="icon_name">media-playlist-repeat-song-symbolic</property>
</object>
(後略)

 

青文字の部分を追加すればOKです。media-playlist-repeat-song-symbolicなんていうアイコンが用意されているのに1曲ループを実装しないRhythmboxさんェ……

 

次はボタンを配置します。main-toolbar.uiの以下の部分↓

<child>
  <object class="GtkToggleButton" id="repeat-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-repeat</property>
    <property name="image">image2</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">0</property>
  </packing>
</child>
<child>
  <object class="GtkToggleButton" id="shuffle-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-shuffle</property>
    <property name="image">image1</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">1</property>
  </packing>
</child>

 

 これを次のように変えます。

<child>
  <object class="GtkToggleButton" id="repeat1-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-repeat1</property>
    <property name="image">image6</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">0</property>
  </packing>
</child>
<child>
  <object class="GtkToggleButton" id="repeat-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-repeat</property>
    <property name="image">image2</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">1</property>
  </packing>
</child>
<child>
  <object class="GtkToggleButton" id="shuffle-button">
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="receives_default">True</property>
    <property name="action_name">app.play-shuffle</property>
    <property name="image">image1</property>
    <style>
      <class name="raised" />
    </style>
  </object>
  <packing>
    <property name="expand">False</property>
    <property name="fill">True</property>
    <property name="position">2</property>
  </packing>
</child>

 

これで1曲ループ用のボタンが配置できます。positionは0を左端としてどの順番で並べるかを決めるオプションです。今回は[1曲ループ][ループ][シャッフル]の順で並べています。

 

これでmain-toolbar.uiに対する変更は完了です。

次回は1曲ループ動作を実現します。

Rhythmboxで1曲ループを実現する −1−

具体的に中身をいじって行こうと思います。

 

…...が。

その前にちょっとだけ、他にも自分で機能を実装したい! という方に向けて覚書みたいなものを書いておきます。そんな面倒なことしないよぅ、という方は読み飛ばしてくれて構いません 。

 

 ** ** ** ** **

 

今回、ループを司る関数を見つけるにあたってディレクトリ配下のファイル内に書かれた文字列を検索する(参考)といったことを行いました。

まあgrepですね。

 

具体的には、

$ find (探したいディレクトリ) | xargs grep "(検索文字列)" -I > (結果)

といったものです。-Iはバイナリファイルを検索に含めないようにするオプションです。

 

例えばホームディレクトリ直下のfooというディレクトリに含まれるファイル内からbarという文字列を探して、その結果をresult.txtに書き込む場合、

$ find ./foo | xargs grep "bar" -I > result.txt

という具合です。

 

これがかなり強力で、それっぽいワード(今回であればloopとか)で検索をかけるとそれらしき関数名や変数名が一気に引っかかります。しゅごい!

 

 

あともうひとつ。

これは裏技的なものなのですが、Rhythmboxには翻訳ファイルが存在しています。(ソースコード群からja.poというファイルを検索して見つけましょう)

開いてみると英語と日本語の対応が書かれていることがわかると思います。

 

で。

 

例えばこのファイル内から"プレイリスト"という文字列を検索してみると"Playlists"などと対訳されているはずです。さらによく見ると元のソースコード群のどこのファイルに"Playlists"という記述があるかがコメントとして書かれています。

 

この対訳はGUIとして表示されるような文字列(例えばボタンの文字)にしか関係しないので、簡単にボタンなどに関わる関数を見つけることが出来ます。ちょっとずるいですね。

 

 

その他、gdbによるデバッグも行いました。が、あまり使っていません。