システム/技術共有所

Last-modified: 2024-03-16 (土) 18:07:41

SPの技術

変数とその詳細

変数詳細
HISCP[曲番号,内容]過去バージョンからのデータの引き継ぎに利用される。
HISC[難易度,曲番号,内容]ハイスコアを保存する
難易度は0がかため、1がやわらかめ(使われてない)
内容は0→おもち%、1→○、2→△、3→×、4→使われていない
例えばHISC[0,0,1]なら最初の曲のかための○の数を表す
BTIPE[0]ボタン設定
BGM_TIT$[曲番号]曲のタイトル
BGM_HARD[難易度,曲番号]曲の難易度
BGM_VOL[曲番号]曲の音量
TROPHY[難易度,曲番号]トロフィーの有無
CMPS$[曲番号]提供者名
FMT[3000]音符の判定を行うフレーム数
音符が3000個を超えるとエラーになってしまう。
また、FMT[1023]までしか初期化されていないため実質1024個しか使えない。*1*2
FMC[3000]赤音符を流すか、青音符を流すかの判定。赤は0、青は1
FMK[3000]音符のスプライトの管理番号
FMR[3000]音符の判定結果。と書かれているが、使われていない。
FMF[3000]1→おもちが飛んでいる。0→飛んでいない。おもちを飛ばす処理は@FLY
FMX[3000]
FMY[3000]
FMV[3000]
FMXV[3000]
おもちを飛ばす処理で使用。

いろいろな制限、仕様

  • 曲数…64曲
    • HISCやBGM_TIT$などのサイズ
    • 91行目のFOR Q=0 TO 128-1(あまり関係ないが)
    • 146行目のFOR Q=0 TO 63
  • 音符数…1024個
    • FM~で始まる変数のサイズ
    • 2023行目のFOR W=0 TO 1023
  • 譜面の行数…129行
    • 2232行目のFOR W=0 TO 128
  • その他
    • 最後の曲(BGM 1)を選んでオプション画面などに行った後選曲画面に戻ると、曲名の表示が間違っている
      • 1535行目IF QQ+Q>=BGM_MAX+1 THEN QQ=QQ-(BGM_MAX+1)

NSPの技術

譜面読み込み(ver4.x)

MML解析

  • MMLを1文字ずつ読み込み、Tコマンドがあれば配列TMPCHGP%にその位置(最初から192分音符何個目か)、TMPCHGV%に変更後のテンポを追加
  • これをやるにはLコマンドを認識し音符や休符を数え、ループも正しく処理し、さらに他のMMLコマンドを音符と区別する必要もあるので、かなり複雑になっています
  • ついでにNEx.の譜面もこのとき生成します
  • その後TMPCHGP%のそれぞれが最初から何ms後かを計算しTMPCHGM#に入れます

譜面解析

  • 音符1個ごとにタイミングや速度などを計算し以下の配列に追加します
  • 音符のタイミングを計算する時にすでに計算したテンポ変化を考慮に入れます (例えば譜面がL1の休符なのにMMLは4分音符でテンポが変わっているかもしれない)
  • 時刻は最初の音符or休符が判定枠に到達する時刻を0とする
    配列概要
    NT1#音符を流し始める時刻(ms)
    NT2#音符を流し終わる時刻(ms)
    NT3#判定する時刻(ms)
    NX1#音符を流し始める位置(0=判定枠,368=右端)
    NX2#音符を流し終わる位置(0=判定枠,368=右端)
    NC%音符の種類(赤0,青1)
    NI%後述、使わない場合-1
    NSP%今は-1, あとで使う
  • Hコマンドによる速度変化がない場合、「流し終わる」とは判定するのと同じとします。
  • Hコマンドによる速度変化がある場合
    • Hコマンドの値は配列HBCHGM#に速度変化するタイミング(ms)、HBCHGVに変化後のHの値が入ります
    • これを逆順に読み込み、音符の速度が変化するごとに別々の音符とみなして上の各配列に入れます
  • 例「L4H400_H200_H100_●」(_は空白、●はHS1の赤餅)
    • 以下話を単純にするために4分音符1個分の時間を1とし、右端を位置4とします(=H100の4分音符1個分の距離を1とします)
    • この音符は時刻2から時刻3までH100の速度で動き、時刻3で判定します
    • 位置を計算すると時刻3で位置0、時刻2で位置1となります
      • →NT1#に2, NT2#に3, NT3#に3, NX1#に1, NX2#に0, NC%に0, NI%とNSP%に-1を追加
    • 次に時刻1から時刻2までH200の速度で動きます
    • 前の計算結果から時刻2で位置1なので、時刻1では位置3となります
      • →NT1#に1, NT2#に2, NT3#に3, NX1#に3, NX2#に1, NC%に0, NSP%に-1を追加
      • このときNI%にLEN(NI%)-1 (この場合0)を追加します。これは速度変化後の次のデータがNT1#[0]~NC%[0]に入っていることを表します
    • 次に時刻0から時刻1までH400の速度で動きます
    • 前の計算結果から時刻1で位置3なので、時刻0では位置7となります
    • これは画面をはみ出すので、画面をはみ出さないよう再計算して時刻0.75で位置4です
      • →NT1#に0.75, NT2#に1, NT3#に3, NX1#に4, NX2#に3, NC%に0, NSP%に-1を追加
      • NI%にはLEN(NI%)-1 (この場合1)を追加します
    • 時刻0より前はH100(初期値)の速度で動きますが、すでに画面外なので計算しません

