Skip to content

Commit c2fbc04

Browse files
committed
First version.
Essentials working, including compile-time configurable settings. Status indication and runtime configuration not yet implemented. Hardware description to follow, but the basic idea is very simple: - Connect pin A0 to audio in, biased to 2.5V - Connect pin D3 to the gate of two 2n7000 N-Fets, back to back, switchin the signal on an off at a high rate
0 parents  commit c2fbc04

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

Readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
== Very low part count audio compressor based on Arduino ==
2+
3+
Hardware description to follow, but the basic idea is very simple:
4+
- Connect pin A0 to audio in, biased to 2.5V
5+
- Connect pin D3 to the gate of two 2n7000 N-Fets, back to back, switchin the signal on an off at a high rate

compressor.ino

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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.
3+
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.
5+
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
8+
// peak-to-peak.
9+
float dampening = .5; // dampening applied to signals exceeding the threshold. 0 corresponds to no compression, 1 corresponds
10+
// to limiting the signal to the threshold value (if possible: see below)
11+
const int duty_min = 10; // ceiling value for attenuation (lower values = more attenuation, 0 = off, 255 = no attenuation)
12+
// 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.
14+
const int duty_warn = 2 * duty_min; // See above. At attenuation beyond this (i.e. smaller numbers), warning LED will flash.
15+
// 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.
17+
const int signal_warn = 300; // A warning LED will flash for signals exceeding this amplitude (5mv per unit, peak-to-peak) as
18+
// it is probably (almost) too much for the circuit too handle (default value corresponds to about +-750mV
19+
// in order to stay below typical 2N7000 body diode forward voltage drop of .88V)
20+
21+
volatile int cmin = 1024; // minimum amplitude found in current measurement window
22+
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.
24+
int buf[buf_len]; // ring buffer for moving averages / sums
25+
int pos = 0; // current buffer position
26+
int attack_mova = 0; // moving average (actually sum) of amplitudes over past attack period
27+
int release_mova = 0; // moving average (actually sum) of amplitudes over past release period
28+
int32_t last = 0; // time of last loop
29+
int duty = 255; // current attenuation duty cycle (0: hard off, 255: no attenuation)
30+
31+
#define DEBUG 1 // serial communication appear to introduce audible noise ("ticks"), thus diabled by default
32+
#if DEBUG
33+
int it = 0;
34+
#endif
35+
36+
void setup() {
37+
for (int i = 0; i < buf_len; ++i) { // clear buffer
38+
buf[i] = 0;
39+
}
40+
#if DEBUG
41+
Serial.begin (9600);
42+
#endif
43+
44+
// start fast pwm with no prescaler (~62kHz) on pin 3, controlling the attenuator switch(es)
45+
pinMode(3, OUTPUT);
46+
TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
47+
TCCR2B = _BV(CS20); // Prescale factor 1
48+
OCR2B = 255; // 100% duty cycle, initially
49+
50+
// setup fast continuous analog input sampling. Kudos go to https://meettechniek.info/embedded/arduino-analog.html
51+
DIDR0 = 0x3F; // digital inputs disabled
52+
ADMUX = 0b01000000; // measuring on ADC0, use 5v reference
53+
ADCSRA = 0xAC; // AD-converter on, interrupt enabled, prescaler = 16 --> aroudn 77k samples per second
54+
ADCSRB = 0x40; // AD channels MUX on, free running mode
55+
bitWrite(ADCSRA, 6, 1); // Start the conversion by setting bit 6 (=ADSC) in ADCSRA
56+
sei(); // set interrupt flag
57+
58+
last = millis ();
59+
}
60+
61+
void loop() {
62+
int32_t now = millis ();
63+
if (now < last || now - last > window_ms) { // measurment window elapsed (or timer overflow)
64+
last = now;
65+
} else return;
66+
67+
#if DEBUG
68+
if (++it == 40) {
69+
it = 0;
70+
Serial.print (cmax);
71+
Serial.print ("-");
72+
Serial.print (cmin);
73+
Serial.print ("-");
74+
Serial.println (duty);
75+
}
76+
#endif
77+
78+
// get amplitude in current meausrement window, and set up next window
79+
if (++pos >= buf_len) pos = 0;
80+
int val = cmax - cmin;
81+
if (val < 0) val = 0;
82+
cmax = 0;
83+
cmin = 1024;
84+
85+
// update the two moving averages (sums)
86+
int old_pos = pos - attack_f;
87+
if (old_pos < 0) old_pos += buf_len;
88+
attack_mova += val - buf[old_pos];
89+
old_pos = pos - release_f;
90+
if (old_pos < 0) old_pos += buf_len;
91+
release_mova += val - buf[old_pos];
92+
93+
// store new value in ring buffer
94+
buf[pos] = val;
95+
96+
// calculate new attenuation settings
97+
// first caculate based on attack period
98+
const int attack_threshold = threshold * attack_f;
99+
const float attack_overshoot = max (0, (attack_mova - attack_threshold) / (float) attack_threshold);
100+
const int attack_duty = 255 / (attack_overshoot * dampening + 1);
101+
// if the new duty setting is _below_ the current, based on attack period, check release window to see, if
102+
// the time has come to release attenuation, yet:
103+
if (attack_duty >= duty) duty = attack_duty;
104+
else {
105+
const int release_threshold = threshold * release_f;
106+
const float release_overshoot = max (0, (release_mova - release_threshold) / (float) release_threshold);
107+
const int release_duty = 255 / (release_overshoot * dampening + 1);
108+
if (release_duty < duty) duty = release_duty;
109+
}
110+
111+
OCR2B = duty; // enable the new duty cycle
112+
}
113+
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;
120+
}
121+

0 commit comments

Comments
 (0)