From 21e1f1e3f5667e87af952b13aa58e2e019283a78 Mon Sep 17 00:00:00 2001 From: Alexander Shabarshov Date: Sun, 19 Jul 2026 13:19:59 +0100 Subject: [PATCH] Added resampling for mp3 with bitrate that differs to 44100. --- .../ViewModels/WaveformBandViewModel.cs | 2 + ABStemPlayer/Views/MainWindow.axaml | 4 +- AudioCore/Impl/FfmpegProcess.cs | 2 +- AudioCore/Impl/Htdemucs6sSeparator.cs | 133 ++++++++++-------- AudioCore/Models/StemTrack.cs | 2 + 5 files changed, 84 insertions(+), 59 deletions(-) diff --git a/ABStemPlayer/ViewModels/WaveformBandViewModel.cs b/ABStemPlayer/ViewModels/WaveformBandViewModel.cs index 13d5c76..df6b69b 100644 --- a/ABStemPlayer/ViewModels/WaveformBandViewModel.cs +++ b/ABStemPlayer/ViewModels/WaveformBandViewModel.cs @@ -19,6 +19,8 @@ public partial class WaveformBandViewModel : ObservableObject private readonly StemTrack _stem; public string BandName => _stem.Name; + public override string ToString() => $"{Duration} {BandName} X:{PlaybackX}"; + [ObservableProperty] private double _canvasWidth; [ObservableProperty] private double _canvasHeight; [ObservableProperty] private Geometry? _waveformGeometry; diff --git a/ABStemPlayer/Views/MainWindow.axaml b/ABStemPlayer/Views/MainWindow.axaml index 6785b92..7a81833 100644 --- a/ABStemPlayer/Views/MainWindow.axaml +++ b/ABStemPlayer/Views/MainWindow.axaml @@ -39,9 +39,7 @@ Grid.Column="0" Margin="8"> - (_stemNames[i]), - Name = name, - FilePath = path, + Type = Enum.Parse(_stemNames[i]), + Name = name, + FilePath = path, SampleRate = _sampleRate, - Channels = _channels, - Duration = TimeSpan.FromSeconds((double)totalSamples / _sampleRate) + Channels = _channels, + Duration = TimeSpan.FromSeconds((double)totalSamples / _sampleRate) }); } @@ -150,36 +151,49 @@ public sealed class Htdemucs6sSeparator : IStemSeparator }; } - private StemSet? CheckExistingStems(StemSeparationRequest request) + private static float[,] LoadStereoFloatWave(string path, int totalSamples) { - var filesToCheck = Enum.GetNames(typeof(StemType)) - .Select(stemType => (stemType, Path.Combine(request.OutputDirectory, - $"{Path.GetFileNameWithoutExtension(request.SourceFilePath)}_{stemType}.flac"))) - .ToList(); + var result = new float[_channels, totalSamples]; - var stems = new List(); - var set = new StemSet + var cmd = + "-hide_banner -loglevel error " + + $"-i \"{path}\" " + + "-map a:0 " + + "-af aresample " + + "-f f32le -ac 2 -ar 44100 pipe:1"; + + using var ff = new FfmpegProcess( + name: $"decode:{Path.GetFileName(path)}", + commandLine: cmd, + redirectOutput: true, + redirectInput: false); + + ff.StartProcess(); + + var buffer = new float[4096 * _channels]; + var pos = 0; + + const float SCALE = 2f; + + while (true) { - OriginalFilePath = request.SourceFilePath, - Stems = stems - }; + var readFloats = ff.ReadAsync(buffer.AsMemory(), CancellationToken.None).GetAwaiter().GetResult(); + if (readFloats <= 0) + break; - foreach (var f in filesToCheck.Where(f => File.Exists(f.Item2))) - { - using var reader = new AudioFileReader(f.Item2); - - stems.Add(new StemTrack + var framesRead = readFloats / _channels; + for (var f = 0; f < framesRead && pos < totalSamples; f++, pos++) { - Type = Enum.Parse(f.Item1), - Name = Path.GetFileName(f.Item2), - FilePath = f.Item2, - SampleRate = reader.WaveFormat.SampleRate, - Channels = reader.WaveFormat.Channels, - Duration = reader.TotalTime - }); + var baseIndex = f * _channels; + result[0, pos] = buffer[baseIndex + 0] * SCALE; + result[1, pos] = buffer[baseIndex + 1] * SCALE; + } + + if (pos >= totalSamples) + break; } - return stems.Count > 0 ? set : null; + return result; } private static float[] MakeWindow(int n, int overlap) @@ -196,29 +210,6 @@ public sealed class Htdemucs6sSeparator : IStemSeparator return w; } - private static float[,] LoadStereoFloatWave(string path, out int sampleRate) - { - using var reader = new AudioFileReader(path); - sampleRate = reader.WaveFormat.SampleRate; - - var samples = new List(); - var buffer = new float[reader.WaveFormat.SampleRate * 4]; - int read; - while ((read = reader.Read(buffer, 0, buffer.Length)) > 0) - samples.AddRange(buffer.AsSpan(0, read)); - - var total = samples.Count / 2; - var result = new float[2, total]; - - for (var i = 0; i < total; i++) - { - result[0, i] = samples[2 * i]; - result[1, i] = samples[2 * i + 1]; - } - - return result; - } - private static void WriteFlac(string path, float[,,] stems, int stemIndex, int totalSamples) { var cmd = @@ -253,4 +244,36 @@ public sealed class Htdemucs6sSeparator : IStemSeparator ff.Proc!.WaitForExit(); } + + private StemSet? CheckExistingStems(StemSeparationRequest request) + { + var filesToCheck = Enum.GetNames(typeof(StemType)) + .Select(stemType => (stemType, Path.Combine(request.OutputDirectory, + $"{Path.GetFileNameWithoutExtension(request.SourceFilePath)}_{stemType}.flac"))) + .ToList(); + + var stems = new List(); + var set = new StemSet + { + OriginalFilePath = request.SourceFilePath, + Stems = stems + }; + + foreach (var f in filesToCheck.Where(f => File.Exists(f.Item2))) + { + using var reader = new AudioFileReader(f.Item2); + + stems.Add(new StemTrack + { + Type = Enum.Parse(f.Item1), + Name = Path.GetFileName(f.Item2), + FilePath = f.Item2, + SampleRate = reader.WaveFormat.SampleRate, + Channels = reader.WaveFormat.Channels, + Duration = reader.TotalTime + }); + } + + return stems.Count > 0 ? set : null; + } } diff --git a/AudioCore/Models/StemTrack.cs b/AudioCore/Models/StemTrack.cs index ec33bcb..f804d27 100644 --- a/AudioCore/Models/StemTrack.cs +++ b/AudioCore/Models/StemTrack.cs @@ -11,4 +11,6 @@ public sealed class StemTrack public float[] Waveform { get; set; } = []; public long TotalFrames => (long)(Duration.TotalMilliseconds * SampleRate / 1000.0); + + public override string ToString() => $"{Duration} {SampleRate}*{Channels} {Type}: {FilePath}"; }