-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSpecFilt.ts
125 lines (118 loc) · 5.51 KB
/
SpecFilt.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
* Spectral filtering.
*
* Refer to the browser-based SpecFilt application for a test bed for spectral filtering.
*/
import ComplexArray from "../math/ComplexArray.js";
import * as Fft from "../signal/Fft.js";
export const enum FilterType {
lowPass = "LP",
highPass = "HP",
bandPass = "BP",
bandStop = "BS" }
export type FilterCurveFunction = (frequency: number) => number;
// A symetric Hann-like function is used for the smoothing, so that overlapping band filtered regions can be added without loss of total power.
function smoothingFunction (x: number) : number {
return (Math.sin(x * Math.PI / 2) + 1) / 2; }
/**
* Returns a filter curve function for a specified filter type.
*
* @param filterType
* filter type (low-pass, high-pass, etc.).
* @param filterFreq1
* Filter frequency for low-pass or high-pass. Lower filter frequecy for band-pass or band-stop.
* @param filterFreq2
* Upper filter frequecy for band-pass or band-stop. Ignored for low-pass or high-pass.
* @param smoothingWidth
* Distance between the start of the smoothing and the -6dB point.
* @returns
* A filter curve function, which returns values between 0 (=silence) and 1 (=pass unfiltered).
*/
export function getFilterCurveFunction (filterType: FilterType, filterFreq1: number, filterFreq2: number, smoothingWidth: number) : FilterCurveFunction {
switch (filterType) {
case FilterType.lowPass: {
return (freq: number) => {
if (freq < filterFreq1 - smoothingWidth) {
return 1; }
else if (freq < filterFreq1 + smoothingWidth) {
return smoothingFunction((filterFreq1 - freq) / smoothingWidth); }
else {
return 0; }}; }
case FilterType.highPass: {
return (freq: number) => {
if (freq < filterFreq1 - smoothingWidth) {
return 0; }
else if (freq < filterFreq1 + smoothingWidth) {
return smoothingFunction((freq - filterFreq1) / smoothingWidth); }
else {
return 1; }}; }
case FilterType.bandPass: {
return (freq: number) => {
if (freq < filterFreq1 - smoothingWidth) {
return 0; }
else if (freq < filterFreq1 + smoothingWidth) {
return smoothingFunction((freq - filterFreq1) / smoothingWidth); }
else if (freq < filterFreq2 - smoothingWidth) {
return 1; }
else if (freq < filterFreq2 + smoothingWidth) {
return smoothingFunction((filterFreq2 - freq) / smoothingWidth); }
else {
return 0; }}; }
case FilterType.bandStop: {
return (freq: number) => {
if (freq < filterFreq1 - smoothingWidth) {
return 1; }
else if (freq < filterFreq1 + smoothingWidth) {
return smoothingFunction((filterFreq1 - freq) / smoothingWidth); }
else if (freq < filterFreq2 - smoothingWidth) {
return 0; }
else if (freq < filterFreq2 + smoothingWidth) {
return smoothingFunction((freq - filterFreq2) / smoothingWidth); }
else {
return 1; }}; }
default: {
throw new Error("Unsupported filter type."); }}}
/**
* Applies a filter curve function to an array of spectral amplitudes.
*/
export function applyFilterCurveFunction (inAmplitudes: ArrayLike<number>, scalingFactor: number, filterCurveFunction: FilterCurveFunction) {
const n = inAmplitudes.length;
const outAmplitudes = new Float64Array(n);
for (let p = 0; p < n; p++) {
const frequency = p / scalingFactor;
const filterFactor = filterCurveFunction(frequency);
outAmplitudes[p] = filterFactor * inAmplitudes[p]; }
return outAmplitudes; }
/**
* Filters a signal using FFT and iFFT.
*
* The input signal should ideally be windoweded to prevent artifacts. But this would distort the amplitude curve of the output signal.
* In practice, the input signal should at least have a fade-in/fade-out.
*
* @param inSamples
* The input signal samples.
* If the length of the input array is even, the computation is faster. If it's a power of 2, it's even faster.
* @param sampleRate
* The sample rate of the input signal.
* @param filterType
* The filter type (low-pass, high-pass, etc.).
* @param filterFreq1
* Filter frequency for low-pass or high-pass. Lower filter frequecy for band-pass or band-stop.
* @param filterFreq2
* Upper filter frequecy for band-pass or band-stop. Ignored for low-pass or high-pass.
* @param smoothingWidth
* Distance between the start of the smoothing and the -6dB point.
* @returns
* The filtered output signal samples.
*/
export function filterSignal (inSamples: ArrayLike<number>, sampleRate: number, filterType: FilterType, filterFreq1: number, filterFreq2: number, smoothingWidth: number) : Float64Array {
const n = inSamples.length;
const inSpectrum = Fft.fftRealSpectrum(inSamples);
const inAmplitudes = inSpectrum.getAbsArray();
const inPhases = inSpectrum.getArgArray();
const filterCurveFunction = getFilterCurveFunction(filterType, filterFreq1, filterFreq2, smoothingWidth);
const outAmplitudes = applyFilterCurveFunction(inAmplitudes, n / sampleRate, filterCurveFunction);
const outPhases = inPhases;
const outSpectrum = ComplexArray.fromPolar(outAmplitudes, outPhases);
const outSignal = Fft.iFftRealHalf(outSpectrum, n);
return outSignal; }