Skip to content

Commit b9e5f0d

Browse files
AudioFlinger: improve optimized audio scaling
1 parent 91127df commit b9e5f0d

File tree

3 files changed

+40
-85
lines changed

3 files changed

+40
-85
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
0.5.2
2+
=====
3+
- AudioFlinger: optimize WAV volume scaling
4+
15
0.5.1
26
=====
37
- Fri3d Camp 2024 Board: add startup light and sound

internal_filesystem/lib/mpos/audio/stream_wav.py

Lines changed: 34 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
# Ported from MusicPlayer's AudioPlayer class
44

55
import machine
6+
import micropython
67
import os
78
import time
89
import sys
910

1011
# Volume scaling function - Viper-optimized for ESP32 performance
1112
# NOTE: The line below is automatically commented out by build_mpos.sh during
1213
# Unix/macOS builds (cross-compiler doesn't support Viper), then uncommented after build.
13-
import micropython
1414
@micropython.viper
1515
def _scale_audio(buf: ptr8, num_bytes: int, scale_fixed: int):
1616
"""Fast volume scaling for 16-bit audio samples using Viper (ESP32 native code emitter)."""
@@ -28,99 +28,46 @@ def _scale_audio(buf: ptr8, num_bytes: int, scale_fixed: int):
2828
buf[i] = sample & 255
2929
buf[i + 1] = (sample >> 8) & 255
3030

31-
import micropython
3231
@micropython.viper
3332
def _scale_audio_optimized(buf: ptr8, num_bytes: int, scale_fixed: int):
34-
"""
35-
Very fast 16-bit volume scaling using only shifts + adds.
36-
- 100 % and above → no change
37-
- < ~12.5 % → pure right-shift (fastest)
38-
- otherwise → high-quality shift/add approximation
39-
"""
40-
if scale_fixed >= 32768: # 100 % or more
33+
if scale_fixed >= 32768:
4134
return
42-
if scale_fixed <= 0: # muted
35+
if scale_fixed <= 0:
4336
for i in range(num_bytes):
4437
buf[i] = 0
4538
return
4639

47-
# --------------------------------------------------------------
48-
# Very low volumes → simple right-shift (super cheap)
49-
# --------------------------------------------------------------
50-
if scale_fixed < 4096: # < ~12.5 %
51-
shift: int = 0
52-
tmp: int = 32768
53-
while tmp > scale_fixed:
54-
shift += 1
55-
tmp >>= 1
56-
for i in range(0, num_bytes, 2):
57-
lo: int = int(buf[i])
58-
hi: int = int(buf[i + 1])
59-
s: int = (hi << 8) | lo
60-
if hi & 128: # sign extend
61-
s -= 65536
62-
s >>= shift
63-
buf[i] = s & 255
64-
buf[i + 1] = (s >> 8) & 255
65-
return
40+
mask: int = scale_fixed
6641

