• R/O
  • HTTP
  • SSH
  • HTTPS

CsWin10Desktop3: Commit

Visual C# 7.0, Windows10 Desktop App


Commit MetaInfo

Revisiondca198358be7773fbf3ffa57a8c6991be87e87b0 (tree)
Time2017-10-15 00:11:19
Authorくまかみ工房 <kumakamikoubou@gmai...>
Commiterくまかみ工房

Log Message

いろいろ更新。(ログ消えた

Change Summary

Incremental Difference

--- a/FDK/FDK.csproj
+++ b/FDK/FDK.csproj
@@ -45,6 +45,9 @@
4545 <Reference Include="CSCore, Version=1.2.1.1, Culture=neutral, PublicKeyToken=5a08f2b6f4415dea, processorArchitecture=MSIL">
4646 <HintPath>..\packages\CSCore.1.2.1.1\lib\net35-client\CSCore.dll</HintPath>
4747 </Reference>
48+ <Reference Include="NVorbis, Version=0.8.5.0, Culture=neutral, processorArchitecture=MSIL">
49+ <HintPath>..\..\..\@DTXMania\DTXmatixx\packages\NVorbis.0.8.5.0\lib\NVorbis.dll</HintPath>
50+ </Reference>
4851 <Reference Include="SharpDX, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1, processorArchitecture=MSIL">
4952 <HintPath>..\packages\SharpDX.4.0.1\lib\net45\SharpDX.dll</HintPath>
5053 </Reference>
@@ -129,10 +132,13 @@
129132 <Compile Include="カウンタ\経過時間測定.cs" />
130133 <Compile Include="メディア\XML.cs" />
131134 <Compile Include="メディア\グラフィックデバイス.cs" />
132- <Compile Include="メディア\サウンド\WASAPI\DecodedWaveSource.cs" />
133- <Compile Include="メディア\サウンド\WASAPI\Device.cs" />
135+ <Compile Include="メディア\サウンド\WASAPI\MediaFoundationSampleSource.cs" />
134136 <Compile Include="メディア\サウンド\WASAPI\Mixer.cs" />
137+ <Compile Include="メディア\サウンド\WASAPI\NVorbisSampleSource2.cs" />
138+ <Compile Include="メディア\サウンド\WASAPI\NVorbisSampleSource.cs" />
139+ <Compile Include="メディア\サウンド\WASAPI\SampleSourceFactory.cs" />
135140 <Compile Include="メディア\サウンド\WASAPI\Sound.cs" />
141+ <Compile Include="メディア\サウンド\WASAPI\SoundDevice.cs" />
136142 <Compile Include="メディア\サウンド\WASAPI\SoundTimer.cs" />
137143 <Compile Include="メディア\テクスチャ.cs" />
138144 <Compile Include="メディア\テクスチャフォント.cs" />
--- a/FDK/FDKUtilities.cs
+++ b/FDK/FDKUtilities.cs
@@ -89,6 +89,17 @@ namespace FDK
8989 }
9090
9191 /// <summary>
92+ /// 指定された位置を、それを超えないブロック境界に揃えて返す。
93+ /// </summary>
94+ /// <param name="position">位置[byte]。</param>
95+ /// <param name="blockAlign">ブロック境界[byte]。</param>
96+ /// <returns></returns>
97+ public static int 位置をブロック境界単位にそろえて返す( int position, int blockAlign )
98+ {
99+ return ( position - ( position % blockAlign ) );
100+ }
101+
102+ /// <summary>
92103 /// このメソッドの 呼び出し元のメソッド名 を返す。デバッグログ用。
93104 /// </summary>
94105 public static string 現在のメソッド名
--- a/FDK/packages.config
+++ b/FDK/packages.config
@@ -1,6 +1,7 @@
11 <?xml version="1.0" encoding="utf-8"?>
22 <packages>
33 <package id="CSCore" version="1.2.1.1" targetFramework="net452" />
4+ <package id="NVorbis" version="0.8.5.0" targetFramework="net452" />
45 <package id="SharpDX" version="4.0.1" targetFramework="net452" />
56 <package id="SharpDX.Animation" version="4.0.1" targetFramework="net452" />
67 <package id="SharpDX.D3DCompiler" version="4.0.1" targetFramework="net452" />
--- a/FDK/メディア/サウンド/WASAPI/DecodedWaveSource.cs
+++ /dev/null
@@ -1,240 +0,0 @@
1-using System;
2-using System.Collections.Generic;
3-using System.Diagnostics;
4-using System.IO;
5-using System.Linq;
6-using System.Runtime.InteropServices;
7-using CSCore;
8-using CSCore.MediaFoundation;
9-using SharpDX.MediaFoundation;
10-
11-namespace FDK.メディア.サウンド.WASAPI
12-{
13- /// <summary>
14- /// 指定されたメディアファイル(動画, 音楽)をデコードして、CSCore.IWaveSource オブジェクトを生成する。
15- /// </summary>
16- public class DecodedWaveSource : IWaveSource
17- {
18- /// <summary>
19- /// シーク能力があるなら true 。
20- /// </summary>
21- public bool CanSeek => true; // 常にサポートする。
22-
23- /// <summary>
24- /// デコード後のオーディオデータの長さ[byte]。
25- /// </summary>
26- public long Length
27- {
28- get
29- => this._EncodedWaveData.Length;
30- }
31-
32- /// <summary>
33- /// 現在の位置。
34- /// 先頭からのオフセット[byte]で表す。
35- /// </summary>
36- public long Position
37- {
38- get
39- => this._Position;
40-
41- set
42- {
43- if( ( 0 > value ) || ( ( this.Length > 0 ) && ( this.Length <= value ) ) ) // ※ Length == 0 なら例外は出さない
44- throw new ArgumentOutOfRangeException();
45-
46- this._Position = value;
47- }
48- }
49-
50- /// <summary>
51- /// デコード後のオーディオデータのフォーマット。
52- /// </summary>
53- public WaveFormat WaveFormat
54- {
55- get;
56- protected set;
57- }
58-
59- /// <summary>
60- /// コンストラクタで指定されたファイルパス。
61- /// </summary>
62- public string Path
63- {
64- get;
65- } = null;
66-
67-
68- /// <summary>
69- /// コンストラクタ。
70- /// 指定したメディアファイル(動画、音楽)をデコードする。
71- /// </summary>
72- public DecodedWaveSource( string path, WaveFormat targetFormat )
73- {
74- this.Path = path;
75-
76- // ISampleSource は IWaveSource を 32bit-float に変換して出力する仕様なので、
77- // 最初からその形式でデコードして ISampleSource.Read() の変換負荷を下げる。
78-
79- this.WaveFormat = new WaveFormat(
80- targetFormat.SampleRate, // サンプルレートと
81- 32,
82- targetFormat.Channels, // チャンネルは、指定されたものを使う。
83- AudioEncoding.IeeeFloat );
84-
85- this._初期化する( path );
86- }
87-
88- /// <summary>
89- /// 解放する。
90- /// </summary>
91- public void Dispose()
92- {
93- this._解放する();
94- }
95-
96- /// <summary>
97- /// 連続した要素を読み込み、this.Position を読み込んだ要素の数だけ進める。
98- /// </summary>
99- /// <param name="buffer">
100- /// 読み込んだ要素を格納するための配列。
101- /// このメソッドから戻ると、buffer には offset ~ (offset + count - 1) の数の要素が格納されている。
102- /// </param>
103- /// <param name="offset">
104- /// buffer に格納を始める位置。
105- /// </param>
106- /// <param name="count">
107- /// 読み込む最大の要素数。
108- /// </param>
109- /// <returns>
110- /// buffer に読み込んだ要素の総数。
111- /// </returns>
112- public int Read( byte[] buffer, int offset, int count )
113- {
114- // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
115-
116- if( ( null == this._EncodedWaveData ) && ( null == buffer ) )
117- return 0;
118-
119- // offset を 0~buffer.Length-1 に収める。
120- offset = Math.Max( 0, Math.Min( buffer.Length - 1, offset ) );
121-
122- // count は、_EncodeWaveData.Length-Position, buffer.Length-offset, count のうちの最小値とする。
123- count = Math.Min( Math.Min( this._EncodedWaveData.Length - (int) this._Position, count ), buffer.Length - offset );
124-
125- if( 0 < count )
126- {
127- Array.Copy(
128- sourceArray: this._EncodedWaveData,
129- sourceIndex: this._Position,
130- destinationArray: buffer,
131- destinationIndex: offset,
132- length: count );
133-
134- this._Position += count;
135- }
136-
137- return count;
138- }
139-
140-
141- private MFMediaType _MediaType = null;
142-
143- private byte[] _EncodedWaveData = null;
144-
145- private long _Position = 0;
146-
147-
148- /// <summary>
149- /// 指定されたファイルをデコードする。
150- /// </summary>
151- /// <param name="path">
152- /// サウンドファイル。
153- /// </param>
154- private void _初期化する( string path )
155- {
156- try
157- {
158- using( var sourceReader = new MFSourceReader( path ) ) // SourceReader は、SharpDX ではなく CSCore のものを使う。(MediaType から WaveFormat に一発で変換できるので。)
159- using( var waveStream = new MemoryStream() )
160- {
161- // 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
162-
163- sourceReader.SetStreamSelection( ( int ) SourceReaderIndex.AllStreams, false );
164- sourceReader.SetStreamSelection( ( int ) SourceReaderIndex.FirstAudioStream, true );
165-
166- // デコード後フォーマットを持つメディアタイプを作成し、SourceReader に登録する。
167- using( var partialMediaType = MFMediaType.FromWaveFormat( this.WaveFormat ) ) // WaveFormatEx にも対応。
168- {
169- // 作成したメディアタイプを sourceReader にセットする。必要なデコーダが見つからなかったら、ここで例外が発生する。
170- sourceReader.SetCurrentMediaType( (int) SourceReaderIndex.FirstAudioStream, partialMediaType );
171-
172- // 完成されたメディアタイプを取得する。
173- this._MediaType = sourceReader.GetCurrentMediaType( (int) SourceReaderIndex.FirstAudioStream );
174-
175- // メディアタイプからフォーマットを取得する。(同じであるはずだが念のため)
176- this.WaveFormat = this._MediaType.ToWaveFormat( MFWaveFormatExConvertFlags.Normal );
177-
178- // 最初のオーディオストリームが選択されていることを保証する。
179- sourceReader.SetStreamSelection( (int) SourceReaderIndex.FirstAudioStream, true );
180- }
181-
182- // sourceReader からサンプルを取得してデコードし、waveStream へ書き込んだのち、byte[] _EncodedWaveData へ出力する。
183- while( true )
184- {
185- // 次のサンプルを読み込む。
186- using( var sample = sourceReader.ReadSample(
187- (int) SourceReaderIndex.FirstAudioStream,
188- (int) CSCore.MediaFoundation.SourceReaderControlFlags.None,
189- out int actualStreamIndexRef,
190- out var dwStreamFlagsRef,
191- out Int64 llTimestampRef ) )
192- {
193- if( null == sample )
194- break; // EndOfStream やエラーのときも null になる。
195-
196- // sample をロックし、オーディオデータへのポインタを取得する。
197- using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
198- {
199- // オーディオデータをメモリストリームに書き込む。
200- var audioData = mediaBuffer.Lock( out int cbMaxLengthRef, out int cbCurrentLengthRef );
201- try
202- {
203- byte[] dstData = new byte[ cbCurrentLengthRef ];
204- Marshal.Copy( audioData, dstData, 0, cbCurrentLengthRef );
205- waveStream.Write( dstData, 0, cbCurrentLengthRef );
206- }
207- finally
208- {
209- mediaBuffer.Unlock();
210- }
211- }
212- }
213- }
214-
215- // ストリームの内容を byte 配列に出力。
216- this._EncodedWaveData = waveStream.ToArray();
217- }
218- }
219- catch( Exception e )
220- {
221- Log.ERROR( $"MediaFoundation SourceReader の初期化に失敗しました。[{e.Message}]" );
222- this._EncodedWaveData = new byte[] { };
223- }
224-
225- this._Position = 0;
226- }
227-
228- private void _解放する()
229- {
230- FDKUtilities.解放する( ref this._MediaType );
231- }
232-
233-
234- #region " Win32 "
235- //----------------
236- private const int MF_E_INVALIDMEDIATYPE = unchecked((int) 0xC00D36B4);
237- //----------------
238- #endregion
239- }
240-}
--- /dev/null
+++ b/FDK/メディア/サウンド/WASAPI/MediaFoundationSampleSource.cs
@@ -0,0 +1,189 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Diagnostics;
4+using System.IO;
5+using System.Linq;
6+using System.Runtime.InteropServices;
7+using CSCore;
8+using CSCore.MediaFoundation;
9+using SharpDX.MediaFoundation;
10+
11+namespace FDK.メディア.サウンド.WASAPI
12+{
13+ /// <summary>
14+ /// 指定されたメディアファイル(動画, 音楽)をデコードして、<see cref="CSCore.ISampleSource"/> オブジェクトを生成する。
15+ /// </summary>
16+ public class MediaFoundationSampleSource : IWaveSource // MediaFoundation はデータが byte[] のほうが都合がいいので、ISampleSource じゃなく IWaveSource を使う。
17+ {
18+ /// <summary>
19+ /// シーク能力があるなら true 。
20+ /// </summary>
21+ public bool CanSeek => true; // オンメモリなので常にサポートできる。
22+
23+ /// <summary>
24+ /// デコード後のオーディオデータのフォーマット。
25+ /// </summary>
26+ public WaveFormat WaveFormat
27+ {
28+ get;
29+ protected set;
30+ } = null;
31+
32+ /// <summary>
33+ /// デコード後のオーディオデータの長さ[byte]。
34+ /// </summary>
35+ public long Length
36+ => this._EncodedWaveData.Length;
37+
38+ /// <summary>
39+ /// 現在の位置。
40+ /// 先頭からのオフセット[byte]で表す。
41+ /// </summary>
42+ public long Position
43+ {
44+ get
45+ => this._Position;
46+
47+ set
48+ {
49+ if( ( 0 > value ) || ( ( this.Length > 0 ) && ( this.Length < value ) ) ) // ※ Length == 0 なら例外は出さない
50+ throw new ArgumentOutOfRangeException( $"Position に指定された値が不正です。[{value}]" );
51+
52+ this._Position = value;
53+ }
54+ }
55+
56+
57+ /// <summary>
58+ /// コンストラクタ。
59+ /// 指定されたファイルを指定されたフォーマットでデコードし、内部にオンメモリで保管する。
60+ /// </summary>
61+ public MediaFoundationSampleSource( string path, WaveFormat deviceFormat )
62+ {
63+ this.WaveFormat = new WaveFormat(
64+ deviceFormat.SampleRate, // 指定されたレート
65+ 32, // 32bit 固定
66+ deviceFormat.Channels, // 指定されたチャンネル数
67+ AudioEncoding.IeeeFloat ); // IeeeFloat 固定
68+
69+ using( var sourceReader = new MFSourceReader( path ) ) // SourceReader は、SharpDX ではなく CSCore のものを使う。(MediaType から WaveFormat に一発で変換できるので。)
70+ using( var waveStream = new MemoryStream() )
71+ {
72+ // (1) 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
73+ sourceReader.SetStreamSelection( (int) SourceReaderIndex.AllStreams, false );
74+ sourceReader.SetStreamSelection( (int) SourceReaderIndex.FirstAudioStream, true );
75+
76+ // (2) デコード後フォーマットを持つメディアタイプを作成し、SourceReader に登録する。
77+ using( var partialMediaType = MFMediaType.FromWaveFormat( this.WaveFormat ) ) // WaveFormatEx にも対応。
78+ {
79+ // 作成したメディアタイプを sourceReader にセットする。必要なデコーダが見つからなかったら、ここで例外が発生する。
80+ sourceReader.SetCurrentMediaType( (int) SourceReaderIndex.FirstAudioStream, partialMediaType );
81+
82+ // 完成されたメディアタイプを取得する。
83+ this._MediaType = sourceReader.GetCurrentMediaType( (int) SourceReaderIndex.FirstAudioStream );
84+
85+ // メディアタイプからフォーマットを取得する。(同じであるはずだが念のため)
86+ this.WaveFormat = this._MediaType.ToWaveFormat( MFWaveFormatExConvertFlags.Normal );
87+
88+ // 最初のオーディオストリームが選択されていることを保証する。
89+ sourceReader.SetStreamSelection( (int) SourceReaderIndex.FirstAudioStream, true );
90+ }
91+
92+ // (3) sourceReader からサンプルを取得してデコードし、waveStream へ書き込む。
93+ while( true )
94+ {
95+ // 次のサンプルを読み込む。
96+ using( var sample = sourceReader.ReadSample(
97+ (int) SourceReaderIndex.FirstAudioStream,
98+ (int) CSCore.MediaFoundation.SourceReaderControlFlags.None,
99+ out int actualStreamIndexRef,
100+ out var dwStreamFlagsRef,
101+ out Int64 llTimestampRef ) )
102+ {
103+ if( null == sample )
104+ break; // EndOfStream やエラーのときも null になる。
105+
106+ // sample をロックし、オーディオデータへのポインタを取得する。
107+ using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
108+ {
109+ // オーディオデータをメモリストリームに書き込む。
110+ var audioData = mediaBuffer.Lock( out int cbMaxLengthRef, out int cbCurrentLengthRef );
111+ try
112+ {
113+ byte[] dstData = new byte[ cbCurrentLengthRef ];
114+ Marshal.Copy( audioData, dstData, 0, cbCurrentLengthRef );
115+ waveStream.Write( dstData, 0, cbCurrentLengthRef );
116+ }
117+ finally
118+ {
119+ mediaBuffer.Unlock();
120+ }
121+ }
122+ }
123+ }
124+
125+ // (4) ストリームの内容を byte 配列に出力する。
126+ this._EncodedWaveData = waveStream.ToArray();
127+ }
128+ }
129+
130+ /// <summary>
131+ /// 解放する。
132+ /// </summary>
133+ public void Dispose()
134+ {
135+ this._EncodedWaveData = null;
136+ FDKUtilities.解放する( ref this._MediaType );
137+ }
138+
139+ /// <summary>
140+ /// 連続したsampleを読み込み、<see cref="Position"/> を読み込んだsampleの数だけ進める。
141+ /// </summary>
142+ /// <param name="buffer">
143+ /// 読み込んだsampleを格納するための配列。
144+ /// このメソッドから戻ると、<paramref name="buffer"/> には <paramref name="offset"/> ~ (<paramref name="offset"/> + <paramref name="offset"/> - 1) の数の要素が格納されている。
145+ /// </param>
146+ /// <param name="offset">
147+ /// <paramref name="buffer"/> に格納を始める位置[先頭からのsample]。
148+ /// </param>
149+ /// <param name="count">
150+ /// 読み込む最大のsample数。
151+ /// </param>
152+ /// <returns>
153+ /// <paramref name="buffer"/> に読み込んだsampleの総数。
154+ /// </returns>
155+ public int Read( byte[] buffer, int offset, int count )
156+ {
157+ // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
158+ if( ( null == this._EncodedWaveData ) || ( null == buffer ) )
159+ return 0;
160+
161+ // offset を 0~buffer.Length-1 に収める。
162+ if( 0 > offset )
163+ offset = 0;
164+ else if( buffer.Length - 1 < offset )
165+ offset = buffer.Length - 1;
166+
167+ // count は、Length-Position, buffer.Length-offset, count のうちの最小値とする。
168+ count = Math.Min( Math.Min( (int)( this.Length - this._Position ), count ), ( buffer.Length - offset ) );
169+
170+ if( 0 < count )
171+ {
172+ Array.Copy(
173+ sourceArray: this._EncodedWaveData,
174+ sourceIndex: this._Position,
175+ destinationArray: buffer,
176+ destinationIndex: offset,
177+ length: count );
178+
179+ this._Position += count;
180+ }
181+
182+ return count;
183+ }
184+
185+ private MFMediaType _MediaType = null;
186+ private byte[] _EncodedWaveData = null;
187+ private long _Position = 0;
188+ }
189+}
--- a/FDK/メディア/サウンド/WASAPI/Mixer.cs
+++ b/FDK/メディア/サウンド/WASAPI/Mixer.cs
@@ -8,22 +8,21 @@ namespace FDK.メディア.サウンド.WASAPI
88 {
99 /// <summary>
1010 /// オーディオミキサー。
11- /// 自身が ISampleSource であり、そのまま AudioClient のレンダリングターゲットに指定する。
11+ /// 自身が ISampleSource であり、そのまま AudioClient のレンダリングターゲットに指定することで、無限の出力を生成する。
1212 /// </summary>
1313 internal class Mixer : ISampleSource
1414 {
1515 /// <summary>
16- /// 音量。0.0(無音)~1.0(原音)。
16+ /// 音量。0.0(無音)~1.0(原音)~... 上限なし
1717 /// </summary>
1818 public float Volume
1919 {
2020 get
2121 => this._Volume;
22-
2322 set
2423 => this._Volume =
2524 ( 0.0f > value ) ? throw new ArgumentOutOfRangeException() :
26- ( 1.0f < value ) ? throw new ArgumentOutOfRangeException() :
25+ //( 1.0f < value ) ? throw new ArgumentOutOfRangeException() : --> 上限なし。
2726 value;
2827 }
2928
@@ -31,10 +30,7 @@ namespace FDK.メディア.サウンド.WASAPI
3130 /// ミキサーのフォーマット。
3231 /// </summary>
3332 public WaveFormat WaveFormat
34- {
35- get
36- => this._WaveFormat;
37- }
33+ => this._WaveFormat;
3834
3935 /// <summary>
4036 /// ミキサーはループするので、Position には 非対応。
@@ -43,8 +39,7 @@ namespace FDK.メディア.サウンド.WASAPI
4339 {
4440 get
4541 => 0;
46-
47- set
42+ set
4843 => throw new NotSupportedException();
4944 }
5045
@@ -54,9 +49,10 @@ namespace FDK.メディア.サウンド.WASAPI
5449 public bool CanSeek => false;
5550
5651 /// <summary>
57- /// ミキサーはループするので、長さの概念はない。
52+ /// ミキサーは無限にループするので、長さの概念はない。
5853 /// </summary>
59- public long Length => 0;
54+ public long Length
55+ => throw new NotSupportedException();
6056
6157
6258 /// <summary>
@@ -65,6 +61,7 @@ namespace FDK.メディア.サウンド.WASAPI
6561 /// </summary>
6662 public Mixer( WaveFormat deviceWaveFormat )
6763 {
64+ // ミキサーのフォーマットは、デバイスのフォーマットをそのまま使う。
6865 this._WaveFormat = deviceWaveFormat;
6966 }
7067
@@ -75,11 +72,9 @@ namespace FDK.メディア.サウンド.WASAPI
7572 {
7673 lock( this._スレッド間同期 )
7774 {
78- // すべての Sound を解放する。
79- while( 0 < this._Sounds.Count )
80- this._Sounds[ 0 ].Dispose(); // Dispose() 内で RemoveSound() が呼び出され、_Sounds の内容が変化するので foreach は使えない。
75+ foreach( var sound in this._Sounds )
76+ sound.Dispose();
8177
82- // Sound リストをクリアする。
8378 this._Sounds.Clear();
8479 }
8580 }
@@ -93,20 +88,23 @@ namespace FDK.メディア.サウンド.WASAPI
9388 if( null == sound )
9489 throw new ArgumentNullException();
9590
96- if( ( sound.SampleSource.WaveFormat.Channels != this._WaveFormat.Channels ) || // 同じチャンネル数、
97- ( sound.SampleSource.WaveFormat.SampleRate != this._WaveFormat.SampleRate ) || // 同じ周波数、
98- ( sound.SampleSource.WaveFormat.WaveFormatTag != AudioEncoding.IeeeFloat ) ) // 常に 32bit-float であること。
99- {
100- // 違った場合の変換はサポートしない。
101- throw new ArgumentException( "ミキサーと同じチャンネル数、サンプルレート、かつ 32bit float 型である必要があります。" );
102- }
103-
10491 lock( this._スレッド間同期 )
10592 {
106- if( false == this.Contains( sound ) )
93+ // すでに登録済み(まだ再生中)なら削除する。
94+ if( this._Sounds.Contains( sound ) )
95+ this._Sounds.Remove( sound ); // 再生も止まる。
96+
97+ // Soundのフォーマットがミキサーのフォーマットと適合するかをチェック。
98+ if( ( sound.WaveFormat.Channels != this._WaveFormat.Channels ) || // 同じチャンネル数、
99+ ( sound.WaveFormat.SampleRate != this._WaveFormat.SampleRate ) || // 同じ周波数、
100+ ( sound.WaveFormat.WaveFormatTag != AudioEncoding.IeeeFloat ) ) // 常に 32bit-float であること。
107101 {
108- this._Sounds.Add( sound );
102+ // 違った場合の変換はサポートしない。
103+ throw new ArgumentException( "ミキサーと同じチャンネル数、サンプルレート、かつ 32bit float 型である必要があります。" );
109104 }
105+
106+ // サウンドリストに登録。
107+ this._Sounds.AddLast( sound );
110108 }
111109 }
112110
@@ -118,10 +116,8 @@ namespace FDK.メディア.サウンド.WASAPI
118116 {
119117 lock( this._スレッド間同期 )
120118 {
121- if( this.Contains( sound ) )
122- {
119+ if( this._Sounds.Contains( sound ) )
123120 this._Sounds.Remove( sound );
124- }
125121 }
126122 }
127123
@@ -143,69 +139,74 @@ namespace FDK.メディア.サウンド.WASAPI
143139 }
144140
145141 /// <summary>
146- /// バッファにサウンドデータを出力する。
142+ /// バッファにサウンドサンプルを出力する。
147143 /// </summary>
148144 /// <returns>
149145 /// 実際に出力したサンプル数。
150146 /// </returns>
151- public int Read( float[] バッファ, int バッファの出力開始位置, int 出力サンプル数 )
147+ public int Read( float[] 出力バッファ, int 出力バッファの出力開始位置, int 出力サンプル数 )
152148 {
153- // ミキサに登録されている Sound の入力とこのメソッドが出力するデータはいずれも常に 32bit-float であり、
149+ // ミキサに登録されている Sound の入力と、このメソッドが出力するデータは、いずれも常に 32bit-float である。
154150 // これは this.WaveFormat.WaveFormatTag とは無関係なので注意。(this.WaveFormat は、チャンネル数とサンプルレートしか見てない。)
155151
156- if( 0 < 出力サンプル数 )
152+ if( 0 >= 出力サンプル数 )
153+ return 0;
154+
155+ lock( this._スレッド間同期 )
157156 {
158- lock( this._スレッド間同期 )
159- {
160- // 中間バッファが十分あることを確認する。足りなければ新しく確保して戻ってくる。
161- this._中間バッファ = this._中間バッファ.CheckBuffer( 出力サンプル数 ); // サンプル数であり、フレーム数(サンプル数×チャンネル数)ではない。
157+ // 中間バッファが十分あることを確認する。足りなければ新しく確保して戻ってくる。
158+ this._中間バッファ = this._中間バッファ.CheckBuffer( 出力サンプル数 ); // サンプル数であり、フレーム数(サンプル数×チャンネル数)ではない。
162159
163- // まずは無音で埋める。
164- Array.Clear( バッファ, 0, 出力サンプル数 );
160+ // まずは無音で埋める。
161+ Array.Clear( 出力バッファ, 0, 出力サンプル数 );
165162
166- // その上に、ミキサに登録されているすべての Sound を加算合成する。
167- if( 0 < this._Sounds.Count )
168- {
169- for( int m = this._Sounds.Count - 1; m >= 0; m-- ) // リストから Remove する場合があるので、リストの後ろから進める。
170- {
171- var sound = this._Sounds[ m ];
163+ // その上に、ミキサに登録されているすべての Sound を加算合成する。
164+ if( 0 < this._Sounds.Count )
165+ {
166+ var 再生終了したSound一覧 = new List<Sound>();
172167
173- // 中間バッファにサウンドデータを受け取る。
174- int 受け取ったサンプル数 = sound.SampleSource.Read( this._中間バッファ, 0, 出力サンプル数 );
168+ foreach( var sound in this._Sounds )
169+ {
170+ // 中間バッファにサウンドデータを受け取る。
171+ int 受け取ったサンプル数 = sound.Read( this._中間バッファ, 0, 出力サンプル数 );
175172
173+ if( 0 < 受け取ったサンプル数 )
174+ {
176175 // 中間バッファから出力バッファへ合成する。
177- for( int i = バッファの出力開始位置, n = 0; n < 受け取ったサンプル数; i++, n++ )
176+ for( int i = 出力バッファの出力開始位置, n = 0; n < 受け取ったサンプル数; i++, n++ )
178177 {
179178 float data = this._中間バッファ[ n ] // 原音
180179 * sound.Volume // 個別音量(Sound)
181180 * this._Volume; // マスタ音量(ミキサ)
182181
183182 // 先に無音を出力済みなので、上書きかどうかを気にしないで常に加算。
184- バッファ[ i ] += data;
185- }
186-
187- if( 0 == 受け取ったサンプル数 )
188- {
189- // 再生終了。リストから削除。
190- this.RemoveSound( sound );
183+ 出力バッファ[ i ] += data;
191184 }
192185 }
186+ else
187+ {
188+ // 再生終了。
189+ 再生終了したSound一覧.Add( sound );
190+ }
193191 }
192+
193+ // 再生が終了したSoundをサウンドリストから削除する。
194+ foreach( var sound in 再生終了したSound一覧 )
195+ {
196+ sound.Stop(); // Stopしないままリストから除去したらメモリリークするので注意!
197+ this.RemoveSound( sound ); // Sound は(また再利用できるように)Dispose() しないので、Sound の生成元で管理すること。
198+ }
199+ 再生終了したSound一覧.Clear();
194200 }
195201 }
196202
197203 return 出力サンプル数;
198204 }
199205
200-
206+ private readonly LinkedList<Sound> _Sounds = new LinkedList<Sound>();
201207 private float _Volume = 1.0f;
202-
203208 private WaveFormat _WaveFormat = null;
204-
205- private readonly List<Sound> _Sounds = new List<Sound>();
206-
207209 private float[] _中間バッファ = null;
208-
209210 private readonly object _スレッド間同期 = new object();
210211 }
211212 }
--- /dev/null
+++ b/FDK/メディア/サウンド/WASAPI/NVorbisSampleSource.cs
@@ -0,0 +1,70 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Diagnostics;
4+using System.IO;
5+using System.Linq;
6+using CSCore;
7+using NVorbis;
8+
9+namespace FDK.メディア.サウンド.WASAPI
10+{
11+ /// <summary>
12+ /// 指定されたメディアファイル(動画, 音楽)を Vorbis としてデコードして、CSCore.IWaveSource オブジェクトを生成する。
13+ /// リサンプラーなし版。
14+ /// 参照:<seealso cref="https://cscore.codeplex.com/SourceControl/latest#Samples/NVorbisIntegration/Program.cs"/>
15+ /// </summary>
16+ public class NVorbisSampleSource : ISampleSource
17+ {
18+ private Stream _stream;
19+ private VorbisReader _vorbisReader;
20+ private WaveFormat _waveFormat;
21+
22+ public bool CanSeek
23+ => this._stream.CanSeek;
24+
25+ public WaveFormat WaveFormat
26+ => this._waveFormat;
27+
28+ public long Position
29+ {
30+ get
31+ => ( this.CanSeek ) ? this._vorbisReader.DecodedPosition : 0;
32+
33+ set
34+ => this._vorbisReader.DecodedPosition = ( this.CanSeek ) ?
35+ value : throw new InvalidOperationException( "DecodedNVorbisSource is not seekable." );
36+ }
37+
38+ public long Length
39+ => ( this.CanSeek ) ? this._vorbisReader.TotalSamples : 0;
40+
41+
42+ public NVorbisSampleSource( Stream stream, WaveFormat deviceFormat )
43+ {
44+ if( null == stream )
45+ throw new ArgumentException( "stream" );
46+ if( !( stream.CanRead ) )
47+ throw new ArgumentException( "Stream is not readable.", "stream" );
48+
49+ this._stream = stream;
50+ this._vorbisReader = new VorbisReader( stream, false );
51+
52+ this._waveFormat = new WaveFormat(
53+ this._vorbisReader.SampleRate,
54+ 32, // 32bit 固定
55+ this._vorbisReader.Channels,
56+ AudioEncoding.IeeeFloat ); // IeeeFloat 固定
57+ }
58+
59+ public int Read( float[] buffer, int offset, int count )
60+ {
61+ return this._vorbisReader.ReadSamples( buffer, offset, count );
62+ }
63+
64+ public void Dispose()
65+ {
66+ this._vorbisReader?.Dispose();
67+ this._vorbisReader = null;
68+ }
69+ }
70+}
--- /dev/null
+++ b/FDK/メディア/サウンド/WASAPI/NVorbisSampleSource2.cs
@@ -0,0 +1,107 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Diagnostics;
4+using System.IO;
5+using System.Linq;
6+using CSCore;
7+using CSCore.DSP;
8+using NVorbis;
9+
10+namespace FDK.メディア.サウンド.WASAPI
11+{
12+ /// <summary>
13+ /// 指定されたメディアファイル(動画, 音楽)を Vorbis としてデコードして、CSCore.IWaveSource オブジェクトを生成する。
14+ /// リサンプラーあり版。
15+ /// 参照:<seealso cref="https://cscore.codeplex.com/SourceControl/latest#Samples/NVorbisIntegration/Program.cs"/>
16+ /// </summary>
17+ public class NVorbisSampleSource2 : IWaveSource
18+ {
19+ public bool CanSeek => true; // オンメモリなので常にサポートできる。
20+
21+ /// <summary>
22+ /// デコード&李サンプル後のオーディオデータのフォーマット。
23+ /// </summary>
24+ public WaveFormat WaveFormat
25+ {
26+ get;
27+ protected set;
28+ } = null;
29+
30+ /// <summary>
31+ /// デコード後のオーディオデータの長さ[byte]。
32+ /// </summary>
33+ public long Length
34+ => this._EncodedWaveData.Length;
35+
36+ /// <summary>
37+ /// 現在の位置。
38+ /// 先頭からのオフセット[byte]で表す。
39+ /// </summary>
40+ public long Position
41+ {
42+ get
43+ => this._Position;
44+
45+ set
46+ {
47+ if( ( 0 > value ) || ( ( this.Length > 0 ) && ( this.Length < value ) ) ) // ※ Length == 0 なら例外は出さない
48+ throw new ArgumentOutOfRangeException( $"Position に指定された値が不正です。[{value}]" );
49+
50+ this._Position = value;
51+ }
52+ }
53+
54+
55+ public NVorbisSampleSource2( Stream stream, WaveFormat deviceFormat )
56+ {
57+ this.WaveFormat = new WaveFormat(
58+ deviceFormat.SampleRate,
59+ 32,
60+ deviceFormat.Channels,
61+ AudioEncoding.IeeeFloat );
62+
63+ using( var vorbisSource = new NVorbisSampleSource( stream, deviceFormat ) )
64+ using( var resampler = new DmoResampler( vorbisSource.ToWaveSource(), this.WaveFormat ) )
65+ {
66+ this._EncodedWaveData = new byte[ resampler.Length ];
67+ resampler.Read( this._EncodedWaveData, 0, (int) resampler.Length );
68+ }
69+ }
70+ public void Dispose()
71+ {
72+ this._EncodedWaveData = null;
73+ }
74+ public int Read( byte[] buffer, int offset, int count )
75+ {
76+ // ※ 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
77+ if( ( null == this._EncodedWaveData ) || ( null == buffer ) )
78+ return 0;
79+
80+ // offset を 0~buffer.Length-1 に収める。
81+ if( 0 > offset )
82+ offset = 0;
83+ else if( buffer.Length - 1 < offset )
84+ offset = buffer.Length - 1;
85+
86+ // count は、Length-Position, buffer.Length-offset, count のうちの最小値とする。
87+ count = Math.Min( Math.Min( (int) ( this.Length - this._Position ), count ), ( buffer.Length - offset ) );
88+
89+ if( 0 < count )
90+ {
91+ Array.Copy(
92+ sourceArray: this._EncodedWaveData,
93+ sourceIndex: this._Position,
94+ destinationArray: buffer,
95+ destinationIndex: offset,
96+ length: count );
97+
98+ this._Position += count;
99+ }
100+
101+ return count;
102+ }
103+
104+ private byte[] _EncodedWaveData = null;
105+ private long _Position = 0;
106+ }
107+}
--- /dev/null
+++ b/FDK/メディア/サウンド/WASAPI/SampleSourceFactory.cs
@@ -0,0 +1,69 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Diagnostics;
4+using System.IO;
5+using System.Linq;
6+using CSCore;
7+using FDK;
8+
9+namespace FDK.メディア.サウンド.WASAPI
10+{
11+ public static class SampleSourceFactory
12+ {
13+ /// <summary>
14+ /// 指定されたファイルから <see cref="ISampleSource"/> を生成して返す。
15+ /// 失敗すれば例外発生。
16+ /// </summary>
17+ public static ISampleSource Create( SoundDevice device, string ファイルパス, bool オンメモリ = true )
18+ {
19+ var path = Folder.絶対パスに含まれるフォルダ変数を展開して返す( ファイルパス );
20+
21+ #region " NVorbis を試みる "
22+ //----------------
23+ try
24+ {
25+ using( var audioStream = new FileStream( path, FileMode.Open ) )
26+ {
27+ return new NVorbisSampleSource2( audioStream, device.WaveFormat )
28+ .ToSampleSource();
29+ }
30+ }
31+ catch
32+ {
33+ // ダメだったので次へ。
34+ }
35+ //----------------
36+ #endregion
37+
38+ #region " CSCore を試みる "
39+ //----------------
40+ try
41+ {
42+ // todo: 実装する
43+ //return new CSCoreSampleSource( path );
44+ }
45+ catch
46+ {
47+ // ダメだったので次へ。
48+ }
49+ //----------------
50+ #endregion
51+
52+ #region " MediaFoundation を試みる "
53+ //----------------
54+ try
55+ {
56+ return new MediaFoundationSampleSource( path, device.WaveFormat )
57+ .ToSampleSource();
58+ }
59+ catch
60+ {
61+ // ダメだったので次へ。
62+ }
63+ //----------------
64+ #endregion
65+
66+ throw new InvalidDataException( $"未対応のオーディオファイルです。[{ファイルパス}]" );
67+ }
68+ }
69+}
--- a/FDK/メディア/サウンド/WASAPI/Sound.cs
+++ b/FDK/メディア/サウンド/WASAPI/Sound.cs
@@ -1,206 +1,152 @@
11 using System;
22 using System.Collections.Generic;
33 using System.Diagnostics;
4+using System.IO;
45 using System.Linq;
56 using CSCore;
67
78 namespace FDK.メディア.サウンド.WASAPI
89 {
9- public class Sound : IDisposable
10+ public class Sound : ISampleSource
1011 {
11- public long 長さsample
12+ public bool 再生中である
1213 {
1314 get
14- => this._SampleSource.Length;
15+ => this._DeviceRef.TryGetTarget( out SoundDevice device ) && device.Mixer.Contains( this );
1516 }
16-
17- public double 長さsec
17+ public bool 再生中ではない
1818 {
1919 get
20- => this._SampleToSec( this.長さsample );
20+ => !( this.再生中である );
2121 }
2222
23- public long 位置sample
24- {
25- get
26- => this._SampleSource.Position;
27-
28- set
29- => this._SampleSource.Position = value;
30- }
23+ /// <summary>
24+ /// シークが可能なら true。
25+ /// </summary>
26+ public bool CanSeek
27+ => this._SampleSource.CanSeek;
3128
32- public double 位置sec
29+ /// <summary>
30+ /// このサウンドのフォーマット。
31+ /// </summary>
32+ public WaveFormat WaveFormat
33+ => this._SampleSource.WaveFormat;
34+
35+ /// <summary>
36+ /// 現在の再生位置。sample単位。
37+ /// </summary>
38+ /// <remarks>
39+ /// 1つの <see cref="SampleSource"/>を複数の<see cref="Sound"/>インスタンスで共有できるように、
40+ /// このプロパティは<see cref="Sound"/>インスタンスごとに独立して管理する。
41+ /// </remarks>
42+ public long Position
3343 {
3444 get
35- => this._SampleToSec( this.位置sample );
36-
45+ => this._Position;
3746 set
38- {
39- long position = this._SecToSample( value );
40-
41- if( ( 0 > position ) || ( ( this.長さsample > 0 ) && ( this.長さsample <= position ) ) ) // 長さsample == 0 では例外は出さない
42- throw new ArgumentOutOfRangeException();
43-
44- this.位置sample = position;
45- }
46- }
47-
48- public bool 再生中である
49- {
50- get;
51- protected set;
52- } = false;
53-
54- public bool 再生停止中である
55- {
56- get
57- => !( this.再生中である );
58-
59- protected set
60- => this.再生中である = !( value );
47+ => this._Position = Math.Min( Math.Max( value, 0 ), this.Length );
6148 }
6249
63- public ISampleSource SampleSource
64- {
65- get
66- => this._SampleSource;
67- }
68-
69- public IWaveSource WaveSource
70- {
71- get
72- => this._WaveSource;
73- }
50+ /// <summary>
51+ /// 全体の長さ。sample単位。
52+ /// </summary>
53+ public long Length
54+ => this._SampleSource.Length;
7455
7556 /// <summary>
76- /// 音量。0.0(無音)~1.0(原音)。
57+ /// 音量。0.0(無音)~1.0(原音)~...上限なし
7758 /// </summary>
59+ /// <remarks>
60+ /// このクラスではなく、<see cref="Mixer"/>クラスから参照して使用する。
61+ /// </remarks>
7862 public float Volume
7963 {
8064 get
8165 => this._Volume;
82-
8366 set
84- => this._Volume =
85- ( 0.0f > value ) ? throw new ArgumentOutOfRangeException() :
86- ( 1.0f < value ) ? throw new ArgumentOutOfRangeException() :
87- value;
67+ => this._Volume = Math.Max( value, 0 );
8868 }
8969
90-
91- /// <summary>
92- /// Sound の生成は、コンストラクタではなく <see cref="Device.サウンドを生成する(string)"/> で行うこと。
93- /// (Device 内部で持っている Mixer への参照が必要なため。)
94- /// </summary>
95- /// <param name="path">
96- /// サウンドファイルパス
97- /// </param>
98- /// <param name="mixer">
99- /// 使用する Mixer。
100- /// </param>
101- internal Sound( string path, Mixer mixer )
70+ protected Sound( SoundDevice device )
10271 {
103- this._MixerRef = new WeakReference<Mixer>( mixer );
104- this._WaveSource = new DecodedWaveSource( path, mixer.WaveFormat );
105- this._SampleSource = this._WaveSource.ToSampleSource();
72+ Debug.Assert( null != device );
73+ this._DeviceRef = new WeakReference<SoundDevice>( device );
10674 }
107-
108- /// <summary>
109- /// Sound の生成は、コンストラクタではなく Device.CreateSound() で行うこと。
110- /// (Device 内部で持っている Mixer への参照が必要なため。)
111- /// </summary>
112- /// <param name="source">
113- /// サウンドの IWaveSource インスタンス。
114- /// </param>
115- /// <param name="mixer">
116- /// 使用する Mixer。
117- /// </param>
118- internal Sound( IWaveSource source, Mixer mixer )
75+ public Sound( SoundDevice device, ISampleSource sampleSource )
76+ : this( device )
11977 {
120- this._MixerRef = new WeakReference<Mixer>( mixer );
121- this._WaveSource = source;
122- this._SampleSource = this._WaveSource.ToSampleSource();
78+ this._SampleSource = sampleSource;
12379 }
124-
125- /// <summary>
126- /// サウンドデータを解放する。
127- /// </summary>
12880 public void Dispose()
12981 {
13082 this.Stop();
13183
132- FDKUtilities.解放する( ref this._SampleSource );
133- FDKUtilities.解放する( ref this._WaveSource );
84+ this._SampleSource?.Dispose();
85+ this._SampleSource = null;
13486
135- this._MixerRef = null;
87+ this._DeviceRef = null;
13688 }
137-
138- /// <summary>
139- /// 再生を開始する。
140- /// </summary>
141- /// <param name="再生開始位置sample">
142- /// 再生開始位置。サンプル単位。(フレーム単位じゃない。)
143- /// </param>
14489 public void Play( long 再生開始位置sample = 0 )
14590 {
146- this.位置sample = 再生開始位置sample;
147-
148- if( this._MixerRef.TryGetTarget( out Mixer mixer ) )
91+ if( this._DeviceRef.TryGetTarget( out SoundDevice device ) )
14992 {
150- mixer.AddSound( this );
151- this.再生中である = true;
93+ // SampleSource の位置を、再生開始位置へ移動。
94+ if( this._SampleSource.CanSeek )
95+ {
96+ this._Position = 再生開始位置sample;
97+ //this._SampleSource.Position = 再生開始位置sample; --> ここではまだ設定しない。Read() で設定する。
98+ }
99+ else
100+ {
101+ Log.ERROR( $"このサンプルソースの再生位置を変更することができません。既定の位置から再生を開始します。" );
102+ }
103+
104+ // ミキサーに追加(=再生開始)。
105+ device.Mixer.AddSound( this );
152106 }
153107 }
154-
155- /// <summary>
156- /// 再生を開始する。
157- /// </summary>
158- /// <param name="再生開始位置sec">
159- /// 再生開始位置。秒単位。
160- /// </param>
161108 public void Play( double 再生開始位置sec )
162- => this.Play( this._SecToSample( 再生開始位置sec ) );
109+ => this.Play( this._秒ToSample( 再生開始位置sec ) );
110+ public int Read( float[] buffer, int offset, int count )
111+ {
112+ if( this._SampleSource.Length == this._Position )
113+ return 0; // 同じ場合でも Read() が 2 とか返してきて永遠に終わらないことがあるので、ここで阻止する。
163114
164- /// <summary>
165- /// 再生を停止する。
166- /// </summary>
115+ // 同じ SampleSource を複数の Sound で共有するために、Position は Sound ごとに管理している。
116+ this._SampleSource.Position = this._Position;
117+
118+ // 読み込み。
119+ var readCount = this._SampleSource.Read( buffer, offset, count );
120+
121+ // こちら側の Position も更新。
122+ this._Position = this._SampleSource.Position;
123+
124+ return readCount;
125+ }
167126 public void Stop()
168127 {
169- if( this._MixerRef.TryGetTarget( out Mixer mixer ) )
128+ if( this._DeviceRef.TryGetTarget( out SoundDevice device ) )
170129 {
171- mixer.RemoveSound( this );
130+ device.Mixer.RemoveSound( this );
172131 }
173-
174- this.再生停止中である = true;
175132 }
176133
177-
178- private IWaveSource _WaveSource = null;
179-
134+ private WeakReference<SoundDevice> _DeviceRef = null;
180135 private ISampleSource _SampleSource = null;
181-
182- /// <summary>
183- /// この Sound が登録されているミキサーへの弱参照。
184- /// 循環参照を避けるため、強参照は持たない。
185- /// </summary>
186- private WeakReference<Mixer> _MixerRef = null;
187-
136+ private long _Position = 0;
188137 private float _Volume = 1.0f;
189138
190-
191- private long _SecToSample( double 時間sec )
139+ private long _秒ToSample( double 時間sec )
192140 {
193- var wf = this.SampleSource.WaveFormat;
141+ var wf = this._SampleSource.WaveFormat;
194142
195- long 時間sample = (long) ( 時間sec * wf.SampleRate * wf.Channels + 0.5 ); // +0.5 は四捨五入
196- 時間sample -= ( 時間sample % wf.Channels ); // チャンネル数の倍数にする。
143+ int 時間sample = (int) ( 時間sec * wf.SampleRate * wf.Channels + 0.5 ); // +0.5 で四捨五入ができる
197144
198- return 時間sample;
145+ return FDKUtilities.位置をブロック境界単位にそろえて返す( 時間sample, wf.BlockAlign );
199146 }
200-
201- private double _SampleToSec( long 時間sample )
147+ private double _SampleTo秒( long 時間sample )
202148 {
203- var wf = this.SampleSource.WaveFormat;
149+ var wf = this._SampleSource.WaveFormat;
204150
205151 return 時間sample / ( wf.Channels * wf.SampleRate );
206152 }
--- a/FDK/メディア/サウンド/WASAPI/Device.cs
+++ b/FDK/メディア/サウンド/WASAPI/SoundDevice.cs
@@ -5,34 +5,29 @@ using System.Linq;
55 using System.Runtime.InteropServices;
66 using System.Threading;
77 using CSCore;
8+using CSCore.Codecs;
89 using CSCore.CoreAudioAPI;
910 using CSCore.SoundOut;
1011 using CSCore.Win32;
1112
1213 namespace FDK.メディア.サウンド.WASAPI
1314 {
14- public class Device : IDisposable
15+ public class SoundDevice : IDisposable
1516 {
1617 public PlaybackState レンダリング状態
17- {
18- get
19- => this._レンダリング状態;
20- }
18+ => this._レンダリング状態;
2119
22- /// <summary>
23- /// デバイスの再生レイテンシ(秒単位)。
24- /// </summary>
25- public double 遅延sec
20+ public double 再生遅延sec
2621 {
2722 get;
2823 protected set;
2924 }
3025
26+ /// <summary>
27+ /// デバイスのレンダリングフォーマット。
28+ /// </summary>
3129 public WaveFormat WaveFormat
32- {
33- get
34- => this._WaveFormat;
35- }
30+ => this._WaveFormat;
3631
3732 /// <summary>
3833 /// レンダリングボリューム。
@@ -41,93 +36,151 @@ namespace FDK.メディア.サウンド.WASAPI
4136 public float 音量
4237 {
4338 get
44- => this._Mixer?.Volume ?? 1.0f;
39+ => this.Mixer.Volume;
4540
4641 set
4742 {
4843 if( ( 0.0f > value ) || ( 1.0f < value ) )
49- throw new ArgumentOutOfRangeException();
44+ throw new ArgumentOutOfRangeException( $"音量の値が、範囲(0~1)を超えています。[{value}]" );
5045
51- this._Mixer.Volume = value;
46+ this.Mixer.Volume = value;
5247 }
5348 }
5449
50+ /// <summary>
51+ /// ミキサー。
52+ /// </summary>
53+ internal Mixer Mixer
54+ {
55+ get;
56+ private set;
57+ } = null;
58+
5559
5660 /// <summary>
57- /// コンストラクタ。
61+ /// デバイスを初期化する。
5862 /// </summary>
59- /// <param name="共有モード">
60- /// true なら共有モード、false なら排他モード。
61- /// </param>
62- public Device( AudioClientShareMode 共有モード, double バッファサイズsec = 0.010, WaveFormat 希望フォーマット = null )
63+ /// <param name="共有モード">true なら共有モード、false なら排他モード。</param>
64+ public SoundDevice( AudioClientShareMode 共有モード, double バッファサイズsec = 0.010, WaveFormat 希望フォーマット = null )
6365 {
6466 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
6567 {
68+ this._レンダリング状態 = PlaybackState.Stopped;
6669 this._共有モード = 共有モード;
70+ this.再生遅延sec = バッファサイズsec;
6771
68- this.遅延sec = バッファサイズsec;
69- this._レンダリング状態 = PlaybackState.Stopped;
72+ lock( this._スレッド間同期 )
73+ {
74+ if( this._レンダリング状態 != PlaybackState.Stopped )
75+ throw new InvalidOperationException( "WASAPI のレンダリングを停止しないまま初期化することはできません。" );
76+ if( null != this._レンダリングスレッド )
77+ throw new Exception( "レンダリングスレッドがすでに起動しています。" );
7078
71- this._初期化する( 希望フォーマット );
72- this.レンダリングを開始する();
79+ this._解放する();
7380
74- // ログ表示
75- var format = (this.WaveFormat is WaveFormatExtensible wfx) ?
76- $"{wfx.WaveFormatTag}[{AudioSubTypes.EncodingFromSubType( wfx.SubFormat )}], {wfx.SampleRate}Hz, {wfx.Channels}ch, {wfx.BitsPerSample}bits" :
77- $"{this.WaveFormat.WaveFormatTag}, {this.WaveFormat.SampleRate}Hz, {this.WaveFormat.Channels}ch, {this.WaveFormat.BitsPerSample}bits";
81+ // MMDevice を取得する。
82+ this._MMDevice = MMDeviceEnumerator.DefaultAudioEndpoint(
83+ DataFlow.Render, // 方向 ... 書き込み
84+ Role.Console ); // 用途 ... ゲーム、システム通知音、音声命令
7885
79- Log.Info( $"WASAPIデバイスを初期化しました。({this._共有モード}, {this.遅延sec * 1000.0}ms, {format})" );
80- }
81- }
86+ // AudioClient を取得する。
87+ this._AudioClient = AudioClient.FromMMDevice( this._MMDevice );
8288
83- /// <summary>
84- /// メディアファイル(動画、音声)からサウンドインスタンスを生成して返す。
85- /// </summary>
86- public Sound サウンドを生成する( string path )
87- {
88- return new Sound( path, this._Mixer );
89- }
89+ // フォーマットを決定する。
90+ this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ) ??
91+ throw new NotSupportedException( "サポート可能な WaveFormat が見つかりませんでした。" );
9092
91- /// <summary>
92- /// IWaveSource からサウンドインスタンスを生成して返す。
93- /// </summary>
94- public Sound サウンドを生成する( IWaveSource source )
95- {
96- return new Sound( source, this._Mixer );
93+ // AudioClient を初期化する。
94+ try
95+ {
96+ long 期間100ns = ( this._共有モード == AudioClientShareMode.Shared ) ?
97+ this._AudioClient.DefaultDevicePeriod : // 共有モードの場合、遅延を既定値に設定する。
98+ FDKUtilities.変換_sec単位から100ns単位へ( this.再生遅延sec ); // 排他モードの場合、コンストラクタで指定された値。
99+
100+ this._AudioClientを初期化する( 期間100ns );
101+ }
102+ catch( CoreAudioAPIException e )
103+ {
104+ // 排他モードかつイベント駆動 の場合、この例外が返されることがある。
105+ // この場合、バッファサイズを調整して再度初期化する。
106+ if( e.ErrorCode == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED )
107+ {
108+ int サイズframe = this._AudioClient.GetBufferSize(); // アライメント済みサイズが取得できる。
109+ this.再生遅延sec = (double) サイズframe / this._WaveFormat.SampleRate;
110+ long 期間100ns = FDKUtilities.変換_sec単位から100ns単位へ( this.再生遅延sec );
111+
112+ this._AudioClientを初期化する( 期間100ns ); // 再度初期化。それでも例外なら知らん。
113+ }
114+ else
115+ {
116+ throw;
117+ }
118+ }
119+
120+ // デバイスの遅延を取得。
121+ //int バッファのフレーム数 = this._AudioClient.GetBufferSize();
122+ //this.再生遅延sec = (double) バッファのフレーム数 / this._WaveFormat.SampleRate; // 例: 1056[frames] ÷ 48000[frames/sec] = 0.022[sec]
123+ // --> 取得しなくても、自分が指定した 期間100ns が採用されている。
124+
125+ // イベント駆動に使うイベントを生成し、AudioClient へ登録する。
126+ this._レンダリングイベント = new EventWaitHandle( false, EventResetMode.AutoReset );
127+ this._AudioClient.SetEventHandle( this._レンダリングイベント.SafeWaitHandle.DangerousGetHandle() );
128+
129+ // その他の WASAPI インターフェースを取得する。
130+ this._AudioRenderClient = AudioRenderClient.FromAudioClient( this._AudioClient );
131+ this._AudioClock = AudioClock.FromAudioClient( this._AudioClient );
132+
133+ // ミキサーを生成する。
134+ this.Mixer = new Mixer( this._WaveFormat );
135+ }
136+
137+ this.レンダリングを開始する();
138+
139+ // todo: 追加のコーデックを登録。
140+ //CodecFactory.Instance.Register( "ogg-vorbis", new CodecFactoryEntry( ( s ) => new NVorbisSampleSource( s ).ToWaveSource(), ".ogg" ) );
141+
142+ // 完了。
143+ var format = ( this.WaveFormat is WaveFormatExtensible wfx ) ?
144+ $"{wfx.WaveFormatTag}[{AudioSubTypes.EncodingFromSubType( wfx.SubFormat )}], {wfx.SampleRate}Hz, {wfx.Channels}ch, {wfx.BitsPerSample}bits" :
145+ $"{this.WaveFormat.WaveFormatTag}, {this.WaveFormat.SampleRate}Hz, {this.WaveFormat.Channels}ch, {this.WaveFormat.BitsPerSample}bits";
146+
147+ Log.Info( $"WASAPIデバイスを初期化しました。({this._共有モード}, {this.再生遅延sec * 1000.0}ms, {format})" );
148+ }
97149 }
98150
99151 /// <summary>
100152 /// ミキサーの出力を開始する。
101- /// 以降、ミキサーに Sound を追加すれば、自動的に再生される。
153+ /// 以降、ミキサーに Sound を追加すれば自動的に再生され、再生が完了した Sound は自動的にミキサーから削除される。
102154 /// </summary>
103155 public void レンダリングを開始する()
104156 {
157+ var 現在の状態 = PlaybackState.Stopped;
105158 lock( this._スレッド間同期 )
159+ 現在の状態 = this._レンダリング状態;
160+
161+ switch( 現在の状態 )
106162 {
107- switch( this._レンダリング状態 )
108- {
109- case PlaybackState.Paused:
110- this.レンダリングを再開する(); // Resume する。
111- break;
163+ case PlaybackState.Paused:
164+ this.レンダリングを再開する(); // Resume する。
165+ break;
112166
113- case PlaybackState.Stopped:
114- using( var 起動完了通知 = new AutoResetEvent( false ) )
115- {
116- // スレッドがすでに終了していることを確認する。
117- this._レンダリングスレッド?.Join();
118-
119- // レンダリングスレッドを起動する。
120- this._レンダリングスレッド = new Thread( this._レンダリングスレッドエントリ ) {
121- Name = "WASAPI Playback",
122- Priority = ThreadPriority.AboveNormal, // 標準よりやや上
123- };
124- this._レンダリングスレッド.Start( 起動完了通知 );
125-
126- // スレッドからの起動完了通知を待つ。
127- 起動完了通知.WaitOne();
128- }
129- break;
130- }
167+ case PlaybackState.Stopped:
168+ using( var 起動完了通知 = new AutoResetEvent( false ) )
169+ {
170+ Debug.Assert( ( null == this._レンダリングスレッド ), "レンダリングスレッドがすでに起動しています。" );
171+
172+ // レンダリングスレッドを起動する。
173+ this._レンダリングスレッド = new Thread( this._レンダリングスレッドエントリ ) {
174+ Name = "WASAPI Playback",
175+ Priority = ThreadPriority.AboveNormal, // 標準よりやや上
176+ };
177+ this._レンダリングスレッド.Start( 起動完了通知 );
178+
179+ // スレッドからの起動完了通知を待つ。
180+ 起動完了通知.WaitOne();
181+ Log.Info( "WASAPIのレンダリングスレッドを起動しました。" );
182+ }
183+ break;
131184 }
132185 }
133186
@@ -143,15 +196,21 @@ namespace FDK.メディア.サウンド.WASAPI
143196 {
144197 // レンダリングスレッドに終了を通知し、その終了を待つ。
145198 this._レンダリング状態 = PlaybackState.Stopped;
146- this._レンダリングスレッド.Join();
147- this._レンダリングスレッド = null;
148- Log.Info( "WASAPIのレンダリングを停止しました。" );
149199 }
150200 else
151201 {
152202 Log.WARNING( "WASAPIのレンダリングを停止しようとしましたが、すでに停止しています。" );
203+ return;
153204 }
154205 }
206+
207+ // lock を外してから Join しないとデッドロックするので注意。
208+ if( null != this._レンダリングスレッド )
209+ {
210+ this._レンダリングスレッド.Join();
211+ this._レンダリングスレッド = null;
212+ Log.Info( "WASAPIのレンダリングを停止しました。" );
213+ }
155214 }
156215
157216 /// <summary>
@@ -201,175 +260,56 @@ namespace FDK.メディア.サウンド.WASAPI
201260 /// <summary>
202261 /// 現在のデバイス位置を返す[秒]。
203262 /// </summary>
204- /// <returns>
205- /// エラー時は double.NaN 。
206- /// </returns>
207263 public double GetDevicePosition()
208264 {
209265 lock( this._スレッド間同期 )
210266 {
211267 this.GetClock( out long position, out long qpcPosition, out long frequency );
212268
213- return ( 0 != frequency ) ?
214- ( ( double ) position / frequency ) : double.NaN;
269+ return ( (double) position / frequency );
215270 }
216271 }
217272
218-
219- ~Device()
220- {
221- this.Dispose( false );
222- }
223-
273+ /// <summary>
274+ /// 終了する。
275+ /// </summary>
224276 public void Dispose()
225277 {
226- using( Log.Block( FDKUtilities.現在のメソッド名 ) )
227- {
228- this.Dispose( true );
229- GC.SuppressFinalize( this );
230- }
231- }
232-
233- protected virtual void Dispose( bool bDisposeManaged )
234- {
235- if( !this._dispose済み )
236- {
237- lock( this._スレッド間同期 )
238- {
239- if( bDisposeManaged )
240- {
241- // (A) ここでマネージリソースを解放する。
242- this.レンダリングを停止する();
243- this._解放する();
244- }
245-
246- // (B) ここでネイティブリソースを解放する。
247-
248- // ...特にない。
249-
250- }
251-
252- this._dispose済み = true;
253- }
278+ this.レンダリングを停止する();
279+ this._解放する();
254280 }
255281
256282
257283 private volatile PlaybackState _レンダリング状態 = PlaybackState.Stopped;
258-
259284 private AudioClientShareMode _共有モード;
260-
261285 private WaveFormat _WaveFormat = null;
262-
263286 private AudioClock _AudioClock = null;
264-
265287 private AudioRenderClient _AudioRenderClient = null;
266-
267288 private AudioClient _AudioClient = null;
268-
269289 private MMDevice _MMDevice = null;
270290
271291 private Thread _レンダリングスレッド = null;
272-
273292 private EventWaitHandle _レンダリングイベント = null;
274-
275- private Mixer _Mixer = null;
276-
277- private bool _dispose済み = false;
278-
279293 private readonly object _スレッド間同期 = new object();
280294
281-
282- /// <summary>
283- /// サウンドデバイスリソースとミキサーを生成する。
284- /// </summary>
285- private void _初期化する( WaveFormat 希望フォーマット )
295+ private void _AudioClientを初期化する( long 期間100ns )
286296 {
287- lock( this._スレッド間同期 )
288- {
289- if( this._レンダリング状態 != PlaybackState.Stopped )
290- throw new InvalidOperationException( "WASAPI のレンダリングを停止しないまま初期化することはできません。" );
291-
292- // レンダリングスレッドが存在しているなら、終了するのを待つ。
293- this._レンダリングスレッド?.Join();
294-
295- this._解放する();
296-
297- // MMDevice を取得する。
298- this._MMDevice = MMDeviceEnumerator.DefaultAudioEndpoint(
299- DataFlow.Render, // 方向 ... 書き込み
300- Role.Console ); // 用途 ... ゲーム、システム通知音、音声命令
301-
302- // AudioClient を取得する。
303- this._AudioClient = AudioClient.FromMMDevice( this._MMDevice );
304-
305- // フォーマットを決定する。
306- this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ) ??
307- throw new NotSupportedException( "サポート可能な WaveFormat が見つかりませんでした。" );
308-
309- // AudioClient を初期化する。
310- try
311- {
312- long 期間100ns = ( this._共有モード == AudioClientShareMode.Shared ) ?
313- this._AudioClient.DefaultDevicePeriod : // 共有モードの場合、遅延を既定値に設定する。
314- FDKUtilities.変換_sec単位から100ns単位へ( this.遅延sec ); // 排他モードの場合、コンストラクタで指定された値。
315-
316- _AudioClientを初期化する( 期間100ns );
317- }
318- catch( CoreAudioAPIException e )
319- {
320- // 排他モードかつイベント駆動 の場合、この例外が返されることがある。
321- // この場合、バッファサイズを調整して再度初期化する。
322- if( e.ErrorCode == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED )
323- {
324- int サイズframe = this._AudioClient.GetBufferSize(); // アライメント済みサイズが取得できる。
325- this.遅延sec = (double) サイズframe / this._WaveFormat.SampleRate;
326- long 期間100ns = FDKUtilities.変換_sec単位から100ns単位へ( this.遅延sec );
327-
328- _AudioClientを初期化する( 期間100ns ); // 再度初期化。それでも例外なら知らん。
329- }
330- else
331- {
332- throw;
333- }
334- }
335-
336- // デバイスの遅延を取得。
337- //int バッファのフレーム数 = this._AudioClient.GetBufferSize();
338- //this.遅延sec = (double) バッファのフレーム数 / this._WaveFormat.SampleRate; // 例: 1056[frames] ÷ 48000[frames/sec] = 0.022[sec]
339- // --> 取得しなくても、自分が指定した 期間100ns が採用されている。
340-
341- // イベント駆動に使うイベントを生成し、AudioClient へ登録する。
342- this._レンダリングイベント = new EventWaitHandle( false, EventResetMode.AutoReset );
343- this._AudioClient.SetEventHandle( this._レンダリングイベント.SafeWaitHandle.DangerousGetHandle() );
344-
345- // その他の WASAPI インターフェースを取得する。
346- this._AudioRenderClient = AudioRenderClient.FromAudioClient( this._AudioClient );
347- this._AudioClock = AudioClock.FromAudioClient( this._AudioClient );
348-
349- // ミキサーを生成する。
350- this._Mixer = new Mixer( this._WaveFormat );
351- }
352-
353- // 以下、ローカル関数。
354-
355- void _AudioClientを初期化する( long 期間100ns )
356- {
357- this._AudioClient.Initialize(
358- this._共有モード,
359- AudioClientStreamFlags.StreamFlagsEventCallback, // イベント駆動で固定。
360- 期間100ns,
361- 期間100ns, // イベント駆動の場合、Periodicity は BufferDuration と同じ値でなければならない。
362- this._WaveFormat,
363- Guid.Empty );
364- }
297+ this._AudioClient.Initialize(
298+ this._共有モード,
299+ AudioClientStreamFlags.StreamFlagsEventCallback, // イベント駆動で固定。
300+ 期間100ns,
301+ 期間100ns, // イベント駆動の場合、Periodicity は BufferDuration と同じ値でなければならない。
302+ this._WaveFormat,
303+ Guid.Empty );
365304 }
366305
367- /// <summary>
368- /// サウンドデバイスリソースとミキサーを解放する。
369- /// </summary>
370306 private void _解放する()
371307 {
372- FDKUtilities.解放する( ref this._Mixer );
308+ Debug.Assert( null == this._レンダリングスレッド, "レンダリングスレッドが稼働しています。先に終了してください。" );
309+
310+ this.Mixer?.Dispose();
311+ this.Mixer = null;
312+
373313 FDKUtilities.解放する( ref this._AudioClock );
374314 FDKUtilities.解放する( ref this._AudioRenderClient );
375315
@@ -395,15 +335,9 @@ namespace FDK.メディア.サウンド.WASAPI
395335 /// <summary>
396336 /// 希望したフォーマットをもとに、適切なフォーマットを調べて返す。
397337 /// </summary>
398- /// <param name="waveFormat">
399- /// 希望するフォーマット
400- /// </param>
401- /// <param name="audioClient">
402- /// AudioClient インスタンス。Initialize 前でも可。
403- /// </param>
404- /// <returns>
405- /// 適切なフォーマット。見つからなかったら null。
406- /// </returns>
338+ /// <param name="waveFormat">希望するフォーマット</param>
339+ /// <param name="audioClient">AudioClient インスタンス。Initialize 前でも可。</param>
340+ /// <returns>適切なフォーマット。見つからなかったら null。</returns>
407341 private WaveFormat _適切なフォーマットを調べて返す( WaveFormat waveFormat )
408342 {
409343 Trace.Assert( null != this._AudioClient );
@@ -476,7 +410,7 @@ namespace FDK.メディア.サウンド.WASAPI
476410
477411 return 最終的に決定されたフォーマット;
478412 }
479-
413+
480414 /// <summary>
481415 /// WASAPIイベント駆動スレッドのエントリ。
482416 /// </summary>
@@ -497,7 +431,7 @@ namespace FDK.メディア.サウンド.WASAPI
497431
498432 // このスレッドの MMCSS 型を登録する。
499433 string mmcssType;
500- switch( this.遅延sec )
434+ switch( this.再生遅延sec )
501435 {
502436 case double 遅延 when( 0.0105 > 遅延 ):
503437 mmcssType = "Pro Audio";
@@ -511,11 +445,12 @@ namespace FDK.メディア.サウンド.WASAPI
511445 mmcssType = "Audio";
512446 break;
513447 }
514- 元のMMCSS特性 = Device.AvSetMmThreadCharacteristics( mmcssType, out int taskIndex );
448+ 元のMMCSS特性 = SoundDevice.AvSetMmThreadCharacteristics( mmcssType, out int taskIndex );
515449
516450 // AudioClient を開始する。
517451 this._AudioClient.Start();
518- this._レンダリング状態 = PlaybackState.Playing;
452+ lock( this._スレッド間同期 )
453+ this._レンダリング状態 = PlaybackState.Playing;
519454
520455 // 起動完了を通知する。
521456 ( 起動完了通知 as EventWaitHandle )?.Set();
@@ -527,23 +462,33 @@ namespace FDK.メディア.サウンド.WASAPI
527462 //----------------
528463 var イベントs = new WaitHandle[] { this._レンダリングイベント };
529464
530- while( this.レンダリング状態 != PlaybackState.Stopped )
465+ while( true )
531466 {
467+ // 終了?
468+ var 現在の状態 = PlaybackState.Playing;
469+ lock( this._スレッド間同期 )
470+ 現在の状態 = this._レンダリング状態;
471+
472+ if( 現在の状態 == PlaybackState.Stopped )
473+ break; // 終わる。
474+
532475 // イベントs[] のいずれかのイベントが発火する(かタイムアウトする)まで待つ。
533476 int イベント番号 = WaitHandle.WaitAny(
534477 waitHandles: イベントs,
535- millisecondsTimeout: (int) ( 3000.0 * this.遅延sec ), // 適正値は レイテンシ×3 [ms] (MSDNより)
478+ millisecondsTimeout: (int) ( 3000.0 * this.再生遅延sec ), // 適正値は レイテンシ×3 [ms] (MSDNより)
536479 exitContext: false );
537480
538481 // タイムアウトした=まだどのイベントもきてない。
539482 if( イベント番号 == WaitHandle.WaitTimeout )
540483 continue;
541484
542- if( this.レンダリング状態 == PlaybackState.Playing )
485+ lock( this._スレッド間同期 )
543486 {
487+ if( this.レンダリング状態 != PlaybackState.Playing )
488+ continue;
489+
544490 int 未再生数frame = ( this._共有モード == AudioClientShareMode.Exclusive ) ? 0 : this._AudioClient.GetCurrentPadding();
545491 int 空きframe = バッファサイズframe - 未再生数frame;
546-
547492 if( 5 >= 空きframe )
548493 continue; // あまりに空きが小さいなら、何もせずスキップする。
549494
@@ -555,9 +500,9 @@ namespace FDK.メディア.サウンド.WASAPI
555500 continue; // サンプルなし。スキップ。
556501
557502 // ミキサーからの出力(32bit-float)をバッファに取得する。
558- int 読み込んだサイズsample = this._Mixer.Read( バッファ, 0, 読み込むサイズsample );
503+ int 読み込んだサイズsample = this.Mixer.Read( バッファ, 0, 読み込むサイズsample );
559504
560- // バッファのデータを変換しつつ、AudioRenderClient へ出力する。
505+ // バッファのデータをレンダリングフォーマットに変換しつつ、AudioRenderClient へ出力する。
561506 IntPtr bufferPtr = this._AudioRenderClient.GetBuffer( 空きframe );
562507 try
563508 {
@@ -669,16 +614,16 @@ namespace FDK.メディア.サウンド.WASAPI
669614
670615 #region " 終了。"
671616 //----------------
672- // このスレッドの MMCSS 特性を元に戻す。
673- Device.AvRevertMmThreadCharacteristics( 元のMMCSS特性 );
674- 元のMMCSS特性 = IntPtr.Zero;
675-
676- // ハードウェアの再生が終わるくらいまで、少し待つ。
677- Thread.Sleep( (int) ( this.遅延sec * 1000 / 2 ) );
678-
679617 // AudioClient を停止する。
680618 this._AudioClient.Stop();
681619 this._AudioClient.Reset();
620+
621+ // ハードウェアの再生が終わるくらいまで、少し待つ。
622+ Thread.Sleep( (int) ( this.再生遅延sec * 1000 / 2 ) );
623+
624+ // このスレッドの MMCSS 特性を元に戻す。
625+ SoundDevice.AvRevertMmThreadCharacteristics( 元のMMCSS特性 );
626+ 元のMMCSS特性 = IntPtr.Zero;
682627 //----------------
683628 #endregion
684629 }
@@ -692,7 +637,7 @@ namespace FDK.メディア.サウンド.WASAPI
692637 #region " 完了。"
693638 //----------------
694639 if( 元のMMCSS特性 != IntPtr.Zero )
695- Device.AvRevertMmThreadCharacteristics( 元のMMCSS特性 );
640+ SoundDevice.AvRevertMmThreadCharacteristics( 元のMMCSS特性 );
696641
697642 // 失敗時を想定して。
698643 ( 起動完了通知 as EventWaitHandle )?.Set();
@@ -741,7 +686,6 @@ namespace FDK.メディア.サウンド.WASAPI
741686 }
742687 }
743688
744-
745689 #region " Win32 "
746690 //----------------
747691 private const int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
--- a/FDK/メディア/サウンド/WASAPI/SoundTimer.cs
+++ b/FDK/メディア/サウンド/WASAPI/SoundTimer.cs
@@ -17,46 +17,59 @@ namespace FDK.メディア.サウンド.WASAPI
1717 {
1818 lock( this._スレッド間同期 )
1919 {
20- return ( 0 < this._停止回数 ) ?
21- this._停止位置sec - this._開始位置sec : // 一時停止中
22- this._Device.GetDevicePosition() - this._開始位置sec; // 稼働中
20+ if( 0 < this._停止回数 )
21+ {
22+ // 一時停止中。
23+ return ( this._停止位置sec - this._開始位置sec );
24+ }
25+ else if( this._DeviceRef.TryGetTarget( out SoundDevice device ) )
26+ {
27+ // 稼働中。
28+ return ( device.GetDevicePosition() - this._開始位置sec );
29+ }
30+ else
31+ throw new InvalidOperationException( "サウンドデバイスが無効です。" );
2332 }
2433 }
2534 }
2635
27-
28- public SoundTimer( Device device )
36+ public SoundTimer( SoundDevice device )
2937 {
30- this._Device = device;
38+ this._DeviceRef = new WeakReference<SoundDevice>( device );
3139 this.リセットする();
3240 }
33-
3441 public void Dispose()
3542 {
36- this._Device = null; // Disposeは不要
43+ lock( this._スレッド間同期 )
44+ {
45+ this._DeviceRef = null;
46+ }
3747 }
38-
3948 public void リセットする( double 新しい現在時刻sec = 0.0 )
4049 {
4150 lock( this._スレッド間同期 )
4251 {
43- this._開始位置sec = this._Device.GetDevicePosition() - 新しい現在時刻sec;
44- this._停止回数 = 0;
45- this._停止位置sec = 0;
52+ if( this._DeviceRef.TryGetTarget( out SoundDevice device ) )
53+ {
54+ this._開始位置sec = device.GetDevicePosition() - 新しい現在時刻sec;
55+ this._停止回数 = 0;
56+ this._停止位置sec = 0;
57+ }
4658 }
4759 }
48-
4960 public void 一時停止する()
5061 {
5162 lock( this._スレッド間同期 )
5263 {
53- if( 0 == this._停止回数 )
54- this._停止位置sec = this._Device.GetDevicePosition();
64+ if( this._DeviceRef.TryGetTarget( out SoundDevice device ) )
65+ {
66+ if( 0 == this._停止回数 )
67+ this._停止位置sec = device.GetDevicePosition();
5568
56- this._停止回数++;
69+ this._停止回数++;
70+ }
5771 }
5872 }
59-
6073 public void 再開する()
6174 {
6275 lock( this._スレッド間同期 )
@@ -68,15 +81,10 @@ namespace FDK.メディア.サウンド.WASAPI
6881 }
6982 }
7083
71-
72- private Device _Device = null;
73-
84+ private WeakReference<SoundDevice> _DeviceRef = null;
7485 private int _停止回数 = 0;
75-
7686 private double _開始位置sec = 0.0;
77-
7887 private double _停止位置sec = 0.0;
79-
8088 private readonly object _スレッド間同期 = new object();
8189 }
8290 }
--- a/SSTFEditor/メインフォーム.Designer.cs
+++ b/SSTFEditor/メインフォーム.Designer.cs
@@ -33,6 +33,9 @@
3333 this.splitContainer分割パネルコンテナ = new System.Windows.Forms.SplitContainer();
3434 this.tabControl情報タブコンテナ = new System.Windows.Forms.TabControl();
3535 this.tabPage基本情報 = new System.Windows.Forms.TabPage();
36+ this.textBoxLevel = new System.Windows.Forms.TextBox();
37+ this.trackBarLevel = new System.Windows.Forms.TrackBar();
38+ this.labelLevel = new System.Windows.Forms.Label();
3639 this.textBoxサウンド遅延ms = new System.Windows.Forms.TextBox();
3740 this.labelサウンド遅延ms = new System.Windows.Forms.Label();
3841 this.textBoxメモ = new System.Windows.Forms.TextBox();
@@ -146,6 +149,7 @@
146149 this.splitContainer分割パネルコンテナ.SuspendLayout();
147150 this.tabControl情報タブコンテナ.SuspendLayout();
148151 this.tabPage基本情報.SuspendLayout();
152+ ((System.ComponentModel.ISupportInitialize)(this.trackBarLevel)).BeginInit();
149153 ((System.ComponentModel.ISupportInitialize)(this.numericUpDownメモ用小節番号)).BeginInit();
150154 ((System.ComponentModel.ISupportInitialize)(this.pictureBox譜面パネル)).BeginInit();
151155 this.menuStripメニューバー.SuspendLayout();
@@ -182,6 +186,9 @@
182186 // tabPage基本情報
183187 //
184188 this.tabPage基本情報.BackColor = System.Drawing.SystemColors.Window;
189+ this.tabPage基本情報.Controls.Add(this.textBoxLevel);
190+ this.tabPage基本情報.Controls.Add(this.trackBarLevel);
191+ this.tabPage基本情報.Controls.Add(this.labelLevel);
185192 this.tabPage基本情報.Controls.Add(this.textBoxサウンド遅延ms);
186193 this.tabPage基本情報.Controls.Add(this.labelサウンド遅延ms);
187194 this.tabPage基本情報.Controls.Add(this.textBoxメモ);
@@ -197,6 +204,27 @@
197204 resources.ApplyResources(this.tabPage基本情報, "tabPage基本情報");
198205 this.tabPage基本情報.Name = "tabPage基本情報";
199206 //
207+ // textBoxLevel
208+ //
209+ resources.ApplyResources(this.textBoxLevel, "textBoxLevel");
210+ this.textBoxLevel.Name = "textBoxLevel";
211+ this.textBoxLevel.Validating += new System.ComponentModel.CancelEventHandler(this.textBoxLevel_Validating);
212+ this.textBoxLevel.Validated += new System.EventHandler(this.textBoxLevel_Validated);
213+ //
214+ // trackBarLevel
215+ //
216+ this.trackBarLevel.LargeChange = 50;
217+ resources.ApplyResources(this.trackBarLevel, "trackBarLevel");
218+ this.trackBarLevel.Maximum = 999;
219+ this.trackBarLevel.Name = "trackBarLevel";
220+ this.trackBarLevel.Value = 500;
221+ this.trackBarLevel.Scroll += new System.EventHandler(this.trackBarLevel_Scroll);
222+ //
223+ // labelLevel
224+ //
225+ resources.ApplyResources(this.labelLevel, "labelLevel");
226+ this.labelLevel.Name = "labelLevel";
227+ //
200228 // textBoxサウンド遅延ms
201229 //
202230 resources.ApplyResources(this.textBoxサウンド遅延ms, "textBoxサウンド遅延ms");
@@ -213,7 +241,7 @@
213241 resources.ApplyResources(this.textBoxメモ, "textBoxメモ");
214242 this.textBoxメモ.Name = "textBoxメモ";
215243 this.textBoxメモ.TextChanged += new System.EventHandler(this.textBoxメモ_TextChanged);
216- this.textBoxメモ.Leave += new System.EventHandler(this.textBoxメモ_Leave);
244+ this.textBoxメモ.Validated += new System.EventHandler(this.textBoxメモ_Validated);
217245 //
218246 // labelメモ用小節番号
219247 //
@@ -257,7 +285,7 @@
257285 resources.ApplyResources(this.textBox説明, "textBox説明");
258286 this.textBox説明.Name = "textBox説明";
259287 this.textBox説明.TextChanged += new System.EventHandler(this.textBox説明_TextChanged);
260- this.textBox説明.Leave += new System.EventHandler(this.textBox説明_Leave);
288+ this.textBox説明.Validated += new System.EventHandler(this.textBox説明_Validated);
261289 //
262290 // label曲名
263291 //
@@ -269,7 +297,7 @@
269297 resources.ApplyResources(this.textBox曲名, "textBox曲名");
270298 this.textBox曲名.Name = "textBox曲名";
271299 this.textBox曲名.TextChanged += new System.EventHandler(this.textBox曲名_TextChanged);
272- this.textBox曲名.Leave += new System.EventHandler(this.textBox曲名_Leave);
300+ this.textBox曲名.Validated += new System.EventHandler(this.textBox曲名_Validated);
273301 //
274302 // label現在のチップ種別
275303 //
@@ -1019,6 +1047,7 @@
10191047 this.tabControl情報タブコンテナ.ResumeLayout(false);
10201048 this.tabPage基本情報.ResumeLayout(false);
10211049 this.tabPage基本情報.PerformLayout();
1050+ ((System.ComponentModel.ISupportInitialize)(this.trackBarLevel)).EndInit();
10221051 ((System.ComponentModel.ISupportInitialize)(this.numericUpDownメモ用小節番号)).EndInit();
10231052 ((System.ComponentModel.ISupportInitialize)(this.pictureBox譜面パネル)).EndInit();
10241053 this.menuStripメニューバー.ResumeLayout(false);
@@ -1143,6 +1172,9 @@
11431172 private System.Windows.Forms.TextBox textBoxサウンド遅延ms;
11441173 private System.Windows.Forms.Label labelサウンド遅延ms;
11451174 private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemガイド間隔36分;
1175+ private System.Windows.Forms.TextBox textBoxLevel;
1176+ private System.Windows.Forms.TrackBar trackBarLevel;
1177+ private System.Windows.Forms.Label labelLevel;
11461178 }
11471179 }
11481180
--- a/SSTFEditor/メインフォーム.cs
+++ b/SSTFEditor/メインフォーム.cs
@@ -1485,9 +1485,11 @@ namespace SSTFEditor
14851485 //-----------------
14861486 this._次のプロパティ変更がUndoRedoリストに載らないようにする();
14871487 this.textBox曲名.Clear();
1488-
1489- this._次のプロパティ変更がUndoRedoリストに載らないようにする();
1488+ this.textBoxLevel.Text = "5.00";
1489+ this.trackBarLevel.Value = 500;
14901490 this.textBox説明.Clear();
1491+ this.numericUpDownメモ用小節番号.Value = 0;
1492+ this.textBoxメモ.Clear();
14911493 //-----------------
14921494 #endregion
14931495 #region " Viewer 再生 "
@@ -1619,14 +1621,25 @@ namespace SSTFEditor
16191621 ( from file in Directory.GetFiles( Path.GetDirectoryName( this._作業フォルダパス ) )
16201622 where スコア.背景動画のデフォルト拡張子s.Any( 拡張子名 => ( Path.GetExtension( file ).ToLower() == 拡張子名 ) )
16211623 select file ).FirstOrDefault(); // 複数あったら、最初に見つけたほうを採用。1つも見つからなければ null。
1624+
16221625 this._次のプロパティ変更がUndoRedoリストに載らないようにする();
16231626 this.textBox曲名.Text = 譜面.SSTFormatScore.曲名;
1627+
1628+ this._次のプロパティ変更がUndoRedoリストに載らないようにする();
1629+ this.textBoxLevel.Text = 譜面.SSTFormatScore.難易度.ToString( "0.00" );
1630+
1631+ this._次のプロパティ変更がUndoRedoリストに載らないようにする();
1632+ this.trackBarLevel.Value = Math.Min( Math.Max( (int) ( 譜面.SSTFormatScore.難易度 * 100 ), 0 ), 999 );
1633+
16241634 this._次のプロパティ変更がUndoRedoリストに載らないようにする();
16251635 this.textBox説明.Text = 譜面.SSTFormatScore.説明文;
1636+
16261637 this._次のプロパティ変更がUndoRedoリストに載らないようにする();
16271638 this.textBox背景動画.Text = Path.GetFileName( 譜面.SSTFormatScore.背景動画ファイル名 );
1639+
16281640 this._次のプロパティ変更がUndoRedoリストに載らないようにする();
16291641 this.textBoxメモ.Text = ( this.譜面.SSTFormatScore.dicメモ.ContainsKey( 0 ) ) ? this.譜面.SSTFormatScore.dicメモ[ 0 ] : "";
1642+
16301643 this._次のプロパティ変更がUndoRedoリストに載らないようにする();
16311644 this.textBoxサウンド遅延ms.Text = this.譜面.SSTFormatScore.サウンドデバイス遅延ms.ToString();
16321645
@@ -2707,13 +2720,11 @@ namespace SSTFEditor
27072720 // スコアには随時保存する。
27082721 譜面.SSTFormatScore.曲名 = this.textBox曲名.Text;
27092722 }
2710-
2711- protected void textBox曲名_Leave( object sender, EventArgs e )
2723+ protected void textBox曲名_Validated( object sender, EventArgs e )
27122724 {
27132725 // 最新の UndoRedoセル の所有権を放棄する。
27142726 this.UndoRedo管理.Undoするセルを取得して返す_見るだけ()?.所有権を放棄する( this.textBox曲名 );
27152727 }
2716-
27172728 private string textBox曲名_以前の値 = "";
27182729
27192730 protected void textBox説明_TextChanged( object sender, EventArgs e )
@@ -2772,13 +2783,11 @@ namespace SSTFEditor
27722783 // スコアには随時保存する。
27732784 譜面.SSTFormatScore.説明文 = this.textBox説明.Text;
27742785 }
2775-
2776- protected void textBox説明_Leave( object sender, EventArgs e )
2786+ protected void textBox説明_Validated( object sender, EventArgs e )
27772787 {
27782788 // 最新 UndoRedoセル の所有権を放棄する。
27792789 this.UndoRedo管理.Undoするセルを取得して返す_見るだけ()?.所有権を放棄する( this.textBox説明 );
27802790 }
2781-
27822791 private string textBox説明_以前の値 = "";
27832792
27842793 protected void textBoxメモ_TextChanged( object sender, EventArgs e )
@@ -2921,8 +2930,7 @@ namespace SSTFEditor
29212930 //-----------------
29222931 #endregion
29232932 }
2924-
2925- protected void textBoxメモ_Leave( object sender, EventArgs e )
2933+ protected void textBoxメモ_Validated( object sender, EventArgs e )
29262934 {
29272935 // 最新 UndoRedoセル の所有権を放棄する。
29282936 this.UndoRedo管理.Undoするセルを取得して返す_見るだけ()?.所有権を放棄する( this.textBoxメモ );
@@ -2930,7 +2938,6 @@ namespace SSTFEditor
29302938 // 小節メモをリフレッシュ。
29312939 this.splitContainer分割パネルコンテナ.Panel2.Refresh();
29322940 }
2933-
29342941 private string textBoxメモ_以前の値 = "";
29352942
29362943 protected void numericUpDownメモ用小節番号_ValueChanged( object sender, EventArgs e )
@@ -2944,7 +2951,108 @@ namespace SSTFEditor
29442951 this.textBoxメモ.Text = "";
29452952 this._次のプロパティ変更がUndoRedoリストに載るようにする();
29462953 }
2954+
2955+ protected void trackBarLevel_Scroll( object sender, EventArgs e )
2956+ {
2957+ // テキストボックスに数値を反映。(0~999 → 0.00~9.99 に変換)
2958+ this.textBoxLevel.Text = ( this.trackBarLevel.Value / 100.0 ).ToString( "0.00" );
2959+
2960+ // テキストボックスに Validation を起こさせる。
2961+ this.textBoxLevel.Focus();
2962+ this.trackBarLevel.Focus();
2963+ }
2964+
2965+ protected void textBoxLevel_Validating( object sender, System.ComponentModel.CancelEventArgs e )
2966+ {
2967+ // 入力値が 0.00 ~ 9.99 の小数であるか確認する。
2968+ if( float.TryParse( this.textBoxLevel.Text, out float val ) )
2969+ {
2970+ // 値を丸める
2971+ if( val < 0.0f )
2972+ {
2973+ this.textBoxLevel.Text = "0.00";
2974+ val = 0.0f;
2975+ }
2976+ else if( val > 9.99f )
2977+ {
2978+ this.textBoxLevel.Text = "9.99";
2979+ val = 9.99f;
2980+ }
2981+
2982+ #region " この変更が Undo/Redo したことによるものではない場合、UndoRedoセルを追加 or 修正する。"
2983+ //-----------------
2984+ if( false == UndoRedo.管理.UndoRedoした直後である )
2985+ {
2986+ // 最新のセルの所有者が自分?
2987+ var cell = this.UndoRedo管理.Undoするセルを取得して返す_見るだけ();
2988+
2989+ if( ( null != cell ) && cell.所有権がある( this.textBoxLevel ) )
2990+ {
2991+ // (A) 所有者である → 最新のセルの "変更後の値" を現在のコントロールの値に更新する。
2992+ ( (UndoRedo.セル<string>) cell ).変更後の値 = this.textBoxLevel.Text;
2993+ }
2994+ else
2995+ {
2996+ // (B) 所有者ではない → 以下のようにセルを新規追加する。
2997+ // "変更前の値" ← 以前の値
2998+ // "変更後の値" ← 現在の値
2999+ // "所有者ID" ← 対象となるコンポーネントオブジェクト
3000+ var cc = new UndoRedo.セル<string>(
3001+ 所有者ID: this.textBoxLevel,
3002+ Undoアクション: ( 変更対象, 変更前, 変更後, 任意1, 任意2 ) => {
3003+ this._タブを選択する( タブ種別.基本情報 );
3004+ this._次のプロパティ変更がUndoRedoリストに載らないようにする();
3005+ this.textBoxLevel.Text = 変更前;
3006+ this.textBoxLevel.Focus();
3007+ this.trackBarLevel.Value = ( string.IsNullOrEmpty( 変更前 ) ) ? 0 : (int) ( float.Parse( 変更前 ) * 100 );
3008+ },
3009+ Redoアクション: ( 変更対象, 変更前, 変更後, 任意1, 任意2 ) => {
3010+ this._タブを選択する( タブ種別.基本情報 );
3011+ this._次のプロパティ変更がUndoRedoリストに載らないようにする();
3012+ this.textBoxLevel.Text = 変更後;
3013+ this.textBoxLevel.Focus();
3014+ this.trackBarLevel.Value = ( string.IsNullOrEmpty( 変更後 ) ) ? 0 : (int) ( float.Parse( 変更後 ) * 100 );
3015+ },
3016+ 変更対象: null,
3017+ 変更前の値: this.textBoxLevel_以前の値,
3018+ 変更後の値: this.textBoxLevel.Text,
3019+ 任意1: null,
3020+ 任意2: null );
3021+
3022+ this.UndoRedo管理.セルを追加する( cc );
3023+
3024+ // Undo ボタンを有効にする。
3025+ this.UndoRedo用GUIのEnabledを設定する();
3026+ }
3027+ }
3028+ //-----------------
3029+ #endregion
3030+
3031+ this.textBoxLevel_以前の値 = this.textBoxLevel.Text; // 以前の値 ← 現在の値
3032+ UndoRedo.管理.UndoRedoした直後である = false;
3033+ this.未保存である = true;
3034+
3035+ // トラックバーに反映する。
3036+ this.trackBarLevel.Value = (int) ( val * 100 );
3037+
3038+ // スコアに反映する。
3039+ 譜面.SSTFormatScore.難易度 = val;
3040+ }
3041+ else
3042+ {
3043+ e.Cancel = true;
3044+ this.textBoxLevel.Text = ( this.trackBarLevel.Value / 100 ).ToString( "0.00" );
3045+ this.textBoxLevel.Select();
3046+ }
3047+ }
3048+ protected void textBoxLevel_Validated( object sender, EventArgs e )
3049+ {
3050+ // 最新の UndoRedoセル の所有権を放棄する。
3051+ this.UndoRedo管理.Undoするセルを取得して返す_見るだけ()?.所有権を放棄する( this.textBoxLevel );
3052+ }
3053+ private string textBoxLevel_以前の値 = "5.00";
29473054 //-----------------
29483055 #endregion
3056+
29493057 }
29503058 }
--- a/SSTFEditor/メインフォーム.ja-JP.resx
+++ b/SSTFEditor/メインフォーム.ja-JP.resx
@@ -117,10 +117,26 @@
117117 <resheader name="writer">
118118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119 </resheader>
120+ <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
121+ <data name="labelCurrentChipType.Size" type="System.Drawing.Size, System.Drawing">
122+ <value>132, 24</value>
123+ </data>
124+ <data name="labelCurrentChipType.Text" xml:space="preserve">
125+ <value>現在のレーンのチップ種別:
126+(SPACEキーで変更)</value>
127+ </data>
128+ <data name="tabPage基本情報.Text" xml:space="preserve">
129+ <value>基本情報</value>
130+ </data>
131+ <data name="labelLevel.Size" type="System.Drawing.Size, System.Drawing">
132+ <value>34, 12</value>
133+ </data>
134+ <data name="labelLevel.Text" xml:space="preserve">
135+ <value>レベル</value>
136+ </data>
120137 <data name="labelサウンド遅延ms.Text" xml:space="preserve">
121138 <value>サウンド遅延 [ms] (自動検出)</value>
122139 </data>
123- <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
124140 <data name="labelメモ用小節番号.Location" type="System.Drawing.Point, System.Drawing">
125141 <value>147, 214</value>
126142 </data>
@@ -154,15 +170,11 @@
154170 <data name="label曲名.Text" xml:space="preserve">
155171 <value>曲名</value>
156172 </data>
157- <data name="tabPage基本情報.Text" xml:space="preserve">
158- <value>基本情報</value>
159- </data>
160- <data name="labelCurrentChipType.Size" type="System.Drawing.Size, System.Drawing">
161- <value>132, 24</value>
173+ <data name="toolStripMenuItemファイル.Size" type="System.Drawing.Size, System.Drawing">
174+ <value>67, 20</value>
162175 </data>
163- <data name="labelCurrentChipType.Text" xml:space="preserve">
164- <value>現在のレーンのチップ種別:
165-(SPACEキーで変更)</value>
176+ <data name="toolStripMenuItemファイル.Text" xml:space="preserve">
177+ <value>ファイル(&amp;F)</value>
166178 </data>
167179 <data name="toolStripMenuItem新規作成.Size" type="System.Drawing.Size, System.Drawing">
168180 <value>184, 22</value>
@@ -197,95 +209,101 @@
197209 <data name="toolStripMenuItem終了.Text" xml:space="preserve">
198210 <value>終了(&amp;X)</value>
199211 </data>
200- <data name="toolStripMenuItemファイル.Size" type="System.Drawing.Size, System.Drawing">
201- <value>66, 20</value>
212+ <data name="toolStripMenuItem編集.Size" type="System.Drawing.Size, System.Drawing">
213+ <value>57, 20</value>
202214 </data>
203- <data name="toolStripMenuItemファイル.Text" xml:space="preserve">
204- <value>ファイル(&amp;F)</value>
215+ <data name="toolStripMenuItem編集.Text" xml:space="preserve">
216+ <value>編集(&amp;E)</value>
205217 </data>
206218 <data name="toolStripMenuItem元に戻す.Size" type="System.Drawing.Size, System.Drawing">
207- <value>183, 22</value>
219+ <value>184, 22</value>
208220 </data>
209221 <data name="toolStripMenuItem元に戻す.Text" xml:space="preserve">
210222 <value>元に戻す(&amp;U)</value>
211223 </data>
212224 <data name="toolStripMenuItemやり直す.Size" type="System.Drawing.Size, System.Drawing">
213- <value>183, 22</value>
225+ <value>184, 22</value>
214226 </data>
215227 <data name="toolStripMenuItemやり直す.Text" xml:space="preserve">
216228 <value>やり直す(&amp;R)</value>
217229 </data>
218230 <data name="toolStripSeparator2.Size" type="System.Drawing.Size, System.Drawing">
219- <value>180, 6</value>
231+ <value>181, 6</value>
220232 </data>
221233 <data name="toolStripMenuItem切り取り.Size" type="System.Drawing.Size, System.Drawing">
222- <value>183, 22</value>
234+ <value>184, 22</value>
223235 </data>
224236 <data name="toolStripMenuItem切り取り.Text" xml:space="preserve">
225237 <value>切り取り(&amp;T)</value>
226238 </data>
227239 <data name="toolStripMenuItemコピー.Size" type="System.Drawing.Size, System.Drawing">
228- <value>183, 22</value>
240+ <value>184, 22</value>
229241 </data>
230242 <data name="toolStripMenuItemコピー.Text" xml:space="preserve">
231243 <value>コピー(&amp;C)</value>
232244 </data>
233245 <data name="toolStripMenuItem貼り付け.Size" type="System.Drawing.Size, System.Drawing">
234- <value>183, 22</value>
246+ <value>184, 22</value>
235247 </data>
236248 <data name="toolStripMenuItem貼り付け.Text" xml:space="preserve">
237249 <value>貼り付け(&amp;P)</value>
238250 </data>
239251 <data name="toolStripMenuItem削除.Size" type="System.Drawing.Size, System.Drawing">
240- <value>183, 22</value>
252+ <value>184, 22</value>
241253 </data>
242254 <data name="toolStripMenuItem削除.Text" xml:space="preserve">
243255 <value>削除(&amp;D)</value>
244256 </data>
245257 <data name="toolStripSeparator3.Size" type="System.Drawing.Size, System.Drawing">
246- <value>180, 6</value>
258+ <value>181, 6</value>
247259 </data>
248260 <data name="toolStripMenuItemすべて選択.Size" type="System.Drawing.Size, System.Drawing">
249- <value>183, 22</value>
261+ <value>184, 22</value>
250262 </data>
251263 <data name="toolStripMenuItemすべて選択.Text" xml:space="preserve">
252264 <value>すべて選択(&amp;A)</value>
253265 </data>
254266 <data name="toolStripSeparator4.Size" type="System.Drawing.Size, System.Drawing">
255- <value>180, 6</value>
267+ <value>181, 6</value>
256268 </data>
257269 <data name="toolStripMenuItem選択モード.Size" type="System.Drawing.Size, System.Drawing">
258- <value>183, 22</value>
270+ <value>184, 22</value>
259271 </data>
260272 <data name="toolStripMenuItem選択モード.Text" xml:space="preserve">
261273 <value>選択モード(&amp;S)</value>
262274 </data>
263275 <data name="toolStripMenuItem編集モード.Size" type="System.Drawing.Size, System.Drawing">
264- <value>183, 22</value>
276+ <value>184, 22</value>
265277 </data>
266278 <data name="toolStripMenuItem編集モード.Text" xml:space="preserve">
267279 <value>編集モード(&amp;E)</value>
268280 </data>
269281 <data name="toolStripMenuItemモード切替え.Size" type="System.Drawing.Size, System.Drawing">
270- <value>183, 22</value>
282+ <value>184, 22</value>
271283 </data>
272284 <data name="toolStripMenuItemモード切替え.Text" xml:space="preserve">
273285 <value>モード切替え(&amp;M)</value>
274286 </data>
275287 <data name="toolStripSeparator5.Size" type="System.Drawing.Size, System.Drawing">
276- <value>180, 6</value>
288+ <value>181, 6</value>
277289 </data>
278290 <data name="toolStripMenuItem検索.Size" type="System.Drawing.Size, System.Drawing">
279- <value>183, 22</value>
291+ <value>184, 22</value>
280292 </data>
281293 <data name="toolStripMenuItem検索.Text" xml:space="preserve">
282294 <value>検索(&amp;F)</value>
283295 </data>
284- <data name="toolStripMenuItem編集.Size" type="System.Drawing.Size, System.Drawing">
285- <value>57, 20</value>
296+ <data name="toolStripMenuItem表示.Size" type="System.Drawing.Size, System.Drawing">
297+ <value>58, 20</value>
286298 </data>
287- <data name="toolStripMenuItem編集.Text" xml:space="preserve">
288- <value>編集(&amp;E)</value>
299+ <data name="toolStripMenuItem表示.Text" xml:space="preserve">
300+ <value>表示(&amp;V)</value>
301+ </data>
302+ <data name="toolStripMenuItemガイド間隔.Size" type="System.Drawing.Size, System.Drawing">
303+ <value>141, 22</value>
304+ </data>
305+ <data name="toolStripMenuItemガイド間隔.Text" xml:space="preserve">
306+ <value>ガイド間隔(&amp;G)</value>
289307 </data>
290308 <data name="toolStripMenuItemガイド間隔4分.Size" type="System.Drawing.Size, System.Drawing">
291309 <value>136, 22</value>
@@ -308,6 +326,9 @@
308326 <data name="toolStripMenuItemガイド間隔32分.Size" type="System.Drawing.Size, System.Drawing">
309327 <value>136, 22</value>
310328 </data>
329+ <data name="toolStripMenuItemガイド間隔36分.Size" type="System.Drawing.Size, System.Drawing">
330+ <value>136, 22</value>
331+ </data>
311332 <data name="toolStripMenuItemガイド間隔48分.Size" type="System.Drawing.Size, System.Drawing">
312333 <value>136, 22</value>
313334 </data>
@@ -338,17 +359,11 @@
338359 <data name="toolStripMenuItemガイド間隔縮小.Text" xml:space="preserve">
339360 <value>縮小(&amp;N)</value>
340361 </data>
341- <data name="toolStripMenuItemガイド間隔.Size" type="System.Drawing.Size, System.Drawing">
342- <value>140, 22</value>
343- </data>
344- <data name="toolStripMenuItemガイド間隔.Text" xml:space="preserve">
345- <value>ガイド間隔(&amp;G)</value>
346- </data>
347- <data name="toolStripMenuItem表示.Size" type="System.Drawing.Size, System.Drawing">
362+ <data name="toolStripMenuItem再生.Size" type="System.Drawing.Size, System.Drawing">
348363 <value>58, 20</value>
349364 </data>
350- <data name="toolStripMenuItem表示.Text" xml:space="preserve">
351- <value>表示(&amp;V)</value>
365+ <data name="toolStripMenuItem再生.Text" xml:space="preserve">
366+ <value>再生(&amp;P)</value>
352367 </data>
353368 <data name="toolStripMenuItem先頭から再生.Size" type="System.Drawing.Size, System.Drawing">
354369 <value>237, 22</value>
@@ -374,23 +389,23 @@
374389 <data name="toolStripMenuItem再生停止.Text" xml:space="preserve">
375390 <value>再生停止(&amp;S)</value>
376391 </data>
377- <data name="toolStripMenuItem再生.Size" type="System.Drawing.Size, System.Drawing">
378- <value>58, 20</value>
392+ <data name="toolStripMenuItemツール.Size" type="System.Drawing.Size, System.Drawing">
393+ <value>60, 20</value>
379394 </data>
380- <data name="toolStripMenuItem再生.Text" xml:space="preserve">
381- <value>再生(&amp;P)</value>
395+ <data name="toolStripMenuItemツール.Text" xml:space="preserve">
396+ <value>ツール(&amp;T)</value>
382397 </data>
383398 <data name="toolStripMenuItemオプション.Size" type="System.Drawing.Size, System.Drawing">
384- <value>134, 22</value>
399+ <value>135, 22</value>
385400 </data>
386401 <data name="toolStripMenuItemオプション.Text" xml:space="preserve">
387402 <value>オプション(&amp;O)</value>
388403 </data>
389- <data name="toolStripMenuItemツール.Size" type="System.Drawing.Size, System.Drawing">
390- <value>60, 20</value>
404+ <data name="toolStripMenuItemヘルプ.Size" type="System.Drawing.Size, System.Drawing">
405+ <value>65, 20</value>
391406 </data>
392- <data name="toolStripMenuItemツール.Text" xml:space="preserve">
393- <value>ツール(&amp;T)</value>
407+ <data name="toolStripMenuItemヘルプ.Text" xml:space="preserve">
408+ <value>ヘルプ(&amp;H)</value>
394409 </data>
395410 <data name="toolStripMenuItemバージョン.Size" type="System.Drawing.Size, System.Drawing">
396411 <value>133, 22</value>
@@ -398,12 +413,6 @@
398413 <data name="toolStripMenuItemバージョン.Text" xml:space="preserve">
399414 <value>バージョン(&amp;V)</value>
400415 </data>
401- <data name="toolStripMenuItemヘルプ.Size" type="System.Drawing.Size, System.Drawing">
402- <value>65, 20</value>
403- </data>
404- <data name="toolStripMenuItemヘルプ.Text" xml:space="preserve">
405- <value>ヘルプ(&amp;H)</value>
406- </data>
407416 <data name="toolStripButton新規作成.ToolTipText" xml:space="preserve">
408417 <value>新しい譜面を作成します。</value>
409418 </data>
@@ -497,6 +506,9 @@
497506 <data name="toolStripButton音量UP.ToolTipText" xml:space="preserve">
498507 <value>チップ音量を上げる</value>
499508 </data>
509+ <data name="contextMenuStrip譜面右メニュー.Size" type="System.Drawing.Size, System.Drawing">
510+ <value>191, 198</value>
511+ </data>
500512 <data name="toolStripMenuItem選択チップの切り取り.Size" type="System.Drawing.Size, System.Drawing">
501513 <value>190, 22</value>
502514 </data>
@@ -554,7 +566,4 @@
554566 <data name="toolStripMenuItem小節の削除.Text" xml:space="preserve">
555567 <value>小節の削除(&amp;E)</value>
556568 </data>
557- <data name="contextMenuStrip譜面右メニュー.Size" type="System.Drawing.Size, System.Drawing">
558- <value>191, 198</value>
559- </data>
560569 </root>
\ No newline at end of file
--- a/SSTFEditor/メインフォーム.resx
+++ b/SSTFEditor/メインフォーム.resx
@@ -125,15 +125,96 @@
125125 <data name="splitContainer分割パネルコンテナ.Location" type="System.Drawing.Point, System.Drawing">
126126 <value>0, 50</value>
127127 </data>
128+ <data name="textBoxLevel.Location" type="System.Drawing.Point, System.Drawing">
129+ <value>10, 62</value>
130+ </data>
131+ <assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
132+ <data name="textBoxLevel.MaxLength" type="System.Int32, mscorlib">
133+ <value>5</value>
134+ </data>
135+ <data name="textBoxLevel.Size" type="System.Drawing.Size, System.Drawing">
136+ <value>51, 19</value>
137+ </data>
138+ <data name="textBoxLevel.TabIndex" type="System.Int32, mscorlib">
139+ <value>3</value>
140+ </data>
141+ <data name="textBoxLevel.Text" xml:space="preserve">
142+ <value>5.00</value>
143+ </data>
144+ <data name="textBoxLevel.TextAlign" type="System.Windows.Forms.HorizontalAlignment, System.Windows.Forms">
145+ <value>Right</value>
146+ </data>
147+ <data name="&gt;&gt;textBoxLevel.Name" xml:space="preserve">
148+ <value>textBoxLevel</value>
149+ </data>
150+ <data name="&gt;&gt;textBoxLevel.Type" xml:space="preserve">
151+ <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
152+ </data>
153+ <data name="&gt;&gt;textBoxLevel.Parent" xml:space="preserve">
154+ <value>tabPage基本情報</value>
155+ </data>
156+ <data name="&gt;&gt;textBoxLevel.ZOrder" xml:space="preserve">
157+ <value>0</value>
158+ </data>
159+ <data name="trackBarLevel.Location" type="System.Drawing.Point, System.Drawing">
160+ <value>67, 49</value>
161+ </data>
162+ <data name="trackBarLevel.Size" type="System.Drawing.Size, System.Drawing">
163+ <value>217, 45</value>
164+ </data>
165+ <data name="trackBarLevel.TabIndex" type="System.Int32, mscorlib">
166+ <value>4</value>
167+ </data>
168+ <data name="&gt;&gt;trackBarLevel.Name" xml:space="preserve">
169+ <value>trackBarLevel</value>
170+ </data>
171+ <data name="&gt;&gt;trackBarLevel.Type" xml:space="preserve">
172+ <value>System.Windows.Forms.TrackBar, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
173+ </data>
174+ <data name="&gt;&gt;trackBarLevel.Parent" xml:space="preserve">
175+ <value>tabPage基本情報</value>
176+ </data>
177+ <data name="&gt;&gt;trackBarLevel.ZOrder" xml:space="preserve">
178+ <value>1</value>
179+ </data>
180+ <data name="labelLevel.AutoSize" type="System.Boolean, mscorlib">
181+ <value>True</value>
182+ </data>
183+ <data name="labelLevel.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
184+ <value>NoControl</value>
185+ </data>
186+ <data name="labelLevel.Location" type="System.Drawing.Point, System.Drawing">
187+ <value>8, 46</value>
188+ </data>
189+ <data name="labelLevel.Size" type="System.Drawing.Size, System.Drawing">
190+ <value>32, 12</value>
191+ </data>
192+ <data name="labelLevel.TabIndex" type="System.Int32, mscorlib">
193+ <value>2</value>
194+ </data>
195+ <data name="labelLevel.Text" xml:space="preserve">
196+ <value>Level</value>
197+ </data>
198+ <data name="&gt;&gt;labelLevel.Name" xml:space="preserve">
199+ <value>labelLevel</value>
200+ </data>
201+ <data name="&gt;&gt;labelLevel.Type" xml:space="preserve">
202+ <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
203+ </data>
204+ <data name="&gt;&gt;labelLevel.Parent" xml:space="preserve">
205+ <value>tabPage基本情報</value>
206+ </data>
207+ <data name="&gt;&gt;labelLevel.ZOrder" xml:space="preserve">
208+ <value>2</value>
209+ </data>
128210 <data name="textBoxサウンド遅延ms.Location" type="System.Drawing.Point, System.Drawing">
129- <value>212, 322</value>
211+ <value>212, 369</value>
130212 </data>
131213 <data name="textBoxサウンド遅延ms.Size" type="System.Drawing.Size, System.Drawing">
132214 <value>72, 19</value>
133215 </data>
134- <assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
135216 <data name="textBoxサウンド遅延ms.TabIndex" type="System.Int32, mscorlib">
136- <value>17</value>
217+ <value>14</value>
137218 </data>
138219 <data name="textBoxサウンド遅延ms.TextAlign" type="System.Windows.Forms.HorizontalAlignment, System.Windows.Forms">
139220 <value>Right</value>
@@ -148,7 +229,7 @@
148229 <value>tabPage基本情報</value>
149230 </data>
150231 <data name="&gt;&gt;textBoxサウンド遅延ms.ZOrder" xml:space="preserve">
151- <value>0</value>
232+ <value>3</value>
152233 </data>
153234 <data name="labelサウンド遅延ms.AutoSize" type="System.Boolean, mscorlib">
154235 <value>True</value>
@@ -157,13 +238,13 @@
157238 <value>NoControl</value>
158239 </data>
159240 <data name="labelサウンド遅延ms.Location" type="System.Drawing.Point, System.Drawing">
160- <value>8, 325</value>
241+ <value>8, 372</value>
161242 </data>
162243 <data name="labelサウンド遅延ms.Size" type="System.Drawing.Size, System.Drawing">
163244 <value>153, 12</value>
164245 </data>
165246 <data name="labelサウンド遅延ms.TabIndex" type="System.Int32, mscorlib">
166- <value>16</value>
247+ <value>13</value>
167248 </data>
168249 <data name="labelサウンド遅延ms.Text" xml:space="preserve">
169250 <value>Sound Delay [ms] (auto find)</value>
@@ -178,10 +259,10 @@
178259 <value>tabPage基本情報</value>
179260 </data>
180261 <data name="&gt;&gt;labelサウンド遅延ms.ZOrder" xml:space="preserve">
181- <value>1</value>
262+ <value>4</value>
182263 </data>
183264 <data name="textBoxメモ.Location" type="System.Drawing.Point, System.Drawing">
184- <value>10, 237</value>
265+ <value>10, 284</value>
185266 </data>
186267 <data name="textBoxメモ.Multiline" type="System.Boolean, mscorlib">
187268 <value>True</value>
@@ -193,7 +274,7 @@
193274 <value>274, 71</value>
194275 </data>
195276 <data name="textBoxメモ.TabIndex" type="System.Int32, mscorlib">
196- <value>15</value>
277+ <value>12</value>
197278 </data>
198279 <data name="textBoxメモ.WordWrap" type="System.Boolean, mscorlib">
199280 <value>False</value>
@@ -208,7 +289,7 @@
208289 <value>tabPage基本情報</value>
209290 </data>
210291 <data name="&gt;&gt;textBoxメモ.ZOrder" xml:space="preserve">
211- <value>2</value>
292+ <value>5</value>
212293 </data>
213294 <data name="labelメモ用小節番号.AutoSize" type="System.Boolean, mscorlib">
214295 <value>True</value>
@@ -217,13 +298,13 @@
217298 <value>NoControl</value>
218299 </data>
219300 <data name="labelメモ用小節番号.Location" type="System.Drawing.Point, System.Drawing">
220- <value>174, 214</value>
301+ <value>174, 261</value>
221302 </data>
222303 <data name="labelメモ用小節番号.Size" type="System.Drawing.Size, System.Drawing">
223304 <value>32, 12</value>
224305 </data>
225306 <data name="labelメモ用小節番号.TabIndex" type="System.Int32, mscorlib">
226- <value>14</value>
307+ <value>10</value>
227308 </data>
228309 <data name="labelメモ用小節番号.Text" xml:space="preserve">
229310 <value>Part#</value>
@@ -238,16 +319,16 @@
238319 <value>tabPage基本情報</value>
239320 </data>
240321 <data name="&gt;&gt;labelメモ用小節番号.ZOrder" xml:space="preserve">
241- <value>3</value>
322+ <value>6</value>
242323 </data>
243324 <data name="numericUpDownメモ用小節番号.Location" type="System.Drawing.Point, System.Drawing">
244- <value>212, 212</value>
325+ <value>212, 259</value>
245326 </data>
246327 <data name="numericUpDownメモ用小節番号.Size" type="System.Drawing.Size, System.Drawing">
247328 <value>72, 19</value>
248329 </data>
249330 <data name="numericUpDownメモ用小節番号.TabIndex" type="System.Int32, mscorlib">
250- <value>13</value>
331+ <value>11</value>
251332 </data>
252333 <data name="numericUpDownメモ用小節番号.TextAlign" type="System.Windows.Forms.HorizontalAlignment, System.Windows.Forms">
253334 <value>Right</value>
@@ -262,7 +343,7 @@
262343 <value>tabPage基本情報</value>
263344 </data>
264345 <data name="&gt;&gt;numericUpDownメモ用小節番号.ZOrder" xml:space="preserve">
265- <value>4</value>
346+ <value>7</value>
266347 </data>
267348 <data name="labelメモ小節単位.AutoSize" type="System.Boolean, mscorlib">
268349 <value>True</value>
@@ -271,13 +352,13 @@
271352 <value>NoControl</value>
272353 </data>
273354 <data name="labelメモ小節単位.Location" type="System.Drawing.Point, System.Drawing">
274- <value>8, 214</value>
355+ <value>8, 261</value>
275356 </data>
276357 <data name="labelメモ小節単位.Size" type="System.Drawing.Size, System.Drawing">
277358 <value>108, 12</value>
278359 </data>
279360 <data name="labelメモ小節単位.TabIndex" type="System.Int32, mscorlib">
280- <value>12</value>
361+ <value>9</value>
281362 </data>
282363 <data name="labelメモ小節単位.Text" xml:space="preserve">
283364 <value>Memo (in each part)</value>
@@ -292,19 +373,19 @@
292373 <value>tabPage基本情報</value>
293374 </data>
294375 <data name="&gt;&gt;labelメモ小節単位.ZOrder" xml:space="preserve">
295- <value>5</value>
376+ <value>8</value>
296377 </data>
297378 <data name="label背景動画.AutoSize" type="System.Boolean, mscorlib">
298379 <value>True</value>
299380 </data>
300381 <data name="label背景動画.Location" type="System.Drawing.Point, System.Drawing">
301- <value>8, 167</value>
382+ <value>8, 214</value>
302383 </data>
303384 <data name="label背景動画.Size" type="System.Drawing.Size, System.Drawing">
304385 <value>86, 12</value>
305386 </data>
306387 <data name="label背景動画.TabIndex" type="System.Int32, mscorlib">
307- <value>9</value>
388+ <value>7</value>
308389 </data>
309390 <data name="label背景動画.Text" xml:space="preserve">
310391 <value>BGV (auto find)</value>
@@ -319,10 +400,10 @@
319400 <value>tabPage基本情報</value>
320401 </data>
321402 <data name="&gt;&gt;label背景動画.ZOrder" xml:space="preserve">
322- <value>6</value>
403+ <value>9</value>
323404 </data>
324405 <data name="textBox背景動画.Location" type="System.Drawing.Point, System.Drawing">
325- <value>10, 182</value>
406+ <value>10, 229</value>
326407 </data>
327408 <data name="textBox背景動画.Size" type="System.Drawing.Size, System.Drawing">
328409 <value>275, 19</value>
@@ -340,7 +421,7 @@
340421 <value>tabPage基本情報</value>
341422 </data>
342423 <data name="&gt;&gt;textBox背景動画.ZOrder" xml:space="preserve">
343- <value>7</value>
424+ <value>10</value>
344425 </data>
345426 <data name="label説明.AutoSize" type="System.Boolean, mscorlib">
346427 <value>True</value>
@@ -349,13 +430,13 @@
349430 <value>NoControl</value>
350431 </data>
351432 <data name="label説明.Location" type="System.Drawing.Point, System.Drawing">
352- <value>8, 46</value>
433+ <value>8, 93</value>
353434 </data>
354435 <data name="label説明.Size" type="System.Drawing.Size, System.Drawing">
355436 <value>63, 12</value>
356437 </data>
357438 <data name="label説明.TabIndex" type="System.Int32, mscorlib">
358- <value>7</value>
439+ <value>5</value>
359440 </data>
360441 <data name="label説明.Text" xml:space="preserve">
361442 <value>Description</value>
@@ -370,10 +451,10 @@
370451 <value>tabPage基本情報</value>
371452 </data>
372453 <data name="&gt;&gt;label説明.ZOrder" xml:space="preserve">
373- <value>8</value>
454+ <value>11</value>
374455 </data>
375456 <data name="textBox説明.Location" type="System.Drawing.Point, System.Drawing">
376- <value>10, 61</value>
457+ <value>10, 108</value>
377458 </data>
378459 <data name="textBox説明.Multiline" type="System.Boolean, mscorlib">
379460 <value>True</value>
@@ -397,7 +478,7 @@
397478 <value>tabPage基本情報</value>
398479 </data>
399480 <data name="&gt;&gt;textBox説明.ZOrder" xml:space="preserve">
400- <value>9</value>
481+ <value>12</value>
401482 </data>
402483 <data name="label曲名.AutoSize" type="System.Boolean, mscorlib">
403484 <value>True</value>
@@ -409,7 +490,7 @@
409490 <value>28, 12</value>
410491 </data>
411492 <data name="label曲名.TabIndex" type="System.Int32, mscorlib">
412- <value>1</value>
493+ <value>0</value>
413494 </data>
414495 <data name="label曲名.Text" xml:space="preserve">
415496 <value>Title</value>
@@ -424,7 +505,7 @@
424505 <value>tabPage基本情報</value>
425506 </data>
426507 <data name="&gt;&gt;label曲名.ZOrder" xml:space="preserve">
427- <value>10</value>
508+ <value>13</value>
428509 </data>
429510 <data name="textBox曲名.Location" type="System.Drawing.Point, System.Drawing">
430511 <value>10, 24</value>
@@ -433,7 +514,7 @@
433514 <value>275, 19</value>
434515 </data>
435516 <data name="textBox曲名.TabIndex" type="System.Int32, mscorlib">
436- <value>0</value>
517+ <value>1</value>
437518 </data>
438519 <data name="&gt;&gt;textBox曲名.Name" xml:space="preserve">
439520 <value>textBox曲名</value>
@@ -445,7 +526,7 @@
445526 <value>tabPage基本情報</value>
446527 </data>
447528 <data name="&gt;&gt;textBox曲名.ZOrder" xml:space="preserve">
448- <value>11</value>
529+ <value>14</value>
449530 </data>
450531 <data name="tabPage基本情報.Location" type="System.Drawing.Point, System.Drawing">
451532 <value>4, 22</value>
--- a/SSTFormat/v3/DTXReader.cs
+++ b/SSTFormat/v3/DTXReader.cs
@@ -33,7 +33,9 @@ namespace SSTFormat.v3
3333 全入力文字列 = sr.ReadToEnd();
3434 }
3535
36- return ReadFromString( 全入力文字列 );
36+ var score = ReadFromString( 全入力文字列 );
37+ score.譜面ファイルパス = DTXファイルパス;
38+ return score;
3739 }
3840
3941 /// <summary>
@@ -56,41 +58,17 @@ namespace SSTFormat.v3
5658 // ※ テストプロジェクトに対しては InternalsVisibleTo 属性により internal メソッドを可視としているため、
5759 //   以降のテスト対象のメソッドは、本来 private でも internal として宣言している。
5860
59- /// <summary>
60- /// 行を、コマンドとパラメータとコメントの3ブロックに分割して返す。
61- /// 存在しないブロックは null または 空文字列 に設定される。
62- /// </summary>
63- /// <returns>行分解に成功すればtrue、失敗すればfalse。</returns>
64- internal static bool _行分解( string 行, out string コマンド, out string パラメータ, out string コメント )
61+ internal class C行解析時の状態変数
6562 {
66- コマンド = null;
67- パラメータ = null;
68- コメント = null;
69-
70- 行 = 行.Trim( ' ', ' ', '\t' );
71- if( 0 == 行.Length )
72- return true; // 空行。
73-
74- // この書式の具体的な仕様については、単体テストコードを参照。
75- string DTX行書式 = @"^\s*(?:#\s*([^:;\s]*)[:\s]*([^;]*)?)?[;\s]*(.*)$";
76-
77- var m = Regex.Match( 行, DTX行書式 );
78-
79- if( m.Success && ( 4 == m.Groups.Count ) )
63+ public スコア スコア
8064 {
81- コマンド = m.Groups[ 1 ].Value?.Trim();
82- パラメータ = m.Groups[ 2 ].Value?.Trim();
83- コメント = m.Groups[ 3 ].Value?.Trim();
84- return true;
85- }
86- else
87- {
88- return false;
89- }
90- }
65+ get;
66+ protected set;
67+ } = null;
68+ public string コマンド = "";
69+ public string パラメータ = "";
70+ public string コメント = "";
9171
92- internal class C行解析時の状態変数
93- {
9472 public int 行番号 = 0;
9573 public int 小節番号 = 0;
9674 public int チャンネル番号 = 0;
@@ -129,18 +107,39 @@ namespace SSTFormat.v3
129107 /// &lt;SSTチップ(BPM), BPM定義のzz番号&gt;
130108 /// </summary>
131109 public Dictionary<チップ, int> BPM参照マップ = null;
110+
111+
112+ /// <summary>
113+ /// &lt;zz番号, PAN値&gt;
114+ /// PAN値は int 型(左:-100~中央:0~+100:右)
115+ /// </summary>
116+ public Dictionary<int, int> PAN定義マップ = null;
117+
118+ /// <summary>
119+ /// &lt;zz番号, VOLUME値&gt;
120+ /// VOLUME値は int 型(0:無音 ~ 100:原音)
121+ /// </summary>
122+ public Dictionary<int, int> VOLUME定義マップ = null;
123+
124+
125+ public C行解析時の状態変数( スコア score )
126+ {
127+ this.スコア = score;
128+ }
132129 }
133130
134131 internal static void _行解析( ref スコア スコア, ref string 全入力文字列 )
135132 {
136133 // 現在の状態の初期化。
137- var 現在の = new C行解析時の状態変数() {
134+ var 現在の = new C行解析時の状態変数( スコア ) {
138135 小節番号 = 0,
139136 小節解像度 = 384, // DTX の小節解像度。
140137 チップ種別 = チップ種別.Unknown,
141138 小節長倍率マップ = new SortedDictionary<int, float>(),
142139 BPM定義マップ = new Dictionary<int, float>(),
143140 BPM参照マップ = new Dictionary<チップ, int>(),
141+ PAN定義マップ = new Dictionary<int, int>(),
142+ VOLUME定義マップ = new Dictionary<int, int>(),
144143 };
145144
146145 Debug.WriteLineIf( Verbose, "行解析を開始します。" );
@@ -151,8 +150,7 @@ namespace SSTFormat.v3
151150 //----------------
152151 #endregion
153152
154- #region " すべての行について解析。"
155- //----------------
153+ // すべての行について解析。
156154 using( var sr = new StringReader( 全入力文字列 ) )
157155 {
158156 string 行;
@@ -169,22 +167,29 @@ namespace SSTFormat.v3
169167 if( string.IsNullOrEmpty( コマンド ) )
170168 continue;
171169
170+ 現在の.コマンド = コマンド;
171+ 現在の.パラメータ = パラメータ;
172+ 現在の.コメント = コメント;
173+
172174 // 行処理。
173175 var done =
174- _行解析_TITLE( ref スコア, ref 現在の, コマンド, パラメータ, コメント ) ||
175- _行解析_COMMENT( ref スコア, ref 現在の, コマンド, パラメータ, コメント ) ||
176- _行解析_BASEBPM( ref スコア, ref 現在の, コマンド, パラメータ, コメント ) ||
177- _行解析_BPM( ref スコア, ref 現在の, コマンド, パラメータ, コメント ) ||
178- _行解析_BPMzz( ref スコア, ref 現在の, コマンド, パラメータ, コメント ) ||
179- _行解析_オブジェクト記述( ref スコア, ref 現在の, コマンド, パラメータ, コメント );
176+ _行解析_TITLE( 現在の ) ||
177+ _行解析_COMMENT( 現在の ) ||
178+ _行解析_BASEBPM( 現在の ) ||
179+ _行解析_BPM( 現在の ) ||
180+ _行解析_BPMzz( 現在の ) ||
181+ _行解析_WAVPANzz_PANzz( 現在の ) ||
182+ _行解析_WAVVOLzz_VOLUMEzz( 現在の ) ||
183+ _行解析_PATH_WAV( 現在の ) ||
184+ _行解析_WAVzz( 現在の ) ||
185+ _行解析_DLEVEL( 現在の ) ||
186+ _行解析_オブジェクト記述( 現在の );
180187
181188 // 行処理に失敗
182189 //if( !( done ) )
183- // Debug.WriteLineIf( Verbose, $"{現在の.行番号}: 未知のコマンドが使用されました。スキップします。[{コマンド}]" );
190+ // Debug.WriteLineIf( Verbose, $"{現在の.行番号}: 未知のコマンドが使用されました。スキップします。[{現在の.コマンド}]" );
184191 }
185192 }
186- //----------------
187- #endregion
188193
189194 #region " 後処理(1) BPMチップの値を引き当てる。"
190195 //----------------
@@ -200,6 +205,7 @@ namespace SSTFormat.v3
200205 }
201206 //----------------
202207 #endregion
208+
203209 #region " 後処理(2) 小節長倍率マップをもとに、スコアの小節長倍率リストを構築する。"
204210 //----------------
205211 {
@@ -216,34 +222,101 @@ namespace SSTFormat.v3
216222 //----------------
217223 #endregion
218224
225+ #region " 後処理(3) チップのPAN値, VOLUME値を引き当てる。"
226+ //----------------
227+ foreach( var chip in スコア.チップリスト )
228+ {
229+ // このDTXreaderクラスの実装で対応しているチャンネルで、
230+ var query = _チャンネルプロパティ.Where( ( r ) => ( r.chipType == chip.チップ種別 ) );
231+ if( 1 == query.Count() )
232+ {
233+ var record = query.Single();
234+
235+ // WAV を使うチャンネルで、
236+ if( record.Wavを使う )
237+ {
238+ // PAN の指定があるなら、
239+ if( 現在の.PAN定義マップ.ContainsKey( chip.チップサブID ) )
240+ {
241+ // それをチップに設定する。
242+ chip.位置 = 現在の.PAN定義マップ[ chip.チップサブID ];
243+ }
244+
245+ // VOLUME の指定があるなら、
246+ if( 現在の.VOLUME定義マップ.ContainsKey( chip.チップサブID ) )
247+ {
248+ // それをチップに設定する。(無音:0~100:原音 を 小:1~8:原音 に変換する。
249+ var dtxvol = Math.Min( Math.Max( 現在の.VOLUME定義マップ[ chip.チップサブID ], 0 ), 100 );
250+ chip.音量 = ( 0 == dtxvol ) ? 1 : // SSTFには「無音」はない。
251+ (int) Math.Ceiling( ( 8.0 * dtxvol ) / 100.0 );
252+ }
253+ }
254+ }
255+ }
256+ //----------------
257+ #endregion
258+
219259 // 解析終了。
220260 Debug.WriteLineIf( Verbose, "行解析を終了しました。" );
221261 }
222262
223- internal static bool _行解析_TITLE( ref スコア スコア, ref C行解析時の状態変数 現在の, string コマンド, string パラメータ, string コメント )
263+ /// <summary>
264+ /// 行を、コマンドとパラメータとコメントの3ブロックに分割して返す。
265+ /// 存在しないブロックは null または 空文字列 に設定される。
266+ /// </summary>
267+ /// <returns>行分解に成功すればtrue、失敗すればfalse。</returns>
268+ internal static bool _行分解( string 行, out string コマンド, out string パラメータ, out string コメント )
269+ {
270+ コマンド = null;
271+ パラメータ = null;
272+ コメント = null;
273+
274+ 行 = 行.Trim( ' ', ' ', '\t' );
275+ if( 0 == 行.Length )
276+ return true; // 空行。
277+
278+ // この書式の具体的な仕様については、単体テストコードを参照。
279+ string DTX行書式 = @"^\s*(?:#\s*([^:;\s]*)[:\s]*([^;]*)?)?[;\s]*(.*)$";
280+
281+ var m = Regex.Match( 行, DTX行書式 );
282+
283+ if( m.Success && ( 4 == m.Groups.Count ) )
284+ {
285+ コマンド = m.Groups[ 1 ].Value?.Trim();
286+ パラメータ = m.Groups[ 2 ].Value?.Trim();
287+ コメント = m.Groups[ 3 ].Value?.Trim();
288+ return true;
289+ }
290+ else
291+ {
292+ return false;
293+ }
294+ }
295+
296+ internal static bool _行解析_TITLE( C行解析時の状態変数 現在の )
224297 {
225- if( "title" != コマンド.ToLower() )
298+ if( "title" != 現在の.コマンド.ToLower() )
226299 return false;
227300
228- スコア.曲名 = パラメータ;
301+ 現在の.スコア.曲名 = 現在の.パラメータ;
229302
230303 return true;
231304 }
232- internal static bool _行解析_COMMENT( ref スコア スコア, ref C行解析時の状態変数 現在の, string コマンド, string パラメータ, string コメント )
305+ internal static bool _行解析_COMMENT( C行解析時の状態変数 現在の )
233306 {
234- if( "comment" != コマンド.ToLower() )
307+ if( "comment" != 現在の.コマンド.ToLower() )
235308 return false;
236309
237- スコア.説明文 = パラメータ;
310+ 現在の.スコア.説明文 = 現在の.パラメータ;
238311
239312 return true;
240313 }
241- internal static bool _行解析_BASEBPM( ref スコア スコア, ref C行解析時の状態変数 現在の, string コマンド, string パラメータ, string コメント )
314+ internal static bool _行解析_BASEBPM( C行解析時の状態変数 現在の )
242315 {
243- if( "basebpm" != コマンド.ToLower() )
316+ if( "basebpm" != 現在の.コマンド.ToLower() )
244317 return false;
245318
246- if( float.TryParse( パラメータ, out float bpm値 ) )
319+ if( float.TryParse( 現在の.パラメータ, out float bpm値 ) )
247320 {
248321 現在の.BASEBPM = bpm値; // 上書き可
249322 return true;
@@ -253,19 +326,19 @@ namespace SSTFormat.v3
253326 return false;
254327 }
255328 }
256- internal static bool _行解析_BPM( ref スコア スコア, ref C行解析時の状態変数 現在の, string コマンド, string パラメータ, string コメント )
329+ internal static bool _行解析_BPM( C行解析時の状態変数 現在の )
257330 {
258- if( "bpm" != コマンド.ToLower() )
331+ if( "bpm" != 現在の.コマンド.ToLower() )
259332 return false;
260333
261- if( float.TryParse( パラメータ, out float bpm値 ) )
334+ if( float.TryParse( 現在の.パラメータ, out float bpm値 ) )
262335 {
263336 // ※ 無限管理には非対応。
264337
265338 //bpm値 += 現在の.BASEBPM; --> #BPM: の値には #BASEBPM を加算しない。(DTX仕様)
266339
267340 // "#BPM:" に対応するBPMチップは、常に、小節番号==0かつ小節内位置==0に置かれる。
268- var bpmChip = スコア.チップリスト.FirstOrDefault( ( c ) => ( c.チップ種別 == チップ種別.BPM && c.小節番号 == 0 && c.小節内位置 == 0 ) );
341+ var bpmChip = 現在の.スコア.チップリスト.FirstOrDefault( ( c ) => ( c.チップ種別 == チップ種別.BPM && c.小節番号 == 0 && c.小節内位置 == 0 ) );
269342 if( null != bpmChip )
270343 {
271344 // (A) すでに存在するなら上書き
@@ -283,7 +356,7 @@ namespace SSTFormat.v3
283356 BPM = bpm値,
284357 };
285358
286- スコア.チップリスト.Add( bpmChip );
359+ 現在の.スコア.チップリスト.Add( bpmChip );
287360 }
288361
289362 return true;
@@ -293,19 +366,16 @@ namespace SSTFormat.v3
293366 return false;
294367 }
295368 }
296- internal static bool _行解析_BPMzz( ref スコア スコア, ref C行解析時の状態変数 現在の, string コマンド, string パラメータ, string コメント )
369+ internal static bool _行解析_BPMzz( C行解析時の状態変数 現在の )
297370 {
298- if( !( コマンド.ToLower().StartsWith( "bpm", ignoreCase: true, culture: null ) ) || ( 5 != コマンド.Length ) )
371+ if( !( 現在の.コマンド.ToLower().StartsWith( "bpm" ) ) || ( 5 != 現在の.コマンド.Length ) )
299372 return false;
300373
301- int zz =
302- _三十六進数変換表.IndexOf( コマンド[ 3 ] ) * 36 +
303- _三十六進数変換表.IndexOf( コマンド[ 4 ] ); // 36進数2桁表記
304-
374+ int zz = _36進数2桁の文字列を数値に変換して取得する( 現在の.コマンド.Substring( 3, 2 ) );
305375 if( ( 1 > zz ) || ( 36 * 36 - 1 < zz ) )
306376 return false; // 有効範囲は 1~3599
307377
308- if( float.TryParse( パラメータ, out float bpm値 ) && ( 0f < bpm値 ) && ( 1000f > bpm値 ) ) // 値域制限はDTX仕様
378+ if( float.TryParse( 現在の.パラメータ, out float bpm値 ) && ( 0f < bpm値 ) && ( 1000f > bpm値 ) ) // 値域制限はDTX仕様
309379 {
310380 // ※ 無限管理には非対応。
311381 現在の.BPM定義マップ.Remove( zz ); // 上書き可
@@ -317,19 +387,118 @@ namespace SSTFormat.v3
317387 return false;
318388 }
319389 }
320- internal static bool _行解析_オブジェクト記述( ref スコア スコア, ref C行解析時の状態変数 現在の, string コマンド, string パラメータ, string コメント )
390+ internal static bool _行解析_WAVPANzz_PANzz( C行解析時の状態変数 現在の )
391+ {
392+ int zz位置 = 0;
393+
394+ if( 現在の.コマンド.ToLower().StartsWith( "wavpan" ) && ( 8 == 現在の.コマンド.Length ) )
395+ {
396+ zz位置 = 6;
397+ }
398+ else if( 現在の.コマンド.ToLower().StartsWith( "pan" ) && ( 5 == 現在の.コマンド.Length ) )
399+ {
400+ zz位置 = 3;
401+ }
402+ else
403+ {
404+ return false;
405+ }
406+
407+ int zz = _36進数2桁の文字列を数値に変換して取得する( 現在の.コマンド.Substring( zz位置, 2 ) );
408+ if( ( 1 > zz ) || ( 36 * 36 - 1 < zz ) )
409+ return false; // 有効範囲は 1~3599
410+
411+ if( int.TryParse( 現在の.パラメータ, out int pan値 ) )
412+ {
413+ // ※ 無限管理には非対応。
414+ 現在の.PAN定義マップ[ zz ] = Math.Min( Math.Max( pan値, -100 ), +100 );
415+ return true;
416+ }
417+ else
418+ {
419+ return false;
420+ }
421+ }
422+ internal static bool _行解析_WAVVOLzz_VOLUMEzz( C行解析時の状態変数 現在の )
321423 {
322- if( !( _小節番号とチャンネル番号を取得する( コマンド, out 現在の.小節番号, out 現在の.チャンネル番号 ) ) )
424+ int zz位置 = 0;
425+
426+ if( 現在の.コマンド.ToLower().StartsWith( "wavvol" ) && ( 8 == 現在の.コマンド.Length ) )
427+ {
428+ zz位置 = 6;
429+ }
430+ else if( 現在の.コマンド.ToLower().StartsWith( "volume" ) && ( 8 == 現在の.コマンド.Length ) )
431+ {
432+ zz位置 = 6;
433+ }
434+ else
435+ {
323436 return false;
437+ }
324438
325- パラメータ = パラメータ.Replace( "_", "" ); // 見やすさのために '_' を区切り文字として使用できる(DTX仕様)
326- パラメータ = パラメータ.ToLower(); // すべて小文字化(三十六進数変換表には大文字がないので)
439+ int zz = _36進数2桁の文字列を数値に変換して取得する( 現在の.コマンド.Substring( zz位置, 2 ) );
440+ if( ( 1 > zz ) || ( 36 * 36 - 1 < zz ) )
441+ return false; // 有効範囲は 1~3599
442+
443+ if( int.TryParse( 現在の.パラメータ, out int pan値 ) )
444+ {
445+ // ※ 無限管理には非対応。
446+ 現在の.VOLUME定義マップ[ zz ] = Math.Min( Math.Max( pan値, 0 ), 100 );
447+ return true;
448+ }
449+ else
450+ {
451+ return false;
452+ }
453+ }
454+ internal static bool _行解析_PATH_WAV( C行解析時の状態変数 現在の )
455+ {
456+ if( "path_wav" != 現在の.コマンド.ToLower() )
457+ return false;
458+
459+ 現在の.スコア.PATH_WAV = 現在の.パラメータ;
460+
461+ return true;
462+ }
463+ internal static bool _行解析_WAVzz( C行解析時の状態変数 現在の )
464+ {
465+ if( !( 現在の.コマンド.ToLower().StartsWith( "wav" ) ) || ( 5 != 現在の.コマンド.Length ) )
466+ return false;
467+
468+ int zz = _36進数2桁の文字列を数値に変換して取得する( 現在の.コマンド.Substring( 3, 2 ) );
469+ if( ( 1 > zz ) || ( 36 * 36 - 1 < zz ) )
470+ return false; // 有効範囲は 1~3599
471+
472+ if( 現在の.スコア.dicWAVファイルパス.ContainsKey( zz ) )
473+ 現在の.スコア.dicWAVファイルパス.Remove( zz );
474+
475+ // ここでは、PATH_WAV は反映しない。(後方定義に対応するため)
476+ 現在の.スコア.dicWAVファイルパス.Add( zz, 現在の.パラメータ );
477+ return true;
478+ }
479+ internal static bool _行解析_DLEVEL( C行解析時の状態変数 現在の )
480+ {
481+ if( "dlevel" != 現在の.コマンド.ToLower() )
482+ return false;
483+
484+ if( int.TryParse( 現在の.パラメータ, out int level ) )
485+ 現在の.スコア.難易度 = Math.Min( Math.Max( level, 0 ), 99 ) / 10.0f; // 0.0 ~ 9.9
486+
487+ return true;
488+ }
489+ internal static bool _行解析_オブジェクト記述( C行解析時の状態変数 現在の )
490+ {
491+ if( !( _小節番号とチャンネル番号を取得する( 現在の.コマンド, out 現在の.小節番号, out 現在の.チャンネル番号 ) ) )
492+ return false;
493+
494+ 現在の.パラメータ = 現在の.パラメータ.Replace( "_", "" ); // 見やすさのために '_' を区切り文字として使用できる(DTX仕様)
495+ 現在の.パラメータ = 現在の.パラメータ.ToLower(); // すべて小文字化(三十六進数変換表には大文字がないので)
327496
328497 if( 0x02 == 現在の.チャンネル番号 )
329498 {
330499 #region " ch02 小節長倍率 "
331500 //----------------
332- if( !( _DTX仕様の実数を取得する( パラメータ, out float 小節長倍率 ) ) )
501+ if( !( _DTX仕様の実数を取得する( 現在の.パラメータ, out float 小節長倍率 ) ) )
333502 {
334503 Debug.WriteLineIf( Verbose, $"{現在の.行番号}: ch02 のパラメータ(小節長倍率)に指定された実数の解析に失敗しました。" );
335504 return false;
@@ -347,7 +516,7 @@ namespace SSTFormat.v3
347516 #endregion
348517 }
349518
350- 現在の.オブジェクト総数 = パラメータ.Length / 2; // 1オブジェクトは2文字;余った末尾は切り捨てる。(DTX仕様)
519+ 現在の.オブジェクト総数 = 現在の.パラメータ.Length / 2; // 1オブジェクトは2文字;余った末尾は切り捨てる。(DTX仕様)
351520
352521 for( 現在の.オブジェクト番号 = 0; 現在の.オブジェクト番号 < 現在の.オブジェクト総数; 現在の.オブジェクト番号++ )
353522 {
@@ -358,14 +527,12 @@ namespace SSTFormat.v3
358527 if( 0x03 == 現在の.チャンネル番号 )
359528 {
360529 // (A) ch03 (BPM) のみ 16進数2桁表記
361- オブジェクト値 = Convert.ToInt32( パラメータ.Substring( 現在の.オブジェクト番号 * 2, 2 ), 16 );
530+ オブジェクト値 = Convert.ToInt32( 現在の.パラメータ.Substring( 現在の.オブジェクト番号 * 2, 2 ), 16 );
362531 }
363532 else
364533 {
365534 // (B) その他は 36進数2桁表記
366- オブジェクト値 =
367- _三十六進数変換表.IndexOf( パラメータ[ 現在の.オブジェクト番号 * 2 ] ) * 36 +
368- _三十六進数変換表.IndexOf( パラメータ[ 現在の.オブジェクト番号 * 2 + 1 ] );
535+ オブジェクト値 = _36進数2桁の文字列を数値に変換して取得する( 現在の.パラメータ.Substring( 現在の.オブジェクト番号 * 2, 2 ) );
369536 }
370537
371538 if( 0 > オブジェクト値 )
@@ -436,7 +603,7 @@ namespace SSTFormat.v3
436603 // チップリストに登録。
437604 if( チップ種別.Unknown != chip.チップ種別 )
438605 {
439- スコア.チップリスト.Add( chip );
606+ 現在の.スコア.チップリスト.Add( chip );
440607 }
441608 }
442609 return true;
@@ -494,7 +661,42 @@ namespace SSTFormat.v3
494661 // 整数部+小数点+小数部 で CurrentCulture な実数文字列を作成し、float へ変換する。
495662 return float.TryParse( $"{整数部}{CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator}{小数部}", out 数値 );
496663 }
664+ internal static int _36進数2桁の文字列を数値に変換して取得する( string 文字列 )
665+ {
666+ Debug.Assert( 2 == 文字列.Length );
667+
668+ var 変換文字列 = 文字列.ToLower();
669+
670+ return
671+ _三十六進数変換表.IndexOf( 変換文字列[ 0 ] ) * 36 +
672+ _三十六進数変換表.IndexOf( 変換文字列[ 1 ] );
673+ }
674+
675+ private static readonly string _三十六進数変換表 = "0123456789abcdefghijklmnopqrstuvwxyz"; // めんどいので大文字は考慮しない。参照元で小文字に変換のこと。
676+
677+ /// <summary>
678+ /// チャンネル情報データベース。
679+ /// </summary>
680+ private static readonly List<(int channel, チップ種別 chipType, bool Wavを使う)> _チャンネルプロパティ = new List<(int channel, チップ種別 chipType, bool Wavを使う)>() {
681+ ( 0x01, チップ種別.背景動画, true ), // バックコーラス(BGM)
682+ ( 0x02, チップ種別.Unknown, false ), // 小節長倍率
683+ ( 0x03, チップ種別.BPM, false ), // BPM
684+ ( 0x08, チップ種別.BPM, false ), // 拡張BPM
685+ ( 0x11, チップ種別.HiHat_Close, true ), // チップ配置(ドラム)・ハイハットクローズ
686+ ( 0x12, チップ種別.Snare, true ), // チップ配置(ドラム)・スネア
687+ ( 0x13, チップ種別.Bass, true ), // チップ配置(ドラム)・バス
688+ ( 0x14, チップ種別.Tom1, true ), // チップ配置(ドラム)・ハイタム
689+ ( 0x15, チップ種別.Tom2, true ), // チップ配置(ドラム)・ロータム
690+ ( 0x16, チップ種別.RightCrash, true ), // チップ配置(ドラム)・右シンバル
691+ ( 0x17, チップ種別.Tom3, true ), // チップ配置(ドラム)・フロアタム
692+ ( 0x18, チップ種別.HiHat_Open, true ), // チップ配置(ドラム)・ハイハットオープン
693+ ( 0x19, チップ種別.Ride, true ), // チップ配置(ドラム)・ライドシンバル
694+ ( 0x1A, チップ種別.LeftCrash, true ), // チップ配置(ドラム)・左シンバル
695+ ( 0x1B, チップ種別.HiHat_Foot, true ), // チップ配置(ドラム)・左ペダル
696+ ( 0x1C, チップ種別.Bass, true ), // チップ配置(ドラム)・左バス
697+ ( 0x50, チップ種別.小節線, false ), // 小節線
698+ ( 0x51, チップ種別.拍線, false ), // 拍線
699+ };
497700
498- private static readonly string _三十六進数変換表 = "0123456789abcdefghijklmnopqrstuvwxyz"; // めんどいので大文字は考慮しない
499701 }
500702 }
--- a/SSTFormat/v3/スコア.cs
+++ b/SSTFormat/v3/スコア.cs
@@ -135,7 +135,7 @@ namespace SSTFormat.v3
135135 {
136136 get;
137137 set;
138- } = 0.0f;
138+ } = 5.0f;
139139 /// <remarks>
140140 /// 背景動画ファイル名は、sstf ファイルには保存されず、必要時に sstf ファイルと同じフォルダを検索して取得する。
141141 /// </remarks>
@@ -178,6 +178,50 @@ namespace SSTFormat.v3
178178 protected set;
179179 } = new Dictionary<int, string>();
180180
181+ /// <summary>
182+ /// #WAV で指定された、WAVファイルへの相対パス。
183+ /// パスの基点は、#PATH_WAV があればそこ、なければ曲譜面ファイルと同じ場所。
184+ /// SSTFでは未使用。
185+ /// </summary>
186+ public Dictionary<int, string> dicWAVファイルパス
187+ {
188+ get;
189+ protected set;
190+ } = new Dictionary<int, string>();
191+ /// <summary>
192+ /// WAV/AVIファイルの基点フォルダの絶対パス。
193+ /// 末尾は '\' 。(例: "D:\DTXData\DemoSong\Sounds\")
194+ /// </summary>
195+ public string PATH_WAV
196+ {
197+ get
198+ {
199+ if( null != this.譜面ファイルパス )
200+ return Path.Combine( Path.GetDirectoryName( this.譜面ファイルパス ), this._PATH_WAV );
201+ else
202+ return this._PATH_WAV;
203+ }
204+
205+ set
206+ {
207+ this._PATH_WAV = value;
208+
209+ if( this._PATH_WAV.Last() != '\\' )
210+ this._PATH_WAV += '\\';
211+
212+ //if( !( Directory.Exists( this.PATH_WAV ) ) )
213+ // throw new DirectoryNotFoundException( "PATH_WAV に存在しないフォルダが指定されました。" );
214+ }
215+ }
216+ /// <summary>
217+ /// 譜面ファイルの絶対パス。
218+ /// </summary>
219+ public string 譜面ファイルパス
220+ {
221+ get;
222+ set;
223+ } = null;
224+
181225
182226 public スコア()
183227 {
@@ -191,12 +235,16 @@ namespace SSTFormat.v3
191235 public void Dispose()
192236 {
193237 }
194-
238+
195239 /// <remarks>
196240 /// 失敗すれば何らかの例外を発出する。
197241 /// </remarks>
198242 public void 曲データファイルを読み込む( string 曲データファイル名 )
199243 {
244+ this.譜面ファイルパス = Path.GetFullPath( 曲データファイル名 );
245+ if( !( File.Exists( this.譜面ファイルパス ) ) )
246+ throw new FileNotFoundException( $"指定されたファイルが存在しません。" );
247+
200248 var ファイルのSSTFバージョン = Version.CreateVersionFromFile( 曲データファイル名 );
201249
202250 // ファイルのSSTFバージョンによって処理分岐。
@@ -215,6 +263,10 @@ namespace SSTFormat.v3
215263
216264 public void 曲データファイルを読み込む_ヘッダだけ( string 曲データファイル名 )
217265 {
266+ this.譜面ファイルパス = Path.GetFullPath( 曲データファイル名 );
267+ if( !( File.Exists( this.譜面ファイルパス ) ) )
268+ throw new FileNotFoundException( $"指定されたファイルが存在しません。" );
269+
218270 var ファイルのSSTFバージョン = Version.CreateVersionFromFile( 曲データファイル名 );
219271
220272 // ファイルのSSTFバージョンによって処理分岐。
@@ -377,9 +429,10 @@ namespace SSTFormat.v3
377429 if( !string.IsNullOrEmpty( this.説明文 ) )
378430 {
379431 // 改行コードは、2文字のリテラル "\n" に置換。
380- sw.WriteLine( "Description=" + this.説明文.Replace( Environment.NewLine, @"\n" ) );
432+ sw.WriteLine( $"Description=" + this.説明文.Replace( Environment.NewLine, @"\n" ) );
381433 }
382- sw.WriteLine( "SoundDevice.Delay={0}", this.サウンドデバイス遅延ms );
434+ sw.WriteLine( $"SoundDevice.Delay={this.サウンドデバイス遅延ms}" );
435+ sw.WriteLine( $"Level={this.難易度.ToString( "0.00" )}" );
383436 sw.WriteLine( "" );
384437
385438 // 全チップの出力
@@ -647,16 +700,27 @@ namespace SSTFormat.v3
647700 this.小節長倍率リスト[ 小節番号 ] = 倍率;
648701 }
649702
703+
704+ /// <summary>
705+ /// 空文字、または絶対パス。
706+ /// null は不可。
707+ /// </summary>
708+ private string _PATH_WAV = "";
709+
710+
650711 private void _初期化する()
651712 {
652713 this.SSTFバージョン = SSTFVERSION;
653714 this.曲名 = "(no title)";
654715 this.説明文 = "";
655716 this.サウンドデバイス遅延ms = 0f;
656- this.難易度 = 0f;
717+ this.難易度 = 5.0f;
657718 this.背景動画ファイル名 = "";
658719 this.小節長倍率リスト = new List<double>();
659720 this.dicメモ = new Dictionary<int, string>();
721+ this.dicWAVファイルパス = new Dictionary<int, string>();
722+ this._PATH_WAV = "";
723+ this.譜面ファイルパス = null;
660724 }
661725
662726 /// <summary>
@@ -761,7 +825,7 @@ namespace SSTFormat.v3
761825 this.曲名 = v2score.Header.曲名;
762826 this.説明文 = v2score.Header.説明文;
763827 this.サウンドデバイス遅延ms = v2score.Header.サウンドデバイス遅延ms;
764- this.難易度 = 0.0f; // 新規
828+ this.難易度 = 5.0f; // 新規
765829 this.背景動画ファイル名 = v2score.背景動画ファイル名;
766830 }
767831
--- a/SSTFormat/v3/チップ.cs
+++ b/SSTFormat/v3/チップ.cs
@@ -72,6 +72,21 @@ namespace SSTFormat.v3
7272 }
7373
7474 /// <summary>
75+ /// PAN。
76+ /// 左:-100 ~ 中央:0 ~ +100:右。
77+ /// </summary>
78+ public int 位置
79+ {
80+ get
81+ => this._位置;
82+
83+ set
84+ => this._位置 = ( -100 > value || +100 < value ) ?
85+ throw new ArgumentOutOfRangeException( $"位置の値域(-100~+100)を超える値 '{value}' が指定されました。" ) :
86+ value;
87+ }
88+
89+ /// <summary>
7590 /// チップが BPM チップである場合は、その BPM 値。
7691 /// それ以外の場合は無効。
7792 /// </summary>
@@ -312,5 +327,6 @@ namespace SSTFormat.v3
312327 #endregion
313328
314329 private int _音量 = チップ.最大音量;
330+ private int _位置 = 0;
315331 }
316332 }
--- a/SSTFormatTests/SSTFormatTests.csproj
+++ b/SSTFormatTests/SSTFormatTests.csproj
@@ -66,6 +66,7 @@
6666 <Compile Include="Properties\AssemblyInfo.cs" />
6767 <Compile Include="v1\スコアTests.cs" />
6868 <Compile Include="v2\DTXReaderTests.cs" />
69+ <Compile Include="v3\DTXReaderTests.cs" />
6970 <Compile Include="VersionTests.cs" />
7071 </ItemGroup>
7172 <ItemGroup>
@@ -80,6 +81,7 @@
8081 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
8182 </None>
8283 </ItemGroup>
84+ <ItemGroup />
8385 <Choose>
8486 <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
8587 <ItemGroup>
--- /dev/null
+++ b/SSTFormatTests/v3/DTXReaderTests.cs
@@ -0,0 +1,673 @@
1+using Microsoft.VisualStudio.TestTools.UnitTesting;
2+using SSTFormat.v2;
3+using System;
4+using System.Collections.Generic;
5+using System.Diagnostics;
6+using System.Linq;
7+
8+namespace SSTFormat.v3.Tests
9+{
10+ [TestClass()]
11+ public class DTXReaderTests
12+ {
13+ // 例外検証用メソッド
14+ private void 例外が出れば成功( Action action )
15+ {
16+ try
17+ {
18+ action();
19+ Assert.Fail(); // ここに来るということは、action() で例外がでなかったということ。
20+ }
21+ catch( AssertFailedException )
22+ {
23+ throw; // 失敗。
24+ }
25+ catch
26+ {
27+ // 成功。
28+ }
29+ }
30+
31+ [TestMethod()]
32+ public void 行分解Test()
33+ {
34+ string コマンド, パラメータ, コメント;
35+
36+ #region " 空行 "
37+ //----------------
38+ DTXReader._行分解( "", out コマンド, out パラメータ, out コメント );
39+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
40+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
41+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
42+
43+ DTXReader._行分解( " ", out コマンド, out パラメータ, out コメント );
44+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
45+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
46+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
47+
48+ // TAB → 空白文字扱い。
49+ DTXReader._行分解( "\t", out コマンド, out パラメータ, out コメント );
50+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
51+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
52+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
53+
54+ DTXReader._行分解( " \t ", out コマンド, out パラメータ, out コメント );
55+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
56+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
57+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
58+ //----------------
59+ #endregion
60+ #region " コマンド "
61+ //----------------
62+ {
63+ // 区切り文字なし → OK
64+ DTXReader._行分解( "#TITLE", out コマンド, out パラメータ, out コメント );
65+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
66+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
67+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
68+
69+ // 区切り文字 ":" あり → OK
70+ DTXReader._行分解( "#TITLE:", out コマンド, out パラメータ, out コメント );
71+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
72+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
73+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
74+
75+ // 区切り文字は空白文字でもいい。
76+ DTXReader._行分解( "#TITLE ", out コマンド, out パラメータ, out コメント );
77+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
78+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
79+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
80+
81+ DTXReader._行分解( "#TITLE\t", out コマンド, out パラメータ, out コメント );
82+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
83+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
84+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
85+
86+ DTXReader._行分解( "#TITLE\t:", out コマンド, out パラメータ, out コメント );
87+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
88+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
89+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
90+
91+ // "#" の前後の空白文字 → 無視される。
92+ DTXReader._行分解( " # TITLE", out コマンド, out パラメータ, out コメント );
93+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
94+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
95+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
96+
97+ // "#" がない → コマンドではなくすべてコメントだとみなされる。
98+ DTXReader._行分解( "TITLE", out コマンド, out パラメータ, out コメント );
99+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
100+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
101+ Assert.AreEqual( expected: "TITLE", actual: コメント );
102+ }
103+ //----------------
104+ #endregion
105+ #region " コメント "
106+ //----------------
107+ {
108+ // 行頭から。→ OK
109+ DTXReader._行分解( @";コメントだよ!", out コマンド, out パラメータ, out コメント );
110+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
111+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
112+ Assert.AreEqual( expected: @"コメントだよ!", actual: コメント );
113+
114+ // コマンドが NG である("#"がない) → 行中に ";" があっても、すべてコメントだとみなされる。
115+ DTXReader._行分解( @"NGコマンド文 ; コメントだよ!", out コマンド, out パラメータ, out コメント );
116+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
117+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
118+ Assert.AreEqual( expected: @"NGコマンド文 ; コメントだよ!", actual: コメント );
119+
120+ // コメントの前後の空白 → 無視される。
121+ DTXReader._行分解( @"; コメントだよ! ", out コマンド, out パラメータ, out コメント );
122+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
123+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
124+ Assert.AreEqual( expected: @"コメントだよ!", actual: コメント );
125+
126+ // コメントの区切り文字だけが存在する → すべて null または空文字列になる。
127+ DTXReader._行分解( ";", out コマンド, out パラメータ, out コメント );
128+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
129+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
130+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
131+
132+ // コメントの区切り文字が複数ある → 最初に現れた ';' のみ有効。2回目以降の出現はコメント文に文字列として含まれる。
133+ DTXReader._行分解( @";コメントその1;その2", out コマンド, out パラメータ, out コメント );
134+ Assert.IsTrue( string.IsNullOrEmpty( コマンド ) );
135+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
136+ Assert.AreEqual( expected: @"コメントその1;その2", actual: コメント );
137+ }
138+ //----------------
139+ #endregion
140+ #region " コマンド+パラメータ "
141+ //----------------
142+ {
143+ // 基本形。
144+ DTXReader._行分解( "#TITLE: タイトルだよ!", out コマンド, out パラメータ, out コメント );
145+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
146+ Assert.AreEqual( expected: "タイトルだよ!", actual: パラメータ );
147+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
148+
149+ // 区切り文字は空白でもいい。
150+ DTXReader._行分解( "#TITLE タイトルだよ!", out コマンド, out パラメータ, out コメント );
151+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
152+ Assert.AreEqual( expected: "タイトルだよ!", actual: パラメータ );
153+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
154+
155+ DTXReader._行分解( "#TITLE\tタイトルだよ!", out コマンド, out パラメータ, out コメント );
156+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
157+ Assert.AreEqual( expected: "タイトルだよ!", actual: パラメータ );
158+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
159+
160+ DTXReader._行分解( "#TITLE \t タイトルだよ!", out コマンド, out パラメータ, out コメント );
161+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
162+ Assert.AreEqual( expected: "タイトルだよ!", actual: パラメータ );
163+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
164+
165+ // パラメータには途中に空白を含めることができる。ただし、パラメータの前後の空白は無視される。
166+ DTXReader._行分解( "#TITLE: タイトルだよ! これも! ……これも! ", out コマンド, out パラメータ, out コメント );
167+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
168+ Assert.AreEqual( expected: "タイトルだよ! これも! ……これも!", actual: パラメータ );
169+ Assert.IsTrue( string.IsNullOrEmpty( コメント ) );
170+ }
171+ //----------------
172+ #endregion
173+ #region " コマンド+パラメータ+コメント "
174+ //----------------
175+ {
176+ // 基本形。
177+ DTXReader._行分解( "#TITLE: タイトルだよ!;コメントだよ!", out コマンド, out パラメータ, out コメント );
178+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
179+ Assert.AreEqual( expected: "タイトルだよ!", actual: パラメータ );
180+ Assert.AreEqual( expected: "コメントだよ!", actual: コメント );
181+
182+ // 間に空白を入れても無視される。
183+ DTXReader._行分解( "#TITLE: タイトルだよ! ; \t コメントだよ! ", out コマンド, out パラメータ, out コメント );
184+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
185+ Assert.AreEqual( expected: "タイトルだよ!", actual: パラメータ );
186+ Assert.AreEqual( expected: "コメントだよ!", actual: コメント );
187+ }
188+ //----------------
189+ #endregion
190+ #region " コマンド+コメント "
191+ //----------------
192+ {
193+ // 基本形。
194+ DTXReader._行分解( "#TITLE;コメントだよ!", out コマンド, out パラメータ, out コメント );
195+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
196+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
197+ Assert.AreEqual( expected: "コメントだよ!", actual: コメント );
198+
199+ // 間に空白を入れても無視される。
200+ DTXReader._行分解( "#TITLE ; コメントだよ! ", out コマンド, out パラメータ, out コメント );
201+ Assert.AreEqual( expected: "TITLE", actual: コマンド );
202+ Assert.IsTrue( string.IsNullOrEmpty( パラメータ ) );
203+ Assert.AreEqual( expected: "コメントだよ!", actual: コメント );
204+ }
205+ //----------------
206+ #endregion
207+ }
208+
209+ [TestMethod()]
210+ public void ReadFromStringTest()
211+ {
212+ // インライン
213+ {
214+ #region " 小節長倍率チップの検証 "
215+ //----------------
216+ {
217+ var score = DTXReader.ReadFromString( @"
218+#title: てすと
219+#00111: 01 ; HiHat_Close
220+#00202: 3.14 ; 小節長倍率
221+#00311: 02 ; HiHat_Close
222+" );
223+ Assert.AreEqual( 5, score.小節長倍率リスト.Count ); // 譜面の小節数(0~3の4個)+最後の小節線だけのために自動追加される空の小節1個
224+
225+ // 小節長倍率は、指定した小節以降すべての小節で有効。(DTX仕様; SSTではその小節のみ有効なので注意)
226+ // --> 上のサンプルでは、小節 02 で x3.14 を指定しているだけだが小節 03 も x3.14 になる。
227+ var 倍率s = new float[ 5 ] { 1f, 1f, 3.14f, 3.14f, 1f }; // また、一番最後の小節長倍率は 1f で固定。拍線はない小節なので、見た目も問題ない。(SST仕様)
228+
229+ for( int i = 0; i < score.小節長倍率リスト.Count; i++ )
230+ Assert.AreEqual( 倍率s[ i ], score.小節長倍率リスト[ i ] );
231+ }
232+ //----------------
233+ #endregion
234+ #region " BPM, BASEBPM, ch03, ch08 チップの検証 "
235+ //----------------
236+ {
237+ var score = DTXReader.ReadFromString( @"
238+#title: てすと
239+#basebpm 220
240+#bpm 140
241+#bpm01: 132.4
242+#00003: 10
243+#00103: 20
244+#00208: 01
245+" );
246+ var chips = from chip in score.チップリスト
247+ where ( chip.チップ種別 == チップ種別.BPM )
248+ orderby chip.小節番号
249+ select chip;
250+
251+ Assert.AreEqual( 4, chips.Count() ); // #BPM:x1, ch03x2, ch08x1
252+
253+ // #BPM には #BASEBPM が加算されないこと。
254+ Assert.AreEqual( 140f, chips.ElementAt( 0 ).BPM );
255+
256+ // ch03 には #BASEBPM が加算されていること。
257+ Assert.AreEqual( ( 220f + 0x10 ), chips.ElementAt( 1 ).BPM );
258+ Assert.AreEqual( ( 220f + 0x20 ), chips.ElementAt( 2 ).BPM );
259+
260+ // ch08 には #BASEBPM が加算されていること。
261+ Assert.AreEqual( ( 220f + 132.4f ), chips.ElementAt( 3 ).BPM );
262+ }
263+ //----------------
264+ #endregion
265+ #region " PAN, WAVPAN の検証 "
266+ //----------------
267+ {
268+ var score = DTXReader.ReadFromString( @"
269+#title: てすと
270+#pan01: 10
271+#pan02: 20
272+#pan03: 100
273+#wavpan04: -100
274+#pan05: 101
275+#00111: 010203040506
276+" );
277+ var chips = from chip in score.チップリスト
278+ where ( chip.チップ種別 == チップ種別.HiHat_Close )
279+ orderby chip.小節内位置
280+ select chip;
281+
282+ Assert.AreEqual( 6, chips.Count() ); // ch.11 × 6
283+
284+ #region " チップ[0]について検証 "
285+ //----------------
286+ {
287+ var chip = chips.ElementAt( 0 );
288+
289+ // オブジェクト値 01 がチップサブIDプロパティに格納されていること。
290+ Assert.AreEqual( 1, chip.チップサブID );
291+
292+ // 位置が 10 であること。
293+ Assert.AreEqual( 10, chip.位置 );
294+ }
295+ //----------------
296+ #endregion
297+ #region " チップ[1]について検証 "
298+ //----------------
299+ {
300+ var chip = chips.ElementAt( 1 );
301+
302+ // オブジェクト値 02 がチップサブIDプロパティに格納されていること。
303+ Assert.AreEqual( 2, chip.チップサブID );
304+
305+ // 位置が 20 であること。
306+ Assert.AreEqual( 20, chip.位置 );
307+ }
308+ //----------------
309+ #endregion
310+ #region " チップ[2]について検証 "
311+ //----------------
312+ {
313+ var chip = chips.ElementAt( 2 );
314+
315+ // オブジェクト値 03 がチップサブIDプロパティに格納されていること。
316+ Assert.AreEqual( 3, chip.チップサブID );
317+
318+ // 位置が 100 であること。
319+ Assert.AreEqual( 100, chip.位置 );
320+ }
321+ //----------------
322+ #endregion
323+ #region " チップ[3]について検証 "
324+ //----------------
325+ {
326+ var chip = chips.ElementAt( 3 );
327+
328+ // オブジェクト値 04 がチップサブIDプロパティに格納されていること。
329+ Assert.AreEqual( 4, chip.チップサブID );
330+
331+ // 位置が -100 であること。(#WAVPAN に対応していること。)
332+ Assert.AreEqual( -100, chip.位置 );
333+ }
334+ //----------------
335+ #endregion
336+ #region " チップ[4]について検証 "
337+ //----------------
338+ {
339+ var chip = chips.ElementAt( 4 );
340+
341+ // オブジェクト値 05 がチップサブIDプロパティに格納されていること。
342+ Assert.AreEqual( 5, chip.チップサブID );
343+
344+ // 位置が 100 であること。(-100~+100 の範囲外の値は、この範囲内に丸められる。)
345+ Assert.AreEqual( 100, chip.位置 );
346+ }
347+ //----------------
348+ #endregion
349+ #region " チップ[5]について検証 "
350+ //----------------
351+ {
352+ var chip = chips.ElementAt( 5 );
353+
354+ // オブジェクト値 06 がチップサブIDプロパティに格納されていること。
355+ Assert.AreEqual( 6, chip.チップサブID );
356+
357+ // 位置が 0 であること。(#PAN/#WAVPANの指定がない場合の規定値)
358+ Assert.AreEqual( 0, chip.位置 );
359+ }
360+ //----------------
361+ #endregion
362+ }
363+ //----------------
364+ #endregion
365+ #region " VOLUME, WAVVOL の検証 "
366+ //----------------
367+ {
368+ var score = DTXReader.ReadFromString( @"
369+#title: てすと
370+#volume01: 10
371+#volume02: 20
372+#volume03: 100
373+#wavvol04: 0
374+#volume05: 101
375+#00111: 010203040506
376+" );
377+ var chips = from chip in score.チップリスト
378+ where ( chip.チップ種別 == チップ種別.HiHat_Close )
379+ orderby chip.小節内位置
380+ select chip;
381+
382+ Assert.AreEqual( 6, chips.Count() ); // ch.11 × 6
383+
384+ #region " チップ[0]について検証 "
385+ //----------------
386+ {
387+ var chip = chips.ElementAt( 0 );
388+
389+ // オブジェクト値 01 がチップサブIDプロパティに格納されていること。
390+ Assert.AreEqual( 1, chip.チップサブID );
391+
392+ // 音量が 1 であること。
393+ Assert.AreEqual( 1, chip.音量 ); // 8×音量(10/100) = 0.8, 切り上げて 1 。
394+ }
395+ //----------------
396+ #endregion
397+ #region " チップ[1]について検証 "
398+ //----------------
399+ {
400+ var chip = chips.ElementAt( 1 );
401+
402+ // オブジェクト値 02 がチップサブIDプロパティに格納されていること。
403+ Assert.AreEqual( 2, chip.チップサブID );
404+
405+ // 音量が 2 であること。
406+ Assert.AreEqual( 2, chip.音量 ); // 8×音量(20/100) = 1.6, 切り上げて 2 。
407+ }
408+ //----------------
409+ #endregion
410+ #region " チップ[2]について検証 "
411+ //----------------
412+ {
413+ var chip = chips.ElementAt( 2 );
414+
415+ // オブジェクト値 03 がチップサブIDプロパティに格納されていること。
416+ Assert.AreEqual( 3, chip.チップサブID );
417+
418+ // 音量が 8 であること。
419+ Assert.AreEqual( 8, chip.音量 ); // 8×音量(100/100) = 8.0, 切り上げても 8 。
420+ }
421+ //----------------
422+ #endregion
423+ #region " チップ[3]について検証 "
424+ //----------------
425+ {
426+ var chip = chips.ElementAt( 3 );
427+
428+ // オブジェクト値 04 がチップサブIDプロパティに格納されていること。
429+ Assert.AreEqual( 4, chip.チップサブID );
430+
431+ // 音量が 1 であること。
432+ Assert.AreEqual( 1, chip.音量 ); // 8×音量(1/100) = 0.08, 切り上げて 1 。(DTX音量 0 は SSTF音量で 1 になること)
433+ }
434+ //----------------
435+ #endregion
436+ #region " チップ[4]について検証 "
437+ //----------------
438+ {
439+ var chip = chips.ElementAt( 4 );
440+
441+ // オブジェクト値 05 がチップサブIDプロパティに格納されていること。
442+ Assert.AreEqual( 5, chip.チップサブID );
443+
444+ // 音量が 8 であること。
445+ Assert.AreEqual( 8, chip.音量 ); // 8×音量(100/100) = 8 。DTX音量101は100に丸められること。
446+ }
447+ //----------------
448+ #endregion
449+ #region " チップ[5]について検証 "
450+ //----------------
451+ {
452+ var chip = chips.ElementAt( 5 );
453+
454+ // オブジェクト値 06 がチップサブIDプロパティに格納されていること。
455+ Assert.AreEqual( 6, chip.チップサブID );
456+
457+ // 音量が 8 であること。
458+ Assert.AreEqual( 8, chip.音量 ); // 8×音量(100/100) = 8 。DTXで音量の指定がない場合の規定値は100。
459+ }
460+ //----------------
461+ #endregion
462+ }
463+ //----------------
464+ #endregion
465+ #region " #PATH_WAV の検証 "
466+ //----------------
467+ {
468+ var score = DTXReader.ReadFromString( @"
469+#title: てすと
470+#path_wav: テストフォルダ
471+#00111: 010203040506
472+" );
473+ Assert.AreEqual( @"テストフォルダ\", score.PATH_WAV );
474+ }
475+ //----------------
476+ #endregion
477+ #region " #WAV の検証 "
478+ //----------------
479+ {
480+ var score = DTXReader.ReadFromString( @"
481+#title: てすと
482+#wav01: snare.wav
483+#wav02: bass.wav
484+#00111: 010203
485+" );
486+ Assert.AreEqual( @"snare.wav", score.dicWAVファイルパス[ 1 ] );
487+ Assert.AreEqual( @"bass.wav", score.dicWAVファイルパス[ 2 ] );
488+ this.例外が出れば成功( () => { var p = score.dicWAVファイルパス[ 3 ]; } );
489+
490+ score = DTXReader.ReadFromString( @"
491+#title: てすと
492+#wav01: snare.wav
493+#path_wav: sounds
494+#wav02: bass.wav
495+#00111: 010203
496+" );
497+ // PATH_WAV はまだどちらにも反映されないこと。
498+ Assert.AreEqual( @"snare.wav", score.dicWAVファイルパス[ 1 ] );
499+ Assert.AreEqual( @"bass.wav", score.dicWAVファイルパス[ 2 ] );
500+ }
501+ //----------------
502+ #endregion
503+ }
504+ }
505+
506+ [TestMethod()]
507+ public void 小節番号とチャンネル番号を取得するTest()
508+ {
509+ int 小節番号, チャンネル番号;
510+
511+ #region " 基本形。"
512+ //----------------
513+ {
514+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "01234", out 小節番号, out チャンネル番号 ) );
515+ Assert.AreEqual( expected: 12, actual: 小節番号 );
516+ Assert.AreEqual( expected: 3 * 16 + 4, actual: チャンネル番号 );
517+ }
518+ //----------------
519+ #endregion
520+ #region " 小節番号は 000 ~ Z99 (36進数1桁&10進数2桁)であること。"
521+ //----------------
522+ {
523+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "00001", out 小節番号, out チャンネル番号 ) );
524+ Assert.AreEqual( expected: 0, actual: 小節番号 );
525+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
526+
527+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "09901", out 小節番号, out チャンネル番号 ) );
528+ Assert.AreEqual( expected: 99, actual: 小節番号 );
529+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
530+
531+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "10001", out 小節番号, out チャンネル番号 ) );
532+ Assert.AreEqual( expected: 100, actual: 小節番号 );
533+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
534+
535+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "19901", out 小節番号, out チャンネル番号 ) );
536+ Assert.AreEqual( expected: 199, actual: 小節番号 );
537+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
538+
539+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "99901", out 小節番号, out チャンネル番号 ) );
540+ Assert.AreEqual( expected: 999, actual: 小節番号 );
541+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
542+
543+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "A0001", out 小節番号, out チャンネル番号 ) ); // A00 == 1000
544+ Assert.AreEqual( expected: 1000, actual: 小節番号 );
545+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
546+
547+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "A9901", out 小節番号, out チャンネル番号 ) );
548+ Assert.AreEqual( expected: 10 * 100 + 99, actual: 小節番号 );
549+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
550+
551+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "A9901", out 小節番号, out チャンネル番号 ) );
552+ Assert.AreEqual( expected: 10 * 100 + 99, actual: 小節番号 );
553+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
554+
555+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "Z9901", out 小節番号, out チャンネル番号 ) ); // Z99 == 3599(最大小節番号)
556+ Assert.AreEqual( expected: 35 * 100 + 99, actual: 小節番号 );
557+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
558+
559+ Assert.IsFalse( DTXReader._小節番号とチャンネル番号を取得する( "Z9A01", out 小節番号, out チャンネル番号 ) ); // NG; Z99 の次はない
560+
561+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "z9901", out 小節番号, out チャンネル番号 ) ); // 小文字でもOK
562+ Assert.AreEqual( expected: 35 * 100 + 99, actual: 小節番号 );
563+ Assert.AreEqual( expected: 1, actual: チャンネル番号 );
564+ }
565+ //----------------
566+ #endregion
567+ #region " チャンネル番号は 00 ~ FF (16進数2桁)であること。"
568+ //----------------
569+ {
570+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "0009A", out 小節番号, out チャンネル番号 ) ); // OK
571+ Assert.AreEqual( expected: 0, actual: 小節番号 );
572+ Assert.AreEqual( expected: 9 * 16 + 10, actual: チャンネル番号 );
573+
574+ Assert.IsTrue( DTXReader._小節番号とチャンネル番号を取得する( "000FF", out 小節番号, out チャンネル番号 ) ); // OK
575+ Assert.AreEqual( expected: 0, actual: 小節番号 );
576+ Assert.AreEqual( expected: 255, actual: チャンネル番号 );
577+
578+ Assert.IsFalse( DTXReader._小節番号とチャンネル番号を取得する( "000FG", out 小節番号, out チャンネル番号 ) ); // NG
579+ }
580+ //----------------
581+ #endregion
582+ #region " 取得元文字列は、常に5文字であること。"
583+ //----------------
584+ {
585+ Assert.IsFalse( DTXReader._小節番号とチャンネル番号を取得する( "123456", out 小節番号, out チャンネル番号 ) ); // NG
586+ Assert.IsFalse( DTXReader._小節番号とチャンネル番号を取得する( "1234", out 小節番号, out チャンネル番号 ) ); // NG
587+ Assert.IsFalse( DTXReader._小節番号とチャンネル番号を取得する( "", out 小節番号, out チャンネル番号 ) ); // NG; 空文字もダメ。
588+ }
589+ //----------------
590+ #endregion
591+ }
592+
593+ [TestMethod()]
594+ public void DTX仕様の実数を取得するTest()
595+ {
596+ // DTX仕様の実数の定義(カルチャ非依存)
597+ //  小数点文字s = ".,"; // '.' の他に ',' も使える。
598+ //  桁区切り文字s = ".,' "; // 桁区切り文字が小数点文字と被ってるが、一番最後に現れたものだけが小数点として認識される。
599+
600+ float num = 0f;
601+
602+ // 整数(小数点なし)→ OK
603+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "123", out num ) );
604+ Assert.AreEqual( 123f, num );
605+
606+
607+ // 小数点(.)あり → OK
608+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "123.4", out num ) );
609+ Assert.AreEqual( 123.4f, num );
610+
611+ // 小数点(,)あり → OK
612+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "123,4", out num ) );
613+ Assert.AreEqual( 123.4f, num );
614+
615+
616+ // 桁区切りに見える小数点(,) → OK
617+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "1,234", out num ) );
618+ Assert.AreEqual( 1.234f, num ); // 1234 ではない。
619+
620+
621+ // 整数部なしの小数(.) → OK
622+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( ".1234", out num ) );
623+ Assert.AreEqual( 0.1234f, num );
624+
625+ // 整数部なしの小数(,) → OK
626+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( ",1234", out num ) );
627+ Assert.AreEqual( 0.1234f, num );
628+
629+
630+ // 小数部なしの小数(.) → OK
631+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "1234.", out num ) );
632+ Assert.AreEqual( 1234f, num );
633+
634+ // 小数部なしの小数(,) → OK
635+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "1234,", out num ) );
636+ Assert.AreEqual( 1234f, num );
637+
638+
639+ // 整数部に桁区切り(,)あり、小数点あり(.) → OK
640+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12,345.6", out num ) );
641+ Assert.AreEqual( 12345.6f, num );
642+
643+ // 整数部に桁区切り(,)あり、小数点あり(,) → OK
644+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12,345,6", out num ) );
645+ Assert.AreEqual( 12345.6f, num ); // 123456 ではない。
646+
647+ // 整数部に桁区切り(.)あり、小数点あり(,) → OK
648+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12.345,6", out num ) );
649+ Assert.AreEqual( 12345.6f, num ); // 12.3456 ではない。
650+
651+
652+ // 小数点(.)の連続 → OK
653+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12...345", out num ) );
654+ Assert.AreEqual( 12.345f, num ); // エラーではない。
655+
656+ // 小数点(,)の連続 → OK
657+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "123,,,45", out num ) );
658+ Assert.AreEqual( 123.45f, num ); // エラーではない。
659+
660+ // 小数点(.)の連続 → OK
661+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12...34..5", out num ) );
662+ Assert.AreEqual( 1234.5f, num ); // エラーではない。12.345 でもない。
663+
664+ // 小数点(,)の連続 → OK
665+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12,,,34,,5", out num ) );
666+ Assert.AreEqual( 1234.5f, num ); // エラーではない。12.345 でもない。
667+
668+ // 小数点(.,)の混在 → OK
669+ Assert.IsTrue( DTXReader._DTX仕様の実数を取得する( "12...34,,5", out num ) );
670+ Assert.AreEqual( 1234.5f, num ); // エラーではない。12.345 でもない。
671+ }
672+ }
673+}
\ No newline at end of file
--- /dev/null
+++ b/SSTFormatTests1/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
1+using System.Reflection;
2+using System.Runtime.CompilerServices;
3+using System.Runtime.InteropServices;
4+
5+// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6+// アセンブリに関連付けられている情報を変更するには、
7+// これらの属性値を変更してください。
8+[assembly: AssemblyTitle( "SSTFormatTests1" )]
9+[assembly: AssemblyDescription( "" )]
10+[assembly: AssemblyConfiguration( "" )]
11+[assembly: AssemblyCompany( "" )]
12+[assembly: AssemblyProduct( "SSTFormatTests1" )]
13+[assembly: AssemblyCopyright( "Copyright © 2017" )]
14+[assembly: AssemblyTrademark( "" )]
15+[assembly: AssemblyCulture( "" )]
16+
17+// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
18+// 参照できなくなります。このアセンブリ内で COM から型にアクセスする必要がある場合は、
19+// その型の ComVisible 属性を true に設定してください。
20+[assembly: ComVisible( false )]
21+
22+// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります
23+[assembly: Guid( "b1ed6f8e-cfe2-4f89-a526-db5937e30399" )]
24+
25+// アセンブリのバージョン情報は次の 4 つの値で構成されています:
26+//
27+// メジャー バージョン
28+// マイナー バージョン
29+// ビルド番号
30+// Revision
31+//
32+// すべての値を指定するか、以下のように '*' を使用してビルド番号とリビジョン番号を
33+// 既定値にすることができます:
34+//[アセンブリ: AssemblyVersion("1.0.*")]
35+[assembly: AssemblyVersion( "1.0.0.0" )]
36+[assembly: AssemblyFileVersion( "1.0.0.0" )]
--- /dev/null
+++ b/SSTFormatTests1/SSTFormatTests1.csproj
@@ -0,0 +1,103 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+ <Import Project="..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.props')" />
4+ <PropertyGroup>
5+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+ <ProjectGuid>{B1ED6F8E-CFE2-4F89-A526-DB5937E30399}</ProjectGuid>
8+ <OutputType>Library</OutputType>
9+ <AppDesignerFolder>Properties</AppDesignerFolder>
10+ <RootNamespace>SSTFormatTests1</RootNamespace>
11+ <AssemblyName>SSTFormatTests1</AssemblyName>
12+ <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
13+ <FileAlignment>512</FileAlignment>
14+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
15+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
16+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
17+ <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
18+ <IsCodedUITest>False</IsCodedUITest>
19+ <TestProjectType>UnitTest</TestProjectType>
20+ <TargetFrameworkProfile />
21+ <NuGetPackageImportStamp>
22+ </NuGetPackageImportStamp>
23+ </PropertyGroup>
24+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
25+ <DebugSymbols>true</DebugSymbols>
26+ <DebugType>full</DebugType>
27+ <Optimize>false</Optimize>
28+ <OutputPath>bin\Debug\</OutputPath>
29+ <DefineConstants>DEBUG;TRACE</DefineConstants>
30+ <ErrorReport>prompt</ErrorReport>
31+ <WarningLevel>4</WarningLevel>
32+ </PropertyGroup>
33+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
34+ <DebugType>pdbonly</DebugType>
35+ <Optimize>true</Optimize>
36+ <OutputPath>bin\Release\</OutputPath>
37+ <DefineConstants>TRACE</DefineConstants>
38+ <ErrorReport>prompt</ErrorReport>
39+ <WarningLevel>4</WarningLevel>
40+ </PropertyGroup>
41+ <ItemGroup>
42+ <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
43+ <HintPath>..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
44+ </Reference>
45+ <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
46+ <HintPath>..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
47+ </Reference>
48+ <Reference Include="System" />
49+ </ItemGroup>
50+ <Choose>
51+ <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
52+ <ItemGroup>
53+ <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
54+ </ItemGroup>
55+ </When>
56+ <Otherwise>
57+ <ItemGroup>
58+ <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
59+ </ItemGroup>
60+ </Otherwise>
61+ </Choose>
62+ <ItemGroup>
63+ <Compile Include="Properties\AssemblyInfo.cs" />
64+ </ItemGroup>
65+ <ItemGroup>
66+ <None Include="packages.config" />
67+ </ItemGroup>
68+ <Choose>
69+ <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
70+ <ItemGroup>
71+ <Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
72+ <Private>False</Private>
73+ </Reference>
74+ <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
75+ <Private>False</Private>
76+ </Reference>
77+ <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
78+ <Private>False</Private>
79+ </Reference>
80+ <Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
81+ <Private>False</Private>
82+ </Reference>
83+ </ItemGroup>
84+ </When>
85+ </Choose>
86+ <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
87+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
88+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
89+ <PropertyGroup>
90+ <ErrorText>このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。</ErrorText>
91+ </PropertyGroup>
92+ <Error Condition="!Exists('..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.props'))" />
93+ <Error Condition="!Exists('..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.targets'))" />
94+ </Target>
95+ <Import Project="..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\..\..\@DTXMania\DTXmatixx\packages\MSTest.TestAdapter.1.1.18\build\net45\MSTest.TestAdapter.targets')" />
96+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
97+ Other similar extension points exist, see Microsoft.Common.targets.
98+ <Target Name="BeforeBuild">
99+ </Target>
100+ <Target Name="AfterBuild">
101+ </Target>
102+ -->
103+</Project>
\ No newline at end of file
--- /dev/null
+++ b/SSTFormatTests1/packages.config
@@ -0,0 +1,5 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<packages>
3+ <package id="MSTest.TestAdapter" version="1.1.18" targetFramework="net452" />
4+ <package id="MSTest.TestFramework" version="1.1.18" targetFramework="net452" />
5+</packages>
\ No newline at end of file
--- /dev/null
+++ b/SSTFormatTests1/v3/DTXReaderTests.cs
@@ -0,0 +1,11 @@
1+using System;
2+using System.Collections.Generic;
3+using System.Diagnostics;
4+using System.Linq;
5+
6+namespace SSTFormatTests1.v3
7+{
8+ class DTXReaderTests
9+ {
10+ }
11+}
Show on old repository browser