Separate microphone

This commit is contained in:
Lewis Moten
2024-05-13 02:09:13 -04:00
parent e8634b98d4
commit f548d9572b
3 changed files with 159 additions and 56 deletions

View File

@@ -8,15 +8,7 @@ class CommunicationsPanel extends BasePanel {
{text: 'Analyzer', id: 'send-via-analyzer', eventName: 'sendAnalyzerChange'}, {text: 'Analyzer', id: 'send-via-analyzer', eventName: 'sendAnalyzerChange'},
{text: 'Speakers', id: 'send-via-speaker', eventName: 'sendSpeakersChange'} {text: 'Speakers', id: 'send-via-speaker', eventName: 'sendSpeakersChange'}
]); ]);
this.addSection('Receive');
this.addCheckboxes('receive-via', [
{text: 'Listening', id: 'is-listening-checkbox', eventName: 'listeningChange'}
]);
} }
isListeningChecked = () => {
return this.getCheckedById('is-listening-checkbox');
}
setListening = checked => this.setCheckedById('is-listening-checkbox', checked);
setSendSpeakers = checked => this.setCheckedById('send-via-speaker', checked); setSendSpeakers = checked => this.setCheckedById('send-via-speaker', checked);
setSendAnalyzer = checked => this.setCheckedById('send-via-analyzer', checked); setSendAnalyzer = checked => this.setCheckedById('send-via-analyzer', checked);
} }

150
Panels/MicrophonePanel.js Normal file
View File

