@atmosx/event-product-parser 2.0.16 → 3.0.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 (116) hide show
  1. package/README.md +4 -237
  2. package/dist/cjs/index.cjs +2233 -3105
  3. package/dist/esm/index.mjs +2233 -3108
  4. package/package.json +3 -2
  5. package/src/@building/building.clean.ts +30 -0
  6. package/src/@building/building.create.ts +42 -0
  7. package/src/@building/building.enhance.ts +56 -0
  8. package/src/@building/building.geometry.ts +42 -0
  9. package/src/@building/building.headers.ts +37 -0
  10. package/src/@building/building.office.ts +43 -0
  11. package/src/@building/building.polygon.ts +71 -0
  12. package/src/@building/building.properties.ts +89 -0
  13. package/src/@building/building.signature.ts +78 -0
  14. package/src/@building/building.tags.ts +24 -0
  15. package/src/@building/building.tracking.ts +68 -0
  16. package/src/@building/building.validate.ts +132 -0
  17. package/src/@core/core.callback.ts +39 -0
  18. package/src/@core/core.getEvents.ts +25 -0
  19. package/src/@core/core.getNodes.ts +25 -0
  20. package/src/@core/core.listener.ts +24 -0
  21. package/src/@core/core.setNode.ts +81 -0
  22. package/src/@core/core.start.ts +54 -0
  23. package/src/@core/core.stop.ts +32 -0
  24. package/src/@dictionaries/dictionaries.betterEventNames.ts +85 -0
  25. package/src/@dictionaries/dictionaries.eventActions.ts +28 -0
  26. package/src/@dictionaries/{awips.ts → dictionaries.eventAwipAbreviations.ts} +12 -6
  27. package/src/@dictionaries/dictionaries.eventCancelMessages.ts +29 -0
  28. package/src/@dictionaries/dictionaries.eventCauses.ts +36 -0
  29. package/src/@dictionaries/dictionaries.eventProducts.ts +25 -0
  30. package/src/@dictionaries/dictionaries.eventRecords.ts +25 -0
  31. package/src/@dictionaries/dictionaries.eventSeverity.ts +27 -0
  32. package/src/@dictionaries/dictionaries.eventStatus.ts +31 -0
  33. package/src/@dictionaries/{signatures.ts → dictionaries.eventTags.ts} +13 -68
  34. package/src/@dictionaries/dictionaries.eventTypes.ts +82 -0
  35. package/src/@dictionaries/dictionaries.eventsOffshore.ts +31 -0
  36. package/src/@dictionaries/dictionaries.hailStrings.ts +31 -0
  37. package/src/@dictionaries/{icao.ts → dictionaries.officeICAOs.ts} +13 -6
  38. package/src/@dictionaries/dictionaries.regExp.ts +28 -0
  39. package/src/@dictionaries/dictionaries.shapefileLinks.ts +36 -0
  40. package/src/@dictionaries/dictionaries.statusCorrelationText.ts +40 -0
  41. package/src/@dictionaries/dictionaries.test_signatures.ts +23 -0
  42. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +68 -0
  43. package/src/@events/events.api.ts +113 -0
  44. package/src/@events/events.text.ts +79 -0
  45. package/src/@events/events.ugc.ts +83 -0
  46. package/src/@events/events.vtec.ts +87 -0
  47. package/src/@manager/manager.mkEvent.ts +79 -0
  48. package/src/@manager/manager.rmEvent.ts +44 -0
  49. package/src/@manager/manager.setHash.ts +37 -0
  50. package/src/@manager/manager.updateNodes.ts +51 -0
  51. package/src/@modules/@database/database.cache.ts +48 -0
  52. package/src/@modules/@database/database.init.ts +45 -0
  53. package/src/@modules/@database/database.shapefiles.ts +97 -0
  54. package/src/@modules/@database/database.stanza.ts +47 -0
  55. package/src/@modules/@stanza/stanza.getAwipsType.ts +46 -0
  56. package/src/@modules/@stanza/stanza.validate.ts +50 -0
  57. package/src/@modules/@utilities/utilities.createHttp.ts +75 -0
  58. package/src/@modules/@utilities/utilities.getFormattedTime.ts +43 -0
  59. package/src/@modules/@utilities/utilities.getSettings.ts +25 -0
  60. package/src/@modules/@utilities/utilities.getShapeNearestPoint.ts +114 -0
  61. package/src/@modules/@utilities/utilities.setCronSchedule.ts +38 -0
  62. package/src/@modules/@utilities/utilities.setEventEmit.ts +41 -0
  63. package/src/@modules/@utilities/utilities.setListener.ts +30 -0
  64. package/src/@modules/@utilities/utilities.setSettings.ts +42 -0
  65. package/src/@modules/@utilities/utilities.setSleep.ts +33 -0
  66. package/src/@modules/@utilities/utilities.setTimeoutAction.ts +59 -0
  67. package/src/@modules/@utilities/utilities.setWarning.ts +34 -0
  68. package/src/@modules/@xmpp/xmpp.xDeploy.ts +58 -0
  69. package/src/@modules/@xmpp/xmpp.xError.ts +38 -0
  70. package/src/@modules/@xmpp/xmpp.xOffline.ts +38 -0
  71. package/src/@modules/@xmpp/xmpp.xOnline.ts +61 -0
  72. package/src/@modules/@xmpp/xmpp.xReconnect.ts +59 -0
  73. package/src/@modules/@xmpp/xmpp.xStanza.ts +63 -0
  74. package/src/@parsers/@hvtec/hvtec.extract.ts +40 -0
  75. package/src/@parsers/@pvtec/pvtec.expires.ts +26 -0
  76. package/src/@parsers/@pvtec/pvtec.extract.ts +50 -0
  77. package/src/@parsers/@text/text.getDescriptionFromProduct.ts +53 -0
  78. package/src/@parsers/@text/text.getPolygonFromProduct.ts +32 -0
  79. package/src/@parsers/@text/text.getTextFromProduct.ts +43 -0
  80. package/src/@parsers/@text/text.getXML.ts +61 -0
  81. package/src/@parsers/@ugc/ugc.coordinates.ts +110 -0
  82. package/src/@parsers/@ugc/ugc.expiry.ts +32 -0
  83. package/src/@parsers/@ugc/ugc.extract.ts +37 -0
  84. package/src/@parsers/@ugc/ugc.header.ts +30 -0
  85. package/src/@parsers/@ugc/ugc.locations.ts +29 -0
  86. package/src/@parsers/@ugc/ugc.zones.ts +52 -0
  87. package/src/@types/type.event.ts +67 -0
  88. package/src/@types/type.properties.ts +75 -0
  89. package/src/@types/types.attributes.ts +28 -0
  90. package/src/@types/types.compiled.ts +35 -0
  91. package/src/@types/types.hash.ts +24 -0
  92. package/src/@types/types.hvtec.ts +25 -0
  93. package/src/@types/types.pvtec.ts +29 -0
  94. package/src/@types/types.settings.ts +71 -0
  95. package/src/@types/types.stanza.ts +37 -0
  96. package/src/@types/types.ugc.ts +24 -0
  97. package/src/bootstrap.ts +82 -163
  98. package/src/index.ts +48 -216
  99. package/test.js +65 -49
  100. package/src/@dictionaries/events.ts +0 -168
  101. package/src/@parsers/@events/api.ts +0 -146
  102. package/src/@parsers/@events/cap.ts +0 -123
  103. package/src/@parsers/@events/text.ts +0 -104
  104. package/src/@parsers/@events/ugc.ts +0 -107
  105. package/src/@parsers/@events/vtec.ts +0 -76
  106. package/src/@parsers/events.ts +0 -392
  107. package/src/@parsers/hvtec.ts +0 -46
  108. package/src/@parsers/pvtec.ts +0 -72
  109. package/src/@parsers/stanza.ts +0 -97
  110. package/src/@parsers/text.ts +0 -165
  111. package/src/@parsers/ugc.ts +0 -247
  112. package/src/@submodules/database.ts +0 -201
  113. package/src/@submodules/eas.ts +0 -490
  114. package/src/@submodules/utils.ts +0 -191
  115. package/src/@submodules/xmpp.ts +0 -142
  116. package/src/types.ts +0 -259
