@cordfuse/nux-qr-tool 1.1.2 → 1.2.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/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # @cordfuse/nux-qr-tool
2
+
3
+ NUX MightyAmp QR preset encoder — generates decorated QR PNG cards from preset JSON.
4
+
5
+ Takes a JSON file describing a NUX MightyAmp tone preset (amp model, effects chain, device target) and outputs a dark-themed PNG card containing a scannable QR code. Works as both a CLI tool (via `npx`) and a Node.js library.
6
+
7
+ [![npm](https://img.shields.io/npm/v/@cordfuse/nux-qr-tool)](https://www.npmjs.com/package/@cordfuse/nux-qr-tool)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+
10
+ ---
11
+
12
+ ## Used by
13
+
14
+ | Repo | How it's used |
15
+ |---|---|
16
+ | [cordfuse/toneai-nux-imprint](https://github.com/cordfuse/toneai-nux-imprint) | Agent invokes `npx @cordfuse/nux-qr-tool` to generate QR cards mid-conversation |
17
+ | [cordfuse/toneai-nux-cli](https://github.com/cordfuse/toneai-nux-cli) | Imports `decorateQR` as a library to decorate QR PNGs inside the compiled binary |
18
+
19
+ ---
20
+
21
+ ## Install
22
+
23
+ **Run without installing (recommended for agent use):**
24
+ ```bash
25
+ npx @cordfuse/nux-qr-tool preset.json
26
+ ```
27
+
28
+ **Install globally:**
29
+ ```bash
30
+ npm install -g @cordfuse/nux-qr-tool
31
+ nux-qr-tool preset.json
32
+ ```
33
+
34
+ **Install as a library:**
35
+ ```bash
36
+ npm install @cordfuse/nux-qr-tool
37
+ ```
38
+
39
+ ---
40
+
41
+ ## CLI Usage
42
+
43
+ ```bash
44
+ npx @cordfuse/nux-qr-tool <preset-json-file> [--output <dir>]
45
+ ```
46
+
47
+ Reads the preset JSON, encodes the NUX QR payload, generates a decorated PNG, writes it to `<dir>/<artist>-<song>.png`, and prints the full output path to stdout.
48
+
49
+ The output directory defaults to the current working directory. Use `--output` (or `-o`) to write elsewhere.
50
+
51
+ ### Example
52
+
53
+ ```bash
54
+ cat > preset.json << 'EOF'
55
+ {
56
+ "artist": "Led Zeppelin",
57
+ "song": "Whole Lotta Love",
58
+ "device": "plugpro",
59
+ "preset_name": "Page Sunburst",
60
+ "preset_name_short": "Page SB",
61
+ "amp": { "id": 3, "gain": 72, "master": 75, "bass": 55, "mid": 50, "treble": 48 },
62
+ "cabinet": { "id": 5, "level_db": 0, "low_cut_hz": 80, "high_cut": 50 },
63
+ "noise_gate": { "enabled": true, "sensitivity": 35, "decay": 50 },
64
+ "efx": { "id": 7, "enabled": true, "p1": 60, "p2": 50, "p3": 55 },
65
+ "reverb": { "id": 1, "enabled": true, "p1": 20, "p2": 40 },
66
+ "master_db": 0
67
+ }
68
+ EOF
69
+
70
+ npx @cordfuse/nux-qr-tool preset.json
71
+ # → /current/working/dir/led-zeppelin-whole-lotta-love.png
72
+
73
+ npx @cordfuse/nux-qr-tool preset.json --output ./cards
74
+ # → ./cards/led-zeppelin-whole-lotta-love.png
75
+ ```
76
+
77
+ The output directory is created automatically if it doesn't exist.
78
+
79
+ ---
80
+
81
+ ## Output Card
82
+
83
+ Each PNG is a 548×620px decorated card:
84
+
85
+ - **Header:** app name (top-left) and version (top-right) in red/grey on a dark background
86
+ - **QR code:** 500×500px, high error correction (level H), white on black
87
+ - **Footer:** artist and song title (bold), device name and embedded-name indicator
88
+
89
+ Pro format devices embed the preset name directly into the QR payload. The footer notes this with `· name embedded in QR`.
90
+
91
+ ---
92
+
93
+ ## Library Usage
94
+
95
+ ```typescript
96
+ import { decorateQR } from '@cordfuse/nux-qr-tool'
97
+ ```
98
+
99
+ ### `decorateQR(qrPng, artist, song, deviceId, deviceName, options?)`
100
+
101
+ Takes a raw QR PNG buffer and adds the dark card decoration around it.
102
+
103
+ ```typescript
104
+ import QRCode from 'qrcode'
105
+ import { decorateQR } from '@cordfuse/nux-qr-tool'
106
+
107
+ const qrBuffer = await QRCode.toBuffer('nux://MightyAmp:...', {
108
+ errorCorrectionLevel: 'H',
109
+ width: 500,
110
+ margin: 4,
111
+ color: { dark: '#000000', light: '#ffffff' },
112
+ }) as Buffer
113
+
114
+ const decorated = await decorateQR(
115
+ qrBuffer,
116
+ 'Led Zeppelin',
117
+ 'Whole Lotta Love',
118
+ 'plugpro',
119
+ 'Mighty Plug Pro',
120
+ { appName: 'my-app', appVersion: '1.0.0' } // optional — defaults to 'ToneAI' + package version
121
+ )
122
+
123
+ fs.writeFileSync('output.png', decorated)
124
+ ```
125
+
126
+ #### Parameters
127
+
128
+ | Param | Type | Description |
129
+ |---|---|---|
130
+ | `qrPng` | `Buffer` | Raw QR code PNG at 500×500px |
131
+ | `artist` | `string` | Artist name — shown in footer |
132
+ | `song` | `string` | Song title — shown in footer |
133
+ | `deviceId` | `string` | NUX device ID (e.g. `plugpro`) — determines footer note |
134
+ | `deviceName` | `string` | Human-readable device name — shown in footer |
135
+ | `options.appName` | `string` | App name in header (default: `'ToneAI'`) |
136
+ | `options.appVersion` | `string` | Version in header (default: package version) |
137
+
138
+ Returns `Promise<Buffer>` — the decorated PNG as a Buffer.
139
+
140
+ ---
141
+
142
+ ## Preset JSON Format
143
+
144
+ The CLI input JSON must include at minimum `artist`, `song`, `device`, and `amp`. All other fields are optional and default to off/zero.
145
+
146
+ ### Top-level fields
147
+
148
+ | Field | Type | Required | Description |
149
+ |---|---|---|---|
150
+ | `artist` | `string` | yes | Artist name — used for output filename and card footer |
151
+ | `song` | `string` | yes | Song title — used for output filename and card footer |
152
+ | `device` | `string` | yes | Target NUX device ID (see Devices table below) |
153
+ | `preset_name` | `string` | yes | Full preset name |
154
+ | `preset_name_short` | `string` | no | Short name for Pro QR payload (max 15 chars) — falls back to `preset_name` |
155
+ | `amp` | `AmpParams` | yes | Amp model and EQ settings |
156
+ | `cabinet` | `CabinetParams` | no | Cabinet IR settings (Pro and most Standard devices) |
157
+ | `noise_gate` | `NoiseGateParams` | yes | Noise gate settings |
158
+ | `efx` | `EffectParams` | no | EFX slot (drive/wah effects) |
159
+ | `compressor` | `EffectParams` | no | Compressor (Pro format only) |
160
+ | `modulation` | `EffectParams` | no | Modulation effect |
161
+ | `delay` | `EffectParams` | no | Delay effect |
162
+ | `reverb` | `EffectParams` | no | Reverb effect |
163
+ | `eq` | `EQParams` | no | EQ (Pro format only) |
164
+ | `wah` | `WahParams` | no | Wah pedal (2040bt/40bt only) |
165
+ | `master_db` | `number` | yes | Master volume offset in dB (range: −12 to +12) |
166
+
167
+ ### AmpParams
168
+
169
+ | Field | Type | Range | Description |
170
+ |---|---|---|---|
171
+ | `id` | `number` | device-specific | Amp model ID (nux index, 0- or 1-indexed depending on device) |
172
+ | `gain` | `number` | 0–100 | Gain |
173
+ | `master` | `number` | 0–100 | Master volume |
174
+ | `bass` | `number` | 0–100 | Bass EQ |
175
+ | `mid` | `number` | 0–100 | Mid EQ |
176
+ | `treble` | `number` | 0–100 | Treble EQ |
177
+ | `param6` | `number` | 0–100 | Amp-specific 6th parameter (presence, resonance, etc.) |
178
+ | `param7` | `number` | 0–100 | Amp-specific 7th parameter (rare) |
179
+
180
+ ### CabinetParams
181
+
182
+ | Field | Type | Range | Description |
183
+ |---|---|---|---|
184
+ | `id` | `number` | device-specific | Cabinet IR model ID |
185
+ | `level_db` | `number` | −12 to +12 | Cabinet output level in dB |
186
+ | `low_cut_hz` | `number` | 20–300 | Low cut frequency |
187
+ | `high_cut` | `number` | 0–100 | High cut amount |
188
+
189
+ ### NoiseGateParams
190
+
191
+ | Field | Type | Range | Description |
192
+ |---|---|---|---|
193
+ | `enabled` | `boolean` | — | Whether noise gate is active |
194
+ | `sensitivity` | `number` | 0–100 | Gate sensitivity (threshold) |
195
+ | `decay` | `number` | 0–100 | Gate decay/release |
196
+
197
+ ### EffectParams (efx, compressor, modulation, delay, reverb)
198
+
199
+ | Field | Type | Range | Description |
200
+ |---|---|---|---|
201
+ | `id` | `number` | device-specific | Effect model ID (nux index) |
202
+ | `enabled` | `boolean` | — | Whether effect is active |
203
+ | `p1` | `number` | 0–100 | Parameter 1 |
204
+ | `p2` | `number` | 0–100 | Parameter 2 |
205
+ | `p3` | `number` | 0–100 | Parameter 3 (effect-dependent) |
206
+
207
+ ### EQParams (Pro format only)
208
+
209
+ | Field | Type | Description |
210
+ |---|---|---|
211
+ | `id` | `number` | `1` = 6-Band, `3` = 10-Band |
212
+ | `enabled` | `boolean` | Whether EQ is active |
213
+ | `bands` | `number[]` | Per-band dB values (−15 to +15). 6 values for 6-Band, 11 for 10-Band |
214
+
215
+ ### WahParams (2040bt and 40bt only)
216
+
217
+ | Field | Type | Range | Description |
218
+ |---|---|---|---|
219
+ | `enabled` | `boolean` | — | Whether wah is active |
220
+ | `pedal` | `number` | 0–100 | Pedal position |
221
+
222
+ ---
223
+
224
+ ## Devices
225
+
226
+ | ID | Device | Format | Notes |
227
+ |---|---|---|---|
228
+ | `plugpro` | Mighty Plug Pro | Pro (113 bytes) | Full chain, preset name in QR |
229
+ | `space` | Mighty Space | Pro (113 bytes) | Full chain, preset name in QR |
230
+ | `litemk2` | Mighty Lite MkII | Pro (113 bytes) | Full chain, preset name in QR |
231
+ | `8btmk2` | Mighty 8BT MkII | Pro (113 bytes) | Full chain, preset name in QR |
232
+ | `20btmk2` | Mighty 20BT MkII | Pro (113 bytes) | Full chain, preset name in QR |
233
+ | `40btmk2` | Mighty 40BT MkII | Pro (113 bytes) | Full chain, preset name in QR |
234
+ | `60btmk2` | Mighty 60BT MkII | Pro (113 bytes) | Full chain, preset name in QR |
235
+ | `plugair_v1` | Mighty Plug (v1) | Standard (40 bytes) | EFX slot, no preset name |
236
+ | `plugair_v2` | Mighty Plug (v2) | Standard (40 bytes) | EFX slot, no preset name |
237
+ | `mightyair_v1` | Mighty Air (v1) | Standard (40 bytes) | EFX slot, no preset name |
238
+ | `mightyair_v2` | Mighty Air (v2) | Standard (40 bytes) | EFX slot, no preset name |
239
+ | `mightygo` | Mighty Go | Standard (40 bytes) | EFX slot, no preset name |
240
+ | `lite` | Mighty Lite BT | Standard (40 bytes) | Single ambience slot (delay OR reverb) |
241
+ | `8bt` | Mighty 8BT | Standard (40 bytes) | Separate delay and reverb |
242
+ | `2040bt` | Mighty 20/40BT (original) | Standard (40 bytes) | Wah pedal, bass/mid/treble EQ |
243
+ | `40bt` | Mighty 40BT (original) | Standard (40 bytes) | Same format as 2040bt, separate QR ID |
244
+
245
+ ### Pro vs Standard format
246
+
247
+ **Pro** devices use a 113-byte payload with the full effects chain (Compressor, EFX, Amp, EQ, Noise Gate, Modulation, Delay, Reverb, Cabinet) and embed the preset name in bytes 98–112.
248
+
249
+ **Standard** devices use a 40-byte device-specific payload. Amp and effect IDs are different from Pro devices and are 0-indexed. Cabinets and EQ are absent on Lite/8BT/2040BT. The Lite BT uses a single ambience slot shared between delay and reverb — reverb takes priority.
250
+
251
+ ### QR string format
252
+
253
+ The encoded QR string has the form:
254
+
255
+ ```
256
+ nux://MightyAmp:<base64>
257
+ ```
258
+
259
+ Where the base64 decodes to `[deviceQRId, deviceQRVersion, ...payload]`.
260
+
261
+ ---
262
+
263
+ ## Requirements
264
+
265
+ - Node.js 18+ (for `npx` / global install)
266
+ - Or any runtime that can execute Node-compatible ESM (Bun, Deno with compat flag)
267
+
268
+ No external binaries required. Canvas rendering is handled by [`@napi-rs/canvas`](https://github.com/Brooooooklyn/canvas) — pre-built native binaries are downloaded automatically on install for Linux x64 (glibc and musl), macOS (arm64 and x64), and Windows x64.
269
+
270
+ ---
271
+
272
+ ## QR format reference
273
+
274
+ The NUX QR format was reverse-engineered from the open-source [mightier_amp](https://github.com/tuntorius/mightier_amp) Flutter app by [tuntorius](https://github.com/tuntorius). Key reference files: `NuxConstants.dart` and the per-device effect files under `lib/bluetooth/devices/effects/`.
275
+
276
+ ---
277
+
278
+ ## License
279
+
280
+ MIT
@@ -6108,9 +6108,12 @@ async function decorateQR(qrPng, artist, song, deviceId, deviceName, options) {
6108
6108
  return canvas.toBuffer("image/png");
6109
6109
  }
6110
6110
  async function main() {
6111
- const jsonPath = process.argv[2];
6111
+ const args = process.argv.slice(2);
6112
+ const outputFlagIdx = args.findIndex((a) => a === "--output" || a === "-o");
6113
+ const outDir = outputFlagIdx !== -1 ? resolve(args[outputFlagIdx + 1]) : process.cwd();
6114
+ const jsonPath = args.find((a, i) => !a.startsWith("-") && i !== outputFlagIdx + 1);
6112
6115
  if (!jsonPath) {
6113
- console.error("Usage: npx tsx src/qr-generator.ts <params-json-file>");
6116
+ console.error("Usage: npx @cordfuse/nux-qr-tool <params-json-file> [--output <dir>]");
6114
6117
  process.exit(1);
6115
6118
  }
6116
6119
  const raw = JSON.parse(readFileSync(resolve(jsonPath), "utf8"));
@@ -6129,7 +6132,6 @@ async function main() {
6129
6132
  });
6130
6133
  const decorated = await decorateQR(qrPng, params.artist, params.song, params.device, device.displayName);
6131
6134
  const slug = `${params.artist}-${params.song}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
6132
- const outDir = resolve("./output");
6133
6135
  mkdirSync(outDir, { recursive: true });
6134
6136
  const outPath = join(outDir, `${slug}.png`);
6135
6137
  const { writeFileSync } = await import("fs");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cordfuse/nux-qr-tool",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "NUX MightyAmp QR preset encoder — generates decorated QR PNG cards from preset JSON",
5
5
  "type": "module",
6
6
  "bin": {