Compare commits

..

2 Commits

Author SHA1 Message Date
7465ef13ea Added usage notes to the README 2024-05-29 22:22:03 +09:30
f1a88f4f47 Live graph working (somewhat) 2024-05-29 22:20:41 +09:30
4 changed files with 98 additions and 49 deletions

View File

@ -51,3 +51,13 @@ low pass & decimate if necessary
reset code detection counter (prohibit detecting codes for another 0.5 sec) reset code detection counter (prohibit detecting codes for another 0.5 sec)
``` ```
## Usage
Either
```
./audio-capture.sh | ./live.py
```
or
```
./audio-pipe.sh ./samples/MA-ZC.wav | ./live.py
```

View File

@ -3,6 +3,6 @@
set -eux set -eux
sample_rate="44100" sample_rate="44100"
channels="2" channels="1"
arecord -t raw -c ${channels} -f S16_LE -r ${sample_rate} arecord -t raw -c ${channels} -f S16_LE -r ${sample_rate}

View File

@ -4,6 +4,6 @@ set -eux
audio_file=$1 audio_file=$1
sample_rate="44100" sample_rate="44100"
channels="2" channels="1"
ffmpeg -i ${audio_file} -f s16le -ac ${channels} -ar ${sample_rate} - ffmpeg -hide_banner -loglevel error -i ${audio_file} -f s16le -ac ${channels} -ar ${sample_rate} -

View File

@ -3,19 +3,14 @@
import sys import sys
import threading import threading
import numpy as np import numpy as np
import signal import signal as sig
from tones import TONES
from filters import anti_alias, bandpass_filter, note
from scipy import signal
from scipy.signal import butter, lfilter, decimate
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtWidgets, mkQApp
data1 = np.random.normal(size=300)
ptr1 = 0
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
plot = win.addPlot()
curve = plot.plot(data1)
keep_running = True keep_running = True
def signal_handler(sig, frame): def signal_handler(sig, frame):
@ -23,6 +18,62 @@ def signal_handler(sig, frame):
print('SIGINT received. Stopping...') print('SIGINT received. Stopping...')
keep_running = False keep_running = False
class DetectorGui(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(DetectorGui, self).__init__(*args, **kwargs)
layout = pg.GraphicsLayoutWidget(show=True)
self.setCentralWidget(layout)
self.setWindowTitle('SELCAL Detector')
self.resize(1280, 800)
self.plot = layout.addPlot()
legend_view = layout.addViewBox()
legend = pg.LegendItem(offset=(0, 0))
legend.setParentItem(legend_view)
color_map = pg.colormap.get('CET-C6s')
colors = color_map.getLookupTable(nPts=len(TONES))
t = np.linspace(0, 500, 100)
self.tone_data = {}
self.tone_lines = {}
for tone,color in zip(TONES.keys(), colors):
self.tone_data[tone] = np.zeros(int(10000), dtype=np.float64) #np.array([], dtype=np.float64)
self.tone_lines[tone] = self.plot.plot(self.tone_data[tone], pen=pg.mkPen(color=color), name=tone)
legend.addItem(self.tone_lines[tone], tone)
self.plot.setLabel('left', 'Signal Correlation')
self.plot.setLabel('bottom', 'Time (samples)')
self.plot.showGrid(x=True, y=True)
legend_view.setFixedWidth(80)
layout.ci.layout.setColumnFixedWidth(1, 80)
self.show()
def set_position(self, pos):
pass
def push_tone(self, tone, value):
self.tone_data[tone] = np.roll(self.tone_data[tone], -1)
self.tone_data[tone][-1] = value
self.tone_lines[tone].setData(self.tone_data[tone])
def push_tones(self, tone, values):
#self.tone_data[tone] = np.append(self.tone_data[tone], values)
self.tone_data[tone] = np.roll(self.tone_data[tone], -len(values))
self.tone_data[tone][-len(values):] = values
self.tone_lines[tone].setData(self.tone_data[tone])
mkQApp("Correlation matrix display")
gui = DetectorGui()
def read_audio_from_stdin(chunk_size, process_chunk): def read_audio_from_stdin(chunk_size, process_chunk):
global keep_running global keep_running
@ -39,55 +90,43 @@ def read_audio_from_stdin(chunk_size, process_chunk):
audio_chunk = np.frombuffer(data, dtype=np.int16) audio_chunk = np.frombuffer(data, dtype=np.int16)
process_chunk(audio_chunk) process_chunk(audio_chunk)
sample_rate = 44100
note_length = 0.1
N = 256
cumsum_convolution = np.ones(N)/N
def process_audio_chunk(audio_chunk): def process_audio_chunk(audio_chunk):
# Example processing: simply print the chunk global gui
global data1, ptr1, curve
print(f"Read chunk: {len(audio_chunk)}")
data1[:-1] = data1[1:] # shift data in the array one sample left data = audio_chunk
# (see also: np.roll) sample_rate = 44100
data1[-1] = len(audio_chunk)
ptr1 += 1 data, sample_rate, decimation = anti_alias(data, sample_rate, 4800)
curve.setData(data1) pure_signals = {tone:note(freq, note_length, rate=sample_rate) for tone,freq in TONES.items()}
curve.setPos(ptr1, 0) correlations = {tone:np.abs(signal.correlate(data, pure, mode='same')) for tone,pure in pure_signals.items()}
massaged = {tone:decimate(np.convolve(correlation, cumsum_convolution, mode='valid'), 4) for tone,correlation in correlations.items()}
print('processing done')
for tone,massage in massaged.items():
gui.push_tones(tone, massage )
if __name__ == '__main__': if __name__ == '__main__':
signal.signal(signal.SIGINT, signal_handler) sig.signal(sig.SIGINT, signal_handler)
chunk_duration = 0.1 # seconds chunk_duration = 0.1 # seconds
sample_rate = 44100 sample_rate = 44100
channels = 2 channels = 1
chunk_size = int(sample_rate * chunk_duration) * channels chunk_size = int(sample_rate * chunk_duration) * channels
reader_thread = threading.Thread(target=read_audio_from_stdin, args=(chunk_size, process_audio_chunk)) reader_thread = threading.Thread(target=read_audio_from_stdin, args=(chunk_size, process_audio_chunk))
reader_thread.daemon = True reader_thread.daemon = True
reader_thread.start() reader_thread.start()
pg.exec() pg.exec()
# Wait... # Wait...
reader_thread.join() reader_thread.join()
'''
# 1) Simplest approach -- update data in the array such that plot appears to scroll
# In these examples, the array size is fixed.
p1 = win.addPlot()
p2 = win.addPlot()
data1 = np.random.normal(size=300)
curve1 = p1.plot(data1)
curve2 = p2.plot(data1)
ptr1 = 0
def update1():
global data1, ptr1
data1[:-1] = data1[1:] # shift data in the array one sample left
# (see also: np.roll)
data1[-1] = np.random.normal()
curve1.setData(data1)
ptr1 += 1
curve2.setData(data1)
curve2.setPos(ptr1, 0)
'''