譜面開始後

  • 他の多くのおもちつきとの一番の違いは譜面開始後に譜面データを1文字ずつ読むのではなく事前に解析した配列のみを読んでいる点です (KRを参考にしたものだった気がする)
  • NT1#をソートしたものがNT1_C#にあり、NT1#が小さい順にデータを読み込みます
  • 変数HMSに譜面開始からの時間(ms)があるのでHMSがNT1#の値を超えたら音符を表示します
  • NSP%が-1の場合新しいSP管理番号を決め音符を表示し、以下の配列に表示した音符の判定の情報を追加します
    配列概要
    FMT%判定する時刻(ms)
    FMC%音符の種類(赤0,青1)
    FMK%SPの管理番号
    FME%判定済=1
  • また以下のSPVARに音符の表示情報を追加します
    SPVAR番号概要
    0おもちが飛んでいる=1
    1音符を流し始める時刻(ms)
    2音符を流し終わる時刻(ms)
    3音符を流し始める位置
    4音符を流し終わる位置
    5音符を流し始めるZ座標
    6音符を流し終わるZ座標
  • NI%が-1でない場合(=この音符がこの後速度変化する場合)、NSP%[NI%]にSPの管理番号を入れます
  • NSP%が-1でない場合
    • これはつまりこれが速度変化した後の音符を表し、速度変化する前の音符が管理番号NSP%のSPとして表示されているということです
    • なのでそのSPのSPVARを変化後の情報で上書きします
  • 画面上の各SPについて、SPVARの情報をもとに毎フレームSPOFSします
  • 配列FMT%,FMC%,FMK%,FME%の情報をもとに音符の判定をします
    • 判定が終わって不要になったら配列の先頭から消します
  • ver4.8では速度変化の影響で1個の音符を2回判定してしまうバグがあるっぽいです

(オプションの)テンポ変更

MMLのテンポは必ず整数なので、そのまま0.9倍して四捨五入とかするとずれてしまいます。

例えばMMLのテンポが234だったとします。
0.9倍すると210.6になるので、四捨五入してMMLのテンポは211になります。
→それなら譜面のテンポも211にすればいいじゃないですか。

じゃあ譜面のテンポがMMLと違った時は?
例えばMMLのテンポが234、譜面のテンポが117だったとします。
同様に0.9倍するとMMLのテンポは211になります。
ここで、211は234の何倍になっているかを計算すると、211÷234=0.9017...倍になります。
→それなら譜面のテンポも0.9017...倍に(117×0.9017...=105.5)すればMMLとずれなくなりますよね。

譜面読み込み(ver3.x以前)

テンポ自動読み込み

MML文字列を1文字ずつ調べ、テンポ変更(Tまたはt)を探します。
見つかったらそのテンポを配列TMPCHGに追加し、「$7=配列の番号」を追加します。
例えば「T120」を見つけたらTMPCHG[0]=120になり、MMLは「T120$7=1」にします。
(再生直後にBGMVARが0を返すため、$7の値は配列のインデックスより1多くしています)
こうすることで再生時にBGMVARで$7の値を調べ、配列からテンポが取得できます。

MILLISEC

プレイ開始時のMILLISECの値をLASTMSに入れます。
譜面読み込み時に、音符の長さ(ミリ秒)を計算します。(60000/テンポ*4/L値)
現在のMILLISECとLASTMSの差が上の値より大きくなったら、音符を1つ読み込み、LASTMSに上の値を加算します。

テンポ自動変更

