import wave
import struct
import random
from decimal import Decimal, ROUND_HALF_UP
from numpy import interp, clip
import sys
SECONDS = 10
CHANNELS = 1
AMPLITUDE_MIN = 400
AMPLITUDE_MAX = 2000
FREQUENCY_MIN = 400
FREQUENCY_MAX = 4000
FRAME_RATE = 48000
SAMPLE_WIDTH = 2
class RandomWalk():
def __init__(self, **kwargs):
self.start_value = kwargs.get('start_value', 0)
self.value = self.start_value
self.possible_steps = kwargs.get('possible_steps', [1, -1])
self.steps = kwargs.get('steps', 0)
self.step_mode = kwargs.get('step_mode', 'normal') # alternative: fixed
self.hit_zero = kwargs.get('hit_zero', True) # possible obsolete with min/max value
self.step_number = 0
self.positions = ()
self.min_value = kwargs.get('min_value', None)
self.max_value = kwargs.get('max_value', None)
self.second_order = kwargs.get('second_order', False)
self.second_order_alpha = kwargs.get('second_order_alpha', 0.5)
if self.min_value is None and self.max_value is None:
self.barriers = False
else:
self.barriers = True
if self.second_order:
boundary = (self.max_value - self.min_value)*self.second_order_alpha
self.primary_walk = RandomWalk(
start_value=random.randint(-boundary, boundary),
possible_steps=self.possible_steps,
step_mode='normal',
min_value=-boundary,
max_value=boundary,
)
def one_step_forward(self):
# select a valid step
if self.second_order:
step = self.primary_walk.one_step_forward()
else:
while True:
step = random.choice(self.possible_steps)
valid = True
# check if random walk crosses or hits zero
if self.hit_zero is False:
if self.value > 0 and self.value+step <= 0:
valid = False
elif self.value < 0 and self.value+step >= 0:
valid = False
# check if mode is fixed and it hits a barrier
if self.step_mode == 'fixed' and self.barriers:
if self.value+step > self.max_value or self.value+step < self.min_value:
valid = False
if valid:
break
# if we have a fixed random walk, remove the selected step from the possible steps
if self.step_mode == 'fixed':
self.possible_steps.remove(step)
# mirror step if it hits barrier
if self.barriers:
if self.value+step > self.max_value:
self.value -= step
if self.value+step < self.min_value:
self.value -= step
else:
self.value += step
else:
self.value += step
self.step_number += 1
self.positions += (self.value,)
return self.value
def show_random_walk(self, steps=None):
if steps is None:
steps = len(self.possible_steps)
for i in range(0, steps):
self.one_step_forward()
return self.positions
def create_sound():
# Init new wave File
stream = wave.open('sound.wav', 'wb')
stream.setnchannels(CHANNELS)
stream.setsampwidth(SAMPLE_WIDTH)
stream.setframerate(FRAME_RATE)
# Init amplitude and pitch random walk
amplitude_walk = RandomWalk(
start_value=random.randint(AMPLITUDE_MIN, AMPLITUDE_MAX),
possible_steps=[100, -100],
step_mode='normal',
min_value=AMPLITUDE_MIN,
max_value=AMPLITUDE_MAX,
second_order=True,
)
pitch_walk = RandomWalk(
start_value=random.randint(FREQUENCY_MIN, FREQUENCY_MAX),
possible_steps=[10, -10],
step_mode='normal',
min_value=FREQUENCY_MIN,
max_value=FREQUENCY_MAX,
second_order=True,
)
break_point_walk = RandomWalk(
start_value=random.randint(3, 20),
possible_steps=[0],
step_mode='normal',
min_value=3,
max_value=20,
)
frame = 0
while frame < FRAME_RATE * SECONDS * CHANNELS:
# get a pitch, amplitude and breakpoint value
pitch = pitch_walk.one_step_forward()
amplitude = amplitude_walk.one_step_forward()
break_points = break_point_walk.one_step_forward()
frame_window = int(Decimal(FRAME_RATE/pitch).quantize(Decimal(1.0), rounding=ROUND_HALF_UP))
# todo: round to even number?
# start first half of period
all_possible_steps = []
for i in range(0, int(frame_window/4)):
all_possible_steps.append(1)
all_possible_steps.append(-1)
random_walk = RandomWalk(
start_value=1,
possible_steps=all_possible_steps,
step_mode='fixed',
hit_zero=False,
)
steps = random_walk.show_random_walk()
# start second half of period
all_possible_steps = []
for i in range(0, int(frame_window/4)):
all_possible_steps.append(1)
all_possible_steps.append(-1)
random_walk = RandomWalk(
start_value=-1,
possible_steps=all_possible_steps,
step_mode='fixed',
hit_zero=False,
)
steps += random_walk.show_random_walk()
break_point_y = []
break_point_x = []
# add a break at the beginning
break_point_y.append(0)
break_point_x.append(0)
for i in range(0, break_points):
if break_points % 2 == 0:
break_point_y.append(int(((i+1)/(break_points+1))*len(steps)))
break_point_x.append(steps[int(((i+1)/(break_points+1))*len(steps))])
elif break_points == 1:
break_point_y.append(int((1/2)*len(steps)))
break_point_x.append(steps[int((1/2)*len(steps))])
else:
break_point_y.append(int((i/(break_points-1))*len(steps)))
try:
break_point_x.append(steps[int((i/(break_points-1))*len(steps))])
except IndexError: # steps[len(steps)] will result in IndexError
break_point_x.append(steps[-1])
# add a break point to the end
break_point_y.append(len(steps)-1)
break_point_x.append(0)
for step_number, step in enumerate(steps):
frame_value = interp(step_number, break_point_y, break_point_x)
stream.writeframes(struct.pack('h', clip(int(frame_value*amplitude), -32767, 32767)))
frame += 1
if __name__ == '__main__':
create_sound()