1use crate::{
2 errors::{ReaperError, ReaperStaticResult},
3 ptr_wrappers::{MediaItem, MediaItemTake, MediaTrack},
4 utils::{as_c_str, as_c_string, as_string_mut},
5 utils::{make_c_string_buf, WithNull},
6 Color, Immutable, KnowsProject, Mutable, Position, ProbablyMutable,
7 Project, Reaper, Take, TimeMode, Track, Volume, WithReaperPtr, GUID,
8};
9use int_enum::IntEnum;
10use serde_derive::{Deserialize, Serialize};
11use std::{marker::PhantomData, ptr::NonNull, time::Duration};
12
13#[derive(Debug, PartialEq)]
14pub struct Item<'a, T: ProbablyMutable> {
15 project: &'a Project,
16 ptr: MediaItem,
17 should_check: bool,
18 phantom_mut: PhantomData<T>,
19}
20impl<'a, T: ProbablyMutable> WithReaperPtr for Item<'a, T> {
21 type Ptr = MediaItem;
22 fn get_pointer(&self) -> Self::Ptr {
23 self.ptr
24 }
25 fn get(&self) -> Self::Ptr {
26 self.require_valid_2(self.project).unwrap();
27 self.ptr
28 }
29 fn make_unchecked(&mut self) {
30 self.should_check = false;
31 }
32 fn make_checked(&mut self) {
33 self.should_check = true;
34 }
35 fn should_check(&self) -> bool {
36 self.should_check
37 }
38}
39impl<'a, T: ProbablyMutable> KnowsProject for Item<'a, T> {
40 fn project(&self) -> &'a Project {
41 self.project
42 }
43}
44impl<'a, T: ProbablyMutable> Item<'a, T> {
45 pub fn new(project: &'a Project, ptr: MediaItem) -> Self {
46 Self {
47 project,
48 ptr,
49 should_check: true,
50 phantom_mut: PhantomData,
51 }
52 }
53 pub fn track(&self) -> Track<Immutable> {
54 let ptr = unsafe {
55 Reaper::get().low().GetMediaItem_Track(self.get().as_ptr())
56 };
57 match MediaTrack::new(ptr) {
58 None => panic!("Got null ptr! Maybe track is deleted?"),
59 Some(ptr) => Track::new(self.project, ptr),
60 }
61 }
62 pub fn get_take(&'a self, index: usize) -> Option<Take<'a, Immutable>> {
63 let ptr = self.get_take_ptr(index)?;
64 Some(Take::new(ptr, unsafe { std::mem::transmute(self) }))
65 }
66 fn get_take_ptr(&self, index: usize) -> Option<MediaItemTake> {
67 let ptr = unsafe {
68 Reaper::get()
69 .low()
70 .GetTake(self.get().as_ptr(), index as i32)
71 };
72 let ptr = NonNull::new(ptr);
73 match ptr {
74 None => None,
75 x => x,
76 }
77 }
78 pub fn active_take(&'a self) -> Take<'a, Immutable> {
79 let ptr = self
80 .active_take_ptr()
81 .expect("NullPtr, probably, item is deleted");
82 Take::<Immutable>::new(ptr, unsafe { std::mem::transmute(self) })
83 }
84 fn active_take_ptr(&self) -> Option<MediaItemTake> {
85 let ptr =
86 unsafe { Reaper::get().low().GetActiveTake(self.get().as_ptr()) };
87 let ptr = NonNull::new(ptr);
88 match ptr {
89 None => None,
90 x => x,
91 }
92 }
93
94 pub fn is_selected(&self) -> bool {
95 unsafe { Reaper::get().low().IsMediaItemSelected(self.get().as_ptr()) }
96 }
97
98 fn get_info_value(&self, category: impl Into<String>) -> f64 {
99 let mut category = category.into();
100 unsafe {
101 Reaper::get().low().GetMediaItemInfo_Value(
102 self.get().as_ptr(),
103 as_c_str(&category.with_null()).as_ptr(),
104 )
105 }
106 }
107
108 pub fn position(&self) -> Position {
109 self.get_info_value("D_POSITION").into()
110 }
111 pub fn length(&self) -> Duration {
112 Duration::from_secs_f64(self.get_info_value("D_LENGTH"))
113 }
114 pub fn end_position(&self) -> Position {
115 self.position() + self.length().into()
116 }
117 pub fn is_muted(&self) -> bool {
118 self.get_info_value("B_MUTE") != 0.0
119 }
120 pub fn mute_actual(&self) -> bool {
123 self.get_info_value("B_MUTE_ACTUAL") != 0.0
124 }
125 pub fn solo_override(&self) -> ItemSoloOverride {
129 ItemSoloOverride::from_int(self.get_info_value("C_MUTE_SOLO") as i32)
130 .expect("can not convert value to item solo override")
131 }
132 pub fn is_looped(&self) -> bool {
133 self.get_info_value("B_LOOPSRC") != 0.0
134 }
135 pub fn all_takes_play(&self) -> bool {
136 self.get_info_value("B_ALLTAKESPLAY") != 0.0
137 }
138 pub fn time_base(&self) -> TimeMode {
139 TimeMode::from_int(self.get_info_value("C_BEATATTACHMODE") as i32)
140 .expect("Can not convert balue to time mode")
141 }
142 pub fn auto_stretch(&self) -> bool {
143 self.get_info_value("C_AUTOSTRETCH") != 0.0
144 }
145 pub fn locked(&self) -> bool {
146 self.get_info_value("C_LOCK") != 0.0
147 }
148 pub fn volume(&self) -> Volume {
149 Volume::from(self.get_info_value("D_VOL"))
150 }
151 pub fn snap_offset(&self) -> Duration {
152 Duration::from_secs_f64(self.get_info_value("D_SNAPOFFSET"))
153 }
154 pub fn fade_in(&self) -> ItemFade {
155 let length =
156 Duration::from_secs_f64(self.get_info_value("D_FADEINLEN"));
157 let curve = self.get_info_value("D_FADEINDIR");
158 let shape = ItemFadeShape::from_int(
159 self.get_info_value("C_FADEINSHAPE") as i32,
160 )
161 .unwrap();
162 let auto_fade_length = self.get_info_value("D_FADEINLEN_AUTO") != 0.0;
163 ItemFade {
164 length,
165 curve,
166 shape,
167 auto_fade_length,
168 }
169 }
170 pub fn fade_out(&self) -> ItemFade {
171 let auto_fade_length = self.get_info_value("D_FADEOUTLEN_AUTO") != 0.0;
172 let length = if auto_fade_length {
173 Duration::from_secs_f64(self.get_info_value("D_FADEOUTLEN_AUTO"))
174 } else {
175 Duration::from_secs_f64(self.get_info_value("D_FADEOUTLEN"))
176 };
177 let curve = self.get_info_value("D_FADEOUTDIR");
178 let shape = ItemFadeShape::from_int(
179 self.get_info_value("C_FADEOUTSHAPE") as i32,
180 )
181 .unwrap();
182 ItemFade {
183 length,
184 curve,
185 shape,
186 auto_fade_length,
187 }
188 }
189 pub fn group_id(&self) -> usize {
190 self.get_info_value("I_GROUPID") as usize
191 }
192 pub fn y_pos_relative(&self) -> usize {
194 self.get_info_value("I_LASTY") as usize
195 }
196 pub fn y_pos_free_mode(&self) -> f64 {
201 self.get_info_value("F_FREEMODE_Y")
202 }
203 pub fn color(&self) -> Option<Color> {
205 let raw = self.get_info_value("I_CUSTOMCOLOR") as i32;
206 if raw == 0 {
207 return None;
208 }
209 Some(Color::from_native(raw & 0xffffff))
210 }
211 pub fn height_free_mode(&self) -> f64 {
215 self.get_info_value("F_FREEMODE_H")
216 }
217 pub fn height(&self) -> usize {
219 self.get_info_value("I_LASTH") as usize
220 }
221
222 pub fn n_takes(&self) -> usize {
223 unsafe {
224 Reaper::get()
225 .low()
226 .GetMediaItemNumTakes(self.get().as_ptr()) as usize
227 }
228 }
229
230 fn get_info_string(
231 &self,
232 category: impl Into<String>,
233 buf_size: usize,
234 ) -> ReaperStaticResult<String> {
235 let mut category = category.into();
236 let buf = make_c_string_buf(buf_size).into_raw();
237 let result = unsafe {
238 Reaper::get().low().GetSetMediaItemInfo_String(
239 self.get().as_ptr(),
240 as_c_str(category.with_null()).as_ptr(),
241 buf,
242 false,
243 )
244 };
245 match result {
246 false => {
247 Err(ReaperError::UnsuccessfulOperation("Can not get value!"))
248 }
249 true => {
250 Ok(as_string_mut(buf).expect("can not convert buf to string"))
251 }
252 }
253 }
254 pub fn notes(
255 &self,
256 buf_size: impl Into<Option<u32>>,
257 ) -> ReaperStaticResult<String> {
258 let size = buf_size.into().unwrap_or(1024);
259 self.get_info_string("P_NOTES", size as usize)
260 }
261 pub fn guid(&self) -> GUID {
262 let guid_str = self
263 .get_info_string("GUID", 50)
264 .expect("Can not get GUID string");
265 GUID::from_string(guid_str)
266 .expect("Can not convert GUID string to GUID")
267 }
268}
269impl<'a> Item<'a, Mutable> {
270 pub fn add_take(&mut self) -> Take<Mutable> {
271 let ptr = unsafe {
272 Reaper::get().low().AddTakeToMediaItem(self.get().as_ptr())
273 };
274 match MediaItemTake::new(ptr) {
275 None => panic!("can not make Take"),
276 Some(ptr) => Take::new(ptr, self),
277 }
278 }
279 pub fn get_take_mut(&mut self, index: usize) -> Option<Take<'_, Mutable>> {
280 let ptr = self.get_take_ptr(index)?;
281 Some(Take::new(ptr, self))
282 }
283 pub fn active_take_mut(&mut self) -> Take<'_, Mutable> {
284 let ptr = self.active_take_ptr().unwrap();
285 Take::new(ptr, self)
286 }
287
288 pub fn set_position(&mut self, position: impl Into<Position>) {
289 unsafe {
290 Reaper::get().low().SetMediaItemPosition(
291 self.get().as_ptr(),
292 position.into().into(),
293 true,
294 );
295 }
296 }
297 pub fn set_length(&mut self, length: Duration) {
298 unsafe {
299 Reaper::get().low().SetMediaItemLength(
300 self.get().as_ptr(),
301 length.as_secs_f64(),
302 true,
303 );
304 }
305 }
306 pub fn set_end_position(&mut self, end_position: impl Into<Position>) {
307 let length: Duration = (end_position.into() - self.position()).into();
308 unsafe {
309 Reaper::get().low().SetMediaItemLength(
310 self.get().as_ptr(),
311 length.as_secs_f64(),
312 true,
313 );
314 }
315 }
316 pub fn set_selected(&mut self, selected: bool) {
317 unsafe {
318 Reaper::get()
319 .low()
320 .SetMediaItemSelected(self.get().as_ptr(), selected)
321 }
322 }
323 pub fn delete(self) {
324 unsafe {
325 Reaper::get().low().DeleteTrackMediaItem(
326 self.track().get().as_ptr(),
327 self.get().as_ptr(),
328 );
329 }
330 }
331
332 pub fn make_only_selected_item(&mut self) {
333 let mut pr = Project::new(self.project().context());
334 pr.select_all_items(false);
335 self.set_selected(true)
336 }
337
338 pub fn split(
339 self,
340 position: impl Into<Position>,
341 ) -> ReaperStaticResult<ItemSplit<'a>> {
342 let position: f64 = position.into().into();
343 let ptr = unsafe {
344 Reaper::get()
345 .low()
346 .SplitMediaItem(self.get().as_ptr(), position)
347 };
348 let ptr = match MediaItem::new(ptr) {
349 None => {
350 return Err(ReaperError::InvalidObject(
351 "Can not split item, probably, bad position.",
352 ))
353 }
354 Some(ptr) => ptr,
355 };
356 Ok(ItemSplit {
357 left_ptr: self.get(),
358 right_ptr: ptr,
359 project: self.project,
360 })
361 }
362
363 pub fn update(&mut self) {
364 unsafe { Reaper::get().low().UpdateItemInProject(self.get().as_ptr()) }
365 }
366
367 pub fn move_to_track(&self, track_index: usize) -> ReaperStaticResult<()> {
368 let track =
369 Track::<Immutable>::from_index(self.project(), track_index)
370 .ok_or(ReaperError::InvalidObject(
371 "No track with given index!",
372 ))?;
373 let track_ptr = track.get().as_ptr();
374 let result = unsafe {
375 Reaper::get()
376 .low()
377 .MoveMediaItemToTrack(self.get().as_ptr(), track_ptr)
378 };
379 match result {
380 false => {
381 Err(ReaperError::UnsuccessfulOperation("Can not move item."))
382 }
383 true => Ok(()),
384 }
385 }
386
387 fn set_info_value(
388 &mut self,
389 category: impl Into<String>,
390 value: f64,
391 ) -> ReaperStaticResult<()> {
392 let mut category = category.into();
393 let result = unsafe {
394 Reaper::get().low().SetMediaItemInfo_Value(
395 self.get().as_ptr(),
396 as_c_str(category.with_null()).as_ptr(),
397 value,
398 )
399 };
400 match result {
401 true => Ok(()),
402 false => {
403 Err(ReaperError::UnsuccessfulOperation("Can not set value."))
404 }
405 }
406 }
407
408 pub fn set_muted(&mut self, muted: bool) {
409 self.set_info_value("B_MUTE", muted as i32 as f64).unwrap()
410 }
411 pub fn set_mute_actual(&mut self, mute: bool) {
414 self.set_info_value("B_MUTE_ACTUAL", mute as i32 as f64)
415 .unwrap()
416 }
417 pub fn set_solo_override(&mut self, solo: ItemSoloOverride) {
421 self.set_info_value("C_MUTE_SOLO", solo.int_value() as f64)
422 .unwrap()
423 }
424 pub fn set_looped(&mut self, looped: bool) {
425 self.set_info_value("B_LOOPSRC", looped as i32 as f64)
426 .unwrap()
427 }
428 pub fn set_all_takes_play(&mut self, should_play: bool) {
429 self.set_info_value("B_ALLTAKESPLAY", should_play as i32 as f64)
430 .unwrap()
431 }
432 pub fn set_time_base(&mut self, mode: TimeMode) {
433 self.set_info_value("C_BEATATTACHMODE", mode.int_value() as f64)
434 .unwrap()
435 }
436 pub fn set_auto_stretch(&mut self, state: bool) {
437 self.set_info_value("C_AUTOSTRETCH", state as i32 as f64)
438 .unwrap()
439 }
440 pub fn set_locked(&mut self, locked: bool) {
441 self.set_info_value("C_LOCK", locked as i32 as f64).unwrap()
442 }
443 pub fn set_volume(&mut self, volume: Volume) {
444 self.set_info_value("D_VOL", volume.get()).unwrap()
445 }
446 pub fn set_snap_offset(
447 &mut self,
448 offset: Duration,
449 ) -> ReaperStaticResult<()> {
450 self.set_info_value("D_SNAPOFFSET", offset.as_secs_f64())
451 }
452 pub fn set_fade_in(&mut self, fade: ItemFade) -> ReaperStaticResult<()> {
453 self.set_info_value("D_FADEINLEN", fade.length.as_secs_f64())?;
454 self.set_info_value("D_FADEINDIR", fade.curve)?;
455 self.set_info_value("C_FADEINSHAPE", fade.shape.int_value() as f64)?;
456 self.set_info_value(
457 "D_FADEINLEN_AUTO",
458 fade.auto_fade_length as i32 as f64,
459 )?;
460 Ok(())
461 }
462 pub fn set_fade_out(&mut self, fade: ItemFade) -> ReaperStaticResult<()> {
463 self.set_info_value("D_FADEOUTLEN", fade.length.as_secs_f64())?;
464 self.set_info_value("D_FADEOUTDIR", fade.curve)?;
465 self.set_info_value("C_FADEOUTSHAPE", fade.shape.int_value() as f64)?;
466 self.set_info_value(
467 "D_FADEOUTLEN_AUTO",
468 fade.auto_fade_length as i32 as f64,
469 )?;
470 Ok(())
471 }
472 pub fn set_group_id(&mut self, id: usize) -> ReaperStaticResult<()> {
473 self.set_info_value("I_GROUPID", id as f64)
474 }
475 pub fn set_y_pos_free_mode(
480 &mut self,
481 y_pos: f64,
482 ) -> ReaperStaticResult<()> {
483 assert!((0.0..1.0).contains(&y_pos));
484 self.set_info_value("F_FREEMODE_Y", y_pos as f64)
485 }
486 pub fn set_height_free_mode(&mut self, height: f64) {
490 assert!((0.0..1.0).contains(&height));
491 self.set_info_value("F_FREEMODE_H", height).unwrap()
492 }
493 pub fn set_color(&mut self, color: Option<Color>) {
495 let color = match color {
496 None => 0,
497 Some(color) => color.to_native() | 0x1000000,
498 };
499 self.set_info_value("I_CUSTOMCOLOR", color as f64).unwrap()
500 }
501
502 fn set_info_string(
503 &self,
504 category: impl Into<String>,
505 value: impl Into<String>,
506 ) -> ReaperStaticResult<()> {
507 let mut category = category.into();
508 let value = value.into();
509 let buf = as_c_string(&value).into_raw();
510 let result = unsafe {
511 Reaper::get().low().GetSetMediaItemInfo_String(
512 self.get().as_ptr(),
513 as_c_str(category.with_null()).as_ptr(),
514 buf,
515 true,
516 )
517 };
518 match result {
519 false => {
520 Err(ReaperError::UnsuccessfulOperation("Can not get value!"))
521 }
522 true => Ok(()),
523 }
524 }
525 pub fn set_notes(
526 &self,
527 notes: impl Into<String>,
528 ) -> ReaperStaticResult<()> {
529 let notes: String = notes.into();
530 self.set_info_string("P_NOTES", notes)
531 }
532 pub fn set_guid(&self, guid: GUID) -> ReaperStaticResult<()> {
533 let guid_str = guid.to_string();
534 self.set_info_string("GUID", guid_str)
535 }
536}
537
538#[derive(Debug)]
540pub struct ItemSplit<'a> {
541 left_ptr: MediaItem,
542 right_ptr: MediaItem,
543 project: &'a Project,
544}
545impl<'a> ItemSplit<'a> {
546 fn left(&mut self) -> Item<'a, Mutable> {
547 Item::new(self.project, self.left_ptr)
548 }
549 fn right(&mut self) -> Item<'a, Mutable> {
550 Item::new(self.project, self.right_ptr)
551 }
552 pub fn get(mut self) -> (Item<'a, Mutable>, Item<'a, Mutable>) {
553 (self.left(), self.right())
554 }
555}
556
557#[repr(i32)]
562#[derive(
563 Debug, Copy, Clone, Eq, PartialEq, IntEnum, Serialize, Deserialize,
564)]
565pub enum ItemSoloOverride {
566 Soloed = -1,
567 NoOverride = 0,
568 NotSoloed = 1,
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
573pub struct ItemFade {
574 pub length: Duration,
575 pub curve: f64,
576 pub shape: ItemFadeShape,
577 pub auto_fade_length: bool,
579}
580impl ItemFade {
581 pub fn new(
582 length: Duration,
583 curve: f64,
584 shape: ItemFadeShape,
585 auto_fade_length: bool,
586 ) -> Self {
587 Self {
588 length,
589 curve,
590 shape,
591 auto_fade_length,
592 }
593 }
594}
595
596#[repr(i32)]
602#[derive(
603 Debug, Clone, Copy, PartialEq, Eq, IntEnum, Serialize, Deserialize,
604)]
605pub enum ItemFadeShape {
606 Linear = 0,
607 EqualPower = 1,
608 SlightInner = 2,
609 FastEnd = 3,
610 FastStart = 4,
611 SlowStartEnd = 5,
612 Beizer = 6,
613 Default = 7,
614}