BGMVARでMMLのテンポの変更を検知したとき、
テンポが前の何倍になったかを計算し、
現在流れているすべての音符の流れる速度を一度に変更しています。
Hコマンドもこれとほぼ同じことをしています。

譜面ファイル読み込み

以下、「曲番号」「譜面ID」「ファイル番号」は別物なので注意
曲数が変わっても対応できるように、すべて1次元配列にしてPUSHしています。

変数詳細
BGM_TIT$[曲番号]曲のタイトル
BGM_HARD[曲番号*5+難易度]曲の難易度
1次元配列で、1曲目軟→普→硬→カチ→黒→2曲目軟→...
BGM_VOL[曲番号]曲の音量
BGM_EXIST[曲番号*5+難易度]譜面が存在すれば1、なければ0
これも1次元配列
BGM_FILE0[曲番号]現在使用されていない
BGM_ID0[曲番号]
BGM_FILE[曲番号]曲番号に対応するファイル番号を返す。
BGM_ID[曲番号]曲番号に対応する譜面ID(譜面ファイルで設定したID)を返す。
CMPS$[曲番号]曲の作曲者
RDTMP[7]読み込むときにちょっと使っただけです
F$[ファイル番号]FILESの戻り値
ファイル名を返す。

FILESの戻り値F$を1つずつ調べて読み込みます。
なのでファイルの読み込み順はABC順になります。
曲番号はNewSPが読み込んだ順に0,1,2,...と自動でつきます。
譜面やMMLを読み込むときだけ曲番号→ファイル番号、譜面IDに変換して使っています。

譜面読み込み1(DEF FUMENREAD$())

NewSPモード
そのまま。

TJASモード
NewSPモードに変換する。
#BPM,数値→T数値*3
#HS,数値→S数値*100
#HB,数値→H数値*100
#MEA,数値→とりあえず変数MEAに入れる
コンマを読み込んだとき→L(文字数/MEA)とその小節の譜面データ

譜面読み込み2

FORで譜面を1文字ずつ調べる。
コマンドは飛ばす。
ただしLコマンドは最大値を変数MAXLに入れて記憶しておく。
音符の記号(ONP_D$とONP_K$にすべて一覧になって入っている)の個数を数えてMAXCMBに入れる。

譜面読み込み2.5

譜面先頭にT数値があった場合はそれを曲のテンポとする

譜面読み込み3(メインループ直前)

BEF=1にする。(確かbeforeの略だった気がする)
ONPLIMITは読み込む小節数。(旧3DSでは重くなるためMAXLによって変更する。)
STARTMS(曲を流し始めるMILLISEC)とLASTMS(タイミング合わせ)をここで設定。
FOR文でCNTとMSを少しずつ変えながら@HUMEN2を呼び出して、(ONPLIMIT-1)小節を読み込む。(その後もう1小節追加された)
このあたりの実装は動けばいいやで試行錯誤したので自分でもよくわかってない。
終わったらBEF=-1にする。

譜面読み込み4(@HUMEN)

CNTに開始からのフレーム数、MSにミリ秒を入れる。
BGMVARでテンポが変わったか確認。
テンポまたはHBが変わった場合は、今流れているすべての餅の速度を変更する。
STARTMSが経過したら曲を流し始め、BEF=0
そのまま@HUMEN2へ行く。

譜面読み込み5(@HUMEN2)

BEF=-1の間の1小節分は開始前に読み込んでしまったので、スルーする。
LASTMSでタイミングを合わせる。
各種コマンドを読み込む。
L,Sの値は変数ONPL,ONPSに入れる。
Hは1小節後に変化させるため配列HBCHGF,HBCHGVに入れ、後で@HUMENで読み込む
IFを並べる気にはならないのでINSTRN(INSTR)とMID$,VALを使って音符の種類、速度を読み込む。
音符位置(SPOF)を計算する。この式もよくわからない。
SPでは3000の配列に音符の情報を入れていたが、NewSPではPUSHで追加していき、判定が終わったものはPOPで削除している。

TGの技術

複数のデータ作成

SPTG
HISC[難易度(かため),曲番号,内容]SVDT[難易度,曲ID,内容,データ番号]
BTIPE[内容(ボタン)]SETTING[内容,データ番号]

要素数を1つ増やしています。
SPから引き継ぐことも可能です(その場合はDATA 1のかために引き継がれる)。

移植元表示

ver.4.10以前

