rytm2_mods
rytm-mods-src's Analog Rytm MKII OS 1.73 mods, for OS 1.73 and 1.74
(MrScruff, published with permission), ported one by one to the elemod format so they
link with elekloader's core-ar2 and with each other,
instead of being patched into code caves at fixed addresses.
From v1.2, euclid-accents, velocity-humanise and voice-dac keep their settings in core-ar2 0.3's mod storage and build for OS 1.74 only; lfo-rnd uses core-ar2's storage too. From v1.3, lfo-rnd 1.2, voice-dac 1.3, euclid-accents 1.2 and velocity-humanise 1.2 reach the parameter pages through core-ar2 0.4's page tables, so they need core-ar2 0.4 (OS 1.74 only), and other mods with pages can link beside them. comp-preview 1.1 lists its preview through core 0.4's page-key lists for the same reason, so a mod can add a page behind COMP too. From v1.4, voice-dac 1.4 (MrScruff's rytm-mods-src 3a98e23: DAC FILTER's knobs follow TYP) needs core-ar2 0.6, whose page functions let a page's knobs follow the selected sound.
No firmware is included: no OS files, sections, dumps or built .syx/.elemod files. The
mods name firmware routines by address only (include/ar2-1.73.inc, include/ar2-1.74.inc), and their mod.json
sites carry the stock instruction bytes they replace, as elekloader's own mods do.
Layout
| path | what |
|---|---|
include/ar2-1.73.inc | every main-OS symbol of rytm-mods-src's re/symbols.toml as an absolute .equ (generated, 276 symbols) |
include/ar2-1.74.inc | the same symbols at their OS 1.74 addresses (generated by tools/port_os.py) |
shared/shared.s | rytm-mods-src's 0000-shared runtime, included into each mod that uses it |
<mod>/ | one elemod mod: mod.json, its source, README.md; os1.74/ holds its source for OS 1.74 (generated) |
tools/gen_symbols.py | regenerates the include from a rytm-mods-src checkout |
tools/port_mod.py | ports one rytm-mods mod mechanically (source and mod.json; the README is by hand) |
tools/port_os.py | makes every mod's OS 1.74 port from its 1.73 one: include/ar2-1.74.inc, <mod>/os1.74/, and the "1.74" entry of mod.json's ports |
tools/verify_port.py | checks a port against rytm-mods' own build, label by label (re-assembles the original, then compares disassembly with every address named) |
How a rytm-mods mod becomes an elemod mod
| rytm-mods | here |
|---|---|
a [[detour]] in registry/allocations.toml (addr, expect bytes, entry) | a sites entry: {"addr", "stock": <the whole instructions displaced>, "op": "jmp", "target": <entry>}; where the 6-byte jmp lands across an instruction the stub re-emits, the site takes it whole |
a [[patch]] with replace | a bytes site ("kind": "code" in .text), widened to whole instructions |
a [[patch]] with from_symbol, in a data table | a ptr site |
a [[patch]] with from_symbol, the address operand of a jsr/lea/move.l # | a keep2 site: the opcode word kept, the address the mod's (any other instruction: a bytes code site with an abs32 reloc) |
a stub at a fixed cave address, or split over several caves by .section | ordinary code, placed by the linker in the 128 KB DDR area: every section becomes .text → .run |
| state kept in a cave | stays with the code in .run (DDR is RAM) |
a gate on a claim | applied with the default build's params |
build/symbols.inc | include/ar2-1.73.inc |
build/shared.inc + mod 0000 | .include "../shared/shared.s" in the mod itself |
build params | fixed at the default build's values |
Rules that matter (elekloader docs/ADAPTING.md): only labels that a site or event names are
.globl, and they start with the mod's id; everything else stays local, so two mods never
export the same name. port_mod.py adds a prefixed global alias beside each such label, so the
stub's own references stay local and assemble as they did in rytm-mods (the hand-ported
slice-loop and env-reset-amp rename their entry label instead). A site covers whole stock instructions; the stub re-emits what it
displaced, exactly as in rytm-mods. Two mods may never patch the same bytes.
0000-shared is not a mod here: its ~234 bytes are assembled into each mod that calls it
(velocity-humanise, lfo-rnd and voice-dac), so every mod stands alone. euclid-accents,
velocity-humanise, lfo-rnd and voice-dac keep their settings in core-ar2's mod storage
(storage in their mod.json), not in the OS's saved records: euclid-accents 1.1,
velocity-humanise 1.1 and voice-dac 1.2 need core-ar2 0.3, which is OS 1.74 only, and lfo-rnd
1.1 needs 0.2 or later. velocity-humanise no longer needs euclid-accents.
The page routines: from core-ar2 0.4, core owns the entries of page_info, page_get_value,
param_apply_delta and param_value_text and asks each mod's core_pages and core_params entries
in turn (lfo-rnd, voice-dac, euclid-accents, velocity-humanise, by their order), before stock;
the four answer for different ids, so the order does not change what they do.
Mods that hook deeper in those routines keep their own sites: ctrl-all in param_apply_delta
(0x400378c2 on 1.74) and filter-hz in param_value_text. Before core 0.4 the four mods detoured
consecutive instructions at each entry, each rejoining at the next mod's jmp, as in rytm-mods,
which left no room for a fifth mod with pages.
OS 1.74
Elektron's 1.74 changes only the main OS, and that is 1.73's with code added in two places (Outbox 8's main-output routing): the code after 0x4005702a sits 0x6c further on, after 0x40123e2a 0x1cc, and the data after the code by other amounts. RAM from 0x402ce000 on and the SRAM did not move. Building for 1.74 needs an elekloader that knows the Rytm's 1.74 (its release in the profile, and core-ar2's 1.74 port).
Every mod's 1.73 source is its source of truth. tools/port_os.py (run with both stock files)
writes the 1.74 port from it: each firmware address in the include, the sources' code and the
sites, moved and checked (see the tool), and each site's stock bytes read from 1.74. It writes
nothing if any address fails its check. Run it again after changing a 1.73 source. elekloader
builds the port when given the 1.74 stock file, as <id>-<version>-os1.74.elemod.
Checked with both stock files: core-ar2 with all eleven ports, built for each, changes the same
instructions at the same (moved) sites, and the mods' code differs only in the firmware
addresses it names, each the moved one. A word in the mods' code that holds a 1.73 address
whose 1.74 address differs, left unmoved, is a port error: the include once kept two vtable
addresses tagged const (ctrl-all's set checks) at their 1.73 values, which made TRK + turn
reach only the selected track on 1.74.
Build
You need:
- an elekloader checkout at
mainwith core-ar2 0.6 (irpina/elekloader#179) and the Python 3 it asks for (v1.3 builds with core-ar2 0.4 or 0.5, from irpina/elekloader#155 on); - ColdFire binutils on
PATH(m68k-linux-gnu-asand-ld; on Debian or Ubuntu,apt install binutils-m68k-linux-gnu); - your own copy of Elektron's stock Analog Rytm MKII OS 1.74
.syx. It is never part of this repository or of anything built from it. (On OS 1.73, v1.0 still has all eleven mods; its own README says which elekloader it builds with. v1.2's lfo-rnd, voice-dac, euclid-accents, velocity-humanise and comp-preview do not link with core 0.4, so v1.3's are needed for it.)
EL=path/to/elekloader # the elekloader checkout
R2=path/to/rytm2_mods # this repository
STOCK=path/to/Analog-Rytm_MKII_OS1.74.syx # your stock OS file
OS=-os1.74
cd "$EL"
python3 -m elekloader.sdk.build mods/core-ar2 --stock "$STOCK"
MODS="filter-hz ctrl-all env-reset-amp slice-loop euclid-accents velocity-humanise lfo-rnd
comp-preview main-rms kit-ab voice-dac"
ARGS=""
for m in $MODS; do
python3 -m elekloader.sdk.build "$R2/$m" --stock "$STOCK"
V=$(python3 -c "import json; print(json.load(open('$R2/$m/mod.json'))['version'])")
ARGS="$ARGS --mod $R2/$m/out/$m-$V$OS.elemod"
done
python3 -m elekloader.patch --stock "$STOCK" --mod mods/core-ar2/out/core-0.6.elemod $ARGS \
--out rytm-mods.syx --version R2AL --lockfile rytm-mods.lock.json
Leave out any mod you don't want from MODS. Keep rytm-mods.lock.json and pass it again
for every later build: it says where each mod's settings live in your projects, kits and
sounds, so a build that adds or drops a mod still reads them. (A lost lockfile can be taken back
out of the last build with --lockfile-from rytm-mods.syx.) elekloader.patch
checks that the selected mods combine before it writes anything. --version is the
four characters the unit shows as its OS version. The result is your own firmware file:
keep it, and the out/ build folders, out of git and out of any upload.
Status
| rytm-mods-src | here | source status | port |
|---|---|---|---|
| 0000-shared | shared/shared.s | built | ported (included by velocity-humanise, lfo-rnd, voice-dac) |
| 0008-filter-hz | filter-hz/ | hardware-verified | matches label by label; confirmed on a unit |
| 0012-slice-loop | slice-loop/ | hardware-verified | matches label by label; confirmed on a unit |
| 0014-ctrl-all | ctrl-all/ | hardware-verified | 1.2, MrScruff's SYNTH knob fix (9831bdf): matches label by label, confirmed on a unit on OS 1.74; 1.0 and 1.1 confirmed on a unit |
| 0015-env-reset-amp | env-reset-amp/ | hardware-verified | bit probe left out; matches label by label; confirmed on a unit |
| 0002-euclid-accents | euclid-accents/ | hardware-verified | 1.2 on core-ar2 0.4's pages and 1.1, settings in core-ar2 0.3's storage: confirmed on a unit on OS 1.74; 1.0 matches label by label, confirmed on a unit |
| 0003-velocity-humanise | velocity-humanise/ | built (MANUAL: verified) | 1.2 on core-ar2 0.4's pages: confirmed on a unit on OS 1.74; 1.1, settings in core-ar2 0.3's storage, stands alone: confirmed on a unit on OS 1.74; 1.0 matches label by label, confirmed on a unit |
| 0004-lfo-rnd | lfo-rnd/ | built (MANUAL: verified) | 1.2 on core-ar2 0.4's pages: confirmed on a unit on OS 1.74; 1.1, settings in core-ar2's storage: confirmed on a unit on OS 1.74 with core 0.3; 1.0 matches label by label, confirmed on a unit |
| 0006-comp-preview | comp-preview/ | hardware-verified | 1.1, its preview in core-ar2 0.4's COMP list: confirmed on a unit on OS 1.74, also beside duck; 1.0 matches label by label, confirmed on a unit |
| 0009-main-rms | main-rms/ | built (MANUAL: display checked on the device) | matches label by label; confirmed on a unit |
| 0010-kit-ab | kit-ab/ | hardware-verified | matches label by label; confirmed on a unit |
| 0013-voice-dac | voice-dac/ | hardware-verified | default build only (no timing, prof, burn or dbg); 1.1, MrScruff's update (76ae299): matches label by label but for three names where sections meet; 1.4 (3a98e23, knobs follow TYP) on core-ar2 0.6: confirmed on a unit on OS 1.74; 1.3 on core-ar2 0.4's pages and 1.2, settings in core-ar2 0.3's storage: confirmed on a unit on OS 1.74; 1.0 confirmed on a unit |
Every mod on rytm-mods-src main is now ported. "Matches label by label": tools/verify_port.py
re-assembles rytm-mods' own code, checks it equals rytm-mods-src's default build ("T2"), then
compares each label's instructions with the port's, every address named by its label. For all
eleven, in the full build (core-ar2 with every port, as in Build above): every byte it changes
in the OS is inside one of the ports' sites, every one of those sites is also patched by
rytm-mods' default build (T2), and the bytes sites write T2's bytes exactly. rytm-mods'
diagnostic builds (its non-default params and the claims they gate) are not ported.
Licence
GPL-2.0, as rytm-mods-src (LICENSE). The mods are MrScruff's work; the elemod ports keep his
code and note every change.
