Digitone II mod
Build the selectable SINE synth and LOWPASS filter, both in C, with DigiHealth:
mise run build-c
Load out/c/firmware.syx. In FUNC+SYN, select SINE in SYN and
LOWPASS in FLTR. SINE uses synth ID 6; LOWPASS uses filter ID 8. The synth
and filter have separate memory and run through the stock track pipeline.
SINE has two SYN pages: LEV and TUNE on SYN1, OCT and FINE on SYN2, the
other slots empty. LEV sets the level, TUNE transposes in semitones around
64 and OCT in octaves (one per 16); FINE is not used yet. The knobs save with
the sound and answer FM TONE's MIDI CC/NRPN for their position (LEV is CC 40).
LOWPASS shows LOWPASS 4's FLTR page, and its FREQ, RESO and the stock filter
envelope (ENV, ATK/DEC/SUS/REL, pre-delay) move it. Its graph draws
LOWPASS's own response, from the curve callback in
examples/sharc-filter/machine.c. AMP, pan and mixer remain native.
This is experimental and timing is unqualified.
Edit examples/sharc-sine/sine.c or examples/sharc-filter/filter.c for
DSP changes. LOWPASS is a resonant 2-pole state-variable low-pass. Its
cutoff reads the stock cutoff like a MIDI note (FREQ 69 is 440 Hz, held to
20 Hz..16 kHz) and RESO sets its damping. Each voice has its own state,
reset when the adapter signals a new trigger or entry to the filter. Registration
uses mk2/machines.h and the mk2_* contribution tables. Only DN2 OS 1.12
is implemented.
Set up
git clone --recurse-submodules https://github.com/simonjohansson/digitone-2-mod-hack.git
cd digitone-2-mod-hack
Install mise and the ColdFire cross tools. Supply
your own stock Digitone II OS 1.12 file, SHA-256
7e5b8a733d4ab1fdf7e5d22757f7d8eddaa3cbf71d0858874a791844c02ceb35,
and a built DN2 DigiHealth module.
Set DN2_DIGIHEALTH to the module filename printed by that build.
Build the pinned open Selache compiler:
git clone https://github.com/js216/selache.git "$HOME/src/selache"
cd "$HOME/src/selache"
git switch --detach 2b26d3b75c53063575bc5c820fa0d38879335187
mise exec rust@1.98.1 -- cargo build --release --locked -p selcc -p selas -p seld
In this repository:
git submodule update --init
cp mise.local.example.toml mise.local.toml
# Set DN2_STOCK, SELACHE and DN2_DIGIHEALTH.
mise trust
mise install
mise run build-c
SELACHE points to the clean compiler checkout with its three tools built.
Python 3.12.13 and uv are managed by mise. The build needs no Python
packages; mise run test and mise run qualify fetch pinned ones with uv.
Build and test
mise run build-c compiles the current source, links it with the linker
script Elekloader's SDK writes, turns the executable into a machine with the
SDK (no address appears in this repository) and writes verified firmware with core, machines and
DigiHealth. A failed build removes stale firmware. Configuration, firmware
and build reports are ignored.
mise run test checks the toolchain pin, failed builds,
independent per-voice phase/retrigger state, and the qualification's
measurements, including LOWPASS's closed-form response against a
sample-by-sample model. The compiled filter itself is checked by
mise run qualify. See the voice contract
and filter interface.
Qualify
mise run qualify builds one candidate, labelled with the four
characters the unit shows, and runs the same cases in DigiKit's coupled
emulator and on the unit: SINE and FM TONE, each through MULTI-MODE and
LOWPASS on track 1. Each case selects the machines and reads them back
from RAM, opens the stock filter over MIDI, records notes 36 and 84,
and checks the FLTR page. Every note must sound at its pitch, and
LOWPASS at FREQ 72 must take note 84 down against note 36 by its response
at the synth's pitch, within -2/+3 dB, compared with the open stock
filter. LOWPASS's page must match LOWPASS 4's pixel for pixel outside the
response graph, and the graph must show exactly the dots its curve gives,
following FREQ and RESO edits and, while a locked step is held, the lock. Note 60 must follow LOWPASS's response at four
FREQ and two RESO settings within 1.5 dB. Over a held note, ENV 0 must
leave it steady, positive ENV must open then close it, negative ENV the
reverse, ATK must delay its peak and pre-delay its start. Tracks 1 and 2
played together must each follow their own FREQ, an LFO on track 1 must
leave track 2's note alone, and FLTR2 BASE and AMP VOL must change LOWPASS
as they change MULTI-MODE. Key tracking must move it by its response, and
LFO 1 on FREQ, RESO, ENV, pre-delay and ATK/DEC/SUS/REL must each change
the note. Parameter locks on step 9 must change that step and not step 1;
for now this runs in the emulator only, because DigiHealth's remote turns
do not edit a held trig's lock on the unit. Back on LOWPASS after
MULTI-MODE, the first note must match a repeat of it, and a note must
sound the same after the project is saved and loaded. Each of its controls must move with its knob
(pre-delay with FUNC+A), its CC and its NRPN, read back from RAM. The
filter and its values must survive SAVE PROJECT AS into slot 128 and
LOAD PROJECT. Nothing is checked by hand. The report, the recordings and
the firmware's SHA-256 go to out/qualify/.
The unit part flashes the candidate with elektroid-cli and confirms it
with DigiHealth's remote YES, so the installed firmware must include
DigiHealth. It runs only in the project recorded as the scratch project.
It saves only into slot 128, and only when that slot is empty or holds
the project an earlier run saved there. Afterwards it loads the scratch
project again, discarding the run's changes. Load a project you can
lose, then record it once:
mise run qualify -- --remember-scratch
Set DIGIHEALTH, DIGIKIT, DIGI_EMUD and DN2_EMU_READY in
mise.local.toml (see the example). Without a unit, the device part is
reported as skipped and the run exits with status 2, not as passed.
--targets emulator, --cases save,tracks, --no-build and --no-flash run
part of it; after a failure, rerun only the failed cases on the same candidate.
The C example plays one octave above integer MIDI notes 36..84, rounds fractional pitch down and writes zeros outside that range. Elekloader supplies the linker placement: each mod has its own 64 KiB, with 30 KiB of code including its guard and 31 KiB of data, and its own 4 KiB stack, of which the callback may declare 2,048 bytes. The adapter adds eight bytes to each call. Edited kernels need no instruction allowlist update.
Sync Elekloader
The submodule tracks the public Elekloader fork's main branch.
For local Elekloader development, keep the checkout's remote on your
filesystem with this uncommitted setting:
git config submodule.vendor/elekloader.url "$HOME/src/elekloader"
cd vendor/elekloader
git config remote.origin.url "$HOME/src/elekloader"
cd ../..
Then sync from the repository root:
git -c protocol.file.allow=always submodule update --remote vendor/elekloader
mise run test
mise run build-c
Commit the submodule pointer after checking the build.
