Skip to content

Commit d149883

Browse files
Add simple Music Player app
Currently only handles mono wav files - to be improved.
1 parent 2ca5170 commit d149883

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "Music Player",
3+
"publisher": "MicroPythonOS",
4+
"short_description": "Player audio files",
5+
"long_description": "Traverse around the filesystem and play audio files that you select.",
6+
"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.musicplayer/icons/com.micropythonos.musicplayer_0.0.1_64x64.png",
7+
"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.musicplayer/mpks/com.micropythonos.musicplayer_0.0.1.mpk",
8+
"fullname": "com.micropythonos.musicplayer",
9+
"version": "0.0.1",
10+
"category": "development",
11+
"activities": [
12+
{
13+
"entrypoint": "assets/music_player.py",
14+
"classname": "MusicPlayer",
15+
"intent_filters": [
16+
{
17+
"action": "main",
18+
"category": "launcher"
19+
}
20+
]
21+
}
22+
]
23+
}
24+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from mpos.apps import Activity
2+
import mpos.sdcard
3+
import mpos.ui
4+
# play music
5+
import machine
6+
import uos
7+
from machine import I2S, Pin
8+
9+
class MusicPlayer(Activity):
10+
11+
# Widgets:
12+
file_explorer = None
13+
14+
def onCreate(self):
15+
screen = lv.obj()
16+
# the user might have recently plugged in the sd card so try to mount it
17+
mpos.sdcard.mount_with_optional_format('/sdcard')
18+
self.file_explorer = lv.file_explorer(screen)
19+
self.file_explorer.explorer_open_dir('M:/')
20+
self.file_explorer.align(lv.ALIGN.CENTER, 0, 0)
21+
self.file_explorer.add_event_cb(self.file_explorer_event_cb, lv.EVENT.ALL, None)
22+
self.setContentView(screen)
23+
24+
def onResume(self, screen):
25+
# the user might have recently plugged in the sd card so try to mount it
26+
mpos.sdcard.mount_with_optional_format('/sdcard') # would be good to refresh the file_explorer so the /sdcard folder shows up
27+
28+
def file_explorer_event_cb(self, event):
29+
event_code = event.get_code()
30+
if event_code not in [2,19,23,24,25,26,27,28,29,30,31,32,33,47,49,52]:
31+
name = mpos.ui.get_event_name(event_code)
32+
print(f"file_explorer_event_cb {event_code} with name {name}")
33+
if event_code == lv.EVENT.VALUE_CHANGED:
34+
path = self.file_explorer.explorer_get_current_path()
35+
clean_path = path[2:] if path[1] == ':' else path
36+
file = self.file_explorer.explorer_get_selected_file_name()
37+
fullpath = f"{clean_path}{file}"
38+
print(f"Selected: {fullpath}")
39+
if fullpath.lower().endswith('.wav'):
40+
self.play_wav(fullpath)
41+
else:
42+
print("INFO: ignoring unsupported file format")
43+
44+
def parse_wav_header(self, f):
45+
"""Parse standard WAV header (44 bytes) and return channels, sample_rate, bits_per_sample, data_size."""
46+
header = f.read(44)
47+
if header[0:4] != b'RIFF' or header[8:12] != b'WAVE' or header[12:16] != b'fmt ':
48+
raise ValueError("Invalid WAV file")
49+
audio_format = int.from_bytes(header[20:22], 'little')
50+
if audio_format != 1: # PCM only
51+
raise ValueError("Only PCM WAV supported")
52+
channels = int.from_bytes(header[22:24], 'little')
53+
sample_rate = int.from_bytes(header[24:28], 'little')
54+
bits_per_sample = int.from_bytes(header[34:36], 'little')
55+
# Skip to data chunk
56+
f.read(8) # 'data' + size
57+
data_size = int.from_bytes(f.read(4), 'little')
58+
return channels, sample_rate, bits_per_sample, data_size
59+
60+
def play_wav(self, filename):
61+
"""Play WAV file via I2S to MAX98357A."""
62+
with open(filename, 'rb') as f:
63+
try:
64+
channels, sample_rate, bits_per_sample, data_size = self.parse_wav_header(f)
65+
if bits_per_sample != 16:
66+
raise ValueError("Only 16-bit audio supported")
67+
if channels != 1:
68+
raise ValueError("Only mono audio supported (convert with -ac 1 in FFmpeg)")
69+
70+
# Configure I2S (TX mode for output)
71+
i2s = I2S(0, # I2S peripheral 0
72+
sck=Pin(2, Pin.OUT), # BCK
73+
ws=Pin(47, Pin.OUT), # LRCK
74+
sd=Pin(16, Pin.OUT), # DIN
75+
mode=I2S.TX,
76+
bits=16,
77+
format=I2S.MONO,
78+
rate=sample_rate,
79+
ibuf=16000) # Internal buffer size (adjust if audio stutters)
80+
81+
print(f"Playing {data_size} bytes at {sample_rate} Hz...")
82+
83+
# Stream data in chunks (16-bit = 2 bytes per sample)
84+
chunk_size = 1024 * 2 # 1KB chunks (tune for your RAM)
85+
total_read = 0
86+
while total_read < data_size:
87+
chunk = f.read(min(chunk_size, data_size - total_read))
88+
if not chunk:
89+
break
90+
i2s.write(chunk) # Direct byte stream (little-endian matches I2S)
91+
total_read += len(chunk)
92+
93+
print("Playback finished.")
94+
except Exception as e:
95+
print(f"Error: {e}")
96+
finally:
97+
i2s.deinit() # Clean up
5.12 KB
Loading

0 commit comments

Comments
 (0)