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;
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;

View File

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

View File

@ -27,7 +27,7 @@ public sealed class FfmpegProcess : IDisposable
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
{

View File

@ -26,11 +26,12 @@ public sealed class Htdemucs6sSeparator : IStemSeparator
if (existingStems != null)
return existingStems;
var mix = LoadStereoFloatWave(request.SourceFilePath, out var sr);
//if (sr != _sampleRate)
// throw new InvalidOperationException($"Input must be {_sampleRate} Hz");
var probe = FfprobeProcess.ProbeAudio(request.SourceFilePath);
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();
opts.AppendExecutionProvider_CPU();
@ -134,12 +135,12 @@ public sealed class Htdemucs6sSeparator : IStemSeparator
result.Add(new StemTrack
{
Type = Enum.Parse<StemType>(_stemNames[i]),
Name = name,
FilePath = path,
Type = Enum.Parse<StemType>(_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<StemTrack>();
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<StemType>(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<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)
{
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<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 long TotalFrames => (long)(Duration.TotalMilliseconds * SampleRate / 1000.0);
public override string ToString() => $"{Duration} {SampleRate}*{Channels} {Type}: {FilePath}";
}