55
66
77# ----------------------------------------------------------------------
8- # AudioPlayer – robust, volume-controllable WAV player (MONO + STEREO)
9- # Auto-up-samples any rate < 22050 Hz to 22050 Hz for MAX98357
8+ # AudioPlayer – robust, volume-controllable WAV player
9+ # Supports 8 / 16 / 24 / 32-bit PCM, mono + stereo
10+ # Auto-up-samples any rate < 22050 Hz to >=22050 Hz
1011# ----------------------------------------------------------------------
1112class AudioPlayer :
1213_i2s = None
1314_volume = 50 # 0-100
1415_keep_running = True
1516
1617# ------------------------------------------------------------------
17- # WAV header parser
18+ # WAV header parser – returns bit-depth
1819# ------------------------------------------------------------------
1920@staticmethod
2021def find_data_chunk (f ):
21- """Return (data_start, data_size, sample_rate, channels)"""
22+ """Return (data_start, data_size, sample_rate, channels, bits_per_sample )"""
2223f .seek (0 )
2324if f .read (4 )!= b'RIFF' :
2425raise ValueError ("Not a RIFF file" )
@@ -29,6 +30,7 @@ def find_data_chunk(f):
2930pos = 12
3031sample_rate = None
3132channels = None
33+ bits_per_sample = None
3234while pos < file_size :
3335f .seek (pos )
3436chunk_id = f .read (4 )
@@ -45,10 +47,11 @@ def find_data_chunk(f):
4547if channels not in (1 ,2 ):
4648raise ValueError ("Only mono or stereo supported" )
4749sample_rate = int .from_bytes (fmt [4 :8 ],'little' )
48- if int .from_bytes (fmt [14 :16 ],'little' )!= 16 :
49- raise ValueError ("Only 16-bit supported" )
50+ bits_per_sample = int .from_bytes (fmt [14 :16 ],'little' )
51+ if bits_per_sample not in (8 ,16 ,24 ,32 ):
52+ raise ValueError ("Only 8/16/24/32-bit PCM supported" )
5053elif chunk_id == b'data' :
51- return f .tell (),chunk_size ,sample_rate ,channels
54+ return f .tell (),chunk_size ,sample_rate ,channels , bits_per_sample
5255pos += 8 + chunk_size
5356if chunk_size % 2 :
5457pos += 1
@@ -72,21 +75,14 @@ def stop_playing(cls):
7275cls ._keep_running = False
7376
7477# ------------------------------------------------------------------
75- #Helper: up -samplea raw PCM buffer (zero-order-hold)
78+ #1. Up -sample16-bit buffer (zero-order-hold)
7679# ------------------------------------------------------------------
7780@staticmethod
7881def _upsample_buffer (raw :bytearray ,factor :int )-> bytearray :
79- """
80- Duplicate each 16-bit sample `factor` times.
81- Input: interleaved L,R,L,R... (or mono)
82- Output: same layout, each sample repeated `factor` times.
83- """
8482if factor == 1 :
8583return raw
86-
8784upsampled = bytearray (len (raw )* factor )
8885out_idx = 0
89- # each sample = 2 bytes
9086for i in range (0 ,len (raw ),2 ):
9187lo = raw [i ]
9288hi = raw [i + 1 ]
@@ -96,6 +92,64 @@ def _upsample_buffer(raw: bytearray, factor: int) -> bytearray:
9692out_idx += 2
9793return upsampled
9894
95+ # ------------------------------------------------------------------
96+ # 2. Convert 8-bit to 16-bit (non-viper, Viper-safe)
97+ # ------------------------------------------------------------------
98+ @staticmethod
99+ def _convert_8_to_16 (buf :bytearray )-> bytearray :
100+ out = bytearray (len (buf )* 2 )
101+ j = 0
102+ for i in range (len (buf )):
103+ u8 = buf [i ]
104+ s16 = (u8 - 128 )<< 8
105+ out [j ]= s16 & 0xFF
106+ out [j + 1 ]= (s16 >> 8 )& 0xFF
107+ j += 2
108+ return out
109+
110+ # ------------------------------------------------------------------
111+ # 3. Convert 24-bit to 16-bit (non-viper)
112+ # ------------------------------------------------------------------
113+ @staticmethod
114+ def _convert_24_to_16 (buf :bytearray )-> bytearray :
115+ samples = len (buf )// 3
116+ out = bytearray (samples * 2 )
117+ j = 0
118+ for i in range (samples ):
119+ b0 = buf [j ]
120+ b1 = buf [j + 1 ]
121+ b2 = buf [j + 2 ]
122+ s24 = (b2 << 16 )| (b1 << 8 )| b0
123+ if b2 & 0x80 :
124+ s24 -= 0x1000000
125+ s16 = s24 >> 8
126+ out [i * 2 ]= s16 & 0xFF
127+ out [i * 2 + 1 ]= (s16 >> 8 )& 0xFF
128+ j += 3
129+ return out
130+
131+ # ------------------------------------------------------------------
132+ # 4. Convert 32-bit to 16-bit (non-viper)
133+ # ------------------------------------------------------------------
134+ @staticmethod
135+ def _convert_32_to_16 (buf :bytearray )-> bytearray :
136+ samples = len (buf )// 4
137+ out = bytearray (samples * 2 )
138+ j = 0
139+ for i in range (samples ):
140+ b0 = buf [j ]
141+ b1 = buf [j + 1 ]
142+ b2 = buf [j + 2 ]
143+ b3 = buf [j + 3 ]
144+ s32 = (b3 << 24 )| (b2 << 16 )| (b1 << 8 )| b0
145+ if b3 & 0x80 :
146+ s32 -= 0x100000000
147+ s16 = s32 >> 16
148+ out [i * 2 ]= s16 & 0xFF
149+ out [i * 2 + 1 ]= (s16 >> 8 )& 0xFF
150+ j += 4
151+ return out
152+
99153# ------------------------------------------------------------------
100154# Main playback routine
101155# ------------------------------------------------------------------
@@ -109,25 +163,25 @@ def play_wav(cls, filename):
109163print (f"File size:{ file_size } bytes" )
110164
111165# ----- parse header ------------------------------------------------
112- data_start ,data_size ,original_rate ,channels = cls .find_data_chunk (f )
166+ data_start ,data_size ,original_rate ,channels ,bits_per_sample = \
167+ cls .find_data_chunk (f )
113168
114- # ----- decide playback rate (force >= 22050 Hz) --------------------
169+ # ----- decide playback rate (force >=22050 Hz) --------------------
115170target_rate = 22050
116171if original_rate >= target_rate :
117172playback_rate = original_rate
118173upsample_factor = 1
119174else :
120- # find smallest integer factor so original * factor >= target
121175upsample_factor = (target_rate + original_rate - 1 )// original_rate
122176playback_rate = original_rate * upsample_factor
123177
124- print (f"Original:{ original_rate } Hz → Playback: { playback_rate } Hz "
125- f"(factor{ upsample_factor } ), { channels } -ch " )
178+ print (f"Original:{ original_rate } Hz, { bits_per_sample } -bit, { channels } -ch "
179+ f"to Playback: { playback_rate } Hz (factor{ upsample_factor } )" )
126180
127181if data_size > file_size - data_start :
128182data_size = file_size - data_start
129183
130- # ----- I2S init------------------ ----------------------------------
184+ # ----- I2S init(always 16-bit) ----------------------------------
131185try :
132186i2s_format = machine .I2S .MONO if channels == 1 else machine .I2S .STEREO
133187cls ._i2s = machine .I2S (
@@ -144,10 +198,10 @@ def play_wav(cls, filename):
144198except Exception as e :
145199print (f"Warning: simulating playback (I2S init failed):{ e } " )
146200
147- print (f"Playing{ data_size } original bytes (vol{ cls ._volume } %)… " )
201+ print (f"Playing{ data_size } original bytes (vol{ cls ._volume } %)... " )
148202f .seek (data_start )
149203
150- # ----- Viper volume scaler (works on any buffer) -------------------
204+ # ----- Viper volume scaler (16-bit only) ------ -------------------
151205@micropython .viper
152206def scale_audio (buf :ptr8 ,num_bytes :int ,scale_fixed :int ):
153207for i in range (0 ,num_bytes ,2 ):
@@ -165,15 +219,15 @@ def scale_audio(buf: ptr8, num_bytes: int, scale_fixed: int):
165219buf [i + 1 ]= (sample >> 8 )& 255
166220
167221chunk_size = 4096
168- bytes_per_original_sample = 2 * channels # 2 bytes per channel
222+ bytes_per_original_sample = ( bits_per_sample // 8 ) * channels
169223total_original = 0
170224
171225while total_original < data_size :
172226if not cls ._keep_running :
173227print ("Playback stopped by user." )
174228break
175229
176- # read a chunk of* original* data
230+ #---- read awhole-sample chunk of original data -------------
177231to_read = min (chunk_size ,data_size - total_original )
178232to_read -= (to_read % bytes_per_original_sample )
179233if to_read <= 0 :
@@ -183,25 +237,33 @@ def scale_audio(buf: ptr8, num_bytes: int, scale_fixed: int):
183237if not raw :
184238break
185239
186- # ----- up-sample if needed ---------------------------------
240+ # ---- 1. Convert bit-depth to 16-bit (non-viper) -------------
241+ if bits_per_sample == 8 :
242+ raw = cls ._convert_8_to_16 (raw )
243+ elif bits_per_sample == 24 :
244+ raw = cls ._convert_24_to_16 (raw )
245+ elif bits_per_sample == 32 :
246+ raw = cls ._convert_32_to_16 (raw )
247+ # 16-bit to unchanged
248+
249+ # ---- 2. Up-sample if needed ---------------------------------
187250if upsample_factor > 1 :
188251raw = cls ._upsample_buffer (raw ,upsample_factor )
189252
190- # ----- volume scaling- --------------------------------------
253+ # ---- 3. Volume scaling --------------------------------------
191254scale = cls ._volume / 100.0
192255if scale < 1.0 :
193256scale_fixed = int (scale * 32768 )
194257scale_audio (raw ,len (raw ),scale_fixed )
195258
196- # ----- output --- ---------------------------------------------
259+ # ---- 4. Output ---------------------------------------------
197260if cls ._i2s :
198261cls ._i2s .write (raw )
199262else :
200- # simulate timing with the *playback* rate
201263num_samples = len (raw )// (2 * channels )
202264time .sleep (num_samples / playback_rate )
203265
204- total_original += to_read # count original bytes only
266+ total_original += to_read
205267
206268print ("Playback finished." )
207269except Exception as e :