@jtarrio/webrtlsdr 1.1.1 → 2.0.0-pre1
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.
- package/dist/demod/demod-am.d.ts +45 -0
- package/dist/demod/demod-am.d.ts.map +1 -0
- package/dist/demod/{scheme-am.js → demod-am.js} +36 -3
- package/dist/demod/demod-am.js.map +1 -0
- package/dist/demod/demod-cw.d.ts +36 -0
- package/dist/demod/demod-cw.d.ts.map +1 -0
- package/dist/demod/{scheme-cw.js → demod-cw.js} +30 -2
- package/dist/demod/demod-cw.js.map +1 -0
- package/dist/demod/demod-nbfm.d.ts +45 -0
- package/dist/demod/demod-nbfm.d.ts.map +1 -0
- package/dist/demod/{scheme-nbfm.js → demod-nbfm.js} +36 -3
- package/dist/demod/demod-nbfm.js.map +1 -0
- package/dist/demod/demod-ssb.d.ts +45 -0
- package/dist/demod/demod-ssb.d.ts.map +1 -0
- package/dist/demod/{scheme-ssb.js → demod-ssb.js} +36 -4
- package/dist/demod/demod-ssb.js.map +1 -0
- package/dist/demod/demod-wbfm.d.ts +95 -0
- package/dist/demod/demod-wbfm.d.ts.map +1 -0
- package/dist/demod/demod-wbfm.js +205 -0
- package/dist/demod/demod-wbfm.js.map +1 -0
- package/dist/demod/demodulator.d.ts +2 -62
- package/dist/demod/demodulator.d.ts.map +1 -1
- package/dist/demod/demodulator.js +15 -137
- package/dist/demod/demodulator.js.map +1 -1
- package/dist/demod/empty-demodulator.d.ts +62 -0
- package/dist/demod/empty-demodulator.d.ts.map +1 -0
- package/dist/demod/empty-demodulator.js +145 -0
- package/dist/demod/empty-demodulator.js.map +1 -0
- package/dist/demod/modes.d.ts +76 -65
- package/dist/demod/modes.d.ts.map +1 -1
- package/dist/demod/modes.js +59 -99
- package/dist/demod/modes.js.map +1 -1
- package/package.json +1 -1
- package/dist/demod/scheme-am.d.ts +0 -32
- package/dist/demod/scheme-am.d.ts.map +0 -1
- package/dist/demod/scheme-am.js.map +0 -1
- package/dist/demod/scheme-cw.d.ts +0 -22
- package/dist/demod/scheme-cw.d.ts.map +0 -1
- package/dist/demod/scheme-cw.js.map +0 -1
- package/dist/demod/scheme-nbfm.d.ts +0 -32
- package/dist/demod/scheme-nbfm.d.ts.map +0 -1
- package/dist/demod/scheme-nbfm.js.map +0 -1
- package/dist/demod/scheme-ssb.d.ts +0 -33
- package/dist/demod/scheme-ssb.d.ts.map +0 -1
- package/dist/demod/scheme-ssb.js.map +0 -1
- package/dist/demod/scheme-wbfm.d.ts +0 -38
- package/dist/demod/scheme-wbfm.d.ts.map +0 -1
- package/dist/demod/scheme-wbfm.js +0 -106
- package/dist/demod/scheme-wbfm.js.map +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Copyright 2013 Google Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { makeLowPassKernel } from "../dsp/coefficients";
|
|
15
|
+
import { FMDemodulator, StereoSeparator } from "../dsp/demodulators";
|
|
16
|
+
import { FrequencyShifter, Deemphasizer, FIRFilter } from "../dsp/filters";
|
|
17
|
+
import { getPower } from "../dsp/power";
|
|
18
|
+
import { ComplexDownsampler, RealDownsampler } from "../dsp/resamplers";
|
|
19
|
+
import { Configurator } from "./modes";
|
|
20
|
+
/** A demodulator for wideband FM signals. */
|
|
21
|
+
export class DemodWBFM {
|
|
22
|
+
mode;
|
|
23
|
+
/**
|
|
24
|
+
* @param inRate The sample rate of the input samples.
|
|
25
|
+
* @param outRate The sample rate of the output samples.
|
|
26
|
+
* @param mode The mode to use initially.
|
|
27
|
+
*/
|
|
28
|
+
constructor(inRate, outRate, mode) {
|
|
29
|
+
this.mode = mode;
|
|
30
|
+
let interRate = Math.min(inRate, 336000);
|
|
31
|
+
this.stage1 = new DemodWBFMStage1(inRate, interRate, mode);
|
|
32
|
+
this.stage2 = new DemodWBFMStage2(interRate, outRate, mode);
|
|
33
|
+
}
|
|
34
|
+
stage1;
|
|
35
|
+
stage2;
|
|
36
|
+
getMode() {
|
|
37
|
+
return this.mode;
|
|
38
|
+
}
|
|
39
|
+
setMode(mode) {
|
|
40
|
+
this.mode = mode;
|
|
41
|
+
this.stage1.setMode(mode);
|
|
42
|
+
this.stage2.setMode(mode);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Demodulates the signal.
|
|
46
|
+
* @param samplesI The I components of the samples.
|
|
47
|
+
* @param samplesQ The Q components of the samples.
|
|
48
|
+
* @param freqOffset The offset of the signal in the samples.
|
|
49
|
+
* @returns The demodulated audio signal.
|
|
50
|
+
*/
|
|
51
|
+
demodulate(samplesI, samplesQ, freqOffset) {
|
|
52
|
+
let o1 = this.stage1.demodulate(samplesI, samplesQ, freqOffset);
|
|
53
|
+
let o2 = this.stage2.demodulate(o1.left);
|
|
54
|
+
o2.snr = o1.snr;
|
|
55
|
+
return o2;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* First stage demodulator for wideband FM signals.
|
|
60
|
+
* Returns the raw demodulated FM signal, with stereo pilot, difference signal and RDS, if they exist.
|
|
61
|
+
* The output is duplicated in the left and right channels.
|
|
62
|
+
*/
|
|
63
|
+
export class DemodWBFMStage1 {
|
|
64
|
+
outRate;
|
|
65
|
+
mode;
|
|
66
|
+
/**
|
|
67
|
+
* @param inRate The sample rate of the input samples.
|
|
68
|
+
* @param outRate The sample rate of the output audio.
|
|
69
|
+
* @param mode The mode to use initially.
|
|
70
|
+
*/
|
|
71
|
+
constructor(inRate, outRate, mode) {
|
|
72
|
+
this.outRate = outRate;
|
|
73
|
+
this.mode = mode;
|
|
74
|
+
const maxF = 75000;
|
|
75
|
+
this.shifter = new FrequencyShifter(inRate);
|
|
76
|
+
if (inRate != outRate) {
|
|
77
|
+
this.downsampler = new ComplexDownsampler(inRate, outRate, 151);
|
|
78
|
+
}
|
|
79
|
+
const kernel = makeLowPassKernel(outRate, maxF, 151);
|
|
80
|
+
this.filterI = new FIRFilter(kernel);
|
|
81
|
+
this.filterQ = new FIRFilter(kernel);
|
|
82
|
+
this.demodulator = new FMDemodulator(maxF / outRate);
|
|
83
|
+
}
|
|
84
|
+
shifter;
|
|
85
|
+
downsampler;
|
|
86
|
+
filterI;
|
|
87
|
+
filterQ;
|
|
88
|
+
demodulator;
|
|
89
|
+
getMode() {
|
|
90
|
+
return this.mode;
|
|
91
|
+
}
|
|
92
|
+
setMode(mode) {
|
|
93
|
+
this.mode = mode;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Demodulates the signal.
|
|
97
|
+
* @param samplesI The I components of the samples.
|
|
98
|
+
* @param samplesQ The Q components of the samples.
|
|
99
|
+
* @param freqOffset The offset of the signal in the samples.
|
|
100
|
+
* @returns The demodulated audio signal.
|
|
101
|
+
*/
|
|
102
|
+
demodulate(samplesI, samplesQ, freqOffset) {
|
|
103
|
+
this.shifter.inPlace(samplesI, samplesQ, -freqOffset);
|
|
104
|
+
let [I, Q] = this.downsampler
|
|
105
|
+
? this.downsampler.downsample(samplesI, samplesQ)
|
|
106
|
+
: [samplesI, samplesQ];
|
|
107
|
+
let allPower = getPower(I, Q);
|
|
108
|
+
this.filterI.inPlace(I);
|
|
109
|
+
this.filterQ.inPlace(Q);
|
|
110
|
+
let signalPower = (getPower(I, Q) * this.outRate) / 150000;
|
|
111
|
+
this.demodulator.demodulate(I, Q, I);
|
|
112
|
+
return {
|
|
113
|
+
left: I,
|
|
114
|
+
right: new Float32Array(I),
|
|
115
|
+
stereo: false,
|
|
116
|
+
snr: signalPower / allPower,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Second stage demodulator for wideband FM signals.
|
|
122
|
+
* Takes the output of stage 1 and does stereo extraction.
|
|
123
|
+
* Only the I channel is used; Q channel is ignored.
|
|
124
|
+
* */
|
|
125
|
+
export class DemodWBFMStage2 {
|
|
126
|
+
mode;
|
|
127
|
+
/**
|
|
128
|
+
* @param inRate The sample rate of the input samples.
|
|
129
|
+
* @param outRate The sample rate of the output audio.
|
|
130
|
+
* @param mode The mode to use initially.
|
|
131
|
+
*/
|
|
132
|
+
constructor(inRate, outRate, mode) {
|
|
133
|
+
this.mode = mode;
|
|
134
|
+
const pilotF = 19000;
|
|
135
|
+
const deemphTc = 50;
|
|
136
|
+
this.monoSampler = new RealDownsampler(inRate, outRate, 41);
|
|
137
|
+
this.stereoSampler = new RealDownsampler(inRate, outRate, 41);
|
|
138
|
+
this.stereoSeparator = new StereoSeparator(inRate, pilotF);
|
|
139
|
+
this.leftDeemph = new Deemphasizer(outRate, deemphTc);
|
|
140
|
+
this.rightDeemph = new Deemphasizer(outRate, deemphTc);
|
|
141
|
+
}
|
|
142
|
+
monoSampler;
|
|
143
|
+
stereoSampler;
|
|
144
|
+
stereoSeparator;
|
|
145
|
+
leftDeemph;
|
|
146
|
+
rightDeemph;
|
|
147
|
+
getMode() {
|
|
148
|
+
return this.mode;
|
|
149
|
+
}
|
|
150
|
+
setMode(mode) {
|
|
151
|
+
this.mode = mode;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Demodulates the signal.
|
|
155
|
+
* @param samplesI The I components of the samples.
|
|
156
|
+
* @returns The demodulated audio signal.
|
|
157
|
+
*/
|
|
158
|
+
demodulate(samplesI) {
|
|
159
|
+
const leftAudio = this.monoSampler.downsample(samplesI);
|
|
160
|
+
const rightAudio = new Float32Array(leftAudio);
|
|
161
|
+
let stereoOut = false;
|
|
162
|
+
if (this.mode.stereo) {
|
|
163
|
+
const stereo = this.stereoSeparator.separate(samplesI);
|
|
164
|
+
if (stereo.found) {
|
|
165
|
+
stereoOut = true;
|
|
166
|
+
const diffAudio = this.stereoSampler.downsample(stereo.diff);
|
|
167
|
+
for (let i = 0; i < diffAudio.length; ++i) {
|
|
168
|
+
rightAudio[i] -= diffAudio[i];
|
|
169
|
+
leftAudio[i] += diffAudio[i];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.leftDeemph.inPlace(leftAudio);
|
|
174
|
+
this.rightDeemph.inPlace(rightAudio);
|
|
175
|
+
return {
|
|
176
|
+
left: leftAudio,
|
|
177
|
+
right: rightAudio,
|
|
178
|
+
stereo: stereoOut,
|
|
179
|
+
snr: 1,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Configurator for the WBFM mode. */
|
|
184
|
+
export class ConfigWBFM extends Configurator {
|
|
185
|
+
constructor(mode) {
|
|
186
|
+
super(mode);
|
|
187
|
+
}
|
|
188
|
+
create() {
|
|
189
|
+
return { scheme: "WBFM", stereo: true };
|
|
190
|
+
}
|
|
191
|
+
hasStereo() {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
getStereo() {
|
|
195
|
+
return this.mode.stereo;
|
|
196
|
+
}
|
|
197
|
+
setStereo(stereo) {
|
|
198
|
+
this.mode = { ...this.mode, stereo: stereo };
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
getBandwidth() {
|
|
202
|
+
return 150000;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=demod-wbfm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demod-wbfm.js","sourceRoot":"","sources":["../../src/demod/demod-wbfm.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,63 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* it into an audio signal.
|
|
4
|
-
*
|
|
5
|
-
* The demodulator parameters (scheme, bandwidth, etc) are settable
|
|
6
|
-
* on the fly.
|
|
7
|
-
*
|
|
8
|
-
* Whenever a parameter is changed, the demodulator emits a
|
|
9
|
-
* 'demodulator' event containing the new value. This makes it easy
|
|
10
|
-
* to observe the demodulator's state.
|
|
11
|
-
*/
|
|
12
|
-
import { Mode } from "./modes";
|
|
13
|
-
import { Player } from "./player";
|
|
14
|
-
import { SampleReceiver } from "../radio";
|
|
15
|
-
/** The demodulator class. */
|
|
16
|
-
export declare class Demodulator extends EventTarget implements SampleReceiver {
|
|
17
|
-
/**
|
|
18
|
-
* @param player The player to use. If undefined, an AudioPlayer will be used.
|
|
19
|
-
*/
|
|
20
|
-
constructor(player?: Player);
|
|
21
|
-
/** The sample rate. */
|
|
22
|
-
private inRate;
|
|
23
|
-
/** The audio output device. */
|
|
24
|
-
private player;
|
|
25
|
-
/** Controller that silences the output if the SNR is low. */
|
|
26
|
-
private squelchControl;
|
|
27
|
-
/** The modulation parameters as a Mode object. */
|
|
28
|
-
private mode;
|
|
29
|
-
/** The demodulator class. */
|
|
30
|
-
private scheme;
|
|
31
|
-
/** The frequency offset to demodulate from. */
|
|
32
|
-
private frequencyOffset;
|
|
33
|
-
/** Whether the latest samples were in stereo. */
|
|
34
|
-
private latestStereo;
|
|
35
|
-
/** A frequency change we are expecting. */
|
|
36
|
-
private expectingFrequency?;
|
|
37
|
-
/** Changes the modulation parameters. */
|
|
38
|
-
setMode(mode: Mode): void;
|
|
39
|
-
/** Returns the current modulation parameters. */
|
|
40
|
-
getMode(): Mode;
|
|
41
|
-
/** Changes the frequency offset. */
|
|
42
|
-
setFrequencyOffset(offset: number): void;
|
|
43
|
-
/** Returns the current frequency offset. */
|
|
44
|
-
getFrequencyOffset(): number;
|
|
45
|
-
/** Waits until samples arrive with the given center frequency and then sets the offset. */
|
|
46
|
-
expectFrequencyAndSetOffset(center: number, offset: number): void;
|
|
47
|
-
/** Sets the audio volume level, from 0 to 1. */
|
|
48
|
-
setVolume(volume: number): void;
|
|
49
|
-
/** Returns the current audio volume level. */
|
|
50
|
-
getVolume(): number;
|
|
51
|
-
/** Returns an appropriate instance of ModulationScheme for the requested mode. */
|
|
52
|
-
private getScheme;
|
|
53
|
-
/** Changes the sample rate. */
|
|
54
|
-
setSampleRate(sampleRate: number): void;
|
|
55
|
-
/** Receives radio samples. */
|
|
56
|
-
receiveSamples(I: Float32Array, Q: Float32Array, frequency: number): void;
|
|
57
|
-
addEventListener(type: "stereo-status", callback: (e: StereoStatusEvent) => void | null, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
58
|
-
addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
59
|
-
}
|
|
60
|
-
export declare class StereoStatusEvent extends CustomEvent<boolean> {
|
|
61
|
-
constructor(stereo: boolean);
|
|
62
|
-
}
|
|
1
|
+
/** Exports the `Demodulator` class with all the demodulation schemes already registered. */
|
|
2
|
+
export * from "./empty-demodulator";
|
|
63
3
|
//# sourceMappingURL=demodulator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demodulator.d.ts","sourceRoot":"","sources":["../../src/demod/demodulator.ts"],"names":[],"mappings":"AAcA
|
|
1
|
+
{"version":3,"file":"demodulator.d.ts","sourceRoot":"","sources":["../../src/demod/demodulator.ts"],"names":[],"mappings":"AAcA,4FAA4F;AAE5F,cAAc,qBAAqB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright
|
|
1
|
+
// Copyright 2025 Jacobo Tarrio Barreiro. All rights reserved.
|
|
2
2
|
//
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
// you may not use this file except in compliance with the License.
|
|
@@ -11,140 +11,18 @@
|
|
|
11
11
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.player = player ? player : new AudioPlayer();
|
|
29
|
-
this.squelchControl = new SquelchControl(this.player.sampleRate);
|
|
30
|
-
this.mode = { scheme: "WBFM", stereo: true };
|
|
31
|
-
this.scheme = this.getScheme(this.mode);
|
|
32
|
-
this.frequencyOffset = 0;
|
|
33
|
-
this.latestStereo = false;
|
|
34
|
-
}
|
|
35
|
-
/** The sample rate. */
|
|
36
|
-
inRate;
|
|
37
|
-
/** The audio output device. */
|
|
38
|
-
player;
|
|
39
|
-
/** Controller that silences the output if the SNR is low. */
|
|
40
|
-
squelchControl;
|
|
41
|
-
/** The modulation parameters as a Mode object. */
|
|
42
|
-
mode;
|
|
43
|
-
/** The demodulator class. */
|
|
44
|
-
scheme;
|
|
45
|
-
/** The frequency offset to demodulate from. */
|
|
46
|
-
frequencyOffset;
|
|
47
|
-
/** Whether the latest samples were in stereo. */
|
|
48
|
-
latestStereo;
|
|
49
|
-
/** A frequency change we are expecting. */
|
|
50
|
-
expectingFrequency;
|
|
51
|
-
/** Changes the modulation parameters. */
|
|
52
|
-
setMode(mode) {
|
|
53
|
-
this.scheme = this.getScheme(mode, this.scheme);
|
|
54
|
-
this.mode = mode;
|
|
55
|
-
}
|
|
56
|
-
/** Returns the current modulation parameters. */
|
|
57
|
-
getMode() {
|
|
58
|
-
return this.mode;
|
|
59
|
-
}
|
|
60
|
-
/** Changes the frequency offset. */
|
|
61
|
-
setFrequencyOffset(offset) {
|
|
62
|
-
this.frequencyOffset = offset;
|
|
63
|
-
}
|
|
64
|
-
/** Returns the current frequency offset. */
|
|
65
|
-
getFrequencyOffset() {
|
|
66
|
-
return this.frequencyOffset;
|
|
67
|
-
}
|
|
68
|
-
/** Waits until samples arrive with the given center frequency and then sets the offset. */
|
|
69
|
-
expectFrequencyAndSetOffset(center, offset) {
|
|
70
|
-
this.expectingFrequency = { center, offset };
|
|
71
|
-
}
|
|
72
|
-
/** Sets the audio volume level, from 0 to 1. */
|
|
73
|
-
setVolume(volume) {
|
|
74
|
-
this.player.setVolume(volume);
|
|
75
|
-
}
|
|
76
|
-
/** Returns the current audio volume level. */
|
|
77
|
-
getVolume() {
|
|
78
|
-
return this.player.getVolume();
|
|
79
|
-
}
|
|
80
|
-
/** Returns an appropriate instance of ModulationScheme for the requested mode. */
|
|
81
|
-
getScheme(mode, scheme) {
|
|
82
|
-
if (mode.scheme == scheme?.getMode().scheme) {
|
|
83
|
-
scheme.setMode(mode);
|
|
84
|
-
return scheme;
|
|
85
|
-
}
|
|
86
|
-
switch (mode.scheme) {
|
|
87
|
-
case "AM":
|
|
88
|
-
return new SchemeAM(this.inRate, this.player.sampleRate, mode);
|
|
89
|
-
case "NBFM":
|
|
90
|
-
return new SchemeNBFM(this.inRate, this.player.sampleRate, mode);
|
|
91
|
-
case "WBFM":
|
|
92
|
-
return new SchemeWBFM(this.inRate, this.player.sampleRate, mode);
|
|
93
|
-
case "LSB":
|
|
94
|
-
case "USB":
|
|
95
|
-
return new SchemeSSB(this.inRate, this.player.sampleRate, mode);
|
|
96
|
-
case "CW":
|
|
97
|
-
return new SchemeCW(this.inRate, this.player.sampleRate, mode);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/** Changes the sample rate. */
|
|
101
|
-
setSampleRate(sampleRate) {
|
|
102
|
-
this.inRate = sampleRate;
|
|
103
|
-
this.scheme = this.getScheme(this.mode, undefined);
|
|
104
|
-
}
|
|
105
|
-
/** Receives radio samples. */
|
|
106
|
-
receiveSamples(I, Q, frequency) {
|
|
107
|
-
if (this.expectingFrequency?.center === frequency) {
|
|
108
|
-
this.frequencyOffset = this.expectingFrequency.offset;
|
|
109
|
-
this.expectingFrequency = undefined;
|
|
110
|
-
}
|
|
111
|
-
let { left, right, stereo, snr } = this.scheme.demodulate(I, Q, this.frequencyOffset);
|
|
112
|
-
this.squelchControl.applySquelch(this.mode, left, right, snr);
|
|
113
|
-
this.player.play(left, right);
|
|
114
|
-
if (stereo != this.latestStereo) {
|
|
115
|
-
this.dispatchEvent(new StereoStatusEvent(stereo));
|
|
116
|
-
this.latestStereo = stereo;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
addEventListener(type, callback, options) {
|
|
120
|
-
super.addEventListener(type, callback, options);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
export class StereoStatusEvent extends CustomEvent {
|
|
124
|
-
constructor(stereo) {
|
|
125
|
-
super("stereo-status", { detail: stereo, bubbles: true, composed: true });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
class SquelchControl {
|
|
129
|
-
sampleRate;
|
|
130
|
-
constructor(sampleRate) {
|
|
131
|
-
this.sampleRate = sampleRate;
|
|
132
|
-
}
|
|
133
|
-
countdown = 0;
|
|
134
|
-
applySquelch(mode, left, right, snr) {
|
|
135
|
-
const SQUELCH_TAIL = 0.1;
|
|
136
|
-
if (mode.scheme == "WBFM" || mode.scheme == "CW")
|
|
137
|
-
return;
|
|
138
|
-
if (mode.squelch < snr) {
|
|
139
|
-
this.countdown = SQUELCH_TAIL * this.sampleRate;
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (this.countdown > 0) {
|
|
143
|
-
this.countdown -= left.length;
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
left.fill(0);
|
|
147
|
-
right.fill(0);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
14
|
+
/** Exports the `Demodulator` class with all the demodulation schemes already registered. */
|
|
15
|
+
export * from "./empty-demodulator";
|
|
16
|
+
import { registerDemod } from "./modes";
|
|
17
|
+
import { ConfigAM, DemodAM } from "./demod-am";
|
|
18
|
+
import { ConfigCW, DemodCW } from "./demod-cw";
|
|
19
|
+
import { ConfigNBFM, DemodNBFM } from "./demod-nbfm";
|
|
20
|
+
import { ConfigSSB, DemodSSB } from "./demod-ssb";
|
|
21
|
+
import { ConfigWBFM, DemodWBFM } from "./demod-wbfm";
|
|
22
|
+
registerDemod("WBFM", DemodWBFM, ConfigWBFM);
|
|
23
|
+
registerDemod("NBFM", DemodNBFM, ConfigNBFM);
|
|
24
|
+
registerDemod("AM", DemodAM, ConfigAM);
|
|
25
|
+
registerDemod("USB", DemodSSB, ConfigSSB);
|
|
26
|
+
registerDemod("LSB", DemodSSB, ConfigSSB);
|
|
27
|
+
registerDemod("CW", DemodCW, ConfigCW);
|
|
150
28
|
//# sourceMappingURL=demodulator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demodulator.js","sourceRoot":"","sources":["../../src/demod/demodulator.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;
|
|
1
|
+
{"version":3,"file":"demodulator.js","sourceRoot":"","sources":["../../src/demod/demodulator.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,4FAA4F;AAE5F,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAErD,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC7C,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC7C,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AACvC,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC1C,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC1C,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Mode } from "./modes";
|
|
2
|
+
import { Player } from "./player";
|
|
3
|
+
import { SampleReceiver } from "../radio";
|
|
4
|
+
/**
|
|
5
|
+
* A class that takes a stream of radio samples and demodulates
|
|
6
|
+
* it into an audio signal.
|
|
7
|
+
*
|
|
8
|
+
* The demodulator parameters (scheme, bandwidth, etc) are settable
|
|
9
|
+
* on the fly.
|
|
10
|
+
*
|
|
11
|
+
* Whenever a parameter is changed, the demodulator emits a
|
|
12
|
+
* 'demodulator' event containing the new value. This makes it easy
|
|
13
|
+
* to observe the demodulator's state.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Demodulator extends EventTarget implements SampleReceiver {
|
|
16
|
+
/**
|
|
17
|
+
* @param player The player to use. If undefined, an AudioPlayer will be used.
|
|
18
|
+
*/
|
|
19
|
+
constructor(player?: Player);
|
|
20
|
+
/** The sample rate. */
|
|
21
|
+
private inRate;
|
|
22
|
+
/** The audio output device. */
|
|
23
|
+
private player;
|
|
24
|
+
/** Controller that silences the output if the SNR is low. */
|
|
25
|
+
private squelchControl;
|
|
26
|
+
/** The modulation parameters as a Mode object. */
|
|
27
|
+
private mode;
|
|
28
|
+
/** The demodulator class. */
|
|
29
|
+
private demod;
|
|
30
|
+
/** The frequency offset to demodulate from. */
|
|
31
|
+
private frequencyOffset;
|
|
32
|
+
/** Whether the latest samples were in stereo. */
|
|
33
|
+
private latestStereo;
|
|
34
|
+
/** A frequency change we are expecting. */
|
|
35
|
+
private expectingFrequency?;
|
|
36
|
+
/** Changes the modulation parameters. */
|
|
37
|
+
setMode(mode: Mode): void;
|
|
38
|
+
/** Returns the current modulation parameters. */
|
|
39
|
+
getMode(): Mode;
|
|
40
|
+
/** Changes the frequency offset. */
|
|
41
|
+
setFrequencyOffset(offset: number): void;
|
|
42
|
+
/** Returns the current frequency offset. */
|
|
43
|
+
getFrequencyOffset(): number;
|
|
44
|
+
/** Waits until samples arrive with the given center frequency and then sets the offset. */
|
|
45
|
+
expectFrequencyAndSetOffset(center: number, offset: number): void;
|
|
46
|
+
/** Sets the audio volume level, from 0 to 1. */
|
|
47
|
+
setVolume(volume: number): void;
|
|
48
|
+
/** Returns the current audio volume level. */
|
|
49
|
+
getVolume(): number;
|
|
50
|
+
/** Returns an appropriate instance of Scheme for the requested mode. */
|
|
51
|
+
private getScheme;
|
|
52
|
+
/** Changes the sample rate. */
|
|
53
|
+
setSampleRate(sampleRate: number): void;
|
|
54
|
+
/** Receives radio samples. */
|
|
55
|
+
receiveSamples(I: Float32Array, Q: Float32Array, frequency: number): void;
|
|
56
|
+
addEventListener(type: "stereo-status", callback: (e: StereoStatusEvent) => void | null, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
57
|
+
addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined): void;
|
|
58
|
+
}
|
|
59
|
+
export declare class StereoStatusEvent extends CustomEvent<boolean> {
|
|
60
|
+
constructor(stereo: boolean);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=empty-demodulator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-demodulator.d.ts","sourceRoot":"","sources":["../../src/demod/empty-demodulator.ts"],"names":[],"mappings":"AAcA,OAAO,EAAS,IAAI,EAAqC,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,qBAAa,WAAY,SAAQ,WAAY,YAAW,cAAc;IACpE;;OAEG;gBACS,MAAM,CAAC,EAAE,MAAM;IAW3B,uBAAuB;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,+BAA+B;IAC/B,OAAO,CAAC,MAAM,CAAS;IACvB,6DAA6D;IAC7D,OAAO,CAAC,cAAc,CAAiB;IACvC,kDAAkD;IAClD,OAAO,CAAC,IAAI,CAAO;IACnB,6BAA6B;IAC7B,OAAO,CAAC,KAAK,CAAa;IAC1B,+CAA+C;IAC/C,OAAO,CAAC,eAAe,CAAS;IAChC,iDAAiD;IACjD,OAAO,CAAC,YAAY,CAAU;IAC9B,2CAA2C;IAC3C,OAAO,CAAC,kBAAkB,CAAC,CAAY;IAEvC,yCAAyC;IACzC,OAAO,CAAC,IAAI,EAAE,IAAI;IAKlB,iDAAiD;IACjD,OAAO,IAAI,IAAI;IAIf,oCAAoC;IACpC,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAIjC,4CAA4C;IAC5C,kBAAkB;IAIlB,2FAA2F;IAC3F,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAI1D,gDAAgD;IAChD,SAAS,CAAC,MAAM,EAAE,MAAM;IAIxB,8CAA8C;IAC9C,SAAS;IAIT,wEAAwE;IACxE,OAAO,CAAC,SAAS;IASjB,+BAA+B;IAC/B,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAKvC,8BAA8B;IAC9B,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAmBzE,gBAAgB,CACd,IAAI,EAAE,eAAe,EACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,GAAG,IAAI,EAC/C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAAG,SAAS,GACtD,IAAI;IACP,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAAG,SAAS,GACtD,IAAI;CAYR;AAED,qBAAa,iBAAkB,SAAQ,WAAW,CAAC,OAAO,CAAC;gBAC7C,MAAM,EAAE,OAAO;CAG5B"}
|