Skip to content

Commit 5ceb76b

Browse files
committed
macros for parameters // work on cassette player
1 parent 20ae580 commit 5ceb76b

File tree

3 files changed

+153
-62
lines changed

3 files changed

+153
-62
lines changed

cassette_player/src/cassette_player.rs

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,37 @@ use circular_buffer::CircularBuffer;
22
use scamble::dsp::decode::decode_into;
33
use scamble::dsp::signal::{Signal, SignalConst, SignalMut};
44
use scamble::dsp::{Dsp, DspType, Parameter, ParameterType, ProcessResult};
5+
use scamble::{bool_param, enum_param, float_param, int_param};
6+
7+
#[derive(Copy, Clone)]
8+
enum VoiceMode {
9+
Combine,
10+
Average,
11+
Overtake,
12+
}
513

614
#[derive(Copy, Clone)]
715
struct TrailingNote {
816
pos: usize,
917
end: usize,
18+
fadeout_samples: usize,
1019
}
1120

1221
pub struct CassettePlayer {
1322
// user-specified parameters
1423
samples: Vec<f32>, // downsample to mono for now
1524
num_notes: usize,
25+
_async: bool,
1626
start_offset_percent: f32,
1727
end_offset_percent: f32,
28+
voices: u8,
29+
voice_mode: VoiceMode,
1830
// game-state parameters
1931
note_frac: f32,
2032
// state
21-
trailing_notes: CircularBuffer<4, TrailingNote>,
33+
trailing_notes: CircularBuffer<8, TrailingNote>,
2234
prev_note_frac: f32,
35+
async_note_tick: usize,
2336
}
2437

2538
impl CassettePlayer {
@@ -37,11 +50,19 @@ impl CassettePlayer {
3750
self.end_idx().saturating_sub(self.start_idx())
3851
}
3952

53+
fn approx_note_len(&self) -> usize {
54+
((self.content_len() as f32) / (self.num_notes as f32)) as usize
55+
}
56+
4057
fn note_at_pos(&self, pos: f32) -> TrailingNote {
4158
let clen = self.content_len() as f32;
4259
let start = (clen * pos) as usize + self.start_idx();
4360
let end = start + (clen / self.num_notes as f32) as usize;
44-
TrailingNote { pos: start, end }
61+
TrailingNote {
62+
pos: start,
63+
end,
64+
fadeout_samples: 0,
65+
}
4566
}
4667
}
4768

