# pico_synth_sandbox/py
# 2023 Cooper Dalrymple - me@dcdalrymple.com
# GPL v3 License
from pico_synth_sandbox import normalize
import os, random, gc
import ulab.numpy as numpy
import adafruit_wave
[docs]def get_samples():
"""Retrieve the number of samples in a waveform as defined by `WAVE_SAMPLES` in the settings.toml file.
:return: waveform buffer size
:rtype: int
"""
return os.getenv("WAVE_SAMPLES", 256)
[docs]def get_amplitude():
"""Retrieve the maximum level peak-to-peak (+/-) of waveform sample values as defined by `WAVE_AMPLITUDE` in the settings.toml file.
:return: waveform amplitude level
:rtype: int
"""
return os.getenv("WAVE_AMPLITUDE", 12000)
_saw = None
[docs]def get_saw():
"""Generate a decrementing sawtooth
:return: waveform
:rtype: numpy array
"""
global _saw
if _saw is None:
_saw = numpy.linspace(get_amplitude(), -get_amplitude(), num=get_samples(), dtype=numpy.int16)
return _saw
def _get_sine(offset=0.0):
return numpy.array(numpy.sin(numpy.linspace(offset*numpy.pi, (2+offset)*numpy.pi, get_samples(), endpoint=False)) * get_amplitude(), dtype=numpy.int16)
_sine = None
[docs]def get_sine():
"""Generate a sine
:return: waveform
:rtype: numpy array
"""
global _sine
if _sine is None:
_sine = _get_sine()
return _sine
_offset_sine = None
[docs]def get_offset_sine():
"""Generate a sine waveform offset by a quarter period (PI/2).
:return: waveform
:rtype: numpy array
"""
global _offset_sine
if _offset_sine is None:
_offset_sine = _get_sine(0.5)
return _offset_sine
_square = None
[docs]def get_square():
"""Generate a square
:return: waveform
:rtype: numpy array
"""
global _square
if _square is None:
_square = numpy.concatenate((numpy.ones(get_samples()//2, dtype=numpy.int16)*get_amplitude(),numpy.ones(get_samples()//2, dtype=numpy.int16)*-get_amplitude()))
return _square
_triangle = None
[docs]def get_triangle():
"""Generate a triangle
:return: waveform
:rtype: numpy array
"""
global _triangle
if _triangle is None:
_triangle = numpy.concatenate((
numpy.linspace(-get_amplitude(), get_amplitude(), num=get_samples()//2, dtype=numpy.int16),
numpy.linspace(get_amplitude(), -get_amplitude(), num=get_samples()//2, dtype=numpy.int16)
))
return _triangle
_noise = None
[docs]def get_noise():
"""Generate a white (random) noise
:return: waveform
:rtype: numpy array
"""
global _noise
if _noise is None:
_noise = numpy.array([random.randint(-get_amplitude(), get_amplitude()) for i in range(get_samples())], dtype=numpy.int16)
return _noise
_sine_noise = None
[docs]def get_sine_noise():
"""Generate a sine waveform with white noise added. Useful for percussion synthesis.
:return: waveform
:rtype: numpy array
"""
global _sine_noise, _sine, _noise
if _sine_noise is None:
get_sine()
get_noise()
_sine_noise = numpy.array([int(max(min(_sine[i] + (_noise[i]/2.0), get_amplitude()), -get_amplitude())) for i in range(get_samples())], dtype=numpy.int16)
return _sine_noise
_offset_sine_noise = None
[docs]def get_offset_sine_noise():
"""Generate a sine waveform offset by a quarter period (PI/2) with white noise added. Useful for percussion synthesis.
:return: waveform
:rtype: numpy array
"""
global _offset_sine_noise, _offset_sine, _noise
if _offset_sine_noise is None:
get_offset_sine()
get_noise()
_offset_sine_noise = numpy.array([int(max(min(_offset_sine[i] + (_noise[i]/2.0), get_amplitude()), -get_amplitude())) for i in range(get_samples())], dtype=numpy.int16)
return _offset_sine_noise
def load_from_file(filepath, max_samples=4096):
data = None
sample_rate = 0
with adafruit_wave.open(filepath, "rb") as wave:
if wave.getsampwidth() != 2 or wave.getnchannels() > 2:
return False
sample_rate = wave.getframerate()
# Read sample and convert to numpy
frames = min(wave.getnframes(), max_samples)
data = list(memoryview(wave.readframes(frames)).cast('h'))
if wave.getnchannels() == 2: # Filter out right channel
data = [i for i in range(0, frames, 2)]
data = numpy.array(data, dtype=numpy.int16)
# Normalize volume
data = normalize(data)
gc.collect()
return data, sample_rate