Sunday, May 27, 2018

Pitch CV to Frequency Conversion with Offset Opamp and ADC

As a follow on from the previous post, another (related) way of converting a pitch CV signal to a frequency value is to offset any negative voltages, and then 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 pitch CV signals may be unipolar, with negative voltages and positive voltages
• A user can set the offset via a pot, thereby having a variable range in terms of negative and positive voltages
• The pitch CV signal will be 1 volt per octave
• The pitch CV signal will be roughly a 10v range

12V and -12V power supply rails are required. In this case, the Digilent PowerBrick supplies both from USB 5V. A Eurorack power supply could also deliver 12V and -12V.

The pitch CV signal is summed with an offset voltage. The offset voltage is derived by dividing +12v to -12v with a 10k potentiometer. The sum of both signals is fed into an inverting opamp with unity gain, whose output is fed into a second inverting opamp. The resulting signal is a phase-correct unipolar pitch CV signal (assuming the 10k potentiometer is set correctly) in the range of perhaps 0V to 10V.

This signal is then 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 120 - 132 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. Note that a scaler value of 1.03 corrects for tuning issues - this value may change depending on setup.

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

float data; 
float pitch; 
float pitch_offset = 0; 
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 scaler = 1.03;

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

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);