use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::ptr::{null_mut, NonNull};
use reaper_low::raw;
use crate::ProjectContext::CurrentProject;
use crate::{
require_non_null_panic, ActionValueChange, AddFxBehavior, AutomationMode, Bpm, ChunkCacheHint,
CommandId, Db, EnvChunkName, FxAddByNameBehavior, FxPresetRef, FxShowInstruction, GangBehavior,
GlobalAutomationModeOverride, Hwnd, InputMonitoringMode, KbdSectionInfo, MasterTrackBehavior,
MediaTrack, MessageBoxResult, MessageBoxType, MidiInput, MidiInputDeviceId, MidiOutputDeviceId,
NotificationBehavior, PlaybackSpeedFactor, ProjectContext, ProjectRef, ReaProject,
ReaperFunctionError, ReaperFunctionResult, ReaperNormalizedFxParamValue, ReaperPanValue,
ReaperPointer, ReaperStringArg, ReaperVersion, ReaperVolumeValue, RecordArmMode,
RecordingInput, SectionContext, SectionId, SendTarget, StuffMidiMessageTarget,
TrackAttributeKey, TrackDefaultsBehavior, TrackEnvelope, TrackFxChainType, TrackFxLocation,
TrackRef, TrackSendAttributeKey, TrackSendCategory, TrackSendDirection, TransferBehavior,
UndoBehavior, UndoScope, ValueChange, VolumeSliderValue, WindowContext,
};
use helgoboss_midi::ShortMessage;
use reaper_low;
use reaper_low::raw::GUID;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::path::PathBuf;
pub trait MainThreadOnly: private::Sealed {}
#[derive(Debug, Default)]
pub struct MainThreadScope(pub(crate) ());
impl MainThreadOnly for MainThreadScope {}
pub trait AudioThreadOnly: private::Sealed {}
#[derive(Debug)]
pub struct RealTimeAudioThreadScope(pub(crate) ());
impl AudioThreadOnly for RealTimeAudioThreadScope {}
#[derive(Clone, Debug, Default)]
pub struct ReaperFunctions<UsageScope = MainThreadScope> {
low: reaper_low::Reaper,
p: PhantomData<UsageScope>,
}
impl<UsageScope> ReaperFunctions<UsageScope> {
pub(crate) fn new(low: reaper_low::Reaper) -> ReaperFunctions<UsageScope> {
ReaperFunctions {
low,
p: PhantomData,
}
}
pub fn low(&self) -> &reaper_low::Reaper {
&self.low
}
pub fn enum_projects(
&self,
project_ref: ProjectRef,
buffer_size: u32,
) -> Option<EnumProjectsResult>
where
UsageScope: MainThreadOnly,
{
let idx = project_ref.to_raw();
if buffer_size == 0 {
let ptr = unsafe { self.low.EnumProjects(idx, null_mut(), 0) };
let project = NonNull::new(ptr)?;
Some(EnumProjectsResult {
project,
file_path: None,
})
} else {
let (owned_c_string, ptr) =
with_string_buffer(buffer_size, |buffer, max_size| unsafe {
self.low.EnumProjects(idx, buffer, max_size)
});
let project = NonNull::new(ptr)?;
if owned_c_string.to_bytes().len() == 0 {
return Some(EnumProjectsResult {
project,
file_path: None,
});
}
let owned_string = owned_c_string
.into_string()
.expect("project file path contains non-UTF8 characters");
Some(EnumProjectsResult {
project,
file_path: Some(PathBuf::from(owned_string)),
})
}
}
pub fn get_track(&self, project: ProjectContext, track_index: u32) -> Option<MediaTrack>
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.get_track_unchecked(project, track_index) }
}
pub unsafe fn get_track_unchecked(
&self,
project: ProjectContext,
track_index: u32,
) -> Option<MediaTrack>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.GetTrack(project.to_raw(), track_index as i32);
NonNull::new(ptr)
}
pub fn validate_ptr_2<'a>(
&self,
project: ProjectContext,
pointer: impl Into<ReaperPointer<'a>>,
) -> bool {
let pointer = pointer.into();
unsafe {
self.low.ValidatePtr2(
project.to_raw(),
pointer.ptr_as_void(),
pointer.key_into_raw().as_ptr(),
)
}
}
pub fn validate_ptr<'a>(&self, pointer: impl Into<ReaperPointer<'a>>) -> bool
where
UsageScope: MainThreadOnly,
{
let pointer = pointer.into();
unsafe {
self.low
.ValidatePtr(pointer.ptr_as_void(), pointer.key_into_raw().as_ptr())
}
}
pub fn update_timeline(&self)
where
UsageScope: MainThreadOnly,
{
self.low.UpdateTimeline();
}
pub fn show_console_msg<'a>(&self, message: impl Into<ReaperStringArg<'a>>) {
unsafe { self.low.ShowConsoleMsg(message.into().as_ptr()) }
}
pub unsafe fn get_set_media_track_info(
&self,
track: MediaTrack,
attribute_key: TrackAttributeKey,
new_value: *mut c_void,
) -> *mut c_void
where
UsageScope: MainThreadOnly,
{
self.low
.GetSetMediaTrackInfo(track.as_ptr(), attribute_key.into_raw().as_ptr(), new_value)
}
pub unsafe fn get_set_media_track_info_get_par_track(
&self,
track: MediaTrack,
) -> Option<MediaTrack>
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_media_track_info(track, TrackAttributeKey::ParTrack, null_mut())
as *mut raw::MediaTrack;
NonNull::new(ptr)
}
pub unsafe fn get_set_media_track_info_get_project(
&self,
track: MediaTrack,
) -> Option<ReaProject>
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_media_track_info(track, TrackAttributeKey::Project, null_mut())
as *mut raw::ReaProject;
NonNull::new(ptr)
}
pub unsafe fn get_set_media_track_info_get_name<R>(
&self,
track: MediaTrack,
use_name: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_media_track_info(track, TrackAttributeKey::Name, null_mut());
create_passing_c_str(ptr as *const c_char).map(use_name)
}
pub unsafe fn get_set_media_track_info_get_rec_mon(
&self,
track: MediaTrack,
) -> InputMonitoringMode
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_media_track_info(track, TrackAttributeKey::RecMon, null_mut());
let irecmon = deref_as::<i32>(ptr).expect("irecmon pointer is null");
InputMonitoringMode::try_from_raw(irecmon).expect("unknown input monitoring mode")
}
pub unsafe fn get_set_media_track_info_get_rec_input(
&self,
track: MediaTrack,
) -> Option<RecordingInput>
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_media_track_info(track, TrackAttributeKey::RecInput, null_mut());
let rec_input_index = deref_as::<i32>(ptr).expect("rec_input_index pointer is null");
if rec_input_index < 0 {
None
} else {
Some(RecordingInput::try_from_raw(rec_input_index).expect("unknown recording input"))
}
}
pub unsafe fn get_set_media_track_info_get_track_number(
&self,
track: MediaTrack,
) -> Option<TrackRef>
where
UsageScope: MainThreadOnly,
{
use TrackRef::*;
match self.get_set_media_track_info(track, TrackAttributeKey::TrackNumber, null_mut())
as i32
{
-1 => Some(MasterTrack),
0 => None,
n if n > 0 => Some(NormalTrack(n as u32 - 1)),
_ => unreachable!(),
}
}
pub unsafe fn get_set_media_track_info_get_guid(&self, track: MediaTrack) -> GUID
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_media_track_info(track, TrackAttributeKey::Guid, null_mut());
deref_as::<GUID>(ptr).expect("GUID pointer is null")
}
pub fn is_in_real_time_audio(&self) -> bool {
self.low.IsInRealTimeAudio() != 0
}
pub fn main_on_command_ex(&self, command: CommandId, flag: i32, project: ProjectContext) {
self.require_valid_project(project);
unsafe { self.main_on_command_ex_unchecked(command, flag, project) }
}
pub unsafe fn main_on_command_ex_unchecked(
&self,
command_id: CommandId,
flag: i32,
project: ProjectContext,
) {
self.low
.Main_OnCommandEx(command_id.to_raw(), flag, project.to_raw());
}
pub unsafe fn csurf_set_surface_mute(
&self,
track: MediaTrack,
mute: bool,
notification_behavior: NotificationBehavior,
) {
self.low
.CSurf_SetSurfaceMute(track.as_ptr(), mute, notification_behavior.to_raw());
}
pub unsafe fn csurf_set_surface_solo(
&self,
track: MediaTrack,
solo: bool,
notification_behavior: NotificationBehavior,
) {
self.low
.CSurf_SetSurfaceSolo(track.as_ptr(), solo, notification_behavior.to_raw());
}
pub fn gen_guid(&self) -> GUID
where
UsageScope: MainThreadOnly,
{
let mut guid = MaybeUninit::uninit();
unsafe {
self.low.genGuid(guid.as_mut_ptr());
}
unsafe { guid.assume_init() }
}
pub fn section_from_unique_id<R>(
&self,
section_id: SectionId,
use_section: impl FnOnce(&KbdSectionInfo) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.SectionFromUniqueID(section_id.to_raw());
if ptr.is_null() {
return None;
}
NonNull::new(ptr).map(|nnp| use_section(&KbdSectionInfo(nnp)))
}
pub unsafe fn section_from_unique_id_unchecked(
&self,
section_id: SectionId,
) -> Option<KbdSectionInfo>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.SectionFromUniqueID(section_id.to_raw());
NonNull::new(ptr).map(KbdSectionInfo)
}
pub unsafe fn kbd_on_main_action_ex(
&self,
command_id: CommandId,
value_change: ActionValueChange,
window: WindowContext,
project: ProjectContext,
) -> i32
where
UsageScope: MainThreadOnly,
{
use ActionValueChange::*;
let (val, valhw, relmode) = match value_change {
AbsoluteLowRes(v) => (i32::from(v), -1, 0),
AbsoluteHighRes(v) => (
((u32::from(v) >> 7) & 0x7f) as i32,
(u32::from(v) & 0x7f) as i32,
0,
),
Relative1(v) => (i32::from(v), -1, 1),
Relative2(v) => (i32::from(v), -1, 2),
Relative3(v) => (i32::from(v), -1, 3),
};
self.low.KBD_OnMainActionEx(
command_id.to_raw(),
val,
valhw,
relmode,
window.to_raw(),
project.to_raw(),
)
}
pub fn get_main_hwnd(&self) -> Hwnd
where
UsageScope: MainThreadOnly,
{
require_non_null_panic(self.low.GetMainHwnd())
}
pub fn named_command_lookup<'a>(
&self,
command_name: impl Into<ReaperStringArg<'a>>,
) -> Option<CommandId>
where
UsageScope: MainThreadOnly,
{
let raw_id = unsafe { self.low.NamedCommandLookup(command_name.into().as_ptr()) as u32 };
if raw_id == 0 {
return None;
}
Some(CommandId(raw_id))
}
pub fn clear_console(&self) {
self.low.ClearConsole();
}
pub fn count_tracks(&self, project: ProjectContext) -> u32
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.count_tracks_unchecked(project) }
}
pub unsafe fn count_tracks_unchecked(&self, project: ProjectContext) -> u32
where
UsageScope: MainThreadOnly,
{
self.low.CountTracks(project.to_raw()) as u32
}
pub fn insert_track_at_index(&self, index: u32, defaults_behavior: TrackDefaultsBehavior) {
self.low.InsertTrackAtIndex(
index as i32,
defaults_behavior == TrackDefaultsBehavior::AddDefaultEnvAndFx,
);
}
pub fn get_max_midi_inputs(&self) -> u32 {
self.low.GetMaxMidiInputs() as u32
}
pub fn get_max_midi_outputs(&self) -> u32 {
self.low.GetMaxMidiOutputs() as u32
}
pub fn get_midi_input_name(
&self,
device_id: MidiInputDeviceId,
buffer_size: u32,
) -> GetMidiDevNameResult
where
UsageScope: MainThreadOnly,
{
if buffer_size == 0 {
let is_present =
unsafe { self.low.GetMIDIInputName(device_id.to_raw(), null_mut(), 0) };
GetMidiDevNameResult {
is_present,
name: None,
}
} else {
let (name, is_present) = with_string_buffer(buffer_size, |buffer, max_size| unsafe {
self.low
.GetMIDIInputName(device_id.to_raw(), buffer, max_size)
});
if name.to_bytes().len() == 0 {
return GetMidiDevNameResult {
is_present,
name: None,
};
}
GetMidiDevNameResult {
is_present,
name: Some(name),
}
}
}
pub fn get_midi_output_name(
&self,
device_id: MidiOutputDeviceId,
buffer_size: u32,
) -> GetMidiDevNameResult
where
UsageScope: MainThreadOnly,
{
if buffer_size == 0 {
let is_present = unsafe {
self.low
.GetMIDIOutputName(device_id.to_raw(), null_mut(), 0)
};
GetMidiDevNameResult {
is_present,
name: None,
}
} else {
let (name, is_present) = with_string_buffer(buffer_size, |buffer, max_size| unsafe {
self.low
.GetMIDIOutputName(device_id.to_raw(), buffer, max_size)
});
if name.to_bytes().len() == 0 {
return GetMidiDevNameResult {
is_present,
name: None,
};
}
GetMidiDevNameResult {
is_present,
name: Some(name),
}
}
}
unsafe fn track_fx_add_by_name<'a>(
&self,
track: MediaTrack,
fx_name: impl Into<ReaperStringArg<'a>>,
fx_chain_type: TrackFxChainType,
behavior: FxAddByNameBehavior,
) -> i32
where
UsageScope: MainThreadOnly,
{
self.low.TrackFX_AddByName(
track.as_ptr(),
fx_name.into().as_ptr(),
fx_chain_type == TrackFxChainType::InputFxChain,
behavior.to_raw(),
)
}
pub unsafe fn track_fx_add_by_name_query<'a>(
&self,
track: MediaTrack,
fx_name: impl Into<ReaperStringArg<'a>>,
fx_chain_type: TrackFxChainType,
) -> Option<u32>
where
UsageScope: MainThreadOnly,
{
match self.track_fx_add_by_name(track, fx_name, fx_chain_type, FxAddByNameBehavior::Query) {
-1 => None,
idx if idx >= 0 => Some(idx as u32),
_ => unreachable!(),
}
}
pub unsafe fn track_fx_add_by_name_add<'a>(
&self,
track: MediaTrack,
fx_name: impl Into<ReaperStringArg<'a>>,
fx_chain_type: TrackFxChainType,
behavior: AddFxBehavior,
) -> ReaperFunctionResult<u32>
where
UsageScope: MainThreadOnly,
{
match self.track_fx_add_by_name(track, fx_name, fx_chain_type, behavior.into()) {
-1 => Err(ReaperFunctionError::new("FX couldn't be added")),
idx if idx >= 0 => Ok(idx as u32),
_ => unreachable!(),
}
}
pub unsafe fn track_fx_get_enabled(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
) -> bool
where
UsageScope: MainThreadOnly,
{
self.low
.TrackFX_GetEnabled(track.as_ptr(), fx_location.to_raw())
}
pub unsafe fn track_fx_get_fx_name(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
buffer_size: u32,
) -> ReaperFunctionResult<CString>
where
UsageScope: MainThreadOnly,
{
assert!(buffer_size > 0);
let (name, successful) = with_string_buffer(buffer_size, |buffer, max_size| {
self.low
.TrackFX_GetFXName(track.as_ptr(), fx_location.to_raw(), buffer, max_size)
});
if !successful {
return Err(ReaperFunctionError::new(
"couldn't get FX name (probably FX doesn't exist)",
));
}
Ok(name)
}
pub unsafe fn track_fx_get_instrument(&self, track: MediaTrack) -> Option<u32>
where
UsageScope: MainThreadOnly,
{
let index = self.low.TrackFX_GetInstrument(track.as_ptr());
if index == -1 {
return None;
}
Some(index as u32)
}
pub unsafe fn track_fx_set_enabled(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
is_enabled: bool,
) {
self.low
.TrackFX_SetEnabled(track.as_ptr(), fx_location.to_raw(), is_enabled);
}
pub unsafe fn track_fx_get_num_params(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
) -> u32
where
UsageScope: MainThreadOnly,
{
self.low
.TrackFX_GetNumParams(track.as_ptr(), fx_location.to_raw()) as u32
}
pub fn get_current_project_in_load_save(&self) -> Option<ReaProject>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.GetCurrentProjectInLoadSave();
NonNull::new(ptr)
}
pub unsafe fn track_fx_get_param_name(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
buffer_size: u32,
) -> ReaperFunctionResult<CString>
where
UsageScope: MainThreadOnly,
{
assert!(buffer_size > 0);
let (name, successful) = with_string_buffer(buffer_size, |buffer, max_size| {
self.low.TrackFX_GetParamName(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
buffer,
max_size,
)
});
if !successful {
return Err(ReaperFunctionError::new(
"couldn't get FX parameter name (probably FX or parameter doesn't exist)",
));
}
Ok(name)
}
pub unsafe fn track_fx_get_formatted_param_value(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
buffer_size: u32,
) -> ReaperFunctionResult<CString>
where
UsageScope: MainThreadOnly,
{
assert!(buffer_size > 0);
let (name, successful) = with_string_buffer(buffer_size, |buffer, max_size| {
self.low.TrackFX_GetFormattedParamValue(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
buffer,
max_size,
)
});
if !successful {
return Err(ReaperFunctionError::new(
"couldn't format current FX parameter value (probably FX or parameter doesn't exist)",
));
}
Ok(name)
}
pub unsafe fn track_fx_format_param_value_normalized(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
param_value: ReaperNormalizedFxParamValue,
buffer_size: u32,
) -> ReaperFunctionResult<CString>
where
UsageScope: MainThreadOnly,
{
assert!(buffer_size > 0);
let (name, successful) = with_string_buffer(buffer_size, |buffer, max_size| {
self.low.TrackFX_FormatParamValueNormalized(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
param_value.get(),
buffer,
max_size,
)
});
if !successful {
"FX or FX parameter not found or Cockos extensions not supported";
return Err(ReaperFunctionError::new(
"couldn't format FX parameter value (FX maybe doesn't support Cockos extensions or FX or parameter doesn't exist)",
));
}
Ok(name)
}
pub unsafe fn track_fx_set_param_normalized(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
param_value: ReaperNormalizedFxParamValue,
) -> ReaperFunctionResult<()>
where
UsageScope: MainThreadOnly,
{
let successful = self.low.TrackFX_SetParamNormalized(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
param_value.get(),
);
if !successful {
return Err(ReaperFunctionError::new(
"couldn't set FX parameter value (probably FX or parameter doesn't exist)",
));
}
Ok(())
}
pub fn get_focused_fx(&self) -> Option<GetFocusedFxResult>
where
UsageScope: MainThreadOnly,
{
let mut tracknumber = MaybeUninit::uninit();
let mut itemnumber = MaybeUninit::uninit();
let mut fxnumber = MaybeUninit::uninit();
let result = unsafe {
self.low.GetFocusedFX(
tracknumber.as_mut_ptr(),
itemnumber.as_mut_ptr(),
fxnumber.as_mut_ptr(),
)
};
let tracknumber = unsafe { tracknumber.assume_init() as u32 };
let fxnumber = unsafe { fxnumber.assume_init() };
use GetFocusedFxResult::*;
match result {
0 => None,
1 => Some(TrackFx {
track_ref: convert_tracknumber_to_track_ref(tracknumber),
fx_location: TrackFxLocation::try_from_raw(fxnumber)
.expect("unknown track FX location"),
}),
2 => {
let fxnumber = fxnumber as u32;
Some(TakeFx {
track_index: tracknumber - 1,
item_index: unsafe { itemnumber.assume_init() as u32 },
take_index: (fxnumber >> 16) & 0xFFFF,
fx_index: fxnumber & 0xFFFF,
})
}
_ => panic!("Unknown GetFocusedFX result value"),
}
}
pub fn get_last_touched_fx(&self) -> Option<GetLastTouchedFxResult>
where
UsageScope: MainThreadOnly,
{
let mut tracknumber = MaybeUninit::uninit();
let mut fxnumber = MaybeUninit::uninit();
let mut paramnumber = MaybeUninit::uninit();
let is_valid = unsafe {
self.low.GetLastTouchedFX(
tracknumber.as_mut_ptr(),
fxnumber.as_mut_ptr(),
paramnumber.as_mut_ptr(),
)
};
if !is_valid {
return None;
}
let tracknumber = unsafe { tracknumber.assume_init() as u32 };
let tracknumber_high_word = (tracknumber >> 16) & 0xFFFF;
let fxnumber = unsafe { fxnumber.assume_init() };
let paramnumber = unsafe { paramnumber.assume_init() as u32 };
use GetLastTouchedFxResult::*;
if tracknumber_high_word == 0 {
Some(TrackFx {
track_ref: convert_tracknumber_to_track_ref(tracknumber),
fx_location: TrackFxLocation::try_from_raw(fxnumber)
.expect("unknown track FX location"),
param_index: paramnumber,
})
} else {
let fxnumber = fxnumber as u32;
Some(TakeFx {
track_index: (tracknumber & 0xFFFF) - 1,
item_index: tracknumber_high_word - 1,
take_index: (fxnumber >> 16) & 0xFFFF,
fx_index: fxnumber & 0xFFFF,
param_index: paramnumber,
})
}
}
pub unsafe fn track_fx_copy_to_track(
&self,
source: (MediaTrack, TrackFxLocation),
destination: (MediaTrack, TrackFxLocation),
transfer_behavior: TransferBehavior,
) {
self.low.TrackFX_CopyToTrack(
source.0.as_ptr(),
source.1.to_raw(),
destination.0.as_ptr(),
destination.1.to_raw(),
transfer_behavior == TransferBehavior::Move,
);
}
pub unsafe fn track_fx_delete(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
) -> ReaperFunctionResult<()>
where
UsageScope: MainThreadOnly,
{
let succesful = self
.low
.TrackFX_Delete(track.as_ptr(), fx_location.to_raw());
if !succesful {
return Err(ReaperFunctionError::new(
"couldn't delete FX (probably FX doesn't exist)",
));
}
Ok(())
}
pub unsafe fn track_fx_get_parameter_step_sizes(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
) -> Option<GetParameterStepSizesResult>
where
UsageScope: MainThreadOnly,
{
let mut step = MaybeUninit::uninit();
let mut small_step = MaybeUninit::uninit();
let mut large_step = MaybeUninit::uninit();
let mut is_toggle = MaybeUninit::uninit();
let successful = self.low.TrackFX_GetParameterStepSizes(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
step.as_mut_ptr(),
small_step.as_mut_ptr(),
large_step.as_mut_ptr(),
is_toggle.as_mut_ptr(),
);
if !successful {
return None;
}
let is_toggle = is_toggle.assume_init();
if is_toggle {
Some(GetParameterStepSizesResult::Toggle)
} else {
Some(GetParameterStepSizesResult::Normal {
normal_step: step.assume_init(),
small_step: make_some_if_greater_than_zero(small_step.assume_init()),
large_step: make_some_if_greater_than_zero(large_step.assume_init()),
})
}
}
pub unsafe fn track_fx_get_param_ex(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
) -> GetParamExResult
where
UsageScope: MainThreadOnly,
{
let mut min_val = MaybeUninit::uninit();
let mut max_val = MaybeUninit::uninit();
let mut mid_val = MaybeUninit::uninit();
let value = self.low.TrackFX_GetParamEx(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
min_val.as_mut_ptr(),
max_val.as_mut_ptr(),
mid_val.as_mut_ptr(),
);
GetParamExResult {
current_value: value,
min_value: min_val.assume_init(),
mid_value: mid_val.assume_init(),
max_value: max_val.assume_init(),
}
.into()
}
pub fn undo_begin_block_2(&self, project: ProjectContext) {
self.require_valid_project(project);
unsafe { self.undo_begin_block_2_unchecked(project) };
}
pub unsafe fn undo_begin_block_2_unchecked(&self, project: ProjectContext) {
self.low.Undo_BeginBlock2(project.to_raw());
}
pub fn undo_end_block_2<'a>(
&self,
project: ProjectContext,
description: impl Into<ReaperStringArg<'a>>,
scope: UndoScope,
) {
self.require_valid_project(project);
unsafe {
self.undo_end_block_2_unchecked(project, description, scope);
}
}
pub unsafe fn undo_end_block_2_unchecked<'a>(
&self,
project: ProjectContext,
description: impl Into<ReaperStringArg<'a>>,
scope: UndoScope,
) {
self.low.Undo_EndBlock2(
project.to_raw(),
description.into().as_ptr(),
scope.to_raw(),
);
}
pub fn undo_can_undo_2<R>(
&self,
project: ProjectContext,
use_description: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.undo_can_undo_2_unchecked(project, use_description) }
}
pub unsafe fn undo_can_undo_2_unchecked<R>(
&self,
project: ProjectContext,
use_description: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.Undo_CanUndo2(project.to_raw());
create_passing_c_str(ptr).map(use_description)
}
pub fn undo_can_redo_2<R>(
&self,
project: ProjectContext,
use_description: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.undo_can_redo_2_unchecked(project, use_description) }
}
pub unsafe fn undo_can_redo_2_unchecked<R>(
&self,
project: ProjectContext,
use_description: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.Undo_CanRedo2(project.to_raw());
create_passing_c_str(ptr).map(use_description)
}
pub fn undo_do_undo_2(&self, project: ProjectContext) -> bool
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.undo_do_undo_2_unchecked(project) }
}
pub unsafe fn undo_do_undo_2_unchecked(&self, project: ProjectContext) -> bool
where
UsageScope: MainThreadOnly,
{
self.low.Undo_DoUndo2(project.to_raw()) != 0
}
pub fn undo_do_redo_2(&self, project: ProjectContext) -> bool
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.undo_do_redo_2_unchecked(project) }
}
pub unsafe fn undo_do_redo_2_unchecked(&self, project: ProjectContext) -> bool
where
UsageScope: MainThreadOnly,
{
self.low.Undo_DoRedo2(project.to_raw()) != 0
}
pub fn mark_project_dirty(&self, project: ProjectContext) {
self.require_valid_project(project);
unsafe {
self.mark_project_dirty_unchecked(project);
}
}
pub unsafe fn mark_project_dirty_unchecked(&self, project: ProjectContext) {
self.low.MarkProjectDirty(project.to_raw());
}
pub fn is_project_dirty(&self, project: ProjectContext) -> bool
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.is_project_dirty_unchecked(project) }
}
pub unsafe fn is_project_dirty_unchecked(&self, project: ProjectContext) -> bool
where
UsageScope: MainThreadOnly,
{
self.low.IsProjectDirty(project.to_raw()) != 0
}
pub fn track_list_update_all_external_surfaces(&self) {
self.low.TrackList_UpdateAllExternalSurfaces();
}
pub fn get_app_version(&self) -> ReaperVersion<'static>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.GetAppVersion();
let version_str = unsafe { CStr::from_ptr(ptr) };
ReaperVersion::new(version_str)
}
pub unsafe fn get_track_automation_mode(&self, track: MediaTrack) -> AutomationMode
where
UsageScope: MainThreadOnly,
{
let result = self.low.GetTrackAutomationMode(track.as_ptr());
AutomationMode::try_from_raw(result).expect("unknown automation mode")
}
pub fn get_global_automation_override(&self) -> Option<GlobalAutomationModeOverride>
where
UsageScope: MainThreadOnly,
{
use GlobalAutomationModeOverride::*;
match self.low.GetGlobalAutomationOverride() {
-1 => None,
6 => Some(Bypass),
x => Some(Mode(
AutomationMode::try_from_raw(x).expect("unknown automation mode"),
)),
}
}
pub unsafe fn get_track_envelope_by_chunk_name(
&self,
track: MediaTrack,
chunk_name: EnvChunkName,
) -> Option<TrackEnvelope>
where
UsageScope: MainThreadOnly,
{
let ptr = self
.low
.GetTrackEnvelopeByChunkName(track.as_ptr(), chunk_name.into_raw().as_ptr());
NonNull::new(ptr)
}
pub unsafe fn get_track_envelope_by_name<'a>(
&self,
track: MediaTrack,
env_name: impl Into<ReaperStringArg<'a>>,
) -> Option<TrackEnvelope>
where
UsageScope: MainThreadOnly,
{
let ptr = self
.low
.GetTrackEnvelopeByName(track.as_ptr(), env_name.into().as_ptr());
NonNull::new(ptr)
}
pub unsafe fn get_media_track_info_value(
&self,
track: MediaTrack,
attribute_key: TrackAttributeKey,
) -> f64
where
UsageScope: MainThreadOnly,
{
self.low
.GetMediaTrackInfo_Value(track.as_ptr(), attribute_key.into_raw().as_ptr())
}
pub unsafe fn track_fx_get_count(&self, track: MediaTrack) -> u32
where
UsageScope: MainThreadOnly,
{
self.low.TrackFX_GetCount(track.as_ptr()) as u32
}
pub unsafe fn track_fx_get_rec_count(&self, track: MediaTrack) -> u32
where
UsageScope: MainThreadOnly,
{
self.low.TrackFX_GetRecCount(track.as_ptr()) as u32
}
pub unsafe fn track_fx_get_fx_guid(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
) -> ReaperFunctionResult<GUID>
where
UsageScope: MainThreadOnly,
{
let ptr = self
.low
.TrackFX_GetFXGUID(track.as_ptr(), fx_location.to_raw());
deref(ptr).ok_or(ReaperFunctionError::new(
"couldn't get FX GUID (probably FX doesn't exist)",
))
}
pub unsafe fn track_fx_get_param_normalized(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
param_index: u32,
) -> ReaperFunctionResult<ReaperNormalizedFxParamValue>
where
UsageScope: MainThreadOnly,
{
let raw_value = self.low.TrackFX_GetParamNormalized(
track.as_ptr(),
fx_location.to_raw(),
param_index as i32,
);
if raw_value < 0.0 {
return Err(ReaperFunctionError::new(
"couldn't get current FX parameter value (probably FX or parameter doesn't exist)",
));
}
Ok(ReaperNormalizedFxParamValue::new(raw_value))
}
pub fn get_master_track(&self, project: ProjectContext) -> MediaTrack
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.get_master_track_unchecked(project) }
}
pub unsafe fn get_master_track_unchecked(&self, project: ProjectContext) -> MediaTrack
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.GetMasterTrack(project.to_raw());
require_non_null_panic(ptr)
}
pub fn guid_to_string(&self, guid: &GUID) -> CString
where
UsageScope: MainThreadOnly,
{
let (guid_string, _) = with_string_buffer(64, |buffer, _| unsafe {
self.low.guidToString(guid as *const GUID, buffer)
});
guid_string
}
pub fn master_get_tempo(&self) -> Bpm
where
UsageScope: MainThreadOnly,
{
Bpm(self.low.Master_GetTempo())
}
pub fn set_current_bpm(
&self,
project: ProjectContext,
tempo: Bpm,
undo_behavior: UndoBehavior,
) {
self.require_valid_project(project);
unsafe {
self.set_current_bpm_unchecked(project, tempo, undo_behavior);
}
}
pub unsafe fn set_current_bpm_unchecked(
&self,
project: ProjectContext,
tempo: Bpm,
undo_behavior: UndoBehavior,
) {
self.low.SetCurrentBPM(
project.to_raw(),
tempo.get(),
undo_behavior == UndoBehavior::AddUndoPoint,
);
}
pub fn master_get_play_rate(&self, project: ProjectContext) -> PlaybackSpeedFactor
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.master_get_play_rate_unchecked(project) }
}
pub unsafe fn master_get_play_rate_unchecked(
&self,
project: ProjectContext,
) -> PlaybackSpeedFactor
where
UsageScope: MainThreadOnly,
{
let raw = self.low.Master_GetPlayRate(project.to_raw());
PlaybackSpeedFactor(raw)
}
pub fn csurf_on_play_rate_change(&self, play_rate: PlaybackSpeedFactor) {
self.low.CSurf_OnPlayRateChange(play_rate.get());
}
pub fn show_message_box<'a>(
&self,
message: impl Into<ReaperStringArg<'a>>,
title: impl Into<ReaperStringArg<'a>>,
r#type: MessageBoxType,
) -> MessageBoxResult
where
UsageScope: MainThreadOnly,
{
let result = unsafe {
self.low.ShowMessageBox(
message.into().as_ptr(),
title.into().as_ptr(),
r#type.to_raw(),
)
};
MessageBoxResult::try_from_raw(result).expect("unknown message box result")
}
pub fn string_to_guid<'a>(
&self,
guid_string: impl Into<ReaperStringArg<'a>>,
) -> ReaperFunctionResult<GUID>
where
UsageScope: MainThreadOnly,
{
let mut guid = MaybeUninit::uninit();
unsafe {
self.low
.stringToGuid(guid_string.into().as_ptr(), guid.as_mut_ptr());
}
let guid = unsafe { guid.assume_init() };
if guid == ZERO_GUID {
return Err(ReaperFunctionError::new("GUID string is invalid"));
}
Ok(guid)
}
pub unsafe fn csurf_on_input_monitoring_change_ex(
&self,
track: MediaTrack,
mode: InputMonitoringMode,
gang_behavior: GangBehavior,
) -> i32
where
UsageScope: MainThreadOnly,
{
self.low.CSurf_OnInputMonitorChangeEx(
track.as_ptr(),
mode.to_raw(),
gang_behavior == GangBehavior::AllowGang,
)
}
pub unsafe fn set_media_track_info_value(
&self,
track: MediaTrack,
attribute_key: TrackAttributeKey,
new_value: f64,
) -> ReaperFunctionResult<()>
where
UsageScope: MainThreadOnly,
{
let successful = self.low.SetMediaTrackInfo_Value(
track.as_ptr(),
attribute_key.into_raw().as_ptr(),
new_value,
);
if !successful {
return Err(ReaperFunctionError::new(
"couldn't set track attribute (maybe attribute key is invalid)",
));
}
Ok(())
}
pub fn stuff_midimessage(&self, target: StuffMidiMessageTarget, message: impl ShortMessage) {
let bytes = message.to_bytes();
self.low.StuffMIDIMessage(
target.to_raw(),
bytes.0.into(),
bytes.1.into(),
bytes.2.into(),
);
}
pub fn db2slider(&self, value: Db) -> VolumeSliderValue
where
UsageScope: MainThreadOnly,
{
VolumeSliderValue(self.low.DB2SLIDER(value.get()))
}
pub fn slider2db(&self, value: VolumeSliderValue) -> Db
where
UsageScope: MainThreadOnly,
{
Db(self.low.SLIDER2DB(value.get()))
}
pub unsafe fn get_track_ui_vol_pan(
&self,
track: MediaTrack,
) -> ReaperFunctionResult<VolumeAndPan>
where
UsageScope: MainThreadOnly,
{
let mut volume = MaybeUninit::uninit();
let mut pan = MaybeUninit::uninit();
let successful =
self.low
.GetTrackUIVolPan(track.as_ptr(), volume.as_mut_ptr(), pan.as_mut_ptr());
if !successful {
return Err(ReaperFunctionError::new(
"couldn't get track volume and pan",
));
}
Ok(VolumeAndPan {
volume: ReaperVolumeValue::new(volume.assume_init()),
pan: ReaperPanValue::new(pan.assume_init()),
})
}
pub unsafe fn csurf_set_surface_volume(
&self,
track: MediaTrack,
volume: ReaperVolumeValue,
notification_behavior: NotificationBehavior,
) {
self.low.CSurf_SetSurfaceVolume(
track.as_ptr(),
volume.get(),
notification_behavior.to_raw(),
);
}
pub unsafe fn csurf_on_volume_change_ex(
&self,
track: MediaTrack,
value_change: ValueChange<ReaperVolumeValue>,
gang_behavior: GangBehavior,
) -> ReaperVolumeValue
where
UsageScope: MainThreadOnly,
{
let raw = self.low.CSurf_OnVolumeChangeEx(
track.as_ptr(),
value_change.value(),
value_change.is_relative(),
gang_behavior == GangBehavior::AllowGang,
);
ReaperVolumeValue::new(raw)
}
pub unsafe fn csurf_set_surface_pan(
&self,
track: MediaTrack,
pan: ReaperPanValue,
notification_behavior: NotificationBehavior,
) {
self.low
.CSurf_SetSurfacePan(track.as_ptr(), pan.get(), notification_behavior.to_raw());
}
pub unsafe fn csurf_on_pan_change_ex(
&self,
track: MediaTrack,
value_change: ValueChange<ReaperPanValue>,
gang_behavior: GangBehavior,
) -> ReaperPanValue
where
UsageScope: MainThreadOnly,
{
let raw = self.low.CSurf_OnPanChangeEx(
track.as_ptr(),
value_change.value(),
value_change.is_relative(),
gang_behavior == GangBehavior::AllowGang,
);
ReaperPanValue::new(raw)
}
pub fn count_selected_tracks_2(
&self,
project: ProjectContext,
master_track_behavior: MasterTrackBehavior,
) -> u32
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe { self.count_selected_tracks_2_unchecked(project, master_track_behavior) }
}
pub unsafe fn count_selected_tracks_2_unchecked(
&self,
project: ProjectContext,
master_track_behavior: MasterTrackBehavior,
) -> u32
where
UsageScope: MainThreadOnly,
{
self.low.CountSelectedTracks2(
project.to_raw(),
master_track_behavior == MasterTrackBehavior::IncludeMasterTrack,
) as u32
}
pub unsafe fn set_track_selected(&self, track: MediaTrack, is_selected: bool) {
self.low.SetTrackSelected(track.as_ptr(), is_selected);
}
pub fn get_selected_track_2(
&self,
project: ProjectContext,
selected_track_index: u32,
master_track_behavior: MasterTrackBehavior,
) -> Option<MediaTrack>
where
UsageScope: MainThreadOnly,
{
self.require_valid_project(project);
unsafe {
self.get_selected_track_2_unchecked(
project,
selected_track_index,
master_track_behavior,
)
}
}
pub unsafe fn get_selected_track_2_unchecked(
&self,
project: ProjectContext,
selected_track_index: u32,
master_track_behavior: MasterTrackBehavior,
) -> Option<MediaTrack>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.GetSelectedTrack2(
project.to_raw(),
selected_track_index as i32,
master_track_behavior == MasterTrackBehavior::IncludeMasterTrack,
);
NonNull::new(ptr)
}
pub unsafe fn set_only_track_selected(&self, track: Option<MediaTrack>) {
let ptr = match track {
None => null_mut(),
Some(t) => t.as_ptr(),
};
self.low.SetOnlyTrackSelected(ptr);
}
pub unsafe fn delete_track(&self, track: MediaTrack) {
self.low.DeleteTrack(track.as_ptr());
}
pub unsafe fn get_track_num_sends(&self, track: MediaTrack, category: TrackSendCategory) -> u32
where
UsageScope: MainThreadOnly,
{
self.low.GetTrackNumSends(track.as_ptr(), category.to_raw()) as u32
}
pub unsafe fn get_set_track_send_info(
&self,
track: MediaTrack,
category: TrackSendCategory,
send_index: u32,
attribute_key: TrackSendAttributeKey,
new_value: *mut c_void,
) -> *mut c_void
where
UsageScope: MainThreadOnly,
{
self.low.GetSetTrackSendInfo(
track.as_ptr(),
category.to_raw(),
send_index as i32,
attribute_key.into_raw().as_ptr(),
new_value,
)
}
pub unsafe fn get_track_send_info_desttrack(
&self,
track: MediaTrack,
direction: TrackSendDirection,
send_index: u32,
) -> ReaperFunctionResult<MediaTrack>
where
UsageScope: MainThreadOnly,
{
let ptr = self.get_set_track_send_info(
track,
direction.into(),
send_index,
TrackSendAttributeKey::DestTrack,
null_mut(),
) as *mut raw::MediaTrack;
NonNull::new(ptr).ok_or(ReaperFunctionError::new(
"couldn't get destination track (maybe send doesn't exist)",
))
}
pub unsafe fn get_track_state_chunk(
&self,
track: MediaTrack,
buffer_size: u32,
cache_hint: ChunkCacheHint,
) -> ReaperFunctionResult<CString>
where
UsageScope: MainThreadOnly,
{
assert!(buffer_size > 0);
let (chunk_content, successful) = with_string_buffer(buffer_size, |buffer, max_size| {
self.low.GetTrackStateChunk(
track.as_ptr(),
buffer,
max_size,
cache_hint == ChunkCacheHint::UndoMode,
)
});
if !successful {
return Err(ReaperFunctionError::new("couldn't get track chunk"));
}
Ok(chunk_content)
}
pub unsafe fn create_track_send(
&self,
track: MediaTrack,
target: SendTarget,
) -> ReaperFunctionResult<u32>
where
UsageScope: MainThreadOnly,
{
let result = self.low.CreateTrackSend(track.as_ptr(), target.to_raw());
if result < 0 {
return Err(ReaperFunctionError::new("couldn't create track send"));
}
Ok(result as u32)
}
pub unsafe fn csurf_on_rec_arm_change_ex(
&self,
track: MediaTrack,
mode: RecordArmMode,
gang_behavior: GangBehavior,
) -> bool
where
UsageScope: MainThreadOnly,
{
self.low.CSurf_OnRecArmChangeEx(
track.as_ptr(),
mode.to_raw(),
gang_behavior == GangBehavior::AllowGang,
)
}
pub unsafe fn set_track_state_chunk<'a>(
&self,
track: MediaTrack,
chunk: impl Into<ReaperStringArg<'a>>,
cache_hint: ChunkCacheHint,
) -> ReaperFunctionResult<()>
where
UsageScope: MainThreadOnly,
{
let successful = self.low.SetTrackStateChunk(
track.as_ptr(),
chunk.into().as_ptr(),
cache_hint == ChunkCacheHint::UndoMode,
);
if !successful {
return Err(ReaperFunctionError::new(
"couldn't set track chunk (maybe chunk was invalid)",
));
}
Ok(())
}
pub unsafe fn track_fx_show(&self, track: MediaTrack, instruction: FxShowInstruction) {
self.low.TrackFX_Show(
track.as_ptr(),
instruction.location_to_raw(),
instruction.instruction_to_raw(),
);
}
pub unsafe fn track_fx_get_floating_window(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
) -> Option<Hwnd>
where
UsageScope: MainThreadOnly,
{
let ptr = self
.low
.TrackFX_GetFloatingWindow(track.as_ptr(), fx_location.to_raw());
NonNull::new(ptr)
}
pub unsafe fn track_fx_get_open(&self, track: MediaTrack, fx_location: TrackFxLocation) -> bool
where
UsageScope: MainThreadOnly,
{
self.low
.TrackFX_GetOpen(track.as_ptr(), fx_location.to_raw())
}
pub unsafe fn csurf_on_send_volume_change(
&self,
track: MediaTrack,
send_index: u32,
value_change: ValueChange<ReaperVolumeValue>,
) -> ReaperVolumeValue
where
UsageScope: MainThreadOnly,
{
let raw = self.low.CSurf_OnSendVolumeChange(
track.as_ptr(),
send_index as i32,
value_change.value(),
value_change.is_relative(),
);
ReaperVolumeValue::new(raw)
}
pub unsafe fn csurf_on_send_pan_change(
&self,
track: MediaTrack,
send_index: u32,
value_change: ValueChange<ReaperPanValue>,
) -> ReaperPanValue
where
UsageScope: MainThreadOnly,
{
let raw = self.low.CSurf_OnSendPanChange(
track.as_ptr(),
send_index as i32,
value_change.value(),
value_change.is_relative(),
);
ReaperPanValue::new(raw)
}
pub unsafe fn kbd_get_text_from_cmd<R>(
&self,
command_id: CommandId,
section: SectionContext,
use_action_name: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
let ptr = self
.low
.kbd_getTextFromCmd(command_id.get() as _, section.to_raw());
create_passing_c_str(ptr)
.filter(|s| s.to_bytes().len() > 0)
.map(use_action_name)
}
pub unsafe fn get_toggle_command_state_2(
&self,
section: SectionContext,
command_id: CommandId,
) -> Option<bool>
where
UsageScope: MainThreadOnly,
{
let result = self
.low
.GetToggleCommandState2(section.to_raw(), command_id.to_raw());
if result == -1 {
return None;
}
return Some(result != 0);
}
pub fn reverse_named_command_lookup<R>(
&self,
command_id: CommandId,
use_command_name: impl FnOnce(&CStr) -> R,
) -> Option<R>
where
UsageScope: MainThreadOnly,
{
let ptr = self.low.ReverseNamedCommandLookup(command_id.to_raw());
unsafe { create_passing_c_str(ptr) }.map(use_command_name)
}
pub unsafe fn get_track_send_ui_vol_pan(
&self,
track: MediaTrack,
send_index: u32,
) -> ReaperFunctionResult<VolumeAndPan>
where
UsageScope: MainThreadOnly,
{
let mut volume = MaybeUninit::uninit();
let mut pan = MaybeUninit::uninit();
let successful = self.low.GetTrackSendUIVolPan(
track.as_ptr(),
send_index as i32,
volume.as_mut_ptr(),
pan.as_mut_ptr(),
);
if !successful {
return Err(ReaperFunctionError::new(
"couldn't get track send volume and pan (probably send doesn't exist)",
));
}
Ok(VolumeAndPan {
volume: ReaperVolumeValue::new(volume.assume_init()),
pan: ReaperPanValue::new(pan.assume_init()),
})
}
pub unsafe fn track_fx_get_preset_index(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
) -> ReaperFunctionResult<TrackFxGetPresetIndexResult>
where
UsageScope: MainThreadOnly,
{
let mut num_presets = MaybeUninit::uninit();
let index = self.low.TrackFX_GetPresetIndex(
track.as_ptr(),
fx_location.to_raw(),
num_presets.as_mut_ptr(),
);
if index == -1 {
return Err(ReaperFunctionError::new(
"couldn't get FX preset index (maybe FX doesn't exist)",
));
}
Ok(TrackFxGetPresetIndexResult {
index: index as u32,
count: num_presets.assume_init() as u32,
})
}
pub unsafe fn track_fx_set_preset_by_index(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
preset: FxPresetRef,
) -> ReaperFunctionResult<()>
where
UsageScope: MainThreadOnly,
{
let successful = self.low.TrackFX_SetPresetByIndex(
track.as_ptr(),
fx_location.to_raw(),
preset.to_raw(),
);
if !successful {
return Err(ReaperFunctionError::new(
"couldn't select FX preset (maybe FX doesn't exist)",
));
}
Ok(())
}
pub unsafe fn track_fx_navigate_presets(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
increment: i32,
) -> ReaperFunctionResult<()>
where
UsageScope: MainThreadOnly,
{
let successful =
self.low
.TrackFX_NavigatePresets(track.as_ptr(), fx_location.to_raw(), increment);
if !successful {
return Err(ReaperFunctionError::new(
"couldn't navigate FX presets (maybe FX doesn't exist)",
));
}
Ok(())
}
pub unsafe fn track_fx_get_preset(
&self,
track: MediaTrack,
fx_location: TrackFxLocation,
buffer_size: u32,
) -> TrackFxGetPresetResult
where
UsageScope: MainThreadOnly,
{
if buffer_size == 0 {
let state_matches_preset =
self.low
.TrackFX_GetPreset(track.as_ptr(), fx_location.to_raw(), null_mut(), 0);
TrackFxGetPresetResult {
state_matches_preset,
name: None,
}
} else {
let (name, state_matches_preset) =
with_string_buffer(buffer_size, |buffer, max_size| {
self.low.TrackFX_GetPreset(
track.as_ptr(),
fx_location.to_raw(),
buffer,
max_size,
)
});
if name.to_bytes().len() == 0 {
return TrackFxGetPresetResult {
state_matches_preset,
name: None,
};
}
TrackFxGetPresetResult {
state_matches_preset,
name: Some(name),
}
}
}
pub fn get_midi_input<R>(
&self,
device_id: MidiInputDeviceId,
use_device: impl FnOnce(&MidiInput) -> R,
) -> Option<R>
where
UsageScope: AudioThreadOnly,
{
let ptr = self.low.GetMidiInput(device_id.to_raw());
if ptr.is_null() {
return None;
}
NonNull::new(ptr).map(|nnp| use_device(&MidiInput(nnp)))
}
fn require_valid_project(&self, project: ProjectContext) {
if let ProjectContext::Proj(p) = project {
assert!(
self.validate_ptr_2(CurrentProject, p),
"ReaProject doesn't exist anymore"
)
}
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum GetParameterStepSizesResult {
Normal {
normal_step: f64,
small_step: Option<f64>,
large_step: Option<f64>,
},
Toggle,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct GetParamExResult {
pub current_value: f64,
pub min_value: f64,
pub mid_value: f64,
pub max_value: f64,
}
#[derive(Clone, PartialEq, Hash, Debug)]
pub struct EnumProjectsResult {
pub project: ReaProject,
pub file_path: Option<PathBuf>,
}
#[derive(Clone, PartialEq, Hash, Debug)]
pub struct GetMidiDevNameResult {
pub is_present: bool,
pub name: Option<CString>,
}
#[derive(Clone, PartialEq, Hash, Debug)]
pub struct TrackFxGetPresetResult {
pub state_matches_preset: bool,
pub name: Option<CString>,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct TrackFxGetPresetIndexResult {
pub index: u32,
pub count: u32,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct VolumeAndPan {
pub volume: ReaperVolumeValue,
pub pan: ReaperPanValue,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum GetLastTouchedFxResult {
TrackFx {
track_ref: TrackRef,
fx_location: TrackFxLocation,
param_index: u32,
},
TakeFx {
track_index: u32,
item_index: u32,
take_index: u32,
fx_index: u32,
param_index: u32,
},
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum GetFocusedFxResult {
TrackFx {
track_ref: TrackRef,
fx_location: TrackFxLocation,
},
TakeFx {
track_index: u32,
item_index: u32,
take_index: u32,
fx_index: u32,
},
}
fn make_some_if_greater_than_zero(value: f64) -> Option<f64> {
if value <= 0.0 || value.is_nan() {
return None;
}
Some(value)
}
unsafe fn deref<T: Copy>(ptr: *const T) -> Option<T> {
if ptr.is_null() {
return None;
}
Some(*ptr)
}
unsafe fn deref_as<T: Copy>(ptr: *mut c_void) -> Option<T> {
deref(ptr as *const T)
}
unsafe fn create_passing_c_str<'a>(ptr: *const c_char) -> Option<&'a CStr> {
if ptr.is_null() {
return None;
}
Some(CStr::from_ptr(ptr))
}
fn convert_tracknumber_to_track_ref(tracknumber: u32) -> TrackRef {
if tracknumber == 0 {
TrackRef::MasterTrack
} else {
TrackRef::NormalTrack(tracknumber - 1)
}
}
fn with_string_buffer<T>(
max_size: u32,
fill_buffer: impl FnOnce(*mut c_char, i32) -> T,
) -> (CString, T) {
let vec: Vec<u8> = vec![1; max_size as usize];
let c_string = unsafe { CString::from_vec_unchecked(vec) };
let raw = c_string.into_raw();
let result = fill_buffer(raw, max_size as i32);
let string = unsafe { CString::from_raw(raw) };
(string, result)
}
const ZERO_GUID: GUID = GUID {
Data1: 0,
Data2: 0,
Data3: 0,
Data4: [0; 8],
};
mod private {
use crate::{MainThreadScope, RealTimeAudioThreadScope};
pub trait Sealed {}
impl Sealed for MainThreadScope {}
impl Sealed for RealTimeAudioThreadScope {}
}