feat: disable inputs if peq dont use them
This commit is contained in:
@@ -113,25 +113,14 @@ export function BoltIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="2" />
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="4"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GainIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function GainIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M3 12H7M17 12H21"
|
d="M3 12H7M17 12H21"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -152,12 +141,7 @@ export function GainIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||||||
|
|
||||||
export function GateIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function GateIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M3 3V21H21"
|
d="M3 3V21H21"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -179,12 +163,7 @@ export function GateIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||||||
|
|
||||||
export function CompressorIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function CompressorIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M3 3V21H21"
|
d="M3 3V21H21"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -206,12 +185,7 @@ export function CompressorIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||||||
|
|
||||||
export function LimiterIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function LimiterIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M3 3V21H21"
|
d="M3 3V21H21"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -233,12 +207,7 @@ export function LimiterIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||||||
|
|
||||||
export function DelayIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function DelayIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M4 5V19M12 8.5V15.5M20 11V13"
|
d="M4 5V19M12 8.5V15.5M20 11V13"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -252,12 +221,7 @@ export function DelayIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||||||
|
|
||||||
export function MatrixIcon(props: React.SVGProps<SVGSVGElement>) {
|
export function MatrixIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path d="M3 7C11 7 11 4 21 4" />
|
<path d="M3 7C11 7 11 4 21 4" />
|
||||||
<path d="M3 7C11 7 11 20 21 20" />
|
<path d="M3 7C11 7 11 20 21 20" />
|
||||||
<path d="M3 17C11 17 11 12 21 12" />
|
<path d="M3 17C11 17 11 12 21 12" />
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
font-family: var(--font-ui);
|
font-family: var(--font-ui);
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -122,4 +122,4 @@
|
|||||||
.choice-select__listbox--portal {
|
.choice-select__listbox--portal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000; /* clear of any local stacking contexts in the panel */
|
z-index: 1000; /* clear of any local stacking contexts in the panel */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,35 +40,35 @@ function ChoiceSelect<T extends string>({
|
|||||||
const listboxRef = useRef<HTMLUListElement>(null);
|
const listboxRef = useRef<HTMLUListElement>(null);
|
||||||
const suppressNextClick = useRef(false);
|
const suppressNextClick = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleMouseDown(e: MouseEvent) {
|
function handleMouseDown(e: MouseEvent) {
|
||||||
const target = e.target as Node;
|
const target = e.target as Node;
|
||||||
const clickedOutside =
|
const clickedOutside =
|
||||||
rootRef.current &&
|
rootRef.current &&
|
||||||
!rootRef.current.contains(target) &&
|
!rootRef.current.contains(target) &&
|
||||||
!listboxRef.current?.contains(target);
|
!listboxRef.current?.contains(target);
|
||||||
|
|
||||||
if (clickedOutside && open) {
|
if (clickedOutside && open) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
suppressNextClick.current = true;
|
suppressNextClick.current = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function handleClickCapture(e: MouseEvent) {
|
function handleClickCapture(e: MouseEvent) {
|
||||||
if (suppressNextClick.current) {
|
if (suppressNextClick.current) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
suppressNextClick.current = false;
|
suppressNextClick.current = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleMouseDown);
|
document.addEventListener('mousedown', handleMouseDown);
|
||||||
document.addEventListener('click', handleClickCapture, true); // capture phase
|
document.addEventListener('click', handleClickCapture, true); // capture phase
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', handleMouseDown);
|
document.removeEventListener('mousedown', handleMouseDown);
|
||||||
document.removeEventListener('click', handleClickCapture, true);
|
document.removeEventListener('click', handleClickCapture, true);
|
||||||
};
|
};
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
// Re-check placement/position on scroll/resize while open — the trigger's
|
// Re-check placement/position on scroll/resize while open — the trigger's
|
||||||
// position can change (e.g. inside a scrollable panel), and since the
|
// position can change (e.g. inside a scrollable panel), and since the
|
||||||
@@ -93,27 +93,27 @@ function ChoiceSelect<T extends string>({
|
|||||||
};
|
};
|
||||||
|
|
||||||
function updatePlacement() {
|
function updatePlacement() {
|
||||||
const el = triggerRef.current;
|
const el = triggerRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
const spaceBelow = window.innerHeight - rect.bottom;
|
const spaceBelow = window.innerHeight - rect.bottom;
|
||||||
const spaceAbove = rect.top;
|
const spaceAbove = rect.top;
|
||||||
const needsFlip = spaceBelow < LISTBOX_MAX_HEIGHT && spaceAbove > spaceBelow;
|
const needsFlip = spaceBelow < LISTBOX_MAX_HEIGHT && spaceAbove > spaceBelow;
|
||||||
setDropUp(needsFlip);
|
setDropUp(needsFlip);
|
||||||
|
|
||||||
// Capture the trigger's resolved font-size (its cqh has already resolved
|
// Capture the trigger's resolved font-size (its cqh has already resolved
|
||||||
// correctly here, since it's still inside the real container tree) and
|
// correctly here, since it's still inside the real container tree) and
|
||||||
// carry it as a plain px value into the portal, where cqh would break.
|
// carry it as a plain px value into the portal, where cqh would break.
|
||||||
const fontSize = parseFloat(getComputedStyle(el).fontSize);
|
const fontSize = parseFloat(getComputedStyle(el).fontSize);
|
||||||
|
|
||||||
setCoords({
|
setCoords({
|
||||||
top: needsFlip ? rect.top - LISTBOX_GAP : rect.bottom + LISTBOX_GAP,
|
top: needsFlip ? rect.top - LISTBOX_GAP : rect.bottom + LISTBOX_GAP,
|
||||||
left: rect.left,
|
left: rect.left,
|
||||||
width: rect.width,
|
width: rect.width,
|
||||||
fontSize,
|
fontSize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openList() {
|
function openList() {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
@@ -229,4 +229,4 @@ function ChoiceSelect<T extends string>({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChoiceSelect;
|
export default ChoiceSelect;
|
||||||
|
|||||||
@@ -78,9 +78,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.choice-stepper[data-disabled] {
|
.choice-stepper[data-disabled] {
|
||||||
opacity: 0.5;
|
opacity: 0.45;
|
||||||
|
filter: grayscale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.choice-stepper__input:disabled {
|
.choice-stepper__input:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-stepper[data-disabled] .choice-stepper__input,
|
||||||
|
.choice-stepper[data-disabled] .choice-stepper__arrow-btn {
|
||||||
|
color: var(--border-hairline);
|
||||||
|
}
|
||||||
|
|||||||
@@ -146,3 +146,26 @@
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gauge-viewport.is-disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-viewport.is-disabled .slider {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-viewport.is-disabled .thumb {
|
||||||
|
border-color: var(--border-hairline);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-viewport.is-disabled .value {
|
||||||
|
color: var(--border-hairline);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value input:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ type VerticalSliderProps = {
|
|||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
|
||||||
|
/** Greys out the slider and blocks all interaction. */
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultStep: StepFn = () => 0.1;
|
const defaultStep: StepFn = () => 0.1;
|
||||||
@@ -174,6 +177,7 @@ export default function VerticalSlider({
|
|||||||
formatValue,
|
formatValue,
|
||||||
showTicks = true,
|
showTicks = true,
|
||||||
scale = 'linear',
|
scale = 'linear',
|
||||||
|
disabled = false,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: VerticalSliderProps) {
|
}: VerticalSliderProps) {
|
||||||
@@ -240,6 +244,26 @@ export default function VerticalSlider({
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!disabled) return;
|
||||||
|
|
||||||
|
dragging.current = false;
|
||||||
|
keyboardEditing.current = false;
|
||||||
|
keyboardValue.current = null;
|
||||||
|
wheelEditing.current = false;
|
||||||
|
wheelValue.current = null;
|
||||||
|
if (wheelCommitTimeout.current) {
|
||||||
|
clearTimeout(wheelCommitTimeout.current);
|
||||||
|
wheelCommitTimeout.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditingText(null);
|
||||||
|
setDisplayValue(value);
|
||||||
|
dragValue.current = value;
|
||||||
|
onInput?.(value);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [disabled]);
|
||||||
|
|
||||||
const decimals = useMemo(() => {
|
const decimals = useMemo(() => {
|
||||||
if (listValues) return Math.min(3, getDecimals(displayValue));
|
if (listValues) return Math.min(3, getDecimals(displayValue));
|
||||||
return Math.min(3, getDecimals(step(displayValue)));
|
return Math.min(3, getDecimals(step(displayValue)));
|
||||||
@@ -273,6 +297,8 @@ export default function VerticalSlider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -307,6 +333,8 @@ export default function VerticalSlider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleKeyUp() {
|
async function handleKeyUp() {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (keyboardValue.current == null) return;
|
if (keyboardValue.current == null) return;
|
||||||
|
|
||||||
const next = keyboardValue.current;
|
const next = keyboardValue.current;
|
||||||
@@ -323,6 +351,7 @@ export default function VerticalSlider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleWheel(e: WheelEvent) {
|
function handleWheel(e: WheelEvent) {
|
||||||
|
if (disabled) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
wheelEditing.current = true;
|
wheelEditing.current = true;
|
||||||
@@ -393,6 +422,8 @@ export default function VerticalSlider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startDrag(e: React.PointerEvent<HTMLDivElement>) {
|
function startDrag(e: React.PointerEvent<HTMLDivElement>) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
const target = e.currentTarget;
|
const target = e.currentTarget;
|
||||||
dragging.current = true;
|
dragging.current = true;
|
||||||
target.setPointerCapture(e.pointerId);
|
target.setPointerCapture(e.pointerId);
|
||||||
@@ -427,6 +458,11 @@ export default function VerticalSlider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function commitEditing() {
|
async function commitEditing() {
|
||||||
|
if (disabled) {
|
||||||
|
setEditingText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (editingText !== null && editingText !== '' && editingText !== '-') {
|
if (editingText !== null && editingText !== '' && editingText !== '-') {
|
||||||
const parsed = Number(editingText);
|
const parsed = Number(editingText);
|
||||||
|
|
||||||
@@ -452,7 +488,10 @@ export default function VerticalSlider({
|
|||||||
const percent = valueToPercent(displayValue, min, max, scale, listValues);
|
const percent = valueToPercent(displayValue, min, max, scale, listValues);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={['gauge-viewport', className].filter(Boolean).join(' ')} style={style}>
|
<div
|
||||||
|
className={['gauge-viewport', disabled && 'is-disabled', className].filter(Boolean).join(' ')}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={['gauge-track', !showTicks && 'no-ticks'].filter(Boolean).join(' ')}
|
className={['gauge-track', !showTicks && 'no-ticks'].filter(Boolean).join(' ')}
|
||||||
ref={trackRef}
|
ref={trackRef}
|
||||||
@@ -468,7 +507,12 @@ export default function VerticalSlider({
|
|||||||
<div
|
<div
|
||||||
className="slider"
|
className="slider"
|
||||||
ref={sliderRef}
|
ref={sliderRef}
|
||||||
tabIndex={0}
|
role="slider"
|
||||||
|
aria-disabled={disabled}
|
||||||
|
aria-valuemin={min}
|
||||||
|
aria-valuemax={max}
|
||||||
|
aria-valuenow={displayValue}
|
||||||
|
tabIndex={disabled ? -1 : 0}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onPointerDown={startDrag}
|
onPointerDown={startDrag}
|
||||||
@@ -494,6 +538,7 @@ export default function VerticalSlider({
|
|||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
|
disabled={disabled}
|
||||||
value={editingText ?? formattedValue}
|
value={editingText ?? formattedValue}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
onFocus={() => setEditingText(value.toFixed(decimals))} // plain number while editing
|
onFocus={() => setEditingText(value.toFixed(decimals))} // plain number while editing
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
PEQ_FILTERS_LABEL,
|
PEQ_FILTERS_LABEL,
|
||||||
PEQ_Q_RANGES,
|
PEQ_Q_RANGES,
|
||||||
PEAK_Q_VALUES,
|
PEAK_Q_VALUES,
|
||||||
|
PEQ_Q_UNUSED,
|
||||||
|
PEQ_GAIN_USED,
|
||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
import { Channel, PEQBand, PEQFilter, PEQ, Crossover } from '../../types';
|
import { Channel, PEQBand, PEQFilter, PEQ, Crossover } from '../../types';
|
||||||
import { DSP408 as DSP } from '../../../../types';
|
import { DSP408 as DSP } from '../../../../types';
|
||||||
@@ -128,6 +130,8 @@ function PeqPanelSliders({
|
|||||||
onLiveValue: (value: PEQ) => void;
|
onLiveValue: (value: PEQ) => void;
|
||||||
}) {
|
}) {
|
||||||
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
||||||
|
const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type);
|
||||||
|
const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type);
|
||||||
|
|
||||||
const commit = (overrides: Partial<PEQ>) => {
|
const commit = (overrides: Partial<PEQ>) => {
|
||||||
const next = { ...peq, ...overrides };
|
const next = { ...peq, ...overrides };
|
||||||
@@ -168,6 +172,7 @@ function PeqPanelSliders({
|
|||||||
value={peq.q}
|
value={peq.q}
|
||||||
onChange={(value) => commit({ q: value })}
|
onChange={(value) => commit({ q: value })}
|
||||||
onInput={(value) => onLiveValue({ ...peq, q: value })}
|
onInput={(value) => onLiveValue({ ...peq, q: value })}
|
||||||
|
disabled={qDisabled}
|
||||||
style={{ width: '100%', flex: '1 1 auto' }}
|
style={{ width: '100%', flex: '1 1 auto' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -184,6 +189,7 @@ function PeqPanelSliders({
|
|||||||
formatValue={(v, d) => (v > 0 ? `+${v.toFixed(d)}` : v.toFixed(d))}
|
formatValue={(v, d) => (v > 0 ? `+${v.toFixed(d)}` : v.toFixed(d))}
|
||||||
onChange={(value) => commit({ gain: value })}
|
onChange={(value) => commit({ gain: value })}
|
||||||
onInput={(value) => onLiveValue({ ...peq, gain: value })}
|
onInput={(value) => onLiveValue({ ...peq, gain: value })}
|
||||||
|
disabled={gainDisabled}
|
||||||
style={{ width: '100%', flex: '1 1 auto' }}
|
style={{ width: '100%', flex: '1 1 auto' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -335,6 +341,9 @@ function PeqBandList({
|
|||||||
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
||||||
const qValues = PEAK_Q_VALUES.filter((q) => q >= qMin && q <= qMax);
|
const qValues = PEAK_Q_VALUES.filter((q) => q >= qMin && q <= qMax);
|
||||||
|
|
||||||
|
const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type);
|
||||||
|
const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`peq-band-list__row${isActive ? ' peq-band-list__row--active' : ''}`}
|
className={`peq-band-list__row${isActive ? ' peq-band-list__row--active' : ''}`}
|
||||||
@@ -370,6 +379,7 @@ function PeqBandList({
|
|||||||
value={peq.q}
|
value={peq.q}
|
||||||
values={qValues}
|
values={qValues}
|
||||||
formatLabel={(v) => v.toFixed(2)}
|
formatLabel={(v) => v.toFixed(2)}
|
||||||
|
disabled={qDisabled}
|
||||||
onChange={(value) => commit(band, { q: value })}
|
onChange={(value) => commit(band, { q: value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -380,6 +390,7 @@ function PeqBandList({
|
|||||||
max={12}
|
max={12}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
formatLabel={(v) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
formatLabel={(v) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
||||||
|
disabled={gainDisabled}
|
||||||
onChange={(value) => commit(band, { gain: value })}
|
onChange={(value) => commit(band, { gain: value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -715,8 +726,8 @@ function ChannelSection({
|
|||||||
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
|
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
<h3>Reset {activeChannelState.name} PEQ?</h3>
|
<h3>Reset {activeChannelState.name} PEQ?</h3>
|
||||||
<p>
|
<p>
|
||||||
This will set all PEQ bands on {activeChannelState.name} back to flat (0 dB,
|
This will set all PEQ bands on {activeChannelState.name} back to flat (0 dB, Peak).
|
||||||
Peak). This action cannot be undone.
|
This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="confirm-modal__actions">
|
<div className="confirm-modal__actions">
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Sidebar, SidebarItem } from '../../../../components/Sidebar/Sidebar';
|
import { Sidebar, SidebarItem } from '../../../../components/Sidebar/Sidebar';
|
||||||
import { BoltIcon, CompressorIcon, DelayIcon, GainIcon, GateIcon, InfoIcon, LimiterIcon, MatrixIcon, SlidersVerticalIcon, TextIcon } from '../../../../assets/icons/AudioIcons';
|
import {
|
||||||
|
BoltIcon,
|
||||||
|
CompressorIcon,
|
||||||
|
DelayIcon,
|
||||||
|
GainIcon,
|
||||||
|
GateIcon,
|
||||||
|
InfoIcon,
|
||||||
|
LimiterIcon,
|
||||||
|
MatrixIcon,
|
||||||
|
SlidersVerticalIcon,
|
||||||
|
TextIcon,
|
||||||
|
} from '../../../../assets/icons/AudioIcons';
|
||||||
import ConnectionPanel from '../ConnectionSection/ConnectionSection';
|
import ConnectionPanel from '../ConnectionSection/ConnectionSection';
|
||||||
import GainSection from '../GainSection/GainSection';
|
import GainSection from '../GainSection/GainSection';
|
||||||
import CompressorSection from '../CompressorSection/CompressorSection';
|
import CompressorSection from '../CompressorSection/CompressorSection';
|
||||||
|
|||||||
@@ -97,9 +97,7 @@ function getInputChannelCurves(
|
|||||||
): ChannelCurves {
|
): ChannelCurves {
|
||||||
const gain = liveGain ?? state.gain;
|
const gain = liveGain ?? state.gain;
|
||||||
|
|
||||||
const geqValues = GEQ_BANDS_LABEL.map(
|
const geqValues = GEQ_BANDS_LABEL.map(({ frequency }) => state.geq.gains[frequency] ?? 0);
|
||||||
({ frequency }) => state.geq.gains[frequency] ?? 0
|
|
||||||
);
|
|
||||||
|
|
||||||
const geq = state.geq.bypass
|
const geq = state.geq.bypass
|
||||||
? []
|
? []
|
||||||
@@ -109,17 +107,9 @@ function getInputChannelCurves(
|
|||||||
q: GEQ_CURVE_Q,
|
q: GEQ_CURVE_Q,
|
||||||
});
|
});
|
||||||
|
|
||||||
const peqCrossover = getPeqCrossoverPoints(
|
const peqCrossover = getPeqCrossoverPoints(freqs, state.peq_chain, state.crossover);
|
||||||
freqs,
|
|
||||||
state.peq_chain,
|
|
||||||
state.crossover
|
|
||||||
);
|
|
||||||
|
|
||||||
const total = sumCurvePoints([
|
const total = sumCurvePoints([geq, peqCrossover, getGainCurvePoints(freqs, gain)]);
|
||||||
geq,
|
|
||||||
peqCrossover,
|
|
||||||
getGainCurvePoints(freqs, gain),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { geq, peqCrossover, total };
|
return { geq, peqCrossover, total };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function MiscellaneousPanel({
|
|||||||
value={inputSource.type}
|
value={inputSource.type}
|
||||||
options={INPUT_TYPES}
|
options={INPUT_TYPES}
|
||||||
onChange={handleInputTypeChange}
|
onChange={handleInputTypeChange}
|
||||||
style={{color: 'var(--font-primary)'}}
|
style={{ color: 'var(--font-primary)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ function MiscellaneousPanel({
|
|||||||
return `${frequency.slice(1)} Hz`;
|
return `${frequency.slice(1)} Hz`;
|
||||||
}}
|
}}
|
||||||
onChange={handleFrequencyChange}
|
onChange={handleFrequencyChange}
|
||||||
style={{color: 'var(--font-primary)'}}
|
style={{ color: 'var(--font-primary)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -225,6 +225,20 @@ export const PEQ_FILTERS_LABEL: { value: PEQFilter; label: string }[] = [
|
|||||||
{ value: PEQFilter.AllPass2, label: 'All Pass 2' },
|
{ value: PEQFilter.AllPass2, label: 'All Pass 2' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** Filter types that ignore Q (first-order filters). */
|
||||||
|
export const PEQ_Q_UNUSED = new Set<PEQFilter>([
|
||||||
|
PEQFilter.Lp6Db,
|
||||||
|
PEQFilter.Hp6Db,
|
||||||
|
PEQFilter.AllPass1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Filter types that ignore gain (everything except Peak and the shelves). */
|
||||||
|
export const PEQ_GAIN_USED = new Set<PEQFilter>([
|
||||||
|
PEQFilter.Peak,
|
||||||
|
PEQFilter.LowShelf,
|
||||||
|
PEQFilter.HighShelf,
|
||||||
|
]);
|
||||||
|
|
||||||
export const FREQUENCY_MIN = 19.7;
|
export const FREQUENCY_MIN = 19.7;
|
||||||
export const FREQUENCY_MAX = 20160;
|
export const FREQUENCY_MAX = 20160;
|
||||||
export const FREQUENCY_QUANTIZATION = 300;
|
export const FREQUENCY_QUANTIZATION = 300;
|
||||||
|
|||||||
@@ -773,7 +773,7 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|||||||
|
|
||||||
await updateDSPState();
|
await updateDSPState();
|
||||||
|
|
||||||
notify('Channels copied successfully', 'success', dsp.name)
|
notify('Channels copied successfully', 'success', dsp.name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user