pythonaudiofilteringpython-sounddevice

What technique is required to remove unwanted frequencies in my sine wave audio signal


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.


Solution

  • 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()