Median Denoiser
Rendering & Graphics
A median filter is the classic cure for
"salt and pepper" speckle: replace each pixel with the middle value of its 3×3
neighbourhood, and stray black/white dots vanish while edges stay sharp. Finding that middle value
fast means a sorting network of
min and max operations. Here a sweeping wipe shows the cleaned image on one side
and the raw noise on the other. Runs on its own; no joypad needed. Written in C and compiled with
the llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — the wipe sweeps between noisy and denoised.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
The median of nine numbers is the fifth-largest. You don't have to fully sort them — a fixed sequence of compare-and-swap steps (a sorting network) leaves the median in the middle slot:
// one compare-exchange: branchless min then max lo = (a < b) ? a : b; // min hi = (a < b) ? b : a; // max // 19 of these sort just enough to leave the MEDIAN in slot 4: median = network9(pixels)[4];
Nineteen min/max pairs do it. On a processor with no conditional-move instruction each becomes a compare and a branch, so the whole filter is compares, loads and stores — no multiply, no divide. The picture stays bit-exact across every codegen mode.
Compiler stress-test
| Item | What it exercises |
|---|---|
| min / max as the hot op | A median filter needs the middle of nine values. A sorting network does it with 19 compare-exchanges — each one a min and a max, written as (a<b)?a:b. Those are the generic min/max opcodes the compiler lowers directly, no library call. |
| no conditional move | The 65816 has no "move-if" instruction, so each min/max becomes a compare and a short branch rather than a branchless select. "Branchless" describes the source — no data-dependent control flow — which the compiler realises with compares. Measuring that is the point. |
| abs | The difference between the noisy pixel and the cleaned one uses absolute value — another small opcode the backend expands inline (compare-to-zero and negate). |
| cross-checked | The build gate runs the network median against an independent insertion-sort median over thousands of random 9-tuples; they must agree exactly. Verified with zero mismatches over 200,000 tuples. |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg
(and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build
gate's WRAM assert (gate CRC 0x87FE — a fold of median-filtered windows, cross-checked
against an insertion-sort median) live in this tab. No far pointers — the same source passes every
way: host == default == +mos-a16 == +mos-xy16 == bsnes-jg,
-verify clean.