Thursday, July 12, 2018

A Small Hardware Project: The O_C Programming Panel

For the first time since I bought my Ornament and Crime, it is fully-racked in my case with four screws. It will no longer spend most of its time propped up by a USB cable at an awkward 45-degree angle.

I cut into my case this evening and added an Ornament and Crime programming panel. It consists of a Switchcraft USB jack, a momentary push button, and a short USB cable for inside the case. Here's the panel in progress:

The Switchcraft jack has a USB Type B jack on one side, and a Type A jack on the other side. So all I had to do was connect a six-inch USB cable into my O_C's Teensy. To maximize its life span in there, I cracked open a brand new Teensy.

The button is to put the Teensy into programming mode, and it just connects to Program and Ground
terminals near the Teensy's on-board button. Here's the O_C hooked up:

I had to swap the positions of my O_C and my Varigate 4+, because the internal USB cable takes a lot of space to the left of the module, and it can no longer go up against the edge of the case. Fortunately, Varigate 4+ is really slim, and the cable has plenty of clearance below the Varigate 4+.

I'd prefer to have the O_C on the left edge, but I'll learn to like it this way. I'll keep my eyes open for a short right-angle USB cable. I've seen them right-angle, and I've seen them short, but....

Anyway, here's what the programming panel looks like from the back, once everything was back together:

This simple thing is going to save me lots of time that I've been spending hooking up the stupid USB cable and fishing around for the stupid tiny little button.

Wednesday, July 11, 2018

Checking In, and a Slight Course Correction

I've been busy with the Hemisphere beta releases, and really busy at my actual day job, and haven't had time for the blog. Sorry about that. So for those readers among you out there, I wanted to quickly bring you up to speed on upcoming projects.

This Blog


This blog was originally intended to be a journal about how to develop O_C apps. But I think I'm pretty much at the end of the line there. First of all, it doesn't seem like that's something that other people are really that interested in. I knew it would be a small audience, with the hobby of developing applications for a specific eurorack module buried under several nested layers of "niche." But second, I've pretty much covered the topic. Anybody that wants to write an app for O_C can get a pretty good start with the information here, and I don't have that much more to say about it.

On the other hand, the topic of the software that I'm producing is up one or two levels of "niche," and there's a relatively strong level of interest in it.

So, this blog will shift a bit toward my own O_C projects, of which there are many. I'll discuss code where it's appropriate or (mildly) interesting. And I might wind up doing more development tutorials if there are any requests, or I find a topic I've neglected.

Hemisphere


The first version of Hemisphere is in what I hope is its final beta release. If no more bugs are found, it'll be released with a version number on July 16, 2018. After this, I plan to merge any new O_C operating system updates into the Hemisphere release.

This release keeps Ornament and Crime apps, with the exception of Meta-Q. So it's basically O_C + Hemisphere and 13 applets. This configuration takes up all of the O_C's program storage, so the plan is to go into a stable release that won't change much over time.

Hemisphere Suite


This is my current project. For the multi-O_C crowd, this removes the O_C apps to make room for many applets and new full apps. This will be rapidly-changing system and will contain everything that I do. There will be some weird stuff in there, but hopefully pretty interesting.

At the moment, it includes a slew processor, a gated VCA, a port of an accent sequencer I wrote for Peaks called Palimpsest, a playable implementation of John Conway's Game of Life, and a dual Euclidean drummer called Annular Fusion. I'm also planning to update the Hemisphere framework with a master clock forwarding mode, so that both hemispheres can share a single clock source.

Documentation


This is important, and I can't neglect it. I have to document the applets' functions and keep the documentation somewhere. Right now, information is kind of fragmented between this blog, GitHub, MuffWiggler, and my website. I need to consolidate. So that's a thing, too.

Neural Network


The neural network full O_C app is also in active development, although right now it largely involves research. So more to come on this as it comes together.

Messing With My Case


