diff --git a/au-o2-gui/src/editor/stems.rs b/au-o2-gui/src/editor/stems.rs index 009dd05..ce6d75d 100644 --- a/au-o2-gui/src/editor/stems.rs +++ b/au-o2-gui/src/editor/stems.rs @@ -72,6 +72,7 @@ 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; diff --git a/au-o2-gui/src/editor/tracks.rs b/au-o2-gui/src/editor/tracks.rs index 640bfd6..151b12c 100644 --- a/au-o2-gui/src/editor/tracks.rs +++ b/au-o2-gui/src/editor/tracks.rs @@ -40,6 +40,9 @@ 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 + } } } } diff --git a/au-o2-gui/src/editor/view.rs b/au-o2-gui/src/editor/view.rs index cb483c1..93ea823 100644 --- a/au-o2-gui/src/editor/view.rs +++ b/au-o2-gui/src/editor/view.rs @@ -215,6 +215,11 @@ impl Editor { &self.tracks, &self.active_clips, ), + BottomPanelMode::Visualizer => { + iced::widget::column![ + iced::widget::text("Visualizer").size(14), + ].spacing(4).into() + } }; container(panel_content) .height(self.bottom_panel_height) diff --git a/au-o2-gui/src/engine/host.rs b/au-o2-gui/src/engine/host.rs index 809689e..f9af43f 100644 --- a/au-o2-gui/src/engine/host.rs +++ b/au-o2-gui/src/engine/host.rs @@ -82,7 +82,7 @@ impl ModuleHost { path: &Path, config: &GlobalConfig, ) -> Option<(u32, String, Option)> { - let (plugin, _load_info) = crate::modules::plugin_host::DynamicPlugin::load(path, config)?; + let (mut plugin, _load_info) = crate::modules::plugin_host::DynamicPlugin::load(path, config)?; let id = self.next_id; self.next_id += 1; diff --git a/au-o2-gui/src/entry.rs b/au-o2-gui/src/entry.rs new file mode 100644 index 0000000..c56a8ec --- /dev/null +++ b/au-o2-gui/src/entry.rs @@ -0,0 +1,90 @@ +use std::path::PathBuf; + +use chrono::NaiveDateTime; +use iced::{Element, Task}; + +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 }, + 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 }, + 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, + + EditorMessage(crate::editor::Message), +} + +pub struct App { + pub state: AppState, +} + +impl Default for App { + fn default() -> Self { + Self { + state: AppState::ProjectView(ProjectViewState::Splash), + } + } +} + +pub fn main() -> iced::Result { + iced::application("Audio Oxide", App::update, App::view) + .run() +} + +impl App { + fn update(&mut self, _message: Message) -> Task { + Task::none() + } + + fn view(&self) -> Element<'_, Message> { + crate::gui::splash::view() + } +} diff --git a/au-o2-gui/src/gui/editor/clip_launcher.rs b/au-o2-gui/src/gui/editor/clip_launcher.rs new file mode 100644 index 0000000..31ec31f --- /dev/null +++ b/au-o2-gui/src/gui/editor/clip_launcher.rs @@ -0,0 +1,26 @@ +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, +) -> 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() +} diff --git a/au-o2-gui/src/gui/editor/control_bar.rs b/au-o2-gui/src/gui/editor/control_bar.rs index 8a50720..320b1db 100644 --- a/au-o2-gui/src/gui/editor/control_bar.rs +++ b/au-o2-gui/src/gui/editor/control_bar.rs @@ -15,11 +15,17 @@ 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; diff --git a/au-o2-gui/src/gui/editor/editor_pane.rs b/au-o2-gui/src/gui/editor/editor_pane.rs index 208122c..4be9120 100644 --- a/au-o2-gui/src/gui/editor/editor_pane.rs +++ b/au-o2-gui/src/gui/editor/editor_pane.rs @@ -3,7 +3,14 @@ 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>) -> Element<'a, Message> { +pub fn view<'a>( + selected_track: Option<&'a Track>, + _selected_index: Option, + _h_zoom: f32, + _tempo: f32, + _time_sig_num: u8, + _wf_peaks: Option<&'a crate::waveform::WaveformPeaks>, +) -> Element<'a, Message> { let header = text("Editor").size(16).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)); let content = if let Some(track) = selected_track { diff --git a/au-o2-gui/src/gui/editor/inspector.rs b/au-o2-gui/src/gui/editor/inspector.rs index 756b3b1..9e4c954 100644 --- a/au-o2-gui/src/gui/editor/inspector.rs +++ b/au-o2-gui/src/gui/editor/inspector.rs @@ -22,8 +22,16 @@ pub fn view<'a>( module_names: &'a HashMap, track_index: Option, hilbert_fft_size: usize, - visualizer_buffer_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, + _groups: &'a [crate::track::TrackGroup], ) -> 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 { diff --git a/au-o2-gui/src/gui/editor/mixer.rs b/au-o2-gui/src/gui/editor/mixer.rs index 3d8c08f..15e7bd7 100644 --- a/au-o2-gui/src/gui/editor/mixer.rs +++ b/au-o2-gui/src/gui/editor/mixer.rs @@ -25,7 +25,22 @@ fn pan_label(pan: f32) -> String { } } -pub fn view<'a>(tracks: &'a [Track], icons: &'a IconSet) -> Element<'a, Message> { +pub fn view<'a>( + tracks: &'a [Track], + _groups: &'a [crate::track::TrackGroup], + icons: &'a IconSet, + _module_names: &'a std::collections::HashMap, + _disabled_modules: &'a std::collections::HashSet, + _modules_with_gui: &'a std::collections::HashSet, + _module_picker_track: Option, + _send_picker_track: Option, + _master_volume: f32, + _master_pan: f32, + _meter_levels: &'a std::collections::HashMap, + _master_meter: (f32, f32), + _discovered_plugins: &'a [crate::modules::plugin_host::PluginInfo], + _show_network_view: bool, +) -> Element<'a, Message> { let header = container( text("Mixer").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)), ) diff --git a/au-o2-gui/src/gui/editor/mod.rs b/au-o2-gui/src/gui/editor/mod.rs index 82b42fc..e417175 100644 --- a/au-o2-gui/src/gui/editor/mod.rs +++ b/au-o2-gui/src/gui/editor/mod.rs @@ -1,8 +1,11 @@ +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; diff --git a/au-o2-gui/src/gui/editor/score.rs b/au-o2-gui/src/gui/editor/score.rs new file mode 100644 index 0000000..f04ef7c --- /dev/null +++ b/au-o2-gui/src/gui/editor/score.rs @@ -0,0 +1,47 @@ +use crate::editor::Message; +use crate::track::Track; +use iced::widget::{column, container, text}; +use iced::{Background, Color, Element, Length, Theme}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScoreNoteDuration { + Whole, + Half, + Quarter, + Eighth, + Sixteenth, +} + +pub fn view<'a>( + selected_track: Option<&'a Track>, + _selected_index: Option, + _h_zoom: f32, + _tempo: f32, + _time_sig_num: u8, + _note_duration: ScoreNoteDuration, +) -> Element<'a, Message> { + let content = if let Some(track) = selected_track { + column![ + text("Score Editor").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)), + text(format!("{} - Score View", track.name)).size(12), + ] + .spacing(4) + .padding(8) + } else { + column![ + text("Score Editor").size(14).color(Color::from_rgb8(0xAA, 0xAA, 0xAA)), + 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() +} diff --git a/au-o2-gui/src/gui/editor/step_seq.rs b/au-o2-gui/src/gui/editor/step_seq.rs new file mode 100644 index 0000000..ffbff58 --- /dev/null +++ b/au-o2-gui/src/gui/editor/step_seq.rs @@ -0,0 +1,20 @@ +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, + _h_zoom: f32, + _tempo: f32, + _time_sig_numerator: u8, + _pattern_length: usize, +) -> Element<'a, Message> { + column![ + text("Step Sequencer").size(14), + ] + .spacing(4) + .into() +} diff --git a/au-o2-gui/src/gui/editor/timeline.rs b/au-o2-gui/src/gui/editor/timeline.rs index cf1d120..77c497e 100644 --- a/au-o2-gui/src/gui/editor/timeline.rs +++ b/au-o2-gui/src/gui/editor/timeline.rs @@ -76,6 +76,12 @@ 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; diff --git a/au-o2-gui/src/gui/mod.rs b/au-o2-gui/src/gui/mod.rs index a3a1862..62f7a4d 100644 --- a/au-o2-gui/src/gui/mod.rs +++ b/au-o2-gui/src/gui/mod.rs @@ -2,10 +2,12 @@ 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; diff --git a/au-o2-gui/src/gui/module_window.rs b/au-o2-gui/src/gui/module_window.rs new file mode 100644 index 0000000..f9971f3 --- /dev/null +++ b/au-o2-gui/src/gui/module_window.rs @@ -0,0 +1,110 @@ +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, + window_to_module: HashMap, +} + +struct ModuleWindowState { + window_id: window::Id, + module_name: String, + _gui_desc: ModuleGuiDescriptor, + fence: AudioFenceHandle, + _bridge: Option, +} + +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, + ) -> (window::Id, Task, 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> { + if let Some(state) = self.windows.remove(&module_id) { + self.window_to_module.remove(&state.window_id); + let task: Task = window::close::(state.window_id).discard(); + return Some(task); + } + None + } + + pub fn module_for_window(&self, window_id: window::Id) -> Option { + self.window_to_module.get(&window_id).copied() + } + + pub fn view(&self, _window_id: window::Id) -> Option> { + None + } + + pub fn window_title(&self, window_id: window::Id) -> Option { + 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) {} + + 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) {} +} diff --git a/au-o2-gui/src/gui/new_project.rs b/au-o2-gui/src/gui/new_project.rs index 0fcadd3..818deab 100644 --- a/au-o2-gui/src/gui/new_project.rs +++ b/au-o2-gui/src/gui/new_project.rs @@ -44,6 +44,9 @@ 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, diff --git a/au-o2-gui/src/gui/theme.rs b/au-o2-gui/src/gui/theme.rs new file mode 100644 index 0000000..38d6870 --- /dev/null +++ b/au-o2-gui/src/gui/theme.rs @@ -0,0 +1,37 @@ +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; diff --git a/au-o2-gui/src/modules/mod.rs b/au-o2-gui/src/modules/mod.rs index 19d3eb0..6ed3088 100644 --- a/au-o2-gui/src/modules/mod.rs +++ b/au-o2-gui/src/modules/mod.rs @@ -1,3 +1,4 @@ +pub mod plugin_host; pub mod registry; pub use oxforge::mdk::{AnalyticSignal, PhasePoint, VisualizationFrame}; diff --git a/au-o2-gui/src/modules/plugin_host.rs b/au-o2-gui/src/modules/plugin_host.rs new file mode 100644 index 0000000..aae61ed --- /dev/null +++ b/au-o2-gui/src/modules/plugin_host.rs @@ -0,0 +1,80 @@ +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, +} + +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 { + Vec::new() + } + + pub fn take_framebuffer_bridge(&mut self) -> Option { + 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) {} + + fn param_descriptors(&self) -> Vec { + Vec::new() + } +} + +pub fn scan_all_plugins() -> Vec { + Vec::new() +} diff --git a/au-o2-gui/src/track.rs b/au-o2-gui/src/track.rs index 9f9a115..c80ad84 100644 --- a/au-o2-gui/src/track.rs +++ b/au-o2-gui/src/track.rs @@ -86,6 +86,7 @@ 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 { @@ -93,6 +94,7 @@ impl Default for TrackConfig { Self { name: "New Track".to_string(), track_type: TrackType::Audio, + input_buffer_size: 512, } } } diff --git a/oxforge/src/mdk/gui.rs b/oxforge/src/mdk/gui.rs index 9ec3ef3..4ddf462 100644 --- a/oxforge/src/mdk/gui.rs +++ b/oxforge/src/mdk/gui.rs @@ -7,6 +7,12 @@ pub struct AudioFenceHandle { audio_to_gui: Arc>>, } +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 {