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 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 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 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#[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}