Skip to content

Commit

Permalink
audio: Add wave shapes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gadgetoid committed Jul 31, 2024
1 parent 630dbe2 commit 22a2af6
Showing 1 changed file with 19 additions and 4 deletions.
23 changes: 19 additions & 4 deletions boards/PIMORONI_TINYFX/visible_libs/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class WavPlayer:
MODE_WAV = 0
MODE_TONE = 1

TONE_SINE = 0
TONE_SQUARE = 1
TONE_TRIANGLE = 2

# Default buffer length
SILENCE_BUFFER_LENGTH = 1024
WAV_BUFFER_LENGTH = 1024
Expand All @@ -107,6 +111,9 @@ def __init__(self, id, sck_pin, ws_pin, sd_pin, amp_enable=None, ibuf_len=INTERN
self.__ibuf_len = ibuf_len
self.__enable = None

# Manually tweak the tone amplitude for equal loudness of sine/square/triangle
self.__amplitude_scale = [1.0, 0.2, 0.5]

if amp_enable is not None:
self.__enable = Pin(amp_enable, Pin.OUT)

Expand Down Expand Up @@ -152,25 +159,33 @@ def play_wav(self, wav_file, loop=False):
state=WavPlayer.PLAY,
mode=WavPlayer.MODE_WAV)

def play_tone(self, frequency, amplitude):
def play_tone(self, frequency, amplitude, shape=TONE_SINE):
if frequency < 20.0 or frequency > 20_000:
raise ValueError("frequency out of range. Expected between 20Hz and 20KHz")

if amplitude < 0.0 or amplitude > 1.0:
raise ValueError("amplitude out of range. Expected 0.0 to 1.0")

amplitude *= self.__amplitude_scale[shape]

# Create a buffer containing the pure tone samples
samples_per_cycle = self.TONE_SAMPLE_RATE // frequency
sample_size_in_bytes = self.TONE_BITS_PER_SAMPLE // 8
samples = bytearray(self.TONE_FULL_WAVES * samples_per_cycle * sample_size_in_bytes)
range = pow(2, self.TONE_BITS_PER_SAMPLE) // 2
maximum = (pow(2, self.TONE_BITS_PER_SAMPLE) // 2 - 1) * amplitude

format = "<h" if self.TONE_BITS_PER_SAMPLE == 16 else "<l"

# Populate the buffer with multiple cycles to avoid it completing too quickly and causing drop outs
for i in range(samples_per_cycle * self.TONE_FULL_WAVES):
sample = int((range - 1) * (math.sin(2 * math.pi * i / samples_per_cycle)) * amplitude)
struct.pack_into(format, samples, i * sample_size_in_bytes, sample)
if shape == self.TONE_TRIANGLE:
sample = (i % samples_per_cycle) - (samples_per_cycle // 2)
sample /= samples_per_cycle
if shape == self.TONE_SINE:
sample = math.sin(2 * math.pi * i / samples_per_cycle)
if shape == self.TONE_SQUARE:
sample = 1 if (i % samples_per_cycle) < (samples_per_cycle // 2) else -1
struct.pack_into(format, samples, i * sample_size_in_bytes, int(sample * maximum))

# Are we not already playing tones?
if not (self.__mode == WavPlayer.MODE_TONE and (self.__state == WavPlayer.PLAY or self.__state == WavPlayer.PAUSE)):
Expand Down

0 comments on commit 22a2af6

Please sign in to comment.