/* 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 <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <tables/waveshape_chebyshev_3rd_256_int8.h>
#include <tables/waveshape_chebyshev_6th_256_int8.h>
#include <tables/waveshape_compress_512_to_488_int16.h>
#include <tables/waveshape2_softerclip_int8.h>
#include <tables/saw_analogue512_int8.h>
#include <MozziGuts.h>
#include <Oscil.h>
#include <WaveShaper.h>
#include <EventDelay.h>
#include <utils.h>
#include <Smooth.h>
#include <fixedMath.h> // for fractional modulation frequency
#include <Ead.h> // 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 <SIN2048_NUM_CELLS, AUDIO_RATE> aSinGuitar1(SIN2048_DATA); // sine wave sound source
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSinGuitar2(SIN2048_DATA);
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSinGuitar3(SIN2048_DATA);
Oscil <SAW_ANALOGUE512_NUM_CELLS, AUDIO_RATE> aSawBass(SAW_ANALOGUE512_DATA);
WaveShaper <int> aCompress(WAVESHAPE_COMPRESS_512_TO_488_DATA); // to compress instead of dividing by 2 after adding signals
WaveShaper <char> aGuitar1Shaper(WAVESHAPE2_SOFTERCLIP_DATA);
WaveShaper <char> aGuitar2Shaper(WAVESHAPE2_SOFTERCLIP_DATA);
WaveShaper <char> 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 guitar1Val = 0, guitar2Val = 0, guitar3Val = 0, bassVal = 0;
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;
const unsigned char blackKeys[12] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0};
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);
pinMode(A5, OUTPUT);
digitalWrite(A5, LOW);
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 */
analogRead(A5);
pitch = analogRead(PITCH_PIN); // Range: 0 to 1023
analogRead(A5);
volume = analogRead(VOLUME_PIN) >> 2; // Range: 0 to 255
analogRead(A5);
decay = analogRead(DECAY_PIN); // Range: 0 to 1023
analogRead(A5);
tuning = analogRead(TUNING_PIN); // Range: 0 to 1023
/* Read digital sensor data (buttons) */
guitar1Val = digitalRead(GUITAR1_PIN);
guitar2Val = digitalRead(GUITAR2_PIN);
guitar3Val = digitalRead(GUITAR3_PIN);
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);
/* Calculate the ratios for each of the strings to create a harmony.
* Ratios:
* Octave --> 2
* Fifth --> 3/2
* Fourth --> 4/3
* Major Third --> 5/4
* Minor Third --> 6/5
* Major 2nd --> 9/8
* Tritone --> sqrt(2) or ~45/32
*/
aSawBass.setFreq_Q16n16(baseFrequency);
aSinGuitar1.setFreq_Q16n16(baseFrequency * 3);
aSinGuitar2.setFreq_Q16n16(baseFrequency * 4);
aSinGuitar3.setFreq_Q16n16(baseFrequency * 5);
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;
}
// Given a MIDI input pitch, round it to the nearest note of a
// major scale starting at MIDI_NOTE_MIN.
Q16n16 restrictMidiToMajorScale(Q16n16 input) {
int pitchClass = ((input - MIDI_NOTE_MIN) >> 16) % 12;
// If it's a chromatic pitch (black key), return the note below
// (the nearest white key), otherwise return the current note.
if(blackKeys[pitchClass]) {
return input - (1L << 16);
}
else {
return input;
}
}
int updateAudio(){
char aGuitar1 = 0, aGuitar2 = 0, aGuitar3 = 0, aBass = 0;
aGuitar1 = aSinGuitar1.next(); // sine wave source
aGuitar2 = aSinGuitar2.next(); // sine wave source
aGuitar3 = aSinGuitar3.next(); // sine wave source
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();
}