I got this awesome Switchcraft flush-mount USB jack. When I get some time, I'm going tear my system down and install the jack and a button, so that I can program my O_C without it hanging out of the case. I'll post photos, at least!

Tuesday, July 3, 2018

Hemisphere Beta Release PFAQ

PFAQ? Potentially Frequently-Asked Questions, because I haven't been asked any questions yet. But I'm looking for synthesists who are interested in trying alternate firmware for Ornament and Crime, and providing bug reports and feedback.

When is the release?


July 4, 2018, at www.beigemaze.com

How will it be released?


A Teensy hex file is available at http://www.beigemaze.com/hemisphere.html.

You can install with the instructions at http://ornament-and-cri.me/firmware/#method_a.

This is a beta release. I'm looking for people who'd like to try Hemisphere and provide feedback. You should be comfortable installing firmware, and optionally reverting back to the previous version if you want to return your O_C to its factory state. It's not a difficult process, but unfortunately I can't hold your hand very much.

Will I lose any data saved in my O_C?


Yeah, it's a pretty safe bet that you'll lose everything saved in your O_C. This isn't something you want to do just before a show.

Will I lose any apps?


The native Ornament and Crime firmware pretty much takes all of the Teensy's memory. So I had to free up about 20K by disabling Meta-Q in this release.

Do I have to be part of the beta, or can I just download Hemisphere and install it?


When the hex file is released, you can just download Hemisphere and install it without participating in the beta. But it's all going to be pretty informal anyway. I won't be asking beta testers to follow any particular procedures or use bug tracking software. The best place to talk about the beta will probably be right on this here blog page.

Is it finished?


There's always room for improvement, but there are 14 applets available now. These are:
  1. Dual Quantizer - Selectable scales, continuous or clocked operation
  2. Dual Clock Divider/Multiplier - Two divider/multipliers share a single clock, from 1/8 to 8/1
  3. 5-Step Sequencer - 2 1/2 octaves, quantized to semitones
  4. Dual 8-Step Trigger Sequencer - Edit steps in groups of four, with adjustable length
  5. Sample and Hold with Random - One is a conventional S&H implementation, the other side is a clocked random source, with the option for both sides share a single clock
  6. Dual Calculator (arithmetical operations) - Sum, difference, min, max, average, and ranged random
  7. Dual Logic (boolean operations) - AND, OR, XOR, NAND, NOR, XNOR, with the option to put the operator under CV control for both channels
  8. Threshold Logic Neuron - Generate complex but deterministic clocks or states; this is how they thought the brain worked in the 1950s
  9. Two-Channel Gated/Sequential Switch - Switch between two voltages with a gate signal and/or a clock
  10. Probability Brancher - Switch between two voltages for each clock pulse, based on a set probability
  11. Skewed LFO - From saw to triangle to ramp, add a little simple modulation
  12. Dual ADSR - This
  13. Lo-Fi Tape Looper - One second. 8 bits. 2kHz. Go!
  14. Gated VCA - Sort of a two-level VCA, accepts a signal, modulated by CV, and unleashed with a gate... or not, your call
There's a YouTube video going over the functions of each module at https://www.youtube.com/playlist?list=PLC5d4vp670NsuEUQybeCPYOvoDJb9fnBi.

There will be more apps in the future, but to make space on the O_C for more, I'll have to do a full release that removes most of the native O_C apps.

Monday, July 2, 2018

Tutorial: Working with Menus, Buttons, Encoders

Way back in the Hello World post, like three weeks ago, I promised that I'd cover menu generation. Since then, I've decided that my own apps wouldn't use menus. But, you know, a deal's a deal, and using O_C's native menu generation system is an important part of development.

In this post, I'll be referring back to the last tutorial, about saving app settings. The reason for this is that the menu system dovetails very nicely with the setting system. The work that you do to declare data types, ranges, potential values, and names of settings also goes toward generating the menu pretty much automatically. If you need to whip some CV processing up quickly, but don't want to mess around with building UI from scratch, you're in luck.

