janbogar / bird_song_generator

Birdsong generator by ilknuricke, but now in python!
2 stars 0 forks source link

clicks pops removal #2

Open sloev opened 4 years ago

sloev commented 4 years ago

did you get the clicks and pops removed, apparently it has something to do with phase

sloev commented 4 years ago

got it working by shifting around the array and fading in / out, still not a nice solution though.

import numpy as np
from scipy.integrate import odeint
from scipy.io import wavfile
import os
import os.path
import random
import itertools

"""
This is a bird song generator by ilknuricke

https://github.com/ilknuricke/birdsong_generator_CPG

but in python!

To see how it works, see the original, especially this wonderfull blog:
http://mindwriting.org/blog/?p=229

Changes compared to the original:
-Brain and syrinx models are merged into a single set of ODE to avoid the interpolation
-Duration of a syllable is 0.3 s, not 0.24 s as stated in the original source

Variables meaning (see the original):
y = [ xp , y , xk,  x , y  ]
 ...|    brain   |  syrinx  |

Some changes would have to be done to remove the pops between the syllables
"""

#brain global variables
rho1=0.
rho3=6.
A=10.
B=10.
C=10.
D=-2.
E=4.
alpha=2.
beta=20.

#syrinx global variables
b=1000.
d=10.0**8

#syllable creation variables

Fs = 44100    #sampling frequency
t_lim=0.24     #duration of a syllable in seconds

def bird_model(y,t_now,rho2):
    dydt=np.zeros(5)

    #brain part

    dydt[0] = 30.*(-y[0]+(1/(1+np.exp(-1*(rho1+A*y[0]-B*y[1])))))
    dydt[1] = 30.*(-y[1]+(1/(1+np.exp(-1*(rho2+C*y[0]-D*y[1]+alpha*y[2])))))
    dydt[2] = 120.*(-y[2]+(1/(1+np.exp(-1*(rho3+E*y[2]-beta*y[1])))))

    #syrinx part

    p=7000.*y[0]-2200.
    k=1.4*10**9*y[2]+4.8*10.**8

    dydt[3]=y[4]
    dydt[4]=-k*y[3]-(b-p)*y[4]-d*y[4]*y[3]**2

    return dydt

def sing_syllable(rho2):
    t = np.arange(0,t_lim,1./Fs)

    result=odeint(bird_model,0.01*np.ones(5), t, (rho2,))

    sound=result.T[4].astype('float32')
    sound/=np.max(sound)
    return sound

def fade(x, in_length, out_length=None, type='l', copy=True):
    """Apply fade in/out to a signal.
    If `x` is two-dimenstional, this works along the columns (= first
    axis).
    This is based on the *fade* effect of SoX, see:
    http://sox.sourceforge.net/sox.html
    The C implementation can be found here:
    http://sourceforge.net/p/sox/code/ci/master/tree/src/fade.c
    Parameters
    ----------
    x : array_like
        Input signal.
    in_length : int
        Length of fade-in in samples (contrary to SoX, where this is
        specified in seconds).
    out_length : int, optional
        Length of fade-out in samples.  If not specified, `fade_in` is
        used also for the fade-out.
    type : {'t', 'q', 'h', 'l', 'p'}, optional
        Select the shape of the fade curve: 'q' for quarter of a sine
        wave, 'h' for half a sine wave, 't' for linear ("triangular")
        slope, 'l' for logarithmic, and 'p' for inverted parabola.
        The default is logarithmic.
    copy : bool, optional
        If `False`, the fade is applied in-place and a reference to
        `x` is returned.
    """
    x = np.array(x, copy=copy)

    if out_length is None:
        out_length = in_length

    def make_fade(length, type):
        fade = np.arange(length) / length
        if type == 't':  # triangle
            pass
        elif type == 'q':  # quarter of sinewave
            fade = np.sin(fade * np.pi / 2)
        elif type == 'h':  # half of sinewave... eh cosine wave
            fade = (1 - np.cos(fade * np.pi)) / 2
        elif type == 'l':  # logarithmic
            fade = np.power(0.1, (1 - fade) * 5)  # 5 means 100 db attenuation
        elif type == 'p':  # inverted parabola
            fade = (1 - (1 - fade)**2)
        else:
            raise ValueError("Unknown fade type {0!r}".format(type))
        return fade

    # Using .T w/o [:] causes error: https://github.com/numpy/numpy/issues/2667
    x[:in_length].T[:] *= make_fade(in_length, type)
    x[len(x) - out_length:].T[:] *= make_fade(out_length, type)[::-1]
    return x
print('creating syllables')

def create_sound(syl=-10.8):
    sound = sing_syllable(syl)
    sound = sound[200:]
    sound = fade(sound, 200, 200)
    return sound

syllables = {
    'a':-11.0,
    'b':-11.8,
    'c':-7.1,
    'd':-8.1,
    'e':-9.1
}
for k,v in syllables.items():
    print(f'creating syl: {k}', flush=True)
    syllables[k] = create_sound(v)

# for index, val in enumerate(np.nditer(syllables['a'])):
#     print(index, "{:10.30f}".format(abs(val)), flush=True)

#syllables['b']=sing_syllable(-11.8)
#syllables['c']=sing_syllable(-7.1)

#uncomment to save the syllables as wav files

# for i in syllables.keys():
#     wavfile.write('syllables/'+i+'.wav',Fs,syllables[i])
# exit(0)
# print('creating songs')
# if not os.path.isdir('songs'):
#     os.mkdir('songs')

# songs=['abcc','bbc','cccc','bba']

song_sound=np.array([])

all_songs = list(itertools.product('abcde ', repeat=10))

for i in range(5):
    song = random.choice(all_songs)
    print('|'+''.join(song), flush=True)

    silence = np.zeros(random.randint(int(t_lim*Fs), 2*Fs))
    song = [syllables.get(c, np.zeros(int(t_lim*Fs))) for c in song]
    song_sound=np.concatenate((song_sound, silence, *song))
    print(f"appended song:{i}", flush=True)
print("writing song", flush=True)

wavfile.write(os.path.join('final_song.wav'),Fs, song_sound)