Hermetic Modular

02/Fundamentals

The Shape of a Firmware

When working with the framework, there are five steps to consider: declare your controls, compose them, route CV, opt into the extra features, and bind to your DSP.

1DeclareVirtualKnobVirtualButton2ComposePagePager3RouteCvMatrixjacks4Opt InPresets · SettingsParamLock · SD · Host5Bindbinder function→ your DSP

Declare your controls

The physical knob on an Alchemy Lab can be in many states. Multiple parameters can be controlled by a single knob, the state of the physical pointer might not match the underlying parameter, and the value may be modulated by an active parameter lock or CV modulation. On top of that, it needs a name, behavior for its associated 16 LEDs, and actual parameters like a response curve and range.

To capture this nuance, we call this collection of properties a VirtualKnob.

static VirtualKnob cutoff = VirtualKnob(0, "Cutoff")
    .Exp(20.f, 18000.f)
    .Ring(Level(kAmber, FillAnim::Pulse)); 

static VirtualKnob mode   = VirtualKnob(2, "Mode")
    .Selector(3)
    .Ring(SelectorRing(kOn, kOff, 3));

A Selector ring, for example, renders discrete regions of selection. There's more you can declare here with a VirtualKnob, but we'll start with just these simple examples.

Buttons get the same treatment with VirtualButton: name the button, declare its state and gestures, and a ButtonBank persists and renders them.

For both, naming them and adding metadata also allows that information to be rendered on the web editor.

Compose them into pages

Pages and the page system allow a VirtualKnob or VirtualButton to be mapped to a physical location. One page is fine. Add a second and a Pager makes a button cycle between them, with the SDK's pot-catch handling orientation and value mismatch.

static Page  filter = Page(0).Knobs(cutoff, reso, drive ...);
static Page  voice  = Page(1).Knobs(shape, detune, mode ...);
static Pager pager(hw.buttons[0], 2, kNumPots);    /* B1 cycles */

Route CV

CV routing is one line per jack: point it at a knob, hand it to a callback, or turn it off. And because the analog jacks are field-programmable, firmware decides which ones are inputs and which are outputs, at boot or live.

cv_matrix.Jack(0).To(cutoff); 
cv_matrix.Jack(1).Custom(OnTap, &tap);
hw.j8.EnableCvOutput();

This structure is mutable, so you can change it at any time, e.g. from a user setting change for routable CV. By default, .To() sums CV with the underlying VirtualKnob offset. You can see above a .Custom() destination as well.

Opt into the extras

Presets, a settings menu, looping automation, the SD card, the browser link: each is one object and one attach. The ControlLoop runs whatever you hand it and skips whatever you don't.

static Presets  presets (hw.seed.qspi);
static Settings settings(hw, &pager);

presets.Manage(pager);         /* pages + knob values get serialized for saving */
settings.UsePresets(presets);  /* stock save / recall UI      */

loop.Use(pager).Use(settings).Use(cv_matrix)
    .Use(filter).Use(voice);

Later sections dive into these functions in more detail. For now, just know that theControlLoop simply orchestrates and executes all the bookkeeping in the proper order. You can dive directly into the source code for that class and "unroll" the loop to support any advanced or custom features you want to implement.

Bind it to your DSP

The last move is the glue: a binder function. It reads each knob and hands finished values to your DSP, and the loop calls it every frame. In this way, you can completely isolate your DSP code from your control code: your DSP gets handed values and instruments them. It doesn't need to know where the value came from, be it a CV jack or a knob, or a parameter lock.

static void Bind()
{
    synth.SetCutoff(cutoff.Value());
    synth.SetMode  (mode.Value());
}

/* in main(): */
loop.OnFrame(Bind);
for (;;) loop.Tick();

Tip

Reads are safe anywhere: Value() and Norm() may be called straight from the audio callback when a parameter wants per-block freshness.

That is the whole pattern. Declare, compose, route, opt in, bind: the template is these five moves with a simple DSP for demonstration.

From here, Control Surfaces is the catalog of everything you can opt into, LED Animations is the ring vocabulary, and when you want to know exactly what runs where and when, and Firmware Anatomy gets into more detail on the control loop and audio callback loop.