11//! Sound related commands for Neotron OS
22
3- use crate :: { osprintln, Ctx , API } ;
3+ use crate :: { osprint , osprintln, Ctx , API } ;
44
55pub 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+ "\r Played: {}:{} 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 ! ( "\n Error during playback: {:?}" , e) ;
167+ }
168+ }
0 commit comments