@@ -10,6 +10,8 @@ class MusicPlayer(Activity):
1010
1111 # Widgets:
1212 file_explorer = None
13+ _slider_label = None
14+ _slider = None
1315
1416 def onCreate (self ):
1517 screen = lv .obj ()
@@ -19,6 +21,19 @@ def onCreate(self):
1921 self .file_explorer .explorer_open_dir ('M:/' )
2022 self .file_explorer .align (lv .ALIGN .CENTER , 0 , 0 )
2123 self .file_explorer .add_event_cb (self .file_explorer_event_cb , lv .EVENT .ALL , None )
24+ self ._slider_label = lv .label (screen )
25+ self ._slider_label .set_text (f"Volume: 100%" )
26+ self ._slider_label .align (lv .ALIGN .TOP_MID ,0 ,lv .pct (4 ))
27+ self ._slider = lv .slider (screen )
28+ self ._slider .set_range (0 ,100 )
29+ self ._slider .set_value (100 ,False )
30+ self ._slider .set_width (lv .pct (80 ))
31+ self ._slider .align_to (self ._slider_label ,lv .ALIGN .OUT_BOTTOM_MID ,0 ,10 )
32+ def volume_slider_changed (e ):
33+ volume_int = self ._slider .get_value ()
34+ self ._slider_label .set_text (f"Volume: { volume_int } %" )
35+ # TODO: set volume using AudioPlayer.set_volume(volume_int)
36+ self ._slider .add_event_cb (volume_slider_changed ,lv .EVENT .VALUE_CHANGED ,None )
2237 self .setContentView (screen )
2338
2439 def onResume (self , screen ):
@@ -39,61 +54,99 @@ def file_explorer_event_cb(self, event):
3954 if fullpath .lower ().endswith ('.wav' ):
4055 _thread .stack_size (mpos .apps .good_stack_size ())
4156 _thread .start_new_thread (self .play_wav , (fullpath ,))
42- #self.play_wav(fullpath)
4357 else :
4458 print ("INFO: ignoring unsupported file format" )
4559
46- def parse_wav_header (self , f ):
47- """Parse standard WAV header (44 bytes) and return channels, sample_rate, bits_per_sample, data_size."""
48- header = f .read (44 )
49- if header [0 :4 ] != b'RIFF' or header [8 :12 ] != b'WAVE' or header [12 :16 ] != b'fmt ' :
50- raise ValueError ("Invalid WAV file" )
51- audio_format = int .from_bytes (header [20 :22 ], 'little' )
52- if audio_format != 1 : # PCM only
53- raise ValueError ("Only PCM WAV supported" )
54- channels = int .from_bytes (header [22 :24 ], 'little' )
55- sample_rate = int .from_bytes (header [24 :28 ], 'little' )
56- bits_per_sample = int .from_bytes (header [34 :36 ], 'little' )
57- # Skip to data chunk
58- f .read (8 ) # 'data' + size
59- data_size = int .from_bytes (f .read (4 ), 'little' )
60- return channels , sample_rate , bits_per_sample , data_size
61-
62- def play_wav (self , filename ):
63- """Play WAV file via I2S to MAX98357A."""
64- with open (filename , 'rb' ) as f :
65- try :
66- channels , sample_rate , bits_per_sample , data_size = self .parse_wav_header (f )
60+ def find_data_chunk (self , f ):
61+ """Skip chunks until 'data' is found. Returns (data_start_pos, data_size)."""
62+ # Go back to start
63+ f .seek (0 )
64+ riff = f .read (4 )
65+ if riff != b'RIFF' :
66+ raise ValueError ("Not a RIFF file" )
67+ file_size = int .from_bytes (f .read (4 ), 'little' ) + 8 # Total file size
68+ wave = f .read (4 )
69+ if wave != b'WAVE' :
70+ raise ValueError ("Not a WAVE file" )
71+
72+ pos = 12 # Start after RIFF header
73+ while pos < file_size :
74+ f .seek (pos )
75+ chunk_id = f .read (4 )
76+ if len (chunk_id ) < 4 :
77+ break
78+ chunk_size = int .from_bytes (f .read (4 ), 'little' )
79+ if chunk_id == b'fmt ' :
80+ fmt_data = f .read (chunk_size )
81+ if len (fmt_data ) < 16 :
82+ raise ValueError ("Invalid fmt chunk" )
83+ audio_format = int .from_bytes (fmt_data [0 :2 ], 'little' )
84+ channels = int .from_bytes (fmt_data [2 :4 ], 'little' )
85+ sample_rate = int .from_bytes (fmt_data [4 :8 ], 'little' )
86+ bits_per_sample = int .from_bytes (fmt_data [14 :16 ], 'little' )
87+ if audio_format != 1 :
88+ raise ValueError ("Only PCM supported" )
6789 if bits_per_sample != 16 :
68- raise ValueError ("Only 16-bit audio supported" )
90+ raise ValueError ("Only 16-bit supported" )
6991 if channels != 1 :
70- raise ValueError ("Only mono audio supported (convert with -ac 1 in FFmpeg)" )
71-
72- # Configure I2S (TX mode for output)
73- i2s = machine .I2S (0 , # I2S peripheral 0
74- sck = machine .Pin (2 , machine .Pin .OUT ), # BCK
75- ws = machine .Pin (47 , machine .Pin .OUT ), # LRCK
76- sd = machine .Pin (16 , machine .Pin .OUT ), # DIN
77- mode = machine .I2S .TX ,
78- bits = 16 ,
79- format = machine .I2S .MONO ,
80- rate = sample_rate ,
81- ibuf = 16000 ) # Internal buffer size (adjust if audio stutters)
82-
92+ raise ValueError ("Only mono supported" )
93+ elif chunk_id == b'data' :
94+ data_start = f .tell ()
95+ data_size = chunk_size
96+ return data_start , data_size , sample_rate
97+ # Skip chunk (pad byte if odd size)
98+ pos += 8 + chunk_size
99+ if chunk_size % 2 == 1 :
100+ pos += 1
101+ raise ValueError ("No 'data' chunk found" )
102+
103+ def play_wav (self , filename ):
104+ """Play large WAV files robustly with chunk skipping and streaming."""
105+ try :
106+ with open (filename , 'rb' ) as f :
107+ stat = uos .stat (filename )
108+ file_size = stat [6 ]
109+ print (f"File size: { file_size } bytes" )
110+
111+ data_start , data_size , sample_rate = self .find_data_chunk (f )
112+ print (f"Found 'data' chunk: { data_size } bytes at { sample_rate } Hz" )
113+
114+ if data_size > file_size - data_start :
115+ print ("Warning: data_size exceeds file bounds. Truncating." )
116+ data_size = file_size - data_start
117+
118+ # Configure I2S
119+ i2s = machine .I2S (
120+ 0 ,
121+ sck = machine .Pin (2 , machine .Pin .OUT ),
122+ ws = machine .Pin (47 , machine .Pin .OUT ),
123+ sd = machine .Pin (16 , machine .Pin .OUT ),
124+ mode = machine .I2S .TX ,
125+ bits = 16 ,
126+ format = machine .I2S .MONO ,
127+ rate = sample_rate ,
128+ ibuf = 32000 # Larger buffer for stability
129+ )
130+
83131 print (f"Playing { data_size } bytes at { sample_rate } Hz..." )
84-
85- # Stream data in chunks (16-bit = 2 bytes per sample)
86- chunk_size = 1024 * 2 # 1KB chunks (tune for your RAM)
132+ f . seek ( data_start )
133+
134+ chunk_size = 4096 # 4KB chunks = safe for ESP32
87135 total_read = 0
88136 while total_read < data_size :
89- chunk = f .read (min (chunk_size , data_size - total_read ))
137+ remaining = data_size - total_read
138+ read_size = min (chunk_size , remaining )
139+ chunk = f .read (read_size )
90140 if not chunk :
91141 break
92- i2s .write (chunk ) # Direct byte stream (little-endian matches I2S)
142+ i2s .write (chunk )
93143 total_read += len (chunk )
94-
144+
95145 print ("Playback finished." )
96- except Exception as e :
97- print (f"Error: { e } " )
98- finally :
99- i2s .deinit () # Clean up
146+ except Exception as e :
147+ print (f"Error: { e } " )
148+ finally :
149+ try :
150+ i2s .deinit ()
151+ except :
152+ pass
0 commit comments