pgfw/pgfw/Note.py

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