0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-06-07 04:55:36 +00:00
Files
Linus Torvalds 9c208692f7 Convert effects to metadata-driven boiler plate generation
This is the actual effect format update mentioned in the previous
commit: express the boiler plate details in the comment at the top of
the effect so that it can be auto-generated.

Part of that is that now the default values can be much more sanely
expressed: instead of setting the defaults as the particular integer
values the pot takes, express them in the user-visible units.

So the default noise gate level value can now be described as

    // POT: "Level" LINEAR(-100.0 -40.0) = -70.0 dB

instead of the inscrutable

    EFFECT_POT("Level", desc_dB, gate_pot0_level, 0),   // -70dB

where the default value of 0 ended up indeed being -70dB, but we had to
have a comment to that effect because it was so non-obvious (it came
from the fact that gate_pot0_level() did a linear interpolation between
-100 and -40, and -70 is the middle value).

So now the pot values make a lot more sense and defaults are easier to
see and set.

This also means that instead of having the pot values be -60 to +60 (in
order to make the default "noon" be zero and needing no initializer),
since we auto-generate the defaults we can make the actual internal
values be the more straightforward 0..120 range - not only matching what
we did for MIDI, but also simplifying a lot of the helper math cases.

As a result, the pot arrays are now done as 'unsigned char'.

While doing all this, I also noticed that the ->describe() function is
just entirely historical: it used to depend on the pot value for when
the enumerations were done as a separate hack.  But these days
enumeration pots are an integral part of the pot description, and the
->describe() functions were all just returning the unit string.

So get rid of the function pointer indirection, and just make it have a
simple and straightforward 'const char *unit' string.

This all makes the 'gen_effect' python script a bit bigger, but that is
more than made up for by the boiler plate removal in the effects
themselves, so not only does it make things clearer, this all removes
more lines than it adds.

And equally importantly, it means that now the Web UI actually can
convert the raw pot values into sensible units, because it too can take
the pot descriptions into account.  Some other minor UI updates while at
it.

All the python and JS scripting itself was obviously all done by AI.
Some day I might care enough to learn to actually write python, but for
now I'm perfectly happy just reading the end result.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-05-31 13:39:35 -07:00

159 lines
4.1 KiB
C

#include "lfo.h"
float get_usb_audio_input(void);
typedef float (*pot_convert_fn)(unsigned char);
struct pot_descr {
const char *label;
const char *unit;
pot_convert_fn convert;
unsigned char def_val;
const char *const *enum_names;
};
//
// Primary interface for audio DSP algorithms.
//
// Each effect plugin defines its runtime processing callbacks and UI
// layout.
//
// Note: effects[] may leave `pots` array with NULL labels, which tells
// the OLED and I2C pollers to omit querying/drawing parameters that the
// hook doesn't use.
//
// We have two set of pot values, because cpu 0 - the UI core - will
// prepare the pot state in the inactive set, and then atomically switch
// that state so that code 1 - the audio core - will never use pot
// values that are in some halfway state. The active state is the LSB of
// the 'seq' value, which is used to tell whether the state has changed.
//
// 'mix' is the current mixing state, and 'target' is the target mixing
// state for fading in and fading out the effect (in fractions of
// EFF_ENABLE_STEPS)
//
struct effect {
const char *name, *short_name;
unsigned int mix, target;
volatile unsigned int seq, last;
unsigned char intense, active_pot;
unsigned char pot_values[2][10];
void (*graph)(struct effect *, int, unsigned char[10]);
void (*init)(unsigned char[10]);
void (*load)(struct effect *, unsigned char[10]);
void (*save)(struct effect *, unsigned char[10]);
float (*step)(float);
const struct pot_descr pots[10];
};
#define EFFECT_POT(...) { __VA_ARGS__ }
// Effects and MIDI mapping auto-generated from scripts/gen_effects.py
#include "effect_map.h"
static inline void generic_effect_describe(struct effect *e, unsigned char pots[10])
{
for (int i = 0; i < 10; i++) {
if (e->pots[i].label) {
const char *unit = e->pots[i].unit ? e->pots[i].unit : "";
if (e->pots[i].enum_names) {
int idx = (int)e->pots[i].convert(pots[i]);
fprintf(stderr, " %s=%s %s", e->pots[i].label, e->pots[i].enum_names[idx], unit);
} else if (e->pots[i].convert) {
float val = e->pots[i].convert(pots[i]);
fprintf(stderr, " %s=%g %s", e->pots[i].label, val, unit);
}
}
}
fprintf(stderr, "\n");
}
static unsigned int dropped;
static inline float do_effect_step(struct effect *effect, float val)
{
if (effect->mix == effect->target) {
if (effect->mix)
val = effect->step(val);
} else {
int dir = effect->mix < effect->target ? +1 : -1;
float mix = effect->mix / (float) EFF_ENABLE_STEPS;
effect->mix += dir;
float effect_val = effect->step(val);
val = linear(mix, val, effect_val);
}
return val;
}
#include "process.h"
static int disable_all;
#define BLOCKSIZE 200
static void bypass(void)
{
PIO pio = pio0;
for (int i = 0; i < BLOCKSIZE; i++) {
int32_t sample = pio_sm_get_blocking(pio, PIO0_I2S_RX_SM) << 8;
float val = process_input(sample);
sample = process_output(val, sample);
pio_sm_put_blocking(pio0, PIO0_I2S_TX_SM, sample);
}
}
static inline void single_sample(float mix)
{
PIO pio = pio0;
int32_t sample = pio_sm_get_blocking(pio, PIO0_I2S_RX_SM) << 8;
float in = process_input(sample);
float usb_in = get_usb_audio_input();
if (usb.input == USB_IN_PRE_FX)
in += usb_in;
float out = in;
for (int i = 0; i < ARRAY_SIZE(effects); i++)
out = do_effect_step(effects[i], out);
float val = linear(mix, in, out);
if (usb.input == USB_IN_MIX)
val += usb_in;
sample = process_output(val, sample);
if (pio_sm_is_tx_fifo_empty(pio, PIO0_I2S_TX_SM)) {
dropped++;
clipping = 1;
}
pio_sm_put_blocking(pio0, PIO0_I2S_TX_SM, sample);
}
static __attribute__((noinline)) void make_one_noise(void)
{
for (int i = 0; i < ARRAY_SIZE(effects); i++) {
struct effect *effect = effects[i];
unsigned seq = effect->seq;
if (seq == effect->last)
continue;
effect->last = seq;
effect->init(effect->pot_values[seq & 1]);
}
static int disable = 0;
while (disable != disable_all) {
float mix = disable / (float) EFF_ENABLE_STEPS;
disable += (disable < disable_all) ? 1 : -1;
single_sample(mix);
}
if (disable)
return bypass();
for (int i = 0; i < BLOCKSIZE; i++)
single_sample(1.0);
}