|
1 | 1 | import machine |
2 | 2 | import os |
3 | 3 | import time |
| 4 | +import micropython |
| 5 | + |
4 | 6 |
|
5 | 7 | # ---------------------------------------------------------------------- |
6 | 8 | # AudioPlayer – robust, volume-controllable WAV player |
7 | 9 | # ---------------------------------------------------------------------- |
8 | 10 | class AudioPlayer: |
9 | 11 | # class-level defaults (shared by every instance) |
10 | 12 | _i2s = None # the I2S object (created once per playback) |
11 | | - _volume = 100 # 0-100 (100 = full scale) |
| 13 | + _volume = 50 # 0-100 (100 = full scale) |
12 | 14 |
|
13 | 15 | @staticmethod |
14 | 16 | def find_data_chunk(f): |
@@ -102,25 +104,37 @@ def play_wav(cls, filename): |
102 | 104 | print(f"Playing {data_size} bytes (vol {cls._volume}%) …") |
103 | 105 | f.seek(data_start) |
104 | 106 |
|
| 107 | + @micropython.viper |
| 108 | + def scale_audio(buf: ptr8, num_bytes: int, scale_fixed: int): |
| 109 | + for i in range(0, num_bytes, 2): |
| 110 | + lo = int(buf[i]) |
| 111 | + hi = int(buf[i+1]) |
| 112 | + sample = (hi << 8) | lo |
| 113 | + if hi & 128: |
| 114 | + sample -= 65536 |
| 115 | + sample = (sample * scale_fixed) // 32768 |
| 116 | + if sample > 32767: |
| 117 | + sample = 32767 |
| 118 | + elif sample < -32768: |
| 119 | + sample = -32768 |
| 120 | + buf[i] = sample & 255 |
| 121 | + buf[i+1] = (sample >> 8) & 255 |
| 122 | + |
105 | 123 | chunk_size = 4096 # 4 KB → safe on ESP32 |
106 | 124 |
|
107 | 125 | total = 0 |
108 | 126 | while total < data_size: |
109 | 127 | to_read = min(chunk_size, data_size - total) |
110 | | - raw = f.read(to_read) |
| 128 | + raw = bytearray(f.read(to_read)) # mutable for in-place scaling |
111 | 129 | if not raw: |
112 | 130 | break |
113 | 131 |
|
114 | | - # ---- on-the-fly volume scaling (16-bit little-endian) ---- |
115 | | - scale = cls._volume / 100.0 # float 0.0-1.0 |
116 | | - if scale < 0.9: |
117 | | - scaled = bytearray(len(raw)) |
118 | | - for i in range(0, len(raw), 2): |
119 | | - sample = int.from_bytes(raw[i:i+2], 'little', True) |
120 | | - sample = int(sample * scale) |
121 | | - scaled[i:i+2] = sample.to_bytes(2, 'little', True) |
122 | | - raw = bytes(scaled) |
123 | | - # --------------------------------------------------------- |
| 132 | + # ---- fast viper scaling (in-place) ---- |
| 133 | + scale = cls._volume / 100.0 # adjust the volume on the fly instead of at the start of playback |
| 134 | + if scale < 1.0: |
| 135 | + scale_fixed = int(scale * 32768) |
| 136 | + scale_audio(raw, len(raw), scale_fixed) |
| 137 | + # --------------------------------------- |
124 | 138 |
|
125 | 139 | if cls._i2s: |
126 | 140 | cls._i2s.write(raw) |
|
0 commit comments