From 72b4a1637837e6235a1cde14d776cbde3e022e8d Mon Sep 17 00:00:00 2001 From: Alexander Shabarshov Date: Sat, 25 Jul 2026 11:02:03 +0100 Subject: [PATCH] Fixed mono stems mixing --- ABStemPlayer/Views/PlaybackControls.axaml | 48 +++++++-------------- AudioCore/Impl/AudioMixer.cs | 26 ++++++++--- AudioCore/Impl/StemPlaybackEngine.cs | 12 +++++- AudioCore_Tests/AudioMixer_Tests.cs | 2 +- AudioCore_Tests/StemPlaybackEngine_Tests.cs | 4 +- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/ABStemPlayer/Views/PlaybackControls.axaml b/ABStemPlayer/Views/PlaybackControls.axaml index 8122128..cf8fa64 100644 --- a/ABStemPlayer/Views/PlaybackControls.axaml +++ b/ABStemPlayer/Views/PlaybackControls.axaml @@ -194,49 +194,33 @@ Margin="0,0,6,0"/> diff --git a/AudioCore/Impl/AudioMixer.cs b/AudioCore/Impl/AudioMixer.cs index 1765f0a..74ce338 100644 --- a/AudioCore/Impl/AudioMixer.cs +++ b/AudioCore/Impl/AudioMixer.cs @@ -50,18 +50,34 @@ public sealed class AudioMixer : IAudioMixer for (var i = 0; i < frames; i++) { - if (i * inChannels + 1 >= inSpan.Length) + var baseIndex = i * inChannels; + + // If there are no samples for this frame, skip + if (baseIndex >= inSpan.Length) { outSpan[i * 2 + 0] = 0f; outSpan[i * 2 + 1] = 0f; continue; } - var l = inChannels > 1 ? inSpan[i * inChannels + 0] : inSpan[i]; - var r = inChannels > 1 ? inSpan[i * inChannels + 1] : inSpan[i]; + if (inChannels == 1) + { + var inp = inSpan[baseIndex]; + outSpan[i * 2 + 0] += inp * leftGain; + outSpan[i * 2 + 1] += inp * rightGain; + } + else + { + // ensure both left and right samples exist for multi-channel input + if (baseIndex + 1 >= inSpan.Length) + continue; // or treat missing right as 0, depending on desired behavior - outSpan[i * 2 + 0] += l * leftGain; - outSpan[i * 2 + 1] += r * rightGain; + var l = inSpan[baseIndex + 0]; + var r = inSpan[baseIndex + 1]; + + outSpan[i * 2 + 0] += l * leftGain; + outSpan[i * 2 + 1] += r * rightGain; + } } } diff --git a/AudioCore/Impl/StemPlaybackEngine.cs b/AudioCore/Impl/StemPlaybackEngine.cs index c1ae79e..ccfc513 100644 --- a/AudioCore/Impl/StemPlaybackEngine.cs +++ b/AudioCore/Impl/StemPlaybackEngine.cs @@ -343,9 +343,17 @@ public sealed class StemPlaybackEngine : IStemPlaybackEngine, IDisposable if (loopEnabled && loopEnd > loopStart && nextPosition >= loopEnd) { + // Rewind decoders to the loop start and continue decoding so playback loops + foreach (var d in decodersSnapshot) + { + try { d.Seek(loopStart); } catch { } + } + lock (_stateLock) - _decodedFramePosition = loopEnd; - break; + _decodedFramePosition = loopStart; + + // continue decoding from the loop start + continue; } lock (_stateLock) diff --git a/AudioCore_Tests/AudioMixer_Tests.cs b/AudioCore_Tests/AudioMixer_Tests.cs index 7256a44..caec258 100644 --- a/AudioCore_Tests/AudioMixer_Tests.cs +++ b/AudioCore_Tests/AudioMixer_Tests.cs @@ -147,7 +147,7 @@ public sealed class AudioMixer_Tests [TestMethod] [TestCategory("ProductionBugSuspected")] - [Ignore("ProductionBugSuspected")] + //[Ignore("ProductionBugSuspected")] public void Mixer_Handles_Mono_Stem() { // mono block diff --git a/AudioCore_Tests/StemPlaybackEngine_Tests.cs b/AudioCore_Tests/StemPlaybackEngine_Tests.cs index f40eb95..f52f9b1 100644 --- a/AudioCore_Tests/StemPlaybackEngine_Tests.cs +++ b/AudioCore_Tests/StemPlaybackEngine_Tests.cs @@ -302,7 +302,7 @@ public sealed class StemPlaybackEngine_Tests public async Task LoopRegion_SeeksBackOnBoundary() { var pool = new AudioBufferPool(); - var decoderFactory = new MockDecoderFactory(pool, 1024, 5); + var decoderFactory = new MockDecoderFactory(pool, 44100, 5); var output = new MockOutput(); var mixer = new MockMixer(pool); var stretch = new MockTimeStretch(); @@ -320,7 +320,7 @@ public sealed class StemPlaybackEngine_Tests await engine.LoadSessionAsync(session, new DummyProgressReporter()); await engine.PlayAsync(); - await Task.Delay(TimeSpan.FromSeconds(3)); + await Task.Delay(TimeSpan.FromSeconds(6)); await engine.StopAsync();