@@ -2,24 +2,37 @@ use circular_buffer::CircularBuffer;
22use scamble:: dsp:: decode:: decode_into;
33use scamble:: dsp:: signal:: { Signal , SignalConst , SignalMut } ;
44use 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 ) ]
715struct TrailingNote {
816 pos : usize ,
917 end : usize ,
18+ fadeout_samples : usize ,
1019}
1120
1221pub 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
2538impl 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
0 commit comments