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
//! `Sound` is the parent structure for the Playdate audio API, and you can get access specific
//! subsystems through its 'get' methods.
//!
//! For example, to play an audio sample (sound effect):
//!
//! ```rust
//! let sound = Sound::get();
//! let player = sound.get_sample_player()?;
//! let mut sample = sound.load_audio_sample("test.wav")?;
//! player.set_sample(&mut sample)?;
//! player.play(1, 1.0)?;
//! ```
//!
//! To play a music file:
//! ```rust
//! let music = Sound::get().get_file_player()?;
//! music.load_into_player("music.pda")?;
//! music.play(0)?;
//! ```

use crate::{pd_func_caller, pd_func_caller_log};
use crankstart_sys::ctypes;

use anyhow::{anyhow, ensure, Error, Result};
use core::ptr;
use cstr_core::CString;

pub mod sampleplayer;
pub use sampleplayer::{AudioSample, SamplePlayer};
pub mod fileplayer;
pub use fileplayer::FilePlayer;

// When the Playdate system struct is created, it passes the given playdate_sound to Sound::new,
// which then replaces this.
static mut SOUND: Sound = Sound::null();

/// `Sound` is the main interface to the Playdate audio subsystems.
#[derive(Clone, Debug)]
pub struct Sound {
    raw_sound: *const crankstart_sys::playdate_sound,

    // Each audio API subsystem has a struct with all of the relevant functions for that subsystem.
    // These functions are used repeatedly, so pointers to them are stored here for convenience.
    raw_file_player: *const crankstart_sys::playdate_sound_fileplayer,
    raw_sample: *const crankstart_sys::playdate_sound_sample,
    raw_sample_player: *const crankstart_sys::playdate_sound_sampleplayer,
}

// Not implemented: addSource, removeSource, setMicCallback, and getHeadphoneState (waiting on
// crankstart callback strategy), getDefaultChannel, addChannel, removeChannel.
impl Sound {
    const fn null() -> Self {
        Self {
            raw_sound: ptr::null(),
            raw_file_player: ptr::null(),
            raw_sample: ptr::null(),
            raw_sample_player: ptr::null(),
        }
    }

    /// Internal: builds the `Sound` struct from the pointers given in the Playdate SDK after it's started.
    #[allow(clippy::new_ret_no_self)]
    pub(crate) fn new(raw_sound: *const crankstart_sys::playdate_sound) -> Result<()> {
        ensure!(!raw_sound.is_null(), "Null pointer passed to Sound::new");

        // Get supported subsystem pointers.
        let raw_file_player = unsafe { (*raw_sound).fileplayer };
        ensure!(!raw_file_player.is_null(), "Null sound.fileplayer");
        let raw_sample = unsafe { (*raw_sound).sample };
        ensure!(!raw_sample.is_null(), "Null sound.sample");
        let raw_sample_player = unsafe { (*raw_sound).sampleplayer };
        ensure!(!raw_sample_player.is_null(), "Null sound.sampleplayer");

        let sound = Self {
            raw_sound,
            raw_file_player,
            raw_sample,
            raw_sample_player,
        };
        unsafe { SOUND = sound };
        Ok(())
    }

    /// Gets a handle to the Sound system.  This is the primary entry point for users.
    pub fn get() -> Self {
        unsafe { SOUND.clone() }
    }

    /// Get a `FilePlayer` that can be used to stream audio from disk, e.g. for music.
    pub fn get_file_player(&self) -> Result<FilePlayer> {
        let raw_player = pd_func_caller!((*self.raw_file_player).newPlayer)?;
        ensure!(
            !raw_player.is_null(),
            "Null returned from fileplayer.newPlayer"
        );
        FilePlayer::new(self.raw_file_player, raw_player)
    }

    /// Get a `SamplePlayer` that can be used to play sound effects.
    pub fn get_sample_player(&self) -> Result<SamplePlayer> {
        let raw_player = pd_func_caller!((*self.raw_sample_player).newPlayer)?;
        ensure!(
            !raw_player.is_null(),
            "Null returned from sampleplayer.newPlayer"
        );
        SamplePlayer::new(self.raw_sample_player, raw_player)
    }

    /// Loads an `AudioSample` sound effect.  Assign it to a `SamplePlayer` with
    /// `SamplePlayer.set_sample`.
    pub fn load_audio_sample(&self, sample_path: &str) -> Result<AudioSample> {
        let sample_path_c = CString::new(sample_path).map_err(Error::msg)?;
        let arg_ptr = sample_path_c.as_ptr() as *const ctypes::c_char;
        let raw_audio_sample = pd_func_caller!((*self.raw_sample).load, arg_ptr)?;
        ensure!(
            !raw_audio_sample.is_null(),
            "Null returned from sample.load"
        );
        AudioSample::new(self.raw_sample, raw_audio_sample)
    }

    /// Returns the sound engine's current time, in frames, 44.1k per second.
    pub fn get_current_time(&self) -> Result<ctypes::c_uint> {
        pd_func_caller!((*self.raw_sound).getCurrentTime)
    }

    /// Sets which audio outputs should be active.  Note: if you disable headphones and enable
    /// speaker, sound will be played through the speaker even if headphones are plugged in.
    pub fn set_outputs_active(&self, headphone: bool, speaker: bool) -> Result<()> {
        pd_func_caller!(
            (*self.raw_sound).setOutputsActive,
            headphone as ctypes::c_int,
            speaker as ctypes::c_int
        )
    }
}