iOS 1.0.4: per-song saved mixes, karaoke mode with synced lyrics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e0184f540b
commit
bea3f8f5a2
5 files changed
+234
-6
No files matched your search
@@ -106,6 +106,7 @@ final class PlaybackManager: StemEngineListener, @unchecked Sendable {
|
||||
@ObservationIgnored private let eq: EqController
|
||||
@ObservationIgnored private(set) var engine: StemEngine!
|
||||
@ObservationIgnored private let queueFile: URL
|
||||
@ObservationIgnored private let songMixFile: URL
|
||||
|
||||
private(set) var queue: [QueueItem] = []
|
||||
private(set) var index = -1
|
||||
@@ -121,6 +122,11 @@ final class PlaybackManager: StemEngineListener, @unchecked Sendable {
|
||||
private(set) var waveform: Waveform?
|
||||
private(set) var durationMs: Int64 = 0
|
||||
private(set) var message: AppMessage?
|
||||
/// Mixes saved for single songs (by rating key), put back whenever that song starts.
|
||||
private(set) var songMixes: [String: [String: StemSetting]] = [:]
|
||||
/// Karaoke mode: vocals out on every song, lyrics on Now Playing.
|
||||
private(set) var karaoke = false
|
||||
@ObservationIgnored private var preKaraokeMix: [String: StemSetting]?
|
||||
|
||||
@ObservationIgnored private var listeners: [UUID: () -> Void] = [:]
|
||||
@ObservationIgnored private var messageListeners: [UUID: (String) -> Void] = [:]
|
||||
@@ -148,6 +154,8 @@ final class PlaybackManager: StemEngineListener, @unchecked Sendable {
|
||||
self.cache = cache
|
||||
self.eq = eq
|
||||
queueFile = dir.appendingPathComponent("queue.json")
|
||||
songMixFile = dir.appendingPathComponent("song-mixes.json")
|
||||
songMixes = (try? JSONDecoder().decode([String: [String: StemSetting]].self, from: Data(contentsOf: songMixFile))) ?? [:]
|
||||
master = settings.masterVolume
|
||||
engine = StemEngine(listener: self)
|
||||
eq.onParams = { [weak self] p in self?.engine.eqParams = p }
|
||||
@@ -405,6 +413,42 @@ final class PlaybackManager: StemEngineListener, @unchecked Sendable {
|
||||
pushMix()
|
||||
}
|
||||
|
||||
/// The mix a song starts with: karaoke if that's on, else the song's saved mix, else cleared (or kept, per settings).
|
||||
private func startMix(_ t: Track) -> [String: StemSetting] {
|
||||
if karaoke { return Self.karaokeMix }
|
||||
return songMixes[t.ratingKey] ?? (settings.keepStemMix ? mix : [:])
|
||||
}
|
||||
|
||||
private static let karaokeMix = ["vocals": StemSetting(muted: true)]
|
||||
|
||||
/// Turning it off puts back the song's saved mix, or the one from before, unless the mix was changed meanwhile.
|
||||
func setKaraoke(_ on: Bool) {
|
||||
guard on != karaoke else { return }
|
||||
karaoke = on
|
||||
if on {
|
||||
preKaraokeMix = mix
|
||||
applyMix(Self.karaokeMix)
|
||||
} else {
|
||||
if MixPresets.normalized(mix) == Self.karaokeMix { applyMix(songMix ?? preKaraokeMix ?? [:]) }
|
||||
preKaraokeMix = nil
|
||||
changed()
|
||||
}
|
||||
}
|
||||
|
||||
var songMix: [String: StemSetting]? { current.flatMap { songMixes[$0.track.ratingKey] } }
|
||||
|
||||
func saveSongMix() { setSongMix(MixPresets.normalized(mix)) }
|
||||
func forgetSongMix() { setSongMix(nil) }
|
||||
|
||||
private func setSongMix(_ m: [String: StemSetting]?) {
|
||||
guard let key = current?.track.ratingKey else { return }
|
||||
songMixes[key] = m
|
||||
let all = songMixes, file = songMixFile
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
if let data = try? JSONEncoder().encode(all) { try? data.write(to: file, options: .atomic) }
|
||||
}
|
||||
}
|
||||
|
||||
private func editMix(_ name: String, _ f: (inout StemSetting) -> Void) {
|
||||
var s = mix[name] ?? StemSetting()
|
||||
f(&s)
|
||||
@@ -462,7 +506,7 @@ final class PlaybackManager: StemEngineListener, @unchecked Sendable {
|
||||
if play && !activateSession() { return }
|
||||
playWhenReady = play
|
||||
buffering = true
|
||||
if !settings.keepStemMix { mix = [:] }
|
||||
mix = startMix(item.track)
|
||||
changed()
|
||||
save()
|
||||
loadTask = Task { @MainActor [weak self] in
|
||||
@@ -635,7 +679,7 @@ final class PlaybackManager: StemEngineListener, @unchecked Sendable {
|
||||
guard let item = current else { return }
|
||||
durationMs = item.track.durationMs
|
||||
waveform = nil
|
||||
if !settings.keepStemMix { mix = [:] }
|
||||
mix = startMix(item.track)
|
||||
source = currentEntry != nil ? .Stems : .Original
|
||||
stemNames = Stems.sort(currentEntry?.stems.map(\.name) ?? ["mix"])
|
||||
pushMix()
|
||||
|
||||
Reference in new issue
Block a user