react-native-simple-note-pitch-detector 0.7.4 → 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,11 +91,13 @@ public class ReactNativeSimpleNotePitchDetectorModule: Module {
25
91
  do {
26
92
  try session.setCategory(
27
93
  .playAndRecord,
28
- mode: .measurement,
29
- options: [.defaultToSpeaker, .allowBluetooth]
94
+ options: [.defaultToSpeaker, .allowBluetoothHFP]
30
95
  )
31
96
  try session.setActive(true, options: .notifyOthersOnDeactivation)
32
- self.sendStatus("debug", "AVAudioSession configured: category=playAndRecord, mode=measurement")
97
+ self.sendStatus(
98
+ "debug",
99
+ "AVAudioSession configured: category=playAndRecord, sampleRate=\(session.sampleRate), inputChannels=\(session.inputNumberOfChannels), inputAvailable=\(session.isInputAvailable)"
100
+ )
33
101
  } catch {
34
102
  self.sendStatus("error", "Failed to configure AVAudioSession: \(error.localizedDescription)")
35
103
  }
@@ -138,9 +206,10 @@ public class ReactNativeSimpleNotePitchDetectorModule: Module {
138
206
  return engine
139
207
  }
140
208
  let config = Config(bufferSize: bufferSize, estimationStrategy: estimationStrategy)
141
- let engine = PitchEngine(config: config, delegate: self)
209
+ let signalTracker = BufferLevelSignalTracker(bufferSize: bufferSize)
210
+ let engine = PitchEngine(config: config, signalTracker: signalTracker, delegate: self)
142
211
  // Default threshold - can be adjusted from JS via setLevelThreshold()
143
- engine.levelThreshold = -30
212
+ engine.levelThreshold = -60
144
213
  _pitchEngine = engine
145
214
  return engine
146
215
  }
@@ -180,6 +249,10 @@ extension ReactNativeSimpleNotePitchDetectorModule: PitchEngineDelegate {
180
249
  }
181
250
 
182
251
  public func pitchEngineWentBelowLevelThreshold(_ pitchEngine: PitchEngine) {
183
- // Audio dropped below threshold - could notify JS if needed
252
+ // Log once per session to avoid spamming
253
+ if !belowThresholdLogged {
254
+ self.sendStatus("debug", "Audio level below threshold (audio is reaching Beethoven but too quiet)")
255
+ belowThresholdLogged = true
256
+ }
184
257
  }
185
258
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-simple-note-pitch-detector",
3
- "version": "0.7.4",
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",