rea_rs/
item.rs

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    /// muted (ignores solo). setting this value will not affect
121    /// [Item::solo_override].
122    pub fn mute_actual(&self) -> bool {
123        self.get_info_value("B_MUTE_ACTUAL") != 0.0
124    }
125    /// Basically, a way to solo particular item.
126    ///
127    /// This function will not override the same parameters on other items
128    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    /// Y-position (relative to top of track) in pixels
193    pub fn y_pos_relative(&self) -> usize {
194        self.get_info_value("I_LASTY") as usize
195    }
196    /// Y-position (relative to top of track) in pixels when track is in free
197    /// mode
198    ///
199    /// 0 → top of track, 1 → bottom of track (will never be 1)
200    pub fn y_pos_free_mode(&self) -> f64 {
201        self.get_info_value("F_FREEMODE_Y")
202    }
203    /// if None → default.
204    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    /// Height in pixels, when track is in free mode
212    ///
213    /// 0 → no height (will never be), 1 → full track.
214    pub fn height_free_mode(&self) -> f64 {
215        self.get_info_value("F_FREEMODE_H")
216    }
217    /// height in pixels
218    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    /// muted (ignores solo). setting this value will not affect
412    /// [Item::solo_override].
413    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    /// Basically, a way to solo particular item.
418    ///
419    /// This function will not override the same parameters on other items
420    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    /// Y-position (relative to top of track) in pixels when track is in free
476    /// mode
477    ///
478    /// 0 → top of track, 1 → bottom of track (will never be 1)
479    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    /// Height in pixels, when track is in free mode
487    ///
488    /// 0 → no height (will never be), 1 → full track.
489    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    /// if None → default.
494    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/// Holds two new items after [Item::split].
539#[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/// Item personal solo state.
558///
559/// If soloed → other items and tracks will be not
560/// overrided
561#[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/// Item FadeIn\FadeOut parameters.
572#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
573pub struct ItemFade {
574    pub length: Duration,
575    pub curve: f64,
576    pub shape: ItemFadeShape,
577    /// if true — length is not counted.
578    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/// Item fade-in\fade-out shape.
597///
598/// # Note
599///
600/// Shape affects curve attribute of [ItemFade]
601#[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}