feat: add fixed-point coefficient adjustment controls with shift-key mouse wheel support
This commit is contained in:
@@ -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. 更新圖表
|
||||
|
||||
Reference in New Issue
Block a user