Skip to content
Snippets Groups Projects
Commit 9d6cdb15 authored by Per Lindgren's avatar Per Lindgren
Browse files

exponential A in ADSR

parent 20e74727
Branches
No related tags found
No related merge requests found
// Copyright 2019 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! An example of an animating widget.
use druid::kurbo::BezPath;
use druid::widget::{Flex, Label, WidgetExt};
use druid::{
AppLauncher, BoxConstraints, Color, Data, Env, Event, EventCtx, LayoutCtx, Lens, PaintCtx,
PlatformError, Point, RenderContext, Size, UpdateCtx, Widget, WindowDesc,
};
use druid_widgets::Dial;
use log::info;
#[derive(Clone, Data, Lens, Default)]
struct DialLabel {
value: f64,
}
impl DialLabel {
fn new(text: &str) -> impl Widget<DialLabel> {
let text = text.to_string();
let label =
Label::new(move |data: &DialLabel, _env: &_| format!("{} {:.3}", text, data.value));
let solid = Color::rgb8(0x3a, 0x3a, 0x3a);
let mut col = Flex::column();
col.add_child(
Dial::new(Size::new(100.0, 100.0))
.lens(DialLabel::value)
.background(solid.clone())
.padding(5.0),
0.0,
);
col.add_child(label.padding(5.0).background(solid.clone()), 0.0);
col
}
}
#[derive(Clone, Data, Lens, Default)]
struct Envelope {
attack: DialLabel,
tilt: DialLabel,
decay: DialLabel,
sustain: DialLabel,
release: DialLabel,
}
impl Envelope {
fn new() -> impl Widget<Envelope> {
let solid = Color::rgb8(0x3a, 0x3a, 0x3a);
Flex::row()
.with_child(
DialLabel::new("Attack")
.lens(Envelope::attack)
.background(solid.clone())
.padding(5.0),
1.0,
)
.with_child(
DialLabel::new("Tilt")
.lens(Envelope::tilt)
.background(solid.clone())
.padding(5.0),
1.0,
)
.with_child(
DialLabel::new("Decay")
.lens(Envelope::decay)
.background(solid.clone())
.padding(5.0),
1.0,
)
.with_child(
DialLabel::new("Sustain")
.lens(Envelope::sustain)
.background(solid.clone())
.padding(5.0),
1.0,
)
.with_child(
DialLabel::new("Release")
.lens(Envelope::release)
.background(solid.clone())
.padding(5.0),
1.0,
)
}
}
// Draw has no internal state, just reflects the associated type
#[derive(Clone, Data, Lens)]
struct Draw {}
impl Widget<Envelope> for Draw {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut Envelope, _env: &Env) {}
fn update(
&mut self,
ctx: &mut UpdateCtx,
_old_data: Option<&Envelope>,
_data: &Envelope,
_env: &Env,
) {
ctx.invalidate();
}
fn layout(
&mut self,
_layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &Envelope,
_env: &Env,
) -> Size {
// info!("layout bc {:?}", bc);
// info!("layout bc.min {:?}", bc.min());
bc.constrain((100.0, 100.0)) // this gives a firm size, which is not what I want
}
fn paint(&mut self, paint_ctx: &mut PaintCtx, data: &Envelope, _env: &Env) {
let size = paint_ctx.size();
// info!("paint : {:?}", paint_ctx.size());
let mut path = BezPath::new();
let a = data.attack.value;
let t = data.tilt.value;
let d = data.decay.value;
let s = data.sustain.value;
let r = data.release.value;
let adr_time = a + d + r;
let sustain_time = adr_time / 3.0; // adjust the sustain time
let env_time = adr_time + sustain_time;
let x_scale = size.width / env_time;
let y_scale = size.height;
// k : 0.0 ..= 1.0
fn exp_interpolate(x: f64, k: f64, y0: f64, y1: f64) -> f64 {
// we want three digits of accuracy for the parameter
let c = 20.0 * (k * 1000.0) / 1000.0;
let y = y1 - y0;
// let x
// if k == 0.0 {
// 1.0\ -\ \left(1\ -\ x\right)\ \cdot\ \exp\left(-k\ \cdot\ x\right)
// (k*(y1 - y0)/ x)
// } else if k <= 0.0 {
// unimplemented();
// } else {
// unimplemented();
// }
// match k {
// (k == 0.0) =>
// //(k < 0.0) => x * (k* (x -1.0)).exp() // convex
// }
// y = 1 - (1-x) e ^ (k(1-x)), concave
0.0
}
path.move_to(Point {
x: 0.,
y: size.height,
});
let c = ((t - 0.5) * 1000.0) as i32;
if c == 0 {
path.line_to(Point {
x: a * x_scale,
y: 0.,
});
} else if c > 0 {
let min_x = 0;
let max_x = (a * x_scale).round() as u32;
let min_y = 0.0;
let max_y = size.height;
let k = (20 * c) as f64 / 1000.0; // 0 .. 10
info!("convex {}, k {}", max_x, k);
for x in 0..max_x {
// -\ x\ \cdot\ \exp\ \left(k\ \cdot\ \left(x-1\right)\right)
let x1 = x as f64 / max_x as f64;
// info!("x1 {}", x1);
let y1 = x1 * (k * (x1 - 1.0)).exp();
path.line_to(Point {
x: x as f64,
y: (1.0 - y1) * size.height,
})
}
path.line_to(Point {
x: a * x_scale,
y: 0.,
})
} else {
let min_x = 0;
let max_x = (a * x_scale).round() as u32;
let min_y = 0.0;
let max_y = size.height;
let k = (20 * c) as f64 / 1000.0; // 0 .. 20
info!("concave {}, k {}", max_x, k);
for x in 0..max_x {
// -\ x\ \cdot\ \exp\ \left(k\ \cdot\ \left(x-1\right)\right)
let x1 = x as f64 / max_x as f64;
// info!("x1 {}", x1);
let y1 = (1.0 - x1) * (k * x1).exp();
path.line_to(Point {
x: x as f64,
y: y1 * size.height,
})
}
path.line_to(Point {
x: a * x_scale,
y: 0.,
})
}
path.line_to(Point {
x: (a + d) * x_scale,
y: size.height - s * y_scale,
});
path.line_to(Point {
x: (a + d + sustain_time) * x_scale,
y: size.height - s * y_scale,
});
path.line_to(Point {
x: size.width,
y: size.height,
});
paint_ctx.stroke(path, &Color::WHITE, 1.0);
}
}
#[derive(Clone, Data, Lens)]
struct Adsr {
envelope: Envelope,
}
impl Adsr {
pub fn new() -> impl Widget<Adsr> {
let solid = Color::rgb8(0x3a, 0x3a, 0x3a);
Flex::column()
.with_child(
Draw {}
//.expand() // this would expand to the whole window
.lens(Adsr::envelope)
.background(solid.clone())
.padding(5.0),
1.0,
)
.with_child(Envelope::new().lens(Adsr::envelope), 1.0)
// .expand() // this gives very strange and erroneous layout
}
}
fn build_app() -> impl Widget<Adsr> {
let solid = Color::rgb8(0x3a, 0x3a, 0x3a);
Flex::column()
.with_child(
Flex::row()
.with_child(Adsr::new().padding(5.0), 1.0)
//.with_child(Adsr::new().expand().padding(5.0), 0.0)
.with_child(
Label::new("top right")
.background(solid.clone())
.padding(5.0),
1.0,
),
1.0,
)
.with_child(
Flex::row()
.with_child(
Label::new("bottom left")
.background(solid.clone())
.padding(5.0),
1.0,
)
.with_child(
Label::new("bottom right")
.border(solid.clone(), 4.0)
.padding(5.0),
1.0,
),
1.0,
)
}
fn main() -> Result<(), PlatformError> {
AppLauncher::with_window(WindowDesc::new(build_app))
.use_simple_logger()
.launch(Adsr {
envelope: Envelope {
attack: DialLabel { value: 0.1 },
tilt: DialLabel { value: 0.5 },
decay: DialLabel { value: 0.2 },
sustain: DialLabel { value: 0.3 },
release: DialLabel { value: 0.4 },
},
})?;
Ok(())
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment