手拍子をカウントする

Siv3D Advent Calendar 2013, 4 日目の記事です。
今日はマイク入力を使って手拍子を認識するソフトを作ってみましょう。

まずは Play Siv3D!チュートリアルからマイク入力のサンプルを持ってきます。
チュートリアル | マイク入力 | 録音した波形の解析 | 振幅

# include <Siv3D.hpp>

void Main()
{
	const Font font(30);

	// 10 秒間, サンプリングレート 44100Hz で録音を開始する
	if (!Recorder::Start())
	{
		return;
	}

	while (System::Update())
	{
		const double max = Recorder::GetMaxAmplitude();
		const double ave = Recorder::GetAverageAmplitude();

		font.draw(Format(L"最大振幅: ", max));

		RectF(0, 50, max*Window::Width(), 30).draw();

		font.draw(Format(L"平均振幅: ", ave), 0, 100);

		RectF(0, 150, ave*Window::Width(), 30).draw();

		if (Recorder::IsEnd()) // 録音が終了
		{
			// 最初の位置から録音し直し
			Recorder::Restart();
		}
	}
}

録音中は
Recorder::GetMaxAmplitude(sec) で最新の sec 秒間の最大の振幅 [0.0, 1.0] を、
Recorder::GetAverageAmplitude(sec) で最新の sec 秒間の平均の振幅を取得できます。
sec はデフォルトでは 0.02 です。

さて、手拍子の波形は、ごく一瞬だけ大きな振動が現れるのが特徴です。

この特徴を利用して、毎フレーム最大の振幅を調べ、閾値を超えた瞬間を調べれば、それなりの精度で手拍子を認識できます。

実際に作ってみましょう。

# include <Siv3D.hpp>

void Main()
{
	const double threshold = 0.2;

	const Font font(30);

	const Point center = Window::Size() / 2;

	if (!Recorder::Start())
	{
		return;
	}

	bool clapping = false;

	int count = 0;

	int impact = 0;

	while (System::Update())
	{
		const double max = Recorder::GetMaxAmplitude();

		if (!clapping && max >= threshold)
		{
			++count;

			clapping = true;

			impact = 40;
		}

		if (max < threshold)
		{
			clapping = false;
		}

		font.draw(Format(L"最大振幅: ", max));

		RectF(0, 50, max*Window::Width(), 30).draw();

		impact = Max(impact - 4, 0);

		Circle(center, 80 - impact).draw(Color(Palette::Orange, 120 + impact*3));

		font.drawCenter(Format(count), center - Vec2(0, impact*0.3));

		if (Recorder::IsEnd())
		{
			Recorder::Restart();
		}
	}
}

ポケットをたたく(手拍子をする)たびにクッキーが増えていくゲームなんてどうでしょうか。

Circle の描画関数

Siv3D Advent Calendar 2013, 3 日目の記事です。
今日は Circle の描画関数を紹介します。

基本の円描画 draw(color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).draw(Palette::Yellow);

	Circle(100, 100, 50).draw(Palette::Skyblue);

	Circle(400, 400, 300).draw(Palette::Orange);

	WaitKey();
}

円の枠 drawFrame(innerThickness, outerThickness, color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).drawFrame(2.0, 0.0, Palette::Yellow);

	Circle(100, 100, 50).drawFrame(10.0, 0.0, Palette::Green);

	Circle(100, 100, 50).drawFrame(1.0, 10.0, Palette::Skyblue);

	Circle(400, 400, 300).drawFrame(250.0, 0.0, Palette::Orange);

	WaitKey();
}

丸いパイを切ったような形 drawPie(startAngle, angle, color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).drawPie(0.0, Radians(120), Palette::Yellow);

	Circle(100, 100, 50).drawPie(Radians(45), Pi, Palette::Skyblue);

	Circle(400, 400, 300).drawPie(0.0, -Radians(60), Palette::Orange);

	WaitKey();
}

アーチ型 drawArc(startAngle, angle, innerThickness, outerThickness, color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).drawArc(0.0, Radians(120), 5.0, 0.0, Palette::Yellow);

	Circle(100, 100, 50).drawArc(Radians(45), Pi, 10.0, 10.0, Palette::Skyblue);

	Circle(400, 400, 300).drawArc(0.0, -Radians(60), 200.0, 0.0, Palette::Orange);

	Circle(50, 50, 350).drawArc(HalfPi, HalfPi, 60.0, 0.0, { 255, 64 });

	WaitKey();
}

Siv3D の円の描画にはちょっとした工夫があって、半径が 32px 以下の円は、あらかじめ円がレンダリングされたスプライトを描画し、それより大きい円はその都度適当な分割数のポリゴンを生成しています。
draw() の画像をポリゴンで表示するとこうなります。

