def test_sine_tone(): """The wavetable is repeated through the tone.""" # Calculate the envelope so we can divide it out of the generated sample envelope = tone(wavetable=Wavetable.from_function(lambda t: 1)) pitch = 50.0 sound = tone(pitch=pitch) samples = [] expected = [] for i in range(1000, 44100, 1000): t = i / 44100.0 phase = (t * pitch) % 1.0 * tau sample = sound[i] env = envelope[i] samples.append(sample / env) expected.append(sin(phase)) assert samples == approx(expected, abs=0.05)
def on_click(self, pos): super().on_click(pos) print("tone = pyfxr.tone(", f" {self.note!r}", " attack=0.05,", " sustain=0.0,", " release=0.1,", " wavetable=wavetable,", ")", sep="\n") s = pygame.mixer.Sound(buffer=pyfxr.tone(self.note, attack=0.05, sustain=0.0, release=0.1, wavetable=Waveform.current)) s.set_volume(0.5) s.play()
def test_adsr_envelope(): """Tones are modulated by an ADSR envelope.""" w = Wavetable.from_function(lambda t: 1) sound = tone(attack=0.1, decay=0.1, sustain=0.75, release=0.25, wavetable=w) def sample_at(t: float) -> float: """Get the (float) value of the waveform at time t.""" return sound[min(int(t * 44100), len(sound) - 1)] / (1 << 15) samples = list(map( sample_at, [0, 0.05, 0.1, 0.2, 0.5, 0.95, 1.2], )) assert samples == approx([0, 0.5, 1.0, 0.7, 0.7, 0.7, 0], abs=1e-3)
def _create(params): """Actually create a tone.""" # Construct a mono tone of the right length tone = pyfxr.tone( pitch=params.hz, sustain=max(0, params.duration - 0.2), wavetable=params.waveform.value, ) # NB. pygame assumes that the sound format of any buffer object matches # that of the current mixer settings. We use mixer.pre_init(22050, -16, 2) # which means that it is expecting 22kHz audio stereo, but we're feeding it # 44kHz mono - but, perhaps surprisingly, that works Ok. The extra samples # get interpreted as the second channel. # # If we change the mixer to 44kHz we'd need to convert to stereo here by # doubling samples. # # Really this is a mess and Pygame should support converting the format # of buffers (as it does for .wav files). snd = pygame.mixer.Sound(buffer=tone) snd.set_volume(params.volume) return snd
from math import sin import pyglet.media import pyfxr window = pyglet.window.Window() wt = pyfxr.Wavetable.from_function( lambda t: 0.75 * sin(t) + 0.25 * sin(3 * t + 0.5)) tone = pyfxr.tone(pitch='A4') #tone = pyfxr.pluck(duration=1.0, pitch='A4') source = pyglet.media.StaticSource(tone) @window.event def on_mouse_press(x, y, button, modifiers): source.play() pyglet.app.run()