feat: add fixed-point coefficient adjustment controls with shift-key mouse wheel support

This commit is contained in:
ws50529
2026-05-13 15:56:04 +08:00
parent 9a65391afd
commit caaccbd760
4 changed files with 335 additions and 85 deletions
+184
View File
@@ -55,9 +55,16 @@ export default {
aFineRepeatDelayTimer: null,
aFineRepeatTimer: null,
aFineDrag: null,
shiftBitRepeatDelayTimer: null,
shiftBitRepeatTimer: null,
shiftBitDrag: null,
fixedAFineStep: 1,
fixedAFineDrag: null,
fixedAFineRepeatDelayTimer: null,
fixedAFineRepeatTimer: null,
isTouchInput: false,
showAdvancedAdjustment: false,
activeCoeffAdjustment: null,
}
},
@@ -83,6 +90,7 @@ export default {
nums.forEach((n, i) => {
this.fixedOverrides.b[i] = Math.round(n);
});
this.debouncedUpdateBode();
}
},
a_int_str: {
@@ -95,6 +103,7 @@ export default {
nums.forEach((n, i) => {
this.fixedOverrides.a[i] = Math.round(n);
});
this.debouncedUpdateBode();
}
},
fixedPointCoeffs() {
@@ -142,6 +151,12 @@ export default {
}
return false;
},
isFixedPointDirty() {
return this.shiftBitsB !== DEFAULT_SHIFT_BITS
|| this.shiftBitsA !== DEFAULT_SHIFT_BITS
|| Object.keys(this.fixedOverrides.b).length > 0
|| Object.keys(this.fixedOverrides.a).length > 0;
},
currentA1() {
const a = this.parseCoeffs(this.a_str);
return a[1] || 0;
@@ -161,6 +176,27 @@ export default {
},
currentA1A2Sum() {
return this.currentA1 + this.currentA2;
},
fixedA0Int() {
return Math.pow(2, this.shiftBitsA);
},
fixedCurrentA1() {
return this.fixedPointCoeffs.a[1]?.val || 0;
},
fixedCurrentA2() {
return this.fixedPointCoeffs.a[2]?.val || 0;
},
fixedCurrentDelta() {
return (this.fixedCurrentA1 + this.fixedCurrentA2 + this.fixedA0Int) / 2;
},
fixedCurrentR() {
return (this.fixedCurrentA2 - this.fixedCurrentA1 - this.fixedA0Int) / 2;
},
fixedCurrentA1A2Diff() {
return this.fixedCurrentA1 - this.fixedCurrentA2;
},
fixedCurrentA1A2Sum() {
return this.fixedCurrentA1 + this.fixedCurrentA2;
}
},
mounted() {
@@ -182,7 +218,10 @@ export default {
window.removeEventListener('pointerdown', this.rememberPointerType);
this.stopA1A2Repeat();
this.stopAFineDrag();
this.stopShiftBitRepeat();
this.stopShiftBitDrag();
this.stopFixedA1A2Repeat();
this.stopFixedAFineDrag();
},
methods: {
rememberPointerType(event) {
@@ -234,10 +273,12 @@ export default {
resetSlidersB() {
this.bSliders = [0, 0, 0, 0, 0, 0, 0];
this.b_str = this.baseB.map(x => parseFloat(x.toPrecision(10))).join(', ');
this.debouncedUpdateBode();
},
resetSlidersA() {
this.aSliders = [0, 0, 0, 0, 0, 0];
this.a_str = this.baseA.map(x => parseFloat(x.toPrecision(10))).join(', ');
this.debouncedUpdateBode();
},
clearManualAdjustments() {
this.bSliders = [0, 0, 0, 0, 0, 0, 0];
@@ -246,6 +287,7 @@ export default {
this.b_str = this.baseB.map(x => parseFloat((x * gain).toPrecision(10))).join(', ');
this.a_str = this.baseA.map(x => parseFloat(x.toPrecision(10))).join(', ');
this.showAdvancedAdjustment = false;
this.activeCoeffAdjustment = null;
this.calcSlidersFromText();
this.updateBodePlot({ switchToChart: false });
},
@@ -257,6 +299,7 @@ export default {
}
this.b_str = currentB.map(x => parseFloat(x.toPrecision(10))).join(', ');
this.a_str = currentA.map(x => parseFloat(x.toPrecision(10))).join(', ');
this.debouncedUpdateBode();
},
updateFromText() {
// 不再呼叫 syncBaseFromText(),保留基礎設計 (baseB/baseA) 作為參考點
@@ -279,11 +322,22 @@ export default {
formatAFineDelta(step = this.aFineStep) {
return this.roundToStep(step, step).toFixed(this.stepDecimals(step));
},
formatFixedFineCoeff(value) {
if (!Number.isFinite(value)) return '0';
return Number.isInteger(value) ? String(value) : String(value);
},
formatFixedAFineDelta(step = this.fixedAFineStep) {
return String(step);
},
adjustAFineStep(direction) {
const nextStep = this.aFineStep * (direction > 0 ? 0.1 : 10);
const clampedStep = Math.min(1, Math.max(1e-9, nextStep));
this.aFineStep = Number(clampedStep.toPrecision(12));
},
adjustFixedAFineStep(direction) {
const nextStep = this.fixedAFineStep * (direction > 0 ? 0.1 : 10);
this.fixedAFineStep = Math.max(1, Math.min(1000000000, Math.round(nextStep)));
},
setDeltaR(delta, r) {
const nextDelta = Number(delta);
const nextR = Number(r);
@@ -308,6 +362,11 @@ export default {
}
},
handleAFineWheel(target, event) {
if (!event.shiftKey) {
event.currentTarget?.blur?.();
return;
}
event.preventDefault();
const direction = event.deltaY < 0 ? 1 : -1;
const steps = Math.max(1, Math.round(Math.abs(event.deltaY) / 80));
this.adjustAFineValue(target, direction * steps);
@@ -337,6 +396,81 @@ export default {
window.removeEventListener('pointerup', this.stopAFineDrag);
window.removeEventListener('pointercancel', this.stopAFineDrag);
},
setFixedDeltaR(delta, r) {
const nextDelta = Number(delta);
const nextR = Number(r);
if (!Number.isFinite(nextDelta) || !Number.isFinite(nextR)) return;
this.fixedOverrides.a[1] = Math.round(-this.fixedA0Int - nextR + nextDelta);
this.fixedOverrides.a[2] = Math.round(nextR + nextDelta);
this.debouncedUpdateBode();
},
setFixedAFineDirect(target, rawValue) {
const nextValue = parseFloat(rawValue);
if (!Number.isFinite(nextValue)) return;
if (target === 'delta') {
this.setFixedDeltaR(nextValue, this.fixedCurrentR);
} else if (target === 'r') {
this.setFixedDeltaR(this.fixedCurrentDelta, nextValue);
}
},
adjustFixedAFineValue(target, steps) {
const delta = steps * this.fixedAFineStep;
if (target === 'delta') {
this.setFixedDeltaR(this.fixedCurrentDelta + delta, this.fixedCurrentR);
} else if (target === 'r') {
this.setFixedDeltaR(this.fixedCurrentDelta, this.fixedCurrentR + delta);
}
},
handleFixedAFineWheel(target, event) {
if (!event.shiftKey) {
event.currentTarget?.blur?.();
return;
}
event.preventDefault();
const direction = event.deltaY < 0 ? 1 : -1;
const steps = Math.max(1, Math.round(Math.abs(event.deltaY) / 80));
this.adjustFixedAFineValue(target, direction * steps);
},
startFixedA1A2Repeat(target, direction) {
this.stopFixedA1A2Repeat();
this.adjustFixedAFineValue(target, direction);
this.fixedAFineRepeatDelayTimer = setTimeout(() => {
this.fixedAFineRepeatTimer = setInterval(() => {
this.adjustFixedAFineValue(target, direction);
}, 80);
}, 350);
},
stopFixedA1A2Repeat() {
clearTimeout(this.fixedAFineRepeatDelayTimer);
clearInterval(this.fixedAFineRepeatTimer);
this.fixedAFineRepeatDelayTimer = null;
this.fixedAFineRepeatTimer = null;
},
startFixedAFineDrag(target, event) {
if (event.pointerType !== 'touch') return;
event.preventDefault();
this.fixedAFineDrag = { target, lastY: event.clientY, remainder: 0 };
window.addEventListener('pointermove', this.moveFixedAFineDrag, { passive: false });
window.addEventListener('pointerup', this.stopFixedAFineDrag);
window.addEventListener('pointercancel', this.stopFixedAFineDrag);
},
moveFixedAFineDrag(event) {
if (!this.fixedAFineDrag) return;
event.preventDefault();
const delta = this.fixedAFineDrag.lastY - event.clientY;
this.fixedAFineDrag.lastY = event.clientY;
this.fixedAFineDrag.remainder += delta;
const steps = Math.trunc(this.fixedAFineDrag.remainder / 18);
if (steps === 0) return;
this.fixedAFineDrag.remainder -= steps * 18;
this.adjustFixedAFineValue(this.fixedAFineDrag.target, steps);
},
stopFixedAFineDrag() {
this.fixedAFineDrag = null;
window.removeEventListener('pointermove', this.moveFixedAFineDrag);
window.removeEventListener('pointerup', this.stopFixedAFineDrag);
window.removeEventListener('pointercancel', this.stopFixedAFineDrag);
},
normalizeShiftBits(rawValue) {
const nextValue = parseInt(rawValue, 10);
if (!Number.isFinite(nextValue)) return 0;
@@ -350,16 +484,37 @@ export default {
this.shiftBitsA = nextValue;
}
this.clearFixedOverrides(type);
this.debouncedUpdateBode();
},
adjustShiftBits(type, steps) {
const current = type === 'b' ? this.shiftBitsB : this.shiftBitsA;
this.setShiftBits(type, current + steps);
},
handleShiftBitWheel(type, event) {
if (!event.shiftKey) {
event.currentTarget?.blur?.();
return;
}
event.preventDefault();
const direction = event.deltaY < 0 ? 1 : -1;
const steps = Math.max(1, Math.round(Math.abs(event.deltaY) / 80));
this.adjustShiftBits(type, direction * steps);
},
startShiftBitRepeat(type, direction) {
this.stopShiftBitRepeat();
this.adjustShiftBits(type, direction);
this.shiftBitRepeatDelayTimer = setTimeout(() => {
this.shiftBitRepeatTimer = setInterval(() => {
this.adjustShiftBits(type, direction);
}, 80);
}, 350);
},
stopShiftBitRepeat() {
clearTimeout(this.shiftBitRepeatDelayTimer);
clearInterval(this.shiftBitRepeatTimer);
this.shiftBitRepeatDelayTimer = null;
this.shiftBitRepeatTimer = null;
},
startShiftBitDrag(type, event) {
if (event.pointerType !== 'touch') return;
event.preventDefault();
@@ -392,6 +547,7 @@ export default {
a[2] = Number(a2);
this.a_str = a.map(x => Number.isFinite(x) ? Number(x).toString() : '0').join(', ');
this.calcSlidersFromText();
this.debouncedUpdateBode();
},
adjustA1A2(mode, direction) {
const step = this.aFineStep * direction;
@@ -420,6 +576,19 @@ export default {
enableAdvancedAdjustment() {
this.calcSlidersFromText();
this.showAdvancedAdjustment = true;
this.activeCoeffAdjustment = 'a1a2';
},
toggleCoeffAdjustment(panel) {
if (this.activeCoeffAdjustment === panel) {
this.activeCoeffAdjustment = null;
this.showAdvancedAdjustment = false;
return;
}
this.activeCoeffAdjustment = panel;
this.showAdvancedAdjustment = panel === 'a1a2';
if (panel === 'a1a2') {
this.calcSlidersFromText();
}
},
calcSlidersFromText() {
const gain = this.systemGain || 1.0;
@@ -463,6 +632,7 @@ export default {
// 當文字改變時,反向計算滑桿位置
this.calcSlidersFromText();
this.debouncedUpdateBode();
},
onlyNumberKey(e, allowDot = true) {
// 允許的控制鍵
@@ -488,6 +658,13 @@ export default {
}
this.fixedOverrides = { a: {}, b: {} };
},
resetFixedPointSettings() {
this.shiftBitsB = DEFAULT_SHIFT_BITS;
this.shiftBitsA = DEFAULT_SHIFT_BITS;
this.fixedAFineStep = 1;
this.clearFixedOverrides();
this.updateBodePlot({ switchToChart: false });
},
onFsChange() {
if (this.filterType !== MANUAL_FILTER_TYPE) {
this.applyFilterDesign();
@@ -520,6 +697,7 @@ export default {
this.outOfRangeB = [false, false, false, false, false, false, false];
this.outOfRangeA = [false, false, false, false, false, false, false];
this.showAdvancedAdjustment = false;
this.activeCoeffAdjustment = null;
if (this.filterType !== MANUAL_FILTER_TYPE) {
this.params = cloneDefaultParams();
}
@@ -545,10 +723,15 @@ export default {
this.shiftBitsB = DEFAULT_SHIFT_BITS;
this.shiftBitsA = DEFAULT_SHIFT_BITS;
this.aFineStep = 0.01;
this.fixedAFineStep = 1;
this.stopA1A2Repeat();
this.stopAFineDrag();
this.stopFixedA1A2Repeat();
this.stopFixedAFineDrag();
this.stopShiftBitRepeat();
this.stopShiftBitDrag();
this.showAdvancedAdjustment = false;
this.activeCoeffAdjustment = null;
this.globalError = null;
this.applyFilterDesign();
},
@@ -584,6 +767,7 @@ export default {
this.bSliders = [0, 0, 0, 0, 0, 0, 0];
this.aSliders = [0, 0, 0, 0, 0, 0, 0];
this.showAdvancedAdjustment = false;
this.activeCoeffAdjustment = null;
this.updateFromControls();
// 3. 更新圖表