-
Notifications
You must be signed in to change notification settings - Fork 17
/
edrumulus.h
380 lines (346 loc) · 20.2 KB
/
edrumulus.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/******************************************************************************\
* Copyright (c) 2020-2021
* Author(s): Volker Fischer
******************************************************************************
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
\******************************************************************************/
#pragma once
#include "Arduino.h"
#include "edrumulus_hardware.h"
class Edrumulus
{
public:
enum Epadtype // note that the enums need assigned integers for MIDI settings transfer
{
// TODO if new pads are added, check if get_is_control() and get_is_rim_switch() must be updated
PD120 = 0,
PD80R = 1,
PD8 = 2,
FD8 = 3, // control pedal
VH12 = 4,
VH12CTRL = 5,
KD7 = 6,
TP80 = 7,
CY6 = 8,
CY8 = 9
};
enum Ecurvetype // note that the enums need assigned integers for MIDI settings transfer
{
LINEAR = 0,
EXP1 = 1,
EXP2 = 2,
LOG1 = 3,
LOG2 = 4
};
Edrumulus();
// call this function during the Setup function of the main program
void setup ( const int conf_num_pads,
const int* conf_analog_pins,
const int* conf_analog_pins_rim_shot );
// call the process function during the main loop
void process();
// after calling the process function, query the results for each configured pad
bool get_peak_found ( const int pad_idx ) { return peak_found[pad_idx]; }
bool get_choke_on_found ( const int pad_idx ) { return !pad[pad_idx].get_is_control() && is_choke_on[pad_idx]; }
bool get_choke_off_found ( const int pad_idx ) { return !pad[pad_idx].get_is_control() && is_choke_off[pad_idx]; }
bool get_control_found ( const int pad_idx ) { return pad[pad_idx].get_is_control() && control_found[pad_idx]; }
int get_midi_velocity ( const int pad_idx ) { return midi_velocity[pad_idx]; }
int get_midi_pos ( const int pad_idx ) { return midi_pos[pad_idx]; }
int get_midi_note ( const int pad_idx ) { return is_rim_shot[pad_idx] ? pad[pad_idx].get_midi_note_rim() : pad[pad_idx].get_midi_note(); }
int get_midi_note_norm ( const int pad_idx ) { return pad[pad_idx].get_midi_note(); }
int get_midi_note_rim ( const int pad_idx ) { return pad[pad_idx].get_midi_note_rim(); }
int get_midi_note_open ( const int pad_idx ) { return is_rim_shot[pad_idx] ? pad[pad_idx].get_midi_note_open_rim() : pad[pad_idx].get_midi_note_open(); }
int get_midi_note_open_norm ( const int pad_idx ) { return pad[pad_idx].get_midi_note_open(); }
int get_midi_note_open_rim ( const int pad_idx ) { return pad[pad_idx].get_midi_note_open_rim(); }
int get_midi_ctrl_ch ( const int pad_idx ) { return pad[pad_idx].get_midi_ctrl_ch(); }
int get_midi_ctrl_value ( const int pad_idx ) { return midi_ctrl_value[pad_idx]; }
bool get_midi_ctrl_is_open ( const int pad_idx ) { return midi_ctrl_value[pad_idx] < Pad::hi_hat_is_open_MIDI_threshold; }
// configure the pads
void set_pad_type ( const int pad_idx, const Epadtype new_pad_type ) { pad[pad_idx].set_pad_type ( new_pad_type ); }
Epadtype get_pad_type ( const int pad_idx ) { return pad[pad_idx].get_pad_type(); }
void set_velocity_threshold ( const int pad_idx, const int new_threshold ) { pad[pad_idx].set_velocity_threshold ( new_threshold ); }
int get_velocity_threshold ( const int pad_idx ) { return pad[pad_idx].get_velocity_threshold(); }
void set_velocity_sensitivity ( const int pad_idx, const int new_velocity ) { pad[pad_idx].set_velocity_sensitivity ( new_velocity ); }
int get_velocity_sensitivity ( const int pad_idx ) { return pad[pad_idx].get_velocity_sensitivity(); }
void set_pos_threshold ( const int pad_idx, const int new_threshold ) { pad[pad_idx].set_pos_threshold ( new_threshold ); }
int get_pos_threshold ( const int pad_idx ) { return pad[pad_idx].get_pos_threshold(); }
void set_pos_sensitivity ( const int pad_idx, const int new_velocity ) { pad[pad_idx].set_pos_sensitivity ( new_velocity ); }
int get_pos_sensitivity ( const int pad_idx ) { return pad[pad_idx].get_pos_sensitivity(); }
void set_mask_time ( const int pad_idx, const int new_time ) { pad[pad_idx].set_mask_time ( new_time ); }
int get_mask_time ( const int pad_idx ) { return pad[pad_idx].get_mask_time(); }
void set_rim_shot_treshold ( const int pad_idx, const int new_threshold ) { pad[pad_idx].set_rim_shot_treshold ( new_threshold ); }
int get_rim_shot_treshold ( const int pad_idx ) { return pad[pad_idx].get_rim_shot_treshold(); }
void set_curve ( const int pad_idx, const Ecurvetype new_curve ) { pad[pad_idx].set_curve ( new_curve ); }
Ecurvetype get_curve ( const int pad_idx ) { return pad[pad_idx].get_curve(); }
void set_cancellation ( const int pad_idx, const int new_cancel ) { pad[pad_idx].set_cancellation ( new_cancel ); }
int get_cancellation ( const int pad_idx ) { return pad[pad_idx].get_cancellation(); }
void set_midi_notes ( const int pad_idx, const int new_midi_note, const int new_midi_note_rim ) { pad[pad_idx].set_midi_notes ( new_midi_note, new_midi_note_rim ); }
void set_midi_notes_open ( const int pad_idx, const int new_midi_note, const int new_midi_note_rim ) { pad[pad_idx].set_midi_notes_open ( new_midi_note, new_midi_note_rim ); }
void set_midi_ctrl_ch ( const int pad_idx, const int new_midi_ctrl_ch ) { pad[pad_idx].set_midi_ctrl_ch ( new_midi_ctrl_ch ); }
void set_rim_shot_is_used ( const int pad_idx, const bool new_is_used ) { pad[pad_idx].set_rim_shot_is_used ( new_is_used ); }
void set_pos_sense_is_used ( const int pad_idx, const bool new_is_used ) { pad[pad_idx].set_pos_sense_is_used ( new_is_used ); }
void set_spike_cancel_level ( const int new_level ) { spike_cancel_level = new_level; }
int get_spike_cancel_level () { return spike_cancel_level; }
// overload and error handling
bool get_status_is_overload() { return status_is_overload; }
bool get_status_is_error() { return status_is_error; }
// query functions
bool get_pos_sense_is_used ( const int pad_idx ) { return pad[pad_idx].get_pos_sense_is_used(); }
protected:
class Pad
{
public:
void setup ( const int conf_Fs,
const int conf_number_inputs = 1 );
void process_sample ( const float* input,
bool& peak_found,
int& midi_velocity,
int& midi_pos,
bool& is_rim_shot,
bool& is_choke_on,
bool& is_choke_off,
float& debug );
void process_control_sample ( const int* input,
bool& change_found,
int& midi_ctrl_value,
bool& peak_found,
int& midi_velocity );
void set_pad_type ( const Epadtype new_pad_type );
Epadtype get_pad_type() { return pad_settings.pad_type; }
void set_midi_notes ( const int new_midi_note, const int new_midi_note_rim ) { midi_note = new_midi_note; midi_note_rim = new_midi_note_rim; }
void set_midi_notes_open ( const int new_midi_note, const int new_midi_note_rim ) { midi_note_open = new_midi_note; midi_note_open_rim = new_midi_note_rim; }
void set_midi_ctrl_ch ( const int new_midi_ctrl_ch ) { midi_ctrl_ch = new_midi_ctrl_ch; }
void set_rim_shot_is_used ( const bool new_is_used ) { pad_settings.rim_shot_is_used = new_is_used; }
void set_pos_sense_is_used ( const bool new_is_used ) { pad_settings.pos_sense_is_used = new_is_used; }
void set_velocity_threshold ( const int new_threshold ) { pad_settings.velocity_threshold = new_threshold; initialize(); }
int get_velocity_threshold () { return pad_settings.velocity_threshold; }
void set_velocity_sensitivity ( const int new_velocity ) { pad_settings.velocity_sensitivity = new_velocity; initialize(); }
int get_velocity_sensitivity () { return pad_settings.velocity_sensitivity; }
void set_pos_threshold ( const int new_threshold ) { pad_settings.pos_threshold = new_threshold; initialize(); }
int get_pos_threshold () { return pad_settings.pos_threshold; }
void set_pos_sensitivity ( const int new_velocity ) { pad_settings.pos_sensitivity = new_velocity; initialize(); }
int get_pos_sensitivity () { return pad_settings.pos_sensitivity; }
void set_mask_time ( const int new_time_ms ) { pad_settings.mask_time_ms = new_time_ms; initialize(); }
int get_mask_time () { return pad_settings.mask_time_ms; }
void set_rim_shot_treshold ( const int new_threshold ) { pad_settings.rim_shot_treshold = new_threshold; initialize(); }
int get_rim_shot_treshold () { return pad_settings.rim_shot_treshold; }
void set_curve ( const Ecurvetype new_curve ) { pad_settings.curve_type = new_curve; initialize(); }
Ecurvetype get_curve () { return pad_settings.curve_type; }
void set_cancellation ( const int new_cancel ) { pad_settings.cancellation = new_cancel; initialize(); }
int get_cancellation () { return pad_settings.cancellation; }
int get_midi_note() { return midi_note; }
int get_midi_note_rim() { return midi_note_rim; }
int get_midi_note_open() { return midi_note_open; }
int get_midi_note_open_rim() { return midi_note_open_rim; }
int get_midi_ctrl_ch() { return midi_ctrl_ch; }
float get_cancellation_factor() { return cancellation_factor; }
bool get_is_control() { return ( pad_settings.pad_type == FD8 ) ||
( pad_settings.pad_type == VH12CTRL ); } // TODO check if new pads must be added here
bool get_is_rim_switch() { return ( pad_settings.pad_type == PD8 ) ||
( pad_settings.pad_type == VH12 ) ||
( pad_settings.pad_type == TP80 ) ||
( pad_settings.pad_type == CY6 ) ||
( pad_settings.pad_type == CY8 ); } // TODO check if new pads must be added here
bool get_pos_sense_is_used() { return pad_settings.pos_sense_is_used; }
// definitions which can be used outside the pad class, too
static const int control_midi_hysteresis = ADC_MAX_NOISE_AMPL / 2; // MIDI hysteresis for the controller to suppress noise
static const int hi_hat_is_open_MIDI_threshold = 100; // MIDI values smaller than the limit value are "open hi-hat"
protected:
struct Epadsettings
{
Epadtype pad_type;
int velocity_threshold; // 0..31
int velocity_sensitivity; // 0..31, high values give higher sensitivity
int mask_time_ms; // 0..31 (ms)
int pos_threshold; // 0..31
int pos_sensitivity; // 0..31, high values give higher sensitivity
int rim_shot_treshold; // 0..31
int cancellation; // 0..31
bool pos_sense_is_used; // switches positional sensing support on or off
bool rim_shot_is_used; // switches rim shot detection on or off
Ecurvetype curve_type;
float energy_win_len_ms;
float scan_time_ms;
float main_peak_dist_ms;
float decay_est_delay2nd_ms;
float decay_est_len_ms;
float decay_est_fact_db;
float decay_fact_db;
float decay_len1_ms, decay_len2_ms, decay_len3_ms;
float decay_grad_fact1, decay_grad_fact2, decay_grad_fact3;
float pos_energy_win_len_ms;
float pos_iir_alpha;
bool pos_invert;
float rim_shot_window_len_ms;
int rim_shot_velocity_thresh;
};
void initialize();
// Hilbert filter coefficients (they are constant and must not be changed)
const int hil_filt_len = 7;
const float a_re[7] = { -0.037749783581601f, -0.069256807147465f, -1.443799477299919f, 2.473967088799056f,
0.551482327389238f, -0.224119735833791f, -0.011665324660691f };
const float a_im[7] = { 0.0f, 0.213150535195075f, -1.048981722170302f, -1.797442302898130f,
1.697288080048948f, 0.0f, 0.035902177664014f };
// high pass filter coefficients used for rim shot detection (they are constant and must not be changed)
const float b_rim_high[2] = { 0.969531252908746f, -0.969531252908746f };
const float a_rim_high = -0.939062505817492f;
float* hil_hist = nullptr;
float* mov_av_hist_re = nullptr;
float* mov_av_hist_im = nullptr;
float* decay = nullptr;
float* hist_main_peak_pow_left = nullptr;
float* hil_hist_re = nullptr;
float* hil_hist_im = nullptr;
float* hil_hist_velocity = nullptr;
float* hil_low_hist_re = nullptr;
float* hil_low_hist_im = nullptr;
float* rim_x_high_hist = nullptr;
float* ctrl_hist = nullptr;
int Fs;
int number_inputs;
int energy_window_len;
int hil_hist_velocity_len;
float mov_av_norm_fact;
int scan_time;
int scan_time_cnt;
int decay_len, decay_len1, decay_len2, decay_len3;
int mask_time;
int mask_back_cnt;
float threshold;
float velocity_factor;
float velocity_exponent;
float velocity_offset;
float pos_threshold;
float pos_range_db;
float control_threshold;
float control_range;
float first_peak_diff_thresh;
float first_peak_val;
bool was_above_threshold;
float prev_hil_filt_val;
int main_peak_dist;
int decay_est_delay2nd;
int decay_est_len;
float decay_est_fact;
float power_hypo_left;
int power_hypo_right_cnt;
int decay_pow_est_start_cnt;
int decay_pow_est_cnt;
float decay_pow_est_sum;
float decay_fact;
int decay_back_cnt;
float decay_scaling;
float alpha;
float rim_high_prev_x;
float rim_x_high;
int rim_shot_window_len;
float rim_shot_treshold_dB;
float rim_switch_treshold;
int rim_switch_on_cnt;
int rim_switch_on_cnt_thresh;
int pos_energy_window_len;
int pos_sense_cnt;
int rim_shot_cnt;
float hil_filt_max_pow;
int stored_midi_velocity;
int stored_midi_pos;
bool stored_is_rimshot;
float max_hil_filt_val;
int peak_found_offset;
bool was_peak_found;
bool was_pos_sense_ready;
bool was_rim_shot_ready;
float hil_low_re;
float hil_low_im;
Epadsettings pad_settings;
int midi_note;
int midi_note_rim;
int midi_note_open;
int midi_note_open_rim;
int midi_ctrl_ch;
int ctrl_history_len;
int ctrl_velocity_threshold;
float ctrl_velocity_range_fact;
int prev_ctrl_value;
float cancellation_factor;
};
// constant definitions
const int dc_offset_est_len = 10000; // samples (about a second at 8 kHz sampling rate)
const int samplerate_max_cnt = 10000; // samples
const int samplerate_max_error_Hz = 100; // tolerate a sample rate deviation of 100 Hz
const int cancel_time_ms = 30; // on same stand approx. 10 ms + some margin (20 ms)
#ifdef ESP_PLATFORM
// for ESP we have a coupling of ADC inputs so that a hi-hat control pedal movement may
// influence the DC offset of some pad inputs, therefore we need to adapt faster to
// compensate for this
const int dc_offset_iir_tau_seconds = 5; // DC offset update IIR filter tau in seconds
#else
const int dc_offset_iir_tau_seconds = 30; // DC offset update IIR filter tau in seconds
#endif
int Fs;
Edrumulus_hardware edrumulus_hardware;
int number_pads;
int number_inputs[MAX_NUM_PADS];
int analog_pin[MAX_NUM_PADS][MAX_NUM_PAD_INPUTS];
double dc_offset[MAX_NUM_PADS][MAX_NUM_PAD_INPUTS]; // must be double type for IIR filter
int sample_org[MAX_NUM_PADS][MAX_NUM_PAD_INPUTS];
float dc_offset_iir_gamma;
float dc_offset_iir_one_minus_gamma;
int spike_cancel_level;
int overload_LED_cnt;
int overload_LED_on_time;
bool status_is_overload;
bool status_is_error;
int samplerate_prev_micros_cnt;
unsigned long samplerate_prev_micros;
Pad pad[MAX_NUM_PADS];
bool peak_found[MAX_NUM_PADS];
bool control_found[MAX_NUM_PADS];
int midi_velocity[MAX_NUM_PADS];
int midi_pos[MAX_NUM_PADS];
int midi_ctrl_value[MAX_NUM_PADS];
bool is_rim_shot[MAX_NUM_PADS];
bool is_choke_on[MAX_NUM_PADS];
bool is_choke_off[MAX_NUM_PADS];
int cancel_num_samples;
int cancel_cnt;
int cancel_MIDI_velocity;
int cancel_pad_index;
};
// Utility functions -----------------------------------------------------------------
static void update_fifo ( const float input,
const int fifo_length,
float* fifo_memory )
{
// move all values in the history one step back and put new value on the top
for ( int i = 0; i < fifo_length - 1; i++ )
{
fifo_memory[i] = fifo_memory[i + 1];
}
fifo_memory[fifo_length - 1] = input;
}
static void allocate_initialize ( float** array_memory,
const int array_length )
{
// (delete and) allocate memory
if ( *array_memory != nullptr )
{
delete[] *array_memory;
}
*array_memory = new float[array_length];
// initialization values
for ( int i = 0; i < array_length; i++ )
{
( *array_memory )[i] = 0.0f;
}
}