pgfw/pgfw/Note.py

157 lines
5.1 KiB
Python

# adapted from Pythoven
# https://github.com/phiresky/pythoven
from random import randint
from math import sin, log, pi
from array import array
from pygame import version
from pygame.mixer import Sound, get_init
class Samples(Sound):
def __init__(self):
self.set_amplitude()
if version.vernum < (1, 9, 2):
Sound.__init__(self, self.build())
else:
Sound.__init__(self, buffer=self.build())
def set_amplitude(self):
self.amplitude = (1 << (self.get_sample_width() * 8 - 1)) - 1
def get_sample_width(self):
return abs(int(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
if period > 1:
coefficient = 4 * amplitude / float(period - 1)
for time in range(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
if period > 1:
for time in range(period):
samples[time] = int(2 * amplitude / float(period - 1) * time - \
amplitude)
def store_sine_wave(self, samples, period):
amplitude = self.amplitude
for time in range(period):
samples[time] = int(round(sin(time / (period / pi / 2)) * \
amplitude))
def store_dirty_wave(self, samples):
amplitude = self.amplitude
for time in range(len(samples)):
samples[time] = randint(-amplitude, amplitude)
def store_square_wave(self, samples, period):
amplitude = self.amplitude
for time in range(period):
if time < period / 2:
samples[time] = amplitude
else:
samples[time] = -amplitude
def play(self, maxtime=0, fadeout=None, panning=None, fade_in=0, position=None):
channel = Samples.play(self, -1, maxtime, fade_in)
if fadeout:
self.fadeout(fadeout)
if position is not None:
panning = self.get_panning(position)
if channel and panning:
channel.set_volume(*panning)
return channel
def get_panning(self, position):
return 1 - max(0, ((position - .5) * 2)), 1 + min(0, ((position - .5) * 2))
class Chord:
def __init__(self, *args):
self.notes = args
def play(self, maxtime=0, fadeout=[None], panning=None, fade_in=[0], position=None):
if isinstance(fadeout, int):
fadeout = [fadeout]
if isinstance(fade_in, int):
fade_in = [fade_in]
for ii, note in enumerate(self.notes):
note.play(maxtime, fadeout[ii % len(fadeout)], panning, fade_in[ii % len(fade_in)], position)
def stop(self):
for note in self.notes:
note.stop()