Skip to content

Commit 09f6818

Browse files
committed
tester UI
1 parent b77d087 commit 09f6818

File tree

8 files changed

+3657
-619
lines changed

8 files changed

+3657
-619
lines changed

Cargo.lock

Lines changed: 3365 additions & 614 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ edition = "2024"
66
[lib]
77
crate-type = ["dylib"]
88

9+
[workspace]
10+
members = ["tester"]
11+
912
[dependencies]
1013
rand = "0.9.0"
1114
paste = "1.0.15"
@@ -15,6 +18,5 @@ realfft = "3.4.0"
1518
[build-dependencies]
1619
bindgen = "0.71.1"
1720

18-
[dev-dependencies]
19-
lockfree = "0.5.1"
20-
plotters = "0.3.7"
21+
[features]
22+
fmod_bindings = []

src/fmod/dsp.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1-
use crate::raw_bindings::FMOD_DSP;
1+
use crate::raw_bindings::*;
2+
use crate::result::FmodResult;
3+
use std::ffi::c_int;
4+
use std::ptr;
25

6+
#[derive(Copy, Clone)]
37
pub struct DspInstance(pub(in crate::fmod) *mut FMOD_DSP);
8+
9+
impl DspInstance {
10+
pub fn get_float_parameter(&self, index: usize) -> FmodResult<f32> {
11+
let mut result: f32 = 0.;
12+
unsafe {
13+
FMOD_DSP_GetParameterFloat(self.0, index as c_int, &mut result, ptr::null_mut(), 0)
14+
}
15+
.ok_then(|| result)
16+
}
17+
18+
pub fn get_int_parameter(&self, index: usize) -> FmodResult<i32> {
19+
let mut result: i32 = 0;
20+
unsafe { FMOD_DSP_GetParameterInt(self.0, index as c_int, &mut result, ptr::null_mut(), 0) }
21+
.ok_then(|| result)
22+
}
23+
24+
pub fn get_bool_parameter(&self, index: usize) -> FmodResult<bool> {
25+
let mut result: FMOD_BOOL = 0;
26+
unsafe {
27+
FMOD_DSP_GetParameterBool(self.0, index as c_int, &mut result, ptr::null_mut(), 0)
28+
}
29+
.ok_then(|| result != 0)
30+
}
31+
32+
pub fn set_float_parameter(&self, index: usize, value: f32) -> FmodResult<()> {
33+
unsafe { FMOD_DSP_SetParameterFloat(self.0, index as c_int, value) }.ok_then(|| ())
34+
}
35+
36+
pub fn set_bool_parameter(&self, index: usize, value: bool) -> FmodResult<()> {
37+
unsafe { FMOD_DSP_SetParameterBool(self.0, index as c_int, if value { 1 } else { 0 }) }
38+
.ok_then(|| ())
39+
}
40+
41+
pub fn set_int_parameter(&self, index: usize, value: i32) -> FmodResult<()> {
42+
unsafe { FMOD_DSP_SetParameterInt(self.0, index as c_int, value) }.ok_then(|| ())
43+
}
44+
}

src/fmod/system.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::result::*;
77
use std::ffi::CString;
88
use std::ptr;
99

10+
#[derive(Copy, Clone)]
1011
pub struct System(*mut FMOD_SYSTEM);
1112

