Home » Tutorials » How to Make a Voice Recorder using Python

How to Make a Voice Recorder using Python

Have you ever wished you could easily record your thoughts, meetings, or any sound that catches your ear, exactly when you want and for how long you desire? Today, we’re turning that wish into reality!

In today’s tutorial, we’re going to create a voice recorder app in Python, we will use a timer for the recording in which the user can control its duration and when to stop the recording.

Let’s get started!

Table of Contents

Necessary Libraries

To make this code run as intended, it’s necessary to install these libraries using your terminal or command prompt:

$ pip install tk 
$ pip install sounddevice 
$ pip install scipy

The sounddevice and scipy libraries for recording, playing, and saving audio.

Importing Libraries

from tkinter import *
from tkinter import messagebox
import sounddevice as sound
from scipy.io.wavfile import write
import threading
import time

We start by importing the tkinter library, which creates a  graphical user interface (GUI), then from tkinter we import messagebox which displays pop-up message boxes.

After that, we import sounddevice to record and play audio, and then scipy.io.wavfile that saves audio recordings to a WAV file.

Lastly, we import threading which allows running multiple threads or parallel tasks.

VoiceRecorder Class

Once we completed importing our libraries we start by defining our new class which we will name VoiceRecorder.

class VoiceRecorder:

Initializer Method and GUI Setup

def __init__(self, master):
       self.master = master
       master.title("Voice Recorder - The Pycodes")
       master.geometry("600x500")
       master.configure(bg="black")
       master.resizable(False, False)

Next, we set up our VoiceRecorder application, by defining the title size (600x500) and the color of the background (black) along with disabling the resizing of the main window.

Recording Variables

  self.recording = None
       self.freq = 44100
       self.stop_recording_flag = False  # Variable to signal the countdown to stop
       self.countdown_thread = None  # Variable to store the countdown thread

After that, we create an empty container (self.recording) So we can store the sound that we are going to record, then we set our Recorder to take 44100 sound snapshots per second, after that we have set the self.stop_recording_flag to False which tells our Recorder to not stop recording, lastly, we put a placeholder for a special helper that will count down the time while we’re recording.

Title Label

 self.label = Label(master, text="Voice Recorder", font="arial 30 bold", bg="black", fg="red")
       self.label.pack()

Here, we created a label named Voice Recorder and then packed it into the GUI.

Duration Label and Entry

  self.duration_label = Label(master, text="Enter recording time in seconds:", font="arial 15", bg="black", fg="red")
       self.duration_label.pack()

       self.duration_var = StringVar()
       self.entry = Entry(master, textvariable=self.duration_var, font="arial 30", width=15)
       self.entry.pack(pady=10)

In this section, we create a label that asks the user to enter the recording time and the entry where to put the answer and then pack it into the GUI.

Buttons

 self.start_button = Button(master, font="arial 20", text="Start Recording", bg="green", fg="white", bd=0, command=self.start_recording)
       self.start_button.pack(pady=10)


       self.stop_button = Button(master, font="arial 20", text="Stop Recording", bg="red", fg="white", bd=0, command=self.stop_recording, state=DISABLED)
       self.stop_button.pack(pady=10)


       self.save_button = Button(master, font="arial 20", text="Save Recording", bg="blue", fg="white", bd=0, command=self.save_recording, state=DISABLED)
       self.save_button.pack(pady=10)

Now, we create three buttons initially disabled and each has a different color but the same font, the buttons are “Start Recording“, “Stop Recording” and “Save Recording“.

Counter Label

self.counter_label = Label(master, text="", font="arial 40", width=4, background="black", fg="white")
       self.counter_label.pack()

Following that, we create the countdown label.

Start Recording Function

 def start_recording(self):
       try:
           recording_time = int(self.duration_var.get())
       except ValueError:
           messagebox.showerror("Error", "Please enter a valid duration.")
           return


       self.recording = sound.rec(recording_time * self.freq, samplerate=self.freq, channels=2)
       self.stop_recording_flag = False  # Reset the flag
       self.countdown_thread = threading.Thread(target=self.update_counter, args=(recording_time,))
       self.countdown_thread.start()


       self.start_button.config(state=DISABLED)
       self.stop_button.config(state=NORMAL)
       self.save_button.config(state=DISABLED)

