You've already forked torvalds-GuitarPedal
mirror of
https://github.com/torvalds/GuitarPedal.git
synced 2026-09-08 08:47:00 +00:00
The bench could already run the pedal's own audio core on a host. What
it could not do is say whether the answer was right, because there was
nothing to be right against. This adds the two things to be right
against and the plumbing between them.
ngspice.py drives the netlists in spice/ and gets numbers back.
The point is not the simulator, which anyone has; it
is that the netlist lives in the repository, so the
conditions a measurement was taken under end up in a
file rather than in somebody's memory of which switch
was where.
loop.py the other direction: stimulus out to a real pedal over
USB audio and the answer back, calibrated, with the
window checks that stop a mis-triggered capture from
being read as a result.
targets.py which effect claims to be which netlist, and the
pot-to-parameter map between them. Its own file
because two copies of that map is an afternoon spent
measuring one setting against another.
compare-spice.py the comparison itself - level ladder, harmonics,
small signal, and energy off the harmonic grid.
figure.py one place that decides what a figure looks like.
shootout.py a blind, level-matched A/B page, because a tone
shoot-loop.py decision that was not listened to is not a decision.
Deliberately no check- target for compare-spice.py: it reports rather
than passes, the answer being a handful of decibels whose meaning is a
judgement about how much a simplification gave up. A check- target that
cannot fail gets read as a green light.
check-effect-ids.py is here because an effect id is a position in a list
ordered by PRIORITY, so adding an effect renumbers everything below it,
and a number written down instead of asked for silently starts meaning
something else. The failure has no symptom: a stale id is still a valid
id, so the write lands on a real pot of the wrong effect and the run
goes on printing plausible numbers. measure-load.py asks
pedal.settings_effect() here rather than naming a position, which is the
one place in this directory that had written one down.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
288 lines
11 KiB
Makefile
288 lines
11 KiB
Makefile
CC = gcc
|
|
#
|
|
# -I../Firmware/midi because test-midi-cin.c says #include "midi.h" and
|
|
# that header is a directory further down. It was missing, and invisible
|
|
# with it: a built test-midi-cin sitting in the tree is newer than its
|
|
# source, so make never rebuilds it and 'make check' passes. It only
|
|
# fails from a clean checkout, which is the one place it matters.
|
|
#
|
|
CFLAGS = -Wall -O2 -I.. -I../Firmware -I../Firmware/midi -I../build/ \
|
|
-ffast-math -fsingle-precision-constant -Wfloat-conversion
|
|
|
|
visualize: test-fft
|
|
python visualize.py DADGAD.wav
|
|
|
|
test-fft: test-fft.c
|
|
|
|
test-midi-cin: test-midi-cin.c
|
|
|
|
#
|
|
# The effect bench: the pedal's own audio core, built for the host.
|
|
#
|
|
# Everything it needs is generated here rather than taken from
|
|
# ../build, so it does not need the firmware to have been built
|
|
# and never writes into the source tree. The three math tables carry
|
|
# the shift values from CMakeLists.txt; if those move, these
|
|
# have to move with them, and the bench will be measuring a different
|
|
# pedal until they do.
|
|
#
|
|
# The include paths want a word. Three of the four are the top-level
|
|
# directories the bench compiles against - the repository root so that
|
|
# "Audio/x.h" and the generated map's "Effects/y.h" resolve from
|
|
# wherever they are included, ../Firmware for the pedal-side headers the
|
|
# audio core reaches for (status.h, effect-state.h), and ../Audio
|
|
# because the files in there include each other by bare name. The
|
|
# fourth, bench/shim, has to come first: it is how a workstation gets
|
|
# past the things that only exist on an M33.
|
|
#
|
|
BENCH_GEN = bench/gen
|
|
#
|
|
# The math tables belong to Audio/util.h and are wanted whatever is in
|
|
# Effects/. rat_clamp.h belongs to one effect and is generated only when
|
|
# that effect is here to include it, so a tree without [RAT] builds the
|
|
# bench without reaching for its generator.
|
|
#
|
|
BENCH_CLAMP = $(if $(wildcard ../Effects/rat.h),$(BENCH_GEN)/rat_clamp.h)
|
|
BENCH_HDRS = $(BENCH_GEN)/effect_map.h $(BENCH_CLAMP) \
|
|
$(BENCH_GEN)/pow2.h \
|
|
$(BENCH_GEN)/log2.h $(BENCH_GEN)/quarter_sine.h
|
|
BENCH_CFLAGS = -Wall -Wno-unused-function -O2 \
|
|
-ffast-math -fsingle-precision-constant -Wfloat-conversion \
|
|
-DSAMPLES_PER_SEC=48000.0f \
|
|
-Ibench/shim -I$(BENCH_GEN) -I.. -I../Firmware -I../Audio
|
|
|
|
$(BENCH_GEN)/effect_map.h: $(wildcard ../Effects/*.h) \
|
|
../scripts/gen_effects.py
|
|
@mkdir -p $(BENCH_GEN)
|
|
python3 ../scripts/gen_effects.py ../Effects/ \
|
|
$(BENCH_GEN)/effect_map.h $(BENCH_GEN)/effects.js \
|
|
$(BENCH_GEN)/dummy_map.md
|
|
|
|
#
|
|
# [RAT]'s clipping node, solved at every point of a table.
|
|
#
|
|
# The diode constants come out of the effect rather than out of the
|
|
# netlist, which is the opposite of the rule above and is deliberate:
|
|
# they were fitted to the pedal and the netlist's are not the same
|
|
# numbers. rat_clamp.py says why at length. rat.h is therefore a build
|
|
# input and is listed as one.
|
|
#
|
|
$(BENCH_GEN)/rat_clamp.h: ../scripts/rat_clamp.py ../Effects/rat.h
|
|
@mkdir -p $(BENCH_GEN)
|
|
python3 $< ../Effects/rat.h -o $@
|
|
|
|
$(BENCH_GEN)/pow2.h: ../scripts/pow2.py
|
|
@mkdir -p $(BENCH_GEN)
|
|
python3 $< $@ 8
|
|
|
|
$(BENCH_GEN)/log2.h: ../scripts/log2.py
|
|
@mkdir -p $(BENCH_GEN)
|
|
python3 $< $@ 8
|
|
|
|
$(BENCH_GEN)/quarter_sine.h: ../scripts/quarter_sine.py
|
|
@mkdir -p $(BENCH_GEN)
|
|
python3 $< $@ 8
|
|
|
|
bench/bench: bench/bench.c $(BENCH_HDRS) \
|
|
$(wildcard bench/shim/*/*.h bench/shim/*/*/*.h) \
|
|
$(wildcard ../Audio/*.h) ../Firmware/effect-state.h \
|
|
../Firmware/status.h
|
|
$(CC) $(BENCH_CFLAGS) -o $@ $< -lm
|
|
|
|
#
|
|
# The same headers, without the audio path: just the biquad
|
|
# constructors, so a filter can be asked where it landed.
|
|
#
|
|
#
|
|
# All three math tables, not just the sine: Audio/util.h includes pow2.h
|
|
# and log2.h too, and coeff.c reaches util.h through biquad.h. Only the
|
|
# sine was listed, which worked for as long as nobody built this target
|
|
# with an empty gen/ - 'make bench' builds bench/bench first and that
|
|
# generates all three, so the gap stayed hidden.
|
|
#
|
|
bench/coeff: bench/coeff.c $(BENCH_GEN)/quarter_sine.h \
|
|
$(BENCH_GEN)/pow2.h $(BENCH_GEN)/log2.h \
|
|
$(wildcard bench/shim/*/*.h) $(wildcard ../Audio/*.h)
|
|
$(CC) $(BENCH_CFLAGS) -o $@ $< -lm
|
|
|
|
bench: bench/bench bench/coeff
|
|
|
|
#
|
|
# What the bench is for is looking at one effect, which is a
|
|
# conversation and not a target. What belongs in 'check' is only the
|
|
# part that has a right answer: the two controls that say whether the
|
|
# instrument is working at all. A bench that passes these and an effect
|
|
# that then reads strangely is a finding; a bench that fails these makes
|
|
# every other number in the session worthless.
|
|
#
|
|
check-effects: bench/bench
|
|
./test-effects.py
|
|
|
|
#
|
|
# Where every biquad in the pedal actually lands, swept across the range
|
|
# the pots offer. Host arithmetic only, so it needs no hardware and is
|
|
# part of 'check'.
|
|
#
|
|
check-biquad: bench/coeff
|
|
./test-biquad.py
|
|
|
|
#
|
|
# Do the pages in Documentation/effects still describe the effects, and
|
|
# do their charts still draw?
|
|
#
|
|
# Not slow - three seconds for a page, because bench.py parallelises the
|
|
# sweeps and mmdc takes the whole markdown file in one browser start.
|
|
# It is out of 'check' for the other reason: drawing the charts wants
|
|
# node and a headless browser, fetched through npx the first time, and
|
|
# 'check' should stay runnable with nothing but python and a compiler.
|
|
# It says so rather than skipping quietly when it cannot draw them.
|
|
#
|
|
# This is the target to run after changing an effect, which is exactly
|
|
# when a page goes stale - the last analysis was wrong from the moment
|
|
# single_pole_freq() was fixed under it, and nothing said so for a day.
|
|
#
|
|
check-analysis: bench/bench
|
|
./check-analysis.py
|
|
|
|
#
|
|
# There is deliberately no check- target for compare-spice.py.
|
|
#
|
|
# It is the level under all of this - ngspice on a netlist, the only
|
|
# thing here that is not our own arithmetic checking our own arithmetic -
|
|
# but it reports rather than passes: the answer is six numbers between
|
|
# 0.9 and 1.5 dB, and what those mean is a judgement about how much a
|
|
# simplification gave up. A check- target that cannot fail is worse than
|
|
# no target, because it gets read as a green light. Run it by hand:
|
|
#
|
|
# ./compare-spice.py rat
|
|
#
|
|
# and it needs the ngspice front-end on the path, which is the one
|
|
# dependency in this directory that is not python or a compiler.
|
|
#
|
|
#
|
|
# The other half of the bench: whether it agrees with a real pedal.
|
|
#
|
|
# Separate from check-effects because it needs hardware, and separate
|
|
# from check-hw because it needs no signal generator - the stimulus is
|
|
# [TESTTONE] and the capture is the pedal's own USB audio, so the path
|
|
# is digital end to end. Skips when there is no pedal plugged in.
|
|
#
|
|
check-bench: bench/bench
|
|
./test-bench.py $(if $(TARGET),--target $(TARGET))
|
|
|
|
#
|
|
# The analog half, which needs a patch cable from the pedal's output
|
|
# back to its own input and nothing else. One board, one cable, so the
|
|
# DAC and the ADC are the same codec on the same clock and there is no
|
|
# drift to chase.
|
|
#
|
|
# Reports and never fails. What it measures is a converter and a cable
|
|
# rather than any code of ours, and 95 is the issue about a bench
|
|
# measurement that carries no record of the bench - so this prints
|
|
# numbers and lets a person decide whether the cable moved.
|
|
#
|
|
check-analog:
|
|
./test-analog.py $(if $(TARGET),--target $(TARGET))
|
|
|
|
#
|
|
# The web app gets checked too, which needs node.
|
|
#
|
|
# Skipped rather than fatal when node isn't installed: it is the only
|
|
# thing here that needs anything beyond a C compiler and python, and
|
|
# 'make check' should still be worth running without it. It says so when
|
|
# it skips, so it can't quietly stop testing anything.
|
|
#
|
|
NODE := $(shell command -v node 2>/dev/null)
|
|
|
|
#
|
|
# The hardware half. Wants a pedal on the USB and a signal generator on
|
|
# its input, so it is a separate target rather than part of 'check' -
|
|
# and it skips rather than fails when there is nothing plugged in, the
|
|
# same bargain the node check makes below.
|
|
#
|
|
# The generator cannot be set from here. --ptp and --freq say what it
|
|
# is set to, and every number that depends on it is reported against
|
|
# what was declared.
|
|
#
|
|
check-hw:
|
|
./test-audio.py --ptp 0.100 --freq 440 $(if $(TARGET),--target $(TARGET))
|
|
|
|
#
|
|
# Separate from check-hw because it reflashes: planting a scene with
|
|
# channel routing in it means a trip through BOOTSEL, since nothing sets
|
|
# that over MIDI yet. It leaves two scenes behind in the save area.
|
|
#
|
|
check-split:
|
|
./test-split.py
|
|
|
|
#
|
|
# Two pedals, patched output to input in both directions, one generating
|
|
# and the other measured. Separate from check-hw because it needs the
|
|
# second board, and because what it is for is different: check-hw asks
|
|
# whether the pedal does the right thing to a signal somebody else
|
|
# provides, and this asks the questions that need the signal to be under
|
|
# the test's control - what the analog link does, how quiet a properly
|
|
# driven input is, and whether the audio survives the pedal's own sysex.
|
|
#
|
|
check-loop:
|
|
./test-loop.py
|
|
|
|
#
|
|
# The hardware MIDI jacks, through a USB-MIDI adapter on the other end.
|
|
#
|
|
# Reports and never fails. The adapter is a cheap CH345 and the path has
|
|
# shown intermittent trouble nobody has pinned down, so wiring it into a
|
|
# gate would only teach us to ignore the gate. What is wanted is the
|
|
# rate: run it, watch the number, and let the data say whether the
|
|
# flakiness is the dongle, the parser, or nothing.
|
|
#
|
|
check-midi:
|
|
./test-midi.py
|
|
|
|
#
|
|
# Cold power cycles on one board, tallied. Needs a hand on the USB plug
|
|
# - there is no switched hub here - so it is as far from 'check' as a
|
|
# target gets, and it takes a --target because cycling the wrong board
|
|
# yields a number indistinguishable from a real one.
|
|
#
|
|
# make check-boot TARGET=7989 JSON=boot-A.json
|
|
#
|
|
TARGET ?=
|
|
JSON ?=
|
|
check-boot:
|
|
./test-boot.py $(if $(TARGET),--target $(TARGET)) \
|
|
$(if $(JSON),--json $(JSON)) $(BOOTARGS)
|
|
|
|
#
|
|
# The same hang, chased unattended through BOOTSEL instead of through the
|
|
# plug. Cheap enough to leave running, and blind to whatever needs a
|
|
# genuinely cold crystal - a clean run here is not a fixed pedal. See
|
|
# the header of test-reboot.py before believing a result either way.
|
|
#
|
|
check-reboot:
|
|
./test-reboot.py $(if $(TARGET),--target $(TARGET)) \
|
|
$(if $(JSON),--json $(JSON)) $(BOOTARGS)
|
|
|
|
check: test-midi-cin bench/coeff bench/gen/effect_map.h
|
|
./test-midi-cin
|
|
./test-biquad.py
|
|
./check-effect-ids.py
|
|
ifeq ($(NODE),)
|
|
@echo "test-webmidi: SKIPPED - no node in PATH"
|
|
else
|
|
@#
|
|
@# effects.js is generated, so generate it - into a temp directory,
|
|
@# because a test has no business writing into the source tree, and
|
|
@# this way it doesn't matter whether the firmware has been built.
|
|
@#
|
|
@t=`mktemp -d` && \
|
|
python3 ../scripts/gen_effects.py ../Effects/ \
|
|
$$t/dummy_map.h $$t/effects.js $$t/dummy_map.md && \
|
|
$(NODE) test-webmidi.js $$t/effects.js; \
|
|
r=$$?; rm -rf $$t; exit $$r
|
|
endif
|
|
|
|
.PHONY: bench check check-analog check-analysis check-bench check-biquad
|
|
.PHONY: check-boot check-effects check-hw check-loop
|
|
.PHONY: check-midi check-reboot check-split
|