Skip to content

Commit 2439089

Browse files
authored
Merge pull request #65 from Neotron-Compute/mixer-fun
Adds mixer command
2 parents 6ccfb83 + 8b9de6c commit 2439089

File tree

4 files changed

+182
-148
lines changed

4 files changed

+182
-148
lines changed

src/commands/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod hardware;
1111
mod input;
1212
mod ram;
1313
mod screen;
14+
mod sound;
1415
mod timedate;
1516

1617
pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
@@ -26,12 +27,11 @@ pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
2627
&ram::RUN_ITEM,
2728
&ram::LOAD_ITEM,
2829
&fs::LOAD_ITEM,
29-
&screen::CLEAR_ITEM,
30-
&screen::BENCH_ITEM,
31-
&screen::FILL_ITEM,
32-
&screen::MANDEL_ITEM,
30+
&screen::CLS_ITEM,
3331
&input::KBTEST_ITEM,
3432
&hardware::SHUTDOWN_ITEM,
33+
&sound::MIXER_ITEM,
34+
&sound::PLAY_ITEM,
3535
],
3636
entry: None,
3737
exit: None,

src/commands/screen.rs

Lines changed: 8 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,18 @@
11
//! Screen-related commands for Neotron OS
22
3-
use neotron_common_bios::video::{Attr, TextBackgroundColour, TextForegroundColour};
3+
use crate::{osprint, Ctx};
44

5-
use crate::{osprint, osprintln, Ctx, API, VGA_CONSOLE};
6-
7-
pub static CLEAR_ITEM: menu::Item<Ctx> = menu::Item {
5+
pub static CLS_ITEM: menu::Item<Ctx> = menu::Item {
86
item_type: menu::ItemType::Callback {
9-
function: clear,
7+
function: cls,
108
parameters: &[],
119
},
12-
command: "screen_clear",
10+
command: "cls",
1311
help: Some("Clear the screen"),
1412
};
1513

16-
pub static FILL_ITEM: menu::Item<Ctx> = menu::Item {
17-
item_type: menu::ItemType::Callback {
18-
function: fill,
19-
parameters: &[],
20-
},
21-
command: "screen_fill",
22-
help: Some("Fill the screen with characters"),
23-
};
24-
25-
pub static BENCH_ITEM: menu::Item<Ctx> = menu::Item {
26-
item_type: menu::ItemType::Callback {
27-
function: bench,
28-
parameters: &[],
29-
},
30-
command: "screen_bench",
31-
help: Some("Time how long to put 1,000,000 characters on the screen, with scrolling."),
32-
};
33-
34-
pub static MANDEL_ITEM: menu::Item<Ctx> = menu::Item {
35-
item_type: menu::ItemType::Callback {
36-
function: mandel,
37-
parameters: &[],
38-
},
39-
command: "screen_mandel",
40-
help: Some("Calculate the Mandelbrot set"),
41-
};
42-
43-
/// Called when the "clear" command is executed.
44-
fn clear(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
45-
let mut guard = VGA_CONSOLE.try_lock().unwrap();
46-
if let Some(vga_console) = guard.as_mut() {
47-
vga_console.clear();
48-
}
49-
}
50-
51-
/// Called when the "fill" command is executed.
52-
fn fill(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
53-
let mut guard = VGA_CONSOLE.try_lock().unwrap();
54-
if let Some(console) = guard.as_mut() {
55-
console.clear();
56-
let api = API.get();
57-
let mode = (api.video_get_mode)();
58-
let (Some(width), Some(height)) = (mode.text_width(), mode.text_height()) else {
59-
osprintln!("Unable to get console size");
60-
return;
61-
};
62-
// A range of printable ASCII compatible characters
63-
let mut char_cycle = (b' '..=b'~').cycle();
64-
let mut remaining = height * width;
65-
66-
// Scroll two screen fulls
67-
'outer: for bg in (0..=7).cycle() {
68-
let bg_colour = TextBackgroundColour::new(bg).unwrap();
69-
for fg in 1..=15 {
70-
if fg == bg {
71-
continue;
72-
}
73-
let fg_colour = TextForegroundColour::new(fg).unwrap();
74-
remaining -= 1;
75-
if remaining == 0 {
76-
break 'outer;
77-
}
78-
let attr = Attr::new(fg_colour, bg_colour, false);
79-
let glyph = char_cycle.next().unwrap();
80-
console.set_attr(attr);
81-
console.write_bstr(&[glyph]);
82-
}
83-
}
84-
let attr = Attr::new(
85-
TextForegroundColour::WHITE,
86-
TextBackgroundColour::BLACK,
87-
false,
88-
);
89-
console.set_attr(attr);
90-
}
91-
}
92-
93-
/// Called when the "bench" command is executed.
94-
fn bench(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
95-
const NUM_CHARS: u64 = 1_000_000;
96-
let mut guard = VGA_CONSOLE.try_lock().unwrap();
97-
if let Some(console) = guard.as_mut() {
98-
let api = API.get();
99-
let start = (api.time_ticks_get)();
100-
console.clear();
101-
let glyphs = &[b'x'];
102-
for _idx in 0..NUM_CHARS {
103-
console.write_bstr(glyphs);
104-
}
105-
let end = (api.time_ticks_get)();
106-
let delta = end.0 - start.0;
107-
let chars_per_second = (NUM_CHARS * (api.time_ticks_per_second)().0) / delta;
108-
osprintln!(
109-
"{} chars in {} ticks, or {} chars per second",
110-
NUM_CHARS,
111-
delta,
112-
chars_per_second
113-
);
114-
}
115-
}
116-
117-
/// Called when the "mandel" command is executed.
118-
fn mandel(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
119-
fn mandelbrot(cx: f64, cy: f64, max_loops: u32) -> u32 {
120-
let mut x = cx;
121-
let mut y = cy;
122-
for i in 1..max_loops {
123-
let x_squared = x * x;
124-
let y_squared = y * y;
125-
if x_squared + y_squared > 4.0 {
126-
return i;
127-
}
128-
let x1 = x_squared - y_squared + cx;
129-
let y1 = (2.0 * x * y) + cy;
130-
x = x1;
131-
y = y1;
132-
}
133-
0
134-
}
135-
136-
let api = API.get();
137-
let mode = (api.video_get_mode)();
138-
let (Some(width), Some(height)) = (mode.text_width(), mode.text_height()) else {
139-
osprintln!("Unable to get screen size");
140-
return;
141-
};
142-
143-
let glyphs = b" .,'~!^:;[/<&?oxOX# ";
144-
for y_pos in 0..height - 2 {
145-
let y = (f64::from(y_pos) * 4.0 / f64::from(height)) - 2.0;
146-
for x_pos in 0..width {
147-
let x = (f64::from(x_pos) * 4.0 / f64::from(width)) - 2.0;
148-
let result = mandelbrot(x, y, 20);
149-
osprint!("{}", glyphs[result as usize] as char);
150-
}
151-
}
14+
/// Called when the "cls" command is executed.
15+
fn cls(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
16+
// Reset SGR, go home, clear screen,
17+
let _ = osprint!("\u{001b}[0m\u{001b}[1;1H\u{001b}[2J");
15218
}

src/commands/sound.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//! Sound related commands for Neotron OS
2+
3+
use crate::{osprint, osprintln, Ctx, API};
4+
5+
pub static MIXER_ITEM: menu::Item<Ctx> = menu::Item {
6+
item_type: menu::ItemType::Callback {
7+
function: mixer,
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+
],
18+
},
19+
command: "mixer",
20+
help: Some("Control the audio mixer"),
21+
};
22+
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+
35+
/// Called when the "mixer" command is executed.
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+
50+
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+
84+
osprintln!("Mixers:");
85+
for mixer_id in 0u8..=255u8 {
86+
match (api.audio_mixer_channel_get_info)(mixer_id) {
87+
neotron_common_bios::FfiOption::Some(mixer_info) => {
88+
let dir_str = match mixer_info.direction {
89+
neotron_common_bios::audio::Direction::Input => "In",
90+
neotron_common_bios::audio::Direction::Loopback => "Loop",
91+
neotron_common_bios::audio::Direction::Output => "Out",
92+
};
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+
}
106+
}
107+
neotron_common_bios::FfiOption::None => {
108+
// Run out of mixers
109+
break;
110+
}
111+
}
112+
}
113+
}
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+
}

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ static STD_INPUT: CsRefCell<StdInput> = CsRefCell::new(StdInput::new());
6666
/// Prints to the screen
6767
#[macro_export]
6868
macro_rules! osprint {
69-
($($arg:tt)*) => {
69+
($($arg:tt)*) => { {
7070
#[allow(unused)]
7171
use core::fmt::Write as _;
7272
let _ = write!(&$crate::CONSOLE, $($arg)*);
73-
}
73+
} }
7474
}
7575

7676
/// Prints to the screen and puts a new-line on the end

0 commit comments

Comments
 (0)