Completed the audio panel

This commit is contained in:
Davide Passoni
2024-10-15 17:40:39 +02:00
parent 6cc1572b11
commit 065cdf3648
19 changed files with 772 additions and 486 deletions

View File

@@ -209,6 +209,10 @@ export class AudioManager {
if (sink instanceof RadioSink) sink.setName(`Radio ${idx++}`);
});
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
this.#sources.forEach((source) => {
if (source.getConnectedTo().includes(sink))
source.disconnect(sink)
})
}
getGuid() {

View File

@@ -1,7 +1,7 @@
import { getApp } from "../olympusapp";
/* Base audio sink class */
export class AudioSink {
export abstract class AudioSink {
#name: string;
#gainNode: GainNode;
@@ -25,4 +25,7 @@ export class AudioSink {
getInputNode() {
return this.#gainNode;
}
abstract setPtt(ptt: boolean): void;
abstract getPtt(): boolean;
}

View File

@@ -18,9 +18,11 @@ export abstract class AudioSource {
}
connect(sink: AudioSink) {
this.getOutputNode().connect(sink.getInputNode());
this.#connectedTo.push(sink);
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
if (!this.#connectedTo.includes(sink)) {
this.getOutputNode().connect(sink.getInputNode());
this.#connectedTo.push(sink);
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
}
}
disconnect(sinkToDisconnect?: AudioSink) {

View File

@@ -3,8 +3,6 @@ import { Unit } from "../unit/unit";
import { Filter, Noise } from "./audiolibrary";
import { AudioPacket } from "./audiopacket";
const MAX_DISTANCE = 1852; // Ignore clients that are further away than 1NM, to save performance.
export class AudioUnitPipeline {
#inputNode: GainNode;
#sourceUnit: Unit;
@@ -14,11 +12,15 @@ export class AudioUnitPipeline {
#audioTrackProcessor: any;
#encoder: AudioEncoder;
#wetGainNode: GainNode;
#delayNode: DelayNode;
#convolverNode: ConvolverNode;
#tailOsc: Noise;
#tailOscillator: Noise;
#distance: number = 0;
#packetID = 0;
#ptt: boolean = false;
#maxDistance: number = 1852;
constructor(sourceUnit: Unit, unitID: number, inputNode: GainNode) {
this.#sourceUnit = sourceUnit;
@@ -64,6 +66,27 @@ export class AudioUnitPipeline {
/* Create the pipeline */
this.#inputNode = inputNode;
this.#setupEffects();
/* Create the interval task to update the data */
setInterval(() => {
/* Get the destination unit and compute the distance to it */
let destinationUnit = getApp().getUnitsManager().getUnitByID(this.#unitID);
if (destinationUnit) {
let distance = destinationUnit?.getPosition().distanceTo(this.#sourceUnit.getPosition());
/* The units positions are updated at a low frequency. Filter the distance to avoid sudden volume jumps */
this.#distance = 0.9 * this.#distance + 0.1 * distance;
/* Don't bother updating parameters if the client is too far away */
if (this.#distance < this.#maxDistance) {
/* Compute a new gain decreasing with distance. */
let newGain = 1.0 - Math.pow(this.#distance / this.#maxDistance, 2); // Arbitrary
/* Set the values of the main gain node and the multitap gain node, used for reverb effect */
this.#gainNode.gain.setValueAtTime(newGain, getApp().getAudioManager().getAudioContext().currentTime);
}
}
}, 100);
}
handleEncodedData(encodedAudioChunk, unitID) {
@@ -92,7 +115,7 @@ export class AudioUnitPipeline {
handleRawData(audioData) {
/* Ignore players that are too far away */
if (this.#distance < MAX_DISTANCE) {
if (this.#distance < this.#maxDistance && this.#ptt) {
this.#encoder.encode(audioData);
audioData.close();
}
@@ -102,18 +125,18 @@ export class AudioUnitPipeline {
/* Create the nodes necessary for the pipeline */
this.#convolverNode = getApp().getAudioManager().getAudioContext().createConvolver();
let wetGainNode = getApp().getAudioManager().getAudioContext().createGain();
wetGainNode.gain.setValueAtTime(2.0, getApp().getAudioManager().getAudioContext().currentTime)
let delayNode = getApp().getAudioManager().getAudioContext().createDelay(1);
delayNode.delayTime.setValueAtTime(0.09, getApp().getAudioManager().getAudioContext().currentTime)
this.#wetGainNode = getApp().getAudioManager().getAudioContext().createGain();
this.#wetGainNode.gain.setValueAtTime(2.0, getApp().getAudioManager().getAudioContext().currentTime)
this.#delayNode = getApp().getAudioManager().getAudioContext().createDelay(1);
this.#delayNode.delayTime.setValueAtTime(0.09, getApp().getAudioManager().getAudioContext().currentTime)
this.#inputNode.connect(this.#gainNode);
this.#gainNode.connect(this.#destinationNode);
this.#gainNode.connect(wetGainNode);
wetGainNode.connect(delayNode);
delayNode.connect(this.#convolverNode);
this.#gainNode.connect(this.#wetGainNode);
this.#wetGainNode.connect(this.#delayNode);
this.#delayNode.connect(this.#convolverNode);
this.#convolverNode.connect(this.#destinationNode);
/* Render the random noise needed for the convolver node to simulate reverb */
@@ -132,17 +155,17 @@ export class AudioUnitPipeline {
);
/* A noise oscillator and a two filters are added to smooth the reverb */
this.#tailOsc = new Noise(tailContext, 1);
this.#tailOscillator = new Noise(tailContext, 1);
const tailLPFilter = new Filter(tailContext, "lowpass", 5000, 1);
const tailHPFilter = new Filter(tailContext, "highpass", 500, 1);
/* Initialize and connect the oscillator with the filters */
this.#tailOsc.init();
this.#tailOsc.connect(tailHPFilter.input);
this.#tailOscillator.init();
this.#tailOscillator.connect(tailHPFilter.input);
tailHPFilter.connect(tailLPFilter.input);
tailLPFilter.connect(tailContext.destination);
this.#tailOsc.attack = attack;
this.#tailOsc.decay = decay;
this.#tailOscillator.attack = attack;
this.#tailOscillator.decay = decay;
setTimeout(() => {
/* Set the buffer of the convolver node */
@@ -150,8 +173,16 @@ export class AudioUnitPipeline {
this.#convolverNode.buffer = buffer;
});
this.#tailOsc.on({ frequency: 500, velocity: 127 });
this.#tailOscillator.on({ frequency: 500, velocity: 127 });
//tailOsc.off(); // TODO In the original example I copied, this was turned off. No idea why but it seems to work correctly if left on. To investigate.
}, 20);
}
setPtt(ptt) {
this.#ptt = ptt;
}
setMaxDistance(maxDistance) {
this.#maxDistance = maxDistance;
}
}

View File

@@ -8,6 +8,8 @@ scramble calls and so on. Ideally, one may want to move this code to the backend
export class UnitSink extends AudioSink {
#unit: Unit;
#unitPipelines: { [key: string]: AudioUnitPipeline } = {};
#ptt: boolean = false;
#maxDistance: number = 1852;
constructor(unit: Unit) {
super();
@@ -33,6 +35,8 @@ export class UnitSink extends AudioSink {
.forEach((unitID) => {
if (unitID !== 0 && !(unitID in this.#unitPipelines)) {
this.#unitPipelines[unitID] = new AudioUnitPipeline(this.#unit, unitID, this.getInputNode());
this.#unitPipelines[unitID].setPtt(false);
this.#unitPipelines[unitID].setMaxDistance(this.#maxDistance);
}
});
@@ -42,4 +46,28 @@ export class UnitSink extends AudioSink {
}
});
}
setPtt(ptt) {
this.#ptt = ptt;
Object.values(this.#unitPipelines).forEach((pipeline) => {
pipeline.setPtt(ptt);
})
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
}
getPtt() {
return this.#ptt;
}
setMaxDistance(maxDistance) {
this.#maxDistance = maxDistance;
Object.values(this.#unitPipelines).forEach((pipeline) => {
pipeline.setMaxDistance(maxDistance);
})
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
}
getMaxDistance() {
return this.#maxDistance;
}
}