better hourly
[scannr.git] / snd.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
""" Record a few seconds of audio and save to a WAVE file.
Based on http://stackoverflow.com/questions/892199/detect-record-audio-in-python/6743593#6743593
"""
 
import pyaudio
import wave
import sys
from array import array
from struct import unpack, pack
from pydispatch import dispatcher
 
THRESHOLD = 500
CHUNK_SIZE = 1024
FORMAT = pyaudio.paInt16
RATE = 44100
if sys.platform.startswith('darwin'):
    CHANNELS = 2
elif sys.platform.startswith('win32'):
    CHANNELS = 1
 
MAX_SILENT = 80
 
def is_silent(L):
    "Returns `True` if below the 'silent' threshold"
    "print max(L)"
    "print max(L) < THRESHOLD"
    return max(L) < THRESHOLD
 
 
def normalize(L):
    "Average the volume out"
    MAXIMUM = 16384
    times = float(MAXIMUM) / max(abs(i) for i in L)
 
    LRtn = array('h')
    for i in L:
        LRtn.append(int(i * times))
    return LRtn
 
 
def trim(L):
    "Trim the blank spots at the start and end"
 
    def _trim(L):
        snd_started = False
        LRtn = array('h')
 
        for i in L:
            if not snd_started and abs(i) > THRESHOLD:
                snd_started = True
                LRtn.append(i)
 
            elif snd_started:
                LRtn.append(i)
        return LRtn
 
    # Trim to the left
    L = _trim(L)
 
    # Trim to the right
    L.reverse()
    L = _trim(L)
    L.reverse()
    return L
 
 
def add_silence(L, seconds):
    "Add silence to the start and end of `L` of length `seconds` (float)"
    LRtn = array('h', [0 for i in xrange(int(seconds * RATE))])
    LRtn.extend(L)
    LRtn.extend([0 for i in xrange(int(seconds * RATE))])
    return LRtn
 
 
def record():
    """
    Record a word or words from the microphone and
    return the data as an array of signed shorts.
 
    Normalizes the audio, trims silence from the
    start and end, and pads with 0.5 seconds of
    blank sound to make sure VLC et al can play
    it without getting chopped off.
    """
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE,
        input=True,
        frames_per_buffer=CHUNK_SIZE)
 
    num_silent = 0
    snd_started = False
 
    LRtn = array('h')
 
    while 1:
        try:
            data = stream.read(CHUNK_SIZE)
        except IOError as ex:
            if ex[1] != pyaudio.paInputOverflowed:
                raise
            data = '\x00' * CHUNK_SIZE
        L = unpack('<' + ('h' * (len(data) / 2)), data) # little endian, signed short
        L = array('h', L)
 
        silent = is_silent(L)
        #print silent, num_silent, L[:10]
 
        if silent and snd_started:
            num_silent += 1
            #print num_silent
        elif not silent and not snd_started:
            dispatcher.send(signal='SND_STARTED')
            snd_started = True
            print snd_started
        if snd_started and not silent:
            num_silent = 0
        if snd_started:
            LRtn.extend(L)
        if snd_started and num_silent > MAX_SILENT:
            break
 
    sample_width = p.get_sample_size(FORMAT)
    stream.stop_stream()
    stream.close()
    p.terminate()
 
    LRtn = normalize(LRtn)
    LRtn = trim(LRtn)
    LRtn = add_silence(LRtn, 0.5)
    return sample_width, LRtn
 
 
def record_to_file(path):
    "Records from the microphone and outputs the resulting data to `path`"
    sample_width, data = record()
    data = pack('<' + ('h' * len(data)), *data)
 
    wf = wave.open(path, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(sample_width)
    wf.setframerate(RATE)
    wf.writeframes(data)
    wf.close()
    print("done - result written to " + path)
    del data
 
 
if __name__ == '__main__':
    filename = 'demo.wav'
    record_to_file(filename)
    print("done - result written to " + filename)