@e04/ft8ts 0.0.9 → 0.0.10
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/README.md +27 -26
- package/dist/cli.js +806 -610
- package/dist/cli.js.map +1 -1
- package/dist/ft8ts.cjs +813 -617
- package/dist/ft8ts.cjs.map +1 -1
- package/dist/ft8ts.d.ts +1 -0
- package/dist/ft8ts.mjs +813 -617
- package/dist/ft8ts.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +3 -0
- package/src/ft4/constants.ts +0 -38
- package/src/ft4/decode.ts +297 -338
- package/src/ft4/encode.ts +6 -1
- package/src/ft4/scramble.ts +6 -1
- package/src/ft8/constants.ts +0 -11
- package/src/ft8/decode.ts +530 -270
- package/src/util/constants.ts +0 -4
- package/src/util/decode174_91.ts +61 -55
- package/src/util/fft.ts +97 -37
- package/src/util/waveform.ts +8 -4
package/src/ft4/decode.ts
CHANGED
|
@@ -3,29 +3,44 @@ import { decode174_91 } from "../util/decode174_91.js";
|
|
|
3
3
|
import { fftComplex } from "../util/fft.js";
|
|
4
4
|
import type { HashCallBook } from "../util/hashcall.js";
|
|
5
5
|
import { unpack77 } from "../util/unpack_jt77.js";
|
|
6
|
-
import {
|
|
7
|
-
COSTAS_A,
|
|
8
|
-
COSTAS_B,
|
|
9
|
-
COSTAS_C,
|
|
10
|
-
COSTAS_D,
|
|
11
|
-
FS2,
|
|
12
|
-
GRAYMAP,
|
|
13
|
-
HARD_SYNC_PATTERNS,
|
|
14
|
-
MAX_FREQ,
|
|
15
|
-
NDOWN,
|
|
16
|
-
NFFT1,
|
|
17
|
-
NFFT2,
|
|
18
|
-
NH1,
|
|
19
|
-
NHSYM,
|
|
20
|
-
NMAX,
|
|
21
|
-
NN,
|
|
22
|
-
NSPS,
|
|
23
|
-
NSS,
|
|
24
|
-
SYNC_PASS_MIN,
|
|
25
|
-
TWO_PI,
|
|
26
|
-
} from "./constants.js";
|
|
6
|
+
import { GRAYMAP } from "./constants.js";
|
|
27
7
|
import { xorWithScrambler } from "./scramble.js";
|
|
28
8
|
|
|
9
|
+
const COSTAS_A = [0, 1, 3, 2] as const;
|
|
10
|
+
const COSTAS_B = [1, 0, 2, 3] as const;
|
|
11
|
+
const COSTAS_C = [2, 3, 1, 0] as const;
|
|
12
|
+
const COSTAS_D = [3, 2, 0, 1] as const;
|
|
13
|
+
const NSPS = 576;
|
|
14
|
+
const NFFT1 = 4 * NSPS; // 2304
|
|
15
|
+
const NH1 = NFFT1 / 2; // 1152
|
|
16
|
+
const NMAX = 21 * 3456; // 72576
|
|
17
|
+
const NHSYM = Math.floor((NMAX - NFFT1) / NSPS); // 122
|
|
18
|
+
const NDOWN = 18;
|
|
19
|
+
const NN = 103;
|
|
20
|
+
const NFFT2 = NMAX / NDOWN; // 4032
|
|
21
|
+
const NSS = NSPS / NDOWN; // 32
|
|
22
|
+
const FS2 = SAMPLE_RATE / NDOWN; // 666.67 Hz
|
|
23
|
+
const MAX_FREQ = 4910;
|
|
24
|
+
const SYNC_PASS_MIN = 1.2;
|
|
25
|
+
const TWO_PI = 2 * Math.PI;
|
|
26
|
+
const HARD_SYNC_PATTERNS = [
|
|
27
|
+
{ offset: 0, bits: [0, 0, 0, 1, 1, 0, 1, 1] as const },
|
|
28
|
+
{ offset: 66, bits: [0, 1, 0, 0, 1, 1, 1, 0] as const },
|
|
29
|
+
{ offset: 132, bits: [1, 1, 1, 0, 0, 1, 0, 0] as const },
|
|
30
|
+
{ offset: 198, bits: [1, 0, 1, 1, 0, 0, 0, 1] as const },
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
const COSTAS_BLOCKS = 4;
|
|
34
|
+
const FT4_SYNC_STRIDE = 33 * NSS;
|
|
35
|
+
const FT4_MAX_TWEAK = 16;
|
|
36
|
+
const LDPC_BITS = 174;
|
|
37
|
+
const BITMETRIC_LEN = 2 * NN;
|
|
38
|
+
const FRAME_LEN = NN * NSS;
|
|
39
|
+
|
|
40
|
+
const NUTTALL_WINDOW = makeNuttallWindow(NFFT1);
|
|
41
|
+
const DOWNSAMPLE_CTX = createDownsampleContext();
|
|
42
|
+
const TWEAKED_SYNC_TEMPLATES = createTweakedSyncTemplates();
|
|
43
|
+
|
|
29
44
|
export interface DecodedMessage {
|
|
30
45
|
freq: number;
|
|
31
46
|
dt: number;
|
|
@@ -59,11 +74,6 @@ interface Candidate {
|
|
|
59
74
|
sync: number;
|
|
60
75
|
}
|
|
61
76
|
|
|
62
|
-
interface ComplexBuffer {
|
|
63
|
-
re: Float64Array;
|
|
64
|
-
im: Float64Array;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
77
|
interface DownsampleContext {
|
|
68
78
|
df: number;
|
|
69
79
|
window: Float64Array;
|
|
@@ -76,11 +86,33 @@ interface SyncTemplate {
|
|
|
76
86
|
|
|
77
87
|
type SyncTemplates = [SyncTemplate, SyncTemplate, SyncTemplate, SyncTemplate];
|
|
78
88
|
|
|
79
|
-
interface
|
|
89
|
+
interface DecodeWorkspace {
|
|
90
|
+
coarseRe: Float64Array;
|
|
91
|
+
coarseIm: Float64Array;
|
|
92
|
+
fineRe: Float64Array;
|
|
93
|
+
fineIm: Float64Array;
|
|
94
|
+
frameRe: Float64Array;
|
|
95
|
+
frameIm: Float64Array;
|
|
96
|
+
symbRe: Float64Array;
|
|
97
|
+
symbIm: Float64Array;
|
|
98
|
+
csRe: Float64Array;
|
|
99
|
+
csIm: Float64Array;
|
|
100
|
+
s4: Float64Array;
|
|
101
|
+
s2: Float64Array;
|
|
80
102
|
bitmetrics1: Float64Array;
|
|
81
103
|
bitmetrics2: Float64Array;
|
|
82
104
|
bitmetrics3: Float64Array;
|
|
83
|
-
|
|
105
|
+
llra: Float64Array;
|
|
106
|
+
llrb: Float64Array;
|
|
107
|
+
llrc: Float64Array;
|
|
108
|
+
llr: Float64Array;
|
|
109
|
+
apmask: Int8Array;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface CandidateSearchResult {
|
|
113
|
+
ibest: number;
|
|
114
|
+
idfbest: number;
|
|
115
|
+
smax: number;
|
|
84
116
|
}
|
|
85
117
|
|
|
86
118
|
/**
|
|
@@ -101,44 +133,26 @@ export function decode(
|
|
|
101
133
|
|
|
102
134
|
const dd =
|
|
103
135
|
sampleRate === SAMPLE_RATE
|
|
104
|
-
?
|
|
136
|
+
? copySamplesToDecodeWindow(samples)
|
|
105
137
|
: resample(samples, sampleRate, SAMPLE_RATE, NMAX);
|
|
106
138
|
|
|
107
139
|
const cxRe = new Float64Array(NMAX);
|
|
108
140
|
const cxIm = new Float64Array(NMAX);
|
|
109
|
-
for (let i = 0; i < NMAX; i++)
|
|
110
|
-
cxRe[i] = dd[i] ?? 0;
|
|
111
|
-
}
|
|
141
|
+
for (let i = 0; i < NMAX; i++) cxRe[i] = dd[i] ?? 0;
|
|
112
142
|
fftComplex(cxRe, cxIm, false);
|
|
113
143
|
|
|
114
144
|
const candidates = getCandidates4(dd, freqLow, freqHigh, syncMin, maxCandidates);
|
|
115
|
-
if (candidates.length === 0)
|
|
116
|
-
return [];
|
|
117
|
-
}
|
|
145
|
+
if (candidates.length === 0) return [];
|
|
118
146
|
|
|
119
|
-
const
|
|
120
|
-
const tweakedSyncTemplates = createTweakedSyncTemplates();
|
|
147
|
+
const workspace = createDecodeWorkspace();
|
|
121
148
|
const decoded: DecodedMessage[] = [];
|
|
122
149
|
const seenMessages = new Set<string>();
|
|
123
|
-
const apmask = new Int8Array(174);
|
|
124
150
|
|
|
125
151
|
for (const candidate of candidates) {
|
|
126
|
-
const one = decodeCandidate(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
downsampleCtx,
|
|
131
|
-
tweakedSyncTemplates,
|
|
132
|
-
depth,
|
|
133
|
-
book,
|
|
134
|
-
apmask,
|
|
135
|
-
);
|
|
136
|
-
if (!one) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
if (seenMessages.has(one.msg)) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
152
|
+
const one = decodeCandidate(candidate, cxRe, cxIm, depth, book, workspace);
|
|
153
|
+
if (!one) continue;
|
|
154
|
+
if (seenMessages.has(one.msg)) continue;
|
|
155
|
+
|
|
142
156
|
seenMessages.add(one.msg);
|
|
143
157
|
decoded.push(one);
|
|
144
158
|
}
|
|
@@ -146,145 +160,152 @@ export function decode(
|
|
|
146
160
|
return decoded;
|
|
147
161
|
}
|
|
148
162
|
|
|
163
|
+
function createDecodeWorkspace(): DecodeWorkspace {
|
|
164
|
+
return {
|
|
165
|
+
coarseRe: new Float64Array(NFFT2),
|
|
166
|
+
coarseIm: new Float64Array(NFFT2),
|
|
167
|
+
fineRe: new Float64Array(NFFT2),
|
|
168
|
+
fineIm: new Float64Array(NFFT2),
|
|
169
|
+
frameRe: new Float64Array(FRAME_LEN),
|
|
170
|
+
frameIm: new Float64Array(FRAME_LEN),
|
|
171
|
+
symbRe: new Float64Array(NSS),
|
|
172
|
+
symbIm: new Float64Array(NSS),
|
|
173
|
+
csRe: new Float64Array(4 * NN),
|
|
174
|
+
csIm: new Float64Array(4 * NN),
|
|
175
|
+
s4: new Float64Array(4 * NN),
|
|
176
|
+
s2: new Float64Array(1 << 8),
|
|
177
|
+
bitmetrics1: new Float64Array(BITMETRIC_LEN),
|
|
178
|
+
bitmetrics2: new Float64Array(BITMETRIC_LEN),
|
|
179
|
+
bitmetrics3: new Float64Array(BITMETRIC_LEN),
|
|
180
|
+
llra: new Float64Array(LDPC_BITS),
|
|
181
|
+
llrb: new Float64Array(LDPC_BITS),
|
|
182
|
+
llrc: new Float64Array(LDPC_BITS),
|
|
183
|
+
llr: new Float64Array(LDPC_BITS),
|
|
184
|
+
apmask: new Int8Array(LDPC_BITS),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function copySamplesToDecodeWindow(samples: Float32Array | Float64Array): Float64Array {
|
|
189
|
+
const out = new Float64Array(NMAX);
|
|
190
|
+
const len = Math.min(samples.length, NMAX);
|
|
191
|
+
for (let i = 0; i < len; i++) out[i] = samples[i]!;
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
149
195
|
function decodeCandidate(
|
|
150
196
|
candidate: Candidate,
|
|
151
197
|
cxRe: Float64Array,
|
|
152
198
|
cxIm: Float64Array,
|
|
153
|
-
downsampleCtx: DownsampleContext,
|
|
154
|
-
tweakedSyncTemplates: Map<number, SyncTemplates>,
|
|
155
199
|
depth: number,
|
|
156
200
|
book: HashCallBook | undefined,
|
|
157
|
-
|
|
201
|
+
workspace: DecodeWorkspace,
|
|
158
202
|
): DecodedMessage | null {
|
|
159
|
-
|
|
160
|
-
normalizeComplexPower(
|
|
203
|
+
ft4Downsample(cxRe, cxIm, candidate.freq, DOWNSAMPLE_CTX, workspace.coarseRe, workspace.coarseIm);
|
|
204
|
+
normalizeComplexPower(workspace.coarseRe, workspace.coarseIm, NMAX / NDOWN);
|
|
161
205
|
|
|
162
206
|
for (let segment = 1; segment <= 3; segment++) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
idfmax = 12;
|
|
178
|
-
idfstp = 3;
|
|
179
|
-
ibmin = -344;
|
|
180
|
-
ibmax = 1012;
|
|
181
|
-
if (segment === 1) {
|
|
182
|
-
ibmin = 108;
|
|
183
|
-
ibmax = 560;
|
|
184
|
-
} else if (segment === 2) {
|
|
185
|
-
ibmin = 560;
|
|
186
|
-
ibmax = 1012;
|
|
187
|
-
} else {
|
|
188
|
-
ibmin = -344;
|
|
189
|
-
ibmax = 108;
|
|
190
|
-
}
|
|
191
|
-
ibstp = 4;
|
|
192
|
-
} else {
|
|
193
|
-
idfmin = idfbest - 4;
|
|
194
|
-
idfmax = idfbest + 4;
|
|
195
|
-
idfstp = 1;
|
|
196
|
-
ibmin = ibest - 5;
|
|
197
|
-
ibmax = ibest + 5;
|
|
198
|
-
ibstp = 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for (let idf = idfmin; idf <= idfmax; idf += idfstp) {
|
|
202
|
-
const templates = tweakedSyncTemplates.get(idf);
|
|
203
|
-
if (!templates) {
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
for (let istart = ibmin; istart <= ibmax; istart += ibstp) {
|
|
207
|
-
const sync = sync4d(cd2.re, cd2.im, istart, templates);
|
|
208
|
-
if (sync > smax) {
|
|
209
|
-
smax = sync;
|
|
210
|
-
ibest = istart;
|
|
211
|
-
idfbest = idf;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (smax < SYNC_PASS_MIN) {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const f1 = candidate.freq + idfbest;
|
|
222
|
-
if (f1 <= 10 || f1 >= 4990) {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const cb = ft4Downsample(cxRe, cxIm, f1, downsampleCtx);
|
|
227
|
-
normalizeComplexPower(cb.re, cb.im, NSS * NN);
|
|
228
|
-
const frame = extractFrame(cb.re, cb.im, ibest);
|
|
229
|
-
const metrics = getFt4Bitmetrics(frame.re, frame.im);
|
|
230
|
-
|
|
231
|
-
if (metrics.badsync) {
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
if (!passesHardSyncQuality(metrics.bitmetrics1)) {
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const [llra, llrb, llrc] = buildLlrs(
|
|
239
|
-
metrics.bitmetrics1,
|
|
240
|
-
metrics.bitmetrics2,
|
|
241
|
-
metrics.bitmetrics3,
|
|
207
|
+
const coarse = findBestSyncLocation(workspace.coarseRe, workspace.coarseIm, segment);
|
|
208
|
+
if (coarse.smax < SYNC_PASS_MIN) continue;
|
|
209
|
+
|
|
210
|
+
const f1 = candidate.freq + coarse.idfbest;
|
|
211
|
+
if (f1 <= 10 || f1 >= 4990) continue;
|
|
212
|
+
|
|
213
|
+
ft4Downsample(cxRe, cxIm, f1, DOWNSAMPLE_CTX, workspace.fineRe, workspace.fineIm);
|
|
214
|
+
normalizeComplexPower(workspace.fineRe, workspace.fineIm, NSS * NN);
|
|
215
|
+
extractFrame(
|
|
216
|
+
workspace.fineRe,
|
|
217
|
+
workspace.fineIm,
|
|
218
|
+
coarse.ibest,
|
|
219
|
+
workspace.frameRe,
|
|
220
|
+
workspace.frameIm,
|
|
242
221
|
);
|
|
243
|
-
const maxosd = depth >= 3 ? 2 : depth >= 2 ? 0 : -1;
|
|
244
|
-
const scalefac = 2.83;
|
|
245
222
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
llr[i] = scalefac * src[i]!;
|
|
250
|
-
}
|
|
223
|
+
const badsync = buildBitMetrics(workspace.frameRe, workspace.frameIm, workspace);
|
|
224
|
+
if (badsync) continue;
|
|
225
|
+
if (!passesHardSyncQuality(workspace.bitmetrics1)) continue;
|
|
251
226
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
227
|
+
buildLlrs(workspace);
|
|
228
|
+
const result = tryDecodePasses(workspace, depth);
|
|
229
|
+
if (!result) continue;
|
|
256
230
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
231
|
+
const message77Scrambled = result.message91.slice(0, 77);
|
|
232
|
+
if (!hasNonZeroBit(message77Scrambled)) continue;
|
|
261
233
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
234
|
+
const message77 = xorWithScrambler(message77Scrambled);
|
|
235
|
+
const { msg, success } = unpack77(message77, book);
|
|
236
|
+
if (!success || msg.trim().length === 0) continue;
|
|
267
237
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
238
|
+
return {
|
|
239
|
+
freq: f1,
|
|
240
|
+
dt: coarse.ibest / FS2 - 0.5,
|
|
241
|
+
snr: toFt4Snr(candidate.sync - 1.0),
|
|
242
|
+
msg,
|
|
243
|
+
sync: coarse.smax,
|
|
244
|
+
};
|
|
276
245
|
}
|
|
277
246
|
|
|
278
247
|
return null;
|
|
279
248
|
}
|
|
280
249
|
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
250
|
+
function findBestSyncLocation(
|
|
251
|
+
cdRe: Float64Array,
|
|
252
|
+
cdIm: Float64Array,
|
|
253
|
+
segment: number,
|
|
254
|
+
): CandidateSearchResult {
|
|
255
|
+
let ibest = -1;
|
|
256
|
+
let idfbest = 0;
|
|
257
|
+
let smax = -99;
|
|
258
|
+
|
|
259
|
+
for (let isync = 1; isync <= 2; isync++) {
|
|
260
|
+
let idfmin: number;
|
|
261
|
+
let idfmax: number;
|
|
262
|
+
let idfstp: number;
|
|
263
|
+
let ibmin: number;
|
|
264
|
+
let ibmax: number;
|
|
265
|
+
let ibstp: number;
|
|
266
|
+
|
|
267
|
+
if (isync === 1) {
|
|
268
|
+
idfmin = -12;
|
|
269
|
+
idfmax = 12;
|
|
270
|
+
idfstp = 3;
|
|
271
|
+
ibmin = -344;
|
|
272
|
+
ibmax = 1012;
|
|
273
|
+
if (segment === 1) {
|
|
274
|
+
ibmin = 108;
|
|
275
|
+
ibmax = 560;
|
|
276
|
+
} else if (segment === 2) {
|
|
277
|
+
ibmin = 560;
|
|
278
|
+
ibmax = 1012;
|
|
279
|
+
} else {
|
|
280
|
+
ibmin = -344;
|
|
281
|
+
ibmax = 108;
|
|
282
|
+
}
|
|
283
|
+
ibstp = 4;
|
|
284
|
+
} else {
|
|
285
|
+
idfmin = idfbest - 4;
|
|
286
|
+
idfmax = idfbest + 4;
|
|
287
|
+
idfstp = 1;
|
|
288
|
+
ibmin = ibest - 5;
|
|
289
|
+
ibmax = ibest + 5;
|
|
290
|
+
ibstp = 1;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (let idf = idfmin; idf <= idfmax; idf += idfstp) {
|
|
294
|
+
const templates = TWEAKED_SYNC_TEMPLATES.get(idf);
|
|
295
|
+
if (!templates) continue;
|
|
296
|
+
|
|
297
|
+
for (let istart = ibmin; istart <= ibmax; istart += ibstp) {
|
|
298
|
+
const sync = sync4d(cdRe, cdIm, istart, templates);
|
|
299
|
+
if (sync > smax) {
|
|
300
|
+
smax = sync;
|
|
301
|
+
ibest = istart;
|
|
302
|
+
idfbest = idf;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
286
306
|
}
|
|
287
|
-
|
|
307
|
+
|
|
308
|
+
return { ibest, idfbest, smax };
|
|
288
309
|
}
|
|
289
310
|
|
|
290
311
|
function getCandidates4(
|
|
@@ -296,7 +317,6 @@ function getCandidates4(
|
|
|
296
317
|
): Candidate[] {
|
|
297
318
|
const df = SAMPLE_RATE / NFFT1;
|
|
298
319
|
const fac = 1 / 300;
|
|
299
|
-
const window = makeNuttallWindow(NFFT1);
|
|
300
320
|
const savg = new Float64Array(NH1);
|
|
301
321
|
const s = new Float64Array(NH1 * NHSYM);
|
|
302
322
|
const savsm = new Float64Array(NH1);
|
|
@@ -307,13 +327,10 @@ function getCandidates4(
|
|
|
307
327
|
for (let j = 0; j < NHSYM; j++) {
|
|
308
328
|
const ia = j * NSPS;
|
|
309
329
|
const ib = ia + NFFT1;
|
|
310
|
-
if (ib > NMAX)
|
|
311
|
-
|
|
312
|
-
}
|
|
330
|
+
if (ib > NMAX) break;
|
|
331
|
+
|
|
313
332
|
xIm.fill(0);
|
|
314
|
-
for (let i = 0; i < NFFT1; i++)
|
|
315
|
-
xRe[i] = fac * dd[ia + i]! * window[i]!;
|
|
316
|
-
}
|
|
333
|
+
for (let i = 0; i < NFFT1; i++) xRe[i] = fac * dd[ia + i]! * NUTTALL_WINDOW[i]!;
|
|
317
334
|
fftComplex(xRe, xIm, false);
|
|
318
335
|
|
|
319
336
|
for (let bin = 1; bin <= NH1; bin++) {
|
|
@@ -326,32 +343,22 @@ function getCandidates4(
|
|
|
326
343
|
}
|
|
327
344
|
}
|
|
328
345
|
|
|
329
|
-
for (let i = 0; i < NH1; i++)
|
|
330
|
-
savg[i] = (savg[i] ?? 0) / NHSYM;
|
|
331
|
-
}
|
|
346
|
+
for (let i = 0; i < NH1; i++) savg[i] = (savg[i] ?? 0) / NHSYM;
|
|
332
347
|
|
|
333
348
|
for (let i = 7; i < NH1 - 7; i++) {
|
|
334
349
|
let sum = 0;
|
|
335
|
-
for (let j = i - 7; j <= i + 7; j++)
|
|
336
|
-
sum += savg[j]!;
|
|
337
|
-
}
|
|
350
|
+
for (let j = i - 7; j <= i + 7; j++) sum += savg[j]!;
|
|
338
351
|
savsm[i] = sum / 15;
|
|
339
352
|
}
|
|
340
353
|
|
|
341
354
|
let nfa = Math.round(freqLow / df);
|
|
342
|
-
if (nfa < Math.round(200 / df))
|
|
343
|
-
nfa = Math.round(200 / df);
|
|
344
|
-
}
|
|
355
|
+
if (nfa < Math.round(200 / df)) nfa = Math.round(200 / df);
|
|
345
356
|
let nfb = Math.round(freqHigh / df);
|
|
346
|
-
if (nfb > Math.round(MAX_FREQ / df))
|
|
347
|
-
nfb = Math.round(MAX_FREQ / df);
|
|
348
|
-
}
|
|
357
|
+
if (nfb > Math.round(MAX_FREQ / df)) nfb = Math.round(MAX_FREQ / df);
|
|
349
358
|
|
|
350
359
|
const sbase = ft4Baseline(savg, nfa, nfb, df);
|
|
351
360
|
for (let bin = nfa; bin <= nfb; bin++) {
|
|
352
|
-
if ((sbase[bin - 1] ?? 0) <= 0)
|
|
353
|
-
return [];
|
|
354
|
-
}
|
|
361
|
+
if ((sbase[bin - 1] ?? 0) <= 0) return [];
|
|
355
362
|
}
|
|
356
363
|
|
|
357
364
|
for (let bin = nfa; bin <= nfb; bin++) {
|
|
@@ -370,9 +377,7 @@ function getCandidates4(
|
|
|
370
377
|
const den = left - 2 * center + right;
|
|
371
378
|
const del = den !== 0 ? (0.5 * (left - right)) / den : 0;
|
|
372
379
|
const fpeak = (i + del) * df + fOffset;
|
|
373
|
-
if (fpeak < 200 || fpeak > MAX_FREQ)
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
380
|
+
if (fpeak < 200 || fpeak > MAX_FREQ) continue;
|
|
376
381
|
const speak = center - 0.25 * (left - right) * del;
|
|
377
382
|
candidates.push({ freq: fpeak, sync: speak });
|
|
378
383
|
}
|
|
@@ -404,14 +409,10 @@ function ft4Baseline(savg: Float64Array, nfa: number, nfb: number, df: number):
|
|
|
404
409
|
|
|
405
410
|
const ia = Math.max(Math.round(200 / df), nfa);
|
|
406
411
|
const ib = Math.min(NH1, nfb);
|
|
407
|
-
if (ib <= ia)
|
|
408
|
-
return sbase;
|
|
409
|
-
}
|
|
412
|
+
if (ib <= ia) return sbase;
|
|
410
413
|
|
|
411
414
|
const sDb = new Float64Array(NH1);
|
|
412
|
-
for (let i = ia; i <= ib; i++)
|
|
413
|
-
sDb[i - 1] = 10 * Math.log10(Math.max(1e-30, savg[i - 1]!));
|
|
414
|
-
}
|
|
415
|
+
for (let i = ia; i <= ib; i++) sDb[i - 1] = 10 * Math.log10(Math.max(1e-30, savg[i - 1]!));
|
|
415
416
|
|
|
416
417
|
const nseg = 10;
|
|
417
418
|
const npct = 10;
|
|
@@ -420,18 +421,13 @@ function ft4Baseline(savg: Float64Array, nfa: number, nfb: number, df: number):
|
|
|
420
421
|
|
|
421
422
|
const x: number[] = [];
|
|
422
423
|
const y: number[] = [];
|
|
423
|
-
|
|
424
424
|
for (let seg = 0; seg < nseg; seg++) {
|
|
425
425
|
const ja = ia + seg * nlen;
|
|
426
|
-
if (ja > ib)
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
426
|
+
if (ja > ib) break;
|
|
429
427
|
const jb = Math.min(ib, ja + nlen - 1);
|
|
430
428
|
|
|
431
429
|
const vals: number[] = [];
|
|
432
|
-
for (let i = ja; i <= jb; i++)
|
|
433
|
-
vals.push(sDb[i - 1]!);
|
|
434
|
-
}
|
|
430
|
+
for (let i = ja; i <= jb; i++) vals.push(sDb[i - 1]!);
|
|
435
431
|
const base = percentile(vals, npct);
|
|
436
432
|
|
|
437
433
|
for (let i = ja; i <= jb; i++) {
|
|
@@ -444,7 +440,6 @@ function ft4Baseline(savg: Float64Array, nfa: number, nfb: number, df: number):
|
|
|
444
440
|
}
|
|
445
441
|
|
|
446
442
|
const coeff = x.length >= 5 ? polyfitLeastSquares(x, y, 4) : null;
|
|
447
|
-
|
|
448
443
|
if (coeff) {
|
|
449
444
|
for (let i = ia; i <= ib; i++) {
|
|
450
445
|
const t = i - i0;
|
|
@@ -471,9 +466,7 @@ function ft4Baseline(savg: Float64Array, nfa: number, nfb: number, df: number):
|
|
|
471
466
|
}
|
|
472
467
|
|
|
473
468
|
function percentile(values: readonly number[], pct: number): number {
|
|
474
|
-
if (values.length === 0)
|
|
475
|
-
return 0;
|
|
476
|
-
}
|
|
469
|
+
if (values.length === 0) return 0;
|
|
477
470
|
const sorted = [...values].sort((a, b) => a - b);
|
|
478
471
|
const idx = Math.max(
|
|
479
472
|
0,
|
|
@@ -493,20 +486,14 @@ function polyfitLeastSquares(
|
|
|
493
486
|
const xPows = new Float64Array(2 * degree + 1);
|
|
494
487
|
for (let p = 0; p <= 2 * degree; p++) {
|
|
495
488
|
let sum = 0;
|
|
496
|
-
for (let i = 0; i < x.length; i++)
|
|
497
|
-
sum += x[i]! ** p;
|
|
498
|
-
}
|
|
489
|
+
for (let i = 0; i < x.length; i++) sum += x[i]! ** p;
|
|
499
490
|
xPows[p] = sum;
|
|
500
491
|
}
|
|
501
492
|
|
|
502
493
|
for (let row = 0; row < n; row++) {
|
|
503
|
-
for (let col = 0; col < n; col++)
|
|
504
|
-
mat[row]![col] = xPows[row + col]!;
|
|
505
|
-
}
|
|
494
|
+
for (let col = 0; col < n; col++) mat[row]![col] = xPows[row + col]!;
|
|
506
495
|
let rhs = 0;
|
|
507
|
-
for (let i = 0; i < x.length; i++)
|
|
508
|
-
rhs += y[i]! * x[i]! ** row;
|
|
509
|
-
}
|
|
496
|
+
for (let i = 0; i < x.length; i++) rhs += y[i]! * x[i]! ** row;
|
|
510
497
|
mat[row]![n] = rhs;
|
|
511
498
|
}
|
|
512
499
|
|
|
@@ -520,9 +507,7 @@ function polyfitLeastSquares(
|
|
|
520
507
|
pivot = row;
|
|
521
508
|
}
|
|
522
509
|
}
|
|
523
|
-
if (maxAbs < 1e-12)
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
510
|
+
if (maxAbs < 1e-12) return null;
|
|
526
511
|
if (pivot !== col) {
|
|
527
512
|
const tmp = mat[col]!;
|
|
528
513
|
mat[col] = mat[pivot]!;
|
|
@@ -530,28 +515,18 @@ function polyfitLeastSquares(
|
|
|
530
515
|
}
|
|
531
516
|
|
|
532
517
|
const pivotVal = mat[col]![col]!;
|
|
533
|
-
for (let c = col; c <= n; c++)
|
|
534
|
-
mat[col]![c] = mat[col]![c]! / pivotVal;
|
|
535
|
-
}
|
|
518
|
+
for (let c = col; c <= n; c++) mat[col]![c] = mat[col]![c]! / pivotVal;
|
|
536
519
|
|
|
537
520
|
for (let row = 0; row < n; row++) {
|
|
538
|
-
if (row === col)
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
521
|
+
if (row === col) continue;
|
|
541
522
|
const factor = mat[row]![col]!;
|
|
542
|
-
if (factor === 0)
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
for (let c = col; c <= n; c++) {
|
|
546
|
-
mat[row]![c] = mat[row]![c]! - factor * mat[col]![c]!;
|
|
547
|
-
}
|
|
523
|
+
if (factor === 0) continue;
|
|
524
|
+
for (let c = col; c <= n; c++) mat[row]![c] = mat[row]![c]! - factor * mat[col]![c]!;
|
|
548
525
|
}
|
|
549
526
|
}
|
|
550
527
|
|
|
551
528
|
const coeff = new Array<number>(n);
|
|
552
|
-
for (let i = 0; i < n; i++)
|
|
553
|
-
coeff[i] = mat[i]![n]!;
|
|
554
|
-
}
|
|
529
|
+
for (let i = 0; i < n; i++) coeff[i] = mat[i]![n]!;
|
|
555
530
|
return coeff;
|
|
556
531
|
}
|
|
557
532
|
|
|
@@ -568,9 +543,7 @@ function createDownsampleContext(): DownsampleContext {
|
|
|
568
543
|
for (let i = 0; i < iwt && i < raw.length; i++) {
|
|
569
544
|
raw[i] = 0.5 * (1 + Math.cos((Math.PI * (iwt - 1 - i)) / iwt));
|
|
570
545
|
}
|
|
571
|
-
for (let i = iwt; i < iwt + iwf && i < raw.length; i++)
|
|
572
|
-
raw[i] = 1;
|
|
573
|
-
}
|
|
546
|
+
for (let i = iwt; i < iwt + iwf && i < raw.length; i++) raw[i] = 1;
|
|
574
547
|
for (let i = iwt + iwf; i < 2 * iwt + iwf && i < raw.length; i++) {
|
|
575
548
|
raw[i] = 0.5 * (1 + Math.cos((Math.PI * (i - (iwt + iwf))) / iwt));
|
|
576
549
|
}
|
|
@@ -589,49 +562,47 @@ function ft4Downsample(
|
|
|
589
562
|
cxIm: Float64Array,
|
|
590
563
|
f0: number,
|
|
591
564
|
ctx: DownsampleContext,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
565
|
+
outRe: Float64Array,
|
|
566
|
+
outIm: Float64Array,
|
|
567
|
+
): void {
|
|
568
|
+
outRe.fill(0);
|
|
569
|
+
outIm.fill(0);
|
|
595
570
|
const i0 = Math.round(f0 / ctx.df);
|
|
596
571
|
|
|
597
572
|
if (i0 >= 0 && i0 <= NMAX / 2) {
|
|
598
|
-
|
|
599
|
-
|
|
573
|
+
outRe[0] = cxRe[i0] ?? 0;
|
|
574
|
+
outIm[0] = cxIm[i0] ?? 0;
|
|
600
575
|
}
|
|
601
576
|
|
|
602
577
|
for (let i = 1; i <= NFFT2 / 2; i++) {
|
|
603
578
|
const hi = i0 + i;
|
|
604
579
|
if (hi >= 0 && hi <= NMAX / 2) {
|
|
605
|
-
|
|
606
|
-
|
|
580
|
+
outRe[i] = cxRe[hi] ?? 0;
|
|
581
|
+
outIm[i] = cxIm[hi] ?? 0;
|
|
607
582
|
}
|
|
608
583
|
const lo = i0 - i;
|
|
609
584
|
if (lo >= 0 && lo <= NMAX / 2) {
|
|
610
585
|
const idx = NFFT2 - i;
|
|
611
|
-
|
|
612
|
-
|
|
586
|
+
outRe[idx] = cxRe[lo] ?? 0;
|
|
587
|
+
outIm[idx] = cxIm[lo] ?? 0;
|
|
613
588
|
}
|
|
614
589
|
}
|
|
615
590
|
|
|
616
591
|
const scale = 1 / NFFT2;
|
|
617
592
|
for (let i = 0; i < NFFT2; i++) {
|
|
618
593
|
const w = (ctx.window[i] ?? 0) * scale;
|
|
619
|
-
|
|
620
|
-
|
|
594
|
+
outRe[i] = outRe[i]! * w;
|
|
595
|
+
outIm[i] = outIm[i]! * w;
|
|
621
596
|
}
|
|
622
597
|
|
|
623
|
-
fftComplex(
|
|
624
|
-
return { re: c1Re, im: c1Im };
|
|
598
|
+
fftComplex(outRe, outIm, true);
|
|
625
599
|
}
|
|
626
600
|
|
|
627
601
|
function normalizeComplexPower(re: Float64Array, im: Float64Array, denom: number): void {
|
|
628
602
|
let sum = 0;
|
|
629
|
-
for (let i = 0; i < re.length; i++)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (sum <= 0) {
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
603
|
+
for (let i = 0; i < re.length; i++) sum += re[i]! * re[i]! + im[i]! * im[i]!;
|
|
604
|
+
if (sum <= 0) return;
|
|
605
|
+
|
|
635
606
|
const scale = 1 / Math.sqrt(sum / denom);
|
|
636
607
|
for (let i = 0; i < re.length; i++) {
|
|
637
608
|
re[i] = re[i]! * scale;
|
|
@@ -639,17 +610,23 @@ function normalizeComplexPower(re: Float64Array, im: Float64Array, denom: number
|
|
|
639
610
|
}
|
|
640
611
|
}
|
|
641
612
|
|
|
642
|
-
function extractFrame(
|
|
643
|
-
|
|
644
|
-
|
|
613
|
+
function extractFrame(
|
|
614
|
+
cbRe: Float64Array,
|
|
615
|
+
cbIm: Float64Array,
|
|
616
|
+
ibest: number,
|
|
617
|
+
outRe: Float64Array,
|
|
618
|
+
outIm: Float64Array,
|
|
619
|
+
): void {
|
|
645
620
|
for (let i = 0; i < outRe.length; i++) {
|
|
646
621
|
const src = ibest + i;
|
|
647
622
|
if (src >= 0 && src < cbRe.length) {
|
|
648
623
|
outRe[i] = cbRe[src]!;
|
|
649
624
|
outIm[i] = cbIm[src]!;
|
|
625
|
+
} else {
|
|
626
|
+
outRe[i] = 0;
|
|
627
|
+
outIm[i] = 0;
|
|
650
628
|
}
|
|
651
629
|
}
|
|
652
|
-
return { re: outRe, im: outIm };
|
|
653
630
|
}
|
|
654
631
|
|
|
655
632
|
function createTweakedSyncTemplates(): Map<number, SyncTemplates> {
|
|
@@ -657,7 +634,7 @@ function createTweakedSyncTemplates(): Map<number, SyncTemplates> {
|
|
|
657
634
|
const fsample = FS2 / 2;
|
|
658
635
|
const out = new Map<number, SyncTemplates>();
|
|
659
636
|
|
|
660
|
-
for (let idf = -
|
|
637
|
+
for (let idf = -FT4_MAX_TWEAK; idf <= FT4_MAX_TWEAK; idf++) {
|
|
661
638
|
const tweak = createFrequencyTweak(idf, 2 * NSS, fsample);
|
|
662
639
|
out.set(idf, [
|
|
663
640
|
applyTweak(base[0], tweak),
|
|
@@ -684,6 +661,7 @@ function buildSyncTemplate(tones: readonly number[]): SyncTemplate {
|
|
|
684
661
|
const im = new Float64Array(2 * NSS);
|
|
685
662
|
let k = 0;
|
|
686
663
|
let phi = 0;
|
|
664
|
+
|
|
687
665
|
for (const tone of tones) {
|
|
688
666
|
const dphi = (TWO_PI * tone * 2) / NSS;
|
|
689
667
|
for (let j = 0; j < NSS / 2; j++) {
|
|
@@ -693,6 +671,7 @@ function buildSyncTemplate(tones: readonly number[]): SyncTemplate {
|
|
|
693
671
|
k++;
|
|
694
672
|
}
|
|
695
673
|
}
|
|
674
|
+
|
|
696
675
|
return { re, im };
|
|
697
676
|
}
|
|
698
677
|
|
|
@@ -704,6 +683,7 @@ function createFrequencyTweak(idf: number, npts: number, fsample: number): SyncT
|
|
|
704
683
|
const stepIm = Math.sin(dphi);
|
|
705
684
|
let wRe = 1;
|
|
706
685
|
let wIm = 0;
|
|
686
|
+
|
|
707
687
|
for (let i = 0; i < npts; i++) {
|
|
708
688
|
const newRe = wRe * stepRe - wIm * stepIm;
|
|
709
689
|
const newIm = wRe * stepIm + wIm * stepRe;
|
|
@@ -712,6 +692,7 @@ function createFrequencyTweak(idf: number, npts: number, fsample: number): SyncT
|
|
|
712
692
|
re[i] = wRe;
|
|
713
693
|
im[i] = wIm;
|
|
714
694
|
}
|
|
695
|
+
|
|
715
696
|
return { re, im };
|
|
716
697
|
}
|
|
717
698
|
|
|
@@ -735,13 +716,11 @@ function sync4d(
|
|
|
735
716
|
i0: number,
|
|
736
717
|
templates: SyncTemplates,
|
|
737
718
|
): number {
|
|
738
|
-
const starts = [i0, i0 + 33 * NSS, i0 + 66 * NSS, i0 + 99 * NSS];
|
|
739
719
|
let sync = 0;
|
|
740
|
-
for (let i = 0; i <
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
720
|
+
for (let i = 0; i < COSTAS_BLOCKS; i++) {
|
|
721
|
+
const start = i0 + i * FT4_SYNC_STRIDE;
|
|
722
|
+
const z = correlateStride2(cdRe, cdIm, start, templates[i]!.re, templates[i]!.im);
|
|
723
|
+
if (z.count <= 16) continue;
|
|
745
724
|
sync += Math.hypot(z.re, z.im) / (2 * NSS);
|
|
746
725
|
}
|
|
747
726
|
return sync;
|
|
@@ -759,9 +738,7 @@ function correlateStride2(
|
|
|
759
738
|
let count = 0;
|
|
760
739
|
for (let i = 0; i < templateRe.length; i++) {
|
|
761
740
|
const idx = start + 2 * i;
|
|
762
|
-
if (idx < 0 || idx >= cdRe.length)
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
741
|
+
if (idx < 0 || idx >= cdRe.length) continue;
|
|
765
742
|
const sRe = templateRe[i]!;
|
|
766
743
|
const sIm = templateIm[i]!;
|
|
767
744
|
const dRe = cdRe[idx]!;
|
|
@@ -773,13 +750,12 @@ function correlateStride2(
|
|
|
773
750
|
return { re: zRe, im: zIm, count };
|
|
774
751
|
}
|
|
775
752
|
|
|
776
|
-
function
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const symbRe =
|
|
782
|
-
const symbIm = new Float64Array(NSS);
|
|
753
|
+
function buildBitMetrics(
|
|
754
|
+
cdRe: Float64Array,
|
|
755
|
+
cdIm: Float64Array,
|
|
756
|
+
workspace: DecodeWorkspace,
|
|
757
|
+
): boolean {
|
|
758
|
+
const { csRe, csIm, s4, symbRe, symbIm, bitmetrics1, bitmetrics2, bitmetrics3, s2 } = workspace;
|
|
783
759
|
|
|
784
760
|
for (let k = 0; k < NN; k++) {
|
|
785
761
|
const i1 = k * NSS;
|
|
@@ -801,33 +777,21 @@ function getFt4Bitmetrics(cdRe: Float64Array, cdIm: Float64Array): Ft4BitMetrics
|
|
|
801
777
|
|
|
802
778
|
let nsync = 0;
|
|
803
779
|
for (let k = 0; k < 4; k++) {
|
|
804
|
-
if (maxTone(s4, k) === COSTAS_A[k])
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (maxTone(s4,
|
|
808
|
-
nsync++;
|
|
809
|
-
}
|
|
810
|
-
if (maxTone(s4, 66 + k) === COSTAS_C[k]) {
|
|
811
|
-
nsync++;
|
|
812
|
-
}
|
|
813
|
-
if (maxTone(s4, 99 + k) === COSTAS_D[k]) {
|
|
814
|
-
nsync++;
|
|
815
|
-
}
|
|
780
|
+
if (maxTone(s4, k) === COSTAS_A[k]) nsync++;
|
|
781
|
+
if (maxTone(s4, 33 + k) === COSTAS_B[k]) nsync++;
|
|
782
|
+
if (maxTone(s4, 66 + k) === COSTAS_C[k]) nsync++;
|
|
783
|
+
if (maxTone(s4, 99 + k) === COSTAS_D[k]) nsync++;
|
|
816
784
|
}
|
|
817
785
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if (nsync < 6) {
|
|
823
|
-
return { bitmetrics1, bitmetrics2, bitmetrics3, badsync: true };
|
|
824
|
-
}
|
|
786
|
+
bitmetrics1.fill(0);
|
|
787
|
+
bitmetrics2.fill(0);
|
|
788
|
+
bitmetrics3.fill(0);
|
|
789
|
+
if (nsync < 6) return true;
|
|
825
790
|
|
|
826
791
|
for (let nseq = 1; nseq <= 3; nseq++) {
|
|
827
792
|
const nsym = nseq === 1 ? 1 : nseq === 2 ? 2 : 4;
|
|
828
|
-
const nt = 1 << (2 * nsym);
|
|
793
|
+
const nt = 1 << (2 * nsym);
|
|
829
794
|
const ibmax = nseq === 1 ? 1 : nseq === 2 ? 3 : 7;
|
|
830
|
-
const s2 = new Float64Array(nt);
|
|
831
795
|
|
|
832
796
|
for (let ks = 1; ks <= NN - nsym + 1; ks += nsym) {
|
|
833
797
|
for (let i = 0; i < nt; i++) {
|
|
@@ -871,18 +835,15 @@ function getFt4Bitmetrics(cdRe: Float64Array, cdIm: Float64Array): Ft4BitMetrics
|
|
|
871
835
|
for (let i = 0; i < nt; i++) {
|
|
872
836
|
const v = s2[i]!;
|
|
873
837
|
if ((i & mask) !== 0) {
|
|
874
|
-
if (v > max1)
|
|
875
|
-
max1 = v;
|
|
876
|
-
}
|
|
838
|
+
if (v > max1) max1 = v;
|
|
877
839
|
} else if (v > max0) {
|
|
878
840
|
max0 = v;
|
|
879
841
|
}
|
|
880
842
|
}
|
|
881
843
|
|
|
882
844
|
const idx = ipt + ib;
|
|
883
|
-
if (idx >
|
|
884
|
-
|
|
885
|
-
}
|
|
845
|
+
if (idx > BITMETRIC_LEN) continue;
|
|
846
|
+
|
|
886
847
|
const bm = max1 - max0;
|
|
887
848
|
if (nseq === 1) {
|
|
888
849
|
bitmetrics1[idx - 1] = bm;
|
|
@@ -903,8 +864,7 @@ function getFt4Bitmetrics(cdRe: Float64Array, cdIm: Float64Array): Ft4BitMetrics
|
|
|
903
864
|
normalizeBitMetrics(bitmetrics1);
|
|
904
865
|
normalizeBitMetrics(bitmetrics2);
|
|
905
866
|
normalizeBitMetrics(bitmetrics3);
|
|
906
|
-
|
|
907
|
-
return { bitmetrics1, bitmetrics2, bitmetrics3, badsync: false };
|
|
867
|
+
return false;
|
|
908
868
|
}
|
|
909
869
|
|
|
910
870
|
function maxTone(s4: Float64Array, symbolIndex: number): number {
|
|
@@ -931,40 +891,25 @@ function normalizeBitMetrics(bmet: Float64Array): void {
|
|
|
931
891
|
const avg2 = sum2 / bmet.length;
|
|
932
892
|
const variance = avg2 - avg * avg;
|
|
933
893
|
const sigma = variance > 0 ? Math.sqrt(variance) : Math.sqrt(avg2);
|
|
934
|
-
if (sigma <= 0)
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
for (let i = 0; i < bmet.length; i++) {
|
|
938
|
-
bmet[i] = bmet[i]! / sigma;
|
|
939
|
-
}
|
|
894
|
+
if (sigma <= 0) return;
|
|
895
|
+
for (let i = 0; i < bmet.length; i++) bmet[i] = bmet[i]! / sigma;
|
|
940
896
|
}
|
|
941
897
|
|
|
942
898
|
function passesHardSyncQuality(bitmetrics1: Float64Array): boolean {
|
|
943
899
|
const hard = new Uint8Array(bitmetrics1.length);
|
|
944
|
-
for (let i = 0; i < bitmetrics1.length; i++)
|
|
945
|
-
hard[i] = bitmetrics1[i]! >= 0 ? 1 : 0;
|
|
946
|
-
}
|
|
900
|
+
for (let i = 0; i < bitmetrics1.length; i++) hard[i] = bitmetrics1[i]! >= 0 ? 1 : 0;
|
|
947
901
|
|
|
948
902
|
let score = 0;
|
|
949
903
|
for (const pattern of HARD_SYNC_PATTERNS) {
|
|
950
904
|
for (let i = 0; i < pattern.bits.length; i++) {
|
|
951
|
-
if (hard[pattern.offset + i] === pattern.bits[i])
|
|
952
|
-
score++;
|
|
953
|
-
}
|
|
905
|
+
if (hard[pattern.offset + i] === pattern.bits[i]) score++;
|
|
954
906
|
}
|
|
955
907
|
}
|
|
956
908
|
return score >= 10;
|
|
957
909
|
}
|
|
958
910
|
|
|
959
|
-
function buildLlrs(
|
|
960
|
-
bitmetrics1
|
|
961
|
-
bitmetrics2: Float64Array,
|
|
962
|
-
bitmetrics3: Float64Array,
|
|
963
|
-
): [Float64Array, Float64Array, Float64Array] {
|
|
964
|
-
const llra = new Float64Array(174);
|
|
965
|
-
const llrb = new Float64Array(174);
|
|
966
|
-
const llrc = new Float64Array(174);
|
|
967
|
-
|
|
911
|
+
function buildLlrs(workspace: DecodeWorkspace): void {
|
|
912
|
+
const { bitmetrics1, bitmetrics2, bitmetrics3, llra, llrb, llrc } = workspace;
|
|
968
913
|
for (let i = 0; i < 58; i++) {
|
|
969
914
|
llra[i] = bitmetrics1[8 + i]!;
|
|
970
915
|
llra[58 + i] = bitmetrics1[74 + i]!;
|
|
@@ -978,15 +923,29 @@ function buildLlrs(
|
|
|
978
923
|
llrc[58 + i] = bitmetrics3[74 + i]!;
|
|
979
924
|
llrc[116 + i] = bitmetrics3[140 + i]!;
|
|
980
925
|
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function tryDecodePasses(
|
|
929
|
+
workspace: DecodeWorkspace,
|
|
930
|
+
depth: number,
|
|
931
|
+
): import("../util/decode174_91.js").DecodeResult | null {
|
|
932
|
+
const maxosd = depth >= 3 ? 2 : depth >= 2 ? 0 : -1;
|
|
933
|
+
const scalefac = 2.83;
|
|
934
|
+
const sources = [workspace.llra, workspace.llrb, workspace.llrc];
|
|
981
935
|
|
|
982
|
-
|
|
936
|
+
workspace.apmask.fill(0);
|
|
937
|
+
for (const src of sources) {
|
|
938
|
+
for (let i = 0; i < LDPC_BITS; i++) workspace.llr[i] = scalefac * src[i]!;
|
|
939
|
+
const result = decode174_91(workspace.llr, workspace.apmask, maxosd);
|
|
940
|
+
if (result) return result;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return null;
|
|
983
944
|
}
|
|
984
945
|
|
|
985
946
|
function hasNonZeroBit(bits: readonly number[]): boolean {
|
|
986
947
|
for (const bit of bits) {
|
|
987
|
-
if (bit !== 0)
|
|
988
|
-
return true;
|
|
989
|
-
}
|
|
948
|
+
if (bit !== 0) return true;
|
|
990
949
|
}
|
|
991
950
|
return false;
|
|
992
951
|
}
|