@@ -0,0 +1,150 @@
import BasePanel from './BasePanel';
const media = {
audio: {
mandatory: {
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false,
suppressLocalAudioPlayback: false,
voiceIsolation: false
},
optional: []
}
};
class MicrophonePanel extends BasePanel {
constructor() {
super('Microphone');
this.addCheckboxes('receive-via', [
{text: 'Listen', id: 'listen', eventName: 'listenChange'}
]);
this.addCanvas('canvas', 100, 25);
this.addEventListener('listenChange', (e) => {
if(e.checked) {
this.error = undefined;
navigator.mediaDevices.getUserMedia(media)
.then(stream => {
this.stream = stream;
this.connectStream();
this.dispatcher.emit('on', stream);
})
.catch(error => {
this.error = error;
console.log(error);
this.setListening(false);
this.disconnectStream();
this.stopSampling();
})
} else {
if(this.stream) {
this.error = undefined;
this.disconnectStream();
this.dispatcher.emit('off');
}
this.stopSampling();
}
})
};
setAnalyser = analyser => {
if(analyser) {
this.analyser = analyser;
this.connectStream();
} else {
this.disconnectStream();
this.analyser = analyser;
}
}
setAudioContext = audioContext => {
if(audioContext) {
this.audioContext = audioContext;
this.connectStream();
} else {
this.disconnectStream();
this.audioContext = audioContext;
}
}
connectStream = () => {
if(this.stream) {
if(this.audioContext) {
if(!this.streamNode) {
this.streamNode = this.audioContext.createMediaStreamSource(this.stream);
}
if(this.analyser) {
this.streamNode.connect(this.analyser);
}
if(this.getListening()) {
this.startSampling();
}
}
}
}
disconnectStream = () => {
if(this.streamNode) {
if(this.analyser) {
this.streamNode.disconnect(this.analyser);
this.streamNode = undefined;
}
}
if(this.stream) {
this.stream.getTracks().forEach(track => track.stop());
this.stream = undefined;
}
this.stopSampling();
}
isSampling = () => !!this.samplingId;
startSampling = () => {
if(this.isSampling()) return;
// nothing to analyse
if(!this.stream) return;
// nothing to analyse with
if(!this.analyser) return;
this.samplingId = window.requestAnimationFrame(this.drawSpectrumAnalyzer);
}
stopSampling = () => {
if(!this.isSampling()) return;
window.cancelAnimationFrame(this.samplingId);
this.samplingId = undefined;
}
getListening = () => this.getCheckedById('listen');
setListening = checked => {
if(this.getListening() !== checked) {
this.setCheckedById('listen', checked)
this.dispatcher.emit('listenChange', {checked});
}
};
getStream = () => this.stream;
drawSpectrumAnalyzer = () => {
const canvas = this.getElement('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
let frequencyResolution = this.audioContext.sampleRate / this.analyser.fftSize;
let nyquistFrequency = this.audioContext.sampleRate / 2;
let buffer = new Uint8Array(this.analyser.frequencyBinCount);
this.analyser.getByteFrequencyData(buffer);
ctx.clearRect(0, 0, width, height);
let barWidth = (1/buffer.length) * width;
for(let i = 0; i < buffer.length; i++) {
let x = i * barWidth;
let y = (1 - (buffer[i] / 255)) * height;
const hue = Math.floor(((i * frequencyResolution) / nyquistFrequency) * 360)
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
ctx.fillRect(x, y, barWidth, height-y);
}
this.samplingId = window.requestAnimationFrame(this.drawSpectrumAnalyzer);
}
}
export default MicrophonePanel;

View File

@@ -29,6 +29,7 @@ import {
textToBits, textToBits,
textToBytes, textToBytes,
} from './converters'; } from './converters';
import MicrophonePanel from "./Panels/MicrophonePanel";
var audioContext; var audioContext;
var microphoneStream; var microphoneStream;
var microphoneNode; var microphoneNode;
@@ -68,6 +69,7 @@ const availableFskPairsPanel = new AvailableFskPairsPanel();
const frequencyGraphPanel = new FrequencyGraphPanel(); const frequencyGraphPanel = new FrequencyGraphPanel();
const graphConfigurationPanel = new GraphConfigurationPanel(); const graphConfigurationPanel = new GraphConfigurationPanel();
const speedPanel = new SpeedPanel(); const speedPanel = new SpeedPanel();
const microphonePanel = new MicrophonePanel();
function handleWindowLoad() { function handleWindowLoad() {
const panelContainer = document.getElementById('panel-container'); const panelContainer = document.getElementById('panel-container');
@@ -82,9 +84,11 @@ function handleWindowLoad() {
panelContainer.prepend(bitsSentPanel.getDomElement()); panelContainer.prepend(bitsSentPanel.getDomElement());
panelContainer.prepend(messagePanel.getDomElement()); panelContainer.prepend(messagePanel.getDomElement());
panelContainer.prepend(communicationsPanel.getDomElement()); panelContainer.prepend(communicationsPanel.getDomElement());
panelContainer.prepend(microphonePanel.getDomElement());
// Initialize Values // Initialize Values
communicationsPanel.setListening(false); microphonePanel.setListening(false);
communicationsPanel.setSendSpeakers(false); communicationsPanel.setSendSpeakers(false);
communicationsPanel.setSendAnalyzer(true); communicationsPanel.setSendAnalyzer(true);
@@ -135,7 +139,6 @@ function handleWindowLoad() {
// Events // Events
communicationsPanel.addEventListener('listeningChange', handleChangeListening);
communicationsPanel.addEventListener('sendSpeakersChange', handleChangeSendSpeakers); communicationsPanel.addEventListener('sendSpeakersChange', handleChangeSendSpeakers);
communicationsPanel.addEventListener('sendAnalyzerChange', handleChangeSendAnalyzer); communicationsPanel.addEventListener('sendAnalyzerChange', handleChangeSendAnalyzer);
@@ -554,7 +557,7 @@ function stopGraph() {
} }
function resumeGraph() { function resumeGraph() {
if(communicationsPanel.isListeningChecked()) { if(microphonePanel.getListening()) {
if(PAUSE) { if(PAUSE) {
PAUSE = false; PAUSE = false;
AudioReceiver.start(); AudioReceiver.start();
@@ -830,6 +833,7 @@ function getAudioContext() {
audioContext = new (window.AudioContext || webkitAudioContext)(); audioContext = new (window.AudioContext || webkitAudioContext)();
frequencyPanel.setSampleRate(audioContext.sampleRate); frequencyPanel.setSampleRate(audioContext.sampleRate);
availableFskPairsPanel.setSampleRate(audioContext.sampleRate); availableFskPairsPanel.setSampleRate(audioContext.sampleRate);
microphonePanel.setAudioContext(audioContext);
} }
if(audioContext.state === 'suspended') { if(audioContext.state === 'suspended') {
audioContext.resume(); audioContext.resume();
@@ -847,6 +851,8 @@ function getAnalyser() {
if(analyser) return analyser; if(analyser) return analyser;
analyser = audioContext.createAnalyser(); analyser = audioContext.createAnalyser();
frequencyGraphPanel.setAnalyser(analyser); frequencyGraphPanel.setAnalyser(analyser);
microphonePanel.setAnalyser(analyser);
analyser.smoothingTimeConstant = signalPanel.getSmoothingTimeConstant(); analyser.smoothingTimeConstant = signalPanel.getSmoothingTimeConstant();
analyser.fftSize = frequencyPanel.getFftSize(); analyser.fftSize = frequencyPanel.getFftSize();
return analyser; return analyser;
@@ -859,51 +865,6 @@ function handleChangeSendSpeakers({checked}) {
SEND_VIA_SPEAKER = checked; SEND_VIA_SPEAKER = checked;
configurationChanged(); configurationChanged();
} }
function handleChangeListening({checked}) {
stopGraph();
var audioContext = getAudioContext();
function handleMicrophoneOn(stream) {
microphoneStream = stream;
microphoneNode = audioContext.createMediaStreamSource(stream);
analyser = getAnalyser();
microphoneNode.connect(analyser);
resumeGraph();
}
function handleMicrophoneError(error) {
console.error('Microphone Error', error);
}
if(checked) {
navigator.mediaDevices
.getUserMedia({
audio: {
mandatory: {
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false,
suppressLocalAudioPlayback: false,
voiceIsolation: false
},
optional: []
}
})
.then(handleMicrophoneOn)
.catch(handleMicrophoneError)
} else {
if(microphoneStream) {
microphoneStream.getTracks().forEach(track => track.stop());
microphoneStream = undefined;
}
if(analyser && microphoneNode) {
try {
analyser.disconnect(microphoneNode);
} catch(e) {
}
microphoneNode = undefined;
analyser = undefined;
}
}
}
function drawChannelData() { function drawChannelData() {
// Do/did we have a stream? // Do/did we have a stream?