/* ### arduino git2midi ### Arduino Git2Midi 2011 michael wolkstein wolke@k-27.de licensend under Attribution-NonCommercial 3.0 Unported Creative Commons license ## IMPORTANT license note! ## the git2midi code is mostly a copy from Frederic Hamel's arduino chromatic guitar tuner. so it is important to take care of the CC licence under which frederic published his code. see comment below. also, i really thx frediric for this nice peace of YIN2Arduino implementation miracle, which powered an arduino uno too an really fast guitar2midi converter. all other stuff which comes from me, is also licensed under Attribution-NonCommercial 3.0 Unported Creative Commons license: http://creativecommons.org/licenses/by-nc/3.0/ ## git2midi arduino uno pinning ## analog: A0 = guitar in IMPORTANT need an input amp which bring the git signal to a usefully level. also important, a build in low-pass at ~1khz. MOST IMPORTANT we need 2.5V DC offset on analog pin to centre the signal. digital: Pin 1 (TX) = midi out Pin 8 = ONE SHOT SCHWITCH (is LOW and need an 10k pull-up resistor to GND) Pin 9 = ONE SHOT MODE SCHWITCH (is LOW and need an 10k pull-up resistor to GND) Pin 12 = ONE SHOT MODE LED Pin 11 = ONE SHOT LED Pin 13 = Midi Note On/Off LED External Switches in analog circuit: Switch 1 = Guitar input on/off to enable/disable the git2midi converter and hold the bypass signal clean. Switch 2 = cut Guitar bypass to output jack. so we can turn off normal guitar signal and only play midi. ## arduino git2midi functions ## Toggle Switch 1: Enable the g2m :) and play midi mixed with analog guitar. Toggle Switch 2: Cut guitar bypass to disable guitar signal flow to analog guitar amp. Toggle Switch 3: Enable ONE SHOT MODE: in this mode the g2m do nothing and wait for pushing push-button/switch 4 Push Button/Switch 4: only active in ONE SHOT MODE. now it is possible to catch one Note by hold the button down. if you release the button and you have successfully catch a note the g2m hold the note until you catch an new note by pressing the push button again, or by disabling ONE SHOT MODE. ## Original comment by frederic hamel ## Arduino chromatic guitar tuner. 2008 Frederic Hamel As a modified version of the Arduino core library, the LiquidCrystal class is licensed under the LGPL. See attached license. The rest is released under the Attribution-NonCommercial 3.0 Unported Creative Commons license: http://creativecommons.org/licenses/by-nc/3.0/ See the project writeup: http://deambulatorymatrix.blogspot.com/2010/11/digital-chromatic-guitar-tuner-2008.html. Note that this code was more or less hacked together in a marathon along with the building of the actual device; it should *definitely* not be considered an example of production-quality code. For all pitch detection-related code, see the YIN paper: de Cheveigne, Alain and Kawahara, Hideki. "YIN, a fundamental frequency estimator for speech and music", Journal of the Acoustical Society of America, Vol 111(4), pp. 1917-30, April 2002. http://recherche.ircam.fr/equipes/pcm/cheveign/pss/2002_JASA_YIN.pdf */ // from wiring_private.h #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif #define ENABLE_DEBUG 0 static int const isMidiNotePlayingPin = 13; static int const isOnShotModePin = 12; static int const isOnShotPin = 11; static int const sw_oneShotModePin = 8; static int const sw_oneShotPin = 9; /////////////////////////////////////////////////////////////////////////////// // Utility stuff /////////////////////////////////////////////////////////////////////////////// // From http://www.arduino.cc/playground/Code/AvailableMemory // This function will return the number of bytes currently free in RAM. // written by David A. Mellis // based on code by Rob Faludi http://www.faludi.com // (reformatted for clarity) // Note: I believe this does not work on recent versions of the Arduino environment. // See the webpage above for alternatives. int AvailableMemory() { int size = 2048; byte* buf; while ((buf = (byte *) malloc(--size)) == NULL) ; free(buf); return size; } /////////////////////////////////////////////////////////////////////////////// // The Tuner /////////////////////////////////////////////////////////////////////////////// // Note Freq Cycles // A 110 145454.55 // A# 116.54 137290.81 // B 123.47 129585.27 // C 130.81 122312.21 // C# 138.59 115447.35 // D 146.83 108967.79 // D# 155.56 102851.9 // E 164.81 97079.26 // F 174.61 91630.62 // F# 185 86487.79 // G 196 81633.6 // G# 207.65 77051.86 // A 220 72727.27 static int const A440_NOTE = 69; static int const A880_NOTE = A440_NOTE + 12; float const QUARTERTONE_UP = 1.0293022366f; float const QUARTERTONE_DOWN = 0.9715319412f; float const SEMITONE_UP = 1.0594630944f; float const SEMITONE_DOWN = 0.9438743127f; typedef int Fixed; static int const FIXED_SHIFT = 5; static int const FIXED_ONE = (1 << FIXED_SHIFT); static int const FRAC_MASK = (1 << FIXED_SHIFT) - 1; static int const FIXED_SHIFT_HI = 8 - FIXED_SHIFT; static int const FIXED_MASK_HI = ~((1 << FIXED_SHIFT_HI) - 1); #define FIXED_INT(x) int(x >> FIXED_SHIFT) #define FIXED_FRAC(x) int(x & FRAC_MASK) #define I2FIXED(x) ((Fixed) ((x) << FIXED_SHIFT)) #define F2FIXED(x) ((Fixed) ((x) * (1 << FIXED_SHIFT))) #define FIXED2I(x) ((x) >> FIXED_SHIFT) #define FIXED2F(x) ((x) / float(1 << FIXED_SHIFT)) // Linearly interpolates between two 8-bit signed values. char InterpolateChar(char a, char b, char tFrac) { int d = b - a; return a + ((d * tFrac) >> FIXED_SHIFT); } class GitToMidi { public: GitToMidi(int audioPin) : m_audioPin(audioPin) , m_midiNote(-1) , m_a440(440.0f) , b_oneShotModeIsActive(false) //, b_oneShotModeIsRising(false) //, b_oneShotModeIsFalling(false) , b_oneShotActive(false) //, b_oneShotRising(false) //, b_oneShotFalling(false) , b_aNoteIsPlaying(false) { } ~GitToMidi() { } static int const PRESCALER = 0b00000111; static int const PRESCALER_DIVIDE = (1 << PRESCALER); static int const ADC_CLOCKS_PER_ADC_CONVERSION = 13; static unsigned long const CPU_CYCLES_PER_SAMPLE = ADC_CLOCKS_PER_ADC_CONVERSION * PRESCALER_DIVIDE; static unsigned long const SAMPLES_PER_SECOND = F_CPU / CPU_CYCLES_PER_SAMPLE; static int const MIN_FREQUENCY = 76; static int const MAX_FREQUENCY = 880; static int const MIN_SAMPLES = SAMPLES_PER_SECOND / MAX_FREQUENCY; static int const MAX_SAMPLES = SAMPLES_PER_SECOND / MIN_FREQUENCY; static int const WINDOW_SIZE = MAX_SAMPLES; //96; // samples static int const BUFFER_SIZE = WINDOW_SIZE + MAX_SAMPLES + 1; // for interpolation static int const AMPLITUDE_THRESHOLD = 30; static int const CORRELLATION_STEP = 2; // For pure sine, 8 is better than 10 or 12, which causes octave errors at higher frequencies // (>400 for 10, >350 for 12). // For guitar, sweet spot is between 1/2 and 1/3 static int const PRIME_THRESHOLD = (FIXED_ONE * 5) / 12; static int const OFFSET_STEP = 1; void Start() { // Enable auto-trigger enable sbi(ADCSRA, ADATE); // Set auto-trigger to free-running mode cbi(ADCSRB, ADTS0); cbi(ADCSRB, ADTS1); cbi(ADCSRB, ADTS2); // Select input channel + set reference to Vcc ADMUX = /*(0 << 6) |*/ (m_audioPin & 0x0f); ADMUX = (1 << 6) | (m_audioPin & 0x0f); // Disable other ADC channels (try to reduce noise?) DIDR0 = (0x3F ^ (1 << m_audioPin)); // Left-adjust result so we only have to read 8 bits sbi(ADMUX, ADLAR); // right-adjust for 8 bits // Setup the prescaler; divide by 32 unsigned char adcsra = ADCSRA; adcsra = ((adcsra & 0xF8) | PRESCALER); // mask off / re-set prescaler bits ADCSRA = adcsra; // Disable the conversion complete interrupt so we can read the flag cbi(ADCSRA, ADIE); // Interrupt Enable // Start the shebang sbi(ADCSRA, ADSC); } void Stop() { // Disable auto-trigger mode cbi(ADCSRA, ADATE); // Right-adjust result (default) cbi(ADMUX, ADLAR); } // The following two functions require proper setup in Start() unsigned int ReadInput8BitsUnsigned() { while ((ADCSRA & _BV(ADIF)) == 0) { } unsigned int result = ADCH; sbi(ADCSRA, ADIF); return result; } int ReadInput8BitsSigned() { return ReadInput8BitsUnsigned() - 128; } // Converts a fundamental frequency in Hz to a MIDI note index. Slow. int GetMidiNoteIndexForFrequency(float frequency) { if (frequency < 0.0f) { return -1; } frequency *= QUARTERTONE_DOWN; // kind of like rounding the note, half a semitone (it's a multiplication because of logarithmic space) int note = A440_NOTE; float a440 = m_a440; float a440_2 = 2.0f * a440; while (frequency < a440) { frequency *= 2.0f; note -= 12; } while (frequency > a440_2) { frequency *= 0.5f; note += 12; } while (frequency > a440) { frequency *= SEMITONE_DOWN; note += 1; } return note; } // Compute the fundamental frequency of a given MIDI note index. Slow. float GetFrequencyForMidiNoteIndex(int note) { if (note < 0.0f) { return -1.0f; } float result = m_a440; while (note < A440_NOTE) { note += 12; result *= 0.5f; } while (note > A880_NOTE) { note -= 12; result *= 2.0f; } while (note > A440_NOTE) { --note; result *= SEMITONE_UP; } return result; } unsigned long GetCorrellationFactorFixed(char* buffer, Fixed fixedOffset) { unsigned long result = 0; int integer = FIXED_INT(fixedOffset); int frac = FIXED_FRAC(fixedOffset); int correllationStep = CORRELLATION_STEP; correllationStep <<= 1; for (int i = 0; i < WINDOW_SIZE; i += correllationStep) { // Note this is done with 16-bit math; this is slower, but gives more precision. In tests, using 8-bit // math did not yield sufficient precision. int a = buffer[i]; int b = InterpolateChar(buffer[i + integer], buffer[i + integer + 1], frac); result += abs(b - a); } return result; } unsigned long GetCorrellationFactorPrime(unsigned long currentCorrellation, int numToDate, unsigned long sumToDate) { if (numToDate == 0) { return FIXED_ONE; } else { return ((currentCorrellation << FIXED_SHIFT) * numToDate) / sumToDate; } } // Compute the frequency corresponding to a given a fixed-point offset into our sampling buffer (usually where // the best/minimal autocorrellation was achieved). float GetFrequencyForOffsetFixed(Fixed offset) { float floatOffset = FIXED2F(offset); return F_CPU / (floatOffset * CPU_CYCLES_PER_SAMPLE); } float DetermineSignalPitch() { // Sample the signal into our buffer, and track its amplitude. m_maxAmplitude = 0; for (int i = 0; i < BUFFER_SIZE; ++i) { m_buffer[i] = ReadInput8BitsSigned(); if (abs(m_buffer[i]) > m_maxAmplitude) { m_maxAmplitude = abs(m_buffer[i]); } } float result = 0.0f; // If we haven't reached the amplitude threshold, don't try to determine pitch. if (m_maxAmplitude < AMPLITUDE_THRESHOLD) { result = -1.0f; } // Alright, now try to figure what the ballpark note this is by calculating autocorrellation Fixed bestOffset = 0; Fixed incrementAtBestOffset = 0; if (result >= 0.0f) { int numCorrellations = 0; unsigned long sumToDate = 0; bool inThreshold = false; unsigned long bestPrime = ~0; Fixed offsetStep = OFFSET_STEP; // If we're in MIDI mode, double the step; this will reduce precision, but increase speed (and thus reduce // latency). offsetStep <<= 1; Fixed offsetIncrement = offsetStep; for (Fixed offset = I2FIXED(MIN_SAMPLES); FIXED_INT(offset) < MAX_SAMPLES; offset += offsetIncrement) { unsigned long curCorrellation = GetCorrellationFactorFixed(m_buffer, offset); ++numCorrellations; sumToDate += curCorrellation; // Increment the increment right away, so we save the range appropriately for the refined search offsetIncrement += offsetStep; unsigned long prime = GetCorrellationFactorPrime(curCorrellation, numCorrellations, sumToDate); if (prime < bestPrime) { bestPrime = prime; bestOffset = offset; incrementAtBestOffset = offsetIncrement; } if (prime < PRIME_THRESHOLD) { inThreshold = true; } else if (inThreshold) // was in threshold, now exited, have best minimum in threshold { break; } } } if (FIXED_INT(bestOffset) == MAX_SAMPLES) { result = -1.0f; } //int const T1 = AMPLITUDE_THRESHOLD; if (m_maxAmplitude > AMPLITUDE_THRESHOLD) { if (result >= 0.0f) { // If we hit the end, assume no periodicity result = GetFrequencyForOffsetFixed(bestOffset); } } else { result = -1.0f; } return result; } ///------------------------------------------------------ void NoteOn() { //// wieder einkommentieren if(!b_aNoteIsPlaying){ #if ENABLE_DEBUG Serial.print(" NOTE_ON: "); Serial.println(m_midiNote); #else Serial.print(0x90, BYTE); // note on, channel 0 Serial.print(char(m_midiNote), BYTE); Serial.print(0x40, BYTE); // velocity 64 #endif digitalWrite(isMidiNotePlayingPin, HIGH); b_aNoteIsPlaying = true; } } void NoteOff() { if (m_midiNote >= 0 && b_aNoteIsPlaying) { #if ENABLE_DEBUG Serial.print("NOTE_OFF: "); Serial.println(m_midiNote); #else Serial.print(0x80, BYTE); // note off, channel 0; Serial.print(char(m_midiNote), BYTE); Serial.print(0x7F, BYTE); // velocity 127 #endif digitalWrite(isMidiNotePlayingPin, LOW); b_aNoteIsPlaying = false; m_midiNote = -1; } } ///------------------------------------------------------ void Go() { Start(); int i = 0; int noteRepeatCount = 0; #if ENABLE_DEBUG int decelerate = 0; #endif while(1) { #if ENABLE_DEBUG long milliseconds = millis(); #endif // check state of switches. this happens only one time on change switch state if(digitalRead(sw_oneShotModePin) == HIGH && !b_oneShotModeIsActive){ digitalWrite(isOnShotModePin, HIGH); b_oneShotModeIsActive = true; NoteOff();// close playing note and begin O-S-MODE }else if (digitalRead(sw_oneShotModePin) == LOW && b_oneShotModeIsActive) { digitalWrite(isOnShotModePin, LOW); b_oneShotModeIsActive = false; NoteOff();// close playing note to get silence and end the O-S-MODE } if(digitalRead(sw_oneShotPin) == HIGH && !b_oneShotActive ){ digitalWrite(isOnShotPin, HIGH); b_oneShotActive = true; NoteOff(); }else if (digitalRead(sw_oneShotPin) == LOW && b_oneShotActive) { digitalWrite(isOnShotPin, LOW); b_oneShotActive = false; } // ~check state of switches m_newMidiNote = false; m_lastMaxAmplitude = 0; float instantFrequency = DetermineSignalPitch(); m_tunerNote = GetMidiNoteIndexForFrequency(instantFrequency); // no new notes if O-S-Mode is active. if(b_oneShotModeIsActive && !b_oneShotActive) m_tunerNote = -1; #if ENABLE_DEBUG if(decelerate >= 500){ Serial.println(AvailableMemory()); decelerate = 0; } decelerate++; #endif if (m_maxAmplitude - m_lastMaxAmplitude > 25) { m_newMidiNote = true; } if (m_tunerNote >= 0) { if (m_tunerNote != m_midiNote) { NoteOff(); m_midiNote = m_tunerNote; noteRepeatCount = 0; } else { ++noteRepeatCount; } if ((m_newMidiNote && (noteRepeatCount == 1)) || (!m_newMidiNote && (noteRepeatCount == 3))) { #if ENABLE_DEBUG Serial.println(millis()-milliseconds); #endif NoteOn(); if (m_newMidiNote) { // Skip the repeat noteRepeatCount = 5; m_newMidiNote = false; } } } if(m_maxAmplitude <= 1 && b_aNoteIsPlaying && !b_oneShotModeIsActive) { NoteOff(); } m_lastMaxAmplitude = m_maxAmplitude; // at least disable all temp states of switches //b_oneShotModeIsRising = false; //b_oneShotModeIsFalling = false; //b_oneShotRising = false; //b_oneShotFalling = false; // ~at least disable all temp states of switches } Stop(); } private: int m_audioPin; char m_buffer[BUFFER_SIZE]; int m_lastLcdTickX; float m_a440; int m_midiNote; int m_tunerNote; int m_maxAmplitude; int m_lastMaxAmplitude; bool m_newMidiNote; bool b_oneShotModeIsActive; //bool b_oneShotModeIsRising; //bool b_oneShotModeIsFalling; bool b_oneShotActive; //bool b_oneShotRising; //bool b_oneShotFalling; bool b_aNoteIsPlaying; }; /////////////////////////////////////////////////////////////////////////////// // Main stuff /////////////////////////////////////////////////////////////////////////////// void setup() // run once, when the sketch starts { #if ENABLE_DEBUG Serial.begin(9600); // for debugging #else Serial.begin(31250); // for MIDI #endif pinMode(isMidiNotePlayingPin, OUTPUT); // sets the digital pin as output pinMode(isOnShotModePin, OUTPUT); // sets the digital pin as output pinMode(isOnShotPin, OUTPUT); pinMode(sw_oneShotModePin, INPUT); pinMode(sw_oneShotPin, INPUT); #if ENABLE_DEBUG digitalWrite(isMidiNotePlayingPin, LOW); digitalWrite(isOnShotModePin, LOW); digitalWrite(isOnShotPin, LOW); #else ledIntro(); #endif } void loop() { GitToMidi GitToMidi(0); // tried ADC pins 0, 3, 5, none is better GitToMidi.Go(); } void ledIntro() { for(int i =0;i<=10;i++){ digitalWrite(isMidiNotePlayingPin, HIGH); delay(17*i); digitalWrite(isOnShotModePin, HIGH); delay(17*i); digitalWrite(isOnShotPin, HIGH); delay(17*i); digitalWrite(isMidiNotePlayingPin, LOW); delay(17*i); digitalWrite(isOnShotModePin, LOW); delay(17*i); digitalWrite(isOnShotPin, LOW); delay(17*i); } delay(500); digitalWrite(isMidiNotePlayingPin, HIGH); digitalWrite(isOnShotModePin, HIGH); digitalWrite(isOnShotPin, HIGH); delay(1000); digitalWrite(isMidiNotePlayingPin, LOW); digitalWrite(isOnShotModePin, LOW); digitalWrite(isOnShotPin, LOW); }