mod preview_shader;
use crate::ui::scoped_frame;
use egui::load::SizedTexture;
use egui::{Color32, Frame, Image, Layout, Ui, UiBuilder};
use egui_knob::{Knob, KnobStyle, LabelPosition};
use emath::{Align, Pos2, Rect, Vec2, pos2, remap_clamp, vec2};
use epaint::{PathShape, PathStroke, Stroke};
use gled_model::sound::trigger::{SoundTrigger, SoundTriggerParams};
use gled_model::sound::trigger_params::MAX_FREQ;
use gled_model::sound::trigger_ref::{SoundLevels, SoundTriggerRef};
use gled_protocol::state::SoundSnapshot;
use ndarray::Array1;
use preview_shader::{PreviewShader, preview_shader};
pub(crate) fn sound_trigger_editor(
ui: &mut Ui,
trigger: &mut SoundTriggerRef,
sound_data: &SoundSnapshot,
) -> bool {
#[cfg(feature = "profiling")]
puffin::profile_function!("sound_trigger_editor");
let mut changed = false;
Frame::new().inner_margin(5.).show(ui, |ui| {
crate::ui::frame_requests::watch_sound_trigger(trigger.id);
let (impulse, output_level, preview) = match sound_data.reading(&trigger.id) {
Some((impulse, level, spectrum)) => {
let mut spectrum = Array1::from_vec(spectrum);
spectrum.map_inplace(|x| {
*x = (*x * 10.0 + 10.0).log10() - 1.0;
});
let preview = preview_shader(ui, trigger.id);
if let Some(preview) = &preview
&& !spectrum.is_empty()
{
preview.draw_spectrum_texture(spectrum);
}
(impulse.clamp(0.0, 1.0), level, preview)
}
None => (0.0, 0.0, None),
};
changed |= draw_spectrum(ui, trigger, preview.as_deref());
ui.separator();
changed |= draw_adsr(ui, trigger, impulse, output_level);
});
changed
}
pub(crate) fn sound_trigger_minified(
ui: &mut Ui,
rect: Rect,
trigger: &SoundTriggerRef,
sound_data: &SoundSnapshot,
) {
#[cfg(feature = "profiling")]
puffin::profile_function!("sound_trigger_minified");
let level = sound_data.level(&trigger.id);
scoped_frame(
ui,
UiBuilder::new().max_rect(rect),
Frame::default(),
|ui| {
let to_screen = emath::RectTransform::from_to(
Rect::from_min_size(Pos2::ZERO, Vec2::new(4.0, 1.0)),
rect,
);
let painter = ui.painter().with_clip_rect(rect);
for i in 0..=4 {
let stroke = Stroke::new(
match i {
0 | 4 => 1.0,
_ => 0.5,
},
Color32::GRAY,
);
painter.add(PathShape::line(
vec![
to_screen.transform_pos(Pos2::new(i as f32, 0.0)),
to_screen.transform_pos(Pos2::new(i as f32, 1.0)),
],
stroke,
));
painter.add(PathShape::line(
vec![
to_screen.transform_pos(Pos2::new(0.0, i as f32 * 0.25)),
to_screen.transform_pos(Pos2::new(4.0, i as f32 * 0.25)),
],
stroke,
));
}
draw_envelope(ui, rect, level, &trigger.params);
},
);
}
fn draw_adsr(
ui: &mut Ui,
trigger: &mut SoundTriggerRef,
input_level: f32,
output_level: f32,
) -> bool {
let params = &mut trigger.params;
let mut changed = false;
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
let meter_height = 200.0;
let (_, rect) = ui.allocate_space(Vec2::new(40.0, meter_height));
ui.painter().rect_filled(rect, 0., Color32::from_gray(100));
let mut value_rect = rect;
value_rect.min.y += meter_height * (1.0 - input_level);
ui.painter()
.rect_filled(value_rect, 0., Color32::LIGHT_GREEN);
let threshold_line_y = rect.min.y + meter_height * (1.0 - params.gate_activation_threshold);
ui.painter().line(
vec![
pos2(rect.min.x, threshold_line_y),
pos2(rect.max.x, threshold_line_y),
],
Stroke::new(2., Color32::BLUE),
);
let threshold_deac_line_y =
rect.min.y + meter_height * (1.0 - params.gate_deactivation_threshold);
ui.painter().line(
vec![
pos2(rect.min.x, threshold_deac_line_y),
pos2(rect.max.x, threshold_deac_line_y),
],
Stroke::new(2., Color32::BLUE),
);
let mut rect = ui.available_rect_before_wrap();
rect.set_height(200.);
let frame = Frame::new().inner_margin(5.0).fill(Color32::from_gray(60));
scoped_frame(ui, UiBuilder::new().max_rect(rect), frame, |ui| {
let rect = ui.available_rect_before_wrap();
draw_envelope(ui, rect, output_level, params);
});
});
scoped_frame(
ui,
UiBuilder::new()
.max_rect(Rect::from_min_size(
ui.cursor().min,
vec2(ui.available_width(), 100.),
))
.layout(Layout::left_to_right(Align::Max).with_cross_justify(true)),
Frame::default().inner_margin(5.0),
|ui| {
Frame::new().inner_margin(5.).show(ui, |ui| {
changed |= ui
.add(
knob_default(&mut params.attack_duration, 0.0, 2.0, 0.01)
.with_size(50.0)
.with_logarithmic_scaling()
.with_label("Attack", LabelPosition::Bottom),
)
.changed();
changed |= ui
.add(
knob_default(&mut params.release_duration, 0.0, 10.0, 1.0)
.with_size(50.0)
.with_logarithmic_scaling()
.with_label("Release", LabelPosition::Bottom),
)
.changed();
changed |= ui
.add(
knob_default(&mut params.gate_activation_threshold, 0.0, 1.0, 0.5)
.with_size(30.0)
.with_label("Threshold", LabelPosition::Bottom),
)
.changed();
});
},
);
changed
}
fn draw_spectrum(
ui: &mut Ui,
trigger: &mut SoundTriggerRef,
preview: Option<&PreviewShader>,
) -> bool {
#[cfg(feature = "profiling")]
puffin::profile_function!("draw_spectrum");
let mut changed = false;
let size = vec2(ui.available_width(), 190.);
let mut spectrum_rect = Rect::from_min_size(ui.cursor().min, size);
if let Some(preview) = preview {
ui.add(Image::new(SizedTexture::new(
preview.spectrum_texture_id.0,
size,
)));
}
let lower_f = trigger.f_center - trigger.f_radius;
let upper_f = trigger.f_center + trigger.f_radius;
let max_frequency_range = 0.0..=MAX_FREQ;
let ui_position_range = spectrum_rect.left()..=spectrum_rect.right();
spectrum_rect.min.x = remap_clamp(
lower_f,
max_frequency_range.clone(),
ui_position_range.clone(),
);
spectrum_rect.max.x = remap_clamp(upper_f, max_frequency_range, ui_position_range);
ui.painter()
.rect_filled(spectrum_rect, 0., Color32::from_white_alpha(100));
scoped_frame(
ui,
UiBuilder::new()
.max_rect(Rect::from_min_size(
ui.cursor().min,
vec2(ui.available_width(), 100.),
))
.layout(Layout::left_to_right(Align::Max)),
Frame::default().inner_margin(5.0),
|ui| {
let mut filter_tune_changed = ui
.add(
knob_default(&mut trigger.f_center, 0., 24_000., 100.0)
.with_size(50.0)
.with_logarithmic_scaling()
.with_label("Frequency", LabelPosition::Bottom),
)
.changed();
filter_tune_changed |= ui
.add(
knob_default(&mut trigger.f_radius, 100., 24_000., 50.0)
.with_size(50.0)
.with_logarithmic_scaling()
.with_label("Range", LabelPosition::Bottom),
)
.changed();
changed |= ui
.add(
knob_default(&mut trigger.params.sensitivity, 0.01, 10.0, 1.0)
.with_size(50.0)
.with_logarithmic_scaling()
.with_label("Sensitivity", LabelPosition::Bottom),
)
.changed();
if filter_tune_changed {
trigger
.params
.set_filter_tune(trigger.f_center, trigger.f_radius);
changed = true;
}
},
);
changed
}
fn draw_envelope(ui: &mut Ui, rect: Rect, output_level: f32, params: &SoundTriggerParams) {
let n = 100;
let to_screen =
emath::RectTransform::from_to(Rect::from_x_y_ranges(0.0..=1.0, 0.0..=1.0), rect);
let mut level_rect = rect;
level_rect.min.y += (1.0 - (output_level)) * rect.height();
ui.painter()
.rect_filled(level_rect, 0., Color32::LIGHT_GREEN);
let mut sound_trigger = SoundTrigger::new(*params, 100.);
let points: Vec<Pos2> = (0..=n)
.map(|i| {
let t = i as f32 / (n as f32);
let input = if 0.2 < t
&& t < (0.4
+ sound_trigger.params.decay_duration * 0.4
+ sound_trigger.params.attack_duration * 0.4)
{
1.0
} else {
0.0
};
let y = -sound_trigger.tick_adsr(input) + 1.;
to_screen * pos2(t, y)
})
.collect();
let shape = epaint::Shape::line(points, PathStroke::new(1.0, Color32::WHITE));
ui.painter().add(shape);
}
fn knob_default<'a>(value: &'a mut f32, min: f32, max: f32, reset: f32) -> Knob<'a> {
Knob::new(value, min, max, KnobStyle::Wiper)
.with_font_size(12.0)
.with_knob_color(Color32::GRAY)
.with_line_color(Color32::WHITE)
.with_text_color(Color32::WHITE)
.with_stroke_width(3.0)
.with_double_click_reset(reset)
}