Skip to content

Commit 8b9de6c

Browse files
committed
Add raw PCM player.
1 parent 82be207 commit 8b9de6c

File tree

2 files changed

+139
-11
lines changed

2 files changed

+139
-11
lines changed

src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
3131
&input::KBTEST_ITEM,
3232
&hardware::SHUTDOWN_ITEM,
3333
&sound::MIXER_ITEM,
34+
&sound::PLAY_ITEM,
3435
],
3536
entry: None,
3637
exit: None,

src/commands/sound.rs

Lines changed: 138 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,86 @@
11
//! Sound related commands for Neotron OS
22
3-
use crate::{osprintln, Ctx, API};
3+
use crate::{osprint, osprintln, Ctx, API};
44

55
pub static MIXER_ITEM: menu::Item<Ctx> = menu::Item {
66
item_type: menu::ItemType::Callback {
77
function: mixer,
8-
parameters: &[],
8+
parameters: &[
9+
menu::Parameter::Optional {
10+
parameter_name: "mixer",
11+
help: Some("Which mixer to adjust"),
12+
},
13+
menu::Parameter::Optional {
14+
parameter_name: "level",
15+
help: Some("New level for this mixer, as an integer."),
16+
},
17+
],
918
},
1019
command: "mixer",
1120
help: Some("Control the audio mixer"),
1221
};
1322

23+
pub static PLAY_ITEM: menu::Item<Ctx> = menu::Item {
24+
item_type: menu::ItemType::Callback {
25+
function: play,
26+
parameters: &[menu::Parameter::Mandatory {
27+
parameter_name: "filename",
28+
help: Some("Which file to play"),
29+
}],
30+
},
31+
command: "play",
32+
help: Some("Play a raw 16-bit LE 48 kHz stereo file"),
33+
};
34+
1435
/// Called when the "mixer" command is executed.
15-
fn mixer(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
36+
fn mixer(_menu: &menu::Menu<Ctx>, item: &menu::Item<Ctx>, args: &[&str], _ctx: &mut Ctx) {
37+
let selected_mixer = menu::argument_finder(item, args, "mixer").unwrap();
38+
let level_str = menu::argument_finder(item, args, "level").unwrap();
39+
40+
let level_int = if let Some(level_str) = level_str {
41+
let Ok(value) = level_str.parse::<u8>() else {
42+
osprintln!("{} is not an integer", level_str);
43+
return;
44+
};
45+
Some(value)
46+
} else {
47+
None
48+
};
49+
1650
let api = API.get();
51+
52+
if let (Some(selected_mixer), Some(level_int)) = (selected_mixer, level_int) {
53+
let mut found = false;
54+
for mixer_id in 0u8..=255u8 {
55+
match (api.audio_mixer_channel_get_info)(mixer_id) {
56+
neotron_common_bios::FfiOption::Some(mixer_info) => {
57+
if mixer_info.name.as_str() == selected_mixer {
58+
if let Err(e) =
59+
(api.audio_mixer_channel_set_level)(mixer_id, level_int).into()
60+
{
61+
osprintln!(
62+
"Failed to set mixer {:?} (id {}) to {}: {:?}",
63+
selected_mixer,
64+
mixer_id,
65+
level_int,
66+
e
67+
);
68+
}
69+
found = true;
70+
break;
71+
}
72+
}
73+
neotron_common_bios::FfiOption::None => {
74+
break;
75+
}
76+
}
77+
}
78+
79+
if !found {
80+
osprintln!("Don't know mixer {:?}", selected_mixer);
81+
}
82+
}
83+
1784
osprintln!("Mixers:");
1885
for mixer_id in 0u8..=255u8 {
1986
match (api.audio_mixer_channel_get_info)(mixer_id) {
@@ -23,14 +90,19 @@ fn mixer(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx:
2390
neotron_common_bios::audio::Direction::Loopback => "Loop",
2491
neotron_common_bios::audio::Direction::Output => "Out",
2592
};
26-
osprintln!(
27-
"#{}: {} ({}) {}/{}",
28-
mixer_id,
29-
mixer_info.name,
30-
dir_str,
31-
mixer_info.current_level,
32-
mixer_info.max_level
33-
);
93+
if selected_mixer
94+
.and_then(|s| Some(s == mixer_info.name.as_str()))
95+
.unwrap_or(true)
96+
{
97+
osprintln!(
98+
"#{}: {} ({}) {}/{}",
99+
mixer_id,
100+
mixer_info.name,
101+
dir_str,
102+
mixer_info.current_level,
103+
mixer_info.max_level
104+
);
105+
}
34106
}
35107
neotron_common_bios::FfiOption::None => {
36108
// Run out of mixers
@@ -39,3 +111,58 @@ fn mixer(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx:
39111
}
40112
}
41113
}
114+
115+
/// Called when the "play" command is executed.
116+
fn play(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], ctx: &mut Ctx) {
117+
fn play_inner(
118+
file_name: &str,
119+
scratch: &mut [u8],
120+
) -> Result<(), embedded_sdmmc::Error<neotron_common_bios::Error>> {
121+
osprintln!("Loading /{} from Block Device 0", file_name);
122+
let bios_block = crate::fs::BiosBlock();
123+
let time = crate::fs::BiosTime();
124+
let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time);
125+
// Open the first partition
126+
let mut volume = mgr.get_volume(embedded_sdmmc::VolumeIdx(0))?;
127+
let root_dir = mgr.open_root_dir(&volume)?;
128+
let mut file = mgr.open_file_in_dir(
129+
&mut volume,
130+
&root_dir,
131+
file_name,
132+
embedded_sdmmc::Mode::ReadOnly,
133+
)?;
134+
135+
let api = API.get();
136+
137+
let mut buffer = &mut scratch[0..4096];
138+
let mut bytes = 0;
139+
let mut delta = 0;
140+
141+
while !file.eof() {
142+
let bytes_read = mgr.read(&mut volume, &mut file, &mut buffer)?;
143+
let mut buffer = &buffer[0..bytes_read];
144+
while !buffer.is_empty() {
145+
let slice = neotron_common_bios::FfiByteSlice::new(buffer);
146+
let played = unsafe { (api.audio_output_data)(slice).unwrap() };
147+
buffer = &buffer[played..];
148+
delta += played;
149+
if delta > 48000 {
150+
bytes += delta;
151+
delta = 0;
152+
let milliseconds = bytes / ((48000 / 1000) * 4);
153+
osprint!(
154+
"\rPlayed: {}:{} ms",
155+
milliseconds / 1000,
156+
milliseconds % 1000
157+
);
158+
}
159+
}
160+
}
161+
osprintln!();
162+
Ok(())
163+
}
164+
165+
if let Err(e) = play_inner(args[0], ctx.tpa.as_slice_u8()) {
166+
osprintln!("\nError during playback: {:?}", e);
167+
}
168+
}

0 commit comments

Comments
 (0)