Skip to content

Commit

Permalink
Merge pull request #199 from rewbs/improve-filters
Browse files Browse the repository at this point in the history
Expose all biquad filter options. Make timeseries audio use shared biquad filter component.
  • Loading branch information
rewbs authored Aug 11, 2023
2 parents 1a2feee + 5ada2d6 commit 1b7fddd
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 85 deletions.
47 changes: 40 additions & 7 deletions src/components/BiquadFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable react/jsx-no-target-blank */
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
//@ts-ignore
import { MenuItem, TextField, Tooltip } from '@mui/material';
import { Link, MenuItem, TextField, Tooltip } from '@mui/material';
import { SmallTextField } from './SmallTextField';
import { useState } from 'react';
import { createAudioBufferCopy } from '../utils/utils';
Expand All @@ -16,7 +17,8 @@ export function BiquadFilter({ unfilteredAudioBuffer, updateAudioBuffer }: Biqua

const [biquadFilterFreq, setBiquadFilterFreq] = useState(800);
const [biquadFilterQ, setBiquadFilterQ] = useState(0.5);
const [biquadFilterType, setBiquadFilterType] = useState<"lowpass" | "highpass" | "bandpass">("lowpass");
const [biquadFilterGain, setBiquadFilterGain] = useState(0);
const [biquadFilterType, setBiquadFilterType] = useState<BiquadFilterType>("lowpass");


const handleResetFilter = () => {
Expand All @@ -41,29 +43,34 @@ export function BiquadFilter({ unfilteredAudioBuffer, updateAudioBuffer }: Biqua
biquadFilter.type = biquadFilterType;
biquadFilter.frequency.value = biquadFilterFreq;
biquadFilter.Q.value = biquadFilterQ;
biquadFilter.gain.value = biquadFilterGain;
source.connect(biquadFilter);
biquadFilter.connect(context.destination);

source.start();
context.startRendering().then(renderedBuffer => updateAudioBuffer(renderedBuffer));
}


return <Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography fontSize={"0.75em"}>Filter: </Typography>
<Typography fontSize={"0.75em"}>Filter <Link href="https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode/type" target='_blank' rel="noopener">(?)</Link>: </Typography>
<TextField
size="small"
style={{ width: "6em" }}
label="Type"
InputLabelProps={{ shrink: true, }}
InputProps={{ style: { fontSize: '0.75em' } }}
value={biquadFilterType}
onChange={(e) => setBiquadFilterType(e.target.value as "lowpass" | "highpass" | "bandpass")}
onChange={(e) => setBiquadFilterType(e.target.value as BiquadFilterType) }
select
>
<MenuItem value={"lowpass"}>lowpass</MenuItem>
<MenuItem value={"highpass"}>highpass</MenuItem>
<MenuItem value={"bandpass"}>bandpass</MenuItem>
<MenuItem value={"lowshelf"}>lowshelf</MenuItem>
<MenuItem value={"highshelf"}>highshelf</MenuItem>
<MenuItem value={"peaking"}>peaking</MenuItem>
<MenuItem value={"notch"}>notch</MenuItem>
<MenuItem value={"allpass"}>allpass</MenuItem>
</TextField>
<SmallTextField
label="Freq (Hz)"
Expand All @@ -72,16 +79,42 @@ export function BiquadFilter({ unfilteredAudioBuffer, updateAudioBuffer }: Biqua
onChange={(e) => setBiquadFilterFreq(Number(e.target.value))}
/>
<SmallTextField
label="Resonance"
label = {filterTypeToQLabel(biquadFilterType)}
disabled = {["lowshelf", "highshelf"].includes(biquadFilterType)}
type="number"
value={biquadFilterQ}
onChange={(e) => setBiquadFilterQ(Number(e.target.value))}
/>
<SmallTextField
label = {["lowshelf", "highshelf", "peaking"].includes(biquadFilterType)? "Gain (db)" : "Gain (unused)"}
disabled = {!["lowshelf", "highshelf", "peaking"].includes(biquadFilterType)}
type="number"
value={biquadFilterGain}
onChange={(e) => setBiquadFilterGain(Number(e.target.value))}
/>
<Tooltip arrow placement="top" title={"Apply a filter to your audio."} >
<Button size='small' variant='contained' onClick={handleApplyFilter}> Apply</Button>
</Tooltip>
<Tooltip arrow placement="top" title={"Undo the filter to restore your original audio."} >
<Button size='small' variant='outlined' onClick={handleResetFilter}> Reset</Button>
</Tooltip>
</Stack>;
}
}


function filterTypeToQLabel(biquadFilterType: BiquadFilterType): string {
switch(biquadFilterType) {
case "lowpass":
case "highpass":
return "Q (peak)";
case "bandpass":
case "notch":
case "peaking":
return "Q (width)";
case "allpass":
return "Q (phase)";
case "lowshelf":
case "highshelf":
return "Q (unused)";
}
}
82 changes: 4 additions & 78 deletions src/components/TimeSeriesUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { channelToRgba, createAudioBufferCopy } from '../utils/utils';
import { SmallTextField } from './SmallTextField';
import { TabPanel } from './TabPanel';
import WavesurferAudioWaveform from './WavesurferWaveform';
import { BiquadFilter } from './BiquadFilter';

type TimeSeriesUIProps = {
lastFrame: number,
Expand Down Expand Up @@ -98,10 +99,6 @@ export const TimeSeriesUI = (props: TimeSeriesUIProps) => {
const [pitchMethod, setPitchMethod] = useState("default");
const pitchProgressRef = useRef<HTMLInputElement>(null);

const [biquadFilterFreq, setBiquadFilterFreq] = useState(800);
const [biquadFilterQ, setBiquadFilterQ] = useState(0.5);
const [biquadFilterType, setBiquadFilterType] = useState<"lowpass" | "highpass" | "bandpass">("lowpass");

const [unfilteredAudioBuffer, setUnfilteredAudioBuffer] = useState<AudioBuffer>();
const [audioBuffer, setAudioBuffer] = useState<AudioBuffer>();

Expand Down Expand Up @@ -232,35 +229,6 @@ export const TimeSeriesUI = (props: TimeSeriesUIProps) => {
}
}

const handleResetBandPass = () => {
if (unfilteredAudioBuffer) {
setAudioBuffer(createAudioBufferCopy(unfilteredAudioBuffer));
}
}

const handleApplyBandPass = () => {
if (!unfilteredAudioBuffer) {
return;
}

const audioData = unfilteredAudioBuffer.getChannelData(0);
const context = new OfflineAudioContext(1, audioData.length, unfilteredAudioBuffer.sampleRate);
const source = context.createBufferSource();
const filteredBuffer = context.createBuffer(1, audioData.length, unfilteredAudioBuffer.sampleRate);
filteredBuffer.getChannelData(0).set(audioData);
source.buffer = filteredBuffer;

const biquadFilter = context.createBiquadFilter();
biquadFilter.type = biquadFilterType;
biquadFilter.frequency.value = biquadFilterFreq;
biquadFilter.Q.value = biquadFilterQ;
source.connect(biquadFilter);
biquadFilter.connect(context.destination);

source.start();
context.startRendering().then(renderedBuffer => setAudioBuffer(renderedBuffer));
}

const handleLoadFromCSV = async (event: any) => {
const file = event.target.files[0];
if (file && lastFrame) {
Expand Down Expand Up @@ -477,41 +445,10 @@ export const TimeSeriesUI = (props: TimeSeriesUIProps) => {
onChange={handleLoadFromAudio} />
</Box>
{audioBuffer && <>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography fontSize={"0.75em"}>Filter: </Typography>
<TextField
size="small"
style={{ width: "6em" }}
label="Type"
InputLabelProps={{ shrink: true, }}
InputProps={{ style: { fontSize: '0.75em' } }}
value={biquadFilterType}
onChange={(e) => setBiquadFilterType(e.target.value as "lowpass" | "highpass" | "bandpass")}
select
>
<MenuItem value={"lowpass"}>lowpass</MenuItem>
<MenuItem value={"highpass"}>highpass</MenuItem>
<MenuItem value={"bandpass"}>bandpass</MenuItem>
</TextField>
<SmallTextField
label="Freq (Hz)"
type="number"
value={biquadFilterFreq}
onChange={(e) => setBiquadFilterFreq(Number(e.target.value))}
<BiquadFilter
unfilteredAudioBuffer={unfilteredAudioBuffer||audioBuffer}
updateAudioBuffer={newAudioBuffer => setAudioBuffer(newAudioBuffer)}
/>
<SmallTextField
label="Resonance"
type="number"
value={biquadFilterQ}
onChange={(e) => setBiquadFilterQ(Number(e.target.value))}
/>
<Tooltip arrow placement="top" title={"Apply a filter to your audio."} >
<Button size='small' variant='contained' onClick={handleApplyBandPass}> Apply</Button>
</Tooltip>
<Tooltip arrow placement="top" title={"Undo the filter to restore your original audio."} >
<Button size='small' variant='outlined' onClick={handleResetBandPass}> Reset</Button>
</Tooltip>
</Stack>
</>}
</Stack>
{waveSuferWaveform}
Expand Down Expand Up @@ -570,17 +507,6 @@ export const TimeSeriesUI = (props: TimeSeriesUIProps) => {
</Stack>
<Typography fontSize="0.75em">Each row of the supplied file must be formatted like &nbsp;&nbsp; <span><code>timestamp,value</code></span> &nbsp;&nbsp; where the type of the timestamp can be milliseconds or frames as selected above.</Typography>
</TabPanel>

{/* <FormControl fullWidth>
<InputLabel>Previously Loaded TimeSeries</InputLabel>
<Select value={selectedTimeSeriesIndex} onChange={handleSelectTimeSeries}>
{timeSeriesList.map((_, index) => (
<MenuItem key={index} value={index}>
TimeSeries {index + 1}
</MenuItem>
))}
</Select>
</FormControl> */}
<h3>Processing</h3>
<small><p>Modify the loaded data before adding it as a timeseries.</p></small>
<Stack direction="row" alignItems={"center"} justifyContent="space-around" >
Expand Down

0 comments on commit 1b7fddd

Please sign in to comment.