using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; using FDK.メディア; namespace SST.ステージ.演奏 { class ドラムサウンド : FDK.Activity { public ドラムサウンド() { } protected override void On活性化( デバイスリソース dr ) { this.KitXmlを読み込む(); } protected override void On非活性化( デバイスリソース dr ) { this.すべてのコンテキストを初期化する(); } public void 発声する( SSTFormat.チップ種別 chipType, float 音量0to1 ) { lock( this.スレッド間同期 ) { if( false == this.チップtoコンテキスト.ContainsKey( chipType ) ) return; // コンテキスト未登録のチップなら何もしない。 var context = this.チップtoコンテキスト[ chipType ]; // 現在発声中のサウンドを全部止めるチップ種別の場合は止める。 if( 0 != chipType.排他発声グループID() ) // ID = 0 は対象外。 { // 同じ排他発声グループIDを持つコンテキストの Sounds[] を select する。 var 停止するサウンド群 = from kvp in this.チップtoコンテキスト where ( kvp.Key.排他発声グループID() == chipType.排他発声グループID() ) select kvp.Value.Sounds; // 集めた Sounds[] をすべて停止する。 foreach( var sounds in 停止するサウンド群 ) foreach( var sound in sounds ) sound.再生を停止する(); } // 再生する。 if( null != context.Sounds[ context.次に再生するSound番号 ] ) { context.Sounds[ context.次に再生するSound番号 ].音量 = 音量0to1; context.Sounds[ context.次に再生するSound番号 ].再生を開始する(); } // サウンドローテーション。 context.次に再生するSound番号++; if( context.次に再生するSound番号 >= ドラムサウンド.多重度 ) context.次に再生するSound番号 = 0; } } protected const int 多重度 = 2; protected readonly string KitXmlファイルパス = @"$(Static)\sounds\Kit.xml"; protected class Cコンテキスト : IDisposable { public FDK.メディア.サウンド.WASAPI排他.Sound[] Sounds = new FDK.メディア.サウンド.WASAPI排他.Sound[ ドラムサウンド.多重度 ]; public int 次に再生するSound番号 = 0; public void Dispose() { if( null != this.Sounds ) { for( int i = 0; i < this.Sounds.Length; i++ ) { StrokeStyleT.Wasapiデバイス.サウンドをミキサーから削除する( this.Sounds[ i ] ); FDK.Utilities.解放する( ref this.Sounds[ i ] ); } } } }; protected Dictionary チップtoコンテキスト = null; private void すべてのコンテキストを初期化する() { // すでに存在しているなら解放する。 if( null != this.チップtoコンテキスト ) { foreach( var kvp in this.チップtoコンテキスト ) ( (Cコンテキスト) kvp.Value ).Dispose(); } // 生成する。 this.チップtoコンテキスト = new Dictionary(); } private void KitXmlを読み込む() { this.すべてのコンテキストを初期化する(); string ファイルパス = FDK.フォルダ.絶対パスに含まれるフォルダ変数を展開して返す( this.KitXmlファイルパス ); if( false == File.Exists( ファイルパス ) ) { FDK.Log.WARNING( $"Kit ファイルが存在しません。ドラムサウンドは生成されません。[{this.KitXmlファイルパス}]" ); return; } try { var xml文書 = XDocument.Load( ファイルパス ); // var Root要素 = xml文書.Element( nameof( XML.Root ) ); { // var DrumKit要素 = Root要素.Element( nameof( XML.DrumKit ) ); // 最初の1つのみサポート。 var DrumKit要素のVersion属性 = DrumKit要素.Attribute( nameof( XML.Version ) ); var DrumKit要素のName属性 = DrumKit要素.Attribute( nameof( XML.Name ) ); // 現在は未使用。 if( "1.2" == DrumKit要素のVersion属性.Value ) { #region " DrumKit ver1.2 " //---------------- foreach( var DrumKitの子要素 in DrumKit要素.Elements() ) { switch( DrumKitの子要素.Name.LocalName ) { // case nameof( XML.Sound ): // Name="..." 属性の値は、SSTFormat.チップ種別 の各メンバの名前に等しいものとする。 var Sound要素のName属性 = DrumKitの子要素.Attribute( nameof( XML.Name ) ); var チップ種別 = SSTFormat.チップ種別.Unknown; if( Enum.TryParse( Sound要素のName属性.Value, out チップ種別 ) ) { string サウンドファイルパス = Path.Combine( StrokeStyleT.フォルダ.StaticFolder + @"\sounds\", DrumKitの子要素.Value ); if( File.Exists( サウンドファイルパス ) ) { // すでに辞書に存在してるなら、解放して削除する。 if( this.チップtoコンテキスト.ContainsKey( チップ種別 ) ) { this.チップtoコンテキスト[ チップ種別 ]?.Dispose(); this.チップtoコンテキスト.Remove( チップ種別 ); } // コンテキストを作成する。 var context = new Cコンテキスト() { Sounds = new FDK.メディア.サウンド.WASAPI排他.Sound[ ドラムサウンド.多重度 ], 次に再生するSound番号 = 0, }; for( int i = 0; i < context.Sounds.Length; i++ ) { // 多重度分のサウンドを生成しつつ、ミキサーにも登録。 context.Sounds[ i ] = new FDK.メディア.サウンド.WASAPI排他.Sound( サウンドファイルパス ); StrokeStyleT.Wasapiデバイス.サウンドをミキサーに追加する( context.Sounds[ i ] ); } // コンテキストを辞書に追加する。 this.チップtoコンテキスト.Add( チップ種別, context ); } else { FDK.Log.WARNING( $"サウンドファイル {FDK.フォルダ.絶対パスをフォルダ変数付き絶対パスに変換して返す( サウンドファイルパス )} が存在しません。[{this.KitXmlファイルパス}]" ); } } else { FDK.Log.WARNING( $"未知の要素 {Sound要素のName属性.Value} をスキップします。[{this.KitXmlファイルパス}]" ); } break; } } //---------------- #endregion } } } catch( Exception e ) { FDK.Log.ERROR( $"Kitファイルの読み込みに失敗しました。{e.Message}[{this.KitXmlファイルパス}]" ); } } private readonly object スレッド間同期 = new object(); } }