Added resampling for mp3 with bitrate that differs to 44100.

This commit is contained in:
Alexander Shabarshov 2026-07-19 13:19:59 +01:00
parent 7efd223a6c
commit 21e1f1e3f5
5 changed files with 84 additions and 59 deletions

View File

@ -19,6 +19,8 @@ public partial class WaveformBandViewModel : ObservableObject
private readonly StemTrack _stem; private readonly StemTrack _stem;
public string BandName => _stem.Name; public string BandName => _stem.Name;
public override string ToString() => $"{Duration} {BandName} X:{PlaybackX}";
[ObservableProperty] private double _canvasWidth; [ObservableProperty] private double _canvasWidth;
[ObservableProperty] private double _canvasHeight; [ObservableProperty] private double _canvasHeight;
[ObservableProperty] private Geometry? _waveformGeometry; [ObservableProperty] private Geometry? _waveformGeometry;

View File

@ -39,9 +39,7 @@
Grid.Column="0" Grid.Column="0"
Margin="8"> Margin="8">
<ScrollViewer Grid.Row="1" <ScrollViewer Margin="8"
Grid.Column="0"
Margin="8"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Bands}" <ItemsControl ItemsSource="{Binding Bands}"

View File

@ -27,7 +27,7 @@ public sealed class FfmpegProcess : IDisposable
public void StartProcess() public void StartProcess()
{ {
Debug.WriteLine($"{_name}: Starting ffmpeg process"); Debug.WriteLine($"{_name}: Starting ffmpeg process: {_commandLine.Replace("\r", "").Replace("\n", " ").Replace("\t", " ")}");
var psi = new ProcessStartInfo var psi = new ProcessStartInfo
{ {

View File

@ -26,11 +26,12 @@ public sealed class Htdemucs6sSeparator : IStemSeparator
if (existingStems != null) if (existingStems != null)
return existingStems; return existingStems;
var mix = LoadStereoFloatWave(request.SourceFilePath, out var sr); var probe = FfprobeProcess.ProbeAudio(request.SourceFilePath);
//if (sr != _sampleRate)
// throw new InvalidOperationException($"Input must be {_sampleRate} Hz");
var totalSamples = mix.GetLength(1); int totalSamples = (int)(probe.Duration.TotalSeconds * _sampleRate);
// Decode whole file to stereo float array via ffmpeg, resampled to 44.1kHz.
var mix = LoadStereoFloatWave(request.SourceFilePath, totalSamples);
var opts = new SessionOptions(); var opts = new SessionOptions();
opts.AppendExecutionProvider_CPU(); opts.AppendExecutionProvider_CPU();
@ -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)) var result = new float[_channels, totalSamples];
.Select(stemType => (stemType, Path.Combine(request.OutputDirectory,
$"{Path.GetFileNameWithoutExtension(request.SourceFilePath)}_{stemType}.flac")))
.ToList();
var stems = new List<StemTrack>(); var cmd =
var set = new StemSet "-hide_banner -loglevel error " +
{ $"-i \"{path}\" " +
OriginalFilePath = request.SourceFilePath, "-map a:0 " +
Stems = stems "-af aresample " +
}; "-f f32le -ac 2 -ar 44100 pipe:1";
foreach (var f in filesToCheck.Where(f => File.Exists(f.Item2))) using var ff = new FfmpegProcess(
{ name: $"decode:{Path.GetFileName(path)}",
using var reader = new AudioFileReader(f.Item2); commandLine: cmd,
redirectOutput: true,
redirectInput: false);
stems.Add(new StemTrack ff.StartProcess();
var buffer = new float[4096 * _channels];
var pos = 0;
const float SCALE = 2f;
while (true)
{ {
Type = Enum.Parse<StemType>(f.Item1), var readFloats = ff.ReadAsync(buffer.AsMemory(), CancellationToken.None).GetAwaiter().GetResult();
Name = Path.GetFileName(f.Item2), if (readFloats <= 0)
FilePath = f.Item2, break;
SampleRate = reader.WaveFormat.SampleRate,
Channels = reader.WaveFormat.Channels, var framesRead = readFloats / _channels;
Duration = reader.TotalTime for (var f = 0; f < framesRead && pos < totalSamples; f++, pos++)
}); {
var baseIndex = f * _channels;
result[0, pos] = buffer[baseIndex + 0] * SCALE;
result[1, pos] = buffer[baseIndex + 1] * SCALE;
} }
return stems.Count > 0 ? set : null; if (pos >= totalSamples)
break;
}
return result;
} }
private static float[] MakeWindow(int n, int overlap) private static float[] MakeWindow(int n, int overlap)
@ -196,29 +210,6 @@ public sealed class Htdemucs6sSeparator : IStemSeparator
return w; 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<float>();
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) private static void WriteFlac(string path, float[,,] stems, int stemIndex, int totalSamples)
{ {
var cmd = var cmd =
@ -253,4 +244,36 @@ public sealed class Htdemucs6sSeparator : IStemSeparator
ff.Proc!.WaitForExit(); 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<StemTrack>();
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<StemType>(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;
}
} }

View File

@ -11,4 +11,6 @@ public sealed class StemTrack
public float[] Waveform { get; set; } = []; public float[] Waveform { get; set; } = [];
public long TotalFrames => (long)(Duration.TotalMilliseconds * SampleRate / 1000.0); public long TotalFrames => (long)(Duration.TotalMilliseconds * SampleRate / 1000.0);
public override string ToString() => $"{Duration} {SampleRate}*{Channels} {Type}: {FilePath}";
} }