@audiofab-io/fv1-core 0.1.0 → 0.2.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.
@@ -0,0 +1,10 @@
1
+ export declare const MCP2221_VENDOR_ID = 1240;
2
+ export declare const MCP2221_PRODUCT_ID = 221;
3
+ export declare const DEFAULT_EEPROM_I2C_ADDRESS = 80;
4
+ export declare const DEFAULT_I2C_CLOCK_HZ = 400000;
5
+ export declare const DEFAULT_WRITE_PAGE_SIZE = 32;
6
+ export declare const PROGRAM_SLOT_COUNT = 8;
7
+ export declare const PROGRAM_SLOT_SIZE_BYTES = 512;
8
+ export declare const EEPROM_TOTAL_BYTES: number;
9
+ export declare function slotBaseAddress(slot: number): number;
10
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/pedal/constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAS,CAAA;AACvC,eAAO,MAAM,kBAAkB,MAAS,CAAA;AAExC,eAAO,MAAM,0BAA0B,KAAO,CAAA;AAC9C,eAAO,MAAM,oBAAoB,SAAU,CAAA;AAC3C,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAC1C,eAAO,MAAM,kBAAkB,QAA+C,CAAA;AAE9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
@@ -0,0 +1,17 @@
1
+ // Audiofab pedal (MCP2221 + 24LC32 EEPROM) constants.
2
+ // The FV-1 reads 8 programs of 128 × 32-bit words each from the EEPROM.
3
+ export const MCP2221_VENDOR_ID = 0x04D8;
4
+ export const MCP2221_PRODUCT_ID = 0x00DD;
5
+ export const DEFAULT_EEPROM_I2C_ADDRESS = 0x50;
6
+ export const DEFAULT_I2C_CLOCK_HZ = 400000;
7
+ export const DEFAULT_WRITE_PAGE_SIZE = 32;
8
+ export const PROGRAM_SLOT_COUNT = 8;
9
+ export const PROGRAM_SLOT_SIZE_BYTES = 512;
10
+ export const EEPROM_TOTAL_BYTES = PROGRAM_SLOT_COUNT * PROGRAM_SLOT_SIZE_BYTES;
11
+ export function slotBaseAddress(slot) {
12
+ if (slot < 0 || slot >= PROGRAM_SLOT_COUNT) {
13
+ throw new RangeError(`Slot ${slot} out of range [0, ${PROGRAM_SLOT_COUNT})`);
14
+ }
15
+ return slot * PROGRAM_SLOT_SIZE_BYTES;
16
+ }
17
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/pedal/constants.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,wEAAwE;AAExE,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAA;AACvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAExC,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAA;AAC9C,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAO,CAAA;AAC3C,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAA;AAEzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAA;AACnC,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAC1C,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,GAAG,uBAAuB,CAAA;AAE9E,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,kBAAkB,EAAE,CAAC;QAC3C,MAAM,IAAI,UAAU,CAAC,QAAQ,IAAI,qBAAqB,kBAAkB,GAAG,CAAC,CAAA;IAC9E,CAAC;IACD,OAAO,IAAI,GAAG,uBAAuB,CAAA;AACvC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function hashProgram(binary: Uint8Array): Promise<string>;
2
+ /**
3
+ * True if the slot is unprogrammed — i.e. every byte is 0xFF (blank EEPROM).
4
+ */
5
+ export declare function isBlankSlot(binary: Uint8Array): boolean;
6
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/pedal/hash.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrE;AAUD;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAKvD"}
@@ -0,0 +1,28 @@
1
+ // SHA-256 helpers for matching an EEPROM slot's contents against a known program.
2
+ // Works in modern browsers (crypto.subtle) and Node 20+ (globalThis.crypto.subtle).
3
+ export async function hashProgram(binary) {
4
+ // Copy into a fresh, ArrayBuffer-backed view so the type satisfies
5
+ // `BufferSource` on every TS/DOM-lib combo.
6
+ const copy = new Uint8Array(binary.byteLength);
7
+ copy.set(binary);
8
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', copy);
9
+ return bytesToHex(new Uint8Array(digest));
10
+ }
11
+ function bytesToHex(bytes) {
12
+ let out = '';
13
+ for (let i = 0; i < bytes.length; i++) {
14
+ out += bytes[i].toString(16).padStart(2, '0');
15
+ }
16
+ return out;
17
+ }
18
+ /**
19
+ * True if the slot is unprogrammed — i.e. every byte is 0xFF (blank EEPROM).
20
+ */
21
+ export function isBlankSlot(binary) {
22
+ for (let i = 0; i < binary.length; i++) {
23
+ if (binary[i] !== 0xFF)
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/pedal/hash.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,oFAAoF;AAEpF,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAkB;IAClD,mEAAmE;IACnE,4CAA4C;IAC5C,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAC9C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAChB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IACrE,OAAO,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;AAC3C,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB;IACnC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAC/C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,MAAkB;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,OAAO,KAAK,CAAA;IACtC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { MCP2221_VENDOR_ID, MCP2221_PRODUCT_ID, DEFAULT_EEPROM_I2C_ADDRESS, DEFAULT_I2C_CLOCK_HZ, DEFAULT_WRITE_PAGE_SIZE, EEPROM_TOTAL_BYTES, PROGRAM_SLOT_COUNT, PROGRAM_SLOT_SIZE_BYTES, slotBaseAddress, } from './constants.js';
2
+ export { hashProgram, isBlankSlot } from './hash.js';
3
+ export { PedalClient } from './pedal-client.js';
4
+ export type { PedalClientOptions } from './pedal-client.js';
5
+ export { createWebHidBinding } from './webhid-transport.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pedal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { MCP2221_VENDOR_ID, MCP2221_PRODUCT_ID, DEFAULT_EEPROM_I2C_ADDRESS, DEFAULT_I2C_CLOCK_HZ, DEFAULT_WRITE_PAGE_SIZE, EEPROM_TOTAL_BYTES, PROGRAM_SLOT_COUNT, PROGRAM_SLOT_SIZE_BYTES, slotBaseAddress, } from './constants.js';
2
+ export { hashProgram, isBlankSlot } from './hash.js';
3
+ export { PedalClient } from './pedal-client.js';
4
+ export { createWebHidBinding } from './webhid-transport.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pedal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAG/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { MCP2221 } from '@johntalton/mcp2221';
2
+ import { EEPROM } from '@johntalton/eeprom';
3
+ type BindingBufferSource = ArrayBufferLike | ArrayBufferView;
4
+ export interface PedalClientOptions {
5
+ /** EEPROM I²C address. Defaults to 0x50 (standard 24LC32). */
6
+ i2cAddress?: number;
7
+ /** I²C clock speed in Hz. Defaults to 400 kHz. */
8
+ i2cClockHz?: number;
9
+ /** EEPROM write page size in bytes. Defaults to 32. */
10
+ writePageSize?: number;
11
+ }
12
+ export declare class PedalClient {
13
+ readonly device: MCP2221;
14
+ readonly eeprom: EEPROM;
15
+ private constructor();
16
+ /**
17
+ * Open a pedal connection using a pre-built HID stream binding.
18
+ * For browsers, build one with `createWebHidBinding(hidDevice)`.
19
+ */
20
+ static open(binding: ReadableWritablePair<BindingBufferSource, BindingBufferSource>, options?: PedalClientOptions): Promise<PedalClient>;
21
+ /** Read one program slot (512 bytes) from EEPROM. */
22
+ readSlot(slot: number): Promise<Uint8Array>;
23
+ /** Read the entire EEPROM (all 8 slots) in one sweep. */
24
+ readAllSlots(): Promise<Uint8Array[]>;
25
+ /** Write one program slot (512 bytes) to EEPROM and verify the write. */
26
+ writeSlot(slot: number, data: Uint8Array): Promise<void>;
27
+ }
28
+ export {};
29
+ //# sourceMappingURL=pedal-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pedal-client.d.ts","sourceRoot":"","sources":["../../src/pedal/pedal-client.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAY3C,KAAK,mBAAmB,GAAG,eAAe,GAAG,eAAe,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,qBAAa,WAAW;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB,OAAO;IAKP;;;OAGG;WACU,IAAI,CACf,OAAO,EAAE,oBAAoB,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,EACvE,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC;IAkBvB,qDAAqD;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAMjD,yDAAyD;IACnD,YAAY,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAU3C,yEAAyE;IACnE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAc/D"}
@@ -0,0 +1,62 @@
1
+ // High-level pedal API built on the @johntalton/* I2C / MCP2221 / EEPROM stack.
2
+ // Consumers pass in a ReadableWritablePair binding — use `createWebHidBinding`
3
+ // from './webhid-transport' in the browser, or any node-hid bridge on Node.
4
+ import { MCP2221 } from '@johntalton/mcp2221';
5
+ import { I2CBusMCP2221 } from '@johntalton/i2c-bus-mcp2221';
6
+ import { I2CAddressedBus } from '@johntalton/and-other-delights';
7
+ import { EEPROM } from '@johntalton/eeprom';
8
+ import { DEFAULT_EEPROM_I2C_ADDRESS, DEFAULT_I2C_CLOCK_HZ, DEFAULT_WRITE_PAGE_SIZE, EEPROM_TOTAL_BYTES, PROGRAM_SLOT_COUNT, PROGRAM_SLOT_SIZE_BYTES, slotBaseAddress, } from './constants.js';
9
+ export class PedalClient {
10
+ constructor(device, eeprom) {
11
+ this.device = device;
12
+ this.eeprom = eeprom;
13
+ }
14
+ /**
15
+ * Open a pedal connection using a pre-built HID stream binding.
16
+ * For browsers, build one with `createWebHidBinding(hidDevice)`.
17
+ */
18
+ static async open(binding, options = {}) {
19
+ const { i2cAddress = DEFAULT_EEPROM_I2C_ADDRESS, i2cClockHz = DEFAULT_I2C_CLOCK_HZ, writePageSize = DEFAULT_WRITE_PAGE_SIZE, } = options;
20
+ const device = MCP2221.from(binding);
21
+ await device.common.status({
22
+ opaque: `fv1-core::i2c-speed-${i2cClockHz}`,
23
+ i2cClock: i2cClockHz,
24
+ });
25
+ const bus = I2CBusMCP2221.from(device);
26
+ const abus = new I2CAddressedBus(bus, i2cAddress);
27
+ const eeprom = new EEPROM(abus, { writePageSize });
28
+ return new PedalClient(device, eeprom);
29
+ }
30
+ /** Read one program slot (512 bytes) from EEPROM. */
31
+ async readSlot(slot) {
32
+ const address = slotBaseAddress(slot);
33
+ const buffer = await this.eeprom.read(address, PROGRAM_SLOT_SIZE_BYTES);
34
+ return new Uint8Array(buffer);
35
+ }
36
+ /** Read the entire EEPROM (all 8 slots) in one sweep. */
37
+ async readAllSlots() {
38
+ const buffer = await this.eeprom.read(0, EEPROM_TOTAL_BYTES);
39
+ const all = new Uint8Array(buffer);
40
+ const slots = [];
41
+ for (let i = 0; i < PROGRAM_SLOT_COUNT; i++) {
42
+ slots.push(all.slice(i * PROGRAM_SLOT_SIZE_BYTES, (i + 1) * PROGRAM_SLOT_SIZE_BYTES));
43
+ }
44
+ return slots;
45
+ }
46
+ /** Write one program slot (512 bytes) to EEPROM and verify the write. */
47
+ async writeSlot(slot, data) {
48
+ if (data.length !== PROGRAM_SLOT_SIZE_BYTES) {
49
+ throw new Error(`writeSlot expects ${PROGRAM_SLOT_SIZE_BYTES} bytes, got ${data.length}`);
50
+ }
51
+ const address = slotBaseAddress(slot);
52
+ await this.eeprom.write(address, data);
53
+ const verifyBuffer = await this.eeprom.read(address, PROGRAM_SLOT_SIZE_BYTES);
54
+ const verify = new Uint8Array(verifyBuffer);
55
+ for (let i = 0; i < data.length; i++) {
56
+ if (data[i] !== verify[i]) {
57
+ throw new Error(`Slot ${slot} verification failed at byte ${i}`);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ //# sourceMappingURL=pedal-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pedal-client.js","sourceRoot":"","sources":["../../src/pedal/pedal-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ type BindingBufferSource = ArrayBufferLike | ArrayBufferView;
2
+ interface HIDDeviceLike extends EventTarget {
3
+ opened: boolean;
4
+ open(): Promise<void>;
5
+ close(): Promise<void>;
6
+ sendReport(reportId: number, data: BufferSource): Promise<void>;
7
+ }
8
+ /**
9
+ * Create a duplex stream binding for a WebHID MCP2221 device.
10
+ * The returned pair can be passed directly to `MCP2221.from(binding)`.
11
+ */
12
+ export declare function createWebHidBinding(device: HIDDeviceLike): ReadableWritablePair<BindingBufferSource, BindingBufferSource>;
13
+ export {};
14
+ //# sourceMappingURL=webhid-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhid-transport.d.ts","sourceRoot":"","sources":["../../src/pedal/webhid-transport.ts"],"names":[],"mappings":"AAOA,KAAK,mBAAmB,GAAG,eAAe,GAAG,eAAe,CAAA;AAO5D,UAAU,aAAc,SAAQ,WAAW;IACzC,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAKD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,GACpB,oBAAoB,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAuDhE"}
@@ -0,0 +1,75 @@
1
+ // Bridges a WebHID `HIDDevice` to a `ReadableWritablePair` so it can drive
2
+ // `@johntalton/mcp2221`, which expects a generic stream binding.
3
+ //
4
+ // The MCP2221 uses 64-byte reports with report ID 0. Each outgoing write
5
+ // is sent as a single report; each incoming `inputreport` event becomes one
6
+ // 64-byte chunk in the readable side of the pair.
7
+ const REPORT_ID = 0;
8
+ const REPORT_SIZE = 64;
9
+ /**
10
+ * Create a duplex stream binding for a WebHID MCP2221 device.
11
+ * The returned pair can be passed directly to `MCP2221.from(binding)`.
12
+ */
13
+ export function createWebHidBinding(device) {
14
+ const queue = [];
15
+ const waiters = [];
16
+ const onInputReport = (event) => {
17
+ const ev = event;
18
+ const src = new Uint8Array(ev.data.buffer, ev.data.byteOffset, ev.data.byteLength);
19
+ // Copy into a fresh, full-size (64-byte) buffer so downstream consumers
20
+ // always see a stable chunk size regardless of how the browser packages it.
21
+ const chunk = new Uint8Array(REPORT_SIZE);
22
+ chunk.set(src.subarray(0, Math.min(src.length, REPORT_SIZE)));
23
+ const waiter = waiters.shift();
24
+ if (waiter)
25
+ waiter(chunk);
26
+ else
27
+ queue.push(chunk);
28
+ };
29
+ device.addEventListener('inputreport', onInputReport);
30
+ const readable = new ReadableStream({
31
+ type: 'bytes',
32
+ pull(controller) {
33
+ const byob = controller;
34
+ if (queue.length > 0) {
35
+ byob.enqueue(queue.shift());
36
+ return;
37
+ }
38
+ return new Promise(resolve => {
39
+ waiters.push(chunk => {
40
+ byob.enqueue(chunk);
41
+ resolve();
42
+ });
43
+ });
44
+ },
45
+ cancel() {
46
+ device.removeEventListener('inputreport', onInputReport);
47
+ },
48
+ });
49
+ const writable = new WritableStream({
50
+ async write(chunk) {
51
+ const bytes = toBytes(chunk);
52
+ // MCP2221 always expects 64 bytes of payload. Always build a fresh,
53
+ // ArrayBuffer-backed view so it satisfies the DOM BufferSource type.
54
+ const padded = new Uint8Array(REPORT_SIZE);
55
+ padded.set(bytes.subarray(0, Math.min(bytes.length, REPORT_SIZE)));
56
+ await device.sendReport(REPORT_ID, padded);
57
+ },
58
+ close() {
59
+ device.removeEventListener('inputreport', onInputReport);
60
+ },
61
+ abort() {
62
+ device.removeEventListener('inputreport', onInputReport);
63
+ },
64
+ });
65
+ return { readable, writable };
66
+ }
67
+ function toBytes(chunk) {
68
+ if (chunk instanceof Uint8Array)
69
+ return chunk;
70
+ if (ArrayBuffer.isView(chunk)) {
71
+ return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
72
+ }
73
+ return new Uint8Array(chunk);
74
+ }
75
+ //# sourceMappingURL=webhid-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhid-transport.js","sourceRoot":"","sources":["../../src/pedal/webhid-transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audiofab-io/fv1-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "FV-1 DSP toolchain: assembler, simulator, and Intel HEX parser",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "import": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts"
12
+ },
13
+ "./pedal": {
14
+ "import": "./dist/pedal/index.js",
15
+ "types": "./dist/pedal/index.d.ts"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -19,6 +23,12 @@
19
23
  "clean": "rm -rf dist",
20
24
  "prepublishOnly": "npm run build"
21
25
  },
26
+ "dependencies": {
27
+ "@johntalton/and-other-delights": "^8.6.4",
28
+ "@johntalton/eeprom": "^1.1.1",
29
+ "@johntalton/i2c-bus-mcp2221": "^4.2.1",
30
+ "@johntalton/mcp2221": "^4.0.4"
31
+ },
22
32
  "devDependencies": {
23
33
  "typescript": "^5.7.0"
24
34
  },