Reputation: 348
I have this code for creating wav file:
class Note:
def __init__(self,name,octave,duration):
self.name = name
self.octave = octave
self.duration = duration
SAMPLE_RATE = 44100
wav = wave.open("starw.wav", mode="w")
wav.setparams((1, 2, SAMPLE_RATE , SAMPLE_RATE*4, 'NONE', 'noncompressed'))
def generate_sample(note,volume):
freq = FREQUENCIES[note.octave][note.name]
duration = note.duration
total_samples = int(SAMPLE_RATE * duration)
for i in range(0,total_samples):
s = np.clip(int((volume)*math.sin(freq*math.pi*float(i)/float(SAMPLE_RATE))),-32768,32768)
data = struct.pack('<h',s)
wav.writeframes(data)
return data
for i,m in enumerate(melody):
if i<len(melody)/5:
generate_sample(m, 100+i*500)
elif len(melody)/5<=i<=4*len(melody)/5:
generate_sample(m, 8800)
else:
generate_sample(m, 8800-i*20)
Here FREQUENCIES
is a dict describing notes and their frequencies. melody
is an array of Note
objects. Everything works fine except for the fact that strange clicking sounds are produced during pauses. As far as I understood, it has something to do with the sample rate. But I don't know how to change my code in order to avoid pauses. Here is the melody:
https://drive.google.com/open?id=1e03VC90w5WF4QdU-VxrV906dl_c3UvSQ
Upvotes: 3
Views: 676
Reputation: 44918
If you mean the unpleasant clicking sounds between the notes: that's entirely normal.
Even in programming languages that have been designed specifically for generating music, this effect occurs if you simply juxtapose blocks with (sine) waves that have some fixed amplitude during the entire duration.
Here is a sketch of what it looks like when one note ends, and the next note begins:
Unless the first node ends exactly at zero by accident, you will hear this "jump" that is plotted at 2
.
In order to avoid this, you have to implement better note synthesis.
A simple solution would be to smoothly ramp up the amplitude of each note, and then let it smoothly decay in the end. Here is what a transition between two notes would look like if you do this:
This graph has been generated by the following formula:
if x > 2:
return sin((x-2) * 60) * (1 - exp(-(x-2)**2 / 0.5))
else:
return sin(x * 30) * (1- exp(-(x-2)**2 / 0.5))
Hopefully, you can recognize the (1 - exp(-(x - tClick) ** 2 / smoothness))
"mollifier" factors that are responsible for the ramp up / decay.
But then again you see that the gaps between the notes appear to become larger, and if you then try to make the intervals of the different notes intersect, it all becomes rather messy and complicated, and you're probably better off taking a programming language or a library that already knows how to do it properly. I think Chuck manual contained a rather detailed explanation of how to get increasingly better sounding notes.
Upvotes: 6
Reputation: 354
If I were to guess, I'd think it may be related to the fact that the note lengths are sometimes not even multiples of the note's frequency. As a result, sin(end_time)
of the note may sometimes result in a relatively large value: 32768
in the worst case.
The sudden drop in volume at that frequency may be responsible for the clicking/chuckling noise you're hearing.
Terminating notes only when int((volume)*math.sin(freq*math.pi*float(i)/float(SAMPLE_RATE)))
evaluates to a value near 0
would eliminate much of that noise, should this be the case.
Upvotes: 1