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>>);