use crossbeam_channel::Sender; use crate::engine::LinkState; use crate::model::StepContext; use crate::services::pattern_editor; use crate::state::{EditorTarget, FlashKind, Modal, SampleTree}; use super::{App, COMPLETION_CANDIDATES}; impl App { pub(super) fn create_step_context(&self, step_idx: usize, link: &LinkState) -> StepContext<'static> { let (bank, pattern) = self.current_bank_pattern(); let speed = self .project_state .project .pattern_at(bank, pattern) .speed .multiplier(); StepContext { step: step_idx, beat: link.beat(), bank, pattern, tempo: link.tempo(), phase: link.phase(), slot: 0, runs: 0, iter: 0, speed, fill: false, nudge_secs: 0.0, cc_access: None, speed_key: "", chain_key: "", #[cfg(feature = "desktop")] mouse_x: 0.5, #[cfg(feature = "desktop")] mouse_y: 0.5, #[cfg(feature = "desktop")] mouse_down: 0.0, } } pub(super) fn load_step_to_editor(&mut self) { let (bank, pattern) = self.current_bank_pattern(); if let Some(script) = pattern_editor::get_step_script( &self.project_state.project, bank, pattern, self.editor_ctx.step, ) { let lines: Vec = if script.is_empty() { vec![String::new()] } else { script.lines().map(String::from).collect() }; self.editor_ctx.editor.set_content(lines); self.editor_ctx.editor.set_candidates(COMPLETION_CANDIDATES.clone()); self.editor_ctx .editor .set_completion_enabled(self.ui.show_completion); let tree = SampleTree::from_paths(&self.audio.config.sample_paths); self.editor_ctx.editor.set_sample_folders(tree.all_folder_names()); if self.editor_ctx.show_stack { crate::services::stack_preview::update_cache(&self.editor_ctx); } } } pub fn save_editor_to_step(&mut self) { let text = self.editor_ctx.editor.content(); let (bank, pattern) = self.current_bank_pattern(); let change = pattern_editor::set_step_script( &mut self.project_state.project, bank, pattern, self.editor_ctx.step, text, ); self.project_state.mark_dirty(change.bank, change.pattern); } pub fn open_prelude_editor(&mut self) { let prelude = &self.project_state.project.prelude; let lines: Vec = if prelude.is_empty() { vec![String::new()] } else { prelude.lines().map(String::from).collect() }; self.editor_ctx.editor.set_content(lines); self.editor_ctx.editor.set_candidates(COMPLETION_CANDIDATES.clone()); self.editor_ctx .editor .set_completion_enabled(self.ui.show_completion); let tree = SampleTree::from_paths(&self.audio.config.sample_paths); self.editor_ctx.editor.set_sample_folders(tree.all_folder_names()); self.editor_ctx.target = EditorTarget::Prelude; self.ui.modal = Modal::Editor; } pub fn save_prelude(&mut self) { let text = self.editor_ctx.editor.content(); self.project_state.project.prelude = text; } pub fn close_prelude_editor(&mut self) { self.editor_ctx.target = EditorTarget::Step; self.load_step_to_editor(); } pub fn evaluate_prelude(&mut self, link: &LinkState) { let prelude = &self.project_state.project.prelude; if prelude.trim().is_empty() { return; } let ctx = self.create_step_context(0, link); match self.script_engine.evaluate(prelude, &ctx) { Ok(_) => { self.ui.flash("Prelude evaluated", 150, FlashKind::Info); } Err(e) => { self.ui .flash(&format!("Prelude error: {e}"), 300, FlashKind::Error); } } } pub fn execute_script_oneshot( &self, script: &str, link: &LinkState, audio_tx: &arc_swap::ArcSwap>, ) -> Result<(), String> { let ctx = self.create_step_context(self.editor_ctx.step, link); let cmds = self.script_engine.evaluate(script, &ctx)?; for cmd in cmds { let _ = audio_tx .load() .send(crate::engine::AudioCommand::Evaluate { cmd, time: None }); } Ok(()) } pub fn compile_current_step(&mut self, link: &LinkState) { let step_idx = self.editor_ctx.step; let (bank, pattern) = self.current_bank_pattern(); let script = pattern_editor::get_step_script(&self.project_state.project, bank, pattern, step_idx) .unwrap_or_default(); if script.trim().is_empty() { return; } let ctx = self.create_step_context(step_idx, link); match self.script_engine.evaluate(&script, &ctx) { Ok(_) => { self.ui.flash("Script compiled", 150, FlashKind::Info); } Err(e) => { self.ui .flash(&format!("Script error: {e}"), 300, FlashKind::Error); } } } pub fn compile_all_steps(&mut self, link: &LinkState) { let pattern_len = self.current_edit_pattern().length; let (bank, pattern) = self.current_bank_pattern(); for step_idx in 0..pattern_len { let script = pattern_editor::get_step_script( &self.project_state.project, bank, pattern, step_idx, ) .unwrap_or_default(); if script.trim().is_empty() { continue; } let ctx = self.create_step_context(step_idx, link); let _ = self.script_engine.evaluate(&script, &ctx); } } }