Skip to content

Instantly share code, notes, and snippets.

@tkshnwesper tkshnwesper/com.py
Created Jun 9, 2017

Embed
What would you like to do?
Python script that outputs the musical notes of the sounds that it captures through its mic
import alsaaudio
import numpy as np
from scipy.signal import fftconvolve, kaiser
from matplotlib.mlab import find
import math
# import serial
# ser = serial.Serial('/dev/ttyACM0', baudrate=9600, timeout=0)
fqsdict = {
65.41:'C2',
69.30:'C2#',
73.42:'D2',
77.78:'E2b',
82.41:'E2',
87.31:'F2',
92.50:'F2#',
98.00:'G2',
103.80:'G2#',
110.00:'A2',
116.50:'B2b',
123.50:'B2',
130.80:'C3',
138.60:'C3#',
146.80:'D3',
155.60:'E3b',
164.80:'E3',
174.60:'F3',
185.00:'F3#',
196.00:'G3',
207.70:'G3#',
220.00:'A3',
233.10:'B3b',
246.90:'B3',
261.60:'C4',
277.20:'C4#',
293.70:'D4',
311.10:'E4b',
329.60:'E4',
349.20:'F4',
370.00:'F4#',
392.00:'G4',
415.30:'G4#',
440.00:'A4',
466.20:'B4b',
493.90:'B4',
523.30:'C5',
554.40:'C5#',
587.30:'D5',
622.30:'E5b',
659.30:'E5',
698.50:'F5',
740.00:'F5#',
784.00:'G5',
830.60:'G5#',
880.00:'A5',
932.30:'B5b',
987.80:'B5',
1047.00:'C6',
1109.0:'C6#',
1175.0:'D6',
1245.0:'E6b',
1319.0:'E6',
1397.0:'F6',
1480.0:'F6#',
1568.0:'G6',
1661.0:'G6#',
1760.0:'A6',
1865.0:'B6b',
1976.0:'B6',
2093.0:'C7'
}
fqs = np.array(fqsdict.keys())
# Open the device in nonblocking capture mode. The last argument could
# just as well have been zero for blocking mode. Then we could have
# left out the sleep call in the bottom of the loop
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE,alsaaudio.PCM_NONBLOCK)
rate = 8000
frames = 1024
# Set attributes: Mono, 8000 Hz, 16 bit little endian samples
inp.setchannels(1)
inp.setrate(rate)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
# The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api.
# For our purposes, it is suficcient to know that reads from the device
# will return this many frames. Each frame being 2 bytes long.
# This means that the reads below will return either 320 bytes of data
# or 0 bytes of data. The latter is possible because we are in nonblocking
# mode.
inp.setperiodsize(frames)
# maxlen = 100
# arr = temp = d = np.zeros(maxlen)
# count = 0
soundgate = -13
# def unshift(a, ml):
# for i in range(ml - 1, 0, -1):
# a[i] = a[i-1]
# See https://github.com/endolith/waveform-analyzer/blob/master/frequency_estimator.py
def parabolic(f, x):
xv = 1/2. * (f[x-1] - f[x+1]) / (f[x-1] - 2 * f[x] + f[x+1]) + x
yv = f[x] - 1/4. * (f[x-1] - f[x+1]) * (xv - x)
return (xv, yv)
# See https://github.com/endolith/waveform-analyzer/blob/master/frequency_estimator.py
def freq_from_autocorr(raw_data_signal, fs):
corr = fftconvolve(raw_data_signal, raw_data_signal[::-1], mode='full')
corr = corr[len(corr)/2:]
d = np.diff(corr)
start = find(d > 0)[0]
peak = np.argmax(corr[start:]) + start
px, py = parabolic(corr, peak)
return fs / px
def loudness(chunk):
data = np.array(chunk, dtype=float) / 32768.0
ms = math.sqrt(np.sum(data ** 2.0) / len(data))
if ms < 10e-8: ms = 10e-8
return 10.0 * math.log(ms, 10.0)
while True:
l,data = inp.read()
if l:
d = np.fromstring(data, dtype=np.int16)
if loudness(d) < soundgate:
continue
try:
fq = round(freq_from_autocorr(d, rate), 2)
except:
continue
result = fqsdict[fqs[np.abs(fqs - fq).argmin()]]
# ser.write('<' + result + '>')
print(result)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.