<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.ServiceModel" />
+ <Reference Include="System.ServiceModel.Channels" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Project>{f6bd5fad-8cbd-4df4-89bf-bbdbe6c5c6fe}</Project>
<Name>SSTFormat</Name>
</ProjectReference>
+ <ProjectReference Include="..\StrokeStyleT\StrokeStyleT.csproj">
+ <Project>{ed72fd06-c276-42dc-9301-83de699bdf4c}</Project>
+ <Name>StrokeStyleT</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
using System.IO.Pipes;
using System.Linq;
using System.Reflection;
+using System.ServiceModel;
+using System.ServiceModel.Channels;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using FDK; // for string拡張
protected int 現在のガイド間隔 = 0;
protected Point 選択モードのコンテクストメニューを開いたときのマウスの位置;
+ protected ChannelFactory<SST.IStrokeStyleTService> SSTファクトリ = null;
+ protected SST.IStrokeStyleTService SSTサービス = null;
+
private bool bs_未保存である = false;
private SSTFormat.チップ種別 bs_e現在のチップ種別;
private int bs_GRID_PER_PIXEL = 1;
// 最近使ったファイル一覧を更新する。
this.ConfigのRecentUsedFilesをファイルメニューへ追加する();
-
+
// その他の初期化。
this.Act新規作成する();
this.Actガイド間隔を変更する( 16 ); // 初期は 1/16 間隔。
}
protected void Actアプリを終了する()
{
+ // SSTファクトリを閉じる。
+ this.SSTサービス = null; // サービスは解放処理なし
+ try
+ {
+ this.SSTファクトリ.Close();
+ }
+ catch( CommunicationException )
+ {
+ // すでに通信が閉じられている(SSTサービスホストが終了している)。
+ }
+
// 一時ファイルが残っていれば、削除する。
if( File.Exists( this.最後にプレイヤーに渡した一時ファイル名 ) )
File.Delete( this.最後にプレイヤーに渡した一時ファイル名 );
// Config.xml を保存する。
this.Config.保存する( Path.Combine( this.ユーザフォルダパス, Properties.Resources.CONFIG_FILE_NAME ) );
-
+
FDK.Utilities.解放する( ref this.譜面 );
FDK.Utilities.解放する( ref this.選択モード );
}
this.最後にプレイヤーに渡した一時ファイル名,
一時ファイルである: true ); // 一時ファイルなので、「最近使ったファイル一覧」には残さない。
- using( var stream = new NamedPipeServerStream( "SSTFEditor Viewer Device Information" ) )
+ // SSTサービスを取得する。
+ this.SSTサービスが起動していれば取得する();
+
+ // SSTサービスが起動していない場合は、Viewer プロセスを起動する。
+ if( null == this.SSTサービス )
{
- // ビュアーを起動する。
- string 仮想ドラムオプション = ( 仮想ドラムを使う ) ? @" -d" : @"";
try
{
- Process.Start(
- this.Config.ViewerPath,
- $"\"{this.最後にプレイヤーに渡した一時ファイル名}\" -p {小節番号.ToString()}{仮想ドラムオプション}" );
+ Process.Start( this.Config.ViewerPath );
}
catch
{
- return; // 起動に失敗。
+ return; // 起動に失敗。
}
- // デバイス情報を得る。
- stream.WaitForConnection();
-
- using( var reader = new StreamReader( stream ) )
+ // Viewer の提供するSSTサービスへ接続する。
+ for( int retry = 0; retry < 10; retry++ ) // 最大10回リトライ。
{
- var msg = reader.ReadLine();
+ this.SSTサービスが起動していれば取得する();
- var match = Regex.Match( msg, @"^SoundDevice.Delay=(\d+(\.\d+)?)$" ); // 負数やべき乗には非対応。
- if( match.Success )
- {
- var strValue = match.Groups[ 1 ].Value;
- float value = 0f;
- if( float.TryParse( strValue, out value ) ) // 正しく float に変換できる文字列なら、
- {
- this.textBoxサウンド遅延ms.Text = strValue; // GUI と、
- this.譜面.SSTFormatScore.Header.サウンドデバイス遅延ms = value; // 譜面にセットする。
+ if( null != this.SSTサービス )
+ break; // サービスに接続できた。
- this.未保存である = true;
- }
- }
+ // 少し待ってリトライ。
+ System.Threading.Thread.Sleep( 500 );
+ }
+ if( null == this.SSTサービス )
+ {
+ this.Viewer再生関連GUIのEnabledを設定する();
+ return; // サービスへの接続に失敗した。
}
}
+
+ // サウンドデバイス遅延を取得する。
+ float 遅延ms = this.SSTサービス.GetSoundDelay();
+ if( this.譜面.SSTFormatScore.Header.サウンドデバイス遅延ms != 遅延ms )
+ {
+ this.textBoxサウンド遅延ms.Text = 遅延ms.ToString(); // GUI と
+ this.譜面.SSTFormatScore.Header.サウンドデバイス遅延ms = 遅延ms; // 譜面にセットする。
+ this.未保存である = true;
+ }
+
+ // 演奏開始を指示する。
+ this.SSTサービス.ViewerPlay(
+ path: this.最後にプレイヤーに渡した一時ファイル名,
+ startPart: 小節番号,
+ drumsSound: 仮想ドラムを使う );
}
protected void Act再生を停止する()
{
}
protected void Viewer再生関連GUIのEnabledを設定する()
{
- if( File.Exists( this.Config.ViewerPath ) )
- {
- // (A) Viewer が存在するなら、各GUIは有効。
-
- this.toolStripButton先頭から再生.Enabled = true;
- this.toolStripButton現在位置から再生.Enabled = true;
- this.toolStripButton現在位置からBGMのみ再生.Enabled = true;
- this.toolStripButton再生停止.Enabled = true;
+ bool 各GUIは有効 = false;
- this.toolStripMenuItem先頭から再生.Enabled = true;
- this.toolStripMenuItem現在位置から再生.Enabled = true;
- this.toolStripMenuItem現在位置からBGMのみ再生.Enabled = true;
- this.toolStripMenuItem再生停止.Enabled = true;
+ if( null != this.SSTサービス )
+ {
+ // (A) SSTサービスが起動しているなら、各GUIは有効。
+ 各GUIは有効 = true;
+ }
+ else if( File.Exists( this.Config.ViewerPath ) )
+ {
+ // (B) SSTサービスが起動していなくても、Viewer が存在するなら、各GUIは有効。
+ 各GUIは有効 = true;
}
else
{
- // (B) Viewer が存在しないなら、各GUIは無効。
+ // (C) SSTサービスが起動しておらず、Viewer も存在しないなら、各GUIは無効。
+ 各GUIは有効 = false;
+ }
- this.toolStripButton先頭から再生.Enabled = false;
- this.toolStripButton現在位置から再生.Enabled = false;
- this.toolStripButton現在位置からBGMのみ再生.Enabled = false;
- this.toolStripButton再生停止.Enabled = false;
+ this.toolStripButton先頭から再生.Enabled = 各GUIは有効;
+ this.toolStripButton現在位置から再生.Enabled = 各GUIは有効;
+ this.toolStripButton現在位置からBGMのみ再生.Enabled = 各GUIは有効;
+ this.toolStripButton再生停止.Enabled = 各GUIは有効;
- this.toolStripMenuItem先頭から再生.Enabled = false;
- this.toolStripMenuItem現在位置から再生.Enabled = false;
- this.toolStripMenuItem現在位置からBGMのみ再生.Enabled = false;
- this.toolStripMenuItem再生停止.Enabled = false;
- }
+ this.toolStripMenuItem先頭から再生.Enabled = 各GUIは有効;
+ this.toolStripMenuItem現在位置から再生.Enabled = 各GUIは有効;
+ this.toolStripMenuItem現在位置からBGMのみ再生.Enabled = 各GUIは有効;
+ this.toolStripMenuItem再生停止.Enabled = 各GUIは有効;
}
protected void ConfigのRecentUsedFilesをファイルメニューへ追加する()
{
{
this.toolStripLabel音量.Text = ( this.dic音量ラベル.ContainsKey( this.現在のチップ音量 ) ) ? this.dic音量ラベル[ this.現在のチップ音量 ] : @"???";
}
+ protected void SSTサービスが起動していれば取得する()
+ {
+ // ファクトリが未生成なら生成する。
+ if( null == this.SSTファクトリ )
+ this.SSTファクトリ = new ChannelFactory<SST.IStrokeStyleTService>( new NetNamedPipeBinding( NetNamedPipeSecurityMode.None ) );
+
+ // サービスが未取得なら取得する。
+ if( null == this.SSTサービス )
+ {
+ try
+ {
+ this.SSTサービス = this.SSTファクトリ.CreateChannel( new EndpointAddress( "net.pipe://localhost/StrokeStyleT/Viewer" ) );
+ }
+ catch( EndpointNotFoundException )
+ {
+ this.SSTサービス = null; // 取得失敗。
+ }
+ }
+ }
// GUIイベントメソッド
{ SSTFormat.チップ種別.小節線, 編集レーン種別.Unknown },
{ SSTFormat.チップ種別.拍線, 編集レーン種別.Unknown },
{ SSTFormat.チップ種別.小節メモ, 編集レーン種別.Unknown },
+ { SSTFormat.チップ種別.小節の先頭, 編集レーン種別.Unknown },
{ SSTFormat.チップ種別.Unknown, 編集レーン種別.Unknown },
};
//-----------------
if( cc.チップ種別 == チップ種別.小節線 ||
cc.チップ種別 == チップ種別.拍線 ||
cc.チップ種別 == チップ種別.小節メモ ||
+ cc.チップ種別 == チップ種別.小節の先頭 ||
cc.チップ種別 == チップ種別.Unknown )
{
continue; // これらは出力しないので無視。
{
var lane = (レーン種別) laneObj;
- if( 0 < dicレーン別チップリスト[ lane ].Count )
+ if( 0 < dicレーン別チップリスト[ lane ].Count )
{
sw.Write( $"Lane={lane.ToString()}; " );
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
if( StrokeStyleT.ビュアーモードではない )
return;
- // todo: 演奏を開始する。
+ this.ビュアーメッセージキュー.Enqueue( new ViewerMessage() {
+ 種別 = ViewerMessageType.演奏開始,
+ 曲ファイルパス = path,
+ 演奏開始小節番号 = startPart,
+ ドラムチップ発声 = drumsSound,
+ } );
}
public void ViewerStop()
{
if( StrokeStyleT.ビュアーモードではない )
return;
- // todo: 演奏を停止する。
+ this.ビュアーメッセージキュー.Enqueue( new ViewerMessage() {
+ 種別 = ViewerMessageType.演奏停止,
+ } );
}
public float GetSoundDelay()
{
if( StrokeStyleT.ビュアーモードではない )
return 0f;
- // todo: 発声遅延[ms]を返す。
- return 0f;
+ return ( null != StrokeStyleT.Wasapiデバイス ) ? StrokeStyleT.Wasapiデバイス.遅延ms : 0f;
}
//----------------
#endregion
protected ApplicationState State;
protected SharpDX.Windows.RenderForm MainForm = null;
protected SharpDX.Size2F 設計画面サイズdpx = SharpDX.Size2F.Empty;
+ protected ConcurrentQueue<ViewerMessage> ビュアーメッセージキュー = new ConcurrentQueue<ViewerMessage>();
protected FDK.メディア.デバイスリソース デバイスリソース = null;
protected SST.ステージ.ステージ 最初のダミーステージ = null;
protected SST.ステージ.起動.起動ステージ 起動ステージ = null;
SharpDX.DXGI.PresentFlags.None );
//----------------
#endregion
+
#region " ステージの状態をチェックし、必要あれば遷移またはアプリを終了する。"
//----------------
if( null != this.現在のステージ )
//----------------
if( StrokeStyleT.ビュアーモードである )
{
+ // ビュアー用ユーザでログインする。
FDK.Log.Info( "ビュアーモード: AutoPlayer ユーザでログインします。" );
this.ログインする( Properties.Resources.AUTOPLAYER );
+ // 曲読込ステージ向けの初期設定。
StrokeStyleT.曲ツリー管理.現在の管理対象ツリー = null;
StrokeStyleT.曲ツリー管理.現在選択されているノード = null;
+
+ // 演奏ステージ向けの初期設定。
+ StrokeStyleT.演奏スコア = null;
+ // 演奏ステージへ。
this.演奏ステージ.活性化する( this.デバイスリソース );
this.現在のステージ = this.演奏ステージ;
}
break;
case nameof( ステージ.演奏.演奏ステージ ):
- #region " 演奏終了 → 結果ステージへ。"
- //--------------------
- if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.クリアor失敗 )
+ if( StrokeStyleT.ビュアーモードである )
{
- this.現在のステージ.非活性化する( this.デバイスリソース );
- this.現在のステージ = this.結果ステージ;
- this.現在のステージ.活性化する( this.デバイスリソース );
+ // (A) ビュアーモード
+
+ #region " ビュアーメッセージがあれば処理する。"
+ //----------------
+ var msg = this.ビュアーメッセージを取得する();
+
+ if( null != msg )
+ {
+ #region " (A) 演奏開始 "
+ //----------------
+ if( msg.種別 == ViewerMessageType.演奏開始 && (
+ this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.ビュアーメッセージ待機中 ||
+ this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.演奏中 )
+ )
+ {
+ // MusicNode を作成する。
+ var node = (SST.曲.MusicNode) null;
+ try
+ {
+ node = new 曲.MusicNode( msg.曲ファイルパス );
+ }
+ catch
+ {
+ FDK.Log.ERROR( $"MusicNode の作成に失敗しました。" );
+ node = null;
+ }
+
+ // 作成に成功したときのみ次へ進む。
+ if( null != node )
+ {
+ // 演奏を停止する。
+ this.演奏ステージ.非活性化する( this.デバイスリソース );
+ this.演奏ステージ.BGMを解放する();
+
+ // 現在選択されているノードとして登録する。
+ StrokeStyleT.曲ツリー管理.現在選択されているノード = node;
+
+ // todo: 演奏開始小節番号を保存し、反映する。
+
+ // 曲読込ステージへ。
+ this.現在のステージ = this.曲読込ステージ;
+ this.曲読込ステージ.活性化する( this.デバイスリソース );
+ }
+ }
+ //----------------
+ #endregion
+ #region " (B) 演奏停止 "
+ //----------------
+ else if( msg.種別 == ViewerMessageType.演奏停止 && (
+ this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.ビュアーメッセージ待機中 ||
+ this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.演奏中 )
+ )
+ {
+ this.演奏ステージ.演奏を停止する();
+ this.演奏ステージ.現在のフェーズ.Value = ステージ.演奏.演奏ステージ.フェーズ.ビュアーメッセージ待機中;
+ }
+ //----------------
+ #endregion
+ #region " その他 "
+ //----------------
+ else
+ {
+ // その他のメッセージは無視。
+ }
+ //----------------
+ #endregion
+ }
+ //----------------
+ #endregion
+ #region " クリア/キャンセル → メッセージ待機へ。"
+ //----------------
+ if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.クリアor失敗 ||
+ this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.キャンセル )
+ {
+ // フェーズのみ変更。BGM は止めない。
+ this.演奏ステージ.現在のフェーズ.Value = ステージ.演奏.演奏ステージ.フェーズ.ビュアーメッセージ待機中;
+ }
+ //----------------
+ #endregion
}
- //--------------------
- #endregion
- #region " キャンセル → 選曲ステージへ。"
- //--------------------
- if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.キャンセル )
+ else
{
- this.現在のステージ.非活性化する( this.デバイスリソース );
- this.現在のステージ = this.選曲ステージ;
- this.現在のステージ.活性化する( this.デバイスリソース );
+ // (B) 通常モード
+
+ #region " 演奏終了 → 結果ステージへ。"
+ //--------------------
+ if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.クリアor失敗 )
+ {
+ this.現在のステージ.非活性化する( this.デバイスリソース );
+ this.現在のステージ = this.結果ステージ;
+ this.現在のステージ.活性化する( this.デバイスリソース );
+ }
+ //--------------------
+ #endregion
+ #region " キャンセル → 選曲ステージへ。"
+ //--------------------
+ if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.キャンセル )
+ {
+ this.現在のステージ.非活性化する( this.デバイスリソース );
+ this.現在のステージ = this.選曲ステージ;
+ this.現在のステージ.活性化する( this.デバイスリソース );
+ }
+ //--------------------
+ #endregion
}
- //--------------------
- #endregion
break;
case nameof( ステージ.結果.結果ステージ ):
StrokeStyleT.ユーザ管理.ユーザを選択する( ユーザ名 );
FDK.Log.Info( $"ユーザが選択されました。[{StrokeStyleT.ユーザ管理.現在選択されているユーザ.名前}]" );
}
+ /// <returns>メッセージがない場合は null を返す。</returns>
+ private ViewerMessage ビュアーメッセージを取得する()
+ {
+ var msg = (ViewerMessage) null;
+
+ if( StrokeStyleT.ビュアーモードである &&
+ ( 0 < this.ビュアーメッセージキュー.Count ) &&
+ this.ビュアーメッセージキュー.TryDequeue( out msg ) )
+ {
+ return msg;
+ }
+
+ return null;
+ }
#region " バックストア。"
//----------------
<ItemGroup>
<Compile Include="IStrokeStyleTService.cs" />
<Compile Include="Win32.cs" />
+ <Compile Include="ViewerMessage.cs" />
<Compile Include="曲\タイトルテクスチャ.cs" />
<Compile Include="ステージ\選曲\曲パネルビュー.cs" />
<Compile Include="設定\Config.cs" />
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace SST
+{
+ enum ViewerMessageType
+ {
+ 指示なし,
+ 演奏開始,
+ 演奏停止,
+ }
+
+ class ViewerMessage
+ {
+ public ViewerMessageType 種別 { get; set; } = ViewerMessageType.指示なし;
+ public string 曲ファイルパス { get; set; } = null;
+ public int 演奏開始小節番号 { get; set; } = 0;
+ public bool ドラムチップ発声 { get; set; } = true;
+ }
+}
{
this.画面の高さdpx = dr.設計画面サイズdpx.Height;
}
+ public void 演奏を停止する()
+ {
+ this.描画開始チップ番号 = -1;
+ }
public void 小節線拍線を進行描画する( デバイスリソース dr, double 現在の演奏時刻sec )
{
this.画面内に収まるチップをすべて進行描画する(
this.ドラムサウンド.発声する( chip.チップ種別, chip.音量 * 0.25f );
};
this.スクロール譜面.ステージクリア = () => {
- this.現在のフェーズ.Value = ( StrokeStyleT.ビュアーモードである ) ? フェーズ.ビュアーメッセージ待機中 : フェーズ.クリアor失敗;
+ this.現在のフェーズ.Value = フェーズ.クリアor失敗;
};
this.スクロール譜面.リアルタイム演奏時刻sec = () => {
return this.現在の演奏時刻secを返す();
foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
- this.現在のフェーズ.Value = ( StrokeStyleT.ビュアーモードである ) ? フェーズ.ビュアーメッセージ待機中 : フェーズ.演奏中;
+ // 最初のフェーズを設定する。
+ if( StrokeStyleT.ビュアーモードである )
+ {
+ // 演奏スコアが設定済みなら演奏開始。それ以外ならメッセージ待機へ。
+ this.現在のフェーズ.Value = ( null != StrokeStyleT.演奏スコア ) ? フェーズ.演奏中 : フェーズ.ビュアーメッセージ待機中;
+ }
+ else
+ {
+ // 演奏開始。
+ this.現在のフェーズ.Value = フェーズ.演奏中;
+ }
+
this.活性化した直後である = true;
}
protected override void On非活性化( デバイスリソース dr )
this.スクロール譜面.チップを進行描画する( dr, 演奏時刻sec );
this.回転羽.進行描画する( dr );
this.FPS.VPSをカウントする();
- this.FPS.FPSをカウントする();
this.FPS画像.表示文字列 = $"VPS: {this.FPS.現在のVPS.ToString()} / FPS: {this.FPS.現在のFPS.ToString()}";
this.FPS画像.進行描画する( dr, 0f, 0f );
// ESC 押下 → キャンセル
if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Escape ) &&
- ( false == StrokeStyleT.ビュアーモードである ) )
+ StrokeStyleT.ビュアーモードではない ) // ビュアーモードでは無効。
{
this.BGMを解放する();
this.現在のフェーズ.Value = フェーズ.キャンセル;
}
}
+ public void 演奏を停止する()
+ {
+ this.スクロール譜面?.演奏を停止する();
+ this.BGMを解放する();
+ }
public void BGMを解放する()
{
if( null != this.BGM ) // 背景動画がなければ BGM も null である