33# Ported from MusicPlayer's AudioPlayer class
44
55import machine
6+ import micropython
67import os
78import time
89import 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
1515def _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
3332def _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
12572def _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
0 commit comments