Last active
December 25, 2015 11:38
-
-
Save Lanny/6970041 to your computer and use it in GitHub Desktop.
Generate a really crappy .wav containing the main theme of Ode to Joy. Can generate other crappy .wav's as long as they only use the same 5 notes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import wave | |
import random | |
from math import pi, sin, cos | |
OPTIONS = {} | |
Hz_MAP = { | |
'c': 130.813, | |
'd': 146.832, | |
'e': 164.814, | |
'f': 174.614, | |
'g': 195.997 | |
} | |
def sin_wave_oscillator(hz, options=OPTIONS): | |
conv_const = hz*pi | |
def inner(base, dt): | |
return sin(dt*conv_const) | |
return inner | |
def adsr_envelope(attack, decay, sustain, sustain_duration, release, | |
options=OPTIONS): | |
peak_sustain_diff = sustain - 1. | |
decay_start = attack | |
sustain_start = decay_start + decay | |
release_start = sustain_start + sustain_duration | |
release_end = release_start + release | |
def inner(base, dt): | |
if dt < decay_start: | |
return base * (dt/attack) | |
elif dt > decay_start and dt < sustain_start: | |
return base * (((dt-decay_start)/decay) * peak_sustain_diff + 1.) | |
elif dt > sustain_start and dt < release_start: | |
return base * sustain | |
elif dt > release_start < release_end: | |
return base * (dt-release_start)/release * sustain | |
else: return 0 | |
return inner | |
def apply_amplitude(amp, options=OPTIONS): | |
def inner(base, dt): | |
return base * amp | |
return inner | |
def compose_stack(*args): | |
stack = args | |
def inner(base, dt): | |
result = base | |
for f in stack: | |
result = f(result, dt) | |
return result | |
return inner | |
def sample_to_str(sample, options=OPTIONS): | |
width = options['sample_width'] | |
sample_range = 2**(8*width) | |
sample = int((sample + .5) * sample_range) | |
bytes = [0]*width | |
for shift_by in range(width): | |
bytes[width - 1 - shift_by] = sample & 255 | |
sample = sample >> 8 | |
return ''.join(map(chr, bytes)) | |
def apply(duration, fn, options=OPTIONS): | |
frame_count = int(duration*options['frame_rate']) | |
arr = [0]*frame_count | |
for i in xrange(0, frame_count): | |
dt = float(i)/float(frame_count) * duration | |
arr[i] = fn(None, dt) | |
return ''.join(map(sample_to_str, arr)) | |
def noise(duration, frame_rate): | |
return ''.join([chr(random.randint(0, 255)) for _ in range(int(duration*frame_rate))]) | |
if __name__ == '__main__': | |
out_w = wave.open('out.wav', 'w') | |
OPTIONS['frame_rate'] = 44100 | |
OPTIONS['channels'] = 1 | |
OPTIONS['sample_width'] = 1 | |
out_w.setnchannels(OPTIONS['channels']) | |
out_w.setsampwidth(OPTIONS['sample_width']) | |
out_w.setframerate(OPTIONS['frame_rate']) | |
ode_to_joy = 'eefggfedccdeedd' | |
for note in ode_to_joy: | |
tone_gen = compose_stack( | |
sin_wave_oscillator(Hz_MAP[note]), | |
adsr_envelope(.0, .0, 1., .25, .0) | |
#apply_amplitude(.25) | |
) | |
frames = apply(.5, tone_gen) | |
out_w.writeframes(frames) | |
out_w.close() | |
print 'Done!' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment