fmod/core/channel/
playback_control.rs

1// Copyright (c) 2024 Lily Lyons
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7use fmod_sys::*;
8use std::ffi::{c_float, c_int, c_uint};
9
10use crate::{Channel, ChannelGroup, TimeUnit};
11
12impl Channel {
13    /// Sets the frequency or playback rate.
14    ///
15    /// Default frequency is determined by the audio format of the Sound or DSP.
16    ///
17    /// Sounds opened as [`SoundMode::CreateSample`] (not [`SoundMode::CreateStream`] or [`SoundMode::CreateCompressedSample`]) can be played backwards by giving a negative frequency.
18    pub fn set_frequency(&self, frequency: c_float) -> Result<()> {
19        unsafe { FMOD_Channel_SetFrequency(self.inner, frequency).to_result() }
20    }
21
22    /// Retrieves the playback frequency or playback rate.
23    pub fn get_frequency(&self) -> Result<c_float> {
24        let mut frequency = 0.0;
25        unsafe { FMOD_Channel_GetFrequency(self.inner, &mut frequency).to_result()? }
26        Ok(frequency)
27    }
28    /// Sets the priority used for virtual voice ordering.
29    ///
30    /// Priority is used as a coarse grain control for the virtual voice system, lower priority [`Channel`]s will always be stolen before higher ones.
31    /// For [`Channel`]s of equal priority, those with the quietest [`ChannelControl::get_audibility`] value will be stolen first.
32    ///
33    /// See the Virtual Voices guide for more information.
34    pub fn set_priority(&self, priority: c_int) -> Result<()> {
35        unsafe { FMOD_Channel_SetPriority(self.inner, priority).to_result() }
36    }
37
38    /// Retrieves the priority used for virtual voice ordering.
39    ///
40    /// Priority is used as a coarse grain control for the virtual voice system, lower priority [`Channel`]s will always be stolen before higher ones.
41    /// For [`Channel`]s of equal priority, those with the quietest [`ChannelControl::get_audibility`] value will be stolen first.
42    ///
43    ///See the Virtual Voices guide for more information.
44    pub fn get_priority(&self) -> Result<c_int> {
45        let mut priority = 0;
46        unsafe { FMOD_Channel_GetPriority(self.inner, &mut priority).to_result()? }
47        Ok(priority)
48    }
49
50    /// Sets the current playback position.
51    ///
52    /// Certain [`TimeUnit`] types are always available: [`TimeUnit::PCM`], [`TimeUnit::PCMBytes`] and [`TimeUnit::MS`].
53    /// The others are format specific such as [`TimeUnit::ModOrder`] / [`TimeUnit::ModRow`] / [`TimeUnit::ModPattern`] which is specific to files of type MOD / S3M / XM / IT.
54    ///
55    /// If playing a Sound created with [`System::create_stream`] or [`SoundMode::CreateStream`] changing the position may cause a slow reflush operation while the file seek and decode occurs.
56    /// You can avoid this by creating the stream with [`SoundMode::Nonblocking`].
57    /// This will cause the stream to go into FMOD_OPENSTATE_SETPOSITION state (see Sound::getOpenState) and Sound commands will return [`FMOD_RESULT::FMOD_ERR_NOTREADY`].
58    /// [`Channel::get_position`] will also not update until this non-blocking set position operation has completed.
59    ///
60    /// Using a VBR source that does not have an associated seek table or seek information (such as MP3 or MOD/S3M/XM/IT) may cause inaccurate seeking if you specify [`TimeUnit::MS`] or [`TimeUnit::PCM`].
61    /// If you want FMOD to create a PCM vs bytes seek table so that seeking is accurate, you will have to specify [`SoundMode::AccurrateTime`] when loading or opening the sound.
62    /// This means there is a slight delay as FMOD scans the whole file when loading the sound to create this table.
63    pub fn set_position(&self, position: c_uint, time_unit: TimeUnit) -> Result<()> {
64        unsafe { FMOD_Channel_SetPosition(self.inner, position, time_unit.into()).to_result() }
65    }
66
67    /// Retrieves the current playback position.
68    ///
69    /// Certain [`TimeUnit`] types are always available: [`TimeUnit::PCM`], [`TimeUnit::PCMBytes`] and [`TimeUnit::MS`].
70    /// The others are format specific such as [`TimeUnit::ModOrder`] / [`TimeUnit::ModRow`] / [`TimeUnit::ModPattern`] which is specific to files of type MOD / S3M / XM / IT.
71    ///
72    /// If [`TimeUnit::MS`] or [`TimeUnit::PCMBytes`] are used, the value is internally converted from [`TimeUnit::PCM`], so the retrieved value may not exactly match the set value.
73    pub fn get_position(&self, time_unit: TimeUnit) -> Result<c_uint> {
74        let mut position = 0;
75        unsafe {
76            FMOD_Channel_GetPosition(self.inner, &mut position, time_unit.into()).to_result()?;
77        }
78        Ok(position)
79    }
80
81    /// Sets the [`ChannelGroup`] this object outputs to.
82    ///
83    /// A [`ChannelGroup`] may contain many Channels.
84    ///
85    /// [`Channel`]s may only output to a single [`ChannelGroup`]. This operation will remove it from the previous group first.
86    pub fn set_channel_group(&self, channel_group: ChannelGroup) -> Result<()> {
87        unsafe { FMOD_Channel_SetChannelGroup(self.inner, channel_group.into()).to_result() }
88    }
89
90    /// Retrieves the [`ChannelGroup`] this object outputs to.
91    pub fn get_channel_group(&self) -> Result<ChannelGroup> {
92        let mut channel_group = std::ptr::null_mut();
93        unsafe {
94            FMOD_Channel_GetChannelGroup(self.inner, &mut channel_group).to_result()?;
95        }
96        Ok(channel_group.into())
97    }
98
99    /// Sets the number of times to loop before stopping.
100    ///
101    /// The 'mode' of the Sound or Channel must be [`SoundMode::LoopNormal`] or [`SoundMode::LoopBidi`] for this function to work.
102    pub fn set_loop_count(&self, loop_count: c_int) -> Result<()> {
103        unsafe { FMOD_Channel_SetLoopCount(self.inner, loop_count).to_result() }
104    }
105
106    /// Retrieves the number of times to loop before stopping.
107    ///
108    /// This is the current loop countdown value that will decrement as it plays until reaching 0.
109    /// Reset with [`Channel::set_loop_count`].
110    pub fn get_loop_count(&self) -> Result<c_int> {
111        let mut loop_count = 0;
112        unsafe { FMOD_Channel_GetLoopCount(self.inner, &mut loop_count).to_result()? }
113        Ok(loop_count)
114    }
115
116    /// Sets the loop start and end points.
117    ///
118    /// Loop points may only be set on a Channel playing a Sound, not a Channel playing a DSP (See System::playDSP).
119    ///
120    /// Valid [`TimeUnit`] types are [`TimeUnit::PCM`], [`TimeUnit::MS`], [`TimeUnit::PCMBytes`]. Any other time units return [`FMOD_RESULT::FMOD_ERR_FORMAT`].
121    /// If [`TimeUnit::MS`] or [`TimeUnit::PCMBytes`], the value is internally converted to [`TimeUnit::PCM`].
122    ///
123    /// The Channel's mode must be set to [`SoundMode::LoopNormal`] or [`SoundMode::LoopBidi`] for loop points to affect playback.
124    pub fn set_loop_points(
125        &self,
126        start: c_uint,
127        start_type: TimeUnit,
128        end: c_uint,
129        end_type: TimeUnit,
130    ) -> Result<()> {
131        unsafe {
132            FMOD_Channel_SetLoopPoints(self.inner, start, start_type.into(), end, end_type.into())
133                .to_result()
134        }
135    }
136
137    /// Retrieves the loop start and end points.
138    ///
139    /// Valid [`TimeUnit`] types are [`TimeUnit::PCM`], [`TimeUnit::MS`], [`TimeUnit::PCMBytes`]. Any other time units return [`FMOD_RESULT::FMOD_ERR_FORMAT`].
140    /// If [`TimeUnit::MS`] or [`TimeUnit::PCMBytes`] are used, the value is internally converted from [`TimeUnit::PCM`], so the retrieved value may not exactly match the set value.
141    pub fn get_loop_points(
142        &self,
143        start_type: TimeUnit,
144        end_type: TimeUnit,
145    ) -> Result<(c_uint, c_uint)> {
146        let mut start = 0;
147        let mut end = 0;
148        unsafe {
149            FMOD_Channel_GetLoopPoints(
150                self.inner,
151                &mut start,
152                start_type.into(),
153                &mut end,
154                end_type.into(),
155            )
156            .to_result()?;
157        }
158        Ok((start, end))
159    }
160}