1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
//! The music control that are suitable to background music.
use std::{ffi::CString, marker::PhantomData, ptr::NonNull};
use self::{pause::Pauser, ty::MusicType};
use crate::{bind, mixer::device::MixDevice, Result, Sdl, SdlError};
pub mod custom;
pub mod pause;
pub mod ty;
/// A fading state of the music.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FadingState {
/// A music is not fading.
None,
/// A music is fading out.
FadingOut,
/// A music is fading in.
FadingIn,
}
/// A music buffer of the audio data.
pub struct MixMusic<'device> {
ptr: NonNull<bind::Mix_Music>,
_phantom: PhantomData<&'device MixDevice<'device>>,
}
impl<'device> MixMusic<'device> {
/// Constructs a music from the file, or `Err` on failure.
///
/// # Panics
///
/// Panics if `file_name` is empty.
pub fn new(_device: &'device MixDevice<'device>, file_name: &str) -> Result<Self> {
let cstr = CString::new(file_name).expect("file_name must not be empty");
let ptr = unsafe { bind::Mix_LoadMUS(cstr.as_ptr()) };
if ptr.is_null() {
Err(SdlError::Others { msg: Sdl::error() })
} else {
Ok(Self {
ptr: NonNull::new(ptr).unwrap(),
_phantom: PhantomData,
})
}
}
/// Returns the type of the music.
pub fn music_type(&self) -> MusicType {
let raw = unsafe { bind::Mix_GetMusicType(self.ptr.as_ptr()) };
MusicType::from_raw(raw)
}
/// Constructs a music from the file with the custom player command, or `Err` on failure.
///
/// The command must handle the
/// signals emitted by the SDL2_mixer:
/// - On stop: `SIGTERM` signal
/// - On pause: `SIGSTOP` signal
/// - On unpause: `SIGCONT` signal
///
/// # Panics
///
/// Panics if `file_name` or `command` is empty.
pub fn with_cmd(
device: &'device MixDevice<'device>,
file_name: &str,
command: &str,
) -> Result<Self> {
let cmd_cstr = CString::new(command).expect("cmd must not be empty");
let ret = unsafe { bind::Mix_SetMusicCMD(cmd_cstr.as_ptr()) };
if ret == -1 {
return Err(SdlError::Others { msg: Sdl::error() });
}
Self::new(device, file_name)
}
/// Plays the music. If a music is already playing, it synchronously waits until the music ends.
/// If `loops` is `None`, the play continues infinitely.
pub fn play(&self, loops: Option<u32>) -> Result<()> {
let ret = unsafe { bind::Mix_PlayMusic(self.ptr.as_ptr(), loops.map_or(-1, |n| n as _)) };
if ret == -1 {
Err(SdlError::Others { msg: Sdl::error() })
} else {
Ok(())
}
}
/// Plays the music with fade-in times in milliseconds and begin times in seconds. If a music is already playing, it synchronously waits until the music ends.
/// If `loops` is `None`, the play continues infinitely.
/// If `begin` is `None`, the play begins from the start.
pub fn fade_in(&self, fade_in: u32, loops: Option<u32>, begin: Option<f64>) -> Result<()> {
let begin = self.music_type().convert_pos(begin.unwrap_or(0.0));
let ret = unsafe {
bind::Mix_FadeInMusicPos(
self.ptr.as_ptr(),
loops.map_or(-1, |n| n as _),
fade_in as _,
begin,
)
};
if ret == -1 {
Err(SdlError::Others { msg: Sdl::error() })
} else {
Ok(())
}
}
/// Sets the music position in seconds, or `Err` on failure.
pub fn set_pos(&self, pos: f64) -> Result<()> {
let pos = self.music_type().convert_pos(pos);
let ret = unsafe { bind::Mix_SetMusicPosition(pos) };
if ret == -1 {
Err(SdlError::Others { msg: Sdl::error() })
} else {
Ok(())
}
}
/// Returns the volume of the music.
pub fn volume(&self) -> u32 {
unsafe { bind::Mix_VolumeMusic(-1) as _ }
}
/// Sets the volume of the music. The `volume` is clamped in `0..=128`.
pub fn set_volume(&self, volume: u32) {
let _ = unsafe { bind::Mix_VolumeMusic(volume.clamp(0, 128) as _) };
}
/// Rewinds the music to the beginning. Rewinding is valid only mod, ogg vorbis, mpeg-1 layer-3, and midi format.
pub fn rewind(&self) {
unsafe { bind::Mix_RewindMusic() }
}
/// Pauses the music until dropping the [`Pauser`].
pub fn pause(&'device mut self) -> Pauser<'device> {
Pauser::pause(self)
}
/// Halts the music.
pub fn halt(&self) {
let _ = unsafe { bind::Mix_HaltMusic() };
}
/// Halts the music with fade-out in milliseconds.
pub fn fade_out(&self, fade_out: u32) -> Result<()> {
let ret = unsafe { bind::Mix_FadeOutMusic(fade_out as _) };
if ret == 0 {
Err(SdlError::Others { msg: Sdl::error() })
} else {
Ok(())
}
}
/// Returns whether the music is playing.
pub fn is_playing(&self) -> bool {
unsafe { bind::Mix_PlayingMusic() == 1 }
}
/// Returns the fading state of the music.
pub fn fading_state(&self) -> FadingState {
match unsafe { bind::Mix_FadingMusic() } {
bind::MIX_NO_FADING => FadingState::None,
bind::MIX_FADING_OUT => FadingState::FadingOut,
bind::MIX_FADING_IN => FadingState::FadingIn,
_ => unreachable!(),
}
}
}
impl Drop for MixMusic<'_> {
fn drop(&mut self) {
unsafe {
bind::Mix_SetMusicCMD(std::ptr::null());
bind::Mix_FreeMusic(self.ptr.as_ptr());
}
}
}