// 初期化。 public static void 状態をリセットする() { スコア = null; データ種別 = データ種別.DTX; Random = new Random((int)(Stopwatch.GetTimestamp() % int.MaxValue)); 行番号 = 0; コマンド = ""; コマンドzzなし = ""; zz16進数 = -1; zz36進数 = -1; パラメータ = ""; コメント = ""; 小節番号 = 0; チャンネル番号 = 0; チップ種別 = チップ種別.Unknown; 小節解像度 = 384; // DTX の小節解像度は 384 固定 オブジェクト総数 = 0; オブジェクト番号 = 0; PAN定義マップ = new Dictionary <int, int>(); VOLUME定義マップ = new Dictionary <int, int>(); BGMWAVリスト = new List <int>(); BASEBPM = 0.0; BPM定義マップ = new Dictionary <int, double>(); BPM参照マップ = new Dictionary <チップ, int>(); 小節長倍率マップ = new SortedDictionary <int, double>(); }
/// <summary> /// ファイルから指定された種別のデータを読み込み、スコアを生成して返す。 /// 読み込みに失敗した場合は、何らかの例外を発出する。 /// </summary> public static スコア ファイルから生成する(string ファイルの絶対パス, データ種別 データ種別 = データ種別.拡張子から判定, bool ヘッダだけ = false) { if (データ種別 == データ種別.拡張子から判定) { #region " ファイルパスの拡張子からデータ種別を自動判別する。" //---------------- var 拡張子toデータ種別マップ = new Dictionary <string, データ種別>() { { ".dtx", データ種別.DTX }, { ".gda", データ種別.GDA }, { ".g2d", データ種別.G2D }, { ".bms", データ種別.BMS }, { ".bme", データ種別.BME }, }; string ファイルの拡張子 = Path.GetExtension(ファイルの絶対パス).ToLower(); // マップから、拡張子に対応するデータ種別を取得する。 if (!(拡張子toデータ種別マップ.TryGetValue(ファイルの拡張子, out データ種別 確定したデータ種別))) { // マップにない拡張子はすべて DTX とみなす。 確定したデータ種別 = データ種別.DTX; } データ種別 = 確定したデータ種別; //---------------- #endregion } string 全入力文字列 = null; // ファイルの内容を一気読み。 using (var sr = new StreamReader(ファイルの絶対パス, Encoding.GetEncoding(932 /*Shift-JIS*/))) // DTX互換ファイルは Shift-JIS 全入力文字列 = sr.ReadToEnd(); // 読み込んだ内容でスコアを生成する。 var score = 文字列から生成する(全入力文字列, データ種別, ヘッダだけ); // ファイルから読み込んだ場合のみ、このメンバが有効。 score.譜面ファイルの絶対パス = ファイルの絶対パス; return(score); }
/// <summary> /// 指定されたデータ種別のテキストデータを含んだ1つの文字列から、スコアを生成して返す。 /// 読み込みに失敗した場合は、何らかの例外を発出する。 /// </summary> public static スコア 文字列から生成する(string 全入力文字列, データ種別 データ種別 = データ種別.DTX, bool ヘッダだけ = false) { if (データ種別 == データ種別.拡張子から判定) { throw new Exception("文字列から生成する場合には、拡張子からの自動判定は行えません。"); } 現在の.状態をリセットする(); 現在の.スコア = new スコア(); 現在の.スコア.譜面ファイルの絶対パス = null; // ファイルから読み込んだ場合のみ、このメンバが有効。 現在の.データ種別 = データ種別; 全入力文字列 = 全入力文字列.Replace('\t', ' '); // TAB は空白に置換しておく。 // 読み込み using (var sr = new StringReader(全入力文字列)) { #region " すべての行について解析する。" //---------------- string 行; for (現在の.行番号 = 1; (行 = sr.ReadLine()) != null; 現在の.行番号++) { // 行を分割する。 if (!_行をコマンドとパラメータとコメントに分解する(行, out 現在の.コマンド, out 現在の.コマンドzzなし, out 現在の.zz16進数, out 現在の.zz36進数, out 現在の.パラメータ, out 現在の.コメント)) { //Trace.TraceWarning( $"書式が不正です。無視します。[{現在の.行番号}行]" ); continue; } if (string.IsNullOrEmpty(現在の.コマンド)) { continue; } // コマンド別に解析する。 // zzを含めたコマンドがzzを含まないコマンドと一致する場合は、前者のほうが優先。 if (_コマンドtoアクションマップ.TryGetValue(現在の.コマンド.ToLower(), out var アクション) || _コマンドtoアクションマップ.TryGetValue(現在の.コマンドzzなし.ToLower(), out アクション)) { if (!ヘッダだけ || アクション.ヘッダである) { アクション.解析アクション(); } } else { // マップにあるコマンドに該当がなければ、オブジェクト配置として解析する。 if (!ヘッダだけ) { _コマンド_オブジェクト記述(); } } } //---------------- #endregion } // 後処理 if (!ヘッダだけ) { #region " チップリストの先頭に #BPM チップを追加する。" //---------------- { if (!(現在の.BPM定義マップ.TryGetValue(0, out double BPM値))) { BPM値 = スコア.初期BPM; // '#BPM:' が定義されてないなら初期BPMを使う } 現在の.スコア.チップリスト.Add( new チップ() { BPM = BPM値, チップ種別 = チップ種別.BPM, 小節番号 = 0, 小節解像度 = 現在の.小節解像度, 小節内位置 = 0, 音量 = チップ.最大音量, 可視 = false, }); } //---------------- #endregion #region " 拍線を追加する。" //----------------- { // 小節線を先に追加すると小節が1つ増えてしまうので、拍線から先に追加する。 int 最大小節番号 = 現在の.スコア.最大小節番号を返す(); // 最大小節番号はチップ数に依存して変化するので、次の for 文には組み込まないこと。 for (int i = 0; i <= 最大小節番号; i++) { double 小節長倍率 = 現在の.スコア.小節長倍率を取得する(i); for (int n = 1; n * 0.25 < 小節長倍率; n++) { 現在の.スコア.チップリスト.Add( new チップ() { 小節番号 = i, チップ種別 = チップ種別.拍線, 小節内位置 = (int)((n * 0.25) * 100), 小節解像度 = (int)(小節長倍率 * 100), }); } } } //----------------- #endregion #region " 小節線を追加する。" //----------------- { int 最大小節番号 = 現在の.スコア.最大小節番号を返す(); for (int i = 0; i <= 最大小節番号 + 1; i++) { 現在の.スコア.チップリスト.Add( new チップ() { 小節番号 = i, チップ種別 = チップ種別.小節線, 小節内位置 = 0, 小節解像度 = 1, }); } } //----------------- #endregion #region " 小節長倍率マップをもとに、スコアの小節長倍率リストを構築する。" //---------------- { double 現在の倍率 = 1.0; int 最大小節番号 = 現在の.スコア.最大小節番号を返す(); for (int i = 0; i <= 最大小節番号; i++) // すべての小節に対して設定。(SST仕様) { if (現在の.小節長倍率マップ.ContainsKey(i)) { 現在の倍率 = 現在の.小節長倍率マップ[i]; // 指定された倍率は、それが指定された小節以降の小節にも適用する。(DTX仕様) } 現在の.スコア.小節長倍率を設定する(i, 現在の倍率); } } //---------------- #endregion #region " BPMチップの値を引き当てる。" //---------------- foreach (var kvp in 現在の.BPM参照マップ) { // BPMチャンネルから作成された BPMチップには、BASEBPM を加算する。 // #BASEBPM が複数宣言されていた場合は、最後の値が使用される。 if (現在の.BPM定義マップ.TryGetValue(kvp.Value, out double bpm)) { // (A) 参照している #BGMzz が定義されている場合 kvp.Key.BPM = 現在の.BASEBPM + bpm; } else { Trace.TraceWarning($"定義されていない #BPM が参照されています。"); if (0.0 < 現在の.BASEBPM) { // (B) 参照している #BPMzz が未宣言、かつ、BASEBPM が有効である場合 kvp.Key.BPM = 現在の.BASEBPM; // BASEBPM のみ適用 } else { // (C) 参照している #BPMzz が未宣言、かつ、BASEBPM が無効である場合 kvp.Key.チップ種別 = チップ種別.Unknown; // このチップは無効 } } } // 引き当てが終わったら、マップが持つチップへの参照を解放する。 現在の.BPM参照マップ.Clear(); //---------------- #endregion #region " WAVチップのPAN値, VOLUME値を引き当てる。" //---------------- foreach (var chip in 現在の.スコア.チップリスト) { // このクラスで実装しているチャンネルで、 var query = _DTXチャンネルプロパティマップ.Where((kvp) => (kvp.Value.チップ種別 == chip.チップ種別)); if (1 == query.Count()) { var kvp = query.Single(); // タプル型なので SingleOrDefault() は使えない。 // WAV を使うチャンネルで、 if (kvp.Value.WAVを使う) { // PAN の指定があるなら、 if (現在の.PAN定義マップ.ContainsKey(chip.チップサブID)) { // それをチップに設定する。 chip.左右位置 = 現在の.PAN定義マップ[chip.チップサブID]; } // VOLUME の指定があるなら、 if (現在の.VOLUME定義マップ.ContainsKey(chip.チップサブID)) { // それをチップに設定する。 var DTX音量 = Math.Min(Math.Max(現在の.VOLUME定義マップ[chip.チップサブID], 0), 100); // 無音:0 ~ 100:原音 chip.音量 = (100 == DTX音量) ? チップ.最大音量 : (int)(DTX音量 * チップ.最大音量 / 100.0) + 1; } } } } //---------------- #endregion #region " BGMWAV を WAVリストに反映する。" //---------------- foreach (int WAV番号 in 現在の.BGMWAVリスト) { if (現在の.スコア.WAVリスト.ContainsKey(WAV番号)) { 現在の.スコア.WAVリスト[WAV番号].BGMである = true; // BGMである } } //---------------- #endregion スコア._スコア読み込み時の後処理を行う(現在の.スコア); } return(現在の.スコア); }