Please pull the Tutorial repo at the usual place, and follow along in the new APP_LOGIC.ino file.

The App


The application is a simple 4-channel logic app. Each channel takes input from two digital inputs, processes it with one of six logic gates (and, or, xor, nand, nor, xnor), and outputs the result to the corresponding channel.

For each output channel (A, B, C, D), the user chooses a logic gate operation and selects which digital inputs are used for the calculation. The selection follows conventional O_C functionality: the right button switches between selection of the parameter and editing, while the right encoder scrolls through the parameters or selects values. The underlying framework asks you to code the functions of the controls, but the data management and screen handling (switching, scrolling, etc.) is handled for you.






Step 1: Set up the enum containing the settings

Don't want to dwell on the first step too much, because we've seen this stuff before. The enum is a list of labels for settings, with a last setting marker:

enum LOGIC_SETTINGS {
    LOGIC_SETTING_OPERATION_A,
    LOGIC_SETTING_SOURCE_A,
    LOGIC_SETTING_OPERATION_B,
    LOGIC_SETTING_SOURCE_B,
    LOGIC_SETTING_OPERATION_C,
    LOGIC_SETTING_SOURCE_C,
    LOGIC_SETTING_OPERATION_D,
    LOGIC_SETTING_SOURCE_D,
    LOGIC_SETTING_LAST
};


See also the LOGIC class definition, which uses the SettingsBase parent class.

Step 2: Set some value lists


In the last tutorial, I used settings strings from OC::Strings. This time, I'm defining my own settings strings, which will be displayed on the screen and used in Step 3:

const char* const gates[6] = {
  "AND", "OR", "XOR", "NAND", "NOR", "XNOR"
};

const char* const input_pairs[3] = {
  "1,2", "2, 3", "3,4"
};


Step 3: Declare the settings


In the last tutorial, the settings declaration was only used to identify what would be saved. This time, the declarations will actually be used for displaying and validating menu values, so it's important to set ranges and names up as you want them to function:

SETTINGS_DECLARE(LOGIC, LOGIC_SETTING_LAST) {
    { 0, 0, 5, "Logic Gate A", gates, settings::STORAGE_TYPE_U8 },
    { 0, 0, 2, "Source for A", input_pairs, settings::STORAGE_TYPE_U8 },
    { 0, 0, 5, "Logic Gate B", gates, settings::STORAGE_TYPE_U8 },
    { 0, 0, 2, "Source for B", input_pairs, settings::STORAGE_TYPE_U8 },
    { 0, 0, 5, "Logic Gate C", gates, settings::STORAGE_TYPE_U8 },
    { 0, 0, 2, "Source for C", input_pairs, settings::STORAGE_TYPE_U8 },
    { 0, 0, 5, "Logic Gate D", gates, settings::STORAGE_TYPE_U8 },
    { 0, 0, 2, "Source for D", input_pairs, settings::STORAGE_TYPE_U8 }
};


Step 4: The Cursor


menu::ScreenCursor<menu::kScreenLines> cursor;

You'll provide this as a public property in your app's class somewhere. Some native apps put it in a struct. The idea is that it needs to be available, because it handles lots of stuff, as we'll soon see.

In this case, it's in the LOGIC class, and will be referred to via logic_instance as logic_instance.cursor. If you want to hide it in a private property and create a getter for it, knock yourself out.

Step 5: Initialize the cursor in Init()


This tells the cursor which parameter to start with (from the enum generated in Step 1), and which parameter to end with. For a simple menu, you can just provide the starting and ending points of the entire list of settings. But your app may have different needs.

void LOGIC_init() {
    logic_instance.cursor.Init(LOGIC_SETTING_OPERATION_A, LOGIC_SETTING_LAST - 1);
    logic_instance.Init();
}


Step 6: Drawing the menu


To draw the menu, create a SettingsList and then iterate through its available() method until you've reached the end of the settings. The pattern below will get you a conventional O_C menu, and you really don't need to do much else, other than provide logic that might show or hide settings conditionally. But you don't need me for that.

