From b4b9f9e857effc5f34eeb4670c1793ff4fc68aa4 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sat, 12 Nov 2022 08:57:33 +0100 Subject: [PATCH 01/16] Medium-level API: Add PPQ functions midi_get_ppq_pos_start_of_measure midi_get_ppq_pos_end_of_measure midi_get_proj_qn_from_ppq_pos midi_get_ppq_pos_from_proj_qn midi_get_proj_time_from_ppq_pos midi_get_ppq_pos_from_proj_time every PPQ function is unsafe, because of take requirement Fixing naming convencions Co-authored-by: helgoboss rebasing to master Replaced all mut to ptr --- main/medium/src/misc_newtypes.rs | 59 ++++++++++++++ main/medium/src/reaper.rs | 134 ++++++++++++++++++++++++++++--- 2 files changed, 181 insertions(+), 12 deletions(-) diff --git a/main/medium/src/misc_newtypes.rs b/main/medium/src/misc_newtypes.rs index 0b2ab6ef..6456edd1 100644 --- a/main/medium/src/misc_newtypes.rs +++ b/main/medium/src/misc_newtypes.rs @@ -1118,6 +1118,65 @@ impl TryFrom for PositionInQuarterNotes { } } +/// This represents a position expressed as an amount of MIDI ticks (PPQ). +/// +/// Can be negative, see [`PositionInSeconds`](struct.PositionInSeconds.html). +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default, Display)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(try_from = "f64") +)] +pub struct PositionInPpq(pub(crate) f64); + +impl PositionInPpq { + /// Position at 0.0 quarter notes. + pub const ZERO: PositionInPpq = PositionInPpq(0.0); + + fn is_valid(value: f64) -> bool { + !value.is_infinite() && !value.is_nan() + } + + /// Creates a value. + /// + /// # Panics + /// + /// This function panics if the given value is a special number. + pub fn new(value: f64) -> PositionInPpq { + assert!( + Self::is_valid(value), + "{} is not a valid PositionInQn value", + value + ); + PositionInPpq(value) + } + + /// Creates a PositionInQn value without bound checking. + /// + /// # Safety + /// + /// You must ensure that the given value is not a special number. + pub unsafe fn new_unchecked(value: f64) -> PositionInPpq { + PositionInPpq(value) + } + + /// Returns the wrapped value. + pub const fn get(self) -> f64 { + self.0 + } +} + +impl TryFrom for PositionInPpq { + type Error = TryFromGreaterError; + + fn try_from(value: f64) -> Result { + if !Self::is_valid(value) { + return Err(TryFromGreaterError::new("value must be non-special", value)); + } + Ok(PositionInPpq(value)) + } +} + /// This represents a volume measured in decibel. #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default, Display)] #[cfg_attr( diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 5a2beeb9..3ce12ab6 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -15,18 +15,18 @@ use crate::{ MessageBoxType, MidiImportBehavior, MidiInput, MidiInputDeviceId, MidiOutput, MidiOutputDeviceId, NativeColor, NormalizedPlayRate, NotificationBehavior, OwnedPcmSource, OwnedReaperPitchShift, OwnedReaperResample, PanMode, ParamId, PcmSource, PitchShiftMode, - PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, PositionInBeats, PositionInQuarterNotes, - PositionInSeconds, Progress, ProjectContext, ProjectRef, PromptForActionResult, ReaProject, - ReaperFunctionError, ReaperFunctionResult, ReaperNormalizedFxParamValue, ReaperPanLikeValue, - ReaperPanValue, ReaperPointer, ReaperStr, ReaperString, ReaperStringArg, ReaperVersion, - ReaperVolumeValue, ReaperWidthValue, RecordArmMode, RecordingInput, RequiredViewMode, - ResampleMode, SectionContext, SectionId, SendTarget, SetTrackUiFlags, SoloMode, - StuffMidiMessageTarget, TakeAttributeKey, TimeModeOverride, TimeRangeType, TrackArea, - TrackAttributeKey, TrackDefaultsBehavior, TrackEnvelope, TrackFxChainType, TrackFxLocation, - TrackLocation, TrackMuteOperation, TrackPolarityOperation, TrackRecArmOperation, - TrackSendAttributeKey, TrackSendCategory, TrackSendDirection, TrackSendRef, TrackSoloOperation, - TransferBehavior, UiRefreshBehavior, UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, - WindowContext, + PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, PositionInBeats, PositionInPpq, + PositionInQuarterNotes, PositionInSeconds, Progress, ProjectContext, ProjectRef, + PromptForActionResult, ReaProject, ReaperFunctionError, ReaperFunctionResult, + ReaperNormalizedFxParamValue, ReaperPanLikeValue, ReaperPanValue, ReaperPointer, ReaperStr, + ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, ReaperWidthValue, + RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, SectionContext, SectionId, + SendTarget, SetTrackUiFlags, SoloMode, StuffMidiMessageTarget, TakeAttributeKey, + TimeModeOverride, TimeRangeType, TrackArea, TrackAttributeKey, TrackDefaultsBehavior, + TrackEnvelope, TrackFxChainType, TrackFxLocation, TrackLocation, TrackMuteOperation, + TrackPolarityOperation, TrackRecArmOperation, TrackSendAttributeKey, TrackSendCategory, + TrackSendDirection, TrackSendRef, TrackSoloOperation, TransferBehavior, UiRefreshBehavior, + UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext, }; use helgoboss_midi::ShortMessage; @@ -1696,6 +1696,116 @@ impl Reaper { PositionInQuarterNotes::new(qn) } + /// Returns the MIDI tick (PPQ) position corresponding to the start of the measure. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid take. + pub unsafe fn midi_get_ppq_pos_start_of_measure( + &self, + take: MediaItemTake, + ppq: PositionInPpq, + ) -> PositionInPpq + where + UsageScope: AnyThread, + { + let ppq = self.low.MIDI_GetPPQPos_StartOfMeasure(take.as_ptr(), ppq.0); + PositionInPpq::new(ppq) + } + + /// Returns the MIDI tick (ppq) position corresponding to the start of the measure. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid take. + pub unsafe fn midi_get_ppq_pos_end_of_measure( + &self, + take: MediaItemTake, + ppq: PositionInPpq, + ) -> PositionInPpq + where + UsageScope: AnyThread, + { + let ppq = self.low.MIDI_GetPPQPos_EndOfMeasure(take.as_ptr(), ppq.0); + PositionInPpq::new(ppq) + } + + /// Converts the given ppq in take to a project quarter-note position. + /// + /// Quarter notes are counted from the start of the project, regardless of any partial measures. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid take. + pub unsafe fn midi_get_proj_qn_from_ppq_pos( + &self, + take: MediaItemTake, + ppqpos: PositionInPpq, + ) -> PositionInQuarterNotes + where + UsageScope: AnyThread, + { + let qn = self.low.MIDI_GetProjQNFromPPQPos(take.as_ptr(), ppqpos.0); + PositionInQuarterNotes::new(qn) + } + + /// Converts the given project quarter-note position to take PPQ. + /// + /// Quarter notes are counted from the start of the project, regardless of any partial measures. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid take. + pub unsafe fn midi_get_ppq_pos_from_proj_qn( + &self, + take: MediaItemTake, + qn: PositionInQuarterNotes, + ) -> PositionInPpq + where + UsageScope: AnyThread, + { + let ppq = self.low.MIDI_GetPPQPosFromProjQN(take.as_ptr(), qn.0); + PositionInPpq::new(ppq) + } + + /// Converts the given ppq in take to a seconds from the project start. + /// + /// Time is counted from the start of the project, regardless of any partial measures. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid take. + pub unsafe fn midi_get_proj_time_from_ppq_pos( + &self, + take: MediaItemTake, + ppqpos: PositionInPpq, + ) -> PositionInSeconds + where + UsageScope: AnyThread, + { + let seconds = self.low.MIDI_GetProjTimeFromPPQPos(take.as_ptr(), ppqpos.0); + PositionInSeconds::new(seconds) + } + + /// Converts the given project time in seconds to the take PPQ + /// + /// Time is counted from the start of the project, regardless of any partial measures. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid take. + pub unsafe fn midi_get_ppq_pos_from_proj_time( + &self, + take: MediaItemTake, + time: PositionInSeconds, + ) -> PositionInPpq + where + UsageScope: AnyThread, + { + let ppq = self.low.MIDI_GetPPQPosFromProjTime(take.as_ptr(), time.0); + PositionInPpq::new(ppq) + } + /// Gets the arrange view start/end time for the given screen coordinates. /// /// Set both `screen_x_start` and `screen_x_end` to 0 to get the full arrange view's start/end From 4dee3b4ede0ff4f2c22dea228ecd3609cf2bc82c Mon Sep 17 00:00:00 2001 From: Timofey Date: Wed, 2 Nov 2022 02:07:48 +0100 Subject: [PATCH 02/16] added reaper_medium::Reaper::time_map_2_qn_to_measure also introduced the new struct `TimeMapQNToMeasuresResult` for storing bar start\end positions renamed to time_map (without 2) and again --- main/medium/src/reaper.rs | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 3ce12ab6..c2ac2780 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -1548,6 +1548,54 @@ impl Reaper { PositionInSeconds::new(tpos) } + /// Converts the given quarter-note position to measure index + /// and returnes also measure bounds in quarter notes. + /// + /// # Panics + /// + /// Panics if the given project is not valid anymore. + pub fn time_map_qn_to_measure( + &self, + project: ProjectContext, + qn: PositionInQuarterNotes, + ) -> TimeMapQNToMeasuresResult + where + UsageScope: AnyThread, + { + self.require_valid_project(project); + unsafe { self.time_map_qn_to_measure_unchecked(project, qn) } + } + + /// Like [`time_map_qn_to_measure()`] but doesn't check if project is valid. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid project. + /// + /// [`time_map_qn_to_measure()`]: #method.time_map_qn_to_measure + pub unsafe fn time_map_qn_to_measure_unchecked( + &self, + project: ProjectContext, + qn: PositionInQuarterNotes, + ) -> TimeMapQNToMeasuresResult + where + UsageScope: AnyThread, + { + let mut start_qn = MaybeUninit::zeroed(); + let mut end_qn = MaybeUninit::zeroed(); + let measure = self.low.TimeMap_QNToMeasures( + project.to_raw(), + qn.0, + start_qn.as_mut_ptr(), + end_qn.as_mut_ptr(), + ); + TimeMapQNToMeasuresResult{ + measure_index: measure, + start_qn: PositionInQuarterNotes::new(start_qn.assume_init()), + end_qn: PositionInQuarterNotes::new(end_qn.assume_init()), + } + } + /// Converts the given quarter-note position to time. /// /// # Panics @@ -7297,6 +7345,16 @@ pub struct TimeMapGetMeasureInfoResult { pub tempo: Bpm, } +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct TimeMapQNToMeasuresResult { + /// Measue index in project. + pub measure_index: i32, + /// Start position of the measure in quarter notes. + pub start_qn: PositionInQuarterNotes, + /// End position of the measure in quarter notes. + pub end_qn: PositionInQuarterNotes, +} + /// Time signature. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct TimeSignature { From 2274dcab2232cbc41503ba8d172af1361503adee Mon Sep 17 00:00:00 2001 From: Timofey Date: Wed, 2 Nov 2022 04:12:08 +0100 Subject: [PATCH 03/16] basics arithmetic for DurationInQuarterNotes --- main/medium/src/misc_newtypes.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/main/medium/src/misc_newtypes.rs b/main/medium/src/misc_newtypes.rs index 6456edd1..a6901270 100644 --- a/main/medium/src/misc_newtypes.rs +++ b/main/medium/src/misc_newtypes.rs @@ -1118,7 +1118,32 @@ impl TryFrom for PositionInQuarterNotes { } } -/// This represents a position expressed as an amount of MIDI ticks (PPQ). +impl std::ops::Add for PositionInQuarterNotes { + fn add(self, rhs: Self) -> Self { + PositionInQuarterNotes::new(self.get() + rhs.get()) + } + type Output = Self; +} +impl std::ops::Sub for PositionInQuarterNotes { + fn sub(self, rhs: Self) -> Self { + PositionInQuarterNotes::new(self.get() - rhs.get()) + } + type Output = Self; +} +impl std::ops::Div for PositionInQuarterNotes { + fn div(self, rhs: Self) -> Self { + PositionInQuarterNotes::new(self.get() / rhs.get()) + } + type Output = Self; +} +impl std::ops::Mul for PositionInQuarterNotes { + fn mul(self, rhs: Self) -> Self { + PositionInQuarterNotes::new(self.get() * rhs.get()) + } + type Output = Self; +} + +/// This represents a position expressed as an amount of midi ticks (PPQ). /// /// Can be negative, see [`PositionInSeconds`](struct.PositionInSeconds.html). #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default, Display)] From c16fca4a70e4b082a0e6da1b3c8aea6b5e512188 Mon Sep 17 00:00:00 2001 From: Timofey Date: Wed, 9 Nov 2022 02:25:44 +0100 Subject: [PATCH 04/16] started MIDI-functions (for issue) --- main/medium/src/reaper.rs | 243 +++++++++++++++++++++++++++++++++++++- test/test/src/tests.rs | 55 +++++++++ 2 files changed, 297 insertions(+), 1 deletion(-) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index c2ac2780..95b98bd2 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -1589,7 +1589,7 @@ impl Reaper { start_qn.as_mut_ptr(), end_qn.as_mut_ptr(), ); - TimeMapQNToMeasuresResult{ + TimeMapQNToMeasuresResult { measure_index: measure, start_qn: PositionInQuarterNotes::new(start_qn.assume_init()), end_qn: PositionInQuarterNotes::new(end_qn.assume_init()), @@ -5731,6 +5731,237 @@ impl Reaper { NonNull::new(ptr).ok_or(ReaperFunctionError::new("couldn't get MIDI editor take")) } + /// Create a new MIDI media item, containing no MIDI events. + /// + /// Time is in seconds unless time_in_qn is true. + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid track. + pub unsafe fn create_midi_item_in_proj( + &self, + track: MediaTrack, + starttime: f64, + endtime: f64, + time_in_qn: bool, + ) -> ReaperFunctionResult + where + UsageScope: MainThreadOnly, + { + self.require_main_thread(); + let ptr = self + .low + .CreateNewMIDIItemInProj(track.as_ptr(), starttime, endtime, &time_in_qn); + NonNull::new(ptr).ok_or_else(|| { + ReaperFunctionError::new("couldn't create MediaItem (maybe track is invalid)") + }) + } + + /// returns false if there are no plugins on the track that support MIDI programs, + /// or if all programs have been enumerated + pub fn enum_track_midi_program_names( + &self, + track: i32, + program_number: i32, + buffer_size: u32, + ) -> Option + where + UsageScope: MainThreadOnly, + { + unsafe { + let (program_name, status) = with_string_buffer(buffer_size, |buffer, max_size| { + self.low + .EnumTrackMIDIProgramNames(track, program_number, buffer, max_size) + }); + match status { + true => Some(program_name), + false => None, + } + } + } + + /// returns false if there are no plugins on the track that support MIDI programs, + /// or if all programs have been enumerated + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid project or track. + pub unsafe fn enum_track_midi_program_names_ex( + &self, + project: ProjectContext, + track: MediaTrack, + program_number: i32, + buffer_size: u32, + ) -> Option + where + UsageScope: MainThreadOnly, + { + self.require_valid_project(project); + self.enum_track_midi_program_names_ex_unchecked(project, track, program_number, buffer_size) + } + + /// returns false if there are no plugins on the track that support MIDI programs, + /// or if all programs have been enumerated + /// + /// # Safety + /// + /// REAPER can crash if you pass an invalid project or track. + pub unsafe fn enum_track_midi_program_names_ex_unchecked( + &self, + project: ProjectContext, + track: MediaTrack, + program_number: i32, + buffer_size: u32, + ) -> Option + where + UsageScope: MainThreadOnly, + { + let (program_name, status) = with_string_buffer(buffer_size, |buffer, max_size| { + self.low.EnumTrackMIDIProgramNamesEx( + project.to_raw(), + track.as_ptr(), + program_number, + buffer, + max_size, + ) + }); + match status { + true => Some(program_name), + false => None, + } + } + + /// Gets note name for the note, if set on track. + pub fn get_track_midi_note_name<'a>( + &self, + track_index: u32, + pitch: u32, + channel: u32, + ) -> Option<&'a ReaperStr> + where + UsageScope: MainThreadOnly, + { + let ptr = self + .low + .GetTrackMIDINoteName(track_index as i32, pitch as i32, channel as i32); + unsafe { create_passing_c_str(ptr) } + } + + /// Gets note name for the note, if set on track. + /// + /// # Safety + /// + /// REAPER can crash, if you pass an invalid track. + pub unsafe fn get_track_midi_note_name_ex<'a>( + &self, + project: ProjectContext, + track: MediaTrack, + pitch: u32, + channel: u32, + ) -> Option<&'a ReaperStr> + where + UsageScope: MainThreadOnly, + { + self.require_valid_project(project); + self.get_track_midi_note_name_ex_unchecked(project, track, pitch, channel) + } + + /// Gets note name for the note, if set on track. + /// + /// # Safety + /// + /// REAPER can crash, if you pass an invalid track. + pub unsafe fn get_track_midi_note_name_ex_unchecked<'a>( + &self, + project: ProjectContext, + track: MediaTrack, + pitch: u32, + channel: u32, + ) -> Option<&'a ReaperStr> + where + UsageScope: MainThreadOnly, + { + let ptr = self.low.GetTrackMIDINoteNameEx( + project.to_raw(), + track.as_ptr(), + pitch as i32, + channel as i32, + ); + create_passing_c_str(ptr) + } + + /// Assignes name to the midi note or CC on the entire track. + /// + /// channel < 0 assigns these note names to all channels. + pub fn set_track_midi_note_name<'a>( + &self, + track: u32, + pitch: u32, + channel: i32, + name: impl Into>, + ) -> bool + where + UsageScope: MainThreadOnly, + { + unsafe { + self.low.SetTrackMIDINoteName( + track as i32, + pitch as i32, + channel as i32, + name.into().as_ptr(), + ) + } + } + + // Assignes name to the midi note or CC on the entire track. + /// + /// channel < 0 assigns these note names to all channels. + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid track. + pub unsafe fn set_track_midi_note_name_ex<'a>( + &self, + project: ProjectContext, + track: MediaTrack, + pitch: u32, + channel: i32, + name: impl Into>, + ) -> bool + where + UsageScope: MainThreadOnly, + { + self.project_is_valid(project); + self.set_track_midi_note_name_ex_unchecked(project, track, pitch, channel, name) + } + + // Assignes name to the midi note or CC on the entire track. + /// + /// channel < 0 assigns these note names to all channels. + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid track. + pub unsafe fn set_track_midi_note_name_ex_unchecked<'a>( + &self, + project: ProjectContext, + mut track: MediaTrack, + pitch: u32, + channel: i32, + name: impl Into>, + ) -> bool + where + UsageScope: MainThreadOnly, + { + self.low.SetTrackMIDINoteNameEx( + project.to_raw(), + track.as_mut(), + pitch as i32, + channel, + name.into().as_ptr(), + ) + } + /// Selects exactly one track and deselects all others. /// /// If `None` is passed, deselects all tracks. @@ -6434,6 +6665,16 @@ impl Reaper { use_name(passing_c_str.ok_or_else(|| ReaperFunctionError::new("invalid take"))) } + /// #Safety + /// + /// REAPER can crash if invalid track is passed. + pub fn take_is_midi(&self, take: &MediaItemTake) -> bool + where + UsageScope: MainThreadOnly, + { + unsafe { self.low.TakeIsMIDI(take.as_ptr()) } + } + /// Returns the current on/off state of a toggleable action. /// /// Returns `None` if the action doesn't support on/off states (or if the action doesn't exist). diff --git a/test/test/src/tests.rs b/test/test/src/tests.rs index 7523966c..c8f9d9e9 100644 --- a/test/test/src/tests.rs +++ b/test/test/src/tests.rs @@ -139,6 +139,7 @@ pub fn create_test_steps() -> impl Iterator { set_project_play_rate(), get_project_tempo(), set_project_tempo(), + track_midi_functions(), swell(), ] .into_iter(); @@ -3577,6 +3578,60 @@ fn add_track_fx_by_original_name(get_fx_chain: GetFxChain) -> TestStep { ) } +fn track_midi_functions() -> TestStep { + step(AllVersions, "Test MIDI functions on track.", |_, _| { + let reaper = Reaper::get(); + let project = reaper.current_project(); + + let track = project.add_track()?; + + let medium = reaper.medium_reaper(); + + unsafe { + let item = medium.create_midi_item_in_proj(track.raw(), 1.0, 2.0, true)?; + let take = medium + .get_active_take(item) + .ok_or("No tke in created item!")?; + + assert!(medium.take_is_midi(&take)); + let track_index = track.index().ok_or("Can not take Track index.")?; + match medium.set_track_midi_note_name(track_index, 60, 0, "test name") { + true => Ok(true), + false => Err("returned false"), + }?; + match medium.set_track_midi_note_name_ex( + project.context(), + track.raw(), + 61, + -1, + "test name_ex", + ) { + true => Ok(true), + false => Err("returned false"), + }?; + assert_eq!( + medium + .get_track_midi_note_name(track_index, 61, 3) + .ok_or("should be set")? + .to_str(), + "test name_ex" + ); + assert_eq!( + medium + .get_track_midi_note_name_ex(project.context(), track.raw(), 60, 0) + .ok_or("should be set")? + .to_str(), + "test name ahh" + ); + } + assert_eq!("ahhh!", "Not!"); + + project.remove_track(&track); + + Ok(()) + }) +} + fn get_track(index: u32) -> Result { Reaper::get() .current_project() From bb25dbb15e465d7ed13ddc7729de58af89583b5b Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 11 Nov 2022 22:36:25 +0100 Subject: [PATCH 05/16] Medium-level API: get_media_item_track --- main/medium/src/reaper.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 95b98bd2..55f898f7 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -5700,6 +5700,19 @@ impl Reaper { NonNull::new(ptr) } + /// # Safety + /// + /// REAPER can crash if passed item is invalid. + pub unsafe fn get_media_item_track(&self, item: MediaItem) -> ReaperFunctionResult + where + UsageScope: MainThreadOnly, + { + let ptr = self.low.GetMediaItemTrack(item.as_ptr()); + MediaTrack::new(ptr).ok_or(ReaperFunctionError::new( + "Can not find item track. Probably, item is invalid.", + )) + } + /// Returns the active take in this item. /// /// # Safety From b72c902b4347fa242b116dd4dcba0b9a1c90d3be Mon Sep 17 00:00:00 2001 From: Timofey Date: Sat, 12 Nov 2022 09:22:30 +0100 Subject: [PATCH 06/16] Revert "Merge branch 'ppqfunctions' into midi_functions" This reverts commit 59f2716cfcdf7efe543aaac796d2242b8ad8981b. --- main/medium/src/misc_enums.rs | 12 ++++++++++++ main/medium/src/misc_newtypes.rs | 16 +++++++++++++++- main/medium/src/reaper.rs | 19 +++++++++++++++++++ test/test/src/tests.rs | 8 ++++---- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/main/medium/src/misc_enums.rs b/main/medium/src/misc_enums.rs index 7323f259..1e4f7895 100644 --- a/main/medium/src/misc_enums.rs +++ b/main/medium/src/misc_enums.rs @@ -5,6 +5,7 @@ use crate::{ }; use crate::util::concat_reaper_strs; +use derive_more::Display; use enumflags2::BitFlags; use helgoboss_midi::{U14, U7}; use reaper_low::raw; @@ -1586,3 +1587,14 @@ impl InsertMediaMode { bits as i32 } } + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Display)] +pub enum CcShape { + #[default] + Square = 0, + Linear = 16, + SlowStartEnd = 32, + FastStart = 16 | 32, + FastEnd = 64, + Beizer = 16 | 64, +} diff --git a/main/medium/src/misc_newtypes.rs b/main/medium/src/misc_newtypes.rs index a6901270..2f13d75a 100644 --- a/main/medium/src/misc_newtypes.rs +++ b/main/medium/src/misc_newtypes.rs @@ -1,5 +1,5 @@ //! This module defines various newtypes in order to achieve more type safety. -use crate::{ReaperStr, ReaperStringArg, TryFromGreaterError}; +use crate::{CcShape, ReaperStr, ReaperStringArg, TryFromGreaterError}; use derive_more::*; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -269,6 +269,20 @@ impl ResampleMode { } } +#[derive(Clone, PartialEq, PartialOrd, Debug, Default)] +pub struct SourceMidiEvent { + position_in_ppq: PositionInPPQ, + is_selected: bool, + is_muted: bool, + cc_shape: CcShape, + buf: Vec, +} +// impl Display for SourceMidiEvent{ +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + +// } +// } + /// A pitch shift mode, backed by a positive integer. /// /// This uniquely identifies a pitch shift mode. diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 55f898f7..800978ef 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -5975,6 +5975,25 @@ impl Reaper { ) } + pub unsafe fn midi_get_all_events_raw( + &self, + take: MediaItemTake, + max_size: u32, + ) -> Option> + where + UsageScope: MainThreadOnly, + { + let (events, status) = with_buffer(max_size, |buffer, size| { + let mut size = MaybeUninit::new(size); + self.low + .MIDI_GetAllEvts(take.as_ptr(), buffer, size.as_mut_ptr()) + }); + match status { + true => Some(events), + false => None, + } + } + /// Selects exactly one track and deselects all others. /// /// If `None` is passed, deselects all tracks. diff --git a/test/test/src/tests.rs b/test/test/src/tests.rs index c8f9d9e9..3c3471d2 100644 --- a/test/test/src/tests.rs +++ b/test/test/src/tests.rs @@ -120,6 +120,7 @@ pub fn create_test_steps() -> impl Iterator { unsolo_track(), generate_guid(), main_section_functions(), + midi_functions(), register_and_unregister_action(), register_and_unregister_toggle_action(), ] @@ -139,7 +140,6 @@ pub fn create_test_steps() -> impl Iterator { set_project_play_rate(), get_project_tempo(), set_project_tempo(), - track_midi_functions(), swell(), ] .into_iter(); @@ -3578,7 +3578,7 @@ fn add_track_fx_by_original_name(get_fx_chain: GetFxChain) -> TestStep { ) } -fn track_midi_functions() -> TestStep { +fn midi_functions() -> TestStep { step(AllVersions, "Test MIDI functions on track.", |_, _| { let reaper = Reaper::get(); let project = reaper.current_project(); @@ -3621,10 +3621,9 @@ fn track_midi_functions() -> TestStep { .get_track_midi_note_name_ex(project.context(), track.raw(), 60, 0) .ok_or("should be set")? .to_str(), - "test name ahh" + "test name" ); } - assert_eq!("ahhh!", "Not!"); project.remove_track(&track); @@ -3632,6 +3631,7 @@ fn track_midi_functions() -> TestStep { }) } + fn get_track(index: u32) -> Result { Reaper::get() .current_project() From de342080677e87aad08a6addab9b268846516502 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sat, 12 Nov 2022 09:23:11 +0100 Subject: [PATCH 07/16] resolve conflicts --- main/medium/src/misc_newtypes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/medium/src/misc_newtypes.rs b/main/medium/src/misc_newtypes.rs index 2f13d75a..81dde132 100644 --- a/main/medium/src/misc_newtypes.rs +++ b/main/medium/src/misc_newtypes.rs @@ -271,7 +271,7 @@ impl ResampleMode { #[derive(Clone, PartialEq, PartialOrd, Debug, Default)] pub struct SourceMidiEvent { - position_in_ppq: PositionInPPQ, + position_in_ppq: PositionInPpq, is_selected: bool, is_muted: bool, cc_shape: CcShape, From 4ee5df4b6d62d4fec44c2134a08e5e68e9882722 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sat, 12 Nov 2022 16:59:08 +0100 Subject: [PATCH 08/16] midi_get_evt for consult --- main/medium/src/lib.rs | 3 + main/medium/src/misc_enums.rs | 2 +- main/medium/src/misc_newtypes.rs | 18 ++--- main/medium/src/reaper.rs | 135 ++++++++++++++++++++++++++----- main/medium/src/source_midi.rs | 122 ++++++++++++++++++++++++++++ 5 files changed, 249 insertions(+), 31 deletions(-) create mode 100644 main/medium/src/source_midi.rs diff --git a/main/medium/src/lib.rs b/main/medium/src/lib.rs index ffd7e125..ac429bae 100644 --- a/main/medium/src/lib.rs +++ b/main/medium/src/lib.rs @@ -371,6 +371,9 @@ pub use control_surface::*; mod midi; pub use midi::*; +mod source_midi; +pub use source_midi::*; + mod pcm_source; pub use pcm_source::*; diff --git a/main/medium/src/misc_enums.rs b/main/medium/src/misc_enums.rs index 1e4f7895..407b6ae5 100644 --- a/main/medium/src/misc_enums.rs +++ b/main/medium/src/misc_enums.rs @@ -1589,7 +1589,7 @@ impl InsertMediaMode { } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Display)] -pub enum CcShape { +pub enum CcShapeKind { #[default] Square = 0, Linear = 16, diff --git a/main/medium/src/misc_newtypes.rs b/main/medium/src/misc_newtypes.rs index 81dde132..5d0856b3 100644 --- a/main/medium/src/misc_newtypes.rs +++ b/main/medium/src/misc_newtypes.rs @@ -1,5 +1,5 @@ //! This module defines various newtypes in order to achieve more type safety. -use crate::{CcShape, ReaperStr, ReaperStringArg, TryFromGreaterError}; +use crate::{ReaperStr, ReaperStringArg, TryFromGreaterError}; use derive_more::*; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -269,14 +269,14 @@ impl ResampleMode { } } -#[derive(Clone, PartialEq, PartialOrd, Debug, Default)] -pub struct SourceMidiEvent { - position_in_ppq: PositionInPpq, - is_selected: bool, - is_muted: bool, - cc_shape: CcShape, - buf: Vec, -} +// #[derive(Clone, PartialEq, PartialOrd, Debug, Default)] +// pub struct SourceMidiEvent { +// position_in_ppq: PositionInPpq, +// is_selected: bool, +// is_muted: bool, +// cc_shape: CcShapeKind, +// buf: Vec, +// } // impl Display for SourceMidiEvent{ // fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 800978ef..6ebe28aa 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -7,29 +7,30 @@ use reaper_low::{raw, register_plugin_destroy_hook}; use crate::ProjectContext::CurrentProject; use crate::{ require_non_null_panic, Accel, ActionValueChange, AddFxBehavior, AudioDeviceAttributeKey, - AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, ChunkCacheHint, CommandId, Db, - DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, FxChainVisibility, FxPresetRef, - FxShowInstruction, GangBehavior, GlobalAutomationModeOverride, HelpMode, Hidden, Hwnd, - InitialAction, InputMonitoringMode, InsertMediaFlag, InsertMediaMode, KbdSectionInfo, - MasterTrackBehavior, MeasureMode, MediaItem, MediaItemTake, MediaTrack, MessageBoxResult, - MessageBoxType, MidiImportBehavior, MidiInput, MidiInputDeviceId, MidiOutput, - MidiOutputDeviceId, NativeColor, NormalizedPlayRate, NotificationBehavior, OwnedPcmSource, - OwnedReaperPitchShift, OwnedReaperResample, PanMode, ParamId, PcmSource, PitchShiftMode, - PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, PositionInBeats, PositionInPpq, - PositionInQuarterNotes, PositionInSeconds, Progress, ProjectContext, ProjectRef, - PromptForActionResult, ReaProject, ReaperFunctionError, ReaperFunctionResult, - ReaperNormalizedFxParamValue, ReaperPanLikeValue, ReaperPanValue, ReaperPointer, ReaperStr, - ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, ReaperWidthValue, - RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, SectionContext, SectionId, - SendTarget, SetTrackUiFlags, SoloMode, StuffMidiMessageTarget, TakeAttributeKey, - TimeModeOverride, TimeRangeType, TrackArea, TrackAttributeKey, TrackDefaultsBehavior, - TrackEnvelope, TrackFxChainType, TrackFxLocation, TrackLocation, TrackMuteOperation, - TrackPolarityOperation, TrackRecArmOperation, TrackSendAttributeKey, TrackSendCategory, - TrackSendDirection, TrackSendRef, TrackSoloOperation, TransferBehavior, UiRefreshBehavior, - UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext, + AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, CcMessage, ChunkCacheHint, + CommandId, Db, DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, + FxChainVisibility, FxPresetRef, FxShowInstruction, GangBehavior, GenericMessage, + GlobalAutomationModeOverride, HelpMode, Hidden, Hwnd, InitialAction, InputMonitoringMode, + InsertMediaFlag, InsertMediaMode, KbdSectionInfo, MasterTrackBehavior, MeasureMode, MediaItem, + MediaItemTake, MediaTrack, MessageBoxResult, MessageBoxType, MidiImportBehavior, MidiInput, + MidiInputDeviceId, MidiOutput, MidiOutputDeviceId, NativeColor, NormalizedPlayRate, + NotificationBehavior, OwnedPcmSource, OwnedReaperPitchShift, OwnedReaperResample, PanMode, + ParamId, PcmSource, PitchShiftMode, PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, + PositionInBeats, PositionInPpq, PositionInQuarterNotes, PositionInSeconds, Progress, + ProjectContext, ProjectRef, PromptForActionResult, ReaProject, ReaperFunctionError, + ReaperFunctionResult, ReaperNormalizedFxParamValue, ReaperPanLikeValue, ReaperPanValue, + ReaperPointer, ReaperStr, ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, + ReaperWidthValue, RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, + SectionContext, SectionId, SendTarget, SetTrackUiFlags, SoloMode, SourceMidiEvent, + StuffMidiMessageTarget, TakeAttributeKey, TimeModeOverride, TimeRangeType, TrackArea, + TrackAttributeKey, TrackDefaultsBehavior, TrackEnvelope, TrackFxChainType, TrackFxLocation, + TrackLocation, TrackMuteOperation, TrackPolarityOperation, TrackRecArmOperation, + TrackSendAttributeKey, TrackSendCategory, TrackSendDirection, TrackSendRef, TrackSoloOperation, + TransferBehavior, UiRefreshBehavior, UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, + WindowContext, }; -use helgoboss_midi::ShortMessage; +use helgoboss_midi::{ShortMessage, U4, U7}; use reaper_low::raw::GUID; use crate::util::{ @@ -42,6 +43,8 @@ use std::mem::MaybeUninit; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; +const MAX_MIDI_MESSAGE_SIZE: usize = 2; + /// Represents a privilege to execute functions which are safe to execute from any thread. pub trait AnyThread: private::Sealed {} @@ -5994,6 +5997,96 @@ impl Reaper { } } + pub unsafe fn midi_get_cc( + &self, + take: MediaItemTake, + cc_index: u32, + ) -> Option> { + let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); + let mut ppqpos = MaybeUninit::new(0.0); + let (mut chanmsg, mut chan, mut msg2, mut msg3) = ( + MaybeUninit::new(0), + MaybeUninit::new(0), + MaybeUninit::new(0), + MaybeUninit::new(0), + ); + + let result = self.low.MIDI_GetCC( + take.as_ptr(), + cc_index as i32, + selected.as_mut_ptr(), + muted.as_mut_ptr(), + ppqpos.as_mut_ptr(), + chanmsg.as_mut_ptr(), + chan.as_mut_ptr(), + msg2.as_mut_ptr(), + msg3.as_mut_ptr(), + ); + match result { + false => None, + true => Some(SourceMidiEvent::new( + PositionInPpq(ppqpos.assume_init()), + selected.assume_init(), + muted.assume_init(), + CcMessage { + channel_message: U4::new(chanmsg.assume_init() as u8), + channel: U4::new(chan.assume_init() as u8), + cc_num: U7::new(msg2.assume_init() as u8), + value: U7::new(msg3.assume_init() as u8), + }, + )), + } + } + + pub unsafe fn midi_get_evt( + &self, + take: MediaItemTake, + evt_index: u32, + ) -> Option> { + let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); + let mut ppqpos = MaybeUninit::new(0.0); + let mut msg_size = MaybeUninit::new(5); + let (mut msg, result) = with_buffer(2, |msg, _| { + self.low.MIDI_GetEvt( + take.as_ptr(), + evt_index as i32, + selected.as_mut_ptr(), + muted.as_mut_ptr(), + ppqpos.as_mut_ptr(), + msg, + msg_size.as_mut_ptr(), + ) + }); + match result { + false => None, + true => { + let m_size = msg_size.assume_init() as u32; + if m_size > msg.len() as u32 { + (msg, _) = with_buffer(m_size, |msg, _| { + self.low.MIDI_GetEvt( + take.as_ptr(), + evt_index as i32, + selected.as_mut_ptr(), + muted.as_mut_ptr(), + ppqpos.as_mut_ptr(), + msg, + msg_size.as_mut_ptr(), + ) + }); + } + Some(SourceMidiEvent::new( + PositionInPpq(ppqpos.assume_init()), + selected.assume_init(), + muted.assume_init(), + GenericMessage { + size: m_size as u32, + message: msg, + }, + )) + } + } + } + /// Selects exactly one track and deselects all others. /// /// If `None` is passed, deselects all tracks. diff --git a/main/medium/src/source_midi.rs b/main/medium/src/source_midi.rs new file mode 100644 index 00000000..a12595c9 --- /dev/null +++ b/main/medium/src/source_midi.rs @@ -0,0 +1,122 @@ +// use crate::{CcShapeKind, MidiFrameOffset, PositionInPpq}; + +// #[derive(Debug, Clone)] +// pub struct MidiMessage { +// size: u32, +// bytes: Box<[u8]>, +// } +// impl MidiMessage { +// pub fn size(&self) -> u32 { +// self.size +// } +// pub fn get(self) -> Box<[u8]> { +// self.bytes +// } +// pub fn set(&mut self, bytes: Box<[u8]>) { +// self.bytes = bytes; +// } +// } + +// pub struct EnumSourceMidiEvent { +// pub message: MidiMessage, +// pub offset: MidiFrameOffset +// } +// impl EnumSourceMidiEvent{ +// pub fn new(message:MidiMessage, offset:MidiFrameOffset)->Self{ +// Self { message, offset } +// } +// } + +// pub enum SourceMidiEvent{ +// Cc(SourceCcEvent), +// NoteOn(SourceNoteOnEvent), + +// } + +use helgoboss_midi::{U4, U7}; + +use crate::PositionInPpq; + +#[derive(Debug)] +pub struct SourceMidiEvent { + position_in_ppq: PositionInPpq, + is_selected: bool, + is_muted: bool, + event: T, +} +impl SourceMidiEvent { + pub fn new(position_in_ppq: PositionInPpq, selected: bool, muted: bool, event: T) -> Self { + Self { + position_in_ppq, + is_selected: selected, + is_muted: muted, + event, + } + } +} + +#[derive(Debug)] +pub struct GenericMessage{ + pub size:u32, + pub message: Vec +} + +#[derive(Debug)] +pub struct CcMessage { + pub channel_message: U4, + pub channel: U4, + pub cc_num: U7, + pub value: U7, +} +// impl SourceMidiEvent_bck { +// pub fn get_pos_in_ppq(&self) -> PositionInPpq { +// self.position_in_ppq +// } +// pub fn set_pos_in_ppq(&mut self, position: PositionInPpq) { +// self.position_in_ppq = position; +// } +// pub fn is_selected(&self) -> bool { +// self.is_selected +// } +// pub fn set_selected(&mut self, selected: bool) { +// self.is_selected = selected; +// } +// pub fn is_muted(&self) -> bool { +// self.is_muted +// } +// pub fn set_muted(&mut self, muted: bool) { +// self.is_muted = muted; +// } +// pub fn get_message(self) -> MidiMessage { +// self.message +// } +// pub fn set_message(&mut self, message: MidiMessage) { +// self.message = message +// } +// pub fn get_cc_shape(self) -> CcShape { +// self.cc_shape +// } +// pub fn set_cc_shape(&mut self, cc_shape: CcShape) { +// self.cc_shape = cc_shape; +// } +// } + +// pub struct CcShape { +// kind: CcShapeKind, +// tension: Option, +// } +// impl CcShape { +// pub fn get_kind(&self) -> CcShapeKind { +// self.kind +// } +// pub fn set_kind(&mut self, kind: CcShapeKind) { +// self.kind = kind; +// } +// pub fn get_tension(&self) -> Option { +// self.tension +// } +// pub fn set_tension(&mut self, tension: Option) { +// self.tension = tension; +// } +// // pub fn from_events(events:) +// } From 4cfbe3823120a01b1ec3d6d7771547ba749a6447 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 00:46:58 +0100 Subject: [PATCH 09/16] skeleton of SourceMidiEvent iterator --- main/medium/src/misc_enums.rs | 36 ++++- main/medium/src/reaper.rs | 239 +++++++++++++++++---------------- main/medium/src/source_midi.rs | 203 ++++++++++++++-------------- 3 files changed, 254 insertions(+), 224 deletions(-) diff --git a/main/medium/src/misc_enums.rs b/main/medium/src/misc_enums.rs index 407b6ae5..74b12a05 100644 --- a/main/medium/src/misc_enums.rs +++ b/main/medium/src/misc_enums.rs @@ -1591,10 +1591,34 @@ impl InsertMediaMode { #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Display)] pub enum CcShapeKind { #[default] - Square = 0, - Linear = 16, - SlowStartEnd = 32, - FastStart = 16 | 32, - FastEnd = 64, - Beizer = 16 | 64, + Square, + Linear, + SlowStartEnd, + FastStart, + FastEnd, + Beizer, +} +impl CcShapeKind { + pub fn from_raw(value: u8) -> Self { + match value { + v if v == 0 => Self::Square, + v if v == 16 => Self::Linear, + v if v == 32 => Self::SlowStartEnd, + v if v == 16 | 32 => Self::FastStart, + v if v == 64 => Self::FastEnd, + v if v == 16 | 64 => Self::Beizer, + _ => panic!("not a cc shape: {:?}", value), + } + } + + pub fn to_raw(&self) -> u8 { + match self { + Self::Square => 0, + Self::Linear => 16, + Self::SlowStartEnd => 32, + Self::FastStart => 16 | 32, + Self::FastEnd => 64, + Self::Beizer => 16 | 64, + } + } } diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 6ebe28aa..c4ab8bda 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -7,30 +7,29 @@ use reaper_low::{raw, register_plugin_destroy_hook}; use crate::ProjectContext::CurrentProject; use crate::{ require_non_null_panic, Accel, ActionValueChange, AddFxBehavior, AudioDeviceAttributeKey, - AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, CcMessage, ChunkCacheHint, - CommandId, Db, DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, - FxChainVisibility, FxPresetRef, FxShowInstruction, GangBehavior, GenericMessage, - GlobalAutomationModeOverride, HelpMode, Hidden, Hwnd, InitialAction, InputMonitoringMode, - InsertMediaFlag, InsertMediaMode, KbdSectionInfo, MasterTrackBehavior, MeasureMode, MediaItem, - MediaItemTake, MediaTrack, MessageBoxResult, MessageBoxType, MidiImportBehavior, MidiInput, - MidiInputDeviceId, MidiOutput, MidiOutputDeviceId, NativeColor, NormalizedPlayRate, - NotificationBehavior, OwnedPcmSource, OwnedReaperPitchShift, OwnedReaperResample, PanMode, - ParamId, PcmSource, PitchShiftMode, PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, - PositionInBeats, PositionInPpq, PositionInQuarterNotes, PositionInSeconds, Progress, - ProjectContext, ProjectRef, PromptForActionResult, ReaProject, ReaperFunctionError, - ReaperFunctionResult, ReaperNormalizedFxParamValue, ReaperPanLikeValue, ReaperPanValue, - ReaperPointer, ReaperStr, ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, - ReaperWidthValue, RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, - SectionContext, SectionId, SendTarget, SetTrackUiFlags, SoloMode, SourceMidiEvent, - StuffMidiMessageTarget, TakeAttributeKey, TimeModeOverride, TimeRangeType, TrackArea, - TrackAttributeKey, TrackDefaultsBehavior, TrackEnvelope, TrackFxChainType, TrackFxLocation, - TrackLocation, TrackMuteOperation, TrackPolarityOperation, TrackRecArmOperation, - TrackSendAttributeKey, TrackSendCategory, TrackSendDirection, TrackSendRef, TrackSoloOperation, - TransferBehavior, UiRefreshBehavior, UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, - WindowContext, + AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, ChunkCacheHint, CommandId, Db, + DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, FxChainVisibility, FxPresetRef, + FxShowInstruction, GangBehavior, GlobalAutomationModeOverride, HelpMode, Hidden, Hwnd, + InitialAction, InputMonitoringMode, InsertMediaFlag, InsertMediaMode, KbdSectionInfo, + MasterTrackBehavior, MeasureMode, MediaItem, MediaItemTake, MediaTrack, MessageBoxResult, + MessageBoxType, MidiImportBehavior, MidiInput, MidiInputDeviceId, MidiOutput, + MidiOutputDeviceId, NativeColor, NormalizedPlayRate, NotificationBehavior, OwnedPcmSource, + OwnedReaperPitchShift, OwnedReaperResample, PanMode, ParamId, PcmSource, PitchShiftMode, + PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, PositionInBeats, PositionInPpq, + PositionInQuarterNotes, PositionInSeconds, Progress, ProjectContext, ProjectRef, + PromptForActionResult, ReaProject, ReaperFunctionError, ReaperFunctionResult, + ReaperNormalizedFxParamValue, ReaperPanLikeValue, ReaperPanValue, ReaperPointer, ReaperStr, + ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, ReaperWidthValue, + RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, SectionContext, SectionId, + SendTarget, SetTrackUiFlags, SoloMode, SourceMidiEventIterator, StuffMidiMessageTarget, + TakeAttributeKey, TimeModeOverride, TimeRangeType, TrackArea, TrackAttributeKey, + TrackDefaultsBehavior, TrackEnvelope, TrackFxChainType, TrackFxLocation, TrackLocation, + TrackMuteOperation, TrackPolarityOperation, TrackRecArmOperation, TrackSendAttributeKey, + TrackSendCategory, TrackSendDirection, TrackSendRef, TrackSoloOperation, TransferBehavior, + UiRefreshBehavior, UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext, }; -use helgoboss_midi::{ShortMessage, U4, U7}; +use helgoboss_midi::ShortMessage; use reaper_low::raw::GUID; use crate::util::{ @@ -43,8 +42,6 @@ use std::mem::MaybeUninit; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; -const MAX_MIDI_MESSAGE_SIZE: usize = 2; - /// Represents a privilege to execute functions which are safe to execute from any thread. pub trait AnyThread: private::Sealed {} @@ -5978,7 +5975,21 @@ impl Reaper { ) } - pub unsafe fn midi_get_all_events_raw( + pub unsafe fn midi_get_all_events_iter( + &self, + take: MediaItemTake, + max_size: u32, + ) -> Option + where + UsageScope: MainThreadOnly, + { + match self.midi_get_all_events(take, max_size) { + None => None, + Some(buf) => Some(SourceMidiEventIterator::new(buf)), + } + } + + pub unsafe fn midi_get_all_events( &self, take: MediaItemTake, max_size: u32, @@ -5997,95 +6008,95 @@ impl Reaper { } } - pub unsafe fn midi_get_cc( - &self, - take: MediaItemTake, - cc_index: u32, - ) -> Option> { - let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); - let mut ppqpos = MaybeUninit::new(0.0); - let (mut chanmsg, mut chan, mut msg2, mut msg3) = ( - MaybeUninit::new(0), - MaybeUninit::new(0), - MaybeUninit::new(0), - MaybeUninit::new(0), - ); - - let result = self.low.MIDI_GetCC( - take.as_ptr(), - cc_index as i32, - selected.as_mut_ptr(), - muted.as_mut_ptr(), - ppqpos.as_mut_ptr(), - chanmsg.as_mut_ptr(), - chan.as_mut_ptr(), - msg2.as_mut_ptr(), - msg3.as_mut_ptr(), - ); - match result { - false => None, - true => Some(SourceMidiEvent::new( - PositionInPpq(ppqpos.assume_init()), - selected.assume_init(), - muted.assume_init(), - CcMessage { - channel_message: U4::new(chanmsg.assume_init() as u8), - channel: U4::new(chan.assume_init() as u8), - cc_num: U7::new(msg2.assume_init() as u8), - value: U7::new(msg3.assume_init() as u8), - }, - )), - } - } - - pub unsafe fn midi_get_evt( - &self, - take: MediaItemTake, - evt_index: u32, - ) -> Option> { - let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); - let mut ppqpos = MaybeUninit::new(0.0); - let mut msg_size = MaybeUninit::new(5); - let (mut msg, result) = with_buffer(2, |msg, _| { - self.low.MIDI_GetEvt( - take.as_ptr(), - evt_index as i32, - selected.as_mut_ptr(), - muted.as_mut_ptr(), - ppqpos.as_mut_ptr(), - msg, - msg_size.as_mut_ptr(), - ) - }); - match result { - false => None, - true => { - let m_size = msg_size.assume_init() as u32; - if m_size > msg.len() as u32 { - (msg, _) = with_buffer(m_size, |msg, _| { - self.low.MIDI_GetEvt( - take.as_ptr(), - evt_index as i32, - selected.as_mut_ptr(), - muted.as_mut_ptr(), - ppqpos.as_mut_ptr(), - msg, - msg_size.as_mut_ptr(), - ) - }); - } - Some(SourceMidiEvent::new( - PositionInPpq(ppqpos.assume_init()), - selected.assume_init(), - muted.assume_init(), - GenericMessage { - size: m_size as u32, - message: msg, - }, - )) - } - } - } + // unsafe fn midi_get_cc( + // &self, + // take: MediaItemTake, + // cc_index: u32, + // ) -> Option> { + // let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); + // let mut ppqpos = MaybeUninit::new(0.0); + // let (mut chanmsg, mut chan, mut msg2, mut msg3) = ( + // MaybeUninit::new(0), + // MaybeUninit::new(0), + // MaybeUninit::new(0), + // MaybeUninit::new(0), + // ); + + // let result = self.low.MIDI_GetCC( + // take.as_ptr(), + // cc_index as i32, + // selected.as_mut_ptr(), + // muted.as_mut_ptr(), + // ppqpos.as_mut_ptr(), + // chanmsg.as_mut_ptr(), + // chan.as_mut_ptr(), + // msg2.as_mut_ptr(), + // msg3.as_mut_ptr(), + // ); + // match result { + // false => None, + // true => Some(SourceMidiEvent::new( + // PositionInPpq(ppqpos.assume_init()), + // selected.assume_init(), + // muted.assume_init(), + // CcMessage { + // channel_message: U4::new(chanmsg.assume_init() as u8), + // channel: U4::new(chan.assume_init() as u8), + // cc_num: U7::new(msg2.assume_init() as u8), + // value: U7::new(msg3.assume_init() as u8), + // }, + // )), + // } + // } + + // unsafe fn midi_get_evt( + // &self, + // take: MediaItemTake, + // evt_index: u32, + // ) -> Option> { + // let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); + // let mut ppqpos = MaybeUninit::new(0.0); + // let mut msg_size = MaybeUninit::new(4); + // let (mut msg, result) = with_buffer(3, |msg, _| { + // self.low.MIDI_GetEvt( + // take.as_ptr(), + // evt_index as i32, + // selected.as_mut_ptr(), + // muted.as_mut_ptr(), + // ppqpos.as_mut_ptr(), + // msg, + // msg_size.as_mut_ptr(), + // ) + // }); + // match result { + // false => None, + // true => { + // let m_size = msg_size.assume_init() as u32; + // if m_size > msg.len() as u32 { + // (msg, _) = with_buffer(m_size, |msg, _| { + // self.low.MIDI_GetEvt( + // take.as_ptr(), + // evt_index as i32, + // selected.as_mut_ptr(), + // muted.as_mut_ptr(), + // ppqpos.as_mut_ptr(), + // msg, + // msg_size.as_mut_ptr(), + // ) + // }); + // } + // Some(SourceMidiEvent::new( + // PositionInPpq(ppqpos.assume_init()), + // selected.assume_init(), + // muted.assume_init(), + // GenericMessage { + // size: m_size as u32, + // message: msg, + // }, + // )) + // } + // } + // } /// Selects exactly one track and deselects all others. /// diff --git a/main/medium/src/source_midi.rs b/main/medium/src/source_midi.rs index a12595c9..abcd8d6a 100644 --- a/main/medium/src/source_midi.rs +++ b/main/medium/src/source_midi.rs @@ -1,122 +1,117 @@ -// use crate::{CcShapeKind, MidiFrameOffset, PositionInPpq}; +use std::vec::IntoIter; -// #[derive(Debug, Clone)] -// pub struct MidiMessage { -// size: u32, -// bytes: Box<[u8]>, -// } -// impl MidiMessage { -// pub fn size(&self) -> u32 { -// self.size -// } -// pub fn get(self) -> Box<[u8]> { -// self.bytes -// } -// pub fn set(&mut self, bytes: Box<[u8]>) { -// self.bytes = bytes; -// } -// } +use helgoboss_midi::U7; -// pub struct EnumSourceMidiEvent { -// pub message: MidiMessage, -// pub offset: MidiFrameOffset -// } -// impl EnumSourceMidiEvent{ -// pub fn new(message:MidiMessage, offset:MidiFrameOffset)->Self{ -// Self { message, offset } -// } -// } - -// pub enum SourceMidiEvent{ -// Cc(SourceCcEvent), -// NoteOn(SourceNoteOnEvent), - -// } - -use helgoboss_midi::{U4, U7}; - -use crate::PositionInPpq; +use crate::{CcShapeKind, PositionInPpq}; #[derive(Debug)] -pub struct SourceMidiEvent { +pub struct SourceMidiEvent { position_in_ppq: PositionInPpq, is_selected: bool, is_muted: bool, - event: T, + cc_shape_kind: CcShapeKind, + message: Vec, } -impl SourceMidiEvent { - pub fn new(position_in_ppq: PositionInPpq, selected: bool, muted: bool, event: T) -> Self { +impl SourceMidiEvent { + pub fn new( + position_in_ppq: PositionInPpq, + is_selected: bool, + is_muted: bool, + cc_shape_kind: CcShapeKind, + message: Vec, + ) -> Self { Self { position_in_ppq, - is_selected: selected, - is_muted: muted, - event, + is_selected, + is_muted, + cc_shape_kind, + message, } } + pub fn get_position(&self) -> PositionInPpq { + self.position_in_ppq + } + pub fn set_position(&mut self, position: PositionInPpq) { + self.position_in_ppq = position; + } + pub fn get_selected(&self) -> bool { + self.is_selected + } + pub fn set_selected(&mut self, selected: bool) { + self.is_selected = selected; + } + pub fn get_muted(&self) -> bool { + self.is_muted + } + pub fn set_muted(&mut self, muted: bool) { + self.is_muted = muted; + } + pub fn get_cc_shape_kind(&self) -> CcShapeKind { + self.cc_shape_kind + } + pub fn set_cc_shape_kind(&mut self, cc_shape_kind: CcShapeKind) { + self.cc_shape_kind = cc_shape_kind; + } + pub fn get_message(&self) -> &Vec { + &self.message + } + pub fn get_message_mut(&mut self) -> &mut Vec { + &mut self.message + } + pub fn set_message(&mut self, message: Vec) { + self.message = message; + } } -#[derive(Debug)] -pub struct GenericMessage{ - pub size:u32, - pub message: Vec +pub struct SourceMidiEventIterator { + buf: IntoIter, + current_ppq: u32, } +impl SourceMidiEventIterator { + pub(crate) fn new(buf: Vec) -> Self { + Self { + buf: buf.into_iter(), + current_ppq: 0, + } + } -#[derive(Debug)] -pub struct CcMessage { - pub channel_message: U4, - pub channel: U4, - pub cc_num: U7, - pub value: U7, + fn next_4(&mut self) -> Option<[u8; 4]> { + match ( + self.buf.next(), + self.buf.next(), + self.buf.next(), + self.buf.next(), + ) { + (Some(a), Some(b), Some(c), Some(d)) => Some([a, b, c, d]), + _ => None, + } + } } -// impl SourceMidiEvent_bck { -// pub fn get_pos_in_ppq(&self) -> PositionInPpq { -// self.position_in_ppq -// } -// pub fn set_pos_in_ppq(&mut self, position: PositionInPpq) { -// self.position_in_ppq = position; -// } -// pub fn is_selected(&self) -> bool { -// self.is_selected -// } -// pub fn set_selected(&mut self, selected: bool) { -// self.is_selected = selected; -// } -// pub fn is_muted(&self) -> bool { -// self.is_muted -// } -// pub fn set_muted(&mut self, muted: bool) { -// self.is_muted = muted; -// } -// pub fn get_message(self) -> MidiMessage { -// self.message -// } -// pub fn set_message(&mut self, message: MidiMessage) { -// self.message = message -// } -// pub fn get_cc_shape(self) -> CcShape { -// self.cc_shape -// } -// pub fn set_cc_shape(&mut self, cc_shape: CcShape) { -// self.cc_shape = cc_shape; -// } -// } +impl Iterator for SourceMidiEventIterator { + type Item = SourceMidiEvent; -// pub struct CcShape { -// kind: CcShapeKind, -// tension: Option, -// } -// impl CcShape { -// pub fn get_kind(&self) -> CcShapeKind { -// self.kind -// } -// pub fn set_kind(&mut self, kind: CcShapeKind) { -// self.kind = kind; -// } -// pub fn get_tension(&self) -> Option { -// self.tension -// } -// pub fn set_tension(&mut self, tension: Option) { -// self.tension = tension; -// } -// // pub fn from_events(events:) -// } + fn next(&mut self) -> Option { + let result = match self.next_4() { + Some(value) => value, + None => return None, + }; + let offset = u32::from_le_bytes(result); + let flag = self + .buf + .next() + .expect("unexpectetly ended. Should be flag."); + let length = u32::from_le_bytes(self.next_4().expect("should take length")); + if length == 0 { + return None; + } + self.current_ppq += offset; + let buf = self.buf.by_ref().take(length as usize); + Some(SourceMidiEvent { + position_in_ppq: PositionInPpq::new(self.current_ppq as f64), + cc_shape_kind: CcShapeKind::from_raw(flag & 0b11110000), + is_selected: (flag & 1) != 0, + is_muted: (flag & 2) != 0, + message: Vec::from_iter(buf.map(|byte| U7::new(byte))), + }) + } +} From a6358ba0edb9c056b471caea837270b459b75c86 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 01:03:08 +0100 Subject: [PATCH 10/16] changed U7 to u8 as status bytes and etc uses 8 bits --- main/medium/src/source_midi.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/main/medium/src/source_midi.rs b/main/medium/src/source_midi.rs index abcd8d6a..f0c96e67 100644 --- a/main/medium/src/source_midi.rs +++ b/main/medium/src/source_midi.rs @@ -1,7 +1,5 @@ use std::vec::IntoIter; -use helgoboss_midi::U7; - use crate::{CcShapeKind, PositionInPpq}; #[derive(Debug)] @@ -10,7 +8,7 @@ pub struct SourceMidiEvent { is_selected: bool, is_muted: bool, cc_shape_kind: CcShapeKind, - message: Vec, + message: Vec, } impl SourceMidiEvent { pub fn new( @@ -18,7 +16,7 @@ impl SourceMidiEvent { is_selected: bool, is_muted: bool, cc_shape_kind: CcShapeKind, - message: Vec, + message: Vec, ) -> Self { Self { position_in_ppq, @@ -52,13 +50,13 @@ impl SourceMidiEvent { pub fn set_cc_shape_kind(&mut self, cc_shape_kind: CcShapeKind) { self.cc_shape_kind = cc_shape_kind; } - pub fn get_message(&self) -> &Vec { + pub fn get_message(&self) -> &Vec { &self.message } - pub fn get_message_mut(&mut self) -> &mut Vec { + pub fn get_message_mut(&mut self) -> &mut Vec { &mut self.message } - pub fn set_message(&mut self, message: Vec) { + pub fn set_message(&mut self, message: Vec) { self.message = message; } } @@ -111,7 +109,7 @@ impl Iterator for SourceMidiEventIterator { cc_shape_kind: CcShapeKind::from_raw(flag & 0b11110000), is_selected: (flag & 1) != 0, is_muted: (flag & 2) != 0, - message: Vec::from_iter(buf.map(|byte| U7::new(byte))), + message: Vec::from_iter(buf), }) } } From a303845434d19190feef2db0876f35cba0400dc6 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 19:02:03 +0100 Subject: [PATCH 11/16] stabilized `*all_evts` iterators and functions --- main/medium/src/misc_enums.rs | 30 +++++-- main/medium/src/reaper.rs | 151 +++++++++++++++++++-------------- main/medium/src/source_midi.rs | 80 +++++++++++++++-- 3 files changed, 184 insertions(+), 77 deletions(-) diff --git a/main/medium/src/misc_enums.rs b/main/medium/src/misc_enums.rs index 74b12a05..1b175f4a 100644 --- a/main/medium/src/misc_enums.rs +++ b/main/medium/src/misc_enums.rs @@ -1588,6 +1588,16 @@ impl InsertMediaMode { } } +/// Represents MediaItemTake midi CC shape kind. +/// +/// # Note +/// +/// If CcShapeKind::Beizer is given to CC event, additional midi event +/// should be put at the same position: +/// 0xF followed by 'CCBZ ' and 5 more bytes represents +/// bezier curve data for the previous MIDI event: +/// - 1 byte for the bezier type (usually 0) +/// - 4 bytes for the bezier tension as a float. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Display)] pub enum CcShapeKind { #[default] @@ -1599,18 +1609,22 @@ pub enum CcShapeKind { Beizer, } impl CcShapeKind { - pub fn from_raw(value: u8) -> Self { + /// CcShapeKind from u8. + /// + /// Returns Err if can not find proper variant. + pub fn from_raw(value: u8) -> Result { match value { - v if v == 0 => Self::Square, - v if v == 16 => Self::Linear, - v if v == 32 => Self::SlowStartEnd, - v if v == 16 | 32 => Self::FastStart, - v if v == 64 => Self::FastEnd, - v if v == 16 | 64 => Self::Beizer, - _ => panic!("not a cc shape: {:?}", value), + v if v == 0 => Ok(Self::Square), + v if v == 16 => Ok(Self::Linear), + v if v == 32 => Ok(Self::SlowStartEnd), + v if v == 16 | 32 => Ok(Self::FastStart), + v if v == 64 => Ok(Self::FastEnd), + v if v == 16 | 64 => Ok(Self::Beizer), + _ => Err(format!("not a cc shape: {:?}", value)), } } + /// u8 representation of CcShapeKind pub fn to_raw(&self) -> u8 { match self { Self::Square => 0, diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index c4ab8bda..eeb3f312 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -21,12 +21,13 @@ use crate::{ ReaperNormalizedFxParamValue, ReaperPanLikeValue, ReaperPanValue, ReaperPointer, ReaperStr, ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, ReaperWidthValue, RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, SectionContext, SectionId, - SendTarget, SetTrackUiFlags, SoloMode, SourceMidiEventIterator, StuffMidiMessageTarget, - TakeAttributeKey, TimeModeOverride, TimeRangeType, TrackArea, TrackAttributeKey, - TrackDefaultsBehavior, TrackEnvelope, TrackFxChainType, TrackFxLocation, TrackLocation, - TrackMuteOperation, TrackPolarityOperation, TrackRecArmOperation, TrackSendAttributeKey, - TrackSendCategory, TrackSendDirection, TrackSendRef, TrackSoloOperation, TransferBehavior, - UiRefreshBehavior, UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext, + SendTarget, SetTrackUiFlags, SoloMode, SourceMidiEvent, SourceMidiEventBuilder, + SourceMidiEventConsumer, StuffMidiMessageTarget, TakeAttributeKey, TimeModeOverride, + TimeRangeType, TrackArea, TrackAttributeKey, TrackDefaultsBehavior, TrackEnvelope, + TrackFxChainType, TrackFxLocation, TrackLocation, TrackMuteOperation, TrackPolarityOperation, + TrackRecArmOperation, TrackSendAttributeKey, TrackSendCategory, TrackSendDirection, + TrackSendRef, TrackSoloOperation, TransferBehavior, UiRefreshBehavior, UndoBehavior, UndoScope, + ValueChange, VolumeSliderValue, WindowContext, }; use helgoboss_midi::ShortMessage; @@ -5910,7 +5911,7 @@ impl Reaper { &self, track: u32, pitch: u32, - channel: i32, + channel: u32, name: impl Into>, ) -> bool where @@ -5938,7 +5939,7 @@ impl Reaper { project: ProjectContext, track: MediaTrack, pitch: u32, - channel: i32, + channel: u32, name: impl Into>, ) -> bool where @@ -5960,7 +5961,7 @@ impl Reaper { project: ProjectContext, mut track: MediaTrack, pitch: u32, - channel: i32, + channel: u32, name: impl Into>, ) -> bool where @@ -5970,26 +5971,59 @@ impl Reaper { project.to_raw(), track.as_mut(), pitch as i32, - channel, + channel as i32, name.into().as_ptr(), ) } - pub unsafe fn midi_get_all_events_iter( + /// Returns Iterator over all midi events from the given take + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_get_all_evts( &self, take: MediaItemTake, max_size: u32, - ) -> Option + ) -> Option where UsageScope: MainThreadOnly, { - match self.midi_get_all_events(take, max_size) { + match self.midi_get_all_evts_raw(take, max_size) { None => None, - Some(buf) => Some(SourceMidiEventIterator::new(buf)), + Some(buf) => Some(SourceMidiEventBuilder::new(buf)), } } - pub unsafe fn midi_get_all_events( + /// Returns all midi events from the given take + /// + /// These events represented as Vec as they are returned from + /// REAPER take. + /// + /// Each event consist of: + /// - offset in ppq from the previous event: 4 bytes — little-endian i32 (u32) + /// - flag: 1 bit + /// - 0b1000_0000 — selected + /// - 0b0100_0000 — muted + /// - 0b0000_1000 — CcShapeKind::Linear + /// - 0b0000_0100 — CcShapeKind::SlowStartEnd + /// - 0b0000_1100 — CcShapeKind::FastStart + /// - 0b0000_0010 — CcShapeKind::FastEnd + /// - 0b0000_1010 — CcShapeKind::Beizer + /// - length in bytes: 4 bytes — little-endinan i32 (u32) + /// - message: bytes + /// + /// A meta-event of type 0xF followed by 'CCBZ ' and 5 more bytes represents + /// bezier curve data for the previous MIDI event: + /// - 1 byte for the bezier type (usually 0) + /// - 4 bytes for the bezier tension as a float. + /// + /// The rest of the vector is filled by zeroes. + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_get_all_evts_raw( &self, take: MediaItemTake, max_size: u32, @@ -6008,6 +6042,44 @@ impl Reaper { } } + /// Replace all events in the given take. + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_set_all_evts( + &self, + take: MediaItemTake, + events: Vec, + sort: bool, + ) -> bool + where + UsageScope: MainThreadOnly, + { + self.midi_set_all_evts_raw( + take, + SourceMidiEventConsumer::new(events, sort).collect::>(), + ) + } + + /// Returns all midi events from the given take + /// + /// These events represented as Vec as they are returned from + /// REAPER take. + /// + /// For the raw event representation of events see `Reaper::midi_get_all_evts_raw()` + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_set_all_evts_raw(&self, take: MediaItemTake, buffer: Vec) -> bool + where + UsageScope: MainThreadOnly, + { + self.low + .MIDI_SetAllEvts(take.as_ptr(), buffer.as_ptr(), buffer.len() as i32) + } + // unsafe fn midi_get_cc( // &self, // take: MediaItemTake, @@ -6049,55 +6121,6 @@ impl Reaper { // } // } - // unsafe fn midi_get_evt( - // &self, - // take: MediaItemTake, - // evt_index: u32, - // ) -> Option> { - // let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); - // let mut ppqpos = MaybeUninit::new(0.0); - // let mut msg_size = MaybeUninit::new(4); - // let (mut msg, result) = with_buffer(3, |msg, _| { - // self.low.MIDI_GetEvt( - // take.as_ptr(), - // evt_index as i32, - // selected.as_mut_ptr(), - // muted.as_mut_ptr(), - // ppqpos.as_mut_ptr(), - // msg, - // msg_size.as_mut_ptr(), - // ) - // }); - // match result { - // false => None, - // true => { - // let m_size = msg_size.assume_init() as u32; - // if m_size > msg.len() as u32 { - // (msg, _) = with_buffer(m_size, |msg, _| { - // self.low.MIDI_GetEvt( - // take.as_ptr(), - // evt_index as i32, - // selected.as_mut_ptr(), - // muted.as_mut_ptr(), - // ppqpos.as_mut_ptr(), - // msg, - // msg_size.as_mut_ptr(), - // ) - // }); - // } - // Some(SourceMidiEvent::new( - // PositionInPpq(ppqpos.assume_init()), - // selected.assume_init(), - // muted.assume_init(), - // GenericMessage { - // size: m_size as u32, - // message: msg, - // }, - // )) - // } - // } - // } - /// Selects exactly one track and deselects all others. /// /// If `None` is passed, deselects all tracks. diff --git a/main/medium/src/source_midi.rs b/main/medium/src/source_midi.rs index f0c96e67..68c693b7 100644 --- a/main/medium/src/source_midi.rs +++ b/main/medium/src/source_midi.rs @@ -2,12 +2,14 @@ use std::vec::IntoIter; use crate::{CcShapeKind, PositionInPpq}; -#[derive(Debug)] +#[derive(Clone, PartialEq, PartialOrd, Debug, Default)] pub struct SourceMidiEvent { position_in_ppq: PositionInPpq, is_selected: bool, is_muted: bool, cc_shape_kind: CcShapeKind, + /// Message can be as ordinary 3-bytes midi-message, + /// as well as SysEx and custom messages, including lyrics and text. message: Vec, } impl SourceMidiEvent { @@ -61,11 +63,13 @@ impl SourceMidiEvent { } } -pub struct SourceMidiEventIterator { +/// Iterates over raw take midi data and builds SourceMediaEvent objects. +#[derive(Debug)] +pub struct SourceMidiEventBuilder { buf: IntoIter, current_ppq: u32, } -impl SourceMidiEventIterator { +impl SourceMidiEventBuilder { pub(crate) fn new(buf: Vec) -> Self { Self { buf: buf.into_iter(), @@ -85,7 +89,7 @@ impl SourceMidiEventIterator { } } } -impl Iterator for SourceMidiEventIterator { +impl Iterator for SourceMidiEventBuilder { type Item = SourceMidiEvent; fn next(&mut self) -> Option { @@ -106,10 +110,76 @@ impl Iterator for SourceMidiEventIterator { let buf = self.buf.by_ref().take(length as usize); Some(SourceMidiEvent { position_in_ppq: PositionInPpq::new(self.current_ppq as f64), - cc_shape_kind: CcShapeKind::from_raw(flag & 0b11110000), + cc_shape_kind: CcShapeKind::from_raw(flag & 0b11110000) + .expect("Can not infer CcShapeKind, received from take."), is_selected: (flag & 1) != 0, is_muted: (flag & 2) != 0, message: Vec::from_iter(buf), }) } } + +/// Iterates through SourceMediaEvent objects and builds raw midi data +/// to be passed to take. +#[derive(Debug)] +pub struct SourceMidiEventConsumer { + events: IntoIter, + last_ppq: u32, + buf: Option>, +} +impl SourceMidiEventConsumer { + /// Build iterator. + /// + /// If sort is true — vector would be sorted by ppq_position. + /// Be careful, this costs additional O(log n) operation in the worst case. + pub fn new(mut events: Vec, sort: bool) -> Self { + if sort == true { + events.sort_by_key(|ev| ev.get_position().get() as u32); + } + Self { + events: events.into_iter(), + last_ppq: 0, + buf: None, + } + } + + /// Checks if some events are left and builds new buf for iteration. + fn next_buf(&mut self) -> Option { + match self.events.next() { + None => None, + Some(mut event) => { + let size = event.get_message().len() + 9; + let pos = event.get_position().get() as u32; + let mut offset = (pos - self.last_ppq).to_le_bytes().to_vec(); + self.last_ppq = pos; + let flag = (event.get_selected() as u8) + | ((event.get_muted() as u8) << 1) + | event.get_cc_shape_kind().to_raw(); + let mut length = event.get_message().len().to_le_bytes().to_vec(); + // + let mut buf = Vec::with_capacity(size); + buf.append(&mut offset); + buf.push(flag); + buf.append(&mut length); + buf.append(event.get_message_mut()); + // + self.buf = Some(buf.into_iter()); + // Some(i8) + Some(self.buf.as_mut().unwrap().next().unwrap() as i8) + } + } + } +} + +impl Iterator for SourceMidiEventConsumer { + type Item = i8; + fn next(&mut self) -> Option { + match self.buf.as_mut() { + Some(buf) => match buf.next() { + Some(next) => Some(next as i8), + None => self.next_buf(), + }, + None => self.next_buf(), + } + } +} From b9b88fa6ad45de70b2b8f10b8d52135a15c42fb7 Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 19:04:00 +0100 Subject: [PATCH 12/16] removed unused type in misc_enums --- main/medium/src/misc_newtypes.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/main/medium/src/misc_newtypes.rs b/main/medium/src/misc_newtypes.rs index 5d0856b3..a6901270 100644 --- a/main/medium/src/misc_newtypes.rs +++ b/main/medium/src/misc_newtypes.rs @@ -269,20 +269,6 @@ impl ResampleMode { } } -// #[derive(Clone, PartialEq, PartialOrd, Debug, Default)] -// pub struct SourceMidiEvent { -// position_in_ppq: PositionInPpq, -// is_selected: bool, -// is_muted: bool, -// cc_shape: CcShapeKind, -// buf: Vec, -// } -// impl Display for SourceMidiEvent{ -// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - -// } -// } - /// A pitch shift mode, backed by a positive integer. /// /// This uniquely identifies a pitch shift mode. From 1ef9e4487aa749a39507f72987291363ff06835c Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 19:50:56 +0100 Subject: [PATCH 13/16] channel can be negative --- main/medium/src/reaper.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index eeb3f312..51c705c0 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -5939,7 +5939,7 @@ impl Reaper { project: ProjectContext, track: MediaTrack, pitch: u32, - channel: u32, + channel: i32, name: impl Into>, ) -> bool where @@ -5961,7 +5961,7 @@ impl Reaper { project: ProjectContext, mut track: MediaTrack, pitch: u32, - channel: u32, + channel: i32, name: impl Into>, ) -> bool where @@ -5971,7 +5971,7 @@ impl Reaper { project.to_raw(), track.as_mut(), pitch as i32, - channel as i32, + channel, name.into().as_ptr(), ) } From f377932415389ec76cf307b6892e6bb3899d84cd Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 21:08:39 +0100 Subject: [PATCH 14/16] refactored `SourceMidiEvent` to be generic Now it can hold various messages and still can represent raw bytes with `RawMidiMessage` --- main/medium/src/reaper.rs | 120 ++++++++++++++++++--------------- main/medium/src/source_midi.rs | 97 ++++++++++++++++++++------ 2 files changed, 143 insertions(+), 74 deletions(-) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 51c705c0..4b61d85c 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -7,12 +7,12 @@ use reaper_low::{raw, register_plugin_destroy_hook}; use crate::ProjectContext::CurrentProject; use crate::{ require_non_null_panic, Accel, ActionValueChange, AddFxBehavior, AudioDeviceAttributeKey, - AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, ChunkCacheHint, CommandId, Db, - DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, FxChainVisibility, FxPresetRef, - FxShowInstruction, GangBehavior, GlobalAutomationModeOverride, HelpMode, Hidden, Hwnd, - InitialAction, InputMonitoringMode, InsertMediaFlag, InsertMediaMode, KbdSectionInfo, - MasterTrackBehavior, MeasureMode, MediaItem, MediaItemTake, MediaTrack, MessageBoxResult, - MessageBoxType, MidiImportBehavior, MidiInput, MidiInputDeviceId, MidiOutput, + AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, CcMessage, CcShapeKind, + ChunkCacheHint, CommandId, Db, DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, + FxChainVisibility, FxPresetRef, FxShowInstruction, GangBehavior, GlobalAutomationModeOverride, + HelpMode, Hidden, Hwnd, InitialAction, InputMonitoringMode, InsertMediaFlag, InsertMediaMode, + KbdSectionInfo, MasterTrackBehavior, MeasureMode, MediaItem, MediaItemTake, MediaTrack, + MessageBoxResult, MessageBoxType, MidiImportBehavior, MidiInput, MidiInputDeviceId, MidiOutput, MidiOutputDeviceId, NativeColor, NormalizedPlayRate, NotificationBehavior, OwnedPcmSource, OwnedReaperPitchShift, OwnedReaperResample, PanMode, ParamId, PcmSource, PitchShiftMode, PitchShiftSubMode, PlaybackSpeedFactor, PluginContext, PositionInBeats, PositionInPpq, @@ -22,15 +22,15 @@ use crate::{ ReaperString, ReaperStringArg, ReaperVersion, ReaperVolumeValue, ReaperWidthValue, RecordArmMode, RecordingInput, RequiredViewMode, ResampleMode, SectionContext, SectionId, SendTarget, SetTrackUiFlags, SoloMode, SourceMidiEvent, SourceMidiEventBuilder, - SourceMidiEventConsumer, StuffMidiMessageTarget, TakeAttributeKey, TimeModeOverride, - TimeRangeType, TrackArea, TrackAttributeKey, TrackDefaultsBehavior, TrackEnvelope, - TrackFxChainType, TrackFxLocation, TrackLocation, TrackMuteOperation, TrackPolarityOperation, - TrackRecArmOperation, TrackSendAttributeKey, TrackSendCategory, TrackSendDirection, - TrackSendRef, TrackSoloOperation, TransferBehavior, UiRefreshBehavior, UndoBehavior, UndoScope, - ValueChange, VolumeSliderValue, WindowContext, + SourceMidiEventConsumer, SourceMidiMessage, StuffMidiMessageTarget, TakeAttributeKey, + TimeModeOverride, TimeRangeType, TrackArea, TrackAttributeKey, TrackDefaultsBehavior, + TrackEnvelope, TrackFxChainType, TrackFxLocation, TrackLocation, TrackMuteOperation, + TrackPolarityOperation, TrackRecArmOperation, TrackSendAttributeKey, TrackSendCategory, + TrackSendDirection, TrackSendRef, TrackSoloOperation, TransferBehavior, UiRefreshBehavior, + UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext, }; -use helgoboss_midi::ShortMessage; +use helgoboss_midi::{Channel, ShortMessage, U4, U7}; use reaper_low::raw::GUID; use crate::util::{ @@ -6047,10 +6047,10 @@ impl Reaper { /// # Safety /// /// REAPER can crash, it you pass an invalid take. - pub unsafe fn midi_set_all_evts( + pub unsafe fn midi_set_all_evts( &self, take: MediaItemTake, - events: Vec, + events: Vec>, sort: bool, ) -> bool where @@ -6080,46 +6080,56 @@ impl Reaper { .MIDI_SetAllEvts(take.as_ptr(), buffer.as_ptr(), buffer.len() as i32) } - // unsafe fn midi_get_cc( - // &self, - // take: MediaItemTake, - // cc_index: u32, - // ) -> Option> { - // let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); - // let mut ppqpos = MaybeUninit::new(0.0); - // let (mut chanmsg, mut chan, mut msg2, mut msg3) = ( - // MaybeUninit::new(0), - // MaybeUninit::new(0), - // MaybeUninit::new(0), - // MaybeUninit::new(0), - // ); - - // let result = self.low.MIDI_GetCC( - // take.as_ptr(), - // cc_index as i32, - // selected.as_mut_ptr(), - // muted.as_mut_ptr(), - // ppqpos.as_mut_ptr(), - // chanmsg.as_mut_ptr(), - // chan.as_mut_ptr(), - // msg2.as_mut_ptr(), - // msg3.as_mut_ptr(), - // ); - // match result { - // false => None, - // true => Some(SourceMidiEvent::new( - // PositionInPpq(ppqpos.assume_init()), - // selected.assume_init(), - // muted.assume_init(), - // CcMessage { - // channel_message: U4::new(chanmsg.assume_init() as u8), - // channel: U4::new(chan.assume_init() as u8), - // cc_num: U7::new(msg2.assume_init() as u8), - // value: U7::new(msg3.assume_init() as u8), - // }, - // )), - // } - // } + /// Get CC event from given take. + /// + /// index is 0-based + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_get_cc( + &self, + take: MediaItemTake, + cc_index: u32, + ) -> Option> + where + UsageScope: MainThreadOnly, + { + let (mut selected, mut muted) = (MaybeUninit::new(false), MaybeUninit::new(false)); + let mut ppqpos = MaybeUninit::new(0.0); + let (mut chanmsg, mut chan, mut msg2, mut msg3) = ( + MaybeUninit::new(0), + MaybeUninit::new(0), + MaybeUninit::new(0), + MaybeUninit::new(0), + ); + let result = self.low.MIDI_GetCC( + take.as_ptr(), + cc_index as i32, + selected.as_mut_ptr(), + muted.as_mut_ptr(), + ppqpos.as_mut_ptr(), + chanmsg.as_mut_ptr(), + chan.as_mut_ptr(), + msg2.as_mut_ptr(), + msg3.as_mut_ptr(), + ); + match result { + false => None, + true => Some(SourceMidiEvent::new( + PositionInPpq(ppqpos.assume_init()), + selected.assume_init(), + muted.assume_init(), + CcShapeKind::Square, + CcMessage { + channel_message: U4::new(chanmsg.assume_init() as u8), + channel: Channel::new(chan.assume_init() as u8), + cc_num: U7::new(msg2.assume_init() as u8), + value: U7::new(msg3.assume_init() as u8), + }, + )), + } + } /// Selects exactly one track and deselects all others. /// diff --git a/main/medium/src/source_midi.rs b/main/medium/src/source_midi.rs index 68c693b7..1df5e112 100644 --- a/main/medium/src/source_midi.rs +++ b/main/medium/src/source_midi.rs @@ -1,24 +1,81 @@ use std::vec::IntoIter; +use helgoboss_midi::{Channel, U4, U7}; + use crate::{CcShapeKind, PositionInPpq}; +pub trait SourceMidiMessage { + fn from_raw(buf: Vec) -> Option + where + Self: Sized; + fn get_raw(&self) -> Vec; +} + #[derive(Clone, PartialEq, PartialOrd, Debug, Default)] -pub struct SourceMidiEvent { +pub struct RawMidiMessage { + buf: Vec, +} +impl SourceMidiMessage for RawMidiMessage { + fn from_raw(buf: Vec) -> Option { + Some(Self { buf }) + } + fn get_raw(&self) -> Vec { + self.buf.clone() + } +} + +#[derive(Clone, PartialEq, PartialOrd, Debug, Default)] +pub struct CcMessage { + pub channel_message: U4, + pub channel: Channel, + pub cc_num: U7, + pub value: U7, +} +impl SourceMidiMessage for CcMessage { + fn from_raw(buf: Vec) -> Option { + if buf.len() != 3 { + return None; + }; + if buf[0] < 0xb0 || buf[0] >= 0xc0 { + return None; + }; + let channel_message: U4 = U4::new(buf[0] >> 4); + let channel: Channel = Channel::new(buf[0] & 0xf); + let cc_num: U7 = U7::new(buf[1]); + let value: U7 = U7::new(buf[2]); + Some(Self { + channel_message, + channel, + cc_num, + value, + }) + } + fn get_raw(&self) -> Vec { + vec![ + (u8::from(self.channel_message)) << 4_u8 | u8::from(self.channel), + u8::from(self.cc_num), + u8::from(self.value), + ] + } +} + +#[derive(Clone, PartialEq, PartialOrd, Debug, Default)] +pub struct SourceMidiEvent { position_in_ppq: PositionInPpq, is_selected: bool, is_muted: bool, cc_shape_kind: CcShapeKind, /// Message can be as ordinary 3-bytes midi-message, /// as well as SysEx and custom messages, including lyrics and text. - message: Vec, + message: T, } -impl SourceMidiEvent { +impl SourceMidiEvent { pub fn new( position_in_ppq: PositionInPpq, is_selected: bool, is_muted: bool, cc_shape_kind: CcShapeKind, - message: Vec, + message: T, ) -> Self { Self { position_in_ppq, @@ -52,13 +109,13 @@ impl SourceMidiEvent { pub fn set_cc_shape_kind(&mut self, cc_shape_kind: CcShapeKind) { self.cc_shape_kind = cc_shape_kind; } - pub fn get_message(&self) -> &Vec { + pub fn get_message(&self) -> &T { &self.message } - pub fn get_message_mut(&mut self) -> &mut Vec { + pub fn get_message_mut(&mut self) -> &mut T { &mut self.message } - pub fn set_message(&mut self, message: Vec) { + pub fn set_message(&mut self, message: T) { self.message = message; } } @@ -90,7 +147,7 @@ impl SourceMidiEventBuilder { } } impl Iterator for SourceMidiEventBuilder { - type Item = SourceMidiEvent; + type Item = SourceMidiEvent; fn next(&mut self) -> Option { let result = match self.next_4() { @@ -114,25 +171,27 @@ impl Iterator for SourceMidiEventBuilder { .expect("Can not infer CcShapeKind, received from take."), is_selected: (flag & 1) != 0, is_muted: (flag & 2) != 0, - message: Vec::from_iter(buf), + message: RawMidiMessage { + buf: Vec::from_iter(buf), + }, }) } } /// Iterates through SourceMediaEvent objects and builds raw midi data -/// to be passed to take. +/// to be passed to take. #[derive(Debug)] -pub struct SourceMidiEventConsumer { - events: IntoIter, +pub struct SourceMidiEventConsumer { + events: IntoIter>, last_ppq: u32, buf: Option>, } -impl SourceMidiEventConsumer { +impl SourceMidiEventConsumer { /// Build iterator. - /// + /// /// If sort is true — vector would be sorted by ppq_position. /// Be careful, this costs additional O(log n) operation in the worst case. - pub fn new(mut events: Vec, sort: bool) -> Self { + pub fn new(mut events: Vec>, sort: bool) -> Self { if sort == true { events.sort_by_key(|ev| ev.get_position().get() as u32); } @@ -148,20 +207,20 @@ impl SourceMidiEventConsumer { match self.events.next() { None => None, Some(mut event) => { - let size = event.get_message().len() + 9; + let size = event.get_message().get_raw().len() + 9; let pos = event.get_position().get() as u32; let mut offset = (pos - self.last_ppq).to_le_bytes().to_vec(); self.last_ppq = pos; let flag = (event.get_selected() as u8) | ((event.get_muted() as u8) << 1) | event.get_cc_shape_kind().to_raw(); - let mut length = event.get_message().len().to_le_bytes().to_vec(); + let mut length = event.get_message().get_raw().len().to_le_bytes().to_vec(); // let mut buf = Vec::with_capacity(size); buf.append(&mut offset); buf.push(flag); buf.append(&mut length); - buf.append(event.get_message_mut()); + buf.append(&mut event.get_message_mut().get_raw()); // self.buf = Some(buf.into_iter()); // Some(i8) @@ -171,7 +230,7 @@ impl SourceMidiEventConsumer { } } -impl Iterator for SourceMidiEventConsumer { +impl Iterator for SourceMidiEventConsumer { type Item = i8; fn next(&mut self) -> Option { match self.buf.as_mut() { From 17e25244cd875efb1aa3d747b3873dbbad6d3c2d Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 21:50:47 +0100 Subject: [PATCH 15/16] Adopted `SourceMidiEvent` to `RawShortMessage` So, the main message types from `helgoboss_midi` now covered --- main/medium/src/reaper.rs | 21 ++++++++-------- main/medium/src/source_midi.rs | 44 +++++++++++++--------------------- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 4b61d85c..0673493f 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -7,8 +7,8 @@ use reaper_low::{raw, register_plugin_destroy_hook}; use crate::ProjectContext::CurrentProject; use crate::{ require_non_null_panic, Accel, ActionValueChange, AddFxBehavior, AudioDeviceAttributeKey, - AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, CcMessage, CcShapeKind, - ChunkCacheHint, CommandId, Db, DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, + AutoSeekBehavior, AutomationMode, BookmarkId, BookmarkRef, Bpm, CcShapeKind, ChunkCacheHint, + CommandId, Db, DurationInSeconds, EditMode, EnvChunkName, FxAddByNameBehavior, FxChainVisibility, FxPresetRef, FxShowInstruction, GangBehavior, GlobalAutomationModeOverride, HelpMode, Hidden, Hwnd, InitialAction, InputMonitoringMode, InsertMediaFlag, InsertMediaMode, KbdSectionInfo, MasterTrackBehavior, MeasureMode, MediaItem, MediaItemTake, MediaTrack, @@ -30,7 +30,9 @@ use crate::{ UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext, }; -use helgoboss_midi::{Channel, ShortMessage, U4, U7}; +use helgoboss_midi::{ + Channel, ControllerNumber, RawShortMessage, ShortMessage, ShortMessageFactory, U7, +}; use reaper_low::raw::GUID; use crate::util::{ @@ -6091,7 +6093,7 @@ impl Reaper { &self, take: MediaItemTake, cc_index: u32, - ) -> Option> + ) -> Option> where UsageScope: MainThreadOnly, { @@ -6121,12 +6123,11 @@ impl Reaper { selected.assume_init(), muted.assume_init(), CcShapeKind::Square, - CcMessage { - channel_message: U4::new(chanmsg.assume_init() as u8), - channel: Channel::new(chan.assume_init() as u8), - cc_num: U7::new(msg2.assume_init() as u8), - value: U7::new(msg3.assume_init() as u8), - }, + RawShortMessage::control_change( + Channel::new(chan.assume_init() as u8), + ControllerNumber::new(msg2.assume_init() as u8), + U7::new(msg3.assume_init() as u8), + ), )), } } diff --git a/main/medium/src/source_midi.rs b/main/medium/src/source_midi.rs index 1df5e112..a119f139 100644 --- a/main/medium/src/source_midi.rs +++ b/main/medium/src/source_midi.rs @@ -1,6 +1,6 @@ use std::vec::IntoIter; -use helgoboss_midi::{Channel, U4, U7}; +use helgoboss_midi::{RawShortMessage, ShortMessage, ShortMessageFactory, U7}; use crate::{CcShapeKind, PositionInPpq}; @@ -24,37 +24,25 @@ impl SourceMidiMessage for RawMidiMessage { } } -#[derive(Clone, PartialEq, PartialOrd, Debug, Default)] -pub struct CcMessage { - pub channel_message: U4, - pub channel: Channel, - pub cc_num: U7, - pub value: U7, -} -impl SourceMidiMessage for CcMessage { - fn from_raw(buf: Vec) -> Option { - if buf.len() != 3 { - return None; - }; - if buf[0] < 0xb0 || buf[0] >= 0xc0 { +impl SourceMidiMessage for RawShortMessage { + fn from_raw(buf: Vec) -> Option + where + Self: Sized, + { + if buf.len() > 3 { return None; - }; - let channel_message: U4 = U4::new(buf[0] >> 4); - let channel: Channel = Channel::new(buf[0] & 0xf); - let cc_num: U7 = U7::new(buf[1]); - let value: U7 = U7::new(buf[2]); - Some(Self { - channel_message, - channel, - cc_num, - value, - }) + } + match RawShortMessage::from_bytes((buf[0], U7::new(buf[1]), U7::new(buf[2]))) { + Err(_) => None, + Ok(msg) => Some(msg), + } } + fn get_raw(&self) -> Vec { vec![ - (u8::from(self.channel_message)) << 4_u8 | u8::from(self.channel), - u8::from(self.cc_num), - u8::from(self.value), + self.status_byte(), + u8::from(self.data_byte_1()), + u8::from(self.data_byte_2()), ] } } From 0f28db46ae842ea7749c9f74b1d21beba26972de Mon Sep 17 00:00:00 2001 From: Timofey Date: Sun, 13 Nov 2022 22:24:55 +0100 Subject: [PATCH 16/16] `insert_* get_* set_*` `*cc` functions --- main/medium/src/reaper.rs | 73 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/main/medium/src/reaper.rs b/main/medium/src/reaper.rs index 0673493f..7e19c635 100644 --- a/main/medium/src/reaper.rs +++ b/main/medium/src/reaper.rs @@ -31,7 +31,8 @@ use crate::{ }; use helgoboss_midi::{ - Channel, ControllerNumber, RawShortMessage, ShortMessage, ShortMessageFactory, U7, + Channel, ControllerNumber, RawShortMessage, ShortMessage, ShortMessageFactory, + ShortMessageType, U7, }; use reaper_low::raw::GUID; @@ -6132,6 +6133,76 @@ impl Reaper { } } + /// Change ControlChange event at the given index. + /// + /// Returns error if: + /// - is not CC message + /// - faced problems with unpacking the message + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_set_cc( + &self, + take: MediaItemTake, + cc_index: u32, + event: SourceMidiEvent, + sort_after: bool, + ) -> Result + where + UsageScope: MainThreadOnly, + { + let msg = event.get_message(); + if msg.r#type() != ShortMessageType::ControlChange { + return Err(String::from("should be ControlChange message")); + } + Ok(self.low().MIDI_SetCC( + take.as_ptr(), + cc_index as i32, + &event.get_selected(), + &event.get_muted(), + &event.get_position().get(), + &(msg.status_byte() as i32), + &(msg.channel().ok_or("should have channel")?.get() as i32), + &i32::from(msg.controller_number().ok_or("should have cc_num")?), + &i32::from(msg.control_value().ok_or("should have control value")?), + &sort_after, + )) + } + + /// Insert ControlChange event. + /// + /// Returns error if: + /// - is not CC message + /// - faced problems with unpacking the message + /// + /// # Safety + /// + /// REAPER can crash, it you pass an invalid take. + pub unsafe fn midi_insert_cc( + &self, + take: MediaItemTake, + event: SourceMidiEvent, + ) -> Result + where + UsageScope: MainThreadOnly, + { + let msg = event.get_message(); + if msg.r#type() != ShortMessageType::ControlChange { + return Err(String::from("should be ControlChange message")); + } + Ok(self.low().MIDI_InsertCC( + take.as_ptr(), + event.get_selected(), + event.get_muted(), + event.get_position().get(), + msg.status_byte() as i32, + msg.channel().ok_or("should have channel")?.get() as i32, + i32::from(msg.controller_number().ok_or("should have cc_num")?), + i32::from(msg.control_value().ok_or("should have control value")?), + )) + } + /// Selects exactly one track and deselects all others. /// /// If `None` is passed, deselects all tracks.