broken changes

This commit is contained in:
Mark Gregory
2026-11-05 12:11:05 +00:00
parent 3efe4f616e
commit 2ac7619ee1
5 changed files with 41 additions and 8 deletions
+5
View File
@@ -65,6 +65,7 @@ struct SongDragReference: Codable, Sendable {
@Published var activeSection = "Library"
@Published var studio = StudioSettings() { didSet { applyStudioSettings(save:true,previous:oldValue) } }
let backgroundDeck = BackgroundDeck()
let dj = DJSystem()
var backgroundAutomationReady = false
var backgroundPlaybackObserver: AnyCancellable?
var backgroundHandoff: Task<Void,Never>?
@@ -717,6 +718,7 @@ struct LibraryView: View {
private var selectedSong: Song? { model.songs.first { $0.id == selected } }
private var workspace: some View {
VSplitView {
HSplitView {
sidebar.frame(minWidth:220,idealWidth:240,maxWidth:260)
VStack(spacing:0) {
@@ -752,6 +754,9 @@ struct LibraryView: View {
footer.padding(.horizontal,18).padding(.vertical,8)
}.frame(minWidth:780)
}
.frame(minHeight:400)
DJConsole(model:model,system:model.dj).frame(minHeight:400,idealHeight:470)
}
.background(StudioTheme.frame)
.tint(.accentColor)
.focusedSceneValue(\.nameReinterpretation,NameReinterpretationCommands(perform:{beginReinterpretation()},enabled:["Library","Singer Songs"].contains(source) && (selectedSong != nil || !tableSelection.isEmpty)))
+14 -3
View File
@@ -2,6 +2,8 @@ import SwiftUI
import AVFoundation
import CryptoKit
import CoreImage
import AudioToolbox
import CoreAudio
/// CDG follows consumed source frames, so tempo adjustment preserves graphics timing.
@MainActor final class Player: ObservableObject {
@@ -81,12 +83,20 @@ import CoreImage
private let videoContext = CIContext()
private var mixURL: URL { waveformCacheDirectory.deletingLastPathComponent().appendingPathComponent("GlobalMixer.json") }
private var restoringMix = true
private var persistsMix = true
func routeOutput(_ device: AudioDeviceID) throws {
guard let unit = engine.outputNode.audioUnit else { throw LibraryError.message("Audio output unavailable") }
let running = engine.isRunning;engine.stop();var value = device
let result = AudioUnitSetProperty(unit,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,0,&value,UInt32(MemoryLayout.size(ofValue:value)))
guard result == noErr else { throw LibraryError.message("Audio routing failed (\(result))") }
if running { try engine.start() }
}
struct GlobalMix: Codable, Equatable {
var volume: Double; var low: Double; var mid: Double; var high: Double; var filter: Double; var enabled: Bool
}
var globalMix: GlobalMix { .init(volume:volume,low:lowGain,mid:midGain,high:highGain,filter:filter,enabled:toneEnabled) }
private func saveMix() {
guard !restoringMix, let data = try? JSONEncoder().encode(globalMix) else { return }
guard persistsMix,!restoringMix, let data = try? JSONEncoder().encode(globalMix) else { return }
try? FileManager.default.createDirectory(at:mixURL.deletingLastPathComponent(),withIntermediateDirectories:true)
try? data.write(to:mixURL,options:.atomic)
onGlobalMixChanged?(globalMix)
@@ -131,10 +141,11 @@ import CoreImage
private var cdg = Data()
private var decoder = CDGDecoder()
private var timer: Timer?
init(waveformCacheDirectory: URL? = nil) {
init(waveformCacheDirectory: URL? = nil, persistsMix: Bool = true) {
self.persistsMix = persistsMix
self.waveformCacheDirectory = waveformCacheDirectory ?? FileManager.default.urls(for:.applicationSupportDirectory,in:.userDomainMask)[0].appendingPathComponent("KJamsClone/Waveforms")
engine.attach(node);engine.attach(effect);engine.attach(tone)
if let data = try? Data(contentsOf:mixURL),let mix = try? JSONDecoder().decode(GlobalMix.self,from:data) {
if persistsMix,let data = try? Data(contentsOf:mixURL),let mix = try? JSONDecoder().decode(GlobalMix.self,from:data) {
volume = min(1,max(0,mix.volume));lowGain = min(12,max(-12,mix.low));midGain = min(12,max(-12,mix.mid));highGain = min(12,max(-12,mix.high));filter = min(1,max(-1,mix.filter));toneEnabled = mix.enabled
}
restoringMix = false;updateTone()