From 05bc1c65f3418d53c5c977e23f3d68364df2c63c Mon Sep 17 00:00:00 2001 From: AutumnSky1010 <66455966+AutumnSky1010@users.noreply.github.com> Date: Tue, 24 Dec 2024 22:51:43 +0900 Subject: [PATCH 1/5] :+1: Add GeneratePartialWave() --- src/SoundMaker/Sounds/Track.cs | 121 ++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 10 deletions(-) diff --git a/src/SoundMaker/Sounds/Track.cs b/src/SoundMaker/Sounds/Track.cs index 4c731a5..d2eb774 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,80 @@ public short[] GenerateWave() return [.. result]; } + /// + /// 指定した範囲の波形を生成する。 + /// + /// 開始インデックス + /// 終了インデックス + /// 指定範囲の波形データ + 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); + + // currentComponentIndexがstartIndexより大きい場合は、開始位置を調製するために0-paddingする必要がある + 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 +227,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 +252,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 +272,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 +294,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 +317,7 @@ public void Clear() { WaveArrayLength = 0; _soundComponents.Clear(); + _waveArrayLengthPair.Clear(); } /// @@ -231,7 +328,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 +345,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; } From 4f68f1985e3e592aa6aa3d338f8f0e52c18a7a77 Mon Sep 17 00:00:00 2001 From: AutumnSky1010 <66455966+AutumnSky1010@users.noreply.github.com> Date: Tue, 24 Dec 2024 23:04:05 +0900 Subject: [PATCH 2/5] :arrow_double_down: Bufferable mono waveform generation function implemented --- src/SoundMaker/Sounds/TrackBaseSound.cs | 117 ++++++++++++++++++++---- 1 file changed, 97 insertions(+), 20 deletions(-) diff --git a/src/SoundMaker/Sounds/TrackBaseSound.cs b/src/SoundMaker/Sounds/TrackBaseSound.cs index d4f3ced..6034ad5 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,54 @@ 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); + } + + 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 +302,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,22 +319,61 @@ 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)); + left[i] += (short)(trackWave[i - track.StartIndex] * pan / concurrentTracksCount); + right[i] += (short)(trackWave[i - track.StartIndex] * (1 - pan) / concurrentTracksCount); + } + } + } + return new(right, left); + } - var amplitudeLeft = Math.Abs(left[i]); - var amplitudeRight = Math.Abs(right[i]); - maxAmplitudeRight = maxAmplitudeRight < amplitudeRight ? amplitudeRight : maxAmplitudeRight; - maxAmplitudeLeft = maxAmplitudeLeft < amplitudeLeft ? amplitudeLeft : maxAmplitudeLeft; + /// + /// Calculates the maximum number of overlapping tracks at any time.
+ /// 任意の時点で同時に再生されているトラック数の最大値を計算するメソッド。 + ///
+ /// The maximum number of overlapping tracks.
同時に再生されているトラック数の最大値。
+ private int GetMaxConcurrentTracks() + { + 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 normalizedRight = NormalizeAndClamp(right, maxAmplitudeRight); - var normalizedLeft = NormalizeAndClamp(left, maxAmplitudeLeft); - return new(normalizedRight, normalizedLeft); + // 時間順、同時刻なら終了イベントを先に処理 + events.Sort((a, b) => a.time == b.time ? a.type.CompareTo(b.type) : a.time.CompareTo(b.time)); + + // 最大同時トラック数を計算 + int maxConcurrentTracks = 0; + int currentTracks = 0; + + foreach (var (time, type) in events) + { + currentTracks += type; + maxConcurrentTracks = Math.Max(maxConcurrentTracks, currentTracks); + } + + return maxConcurrentTracks; } + /// /// Normalizes and clamps the wave data.
/// 波形データを正規化してクランプするメソッド。 From 57bdb9ebcec4659722b869450c6b2a1e504f1005 Mon Sep 17 00:00:00 2001 From: AutumnSky1010 <66455966+AutumnSky1010@users.noreply.github.com> Date: Mon, 30 Dec 2024 13:34:16 +0900 Subject: [PATCH 3/5] :arrow_double_down: Buffered stereo waveforms --- src/SoundMaker/Sounds/Track.cs | 13 +++- src/SoundMaker/Sounds/TrackBaseSound.cs | 91 ++++++++++++++++++------- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/SoundMaker/Sounds/Track.cs b/src/SoundMaker/Sounds/Track.cs index d2eb774..7c02980 100644 --- a/src/SoundMaker/Sounds/Track.cs +++ b/src/SoundMaker/Sounds/Track.cs @@ -159,12 +159,16 @@ public short[] GeneratePartialWave(int startIndex, int endIndex) return []; } + // 生成する波形の長さを計算する var expectedLength = endIndex - startIndex + 1; - /** 探索用インデクス */ + // 現在探索中のコンポーネント開始インデクス int currentComponentIndex = StartIndex; + + // 生成した波形データを格納するリスト var result = new List(); + // 余分に生成した波形の長さ(データの先頭側) var unnecessaryLengthFirst = 0; foreach (var soundComponent in _soundComponents) { @@ -189,19 +193,22 @@ public short[] GeneratePartialWave(int startIndex, int endIndex) break; } + // 以下、コンポーネントが必要な範囲に含まれる場合の処理 var wave = soundComponent.GenerateWave(_format, _tempo, WaveType); if (result.Count == 0) { unnecessaryLengthFirst = Math.Max(startIndex - currentComponentIndex, 0); - // currentComponentIndexがstartIndexより大きい場合は、開始位置を調製するために0-paddingする必要がある + // 開始インデクスより先に波形がある場合は0で埋めて、波形の開始位置をあわせる if (currentComponentIndex > startIndex) { result.AddRange(Enumerable.Repeat(0, currentComponentIndex - startIndex)); } } + // 生成した波形を追加 result.AddRange(wave); + // 次のコンポーネントへ進む currentComponentIndex = nextComponentIndex; // 必要な範囲の波形をすべて取得したら終了 @@ -211,12 +218,14 @@ public short[] GeneratePartialWave(int startIndex, int endIndex) } } + // 最初の不要な長さをスキップする var skipped = result.Skip(unnecessaryLengthFirst).ToArray(); if (skipped.Length <= expectedLength) { return skipped; } + // 波形後ろ側の余分な長さをスキップする return skipped.Take(expectedLength).ToArray(); } diff --git a/src/SoundMaker/Sounds/TrackBaseSound.cs b/src/SoundMaker/Sounds/TrackBaseSound.cs index 6034ad5..4170c1e 100644 --- a/src/SoundMaker/Sounds/TrackBaseSound.cs +++ b/src/SoundMaker/Sounds/TrackBaseSound.cs @@ -242,6 +242,16 @@ public MonauralWave GenerateMonauralWave() 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) @@ -327,6 +337,62 @@ public StereoWave GenerateStereoWave() return new(right, left); } + /// + /// 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[maxEndIndex + 1]; + var left = new short[maxEndIndex + 1]; + 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 - track.StartIndex] * pan / concurrentTracksCount); + right[i] += (short)(trackWave[i - track.StartIndex] * (1 - pan) / concurrentTracksCount); + } + } + } + + yield return new(right, left); + } + } + /// /// Calculates the maximum number of overlapping tracks at any time.
/// 任意の時点で同時に再生されているトラック数の最大値を計算するメソッド。 @@ -374,31 +440,6 @@ private int GetMaxConcurrentTracks() } - /// - /// Normalizes and clamps the wave data.
- /// 波形データを正規化してクランプするメソッド。 - ///
- /// The wave data.
波形データ。 - /// The normalized and clamped wave data.
正規化およびクランプされた波形データ。
- private static short[] NormalizeAndClamp(long[] wave, long maxAmplitude) - { - const int MaxValue = short.MaxValue; - const int MinValue = short.MinValue; - - var scaleFactor = maxAmplitude > MaxValue ? (double)MaxValue / maxAmplitude : 1.0; - - var normalizedWave = new short[wave.Length]; - - for (int i = 0; i < wave.Length; i++) - { - var scaledSample = wave[i] * scaleFactor; - normalizedWave[i] = (short)Math.Clamp(scaledSample, MinValue, MaxValue); - } - - return normalizedWave; - } - - /// /// Imports tracks into the internal map based on their start times.
/// トラックを開始時間に基づいて内部のマップにインポートするメソッド。 From 681a87b401f40114ab51dd9d5bf82230860b6fa1 Mon Sep 17 00:00:00 2001 From: AutumnSky1010 <66455966+AutumnSky1010@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:53:23 +0900 Subject: [PATCH 4/5] :bug: Fixed a bug that caused incorrect index calculations --- src/SoundMaker/Sounds/TrackBaseSound.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SoundMaker/Sounds/TrackBaseSound.cs b/src/SoundMaker/Sounds/TrackBaseSound.cs index 4170c1e..91d2e05 100644 --- a/src/SoundMaker/Sounds/TrackBaseSound.cs +++ b/src/SoundMaker/Sounds/TrackBaseSound.cs @@ -329,8 +329,8 @@ public StereoWave GenerateStereoWave() var pan = (track.Pan + 1) / 2.0f; for (int i = track.StartIndex; i <= track.EndIndex; i++) { - left[i] += (short)(trackWave[i - track.StartIndex] * pan / concurrentTracksCount); - right[i] += (short)(trackWave[i - track.StartIndex] * (1 - pan) / concurrentTracksCount); + left[i] += (short)((trackWave[i - track.StartIndex] / concurrentTracksCount) * pan); + right[i] += (short)((trackWave[i - track.StartIndex] / concurrentTracksCount) * (1 - pan)); } } } @@ -363,8 +363,8 @@ public IEnumerable GenerateBufferedStereoWave(int startIndex, int bu for (int seekIndex = startIndex; seekIndex <= maxEndIndex; seekIndex += bufferSize) { - var right = new short[maxEndIndex + 1]; - var left = new short[maxEndIndex + 1]; + var right = new short[bufferSize]; + var left = new short[bufferSize]; foreach (var (_, tracks) in _tracksTimeMap) { foreach (var track in tracks) @@ -383,8 +383,8 @@ public IEnumerable GenerateBufferedStereoWave(int startIndex, int bu var pan = (track.Pan + 1) / 2.0f; for (int i = 0; i < trackWave.Length; i++) { - left[i] += (short)(trackWave[i - track.StartIndex] * pan / concurrentTracksCount); - right[i] += (short)(trackWave[i - track.StartIndex] * (1 - pan) / concurrentTracksCount); + left[i] += (short)((trackWave[i] / concurrentTracksCount) * pan); + right[i] += (short)((trackWave[i] / concurrentTracksCount) * (1 - pan)); } } } From f90ea35f973a322b69ee4b18c7c6de6f2e9689aa Mon Sep 17 00:00:00 2001 From: AutumnSky1010 <66455966+AutumnSky1010@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:55:29 +0900 Subject: [PATCH 5/5] :recycle: Comment translated into English --- src/SoundMaker/Sounds/Track.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/SoundMaker/Sounds/Track.cs b/src/SoundMaker/Sounds/Track.cs index 7c02980..ac560be 100644 --- a/src/SoundMaker/Sounds/Track.cs +++ b/src/SoundMaker/Sounds/Track.cs @@ -146,11 +146,13 @@ public short[] GenerateWave() } /// - /// 指定した範囲の波形を生成する。 + /// 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) { // 入力検証