react-native-tuner-engine 1.0.0 → 1.1.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.
- package/android/src/main/cpp/TunerEngineJni.cpp +16 -0
- package/android/src/main/java/com/tunerengine/TunerEngineModule.kt +79 -47
- package/cpp/build/CMakeFiles/Makefile.cmake +0 -66
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/compiler_depend.internal +9707 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/compiler_depend.make +11357 -2
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o +0 -0
- package/cpp/build/libtuner_engine_core.a +0 -0
- package/cpp/include/AudioFrameDispatcher.hpp +17 -0
- package/cpp/include/NoteMapper.hpp +21 -0
- package/cpp/include/Pipeline.hpp +1 -0
- package/cpp/include/TunerEngine.hpp +1 -0
- package/cpp/src/AudioFrameDispatcher.cpp +43 -4
- package/cpp/src/NoteMapper.cpp +14 -2
- package/cpp/src/Pipeline.cpp +4 -0
- package/cpp/src/TunerEngine.cpp +4 -0
- package/cpp/tests/build/CMakeFiles/Makefile.cmake +0 -66
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/compiler_depend.internal +817 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/compiler_depend.make +2440 -2
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/compiler_depend.internal +827 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/compiler_depend.make +2470 -2
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/main.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_bench +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/compiler_depend.internal +9707 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/compiler_depend.make +11357 -2
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/libtuner_engine_core.a +0 -0
- package/cpp/tests/build/tuner_engine_tests +0 -0
- package/ios/TunerBridge.mm +6 -1
- package/lib/module/TunerEngine.js +74 -2
- package/lib/module/TunerEngine.js.map +1 -1
- package/lib/typescript/src/TunerEngine.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TunerEngine.ts +64 -2
|
Binary file
|
package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o
CHANGED
|
Binary file
|
package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o
CHANGED
|
Binary file
|
package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/ios/TunerBridge.mm
CHANGED
|
@@ -66,6 +66,9 @@
|
|
|
66
66
|
if (opts[@"onsetDetection"]) {
|
|
67
67
|
_dispatcher->setOnsetDetectionEnabled([opts[@"onsetDetection"] boolValue]);
|
|
68
68
|
}
|
|
69
|
+
if (opts[@"adaptiveFrameSize"] != nil) {
|
|
70
|
+
_dispatcher->setAdaptiveFrameSize([opts[@"adaptiveFrameSize"] boolValue]);
|
|
71
|
+
}
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
- (void)startWithCompletion:(void(^)(NSError* _Nullable error))completion {
|
|
@@ -124,7 +127,9 @@
|
|
|
124
127
|
if (_dispatcher) _dispatcher->setTuning(std::string([name UTF8String]));
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
- (void)setTemperament:(NSString *)name {
|
|
130
|
+
- (void)setTemperament:(NSString *)name {
|
|
131
|
+
if (_dispatcher) _dispatcher->setTemperament(std::string([name UTF8String]));
|
|
132
|
+
}
|
|
128
133
|
|
|
129
134
|
- (NSDictionary *)getStatus {
|
|
130
135
|
return @{
|
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { DeviceEventEmitter } from 'react-native';
|
|
3
|
+
import { Platform, DeviceEventEmitter } from 'react-native';
|
|
4
4
|
import NativeTunerEngine from "./NativeTunerEngine.js";
|
|
5
|
+
const QUALITY_PRESETS = {
|
|
6
|
+
'low-latency': {
|
|
7
|
+
frameSize: 1024,
|
|
8
|
+
overlapRatio: 0
|
|
9
|
+
},
|
|
10
|
+
'balanced': {
|
|
11
|
+
frameSize: 2048,
|
|
12
|
+
overlapRatio: 0.5
|
|
13
|
+
},
|
|
14
|
+
'high-accuracy': {
|
|
15
|
+
frameSize: 4096,
|
|
16
|
+
overlapRatio: 0.75
|
|
17
|
+
}
|
|
18
|
+
};
|
|
5
19
|
class TunerEngine {
|
|
6
20
|
configure(opts) {
|
|
7
|
-
|
|
21
|
+
const {
|
|
22
|
+
quality,
|
|
23
|
+
adaptiveFrameSize,
|
|
24
|
+
...rest
|
|
25
|
+
} = opts;
|
|
26
|
+
const resolved = {
|
|
27
|
+
...rest
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// quality preset overrides frameSize and overlapRatio
|
|
31
|
+
if (quality && QUALITY_PRESETS[quality]) {
|
|
32
|
+
const preset = QUALITY_PRESETS[quality];
|
|
33
|
+
resolved.frameSize = preset.frameSize;
|
|
34
|
+
resolved.overlapRatio = preset.overlapRatio;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If user explicitly provided frameSize or quality, disable adaptive frame sizing
|
|
38
|
+
// so setInstrument won't override the chosen frame size.
|
|
39
|
+
if (resolved.frameSize !== undefined || quality !== undefined) {
|
|
40
|
+
resolved.adaptiveFrameSize = false;
|
|
41
|
+
} else {
|
|
42
|
+
resolved.adaptiveFrameSize = adaptiveFrameSize !== false;
|
|
43
|
+
}
|
|
44
|
+
return NativeTunerEngine.configure(resolved);
|
|
8
45
|
}
|
|
9
46
|
start() {
|
|
10
47
|
return NativeTunerEngine.start();
|
|
@@ -31,6 +68,41 @@ class TunerEngine {
|
|
|
31
68
|
return NativeTunerEngine.getStatus();
|
|
32
69
|
}
|
|
33
70
|
onPitch(callback) {
|
|
71
|
+
if (Platform.OS === 'android') {
|
|
72
|
+
// Bridgeless mode on Android doesn't deliver RCTDeviceEventEmitter events.
|
|
73
|
+
// Poll getStatus() which includes latest pitch via requestAnimationFrame.
|
|
74
|
+
let lastSeq = -1;
|
|
75
|
+
let rafId;
|
|
76
|
+
let stopped = false;
|
|
77
|
+
const poll = () => {
|
|
78
|
+
if (stopped) return;
|
|
79
|
+
try {
|
|
80
|
+
const s = NativeTunerEngine.getStatus();
|
|
81
|
+
if (s.seq !== lastSeq) {
|
|
82
|
+
lastSeq = s.seq;
|
|
83
|
+
callback({
|
|
84
|
+
hasPitch: s.hasPitch,
|
|
85
|
+
frequency: s.frequency,
|
|
86
|
+
confidence: s.confidence,
|
|
87
|
+
rmsDb: s.rmsDb,
|
|
88
|
+
noteName: s.noteName,
|
|
89
|
+
octave: s.octave,
|
|
90
|
+
cents: s.cents,
|
|
91
|
+
nearestString: s.nearestString,
|
|
92
|
+
stringDeviation: s.stringDeviation
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} catch (_) {}
|
|
96
|
+
rafId = requestAnimationFrame(poll);
|
|
97
|
+
};
|
|
98
|
+
rafId = requestAnimationFrame(poll);
|
|
99
|
+
return () => {
|
|
100
|
+
stopped = true;
|
|
101
|
+
cancelAnimationFrame(rafId);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// iOS: JSI direct callback via global + DeviceEventEmitter fallback
|
|
34
106
|
globalThis.__tunerEngineOnPitch = callback;
|
|
35
107
|
const sub = DeviceEventEmitter.addListener('onPitch', callback);
|
|
36
108
|
return () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["DeviceEventEmitter","NativeTunerEngine","TunerEngine","configure","opts","start","stop","requestPermission","setA4","hz","setInstrument","name","setTemperament","setTuning","getStatus","onPitch","callback","globalThis","__tunerEngineOnPitch","sub","addListener","
|
|
1
|
+
{"version":3,"names":["Platform","DeviceEventEmitter","NativeTunerEngine","QUALITY_PRESETS","frameSize","overlapRatio","TunerEngine","configure","opts","quality","adaptiveFrameSize","rest","resolved","preset","undefined","start","stop","requestPermission","setA4","hz","setInstrument","name","setTemperament","setTuning","getStatus","onPitch","callback","OS","lastSeq","rafId","stopped","poll","s","seq","hasPitch","frequency","confidence","rmsDb","noteName","octave","cents","nearestString","stringDeviation","_","requestAnimationFrame","cancelAnimationFrame","globalThis","__tunerEngineOnPitch","sub","addListener","remove"],"sourceRoot":"../../src","sources":["TunerEngine.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunerEngine.d.ts","sourceRoot":"","sources":["../../../src/TunerEngine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,UAAU,
|
|
1
|
+
{"version":3,"file":"TunerEngine.d.ts","sourceRoot":"","sources":["../../../src/TunerEngine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,UAAU,EAEV,WAAW,EACX,WAAW,EACX,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;AACjD,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAQ9B,cAAM,WAAW;IACf,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIrC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIvB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAIrC,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAIvC,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,EAAE,GAAG,IAAI;IAIxC,SAAS,IAAI,YAAY;IAIzB,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW;CA6C9C;;AAED,wBAAiC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-tuner-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A high-performance React Native Turbo Module for real-time instrument tuning with native audio processing and pitch detection.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"source": "./src/index.tsx",
|
package/src/TunerEngine.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { DeviceEventEmitter } from 'react-native';
|
|
1
|
+
import { Platform, DeviceEventEmitter } from 'react-native';
|
|
2
2
|
import NativeTunerEngine from './NativeTunerEngine';
|
|
3
3
|
import type {
|
|
4
4
|
EngineStatus,
|
|
5
5
|
Instrument,
|
|
6
6
|
PitchEvent,
|
|
7
|
+
QualityPreset,
|
|
7
8
|
Temperament,
|
|
8
9
|
TunerConfig,
|
|
9
10
|
TuningPreset,
|
|
@@ -12,9 +13,33 @@ import type {
|
|
|
12
13
|
type PitchCallback = (event: PitchEvent) => void;
|
|
13
14
|
type Unsubscribe = () => void;
|
|
14
15
|
|
|
16
|
+
const QUALITY_PRESETS: Record<QualityPreset, { frameSize: number; overlapRatio: number }> = {
|
|
17
|
+
'low-latency': { frameSize: 1024, overlapRatio: 0 },
|
|
18
|
+
'balanced': { frameSize: 2048, overlapRatio: 0.5 },
|
|
19
|
+
'high-accuracy': { frameSize: 4096, overlapRatio: 0.75 },
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
class TunerEngine {
|
|
16
23
|
configure(opts: TunerConfig): Promise<void> {
|
|
17
|
-
|
|
24
|
+
const { quality, adaptiveFrameSize, ...rest } = opts;
|
|
25
|
+
const resolved = { ...rest };
|
|
26
|
+
|
|
27
|
+
// quality preset overrides frameSize and overlapRatio
|
|
28
|
+
if (quality && QUALITY_PRESETS[quality]) {
|
|
29
|
+
const preset = QUALITY_PRESETS[quality];
|
|
30
|
+
resolved.frameSize = preset.frameSize;
|
|
31
|
+
resolved.overlapRatio = preset.overlapRatio;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// If user explicitly provided frameSize or quality, disable adaptive frame sizing
|
|
35
|
+
// so setInstrument won't override the chosen frame size.
|
|
36
|
+
if (resolved.frameSize !== undefined || quality !== undefined) {
|
|
37
|
+
(resolved as any).adaptiveFrameSize = false;
|
|
38
|
+
} else {
|
|
39
|
+
(resolved as any).adaptiveFrameSize = adaptiveFrameSize !== false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return NativeTunerEngine.configure(resolved);
|
|
18
43
|
}
|
|
19
44
|
|
|
20
45
|
start(): Promise<void> {
|
|
@@ -50,6 +75,43 @@ class TunerEngine {
|
|
|
50
75
|
}
|
|
51
76
|
|
|
52
77
|
onPitch(callback: PitchCallback): Unsubscribe {
|
|
78
|
+
if (Platform.OS === 'android') {
|
|
79
|
+
// Bridgeless mode on Android doesn't deliver RCTDeviceEventEmitter events.
|
|
80
|
+
// Poll getStatus() which includes latest pitch via requestAnimationFrame.
|
|
81
|
+
let lastSeq = -1;
|
|
82
|
+
let rafId: number;
|
|
83
|
+
let stopped = false;
|
|
84
|
+
|
|
85
|
+
const poll = () => {
|
|
86
|
+
if (stopped) return;
|
|
87
|
+
try {
|
|
88
|
+
const s = NativeTunerEngine.getStatus() as any;
|
|
89
|
+
if (s.seq !== lastSeq) {
|
|
90
|
+
lastSeq = s.seq;
|
|
91
|
+
callback({
|
|
92
|
+
hasPitch: s.hasPitch,
|
|
93
|
+
frequency: s.frequency,
|
|
94
|
+
confidence: s.confidence,
|
|
95
|
+
rmsDb: s.rmsDb,
|
|
96
|
+
noteName: s.noteName,
|
|
97
|
+
octave: s.octave,
|
|
98
|
+
cents: s.cents,
|
|
99
|
+
nearestString: s.nearestString,
|
|
100
|
+
stringDeviation: s.stringDeviation,
|
|
101
|
+
} as PitchEvent);
|
|
102
|
+
}
|
|
103
|
+
} catch (_) {}
|
|
104
|
+
rafId = requestAnimationFrame(poll);
|
|
105
|
+
};
|
|
106
|
+
rafId = requestAnimationFrame(poll);
|
|
107
|
+
|
|
108
|
+
return () => {
|
|
109
|
+
stopped = true;
|
|
110
|
+
cancelAnimationFrame(rafId);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// iOS: JSI direct callback via global + DeviceEventEmitter fallback
|
|
53
115
|
(globalThis as any).__tunerEngineOnPitch = callback;
|
|
54
116
|
const sub = DeviceEventEmitter.addListener('onPitch', callback);
|
|
55
117
|
return () => {
|