S98Player for iPhoneではYM2608音源のエミュレーションにはfmgenを利用させていただいているのですが、それとは異なるMAMEベースのymfmというソフトウェアがgithubに登録されたということを知りました。

そこで、とりあえず簡易的にS98を再生させてその再現性を確認してみることにしました。結論としてはなかなかいいかも!です。

ymfmって?

一言で言えば、YAMAHA製FM音源チップのエミュレータ群(OPM, OPN, OPLなど…)です。出自はMAMEとのこと。ライセンスが明確(BSDライセンス)なのは利用者にとっては嬉しいところです。

「エミュレータ群」と書きましたが、vgmrenderというサンプルコードの中では、以下のようなチップのタイプが定義されています。

enum chip_type
{
	CHIP_YM2149,
	CHIP_YM2151,
	CHIP_YM2203,
	CHIP_YM2413,
	CHIP_YM2608,
	CHIP_YM2610,
	CHIP_YM2612,
	CHIP_YM3526,
	CHIP_Y8950,
	CHIP_YM3812,
	CHIP_YMF262,
	CHIP_YMF278B,
	CHIP_TYPES
};

ウェブサイトにはより詳細な記述があります。

このリポジトリを知ったきっかけは、以下のツイートが流れてきたことでした。

MAMEの方は触ったことがないので自分としては品質は想像つかないものの、簡単に確認できるなら触ってみたいということで、少しいじってみることにしたわけです。

S98をどう再生するか

案1: S98Player for iPhone内のfmgenを置き換えてみる

一つ目は、真っ当に(?)S98Player for iPhone内のコードでfmgenを叩いている部分をymfmに置き換える方法。

チップのレジスタに書き込んで、音のデータを配列に書き出すみたいな構造は同じなのでそれほど難しくはなさそうなのですが、S98Player for iPhone側のコードにいけてない部分があり、ちょっと時間がかかりそうなので一旦深追いはストップ。実際には次の方法を試しました。

案2: サンプルのvgmrenderに突っ込む

ymfmのサンプルコードに、vgmファイルをwavに変換するvgmrenderというものがあります。

VGMファイルはいわゆるサウンドログのファイルフォーマットで、S98と似ています。S98からvgmにファイルを変換できればとりあえずは音を確認できるだろうってことで、こちらのアプローチで行くことに。

S98からVGMへの変換ですが、ファイルフォーマットを確認して地道にやっていくという方法もありますが、先人がいないかどうかを(正直、「念の為」レベルでした)調べてみたところ、いらっしゃいました!s98-to-vgmというそのものズバリのリポジトリ。これでやってみることにします。

vgmrenderで再生する

s98からvgmへの変換

s98-to-vgmのREADME.mdに記載の通りです。Mac上ではまずhomebrewなどでnpmをインストールし、その後リンク先に記載のあるようにnpm installして動作させます。

インストールしたら、以下のようにして変換します。

% s98-to-vgm -o output.vgm input.s98

vgmrenderのビルドと実行

こちらもvgmrender.cpp内のコメントに記載のビルド手順でビルドすればOKです。

% g++ --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_misc.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe

との記載があります。まぁ、Macならexeが嫌なら拡張子なし(-o vgmrender にする)でも。

実行前にちょっとコードを眺めると、以下のような記述が見つかります。

if (type == CHIP_YM2608)
{
	FILE *rom = fopen("ym2608_adpcm_rom.bin", "rb");
	if (rom == nullptr)
		fprintf(stderr, "Warning: YM2608 enabled but ym2608_adpcm_rom.bin not found\n");
	else
	{
		fseek(rom, 0, SEEK_END);
		uint32_t size = ftell(rom);
		fseek(rom, 0, SEEK_SET);
		std::vector<uint8_t> temp(size);
		fread(&temp[0], 1, size, rom);
		fclose(rom);
		for (auto chip : active_chips)
			if (chip->type() == type)
				chip->write_data(ymfm::ACCESS_ADPCM_A, 0, size, &temp[0]);
	}
}

ym2608にはADPCMのリズム音源が搭載されており、チップからダンプした音源ファイルを読み込ませているところです。上記ファイル名のダンプデータをお持ちの方は同じディレクトリに置いておきましょう(ファイル名で検索かけると出てきてしまいますが…)。

実際に実行すると、以下のように使用する音源などが表示されWAVファイルを生成します。

% ./vgmrender.exe sample.vgm -o sample.wav
Adding YM2608 @ 7987200Hz

これで出来上がったWAVファイルを開いてみると、きちんと音が聞こえます!なんですが…。

SSG音源のボリュームを下げたい

PC-98向けのサウンドログを再生されたことある方は、「あー、あれね」ですよね。

これ、YM2608のエミュレーションとしては全く問題ないのですが、PC-98向けのS98ファイル再生という観点からすると、SSG音源のボリュームが大きいんです。YM2608から出力されたFM音源とSSG音源の音色をサウンドボード上でミックスするため、サウンドボードによってそのボリュームが異なってしまうというのが理由です。PC-98のサウンドボード(いわゆる86音源)向けに、もうちょい工夫が必要というわけです。

これ、fmgenでも同様で、数字としてSSG側のボリュームを「-18(-9db)」くらいにするとちょうどいい感じになるようです(自分の好み。もしかしたらちょっとSSG小さめかも)。

ymfmにSSGのボリュームを調整するAPIがなさそう→突貫の解決策

ちょっとみた感じ、ボリューム調整のメソッドがなさそうです。fmgenのようにFM/SSG/RHYTHMをシンプルにミックスしている箇所もないです(リサンプリングとかしている)。ただまぁ、今回はとりあえずそれっぽい音になるかを確かめたいだけなので、突貫でやっつけます。

もう、お披露目するのも恥ずかしいのですが、こうです。

hiroaki@HiroMacMini src % git diff
diff --git a/src/ymfm_ssg.cpp b/src/ymfm_ssg.cpp
index 410452b..9ef790f 100644
--- a/src/ymfm_ssg.cpp
+++ b/src/ymfm_ssg.cpp
@@ -225,7 +225,7 @@ void ssg_engine::output(output_data &output)
                }
 
                // convert to amplitude
-               output.data[chan] = s_amplitudes[volume];
+               output.data[chan] = s_amplitudes[volume] / 4;
        }
 }

ssg音源部分のデータ出力部分を、思いっきり割り算して音を小さくしています…。まぁ、とりあえず数字はこんなもんかね、くらいですが(-10dbで1/3くらいなので、fmgenの値とも違いそうです)。

感想とまとめ

実際に聞いてみると、ダンプデータを使うとよりリズム部分が本物っぽいというのと(これは当たり前)、SSGに関しても上品すぎないというか、FM音源と馴染んでる気がします。

ここまで来たら、やっぱりS98Player for iPhoneに移植するのが正解かもですねぇ。時間ができたらやろう…。