1213
impl System {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use effects::fantasy::Fantasy;
66
use effects::noise_reduction::NoiseReduction;
77
use effects::vocoder::Vocoder;
88

9-
#[cfg(test)]
9+
#[cfg(feature = "fmod_bindings")]
1010
pub mod fmod;
1111
#[cfg(test)]
1212
pub mod simulate;

tester/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "tester"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
scamble = { path = "..", features = ["fmod_bindings"] }
8+
vizia = { git = "https://github.com/vizia/vizia" }

tester/src/dsp_lenses.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use vizia::binding::{Lens, LensValue};
2+
use scamble::fmod::dsp::DspInstance;
3+
4+
#[derive(Copy, Clone, Debug, Hash)]
5+
pub struct FloatParamLens {
6+
idx: usize
7+
}
8+
9+
impl FloatParamLens {
10+
pub fn new(idx: usize) -> Self {
11+
Self { idx }
12+
}
13+
}
14+
15+
impl Lens for FloatParamLens {
16+
type Source = DspInstance;
17+
type Target = f32;
18+
19+
fn view<'a>(&self, source: &'a DspInstance) -> Option<LensValue<'a, f32>> {
20+
source.get_float_parameter(self.idx).ok().map(LensValue::Owned)
21+
}
22+
}
23+
24+
#[derive(Copy, Clone, Debug, Hash)]
25+
pub struct BoolParamLens {
26+
idx: usize
27+
}
28+
29+
impl BoolParamLens {
30+
pub fn new(idx: usize) -> Self {
31+
Self { idx }
32+
}
33+
}
34+
35+
impl Lens for BoolParamLens {
36+
type Source = DspInstance;
37+
type Target = bool;
38+
39+
fn view<'a>(&self, source: &'a DspInstance) -> Option<LensValue<'a, bool>> {
40+
source.get_bool_parameter(self.idx).ok().map(LensValue::Owned)
41+
}
42+
}
43+
44+
#[derive(Copy, Clone, Debug, Hash)]
45+
pub struct IntParamLens {
46+
idx: usize
47+
}
48+
49+
impl IntParamLens {
50+
pub fn new(idx: usize) -> Self {
51+
Self { idx }
52+
}
53+
}
54+
55+
impl Lens for IntParamLens {
56+
type Source = DspInstance;
57+
type Target = i32;
58+
59+
fn view<'a>(&self, source: &'a DspInstance) -> Option<LensValue<'a, i32>> {
60+
source.get_int_parameter(self.idx).ok().map(LensValue::Owned)
61+
}
62+
}

