feat(anvil): add Ghost harmonics and Phone listen
Ghost adds the sub's harmonics 2–10 above Ghost from, so phones hear the note. The band above the split is measured per harmonic and only missing harmonics are filled, so added ones cannot cancel the patch's own. The sub level does not change. Phone monitors the output in mono, 400 Hz–7 kHz.
This commit is contained in:
1 parent
4f1e237d66
commit
e7f3346077
12 files changed
+489
-50
No files matched your search
@@ -13,10 +13,11 @@ use std::sync::{
|
||||
struct Shared {
|
||||
analyzer: ArrayQueue<AnalyzerFrame>,
|
||||
solo: AtomicBool,
|
||||
phone: AtomicBool,
|
||||
}
|
||||
struct EditorData {
|
||||
ui: anvil_ui::State,
|
||||
gestures: [bool; 9],
|
||||
gestures: [bool; 11],
|
||||
}
|
||||
pub struct Anvil {
|
||||
params: Arc<AnvilParams>,
|
||||
@@ -32,6 +33,7 @@ impl Default for Anvil {
|
||||
shared: Arc::new(Shared {
|
||||
analyzer: ArrayQueue::new(2),
|
||||
solo: AtomicBool::new(false),
|
||||
phone: AtomicBool::new(false),
|
||||
}),
|
||||
engine: None,
|
||||
publish_count: 0,
|
||||
@@ -71,10 +73,11 @@ impl Plugin for Anvil {
|
||||
params.editor.clone(),
|
||||
EditorData {
|
||||
ui: anvil_ui::State::default(),
|
||||
gestures: [false; 9],
|
||||
gestures: [false; 11],
|
||||
},
|
||||
|ctx, data| {
|
||||
data.ui.solo = false;
|
||||
data.ui.phone = false;
|
||||
anvil_ui::style(ctx);
|
||||
},
|
||||
move |ctx, setter, data| {
|
||||
@@ -98,8 +101,10 @@ impl Plugin for Anvil {
|
||||
data.ui.gesture_active,
|
||||
&mut data.gestures,
|
||||
);
|
||||
// Solo is a monitor only: never saved and never left on after the editor closes.
|
||||
// Solo and phone listen are monitors only: never saved and never left on
|
||||
// after the editor closes.
|
||||
shared.solo.store(data.ui.solo, Ordering::Release);
|
||||
shared.phone.store(data.ui.phone, Ordering::Release);
|
||||
if data.ui.analyzer.note.is_some() {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
@@ -125,6 +130,7 @@ impl Plugin for Anvil {
|
||||
self.publish_interval = (config.sample_rate / 30.) as usize;
|
||||
self.engine = Some(engine);
|
||||
self.shared.solo.store(false, Ordering::Release);
|
||||
self.shared.phone.store(false, Ordering::Release);
|
||||
true
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
@@ -143,7 +149,11 @@ impl Plugin for Anvil {
|
||||
return ProcessStatus::Normal;
|
||||
};
|
||||
let _ = engine.set_settings(self.params.settings());
|
||||
let solo = self.shared.solo.load(Ordering::Acquire) && self.params.editor.is_open();
|
||||
let open = self.params.editor.is_open();
|
||||
let monitor = Monitor {
|
||||
solo: self.shared.solo.load(Ordering::Acquire) && open,
|
||||
phone: self.shared.phone.load(Ordering::Acquire) && open,
|
||||
};
|
||||
let mut next = context.next_event();
|
||||
for (index, mut sample) in buffer.iter_samples().enumerate() {
|
||||
while let Some(event) = next {
|
||||
@@ -170,7 +180,7 @@ impl Plugin for Anvil {
|
||||
0.
|
||||
},
|
||||
];
|
||||
let out = engine.process_sample(x, Monitor { solo });
|
||||
let out = engine.process_sample(x, monitor);
|
||||
for (c, value) in sample.into_iter().enumerate() {
|
||||
*value = out[c];
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ pub struct AnvilParams {
|
||||
pub dynamics: FloatParam,
|
||||
#[id = "drive"]
|
||||
pub drive: FloatParam,
|
||||
#[id = "ghost"]
|
||||
pub ghost: FloatParam,
|
||||
#[id = "ghost_from"]
|
||||
pub ghost_from: FloatParam,
|
||||
#[id = "octave"]
|
||||
pub octave: IntParam,
|
||||
#[id = "glide"]
|
||||
@@ -66,6 +70,17 @@ impl Default for AnvilParams {
|
||||
.with_unit(" Hz"),
|
||||
dynamics: linear("Dynamics", d.dynamics, 0., 100., " %"),
|
||||
drive: linear("Drive", d.drive, 0., 100., " %"),
|
||||
ghost: linear("Ghost", d.ghost, 0., 100., " %"),
|
||||
ghost_from: FloatParam::new(
|
||||
"Ghost from",
|
||||
d.ghost_from,
|
||||
FloatRange::Skewed {
|
||||
min: 150.,
|
||||
max: 600.,
|
||||
factor: FloatRange::skew_factor(-1.),
|
||||
},
|
||||
)
|
||||
.with_unit(" Hz"),
|
||||
octave: IntParam::new("Octave", d.octave, IntRange::Linear { min: -2, max: 1 }),
|
||||
glide: linear("Glide", d.glide, 0., 200., " ms"),
|
||||
phase: linear("Phase", d.phase, 0., 360., "°"),
|
||||
@@ -89,6 +104,8 @@ impl AnvilParams {
|
||||
split: self.split.value(),
|
||||
dynamics: self.dynamics.value(),
|
||||
drive: self.drive.value(),
|
||||
ghost: self.ghost.value(),
|
||||
ghost_from: self.ghost_from.value(),
|
||||
octave: self.octave.value(),
|
||||
glide: self.glide.value(),
|
||||
phase: self.phase.value(),
|
||||
@@ -108,9 +125,9 @@ impl AnvilParams {
|
||||
old: &Settings,
|
||||
new: &Settings,
|
||||
dragging: bool,
|
||||
open: &mut [bool; 9],
|
||||
open: &mut [bool; 11],
|
||||
) {
|
||||
let floats: [(&FloatParam, f32, f32); 8] = [
|
||||
let floats: [(&FloatParam, f32, f32); 10] = [
|
||||
(&self.level, old.level, new.level),
|
||||
(&self.split, old.split, new.split),
|
||||
(&self.dynamics, old.dynamics, new.dynamics),
|
||||
@@ -119,6 +136,8 @@ impl AnvilParams {
|
||||
(&self.phase, old.phase, new.phase),
|
||||
(&self.gate, old.gate, new.gate),
|
||||
(&self.keep, old.keep, new.keep),
|
||||
(&self.ghost, old.ghost, new.ghost),
|
||||
(&self.ghost_from, old.ghost_from, new.ghost_from),
|
||||
];
|
||||
let mut continuous = |i: usize, changed: bool, set: &dyn Fn()| {
|
||||
if changed {
|
||||
@@ -136,7 +155,7 @@ impl AnvilParams {
|
||||
for (i, (p, a, b)) in floats.into_iter().enumerate() {
|
||||
continuous(i, a != b, &|| setter.set_parameter(p, b));
|
||||
}
|
||||
continuous(8, old.output != new.output, &|| {
|
||||
continuous(10, old.output != new.output, &|| {
|
||||
setter.set_parameter(&self.output, new.output)
|
||||
});
|
||||
let once = |changed: bool, f: &dyn Fn()| {
|
||||
@@ -180,6 +199,8 @@ impl AnvilParams {
|
||||
&self.phase,
|
||||
&self.gate,
|
||||
&self.keep,
|
||||
&self.ghost,
|
||||
&self.ghost_from,
|
||||
&self.output,
|
||||
][i]
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user