Skip to content

Commit

Permalink
Implement audio_open_input. Fix ball.c sound output.
Browse files Browse the repository at this point in the history
  • Loading branch information
maximecb committed Sep 10, 2024
1 parent 46924ac commit 335ef65
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 21 deletions.
28 changes: 28 additions & 0 deletions api/syscalls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
10 changes: 10 additions & 0 deletions doc/syscalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
10 changes: 5 additions & 5 deletions ncc/examples/ball.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -110,21 +109,22 @@ 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;
}

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);
}
4 changes: 4 additions & 0 deletions ncc/include/uvm/syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
108 changes: 93 additions & 15 deletions vm/src/audio.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -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<AudioDevice<AudioCB>> = None;
#[derive(Default)]
struct AudioState
{
input_dev: Option<AudioDevice<AudioCB>>,
output_dev: Option<AudioDevice<AudioCB>>,
}

// This is only accessed from the main thread
thread_local! {
static AUDIO_STATE: RefCell<AudioState> = 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();
Expand All @@ -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(),
Expand All @@ -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)
}
3 changes: 2 additions & 1 deletion vm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,7 +57,7 @@ pub const SYSCALL_DESCS: [Option<SysCallDesc>; 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,
Expand Down
8 changes: 8 additions & 0 deletions vm/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<CEvent>());
Expand All @@ -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::<CEvent>());
Expand Down

0 comments on commit 335ef65

Please sign in to comment.