Files
2022-09-12 22:07:39 +10:00

212 lines
5.3 KiB
Python

# Run with "ipython --matplotlib=qt receiver.py <file>.wav"
#
from __future__ import print_function
import sys
import numpy as np
from scipy import signal
from scipy.io.wavfile import read
from scipy.signal import butter, lfilter
from math import log10, floor
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
FRAME_TIME = 0.04 # Frame time in seconds
TONES = [1124,
1197,
1275,
1358,
1446,
1540,
1640,
1747,
1860,
1981,
2110]
ALPHABET = ['1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'0',
'E']
reffreq = {}
reffreq['1'] = 1124
reffreq['2'] = 1197
reffreq['3'] = 1275
reffreq['4'] = 1358
reffreq['5'] = 1446
reffreq['6'] = 1540
reffreq['7'] = 1640
reffreq['8'] = 1747
reffreq['9'] = 1860
reffreq['0'] = 1981
reffreq['e'] = 2110
FILTER_LEN = 1000 # Samples
# Shamelessly lifted from
# https://scipy.github.io/old-wiki/pages/Cookbook/ButterworthBandpass
def butter_bandpass(lowcut, highcut, fs, order=5):
nyq = 0.5 * fs
low = lowcut / nyq
high = highcut / nyq
b, a = butter(order, [low, high], btype='band')
return b, a
def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
b, a = butter_bandpass(lowcut, highcut, fs, order=order)
y = lfilter(b, a, data)
return y
# Tone synthesis
def note(freq, cycles, amp=32767.0, rate=44100):
len = cycles * (1.0/rate)
t = np.linspace(0, len, int(len * rate))
if freq == 0:
data = np.zeros(int(len * rate))
else:
data = np.sin(2 * np.pi * freq * t) * amp
return data.astype(int)
def checktrain(train):
# Look for 5 definitive tones in a row
for idx,[tone,corr] in enumerate(train):
#print(idx,tone,corr)
ener = 0
val = []
if(idx>4):
for i in range(idx-5,idx):
tone,corr = train[i]
val.append(tone)
ener += corr
if(ener > 54):
print(ener," value:",val)
return 0
# analyze wav file by chunks
def receiver(file_name):
try:
sig_rate, sig_noise = read(file_name)
except Exception:
print('Error opening {}'.format(file_name))
return
print('file: ', file_name, ' rate: ', sig_rate, ' len: ', len(sig_noise))
if sig_rate == 44100:
decimate = 4 # rate = 11025, Fmax = 5512.5 Hz
elif sig_rate == 48000:
decimate = 5 # rate = 9600, Fmax = 4800 Hz
elif sig_rate == 22050:
decimate = 2 # rate = 11025, Fmax = 5512.5 Hz
elif sig_rate == 11025:
decimate = 1 # rate = 11025, Fmax = 5512.5 Hz
else:
print('Sample rate {} not supported.'.format(sig_rate))
return
if decimate > 1:
sig_noise = signal.decimate(sig_noise, decimate)
sig_rate = sig_rate / decimate
print('length after decimation: ', len(sig_noise))
frame_len = int(sig_rate * FRAME_TIME)
frames = int(floor((len(sig_noise) / frame_len) + 1))
sig_noise = butter_bandpass_filter(sig_noise,
1000,
2200,
sig_rate,
order=8)
template = []
for tone in range(0, len(TONES)):
template.append(note(TONES[tone], frame_len, rate=sig_rate))
# See http://stackoverflow.com/questions/23507217/
# python-plotting-2d-data-on-to-3d-axes
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
y = np.arange(len(TONES))
print(' Index 1 2 3 4 5 6 7', end='')
print(' 8 9 0 E Avg')
x = range(0, frames)
X, Y = np.meshgrid(y, x)
Z = np.zeros((len(x), len(y)))
hist = []
for frame in range(0, frames):
beg = frame * frame_len
end = (frame+1) * frame_len
corr = np.zeros(len(TONES))
for tone in range(0, len(TONES)):
corr[tone] = log10(np.abs(signal.correlate(sig_noise[beg:end],template[tone],mode='same')).sum())
Z[frame, tone] = corr[tone]
max1 = 0.0
# Find most likely tone in set
for tone in range(0, len(TONES)):
if corr[tone] > max1:
max1 = corr[tone]
max1idx = tone
hist.append([max1idx,max1])
print('{0:6d}: '.format(frame), end='')
avg = np.mean(corr)
for tone in range(0, len(TONES)):
if tone == max1idx:
print('[{0:2.2f}]'.format(corr[tone]), end='')
else:
if corr[tone] > 1:
print(' {0:2.2f} '.format(corr[tone]), end='')
else:
print(' . ', end='')
print(' {0:2.2f}'.format(avg))
checktrain(hist)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1000, color='w', shade=True,
lw=.5)
# ax.plot_wireframe(X, Y, Z, rstride=1, cstride=1000, lw=.5)
ax.set_title(file_name)
ax.set_xlabel("Tone")
ax.set_ylabel("Frame")
ax.set_zlabel("Log Correlation")
ax.set_zlim(10.0, 15.0)
ax.set_ylim(0, frames)
ax.view_init(30, -130)
plt.show()
if __name__ == "__main__":
receiver(sys.argv[1])