SpencerLS
SpencerLS

Reputation: 371

Gaps in between waveforms after generating wave file using numpy and scipy

I made a program using python 3.7, numpy, and scipy that generates waveforms using the digits of pi, and stitches them together to make a "song". My only problem is that there are gaps between each note.

I have tried using mathematical functions that make the waves fade out for each note, I tried getting the notes to overlap a bit (With little luck of figuring it out), and a few more crazy things that didn't do anything...

import numpy as np
from scipy.io.wavfile import write

pi = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848"
piarray = list(pi)
piarray.remove(".")

print(piarray)

# Samples per second
sps = 44100

# Frequency / pitch of the sine wave
freq_hz = 440.0

# Duration
duration_s = 0.2

each_sample_number = np.arange(duration_s * sps)

for i in range(len(piarray)):
    if(piarray[i] == "0"):
        freq_hz = 277.18
    elif(piarray[i] == "1"):
        freq_hz = 311.13
    elif(piarray[i] == "2"):
        freq_hz = 369.99
    elif(piarray[i] == "3"):
        freq_hz = 415.30
    elif(piarray[i] == "4"):
        freq_hz = 466.16
    elif(piarray[i] == "5"):
        freq_hz = 554.37
    elif(piarray[i] == "6"):
        freq_hz = 622.25
    elif(piarray[i] == "7"):
        freq_hz = 739.99
    elif(piarray[i] == "8"):
        freq_hz = 830.61
    else:
        freq_hz = 932.33

    waveform = np.sin(2 * np.pi * each_sample_number * freq_hz / sps)*0.3
    #The line above and below this one make an individual note.
    waveform_integers = np.int16(waveform * 32767)

    if(i == 0):
        waveformc = waveform_integers
        print(waveformc)
    else:
        waveformc = np.append(waveformc, waveform_integers, axis=None)

write('song.wav', sps, waveformc)
print("DONE")


I have tried searching for solutions to this specific problem, but I haven't found anything related anywhere. I would just like the wave file to not have gaps between each notes, but there is. Thanks for any help you can give me!

Upvotes: 2

Views: 267

Answers (2)

Rodrigo Torres
Rodrigo Torres

Reputation: 384

I added a "correction factor" to the frequency to make sure each wave will end in with zero amplitude, and there will be no discontinuities. It changes the frequency a little bit, but not more than 1%. That was how I did that:

cor_fac = round(each_sample_number[-1] * freq_hz / sps)/(each_sample_number[-1] * freq_hz / sps)
cor_factors.append(cor_fac)
waveform = np.sin(2 * np.pi * each_sample_number * freq_hz / sps * cor_fac)*0.3

I think it solves the problem.

If that change on the frequency is not acceptable, you could try to change the start of your sample array to make it start with the same amplitude as the last waveform finished.

I will try to do it and post here.

Let me know it that worked for you.

EDIT: Code not changing the sound frequency:

import numpy as np
from scipy.io.wavfile import write
import matplotlib.pyplot as plt
import wave
import sys


pi = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848"
piarray = list(pi)
piarray.remove(".")

print(piarray)

# Samples per second
sps = 44100

# Frequency / pitch of the sine wave
freq_hz = 440.0

# Duration
duration_s = 0.2

last_amp = 0
cor_factors = []
direction_down = False

for i in range(len(piarray)):
    if(piarray[i] == "0"):
        freq_hz = 277.18
    elif(piarray[i] == "1"):
        freq_hz = 311.13
    elif(piarray[i] == "2"):
        freq_hz = 369.99
    elif(piarray[i] == "3"):
        freq_hz = 415.30
    elif(piarray[i] == "4"):
        freq_hz = 466.16
    elif(piarray[i] == "5"):
        freq_hz = 554.37
    elif(piarray[i] == "6"):
        freq_hz = 622.25
    elif(piarray[i] == "7"):
        freq_hz = 739.99
    elif(piarray[i] == "8"):
        freq_hz = 830.61
    else:
        freq_hz = 932.33

    # cor_fac = round(each_sample_number[-1] * freq_hz / sps)/(each_sample_number[-1] * freq_hz / sps)
    # cor_factors.append(cor_fac)

    start = np.arcsin(last_amp/0.3)    
    if direction_down:
        start = np.pi - start   
    start = start/(2 * np.pi * freq_hz / sps)

    each_sample_number = np.arange(start, start + duration_s * sps)
    waveform = np.sin(2 * np.pi * each_sample_number * freq_hz / sps)*0.3
    print(waveform[0]-last_amp)
    last_amp = waveform[-1]
    direction_down = waveform[-1]<waveform[-2]
    #The line above and below this one make an individual note.
    waveform_integers = np.int16(waveform * 32767)

    if(i == 0):
        waveformc = waveform_integers
        print(waveformc)
    else:
        waveformc = np.append(waveformc, waveform_integers, axis=None)

write('song_2.wav', sps, waveformc)
print("DONE")

Upvotes: 1

user11563547
user11563547

Reputation:

You don't have any gap in between your waveforms. You can see from this view of the result from Reaper that you have continuous sound:

enter image description here

What you have is a discontinuity in your waveform each time you start up a new note. This will be heard as clicks or pops each time the note changes. Since the waveforms for each note are being calculated against the underlying data structure, they will all have a zero crossing at 0s, and then quickly get out a phase with each other from there.

enter image description here

To fix this you could try either a proper gradual fade in/out of each sound, or make sure you are tracking the phase of your waveform and keeping it consistent as the notes change.

For a falloff function, you want something along the lines of (frames - each_sample_number) / frames)**n so that it will reach zero by the end. You can play around with this function to see how it affects the duration of the sound and the perceived clipping between notes.

import numpy as np
from scipy.io.wavfile import write

pi = "3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848"
piarray = list(pi)
piarray.remove(".")

print(piarray)

# Samples per second
sps = 44100

# Frequency / pitch of the sine wave
freq_hz = 440.0

# Duration
duration_s = 0.2
frames = duration_s * sps # counting how many frames for a note
each_sample_number = np.arange(duration_s * sps)



for i in range(len(piarray)):
    if(piarray[i] == "0"):
        freq_hz = 277.18
    elif(piarray[i] == "1"):
        freq_hz = 311.13
    elif(piarray[i] == "2"):
        freq_hz = 369.99
    elif(piarray[i] == "3"):
        freq_hz = 415.30
    elif(piarray[i] == "4"):
        freq_hz = 466.16
    elif(piarray[i] == "5"):
        freq_hz = 554.37
    elif(piarray[i] == "6"):
        freq_hz = 622.25
    elif(piarray[i] == "7"):
        freq_hz = 739.99
    elif(piarray[i] == "8"):
        freq_hz = 830.61
    else:
        freq_hz = 932.33

    # added fall off feature
    waveform = (((frames - each_sample_number) / frames)**0.5) * np.sin(
                        np.pi+ 2 * np.pi * each_sample_number * freq_hz / sps)*0.3

    #The line above and below this one make an individual note.
    waveform_integers = np.int16(waveform * 32767)

    if(i == 0):
        waveformc = waveform_integers
        print(waveformc)
    else:
        waveformc = np.append(waveformc, waveform_integers, axis=None)

write('song.wav', sps, waveformc)
print("DONE")

You can see the results of the current settings on the waveform: enter image description here

Upvotes: 2

Related Questions