@@ -63,7 +84,6 @@ impl Dsp for CassettePlayer {
6384
Parameter {
6485
ty: ParameterType::Data {
6586
setter: |data, dsp| {
66-
// TODO: actually decode
6787
dsp.samples.clear();
6888
decode_into(data, &mut dsp.samples);
6989
},
@@ -73,68 +93,32 @@ impl Dsp for CassettePlayer {
7393
unit: "",
7494
desc: "",
7595
},
76-
Parameter {
77-
ty: ParameterType::Int {
78-
min: 1,
79-
max: 4096,
80-
default: 256,
81-
max_is_inf: false,
82-
names: None,
83-
setter: |value, dsp| dsp.num_notes = value as usize,
84-
getter: |dsp| dsp.num_notes as i32,
85-
},
86-
name: "num_notes",
87-
unit: "",
88-
desc: "",
89-
},
90-
Parameter {
91-
ty: ParameterType::Float {
92-
min: 0.0,
93-
max: 100.0,
94-
default: 0.0,
95-
setter: |value, dsp| dsp.start_offset_percent = value,
96-
getter: |dsp| dsp.start_offset_percent,
97-
},
98-
name: "start_offset",
99-
unit: "%",
100-
desc: "",
101-
},
102-
Parameter {
103-
ty: ParameterType::Float {
104-
min: 0.0,
105-
max: 100.0,
106-
default: 100.0,
107-
setter: |value, dsp| dsp.end_offset_percent = value,
108-
getter: |dsp| dsp.end_offset_percent,
109-
},
110-
name: "end_offset",
111-
unit: "%",
112-
desc: "",
113-
},
114-
Parameter {
115-
ty: ParameterType::Float {
116-
min: 0.,
117-
max: 1.,
118-
default: 0.,
119-
setter: |value, dsp| dsp.note_frac = value,
120-
getter: |dsp| dsp.note_frac,
121-
},
122-
name: "note",
123-
unit: "",
124-
desc: "",
125-
},
96+
Parameter::new("num_notes", int_param!(num_notes: usize, range: 1..4096, default: 256)),
97+
Parameter::new("async", bool_param!(_async, default: false)),
98+
Parameter::with_unit("start_offset", "%", float_param!(start_offset_percent, range: 0.0..100.0, default: 0.)),
99+
Parameter::with_unit("end_offset", "%", float_param!(end_offset_percent, range: 0.0..100.0, default: 100.)),
100+
Parameter::new("voices", int_param!(voices: u8, range: 1..8, default: 4)),
101+
Parameter::new(
102+
"voice_mode",
103+
enum_param!(voice_mode: VoiceMode, options: [Combine, Average, Overtake], default: Combine),
104+
),
105+
Parameter::new("note", float_param!(note_frac, range: 0.0..1.0, default: 0.)),
126106
]
127107
}
128108

129109
fn create() -> Self {
130110
CassettePlayer {
131111
samples: vec![],
132112
num_notes: 0,
113+
_async: false,
133114
start_offset_percent: 0.0,
134115
end_offset_percent: 100.0,
116+
voices: 4,
117+
voice_mode: VoiceMode::Combine,
135118
note_frac: 0.,
136119
trailing_notes: Default::default(),
137120
prev_note_frac: 2., // make sure the initial beat always triggers
121+
async_note_tick: 0,
138122
}
139123
}
140124

@@ -158,8 +142,20 @@ impl Dsp for CassettePlayer {
158142

159143
// trigger new notes when parameter changes
160144
if self.note_frac != self.prev_note_frac {
161-
self.trailing_notes
162-
.push_back(self.note_at_pos(self.note_frac));
145+
if self._async {
146+
// in async mode, run an internal timer disconnected from the input parameter
147+
// (parameter changing is all that matter)
148+
let async_note_frac = (self.async_note_tick as f32) / (self.num_notes as f32);
149+
self.trailing_notes.push_back(self.note_at_pos(async_note_frac));
150+
self.async_note_tick += 1;
151+
self.async_note_tick %= self.num_notes;
152+
} else {
153+
// in sync mode, use the position of the input as the position of the note
154+
self.trailing_notes.push_back(self.note_at_pos(self.note_frac));
155+
}
156+
if self.trailing_notes.len() > self.voices as usize {
157+
self.trailing_notes.pop_front();
158+
}
163159
}
164160
self.prev_note_frac = self.note_frac;
165161

rustfmt.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
max_width = 160

src/dsp/mod.rs

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::data::*;
22
use crate::dsp::signal::*;
33

4+
pub mod decode;
45
pub mod interop;
56
pub mod signal;
6-
pub mod decode;
77

88
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
99
pub enum DspType {
@@ -62,32 +62,32 @@ pub enum ParameterType<Dsp: ?Sized> {
6262
},
6363
DynamicResponse {
6464
setter: fn(DynamicResponseData, &mut Dsp),
65-
getter: fn(&Dsp) -> DynamicResponseData
65+
getter: fn(&Dsp) -> DynamicResponseData,
6666
},
6767
/// Read by FMOD Studio to decide when to virtualize sounds.
6868
OverallGain {
6969
setter: fn(OverallGainData, &mut Dsp),
70-
getter: fn(&Dsp) -> OverallGainData
70+
getter: fn(&Dsp) -> OverallGainData,
7171
},
7272
/// Set by FMOD Studio with the player's position and attributes.
7373
ListenerAttributes {
7474
setter: fn(ListenerAttributesData, &mut Dsp),
75-
getter: fn(&Dsp) -> ListenerAttributesData
75+
getter: fn(&Dsp) -> ListenerAttributesData,
7676
},
7777
/// Set by FMOD Studio with all player's positions and attributes, if there are multiple.
7878
ListenerAttributesList {
7979
setter: fn(ListenerAttributesListData, &mut Dsp),
80-
getter: fn(&Dsp) -> ListenerAttributesListData
80+
getter: fn(&Dsp) -> ListenerAttributesListData,
8181
},
8282
/// Set by FMOD Studio to the min/max range of the event containing this DSP.
8383
AttenuationRange {
8484
setter: fn(AttenuationRangeData, &mut Dsp),
85-
getter: fn(&Dsp) -> AttenuationRangeData
85+
getter: fn(&Dsp) -> AttenuationRangeData,
8686
},
8787
/// Set to provide access to FFT data to games.
8888
Fft {
8989
setter: fn(FftData, &mut Dsp),
90-
getter: fn(&Dsp) -> FftData
90+
getter: fn(&Dsp) -> FftData,
9191
},
9292
}
9393

@@ -122,3 +122,97 @@ pub trait Dsp {
122122

123123
fn read(&mut self, input: SignalConst, output: SignalMut);
124124
}
125+
126+
impl<T: ?Sized> Parameter<T> {
127+
pub const fn new(name: &'static str, ty: ParameterType<T>) -> Self {
128+
Self {
129+
ty,
130+
name,
131+
unit: "",
132+
desc: "",
133+
}
134+
}
135+
136+
pub const fn with_unit(name: &'static str, unit: &'static str, ty: ParameterType<T>) -> Self {
137+
Self {
138+
ty,
139+
name,
140+
unit,
141+
desc: "",
142+
}
143+
}
144+
}
145+
146+
// float_param!(note, range: 0.0..1.0, default: 0.0)
147+
#[macro_export]
148+
macro_rules! float_param {
149+
($name:ident $(: $t:ty)?, range: $min:literal..$max:literal, default: $default:literal) => {
150+
ParameterType::Float {
151+
min: $min,
152+
max: $max,
153+
default: $default,
154+
setter: |value, dsp| dsp.$name = value $(as $t)?,
155+
getter: |dsp| dsp.$name as f32
156+
}
157+
}
158+
}
159+
160+
// int_param!(value: u8, range: 0..24, default: 1)
161+
#[macro_export]
162+
macro_rules! int_param {
163+
($name:ident $(: $t:ty)?, range: $min:literal..$max:literal, default: $default:literal) => {
164+
ParameterType::Int {
165+
min: $min,
166+
max: $max,
167+
default: $default,
168+
max_is_inf: false,
169+
names: None,
170+
setter: |value, dsp| dsp.$name = value $(as $t)?,
171+
getter: |dsp| dsp.$name as i32
172+
}
173+
}
174+
}
175+
176+
// bool_param!(_async, default: false)
177+
#[macro_export]
178+
macro_rules! bool_param {
179+
($name:ident, default: $default:literal) => {
180+
ParameterType::Bool {
181+
default: $default,
182+
names: None,
183+
setter: |value, dsp| dsp.$name = value,
184+
getter: |dsp| dsp.$name,
185+
}
186+
};
187+
}
188+
189+
// enum_param!(voice_mode: VoiceMode, options: [Sum, Average, Solo], default: Solo)
190+
#[macro_export]
191+
macro_rules! enum_param {
192+
($name:ident: $t:ty, options: [$($opt:ident $(,)?)*], default: $default:ident) => {
193+
ParameterType::Int {
194+
min: 0,
195+
max: ${count($opt)} - 1,
196+
default: {
197+
// aid autocomplete/deref by claiming that it's a variant of $t
198+
let _: $t = <$t>::$default;
199+
// ...but match by name in the list
200+
[$(stringify!($opt),)*].iter().position(|h| *h == stringify!($default)).unwrap() as i32
201+
},
202+
max_is_inf: false,
203+
names: Some(vec![$(stringify!($opt),)*]),
204+
setter: |value, dsp| dsp.$name = match value {
205+
$(
206+
${index()} => <$t>::$opt,
207+
)*
208+
_ => panic!(concat!("Unknown variant {} for field ", stringify!($name)), value)
209+
},
210+
getter: |dsp| match dsp.$name {
211+
$(
212+
<$t>::$opt => ${index()},
213+
)*
214+
_ => panic!()
215+
}
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)