react-native-nitro-player 0.3.0-alpha.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -4
- package/android/build.gradle +4 -1
- package/android/src/main/AndroidManifest.xml +16 -1
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +2 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +8 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridDownloadManager.kt +225 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridEqualizer.kt +105 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +37 -12
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +970 -213
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +475 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadFileManager.kt +159 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +489 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadWorker.kt +209 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +486 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaBrowserService.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +14 -6
- package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +27 -0
- package/ios/HybridDownloadManager.swift +226 -0
- package/ios/HybridEqualizer.swift +111 -0
- package/ios/HybridTrackPlayer.swift +36 -8
- package/ios/core/TrackPlayerCore.swift +998 -276
- package/ios/download/DownloadDatabase.swift +493 -0
- package/ios/download/DownloadFileManager.swift +241 -0
- package/ios/download/DownloadManagerCore.swift +923 -0
- package/ios/equalizer/EqualizerCore.swift +685 -0
- package/ios/media/MediaSessionManager.swift +40 -28
- package/ios/playlist/PlaylistManager.swift +40 -9
- package/ios/queue/HybridPlayerQueue.swift +33 -13
- package/lib/hooks/downloadCallbackManager.d.ts +36 -0
- package/lib/hooks/downloadCallbackManager.js +108 -0
- package/lib/hooks/equalizerCallbackManager.d.ts +37 -0
- package/lib/hooks/equalizerCallbackManager.js +109 -0
- package/lib/hooks/index.d.ts +16 -0
- package/lib/hooks/index.js +10 -0
- package/lib/hooks/useActualQueue.d.ts +48 -0
- package/lib/hooks/useActualQueue.js +98 -0
- package/lib/hooks/useDownloadActions.d.ts +26 -0
- package/lib/hooks/useDownloadActions.js +117 -0
- package/lib/hooks/useDownloadProgress.d.ts +25 -0
- package/lib/hooks/useDownloadProgress.js +79 -0
- package/lib/hooks/useDownloadStorage.d.ts +19 -0
- package/lib/hooks/useDownloadStorage.js +60 -0
- package/lib/hooks/useDownloadedTracks.d.ts +25 -0
- package/lib/hooks/useDownloadedTracks.js +69 -0
- package/lib/hooks/useEqualizer.d.ts +25 -0
- package/lib/hooks/useEqualizer.js +124 -0
- package/lib/hooks/useEqualizerPresets.d.ts +22 -0
- package/lib/hooks/useEqualizerPresets.js +96 -0
- package/lib/hooks/useNowPlaying.js +3 -2
- package/lib/hooks/useOnChangeTrack.js +15 -12
- package/lib/hooks/useOnPlaybackStateChange.js +16 -13
- package/lib/hooks/usePlaylist.d.ts +48 -0
- package/lib/hooks/usePlaylist.js +136 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/specs/DownloadManager.nitro.d.ts +152 -0
- package/lib/specs/DownloadManager.nitro.js +1 -0
- package/lib/specs/Equalizer.nitro.d.ts +43 -0
- package/lib/specs/Equalizer.nitro.js +1 -0
- package/lib/specs/TrackPlayer.nitro.d.ts +6 -2
- package/lib/types/DownloadTypes.d.ts +110 -0
- package/lib/types/DownloadTypes.js +1 -0
- package/lib/types/EqualizerTypes.d.ts +52 -0
- package/lib/types/EqualizerTypes.js +1 -0
- package/lib/types/PlayerQueue.d.ts +4 -0
- package/nitro.json +8 -0
- package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +10 -1
- package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +32 -2
- package/nitrogen/generated/android/c++/JCurrentPlayingType.hpp +65 -0
- package/nitrogen/generated/android/c++/JDownloadConfig.hpp +92 -0
- package/nitrogen/generated/android/c++/JDownloadError.hpp +71 -0
- package/nitrogen/generated/android/c++/JDownloadErrorReason.hpp +74 -0
- package/nitrogen/generated/android/c++/JDownloadProgress.hpp +79 -0
- package/nitrogen/generated/android/c++/JDownloadQueueStatus.hpp +81 -0
- package/nitrogen/generated/android/c++/JDownloadState.hpp +71 -0
- package/nitrogen/generated/android/c++/JDownloadStorageInfo.hpp +73 -0
- package/nitrogen/generated/android/c++/JDownloadTask.hpp +108 -0
- package/nitrogen/generated/android/c++/JDownloadedPlaylist.hpp +111 -0
- package/nitrogen/generated/android/c++/JDownloadedTrack.hpp +92 -0
- package/nitrogen/generated/android/c++/JEqualizerBand.hpp +69 -0
- package/nitrogen/generated/android/c++/JEqualizerPreset.hpp +78 -0
- package/nitrogen/generated/android/c++/JEqualizerState.hpp +91 -0
- package/nitrogen/generated/android/c++/JFunc_void_DownloadProgress.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_DownloadedTrack.hpp +89 -0
- package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__std__string__.hpp +81 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_DownloadState_std__optional_DownloadError_.hpp +83 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_EqualizerBand_.hpp +97 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +2 -0
- package/nitrogen/generated/android/c++/JGainRange.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +470 -0
- package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +99 -0
- package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +204 -0
- package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +82 -0
- package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +2 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +117 -15
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +6 -2
- package/nitrogen/generated/android/c++/JPlaybackSource.hpp +62 -0
- package/nitrogen/generated/android/c++/JPlayerState.hpp +11 -3
- package/nitrogen/generated/android/c++/JPlaylist.hpp +2 -0
- package/nitrogen/generated/android/c++/JPresetType.hpp +59 -0
- package/nitrogen/generated/android/c++/JStorageLocation.hpp +59 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +9 -3
- package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +3 -3
- package/nitrogen/generated/android/c++/JVariant_NullType_Double.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +69 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.hpp +74 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.hpp +84 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.hpp +85 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.hpp +80 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +2 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/CurrentPlayingType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadConfig.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadError.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadErrorReason.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadProgress.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadQueueStatus.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadState.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadStorageInfo.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadTask.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedPlaylist.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedTrack.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerBand.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerPreset.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerState.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadProgress.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadedTrack.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__optional_std__variant_nitro__NullType__std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_EqualizerBand_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/GainRange.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +210 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +141 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +19 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlaybackSource.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerState.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PresetType.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/StorageLocation.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackItem.kt +7 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackPlayerState.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Double.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadError.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadTask.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedPlaylist.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedTrack.kt +59 -0
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +138 -8
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +1046 -121
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +66 -0
- package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +16 -0
- package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +30 -0
- package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +386 -0
- package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +223 -0
- package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +1 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +46 -6
- package/nitrogen/generated/ios/swift/CurrentPlayingType.swift +48 -0
- package/nitrogen/generated/ios/swift/DownloadConfig.swift +270 -0
- package/nitrogen/generated/ios/swift/DownloadError.swift +69 -0
- package/nitrogen/generated/ios/swift/DownloadErrorReason.swift +60 -0
- package/nitrogen/generated/ios/swift/DownloadProgress.swift +91 -0
- package/nitrogen/generated/ios/swift/DownloadQueueStatus.swift +102 -0
- package/nitrogen/generated/ios/swift/DownloadState.swift +56 -0
- package/nitrogen/generated/ios/swift/DownloadStorageInfo.swift +80 -0
- package/nitrogen/generated/ios/swift/DownloadTask.swift +315 -0
- package/nitrogen/generated/ios/swift/DownloadedPlaylist.swift +103 -0
- package/nitrogen/generated/ios/swift/DownloadedTrack.swift +147 -0
- package/nitrogen/generated/ios/swift/EqualizerBand.swift +69 -0
- package/nitrogen/generated/ios/swift/EqualizerPreset.swift +70 -0
- package/nitrogen/generated/ios/swift/EqualizerState.swift +115 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_DownloadProgress.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_DownloadStorageInfo.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_DownloadedTrack.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlayerState.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__variant_nitro__NullType__std__string__.swift +66 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +47 -0
- package/nitrogen/generated/ios/swift/GainRange.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +90 -0
- package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +705 -0
- package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +73 -0
- package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +396 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +6 -2
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +105 -8
- package/nitrogen/generated/ios/swift/PlaybackSource.swift +44 -0
- package/nitrogen/generated/ios/swift/PlayerState.swift +13 -2
- package/nitrogen/generated/ios/swift/PresetType.swift +40 -0
- package/nitrogen/generated/ios/swift/StorageLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +31 -1
- package/nitrogen/generated/ios/swift/TrackPlayerState.swift +4 -4
- package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_DownloadError.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_DownloadTask.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedPlaylist.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedTrack.swift +18 -0
- package/nitrogen/generated/shared/c++/CurrentPlayingType.hpp +84 -0
- package/nitrogen/generated/shared/c++/DownloadConfig.hpp +108 -0
- package/nitrogen/generated/shared/c++/DownloadError.hpp +89 -0
- package/nitrogen/generated/shared/c++/DownloadErrorReason.hpp +96 -0
- package/nitrogen/generated/shared/c++/DownloadProgress.hpp +97 -0
- package/nitrogen/generated/shared/c++/DownloadQueueStatus.hpp +99 -0
- package/nitrogen/generated/shared/c++/DownloadState.hpp +92 -0
- package/nitrogen/generated/shared/c++/DownloadStorageInfo.hpp +91 -0
- package/nitrogen/generated/shared/c++/DownloadTask.hpp +122 -0
- package/nitrogen/generated/shared/c++/DownloadedPlaylist.hpp +101 -0
- package/nitrogen/generated/shared/c++/DownloadedTrack.hpp +107 -0
- package/nitrogen/generated/shared/c++/EqualizerBand.hpp +87 -0
- package/nitrogen/generated/shared/c++/EqualizerPreset.hpp +86 -0
- package/nitrogen/generated/shared/c++/EqualizerState.hpp +89 -0
- package/nitrogen/generated/shared/c++/GainRange.hpp +79 -0
- package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.cpp +55 -0
- package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +134 -0
- package/nitrogen/generated/shared/c++/HybridEqualizerSpec.cpp +38 -0
- package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +95 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +11 -5
- package/nitrogen/generated/shared/c++/PlaybackSource.hpp +80 -0
- package/nitrogen/generated/shared/c++/PlayerState.hpp +9 -2
- package/nitrogen/generated/shared/c++/PresetType.hpp +76 -0
- package/nitrogen/generated/shared/c++/StorageLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +7 -2
- package/nitrogen/generated/shared/c++/TrackPlayerState.hpp +5 -5
- package/package.json +1 -1
- package/src/hooks/downloadCallbackManager.ts +149 -0
- package/src/hooks/equalizerCallbackManager.ts +138 -0
- package/src/hooks/index.ts +23 -0
- package/src/hooks/useActualQueue.ts +116 -0
- package/src/hooks/useDownloadActions.ts +179 -0
- package/src/hooks/useDownloadProgress.ts +126 -0
- package/src/hooks/useDownloadStorage.ts +84 -0
- package/src/hooks/useDownloadedTracks.ts +138 -0
- package/src/hooks/useEqualizer.ts +173 -0
- package/src/hooks/useEqualizerPresets.ts +140 -0
- package/src/hooks/useNowPlaying.ts +3 -2
- package/src/hooks/useOnChangeTrack.ts +15 -11
- package/src/hooks/useOnPlaybackStateChange.ts +19 -15
- package/src/hooks/usePlaylist.ts +161 -0
- package/src/index.ts +12 -0
- package/src/specs/DownloadManager.nitro.ts +203 -0
- package/src/specs/Equalizer.nitro.ts +69 -0
- package/src/specs/TrackPlayer.nitro.ts +6 -2
- package/src/types/DownloadTypes.ts +135 -0
- package/src/types/EqualizerTypes.ts +72 -0
- package/src/types/PlayerQueue.ts +9 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
//
|
|
2
|
+
// EqualizerCore.swift
|
|
3
|
+
// NitroPlayer
|
|
4
|
+
//
|
|
5
|
+
// Created by Ritesh Shukla on 04/02/26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import AVFoundation
|
|
9
|
+
import Accelerate
|
|
10
|
+
import Foundation
|
|
11
|
+
import MediaToolbox
|
|
12
|
+
import NitroModules
|
|
13
|
+
|
|
14
|
+
class EqualizerCore {
|
|
15
|
+
// MARK: - Singleton
|
|
16
|
+
|
|
17
|
+
static let shared = EqualizerCore()
|
|
18
|
+
|
|
19
|
+
// MARK: - Properties
|
|
20
|
+
|
|
21
|
+
// Internal so TapContext can access it
|
|
22
|
+
private(set) var isEqualizerEnabled: Bool = false
|
|
23
|
+
private var currentPresetName: String?
|
|
24
|
+
|
|
25
|
+
// Standard 5-band frequencies: 60Hz, 230Hz, 910Hz, 3.6kHz, 14kHz
|
|
26
|
+
let frequencies: [Float] = [60, 230, 910, 3600, 14000]
|
|
27
|
+
private let frequencyLabels = ["60 Hz", "230 Hz", "910 Hz", "3.6 kHz", "14 kHz"]
|
|
28
|
+
|
|
29
|
+
// Current gains storage - internal so TapContext can access
|
|
30
|
+
private(set) var currentGains: [Double] = [0, 0, 0, 0, 0]
|
|
31
|
+
|
|
32
|
+
// UserDefaults keys
|
|
33
|
+
private let enabledKey = "eq_enabled"
|
|
34
|
+
private let bandGainsKey = "eq_band_gains"
|
|
35
|
+
private let currentPresetKey = "eq_current_preset"
|
|
36
|
+
private let customPresetsKey = "eq_custom_presets"
|
|
37
|
+
|
|
38
|
+
// MARK: - Weak Callback Wrapper
|
|
39
|
+
|
|
40
|
+
private class WeakCallbackBox<T> {
|
|
41
|
+
private(set) weak var owner: AnyObject?
|
|
42
|
+
let callback: T
|
|
43
|
+
|
|
44
|
+
init(owner: AnyObject, callback: T) {
|
|
45
|
+
self.owner = owner
|
|
46
|
+
self.callback = callback
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var isAlive: Bool { owner != nil }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Event callbacks
|
|
53
|
+
private var onEnabledChangeListeners: [WeakCallbackBox<(Bool) -> Void>] = []
|
|
54
|
+
private var onBandChangeListeners: [WeakCallbackBox<([EqualizerBand]) -> Void>] = []
|
|
55
|
+
private var onPresetChangeListeners: [WeakCallbackBox<(Variant_NullType_String?) -> Void>] = []
|
|
56
|
+
|
|
57
|
+
private let listenersQueue = DispatchQueue(
|
|
58
|
+
label: "com.equalizer.listeners", attributes: .concurrent)
|
|
59
|
+
|
|
60
|
+
// MARK: - Built-in Presets
|
|
61
|
+
|
|
62
|
+
private static let builtInPresets: [String: [Double]] = [
|
|
63
|
+
"Flat": [0, 0, 0, 0, 0],
|
|
64
|
+
"Bass Boost": [6, 4, 0, 0, 0],
|
|
65
|
+
"Bass Reducer": [-6, -4, 0, 0, 0],
|
|
66
|
+
"Treble Boost": [0, 0, 0, 4, 6],
|
|
67
|
+
"Treble Reducer": [0, 0, 0, -4, -6],
|
|
68
|
+
"Vocal Boost": [-2, 0, 4, 2, 0],
|
|
69
|
+
"Rock": [5, 3, -1, 3, 5],
|
|
70
|
+
"Pop": [-1, 2, 4, 2, -1],
|
|
71
|
+
"Jazz": [3, 1, -2, 2, 4],
|
|
72
|
+
"Classical": [4, 2, -1, 2, 3],
|
|
73
|
+
"Hip Hop": [6, 4, 0, 1, 3],
|
|
74
|
+
"Electronic": [5, 3, 0, 2, 5],
|
|
75
|
+
"Acoustic": [4, 2, 1, 3, 3],
|
|
76
|
+
"R&B": [3, 6, 2, -1, 2],
|
|
77
|
+
"Loudness": [6, 3, -1, 3, 6],
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
// MARK: - Initialization
|
|
81
|
+
|
|
82
|
+
private init() {
|
|
83
|
+
restoreSettings()
|
|
84
|
+
print("✅ EqualizerCore: Initialized with MTAudioProcessingTap support")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// MARK: - Audio Mix Creation for AVPlayerItem
|
|
88
|
+
|
|
89
|
+
/// Applies an AVAudioMix with equalizer processing for the given AVPlayerItem asynchronously
|
|
90
|
+
func applyAudioMix(to playerItem: AVPlayerItem) {
|
|
91
|
+
let asset = playerItem.asset
|
|
92
|
+
|
|
93
|
+
// Load "tracks" key asynchronously to avoid blocking
|
|
94
|
+
asset.loadValuesAsynchronously(forKeys: ["tracks"]) { [weak self] in
|
|
95
|
+
guard let self = self else { return }
|
|
96
|
+
|
|
97
|
+
var error: NSError?
|
|
98
|
+
let status = asset.statusOfValue(forKey: "tracks", error: &error)
|
|
99
|
+
|
|
100
|
+
if status == .failed {
|
|
101
|
+
print(
|
|
102
|
+
"⚠️ EqualizerCore: Failed to load tracks key: \(error?.localizedDescription ?? "unknown")")
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Proceed only if loaded successfully
|
|
107
|
+
guard status == .loaded else {
|
|
108
|
+
print("⚠️ EqualizerCore: Tracks not loaded, status: \(status.rawValue)")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
|
|
113
|
+
print("⚠️ EqualizerCore: No audio track found in asset")
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Create audio mix input parameters
|
|
118
|
+
let inputParams = AVMutableAudioMixInputParameters(track: audioTrack)
|
|
119
|
+
|
|
120
|
+
// Create the audio processing tap
|
|
121
|
+
var callbacks = MTAudioProcessingTapCallbacks(
|
|
122
|
+
version: kMTAudioProcessingTapCallbacksVersion_0,
|
|
123
|
+
clientInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()),
|
|
124
|
+
init: tapInitCallback,
|
|
125
|
+
finalize: tapFinalizeCallback,
|
|
126
|
+
prepare: tapPrepareCallback,
|
|
127
|
+
unprepare: tapUnprepareCallback,
|
|
128
|
+
process: tapProcessCallback
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
var tap: MTAudioProcessingTap?
|
|
132
|
+
let createStatus = MTAudioProcessingTapCreate(
|
|
133
|
+
kCFAllocatorDefault,
|
|
134
|
+
&callbacks,
|
|
135
|
+
kMTAudioProcessingTapCreationFlag_PreEffects,
|
|
136
|
+
&tap
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
guard createStatus == noErr, let audioTap = tap else {
|
|
140
|
+
print("❌ EqualizerCore: Failed to create audio processing tap, status: \(createStatus)")
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
inputParams.audioTapProcessor = audioTap
|
|
145
|
+
|
|
146
|
+
// Create audio mix
|
|
147
|
+
let audioMix = AVMutableAudioMix()
|
|
148
|
+
audioMix.inputParameters = [inputParams]
|
|
149
|
+
|
|
150
|
+
// Apply to player item on main thread (AVPlayerItem properties should be accessed/modified on main thread or serial queue usually, but audioMix is thread safe - safely done on main to be sure)
|
|
151
|
+
DispatchQueue.main.async {
|
|
152
|
+
playerItem.audioMix = audioMix
|
|
153
|
+
print("✅ EqualizerCore: Applied audio mix with EQ tap to player item (async)")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// MARK: - Public Methods
|
|
159
|
+
|
|
160
|
+
func setEnabled(_ enabled: Bool) -> Bool {
|
|
161
|
+
isEqualizerEnabled = enabled
|
|
162
|
+
|
|
163
|
+
notifyEnabledChange(enabled)
|
|
164
|
+
saveEnabled(enabled)
|
|
165
|
+
|
|
166
|
+
print("🎚️ EqualizerCore: Equalizer \(enabled ? "enabled" : "disabled")")
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func isEnabled() -> Bool {
|
|
171
|
+
return isEqualizerEnabled
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func getBands() -> [EqualizerBand] {
|
|
175
|
+
return (0..<5).map { i in
|
|
176
|
+
EqualizerBand(
|
|
177
|
+
index: Double(i),
|
|
178
|
+
centerFrequency: Double(frequencies[i]),
|
|
179
|
+
gainDb: currentGains[i],
|
|
180
|
+
frequencyLabel: frequencyLabels[i]
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func setBandGain(bandIndex: Int, gainDb: Double) -> Bool {
|
|
186
|
+
guard bandIndex >= 0 && bandIndex < 5 else { return false }
|
|
187
|
+
|
|
188
|
+
let clampedGain = max(-12.0, min(12.0, gainDb))
|
|
189
|
+
currentGains[bandIndex] = clampedGain
|
|
190
|
+
|
|
191
|
+
currentPresetName = nil
|
|
192
|
+
notifyBandChange(getBands())
|
|
193
|
+
notifyPresetChange(nil)
|
|
194
|
+
saveBandGains(currentGains)
|
|
195
|
+
saveCurrentPreset(nil)
|
|
196
|
+
|
|
197
|
+
print("🎚️ EqualizerCore: Band \(bandIndex) gain set to \(clampedGain) dB")
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
func setAllBandGains(_ gains: [Double]) -> Bool {
|
|
202
|
+
guard gains.count == 5 else { return false }
|
|
203
|
+
|
|
204
|
+
for i in 0..<5 {
|
|
205
|
+
currentGains[i] = max(-12.0, min(12.0, gains[i]))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
notifyBandChange(getBands())
|
|
209
|
+
saveBandGains(currentGains)
|
|
210
|
+
|
|
211
|
+
print("🎚️ EqualizerCore: All band gains updated")
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
func getBandRange() -> GainRange {
|
|
216
|
+
return GainRange(min: -12.0, max: 12.0)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
func getPresets() -> [EqualizerPreset] {
|
|
220
|
+
return getBuiltInPresets() + getCustomPresets()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func getBuiltInPresets() -> [EqualizerPreset] {
|
|
224
|
+
return Self.builtInPresets.map { name, gains in
|
|
225
|
+
EqualizerPreset(name: name, gains: gains, type: .builtIn)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func getCustomPresets() -> [EqualizerPreset] {
|
|
230
|
+
guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
|
|
231
|
+
let presets = try? JSONDecoder().decode([String: [Double]].self, from: data)
|
|
232
|
+
else {
|
|
233
|
+
return []
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return presets.map { name, gains in
|
|
237
|
+
EqualizerPreset(name: name, gains: gains, type: .custom)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func applyPreset(_ presetName: String) -> Bool {
|
|
242
|
+
// Try built-in preset first
|
|
243
|
+
if let gains = Self.builtInPresets[presetName] {
|
|
244
|
+
if setAllBandGains(gains) {
|
|
245
|
+
currentPresetName = presetName
|
|
246
|
+
notifyPresetChange(presetName)
|
|
247
|
+
saveCurrentPreset(presetName)
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Try custom preset
|
|
253
|
+
if let gains = getCustomPresetGains(presetName) {
|
|
254
|
+
if setAllBandGains(gains) {
|
|
255
|
+
currentPresetName = presetName
|
|
256
|
+
notifyPresetChange(presetName)
|
|
257
|
+
saveCurrentPreset(presetName)
|
|
258
|
+
return true
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private func getCustomPresetGains(_ name: String) -> [Double]? {
|
|
266
|
+
guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
|
|
267
|
+
let presets = try? JSONDecoder().decode([String: [Double]].self, from: data)
|
|
268
|
+
else {
|
|
269
|
+
return nil
|
|
270
|
+
}
|
|
271
|
+
return presets[name]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func getCurrentPresetName() -> String? {
|
|
275
|
+
return currentPresetName
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
func saveCustomPreset(_ name: String) -> Bool {
|
|
279
|
+
var presets: [String: [Double]] = [:]
|
|
280
|
+
|
|
281
|
+
if let data = UserDefaults.standard.data(forKey: customPresetsKey),
|
|
282
|
+
let existing = try? JSONDecoder().decode([String: [Double]].self, from: data)
|
|
283
|
+
{
|
|
284
|
+
presets = existing
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
presets[name] = currentGains
|
|
288
|
+
|
|
289
|
+
if let data = try? JSONEncoder().encode(presets) {
|
|
290
|
+
UserDefaults.standard.set(data, forKey: customPresetsKey)
|
|
291
|
+
currentPresetName = name
|
|
292
|
+
notifyPresetChange(name)
|
|
293
|
+
saveCurrentPreset(name)
|
|
294
|
+
return true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return false
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
func deleteCustomPreset(_ name: String) -> Bool {
|
|
301
|
+
guard
|
|
302
|
+
var presets: [String: [Double]] = {
|
|
303
|
+
guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
|
|
304
|
+
let existing = try? JSONDecoder().decode([String: [Double]].self, from: data)
|
|
305
|
+
else {
|
|
306
|
+
return nil
|
|
307
|
+
}
|
|
308
|
+
return existing
|
|
309
|
+
}()
|
|
310
|
+
else {
|
|
311
|
+
return false
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
guard presets[name] != nil else { return false }
|
|
315
|
+
|
|
316
|
+
presets.removeValue(forKey: name)
|
|
317
|
+
|
|
318
|
+
if let data = try? JSONEncoder().encode(presets) {
|
|
319
|
+
UserDefaults.standard.set(data, forKey: customPresetsKey)
|
|
320
|
+
|
|
321
|
+
if currentPresetName == name {
|
|
322
|
+
currentPresetName = nil
|
|
323
|
+
notifyPresetChange(nil)
|
|
324
|
+
saveCurrentPreset(nil)
|
|
325
|
+
}
|
|
326
|
+
return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func getState() -> EqualizerState {
|
|
333
|
+
let presetVariant: Variant_NullType_String?
|
|
334
|
+
if let name = currentPresetName {
|
|
335
|
+
presetVariant = .second(name)
|
|
336
|
+
} else {
|
|
337
|
+
presetVariant = .first(NullType.null)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return EqualizerState(
|
|
341
|
+
enabled: isEqualizerEnabled,
|
|
342
|
+
bands: getBands(),
|
|
343
|
+
currentPreset: presetVariant
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func reset() {
|
|
348
|
+
_ = setAllBandGains([0, 0, 0, 0, 0])
|
|
349
|
+
currentPresetName = "Flat"
|
|
350
|
+
notifyPresetChange("Flat")
|
|
351
|
+
saveCurrentPreset("Flat")
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// MARK: - Persistence
|
|
355
|
+
|
|
356
|
+
private func saveEnabled(_ enabled: Bool) {
|
|
357
|
+
UserDefaults.standard.set(enabled, forKey: enabledKey)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private func saveBandGains(_ gains: [Double]) {
|
|
361
|
+
if let data = try? JSONEncoder().encode(gains) {
|
|
362
|
+
UserDefaults.standard.set(data, forKey: bandGainsKey)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private func saveCurrentPreset(_ name: String?) {
|
|
367
|
+
UserDefaults.standard.set(name, forKey: currentPresetKey)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private func restoreSettings() {
|
|
371
|
+
let enabled = UserDefaults.standard.bool(forKey: enabledKey)
|
|
372
|
+
|
|
373
|
+
if let data = UserDefaults.standard.data(forKey: bandGainsKey),
|
|
374
|
+
let gains = try? JSONDecoder().decode([Double].self, from: data),
|
|
375
|
+
gains.count == 5
|
|
376
|
+
{
|
|
377
|
+
currentGains = gains
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
currentPresetName = UserDefaults.standard.string(forKey: currentPresetKey)
|
|
381
|
+
isEqualizerEnabled = enabled
|
|
382
|
+
|
|
383
|
+
print("✅ EqualizerCore: Restored settings - enabled: \(enabled), gains: \(currentGains)")
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// MARK: - Callback Management
|
|
387
|
+
|
|
388
|
+
func addOnEnabledChangeListener(owner: AnyObject, _ callback: @escaping (Bool) -> Void) {
|
|
389
|
+
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
390
|
+
let box = WeakCallbackBox(owner: owner, callback: callback)
|
|
391
|
+
self?.onEnabledChangeListeners.append(box)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
func addOnBandChangeListener(owner: AnyObject, _ callback: @escaping ([EqualizerBand]) -> Void) {
|
|
396
|
+
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
397
|
+
let box = WeakCallbackBox(owner: owner, callback: callback)
|
|
398
|
+
self?.onBandChangeListeners.append(box)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func addOnPresetChangeListener(
|
|
403
|
+
owner: AnyObject, _ callback: @escaping (Variant_NullType_String?) -> Void
|
|
404
|
+
) {
|
|
405
|
+
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
406
|
+
let box = WeakCallbackBox(owner: owner, callback: callback)
|
|
407
|
+
self?.onPresetChangeListeners.append(box)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private func notifyEnabledChange(_ enabled: Bool) {
|
|
412
|
+
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
413
|
+
guard let self = self else { return }
|
|
414
|
+
self.onEnabledChangeListeners.removeAll { !$0.isAlive }
|
|
415
|
+
|
|
416
|
+
let callbacks = self.onEnabledChangeListeners.compactMap {
|
|
417
|
+
$0.isAlive ? $0.callback : nil
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if !callbacks.isEmpty {
|
|
421
|
+
DispatchQueue.main.async {
|
|
422
|
+
for callback in callbacks {
|
|
423
|
+
callback(enabled)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private func notifyBandChange(_ bands: [EqualizerBand]) {
|
|
431
|
+
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
432
|
+
guard let self = self else { return }
|
|
433
|
+
self.onBandChangeListeners.removeAll { !$0.isAlive }
|
|
434
|
+
|
|
435
|
+
let callbacks = self.onBandChangeListeners.compactMap {
|
|
436
|
+
$0.isAlive ? $0.callback : nil
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if !callbacks.isEmpty {
|
|
440
|
+
DispatchQueue.main.async {
|
|
441
|
+
for callback in callbacks {
|
|
442
|
+
callback(bands)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private func notifyPresetChange(_ presetName: String?) {
|
|
450
|
+
listenersQueue.async(flags: .barrier) { [weak self] in
|
|
451
|
+
guard let self = self else { return }
|
|
452
|
+
self.onPresetChangeListeners.removeAll { !$0.isAlive }
|
|
453
|
+
|
|
454
|
+
let callbacks = self.onPresetChangeListeners.compactMap {
|
|
455
|
+
$0.isAlive ? $0.callback : nil
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if !callbacks.isEmpty {
|
|
459
|
+
let variant: Variant_NullType_String? = presetName.map { .second($0) }
|
|
460
|
+
|
|
461
|
+
DispatchQueue.main.async {
|
|
462
|
+
for callback in callbacks {
|
|
463
|
+
callback(variant)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// MARK: - MTAudioProcessingTap Context
|
|
472
|
+
|
|
473
|
+
/// Context passed to the audio processing tap
|
|
474
|
+
private class TapContext {
|
|
475
|
+
weak var eqCore: EqualizerCore?
|
|
476
|
+
var sampleRate: Float = 44100.0
|
|
477
|
+
var channelCount: Int = 2
|
|
478
|
+
|
|
479
|
+
// Biquad filter states for 5 bands
|
|
480
|
+
// Each band needs 4 delay elements per channel (x[n-1], x[n-2], y[n-1], y[n-2])
|
|
481
|
+
var filterStates: [[Float]] = []
|
|
482
|
+
|
|
483
|
+
// Biquad coefficients for 5 bands
|
|
484
|
+
// Each band: [b0, b1, b2, a1, a2] (normalized, a0 = 1)
|
|
485
|
+
var filterCoeffs: [[Double]] = []
|
|
486
|
+
|
|
487
|
+
init(eqCore: EqualizerCore) {
|
|
488
|
+
self.eqCore = eqCore
|
|
489
|
+
// Initialize 5 bands with flat coefficients
|
|
490
|
+
for _ in 0..<5 {
|
|
491
|
+
filterCoeffs.append([1.0, 0.0, 0.0, 0.0, 0.0]) // Flat/bypass
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
func updateCoefficients() {
|
|
496
|
+
guard let eqCore = eqCore else { return }
|
|
497
|
+
let frequencies: [Float] = [60, 230, 910, 3600, 14000]
|
|
498
|
+
let gains = eqCore.currentGains
|
|
499
|
+
|
|
500
|
+
for i in 0..<5 {
|
|
501
|
+
filterCoeffs[i] = calculatePeakingEQCoefficients(
|
|
502
|
+
frequency: Double(frequencies[i]),
|
|
503
|
+
gain: gains[i],
|
|
504
|
+
q: 1.41, // Standard Q for graphic EQ
|
|
505
|
+
sampleRate: Double(sampleRate)
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/// Calculate biquad coefficients for a peaking EQ filter
|
|
511
|
+
private func calculatePeakingEQCoefficients(
|
|
512
|
+
frequency: Double, gain: Double, q: Double, sampleRate: Double
|
|
513
|
+
) -> [Double] {
|
|
514
|
+
// If gain is essentially 0, return bypass coefficients
|
|
515
|
+
let absGain: Double = Swift.abs(gain)
|
|
516
|
+
if absGain < 0.01 {
|
|
517
|
+
return [1.0, 0.0, 0.0, 0.0, 0.0]
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let A = pow(10.0, gain / 40.0) // sqrt(10^(gain/20))
|
|
521
|
+
let omega = 2.0 * Double.pi * frequency / sampleRate
|
|
522
|
+
let sinOmega = sin(omega)
|
|
523
|
+
let cosOmega = cos(omega)
|
|
524
|
+
let alpha = sinOmega / (2.0 * q)
|
|
525
|
+
|
|
526
|
+
let b0 = 1.0 + alpha * A
|
|
527
|
+
let b1 = -2.0 * cosOmega
|
|
528
|
+
let b2 = 1.0 - alpha * A
|
|
529
|
+
let a0 = 1.0 + alpha / A
|
|
530
|
+
let a1 = -2.0 * cosOmega
|
|
531
|
+
let a2 = 1.0 - alpha / A
|
|
532
|
+
|
|
533
|
+
// Normalize by a0
|
|
534
|
+
return [b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0]
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
func resetFilterStates() {
|
|
538
|
+
filterStates = []
|
|
539
|
+
for _ in 0..<5 {
|
|
540
|
+
// 4 delay elements per channel (2 for input history, 2 for output history)
|
|
541
|
+
filterStates.append(Array(repeating: Float(0.0), count: channelCount * 4))
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// MARK: - MTAudioProcessingTap Callbacks
|
|
547
|
+
|
|
548
|
+
private func tapInitCallback(
|
|
549
|
+
tap: MTAudioProcessingTap,
|
|
550
|
+
clientInfo: UnsafeMutableRawPointer?,
|
|
551
|
+
tapStorageOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>
|
|
552
|
+
) {
|
|
553
|
+
guard let clientInfo = clientInfo else { return }
|
|
554
|
+
|
|
555
|
+
let eqCore = Unmanaged<EqualizerCore>.fromOpaque(clientInfo).takeUnretainedValue()
|
|
556
|
+
let context = TapContext(eqCore: eqCore)
|
|
557
|
+
tapStorageOut.pointee = Unmanaged.passRetained(context).toOpaque()
|
|
558
|
+
|
|
559
|
+
print("🎛️ EqualizerCore: Tap initialized")
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private func tapFinalizeCallback(tap: MTAudioProcessingTap) {
|
|
563
|
+
let storage = MTAudioProcessingTapGetStorage(tap)
|
|
564
|
+
Unmanaged<TapContext>.fromOpaque(storage).release()
|
|
565
|
+
print("🎛️ EqualizerCore: Tap finalized")
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private func tapPrepareCallback(
|
|
569
|
+
tap: MTAudioProcessingTap,
|
|
570
|
+
maxFrames: CMItemCount,
|
|
571
|
+
processingFormat: UnsafePointer<AudioStreamBasicDescription>
|
|
572
|
+
) {
|
|
573
|
+
let storage = MTAudioProcessingTapGetStorage(tap)
|
|
574
|
+
let context = Unmanaged<TapContext>.fromOpaque(storage).takeUnretainedValue()
|
|
575
|
+
|
|
576
|
+
context.sampleRate = Float(processingFormat.pointee.mSampleRate)
|
|
577
|
+
context.channelCount = Int(processingFormat.pointee.mChannelsPerFrame)
|
|
578
|
+
context.updateCoefficients()
|
|
579
|
+
context.resetFilterStates()
|
|
580
|
+
|
|
581
|
+
print(
|
|
582
|
+
"🎛️ EqualizerCore: Tap prepared - sampleRate: \(context.sampleRate), channels: \(context.channelCount)"
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private func tapUnprepareCallback(tap: MTAudioProcessingTap) {
|
|
587
|
+
print("🎛️ EqualizerCore: Tap unprepared")
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private func tapProcessCallback(
|
|
591
|
+
tap: MTAudioProcessingTap,
|
|
592
|
+
numberFrames: CMItemCount,
|
|
593
|
+
flags: MTAudioProcessingTapFlags,
|
|
594
|
+
bufferListInOut: UnsafeMutablePointer<AudioBufferList>,
|
|
595
|
+
numberFramesOut: UnsafeMutablePointer<CMItemCount>,
|
|
596
|
+
flagsOut: UnsafeMutablePointer<MTAudioProcessingTapFlags>
|
|
597
|
+
) {
|
|
598
|
+
let storage = MTAudioProcessingTapGetStorage(tap)
|
|
599
|
+
let context = Unmanaged<TapContext>.fromOpaque(storage).takeUnretainedValue()
|
|
600
|
+
|
|
601
|
+
// Get source audio
|
|
602
|
+
var sourceFlags = MTAudioProcessingTapFlags()
|
|
603
|
+
let status = MTAudioProcessingTapGetSourceAudio(
|
|
604
|
+
tap,
|
|
605
|
+
numberFrames,
|
|
606
|
+
bufferListInOut,
|
|
607
|
+
&sourceFlags,
|
|
608
|
+
nil,
|
|
609
|
+
numberFramesOut
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
guard status == noErr else {
|
|
613
|
+
print("❌ EqualizerCore: Failed to get source audio: \(status)")
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Check if equalizer is enabled
|
|
618
|
+
guard let eqCore = context.eqCore, eqCore.isEqualizerEnabled else {
|
|
619
|
+
// Bypass - audio is already in bufferListInOut
|
|
620
|
+
return
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Update coefficients (in case gains changed)
|
|
624
|
+
context.updateCoefficients()
|
|
625
|
+
|
|
626
|
+
// Process each buffer (channel)
|
|
627
|
+
let bufferList = UnsafeMutableAudioBufferListPointer(bufferListInOut)
|
|
628
|
+
|
|
629
|
+
for bufferIndex in 0..<bufferList.count {
|
|
630
|
+
guard let data = bufferList[bufferIndex].mData else { continue }
|
|
631
|
+
|
|
632
|
+
let frameCount = Int(numberFramesOut.pointee)
|
|
633
|
+
let samples = data.assumingMemoryBound(to: Float.self)
|
|
634
|
+
|
|
635
|
+
// Apply all 5 EQ bands in series
|
|
636
|
+
for bandIndex in 0..<5 {
|
|
637
|
+
let coeffs: [Double] = context.filterCoeffs[bandIndex]
|
|
638
|
+
|
|
639
|
+
// Skip if essentially flat
|
|
640
|
+
let c0: Double = coeffs[0]
|
|
641
|
+
let c1: Double = coeffs[1]
|
|
642
|
+
let c2: Double = coeffs[2]
|
|
643
|
+
if Swift.abs(c0 - 1.0) < 0.001 && Swift.abs(c1) < 0.001 && Swift.abs(c2) < 0.001 {
|
|
644
|
+
continue
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Ensure we have enough filter states for this channel
|
|
648
|
+
guard bufferIndex * 4 + 3 < context.filterStates[bandIndex].count else {
|
|
649
|
+
continue
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Get filter state for this band and channel
|
|
653
|
+
let stateOffset = bufferIndex * 4
|
|
654
|
+
var x1 = context.filterStates[bandIndex][stateOffset]
|
|
655
|
+
var x2 = context.filterStates[bandIndex][stateOffset + 1]
|
|
656
|
+
var y1 = context.filterStates[bandIndex][stateOffset + 2]
|
|
657
|
+
var y2 = context.filterStates[bandIndex][stateOffset + 3]
|
|
658
|
+
|
|
659
|
+
let b0 = Float(coeffs[0])
|
|
660
|
+
let b1 = Float(coeffs[1])
|
|
661
|
+
let b2 = Float(coeffs[2])
|
|
662
|
+
let a1 = Float(coeffs[3])
|
|
663
|
+
let a2 = Float(coeffs[4])
|
|
664
|
+
|
|
665
|
+
// Process samples using Direct Form II transposed
|
|
666
|
+
for i in 0..<frameCount {
|
|
667
|
+
let x = samples[i]
|
|
668
|
+
let y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2
|
|
669
|
+
|
|
670
|
+
x2 = x1
|
|
671
|
+
x1 = x
|
|
672
|
+
y2 = y1
|
|
673
|
+
y1 = y
|
|
674
|
+
|
|
675
|
+
samples[i] = y
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Save filter state
|
|
679
|
+
context.filterStates[bandIndex][stateOffset] = x1
|
|
680
|
+
context.filterStates[bandIndex][stateOffset + 1] = x2
|
|
681
|
+
context.filterStates[bandIndex][stateOffset + 2] = y1
|
|
682
|
+
context.filterStates[bandIndex][stateOffset + 3] = y2
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|