从前没机会学一门乐器,看到别人吹吹弹弹,煞是羡慕。后来表弟把osu介绍给偶,立马喜欢上了其中的太鼓模式,而且也只玩这一种——可能是小学时候曾参加仪仗队敲小鼓吧……
先是用手指敲键盘,后来看到淘宝有wii太鼓,就买了一个国产的改良型……意料之中,判定效果不好,因为它是用手柄按键那样的橡胶按钮来触发的,敲得很费劲。于是又淘了两个压电式的振动传感器装上,发现虽然传感器的响应能跟上(上面的指示灯可以和鼓点同步闪烁),但是鼓上的信号处理需要比较长的时间才能触发,可是振动信号时间很短。
在网上搜索,贴吧http://tieba.baidu.com/p/2247921967有人用声音作判据,用单片机处理并和PC通讯,映射键盘。这个已经做成产品卖了,效果还可以。不过由于国产鼓鼓边太软,容易出错,于是偶就在两边用透明胶粘上两层硬币……虽然看上去矬了些,也无可奈何……
正好最近在上python编程的课,偶就在想:这种声音识别算法应该不复杂,可以交给PC做。于是就敲了些代码试验,总结如下:
1、对于声音识别的算法,原本想用比较简单的时域处理(贴吧那位用上半周时间判别、或者过零点检测),但是效果不明显,改用FFT在频域上看功率谱:鼓边的高频成分较多,区别比较明显。
2、原先想用Matlab做,但是为了映射键盘,需调用API,而在C++中调用Matlab函数又有些复杂,于是改用Python,它有不少用于科学计算的库,详见:http://hyry.dip.jp/tech/book/page/scipy/index.html,做界面也比较简单。更重要的是可以直接调用win32的api,虚拟键盘的动作。
3、由于winXP不是实时的操作系统,做数据采集可能不太合适,所以一些参数需要根据情况调节,并提高处理程序的优先级。而且需要用Bill2's Process Manager把osu和程序放在一个cpu核心里运行才能使虚拟的按键有效。如果只是在记事本上打字却不必如此。
4、实际使用效果……一般般吧……如果把麦克风放在不同的位置,就有不同的效果。
嘛,就当是python课的作业吧……
操作系统:xp 32位
开发工具:python2.7
exe文件
源码(包含一些中间过程):
py文件
# -*- coding: utf-8 -*-
"""
Created on Fri Dec 20 21:53:50 2013
@author: Administrator
"""
import scipy as sp
## Importing all NumPy functions, modules and classes
from numpy import *
import pylab as pl
import matplotlib.pyplot as plt
import win32gui,win32api,win32con
import msvcrt
# -*- coding: utf-8 -*-
from pyaudio import PyAudio, paInt16
import numpy as np
from datetime import datetime
import wave
import pythoncom
import pyHook
import threading
import ctypes
import numpy as np
import time
from Tkinter import *
global st
st=False
def cb1():
NUM_SAMPLES = int(ns.get()) # pyAudio内部缓存的块的大小
SAMPLING_RATE = int(sr.get()) # 取样频率
LEVEL = int(l.get()) # 声音保存的阈值
COUNT_NUM = int(cn.get()) # NUM_SAMPLES个取样之内出现COUNT_NUM个大于LEVEL的取样则记录声音
SAVE_LENGTH = int(sl.get()) # 声音记录的最小长度:SAVE_LENGTH * NUM_SAMPLES 个取样
n=float(fz.get())
print NUM_SAMPLES,SAMPLING_RATE,LEVEL,COUNT_NUM ,SAVE_LENGTH,n
def onKeyboardEvent(event):
#print "Key:", event.Key
#print "---"
if str(event.Key)=='F12': #按F12退出
print "END"
ctypes.windll.user32.PostQuitMessage(0)
if str(event.Key)=='F2': #按F2暂停
global st
st=not st
return True
class timer(threading.Thread): #The timer class is derived from the class threading.Thread
def __init__(self):
threading.Thread.__init__(self)
self.thread_stop = False
def run(self): #Overwrite run() method, put what you want the thread do here
while not self.thread_stop:
hm = pyHook.HookManager()
hm.KeyDown = onKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages()
self.thread_stop = True
print 'ok'
def stop(self):
self.thread_stop = True
thread1 = timer()
thread1.start()
'''
# 将data中的数据保存到名为filename的WAV文件中
def save_wave_file(filename, data):
wf = wave.open(filename, 'wb')
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(SAMPLING_RATE)
wf.writeframes("".join(data))
wf.close()
'''
'''
NUM_SAMPLES = 1000 # pyAudio内部缓存的块的大小
SAMPLING_RATE = 44100 # 取样频率
LEVEL = 1500 # 声音保存的阈值
COUNT_NUM = 5 # NUM_SAMPLES个取样之内出现COUNT_NUM个大于LEVEL的取样则记录声音
SAVE_LENGTH = 3 # 声音记录的最小长度:SAVE_LENGTH * NUM_SAMPLES 个取样
n=0.1
'''
# 开启声音输入
pa = PyAudio()
stream = pa.open(format=paInt16, channels=1, rate=SAMPLING_RATE, input=True,
frames_per_buffer=NUM_SAMPLES)
save_count = SAVE_LENGTH
save_buffer = []
while True:
if thread1.isAlive()==False:
break
while True:
if thread1.isAlive()==False:
break
while st:
time.sleep(3)
# 读入NUM_SAMPLES个取样
string_audio_data = stream.read(NUM_SAMPLES)
# 将读入的数据转换为数组
audio_data = np.fromstring(string_audio_data, dtype=np.short)
#time.sleep(2)
# 计算大于LEVEL的取样的个数
large_sample_count = np.sum( audio_data > LEVEL )+np.sum( audio_data < -LEVEL )
#print np.max(audio_data)
# 如果个数大于COUNT_NUM,则至少保存SAVE_LENGTH个块
if save_count == SAVE_LENGTH:
if large_sample_count > COUNT_NUM:
save_buffer.append( string_audio_data )
save_count -= 1
else:
save_count -= 1
if save_count>0:
# 将要保存的数据存放到save_buffer中
save_buffer.append( string_audio_data )
else:
break
# 将save_buffer中的数据写入WAV文件,WAV文件的文件名是保存的时刻
if len(save_buffer) > 0:
filename = datetime.now().strftime("%Y-%m-%d_%H_%M_%S") + ".wav"
#save_wave_file(filename, save_buffer) #这里没用到文件保存
#print filename, "saved"
str_save_buffer=''.join(save_buffer)
y=np.fromstring(str_save_buffer, dtype=np.short)
#t=np.linspace(0,len(y),len(y))
'''
plt.figure(datetime.now().strftime("%Y-%m-%d_%H_%M_%S")+'t')
plt.plot(t,y) #看时域波形
plt.show()
'''
'''#一些时域的算法
sumy=np.sum(y>0)
print sumy
dy=[abs(j-i) for i,j in zip(y[:-1],y[1:])]
sumdy=sum(dy>2500)
print sumdy
avy=average(map(abs,y))
print avy
print sumdy/avy
up=[i>5000 for i in y]
sumup=np.sum([abs(j-i) for i,j in zip(up[:-1],up[1:])])
print 1000*sumup/len(y)
down=[i<-5000 for i in y]
sumdown=np.sum([abs(j-i) for i,j in zip(down[:-1],down[1:])])
print 1000*sumdown/len(y)
'''
fy=np.fft.fft(y)/len(y)#快速傅里叶变换
afy=abs(fy)
'''
plt.figure(datetime.now().strftime("%Y-%m-%d_%H_%M_%S"))
plt.plot(afy)#看频谱分布
plt.show()
plt.xlim(0,700)
plt.ylim(0,700)
'''
re=sum([i**2 for i in afy[150:700]])/sum([i**2 for i in afy[:700]])#高频信号的功率比
#print re
#虚拟键盘
if re>n:
win32api.keybd_event(65,0,0,0)
win32api.keybd_event(65,0,win32con.KEYEVENTF_KEYUP,0)
else:
win32api.keybd_event(83,0,0,0)
win32api.keybd_event(83,0,win32con.KEYEVENTF_KEYUP,0)
save_buffer = []
save_count = SAVE_LENGTH
root.destroy()
#界面
root = Tk()
label1 = Label(root,text = 'NUM_SAMPLES ')
label1.pack()
ns= StringVar()
entry1 = Entry(root,textvariable =ns)
ns.set('1000')
entry1.pack()
label2 = Label(root,text = 'SAMPLING_RATE ')
label2.pack()
sr = StringVar()
entry2 = Entry(root,textvariable = sr)
sr.set('44100')
entry2.pack()
label3 = Label(root,text = 'LEVEL')
label3.pack()
l= StringVar()
entry3 = Entry(root,textvariable = l)
l.set('1500')
entry3.pack()
label4 = Label(root,text = 'COUNT_NUM ')
label4.pack()
cn= StringVar()
entry4 = Entry(root,textvariable =cn)
cn.set('5')
entry4.pack()
label5 = Label(root,text = 'SAVE_LENGTH ')
label5.pack()
sl= StringVar()
entry5 = Entry(root,textvariable =sl)
sl.set('3')
entry5.pack()
label6 = Label(root,text = 'fazhi')
label6.pack()
fz= StringVar()
entry6 = Entry(root,textvariable =fz)
fz.set('0.12')
entry6.pack()
b1 = Button(root,text = 'OK',command = cb1)
b1.pack()
root.mainloop()
root.quit()
先是用手指敲键盘,后来看到淘宝有wii太鼓,就买了一个国产的改良型……意料之中,判定效果不好,因为它是用手柄按键那样的橡胶按钮来触发的,敲得很费劲。于是又淘了两个压电式的振动传感器装上,发现虽然传感器的响应能跟上(上面的指示灯可以和鼓点同步闪烁),但是鼓上的信号处理需要比较长的时间才能触发,可是振动信号时间很短。
在网上搜索,贴吧http://tieba.baidu.com/p/2247921967有人用声音作判据,用单片机处理并和PC通讯,映射键盘。这个已经做成产品卖了,效果还可以。不过由于国产鼓鼓边太软,容易出错,于是偶就在两边用透明胶粘上两层硬币……虽然看上去矬了些,也无可奈何……
正好最近在上python编程的课,偶就在想:这种声音识别算法应该不复杂,可以交给PC做。于是就敲了些代码试验,总结如下:
1、对于声音识别的算法,原本想用比较简单的时域处理(贴吧那位用上半周时间判别、或者过零点检测),但是效果不明显,改用FFT在频域上看功率谱:鼓边的高频成分较多,区别比较明显。
2、原先想用Matlab做,但是为了映射键盘,需调用API,而在C++中调用Matlab函数又有些复杂,于是改用Python,它有不少用于科学计算的库,详见:http://hyry.dip.jp/tech/book/page/scipy/index.html,做界面也比较简单。更重要的是可以直接调用win32的api,虚拟键盘的动作。
3、由于winXP不是实时的操作系统,做数据采集可能不太合适,所以一些参数需要根据情况调节,并提高处理程序的优先级。而且需要用Bill2's Process Manager把osu和程序放在一个cpu核心里运行才能使虚拟的按键有效。如果只是在记事本上打字却不必如此。
4、实际使用效果……一般般吧……如果把麦克风放在不同的位置,就有不同的效果。
嘛,就当是python课的作业吧……
操作系统:xp 32位
开发工具:python2.7
exe文件
源码(包含一些中间过程):
py文件
# -*- coding: utf-8 -*-
"""
Created on Fri Dec 20 21:53:50 2013
@author: Administrator
"""
import scipy as sp
## Importing all NumPy functions, modules and classes
from numpy import *
import pylab as pl
import matplotlib.pyplot as plt
import win32gui,win32api,win32con
import msvcrt
# -*- coding: utf-8 -*-
from pyaudio import PyAudio, paInt16
import numpy as np
from datetime import datetime
import wave
import pythoncom
import pyHook
import threading
import ctypes
import numpy as np
import time
from Tkinter import *
global st
st=False
def cb1():
NUM_SAMPLES = int(ns.get()) # pyAudio内部缓存的块的大小
SAMPLING_RATE = int(sr.get()) # 取样频率
LEVEL = int(l.get()) # 声音保存的阈值
COUNT_NUM = int(cn.get()) # NUM_SAMPLES个取样之内出现COUNT_NUM个大于LEVEL的取样则记录声音
SAVE_LENGTH = int(sl.get()) # 声音记录的最小长度:SAVE_LENGTH * NUM_SAMPLES 个取样
n=float(fz.get())
print NUM_SAMPLES,SAMPLING_RATE,LEVEL,COUNT_NUM ,SAVE_LENGTH,n
def onKeyboardEvent(event):
#print "Key:", event.Key
#print "---"
if str(event.Key)=='F12': #按F12退出
print "END"
ctypes.windll.user32.PostQuitMessage(0)
if str(event.Key)=='F2': #按F2暂停
global st
st=not st
return True
class timer(threading.Thread): #The timer class is derived from the class threading.Thread
def __init__(self):
threading.Thread.__init__(self)
self.thread_stop = False
def run(self): #Overwrite run() method, put what you want the thread do here
while not self.thread_stop:
hm = pyHook.HookManager()
hm.KeyDown = onKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages()
self.thread_stop = True
print 'ok'
def stop(self):
self.thread_stop = True
thread1 = timer()
thread1.start()
'''
# 将data中的数据保存到名为filename的WAV文件中
def save_wave_file(filename, data):
wf = wave.open(filename, 'wb')
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(SAMPLING_RATE)
wf.writeframes("".join(data))
wf.close()
'''
'''
NUM_SAMPLES = 1000 # pyAudio内部缓存的块的大小
SAMPLING_RATE = 44100 # 取样频率
LEVEL = 1500 # 声音保存的阈值
COUNT_NUM = 5 # NUM_SAMPLES个取样之内出现COUNT_NUM个大于LEVEL的取样则记录声音
SAVE_LENGTH = 3 # 声音记录的最小长度:SAVE_LENGTH * NUM_SAMPLES 个取样
n=0.1
'''
# 开启声音输入
pa = PyAudio()
stream = pa.open(format=paInt16, channels=1, rate=SAMPLING_RATE, input=True,
frames_per_buffer=NUM_SAMPLES)
save_count = SAVE_LENGTH
save_buffer = []
while True:
if thread1.isAlive()==False:
break
while True:
if thread1.isAlive()==False:
break
while st:
time.sleep(3)
# 读入NUM_SAMPLES个取样
string_audio_data = stream.read(NUM_SAMPLES)
# 将读入的数据转换为数组
audio_data = np.fromstring(string_audio_data, dtype=np.short)
#time.sleep(2)
# 计算大于LEVEL的取样的个数
large_sample_count = np.sum( audio_data > LEVEL )+np.sum( audio_data < -LEVEL )
#print np.max(audio_data)
# 如果个数大于COUNT_NUM,则至少保存SAVE_LENGTH个块
if save_count == SAVE_LENGTH:
if large_sample_count > COUNT_NUM:
save_buffer.append( string_audio_data )
save_count -= 1
else:
save_count -= 1
if save_count>0:
# 将要保存的数据存放到save_buffer中
save_buffer.append( string_audio_data )
else:
break
# 将save_buffer中的数据写入WAV文件,WAV文件的文件名是保存的时刻
if len(save_buffer) > 0:
filename = datetime.now().strftime("%Y-%m-%d_%H_%M_%S") + ".wav"
#save_wave_file(filename, save_buffer) #这里没用到文件保存
#print filename, "saved"
str_save_buffer=''.join(save_buffer)
y=np.fromstring(str_save_buffer, dtype=np.short)
#t=np.linspace(0,len(y),len(y))
'''
plt.figure(datetime.now().strftime("%Y-%m-%d_%H_%M_%S")+'t')
plt.plot(t,y) #看时域波形
plt.show()
'''
'''#一些时域的算法
sumy=np.sum(y>0)
print sumy
dy=[abs(j-i) for i,j in zip(y[:-1],y[1:])]
sumdy=sum(dy>2500)
print sumdy
avy=average(map(abs,y))
print avy
print sumdy/avy
up=[i>5000 for i in y]
sumup=np.sum([abs(j-i) for i,j in zip(up[:-1],up[1:])])
print 1000*sumup/len(y)
down=[i<-5000 for i in y]
sumdown=np.sum([abs(j-i) for i,j in zip(down[:-1],down[1:])])
print 1000*sumdown/len(y)
'''
fy=np.fft.fft(y)/len(y)#快速傅里叶变换
afy=abs(fy)
'''
plt.figure(datetime.now().strftime("%Y-%m-%d_%H_%M_%S"))
plt.plot(afy)#看频谱分布
plt.show()
plt.xlim(0,700)
plt.ylim(0,700)
'''
re=sum([i**2 for i in afy[150:700]])/sum([i**2 for i in afy[:700]])#高频信号的功率比
#print re
#虚拟键盘
if re>n:
win32api.keybd_event(65,0,0,0)
win32api.keybd_event(65,0,win32con.KEYEVENTF_KEYUP,0)
else:
win32api.keybd_event(83,0,0,0)
win32api.keybd_event(83,0,win32con.KEYEVENTF_KEYUP,0)
save_buffer = []
save_count = SAVE_LENGTH
root.destroy()
#界面
root = Tk()
label1 = Label(root,text = 'NUM_SAMPLES ')
label1.pack()
ns= StringVar()
entry1 = Entry(root,textvariable =ns)
ns.set('1000')
entry1.pack()
label2 = Label(root,text = 'SAMPLING_RATE ')
label2.pack()
sr = StringVar()
entry2 = Entry(root,textvariable = sr)
sr.set('44100')
entry2.pack()
label3 = Label(root,text = 'LEVEL')
label3.pack()
l= StringVar()
entry3 = Entry(root,textvariable = l)
l.set('1500')
entry3.pack()
label4 = Label(root,text = 'COUNT_NUM ')
label4.pack()
cn= StringVar()
entry4 = Entry(root,textvariable =cn)
cn.set('5')
entry4.pack()
label5 = Label(root,text = 'SAVE_LENGTH ')
label5.pack()
sl= StringVar()
entry5 = Entry(root,textvariable =sl)
sl.set('3')
entry5.pack()
label6 = Label(root,text = 'fazhi')
label6.pack()
fz= StringVar()
entry6 = Entry(root,textvariable =fz)
fz.set('0.12')
entry6.pack()
b1 = Button(root,text = 'OK',command = cb1)
b1.pack()
root.mainloop()
root.quit()