Open sloev opened 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)
did you get the clicks and pops removed, apparently it has something to do with phase