1
0
mirror of synced 2024-11-27 17:00:50 +01:00

fix: remove DTX parsing to prevent freezing due to DTX commands in TJA (#678)

Currently, the engine support DTX commands within TJA file,
where chart objects can be placed using DTX commands.

However, TJA charts with DTX commands will only be shown in the song list
when there are any TJA chart definition in the TJA,
which seems to cause weird behaviors when DTX chart definition is also present.

To prevent this issue, the core DTX parsing functionality is now removed.

* CDTX.CDTX():
    * param nReadVersion -> nReadVersionUnused as its all usages are 0
* CDTX.t入力() [tInput]:
    * remove unused overloads
    * param nReadVersion -> nReadVersionUnused
    * simplify codes by assuming nReadVersionUnused to be 0
    * remove now-irrelevant comments
* CDTX.t入力_全入力文字列から() [tInput_FromFullInputText]:
    * param str1 -> str1Unused
    * simplify codes by assuming str1 to be str全入力文字列 [strFullInputText]
    * remove pre-processing steps of str全入力文字列 which have never been applied to TJA parsing
    * remove codes for handling DTX commands
* remove now-unused DTX-parsing methods
    * CDTX.t入力_コマンド文字列を抜き出す() [tInput_ExtractCommandText]
    * CDTX.t入力_コメントをスキップする() [tInput_SkipComment]
    * CDTX.t入力_コメント文字列を抜き出す() [tInput_ExtractCommentText]
    * CDTX.t入力_パラメータ食い込みチェック() [tInput_CheckAndTrimParameter]
    * CDTX.t入力_パラメータ文字列を抜き出す() [tInput_ExtractParameterText]
    * CDTX.t入力_空白と改行をスキップする() [tInput_SkipSpacesAndNewlines]
    * CDTX.t入力_空白をスキップする() [tInput_SkipSpaces]
    * CDTX.t入力_行解析() [tInput_ParseLine]
    * CDTX.t入力_行解析_BPM_BPMzz() [tInput_ParseLine_BPM_BPMzz]
    * CDTX.t入力_行解析_SIZE() [tInput_ParseLine_SIZE]
    * CDTX.t入力_行解析_WAVPAN_PAN() [tInput_ParseLine_WAVPAN_PAN]
    * CDTX.t入力_行解析_チップ配置() [tInput_ParseLine_PlaceChips]
    * CDTX.TryParse() (parse float, with either `.` or `,` as the decimal point)
This commit is contained in:
Wei-Cheng Yeh (IID) 2024-09-25 11:37:10 +08:00 committed by GitHub
parent 558f57aff2
commit 0ff4869cb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1326,10 +1326,10 @@ namespace OpenTaiko {
this.t入力(strファイル名, bヘッダのみ, db再生速度, nBGMAdjust, nReadVersion, 0, false, difficulty);
}
*/
public CDTX(string strファイル名, bool bヘッダのみ, double db再生速度, int nBGMAdjust, int nReadVersion, int nPlayerSide, bool bSession, int difficulty)
public CDTX(string strファイル名, bool bヘッダのみ, double db再生速度, int nBGMAdjust, int nReadVersionUnused, int nPlayerSide, bool bSession, int difficulty)
: this() {
this.Activate();
this.t入力(strファイル名, bヘッダのみ, db再生速度, nBGMAdjust, nReadVersion, nPlayerSide, bSession, difficulty);
this.t入力(strファイル名, bヘッダのみ, db再生速度, nBGMAdjust, nReadVersionUnused, nPlayerSide, bSession, difficulty);
}
@ -1788,10 +1788,7 @@ namespace OpenTaiko {
}
#endregion
public void t入力(string strファイル名, bool bヘッダのみ, int difficulty) {
this.t入力(strファイル名, bヘッダのみ, 1.0, 0, 0, 0, false, difficulty);
}
public void t入力(string strファイル名, bool bヘッダのみ, double db再生速度, int nBGMAdjust, int nReadVersion, int nPlayerSide, bool bSession, int difficulty) {
public void t入力(string strファイル名, bool bヘッダのみ, double db再生速度, int nBGMAdjust, int nReadVersionUnused, int nPlayerSide, bool bSession, int difficulty) {
this.bヘッダのみ = bヘッダのみ;
this.strファイル名の絶対パス = Path.GetFullPath(strファイル名);
this.strファイル名 = Path.GetFileName(this.strファイル名の絶対パス);
@ -1805,27 +1802,6 @@ namespace OpenTaiko {
try {
this.nPlayerSide = nPlayerSide;
this.bSession譜面を読み込む = bSession;
if (nReadVersion != 0) {
//DTX方式
//DateTime timeBeginLoad = DateTime.Now;
//TimeSpan span;
string[] files = Directory.GetFiles(this.strフォルダ名, "*.tja");
StreamReader reader = new StreamReader(strファイル名, Encoding.GetEncoding(OpenTaiko.sEncType));
string str2 = reader.ReadToEnd();
reader.Close();
//StreamReader reader2 = new StreamReader( this.strフォルダ名 + "test.tja", Encoding.GetEncoding( "Shift_JIS" ) );
StreamReader reader2 = new StreamReader(files[0], Encoding.GetEncoding(OpenTaiko.sEncType));
string str3 = reader2.ReadToEnd();
reader2.Close();
//span = (TimeSpan) ( DateTime.Now - timeBeginLoad );
//Trace.TraceInformation( "DTXfileload時間: {0}", span.ToString() );
this.t入力_全入力文字列から(str2, str3, db再生速度, nBGMAdjust, difficulty);
} else {
//次郎方式
//DateTime timeBeginLoad = DateTime.Now;
@ -1835,17 +1811,10 @@ namespace OpenTaiko {
string str2 = reader.ReadToEnd();
reader.Close();
//StreamReader reader2 = new StreamReader( this.strフォルダ名 + "test.tja", Encoding.GetEncoding( "Shift_JIS" ) );
//StreamReader reader2 = new StreamReader( strファイル名, Encoding.GetEncoding( "Shift_JIS" ) );
//string str3 = reader2.ReadToEnd();
//reader2.Close();
string str3 = str2;
//span = (TimeSpan) ( DateTime.Now - timeBeginLoad );
//Trace.TraceInformation( "DTXfileload時間: {0}", span.ToString() );
this.t入力_全入力文字列から(str2, str3, db再生速度, nBGMAdjust, difficulty);
}
this.t入力_全入力文字列から(str2, str2, db再生速度, nBGMAdjust, difficulty);
} catch (Exception ex) {
//MessageBox.Show( "おや?エラーが出たようです。お兄様。" );
Trace.TraceError("おや?エラーが出たようです。お兄様。");
@ -1854,19 +1823,13 @@ namespace OpenTaiko {
}
}
}
public void t入力_全入力文字列から(string str全入力文字列, int difficulty) {
this.t入力_全入力文字列から(str全入力文字列, str全入力文字列, 1.0, 0, difficulty);
}
public void t入力_全入力文字列から(string str全入力文字列, string str1, double db再生速度, int nBGMAdjust, int Difficulty) {
public void t入力_全入力文字列から(string str全入力文字列, string str1Unused, double db再生速度, int nBGMAdjust, int Difficulty) {
//DateTime timeBeginLoad = DateTime.Now;
//TimeSpan span;
if (!string.IsNullOrEmpty(str全入力文字列)) {
#region [ ]
this.db再生速度 = db再生速度;
str全入力文字列 = str全入力文字列.Replace(Environment.NewLine, "\n");
str全入力文字列 = str全入力文字列.Replace('\t', ' ');
str全入力文字列 = str全入力文字列 + "\n";
#endregion
//span = (TimeSpan) ( DateTime.Now - timeBeginLoad );
//Trace.TraceInformation( "改行カット時間: {0}", span.ToString() );
@ -1897,39 +1860,7 @@ namespace OpenTaiko {
this.dbNowSCROLL_Master = new double[] { 1.0, 0.0 };
this.n現在のコース = ECourse.eNormal;
#endregion
CharEnumerator ce = str全入力文字列.GetEnumerator();
if (ce.MoveNext()) {
this.n現在の行数 = 1;
do {
if (!this.t入力_空白と改行をスキップする(ref ce)) {
break;
}
if (this.listChip.Count == 0) {
//this.t入力(str1);
//this.t入力_V3( str1, 3 );
this.t入力_V4(str1, Difficulty);
}
if (ce.Current == '#') {
if (ce.MoveNext()) {
StringBuilder builder = new StringBuilder(0x20);
if (this.t入力_コマンド文字列を抜き出す(ref ce, ref builder)) {
StringBuilder builder2 = new StringBuilder(0x400);
if (this.t入力_パラメータ文字列を抜き出す(ref ce, ref builder2)) {
StringBuilder builder3 = new StringBuilder(0x400);
if (this.t入力_コメント文字列を抜き出す(ref ce, ref builder3)) {
this.t入力_行解析(ref builder, ref builder2, ref builder3);
this.n現在の行数++;
continue;
}
}
}
}
break;
}
//this.t入力(str1);
}
while (this.t入力_コメントをスキップする(ref ce));
this.t入力_V4(str全入力文字列, Difficulty);
#endregion
//span = (TimeSpan) ( DateTime.Now - timeBeginLoad );
@ -1939,7 +1870,7 @@ namespace OpenTaiko {
this.n無限管理BPM = null;
this.n無限管理PAN = null;
this.n無限管理SIZE = null;
//this.t入力_行解析ヘッダ( str1 );
//this.t入力_行解析ヘッダ( str1Unused );
if (!this.bヘッダのみ) {
#region [ BPM/BMP初期化 ]
int ch;
@ -2556,7 +2487,6 @@ namespace OpenTaiko {
}
}
}
}
private string tコメントを削除する(string input) {
string strOutput = Regex.Replace(input, @" *//.*", ""); //2017.01.28 DD コメント前のスペースも削除するように修正
@ -7558,771 +7488,6 @@ namespace OpenTaiko {
strText = strText.Remove(nCommentPos);
}
private bool t入力_コマンド文字列を抜き出す(ref CharEnumerator ce, ref StringBuilder sb文字列) {
if (!this.t入力_空白をスキップする(ref ce))
return false; // 文字が尽きた
#region [ (':')(';')sb文字列 ]
//-----------------
while (ce.Current != ':' && ce.Current != ' ' && ce.Current != ';' && ce.Current != '\n') {
sb文字列.Append(ce.Current);
if (!ce.MoveNext())
return false; // 文字が尽きた
}
//-----------------
#endregion
#region [ (':')]
//-----------------
if (ce.Current == ':') {
if (!ce.MoveNext())
return false; // 文字が尽きた
if (!this.t入力_空白をスキップする(ref ce))
return false; // 文字が尽きた
}
//-----------------
#endregion
return true;
}
private bool t入力_コメントをスキップする(ref CharEnumerator ce) {
// 改行が現れるまでをコメントと見なしてスキップする。
while (ce.Current != '\n') {
if (!ce.MoveNext())
return false; // 文字が尽きた
}
// 改行の次の文字へ移動した結果を返す。
return ce.MoveNext();
}
private bool t入力_コメント文字列を抜き出す(ref CharEnumerator ce, ref StringBuilder sb文字列) {
if (ce.Current != ';') // コメント開始文字(';')じゃなければ正常帰還。
return true;
if (!ce.MoveNext()) // ';' の次で文字列が終わってたら終了帰還。
return false;
#region [ ';' '\n' sb文字列にコピーする]
//-----------------
while (ce.Current != '\n') {
sb文字列.Append(ce.Current);
if (!ce.MoveNext())
return false;
}
//-----------------
#endregion
return true;
}
private void t入力_パラメータ食い込みチェック(string strコマンド名, ref string strコマンド, ref string strパラメータ) {
if ((strコマンド.Length > strコマンド名.Length) && strコマンド.StartsWith(strコマンド名, StringComparison.OrdinalIgnoreCase)) {
strパラメータ = strコマンド.Substring(strコマンド名.Length).Trim();
strコマンド = strコマンド.Substring(0, strコマンド名.Length);
}
}
private bool t入力_パラメータ文字列を抜き出す(ref CharEnumerator ce, ref StringBuilder sb文字列) {
if (!this.t入力_空白をスキップする(ref ce))
return false; // 文字が尽きた
#region [ (';')sb文字列 ]
//-----------------
while (ce.Current != '\n' && ce.Current != ';') {
sb文字列.Append(ce.Current);
if (!ce.MoveNext())
return false;
}
//-----------------
#endregion
return true;
}
private bool t入力_空白と改行をスキップする(ref CharEnumerator ce) {
// 空白と改行が続く間はこれらをスキップする。
while (ce.Current == ' ' || ce.Current == '\n') {
if (ce.Current == '\n')
this.n現在の行数++; // 改行文字では行番号が増える。
if (!ce.MoveNext())
return false; // 文字が尽きた
}
return true;
}
private bool t入力_空白をスキップする(ref CharEnumerator ce) {
// 空白が続く間はこれをスキップする。
while (ce.Current == ' ') {
if (!ce.MoveNext())
return false; // 文字が尽きた
}
return true;
}
private void t入力_行解析(ref StringBuilder sbコマンド, ref StringBuilder sbパラメータ, ref StringBuilder sbコメント) {
string strコマンド = sbコマンド.ToString();
string strパラメータ = sbパラメータ.ToString().Trim();
string strコメント = sbコメント.ToString();
// 行頭コマンドの処理
#region [ IF ]
//-----------------
if (strコマンド.StartsWith("IF", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("IF", ref strコマンド, ref strパラメータ);
if (this.bstackIFからENDIFをスキップする.Count == 255) {
Trace.TraceWarning("#IF の入れ子の数が 255 を超えました。この #IF を無視します。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
} else if (this.bstackIFからENDIFをスキップする.Peek()) {
this.bstackIFからENDIFをスキップする.Push(true); // 親が true ならその入れ子も問答無用で true 。
} else // 親が false なら入れ子はパラメータと乱数を比較して結果を判断する。
{
int n数値 = 0;
if (!int.TryParse(strパラメータ, out n数値))
n数値 = 1;
this.bstackIFからENDIFをスキップする.Push(n数値 != this.n現在の乱数); // 乱数と数値が一致したら true 。
}
}
//-----------------
#endregion
#region [ ENDIF ]
//-----------------
else if (strコマンド.StartsWith("ENDIF", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("ENDIF", ref strコマンド, ref strパラメータ);
if (this.bstackIFからENDIFをスキップする.Count > 1) {
this.bstackIFからENDIFをスキップする.Pop(); // 入れ子を1つ脱出。
} else {
Trace.TraceWarning("#ENDIF に対応する #IF がありません。この #ENDIF を無視します。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
}
}
//-----------------
#endregion
else if (!this.bstackIFからENDIFをスキップする.Peek()) // IFENDIF をスキップするなら以下はすべて無視。
{
#region [ PATH_WAV ]
//-----------------
if (strコマンド.StartsWith("PATH_WAV", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("PATH_WAV", ref strコマンド, ref strパラメータ);
this.PATH_WAV = strパラメータ;
}
//-----------------
#endregion
#region [ TITLE ]
//-----------------
else if (strコマンド.StartsWith("TITLE", StringComparison.OrdinalIgnoreCase)) {
//this.t入力_パラメータ食い込みチェック( "TITLE", ref strコマンド, ref strパラメータ );
//this.TITLE = strパラメータ;
}
//-----------------
#endregion
#region [ ARTIST ]
//-----------------
else if (strコマンド.StartsWith("ARTIST", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("ARTIST", ref strコマンド, ref strパラメータ);
this.ARTIST = strパラメータ;
}
//-----------------
#endregion
#region [ COMMENT ]
//-----------------
else if (strコマンド.StartsWith("COMMENT", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("COMMENT", ref strコマンド, ref strパラメータ);
this.COMMENT = strパラメータ;
}
//-----------------
#endregion
#region [ GENRE ]
//-----------------
else if (strコマンド.StartsWith("GENRE", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("GENRE", ref strコマンド, ref strパラメータ);
this.GENRE = strパラメータ;
}
//-----------------
#endregion
#region [ MAKER ]
//-----------------
else if (strコマンド.StartsWith("MAKER", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("MAKER", ref strコマンド, ref strパラメータ);
this.MAKER = strパラメータ;
}
//-----------------
#endregion
#region [ SELECTBG ]
//-----------------
else if (strコマンド.StartsWith("SELECTBG", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("SELECTBG", ref strコマンド, ref strパラメータ);
this.SELECTBG = strパラメータ;
}
//-----------------
#endregion
#region [ HIDDENLEVEL ]
//-----------------
else if (strコマンド.StartsWith("HIDDENLEVEL", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("HIDDENLEVEL", ref strコマンド, ref strパラメータ);
this.HIDDENLEVEL = strパラメータ.ToLower().Equals("on");
}
//-----------------
#endregion
#region [ PREVIEW ]
//-----------------
else if (strコマンド.StartsWith("PREVIEW", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("PREVIEW", ref strコマンド, ref strパラメータ);
this.PREVIEW = strパラメータ;
}
//-----------------
#endregion
#region [ PREIMAGE ]
//-----------------
else if (strコマンド.StartsWith("PREIMAGE", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("PREIMAGE", ref strコマンド, ref strパラメータ);
this.PREIMAGE = strパラメータ;
}
//-----------------
#endregion
#region [ RANDOM ]
//-----------------
else if (strコマンド.StartsWith("RANDOM", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("RANDOM", ref strコマンド, ref strパラメータ);
int n数値 = 1;
if (!int.TryParse(strパラメータ, out n数値))
n数値 = 1;
this.n現在の乱数 = OpenTaiko.Random.Next(n数値) + 1; // 1数値 までの乱数を生成。
}
//-----------------
#endregion
#region [ BPM ]
//-----------------
else if (strコマンド.StartsWith("BPM", StringComparison.OrdinalIgnoreCase)) {
//this.t入力_行解析_BPM_BPMzz( strコマンド, strパラメータ, strコメント );
}
//-----------------
#endregion
#region [ DTXVPLAYSPEED ]
//-----------------
else if (strコマンド.StartsWith("DTXVPLAYSPEED", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("DTXVPLAYSPEED", ref strコマンド, ref strパラメータ);
double dtxvplayspeed = 0.0;
if (TryParse(strパラメータ, out dtxvplayspeed) && dtxvplayspeed > 0.0) {
this.dbDTXVPlaySpeed = dtxvplayspeed;
}
}
//-----------------
#endregion
else if (!this.bヘッダのみ) // ヘッダのみの解析の場合、以下は無視。
{
#region [ PANEL ]
//-----------------
if (strコマンド.StartsWith("PANEL", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("PANEL", ref strコマンド, ref strパラメータ);
int dummyResult; // #23885 2010.12.12 yyagi: not to confuse "#PANEL strings (panel)" and "#PANEL int (panpot of EL)"
if (!int.TryParse(strパラメータ, out dummyResult)) { // 数値じゃないならPANELとみなす
this.PANEL = strパラメータ; //
goto EOL; //
} // 数値ならPAN ELとみなす
}
//-----------------
#endregion
#region [ BASEBPM ]
//-----------------
else if (strコマンド.StartsWith("BASEBPM", StringComparison.OrdinalIgnoreCase)) {
this.t入力_パラメータ食い込みチェック("BASEBPM", ref strコマンド, ref strパラメータ);
double basebpm = 0.0;
//if( double.TryParse( str2, out num6 ) && ( num6 > 0.0 ) )
if (TryParse(strパラメータ, out basebpm) && basebpm > 0.0) // #23880 2010.12.30 yyagi: alternative TryParse to permit both '.' and ',' for decimal point
{ // #24204 2011.01.21 yyagi: Fix the condition correctly
this.BASEBPM = basebpm;
}
}
//-----------------
#endregion
// オブジェクト記述コマンドの処理。
else if (
!this.t入力_行解析_WAVPAN_PAN(strコマンド, strパラメータ, strコメント) &&
// !this.t入力_行解析_BPM_BPMzz( strコマンド, strパラメータ, strコメント ) && // bヘッダのみ==trueの場合でもチェックするよう変更
!this.t入力_行解析_SIZE(strコマンド, strパラメータ, strコメント)) {
this.t入力_行解析_チップ配置(strコマンド, strパラメータ, strコメント);
}
EOL:
Debug.Assert(true); // #23885 2010.12.12 yyagi: dummy line to exit parsing the line
// 2011.8.17 from: "int xx=0;" から変更。毎回警告が出るので。
}
//else
//{ // Duration測定のため、bヘッダのみ==trueでも、チップ配置は行う
// this.t入力_行解析_チップ配置( strコマンド, strパラメータ, strコメント );
//}
}
}
private bool t入力_行解析_BPM_BPMzz(string strコマンド, string strパラメータ, string strコメント) {
// (1) コマンドを処理。
#region [ "BPM" ]
//-----------------
if (!strコマンド.StartsWith("BPM", StringComparison.OrdinalIgnoreCase))
return false;
strコマンド = strコマンド.Substring(3); // strコマンド から先頭の"BPM"文字を除去。
//-----------------
#endregion
// (2) パラメータを処理。
int zz = 0;
#region [ BPM番号 zz ]
//-----------------
if (strコマンド.Length < 2) {
#region [ (A) "#BPM:" zz = 00 ]
//-----------------
zz = 0;
//-----------------
#endregion
} else {
#region [ (B) "#BPMzz:" zz = 00 ZZ ]
//-----------------
zz = CConversion.n36進数2桁の文字列を数値に変換して返す(strコマンド.Substring(0, 2));
if (zz < 0 || zz >= 36 * 36) {
Trace.TraceError("BPM番号に 00ZZ 以外の値または不正な文字列が指定されました。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
return false;
}
//-----------------
#endregion
}
//-----------------
#endregion
double dbBPM = 0.0;
#region [ BPM値を取得する]
//-----------------
//if( !double.TryParse( strパラメータ, out result ) )
if (!TryParse(strパラメータ, out dbBPM)) // #23880 2010.12.30 yyagi: alternative TryParse to permit both '.' and ',' for decimal point
return false;
if (dbBPM <= 0.0)
return false;
//-----------------
#endregion
if (zz == 0) // "#BPM00:" と "#BPM:" は等価。
this.BPM = dbBPM; // この曲の代表 BPM に格納する。
#region [ BPMリストに {, zz, dbBPM} ]
//-----------------
this.listBPM.Add(
this.n内部番号BPM1to,
new CBPM() {
n内部番号 = this.n内部番号BPM1to,
n表記上の番号 = zz,
dbBPM値 = dbBPM,
});
//-----------------
#endregion
#region [ BPM番号が zz BPM未設定のBPMチップがあれば]
//-----------------
if (this.n無限管理BPM[zz] == -zz) // 初期状態では n無限管理BPM[zz] = -zz である。この場合、#BPMzz がまだ出現していないことを意味する。
{
for (int i = 0; i < this.listChip.Count; i++) // これまでに出てきたチップのうち、該当するBPM値が未設定のBPMチップの値を変更する仕組み上、必ず後方参照となる
{
var chip = this.listChip[i];
if (chip.bBPMチップである && chip.n整数値_内部番号 == -zz) // #BPMzz 行より前の行に出現した #BPMzz では、整数値_内部番号は -zz に初期化されている。
chip.n整数値_内部番号 = this.n内部番号BPM1to;
}
}
this.n無限管理BPM[zz] = this.n内部番号BPM1to; // 次にこの BPM番号 zz を使うBPMチップが現れたら、このBPM値が格納されることになる。
this.n内部番号BPM1to++; // 内部番号は単純増加連番。
//-----------------
#endregion
return true;
}
private bool t入力_行解析_SIZE(string strコマンド, string strパラメータ, string strコメント) {
// (1) コマンドを処理。
#region [ "SIZE" 2]
//-----------------
if (!strコマンド.StartsWith("SIZE", StringComparison.OrdinalIgnoreCase))
return false;
strコマンド = strコマンド.Substring(4); // strコマンド から先頭の"SIZE"文字を除去。
if (strコマンド.Length < 2) // サイズ番号の指定がない場合は無効。
return false;
//-----------------
#endregion
#region [ nWAV番号362]
//-----------------
int nWAV番号 = CConversion.n36進数2桁の文字列を数値に変換して返す(strコマンド.Substring(0, 2));
if (nWAV番号 < 0 || nWAV番号 >= 36 * 36) {
Trace.TraceError("SIZEのWAV番号に 00ZZ 以外の値または不正な文字列が指定されました。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
return false;
}
//-----------------
#endregion
// (2) パラメータを処理。
#region [ nサイズ値 0100 ]
//-----------------
int nサイズ値;
if (!int.TryParse(strパラメータ, out nサイズ値))
return true; // int変換に失敗しても、この行自体の処理は終えたのでtrueを返す。
nサイズ値 = Math.Min(Math.Max(nサイズ値, 0), 100); // 0未満は0、100超えは100に強制変換。
//-----------------
#endregion
#region [ nWAV番号で示されるサイズ未設定のWAVチップがあれば]
//-----------------
if (this.n無限管理SIZE[nWAV番号] == -nWAV番号) // 初期状態では n無限管理SIZE[xx] = -xx である。この場合、#SIZExx がまだ出現していないことを意味する。
{
foreach (CWAV wav in this.listWAV.Values) // これまでに出てきたWAVチップのうち、該当するサイズが未設定のチップのサイズを変更する仕組み上、必ず後方参照となる
{
if (wav.nチップサイズ == -nWAV番号) // #SIZExx 行より前の行に出現した #WAVxx では、チップサイズは -xx に初期化されている。
wav.nチップサイズ = nサイズ値;
}
}
this.n無限管理SIZE[nWAV番号] = nサイズ値; // 次にこの nWAV番号を使うWAVチップが現れたら、負数の代わりに、このサイズ値が格納されることになる。
//-----------------
#endregion
return true;
}
private bool t入力_行解析_WAVPAN_PAN(string strコマンド, string strパラメータ, string strコメント) {
// (1) コマンドを処理。
#region [ "WAVPAN" or "PAN" ]
//-----------------
if (strコマンド.StartsWith("WAVPAN", StringComparison.OrdinalIgnoreCase))
strコマンド = strコマンド.Substring(6); // strコマンド から先頭の"WAVPAN"文字を除去。
else if (strコマンド.StartsWith("PAN", StringComparison.OrdinalIgnoreCase))
strコマンド = strコマンド.Substring(3); // strコマンド から先頭の"PAN"文字を除去。
else
return false;
//-----------------
#endregion
// (2) パラメータを処理。
if (strコマンド.Length < 2)
return false; // WAV番号 zz がないなら無効。
#region [ WAV番号 zz ]
//-----------------
int zz = CConversion.n36進数2桁の文字列を数値に変換して返す(strコマンド.Substring(0, 2));
if (zz < 0 || zz >= 36 * 36) {
Trace.TraceError("WAVPAN(PAN)のWAV番号に 00ZZ 以外の値または不正な文字列が指定されました。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
return false;
}
//-----------------
#endregion
#region [ WAV番号 zz WAVチップの位置を変更する]
//-----------------
int n位置;
if (int.TryParse(strパラメータ, out n位置)) {
n位置 = Math.Min(Math.Max(n位置, -100), 100); // -100+100 に丸める
if (this.n無限管理PAN[zz] == (-10000 - zz)) // 初期状態では n無限管理PAN[zz] = -10000 - zz である。この場合、#WAVPANzz, #PANzz がまだ出現していないことを意味する。
{
foreach (CWAV wav in this.listWAV.Values) // これまでに出てきたチップのうち、該当する位置が未設定のWAVチップの値を変更する仕組み上、必ず後方参照となる
{
if (wav.n位置 == (-10000 - zz)) // #WAVPANzz, #PANzz 行より前の行に出現した #WAVzz では、位置は -10000-zz に初期化されている。
wav.n位置 = n位置;
}
}
this.n無限管理PAN[zz] = n位置; // 次にこの WAV番号 zz を使うWAVチップが現れたら、この位置が格納されることになる。
}
//-----------------
#endregion
return true;
}
private bool t入力_行解析_チップ配置(string strコマンド, string strパラメータ, string strコメント) {
// (1) コマンドを処理。
if (strコマンド.Length != 5) // コマンドは必ず5文字であること。
return false;
#region [ n小節番号 ]
//-----------------
int n小節番号 = CConversion.n小節番号の文字列3桁を数値に変換して返す(strコマンド.Substring(0, 3));
if (n小節番号 < 0)
return false;
n小節番号++; // 先頭に空の1小節を設ける。
//-----------------
#endregion
#region [ nチャンネル番号 ]
//-----------------
int nチャンネル番号 = -1;
// ファイルフォーマットによって処理が異なる。
#region [ (B) 162]
//-----------------
nチャンネル番号 = CConversion.n16進数2桁の文字列を数値に変換して返す(strコマンド.Substring(3, 2));
if (nチャンネル番号 < 0)
return false;
//-----------------
#endregion
//-----------------
#endregion
#region [ this.bチップがある ]
//-----------------
if ((nチャンネル番号 >= 0x11) && (nチャンネル番号 <= 0x1a)) {
this.bチップがある.Drums = true;
} else if ((nチャンネル番号 >= 0x20) && (nチャンネル番号 <= 0x27)) {
this.bチップがある.Guitar = true;
} else if ((nチャンネル番号 >= 0xA0) && (nチャンネル番号 <= 0xa7)) {
this.bチップがある.Bass = true;
}
switch (nチャンネル番号) {
case 0x18:
this.bチップがある.HHOpen = true;
break;
case 0x19:
this.bチップがある.Ride = true;
break;
case 0x1a:
this.bチップがある.LeftCymbal = true;
break;
case 0x20:
this.bチップがある.OpenGuitar = true;
break;
case 0xA0:
this.bチップがある.OpenBass = true;
break;
}
//-----------------
#endregion
// (2) Ch.02を処理。
#region [ (Ch.02) ]
//-----------------
if (nチャンネル番号 == 0x02) {
// 小節長倍率を取得する。
double db小節長倍率 = 1.0;
//if( !double.TryParse( strパラメータ, out result ) )
if (!this.TryParse(strパラメータ, out db小節長倍率)) // #23880 2010.12.30 yyagi: alternative TryParse to permit both '.' and ',' for decimal point
{
Trace.TraceError("小節長倍率に不正な値を指定しました。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
return false;
}
// 小節長倍率チップを配置する。
this.listChip.Insert(
0,
new CChip() {
nチャンネル番号 = nチャンネル番号,
db実数値 = db小節長倍率,
n発声位置 = n小節番号 * 384,
});
return true; // 配置終了。
}
//-----------------
#endregion
// (3) パラメータを処理。
if (string.IsNullOrEmpty(strパラメータ)) // パラメータはnullまたは空文字列ではないこと。
return false;
#region [ strパラメータ n文字数 ]
//-----------------
int n文字数 = 0;
var sb = new StringBuilder(strパラメータ.Length);
// strパラメータを先頭から1文字ずつ見ながら正規化無効文字('_')を飛ばしたり不正な文字でエラーを出したりし、sb へ格納する。
CharEnumerator ce = strパラメータ.GetEnumerator();
while (ce.MoveNext()) {
if (ce.Current == '_') // '_' は無視。
continue;
if (CConversion.str36進数文字.IndexOf(ce.Current) < 0) // オブジェクト記述は36進数文字であること。
{
Trace.TraceError("不正なオブジェクト指定があります。[{0}: {1}行]", this.strファイル名の絶対パス, this.n現在の行数);
return false;
}
sb.Append(ce.Current);
n文字数++;
}
strパラメータ = sb.ToString(); // 正規化された文字列になりました。
if ((n文字数 % 2) != 0) // パラメータの文字数が奇数の場合、最後の1文字を無視する。
n文字数--;
//-----------------
#endregion
// (4) パラメータをオブジェクト数値に分解して配置する。
for (int i = 0; i < (n文字数 / 2); i++) // 2文字で1オブジェクト数値
{
#region [ nオブジェクト数値 '00' ]
//-----------------
int nオブジェクト数値 = 0;
if (nチャンネル番号 == 0x03) {
// Ch.03 のみ 16進数2桁。
nオブジェクト数値 = CConversion.n16進数2桁の文字列を数値に変換して返す(strパラメータ.Substring(i * 2, 2));
} else {
// その他のチャンネルは36進数2桁。
nオブジェクト数値 = CConversion.n36進数2桁の文字列を数値に変換して返す(strパラメータ.Substring(i * 2, 2));
}
if (nオブジェクト数値 == 0x00)
continue;
//-----------------
#endregion
// オブジェクト数値に対応するチップを生成。
var chip = new CChip();
chip.nチャンネル番号 = nチャンネル番号;
chip.n発声位置 = (n小節番号 * 384) + ((384 * i) / (n文字数 / 2));
chip.n整数値 = nオブジェクト数値;
chip.n整数値_内部番号 = nオブジェクト数値;
#region [ chip.e楽器パート = ... ]
//-----------------
if ((nチャンネル番号 >= 0x11) && (nチャンネル番号 <= 0x1C)) {
chip.e楽器パート = EInstrumentPad.DRUMS;
}
if ((nチャンネル番号 >= 0x20) && (nチャンネル番号 <= 0x27)) {
chip.e楽器パート = EInstrumentPad.GUITAR;
}
if ((nチャンネル番号 >= 160) && (nチャンネル番号 <= 0xA7)) {
chip.e楽器パート = EInstrumentPad.BASS;
}
//-----------------
#endregion
#region [ ]
//-----------------
if (chip.nチャンネル番号 == 0x01) {
chip.n整数値_内部番号 = this.n無限管理WAV[nオブジェクト数値]; // これが本当に一意なWAV番号となる。無限定義の場合、chip.n整数値 は一意である保証がない。)
} else if (chip.bBPMチップである) {
chip.n整数値_内部番号 = this.n無限管理BPM[nオブジェクト数値]; // これが本当に一意なBPM番号となる。同上。
}
//-----------------
#endregion
#region [ ON/OFFチャンネル(Ch.53)]
//-----------------
if (nチャンネル番号 == 0x53) {
// ずらすのは、フィルインONチップと同じ位置にいるチップでも確実にフィルインが発動し、
// 同様に、フィルインOFFチップと同じ位置にいるチップでも確実にフィルインが終了するようにするため。
if ((nオブジェクト数値 > 0) && (nオブジェクト数値 != 2)) {
chip.n発声位置 -= 32; // 384÷3212 ということで、フィルインONチップは12分音符ほど前へ移動。
} else if (nオブジェクト数値 == 2) {
chip.n発声位置 += 32; // 同じく、フィルインOFFチップは12分音符ほど後ろへ移動。
}
}
//-----------------
#endregion
// チップを配置。
this.listChip.Add(chip);
}
return true;
}
#region [#23880 2010.12.30 yyagi: TryParse]
/// <summary>
/// 小数点としてコンマとピリオドの両方を受け付けるTryParse()
/// </summary>
/// <param name="s">strings convert to double</param>
/// <param name="result">parsed double value</param>
/// <returns>s が正常に変換された場合は true。それ以外の場合は false。</returns>
/// <exception cref="ArgumentException">style が NumberStyles 値でないか、style に NumberStyles.AllowHexSpecifier 値が含まれている</exception>
private bool TryParse(string s, out double result) { // #23880 2010.12.30 yyagi: alternative TryParse to permit both '.' and ',' for decimal point
// EU諸国での #BPM 123,45 のような記述に対応するため、
// 小数点の最終位置を検出して、それをlocaleにあった
// 文字に置き換えてからTryParse()する
// 桁区切りの文字はスキップする
const string DecimalSeparators = ".,"; // 小数点文字
const string GroupSeparators = ".,' "; // 桁区切り文字
const string NumberSymbols = "0123456789"; // 数値文字
int len = s.Length; // 文字列長
int decimalPosition = len; // 真の小数点の位置 最初は文字列終端位置に仮置きする
for (int i = 0; i < len; i++) { // まず、真の小数点(一番最後に現れる小数点)の位置を求める
char c = s[i];
if (NumberSymbols.IndexOf(c) >= 0) { // 数値だったらスキップ
continue;
} else if (DecimalSeparators.IndexOf(c) >= 0) { // 小数点文字だったら、その都度位置を上書き記憶
decimalPosition = i;
} else if (GroupSeparators.IndexOf(c) >= 0) { // 桁区切り文字の場合もスキップ
continue;
} else { // 数値_小数点_区切り文字以外がきたらループ終了
break;
}
}
StringBuilder decimalStr = new StringBuilder(16);
for (int i = 0; i < len; i++) { // 次に、localeにあった数値文字列を生成する
char c = s[i];
if (NumberSymbols.IndexOf(c) >= 0) { // 数値だったら
decimalStr.Append(c); // そのままコピー
} else if (DecimalSeparators.IndexOf(c) >= 0) { // 小数点文字だったら
if (i == decimalPosition) { // 最後に出現した小数点文字なら、localeに合った小数点を出力する
decimalStr.Append(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}
} else if (GroupSeparators.IndexOf(c) >= 0) { // 桁区切り文字だったら
continue; // 何もしない(スキップ)
} else {
break;
}
}
return double.TryParse(decimalStr.ToString(), out result); // 最後に、自分のlocale向けの文字列に対してTryParse実行
}
#endregion
/// <summary>
/// 音源再生前の空白を追加するメソッド。
/// </summary>