Skip to content

Instantly share code, notes, and snippets.

@drscotthawley
Last active May 3, 2018 03:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drscotthawley/c344ab278c17b6b57b9d2142e89585b2 to your computer and use it in GitHub Desktop.
Save drscotthawley/c344ab278c17b6b57b9d2142e89585b2 to your computer and use it in GitHub Desktop.
Realtime waveform display with tunable trigger level
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'Scott H. Hawley'
__copyright__ = 'Scott H. Hawley'
__license__ = "MIT Licence (do what you want, don't blame me)"
import numpy as np
import cv2
import soundcard as sc # https://github.com/bastibe/SoundCard
from scipy.ndimage.interpolation import shift
# screen size & related variables
imWidth, imHeight = 1024, 512
y0 = imHeight/2 # horizontal axis
draw_amp = imHeight/2-1 # maximum amplitude in pixels
def draw_wave(screen, mono_audio, xs, title="oscilloscope", gain=5):
screen *= 0 # clear the screen
# next line determines the y-values for the wave form. - sign because CG is upside down
ys = ( y0 - draw_amp * np.clip( gain * mono_audio[0:len(xs)], -1, 1) ).astype(np.int)
pts = np.array(list(zip(xs,ys))) # pair up xs & ys
cv2.polylines(screen,[pts],False,(0,255,0)) # draw lines connecting the points
cv2.imshow(title, screen) # show what we've got
return
def find_trigger(mono_audio, thresh=0.02, pos_slope=True): # thresh = trigger level
start_ind = None # this is where in the buffer the trigger should go; None
shift_forward = shift(mono_audio, 1, cval=0)
if pos_slope:
inds = np.where(np.logical_and(mono_audio >= thresh, shift_forward <= thresh))
else:
inds = np.where(np.logical_and(mono_audio <= thresh, shift_forward >= thresh))
if (len(inds[0]) > 0):
start_ind = inds[0][0]
return start_ind
# main routine; audio buffer & sample rate
def oscilloscope(buf_size=1024, fs=44100):
default_mic = sc.default_microphone()
print("oscilloscope: listening on ",default_mic)
gain, trig_level = 5, 0.02 # seems to work on my mac
# allocate storage for 'screen'
screen = np.zeros((imHeight,imWidth,3), dtype=np.uint8) # 3=color channels
xs = np.arange(imWidth).astype(np.int) # x values of pixels
while (1): # keep looping until someone stops this
with default_mic.recorder(samplerate=fs) as mic:
audio_data = mic.record(numframes=buf_size) # get some audio from the mic
bgn = find_trigger(audio_data[:,0], thresh=trig_level)
if bgn is not None: # try to trigger
end = min(bgn+imWidth, buf_size) # don't go off the end of the buffer
pad_len = max(0, imWidth - (end-bgn) ) # might have to pad with zeros
draw_wave(screen, np.pad(audio_data[bgn:end,0],(0,pad_len), \
'constant',constant_values=0), xs, gain=gain) # draw left channel
else:
draw_wave(screen, audio_data[0:imWidth,0]*0, xs, gain=gain) # draw zero line
key = cv2.waitKey(1) & 0xFF # keyboard input
if ord('q') == key: # quit key
break
elif ord('=') == key: # = sign (couldn't get arrow keys to register!)
trig_level += 0.002
print("trig_level =",trig_level)
elif ord("'") == key: # single quote
trig_level -= 0.002
print("trig_level =",trig_level)
elif ord('[') == key: # left bracket
gain *= 0.9
print("gain =",gain)
elif ord(']') == key: # right bracket
gain += 1.1
print("gain =",gain)
return
if __name__ == '__main__':
oscilloscope(buf_size=int(imWidth*1.5)) # give the buffer a little extra room so you don't see zero-pads
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment