/*Guitar frequency to CV (1v/Oct) v2.0 WORKING Mick Bailey December 26th 2023 The guitar input must be centred around a 2.5v DC bias point and amplified using ideally a rail-to-rail opamp. Code detects whether signal is rising or falling and calculates period accordingly on the basis of the first leading edge encountered. */ #define guitar_in A0 // guitar input #define clipping_LED 4 // clipping LED #define gate_LED 5 // gate LED #define trigger_out 6 // trigger output #define gate_out 7 // gate output #include Adafruit_MCP4725 MCP4725; uint8_t level; // guitar sample value uint8_t level_old = 128; // stores previous value for comparison uint8_t peak_high; // signal maximum peak value uint8_t peak_low; // signal minimum peak value uint8_t midline = 0; // calculate midline between peaks uint8_t trig_high; // calculated upper trigger level for timing routine uint8_t trig_low; // calculated lower trigger level for timing routine uint8_t amplitude; // amplitude calculation uint8_t amplitude_old; // stores previous amplitude value for comparison bool timeout_flag = 0; // indicates if a process has timed out bool note_status = 0; // indicates whether note is off or in progress bool signal_rising = 0; // signal rising or falling bool signal_direction_toggle = 0; // used to limit reading the signal direction to once only bool clipping_toggle = 0; // toggle for clipping LED function bool clipping_active = 0; // flag for clipping in progress bool trigger_active = 0; // flag to activate trigger pulse bool trigger_toggle = 0; // toggle for trigger output unsigned long clip_end_time; // end time for clipping LED timer unsigned long clip_start_time; // start time for clipping LED timer unsigned long timeout_start_time = millis(); // starting time for timeout routine unsigned long timeout_current_time = millis(); // current time for timeout routine unsigned long start_time_micros = micros(); // start time for period timing unsigned long stop_time_micros = micros(); // stop time for period timing unsigned long total_time; // integer for storing total time of a cycle unsigned long trigger_end = 0; // used to set the trigger duration end time const uint8_t arraySize = 49; // holds the number of samples in each array const uint8_t amplitude_new_note = 10; // sets the amplitude threshold for new note detection. Default 10 - decrease for more sensitivity const uint8_t amplitude_value = 25; // sets the amplitude threshold for note end - lower is more sensitive. Default 3 const uint8_t sampleDuration = 12; // based on lowest frequency period (bottom E)in milliseconds. Default 12 const uint8_t timeout_period = 20; // timeout value to prevent loop from waiting infinitely. In milliseconds. Default 14 const uint8_t sample_window = 10; // sample window +/- of midline used to determine signal direction //****************************************************************************** // Frequency and DAC array //****************************************************************************** int frequencies[arraySize] = { 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1046, 1109, 1175, 1245, 1319 }; int dacValues[arraySize] = { 478, 546, 616, 682, 754, 816, 891, 952, 1027, 1086, 1162, 1222, 1297, 1361, 1434, 1501, 1569, 1639, 1705, 1777, 1840, 1916, 1977, 2053, 2112, 2189, 2252, 2325, 2390, 2460, 2527, 2594, 2665, 2730, 2803, 2865, 2941, 3001, 3076, 3137, 3213, 3275, 3349, 3415, 3485, 3554, 3621, 3693, 3758 }; void setup() { pinMode(guitar_in, INPUT); //guitar input pin 0 // outputs pinMode (gate_LED, OUTPUT); // gate on LED pinMode (clipping_LED, OUTPUT); // clipping LED pinMode (gate_out, OUTPUT); // gate output pinMode (trigger_out, OUTPUT); // trigger output // set outputs low bitClear (PORTD, gate_out); // turn the Gate off bitClear (PORTD, gate_LED); // turn gate LED off bitClear (PORTD, trigger_out); // turn trigger out off bitClear (PORTD, clipping_LED); // turn clipping LED off MCP4725.begin(0x60); // set the I2C Address of the MCP4725 //Set ADC to 1MHz ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // clear prescaler bits ADCSRA |= bit (ADPS2); // set ADC prescale to 16 (1 MHz) // Serial.begin(9600); // open the serial port at 9600 bps: - for testing only - comment out afterwards } void loop() { //****************************************************************************** // sample guitar for specified duration //****************************************************************************** // initialise variables peak_high = 0; peak_low = 255; // Get the start time unsigned long start_time = millis(); while (millis() - start_time < sampleDuration) { level = analogRead(guitar_in) >> 2; // Keep only the 8 most significant bits of 10 bits read // Update maximum and minimum values if (level > peak_high) { peak_high = level; } if (level < peak_low) { peak_low = level; } } //****************************************************************************** // Calculate midline, trigger levels & amplitude //****************************************************************************** midline = (peak_high - peak_low) / 2 + peak_low; // calculate mid point of waveform trig_low = (midline + peak_low) / 2; // calculate low trigger level (midpoint between midline and peak_low) trig_high = (midline + peak_high) / 2; // calculate high trigger level (midpoint between midline and peak_high) //calculate amplitude amplitude = abs(peak_high - peak_low); // check if new note detected before old one has ended if (amplitude >= (amplitude_old + amplitude_new_note)) { note_reset(); } //Serial.println(amplitude); //Serial.println(""); //delay(500); //****************************************************************************** // Check for clipping //****************************************************************************** if ((peak_high == 255) || (peak_low == 0)) // check signal level { clipping_active = 1; // signal is clipping } if (clipping_active == 1) { clipping(); // run clipping LED function } if ((note_status == 0) && (timeout_flag == 0) && (amplitude >= amplitude_value)) // only perform calculations & DAC output if note is new { //****************************************************************************** // determine if waveform is within range //****************************************************************************** if ((level > (midline + sample_window)) && (level > (midline - sample_window)) && (timeout_flag == 0)) // Signal is outside the required range { timeout_start_time = millis(); // Reset the timeout timer do { level = analogRead(guitar_in) >> 2; // read guitar signal timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= 8) // check if process timed out { timeout_flag = 1; break; } } while ((level > (midline + sample_window)) && (level > (midline - sample_window)) && (timeout_flag == 0)); // keep sampling until signal in range or process has already timed out } level_old = level ; // save level for comparison //****************************************************************************** // determine if waveform is rising or falling //****************************************************************************** if ((level < (midline + sample_window)) && (level > (midline - sample_window)) && (timeout_flag == 0)) { // Signal is within the trigger range timeout_start_time = millis(); // Reset the timeout timer do { level = analogRead(guitar_in) >> 2; // read guitar signal timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= 8) // check if process timed out { timeout_flag = 1; break; } } while ((level == level_old) && (timeout_flag == 0)); // keep sampling until signal change detected or process has already timed out if (level != level_old) { // Signal has changed if (signal_direction_toggle == 0) // read signal direction once only until flag is reset for new note { if (level > level_old) // is signal rising? { signal_rising = 1; } if (level < level_old) // signal is falling { signal_rising = 0; } signal_direction_toggle = 1; // process only runs once until note is reset } } } //****************************************************************************** // note timing if edge is rising //****************************************************************************** if (signal_rising == 1) { timeout_start_time = millis(); // Get the timeout start time in milliseconds while ((analogRead(guitar_in) >> 2 < trig_high) && (timeout_flag == 0)) // Wait for the signal to reach trig_high if not already timed out { timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= timeout_period) { timeout_flag = 1; break; } } start_time_micros = micros(); // Start the timer when the signal reaches trig_high timeout_start_time = millis(); // Get the timeout start time in milliseconds while ((analogRead(guitar_in) >> 2 > trig_low) && (timeout_flag == 0)) // Wait for the signal to drop below trig_low again if not already timed out { timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= timeout_period) { timeout_flag = 1; break; } } timeout_start_time = millis(); // Get the timeout start time in milliseconds while ((analogRead(guitar_in) >> 2 < trig_high) && (timeout_flag == 0)) // Wait for the signal to reach trig_high if not already timed out { timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= timeout_period) { timeout_flag = 1; break; } } stop_time_micros = micros(); // Stop the timer when the signal reaches trig_high again total_time = stop_time_micros - start_time_micros; // Calculate the time for 1 cycle } //****************************************************************************** // note timing if edge is falling //****************************************************************************** if (signal_rising == 0) // waveform is falling edge { timeout_start_time = millis(); // Get the timeout start time in milliseconds while ((analogRead(guitar_in) >> 2 > trig_low) && (timeout_flag == 0)) // Wait for the signal to reach trig_low if not already timed out { timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= timeout_period) { timeout_flag = 1; break; } } start_time_micros = micros(); // Start the timer when the signal reaches trig_low timeout_start_time = millis(); // Get the timeout start time in milliseconds while ((analogRead(guitar_in) >> 2 < trig_high) && (timeout_flag == 0)) // Wait for the signal to reach trig_high again if not already timed out { timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= timeout_period) { timeout_flag = 1; break; } } timeout_start_time = millis(); // Get the timeout start time in milliseconds while ((analogRead(guitar_in) >> 2 > trig_low) && (timeout_flag == 0)) // Wait for the signal to reach trig_low if not already timed out { timeout_current_time = millis(); // update current time if (timeout_current_time - timeout_start_time >= timeout_period) { timeout_flag = 1; break; } } stop_time_micros = micros(); // Stop the timer when the signal reaches trig_high again total_time = stop_time_micros - start_time_micros; // Calculate the time for 1 cycle } } //****************************************************************************** // Perform note calculation //****************************************************************************** if ((note_status == 0) && (timeout_flag == 0) && (amplitude >= amplitude_value)) // re-check in case something has changed { // frequency calculation and DAC output unsigned long frequency = 1000000 / total_time; // calculate frequency // Find the closest frequency in the array int index = findNearestFrequency(frequency); // Get the corresponding DAC value & output to DAC int dac_output = dacValues[index]; dac_output = max(dac_output, 478); // limit the minimum DAC value to 478 (lowest note in lookup table) dac_output = min(dac_output, 3758); // limit the maximum DAC value to 3758 (highest note in lookup table) MCP4725.setVoltage(dac_output, false); // send output to DAC bitSet (PORTD, gate_out); // turn the Gate on bitSet (PORTD, gate_LED); // turn LED on note_status = 1; // note in progress trigger_active = 1; } if (timeout_flag == 1) // check if waveform timing is unsuccessful - if so, reset for new note { note_reset(); } if ((note_status == 1) && (trigger_active == 1)) { trigger(); } if (amplitude < amplitude_value) // check if note has decayed { note_reset(); } amplitude_old = amplitude; } //****************************************************************************** // note reset function //****************************************************************************** void note_reset() { note_status = 0; bitClear (PORTD, gate_out); // turn the Gate off bitClear (PORTD, gate_LED); // turn LED off // reset values timeout_flag = 0; signal_direction_toggle = 0; } //****************************************************************************** // Function to find the nearest frequency in the array //****************************************************************************** int findNearestFrequency(int target) { int index = 0; int minDiff = abs(target - frequencies[0]); for (int i = 1; i < arraySize; i++) { int diff = abs(target - frequencies[i]); if (diff < minDiff) { minDiff = diff; index = i; } } return index; } //****************************************************************************** // Function for clipping indicator //****************************************************************************** void clipping() { if (clipping_toggle == 0) { digitalWrite(clipping_LED, HIGH); // turn LED on clip_end_time = (millis() + 300); clipping_toggle = 1; // disable further time updating for duration of routine } else { if (clipping_toggle == 1) { if (millis() >= clip_end_time) { digitalWrite(clipping_LED, LOW); // turn LED off after 200ms // reset for the next iteration clipping_toggle = 0; clipping_active = 0; } } } } //****************************************************************************** // Function for trigger pulse //****************************************************************************** void trigger() { if (trigger_toggle == 0) { // Check if trigger activated bitSet(PORTD, trigger_out); // turn the trigger on trigger_end = (millis() + 10); // trigger pulse duration (10ms) trigger_toggle = 1; // toggle to indicate that the trigger timer is activated } else { if (trigger_toggle == 1); { if (millis() >= trigger_end) // check if millis() has reached the trigger_end value { bitClear(PORTD, trigger_out); // turn the trigger output off // reset for the next iteration trigger_toggle = 0; trigger_active = 0; } } } }