# Mediante — settings reference Every option in the Settings window (⚙), with the reasoning behind it. The tooltips in the app say what each control *does*; this says why it exists, what it matches, and when you would want the non-default setting. How a module plays — its player, pitch, clamp and quirks — is chosen for you from the file when it opens. Those rows are in **Song Properties ▸ Playback**, where each one's Auto line says what it came to and whether from the file or from your default; they are documented here under [Playback compatibility](#playback-compatibility). - [Where a setting lives](#where-a-setting-lives) - [Playback engine](#playback-engine) - [Default compatibility](#default-compatibility) - [Playback compatibility](#playback-compatibility) - [Resampler](#resampler) - [Amiga output filter](#amiga-output-filter) - [Enhancements](#enhancements) - [Keyboard](#keyboard) - [Render output](#render-output) - [New module](#new-module) - [Interface](#interface) - [Appendix: the editor bar](#appendix-the-editor-bar) --- ## Where a setting lives Three different things get called "settings" here, and they have three different lifetimes. Most of the confusion about which window to open comes from the fact that nothing on screen separates them. **App preferences** — how *Mediante* behaves, whatever you load. The resampler, the output filter, the render defaults, and everything under Files & history, Online and Interface. These live in your browser, survive a reload, and follow you from module to module. None of them is written into a file you save. **Derived per-module state** — what *this* file needs in order to play correctly: its player and pitch, the upper-frequency clamp, and the quirks. Auto works these out from the file when it opens, and holds them while you edit. They are in **Song Properties ▸ Playback**. **Module data** — what the file actually contains: song name, global transpose, mixing channels, volume adjustment. These live in the module and are saved with it. They are on **Song Properties ▸ Song**. A module's own **choice** is where the second becomes the third. Picking a value other than Auto on the Playback tab stores it with the module — which is why it needs a `.medz`, and why it travels with the file instead of staying in your preferences. Your [defaults](#default-compatibility) sit between the two: they change what Auto comes to, for every module that has not chosen for itself. One consequence is worth stating, because it looks like a bug otherwise: - **Some Audio & output options change what a module's playback means.** They are not independent, and they are not duplicates either: the module says what it *is*, Audio & output says how it is *rendered*. `5-8 ch: lift Amiga limits` is the clearest case — it changes the mixing rules of the channel mode that Song Properties reports. If you cannot remember which window a control is in, the question to ask is **"would this still mean anything with no module loaded?"** If yes, it is in Settings. If no, it is in Song Properties. --- ## Playback engine ### Engine Which code actually plays the module. **Native Mediante (default)** — our own engine. Full MMD0–MMD3 and MED4, synth / hybrid / HighLife instruments, per-group effects and reverb, both software-mixing and Paula modes, and the ProTracker 2.3D replayer for `.mod` files. Every MED file plays here. **libopenmpt (fallback)** — broad tracker-format support, kept as a fallback and cross-check for foreign formats (XM, S3M, IT). It does not fully handle MED4 or the OctaMED SoundStudio extensions, and it cannot load MED files here at all. It is soft-deprecated: it loads lazily, only when a foreign format needs it. This used to be a toolbar button. It moved into Settings because it is a mix/engine option, not a transport control. --- ## Default compatibility Standing preferences, for every module that has not chosen for itself. Five of the [Playback compatibility](#playback-compatibility) rows are normally worked out from each file: player and pitch, the upper-freq clamp, and the three quirks. That is right almost always, and occasionally it is not what you want — if you always work in one mode, choosing it per file is tedious. These are where you say so once. (Soft clip and the plugin tempo have no default: their Auto is always the plain behaviour — hard clipping, the stored tempo value — and any other value is the module's own choice.) Each is **Auto (per module)** by default, and Auto means *no standing preference* rather than *force auto*: leaving them alone changes nothing at all. ### How a value is decided Three things can set a playback value. They resolve most specific first: 1. **The module's own choice** — picked in Song Properties ▸ Playback and kept in the `.medz`, so it travels with the file. 2. **A default here** — your standing preference, every module. 3. **Auto** — derived from the file. A default is what Song Properties' Auto line comes to — "Auto — Paula (31.4 kHz max) (your default)" — for a module that has not chosen for itself. It is never written into a module, and it never outranks one: a module's own choice still wins. There is no third, session-only layer: a pick in Song Properties *is* the module's choice, and picking Auto again removes it. Two things a default will not do: - **It will not force a value a module cannot use.** A mix pitch means nothing to a `.mod`, and the ProTracker replayer means nothing to a mix module, so **Player / pitch** lists both kinds and a module uses only the kind that fits it. A default that does not fit is skipped rather than putting the app into a state its own derivation calls impossible. - **It will not wait for the next load.** Changing one here applies to the module already open. **Amiga video** (PAL or NTSC) and **MOD timing** (CIA or VBlank) have no Auto: no file records either, so there is nothing to derive, and the value set here is what every module plays with. Song Properties ▸ Playback names them under the rows of a module they affect. ### Where the per-module controls are In **Song Properties ▸ Playback**. They are per-module state, and a choice turns one into module data, so they belong in the window that holds what the file contains — see [Where a setting lives](#where-a-setting-lives). --- ## Playback compatibility > **These controls are in Song Properties ▸ Playback, not Settings.** They are > per-module state, and a choice turns one into module data, so they live in the > window that holds what the file contains. Settings holds the app-wide [Default > compatibility](#default-compatibility) instead. Which player, pitch and quirks drive playback for one module. Each row's first option is **Auto**, which says what it came to and where from — "(from the file)" or "(your default)". Any other value is **this module's choice**: it applies at once, the row turns to the accent colour, and it is kept with the module, which means saving as a `.medz`. Picking Auto again removes it. A row that does not apply to the module is not shown — a `.mod` has no mix pitch, and the clamp means nothing to the ProTracker replayer — rather than shown greyed. ### Mixer / tuning On the Playback tab this is two rows, and a module has one of them or neither: | File | Row | Auto | |---|---|---| | ProTracker `.mod` | **Player** | ProTracker 2.3D replayer | | Mix-mode MED | **Pitch** | SoundStudio mix — Windows or Amiga, per file origin | | 4/8-channel MED (MIX flag clear) | none: Paula periods, fixed | — | **ProTracker 2.3D replayer** (Player, `.mod`) — a faithful port of pt2-clone's replayer: period-domain slides with the 113/856 clamp quirks, PT's own vibrato and tremolo tables, the arpeggio overflow quirk, E-commands, 9xx memory, and CIA tick timing. **MED sequencer** (Player, `.mod`) — plays the module's commands translated to MED's, on the same Paula periods, so the two players differ only in how they sequence. Useful as an A/B against the replayer. **SoundStudio mix, Windows** (Pitch) — MED SoundStudio for Windows mixing: A = 14080 Hz base, `AC × 256` (Q16.8) pitch-slide math, continuous equal-tempered synth and hybrid tuning. **SoundStudio mix, Amiga** (Pitch) — Amiga OctaMED SoundStudio mixing; this is the mode that nulls against Amiga direct-to-disk renders. Same A = 14080 Hz base as Windows. It differs in three places: integer-`divu` pitch-slide math, FPU frequency-table truncation, and synth/hybrid pitch quantisation. **Paula periods (4/8-ch MED)** — classic 4/8-channel MED: hardware Paula DMA and period-table pitch. The channel mode sets it, so the tab says so in words instead of offering a choice; changing it means changing the channel mode. **Amiga Paula periods** (Pitch, mix modules) — the same period table, for a song that has been converted to mix mode and should keep the pitch it had. OctaMED's conversion moves every sample and hybrid note up two octaves (synths stay where they are); this reads them two octaves down again, so every note, slide, arpeggio and octave-3 repeat lands exactly where it was, while the song gains the mixer — effects, panning, the Mixer window. What follows the pitch: the period table and its period-domain slides, the 113 floor, the multi-octave IFF slices, and the level of an 8-bit voice (no mix-table +1). What follows the mixer: the effects, the pans, the track budget and the SPD tempo formula, which is the module's own MED's. A 4-channel song converted this way renders bit-identical to its original (measured 2026-09-28: ten corpus modules, converted by hand, every sample equal - samples, synths and multi-octave IFF instruments among them). It is a module's own choice only: a Settings default cannot reach it, because on a song written in mix mode it folds the high notes into octave 3. It needs a `.medz`, like any choice. A switch between Amiga and Windows MED on the Song tab keeps the song's pitch: switching a mix module to Windows first chooses Amiga pitch for it, unless a pitch is already chosen. ### Upper-freq clamp Stops pitch slides at Amiga MED's clamping point instead of letting them run off into inaudible or aliased range. Auto follows the pitch — Paula periods get the Paula clamp, mix pitch gets the mix clamp, and it moves when the pitch does — and the row is exposed mainly for A/B testing. It is not shown while the ProTracker replayer plays: the replayer has period limits of its own. **None** — no clamp. Pitch slides may continue past the audible range. **Paula (31.4 kHz max)** — period ≥ 113, which is ≈ 31388 Hz on PAL (3546895 ÷ 113). Period 113 is where the **period table** bottoms out — B-3, the highest note ProTracker and MED can name — and it is the floor OctaMED's own player clamps pitch slides to. From `proplayer.a` V6.3 `fx_01`: ``` cmp.w #113,d0 ; Paula minimum period bge.s fx_01noovf move.w #113,d0 ; clamp ``` It is **not** the hardware limit, despite the asm comment calling it the "Paula minimum period". The *Amiga Hardware Reference Manual* gives period **124** as the minimum for reliable audio DMA — one word per channel per scanline — which works out at ≈ 28604 Hz on PAL and ≈ 28867 Hz on NTSC. That 28–29 kHz figure is the one usually quoted as Paula's maximum sample rate, and it is a different number answering a different question. Mediante clamps at 113 because that is what MED does, and the point of this setting is to reproduce MED. Periods between 113 and 124 are reachable on real hardware; they simply push the DMA past what the manual guarantees. **Mix (65.5 kHz max)** — frequency ≤ 65535 Hz. This is MED SoundStudio's `fx_21`/`fx_22` word-overflow safeguard, not a deliberate musical limit. ### Synth volume gate Whether a pure-synth instrument's volume is scaled by the **instrument** volume from the instrument header. OctaMED added this in **V6.00h** (late 1995), so that the volume commands 05/06/0C/0D/1A/1B would reach synth tracks. Gating by instrument volume was the side effect. Before V6.00h, synths ignored the instrument volume entirely, and composers used `vol=0` synth instruments freely — so getting this wrong for an old module silences notes that should sound, or vice versa. **Auto (from file format)** — MMD3 → gate. MMD0/1/2 → gate only when the file carries a **SoundStudio-era instrument-extension table** (`MMD0exp`'s InstrExt record size ≥ 18, the first size that reaches the 32-bit `long_repeat` / `long_replen` fields SoundStudio added for large samples); otherwise ignore. The container version alone was not enough. SoundStudio still writes MMD0/1/2 when a song needs nothing newer, so a modern save lands in an old container and lost the gate — measured on *Varta Deathspill — Ageing* (Aminet, 2021), an MMD1 whose four pure synths carry `svol` 32/32/40/16 and played **6 dB loud** on every one of them. The InstrExt size splits the collection cleanly: MMD0/1/2 files hold 2/4/8/10 or 18, MMD3 files hold 18 or 24, and nothing straddles. > **Residual blind spot.** V6.00h through V6.14 (late 1995 – 1996) had the gate > but still wrote the *old* InstrExt sizes, so a save from that window that > expects the gate is read as pre-gate here. Choose the gate for those modules > in Song Properties ▸ Playback ▸ Quirks. **Ignore instrument volume (pre-V6.00h)** — OctaMED up to V6.00g: pure-synth instruments ignore the instrument-header volume and play at their own synth volume, so an instrument saved at `vol=0` is still audible. Confirmed from v1 `modplayer.a` and Pro v3 `proplayer.a`, whose `synth_wftbl` **replaces** the track volume. **Instrument volume gates synths (V6.00h+)** — V6.00h onwards, including SoundStudio: synth volume is multiplied by the instrument volume, `synth_vol × inst_vol / 64` (`proplayer.a` `synth_wftbl`: `mulu` then `asr #6`). An instrument at `vol=0` silences its synth. Choose a value for a module Auto gets wrong. A save that would change what the next open derives looks after itself — see below. #### A choice kept with the module Picking anything but Auto on the Playback tab stores **the value** with the module — `pre-ss` or `ss`, never the word "auto" — so it survives a later change to the derivation, and it travels with the file instead of living in your app settings. Picking Auto again removes it. (Until 2026-09-28 this was a pushpin button beside each row, with the row's own setting as a third, session-only layer above it. Both went: the select *is* the module's choice now, and the pushpin's `.medz` field, `compat.pinned`, is where it is kept.) A changed choice is an **unsaved change**, like an edit: the ● appears, and opening another module asks first, naming the choices. Mediante compares the module's choices with the ones it was opened or last saved with, so trying one value against another and ending where you started leaves nothing unsaved. **Saving keeps what you heard.** Auto is worked out when a module opens and held while you edit. If a save would make the next open derive something else, the value the module has been playing with is stored as its choice, and the save message says so. Two cases need it today: an older module rewritten as MMD3 by a structural save (the writer's InstrExt size would turn the synth volume gate on), and a module playing the nominal filter-sweep rate that has been given both multi-page blocks and sweeps. > **It needs `.medz`.** A choice lives in the `.medz` sidecar, because a `.med` > has nowhere to keep it. A choice is what makes a module want to be saved as > one: a save writes `.medz` when the module has a WAM chain **or** a playback > choice. Save it as a plain `.med` and the choice is gone. #### What else the .medz remembers A `.medz` also carries **provenance**: what the source file said about itself when Mediante first read it — its name, size, SHA-256, container version, the `MMD0exp` InstrExt record size, and what Auto concluded from them — plus a history of every open and save since. This exists because **saving can destroy the evidence Auto reads**. A surgical save re-emits the original container and loses nothing, but a structural edit goes through the MMD3 writer, which pins the InstrExt size at 18. Both signals are then gone — replaced by statements that are perfectly true about the *new* file and say nothing about what was authored. Provenance is never played back. It is a record for you (and for the next investigation), not a setting: only a module's own choice changes how it sounds. Full reasoning in `stuff/medz-provenance-2026-08-19.md`. ### Win-MED filter-sweep rate bug Windows MED scales a **cmd-23** filter sweep's step by the block's command-page count — 1 page = 1×, 2 pages = 2×, 3 pages = 3× — regardless of which page the command actually sits on. Verified empirically against MED 1.4 and 2.1. It is almost certainly the dispatcher-1 epilogue firing once per (channel, page) pair rather than once per channel. Filter commands don't exist on the Amiga, so this only ever affects modules saved in Windows MED. On the Playback tab this is **Filter-sweep rate**, under Quirks. **Auto (keep where the song relies on it)** — fix the bug wherever it has not manifested, and keep it wherever it has (Phil, 2026-09-28). It is kept only for a module that has **both** a block of two or more command pages **and** filter sweeps: that is the only song whose author can have heard the faster sweeps and written to them. Everything else — a module made in Mediante, a module with sweeps but only single-page blocks, a module with multi-page blocks but no sweeps — plays sweeps at their nominal rate. Auto is decided when the module is opened and held while you edit, so adding a page or a sweep does not switch it on under you. If an edit gives a module that plays the fixed rate the shape Auto would read as "keep" — say a new song with a sweep in a two-page block — saving it stores **Not scaled** with the module (a .medz), so it reopens exactly as you heard it. **Scaled by command pages (as Windows MED)** — apply the page-count multiplier everywhere, including to modules created here. **Not scaled** — sweeps always run at their nominal rate. Not what Windows MED does for multi-page blocks. Choose one to A/B a sweep against a MED render. ### Invert-loop edits Invert loop (MED `0F FC`, ProTracker `EFx`) reshapes the playing sample as it goes, negating frames of its loop. It rewrites the sample rather than modulating the voice, which is why the timbre keeps evolving. On an Amiga those edits stay in chip RAM until you reload the module, so two plays of the same module sound different. **Keep between plays (Amiga)** — the reshaped sample carries over between plays, exactly as it does on real hardware. Reload the module to get the original back. **Reset at each play** — every play starts from the original sample, so each play sounds like the last and matches what an export renders. Not what an Amiga does. > This setting affects **live playback only**. Renders and exports always start > from the untouched sample, and saving always writes the original — the edits > live in the playback sample bank, never in the module. (ProTracker itself > saves the reshaped samples; we never do.) ### ProTracker cells Two things a ProTracker cell does that MED has no command for, which the MED sequencer plays for a module that came from a .mod: - **5xy with a note** slides to that note (tone portamento plus volume slide); in MED, 05 with a note retriggers it. - **An instrument number without a note** swaps the playing sample for that instrument's when its loop comes round, as well as reloading the volume; in MED it is a hold marker, which reloads the volume and nothing else. On the Playback tab this is **ProTracker cells**, under Quirks. It is not shown while a .mod plays through the ProTracker replayer, which does both itself. **Auto** — what the file says. A .mod is always ProTracker; a MED module is when it has the exact shape a .mod is given on the way in (4 tracks, no mixer, BPM 125, 6 ticks and 4 lines per beat, samples only, the ProTracker song flags) — which is also what a .mod saved as a MED module looks like. Held while you edit, and a save that changes that shape (another tempo or lines per beat, a fifth track, mix mode) keeps what you have been hearing with the module, in the .medz. Converting a .mod to a mix-mode song sets it on for the module. **As ProTracker (slide, sample swap)** / **As MED** — for a module Auto reads wrongly, or to A/B the two. There is no default in Settings: on for every module would change how every MED song plays its 05 and hold cells. ### What the file says, and what is in effect Compatibility comes from two places that look identical from outside the app — flags stored **in the file**, and values **worked out for it** when it opens. Until 2026-09-28 a read-only Module compatibility report listed both; each half is now where it is used: - **What the file says** is Song Properties ▸ File: the format, which MED wrote it, its tracks and tempo as stored, and every song flag it sets (`FILTERON`, `STSLIDE`, `COCH8` and the rest), each in words rather than by its name — so a flag the Song tab does not show, because it does nothing in this mode, is still reported. - **What is in effect** is each Playback row's Auto line, computed by the same resolver that playback uses, so the two cannot disagree. A `.mod` saved as MMD3 is the case that makes this matter. Afterwards it is a real MED module, played by the MED sequencer, and the only reason it still sounds like the original is the translated commands plus MED's own `STSLIDE` flag. --- ## Resampler ### Kernel The sample-interpolation kernel, listed highest quality and heaviest CPU first. **SINC — windowed sinc (HQ)** — 64–1024 taps × 256 phases with phase interpolation and downsample anti-aliasing. Modern high quality, and the default for mix-mode MED files. **HERMITE — HighLife cubic** — the 4-point cubic Hermite interpolator from discoDSP HighLife: that sampler's own "Engine: Hermite (Realtime)" setting, and what Mediante has always played HighLife instruments through, whatever this is set to. Here any instrument can use it. Four taps and no anti-aliasing filter: much smoother than MED's linear kernel (−3 dB near 0.8·Nyquist of the sample's own rate, against about 0.64 for linear, and with weaker images), at a fraction of SINC's CPU — but it rolls off the top of the band SINC keeps, and a sample pitched far up aliases. Pick it to hear samples the way HighLife plays its own, or as a light middle ground between MED and SINC when SINC is too heavy. **BLEP — band-limited Paula** — band-limited stair-step (MinBLEP). Keeps the Amiga's in-band zero-order-hold grit but removes the fold-back aliasing that digital ZOH adds, by more than 90 dB. The same technique pt2-clone and OpenMPT's Amiga resampler use. Auto-selected for ProTracker MODs and 4/8-channel MED, and pairs with the Amiga output filter. **MED — SoundStudio linear** — MED SoundStudio's own 2-tap linear interpolation, verified to null against MED SoundStudio 2.1. About 3.9 dB of HF rolloff at fs/4. This is MED's own render character. **ZOH — no interpolation** — zero-order hold, which is MED with interpolation switched off: crunchy and aliased, no fractional sample positioning. Lightest CPU. ### Taps Windowed-sinc tap count. **SINC kernel only.** More taps means a deeper stopband and sharper transition, at roughly linear CPU cost. | Taps | Character | Cost | |---|---|---| | 64 (default) | Bright: passband open to ~0.97·Nyquist, near-Nyquist imaging ~−18 dB avg — still brighter *and* cleaner than libopenmpt | baseline | | 128 | Open to ~0.98·Nyquist, imaging ~−22 dB avg | ~2× resampler CPU | | 256 | Open to ~0.98·Nyquist, imaging ~−36 dB avg | ~4× resampler CPU | | 512 | Very clean: imaging ~−74 dB avg (inaudible) | ~8× — offline | | 1024 | Full RX match: flat to ~0.99·Nyquist, top reflection ~−106 dB | ~16× + ~14 MB — offline / reference renders | ### Oversampling Render the whole engine — voices, effects, Amiga filter — at 2× the output rate, then half-band-decimate. This is the structure ProTracker 2's MOD2WAV uses, with the same 59-tap kernel. It works with every kernel and costs about 2× the DSP. **1× (native)** — the authentic MED baseline, and regression-pinned. MED and ZOH keep their characteristic rolloff and alias grit by design. **2× + half-band (HQ)** — flat top end and full alias suppression. 2× pairs best with BLEP, where it matches pt2-clone's MOD2WAV top end. With MED or ZOH it is a cheaper high-quality alternative to SINC. SINC gains little from it. --- ## Amiga output filter ### Filter model Models the analog output path *after* Paula — the other half of "the Amiga sound", alongside the BLEP kernel. Component values follow pt2-clone's schematic derivation. Applies to all playback and renders. **Off — no filter** — the default for mix-mode MED; playback keeps its current character. **A500 — 4.4 kHz low-pass** — the A500 output path: a fixed 1-pole RC low-pass at 4.4 kHz plus a ~5 Hz DC-blocking high-pass. The classic darker Amiga sound. **A1200 — DC-block only** — the A1200 output path: just the ~5 Hz DC-block, as its fixed RC low-pass sits above the audio band. Brighter, and the default for ProTracker MODs and 4/8-channel MED. ### LED filter The Amiga's switchable "power LED" filter: a 2-pole Sallen-Key low-pass at 3.1 kHz, Q = 0.66. Present on both models and toggled in software on real hardware. It needs a filter model above it to be audible. **Off — never** — the LED filter never engages; the module's filter commands and flags are ignored. **On — always** — always on; the module's filter commands and flags are ignored. **Command — follow module** — the authentic behaviour, sample-accurate in live playback and offline renders alike. Auto-selected for MODs and 4/8-channel MED. - *ProTracker MODs*: per-row `E00` (on) / `E01` (off) commands, matching pt2-clone. - *MED files*: the song's `Filter` flag sets the state at play start, and commands `0FF9` (on) / `0FF8` (off) toggle it mid-song, per `proplayer.a`. --- ## Enhancements Playback-quality options, not accuracy ones — real Amiga hardware behaves as "Off". They apply to live playback and renders alike, and the two de-click controls are bypassed entirely in ProTracker-accurate mode, where volume writes are deliberately hard Paula register steps. ### Stereo separation How wide Paula's left-right-right-left plays, for the modules whose panning is Paula's and not the file's: every ProTracker `.mod`, and every 4- and 5–8-channel MED song. The Amiga has no panning at all — channels 1 and 4 go to the left speaker and 2 and 3 to the right, hard — which is striking on speakers and tiring on headphones. A slider, from **100%** (the hardware, and the default) to **0%** (mono, every channel centred). Double-click it for 100%. It is the same rule as pt2-clone's *stereo separation*: a left channel sends `(1 + s)/2` of itself left and `(1 − s)/2` right, so the mono sum never changes, and a value set there sounds the same here. pt2-clone's own default is 20%. - **Mix-mode songs are not affected.** Each track has its own pan (the Mixer window), which is the module's, not Paula's. - **A .mod that libopenmpt plays** — more than four channels, or a Soundtracker variant — gets the same width through libopenmpt's own separation. Other formats libopenmpt plays (XM, IT, S3M…) carry panning of their own and are left alone. - **It changes as you drag**, even mid-song, and the header's pan knobs, which draw where the audio is, move with it. - **Renders and stems follow it** — what you hear is what you render. - **It is kept between sessions**, unlike the other Enhancements: it is how you like to listen rather than an A/B knob. The regression suite and `renderCompare` pin it at 100%, so it cannot move a measurement. ### Volume de-click Smooths sudden volume changes — command 0C, volume slides, note cuts, and 8-channel shared-register pairing — with a short smoothstep instead of a hard step. Paula steps its volume register instantly, so this is a playback-quality feature rather than a fidelity one. The length is adaptive: tiny changes stay near-instant, full-scale jumps get the longest ramp. It also scales the short fade-in applied at the start of every note. | Setting | Ramp | Notes | |---|---|---| | Off — hard steps (Paula) | none | Authentic, and clicks where the module clicks | | Subtle — half length | 0.5–2 ms by jump size | Barely-there; leaves transients untouched | | Normal (default) | 1–4 ms by jump size | Kills the click while staying below the threshold where transients soften | | Strong — double length | 2–8 ms by jump size | Smoothest, but audibly softens sharp attacks and cuts, and stopped voices linger | ### Retrigger fade When a new note reuses a voice, the old note's tail is faded out on a "ghost" voice while the new note rises, instead of being cut dead. Paula's DMA restart is a hard cut, so this too is playback quality rather than fidelity. It is separate from — and shorter than — the volume de-click ramp, so fast retriggers stay punchy. It is not applied to HighLife voices, whose own envelope handles it. | Setting | Fade | Notes | |---|---|---| | Off — hard cut (Paula) | none | No ghost voice is created at all | | Subtle — half length | 0.25–1 ms | Takes the edge off without smearing | | Normal (default) | 0.5–2 ms by the old note's volume | A/B'd against 4 ms, which made retriggers too soft | | Strong — double length | 1–4 ms | The old tail lingers over the new attack; rhythm can go soft on busy retriggers | ### 5-8 ch: lift Amiga limits Only affects 5–8 channel modules (OctaMED's `FLAG_8CHANNEL`). That mode mixed 8 tracks down to 4 Amiga channels and could not afford per-track volume work in its inner loop, so it gave up two things: 1. **Paired tracks share a volume.** Tracks 0/4, 1/5, 2/6 and 3/7 share one volume register — setting one ducks its partner. 2. **Hold and decay do not exist**, because that player has no decay code at all. Neither limit applies to a software mixer. OctaMED itself lifted the volume half in Mix mode, which its manual sells as "each track has an independent volume". **Off — faithful to hardware** (default) — paired tracks share a volume, and cmd 08 sets hold only, with no decay. What the author heard on real hardware, which is why it is the default. **On — independent volumes + decay** — every track gets its own volume with no partner ducking, and cmd 08 and instrument decay work as they do in the 4-channel and Mix players. Better than the Amiga could manage; not what the author heard. --- ## Keyboard ### Command modifier MED's own shortcut table is Ctrl-based, and so is Mediante's keymap. On macOS that role belongs to Command, so the same binding is written and read as ⌘. **Auto (follow the OS)** — ⌘ on macOS, Ctrl everywhere else. This is the right signal: a Windows keyboard plugged into a Mac still reports its Windows key as Meta, and a web page cannot see the physical keyboard at all. **⌘ Command (macOS)** / **Ctrl (Windows / Amiga)** — pin it when Auto is wrong, which in practice means a remapped setup. **On macOS both ⌘ and Ctrl trigger every shortcut.** That is not redundancy — the two are reserved by the system in *different* places, and accepting both is what keeps the transport reachable. Measured on macOS with Chrome 150: | Chord | Reaches the browser? | Taken by | |---|---|---| | ⌘Space | **no** | Spotlight | | ⌘⌥Space | **no** | Spotlight Finder window | | ⌃Space | yes | — | | ⌃⌥Space | yes | — | | ⌃← ⌃→ ⌃↑ ⌃↓ | **no** | Mission Control, Spaces, App Exposé | | ⌘← ⌘→ | yes | — | | ⌘↑ ⌘↓ | **no** | — | So Continue Block is reachable as ⌃Space and Play Block as ⌃⌥Space, even though their ⌘ forms are not. Menus show the ⌘ spelling because that is the one to reach for first; where it is reserved, the Ctrl form still works. ### Chords that differ on macOS **First block / last block** are the one pair macOS leaves no route to — both ⌘↑/⌘↓ and ⌃↑/⌃↓ are taken. They gain a Mac-only chord: | | Windows / Amiga | macOS | |---|---|---| | First block | Ctrl+↑ | ⌥⌘↑ *(or ⌃⌥↑)* | | Last block | Ctrl+↓ | ⌥⌘↓ *(or ⌃⌥↓)* | Everything else is the same chord on every platform. ### Keys that work in a window, not just the grid The **octave** and **instrument** keys work inside the Instrument Properties window — on all four of its tabs, including Synth, HighLife and MIDI, where stepping to another instrument is most of what a keyboard is for. They are the grid's own bindings, not a second set: | | | |---|---| | F2 / F3 | entry octave, down / up | | Shift+← / Shift+→ | previous / next instrument | | Numpad + / − | previous / next instrument (Shift: by sixteen) | | Numpad 0–9, / , . | pick by number, the last one, the high digit | **Function keys now leave every floating window.** F2/F3 (octave), F4 (row marks) and F5–F9 (block jump) reach the song from the Jobs window, an effect window, the Library — anywhere. A window that wants a function key for itself claims it, and one does: **F2 renames** in the Library, and does not also change the octave. Two keys in the sample editor moved to make room, and neither is lost: | | was | now | |---|---|---| | Extend the selection by a frame | Shift+← / → | **Alt+Shift+← / →** *(Ctrl still makes it a screenful)* | | Zoom | +/− anywhere | **+/− on the main row** — the numpad pair steps the instrument | The alternative was for one key to mean two things depending on which window was in front, which is the keyboard equivalent of moving a button around the screen. ### Keys a laptop keyboard lacks **Delete** and **Backspace** are different keys doing different jobs, on every platform: Delete clears the cell where it stands, Backspace deletes the row and pulls the rest up, and Shift+Backspace inserts a blank slot. Mac laptops have no dedicated forward-delete key — the key marked ⌫ sends Backspace — so **Delete is Fn+⌫** there. The same is true of **Home**, **End**, **PageUp** and **PageDown** (first/last track, and move ±16 lines), reached with Fn+←, Fn+→, Fn+↑ and Fn+↓. The **numeric keypad** is the real gap. Seventeen bindings use it for instrument selection — direct digit entry, last-instrument recall, pick, and the coarse ±16 step. On any laptop without a keypad, Mac or Windows, those are unreachable. Instrument selection itself is not lost: Shift+← / Shift+→ step through instruments, and the INS dropdown in the editor bar picks one directly. --- ## Render output ### Channels Stereo, or mono summing L+R at equal gain (−6 dB each, no clipping). Applies everywhere — live playback, REC and RENDER — so what you hear is what you get. Mono is useful for checking the mono compatibility of hard-panned Amiga modules, and for smaller mono renders. ### Format **WAV** — uncompressed PCM, 16/24-bit integer or 32-bit float. **FLAC** — lossless compressed and bit-identical to the WAV, but much smaller. FLAC is an integer codec, so it covers 16 and 24-bit only; selecting FLAC with 32-bit float depth drops the depth to 24-bit. ### Bit depth The sample word size of the rendered file. Applies to RENDER and REC. **16-bit integer** — CD quality. **24-bit integer** — studio standard. **32-bit float** — full headroom, no ±1 clamp on inter-sample overs. WAV only; selecting it switches the container to WAV. ### Sample rate Mixing and output sample rate of the render — 44.1, 48, 88.2, 96 or 192 kHz. Higher rates push images and aliasing further above the audible band. Applies to RENDER only; REC captures at the live engine rate. Mix-mode SPD tempo tracks this rate, while BPM and Paula tempo are rate-independent. --- ## New module What File ▸ New creates. ### Flavour Which MED flavour a new module is created as. It is a property of the file itself, not a playback mode — loaded modules keep whatever flavour they were saved as. **Windows MED (default)** — MED SoundStudio for Windows (MED4). 7-bit command 0C volumes (0–127). The right answer for almost everyone, and what the newer features target. **Amiga OctaMED** — Amiga-flavoured MMD3. Command 0C is 6-bit (0–64); the Amiga player treats anything above 64 as a whole-command no-op. Choose this when authoring against the real thing. --- ## Sidecar (.medz) An edited sample keeps a float working copy — its *master* — and that has nowhere to go in a `.med`, so a module with any edited sample is saved as a `.medz` (a zip of the `.med`, its metadata, and the masters). These two settings choose how that sidecar is written. Both are read back regardless of the choice: each entry says what it is. **Sample masters** — how the float master is stored. - *24-bit FLAC* (default): about half the size. A float has 24 bits of *relative* precision at every level; an int24 has 24 bits of *absolute* precision, so each edit-then-save rounds every value to the nearest 2⁻²³ — at most 2⁻²⁴, about −144 dBFS. A save with no edit since the last one is exact, so nothing compounds unless you keep editing between saves. - *32-bit float, bit-exact*: the master as it is, four bytes a frame. **Compression** — whether the zip is deflated. *Deflate* shrinks the module and the metadata; a FLAC entry is already packed and is stored as it is. *Store only* writes an uncompressed zip. Saving a plain `.med` keeps only the rendered 8- or 16-bit audio. ## History Two caps on undo memory, in megabytes. **Pattern history** and **Sample history** are budgeted apart — a run of sample edits cannot shorten your pattern undo, and vice versa — so the two add: a session may hold both at once. They are caps on *live* memory, not reservations and not disk. History grows as you edit — an undo step holds a reference to what it replaced — and when a cap is reached the oldest steps are released. Lowering a cap releases older steps immediately. The line under the inputs says what history holds now. One sample-edit step on a 45-second 16-bit stereo sample is about 12 MB (its float master plus its rendered audio), so 256 MB is roughly twenty such steps; an 8-bit chip sample costs kilobytes a step. 64 MB to 2 GB. **History on disk** — what happens when sample history reaches its memory cap. Instead of dropping the oldest steps, their sample data (the rendered audio and the float master) is written to the browser's own storage for this site and read back the moment an undo or redo reaches such a step — that undo takes a beat longer, and the status line says it is reading. The step is still in the history; only its bytes have moved. This is how much may live there, 0 to 8 GB; 0 turns the spill off and history is dropped at the memory cap as before. The oldest steps are dropped for good only when this cap is reached too. Nothing here is a file of yours: the directory is the browser's, it is cleared when the tab starts, and one left behind by a closed or crashed tab is removed on the next start. **Auto-save every** — minutes between snapshots while the module has unsaved edits; 0 turns it off. A snapshot is the same `.medz` a Save would write (the module, its metadata, every float master), kept in the browser's own storage for this site. **Auto-save never overwrites a file of yours**: nothing is written to your folders, and only a Save you perform touches a file. One is also written when the tab is hidden or closed. On the next start, a snapshot newer than your last Save is offered for resume; a Save clears it. The first Save after a resume is a Save As, because the app cannot know which file the module came from. Each tab keeps its own snapshot, and only ever clears its own. The browser itself says which tabs are still open (the Web Locks API), so what is offered on a start is exactly the snapshots left behind by tabs that closed or crashed — never one a live tab is still writing. If more than one is waiting, the newest is offered and the rest are listed with their own Resume and Discard. When the app is already open in another tab, the welcome screen says so; that is a warning, not a refusal. ## Online services Two commands in the sample editor (**Commands ▸ Online**) do not run on this machine. They upload the audio you have selected to somebody else's server, which is processed there and downloaded back. Everything about them is off until you paste your own key **and** allow the service here; nothing contacts anything by itself, and turning a service off again revokes it. Your keys live in this browser's preference store, alongside every other setting. They are shown as password fields with a **SHOW** button, are never written to a log line, and **are left out of an exported settings file** — a settings file is a document people hand to each other, and an API key in one is a credential leak with no visible moment of leaking. Loading a settings file that carries no key leaves yours alone, so somebody else's export cannot clear it. ### MVSep Source separation, transcription and generation — 126 models, from a two-stem vocals/instrumental split to a 53-instrument one, plus drum-kit splitting, super-resolution, reverb removal and note transcription to MIDI. This is a commercial service and needs your own account; **the integration is unofficial** — MVSep neither endorses nor maintains it. **Where the audio goes and how long it stays.** To `mvsep.com`, processed in **Germany or Hong Kong** depending on the region below. MVSep's privacy policy says the file as received is deleted after 1 day, the working copy and the generated outputs after 3 days (plus up to a day's slack), and that uploads are **not used to train, fine-tune or evaluate models**. ⚠ It also states plainly that they have **no Standard Contractual Clauses** for international transfers, and asks EEA and UK users to take that into account. Their terms require that you hold the rights to what you upload — in a tracker, where samples are often lifted from other people's modules, that is a real condition. Mediante never asks MVSep to publish your audio. Their API has an `is_demo` flag which makes your **original, un-separated upload** downloadable by anyone with no key at all; there is no preference here that could turn it on, and the client sends `0` unconditionally. **MVSep key** — your API token, from your MVSep account page. **TEST** asks the service who it belongs to and shows your credit balance, the credits-enabled switch, and how many free separations the day has left. **My presets** are not a row here: the MVSep window's model list starts with them, and **ADD** takes a preset's id and a name. MVSep lists your ids on its API page once you are signed in, in the "Preset (preset_id) values" table far down the collapsed "create separation" section; **Find my ids** opens that section, and searching the page for "preset_id) values" jumps to the table. They are stored with your settings, so a settings file carries their ids and names — useless without a key, and the key is never in one. **Region** — which of MVSep's hosts processes and stores a job. A job is processed *and kept* in the region that created it, and its download links only ever point there, so the region is pinned per job the moment the job is accepted. `Auto` lets `mvsep.com` steer you by location and then pins whatever it chose. **Result format** — what the service is *asked* for. It is a request and not a guarantee: two of six sampled jobs came back as 32-bit float while the response still said 16-bit, and every model resamples to its own native rate (usually 44.1 kHz, but 24 kHz for Medley Vox and 48 kHz for AudioSR). Mediante therefore decodes what actually arrives rather than believing the label. 24-bit and 32-bit float are the premium tier; with no credits they are asked for as 16-bit instead, because what a free account gets when it asks for 24-bit is undocumented and a failed job costs a place in a long queue. **Upload format** — how the audio is encoded on the way *to* MVSep, which is a different question from what comes back and now a different setting. Every choice is lossless; what differs is size and what happens to audio that sits **above full scale**. `Match the audio`, the default, sends the smallest format that holds what the sample actually has. **FLAC 16** when the audio sits exactly on the 16-bit grid, which is where a plain 8- or 16-bit tracker sample sits. **FLAC 24** when it does not — a 24-bit or float import, or anything edited in float, has detail finer than a 16-bit step, and since a wide import now keeps its precision all the way into the editor it would be odd to throw it away at the last step. **32-bit float** when the region peaks above 1.0. So the upload grows only when there is something to carry: about 1.5x for 24 bits, about double for float. It buys something because the float master is never clamped: a sample you have just declipped deliberately holds peaks past the rail, and an integer encode would flatten exactly the peaks the declipper reconstructed. Both services accept float and neither clamps it on the way in; that was measured against the live APIs on 2026-09-07, not assumed. Choosing `FLAC 16-bit` or `WAV 16-bit` explicitly is you saying 16 bits. If such a region is over the rail it is **scaled down by 1/peak rather than clipped**, and the factor is reported, so nothing is silently flattened. `WAV 32-bit float` forces float always, at about twice the bytes of FLAC. Note what is uploaded is **always re-encoded from the sample as it stands**, never the file it came from. An MP3 or OGG is decoded at import and the compressed bytes are not retained anywhere, so there is no original to send. That costs nothing in quality — MVSep would decode the same file to the same samples — but it does mean a four-minute import uploads as tens of megabytes rather than a few. **MVSep sample rate** — whether the audio is converted to 44.1 kHz here before it is uploaded, or sent at whatever rate the sample already has. **Sending as-is is the default, and it loses nothing:** MVSep resamples everything it receives to whatever its model was trained at. A 48 kHz upload and a 96 kHz upload were both measured coming back at 44.1 kHz. So this setting does not decide *whether* a resample happens — it decides *whose* resampler does it, and converting here costs a 1024-tap sinc pass over audio the model is about to rewrite anyway. It is the same argument that makes `retag` the A||GO default: a MED sample has no true sample rate, only a rate derived from the note it is played at. Convert here if you would rather this resampler did the work than theirs — the choice is offered per job in the MVSep window as well, where it starts from this setting. Either way, **what comes back is whatever the model emits** — usually 44.1 kHz, but 48 kHz from AudioSR and 24 kHz from Medley Vox — and is decoded at its real rate rather than an assumed one. **Proxy** — where MVSep's JSON calls are routed, default `/cors/`. MVSep's API sends no CORS headers at all, so a browser cannot call it directly: the model list, the upload and the status polls go through a same-origin reverse proxy on this site. The **downloads do not** — MVSep's file host already allows them cross-origin — so the stems come straight to the browser and none of that bulk touches the proxy. That also matters for rate limiting: MVSep allows 120 requests a minute **per IP address**, which behind a proxy is one address for everyone using that deployment, so keeping everything but the small calls off it is what makes the budget last. The development server (`stuff/dev-server.js`) already answers `/cors/mvsep/…`. **A production server needs a matching rule, and adding it is a hand edit to the web server's configuration — Mediante cannot do it.** For Caddy: ```caddyfile # Inside the site block, BEFORE the final handle { file_server }. handle_path /cors/mvsep/* { # The API key rides in the query string; keep it out of the access log. log_skip reverse_proxy https://mvsep.com { header_up Host {upstream_hostport} } } ``` The page and the proxy share one origin, so no CORS headers are needed here (the browser only needed them because mvsep.com sends none). Caddy has no request-body cap by default, so the upload passes as is. Repeat the block for `/cors/mvsep-de/*` → `de.mvsep.com`, `/cors/mvsep-de2/*` → `de2.mvsep.com` and `/cors/mvsep-hk/*` → `hk.mvsep.com` if you pin a region. Two things matter beyond the routing: the upload may be up to 100 MB (1000 MB on a premium account), so **do not put a request-body cap on it**; and your access log records the API key without `log_skip`, because MVSep passes it as a query parameter as well as a form field. Do **not** make this a general proxy that forwards an arbitrary destination: that is an open proxy, reachable by anyone who can load the page. ### The two A||GO sites, and why there are two token fields `allgo18.inria.fr` and `allgo.inria.fr` are **two separate applications with separate accounts**. The platform source keeps its users in `dj_users` and looks the `Token token=…` header up there; a token it does not find is a 401, which is why one site can accept a token the other rejects. There is one field per site because a single field could only ever be right about one of them. The **TEST** button asks each site with the token stored for it, and a site with no token is reported rather than asked. A 404 in that answer means *accepted* — the probe reads a job that does not exist, and the auth check runs before the lookup, so "no such job" is proof the token got through. Neither site is simply better, which is why the choice is offered: | | allgo.inria.fr | allgo18.inria.fr | |---|---|---| | A-SPADE | **3.3** (13 Nov 2024) | 3.2.1 (22 Mar 2024) | | longest job | 20 minutes | 10 minutes | | live log while it runs | no | **yes** | | cancel / delete a job | no | **yes** | | upload cap | 1 GB | none at the proxy | So the older *platform* runs the newer *algorithm*. Read from both sites on 2026-09-07: allgo18's version table ends at 3.2.1, and allgo.inria.fr's history ends at 3.3. **On the published evidence the difference between them is a length limit and nothing else** — both changelog entries say only "extend first 30 seconds to 20 minutes" — and a tracker sample is seconds long, so `Automatic` prefers allgo18 for the live log, the cancel and the delete. Pin the host if you would rather have 3.3. A log still appears for a legacy job: with no `/events` to stream, the client polls A||GO's own `allgo.log` while the job runs and reports it as it grows. ### A||GO declip Audio goes to A||GO as integer WAV **at the depth the audio actually needs**: 16-bit when the region sits exactly on the 16-bit grid, which is where an untouched 8- or 16-bit tracker sample sits, and 24-bit when it does not — a 24-bit or float import, or anything edited in float. This used to be 24 bits for everything, on the reasoning that headroom is free; it is not free, it is half again on every upload to a service measured in minutes per job. A region whose peaks sit above full scale is **scaled down by 1/peak first** rather than being widened to float or clipped. That is measured, not assumed. Four real jobs on 2026-09-07 established that **A-SPADE normalises internally**: the same clipped audio sent with its rail at 1.5 and at 1.0 came back as the same declipped signal, every output peaks at exactly 1.0 whatever went in, and the same audio sent as 24-bit integer and as 32-bit float declipped identically (0.22 % against 0.23 % residual distortion, with the detected clipping threshold agreeing to five decimal places). So an out-of-range upload tells A-SPADE nothing that scaling does not, while costing a third more bytes. What must not happen is an unscaled integer encode, which would clip those peaks off and hand the declipper flat tops we had just manufactured — the one thing it is being asked to undo. Sending 16 bits where 16 bits are exact gives up nothing on the way back, either: **A||GO always writes 24-bit integer**, whatever depth it was given. Measured over sixteen kept cloud results — eight 8-bit inputs and one 16-bit input all returned 24-bit, at their own sample rates unchanged, which is also what makes the `retag` rate mode safe. One consequence worth knowing: because the output is normalised, **the level that comes back is not the level that went out**, and relating the two is the importer's job rather than something the service preserves. Declipping by **A-SPADE**, the Analysis SParse Audio DEclipper, running on **A||GO** — a scientific web-application platform operated by SED, IRISA / Inria in Rennes, France. You need your own A||GO account and personal token. **Where the audio goes and how long it stays.** To Inria's servers. A||GO's privacy page says job inputs and generated data are kept for **30 days** and then deleted. A-SPADE is licensed for **non-commercial scientific use** and its terms ask to be cited; the citation is in [CREDITS.md](../CREDITS.md). Mediante is not affiliated with, and not endorsed by, Inria, CNRS or IRISA. **A||GO token** — from your A||GO profile page. **TEST** asks **both** hosts whether they accept it and reports what each said, because whether one personal token works on both deployments is genuinely unknown. The probe reads a job that does not exist, so it never queues work on your account. **A||GO host** — the two deployments are two applications sharing one machine. `allgo18.inria.fr` is the current one and the default: it streams the live processing log (which is where the progress percentage comes from), and it can cancel and delete a job. `allgo.inria.fr` is the older application and has none of those — a job there is submitted, waited for, and cannot be called back. **Queue** — named by the longest job each allows: interactive under a minute, standard under twenty minutes, batch under a day. It is a ceiling, not a priority: a job that exceeds its queue's limit ends as a timeout. Standard is A-SPADE's own default; a tracker sample is usually short enough for interactive. **Rate handling** — A-SPADE accepts 16, 32, 44.1 and 48 kHz, and a MED sample is at none of them, because *a MED sample has no sample rate at all*: its pitch comes from the note it is played at. So the rate written into a WAV header is a label rather than a fact. - **Re-tag the header** (default) relabels it as the nearest accepted rate at or above the sample's nominal one, and declares the original again on the way back. **Not one audio sample moves** — the audio A||GO sees is bit-identical to yours, merely played faster — and a declipper reads levels and the shape of a clipped run, not absolute frequency. - **Resample to 44100 Hz** actually converts the audio through the sinc resampler and converts the result back, which is two passes and the ringing that goes with them. Choose it if you would rather the service see genuinely 44.1 kHz audio. ### Tidy up after Whether a finished job is deleted at the service once its result has arrived here. On by default: your audio and its results otherwise sit on somebody else's disk until their retention sweep, and this takes them off early. It happens **only after the result has decoded here** — until that moment the copy on their server is the only one — and never after a failure, a cancellation, or a result that could not be placed. ### The Jobs window A cloud job outlives the page: A||GO's standard queue allows twenty minutes, and an MVSep free-tier job has been observed with a forty-six-minute estimate. So jobs are remembered. While one is running the status bar carries a line naming it; clicking that line opens the **Jobs** window, which is also under **Commands ▸ Online ▸ Jobs**. It lists every job with its progress, elapsed time, estimate, log tail, and buttons to cancel, remove, or open the results. Reloading the page does not lose a job — the job's identity at the service is written down before anything else happens with it, and reconnecting on the next start costs no upload and no place in the queue. What is remembered is the job's remote id, which host or region holds it, and which sample it came from. **Your key is never written there**, and neither is any audio. Records are dropped once they are older than the service's own retention (30 days for A||GO, 3 for MVSep), because past that they can only ever answer "not found". If the sample slot changed while a declip was running — you edited it, or loaded another module — the result is **added to the project without taking a slot** instead of being written over whatever is there now, and the Jobs window says so. ## Library **Look inside archives** — how big an archive may be for a single click in the Library's Online tab to download it and list what it holds. It applies to scene.org and Aminet, whose music is mostly zip, LHA and LZX files rather than bare modules. Choosing **Only when asked** means no click downloads anything until you double-click the archive or press **LOOK INSIDE**; those two open an archive of any size up to 12 MB, which is the ceiling the Library holds every archive to. The default is 4 MB (preference `library.autoLookMB`). The size compared is the one scene.org's folder listing or Aminet's index reports, both rounded (“213.8K”, “37K”), so an archive right at the limit can fall either side of it. ## Interface Non-audio options — layout and renderer choices. ### Colour theme Ten themes. Five are Mediante's own, four are tributes measured from screenshots of the programs they honour, and one is yours. Each of the five house themes is a two-hue **ramp**, not a single colour with the saturation turned up: the deep chrome end and the bright accent end have different hues, interpolated by lightness, so the dark surfaces carry a different cast from the highlights. A single-hue theme reads as monotone no matter how strong the colour is. **Funk** (default) — deep purple into violet. **Glow** — deep brown into amber; the closest to what the app used to be. **Heat** — dark red into magenta. **Nightvision** — dark forest green into an acidic yellow-green. **Chill** — navy into teal. The tributes are built from measured pen sets rather than impressions. Each program's screenshot was reduced to its actual palette and those pens were mapped onto roles, so the colours are the originals, not approximations of them: **Professional** — OctaMED Pro 4: the `#000000 / #666677 / #9999AA / #CCCCCC` grey ladder with a red edit cursor. ProTracker used to be a separate theme and was merged into this one, because the two palettes are the same four-step grey bevel — PT's `#000000 / #555555 / #888888 / #BBBBBB` — one tinted and one not. **Magique** — MagicWB 2.0 (Martin Huttenloher, 1992–94): light chrome, blue title bars, white bevels. **Das Nonplusultra** — ScreamTracker's tan and sandy palette, with its bright highlight used for headings. **Blorange** — Workbench 1.3 as it actually shipped: white title bars with blue text, blue text on a white menu bar, white-on-blue scrollbars, inverse-video selections, and orange reserved for accents and the one menu highlight. The orange title bars people remember are 2.0 and later. **Custom** — your own ten colours. **Edit custom…** opens them: the well, the panel, the raised and shadow tones, the outline, the text, the accent ink and fill, a bright, and a warm. They preview live as you adjust. The remaining eighty-odd tokens are derived from those ten by the same role map that generates the tributes, so a custom theme is built exactly the way a shipped one is. The pattern grid's own colours — Note, Instr.Number, Command, the two Level slots and Volume — are **graded per theme** rather than fixed or free. Each keeps its own hue family, because these are MED SoundStudio's own Display slots and a pattern is unreadable if they stop being tellable apart, but each is pulled a little way toward the theme's hue and then lifted or darkened until it clears a contrast floor against *that theme's* pattern well. This is why the grid does not look like an amber-era island inside a green theme, and why the inks invert sensibly on a light-welled theme like Magique. ### Module colours OctaMED saved a screen palette inside every song, and Mediante can honour it. **Ask (default)** — prompt when a module carries a genuinely custom palette. **Apply automatically** — recolour the editor from the module without asking. **Ignore** — never recolour from a module. **The two factory palettes are skipped in all three modes**, not just in Ask. They are matched on their raw `rgbTable` words: | | palette | |---|---| | OctaMED pre-V5 | `0000 0CCC 0667 099A 0F00 0F88 0FAA 0FCC` | | MED 2.00 | `0000 0CCC 0556 099A 0F00 0F66 0F99 0FCC` | The reasoning is that a stock palette records no decision — it is what the editor wrote because nobody changed it — and recolouring the whole app from a non-choice is noise. The census behind that (`stuff/palette-census.js`, 556 unique MMD files) says how often it matters: **24 carry a palette at all, and 16 of those 24 are one of the two sets above.** Only 8 modules in the whole collection carry a palette somebody actually chose. It is checkable in the regression corpus, where all three palette-carrying modules are stock and none prompts: `floodworks.med` and `zebes-ami.med` (OctaMED pre-V5), `audible-power.med` (MED 2.00). > A consequence worth knowing: **Apply automatically does nothing on a > stock-palette module.** If you would rather it applied those too — the > pre-V5 grey-and-red set is a real look, and it is where the Professional > theme comes from — that is a one-line change to where the check sits. A `.medz` can also carry a custom palette pinned from the theme editor, which is honoured the same way. Picking any theme afterwards overrides the module — that is what picking a theme means. ### Pattern renderer — gone There is no longer a choice: the canvas renderer draws the pattern grid. Loading the app with `?canvasgrid=0` still forces the old DOM grid, because it is the scaffolding the canvas is built on rather than a rival to it, but that is a developer escape and nothing stores it — reload without the parameter and you are back on canvas. ### Interface scale Scales the whole interface, the way the browser's own zoom does, but in 5% steps and remembered between sessions. 75% to 250%. The reason it exists rather than deferring to the browser is granularity in the band people actually use: between 100% and 150% Chrome offers 110 and 125 and nothing else. This gives eleven stops there. It is a stepper rather than a slider on purpose — a slider resizes the interface continuously under the thumb, so the control moves while you are pointing at it. Type a value, use the arrows, or press **Auto**. **Auto** sets what your display suggests, and is a one-time suggestion rather than a mode: it never re-derives on its own. If it did, moving the window between monitors would silently rescale the app, and a value you had chosen would be indistinguishable from one derived for you. Be aware that no browser reports OS display scaling as a distinct quantity — `devicePixelRatio` is the product of OS scaling, panel density and browser zoom, unlabelled, so Auto asks the narrower question it can actually answer: is anything already compensating for a small UI? On a Retina or fractionally-scaled display the answer is yes and it suggests 100%. **Browser zoom still works and multiplies with this**, so Ctrl+= remains the escape hatch and is deliberately not intercepted. The value is clamped down if the window is too small to hold the app at that size — the field then shows what was applied, not what you asked for. That matters because Settings, where you would turn the scale back down, is inside the window a too-large scale would cover. If you ever do get stuck, loading the app with `?prefs=reset` clears every stored preference. ### Density How much space the interface uses, independent of its size. **Normal (default)** — as of 2026-08-24 this *is* what earlier versions called Compact. The old roomier spacing is gone rather than demoted: nobody was choosing it, and every measurement that mattered — whether Settings fits on a laptop, how many pattern rows you see — came out better without it. **Compact** — tighter again, and measured against the new Normal rather than the old one: about 4px more of permanent chrome (2 off the menu bar, 2 off the status bar), 2px off every panel header, **13px more pattern area**, and 50–55px off the two tall Settings tabs. Nothing shrinks that you click or read. Type size never changes; the spin arrows, channel buttons, splitters, scrollbars and collapse controls are already at their smallest and are explicitly left alone. What gives is padding, gaps and the minimum heights of bars whose content does not fill them — and each of those minimums is measured against its own tallest child, with the figures recorded in `stuff/regression/density-tokens.js` so a future squeeze cannot quietly clip something. Density is not scale. Scale makes everything bigger or smaller; density changes only the space around things, and leaves type size alone. ## Fonts Three separate choices, each applied at once and remembered between sessions: | Role | What it covers | Default | |---|---|---| | **Titles** | dialog titles and the section headings in Settings | Oswald | | **Text** | menus, buttons, lists, panels, dialogs — everything else | Share Tech Mono | | **Pattern** | the pattern grid: notes, instruments, commands, row numbers | Share Tech Mono | **Nothing moves when you switch.** Every font is scaled to take exactly the space its role's default takes: the same width per character (so text, columns and the grid's cells keep their length) and the same line height. Mediante does this with the CSS `size-adjust`, `ascent-override` and `descent-override` descriptors, so it works for any font, including one you load yourself. Measured on a loaded module: switching every role to Topaz changes the size of 3 of the 532 elements on screen, each by 1px. **Taller letters are expected.** Topaz's character cell is narrow for its height, so drawn at Share Tech Mono's width its letters stand about a third taller. Making them shorter would make them narrower too, and that would move the layout. **Nothing is cut off, either.** Letters that tall would poke above the line, and wherever a row trims long text (the sequence and block lists, the song title, panel headings) the top row of pixels was clipped. So in the text and pattern roles Mediante moves every glyph down a little — for Topaz, 0.086 of the font size — until its tallest letter is no taller than Share Tech Mono's own, rewriting the font in memory as it loads. The width and the line height stay exactly as they were; titles need no move, because Oswald's line is tall. A font you load that cannot be rewritten (an OpenType CFF or WOFF2 file) is scaled down just enough to fit instead, which makes it slightly narrower. **Not pixel-perfect, deliberately.** These are bitmap designs, and sized to match the default they no longer land on whole pixels, so their edges are smoothed. Keeping the layout still was the priority. ### The fonts on offer Every bundled font has a regular and a bold face; bold is the Amiga's own emboldening, each pixel smeared one to the right. **Topaz (Kickstart 1.3)** — the Amiga's own ROM font, pixel for pixel (188 of 189 Latin-1 glyphs match the Kickstart 1.3 ROM; the one difference, ß, was redrawn on purpose), extended with about 800 more characters in the same style so the arrows, dashes and ellipses Mediante uses are Topaz too. It is [Topaz Unicode](https://gitlab.com/Screwtapello/topaz-unicode) by Screwtape, shipped with Mediante under its ISC licence. **Topaz (Kickstart 3.0)** — the Kickstart 2 and 3 Topaz, pixel for pixel (187 of 189 Latin-1 glyphs match the 3.0 ROM). **TopazPlus (1.3 / 3.0)** are dMG's reworks of both, with rounder brackets, slashes and underscore. **MicroKnight**, **MicroKnight Plus**, **P0T-NOoDLE** and **mO'sOul** are the demo-scene favourites. All seven come from dMG's [amigafonts](https://github.com/rewtnull/amigafonts) (GPL with the font exception), rebuilt for Mediante: the letters sit on the proper baseline, and the characters beyond Latin-1 are borrowed from Topaz Unicode so arrows and dashes stay pixel glyphs. **Topaz New (Kickstart 3.0, smoothed)** — Alan Tinsley's 1997 smoothed outline of the Kickstart 2/3 Topaz, freeware for non-commercial use. It ships as its original 1997 archive, unaltered, and Mediante reads the font out of it; see `fonts/topaz-new/README.md` for its terms and why it is included. It draws only plain ASCII — every other character in the file is a copy of "!" — so Mediante uses it for ASCII and takes everything else, including the pattern grid's · dots, from the default font. **Load font file…** — use any TrueType, OpenType or WOFF font of your own (up to 8 MB). It is sized and fitted the same way, offered in all three lists, and kept in this browser only: it does not follow you to another browser or computer, and clearing the site's data removes it. The ✕ beside a loaded font removes it; any role using it goes back to its default. **A font that is not fixed width** can take the pattern grid too. The canvas grid sizes its cell to the widest character it draws and centres every character in that cell, so the columns line up exactly as a tracker needs, the way a terminal does it — measured with a proportional font, the cell came out 7.08px against Share Tech Mono's 7.02px at the same settings. Fixed-width fonts are unaffected, to the pixel: the three bundled ones render byte-identically either way. The legacy DOM grid has no such cells, so a proportional font is ragged there. **“(default)” can mean something else here.** An install that carries its own fonts (below) may also say which of them each role starts with, and at what size. The first entry in each list then names that font and still reads `(default)`, and nothing is stored against your account until you pick something yourself — so the same build on another machine, or a copy of the source without those font files, is back to Oswald and Share Tech Mono with nothing to undo. **A font you may not pass on.** If you run Mediante from its source and you have a font you are licensed to use but not to redistribute — a commercial licence, a beta — put it in `fonts/local/` with a small `fonts.json` beside it and it joins the three lists like any other. The folder is ignored by git, so it stays on that one install; `fonts/local/README.md` describes the manifest, including the `noModify` flag for a licence that forbids the in-memory rewrite described above, and a `scale` multiplier. **Why a proportional font comes out smaller.** The fit above matches the default's average advance, but a proportional font would need to be drawn far larger to average a monospace font's width — large enough for its letters to leave the line box. The height limit wins, so such a font lands at about the default's letter height and noticeably narrower, and text-width-driven things (menu labels, list entries) get shorter: measured with a proportional font, 284 of 518 elements changed size, 251 of them in width only and none moved vertically, against 3 of 518 for Topaz. `scale` in the local manifest is the dial for this — it multiplies the drawn size without touching the line box, so the layout stays put, at the cost of letting tall accents rise toward the top of the row. ### Font size The fourth appearance axis, beside Interface scale, Density and the theme. Interface scale magnifies the whole app — spacing, borders, row heights and type together. **Font size changes only the letters**: it multiplies the size each font is fitted to and derives the line-height overrides from the result, so the rows stay exactly where they are. It applies at once, to whichever font each of the three roles is using, the defaults included — those are Google's faces, so Mediante re-declares them at the new size the first time you move off 100%. 75 to 150 percent, in steps of 5, remembered between sessions and multiplying with Interface scale and browser zoom. **The rows grow with it.** Raising the size raises the line box by the same factor, so the type sits in its row in exactly the proportion it does at 100%, and a row that did not trim its letters then cannot start trimming them now. Most rows get that for nothing, because their height comes from the font's own line height; the few that fix a box or a line-height in pixels — the transport readouts, the instrument rows, the channel headers — read a `--font-room` variable and scale with it. Measured over every trimming row on screen with a module loaded: **0 clipped at 100, 115, 125 and 150%**, for Share Tech Mono, Topaz, MicroKnight and a proportional font alike. Before the rows grew, the same test found 82 clipped rows at 115% and 121 at 150%. So the app gets taller as the type does — at 130% the sequence list shows 12 rows where it showed 15. That is the trade: the room has to come from somewhere, and taking it from the rows is what keeps the letters whole. **Dialog titles do not wrap, and windows widen to keep them.** A title bar holds the title, an optional grey note on the right and the close button, and the title grows with this setting. Surveyed across 35 dialogs with a module loaded: nothing wrapped at 100%, and at 135% two titles did — Custom Theme and Playing Sequence, short by 23px and 8px. A window's width is declared for the type it was drawn with, so above 100% a window may take the width its own content needs; at 100% it may not, and every window keeps its shipped width to the pixel (measured across 29 windows: none changed). At 135% four grew — Custom Theme 344 → 396px, Generate with CDP 430 → 489, Playing Sequence 340 → 352, Instrument List 720 → 737 — and no title wrapped and no note was shortened, at 135% or at 150%. The title also refuses to shrink and the note can give way with an ellipsis, but only as a backstop for a window that has nowhere left to grow. **The track names above the pattern grow with it too.** With the mixer strip collapsed, a track name gets whatever height the strip has left after its buttons, and that strip was a fixed 52px — so at a larger size the name's letters were cut top and bottom. The collapsed strip, and each of the heights at which the strip's faders, pan and readout appear, now move by exactly the name line's growth: 5px at 135%. Two fixes came with it that apply at every size, because they were wrong at 100% too: the name is centred in its box rather than sitting on its floor, and the collapsed strip is one pixel taller (53px), which is what a name with descenders — “Gypsy lead” lost 2.8px of its tails — needed to fit. **Density is unaffected.** Compact still only removes padding, and the room the type needs is added on top of whatever it leaves, so the two settings do not fight: Compact at 130% is the same type in the same rows as Normal at 130%, with less space around them. **What it still costs.** Text gets wider as well as taller, so at a narrow window a toolbar can wrap and push what is below it down: at 1600px wide nothing moved vertically even at 150%, at 800px the editor bar wrapped at 105%. Going below 100% costs nothing and moves nothing. **Browser support.** Chrome, Edge and Firefox honour all three descriptors. Safari applies the width match but not the line-height one, so rows there can come out a pixel different. --- ## Appendix: the editor bar The flags along the top of the pattern editor, where the behaviour is less obvious than the label. ### HOLD **Off** (default) — a tap plays the whole sample: it rings out, with no need to keep the key down. **On** — sustain-while-held. Releasing the key cuts the note, and holding a key in edit mode lays hold symbols (`-|-`) down the rows — at the playhead if the song is playing. ### KEEP — keep selection **Off** (default) — any plain cursor move clears the marked range, so cut, copy and Delete then act on the cursor cell. **On** — the range survives cursor moves until you re-mark it or Deselect. Mouse and keyboard behave the same either way. This one is Mediante's own: both reference programs hard-code the clear-on-move behaviour. ### PAGES — affect all command pages **On** (default) — Cut and Copy grab every command page. **Off** — Cut and Copy affect only the current command page, and pasting a single-page copy lands on the current page without disturbing the target's other pages. It governs Cut and Copy only. Delete always clears all pages. ### Marking a range with the mouse **Drag** marks a range. **Shift+click** extends the standing range to where you click, and **Shift+drag** carries on from that same anchor — the same gesture as Shift+Alt+arrows on the keyboard, and it uses the same anchor. With nothing marked, Shift+click spans from the cursor to the cell you click. **Alt+drag** restricts the range to the kind of field the drag started on: begin on the note or instrument columns for a notes-only range, on a command digit for a commands-only one. Extending with Shift keeps whichever mode the range already has, so a notes-only range does not silently widen to all fields. **Ctrl+click** (⌘+click on macOS) a track header adds that track to the selection; double-click a header to mark the track outright. ### CANVAS Draws the pattern on a `` overlay instead of the DOM grid. This is the renderer — faster on wide blocks, and what all new work targets. There is no longer a setting for the alternative: the DOM pool remains as the scaffolding the canvas attaches into and measures from, and `?canvasgrid=0` still shows it, but it is not something to choose between. --- ## See also - `docs/RESEARCH.md` — the MED format and playback semantics in full. - `docs/findings.md` — the reverse-engineering trail through the MicroPlayer and MED 1.4 disassembly. - `CREDITS.md` — the players, libraries and sources this behaviour was derived from.