/
PhaseShiftAllpass.ts
81 lines (65 loc) · 2.49 KB
/
PhaseShiftAllpass.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
import { Gain } from "../../core/context/Gain";
import { connectSeries, ToneAudioNode, ToneAudioNodeOptions } from "../../core/context/ToneAudioNode";
/**
* PhaseShiftAllpass is an very efficient implementation of a Hilbert Transform
* using two Allpass filter banks whose outputs have a phase difference of 90°.
* Here the `offset90` phase is offset by +90° in relation to `output`.
* Coefficients and structure was developed by Olli Niemitalo.
* For more details see: http://yehar.com/blog/?p=368
* @category Component
*/
export class PhaseShiftAllpass extends ToneAudioNode<ToneAudioNodeOptions> {
readonly name: string = "PhaseShiftAllpass";
readonly input = new Gain({ context: this.context });
/**
* The Allpass filter in the first bank
*/
private _bank0: IIRFilterNode[];
/**
* The Allpass filter in the seconds bank
*/
private _bank1: IIRFilterNode[];
/**
* A IIR filter implementing a delay by one sample used by the first bank
*/
private _oneSampleDelay: IIRFilterNode;
/**
* The phase shifted output
*/
readonly output = new Gain({ context: this.context });
/**
* The PhaseShifted allpass output
*/
readonly offset90 = new Gain({ context: this.context });
constructor(options?: Partial<ToneAudioNodeOptions>) {
super(options);
const allpassBank1Values = [0.6923878, 0.9360654322959, 0.9882295226860, 0.9987488452737];
const allpassBank2Values = [0.4021921162426, 0.8561710882420, 0.9722909545651, 0.9952884791278];
this._bank0 = this._createAllPassFilterBank(allpassBank1Values);
this._bank1 = this._createAllPassFilterBank(allpassBank2Values);
this._oneSampleDelay = this.context.createIIRFilter([0.0, 1.0], [1.0, 0.0]);
// connect Allpass filter banks
connectSeries(this.input, ...this._bank0, this._oneSampleDelay, this.output);
connectSeries(this.input, ...this._bank1, this.offset90);
}
/**
* Create all of the IIR filters from an array of values using the coefficient calculation.
*/
private _createAllPassFilterBank(bankValues: number[]): IIRFilterNode[] {
const nodes: IIRFilterNode[] = bankValues.map(value => {
const coefficients = [[value * value, 0, -1], [1, 0, -(value * value)]];
return this.context.createIIRFilter(coefficients[0], coefficients[1]);
});
return nodes;
}
dispose(): this {
super.dispose();
this.input.dispose();
this.output.dispose();
this.offset90.dispose();
this._bank0.forEach(f => f.disconnect());
this._bank1.forEach(f => f.disconnect());
this._oneSampleDelay.disconnect();
return this;
}
}