@jtarrio/signals 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +54 -0
  3. package/dist/demod/demod-am.d.ts +55 -0
  4. package/dist/demod/demod-am.d.ts.map +1 -0
  5. package/dist/demod/demod-am.js +118 -0
  6. package/dist/demod/demod-am.js.map +1 -0
  7. package/dist/demod/demod-cw.d.ts +49 -0
  8. package/dist/demod/demod-cw.d.ts.map +1 -0
  9. package/dist/demod/demod-cw.js +106 -0
  10. package/dist/demod/demod-cw.js.map +1 -0
  11. package/dist/demod/demod-nbfm.d.ts +55 -0
  12. package/dist/demod/demod-nbfm.d.ts.map +1 -0
  13. package/dist/demod/demod-nbfm.js +119 -0
  14. package/dist/demod/demod-nbfm.js.map +1 -0
  15. package/dist/demod/demod-ssb.d.ts +56 -0
  16. package/dist/demod/demod-ssb.d.ts.map +1 -0
  17. package/dist/demod/demod-ssb.js +118 -0
  18. package/dist/demod/demod-ssb.js.map +1 -0
  19. package/dist/demod/demod-wbfm.d.ts +114 -0
  20. package/dist/demod/demod-wbfm.d.ts.map +1 -0
  21. package/dist/demod/demod-wbfm.js +226 -0
  22. package/dist/demod/demod-wbfm.js.map +1 -0
  23. package/dist/demod/demodulator.d.ts +3 -0
  24. package/dist/demod/demodulator.d.ts.map +1 -0
  25. package/dist/demod/demodulator.js +28 -0
  26. package/dist/demod/demodulator.js.map +1 -0
  27. package/dist/demod/empty-demodulator.d.ts +78 -0
  28. package/dist/demod/empty-demodulator.d.ts.map +1 -0
  29. package/dist/demod/empty-demodulator.js +148 -0
  30. package/dist/demod/empty-demodulator.js.map +1 -0
  31. package/dist/demod/modes.d.ts +113 -0
  32. package/dist/demod/modes.d.ts.map +1 -0
  33. package/dist/demod/modes.js +107 -0
  34. package/dist/demod/modes.js.map +1 -0
  35. package/dist/demod/player.d.ts +19 -0
  36. package/dist/demod/player.d.ts.map +1 -0
  37. package/dist/demod/player.js +15 -0
  38. package/dist/demod/player.js.map +1 -0
  39. package/dist/demod/sample-counter.d.ts +22 -0
  40. package/dist/demod/sample-counter.d.ts.map +1 -0
  41. package/dist/demod/sample-counter.js +57 -0
  42. package/dist/demod/sample-counter.js.map +1 -0
  43. package/dist/demod/spectrum.d.ts +23 -0
  44. package/dist/demod/spectrum.d.ts.map +1 -0
  45. package/dist/demod/spectrum.js +76 -0
  46. package/dist/demod/spectrum.js.map +1 -0
  47. package/dist/dsp/buffers.d.ts +127 -0
  48. package/dist/dsp/buffers.d.ts.map +1 -0
  49. package/dist/dsp/buffers.js +171 -0
  50. package/dist/dsp/buffers.js.map +1 -0
  51. package/dist/dsp/coefficients.d.ts +23 -0
  52. package/dist/dsp/coefficients.d.ts.map +1 -0
  53. package/dist/dsp/coefficients.js +81 -0
  54. package/dist/dsp/coefficients.js.map +1 -0
  55. package/dist/dsp/converters.d.ts +15 -0
  56. package/dist/dsp/converters.d.ts.map +1 -0
  57. package/dist/dsp/converters.js +43 -0
  58. package/dist/dsp/converters.js.map +1 -0
  59. package/dist/dsp/demodulators.d.ts +68 -0
  60. package/dist/dsp/demodulators.d.ts.map +1 -0
  61. package/dist/dsp/demodulators.js +149 -0
  62. package/dist/dsp/demodulators.js.map +1 -0
  63. package/dist/dsp/fft.d.ts +54 -0
  64. package/dist/dsp/fft.d.ts.map +1 -0
  65. package/dist/dsp/fft.js +175 -0
  66. package/dist/dsp/fft.js.map +1 -0
  67. package/dist/dsp/filters.d.ts +197 -0
  68. package/dist/dsp/filters.d.ts.map +1 -0
  69. package/dist/dsp/filters.js +486 -0
  70. package/dist/dsp/filters.js.map +1 -0
  71. package/dist/dsp/math.d.ts +10 -0
  72. package/dist/dsp/math.d.ts.map +1 -0
  73. package/dist/dsp/math.js +55 -0
  74. package/dist/dsp/math.js.map +1 -0
  75. package/dist/dsp/power.d.ts +2 -0
  76. package/dist/dsp/power.d.ts.map +1 -0
  77. package/dist/dsp/power.js +23 -0
  78. package/dist/dsp/power.js.map +1 -0
  79. package/dist/dsp/resamplers.d.ts +45 -0
  80. package/dist/dsp/resamplers.d.ts.map +1 -0
  81. package/dist/dsp/resamplers.js +85 -0
  82. package/dist/dsp/resamplers.js.map +1 -0
  83. package/dist/errors.d.ts +15 -0
  84. package/dist/errors.d.ts.map +1 -0
  85. package/dist/errors.js +33 -0
  86. package/dist/errors.js.map +1 -0
  87. package/dist/players/audioplayer.d.ts +25 -0
  88. package/dist/players/audioplayer.d.ts.map +1 -0
  89. package/dist/players/audioplayer.js +68 -0
  90. package/dist/players/audioplayer.js.map +1 -0
  91. package/dist/radio/msgqueue.d.ts +32 -0
  92. package/dist/radio/msgqueue.d.ts.map +1 -0
  93. package/dist/radio/msgqueue.js +62 -0
  94. package/dist/radio/msgqueue.js.map +1 -0
  95. package/dist/radio/radio.d.ts +107 -0
  96. package/dist/radio/radio.d.ts.map +1 -0
  97. package/dist/radio/radio.js +279 -0
  98. package/dist/radio/radio.js.map +1 -0
  99. package/dist/radio/sample_receiver.d.ts +17 -0
  100. package/dist/radio/sample_receiver.d.ts.map +1 -0
  101. package/dist/radio/sample_receiver.js +52 -0
  102. package/dist/radio/sample_receiver.js.map +1 -0
  103. package/dist/radio/signal_source.d.ts +53 -0
  104. package/dist/radio/signal_source.d.ts.map +1 -0
  105. package/dist/radio/signal_source.js +15 -0
  106. package/dist/radio/signal_source.js.map +1 -0
  107. package/dist/radio.d.ts +4 -0
  108. package/dist/radio.d.ts.map +1 -0
  109. package/dist/radio.js +4 -0
  110. package/dist/radio.js.map +1 -0
  111. package/dist/sources/generators.d.ts +41 -0
  112. package/dist/sources/generators.d.ts.map +1 -0
  113. package/dist/sources/generators.js +238 -0
  114. package/dist/sources/generators.js.map +1 -0
  115. package/dist/sources/provider.d.ts +7 -0
  116. package/dist/sources/provider.d.ts.map +1 -0
  117. package/dist/sources/provider.js +23 -0
  118. package/dist/sources/provider.js.map +1 -0
  119. package/dist/sources/push.d.ts +30 -0
  120. package/dist/sources/push.d.ts.map +1 -0
  121. package/dist/sources/push.js +106 -0
  122. package/dist/sources/push.js.map +1 -0
  123. package/dist/sources/read_ring.d.ts +14 -0
  124. package/dist/sources/read_ring.d.ts.map +1 -0
  125. package/dist/sources/read_ring.js +59 -0
  126. package/dist/sources/read_ring.js.map +1 -0
  127. package/dist/sources/realtime.d.ts +44 -0
  128. package/dist/sources/realtime.d.ts.map +1 -0
  129. package/dist/sources/realtime.js +136 -0
  130. package/dist/sources/realtime.js.map +1 -0
  131. package/package.json +25 -0
