BGM周り改善

いい加減dat読み込み関数をフックしてogg/sflの読み込み時に無音BGMデータを渡して流させDLLの方がこっちもDirectSoundで別に本命のBGMを流すという、実は2重にBGM再生が働いているという残念な状態を改善しにかかりました。1プロセスにDirectSoundオブジェクトは1つしか存在してはいけないという情報もあったし…(まあ動いちゃってたんだけど)。


id:nagoya313からの情報を書きとめたヘッダ記述

const DWORD SWRS_ADDR_PLAY_BGM   = 0x0043F4A0;

よっこらせ

void BattleHook::playBGM(const char *file)
{
	typedef void (*PLAY_BGM_FUNC)(const char *name);
	PLAY_BGM_FUNC pFunc = (PLAY_BGM_FUNC)SWRS_ADDR_PLAY_BGM;
	pFunc(file);
}


でまあこれを使うのはテーレッテー時のみなんですね。通常のBGM切り換え時は内部でこれが呼ばれてるだけなのでdatロード関数フックの部分をいじくって本物のBGMを読ませればいいわけです。北斗戦闘システムからテーレッテーを流したいときは

playBGM("st99.ogg");

とします。st99.oggはかの夢想天生テレッテアレンジです。もちろんこいつをロードする時には本物に差し替えるわけですが…。


さて、ここからですね。あとはogg/sflの読み込みをすり替えるんですが、現在のcsv/wavなどのやり方(専用の場所にその名前のファイルが置いてあったらそれにすり替える)だと、同じファイルのコピーが大量にできてしまうんですね(非想天則は30くらい?北斗は8個)。北斗のBGMは1つ1〜2MBあるし、ファイル名をstXX.oggにしてしまうと割り当てを変更したいときに何が何やら。ということでマッピングをiniに書くことにしました。結局BGMChangerと同じようなもんか…。

[BGM]
; 君はあの影を見たか(オープニング)
op2=title
; 萃夢想(キャラセレ)
select=charasel

; 地の色は黄色 (倒壊神社)
st00=kenshiro
; 香る樹葉花 (魔法の森)
st01=raoh
; 踊る水飛沫 (玄武の沢)
st02=toki
; 嘲りの遊戯 (妖怪の山)
st03=toki
...

op2→op2.ogg/op2.sflと補完してください。


で、最後の課題がSFLデータでした。どっかの波形編集ソフトのしおりデータみたいなものをそのままループポイントにしているようなのですが、プログラム上で適当に(繰り返し頭=0x0, 繰り返し終わり=0xffffffff)とかにしたら1回再生して終わりになってしまったのでごちゃごちゃやってたのですが、結局(繰り返し頭=0x1, 繰り返し終わり=0x0fffffff)あたりにすれば共通で最初から最後までをループし続けることが分かりました。
それがわかるまでにした無駄な行動リスト

  • ファイル名と2つのループポイント(単位: サンプル)を入力するとSFLファイルを出力するへなちょこプログラムを書く。
  • SoundEngineFreeで時間表示を秒とかからサンプル数単位に変えられるのにさすがと思い、さっきのプログラムでSFLを作って試す。
  • ループが長すぎるのでステートを見てタイトル画面に変わった瞬間playBGM("st99.ogg");するように。
  • 作っては上書きコピーとか、バイナリデータとか明らかに試行錯誤するのに向いてないのに気付き、DLLのSFLロード差し替えを、テキストファイルからifstream >> intro >> loop;な感じで数値を2つ読み込んで構造体にぶち込み、それを読んだことにするようにする。
  • さっきのコンソールアプリケーションプロジェクトがお払い箱。
  • テキストファイルの書き換えで、非想天則を立ち上げたまま何度もテストができる。やはり時代はスクリプト言語なのだと実感する。
  • その効率の良いトライアンドエラーにより、左端は0ではだめだが1では大丈夫で、右端は別にBGMの長さに合わせなくても無駄に大きい値で大丈夫なことが発覚した。つまりoggごとにループポイントデータを用意しなくてもよし(北斗のBGMってループポイントないのよね)。
  • テキスト→バイナリ変換つきローダクラスがお払い箱に。
  • 一応北斗マッピングされてないBGMのSFLは元のままの処理にしています。
fake->dwIntroSamples = 1;
fake->dwLoopSamples  = 0x0fffffff;

決め打ちでおk。0xffffffffだとうまくいかなかった。どうせあっちの構造体宣言が(signed)intにでもなってるんだろ*1。その予想が当たってるならINT_MAXとか0x7fffffffとか書いてもいいけどサンプル数はそこまでの桁までいかないので適当で。


BGMのマッピングなんですが、ランダム(初期カーソル)を選ぶとステージごとにBGMが決まっていて、複数BGMがあるステージだとその中からランダムに選ばれるんですね。だからステージごとにBGMを設定することが可能です。たまたま見つけて読んだことには、非想天則になってステージに関係ないBGMを手動で選べるようになったということです。なるほど緋想天を見てみると1ステージに1or2BGMで、左右で2つを切り替えることしかできません。非になってこの制限がなくなったけど、ランダムセレクト時はそのままということですね。何というか気付かなかった。ということで緋想天のステージセレクト画面のBGM対応を見ながらせっせとマッピングしました。
void playBGM(const char *)のアドレスが解析済みっていう明快な作業のはずなのにこの作業時間。いや予期できていたから今までぐだってたんだな…。


プログラマの産物を使うときはそれの生成過程でこういう工程を得ていることを心にとめてほしい(別にとめなくてもいいけど)。そしてこんなことやって眠くて疲れて腹が減った状態を悦べる人以外はプログラマをやっちゃだめだと思う。絶対不幸になる。

*1:おいおいおいおい、喧嘩売ってるのか。