Skip to content

Commit 57fc5e8

Browse files
authored
Merge pull request #20 from relic-se/dac-settings
Check if dac is present and add parameters for sample rate and bit depth
2 parents e67239b + 9836665 commit 57fc5e8

File tree

1 file changed

+99
-48
lines changed

1 file changed

+99
-48
lines changed

adafruit_fruitjam/peripherals.py

Lines changed: 99 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"""
2828

2929
import os
30+
import time
3031

3132
import adafruit_sdcard
3233
import adafruit_tlv320
@@ -135,15 +136,25 @@ class Peripherals:
135136
"""Peripherals Helper Class for the FruitJam Library
136137
137138
:param audio_output: The audio output interface to use 'speaker' or 'headphone'
138-
:param safe_volume_limit: The maximum volume allowed for the audio output. Default is 15
139+
:param safe_volume_limit: The maximum volume allowed for the audio output. Default is 12.
139140
Using higher values can damage some speakers, change at your own risk.
141+
:param sample_rate: The sample rate to play back audio data in hertz. Default is 11025.
142+
:param bit_depth: The bits per sample of the audio data. Supports 8 and 16 bits. Default is 16.
143+
:param i2c: The I2C bus the audio DAC is connected to. Set as False to disable audio.
140144
141145
Attributes:
142146
neopixels (NeoPxiels): The NeoPixels on the Fruit Jam board.
143147
See https://circuitpython.readthedocs.io/projects/neopixel/en/latest/api.html
144148
"""
145149

146-
def __init__(self, audio_output="headphone", safe_volume_limit=12):
150+
def __init__( # noqa: PLR0913, PLR0912
151+
self,
152+
audio_output: str = "headphone",
153+
safe_volume_limit: int = 12,
154+
sample_rate: int = 11025,
155+
bit_depth: int = 16,
156+
i2c: busio.I2C = None,
157+
):
147158
self.neopixels = NeoPixel(board.NEOPIXEL, 5)
148159

149160
self._buttons = []
@@ -153,20 +164,35 @@ def __init__(self, audio_output="headphone", safe_volume_limit=12):
153164
switch.pull = Pull.UP
154165
self._buttons.append(switch)
155166

156-
i2c = board.I2C()
157-
self._dac = adafruit_tlv320.TLV320DAC3100(i2c)
167+
if i2c is None:
168+
i2c = board.I2C()
169+
if i2c is False:
170+
self._dac = None
171+
else:
172+
while not i2c.try_lock():
173+
time.sleep(0.01)
174+
dac_present = 0x18 in i2c.scan()
175+
i2c.unlock()
176+
177+
if dac_present:
178+
self._dac = adafruit_tlv320.TLV320DAC3100(i2c)
179+
self._dac.configure_clocks( # set sample rate & bit depth
180+
sample_rate=sample_rate, bit_depth=bit_depth
181+
)
182+
else:
183+
self._dac = None
158184

159-
# set sample rate & bit depth
160-
self._dac.configure_clocks(sample_rate=11030, bit_depth=16)
185+
if "I2S_BCLK" in dir(board) and "I2S_WS" in dir(board) and "I2S_DIN" in dir(board):
186+
self._audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
187+
else:
188+
self._audio = None
161189

162-
self._audio_output = audio_output
163-
self.audio_output = audio_output
164-
self._audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
165190
if safe_volume_limit < 1 or safe_volume_limit > 20:
166191
raise ValueError("safe_volume_limit must be between 1 and 20")
167192
self.safe_volume_limit = safe_volume_limit
168-
self._volume = 7
169-
self._apply_volume()
193+
194+
self.audio_output = audio_output
195+
self._apply_volume(7)
170196

171197
self._sd_mounted = False
172198
sd_pins_in_use = False
@@ -227,14 +253,31 @@ def any_button_pressed(self) -> bool:
227253
return True in [not button.value for (i, button) in enumerate(self._buttons)]
228254

229255
@property
230-
def dac(self):
256+
def dac(self) -> adafruit_tlv320.TLV320DAC3100:
231257
return self._dac
232258

259+
@dac.setter
260+
def dac(self, value: adafruit_tlv320.TLV320DAC3100) -> None:
261+
if self._dac is not None:
262+
self._dac.reset()
263+
del self._dac
264+
self._dac = value
265+
self._apply_audio_output()
266+
self._apply_volume()
267+
233268
@property
234-
def audio(self):
269+
def audio(self) -> audiobusio.I2SOut:
235270
return self._audio
236271

237-
def sd_check(self):
272+
@audio.setter
273+
def audio(self, value: audiobusio.I2SOut) -> None:
274+
if self._audio is not None:
275+
self._audio.stop()
276+
self._audio.deinit()
277+
del self._audio
278+
self._audio = value
279+
280+
def sd_check(self) -> bool:
238281
return self._sd_mounted
239282

240283
def play_file(self, file_name, wait_to_finish=True):
@@ -244,34 +287,36 @@ def play_file(self, file_name, wait_to_finish=True):
244287
:param bool wait_to_finish: flag to determine if this is a blocking call
245288
246289
"""
247-
248-
# can't use `with` because we need wavefile to remain open after return
249-
self.wavfile = open(file_name, "rb")
250-
wavedata = audiocore.WaveFile(self.wavfile)
251-
self.audio.play(wavedata)
252-
if not wait_to_finish:
253-
return
254-
while self.audio.playing:
255-
pass
256-
self.wavfile.close()
290+
if self._audio is not None:
291+
# can't use `with` because we need wavefile to remain open after return
292+
self.wavfile = open(file_name, "rb")
293+
wavedata = audiocore.WaveFile(self.wavfile)
294+
self._audio.play(wavedata)
295+
if not wait_to_finish:
296+
return
297+
while self._audio.playing:
298+
pass
299+
self.wavfile.close()
257300

