Mod

rytm2_mods

Loud84/100

MrScruff's rytm-mods for Analog Rytm MKII OS 1.73, ported to elemod (elekloader core-ar2)

rytm2_mods is a mod for the Elektron Analog Rytm MKII, by @Iced-Cream. Open source under GPL-2.0, latest release v1.4 (October 2026), 4 stars on GitHub.

forElektron Analog Rytm MKII·by@Iced-Cream

Unclaimed. Is this yours? Sign in with GitHub as @Iced-Cream to claim it.

Downloads
0
Stars
4
Fork
1
Commits
30
Last commit
Download v1.4

voice-dac-1.4-os1.74.elemod· 80 KB· +11 files

GPL-2.0· Iced-Cream/rytm2_mods·downloads come from the creator’s GitHub releases

25
Commits
5
Releases
25 commits and 5 releases in the last year
Activity · last 12 months

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

pathwhat
include/ar2-1.73.incevery main-OS symbol of rytm-mods-src's re/symbols.toml as an absolute .equ (generated, 276 symbols)
include/ar2-1.74.incthe same symbols at their OS 1.74 addresses (generated by tools/port_os.py)
shared/shared.srytm-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.pyregenerates the include from a rytm-mods-src checkout
tools/port_mod.pyports one rytm-mods mod mechanically (source and mod.json; the README is by hand)
tools/port_os.pymakes 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.pychecks 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-modshere
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 replacea bytes site ("kind": "code" in .text), widened to whole instructions
a [[patch]] with from_symbol, in a data tablea 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 .sectionordinary code, placed by the linker in the 128 KB DDR area: every section becomes .text → .run
state kept in a cavestays with the code in .run (DDR is RAM)
a gate on a claimapplied with the default build's params
build/symbols.incinclude/ar2-1.73.inc
build/shared.inc + mod 0000.include "../shared/shared.s" in the mod itself
build paramsfixed 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 main with 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-as and -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-srcheresource statusport
0000-sharedshared/shared.sbuiltported (included by velocity-humanise, lfo-rnd, voice-dac)
0008-filter-hzfilter-hz/hardware-verifiedmatches label by label; confirmed on a unit
0012-slice-loopslice-loop/hardware-verifiedmatches label by label; confirmed on a unit
0014-ctrl-allctrl-all/hardware-verified1.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-ampenv-reset-amp/hardware-verifiedbit probe left out; matches label by label; confirmed on a unit
0002-euclid-accentseuclid-accents/hardware-verified1.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-humanisevelocity-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-rndlfo-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-previewcomp-preview/hardware-verified1.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-rmsmain-rms/built (MANUAL: display checked on the device)matches label by label; confirmed on a unit
0010-kit-abkit-ab/hardware-verifiedmatches label by label; confirmed on a unit
0013-voice-dacvoice-dac/hardware-verifieddefault 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.