react-native-simple-note-pitch-detector 0.4.4 → 0.5.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.
Files changed (28) hide show
  1. package/android/src/main/java/expo/modules/simplenotepitchdetector/PitchAnalyzer.kt +43 -51
  2. package/android/src/main/java/expo/modules/simplenotepitchdetector/ReactNativeSimpleNotePitchDetectorModule.kt +13 -3
  3. package/build/ReactNativeSimpleNotePitchDetector.types.d.ts +7 -0
  4. package/build/ReactNativeSimpleNotePitchDetector.types.d.ts.map +1 -1
  5. package/build/ReactNativeSimpleNotePitchDetector.types.js.map +1 -1
  6. package/build/index.d.ts +8 -0
  7. package/build/index.d.ts.map +1 -1
  8. package/build/index.js +10 -0
  9. package/build/index.js.map +1 -1
  10. package/ios/ReactNativeSimpleNotePitchDetectorModule.swift +33 -45
  11. package/package.json +2 -2
  12. package/src/ReactNativeSimpleNotePitchDetector.types.ts +7 -0
  13. package/src/index.ts +11 -0
  14. package/android/.gradle/8.10/checksums/checksums.lock +0 -0
  15. package/android/.gradle/8.10/dependencies-accessors/gc.properties +0 -0
  16. package/android/.gradle/8.10/fileChanges/last-build.bin +0 -0
  17. package/android/.gradle/8.10/fileHashes/fileHashes.bin +0 -0
  18. package/android/.gradle/8.10/fileHashes/fileHashes.lock +0 -0
  19. package/android/.gradle/8.10/gc.properties +0 -0
  20. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  21. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  22. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  23. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  24. package/android/.gradle/8.9/gc.properties +0 -0
  25. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  26. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  27. package/android/.gradle/nb-cache/trust/CAEFFA28704E1545FBB04BFE5CC58F4EF060708117C17DB37BCD57FB3462AE23 +0 -1
  28. package/android/.gradle/vcs-1/gc.properties +0 -0
@@ -8,20 +8,24 @@ import be.tarsos.dsp.pitch.PitchProcessor
8
8
  import be.tarsos.dsp.pitch.PitchProcessor.PitchEstimationAlgorithm
9
9
  import kotlin.math.log2
10
10
  import kotlin.math.round
