4040import picodvi
4141import storage
4242import supervisor
43+ from adafruit_simplemath import map_range
4344from digitalio import DigitalInOut , Direction , Pull
4445from neopixel import NeoPixel
4546
@@ -133,13 +134,16 @@ def get_display_config():
133134class Peripherals :
134135 """Peripherals Helper Class for the FruitJam Library
135136
137+ :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+ Using higher values can damage some speakers, change at your own risk.
136140
137141 Attributes:
138142 neopixels (NeoPxiels): The NeoPixels on the Fruit Jam board.
139143 See https://circuitpython.readthedocs.io/projects/neopixel/en/latest/api.html
140144 """
141145
142- def __init__ (self ):
146+ def __init__ (self , audio_output = "headphone" , safe_volume_limit = 12 ):
143147 self .neopixels = NeoPixel (board .NEOPIXEL , 5 )
144148
145149 self ._buttons = []
@@ -155,11 +159,14 @@ def __init__(self):
155159 # set sample rate & bit depth
156160 self ._dac .configure_clocks (sample_rate = 11030 , bit_depth = 16 )
157161
158- # use headphones
159- self ._dac .headphone_output = True
160- self ._dac .headphone_volume = - 15 # dB
161-
162+ self ._audio_output = audio_output
163+ self .audio_output = audio_output
162164 self ._audio = audiobusio .I2SOut (board .I2S_BCLK , board .I2S_WS , board .I2S_DIN )
165+ if safe_volume_limit < 1 or safe_volume_limit > 20 :
166+ raise ValueError ("safe_volume_limit must be between 1 and 20" )
167+ self .safe_volume_limit = safe_volume_limit
168+ self ._volume = 7
169+ self ._apply_volume ()
163170
164171 self ._sd_mounted = False
165172 sd_pins_in_use = False
@@ -252,3 +259,61 @@ def stop_play(self):
252259 self .audio .stop ()
253260 if self .wavfile is not None :
254261 self .wavfile .close ()
262+
263+ @property
264+ def volume (self ) -> int :
265+ """
266+ The volume level of the Fruit Jam audio output. Valid values are 1-20.
267+ """
268+ return self ._volume
269+
270+ @volume .setter
271+ def volume (self , volume_level : int ) -> None :
272+ """
273+ :param volume_level: new volume level 1-20
274+ :return: None
275+ """
276+ if not (1 <= volume_level <= 20 ):
277+ raise ValueError ("Volume level must be between 1 and 20" )
278+
279+ if volume_level > self .safe_volume_limit :
280+ raise ValueError (
281+ f"""Volume level must be less than or equal to
282+ safe_volume_limit: { self .safe_volume_limit } . Using higher values could damage speakers.
283+ To override this limitation set a larger value than { self .safe_volume_limit }
284+ for the safe_volume_limit with the constructor or property."""
285+ )
286+
287+ self ._volume = volume_level
288+ self ._apply_volume ()
289+
290+ @property
291+ def audio_output (self ) -> str :
292+ """
293+ The audio output interface. 'speaker' or 'headphone'
294+ :return:
295+ """
296+ return self ._audio_output
297+
298+ @audio_output .setter
299+ def audio_output (self , audio_output : str ) -> None :
300+ """
301+
302+ :param audio_output: The audio interface to use 'speaker' or 'headphone'.
303+ :return: None
304+ """
305+ if audio_output == "headphone" :
306+ self ._dac .headphone_output = True
307+ self ._dac .speaker_output = False
308+ elif audio_output == "speaker" :
309+ self ._dac .headphone_output = False
310+ self ._dac .speaker_output = True
311+ else :
312+ raise ValueError ("audio_output must be either 'headphone' or 'speaker'" )
313+
314+ def _apply_volume (self ) -> None :
315+ """
316+ Map the basic volume level to a db value and set it on the DAC.
317+ """
318+ db_val = map_range (self ._volume , 1 , 20 , - 63 , 23 )
319+ self ._dac .dac_volume = db_val
0 commit comments