Refactor Vite configuration for improved chunking strategy and caching. Enhance DeviceContext with new upgradePeqChange method for PEQ updates. Update audio page to support localized labels and dynamic volume control. Introduce new filter types and improve EQPage functionality with enhanced frequency response visualization.
This commit is contained in:
@@ -37,7 +37,13 @@ export function decodeCustomBase64(encoded: string): string {
|
||||
}
|
||||
|
||||
export function encodeCustomBase64(data: string): string {
|
||||
const standard = btoa(data);
|
||||
// btoa only supports Latin1; convert UTF-8 bytes to a binary string first.
|
||||
const bytes = new TextEncoder().encode(data);
|
||||
let binary = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
const standard = btoa(binary);
|
||||
let translated = "";
|
||||
for (let i = 0; i < standard.length; i++) {
|
||||
const char = standard.charAt(i);
|
||||
@@ -61,6 +67,8 @@ export interface DeviceState {
|
||||
output: number;
|
||||
audioFormat: string;
|
||||
pcm: number;
|
||||
hdmimutepolar: number;
|
||||
hdmiType: number;
|
||||
vu: number;
|
||||
vuSensor: number;
|
||||
vu_count: number;
|
||||
@@ -115,12 +123,29 @@ export interface PeqFilter {
|
||||
|
||||
export interface PeqState {
|
||||
filters: PeqFilter[];
|
||||
peqSelect?: number;
|
||||
peq?: Array<{
|
||||
name: string;
|
||||
filters?: PeqFilter[];
|
||||
filters?: PeqFilter[] | string;
|
||||
autoPre?: number;
|
||||
preamp?: number;
|
||||
canDel?: number;
|
||||
brand?: string;
|
||||
model?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/** POST body for `/dev/info.cgi` — full peq preset update (custom base64 `json` field). */
|
||||
export interface PeqChangePayload {
|
||||
peqChange: {
|
||||
name: string;
|
||||
filters: PeqFilter[];
|
||||
autoPre?: number;
|
||||
preamp?: number;
|
||||
canDel?: number;
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Input/Output Labels
|
||||
// ============================================================
|
||||
@@ -183,6 +208,28 @@ export class LuxsinAPI {
|
||||
});
|
||||
}
|
||||
|
||||
/** Full peq preset: name, filters, autoPre, preamp, canDel — same encoding as legacy `upgradePeq`. */
|
||||
async upgradePeqChange(body: PeqChangePayload): Promise<void> {
|
||||
const payload = JSON.stringify(body);
|
||||
const encoded = encodeCustomBase64(payload);
|
||||
await fetch(`${this.baseUrl}/dev/info.cgi`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: `json=${encodeURIComponent(encoded)}`,
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove one or more headphone PEQ profiles. */
|
||||
async removePeq(names: string[]): Promise<void> {
|
||||
const payload = JSON.stringify({ peqRemove: names });
|
||||
const encoded = encodeCustomBase64(payload);
|
||||
await fetch(`${this.baseUrl}/dev/info.cgi`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
|
||||
body: `json=${encodeURIComponent(encoded)}`,
|
||||
});
|
||||
}
|
||||
|
||||
setVolume(volume: number) { return this.setSetting({ volume }); }
|
||||
setInput(input: number) { return this.setSetting({ input }); }
|
||||
setOutput(output: number) { return this.setSetting({ output }); }
|
||||
@@ -227,6 +274,8 @@ export const MOCK_DEVICE_STATE: DeviceState = {
|
||||
output: 0,
|
||||
audioFormat: "PCM 44.1 KHz",
|
||||
pcm: 1,
|
||||
hdmimutepolar: 0,
|
||||
hdmiType: 0,
|
||||
vu: 0,
|
||||
vuSensor: 0,
|
||||
vu_count: 5,
|
||||
|
||||
@@ -13,8 +13,11 @@ export const TYPE_BANDPASS = 2;
|
||||
export const TYPE_NOTCH = 3;
|
||||
export const TYPE_ALLPASS = 7;
|
||||
|
||||
// Filter type mapping
|
||||
export function getFilterType(typeName: string): number {
|
||||
// Filter type mapping (device uses numeric type; pass-through when already a number)
|
||||
export function getFilterType(typeName: string | number): number {
|
||||
if (typeof typeName === "number" && Number.isFinite(typeName)) {
|
||||
return typeName;
|
||||
}
|
||||
switch (typeName) {
|
||||
case 'LPF':
|
||||
case 'LOW_PASS':
|
||||
@@ -309,10 +312,53 @@ export function visualizeResponse(coeffList: Coeff[], fs: number): [number[], nu
|
||||
}
|
||||
});
|
||||
|
||||
if (validCoeffList.length === 0) {
|
||||
const flat = f.map(() => 0);
|
||||
return [semilogf, flat];
|
||||
}
|
||||
|
||||
const overall = getFreqznList(validCoeffList, fs, f);
|
||||
return [semilogf, overall];
|
||||
}
|
||||
|
||||
/** Same log-spaced grid as visualizeResponse (20 Hz … 20 kHz, 349 points). */
|
||||
export function getPeqLogSpacedFreqs(): number[] {
|
||||
const n = 349;
|
||||
const startF = 20;
|
||||
const logStep = (Math.log10(20000) - Math.log10(20)) / n;
|
||||
const step = Math.pow(10, logStep);
|
||||
const f: number[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
f.push(startF * Math.pow(step, i));
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
export type PeqBandForResponse = {
|
||||
enabled: boolean;
|
||||
gain: number;
|
||||
freq: number;
|
||||
q: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Combined magnitude response (dB) per band — same pipeline as legacy:
|
||||
* getSectionsMatrix(...) per filter, then cascade via getFreqznList.
|
||||
*/
|
||||
export function computePeqMagnitudeDb(bands: PeqBandForResponse[], fs: number): number[] {
|
||||
const f = getPeqLogSpacedFreqs();
|
||||
const list: Coeff[] = [];
|
||||
bands.forEach((b) => {
|
||||
if (!b.enabled) return;
|
||||
list.push(getSectionsMatrix(b.gain, b.freq, b.q, getFilterType(b.type), false, fs));
|
||||
});
|
||||
if (list.length === 0) {
|
||||
return f.map(() => 0);
|
||||
}
|
||||
return getFreqznList(list, fs, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ECharts options for frequency response chart
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user