Migrate MCP and utilities to CircuitPython, including color GIF and volume fixes
This commit is contained in:
@@ -5,13 +5,17 @@ playback using CircuitPython's audiomp3 + audiobusio stack.
|
||||
"""
|
||||
|
||||
import time
|
||||
import array
|
||||
import math
|
||||
|
||||
import audiobusio
|
||||
import audiomp3
|
||||
import audiocore
|
||||
import digitalio
|
||||
import pwmio
|
||||
|
||||
|
||||
|
||||
class ES8311:
|
||||
ADDR = 0x18
|
||||
|
||||
@@ -63,20 +67,29 @@ class ES8311:
|
||||
|
||||
def set_volume(self, volume):
|
||||
volume = max(0, min(100, int(volume)))
|
||||
self._write(0x32, int(volume * 255 / 100))
|
||||
if volume <= 0:
|
||||
reg_val = 0
|
||||
elif volume <= 50:
|
||||
# 1..50 maps to 0..191 (0xBF = 0dB)
|
||||
reg_val = int((volume / 50.0) * 191)
|
||||
else:
|
||||
# 51..100 maps to 192..255 (0xFF = +32dB)
|
||||
reg_val = 191 + int(((volume - 50) / 50.0) * (255 - 191))
|
||||
self._write(0x32, reg_val)
|
||||
|
||||
|
||||
class BoardAudio:
|
||||
def __init__(self, i2c, *, bit_clock, word_select, data, mclk, amp):
|
||||
def __init__(self, i2c, *, bit_clock, word_select, data, mclk, amp, amp_active_level=1):
|
||||
self.i2c = i2c
|
||||
self.bit_clock_pin = bit_clock
|
||||
self.word_select_pin = word_select
|
||||
self.data_pin = data
|
||||
self.mclk_pin = mclk
|
||||
self.amp_pin = amp
|
||||
self.amp_active_level = amp_active_level
|
||||
self._mclk = None
|
||||
self._amp = digitalio.DigitalInOut(amp)
|
||||
self._amp.switch_to_output(value=False)
|
||||
self._amp.switch_to_output(value=not amp_active_level)
|
||||
self.codec = ES8311(i2c)
|
||||
|
||||
def start_mclk(self):
|
||||
@@ -88,7 +101,7 @@ class BoardAudio:
|
||||
frequency=12288000,
|
||||
duty_cycle=32768,
|
||||
variable_frequency=False,
|
||||
)
|
||||
)
|
||||
|
||||
def stop_mclk(self):
|
||||
if self._mclk is not None:
|
||||
@@ -106,25 +119,92 @@ class BoardAudio:
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
decoder = audiomp3.MP3Decoder(f)
|
||||
# Use decoder-derived rate if available, else fall back.
|
||||
sample_rate = getattr(decoder, "sample_rate", 44100)
|
||||
audio = audiobusio.I2SOut(
|
||||
bit_clock=self.bit_clock_pin,
|
||||
word_select=self.word_select_pin,
|
||||
data=self.data_pin,
|
||||
)
|
||||
self._amp.value = True
|
||||
self._amp.value = self.amp_active_level
|
||||
audio.play(decoder)
|
||||
while audio.playing:
|
||||
time.sleep(0.05)
|
||||
return True
|
||||
finally:
|
||||
self._amp.value = False
|
||||
self._amp.value = not self.amp_active_level
|
||||
if audio is not None:
|
||||
audio.deinit()
|
||||
if decoder is not None:
|
||||
decoder.deinit()
|
||||
if f is not None:
|
||||
f.close()
|
||||
# Leave MCLK running for repeated playback; call stop_mclk() before
|
||||
# deep sleep if power matters.
|
||||
|
||||
def play_wav(self, filename, volume=60):
|
||||
"""Play a WAV file from CIRCUITPY storage over the onboard speaker."""
|
||||
self.start_mclk()
|
||||
self.codec.init()
|
||||
self.codec.set_volume(volume)
|
||||
wav = None
|
||||
audio = None
|
||||
f = None
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
wav = audiocore.WaveFile(f)
|
||||
audio = audiobusio.I2SOut(
|
||||
bit_clock=self.bit_clock_pin,
|
||||
word_select=self.word_select_pin,
|
||||
data=self.data_pin,
|
||||
)
|
||||
self._amp.value = self.amp_active_level
|
||||
audio.play(wav)
|
||||
while audio.playing:
|
||||
time.sleep(0.05)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error playing WAV: {e}")
|
||||
return False
|
||||
finally:
|
||||
self._amp.value = not self.amp_active_level
|
||||
if audio is not None:
|
||||
audio.deinit()
|
||||
if wav is not None:
|
||||
wav.deinit()
|
||||
if f is not None:
|
||||
f.close()
|
||||
|
||||
def play_tone(self, frequency=440, duration_ms=1000, volume=50):
|
||||
"""Generate and play a pure sine wave tone on the speaker."""
|
||||
self.start_mclk()
|
||||
self.codec.init()
|
||||
self.codec.set_volume(volume)
|
||||
|
||||
sample_rate = 16000
|
||||
length = int(sample_rate / frequency)
|
||||
if length < 4:
|
||||
length = 4
|
||||
|
||||
sine_wave = array.array("h", [0] * length)
|
||||
for i in range(length):
|
||||
sine_wave[i] = int(math.sin(2 * math.pi * i / length) * 32767)
|
||||
|
||||
sample = audiocore.RawSample(sine_wave, sample_rate=sample_rate)
|
||||
audio = None
|
||||
try:
|
||||
audio = audiobusio.I2SOut(
|
||||
bit_clock=self.bit_clock_pin,
|
||||
word_select=self.word_select_pin,
|
||||
data=self.data_pin,
|
||||
)
|
||||
self._amp.value = self.amp_active_level
|
||||
audio.play(sample, loop=True)
|
||||
time.sleep(duration_ms / 1000.0)
|
||||
audio.stop()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error playing tone: {e}")
|
||||
return False
|
||||
finally:
|
||||
self._amp.value = not self.amp_active_level
|
||||
if audio is not None:
|
||||
audio.deinit()
|
||||
sample.deinit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user