1- const int window_ms = 5 ; // milliseconds per measurement window. A narrow window will allow finer control over attack and release,
2- // but it will also cripple detection of low frequency amplitudes. Probably you don't want to change this.
1+ // // main compressor parameters. Adjust these to your needs. ////
32int attack_f = 10 ; // attack period (how soon the compressor will start attenuating loud signals) given in measurement frame
4- // units. Default setting corresponds to 50ms. Max buf_len.
3+ // units (see window_ms) . Default setting corresponds to 50ms. Max buf_len / 2. Min 4 .
54int release_f = 40 ; // release period (how soon the compressor will soften attenuation after signals have become more silent),
6- // given in measurement frame units. Default setting corresponds to 200ms; Max buf_len. Should be > attack_f
7- int threshold = 17 ; // minimum signal amplitude before the compressor will kick in. Each unit corresponds to roughly 5mV
5+ // given in measurement frame units. Default setting corresponds to 200ms; Max buf_len.
6+ // Does not have an effect if <= attack_f
7+ int threshold = 20 ; // minimum signal amplitude before the compressor will kick in. Each unit corresponds to roughly 5mV
88 // peak-to-peak.
9- float dampening = .5 ; // dampening applied to signals exceeding the threshold. 0 corresponds to no compression, 1 corresponds
9+ float inv_ratio = .5 ; // dampening applied to signals exceeding the threshold. 0 corresponds to no compression, 1 corresponds
1010 // to limiting the signal to the threshold value (if possible: see below)
11+ // (Inverse of "ratio" parameter of typical compressors)
12+
13+ // // Some further constants that you will probably not have to tweak ////
14+ #define DEBUG 1 // serial communication appears to introduce audible noise ("ticks"), thus debugging is diabled by default
15+ const int window_ms = 5 ; // milliseconds per measurement window. A narrow window will allow finer control over attack and release,
16+ // but it will also cripple detection of low frequency amplitudes. Probably you don't want to change this.
17+ const int buf_len = 100 ; // size of buffer. attack_f and release_f cannot exceed this.
1118const int duty_min = 10 ; // ceiling value for attenuation (lower values = more attenuation, 0 = off, 255 = no attenuation)
1219 // beyond a certain value further attenuation is just too coarse grained for good results. Ideally, this
13- // value is never reached, but might be for aggressive dampening and low thresholds.
20+ // value is never reached, but might be for aggressive dampening ratio and low thresholds.
1421const int duty_warn = 2 * duty_min; // See above. At attenuation beyond this (i.e. smaller numbers), warning LED will flash.
1522 // Reaching this point on occasion is quite benign. Reaching this point much of the time means too strong
16- // signal, too low threshold setting, or too aggressive dampening .
23+ // signal, too low threshold setting, or too aggressive inv_ratio .
1724const int signal_warn = 300 ; // A warning LED will flash for signals exceeding this amplitude (5mv per unit, peak-to-peak) as
1825 // it is probably (almost) too much for the circuit too handle (default value corresponds to about +-750mV
1926 // in order to stay below typical 2N7000 body diode forward voltage drop of .88V)
2027
28+ // // Adjustable pin assignments
29+ const int pin_led_warn = 13 ;
30+ const int pin_led_high = 12 ;
31+ const int pin_led_mid = 11 ;
32+ const int pin_led_low = 10 ;
33+
34+ const int pin_attack = 4 ;
35+ const int pin_release = 5 ;
36+ const int pin_threshold = 6 ;
37+ const int pin_ratio = 7 ;
38+ const int pin_control_plus = 8 ;
39+ const int pin_control_minus = 9 ;
40+
41+ // // working variables ////
2142volatile int cmin = 1024 ; // minimum amplitude found in current measurement window
2243volatile int cmax = 0 ; // maximum amplitude found in current measurement window
23- const int buf_len = 100 ; // size of buffer. attack_f and release_f cannot exceed this.
2444int buf[buf_len]; // ring buffer for moving averages / sums
2545int pos = 0 ; // current buffer position
2646int attack_mova = 0 ; // moving average (actually sum) of amplitudes over past attack period
2747int release_mova = 0 ; // moving average (actually sum) of amplitudes over past release period
48+ int32_t now = 0 ; // start time of current loop
2849int32_t last = 0 ; // time of last loop
29- int duty = 255 ; // current attenuation duty cycle (0: hard off, 255: no attenuation)
50+ int duty = 255 ; // current PWM duty cycle for attenuator switch(es) (0: hard off, 255: no attenuation)
51+ byte display_hold = 0 ;
3052
31- #define DEBUG 1 // serial communication appear to introduce audible noise ("ticks"), thus diabled by default
3253#if DEBUG
3354int it = 0 ;
3455#endif
3556
57+ /* ** Handle new analog readings as they become available. This simply records the highest and lowest voltages seen in the current
58+ measurement window. All real (and more computation-heavy) handling is done inside loop(). ***/
59+ ISR (ADC_vect) {
60+ int aval = ADCL; // store lower byte ADC
61+ aval += ADCH << 8 ; // store higher byte ADC
62+ if (aval < cmin) cmin = aval;
63+ if (aval > cmax) cmax = aval;
64+ }
65+
3666void setup () {
3767 for (int i = 0 ; i < buf_len; ++i) { // clear buffer
3868 buf[i] = 0 ;
3969 }
4070#if DEBUG
41- Serial.begin (9600 );
71+ Serial.begin (9600 );
4272#endif
4373
4474 // start fast pwm with no prescaler (~62kHz) on pin 3, controlling the attenuator switch(es)
@@ -48,30 +78,45 @@ void setup() {
4878 OCR2B = 255 ; // 100% duty cycle, initially
4979
5080 // setup fast continuous analog input sampling. Kudos go to https://meettechniek.info/embedded/arduino-analog.html
51- DIDR0 = 0x3F ; // digital inputs disabled
81+ // whenever a new reading is available, the routine defined by ISR(ADC_vect) is called.
82+ DIDR0 = 0x3F ; // digital input buffers disabled on all analog pins
5283 ADMUX = 0b01000000 ; // measuring on ADC0, use 5v reference
53- ADCSRA = 0xAC ; // AD-converter on, interrupt enabled, prescaler = 16 --> aroudn 77k samples per second
84+ ADCSRA = 0xAC ; // AD-converter on, interrupt enabled, prescaler = 16 --> around 77k samples per second
5485 ADCSRB = 0x40 ; // AD channels MUX on, free running mode
5586 bitWrite (ADCSRA, 6 , 1 ); // Start the conversion by setting bit 6 (=ADSC) in ADCSRA
5687 sei (); // set interrupt flag
5788
58- last = millis ();
89+ last = millis ();
90+
91+ // status display
92+ pinMode (pin_led_low, OUTPUT);
93+ pinMode (pin_led_mid, OUTPUT);
94+ pinMode (pin_led_high, OUTPUT);
95+ pinMode (pin_led_warn, OUTPUT);
96+
97+ // control buttons. Set up for attaching an 4*2 (or larger) button matrix
98+ pinMode (pin_control_plus, OUTPUT);
99+ pinMode (pin_control_minus, OUTPUT);
100+ pinMode (pin_attack, INPUT_PULLUP);
101+ pinMode (pin_release, INPUT_PULLUP);
102+ pinMode (pin_threshold, INPUT_PULLUP);
103+ pinMode (pin_ratio, INPUT_PULLUP);
59104}
60105
61106void loop () {
62- int32_t now = millis ();
107+ now = millis ();
63108 if (now < last || now - last > window_ms) { // measurment window elapsed (or timer overflow)
64109 last = now;
65110 } else return ;
66111
67112#if DEBUG
68113 if (++it == 40 ) {
69114 it = 0 ;
70- Serial.print (cmax);
71- Serial.print (" -" );
72- Serial.print (cmin);
73- Serial.print (" -" );
74- Serial.println (duty);
115+ Serial.print (cmax);
116+ Serial.print (" -" );
117+ Serial.print (cmin);
118+ Serial.print (" -" );
119+ Serial.println (duty);
75120 }
76121#endif
77122
@@ -97,25 +142,92 @@ void loop() {
97142 // first caculate based on attack period
98143 const int attack_threshold = threshold * attack_f;
99144 const float attack_overshoot = max (0 , (attack_mova - attack_threshold) / (float ) attack_threshold);
100- const int attack_duty = 255 / (attack_overshoot * dampening + 1 );
145+ const int attack_duty = 255 / (attack_overshoot * inv_ratio + 1 );
101146 // if the new duty setting is _below_ the current, based on attack period, check release window to see, if
102147 // the time has come to release attenuation, yet:
103148 if (attack_duty >= duty) duty = attack_duty;
104149 else {
105150 const int release_threshold = threshold * release_f;
106151 const float release_overshoot = max (0 , (release_mova - release_threshold) / (float ) release_threshold);
107- const int release_duty = 255 / (release_overshoot * dampening + 1 );
152+ const int release_duty = 255 / (release_overshoot * inv_ratio + 1 );
108153 if (release_duty < duty) duty = release_duty;
109154 }
110155
111156 OCR2B = duty; // enable the new duty cycle
157+
158+ if ((display_hold < 90 ) && handleControls ()) { // check state of control buttons. If any was pressed, the status LEDs shall not be
159+ // updated for the next half second (they will indicate control status, instead)
160+ display_hold = 100 ;
161+ }
162+ if (display_hold) {
163+ --display_hold;
164+ } else {
165+ indicateLevels (val, duty);
166+ }
112167}
113168
114- /* ** Interrupt routine ADC ready ***/
115- ISR (ADC_vect) {
116- int aval = ADCL; // store lower byte ADC
117- aval += ADCH << 8 ; // store higher bytes ADC
118- if (aval < cmin) cmin = aval;
119- if (aval > cmax) cmax = aval;
169+ // query matrix of control buttons and handle any presses. Returns true, if something was changed.
170+ bool handleControls () {
171+ digitalWrite (pin_control_plus, LOW);
172+ digitalWrite (pin_control_minus, HIGH);
173+ if (!digitalRead (pin_attack)) {
174+ attack_f = min (buf_len/2 , attack_f + 1 );
175+ indicateControls (attack_f, 4 , buf_len / 2 );
176+ return true ;
177+ }
178+ if (!digitalRead (pin_release)) {
179+ release_f = min (buf_len, release_f + 2 );
180+ indicateControls (release_f, 4 , buf_len);
181+ return true ;
182+ }
183+ if (!digitalRead (pin_threshold)) {
184+ threshold = min (signal_warn / 2 , min (threshold+1 , (int ) threshold*1.1 ));
185+ indicateControls (threshold, 0 , signal_warn / 2 );
186+ return true ;
187+ }
188+ if (!digitalRead (pin_ratio)) {
189+ inv_ratio = min (1.0 , inv_ratio + .05 );
190+ indicateControls (inv_ratio*100 , 0 , 100 );
191+ return true ;
192+ }
193+ digitalWrite (pin_control_minus, LOW);
194+ digitalWrite (pin_control_plus, HIGH);
195+ if (!digitalRead (pin_attack)) {
196+ attack_f = max (4 , attack_f-1 );
197+ indicateControls (attack_f, 4 , buf_len / 2 );
198+ return true ;
199+ }
200+ if (!digitalRead (pin_release)) {
201+ release_f = max (4 , release_f - 2 );
202+ indicateControls (release_f, 4 , buf_len);
203+ return true ;
204+ }
205+ if (!digitalRead (pin_threshold)) {
206+ threshold = max (0 , min (threshold-1 , (int ) threshold/1.1 ));
207+ indicateControls (threshold, 0 , signal_warn / 2 );
208+ return true ;
209+ }
210+ if (!digitalRead (pin_ratio)) {
211+ inv_ratio = max (0 , inv_ratio - .05 );
212+ indicateControls (inv_ratio*100 , 0 , 100 );
213+ return true ;
214+ }
215+ return false ;
216+ }
217+
218+ void indicateControls (int value, int minv, int maxv) {
219+ digitalWrite (pin_led_warn, (value <= minv) || (value >= maxv)); // Use warning LED to signal either end of scale reached
220+ // NOTE: Intentionally "reversing" the LED scale, here, to make it easier to differentiate from signal level indication
221+ float rate = (float ) value / (maxv-minv);
222+ digitalWrite (pin_led_high, rate > .25 );
223+ digitalWrite (pin_led_mid, rate > .5 );
224+ digitalWrite (pin_led_low, rate > .75 );
225+ }
226+
227+ void indicateLevels (int rawval, int cduty) {
228+ digitalWrite (pin_led_warn, rawval >= signal_warn);
229+ digitalWrite (pin_led_high, cduty <= duty_warn);
230+ digitalWrite (pin_led_mid, cduty < 128 );
231+ digitalWrite (pin_led_low, cduty != 255 );
120232}
121233
0 commit comments