rea_rs/
source.rs

1use crate::{
2    ptr_wrappers::PcmSource,
3    utils::{as_string_mut, make_c_string_buf},
4    KnowsProject, Mutable, Position, ProbablyMutable, Project, ProjectContext,
5    Reaper, Take, Volume, WithReaperPtr,
6};
7use chrono::TimeDelta;
8use int_enum::IntEnum;
9use serde_derive::{Deserialize, Serialize};
10use std::{
11    mem::MaybeUninit,
12    ops::{Add, Sub},
13    path::PathBuf,
14    ptr::NonNull,
15    time::Duration,
16};
17
18#[derive(Debug, PartialEq)]
19pub struct Source<'a, T: ProbablyMutable> {
20    take: &'a Take<'a, T>,
21    ptr: PcmSource,
22    should_check: bool,
23}
24impl<'a, T: ProbablyMutable> WithReaperPtr for Source<'a, T> {
25    type Ptr = PcmSource;
26    fn get_pointer(&self) -> Self::Ptr {
27        self.ptr
28    }
29    fn get(&self) -> Self::Ptr {
30        self.require_valid_2(self.take.project()).unwrap();
31        self.get_pointer()
32    }
33    fn make_unchecked(&mut self) {
34        self.should_check = false;
35    }
36    fn make_checked(&mut self) {
37        self.should_check = true;
38    }
39    fn should_check(&self) -> bool {
40        self.should_check
41    }
42}
43impl<'a, T: ProbablyMutable> Source<'a, T> {
44    pub fn new(take: &'a Take<'a, T>, ptr: PcmSource) -> Self {
45        Self {
46            take,
47            ptr,
48            should_check: true,
49        }
50    }
51
52    pub fn take(&self) -> &Take<'a, T> {
53        self.take
54    }
55
56    pub fn filename(&self) -> PathBuf {
57        let size = 500;
58        let buf = make_c_string_buf(size).into_raw();
59        unsafe {
60            Reaper::get().low().GetMediaSourceFileName(
61                self.get().as_ptr(),
62                buf,
63                size as i32,
64            )
65        };
66        PathBuf::from(as_string_mut(buf).expect("Can not retrieve file name"))
67    }
68
69    /// Get source media length.
70    ///
71    /// # Safety
72    ///
73    /// Reaper can return length as quarter notes. Since, there is no very good
74    /// way to determine length in qn as duration, it can fail sometimes.
75    pub fn length(&self) -> Duration {
76        let mut is_qn = MaybeUninit::zeroed();
77        let result = unsafe {
78            Reaper::get()
79                .low()
80                .GetMediaSourceLength(self.get().as_ptr(), is_qn.as_mut_ptr())
81        };
82        match unsafe { is_qn.assume_init() } {
83            true => {
84                let item_start = self.take().item().position();
85                let offset = self.take().start_offset();
86                let start: Position = SourceOffset::from(
87                    TimeDelta::from_std(item_start.as_duration()).unwrap()
88                        - offset.get(),
89                )
90                .into();
91                let start_in_qn = start.as_quarters(self.take().project());
92                let end_in_qn = start_in_qn + result;
93                let end =
94                    Position::from_quarters(end_in_qn, self.take().project());
95                let length = end - start;
96                length.as_duration()
97            }
98            false => Duration::from_secs_f64(result),
99        }
100    }
101
102    pub fn n_channels(&self) -> usize {
103        unsafe {
104            Reaper::get()
105                .low()
106                .GetMediaSourceNumChannels(self.get().as_ptr())
107                as usize
108        }
109    }
110
111    pub fn sample_rate(&self) -> usize {
112        unsafe {
113            Reaper::get()
114                .low()
115                .GetMediaSourceSampleRate(self.get().as_ptr())
116                as usize
117        }
118    }
119
120    /// Source type ("WAV, "MIDI", etc.).
121    pub fn type_string(&self) -> String {
122        let size = 20;
123        let buf = make_c_string_buf(size).into_raw();
124        unsafe {
125            Reaper::get().low().GetMediaSourceType(
126                self.get().as_ptr(),
127                buf,
128                size as i32,
129            )
130        };
131        as_string_mut(buf).expect("Can not convert type to string")
132    }
133
134    pub fn sub_project(&self) -> Option<Project> {
135        let ptr = unsafe {
136            Reaper::get()
137                .low()
138                .GetSubProjectFromSource(self.get().as_ptr())
139        };
140        match NonNull::new(ptr) {
141            None => None,
142            Some(ptr) => Project::new(ProjectContext::Proj(ptr)).into(),
143        }
144    }
145
146    /// If a section/reverse block, retrieves offset/len/reverse.
147    pub fn section_info(&self) -> Option<SourceSectionInfo> {
148        let (mut ofst, mut len, mut rev) = (
149            MaybeUninit::zeroed(),
150            MaybeUninit::zeroed(),
151            MaybeUninit::zeroed(),
152        );
153        let result = unsafe {
154            Reaper::get().low().PCM_Source_GetSectionInfo(
155                self.get().as_ptr(),
156                ofst.as_mut_ptr(),
157                len.as_mut_ptr(),
158                rev.as_mut_ptr(),
159            )
160        };
161        match result {
162            false => None,
163            true => Some(SourceSectionInfo {
164                offset: Duration::from_secs_f64(unsafe { ofst.assume_init() }),
165                length: Duration::from_secs_f64(unsafe { len.assume_init() }),
166                reversed: unsafe { rev.assume_init() },
167            }),
168        }
169    }
170
171    pub fn calculate_normalization(
172        &self,
173        units: SourceNoramlizeUnit,
174        target: Volume,
175        start: SourceOffset,
176        end: SourceOffset,
177    ) -> Volume {
178        let result = unsafe {
179            Reaper::get().low().CalculateNormalization(
180                self.get().as_ptr(),
181                units.int_value(),
182                target.get(),
183                start.as_secs_f64(),
184                end.as_secs_f64(),
185            )
186        };
187        Volume::from(result)
188    }
189}
190impl<'a> Source<'a, Mutable> {
191    pub fn delete(&mut self) {
192        unsafe { Reaper::get().low().PCM_Source_Destroy(self.get().as_ptr()) }
193    }
194}
195
196#[test]
197fn test_source_offset() {
198    let offset = SourceOffset::from_secs_f64(2.0);
199    assert_eq!(offset.as_secs_f64(), 2.0);
200    let offset = SourceOffset::from_secs_f64(-2.0);
201    assert_eq!(offset.as_secs_f64(), -2.0);
202    let offset = SourceOffset::from_secs_f64(-2.543);
203    assert_eq!(offset.as_secs_f64(), -2.543);
204}
205
206#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Copy, Clone)]
207pub struct SourceOffset {
208    offset: TimeDelta,
209}
210impl SourceOffset {
211    pub fn from_secs_f64(secs: f64) -> Self {
212        let duration = Duration::from_secs_f64(secs.abs());
213        let offset = TimeDelta::from_std(duration).unwrap();
214        if secs.is_sign_negative() {
215            Self { offset: -offset }
216        } else {
217            Self { offset }
218        }
219    }
220    pub fn get(&self) -> TimeDelta {
221        self.offset
222    }
223    pub fn as_secs_f64(&self) -> f64 {
224        let seconds = self.offset.num_seconds();
225        let nanoseconds = self.offset.num_microseconds().unwrap();
226        seconds as f64 + (nanoseconds - seconds * 1000000) as f64 / 1000000.0
227    }
228}
229impl From<TimeDelta> for SourceOffset {
230    fn from(value: TimeDelta) -> Self {
231        Self { offset: value }
232    }
233}
234impl From<Position> for SourceOffset {
235    fn from(value: Position) -> Self {
236        Self {
237            offset: TimeDelta::from_std(value.as_duration()).unwrap(),
238        }
239    }
240}
241impl Into<Position> for SourceOffset {
242    fn into(self) -> Position {
243        self.offset.abs().to_std().unwrap().into()
244    }
245}
246impl From<Duration> for SourceOffset {
247    fn from(value: Duration) -> Self {
248        Self {
249            offset: TimeDelta::from_std(value).unwrap(),
250        }
251    }
252}
253impl Into<Duration> for SourceOffset {
254    fn into(self) -> Duration {
255        self.offset.abs().to_std().unwrap()
256    }
257}
258impl Add<SourceOffset> for SourceOffset {
259    type Output = SourceOffset;
260
261    fn add(self, rhs: SourceOffset) -> Self::Output {
262        SourceOffset::from(self.offset + rhs.offset)
263    }
264}
265impl Add<Duration> for SourceOffset {
266    type Output = SourceOffset;
267
268    fn add(self, rhs: Duration) -> Self::Output {
269        SourceOffset::from(self.offset + TimeDelta::from_std(rhs).unwrap())
270    }
271}
272impl Sub<SourceOffset> for SourceOffset {
273    type Output = SourceOffset;
274
275    fn sub(self, rhs: SourceOffset) -> Self::Output {
276        SourceOffset::from(self.offset - rhs.offset)
277    }
278}
279impl Sub<Duration> for SourceOffset {
280    type Output = SourceOffset;
281
282    fn sub(self, rhs: Duration) -> Self::Output {
283        SourceOffset::from(self.offset - TimeDelta::from_std(rhs).unwrap())
284    }
285}
286
287/// If a section/reverse block, retrieves offset/len/reverse.
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct SourceSectionInfo {
290    offset: Duration,
291    length: Duration,
292    reversed: bool,
293}
294
295#[allow(non_camel_case_types)]
296#[repr(i32)]
297#[derive(
298    Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Copy, Clone, IntEnum,
299)]
300pub enum SourceNoramlizeUnit {
301    LUFS_I = 0,
302    RMS_I = 1,
303    Peak = 2,
304    TruePeak = 3,
305    LUFS_M = 4,
306    LUFS_S = 5,
307}