forked from MRSallee/CrinGraph
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fiioUsbHidHandler.js
402 lines (344 loc) · 14.9 KB
/
fiioUsbHidHandler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
//
// Copyright 2024 : Pragmatic Audio
//
// Define the shared logic for JadeAudio / FiiO devices - Each manufacturer will have slightly
// different code so best to each have a separate 'module'
const PEQ_FILTER_COUNT = 24;
const PEQ_GLOBAL_GAIN = 23;
const PEQ_FILTER_PARAMS = 21;
const PEQ_PRESET_SWITCH = 22;
const PEQ_SAVE_TO_DEVICE = 25;
const SET_HEADER1 = 0xAA;
const SET_HEADER2 = 0x0A;
const GET_HEADER1 = 0xBB;
const GET_HEADER2 = 0x0B;
const END_HEADERS = 0xEE;
const fiioUsbHID = {
connect: async function(device) {
try {
if (!device.opened) {
await device.open();
}
console.log("FiiO Device connected");
} catch (error) {
console.error("Failed to connect to FiiO Device:", error);
throw error;
}
},
getCurrentSlot: async function(device) {
try {
let currentSlot = -99;
device.oninputreport = async (event) => {
const data = new Uint8Array(event.data.buffer);
if (data[0] === GET_HEADER1 && data[1] === GET_HEADER2) {
switch (data[4]) {
case PEQ_PRESET_SWITCH:
currentSlot = handleEqPreset(data, device);
break;
default:
console.log("Unhandled data type:", data[4]);
}
}
};
await getPresetPeq(device);
// Wait at most 10 seconds for filters to be populated
const result = await waitForFilters(() => { return currentSlot > -99}, device, 10000, (device) => (
currentSlot
));
return result;
} catch (error) {
console.error("Failed to pull data from FiiO Device:", error);
throw error;
}
},
pushToDevice: async function(device, slot, preamp, filters) {
try {
await setGlobalGain(device, preamp);
const maxFilters = getModelConfig(device).maxFilters;
const maxFiltersToUse = Math.min(filters.length, maxFilters);
await setPeqCounter(device, maxFiltersToUse);
for (let filterIdx = 0; filterIdx < maxFiltersToUse; filterIdx++) {
const filter = filters[filterIdx];
await setPeqParams(device, filterIdx, filter.freq, filter.gain, filter.q, convertFromFilterType(filter.type));
}
saveToDevice(device, slot);
console.log("PEQ filters pushed successfully.");
} catch (error) {
console.error("Failed to push data to FiiO Device:", error);
throw error;
}
},
pullFromDevice: async function(device, slot) {
try {
const filters = [];
let peqCount = 0;
let globalGain = 0;
let currentSlot = 0;
device.oninputreport = async (event) => {
const data = new Uint8Array(event.data.buffer);
if (data[0] === GET_HEADER1 && data[1] === GET_HEADER2) {
switch (data[4]) {
case PEQ_FILTER_COUNT:
peqCount = handlePeqCounter(data, device);
break;
case PEQ_FILTER_PARAMS:
handlePeqParams(data, device, filters);
break;
case PEQ_GLOBAL_GAIN:
globalGain = handleGlobalGain(data, device);
break;
case PEQ_PRESET_SWITCH:
currentSlot = handleEqPreset(data, device);
break;
case PEQ_SAVE_TO_DEVICE:
savedEQ(data, device);
break;
default:
console.log("Unhandled data type:", data[4]);
}
}
};
await getPresetPeq(device);
await getPeqCounter(device);
await getGlobalGain(device);
// Wait at most 10 seconds for filters to be populated
const result = await waitForFilters(() => { return filters.length == peqCount}, device, 10000, (device) => ({
filters: filters,
globalGain: globalGain,
currentSlot: currentSlot,
deviceDetails: getModelConfig(device)
}));
return result;
} catch (error) {
console.error("Failed to pull data from FiiO Device:", error);
throw error;
}
},
enablePEQ: async function(device, enable, slotId) {
var deviceModel = getModelConfig(device);
if (enable) { // take the slotId we are given and switch to it
await setPresetPeq(device, slotId);
} else {
await setPresetPeq(device, deviceModel.maxFilters);
}
}
};
// Helper Functions
async function setPeqParams(device, filterIndex, fc, gain, q, filterType) {
const [frequencyHigh, frequencyLow] = splitUnsignedValue(fc);
const gainValue = Math.round(gain * 10);
const [gainHigh, gainLow] = splitSignedValue(gainValue);
const qFactorValue = Math.round(q * 100);
const [qFactorHigh, qFactorLow] = splitUnsignedValue(qFactorValue);
const packet = [
SET_HEADER1, SET_HEADER2, 0, 0, PEQ_FILTER_PARAMS, 8,
filterIndex, gainHigh, gainLow,
frequencyHigh, frequencyLow,
qFactorHigh, qFactorLow,
filterType, 0, END_HEADERS
];
const data = new Uint8Array(packet);
const reportId = device.collections[0].outputReports[0].reportId;
await device.sendReport(reportId, data);
}
async function setPresetPeq(device, presetId ) { // Default to 0 if not specified
const packet = [
SET_HEADER1, SET_HEADER2, 0, 0, PEQ_PRESET_SWITCH, 1,
presetId, 0, END_HEADERS
];
const data = new Uint8Array(packet);
const reportId = device.collections[0].outputReports[0].reportId;
await device.sendReport(reportId, data);
}
async function setGlobalGain(device, gain) {
const globalGain = Math.round(gain * 100);
const gainBytes = toBytePair(globalGain);
const packet = [
SET_HEADER1, SET_HEADER2, 0, 0, PEQ_GLOBAL_GAIN, 2,
gainBytes[0], gainBytes[1], 0, END_HEADERS
];
const data = new Uint8Array(packet);
const reportId = device.collections[0].outputReports[0].reportId;
await device.sendReport(reportId, data);
}
async function setPeqCounter(device, counter) {
const packet = [
SET_HEADER1, SET_HEADER2, 0, 0, PEQ_FILTER_COUNT, 1,
counter, 0, END_HEADERS
];
const data = new Uint8Array(packet);
const reportId = device.collections[0].outputReports[0].reportId;
await device.sendReport(reportId, data);
}
function convertFromFilterType(filterType) {
const mapping = { "PK": 0, "LSQ": 1, "HSQ": 2 };
return mapping[filterType] !== undefined ? mapping[filterType] : 0;
}
function convertToFilterType(datum) {
switch (datum) {
case 0:
return "PK";
case 1:
return "LSQ";
case 2:
return "HSQ";
default:
return "PK";
}
}
function toBytePair(value) {
return [
value & 0xFF,
(value & 0xFF00) >> 8
];
}
function splitSignedValue(value) {
const signedValue = value < 0 ? value + 65536 : value;
return [
(signedValue >> 8) & 0xFF,
signedValue & 0xFF
];
}
function splitUnsignedValue(value) {
return [
(value >> 8) & 0xFF,
value & 0xFF
];
}
function combineBytes(lowByte, highByte) {
return (highByte << 8) | lowByte;
}
function signedCombine(highByte, lowByte) {
const combined = (highByte << 8) | lowByte;
return combined > 32767 ? combined - 65536 : combined;
}
function getGlobalGain(device) {
const packet = [GET_HEADER1, GET_HEADER2, 0, 0, PEQ_GLOBAL_GAIN, 0, 0, END_HEADERS];
const data = new Uint8Array(packet);
console.log("getGlobalGain() Send data:", data);
const reportId = device.collections[0].outputReports[0].reportId;
device.sendReport(reportId, data);
}
function getPeqCounter(device) {
const packet = [GET_HEADER1, GET_HEADER2, 0, 0, PEQ_FILTER_COUNT, 0, 0, END_HEADERS];
const data = new Uint8Array(packet);
console.log("getPeqCounter() Send data:", data);
const reportId = device.collections[0].outputReports[0].reportId;
device.sendReport(reportId, data);
}
function getPeqParams(device, filterIndex) {
const packet = [GET_HEADER1, GET_HEADER2, 0, 0, PEQ_FILTER_PARAMS, 1, filterIndex, 0, END_HEADERS];
const data = new Uint8Array(packet);
console.log("getPeqParams() Send data:", data);
const reportId = device.collections[0].outputReports[0].reportId;
device.sendReport(reportId, data);
}
function getPresetPeq(device) {
const packet = [GET_HEADER1, GET_HEADER2, 0, 0, PEQ_PRESET_SWITCH, 0, 0, END_HEADERS];
const data = new Uint8Array(packet);
console.log("getPresetPeq() Send data:", data);
const reportId = device.collections[0].outputReports[0].reportId;
device.sendReport(reportId, data);
}
function saveToDevice(device, slotId) {
const packet = [SET_HEADER1, SET_HEADER2, 0, 0, PEQ_SAVE_TO_DEVICE, 1, slotId, 0, END_HEADERS];
const data = new Uint8Array(packet);
console.log("saveToDevice() reportId Send data:", data);
const reportId = device.collections[0].outputReports[0].reportId;
device.sendReport(reportId, data);
}
function handleGlobalGain(data, device) {
globalGain = combineBytes(data[7], data[6]) / 100;
}
function handlePeqCounter(data, device) {
peqCount = data[6];
console.log("***********oninputreport peq counter=", peqCount);
if (peqCount > 0) {
processPeqCount(device);
}
return peqCount;
}
function processPeqCount(device) {
console.log("PEQ Counter:", peqCount);
// Fetch individual PEQ settings based on count
for (let i = 0; i < peqCount; i++) {
getPeqParams(device, i);
}
}
function handlePeqParams(data, device, filters) {
const filter = data[6];
const gain = signedCombine(data[7], data[8]) / 10;
const frequency = (data[9] << 8) | data[10];
const qFactor = ((data[11] << 8) | data[12]) / 100 || 1;
const filterType = convertToFilterType(data[13]);
console.log(`Filter ${filter}: Gain=${gain}, Frequency=${frequency}, Q=${qFactor}, Type=${filterType}`);
filters[filter] = {
type: filterType,
freq: frequency,
q: qFactor,
gain: gain,
disabled: false
};
}
function handleGlobalGain(data, device) {
const globalGain = combineBytes(data[7], data[6]) / 100;
console.log("Global Gain:", globalGain);
// You can store or apply the global gain as needed
return globalGain;
}
function handleEqPreset(data, device) {
const presetId = data[6];
console.log("EQ Preset ID:", presetId);
if (presetId > getModelConfig(device).availableSlots.length) {
return -1; // with JA11 slot 4 == Off
}
// Handle preset switch if necessary
return presetId;
}
function savedEQ(data, device) {
const slotId = data[6];
console.log("EQ Slot ID:", slotId);
// Handle slot enablement if necessary
}
function getModelConfig(device) {
const configuration = modelConfiguration[device.productName];
return configuration || modelConfiguration["default"];
}
const modelConfiguration = {
"default": { minGain: -12, maxGain: 12, maxFilters: 5, firstWritableEQSlot: -1, maxWritableEQSlots: 0, availableSlots:[] },
"FIIO KA17": { minGain: -12, maxGain: 12, maxFilters: 10, firstWritableEQSlot: 7, maxWritableEQSlots: 3, availableSlots:[{id:0,name:"Jazz"}, {id:1,name:"Pop"}, {id:2,name:"Rock"}, {id:3,name:"Dance"}, {id:5,name:"R&B"}, {id:6,name:"Classic"},{id:7,name:"Hip-hop"}, {id:4,name:"USER1"}, {id:8,name:"USER2"}, {id:9,name:"USER3"}] },
"JadeAudio JA11": { minGain: -12, maxGain: 12, maxFilters: 5, firstWritableEQSlot: 3, maxWritableEQSlots: 1, availableSlots:[{id:0,name:"Vocal"}, {id:1,name:"Classic"}, {id:2,name:"Bass"}, {id:3,name:"USER1"}] },
"FIIO LS-TC2": { minGain: -12, maxGain: 12, maxFilters: 5, firstWritableEQSlot: 3, maxWritableEQSlots: 1, availableSlots:[{id:0,name:"Vocal"}, {id:1,name:"Classic"}, {id:2,name:"Bass"}, {id:3,name:"Dance"}, {id:4,name:"R&B"}, {id:5,name:"Classic"},{id:6,name:"Hip-hop"}, {id:160,name:"USER1"}] },
"FIIO RETRO NANO": { minGain: -12, maxGain: 12, maxFilters: 5, firstWritableEQSlot: 3, maxWritableEQSlots: 1, availableSlots:[{id:0,name:"Vocal"}, {id:1,name:"Classic"}, {id:2,name:"Bass"}, {id:3,name:"Dance"}, {id:4,name:"R&B"}, {id:5,name:"Classic"},{id:6,name:"Hip-hop"}, {id:160,name:"USER1"}, {id:161,name:"USER2"}, {id:162,name:"USER3"}] },
"FIIO BTR13": { minGain: -12, maxGain: 12, maxFilters: 10, firstWritableEQSlot: 7, maxWritableEQSlots: 3 , availableSlots:[{id:0,name:"Jazz"}, {id:1,name:"Pop"}, {id:2,name:"Rock"}, {id:3,name:"Dance"}, {id:4,name:"R&B"}, {id:5,name:"Classic"},{id:6,name:"Hip-hop"}, {id:7,name:"USER1"}, {id:8,name:"USER2"}, {id:9,name:"USER3"}]},
"FIIO BTR17": { minGain: -12, maxGain: 12, maxFilters: 10, firstWritableEQSlot: 7, maxWritableEQSlots: 3 , availableSlots:[{id:0,name:"Jazz"}, {id:1,name:"Pop"}, {id:2,name:"Rock"}, {id:3,name:"Dance"}, {id:4,name:"R&B"}, {id:5,name:"Classic"},{id:6,name:"Hip-hop"}, {id: 160, name: "USER1"}, {id:161,name:"USER2"}, {id:162,name:"USER3"} , {id: 160, name: "USER1"}, {id:161,name:"USER2"}, {id:162,name:"USER3"}, {id: 163, name: "USER4"}, {id:164,name:"USER5"}, {id:165,name:"USER6"}, {id: 166, name: "USER7"}, {id:167,name:"USER8"}, {id:168,name:"USER9"}, {id:169,name:"USER10"}]},
"FIIO KA15": { minGain: -12, maxGain: 12, maxFilters: 10, firstWritableEQSlot: 7, maxWritableEQSlots: 3, availableSlots:[{id:0,name:"Jazz"}, {id:1,name:"Pop"}, {id:2,name:"Rock"}, {id:3,name:"Dance"}, {id:4,name:"R&B"}, {id:5,name:"Classic"},{id:6,name:"Hip-hop"}, {id:7,name:"USER1"}, {id:8,name:"USER2"}, {id:9,name:"USER3"}] }
};
// Utility function to wait for a condition or timeout
function waitForFilters(condition, device, timeout, callback) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
if (!condition()) {
console.warn("Timeout reached before data returned?");
reject(callback(device));
} else {
resolve(callback(device));
}
}, timeout);
// Check every 100 milliseconds if everything is ready based on condition method !!
const interval = setInterval(() => {
if (condition()) {
clearTimeout(timer);
clearInterval(interval);
resolve(callback(device));
}
}, 100);
});
}
// Export fiioUsbHID for use in Node.js or other modules
if (typeof window !== 'undefined') {
window.fiioUsbHID = fiioUsbHID;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = fiioUsbHID;
}