You've already forked torvalds-GuitarPedal
mirror of
https://github.com/torvalds/GuitarPedal.git
synced 2026-08-14 04:43:53 +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>
128 lines
4.5 KiB
Python
128 lines
4.5 KiB
Python
#
|
|
# Building a scene payload, from the host.
|
|
#
|
|
# There is no way to set an effect's channel routing over MIDI - that
|
|
# wants a helper UI rather than a raw byte and is deferred - so a test
|
|
# that needs one writes the scene directly and plants it with picotool.
|
|
#
|
|
# The identities come out of the *built* effect_map.h rather than being
|
|
# recomputed here. gen_effects.py hashes a canonical description of
|
|
# each effect, and reimplementing that hash in a second language is how
|
|
# the two would come to disagree about which effect is which, which is
|
|
# the one thing a stored scene must not be wrong about.
|
|
#
|
|
# The layout is the other half of the _Static_asserts in
|
|
# Firmware/scene.h. Those exist because two of the offsets here are not
|
|
# visible in the C declaration: a scene_effect is 22 bytes of fields in
|
|
# a 24-byte slot, and the array of them is 4-aligned so it starts at 24
|
|
# rather than 22. Both were wrong here first time round.
|
|
#
|
|
import hashlib
|
|
import re
|
|
import struct
|
|
import sys
|
|
|
|
SLOT_SIZE = 4096
|
|
TAIL_SIZE = 64
|
|
PAYLOAD_SIZE = SLOT_SIZE - TAIL_SIZE
|
|
MARKER = b"SAVEAREA"
|
|
CONTAINER_VERSION = 1
|
|
|
|
SCENE_VERSION = 3
|
|
SCENE_MAX_EFFECTS = 32
|
|
MAX_ROUTED = 14
|
|
MAX_RULES = 16
|
|
|
|
EFFECT_SIZE = 24
|
|
EFFECTS_AT = 24
|
|
SCENE_SIZE = EFFECTS_AT + SCENE_MAX_EFFECTS * EFFECT_SIZE + MAX_RULES * 6
|
|
|
|
XIP_BASE = 0x10000000
|
|
AREA_OFFSET = 0x1C0000
|
|
|
|
# do_effect_step()'s two 2-bit fields, and zero is what an effect did
|
|
# before there was a choice: read the left channel, write both.
|
|
IN_LEFT, IN_RIGHT = 0, 1
|
|
OUT_BOTH, OUT_LEFT, OUT_RIGHT, OUT_MERGE = 0, 1, 2, 3
|
|
|
|
|
|
def channels(ch_in=IN_LEFT, ch_out=OUT_BOTH):
|
|
return ch_in | (ch_out << 2)
|
|
|
|
|
|
def effects_from_map(path="../build/effect_map.h"):
|
|
"""Every effect in the built firmware, in id order."""
|
|
text = open(path).read()
|
|
out = []
|
|
#
|
|
# The short name is not here to be matched on: it stopped being
|
|
# stored on struct effect once nothing read it at run time. It was
|
|
# never read here either - by_name() below deliberately matches the
|
|
# display name, because copies share a short one.
|
|
#
|
|
for m in re.finditer(
|
|
r'\.name = "([^"]*)",\s*\n'
|
|
r'\s*\.id_hash = (0x[0-9a-f]+),\s*\n\s*\.pot_hash = (0x[0-9a-f]+),',
|
|
text):
|
|
out.append({"name": m.group(1),
|
|
"id": int(m.group(2), 16), "pot": int(m.group(3), 16),
|
|
"pots": [0] * 10, "mix": 120, "channels": 0, "merge": 120})
|
|
if not out:
|
|
sys.exit(f"scene: no effects in {path} - built?")
|
|
|
|
# The schema defaults, so a scene says what the pedal would have said
|
|
# rather than zeroing every knob it does not mention.
|
|
for eff, block in zip(out, text.split(".pots = {")[1:]):
|
|
# EFFECT_POT(label, unit, def_val[, enum]) - the third field
|
|
# used to be a converting accessor nothing ever called through.
|
|
vals = re.findall(r'EFFECT_POT\("[^"]*",[^,]*,\s*(\d+)', block)
|
|
for i, v in enumerate(vals[:10]):
|
|
eff["pots"][i] = int(v)
|
|
return out
|
|
|
|
|
|
def by_name(effects, name):
|
|
"""By full name, because the copies share a short one.
|
|
|
|
'Tone 1' and 'Tone 2' are both [TONE] - that is what COPIES: means -
|
|
so the short name cannot pick between them and the display name can.
|
|
"""
|
|
for e in effects:
|
|
if e["name"].lower() == name.lower():
|
|
return e
|
|
known = ", ".join(e["name"] for e in effects)
|
|
sys.exit(f"scene: no effect named '{name}'. Have: {known}")
|
|
|
|
|
|
def build(effects, routed):
|
|
"""The payload. 'routed' is a list of indices into 'effects'."""
|
|
body = struct.pack("<HBB", SCENE_VERSION, len(effects), len(routed))
|
|
body += bytes(routed) + bytes(MAX_ROUTED - len(routed))
|
|
body += struct.pack("<B", 0) + bytes(3)
|
|
body = body.ljust(EFFECTS_AT, b"\0")
|
|
|
|
for e in effects:
|
|
body += struct.pack("<II", e["id"], e["pot"])
|
|
body += bytes(e["pots"])
|
|
body += struct.pack("<BBB", e["mix"], e["channels"], e["merge"])
|
|
body += bytes(EFFECT_SIZE - 21)
|
|
body += bytes(EFFECT_SIZE * (SCENE_MAX_EFFECTS - len(effects)))
|
|
body += bytes(6 * MAX_RULES)
|
|
|
|
if len(body) != SCENE_SIZE:
|
|
sys.exit(f"scene: built {len(body)} bytes, "
|
|
f"struct scene_payload is {SCENE_SIZE}")
|
|
return body
|
|
|
|
|
|
def slot_image(payload, key, seq):
|
|
"""Wrap a payload in the container flash_store.h expects."""
|
|
body = payload.ljust(PAYLOAD_SIZE, b"\0")
|
|
body += MARKER + struct.pack("<IHH", seq, CONTAINER_VERSION, key)
|
|
body += b"\0" * 16
|
|
return body + hashlib.sha256(body).digest()
|
|
|
|
|
|
def slot_address(n):
|
|
return XIP_BASE + AREA_OFFSET + n * SLOT_SIZE
|