broken changes
This commit is contained in:
@@ -20,6 +20,16 @@ struct DJAudioRouting: Codable, Equatable {
|
||||
if mode == "Internal",device.outputChannels >= 4 { routing.cue = .init(uid:device.uid,firstChannel:2) }
|
||||
return routing
|
||||
}
|
||||
func changingMode(_ mode: String,preferred: DJAudioDevice?) -> Self {
|
||||
var next = self;next.mode = mode
|
||||
if mode == "External" {
|
||||
if next.deckA == DJAudioRoute(),next.deckB == DJAudioRoute(),let preferred {
|
||||
next.deckA = .init(uid:preferred.uid,firstChannel:0);next.deckB = .init(uid:preferred.uid,firstChannel:2)
|
||||
}
|
||||
if !next.cue.uid.isEmpty,[next.deckA,next.deckB].contains(next.cue) { next.cue = .init() }
|
||||
}
|
||||
return next
|
||||
}
|
||||
static func channelMap(channels: Int,first: Int) throws -> [Int32] {
|
||||
guard channels >= 2,first >= 0,first % 2 == 0,first+1 < channels else { throw LibraryError.message("Choose an available stereo channel pair.") }
|
||||
var map = [Int32](repeating:-1,count:channels);map[first] = 0;map[first+1] = 1;return map
|
||||
@@ -67,7 +77,7 @@ struct DJAudioDevicesPane: View {
|
||||
VStack(alignment:.leading,spacing:22) {
|
||||
PreferencesSection("Audio Routing") {
|
||||
VStack(spacing:22) {
|
||||
HStack { Text("Mixer Mode:").frame(width:130,alignment:.trailing);Picker("Mixer Mode",selection:$draft.mode) { Text("Internal").tag("Internal");Text("External").tag("External") }.pickerStyle(.radioGroup).horizontalRadioGroupLayout().labelsHidden();Spacer() }
|
||||
HStack { Text("Mixer Mode:").frame(width:130,alignment:.trailing);Picker("Mixer Mode",selection:Binding(get:{draft.mode},set:{draft = draft.changingMode($0,preferred:recommended)})) { Text("Internal").tag("Internal");Text("External").tag("External") }.pickerStyle(.radioGroup).horizontalRadioGroupLayout().labelsHidden();Spacer() }
|
||||
if draft.mode == "Internal" { row("Main Output",route:$draft.main,icon:"hifispeaker.fill") }
|
||||
else { row("Deck A",route:$draft.deckA,icon:"a.circle");row("Deck B",route:$draft.deckB,icon:"b.circle") }
|
||||
row("Pre-Cueing",route:$draft.cue,icon:"headphones",optional:true)
|
||||
@@ -76,7 +86,7 @@ struct DJAudioDevicesPane: View {
|
||||
}
|
||||
if let recommended {
|
||||
Button("Use \(recommended.name)") { draft = .controllerPreset(recommended,mode:draft.mode) }
|
||||
.help("Set up the controller’s stereo pairs, then Apply")
|
||||
.help("Use the controller’s stereo pairs immediately")
|
||||
}
|
||||
Text(draft.mode == "Internal" ? "Internal mixes both decks through the main output. Pre-cueing can use channels 3–4 on the same four-channel sound card." : "External sends each deck to its own stereo pair. The software crossfader is bypassed; mix the outputs on your external mixer.").font(.callout).foregroundStyle(.secondary)
|
||||
if system.devices.isEmpty { Text("No audio outputs reported by macOS. Reconnect the interface and refresh.").foregroundStyle(.orange) }
|
||||
@@ -89,7 +99,7 @@ struct DJAudioDevicesPane: View {
|
||||
Text("\(device.outputChannels) out · \(device.inputChannels) in")
|
||||
if device.outputChannels >= 2 {
|
||||
Text(device.stereoPairs.map { "ch \($0+1)–\($0+2)" }.joined(separator:", ")).foregroundStyle(.secondary)
|
||||
} else { Text("Input only").foregroundStyle(.secondary) }
|
||||
} else { Text(device.outputChannels == 0 ? "Input only" : "Mono output").foregroundStyle(.secondary) }
|
||||
}
|
||||
}.font(.caption).padding(.vertical,3)
|
||||
}
|
||||
@@ -101,18 +111,14 @@ struct DJAudioDevicesPane: View {
|
||||
HStack {
|
||||
Button("Refresh Devices") { system.refreshDevices() }
|
||||
Spacer()
|
||||
Button("Revert") { draft = system.routing }.disabled(draft == system.routing)
|
||||
Button("Apply") { system.applyRouting(draft) }.buttonStyle(.borderedProminent).disabled(draft == system.routing || validation != nil)
|
||||
if validation != nil { Button("Revert invalid changes") { draft = system.routing } }
|
||||
Text("Changes apply immediately").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}.onAppear {
|
||||
system.refreshDevices();draft = system.routing
|
||||
if draft == DJAudioRouting(),let recommended { draft = .controllerPreset(recommended,mode:"Internal") }
|
||||
}
|
||||
.onChange(of:draft.mode) { mode in
|
||||
if mode == "External",draft.deckA == DJAudioRoute(),draft.deckB == DJAudioRoute(),let recommended {
|
||||
draft.deckA = .init(uid:recommended.uid,firstChannel:0);draft.deckB = .init(uid:recommended.uid,firstChannel:2)
|
||||
if draft.cue.uid == recommended.uid { draft.cue = .init() }
|
||||
}
|
||||
.onChange(of:draft) { next in
|
||||
if next != system.routing,validation == nil { system.applyRouting(next) }
|
||||
}
|
||||
.task {
|
||||
while !Task.isCancelled {
|
||||
@@ -147,7 +153,7 @@ struct DJAudioChannelPicker: NSViewRepresentable {
|
||||
func makeNSView(context: Context) -> NSPopUpButton {
|
||||
let button = NSPopUpButton(frame:.zero,pullsDown:false)
|
||||
button.target = context.coordinator;button.action = #selector(Coordinator.changed(_:))
|
||||
button.setAccessibilityLabel(label);return button
|
||||
button.setAccessibilityLabel(label);button.identifier = NSUserInterfaceItemIdentifier(label);return button
|
||||
}
|
||||
func updateNSView(_ button: NSPopUpButton,context: Context) {
|
||||
context.coordinator.value = $value
|
||||
|
||||
@@ -52,6 +52,7 @@ import UniformTypeIdentifiers
|
||||
func manualTakeover() { if enabled { stop("Automix off · manual control") } }
|
||||
func start(runTimer: Bool = true) {
|
||||
guard let system,!enabled else { return }
|
||||
guard !system.importing else { message = "Wait for the current DJ import to finish";return }
|
||||
guard system.routing.mode == "Internal" else { message = "Automix requires Internal mixer mode";return }
|
||||
let playing = [system.a,system.b].filter{$0.player.playing}
|
||||
guard playing.count <= 1 else { message = "Pause one deck before starting Automix";return }
|
||||
|
||||
@@ -663,7 +663,7 @@ struct StudioSettingsDialog: View {
|
||||
else if category == "Devices" { KaraokeDevicesPreferences(model:model) }
|
||||
else { StudioSettingsPane(showHeading:false,section:category,model:model).id(category) }
|
||||
Divider()
|
||||
HStack { Text(scope == "DJ" && category == "Devices" ? "DJ routing · Use Apply to save device changes" : scope+" settings · Changes save automatically").font(.caption).foregroundStyle(.secondary);Spacer();Button("Done") { dismiss() }.keyboardShortcut(.defaultAction) }.padding(.horizontal,24).padding(.vertical,16)
|
||||
HStack { Text(scope == "DJ" && category == "Devices" ? "DJ routing · Changes apply immediately" : scope+" settings · Changes save automatically").font(.caption).foregroundStyle(.secondary);Spacer();Button("Done") { dismiss() }.keyboardShortcut(.defaultAction) }.padding(.horizontal,24).padding(.vertical,16)
|
||||
}.frame(width:850,height:700).background(Color(nsColor:.windowBackgroundColor))
|
||||
.onChange(of:scope) { _ in model.dj.midi.learning = nil;if !categories.contains(category) { category = "General" } }
|
||||
.onChange(of:category) { _ in model.dj.midi.learning = nil }
|
||||
|
||||
@@ -773,24 +773,11 @@ struct LibraryView: View {
|
||||
}
|
||||
}
|
||||
private var workspaceLayout: some View {
|
||||
VStack(spacing:0) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Picker("Workspace",selection:$workspaceTab) {
|
||||
Label("Karaoke",systemImage:"music.mic").tag("Karaoke")
|
||||
Label("DJ Studio",systemImage:"opticaldisc").tag("DJ Studio")
|
||||
}.pickerStyle(.segmented).frame(width:340).accessibilityIdentifier("workspace.tabs")
|
||||
Spacer()
|
||||
}.frame(height:43).background(.bar)
|
||||
Divider()
|
||||
// Keep both workspaces mounted: playback, selection and MIDI stay alive between tabs.
|
||||
ZStack {
|
||||
libraryWorkspace.opacity(workspaceTab == "Karaoke" ? 1 : 0)
|
||||
.allowsHitTesting(workspaceTab == "Karaoke").accessibilityHidden(workspaceTab != "Karaoke")
|
||||
DJConsole(model:model,system:model.dj,expanded:true).opacity(workspaceTab == "DJ Studio" ? 1 : 0)
|
||||
.allowsHitTesting(workspaceTab == "DJ Studio").accessibilityHidden(workspaceTab != "DJ Studio")
|
||||
}.frame(maxWidth:.infinity,maxHeight:.infinity)
|
||||
}
|
||||
TabView(selection:$workspaceTab) {
|
||||
libraryWorkspace.tabItem { Label("Karaoke",systemImage:"music.mic") }.tag("Karaoke")
|
||||
DJConsole(model:model,system:model.dj,expanded:true)
|
||||
.tabItem { Label("DJ Studio",systemImage:"opticaldisc") }.tag("DJ Studio")
|
||||
}.accessibilityIdentifier("workspace.tabs")
|
||||
}
|
||||
private var workspace: some View {
|
||||
workspaceLayout
|
||||
|
||||
Reference in New Issue
Block a user