Compare commits
No commits in common. "977b3ea41025de0498524ef2ebf0f4fa5977faa1" and "d7c54c770bf69e662d2df51d3b7723e5133d221e" have entirely different histories.
977b3ea410
...
d7c54c770b
|
|
@ -207,9 +207,6 @@ impl Editor {
|
|||
&mut self.module_params,
|
||||
&self.routing.module_names,
|
||||
);
|
||||
if let Some(frame) = self.module_gui.take_visualization_frame() {
|
||||
self.visualization_frame = Some(frame);
|
||||
}
|
||||
if !gui_tasks.is_empty() {
|
||||
return Task::batch(gui_tasks);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,9 +251,6 @@ impl Editor {
|
|||
inspector_spatial_open: false,
|
||||
inspector_analysis_open: false,
|
||||
show_network_view: false,
|
||||
visualization_frame: None,
|
||||
spiral_rotation: 0.0,
|
||||
visualizer_kind: crate::gui::editor::visualizer::VisualizerKind::Spiral,
|
||||
},
|
||||
Task::none(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -98,28 +98,6 @@ pub enum BottomPanelMode {
|
|||
Visualizer,
|
||||
}
|
||||
|
||||
impl BottomPanelMode {
|
||||
pub const ALL: [BottomPanelMode; 6] = [
|
||||
BottomPanelMode::Editor,
|
||||
BottomPanelMode::Mixer,
|
||||
BottomPanelMode::StepSequencer,
|
||||
BottomPanelMode::ScoreEditor,
|
||||
BottomPanelMode::ClipLauncher,
|
||||
BottomPanelMode::Visualizer,
|
||||
];
|
||||
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Editor => "Editor",
|
||||
Self::Mixer => "Mixer",
|
||||
Self::StepSequencer => "Step Seq",
|
||||
Self::ScoreEditor => "Score",
|
||||
Self::ClipLauncher => "Clips",
|
||||
Self::Visualizer => "Visualizer",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StatusLevel {
|
||||
Info,
|
||||
|
|
@ -259,14 +237,9 @@ pub struct Editor {
|
|||
pub inspector_analysis_open: bool,
|
||||
|
||||
pub show_network_view: bool,
|
||||
|
||||
pub(crate) visualization_frame: Option<crate::modules::VisualizationFrame>,
|
||||
pub(crate) spiral_rotation: f32,
|
||||
pub(crate) visualizer_kind: crate::gui::editor::visualizer::VisualizerKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Message {
|
||||
PlayPressed,
|
||||
StopPressed,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ impl Editor {
|
|||
let config = crate::track::TrackConfig {
|
||||
name: format!("{} - {}", region_label, name),
|
||||
track_type: crate::track::TrackType::Audio,
|
||||
input_buffer_size: self.project_config.input_buffer_size,
|
||||
};
|
||||
let track = Track::new(config, self.track_count);
|
||||
self.track_count += 1;
|
||||
|
|
|
|||
|
|
@ -40,9 +40,6 @@ impl Editor {
|
|||
new_track_wizard::Message::TrackTypeSelected(track_type) => {
|
||||
state.config.track_type = track_type
|
||||
}
|
||||
new_track_wizard::Message::InputBufferSizeSelected(size) => {
|
||||
state.config.input_buffer_size = size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use iced::{alignment, Alignment, Background, Color, Element, Length, Theme};
|
|||
use super::{Editor, Message, ModalState, StatusLevel};
|
||||
use crate::gui::editor::{
|
||||
clip_launcher as clip_launcher_gui, control_bar, editor_pane, inspector, mixer,
|
||||
new_track_wizard, score, step_seq, timeline, toolbar, track_header, visualizer,
|
||||
new_track_wizard, score, step_seq, timeline, toolbar, track_header,
|
||||
};
|
||||
use crate::gui::theme as ui_theme;
|
||||
use crate::track::TRACK_HEIGHT;
|
||||
|
|
@ -215,14 +215,6 @@ impl Editor {
|
|||
&self.tracks,
|
||||
&self.active_clips,
|
||||
),
|
||||
BottomPanelMode::Visualizer => match self.visualizer_kind {
|
||||
visualizer::VisualizerKind::Spiral => {
|
||||
visualizer::spiral::view(
|
||||
self.visualization_frame.as_ref(),
|
||||
self.spiral_rotation,
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
container(panel_content)
|
||||
.height(self.bottom_panel_height)
|
||||
|
|
|
|||
|
|
@ -254,23 +254,6 @@ pub fn downmix_714_to_stereo(input: &[f32], out_l: &mut [f32], out_r: &mut [f32]
|
|||
}
|
||||
}
|
||||
|
||||
pub fn supported_spatial_modes() -> &'static [SpatialRenderMode] {
|
||||
&SpatialRenderMode::ALL
|
||||
}
|
||||
|
||||
pub fn supported_mono_lanes() -> &'static [MonoLane] {
|
||||
&MonoLane::ALL
|
||||
}
|
||||
|
||||
pub fn spatial_mode_channel_count(mode: &SpatialRenderMode) -> u8 {
|
||||
match mode {
|
||||
SpatialRenderMode::Mono => 1,
|
||||
SpatialRenderMode::Stereo => 2,
|
||||
SpatialRenderMode::Binaural => 2,
|
||||
SpatialRenderMode::Surround714 => 12,
|
||||
}
|
||||
}
|
||||
|
||||
fn angle_diff(a: f32, b: f32) -> f32 {
|
||||
let mut d = a - b;
|
||||
while d > 180.0 { d -= 360.0; }
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ impl ModuleHost {
|
|||
path: &Path,
|
||||
config: &GlobalConfig,
|
||||
) -> Option<(u32, String, Option<crate::modules::plugin_host::FramebufferGuiBridge>)> {
|
||||
let (mut plugin, _load_info) = crate::modules::plugin_host::DynamicPlugin::load(path, config)?;
|
||||
let (plugin, _load_info) = crate::modules::plugin_host::DynamicPlugin::load(path, config)?;
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ use std::sync::{Arc, Mutex};
|
|||
|
||||
use oxforge::mdk::ToGuiMessage;
|
||||
|
||||
use super::atmos;
|
||||
use super::cycle::CycleProcessor;
|
||||
use super::device::{self, DeviceCache};
|
||||
use super::device;
|
||||
use super::resample::IoResampler;
|
||||
use super::session_player;
|
||||
use super::{EngineCommand, EngineConfig, EngineEvent};
|
||||
|
||||
struct ResolvedDevice {
|
||||
|
|
@ -64,56 +62,6 @@ fn resolve_input(host: &cpal::Host, requested: &str) -> Option<ResolvedDevice> {
|
|||
Some(ResolvedDevice { device: d, name, was_fallback: true })
|
||||
}
|
||||
|
||||
/// Pre-flight capability check. Ensures all spatial modes, mono lanes,
|
||||
/// session player styles, and scales are reachable.
|
||||
fn log_engine_capabilities() {
|
||||
let spatial_modes = atmos::supported_spatial_modes();
|
||||
let mono_lanes = atmos::supported_mono_lanes();
|
||||
let styles = session_player::available_styles();
|
||||
let scales = session_player::available_scales();
|
||||
|
||||
let _mode_count = spatial_modes.iter()
|
||||
.map(|m| atmos::spatial_mode_channel_count(m))
|
||||
.sum::<u8>();
|
||||
|
||||
let _lane_count = mono_lanes.len();
|
||||
let _style_count = styles.len();
|
||||
let _scale_count = scales.len();
|
||||
}
|
||||
|
||||
fn validate_device_config(
|
||||
cache: &DeviceCache,
|
||||
output_name: &str,
|
||||
input_name: &str,
|
||||
config: &super::EngineConfig,
|
||||
evt_tx: &Sender<EngineEvent>,
|
||||
) {
|
||||
let out_caps = device::find_device(output_name, &cache.output_devices);
|
||||
let in_caps = device::find_device(input_name, &cache.input_devices);
|
||||
|
||||
if let (Some(out), Some(inp)) = (out_caps, in_caps) {
|
||||
let common_rates = device::negotiate_sample_rates(out, inp);
|
||||
let negotiated_depth = device::negotiate_bit_depth(out, inp);
|
||||
let out_depth = out.max_bit_depth();
|
||||
let in_depth = device::format_bit_depth(
|
||||
*out.supported_formats.first()
|
||||
.unwrap_or(&cpal::SampleFormat::F32),
|
||||
);
|
||||
let buf_options = device::buffer_size_options(out.buffer_size_range);
|
||||
|
||||
if !common_rates.contains(&config.sample_rate) && !common_rates.is_empty() {
|
||||
let _ = evt_tx.send(EngineEvent::Error(format!(
|
||||
"requested {}Hz not in common device rates ({:?}), negotiated depth={}bit",
|
||||
config.sample_rate, common_rates, negotiated_depth
|
||||
)));
|
||||
}
|
||||
|
||||
let _ = (out_depth, in_depth, buf_options);
|
||||
} else if let Some(out) = out_caps {
|
||||
let _buf_options = device::buffer_size_options(out.buffer_size_range);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_supported_rates(
|
||||
ranges: impl Iterator<Item = cpal::SupportedStreamConfigRange>,
|
||||
) -> Vec<u32> {
|
||||
|
|
@ -177,17 +125,6 @@ pub fn run_audio(
|
|||
)));
|
||||
}
|
||||
|
||||
let device_cache = device::query_all_devices();
|
||||
validate_device_config(
|
||||
&device_cache,
|
||||
&out_resolved.name,
|
||||
&config.input_device,
|
||||
config,
|
||||
&evt_tx,
|
||||
);
|
||||
|
||||
log_engine_capabilities();
|
||||
|
||||
let output_rate = negotiate_rate(&out_resolved.device, config.sample_rate, false);
|
||||
if output_rate != config.sample_rate {
|
||||
let _ = evt_tx.send(EngineEvent::Error(format!(
|
||||
|
|
|
|||
|
|
@ -393,14 +393,6 @@ fn generate_arpeggio(
|
|||
notes
|
||||
}
|
||||
|
||||
pub fn available_styles() -> Vec<(PlayerStyle, &'static str)> {
|
||||
PlayerStyle::ALL.iter().map(|s| (*s, s.label())).collect()
|
||||
}
|
||||
|
||||
pub fn available_scales() -> &'static [ScaleType] {
|
||||
&ScaleType::ALL
|
||||
}
|
||||
|
||||
/// Convert generated notes to MIDI events at a given sample rate and tempo.
|
||||
pub fn notes_to_midi_events(
|
||||
notes: &[GeneratedNote],
|
||||
|
|
|
|||
|
|
@ -1,540 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use iced::keyboard::{Key, Modifiers};
|
||||
use iced::{Element, Subscription, Task};
|
||||
|
||||
use crate::behaviors::Action;
|
||||
use crate::gui::native_menu::{NativeMenu, NativeMenuAction};
|
||||
use crate::gui::time_utility;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProjectInfo {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub modified: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ProjectViewState {
|
||||
Splash,
|
||||
Recent { projects: Vec<ProjectInfo> },
|
||||
Find { path_input: String },
|
||||
}
|
||||
|
||||
pub enum AppState {
|
||||
FirstRun { project_dir: PathBuf },
|
||||
ProjectView(ProjectViewState),
|
||||
NewProject(crate::gui::new_project::State),
|
||||
TimeUtility { tapper_state: time_utility::State, return_state: Box<AppState> },
|
||||
Settings { settings_state: crate::gui::settings::State, return_state: Box<AppState> },
|
||||
Editor(crate::editor::Editor),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
ViewRecentProjects,
|
||||
ViewFindProject,
|
||||
ViewNewProject,
|
||||
ViewTimeUtility,
|
||||
|
||||
OpenProject(PathBuf),
|
||||
CreateProject,
|
||||
FindPathChanged(String),
|
||||
|
||||
ProjectNameChanged(String),
|
||||
SampleRateSelected(u32),
|
||||
OutputBufferSizeSelected(u32),
|
||||
InputBufferSizeSelected(u32),
|
||||
AudioDeviceSelected(String),
|
||||
InputDeviceSelected(String),
|
||||
TempoChanged(f32),
|
||||
TimeSignatureNumeratorChanged(String),
|
||||
TimeSignatureDenominatorChanged(String),
|
||||
|
||||
FirstRunProjectDirChanged(String),
|
||||
FirstRunComplete,
|
||||
|
||||
TimeUtilityTapPressed,
|
||||
TimeUtilityTapReleased,
|
||||
TimeUtilitySet(u32),
|
||||
TimeUtilitySetTimeSignature(String),
|
||||
TimeUtilitySetBoth(u32, String),
|
||||
TimeUtilityCancel,
|
||||
RunTimeUtilityAnalysis,
|
||||
|
||||
OpenSettings,
|
||||
CloseSettings { save: bool },
|
||||
SettingsMessage(crate::gui::settings::Message),
|
||||
|
||||
KeyPressed(Key, Modifiers),
|
||||
KeyReleased(Key, Modifiers),
|
||||
WindowClosed(iced::window::Id),
|
||||
Tick,
|
||||
|
||||
EditorMessage(crate::editor::Message),
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub state: AppState,
|
||||
pub global_config: crate::config::AudioOxideConfig,
|
||||
native_menu: NativeMenu,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
fn default() -> Self {
|
||||
let config = crate::first_run::load_or_initialize_config();
|
||||
let state = if config.first_run {
|
||||
AppState::FirstRun { project_dir: config.project_dir.clone() }
|
||||
} else {
|
||||
AppState::ProjectView(ProjectViewState::Splash)
|
||||
};
|
||||
Self {
|
||||
state,
|
||||
global_config: config,
|
||||
native_menu: NativeMenu::init(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(App::title, App::update, App::view)
|
||||
.subscription(App::subscription)
|
||||
.run()
|
||||
}
|
||||
|
||||
fn scan_recent_projects(project_dir: &PathBuf) -> Vec<ProjectInfo> {
|
||||
let mut projects = Vec::new();
|
||||
let entries = match std::fs::read_dir(project_dir) {
|
||||
Ok(e) => e,
|
||||
Err(_) => return projects,
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
let config_path = path.join("project.toml");
|
||||
if !config_path.exists() {
|
||||
continue;
|
||||
}
|
||||
let name = path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("Untitled")
|
||||
.to_string();
|
||||
let modified = std::fs::metadata(&config_path)
|
||||
.and_then(|m| m.modified())
|
||||
.ok()
|
||||
.and_then(|t| {
|
||||
let duration = t.duration_since(std::time::UNIX_EPOCH).ok()?;
|
||||
chrono::DateTime::from_timestamp(duration.as_secs() as i64, 0)
|
||||
.map(|dt| dt.naive_local())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
projects.push(ProjectInfo { name, path, modified });
|
||||
}
|
||||
projects.sort_by(|a, b| b.modified.cmp(&a.modified));
|
||||
projects
|
||||
}
|
||||
|
||||
fn parse_time_signature(s: &str) -> Option<(u8, u8)> {
|
||||
let parts: Vec<&str> = s.split('/').collect();
|
||||
if parts.len() == 2 {
|
||||
let num = parts[0].trim().parse::<u8>().ok()?;
|
||||
let den = parts[1].trim().parse::<u8>().ok()?;
|
||||
Some((num, den))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_tempo_to_state(state: &mut AppState, bpm: f32) {
|
||||
if let AppState::NewProject(np) = state {
|
||||
np.config.tempo = bpm;
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_time_signature_to_state(state: &mut AppState, num: u8, den: u8) {
|
||||
if let AppState::NewProject(np) = state {
|
||||
np.config.time_signature_numerator = num;
|
||||
np.config.time_signature_denominator = den;
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn title(&self) -> String {
|
||||
match &self.state {
|
||||
AppState::Editor(editor) => {
|
||||
let name = editor.project_name();
|
||||
let dirty = if editor.is_dirty() { " *" } else { "" };
|
||||
let engine = if editor.has_engine() { "" } else { " [offline]" };
|
||||
let track_count = editor.tracks_ref().len();
|
||||
format!("Audio Oxide - {name}{dirty}{engine} ({track_count} tracks)")
|
||||
}
|
||||
_ => "Audio Oxide".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::ViewRecentProjects => {
|
||||
let projects = scan_recent_projects(&self.global_config.project_dir);
|
||||
self.state = AppState::ProjectView(ProjectViewState::Recent { projects });
|
||||
}
|
||||
Message::ViewFindProject => {
|
||||
self.state = AppState::ProjectView(ProjectViewState::Find {
|
||||
path_input: String::new(),
|
||||
});
|
||||
}
|
||||
Message::ViewNewProject => {
|
||||
self.state = AppState::NewProject(crate::gui::new_project::State::default());
|
||||
}
|
||||
Message::ViewTimeUtility => {
|
||||
let return_state = std::mem::replace(
|
||||
&mut self.state,
|
||||
AppState::ProjectView(ProjectViewState::Splash),
|
||||
);
|
||||
self.state = AppState::TimeUtility {
|
||||
tapper_state: time_utility::State::default(),
|
||||
return_state: Box::new(return_state),
|
||||
};
|
||||
}
|
||||
|
||||
Message::FindPathChanged(s) => {
|
||||
if let AppState::ProjectView(ProjectViewState::Find { ref mut path_input }) = self.state {
|
||||
*path_input = s;
|
||||
}
|
||||
}
|
||||
|
||||
Message::OpenProject(path) => {
|
||||
let (editor, task) = crate::editor::Editor::new(path);
|
||||
self.state = AppState::Editor(editor);
|
||||
return task.map(Message::EditorMessage);
|
||||
}
|
||||
Message::CreateProject => {
|
||||
if let AppState::NewProject(ref np_state) = self.state {
|
||||
let config = &np_state.config;
|
||||
let project_dir = self.global_config.project_dir.join(&config.name);
|
||||
let _ = std::fs::create_dir_all(&project_dir);
|
||||
let config_path = project_dir.join("project.toml");
|
||||
if let Ok(toml_str) = toml::to_string_pretty(config) {
|
||||
let _ = std::fs::write(&config_path, toml_str);
|
||||
}
|
||||
let (editor, task) = crate::editor::Editor::new(project_dir);
|
||||
self.state = AppState::Editor(editor);
|
||||
return task.map(Message::EditorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Message::ProjectNameChanged(s) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.name = s;
|
||||
}
|
||||
}
|
||||
Message::SampleRateSelected(sr) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.sample_rate = sr;
|
||||
}
|
||||
}
|
||||
Message::OutputBufferSizeSelected(bs) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.output_buffer_size = bs;
|
||||
}
|
||||
}
|
||||
Message::InputBufferSizeSelected(bs) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.input_buffer_size = bs;
|
||||
}
|
||||
}
|
||||
Message::AudioDeviceSelected(d) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.audio_device = d;
|
||||
}
|
||||
}
|
||||
Message::InputDeviceSelected(d) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.audio_input_device = d;
|
||||
}
|
||||
}
|
||||
Message::TempoChanged(t) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
np.config.tempo = t;
|
||||
}
|
||||
}
|
||||
Message::TimeSignatureNumeratorChanged(s) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
if let Ok(v) = s.parse::<u8>() {
|
||||
np.config.time_signature_numerator = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::TimeSignatureDenominatorChanged(s) => {
|
||||
if let AppState::NewProject(ref mut np) = self.state {
|
||||
if let Ok(v) = s.parse::<u8>() {
|
||||
np.config.time_signature_denominator = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::FirstRunProjectDirChanged(s) => {
|
||||
if let AppState::FirstRun { ref mut project_dir } = self.state {
|
||||
*project_dir = PathBuf::from(s);
|
||||
}
|
||||
}
|
||||
Message::FirstRunComplete => {
|
||||
if let AppState::FirstRun { ref project_dir } = self.state {
|
||||
self.global_config.first_run = false;
|
||||
self.global_config.project_dir = project_dir.clone();
|
||||
let _ = std::fs::create_dir_all(&self.global_config.project_dir);
|
||||
crate::first_run::save_config(&self.global_config);
|
||||
}
|
||||
self.state = AppState::ProjectView(ProjectViewState::Splash);
|
||||
}
|
||||
|
||||
Message::TimeUtilityTapPressed => {
|
||||
if let AppState::TimeUtility { ref mut tapper_state, .. } = self.state {
|
||||
time_utility::handle_tap_pressed(tapper_state);
|
||||
}
|
||||
}
|
||||
Message::TimeUtilityTapReleased => {
|
||||
if let AppState::TimeUtility { ref mut tapper_state, .. } = self.state {
|
||||
return time_utility::handle_tap_released(tapper_state);
|
||||
}
|
||||
}
|
||||
Message::RunTimeUtilityAnalysis => {
|
||||
if let AppState::TimeUtility { ref mut tapper_state, .. } = self.state {
|
||||
tapper_state.result = time_utility::run_analysis(&tapper_state.tap_events);
|
||||
}
|
||||
}
|
||||
Message::TimeUtilitySet(bpm) => {
|
||||
if let Some(mut rs) = self.take_time_utility_return() {
|
||||
apply_tempo_to_state(&mut rs, bpm as f32);
|
||||
self.state = rs;
|
||||
}
|
||||
}
|
||||
Message::TimeUtilitySetTimeSignature(sig) => {
|
||||
if let Some(mut rs) = self.take_time_utility_return() {
|
||||
if let Some((num, den)) = parse_time_signature(&sig) {
|
||||
apply_time_signature_to_state(&mut rs, num, den);
|
||||
}
|
||||
self.state = rs;
|
||||
}
|
||||
}
|
||||
Message::TimeUtilitySetBoth(bpm, sig) => {
|
||||
if let Some(mut rs) = self.take_time_utility_return() {
|
||||
apply_tempo_to_state(&mut rs, bpm as f32);
|
||||
if let Some((num, den)) = parse_time_signature(&sig) {
|
||||
apply_time_signature_to_state(&mut rs, num, den);
|
||||
}
|
||||
self.state = rs;
|
||||
}
|
||||
}
|
||||
Message::TimeUtilityCancel => {
|
||||
if let Some(rs) = self.take_time_utility_return() {
|
||||
self.state = rs;
|
||||
} else {
|
||||
self.state = AppState::ProjectView(ProjectViewState::Splash);
|
||||
}
|
||||
}
|
||||
|
||||
Message::OpenSettings => {
|
||||
if matches!(self.state, AppState::Settings { .. }) {
|
||||
return Task::none();
|
||||
}
|
||||
let return_state = std::mem::replace(
|
||||
&mut self.state,
|
||||
AppState::ProjectView(ProjectViewState::Splash),
|
||||
);
|
||||
let settings_state = crate::gui::settings::State::new(&self.global_config);
|
||||
self.state = AppState::Settings {
|
||||
settings_state,
|
||||
return_state: Box::new(return_state),
|
||||
};
|
||||
}
|
||||
Message::CloseSettings { save } => {
|
||||
if save {
|
||||
if let AppState::Settings { ref settings_state, .. } = self.state {
|
||||
self.global_config = settings_state.config.clone();
|
||||
crate::first_run::save_config(&self.global_config);
|
||||
}
|
||||
}
|
||||
if let Some(rs) = self.take_settings_return() {
|
||||
self.state = rs;
|
||||
} else {
|
||||
self.state = AppState::ProjectView(ProjectViewState::Splash);
|
||||
}
|
||||
}
|
||||
Message::SettingsMessage(settings_msg) => {
|
||||
if let AppState::Settings { ref mut settings_state, .. } = self.state {
|
||||
let is_save = matches!(settings_msg, crate::gui::settings::Message::Save);
|
||||
let is_cancel = matches!(settings_msg, crate::gui::settings::Message::Cancel);
|
||||
let done = crate::gui::settings::handle_message(settings_state, settings_msg);
|
||||
if done {
|
||||
return self.update(Message::CloseSettings { save: is_save && !is_cancel });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::KeyPressed(key, modifiers) => {
|
||||
if let Some(action) = crate::triggers::map_key_press_to_action(&self.state, key, modifiers) {
|
||||
return self.dispatch_action(action);
|
||||
}
|
||||
}
|
||||
Message::KeyReleased(key, modifiers) => {
|
||||
if let Some(action) = crate::triggers::map_key_release_to_action(&self.state, key, modifiers) {
|
||||
return self.dispatch_action(action);
|
||||
}
|
||||
}
|
||||
Message::Tick => {
|
||||
let menu_actions = self.native_menu.poll_events();
|
||||
if let Some(first) = menu_actions.into_iter().next() {
|
||||
match first {
|
||||
NativeMenuAction::Action(action) => {
|
||||
return self.dispatch_action(action);
|
||||
}
|
||||
NativeMenuAction::ShowNewTrackWizard => {
|
||||
return self.update(Message::EditorMessage(crate::editor::Message::ShowNewTrackWizard));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::WindowClosed(window_id) => {
|
||||
if let AppState::Editor(ref mut editor) = self.state {
|
||||
if editor.module_for_window(window_id).is_some() {
|
||||
let title = editor.module_window_title(window_id);
|
||||
let _had_view = editor.module_window_view(window_id).is_some();
|
||||
debug_log!("module window closed: {:?} (had_view={})", title, _had_view);
|
||||
drop(title);
|
||||
return editor.close_module_window_by_id(window_id).map(Message::EditorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::EditorMessage(editor_msg) => {
|
||||
if let AppState::Editor(ref mut editor) = self.state {
|
||||
return editor.update(editor_msg).map(Message::EditorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
match &self.state {
|
||||
AppState::ProjectView(ProjectViewState::Splash) => {
|
||||
crate::gui::splash::view()
|
||||
}
|
||||
AppState::ProjectView(pv_state) => {
|
||||
crate::gui::project_viewer::view(pv_state)
|
||||
}
|
||||
AppState::FirstRun { project_dir } => {
|
||||
crate::gui::first_run_wizard::view(project_dir)
|
||||
}
|
||||
AppState::NewProject(np_state) => {
|
||||
crate::gui::new_project::view(np_state)
|
||||
}
|
||||
AppState::TimeUtility { tapper_state, .. } => {
|
||||
time_utility::view(tapper_state)
|
||||
}
|
||||
AppState::Settings { settings_state, .. } => {
|
||||
crate::gui::settings::view(settings_state)
|
||||
.map(Message::SettingsMessage)
|
||||
}
|
||||
AppState::Editor(editor) => {
|
||||
editor.view().map(Message::EditorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
use iced::keyboard;
|
||||
Subscription::batch([
|
||||
keyboard::on_key_press(|key, modifiers| Some(Message::KeyPressed(key, modifiers))),
|
||||
keyboard::on_key_release(|key, modifiers| Some(Message::KeyReleased(key, modifiers))),
|
||||
iced::window::close_events().map(Message::WindowClosed),
|
||||
iced::time::every(std::time::Duration::from_millis(100)).map(|_| Message::Tick),
|
||||
])
|
||||
}
|
||||
|
||||
fn dispatch_action(&mut self, action: Action) -> Task<Message> {
|
||||
match action {
|
||||
Action::TimeUtilityTapPressed => self.update(Message::TimeUtilityTapPressed),
|
||||
Action::TimeUtilityTapReleased => self.update(Message::TimeUtilityTapReleased),
|
||||
Action::OpenSettings => self.update(Message::OpenSettings),
|
||||
Action::NewProject => self.update(Message::ViewNewProject),
|
||||
Action::EditorTogglePlayback => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::PlayPressed))
|
||||
}
|
||||
Action::EditorStop => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::StopPressed))
|
||||
}
|
||||
Action::EditorToggleRecord => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::RecordPressed))
|
||||
}
|
||||
Action::EditorRewind => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::RewindPressed))
|
||||
}
|
||||
Action::EditorPlayFromBeginning => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::RewindPressed))
|
||||
}
|
||||
Action::EditorToggleInspector => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::ToggleInspector))
|
||||
}
|
||||
Action::EditorToggleBottomPanel => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::ToggleBottomPanel))
|
||||
}
|
||||
Action::EditorToggleMixer => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::SetBottomPanelMode(
|
||||
crate::editor::BottomPanelMode::Mixer,
|
||||
)))
|
||||
}
|
||||
Action::EditorToggleCycle => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::CycleToggled))
|
||||
}
|
||||
Action::EditorToggleMetronome => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::MetronomeToggled))
|
||||
}
|
||||
Action::EditorToggleToolbar => {
|
||||
Task::none()
|
||||
}
|
||||
Action::ZoomInH => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::ZoomH(1.25)))
|
||||
}
|
||||
Action::ZoomOutH => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::ZoomH(0.8)))
|
||||
}
|
||||
Action::ZoomInV => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::ZoomV(1.25)))
|
||||
}
|
||||
Action::ZoomOutV => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::ZoomV(0.8)))
|
||||
}
|
||||
action => {
|
||||
self.update(Message::EditorMessage(crate::editor::Message::EditAction(action)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn take_time_utility_return(&mut self) -> Option<AppState> {
|
||||
let placeholder = AppState::ProjectView(ProjectViewState::Splash);
|
||||
let old = std::mem::replace(&mut self.state, placeholder);
|
||||
if let AppState::TimeUtility { return_state, .. } = old {
|
||||
Some(*return_state)
|
||||
} else {
|
||||
self.state = old;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn take_settings_return(&mut self) -> Option<AppState> {
|
||||
let placeholder = AppState::ProjectView(ProjectViewState::Splash);
|
||||
let old = std::mem::replace(&mut self.state, placeholder);
|
||||
if let AppState::Settings { return_state, .. } = old {
|
||||
Some(*return_state)
|
||||
} else {
|
||||
self.state = old;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
use crate::editor::Message;
|
||||
use crate::track::Track;
|
||||
use iced::widget::{column, container, text};
|
||||
use iced::{Background, Color, Element, Length, Theme};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn view<'a>(
|
||||
tracks: &'a [Track],
|
||||
_active_clips: &'a HashSet<uuid::Uuid>,
|
||||
) -> Element<'a, Message> {
|
||||
let content = column![
|
||||
text("Clip Launcher").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)),
|
||||
text(format!("{} tracks", tracks.len())).size(12),
|
||||
]
|
||||
.spacing(4)
|
||||
.padding(8);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb8(0x28, 0x2A, 0x2C))),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ use crate::engine::TransportState;
|
|||
use crate::gui::icon_button::{button_group, IconButton};
|
||||
use crate::gui::icons::{Icon, IconSet};
|
||||
use crate::timing::MusicalTime;
|
||||
use iced::widget::{button, container, row, text, Space};
|
||||
use iced::widget::{container, row, text, Space};
|
||||
use iced::{Alignment, Background, Color, Element, Length, Theme};
|
||||
|
||||
pub fn view<'a>(
|
||||
|
|
@ -15,17 +15,11 @@ pub fn view<'a>(
|
|||
cycle_enabled: bool,
|
||||
metronome_enabled: bool,
|
||||
count_in_enabled: bool,
|
||||
_punch_enabled: bool,
|
||||
record_armed: bool,
|
||||
show_inspector: bool,
|
||||
show_bottom_panel: bool,
|
||||
bottom_panel_mode: &BottomPanelMode,
|
||||
_show_tempo_lane: bool,
|
||||
icons: &'a IconSet,
|
||||
_lcd_editing: bool,
|
||||
_lcd_bar_input: &str,
|
||||
_lcd_beat_input: &str,
|
||||
_lcd_tick_input: &str,
|
||||
) -> Element<'a, Message> {
|
||||
let is_playing = *transport == TransportState::Playing;
|
||||
|
||||
|
|
@ -142,35 +136,11 @@ pub fn view<'a>(
|
|||
.into(),
|
||||
]);
|
||||
|
||||
let mut mode_row = row![].spacing(2);
|
||||
for mode in BottomPanelMode::ALL {
|
||||
let active = show_bottom_panel && *bottom_panel_mode == mode;
|
||||
let label = mode.label();
|
||||
let btn = button(text(label).size(10))
|
||||
.on_press(Message::SetBottomPanelMode(mode))
|
||||
.padding([2, 6])
|
||||
.style(move |_theme: &Theme, _status| {
|
||||
let bg = if active {
|
||||
Color::from_rgb8(0x44, 0x66, 0x88)
|
||||
} else {
|
||||
Color::from_rgb8(0x38, 0x3A, 0x3C)
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb8(0xDD, 0xDD, 0xDD),
|
||||
border: iced::Border { radius: 3.0.into(), ..iced::Border::default() },
|
||||
..button::Style::default()
|
||||
}
|
||||
});
|
||||
mode_row = mode_row.push(btn);
|
||||
}
|
||||
|
||||
let left = Space::new(Length::Fill, 0);
|
||||
let right = Space::new(Length::Fill, 0);
|
||||
|
||||
let bar = row![
|
||||
view_toggles,
|
||||
mode_row,
|
||||
left,
|
||||
lcd,
|
||||
transport_controls,
|
||||
|
|
|
|||
|
|
@ -3,14 +3,7 @@ use crate::track::Track;
|
|||
use iced::widget::{column, container, horizontal_rule, text};
|
||||
use iced::{Background, Color, Element, Length, Theme};
|
||||
|
||||
pub fn view<'a>(
|
||||
selected_track: Option<&'a Track>,
|
||||
_selected_index: Option<usize>,
|
||||
_h_zoom: f32,
|
||||
_tempo: f32,
|
||||
_time_sig_num: u8,
|
||||
_wf_peaks: Option<&'a crate::waveform::WaveformPeaks>,
|
||||
) -> Element<'a, Message> {
|
||||
pub fn view<'a>(selected_track: Option<&'a Track>) -> Element<'a, Message> {
|
||||
let header = text("Editor").size(16).color(Color::from_rgb8(0xAA, 0xAA, 0xAA));
|
||||
|
||||
let content = if let Some(track) = selected_track {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use crate::automation::AutomationMode;
|
||||
use crate::config::ProjectConfig;
|
||||
use crate::editor::Message;
|
||||
use crate::modules::registry::BUILTIN_MODULES;
|
||||
use crate::track::{MonitorMode, Track};
|
||||
use crate::track::Track;
|
||||
use iced::widget::{
|
||||
button, column, container, horizontal_rule, pick_list, row, text, vertical_rule, Column,
|
||||
};
|
||||
|
|
@ -23,16 +22,8 @@ pub fn view<'a>(
|
|||
module_names: &'a HashMap<u32, String>,
|
||||
track_index: Option<usize>,
|
||||
hilbert_fft_size: usize,
|
||||
_session_player_config: &'a crate::engine::session_player::SessionPlayerConfig,
|
||||
_session_player_bars: u32,
|
||||
_spatial_mode: crate::engine::atmos::SpatialRenderMode,
|
||||
_mono_lane: crate::engine::atmos::MonoLane,
|
||||
sections: &InspectorSections,
|
||||
_module_params: &'a crate::editor::ModuleParamState,
|
||||
_modules_with_gui: &'a std::collections::HashSet<u32>,
|
||||
_groups: &'a [crate::track::TrackGroup],
|
||||
visualizer_buffer_size: usize,
|
||||
) -> Element<'a, Message> {
|
||||
let visualizer_buffer_size: usize = 4096;
|
||||
let header = text("Inspector").size(16).color(Color::from_rgb8(0xAA, 0xAA, 0xAA));
|
||||
|
||||
let content = if let Some(track) = selected_track {
|
||||
|
|
@ -75,9 +66,8 @@ pub fn view<'a>(
|
|||
let mut add_col = Column::new().spacing(2);
|
||||
if let Some(idx) = track_index {
|
||||
for desc in BUILTIN_MODULES.iter().filter(|d| !d.system) {
|
||||
let label = format!("{} - {}", desc.display_name, desc.description);
|
||||
add_col = add_col.push(
|
||||
button(text(label).size(10))
|
||||
button(text(desc.display_name).size(10))
|
||||
.on_press(Message::AddModuleToTrack(idx, desc.type_name.to_string()))
|
||||
.padding([2, 6])
|
||||
.style(|_theme: &Theme, status| {
|
||||
|
|
@ -150,102 +140,6 @@ pub fn view<'a>(
|
|||
);
|
||||
}
|
||||
|
||||
if sections.signal {
|
||||
col = col.push(horizontal_rule(1));
|
||||
col = col.push(
|
||||
button(text("Signal").size(10).color(Color::from_rgb8(0x99, 0x99, 0x99)))
|
||||
.on_press(Message::ToggleInspectorSignal)
|
||||
.padding([2, 0])
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: Color::from_rgb8(0x99, 0x99, 0x99),
|
||||
..button::Style::default()
|
||||
}),
|
||||
);
|
||||
col = col.push(text(format!("Vol: {:.0}% Pan: {:+.0}", track.volume * 100.0, track.pan * 100.0)).size(10));
|
||||
|
||||
let track_idx = track_index.unwrap_or(0);
|
||||
let monitor_picker = pick_list(
|
||||
MonitorMode::ALL.as_slice(),
|
||||
Some(track.monitor_mode),
|
||||
move |m| Message::SetMonitorMode(track_idx, m),
|
||||
).width(80);
|
||||
col = col.push(
|
||||
row![text("Monitor").size(10).width(60), monitor_picker]
|
||||
.spacing(4).align_y(iced::Alignment::Center),
|
||||
);
|
||||
}
|
||||
|
||||
if sections.sends {
|
||||
col = col.push(
|
||||
button(text("Sends").size(10).color(Color::from_rgb8(0x99, 0x99, 0x99)))
|
||||
.on_press(Message::ToggleInspectorSends)
|
||||
.padding([2, 0])
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: Color::from_rgb8(0x99, 0x99, 0x99),
|
||||
..button::Style::default()
|
||||
}),
|
||||
);
|
||||
col = col.push(text(format!("{} sends", track.sends.len())).size(10));
|
||||
}
|
||||
|
||||
if sections.automation {
|
||||
col = col.push(
|
||||
button(text("Automation").size(10).color(Color::from_rgb8(0x99, 0x99, 0x99)))
|
||||
.on_press(Message::ToggleInspectorAutomation)
|
||||
.padding([2, 0])
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: Color::from_rgb8(0x99, 0x99, 0x99),
|
||||
..button::Style::default()
|
||||
}),
|
||||
);
|
||||
let track_idx = track_index.unwrap_or(0);
|
||||
let auto_mode_picker = pick_list(
|
||||
AutomationMode::ALL.as_slice(),
|
||||
Some(track.automation_mode),
|
||||
move |m| Message::SetTrackAutomationMode(track_idx, m),
|
||||
).width(80);
|
||||
col = col.push(
|
||||
row![text("Mode").size(10).width(60), auto_mode_picker]
|
||||
.spacing(4).align_y(iced::Alignment::Center),
|
||||
);
|
||||
col = col.push(text(format!("{} lanes", track.automation_lanes.len())).size(10));
|
||||
|
||||
for lane in &track.automation_lanes {
|
||||
let (min, max) = lane.value_range();
|
||||
col = col.push(text(format!("{} [{:.1}..{:.1}]", lane.target, min, max)).size(9));
|
||||
}
|
||||
}
|
||||
|
||||
if sections.spatial {
|
||||
col = col.push(
|
||||
button(text("Spatial").size(10).color(Color::from_rgb8(0x99, 0x99, 0x99)))
|
||||
.on_press(Message::ToggleInspectorSpatial)
|
||||
.padding([2, 0])
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: Color::from_rgb8(0x99, 0x99, 0x99),
|
||||
..button::Style::default()
|
||||
}),
|
||||
);
|
||||
col = col.push(text(format!("x:{:.1} y:{:.1} z:{:.1}", track.spatial_x, track.spatial_y, track.spatial_z)).size(10));
|
||||
}
|
||||
|
||||
if sections.analysis {
|
||||
col = col.push(
|
||||
button(text("Analysis").size(10).color(Color::from_rgb8(0x99, 0x99, 0x99)))
|
||||
.on_press(Message::ToggleInspectorAnalysis)
|
||||
.padding([2, 0])
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: Color::from_rgb8(0x99, 0x99, 0x99),
|
||||
..button::Style::default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
col = col.push(horizontal_rule(1));
|
||||
col = col.push(text("Bus").size(10).color(Color::from_rgb8(0x99, 0x99, 0x99)));
|
||||
col = col.push(text(&track.bus_name).size(10));
|
||||
|
|
|
|||
|
|
@ -25,31 +25,9 @@ fn pan_label(pan: f32) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
tracks: &'a [Track],
|
||||
_groups: &'a [crate::track::TrackGroup],
|
||||
icons: &'a IconSet,
|
||||
_module_names: &'a std::collections::HashMap<u32, String>,
|
||||
_disabled_modules: &'a std::collections::HashSet<u32>,
|
||||
_modules_with_gui: &'a std::collections::HashSet<u32>,
|
||||
_module_picker_track: Option<usize>,
|
||||
_send_picker_track: Option<usize>,
|
||||
_master_volume: f32,
|
||||
_master_pan: f32,
|
||||
_meter_levels: &'a std::collections::HashMap<String, (f32, f32)>,
|
||||
_master_meter: (f32, f32),
|
||||
discovered_plugins: &'a [crate::modules::plugin_host::PluginInfo],
|
||||
_show_network_view: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let plugin_count = discovered_plugins.len();
|
||||
let plugin_hint = if plugin_count > 0 {
|
||||
let paths: Vec<_> = discovered_plugins.iter().map(|p| p.path.display().to_string()).collect();
|
||||
format!("Mixer ({} plugins: {})", plugin_count, paths.join(", "))
|
||||
} else {
|
||||
"Mixer".to_string()
|
||||
};
|
||||
pub fn view<'a>(tracks: &'a [Track], icons: &'a IconSet) -> Element<'a, Message> {
|
||||
let header = container(
|
||||
text(plugin_hint).size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)),
|
||||
text("Mixer").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)),
|
||||
)
|
||||
.padding([4, 8]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
pub mod clip_launcher;
|
||||
pub mod control_bar;
|
||||
pub mod editor_pane;
|
||||
pub mod inspector;
|
||||
pub mod mixer;
|
||||
pub mod new_track_wizard;
|
||||
pub mod score;
|
||||
pub mod step_seq;
|
||||
pub mod timeline;
|
||||
pub mod toolbar;
|
||||
pub mod track_header;
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
use crate::editor::Message;
|
||||
use crate::track::Track;
|
||||
use iced::widget::{column, container, pick_list, row, text};
|
||||
use iced::{Background, Color, Element, Length, Theme};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScoreNoteDuration {
|
||||
Whole,
|
||||
Half,
|
||||
Quarter,
|
||||
Eighth,
|
||||
Sixteenth,
|
||||
}
|
||||
|
||||
impl ScoreNoteDuration {
|
||||
pub const ALL: [ScoreNoteDuration; 5] = [
|
||||
ScoreNoteDuration::Whole,
|
||||
ScoreNoteDuration::Half,
|
||||
ScoreNoteDuration::Quarter,
|
||||
ScoreNoteDuration::Eighth,
|
||||
ScoreNoteDuration::Sixteenth,
|
||||
];
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ScoreNoteDuration {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Whole => write!(f, "1"),
|
||||
Self::Half => write!(f, "1/2"),
|
||||
Self::Quarter => write!(f, "1/4"),
|
||||
Self::Eighth => write!(f, "1/8"),
|
||||
Self::Sixteenth => write!(f, "1/16"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
selected_track: Option<&'a Track>,
|
||||
_selected_index: Option<usize>,
|
||||
_h_zoom: f32,
|
||||
_tempo: f32,
|
||||
_time_sig_num: u8,
|
||||
note_duration: ScoreNoteDuration,
|
||||
) -> Element<'a, Message> {
|
||||
let duration_picker = pick_list(
|
||||
ScoreNoteDuration::ALL.as_slice(),
|
||||
Some(note_duration),
|
||||
|d| Message::SetScoreNoteDuration(d),
|
||||
).width(70);
|
||||
|
||||
let content = if let Some(track) = selected_track {
|
||||
column![
|
||||
row![
|
||||
text("Score Editor").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)),
|
||||
duration_picker,
|
||||
].spacing(8),
|
||||
text(format!("{} - Score View", track.name)).size(12),
|
||||
]
|
||||
.spacing(4)
|
||||
.padding(8)
|
||||
} else {
|
||||
column![
|
||||
row![
|
||||
text("Score Editor").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)),
|
||||
duration_picker,
|
||||
].spacing(8),
|
||||
text("Select a track").size(12).color(Color::from_rgb8(0x77, 0x77, 0x77)),
|
||||
]
|
||||
.spacing(4)
|
||||
.padding(8)
|
||||
};
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb8(0x28, 0x2A, 0x2C))),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
use iced::widget::{column, text};
|
||||
use iced::Element;
|
||||
|
||||
use crate::editor::Message;
|
||||
use crate::track::Track;
|
||||
|
||||
pub fn view<'a>(
|
||||
_selected_track: Option<&'a Track>,
|
||||
_track_index: Option<usize>,
|
||||
_h_zoom: f32,
|
||||
_tempo: f32,
|
||||
_time_sig_numerator: u8,
|
||||
_pattern_length: usize,
|
||||
) -> Element<'a, Message> {
|
||||
column![
|
||||
text("Step Sequencer").size(14),
|
||||
]
|
||||
.spacing(4)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ fn bar_groupings(num: u8, den: u8) -> (u32, u32) {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Message {
|
||||
ZoomChanged(f32, f32),
|
||||
PlayheadMoved(MusicalTime),
|
||||
|
|
@ -77,12 +76,6 @@ pub fn view<'a>(
|
|||
v_zoom: f32,
|
||||
recording: bool,
|
||||
waveforms: &'a WaveformCache,
|
||||
_cycle_enabled: bool,
|
||||
_cycle_start_bar: u32,
|
||||
_cycle_end_bar: u32,
|
||||
_markers: &'a [crate::timing::Marker],
|
||||
_tempo_map: &'a crate::timing::TempoMap,
|
||||
_show_tempo_lane: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let effective_track_height = TRACK_HEIGHT * v_zoom;
|
||||
let timeline_height = tracks.len() as f32 * effective_track_height;
|
||||
|
|
@ -330,7 +323,6 @@ impl<'a> Timeline<'a> {
|
|||
(total_beats as f64 * samples_per_beat) as u64
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn sample_to_x(&self, sample: u64) -> f32 {
|
||||
let beats_per_second = self.config.tempo / 60.0;
|
||||
let total_beats = sample as f32 / self.config.sample_rate as f32 * beats_per_second;
|
||||
|
|
@ -570,6 +562,7 @@ impl<'a> Timeline<'a> {
|
|||
}
|
||||
|
||||
fn draw_regions(&self, frame: &mut canvas::Frame, _bounds: Rectangle) {
|
||||
let th = self.effective_track_height();
|
||||
for (i, track) in self.tracks.iter().enumerate() {
|
||||
let region_color = Color::from_rgba8(
|
||||
track.color.r,
|
||||
|
|
|
|||
|
|
@ -58,26 +58,7 @@ pub fn view<'a>(track: &'a Track, icons: &'a IconSet, height: f32) -> Element<'a
|
|||
.hint("Record Arm")
|
||||
.into();
|
||||
|
||||
let freeze_btn: Element<'a, Message> = button(
|
||||
text(if track.frozen { "F*" } else { "F" }).size(10)
|
||||
)
|
||||
.on_press(Message::FreezeToggled)
|
||||
.padding([2, 5])
|
||||
.style(move |_theme: &Theme, _status| {
|
||||
let bg = if track.frozen {
|
||||
Color::from_rgb8(0x33, 0x77, 0xBB)
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb8(0x99, 0x99, 0x99),
|
||||
..button::Style::default()
|
||||
}
|
||||
})
|
||||
.into();
|
||||
|
||||
let controls = row![mute_btn, solo_btn, rec_btn, freeze_btn].spacing(2);
|
||||
let controls = row![mute_btn, solo_btn, rec_btn].spacing(2);
|
||||
|
||||
let volume_slider = slider(0.0..=1.0, track.volume, Message::VolumeChanged)
|
||||
.step(0.01)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ pub mod editor;
|
|||
pub mod first_run_wizard;
|
||||
pub mod icon_button;
|
||||
pub mod icons;
|
||||
pub mod module_window;
|
||||
pub mod native_menu;
|
||||
pub mod new_project;
|
||||
pub mod project_viewer;
|
||||
pub mod settings;
|
||||
pub mod splash;
|
||||
pub mod styles;
|
||||
pub mod theme;
|
||||
pub mod time_utility;
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use iced::window;
|
||||
use iced::{Element, Task};
|
||||
use oxforge::mdk::gui::AudioFenceHandle;
|
||||
use oxforge::mdk::{GuiToolkit, ModuleGuiDescriptor, ParameterDescriptor};
|
||||
|
||||
use crate::editor::Message;
|
||||
use crate::modules::plugin_host::FramebufferGuiBridge;
|
||||
|
||||
pub struct ModuleWindowManager {
|
||||
windows: HashMap<u32, ModuleWindowState>,
|
||||
window_to_module: HashMap<window::Id, u32>,
|
||||
}
|
||||
|
||||
struct ModuleWindowState {
|
||||
window_id: window::Id,
|
||||
module_name: String,
|
||||
_gui_desc: ModuleGuiDescriptor,
|
||||
fence: AudioFenceHandle,
|
||||
_bridge: Option<FramebufferGuiBridge>,
|
||||
}
|
||||
|
||||
impl ModuleWindowManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: HashMap::new(),
|
||||
window_to_module: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_open(&self, module_id: u32) -> bool {
|
||||
self.windows.contains_key(&module_id)
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
&mut self,
|
||||
module_id: u32,
|
||||
module_name: String,
|
||||
gui_desc: ModuleGuiDescriptor,
|
||||
_descriptors: &[ParameterDescriptor],
|
||||
_toolkit: GuiToolkit,
|
||||
bridge: Option<FramebufferGuiBridge>,
|
||||
) -> (window::Id, Task<Message>, AudioFenceHandle) {
|
||||
let fence = AudioFenceHandle::new();
|
||||
let (id, task) = window::open(window::Settings {
|
||||
size: iced::Size::new(gui_desc.width as f32, gui_desc.height as f32),
|
||||
..window::Settings::default()
|
||||
});
|
||||
|
||||
self.window_to_module.insert(id, module_id);
|
||||
self.windows.insert(module_id, ModuleWindowState {
|
||||
window_id: id,
|
||||
module_name,
|
||||
_gui_desc: gui_desc,
|
||||
fence: fence.clone(),
|
||||
_bridge: bridge,
|
||||
});
|
||||
|
||||
(id, task.discard(), fence)
|
||||
}
|
||||
|
||||
pub fn close(&mut self, module_id: u32) -> Option<Task<Message>> {
|
||||
if let Some(state) = self.windows.remove(&module_id) {
|
||||
self.window_to_module.remove(&state.window_id);
|
||||
let task: Task<Message> = window::close::<Message>(state.window_id).discard();
|
||||
return Some(task);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn module_for_window(&self, window_id: window::Id) -> Option<u32> {
|
||||
self.window_to_module.get(&window_id).copied()
|
||||
}
|
||||
|
||||
pub fn view(&self, _window_id: window::Id) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn window_title(&self, window_id: window::Id) -> Option<String> {
|
||||
let module_id = self.window_to_module.get(&window_id)?;
|
||||
let state = self.windows.get(module_id)?;
|
||||
Some(state.module_name.clone())
|
||||
}
|
||||
|
||||
pub fn framebuffer_mouse_down(&self, _module_id: u32, _x: i32, _y: i32) {}
|
||||
pub fn framebuffer_mouse_up(&self, _module_id: u32, _x: i32, _y: i32) {}
|
||||
pub fn resize_framebuffer(&mut self, _module_id: u32, _width: u32, _height: u32) {}
|
||||
|
||||
pub fn write_param(&mut self, module_id: u32, key: &str, value: f32) {
|
||||
if let Some(state) = self.windows.get(&module_id) {
|
||||
state.fence.push_change(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_visualization(&mut self, _module_id: u32, _data: Vec<u8>) {}
|
||||
|
||||
pub fn poll_fence_changes(&mut self) -> Vec<(u32, String, f32)> {
|
||||
let mut changes = Vec::new();
|
||||
for (&module_id, state) in &mut self.windows {
|
||||
let current = state.fence.read_current();
|
||||
for (key, value) in current {
|
||||
changes.push((module_id, key, value));
|
||||
}
|
||||
}
|
||||
changes
|
||||
}
|
||||
|
||||
pub fn tick_framebuffers(&mut self) {}
|
||||
}
|
||||
|
|
@ -44,9 +44,6 @@ impl Default for State {
|
|||
time_signature_numerator: 4,
|
||||
time_signature_denominator: 4,
|
||||
tracks: Vec::new(),
|
||||
markers: Vec::new(),
|
||||
tempo_points: Vec::new(),
|
||||
groups: Vec::new(),
|
||||
},
|
||||
available_output_devices: output_devices,
|
||||
available_input_devices: input_devices,
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
use iced::Color;
|
||||
|
||||
pub const BG_DEEPEST: Color = Color::from_rgb(0.08, 0.08, 0.09);
|
||||
pub const BG_DARKER: Color = Color::from_rgb(0.12, 0.12, 0.14);
|
||||
pub const BG_BASE: Color = Color::from_rgb(0.16, 0.17, 0.18);
|
||||
pub const BG_MID: Color = Color::from_rgb(0.19, 0.20, 0.21);
|
||||
|
||||
pub const TEXT_BRIGHT: Color = Color::from_rgb(0.90, 0.90, 0.90);
|
||||
pub const TEXT_MUTED: Color = Color::from_rgb(0.55, 0.55, 0.55);
|
||||
|
||||
pub const BORDER_SUBTLE: Color = Color::from_rgb(0.22, 0.23, 0.24);
|
||||
pub const BORDER_LIGHT: Color = Color::from_rgb(0.30, 0.31, 0.32);
|
||||
|
||||
pub const PINK: Color = Color::from_rgb(0.85, 0.25, 0.35);
|
||||
pub const ICON_TINT: Color = Color::from_rgb(0.95, 0.75, 0.20);
|
||||
|
||||
pub const TS_SM: f32 = 10.0;
|
||||
pub const TS_MD: f32 = 13.0;
|
||||
pub const TS_XL: f32 = 22.0;
|
||||
|
||||
pub const SP_XS: f32 = 4.0;
|
||||
pub const SP_MD: f32 = 8.0;
|
||||
pub const SP_LG: f32 = 16.0;
|
||||
pub const SP_XXL: f32 = 24.0;
|
||||
|
||||
pub const RESIZE_HANDLE_WIDTH: f32 = 6.0;
|
||||
pub const RESIZE_HANDLE_HEIGHT: f32 = 6.0;
|
||||
|
||||
pub const BOTTOM_PANEL_MIN: f32 = 100.0;
|
||||
pub const BOTTOM_PANEL_MAX: f32 = 800.0;
|
||||
|
||||
pub const TRACKLIST_WIDTH_MIN: f32 = 120.0;
|
||||
pub const TRACKLIST_WIDTH_MAX: f32 = 400.0;
|
||||
|
||||
pub const INSPECTOR_WIDTH: f32 = 230.0;
|
||||
pub const INSPECTOR_WIDTH_MIN: f32 = 180.0;
|
||||
pub const INSPECTOR_WIDTH_MAX: f32 = 400.0;
|
||||
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||
|
||||
use iced::window;
|
||||
use iced::{Element, Task};
|
||||
use oxforge::mdk::{ModuleGuiDescriptor, ToGuiMessage, VisualizationFrame};
|
||||
use oxforge::mdk::{ModuleGuiDescriptor, ToGuiMessage};
|
||||
|
||||
use crate::editor::{Message, ModuleParamState};
|
||||
use crate::engine::{EngineCommand, EngineHandle};
|
||||
|
|
@ -15,7 +15,6 @@ pub struct ModuleGuiManager {
|
|||
module_window_manager: ModuleWindowManager,
|
||||
pending_gui_opens: std::collections::HashSet<u32>,
|
||||
pending_bridges: HashMap<u32, FramebufferGuiBridge>,
|
||||
latest_vis_frame: Option<VisualizationFrame>,
|
||||
}
|
||||
|
||||
impl ModuleGuiManager {
|
||||
|
|
@ -26,7 +25,6 @@ impl ModuleGuiManager {
|
|||
module_window_manager: ModuleWindowManager::new(),
|
||||
pending_gui_opens: std::collections::HashSet::new(),
|
||||
pending_bridges: HashMap::new(),
|
||||
latest_vis_frame: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,9 +100,6 @@ impl ModuleGuiManager {
|
|||
for (module_id, msg) in engine.poll_gui_messages() {
|
||||
match msg {
|
||||
ToGuiMessage::VisualizationData { data } => {
|
||||
if let Some(frame) = VisualizationFrame::deserialize(&data) {
|
||||
self.latest_vis_frame = Some(frame);
|
||||
}
|
||||
self.module_window_manager.receive_visualization(module_id, data);
|
||||
}
|
||||
ToGuiMessage::Log(_) => {}
|
||||
|
|
@ -229,8 +224,4 @@ impl ModuleGuiManager {
|
|||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub fn take_visualization_frame(&mut self) -> Option<VisualizationFrame> {
|
||||
self.latest_vis_frame.take()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
pub mod plugin_host;
|
||||
pub mod registry;
|
||||
|
||||
pub use oxforge::mdk::{PhasePoint, VisualizationFrame};
|
||||
pub use oxforge::mdk::{AnalyticSignal, PhasePoint, VisualizationFrame};
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
use oxforge::mdk::{
|
||||
GlobalConfig, ModuleContract, OxideModule,
|
||||
ParameterDescriptor, PortDeclaration, Ports, ProcessContext,
|
||||
};
|
||||
use std::any::Any;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginInfo {
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FramebufferGuiBridge {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FramebufferGuiBridge {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FramebufferGuiBridge")
|
||||
.field("width", &self.width)
|
||||
.field("height", &self.height)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynamicPlugin {
|
||||
info: PluginInfo,
|
||||
bridge: Option<FramebufferGuiBridge>,
|
||||
}
|
||||
|
||||
impl DynamicPlugin {
|
||||
pub fn load(_path: &Path, _config: &GlobalConfig) -> Option<(Self, PluginInfo)> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn info(&self) -> &PluginInfo {
|
||||
&self.info
|
||||
}
|
||||
|
||||
pub fn contract(&self) -> ModuleContract {
|
||||
ModuleContract::default()
|
||||
}
|
||||
|
||||
pub fn port_declarations(&self) -> Vec<PortDeclaration> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn take_framebuffer_bridge(&mut self) -> Option<FramebufferGuiBridge> {
|
||||
self.bridge.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl OxideModule for DynamicPlugin {
|
||||
fn new(_config: &GlobalConfig) -> Self where Self: Sized {
|
||||
Self {
|
||||
info: PluginInfo {
|
||||
name: String::new(),
|
||||
display_name: String::new(),
|
||||
path: std::path::PathBuf::new(),
|
||||
},
|
||||
bridge: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, _ports: Ports, _context: &ProcessContext) {}
|
||||
|
||||
fn receive_data(&mut self, _key: &str, _data: Box<dyn Any + Send>) {}
|
||||
|
||||
fn param_descriptors(&self) -> Vec<ParameterDescriptor> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan_all_plugins() -> Vec<PluginInfo> {
|
||||
Vec::new()
|
||||
}
|
||||
|
|
@ -86,7 +86,6 @@ impl std::fmt::Display for TrackType {
|
|||
pub struct TrackConfig {
|
||||
pub name: String,
|
||||
pub track_type: TrackType,
|
||||
pub input_buffer_size: u32,
|
||||
}
|
||||
|
||||
impl Default for TrackConfig {
|
||||
|
|
@ -94,7 +93,6 @@ impl Default for TrackConfig {
|
|||
Self {
|
||||
name: "New Track".to_string(),
|
||||
track_type: TrackType::Audio,
|
||||
input_buffer_size: 512,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ pub fn map_key_press_to_action(
|
|||
"x" => Some(Action::EditorToggleMixer),
|
||||
"c" => Some(Action::EditorToggleCycle),
|
||||
"k" => Some(Action::EditorToggleMetronome),
|
||||
"t" => Some(Action::EditorToggleToolbar),
|
||||
"q" => Some(Action::Quantize),
|
||||
"," => Some(Action::EditorRewind),
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -7,12 +7,6 @@ pub struct AudioFenceHandle {
|
|||
audio_to_gui: Arc<Mutex<HashMap<String, f32>>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AudioFenceHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AudioFenceHandle").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioFenceHandle {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
|
|
|||
Loading…
Reference in New Issue