11
-
12
- data class NoteAndDecibel(
13
- val note: String = "",
14
- val decibel: Float = 0f
11
+ import kotlin.math.pow
12
+ import kotlin.math.abs
13
+
14
+ data class PitchData(
15
+ val note: String,
16
+ val octave: Int,
17
+ val frequency: Float,
18
+ val amplitude: Float,
19
+ val offset: Float
15
20
  )
16
21
 
17
22
  class PitchAnalyzer {
18
23
 
19
24
  private val notes = arrayOf("C","C#","D","D#","E","F","F#","G","G#","A","A#","B")
20
- private val notesBuffer = Array(6) { NoteAndDecibel() }
21
- private var counter = 0
22
25
 
23
- private lateinit var onChangeNote: (String) -> Unit
26
+ private lateinit var onPitchDetected: (PitchData) -> Unit
24
27
  private var isRecording = false
28
+ private var levelThreshold = -30f
25
29
 
26
30
  private var dispatcher: AudioDispatcher? = null
27
31
  private var processor: AudioProcessor? = null
@@ -30,7 +34,8 @@ class PitchAnalyzer {
30
34
  private val handler = PitchDetectionHandler { res, e ->
31
35
  val pitchInHz = res.pitch
32
36
  val decibel = e.getdBSPL().toFloat()
33
- if (decibel > -85) {
37
+ // Only process if above the level threshold
38
+ if (decibel > levelThreshold) {
34
39
  process(pitchInHz, decibel)
35
40
  }
36
41
  }
@@ -43,29 +48,42 @@ class PitchAnalyzer {
43
48
  }
44
49
 
45
50
  private fun process(pitchInHz: Float, decibel: Float) {
46
- val index = round(12 * (log2(pitchInHz / 440) / log2(2f)) + 69) % 12
51
+ if (pitchInHz <= 0 || pitchInHz.isNaN()) {
52
+ return
53
+ }
47
54
 
48
- if (!index.isNaN() && pitchInHz > 0) {
49
- val note = notes[index.toInt()]
50
- var noteAndDecibel = NoteAndDecibel(note, decibel)
55
+ // Calculate MIDI note number (A4 = 440Hz = MIDI 69)
56
+ val midiNote = 12 * log2(pitchInHz / 440f) + 69
57
+ val roundedMidiNote = round(midiNote).toInt()
51
58
 
52
- if (counter == notesBuffer.size) {
53
- var mostFrequentNote = getMostFrequentNote(notesBuffer)
59
+ // Calculate note index (0-11) and octave
60
+ val noteIndex = ((roundedMidiNote % 12) + 12) % 12
61
+ val octave = (roundedMidiNote / 12) - 1
54
62
 
55
- if (mostFrequentNote != null) {
56
- onChangeNote(mostFrequentNote)
57
- }
63
+ // Calculate offset from perfect pitch (in cents, then convert to percentage)
64
+ // 100 cents = 1 semitone, so we express as percentage of a semitone
65
+ val centsOff = (midiNote - roundedMidiNote) * 100
66
+ val offsetPercentage = centsOff // Already in a reasonable range (-50 to +50)
58
67
 
59
- counter = 0
60
- }
68
+ val note = notes[noteIndex]
61
69
 
62
- notesBuffer[counter] = noteAndDecibel
63
- counter += 1
64
- }
70
+ val pitchData = PitchData(
71
+ note = note,
72
+ octave = octave,
73
+ frequency = pitchInHz,
74
+ amplitude = decibel,
75
+ offset = offsetPercentage
76
+ )
77
+
78
+ onPitchDetected(pitchData)
65
79
  }
66
80
 
67
- fun addOnChangeNoteListener(onChangeNote: (String) -> Unit) {
68
- this.onChangeNote = onChangeNote
81
+ fun setOnPitchDetectedListener(listener: (PitchData) -> Unit) {
82
+ this.onPitchDetected = listener
83
+ }
84
+
85
+ fun setLevelThreshold(threshold: Float) {
86
+ this.levelThreshold = threshold
69
87
  }
70
88
 
71
89
  fun start() {
@@ -74,6 +92,7 @@ class PitchAnalyzer {
74
92
  runner?.start()
75
93
  isRecording = true
76
94
  }
95
+
77
96
  fun stop() {
78
97
  dispatcher?.stop()
79
98
  runner?.interrupt()
@@ -83,31 +102,4 @@ class PitchAnalyzer {
83
102
  fun isRecording(): Boolean {
84
103
  return isRecording
85
104
  }
86
-
87
- private fun getMostFrequentNote(notes: Array<NoteAndDecibel>) : String? {
88
- val noteToDecibel = mutableMapOf<String, Float>();
89
- notes.forEach {
90
- if (!noteToDecibel.containsKey(it.note)) {
91
- noteToDecibel[it.note] = 0f
92
- }
93
-
94
- noteToDecibel[it.note] = noteToDecibel[it.note]!! + it.decibel
95
- }
96
-
97
- var mostFrequentNote = ""
98
- var maxDecibel = 0f
99
-
100
- noteToDecibel.forEach {
101
- if (mostFrequentNote == "" || it.value < maxDecibel) {
102
- mostFrequentNote = it.key
103
- maxDecibel = it.value
104
- }
105
- }
106
-
107
- if (mostFrequentNote == "") {
108
- return null
109
- }
110
-
111
- return mostFrequentNote
112
- }
113
105
  }
@@ -3,6 +3,7 @@ package expo.modules.simplenotepitchdetector
3
3
  import expo.modules.kotlin.modules.Module
4
4
  import expo.modules.kotlin.modules.ModuleDefinition
5
5
  import expo.modules.simplepitchdetector.PitchAnalyzer
6
+ import expo.modules.simplepitchdetector.PitchData
6
7
  import androidx.core.os.bundleOf
7
8
 
8
9
  class ReactNativeSimpleNotePitchDetectorModule : Module() {
@@ -20,10 +21,15 @@ class ReactNativeSimpleNotePitchDetectorModule : Module() {
20
21
  }
21
22
 
22
23
  Function("start") {
23
- pitchAnalyzer.addOnChangeNoteListener { note: String ->
24
+ pitchAnalyzer.setOnPitchDetectedListener { pitchData: PitchData ->
24
25
  this@ReactNativeSimpleNotePitchDetectorModule.sendEvent(
25
26
  "onChangeNote",
26
- bundleOf("note" to note)
27
+ bundleOf(
28
+ "note" to pitchData.note,
29
+ "octave" to pitchData.octave,
30
+ "frequency" to pitchData.frequency.toDouble(),
31
+ "offset" to pitchData.offset.toDouble()
32
+ )
27
33
  )
28
34
  }
29
35
 
@@ -31,7 +37,11 @@ class ReactNativeSimpleNotePitchDetectorModule : Module() {
31
37
  }
32
38
 
33
39
  Function("stop") {
34
- pitchAnalyzer.stop()
40
+ pitchAnalyzer.stop()
41
+ }
42
+
43
+ Function("setLevelThreshold") { threshold: Double ->
44
+ pitchAnalyzer.setLevelThreshold(threshold.toFloat())
35
45
  }
36
46
  }
37
47
  }
@@ -1,5 +1,12 @@
1
1
  export type ChangeEventPayload = {
2
+ /** Note name without octave (e.g., "C#", "D", "Eb") */
2
3
  note: string;
4
+ /** Octave number (e.g., 4 for middle C) */
5
+ octave: number;
6
+ /** Raw frequency in Hz */
7
+ frequency: number;
8
+ /** Offset from perfect pitch as percentage (-50 to +50, negative = flat, positive = sharp) */
9
+ offset: number;
3
10
  };
4
11
  export type ReactNativeSimpleNotePitchDetectorViewProps = {
5
12
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeSimpleNotePitchDetector.types.d.ts","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetector.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
1
+ {"version":3,"file":"ReactNativeSimpleNotePitchDetector.types.d.ts","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetector.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeSimpleNotePitchDetector.types.js","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetector.types.ts"],"names":[],"mappings":"","sourcesContent":["export type ChangeEventPayload = {\n note: string;\n};\n\nexport type ReactNativeSimpleNotePitchDetectorViewProps = {\n name: string;\n};\n"]}
1
+ {"version":3,"file":"ReactNativeSimpleNotePitchDetector.types.js","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetector.types.ts"],"names":[],"mappings":"","sourcesContent":["export type ChangeEventPayload = {\n /** Note name without octave (e.g., \"C#\", \"D\", \"Eb\") */\n note: string;\n /** Octave number (e.g., 4 for middle C) */\n octave: number;\n /** Raw frequency in Hz */\n frequency: number;\n /** Offset from perfect pitch as percentage (-50 to +50, negative = flat, positive = sharp) */\n offset: number;\n};\n\nexport type ReactNativeSimpleNotePitchDetectorViewProps = {\n name: string;\n};\n"]}
package/build/index.d.ts CHANGED
@@ -3,6 +3,14 @@ import { ChangeEventPayload, ReactNativeSimpleNotePitchDetectorViewProps } from
3
3
  export declare function start(): any;
4
4
  export declare function stop(): any;
5
5
  export declare function isRecording(): any;
6
+ /**
7
+ * Set the minimum audio level threshold for pitch detection.
8
+ * Values are in dB (e.g., -30 means sounds quieter than -30dB are ignored).
9
+ * Lower values = more sensitive (picks up quieter sounds).
10
+ * Default is -30.
11
+ * @param threshold - Level threshold in dB (e.g., -30, -35, -40)
12
+ */
13
+ export declare function setLevelThreshold(threshold: number): any;
6
14
  export declare function onChangeNote(listener: (event: ChangeEventPayload) => void): Subscription;
7
15
  export { ReactNativeSimpleNotePitchDetectorViewProps, ChangeEventPayload };
8
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,kBAAkB,EAClB,2CAA2C,EAC5C,MAAM,4CAA4C,CAAC;AAEpD,wBAAgB,KAAK,QAEpB;AAED,wBAAgB,IAAI,QAEnB;AAED,wBAAgB,WAAW,QAE1B;AAOD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAC5C,YAAY,CAEd;AAED,OAAO,EAAE,2CAA2C,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,kBAAkB,EAClB,2CAA2C,EAC5C,MAAM,4CAA4C,CAAC;AAEpD,wBAAgB,KAAK,QAEpB;AAED,wBAAgB,IAAI,QAEnB;AAED,wBAAgB,WAAW,QAE1B;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,OAElD;AAOD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAC5C,YAAY,CAEd;AAED,OAAO,EAAE,2CAA2C,EAAE,kBAAkB,EAAE,CAAC"}
package/build/index.js CHANGED
@@ -9,6 +9,16 @@ export function stop() {
9
9
  export function isRecording() {
10
10
  return ReactNativeSimpleNotePitchDetectorModule.isRecording();
11
11
  }
12
+ /**
13
+ * Set the minimum audio level threshold for pitch detection.
14
+ * Values are in dB (e.g., -30 means sounds quieter than -30dB are ignored).
15
+ * Lower values = more sensitive (picks up quieter sounds).
16
+ * Default is -30.
17
+ * @param threshold - Level threshold in dB (e.g., -30, -35, -40)
18
+ */
19
+ export function setLevelThreshold(threshold) {
20
+ return ReactNativeSimpleNotePitchDetectorModule.setLevelThreshold(threshold);
21
+ }
12
22
  const emitter = new EventEmitter(ReactNativeSimpleNotePitchDetectorModule ??
13
23
  NativeModulesProxy.ReactNativeSimpleNotePitchDetector);
14
24
  export function onChangeNote(listener) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,GAEb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,wCAAwC,MAAM,4CAA4C,CAAC;AAMlG,MAAM,UAAU,KAAK;IACnB,OAAO,wCAAwC,CAAC,KAAK,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,wCAAwC,CAAC,IAAI,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,wCAAwC,CAAC,WAAW,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,wCAAwC;IACtC,kBAAkB,CAAC,kCAAkC,CACxD,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,QAA6C;IAE7C,OAAO,OAAO,CAAC,WAAW,CAAqB,cAAc,EAAE,QAAQ,CAAC,CAAC;AAC3E,CAAC","sourcesContent":["import {\n NativeModulesProxy,\n EventEmitter,\n Subscription,\n} from \"expo-modules-core\";\n\nimport ReactNativeSimpleNotePitchDetectorModule from \"./ReactNativeSimpleNotePitchDetectorModule\";\nimport {\n ChangeEventPayload,\n ReactNativeSimpleNotePitchDetectorViewProps,\n} from \"./ReactNativeSimpleNotePitchDetector.types\";\n\nexport function start() {\n return ReactNativeSimpleNotePitchDetectorModule.start();\n}\n\nexport function stop() {\n return ReactNativeSimpleNotePitchDetectorModule.stop();\n}\n\nexport function isRecording() {\n return ReactNativeSimpleNotePitchDetectorModule.isRecording();\n}\n\nconst emitter = new EventEmitter(\n ReactNativeSimpleNotePitchDetectorModule ??\n NativeModulesProxy.ReactNativeSimpleNotePitchDetector\n);\n\nexport function onChangeNote(\n listener: (event: ChangeEventPayload) => void\n): Subscription {\n return emitter.addListener<ChangeEventPayload>(\"onChangeNote\", listener);\n}\n\nexport { ReactNativeSimpleNotePitchDetectorViewProps, ChangeEventPayload };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,GAEb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,wCAAwC,MAAM,4CAA4C,CAAC;AAMlG,MAAM,UAAU,KAAK;IACnB,OAAO,wCAAwC,CAAC,KAAK,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,wCAAwC,CAAC,IAAI,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,wCAAwC,CAAC,WAAW,EAAE,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,wCAAwC,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,wCAAwC;IACtC,kBAAkB,CAAC,kCAAkC,CACxD,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,QAA6C;IAE7C,OAAO,OAAO,CAAC,WAAW,CAAqB,cAAc,EAAE,QAAQ,CAAC,CAAC;AAC3E,CAAC","sourcesContent":["import {\n NativeModulesProxy,\n EventEmitter,\n Subscription,\n} from \"expo-modules-core\";\n\nimport ReactNativeSimpleNotePitchDetectorModule from \"./ReactNativeSimpleNotePitchDetectorModule\";\nimport {\n ChangeEventPayload,\n ReactNativeSimpleNotePitchDetectorViewProps,\n} from \"./ReactNativeSimpleNotePitchDetector.types\";\n\nexport function start() {\n return ReactNativeSimpleNotePitchDetectorModule.start();\n}\n\nexport function stop() {\n return ReactNativeSimpleNotePitchDetectorModule.stop();\n}\n\nexport function isRecording() {\n return ReactNativeSimpleNotePitchDetectorModule.isRecording();\n}\n\n/**\n * Set the minimum audio level threshold for pitch detection.\n * Values are in dB (e.g., -30 means sounds quieter than -30dB are ignored).\n * Lower values = more sensitive (picks up quieter sounds).\n * Default is -30.\n * @param threshold - Level threshold in dB (e.g., -30, -35, -40)\n */\nexport function setLevelThreshold(threshold: number) {\n return ReactNativeSimpleNotePitchDetectorModule.setLevelThreshold(threshold);\n}\n\nconst emitter = new EventEmitter(\n ReactNativeSimpleNotePitchDetectorModule ??\n NativeModulesProxy.ReactNativeSimpleNotePitchDetector\n);\n\nexport function onChangeNote(\n listener: (event: ChangeEventPayload) => void\n): Subscription {\n return emitter.addListener<ChangeEventPayload>(\"onChangeNote\", listener);\n}\n\nexport { ReactNativeSimpleNotePitchDetectorViewProps, ChangeEventPayload };\n"]}
@@ -3,13 +3,8 @@ import Beethoven
3
3
  import Pitchy
4
4
 
5
5
 
6
- let NOTE_BUFFER_SIZE = 3
7
-
8
6
  public class ReactNativeSimpleNotePitchDetectorModule: Module {
9
-
10
- var notes = Array(repeating: "", count: NOTE_BUFFER_SIZE)
11
- var counter = 0
12
-
7
+
13
8
  public func definition() -> ModuleDefinition {
14
9
 
15
10
  Name("ReactNativeSimpleNotePitchDetector")
@@ -23,69 +18,62 @@ public class ReactNativeSimpleNotePitchDetectorModule: Module {
23
18
  Function("stop") {
24
19
  pitchEngine.stop()
25
20
  }
26
-
21
+
27
22
  Function("isRecording") {
28
23
  return pitchEngine.active
29
24
  }
25
+
26
+ // Allow JS to configure the level threshold
27
+ Function("setLevelThreshold") { (threshold: Double) in
28
+ self.pitchEngine.levelThreshold = Float(threshold)
29
+ }
30
30
  }
31
-
31
+
32
32
  lazy var pitchEngine: PitchEngine = { [weak self] in
33
- let config = Config(estimationStrategy: .yin)
33
+ // Larger buffer (8192) = more accurate pitch detection, slightly more latency
34
+ // Using .yin which is generally best for monophonic instruments
35
+ let config = Config(bufferSize: 8192, estimationStrategy: .yin)
34
36
  let pitchEngine = PitchEngine(config: config, delegate: self)
35
- pitchEngine.levelThreshold = -35
37
+ // Default threshold - can be adjusted from JS via setLevelThreshold()
38
+ pitchEngine.levelThreshold = -30
36
39
  return pitchEngine
37
40
  }()
38
-
39
- func getMostFrequentNote(notes: [String]) -> String? {
40
- var counts = [String: Int]()
41
-
42
- notes.forEach { counts[$0] = (counts[$0] ?? 0) + 1 }
43
-
44
- if let (value, count) = counts.max(by: {$0.1 < $1.1}) {
45
- if (count <= 1) {
46
- return nil
47
- }
48
- return value
49
- }
50
-
51
- return nil
52
- }
53
41
  }
54
42
 
55
43
  extension ReactNativeSimpleNotePitchDetectorModule: PitchEngineDelegate {
56
44
 
57
45
  public func pitchEngine(_ pitchEngine: PitchEngine, didReceivePitch pitch: Pitch) {
58
-
46
+
47
+ let frequency = pitch.wave.frequency
48
+
49
+ // Offset from the nearest note (in percentage, can be negative or positive)
50
+ // Negative = flat, Positive = sharp
59
51
  let offsetPercentage = pitch.closestOffset.percentage
60
- let absOffsetPercentage = abs(offsetPercentage)
61
52
 
62
- guard absOffsetPercentage > 1.0 else {
63
- return
64
- }
65
-
66
- if (counter == notes.count) {
67
- var note = getMostFrequentNote(notes: notes)
68
- if (note != nil) {
69
- self.sendEvent("onChangeNote", ["note" : note])
70
- }
71
- counter = 0
72
- }
73
-
74
53
  do {
75
- let note = try Note(frequency: pitch.wave.frequency)
54
+ let note = try Note(frequency: frequency)
76
55
  let noteStr = note.string
77
- notes[counter] = String(noteStr.prefix(noteStr.count - 1))
78
- counter += 1
56
+ // Remove the octave number (last character) to get just the note name
57
+ let noteName = String(noteStr.prefix(noteStr.count - 1))
58
+ let octave = note.octave
59
+
60
+ // Send all raw data to JS - let the app decide how to filter
61
+ self.sendEvent("onChangeNote", [
62
+ "note": noteName,
63
+ "octave": octave,
64
+ "frequency": frequency,
65
+ "offset": offsetPercentage
66
+ ])
79
67
  } catch {
80
-
68
+ // Could not determine note from frequency - skip
81
69
  }
82
70
  }
83
71
 
84
72
  public func pitchEngine(_ pitchEngine: PitchEngine, didReceiveError error: Error) {
85
-
73
+ // Optionally send error events to JS
86
74
  }
87
75
 
88
76
  public func pitchEngineWentBelowLevelThreshold(_ pitchEngine: PitchEngine) {
89
-
77
+ // Audio dropped below threshold - could notify JS if needed
90
78
  }
91
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-simple-note-pitch-detector",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
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",
@@ -39,4 +39,4 @@
39
39
  "react": "*",
40
40
  "react-native": "*"
41
41
  }
42
- }
42
+ }
@@ -1,5 +1,12 @@
1
1
  export type ChangeEventPayload = {
2
+ /** Note name without octave (e.g., "C#", "D", "Eb") */
2
3
  note: string;
4
+ /** Octave number (e.g., 4 for middle C) */
5
+ octave: number;
6
+ /** Raw frequency in Hz */
7
+ frequency: number;
8
+ /** Offset from perfect pitch as percentage (-50 to +50, negative = flat, positive = sharp) */
9
+ offset: number;
3
10
  };
4
11
 
5
12
  export type ReactNativeSimpleNotePitchDetectorViewProps = {
package/src/index.ts CHANGED
@@ -22,6 +22,17 @@ export function isRecording() {
22
22
  return ReactNativeSimpleNotePitchDetectorModule.isRecording();
23
23
  }
24
24
 
25
+ /**
26
+ * Set the minimum audio level threshold for pitch detection.
27
+ * Values are in dB (e.g., -30 means sounds quieter than -30dB are ignored).
28
+ * Lower values = more sensitive (picks up quieter sounds).
29
+ * Default is -30.
30
+ * @param threshold - Level threshold in dB (e.g., -30, -35, -40)
31
+ */
32
+ export function setLevelThreshold(threshold: number) {
33
+ return ReactNativeSimpleNotePitchDetectorModule.setLevelThreshold(threshold);
34
+ }
35
+
25
36
  const emitter = new EventEmitter(
26
37
  ReactNativeSimpleNotePitchDetectorModule ??
27
38
  NativeModulesProxy.ReactNativeSimpleNotePitchDetector
File without changes
File without changes
@@ -1,2 +0,0 @@
1
- #Sun Dec 15 01:10:44 PST 2024
2
- gradle.version=8.10
@@ -1 +0,0 @@
1
- 7E27CB4C75232A88AD8EEDA14F3AA768C53AB046AD89E67E493D43A1C1B871E6
File without changes