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, .allowBluetooth]
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 engine = PitchEngine(config: config, delegate: self)
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 = -30
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-simple-note-pitch-detector",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "a simple react native library to detect the pitch of the input recording",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",