You've already forked torvalds-GuitarPedal
mirror of
https://github.com/torvalds/GuitarPedal.git
synced 2026-08-18 21:26:54 +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>
376 lines
11 KiB
C
376 lines
11 KiB
C
#ifndef MIDI_TX_H
|
|
#define MIDI_TX_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
//
|
|
// What the pedal says, queued rather than sent.
|
|
//
|
|
// A reply used to be turned into four-byte MIDI packets and pushed at USB
|
|
// in one pass of the main loop, with no yield anywhere in it.
|
|
// usb_audio_task() is the last thing in that loop, and the audio
|
|
// endpoint's software fifo holds three packets - three milliseconds. A
|
|
// reply that takes longer than that to send starves the audio endpoint
|
|
// for as long as it runs, and the schema takes 15700 bytes' worth.
|
|
//
|
|
// Two things were wrong and they are easy to confuse:
|
|
//
|
|
// - The *size* of a reply. Even with the host reading as fast as it
|
|
// can, one pass of the main loop spent sending 15700 bytes is a pass
|
|
// not spent feeding audio.
|
|
// - Whether anything is *reading*. usb_midi_write() spins for 20ms per
|
|
// packet on a full transmit fifo. A host that drains the endpoint
|
|
// keeps that fifo empty and the spin almost never happens; a host that
|
|
// has stopped reading turns every single packet into a timeout, which
|
|
// measured 12-14x worse than the size effect on its own.
|
|
//
|
|
// A queue answers both. The reply is serialised once, into here, and
|
|
// then handed to USB a few packets at a time from the main loop -
|
|
// so a long reply costs many short passes instead of one enormous one,
|
|
// and a full fifo means "come back later" instead of a 20ms wait.
|
|
//
|
|
// It also closes a hole that a resume-where-you-left-off fix would have
|
|
// opened. usb-device.c explains that incoming MIDI is polled from the
|
|
// main loop rather than from tud_midi_rx_cb() because a parameter write
|
|
// landing in the middle of a sender that is walking effects[] would have
|
|
// it report a mixture of before and after. Serialising the whole reply
|
|
// in one pass and draining it afterwards keeps that guarantee by
|
|
// construction rather than by convention.
|
|
//
|
|
// SysEx goes to USB only. The hardware jacks run at 31250 baud, where
|
|
// the schema is 5.2 seconds of wire time, and uart_midi_write() is a
|
|
// 512-byte ring that drops silently when full - so the TRS side would
|
|
// need to solve the slow-consumer problem before it could carry any of
|
|
// this, and that is a decision rather than an omission.
|
|
//
|
|
|
|
//
|
|
// Sizing.
|
|
//
|
|
// The payload ring only holds *generated* bytes. Anything already in
|
|
// flash is queued by pointer and costs a descriptor and nothing else,
|
|
// which is what keeps the 15700-byte schema out of RAM entirely.
|
|
//
|
|
// What has to fit is a state dump. That is 140 bytes as things stand,
|
|
// and about 1.4kB in the worst case where every effect is routed and
|
|
// every rule table is full. 2kB leaves room for that with an identity
|
|
// reply alongside it.
|
|
//
|
|
// Both sizes are powers of two so that the ring index is a mask.
|
|
//
|
|
#define MIDI_TX_PAYLOAD 2048
|
|
#define MIDI_TX_MSGS 16
|
|
|
|
//
|
|
// One run of bytes waiting to go out.
|
|
//
|
|
// 'more' means the next entry finishes what this one starts, which is how
|
|
// a message can be part flash and part generated without being copied:
|
|
// the schema is a generated 'F0 7D 02', 15700 static bytes, and a
|
|
// generated 'F7'. Three descriptors, four bytes of RAM.
|
|
//
|
|
struct midi_msg {
|
|
const uint8_t *flash; // non-NULL: the bytes are in flash
|
|
uint32_t off; // otherwise: where in the payload ring
|
|
uint16_t len;
|
|
uint16_t sent;
|
|
bool more;
|
|
};
|
|
|
|
//
|
|
// The queue, as one thing.
|
|
//
|
|
// Grouped into a structure rather than into a shared name prefix, so that
|
|
// what belongs together is held together by the language and not by
|
|
// everybody remembering to type 'midi_tx_'. The accesses read the same
|
|
// either way - midi_tx.head against midi_tx_head - and this way there is
|
|
// somewhere obvious to put the next field, and no doubt about whether a
|
|
// given global is part of this or merely named like it.
|
|
//
|
|
// Three things live in here and they have different lifetimes:
|
|
//
|
|
// - the rings themselves, and the counters saying what is in flight
|
|
// - 'pend', the bytes copied so far that have not become a descriptor
|
|
// - 'txn', where to rewind to if the reply being built does not fit
|
|
//
|
|
static struct {
|
|
struct midi_msg ring[MIDI_TX_MSGS];
|
|
uint8_t payload[MIDI_TX_PAYLOAD];
|
|
|
|
//
|
|
// Free-running counters, masked only where they index. head -
|
|
// tail is how much is in flight and stays right across the wrap.
|
|
//
|
|
uint32_t head, tail;
|
|
uint32_t pay_head, pay_tail;
|
|
|
|
//
|
|
// The transaction being built. Nothing here is visible to the
|
|
// drain until midi_tx_commit(), so a reply that turns out not to
|
|
// fit leaves no trace of itself.
|
|
//
|
|
uint32_t txn_head; // descriptor head when it started
|
|
uint32_t txn_pay; // payload head when it started
|
|
uint32_t pend_off; // bytes copied but not yet a descriptor
|
|
uint16_t pend_len;
|
|
bool txn_failed;
|
|
|
|
//
|
|
// The packetiser's carry. See midi_tx_drain() for why a packet
|
|
// has to be able to outlive the call that built it.
|
|
//
|
|
uint8_t pack[3];
|
|
unsigned int pack_len;
|
|
uint8_t pkt[4];
|
|
bool pkt_ready;
|
|
} midi_tx;
|
|
|
|
static inline uint32_t midi_tx_pay_used(void)
|
|
{
|
|
return midi_tx.pay_head - midi_tx.pay_tail;
|
|
}
|
|
|
|
static inline uint32_t midi_tx_msgs_used(void)
|
|
{
|
|
return midi_tx.head - midi_tx.tail;
|
|
}
|
|
|
|
//
|
|
// Start a reply. Anything half-built from a previous one is discarded:
|
|
// a transaction that never committed had already failed, and its bytes
|
|
// are not owed to anybody.
|
|
//
|
|
static void midi_tx_start(void)
|
|
{
|
|
midi_tx.txn_head = midi_tx.head;
|
|
midi_tx.txn_pay = midi_tx.pay_head;
|
|
midi_tx.pend_off = midi_tx.pay_head;
|
|
midi_tx.pend_len = 0;
|
|
midi_tx.txn_failed = false;
|
|
}
|
|
|
|
//
|
|
// Close whatever has been copied so far into a descriptor of its own.
|
|
//
|
|
// Called when a static run interrupts the generated bytes, and again at
|
|
// commit. 'more' is set on everything here and cleared once at commit,
|
|
// because only the last descriptor of a reply knows it is the last.
|
|
//
|
|
static void midi_tx_flush_pending(void)
|
|
{
|
|
struct midi_msg *m;
|
|
|
|
if (!midi_tx.pend_len)
|
|
return;
|
|
if (midi_tx_msgs_used() >= MIDI_TX_MSGS) {
|
|
midi_tx.txn_failed = true;
|
|
return;
|
|
}
|
|
|
|
m = &midi_tx.ring[midi_tx.head & (MIDI_TX_MSGS - 1)];
|
|
m->flash = NULL;
|
|
m->off = midi_tx.pend_off;
|
|
m->len = midi_tx.pend_len;
|
|
m->sent = 0;
|
|
m->more = true;
|
|
midi_tx.head++;
|
|
|
|
midi_tx.pend_off = midi_tx.pay_head;
|
|
midi_tx.pend_len = 0;
|
|
}
|
|
|
|
//
|
|
// Generated bytes, copied.
|
|
//
|
|
static void midi_tx_bytes(const uint8_t *buf, size_t len)
|
|
{
|
|
if (midi_tx.txn_failed)
|
|
return;
|
|
if (MIDI_TX_PAYLOAD - midi_tx_pay_used() < len) {
|
|
midi_tx.txn_failed = true;
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
midi_tx.payload[midi_tx.pay_head & (MIDI_TX_PAYLOAD - 1)] = buf[i];
|
|
midi_tx.pay_head++;
|
|
}
|
|
midi_tx.pend_len += len;
|
|
}
|
|
|
|
//
|
|
// Bytes that are already somewhere permanent, queued where they lie.
|
|
//
|
|
// The caller is promising the bytes outlive the queue, which for a
|
|
// 'static const' in flash is trivially true and for anything else is not.
|
|
//
|
|
static void midi_tx_static(const uint8_t *buf, size_t len)
|
|
{
|
|
struct midi_msg *m;
|
|
|
|
if (midi_tx.txn_failed)
|
|
return;
|
|
|
|
midi_tx_flush_pending();
|
|
if (midi_tx.txn_failed)
|
|
return;
|
|
if (midi_tx_msgs_used() >= MIDI_TX_MSGS) {
|
|
midi_tx.txn_failed = true;
|
|
return;
|
|
}
|
|
|
|
m = &midi_tx.ring[midi_tx.head & (MIDI_TX_MSGS - 1)];
|
|
m->flash = buf;
|
|
m->off = 0;
|
|
m->len = len;
|
|
m->sent = 0;
|
|
m->more = true;
|
|
midi_tx.head++;
|
|
}
|
|
|
|
//
|
|
// Publish, or leave no trace.
|
|
//
|
|
// Returning false is not an error the caller has to report - it means
|
|
// "not now". A sender that leaves its request flag set will be back next
|
|
// time round the main loop, which is the whole of the backpressure and is
|
|
// also why several requests for the same reply still collapse into one.
|
|
//
|
|
static bool midi_tx_commit(void)
|
|
{
|
|
midi_tx_flush_pending();
|
|
|
|
if (midi_tx.txn_failed || midi_tx.head == midi_tx.txn_head) {
|
|
// Rewind. Nothing committed, so nothing is owed.
|
|
midi_tx.head = midi_tx.txn_head;
|
|
midi_tx.pay_head = midi_tx.txn_pay;
|
|
midi_tx.pend_len = 0;
|
|
return false;
|
|
}
|
|
|
|
// Only the last descriptor is the end of the message.
|
|
midi_tx.ring[(midi_tx.head - 1) & (MIDI_TX_MSGS - 1)].more = false;
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// The packetiser, and the one packet it may be holding.
|
|
//
|
|
// MIDI 1.0 carries SysEx three bytes at a time: CIN 0x04 while the stream
|
|
// continues, and 0x05, 0x06 or 0x07 for a packet that ends it with one,
|
|
// two or three bytes in it. Which means the shape of a packet is not
|
|
// known until its last byte has been read, so the accumulator has to
|
|
// survive both the end of a descriptor and the end of a drain.
|
|
//
|
|
// A built packet that USB would not take stays here rather than being
|
|
// rebuilt, because the bytes behind it have already been consumed.
|
|
//
|
|
static uint8_t midi_tx_byte(const struct midi_msg *m, uint16_t i)
|
|
{
|
|
if (m->flash)
|
|
return m->flash[i];
|
|
return midi_tx.payload[(m->off + i) & (MIDI_TX_PAYLOAD - 1)];
|
|
}
|
|
|
|
static bool midi_tx_push(void)
|
|
{
|
|
if (!midi_tx.pkt_ready)
|
|
return true;
|
|
if (!usb_midi_write_nb(midi_tx.pkt))
|
|
return false;
|
|
midi_tx.pkt_ready = false;
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// How much to hand over in one pass.
|
|
//
|
|
// "Until the endpoint is full" is the obvious rule and it is the wrong
|
|
// one, because CFG_TUD_MIDI_TX_BUFSIZE is 8192 - most of a schema. A
|
|
// drain that stops only when the fifo is full very nearly sends the whole
|
|
// reply in one pass, which is the thing this queue exists to stop, and it
|
|
// measured almost exactly that: no better than before.
|
|
//
|
|
// What matters is not what the fifo will hold but what USB will actually
|
|
// move before the main loop comes round again. A full-speed bulk
|
|
// endpoint is 64 bytes a frame, so sixteen four-byte packets a
|
|
// millisecond, and the audio endpoint has three milliseconds of slack in
|
|
// front of it. One frame's worth per pass keeps the wire busy and leaves
|
|
// the loop immediately - and since the loop goes round far faster than a
|
|
// millisecond, the wire stays saturated anyway.
|
|
//
|
|
#define MIDI_TX_PER_PASS 16
|
|
|
|
//
|
|
// Hand over a frame's worth, and no more.
|
|
//
|
|
// Called from the main loop, next to everything else that has to happen
|
|
// there. It returns on the packet budget or on a full endpoint,
|
|
// whichever comes first, which is what keeps usb_audio_task() running on
|
|
// time no matter how long the reply is.
|
|
//
|
|
static void midi_tx_drain(void)
|
|
{
|
|
unsigned int pushed = 0;
|
|
|
|
while (midi_tx.tail != midi_tx.head) {
|
|
struct midi_msg *m = &midi_tx.ring[midi_tx.tail & (MIDI_TX_MSGS - 1)];
|
|
|
|
if (midi_tx.pkt_ready) {
|
|
if (pushed >= MIDI_TX_PER_PASS)
|
|
return;
|
|
if (!midi_tx_push())
|
|
return;
|
|
pushed++;
|
|
}
|
|
|
|
if (m->sent >= m->len) {
|
|
if (!m->flash)
|
|
midi_tx.pay_tail += m->len;
|
|
midi_tx.tail++;
|
|
continue;
|
|
}
|
|
|
|
uint8_t b = midi_tx_byte(m, m->sent++);
|
|
|
|
if (b == 0xF0)
|
|
midi_tx.pack_len = 0;
|
|
midi_tx.pack[midi_tx.pack_len++] = b;
|
|
|
|
if (b == 0xF7) {
|
|
midi_tx.pkt[0] = 0x04 + midi_tx.pack_len;
|
|
midi_tx.pkt[1] = midi_tx.pack[0];
|
|
midi_tx.pkt[2] = midi_tx.pack_len > 1 ? midi_tx.pack[1] : 0;
|
|
midi_tx.pkt[3] = midi_tx.pack_len > 2 ? midi_tx.pack[2] : 0;
|
|
midi_tx.pack_len = 0;
|
|
midi_tx.pkt_ready = true;
|
|
} else if (midi_tx.pack_len == 3) {
|
|
midi_tx.pkt[0] = 0x04;
|
|
midi_tx.pkt[1] = midi_tx.pack[0];
|
|
midi_tx.pkt[2] = midi_tx.pack[1];
|
|
midi_tx.pkt[3] = midi_tx.pack[2];
|
|
midi_tx.pack_len = 0;
|
|
midi_tx.pkt_ready = true;
|
|
}
|
|
}
|
|
|
|
if (pushed < MIDI_TX_PER_PASS)
|
|
midi_tx_push();
|
|
}
|
|
|
|
//
|
|
// Is there a reply in flight?
|
|
//
|
|
// Asked by a sender that would rather wait than queue a second one behind
|
|
// the first, and by anything that wants to know the pedal has finished
|
|
// talking.
|
|
//
|
|
static inline bool midi_tx_busy(void)
|
|
{
|
|
return midi_tx.head != midi_tx.tail;
|
|
}
|
|
|
|
#endif
|