react-native-simple-note-pitch-detector 0.5.1 → 0.6.1

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 (30) hide show
  1. package/android/.gradle/9.0-milestone-1/checksums/checksums.lock +0 -0
  2. package/android/.gradle/9.0-milestone-1/fileChanges/last-build.bin +0 -0
  3. package/android/.gradle/9.0-milestone-1/fileHashes/fileHashes.lock +0 -0
  4. package/android/.gradle/9.0-milestone-1/gc.properties +0 -0
  5. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  6. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  7. package/android/.gradle/config.properties +2 -0
  8. package/android/.gradle/vcs-1/gc.properties +0 -0
  9. package/android/.idea/AndroidProjectSystem.xml +6 -0
  10. package/android/.idea/caches/deviceStreaming.xml +1318 -0
  11. package/android/.idea/gradle.xml +13 -0
  12. package/android/.idea/migrations.xml +10 -0
  13. package/android/.idea/misc.xml +9 -0
  14. package/android/.idea/runConfigurations.xml +17 -0
  15. package/android/.idea/vcs.xml +6 -0
  16. package/android/local.properties +8 -0
  17. package/android/src/main/java/expo/modules/simplenotepitchdetector/PitchAnalyzer.kt +77 -8
  18. package/android/src/main/java/expo/modules/simplenotepitchdetector/ReactNativeSimpleNotePitchDetectorModule.kt +39 -1
  19. package/build/ReactNativeSimpleNotePitchDetectorModule.web.d.ts +6 -1
  20. package/build/ReactNativeSimpleNotePitchDetectorModule.web.d.ts.map +1 -1
  21. package/build/ReactNativeSimpleNotePitchDetectorModule.web.js +6 -1
  22. package/build/ReactNativeSimpleNotePitchDetectorModule.web.js.map +1 -1
  23. package/build/index.d.ts +65 -0
  24. package/build/index.d.ts.map +1 -1
  25. package/build/index.js +71 -0
  26. package/build/index.js.map +1 -1
  27. package/ios/ReactNativeSimpleNotePitchDetectorModule.swift +102 -13
  28. package/package.json +2 -2
  29. package/src/ReactNativeSimpleNotePitchDetectorModule.web.ts +6 -1
  30. package/src/index.ts +83 -0
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="GradleMigrationSettings" migrationVersion="1" />
4
+ <component name="GradleSettings">
5
+ <option name="linkedExternalProjectsSettings">
6
+ <GradleProjectSettings>
7
+ <option name="testRunner" value="CHOOSE_PER_TEST" />
8
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
9
+ <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
10
+ </GradleProjectSettings>
11
+ </option>
12
+ </component>
13
+ </project>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectMigrations">
4
+ <option name="MigrateToGradleLocalJavaHome">
5
+ <set>
6
+ <option value="$PROJECT_DIR$" />
7
+ </set>
8
+ </option>
9
+ </component>
10
+ </project>
@@ -0,0 +1,9 @@
1
+ <project version="4">
2
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
3
+ <component name="ProjectRootManager">
4
+ <output url="file://$PROJECT_DIR$/build/classes" />
5
+ </component>
6
+ <component name="ProjectType">
7
+ <option name="id" value="Android" />
8
+ </component>
9
+ </project>
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="RunConfigurationProducerService">
4
+ <option name="ignoredProducers">
5
+ <set>
6
+ <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
7
+ <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
8
+ <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
9
+ <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
10
+ <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
11
+ <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
12
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
13
+ <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
14
+ </set>
15
+ </option>
16
+ </component>
17
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ ## This file must *NOT* be checked into Version Control Systems,
2
+ # as it contains information specific to your local configuration.
3
+ #
4
+ # Location of the SDK. This is only used by Gradle.
5
+ # For customization when using a Version Control System, please read the
6
+ # header note.
7
+ #Mon Feb 02 16:28:55 PST 2026
8
+ sdk.dir=/Users/derekdawson/Library/Android/sdk
@@ -1,5 +1,6 @@
1
1
  package expo.modules.simplepitchdetector
2
2
 
3
+ import android.util.Log
3
4
  import be.tarsos.dsp.AudioDispatcher
4
5
  import be.tarsos.dsp.AudioProcessor
