pythonarraysaudiowave

Cleaning generated Sinewave artifacts for smooth frequency transitions


I'm a python beginner and as a learning project I'm doing a SSTV encoder using Wraase SC2-120 methods.

SSTV for those who don't know is a technique sending images through radio as sound and to be decoded in the receiving end back to an image. Wraase SC2-120 is one of many types of encoding but it's one of the more simpler ones that support color.

I've been able to create a system that takes an image and converts it to an array. Then take that array and create the needed values for the luminance and chrominance needed for the encoder.

I'm using then this block to create a value between 1500hz - 2300hz for the method.

 def ChrominanceAsHertz(value=0.0):
    value = 800 * value
    value -= value % 128 # Test. Results were promising but too much noise
    value += 1500
    return int(value)

You can ignore the modulus operation. It is just my way of playing around with the data for "fun" and experiments.

I then clean the audio to avoid having too many of the same values in the same array and add their duration together to achieve a cleaner sound

cleanTone = []
cleanDuration = []

for i in range(len(hertzData)-1):
    # If the next tone is not the same
    # Add it to the cleantone array
    # with it's initial duration
    if hertzData[i] != hertzData[i+1]:
        cleanTone.append(hertzData[i])
        cleanDuration.append(durationData[i])
    # else add the duration of the current hertz to the clean duration array
    else:
        # the current duration is the last inserted duration
        currentDur = cleanDuration[len(cleanDuration)-1]
        # Add the new duration to the current duration
        currentDur += durationData[i]
        cleanDuration[len(cleanDuration)-1] = currentDur

My array handling can use some work but it's not why I'm here for now.

The result is a array where no consecutive values are the same and the duration of that tone is still correct.

I then create a sinewave array using this block

audio = []
for i in range(len(cleanTone)):   
    sineAudio = AudioGen.SineWave(cleanTone[i], cleanDuration[i]) 
    for sine in sineAudio:
        audio.append(sine)

The sinewave function is

   def SineWave( freq=440, durationMS = 500, sample_rate = 44100.0 ):
    num_samples = durationMS * (sample_rate / 1000)
    audio = []
    for i in range(int(num_samples)):
        audio.insert(i, np.sin(2 * np.pi * freq * (i / sample_rate)))
    return audio

It works as intended. It creates a sinewave of the frequency I want and for the duration I want.

The problem is that when I create the .wav file then with wave the sinewaves created are not smoothly transitioning.

Screenshot of a closeup of what I mean. Sinusoidal wave artifacts

The audio file has these immense screeches and cracks because of these artifacts that the above method produces, seeing as how it takes a single frequency and duration with no regard of where the last tone ended and starts a new.

What I've tried to do to remedy these is to refactor that SineWave method to take in a whole array and create the sinewaves consecutively right after one another in hopes of achieving a clean sound but it still did the same thing.

I also tried "smoothing" the generated audio array then with a simple filtering operation from this post.

 0.7 * audio[1:-1] + 0.15 * ( audio[2:] + audio[:-2] )

but the results again were not satisfying and the artifacts were still present.

I've also started to look into Fourier Transforms, mainly FFT (fast fourier transform) but I'm not that familiar with them yet to know exactly what it is that I'm trying to do and code.

For SSTV to work the changes in frequency have to sometimes be very fast. 0.3ms fast to be exact, so I'm kinda lost on how to achieve this without loosing too much data in the process.

TL;DR My sinewave function is producing artifacts inbetween tone changes that cause scratches and unwanted pops. How to not do that?


Solution

  • What you need to transfer from one wave-snippet to the next is the phase. You have to start the next wave with the phase you ended the previous phase.

    def SineWave( freq=440, durationMS = 500, phase = 0, sample_rate = 44100.0):
        num_samples = int(durationMS * (sample_rate / 1000))
        audio = []
    
        for i in range(num_samples):
            audio.insert(i, np.sin(2 * np.pi * freq * (i / sample_rate) + phase))
    
        phase = (phase + 2 * np.pi * freq * (num_samples / sample_rate)) % (2 * np.pi)
        return audio, phase
    

    In your main loop pass through the phase from one wave fragment to the next:

    audio = []
    phase = 0
    
    for i in range(len(cleanTone)):   
        sineAudio, phase = AudioGen.SineWave(cleanTone[i], cleanDuration[i], phase)
        for sine in sineAudio:
            audio.append(sine)