diff --git a/frontend/react/src/audio/audiomanager.ts b/frontend/react/src/audio/audiomanager.ts index 95ef049e..7b05cb30 100644 --- a/frontend/react/src/audio/audiomanager.ts +++ b/frontend/react/src/audio/audiomanager.ts @@ -149,6 +149,12 @@ export class AudioManager { }); document.dispatchEvent(new CustomEvent("audioSinksUpdated")); } + + removeSource(source) { + source.disconnect(); + this.#sources = this.#sources.filter((v) => v != source); + document.dispatchEvent(new CustomEvent("audioSourcesUpdated")); + } getSources() { return this.#sources; diff --git a/frontend/react/src/audio/audiopacket.ts b/frontend/react/src/audio/audiopacket.ts index d4688c1d..398d1004 100644 --- a/frontend/react/src/audio/audiopacket.ts +++ b/frontend/react/src/audio/audiopacket.ts @@ -23,7 +23,7 @@ var packetID = 0; export class AudioPacket { #packet: Uint8Array; - constructor(data, settings, guid) { + constructor(data, settings, guid, lat?, lng?, alt?) { let header: number[] = [0, 0, 0, 0, 0, 0]; let encFrequency: number[] = [...doubleToByteArray(settings.frequency)]; @@ -48,6 +48,10 @@ export class AudioPacket { [...Buffer.from(guid, "utf-8")] ); + if (lat !== undefined && lng !== undefined && alt !== undefined) { + packet.concat([...doubleToByteArray(lat)], [...doubleToByteArray(lng)], [...doubleToByteArray(alt)]); + } + let encPacketLen = getBytes(packet.length, 2); packet[0] = encPacketLen[0]; packet[1] = encPacketLen[1]; diff --git a/frontend/react/src/audio/audiosource.ts b/frontend/react/src/audio/audiosource.ts index 2fbdf47f..12256726 100644 --- a/frontend/react/src/audio/audiosource.ts +++ b/frontend/react/src/audio/audiosource.ts @@ -1,9 +1,18 @@ +import { getApp } from "../olympusapp"; import { AudioSink } from "./audiosink"; +import { WebAudioPeakMeter } from "web-audio-peak-meter"; export abstract class AudioSource { #connectedTo: AudioSink[] = []; #name = ""; - #playing = false; + #meter: WebAudioPeakMeter; + #volume: number = 1.0; + #gainNode: GainNode; + + constructor() { + this.#gainNode = getApp().getAudioManager().getAudioContext().createGain(); + this.#meter = new WebAudioPeakMeter(this.#gainNode, document.createElement('div')); + } connect(sink: AudioSink) { this.getNode().connect(sink.getNode()); @@ -18,7 +27,7 @@ export abstract class AudioSource { } else { this.getNode().disconnect(); } - + document.dispatchEvent(new CustomEvent("audioSourcesUpdated")); } @@ -34,6 +43,23 @@ export abstract class AudioSource { return this.#connectedTo; } + setVolume(volume) { + this.#volume = volume; + this.#gainNode.gain.exponentialRampToValueAtTime(volume, getApp().getAudioManager().getAudioContext().currentTime + 0.02); + document.dispatchEvent(new CustomEvent("audioSourcesUpdated")); + } + + getVolume() { + return this.#volume; + } + + getMeter() { + return this.#meter; + } + + getNode() { + return this.#gainNode; + } + abstract play(): void; - abstract getNode(): AudioNode; } diff --git a/frontend/react/src/audio/filesource.ts b/frontend/react/src/audio/filesource.ts index f844144a..7bdf006a 100644 --- a/frontend/react/src/audio/filesource.ts +++ b/frontend/react/src/audio/filesource.ts @@ -3,7 +3,6 @@ import { getApp } from "../olympusapp"; import {WebAudioPeakMeter} from 'web-audio-peak-meter'; export class FileSource extends AudioSource { - #gainNode: GainNode; #file: File | null = null; #source: AudioBufferSourceNode; #duration: number = 0; @@ -14,8 +13,6 @@ export class FileSource extends AudioSource { #audioBuffer: AudioBuffer; #restartTimeout: any; #looping = false; - #meter: WebAudioPeakMeter; - #volume: number = 1.0; constructor(file) { super(); @@ -23,11 +20,10 @@ export class FileSource extends AudioSource { this.setName(this.#file?.name ?? "N/A"); - this.#gainNode = getApp().getAudioManager().getAudioContext().createGain(); - if (!this.#file) { return; } + var reader = new FileReader(); reader.onload = (e) => { var contents = e.target?.result; @@ -44,18 +40,12 @@ export class FileSource extends AudioSource { reader.readAsArrayBuffer(this.#file); } - getNode() { - return this.#gainNode; - } - play() { this.#source = getApp().getAudioManager().getAudioContext().createBufferSource(); this.#source.buffer = this.#audioBuffer; - this.#source.connect(this.#gainNode); + this.#source.connect(this.getNode()); this.#source.loop = this.#looping; - this.#meter = new WebAudioPeakMeter(this.#gainNode, document.createElement('div')); - this.#source.start(0, this.#currentPosition); this.#playing = true; const now = Date.now() / 1000; @@ -79,6 +69,7 @@ export class FileSource extends AudioSource { stop() { this.#source.stop(); + this.#source.disconnect(); this.#playing = false; const now = Date.now() / 1000; @@ -88,10 +79,6 @@ export class FileSource extends AudioSource { document.dispatchEvent(new CustomEvent("audioSourcesUpdated")); } - setGain(gain) { - this.#gainNode.gain.setValueAtTime(gain, getApp().getAudioManager().getAudioContext().currentTime); - } - getPlaying() { return this.#playing; } @@ -123,17 +110,4 @@ export class FileSource extends AudioSource { getLooping() { return this.#looping; } - - getMeter() { - return this.#meter; - } - - setVolume(volume) { - this.#volume = volume; - this.#gainNode.gain.exponentialRampToValueAtTime(volume, getApp().getAudioManager().getAudioContext().currentTime + 0.02); - } - - getVolume() { - return this.#volume; - } } diff --git a/frontend/react/src/audio/microphonesource.ts b/frontend/react/src/audio/microphonesource.ts index 209ffcd2..05610380 100644 --- a/frontend/react/src/audio/microphonesource.ts +++ b/frontend/react/src/audio/microphonesource.ts @@ -2,7 +2,7 @@ import { getApp } from "../olympusapp"; import { AudioSource } from "./audiosource"; export class MicrophoneSource extends AudioSource { - #node: AudioNode; + #node: MediaStreamAudioSourceNode; constructor() { super(); @@ -14,14 +14,12 @@ export class MicrophoneSource extends AudioSource { const microphone = await navigator.mediaDevices.getUserMedia({ audio: true }); if (getApp().getAudioManager().getAudioContext()) { this.#node = getApp().getAudioManager().getAudioContext().createMediaStreamSource(microphone); + + this.#node.connect(this.getNode()); } } - getNode() { - return this.#node; - } - play() { - // TODO, now is always on + document.dispatchEvent(new CustomEvent("audioSourcesUpdated")); } } diff --git a/frontend/react/src/audio/unitsink.ts b/frontend/react/src/audio/unitsink.ts index 9390f9c0..7992aed4 100644 --- a/frontend/react/src/audio/unitsink.ts +++ b/frontend/react/src/audio/unitsink.ts @@ -27,7 +27,10 @@ export class UnitSink extends AudioSink { frequency: 243000000, modulation: 255, // HOPEFULLY this will never be used by SRS, indicates "loudspeaker" mode }, - getApp().getAudioManager().getGuid() + getApp().getAudioManager().getGuid(), + this.#unit.getPosition().lat, + this.#unit.getPosition().lng, + this.#unit.getPosition().alt ); getApp().getAudioManager().send(packet.getArray()); } diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts index eaf90099..43e3b7f9 100644 --- a/frontend/react/src/olympusapp.ts +++ b/frontend/react/src/olympusapp.ts @@ -29,7 +29,7 @@ import { AudioManager } from "./audio/audiomanager"; export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}"; export var IP = window.location.toString(); -export var connectedToServer = true; // Temporary +export var connectedToServer = true; // TODO Temporary export class OlympusApp { /* Global data */ diff --git a/frontend/react/src/ui/panels/audiomenu.tsx b/frontend/react/src/ui/panels/audiomenu.tsx index dccd3e09..834de93c 100644 --- a/frontend/react/src/ui/panels/audiomenu.tsx +++ b/frontend/react/src/ui/panels/audiomenu.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import { Menu } from "./components/menu"; import { getApp } from "../../olympusapp"; import { FaQuestionCircle } from "react-icons/fa"; -import { AudioSourcePanel } from "./components/audiosourcepanel"; +import { AudioSourcePanel } from "./components/sourcepanel"; import { AudioSource } from "../../audio/audiosource"; export function AudioMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) { diff --git a/frontend/react/src/ui/panels/components/audiosourcepanel.tsx b/frontend/react/src/ui/panels/components/audiosourcepanel.tsx deleted file mode 100644 index 13bab676..00000000 --- a/frontend/react/src/ui/panels/components/audiosourcepanel.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { OlStateButton } from "../../components/olstatebutton"; -import { faPause, faPlay, faRepeat, faStop } from "@fortawesome/free-solid-svg-icons"; -import { getApp } from "../../../olympusapp"; -import { AudioSource } from "../../../audio/audiosource"; -import { FaTrash, FaVolumeHigh } from "react-icons/fa6"; -import { OlRangeSlider } from "../../components/olrangeslider"; -import { FaUnlink } from "react-icons/fa"; -import { OlDropdown, OlDropdownItem } from "../../components/oldropdown"; -import { FileSource } from "../../../audio/filesource"; - -export function AudioSourcePanel(props: { source: AudioSource }) { - const [meterLevel, setMeterLevel] = useState(0); - - useEffect(() => { - setInterval(() => { - // TODO apply to all sources - if (props.source instanceof FileSource) { - setMeterLevel(props.source.getMeter().getPeaks().current[0]); - } - }, 50); - }, []); - - return ( -