tuner-cli 2026.4.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tuner contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # tuner-cli
2
+
3
+ Chromatic **instrument tuner** in the terminal: live pitch from your microphone, needle-style cents display, and optional string hints. Built with **[tuner-core](https://www.npmjs.com/package/tuner-core)** (detectors + session) and **[decibri](https://www.npmjs.com/package/decibri)** (PortAudio capture).
4
+
5
+ ![A guitar tuner in your terminal](docs/cli.gif)
6
+
7
+ ## Requirements
8
+
9
+ - **Node.js 20+**
10
+ - A **microphone** and an OS where **decibri** can open an input (prebuilt binaries when available; otherwise a local build toolchain may be needed for that package)
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g tuner-cli
16
+ ```
17
+
18
+ Run **`tuner`** on your `PATH`. To try without a global install:
19
+
20
+ ```bash
21
+ npx tuner-cli
22
+ ```
23
+
24
+
25
+ ## Usage
26
+
27
+ Start the tuner with defaults (guitar, host default input, 48 kHz):
28
+
29
+ ```bash
30
+ tuner
31
+ ```
32
+
33
+ Discover inputs and built-in instruments:
34
+
35
+ ```bash
36
+ tuner --list-devices
37
+ tuner --list-instruments
38
+ tuner --list-tunings guitar
39
+ ```
40
+
41
+ Point at a specific input and instrument:
42
+
43
+ ```bash
44
+ tuner --device 1 --instrument bass --tuning bass-standard
45
+ tuner --rate 44100 --detector pyin --verbose
46
+ ```
47
+
48
+ Use `-h` / `--help` for full CLI text and more examples.
49
+
50
+ ## Options
51
+
52
+ | Flag | Description |
53
+ |------|-------------|
54
+ | `-h`, `--help` | Help |
55
+ | `-v`, `--verbose` | Print resolved config on **stderr** before audio starts |
56
+ | `--list-devices` | List audio inputs |
57
+ | `--list-instruments` | List instrument ids |
58
+ | `--list-tunings <id>` | List tunings for an instrument |
59
+ | `--device …` | Input device: numeric index or substring of the device name |
60
+ | `--rate <hz>` | Sample rate (default `48000`) |
61
+ | `--instrument <id>` | Instrument (default `guitar`) |
62
+ | `--tuning <id>` | Tuning (default: first for that instrument) |
63
+ | `--detector <kind>` | `yin`, `pyin`, `mpm`, `autocorrelation` (default `yin`) |
64
+ | `--cents-threshold <n>` | In-tune window for the string hint (default `5`) |
65
+ | `--style <name>` | `standard`, `colors`, `ansi` |
66
+ | `--color <mode>` | `auto`, `always`, `never` (honours `NO_COLOR` / `FORCE_COLOR` when `auto`) |
67
+
68
+ ## Interactive terminal
69
+
70
+ If stdin and stdout are a TTY, you can change settings without restarting the whole program:
71
+
72
+ | Key | Action |
73
+ |-----|--------|
74
+ | **i** | Instrument |
75
+ | **t** | Tuning |
76
+ | **s** | Display style |
77
+ | **a** | Advanced (detector, rate, device, cents, color, style) |
78
+ | **q**, **Esc**, **←** | Quit (when no menu is open; in lists, **Esc** / **←** goes back) |
79
+ | **Ctrl+C** | Quit |
80
+
81
+ In lists: **↑** / **↓** (or **j** / **k**) move; **Enter** or **→** (or **l**) select; **Esc** or **←** (or **h**) cancel. Changing detector, rate, or device in **Advanced** restarts the audio stream.
82
+
83
+ ## Developing
84
+
85
+ This package lives in the **[Tuner](https://github.com/mducharme/Tuner)** monorepo.
86
+
87
+ ```bash
88
+ git clone https://github.com/mducharme/Tuner.git
89
+ cd Tuner
90
+ pnpm install
91
+ pnpm dev:cli # run from source via tsx
92
+ pnpm --filter tuner-cli build
93
+ pnpm --filter tuner-cli test
94
+ pnpm --filter tuner-cli run package:assert
95
+ ```
96
+
97
+ Releases and versioning are handled in the monorepo (see the root **README** and [CHANGELOG](../../CHANGELOG.md)).
98
+
99
+ ## License
100
+
101
+ MIT — see [LICENSE](./LICENSE).
package/dist/ansi.js ADDED
@@ -0,0 +1,92 @@
1
+ /** Minimal ANSI SGR helpers; gated by {@link resolveColorMode}. */
2
+ export function resolveColorMode(choice, isTTY, env) {
3
+ if (choice === 'never')
4
+ return false;
5
+ if (choice === 'always')
6
+ return true;
7
+ if (env.NO_COLOR !== undefined && env.NO_COLOR !== '')
8
+ return false;
9
+ if (env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== '0')
10
+ return true;
11
+ return isTTY;
12
+ }
13
+ const SGR = {
14
+ reset: '\x1b[0m',
15
+ bold: '\x1b[1m',
16
+ dim: '\x1b[2m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ red: '\x1b[31m',
20
+ cyan: '\x1b[36m',
21
+ magenta: '\x1b[35m',
22
+ white: '\x1b[37m',
23
+ };
24
+ /** Match ANSI SGR sequences (CSI … m). Built without `\x1b` in the pattern for tooling rules. */
25
+ const ANSI_SGR_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g');
26
+ export function stripAnsi(s) {
27
+ return s.replace(ANSI_SGR_PATTERN, '');
28
+ }
29
+ /** Chromatic cents: green near 0, yellow mid, red far. */
30
+ export function styleCents(color, cents) {
31
+ if (!color)
32
+ return { open: '', close: '' };
33
+ const a = Math.abs(cents);
34
+ if (a <= 5)
35
+ return { open: SGR.green, close: SGR.reset };
36
+ if (a <= 20)
37
+ return { open: SGR.yellow, close: SGR.reset };
38
+ return { open: SGR.red, close: SGR.reset };
39
+ }
40
+ export function styleNote(color) {
41
+ if (!color)
42
+ return { open: '', close: '' };
43
+ return { open: SGR.bold, close: SGR.reset };
44
+ }
45
+ export function styleDim(color) {
46
+ if (!color)
47
+ return { open: '', close: '' };
48
+ return { open: SGR.dim, close: SGR.reset };
49
+ }
50
+ export function styleInTune(color) {
51
+ if (!color)
52
+ return { open: '', close: '' };
53
+ return { open: SGR.green, close: SGR.reset };
54
+ }
55
+ /** Marker on gauge: green when string is in tune; else cents-based. */
56
+ export function styleGaugeMarker(color, cents, stringInTune) {
57
+ if (!color)
58
+ return { open: '', close: '' };
59
+ if (stringInTune)
60
+ return { open: SGR.green, close: SGR.reset };
61
+ return styleCents(color, cents);
62
+ }
63
+ /** One inactive track cell: slight flat / sharp tint by side of center. */
64
+ export function formatTrackCell(color, idx, centerIdx) {
65
+ if (!color)
66
+ return '·';
67
+ if (idx < centerIdx) {
68
+ return `${SGR.dim}${SGR.cyan}·${SGR.reset}`;
69
+ }
70
+ if (idx > centerIdx) {
71
+ return `${SGR.dim}${SGR.magenta}·${SGR.reset}`;
72
+ }
73
+ return `${SGR.dim}${SGR.white}·${SGR.reset}`;
74
+ }
75
+ export function formatMarkerCell(color, open, close) {
76
+ if (!color)
77
+ return '█';
78
+ return `${open}${SGR.bold}█${close}`;
79
+ }
80
+ /** Tinted track cell using shade blocks (fancy bar). */
81
+ export function formatTrackShade(color, idx, centerIdx, ch) {
82
+ if (!color)
83
+ return ch;
84
+ if (idx < centerIdx) {
85
+ return `${SGR.dim}${SGR.cyan}${ch}${SGR.reset}`;
86
+ }
87
+ if (idx > centerIdx) {
88
+ return `${SGR.dim}${SGR.magenta}${ch}${SGR.reset}`;
89
+ }
90
+ return `${SGR.dim}${SGR.white}${ch}${SGR.reset}`;
91
+ }
92
+ //# sourceMappingURL=ansi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ansi.js","sourceRoot":"","sources":["../src/ansi.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAInE,MAAM,UAAU,gBAAgB,CAC9B,MAAmB,EACnB,KAAc,EACd,GAAsB;IAEtB,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,KAAK,CAAA;IACpC,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACpC,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,KAAK,CAAA;IACnE,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,GAAG,CAAC,WAAW,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,GAAG,GAAG;IACV,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,UAAU;IACnB,KAAK,EAAE,UAAU;CACT,CAAA;AAEV,iGAAiG;AACjG,MAAM,gBAAgB,GAAG,IAAI,MAAM,CACjC,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,EACvC,GAAG,CACJ,CAAA;AAED,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;AACxC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,UAAU,CACxB,KAAc,EACd,KAAa;IAEb,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACzB,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;IACxD,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;IAC1D,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC1C,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC1C,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC1C,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;AAC9C,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAC9B,KAAc,EACd,KAAa,EACb,YAAqB;IAErB,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC1C,IAAI,YAAY;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAA;IAC9D,OAAO,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AACjC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAC7B,KAAc,EACd,GAAW,EACX,SAAiB;IAEjB,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAA;IACtB,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAA;IAC7C,CAAC;IACD,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,KAAK,EAAE,CAAA;IAChD,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,KAAc,EACd,IAAY,EACZ,KAAa;IAEb,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAA;IACtB,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,KAAK,EAAE,CAAA;AACtC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAC9B,KAAc,EACd,GAAW,EACX,SAAiB,EACjB,EAAU;IAEV,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,CAAA;IACjD,CAAC;IACD,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,OAAO,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,CAAA;IACpD,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,CAAA;AAClD,CAAC"}
@@ -0,0 +1,31 @@
1
+ import Decibri from 'decibri';
2
+ import { INSTRUMENTS, findInstrument } from 'tuner-core';
3
+ export function listAudioDevices() {
4
+ const devices = Decibri.devices();
5
+ if (devices.length === 0) {
6
+ console.log('No input devices found.');
7
+ return;
8
+ }
9
+ for (const d of devices) {
10
+ const def = d.isDefault ? ' (default)' : '';
11
+ console.log(` [${d.index}] ${d.name}${def} ${d.defaultSampleRate} Hz`);
12
+ }
13
+ }
14
+ export function listInstruments() {
15
+ for (const inst of INSTRUMENTS) {
16
+ console.log(` ${inst.id} ${inst.name}`);
17
+ }
18
+ }
19
+ /** @returns false if instrument id is unknown */
20
+ export function listTunings(instrumentId) {
21
+ const inst = findInstrument(instrumentId);
22
+ if (!inst) {
23
+ console.error(`Unknown instrument: ${instrumentId}`);
24
+ return false;
25
+ }
26
+ for (const t of inst.tunings) {
27
+ console.log(` ${t.id} ${t.name}`);
28
+ }
29
+ return true;
30
+ }
31
+ //# sourceMappingURL=cli-lists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-lists.js","sourceRoot":"","sources":["../src/cli-lists.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAExD,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA;IACjC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;QACtC,OAAM;IACR,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;QAC3C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,iBAAiB,KAAK,CAAC,CAAA;IAC1E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IAC3C,CAAC;AACH,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,YAAoB;IAC9C,MAAM,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;IACzC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,uBAAuB,YAAY,EAAE,CAAC,CAAA;QACpD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,78 @@
1
+ import Decibri from 'decibri';
2
+ /**
3
+ * Bridges decibri mic capture to {@link AudioProvider}: fixed-size float frames
4
+ * for {@link TunerSession}.
5
+ */
6
+ export class DecibriAudioProvider {
7
+ sampleRate;
8
+ frameSamples;
9
+ device;
10
+ onStreamError;
11
+ mic = null;
12
+ frameCallback = null;
13
+ accumulator;
14
+ accWrite = 0;
15
+ constructor(sampleRate, frameSamples, options) {
16
+ this.sampleRate = sampleRate;
17
+ this.frameSamples = frameSamples;
18
+ this.device = options?.device;
19
+ this.onStreamError = options?.onStreamError;
20
+ this.accumulator = new Float32Array(frameSamples);
21
+ }
22
+ getSampleRate() {
23
+ return this.sampleRate;
24
+ }
25
+ onFrame(callback) {
26
+ this.frameCallback = callback;
27
+ }
28
+ async start() {
29
+ if (this.mic)
30
+ return;
31
+ const framesPerBuffer = Math.min(8192, Math.max(256, Math.floor(this.frameSamples / 2)));
32
+ const mic = new Decibri({
33
+ sampleRate: this.sampleRate,
34
+ channels: 1,
35
+ format: 'float32',
36
+ framesPerBuffer,
37
+ ...(this.device !== undefined ? { device: this.device } : {}),
38
+ });
39
+ this.mic = mic;
40
+ this.accWrite = 0;
41
+ mic.on('data', (chunk) => {
42
+ this.pushPcm(chunk);
43
+ });
44
+ mic.on('error', (err) => {
45
+ this.onStreamError?.(err);
46
+ });
47
+ mic.resume();
48
+ }
49
+ stop() {
50
+ if (!this.mic)
51
+ return;
52
+ this.mic.removeAllListeners('data');
53
+ this.mic.removeAllListeners('error');
54
+ this.mic.stop();
55
+ this.mic = null;
56
+ this.accWrite = 0;
57
+ this.accumulator.fill(0);
58
+ }
59
+ pushPcm(chunk) {
60
+ const sampleCount = chunk.length >> 2;
61
+ const f32 = new Float32Array(sampleCount);
62
+ for (let i = 0; i < sampleCount; i++) {
63
+ f32[i] = chunk.readFloatLE(i * 4);
64
+ }
65
+ let offset = 0;
66
+ while (offset < f32.length) {
67
+ const take = Math.min(this.frameSamples - this.accWrite, f32.length - offset);
68
+ this.accumulator.set(f32.subarray(offset, offset + take), this.accWrite);
69
+ this.accWrite += take;
70
+ offset += take;
71
+ if (this.accWrite >= this.frameSamples) {
72
+ this.frameCallback?.(Float32Array.from(this.accumulator));
73
+ this.accWrite = 0;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ //# sourceMappingURL=decibri-audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decibri-audio.js","sourceRoot":"","sources":["../src/decibri-audio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { resolveColorMode, stripAnsi } from '../ansi.js';
2
+ import { COLOR_BAR_SLOTS, FANCY_BAR_SLOTS, buildFancyGaugeBar, buildGaugeBar, fitOneTTYLine, formatInfoLine, } from '../format-line.js';
3
+ const STYLE_LABELS = {
4
+ standard: 'Standard',
5
+ colors: 'Colors',
6
+ ansi: 'ANSI',
7
+ };
8
+ export function displayStyleLabel(style) {
9
+ return STYLE_LABELS[style];
10
+ }
11
+ export const DISPLAY_STYLES = ['standard', 'colors', 'ansi'];
12
+ /**
13
+ * Default when --style is omitted: ANSI if color mode would be on (rich escapes),
14
+ * else Colors on a TTY (layout with optional color when --color allows), else Standard.
15
+ */
16
+ export function defaultDisplayStyle(isTTY, env) {
17
+ if (isTTY && resolveColorMode('auto', isTTY, env))
18
+ return 'ansi';
19
+ if (isTTY)
20
+ return 'colors';
21
+ return 'standard';
22
+ }
23
+ function effectiveColor(ctx) {
24
+ if (ctx.style === 'standard')
25
+ return false;
26
+ return resolveColorMode(ctx.colorChoice, ctx.isTTY, ctx.env);
27
+ }
28
+ /** One physical line: info + bar; no wrap past terminal width. */
29
+ export function renderTunerLine(result, ctx) {
30
+ const cols = Math.max(20, ctx.termWidth);
31
+ const color = effectiveColor(ctx);
32
+ if (ctx.style === 'ansi') {
33
+ const bar = buildFancyGaugeBar(result.cents, FANCY_BAR_SLOTS, color, result.closestString?.inTune ?? false);
34
+ const line = `${formatInfoLine(result, color)} ${bar}`;
35
+ return fitOneTTYLine(line, cols);
36
+ }
37
+ const bar = buildGaugeBar(result.cents, COLOR_BAR_SLOTS, color, result.closestString?.inTune ?? false);
38
+ const line = `${formatInfoLine(result, color)} ${bar}`;
39
+ return fitOneTTYLine(line, cols);
40
+ }
41
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/display/styles.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC1E,OAAO,EACL,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,mBAAmB,CAAA;AAI1B,MAAM,YAAY,GAAiC;IACjD,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,MAAM;CACb,CAAA;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAmB,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;AAE5E;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,GAAsB;IAEtB,IAAI,KAAK,IAAI,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC;QAAE,OAAO,MAAM,CAAA;IAChE,IAAI,KAAK;QAAE,OAAO,QAAQ,CAAA;IAC1B,OAAO,UAAU,CAAA;AACnB,CAAC;AAUD,SAAS,cAAc,CAAC,GAAkB;IACxC,IAAI,GAAG,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,KAAK,CAAA;IAC1C,OAAO,gBAAgB,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;AAC9D,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAC7B,MAAmB,EACnB,GAAkB;IAElB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;IACxC,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAA;IAEjC,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,kBAAkB,CAC5B,MAAM,CAAC,KAAK,EACZ,eAAe,EACf,KAAK,EACL,MAAM,CAAC,aAAa,EAAE,MAAM,IAAI,KAAK,CACtC,CAAA;QACD,MAAM,IAAI,GAAG,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG,EAAE,CAAA;QACvD,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,MAAM,GAAG,GAAG,aAAa,CACvB,MAAM,CAAC,KAAK,EACZ,eAAe,EACf,KAAK,EACL,MAAM,CAAC,aAAa,EAAE,MAAM,IAAI,KAAK,CACtC,CAAA;IACD,MAAM,IAAI,GAAG,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG,EAAE,CAAA;IACvD,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AAClC,CAAC"}
@@ -0,0 +1,115 @@
1
+ import { formatMarkerCell, formatTrackCell, formatTrackShade, stripAnsi, styleCents, styleDim, styleGaugeMarker, styleInTune, styleNote, } from './ansi.js';
2
+ export const COLOR_BAR_SLOTS = 17;
3
+ export const FANCY_BAR_SLOTS = 28;
4
+ const SHADE_LIGHT = '░';
5
+ const SHADE_MID = '▒';
6
+ const SHADE_HEAVY = '▓';
7
+ function clampCents(cents) {
8
+ return Math.max(-50, Math.min(50, cents));
9
+ }
10
+ function centsToMarkerIndex(cents, barLen) {
11
+ const c = clampCents(cents);
12
+ const t = (c + 50) / 100;
13
+ return Math.round(t * (barLen - 1));
14
+ }
15
+ export function buildGaugeBar(cents, barLen, color, stringInTune) {
16
+ const c = clampCents(cents);
17
+ const markerIdx = centsToMarkerIndex(c, barLen);
18
+ const centerIdx = (barLen - 1) / 2;
19
+ const mk = styleGaugeMarker(color, c, stringInTune);
20
+ let s = '';
21
+ for (let i = 0; i < barLen; i++) {
22
+ if (i === markerIdx) {
23
+ s += formatMarkerCell(color, mk.open, mk.close);
24
+ }
25
+ else {
26
+ s += formatTrackCell(color, i, centerIdx);
27
+ }
28
+ }
29
+ return s;
30
+ }
31
+ /**
32
+ * Finer cents resolution: shade falloff around needle; tinted track when color is on.
33
+ */
34
+ export function buildFancyGaugeBar(cents, barLen, color, stringInTune) {
35
+ const c = clampCents(cents);
36
+ const pos = (c + 50) / 100;
37
+ const centerIdx = (barLen - 1) / 2;
38
+ const mk = styleGaugeMarker(color, c, stringInTune);
39
+ let s = '';
40
+ for (let i = 0; i < barLen; i++) {
41
+ const cellCenter = (i + 0.5) / barLen;
42
+ const dist = Math.abs(cellCenter - pos) * barLen;
43
+ let ch;
44
+ if (dist < 0.2) {
45
+ s += formatMarkerCell(color, mk.open, mk.close);
46
+ continue;
47
+ }
48
+ if (dist < 0.55) {
49
+ ch = SHADE_HEAVY;
50
+ }
51
+ else if (dist < 1.0) {
52
+ ch = SHADE_MID;
53
+ }
54
+ else {
55
+ ch = SHADE_LIGHT;
56
+ }
57
+ if (!color) {
58
+ s += ch;
59
+ continue;
60
+ }
61
+ if (dist < 1.0) {
62
+ const tint = styleGaugeMarker(color, c, stringInTune);
63
+ s += `${tint.open}${ch}${tint.close}`;
64
+ }
65
+ else {
66
+ s += formatTrackShade(color, i, centerIdx, ch);
67
+ }
68
+ }
69
+ return s;
70
+ }
71
+ /** Fixed width so the following gauge column does not drift (−50…+50, one decimal). */
72
+ const CHROMATIC_CENTS_FIELD = 6;
73
+ /** e.g. 1234.5 Hz — pad so bar starts at same column. */
74
+ const HZ_FIELD = 8;
75
+ /** String-target cents (integer), signed; keeps │ block width stable. */
76
+ const STRING_CENTS_OFF_FIELD = 5;
77
+ function formatSignedOneDecimal(value, fieldWidth) {
78
+ const sign = value >= 0 ? '+' : '-';
79
+ const abs = Math.abs(value).toFixed(1);
80
+ return `${sign}${abs}`.padStart(fieldWidth, ' ');
81
+ }
82
+ function formatHzField(hz) {
83
+ return `${hz.toFixed(1).padStart(HZ_FIELD, ' ')} Hz`;
84
+ }
85
+ function formatStringCentsOff(off) {
86
+ const s = off >= 0 ? `+${off.toFixed(0)}` : off.toFixed(0);
87
+ return s.padStart(STRING_CENTS_OFF_FIELD, ' ');
88
+ }
89
+ /** Info field: note, Hz, cents, optional string row (shared by all styles). */
90
+ export function formatInfoLine(result, color) {
91
+ const centsStr = formatSignedOneDecimal(result.cents, CHROMATIC_CENTS_FIELD);
92
+ const n = styleNote(color);
93
+ const dim = styleDim(color);
94
+ const ct = styleCents(color, result.cents);
95
+ let line = `${n.open}${result.note}${result.octave}${n.close}` +
96
+ ` ${dim.open}${formatHzField(result.frequency)}${dim.close}` +
97
+ ` ${ct.open}${centsStr}¢${ct.close}`;
98
+ const cs = result.closestString;
99
+ if (cs) {
100
+ const off = formatStringCentsOff(cs.centsOff);
101
+ const ok = styleInTune(color);
102
+ const mark = cs.inTune ? `${ok.open} ✓${ok.close}` : '';
103
+ line += ` │ ${cs.name} ${off}¢${mark}`;
104
+ }
105
+ return line;
106
+ }
107
+ export function fitOneTTYLine(ansiLine, maxCols) {
108
+ const plain = stripAnsi(ansiLine);
109
+ if (plain.length <= maxCols)
110
+ return ansiLine;
111
+ const ell = '…';
112
+ const cut = Math.max(1, maxCols - ell.length);
113
+ return plain.slice(0, cut) + ell;
114
+ }
115
+ //# sourceMappingURL=format-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-line.js","sourceRoot":"","sources":["../src/format-line.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process';
3
+ import { listAudioDevices, listInstruments, listTunings } from './cli-lists.js';
4
+ import { createCliCommand, parseCliRuntime } from './parse-args.js';
5
+ import { runTunerSession } from './run-tuner-session.js';
6
+ /** Full help including `addHelpText('after', …)` (not just `helpInformation()`). */
7
+ function printProgramHelp() {
8
+ const cmd = createCliCommand();
9
+ const parts = [];
10
+ cmd.configureOutput({
11
+ writeOut: (s) => parts.push(s),
12
+ writeErr: (s) => parts.push(s),
13
+ });
14
+ cmd.outputHelp();
15
+ process.stdout.write(parts.join(''));
16
+ }
17
+ async function main() {
18
+ let parsed;
19
+ try {
20
+ parsed = parseCliRuntime(process.argv);
21
+ }
22
+ catch (e) {
23
+ const msg = e instanceof Error ? e.message : String(e);
24
+ console.error(msg);
25
+ printProgramHelp();
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+ if (parsed.kind === 'help') {
30
+ printProgramHelp();
31
+ return;
32
+ }
33
+ if (parsed.kind === 'list-devices') {
34
+ listAudioDevices();
35
+ return;
36
+ }
37
+ if (parsed.kind === 'list-instruments') {
38
+ listInstruments();
39
+ return;
40
+ }
41
+ if (parsed.kind === 'list-tunings') {
42
+ if (!listTunings(parsed.instrumentId)) {
43
+ process.exitCode = 1;
44
+ }
45
+ return;
46
+ }
47
+ await runTunerSession(parsed.args);
48
+ }
49
+ void main().catch((e) => {
50
+ console.error(e instanceof Error ? e.message : e);
51
+ process.exitCode = 1;
52
+ });
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,OAAO,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC/E,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEnE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,oFAAoF;AACpF,SAAS,gBAAgB;IACvB,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAA;IAC9B,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,GAAG,CAAC,eAAe,CAAC;QAClB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9B,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;KAC/B,CAAC,CAAA;IACF,GAAG,CAAC,UAAU,EAAE,CAAA;IAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,MAAiB,CAAA;IACrB,IAAI,CAAC;QACH,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACtD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClB,gBAAgB,EAAE,CAAA;QAClB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;QACpB,OAAM;IACR,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,gBAAgB,EAAE,CAAA;QAClB,OAAM;IACR,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACnC,gBAAgB,EAAE,CAAA;QAClB,OAAM;IACR,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACvC,eAAe,EAAE,CAAA;QACjB,OAAM;IACR,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACnC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;YACtC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;QACtB,CAAC;QACD,OAAM;IACR,CAAC;IAED,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AACpC,CAAC;AAED,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACtB,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACjD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACtB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,88 @@
1
+ import readline from 'node:readline';
2
+ function isLetterChar(c) {
3
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
4
+ }
5
+ /**
6
+ * Decode readline keypress; Windows/ConPTY often differs from macOS/Linux (name vs sequence).
7
+ */
8
+ export function parseMenuKey(key) {
9
+ if (!key)
10
+ return null;
11
+ const seq = key.sequence ?? '';
12
+ // Ctrl+C sends ETX in raw mode (especially Windows); does not always set key.name === 'c'
13
+ if (seq === '\u0003' || seq === '\x03') {
14
+ return { kind: 'ctrl-c' };
15
+ }
16
+ if (key.ctrl && (key.name === 'c' || seq === '\u0003')) {
17
+ return { kind: 'ctrl-c' };
18
+ }
19
+ if (key.name === 'up' || key.name === 'k')
20
+ return { kind: 'up' };
21
+ if (key.name === 'down' || key.name === 'j')
22
+ return { kind: 'down' };
23
+ if (key.name === 'left' || key.name === 'h')
24
+ return { kind: 'left' };
25
+ if (key.name === 'right' || key.name === 'l')
26
+ return { kind: 'right' };
27
+ if (key.name === 'return' || key.name === 'enter')
28
+ return { kind: 'enter' };
29
+ if (key.name === 'escape')
30
+ return { kind: 'escape' };
31
+ if (!key.ctrl && !key.meta) {
32
+ const name = key.name ?? '';
33
+ if (name.length === 1 && isLetterChar(name)) {
34
+ return { kind: 'letter', ch: name.toLowerCase() };
35
+ }
36
+ if (seq.length === 1) {
37
+ const ch0 = seq[0];
38
+ if (ch0 !== undefined && isLetterChar(ch0)) {
39
+ return { kind: 'letter', ch: ch0.toLowerCase() };
40
+ }
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Raw stdin + keypress events. Uninstall restores prior rawMode when possible.
47
+ */
48
+ export function installKeypress(stdin) {
49
+ const listeners = new Set();
50
+ if (!stdin.isTTY) {
51
+ return {
52
+ onKey(fn) {
53
+ listeners.add(fn);
54
+ },
55
+ uninstall: () => {
56
+ listeners.clear();
57
+ },
58
+ };
59
+ }
60
+ readline.emitKeypressEvents(stdin);
61
+ stdin.setRawMode(true);
62
+ stdin.resume();
63
+ const handler = (_s, key) => {
64
+ const mk = parseMenuKey(key);
65
+ if (!mk)
66
+ return;
67
+ for (const fn of listeners) {
68
+ fn(mk);
69
+ }
70
+ };
71
+ stdin.on('keypress', handler);
72
+ return {
73
+ onKey(fn) {
74
+ listeners.add(fn);
75
+ },
76
+ uninstall() {
77
+ stdin.off('keypress', handler);
78
+ try {
79
+ stdin.setRawMode(false);
80
+ }
81
+ catch {
82
+ /* ignore */
83
+ }
84
+ listeners.clear();
85
+ },
86
+ };
87
+ }
88
+ //# sourceMappingURL=keys.js.map