Provide memory buffer -> mixer Chunk conversion methods

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.
This commit is contained in:
Jakub Stasiak
2020-02-07 00:39:07 +01:00
parent 4ff7e9ce79
commit 9dde795e3f
2 changed files with 53 additions and 4 deletions
+18 -3
View File
@@ -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 get chunk from buffer: {:?}", e))?
}
};
println!("chunk volume => {:?}", sound_chunk.get_volume());
println!("playing sound twice");
+35 -1
View File
@@ -21,6 +21,7 @@
//! ```
use std::marker::PhantomData;
use std::convert::TryInto;
use std::default;
use std::fmt;
use std::ffi::{CString, CStr};
@@ -29,6 +30,7 @@ use std::borrow::ToOwned;
use std::path::Path;
use libc::c_void;
use libc::{c_int, c_double, c_uint};
use ::audio::AudioFormatNum;
use ::get_error;
use ::rwops::RWops;
use ::version::Version;
@@ -229,7 +231,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);
}
}
}
}
@@ -238,6 +249,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 {