Spaces:
Runtime error
Runtime error
import math as _math | |
import struct as _struct | |
from random import uniform as _uniform | |
from pyglet.media.codecs.base import Source, AudioFormat, AudioData | |
# Envelope classes: | |
class _Envelope: | |
"""Base class for SynthesisSource amplitude envelopes.""" | |
def get_generator(self, sample_rate, duration): | |
raise NotImplementedError | |
class FlatEnvelope(_Envelope): | |
"""A flat envelope, providing basic amplitude setting. | |
:Parameters: | |
`amplitude` : float | |
The amplitude (volume) of the wave, from 0.0 to 1.0. | |
Values outside this range will be clamped. | |
""" | |
def __init__(self, amplitude=0.5): | |
self.amplitude = max(min(1.0, amplitude), 0) | |
def get_generator(self, sample_rate, duration): | |
amplitude = self.amplitude | |
while True: | |
yield amplitude | |
class LinearDecayEnvelope(_Envelope): | |
"""A linearly decaying envelope. | |
This envelope linearly decays the amplitude from the peak value | |
to 0, over the length of the waveform. | |
:Parameters: | |
`peak` : float | |
The Initial peak value of the envelope, from 0.0 to 1.0. | |
Values outside this range will be clamped. | |
""" | |
def __init__(self, peak=1.0): | |
self.peak = max(min(1.0, peak), 0) | |
def get_generator(self, sample_rate, duration): | |
peak = self.peak | |
total_bytes = int(sample_rate * duration) | |
for i in range(total_bytes): | |
yield (total_bytes - i) / total_bytes * peak | |
while True: | |
yield 0 | |
class ADSREnvelope(_Envelope): | |
"""A four part Attack, Decay, Suspend, Release envelope. | |
This is a four part ADSR envelope. The attack, decay, and release | |
parameters should be provided in seconds. For example, a value of | |
0.1 would be 100ms. The sustain_amplitude parameter affects the | |
sustain volume. This defaults to a value of 0.5, but can be provided | |
on a scale from 0.0 to 1.0. | |
:Parameters: | |
`attack` : float | |
The attack time, in seconds. | |
`decay` : float | |
The decay time, in seconds. | |
`release` : float | |
The release time, in seconds. | |
`sustain_amplitude` : float | |
The sustain amplitude (volume), from 0.0 to 1.0. | |
""" | |
def __init__(self, attack, decay, release, sustain_amplitude=0.5): | |
self.attack = attack | |
self.decay = decay | |
self.release = release | |
self.sustain_amplitude = max(min(1.0, sustain_amplitude), 0) | |
def get_generator(self, sample_rate, duration): | |
sustain_amplitude = self.sustain_amplitude | |
total_bytes = int(sample_rate * duration) | |
attack_bytes = int(sample_rate * self.attack) | |
decay_bytes = int(sample_rate * self.decay) | |
release_bytes = int(sample_rate * self.release) | |
sustain_bytes = total_bytes - attack_bytes - decay_bytes - release_bytes | |
decay_step = (1 - sustain_amplitude) / decay_bytes | |
release_step = sustain_amplitude / release_bytes | |
for i in range(1, attack_bytes + 1): | |
yield i / attack_bytes | |
for i in range(1, decay_bytes + 1): | |
yield 1 - (i * decay_step) | |
for i in range(1, sustain_bytes + 1): | |
yield sustain_amplitude | |
for i in range(1, release_bytes + 1): | |
yield sustain_amplitude - (i * release_step) | |
while True: | |
yield 0 | |
class TremoloEnvelope(_Envelope): | |
"""A tremolo envelope, for modulation amplitude. | |
A tremolo envelope that modulates the amplitude of the | |
waveform with a sinusoidal pattern. The depth and rate | |
of modulation can be specified. Depth is calculated as | |
a percentage of the maximum amplitude. For example: | |
a depth of 0.2 and amplitude of 0.5 will fluctuate | |
the amplitude between 0.4 an 0.5. | |
:Parameters: | |
`depth` : float | |
The amount of fluctuation, from 0.0 to 1.0. | |
`rate` : float | |
The fluctuation frequency, in seconds. | |
`amplitude` : float | |
The peak amplitude (volume), from 0.0 to 1.0. | |
""" | |
def __init__(self, depth, rate, amplitude=0.5): | |
self.depth = max(min(1.0, depth), 0) | |
self.rate = rate | |
self.amplitude = max(min(1.0, amplitude), 0) | |
def get_generator(self, sample_rate, duration): | |
total_bytes = int(sample_rate * duration) | |
period = total_bytes / duration | |
max_amplitude = self.amplitude | |
min_amplitude = max(0.0, (1.0 - self.depth) * self.amplitude) | |
step = (_math.pi * 2) / period / self.rate | |
for i in range(total_bytes): | |
value = _math.sin(step * i) | |
yield value * (max_amplitude - min_amplitude) + min_amplitude | |
while True: | |
yield 0 | |
# Waveform generators | |
def silence_generator(frequency, sample_rate): | |
while True: | |
yield 0 | |
def noise_generator(frequency, sample_rate): | |
while True: | |
yield _uniform(-1, 1) | |
def sine_generator(frequency, sample_rate): | |
step = 2 * _math.pi * frequency / sample_rate | |
i = 0 | |
while True: | |
yield _math.sin(i * step) | |
i += 1 | |
def triangle_generator(frequency, sample_rate): | |
step = 4 * frequency / sample_rate | |
value = 0 | |
while True: | |
if value > 1: | |
value = 1 - (value - 1) | |
step = -step | |
if value < -1: | |
value = -1 - (value - -1) | |
step = -step | |
yield value | |
value += step | |
def sawtooth_generator(frequency, sample_rate): | |
period_length = int(sample_rate / frequency) | |
step = 2 * frequency / sample_rate | |
i = 0 | |
while True: | |
yield step * (i % period_length) - 1 | |
i += 1 | |
def pulse_generator(frequency, sample_rate, duty_cycle=50): | |
period_length = int(sample_rate / frequency) | |
duty_cycle = int(duty_cycle * period_length / 100) | |
i = 0 | |
while True: | |
yield int(i % period_length < duty_cycle) * 2 - 1 | |
i += 1 | |
# Source classes: | |
class SynthesisSource(Source): | |
"""Base class for synthesized waveforms. | |
:Parameters: | |
`generator` : A non-instantiated generator object | |
A waveform generator that produces a stream of floats from (-1, 1) | |
`duration` : float | |
The length, in seconds, of audio that you wish to generate. | |
`sample_rate` : int | |
Audio samples per second. (CD quality is 44100). | |
`envelope` : :py:class:`pyglet.media.synthesis._Envelope` | |
An optional Envelope to apply to the waveform. | |
""" | |
def __init__(self, generator, duration, sample_rate=44800, envelope=None): | |
self._generator = generator | |
self._duration = duration | |
self.audio_format = AudioFormat(channels=1, sample_size=16, sample_rate=sample_rate) | |
self._envelope = envelope or FlatEnvelope(amplitude=1.0) | |
self._envelope_generator = self._envelope.get_generator(sample_rate, duration) | |
# Two bytes per sample (16-bit): | |
self._bytes_per_second = sample_rate * 2 | |
# Maximum offset, aligned to sample: | |
self._max_offset = int(self._bytes_per_second * duration) & 0xfffffffe | |
self._offset = 0 | |
def get_audio_data(self, num_bytes, compensation_time=0.0): | |
"""Return `num_bytes` bytes of audio data.""" | |
num_bytes = min(num_bytes, self._max_offset - self._offset) | |
if num_bytes <= 0: | |
return None | |
timestamp = self._offset / self._bytes_per_second | |
duration = num_bytes / self._bytes_per_second | |
self._offset += num_bytes | |
# Generate bytes: | |
samples = num_bytes >> 1 | |
generator = self._generator | |
envelope = self._envelope_generator | |
data = (int(next(generator) * next(envelope) * 0x7fff) for _ in range(samples)) | |
data = _struct.pack(f"{samples}h", *data) | |
return AudioData(data, num_bytes, timestamp, duration, []) | |
def seek(self, timestamp): | |
# Bound within duration & align to sample: | |
offset = int(timestamp * self._bytes_per_second) | |
self._offset = min(max(offset, 0), self._max_offset) & 0xfffffffe | |
self._envelope_generator = self._envelope.get_generator(self.audio_format.sample_rate, self._duration) | |
class Silence(SynthesisSource): | |
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None): | |
"""Create a Silent waveform.""" | |
super().__init__(silence_generator(frequency, sample_rate), duration, sample_rate, envelope) | |
class WhiteNoise(SynthesisSource): | |
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None): | |
"""Create a random white noise waveform.""" | |
super().__init__(noise_generator(frequency, sample_rate), duration, sample_rate, envelope) | |
class Sine(SynthesisSource): | |
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None): | |
"""Create a sinusoid (sine) waveform.""" | |
super().__init__(sine_generator(frequency, sample_rate), duration, sample_rate, envelope) | |
class Square(SynthesisSource): | |
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None): | |
"""Create a Square (pulse) waveform.""" | |
super().__init__(pulse_generator(frequency, sample_rate), duration, sample_rate, envelope) | |
class Triangle(SynthesisSource): | |
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None): | |
"""Create a Triangle waveform.""" | |
super().__init__(triangle_generator(frequency, sample_rate), duration, sample_rate, envelope) | |
class Sawtooth(SynthesisSource): | |
def __init__(self, duration, frequency=440, sample_rate=44800, envelope=None): | |
"""Create a Sawtooth waveform.""" | |
super().__init__(sawtooth_generator(frequency, sample_rate), duration, sample_rate, envelope) | |
############################################# | |
# Experimental multi-operator FM synthesis: | |
############################################# | |
def sine_operator(sample_rate=44800, frequency=440, index=1, modulator=None, envelope=None): | |
"""A sine wave generator that can be optionally modulated with another generator. | |
This generator represents a single FM Operator. It can be used by itself as a | |
simple sine wave, or modulated by another waveform generator. Multiple operators | |
can be linked together in this way. For example:: | |
operator1 = sine_operator(samplerate=44800, frequency=1.22) | |
operator2 = sine_operator(samplerate=44800, frequency=99, modulator=operator1) | |
operator3 = sine_operator(samplerate=44800, frequency=333, modulator=operator2) | |
operator4 = sine_operator(samplerate=44800, frequency=545, modulator=operator3) | |
:Parameters: | |
`sample_rate` : int | |
Audio samples per second. (CD quality is 44100). | |
`frequency` : float | |
The frequency, in Hz, of the waveform you wish to generate. | |
`index` : float | |
The modulation index. Defaults to 1 | |
`modulator` : sine_operator | |
An optional operator to modulate this one. | |
`envelope` : :py:class:`pyglet.media.synthesis._Envelope` | |
An optional Envelope to apply to the waveform. | |
""" | |
# FM equation: sin((i * 2 * pi * carrier_frequency) + sin(i * 2 * pi * modulator_frequency)) | |
envelope = envelope or FlatEnvelope(1).get_generator(sample_rate, duration=None) | |
sin = _math.sin | |
step = 2 * _math.pi * frequency / sample_rate | |
i = 0 | |
if modulator: | |
while True: | |
yield sin(i * step + index * next(modulator)) * next(envelope) | |
i += 1 | |
else: | |
while True: | |
yield sin(i * step) * next(envelope) | |
i += 1 | |
def composite_operator(*operators): | |
return (sum(samples) / len(samples) for samples in zip(*operators)) | |