Sample and Hold seemed like a good candidate for an early project because it covers many of the basic input/output functions: It listens for a clock, it takes input from the analog-to-digital converter (ADC), and it sends output to a digital-to-analog converter (DAC).
Again, the code for this tutorial is on GitHub at
https://github.com/Chysn/O_C_Tutorial1.git
Please either update your repo or, if you're just joining us, clone a new working copy. If you don't know what any of this means, go back a couple posts to catch up.
Modified OC_apps.ino
This tutorial starts out like the last one. First, I'm adding two lines to OC_apps.ino, which do the same thing in two available_apps arrays:
DECLARE_APP('S','H', "Sample and Hold", SANDH, SANDH_isr)
The fourth argument specifies that the app's main code will be in APP_SANDH.ino, and the fifth argument specifies the name of the interrupt service routine. The ISR will be relevant this time.
Created APP_SANDH.ino
I started with the APP_HELLO.ino file, which is kind of a stripped-down app, and made some changes to it. Step 0 here is to change names from HELLO to SANDH. It's largely a matter of find-and-replace here.
Please open APP_SANDH.ino at this point, and we'll go through the things that differentiate this app from the simpler Hello, World app. These steps are documented in the file as "TUTORIAL STEP N".
Step 1: Include the IO files at the top
#include "OC_DAC.h"
#include "OC_ADC.h"
#include "OC_digital_inputs.h"
We'll be using the DAC, the ADC, and the Digital Inputs. Open up and review these files to get an idea of how they work and what functions are available.
Step 2: Specify an instance of SANDH
SANDH sandh_instance;
The main point of this will be to reference the main SANDH class within the SANDH_ functions that are required in OC_apps.ino. We'll see a concrete example of this in Step 3...
Step 3: Set up the Interrupt Service Routine (ISR)
An ISR handles one or more interrupt conditions. This might be a clock-driven interrupt, or an event-driven interrupt. I'm not sure yet. In O_C apps, the ISR is often used to scan for changes in the values of the inputs, and that's what we'll be using it for here.
The macro in OC_apps.ino specifies that the ISR function is SANDH_isr(), and that is implemented like this:
void SANDH_isr() {
return sandh_instance.ISR();
}
This is something you'll often see in the O_C apps. The specified (global) SANDH_isr() function is simply passing execution along to the class ISR() method. The SANDH::ISR() method is where the actual work starts to happen:
void ISR() {
bool update = OC::DigitalInputs::clocked<OC::DIGITAL_INPUT_1>();
if (update) {
this->TriggerAndSetSample();
}
}
OC::DigitalInputs is a class defined in OC_digital_inputs.h. clocked() is a static method that masks off a specific input's bit in a clock status bit field to see if that input has seen a clock signal go high since the last reset. This will trigger our sample and hold. When this update value is non-zero, SANDH::TriggerAndSetSample() is called....
Step 4: Do the Sample and Hold
Please note that this is a naive implementation of Sample and Hold. I'll point out why after showing you the code:
void ReadAndSetSample() {
int32_t current_value;
current_value = OC::ADC::raw_pitch_value(ADC_CHANNEL_1);
OC::DAC::set_pitch(DAC_CHANNEL_A, current_value, 0);
}
After the ISR has determined that there's a new trigger, it reads the pitch value of channel 1 with OC::ADC::raw_pitch_value(). ADC is defined in OC_ADC.h. I already suggested that you take a look at this file. If you do, you'll see that it has several static methods for getting the value of one of the CV inputs. You have your choice between raw values, smoothed values, pitch values, raw pitched values, etc. I don't know what the practical differences are between these types of values, so I took my cues from the CopierMaschine app and used the raw_pitch_value().
The value is immediate sent to the DAC. DAC is defined in OC_DAC.h. There are a couple of static methods for setting an output (DAC::set() and DAC::set_pitch(), among some others). There's no obvious (to me) relationship between ADC value methods and DAC set methods. I call my implementation "naive" because I suspect that I need to do more to get the input voltage and output voltage to be identical. This works pretty well, but it's not a precision app. There's a noticeable difference between the sampled value and the output.
Update: Patrick Dowling says that the code above is correct, and my O_C's calibration might be off; also the ADC is less accurate than the DAC. My takeaway here is that I might not be able to get the input and output voltages to match exactly, so clearly this will never be the best sample and hold in the world.
One thing I was wondering about was normalization. Is it possible for O_C to determine whether a cable is plugged into a CV input, and route normalized sources accordingly? For example, it's useful to normalize S&H input to a noise source. I'm pretty sure that the answer is No here, as I haven't seen this used in O_C apps. I think that it would basically require another line of digital inputs.
Patch Instructions
I'm hoping that you know how to load the code up on your O_C. That's why you're here, right? But if you need to catch up on that part, see the post called Firmware Update!
To use this Sample and Hold, patch a clock source (square LFO, end-of-something trigger on Maths, whatever) to your O_C's first digital input. Then, patch a modulation source (another LFO, or noise) into O_C's first CV input. Finally, patch O_C's first output into a modulation destination (filter cutoff, or V/oct) of your choice.
Exercises
- Expand Sample and Hold so that multiple voltages are sampled and output with a single trigger
- Expand Sample and Hold to use multiple triggers
- Figure out how to improve pitch accuracy (see how I snuck that in there?)
Real quickly wanted to say this is a great learning resource for me so far! Keep up the work. Re: Jack-cable detection - haven’t looked at the schematic for the OC in a while and not at a computer right now, but pretty sure it does t have it. The way other modules (eg Mutable Warps) usually do it is by sending a signal from the microcontroller to the switch of the jack. If that signal is showing up at that jack’s microcontroller input, then there must not be anything plugged in. I think usually the signal is just a high-low cycle that flip flops at a specific rate.
ReplyDeletepretty sure it doesn’t**** have it
Delete