You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
AudioFlinger: optimize WAV volume scaling
This commit is contained in:
@@ -18,7 +18,7 @@ _device_type = DEVICE_NULL
|
||||
_i2s_pins = None # I2S pin configuration dict (created per-stream)
|
||||
_buzzer_instance = None # PWM buzzer instance
|
||||
_current_stream = None # Currently playing stream
|
||||
_volume = 70 # System volume (0-100)
|
||||
_volume = 25 # System volume (0-100)
|
||||
_stream_lock = None # Thread lock for stream management
|
||||
|
||||
|
||||
@@ -290,6 +290,8 @@ def set_volume(volume):
|
||||
"""
|
||||
global _volume
|
||||
_volume = max(0, min(100, volume))
|
||||
if _current_stream:
|
||||
_current_stream.set_volume(_volume)
|
||||
|
||||
|
||||
def get_volume():
|
||||
|
||||
@@ -229,3 +229,6 @@ class RTTTLStream:
|
||||
# Ensure buzzer is off
|
||||
self.buzzer.duty_u16(0)
|
||||
self._is_playing = False
|
||||
|
||||
def set_volume(self, vol):
|
||||
self.volume = vol
|
||||
|
||||
@@ -28,6 +28,128 @@ def _scale_audio(buf: ptr8, num_bytes: int, scale_fixed: int):
|
||||
buf[i] = sample & 255
|
||||
buf[i + 1] = (sample >> 8) & 255
|
||||
|
||||
import micropython
|
||||
@micropython.viper
|
||||
def _scale_audio_optimized(buf: ptr8, num_bytes: int, scale_fixed: int):
|
||||
"""
|
||||
Very fast 16-bit volume scaling using only shifts + adds.
|
||||
- 100 % and above → no change
|
||||
- < ~12.5 % → pure right-shift (fastest)
|
||||
- otherwise → high-quality shift/add approximation
|
||||
"""
|
||||
if scale_fixed >= 32768: # 100 % or more
|
||||
return
|
||||
if scale_fixed <= 0: # muted
|
||||
for i in range(num_bytes):
|
||||
buf[i] = 0
|
||||
return
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# Very low volumes → simple right-shift (super cheap)
|
||||
# --------------------------------------------------------------
|
||||
if scale_fixed < 4096: # < ~12.5 %
|
||||
shift: int = 0
|
||||
tmp: int = 32768
|
||||
while tmp > scale_fixed:
|
||||
shift += 1
|
||||
tmp >>= 1
|
||||
for i in range(0, num_bytes, 2):
|
||||
lo: int = int(buf[i])
|
||||
hi: int = int(buf[i + 1])
|
||||
s: int = (hi << 8) | lo
|
||||
if hi & 128: # sign extend
|
||||
s -= 65536
|
||||
s >>= shift
|
||||
buf[i] = s & 255
|
||||
buf[i + 1] = (s >> 8) & 255
|
||||
return
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# Medium → high volumes: sample * scale_fixed // 32768
|
||||
# approximated with shifts + adds only
|
||||
# --------------------------------------------------------------
|
||||
# Build a 16-bit mask:
|
||||
# bit 0 → add (s >> 15)
|
||||
# bit 1 → add (s >> 14)
|
||||
# ...
|
||||
# bit 15 → add s (>> 0)
|
||||
mask: int = 0
|
||||
bit_value: int = 16384 # starts at 2^-1
|
||||
remaining: int = scale_fixed
|
||||
|
||||
shift_idx: int = 1 # corresponds to >>1
|
||||
while bit_value > 0:
|
||||
if remaining >= bit_value:
|
||||
mask |= (1 << (16 - shift_idx)) # correct bit position
|
||||
remaining -= bit_value
|
||||
bit_value >>= 1
|
||||
shift_idx += 1
|
||||
|
||||
# Apply the mask
|
||||
for i in range(0, num_bytes, 2):
|
||||
lo: int = int(buf[i])
|
||||
hi: int = int(buf[i + 1])
|
||||
s: int = (hi << 8) | lo
|
||||
if hi & 128:
|
||||
s -= 65536
|
||||
|
||||
result: int = 0
|
||||
if mask & 0x8000: result += s # >>0
|
||||
if mask & 0x4000: result += (s >> 1)
|
||||
if mask & 0x2000: result += (s >> 2)
|
||||
if mask & 0x1000: result += (s >> 3)
|
||||
if mask & 0x0800: result += (s >> 4)
|
||||
if mask & 0x0400: result += (s >> 5)
|
||||
if mask & 0x0200: result += (s >> 6)
|
||||
if mask & 0x0100: result += (s >> 7)
|
||||
if mask & 0x0080: result += (s >> 8)
|
||||
if mask & 0x0040: result += (s >> 9)
|
||||
if mask & 0x0020: result += (s >>10)
|
||||
if mask & 0x0010: result += (s >>11)
|
||||
if mask & 0x0008: result += (s >>12)
|
||||
if mask & 0x0004: result += (s >>13)
|
||||
if mask & 0x0002: result += (s >>14)
|
||||
if mask & 0x0001: result += (s >>15)
|
||||
|
||||
# Clamp to 16-bit signed range
|
||||
if result > 32767:
|
||||
result = 32767
|
||||
elif result < -32768:
|
||||
result = -32768
|
||||
|
||||
buf[i] = result & 255
|
||||
buf[i + 1] = (result >> 8) & 255
|
||||
|
||||
import micropython
|
||||
@micropython.viper
|
||||
def _scale_audio_rough(buf: ptr8, num_bytes: int, scale_fixed: int):
|
||||
"""Rough volume scaling for 16-bit audio samples using right shifts for performance."""
|
||||
if scale_fixed >= 32768:
|
||||
return
|
||||
|
||||
# Determine the shift amount
|
||||
shift: int = 0
|
||||
threshold: int = 32768
|
||||
while shift < 16 and scale_fixed < threshold:
|
||||
shift += 1
|
||||
threshold >>= 1
|
||||
|
||||
# If shift is 16 or more, set buffer to zero (volume too low)
|
||||
if shift >= 16:
|
||||
for i in range(num_bytes):
|
||||
buf[i] = 0
|
||||
return
|
||||
|
||||
# Apply right shift to each 16-bit sample
|
||||
for i in range(0, num_bytes, 2):
|
||||
lo: int = int(buf[i])
|
||||
hi: int = int(buf[i + 1])
|
||||
sample: int = (hi << 8) | lo
|
||||
if hi & 128:
|
||||
sample -= 65536
|
||||
sample >>= shift
|
||||
buf[i] = sample & 255
|
||||
buf[i + 1] = (sample >> 8) & 255
|
||||
|
||||
class WAVStream:
|
||||
"""
|
||||
@@ -251,7 +373,12 @@ class WAVStream:
|
||||
print(f"WAVStream: Playing {data_size} bytes (volume {self.volume}%)")
|
||||
f.seek(data_start)
|
||||
|
||||
chunk_size = 4096
|
||||
# smaller chunk size means less jerks but buffer can run empty
|
||||
# at 22050 Hz, 16-bit, 2-ch, 4096/4 = 1024 samples / 22050 = 46ms
|
||||
# 4096 => audio stutters during quasibird
|
||||
# 8192 => no audio stutters and quasibird runs at ~16 fps => good compromise!
|
||||
# 16384 => no audio stutters during quasibird but low framerate (~8fps)
|
||||
chunk_size = 4096*2
|
||||
bytes_per_original_sample = (bits_per_sample // 8) * channels
|
||||
total_original = 0
|
||||
|
||||
@@ -287,7 +414,7 @@ class WAVStream:
|
||||
scale = self.volume / 100.0
|
||||
if scale < 1.0:
|
||||
scale_fixed = int(scale * 32768)
|
||||
_scale_audio(raw, len(raw), scale_fixed)
|
||||
_scale_audio_optimized(raw, len(raw), scale_fixed)
|
||||
|
||||
# 4. Output to I2S
|
||||
if self._i2s:
|
||||
@@ -313,3 +440,6 @@ class WAVStream:
|
||||
if self._i2s:
|
||||
self._i2s.deinit()
|
||||
self._i2s = None
|
||||
|
||||
def set_volume(self, vol):
|
||||
self.volume = vol
|
||||
|
||||
Reference in New Issue
Block a user