2727"""
2828
2929import os
30+ import time
3031
3132import adafruit_sdcard
3233import 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:
297342for 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