@javascriptcommon/react-native-track-player 4.1.23 → 4.1.24
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/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +43 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +30 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/AudioPlayer.kt +111 -19
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/APMRenderersFactory.kt +5 -3
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/processors/BalanceAudioProcessor.kt +62 -0
- package/ios/RNTrackPlayer/RNTrackPlayer.swift +56 -4
- package/ios/RNTrackPlayer/TrackPlayer.mm +30 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioTap.swift +21 -18
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/EqualizerAudioTap.swift +565 -232
- package/lib/specs/NativeTrackPlayer.d.ts +7 -0
- package/lib/src/trackPlayer.d.ts +35 -0
- package/lib/src/trackPlayer.js +50 -0
- package/package.json +1 -1
- package/specs/NativeTrackPlayer.ts +9 -0
- package/src/trackPlayer.ts +58 -0
|
@@ -2,285 +2,588 @@
|
|
|
2
2
|
// EqualizerAudioTap.swift
|
|
3
3
|
// SwiftAudioEx
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// Professional 10-band parametric EQ with audio effects.
|
|
6
|
+
//
|
|
7
|
+
// Quality features:
|
|
8
|
+
// - Float64 arithmetic in all IIR paths (eliminates rounding artefacts)
|
|
9
|
+
// - Per-block coefficient smoothing (click-free parameter changes)
|
|
10
|
+
// - DC blocking filter (removes sub-sonic drift from cascaded IIRs)
|
|
11
|
+
// - Envelope-following true-peak limiter (transparent ceiling protection)
|
|
12
|
+
// - All-pass-based stereo virtualizer (natural phase-width, no comb filtering)
|
|
6
13
|
//
|
|
7
14
|
|
|
8
15
|
import Foundation
|
|
9
16
|
import AVFoundation
|
|
10
|
-
import Accelerate
|
|
11
17
|
|
|
12
|
-
/// 10-band parametric equalizer using cascaded biquad filters
|
|
13
|
-
/// Processes audio in real-time via MTAudioProcessingTap
|
|
14
18
|
public class EqualizerAudioTap: AudioTap {
|
|
15
19
|
|
|
16
20
|
// MARK: - Constants
|
|
17
21
|
|
|
18
|
-
///
|
|
22
|
+
/// 8-band equalizer frequencies (Hz), log-spaced for full-spectrum coverage
|
|
19
23
|
public static let frequencies: [Float] = [
|
|
20
|
-
|
|
21
|
-
64, // Bass
|
|
22
|
-
125, // Low-bass
|
|
23
|
-
250, // Low-mid
|
|
24
|
-
500, // Mid
|
|
25
|
-
1000, // Mid
|
|
26
|
-
2000, // Upper-mid
|
|
27
|
-
4000, // Presence
|
|
28
|
-
8000, // Brilliance
|
|
29
|
-
16000 // Air
|
|
24
|
+
60, 150, 400, 1000, 2500, 6000, 12000, 16000
|
|
30
25
|
]
|
|
26
|
+
public static let bandCount = 8
|
|
27
|
+
|
|
28
|
+
/// Q factor for peaking filters (standard for 1-octave graphic EQ)
|
|
29
|
+
private static let eqQ: Double = 1.41
|
|
30
|
+
|
|
31
|
+
/// Passthrough biquad coefficients [b0, b1, b2, a1, a2]
|
|
32
|
+
private static let kUnity: [Double] = [1, 0, 0, 0, 0]
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
public static let bandCount = 10
|
|
34
|
+
// MARK: - Parameter Lock
|
|
34
35
|
|
|
35
|
-
///
|
|
36
|
-
private
|
|
36
|
+
/// Single lock protecting all mutable parameters shared between threads
|
|
37
|
+
private let paramLock = NSLock()
|
|
37
38
|
|
|
38
|
-
// MARK: -
|
|
39
|
+
// MARK: - EQ Bands
|
|
39
40
|
|
|
40
|
-
/// Whether the
|
|
41
|
+
/// Whether the 10-band EQ is active
|
|
41
42
|
public var isEnabled: Bool = true
|
|
42
43
|
|
|
43
|
-
///
|
|
44
|
+
/// Per-band gain in dB (-12 to +12), protected by paramLock
|
|
44
45
|
private var _gains: [Float] = Array(repeating: 0, count: bandCount)
|
|
46
|
+
private var needsEQUpdate: Bool = true
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
private let gainsLock = NSLock()
|
|
48
|
+
// MARK: - Bass Boost (always-positive low shelf)
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
public var isBassBoostEnabled: Bool = false
|
|
51
|
+
public var bassBoostLevel: Float = 0.5
|
|
52
|
+
private static let bbFreq: Double = 150
|
|
53
|
+
private static let bbMinDB: Double = 6 // slider 0 = gentle warmth
|
|
54
|
+
private static let bbMaxDB: Double = 24 // slider 1 = full boost
|
|
55
|
+
private static let bbQ: Double = 0.8
|
|
56
|
+
private var needsBBUpdate: Bool = true
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
private var coefficients: [[Float]] = []
|
|
58
|
+
// MARK: - Loudness Enhancer (low shelf + high shelf)
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
private
|
|
60
|
+
public var isLoudnessEnabled: Bool = false
|
|
61
|
+
public var loudnessLevel: Float = 0.5
|
|
62
|
+
private static let lnLoFreq: Double = 200
|
|
63
|
+
private static let lnLoMaxDB: Double = 15
|
|
64
|
+
private static let lnHiFreq: Double = 3000
|
|
65
|
+
private static let lnHiMaxDB: Double = 10
|
|
66
|
+
private static let lnQ: Double = 0.7
|
|
67
|
+
private var needsLnUpdate: Bool = true
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
// MARK: - Balance (L/R panning)
|
|
70
|
+
|
|
71
|
+
public var balance: Float = 0.0 // -1.0 (full left) to 1.0 (full right)
|
|
72
|
+
|
|
73
|
+
// MARK: - Virtualizer (cross-channel all-pass stereo widener)
|
|
61
74
|
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
public var isVirtualizerEnabled: Bool = false
|
|
76
|
+
public var virtualizerLevel: Float = 0.5
|
|
77
|
+
/// All-pass center frequencies — different per channel for cross-channel decorrelation
|
|
78
|
+
private static let apFreqsL: [Double] = [250, 630, 1500, 3200, 7500]
|
|
79
|
+
private static let apFreqsR: [Double] = [160, 420, 1000, 2200, 5000, 9500]
|
|
64
80
|
|
|
65
|
-
// MARK: -
|
|
81
|
+
// MARK: - Audio-Thread State (only accessed from process callback)
|
|
82
|
+
|
|
83
|
+
private var sampleRate: Double = 44100
|
|
84
|
+
private var channelCount: Int = 2
|
|
85
|
+
|
|
86
|
+
// EQ: current (smoothed) and target coefficients, plus filter states
|
|
87
|
+
private var eqCur: [[Double]] = [] // [band][5]
|
|
88
|
+
private var eqTgt: [[Double]] = [] // [band][5]
|
|
89
|
+
private var eqZ: [[[Double]]] = [] // [band][channel][2] (z1,z2)
|
|
90
|
+
|
|
91
|
+
// Bass boost
|
|
92
|
+
private var bbCur: [Double] = kUnity
|
|
93
|
+
private var bbTgt: [Double] = kUnity
|
|
94
|
+
private var bbZ: [[Double]] = [] // [channel][2]
|
|
95
|
+
|
|
96
|
+
// Loudness
|
|
97
|
+
private var lnLoCur: [Double] = kUnity
|
|
98
|
+
private var lnLoTgt: [Double] = kUnity
|
|
99
|
+
private var lnHiCur: [Double] = kUnity
|
|
100
|
+
private var lnHiTgt: [Double] = kUnity
|
|
101
|
+
private var lnLoZ: [[Double]] = [] // [channel][2]
|
|
102
|
+
private var lnHiZ: [[Double]] = []
|
|
103
|
+
|
|
104
|
+
// Virtualizer: first-order all-pass coefficients and states (per channel)
|
|
105
|
+
private var apCoeffsL: [Double] = [] // left channel sections
|
|
106
|
+
private var apCoeffsR: [Double] = [] // right channel sections
|
|
107
|
+
private var apStateL: [[Double]] = [] // [section][2: xPrev, yPrev]
|
|
108
|
+
private var apStateR: [[Double]] = [] // [section][2: xPrev, yPrev]
|
|
109
|
+
|
|
110
|
+
// DC blocker: y[n] = x[n] - x[n-1] + R*y[n-1]
|
|
111
|
+
private static let dcR: Double = 0.9995 // ~3.5 Hz cutoff at 44.1 kHz
|
|
112
|
+
private var dcXprev: [Double] = [] // per channel
|
|
113
|
+
private var dcYprev: [Double] = []
|
|
114
|
+
|
|
115
|
+
// Envelope-following peak limiter
|
|
116
|
+
private static let limThreshold: Double = 0.89 // ~ -1 dBFS
|
|
117
|
+
private var limGain: Double = 1.0
|
|
118
|
+
private var limAttCoeff: Double = 0 // computed from sample rate
|
|
119
|
+
private var limRelCoeff: Double = 0
|
|
120
|
+
|
|
121
|
+
// Coefficient smoothing alpha (computed from sample rate)
|
|
122
|
+
private var smoothAlpha: Double = 0.004
|
|
123
|
+
|
|
124
|
+
// Tap lifecycle tracking
|
|
125
|
+
private var activeTapCount: Int = 0
|
|
126
|
+
|
|
127
|
+
// ════════════════════════ Public API ═════════════════════════
|
|
66
128
|
|
|
67
129
|
public override init() {
|
|
68
130
|
super.init()
|
|
69
|
-
// Initialize with flat response (all gains at 0)
|
|
70
|
-
resetGains()
|
|
71
131
|
}
|
|
72
132
|
|
|
73
|
-
/// Set gain for a specific band
|
|
74
|
-
/// - Parameters:
|
|
75
|
-
/// - band: Band index (0-9)
|
|
76
|
-
/// - gainDB: Gain in decibels (-12 to +12)
|
|
133
|
+
/// Set gain for a specific band in dB (-12 to +12)
|
|
77
134
|
public func setGain(band: Int, gainDB: Float) {
|
|
78
|
-
guard band >= 0
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
_gains[band] = clampedGain
|
|
84
|
-
needsUpdate = true
|
|
85
|
-
gainsLock.unlock()
|
|
135
|
+
guard band >= 0, band < Self.bandCount else { return }
|
|
136
|
+
paramLock.lock()
|
|
137
|
+
_gains[band] = max(-12, min(12, gainDB))
|
|
138
|
+
needsEQUpdate = true
|
|
139
|
+
paramLock.unlock()
|
|
86
140
|
}
|
|
87
141
|
|
|
88
|
-
/// Set
|
|
89
|
-
/// - Parameter gains: Array of 10 gain values in dB
|
|
142
|
+
/// Set all 10 band gains at once
|
|
90
143
|
public func setAllGains(_ gains: [Float]) {
|
|
91
144
|
guard gains.count == Self.bandCount else { return }
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
needsUpdate = true
|
|
98
|
-
gainsLock.unlock()
|
|
145
|
+
paramLock.lock()
|
|
146
|
+
for i in 0..<Self.bandCount { _gains[i] = max(-12, min(12, gains[i])) }
|
|
147
|
+
needsEQUpdate = true
|
|
148
|
+
paramLock.unlock()
|
|
99
149
|
}
|
|
100
150
|
|
|
101
|
-
/// Get current gain for a
|
|
151
|
+
/// Get current gain for a band
|
|
102
152
|
public func getGain(band: Int) -> Float {
|
|
103
|
-
guard band >= 0
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let gain = _gains[band]
|
|
107
|
-
gainsLock.unlock()
|
|
108
|
-
return gain
|
|
153
|
+
guard band >= 0, band < Self.bandCount else { return 0 }
|
|
154
|
+
paramLock.lock(); defer { paramLock.unlock() }
|
|
155
|
+
return _gains[band]
|
|
109
156
|
}
|
|
110
157
|
|
|
111
158
|
/// Get all current gains
|
|
112
159
|
public func getAllGains() -> [Float] {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
gainsLock.unlock()
|
|
116
|
-
return gains
|
|
160
|
+
paramLock.lock(); defer { paramLock.unlock() }
|
|
161
|
+
return _gains
|
|
117
162
|
}
|
|
118
163
|
|
|
119
|
-
/// Reset all gains to 0 (flat
|
|
164
|
+
/// Reset all gains to 0 dB (flat)
|
|
120
165
|
public func resetGains() {
|
|
121
|
-
|
|
166
|
+
paramLock.lock()
|
|
122
167
|
_gains = Array(repeating: 0, count: Self.bandCount)
|
|
123
|
-
|
|
124
|
-
|
|
168
|
+
needsEQUpdate = true
|
|
169
|
+
paramLock.unlock()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Update bass boost intensity (0.0 = 6 dB, 1.0 = 24 dB)
|
|
173
|
+
public func updateBassBoostLevel(_ level: Float) {
|
|
174
|
+
paramLock.lock()
|
|
175
|
+
bassBoostLevel = max(0, min(1, level))
|
|
176
|
+
needsBBUpdate = true
|
|
177
|
+
paramLock.unlock()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Update loudness enhancer intensity (0.0 = off, 1.0 = full)
|
|
181
|
+
public func updateLoudnessLevel(_ level: Float) {
|
|
182
|
+
paramLock.lock()
|
|
183
|
+
loudnessLevel = max(0, min(1, level))
|
|
184
|
+
needsLnUpdate = true
|
|
185
|
+
paramLock.unlock()
|
|
125
186
|
}
|
|
126
187
|
|
|
127
|
-
|
|
188
|
+
/// Update virtualizer width (0.0 = subtle, 1.0 = maximum)
|
|
189
|
+
public func updateVirtualizerLevel(_ level: Float) {
|
|
190
|
+
virtualizerLevel = max(0, min(1, level))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ═══════════════════ AudioTap Lifecycle ══════════════════════
|
|
128
194
|
|
|
129
195
|
public override func initialize() {
|
|
130
|
-
|
|
196
|
+
activeTapCount += 1
|
|
131
197
|
}
|
|
132
198
|
|
|
133
199
|
public override func finalize() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
200
|
+
activeTapCount -= 1
|
|
201
|
+
if activeTapCount <= 0 {
|
|
202
|
+
eqCur = []; eqTgt = []; eqZ = []
|
|
203
|
+
bbZ = []; lnLoZ = []; lnHiZ = []
|
|
204
|
+
apStateL = []; apStateR = []; apCoeffsL = []; apCoeffsR = []
|
|
205
|
+
dcXprev = []; dcYprev = []
|
|
206
|
+
activeTapCount = 0
|
|
207
|
+
}
|
|
137
208
|
}
|
|
138
209
|
|
|
139
210
|
public override func prepare(description: AudioStreamBasicDescription) {
|
|
140
|
-
sampleRate =
|
|
211
|
+
sampleRate = Double(description.mSampleRate)
|
|
141
212
|
channelCount = Int(description.mChannelsPerFrame)
|
|
213
|
+
let ch = channelCount
|
|
214
|
+
let z2 = [Double](repeating: 0, count: 2)
|
|
215
|
+
|
|
216
|
+
// Smoothing alpha: ~5 ms time constant, adapted to sample rate
|
|
217
|
+
smoothAlpha = 1.0 - exp(-1.0 / (0.005 * sampleRate))
|
|
218
|
+
|
|
219
|
+
// ── EQ bands ──
|
|
220
|
+
eqCur = Array(repeating: Self.kUnity, count: Self.bandCount)
|
|
221
|
+
eqTgt = Array(repeating: Self.kUnity, count: Self.bandCount)
|
|
222
|
+
eqZ = Array(repeating: Array(repeating: z2, count: ch), count: Self.bandCount)
|
|
223
|
+
refreshEQTargets()
|
|
224
|
+
eqCur = eqTgt // snap to initial values (no smoothing ramp on first prepare)
|
|
225
|
+
|
|
226
|
+
// ── Bass boost ──
|
|
227
|
+
bbZ = Array(repeating: z2, count: ch)
|
|
228
|
+
refreshBBTarget()
|
|
229
|
+
bbCur = bbTgt
|
|
230
|
+
|
|
231
|
+
// ── Loudness ──
|
|
232
|
+
lnLoZ = Array(repeating: z2, count: ch)
|
|
233
|
+
lnHiZ = Array(repeating: z2, count: ch)
|
|
234
|
+
refreshLnTargets()
|
|
235
|
+
lnLoCur = lnLoTgt
|
|
236
|
+
lnHiCur = lnHiTgt
|
|
237
|
+
|
|
238
|
+
// ── Virtualizer all-pass (cross-channel) ──
|
|
239
|
+
apCoeffsL = Self.apFreqsL.map { f in
|
|
240
|
+
let omega = tan(Double.pi * f / sampleRate)
|
|
241
|
+
return (1.0 - omega) / (1.0 + omega)
|
|
242
|
+
}
|
|
243
|
+
apCoeffsR = Self.apFreqsR.map { f in
|
|
244
|
+
let omega = tan(Double.pi * f / sampleRate)
|
|
245
|
+
return (1.0 - omega) / (1.0 + omega)
|
|
246
|
+
}
|
|
247
|
+
apStateL = Array(repeating: z2, count: Self.apFreqsL.count)
|
|
248
|
+
apStateR = Array(repeating: z2, count: Self.apFreqsR.count)
|
|
249
|
+
|
|
250
|
+
// ── DC blocker ──
|
|
251
|
+
dcXprev = Array(repeating: 0, count: ch)
|
|
252
|
+
dcYprev = Array(repeating: 0, count: ch)
|
|
142
253
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
count: channelCount
|
|
148
|
-
),
|
|
149
|
-
count: Self.bandCount
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
// Calculate initial coefficients
|
|
153
|
-
updateCoefficients()
|
|
254
|
+
// ── Limiter ──
|
|
255
|
+
limGain = 1.0
|
|
256
|
+
limAttCoeff = 1.0 - exp(-1.0 / (0.0005 * sampleRate)) // 0.5 ms attack
|
|
257
|
+
limRelCoeff = 1.0 - exp(-1.0 / (0.050 * sampleRate)) // 50 ms release
|
|
154
258
|
}
|
|
155
259
|
|
|
156
260
|
public override func unprepare() {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
for channel in 0..<filterStates[band].count {
|
|
160
|
-
filterStates[band][channel] = [0, 0]
|
|
161
|
-
}
|
|
261
|
+
for b in 0..<eqZ.count {
|
|
262
|
+
for c in 0..<eqZ[b].count { eqZ[b][c] = [0, 0] }
|
|
162
263
|
}
|
|
264
|
+
for c in 0..<bbZ.count { bbZ[c] = [0, 0] }
|
|
265
|
+
for c in 0..<lnLoZ.count { lnLoZ[c] = [0, 0] }
|
|
266
|
+
for c in 0..<lnHiZ.count { lnHiZ[c] = [0, 0] }
|
|
267
|
+
for s in 0..<apStateL.count { apStateL[s] = [0, 0] }
|
|
268
|
+
for s in 0..<apStateR.count { apStateR[s] = [0, 0] }
|
|
269
|
+
for c in 0..<dcXprev.count { dcXprev[c] = 0; dcYprev[c] = 0 }
|
|
270
|
+
limGain = 1.0
|
|
163
271
|
}
|
|
164
272
|
|
|
273
|
+
// ══════════════════════ Processing ═══════════════════════════
|
|
274
|
+
|
|
165
275
|
public override func process(numberOfFrames: Int, buffer: UnsafeMutableAudioBufferListPointer) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
276
|
+
guard numberOfFrames > 0 else { return }
|
|
277
|
+
|
|
278
|
+
let anyActive = isEnabled || isBassBoostEnabled || isLoudnessEnabled || isVirtualizerEnabled || balance != 0
|
|
279
|
+
guard anyActive else { return }
|
|
280
|
+
|
|
281
|
+
let nCh = min(buffer.count, channelCount)
|
|
282
|
+
|
|
283
|
+
// ── 1. Absorb pending parameter changes (briefly hold lock) ──
|
|
284
|
+
paramLock.lock()
|
|
285
|
+
if needsEQUpdate { refreshEQTargetsLocked(); needsEQUpdate = false }
|
|
286
|
+
if needsBBUpdate { refreshBBTargetLocked(); needsBBUpdate = false }
|
|
287
|
+
if needsLnUpdate { refreshLnTargetsLocked(); needsLnUpdate = false }
|
|
288
|
+
paramLock.unlock()
|
|
289
|
+
|
|
290
|
+
// ── 2. Smooth coefficients toward targets ──
|
|
291
|
+
// Uses exponential convergence equivalent to per-sample smoothing
|
|
292
|
+
let sf = 1.0 - pow(1.0 - smoothAlpha, Double(numberOfFrames))
|
|
293
|
+
|
|
294
|
+
if isEnabled {
|
|
295
|
+
smoothCoeffs2D(&eqCur, toward: eqTgt, factor: sf)
|
|
296
|
+
}
|
|
297
|
+
if isBassBoostEnabled { smoothCoeffs(&bbCur, toward: bbTgt, factor: sf) }
|
|
298
|
+
if isLoudnessEnabled {
|
|
299
|
+
smoothCoeffs(&lnLoCur, toward: lnLoTgt, factor: sf)
|
|
300
|
+
smoothCoeffs(&lnHiCur, toward: lnHiTgt, factor: sf)
|
|
174
301
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// Apply each band's filter in cascade
|
|
185
|
-
for bandIndex in 0..<Self.bandCount {
|
|
186
|
-
guard bandIndex < currentCoefficients.count else { continue }
|
|
187
|
-
|
|
188
|
-
let coeff = currentCoefficients[bandIndex]
|
|
189
|
-
guard coeff.count >= 5 else { continue }
|
|
190
|
-
|
|
191
|
-
let b0 = coeff[0]
|
|
192
|
-
let b1 = coeff[1]
|
|
193
|
-
let b2 = coeff[2]
|
|
194
|
-
let a1 = coeff[3]
|
|
195
|
-
let a2 = coeff[4]
|
|
196
|
-
|
|
197
|
-
// Get filter state for this band and channel
|
|
198
|
-
var z1 = filterStates[bandIndex][channelIndex][0]
|
|
199
|
-
var z2 = filterStates[bandIndex][channelIndex][1]
|
|
200
|
-
|
|
201
|
-
// Process samples using Direct Form II Transposed
|
|
202
|
-
for i in 0..<numberOfFrames {
|
|
203
|
-
let input = samples[i]
|
|
204
|
-
let output = b0 * input + z1
|
|
205
|
-
z1 = b1 * input - a1 * output + z2
|
|
206
|
-
z2 = b2 * input - a2 * output
|
|
207
|
-
samples[i] = output
|
|
302
|
+
|
|
303
|
+
// ── 3. EQ bands (10 cascaded peaking filters, Float64) ──
|
|
304
|
+
if isEnabled {
|
|
305
|
+
for ch in 0..<nCh {
|
|
306
|
+
guard let data = buffer[ch].mData else { continue }
|
|
307
|
+
let samples = data.assumingMemoryBound(to: Float.self)
|
|
308
|
+
for band in 0..<Self.bandCount {
|
|
309
|
+
guard band < eqCur.count, band < eqZ.count, ch < eqZ[band].count else { continue }
|
|
310
|
+
biquadD(samples, numberOfFrames, eqCur[band], &eqZ[band][ch])
|
|
208
311
|
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── 4. Bass boost (low shelf, Float64) ──
|
|
316
|
+
if isBassBoostEnabled {
|
|
317
|
+
for ch in 0..<nCh {
|
|
318
|
+
guard ch < bbZ.count, let data = buffer[ch].mData else { continue }
|
|
319
|
+
biquadD(data.assumingMemoryBound(to: Float.self), numberOfFrames, bbCur, &bbZ[ch])
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── 5. Loudness enhancer (low shelf + high shelf, Float64) ──
|
|
324
|
+
if isLoudnessEnabled {
|
|
325
|
+
for ch in 0..<nCh {
|
|
326
|
+
guard ch < lnLoZ.count, ch < lnHiZ.count,
|
|
327
|
+
let data = buffer[ch].mData else { continue }
|
|
328
|
+
let samples = data.assumingMemoryBound(to: Float.self)
|
|
329
|
+
biquadD(samples, numberOfFrames, lnLoCur, &lnLoZ[ch])
|
|
330
|
+
biquadD(samples, numberOfFrames, lnHiCur, &lnHiZ[ch])
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── 6. Virtualizer (mid-side + cross-channel all-pass, stereo only) ──
|
|
335
|
+
if isVirtualizerEnabled && nCh >= 2
|
|
336
|
+
&& apCoeffsL.count == Self.apFreqsL.count
|
|
337
|
+
&& apCoeffsR.count == Self.apFreqsR.count
|
|
338
|
+
{
|
|
339
|
+
guard let lD = buffer[0].mData, let rD = buffer[1].mData else { return }
|
|
340
|
+
let L = lD.assumingMemoryBound(to: Float.self)
|
|
341
|
+
let R = rD.assumingMemoryBound(to: Float.self)
|
|
342
|
+
let lvl = Double(virtualizerLevel)
|
|
343
|
+
let width = 1.0 + lvl * 1.5 // 1.0 (normal) to 2.5 (wide)
|
|
344
|
+
let apMix = 0.5 + lvl * 0.5 // 0.5 (moderate) to 1.0 (full phase shift)
|
|
345
|
+
|
|
346
|
+
for i in 0..<numberOfFrames {
|
|
347
|
+
let l = Double(L[i]), r = Double(R[i])
|
|
348
|
+
|
|
349
|
+
// Mid-side decomposition + widening
|
|
350
|
+
let mid = (l + r) * 0.5
|
|
351
|
+
let side = (l - r) * 0.5
|
|
352
|
+
let wL = mid + side * width
|
|
353
|
+
let wR = mid - side * width
|
|
354
|
+
|
|
355
|
+
// Cascade all-pass on LEFT channel (creates phase shift at apFreqsL)
|
|
356
|
+
var sigL = wL
|
|
357
|
+
for s in 0..<apCoeffsL.count {
|
|
358
|
+
let a = apCoeffsL[s]
|
|
359
|
+
let y = a * sigL + apStateL[s][0] - a * apStateL[s][1]
|
|
360
|
+
apStateL[s][0] = sigL
|
|
361
|
+
apStateL[s][1] = y
|
|
362
|
+
sigL = y
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Cascade all-pass on RIGHT channel (different frequencies → apFreqsR)
|
|
366
|
+
var sigR = wR
|
|
367
|
+
for s in 0..<apCoeffsR.count {
|
|
368
|
+
let a = apCoeffsR[s]
|
|
369
|
+
let y = a * sigR + apStateR[s][0] - a * apStateR[s][1]
|
|
370
|
+
apStateR[s][0] = sigR
|
|
371
|
+
apStateR[s][1] = y
|
|
372
|
+
sigR = y
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Cross-channel decorrelation: each channel gets its own phase-shifted version
|
|
376
|
+
L[i] = Float(wL * (1.0 - apMix) + sigL * apMix)
|
|
377
|
+
R[i] = Float(wR * (1.0 - apMix) + sigR * apMix)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ── 7. DC blocker (removes sub-sonic drift, all active channels) ──
|
|
382
|
+
for ch in 0..<nCh {
|
|
383
|
+
guard ch < dcXprev.count, let data = buffer[ch].mData else { continue }
|
|
384
|
+
let samples = data.assumingMemoryBound(to: Float.self)
|
|
385
|
+
var xp = dcXprev[ch], yp = dcYprev[ch]
|
|
386
|
+
let r = Self.dcR
|
|
387
|
+
for i in 0..<numberOfFrames {
|
|
388
|
+
let x = Double(samples[i])
|
|
389
|
+
let y = x - xp + r * yp
|
|
390
|
+
xp = x; yp = y
|
|
391
|
+
samples[i] = Float(y)
|
|
392
|
+
}
|
|
393
|
+
dcXprev[ch] = xp; dcYprev[ch] = yp
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ── 8. Balance (L/R panning, stereo only) ──
|
|
397
|
+
if balance != 0 && nCh >= 2 {
|
|
398
|
+
guard let lD = buffer[0].mData, let rD = buffer[1].mData else { return }
|
|
399
|
+
let L = lD.assumingMemoryBound(to: Float.self)
|
|
400
|
+
let R = rD.assumingMemoryBound(to: Float.self)
|
|
401
|
+
let bal = Double(max(-1, min(1, balance)))
|
|
402
|
+
let lGain = Float(min(1.0, 1.0 - bal))
|
|
403
|
+
let rGain = Float(min(1.0, 1.0 + bal))
|
|
404
|
+
for i in 0..<numberOfFrames {
|
|
405
|
+
L[i] *= lGain
|
|
406
|
+
R[i] *= rGain
|
|
407
|
+
}
|
|
408
|
+
}
|
|
209
409
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
410
|
+
// ── 9. True-peak limiter (linked stereo, transparent) ──
|
|
411
|
+
// Tracks signal peaks and applies smooth gain reduction only when needed.
|
|
412
|
+
// Does NOT color the audio below the threshold (gain stays 1.0).
|
|
413
|
+
let thresh = Self.limThreshold
|
|
414
|
+
let attC = limAttCoeff, relC = limRelCoeff
|
|
415
|
+
|
|
416
|
+
if nCh >= 2 {
|
|
417
|
+
guard let lD = buffer[0].mData, let rD = buffer[1].mData else { return }
|
|
418
|
+
let L = lD.assumingMemoryBound(to: Float.self)
|
|
419
|
+
let R = rD.assumingMemoryBound(to: Float.self)
|
|
420
|
+
for i in 0..<numberOfFrames {
|
|
421
|
+
let peak = max(abs(Double(L[i])), abs(Double(R[i])))
|
|
422
|
+
let tgt = peak > thresh ? thresh / peak : 1.0
|
|
423
|
+
limGain += (tgt - limGain) * (tgt < limGain ? attC : relC)
|
|
424
|
+
L[i] = Float(Double(L[i]) * limGain)
|
|
425
|
+
R[i] = Float(Double(R[i]) * limGain)
|
|
426
|
+
}
|
|
427
|
+
} else if nCh == 1 {
|
|
428
|
+
guard let data = buffer[0].mData else { return }
|
|
429
|
+
let S = data.assumingMemoryBound(to: Float.self)
|
|
430
|
+
for i in 0..<numberOfFrames {
|
|
431
|
+
let peak = abs(Double(S[i]))
|
|
432
|
+
let tgt = peak > thresh ? thresh / peak : 1.0
|
|
433
|
+
limGain += (tgt - limGain) * (tgt < limGain ? attC : relC)
|
|
434
|
+
S[i] = Float(Double(S[i]) * limGain)
|
|
213
435
|
}
|
|
214
436
|
}
|
|
215
437
|
}
|
|
216
438
|
|
|
217
|
-
//
|
|
439
|
+
// ═══════════════ Internal: Biquad Processing ════════════════
|
|
440
|
+
|
|
441
|
+
/// Process Float32 samples through a biquad filter using Float64 arithmetic.
|
|
442
|
+
/// Direct Form II Transposed for numerical stability.
|
|
443
|
+
private func biquadD(
|
|
444
|
+
_ samples: UnsafeMutablePointer<Float>, _ count: Int,
|
|
445
|
+
_ c: [Double], _ z: inout [Double]
|
|
446
|
+
) {
|
|
447
|
+
guard c.count >= 5, z.count >= 2 else { return }
|
|
448
|
+
let b0 = c[0], b1 = c[1], b2 = c[2], a1 = c[3], a2 = c[4]
|
|
449
|
+
var z1 = z[0], z2 = z[1]
|
|
450
|
+
|
|
451
|
+
for i in 0..<count {
|
|
452
|
+
let x = Double(samples[i])
|
|
453
|
+
let y = b0 * x + z1
|
|
454
|
+
z1 = b1 * x - a1 * y + z2
|
|
455
|
+
z2 = b2 * x - a2 * y
|
|
456
|
+
samples[i] = Float(y)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Flush denormals to zero (prevents FPU slowdown on some architectures)
|
|
460
|
+
if z1.magnitude < 1e-15 { z1 = 0 }
|
|
461
|
+
if z2.magnitude < 1e-15 { z2 = 0 }
|
|
462
|
+
|
|
463
|
+
z[0] = z1; z[1] = z2
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ═══════════════ Internal: Coefficient Smoothing ════════════
|
|
467
|
+
|
|
468
|
+
/// Smoothly interpolate a 1D coefficient vector toward target
|
|
469
|
+
private func smoothCoeffs(_ cur: inout [Double], toward tgt: [Double], factor sf: Double) {
|
|
470
|
+
for k in 0..<min(cur.count, tgt.count) {
|
|
471
|
+
cur[k] += (tgt[k] - cur[k]) * sf
|
|
472
|
+
}
|
|
473
|
+
}
|
|
218
474
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
475
|
+
/// Smoothly interpolate a 2D coefficient array toward target
|
|
476
|
+
private func smoothCoeffs2D(_ cur: inout [[Double]], toward tgt: [[Double]], factor sf: Double) {
|
|
477
|
+
for i in 0..<min(cur.count, tgt.count) {
|
|
478
|
+
for k in 0..<min(cur[i].count, tgt[i].count) {
|
|
479
|
+
cur[i][k] += (tgt[i][k] - cur[i][k]) * sf
|
|
480
|
+
}
|
|
481
|
+
}
|
|
223
482
|
}
|
|
224
483
|
|
|
225
|
-
|
|
226
|
-
private func updateCoefficientsLocked() {
|
|
227
|
-
coefficients = []
|
|
484
|
+
// ═══════════════ Internal: Target Coefficient Calc ══════════
|
|
228
485
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
486
|
+
private func refreshEQTargets() {
|
|
487
|
+
paramLock.lock()
|
|
488
|
+
refreshEQTargetsLocked()
|
|
489
|
+
paramLock.unlock()
|
|
490
|
+
}
|
|
232
491
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
492
|
+
/// Must be called with paramLock held
|
|
493
|
+
private func refreshEQTargetsLocked() {
|
|
494
|
+
eqTgt = (0..<Self.bandCount).map { i in
|
|
495
|
+
Self.peakingEQ(
|
|
496
|
+
freq: Double(Self.frequencies[i]),
|
|
497
|
+
gainDB: Double(_gains[i]),
|
|
498
|
+
q: Self.eqQ, sr: sampleRate
|
|
238
499
|
)
|
|
239
|
-
coefficients.append(coeff)
|
|
240
500
|
}
|
|
241
501
|
}
|
|
242
502
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
/// - q: Q factor (bandwidth)
|
|
249
|
-
/// - sampleRate: Sample rate in Hz
|
|
250
|
-
/// - Returns: Coefficients [b0, b1, b2, a1, a2] (normalized by a0)
|
|
251
|
-
private func calculatePeakingEQCoefficients(
|
|
252
|
-
frequency: Float,
|
|
253
|
-
gainDB: Float,
|
|
254
|
-
q: Float,
|
|
255
|
-
sampleRate: Float
|
|
256
|
-
) -> [Float] {
|
|
257
|
-
// If gain is essentially 0, return unity (pass-through)
|
|
258
|
-
if abs(gainDB) < 0.01 {
|
|
259
|
-
return [1, 0, 0, 0, 0] // b0=1, rest=0 means y[n] = x[n]
|
|
260
|
-
}
|
|
503
|
+
private func refreshBBTarget() {
|
|
504
|
+
paramLock.lock()
|
|
505
|
+
refreshBBTargetLocked()
|
|
506
|
+
paramLock.unlock()
|
|
507
|
+
}
|
|
261
508
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
let
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
509
|
+
/// Must be called with paramLock held
|
|
510
|
+
private func refreshBBTargetLocked() {
|
|
511
|
+
let g = Self.bbMinDB + (Self.bbMaxDB - Self.bbMinDB) * Double(bassBoostLevel)
|
|
512
|
+
bbTgt = Self.lowShelf(freq: Self.bbFreq, gainDB: g, q: Self.bbQ, sr: sampleRate)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private func refreshLnTargets() {
|
|
516
|
+
paramLock.lock()
|
|
517
|
+
refreshLnTargetsLocked()
|
|
518
|
+
paramLock.unlock()
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/// Must be called with paramLock held
|
|
522
|
+
private func refreshLnTargetsLocked() {
|
|
523
|
+
let lo = Self.lnLoMaxDB * Double(loudnessLevel)
|
|
524
|
+
let hi = Self.lnHiMaxDB * Double(loudnessLevel)
|
|
525
|
+
lnLoTgt = Self.lowShelf(freq: Self.lnLoFreq, gainDB: lo, q: Self.lnQ, sr: sampleRate)
|
|
526
|
+
lnHiTgt = Self.highShelf(freq: Self.lnHiFreq, gainDB: hi, q: Self.lnQ, sr: sampleRate)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ═══════════════ Filter Design (Audio EQ Cookbook) ═══════════
|
|
530
|
+
// All calculations in Float64 for maximum precision.
|
|
531
|
+
// Reference: Robert Bristow-Johnson's Audio EQ Cookbook
|
|
532
|
+
|
|
533
|
+
/// Peaking EQ (parametric bell)
|
|
534
|
+
private static func peakingEQ(freq: Double, gainDB: Double, q: Double, sr: Double) -> [Double] {
|
|
535
|
+
if abs(gainDB) < 0.01 { return kUnity }
|
|
536
|
+
let A = pow(10.0, gainDB / 40.0)
|
|
537
|
+
let w0 = 2.0 * Double.pi * freq / sr
|
|
538
|
+
let cw = cos(w0), sw = sin(w0)
|
|
539
|
+
let al = sw / (2.0 * q)
|
|
540
|
+
|
|
541
|
+
let b0 = 1.0 + al * A
|
|
542
|
+
let b1 = -2.0 * cw
|
|
543
|
+
let b2 = 1.0 - al * A
|
|
544
|
+
let a0 = 1.0 + al / A
|
|
545
|
+
let a1 = -2.0 * cw
|
|
546
|
+
let a2 = 1.0 - al / A
|
|
547
|
+
|
|
548
|
+
return [b0/a0, b1/a0, b2/a0, a1/a0, a2/a0]
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/// Low-shelf filter
|
|
552
|
+
private static func lowShelf(freq: Double, gainDB: Double, q: Double, sr: Double) -> [Double] {
|
|
553
|
+
if abs(gainDB) < 0.01 { return kUnity }
|
|
554
|
+
let A = pow(10.0, gainDB / 40.0)
|
|
555
|
+
let w0 = 2.0 * Double.pi * freq / sr
|
|
556
|
+
let cw = cos(w0), sw = sin(w0)
|
|
557
|
+
let al = sw / (2.0 * q)
|
|
558
|
+
let s2a = 2.0 * sqrt(A) * al
|
|
559
|
+
|
|
560
|
+
let b0 = A * ((A + 1) - (A - 1) * cw + s2a)
|
|
561
|
+
let b1 = 2 * A * ((A - 1) - (A + 1) * cw)
|
|
562
|
+
let b2 = A * ((A + 1) - (A - 1) * cw - s2a)
|
|
563
|
+
let a0 = (A + 1) + (A - 1) * cw + s2a
|
|
564
|
+
let a1 = -2 * ((A - 1) + (A + 1) * cw)
|
|
565
|
+
let a2 = (A + 1) + (A - 1) * cw - s2a
|
|
566
|
+
|
|
567
|
+
return [b0/a0, b1/a0, b2/a0, a1/a0, a2/a0]
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/// High-shelf filter
|
|
571
|
+
private static func highShelf(freq: Double, gainDB: Double, q: Double, sr: Double) -> [Double] {
|
|
572
|
+
if abs(gainDB) < 0.01 { return kUnity }
|
|
573
|
+
let A = pow(10.0, gainDB / 40.0)
|
|
574
|
+
let w0 = 2.0 * Double.pi * freq / sr
|
|
575
|
+
let cw = cos(w0), sw = sin(w0)
|
|
576
|
+
let al = sw / (2.0 * q)
|
|
577
|
+
let s2a = 2.0 * sqrt(A) * al
|
|
578
|
+
|
|
579
|
+
let b0 = A * ((A + 1) + (A - 1) * cw + s2a)
|
|
580
|
+
let b1 = -2 * A * ((A - 1) + (A + 1) * cw)
|
|
581
|
+
let b2 = A * ((A + 1) + (A - 1) * cw - s2a)
|
|
582
|
+
let a0 = (A + 1) - (A - 1) * cw + s2a
|
|
583
|
+
let a1 = 2 * ((A - 1) - (A + 1) * cw)
|
|
584
|
+
let a2 = (A + 1) - (A - 1) * cw - s2a
|
|
585
|
+
|
|
586
|
+
return [b0/a0, b1/a0, b2/a0, a1/a0, a2/a0]
|
|
284
587
|
}
|
|
285
588
|
}
|
|
286
589
|
|
|
@@ -290,51 +593,81 @@ extension EqualizerAudioTap {
|
|
|
290
593
|
|
|
291
594
|
/// Predefined equalizer presets
|
|
292
595
|
public enum Preset: String, CaseIterable {
|
|
293
|
-
case
|
|
294
|
-
case
|
|
295
|
-
case
|
|
296
|
-
case
|
|
297
|
-
case
|
|
298
|
-
case
|
|
299
|
-
case electronic = "
|
|
300
|
-
case
|
|
301
|
-
case
|
|
302
|
-
case
|
|
303
|
-
case
|
|
304
|
-
case loudness = "
|
|
305
|
-
|
|
306
|
-
|
|
596
|
+
case acoustic = "eqAcoustic"
|
|
597
|
+
case bass = "eqBassBooster"
|
|
598
|
+
case bassReducer = "eqBassReducer"
|
|
599
|
+
case classical = "eqClassical"
|
|
600
|
+
case dance = "eqDance"
|
|
601
|
+
case deep = "eqDeep"
|
|
602
|
+
case electronic = "eqElectronic"
|
|
603
|
+
case flat = "eqFlat"
|
|
604
|
+
case hiphop = "eqHipHop"
|
|
605
|
+
case jazz = "eqJazz"
|
|
606
|
+
case latin = "eqLatin"
|
|
607
|
+
case loudness = "eqLoudness"
|
|
608
|
+
case lounge = "eqLounge"
|
|
609
|
+
case piano = "eqPiano"
|
|
610
|
+
case pop = "eqPop"
|
|
611
|
+
case rnb = "eqRnb"
|
|
612
|
+
case rock = "eqRock"
|
|
613
|
+
case smallSpeakers = "eqSmallSpeakers"
|
|
614
|
+
case spokenWord = "eqSpokenWord"
|
|
615
|
+
case treble = "eqTrebleBooster"
|
|
616
|
+
case trebleReducer = "eqTrebleReducer"
|
|
617
|
+
case vocal = "eqVocalBooster"
|
|
618
|
+
|
|
619
|
+
/// Gain values per preset (8 bands: 60, 150, 400, 1K, 2.5K, 6K, 12K, 16K)
|
|
307
620
|
public var gains: [Float] {
|
|
308
621
|
switch self {
|
|
309
|
-
case .flat:
|
|
310
|
-
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
311
|
-
case .rock:
|
|
312
|
-
return [5, 4, 3, 1, -1, 0, 2, 3, 4, 4]
|
|
313
|
-
case .pop:
|
|
314
|
-
return [-1, 1, 3, 4, 3, 1, 0, 1, 2, 2]
|
|
315
|
-
case .jazz:
|
|
316
|
-
return [3, 2, 1, 2, -1, -1, 0, 1, 2, 3]
|
|
317
|
-
case .classical:
|
|
318
|
-
return [4, 3, 2, 1, -1, -1, 0, 2, 3, 4]
|
|
319
|
-
case .hiphop:
|
|
320
|
-
return [5, 5, 3, 1, -1, 0, 1, 0, 2, 3]
|
|
321
|
-
case .electronic:
|
|
322
|
-
return [4, 4, 2, 0, -2, -1, 0, 2, 4, 4]
|
|
323
622
|
case .acoustic:
|
|
324
|
-
return [3,
|
|
623
|
+
return [ 3, 2, 1, 0, 1, 2, 2, 2]
|
|
325
624
|
case .bass:
|
|
326
|
-
return [6,
|
|
625
|
+
return [ 6, 5, 3, 1, 0, 0, 0, 0]
|
|
626
|
+
case .bassReducer:
|
|
627
|
+
return [-6, -5, -3, -1, 0, 0, 0, 0]
|
|
628
|
+
case .classical:
|
|
629
|
+
return [ 4, 2, 0, -1, 0, 2, 3, 3]
|
|
630
|
+
case .dance:
|
|
631
|
+
return [ 5, 4, 1, 0, 2, 4, 3, 2]
|
|
632
|
+
case .deep:
|
|
633
|
+
return [ 5, 4, 2, 1, 0, -1, -2, -3]
|
|
634
|
+
case .electronic:
|
|
635
|
+
return [ 4, 3, 0, -1, 0, 3, 4, 4]
|
|
636
|
+
case .flat:
|
|
637
|
+
return [ 0, 0, 0, 0, 0, 0, 0, 0]
|
|
638
|
+
case .hiphop:
|
|
639
|
+
return [ 5, 4, 2, 0, -1, 1, 2, 3]
|
|
640
|
+
case .jazz:
|
|
641
|
+
return [ 3, 2, 1, 0, -1, 1, 2, 2]
|
|
642
|
+
case .latin:
|
|
643
|
+
return [ 4, 3, 0, -1, 0, 2, 4, 3]
|
|
644
|
+
case .loudness:
|
|
645
|
+
return [ 5, 3, 0, -2, 0, 2, 4, 4]
|
|
646
|
+
case .lounge:
|
|
647
|
+
return [-1, 1, 2, 1, 0, -1, 1, 1]
|
|
648
|
+
case .piano:
|
|
649
|
+
return [ 1, 0, 1, 2, 3, 2, 2, 1]
|
|
650
|
+
case .pop:
|
|
651
|
+
return [-1, 1, 3, 3, 2, 1, 1, 1]
|
|
652
|
+
case .rnb:
|
|
653
|
+
return [ 5, 4, 2, 0, -1, 1, 2, 2]
|
|
654
|
+
case .rock:
|
|
655
|
+
return [ 4, 3, 0, -1, 1, 3, 4, 3]
|
|
656
|
+
case .smallSpeakers:
|
|
657
|
+
return [ 5, 4, 3, 1, 0, -1, 2, 3]
|
|
658
|
+
case .spokenWord:
|
|
659
|
+
return [-2, 0, 1, 3, 4, 3, 1, -1]
|
|
327
660
|
case .treble:
|
|
328
|
-
return [
|
|
661
|
+
return [ 0, 0, 0, 0, 1, 3, 5, 6]
|
|
662
|
+
case .trebleReducer:
|
|
663
|
+
return [ 0, 0, 0, 0, -1, -3, -5, -6]
|
|
329
664
|
case .vocal:
|
|
330
|
-
return [-2, -1,
|
|
331
|
-
case .loudness:
|
|
332
|
-
return [5, 4, 2, 0, -2, -2, 0, 2, 4, 5]
|
|
665
|
+
return [-2, -1, 1, 3, 4, 3, 1, 0]
|
|
333
666
|
}
|
|
334
667
|
}
|
|
335
668
|
}
|
|
336
669
|
|
|
337
|
-
/// Apply a preset
|
|
670
|
+
/// Apply a built-in preset
|
|
338
671
|
public func applyPreset(_ preset: Preset) {
|
|
339
672
|
setAllGains(preset.gains)
|
|
340
673
|
}
|