"""CircuitPython audio helpers for Waveshare ESP32-S3-RLCD-4.2. Ports the ES8311 setup sequence from MicroPython audio_util.py and adds MP3 playback using CircuitPython's audiomp3 + audiobusio stack. """ import time import audiobusio import audiomp3 import digitalio import pwmio class ES8311: ADDR = 0x18 def __init__(self, i2c): self.i2c = i2c def _write(self, register, value): # CircuitPython I2CDevice is not used to keep this dependency-free. while not self.i2c.try_lock(): pass try: self.i2c.writeto(self.ADDR, bytes([register & 0xFF, value & 0xFF])) finally: self.i2c.unlock() def init(self, sample_rate=44100): """Initialize ES8311 for I2S DAC playback. The register sequence is the existing MicroPython project's known-good sequence. The original comments targeted 16 kHz WAV playback, but the I2S peripheral is configured with the MP3 decoder sample rate at play time. If MP3 output is silent, hardware-test this sequence first with a 16 kHz WAV/tone and then tune codec dividers for the MP3 sample rate. """ del sample_rate # Kept for API clarity; sequence currently fixed. self._write(0x00, 0x1F) time.sleep(0.01) self._write(0x00, 0x00) time.sleep(0.01) self._write(0x01, 0x3F) self._write(0x02, 0x48) self._write(0x03, 0x10) self._write(0x04, 0x20) self._write(0x05, 0x00) self._write(0x06, 0x03) self._write(0x07, 0x00) self._write(0x08, 0xFF) self._write(0x09, 0x0C) self._write(0x0A, 0x0C) self._write(0x0D, 0x01) self._write(0x0E, 0x02) self._write(0x12, 0x00) self._write(0x13, 0x10) self._write(0x1C, 0x6A) self._write(0x37, 0x08) self._write(0x32, 0xBF) self._write(0x31, 0x00) self._write(0x00, 0x80) def set_volume(self, volume): volume = max(0, min(100, int(volume))) self._write(0x32, int(volume * 255 / 100)) class BoardAudio: def __init__(self, i2c, *, bit_clock, word_select, data, mclk, amp): 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._mclk = None self._amp = digitalio.DigitalInOut(amp) self._amp.switch_to_output(value=False) self.codec = ES8311(i2c) def start_mclk(self): if self._mclk is None: # External MCLK for ES8311. CircuitPython I2SOut on ESP32-S3 does # not accept a main_clock parameter, so keep this independent. self._mclk = pwmio.PWMOut( self.mclk_pin, frequency=12288000, duty_cycle=32768, variable_frequency=False, ) def stop_mclk(self): if self._mclk is not None: self._mclk.deinit() self._mclk = None def play_mp3(self, filename, volume=60): """Play an MP3 file from CIRCUITPY storage over the onboard speaker.""" self.start_mclk() self.codec.init() self.codec.set_volume(volume) decoder = None audio = None f = None 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 audio.play(decoder) while audio.playing: time.sleep(0.05) return True finally: self._amp.value = False 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.