'Record audio while holding a button, replay it on release and overwrite the previous file
I want to record audio while I'm pressing a button and replay it when I release the button.
It works fine for the first time, but when I record one more time, it appends the new record to the existing one. I can't figure out how to replace the file instead of appending to it. I've tried to delete it when I start to record, but it can't create a new one. Basically, I'd like to overwrite the file, instead of appending the sound to it.
from pynput import keyboard
import time
import pyaudio
import wave
import sched
from pygame import mixer
CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
p = pyaudio.PyAudio()
frames = []
def callback(in_data, frame_count, time_info, status):
frames.append(in_data)
return in_data, pyaudio.paContinue
class MyListener(keyboard.Listener):
def __init__(self):
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None
self.wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
self.wf.setnchannels(CHANNELS)
self.wf.setsampwidth(p.get_sample_size(FORMAT))
self.wf.setframerate(RATE)
def on_press(self, key):
if key.char == 'r':
self.key_pressed = True
return True
def on_release(self, key):
if key.char == 'r':
self.key_pressed = False
return True
listener = MyListener()
listener.start()
started = False
stream = None
def recorder():
global started, p, stream, frames
if listener.key_pressed and not started:
# Start the recording
try:
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback=callback)
print("Stream active:", stream.is_active())
started = True
print("start Stream")
except:
raise
elif not listener.key_pressed and started:
try:
started = False
print("Stop recording")
stream.stop_stream()
stream.close()
listener.wf.writeframes(b''.join(frames))
mixer.init()
mixer.music.load("output.wav")
mixer.music.set_volume(0.8)
mixer.music.play()
except:
raise
# Reschedule the recorder function in 100 ms.
task.enter(0.1, 1, recorder, ())
print("Press and hold the 'r' key to begin recording")
print("Release the 'r' key to end recording")
task = sched.scheduler(time.time, time.sleep)
task.enter(0.1, 1, recorder, ())
task.run()
Solution 1:[1]
audiomath provides some high-level classes that can help with tasks like this. Like pyaudio, it also wraps around PortAudio to allow recording and playback. Below is a shorter audiomath-based listing.
I'm guessing you probably don't sit with your finger on the 'r' button for longer than a few seconds. In that case, it's more efficient to pre-allocate several seconds of space in memory (provided it's more than enough) and record into/play back from that. For the sound you want to keep, you can always say player.sound.Write('output.wav') at the end. Or you can uncomment the line that saves every attempt.
If for any reason you definitely do want to stream open-endedly to a file while recording, you can supply a filename in the constructor and that will get passed to the Recorder instance (the memory-resident self.buffer still exists: it's a circular buffer retaining the last N seconds). Streaming to file requires an external ffmpeg installation, though. Note that audiomath's playback loads the entire thing back into memory, so nothing has really been made more memory-efficient overall...
Because of the use of ffmpeg, you can also stream directly to other audio formats, other than .wav if you want.
import time
import pynput
import audiomath as am
class MyListener(pynput.keyboard.Listener):
def __init__(self, filename=None, buffer_length_seconds=10):
"""
Note:
If you specify a long enough `buffer_length_seconds` for your
purposes, then you do not need to record to file and so do not
need to specify `filename`.
If you specify `filename`, you need to have installed `ffmpeg`
(see the help for `audiomath.ffmeg.Install`).
"""
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None
self.queue = []
self.filename = filename
self.buffer_length_seconds = buffer_length_seconds
self.new_recorder()
def new_recorder( self ):
self.buffer = am.Sound(self.buffer_length_seconds, nChannels=2, fs=44100)
self.recorder = am.Recorder(self.buffer, loop=True, recording=False, filename=self.filename)
def on_press(self, key):
try: key.char
except: return
if key.char == 'r' and not self.key_pressed:
print('recording...')
self.recorder.Record()
self.key_pressed = True
return True
def on_release(self, key):
try: key.char
except: return
if key.char == 'r' and self.key_pressed:
print('finished')
self.recorder.Stop()
self.recorder = None # seems to be necessary when filename is used, to ensure the ffmpeg process and file are closed and cleaned up
if self.filename:
player = am.Player( self.filename )
else:
player = am.Player( self.buffer )
# self.buffer.Write( 'output.wav') # if you want to ensure every attempt gets saved. File will be overwritten.
self.queue.append( player )
self.new_recorder()
self.key_pressed = False
return True
#with MyListener('output.wav') as listener: # this variant uses a file (and needs ffmpeg)
with MyListener() as listener: # this variant just records to memory
while True:
if listener.queue:
player = listener.queue.pop( 0 )
player.Play()
time.sleep(0.1)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