67-
# --------------------------------------------------------------
68-
# Medium → high volumes: sample * scale_fixed // 32768
69-
# approximated with shifts + adds only
70-
# --------------------------------------------------------------
71-
# Build a 16-bit mask:
72-
# bit 0 → add (s >> 15)
73-
# bit 1 → add (s >> 14)
74-
# ...
75-
# bit 15 → add s (>> 0)
76-
mask: int = 0
77-
bit_value: int = 16384 # starts at 2^-1
78-
remaining: int = scale_fixed
79-
80-
shift_idx: int = 1 # corresponds to >>1
81-
while bit_value > 0:
82-
if remaining >= bit_value:
83-
mask |= (1 << (16 - shift_idx)) # correct bit position
84-
remaining -= bit_value
85-
bit_value >>= 1
86-
shift_idx += 1
87-
88-
# Apply the mask
8942
for i in range(0, num_bytes, 2):
90-
lo: int = int(buf[i])
91-
hi: int = int(buf[i + 1])
92-
s: int = (hi << 8) | lo
93-
if hi & 128:
94-
s -= 65536
95-
96-
result: int = 0
97-
if mask & 0x8000: result += s # >>0
98-
if mask & 0x4000: result += (s >> 1)
99-
if mask & 0x2000: result += (s >> 2)
100-
if mask & 0x1000: result += (s >> 3)
101-
if mask & 0x0800: result += (s >> 4)
102-
if mask & 0x0400: result += (s >> 5)
103-
if mask & 0x0200: result += (s >> 6)
104-
if mask & 0x0100: result += (s >> 7)
105-
if mask & 0x0080: result += (s >> 8)
106-
if mask & 0x0040: result += (s >> 9)
107-
if mask & 0x0020: result += (s >>10)
108-
if mask & 0x0010: result += (s >>11)
109-
if mask & 0x0008: result += (s >>12)
110-
if mask & 0x0004: result += (s >>13)
111-
if mask & 0x0002: result += (s >>14)
112-
if mask & 0x0001: result += (s >>15)
113-
114-
# Clamp to 16-bit signed range
115-
if result > 32767:
116-
result = 32767
117-
elif result < -32768:
118-
result = -32768
119-
120-
buf[i] = result & 255
121-
buf[i + 1] = (result >> 8) & 255
43+
s: int = int(buf[i]) | (int(buf[i+1]) << 8)
44+
if s >= 0x8000:
45+
s -= 0x10000
46+
47+
r: int = 0
48+
if mask & 0x8000: r += s
49+
if mask & 0x4000: r += s>>1
50+
if mask & 0x2000: r += s>>2
51+
if mask & 0x1000: r += s>>3
52+
if mask & 0x0800: r += s>>4
53+
if mask & 0x0400: r += s>>5
54+
if mask & 0x0200: r += s>>6
55+
if mask & 0x0100: r += s>>7
56+
if mask & 0x0080: r += s>>8
57+
if mask & 0x0040: r += s>>9
58+
if mask & 0x0020: r += s>>10
59+
if mask & 0x0010: r += s>>11
60+
if mask & 0x0008: r += s>>12
61+
if mask & 0x0004: r += s>>13
62+
if mask & 0x0002: r += s>>14
63+
if mask & 0x0001: r += s>>15
64+
65+
if r > 32767: r = 32767
66+
if r < -32768: r = -32768
67+
68+
buf[i] = r & 0xFF
69+
buf[i+1] = (r >> 8) & 0xFF
12270

123-
import micropython
12471
@micropython.viper
12572
def _scale_audio_rough(buf: ptr8, num_bytes: int, scale_fixed: int):
12673
"""Rough volume scaling for 16-bit audio samples using right shifts for performance."""
@@ -375,9 +322,12 @@ def play(self):
375322

376323
# smaller chunk size means less jerks but buffer can run empty
377324
# at 22050 Hz, 16-bit, 2-ch, 4096/4 = 1024 samples / 22050 = 46ms
325+
# with rough volume scaling:
378326
# 4096 => audio stutters during quasibird
379327
# 8192 => no audio stutters and quasibird runs at ~16 fps => good compromise!
380328
# 16384 => no audio stutters during quasibird but low framerate (~8fps)
329+
# with optimized volume scaling:
330+
# 8192 => no audio stutters and quasibird runs at ~12fps
381331
chunk_size = 4096*2
382332
bytes_per_original_sample = (bits_per_sample // 8) * channels
383333
total_original = 0

scripts/install.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ fi
4747
# The issue is that this brings all the .git folders with it:
4848
#$mpremote fs cp -r apps :/
4949

50+
$mpremote fs cp -r lib :/
51+
5052
$mpremote fs mkdir :/apps
5153
$mpremote fs cp -r apps/com.micropythonos.* :/apps/
5254
find apps/ -maxdepth 1 -type l | while read symlink; do
@@ -59,7 +61,6 @@ done
5961
#echo "Unmounting builtin/ so that it can be customized..." # not sure this is necessary
6062
#$mpremote exec "import os ; os.umount('/builtin')"
6163
$mpremote fs cp -r builtin :/
62-
$mpremote fs cp -r lib :/
6364

6465
#$mpremote fs cp -r data :/
6566
#$mpremote fs cp -r data/images :/data/

0 commit comments

Comments
 (0)