Drop progress updates from superseded runs
A progress callback from a cancelled run could still land after the next run started and overwrite its phase. update now checks the run's generation, the same way the completion paths already did. Also clamp the right channel's copy to the buffer's length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: siGit Code <noreply@sigit.si>
Seto committed
Sep 26, 2026 at 14:51 UTC
197c48e2b482e1892c2d291a89d3c001dc309634
1 file changed
+7
-5
Examples/DemucsExample/SeparationViewModel.swift
+7
-5
@@ -49,8 +49,10 @@ final class SeparationViewModel: ObservableObject {
49
}
50
51
/// Progress arrives from background threads and can land after the step
52
- /// it describes has finished; only apply it while that step is current.
53
- private func update(_ progress: Phase) {
52
+ /// it describes has finished, or after a newer run has started; only
53
+ /// apply it while that run and step are current.
54
+ private func update(_ progress: Phase, generation current: Int) {
55
+ guard current == generation else { return }
56
switch (phase, progress) {
57
case (.downloading, .downloading), (.separating, .separating):
58
phase = progress
@@ -65,7 +67,7 @@ final class SeparationViewModel: ObservableObject {
67
if !DemucsEngine.isDownloaded(model) {
68
phase = .downloading(0)
69
try await DemucsEngine.download(model) { progress in
68
- Task { @MainActor in self.update(.downloading(progress.fraction)) }
70
+ Task { @MainActor in self.update(.downloading(progress.fraction), generation: current) }
71
}
72
}
73
if engine.loadedModel != model {
@@ -80,7 +82,7 @@ final class SeparationViewModel: ObservableObject {
82
right: audio.right,
83
sampleRate: audio.sampleRate
84
) { progress in
83
- Task { @MainActor in self.update(.separating(progress.fraction)) }
85
+ Task { @MainActor in self.update(.separating(progress.fraction), generation: current) }
86
}
87
88
let outputDirectory = FileManager.default.temporaryDirectory
@@ -154,7 +156,7 @@ enum AudioFile {
156
for (index, samples) in [stem.left, stem.right].enumerated() {
157
samples.withUnsafeBufferPointer { source in
158
guard let base = source.baseAddress else { return }
157
- channels[index].update(from: base, count: source.count)
159
+ channels[index].update(from: base, count: min(source.count, stem.left.count))
160
}
161
}
162