5
6
  import be.tarsos.dsp.io.android.AudioDispatcherFactory
@@ -11,6 +12,8 @@ import kotlin.math.round
11
12
  import kotlin.math.pow
12
13
  import kotlin.math.abs
13
14
 
15
+ private const val TAG = "PitchAnalyzer"
16
+
14
17
  data class PitchData(
15
18
  val note: String,
16
19
  val octave: Int,
@@ -24,8 +27,16 @@ class PitchAnalyzer {
24
27
  private val notes = arrayOf("C","C#","D","D#","E","F","F#","G","G#","A","A#","B")
25
28
 
26
29
  private lateinit var onPitchDetected: (PitchData) -> Unit
30
+ private var onStatus: ((String, String) -> Unit)? = null
27
31
  private var isRecording = false
28
32
  private var levelThreshold = -30f
33
+ // Configurable buffer size - can be changed from JS
34
+ // Larger buffer = better low frequency detection, more latency
35
+ // Smaller buffer = better high frequency detection, less latency
36
+ private var bufferSize = 2048
37
+ // Configurable algorithm - can be changed from JS
38
+ private var algorithm = PitchEstimationAlgorithm.FFT_YIN
39
+ private var algorithmName = "fft_yin"
29
40
 
30
41
  private var dispatcher: AudioDispatcher? = null
31
42
  private var processor: AudioProcessor? = null
@@ -36,15 +47,26 @@ class PitchAnalyzer {
36
47
  val decibel = e.getdBSPL().toFloat()
37
48
  // Only process if above the level threshold
38
49
  if (decibel > levelThreshold) {
50
+ // Note: Not logging every pitch detection to JS to avoid flooding
51
+ // Use adb logcat for verbose pitch logs
52
+ Log.v(TAG, "Pitch detected: ${pitchInHz}Hz at ${decibel}dB")
39
53
  process(pitchInHz, decibel)
40
54
  }
41
55
  }
42
56
 
43
57
  private fun prepare() {
44
- processor =
45
- PitchProcessor(PitchEstimationAlgorithm.FFT_YIN, 22050f, 1024, handler)
46
- dispatcher = AudioDispatcherFactory.fromDefaultMicrophone(22050, 1024, 0)
47
- dispatcher?.addAudioProcessor(processor)
58
+ try {
59
+ logStatus("debug", "prepare() called with bufferSize=$bufferSize, algorithm=$algorithmName")
60
+ processor = PitchProcessor(algorithm, 22050f, bufferSize, handler)
61
+ logStatus("debug", "PitchProcessor created successfully")
62
+ dispatcher = AudioDispatcherFactory.fromDefaultMicrophone(22050, bufferSize, 0)
63
+ logStatus("debug", "AudioDispatcher created successfully: $dispatcher")
64
+ dispatcher?.addAudioProcessor(processor)
65
+ logStatus("debug", "AudioProcessor added to dispatcher")
66
+ } catch (e: Exception) {
67
+ logStatus("error", "Error in prepare(): ${e.message}")
68
+ throw e
69
+ }
48
70
  }
49
71
 
50
72
  private fun process(pitchInHz: Float, decibel: Float) {
@@ -82,15 +104,62 @@ class PitchAnalyzer {
82
104
  this.onPitchDetected = listener
83
105
  }
84
106
 
107
+ fun setOnStatusListener(listener: (String, String) -> Unit) {
108
+ this.onStatus = listener
109
+ }
110
+
111
+ private fun logStatus(level: String, message: String) {
112
+ when (level) {
113
+ "debug" -> Log.d(TAG, message)
114
+ "error" -> Log.e(TAG, message)
115
+ "verbose" -> Log.v(TAG, message)
116
+ else -> Log.d(TAG, message)
117
+ }
118
+ onStatus?.invoke(level, message)
119
+ }
120
+
85
121
  fun setLevelThreshold(threshold: Float) {
86
122
  this.levelThreshold = threshold
87
123
  }
88
124
 
125
+ fun setBufferSize(size: Int) {
126
+ this.bufferSize = size
127
+ }
128
+
129
+ fun getBufferSize(): Int {
130
+ return this.bufferSize
131
+ }
132
+
133
+ fun setAlgorithm(name: String) {
134
+ this.algorithmName = name.lowercase()
135
+ this.algorithm = when (this.algorithmName) {
136
+ "yin" -> PitchEstimationAlgorithm.YIN
137
+ "fft_yin" -> PitchEstimationAlgorithm.FFT_YIN
138
+ "mpm" -> PitchEstimationAlgorithm.MPM
139
+ "fft_pitch" -> PitchEstimationAlgorithm.FFT_PITCH
140
+ "dynamic_wavelet" -> PitchEstimationAlgorithm.DYNAMIC_WAVELET
141
+ "amdf" -> PitchEstimationAlgorithm.AMDF
142
+ else -> PitchEstimationAlgorithm.FFT_YIN // Default
143
+ }
144
+ }
145
+
146
+ fun getAlgorithm(): String {
147
+ return this.algorithmName
148
+ }
149
+
89
150
  fun start() {
90
- prepare()
91
- runner = Thread(dispatcher)
92
- runner?.start()
93
- isRecording = true
151
+ try {
152
+ logStatus("debug", "start() called")
153
+ prepare()
154
+ runner = Thread(dispatcher)
155
+ logStatus("debug", "Thread created, starting...")
156
+ runner?.start()
157
+ isRecording = true
158
+ logStatus("debug", "Recording started successfully")
159
+ } catch (e: Exception) {
160
+ logStatus("error", "Error in start(): ${e.message}")
161
+ isRecording = false
162
+ }
94
163
  }
95
164
 
96
165
  fun stop() {
@@ -10,11 +10,21 @@ class ReactNativeSimpleNotePitchDetectorModule : Module() {
10
10
 
11
11
  private val pitchAnalyzer = PitchAnalyzer()
12
12
 
13
+ private fun sendStatus(level: String, message: String) {
14
+ this.sendEvent(
15
+ "onStatus",
16
+ bundleOf(
17
+ "level" to level,
18
+ "message" to message
19
+ )
20
+ )
21
+ }
22
+
13
23
  override fun definition() = ModuleDefinition {
14
24
 
15
25
  Name("ReactNativeSimpleNotePitchDetector")
16
26
 
17
- Events("onChangeNote")
27
+ Events("onChangeNote", "onStatus")
18
28
 
19
29
  Function("isRecording") {
20
30
  pitchAnalyzer.isRecording()
@@ -33,6 +43,10 @@ class ReactNativeSimpleNotePitchDetectorModule : Module() {
33
43
  )
34
44
  }
35
45
 
46
+ pitchAnalyzer.setOnStatusListener { level: String, message: String ->
47
+ this@ReactNativeSimpleNotePitchDetectorModule.sendStatus(level, message)
48
+ }
49
+
36
50
  pitchAnalyzer.start()
37
51
  }
38
52
 
@@ -43,5 +57,29 @@ class ReactNativeSimpleNotePitchDetectorModule : Module() {
43
57
  Function("setLevelThreshold") { threshold: Double ->
44
58
  pitchAnalyzer.setLevelThreshold(threshold.toFloat())
45
59
  }
60
+
61
+ // Allow JS to configure the buffer size
62
+ // Must be called before start() to take effect
63
+ // Common values: 1024 (better for high frequencies), 2048 (balanced), 4096 (better for low frequencies)
64
+ Function("setBufferSize") { size: Int ->
65
+ pitchAnalyzer.setBufferSize(size)
66
+ }
67
+
68
+ // Get current buffer size
69
+ Function("getBufferSize") {
70
+ pitchAnalyzer.getBufferSize()
71
+ }
72
+
73
+ // Allow JS to configure the estimation algorithm
74
+ // Android options: "yin", "fft_yin", "mpm", "fft_pitch", "dynamic_wavelet", "amdf"
75
+ // Must be called before start() to take effect
76
+ Function("setAlgorithm") { algorithm: String ->
77
+ pitchAnalyzer.setAlgorithm(algorithm)
78
+ }
79
+
80
+ // Get current algorithm name
81
+ Function("getAlgorithm") {
82
+ pitchAnalyzer.getAlgorithm()
83
+ }
46
84
  }
47
85
  }
@@ -2,8 +2,13 @@ import { ChangeEventPayload } from "./ReactNativeSimpleNotePitchDetector.types";
2
2
  declare const _default: {
3
3
  start: () => void;
4
4
  stop: () => void;
5
- isRecording: () => void;
5
+ isRecording: () => boolean;
6
6
  onChangePitch: (_: ChangeEventPayload) => void;
7
+ setLevelThreshold: (_: number) => void;
8
+ setBufferSize: (_: number) => void;
9
+ getBufferSize: () => number;
10
+ setAlgorithm: (_: string) => void;
11
+ getAlgorithm: () => string;
7
12
  };
8
13
  export default _default;
9
14
  //# sourceMappingURL=ReactNativeSimpleNotePitchDetectorModule.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeSimpleNotePitchDetectorModule.web.d.ts","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetectorModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;;;;;uBAM3D,kBAAkB;;AAJvC,wBAKE"}
1
+ {"version":3,"file":"ReactNativeSimpleNotePitchDetectorModule.web.d.ts","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetectorModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;;;;;uBAM3D,kBAAkB;2BACd,MAAM;uBACV,MAAM;;sBAEP,MAAM;;;AAR1B,wBAUE"}
@@ -1,7 +1,12 @@
1
1
  export default {
2
2
  start: () => { },
3
3
  stop: () => { },
4
- isRecording: () => { },
4
+ isRecording: () => false,
5
5
  onChangePitch: (_) => { },
6
+ setLevelThreshold: (_) => { },
7
+ setBufferSize: (_) => { },
8
+ getBufferSize: () => 8192,
9
+ setAlgorithm: (_) => { },
10
+ getAlgorithm: () => "yin",
6
11
  };
7
12
  //# sourceMappingURL=ReactNativeSimpleNotePitchDetectorModule.web.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeSimpleNotePitchDetectorModule.web.js","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetectorModule.web.ts"],"names":[],"mappings":"AAEA,eAAe;IACb,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,aAAa,EAAE,CAAC,CAAqB,EAAE,EAAE,GAAE,CAAC;CAC7C,CAAC","sourcesContent":["import { ChangeEventPayload } from \"./ReactNativeSimpleNotePitchDetector.types\";\n\nexport default {\n start: () => {},\n stop: () => {},\n isRecording: () => {},\n onChangePitch: (_: ChangeEventPayload) => {},\n};\n"]}
1
+ {"version":3,"file":"ReactNativeSimpleNotePitchDetectorModule.web.js","sourceRoot":"","sources":["../src/ReactNativeSimpleNotePitchDetectorModule.web.ts"],"names":[],"mappings":"AAEA,eAAe;IACb,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK;IACxB,aAAa,EAAE,CAAC,CAAqB,EAAE,EAAE,GAAE,CAAC;IAC5C,iBAAiB,EAAE,CAAC,CAAS,EAAE,EAAE,GAAE,CAAC;IACpC,aAAa,EAAE,CAAC,CAAS,EAAE,EAAE,GAAE,CAAC;IAChC,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI;IACzB,YAAY,EAAE,CAAC,CAAS,EAAE,EAAE,GAAE,CAAC;IAC/B,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK;CAC1B,CAAC","sourcesContent":["import { ChangeEventPayload } from \"./ReactNativeSimpleNotePitchDetector.types\";\n\nexport default {\n start: () => {},\n stop: () => {},\n isRecording: () => false,\n onChangePitch: (_: ChangeEventPayload) => {},\n setLevelThreshold: (_: number) => {},\n setBufferSize: (_: number) => {},\n getBufferSize: () => 8192,\n setAlgorithm: (_: string) => {},\n getAlgorithm: () => \"yin\",\n};\n"]}
package/build/index.d.ts CHANGED
@@ -11,6 +11,71 @@ export declare function isRecording(): any;
11
11
  * @param threshold - Level threshold in dB (e.g., -30, -35, -40)
12
12
  */
13
13
  export declare function setLevelThreshold(threshold: number): any;
14
+ /**
15
+ * Set the buffer size for pitch detection.
16
+ * Must be called before start() or will restart the engine if already running.
17
+ *
18
+ * Buffer size affects the trade-off between frequency range and responsiveness:
19
+ * - Smaller buffer (2048-4096): Better for high frequencies, faster response, but less accurate for low notes
20
+ * - Larger buffer (8192-16384): Better for low frequencies, more accurate, but slightly more latency
21
+ *
22
+ * iOS defaults to 8192, Android defaults to 2048.
23
+ *
24
+ * Recommended values:
25
+ * - 4096: Good balance for most use cases
26
+ * - 8192: Better for bass instruments or full piano range (iOS default)
27
+ * - 2048: Better for high-pitched instruments, faster response (Android default)
28
+ *
29
+ * @param size - Buffer size (must be power of 2: 1024, 2048, 4096, 8192, 16384)
30
+ */
31
+ export declare function setBufferSize(size: number): any;
32
+ /**
33
+ * Get the current buffer size.
34
+ * @returns Current buffer size
35
+ */
36
+ export declare function getBufferSize(): number;
37
+ /**
38
+ * Set the pitch estimation algorithm.
39
+ * Must be called before start() or will restart the engine if already running.
40
+ *
41
+ * Available algorithms differ by platform:
42
+ *
43
+ * **iOS (Beethoven library):**
44
+ * - "yin" (default) - YIN algorithm, good for monophonic instruments
45
+ * - "hps" - Harmonic Product Spectrum
46
+ * - "barycentric" - Barycentric interpolation
47
+ * - "quadradic" - Quadratic interpolation
48
+ * - "jains" - Jain's method
49
+ * - "quinnsFirst" - Quinn's first estimator
50
+ * - "quinnsSecond" - Quinn's second estimator
51
+ * - "maxValue" - Maximum value method
52
+ *
53
+ * **Android (TarsosDSP library):**
54
+ * - "fft_yin" (default) - FFT-based YIN, faster than pure YIN
55
+ * - "yin" - Pure YIN algorithm
56
+ * - "mpm" - McLeod Pitch Method, good for speech and music
57
+ * - "fft_pitch" - FFT bin with most energy
58
+ * - "dynamic_wavelet" - Dynamic wavelet algorithm
59
+ * - "amdf" - Average Magnitude Difference Function
60
+ *
61
+ * @param algorithm - Algorithm name (case-insensitive)
62
+ */
63
+ export declare function setAlgorithm(algorithm: string): any;
64
+ /**
65
+ * Get the current algorithm name.
66
+ * @returns Current algorithm name
67
+ */
68
+ export declare function getAlgorithm(): string;
14
69
  export declare function onChangeNote(listener: (event: ChangeEventPayload) => void): Subscription;
70
+ export interface StatusEventPayload {
71
+ level: "debug" | "error" | "verbose";
72
+ message: string;
73
+ }
74
+ /**
75
+ * Listen for status/debug messages from the native module.
76
+ * Useful for debugging audio initialization issues.
77
+ * @param listener - Callback that receives status events with level and message
78
+ */
79
+ export declare function onStatus(listener: (event: StatusEventPayload) => void): Subscription;
15
80
  export { ReactNativeSimpleNotePitchDetectorViewProps, ChangeEventPayload };
16
81
  //# 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;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"}
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;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,OAEzC;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,OAE7C;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAOD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAC5C,YAAY,CAEd;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAC5C,YAAY,CAEd;AAED,OAAO,EAAE,2CAA2C,EAAE,kBAAkB,EAAE,CAAC"}
package/build/index.js CHANGED
@@ -19,9 +19,80 @@ export function isRecording() {
19
19
  export function setLevelThreshold(threshold) {
20
20
  return ReactNativeSimpleNotePitchDetectorModule.setLevelThreshold(threshold);
21
21
  }
22
+ /**
23
+ * Set the buffer size for pitch detection.
24
+ * Must be called before start() or will restart the engine if already running.
25
+ *
26
+ * Buffer size affects the trade-off between frequency range and responsiveness:
27
+ * - Smaller buffer (2048-4096): Better for high frequencies, faster response, but less accurate for low notes
28
+ * - Larger buffer (8192-16384): Better for low frequencies, more accurate, but slightly more latency
29
+ *
30
+ * iOS defaults to 8192, Android defaults to 2048.
31
+ *
32
+ * Recommended values:
33
+ * - 4096: Good balance for most use cases
34
+ * - 8192: Better for bass instruments or full piano range (iOS default)
35
+ * - 2048: Better for high-pitched instruments, faster response (Android default)
36
+ *
37
+ * @param size - Buffer size (must be power of 2: 1024, 2048, 4096, 8192, 16384)
38
+ */
39
+ export function setBufferSize(size) {
40
+ return ReactNativeSimpleNotePitchDetectorModule.setBufferSize(size);
41
+ }
42
+ /**
43
+ * Get the current buffer size.
44
+ * @returns Current buffer size
45
+ */
46
+ export function getBufferSize() {
47
+ return ReactNativeSimpleNotePitchDetectorModule.getBufferSize();
48
+ }
49
+ /**
50
+ * Set the pitch estimation algorithm.
51
+ * Must be called before start() or will restart the engine if already running.
52
+ *
53
+ * Available algorithms differ by platform:
54
+ *
55
+ * **iOS (Beethoven library):**
56
+ * - "yin" (default) - YIN algorithm, good for monophonic instruments
57
+ * - "hps" - Harmonic Product Spectrum
58
+ * - "barycentric" - Barycentric interpolation
59
+ * - "quadradic" - Quadratic interpolation
60
+ * - "jains" - Jain's method
61
+ * - "quinnsFirst" - Quinn's first estimator
62
+ * - "quinnsSecond" - Quinn's second estimator
63
+ * - "maxValue" - Maximum value method
64
+ *
65
+ * **Android (TarsosDSP library):**
66
+ * - "fft_yin" (default) - FFT-based YIN, faster than pure YIN
67
+ * - "yin" - Pure YIN algorithm
68
+ * - "mpm" - McLeod Pitch Method, good for speech and music
69
+ * - "fft_pitch" - FFT bin with most energy
70
+ * - "dynamic_wavelet" - Dynamic wavelet algorithm
71
+ * - "amdf" - Average Magnitude Difference Function
72
+ *
73
+ * @param algorithm - Algorithm name (case-insensitive)
74
+ */
75
+ export function setAlgorithm(algorithm) {
76
+ return ReactNativeSimpleNotePitchDetectorModule.setAlgorithm(algorithm);
77
+ }
78
+ /**
79
+ * Get the current algorithm name.
80
+ * @returns Current algorithm name
81
+ */
82
+ export function getAlgorithm() {
83
+ return ReactNativeSimpleNotePitchDetectorModule.getAlgorithm();
84
+ }
22
85
  const emitter = new EventEmitter(ReactNativeSimpleNotePitchDetectorModule ??
23
86
  NativeModulesProxy.ReactNativeSimpleNotePitchDetector);
24
87
  export function onChangeNote(listener) {
25
88
  return emitter.addListener("onChangeNote", listener);
26
89
  }
90
+ /**
91
+ * Listen for status/debug messages from the native module.
92
+ * Useful for debugging audio initialization issues.
93
+ * @param listener - Callback that receives status events with level and message
94
+ */
95
+ export function onStatus(listener) {
96
+ return emitter.addListener("onStatus", listener);
97
+ }
27
98
  //# sourceMappingURL=index.js.map
@@ -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;;;;;;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"]}
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;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,wCAAwC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,wCAAwC,CAAC,aAAa,EAAE,CAAC;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,wCAAwC,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;AAC1E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,wCAAwC,CAAC,YAAY,EAAE,CAAC;AACjE,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;AAOD;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CACtB,QAA6C;IAE7C,OAAO,OAAO,CAAC,WAAW,CAAqB,UAAU,EAAE,QAAQ,CAAC,CAAC;AACvE,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\n/**\n * Set the buffer size for pitch detection.\n * Must be called before start() or will restart the engine if already running.\n *\n * Buffer size affects the trade-off between frequency range and responsiveness:\n * - Smaller buffer (2048-4096): Better for high frequencies, faster response, but less accurate for low notes\n * - Larger buffer (8192-16384): Better for low frequencies, more accurate, but slightly more latency\n *\n * iOS defaults to 8192, Android defaults to 2048.\n *\n * Recommended values:\n * - 4096: Good balance for most use cases\n * - 8192: Better for bass instruments or full piano range (iOS default)\n * - 2048: Better for high-pitched instruments, faster response (Android default)\n *\n * @param size - Buffer size (must be power of 2: 1024, 2048, 4096, 8192, 16384)\n */\nexport function setBufferSize(size: number) {\n return ReactNativeSimpleNotePitchDetectorModule.setBufferSize(size);\n}\n\n/**\n * Get the current buffer size.\n * @returns Current buffer size\n */\nexport function getBufferSize(): number {\n return ReactNativeSimpleNotePitchDetectorModule.getBufferSize();\n}\n\n/**\n * Set the pitch estimation algorithm.\n * Must be called before start() or will restart the engine if already running.\n *\n * Available algorithms differ by platform:\n *\n * **iOS (Beethoven library):**\n * - \"yin\" (default) - YIN algorithm, good for monophonic instruments\n * - \"hps\" - Harmonic Product Spectrum\n * - \"barycentric\" - Barycentric interpolation\n * - \"quadradic\" - Quadratic interpolation\n * - \"jains\" - Jain's method\n * - \"quinnsFirst\" - Quinn's first estimator\n * - \"quinnsSecond\" - Quinn's second estimator\n * - \"maxValue\" - Maximum value method\n *\n * **Android (TarsosDSP library):**\n * - \"fft_yin\" (default) - FFT-based YIN, faster than pure YIN\n * - \"yin\" - Pure YIN algorithm\n * - \"mpm\" - McLeod Pitch Method, good for speech and music\n * - \"fft_pitch\" - FFT bin with most energy\n * - \"dynamic_wavelet\" - Dynamic wavelet algorithm\n * - \"amdf\" - Average Magnitude Difference Function\n *\n * @param algorithm - Algorithm name (case-insensitive)\n */\nexport function setAlgorithm(algorithm: string) {\n return ReactNativeSimpleNotePitchDetectorModule.setAlgorithm(algorithm);\n}\n\n/**\n * Get the current algorithm name.\n * @returns Current algorithm name\n */\nexport function getAlgorithm(): string {\n return ReactNativeSimpleNotePitchDetectorModule.getAlgorithm();\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 interface StatusEventPayload {\n level: \"debug\" | \"error\" | \"verbose\";\n message: string;\n}\n\n/**\n * Listen for status/debug messages from the native module.\n * Useful for debugging audio initialization issues.\n * @param listener - Callback that receives status events with level and message\n */\nexport function onStatus(\n listener: (event: StatusEventPayload) => void\n): Subscription {\n return emitter.addListener<StatusEventPayload>(\"onStatus\", listener);\n}\n\nexport { ReactNativeSimpleNotePitchDetectorViewProps, ChangeEventPayload };\n"]}
@@ -5,39 +5,128 @@ import Pitchy
5
5
 
6
6
  public class ReactNativeSimpleNotePitchDetectorModule: Module {
7
7
 
8
+ // Configurable buffer size - can be changed from JS
9
+ // Larger buffer = better low frequency detection, more latency
10
+ // Smaller buffer = better high frequency detection, less latency
11
+ private var bufferSize: UInt32 = 8192
12
+ private var estimationStrategy: EstimationStrategy = .yin
13
+ private var _pitchEngine: PitchEngine?
14
+
15
+ private func sendStatus(_ level: String, _ message: String) {
16
+ self.sendEvent("onStatus", [
17
+ "level": level,
18
+ "message": message
19
+ ])
20
+ }
21
+
8
22
  public func definition() -> ModuleDefinition {
9
23
 
10
24
  Name("ReactNativeSimpleNotePitchDetector")
11
25
 
12
- Events("onChangeNote")
26
+ Events("onChangeNote", "onStatus")
13
27
 
14
28
  Function("start") {
15
- pitchEngine.start()
29
+ self.sendStatus("debug", "start() called with bufferSize=\(self.bufferSize), algorithm=\(self.estimationStrategy)")
30
+ self.pitchEngine.start()
31
+ self.sendStatus("debug", "Recording started successfully")
16
32
  }
17
33
 
18
34
  Function("stop") {
19
- pitchEngine.stop()
35
+ self.pitchEngine.stop()
20
36
  }
21
37
 
22
38
  Function("isRecording") {
23
- return pitchEngine.active
39
+ return self.pitchEngine.active
24
40
  }
25
41
 
26
42
  // Allow JS to configure the level threshold
27
43
  Function("setLevelThreshold") { (threshold: Double) in
28
44
  self.pitchEngine.levelThreshold = Float(threshold)
29
45
  }
46
+
47
+ // Allow JS to configure the buffer size
48
+ // Must be called before start() or after stop() to take effect
49
+ // Common values: 4096 (better for high frequencies), 8192 (balanced), 16384 (better for low frequencies)
50
+ Function("setBufferSize") { (size: Int) in
51
+ let wasActive = self._pitchEngine?.active ?? false
52
+ if wasActive {
53
+ self._pitchEngine?.stop()
54
+ }
55
+ self.bufferSize = UInt32(size)
56
+ self._pitchEngine = nil // Force recreation with new buffer size
57
+ if wasActive {
58
+ self.pitchEngine.start()
59
+ }
60
+ }
61
+
62
+ // Get current buffer size
63
+ Function("getBufferSize") {
64
+ return Int(self.bufferSize)
65
+ }
66
+
67
+ // Allow JS to configure the estimation algorithm
68
+ // iOS options: "yin", "hps", "barycentric", "quadradic", "jains", "quinnsFirst", "quinnsSecond", "maxValue"
69
+ // Must be called before start() or will restart the engine
70
+ Function("setAlgorithm") { (algorithm: String) in
71
+ let wasActive = self._pitchEngine?.active ?? false
72
+ if wasActive {
73
+ self._pitchEngine?.stop()
74
+ }
75
+
76
+ switch algorithm.lowercased() {
77
+ case "yin":
78
+ self.estimationStrategy = .yin
79
+ case "hps":
80
+ self.estimationStrategy = .hps
81
+ case "barycentric":
82
+ self.estimationStrategy = .barycentric
83
+ case "quadradic":
84
+ self.estimationStrategy = .quadradic
85
+ case "jains":
86
+ self.estimationStrategy = .jains
87
+ case "quinnsfirst":
88
+ self.estimationStrategy = .quinnsFirst
89
+ case "quinnssecond":
90
+ self.estimationStrategy = .quinnsSecond
91
+ case "maxvalue":
92
+ self.estimationStrategy = .maxValue
93
+ default:
94
+ // Default to YIN if unknown
95
+ self.estimationStrategy = .yin
96
+ }
97
+
98
+ self._pitchEngine = nil // Force recreation with new algorithm
99
+ if wasActive {
100
+ self.pitchEngine.start()
101
+ }
102
+ }
103
+
104
+ // Get current algorithm name
105
+ Function("getAlgorithm") {
106
+ switch self.estimationStrategy {
107
+ case .yin: return "yin"
108
+ case .hps: return "hps"
109
+ case .barycentric: return "barycentric"
110
+ case .quadradic: return "quadradic"
111
+ case .jains: return "jains"
112
+ case .quinnsFirst: return "quinnsFirst"
113
+ case .quinnsSecond: return "quinnsSecond"
114
+ case .maxValue: return "maxValue"
115
+ }
116
+ }
30
117
  }
31
118
 
32
- lazy var pitchEngine: PitchEngine = { [weak self] in
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)
36
- let pitchEngine = PitchEngine(config: config, delegate: self)
119
+ var pitchEngine: PitchEngine {
120
+ if let engine = _pitchEngine {
121
+ return engine
122
+ }
123
+ let config = Config(bufferSize: bufferSize, estimationStrategy: estimationStrategy)
124
+ let engine = PitchEngine(config: config, delegate: self)
37
125
  // Default threshold - can be adjusted from JS via setLevelThreshold()
38
- pitchEngine.levelThreshold = -30
39
- return pitchEngine
40
- }()
126
+ engine.levelThreshold = -30
127
+ _pitchEngine = engine
128
+ return engine
129
+ }
41
130
  }
42
131
 
43
132
  extension ReactNativeSimpleNotePitchDetectorModule: PitchEngineDelegate {
@@ -70,7 +159,7 @@ extension ReactNativeSimpleNotePitchDetectorModule: PitchEngineDelegate {
70
159
  }
71
160
 
72
161
  public func pitchEngine(_ pitchEngine: PitchEngine, didReceiveError error: Error) {
73
- // Optionally send error events to JS
162
+ self.sendStatus("error", "PitchEngine error: \(error.localizedDescription)")
74
163
  }
75
164
 
76
165
  public func pitchEngineWentBelowLevelThreshold(_ pitchEngine: PitchEngine) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-simple-note-pitch-detector",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
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
+ }