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"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="4"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function GainIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M3 12H7M17 12H21"
|
||||
stroke="currentColor"
|
||||
@@ -152,12 +141,7 @@ export function GainIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function GateIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M3 3V21H21"
|
||||
stroke="currentColor"
|
||||
@@ -179,12 +163,7 @@ export function GateIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function CompressorIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M3 3V21H21"
|
||||
stroke="currentColor"
|
||||
@@ -206,12 +185,7 @@ export function CompressorIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function LimiterIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M3 3V21H21"
|
||||
stroke="currentColor"
|
||||
@@ -233,12 +207,7 @@ export function LimiterIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function DelayIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M4 5V19M12 8.5V15.5M20 11V13"
|
||||
stroke="currentColor"
|
||||
@@ -252,12 +221,7 @@ export function DelayIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function MatrixIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg 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 20 21 20" />
|
||||
<path d="M3 17C11 17 11 12 21 12" />
|
||||
|
||||
@@ -78,9 +78,15 @@
|
||||
}
|
||||
|
||||
.choice-stepper[data-disabled] {
|
||||
opacity: 0.5;
|
||||
opacity: 0.45;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.choice-stepper__input:disabled {
|
||||
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;
|
||||
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;
|
||||
style?: React.CSSProperties;
|
||||
|
||||
/** Greys out the slider and blocks all interaction. */
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const defaultStep: StepFn = () => 0.1;
|
||||
@@ -174,6 +177,7 @@ export default function VerticalSlider({
|
||||
formatValue,
|
||||
showTicks = true,
|
||||
scale = 'linear',
|
||||
disabled = false,
|
||||
className,
|
||||
style,
|
||||
}: VerticalSliderProps) {
|
||||
@@ -240,6 +244,26 @@ export default function VerticalSlider({
|
||||
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(() => {
|
||||
if (listValues) return Math.min(3, getDecimals(displayValue));
|
||||
return Math.min(3, getDecimals(step(displayValue)));
|
||||
@@ -273,6 +297,8 @@ export default function VerticalSlider({
|
||||
}
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (disabled) return;
|
||||
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
||||
return;
|
||||
}
|
||||
@@ -307,6 +333,8 @@ export default function VerticalSlider({
|
||||
}
|
||||
|
||||
async function handleKeyUp() {
|
||||
if (disabled) return;
|
||||
|
||||
if (keyboardValue.current == null) return;
|
||||
|
||||
const next = keyboardValue.current;
|
||||
@@ -323,6 +351,7 @@ export default function VerticalSlider({
|
||||
}
|
||||
|
||||
function handleWheel(e: WheelEvent) {
|
||||
if (disabled) return;
|
||||
e.preventDefault();
|
||||
|
||||
wheelEditing.current = true;
|
||||
@@ -393,6 +422,8 @@ export default function VerticalSlider({
|
||||
}
|
||||
|
||||
function startDrag(e: React.PointerEvent<HTMLDivElement>) {
|
||||
if (disabled) return;
|
||||
|
||||
const target = e.currentTarget;
|
||||
dragging.current = true;
|
||||
target.setPointerCapture(e.pointerId);
|
||||
@@ -427,6 +458,11 @@ export default function VerticalSlider({
|
||||
}
|
||||
|
||||
async function commitEditing() {
|
||||
if (disabled) {
|
||||
setEditingText(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (editingText !== null && editingText !== '' && editingText !== '-') {
|
||||
const parsed = Number(editingText);
|
||||
|
||||
@@ -452,7 +488,10 @@ export default function VerticalSlider({
|
||||
const percent = valueToPercent(displayValue, min, max, scale, listValues);
|
||||
|
||||
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
|
||||
className={['gauge-track', !showTicks && 'no-ticks'].filter(Boolean).join(' ')}
|
||||
ref={trackRef}
|
||||
@@ -468,7 +507,12 @@ export default function VerticalSlider({
|
||||
<div
|
||||
className="slider"
|
||||
ref={sliderRef}
|
||||
tabIndex={0}
|
||||
role="slider"
|
||||
aria-disabled={disabled}
|
||||
aria-valuemin={min}
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={displayValue}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
onPointerDown={startDrag}
|
||||
@@ -494,6 +538,7 @@ export default function VerticalSlider({
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
disabled={disabled}
|
||||
value={editingText ?? formattedValue}
|
||||
style={{ width: '100%' }}
|
||||
onFocus={() => setEditingText(value.toFixed(decimals))} // plain number while editing
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
PEQ_FILTERS_LABEL,
|
||||
PEQ_Q_RANGES,
|
||||
PEAK_Q_VALUES,
|
||||
PEQ_Q_UNUSED,
|
||||
PEQ_GAIN_USED,
|
||||
} from '../../constants';
|
||||
import { Channel, PEQBand, PEQFilter, PEQ, Crossover } from '../../types';
|
||||
import { DSP408 as DSP } from '../../../../types';
|
||||
@@ -128,6 +130,8 @@ function PeqPanelSliders({
|
||||
onLiveValue: (value: PEQ) => void;
|
||||
}) {
|
||||
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 next = { ...peq, ...overrides };
|
||||
@@ -168,6 +172,7 @@ function PeqPanelSliders({
|
||||
value={peq.q}
|
||||
onChange={(value) => commit({ q: value })}
|
||||
onInput={(value) => onLiveValue({ ...peq, q: value })}
|
||||
disabled={qDisabled}
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
@@ -184,6 +189,7 @@ function PeqPanelSliders({
|
||||
formatValue={(v, d) => (v > 0 ? `+${v.toFixed(d)}` : v.toFixed(d))}
|
||||
onChange={(value) => commit({ gain: value })}
|
||||
onInput={(value) => onLiveValue({ ...peq, gain: value })}
|
||||
disabled={gainDisabled}
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
@@ -335,6 +341,9 @@ function PeqBandList({
|
||||
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
||||
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 (
|
||||
<div
|
||||
className={`peq-band-list__row${isActive ? ' peq-band-list__row--active' : ''}`}
|
||||
@@ -370,6 +379,7 @@ function PeqBandList({
|
||||
value={peq.q}
|
||||
values={qValues}
|
||||
formatLabel={(v) => v.toFixed(2)}
|
||||
disabled={qDisabled}
|
||||
onChange={(value) => commit(band, { q: value })}
|
||||
/>
|
||||
|
||||
@@ -380,6 +390,7 @@ function PeqBandList({
|
||||
max={12}
|
||||
step={0.1}
|
||||
formatLabel={(v) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
||||
disabled={gainDisabled}
|
||||
onChange={(value) => commit(band, { gain: value })}
|
||||
/>
|
||||
|
||||
@@ -715,8 +726,8 @@ function ChannelSection({
|
||||
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<h3>Reset {activeChannelState.name} PEQ?</h3>
|
||||
<p>
|
||||
This will set all PEQ bands on {activeChannelState.name} back to flat (0 dB,
|
||||
Peak). This action cannot be undone.
|
||||
This will set all PEQ bands on {activeChannelState.name} back to flat (0 dB, Peak).
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
|
||||
<div className="confirm-modal__actions">
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { useState } from 'react';
|
||||
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 GainSection from '../GainSection/GainSection';
|
||||
import CompressorSection from '../CompressorSection/CompressorSection';
|
||||
|
||||
@@ -97,9 +97,7 @@ function getInputChannelCurves(
|
||||
): ChannelCurves {
|
||||
const gain = liveGain ?? state.gain;
|
||||
|
||||
const geqValues = GEQ_BANDS_LABEL.map(
|
||||
({ frequency }) => state.geq.gains[frequency] ?? 0
|
||||
);
|
||||
const geqValues = GEQ_BANDS_LABEL.map(({ frequency }) => state.geq.gains[frequency] ?? 0);
|
||||
|
||||
const geq = state.geq.bypass
|
||||
? []
|
||||
@@ -109,17 +107,9 @@ function getInputChannelCurves(
|
||||
q: GEQ_CURVE_Q,
|
||||
});
|
||||
|
||||
const peqCrossover = getPeqCrossoverPoints(
|
||||
freqs,
|
||||
state.peq_chain,
|
||||
state.crossover
|
||||
);
|
||||
const peqCrossover = getPeqCrossoverPoints(freqs, state.peq_chain, state.crossover);
|
||||
|
||||
const total = sumCurvePoints([
|
||||
geq,
|
||||
peqCrossover,
|
||||
getGainCurvePoints(freqs, gain),
|
||||
]);
|
||||
const total = sumCurvePoints([geq, peqCrossover, getGainCurvePoints(freqs, gain)]);
|
||||
|
||||
return { geq, peqCrossover, total };
|
||||
}
|
||||
|
||||
@@ -225,6 +225,20 @@ export const PEQ_FILTERS_LABEL: { value: PEQFilter; label: string }[] = [
|
||||
{ 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_MAX = 20160;
|
||||
export const FREQUENCY_QUANTIZATION = 300;
|
||||
|
||||
@@ -773,7 +773,7 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
||||
|
||||
await updateDSPState();
|
||||
|
||||
notify('Channels copied successfully', 'success', dsp.name)
|
||||
notify('Channels copied successfully', 'success', dsp.name);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user