diff --git a/Cargo.lock b/Cargo.lock index 92532577d..6d8790267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4184,6 +4184,7 @@ dependencies = [ "rmp-serde", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/base/src/tracing_util.rs b/base/src/tracing_util.rs index 683c76ad3..5ea9ee933 100644 --- a/base/src/tracing_util.rs +++ b/base/src/tracing_util.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; #[macro_export] macro_rules! tracing_debug { ($($tts:tt)*) => { @@ -33,11 +34,11 @@ macro_rules! tracing_error { } } -pub fn ok_or_log_as_warn(result: Result) -> Option { +pub fn ok_or_log_as_warn(result: Result) -> Option { match result { Ok(v) => Some(v), Err(e) => { - tracing::warn!(e); + tracing::warn!("{e}"); None } } diff --git a/main/src/domain/backbone_state.rs b/main/src/domain/backbone_state.rs index 468e27d9b..e62f2b5bb 100644 --- a/main/src/domain/backbone_state.rs +++ b/main/src/domain/backbone_state.rs @@ -11,6 +11,7 @@ use crate::domain::{ use enum_iterator::IntoEnumIterator; use pot::{PotFavorites, PotFilterExcludes}; +use anyhow::{anyhow, Context}; use once_cell::sync::Lazy; use realearn_api::persistence::TargetTouchCause; use reaper_high::{Fx, Reaper}; @@ -394,12 +395,12 @@ impl BackboneState { &self, instance_state: &SharedInstanceState, f: impl FnOnce(&playtime_clip_engine::base::Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { use crate::domain::ClipMatrixRef::*; let other_instance_id = match instance_state .borrow() .clip_matrix_ref() - .ok_or(NO_CLIP_MATRIX_SET)? + .context(NO_CLIP_MATRIX_SET)? { Own(m) => return Ok(f(m)), Foreign(instance_id) => *instance_id, @@ -412,22 +413,22 @@ impl BackboneState { &self, foreign_instance_id: &InstanceId, f: impl FnOnce(&playtime_clip_engine::base::Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { use crate::domain::ClipMatrixRef::*; let other_instance_state = self .instance_states .borrow() .get(foreign_instance_id) - .ok_or(REFERENCED_INSTANCE_NOT_AVAILABLE)? + .context(REFERENCED_INSTANCE_NOT_AVAILABLE)? .upgrade() - .ok_or(REFERENCED_INSTANCE_NOT_AVAILABLE)?; + .context(REFERENCED_INSTANCE_NOT_AVAILABLE)?; let other_instance_state = other_instance_state.borrow(); match other_instance_state .clip_matrix_ref() - .ok_or(REFERENCED_CLIP_MATRIX_NOT_AVAILABLE)? + .context(REFERENCED_CLIP_MATRIX_NOT_AVAILABLE)? { Own(m) => Ok(f(m)), - Foreign(_) => Err(NESTED_CLIP_BORROW_NOT_SUPPORTED), + Foreign(_) => Err(anyhow!(NESTED_CLIP_BORROW_NOT_SUPPORTED)), } } @@ -438,12 +439,12 @@ impl BackboneState { &self, instance_state: &SharedInstanceState, f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { use crate::domain::ClipMatrixRef::*; let other_instance_id = match instance_state .borrow_mut() .clip_matrix_ref_mut() - .ok_or(NO_CLIP_MATRIX_SET)? + .context(NO_CLIP_MATRIX_SET)? { Own(m) => return Ok(f(m)), Foreign(instance_id) => *instance_id, @@ -456,22 +457,22 @@ impl BackboneState { &self, instance_id: &InstanceId, f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { use crate::domain::ClipMatrixRef::*; let other_instance_state = self .instance_states .borrow() .get(instance_id) - .ok_or(REFERENCED_INSTANCE_NOT_AVAILABLE)? + .context(REFERENCED_INSTANCE_NOT_AVAILABLE)? .upgrade() - .ok_or(REFERENCED_INSTANCE_NOT_AVAILABLE)?; + .context(REFERENCED_INSTANCE_NOT_AVAILABLE)?; let mut other_instance_state = other_instance_state.borrow_mut(); match other_instance_state .clip_matrix_ref_mut() - .ok_or(REFERENCED_CLIP_MATRIX_NOT_AVAILABLE)? + .context(REFERENCED_CLIP_MATRIX_NOT_AVAILABLE)? { Own(m) => Ok(f(m)), - Foreign(_) => Err(NESTED_CLIP_BORROW_NOT_SUPPORTED), + Foreign(_) => Err(anyhow!(NESTED_CLIP_BORROW_NOT_SUPPORTED)), } } diff --git a/main/src/domain/targets/clip_column_target.rs b/main/src/domain/targets/clip_column_target.rs index c2d5afbb6..b40189b0c 100644 --- a/main/src/domain/targets/clip_column_target.rs +++ b/main/src/domain/targets/clip_column_target.rs @@ -54,20 +54,23 @@ impl RealearnTarget for ClipColumnTarget { value: ControlValue, context: MappingControlContext, ) -> Result { - let response = BackboneState::get().with_clip_matrix( - context.control_context.instance_state, - |matrix| -> Result { - match self.action { - ClipColumnAction::Stop => { - if !value.is_on() { - return Ok(HitResponse::ignored()); + let response = BackboneState::get() + .with_clip_matrix( + context.control_context.instance_state, + |matrix| -> anyhow::Result { + match self.action { + ClipColumnAction::Stop => { + if !value.is_on() { + return Ok(HitResponse::ignored()); + } + matrix.stop_column(self.column_index, None)?; } - matrix.stop_column(self.column_index, None)?; } - } - Ok(HitResponse::processed_with_effect()) - }, - )??; + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out column action")?; Ok(response) } diff --git a/main/src/domain/targets/clip_management_target.rs b/main/src/domain/targets/clip_management_target.rs index 91f7eec42..8dd9196ab 100644 --- a/main/src/domain/targets/clip_management_target.rs +++ b/main/src/domain/targets/clip_management_target.rs @@ -39,35 +39,11 @@ pub struct ClipManagementTarget { } impl ClipManagementTarget { - fn with_matrix( - &self, - context: MappingControlContext, - f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, - ) -> Result { - BackboneState::get().with_clip_matrix_mut(context.control_context.instance_state, f) - } -} - -impl RealearnTarget for ClipManagementTarget { - fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { - use ClipManagementAction as A; - match self.action { - A::ClearSlot - | A::FillSlotWithSelectedItem - | A::CopyOrPasteClip - | A::AdjustClipSectionLength(_) => ( - ControlType::AbsoluteContinuousRetriggerable, - TargetCharacter::Trigger, - ), - A::EditClip => (ControlType::AbsoluteContinuous, TargetCharacter::Switch), - } - } - - fn hit( + fn hit_internal( &mut self, value: ControlValue, context: MappingControlContext, - ) -> Result { + ) -> anyhow::Result { use ClipManagementAction as A; match &self.action { A::ClearSlot => { @@ -122,6 +98,39 @@ impl RealearnTarget for ClipManagementTarget { } } + fn with_matrix( + &self, + context: MappingControlContext, + f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + BackboneState::get().with_clip_matrix_mut(context.control_context.instance_state, f) + } +} + +impl RealearnTarget for ClipManagementTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + use ClipManagementAction as A; + match self.action { + A::ClearSlot + | A::FillSlotWithSelectedItem + | A::CopyOrPasteClip + | A::AdjustClipSectionLength(_) => ( + ControlType::AbsoluteContinuousRetriggerable, + TargetCharacter::Trigger, + ), + A::EditClip => (ControlType::AbsoluteContinuous, TargetCharacter::Switch), + } + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + self.hit_internal(value, context) + .map_err(|_| "couldn't carry out clip management action") + } + fn reaper_target_type(&self) -> Option { Some(ReaperTargetType::ClipManagement) } diff --git a/main/src/domain/targets/clip_matrix_target.rs b/main/src/domain/targets/clip_matrix_target.rs index a05fa3c43..ece01765f 100644 --- a/main/src/domain/targets/clip_matrix_target.rs +++ b/main/src/domain/targets/clip_matrix_target.rs @@ -48,44 +48,47 @@ impl RealearnTarget for ClipMatrixTarget { value: ControlValue, context: MappingControlContext, ) -> Result { - BackboneState::get().with_clip_matrix_mut( - context.control_context.instance_state, - |matrix| { - if !value.is_on() { - return Ok(HitResponse::ignored()); - } - match self.action { - ClipMatrixAction::Stop => { - matrix.stop(); - } - ClipMatrixAction::Undo => { - let _ = matrix.undo(); - } - ClipMatrixAction::Redo => { - let _ = matrix.redo(); - } - ClipMatrixAction::BuildScene => { - matrix.build_scene_in_first_empty_row()?; - } - ClipMatrixAction::SetRecordDurationToOpenEnd => { - matrix.set_record_duration(RecordLength::OpenEnd); - } - ClipMatrixAction::SetRecordDurationToOneBar => { - matrix.set_record_duration(record_duration_in_bars(1)); + BackboneState::get() + .with_clip_matrix_mut( + context.control_context.instance_state, + |matrix| -> anyhow::Result { + if !value.is_on() { + return Ok(HitResponse::ignored()); } - ClipMatrixAction::SetRecordDurationToTwoBars => { - matrix.set_record_duration(record_duration_in_bars(2)); + match self.action { + ClipMatrixAction::Stop => { + matrix.stop(); + } + ClipMatrixAction::Undo => { + let _ = matrix.undo(); + } + ClipMatrixAction::Redo => { + let _ = matrix.redo(); + } + ClipMatrixAction::BuildScene => { + matrix.build_scene_in_first_empty_row()?; + } + ClipMatrixAction::SetRecordDurationToOpenEnd => { + matrix.set_record_duration(RecordLength::OpenEnd); + } + ClipMatrixAction::SetRecordDurationToOneBar => { + matrix.set_record_duration(record_duration_in_bars(1)); + } + ClipMatrixAction::SetRecordDurationToTwoBars => { + matrix.set_record_duration(record_duration_in_bars(2)); + } + ClipMatrixAction::SetRecordDurationToFourBars => { + matrix.set_record_duration(record_duration_in_bars(4)); + } + ClipMatrixAction::SetRecordDurationToEightBars => { + matrix.set_record_duration(record_duration_in_bars(8)); + } } - ClipMatrixAction::SetRecordDurationToFourBars => { - matrix.set_record_duration(record_duration_in_bars(4)); - } - ClipMatrixAction::SetRecordDurationToEightBars => { - matrix.set_record_duration(record_duration_in_bars(8)); - } - } - Ok(HitResponse::processed_with_effect()) - }, - )? + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out matrix action") } fn process_change_event( diff --git a/main/src/domain/targets/clip_row_target.rs b/main/src/domain/targets/clip_row_target.rs index 022b53e41..6eb277d53 100644 --- a/main/src/domain/targets/clip_row_target.rs +++ b/main/src/domain/targets/clip_row_target.rs @@ -39,31 +39,11 @@ pub struct ClipRowTarget { } impl ClipRowTarget { - fn with_matrix( - &self, - context: ControlContext, - f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, - ) -> Result { - BackboneState::get().with_clip_matrix_mut(context.instance_state, f) - } -} - -#[derive(Clone, Debug, PartialEq)] -struct ClipRowTargetBasics { - pub row_index: usize, - pub action: ClipRowAction, -} - -impl RealearnTarget for ClipRowTarget { - fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { - control_type_and_character(self.basics.action) - } - - fn hit( + fn hit_internal( &mut self, value: ControlValue, context: MappingControlContext, - ) -> Result { + ) -> anyhow::Result { match self.basics.action { ClipRowAction::PlayScene => { if !value.is_on() { @@ -108,6 +88,35 @@ impl RealearnTarget for ClipRowTarget { } } + fn with_matrix( + &self, + context: ControlContext, + f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, + ) -> anyhow::Result { + BackboneState::get().with_clip_matrix_mut(context.instance_state, f) + } +} + +#[derive(Clone, Debug, PartialEq)] +struct ClipRowTargetBasics { + pub row_index: usize, + pub action: ClipRowAction, +} + +impl RealearnTarget for ClipRowTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + control_type_and_character(self.basics.action) + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + self.hit_internal(value, context) + .map_err(|_| "couldn't carry out row action") + } + fn reaper_target_type(&self) -> Option { Some(ReaperTargetType::ClipRow) } diff --git a/main/src/domain/targets/clip_seek_target.rs b/main/src/domain/targets/clip_seek_target.rs index 5f4a03ea3..12dd0fa80 100644 --- a/main/src/domain/targets/clip_seek_target.rs +++ b/main/src/domain/targets/clip_seek_target.rs @@ -68,10 +68,16 @@ impl RealearnTarget for ClipSeekTarget { context: MappingControlContext, ) -> Result { let value = value.to_unit_value()?; - BackboneState::get().with_clip_matrix(context.control_context.instance_state, |matrix| { - matrix.seek_slot(self.slot_coordinates, value)?; - Ok(HitResponse::processed_with_effect()) - })? + BackboneState::get() + .with_clip_matrix( + context.control_context.instance_state, + |matrix| -> anyhow::Result { + matrix.seek_slot(self.slot_coordinates, value)?; + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out seek action") } fn is_available(&self, _: ControlContext) -> bool { diff --git a/main/src/domain/targets/clip_transport_target.rs b/main/src/domain/targets/clip_transport_target.rs index 11c206885..1efeb24eb 100644 --- a/main/src/domain/targets/clip_transport_target.rs +++ b/main/src/domain/targets/clip_transport_target.rs @@ -5,6 +5,7 @@ use crate::domain::{ RealTimeControlContext, RealTimeReaperTarget, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetTypeDef, UnresolvedReaperTargetDef, VirtualClipSlot, DEFAULT_TARGET, }; +use anyhow::bail; use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, PropValue, Target, UnitValue}; use playtime_api::persistence::{ClipPlayStartTiming, ClipPlayStopTiming}; use playtime_clip_engine::base::{ClipMatrixEvent, ClipSlotAddress}; @@ -70,40 +71,12 @@ impl ClipTransportTarget { let play_state = matrix.find_slot(self.basics.slot_coordinates)?.play_state(); Some(play_state) } -} - -#[derive(Clone, Debug, PartialEq)] -struct ClipTransportTargetBasics { - pub slot_coordinates: ClipSlotAddress, - pub action: ClipTransportAction, - pub options: ClipTransportOptions, -} - -impl ClipTransportTargetBasics { - fn play_options(&self) -> ColumnPlaySlotOptions { - ColumnPlaySlotOptions { - stop_column_if_slot_empty: self.options.stop_column_if_slot_empty, - start_timing: self.options.play_start_timing, - } - } -} -const NOT_RECORDING_BECAUSE_NOT_ARMED: &str = "not recording because not armed"; - -impl RealearnTarget for ClipTransportTarget { - fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { - control_type_and_character(self.basics.action) - } - - fn format_value(&self, value: UnitValue, _: ControlContext) -> String { - format_value_as_on_off(value).to_string() - } - - fn hit( + fn hit_internal( &mut self, value: ControlValue, context: MappingControlContext, - ) -> Result { + ) -> anyhow::Result { use ClipTransportAction::*; let on = value.is_on(); BackboneState::get().with_clip_matrix_mut( @@ -161,7 +134,7 @@ impl RealearnTarget for ClipTransportTarget { self.basics.slot_coordinates.column(), ) { - return Err(NOT_RECORDING_BECAUSE_NOT_ARMED); + bail!(NOT_RECORDING_BECAUSE_NOT_ARMED); } matrix.record_or_overdub_slot(self.basics.slot_coordinates)?; } else { @@ -193,7 +166,7 @@ impl RealearnTarget for ClipTransportTarget { None, )?; } else { - return Err(NOT_RECORDING_BECAUSE_NOT_ARMED); + bail!(NOT_RECORDING_BECAUSE_NOT_ARMED); } } else { // Definitely record. @@ -227,6 +200,43 @@ impl RealearnTarget for ClipTransportTarget { }, )? } +} + +#[derive(Clone, Debug, PartialEq)] +struct ClipTransportTargetBasics { + pub slot_coordinates: ClipSlotAddress, + pub action: ClipTransportAction, + pub options: ClipTransportOptions, +} + +impl ClipTransportTargetBasics { + fn play_options(&self) -> ColumnPlaySlotOptions { + ColumnPlaySlotOptions { + stop_column_if_slot_empty: self.options.stop_column_if_slot_empty, + start_timing: self.options.play_start_timing, + } + } +} + +const NOT_RECORDING_BECAUSE_NOT_ARMED: &str = "not recording because not armed"; + +impl RealearnTarget for ClipTransportTarget { + fn control_type_and_character(&self, _: ControlContext) -> (ControlType, TargetCharacter) { + control_type_and_character(self.basics.action) + } + + fn format_value(&self, value: UnitValue, _: ControlContext) -> String { + format_value_as_on_off(value).to_string() + } + + fn hit( + &mut self, + value: ControlValue, + context: MappingControlContext, + ) -> Result { + self.hit_internal(value, context) + .map_err(|_| "error while hitting clip transport target") + } fn is_available(&self, _: ControlContext) -> bool { // TODO-medium With clip targets we should check the control context (instance state) if diff --git a/main/src/domain/targets/clip_volume_target.rs b/main/src/domain/targets/clip_volume_target.rs index 1ed1135f9..626099261 100644 --- a/main/src/domain/targets/clip_volume_target.rs +++ b/main/src/domain/targets/clip_volume_target.rs @@ -72,13 +72,16 @@ impl RealearnTarget for ClipVolumeTarget { .unwrap_or_default(); let db = volume.db(); let api_db = playtime_api::persistence::Db::new(db.get())?; - BackboneState::get().with_clip_matrix_mut( - context.control_context.instance_state, - |matrix| { - matrix.set_slot_volume(self.slot_coordinates, api_db)?; - Ok(HitResponse::processed_with_effect()) - }, - )? + BackboneState::get() + .with_clip_matrix_mut( + context.control_context.instance_state, + |matrix| -> anyhow::Result { + matrix.set_slot_volume(self.slot_coordinates, api_db)?; + Ok(HitResponse::processed_with_effect()) + }, + ) + .map_err(|_| "couldn't acquire matrix")? + .map_err(|_| "couldn't carry out volume action") } fn is_available(&self, _: ControlContext) -> bool { diff --git a/main/src/infrastructure/plugin/app.rs b/main/src/infrastructure/plugin/app.rs index 650913c6d..e0dacb988 100644 --- a/main/src/infrastructure/plugin/app.rs +++ b/main/src/infrastructure/plugin/app.rs @@ -38,7 +38,7 @@ use crate::infrastructure::plugin::debug_util::resolve_symbols_from_clipboard; use crate::infrastructure::plugin::tracing_util::TracingHook; use crate::infrastructure::server::services::RealearnServices; use crate::infrastructure::test::run_test; -use anyhow::bail; +use anyhow::{bail, Context}; use base::metrics_util::MetricsHook; use helgoboss_allocator::{start_async_deallocation_thread, AsyncDeallocatorCommandReceiver}; use once_cell::sync::Lazy; @@ -1004,10 +1004,10 @@ impl App { &self, clip_matrix_id: &str, f: impl FnOnce(&playtime_clip_engine::base::Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { let session = self .find_session_by_id(clip_matrix_id) - .ok_or("session not found")?; + .context("session not found")?; let session = session.borrow(); let instance_state = session.instance_state(); BackboneState::get().with_clip_matrix(instance_state, f) @@ -1018,20 +1018,20 @@ impl App { &self, clip_matrix_id: &str, f: impl FnOnce(&mut playtime_clip_engine::base::Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { let session = self .find_session_by_id(clip_matrix_id) - .ok_or("session not found")?; + .context("session not found")?; let session = session.borrow(); let instance_state = session.instance_state(); BackboneState::get().with_clip_matrix_mut(instance_state, f) } #[cfg(feature = "playtime")] - pub fn create_clip_matrix(&self, clip_matrix_id: &str) -> Result<(), &'static str> { + pub fn create_clip_matrix(&self, clip_matrix_id: &str) -> anyhow::Result<()> { let session = self .find_session_by_id(clip_matrix_id) - .ok_or("session not found")?; + .context("session not found")?; let session = session.borrow(); let instance_state = session.instance_state(); BackboneState::get() diff --git a/main/src/infrastructure/server/services/playtime_service.rs b/main/src/infrastructure/server/services/playtime_service.rs index 754847d17..e471ee316 100644 --- a/main/src/infrastructure/server/services/playtime_service.rs +++ b/main/src/infrastructure/server/services/playtime_service.rs @@ -17,7 +17,7 @@ impl MatrixProvider for AppMatrixProvider { &self, clip_matrix_id: &str, f: impl FnOnce(&Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { App::get().with_clip_matrix(clip_matrix_id, f) } @@ -25,11 +25,11 @@ impl MatrixProvider for AppMatrixProvider { &self, clip_matrix_id: &str, f: impl FnOnce(&mut Matrix) -> R, - ) -> Result { + ) -> anyhow::Result { App::get().with_clip_matrix_mut(clip_matrix_id, f) } - fn create_matrix(&self, clip_matrix_id: &str) -> Result<(), &'static str> { + fn create_matrix(&self, clip_matrix_id: &str) -> anyhow::Result<()> { App::get().create_clip_matrix(clip_matrix_id) } } diff --git a/playtime-api/Cargo.toml b/playtime-api/Cargo.toml index 928badd32..6910b7a13 100644 --- a/playtime-api/Cargo.toml +++ b/playtime-api/Cargo.toml @@ -18,4 +18,6 @@ derive_more.workspace = true # For encoding/decoding a signed matrix value rmp-serde.workspace = true # For encoding/decoding a signed matrix value -base64 = "0.21.2" \ No newline at end of file +base64 = "0.21.2" +# For proper error types +thiserror.workspace = true \ No newline at end of file diff --git a/playtime-api/src/persistence/mod.rs b/playtime-api/src/persistence/mod.rs index 5843c4832..ea2839b98 100644 --- a/playtime-api/src/persistence/mod.rs +++ b/playtime-api/src/persistence/mod.rs @@ -22,6 +22,7 @@ use std::cmp; use std::error::Error; use std::ops::Add; use std::path::PathBuf; +use thiserror::Error; // TODO-medium Add start time detection // TODO-medium Add legato @@ -151,7 +152,7 @@ pub struct TempoRange { impl TempoRange { pub fn new(min: Bpm, max: Bpm) -> PlaytimeApiResult { if min > max { - return Err("min must be <= max"); + return Err("min must be <= max".into()); } Ok(Self { min, max }) } @@ -590,13 +591,13 @@ impl EvenQuantization { pub fn new(numerator: u32, denominator: u32) -> PlaytimeApiResult { if numerator == 0 { - return Err("numerator must be > 0"); + return Err("numerator must be > 0".into()); } if denominator == 0 { - return Err("denominator must be > 0"); + return Err("denominator must be > 0".into()); } if numerator > 1 && denominator > 1 { - return Err("if numerator > 1, denominator must be 1"); + return Err("if numerator > 1, denominator must be 1".into()); } let q = Self { numerator, @@ -1368,7 +1369,7 @@ impl Bpm { pub fn new(value: f64) -> PlaytimeApiResult { if value <= 0.0 { - return Err("BPM value must be > 0.0"); + return Err("BPM value must be > 0.0".into()); } Ok(Self(value)) } @@ -1385,7 +1386,7 @@ pub struct PositiveSecond(f64); impl PositiveSecond { pub fn new(value: f64) -> PlaytimeApiResult { if value < 0.0 { - return Err("second value must be positive"); + return Err("second value must be positive".into()); } Ok(Self(value)) } @@ -1400,7 +1401,7 @@ impl PositiveSecond { } impl TryFrom for PositiveSecond { - type Error = &'static str; + type Error = PlaytimeApiError; fn try_from(value: f64) -> Result { Self::new(value) @@ -1421,7 +1422,7 @@ pub struct PositiveBeat(f64); impl PositiveBeat { pub fn new(value: f64) -> PlaytimeApiResult { if value < 0.0 { - return Err("beat value must be positive"); + return Err("beat value must be positive".into()); } Ok(Self(value)) } @@ -1439,7 +1440,7 @@ impl Db { pub fn new(value: f64) -> PlaytimeApiResult { if value.is_nan() { - return Err("dB value must not be NaN"); + return Err("dB value must not be NaN".into()); } Ok(Self(value)) } @@ -1452,4 +1453,24 @@ impl Db { #[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize, JsonSchema)] pub struct RgbColor(pub u8, pub u8, pub u8); -type PlaytimeApiResult = Result; +type PlaytimeApiResult = Result; + +/// Important: Since some of these types are going to be used in real-time contexts, we don't +/// want heap-allocated types in here! +#[derive(Error, Debug)] +#[error("{msg}")] +pub struct PlaytimeApiError { + msg: &'static str, +} + +impl From<&'static str> for PlaytimeApiError { + fn from(msg: &'static str) -> Self { + Self { msg } + } +} + +impl From for &'static str { + fn from(value: PlaytimeApiError) -> Self { + value.msg + } +} diff --git a/playtime-clip-engine b/playtime-clip-engine index dcd9e296c..bdc5df40c 160000 --- a/playtime-clip-engine +++ b/playtime-clip-engine @@ -1 +1 @@ -Subproject commit dcd9e296c642e25374f19b78b9374c24cd3f7226 +Subproject commit bdc5df40c829b07dda039bf4ca94388fd0fe7003