2727"""
2828
2929import os
30+ import time
3031
3132import adafruit_sdcard
3233import adafruit_tlv320
@@ -135,15 +136,23 @@ 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.
140143
141144 Attributes:
142145 neopixels (NeoPxiels): The NeoPixels on the Fruit Jam board.
143146 See https://circuitpython.readthedocs.io/projects/neopixel/en/latest/api.html
144147 """
145148
146- def __init__ (self , audio_output = "headphone" , safe_volume_limit = 12 ):
149+ def __init__ (
150+ self ,
151+ audio_output : str = "headphone" ,
152+ safe_volume_limit : int = 12 ,
153+ sample_rate : int = 11025 ,
154+ bit_depth : int = 16 ,
155+ ):
147156 self .neopixels = NeoPixel (board .NEOPIXEL , 5 )
148157
149158 self ._buttons = []
@@ -154,19 +163,24 @@ def __init__(self, audio_output="headphone", safe_volume_limit=12):
154163 self ._buttons .append (switch )
155164
156165 i2c = board .I2C ()
157- self ._dac = adafruit_tlv320 .TLV320DAC3100 (i2c )
166+ while not i2c .try_lock ():
167+ time .sleep (0.01 )
168+ self ._dac_present = 0x18 in i2c .scan ()
158169
159- # set sample rate & bit depth
160- self ._dac .configure_clocks (sample_rate = 11030 , bit_depth = 16 )
170+ if self ._dac_present :
171+ self ._dac = adafruit_tlv320 .TLV320DAC3100 (i2c )
172+
173+ # set sample rate & bit depth
174+ self ._dac .configure_clocks (sample_rate = sample_rate , bit_depth = bit_depth )
175+
176+ self ._audio = audiobusio .I2SOut (board .I2S_BCLK , board .I2S_WS , board .I2S_DIN )
161177
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 )
165178 if safe_volume_limit < 1 or safe_volume_limit > 20 :
166179 raise ValueError ("safe_volume_limit must be between 1 and 20" )
167180 self .safe_volume_limit = safe_volume_limit
168- self ._volume = 7
169- self ._apply_volume ()
181+
182+ self .audio_output = audio_output
183+ self ._apply_volume (7 )
170184
171185 self ._sd_mounted = False
172186 sd_pins_in_use = False
@@ -227,14 +241,18 @@ def any_button_pressed(self) -> bool:
227241 return True in [not button .value for (i , button ) in enumerate (self ._buttons )]
228242
229243 @property
230- def dac (self ):
231- return self ._dac
244+ def dac_present (self ) -> bool :
245+ return self ._dac_present
246+
247+ @property
248+ def dac (self ) -> adafruit_tlv320 .TLV320DAC3100 | None :
249+ return self ._dac if self ._dac_present else None
232250
233251 @property
234- def audio (self ):
235- return self ._audio
252+ def audio (self ) -> audiobusio . I2SOut | None :
253+ return self ._audio if self . _dac_present else None
236254
237- def sd_check (self ):
255+ def sd_check (self ) -> bool :
238256 return self ._sd_mounted
239257
240258 def play_file (self , file_name , wait_to_finish = True ):
@@ -244,34 +262,36 @@ def play_file(self, file_name, wait_to_finish=True):
244262 :param bool wait_to_finish: flag to determine if this is a blocking call
245263
246264 """
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 ()
265+ if self . _dac_present :
266+ # can't use `with` because we need wavefile to remain open after return
267+ self .wavfile = open (file_name , "rb" )
268+ wavedata = audiocore .WaveFile (self .wavfile )
269+ self .audio .play (wavedata )
270+ if not wait_to_finish :
271+ return
272+ while self .audio .playing :
273+ pass
274+ self .wavfile .close ()
257275
258276 def play_mp3_file (self , filename ):
259- if self ._mp3_decoder is None :
260- from audiomp3 import MP3Decoder # noqa: PLC0415, import outside top-level
277+ if self ._dac_present :
278+ if self ._mp3_decoder is None :
279+ from audiomp3 import MP3Decoder # noqa: PLC0415, import outside top-level
261280
262- self ._mp3_decoder = MP3Decoder (filename )
263- else :
264- self ._mp3_decoder .open (filename )
281+ self ._mp3_decoder = MP3Decoder (filename )
282+ else :
283+ self ._mp3_decoder .open (filename )
265284
266- self .audio .play (self ._mp3_decoder )
267- while self .audio .playing :
268- pass
285+ self .audio .play (self ._mp3_decoder )
286+ while self .audio .playing :
287+ pass
269288
270289 def stop_play (self ):
271290 """Stops playing a wav file."""
272- self .audio .stop ()
273- if self .wavfile is not None :
274- self .wavfile .close ()
291+ if self ._dac_present :
292+ self .audio .stop ()
293+ if self .wavfile is not None :
294+ self .wavfile .close ()
275295
276296 @property
277297 def volume (self ) -> int :
@@ -297,8 +317,7 @@ def volume(self, volume_level: int) -> None:
297317for the safe_volume_limit with the constructor or property."""
298318 )
299319
300- self ._volume = volume_level
301- self ._apply_volume ()
320+ self ._apply_volume (volume_level )
302321
303322 @property
304323 def audio_output (self ) -> str :
@@ -311,22 +330,26 @@ def audio_output(self) -> str:
311330 @audio_output .setter
312331 def audio_output (self , audio_output : str ) -> None :
313332 """
314-
315333 :param audio_output: The audio interface to use 'speaker' or 'headphone'.
316334 :return: None
317335 """
318336 if audio_output == "headphone" :
319- self ._dac .headphone_output = True
320- self ._dac .speaker_output = False
337+ if self ._dac_present :
338+ self ._dac .headphone_output = True
339+ self ._dac .speaker_output = False
321340 elif audio_output == "speaker" :
322- self ._dac .headphone_output = False
323- self ._dac .speaker_output = True
341+ if self ._dac_present :
342+ self ._dac .headphone_output = False
343+ self ._dac .speaker_output = True
324344 else :
325345 raise ValueError ("audio_output must be either 'headphone' or 'speaker'" )
346+ self ._audio_output = audio_output
326347
327- def _apply_volume (self ) -> None :
348+ def _apply_volume (self , volume_level : int ) -> None :
328349 """
329350 Map the basic volume level to a db value and set it on the DAC.
330351 """
331- db_val = map_range (self ._volume , 1 , 20 , - 63 , 23 )
332- self ._dac .dac_volume = db_val
352+ self ._volume = volume_level
353+ if self ._dac_present :
354+ db_val = map_range (self ._volume , 1 , 20 , - 63 , 23 )
355+ self ._dac .dac_volume = db_val
0 commit comments