This part of the code is initiated when the user clicks the “Start Recording” button, if the user doesn’t put an integer before clicking the button an error message appears. However, if he puts an integer then that integer is converted to recording time and the recording starts Thanks to the sounddevice library, and the audio is stored in the self.recording which was initially empty with the self.stop_recording_flag reset to False to indicate that the recording is ongoing and the creation of the self.countdown_thread which updates the countdown label during the countdown.

Lastly, once the “Start recording” button is clicked it gets disabled and only the Stop Recording button is enabled.

Stop Recording Function

   def stop_recording(self):
       sound.stop()
       self.stop_recording_flag = True  # Signal the countdown to stop

       # Wait for the countdown thread to complete before proceeding
       threading.Thread(target=self.wait_for_countdown).start()

The code presented here starts once the user clicks the “Stop Recording” button which stops the ongoing audio recording and changes the self.stop_recording_flag from False to True making the countdown thread stop, and starting the self.wait_for_countdown thread.

Wait For Countdown Function

def wait_for_countdown(self):
       # This function is used to join the countdown thread
       self.countdown_thread.join()


       # Update button states after the countdown thread completes
       self.master.after(0, self.update_button_states)


       # Trim the recorded data based on the actual recording time
       if self.recording is not None:
           self.recording = self.recording[:self.get_actual_recorded_frames()]


       # Save the recording automatically when the countdown finishes
       if not self.stop_recording_flag:
           self.master.after(0, self.save_recording)

This part of the code waits until the countdown finishes so that it can update the button states and make sure that the audio is recorded if the audio is recorded then any extra of it is removed, and it checks if the recording is stopped automatically or manually.

If it is stopped after the countdown finishes (automatically), then the audio is saved by automatically calling the save_recording function without the user’s intervention.

Update Button States

def update_button_states(self):
       # Update button states after the countdown thread completes
       self.start_button.config(state=NORMAL)
       self.stop_button.config(state=DISABLED)
       self.save_button.config(state=NORMAL)

This section starts once the countdown is over by enabling the “Start Recording” and “Save Recording” buttons while disabling the “Stop Recording” button.

Update Counter

def update_counter(self, recording_time):
       while recording_time > 0 and not self.stop_recording_flag:
           self.master.update()
           time.sleep(1)
           recording_time -= 1


           if recording_time == 0:
               messagebox.showinfo("Time Countdown", "Time's up")
               self.master.after(0, self.save_recording)  # Save the recording automatically when the countdown finishes
           self.counter_label.config(text=str(recording_time))

       sound.wait()

These lines are for managing the countdown during the recording. In other words, if the countdown is not zero or manually stopped it gets updated constantly by reducing the time until it reaches zero, once that happens a message pops up indicating that the time is up and the audio recording is automatically saved.

Get Actual Recorded Frames

 def get_actual_recorded_frames(self):
       # Calculate the actual number of frames recorded based on the time elapsed
       if self.recording is not None:
           elapsed_time = int(self.duration_var.get()) - int(self.counter_label.cget("text"))
           actual_frames = int(elapsed_time * self.freq)
           return min(actual_frames, len(self.recording))
       return 0

What this part of the code does is determine how much of the recorded audio is valid based on the time that has passed, making sure we don’t include any extra or unnecessary parts in our recording.

Save Recording

 def save_recording(self):
       try:
           write("voice-recording.wav", self.freq, self.recording)
           messagebox.showinfo("Save", "Recording saved successfully!")
       except Exception as e:
           messagebox.showerror("Error", f"An error occurred while saving the recording: {e}")

This one saves the recorded audio to a file named voice-recording.wav if that is successful a message indicating that pops up otherwise a message indicating the failure shows up instead.

Main Part of the Code

if __name__ == "__main__":
   root = Tk()
   app = VoiceRecorder(root)
   root.mainloop()

Lastly, this part creates the tkinter window while initializing the VoiceRecorder class and starts the mainloop.

Example

Full Code

from tkinter import *
from tkinter import messagebox
import sounddevice as sound
from scipy.io.wavfile import write
import threading
import time


