diff --git a/src/SoundMaker/Sounds/Track.cs b/src/SoundMaker/Sounds/Track.cs index 4c731a5..ac560be 100644 --- a/src/SoundMaker/Sounds/Track.cs +++ b/src/SoundMaker/Sounds/Track.cs @@ -11,6 +11,8 @@ public class Track { private List _soundComponents = []; + private Dictionary _waveArrayLengthPair = []; + private readonly SoundFormat _format; private readonly int _tempo; @@ -143,6 +145,91 @@ public short[] GenerateWave() return [.. result]; } + /// + /// Generates the waveform data for the specified range.
+ /// 指定した範囲の波形データを生成するメソッド。 + ///
+ /// The starting index of the range.
範囲の開始インデックス。 + /// The ending index of the range.
範囲の終了インデックス。 + /// The waveform data for the specified range.
指定範囲の波形データ。
+ + public short[] GeneratePartialWave(int startIndex, int endIndex) + { + // 入力検証 + if (startIndex < 0 || startIndex > endIndex) + { + return []; + } + + // 生成する波形の長さを計算する + var expectedLength = endIndex - startIndex + 1; + + // 現在探索中のコンポーネント開始インデクス + int currentComponentIndex = StartIndex; + + // 生成した波形データを格納するリスト + var result = new List(); + + // 余分に生成した波形の長さ(データの先頭側) + var unnecessaryLengthFirst = 0; + foreach (var soundComponent in _soundComponents) + { + // コンポーネントごとの波形長を取得 + int waveArrayLength = _waveArrayLengthPair.TryGetValue(soundComponent, out var value) + ? value + : soundComponent.GetWaveArrayLength(_format, _tempo); + + int nextComponentIndex = currentComponentIndex + waveArrayLength; + + // コンポーネントが必要な範囲に含まれない場合の処理 + if (startIndex >= nextComponentIndex) + { + // `startIndex` が現在のコンポーネントの終了位置より後の場合、次のコンポーネントへ進む + currentComponentIndex = nextComponentIndex; + continue; + } + + if (endIndex < currentComponentIndex) + { + // `endIndex`が現在のコンポーネントの開始位置より前の場合、ブレークする + break; + } + + // 以下、コンポーネントが必要な範囲に含まれる場合の処理 + var wave = soundComponent.GenerateWave(_format, _tempo, WaveType); + if (result.Count == 0) + { + unnecessaryLengthFirst = Math.Max(startIndex - currentComponentIndex, 0); + + // 開始インデクスより先に波形がある場合は0で埋めて、波形の開始位置をあわせる + if (currentComponentIndex > startIndex) + { + result.AddRange(Enumerable.Repeat(0, currentComponentIndex - startIndex)); + } + } + // 生成した波形を追加 + result.AddRange(wave); + + // 次のコンポーネントへ進む + currentComponentIndex = nextComponentIndex; + + // 必要な範囲の波形をすべて取得したら終了 + if (result.Count - unnecessaryLengthFirst >= expectedLength) + { + break; + } + } + + // 最初の不要な長さをスキップする + var skipped = result.Skip(unnecessaryLengthFirst).ToArray(); + if (skipped.Length <= expectedLength) + { + return skipped; + } + + // 波形後ろ側の余分な長さをスキップする + return skipped.Take(expectedLength).ToArray(); + } /// /// Adds a sound component to the track.
@@ -151,8 +238,10 @@ public short[] GenerateWave() /// The sound component to add.
追加するサウンドコンポーネント。 public void Add(ISoundComponent component) { - WaveArrayLength += component.GetWaveArrayLength(_format, _tempo); + var componentLength = component.GetWaveArrayLength(_format, _tempo); + WaveArrayLength += componentLength; _soundComponents.Add(component); + _waveArrayLengthPair.TryAdd(component, componentLength); } /// @@ -174,7 +263,9 @@ public void Insert(int index, ISoundComponent component) } _soundComponents.Insert(index, component); - WaveArrayLength += component.GetWaveArrayLength(_format, _tempo); + var componentLength = component.GetWaveArrayLength(_format, _tempo); + WaveArrayLength += componentLength; + _waveArrayLengthPair.TryAdd(component, componentLength); } /// @@ -192,7 +283,15 @@ public void RemoveAt(int index) var targetComponent = _soundComponents[index]; _soundComponents.Remove(targetComponent); - WaveArrayLength -= targetComponent.GetWaveArrayLength(_format, _tempo); + if (_waveArrayLengthPair.TryGetValue(targetComponent, out var componentLength)) + { + _waveArrayLengthPair.Remove(targetComponent); + WaveArrayLength -= componentLength; + } + else + { + WaveArrayLength -= targetComponent.GetWaveArrayLength(_format, _tempo); + } } /// @@ -206,7 +305,15 @@ public bool Remove(ISoundComponent component) var ok = _soundComponents.Remove(component); if (ok) { - WaveArrayLength -= component.GetWaveArrayLength(_format, _tempo); + if (_waveArrayLengthPair.TryGetValue(component, out var componentLength)) + { + _waveArrayLengthPair.Remove(component); + WaveArrayLength -= componentLength; + } + else + { + WaveArrayLength -= component.GetWaveArrayLength(_format, _tempo); + } return true; } @@ -221,6 +328,7 @@ public void Clear() { WaveArrayLength = 0; _soundComponents.Clear(); + _waveArrayLengthPair.Clear(); } /// @@ -231,7 +339,14 @@ public void Clear() public void Import(IEnumerable components) { _soundComponents = new List(components); - WaveArrayLength = components.Sum(component => component.GetWaveArrayLength(_format, _tempo)); + var sum = 0; + foreach (var component in components) + { + var componentLength = component.GetWaveArrayLength(_format, _tempo); + _waveArrayLengthPair.TryAdd(component, componentLength); + sum += componentLength; + } + WaveArrayLength = sum; } /// @@ -241,11 +356,8 @@ public void Import(IEnumerable components) /// A new instance of the track with the same properties.
同じプロパティを持つトラックの新しいインスタンス。
internal Track Clone() { - var copy = new Track(WaveType.Clone(), _format, _tempo, StartIndex) - { - WaveArrayLength = WaveArrayLength, - _soundComponents = _soundComponents.Select(component => component.Clone()).ToList() - }; + var copy = new Track(WaveType.Clone(), _format, _tempo, StartIndex); + copy.Import(_soundComponents.Select(component => component.Clone())); return copy; } diff --git a/src/SoundMaker/Sounds/TrackBaseSound.cs b/src/SoundMaker/Sounds/TrackBaseSound.cs index d4f3ced..91d2e05 100644 --- a/src/SoundMaker/Sounds/TrackBaseSound.cs +++ b/src/SoundMaker/Sounds/TrackBaseSound.cs @@ -219,8 +219,8 @@ public MonauralWave GenerateMonauralWave() .Where(track => track.Count != 0) .Max(track => track.EndIndex); - var wave = new long[maxEndIndex + 1]; - long maxAmplitude = 0; + var wave = new short[maxEndIndex + 1]; + var concurrentTracksCount = GetMaxConcurrentTracks(); foreach (var (_, tracks) in _tracksTimeMap) { @@ -234,15 +234,64 @@ public MonauralWave GenerateMonauralWave() var trackWave = track.GenerateWave(); for (int i = track.StartIndex; i <= track.EndIndex; i++) { - wave[i] += trackWave[i - track.StartIndex]; - var amplitude = Math.Abs(wave[i]); - maxAmplitude = maxAmplitude < amplitude ? amplitude : maxAmplitude; + wave[i] += (short)(trackWave[i - track.StartIndex] / concurrentTracksCount); } } } - var normalized = NormalizeAndClamp(wave, maxAmplitude); - return new(normalized); + return new(wave); + } + + /// + /// Generates buffered monaural waves from the tracks, starting at the specified index and using the specified buffer size.
+ /// 指定した開始インデックスから指定したバッファサイズを使用して、トラックからバッファリングされたモノラル波を生成するメソッド。 + ///
+ /// The starting index for the buffer.
バッファの開始インデックス。 + /// The size of the buffer.
バッファのサイズ。 + /// + /// An enumerable collection of buffered monaural waves.
+ /// バッファリングされたモノラル波の列挙可能なコレクション。 + ///
+ public IEnumerable GenerateBufferedMonauralWave(int startIndex, int bufferSize) + { + if (_tracksTimeMap.Count == 0) + { + yield break; + } + // 最大の終了時インデクスを取得する + var maxEndIndex = _tracksTimeMap + .SelectMany(pair => pair.Value) + .Where(track => track.Count != 0) + .Max(track => track.EndIndex); + + var concurrentTracksCount = GetMaxConcurrentTracks(); + for (int seekIndex = startIndex; seekIndex <= maxEndIndex; seekIndex += bufferSize) + { + var wave = new short[bufferSize]; + foreach (var (_, tracks) in _tracksTimeMap) + { + foreach (var track in tracks) + { + if (track.Count == 0) + { + continue; + } + + var trackWave = track.GeneratePartialWave(seekIndex, seekIndex + bufferSize - 1); + if (trackWave.Length == 0) + { + continue; + } + + for (int i = 0; i < trackWave.Length; i++) + { + wave[i] += (short)(trackWave[i] / concurrentTracksCount); + } + } + } + + yield return new(wave); + } } /// @@ -263,11 +312,10 @@ public StereoWave GenerateStereoWave() .Where(track => track.Count != 0) .Max(track => track.EndIndex); - var right = new long[maxEndIndex + 1]; - var left = new long[maxEndIndex + 1]; + var right = new short[maxEndIndex + 1]; + var left = new short[maxEndIndex + 1]; + var concurrentTracksCount = GetMaxConcurrentTracks(); - long maxAmplitudeRight = 0; - long maxAmplitudeLeft = 0; foreach (var (_, tracks) in _tracksTimeMap) { foreach (var track in tracks) @@ -281,44 +329,114 @@ public StereoWave GenerateStereoWave() var pan = (track.Pan + 1) / 2.0f; for (int i = track.StartIndex; i <= track.EndIndex; i++) { - left[i] += (long)(trackWave[i - track.StartIndex] * pan); - right[i] += (long)(trackWave[i - track.StartIndex] * (1 - pan)); - - var amplitudeLeft = Math.Abs(left[i]); - var amplitudeRight = Math.Abs(right[i]); - maxAmplitudeRight = maxAmplitudeRight < amplitudeRight ? amplitudeRight : maxAmplitudeRight; - maxAmplitudeLeft = maxAmplitudeLeft < amplitudeLeft ? amplitudeLeft : maxAmplitudeLeft; + left[i] += (short)((trackWave[i - track.StartIndex] / concurrentTracksCount) * pan); + right[i] += (short)((trackWave[i - track.StartIndex] / concurrentTracksCount) * (1 - pan)); } } } + return new(right, left); + } - var normalizedRight = NormalizeAndClamp(right, maxAmplitudeRight); - var normalizedLeft = NormalizeAndClamp(left, maxAmplitudeLeft); - return new(normalizedRight, normalizedLeft); + /// + /// Generates buffered stereo waves from the tracks, starting at the specified index and using the specified buffer size.
+ /// 指定した開始インデックスから指定したバッファサイズを使用して、トラックからバッファリングされたステレオ波を生成するメソッド。 + ///
+ /// The starting index for the buffer.
バッファの開始インデックス。 + /// The size of the buffer.
バッファのサイズ。 + /// + /// An enumerable collection of buffered stereo waves.
+ /// バッファリングされたステレオ波の列挙可能なコレクション。 + ///
+ public IEnumerable GenerateBufferedStereoWave(int startIndex, int bufferSize) + { + if (_tracksTimeMap.Count == 0) + { + yield break; + } + + // 最大の終了時インデクスを取得する + var maxEndIndex = _tracksTimeMap + .SelectMany(pair => pair.Value) + .Where(track => track.Count != 0) + .Max(track => track.EndIndex); + var concurrentTracksCount = GetMaxConcurrentTracks(); + + for (int seekIndex = startIndex; seekIndex <= maxEndIndex; seekIndex += bufferSize) + { + var right = new short[bufferSize]; + var left = new short[bufferSize]; + foreach (var (_, tracks) in _tracksTimeMap) + { + foreach (var track in tracks) + { + if (track.Count == 0) + { + continue; + } + + var trackWave = track.GeneratePartialWave(seekIndex, seekIndex + bufferSize - 1); + if (trackWave.Length == 0) + { + continue; + } + + var pan = (track.Pan + 1) / 2.0f; + for (int i = 0; i < trackWave.Length; i++) + { + left[i] += (short)((trackWave[i] / concurrentTracksCount) * pan); + right[i] += (short)((trackWave[i] / concurrentTracksCount) * (1 - pan)); + } + } + } + + yield return new(right, left); + } } /// - /// Normalizes and clamps the wave data.
- /// 波形データを正規化してクランプするメソッド。 + /// Calculates the maximum number of overlapping tracks at any time.
+ /// 任意の時点で同時に再生されているトラック数の最大値を計算するメソッド。 ///
- /// The wave data.
波形データ。 - /// The normalized and clamped wave data.
正規化およびクランプされた波形データ。
- private static short[] NormalizeAndClamp(long[] wave, long maxAmplitude) + /// The maximum number of overlapping tracks.
同時に再生されているトラック数の最大値。
+ private int GetMaxConcurrentTracks() { - const int MaxValue = short.MaxValue; - const int MinValue = short.MinValue; + if (_tracksTimeMap.Count == 0) + { + return 0; + } + + // イベントの開始と終了を記録するリスト + var events = new List<(int time, int type)>(); + + foreach (var (_, tracks) in _tracksTimeMap) + { + foreach (var track in tracks) + { + if (track.Count == 0) + { + continue; + } + + // 開始時に +1、終了後に -1 のイベントを追加 + events.Add((track.StartIndex, 1)); + events.Add((track.EndIndex + 1, -1)); // 終了インデックスの次の時点で減少 + } + } - var scaleFactor = maxAmplitude > MaxValue ? (double)MaxValue / maxAmplitude : 1.0; + // 時間順、同時刻なら終了イベントを先に処理 + events.Sort((a, b) => a.time == b.time ? a.type.CompareTo(b.type) : a.time.CompareTo(b.time)); - var normalizedWave = new short[wave.Length]; + // 最大同時トラック数を計算 + int maxConcurrentTracks = 0; + int currentTracks = 0; - for (int i = 0; i < wave.Length; i++) + foreach (var (time, type) in events) { - var scaledSample = wave[i] * scaleFactor; - normalizedWave[i] = (short)Math.Clamp(scaledSample, MinValue, MaxValue); + currentTracks += type; + maxConcurrentTracks = Math.Max(maxConcurrentTracks, currentTracks); } - return normalizedWave; + return maxConcurrentTracks; }