Minimalist Arduino DSP (MiniArDSP)
About Minimalist Arduino DSP
This is the the most basic setup for doing decent quality audio effects with an Arduino. The system is based upon both our PWM tutorial and our ATmega ADC tutorial. The basic setup is a 10 bit value read in from ADC0, and played out via a 16 bit, Dual PWM on pins 9 and 10. there are 2 buttons, on pins 2 and 3, which can be used to modify parameters. The reason for buttons and not knobs, is that the ADC is already completely consumed with the analog audio signal coming in. This is a more focused version of our basic Arduino PWM setup, targetted at effects pedals. For a finalized version with filters, amplifiers, feedback and mix, check out our Stomp Shield.
Hardware setup
The following schematic is the basic version to get started. There will eventually be a better version with a dual opamp, and gain, mix, feedback, and level knobs. But that might take a bit to finish. The 3.9k and 1M resistors should be 1% (or better) if possible. Also, the input signal needs to be from a mixer or other powered device with a low output impedance and strong signal. Plugging a guitar in won't work with this version.
Sound Sample
To give an idea of what is possible with this minimal setup, here is a sound sample. It was recorded using a sightly better analog setup than shown above. It uses 3-pole anti-aliasing filters on the input and output, and has mix and feedback knobs. The signal was taken directly off a guitar, and recorded into a computer. The first 2 sound bits demonstrate the noise level and frequency response of a direct pass through of the signal. The last sound chunk is a flanger effect.
Arduino library
The following is an Arduino library (just a first rev, nothing fancy). Download the zipped file, and uncompress it. The entire folder needs to be placed in you libraries folder in your user directory. If you are not sure where this is, check out our description in the CodecShield Wiki.
Example code
Right now there is only one example in the library, and it is a tremolo effect. It has two buttons, one to increase the tremolo rate, the other to decrease it. It is a full depth tremolo, so you will have to mix some of the original signal back in to get a lower depth. The buttons are very hacked at this point, they need to be tapped rather quickly to make small changes.
1 // mini_tremolo.pde
2 // minimalisitic tremelo program
3 // guest - openmusiclabs.com - 1.13.13
4 // takes in audio data from the ADC and plays it out on
5 // Timer1 PWM. 16b, Phase Correct, 31.25kHz. applies a variable
6 // frequency tremolo to the sound.
7
8 #include "MiniArDSP.h"
9
10 #define PWM_FREQ 0x00FF // pwm frequency - see table
11 #define PWM_MODE 0 // Fast (1) or Phase Correct (0)
12 #define PWM_QTY 2 // number of pwms, either 1 or 2
13
14 // create sinewave lookup table
15 // PROGMEM stores the values in the program memory
16 // it is automatically included with MiniArDSP.h
17 PROGMEM prog_int16_t sinewave[] = {
18 // this file is stored in MiniArDSP and is a 1024 value
19 // sinewave lookup table of signed 16bit integers
20 // you can replace it with your own waveform if you like
21 #include <sinetable.inc>
22 };
23 unsigned int location; // lookup table value location
24
25 byte rate = 5; // tremolo rate
26 byte rate_counter; // modifiable version of rate
27 unsigned int amplitude; // current tremolo amplitude
28 unsigned int button;
29
30 void setup() {
31 // setup button pins
32 PORTD |= 0x0c; // turn on pullups for pins 2 and 3
33
34 // setup ADC
35 ADMUX = 0x60; // left adjust, adc0, internal vcc
36 ADCSRA = 0xe5; // turn on adc, ck/32, auto trigger
37 ADCSRB =0x07; // t1 capture for trigger
38 DIDR0 = 0x01; // turn off digital inputs for adc0
39
40 // setup PWM
41 TCCR1A = (((PWM_QTY - 1) << 5) | 0x80 | (PWM_MODE << 1)); //
42 TCCR1B = ((PWM_MODE << 3) | 0x11); // ck/1
43 TIMSK1 = 0x20; // interrupt on capture interrupt
44 ICR1H = (PWM_FREQ >> 8);
45 ICR1L = (PWM_FREQ & 0xff);
46 DDRB |= ((PWM_QTY << 1) | 0x02); // turn on outputs
47
48 TIMSK0 = 0; // turn of t0 - no delay() or millis()
49 sei(); // turn on interrupts - not really necessary with arduino
50 }
51
52 void loop() {
53 while(1); // gets rid of jitter
54 // nothing happens up here. if you want to put code up here
55 // get rid of the ISR_NAKED and the reti(); below
56 }
57
58 ISR(TIMER1_CAPT_vect, ISR_NAKED) { // ISR_NAKED is used to save
59 // clock cycles, but prohibits code in the loop() section.
60
61 // get ADC data
62 byte temp1 = ADCL; // you need to fetch the low byte first
63 byte temp2 = ADCH; // yes it needs to be done this way
64 int input = ((temp2 << 8) | temp1) + 0x8000; // make a signed 16b value
65
66 button--; // check buttons every so often
67 if (button == 0) {
68 byte temp3 = PIND & 0x0c; // get pin 2 and 3 values
69 if (temp3 == 0) { // both buttons pressed
70 } // do nothing
71 else if (temp3 == 0x08) { // up button pressed
72 if (rate == 1); // do nothing
73 else rate--; // make tremolo faster
74 }
75 else if (temp3 == 0x04) { // down button pressed
76 if (rate == 255); // do nothing
77 else rate++; // make tremolo slower
78 }
79 button = 0x0400; // reset counter
80 }
81
82 rate_counter--; // decrement our counter
83 if (rate_counter == 0) {
84 // create a variable frequency and amplitude sinewave
85 // fetch a sample from the lookup table
86 amplitude = pgm_read_word_near(sinewave + location) + 0x8000;
87 // the + 0x8000 turns the signed value to unsigned
88 location++;
89 // if weve gone over the table boundary -> loop back
90 // around to the other side.
91 location &= 0x03ff; // fast way of doing rollover for 2^n numbers
92 rate_counter = rate; // reset rate counter
93 }
94
95
96 // multiply our input by the sinewave value
97 // note the use of the special multiply macro
98 // this is faster than the normal * method
99 // this is a signed * unsigned, returning the high 16 bits
100 int output; // define variable before using it
101 MultiSU16X16toH16(output, input, amplitude);
102
103 // output data
104 OCR1AL = (output + 0x8000) >> 8; // convert to unsigned
105 // and only output the top byte
106 OCR1BL = output; // output the bottom byte
107 reti(); // return from interrupt - required because of ISR_NAKED
108 }
109