Sunday, September 23, 2018

Pitch Calculation and Output

"If pitches were horses, we'd all be eatin' steak." --Jayne Cobb
And 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.
I've got a couple notes on set_pitch() for you:

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

3 comments:

  1. I'm getting mad creating a custom non-octaval scale. I'm asking help here because I'm not good as a programmer. Is there any chance to create a custom scale with let's say 16 fixed voltages that cover more than one octave? Thanks!

    ReplyDelete
  2. I'm getting mad creating a custom non-octaval scale. I'm asking help here because I'm not good as a programmer. Is there any chance to create a custom scale with let's say 16 fixed voltages that cover more than one octave? Thanks!

    ReplyDelete
    Replies
    1. It sounds like you're entering a custom scale using the code, with the braids_quantizer_scales.h file. The first value of each Scale array is the "span," which is the number of DAC values in the whole scale. If you look at the existing ones, like the semitone scale

      // Semitones
      { 12 << 7, 12, { 0, 128, 256, 384, 512, 640, 768, 896, 1024, 1152, 1280, 1408} },

      you'll notice that the first value is always 12 << 7, which is one octave. So for your custom scale, you just put a different number here. Like, if you want it to span two octaves, you can use 2*(12 << 7) or 12 << 8 or 3702, or whatever. Then, of course, you'll want to space out your individual note values within that new span (that is, you'll want 3701 to be the maximum value).

      Hope that helps! Unfortunately, there's no way to get around knowing how to do the math for these things. The O_C developers provide a more detailed overview here http://ornament-and-cri.me/custom-scales/

      Delete

Pitch Calculation and Output

"If pitches were horses, we'd all be eatin' steak." --Jayne Cobb And where have I been for almost two months? I was busy...