/* Arduino "guitar" based on Mozzi audio library by Tim Barrass. * * Code by Andrew McPherson, Queen Mary University of London, September 2012 * * http://www.eecs.qmul.ac.uk/ * * Circuit: * audio output on pin 9 (to filter and LM386 amplifier) * analog inputs (knobs/sliders) on pins A0-A3 * digital inputs (buttons) on pins 2-5 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for fractional modulation frequency #include // exponential attack decay #define CONTROL_RATE 128 // powers of 2 please // Pin definitions for analog and digital inputs: #define PITCH_PIN A0 #define VOLUME_PIN A1 #define DECAY_PIN A2 #define TUNING_PIN A3 #define GUITAR1_PIN 2 #define GUITAR2_PIN 3 #define GUITAR3_PIN 4 #define BASS_PIN 5 // Basic pitch of the instrument (MIDI 45 = A2) #define MIDI_NOTE_MIN (Q16n16)(33L << 16L) Oscil aSinGuitar1(SIN2048_DATA); // sine wave sound source Oscil aSinGuitar2(SIN2048_DATA); Oscil aSinGuitar3(SIN2048_DATA); Oscil aSawBass(SAW_ANALOGUE512_DATA); WaveShaper aCompress(WAVESHAPE_COMPRESS_512_TO_488_DATA); // to compress instead of dividing by 2 after adding signals WaveShaper aGuitar1Shaper(WAVESHAPE2_SOFTERCLIP_DATA); WaveShaper aGuitar2Shaper(WAVESHAPE2_SOFTERCLIP_DATA); WaveShaper aGuitar3Shaper(WAVESHAPE2_SOFTERCLIP_DATA); Ead kEnvelopeGuitar1(CONTROL_RATE); Ead kEnvelopeGuitar2(CONTROL_RATE); Ead kEnvelopeGuitar3(CONTROL_RATE); Ead kEnvelopeBass(CONTROL_RATE); /* Parameters controlled by analog inputs */ unsigned long pitch; unsigned char volume; unsigned int decay; unsigned int tuning; unsigned char lastGuitar1Val = 0, lastGuitar2Val = 0, lastGuitar3Val = 0, lastBassVal = 0; unsigned int attackMilliseconds = 20, decayMilliseconds = 3000; unsigned int lastDecayValue = 0; int gainGuitar1, gainGuitar2, gainGuitar3, gainBass; void updateAttackDecayValues() { kEnvelopeGuitar1.set(attackMilliseconds, decayMilliseconds); kEnvelopeGuitar2.set(attackMilliseconds, decayMilliseconds); kEnvelopeGuitar3.set(attackMilliseconds, decayMilliseconds); kEnvelopeBass.set(attackMilliseconds*4, decayMilliseconds*2); } void setup(){ startMozzi(CONTROL_RATE); aSinGuitar1.setFreq(110u); // set the frequency with an unsigned int or a float aSinGuitar2.setFreq(165u); aSinGuitar3.setFreq(275u); aSawBass.setFreq(55u); updateAttackDecayValues(); setupFastAnalogRead(); Serial.begin(57600); } void updateControl() { static Q16n16 midiNote, midiNoteInt, baseFrequency; static long midiNoteFrac; /* Read sensor data and update audio parameters accordingly */ pitch = analogRead(PITCH_PIN); // Range: 0 to 1023 volume = analogRead(VOLUME_PIN) >> 2; // Range: 0 to 255 decay = analogRead(DECAY_PIN); // Range: 0 to 1023 tuning = analogRead(TUNING_PIN); // Range: 0 to 1023 /* Read digital sensor data (buttons) */ unsigned char guitar1Val = digitalRead(GUITAR1_PIN); unsigned char guitar2Val = digitalRead(GUITAR2_PIN); unsigned char guitar3Val = digitalRead(GUITAR3_PIN); unsigned char bassVal = digitalRead(BASS_PIN); /* Only change the decay value if a significant change has occurred, * to filter out noise on the input. This will reduce unneeded calculations. */ if(abs(decay - lastDecayValue) > 4) { decayMilliseconds = decay << 2; lastDecayValue = decay; updateAttackDecayValues(); } /* Values that are high now and were low last cycle mean that * a button has been pushed. Start the corresponding string. */ if(guitar1Val == HIGH && lastGuitar1Val == LOW) { kEnvelopeGuitar1.start(); } if(guitar2Val == HIGH && lastGuitar2Val == LOW) { kEnvelopeGuitar2.start(); } if(guitar3Val == HIGH && lastGuitar3Val == LOW) { kEnvelopeGuitar3.start(); } if(bassVal == HIGH && lastBassVal == LOW) { kEnvelopeBass.start(); } lastGuitar1Val = guitar1Val; lastGuitar2Val = guitar2Val; lastGuitar3Val = guitar3Val; lastBassVal = bassVal; /* pitch: 0 = minimum value; 1024 = +32 semitones (~2.5 octaves) * 6 bits left to get to decimal point + 5 bits more = 11 bit shift */ midiNote = MIDI_NOTE_MIN + (Q16n16)(pitch << 11); /* Set the amount the pitch is quantized. */ midiNoteInt = (midiNote + 0x00008000) & 0xFFFF0000; // Nearest MIDI note (Q16n16 rounding) midiNoteFrac = (long)midiNote - (long)midiNoteInt; /* Fractional part only */ midiNoteFrac = (midiNoteFrac * (1023 - tuning)) >> 10; /* Scale the fractional part */ midiNote = midiNoteInt + midiNoteFrac; baseFrequency = Q16n16_mtof(midiNote); aSinGuitar1.setFreq_Q16n16(baseFrequency * 3); aSinGuitar2.setFreq_Q16n16(baseFrequency << 2); aSinGuitar3.setFreq_Q16n16(baseFrequency * 5); aSawBass.setFreq_Q16n16(baseFrequency); // down 1 octave gainGuitar1 = kEnvelopeGuitar1.next(); gainGuitar2 = kEnvelopeGuitar2.next(); gainGuitar3 = kEnvelopeGuitar3.next(); gainBass = kEnvelopeBass.next(); if(gainGuitar1 > 96) gainGuitar1 = 96; if(gainGuitar2 > 96) gainGuitar2 = 96; if(gainGuitar3 > 96) gainGuitar3 = 96; } int updateAudio(){ char aGuitar1 = aSinGuitar1.next(); // sine wave source char aGuitar2 = aSinGuitar2.next(); // sine wave source char aGuitar3 = aSinGuitar3.next(); // sine wave source char aBass = aSawBass.next(); // sawtooth wave source int aGuitar1Shaped = aGuitar1Shaper.next(aGuitar1); int aGuitar2Shaped = aGuitar2Shaper.next(aGuitar2); int aGuitar3Shaped = aGuitar3Shaper.next(aGuitar3); long waveSum = (gainGuitar1*aGuitar1Shaped + gainGuitar2*aGuitar2Shaped + gainGuitar3*aGuitar3Shaped + gainBass*aBass) >> 8; waveSum = (waveSum * volume) >> 8; // use a waveshaping table to squeeze 2 summed 8 bit signals into the range -244 to 243 int waveshapedOut = aCompress.next(256u + waveSum); return waveshapedOut; } void loop(){ audioHook(); }