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
179
180
use crate::{pd_func_caller, pd_func_caller_log};
use crankstart_sys::ctypes;

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

/// Note: Make sure you hold on to a FilePlayer until the file has played as much as you want,
/// because dropping it will stop playback.
#[derive(Debug)]
pub struct FilePlayer {
    raw_subsystem: *const crankstart_sys::playdate_sound_fileplayer,
    raw_player: *mut crankstart_sys::FilePlayer,
}

impl Drop for FilePlayer {
    fn drop(&mut self) {
        // Use _log to leak rather than fail
        pd_func_caller_log!((*self.raw_subsystem).freePlayer, self.raw_player);
    }
}

// Not implemented: newPlayer (use Sound::get_file_player), setFinishCallback and fadeVolume
// (waiting on crankstart callback strategy), and setLoopRange (does not seem to do anything).
impl FilePlayer {
    pub(crate) fn new(
        raw_subsystem: *const crankstart_sys::playdate_sound_fileplayer,
        raw_player: *mut crankstart_sys::FilePlayer,
    ) -> Result<Self> {
        ensure!(
            !raw_subsystem.is_null(),
            "Null pointer given as subsystem to FilePlayer::new"
        );
        ensure!(
            !raw_player.is_null(),
            "Null pointer given as player to FilePlayer::new"
        );
        Ok(Self {
            raw_subsystem,
            raw_player,
        })
    }

    /// Loads the given file into the player.  Unlike with SamplePlayer, you must give the
    /// compiled audio filename here, e.g. "file.pda" instead of "file.wav".  MP3 files are
    /// not compiled, so they keep their original .mp3 extension.
    pub fn load_into_player(&self, file_path: &str) -> Result<()> {
        let file_path_c = CString::new(file_path).map_err(Error::msg)?;
        let arg_ptr = file_path_c.as_ptr() as *const ctypes::c_char;
        let result = pd_func_caller!(
            (*self.raw_subsystem).loadIntoPlayer,
            self.raw_player,
            arg_ptr
        )?;
        if result == 1 {
            Ok(())
        } else {
            Err(anyhow!(
                "load_into_player given nonexistent file '{}'",
                file_path
            ))
        }
    }

    /// Play the file 'repeat_count' times; if 0, play until `stop` is called.  See set_loop_range
    /// for the portion of the file that will repeat.
    pub fn play(&self, repeat_count: ctypes::c_int) -> Result<()> {
        let result = pd_func_caller!((*self.raw_subsystem).play, self.raw_player, repeat_count,)?;
        if result == 1 {
            Ok(())
        } else {
            Err(anyhow!(
                "fileplayer.play should return 1; returned {}",
                result
            ))
        }
    }

    /// Can be used to stop a played file early, or stop one that's repeating endlessly because
    /// 'repeat' was set to 0.
    pub fn stop(&self) -> Result<()> {
        pd_func_caller!((*self.raw_subsystem).stop, self.raw_player)
    }

    /// Pause playback.  To resume playback at the same point, use play().
    pub fn pause(&self) -> Result<()> {
        pd_func_caller!((*self.raw_subsystem).pause, self.raw_player)
    }

    /// Returns whether the player is currently playing the file.
    pub fn is_playing(&self) -> Result<bool> {
        let result = pd_func_caller!((*self.raw_subsystem).isPlaying, self.raw_player)?;
        Ok(result == 1)
    }

    /// How much audio to buffer, in seconds.  Larger buffers use more memory but help avoid
    /// underruns, which can cause stuttering (see set_stop_on_underrun).
    pub fn set_buffer_length(&self, length: f32) -> Result<()> {
        pd_func_caller!(
            (*self.raw_subsystem).setBufferLength,
            self.raw_player,
            length
        )
    }

    /// If set to true, and the buffer runs out of data (known as an underrun), the player
    /// will stop playing.  If false (the default), the player will continue playback as soon
    /// as more data is available; this will come across as audio stuttering, particularly
    /// with small buffer sizes.  (Note that Inside Playdate with C says the reverse, but
    /// seems wrong.)
    pub fn set_stop_on_underrun(&self, stop: bool) -> Result<()> {
        pd_func_caller!(
            (*self.raw_subsystem).setStopOnUnderrun,
            self.raw_player,
            stop as ctypes::c_int
        )
    }

    /// Returns whether the buffer has underrun.
    pub fn did_underrun(&self) -> Result<bool> {
        let result = pd_func_caller!((*self.raw_subsystem).didUnderrun, self.raw_player)?;
        Ok(result == 1)
    }

    /// Returns the current offset into the file, in seconds, increasing as it plays.
    pub fn get_offset(&self) -> Result<f32> {
        pd_func_caller!((*self.raw_subsystem).getOffset, self.raw_player)
    }

    /// Set how far into the file to start playing, in seconds.
    pub fn set_offset(&self, offset: f32) -> Result<()> {
        pd_func_caller!((*self.raw_subsystem).setOffset, self.raw_player, offset)
    }

    /// Gets the current volume of the left and right audio channels, out of 1.
    pub fn get_volume(&self) -> Result<(f32, f32)> {
        let mut left = 0.0;
        let mut right = 0.0;
        pd_func_caller!(
            (*self.raw_subsystem).getVolume,
            self.raw_player,
            &mut left,
            &mut right,
        )?;
        Ok((left, right))
    }

    /// Sets the volume of the left and right audio channels, out of 1.
    pub fn set_volume(&self, left: f32, right: f32) -> Result<()> {
        pd_func_caller!(
            (*self.raw_subsystem).setVolume,
            self.raw_player,
            left,
            right
        )
    }

    /// Gets the current playback speed.
    pub fn get_rate(&self) -> Result<f32> {
        pd_func_caller!((*self.raw_subsystem).getRate, self.raw_player)
    }

    /// Sets the playback speed of the player; 1.0 is normal speed, 0.5 is down an octave,
    /// 2.0 is up one, etc.
    pub fn set_rate(&self, playback_speed: f32) -> Result<()> {
        ensure!(
            playback_speed >= 0.0,
            "FilePlayer cannot play in reverse (playback_speed < 0)"
        );
        pd_func_caller!(
            (*self.raw_subsystem).setRate,
            self.raw_player,
            playback_speed
        )
    }

    /// Returns the length of the loaded file, in seconds.
    pub fn get_length(&self) -> Result<f32> {
        pd_func_caller!((*self.raw_subsystem).getLength, self.raw_player)
    }
}