I have created a sine wave that the user can change its frequency in real time using inputs of the key board, however there is a lot of noise I believe to be harmonic waves present in the audio output that I wish to remove. For this project, I require a sine wave tone at 400Hz, and the ability to increase or decrease the frequency by 0.1Hz. My code for the audio generation is here:
import numpy as np
import sounddevice as sd
import time
import msvcrt
from scipy import signal
# Define the initial parameters
duration = 0.026 # Duration of the sine wave audio block
freq = 400 # Starting frequency in Hz
sample_rate = 48000 # Sample rate in Hz
step = 0.1 # Frequency adjustment step in Hz
# Generate the initial time array for one audio block
block_size = int(duration * sample_rate)
t_block = np.linspace(0, duration, block_size, endpoint=False)
#Kaiser window
window = np.kaiser(block_size,
beta=5)#This is for window width
# Design the band-pass filter
nyquist_freq = 0.5 * sample_rate
cutoff_freq = [390/nyquist_freq, 424/nyquist_freq] # Cutoff frequency for the low-pass filter in Hz
b, a = signal.butter(4, cutoff_freq, 'bandpass')
# Define the audio callback function for each block
def audio_callback(outdata, frames, time, status):
global freq
wave = np.sin(2 * np.pi * freq * t_block)
outdata[:, 0] = signal.lfilter(b, a, wave * window) #Filtered signal
# Create an audio stream
stream = sd.OutputStream(callback=audio_callback, channels=1, samplerate=sample_rate) #Change to 2 channels for both ears
# Start the audio stream
stream.start()
# Main loop for real-time frequency adjustment
while True:
if msvcrt.kbhit():
key = msvcrt.getch()
if key == b'\x1b': # 'esc' key to exit the program
break
elif key == b'H': # Up arrow key to increase frequency
freq += step
print(f"Frequency: {freq:.1f}")
elif key == b'P': # Down arrow key to decrease frequency
freq -= step
print(f"Frequency: {freq:.1f}")
time.sleep(duration) # Wait for the duration of each audio block
# Stop and close the audio stream
stream.stop()
stream.close()
I have attached images to show the desired quality I want and the quality I am getting. The desired quality is the first image, the second image is when I applied the band pass filter and the kaiser window, and the third is just the kaiser window. These audio recordings came from an external microphone from my speakers.
Any help will be appreciated and if you believe I am missing an important piece of information just say. I am new to audio engineering so I might be unaware of a technique used for audio filtering.
I've tried applying different forms of filters such as band-pass, notch, and so on. I've tried different beta values of the kaiser window which has improved the signal significantly but not enough for a finished product. I have tried techniques such as frequency modulation synthesis, linear regression, and adjusting notch filters but all have either made the audio quality worse or didn't make significant changes. As you can see from the images, I wish to generate one distinct frequency signal so the audio appears clearer.
Using msvcrt
/time
is fairly awkward and probably not portable. Strongly consider something like pygame instead.
You do not need and should not use a filter. Just fill a buffer properly, keeping track of a rolling phase shift.
Use stream
as a context manager.
import numpy as np
import pygame
import sounddevice
freq = 400
sample_rate = 48_000
increment = 0.1
phase = 0
def audio_callback(
outdata: np.ndarray, frames: int, time: 'CData', status: sounddevice.CallbackFlags,
) -> None:
global phase
omega = 2*np.pi*freq/sample_rate
start_angle = phase
stop_angle = phase + frames*omega
phase = np.fmod(stop_angle, 2*np.pi)
arg = np.linspace(
start=start_angle,
stop=stop_angle,
num=frames,
)
outdata[:, 0] = 10_000*np.sin(arg)
def step(direction: int) -> None:
global freq
freq = max(1, min(sample_rate, freq + direction*increment))
print(f'Frequency: {freq:.1f} Hz')
def main() -> None:
pygame.init()
pygame.display.set_mode(size=(320, 240))
pygame.display.set_caption('Playing sine')
clock = pygame.time.Clock()
with sounddevice.OutputStream(
callback=audio_callback, channels=1, samplerate=sample_rate, dtype='int16',
) as stream:
stream.start()
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
return
pressed = pygame.key.get_pressed()
if pressed[pygame.K_DOWN]:
step(-1)
elif pressed[pygame.K_UP]:
step(1)
clock.tick(100)
if __name__ == '__main__':
main()