Skip to content

Conversation

@stephenhensley
Copy link
Collaborator

Based on #396 so merge that first, though this doesn't actually change anything that's used there.

This provides a new class: VoctCalibration that can be used to perform a simple calibration.

The idea is that you'll use this either in a separate program, or as part of a more complex application, and then store the scale and offset values within a the PersistentStorage class for recall between power cycles.

For convenience there is also a ProcessInput, though I'm used to managing this data elsewhere (usually the board support file for whatever hardware I'm working on).

@github-actions
Copy link

github-actions bot commented Sep 8, 2021

Unit Test Results

    1 files  ±0    13 suites  ±0   0s ⏱️ ±0s
125 tests ±0  125 ✔️ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 956ab87. ± Comparison against base commit 956ab87.

♻️ This comment has been updated with latest results.

@TheSlowGrowth
Copy link
Contributor

I know I'm being a little annoying, but this class could use some nice unit tests ;-)

@stephenhensley
Copy link
Collaborator Author

@TheSlowGrowth not annoying at all! Thanks for the review! I'll add some tests, and address the stuff you mentioned above.

@stephenhensley
Copy link
Collaborator Author

Addressed the notes from the review, and greatly increased simplicity of the class.

Still need to add the unit tests for both PRs, but should be usable now.

I removed all of the "state' stuff, except I did leave a cal_ bool for whether the calibration, or the manual setting-setter was called. It can be checked via the GetData function, but I'm not really sure its useful for much. I suppose we could add a bool IsCalibrated() function, but the only real use case for that would be the same as testing the retval of the Record function. So, I'm open to removing it.

To see everything in context, here's an example of a short calibration program for a single input on the new Patch SM board:

#include "daisy_patch_sm.h"
#include "daisysp.h"

using namespace daisy;

struct MyCalibrationData
{
    float offset, scale;

    /** Constructor sets defaults */
    MyCalibrationData() : offset(0.f), scale(0.f) {}

    /** Simple comparison operators for triggering saves */
    bool operator==(const MyCalibrationData &rhs) const
    {
        return offset == rhs.offset && scale == rhs.scale;
    }
    bool operator!=(const MyCalibrationData &rhs) const
    {
        return !operator==(rhs);
    }
};

/** Order of events for state machine */
enum CalibrationProcedureState
{
    PATCH_1V,
    PATCH_3V,
    PATCH_DONE_CALIBRATING,
};

/** shorthand for our template-based storage class */
using MyStorageClass = PersistentStorage<MyCalibrationData>;

DaisyPatchSM patch;

/** The pieces related to calibration, and storage. */
CalibrationProcedureState cal_state;
VoctCalibration           cal;
MyStorageClass            cal_storage(patch.qspi);
MyCalibrationData         cal_data;
float                     value_1v; /**< Temporary value for 1V */

/** UI elements for triggering the calibration */
Switch button;
Led    led;
bool   trigger_save;

/** Oscillator to "hear" the calibration once it's done. */
daisysp::Oscillator osc;

void AudioCallback(AudioHandle::InputBuffer  in,
                   AudioHandle::OutputBuffer out,
                   size_t                    size)
{
    patch.ProcessAllControls();
    button.Debounce();

    /** This is going to be the value we record */
    float value = patch.GetAdcValue(patch.CV_1);

    float bright = 0.f; /**< LED Brightness */

    /** Handle the LED and button depending on the state of the calibration */
    switch(cal_state)
    {
        case PATCH_1V:
            if(button.RisingEdge())
            {
                value_1v  = value;
                cal_state = PATCH_3V;
            }
            /** Waiting for 1V, slow blink */
            bright = (System::GetNow() & 1023) > 511 ? 1.f : 0.f;
            break;
        case PATCH_3V:
            if(button.RisingEdge())
            {
                if(cal.Record(value_1v, value))
                {
                    /** Calibration is now complete. Let's trigger a save! */
                    trigger_save = true;
                    cal_state    = PATCH_DONE_CALIBRATING;
                }
            }
            /** Waiting for 3V, fast blink */
            bright = (System::GetNow() & 255) > 127 ? 1.f : 0.f;
            break;
        case PATCH_DONE_CALIBRATING:
        default:
            /** Any other state the LED will be a short blip off, but long on */
            bright = (System::GetNow() & 2047) > 63 ? 1.f : 0.f;
            break;
    }
    /** Handle the LED */
    led.Set(bright);
    led.Update();
    /** Use the calibration data to set the pitch of an oscillator */
    float freq = daisysp::mtof(48.f + cal.ProcessInput(value));
    osc.SetFreq(freq);
    for(size_t i = 0; i < size; i++)
        out[0][i] = out[1][i] = osc.Process();
}

int main(void)
{
    /** Initialize the Hardware itself */
    patch.Init();
    /** And set up the button and LED to match the patch sm eval hardware. */
    button.Init(patch.B7, patch.AudioCallbackRate());
    led.Init(patch.C1, false);

    MyCalibrationData cal_defaults;
    cal_storage.Init(cal_defaults);
    cal_state    = PATCH_1V;
    trigger_save = false;

    /** Restore settings from previous power cycle */
    if(cal_storage.GetState() == MyStorageClass::State::USER)
    {
        auto &data = cal_storage.GetSettings();
        cal.SetData(data.scale, data.offset);
    }

    /** Oscillator init */
    osc.Init(patch.AudioSampleRate());
    patch.StartAudio(AudioCallback);

    while(1)
    {
        if(trigger_save)
        {
            auto &data = cal_storage.GetSettings();
            cal.GetData(data.scale, data.offset);
            cal_storage.Save();
            trigger_save = false;
        }
    }
}

@stephenhensley
Copy link
Collaborator Author

Added some unit tests, and pending any other changes this might be good to go.

One thing that occurred to me is that it might be worth setting the defaults to the "ideal" settings instead of to settings that will essentially kill an input. Having the default scale be 0 made it very easy to verify during initial tests, but I think in practice a "close" default is better than literally nothing.

@stephenhensley stephenhensley merged commit 956ab87 into master Sep 22, 2021
@stephenhensley stephenhensley deleted the feature/cal_helpers branch September 22, 2021 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants