Skip to content

Commit 09494a3

Browse files
committed
More complete sketch
- add controls and status indication - assorted edits for clarity and style
1 parent c2fbc04 commit 09494a3

File tree

1 file changed

+141
-29
lines changed

1 file changed

+141
-29
lines changed

compressor.ino

Lines changed: 141 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,74 @@
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. ////
32
int 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.
54
int 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.
1118
const 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.
1421
const 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.
1724
const 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 ////
2142
volatile int cmin = 1024; // minimum amplitude found in current measurement window
2243
volatile 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.
2444
int buf[buf_len]; // ring buffer for moving averages / sums
2545
int pos = 0; // current buffer position
2646
int attack_mova = 0; // moving average (actually sum) of amplitudes over past attack period
2747
int release_mova = 0; // moving average (actually sum) of amplitudes over past release period
48+
int32_t now = 0; // start time of current loop
2849
int32_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
3354
int 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+
3666
void 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

61106
void 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

Comments
 (0)