Skip to content

Commit

Permalink
Provide memory buffer -> mixer Chunk conversion methods
Browse files Browse the repository at this point in the history
This basically provides an API to access Mix_QuickLoad_WAV and
Mix_QuickLoad_RAW with one caveat – thie API takes ownership of the
buffers, while Mix_QuickLoad_* functions don't. Since Rust-SDL2 wraps
Mix_Chunks in its own structure with its own buffer ownership flag it's
easy to work around that and deallocate the buffer when Chunk is
dropped.

There are few design considerations here:

* Taking ownership of the buffer. I couldn't come up with a way to
  get a Chunk based on a buffer while still keeping the code safe and
  making sure that as long as Chunk is alive the buffer is also alive
  *and* not making the change be terribly involved and disruptive
* What's the best way to take an ownership of a buffer passed as a
  parameter? I came up with Box<[T]> as it seems universal enough,
  but I expect a more idiomatic Rust way may exist.

I modified the mixer demo to show how the API is used, a beep is
generated when no sound file is passed to the example.
  • Loading branch information
jstasiak committed Feb 6, 2020
1 parent 4ff7e9c commit 6a2ec5b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
21 changes: 18 additions & 3 deletions examples/mixer-demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,24 @@ fn demo(music_file: &Path, sound_file: Option<&Path>) -> Result<(), String> {
println!("music volume => {:?}", sdl2::mixer::Music::get_volume());
println!("play => {:?}", music.play(1));

if let Some(sound_file_path) = sound_file {
let sound_chunk = sdl2::mixer::Chunk::from_file(sound_file_path)
.map_err(|e| format!("Cannot load sound file: {:?}", e))?;
{
let sound_chunk = match sound_file {
Some(sound_file_path) => sdl2::mixer::Chunk::from_file(sound_file_path)
.map_err(|e| format!("Cannot load sound file: {:?}", e))?,
None => {
// One second of 500Hz sine wave using equation A * sin(2 * PI * f * t)
// (played at half the volume to save people's ears).
let buffer = (0..frequency)
.map(|i| {
(0.1 * i16::max_value() as f32
* (2.0 * 3.14 * 500.0 * (i as f32 / frequency as f32)).sin())
as i16
})
.collect();
sdl2::mixer::Chunk::from_raw_buffer(buffer)
.map_err(|e| format!("Cannot load sound file: {:?}", e))?
}
};

println!("chunk volume => {:?}", sound_chunk.get_volume());
println!("playing sound twice");
Expand Down
34 changes: 33 additions & 1 deletion src/sdl2/mixer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,16 @@ pub struct Chunk {
impl Drop for Chunk {
fn drop(&mut self) {
if self.owned {
unsafe { mixer::Mix_FreeChunk(self.raw) }
unsafe {
// Mix_QuickLoad_* functions don't set the allocated flag, but
// from_wav_buffer and from_raw_buffer *do* take ownership of the data,
// so we need to deallocate the buffers here, because Mix_FreeChunk won't
// and we'd be leaking memory otherwise.
if (*self.raw).allocated == 0 {
drop(Box::from_raw((*self.raw).abuf));
}
mixer::Mix_FreeChunk(self.raw);
}
}
}
}
Expand All @@ -238,6 +247,29 @@ impl Chunk {
/// Load file for use as a sample.
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Chunk, String> {
let raw = unsafe { mixer::Mix_LoadWAV_RW(RWops::from_file(path, "rb")?.raw(), 0) };
Self::from_owned_raw(raw)
}

/// Get chunk based on a buffer containing WAV data in the mixer format. The chunk takes
/// ownership of the buffer.
pub fn from_wav_buffer(buffer: Box<[u8]>) -> Result<Chunk, String> {
let raw = unsafe { mixer::Mix_QuickLoad_WAV(Box::into_raw(buffer) as *mut u8) };
Self::from_owned_raw(raw)
}

/// Load chunk from a buffer containing raw audio data in the mixer format. The length of the
/// buffer has to fit in 32-bit unsigned integer. The chunk takes ownership of the buffer.
///
/// It's your responsibility to provide the audio data in the right format, as no conversion
/// will take place when using this method.
pub fn from_raw_buffer<T: AudioFormatNum>(buffer: Box<[T]>) -> Result<Chunk, String> {
use std::mem::size_of;
let len: u32 = (buffer.len() * size_of::<T>()).try_into().unwrap();
let raw = unsafe { mixer::Mix_QuickLoad_RAW(Box::into_raw(buffer) as *mut u8, len) };
Self::from_owned_raw(raw)
}

fn from_owned_raw(raw: *mut mixer::Mix_Chunk) -> Result<Chunk, String> {
if raw.is_null() {
Err(get_error())
} else {
Expand Down

0 comments on commit 6a2ec5b

Please sign in to comment.