-
Notifications
You must be signed in to change notification settings - Fork 1
/
taps.py
108 lines (87 loc) · 3.36 KB
/
taps.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env python
# Mostly borrowed from PyAudio examples
import pyaudio
import struct
import math
import analyse
import mido
import time
import numpy as np
from midi import pitch_to_midi
SAMPLING_RATE = 44100
# Adjust as necessary
INPUT_BLOCK_TIME = 0.05
INITIAL_TAP_THRESHOLD = 0.010 # Default value is 0.010
INPUT_FRAMES_PER_BLOCK = int(SAMPLING_RATE * INPUT_BLOCK_TIME)
# if we get this many loud blocks in a row, raise the tap threshold
OVERSENSITIVE = 15.0 / INPUT_BLOCK_TIME
# if we get this many quiet blocks in a row, decrease the tap threshold
UNDERSENSITIVE = 120.0 / INPUT_BLOCK_TIME
# if the noise was longer than this many blocks, it's not a 'tap'
MAX_TAP_BLOCKS = 0.15 / INPUT_BLOCK_TIME
def get_rms(block):
# From PyAudio examples
# RMS amplitude is defined as the square root of the
# mean over time of the square of the amplitude.
# so we need to convert this string of bytes into
# a string of 16-bit samples...
SHORT_NORMALIZE = (1.0 / 32768.0)
count = len(block) / 2
format = "%dh" % (count)
shorts = struct.unpack(format, block)
# iterate over the block.
sum_squares = 0.0
for sample in shorts:
# sample is a signed short in +/- 32768.
# normalize it to 1.0
n = sample * SHORT_NORMALIZE
sum_squares += n * n
return math.sqrt(sum_squares / count)
def get_mic_input():
pa = pyaudio.PyAudio()
input_stream = pa.open(format=pyaudio.paInt16, channels=1, rate=SAMPLING_RATE, input=True, frames_per_buffer=INPUT_FRAMES_PER_BLOCK)
return input_stream
def determine_pitch(raw_samples):
# SoundAnalyse needs array of samples in 16-bit format for pitch detection
samples = np.fromstring(raw_samples, dtype=np.int16)
return analyse.detect_pitch(samples, ratio=1.0)
if __name__ == '__main__':
tap_threshold = INITIAL_TAP_THRESHOLD
noisy_count = MAX_TAP_BLOCKS + 1
quiet_count = 0
tap_count = 0
input_stream = get_mic_input()
output = mido.open_output()
while True:
try:
samples = input_stream.read(INPUT_FRAMES_PER_BLOCK)
except IOError as e:
print e
noisy_count = 1
continue
amplitude = get_rms(samples)
pitch = determine_pitch(samples)
if amplitude > tap_threshold:
# too loud to be a tap, adjust threshold if this keeps happening
quiet_count = 0
noisy_count += 1
if noisy_count > OVERSENSITIVE:
# raise sensitivity by 110%
tap_threshold *= 1.1
else:
if 1 <= noisy_count <= MAX_TAP_BLOCKS:
tap_count += 1
# output.send(mido.Message('stop'))
note = analyse.midinum_from_pitch(pitch)
velocity = int(math.log(amplitude) * -20) - 32
note_on = mido.Message('note_on', note=60, velocity=velocity)
# output.send(note_on)
song.append(note_on)
output.send(note_on)
print '{count}: TAP! @ {pitch} Hz velocity: {velocity}'.format(count=tap_count, pitch=(math.ceil(pitch) or 'no pitch'), msg=note_on, amplitude=amplitude, velocity=velocity)
# too quiet
noisy_count = 0
quiet_count += 1
if quiet_count > UNDERSENSITIVE:
# lower sensitivity by 90%
tap_threshold *= 0.9