こうすることで、小さな円を大量に描画するシーンでは頂点数を節約し、大きな円は最適な品質で描くことができます。

INI ファイルからデータを読み込む

Siv3D Advent Calendar 2013, 2 日目の記事です。

今日は INI ファイルからデータを読み込む方法を紹介します。
INI ファイルは設定を記述するのに便利なテキストフォーマットです。

test1.ini

[AAA]
aaa = 50
bbb = 1.234
ccc = Hello

; コメント
[BBB]
aaa = (20, 50)
bbb = (255,128,0)
ccc = ((80,80),(120,120),(80,120))

このファイルを読み込んで、Siv3D のデータ型に変換しましょう。
読み込みの関数は INIReader::get(L"セクション.パラメータ", 失敗時の値) です。
INIReader::getOptionl は失敗時に none を返します。

# include <Siv3D.hpp>

void Main()
{
	const INIReader ini(L"test1.ini");

	const int AAAaaa = ini.get<int>(L"AAA.aaa", 0);

	const double AAAbbb = ini.get<double>(L"AAA.bbb", 0.0);

	const String AAAccc = ini.get<String>(L"AAA.ccc", L"");

	Println(AAAaaa, L' ', AAAbbb, L' ', AAAccc);

	if (const auto value = ini.getOptional<int>(L"AAA.ccc"))
		Println(value.get());
	else
		Println(L"読み込みに失敗");

	if (const auto value = ini.getOptional<int>(L"AAA.xxx"))
		Println(value.get());
	else
		Println(L"読み込みに失敗");

	Println(ini.get<int>(L"AAA.yyy", -123));

	Println(ini.get<Point>(L"BBB.aaa", { 0, 0 }));

	ini.get<Triangle>(L"BBB.ccc", { 0, 0, 0 }).draw(ini.get<Color>(L"BBB.bbb", { 0, 0, 0 }));

	WaitKey();
}

結果

INIReader で読み込んだ INI ファイルが実行中に変更されると、INIReader::hasChanged() が true を返します。INIReader::reload() すると INI ファイルをロードしなおします。
次のプログラムを動かして、実行中に INI ファイルを編集してみましょう。

test2.ini

; 長方形のプロパティ
[Rectangle]
rect = (20, 50, 200, 50)
color = (255,64,128)
frame = true
frameThickness = 5
frameColor = (255,255,128)

; 長方形内のテキストのプロパティ
[Content]
text = Hello
textColor = (255,255,255)
# include <Siv3D.hpp>

void Main()
{
	const Font font{ 20 };

	INIReader ini(L"test2.ini");

	Rect rect = ini.get<Rect>(L"Rectangle.rect", { 0, 0, 0, 0 });
	Color color = ini.get<Color>(L"Rectangle.color", { 0, 0, 0 });
	bool frame = ini.get<bool>(L"Rectangle.frame", false);
	int frameThickness = ini.get<int>(L"Rectangle.frameThickness", 1);
	Color frameColor = ini.get<Color>(L"Rectangle.frameColor", { 0, 0, 0 });
	String text = ini.get<String>(L"Content.text", L"");
	Color textColor = ini.get<Color>(L"Content.textColor", { 0, 0, 0 });

	while (System::Update())
	{
		rect.draw(color);

		if (frame)
		{
			rect.drawFrame(0, frameThickness, frameColor);
		}

		font.drawCenter(text, rect.center, textColor);

		if (ini.hasChanged())
		{
			ini.reload();

			rect = ini.get<Rect>(L"Rectangle.rect", { 0, 0, 0, 0 });
			color = ini.get<Color>(L"Rectangle.color", { 0, 0, 0 });
			frame = ini.get<bool>(L"Rectangle.frame", false);
			frameThickness = ini.get<int>(L"Rectangle.frameThickness", 1);
			frameColor = ini.get<Color>(L"Rectangle.frameColor", { 0, 0, 0 });
			text = ini.get<String>(L"Content.text", L"");
			textColor = ini.get<Color>(L"Content.textColor", { 0, 0, 0 });
		}
	}
}

結果の一例
INI ファイルを編集すると即座に変更が反映されます。

アプリ制作で微調整が必要な個所では INIReader が大いに役立ちます。

Siv3D December 2013 リリース | おすすめ機能

昨日 Siv3D December 2013 をリリースしました。5 か月ぶりの安定版です。
たくさんの更新内容 の中で、僕が特に気に入ってる 8 つのトピックを紹介します。

インストール時間が短くなった

June 2013 で Visual Studio プロジェクトテンプレートを導入、そして今回簡易インストーラを導入することで、セットアップの手間を 3 秒に短縮しました。
ユーザーができるだけ早く Siv3D を遊べることを目標に、さらに短くできる可能性を追求していきます。