@@ -0,0 +1,197 @@
1
+ export interface Filter {
2
+ /** Returns a newly initialized clone of this filter. */
3
+ clone(): Filter;
4
+ /** Returns this filter's delay, in samples. */
5
+ getDelay(): number;
6
+ /** Applies the filter to the input samples, in place. */
7
+ inPlace(samples: Float32Array): void;
8
+ }
9
+ /** A class to apply a FIR filter to a sequence of samples. */
10
+ export declare class FIRFilter implements Filter {
11
+ private coefs;
12
+ /** @param coefs The coefficients of the filter to apply. */
13
+ constructor(coefs: Float32Array);
14
+ private offset;
15
+ private center;
16
+ private curSamples;
17
+ setCoefficients(coefs: Float32Array): void;
18
+ clone(): FIRFilter;
19
+ getDelay(): number;
20
+ inPlace(samples: Float32Array): void;
21
+ delayInPlace(samples: Float32Array): void;
22
+ /**
23
+ * Loads a new block of samples to filter.
24
+ * @param samples The samples to load.
25
+ */
26
+ loadSamples(samples: Float32Array): void;
27
+ /**
28
+ * Returns a filtered sample.
29
+ * Be very careful when you modify this function. About 85% of the total execution
30
+ * time is spent here, so performance is critical.
31
+ * @param index The index of the sample to return, corresponding
32
+ * to the same index in the latest sample block loaded via loadSamples().
33
+ */
34
+ get(index: number): number;
35
+ /**
36
+ * Returns a delayed sample.
37
+ * @param index The index of the relative sample to return.
38
+ */
39
+ getDelayed(index: number): number;
40
+ }
41
+ /** Automatic gain control for audio signals. */
42
+ export declare class AGC implements Filter {
43
+ private sampleRate;
44
+ constructor(sampleRate: number, timeConstantSeconds: number, maxGain?: number);
45
+ private dcBlocker;
46
+ private alpha;
47
+ private counter;
48
+ private maxPower;
49
+ private maxGain;
50
+ clone(): AGC;
51
+ getDelay(): number;
52
+ inPlace(samples: Float32Array): void;
53
+ }
54
+ /** A filter that blocks DC signals. */
55
+ export declare class DcBlocker implements Filter {
56
+ constructor(sampleRate: number);
57
+ private alpha;
58
+ private dc;
59
+ clone(): DcBlocker;
60
+ getDelay(): number;
61
+ inPlace(samples: Float32Array): void;
62
+ }
63
+ /**
64
+ * Returns the decay value to use in a single-pole low-pass or high-pass IIR filter
65
+ * with the given time constant.
66
+ * @param sampleRate The signal's sample rate.
67
+ * @param timeConstant The time constant in seconds
68
+ */
69
+ export declare function decay(sampleRate: number, timeConstant: number): number;
70
+ export declare function frequencyToTimeConstant(freq: number): number;
71
+ export interface IIRFilter extends Filter {
72
+ /** Returns a newly initialized clone of this filter. */
73
+ clone(): IIRFilter;
74
+ /** Filters an individual sample. */
75
+ add(sample: number): number;
76
+ /** Returns the value currently held by the filter. */
77
+ get value(): number;
78
+ /** Returns the phase shift at the given frequency. */
79
+ phaseShift(freq: number): number;
80
+ }
81
+ /** IIR filter with two 'b' coefficients and one 'a' coefficient. */
82
+ declare class IIRFilter21 implements IIRFilter {
83
+ private sampleRate;
84
+ private b0;
85
+ private b1;
86
+ private a1;
87
+ constructor(sampleRate: number, b0: number, b1: number, a1: number);
88
+ private prev;
89
+ private val;
90
+ /** Returns a copy of this filter. */
91
+ clone(): IIRFilter;
92
+ getDelay(): number;
93
+ /**
94
+ * Filters the given samples in place.
95
+ * @param samples The samples to filter.
96
+ */
97
+ inPlace(samples: Float32Array): void;
98
+ /** Filters an individual sample. */
99
+ add(sample: number): number;
100
+ /** Returns the value currently held by the filter. */
101
+ get value(): number;
102
+ /** Returns the phase shift at the given angular frequency. */
103
+ phaseShift(freq: number): number;
104
+ }
105
+ /** A FM de-emphasis filter. */
106
+ export declare class Deemphasis extends IIRFilter21 {
107
+ /**
108
+ * @param sampleRate The signal's sample rate.
109
+ * @param timeConstant The filter's time constant, in seconds.
110
+ */
111
+ constructor(sampleRate: number, timeConstant: number);
112
+ }
113
+ /** A FM pre-emphasis filter. */
114
+ export declare class Preemphasis extends IIRFilter21 {
115
+ /**
116
+ * @param sampleRate The signal's sample rate.
117
+ * @param timeConstant The filter's time constant, in seconds.
118
+ */
119
+ constructor(sampleRate: number, timeConstant: number);
120
+ }
121
+ /** A single-pole IIR low-pass filter. */
122
+ export declare class IIRLowPass extends Deemphasis {
123
+ /**
124
+ * @param sampleRate The signal's sample rate.
125
+ * @param freq The filter's corner frequency.
126
+ */
127
+ constructor(sampleRate: number, freq: number);
128
+ }
129
+ declare class IIRFilterChain implements IIRFilter {
130
+ private filters;
131
+ constructor(filters: IIRFilter[]);
132
+ /** Returns a copy of this filter. */
133
+ clone(): IIRFilter;
134
+ getDelay(): number;
135
+ /**
136
+ * Filters the given samples in place.
137
+ * @param samples The samples to filter.
138
+ */
139
+ inPlace(samples: Float32Array): void;
140
+ /** Filters an individual sample. */
141
+ add(sample: number): number;
142
+ /** Returns the value currently held by the filter. */
143
+ get value(): number;
144
+ /** Returns the phase shift at the given frequency. */
145
+ phaseShift(freq: number): number;
146
+ }
147
+ /** A chain of single-pole IIR low-pass filters. */
148
+ export declare class IIRLowPassChain extends IIRFilterChain {
149
+ /**
150
+ * @param count Number of filters in the chain.
151
+ * @param sampleRate The signal's sample rate.
152
+ * @param freq The corner frequency for the whole chain.
153
+ */
154
+ constructor(count: number, sampleRate: number, freq: number);
155
+ }
156
+ /**
157
+ * Shifts IQ samples by a given frequency.
158
+ */
159
+ export declare class FrequencyShifter {
160
+ private sampleRate;
161
+ constructor(sampleRate: number);
162
+ private cosine;
163
+ private sine;
164
+ inPlace(I: Float32Array, Q: Float32Array, freq: number): void;
165
+ }
166
+ /** A phase-locked loop that can detect a signal with a given frequency. */
167
+ export declare class PLL {
168
+ private sampleRate;
169
+ /**
170
+ * @param sampleRate The sample rate for the input signal.
171
+ * @param freq The frequency of the signal to detect, in Hz.
172
+ * @param tolerance The frequency tolerance for the signal, in Hz.
173
+ */
174
+ constructor(sampleRate: number, freq: number, tolerance: number);
175
+ private phase;
176
+ private speed;
177
+ private maxSpeedCorr;
178
+ private speedCorrection;
179
+ private phaseCorrection;
180
+ private biFlt;
181
+ private bqFlt;
182
+ private siFlt;
183
+ private sqFlt;
184
+ private piFlt;
185
+ private pqFlt;
186
+ private lbI;
187
+ private lbQ;
188
+ private iMagFlt;
189
+ private bMagFlt;
190
+ cos: number;
191
+ sin: number;
192
+ locked: boolean;
193
+ add(sample: number): void;
194
+ addRemaining(sample: number): void;
195
+ }
196
+ export {};
197
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../src/dsp/filters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,486 @@
1
+ // Copyright 2024 Jacobo Tarrio Barreiro. All rights reserved.
2
+ // Copyright 2013 Google Inc. All rights reserved.
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // http://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+ import { atan2 } from "./math.js";
16
+ /** A class to apply a FIR filter to a sequence of samples. */
17
+ export class FIRFilter {
18
+ coefs;
19
+ /** @param coefs The coefficients of the filter to apply. */
20
+ constructor(coefs) {
21
+ this.coefs = coefs;
22
+ this.offset = this.coefs.length - 1;
23
+ this.center = Math.floor(this.coefs.length / 2);
24
+ this.curSamples = new Float32Array(this.offset);
25
+ }
26
+ offset;
27
+ center;
28
+ curSamples;
29
+ setCoefficients(coefs) {
30
+ const oldSamples = this.curSamples;
31
+ this.coefs = coefs;
32
+ this.offset = this.coefs.length - 1;
33
+ this.center = Math.floor(this.coefs.length / 2);
34
+ this.curSamples = new Float32Array(this.offset);
35
+ this.loadSamples(oldSamples);
36
+ }
37
+ clone() {
38
+ return new FIRFilter(this.coefs);
39
+ }
40
+ getDelay() {
41
+ return this.center;
42
+ }
43
+ inPlace(samples) {
44
+ this.loadSamples(samples);
45
+ for (let i = 0; i < samples.length; ++i) {
46
+ samples[i] = this.get(i);
47
+ }
48
+ }
49
+ delayInPlace(samples) {
50
+ this.loadSamples(samples);
51
+ for (let i = 0; i < samples.length; ++i) {
52
+ samples[i] = this.getDelayed(i);
53
+ }
54
+ }
55
+ /**
56
+ * Loads a new block of samples to filter.
57
+ * @param samples The samples to load.
58
+ */
59
+ loadSamples(samples) {
60
+ const len = samples.length + this.offset;
61
+ if (this.curSamples.length != len) {
62
+ let newSamples = new Float32Array(len);
63
+ newSamples.set(this.curSamples.subarray(this.curSamples.length - this.offset));
64
+ this.curSamples = newSamples;
65
+ }
66
+ else {
67
+ this.curSamples.copyWithin(0, samples.length);
68
+ }
69
+ this.curSamples.set(samples, this.offset);
70
+ }
71
+ /**
72
+ * Returns a filtered sample.
73
+ * Be very careful when you modify this function. About 85% of the total execution
74
+ * time is spent here, so performance is critical.
75
+ * @param index The index of the sample to return, corresponding
76
+ * to the same index in the latest sample block loaded via loadSamples().
77
+ */
78
+ get(index) {
79
+ let i = 0;
80
+ let out = 0;
81
+ let len = this.coefs.length;
82
+ let len4 = 4 * Math.floor(len / 4);
83
+ while (i < len4) {
84
+ out +=
85
+ this.coefs[i++] * this.curSamples[index++] +
86
+ this.coefs[i++] * this.curSamples[index++] +
87
+ this.coefs[i++] * this.curSamples[index++] +
88
+ this.coefs[i++] * this.curSamples[index++];
89
+ }
90
+ let len2 = 2 * Math.floor(len / 2);
91
+ while (i < len2) {
92
+ out +=
93
+ this.coefs[i++] * this.curSamples[index++] +
94
+ this.coefs[i++] * this.curSamples[index++];
95
+ }
96
+ while (i < len) {
97
+ out += this.coefs[i++] * this.curSamples[index++];
98
+ }
99
+ return out;
100
+ }
101
+ /**
102
+ * Returns a delayed sample.
103
+ * @param index The index of the relative sample to return.
104
+ */
105
+ getDelayed(index) {
106
+ return this.curSamples[index + this.center];
107
+ }
108
+ }
109
+ /** Automatic gain control for audio signals. */
110
+ export class AGC {
111
+ sampleRate;
112
+ constructor(sampleRate, timeConstantSeconds, maxGain) {
113
+ this.sampleRate = sampleRate;
114
+ this.dcBlocker = new DcBlocker(sampleRate);
115
+ this.alpha = decay(sampleRate, timeConstantSeconds);
116
+ this.counter = 0;
117
+ this.maxPower = 0;
118
+ this.maxGain = maxGain || 100;
119
+ }
120
+ dcBlocker;
121
+ alpha;
122
+ counter;
123
+ maxPower;
124
+ maxGain;
125
+ clone() {
126
+ let copy = new AGC(this.sampleRate, 1, this.maxGain);
127
+ copy.alpha = this.alpha;
128
+ return copy;
129
+ }
130
+ getDelay() {
131
+ return 0;
132
+ }
133
+ inPlace(samples) {
134
+ const alpha = this.alpha;
135
+ let maxPower = this.maxPower;
136
+ let counter = this.counter;
137
+ let gain;
138
+ this.dcBlocker.inPlace(samples);
139
+ for (let i = 0; i < samples.length; ++i) {
140
+ const v = samples[i];
141
+ const power = v * v;
142
+ if (power > 0.9 * maxPower) {
143
+ counter = this.sampleRate;
144
+ if (power > maxPower) {
145
+ maxPower = power;
146
+ }
147
+ }
148
+ else if (counter > 0) {
149
+ --counter;
150
+ }
151
+ else {
152
+ maxPower -= alpha * maxPower;
153
+ }
154
+ gain = Math.min(this.maxGain, 1 / Math.sqrt(maxPower));
155
+ samples[i] *= gain;
156
+ }
157
+ this.maxPower = maxPower;
158
+ this.counter = counter;
159
+ }
160
+ }
161
+ /** A filter that blocks DC signals. */
162
+ export class DcBlocker {
163
+ constructor(sampleRate) {
164
+ this.alpha = decay(sampleRate, 0.5);
165
+ this.dc = 0;
166
+ }
167
+ alpha;
168
+ dc;
169
+ clone() {
170
+ let copy = new DcBlocker(1000);
171
+ copy.alpha = this.alpha;
172
+ copy.dc = this.dc;
173
+ return copy;
174
+ }
175
+ getDelay() {
176
+ return 0;
177
+ }
178
+ inPlace(samples) {
179
+ const alpha = this.alpha;
180
+ let dc = this.dc;
181
+ for (let i = 0; i < samples.length; ++i) {
182
+ dc += alpha * (samples[i] - dc);
183
+ samples[i] -= dc;
184
+ }
185
+ this.dc = dc;
186
+ }
187
+ }
188
+ /**
189
+ * Returns the decay value to use in a single-pole low-pass or high-pass IIR filter
190
+ * with the given time constant.
191
+ * @param sampleRate The signal's sample rate.
192
+ * @param timeConstant The time constant in seconds
193
+ */
194
+ export function decay(sampleRate, timeConstant) {
195
+ return 1 - Math.exp(-1 / (sampleRate * timeConstant));
196
+ }
197
+ /* Returns the time constant corresponding to a -3dB frequency. */
198
+ export function frequencyToTimeConstant(freq) {
199
+ return 1 / (2 * Math.PI * freq);
200
+ }
201
+ /** IIR filter with two 'b' coefficients and one 'a' coefficient. */
202
+ class IIRFilter21 {
203
+ sampleRate;
204
+ b0;
205
+ b1;
206
+ a1;
207
+ constructor(sampleRate, b0, b1, a1) {
208
+ this.sampleRate = sampleRate;
209
+ this.b0 = b0;
210
+ this.b1 = b1;
211
+ this.a1 = a1;
212
+ this.prev = 0;
213
+ this.val = 0;
214
+ }
215
+ prev;
216
+ val;
217
+ /** Returns a copy of this filter. */
218
+ clone() {
219
+ return new IIRFilter21(this.sampleRate, this.b0, this.b1, this.a1);
220
+ }
221
+ getDelay() {
222
+ return 0;
223
+ }
224
+ /**
225
+ * Filters the given samples in place.
226
+ * @param samples The samples to filter.
227
+ */
228
+ inPlace(samples) {
229
+ let prev = this.prev;
230
+ let val = this.val;
231
+ for (let i = 0; i < samples.length; ++i) {
232
+ val = this.b0 * samples[i] + this.b1 * prev + this.a1 * val;
233
+ prev = samples[i];
234
+ samples[i] = val;
235
+ }
236
+ this.prev = prev;
237
+ this.val = val;
238
+ }
239
+ /** Filters an individual sample. */
240
+ add(sample) {
241
+ this.val = this.b0 * sample + this.b1 * this.prev + this.a1 * this.val;
242
+ this.prev = sample;
243
+ return this.val;
244
+ }
245
+ /** Returns the value currently held by the filter. */
246
+ get value() {
247
+ return this.val;
248
+ }
249
+ /** Returns the phase shift at the given angular frequency. */
250
+ phaseShift(freq) {
251
+ const w = (2 * Math.PI * freq) / this.sampleRate;
252
+ const real = Math.cos(w) * (this.b1 - this.b0 * this.a1) + this.b0 - this.b1 * this.a1;
253
+ const imag = Math.sin(w) * (this.b1 + this.b0 * this.a1);
254
+ return atan2(imag, real);
255
+ }
256
+ }
257
+ /** A FM de-emphasis filter. */
258
+ export class Deemphasis extends IIRFilter21 {
259
+ /**
260
+ * @param sampleRate The signal's sample rate.
261
+ * @param timeConstant The filter's time constant, in seconds.
262
+ */
263
+ constructor(sampleRate, timeConstant) {
264
+ const wd = 1 / (timeConstant * sampleRate);
265
+ const wa = 2 * sampleRate * Math.tan(wd / 2);
266
+ const tau = 1 / wa;
267
+ let a = 1 + 2 * tau * sampleRate;
268
+ let b = 1 - 2 * tau * sampleRate;
269
+ super(sampleRate, 1 / a, 1 / a, -b / a);
270
+ }
271
+ }
272
+ /** A FM pre-emphasis filter. */
273
+ export class Preemphasis extends IIRFilter21 {
274
+ /**
275
+ * @param sampleRate The signal's sample rate.
276
+ * @param timeConstant The filter's time constant, in seconds.
277
+ */
278
+ constructor(sampleRate, timeConstant) {
279
+ const wdl = 1 / (timeConstant * sampleRate);
280
+ const wdh = Math.PI * 0.9;
281
+ const wal = Math.tan(wdl / 2);
282
+ const wah = Math.tan(wdh / 2);
283
+ const a = wal + 1;
284
+ const b = wal - 1;
285
+ const c = wah + 1;
286
+ const d = wah - 1;
287
+ const zg = wal / wah;
288
+ super(sampleRate, a / (c * zg), b / (c * zg), -d / c);
289
+ }
290
+ }
291
+ /** A single-pole IIR low-pass filter. */
292
+ export class IIRLowPass extends Deemphasis {
293
+ /**
294
+ * @param sampleRate The signal's sample rate.
295
+ * @param freq The filter's corner frequency.
296
+ */
297
+ constructor(sampleRate, freq) {
298
+ super(sampleRate, frequencyToTimeConstant(freq));
299
+ }
300
+ }
301
+ class IIRFilterChain {
302
+ filters;
303
+ constructor(filters) {
304
+ this.filters = filters;
305
+ }
306
+ /** Returns a copy of this filter. */
307
+ clone() {
308
+ return new IIRFilterChain(this.filters.map((f) => f.clone()));
309
+ }
310
+ getDelay() {
311
+ return 0;
312
+ }
313
+ /**
314
+ * Filters the given samples in place.
315
+ * @param samples The samples to filter.
316
+ */
317
+ inPlace(samples) {
318
+ for (let f of this.filters) {
319
+ f.inPlace(samples);
320
+ }
321
+ }
322
+ /** Filters an individual sample. */
323
+ add(sample) {
324
+ for (let f of this.filters) {
325
+ sample = f.add(sample);
326
+ }
327
+ return sample;
328
+ }
329
+ /** Returns the value currently held by the filter. */
330
+ get value() {
331
+ return this.filters[this.filters.length - 1].value;
332
+ }
333
+ /** Returns the phase shift at the given frequency. */
334
+ phaseShift(freq) {
335
+ let lag = 0;
336
+ for (let f of this.filters) {
337
+ lag += f.phaseShift(freq);
338
+ }
339
+ return ((lag + Math.PI) % (2 * Math.PI)) - Math.PI;
340
+ }
341
+ }
342
+ /** A chain of single-pole IIR low-pass filters. */
343
+ export class IIRLowPassChain extends IIRFilterChain {
344
+ /**
345
+ * @param count Number of filters in the chain.
346
+ * @param sampleRate The signal's sample rate.
347
+ * @param freq The corner frequency for the whole chain.
348
+ */
349
+ constructor(count, sampleRate, freq) {
350
+ const cf = freq / Math.sqrt(Math.pow(2, 1 / count) - 1);
351
+ let filters = Array.from({ length: count }).map((_) => new IIRLowPass(sampleRate, cf));
352
+ super(filters);
353
+ }
354
+ }
355
+ /**
356
+ * Shifts IQ samples by a given frequency.
357
+ */
358
+ export class FrequencyShifter {
359
+ sampleRate;
360
+ constructor(sampleRate) {
361
+ this.sampleRate = sampleRate;
362
+ this.cosine = 1;
363
+ this.sine = 0;
364
+ }
365
+ cosine;
366
+ sine;
367
+ inPlace(I, Q, freq) {
368
+ let cosine = this.cosine;
369
+ let sine = this.sine;
370
+ const deltaCos = Math.cos((2 * Math.PI * freq) / this.sampleRate);
371
+ const deltaSin = Math.sin((2 * Math.PI * freq) / this.sampleRate);
372
+ for (let i = 0; i < I.length; ++i) {
373
+ const newI = I[i] * cosine - Q[i] * sine;
374
+ Q[i] = I[i] * sine + Q[i] * cosine;
375
+ I[i] = newI;
376
+ const newSine = cosine * deltaSin + sine * deltaCos;
377
+ cosine = cosine * deltaCos - sine * deltaSin;
378
+ sine = newSine;
379
+ }
380
+ this.cosine = cosine;
381
+ this.sine = sine;
382
+ }
383
+ }
384
+ /** A phase-locked loop that can detect a signal with a given frequency. */
385
+ export class PLL {
386
+ sampleRate;
387
+ /**
388
+ * @param sampleRate The sample rate for the input signal.
389
+ * @param freq The frequency of the signal to detect, in Hz.
390
+ * @param tolerance The frequency tolerance for the signal, in Hz.
391
+ */
392
+ constructor(sampleRate, freq, tolerance) {
393
+ this.sampleRate = sampleRate;
394
+ this.phase = 0;
395
+ this.speed = (2 * Math.PI * freq) / sampleRate;
396
+ this.maxSpeedCorr = (2 * Math.PI * tolerance) / sampleRate;
397
+ this.speedCorrection = 0;
398
+ this.phaseCorrection = 0;
399
+ this.biFlt = new IIRLowPassChain(4, sampleRate, tolerance);
400
+ this.bqFlt = new IIRLowPassChain(4, sampleRate, tolerance);
401
+ this.siFlt = new IIRLowPassChain(4, sampleRate, 7);
402
+ this.sqFlt = new IIRLowPassChain(4, sampleRate, 7);
403
+ this.piFlt = new IIRLowPassChain(4, sampleRate, 250);
404
+ this.pqFlt = new IIRLowPassChain(4, sampleRate, 250);
405
+ this.lbI = 0;
406
+ this.lbQ = 0;
407
+ this.iMagFlt = new IIRLowPass(sampleRate, 7);
408
+ this.bMagFlt = new IIRLowPass(sampleRate, 7);
409
+ this.cos = 1;
410
+ this.sin = 0;
411
+ this.locked = true;
412
+ }
413
+ phase;
414
+ speed;
415
+ maxSpeedCorr;
416
+ speedCorrection;
417
+ phaseCorrection;
418
+ biFlt;
419
+ bqFlt;
420
+ siFlt;
421
+ sqFlt;
422
+ piFlt;
423
+ pqFlt;
424
+ lbI;
425
+ lbQ;
426
+ iMagFlt;
427
+ bMagFlt;
428
+ cos;
429
+ sin;
430
+ locked;
431
+ add(sample) {
432
+ let phase = this.phase;
433
+ // Generate outputs with last computed parameters
434
+ this.cos = Math.cos(phase);
435
+ this.sin = Math.sin(phase);
436
+ // Compute I+jQ, the difference between the input and our internal oscillator
437
+ this.lbI = this.biFlt.add(Math.cos(-phase) * sample);
438
+ this.lbQ = this.bqFlt.add(Math.sin(-phase) * sample);
439
+ this.phase += this.speed;
440
+ this.add = this.addRemaining;
441
+ }
442
+ addRemaining(sample) {
443
+ let phase = this.phase;
444
+ // Generate outputs with last computed parameters
445
+ let angle = phase + this.speedCorrection + this.phaseCorrection;
446
+ this.cos = Math.cos(angle);
447
+ this.sin = Math.sin(angle);
448
+ // Compute (bI, bQ), the beat (difference) between the input and our reference oscillator
449
+ const rawI = Math.cos(-phase) * sample;
450
+ const rawQ = Math.sin(-phase) * sample;
451
+ const bI = this.biFlt.add(rawI);
452
+ const bQ = this.bqFlt.add(rawQ);
453
+ this.phase = this.phase + this.speed;
454
+ // The beat is going to lag or advance wrt the input because of the input filter chain
455
+ // Compute (sI, sQ), the average phase speed of (bI, bQ). That's the difference in frequency.
456
+ // rs = b * conj(lb)
457
+ const rsI = this.lbI * bI + this.lbQ * bQ;
458
+ const rsQ = this.lbI * bQ - bI * this.lbQ;
459
+ const sI = this.siFlt.add(rsI);
460
+ const sQ = this.sqFlt.add(rsQ);
461
+ this.lbI = bI;
462
+ this.lbQ = bQ;
463
+ const beatSpeed = atan2(sQ, sI);
464
+ const speedCorr = Math.max(-this.maxSpeedCorr, Math.min(beatSpeed, this.maxSpeedCorr));
465
+ this.speedCorrection += speedCorr;
466
+ // Compute (dI, dQ), the difference between the beat (bI, bQ) and our speed correction (cI, cQ)
467
+ // That's the difference in phase.
468
+ const cI = Math.cos(this.speedCorrection);
469
+ const cQ = Math.sin(this.speedCorrection);
470
+ // rd = b * conj(c)
471
+ const rdI = bI * cI + bQ * cQ;
472
+ const rdQ = cI * bQ - bI * cQ;
473
+ const dI = this.piFlt.add(rdI);
474
+ const dQ = this.pqFlt.add(rdQ);
475
+ // But the biFlt/bqFlt are going to shift the phase of (bI, bQ), so compensate for that.
476
+ const freqCorrectionHz = (speedCorr * this.sampleRate) / (2 * Math.PI);
477
+ const shift = this.biFlt.phaseShift(freqCorrectionHz);
478
+ const phaseDiff = atan2(dQ, dI) + shift;
479
+ this.phaseCorrection = phaseDiff;
480
+ // Check if we are locked
481
+ let mag = this.iMagFlt.add(Math.abs(sample));
482
+ let bimag = this.bMagFlt.add(Math.hypot(bI, bQ) * 2);
483
+ this.locked = bimag > mag / 15;
484
+ }
485
+ }
486
+ //# sourceMappingURL=filters.js.map