tuner-core 2026.4.2

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/detectors/autocorrelation.d.ts +19 -0
  4. package/dist/detectors/autocorrelation.d.ts.map +1 -0
  5. package/dist/detectors/autocorrelation.js +60 -0
  6. package/dist/detectors/autocorrelation.js.map +1 -0
  7. package/dist/detectors/create-pitch-detector.d.ts +4 -0
  8. package/dist/detectors/create-pitch-detector.d.ts.map +1 -0
  9. package/dist/detectors/create-pitch-detector.js +39 -0
  10. package/dist/detectors/create-pitch-detector.js.map +1 -0
  11. package/dist/detectors/mpm.d.ts +21 -0
  12. package/dist/detectors/mpm.d.ts.map +1 -0
  13. package/dist/detectors/mpm.js +95 -0
  14. package/dist/detectors/mpm.js.map +1 -0
  15. package/dist/detectors/pyin.d.ts +49 -0
  16. package/dist/detectors/pyin.d.ts.map +1 -0
  17. package/dist/detectors/pyin.js +205 -0
  18. package/dist/detectors/pyin.js.map +1 -0
  19. package/dist/detectors/yin-cmnd.d.ts +13 -0
  20. package/dist/detectors/yin-cmnd.d.ts.map +1 -0
  21. package/dist/detectors/yin-cmnd.js +53 -0
  22. package/dist/detectors/yin-cmnd.js.map +1 -0
  23. package/dist/detectors/yin.d.ts +20 -0
  24. package/dist/detectors/yin.d.ts.map +1 -0
  25. package/dist/detectors/yin.js +66 -0
  26. package/dist/detectors/yin.js.map +1 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +16 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/notes.d.ts +25 -0
  32. package/dist/notes.d.ts.map +1 -0
  33. package/dist/notes.js +56 -0
  34. package/dist/notes.js.map +1 -0
  35. package/dist/preferences.d.ts +8 -0
  36. package/dist/preferences.d.ts.map +1 -0
  37. package/dist/preferences.js +7 -0
  38. package/dist/preferences.js.map +1 -0
  39. package/dist/session.d.ts +25 -0
  40. package/dist/session.d.ts.map +1 -0
  41. package/dist/session.js +131 -0
  42. package/dist/session.js.map +1 -0
  43. package/dist/tuner-settings.d.ts +58 -0
  44. package/dist/tuner-settings.d.ts.map +1 -0
  45. package/dist/tuner-settings.js +69 -0
  46. package/dist/tuner-settings.js.map +1 -0
  47. package/dist/tunings.d.ts +5 -0
  48. package/dist/tunings.d.ts.map +1 -0
  49. package/dist/tunings.js +231 -0
  50. package/dist/tunings.js.map +1 -0
  51. package/dist/types.d.ts +68 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/utils/median.d.ts +3 -0
  56. package/dist/utils/median.d.ts.map +1 -0
  57. package/dist/utils/median.js +15 -0
  58. package/dist/utils/median.js.map +1 -0
  59. package/package.json +69 -0