もっと C++11 に対応

Visual Studio 2013 が対応した C++11 の新機能を活用して、より書きやすく読みやすいコードのために API デザインを一新しました。
いくつか例を紹介します。

Uniform initialization によって Vec2 や Color を省略できるようになりました。

	Triangle triangle(Vec2(10, 10), Vec2(30, 30), Vec2(10, 30));

	triangle.draw(Color(0, 255, 0));
	Triangle triangle({ 10, 10 }, { 30, 30 }, { 10, 30 });

	triangle.draw({ 0, 255, 0 });

Initializer list を使って Polygon を直接構築できるようになりました。

	// 頂点の配列
	const Vec2 pts[] =
	{
		Vec2(0,-200),Vec2(40,-60),
		Vec2(180,-60),Vec2(75,25),
		Vec2(115,160),Vec2(0,80),
		Vec2(-115,160),Vec2(-75,25),
		Vec2(-180,-60),Vec2(-40,-60)
	};

	// 頂点の配列から多角形を作成
	const Polygon star(pts);
	const Polygon star
	{
		{ 0, -200 }, { 40, -60 },
		{ 180, -60 }, { 75, 25 },
		{ 115, 160 }, { 0, 80 },
		{ -115, 160 }, { -75, 25 },
		{ -180, -60 }, { -40, -60 }
	};

Variadic templates のおかげで Format がスタイリッシュになりました。

String text = Format() + L"aaa" + (1 + 1) + Vec2(20, 20) + L'b';
String text = Format(L"aaa", 1 + 1, Vec2(20, 20), L'b');

FFT

再生・録音している音声のスペクトルを解析できるようになりました。音楽に使えばオーディオビジュアライザーや曲調に合わせた演出、マイクに使えば音声に応じたインタラクティブなアプリケーションを開発できます。( サンプル | マイク入力のスペクトラム )
正確な音の高さの口笛を吹く音ゲーを作るのとか面白そうな気がします。

フルスクリーン・ウィンドウの切り替え

実行中にフルスクリーンモード・ウィンドウモードを切り替えられるようになりました。
フルスクリーンモードが対応する解像度は環境によって違うので

std::vector<Point> Graphics::GetFullScreenSize()

関数で、利用可能な解像度を調べておきましょう。

アセット管理

August 2013 beta まで「Resource」という名前の実験的機能でしたが、正式機能に格上げされました。( リファレンス | アセット管理 )
プログラムのどこからでも名前だけで Texture や Sound, Font などのアセットデータを取得できるので、ゲームのように大量のアセットを使うアプリケーションの開発が楽になります。
アプリの実行中に画像やサウンドファイルを変更すると即座に実行中のアプリに反映される機能も開発のスピードを加速させます。

プログラミング入門者向けの簡単な文法

Siv3D で入門者にプログラミングの楽しさを教えられることが目標の 1 つです。
今回の Siv3D には Print / Println / WaitKey という 3 つの強力な関数が追加されました。
これを使えば、メインループや Font のことを考えずに Hello world を書けます。

# include <Siv3D.hpp>

void Main()
{
	Println(L"Hello, world!");

	WaitKey();
}

Print / Println は画面の最前面にテキストを表示する、Siv3D 版の printf や std::cout です。通常の開発時にもデバッグ出力として利用できます。

# include <Siv3D.hpp>

void Main()
{
	while (System::Update())
	{
		if (Input::MouseL.clicked)
		{
			Println(L"クリックした座標: ", Mouse::Pos());
		}
	}
}

WaitKey の内部では特殊なメインループを実行していて、直前に描画、サウンド再生を入れることもできます。

# include <Siv3D.hpp>

void Main()
{
	Circle(100, 100, 50).draw();

	const Sound sound = Dialog::OpenSound();

	sound.play();

	WaitKey();
}

図形描画の機能拡張

アーチ型、LineStrig の頂点の整形、スプライン曲線、グラデーション付き図形といった機能を追加しました。
図形描画はテクスチャ描画に比べて、メモリ消費が少なく描画速度も速いといった利点があるので、なるべく多くのことを図形描画でできるようにしていきたいです。

サウンドファイルの機能拡張

32bit float WAVE や、特別なヘッダの入った WAVE ファイルが読み込めなかったバグ、一部の MP3 のデコードで曲の最後にノイズが入るバグなど、サウンドファイル関連の問題を修正しました。
出力については新しく MP3(Windows 8 以降) と WMA(Windows 7 以降) の保存に対応し、WAVE, OggVorbis, AAC と合わせて主要なオーディオフォーマットをほぼカバーしました。

December 2013 の新機能についてはまだ紹介しきれていないことが多いので、今後ブログで解説記事を増やしていきます。