main
swift 66 lines 2.24 KB
Raw
1 import Demucs
2 import SwiftUI
3 import UniformTypeIdentifiers
4
5 struct ContentView: View {
6 @StateObject private var viewModel = SeparationViewModel()
7 @State private var importing = false
8
9 var body: some View {
10 NavigationStack {
11 Form {
12 Section("Model") {
13 Picker("Model", selection: $viewModel.model) {
14 ForEach(DemucsEngine.models) { info in
15 Text("\(info.label) · \(info.sizeMB) MB").tag(info.model)
16 }
17 }
18 .disabled(viewModel.isBusy)
19 }
20
21 Section {
22 Button("Choose Audio File…") { importing = true }
23 .disabled(viewModel.isBusy)
24 status
25 if viewModel.isBusy {
26 Button("Cancel", role: .destructive) { viewModel.cancel() }
27 }
28 }
29
30 if !viewModel.stems.isEmpty {
31 Section("Stems") {
32 ForEach(viewModel.stems) { stem in
33 ShareLink(item: stem.url) {
34 Label(stem.kind.rawValue.capitalized, systemImage: "waveform")
35 }
36 }
37 }
38 }
39 }
40 .navigationTitle("Demucs")
41 .fileImporter(isPresented: $importing, allowedContentTypes: [.audio]) { result in
42 if case .success(let url) = result {
43 viewModel.separate(fileAt: url)
44 }
45 }
46 }
47 }
48
49 @ViewBuilder private var status: some View {
50 switch viewModel.phase {
51 case .idle:
52 EmptyView()
53 case .downloading(let fraction):
54 ProgressView("Downloading model", value: fraction)
55 case .loading:
56 ProgressView("Loading model")
57 case .separating(let fraction):
58 ProgressView("Separating", value: fraction)
59 case .done:
60 Label("Done", systemImage: "checkmark.circle")
61 case .failed(let message):
62 Label(message, systemImage: "exclamationmark.triangle")
63 .foregroundStyle(.red)
64 }
65 }
66 }