@@ -0,0 +1,131 @@
1
+ import { frequencyToMidi, getCents, getCentsFromTarget, midiToNote, midiToOctave, } from './notes.js';
2
+ import { mergeTunerSettings } from './tuner-settings.js';
3
+ import { median } from './utils/median.js';
4
+ const DEFAULT_CENTS_THRESHOLD = 5;
5
+ export class TunerSession {
6
+ audio;
7
+ detector;
8
+ settings;
9
+ activeTuning = null;
10
+ centsThreshold = DEFAULT_CENTS_THRESHOLD;
11
+ running = false;
12
+ freqWindow = [];
13
+ lastHeldHz = null;
14
+ listeners = {
15
+ result: new Set(),
16
+ started: new Set(),
17
+ stopped: new Set(),
18
+ error: new Set(),
19
+ };
20
+ constructor(audio, detector, settings) {
21
+ this.audio = audio;
22
+ this.detector = detector;
23
+ this.settings = mergeTunerSettings(settings);
24
+ }
25
+ // ─── Configuration ──────────────────────────────────────────────────────────
26
+ setTuning(tuning) {
27
+ this.activeTuning = tuning;
28
+ }
29
+ applyPreferences(prefs) {
30
+ if (prefs.centsThreshold !== undefined) {
31
+ this.centsThreshold = prefs.centsThreshold;
32
+ }
33
+ }
34
+ // ─── Lifecycle ───────────────────────────────────────────────────────────────
35
+ async start() {
36
+ if (this.running)
37
+ return;
38
+ try {
39
+ this.audio.onFrame((samples) => this.processFrame(samples));
40
+ await this.audio.start();
41
+ this.running = true;
42
+ this.emit('started', undefined);
43
+ }
44
+ catch (err) {
45
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
46
+ }
47
+ }
48
+ stop() {
49
+ if (!this.running)
50
+ return;
51
+ this.audio.stop();
52
+ this.running = false;
53
+ this.freqWindow = [];
54
+ this.lastHeldHz = null;
55
+ this.detector.reset?.();
56
+ this.emit('stopped', undefined);
57
+ }
58
+ get isRunning() {
59
+ return this.running;
60
+ }
61
+ // ─── Events ──────────────────────────────────────────────────────────────────
62
+ on(event, listener) {
63
+ ;
64
+ this.listeners[event].add(listener);
65
+ }
66
+ off(event, listener) {
67
+ ;
68
+ this.listeners[event].delete(listener);
69
+ }
70
+ emit(event, payload) {
71
+ for (const listener of this.listeners[event]) {
72
+ ;
73
+ listener(payload);
74
+ }
75
+ }
76
+ // ─── Processing ──────────────────────────────────────────────────────────────
77
+ processFrame(samples) {
78
+ const sampleRate = this.audio.getSampleRate();
79
+ const { frequency: rawHz, confidence } = this.detector.detect(samples, sampleRate);
80
+ if (rawHz === null) {
81
+ this.freqWindow = [];
82
+ return;
83
+ }
84
+ const w = this.settings.medianWindowSize;
85
+ const windowSize = w < 1 ? 1 : w;
86
+ this.freqWindow.push(rawHz);
87
+ while (this.freqWindow.length > windowSize) {
88
+ this.freqWindow.shift();
89
+ }
90
+ const medianHz = median(this.freqWindow);
91
+ let displayHz;
92
+ if (confidence >= this.settings.minConfidence) {
93
+ this.lastHeldHz = medianHz;
94
+ displayHz = medianHz;
95
+ }
96
+ else {
97
+ displayHz = this.lastHeldHz ?? medianHz;
98
+ }
99
+ const midi = frequencyToMidi(displayHz);
100
+ const result = {
101
+ frequency: displayHz,
102
+ note: midiToNote(midi),
103
+ octave: midiToOctave(midi),
104
+ cents: getCents(displayHz),
105
+ closestString: this.findClosestString(displayHz),
106
+ tuningStrings: this.activeTuning ? [...this.activeTuning.strings] : null,
107
+ };
108
+ this.emit('result', result);
109
+ }
110
+ findClosestString(frequency) {
111
+ if (!this.activeTuning)
112
+ return null;
113
+ let closest = null;
114
+ let minAbsCents = Number.POSITIVE_INFINITY;
115
+ for (const string of this.activeTuning.strings) {
116
+ const centsOff = getCentsFromTarget(frequency, string.frequency);
117
+ const absCents = Math.abs(centsOff);
118
+ if (absCents < minAbsCents) {
119
+ minAbsCents = absCents;
120
+ closest = {
121
+ name: string.name,
122
+ targetFrequency: string.frequency,
123
+ centsOff,
124
+ inTune: absCents <= this.centsThreshold,
125
+ };
126
+ }
127
+ }
128
+ return closest;
129
+ }
130
+ }
131
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Engine / pipeline knobs: audio frame size, pitch detector choice, frequency smoothing
3
+ * (`medianWindowSize`), confidence gating, and per-detector parameters (bands, thresholds,
4
+ * PYIN HMM). These affect latency vs stability and how `TunerSession` interprets audio — not
5
+ * persisted “user preferences” like last selected instrument (see `UserPreferences` in
6
+ * `types.ts` and `DEFAULT_PREFERENCES` in `preferences.ts`).
7
+ *
8
+ * Tune defaults here when experimenting globally; apps can merge overrides via
9
+ * `mergeTunerSettings` / `applySettingsPatch`.
10
+ */
11
+ export type PitchDetectorKind = 'mpm' | 'pyin' | 'yin' | 'autocorrelation';
12
+ /** HMM / observation shaping for {@link PyinDetector} */
13
+ export interface PyinHMMSettings {
14
+ yinSharpness: number;
15
+ transitionSigmaSemitones: number;
16
+ maxJumpSemitones: number;
17
+ logPitchToUv: number;
18
+ logUvToPitch: number;
19
+ logUvToUv: number;
20
+ /**
21
+ * Penalty (per semitone²) tying observations to the first YIN CMND dip
22
+ * (same idea as classic YIN’s “first τ below threshold”) to avoid subharmonics.
23
+ */
24
+ firstDipSemitoneWeight: number;
25
+ /** CMND level used to locate that first dip for the hint */
26
+ firstDipThreshold: number;
27
+ }
28
+ export interface DetectorSettings {
29
+ minFrequency: number;
30
+ maxFrequency: number;
31
+ /** Autocorrelation: minimum normalized clarity */
32
+ clarityThreshold: number;
33
+ /** YIN: cumulative mean normalized difference gate (typ. 0.10–0.20) */
34
+ yinThreshold: number;
35
+ /** MPM: minimum NSDF peak height before accepting a period (typ. 0.85–0.95) */
36
+ mpmPeakThreshold: number;
37
+ pyin: PyinHMMSettings;
38
+ }
39
+ export interface TunerSettings {
40
+ /**
41
+ * Samples per analysis frame (web/Capacitor capture buffer).
42
+ * Larger → better low-string resolution, more latency (~ frame / sampleRate).
43
+ */
44
+ audioFrameSamples: number;
45
+ /** How many recent raw frequencies feed the median filter; 1 disables smoothing */
46
+ medianWindowSize: number;
47
+ /**
48
+ * 0…1 from the detector; below this the session keeps emitting the last confident pitch.
49
+ */
50
+ minConfidence: number;
51
+ pitchDetector: PitchDetectorKind;
52
+ detector: DetectorSettings;
53
+ }
54
+ export declare const DEFAULT_TUNER_SETTINGS: TunerSettings;
55
+ export declare function mergeTunerSettings(overrides?: Partial<TunerSettings>): TunerSettings;
56
+ /** Merge a partial update into the current settings (preserves unspecified nested keys). */
57
+ export declare function applySettingsPatch(current: TunerSettings, patch: Partial<TunerSettings>): TunerSettings;
58
+ //# sourceMappingURL=tuner-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tuner-settings.d.ts","sourceRoot":"","sources":["../src/tuner-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,iBAAiB,CAAA;AAE1E,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,wBAAwB,EAAE,MAAM,CAAA;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,sBAAsB,EAAE,MAAM,CAAA;IAC9B,4DAA4D;IAC5D,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,gBAAgB,EAAE,MAAM,CAAA;IACxB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,CAAA;IACxB,IAAI,EAAE,eAAe,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,gBAAgB,EAAE,MAAM,CAAA;IACxB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,iBAAiB,CAAA;IAChC,QAAQ,EAAE,gBAAgB,CAAA;CAC3B;AAcD,eAAO,MAAM,sBAAsB,EAAE,aAapC,CAAA;AAED,wBAAgB,kBAAkB,CAChC,SAAS,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GACjC,aAAa,CAgBf;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,aAAa,CAcf"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Engine / pipeline knobs: audio frame size, pitch detector choice, frequency smoothing
3
+ * (`medianWindowSize`), confidence gating, and per-detector parameters (bands, thresholds,
4
+ * PYIN HMM). These affect latency vs stability and how `TunerSession` interprets audio — not
5
+ * persisted “user preferences” like last selected instrument (see `UserPreferences` in
6
+ * `types.ts` and `DEFAULT_PREFERENCES` in `preferences.ts`).
7
+ *
8
+ * Tune defaults here when experimenting globally; apps can merge overrides via
9
+ * `mergeTunerSettings` / `applySettingsPatch`.
10
+ */
11
+ /** Defaults tuned for monophonic instrument tuning (stable needle, few false subharmonics). */
12
+ const DEFAULT_PYIN_HMM = {
13
+ yinSharpness: 12,
14
+ transitionSigmaSemitones: 1.35,
15
+ maxJumpSemitones: 10,
16
+ logPitchToUv: -3.2,
17
+ logUvToPitch: -2.8,
18
+ logUvToUv: -0.15,
19
+ firstDipSemitoneWeight: 0.14,
20
+ firstDipThreshold: 0.18,
21
+ };
22
+ export const DEFAULT_TUNER_SETTINGS = {
23
+ audioFrameSamples: 4096,
24
+ medianWindowSize: 7,
25
+ minConfidence: 0.28,
26
+ pitchDetector: 'yin',
27
+ detector: {
28
+ minFrequency: 60,
29
+ maxFrequency: 1400,
30
+ clarityThreshold: 0.2,
31
+ yinThreshold: 0.15,
32
+ mpmPeakThreshold: 0.88,
33
+ pyin: { ...DEFAULT_PYIN_HMM },
34
+ },
35
+ };
36
+ export function mergeTunerSettings(overrides) {
37
+ const base = DEFAULT_TUNER_SETTINGS;
38
+ const det = overrides?.detector;
39
+ const py = det?.pyin;
40
+ return {
41
+ ...base,
42
+ ...overrides,
43
+ detector: {
44
+ ...base.detector,
45
+ ...det,
46
+ pyin: {
47
+ ...base.detector.pyin,
48
+ ...py,
49
+ },
50
+ },
51
+ };
52
+ }
53
+ /** Merge a partial update into the current settings (preserves unspecified nested keys). */
54
+ export function applySettingsPatch(current, patch) {
55
+ const det = patch.detector;
56
+ return {
57
+ ...current,
58
+ ...patch,
59
+ detector: {
60
+ ...current.detector,
61
+ ...det,
62
+ pyin: {
63
+ ...current.detector.pyin,
64
+ ...(det?.pyin ?? {}),
65
+ },
66
+ },
67
+ };
68
+ }
69
+ //# sourceMappingURL=tuner-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tuner-settings.js","sourceRoot":"","sources":["../src/tuner-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiDH,+FAA+F;AAC/F,MAAM,gBAAgB,GAAoB;IACxC,YAAY,EAAE,EAAE;IAChB,wBAAwB,EAAE,IAAI;IAC9B,gBAAgB,EAAE,EAAE;IACpB,YAAY,EAAE,CAAC,GAAG;IAClB,YAAY,EAAE,CAAC,GAAG;IAClB,SAAS,EAAE,CAAC,IAAI;IAChB,sBAAsB,EAAE,IAAI;IAC5B,iBAAiB,EAAE,IAAI;CACxB,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,iBAAiB,EAAE,IAAI;IACvB,gBAAgB,EAAE,CAAC;IACnB,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,KAAK;IACpB,QAAQ,EAAE;QACR,YAAY,EAAE,EAAE;QAChB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,GAAG;QACrB,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,IAAI;QACtB,IAAI,EAAE,EAAE,GAAG,gBAAgB,EAAE;KAC9B;CACF,CAAA;AAED,MAAM,UAAU,kBAAkB,CAChC,SAAkC;IAElC,MAAM,IAAI,GAAG,sBAAsB,CAAA;IACnC,MAAM,GAAG,GAAG,SAAS,EAAE,QAAQ,CAAA;IAC/B,MAAM,EAAE,GAAG,GAAG,EAAE,IAAI,CAAA;IACpB,OAAO;QACL,GAAG,IAAI;QACP,GAAG,SAAS;QACZ,QAAQ,EAAE;YACR,GAAG,IAAI,CAAC,QAAQ;YAChB,GAAG,GAAG;YACN,IAAI,EAAE;gBACJ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI;gBACrB,GAAG,EAAE;aACN;SACF;KACF,CAAA;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,kBAAkB,CAChC,OAAsB,EACtB,KAA6B;IAE7B,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAA;IAC1B,OAAO;QACL,GAAG,OAAO;QACV,GAAG,KAAK;QACR,QAAQ,EAAE;YACR,GAAG,OAAO,CAAC,QAAQ;YACnB,GAAG,GAAG;YACN,IAAI,EAAE;gBACJ,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI;gBACxB,GAAG,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC;aACrB;SACF;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Instrument } from './types.js';
2
+ export declare const INSTRUMENTS: Instrument[];
3
+ export declare function findInstrument(id: string): Instrument | null;
4
+ export declare function findTuning(instrumentId: string, tuningId: string): import("./types.js").Tuning | null;
5
+ //# sourceMappingURL=tunings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunings.d.ts","sourceRoot":"","sources":["../src/tunings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,eAAO,MAAM,WAAW,EAAE,UAAU,EA+NnC,CAAA;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAE5D;AAED,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,sCAIhE"}
@@ -0,0 +1,231 @@
1
+ export const INSTRUMENTS = [
2
+ {
3
+ id: 'guitar',
4
+ name: 'Guitar',
5
+ tunings: [
6
+ {
7
+ id: 'guitar-standard',
8
+ name: 'Standard (EADGBe)',
9
+ strings: [
10
+ { name: 'E2', frequency: 82.41 },
11
+ { name: 'A2', frequency: 110.0 },
12
+ { name: 'D3', frequency: 146.83 },
13
+ { name: 'G3', frequency: 196.0 },
14
+ { name: 'B3', frequency: 246.94 },
15
+ { name: 'E4', frequency: 329.63 },
16
+ ],
17
+ },
18
+ {
19
+ id: 'guitar-drop-d',
20
+ name: 'Drop D (DADGBe)',
21
+ strings: [
22
+ { name: 'D2', frequency: 73.42 },
23
+ { name: 'A2', frequency: 110.0 },
24
+ { name: 'D3', frequency: 146.83 },
25
+ { name: 'G3', frequency: 196.0 },
26
+ { name: 'B3', frequency: 246.94 },
27
+ { name: 'E4', frequency: 329.63 },
28
+ ],
29
+ },
30
+ {
31
+ id: 'guitar-drop-c',
32
+ name: 'Drop C (CGCFAd)',
33
+ strings: [
34
+ { name: 'C2', frequency: 65.41 },
35
+ { name: 'G2', frequency: 98.0 },
36
+ { name: 'C3', frequency: 130.81 },
37
+ { name: 'F3', frequency: 174.61 },
38
+ { name: 'A3', frequency: 220.0 },
39
+ { name: 'D4', frequency: 293.66 },
40
+ ],
41
+ },
42
+ {
43
+ id: 'guitar-half-step-down',
44
+ name: 'Half Step Down (Eb)',
45
+ strings: [
46
+ { name: 'Eb2', frequency: 77.78 },
47
+ { name: 'Ab2', frequency: 103.83 },
48
+ { name: 'Db3', frequency: 138.59 },
49
+ { name: 'Gb3', frequency: 185.0 },
50
+ { name: 'Bb3', frequency: 233.08 },
51
+ { name: 'Eb4', frequency: 311.13 },
52
+ ],
53
+ },
54
+ {
55
+ id: 'guitar-open-g',
56
+ name: 'Open G (DGDGBd)',
57
+ strings: [
58
+ { name: 'D2', frequency: 73.42 },
59
+ { name: 'G2', frequency: 98.0 },
60
+ { name: 'D3', frequency: 146.83 },
61
+ { name: 'G3', frequency: 196.0 },
62
+ { name: 'B3', frequency: 246.94 },
63
+ { name: 'D4', frequency: 293.66 },
64
+ ],
65
+ },
66
+ {
67
+ id: 'guitar-open-d',
68
+ name: 'Open D (DADf#ad)',
69
+ strings: [
70
+ { name: 'D2', frequency: 73.42 },
71
+ { name: 'A2', frequency: 110.0 },
72
+ { name: 'D3', frequency: 146.83 },
73
+ { name: 'F#3', frequency: 185.0 },
74
+ { name: 'A3', frequency: 220.0 },
75
+ { name: 'D4', frequency: 293.66 },
76
+ ],
77
+ },
78
+ {
79
+ id: 'guitar-dadgad',
80
+ name: 'DADGAD',
81
+ strings: [
82
+ { name: 'D2', frequency: 73.42 },
83
+ { name: 'A2', frequency: 110.0 },
84
+ { name: 'D3', frequency: 146.83 },
85
+ { name: 'G3', frequency: 196.0 },
86
+ { name: 'A3', frequency: 220.0 },
87
+ { name: 'D4', frequency: 293.66 },
88
+ ],
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ id: 'bass',
94
+ name: 'Bass',
95
+ tunings: [
96
+ {
97
+ id: 'bass-standard-4',
98
+ name: 'Standard 4-string (EADg)',
99
+ strings: [
100
+ { name: 'E1', frequency: 41.2 },
101
+ { name: 'A1', frequency: 55.0 },
102
+ { name: 'D2', frequency: 73.42 },
103
+ { name: 'G2', frequency: 98.0 },
104
+ ],
105
+ },
106
+ {
107
+ id: 'bass-standard-5',
108
+ name: 'Standard 5-string (BEADg)',
109
+ strings: [
110
+ { name: 'B0', frequency: 30.87 },
111
+ { name: 'E1', frequency: 41.2 },
112
+ { name: 'A1', frequency: 55.0 },
113
+ { name: 'D2', frequency: 73.42 },
114
+ { name: 'G2', frequency: 98.0 },
115
+ ],
116
+ },
117
+ {
118
+ id: 'bass-drop-d',
119
+ name: 'Drop D (DADg)',
120
+ strings: [
121
+ { name: 'D1', frequency: 36.71 },
122
+ { name: 'A1', frequency: 55.0 },
123
+ { name: 'D2', frequency: 73.42 },
124
+ { name: 'G2', frequency: 98.0 },
125
+ ],
126
+ },
127
+ ],
128
+ },
129
+ {
130
+ id: 'ukulele',
131
+ name: 'Ukulele',
132
+ tunings: [
133
+ {
134
+ id: 'ukulele-standard',
135
+ name: 'Standard (GCEA)',
136
+ strings: [
137
+ { name: 'G4', frequency: 392.0 },
138
+ { name: 'C4', frequency: 261.63 },
139
+ { name: 'E4', frequency: 329.63 },
140
+ { name: 'A4', frequency: 440.0 },
141
+ ],
142
+ },
143
+ {
144
+ id: 'ukulele-low-g',
145
+ name: 'Low G (GCEA)',
146
+ strings: [
147
+ { name: 'G3', frequency: 196.0 },
148
+ { name: 'C4', frequency: 261.63 },
149
+ { name: 'E4', frequency: 329.63 },
150
+ { name: 'A4', frequency: 440.0 },
151
+ ],
152
+ },
153
+ {
154
+ id: 'ukulele-baritone',
155
+ name: 'Baritone (DGBE)',
156
+ strings: [
157
+ { name: 'D3', frequency: 146.83 },
158
+ { name: 'G3', frequency: 196.0 },
159
+ { name: 'B3', frequency: 246.94 },
160
+ { name: 'E4', frequency: 329.63 },
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ {
166
+ id: 'violin',
167
+ name: 'Violin',
168
+ tunings: [
169
+ {
170
+ id: 'violin-standard',
171
+ name: 'Standard (GDAE)',
172
+ strings: [
173
+ { name: 'G3', frequency: 196.0 },
174
+ { name: 'D4', frequency: 293.66 },
175
+ { name: 'A4', frequency: 440.0 },
176
+ { name: 'E5', frequency: 659.25 },
177
+ ],
178
+ },
179
+ ],
180
+ },
181
+ {
182
+ id: 'mandolin',
183
+ name: 'Mandolin',
184
+ tunings: [
185
+ {
186
+ id: 'mandolin-standard',
187
+ name: 'Standard (GDAE)',
188
+ strings: [
189
+ { name: 'G3', frequency: 196.0 },
190
+ { name: 'D4', frequency: 293.66 },
191
+ { name: 'A4', frequency: 440.0 },
192
+ { name: 'E5', frequency: 659.25 },
193
+ ],
194
+ },
195
+ ],
196
+ },
197
+ {
198
+ id: 'banjo',
199
+ name: 'Banjo',
200
+ tunings: [
201
+ {
202
+ id: 'banjo-standard-5',
203
+ name: 'Standard 5-string (gDGBD)',
204
+ strings: [
205
+ { name: 'G4', frequency: 392.0 },
206
+ { name: 'D3', frequency: 146.83 },
207
+ { name: 'G3', frequency: 196.0 },
208
+ { name: 'B3', frequency: 246.94 },
209
+ { name: 'D4', frequency: 293.66 },
210
+ ],
211
+ },
212
+ {
213
+ id: 'banjo-tenor',
214
+ name: 'Tenor (CGDA)',
215
+ strings: [
216
+ { name: 'C3', frequency: 130.81 },
217
+ { name: 'G3', frequency: 196.0 },
218
+ { name: 'D4', frequency: 293.66 },
219
+ { name: 'A4', frequency: 440.0 },
220
+ ],
221
+ },
222
+ ],
223
+ },
224
+ ];
225
+ export function findInstrument(id) {
226
+ return INSTRUMENTS.find((i) => i.id === id) ?? null;
227
+ }
228
+ export function findTuning(instrumentId, tuningId) {
229
+ return (findInstrument(instrumentId)?.tunings.find((t) => t.id === tuningId) ?? null);
230
+ }
231
+ //# sourceMappingURL=tunings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunings.js","sourceRoot":"","sources":["../src/tunings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ export interface AudioProvider {
2
+ getSampleRate(): number;
3
+ onFrame(callback: (samples: Float32Array) => void): void;
4
+ start(): Promise<void>;
5
+ stop(): void;
6
+ }
7
+ /** Optional tactile feedback; core may call through a host-provided instance. */
8
+ export interface HapticsProvider {
9
+ inTune(): void;
10
+ close(): void;
11
+ }
12
+ /** Detector output; `confidence` is 0…1 (0 when no pitch). */
13
+ export interface PitchDetection {
14
+ frequency: number | null;
15
+ confidence: number;
16
+ }
17
+ export interface PitchDetector {
18
+ detect(samples: Float32Array, sampleRate: number): PitchDetection;
19
+ /** Clear temporal state (e.g. PYIN HMM). Optional. */
20
+ reset?(): void;
21
+ }
22
+ export interface StringDef {
23
+ name: string;
24
+ frequency: number;
25
+ }
26
+ export interface Tuning {
27
+ id: string;
28
+ name: string;
29
+ strings: StringDef[];
30
+ }
31
+ export interface Instrument {
32
+ id: string;
33
+ name: string;
34
+ tunings: Tuning[];
35
+ }
36
+ export interface ClosestString {
37
+ name: string;
38
+ targetFrequency: number;
39
+ centsOff: number;
40
+ inTune: boolean;
41
+ }
42
+ export interface TunerResult {
43
+ frequency: number;
44
+ note: string;
45
+ octave: number;
46
+ cents: number;
47
+ closestString: ClosestString | null;
48
+ /** Active tuning courses for UI; null when no tuning is selected. */
49
+ tuningStrings: StringDef[] | null;
50
+ }
51
+ export interface UserPreferences {
52
+ centsThreshold: number;
53
+ customTunings: Tuning[];
54
+ lastInstrumentId: string | null;
55
+ lastTuningId: string | null;
56
+ }
57
+ export interface PreferencesProvider {
58
+ load(): Promise<UserPreferences>;
59
+ save(prefs: UserPreferences): Promise<void>;
60
+ }
61
+ export type TunerEventMap = {
62
+ result: TunerResult;
63
+ started: undefined;
64
+ stopped: undefined;
65
+ error: Error;
66
+ };
67
+ export type TunerEventListener<K extends keyof TunerEventMap> = (payload: TunerEventMap[K]) => void;
68
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,aAAa,IAAI,MAAM,CAAA;IACvB,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAA;IACxD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,IAAI,CAAA;CACb;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,MAAM,IAAI,IAAI,CAAA;IACd,KAAK,IAAI,IAAI,CAAA;CACd;AAID,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAAA;IACjE,sDAAsD;IACtD,KAAK,CAAC,IAAI,IAAI,CAAA;CACf;AAID,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,aAAa,GAAG,IAAI,CAAA;IACnC,qEAAqE;IACrE,aAAa,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;CAClC;AAID,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC,CAAA;IAChC,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5C;AAID,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,SAAS,CAAA;IAClB,KAAK,EAAE,KAAK,CAAA;CACb,CAAA;AAED,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,aAAa,IAAI,CAC9D,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,KACtB,IAAI,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // ─── Host / platform (injected by app — web, Capacitor, CLI, tests) ─────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+EAA+E"}
@@ -0,0 +1,3 @@
1
+ /** Median of a non-empty list (copies and sorts). */
2
+ export declare function median(values: readonly number[]): number;
3
+ //# sourceMappingURL=median.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"median.d.ts","sourceRoot":"","sources":["../../src/utils/median.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAYxD"}
@@ -0,0 +1,15 @@
1
+ /** Median of a non-empty list (copies and sorts). */
2
+ export function median(values) {
3
+ if (values.length === 0) {
4
+ throw new Error('median requires at least one value');
5
+ }
6
+ const sorted = [...values].sort((a, b) => a - b);
7
+ const mid = Math.floor(sorted.length / 2);
8
+ if (sorted.length % 2 === 1) {
9
+ return sorted[mid] ?? 0;
10
+ }
11
+ const a = sorted[mid - 1] ?? 0;
12
+ const b = sorted[mid] ?? 0;
13
+ return (a + b) / 2;
14
+ }
15
+ //# sourceMappingURL=median.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"median.js","sourceRoot":"","sources":["../../src/utils/median.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,MAAM,UAAU,MAAM,CAAC,MAAyB;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;IACvD,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACzC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC1B,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;AACpB,CAAC"}