1
- /*****************************************************
1
+ /***********************************************************************************
2
2
Global Variables
3
- *****************************************************/
3
+ *********************************************************************************** /
4
4
5
5
// User Config Var
6
6
var config = {
@@ -39,9 +39,9 @@ var audio = {
39
39
40
40
testInput = null ,
41
41
42
- /*****************************************************
42
+ /***********************************************************************************
43
43
State variables
44
- *****************************************************/
44
+ *********************************************************************************** /
45
45
46
46
// Detected timezone
47
47
timeZone = "" ;
@@ -54,9 +54,9 @@ var menuOpen = false;
54
54
// server disconnect commanded
55
55
var disconnecting = false ;
56
56
57
- /*****************************************************
57
+ /***********************************************************************************
58
58
Page Setup Functions
59
- *****************************************************/
59
+ *********************************************************************************** /
60
60
61
61
/**
62
62
* Page load function. Starts timers, etc.
@@ -137,9 +137,9 @@ $(window).blur(function () {
137
137
// Bind pageLoad function to document load
138
138
$ ( document ) . ready ( pageLoad ( ) ) ;
139
139
140
- /*****************************************************
140
+ /***********************************************************************************
141
141
Radio UI Functions
142
- *****************************************************/
142
+ *********************************************************************************** /
143
143
144
144
/**
145
145
* Select a radio
@@ -343,9 +343,9 @@ function updateRadioControls() {
343
343
}
344
344
}
345
345
346
- /*****************************************************
346
+ /***********************************************************************************
347
347
Radio Backend Functions
348
- *****************************************************/
348
+ *********************************************************************************** /
349
349
350
350
/**
351
351
* Start radio PTT
@@ -466,9 +466,9 @@ function toggleMute(event, obj) {
466
466
event . stopPropagation ( ) ;
467
467
}
468
468
469
- /*****************************************************
469
+ /***********************************************************************************
470
470
Global UI Functions
471
- *****************************************************/
471
+ *********************************************************************************** /
472
472
473
473
/**
474
474
* Toggles the state of the sidebar menu
@@ -534,9 +534,9 @@ function connectButton() {
534
534
}
535
535
}
536
536
537
- /*****************************************************
537
+ /***********************************************************************************
538
538
Global Backend Functions
539
- *****************************************************/
539
+ *********************************************************************************** /
540
540
541
541
/**
542
542
* Returns UTC time string in given format
@@ -641,9 +641,9 @@ function readConfig() {
641
641
}
642
642
}
643
643
644
- /*****************************************************
644
+ /***********************************************************************************
645
645
Audio Handling Function
646
- *****************************************************/
646
+ *********************************************************************************** /
647
647
648
648
/**
649
649
* Setup audio devices and context, etc
@@ -707,9 +707,17 @@ function startMicrophone(stream) {
707
707
708
708
/**
709
709
* Buffer and send the mic data string to the server
710
- * @param {string } dataString data string
710
+ * @param {Float32Array } data Float32 intput samples
711
711
*/
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
713
721
if ( pttActive && serverSocket && selectedRadio ) {
714
722
// Add data to buffer (concat keeps dimensions correct)
715
723
audio . inputBuffer += dataString ;
@@ -726,8 +734,10 @@ function sendMicData(dataString) {
726
734
}
727
735
728
736
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 ) ;
731
741
// Create a new buffer and source to play the received data
732
742
var buffer = audio . context . createBuffer ( 1 , spkrData . length , audio . context . sampleRate ) ;
733
743
buffer . copyToChannel ( spkrData , 0 ) ;
@@ -737,9 +747,123 @@ function getSpkrData(dataString) {
737
747
source . start ( 0 ) ;
738
748
}
739
749
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
+ /***********************************************************************************
741
865
Websocket Client Functions
742
- *****************************************************/
866
+ *********************************************************************************** /
743
867
744
868
/**
745
869
* Connect to the server's websocket
0 commit comments