Sunday, May 27, 2018

Pitch CV to Frequency Conversion via Voltage Division and ADC

One simple way of converting a pitch CV signal to a frequency value is to divide the pitch CV signal and then measure the voltage. In this case, a Teensy LC is used.

This method makes a few assumptions:
• That a voltage of 0V is equal to MIDI note 36
• That all pitch CV signals will unipolar, with negative voltages being clamped to 0V
• The pitch CV signal will be 1 volt per octave
• The pitch CV signal will be roughly in the range of 0 - 9.9V

The pitch CV signal is divided by 3 by using a resistor voltage divider, where 20k is on the input side and 10k goes to ground - this will then equate to 1/3V per octave. The output of this voltage divider is then fed through some clamping diodes (such as 1n4148), to limit the range to 0V - 3.3V. The voltage is then read as a 12-bit analog to digital value.

The value range of the ADC conversion is 0 - 4095, and this represents a voltage range of 0V - 3.3V, which in turn represents a pitch range of 0 to 9.9 octaves (at 1/3V per octave) with a resolution of about 2.9 cents.

The ADC conversion value is mapped to MIDI pitch, giving a range of 118.8 semitones. This MIDI note value is then converted to frequency using my Arduino mtof library, which can be downloaded here.

The frequency value could be used to control another synthesiser or oscillator, or used for analysis and further conversion.

In this particular example, the resulting frequency value is sent via two MIDI pitch messages - one for the integer portion and one for the floating portion - and then reconstructed as a frequency value for a sine wave in a Max patch. A comparison can then be made to the analog output of a VCO to see how accurate the pitch conversion is.

#include <mtof.h>
#include <math.h>

float data; 
float pitch; 
float pitch_offset = 36; 
float freq;

float max_voltage_of_adc = 3.3; 
float voltage_division_ratio = 0.3333333333333;  
float notes_per_octave = 12;
float volts_per_octave = 1; 

float mapping_upper_limit = (max_voltage_of_adc / voltage_division_ratio) * notes_per_octave * volts_per_octave;

void setup() {
  analogReadResolution(12); // 12-bit ADC resolution

void loop() {
  data = analogRead(0); // read pitch CV as data value using ADC
  pitch = pitch_offset + map(data, 0.0, 4095.0, 0.0, mapping_upper_limit); // convert pitch CV data value to a MIDI note number
  freq = mtof.toFrequency(pitch); // convert MIDI note number to frequency

  /*  To test out the frequency values that are being generated from pitch cv conversion, the frequency value is sent over MIDI pitch bend. 
   *  Pitch bend channel 1 is used for the integer component, and pitch bend channel 2 is used for the float component
   *  The pitch bend messages are re-constituted in a Max patch for testing. 
   *  The pitch bend messages are not intended for use with a 'normal' MIDI synth
   *  This is because they used as a way of conveying the data and not as a regular pitch bend message. 
  usbMIDI.sendPitchBend(freq, 1); // split up integer part of the frequency and send as a pitch bend value
  usbMIDI.sendPitchBend(fmod(freq, 1.0) * 10000.0, 2);