"If pitches were horses, we'd all be eatin' steak." --Jayne CobbAnd where have I been for almost two months? I was busy. For one thing, I was busy at my actual job where they pay me actual money to write actual code. And when I wasn't doing that, I was working on the last couple releases of Hemisphere Suite.
For each release of Hemisphere Suite, I like to focus on a theme that explores some aspect of electronic music. 1.3 was the MIDI release, 1.5 (coming Sept. 28) will be the Shift Register release, 1.6 (coming Oct. 26) will be the <no spoilers!> release. 1.4 was the scales release. It had several projects that required me to really learn how O_C handles pitch output through its DAC functions. For anybody who wants to do Ornament and Crime development, this is an important topic. I mean, I did okay without this knowledge for a while; but then, when I figured some things out, I had to go back and redo some of my work, to make pitch output more accurate. So best to pass that info along.
The DAC Function
Way back in June, I talked a bit about how the ADC class had several different functions for getting ADC input. Well, it's sort of the same with the DAC. If you take a look at OC_DAC.cpp and OC_DAC.h, there are a few functions to choose from.
For my work, I've settled on one function to use for everything (see OC_DAC.h):
static void set_pitch(DAC_CHANNEL channel, int32_t pitch, int32_t octave_offset)
I'm sure the others have their uses. Actually, I'm not sure about that at all. I never met a job that set_pitch() couldn't do. Anyway, within your code, a call looks like this:
OC::DAC::set_pitch(channel, pitch, octave);
The parameters are as follows:
- channel is an enum with the values DAC_CHANNEL_A through DAC_CHANNEL_D. It specifies the O_C output whose CV output is being changed.
- pitch is a value that corresponds to voltage. Exactly how it corresponds to voltage is something we'll talk about under the next header.
- octave_offset raises and lowers the octave by this many octaves. It's also basically a voltage offset.
It can only be called outside of the menu function. If you're drawing the screen, you can't access the I/O, and vice versa. Doing either type of operation in the wrong place will crash the module.
The channel must have the type DAC_CHANNEL. If you want to use an integer (0-3) to access a specific output, you'll need to use a cast. I've just used C-type casts for this purpose:
int ch = 1; // Channel B
DAC_CHANNEL channel = (DAC_CHANNEL)(ch);
OC::DAC::set_pitch(channel, value, octave);
Also, note that when set_pitch() sets the pitch, the output stays at that level until it's changed. You don't need to keep refreshing the output during every ISR cycle.
What is "pitch" for OC::DAC::set_pitch()?
Pitch is a bipolar value, where 0 means 0 volts. Negative numbers represent negative voltages, and positive numbers represent positive voltages.
One octave is equal to 12 << 7. That's 12 semitones times 128 (or, 2^7), or 1536. You can also read that as "one volt is equal to 12 << 7."
One semitone is equal to 128. 128 is therefore 1/12 of a volt (or about .0833V). This means that each increment of pitch is 1/128 semitones (or, .78125 of a cent) or 1/1536 of a volt (or, about .00065V).
The output range of O_C is said to be between -3V and 6V. This means that pitch has a practical range of -4608 ~ 9216.
Given a CV value (perhaps from an ADC, or sequencer, etc.), you can get octaves and semitones like this:
int cv = <some CV source>;
int octave = cv / 1536;
int semitone = (cv % 1536) / 128;
You can likewise convert a CV value to voltage like this (note that the O_C's M4 doesn't have a hardware floating point unit, so do whatever conversions you need to do):
int cv = <some CV source>;
float volts = cv / (12 << 7);
I know that's a lot of stuff to remember. The good news is, you don't need to remember it. Everything I just told you about the value of pitch can be derived from a single piece of information:
one volt = 12 << 7