配列概要
BGM_TITB$[曲ID]元の曲名です。
移植元表記をオフにした場合に表示されます。
移植元表記をオンにしてても、演奏画面で表示されます。
BGM_TIT$[難易度,曲ID](移植)と表示する際の曲名です。
移植元表記を(移植)にした場合に表示されます。
BGM_TITA$[難易度,曲ID](SP)等と移植元表記する場合の曲名です。
移植元表記を(SP)にした場合に表示されます。
BEFORE$[難易度,曲ID]移植元です。

難易度によって移植元が違ったりするが場合があるので、移植元や、移植元表記を付けたあとの曲名は、難易度別になっています。
BGM_TIT$は、配列BEFORE$の中身がヌル文字じゃない場合のみ、(移植)と表記するようにしています。

ver.4.20以降

BEFORE$を曲名の下に表示するようになった為、BGM_TIT$,BGM_TITA$が消えました。
(そもそもよく考えれば、BGM_TITB$[BGMN]+BEFORE$[HARD,BGMN]とすればよかったので、元々必要なかった。)

コマンド

NSPのLコマンドとSコマンドが使えます。また、l16やs100など、小文字でもいけます。

テンポ変更

Tコマンドも使えるようになりました。
ただし、NSPとは仕様が異なり、自動でHSが掛かります。太鼓シミュレーターの#BPMと同じ仕様です。*4
こちらも、t120など、小文字でも可です。

MILLISEC

NSPとは違い、SMSという変数のみを使っています。
ゲーム開始時に、MILLISECをSMSに代入します。
それ以降音符を読み込む時間*5をSMSに加算していきます。
SMSがMILLISEC+{(1000/60)*(120*4)/TMP}*ONPLIMIT*6以上になったら音符を読み込み、加算します。
これの繰り返しです。

TJASモード

TJASモードでの記述が可能になりました(記号はそのまま)。
NSPでは毎小節最初にL数字が付いていたが、こちらでは変換の際にLの値を保持しているので、Lが変わったときのみL数字が付くようになります(譜面圧縮しない限りあまり気にすることではないですが)。

隠しコマンド

  • L+R+Aを押し続けることでタイトル前のTAGeLorixのロゴを飛ばすことが可能。
  • メニューなどでXボタンを押すと、その項目の説明が確認できる。
  • デバッグ変数DEBUGを1にするとこで、デバッグ情報(FREEMEM、平均密度、最高密度、☆の数の自動計算等)が確認できる。

KR/HD系の技術

MILLISEC

譜面読み込み時に、音符が判定枠に来るタイミング(240000/_ONP*7/BPM*文字目[ミリ秒])を求め、そこから(240000/BPM/HS)引いたのが音符を右端から出すタイミングになる。
最初の音符が判定枠にくる2秒前*8にSTARTCNTをセットし、更に現在のMILLISECからSTARTCNTを引いたものをSTARTCNTに代入する。演奏中のループごとにCNTという変数に現在のMILLISECからSTARTCNTを引いたものを代入し、それが音符を出すタイミング値以上になったら音符を画面右端に出す。

プリセット原曲の使用

起動時にMMLを読み込む時、「#PRESET」であればその後の数字を読み込み、「#PRESET nn」をL_MML$配列に入れます。
曲を流すとき、MML文字列の最初8文字が「#PRESET 」であれば、後ろの2文字を数値に変換し、その番号のプリセット原曲を流します。

一般的な判定

FORループで配列の最初から、ノーツを1つずつ見ます。
判定枠に来るタイミングが×判定の範囲よりも後になったら、その時点でBREAKします。
BPMを負の数にして戻すといった特別なことをしない限りは、判定枠に来るタイミング順で配列に入っているので、これで問題ありません。

MMLのカット

MML解析になります。大まかには以下の流れで処理を行っています。

  1. 解析を行いやすいようにする。
    • コメント削除
    • 小文字→大文字変換
    • ループやマクロの展開
  2. 音符をカウントする
  3. 条件次第で、音符の削除・長さ短縮を行う。
    • 範囲内…何もしない
    • 範囲内と範囲外の両方に跨いでいる…範囲内のみを残し、長さを短くする。
    • 範囲外…音符自体を削除する
  4. MMLの最適化を行う
    • 全く音符がないチャンネルを削除

NSP SODAの技術

画面レイアウト

SODAでは、たしか320x180の解像度(4倍サンプリング)で作りましたが、640x360(2倍サンプリング)で作る方がオススメです(とても苦労しました)。
画面幅が変わっても表示時間が同じようにするために、幅640pxのとき、ノーツをたたく位置が88pxになるようにして、速度的な値を368から552にします。細かいことはSODAのソース見たほうが早いですが、SODAは幅320pxで作ってしまったので一部の数値を倍にする必要があり、見極めは難しいかもしれません。

