Skip to main content

raylib/core/
audio.rs

1//! Contains code related to audio. [`RaylibAudio`] plays sounds and music.
2
3use crate::{
4    core::AsRawMut,
5    error::{AudioInitError, LoadSoundError, UpdateAudioStreamError},
6    ffi,
7};
8use std::ffi::{CStr, CString};
9use std::marker::PhantomData;
10use std::path::Path;
11
12use super::error::ExportWaveError;
13
14// SOUNDNESS: readonly — P2 (Drop=UnloadWave frees data); no Rust-side slice trusts frameCount.
15make_thin_wrapper_lifetime!(
16    /// CPU-side waveform data loaded into RAM.
17    ///
18    /// A `Wave` holds raw PCM samples (and metadata) without occupying any audio-device
19    /// resources. Convert it to a [`Sound`] via [`RaylibAudio::new_sound_from_wave`] for
20    /// repeated low-latency playback, or export it to disk with [`Wave::export`].
21    ///
22    /// Freed via `UnloadWave` on drop. Lifetime is bound to the owning [`RaylibAudio`].
23    Wave,
24    ffi::Wave,
25    RaylibAudio,
26    ffi::UnloadWave,
27    readonly
28);
29
30// SOUNDNESS: readonly — P2 (Drop=UnloadSound frees stream.buffer/processor). Closes the gap #277 originally targeted.
31make_thin_wrapper_lifetime!(
32    /// Audio-device-ready playable sound sample.
33    ///
34    /// A `Sound` is a fully decoded, GPU/audio-card-buffered sample suitable for repeated
35    /// low-latency playback (e.g. effects and short clips). Load one from a file with
36    /// [`RaylibAudio::new_sound`] or from a [`Wave`] with
37    /// [`RaylibAudio::new_sound_from_wave`].
38    ///
39    /// Freed via `UnloadSound` on drop. Lifetime is bound to the owning [`RaylibAudio`].
40    ///
41    /// # Examples
42    ///
43    /// Load and play a sound effect:
44    ///
45    /// ```rust,no_run
46    /// use raylib::prelude::*;
47    /// let audio = RaylibAudio::init_audio_device().unwrap();
48    /// let sound = audio.new_sound("assets/click.wav").unwrap();
49    /// // Later, trigger playback:
50    /// unsafe { raylib::ffi::PlaySound(*sound) };
51    /// ```
52    ///
53    /// ```compile_fail
54    /// # use raylib::prelude::*;
55    /// fn corrupt(sound: &mut Sound<'_>) {
56    ///     sound.stream.buffer = std::ptr::null_mut(); // no DerefMut on Sound (issue #276)
57    /// }
58    /// ```
59    Sound,
60    ffi::Sound,
61    RaylibAudio,
62    (ffi::UnloadSound),
63    readonly
64);
65// SOUNDNESS: readonly — P2 (Drop=UnloadMusicStream frees stream + ctxData).
66make_thin_wrapper_lifetime!(
67    /// Streamed audio for long-form playback.
68    ///
69    /// `Music` streams data from disk (or memory) in chunks, making it suitable for
70    /// background music or any audio exceeding ~10 seconds. Load via
71    /// [`RaylibAudio::new_music`] and update each frame with the raylib
72    /// `UpdateMusicStream` / `PlayMusicStream` calls.
73    ///
74    /// Freed via `UnloadMusicStream` on drop. Lifetime is bound to the owning
75    /// [`RaylibAudio`].
76    Music,
77    ffi::Music,
78    RaylibAudio,
79    ffi::UnloadMusicStream,
80    readonly
81);
82// SOUNDNESS: readonly — P2 (Drop=UnloadAudioStream frees buffer/processor).
83make_thin_wrapper_lifetime!(
84    /// Low-level raw PCM streaming primitive.
85    ///
86    /// `AudioStream` lets you push arbitrary audio data to the audio device one
87    /// buffer at a time, giving full control over sample rate, bit depth, and channel
88    /// count. This is the replacement for the audio-callback API that was removed in
89    /// raylib 6.0 — instead of registering a callback you periodically check
90    /// `IsAudioStreamProcessed` and push the next buffer of samples.
91    ///
92    /// Create via [`RaylibAudio::new_audio_stream`]. Freed via `UnloadAudioStream` on
93    /// drop. Lifetime is bound to the owning [`RaylibAudio`].
94    ///
95    /// # Examples
96    ///
97    /// Create a stereo 44.1 kHz stream and push silence each frame:
98    ///
99    /// ```rust,no_run
100    /// use raylib::prelude::*;
101    /// let audio = RaylibAudio::init_audio_device().unwrap();
102    /// let stream = audio.new_audio_stream(44100, 16, 2);
103    /// // Push PCM data when the device is ready for more samples.
104    /// let silence: Vec<i16> = vec![0; 4096];
105    /// unsafe { raylib::ffi::UpdateAudioStream(*stream, silence.as_ptr() as *const _, silence.len() as i32) };
106    /// ```
107    AudioStream,
108    ffi::AudioStream,
109    RaylibAudio,
110    ffi::UnloadAudioStream,
111    readonly
112);
113
114/// Owned buffer of decoded PCM samples for a [`Wave`], freed via `UnloadWaveSamples` on drop.
115///
116/// Returned by [`Wave::load_samples`]. Values are normalised to `[-1.0, 1.0]` and always
117/// 32-bit float regardless of the source wave's `sample_size` — raylib does the conversion
118/// on load. Length is `frame_count` samples (channel interleaving follows the underlying
119/// wave's `channels`). Access the underlying slice via [`AsRef::<[f32]>::as_ref`]; the
120/// allocation is freed when this guard drops.
121///
122/// # Examples
123///
124/// ```no_run
125/// use raylib::prelude::*;
126///
127/// let audio = RaylibAudio::init_audio_device().expect("audio init");
128/// let wave = audio.new_wave("assets/click.wav").expect("wave load");
129/// let samples = wave.load_samples();
130/// let pcm: &[f32] = samples.as_ref();
131/// println!("{} samples decoded", pcm.len());
132/// // `samples` drops here → UnloadWaveSamples frees the buffer.
133/// ```
134///
135/// # See also
136///
137/// - [`Wave::load_samples`] — constructor.
138/// - [`Wave`] — owning wave handle whose samples this buffer decodes.
139pub struct WaveSamples(*mut f32, usize);
140
141impl AsRef<[f32]> for WaveSamples {
142    fn as_ref(&self) -> &[f32] {
143        unsafe { std::slice::from_raw_parts(self.0, self.1) }
144    }
145}
146
147impl Drop for WaveSamples {
148    fn drop(&mut self) {
149        unsafe { ffi::UnloadWaveSamples(self.0) }
150    }
151}
152
153/// A marker trait specifying an audio sample (`u8`, `i16`, or `f32`).
154pub trait AudioSample: private::AudioSample {}
155impl<T: private::AudioSample> AudioSample for T {}
156
157mod private {
158    pub trait AudioSample {}
159    impl AudioSample for u8 {}
160    impl AudioSample for i16 {}
161    impl AudioSample for f32 {}
162}
163
164/// Audio subsystem handle — initializes the audio device and owns all audio resources.
165///
166/// `RaylibAudio` is a separate handle from [`RaylibHandle`](crate::core::RaylibHandle).
167/// Obtain one with [`RaylibAudio::init_audio_device`].  All audio resource types
168/// ([`Wave`], [`Sound`], [`Music`], [`AudioStream`]) are lifetime-bound to the
169/// `RaylibAudio` that created them; the Rust borrow checker enforces this statically, so
170/// audio resources cannot outlive the device.
171///
172/// The audio device is closed (`CloseAudioDevice`) when the `RaylibAudio` is dropped.
173///
174/// # Examples
175///
176/// Initialize the audio device and load a sound:
177///
178/// ```rust,no_run
179/// use raylib::prelude::*;
180/// let audio = RaylibAudio::init_audio_device().expect("audio init failed");
181/// let sound = audio.new_sound("assets/click.wav").expect("sound load failed");
182/// // `sound` borrows `audio` and cannot outlive it.
183/// ```
184#[derive(Debug, Clone)]
185pub struct RaylibAudio(PhantomData<()>);
186
187impl RaylibAudio {
188    /// Initializes audio device and context.
189    #[inline]
190    pub fn init_audio_device() -> Result<RaylibAudio, AudioInitError> {
191        unsafe {
192            if ffi::IsAudioDeviceReady() {
193                return Err(AudioInitError::DoubleInit);
194            }
195            ffi::InitAudioDevice();
196            if !ffi::IsAudioDeviceReady() {
197                return Err(AudioInitError::InitFailed);
198            }
199        }
200        Ok(RaylibAudio(PhantomData))
201    }
202
203    /// Checks if audio device is ready.
204    #[inline]
205    #[must_use]
206    pub fn is_audio_device_ready(&self) -> bool {
207        unsafe { ffi::IsAudioDeviceReady() }
208    }
209
210    /// Get master volume (listener)
211    #[inline]
212    #[must_use]
213    pub fn get_master_volume(&self) -> f32 {
214        unsafe { ffi::GetMasterVolume() }
215    }
216
217    /// Sets master volume (listener).
218    #[inline]
219    pub fn set_master_volume(&self, volume: f32) {
220        unsafe { ffi::SetMasterVolume(volume) }
221    }
222
223    /// Sets default audio buffer size for new audio streams.
224    #[inline]
225    pub fn set_audio_stream_buffer_size_default(&self, size: i32) {
226        unsafe {
227            ffi::SetAudioStreamBufferSizeDefault(size);
228        }
229    }
230
231    /// Loads a new sound from file.
232    #[inline]
233    pub fn new_sound<'aud>(&'aud self, filename: &str) -> Result<Sound<'aud>, LoadSoundError> {
234        let c_filename = CString::new(filename).unwrap();
235        let s = unsafe { ffi::LoadSound(c_filename.as_ptr()) };
236        if s.stream.buffer.is_null() {
237            return Err(LoadSoundError::LoadFailed {
238                path: filename.into(),
239            });
240        }
241
242        Ok(Sound(s, self))
243    }
244
245    /// Loads sound from wave data.
246    #[inline]
247    pub fn new_sound_from_wave<'aud>(
248        &'aud self,
249        wave: &Wave,
250    ) -> Result<Sound<'aud>, LoadSoundError> {
251        let s = unsafe { ffi::LoadSoundFromWave(wave.0) };
252        if s.stream.buffer.is_null() {
253            return Err(LoadSoundError::LoadFromWaveFailed);
254        }
255        Ok(Sound(s, self))
256    }
257    /// Loads wave data from file into RAM.
258    #[inline]
259    pub fn new_wave<'aud>(&'aud self, filename: &str) -> Result<Wave<'aud>, LoadSoundError> {
260        let c_filename = CString::new(filename).unwrap();
261        let w = unsafe { ffi::LoadWave(c_filename.as_ptr()) };
262        if w.data.is_null() {
263            return Err(LoadSoundError::LoadWaveFromFileFailed {
264                path: filename.into(),
265            });
266        }
267        Ok(Wave(w, self))
268    }
269
270    /// Load wave from memory buffer, fileType refers to extension: i.e. '.wav'
271    #[inline]
272    pub fn new_wave_from_memory<'aud>(
273        &'aud self,
274        filetype: &str,
275        bytes: &[u8],
276    ) -> Result<Wave<'aud>, LoadSoundError> {
277        let c_filetype = CString::new(filetype).unwrap();
278        let w = unsafe {
279            ffi::LoadWaveFromMemory(c_filetype.as_ptr(), bytes.as_ptr(), bytes.len() as i32)
280        };
281        if w.data.is_null() {
282            return Err(LoadSoundError::Null);
283        };
284        Ok(Wave(w, self))
285    }
286
287    /// Loads music stream from file.
288    #[inline]
289    pub fn new_music<'aud>(&'aud self, filename: &str) -> Result<Music<'aud>, LoadSoundError> {
290        let c_filename = CString::new(filename).unwrap();
291        let m = unsafe { ffi::LoadMusicStream(c_filename.as_ptr()) };
292        if m.stream.buffer.is_null() {
293            return Err(LoadSoundError::LoadMusicFromFileFailed {
294                path: filename.into(),
295            });
296        }
297        Ok(Music(m, self))
298    }
299
300    /// Load music stream from data
301    #[inline]
302    pub fn new_music_from_memory<'aud>(
303        &'aud self,
304        filetype: &str,
305        bytes: &[u8],
306    ) -> Result<Music<'aud>, LoadSoundError> {
307        let c_filetype = CString::new(filetype).unwrap();
308        let w = unsafe {
309            ffi::LoadMusicStreamFromMemory(c_filetype.as_ptr(), bytes.as_ptr(), bytes.len() as i32)
310        };
311        if w.stream.buffer.is_null() {
312            return Err(LoadSoundError::MusicNull);
313        };
314        Ok(Music(w, self))
315    }
316
317    /// Initializes audio stream (to stream raw PCM data).
318    #[inline]
319    #[must_use]
320    pub fn new_audio_stream(
321        &self,
322        sample_rate: u32,
323        sample_size: u32,
324        channels: u32,
325    ) -> AudioStream<'_> {
326        unsafe {
327            AudioStream(
328                ffi::LoadAudioStream(sample_rate, sample_size, channels),
329                self,
330            )
331        }
332    }
333}
334
335impl Drop for RaylibAudio {
336    #[inline]
337    fn drop(&mut self) {
338        unsafe { ffi::CloseAudioDevice() }
339    }
340}
341
342impl Wave<'_> {
343    /// Total number of frames (considering channels)
344    #[inline]
345    #[must_use]
346    pub const fn frame_count(&self) -> u32 {
347        self.0.frameCount
348    }
349    /// Frequency (samples per second)
350    #[inline]
351    #[must_use]
352    pub const fn sample_rate(&self) -> u32 {
353        self.0.sampleRate
354    }
355    /// Bit depth (bits per sample): 8, 16, 32 (24 not supported)
356    #[inline]
357    #[must_use]
358    pub const fn sample_size(&self) -> u32 {
359        self.0.sampleSize
360    }
361    /// Number of channels (1-mono, 2-stereo, ...)
362    #[inline]
363    #[must_use]
364    pub const fn channels(&self) -> u32 {
365        self.0.channels
366    }
367    /// # Safety
368    ///
369    /// Caller takes ownership of the raw wave data and must free it via `UnloadWave`.
370    #[inline]
371    #[must_use]
372    pub unsafe fn inner(self) -> ffi::Wave {
373        let inner = self.0;
374        std::mem::forget(self);
375        inner
376    }
377
378    /// Checks if wave data is valid (data loaded and parameters)
379    #[inline]
380    #[must_use]
381    pub fn is_wave_valid(&self) -> bool {
382        unsafe { ffi::IsWaveValid(self.0) }
383    }
384
385    /// Export wave file. Extension must be .wav or .raw
386    #[inline]
387    pub fn export(&self, filename: impl AsRef<Path>) -> Result<(), ExportWaveError> {
388        let c_filename = CString::new(filename.as_ref().to_string_lossy().as_bytes()).unwrap();
389        let success = unsafe { ffi::ExportWave(self.0, c_filename.as_ptr()) };
390        if success {
391            Ok(())
392        } else {
393            // const WAV: &CStr = c".wav";
394            const QOA: &CStr = c".qoa";
395            // const RAW: &CStr = c".raw";
396            let is_qoa = unsafe { ffi::IsFileExtension(c_filename.as_ptr(), QOA.as_ptr()) };
397            if is_qoa {
398                let samples = self.0.sampleSize as i32;
399                if samples != 16 {
400                    return Err(ExportWaveError::QoaBadSamples(self.0.sampleSize as i32));
401                }
402            }
403            Err(ExportWaveError::ExportFailed)
404        }
405    }
406
407    /*/// Export wave sample data to code (.h)
408    #[inline]
409    pub fn export_wave_as_code(&self, filename: &str) -> bool {
410        let c_filename = CString::new(filename).unwrap();
411        unsafe { ffi::ExportWaveAsCode(self.0, c_filename.as_ptr()) }
412    }*/
413
414    /// Copies a wave to a new wave.
415    #[inline]
416    #[must_use]
417    pub(crate) fn copy(&'_ self) -> Wave<'_> {
418        unsafe { Wave(ffi::WaveCopy(self.0), self.1) }
419    }
420
421    /// Converts wave data to desired format.
422    #[inline]
423    pub fn format(&mut self, sample_rate: i32, sample_size: i32, channels: i32) {
424        unsafe { ffi::WaveFormat(&mut self.0, sample_rate, sample_size, channels) }
425    }
426
427    /// Crops a wave to defined sample range.
428    #[inline]
429    pub fn crop(&mut self, init_sample: i32, final_sample: i32) {
430        unsafe { ffi::WaveCrop(&mut self.0, init_sample, final_sample) }
431    }
432
433    /// Load samples data from wave as a floats array
434    /// NOTE 1: Returned sample values are normalized to range [-1..1]
435    /// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples()
436    #[inline]
437    #[must_use]
438    pub fn load_samples(&self) -> WaveSamples {
439        WaveSamples(
440            unsafe { ffi::LoadWaveSamples(self.0) },
441            self.frameCount as usize,
442        )
443    }
444}
445
446impl Sound<'_> {
447    /// Checks if a sound is valid (data loaded and buffers initialized)
448    #[inline]
449    #[must_use]
450    pub fn is_sound_valid(&self) -> bool {
451        unsafe { ffi::IsSoundValid(self.0) }
452    }
453
454    /// Total number of frames (considering channels)
455    #[inline]
456    #[must_use]
457    pub const fn frame_count(&self) -> u32 {
458        self.0.frameCount
459    }
460    /// # Safety
461    ///
462    /// Caller takes ownership of the raw sound data and must free it via `UnloadSound`.
463    #[inline]
464    #[must_use]
465    pub unsafe fn inner(self) -> ffi::Sound {
466        let inner = self.0;
467        std::mem::forget(self);
468        inner
469    }
470
471    /// Plays a sound.
472    #[inline]
473    pub fn play(&self) {
474        unsafe { ffi::PlaySound(self.0) }
475    }
476
477    /// Pauses a sound.
478    #[inline]
479    pub fn pause(&self) {
480        unsafe { ffi::PauseSound(self.0) }
481    }
482
483    /// Resumes a paused sound.
484    #[inline]
485    pub fn resume(&self) {
486        unsafe { ffi::ResumeSound(self.0) }
487    }
488
489    /// Stops playing a sound.
490    #[inline]
491    pub fn stop(&self) {
492        unsafe { ffi::StopSound(self.0) }
493    }
494
495    /// Checks if a sound is currently playing.
496    #[inline]
497    #[must_use]
498    pub fn is_playing(&self) -> bool {
499        unsafe { ffi::IsSoundPlaying(self.0) }
500    }
501
502    /// Sets volume for a sound (`1.0` is max level).
503    #[inline]
504    pub fn set_volume(&self, volume: f32) {
505        unsafe { ffi::SetSoundVolume(self.0, volume) }
506    }
507
508    /// Sets pitch for a sound (`1.0` is base level).
509    #[inline]
510    pub fn set_pitch(&self, pitch: f32) {
511        unsafe { ffi::SetSoundPitch(self.0, pitch) }
512    }
513
514    /// Set pan for a sound (0.5 is center)
515    #[inline]
516    pub fn set_pan(&self, pan: f32) {
517        unsafe { ffi::SetSoundPan(self.0, pan) }
518    }
519
520    /// Updates sound buffer with new data.
521    /// **Notes** (iann):
522    /// 1. raylib’s `UpdateSound` is a raw `memcpy` without size checks, we add safety checks to  here to prevent invalid memory writes.
523    ///     - potential upstream raylib discussion: "too many frames" doesn't exist for the `Sound`'s `AudioStream`
524    ///     - potential upstream raylib discussion: adding sampleSize checks for the `memcpy`
525    /// 2. raylib's `Sound`'s `AudioStream` always gets 32-bit sample size (so we always catch non-32-bit `Sound`'s with a `SampleSizeMismatch`)
526    ///     - 32-bit fixed in config here: <https://github.com/raysan5/raylib/blob/master/src/config.h#L282>
527    ///     - device format set here: <https://github.com/raysan5/raylib/blob/master/src/raudio.c#L288>
528    ///     - potential upstream raylib discussion: allowing for other samplesSizes for `Sound`
529    #[inline]
530    pub fn update<T: AudioSample>(&mut self, data: &[T]) -> Result<(), UpdateAudioStreamError> {
531        let expected_sample_size_bits =
532            usize::try_from(self.stream.sampleSize).expect("sampleSize should be 8, 16, or 32");
533        let provided_sample_size_bits = size_of::<T>() * u8::BITS as usize;
534        if provided_sample_size_bits != expected_sample_size_bits {
535            return Err(UpdateAudioStreamError::SampleSizeMismatch {
536                expected: expected_sample_size_bits,
537                provided: provided_sample_size_bits,
538            });
539        }
540        let max_frame_count = usize::try_from(self.frameCount)
541            .expect("frameCount should be a valid memory allocation size");
542        let provided_frame_count = data.len();
543        if provided_frame_count > max_frame_count {
544            return Err(UpdateAudioStreamError::TooManyFrames {
545                max: max_frame_count,
546                provided: provided_frame_count,
547            });
548        }
549        unsafe {
550            ffi::UpdateSound(
551                self.0,
552                data.as_ptr() as *const std::os::raw::c_void,
553                provided_frame_count.try_into().unwrap(),
554            );
555        }
556        Ok(())
557    }
558}
559
560impl SoundAlias<'_, '_> {
561    /// Checks if a sound is valid (data loaded and buffers initialized)
562    #[inline]
563    #[must_use]
564    pub fn is_sound_valid(&self) -> bool {
565        unsafe { ffi::IsSoundValid(self.0) }
566    }
567
568    /// Total number of frames (considering channels)
569    #[inline]
570    #[must_use]
571    pub const fn frame_count(&self) -> u32 {
572        self.0.frameCount
573    }
574    /// # Safety
575    ///
576    /// Caller takes ownership of the raw sound alias data and must free it via `UnloadSoundAlias`.
577    #[must_use]
578    pub unsafe fn inner(self) -> ffi::Sound {
579        let inner = self.0;
580        std::mem::forget(self);
581        inner
582    }
583
584    /// Plays a sound.
585    #[inline]
586    pub fn play(&self) {
587        unsafe { ffi::PlaySound(self.0) }
588    }
589
590    /// Pauses a sound.
591    #[inline]
592    pub fn pause(&self) {
593        unsafe { ffi::PauseSound(self.0) }
594    }
595
596    /// Resumes a paused sound.
597    #[inline]
598    pub fn resume(&self) {
599        unsafe { ffi::ResumeSound(self.0) }
600    }
601
602    /// Stops playing a sound.
603    #[inline]
604    pub fn stop(&self) {
605        unsafe { ffi::StopSound(self.0) }
606    }
607
608    /// Checks if a sound is currently playing.
609    #[inline]
610    #[must_use]
611    pub fn is_playing(&self) -> bool {
612        unsafe { ffi::IsSoundPlaying(self.0) }
613    }
614
615    /// Sets volume for a sound (`1.0` is max level).
616    #[inline]
617    pub fn set_volume(&self, volume: f32) {
618        unsafe { ffi::SetSoundVolume(self.0, volume) }
619    }
620
621    /// Sets pitch for a sound (`1.0` is base level).
622    #[inline]
623    pub fn set_pitch(&self, pitch: f32) {
624        unsafe { ffi::SetSoundPitch(self.0, pitch) }
625    }
626
627    /// Set pan for a sound (0.5 is center)
628    #[inline]
629    pub fn set_pan(&self, pan: f32) {
630        unsafe { ffi::SetSoundPan(self.0, pan) }
631    }
632}
633
634impl Drop for SoundAlias<'_, '_> {
635    fn drop(&mut self) {
636        unsafe { ffi::UnloadSoundAlias(self.0) }
637    }
638}
639
640impl Music<'_> {
641    /// Starts music playing.
642    #[inline]
643    pub fn play_stream(&self) {
644        unsafe { ffi::PlayMusicStream(self.0) }
645    }
646
647    /// Updates buffers for music streaming.
648    #[inline]
649    pub fn update_stream(&self) {
650        unsafe { ffi::UpdateMusicStream(self.0) }
651    }
652
653    /// Stops music playing.
654    #[inline]
655    pub fn stop_stream(&self) {
656        unsafe { ffi::StopMusicStream(self.0) }
657    }
658
659    /// Pauses music playing.
660    #[inline]
661    pub fn pause_stream(&self) {
662        unsafe { ffi::PauseMusicStream(self.0) }
663    }
664
665    /// Resumes playing paused music.
666    #[inline]
667    pub fn resume_stream(&self) {
668        unsafe { ffi::ResumeMusicStream(self.0) }
669    }
670
671    /// Checks if music is playing.
672    #[inline]
673    #[must_use]
674    pub fn is_stream_playing(&self) -> bool {
675        unsafe { ffi::IsMusicStreamPlaying(self.0) }
676    }
677
678    /// Sets volume for music (`1.0` is max level).
679    #[inline]
680    pub fn set_volume(&self, volume: f32) {
681        unsafe { ffi::SetMusicVolume(self.0, volume) }
682    }
683
684    /// Sets pitch for music (`1.0` is base level).
685    #[inline]
686    pub fn set_pitch(&self, pitch: f32) {
687        unsafe { ffi::SetMusicPitch(self.0, pitch) }
688    }
689
690    /// Gets music time length in seconds.
691    #[inline]
692    #[must_use]
693    pub fn get_time_length(&self) -> f32 {
694        unsafe { ffi::GetMusicTimeLength(self.0) }
695    }
696
697    /// Gets current music time played in seconds.
698    #[inline]
699    #[must_use]
700    pub fn get_time_played(&self) -> f32 {
701        unsafe { ffi::GetMusicTimePlayed(self.0) }
702    }
703
704    /// Seek music to a position (in seconds)
705    #[inline]
706    pub fn seek_stream(&self, position: f32) {
707        unsafe { ffi::SeekMusicStream(self.0, position) }
708    }
709
710    /// Set pan for a music (0.5 is center)
711    #[inline]
712    pub fn set_pan(&self, pan: f32) {
713        unsafe { ffi::SetMusicPan(self.0, pan) }
714    }
715
716    /// Set whether the music stream loops when it reaches the end.
717    #[inline]
718    pub fn set_looping(&mut self, looping: bool) {
719        // SAFETY: looping is an inline bool — not a trusted count or owned pointer.
720        unsafe {
721            self.as_raw_mut().looping = looping;
722        }
723    }
724
725    /// Checks if a music stream is valid (context and buffers initialized)
726    #[inline]
727    #[must_use]
728    pub fn is_music_valid(&self) -> bool {
729        unsafe { ffi::IsMusicValid(self.0) }
730    }
731}
732
733impl AudioStream<'_> {
734    /// Checks if an audio stream is valid (buffers initialized)
735    #[inline]
736    #[must_use]
737    pub fn is_audio_stream_valid(&self) -> bool {
738        unsafe { ffi::IsAudioStreamValid(self.0) }
739    }
740    /// Frequency (samples per second)
741    #[inline]
742    #[must_use]
743    pub const fn sample_rate(&self) -> u32 {
744        self.0.sampleRate
745    }
746    /// Bit depth (bits per sample): 8, 16, 32 (24 not supported)
747    #[inline]
748    #[must_use]
749    pub const fn sample_size(&self) -> u32 {
750        self.0.sampleSize
751    }
752    /// Number of channels (1-mono, 2-stereo, ...)
753    #[inline]
754    #[must_use]
755    pub const fn channels(&self) -> u32 {
756        self.0.channels
757    }
758
759    /// # Safety
760    ///
761    /// Caller takes ownership of the raw audio stream and must free it via `UnloadAudioStream`.
762    #[must_use]
763    pub unsafe fn inner(self) -> ffi::AudioStream {
764        let inner = self.0;
765        std::mem::forget(self);
766        inner
767    }
768
769    /// Updates audio stream buffers with data.
770    #[inline]
771    pub fn update<T: AudioSample>(&mut self, data: &[T]) -> Result<(), UpdateAudioStreamError> {
772        let expected_sample_size =
773            usize::try_from(self.sampleSize).expect("sampleSize should be 8, 16, or 32");
774        let provided_sample_size_bits = size_of::<T>() * u8::BITS as usize;
775        if provided_sample_size_bits != expected_sample_size {
776            return Err(UpdateAudioStreamError::SampleSizeMismatch {
777                expected: expected_sample_size,
778                provided: provided_sample_size_bits,
779            });
780        }
781        let provided_frame_count = data.len();
782
783        unsafe {
784            ffi::UpdateAudioStream(
785                self.0,
786                data.as_ptr() as *const std::os::raw::c_void,
787                provided_frame_count.try_into().unwrap(),
788            );
789        }
790        Ok(())
791    }
792
793    /// Plays audio stream.
794    #[inline]
795    pub fn play(&self) {
796        unsafe {
797            ffi::PlayAudioStream(self.0);
798        }
799    }
800
801    /// Pauses audio stream.
802    #[inline]
803    pub fn pause(&self) {
804        unsafe {
805            ffi::PauseAudioStream(self.0);
806        }
807    }
808
809    /// Resumes audio stream.
810    #[inline]
811    pub fn resume(&self) {
812        unsafe {
813            ffi::ResumeAudioStream(self.0);
814        }
815    }
816
817    /// Checks if audio stream is currently playing.
818    #[inline]
819    #[must_use]
820    pub fn is_playing(&self) -> bool {
821        unsafe { ffi::IsAudioStreamPlaying(self.0) }
822    }
823
824    /// Stops audio stream.
825    #[inline]
826    pub fn stop(&self) {
827        unsafe {
828            ffi::StopAudioStream(self.0);
829        }
830    }
831
832    /// Sets volume for audio stream (`1.0` is max level).
833    #[inline]
834    pub fn set_volume(&self, volume: f32) {
835        unsafe {
836            ffi::SetAudioStreamVolume(self.0, volume);
837        }
838    }
839
840    /// Sets pitch for audio stream (`1.0` is base level).
841    #[inline]
842    pub fn set_pitch(&self, pitch: f32) {
843        unsafe {
844            ffi::SetAudioStreamPitch(self.0, pitch);
845        }
846    }
847
848    /// Sets pitch for audio stream (`1.0` is base level).
849    #[inline]
850    #[must_use]
851    pub fn is_processed(&self) -> bool {
852        unsafe { ffi::IsAudioStreamProcessed(self.0) }
853    }
854
855    /// Set pan for audio stream (0.5 is centered)
856    #[inline]
857    pub fn set_pan(&self, pan: f32) {
858        unsafe {
859            ffi::SetAudioStreamPan(self.0, pan);
860        }
861    }
862}
863
864impl<'bind> Sound<'bind> {
865    /// Clone sound from existing sound data, clone does not own wave data
866    // NOTE: Wave data must be unallocated manually and will be shared across all clones
867    pub fn alias<'snd>(&'snd self) -> Result<SoundAlias<'snd, 'bind>, LoadSoundError> {
868        let s = unsafe { ffi::LoadSoundAlias(self.0) };
869        if s.stream.buffer.is_null() {
870            return Err(LoadSoundError::LoadFromWaveFailed);
871        }
872        Ok(SoundAlias(s, PhantomData))
873    }
874}
875
876/// A lightweight alias handle to a [`Sound`] that shares the same audio buffer without owning it.
877///
878/// Returned by [`Sound::alias`]. Wraps `LoadSoundAlias` — the source [`Sound`] owns the audio
879/// buffer and remains responsible for unloading it; the alias holds an independent playback
880/// head (volume, pitch, pan, play position) so multiple overlapping instances of the same
881/// sound can play concurrently without allocating a fresh decoded buffer per voice.
882///
883/// The `'snd` lifetime borrows the parent [`Sound`] (statically enforced) so the alias
884/// cannot outlive the buffer it points at; the `'bind` lifetime tracks the parent's audio
885/// device.
886///
887/// # Examples
888///
889/// ```no_run
890/// use raylib::prelude::*;
891///
892/// let audio = RaylibAudio::init_audio_device().expect("audio init");
893/// let sound = audio.new_sound("assets/click.wav").expect("sound load");
894/// let voice_a = sound.alias().expect("alias");
895/// let voice_b = sound.alias().expect("alias");
896/// // `voice_a` and `voice_b` can be played simultaneously and tuned independently.
897/// ```
898///
899/// # See also
900///
901/// - [`Sound::alias`] — constructor.
902/// - [`Sound`] — owning sound handle whose buffer this alias shares.
903pub struct SoundAlias<'snd, 'bind>(ffi::Sound, PhantomData<&'snd Sound<'bind>>);