react-native-simple-note-pitch-detector 0.7.5 → 0.7.6
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.
|
@@ -4,6 +4,71 @@ import Pitchy
|
|
|
4
4
|
import AVFoundation
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
// Beethoven's built-in InputSignalTracker runs a second AVCaptureSession
|
|
8
|
+
// just to read audio levels. On iOS 26 that returns -inf, so the tap's
|
|
9
|
+
// `averageLevel > threshold` check is always false and pitch detection
|
|
10
|
+
// never sees a single buffer. This replacement uses ONE AVAudioEngine and
|
|
11
|
+
// computes the level directly from each buffer.
|
|
12
|
+
final class BufferLevelSignalTracker: NSObject, SignalTracker {
|
|
13
|
+
weak var delegate: SignalTrackerDelegate?
|
|
14
|
+
var levelThreshold: Float?
|
|
15
|
+
|
|
16
|
+
private let bufferSize: AVAudioFrameCount
|
|
17
|
+
private var audioEngine: AVAudioEngine?
|
|
18
|
+
private var lastLevel: Float = -160.0
|
|
19
|
+
private let bus = 0
|
|
20
|
+
|
|
21
|
+
var mode: SignalTrackerMode { .record }
|
|
22
|
+
var averageLevel: Float? { lastLevel }
|
|
23
|
+
var peakLevel: Float? { lastLevel }
|
|
24
|
+
|
|
25
|
+
init(bufferSize: AVAudioFrameCount = 8192, delegate: SignalTrackerDelegate? = nil) {
|
|
26
|
+
self.bufferSize = bufferSize
|
|
27
|
+
self.delegate = delegate
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func start() throws {
|
|
31
|
+
let engine = AVAudioEngine()
|
|
32
|
+
audioEngine = engine
|
|
33
|
+
|
|
34
|
+
let inputNode = engine.inputNode
|
|
35
|
+
let format = inputNode.outputFormat(forBus: bus)
|
|
36
|
+
|
|
37
|
+
inputNode.installTap(onBus: bus, bufferSize: bufferSize, format: format) { [weak self] buffer, time in
|
|
38
|
+
guard let self = self else { return }
|
|
39
|
+
guard let channelData = buffer.floatChannelData?[0] else { return }
|
|
40
|
+
|
|
41
|
+
let frameLength = Int(buffer.frameLength)
|
|
42
|
+
var sumSquares: Float = 0
|
|
43
|
+
for i in 0..<frameLength {
|
|
44
|
+
let sample = channelData[i]
|
|
45
|
+
sumSquares += sample * sample
|
|
46
|
+
}
|
|
47
|
+
let rms = sqrt(sumSquares / Float(frameLength))
|
|
48
|
+
let level = 20 * log10f(max(rms, 1e-10))
|
|
49
|
+
self.lastLevel = level
|
|
50
|
+
|
|
51
|
+
let threshold = self.levelThreshold ?? -160.0
|
|
52
|
+
DispatchQueue.main.async {
|
|
53
|
+
if level > threshold {
|
|
54
|
+
self.delegate?.signalTracker(self, didReceiveBuffer: buffer, atTime: time)
|
|
55
|
+
} else {
|
|
56
|
+
self.delegate?.signalTrackerWentBelowLevelThreshold(self)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try engine.start()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func stop() {
|
|
65
|
+
audioEngine?.inputNode.removeTap(onBus: bus)
|
|
66
|
+
audioEngine?.stop()
|
|
67
|
+
audioEngine = nil
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
7
72
|
public class ReactNativeSimpleNotePitchDetectorModule: Module {
|
|
8
73
|
|
|
9
74
|
// Configurable buffer size - can be changed from JS
|
|
@@ -12,6 +77,7 @@ public class ReactNativeSimpleNotePitchDetectorModule: Module {
|
|
|
12
77
|
private var bufferSize: UInt32 = 8192
|
|
13
78
|
private var estimationStrategy: EstimationStrategy = .yin
|
|
14
79
|
private var _pitchEngine: PitchEngine?
|
|
80
|
+
private var belowThresholdLogged = false
|
|
15
81
|
|
|
16
82
|
private func sendStatus(_ level: String, _ message: String) {
|
|
17
83
|
self.sendEvent("onStatus", [
|
|
@@ -25,7 +91,7 @@ public class ReactNativeSimpleNotePitchDetectorModule: Module {
|
|
|
25
91
|
do {
|
|
26
92
|
try session.setCategory(
|
|
27
93
|
.playAndRecord,
|
|
28
|
-
options: [.defaultToSpeaker, .
|
|
94
|
+
options: [.defaultToSpeaker, .allowBluetoothHFP]
|
|
29
95
|
)
|
|
30
96
|
try session.setActive(true, options: .notifyOthersOnDeactivation)
|
|
31
97
|
self.sendStatus(
|
|
@@ -140,9 +206,10 @@ public class ReactNativeSimpleNotePitchDetectorModule: Module {
|
|
|
140
206
|
return engine
|
|
141
207
|
}
|
|
142
208
|
let config = Config(bufferSize: bufferSize, estimationStrategy: estimationStrategy)
|
|
143
|
-
let
|
|
209
|
+
let signalTracker = BufferLevelSignalTracker(bufferSize: bufferSize)
|
|
210
|
+
let engine = PitchEngine(config: config, signalTracker: signalTracker, delegate: self)
|
|
144
211
|
// Default threshold - can be adjusted from JS via setLevelThreshold()
|
|
145
|
-
engine.levelThreshold = -
|
|
212
|
+
engine.levelThreshold = -60
|
|
146
213
|
_pitchEngine = engine
|
|
147
214
|
return engine
|
|
148
215
|
}
|
|
@@ -181,7 +248,6 @@ extension ReactNativeSimpleNotePitchDetectorModule: PitchEngineDelegate {
|
|
|
181
248
|
self.sendStatus("error", "PitchEngine error: \(error.localizedDescription)")
|
|
182
249
|
}
|
|
183
250
|
|
|
184
|
-
private var belowThresholdLogged = false
|
|
185
251
|
public func pitchEngineWentBelowLevelThreshold(_ pitchEngine: PitchEngine) {
|
|
186
252
|
// Log once per session to avoid spamming
|
|
187
253
|
if !belowThresholdLogged {
|
package/package.json
CHANGED