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!

No comments:

Post a Comment

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...