@@ -1,490 +0,0 @@
1
- /*
2
- _ _ __ __
3
- /\ | | | | (_) \ \ / /
4
- / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
- / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
- / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
- /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
- | |
9
- |_|
10
-
11
- Written by: KiyoWx (k3yomi)
12
- */
13
-
14
- import * as loader from '../bootstrap';
15
- import * as types from '../types';
16
- import Utils from './utils';
17
-
18
- export class EAS {
19
-
20
- /**
21
- * @function generateEASAudio
22
- * @description
23
- * Generates an EAS (Emergency Alert System) audio file for a given message
24
- * and SAME/VTEC code. The audio is composed of optional intro tones, SAME
25
- * headers, attention tones, TTS narration of the message, and repeated
26
- * SAME headers. The resulting audio is processed for NWR-style broadcast
27
- * quality and saved as a WAV file.
28
- *
29
- * @static
30
- * @async
31
- * @param {string} message
32
- * @param {string} header
33
- * @returns {Promise<string | null>}
34
- */
35
- public static generateEASAudio(message: string, header: string): Promise<string | null> {
36
- return new Promise(async (resolve) => {
37
- const settings = loader.settings as types.ClientSettingsTypes;
38
- const assetsDir = settings.global_settings.eas_settings.directory;
39
- const rngFile = `${header.replace(/[^a-zA-Z0-9]/g, `_`)}`.substring(0, 32).replace(/^_+|_+$/g, '');
40
- const os = loader.packages.os.platform();
41
- for (const { regex, replacement } of loader.definitions.messageSignatures) { message = message.replace(regex, replacement); }
42
- if (!assetsDir) { Utils.warn(loader.definitions.messages.eas_no_directory); return resolve(null); }
43
- if (!loader.packages.fs.existsSync(assetsDir)) { loader.packages.fs.mkdirSync(assetsDir); }
44
-
45
- const tmpTTS = loader.packages.path.join(assetsDir, `/tmp/${rngFile}.wav`);
46
- const outTTS = loader.packages.path.join(assetsDir, `/output/${rngFile}.wav`);
47
- const voice = process.platform === 'win32' ? 'Microsoft David Desktop' : 'en-US-GuyNeural';
48
-
49
-
50
- if (!loader.packages.fs.existsSync(loader.packages.path.join(assetsDir, `/tmp`))) { loader.packages.fs.mkdirSync(loader.packages.path.join(assetsDir, `/tmp`), { recursive: true }); }
51
- if (!loader.packages.fs.existsSync(loader.packages.path.join(assetsDir, `/output`))) { loader.packages.fs.mkdirSync(loader.packages.path.join(assetsDir, `/output`), { recursive: true }); }
52
- if (os == 'win32') { loader.packages.say.export(message, voice, 1.0, tmpTTS); }
53
- if (os == 'linux') {
54
- message = message.replace(/[\r\n]+/g, ' ');
55
- const festivalCommand = `echo "${message.replace(/"/g, '\\"')}" | text2wave -o "${tmpTTS}"`;
56
- loader.packages.child.execSync(festivalCommand);
57
- }
58
- await Utils.sleep(3500);
59
- let ttsBuffer: Buffer = null;
60
- while (!loader.packages.fs.existsSync(tmpTTS) || (ttsBuffer = loader.packages.fs.readFileSync(tmpTTS)).length === 0) {
61
- await Utils.sleep(25);
62
- }
63
-
64
- const ttsWav = this.parseWavPCM16(ttsBuffer);
65
- const ttsSamples = this.resamplePCM16(ttsWav.samples, ttsWav.sampleRate, 8000);
66
- const ttsRadio = this.applyNWREffect(ttsSamples, 8000);
67
- let toneRadio = null;
68
-
69
- if (loader.packages.fs.existsSync(settings.global_settings.eas_settings.intro_wav)) {
70
- const toneBuffer = loader.packages.fs.readFileSync(settings.global_settings.eas_settings.intro_wav);
71
- const toneWav = this.parseWavPCM16(toneBuffer);
72
- if (toneWav == null) { console.log(`[EAS] Intro tone WAV file is not valid PCM 16-bit format.`); return resolve(null); }
73
- const toneSamples = (toneWav.sampleRate !== 8000 ? this.resamplePCM16(toneWav.samples, toneWav.sampleRate, 8000) : toneWav.samples);
74
- toneRadio = this.applyNWREffect(toneSamples, 8000);
75
- }
76
- let build = toneRadio != null ? [toneRadio, this.generateSilence(0.5, 8000)] : [];
77
- build.push( this.generateSAMEHeader(header, 3, 8000, { preMarkSec: 1.1, gapSec: 0.5 }), this.generateSilence(0.5, 8000), this.generateAttentionTone(8, 8000), this.generateSilence(0.5, 8000), ttsRadio);
78
-
79
- for (let i = 0; i < 3; i++) {
80
- build.push(this.generateSAMEHeader(header, 1, 8000, { preMarkSec: 0.5, gapSec: 0.1 }));
81
- build.push(this.generateSilence(0.5, 8000));
82
- }
83
- const allSamples = this.concatPCM16(build);
84
- const finalSamples = this.addNoise(allSamples, 0.002);
85
- const outBuffer = this.encodeWavPCM16(Array.from(finalSamples).map(v => ({ value: v })), 8000);
86
- loader.packages.fs.writeFileSync(outTTS, outBuffer);
87
- try {
88
- loader.packages.fs.unlinkSync(tmpTTS);
89
- } catch (error) {
90
- if (error.code !== 'EBUSY') { throw error; }
91
- }
92
- return resolve(outTTS);
93
- });
94
- }
95
-
96
- /**
97
- * @function encodeWavPCM16
98
- * @description
99
- * Encodes an array of 16-bit PCM samples into a standard WAV file buffer.
100
- * Produces mono audio with 16 bits per sample and a specified sample rate.
101
- *
102
- * The input `samples` array should be an array of objects containing a
103
- * numeric `value` property representing the PCM sample.
104
- *
105
- * @private
106
- * @static
107
- * @param {Record<string, number>[]} samples
108
- * @param {number} [sampleRate=8000]
109
- * @returns {Buffer}
110
- */
111
- private static encodeWavPCM16(samples: Record<string, number>[], sampleRate: number = 8000): Buffer {
112
- const bytesPerSample = 2;
113
- const blockAlign = 1 * bytesPerSample;
114
- const byteRate = sampleRate * blockAlign;
115
- const subchunk2Size = samples.length * bytesPerSample;
116
- const chunkSize = 36 + subchunk2Size;
117
-
118
- const buffer = Buffer.alloc(44 + subchunk2Size);
119
- let o = 0;
120
- buffer.write("RIFF", o); o += 4;
121
- buffer.writeUInt32LE(chunkSize, o); o += 4;
122
- buffer.write("WAVE", o); o += 4;
123
-
124
- buffer.write("fmt ", o); o += 4;
125
- buffer.writeUInt32LE(16, o); o += 4;
126
- buffer.writeUInt16LE(1, o); o += 2;
127
- buffer.writeUInt16LE(1, o); o += 2;
128
- buffer.writeUInt32LE(sampleRate, o); o += 4;
129
- buffer.writeUInt32LE(byteRate, o); o += 4;
130
- buffer.writeUInt16LE(blockAlign, o); o += 2;
131
- buffer.writeUInt16LE(16, o); o += 2;
132
-
133
- buffer.write("data", o); o += 4;
134
- buffer.writeUInt32LE(subchunk2Size, o); o += 4;
135
-
136
- for (let i = 0; i < samples.length; i++, o += 2) {
137
- buffer.writeInt16LE(samples[i].value, o);
138
- }
139
- return buffer;
140
- }
141
-
142
- /**
143
- * @function parseWavPCM16
144
- * @description
145
- * Parses a WAV buffer containing 16-bit PCM mono audio and extracts
146
- * the sample data along with format information.
147
- *
148
- * Only supports PCM format (audioFormat = 1), 16 bits per sample,
149
- * and single-channel (mono) audio. Returns `null` if the buffer
150
- * is invalid or does not meet these requirements.
151
- *
152
- * @private
153
- * @static
154
- * @param {Buffer} buffer
155
- * @returns { { samples: Int16Array; sampleRate: number; channels: number; bitsPerSample: number } | null }
156
- */
157
-
158
- private static parseWavPCM16(buffer: Buffer): { samples: Int16Array; sampleRate: number; channels: number; bitsPerSample: number } | null {
159
- if (buffer.toString("ascii", 0, 4) !== "RIFF" || buffer.toString("ascii", 8, 12) !== "WAVE") { return null; }
160
- let fmt = null;
161
- let data = null;
162
- let i = 12;
163
- while (i + 8 <= buffer.length) {
164
- const id = buffer.toString("ascii", i, i + 4);
165
- const size = buffer.readUInt32LE(i + 4);
166
- const start = i + 8;
167
- const end = start + size;
168
- if (id === "fmt ") fmt = buffer.slice(start, end);
169
- if (id === "data") data = buffer.slice(start, end);
170
- i = end + (size % 2);
171
- }
172
- if (!fmt || !data) return null;
173
- const audioFormat = fmt.readUInt16LE(0);
174
- const channels = fmt.readUInt16LE(2);
175
- const sampleRate = fmt.readUInt32LE(4);
176
- const bitsPerSample = fmt.readUInt16LE(14);
177
- if (audioFormat !== 1 || bitsPerSample !== 16 || channels !== 1) { return null; }
178
- const samples = new Int16Array(data.buffer, data.byteOffset, data.length / 2);
179
- return { samples: new Int16Array(samples), sampleRate, channels, bitsPerSample };
180
- }
181
-
182
- /**
183
- * @function concatPCM16
184
- * @description
185
- * Concatenates multiple Int16Array PCM audio buffers into a single
186
- * contiguous Int16Array.
187
- *
188
- * @private
189
- * @static
190
- * @param {Int16Array[]} arrays
191
- * @returns {Int16Array}
192
- */
193
- private static concatPCM16(arrays: Int16Array[]): Int16Array {
194
- let total = 0;
195
- for (const a of arrays) total += a.length;
196
- const out = new Int16Array(total);
197
- let o = 0;
198
- for (const a of arrays) {
199
- out.set(a, o);
200
- o += a.length;
201
- }
202
- return out;
203
- }
204
-
205
- /**
206
- * @function pcm16toFloat
207
- * @description
208
- * Converts a PCM16 Int16Array audio buffer to a Float32Array
209
- * with normalized values in the range [-1, 1).
210
- *
211
- * @private
212
- * @static
213
- * @param {Int16Array} int16
214
- * @returns {Float32Array}
215
- */
216
- private static pcm16toFloat(int16: Int16Array): Float32Array {
217
- const out = new Float32Array(int16.length);
218
- for (let i = 0; i < int16.length; i++) out[i] = int16[i] / 32768;
219
- return out;
220
- }
221
-
222
- /**
223
- * @function floatToPcm16
224
- * @description
225
- * Converts a Float32Array of audio samples in the range [-1, 1]
226
- * to a PCM16 Int16Array.
227
- *
228
- * @private
229
- * @static
230
- * @param {Float32Array} float32
231
- * @returns {Int16Array}
232
- */
233
-
234
- private static floatToPcm16(float32: Float32Array): Int16Array {
235
- const out = new Int16Array(float32.length);
236
- for (let i = 0; i < float32.length; i++) {
237
- let v = Math.max(-1, Math.min(1, float32[i]));
238
- out[i] = Math.round(v * 32767);
239
- }
240
- return out;
241
- }
242
-
243
- /**
244
- * @function resamplePCM16
245
- * @description
246
- * Resamples a PCM16 audio buffer from an original sample rate to a
247
- * target sample rate using linear interpolation.
248
- *
249
- * @private
250
- * @static
251
- * @param {Int16Array} int16
252
- * @param {number} originalRate
253
- * @param {number} targetRate
254
- * @returns {Int16Array}
255
- */
256
- private static resamplePCM16(int16: Int16Array, originalRate: number, targetRate: number): Int16Array {
257
- if (originalRate === targetRate) return int16;
258
- const ratio = targetRate / originalRate;
259
- const outLen = Math.max(1, Math.round(int16.length * ratio));
260
- const out = new Int16Array(outLen);
261
- for (let i = 0; i < outLen; i++) {
262
- const pos = i / ratio;
263
- const i0 = Math.floor(pos);
264
- const i1 = Math.min(i0 + 1, int16.length - 1);
265
- const frac = pos - i0;
266
- const v = int16[i0] * (1 - frac) + int16[i1] * frac;
267
- out[i] = Math.round(v);
268
- }
269
- return out;
270
- }
271
-
272
- /**
273
- * @function generateSilence
274
- * @description
275
- * Generates a PCM16 audio buffer containing silence for a specified
276
- * duration.
277
- *
278
- * @private
279
- * @static
280
- * @param {number} ms
281
- * @param {number} [sampleRate=8000]
282
- * @returns {Int16Array}
283
- */
284
- private static generateSilence(ms: number, sampleRate:number = 8000): Int16Array {
285
- return new Int16Array(Math.floor(ms * sampleRate));
286
- }
287
-
288
- /**
289
- * @function generateAttentionTone
290
- * @description
291
- * Generates a dual-frequency Attention Tone (853 Hz and 960 Hz) used in
292
- * EAS/SAME alerts. Produces a PCM16 buffer of the specified duration.
293
- *
294
- * @private
295
- * @static
296
- * @param {number} ms
297
- * @param {number} [sampleRate=8000]
298
- * @returns {Int16Array}
299
- */
300
- private static generateAttentionTone(ms: number, sampleRate: number = 8000): Int16Array {
301
- const len = Math.floor(ms * sampleRate);
302
- const out = new Int16Array(len);
303
- const f1 = 853;
304
- const f2 = 960;
305
- const twoPi = Math.PI * 2;
306
- const amp = 0.1;
307
- const fadeLen = Math.floor(sampleRate * 0.00);
308
- for (let i = 0; i < len; i++) {
309
- const t = i / sampleRate;
310
- const s = Math.sin(twoPi * f1 * t) + Math.sin(twoPi * f2 * t);
311
- let gain = 1;
312
- if (i < fadeLen) gain = i / fadeLen;
313
- else if (i > len - fadeLen) gain = (len - i) / fadeLen;
314
- const v = Math.max(-1, Math.min(1, (s / 2) * amp * gain));
315
- out[i] = Math.round(v * 32767);
316
- }
317
- return out;
318
- }
319
-
320
- /**
321
- * @function applyNWREffect
322
- * @description
323
- * Applies a National Weather Radio (NWR)-style audio effect to a PCM16
324
- * buffer, including high-pass and low-pass filtering, soft clipping
325
- * compression, and optional bit reduction to simulate vintage broadcast
326
- * characteristics.
327
- *
328
- * @private
329
- * @static
330
- * @param {Int16Array} int16
331
- * @param {number} [sampleRate=8000]
332
- * @returns {Int16Array}
333
- */
334
- private static applyNWREffect(int16: Int16Array, sampleRate: number = 8000): Int16Array {
335
- const hpCut = 3555;
336
- const lpCut = 1600;
337
- const noiseLevel = 0.0;
338
- const crushBits = 8;
339
- const x = this.pcm16toFloat(int16);
340
- const dt = 1 / sampleRate;
341
- const rcHP = 1 / (2 * Math.PI * hpCut);
342
- const aHP = rcHP / (rcHP + dt);
343
- let yHP = 0, xPrev = 0;
344
- for (let i = 0; i < x.length; i++) {
345
- const xi = x[i];
346
- yHP = aHP * (yHP + xi - xPrev);
347
- xPrev = xi;
348
- x[i] = yHP;
349
- }
350
- const rcLP = 1 / (2 * Math.PI * lpCut);
351
- const aLP = dt / (rcLP + dt);
352
- let yLP = 0;
353
- for (let i = 0; i < x.length; i++) {
354
- yLP = yLP + aLP * (x[i] - yLP);
355
- x[i] = yLP;
356
- }
357
- const compGain = 2.0;
358
- const norm = Math.tanh(compGain);
359
- for (let i = 0; i < x.length; i++) x[i] = Math.tanh(x[i] * compGain) / norm;
360
- const levels = Math.pow(2, crushBits) - 1;
361
- return this.floatToPcm16(x);
362
- }
363
-
364
- /**
365
- * @function addNoise
366
- * @description
367
- * Adds random noise to a PCM16 audio buffer and normalizes the signal
368
- * to prevent clipping. Useful for simulating real-world signal conditions
369
- * or reducing digital artifacts.
370
- *
371
- * @private
372
- * @static
373
- * @param {Int16Array} int16
374
- * @param {number} [noiseLevel=0.02]
375
- * @returns {Int16Array}
376
- */
377
- private static addNoise(int16: Int16Array, noiseLevel: number = 0.02): Int16Array {
378
- const x = this.pcm16toFloat(int16);
379
- for (let i = 0; i < x.length; i++) x[i] += (Math.random() * 2 - 1) * noiseLevel;
380
- let peak = 0;
381
- for (let i = 0; i < x.length; i++) peak = Math.max(peak, Math.abs(x[i]));
382
- if (peak > 1) for (let i = 0; i < x.length; i++) x[i] *= 0.98 / peak;
383
- return this.floatToPcm16(x);
384
- }
385
-
386
- /**
387
- * @function asciiTo8N1Bits
388
- * @description
389
- * Converts an ASCII string into a sequence of bits using the 8N1 framing
390
- * convention (1 start bit, 8 data bits, 2 stop bits) commonly used in
391
- * serial and EAS transmissions.
392
- *
393
- * @private
394
- * @static
395
- * @param {string} str
396
- * @returns {number[]}
397
- */
398
- private static asciiTo8N1Bits(str: string): number[] {
399
- const bits = [];
400
- for (let i = 0; i < str.length; i++) {
401
- const c = str.charCodeAt(i) & 0xFF;
402
- bits.push(0);
403
- for (let b = 0; b < 8; b++) bits.push((c >> b) & 1);
404
- bits.push(1, 1);
405
- }
406
- return bits;
407
- }
408
-
409
- /**
410
- * @function generateAFSK
411
- * @description
412
- * Converts a sequence of bits into AFSK-modulated PCM16 audio data for EAS
413
- * alerts. Applies a fade-in and fade-out to reduce clicks and generates
414
- * the audio at the specified sample rate.
415
- *
416
- * @private
417
- * @static
418
- * @param {number[]} bits
419
- * @param {number} [sampleRate=8000]
420
- * @returns {Int16Array}
421
- */
422
- private static generateAFSK(bits: number[], sampleRate: number = 8000): Int16Array {
423
- const baud = 520.83;
424
- const markFreq = 2083.3;
425
- const spaceFreq = 1562.5;
426
- const amplitude = 0.6;
427
- const twoPi = Math.PI * 2;
428
- const result = [];
429
- let phase = 0;
430
- let frac = 0;
431
- for (let b = 0; b < bits.length; b++) {
432
- const bit = bits[b];
433
- const freq = bit ? markFreq : spaceFreq;
434
- const samplesPerBit = sampleRate / baud + frac;
435
- const n = Math.round(samplesPerBit);
436
- frac = samplesPerBit - n;
437
- const inc = twoPi * freq / sampleRate;
438
- for (let i = 0; i < n; i++) {
439
- result.push(Math.round(Math.sin(phase) * amplitude * 32767));
440
- phase += inc;
441
- if (phase > twoPi) phase -= twoPi;
442
- }
443
- }
444
- const fadeSamples = Math.floor(sampleRate * 0.002);
445
- for (let i = 0; i < fadeSamples; i++) {
446
- const gain = i / fadeSamples;
447
- result[i] = Math.round(result[i] * gain);
448
- result[result.length - 1 - i] = Math.round(result[result.length - 1 - i] * gain);
449
- }
450
-
451
- return Int16Array.from(result);
452
- }
453
-
454
- /**
455
- * @function generateSAMEHeader
456
- * @description
457
- * Generates a SAME (Specific Area Message Encoding) audio header for
458
- * EAS alerts. Converts a VTEC string into AFSK-modulated PCM16 audio,
459
- * optionally repeating the signal with pre-mark and gap intervals.
460
- *
461
- * @private
462
- * @static
463
- * @param {string} vtec
464
- * @param {number} repeats
465
- * @param {number} [sampleRate=8000]
466
- * @param {{preMarkSec?: number, gapSec?: number}} [options={}]
467
- * @returns {Int16Array}
468
- */
469
- private static generateSAMEHeader(vtec: string, repeats: number, sampleRate: number = 8000, options: {preMarkSec?: number, gapSec?: number} = {}): Int16Array {
470
- const preMarkSec = options.preMarkSec ?? 0.3;
471
- const gapSec = options.gapSec ?? 0.1;
472
- const bursts = [];
473
- const gap = this.generateSilence(gapSec, sampleRate);
474
- for (let i = 0; i < repeats; i++) {
475
- const bodyBits = this.asciiTo8N1Bits(vtec);
476
- const body = this.generateAFSK(bodyBits, sampleRate);
477
- const extendedBodyDuration = Math.round(preMarkSec * sampleRate);
478
- const extendedBody = new Int16Array(extendedBodyDuration + gap.length);
479
- for (let j = 0; j < extendedBodyDuration; j++) {
480
- extendedBody[j] = Math.round(body[j % body.length] * 0.2);
481
- }
482
- extendedBody.set(gap, extendedBodyDuration);
483
- bursts.push(extendedBody);
484
- if (i !== repeats - 1) bursts.push(gap);
485
- }
486
- return this.concatPCM16(bursts);
487
- }
488
- }
489
-
490
- export default EAS;
@@ -1,191 +0,0 @@
1
- /*
2
- _ _ __ __
3
- /\ | | | | (_) \ \ / /
4
- / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
- / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
- / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
- /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
- | |
9
- |_|
10
-
11
- Written by: KiyoWx (k3yomi)
12
- */
13
-
14
- import * as loader from '../bootstrap';
15
- import * as types from '../types';
16
- import EventParser from '../@parsers/events';
17
- import Xmpp from './xmpp';
18
-
19
- export class Utils {
20
-
21
- /**
22
- * @function sleep
23
- * @description
24
- * Pauses execution for a specified number of milliseconds.
25
- *
26
- * @static
27
- * @async
28
- * @param {number} ms
29
- * @returns {Promise<void>}
30
- */
31
- public static async sleep(ms: number): Promise<void> {
32
- return new Promise(resolve => setTimeout(resolve, ms));
33
- }
34
-
35
- /**
36
- * @function warn
37
- * @description
38
- * Emits a log event and prints a warning to the console. Throttles repeated
39
- * warnings within a short interval unless `force` is `true`.
40
- *
41
- * @static
42
- * @param {string} message
43
- * @param {boolean} [force=false]
44
- * @returns {void}
45
- */
46
- public static warn(message: string, force: boolean = false): void {
47
- loader.cache.events.emit('log', message)
48
- if (!loader.settings.journal) return;
49
- if (loader.cache.lastWarn != null && (Date.now() - loader.cache.lastWarn < 500) && !force) return;
50
- loader.cache.lastWarn = Date.now();
51
- console.warn(`\x1b[33m[ATMOSX-PARSER]\x1b[0m [${new Date().toLocaleString()}] ${message}`);
52
- }
53
-
54
- /**
55
- * @function loadGeoJsonData
56
- * @description
57
- * Fetches GeoJSON data from the National Weather Service endpoint and
58
- * passes it to the event parser for processing.
59
- *
60
- * @static
61
- * @async
62
- * @returns {Promise<void>}
63
- */
64
- public static async loadGeoJsonData(): Promise<void> {
65
- try {
66
- const settings = loader.settings as types.ClientSettingsTypes;
67
- const response = await this.createHttpRequest<types.GenericHTTPResponse >(
68
- settings.national_weather_service_settings.endpoint
69
- );
70
- if (response.error) return;
71
- EventParser.eventHandler({
72
- message: JSON.stringify(response.message),
73
- attributes: {},
74
- isCap: true,
75
- isApi: true,
76
- isPVtec: false,
77
- isUGC: false,
78
- isCapDescription: false,
79
- awipsType: { type: 'api', prefix: 'AP' },
80
- ignore: false
81
- });
82
- } catch (error: unknown) {
83
- const msg = error instanceof Error ? error.message : String(error);
84
- Utils.warn(`Failed to load National Weather Service GeoJSON Data: ${msg}`);
85
- }
86
- }
87
-
88
- /**
89
- * @function createHttpRequest
90
- * @description
91
- * Performs an HTTP GET request with default headers and timeout, returning
92
- * either the response data or an error message.
93
- *
94
- * @static
95
- * @template T
96
- * @param {string} url
97
- * @param {types.HTTPSettings} [options]
98
- * @returns {Promise<{ error: boolean; message: T | string }>}
99
- */
100
- public static async createHttpRequest<T = unknown>(url: string, options?: types.HTTPSettings): Promise<{ error: boolean; message: T | string }> {
101
- const defaultOptions = {
102
- timeout: 10000,
103
- headers: {
104
- "User-Agent": "AtmosphericX",
105
- "Accept": "application/geo+json, text/plain, */*; q=0.9",
106
- "Accept-Language": "en-US,en;q=0.9"
107
- }
108
- };
109
- const requestOptions = {
110
- ...defaultOptions,
111
- ...options,
112
- headers: { ...defaultOptions.headers, ...(options?.headers ?? {}) }
113
- };
114
- try {
115
- const controller = new AbortController();
116
- const timeoutId = setTimeout(() => controller.abort(), requestOptions.timeout);
117
- const resp = await fetch(url, {
118
- headers: requestOptions.headers,
119
- signal: controller.signal,
120
- redirect: 'manual'
121
- });
122
- clearTimeout(timeoutId);
123
- if (resp.status !== 200 && resp.status !== 500) {
124
- throw new Error(`HTTP Error: ${resp.status}`);
125
- }
126
-
127
- const data = await resp.json() as T;
128
- return { error: false, message: data };
129
- } catch (err: unknown) {
130
- const msg = err instanceof Error ? err.message : String(err);
131
- return { error: true, message: msg };
132
- }
133
- }
134
-
135
- /**
136
- * @function handleCronJob
137
- * @description
138
- * Performs scheduled tasks for NWWS XMPP session maintenance or GeoJSON data
139
- * updates depending on the job type.
140
- *
141
- * @static
142
- * @param {boolean} isWire
143
- * @returns {void}
144
- */
145
- public static handleCronJob(isWire: boolean): void {
146
- try {
147
- const settings = loader.settings as types.ClientSettingsTypes;
148
- const cache = settings.noaa_weather_wire_service_settings.cache;
149
- const reconnections = settings.noaa_weather_wire_service_settings.reconnection_settings;
150
- if (isWire) {
151
- if (reconnections.enabled) {
152
- void Xmpp.isSessionReconnectionEligible(reconnections.interval);
153
- }
154
- } else {
155
- void this.loadGeoJsonData();
156
- }
157
- } catch (error: unknown) {
158
- const msg = error instanceof Error ? error.message : String(error);
159
- Utils.warn(`Failed to perform scheduled tasks (${isWire ? 'NWWS' : 'GeoJSON'}): ${msg}`);
160
- }
161
- }
162
-
163
- /**
164
- * @function mergeClientSettings
165
- * @description
166
- * Recursively merges a ClientSettings object into a target object,
167
- * preserving nested structures and overriding existing values.
168
- *
169
- * @static
170
- * @param {Record<string, unknown>} target
171
- * @param {types.ClientSettingsTypes} settings
172
- * @returns {Record<string, unknown>}
173
- */
174
- public static mergeClientSettings(target: Record<string, unknown>, settings: types.ClientSettingsTypes): Record<string, unknown> {
175
- for (const key in settings) {
176
- if (!Object.prototype.hasOwnProperty.call(settings, key)) continue;
177
- const value = settings[key];
178
- if (value && typeof value === 'object' && !Array.isArray(value)) {
179
- if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
180
- target[key] = {};
181
- }
182
- this.mergeClientSettings(target[key] as Record<string, unknown>, value as types.ClientSettingsTypes);
183
- } else {
184
- target[key] = value;
185
- }
186
- }
187
- return target;
188
- }
189
- }
190
-
191
- export default Utils;