Known issues
Defects the team has measured, understood, and deliberately left open, with the test that documents each one and what closes it. This is not a bug tracker for the unmeasured; an entry here has a figure, a mechanism, and a named exit.
Alias floor at 8× after volt-true grid staging
The JCM800 measures −23.4 dB through measure::alias_floor() at preamp 1.0 and a −12 dBFS input,
against the −33.8 dB bound (−43.4 dB before DA4, −6.7 dB before DA5 part 1, −15.1 dB before
part 2, −13.4 dB before part 4), and −24.9 dB at its default (−16.3 dB before part 4); the 5150
at its default and the same input measures −26.8 dB against the spec’s −30 dB (−11.4 dB before
part 4), the IIC+ −14.8 dB (−0.1 dB) and the VH4 −23.0 dB (−13.3 dB). DA5 part 4 antialiased
every TriodeStage and PentodeStage with a first-order antiderivative of the composite
grid-and-plate transfer (AntiderivativeAntialiaser, spec section 15): one stage alone now folds
at −102.4 dB at 3× past its window and −79.8 dB at 12× (−81.2 and −53.7 dB before), and the
EL34 at −101.2 and −84.4 dB (−88.1 and −55.1 dB). What remains in the amplifiers is not the
transformer’s tanh, the sag envelope or the solver’s iteration count, each measured and ruled out: at every default the worst
line is the 20 kHz tone’s 19th harmonic folding about 384 kHz to 3995 Hz, made by the stages
after the first, which receive an already-clipped waveform whose content reaches the oversampled
Nyquist and so breaks the linear-between-samples assumption first-order ADAA rests on. Documented
by Regression_Dsp.Jcm800AliasFloorImprovedAtLeast30dBOverD5b and
Regression_Dsp.Peavey5150AliasFloorAtItsDefaultIsUnderMinusThirtyDb
(tests/functional/unit/test_measure.cpp), both skipped with the measured figure printed. 16× was
measured without ADAA in part 3 (JCM800 default −35.0 dB, 5150 −19.9, IIC+ −14.3, VH4 −27.3 at
1.9 to 2.0× the CPU); 16× on top of the antialiaser, measured in part 4, reads JCM800 default
−45.3 dB, preamp 1.0 −32.7 dB, 5150 −36.2 dB, IIC+ −36.9 dB and VH4 −35.3 dB at 2.0× the CPU,
which puts the 5150 and the IIC+ at 167 % of the block at -O0 and 99 to 102 % at -O2. What
closes the rest is the maintainer’s: that 16×, a band limit between the stages that lets 8× stay,
or a higher-order antiderivative.
The IIC+ and the 5150 leave little headroom at the smallest device buffer
sushidsp_bench measures the IIC+ at 7236 ns per output sample and the 5150 at 6286, against
the JCM800’s 2587 and the VH4’s 3875, so a 256-frame block costs the IIC+ 1.852 ms against the
1.5 ms budget that block is allowed at 48 kHz. The cost is the valve count solved at 8x: the
IIC+ runs six single triodes, a long-tailed pair and four power tubes, and a single TriodeStage
at the base rate measures 69.9 ns. Nothing else in a guitarist’s chain is close - the Tube
Screamer costs 594.9 ns and a 4096-tap cabinet convolution 15.6 ns. Per-sample cost does not move
with the block size (7356 ns at 16 frames, 7243 at 256), so a small buffer adds no penalty of its
own; what a small buffer removes is the slack that absorbs a slow block, and the same node
measures 34.8 % of one core at its best and 57.6 % at its worst. A Tube Screamer into the IIC+
into a cabinet therefore runs near 38 % of one core steady and past 60 % on a slow block, which
underruns at the smallest USB buffer while leaving a larger one alone. What closes it is a
decision the maintainer owns: dropping this model to 4x, which halves the cost against the alias
floor the entry above measures, or a cheaper triode solve. Measured by sushidsp_bench
(tests/benchmark/main.cpp), which times each amplifier node.
Removing a track can double one block of audio
SessionTimeline::remove_track republishes lanes index through n-1 one at a time, each
through its own RtSwapSlot, so the removal is not atomic across lanes. A callback that lands
between two of those publishes renders one block in which lane 3 already holds lane 4’s clips
while lane 4 still holds its own, and that block carries the same clip twice. Nothing on the
audio thread allocates, locks or throws, and no memory is raced; what the seam costs is one
block, 5.33 ms at 48 kHz and a 256-frame buffer. What closes it is a generation counter the
callback reads once per block, or refusing the removal while the transport runs; both are the
maintainer’s call, and neither is worth doing before the removal is something people use.
Mesa Mark IIC+ has no clean setting
The dossier’s §2 places an EFFECTS SEND / RETURN network between V2A’s plate and V2B’s grid,
and its §3.7 gives no value for that link, so the model has no loop network at all — neither the
series path nor the shunt one, of which the missing return capacitor’s 7.23 Hz corner is only a
part: V2A’s plate drives
V2B’s grid window about twenty-one times past it, and the node limits at every input level with
no clean end on the knob. Three tests are skipped, not weakened: MesaMark2cHasACleanEndAtMinimumGain,
MesaMark2cToneStackSitsBeforeTheLeadStages and MesaMark2cEqBandBoostsItsCentre
(tests/functional/unit/test_mesa_mark2c.cpp), each printing what it measures today against
what it will re-assert. Closes when the send/return network is read off the schematic — dossier
open questions 19 and 20.
Diezel VH4’s phase inverter and power section are a JCM800 stand-in
The dossier establishes nothing about the VH4’s phase inverter or power section (§5: “nothing established”), so the module keeps the JCM800’s long-tailed-pair degeneration and power-tube grid figures in their place rather than a VH4-specific derivation. Every number downstream of that link is borrowed, not measured from the VH4 itself. Closes when a VH4 power-amp schematic becomes available.
OGG export is deferred, so a take leaves the host as WAV only
DS5 of docs/archive/superpowers/specs/2026-09-07-stereo-tracks-design.md puts libvorbis and libogg
inside apps/host_gui and nowhere else, and DS0 through DS4 shipped without it. A five-minute
stereo take at 48 kHz is 115 MB of 32-bit float on disk, which is what the host writes and what a
user copies out. No test is skipped for this; nothing is broken, the phase is unwritten. Closes
with DS5.
DS0, DS2 and DS3 are committed under each other’s names
Three sessions worked this checkout at once and each committed by explicit path, so 394a605 (“DS0:
RigGraph routes as many channels as its nodes declare”) also carries Task 5’s Transport,
TrackPlayer and test_track_player.cpp, and 18eb040 (“DS2: Take, TrackRecorder and
RecordingBus”) carries the rest of that task. The content of every file is the one its own task
wrote and reviewed; only the commit each landed in is wrong. The task’s
review ruled against rewriting history in a shared checkout, so git log stays as it is and this entry is the record. Closes as recorded here,
not by a repair.
write_rig_output_to_device’s 2 into 1 downmix has no live caller
apps/host_gui/main.cpp prepares the rig at min(device output channels, RigGraph::kMaxChannels),
so the rig is never wider than the device and the sum-and-halve branch of
write_rig_output_to_device cannot run from the host. The branch exists because
ChannelSelection carries the whole RigGraph::channels_adapt table rather than the part one
caller happens to reach, and
WriteRigOutputSumsAndHalvesTwoRigChannelsIntoAMonoDevice
(tests/functional/unit/test_channel_selection.cpp) is the only thing that exercises it. It stops
being dead the first time a rig’s width is set by something other than the device — a fixed stereo
rig, or a preset that pins the width.
The ASIO host still renders the rig in mono
apps/host_asio/main.cpp opens two ASIO output channels but prepares its rig with
PrepareInfo{..., 1}, so a PanNode at the end of a rig run under that host is summed back to one
channel by the graph’s own output stage. It shares the GUI host’s rig_output_adapts_to_device and
write_rig_output_to_device pair, so the mapping is the same rule; only the rig’s own width differs.
The chain it builds is a fixed mono TubeScreamer into a JCM800 and nothing in it pans, so the limit
costs nothing today. Closes by preparing the rig at the device’s width, as the GUI host does.
Transport::seek racing advance can be lost
seek() and advance() both store to position_, so a seek issued while the audio thread is
inside a block can be overwritten by that block’s store, leaving the transport a block-length away
from where the user put it. The host serialises the two, and Transport::seek’s @pre states the
rule rather than paying for a generation counter no caller needs today. Raised in the stereo-tracks
review. Closes when a caller appears
that cannot serialise them.
A stereo branch collapses back to mono at a MixerNode
routing::MixerNode declares PortLayout{8, 1}, so RigGraph::channels_of reads its input width
as 1 and the graph’s gather averages any two-channel producer down to one channel before the mixer
sees it. A PanNode inside a parallel branch therefore loses its width the moment the branch
rejoins. This is what spec §2’s adaptation table says should happen — 2 into 1 sums and halves —
and no test or user asks for anything else, so the behaviour stays. Closes when a mixer that keeps
its inputs’ width exists, which needs a PortLayout that can say “eight slots, two channels each”.
A take’s own sample rate is never read
Take carries the rate it was captured at, and neither TrackPlayer nor TrackTimeline asks
for it: a clip is read frame for frame against the rig’s rate. A 44.1 kHz WAV dropped on a
48 kHz rig therefore plays 8.8 % slow and a semitone’s fifth flat. Imported mp3 files are not
affected — AudioImportCache converts a decoded song to the rig’s rate before the clip ever
points at it, which is why the defect survived the mp3 work. Closes when a clip carries the
ratio between its take’s rate and the rig’s into VariRateReader, which already reads at an
arbitrary speed.
The imported-audio cache is never pruned
Every mp3 dropped on a lane leaves a float32 WAV under the executable’s cache/imported_audio/
and nothing ever deletes one; a 3.4-minute song costs about 79 MB. The cache is what makes a
second drop of the same file cost 0 ms against the first drop’s 3128 ms, and what lets a saved
session find its clip again, so it is kept rather than made optional. Closes when the cache
carries a size cap and evicts by last use.
The built room’s loss fits neither measured room below 500 Hz
RoomScene::built_room_loss adds one absorption figure per band to every surface
(BuiltRoomLoss.hpp). From 500 Hz to 2 kHz it brings the model to 0.96 to 1.04 times the
measured decay of the Motus room and 1.14 to 1.21 times that of the BRAS CR2 seminar room
(test_room_motus.cpp, test_room_bras.cpp). Below 500 Hz the two rooms want different figures:
at 125 Hz the model reads 1.917 s against Motus’s measured 2.572 s, a ratio of 0.745, and
2.991 s against CR2’s 1.402 s, a ratio of 2.133; at 250 Hz 0.913 and 1.476. The 0.020 and 0.025
chosen for those bands sit between the two rooms. A fresh room’s low end is therefore uncertain
by about a factor of two either way. Closes when a third measured room, or a low-frequency term
tied to windows and other lightweight elements, settles the low bands
(docs/design/ROOM_FURNISHINGS.md section 11).
Four furnishing and covering rows carry no measurement of their own
docs/agent/2026_09_22_ROOM_FURNISHINGS/REPORT.md’s “What was not found” records that no ISO 354
or JIS A 1409 test of a household desk, a double bed with its bedding, a filled bookcase or a
flat-woven kilim on a hard floor could be sourced. Furniture.cpp’s desk row is therefore the
Korean apartment study’s 0.0131 m2 per m3 taken at 30 m3, 0.4 m2 flat across the bands, and its
bookcase row is EN 12354-6 formula 4 on an assumed 0.8 x 0.3 x 1.8 m volume, 0.57 m2 flat; the
double bed is the one measured single bed without bedding scaled by the mattress perimeter ratio
1.22; and a rug enters a scene as ThinCarpetOnConcrete, a fitted woven carpet row standing in
for a kilim and probably an upper bound. Each row’s citation names its kind, so nothing is
presented as measured that is not, and the room-level cross-check in the same report puts the sum
inside the 0.16 to 0.35 m2 per m3 the furnished-room studies measure. Closes when a per-object
measurement of each is published or made.
The measured HRTF’s decoder misses the near ear by up to 5.1 dB above 5 kHz
assets/hrtf/sadie2_ku100_48k.hrtf is the SADIE II KU100 set, thinned to 1531 directions at
3 degrees and truncated to 128 taps with a worst loss of -31 dB. Its third-order
magnitude-least-squares decoder matches the measured set within 0.56 dB at the median over every
direction, ear and third octave from 200 Hz to 8 kHz, 2.61 dB at the 95th percentile and 17.75 dB
at the worst, a far-ear notch near 5 kHz. On the 24 directions of
RoomNodeDecoderMagnitudeMatchesTheMeasuredSet the near ear misses by up to 5.1 dB at 5 to 8 kHz,
so that test holds the near ear to 3 dB below 5 kHz, 6 dB above, and the far ear to 10 dB. The
KU100’s notch straight behind is 2.7 dB deep in the raw measurement, not the 6 dB the spec
assumed, and RoomNodeCarriesTheRearNotchWithAMeasuredSet now checks the rendered notch against
the set’s own within 1 dB. Closes with a higher decoder order or a decoder that trades the far
ear for the near one (assets/hrtf/README.md).
Four of the spec’s fourteen material rows are not transcribed
Materials.hpp ships fifteen rows, nine of them added on 2026-09-22, and §4.2’s brick, acoustic
foam, steel garage door and carpet on underlay are not among them. A user whose garage has a
steel door or a foam panel picks the nearest row instead, and the room’s decay is wrong by the
difference between the two, which across §4.2’s table reaches 0.4 in absorption at 500 Hz.
Nothing is skipped; the code takes any row the table holds. Closes by transcribing the other four
from the same source with a citation each, which is data and no code.
The SM57 preset has no measured curve
MicrophoneModel evaluates any on-axis curve it is handed, and the module ships one preset,
kMicrophoneFlatCardioid, whose six band values are zero and whose citation reads “no maker’s
curve”. Spec §8 asks for a Shure SM57: a cardioid with a presence rise near 6 kHz and a low-end
roll-off, which the flat preset does not have, so a microphone render is missing the colour the
capsule itself contributes. The six numbers were not invented, which is why the preset says so in
its own citation. Closes when the SM57’s published frequency response is read off Shure’s sheet
into a second MicrophonePreset.
Modal damping at a soft boundary is about 30 % wrong
ModalSolver::damping_constant damps each mode by the first-order perturbation of spec §9a.2,
which is linear in the absorption coefficient and so right to a few percent while α stays under
about 0.1. Gypsum board on studs absorbs 0.29 at 125 Hz (Materials.cpp), and a mode bounded by
it decays about 30 % away from the true rate. The mode’s frequency is right either way, so the
room’s pitch is right and its low-end decay time is not. Solving the damped eigenproblem against
a complex wall impedance costs what §9a.2 measures FDTD at, for an error inside a layer that did
not exist before 2026-09-08. Closes when someone judges that cost worth paying.
The FDN’s own modes below the Schroeder frequency are crossed out, not corrected
A 16-line feedback delay network has its own modal structure, and below f_s that structure is not
the room’s. CrossoverPair low-passes the modal bank and high-passes the tail at f_s in both
quality modes, so the FDN carries no low end and its line lengths are designed for the band above
f_s alone. The room’s low end therefore comes entirely from ModalSolver’s eigenmodes, and the
tail contributes nothing under f_s that a longer reverberation would fill in. This is the design’s
choice rather than a defect to repair, recorded because a reader measuring the tail alone below
f_s will find it empty and should not go looking for the bug.
A compensated ears set’s tail lands after its direct sound
RoomNode::compensate_direct_delay moves an ears set earlier by the propagation delay, and it
moves the tail’s read delay with everything else. The network’s shortest line is the handover
time, so that delay is already zero for all three reference rooms and the subtraction floors
there: the tail keeps its physical timing while the rest of the render moves up, 363 frames or
7.6 ms for the default scene. Below the 20 ms a listener resolves as a separate arrival, and
inaudible against the 70 ms hole the old timing left, so it is recorded rather than repaired.
Closes when the designer is told the compensation and shortens its lines to absorb it.
Nothing between the node and the device bounds the output
A 12 m unpainted-concrete cube used to be a scene the panel could reach: 1728 m3, an Eyring RT60
of 12.8 s at 500 Hz, 1068 modes under f_s, a slowest modal decay of 48 s, and +36 dB of steady
gain through the node at its 32 Hz mode after 20 s with the cabinet and the capsule in one
corner, still rising. Since 2026-09-10 RoomScene::is_valid refuses it: the mean absorption of
every band has to clear the floor a 12 s decay asks of the room’s volume over its area (spec
§4.1). The three reference rooms reach +9.7 dB, which is what a room booming at a mode does, and
the loudest a scene that now passes can get has not been measured.
What the gate does not do is police the audio. No stage between RoomNode and the device bounds
the output; AsioSampleConverter::clamp_unit clips at full scale for the integer formats and a
float format passes the value through. Whether a guard belongs there is the maintainer’s call.
The raw octave step at the seam is not bounded
RoomNodeIsContinuousAcrossTheSchroederFrequency measures each layer against the shared
DiffuseField reference and holds both within 2 dB, which is what the spec asks. The raw step
between the octave under f_s and the octave over it is recorded and not bounded: it reads
+0.16 dB for the garage, +1.29 dB for the bedroom and +3.75 dB for the block room. The step
cannot be zero, because the reference itself changes across the seam: the 4x12’s directivity
index rises 4.6 dB between 250 and 500 Hz, the block room’s absorption halves from 125 to 250 Hz,
and the poses a room is used at sit 2 to 4 dB under the volume average in the octave below f_s.
An earlier run recorded −0.3, +0.6 and +3.1 dB; those are the figures the Linkwitz-Riley pair
gave before CrossoverPair became power complementary, and each half then lost 2.4 dB at the
seam. Closes when a bound follows from the reference’s own step rather than from a measurement.
BandResponseFit stops at eight steps on about 60 % of fits
kMaxSteps in BandResponseFit.cpp bounds the refinement at eight accepted steps, and about
three fits in five reach it rather than converging, so those tap filters sit at whatever the
eighth step reached. The fit still holds the direct path within 0.1 dB and a one-band 20 dB cut
within 0.5 dB, so the cap is not what the module’s accuracy is limited by today; it is what a
tighter tolerance would run into first. The refinement differentiates its residual numerically,
so each step costs six extra evaluations. Closes when the Jacobian is written analytically, which
buys a higher cap at the cost the fit pays now.
A structural rig edit empties the room’s tail
RigController::publish() rebuilds the graph from the slot list, so every node in the rig is
constructed afresh whenever a node is added, removed, connected or disconnected. The new
RoomNode gets its parameters and its scene back through apply_room_to_live_node, and its
delay lines, its FDN and its modal resonators start at zero, so the room’s tail stops at the edit
and rebuilds over the next RT60. A drag inside the room panel goes through
publish_room_scene instead, which crossfades; only an edit to the graph’s shape rebuilds the
node. Closes when a rebuild
carries surviving nodes across instead of reconstructing them, which is a RigController change
and touches no module.
The HRTF converter counts every measured direction equally
tools/dsp/convert_hrtf.py’s diffuse_field_average sums the magnitude responses of the
directions a measurement file carries without weighting each by the solid angle it stands for,
while SphericalHeadHrtf weights its own grid by 4*pi*cos(e)*sin(delta/2)/36 per ring point.
On a latitude-longitude grid the two disagree: at ten degree spacing the rings near the poles
stand for about a fifth of the solid angle of a ring at the equator, so they carry up to 5.8
times the weight they should, and the tail through a converted set is coloured towards whatever
the poles measured. Nothing in the tree hits this today because assets/hrtf/sadie2_ku100_48k.hrtf
is not in the repository and the fallback set is what every rig loads. Closes when the converter
derives each direction’s solid angle from the grid it finds in the file.
A publish steps the tail’s output filter on its running state
LateReverbDesign fits the tail’s output cascade to what the early taps leave of the diffuse
field in each band, so a moved microphone or listener refits it, by 3 % in the second section’s
coefficients for a 0.36 m move in the garage, and RoomNode loads the new coefficients into the
one LateReverb at the block the fade begins, on the network’s stored energy. With the tail at
its diffuse-field level, 12 dB above where it sat before 2026-09-09, the residual against the
blend of the two steady renders reads -45.8 dBFS for that move where it read -52.1 dBFS, and the
ears’ 0.3 m listener move reads -72.3 dB where it read -88.3 dB; the step ratios are unchanged,
so nothing clicks (RoomNodeCrossfadesAPublishWithoutAStep,
RoomNodeRampsTheEarGainsAsTheOutputBlendWould). Closes when the tail’s output cascade is run
twice under the fade, the retiring coefficients and the arriving ones on the same line sum,
blended under the raised cosine, which is two more biquads per ear in LateReverb.
Open findings of the 2026-10-05 estate audit
docs/agent/2026_10_05_ESTATE_AUDIT/REPORT.md is the audit. This table lists its documentation,
layout and code shape findings that were still open on 2026-10-05, each re-checked against the
tree that day. The licence and command line findings were worked on in their own programmes and
were not re-checked for this list. The work that closes a row is in
docs/design/REMAINING_WORK.md.
Documentation
| Issue | Where |
|---|---|
The ## v0.1.0 — 2026-10-03 heading has no tag behind it; git tag -l prints nothing |
docs/reference/CHANGELOG.md, git |
The entry that folded the untagged headings cites docs/archive/changelog/, which does not exist |
docs/reference/CHANGELOG.md, 2026-10-03 |
Five docs entries of 2026-09-05 and 2026-09-07 name no place |
docs/reference/CHANGELOG.md |
Three entries cite files that no longer exist: cli/sushidsp/services/setup.py, apps/host_gui/ui/TrackPanel.cpp, include/SushiDSP/math/ResamplerCoefficients.hpp |
docs/reference/CHANGELOG.md |
| The archive’s README says the archive is empty; it holds 56 other files | docs/archive/README.md |
Archived plans, specs, dossiers and notes carry dated lower-case names, not UPPER_SNAKE_CASE.md |
docs/archive/superpowers/, docs/archive/research/, docs/archive/notes/ |
| The standards phase 1 work has two plans in the archive, a 288-line plan and a 10-line checklist | docs/archive/superpowers/plans/2026-09-15-sushi-standards-phase-1.md, docs/archive/agent/2026_09_15_SUSHI_STANDARDS_PHASE_1/PLAN.md |
| The report in the v0.1.0 release work folder records what jrb32’s traced schematic settles, not the release | docs/agent/2026_09_20_V0_1_0_RELEASE/REPORT.md |
§11a says SushiDSP is in hub’s catalogue and that cli/pyproject.toml does not list sushicore; the manifest says it is not in the catalogue and pyproject.toml requires sushicore>=0.7.0 |
docs/design/SUSHIDSP.md, sushi-module.toml, cli/pyproject.toml |
The opening block names constants UPPER_SNAKE; the constants in the tree are kPascalCase |
docs/design/SUSHIDSP.md line 4 |
| Em dashes used as punctuation throughout: 106 lines in the design spec, 45 in the GUI redesign, 35 in the CLI guide | docs/design/SUSHIDSP.md, docs/design/GUI_UX_REDESIGN.md, docs/guides/COMMAND_LINE_INTERFACE.md |
| The first entry of this page is one paragraph of nested figures | docs/reference/KNOWN_ISSUES.md, “Alias floor at 8×” |
| No README beside the code | apps/host/, apps/host_asio/, tests/measure/, tests/benchmark/, each area under include/SushiDSP/ |
Two READMEs cite assets/ir/default_4x12.wav, which the tree does not hold |
assets/ir/README.md, apps/host_gui/README.md |
| The admission rule for a bundled impulse response still names the Apache License 2.0 | assets/ir/README.md |
Two comments and one issue template cite module paths from before the tiers: modules/rig_preset, modules/dd3, modules/mesa_mark2c |
modules/session/rig_preset/include/SushiDSP/rig_preset/RigPreset.hpp line 22, tests/functional/unit/test_biquad.cpp line 290, .github/ISSUE_TEMPLATE/model_accuracy.yml line 17 |
| The front door’s count of 1007 tests could not be reproduced without a build and was removed on 2026-10-05; no document states the count | README.md |
Layout
| Issue | Where |
|---|---|
CI calls cmake and ctest directly, runs no checker and has no job for the CLI’s tests; its comments cite docs/CLAUDE.md and sushicore 0.6.0 |
.github/workflows/ci.yml |
| One workflow file for every trigger; no release workflow | .github/workflows/ |
Every module keeps its private code in src/, 62 tracked files, so rule_private_reach, which matches source/, cannot fire |
modules/*/*/src/, tools/layering/check_layering.py |
The core sits at the root beside modules/ and outside the layering check; core/ICabinetHost.hpp includes io/Ir.hpp |
include/SushiDSP/, src/ |
Top-level names differ from the shared tree: src/, apps/, assets/; Doxyfile sits at the root |
repository root |
.clang-format and .editorconfig are missing |
repository root |
Tests are not grouped as unit, integration, regression and common; no module has its own tests/; 24 apps/host_gui sources are compiled into the test binary |
tests/, tests/CMakeLists.txt |
| 317 of 453 C++ files are named in PascalCase | include/, src/, modules/, apps/, tests/ |
*.f32 is not marked binary; 3.1 MB of transient CSVs sit inside the manual |
.gitattributes, docs/reference/spice/*/*_tran.csv |
Eight remote-tracking refs under refs/remotes/local/ have no remote configured |
git |
| The root build file defines targets and fetches dependencies | CMakeLists.txt |
| No generated version header; the version is kept by hand in two files and absent from the manifest | CMakeLists.txt, cli/pyproject.toml, sushi-module.toml |
The ignore file ignores docs/api-site/html/, where nothing is written, names sd doxygen and carries paragraphs of comment |
.gitignore |
The host writes imgui.ini into the working directory |
apps/host_gui/ |
Code shape
| Issue | Where |
|---|---|
check_source_comments.py reports 1106 findings: 270 file headers, 69 blocks over eight lines, 761 comment runs, 6 history words; sd check does not run it |
python tools/documentation/check_source_comments.py . --report |
Exceptions, bool with an error string and bare bool coexist as error models; no function is [[nodiscard]] |
src/io/WavFile.cpp, apps/host_gui/RigController.hpp, modules/nodes/room/include/SushiDSP/room/RoomNode.hpp |
RigController forwards SlotGraph’s surface one for one; main() owns setup and the frame loop |
apps/host_gui/RigController.hpp, apps/host_gui/SlotGraph.hpp, apps/host_gui/main.cpp |
| The host names products by id, so adding a product edits the GUI | apps/host_gui/ui/PanelLibrary.cpp, apps/host_gui/ui/GearFilter.cpp, apps/host_gui/TopologyApplier.cpp |
nlohmann::json is part of the preset module’s public interface |
modules/session/rig_preset/include/SushiDSP/rig_preset/RigPreset.hpp |
| The four amplifier modules repeat the same helpers and constants | modules/nodes/jcm800/src/Jcm800.cpp, Peavey5150.cpp, DiezelVh4.cpp, MesaMark2c.cpp |
Wdf is built into the library and no production code uses it |
include/SushiDSP/math/Wdf.hpp |
One header lacks #pragma once |
tests/common/TimingGate.hpp |
The device backends, the GUI views and the scripts under tools/dsp/ and tools/spice/ have no tests |
src/device/, apps/host_gui/ui/, tools/ |

