broken changes
This commit is contained in:
@@ -165,6 +165,7 @@ struct DJAudioDevice: Identifiable, Equatable {
|
||||
@Published var cueEditor: String?
|
||||
@Published var cueDraft: Double = 0
|
||||
@Published var cueEditing = false
|
||||
@Published var loopEditor: String?
|
||||
@Published var cueLevel: Double = 0.6 { didSet { preview.volume = cueLevel } }
|
||||
@Published private(set) var previewDeck: String?
|
||||
@Published var devices = [DJAudioDevice]()
|
||||
@@ -261,9 +262,7 @@ struct DJAudioDevice: Identifiable, Equatable {
|
||||
if previewDeck == letter { stopPreview() }
|
||||
try deck.load(song)
|
||||
let route = routing.route(letter)
|
||||
if !route.uid.isEmpty || route.firstChannel != 0 || routing.mode == "External" {
|
||||
do { try deck.player.routeOutput(try device(route.uid),firstChannel:route.firstChannel) } catch { deck.unload();throw error }
|
||||
}
|
||||
do { try deck.player.routeOutput(try device(route.uid),firstChannel:route.firstChannel) } catch { deck.unload();throw error }
|
||||
if settings.resetEQOnLoad { deck.player.resetTone() }
|
||||
if settings.autoPlayOnLoad && !automated { deck.player.toggle() }
|
||||
status = "\(song.title) ready on \(letter) · set or tap BPM for tempo sync and beat loops"
|
||||
@@ -287,14 +286,18 @@ struct DJAudioDevice: Identifiable, Equatable {
|
||||
let previous = routing
|
||||
perform {
|
||||
try next.validate(devices:devices,defaultID:DJAudioDevice.defaultOutput())
|
||||
automix.manualTakeover();stopPreview()
|
||||
automix.manualTakeover()
|
||||
let previewActive = previewDeck != nil
|
||||
do {
|
||||
for deck in [a,b] { let route = next.route(deck.letter);try deck.player.routeOutput(try device(route.uid),firstChannel:route.firstChannel) }
|
||||
if previewActive,!next.cue.uid.isEmpty { try preview.routeOutput(try device(next.cue.uid),firstChannel:next.cue.firstChannel) }
|
||||
try JSONEncoder().encode(next).write(to:root.appendingPathComponent("DJAudioRouting.json"),options:.atomic)
|
||||
routing = next;masterUID = next.main.uid;cueUID = next.cue.uid;applyMix()
|
||||
if next.cue.uid.isEmpty { stopPreview() }
|
||||
status = "Audio routing applied · \(next.mode) mixer"
|
||||
} catch {
|
||||
for deck in [a,b] { let route = previous.route(deck.letter);if let output = try? device(route.uid) { try? deck.player.routeOutput(output,firstChannel:route.firstChannel) } }
|
||||
if previewActive,!previous.cue.uid.isEmpty,let output = try? device(previous.cue.uid) { try? preview.routeOutput(output,firstChannel:previous.cue.firstChannel) }
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -734,6 +737,7 @@ struct DJConsole: View {
|
||||
Button("Replace & Load",role:.destructive) { system.confirmReplacement(request) }
|
||||
} message: { request in Text("Deck \(request.deck) is playing. Replacing it with \(request.title) stops the current track. Continue?") }
|
||||
.sheet(isPresented:$system.cueEditing) { DJCueEditor(system:system) }
|
||||
.sheet(isPresented:Binding(get:{system.loopEditor != nil},set:{if !$0 { system.loopEditor = nil }})) { let deck = system.channel(system.loopEditor ?? "A");DJLoopEditor(player:deck.player,letter:deck.letter) }
|
||||
.sheet(isPresented:$settingsModal) { StudioSettingsDialog(model:model,scope:"DJ") }
|
||||
.sheet(isPresented:$routing) { StudioSettingsDialog(model:model,scope:"DJ",category:"Devices") }
|
||||
.sheet(isPresented:$mapping) { StudioSettingsDialog(model:model,scope:"DJ",category:"MIDI") }
|
||||
@@ -940,6 +944,7 @@ private struct DJPlatter: View {
|
||||
}
|
||||
}
|
||||
private struct DJWaveform: View {
|
||||
@State private var frozenStart: Double?
|
||||
@ObservedObject var player: Player
|
||||
let color: Color
|
||||
var bpm: Double?
|
||||
@@ -959,7 +964,7 @@ private struct DJWaveform: View {
|
||||
context.fill(Path(CGRect(x:x,y:0,width:1.5,height:size.height)),with:.color(.white))
|
||||
}.background(.black.opacity(0.3),in:RoundedRectangle(cornerRadius:3))
|
||||
.overlay { if player.waveformLoading { ProgressView().controlSize(.mini) } else if player.waveformUnavailable { Text("Waveform unavailable").font(.caption2).foregroundStyle(.secondary) } }
|
||||
.contentShape(Rectangle()).gesture(DragGesture(minimumDistance:0).onChanged { value in player.seek(Double(value.location.x/max(1,geometry.size.width))*player.duration) })
|
||||
.modifier(DJLoopWaveInteraction(player:player,start:0,span:player.duration,width:geometry.size.width,frozenStart:$frozenStart))
|
||||
}.accessibilityLabel("Track waveform and position").accessibilityValue(DJStyle.stamp(player.time)).accessibilityAdjustableAction { player.jog($0 == .increment ? 1 : -1) }
|
||||
}
|
||||
}
|
||||
@@ -1207,7 +1212,7 @@ private struct DJConsoleTransport: View {
|
||||
HStack(spacing:6) {
|
||||
if deck.letter == "B" { Spacer(minLength:0) }
|
||||
ForEach(0..<4) { index in Button("\(index+1)") { system.hotCue(index,deck:deck) }.foregroundStyle(player.hotCues[index] == nil ? Color.secondary : DJStyle.color(deck.letter)).contextMenu { Button("Clear hot cue") { player.hotCue(index,clear:true) } } }
|
||||
Button("Cue \(DJStyle.stamp(player.cuePosition)) ✎") { system.editCue(deck) };Button("IN") { player.markLoopIn() };Button("OUT") { player.markLoopOut() }
|
||||
Button("Cue \(DJStyle.stamp(player.cuePosition)) ✎") { system.editCue(deck) };Button("IN") { player.markLoopIn() };Button("OUT") { player.markLoopOut() };Button("Loop ✎") { system.loopEditor = deck.letter }
|
||||
Menu { Button("Stop") { player.stopDeck() };Button("Exit loop") { player.clearLoop() };Button("Nudge back") { player.jog(-0.05) };Button("Nudge forward") { player.jog(0.05) } } label: { Image(systemName:"ellipsis") }
|
||||
if deck.letter == "A" { Spacer(minLength:0) }
|
||||
}.controlSize(.mini)
|
||||
@@ -1221,12 +1226,12 @@ private struct DJCentreWaveform: View {
|
||||
let color: Color
|
||||
private var bpm: Double? { deck.bpmConfirmed ? deck.bpm : nil }
|
||||
var settings = DJPreferences()
|
||||
@State private var scrubStart: Double?
|
||||
@State private var frozenStart: Double?
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Canvas { context,size in
|
||||
guard !player.waveform.isEmpty,player.duration > 0 else { return }
|
||||
let span = settings.waveformSeconds,start = player.time-span/2,count = max(1,Int(size.width/2))
|
||||
let span = settings.waveformSeconds,start = frozenStart ?? (player.time-span/2),count = max(1,Int(size.width/2))
|
||||
for second in 0...8 {
|
||||
let x = Double(second)/8*size.width
|
||||
context.fill(Path(CGRect(x:x,y:0,width:0.5,height:size.height)),with:.color(.white.opacity(settings.showTimeGrid ? 0.15 : 0)))
|
||||
@@ -1246,7 +1251,7 @@ private struct DJCentreWaveform: View {
|
||||
DJWaveMarkers.draw(context:&context,size:size,start:start,span:span,bpm:bpm,loop:player.loopEnabled ? player.loopIn.flatMap { a in player.loopOut.map { a...$0 } } : nil)
|
||||
context.fill(Path(CGRect(x:size.width/2,y:0,width:1.5,height:size.height)),with:.color(.red))
|
||||
}.overlay(alignment:.topLeading) { Text(letter+" · \(Int(settings.waveformSeconds)) s").font(.system(size:9,weight:.semibold)).foregroundStyle(color).padding(5) }
|
||||
.contentShape(Rectangle()).gesture(DragGesture(minimumDistance:0).onChanged { value in if scrubStart == nil { scrubStart = player.time-settings.waveformSeconds/2 };player.seek((scrubStart ?? 0)+Double(value.location.x/max(1,geometry.size.width))*settings.waveformSeconds) }.onEnded { _ in scrubStart = nil })
|
||||
.modifier(DJLoopWaveInteraction(player:player,start:player.time-settings.waveformSeconds/2,span:settings.waveformSeconds,width:geometry.size.width,frozenStart:$frozenStart))
|
||||
}.accessibilityLabel("Deck \(letter) scrolling waveform").accessibilityValue(DJStyle.stamp(player.time))
|
||||
}
|
||||
}
|
||||
@@ -1620,6 +1625,8 @@ enum DJWaveMarkers {
|
||||
let x = (time-start)/span*size.width
|
||||
if x >= 0,x <= size.width {
|
||||
context.fill(Path(CGRect(x:x,y:0,width:2,height:size.height)),with:.color(.green))
|
||||
context.fill(Path(CGRect(x:x-3,y:size.height/2-7,width:7,height:14)),with:.color(.green))
|
||||
context.fill(Path(CGRect(x:x,y:size.height/2-5,width:1,height:10)),with:.color(.black))
|
||||
context.draw(Text(label).font(.system(size:8,weight:.bold)).foregroundColor(.green),at:CGPoint(x:min(size.width-12,max(12,x)),y:7))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user