playing with visual effects: splash color
This commit is contained in:
parent
66ec445fdd
commit
3c079312dc
|
|
@ -26,8 +26,12 @@ struct Bin {
|
|||
hue: f32,
|
||||
sat: f32,
|
||||
val: f32,
|
||||
splash: f32, // 0..1 suppressed-energy color splash
|
||||
};
|
||||
|
||||
// 5/12 hue-wheel rotation per full splash, cycling all 12 wheel segments.
|
||||
const SPLASH_HUE_OFFSET: f32 = 0.4166667;
|
||||
|
||||
@group(0) @binding(0) var<uniform> globals: Globals;
|
||||
@group(0) @binding(1) var<storage, read> bins: array<Bin>;
|
||||
|
||||
|
|
@ -84,16 +88,19 @@ fn alpha_for(b: Bin, fade: f32) -> f32 {
|
|||
|
||||
fn dyn_rgb(b: Bin) -> vec3<f32> {
|
||||
let fb = final_brightness(b);
|
||||
let s = clamp(b.sat * globals.hue_param, 0.0, 1.0);
|
||||
let v = clamp(b.val * fb, 0.0, 1.0);
|
||||
return hsv_to_rgb(b.hue, s, v);
|
||||
let hue = fract(b.hue + SPLASH_HUE_OFFSET * b.splash);
|
||||
let s = clamp((b.sat + 0.4 * b.splash) * globals.hue_param, 0.0, 1.0);
|
||||
let v = clamp((b.val + 0.4 * b.splash) * fb, 0.0, 1.0);
|
||||
return hsv_to_rgb(hue, s, v);
|
||||
}
|
||||
|
||||
fn fill_rgb(b: Bin) -> vec3<f32> {
|
||||
if (flag(1u)) {
|
||||
let fb = final_brightness(b);
|
||||
let v = clamp(globals.unified_val * fb, 0.0, 1.0);
|
||||
return hsv_to_rgb(globals.unified_hue, globals.unified_sat, v);
|
||||
let hue = fract(globals.unified_hue + SPLASH_HUE_OFFSET * b.splash);
|
||||
let s = clamp(globals.unified_sat + 0.4 * b.splash, 0.0, 1.0);
|
||||
let v = clamp((globals.unified_val + 0.4 * b.splash) * fb, 0.0, 1.0);
|
||||
return hsv_to_rgb(hue, s, v);
|
||||
}
|
||||
return dyn_rgb(b);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ pub struct FrameData {
|
|||
pub primary_db: Vec<f32>,
|
||||
|
||||
pub cepstrum: Vec<f32>,
|
||||
|
||||
pub activity: Vec<f32>,
|
||||
}
|
||||
|
||||
/// stereo three-band processor pool driven by a streaming hilbert source and surfacing one frame per call to step.
|
||||
|
|
@ -218,6 +220,18 @@ impl Analyzer {
|
|||
}
|
||||
}
|
||||
|
||||
/// toggles smoothed-cadence splash injection on every processor.
|
||||
pub fn set_smooth_splashes(&mut self, on: bool) {
|
||||
for p in self
|
||||
.main
|
||||
.iter_mut()
|
||||
.chain(self.transient.iter_mut())
|
||||
.chain(self.deep.iter_mut())
|
||||
{
|
||||
p.set_smooth_splashes(on);
|
||||
}
|
||||
}
|
||||
|
||||
/// advances one hop of analytic-signal data at the requested normalised playhead and publishes a stereo frame pair.
|
||||
pub fn step(&mut self, position: f64) -> Option<&[FrameData]> {
|
||||
let total = self.total_samples();
|
||||
|
|
@ -508,6 +522,7 @@ impl Analyzer {
|
|||
db: spec_main.db,
|
||||
primary_db,
|
||||
cepstrum,
|
||||
activity: std::mem::take(&mut spec_main.activity),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ enum Cmd {
|
|||
SetGpuBlend(f32),
|
||||
SetSmoothingTilt(f32),
|
||||
SetSmoothingStrength(f32),
|
||||
SetSmoothSplashes(bool),
|
||||
SetNoiseGate(f32),
|
||||
SetMode(AnalyzerMode),
|
||||
PushLivePcm { samples: Vec<f32>, sample_rate: u32, channels: u32 },
|
||||
|
|
@ -122,6 +123,11 @@ impl AnalyzerWorker {
|
|||
let _ = self.cmd_tx.send(Cmd::SetSmoothingStrength(strength));
|
||||
}
|
||||
|
||||
/// queues a smoothed-splash toggle.
|
||||
pub fn set_smooth_splashes(&self, on: bool) {
|
||||
let _ = self.cmd_tx.send(Cmd::SetSmoothSplashes(on));
|
||||
}
|
||||
|
||||
/// queues a noise-gate threshold change in dB.
|
||||
pub fn set_noise_gate(&self, db: f32) {
|
||||
let _ = self.cmd_tx.send(Cmd::SetNoiseGate(db));
|
||||
|
|
@ -267,6 +273,7 @@ fn apply(analyzer: &mut Analyzer, cmd: Cmd) {
|
|||
Cmd::SetGpuBlend(b) => analyzer.set_gpu_blend(b),
|
||||
Cmd::SetSmoothingTilt(t) => analyzer.set_smoothing_tilt(t as f64),
|
||||
Cmd::SetSmoothingStrength(s) => analyzer.set_smoothing_strength(s as f64),
|
||||
Cmd::SetSmoothSplashes(on) => analyzer.set_smooth_splashes(on),
|
||||
Cmd::SetNoiseGate(db) => analyzer.set_noise_gate(db),
|
||||
Cmd::SetMode(_) | Cmd::PushLivePcm { .. } => {}
|
||||
Cmd::Shutdown => {}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ pub struct Spectrum {
|
|||
pub db: Vec<f32>,
|
||||
|
||||
pub cepstrum: Vec<f32>,
|
||||
|
||||
pub activity: Vec<f32>,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -44,9 +46,13 @@ pub struct Processor {
|
|||
|
||||
bin_alphas: Vec<f64>,
|
||||
bin_smoothed: Vec<f64>,
|
||||
bin_activity: Vec<f64>,
|
||||
bin_reservoir: Vec<f64>,
|
||||
bin_prev_delta: Vec<f64>,
|
||||
bin_initialized: bool,
|
||||
smoothing_tilt: f64,
|
||||
smoothing_strength: f64,
|
||||
smooth_splashes: bool,
|
||||
|
||||
history: VecDeque<Vec<f64>>,
|
||||
smoothing_length: usize,
|
||||
|
|
@ -83,9 +89,13 @@ impl Processor {
|
|||
freqs_const: Vec::new(),
|
||||
bin_alphas: Vec::new(),
|
||||
bin_smoothed: Vec::new(),
|
||||
bin_activity: Vec::new(),
|
||||
bin_reservoir: Vec::new(),
|
||||
bin_prev_delta: Vec::new(),
|
||||
bin_initialized: false,
|
||||
smoothing_tilt: DEFAULT_SMOOTHING_TILT,
|
||||
smoothing_strength: DEFAULT_SMOOTHING_STRENGTH,
|
||||
smooth_splashes: true,
|
||||
num_bins: 26,
|
||||
history: VecDeque::new(),
|
||||
smoothing_length: 3,
|
||||
|
|
@ -119,6 +129,11 @@ impl Processor {
|
|||
self.rebuild_smoothing_alphas();
|
||||
}
|
||||
|
||||
/// toggles splash injection between the smoothed-envelope-peak cadence and the raw transient cadence.
|
||||
pub fn set_smooth_splashes(&mut self, on: bool) {
|
||||
self.smooth_splashes = on;
|
||||
}
|
||||
|
||||
/// caps the rolling history depth of the per-bin db time-average.
|
||||
pub fn set_smoothing(&mut self, history_length: usize) {
|
||||
self.smoothing_length = history_length.max(1);
|
||||
|
|
@ -181,7 +196,11 @@ impl Processor {
|
|||
}
|
||||
|
||||
self.rebuild_smoothing_alphas();
|
||||
self.bin_smoothed = vec![-100.0; self.freqs_const.len()];
|
||||
let cols = self.freqs_const.len();
|
||||
self.bin_smoothed = vec![-100.0; cols];
|
||||
self.bin_activity = vec![0.0; cols];
|
||||
self.bin_reservoir = vec![0.0; cols];
|
||||
self.bin_prev_delta = vec![0.0; cols];
|
||||
self.bin_initialized = false;
|
||||
}
|
||||
|
||||
|
|
@ -339,11 +358,15 @@ impl Processor {
|
|||
.map(|i| (cep_buf[i].re * cep_scale) as f32)
|
||||
.collect();
|
||||
|
||||
if self.bin_smoothed.len() != self.freqs_const.len() {
|
||||
self.bin_smoothed = vec![-100.0; self.freqs_const.len()];
|
||||
let cols = self.freqs_const.len();
|
||||
if self.bin_smoothed.len() != cols {
|
||||
self.bin_smoothed = vec![-100.0; cols];
|
||||
self.bin_activity = vec![0.0; cols];
|
||||
self.bin_reservoir = vec![0.0; cols];
|
||||
self.bin_prev_delta = vec![0.0; cols];
|
||||
self.bin_initialized = false;
|
||||
}
|
||||
let mut current_db = vec![0.0_f64; self.freqs_const.len()];
|
||||
let mut current_db = vec![0.0_f64; cols];
|
||||
for (i, &target) in self.sample_freqs.iter().enumerate() {
|
||||
let mag = lerp_at(&freqs_full, &mag_full, target);
|
||||
let raw = 20.0 * mag.max(1e-12).log10();
|
||||
|
|
@ -353,6 +376,24 @@ impl Processor {
|
|||
let a = self.bin_alphas.get(i).copied().unwrap_or(1.0);
|
||||
self.bin_smoothed[i] + a * (raw - self.bin_smoothed[i])
|
||||
};
|
||||
// suppressed-transient energy as the positive residual between raw and the smoothed display.
|
||||
let residual = (raw - smoothed).max(0.0);
|
||||
let norm = (residual / ACTIVITY_REF_DB).clamp(0.0, 1.0);
|
||||
let delta = smoothed - self.bin_smoothed[i];
|
||||
if self.smooth_splashes {
|
||||
self.bin_reservoir[i] += norm;
|
||||
let peaked = self.bin_prev_delta[i] > 0.0 && delta <= 0.0;
|
||||
if peaked {
|
||||
let dump = (self.bin_reservoir[i] * ACTIVITY_RESERVOIR_SCALE).clamp(0.0, 1.0);
|
||||
self.bin_activity[i] = dump.max(self.bin_activity[i]);
|
||||
self.bin_reservoir[i] = 0.0;
|
||||
} else {
|
||||
self.bin_activity[i] *= ACTIVITY_DECAY;
|
||||
}
|
||||
} else {
|
||||
self.bin_activity[i] = norm.max(self.bin_activity[i] * ACTIVITY_DECAY);
|
||||
}
|
||||
self.bin_prev_delta[i] = delta;
|
||||
self.bin_smoothed[i] = smoothed;
|
||||
let mut val = smoothed;
|
||||
if (self.expand_ratio - 1.0).abs() > f32::EPSILON {
|
||||
|
|
@ -366,6 +407,7 @@ impl Processor {
|
|||
current_db[i] = val;
|
||||
}
|
||||
self.bin_initialized = true;
|
||||
let activity_ret: Vec<f32> = self.bin_activity.iter().map(|&a| a as f32).collect();
|
||||
|
||||
self.history.push_back(current_db);
|
||||
while self.history.len() > self.smoothing_length {
|
||||
|
|
@ -391,6 +433,7 @@ impl Processor {
|
|||
freqs: freqs_ret,
|
||||
db: averaged,
|
||||
cepstrum,
|
||||
activity: activity_ret,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -430,6 +473,15 @@ const DEFAULT_SMOOTHING_TILT: f64 = 0.6;
|
|||
/// default per-bin smoothing mix between raw FFT and smoothed values.
|
||||
const DEFAULT_SMOOTHING_STRENGTH: f64 = 0.7;
|
||||
|
||||
/// residual dB mapping to full splash activity.
|
||||
const ACTIVITY_REF_DB: f64 = 12.0;
|
||||
|
||||
/// per-frame peak-hold decay for splash activity.
|
||||
const ACTIVITY_DECAY: f64 = 0.85;
|
||||
|
||||
/// reservoir-to-activity scale at a smoothed-envelope peak.
|
||||
const ACTIVITY_RESERVOIR_SCALE: f64 = 0.25;
|
||||
|
||||
/// builds a Hamming or Blackman-Harris analysis window at the requested size.
|
||||
fn build_window(size: usize) -> Vec<f64> {
|
||||
if size == 0 {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ pub struct App {
|
|||
|
||||
pub show_settings: bool,
|
||||
|
||||
/// expands the advanced settings group at the bottom of the panel.
|
||||
pub show_advanced: bool,
|
||||
|
||||
/// shell-side picker request flag drained by the iOS host once per tick.
|
||||
pub pending_pick: u8,
|
||||
|
||||
|
|
@ -173,6 +176,14 @@ pub struct Settings {
|
|||
#[serde(default = "default_smoothing_strength")]
|
||||
pub smoothing_strength: f32,
|
||||
|
||||
/// fires color splashes at the smoothed-envelope cadence rather than the raw transient cadence.
|
||||
#[serde(default = "default_smooth_splashes")]
|
||||
pub smooth_splashes: bool,
|
||||
|
||||
/// intensity multiplier on the suppressed-energy color splash.
|
||||
#[serde(default = "default_splash")]
|
||||
pub splash: f32,
|
||||
|
||||
/// dB cutoff for the live-mode noise gate.
|
||||
#[serde(default = "default_noise_gate_db")]
|
||||
pub noise_gate_db: f32,
|
||||
|
|
@ -193,6 +204,16 @@ fn default_smoothing_strength() -> f32 {
|
|||
0.7
|
||||
}
|
||||
|
||||
/// default smoothed-splash toggle.
|
||||
fn default_smooth_splashes() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// default splash intensity.
|
||||
fn default_splash() -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
@ -214,6 +235,8 @@ impl Default for Settings {
|
|||
gpu_blend: 0.7,
|
||||
smoothing_tilt: default_smoothing_tilt(),
|
||||
smoothing_strength: default_smoothing_strength(),
|
||||
smooth_splashes: default_smooth_splashes(),
|
||||
splash: default_splash(),
|
||||
noise_gate_db: default_noise_gate_db(),
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +263,7 @@ pub enum Message {
|
|||
ToggleImmersive,
|
||||
ToggleChrome,
|
||||
ToggleSettings,
|
||||
ToggleAdvanced,
|
||||
SetPlaybackMode(PlaybackMode),
|
||||
OpenLocalMode,
|
||||
OpenCaptureMode,
|
||||
|
|
@ -267,6 +291,8 @@ pub enum Message {
|
|||
SetGpuBlend(f32),
|
||||
SetSmoothingTilt(f32),
|
||||
SetSmoothingStrength(f32),
|
||||
SetSmoothSplashes(bool),
|
||||
SetSplash(f32),
|
||||
SetNoiseGate(f32),
|
||||
SetOutputDevice(Option<String>),
|
||||
SetInputDevice(Option<String>),
|
||||
|
|
@ -326,6 +352,7 @@ impl App {
|
|||
worker.set_gpu_blend(settings.gpu_blend);
|
||||
worker.set_smoothing_tilt(settings.smoothing_tilt);
|
||||
worker.set_smoothing_strength(settings.smoothing_strength);
|
||||
worker.set_smooth_splashes(settings.smooth_splashes);
|
||||
worker.set_noise_gate(settings.noise_gate_db);
|
||||
let library_worker = LibraryWorker::spawn();
|
||||
|
||||
|
|
@ -353,6 +380,7 @@ impl App {
|
|||
settings,
|
||||
settings_inactive,
|
||||
show_settings: false,
|
||||
show_advanced: false,
|
||||
pending_pick: 0,
|
||||
pending_capture_action: 0,
|
||||
pending_pip_request: false,
|
||||
|
|
@ -409,6 +437,7 @@ impl App {
|
|||
self.worker.set_gpu_blend(self.settings.gpu_blend);
|
||||
self.worker.set_smoothing_tilt(self.settings.smoothing_tilt);
|
||||
self.worker.set_smoothing_strength(self.settings.smoothing_strength);
|
||||
self.worker.set_smooth_splashes(self.settings.smooth_splashes);
|
||||
self.worker.set_noise_gate(self.settings.noise_gate_db);
|
||||
}
|
||||
|
||||
|
|
@ -908,6 +937,8 @@ impl App {
|
|||
| Message::SetGpuBlend(_)
|
||||
| Message::SetSmoothingTilt(_)
|
||||
| Message::SetSmoothingStrength(_)
|
||||
| Message::SetSmoothSplashes(_)
|
||||
| Message::SetSplash(_)
|
||||
| Message::SetNoiseGate(_)
|
||||
| Message::ResetSettings,
|
||||
);
|
||||
|
|
@ -1024,6 +1055,9 @@ impl App {
|
|||
self.restore_settings_scroll = true;
|
||||
}
|
||||
}
|
||||
Message::ToggleAdvanced => {
|
||||
self.show_advanced = !self.show_advanced;
|
||||
}
|
||||
Message::SetPlaybackMode(mode) => self.set_playback_mode(mode),
|
||||
Message::OpenLocalMode => {
|
||||
self.set_playback_mode(PlaybackMode::Local);
|
||||
|
|
@ -1102,6 +1136,13 @@ impl App {
|
|||
self.settings.smoothing_strength = v.clamp(0.0, 1.0);
|
||||
self.worker.set_smoothing_strength(self.settings.smoothing_strength);
|
||||
}
|
||||
Message::SetSmoothSplashes(on) => {
|
||||
self.settings.smooth_splashes = on;
|
||||
self.worker.set_smooth_splashes(on);
|
||||
}
|
||||
Message::SetSplash(v) => {
|
||||
self.settings.splash = v.clamp(0.0, 8.0);
|
||||
}
|
||||
Message::SetNoiseGate(v) => {
|
||||
self.settings.noise_gate_db = v.clamp(-100.0, 0.0);
|
||||
self.worker.set_noise_gate(self.settings.noise_gate_db);
|
||||
|
|
|
|||
172
src/ui/player.rs
172
src/ui/player.rs
|
|
@ -406,6 +406,7 @@ fn params_from(s: &super::app::Settings) -> VizParams {
|
|||
hue: s.hue,
|
||||
contrast: s.contrast,
|
||||
brightness: s.brightness,
|
||||
splash: s.splash,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -431,13 +432,13 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
let body = column![
|
||||
Space::new().height(Length::Fixed(TOP_BAR_H + 4.0)),
|
||||
header,
|
||||
Space::new().height(Length::Fixed(10.0)),
|
||||
Space::new().height(Length::Fixed(12.0)),
|
||||
section_label("style"),
|
||||
toggle_row("glass", s.glass, Message::SetGlass),
|
||||
toggle_row("album colors", s.album_colors, Message::SetAlbumColors),
|
||||
toggle_row("mirrored", s.mirrored, Message::SetMirrored),
|
||||
toggle_row("inverted", s.inverted, Message::SetInverted),
|
||||
Space::new().height(Length::Fixed(8.0)),
|
||||
Space::new().height(Length::Fixed(12.0)),
|
||||
section_label("color"),
|
||||
slider_row(
|
||||
"hue",
|
||||
|
|
@ -463,18 +464,16 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
format!("{:.2}", s.brightness),
|
||||
Message::SetBrightness,
|
||||
),
|
||||
Space::new().height(Length::Fixed(8.0)),
|
||||
section_label("entropy filter"),
|
||||
toggle_row("enabled", s.entropy_on, Message::SetEntropy),
|
||||
slider_row(
|
||||
"strength",
|
||||
s.entropy_strength,
|
||||
-1.5..=1.5,
|
||||
0.05,
|
||||
format!("{:+.2}", s.entropy_strength),
|
||||
Message::SetEntropyStrength,
|
||||
"splash",
|
||||
s.splash,
|
||||
0.0..=8.0,
|
||||
0.1,
|
||||
format!("{:.1}", s.splash),
|
||||
Message::SetSplash,
|
||||
),
|
||||
Space::new().height(Length::Fixed(8.0)),
|
||||
toggle_row("smooth splashes", s.smooth_splashes, Message::SetSmoothSplashes),
|
||||
Space::new().height(Length::Fixed(12.0)),
|
||||
section_label("dsp"),
|
||||
slider_row(
|
||||
"bins",
|
||||
|
|
@ -484,7 +483,6 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
format!("{}", s.num_bins),
|
||||
|v| Message::SetNumBins(v as u32),
|
||||
),
|
||||
|
||||
pow2_slider_row("fft", s.fft, 9, 16, Message::SetFft),
|
||||
pow2_slider_row(
|
||||
"hop",
|
||||
|
|
@ -509,43 +507,7 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
format!("{:.2}", s.smoothing_strength),
|
||||
Message::SetSmoothingStrength,
|
||||
),
|
||||
Space::new().height(Length::Fixed(8.0)),
|
||||
section_label("cepstral smoothing"),
|
||||
slider_row(
|
||||
"granularity",
|
||||
s.granularity as f32,
|
||||
1.0..=100.0,
|
||||
1.0,
|
||||
format!("{}", s.granularity),
|
||||
|v| Message::SetGranularity(v as i32),
|
||||
),
|
||||
slider_row(
|
||||
"detail",
|
||||
s.detail as f32,
|
||||
1.0..=100.0,
|
||||
1.0,
|
||||
format!("{}", s.detail),
|
||||
|v| Message::SetDetail(v as i32),
|
||||
),
|
||||
slider_row(
|
||||
"strength",
|
||||
s.strength,
|
||||
0.0..=1.0,
|
||||
0.01,
|
||||
format!("{:.2}", s.strength),
|
||||
Message::SetStrength,
|
||||
),
|
||||
Space::new().height(Length::Fixed(8.0)),
|
||||
section_label("fft engine blend"),
|
||||
slider_row(
|
||||
"cpu ↔ gpu",
|
||||
s.gpu_blend,
|
||||
0.0..=1.0,
|
||||
0.01,
|
||||
format!("{:.2}", s.gpu_blend),
|
||||
Message::SetGpuBlend,
|
||||
),
|
||||
Space::new().height(Length::Fixed(8.0)),
|
||||
Space::new().height(Length::Fixed(12.0)),
|
||||
section_label("capture noise gate"),
|
||||
slider_row(
|
||||
"threshold",
|
||||
|
|
@ -556,13 +518,13 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
Message::SetNoiseGate,
|
||||
),
|
||||
]
|
||||
.spacing(8)
|
||||
.spacing(10)
|
||||
.padding(Padding::from(16))
|
||||
.width(Length::Fixed(SETTINGS_W));
|
||||
|
||||
#[cfg(all(not(target_os = "ios"), not(target_os = "android")))]
|
||||
let body = body
|
||||
.push(Space::new().height(Length::Fixed(8.0)))
|
||||
.push(Space::new().height(Length::Fixed(12.0)))
|
||||
.push(section_label("audio devices"))
|
||||
.push(device_row("output", &app.output_devices, app.output_device.as_deref(), Message::SetOutputDevice))
|
||||
.push(device_row("input", &app.input_devices, app.input_device.as_deref(), Message::SetInputDevice))
|
||||
|
|
@ -571,6 +533,59 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
chip_button("Refresh devices", Message::RefreshDeviceList),
|
||||
].padding(Padding::from([4, 0])));
|
||||
|
||||
let mut body = body
|
||||
.push(Space::new().height(Length::Fixed(14.0)))
|
||||
.push(advanced_header(app.show_advanced));
|
||||
if app.show_advanced {
|
||||
body = body
|
||||
.push(section_label("entropy filter"))
|
||||
.push(toggle_row("enabled", s.entropy_on, Message::SetEntropy))
|
||||
.push(slider_row(
|
||||
"strength",
|
||||
s.entropy_strength,
|
||||
-1.5..=1.5,
|
||||
0.05,
|
||||
format!("{:+.2}", s.entropy_strength),
|
||||
Message::SetEntropyStrength,
|
||||
))
|
||||
.push(Space::new().height(Length::Fixed(10.0)))
|
||||
.push(section_label("cepstral smoothing"))
|
||||
.push(slider_row(
|
||||
"granularity",
|
||||
s.granularity as f32,
|
||||
1.0..=100.0,
|
||||
1.0,
|
||||
format!("{}", s.granularity),
|
||||
|v| Message::SetGranularity(v as i32),
|
||||
))
|
||||
.push(slider_row(
|
||||
"detail",
|
||||
s.detail as f32,
|
||||
1.0..=100.0,
|
||||
1.0,
|
||||
format!("{}", s.detail),
|
||||
|v| Message::SetDetail(v as i32),
|
||||
))
|
||||
.push(slider_row(
|
||||
"strength",
|
||||
s.strength,
|
||||
0.0..=1.0,
|
||||
0.01,
|
||||
format!("{:.2}", s.strength),
|
||||
Message::SetStrength,
|
||||
))
|
||||
.push(Space::new().height(Length::Fixed(10.0)))
|
||||
.push(section_label("fft engine blend"))
|
||||
.push(slider_row(
|
||||
"cpu / gpu",
|
||||
s.gpu_blend,
|
||||
0.0..=1.0,
|
||||
0.01,
|
||||
format!("{:.2}", s.gpu_blend),
|
||||
Message::SetGpuBlend,
|
||||
));
|
||||
}
|
||||
|
||||
let scroll = scrollable(body)
|
||||
.id(settings_scroll_id())
|
||||
.on_scroll(|vp| Message::SettingsScrolled(vp.absolute_offset()))
|
||||
|
|
@ -610,16 +625,16 @@ fn slider_row<'a, F>(
|
|||
where
|
||||
F: 'a + Fn(f32) -> Message,
|
||||
{
|
||||
let label_w = 110.0;
|
||||
let value_w = 60.0;
|
||||
let label_w = 120.0;
|
||||
let value_w = 64.0;
|
||||
container(
|
||||
row![
|
||||
container(text(label).size(13).color(palette::text_dim()))
|
||||
container(text(label).size(16).color(palette::text_dim()))
|
||||
.width(Length::Fixed(label_w)),
|
||||
slider(range, value, on_change).step(step).width(Length::Fill).height(28.0),
|
||||
slider(range, value, on_change).step(step).width(Length::Fill).height(38.0),
|
||||
container(
|
||||
text(value_text)
|
||||
.size(13)
|
||||
.size(16)
|
||||
.color(palette::text())
|
||||
.align_x(iced_wgpu::core::alignment::Horizontal::Right)
|
||||
)
|
||||
|
|
@ -629,7 +644,7 @@ where
|
|||
.spacing(12)
|
||||
.align_y(iced_wgpu::core::Alignment::Center)
|
||||
)
|
||||
.height(Length::Fixed(44.0))
|
||||
.height(Length::Fixed(54.0))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -729,24 +744,55 @@ where
|
|||
{
|
||||
container(
|
||||
row![
|
||||
container(text(label).size(13).color(palette::text_dim()))
|
||||
.width(Length::Fixed(110.0)),
|
||||
checkbox(value).on_toggle(on_change).size(26),
|
||||
container(text(label).size(16).color(palette::text_dim()))
|
||||
.width(Length::Fixed(120.0)),
|
||||
checkbox(value).on_toggle(on_change).size(32),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_y(iced_wgpu::core::Alignment::Center)
|
||||
)
|
||||
.height(Length::Fixed(40.0))
|
||||
.height(Length::Fixed(50.0))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// dim small-caps heading separating groups of settings rows.
|
||||
fn section_label<'a>(label: &'a str) -> Element<'a, Message, Theme, iced_wgpu::Renderer> {
|
||||
container(text(label).size(10).color(palette::text_dim()))
|
||||
container(text(label).size(12).color(palette::text_dim()))
|
||||
.padding(Padding::from([0, 0]))
|
||||
.into()
|
||||
}
|
||||
|
||||
/// full-width toggle button heading the collapsible advanced settings group.
|
||||
fn advanced_header<'a>(expanded: bool) -> Element<'a, Message, Theme, iced_wgpu::Renderer> {
|
||||
let caret = if expanded { "v" } else { ">" };
|
||||
let inner = row![
|
||||
text("advanced").size(15).color(palette::text()),
|
||||
Space::new().width(Length::Fill),
|
||||
text(caret).size(13).color(palette::text_dim()),
|
||||
]
|
||||
.align_y(iced_wgpu::core::Alignment::Center)
|
||||
.padding(Padding::from([10, 12]));
|
||||
|
||||
iced_widget::button(inner)
|
||||
.padding(0)
|
||||
.width(Length::Fill)
|
||||
.on_press(Message::ToggleAdvanced)
|
||||
.style(|_t: &Theme, status: ButtonStatus| {
|
||||
let a = if matches!(status, ButtonStatus::Hovered) { 0.10 } else { 0.05 };
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color { a, ..palette::text() })),
|
||||
text_color: palette::text(),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 8.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
/// translucent backdrop styling for the settings overlay.
|
||||
fn settings_panel_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
|
|
|
|||
|
|
@ -657,6 +657,7 @@ fn pip_viz_params(s: &crate::ui::app::Settings) -> crate::visualizer::VizParams
|
|||
hue: s.hue,
|
||||
contrast: s.contrast,
|
||||
brightness: s.brightness,
|
||||
splash: s.splash,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub struct VizParams {
|
|||
pub hue: f32,
|
||||
pub contrast: f32,
|
||||
pub brightness: f32,
|
||||
pub splash: f32,
|
||||
}
|
||||
|
||||
impl Default for VizParams {
|
||||
|
|
@ -41,6 +42,7 @@ impl Default for VizParams {
|
|||
hue: 0.9,
|
||||
contrast: 1.0,
|
||||
brightness: 1.0,
|
||||
splash: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ pub struct BinGpu {
|
|||
pub hue: f32,
|
||||
pub sat: f32,
|
||||
pub val: f32,
|
||||
pub splash: f32,
|
||||
}
|
||||
|
||||
/// uniform block holding viewport size, layout counts, render flags, and the unified glass color.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ use crate::visualizer::VizParams;
|
|||
const HUE_HISTORY_LEN: usize = 40;
|
||||
const HISTORY_LEN: usize = 30;
|
||||
|
||||
/// splash-to-jump response gain, reaching the full offset on a moderate splash.
|
||||
const SPLASH_GAIN: f32 = 4.0;
|
||||
|
||||
/// per-bin smoothed magnitude, modulation offsets, palette color, and recent visual history.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct BinState {
|
||||
|
|
@ -19,6 +22,7 @@ pub struct BinState {
|
|||
pub bright_mod: f32,
|
||||
pub alpha_mod: f32,
|
||||
pub cached_color: [f32; 3],
|
||||
pub splash: f32,
|
||||
pub history: VecDeque<f32>,
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +112,7 @@ impl VisState {
|
|||
hue: b.cached_color[0],
|
||||
sat: b.cached_color[1],
|
||||
val: b.cached_color[2],
|
||||
splash: b.splash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -309,6 +314,24 @@ fn ingest_channel(
|
|||
}
|
||||
}
|
||||
|
||||
// spreads each bin's suppressed-energy activity into a 2-bin-wide paint splash via a 0.5/1.0/0.5 kernel.
|
||||
let mut splash = vec![0.0_f32; n];
|
||||
if frame.activity.len() == n {
|
||||
for i in 0..n {
|
||||
let a = frame.activity[i];
|
||||
if a <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
splash[i] += a;
|
||||
if i > 0 {
|
||||
splash[i - 1] += a * 0.5;
|
||||
}
|
||||
if i + 1 < n {
|
||||
splash[i + 1] += a * 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let use_palette = params.album_colors
|
||||
&& palette
|
||||
.map(|p| !p.is_empty())
|
||||
|
|
@ -339,6 +362,8 @@ fn ingest_channel(
|
|||
}
|
||||
b.cached_color = [hue, 1.0, 1.0];
|
||||
}
|
||||
// per-bin splash amount, applied as a hue/sat/value shift on the gpu for both fills and lines.
|
||||
b.splash = (splash[i] * SPLASH_GAIN * params.splash).min(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue