Plan §5 names an inventory of the load-bearing memllib surface as the prerequisite for the vendor-vs-fork-pin decision. Built it from the firmware LINKER MAP rather than grep — the same method that would have caught daisysp (S8) mechanically instead of by reading. Result: all 24 compiled translation units contribute kept sections to the image; no discarded-only TU exists. memllib is not a daisysp situation. "The load-bearing subset" is ~1.8 MB across 84 files — essentially all of memllib minus examples/. There is no small subset to lift; synth/ alone is 1.3 MB and all three of its TUs link. The wrinkle, which was not visible when §7.5 was decided: src/memllib is not a third-party dependency, it is the LAB's shared library (MusicallyEmbodiedML/memllib), and our fork is 3 commits ahead but 31 commits BEHIND — +2034/-153 across exactly the files we link, including "new staticmlp", "jolts", "noise", "verb". Vendoring as-is freezes that gap permanently. Recommendation recorded: rebase the three small NISPS commits onto upstream main FIRST, then vendor. Same self-contained end state, but it does not silently discard work that sounds directly relevant, and it is the only moment that merge is cheap — after vendoring there is no upstream history to merge against. Flagged for an operator call before the PlatformIO cut, which this gates.
4.6 KiB
| kind | date |
|---|---|
| finding | 2026-07-21 |
memllib usage inventory — the prerequisite for the §7.5 ownership decision
Prerequisite named by ../plans/simplification-plan.md §5: "inventory which memllib surface
(AudioDriver, MEMLNaut board, display/menu, MIDI) is load-bearing" before choosing between a
fork-pin and vendoring. The operator's §7.5 decision is vendor the parts we use ("everything
should be self contained in this repo"). This document is the evidence that decision needs, plus one
consequence that was not visible when it was made.
Method
Not grep. The firmware was built for real (arduino-cli, rp2040:rp2040:solderparty_rp2350_stamp_xl,
SLPWorkshop variant) and the linker map was read: MEMLNaut-NISPS.ino.map distinguishes
translation units that merely compiled from those contributing sections to the final image. This is
the same method that would have caught the daisysp finding (S8) mechanically instead of by reading.
Result: the compile surface is 24 TUs, and all 24 are linked
| subdir | .cpp linked / present | headers | size |
|---|---|---|---|
audio/ |
4 / 4 | 12 | 132K |
hardware/ |
7 / 7 | 21 | 176K |
interface/ |
8 / 8 | 8 | 108K |
synth/ |
3 / 3 | 10 | 1.3M |
utils/ |
2 / 2 | 9 | 80K |
| total | 24 / 24 | 60 | ~1.8M |
Every translation unit reachable through the sketch-tree symlinks contributes kept sections to the image — no discarded-only TU exists. memllib is therefore not a daisysp situation (41 .cpp compiled, zero consumers). It is genuinely load-bearing.
The library has 30 .cpp in total; the 6 not counted above live outside the five symlinked subdirs
(e.g. examples/) and are never compiled by the firmware build. Those are the only part safe to
drop outright.
Firmware's direct include surface is just six entry points — audio/AudioDriver.hpp,
hardware/memlnaut/MEMLNaut.hpp, hardware/memlnaut/Pins.hpp,
hardware/memlnaut/display/{View,SingleSelectView}.hpp, interface/MIDIInOut.hpp, PicoDefs.hpp,
utils/perf.hpp — but the transitive closure is the whole of those five subdirs.
Consequence for vendoring: "the load-bearing subset" is ~1.8 MB / 84 files, i.e. essentially all
of memllib minus examples/. There is no small subset to lift. synth/ alone is 1.3 MB (mostly
maximilian.cpp) and all three of its TUs link.
The consequence that was not visible when §7.5 was decided
src/memllib is not a third-party dependency. It is the lab's shared library
(MusicallyEmbodiedML/memllib), and our fork is:
- 3 commits ahead —
bf9691c(swap memlp includes for nisps/core, inline RL utilities),32cc831(newlib RNG fix),b37fc53(seed-helper rename). These are the NISPS-specific changes. - 31 commits behind — including
770a990 new staticmlp,9fcd459 jolts,d0d8a72 noise,616b8e7 cc select,a8bf255 verb,671abbe screen ordering,e291192 l r input swap. Diffed against the five subdirs we link: 30 files changed, +2034 / −153.
Vendoring freezes that gap permanently and converts "we are behind the lab" into "we have our own
divergent copy". That may be exactly what is wanted — self-containment is a real goal, and the lab's
main is not obviously a branch we track — but it is a fork of a colleague-shared library, not a
snapshot of a vendor drop, and the 31 commits contain work (staticmlp, jolts) that sounds
directly relevant to this project.
Options, with what each costs
- Vendor as decided. Copy the five subdirs into the repo, drop the submodule and the symlink
forest, delete
examples/. Repo grows ~1.8 MB. Self-contained, no submodule init, PlatformIO gets a plainlib/— this materially simplifies the §5 migration. Cost: the 31 upstream commits become a manual merge, forever. - Vendor, but first rebase our 3 commits onto upstream
main. Same end state, except the snapshot is current rather than 31 commits stale. Costs one merge now (the three commits are small and mechanical), and it is the only moment when that merge is cheap. - Fork-pin (status quo + Phase 0's fix). Already works: the pin is reachable, CI is green, fresh clones build. Not self-contained, and keeps the submodule friction PlatformIO would rather not have.
Recommendation: option 2. The operator's goal (self-contained) is satisfied identically by 1 and
2, but 2 does not silently discard staticmlp/jolts/verb. Doing the rebase after vendoring
means doing it against a copy that no longer has upstream history — i.e. never.
This decision gates the PlatformIO migration (plan §5), which otherwise has to keep the submodule and its symlink workaround.