[BMS] BMSに使用されている音声の圧縮前サイズ



BMS製作について Advent Calendar 2015 の 4日目の記事になります。

昨日はasahi3さんで、「かんたん!FL Studioを使った作曲+BMS制作入門」でした。

今日は、BMSに使用されている音声の圧縮前サイズについて話したいと思います。以前「BMSのファイルサイズを小さくする工夫」という記事を書いたのですが、その際に「年々BMSのファイルサイズが増加している」というような内容のことを書きました。しかしそれは本当なのか? と思い、調べてみることにしました。

wavに関してはファイルサイズの合計を、oggに関しては、それぞれのoggファイルに対して、サンプル数×チャンネル数×(ビット深度÷8) を計算し、その合計を取っています。wavファイルの場合、ヘッダの容量(40バイト程度×ファイル数)が余計に加算されてしまいますが、細かいことは気にしないことにします。この結果自体に何か意味がある訳ではありませんが、知っておくといつか役に立つかもしれません。

(ちなみに、なぜ圧縮後のサイズではなく圧縮前のサイズなのかと言うと、LR2ではoggファイルを読み込む際、再生前にメモリ上にデコードしたデータを格納している(と思われる)からという理由によるものです。また、oggファイルの圧縮率による影響を除いて調査したかったという意図もあります。これに関しては圧縮後のファイルサイズを調べてもよかったかもしれません。)

結果

データセットはBOFU2015BOF2009です。フォルダ内に wav と ogg の両方を含む曲、および wav/ogg がサブフォルダにフォルダ分けされている曲は除外してあります。また、正常に読み込みの出来なかった一部のoggファイルに関しても、そのファイルのみ集計から除外しています。(恐らく使用しているライブラリ(=NVorbis)の問題と思われます。) ですので、あくまでおおよその目安としてご利用下さい。

BOFU2015

bofu2015

総数 平均値 中央値 最小値 最大値
wav 103曲 90.4MB 63.1MB 3.29MB 467MB
ogg 329曲 126MB 99.3MB 11.3MB 727MB
除外 8曲

単位:MB(=2^20バイト)

BOF2009

bof2009

総数 平均値 中央値 最小値 最大値
wav 88曲 36.4MB 26.6MB 2.79MB 143MB
ogg 112曲 62.6MB 56.0MB 2.21MB 274MB
除外 2曲

単位:MB(=2^20バイト)

BOFU2015の半分程度、あるいはそれ以下ですね。

考察

予想通り、6年前と比べると1曲あたりのサイズが大きくなっていたということがわかりました。また、意外と wav のままの人が多いので驚きました。全体の1/4くらいでしょうか。グラフを見ると、BOFU2015では、圧縮前サイズが300MBを超える曲が16曲あったということが分かりますが、これは多いような気もしますし、少ないような気もします。昨年のG2Rで優勝したAltaleも、展開後で463MBほどの大きさでした。さらにBOFUの順位ごとに分けて調べたり、★1と★24で比較したりすると面白いかなとも思いましたが、面倒なのでやらない気がします。

余談ですが、実は2009年にはまだBMHelperが世に出ていなかったとか。(※要出典)

明日はBluvelさんの「無難な譜面を作ろう」です。それでは。

ソースコード

NuGetからNVorbisを追加しておいてください。

        private void button1_Click(object sender, EventArgs e)
        {
            var PackagePath = @"C:\********\BOFU2015";

            var w = new StreamWriter("bms_statistics.csv");
            w.WriteLine("path, wav, ogg, errors");

            foreach (var teamPath in Directory.GetDirectories(PackagePath))
            {
                foreach (var bmsFolderPath in Directory.GetDirectories(teamPath))
                {
                    double totalOggBytes = 0;
                    double totalWavBytes = 0;
                    int totalErrorCount = 0;

                    foreach (var wavoggPath in Directory.GetFiles(bmsFolderPath))
                    {
                        try
                        {
                            if (Path.GetExtension(wavoggPath).ToLower() == ".ogg")
                            {
                                var r = new NVorbis.VorbisReader(wavoggPath);

                                var rate = r.SampleRate;
                                var count = r.TotalSamples;
                                var bit = 16;  // 16bit/sample
                                var chCnt = r.Channels;

                                var bytes = count * (bit / 8) * chCnt;

                                totalOggBytes += bytes;
                            }
                            else if (Path.GetExtension(wavoggPath).ToLower() == ".wav")
                            {
                                totalWavBytes += (new FileInfo(wavoggPath)).Length;
                            }
                        }
                        catch
                        {
                            totalErrorCount++;
                        }
                    }

                    // ↓ フォルダ名を取得するためにGetFileNameを使っているところに闇を感じる
                    Console.WriteLine(
                        "path=" + Path.GetFileName(teamPath) + "/" + Path.GetFileName(bmsFolderPath)
                        + ", wav=" + String.Format("{0:#,0} Bytes", totalWavBytes)
                        + ", ogg=" + String.Format("{0:#,0} Bytes", totalOggBytes) 
                        + ", errors=" + totalErrorCount);

                    w.WriteLine(
                        Path.GetFileName(teamPath) + "/" + Path.GetFileName(bmsFolderPath)
                        + ", " + totalWavBytes
                        + ", " + totalOggBytes
                        + ", " + totalErrorCount);
                    w.Flush();
                }
            }
        }