127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
from random import randint
|
|
from math import sin, log, pi
|
|
from array import array
|
|
|
|
from pygame.mixer import Sound, get_init
|
|
|
|
class Samples(Sound):
|
|
|
|
def __init__(self):
|
|
self.set_amplitude()
|
|
Sound.__init__(self, self.build())
|
|
|
|
def set_amplitude(self):
|
|
self.amplitude = (1 << (self.get_sample_width() * 8 - 1)) - 1
|
|
|
|
def get_sample_width(self):
|
|
return abs(get_init()[1] / 8)
|
|
|
|
def build(self):
|
|
pass
|
|
|
|
def get_empty_array(self, length):
|
|
return array(self.get_array_typecode(), [0] * length)
|
|
|
|
def get_array_typecode(self):
|
|
return [None, "b", "h"][self.get_sample_width()]
|
|
|
|
|
|
class Note(Samples):
|
|
|
|
base_frequency = 440.0
|
|
base_octave = 4
|
|
base_name = "A"
|
|
names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
|
|
SQUARE, TRIANGLE, SAW, SINE, DIRTY = range(5)
|
|
|
|
def __init__(self, name=None, octave=4, frequency=None, shape=SQUARE,
|
|
volume=1.0):
|
|
names = self.names
|
|
self.shape = shape
|
|
if frequency is None:
|
|
self.name = name
|
|
self.octave = octave
|
|
self.set_frequency()
|
|
elif name is None:
|
|
self.frequency = float(frequency)
|
|
self.set_name_and_octave()
|
|
Samples.__init__(self)
|
|
self.set_volume(volume)
|
|
|
|
def set_frequency(self):
|
|
name, octave = self.name, self.octave
|
|
names = self.names
|
|
octave_length = len(names)
|
|
offset = (octave - self.base_octave) * octave_length + \
|
|
names.index(name) - names.index(self.base_name)
|
|
self.frequency = self.base_frequency * 2 ** \
|
|
(offset / float(octave_length))
|
|
|
|
def set_name_and_octave(self):
|
|
names = self.names
|
|
octave_length = len(names)
|
|
offset = int(round(log(self.frequency / self.base_frequency, 2) * \
|
|
octave_length)) + names.index(self.base_name)
|
|
self.octave = self.base_octave + offset / octave_length
|
|
self.name = names[offset % octave_length]
|
|
|
|
def __repr__(self):
|
|
return "%s%i %.2f" % (self.name, self.octave, self.frequency)
|
|
|
|
def build(self):
|
|
period = int(round(get_init()[0] / self.frequency))
|
|
samples = self.get_empty_array(period)
|
|
shape = self.shape
|
|
if shape == self.TRIANGLE:
|
|
self.store_triangle_wave(samples, period)
|
|
elif shape == self.SAW:
|
|
self.store_saw_wave(samples, period)
|
|
elif shape == self.SINE:
|
|
self.store_sine_wave(samples, period)
|
|
elif shape == self.DIRTY:
|
|
self.store_dirty_wave(samples)
|
|
else:
|
|
self.store_square_wave(samples, period)
|
|
return samples
|
|
|
|
def store_triangle_wave(self, samples, period):
|
|
amplitude = self.amplitude
|
|
coefficient = 4 * amplitude / float(period - 1)
|
|
for time in xrange(int(round(period / 2.0))):
|
|
y = int((coefficient * time) - amplitude)
|
|
samples[time] = y
|
|
samples[-time - 1] = y
|
|
|
|
def store_saw_wave(self, samples, period):
|
|
amplitude = self.amplitude
|
|
for time in xrange(period):
|
|
samples[time] = int(2 * amplitude / float(period - 1) * time - \
|
|
amplitude)
|
|
|
|
def store_sine_wave(self, samples, period):
|
|
amplitude = self.amplitude
|
|
for time in xrange(period):
|
|
samples[time] = int(round(sin(time / (period / pi / 2)) * \
|
|
amplitude))
|
|
|
|
def store_dirty_wave(self, samples):
|
|
amplitude = self.amplitude
|
|
for time in xrange(len(samples)):
|
|
samples[time] = randint(-amplitude, amplitude)
|
|
|
|
def store_square_wave(self, samples, period):
|
|
amplitude = self.amplitude
|
|
for time in xrange(period):
|
|
if time < period / 2:
|
|
samples[time] = amplitude
|
|
else:
|
|
samples[time] = -amplitude
|
|
|
|
def play(self, maxtime=0, fadeout=None, panning=None, fade_in=0):
|
|
channel = Samples.play(self, -1, maxtime, fade_in)
|
|
if fadeout:
|
|
self.fadeout(fadeout)
|
|
if channel and panning:
|
|
channel.set_volume(*panning)
|
|
return channel
|