258301
def play_mp3_file(self, filename):
259-
if self._mp3_decoder is None:
260-
from audiomp3 import MP3Decoder # noqa: PLC0415, import outside top-level
302+
if self._audio is not None:
303+
if self._mp3_decoder is None:
304+
from audiomp3 import MP3Decoder # noqa: PLC0415, import outside top-level
261305

262-
self._mp3_decoder = MP3Decoder(filename)
263-
else:
264-
self._mp3_decoder.open(filename)
306+
self._mp3_decoder = MP3Decoder(filename)
307+
else:
308+
self._mp3_decoder.open(filename)
265309

266-
self.audio.play(self._mp3_decoder)
267-
while self.audio.playing:
268-
pass
310+
self._audio.play(self._mp3_decoder)
311+
while self._audio.playing:
312+
pass
269313

270314
def stop_play(self):
271315
"""Stops playing a wav file."""
272-
self.audio.stop()
273-
if self.wavfile is not None:
274-
self.wavfile.close()
316+
if self._audio is not None:
317+
self._audio.stop()
318+
if self.wavfile is not None:
319+
self.wavfile.close()
275320

276321
@property
277322
def volume(self) -> int:
@@ -297,8 +342,7 @@ def volume(self, volume_level: int) -> None:
297342
for the safe_volume_limit with the constructor or property."""
298343
)
299344

300-
self._volume = volume_level
301-
self._apply_volume()
345+
self._apply_volume(volume_level)
302346

303347
@property
304348
def audio_output(self) -> str:
@@ -311,22 +355,29 @@ def audio_output(self) -> str:
311355
@audio_output.setter
312356
def audio_output(self, audio_output: str) -> None:
313357
"""
314-
315358
:param audio_output: The audio interface to use 'speaker' or 'headphone'.
316359
:return: None
317360
"""
318-
if audio_output == "headphone":
319-
self._dac.headphone_output = True
320-
self._dac.speaker_output = False
321-
elif audio_output == "speaker":
322-
self._dac.headphone_output = False
323-
self._dac.speaker_output = True
324-
else:
361+
if audio_output not in {"headphone", "speaker"}:
325362
raise ValueError("audio_output must be either 'headphone' or 'speaker'")
363+
self._apply_audio_output(audio_output)
364+
365+
def _apply_audio_output(self, audio_output: str = None) -> None:
366+
"""
367+
Assign the output of the dac based on the desired setting.
368+
"""
369+
if audio_output is not None:
370+
self._audio_output = audio_output
371+
if self._dac is not None:
372+
self._dac.headphone_output = self._audio_output == "headphone"
373+
self._dac.speaker_output = self._audio_output == "speaker"
326374

327-
def _apply_volume(self) -> None:
375+
def _apply_volume(self, volume_level: int = None) -> None:
328376
"""
329377
Map the basic volume level to a db value and set it on the DAC.
330378
"""
331-
db_val = map_range(self._volume, 1, 20, -63, 23)
332-
self._dac.dac_volume = db_val
379+
if volume_level is not None:
380+
self._volume = volume_level
381+
if self._dac is not None:
382+
db_val = map_range(self._volume, 1, 20, -63, 23)
383+
self._dac.dac_volume = db_val

0 commit comments

Comments
 (0)