Load sample .nam amps
This commit is contained in:
parent
7e3f18c95b
commit
725faa4445
|
|
@ -224,6 +224,21 @@ pub enum PendingScriptSampleLoad {
|
|||
FromFile { node_id: NodeId, backend_node_id: u32, slot_index: usize },
|
||||
}
|
||||
|
||||
/// Info about an available NAM model for amp sim selection
|
||||
pub struct NamModelInfo {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub is_bundled: bool,
|
||||
}
|
||||
|
||||
/// Pending AmpSim model load request from bottom_ui(), handled by the node graph pane
|
||||
pub enum PendingAmpSimLoad {
|
||||
/// Load a known model by path (from bundled list or previously loaded)
|
||||
FromPath { node_id: NodeId, backend_node_id: u32, path: String, name: String },
|
||||
/// Open file dialog to browse for a .nam file
|
||||
FromFile { node_id: NodeId, backend_node_id: u32 },
|
||||
}
|
||||
|
||||
/// Pending sampler load request from bottom_ui(), handled by the node graph pane
|
||||
pub enum PendingSamplerLoad {
|
||||
/// Load a single clip from the audio pool into a SimpleSampler
|
||||
|
|
@ -277,8 +292,12 @@ pub struct GraphState {
|
|||
pub pending_draw_param_changes: Vec<(NodeId, u32, f32)>,
|
||||
/// Active sample import dialog (folder import with heuristic mapping)
|
||||
pub sample_import_dialog: Option<crate::sample_import_dialog::SampleImportDialog>,
|
||||
/// Pending AmpSim model load (node_id, backend_node_id) — triggers file dialog for .nam
|
||||
pub pending_amp_sim_load: Option<(NodeId, u32)>,
|
||||
/// Pending AmpSim model load — triggers file dialog or direct load
|
||||
pub pending_amp_sim_load: Option<PendingAmpSimLoad>,
|
||||
/// Available NAM models for amp sim selection, populated before draw
|
||||
pub available_nam_models: Vec<NamModelInfo>,
|
||||
/// Search text for the NAM model picker popup
|
||||
pub nam_search_text: String,
|
||||
}
|
||||
|
||||
impl Default for GraphState {
|
||||
|
|
@ -303,6 +322,8 @@ impl Default for GraphState {
|
|||
pending_draw_param_changes: Vec::new(),
|
||||
sample_import_dialog: None,
|
||||
pending_amp_sim_load: None,
|
||||
available_nam_models: Vec::new(),
|
||||
nam_search_text: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1400,9 +1421,77 @@ impl NodeDataTrait for NodeData {
|
|||
}
|
||||
} else if self.template == NodeTemplate::AmpSim {
|
||||
let backend_node_id = user_state.node_backend_ids.get(&node_id).copied().unwrap_or(0);
|
||||
let button_text = self.nam_model_name.as_deref().unwrap_or("Load Model...");
|
||||
if ui.button(button_text).clicked() {
|
||||
user_state.pending_amp_sim_load = Some((node_id, backend_node_id));
|
||||
let button_text = self.nam_model_name.as_deref().unwrap_or("Select Model...");
|
||||
|
||||
let button = ui.button(button_text);
|
||||
if button.clicked() {
|
||||
user_state.nam_search_text.clear();
|
||||
}
|
||||
let popup_id = egui::Popup::default_response_id(&button);
|
||||
|
||||
let mut close_popup = false;
|
||||
egui::Popup::from_toggle_button_response(&button)
|
||||
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
|
||||
.width(200.0)
|
||||
.show(|ui| {
|
||||
let search_width = ui.available_width();
|
||||
ui.add_sized([search_width, 0.0], egui::TextEdit::singleline(&mut user_state.nam_search_text).hint_text("Search..."));
|
||||
ui.separator();
|
||||
let search = user_state.nam_search_text.to_lowercase();
|
||||
|
||||
let bundled: Vec<&NamModelInfo> = user_state.available_nam_models.iter()
|
||||
.filter(|m| m.is_bundled && (search.is_empty() || m.name.to_lowercase().contains(&search)))
|
||||
.collect();
|
||||
let user_models: Vec<&NamModelInfo> = user_state.available_nam_models.iter()
|
||||
.filter(|m| !m.is_bundled && (search.is_empty() || m.name.to_lowercase().contains(&search)))
|
||||
.collect();
|
||||
|
||||
if !bundled.is_empty() {
|
||||
ui.label(egui::RichText::new("Bundled").small().weak());
|
||||
let items = bundled.iter().map(|m| {
|
||||
let selected = self.nam_model_name.as_deref() == Some(m.name.as_str());
|
||||
(selected, m.name.as_str())
|
||||
});
|
||||
if let Some(idx) = widgets::scrollable_list(ui, 200.0, items) {
|
||||
let model = bundled[idx];
|
||||
user_state.pending_amp_sim_load = Some(PendingAmpSimLoad::FromPath {
|
||||
node_id, backend_node_id,
|
||||
path: model.path.clone(),
|
||||
name: model.name.clone(),
|
||||
});
|
||||
close_popup = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !user_models.is_empty() {
|
||||
ui.separator();
|
||||
ui.label(egui::RichText::new("User").small().weak());
|
||||
let items = user_models.iter().map(|m| {
|
||||
let selected = self.nam_model_name.as_deref() == Some(m.name.as_str());
|
||||
(selected, m.name.as_str())
|
||||
});
|
||||
if let Some(idx) = widgets::scrollable_list(ui, 200.0, items) {
|
||||
let model = user_models[idx];
|
||||
user_state.pending_amp_sim_load = Some(PendingAmpSimLoad::FromPath {
|
||||
node_id, backend_node_id,
|
||||
path: model.path.clone(),
|
||||
name: model.name.clone(),
|
||||
});
|
||||
close_popup = true;
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
if ui.button("Open...").clicked() {
|
||||
user_state.pending_amp_sim_load = Some(PendingAmpSimLoad::FromFile {
|
||||
node_id, backend_node_id,
|
||||
});
|
||||
close_popup = true;
|
||||
}
|
||||
});
|
||||
|
||||
if close_popup {
|
||||
egui::Popup::close_id(ui.ctx(), popup_id);
|
||||
}
|
||||
} else {
|
||||
ui.label("");
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub mod backend;
|
|||
pub mod graph_data;
|
||||
|
||||
use backend::{BackendNodeId, GraphBackend};
|
||||
use graph_data::{AllNodeTemplates, SubgraphNodeTemplates, VoiceAllocatorNodeTemplates, DataType, GraphState, NodeData, NodeTemplate, ValueType};
|
||||
use graph_data::{AllNodeTemplates, SubgraphNodeTemplates, VoiceAllocatorNodeTemplates, DataType, GraphState, NamModelInfo, NodeData, NodeTemplate, PendingAmpSimLoad, ValueType};
|
||||
use super::NodePath;
|
||||
use eframe::egui;
|
||||
use egui_node_graph2::*;
|
||||
|
|
@ -403,6 +403,20 @@ impl NodeGraphPane {
|
|||
);
|
||||
self.node_id_map.insert(node_id, backend_id);
|
||||
self.backend_to_frontend_map.insert(backend_id, node_id);
|
||||
|
||||
// Auto-load default NAM model for new AmpSim nodes
|
||||
if node_type == "AmpSim" {
|
||||
if let Some(model) = self.user_state.available_nam_models.iter().find(|m| m.is_bundled) {
|
||||
controller.amp_sim_load_model(
|
||||
backend_track_id,
|
||||
backend_node.id,
|
||||
model.path.clone(),
|
||||
);
|
||||
if let Some(node) = self.state.graph.nodes.get_mut(node_id) {
|
||||
node.user_data.nam_model_name = Some(model.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -665,6 +679,20 @@ impl NodeGraphPane {
|
|||
);
|
||||
self.node_id_map.insert(frontend_id, backend_id);
|
||||
self.backend_to_frontend_map.insert(backend_id, frontend_id);
|
||||
|
||||
// Auto-load default NAM model for new AmpSim nodes
|
||||
if node_type == "AmpSim" {
|
||||
if let Some(model) = self.user_state.available_nam_models.iter().find(|m| m.is_bundled) {
|
||||
controller.amp_sim_load_model(
|
||||
backend_track_id,
|
||||
backend_node.id,
|
||||
model.path.clone(),
|
||||
);
|
||||
if let Some(node) = self.state.graph.nodes.get_mut(frontend_id) {
|
||||
node.user_data.nam_model_name = Some(model.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2469,6 +2497,46 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
|||
.collect();
|
||||
self.user_state.available_scripts.sort_by(|a, b| a.1.to_lowercase().cmp(&b.1.to_lowercase()));
|
||||
|
||||
// Bundled NAM models — discover once and cache
|
||||
if self.user_state.available_nam_models.is_empty() {
|
||||
let bundled_dirs = [
|
||||
std::env::current_exe().ok()
|
||||
.and_then(|p| p.parent().map(|d| d.join("models")))
|
||||
.unwrap_or_default(),
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../vendor/NeuralAudio/Utils/Models"),
|
||||
];
|
||||
for dir in &bundled_dirs {
|
||||
if let Ok(canon) = dir.canonicalize() {
|
||||
if canon.is_dir() {
|
||||
for entry in std::fs::read_dir(&canon).into_iter().flatten().flatten() {
|
||||
let path = entry.path();
|
||||
if path.extension().map_or(false, |e| e == "nam") {
|
||||
let stem = path.file_stem()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
// Skip LSTM variants (performance alternates, not separate amps)
|
||||
if stem.ends_with("-LSTM") {
|
||||
continue;
|
||||
}
|
||||
// Clean up display name: remove "-WaveNet" suffix
|
||||
let name = stem.strip_suffix("-WaveNet")
|
||||
.unwrap_or(&stem)
|
||||
.to_string();
|
||||
self.user_state.available_nam_models.push(NamModelInfo {
|
||||
name,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
is_bundled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
break; // use first directory found
|
||||
}
|
||||
}
|
||||
}
|
||||
self.user_state.available_nam_models.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
}
|
||||
|
||||
// Node backend ID map
|
||||
self.user_state.node_backend_ids = self.node_id_map.iter()
|
||||
.map(|(&node_id, backend_id)| {
|
||||
|
|
@ -2518,8 +2586,19 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
|||
}
|
||||
|
||||
// Handle pending AmpSim model load from bottom_ui()
|
||||
if let Some((node_id, backend_node_id)) = self.user_state.pending_amp_sim_load.take() {
|
||||
if let Some(load) = self.user_state.pending_amp_sim_load.take() {
|
||||
if let Some(backend_track_id) = self.backend_track_id {
|
||||
if let Some(controller_arc) = &shared.audio_controller {
|
||||
match load {
|
||||
PendingAmpSimLoad::FromPath { node_id, backend_node_id, path, name } => {
|
||||
controller_arc.lock().unwrap().amp_sim_load_model(
|
||||
backend_track_id, backend_node_id, path,
|
||||
);
|
||||
if let Some(node) = self.state.graph.nodes.get_mut(node_id) {
|
||||
node.user_data.nam_model_name = Some(name);
|
||||
}
|
||||
}
|
||||
PendingAmpSimLoad::FromFile { node_id, backend_node_id } => {
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.add_filter("NAM Model", &["nam"])
|
||||
.pick_file()
|
||||
|
|
@ -2527,16 +2606,25 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
|||
let model_name = path.file_stem()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "Model".to_string());
|
||||
if let Some(controller_arc) = &shared.audio_controller {
|
||||
let mut controller = controller_arc.lock().unwrap();
|
||||
controller.amp_sim_load_model(
|
||||
controller_arc.lock().unwrap().amp_sim_load_model(
|
||||
backend_track_id,
|
||||
backend_node_id,
|
||||
path.to_string_lossy().to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(node) = self.state.graph.nodes.get_mut(node_id) {
|
||||
node.user_data.nam_model_name = Some(model_name);
|
||||
node.user_data.nam_model_name = Some(model_name.clone());
|
||||
}
|
||||
// Add user-loaded model to the available list if not already present
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
if !self.user_state.available_nam_models.iter().any(|m| m.path == path_str) {
|
||||
self.user_state.available_nam_models.push(NamModelInfo {
|
||||
name: model_name,
|
||||
path: path_str,
|
||||
is_bundled: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue