Skip to content

Commit 7dd39a8

Browse files
committed
started g.711 audio encoding work
client mic -> server speaker g.711 is fully working and sounds great server mic -> client speaker g.711 is still choppy for some reason
1 parent 4e35e70 commit 7dd39a8

File tree

3 files changed

+317
-33
lines changed

3 files changed

+317
-33
lines changed

console-client/client.js

+146-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
/*****************************************************
1+
/***********************************************************************************
22
Global Variables
3-
*****************************************************/
3+
***********************************************************************************/
44

55
// User Config Var
66
var config = {
@@ -39,9 +39,9 @@ var audio = {
3939

4040
testInput = null,
4141

42-
/*****************************************************
42+
/***********************************************************************************
4343
State variables
44-
*****************************************************/
44+
***********************************************************************************/
4545

4646
// Detected timezone
4747
timeZone = "";
@@ -54,9 +54,9 @@ var menuOpen = false;
5454
// server disconnect commanded
5555
var disconnecting = false;
5656

57-
/*****************************************************
57+
/***********************************************************************************
5858
Page Setup Functions
59-
*****************************************************/
59+
***********************************************************************************/
6060

6161
/**
6262
* Page load function. Starts timers, etc.
@@ -137,9 +137,9 @@ $(window).blur(function () {
137137
// Bind pageLoad function to document load
138138
$(document).ready(pageLoad());
139139

140-
/*****************************************************
140+
/***********************************************************************************
141141
Radio UI Functions
142-
*****************************************************/
142+
***********************************************************************************/
143143

144144
/**
145145
* Select a radio
@@ -343,9 +343,9 @@ function updateRadioControls() {
343343
}
344344
}
345345

346-
/*****************************************************
346+
/***********************************************************************************
347347
Radio Backend Functions
348-
*****************************************************/
348+
***********************************************************************************/
349349

350350
/**
351351
* Start radio PTT
@@ -466,9 +466,9 @@ function toggleMute(event, obj) {
466466
event.stopPropagation();
467467
}
468468

469-
/*****************************************************
469+
/***********************************************************************************
470470
Global UI Functions
471-
*****************************************************/
471+
***********************************************************************************/
472472

473473
/**
474474
* Toggles the state of the sidebar menu
@@ -534,9 +534,9 @@ function connectButton() {
534534
}
535535
}
536536

537-
/*****************************************************
537+
/***********************************************************************************
538538
Global Backend Functions
539-
*****************************************************/
539+
***********************************************************************************/
540540

541541
/**
542542
* Returns UTC time string in given format
@@ -641,9 +641,9 @@ function readConfig() {
641641
}
642642
}
643643

644-
/*****************************************************
644+
/***********************************************************************************
645645
Audio Handling Function
646-
*****************************************************/
646+
***********************************************************************************/
647647

648648
/**
649649
* Setup audio devices and context, etc
@@ -707,9 +707,17 @@ function startMicrophone(stream) {
707707

708708
/**
709709
* Buffer and send the mic data string to the server
710-
* @param {string} dataString data string
710+
* @param {Float32Array} data Float32 intput samples
711711
*/
712-
function sendMicData(dataString) {
712+
function sendMicData(data) {
713+
// Convert the Float32Array data to a Mu-Law Uint8Array
714+
var muLawData = encodeMuLaw(data);
715+
// Convert this mu-law data to a comma-separated string
716+
var dataString = "";
717+
muLawData.forEach(function(element) {
718+
dataString += (element.toString() + ",");
719+
});
720+
// only send stuff if we're actually PTTing into a radio
713721
if (pttActive && serverSocket && selectedRadio) {
714722
// Add data to buffer (concat keeps dimensions correct)
715723
audio.inputBuffer += dataString;
@@ -726,8 +734,10 @@ function sendMicData(dataString) {
726734
}
727735

728736
function getSpkrData(dataString) {
729-
// Convert the comma-separated string to a float32array
730-
var spkrData = Float32Array.from(dataString.split(","), parseFloat);
737+
// Convert the comma-separated string of mu-law samples to a Uint8Array
738+
var spkrMuLawData = Uint8Array.from(dataString.split(","));
739+
// Decode to Float32Array
740+
var spkrData = decodeMuLaw(spkrMuLawData);
731741
// Create a new buffer and source to play the received data
732742
var buffer = audio.context.createBuffer(1, spkrData.length, audio.context.sampleRate);
733743
buffer.copyToChannel(spkrData, 0);
@@ -737,9 +747,123 @@ function getSpkrData(dataString) {
737747
source.start(0);
738748
}
739749

740-
/*****************************************************
750+
/***********************************************************************************
751+
Audio Encoding/Decoding Functions
752+
753+
Borrowed from many places, including:
754+
- https://github.com/rochars/alawmulaw/blob/master/lib/mulaw.js
755+
756+
***********************************************************************************/
757+
758+
const muLawClip = 32635;
759+
760+
const muLawBias = 0x84;
761+
762+
const muLawEncodeTable = [
763+
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
764+
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
765+
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
766+
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
767+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
768+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
769+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
770+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
771+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
772+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
773+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
774+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
775+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
776+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
777+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
778+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
779+
];
780+
781+
const muLawDecodeTable = [0, 132, 396, 924, 1980, 4092, 8316, 16764];
782+
783+
/**
784+
* Convert a Float32 array of samples to mu-law encoded 8 bit samples
785+
* @param {Float32Array} samples input Float32 samples
786+
* @returns {Uint8Array} array of Mu-Law encoded samples
787+
*/
788+
function encodeMuLaw(samples) {
789+
// Convert input float samples to PCM
790+
var intSamples = floatToPcm(samples);
791+
// Create output sample array
792+
var output = new Uint8Array(samples.length);
793+
// Process each sample with the discrete mu-law encoding algorithm
794+
intSamples.forEach((sample, idx) => {
795+
// int vars (saves memory I guess?)
796+
let sign, exponent, mantissa;
797+
// get sign/magnitude
798+
sign = (sample >> 8) & 0x80;
799+
if (sign != 0) sample = -sample;
800+
// mu-law algorithm
801+
sample = sample + muLawBias;
802+
if (sample > muLawClip) sample = muLawClip;
803+
exponent = muLawEncodeTable[(sample>>7) & 0xFF];
804+
mantissa = (sample >> (exponent+3)) & 0x0F;
805+
output[idx] = ~(sign | (exponent << 4) | mantissa);
806+
});
807+
// return
808+
return output;
809+
}
810+
811+
/**
812+
* Convert mu-law encoded Uint8 samples to Float32 samples
813+
* @param {Uint8Array} samples input Mu-law Uint8 samples
814+
* @returns {Float32Array} output Float32 samples
815+
*/
816+
function decodeMuLaw(samples) {
817+
// Create output array
818+
var output = new Float32Array(samples.length);
819+
// Iterate over input array and decode mu-law
820+
samples.forEach((muLawSample, idx) => {
821+
// int vars
822+
let sign, exponent, mantissa;
823+
// do mu-law stuff
824+
muLawSample = ~muLawSample;
825+
sign = (muLawSample & 0x80);
826+
exponent = (muLawSample >> 4) & 0x07;
827+
mantissa = muLawSample & 0x0F;
828+
sample = muLawDecodeTable[exponent] + (mantissa << (exponent + 3));
829+
if (sign != 0) sample = -sample;
830+
output[idx] = sample;
831+
});
832+
// Return float samples
833+
return pcmToFloat(output);
834+
}
835+
836+
/**
837+
* Convert float32 samples to 16-bit PCM by clamping to [-1, 1],
838+
* multiplying by 32768, and taking the integer portion
839+
* @param {Float32Array} samples array of input Float32 samples
840+
* @returns {Int16Array} array of output signed PCM samples
841+
*/
842+
function floatToPcm(samples) {
843+
var output = new Int16Array(samples.length);
844+
samples.forEach((itm, idx) => {
845+
output[idx] = Math.floor(Math.max(-1, Math.min(itm, 1)) * 32768);
846+
});
847+
return output;
848+
}
849+
850+
/**
851+
* Convert Int16 PCM samples to Float 32 samples by dividing
852+
* 32768 and clamping to [-1, 1]
853+
* @param {Int16Array} samples array of input Int16 samples
854+
* @returns {Float32Array} array of output Float32 samples
855+
*/
856+
function pcmToFloat(samples) {
857+
var output = new Float32Array(samples.length);
858+
samples.forEach((itm, idx) => {
859+
output[idx] = Math.max(-1, Math.min(itm / 32768, 1));
860+
});
861+
return output;
862+
}
863+
864+
/***********************************************************************************
741865
Websocket Client Functions
742-
*****************************************************/
866+
***********************************************************************************/
743867

744868
/**
745869
* Connect to the server's websocket

console-client/microphone-processor.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,8 @@ registerProcessor('microphone-processor', class extends AudioWorkletProcessor {
1313
process(inputs, outputs, parameters) {
1414
// get first channel of first input (ignore the others, if any, since we only want mono mic audio)
1515
const inputData = inputs[0][0];
16-
// Convert the Float32Array to an array string, rounded to 4 decimal places
17-
var dataString = "";
18-
inputData.forEach(function(element) {
19-
dataString += (element.toFixed(4) + ",");
20-
});
2116
// Send this data back to the main script via the port
22-
this.port.postMessage(dataString);
17+
this.port.postMessage(inputData);
2318
return true;
2419
}
2520

0 commit comments

Comments
 (0)