void LOGIC_menu() {
    menu::DefaultTitleBar::Draw();
    graphics.print("Logic");

    menu::SettingsList<menu::kScreenLines, 0, menu::kDefaultValueX - 1> settings_list(logic_instance.cursor);
    menu::SettingsListItem list_item;
    while (settings_list.available())
    {
        const int current = settings_list.Next(list_item);
        const int value = logic_instance.get_value(current);
        list_item.DrawDefault(value, LOGIC::value_attr(current));
    }

}


Step 7: Setting the button and encoder behaviors


You should have some idea about how to handle button and encoder events, as this topic has been covered in the Pong Game tutorial. However, looking back, I sort of glossed over that, so we'll talk about it a bit here. First, here's the button event handler:

void LOGIC_handleButtonEvent(const UI::Event &event) {
    if (event.control == OC::CONTROL_BUTTON_R) {
        if (event.type == UI::EVENT_BUTTON_PRESS) {
            logic_instance.cursor.toggle_editing();
        }
    }
}


Whenever a button is pressed, and the screensaver is not active, the APPNAME_handleButtonEvent() function is called, passing a UI::Event reference.

An Event has a few readable properties, two of which are relevant to button presses:

event.control: Indicates which button was pressed. OC::CONTROL_BUTTON_R and OC::CONTROL_BUTTON_L are the encoders, and OC::CONTROL_BUTTON_DOWN and OC::CONTROL_BUTTON_UP are the up/down buttons next to the screen.

event.type: Indicates whether the button was pressed (UI::EVENT_BUTTON_PRESS), or long-pressed (UI::EVENT_BUTTON_LONG_PRESS). Note that a long press of the up button activates the screensaver, and a long press of the right encoder goes to the app selection menu. These events will never be intercepted by an app because they are overridden by the framework.

In the handler above, when the right button is pressed, it toggles the editing mode of the app instance's cursor, between parameter selection and editing. You don't need to do anything special to handle the new state.

Encoder handling works in a similar way:

void LOGIC_handleEncoderEvent(const UI::Event &event) {
    if (event.control == OC::CONTROL_ENCODER_R) {
        if (logic_instance.cursor.editing()) {
            logic_instance.change_value(logic_instance.cursor.cursor_pos(), event.value);
        } else {
            logic_instance.cursor.Scroll(event.value);
        }
    }
}


Here, the event.control is set to either OC::CONTROL_ENCODER_R or OC::CONTROL_ENCODER_R, indicating which encoder was turned.

Instead of event.type, an encoder event's behavior is read with

event.value: Indicates whether the encoder was turned clockwise (1) or anti-clockwise (-1). The O_C system has the ability to fire multiple encoder events when the encoder is turned fast. Your code doesn't need to worry about this. You can just assume that you're handling changes one increment at a time.

The handler above checks the cursor.editing() mode to decide what to do. In editing mode, it changes the value at the cursor position. This is nice and automatic: it checks the appropriate range (from SETTINGS_DECLARE() in Step 3) and updates the values in the app class.

If editing mode is off, it scrolls through the menu items. Again, you don't need to specifically handle the scrolling. The cursor and SettingsList (Step 6) handle all that for you.

That's it for today!

A Word about Hemisphere


