Merge pull request #13 from FoamyGuy/volume_api
Some checks failed
Build CI / test (push) Has been cancelled

Volume and output interface API
This commit is contained in:
foamyguy 2025-08-28 12:13:07 -05:00 committed by GitHub
commit 8ee337c686
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 110 additions and 25 deletions

View file

@ -194,6 +194,8 @@ class FruitJam(PortalBase):
self.sd_check = self.peripherals.sd_check
self.play_file = self.peripherals.play_file
self.stop_play = self.peripherals.stop_play
self.volume = self.peripherals.volume
self.audio_output = self.peripherals.audio_output
self.image_converter_url = self.network.image_converter_url
self.wget = self.network.wget

View file

@ -40,6 +40,7 @@ import framebufferio
import picodvi
import storage
import supervisor
from adafruit_simplemath import map_range
from digitalio import DigitalInOut, Direction, Pull
from neopixel import NeoPixel
@ -133,13 +134,16 @@ def get_display_config():
class Peripherals:
"""Peripherals Helper Class for the FruitJam Library
:param audio_output: The audio output interface to use 'speaker' or 'headphone'
:param safe_volume_limit: The maximum volume allowed for the audio output. Default is 15
Using higher values can damage some speakers, change at your own risk.
Attributes:
neopixels (NeoPxiels): The NeoPixels on the Fruit Jam board.
See https://circuitpython.readthedocs.io/projects/neopixel/en/latest/api.html
"""
def __init__(self):
def __init__(self, audio_output="headphone", safe_volume_limit=12):
self.neopixels = NeoPixel(board.NEOPIXEL, 5)
self._buttons = []
@ -155,11 +159,14 @@ class Peripherals:
# set sample rate & bit depth
self._dac.configure_clocks(sample_rate=11030, bit_depth=16)
# use headphones
self._dac.headphone_output = True
self._dac.headphone_volume = -15 # dB
self._audio_output = audio_output
self.audio_output = audio_output
self._audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
if safe_volume_limit < 1 or safe_volume_limit > 20:
raise ValueError("safe_volume_limit must be between 1 and 20")
self.safe_volume_limit = safe_volume_limit
self._volume = 7
self._apply_volume()
self._sd_mounted = False
sd_pins_in_use = False
@ -252,3 +259,61 @@ class Peripherals:
self.audio.stop()
if self.wavfile is not None:
self.wavfile.close()
@property
def volume(self) -> int:
"""
The volume level of the Fruit Jam audio output. Valid values are 1-20.
"""
return self._volume
@volume.setter
def volume(self, volume_level: int) -> None:
"""
:param volume_level: new volume level 1-20
:return: None
"""
if not (1 <= volume_level <= 20):
raise ValueError("Volume level must be between 1 and 20")
if volume_level > self.safe_volume_limit:
raise ValueError(
f"""Volume level must be less than or equal to
safe_volume_limit: {self.safe_volume_limit}. Using higher values could damage speakers.
To override this limitation set a larger value than {self.safe_volume_limit}
for the safe_volume_limit with the constructor or property."""
)
self._volume = volume_level
self._apply_volume()
@property
def audio_output(self) -> str:
"""
The audio output interface. 'speaker' or 'headphone'
:return:
"""
return self._audio_output
@audio_output.setter
def audio_output(self, audio_output: str) -> None:
"""
:param audio_output: The audio interface to use 'speaker' or 'headphone'.
:return: None
"""
if audio_output == "headphone":
self._dac.headphone_output = True
self._dac.speaker_output = False
elif audio_output == "speaker":
self._dac.headphone_output = False
self._dac.speaker_output = True
else:
raise ValueError("audio_output must be either 'headphone' or 'speaker'")
def _apply_volume(self) -> None:
"""
Map the basic volume level to a db value and set it on the DAC.
"""
db_val = map_range(self._volume, 1, 20, -63, 23)
self._dac.dac_volume = db_val

View file

@ -5,21 +5,16 @@ import time
import adafruit_fruitjam
pobj = adafruit_fruitjam.peripherals.Peripherals()
dac = pobj.dac # use Fruit Jam's codec
# Route once for headphones
dac.headphone_output = True
dac.speaker_output = False
pobj = adafruit_fruitjam.peripherals.Peripherals(audio_output="headphone")
FILES = ["beep.wav", "dip.wav", "rise.wav"]
VOLUMES_DB = [12, 6, 0, -6, -12]
VOLUMES = [5, 7, 10, 11, 12]
while True:
print("\n=== Headphones Test ===")
for vol in VOLUMES_DB:
dac.dac_volume = vol
print(f"Headphones volume: {vol} dB")
for vol in VOLUMES:
pobj.volume = vol
print(f"Headphones volume: {vol}")
for f in FILES:
print(f" -> {f}")
pobj.play_file(f)

View file

@ -5,21 +5,16 @@ import time
import adafruit_fruitjam
pobj = adafruit_fruitjam.peripherals.Peripherals()
dac = pobj.dac # use Fruit Jam's codec
# Route once for speaker
dac.headphone_output = False
dac.speaker_output = True
pobj = adafruit_fruitjam.peripherals.Peripherals(audio_output="speaker")
FILES = ["beep.wav", "dip.wav", "rise.wav"]
VOLUMES_DB = [12, 6, 0, -6, -12]
VOLUMES = [5, 7, 10, 11, 12]
while True:
print("\n=== Speaker Test ===")
for vol in VOLUMES_DB:
dac.dac_volume = vol
print(f"Speaker volume: {vol} dB")
for vol in VOLUMES:
pobj.volume = vol
print(f"Speaker volume: {vol}")
for f in FILES:
print(f" -> {f}")
pobj.play_file(f)

View file

@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import synthio
import adafruit_fruitjam
pobj = adafruit_fruitjam.peripherals.Peripherals(audio_output="headphone")
synth = synthio.Synthesizer(sample_rate=44100)
pobj.audio.play(synth)
VOLUMES = [5, 7, 10, 11, 12]
C_major_scale = [60, 62, 64, 65, 67, 69, 71, 72, 71, 69, 67, 65, 64, 62, 60]
while True:
print("\n=== Synthio Test ===")
for vol in VOLUMES:
pobj.volume = vol
print(f"Volume: {vol}")
for note in C_major_scale:
synth.press(note)
time.sleep(0.1)
synth.release(note)
time.sleep(0.05)
time.sleep(1.0)

View file

@ -15,3 +15,4 @@ adafruit-circuitpython-display-text
adafruit-circuitpython-sd
adafruit-circuitpython-ntp
adafruit-circuitpython-connectionmanager
adafruit-circuitpython-simplemath