1

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.

Merkel
  • 13
  • 2
  • Due to issues with formatting, I couldn't attach the images in the original question, I'll repost them in the order stated above. [Good quality data](https://i.stack.imgur.com/EOYXl.png) [enter image description here](https://i.stack.imgur.com/dn12E.jpg) [enter image description here](https://i.stack.imgur.com/LNqBB.jpg) – Merkel May 27 '23 at 15:32
  • have you tried something similar in C or C++, i think for some reason python is not low level enough of a language to do proper audio synthesis – zhiguang May 27 '23 at 16:48
  • 1
    Thank you for the comment, reinderien's solution worked, but this is a good point which I may think about in the future when I require better-quality audio. For now, I was working in Python for a low-fidelity prototype, but highly likely I will transfer my code to cpp so the audio quality can be improved at faster rates. – Merkel May 28 '23 at 11:32
  • Audio is low bandwidth. Unless you need extremely low latency, Python is totally fine when used properly and fidelity is not going to be a problem – Reinderien May 28 '23 at 17:58

1 Answers1

0

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()
Reinderien
  • 11,755
  • 5
  • 49
  • 77
  • Thank you for the response, this solution makes the signal much clearer, I didn't think a buffer between each callback would solve the issue or audio quality. Your solution significantly improved the audio quality, I wanted to ask though why you mentioned not using filtering to remove sine wave harmonics. It isn't required for me to remove them in the current project, but in the future when I have to remove frequencies in my audio what techniques would you suggest? – Merkel May 28 '23 at 11:30
  • The moral of the story should be "gain a better understanding of your signal generation and how to avoid introducing unnecessary noise" far above "how do I use a filter". The latter is an impossible question to answer in general, and depends entirely on application. – Reinderien May 28 '23 at 12:59