It was nice to see the support of the Hemisphere project. The Hemisphere introduction dwarfed readership of the next most popular post by over 3,000 views. At this point, Hemisphere is ready for beta testing. So if you'd like to try it out, let me know by IM'ing me at MuffWiggler (I'm chysn) and I'll send you a link to the hex file.

In the coming days, I'll be producing a series of one-minute videos, one for each applet. That rollout will be my next blog post.

Sunday, June 24, 2018

Introducing Hemisphere

People often ask about O_C whether it can run two apps at once. The logistics of how this would work--sharing the screen, negotiating the I/O, etc.--are mind-boggling, but it doesn't stop people from asking.

Hemisphere is a dual-channel Applet framework for Ornament and Crime. But it is not a way to use multiple native Ornament and Crime apps in one unit. Instead, Hemisphere applets are smaller modular building blocks. It will have features that are handy if you don't have them anywhere else, or need more. In a way, it's taking the Ornament and Crime hardware and running in a totally different direction, toward minimalist design. The vision is sort of somewhere between Ornament and Crime and Disting, a system that can do many things, but threads the ease-of-use needle with the benefit of O_C's footprint and screen.

The GitHub repo for Hemisphere is in a different place than the previous tutorials. If you're interested in checking out Hemisphere, see the video (at the bottom of the post), and then clone https://github.com/Chysn/O_C-Hemisphere.

Hemisphere development has consumed most of my free time this week, and will continue to do so for a while, as I build new applets. As I go, I'm finding that the framework needs something. Sometimes I totally rethink a design decision. But at some point, I'll have reached a point of near-quiescence. At this point, I'll start documenting the framework and describing the process of building Hemisphere applets.

Since Hemisphere has much to juggle, it's a more opinionated framework than the O_C application framework. But I tried to keep it very, very similar. I've added boilerplate code, too, which makes it terribly easy to get started.

More later, of course. For now, I plan to add a couple more applets, then I'm going to spend some time patching with them and looking for improvements.


Executive Summary (Or, tl;dw)

Hemisphere splits O_C in half. Each applet gets half the screen, two digital inputs, two CV inputs, two outputs, one encoder, and one encoder button. Applets are selected by pressing one of the Up/Down buttons and turning an encoder to flip through available Applets, and then pushing an Up/Down button again to lock the selection.

Because there will be a lot of applets, each one has a concise help screen, activated by holding down the left encoder button for a couple seconds. Go to the other hemisphere's help screen by long-pressing again. Exit help screens by long-pressing a third time. While in the help mode, the applets continue to function as normal.

Hemisphere offers a forwarding mode, which forwards CV1 and CV2 (the left hemisphere's CV inputs) to the right hemisphere. This avoids insane use of multiples or stacked cables for chaining Hemisphere's input. Activate forwarding by long-pressing the Down button.

Wednesday, June 20, 2018

Introducing The Darkest Timeline

In my last post, I mentioned The Darkest Timeline. It's now ready to use, and the code is available at my usual repository, https://github.com/Chysn/O_C_Tutorial1




I've got some near-future plans for O_C, which I'll get into in a later post; but for now I'm formulating a design philosophy, and The Darkest Timeline is a test case for some of these principles. You won't see, from me, apps that look or feel like Ornaments and Crime's native apps. Some of my principles are as follows:
  1. Minimize text, maximize graphics. I won't do scrolling lists of options.
  2. Use the screen for display, not for input. The app should be at least somewhat usable with the screen covered.
  3. Aim for one function per control. An encoder button can toggle the function of that encoder, but the current function must be readily ascertained. Functions beyond basic functionality may be hidden behind long-press buttons.
  4. Avoid multiple screens. That is, no menu diving.
  5. The screensaver should be a skeletalized version of the main screen. Your workflow should not be interrupted by the screensaver.
The Darkest Timeline follows these principles pretty well, and it's a lot of fun to play. It has two features that I'm particularly proud of.

The first is an adjustable index point. The "index point" is basically the start of the sequence. Regardless of the sequence length, the index point may be scrubbed through from the panel or via CV. This lets you warp a set of steps in a lot of different ways.

The second feature is the concept of alternate timeline output. Output B and output D provide alternate versions of the CV and trigger probability, respectively. Output B plays the sequence directly AFTER the one played by output A. That is, if the sequence is 8 steps, output A plays steps 0-8, and output B plays steps 9-16 (assuming an index point of 0). Output D is a probability-based trigger like output C, except it uses the step's complementary probability. That is, if the step has a 70% chance of firing the trigger from output C, there's a 30% chance of output D's trigger firing. These probabilities are independent, so it's possible for C and D to both fire (or not fire) on the same step.

The Controls


Digital Input 1: Advance forward one step
Digital Input 2: Advance backward one step
Digital Input 3: Reset to the index point
CV Input 1: Record value (0-5V) for the CV Timeline
CV Input 2: Record value (0-5V) for the Probability Timeline
CV Input 3: Set the index point
Up Button: Toggle recording to the CV Timeline. The active step (the leftmost step) will display a blinking cursor when recording. Long hold: enter screensaver mode, per usual.
Down Button: Toggle recording to the Probability Timeline. The active step will display a blinking cursor when recording. Long hold: clear both timelines.
Left Encoder: Set sequence length (fewer steps to the right, like you're "zooming in")
Left Button (Long hold): Randomize both timelines and set index point to 0
Right Button: Toggle the function of the Right Encoder between changing the active step and editing the index point. The index point indicator (the vertical bar) will become a solid blinking line when editing the index point.
Right Encoder: Move within the sequence or enable index point editing
Output A: Normal Timeline CV, the values that you see on the screen.
Output B: Alternate Timeline CV, the values that come after the values that you see on the screen.
Output C: Normal Timeline Trigger, (maybe) a 6ms trigger based on the Probability Timeline. All the way up, the trigger will fire. All the way down, it won't. Everywhere else, it's more likely to fire if the bar is higher.
Output D: Alternate Timeline Trigger, (maybe) a 6ms trigger based on the complement of the value from the Probability Timeline. All the way, the trigger will not fire. All the way down, it will. Everywhere else, it's more likely to fire if the bar is lower.

All right, feel free to leave a comment if you have any questions. I hope that some of you grow a goatee and enjoy the alternate timelines....

Monday, June 18, 2018

Tutorial: Saving App Settings

Ornament and Crime is capable of storing your app settings to flash memory on the included Teensy 3.2 board. It's pretty simple storage, capable of storing only a flat list of integers, but there's not much that you absolutely can't do with enough integers.

It's also easy enough to use. I'm going to walk you through that today. Thanks again to Patrick Dowling for helping to fill in several gaps.

The App

 

I made this app for a reason. The O_C's ADC class has no fewer than five methods of getting CV input and I got sick and tired of experimenting with these, and I just wanted to use science. So today's app, CV Inspector, gives you a list of the current values of all five ADC methods from all four CV inputs.

CV Inspector
This app seems too simple to have any settings, and it really is, but I've been promising to talk about settings, so I kind of crammed it in there. There's only a single setting, and that's the selected channel.

If you clone or update your working copy of my repo (https://github.com/Chysn/O_C_Tutorial1.git) and install it on your O_C, you can try it out. Either encoder changes the CV input channel. And that's about it. To save the current channel, long-hold the right encoder button to go back to the menu, then long-hold the same button again to save the setting. You'll see a little rectangle become a big rectangle by way of confirmation.

The app file is APP_CVINSP.ino. Please open it up and take a look at the tutorial sections one at a time. There are seven steps, each with TUTORIAL STEP n in the comment.

Step 1: Add a settings enum


Make an enum containing representations of your setting names. You can use these enum values to reference the settings later. This app has only one setting, but two enum values. Why? Because enum values are assigned as ints starting from 0, so the APPNAME_SETTING_LAST is used to represent the total number of settings:

enum CVINSP_SETTINGS {
    CVINSP_CHANNEL,
    CVINSP_SETTING_LAST
};


Step 2: Make your application class a subclass of SettingsBase


Your app class should be a subclass of settings::SettingsBase. Include the class name, and the APPNAME_SETTING_LAST, as shown below. This lets the base class know the size of the settings array:

class CVInspector : public settings::SettingsBase<CVInspector, CVINSP_SETTING_LAST> {

Step 3: Declare setting attributes


Each setting needs a list of attributes, which are provided using the SETTINGS_DECLARE macro. Each setting is a six-element array that's used to initialize a settings::value_attr struct:

SETTINGS_DECLARE(CVInspector, CVINSP_SETTING_LAST) {
    {0, 0, 3, "channel", OC::Strings::cv_input_names, settings::STORAGE_TYPE_U4}
};


The value_attr struct is in util/util_settings.h, and looks like this:

struct value_attr {
    int default_;
    int min_;
    int max_;
    const char *name;
    const char * const *value_names;
    StorageType storage_type;


    // and some methods...
}

The default, min, and max values are, of course, all integers. The name can be a string literal. The value_names is an array of strings, or NULL if you don't need string values for the setting. The StorageType options are also in util_settings.h:

enum StorageType {
  STORAGE_TYPE_U4, // nibbles are packed where possible, else aligned to next byte
  STORAGE_TYPE_I8, STORAGE_TYPE_U8,
  STORAGE_TYPE_I16, STORAGE_TYPE_U16,
  STORAGE_TYPE_I32, STORAGE_TYPE_U32,
};


Step 4: Implement required global functions


Implement APPNAME_storageSize(), APPNAME_save(), and APPNAME_restore(). Don't miss that storageSize() operates on the class, while save() and restore() operate on the instance. All of these methods are from the base class, SettingsBase.

size_t CVINSP_storageSize() {
    return CVInspector::storageSize();
}

size_t CVINSP_save(void *storage) {
    return inspector_instance.Save(storage);
}

size_t CVINSP_restore(const void *storage) {
    return inspector_instance.Restore(storage);
}



Step 5: Reading a setting value


Within your app class code, it's super-easy to use the settings. There's a values_ array that's part of the base class, and you just read it with the enum value (or integer index), like this:

int channel = values_[CVINSP_CHANNEL];

Step 6: Writing a setting value


There are a few ways to go about writing or changing values. You can just do

values_[CVINSP_CHANNEL] = channel;

of course, but SettingsBase provides two methods for doing it a little better. First, there's apply_value(int index, int value), which simply sets the value. Second, we've got change_value(int index, int delta), which changes the value by the delta amount. In both cases, index refers to an enum value defined in Step 1.

What both of these methods have over simply writing to values_ directly is that they do range clamping based on the min and max attributes that you set in Step 3.

Note that when changing a setting--whether by manipulating values_, using change_value(), or using apply_value()--the settings are not saved to flash memory until you do the double long-hold thing that I described earlier.

Step 7: Using the value names


If you've set an option array in Step 3, you can grab the currently-assigned value by
  1. Accessing the value_attr struct
  2. Using the value as an index to reference the name
const settings::value_attr &attr = value_attr(CVINSP_CHANNEL);graphics.print_right(attr.value_names[values_[CVINSP_CHANNEL]]);

It's well worth taking a look at OC::Strings in the OC_strings.cpp file, where there are a bunch of pre-defined lists of options that apply to common O_C values.

Introducing The Darkest Timeline


The next post, coming tomorrow or Wednesday, isn't really a tutorial, but puts into practice all of things we've gone over so far. It's a 32-step, 2 channel (CV and probability-based-trigger) indexed sequencer/CV recorder called The Darkest Timeline. This project shows the vision I have for my own future O_C apps. It's a lot of fun to play, and instead of using scrolling text option menus, its interface is entirely graphically-based, with strict function-per-control operation. I'm just finishing up a bit of QA, and I want to make a video, and then I'll put it out there.

The Darkest Timeline

What's with the missing apps in the Tutorial repo?


I wanted to sort of streamline the tutorial codebase, as the list was really starting to fill up. So I removed some things, and left the native apps that I thought most people would miss the most. But if you're here reading this thing, you know how to get them back.

Until soon!

A Small Hardware Project: The O_C Programming Panel

For the first time since I bought my Ornament and Crime, it is fully-racked in my case with four screws. It will no longer spend most of its...