You've already forked torvalds-GuitarPedal
mirror of
https://github.com/torvalds/GuitarPedal.git
synced 2026-08-19 05:33:46 +00:00
'Software' was the directory everything that was not KiCad ended up in, which stopped describing anything a while ago - Validation and the web app are software too. Worse, it put the shared parts inside the firmware, where they read as the firmware's own. They are not. Effects/ has three consumers built from it: the firmware, Validation's bench, and the web app's controls, all generated from the same POT: comments by gen_effects.py. Audio/ has two - the bench compiles the same biquads, the same envelope followers and the same single_sample(), which is the whole reason a measurement on a workstation says anything about the pedal. Neither belongs under Firmware/, so neither is under it any more: Effects/ one file per effect Audio/ the DSP they are built from, and the audio loop Firmware/ the rest of what runs on the pedal, and the submodules WebMIDI/ the web app scripts/ what the build runs Validation/ unchanged Hardware/, Documentation/, Images/ CMakeLists.txt and the wrapper Makefile move to the top with them, because the build now consumes four of those directories and generates into a fifth. board.local and build/ come along; MIDI_CC_MAP.md is generated into Documentation/ rather than into the old Software/ root. scripts/ goes with the build rather than staying under the firmware, because six of the ten had nothing to do with the firmware: gen_effects.py reads Effects/ and writes to three different places, pow2/log2/quarter_sine generate Audio/'s tables, check-readme.py compares Effects/ against the README, and server.py serves the web app. Four of them are invoked from Validation, which was reaching into Firmware/ for tooling - the same burying this commit is undoing. The four that really are about the firmware are ELF checks the top-level build drives anyway, and a second scripts directory would only be a second place to look. C includes say "Audio/foo.h" and the generated map says "Effects/bar.h", with the repository root on the include path for both the firmware and the bench. Spelling the directory out rather than relying on a bare name is what keeps Audio/cycles.h shimmable: a quoted include searches the including file's own directory first. The submodules are renamed as well as moved. git mv updates their paths but leaves the section names, and 'Software/pico-sdk' surviving in .gitmodules would be the word this commit removes, still load-bearing. That meant the nested modules under pico-sdk too - six .git files pointing into .git/modules/Software - which is why 'git submodule update --init --recursive' is worth running once after pulling this. Verified rather than assumed: a clean configure and build, make check (failing only on the missing-eeprom case it already failed on), check-effects, all four analysis pages reproducing every series and drawing every chart, and a flash to the board that still measures a routed reverb where it did before. One latent bug fell out of it. bench/coeff declared only quarter_sine.h of the three generated math tables, and Audio/util.h includes pow2.h and log2.h as well - so building that target with an empty gen/ could never have worked. 'make bench' builds bench/bench first, which generates all three, so it stayed hidden until this rebuilt everything from nothing. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
728 lines
25 KiB
C
728 lines
25 KiB
C
//
|
|
// The audio core: the effect machinery, and the loop that runs it.
|
|
//
|
|
// WHAT THIS FILE EXPECTS FROM WHOEVER INCLUDES IT
|
|
//
|
|
// It has two consumers - pedal.c on the hardware and
|
|
// Validation/bench/bench.c on a workstation - and it is not
|
|
// self-contained for either. That is deliberate, and the contract is
|
|
// written here rather than only in the consumers, because it is this
|
|
// file imposing it:
|
|
//
|
|
// status.h included first. meter_in, meter_out,
|
|
// meter_floor, meter_load and samples_dropped
|
|
// all live there and are written from
|
|
// single_sample().
|
|
//
|
|
// int tuner_mode defined before this is included. process.h
|
|
// reads it and diverts the whole signal into
|
|
// the tuner when it is set, so it has to exist
|
|
// by the time process.h is pulled in below.
|
|
//
|
|
// effect_chain[] declared extern here, defined by the
|
|
// routed_effect_count includer, because the routing is the
|
|
// includer's to own - a scene loads it on the
|
|
// pedal and the bench sets it from argv.
|
|
//
|
|
// get_usb_audio_input() declared here, defined by the includer.
|
|
// sample_t usb-device.c on the pedal; silence on the
|
|
// bench, since what an effect does to a signal
|
|
// is not a question about USB.
|
|
//
|
|
// The i2s DMA is *not* in that list even though the buffer and the two
|
|
// pointer helpers are defined below. The bench drives them itself
|
|
// through a shim - it writes i2s_dma_buf and moves fake DMA registers
|
|
// around single_sample() - which is the whole point of the arrangement:
|
|
// what gets measured on a workstation is the pedal's own single_sample()
|
|
// and not a second implementation of it. So single_sample() belongs
|
|
// here rather than beside the hardware, and hardware.h owns the actual
|
|
// bring-up.
|
|
//
|
|
// Include order is program order in this project (see pedal.c), so
|
|
// "before" above means literally earlier in the file.
|
|
//
|
|
#include "lfo.h"
|
|
#include "Audio/cycles.h"
|
|
|
|
//
|
|
// How long an effect takes to fade in or out, in samples - a tenth of a
|
|
// second.
|
|
//
|
|
// Enabling or disabling an effect walks 'mix' up to or down from this
|
|
// instead of switching, so that nothing clicks, and it is the unit
|
|
// 'target' is counted in. Used from here, eeprom.h, ui.h and blink.c;
|
|
// it lived in effects/parametric_eq.h for a while, which worked only
|
|
// because priority 120 happened to be included ahead of everything that
|
|
// needed it.
|
|
//
|
|
#define EFF_ENABLE_STEPS ((int)SAMPLES_PER_SEC/10)
|
|
|
|
sample_t get_usb_audio_input(void);
|
|
|
|
//
|
|
// A pot's raw value turned into whatever the pot is measured in. Takes
|
|
// the whole array rather than the one byte, and knows its own index -
|
|
// so a call site names the pot once and cannot pair the accessor of one
|
|
// with the position of another.
|
|
//
|
|
//
|
|
// How an effect's wet and dry get mixed together.
|
|
//
|
|
// Linear is right when the wet signal is a filtered version of the dry
|
|
// and the two stay correlated - amplitudes add, so half of each gives
|
|
// you back what you started with. Almost everything here is like that.
|
|
//
|
|
// Equal power is right when they aren't correlated: a modulated delay,
|
|
// an echo tail, a reverb. There the *powers* add rather than the
|
|
// amplitudes, and a linear mix leaves a 3dB hole in the middle of the
|
|
// sweep. sin^2 + cos^2 == 1 fills it.
|
|
//
|
|
// Getting this backwards costs 3dB either way, so it is per effect
|
|
// rather than one law for all of them.
|
|
//
|
|
enum mix_law {
|
|
MIX_LINEAR,
|
|
MIX_POWER,
|
|
};
|
|
|
|
struct pot_descr {
|
|
const char *label;
|
|
const char *unit;
|
|
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 {
|
|
//
|
|
// The display name, and only that. The *short* name is not here:
|
|
// it is the effect's name in the generated C and what id_hash is
|
|
// computed from, so it is load-bearing at build time and read by
|
|
// nothing at run time. Renaming one still costs that effect its
|
|
// saved state; storing it cost a pointer per effect for nobody.
|
|
//
|
|
const char *name;
|
|
|
|
//
|
|
// What saved state is matched against. Both are computed by
|
|
// gen_effects.py and compiled in, so nothing here ever hashes
|
|
// anything - loading a scene compares two words per effect.
|
|
//
|
|
// 'id_hash' says *which effect this is*, and comes from the
|
|
// short name, which is already required to be unique and is
|
|
// already the effect's name in this generated code. Rename it
|
|
// and this effect's saved state stops being found, which is the
|
|
// price of not asking people to hand out permanent numbers.
|
|
//
|
|
// 'pot_hash' says *what its pots meant*, and covers their
|
|
// labels, curves, ranges and enumerations in order. Not their
|
|
// defaults or units: changing a default does not change what an
|
|
// already-stored value means, and wiping everybody's scenes over
|
|
// a retuned default would be a poor trade.
|
|
//
|
|
uint32_t id_hash, pot_hash;
|
|
|
|
unsigned int mix, target;
|
|
float mix_pot;
|
|
float def_mix;
|
|
enum mix_law mix_law;
|
|
|
|
//
|
|
// Set by 'MIX: NONE': this is not in the wet-and-dry business
|
|
// at all, and has no 'step' either - whoever runs it calls it
|
|
// by name. So the chain's idea of how much of it you are
|
|
// hearing does not describe it, and neither does its idea of
|
|
// whether it is running - see make_one_noise().
|
|
//
|
|
unsigned char no_mix;
|
|
|
|
//
|
|
// Set by 'MIX: STEREO': this effect reads both channels itself,
|
|
// so the input half of 'channels' does not apply to it. There
|
|
// is nothing to select when it wanted both anyway.
|
|
//
|
|
unsigned char stereo;
|
|
|
|
//
|
|
// Which channels this effect reads and writes, and how much of
|
|
// the channel it did not write survives a merge.
|
|
//
|
|
// A property of the effect exactly like 'mix', and like 'mix' it
|
|
// is applied entirely by do_effect_step() - no effect knows any
|
|
// of this exists. Zero is "read left, write both", which is
|
|
// what every effect did before there was a choice.
|
|
//
|
|
unsigned char channels;
|
|
float merge;
|
|
|
|
// What the mix law works out to, and where we are on the way
|
|
// there. Slewed rather than applied straight so that dragging
|
|
// the mix around doesn't click.
|
|
float dry, wet;
|
|
float dry_target, wet_target;
|
|
unsigned int seq, last;
|
|
unsigned char intense, active_pot;
|
|
unsigned char pot_values[2][10];
|
|
void (*init)(unsigned char[10]);
|
|
sample_t (*step)(sample_t);
|
|
const struct pot_descr pots[10];
|
|
};
|
|
|
|
#define EFFECT_POT(...) { __VA_ARGS__ }
|
|
|
|
//
|
|
// 'seq' does two jobs at once, and that is the point of it.
|
|
//
|
|
// Its low bit says which half of pot_values[] is live. Its value is what
|
|
// the audio core compares against 'last' to notice that anything changed.
|
|
// One counter doing both is what makes the handover a single store: the
|
|
// write that publishes the new data is the same write that says there is
|
|
// new data, so there is no window where one is true and the other is not,
|
|
// and no second variable to keep in step with this one.
|
|
//
|
|
// The cost is that '& 1' at a call site does not say which of the two
|
|
// jobs is being asked about. These do. They are the whole vocabulary:
|
|
//
|
|
// effect_pots() what is running now
|
|
// effect_pots_at() the same, when the caller already has seq
|
|
// effect_spare_pots() the half to fill in
|
|
// effect_publish() make the filled half the live one
|
|
//
|
|
static inline unsigned char *effect_pots_at(struct effect *e, unsigned int seq)
|
|
{
|
|
return e->pot_values[seq & 1];
|
|
}
|
|
|
|
static inline unsigned char *effect_spare_pots(struct effect *e, unsigned int seq)
|
|
{
|
|
return e->pot_values[!(seq & 1)];
|
|
}
|
|
|
|
//
|
|
// Reading 'seq' rather than being handed it, for callers that only want
|
|
// to look. A writer must latch it once and pass it to all three, or the
|
|
// live half could move between deciding what to copy and saying so.
|
|
//
|
|
static inline unsigned char *effect_pots(struct effect *e)
|
|
{
|
|
return effect_pots_at(e, e->seq);
|
|
}
|
|
|
|
//
|
|
// The release is what orders the fill against the publish: core 1 either
|
|
// sees the old set or the new one, never half of each.
|
|
//
|
|
static inline void effect_publish(struct effect *e, unsigned int seq)
|
|
{
|
|
smp_store_release(&e->seq, seq + 1);
|
|
}
|
|
|
|
//
|
|
// The largest value this pot can hold.
|
|
//
|
|
// 120 for anything continuous - see POT_TO_FLOAT() - but an enumeration
|
|
// only has as many valid values as it has names, and a stored value
|
|
// past the end of the list would index a NULL. A pot with no label is
|
|
// not a pot at all and holds nothing.
|
|
//
|
|
static int max_pot_val(struct effect *effect, int pot)
|
|
{
|
|
const struct pot_descr *desc = effect->pots + pot;
|
|
const char *const *enums;
|
|
|
|
if (!desc->label)
|
|
return 0;
|
|
|
|
enums = desc->enum_names;
|
|
if (!enums)
|
|
return 120;
|
|
|
|
for (int i = 0; ; i++) {
|
|
if (!enums[i])
|
|
return i - 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// How many effects one scene can route.
|
|
//
|
|
// This is a storage limit, not a limit on how many effects exist - the
|
|
// point of routing is that there are more effects to choose from than
|
|
// you can have in a chain at once. eeprom.h checks that a scene has the
|
|
// slots for it.
|
|
//
|
|
#define MAX_ROUTED_EFFECTS 14
|
|
|
|
// Effects and MIDI mapping auto-generated from scripts/gen_effects.py
|
|
extern uint8_t effect_chain[MAX_ROUTED_EFFECTS];
|
|
extern uint8_t routed_effect_count;
|
|
#include "effect_map.h"
|
|
|
|
|
|
//
|
|
// Work out the two multipliers for a mix setting.
|
|
//
|
|
// Only called when the setting changes, which is why the equal-power
|
|
// case can afford a fastsincos(): a quarter cycle, so sin climbs from 0
|
|
// to 1 as cos falls from 1 to 0, and sin^2 + cos^2 stays 1 the whole way
|
|
// across. fastsincos() takes its phase in cycles rather than radians.
|
|
//
|
|
static void set_mix_pot(struct effect *eff, float m)
|
|
{
|
|
eff->mix_pot = m;
|
|
|
|
if (eff->mix_law == MIX_POWER) {
|
|
struct sincos w = fastsincos(0.25f * m);
|
|
eff->dry_target = w.cos;
|
|
eff->wet_target = w.sin;
|
|
} else {
|
|
eff->dry_target = 1.0f - m;
|
|
eff->wet_target = m;
|
|
}
|
|
}
|
|
|
|
// How fast the multipliers chase their target: ~10ms at 48kHz
|
|
#define MIX_SLEW (1.0f / 512)
|
|
|
|
//
|
|
// Which channels an effect reads and writes.
|
|
//
|
|
// Two 2-bit fields in one byte, and zero is what every effect did
|
|
// before there was a choice: read the left channel, write both. So an
|
|
// effect that has never been told otherwise behaves exactly as it did,
|
|
// and a saved scene full of zeroes means the same thing it always did.
|
|
//
|
|
#define CH_IN(c) ((c) & 3)
|
|
#define CH_OUT(c) (((c) >> 2) & 3)
|
|
|
|
enum ch_in {
|
|
CH_IN_LEFT,
|
|
CH_IN_RIGHT,
|
|
// 2 and 3 reserved. A "sum of both" input is the one thing the
|
|
// output modes below cannot already express - read both, write
|
|
// one, keep the other for a later merge - and it is reserved
|
|
// rather than written so that adding it is not a format change.
|
|
};
|
|
|
|
enum ch_out {
|
|
CH_OUT_BOTH, // the answer goes everywhere
|
|
CH_OUT_LEFT, // ...to the left, right keeps what it had
|
|
CH_OUT_RIGHT,
|
|
CH_OUT_MERGE, // answer plus what was kept, to both
|
|
};
|
|
|
|
//
|
|
// How a parameter is named, by SysEx and by a binding alike.
|
|
//
|
|
// Zero is the mix and 1..10 are the effect's own pots, which is the
|
|
// numbering the app and the rule table have always used. The three
|
|
// above that are the same kind of thing as the mix rather than the same
|
|
// kind of thing as a pot: properties of how an effect is wired into the
|
|
// chain rather than of what it does to a sample, shared by every effect
|
|
// and declared by none of them.
|
|
//
|
|
// Addressed as pots because that is what makes them reachable. The one
|
|
// place that decides what a number means - set_target() - is also what
|
|
// bindings go through, so a footswitch can put an effect on the left and
|
|
// another one on the right without anything here knowing about
|
|
// footswitches. A command of their own would have needed teaching to
|
|
// SysEx and to bindings separately, and set_effect_mix() already has the
|
|
// comment about where that leads.
|
|
//
|
|
#define POT_MIX 0
|
|
#define POT_LAST 10
|
|
#define POT_CH_IN 11
|
|
#define POT_CH_OUT 12
|
|
#define POT_MERGE 13
|
|
#define POT_MAX POT_MERGE
|
|
|
|
//
|
|
// Run one effect, and work out how much of it to use, and where it goes.
|
|
//
|
|
// The effect itself knows none of this. It is handed a sample and hands
|
|
// one back; everything about which channel it came from, how much of it
|
|
// to use and where to put the answer happens here - the same bargain
|
|
// 'mix' already had, extended to say where as well as how much.
|
|
//
|
|
// The wet and dry are still blended against the channel being *written*,
|
|
// using that channel's own prior value. Which means an effect writing
|
|
// to one side leaves the other exactly alone, and that is what lets a
|
|
// split survive several effects before anything merges it.
|
|
//
|
|
static inline sample_t do_effect_step(struct effect *effect, sample_t val)
|
|
{
|
|
if (effect->mix != effect->target) {
|
|
int dir = effect->mix < effect->target ? +1 : -1;
|
|
effect->mix += dir;
|
|
}
|
|
|
|
if (effect->mix == 0) return val;
|
|
|
|
// Chase the mix setting, so moving it doesn't step the gain
|
|
effect->dry += (effect->dry_target - effect->dry) * MIX_SLEW;
|
|
effect->wet += (effect->wet_target - effect->wet) * MIX_SLEW;
|
|
|
|
// ...and fade the whole thing in on top of that, which is what
|
|
// 'mix' counting up to 'target' is for now that it no longer
|
|
// carries the setting itself.
|
|
float r = effect->mix * (1.0f / EFF_ENABLE_STEPS);
|
|
float dry = 1.0f + r * (effect->dry - 1.0f);
|
|
float wet = r * effect->wet;
|
|
|
|
unsigned int in = CH_IN(effect->channels);
|
|
unsigned int out = CH_OUT(effect->channels);
|
|
|
|
//
|
|
// A mono effect reads whatever is in the left half, so feeding it
|
|
// the right channel means putting it there. A stereo one already
|
|
// wanted both and is handed the sample untouched.
|
|
//
|
|
sample_t fed = val;
|
|
if (!effect->stereo && in == CH_IN_RIGHT)
|
|
fed.left = val.right;
|
|
|
|
sample_t got = effect->step(fed);
|
|
|
|
switch (out) {
|
|
case CH_OUT_LEFT:
|
|
val.left = dry * val.left + wet * got.left;
|
|
return val;
|
|
|
|
case CH_OUT_RIGHT:
|
|
val.right = dry * val.right + wet * got.right;
|
|
return val;
|
|
|
|
case CH_OUT_MERGE: {
|
|
//
|
|
// The answer, blended against the channel it came from,
|
|
// plus whatever weight of the channel it did not touch.
|
|
// At unity that is a plain sum, so a signal split in two
|
|
// and put back together with nothing in between comes
|
|
// back at unity - which is the property that makes a
|
|
// split path worth having at all.
|
|
//
|
|
float src = in == CH_IN_RIGHT ? val.right : val.left;
|
|
float kept = in == CH_IN_RIGHT ? val.left : val.right;
|
|
float here = in == CH_IN_RIGHT ? got.right : got.left;
|
|
|
|
val.left = val.right = dry * src + wet * here +
|
|
effect->merge * kept;
|
|
return val;
|
|
}
|
|
|
|
default:
|
|
val.left = dry * val.left + wet * got.left;
|
|
val.right = dry * val.right + wet * got.right;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
#include "process.h"
|
|
|
|
static int disable_all;
|
|
|
|
#define BLOCKSIZE 200
|
|
|
|
static raw_sample_t __attribute__((aligned(128))) i2s_dma_buf[16];
|
|
static int dma_tx;
|
|
static int dma_rx;
|
|
static unsigned int cpu_idx = 0;
|
|
|
|
static inline raw_sample_t *i2s_dma_tx_ptr(void)
|
|
{
|
|
return (raw_sample_t *) (dma_hw->ch[dma_tx].read_addr & ~7);
|
|
}
|
|
|
|
static inline raw_sample_t *i2s_dma_rx_ptr(void)
|
|
{
|
|
return (raw_sample_t *) (dma_hw->ch[dma_rx].write_addr & ~7);
|
|
}
|
|
|
|
//
|
|
// Metering, at the two ends of the chain.
|
|
//
|
|
// A 'struct envelope' with a fast attack and a slow release is a peak
|
|
// meter, so there is nothing to write here beyond picking the times - and
|
|
// the same thing with those reversed is a floor follower, which is what
|
|
// the noise floor estimate is. Stop playing and the peak falls to the
|
|
// noise in a third of a second while the floor follows it down; start
|
|
// playing and the peak jumps while the floor takes five seconds to
|
|
// notice.
|
|
//
|
|
// The floor is fed the *gate's* envelope and nothing else, and the reason
|
|
// is definitional rather than measured: a floor reading is only useful if
|
|
// it can be compared against 'Gate', Gate is compared against the
|
|
// gate's envelope, so the floor has to be that same envelope. Anything
|
|
// else is a different quantity wearing the same units, and would only
|
|
// happen to agree.
|
|
//
|
|
// How far the two *would* diverge is not known. In theory it depends on
|
|
// the character of the noise rather than its level: the peak meter's
|
|
// 0.1ms attack catches transients that the gate's 1.5ms one averages
|
|
// away, so tonal hum should read the same on both and broadband noise
|
|
// should read higher on the peak meter by something like its crest
|
|
// factor. That is theory. Nothing here has measured it, and an earlier
|
|
// version of this comment claimed nine decibels on the strength of two
|
|
// readings that turned out to be from different boards.
|
|
//
|
|
// What has been measured, for scale (see process.h for the reference):
|
|
//
|
|
// 90mVpp at 110Hz reads -30dBFS on both boards, which is also
|
|
// the arithmetic: 31.8mVrms against 1Vrms is -29.95dB. So the
|
|
// analog front ends agree, and dBFS means the same thing on each.
|
|
//
|
|
// the noise floor reads -61dBFS on a current board and -56dBFS
|
|
// on an early one. Boards differ; the meter is measuring that.
|
|
//
|
|
// on each board the floor now sits within a couple of dB of the
|
|
// Level at which the gate actually starts closing, which is the
|
|
// property this is for.
|
|
//
|
|
static struct envelope meter_in_env, meter_floor_env, meter_out_env;
|
|
|
|
static void init_meters(void)
|
|
{
|
|
//
|
|
// Here rather than at startup because the counter is per-core and
|
|
// this runs inside audio_processing(), on the core that reads it.
|
|
//
|
|
cycle_counter_init();
|
|
|
|
envelope_init(&meter_in_env, 0.1f, 300.0f);
|
|
envelope_init(&meter_floor_env, 5000.0f, 100.0f);
|
|
envelope_init(&meter_out_env, 0.1f, 300.0f);
|
|
}
|
|
|
|
static inline void __audio_func(single_sample)(float mix)
|
|
{
|
|
raw_sample_t *cpu_ptr = i2s_dma_buf + cpu_idx;
|
|
cpu_idx = (cpu_idx + 1) & 15;
|
|
|
|
//
|
|
// The chain's own frame counter, and the only place it moves.
|
|
//
|
|
// lfo_step_X() reads its low bits to decide when to do the real
|
|
// work - see lfo.h. It lives here rather than in the LFOs so that
|
|
// every slow LFO in the chain recalculates on the same frame,
|
|
// which is what lets the compiler hoist one test out of four
|
|
// calls, and so that an effect cannot forget to advance it.
|
|
//
|
|
audio_sample_count++;
|
|
|
|
//
|
|
// Wait for RX DMA to produce the sample.
|
|
//
|
|
// This loop is the idle time, by definition: per sample period the
|
|
// cpu either spins here or works, and the period is known because
|
|
// the DMA sets it. So timing the spin measures the load exactly,
|
|
// with nothing to estimate.
|
|
//
|
|
// One sample in sixteen, because two bus reads at 48kHz to measure
|
|
// a load is about a percent of the thing being measured. Sampling
|
|
// it costs a sixteenth of that and the answer is averaged anyway.
|
|
//
|
|
static unsigned int meter_phase;
|
|
bool timed = !(++meter_phase & 15);
|
|
uint32_t spin_start = timed ? cycle_count() : 0;
|
|
|
|
while (cpu_ptr == i2s_dma_rx_ptr())
|
|
tight_loop_contents();
|
|
|
|
if (timed) {
|
|
//
|
|
// Cycles, not microseconds. timer_hw->timerawl is 1MHz,
|
|
// and one tick of it is 4.8% of a 20.83us sample period -
|
|
// so timing the spin that way makes every answer a
|
|
// rounding of something coarser than most of the effects
|
|
// being measured, and a fraction of a tick's worth of bias
|
|
// is enough to invent a difference that is not there. A
|
|
// cycle at 153.6MHz is 0.03% of the period instead.
|
|
//
|
|
// Self-calibrating, which is the second thing this buys.
|
|
// 'span' is exactly sixteen sample periods, because both
|
|
// ends of it are the moment the DMA delivered a sample, so
|
|
// the load comes out as a ratio of two measured intervals
|
|
// and neither the sample rate nor the system clock has to
|
|
// be known or assumed. 96kHz, a different clock, an
|
|
// overclock - all of it just works.
|
|
//
|
|
static uint32_t meter_prev;
|
|
uint32_t now = cycle_count();
|
|
uint32_t spin = now - spin_start;
|
|
uint32_t span = now - meter_prev;
|
|
|
|
meter_prev = now;
|
|
|
|
//
|
|
// The first time round, meter_prev is meaningless and span
|
|
// is nonsense. It costs one bad sample that the one-pole
|
|
// below swallows in about 21ms of a machine that has just
|
|
// started up, so it is not worth a flag to avoid.
|
|
//
|
|
float idle = span ? (float)spin * 16.0f / (float)span : 1.0f;
|
|
float load = idle < 1.0f ? 1.0f - idle : 0.0f;
|
|
|
|
meter_load += (load - meter_load) * (1.0f / 64);
|
|
}
|
|
|
|
//
|
|
// Check we're safely ahead of TX DMA. Missing the deadline is not
|
|
// clipping, even though the LED shows both - see status.h.
|
|
//
|
|
// This comparison is a race and is meant to be one. One side is a
|
|
// counter this loop keeps, the other is where a DMA engine has got
|
|
// to, and nothing synchronises them - nothing can, which is the
|
|
// point: if the cpu keeps up they stay apart, and if it doesn't
|
|
// they don't. So this is a stochastic detector. Fall behind and
|
|
// it comes out true *sometimes*, at a rate that says how far
|
|
// behind, not every sample and not on the same ones twice.
|
|
//
|
|
// Which makes 'samples_dropped' a rate estimate rather than a
|
|
// tally, and is why nothing here tries to make it exact. The
|
|
// increment is not atomic against the drain in send_status(), so a
|
|
// reset can be lost and the count read high for a tick; against a
|
|
// number that has no exact value to begin with, that is not worth
|
|
// an ldrex/strex pair. Rather like the clipping indicator: a
|
|
// signal is clipping even though it is only over the line some of
|
|
// the time, because a signal goes up and down.
|
|
//
|
|
unsigned int tx_idx = i2s_dma_tx_ptr() - i2s_dma_buf;
|
|
if (((cpu_idx - tx_idx) & 15) < 2)
|
|
samples_dropped++;
|
|
|
|
// In-place processing
|
|
raw_sample_t sample = *cpu_ptr;
|
|
sample_t in = process_input(sample);
|
|
sample_t usb_in = get_usb_audio_input();
|
|
|
|
// We need to do the USB input as stereo too
|
|
if (settings.usb_input == USB_IN_PRE_FX) {
|
|
in.left += usb_in.left;
|
|
in.right += usb_in.right;
|
|
}
|
|
|
|
//
|
|
// Metered here rather than inside the signal chain, because this is
|
|
// the pedal's input: what arrived, before Trim has an opinion.
|
|
//
|
|
meter_in = envelope_step(&meter_in_env, in.left);
|
|
|
|
//
|
|
// The signal chain proper: trim and the gate, then whatever is
|
|
// routed. Called straight rather than through do_effect_step()
|
|
// because it is not an effect - see effects/signal_chain.h.
|
|
//
|
|
sample_t out = chain_step(in);
|
|
|
|
//
|
|
// ...and the floor after it, because it follows the gate's own
|
|
// envelope rather than the peak meter above. Same quantity 'Gate'
|
|
// is compared against, so the two numbers can be read against each
|
|
// other - which is the only reason to show a floor at all.
|
|
//
|
|
meter_floor = envelope_step(&meter_floor_env, chain.envelope.value);
|
|
|
|
for (int i = 0; i < routed_effect_count; i++) {
|
|
out = do_effect_step(effects[effect_chain[i]], out);
|
|
}
|
|
|
|
// ...and the far end of it. Slewed by chain_step() above.
|
|
out.left *= chain.volume;
|
|
out.right *= chain.volume;
|
|
|
|
//
|
|
// Global bypass crossfades to the untouched input, so trim, the
|
|
// gate and the volume all go away with everything else. Bypass
|
|
// means bypass, which does mean it can be a step in level.
|
|
//
|
|
out.left = linear(mix, in.left, out.left);
|
|
out.right = linear(mix, in.right, out.right);
|
|
|
|
if (settings.usb_input == USB_IN_MIX) {
|
|
out.left += usb_in.left;
|
|
out.right += usb_in.right;
|
|
}
|
|
|
|
//
|
|
// And the output meter, on what actually leaves - after Volume and
|
|
// after the bypass crossfade, so a bypassed pedal reads its input.
|
|
//
|
|
float peak = fabsf(out.left) > fabsf(out.right)
|
|
? fabsf(out.left) : fabsf(out.right);
|
|
meter_out = envelope_step(&meter_out_env, peak);
|
|
|
|
*cpu_ptr = process_output(out, sample);
|
|
}
|
|
|
|
static void bypass(void)
|
|
{
|
|
for (int i = 0; i < BLOCKSIZE; i++) {
|
|
single_sample(0.0);
|
|
}
|
|
}
|
|
|
|
static __attribute__((noinline)) void __audio_func(make_one_noise)(void)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(effects); i++) {
|
|
struct effect *effect = effects[i];
|
|
|
|
unsigned seq = smp_load_acquire(&effect->seq);
|
|
if (seq == effect->last)
|
|
continue;
|
|
|
|
//
|
|
// An effect that isn't running doesn't need its
|
|
// coefficients recomputed - but don't mark the update as
|
|
// consumed either, or it just gets lost. 'target' is
|
|
// already set by the time an effect is routed back in, so
|
|
// this picks the change up before it can be heard.
|
|
//
|
|
// 'no_mix' is exempt because "isn't running" is a
|
|
// statement about the wet/dry fade, and such an effect
|
|
// isn't part of it. Both cases need it: the settings
|
|
// pseudo-effect is nothing *but* its init(), and used to
|
|
// force 'target' nonzero from inside init() purely to
|
|
// keep this test from skipping it next time; and the
|
|
// signal chain runs unconditionally, so its coefficients
|
|
// always matter.
|
|
//
|
|
if (!effect->no_mix && !effect->mix && !effect->target)
|
|
continue;
|
|
|
|
effect->last = seq;
|
|
effect->init(effect_pots_at(effect, seq));
|
|
}
|
|
|
|
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);
|
|
}
|