Skip to content

Commit 8ad2851

Browse files
committed
implemented resampling, fixed RX audio (mostly)
implemented resampling so that the audio over the websocket is always 16khz samplerate. Moved audio device & stream handling into radio objects, starting to get actual audio working instead of dummy devices
1 parent 366353e commit 8ad2851

File tree

7 files changed

+368
-252
lines changed

7 files changed

+368
-252
lines changed

console-client/client.js

+15-12
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ var serverSocket = null;
2121
var audio = {
2222
// Audio context
2323
context: null,
24-
// length of buffers in number of 128-sample blocks (these must be the same as the python)
25-
spkrBufferSize: 64,
26-
micBufferSize: 16,
27-
// desired mic sample rate to send to server
28-
micSamplerateTarget: 16000,
24+
// length of buffers in s (these must match the python script)
25+
spkrBufferDur: 0.1,
26+
micBufferDur: 0.1,
27+
// audio transfer sample rate
28+
transferSamplerate: 16000,
2929
// Input device, buffer, resampler, and processor
3030
input: null,
3131
inputStream: null,
@@ -684,9 +684,6 @@ function startAudioDevices() {
684684
function startMicrophone(stream) {
685685
console.log("Starting microphone");
686686

687-
// Send samplerate info of context to server
688-
serverSocket.send("micRate:" + String(audio.context.sampleRate));
689-
690687
// Create an empty buffer
691688
audio.inputBuffer = "";
692689

@@ -704,6 +701,9 @@ function startMicrophone(stream) {
704701
// connect everything together
705702
audio.input.connect(audio.inputProcessor);
706703
});
704+
705+
// Tell the server we're ready to do audio things
706+
serverSocket.send("!startAudio");
707707
}
708708

709709
/**
@@ -724,7 +724,7 @@ function sendMicData(data) {
724724
audio.inputBuffer += dataString;
725725
audio.inputBufferSize += 1;
726726
// Push data if buffer is full
727-
if (audio.inputBufferSize >= audio.micBufferSize) {
727+
if (audio.inputBufferSize >= audio.micBufferDur * audio.transferSamplerate) {
728728
// Send string
729729
serverSocket.send("micAudio:" + audio.inputBuffer);
730730
// Clear buffer
@@ -736,12 +736,15 @@ function sendMicData(data) {
736736

737737
function getSpkrData(dataString) {
738738
// Convert the comma-separated string of mu-law samples to a Uint8Array
739-
const spkrMuLawData = Uint8Array.from(dataString.split(","));
739+
const spkrMuLawData = Uint8Array.from(dataString.split(','));
740740
// Decode to Float32Array
741741
const spkrData = decodeMuLaw(spkrMuLawData);
742+
// Resample to client samplerate
743+
const resampled = waveResampler.resample(spkrData, audio.transferSamplerate, audio.context.sampleRate, {method: "point"});
744+
const resampledFloat32 = Float32Array.from(resampled);
742745
// Create a new buffer and source to play the received data
743-
const buffer = audio.context.createBuffer(1, spkrData.length, audio.context.sampleRate);
744-
buffer.copyToChannel(spkrData, 0);
746+
const buffer = audio.context.createBuffer(1, resampledFloat32.length, audio.context.sampleRate);
747+
buffer.copyToChannel(resampledFloat32, 0);
745748
var source = audio.context.createBufferSource();
746749
source.buffer = buffer;
747750
source.connect(audio.context.destination);

console-client/index.html

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
<!-- js-cookie -->
2323
<script type="text/javascript" src="js.cookie.min.js"></script>
24+
25+
<!-- audio resampler -->
26+
<script type="text/javascript" src="wave-resampler.js"></script>
2427
</head>
2528
<body>
2629
<!-- Top Nav Bar -->

console-client/wave-resampler.js

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

interface/xtl.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ def listen(self):
158158
# Read message
159159
msg = self.bus.read(self.bus.ser.in_waiting)
160160

161+
if len(msg) < 5:
162+
self.logger.logWarn("Skipping invalid message with length {}".format(len(msg)))
163+
continue
164+
161165
# Handle SBEP first
162166
if self.inSBEP:
163167
# reset
@@ -214,35 +218,35 @@ def toggleMonitor(self):
214218
Monitor button
215219
"""
216220
# Press nuis button
217-
self.pressButton(self.O5Address.button_map["btn_key_1"], 0.06)
221+
self.pressButton(self.O5Address.button_map["btn_key_1"], 0.1)
218222

219223
def nuisanceDelete(self):
220224
"""
221225
Nuisance delete button
222226
"""
223227
# Press nuis button
224-
self.pressButton(self.O5Address.button_map["btn_key_2"], 0.06)
228+
self.pressButton(self.O5Address.button_map["btn_key_2"], 0.1)
225229

226230
def togglePower(self):
227231
"""
228232
Power button
229233
"""
230234
# Press nuis button
231-
self.pressButton(self.O5Address.button_map["btn_key_3"], 0.06)
235+
self.pressButton(self.O5Address.button_map["btn_key_3"], 0.1)
232236

233237
def toggleScan(self):
234238
"""
235239
Change state of scan by sending softkey button to toggle
236240
"""
237241
# Press scan button
238-
self.pressButton(self.O5Address.button_map['btn_key_4'],0.06)
242+
self.pressButton(self.O5Address.button_map['btn_key_4'],0.1)
239243

240244
def toggleDirect(self):
241245
"""
242246
DIR button
243247
"""
244248
# Press scan button
245-
self.pressButton(self.O5Address.button_map['btn_key_5'],0.06)
249+
self.pressButton(self.O5Address.button_map['btn_key_5'],0.1)
246250

247251
def processSBEP(self, msg):
248252
"""

mulaw.py

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import numpy as np
2+
3+
class MuLaw:
4+
5+
bias = 0x84
6+
clip = 32635
7+
8+
encodeTable = [
9+
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
10+
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
11+
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
12+
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
13+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
14+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
15+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
16+
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
17+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
18+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
19+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
20+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
21+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
22+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
23+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
24+
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
25+
]
26+
27+
decodeTable = [0,132,396,924,1980,4092,8316,16764]
28+
29+
def decode(muLawSamples):
30+
"""
31+
Decode 8-bit mu-law samples to 32-bit floats
32+
33+
Args:
34+
samples (Uint8[]): array of Uint8 samples
35+
36+
Returns:
37+
np.float32[]: array of float samples
38+
"""
39+
40+
# create output int16 numpy array
41+
output = np.zeros(len(muLawSamples), dtype=np.int16)
42+
43+
# iterate through each sample and decode from Uint8 to Int16
44+
for idx, muLawSample in enumerate(muLawSamples):
45+
# make sure we have a python uint8
46+
muLawSample = muLawSample.astype(np.uint8).item()
47+
# Do the decoding
48+
muLawSample = ~muLawSample
49+
sign = (muLawSample & 0x80)
50+
exponent = (muLawSample >> 4) & 0x07
51+
mantissa = muLawSample & 0x0f
52+
sample = MuLaw.decodeTable[exponent] + (mantissa << (exponent + 3))
53+
if (sign != 0): sample = -sample
54+
output[idx] = sample
55+
56+
# convert to float32 from int16 and return
57+
return output.astype(np.float32, order='C') / 32768.0
58+
59+
def encode(samples):
60+
"""
61+
Encode Float32 samples to 8-bit mu-law samples
62+
63+
Args:
64+
samples (np.float32[]): float32 array of samples
65+
66+
Returns:
67+
np.uint8[]: array of 8-bit mu-law samples
68+
"""
69+
70+
# create output uint8 array
71+
output = np.zeros(len(samples), dtype=np.uint8)
72+
73+
# convert float32 to int16
74+
int16samples = np.zeros(len(samples), dtype=np.int16)
75+
for idx, sample in enumerate(samples):
76+
i = sample * 32768
77+
if i > 32767: i = 32767
78+
if i < -32767: i = -32767
79+
int16samples[idx] = i
80+
81+
# iterate through samples and encode
82+
for idx, sample in enumerate(int16samples):
83+
# Convert numpy int16 to python int16 so we can do bitwise stuff
84+
sample = sample.item()
85+
86+
# Do the encoding stuff
87+
sign = (sample >> 8) & 0x80
88+
if (sign != 0): sample = -sample
89+
sample = sample + MuLaw.bias
90+
if (sample > MuLaw.clip): sample = MuLaw.clip
91+
exponent = MuLaw.encodeTable[(sample >> 7) & 0xff]
92+
mantissa = (sample >> (exponent + 3)) & 0x0f
93+
muLawSample = ~(sign | (exponent << 4) | mantissa)
94+
95+
output[idx] = muLawSample
96+
97+
return output
98+

0 commit comments

Comments
 (0)