その他の画面レイアウトはNaさんからもらった画面レイアウト案に忠実にやったので、同じようにするといいかもしれません(?)

セーブデータコピーツール

何も難しいことはなく、プロジェクトをまたいでセーブデータを見に行くだけのものです。
セーブデータファイルの先頭か末尾に、適当なラベルを置いてDATA$、TIME$(プチコン4ではどちらも関数)を順に書き出させて、比較しただけです。

文字列同士も不等号で比較ができます。およそ、辞書で引いたときに先に出てくる方が「小さい」という順序になります(大文字だけとか小文字だけの比較の場合)。

AOTの技術

プリセット原曲の使用

※名無しさんから意見を頂いての実装です
選曲画面でMMLを読み込んで変数MMLD$に入れたあと、BPLAY命令(下で書く)でユーザーMMLかプリセット原曲かを判断して再生する。
おもちをつく時のBGMPLAYではBRET関数を使っているが、その前のBGMSETのところでプリセット原曲だった場合に「READ H」であらかじめ番号を変数Hに入れているのがポイント(?)。直後にその関数DEF中で使用する。
(DEF中のREADよりも確実な気がした)

DEF2つ

BPLAY C$...MML文字列をC$に渡し、それが「#PS」ならその次の番号をREADで読み込んで再生する。「#PS」以外ならそのまま「BGMPLAY C$」。再生直後に音量を設定。
BRET(G$)...BGMSET後の再生番号の設定。MML文字列をG$に渡し、それが「#PS」ならあらかじめ読み込んで代入しておいた変数Hを返し、「#PS」でなければ128を返す。

Σの技術

リプレイ

配列REPの構造

添字内容
0~曲名の文字コード。10は終端。
難易度名の文字コード。10は終端。
nモード
n+1コラボスキン(未使用)
n+2テンポ
n+3そくど
n+4おんぷ
n+5でたらめ乱数シード値(1~16777215)
n+6がめん
n+7ひょうじ
n+8ふめん
n+9ドン音色
n+10カッ音色
n+11+m*3リプレイデータ(フレーム数)
n+12+m*3リプレイデータ(TB[0]~[22]の値を10進数化)
n+13+m*3リプレイデータ(TB[23]~[45]の値を10進数化)
  • でたらめ乱数シード値を保存することで、でたらめで保存したリプレイを再生しても、おもちの色が毎回同じになる。
  • 音色も保存しているため、リプレイ再生時は保存当時の音色が鳴る。

NewAIの技術

譜面生成

譜面の生成は、元々(おもちAG)で譜面を読み込んでいたタイミングで行っている。譜面生成機構は、ΣのExtraモードからパクって移植してきた。
密度はExtra16 Lv.3相当固定になるように改造している。

特殊音符の配置

特殊音符の配置は、一旦譜面を生成した後に行っている。
Peacefulではこの処理はしていない。
譜面データが入った文字列変数の内容を別の文字列変数にそのまま渡し、一旦空にしてから、1文字ずつチェックし、スペースならスペース1文字分を追加、赤青音符の場合は特殊音符を選ぶ処理に移行するというサイクルを繰り返す。
特殊音符の選定は、乱数により行っている。難易度により音符の種類を変える為、乱数の範囲は難易度により変わる。
また、ある程度規則性を持たせる為、元が赤だったものと青だったもので選定される音符の種類が変わるようになっている。

特殊音符配置の詳細

乱数範囲

  • Basic 0~2
  • Lunatic 0~3
  • Esoteric 0~5
    乱数値赤の置換先青の置換先
    0A
    1(L,E)B(B)赤(L,E)→(B)青
    2(L,E)X(B)赤(L,E)↓(B)青
    3Y
    4LR
    5
    B=Basic時 L=Lunatic時 E=Esoteric時

コメント


*1 対処法1:2023行目のFOR文を書き換える。というかFILL命令のほうが速い。
*2 対処法2:2612行目のFOR文の終わりをONPC-1にする。この場合初期化は不要になる
*3 実際のTJASと仕様が異なる。実際のTJASと同じにするには、同時にHSも変更しなければならない。
*4 NSPやその派生おもちつきに移植する場合は、計算してHSを掛ける必要がある。
*5 {(120/TMP)*30}/(ONPL/4)/60*1000
*6 {(1000/60)*(120*4)/TMP}*ONPLIMITは誤差埋め。
*7 1文字が何分音符を表すかを格納した変数
*8 それが-17より大きい場合は-17。CNTの絶対値が17未満の時にMMLを再生するため。