diff --git a/api/syscalls.json b/api/syscalls.json index c776b16..cb88b01 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -738,6 +738,34 @@ "permission": "audio_output", "const_idx": 18, "description": "Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples." + }, + { + "name": "audio_open_input", + "args": [ + [ + "u32", + "sample_rate" + ], + [ + "u16", + "num_channels" + ], + [ + "u16", + "format" + ], + [ + "void*", + "callback" + ] + ], + "returns": [ + "u32", + "device_id" + ], + "permission": "audio_input", + "const_idx": 12, + "description": "Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples." } ], "constants": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index dd7d32c..9113bbe 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -302,6 +302,16 @@ u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callb Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples. +## audio_open_input + +``` +u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callback) +``` + +**Returns:** `u32 device_id` + +Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples. + ## Constants These are the constants associated with the audio subsystem: diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index 9a5d69b..94a463f 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -90,7 +90,6 @@ void update() window_draw_frame(0, frame_buffer); } -/* u16* audio_cb(u16 num_channels, u32 num_samples) { assert(num_channels == 1); @@ -110,7 +109,9 @@ u16* audio_cb(u16 num_channels, u32 num_samples) // The intensity decreases over time u32 intensity = INT16_MAX - (audio_pos * INT16_MAX / AUDIO_LEN); - u32 sawtooth = 4000 * (i % 128) / 128; + u32 period = 128 - (audio_pos * 64 / AUDIO_LEN); + + u32 sawtooth = 4000 * (i % period) / period; AUDIO_BUFFER[i] = intensity * sawtooth / INT16_MAX; ++audio_pos; @@ -118,13 +119,12 @@ u16* audio_cb(u16 num_channels, u32 num_samples) return AUDIO_BUFFER; } -*/ void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0); - anim_event_loop(60, update); + audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); - //audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); + anim_event_loop(60, update); } diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 568152d..115355c 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -97,6 +97,10 @@ // Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples. #define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; } +// u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callback) +// Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples. +#define audio_open_input(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_input; } + // u64 net_listen(const char* listen_addr, void* on_new_conn) // Open a listening TCP socket to accept incoming connections. A callback function is called when a new connection request is received. #define net_listen(__listen_addr, __on_new_conn) asm (__listen_addr, __on_new_conn) -> u64 { syscall net_listen; } diff --git a/vm/src/audio.rs b/vm/src/audio.rs index 1d97504..b1fcfc3 100644 --- a/vm/src/audio.rs +++ b/vm/src/audio.rs @@ -1,5 +1,7 @@ use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice}; use std::sync::{Arc, Weak, Mutex}; +use std::collections::HashMap; +use std::cell::RefCell; use crate::vm::{Value, VM, Thread}; use crate::host::{get_sdl_context}; use crate::constants::*; @@ -37,17 +39,30 @@ impl AudioCallback for AudioCB } } -// TODO: support multiple audio devices -/// We have to keep the audio device alive -/// This is a global variable because it doesn't implement -/// the Send trait, and so can't be referenced from another thread -static mut DEVICE: Option> = None; +#[derive(Default)] +struct AudioState +{ + input_dev: Option>, + output_dev: Option>, +} + +// This is only accessed from the main thread +thread_local! { + static AUDIO_STATE: RefCell = RefCell::new(AudioState::default()); +} -// NOTE: this can only be called from the main thread since it uses SDL -// However, it creates a new thread to generate audio sample, this thread -// could be given a reference to another VM instance pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value { + if thread.id != 0 { + panic!("audio functions should only be called from the main thread"); + } + + AUDIO_STATE.with_borrow(|s| { + if s.output_dev.is_some() { + panic!("audio output device already open"); + } + }); + let sample_rate = sample_rate.as_u32(); let num_channels = num_channels.as_u16(); let format = format.as_u16(); @@ -66,19 +81,82 @@ pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: panic!("for now, only i16, 16-bit signed audio format supported"); } + let desired_spec = AudioSpecDesired { + freq: Some(sample_rate as i32), + channels: Some(num_channels as u8), + samples: Some(1024) // buffer size, 1024 samples + }; + + // Create a new VM thread in which to run the audio callback + let audio_thread = VM::new_thread(&thread.vm); + let sdl = get_sdl_context(); let audio_subsystem = sdl.audio().unwrap(); + let device = audio_subsystem.open_playback(None, &desired_spec, |spec| { + // initialize the audio callback + AudioCB { + num_channels: num_channels.into(), + thread: audio_thread, + cb: cb, + } + }).unwrap(); + + // Start playback + device.resume(); + + // Keep the audio device alive + AUDIO_STATE.with_borrow_mut(|s| { + s.output_dev = Some(device); + }); + + // FIXME: return the device_id (u32) + Value::from(0) +} + +pub fn audio_open_input(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value +{ + if thread.id != 0 { + panic!("audio functions should only be called from the main thread"); + } + + AUDIO_STATE.with_borrow(|s| { + if s.input_dev.is_some() { + panic!("audio input device already open"); + } + }); + + let sample_rate = sample_rate.as_u32(); + let num_channels = num_channels.as_u16(); + let format = format.as_u16(); + let cb = cb.as_u64(); + + if sample_rate != 44100 { + panic!("for now, only 44100Hz sample rate suppored"); + } + + //if num_channels > 2 { + if num_channels != 1 { + panic!("for now, only one output channel supported"); + } + + if format != AUDIO_FORMAT_I16 { + panic!("for now, only i16, 16-bit signed audio format supported"); + } + let desired_spec = AudioSpecDesired { freq: Some(sample_rate as i32), - channels: Some(num_channels as u8), // mono + channels: Some(num_channels as u8), samples: Some(1024) // buffer size, 1024 samples }; // Create a new VM thread in which to run the audio callback let audio_thread = VM::new_thread(&thread.vm); - let device = audio_subsystem.open_playback(None, &desired_spec, |spec| { + let sdl = get_sdl_context(); + let audio_subsystem = sdl.audio().unwrap(); + + let device = audio_subsystem.open_capture(None, &desired_spec, |spec| { // initialize the audio callback AudioCB { num_channels: num_channels.into(), @@ -91,10 +169,10 @@ pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: device.resume(); // Keep the audio device alive - unsafe { - DEVICE = Some(device); - } + AUDIO_STATE.with_borrow_mut(|s| { + s.input_dev = Some(device); + }); - // TODO: return the device_id (u32) - Value::from(0) + // FIXME: return the device_id (u32) + Value::from(1) } diff --git a/vm/src/constants.rs b/vm/src/constants.rs index fb87a41..9559e73 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -18,6 +18,7 @@ pub const GETCHAR: u16 = 8; pub const WINDOW_POLL_EVENT: u16 = 9; pub const WINDOW_DRAW_FRAME: u16 = 10; pub const EXIT: u16 = 11; +pub const AUDIO_OPEN_INPUT: u16 = 12; pub const VM_HEAP_SIZE: u16 = 14; pub const MEMSET32: u16 = 16; pub const VM_GROW_HEAP: u16 = 17; @@ -56,7 +57,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "window_poll_event", const_idx: 9, argc: 1, has_ret: true }), Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }), Some(SysCallDesc { name: "exit", const_idx: 11, argc: 1, has_ret: false }), - None, + Some(SysCallDesc { name: "audio_open_input", const_idx: 12, argc: 4, has_ret: true }), None, Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }), None, diff --git a/vm/src/window.rs b/vm/src/window.rs index 8c53720..ba89c2b 100644 --- a/vm/src/window.rs +++ b/vm/src/window.rs @@ -175,6 +175,10 @@ struct CEvent /// Returns true if an event was read pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value { + if thread.id != 0 { + panic!("window functions should only be called from the main thread"); + } + let p_event = p_event.as_usize(); assert!(p_event != 0); let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::()); @@ -196,6 +200,10 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value /// Blocks until an event is read pub fn window_wait_event(thread: &mut Thread, p_event: Value) { + if thread.id != 0 { + panic!("window functions should only be called from the main thread"); + } + let p_event = p_event.as_usize(); assert!(p_event != 0); let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::());