mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
First version of external sounds
This commit is contained in:
@@ -1,25 +1,23 @@
|
||||
import { byteArrayToDouble, byteArrayToInteger, doubleToByteArray, integerToByteArray } from "../utils";
|
||||
import { Buffer } from "buffer";
|
||||
import {
|
||||
byteArrayToDouble,
|
||||
byteArrayToInteger,
|
||||
doubleToByteArray,
|
||||
integerToByteArray,
|
||||
} from "../utils";
|
||||
|
||||
var packetID = 0;
|
||||
|
||||
export enum MessageType {
|
||||
audio,
|
||||
settings,
|
||||
unitIDs
|
||||
}
|
||||
|
||||
export class AudioPacket {
|
||||
#encodedData: Uint8Array;
|
||||
|
||||
/* Mandatory data */
|
||||
#frequencies: { frequency: number; modulation: number; encryption: number }[];
|
||||
#frequencies: { frequency: number; modulation: number; encryption: number }[] = [];
|
||||
#audioData: Uint8Array;
|
||||
#transmissionGUID: string;
|
||||
#clientGUID: string;
|
||||
|
||||
/* Default data */
|
||||
#unitID: number = 0;
|
||||
#packetID: number = 0;
|
||||
#hops: number = 0;
|
||||
|
||||
/* Out of standard data (this is not compliant with SRS standard, used for external audio effects) */
|
||||
@@ -27,15 +25,150 @@ export class AudioPacket {
|
||||
#longitude: number | null = null;
|
||||
#altitude: number | null = null;
|
||||
|
||||
setEncodedData(encodedData: Uint8Array) {
|
||||
this.#encodedData = encodedData;
|
||||
/* Usually internally set only */
|
||||
#packetID: number | null = null;
|
||||
|
||||
fromByteArray(byteArray: Uint8Array) {
|
||||
let totalLength = byteArrayToInteger(byteArray.slice(0, 2));
|
||||
let audioLength = byteArrayToInteger(byteArray.slice(2, 4));
|
||||
let frequenciesLength = byteArrayToInteger(byteArray.slice(4, 6));
|
||||
|
||||
/* Perform some sanity checks */
|
||||
if (totalLength !== byteArray.length) {
|
||||
console.log(
|
||||
`Warning, audio packet expected length is ${totalLength} but received length is ${byteArray.length}, aborting...`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frequenciesLength % 10 !== 0) {
|
||||
console.log(
|
||||
`Warning, audio packet frequencies data length is ${frequenciesLength} which is not a multiple of 10, aborting...`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract the audio data */
|
||||
this.#audioData = byteArray.slice(6, 6 + audioLength);
|
||||
|
||||
/* Extract the frequencies */
|
||||
let offset = 6 + audioLength;
|
||||
for (let idx = 0; idx < frequenciesLength / 10; idx++) {
|
||||
this.#frequencies.push({
|
||||
frequency: byteArrayToDouble(byteArray.slice(offset, offset + 8)),
|
||||
modulation: byteArray[offset + 8],
|
||||
encryption: byteArray[offset + 9],
|
||||
});
|
||||
offset += 10;
|
||||
}
|
||||
|
||||
/* If necessary increase the packetID */
|
||||
if (this.#packetID === null) this.#packetID = packetID++;
|
||||
|
||||
/* Extract the remaining data */
|
||||
this.#unitID = byteArrayToInteger(byteArray.slice(offset, offset + 4));
|
||||
offset += 4;
|
||||
this.#packetID = byteArrayToInteger(byteArray.slice(offset, offset + 8));
|
||||
offset += 8;
|
||||
this.#hops = byteArrayToInteger(byteArray.slice(offset, offset + 1));
|
||||
offset += 1;
|
||||
this.#transmissionGUID = new TextDecoder().decode(byteArray.slice(offset, offset + 22));
|
||||
offset += 22;
|
||||
this.#clientGUID = new TextDecoder().decode(byteArray.slice(offset, offset + 22));
|
||||
offset += 22;
|
||||
}
|
||||
|
||||
getEncodedData() {
|
||||
return this.#encodedData;
|
||||
toByteArray() {
|
||||
/* Perform some sanity checks // TODO check correct values */
|
||||
if (this.#frequencies.length === 0) {
|
||||
console.log(
|
||||
"Warning, could not encode audio packet, no frequencies data provided, aborting..."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#audioData === undefined) {
|
||||
console.log(
|
||||
"Warning, could not encode audio packet, no audio data provided, aborting..."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#transmissionGUID === undefined) {
|
||||
console.log(
|
||||
"Warning, could not encode audio packet, no transmission GUID provided, aborting..."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#clientGUID === undefined) {
|
||||
console.log(
|
||||
"Warning, could not encode audio packet, no client GUID provided, aborting..."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the array for the header
|
||||
let header: number[] = [0, 0, 0, 0, 0, 0];
|
||||
|
||||
// Encode the frequencies data
|
||||
let frequenciesData = [] as number[];
|
||||
this.#frequencies.forEach((data) => {
|
||||
frequenciesData = frequenciesData.concat(
|
||||
[...doubleToByteArray(data.frequency)],
|
||||
[data.modulation],
|
||||
[data.encryption]
|
||||
);
|
||||
});
|
||||
|
||||
// Encode unitID, packetID, hops
|
||||
let encUnitID: number[] = integerToByteArray(this.#unitID, 4);
|
||||
let encPacketID: number[] = integerToByteArray(this.#packetID, 8);
|
||||
let encHops: number[] = [this.#hops];
|
||||
|
||||
// Assemble packet
|
||||
let encodedData: number[] = ([] as number[]).concat(
|
||||
header,
|
||||
[...this.#audioData],
|
||||
frequenciesData,
|
||||
encUnitID,
|
||||
encPacketID,
|
||||
encHops,
|
||||
[...Buffer.from(this.#transmissionGUID, "utf-8")],
|
||||
[...Buffer.from(this.#clientGUID, "utf-8")]
|
||||
);
|
||||
|
||||
if (
|
||||
this.#latitude !== undefined &&
|
||||
this.#longitude !== undefined &&
|
||||
this.#altitude !== undefined
|
||||
) {
|
||||
encodedData.concat(
|
||||
[...doubleToByteArray(this.#latitude)],
|
||||
[...doubleToByteArray(this.#longitude)],
|
||||
[...doubleToByteArray(this.#altitude)]
|
||||
);
|
||||
}
|
||||
|
||||
// Set the lengths of the parts
|
||||
let encPacketLen = integerToByteArray(encodedData.length, 2);
|
||||
encodedData[0] = encPacketLen[0];
|
||||
encodedData[1] = encPacketLen[1];
|
||||
|
||||
let encAudioLen = integerToByteArray(this.#audioData.length, 2);
|
||||
encodedData[2] = encAudioLen[0];
|
||||
encodedData[3] = encAudioLen[1];
|
||||
|
||||
let frequencyAudioLen = integerToByteArray(frequenciesData.length, 2);
|
||||
encodedData[4] = frequencyAudioLen[0];
|
||||
encodedData[5] = frequencyAudioLen[1];
|
||||
|
||||
return new Uint8Array([0].concat(encodedData));
|
||||
}
|
||||
|
||||
setFrequencies(frequencies: { frequency: number; modulation: number; encryption: number }[]) {
|
||||
setFrequencies(
|
||||
frequencies: { frequency: number; modulation: number; encryption: number }[]
|
||||
) {
|
||||
this.#frequencies = frequencies;
|
||||
}
|
||||
|
||||
@@ -78,7 +211,7 @@ export class AudioPacket {
|
||||
setPacketID(packetID: number) {
|
||||
this.#packetID = packetID;
|
||||
}
|
||||
|
||||
|
||||
getPacketID() {
|
||||
return this.#packetID;
|
||||
}
|
||||
@@ -114,123 +247,4 @@ export class AudioPacket {
|
||||
getAltitude() {
|
||||
return this.#altitude;
|
||||
}
|
||||
|
||||
fromByteArray(byteArray: Uint8Array) {
|
||||
let totalLength = byteArrayToInteger(byteArray.slice(0, 2));
|
||||
let audioLength = byteArrayToInteger(byteArray.slice(2, 4));
|
||||
let frequenciesLength = byteArrayToInteger(byteArray.slice(4, 6));
|
||||
|
||||
/* Perform some sanity checks */
|
||||
if (totalLength !== byteArray.length) {
|
||||
console.log(
|
||||
`Warning, audio packet expected length is ${totalLength} but received length is ${byteArray.length}, aborting...`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frequenciesLength % 10 !== 0) {
|
||||
console.log(
|
||||
`Warning, audio packet frequencies data length is ${frequenciesLength} which is not a multiple of 10, aborting...`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract the audio data */
|
||||
this.#audioData = byteArray.slice(6, 6 + audioLength);
|
||||
|
||||
/* Extract the frequencies */
|
||||
let offset = 6 + audioLength;
|
||||
for (let idx = 0; idx < frequenciesLength / 10; idx++) {
|
||||
this.#frequencies.push({
|
||||
frequency: byteArrayToDouble(byteArray.slice(offset, offset + 8)),
|
||||
encryption: byteArray[offset + 8],
|
||||
modulation: byteArray[offset + 9],
|
||||
});
|
||||
offset += 10;
|
||||
}
|
||||
|
||||
/* Extract the remaining data */
|
||||
this.#unitID = byteArrayToInteger(byteArray.slice(offset, offset + 4));
|
||||
offset += 4;
|
||||
this.#packetID = byteArrayToInteger(byteArray.slice(offset, offset + 8));
|
||||
offset += 8;
|
||||
this.#hops = byteArrayToInteger(byteArray.slice(offset, offset + 1));
|
||||
offset += 1;
|
||||
this.#transmissionGUID = byteArray.slice(offset, offset + 22).toString();
|
||||
offset += 22;
|
||||
this.#clientGUID = byteArray.slice(offset, offset + 22).toString();
|
||||
offset += 22;
|
||||
}
|
||||
|
||||
toByteArray() {
|
||||
/* Perform some sanity checks // TODO check correct values */
|
||||
if (this.#frequencies.length === 0) {
|
||||
console.log("Warning, could not encode audio packet, no frequencies data provided, aborting...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#audioData === undefined) {
|
||||
console.log("Warning, could not encode audio packet, no audio data provided, aborting...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#transmissionGUID === undefined) {
|
||||
console.log("Warning, could not encode audio packet, no transmission GUID provided, aborting...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#clientGUID === undefined) {
|
||||
console.log("Warning, could not encode audio packet, no client GUID provided, aborting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the array for the header
|
||||
let header: number[] = [0, 0, 0, 0, 0, 0];
|
||||
|
||||
// Encode the frequencies data
|
||||
let frequenciesData = ([] as number[])
|
||||
this.#frequencies.forEach((data) => {
|
||||
return frequenciesData.concat([...doubleToByteArray(data.frequency)], [data.modulation], [data.encryption]);
|
||||
})
|
||||
|
||||
// Encode unitID, packetID, hops
|
||||
let encUnitID: number[] = integerToByteArray(this.#unitID, 4);
|
||||
let encPacketID: number[] = integerToByteArray(this.#packetID, 8);
|
||||
let encHops: number[] = [this.#hops];
|
||||
|
||||
// Assemble packet
|
||||
let encodedData: number[] = ([] as number[]).concat(
|
||||
header,
|
||||
[...this.#audioData],
|
||||
frequenciesData,
|
||||
encUnitID,
|
||||
encPacketID,
|
||||
encHops,
|
||||
[...Buffer.from(this.#transmissionGUID, "utf-8")],
|
||||
[...Buffer.from(this.#clientGUID, "utf-8")]
|
||||
);
|
||||
|
||||
if (this.#latitude !== undefined && this.#longitude !== undefined && this.#altitude !== undefined) {
|
||||
encodedData.concat(
|
||||
[...doubleToByteArray(this.#latitude)],
|
||||
[...doubleToByteArray(this.#longitude)],
|
||||
[...doubleToByteArray(this.#altitude)]
|
||||
);
|
||||
}
|
||||
|
||||
// Set the lengths of the parts
|
||||
let encPacketLen = integerToByteArray(encodedData.length, 2);
|
||||
encodedData[0] = encPacketLen[0];
|
||||
encodedData[1] = encPacketLen[1];
|
||||
|
||||
let encAudioLen = integerToByteArray(this.#audioData.length, 2);
|
||||
encodedData[2] = encAudioLen[0];
|
||||
encodedData[3] = encAudioLen[1];
|
||||
|
||||
let frequencyAudioLen = integerToByteArray(frequenciesData.length, 2);
|
||||
encodedData[4] = frequencyAudioLen[0];
|
||||
encodedData[5] = frequencyAudioLen[1];
|
||||
|
||||
this.#encodedData = new Uint8Array([0].concat(encodedData));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,13 @@
|
||||
import { MessageType } from "./audiopacket";
|
||||
import { defaultSRSData } from "./defaultdata";
|
||||
|
||||
const { OpusEncoder } = require("@discordjs/opus");
|
||||
const encoder = new OpusEncoder(16000, 1);
|
||||
|
||||
let decoder = null;
|
||||
import('opus-decoder').then((res) => {
|
||||
decoder = new res.OpusDecoder();
|
||||
});
|
||||
|
||||
/* TCP/IP socket */
|
||||
var net = require("net");
|
||||
var bufferString = "";
|
||||
|
||||
const SRS_VERSION = "2.1.0.10";
|
||||
|
||||
var globalIndex = 1;
|
||||
|
||||
enum MessageType {
|
||||
audio,
|
||||
settings,
|
||||
}
|
||||
|
||||
function fromBytes(array) {
|
||||
let res = 0;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
res = res << 8;
|
||||
res += array[array.length - i - 1];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getBytes(value, length) {
|
||||
let res: number[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
res.push(value & 255);
|
||||
value = value >> 8;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export class SRSHandler {
|
||||
ws: any;
|
||||
tcp = new net.Socket();
|
||||
@@ -85,6 +55,19 @@ export class SRSHandler {
|
||||
if (this.tcp.readyState == "open")
|
||||
this.tcp.write(`${JSON.stringify(SYNC)}\n`);
|
||||
else clearInterval(this.syncInterval);
|
||||
|
||||
let unitsBuffer = Buffer.from(
|
||||
JSON.stringify({
|
||||
unitIDs: this.clients.map((client) => {
|
||||
return client.RadioInfo.unitId;
|
||||
}),
|
||||
}),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
this.ws.send(
|
||||
([] as number[]).concat([MessageType.unitIDs], [...unitsBuffer])
|
||||
);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
@@ -94,8 +77,7 @@ export class SRSHandler {
|
||||
try {
|
||||
let message = JSON.parse(bufferString.split("\n")[0]);
|
||||
bufferString = bufferString.slice(bufferString.indexOf("\n") + 1);
|
||||
if (message.Clients !== undefined)
|
||||
this.clients = message.Clients;
|
||||
if (message.Clients !== undefined) this.clients = message.Clients;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -108,44 +90,20 @@ export class SRSHandler {
|
||||
});
|
||||
|
||||
this.udp.on("message", (message, remote) => {
|
||||
if (this.ws && message.length > 22) this.ws.send(message);
|
||||
if (this.ws && message.length > 22)
|
||||
this.ws.send(
|
||||
([] as number[]).concat([MessageType.audio], [...message])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
decodeData(data){
|
||||
decodeData(data) {
|
||||
switch (data[0]) {
|
||||
case MessageType.audio:
|
||||
let packetUint8Array = new Uint8Array(data.slice(1));
|
||||
|
||||
let audioLength = fromBytes(packetUint8Array.slice(2, 4));
|
||||
let frequenciesLength = fromBytes(packetUint8Array.slice(4, 6));
|
||||
let modulation = fromBytes(packetUint8Array.slice(6 + audioLength + 8, 6 + audioLength + 8 + 1));
|
||||
let offset = 6 + audioLength + frequenciesLength;
|
||||
|
||||
if (modulation == 255) {
|
||||
packetUint8Array = packetUint8Array.slice(0, -24) // Remove position data
|
||||
packetUint8Array[6 + audioLength + 8] = 2;
|
||||
this.clients.forEach((client) => {
|
||||
getBytes(client.RadioInfo.unitId, 4).forEach((value, idx) => {
|
||||
packetUint8Array[offset + idx] = value;
|
||||
});
|
||||
|
||||
var dst = new ArrayBuffer(packetUint8Array.byteLength);
|
||||
let newBuffer = new Uint8Array(dst);
|
||||
newBuffer.set(new Uint8Array(packetUint8Array));
|
||||
this.udp.send(newBuffer, this.SRSPort, "localhost", (error) => {
|
||||
if (error)
|
||||
console.log(`Error sending data to SRS server: ${error}`);
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.udp.send(packetUint8Array, this.SRSPort, "localhost", (error) => {
|
||||
if (error)
|
||||
console.log(`Error sending data to SRS server: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const encodedData = new Uint8Array(data.slice(1));
|
||||
this.udp.send(encodedData, this.SRSPort, "localhost", (error) => {
|
||||
if (error) console.log(`Error sending data to SRS server: ${error}`);
|
||||
});
|
||||
break;
|
||||
case MessageType.settings:
|
||||
let message = JSON.parse(data.slice(1));
|
||||
|
||||
Reference in New Issue
Block a user