class VoiceRecorder:
   def __init__(self, master):
       self.master = master
       master.title("Voice Recorder - The Pycodes")
       master.geometry("600x500")
       master.configure(bg="black")
       master.resizable(False, False)


       self.recording = None
       self.freq = 44100
       self.stop_recording_flag = False  # Variable to signal the countdown to stop
       self.countdown_thread = None  # Variable to store the countdown thread


       self.label = Label(master, text="Voice Recorder", font="arial 30 bold", bg="black", fg="red")
       self.label.pack()


       self.duration_label = Label(master, text="Enter recording time in seconds:", font="arial 15", bg="black", fg="red")
       self.duration_label.pack()


       self.duration_var = StringVar()
       self.entry = Entry(master, textvariable=self.duration_var, font="arial 30", width=15)
       self.entry.pack(pady=10)


       self.start_button = Button(master, font="arial 20", text="Start Recording", bg="green", fg="white", bd=0, command=self.start_recording)
       self.start_button.pack(pady=10)


       self.stop_button = Button(master, font="arial 20", text="Stop Recording", bg="red", fg="white", bd=0, command=self.stop_recording, state=DISABLED)
       self.stop_button.pack(pady=10)


       self.save_button = Button(master, font="arial 20", text="Save Recording", bg="blue", fg="white", bd=0, command=self.save_recording, state=DISABLED)
       self.save_button.pack(pady=10)


       self.counter_label = Label(master, text="", font="arial 40", width=4, background="black", fg="white")
       self.counter_label.pack()


   def start_recording(self):
       try:
           recording_time = int(self.duration_var.get())
       except ValueError:
           messagebox.showerror("Error", "Please enter a valid duration.")
           return


       self.recording = sound.rec(recording_time * self.freq, samplerate=self.freq, channels=2)
       self.stop_recording_flag = False  # Reset the flag
       self.countdown_thread = threading.Thread(target=self.update_counter, args=(recording_time,))
       self.countdown_thread.start()


       self.start_button.config(state=DISABLED)
       self.stop_button.config(state=NORMAL)
       self.save_button.config(state=DISABLED)


   def stop_recording(self):
       sound.stop()
       self.stop_recording_flag = True  # Signal the countdown to stop


       # Wait for the countdown thread to complete before proceeding
       threading.Thread(target=self.wait_for_countdown).start()


   def wait_for_countdown(self):
       # This function is used to join the countdown thread
       self.countdown_thread.join()


       # Update button states after the countdown thread completes
       self.master.after(0, self.update_button_states)


       # Trim the recorded data based on the actual recording time
       if self.recording is not None:
           self.recording = self.recording[:self.get_actual_recorded_frames()]


       # Save the recording automatically when the countdown finishes
       if not self.stop_recording_flag:
           self.master.after(0, self.save_recording)


   def update_button_states(self):
       # Update button states after the countdown thread completes
       self.start_button.config(state=NORMAL)
       self.stop_button.config(state=DISABLED)
       self.save_button.config(state=NORMAL)


   def update_counter(self, recording_time):
       while recording_time > 0 and not self.stop_recording_flag:
           self.master.update()
           time.sleep(1)
           recording_time -= 1


           if recording_time == 0:
               messagebox.showinfo("Time Countdown", "Time's up")
               self.master.after(0, self.save_recording)  # Save the recording automatically when the countdown finishes
           self.counter_label.config(text=str(recording_time))


       sound.wait()


   def get_actual_recorded_frames(self):
       # Calculate the actual number of frames recorded based on the time elapsed
       if self.recording is not None:
           elapsed_time = int(self.duration_var.get()) - int(self.counter_label.cget("text"))
           actual_frames = int(elapsed_time * self.freq)
           return min(actual_frames, len(self.recording))
       return 0


   def save_recording(self):
       try:
           write("voice-recording.wav", self.freq, self.recording)
           messagebox.showinfo("Save", "Recording saved successfully!")
       except Exception as e:
           messagebox.showerror("Error", f"An error occurred while saving the recording: {e}")


if __name__ == "__main__":
   root = Tk()
   app = VoiceRecorder(root)
   root.mainloop()

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top