Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
SanderVocke committed Nov 26, 2024
1 parent 5d7b671 commit f3a5d0a
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 71 deletions.
10 changes: 5 additions & 5 deletions src/backend/internal/BasicLoop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ BasicLoop::BasicLoop() :
ma_triggering_now(false),
ma_length(0),
ma_position(0),
ma_maybe_next_planned_mode(LOOP_MODE_INVALID),
ma_maybe_next_planned_mode(LoopMode_Unknown),
ma_maybe_next_planned_delay(-1),
ma_already_triggered(false)
{
Expand Down Expand Up @@ -211,7 +211,7 @@ shoop_shared_ptr<LoopInterface> BasicLoop::get_sync_source(bool thread_safe) {
void BasicLoop::PROC_update_planned_transition_cache() {

ma_maybe_next_planned_mode = mp_planned_states.size() > 0 ?
(shoop_loop_mode_t) mp_planned_states.front() : LOOP_MODE_INVALID;
(shoop_loop_mode_t) mp_planned_states.front() : LoopMode_Unknown;
ma_maybe_next_planned_delay = mp_planned_state_countdowns.size() > 0 ?
mp_planned_state_countdowns.front() : -1;
}
Expand Down Expand Up @@ -261,7 +261,7 @@ void BasicLoop::PROC_handle_transition(shoop_loop_mode_t new_state) {
set_length(0, false);
}
ma_mode = new_state;
if(ma_mode > LOOP_MODE_INVALID) {
if(ma_mode >= LOOP_MODE_INVALID) {
throw_error<std::runtime_error>("invalid mode");
}
if (ma_mode == LoopMode_Stopped) { ma_position = 0; }
Expand Down Expand Up @@ -415,11 +415,11 @@ shoop_loop_mode_t BasicLoop::get_mode() const {
void BasicLoop::get_first_planned_transition(shoop_loop_mode_t &maybe_mode_out, uint32_t &delay_out) {
shoop_loop_mode_t maybe_mode = ma_maybe_next_planned_mode;
int maybe_delay = ma_maybe_next_planned_delay;
if (maybe_delay >= 0 && maybe_mode != LOOP_MODE_INVALID) {
if (maybe_delay >= 0 && maybe_mode != LoopMode_Unknown) {
maybe_mode_out = maybe_mode;
delay_out = maybe_delay;
} else {
maybe_mode_out = LOOP_MODE_INVALID;
maybe_mode_out = LoopMode_Unknown;
delay_out = 0;
}
}
Expand Down
41 changes: 5 additions & 36 deletions src/python/shoopdaloop/lib/backend_wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,27 +159,6 @@ def __init__(self, backend_state : 'bindings.loop_midi_channel_state_t' = None):
self.n_preplay_samples = 0

@dataclass
class LoopState:
length: int
position: int
mode: Type[LoopMode]
maybe_next_mode: typing.Union[Type[LoopMode], None]
maybe_next_delay: typing.Union[int, None]

def __init__(self, backend_loop_state : 'bindings.loop_state_t' = None):
if backend_loop_state:
self.length = to_int(backend_loop_state.length)
self.position = to_int(backend_loop_state.position)
self.mode = backend_loop_state.mode
self.maybe_next_mode = (None if backend_loop_state.maybe_next_mode == bindings.LOOP_MODE_INVALID else backend_loop_state.maybe_next_mode)
self.maybe_next_delay = (None if backend_loop_state.maybe_next_mode == bindings.LOOP_MODE_INVALID else backend_loop_state.maybe_next_mode_delay)
else:
self.length = 0
self.position = 0
self.mode = LoopMode.Unknown
self.maybe_next_mode = None
self.maybe_next_delay = None
@dataclass
class AudioPortState:
input_peak: float
output_peak: float
Expand Down Expand Up @@ -617,26 +596,16 @@ def transition_multiple(loops, to_state : Type['LoopMode'],
maybe_to_sync_at_cycle : int):
if len(loops) == 0:
return
HandleType = POINTER(bindings.shoopdaloop_loop_t)
handles = (HandleType * len(loops))()
for idx,l in enumerate(loops):
handles[idx] = l.get_backend_obj()
bindings.loops_transition(len(loops),
handles,
loop_objs = [l._obj for l in loops]
shoop_py_backend.transition_multiple_loops(
loop_objs,
to_state.value,
maybe_cycles_delay,
maybe_to_sync_at_cycle)
del handles
del loop_objs

def get_state(self):
state = self._obj.get_state()
return LoopState(
length=state[1],
position=state[2],
mode=LoopMode(state[0]),
maybe_next_mode=LoopMode(state[3]) if state[3] is not None else None,
maybe_next_delay=state[4]
)
return self._obj.get_state()

def set_length(self, length):
self._obj.set_length(length)
Expand Down
6 changes: 3 additions & 3 deletions src/python/shoopdaloop/lib/q_objects/Loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ def updateOnOtherThread(self):
prev_display_midi_events_triggered = self._display_midi_events_triggered

state = self._backend_loop.get_state()
self._mode = state.mode
self._mode = int(state.mode)
self._length = state.length
self._position = state.position
self._next_mode = (state.maybe_next_mode if state.maybe_next_mode != None else state.mode)
self._next_transition_delay = (state.maybe_next_delay if state.maybe_next_delay != None else -1)
self._next_mode = (int(state.maybe_next_mode) if state.maybe_next_mode != None else int(state.mode))
self._next_transition_delay = (state.maybe_next_mode_delay if state.maybe_next_mode_delay != None else -1)
self._display_peaks = [c._output_peak for c in [a for a in audio_chans if a._mode in [ChannelMode.Direct.value, ChannelMode.Wet.value]]]
self._display_midi_notes_active = (sum([c._n_notes_active for c in midi_chans]) if len(midi_chans) > 0 else 0)
self._display_midi_events_triggered = (sum([c._n_events_triggered for c in midi_chans]) if len(midi_chans) > 0 else 0)
Expand Down
54 changes: 27 additions & 27 deletions src/rust/backend_bindings/src/shoop_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub struct LoopState {

impl LoopState {
pub fn new(obj : &ffi::shoop_loop_state_info_t) -> Self {
let has_next_mode = obj.maybe_next_mode == ffi::shoop_loop_mode_t_LOOP_MODE_INVALID;
let has_next_mode = obj.maybe_next_mode != ffi::shoop_loop_mode_t_LoopMode_Unknown;
return LoopState {
mode : LoopMode::try_from(obj.mode).unwrap(),
length : obj.length,
Expand Down Expand Up @@ -101,32 +101,6 @@ impl Loop {
Ok(())
}

pub fn transition_multiple(
loops: &[Loop],
to_state: LoopMode,
maybe_cycles_delay: i32,
maybe_to_sync_at_cycle: i32,
) -> Result<(), anyhow::Error> {
if loops.is_empty() {
return Ok(());
}
let handles: Vec<*mut ffi::shoopdaloop_loop_t> = loops
.iter()
.map(|l| unsafe { l.unsafe_backend_ptr() })
.collect();
let handles_ptr: *mut *mut ffi::shoopdaloop_loop_t = handles.as_ptr() as *mut *mut ffi::shoopdaloop_loop_t;
unsafe {
ffi::loops_transition(
handles.len() as u32,
handles_ptr,
to_state as u32,
maybe_cycles_delay,
maybe_to_sync_at_cycle,
)
};
Ok(())
}

pub fn get_state(&self) -> Result<LoopState, anyhow::Error> {
let guard = self.obj.lock().unwrap();
let obj = *guard;
Expand Down Expand Up @@ -215,6 +189,32 @@ impl Loop {
}
}

pub fn transition_multiple_loops(
loops: &[&Loop],
to_state: LoopMode,
maybe_cycles_delay: i32,
maybe_to_sync_at_cycle: i32,
) -> Result<(), anyhow::Error> {
if loops.is_empty() {
return Ok(());
}
let handles: Vec<*mut ffi::shoopdaloop_loop_t> = loops
.iter()
.map(|l| unsafe { l.unsafe_backend_ptr() })
.collect();
let handles_ptr: *mut *mut ffi::shoopdaloop_loop_t = handles.as_ptr() as *mut *mut ffi::shoopdaloop_loop_t;
unsafe {
ffi::loops_transition(
handles.len() as u32,
handles_ptr,
to_state as u32,
maybe_cycles_delay,
maybe_to_sync_at_cycle,
)
};
Ok(())
}

impl Drop for Loop {
fn drop(&mut self) {
let guard = self.obj.lock().unwrap();
Expand Down
110 changes: 110 additions & 0 deletions src/rust/shoopdaloop/src/shoop_py_backend/shoop_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,62 @@ use crate::shoop_py_backend::backend_session::BackendSession;
use crate::shoop_py_backend::audio_channel::AudioChannel;
use crate::shoop_py_backend::midi_channel::MidiChannel;

#[pyclass(eq, eq_int)]
#[derive(PartialEq, Clone)]
pub enum LoopMode {
Unknown = backend_bindings::LoopMode::Unknown as isize,
Stopped = backend_bindings::LoopMode::Stopped as isize,
Playing = backend_bindings::LoopMode::Playing as isize,
Recording = backend_bindings::LoopMode::Recording as isize,
Replacing = backend_bindings::LoopMode::Replacing as isize,
PlayingDryThroughWet = backend_bindings::LoopMode::PlayingDryThroughWet as isize,
RecordingDryIntoWet = backend_bindings::LoopMode::RecordingDryIntoWet as isize,
}

impl TryFrom<backend_bindings::LoopMode> for LoopMode {
type Error = anyhow::Error;
fn try_from(value: backend_bindings::LoopMode) -> Result<Self, anyhow::Error> {
match value {
backend_bindings::LoopMode::Unknown => Ok(LoopMode::Unknown),
backend_bindings::LoopMode::Stopped => Ok(LoopMode::Stopped),
backend_bindings::LoopMode::Playing => Ok(LoopMode::Playing),
backend_bindings::LoopMode::Recording => Ok(LoopMode::Recording),
backend_bindings::LoopMode::Replacing => Ok(LoopMode::Replacing),
backend_bindings::LoopMode::PlayingDryThroughWet => Ok(LoopMode::PlayingDryThroughWet),
backend_bindings::LoopMode::RecordingDryIntoWet => Ok(LoopMode::RecordingDryIntoWet),
}
}
}

#[pyclass]
pub struct LoopState {
#[pyo3(get)]
pub mode : LoopMode,
#[pyo3(get)]
pub length : u32,
#[pyo3(get)]
pub position : u32,
#[pyo3(get)]
pub maybe_next_mode : Option<LoopMode>,
#[pyo3(get)]
pub maybe_next_mode_delay : Option<u32>,
}

impl LoopState {
pub fn new(obj : backend_bindings::LoopState) -> Self {
return LoopState {
mode : LoopMode::try_from(obj.mode).unwrap(),
length : obj.length,
position : obj.position,
maybe_next_mode : match obj.maybe_next_mode {
Some(v) => Some (LoopMode::try_from(v).unwrap()),
None => None
},
maybe_next_mode_delay : obj.maybe_next_mode_delay,
};
}
}

#[pyclass]
pub struct Loop {
pub obj : backend_bindings::Loop,
Expand Down Expand Up @@ -41,9 +97,63 @@ impl Loop {
fn unsafe_backend_ptr (&self) -> usize {
unsafe { self.obj.unsafe_backend_ptr() as usize }
}

#[pyo3(signature = (to_mode, maybe_cycles_delay=None, maybe_to_sync_at_cycle=None))]
fn transition(&self, to_mode: i32, maybe_cycles_delay: Option<i32>, maybe_to_sync_at_cycle: Option<i32>) -> PyResult<()> {
let to_mode = backend_bindings::LoopMode::try_from(to_mode)
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid loop mode"))?;
self.obj.transition(to_mode, maybe_cycles_delay.unwrap_or(-1), maybe_to_sync_at_cycle.unwrap_or(-1))
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Transition failed: {:?}", e)))
}

fn get_state(&self) -> PyResult<LoopState> {
let state = self.obj.get_state()
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Get state failed: {:?}", e)))?;
Ok(LoopState::new(state))
}

fn set_length(&self, length: u32) -> PyResult<()> {
self.obj.set_length(length)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Set length failed: {:?}", e)))
}

fn set_position(&self, position: u32) -> PyResult<()> {
self.obj.set_position(position)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Set position failed: {:?}", e)))
}

fn clear(&self, length: u32) -> PyResult<()> {
self.obj.clear(length)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Clear failed: {:?}", e)))
}

#[pyo3(signature = (loop_ref=None))]
fn set_sync_source(&self, loop_ref: Option<&Loop>) -> PyResult<()> {
self.obj.set_sync_source(loop_ref.map(|l| &l.obj))
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Set sync source failed: {:?}", e)))
}

#[pyo3(signature = (reverse_start_cycle=None, cycles_length=None, go_to_cycle=None, go_to_mode=0))]
fn adopt_ringbuffer_contents(&self, reverse_start_cycle: Option<i32>, cycles_length: Option<i32>, go_to_cycle: Option<i32>, go_to_mode: i32) -> PyResult<()> {
let go_to_mode = backend_bindings::LoopMode::try_from(go_to_mode)
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid loop mode"))?;
self.obj.adopt_ringbuffer_contents(reverse_start_cycle, cycles_length, go_to_cycle, go_to_mode)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Adopt ringbuffer contents failed: {:?}", e)))
}
}
#[pyfunction]
#[pyo3(signature = (loops, to_state=0, maybe_cycles_delay=None, maybe_to_sync_at_cycle=None))]
pub fn transition_multiple_loops(loops: Vec<PyRef<Loop>>, to_state: i32, maybe_cycles_delay: Option<i32>, maybe_to_sync_at_cycle: Option<i32>) -> PyResult<()> {
let to_state = backend_bindings::LoopMode::try_from(to_state)
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid loop mode"))?;
let rust_loops: Vec<_> = loops.iter().map(|l| &l.obj).collect();
backend_bindings::transition_multiple_loops(&rust_loops, to_state, maybe_cycles_delay.unwrap_or(-1), maybe_to_sync_at_cycle.unwrap_or(-1))
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Transition multiple failed: {:?}", e)))
}

pub fn register_in_module<'py>(m: &Bound<'py, PyModule>) -> PyResult<()> {
m.add_class::<Loop>()?;
m.add_class::<LoopMode>()?;
m.add_function(wrap_pyfunction!(transition_multiple_loops, m)?)?;
Ok(())
}

0 comments on commit f3a5d0a

Please sign in to comment.