tester/src/main.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
mod dsp_lenses;
2+
3+
use scamble::dsp::{Dsp, Parameter, ParameterType, interop};
4+
use scamble::fmod::dsp::DspInstance;
5+
use scamble::fmod::system::System;
6+
use scamble::result::FmResultTrait;
7+
8+
use vizia::prelude::*;
9+
10+
type It = scamble::effects::fantasy::Fantasy;
11+
12+
#[derive(Lens)]
13+
struct AppModel {
14+
system: System,
15+
dsp: Option<DspInstance>,
16+
}
17+
18+
impl Model for AppModel {
19+
fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
20+
event.map::<AppEvent, _>(|event, _meta| match event {
21+
AppEvent::SetParamFloat(idx, v) => {
22+
if let Some(instance) = self.dsp {
23+
instance.set_float_parameter(*idx, *v).unwrap()
24+
}
25+
}
26+
AppEvent::SetParamBool(idx, v) => {
27+
if let Some(instance) = self.dsp {
28+
instance.set_bool_parameter(*idx, *v).unwrap()
29+
}
30+
}
31+
AppEvent::SetParamInt(idx, v) => {
32+
if let Some(instance) = self.dsp {
33+
instance.set_int_parameter(*idx, *v).unwrap()
34+
}
35+
}
36+
AppEvent::ToggleParamBool(idx) => {
37+
if let Some(instance) = self.dsp {
38+
instance
39+
.set_bool_parameter(*idx, !instance.get_bool_parameter(*idx).unwrap())
40+
.unwrap()
41+
}
42+
}
43+
});
44+
}
45+
}
46+
47+
enum AppEvent {
48+
SetParamFloat(usize, f32),
49+
SetParamBool(usize, bool),
50+
ToggleParamBool(usize),
51+
SetParamInt(usize, i32),
52+
}
53+
54+
fn main() -> Result<(), ApplicationError> {
55+
let system = System::create().fm_unwrap();
56+
let desc = interop::into_desc::<It>();
57+
let dsp = system.create_dsp_from_description(&desc).fm_unwrap();
58+
let sound = system.create_sound("./sound.mp3").fm_unwrap();
59+
let channel = system.play_sound(sound, None, true).fm_unwrap();
60+
channel.add_dsp(0, &dsp).fm_unwrap();
61+
channel.set_paused(false).fm_unwrap();
62+
63+
let model = AppModel {
64+
system,
65+
dsp: Some(dsp),
66+
};
67+
Application::new(|cx| {
68+
model.build(cx);
69+
view_dsp::<It>(cx);
70+
})
71+
.title("Scamble Tester")
72+
.on_idle(move |cx| system.update().unwrap())
73+
.run()
74+
}
75+
76+
fn view_dsp<D: Dsp>(cx: &mut Context) {
77+
HStack::new(cx, |cx| {
78+
for (idx, parameter) in D::parameters().iter().enumerate() {
79+
view_param(cx, idx, parameter);
80+
}
81+
})
82+
.gap(Pixels(20.))
83+
.alignment(Alignment::Center);
84+
}
85+
86+
fn view_param<D: Dsp>(cx: &mut Context, idx: usize, parameter: &Parameter<D>) {
87+
VStack::new(cx, |cx| {
88+
Label::new(cx, parameter.name);
89+
90+
match parameter.ty {
91+
ParameterType::Float {
92+
min, max, default, ..
93+
} => {
94+
Knob::new(
95+
cx,
96+
norm(default, min, max),
97+
read_float_lens(idx, default).map(move |it| norm(*it, min, max)),
98+
false,
99+
)
100+
.on_change(move |cx, value| cx.emit(AppEvent::SetParamFloat(idx, value)));
101+
102+
Label::new(cx, read_float_lens(idx, default));
103+
}
104+
ParameterType::Bool { default, names, .. } => {
105+
ToggleButton::new(
106+
cx,
107+
AppModel::dsp.map(move |dsp| {
108+
dsp.and_then(|it| it.get_bool_parameter(idx).ok())
109+
.unwrap_or(default)
110+
}),
111+
move |cx| {
112+
Label::new(
113+
cx,
114+
read_bool_lens(idx, default).map(move |it| {
115+
if let Some((on, off)) = names {
116+
if *it { on } else { off }
117+
} else {
118+
if *it { "ON" } else { "OFF" }
119+
}
120+
}),
121+
)
122+
},
123+
)
124+
.on_toggle(move |cx| {
125+
cx.emit(AppEvent::ToggleParamBool(idx));
126+
});
127+
}
128+
ParameterType::Int {
129+
min, max, default, ..
130+
} => {
131+
Slider::new(cx, read_int_lens(idx, default).map(|it| *it as f32))
132+
.range(min as f32..max as f32)
133+
.on_change(move |cx, value| {
134+
cx.emit(AppEvent::SetParamInt(idx, value as i32));
135+
});
136+
137+
Label::new(cx, read_int_lens(idx, default));
138+
}
139+
_ => {}
140+
};
141+
})
142+
.gap(Pixels(10.))
143+
.alignment(Alignment::Center);
144+
}
145+
146+
fn read_float_lens(idx: usize, default: f32) -> impl Lens<Target = f32> {
147+
AppModel::dsp.map(move |dsp| {
148+
dsp.and_then(|it| it.get_float_parameter(idx).ok())
149+
.unwrap_or(default)
150+
})
151+
}
152+
153+
fn read_bool_lens(idx: usize, default: bool) -> impl Lens<Target = bool> {
154+
AppModel::dsp.map(move |dsp| {
155+
dsp.and_then(|it| it.get_bool_parameter(idx).ok())
156+
.unwrap_or(default)
157+
})
158+
}
159+
160+
fn read_int_lens(idx: usize, default: i32) -> impl Lens<Target = i32> {
161+
AppModel::dsp.map(move |dsp| {
162+
dsp.and_then(|it| it.get_int_parameter(idx).ok())
163+
.unwrap_or(default)
164+
})
165+
}
166+
167+
fn norm(v: f32, min: f32, max: f32) -> f32 {
168+
(v - min) / (max - min)
169+
}
170+
171+
fn unnorm(p: f32, min: f32, max: f32) -> f32 {
172+
p * (max - min) + min
173+
}

0 commit comments

Comments
 (0)