diff --git a/assets/versions.js b/assets/versions.js
index 258ca30..96d07e9 100644
--- a/assets/versions.js
+++ b/assets/versions.js
@@ -1,8 +1,16 @@
// Generated by tools/make_builder_data.py from tools/changelog.json and the mods' versions.
window.CDJ_VERSIONS = {
- "site": "1.9.0",
+ "site": "1.9.1",
"date": "2026-10-21",
"log": [
+ {
+ "version": "1.9.1",
+ "date": "2026-10-21",
+ "changes": [
+ "Phase meter 1.0.1: PHASE METER REVERSE is kept when the player is switched off and on (it came back as OFF).",
+ "LED animation 1.0.1: its setting no longer shares a slot with BEAT JUMP's, which could mix the two up after a restart."
+ ]
+ },
{
"version": "1.9.0",
"date": "2026-10-21",
@@ -186,12 +194,12 @@ window.CDJ_VERSIONS = {
],
"mods": {
"extended_bpm": "1.0.0",
- "phasemeter": "1.0.0",
+ "phasemeter": "1.0.1",
"advanced_waveforms": "1.2.0",
"beatjump": "1.0.1",
"cuecolours": "1.0.1",
"hotcuekeys": "1.3.0",
- "ledsaver": "1.0.0",
+ "ledsaver": "1.0.1",
"keynotation": "1.2.1",
"usbautoload": "0.1.0",
"quicksearch": "0.4.1",
@@ -408,6 +416,24 @@ window.CDJ_VERSIONS = {
"The big waveform 624 columns wide instead of 536, to the right edge; the extra columns are ahead of the playing position. The ZOOM/GRID wheel replaced by a grey ZOOM / GRID box over the BPM display."
]
}
+ ],
+ "phasemeter": [
+ {
+ "version": "1.0.1",
+ "date": "2026-10-21",
+ "changes": [
+ "PHASE METER REVERSE kept over a restart: the player stores the setting in one bit, so REVERSE came back as OFF. REVERSE is now stored as ON and remembered as REVERSE; OFF stays OFF, ON stays ON, also with MY SETTINGS from a USB."
+ ]
+ }
+ ],
+ "ledsaver": [
+ {
+ "version": "1.0.1",
+ "date": "2026-10-21",
+ "changes": [
+ "Its setting in a slot of its own (it shared BEAT JUMP's, so a restart could give one the other's value). LED ANIMATION is back at its default once after the update."
+ ]
+ }
]
}
};
diff --git a/builder.html b/builder.html
index aedcc9f..ed1fbf6 100644
--- a/builder.html
+++ b/builder.html
@@ -6,12 +6,12 @@
Pionaarr Mods Builder
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/builder/py/bundle.json b/builder/py/bundle.json
index 871139a..05d1867 100644
--- a/builder/py/bundle.json
+++ b/builder/py/bundle.json
@@ -1 +1 @@
-{"mods/patch_cdj900nxs.py":"#!/usr/bin/env python3\n# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"Apply arnoldasr's CDJ-900NXS 1.31 mods to the unpacked MAIN image.\n\n usage: patch_cdj900nxs.py [,...]\n patch_cdj900nxs.py --list\n\nThe mods come from https://github.com/arnoldasr/cdj900nxs, read as data from a\ncheckout of it (CDJ900NXS_MODS_SRC, default mods/cdj900nxs/upstream, which is\nnot committed: that repository carries no licence). Nothing of its Python is\nimported; what its build_firmware.py does is redone here:\n\n extended_bpm the BPM with hundredths: the stock tenths moved into a new\n small digit above, the lower one showing hundredths.\n phasemeter the two-line phase meter (orange master, blue this deck, a\n marker each) in place of the four boxes, chosen in UTILITY >\n PHASE METER (SLIP FLASHING relabelled; mods/phase-meter-\n utility.cdjmod.json).\n advanced_waveforms (was rgb_waveform; based on arnoldasr's, updated and\n maintained by us) the big waveform in rekordbox's RGB colours\n (1.0.1: also when its colour detail is a little short,\n cdj900nxs/wavefix.py), and the overview's colour table as a red-to-white\n gradient (mods/waveform-colour.cdjmod.json); with our\n UTILITY > WAVEFORM: BLUE / RGB / 3BAND (cdj900nxs/wavemodes.py,\n left out with public=True).\n\n The three are arnoldasr's code mod (patches/mods.json): hooks\n in the application redirected to SH-4 code appended after it\n (0x08FC1000), plus an asset blob at 0x08FC2D00 generated from\n the stock image (the resource table extended by four phase-meter\n images, then the RGB waveform palette and LUT). Upstream tested\n it as one build, and as one without the RGB waveform\n (patches/bpm-phase-meter.json); one of those two is built and\n checked against its hash first, then the hooks of a feature not\n chosen get their original bytes back. Their shared startup hook\n (0x08C071FC) installs the BPM digits and the phase markers: the\n call of the one not chosen is taken out there.\n beatjump ours, not arnoldasr's (cdj900nxs/beatjump.py): TRACK SEARCH\n jumps 1/2/4/8 beats along the grid, chosen in UTILITY > BEAT JUMP,\n made at once in the DSP's decoded audio (cdj900nxs/dspjump.py, a\n patch of the audio DSP's code),\n which replaces SCREEN SAVER. hotcuekeys' engine makes the jumps,\n so it needs hotcuekeys and cuecolours. Needs no upstream checkout.\n cuecolours ours (cdj900nxs/cuecolours.py): memory cues drawn in their\n rekordbox colour on the overview and the detail waveform, the\n rekordbox hot cues shown as lettered boxes.\n Needs no upstream checkout.\n hotcuekeys ours (cdj900nxs/hotcuekeys.py): UTILITY > HOT CUE KEYS (a new,\n 18th item); with it ON, QUANTIZE briefly pressed switches a hot\n cue key mode shown in a HOT KEYS box, held it is QUANTIZE. In\n the mode the top three BEAT DIVIDE keys play hot cues A-C (held: a\n gate), set one (empty key held while paused) or delete one\n (DELETE held). The hot cues come from cuecolours.\n ledsaver ours (cdj900nxs/ledsaver.py): UTILITY > LED ANIMATION (one more\n item, after the others); with it ON, while the screen saver dims\n the display the button LEDs and the jog's rings play a loop.\n keynotation ours (cdj900nxs/keynotation.py): UTILITY > KEY NOTATION (one\n more item): every track's key in Camelot, Open Key or classic\n notation, whatever rekordbox exported.\n usbautoload ours (cdj900nxs/usbautoload.py): UTILITY > USB AUTO LOAD (one\n more item): a stick's MY SETTINGS are loaded when the player finds\n them, without waiting for MENU.\n quicksearch ours (cdj900nxs/quicksearch.py): BROWSE held opens SEARCH,\n BROWSE twice the list the playing track came from.\n (phasemeter, in unreleased builds, also gets ours from cdj900nxs/phasereverse.py:\n UTILITY > PHASE METER OFF / ON / REVERSE, and the beat countdown\n moved clear of the line meter; `phasereverse` is an old name for it.)\n wide_waveform ours (cdj900nxs/widewave.py): the big waveform runs on to the\n right edge (624 columns, the extra ones ahead of the position); the\n ZOOM/GRID wheel gives way to a small grey ZOOM / GRID box.\n credit ours (cdj900nxs/credit.py): the boot screen says \"Pionaarr Mods\n 1.0\" where it said \"Ver 1.31\".\n\nEvery patch is applied only where the image holds the expected original\nbytes, and arnoldasr's code is first built as one of the builds he tested\nand checked against its hash.\n\nOur lzss_decode.py runs 54 bytes past the end of the packed stream, so\nmain_unpacked.bin is the 1.31 application plus a tail the firmware never\nreads; the application is cut back to its own length first.\n\"\"\"\nimport hashlib\nimport json\nimport os\nimport struct\nimport sys\n\nHERE = os.path.dirname(os.path.abspath(__file__))\nsys.path.insert(0, os.path.join(HERE, 'cdj900nxs'))\nimport beatjump # noqa: E402\nimport credit # noqa: E402\nimport dspjump # noqa: E402\nimport cuecolours # noqa: E402\nimport hotcuekeys # noqa: E402\nimport keynotation # noqa: E402\nimport ledsaver # noqa: E402\nimport usbautoload # noqa: E402\nimport quicksearch # noqa: E402\nimport phasereverse # noqa: E402\nimport wavemodes # noqa: E402\nimport wavefix # noqa: E402\nimport cuenames # noqa: E402\nimport endwarn # noqa: E402\nimport played # noqa: E402\nimport widewave # noqa: E402\n\nBASE = 0x08000000\nAPP_SIZE = 16519056\nAPP_SHA256 = '1ac3e30cde292865413950fe159663b3958e85939ed0a5d0122f796809a5f8ef'\n\nMODS = {\n 'extended_bpm': 'BPM with hundredths: tenths as a small digit above (arnoldasr)',\n 'phasemeter': 'two-line phase meter, UTILITY > PHASE METER (SLIP FLASHING) chooses it (arnoldasr); '\n 'OFF / ON / REVERSE and the beat countdown moved clear of it (ours: cdj900nxs/phasereverse.py)',\n 'advanced_waveforms': 'big waveform in RGB colours, overview as a red-to-white gradient (arnoldasr); '\n 'UTILITY > WAVEFORM: BLUE / RGB / 3BAND (ours: cdj900nxs/wavemodes.py)',\n 'beatjump': 'TRACK SEARCH jumps 1/2/4/8 beats, UTILITY > BEAT JUMP (ours: cdj900nxs/beatjump.py)',\n 'cuecolours': 'memory cues in their rekordbox colours, hot cues shown (ours: cdj900nxs/cuecolours.py)',\n 'hotcuekeys': 'hot cues A-C on the BEAT DIVIDE keys: play, gate, set, delete; UTILITY > HOT CUE KEYS, '\n 'QUANTIZE short = hot key mode (ours: cdj900nxs/hotcuekeys.py)',\n 'ledsaver': 'the button and jog LEDs play an animation while the screen saver dims the display; '\n 'UTILITY > LED ANIMATION (ours: cdj900nxs/ledsaver.py)',\n 'keynotation': 'every key in one notation (Camelot / Open Key / classic), UTILITY > KEY NOTATION '\n '(ours: cdj900nxs/keynotation.py)',\n 'usbautoload': 'MY SETTINGS on a USB stick loaded at once, UTILITY > USB AUTO LOAD '\n '(ours: cdj900nxs/usbautoload.py)',\n 'quicksearch': 'BROWSE held: SEARCH; BROWSE twice: the playing track in its list '\n '(ours: cdj900nxs/quicksearch.py)',\n 'cuenames': 'the rekordbox comment of the next cue (\"Drop\") in the title bar, in its colour '\n '(ours: cdj900nxs/cuenames.py, needs cuecolours)',\n 'endwarn': 'the playing-address bar blinks red in the last 30 seconds (ours: cdj900nxs/endwarn.py)',\n 'played': 'BROWSE ticks the tracks loaded on this player since it was switched on (ours: cdj900nxs/played.py)',\n 'wide_waveform': 'the big waveform to the right edge, the ZOOM/GRID wheel replaced by a small ZOOM / GRID box '\n '(ours: cdj900nxs/widewave.py)',\n 'credit': 'boot screen says \"Pionaarr Mods 1.0\" instead of \"Ver 1.31\" (ours: cdj900nxs/credit.py)',\n}\n# every mod's version (Semantic Versioning); ours from their modules, arnoldasr's\n# split-up mods as packaged here\nVERSIONS = {\n 'extended_bpm': '1.0.0', 'phasemeter': '1.0.0', 'advanced_waveforms': wavemodes.VERSION, # with its WAVEFORM modes (wavefix: wavefix.VERSION)\n 'beatjump': beatjump.VERSION, 'cuecolours': cuecolours.VERSION, 'hotcuekeys': hotcuekeys.VERSION,\n 'ledsaver': ledsaver.VERSION, 'keynotation': keynotation.VERSION, 'usbautoload': usbautoload.VERSION,\n 'quicksearch': quicksearch.VERSION,\n 'cuenames': cuenames.VERSION, 'endwarn': endwarn.VERSION, 'played': played.VERSION,\n 'wide_waveform': widewave.VERSION, 'credit': credit.VERSION,\n}\nMANIFESTS = {\n 'phasemeter': 'mods/phase-meter-utility.cdjmod.json',\n 'advanced_waveforms': 'mods/waveform-colour.cdjmod.json',\n}\n# arnoldasr's code mod split up: each feature's hooks in the application\nUPSTREAM = ('extended_bpm', 'phasemeter', 'advanced_waveforms')\nHOOKS = {\n 'extended_bpm': (0x08BD3E50, 0x08BD3ED8, 0x08BD3A48, 0x08BCBA60, 0x08BCBCFC),\n 'phasemeter': (0x08B2E2EE, 0x08C6A6CC, 0x08C6CCD0),\n 'advanced_waveforms': (0x089A061C, 0x08BC6830, 0x08BC6A7C, 0x08BCFD72, 0x08BCFF50),\n}\nSHARED_HOOKS = (0x08C071FC, 0x08B27BC8) # startup binding, arena reset: BPM and phase\nBPM_INSTALL_CALL = 0x08FC2444 # startup_bind's jsr of the BPM digits' installer\nPHASE_MARKERS_CALL = 0x08FC2450 # ... and of the phase markers' (startup_markers)\n\n\ndef source_dir():\n return os.environ.get('CDJ900NXS_MODS_SRC') or os.path.join(HERE, 'cdj900nxs', 'upstream')\n\n\ndef read_json(rel):\n path = os.path.join(source_dir(), rel)\n if not os.path.isfile(path):\n raise SystemExit('patch_cdj900nxs: %s missing -- check out '\n 'https://github.com/arnoldasr/cdj900nxs into %s'\n % (path, source_dir()))\n with open(path, encoding='utf-8') as f:\n return json.load(f)\n\n\ndef sha(data):\n return hashlib.sha256(data).hexdigest()\n\n\n# The asset blob, as tools/phase_assets.py builds it (same constants).\nTABLE, DESCRIPTOR, ORIGINAL_COUNT, TEMPLATE = 0x08100000, 0x2C, 0x2A1, 0x1C8\nDATA_BASE = 0x08FC2D00\nWIDTH, HEIGHT, MARKER_WIDTH, BAR = 200, 24, 8, 5\nKEY = 0xF81F\nORANGE_LINE, ORANGE_MARK = (120, 65, 20), (255, 140, 0)\nBLUE_LINE, BLUE_MARK = (40, 90, 120), (60, 170, 255)\nWAVE_COLOURS = [\n 448, 457, 47, 458, 449, 111, 495, 466, 175, 467, 487, 239, 303, 479, 367, 459, 456, 431,\n 468, 465, 475, 476, 474, 39, 477, 31, 471, 46, 469, 45, 23, 460, 109, 484, 110, 485, 103,\n 478, 483, 493, 494, 486, 95, 15, 450, 167, 492, 173, 174, 231, 115, 470, 238, 44, 237, 159,\n 295, 87, 108, 302, 51, 301, 491, 482, 359, 223, 366, 365, 236, 300, 179, 423, 461, 287, 430,\n 429, 172, 364, 473, 428, 351, 114, 151, 463, 299, 363, 415, 215, 427, 178, 490, 50, 279, 235,\n 79, 462, 451, 343, 511, 298, 362, 400, 52, 116, 426, 234, 407, 7, 143, 180, 207, 113, 233,\n 243, 481, 57, 271, 452, 417, 177]\nWHITENESS_COLOURS = [(150, 20, 20), (220, 40, 30), (255, 110, 20), (255, 190, 30), (170, 230, 60),\n (40, 210, 170), (60, 160, 255), (225, 240, 255)]\n\n\ndef rgb565(rgb):\n r, g, b = rgb\n return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)\n\n\ndef line_image(colour):\n px = [KEY] * (WIDTH * HEIGHT)\n for x in range(2, WIDTH - 2):\n px[12 * WIDTH + x] = rgb565(colour)\n for i in range(5):\n x = 2 + i * (WIDTH - 5) // 4\n for y in range(7, 18):\n px[y * WIDTH + x] = rgb565(colour)\n return px\n\n\ndef marker_image(colour):\n return [rgb565(colour) if 3 <= y <= 21 and x < BAR else KEY\n for y in range(HEIGHT) for x in range(MARKER_WIDTH)]\n\n\ndef rgb9(code):\n return tuple(((code >> shift) & 7) * 255 // 7 for shift in (6, 3, 0))\n\n\ndef wave_palette():\n colours = WHITENESS_COLOURS + [rgb9(c) for c in WAVE_COLOURS]\n return struct.pack('<128H', *[rgb565(c) for c in colours])\n\n\ndef wave_lut():\n def nearest(code):\n r, g, b = (code >> 6) & 7, (code >> 3) & 7, code & 7\n return min(range(len(WAVE_COLOURS)), key=lambda i: (\n (((WAVE_COLOURS[i] >> 6) & 7) - r) ** 2 + (((WAVE_COLOURS[i] >> 3) & 7) - g) ** 2\n + ((WAVE_COLOURS[i] & 7) - b) ** 2, i))\n return bytes(8 + nearest(code) for code in range(512))\n\n\ndef assets(app):\n images = [(WIDTH, line_image(ORANGE_LINE)), (WIDTH, line_image(BLUE_LINE)),\n (MARKER_WIDTH, marker_image(ORANGE_MARK)), (MARKER_WIDTH, marker_image(BLUE_MARK))]\n original = app[TABLE - BASE:TABLE - BASE + ORIGINAL_COUNT * DESCRIPTOR]\n template = original[TEMPLATE * DESCRIPTOR:(TEMPLATE + 1) * DESCRIPTOR]\n count = ORIGINAL_COUNT + len(images)\n table_bytes = count * DESCRIPTOR\n pixels_base = DATA_BASE + ((table_bytes + 31) & ~31)\n table, pixels = bytearray(original), bytearray()\n for i, (width, data) in enumerate(images):\n entry = bytearray(template)\n struct.pack_into(' mov #0,r0 (not installed)\n if 'phasemeter' not in names:\n call(PHASE_MARKERS_CALL, 0x410B, 0x0009) # jsr @r1 -> nop\n return bytes(out)\n\n\ndef apply_manifest(app, stock, name):\n doc = read_json(MANIFESTS[name])\n if doc['target']['application_sha256'] != APP_SHA256:\n raise SystemExit('patch_cdj900nxs: %s targets another application' % name)\n out = bytearray(app)\n for p in doc['patches']:\n off = int(p['offset'], 16) if isinstance(p['offset'], str) else p['offset']\n before, after = bytes.fromhex(p['expect']), bytes.fromhex(p['replace'])\n if len(before) != len(after) or off + len(before) > APP_SIZE:\n raise SystemExit('patch_cdj900nxs: %s: bad patch at 0x%x' % (name, off))\n if stock[off:off + len(before)] != before or app[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: %s: bytes at 0x%x are not the expected original'\n % (name, off))\n out[off:off + len(before)] = after\n return bytes(out)\n\n\n# old names still taken\nALIASES = {'rgb_waveform': 'advanced_waveforms', 'waveform3band': 'advanced_waveforms',\n 'phasereverse': 'phasemeter'}\n\n\ndef check_names(names):\n \"\"\"The names with old ones taken, every mod known and what each needs.\"\"\"\n names = list(dict.fromkeys(ALIASES.get(n, n) for n in names))\n unknown = [n for n in names if n not in MODS]\n if unknown:\n raise SystemExit('patch_cdj900nxs: unknown mod(s) %s; known: %s'\n % (', '.join(unknown), ', '.join(MODS)))\n if 'cuenames' in names and 'cuecolours' not in names:\n raise SystemExit('patch_cdj900nxs: cuenames needs cuecolours (its cue reader brings the comments)')\n if 'beatjump' in names and not {'cuecolours', 'hotcuekeys'} <= set(names):\n raise SystemExit('patch_cdj900nxs: beatjump needs cuecolours and hotcuekeys '\n '(the jumps are made by hotcuekeys)')\n return names\n\n\ndef patch_upstream(stock, names):\n \"\"\"Stage 1: arnoldasr's mods (his code and assets, his manifests, and\n advanced_waveforms' fix that sits on his code). The website's builder\n has this stage as data (tools/make_builder_data.py).\"\"\"\n upstream = [n for n in UPSTREAM if n in names]\n app = apply_code(stock, upstream) if upstream else stock\n for name in ('phasemeter', 'advanced_waveforms'):\n if name in names:\n app = apply_manifest(app, stock, name)\n if 'advanced_waveforms' in names:\n app = wavefix.patch(app, stock) # 1.0.1: a slightly short PWV5 is taken too\n return app\n\n\ndef patch_ours(app, stock, names):\n \"\"\"Stage 2: our mods, on the application stage 1 made (the website's\n builder runs this function itself, in the browser).\"\"\"\n if 'beatjump' in names:\n app = beatjump.patch(app, stock)\n app = dspjump.patch(app, stock) # the jumps in the DSP's decoded audio\n if 'cuecolours' in names:\n app = cuecolours.patch(app, stock)\n if 'hotcuekeys' in names:\n app = hotcuekeys.patch(app, stock)\n if 'ledsaver' in names:\n app = ledsaver.patch(app, stock)\n if 'keynotation' in names:\n app = keynotation.patch(app, stock)\n if 'cuenames' in names:\n app = cuenames.patch(app, stock) # after keynotation: chains its text draw\n if 'played' in names:\n app = played.patch(app, stock) # after keynotation and cuenames: chains their text draw\n if 'endwarn' in names:\n app = endwarn.patch(app, stock)\n if 'usbautoload' in names:\n app = usbautoload.patch(app, stock)\n if 'quicksearch' in names:\n app = quicksearch.patch(app, stock)\n if 'phasemeter' in names:\n app = phasereverse.patch(app, stock) # PHASE METER: OFF / ON / REVERSE\n app = phasereverse.shift_bars(app, stock) # the beat countdown clear of the line meter\n if 'advanced_waveforms' in names:\n app = wavemodes.patch(app, stock) # UTILITY > WAVEFORM: BLUE / RGB / 3BAND\n if 'wide_waveform' in names:\n app = widewave.patch(app, stock) # after advanced_waveforms (its centre-row literal) and hotcuekeys (the draw method)\n if 'credit' in names:\n app = credit.patch(app, stock)\n return app\n\n\ndef patch(data, names, public=False):\n \"\"\"The patched application for the mods named, in a fixed order: stage 1\n (arnoldasr's) and stage 2 (ours). Everything is released now; `public`\n is still taken and changes nothing.\"\"\"\n names = check_names(names)\n stock = data[:APP_SIZE]\n if sha(stock) != APP_SHA256:\n raise SystemExit('patch_cdj900nxs: not the CDJ-900NXS 1.31 application')\n return patch_ours(patch_upstream(stock, names), stock, names)\n\n\ndef main():\n args = sys.argv[1:]\n if args == ['--list']:\n for name, what in MODS.items():\n print('%-13s %-6s %s' % (name, VERSIONS[name], what))\n return\n if len(args) != 3:\n raise SystemExit(__doc__)\n names = [n for n in args[2].replace(' ', ',').split(',') if n]\n with open(args[0], 'rb') as f:\n out = patch(f.read(), names)\n with open(args[1], 'wb') as f:\n f.write(out)\n print('patch_cdj900nxs: %s (%s), %d bytes, sha256 %s'\n % (args[1], ','.join(names), len(out), sha(out)))\n\n\nif __name__ == '__main__':\n main()\n","mods/cdj900nxs/beatjump.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"BEAT JUMP for the CDJ-900NXS 1.31: TRACK SEARCH jumps N beats along the\ntrack's beat grid, N set in UTILITY > BEAT JUMP (OFF/1/2/4/8, default 4).\n\nOur own mod, not arnoldasr's; mods/patch_cdj900nxs.py applies it as `beatjump`.\nEverything here was found by tracing the firmware in the emulator:\n\n * The UI's poller calls the panel decoder 0x08B63278 (through its literal\n at 0x08B622A8), which turns report byte 0x12 bits 0x04 / 0x08 of the\n frame at 0x0B2E8280 into the key bits TRACK - (0x0B2E82C6 bit 0x01) and\n TRACK + (0x0B2E82C5 bit 0x80); later readers (0x08B630D0) turn those\n into the track search. The literal now points at bj_poll, which runs the\n decoder and then, with BEAT JUMP on and a beat grid loaded, takes both\n key bits out again: the track search never starts, so neither the UI nor\n the player changes track, and a press is a jump instead. (Clearing the\n report byte before the decoder instead lost a race with the panel task\n refilling the frame.)\n * The jump is made by hotcuekeys' engine (hk_engine's he_bj, which is\n why beatjump needs hotcuekeys and cuecolours): bj_poll writes the\n target in 1/150 s with bit 31 set to hotcuekeys.BJ_REQ, the tick of\n the press to BJ_TICK and the jump's length in ms to BJ_DELTA, unless a\n jump is still waiting. The engine has the DSP move its playing head by\n that many 1/75 s slots inside the audio it has already decoded\n (dspjump.py, a patch of the audio DSP): at once, gapless, the CUE point\n stays, playing, paused or at cue standby (standing, the UI is left\n paused, so PLAY plays on from there). Only where the DSP has not that\n much audio decoded does it refuse: playing, the engine then loads the\n target into the beat jumps' own DSP cue slot (hotcuekeys.BJ_SLOT) the\n old way, moved on by the time such a load takes so the deck comes back\n in phase; standing, the press is dropped (the old way there, a memory\n CALL, left the UI waiting for good after the third or fourth).\n [Since: no fallback at all; the slot load left the deck standing. A\n press the DSP cannot jump now (it keeps dspjump.MARGIN decoded ahead, so\n the time needed grows with the setting) or made while a jump still\n waits is ignored: nothing is put off, the next press counts again.]\n * The setting is kept in the player's settings journal (hotcuekeys'\n hk_save2/hk_load2 through HK_API, id SAVE_ID): the first poll takes the\n last saved value (else DEFAULT, 4 beats) into the setting's word, a\n change is written back. The firmware itself kept SCREEN SAVER's ON/OFF\n only, so BEAT JUMP came back as 1 after a restart.\n * The grid is read as arnoldasr's phase meter reads it, from the player\n context 0x099445DC: track *(ctx+0x20), X = *(track+0xC4), grid\n *(X+0x104), timestamp adjustment *(X+0x10C); 0x08A27D4C(grid, adj,\n pos_ms) gives 1 + the index of the beat at or before pos_ms, 0x08A0EFB6\n (record, adj) a beat's time in ms. The position in ms is ctx+0x2CC.\n * A jump keeps the position's offset within its beat: target = pos +\n t[i +/- N] - t[i]. Before the first beat (a track just loaded stands at\n its CUE, often a hair before beat 1) i is beat 0. A press whose target\n would leave the grid does nothing. With the setting OFF, or no grid, TRACK SEARCH is untouched.\n\nUTILITY > SCREEN SAVER (item 14, a word at 0x0A4DD270 + 0x1760, values\nOFF/ON, factory default ON = 1) becomes BEAT JUMP: its label in all 19\nlanguages, five values OFF/1/2/4/8 (the existing \"OFF\" and MIDI CHANNEL's digit\nstrings), so the old ON is now \"1\". The screen saver itself no longer reads\nthe setting: its getter literal (0x08BB7E60) points at a firmware tail that\nreturns 1, so it stays on as it was by default.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.0.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nBJ_BASE = 0x08FCF400 # after arnoldasr's code mod (ends 0x08FCF260)\n\nPLAYER_CTX = 0x099445DC\nDECODER_LIT = 0x08B622A8 # the UI poller's literal of the panel decoder\nDECODER = 0x08B63278\nKEYS_TRACK = 0x0B2E82C5 # decoded keys: TRACK + is bit 0x80 here, TRACK - 0x01 of the next byte\nSEND = 0x08B3B666 # the UI's player-message builder\nGET_SETTING = 0x08AFD9E0 # SCREEN SAVER getter: *(0x0A4DD270 + 0x1760)\nGRID_LOOKUP = 0x08A27D4C\nGRID_TIME = 0x08A0EFB6\nFPSCR_MASK = 0xFFE7FFFF # what the firmware does to fpscr after every call\n\nSAVER_ITEM = 0x08C6CBB4 # UTILITY item 14 (copied to RAM at start-up)\nSAVER_CONSUMER_LIT = 0x08BB7E60 # the screen saver's literal of the getter\nRETURN_ONE = 0x08BDA1B0 # 'rts; mov #1,r0' at the end of a firmware function\nVALUE_STRINGS = [0xA80D013C, 0xA80D0144, 0xA80D0148, 0xA80D0150, 0xA80D0160] # OFF 1 2 4 8\nLABEL_OLD = 'SCREEN SAVER\\0'.encode('utf-16-le')\nLABEL_NEW = 'BEAT JUMP'.encode('utf-16-le').ljust(len(LABEL_OLD), b'\\0')\nLABEL_COPIES = 19\n\nVALUE_LIST = BJ_BASE + 0x400 # the five value-string pointers, after the code\nPREV_BITS = BJ_BASE + 0x3FC # last frame's TRACK bits, for press edges\nLAST_SET = BJ_BASE + 0x3F8 # word: the setting last seen (-1 at start: taken from the journal then)\nSETTING = 0x0A4DD270 + 0x1760 # the setting's word (the SCREEN SAVER's)\nSAVE_ID = 0x83E2 # ours in the settings journal (hotcuekeys' is 0x83E1)\nDEFAULT = 3 # 4 beats\nBJ_DELTA = 0x08FCF818 # word: the last jump's length in ms (signed), for hotcuekeys' DSP jump\nBJ_POSV = hotcuekeys.BJ_POSV # word: the position (ms) to reckon from, if not 0\nBJ_PEND = hotcuekeys.BJ_PEND # word: presses counted while hotcuekeys' CALL had no grid\n\n\nclass Asm:\n \"\"\"Just enough SH-4 to write bj_poll: two passes over a list of\n instructions, labels, and a literal pool of .long/.word constants.\"\"\"\n\n def __init__(self, base):\n self.base, self.items, self.labels = base, [], {}\n\n def label(self, name):\n self.items.append(('label', name))\n\n def op(self, fn):\n self.items.append(('op', fn))\n\n def long(self, name, value):\n self.items.append(('align4', None))\n self.label(name)\n self.items.append(('long', value))\n\n def word(self, name, value):\n self.label(name)\n self.items.append(('word', value))\n\n def assemble(self):\n for final in (False, True):\n pc, out = self.base, bytearray()\n for kind, arg in self.items:\n if kind == 'label':\n self.labels[arg] = pc\n continue\n if kind == 'align4':\n while pc % 4:\n out += struct.pack('> 36 = q / 20\n a.long('L_req', hotcuekeys.BJ_REQ)\n a.long('L_tick', hotcuekeys.TICK)\n a.long('L_bit31', 0x80000000)\n a.long('L_delta', BJ_DELTA)\n a.long('L_posv', BJ_POSV)\n a.long('L_pend', BJ_PEND)\n a.long('L_third', 0xAAAAAAAB) # q * this >> 33 = q / 3\n a.long('L_last', LAST_SET)\n a.long('L_setting', SETTING)\n a.long('L_bjid', SAVE_ID)\n a.long('L_bjid16', SAVE_ID << 16)\n a.long('L_api', hotcuekeys.HK_API)\n code = a.assemble()\n return code, None\n\n\ndef patch(app, stock):\n \"\"\"The application with BEAT JUMP added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n code, _ = routine()\n assert len(code) <= LAST_SET - BJ_BASE\n out = bytearray(app)\n end = VALUE_LIST + 4 * len(VALUE_STRINGS) - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[BJ_BASE - BASE:end]):\n raise SystemExit('patch_cdj900nxs: beatjump: its area 0x%08x.. is not free' % BJ_BASE)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + len(before)] != before or out[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: beatjump: bytes at 0x%08x are not the expected original'\n % addr)\n out[off:off + len(after)] = after\n\n out[BJ_BASE - BASE:BJ_BASE - BASE + len(code)] = code\n out[LAST_SET - BASE:LAST_SET - BASE + 4] = b'\\xff\\xff\\xff\\xff'\n out[VALUE_LIST - BASE:end] = b''.join(struct.pack('= 0:\n put(BASE + at, LABEL_OLD, LABEL_NEW)\n found += 1\n at = stock.find(LABEL_OLD, at + 2)\n if found != LABEL_COPIES:\n raise SystemExit('patch_cdj900nxs: beatjump: %d SCREEN SAVER labels, expected %d'\n % (found, LABEL_COPIES))\n return bytes(out)\n","mods/cdj900nxs/c6asm.py":"#!/usr/bin/env python3\n\"\"\"casm.py: a minimal TI C674x assembler for our DSP patches (32-bit, serial\ninstructions only: every p bit 0, no compact forms). Every encoding below is\nchecked against words of the CDJ-900NXS DSP image (selftest()).\n\nRegisters: 'a0'..'a31', 'b0'..'b31'. Predicates: '[b0]', '[!a1]' prefix\n(A1, A2, B0, B1, B2 only). Delay slots are the caller's business: loads need\n4 slots before their result is used, branches 5.\n\n asm = Asm(base)\n asm.label('x'); asm.op('mvk', 0x1234, 'a3'); asm.op('b', 'x') ...\n words = asm.assemble()\n\"\"\"\nimport struct\n\nCREG = {'b0': 1, 'b1': 2, 'b2': 3, 'a1': 4, 'a2': 5, 'a0': 6}\n\n\ndef reg(r):\n side = 1 if r[0] == 'b' else 0\n return side, int(r[1:])\n\n\ndef pred(p):\n if not p:\n return 0\n neg = p.startswith('!')\n return CREG[p.lstrip('!')] << 29 | (1 << 28 if neg else 0)\n\n\n# .L unit 3-operand ops (op field bits 11-5): (si,xsi) form and (s5,xsi) form\nL_OPS = {\n 'add': (0x03, 0x02), 'sub': (0x07, 0x06), 'cmpeq': (0x53, 0x52),\n 'cmpgt': (0x47, 0x46), 'cmplt': (0x57, 0x56), 'cmpgtu': (0x4F, 0x4E),\n 'cmpltu': (0x5F, 0x5E), 'or': (0x7F, 0x7E), 'and': (0x7B, 0x7A),\n}\n# .D load/store op field (bits 6-4)\nD_OPS = {'ldhu': 0, 'ldbu': 1, 'ldb': 2, 'stb': 3, 'ldh': 4, 'sth': 5, 'ldw': 6, 'stw': 7}\nD_SCALE = {'ldhu': 2, 'ldbu': 1, 'ldb': 1, 'stb': 1, 'ldh': 2, 'sth': 2, 'ldw': 4, 'stw': 4}\n\n\ndef enc_l(op, src1, src2, dst, p=''):\n \"\"\"op src1,src2,dst on .L (unit side = dst side; src2 may cross).\"\"\"\n ds, dn = reg(dst)\n s2s, s2n = reg(src2)\n x = 1 if s2s != ds else 0\n if isinstance(src1, int):\n assert -16 <= src1 < 16\n code = L_OPS[op][1]\n s1 = src1 & 31\n else:\n s1s, s1 = reg(src1)\n assert s1s == ds, 'src1 must be on the unit side'\n code = L_OPS[op][0]\n return pred(p) | dn << 23 | s2n << 18 | s1 << 13 | x << 12 | code << 5 | 0b110 << 2 | ds << 1\n\n\ndef enc_mvk(hi, val, dst, p=''):\n ds, dn = reg(dst)\n cst = (val >> 16 if hi else val) & 0xFFFF\n return pred(p) | dn << 23 | cst << 7 | (0b11010 if hi else 0b01010) << 2 | ds << 1\n\n\ndef enc_d(op, data, base, off, p=''):\n \"\"\"ld/st *+base(off) (byte offset, scaled by the access size).\"\"\"\n ss, sn = reg(data)\n bs, bn = reg(base)\n sc = D_SCALE[op]\n assert off % sc == 0 and 0 <= off // sc < 32, (op, off)\n return pred(p) | sn << 23 | bn << 18 | (off // sc) << 13 | 0b0001 << 9 | bs << 7 | D_OPS[op] << 4 | 0b01 << 2 | ss << 1\n\n\ndef enc_b_imm(at, target, side=0, p=''):\n disp = (target - (at & ~31)) // 4\n assert -(1 << 20) <= disp < (1 << 20)\n return pred(p) | (disp & 0x1FFFFF) << 7 | 0b00100 << 2 | side << 1\n\n\ndef enc_b_reg(r, p=''):\n rs, rn = reg(r)\n return pred(p) | rn << 18 | (1 if rs == 0 else 0) << 12 | 0x362 # .S2 (X for an A register)\n\n\ndef enc_nop(n):\n assert 1 <= n <= 9\n return (n - 1) << 13\n\n\nDINT, RINT = 0x10004000, 0x10006000\n\n\ndef enc_shl(src, cst, dst, p=''):\n \"\"\"shl .S src,ucst5,dst\"\"\"\n ds, dn = reg(dst)\n ss, sn = reg(src)\n assert ss == ds and 0 <= cst < 32\n return pred(p) | dn << 23 | sn << 18 | cst << 13 | 0x32 << 6 | 0b1000 << 2 | ds << 1\n\n\ndef enc_dp(op, data, off, p=''):\n \"\"\"ld/st *+b14(ucst15) (byte offset, scaled), .D2\"\"\"\n ds, dn = reg(data)\n sc = D_SCALE[op]\n assert off % sc == 0 and 0 <= off // sc < 32768\n return pred(p) | dn << 23 | (off // sc) << 8 | 0 << 7 | D_OPS[op] << 4 | 0b11 << 2 | ds << 1\n\n\ndef enc_extu(src, csta, cstb, dst, p=''):\n \"\"\"extu .S src,csta,cstb,dst (field form, bits 7-6 = 00).\"\"\"\n ds, dn = reg(dst)\n ss, sn = reg(src)\n assert ss == ds\n return pred(p) | dn << 23 | sn << 18 | csta << 13 | cstb << 8 | 0b00 << 6 | 0b0010 << 2 | ds << 1\n\n\ndef enc_addaw(src2, src1, dst, p=''):\n \"\"\"addaw .D dst = src2 + src1*4 (src1 register or ucst5).\"\"\"\n ds, dn = reg(dst)\n s2s, s2n = reg(src2)\n assert s2s == ds\n if isinstance(src1, int):\n return pred(p) | dn << 23 | s2n << 18 | (src1 & 31) << 13 | 0x3A << 7 | 0b10000 << 2 | ds << 1\n s1s, s1n = reg(src1)\n return pred(p) | dn << 23 | s2n << 18 | s1n << 13 | 0x38 << 7 | 0b10000 << 2 | ds << 1\n\n\nclass Asm:\n def __init__(self, base):\n self.base, self.items, self.labels = base, [], {}\n\n def label(self, name):\n self.items.append(('label', name))\n\n def org(self, addr):\n \"\"\"continue at addr (a new block; the caller branches there)\"\"\"\n self.items.append(('org', addr))\n\n def op(self, mn, *a, p=''):\n self.items.append(('op', (mn, a, p)))\n\n def word(self, w):\n self.items.append(('word', w))\n\n def _enc(self, at, mn, a, p):\n if mn == 'mvk':\n return enc_mvk(0, a[0], a[1], p)\n if mn == 'mvkh':\n return enc_mvk(1, a[0], a[1], p)\n if mn == 'mvkl32': # helper: emitted as two words by assemble()\n raise AssertionError\n if mn in L_OPS:\n return enc_l(mn, a[0], a[1], a[2], p)\n if mn in D_OPS:\n return enc_d(mn, a[0], a[1], a[2], p)\n if mn == 'b':\n t = a[0]\n if isinstance(t, str) and t[0] in 'ab' and t[1:].isdigit():\n return enc_b_reg(t, p)\n t = self.labels[t] if isinstance(t, str) else t\n return enc_b_imm(at, t, a[1] if len(a) > 1 else 0, p)\n if mn == 'nop':\n return enc_nop(a[0] if a else 1)\n if mn == 'dint':\n return DINT\n if mn == 'rint':\n return RINT\n if mn == 'extu':\n return enc_extu(a[0], a[1], a[2], a[3], p)\n if mn == 'addaw':\n return enc_addaw(a[0], a[1], a[2], p)\n if mn == 'shl':\n return enc_shl(a[0], a[1], a[2], p)\n if mn == 'lddp': # ldbu/ldw/... *+b14(off)\n return enc_dp(a[0], a[1], a[2], p)\n if mn == 'mv':\n return enc_l('add', 0, a[0], a[1], p)\n raise KeyError(mn)\n\n def assemble(self):\n \"\"\"-> [(addr, word)]\"\"\"\n out = []\n for final in (False, True):\n pc, out = self.base, []\n for kind, arg in self.items:\n if kind == 'label':\n self.labels[arg] = pc\n continue\n if kind == 'org':\n pc = arg\n continue\n if kind == 'word':\n out.append((pc, arg))\n pc += 4\n continue\n mn, a, p = arg\n if mn == 'const': # mvk + mvkh of a 32-bit value\n v = self.labels.get(a[0], a[0]) if isinstance(a[0], str) else a[0]\n out.append((pc, enc_mvk(0, v if final or not isinstance(a[0], str) else 0, a[1], p)))\n out.append((pc + 4, enc_mvk(1, v if final or not isinstance(a[0], str) else 0, a[1], p)))\n pc += 8\n continue\n try:\n w = self._enc(pc, mn, a, p)\n except KeyError:\n if final:\n raise\n w = 0\n out.append((pc, w))\n pc += 4\n return out\n\n\ndef selftest():\n ref = [\n (enc_d('ldw', 'a4', 'a3', 0), 0x020c0264),\n (enc_d('stw', 'a3', 'a5', 0), 0x01940274),\n (enc_d('sth', 'a4', 'a3', 2), 0x020c2254),\n (enc_d('ldh', 'a4', 'a3', 6), 0x020c6244),\n (enc_d('ldhu', 'a4', 'a3', 0), 0x020c0204),\n (enc_l('cmpeq', 1, 'a4', 'a0'), 0x00102a58),\n (enc_l('or', 0, 'a4', 'a0'), 0x00100fd8),\n (enc_l('add', 'b10', 'b4', 'b6'), 0x0311407b & ~1),\n (enc_l('cmplt', 0, 'b4', 'b0'), 0x00100adb & ~1),\n (enc_l('cmpgt', 'a3', 'a10', 'a0'), 0x002868f8),\n (enc_l('sub', 'a3', 'a4', 'a10'), 0x051060f8),\n (enc_l('add', 'a4', 'a3', 'a3'), 0x018c8078),\n (enc_mvk(0, -7576, 'b4'), 0x0271342a),\n (enc_mvk(1, 0x1182e268, 'b4'), 0x0208c16a),\n (enc_b_reg('b4'), 0x00100362),\n (enc_addaw('a4', 'a6', 'a3'), 0x0190dc41 & ~1),\n (enc_nop(5), 0x00008000),\n (enc_shl('a14', 3, 'a8'), 0x04386ca1 & ~1),\n (enc_dp('ldbu', 'b6', 969), 0x0303c91e),\n ]\n bad = [(hex(a), hex(b)) for a, b in ref if a != b]\n assert not bad, bad\n return True\n\n\nif __name__ == '__main__':\n print('selftest', selftest())\n","mods/cdj900nxs/credit.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"The mods' credit on the CDJ-900NXS 1.31 boot screen: \"Pionaarr Mods\"\nand the release (RELEASE, e.g. \"Pionaarr Mods 1.8.1\") where the splash\nshows \"Ver 1.31\". Our own; mods/patch_cdj900nxs.py\napplies it as `credit` (the patcher ticks it at the start).\n\nThe splash's text is made by 0x08BDCC14: a 50-byte buffer gets strcpy\n(0x08C28460) of the string the .data word at RAM 0x1110766C points to\n(image 0x08C6D22C, \"Ver \" at 0xA80D12BC), then strcat (the same function)\nof the version the getter 0x08BE9A8C returns (\"1.31\" at 0xA8000740), and is\nshown. The .data word now points at our text, and the function's own\nliteral of the getter (0x08BDCD8C, read only at 0x08BDCC34) at a stub that\nreturns an empty string, so nothing is added to it. The text's box (.data\n0x08C841E0: 139,363 - 619,385, right-aligned) is 8 lines taller, so the\ndescenders of p and y show. The getter itself and\neverything else that shows the version (the LINK screen's \"Ver %s\",\nUTILITY, the updater, the boot loader) are untouched.\"\"\"\nimport struct\n\nVERSION = '1.1.0' # Semantic Versioning: MAJOR.MINOR.PATCH\nRELEASE = '1.9.0' # the Pionaarr Mods release, ALWAYS the latest: the website's generator\n # (tools/make_builder_data.py) writes its version here\n\nBASE = 0x08000000\nDATA_WORD = 0x08C6D22C # .data: the splash's \"Ver \" pointer (RAM 0x1110766C)\nDATA_VER = 0xA80D12BC # \"Ver \"\nGETTER_LIT = 0x08BDCD8C # the splash function's literal of the version getter\nGETTER = 0x08BE9A8C\nTEXT = 'Pionaarr Mods ' + RELEASE\nCREDIT = 0x08FCF900 # between beatjump's 0x08FCF400..0x08FCF814 and cuecolours' 0x08FCFA00\nCREDIT_LEN = 0x40\nSTUB = CREDIT + 0x20 # mov.l @(4,pc),r0; rts; nop; nop; .long the text's final NUL\nBOX = 0x08C841E0 # .data: the splash text's box, x1 y1 x2 y2 (16 bits each)\nBOX_STOCK = (139, 363, 619, 385)\nBOX_NEW = (139, 363, 619, 393) # \"Ver 1.31\" has no descender; p and y need 8 more lines\n\n\ndef area():\n \"\"\"The credit area's bytes: the text, then the stub returning \"\".\"\"\"\n text = TEXT.encode('ascii') + b'\\0' # the splash takes bytes as Latin-1: no UTF-8\n # the splash's buffer is 50 bytes, its UTF-16 copy 100\n assert len(text) <= STUB - CREDIT and len(text) <= 50\n empty = CREDIT + len(text) - 1\n stub = struct.pack('<4HI', 0xD001, 0x000B, 0x0009, 0x0009, empty | 0xA0000000)\n return text.ljust(STUB - CREDIT, b'\\0') + stub\n\n\ndef patch(app, stock):\n \"\"\"The application with the credit; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n end = CREDIT + CREDIT_LEN - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[CREDIT - BASE:end]):\n raise SystemExit('patch_cdj900nxs: credit: its area 0x%08x.. is not free' % CREDIT)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + 4] != struct.pack(' the 44-byte resource records\nTRI_ID = 0xC5 # the red memory-cue triangle, 16 x 5, RGB565\nTRI_PIXELS = 0x0815A1B0 # its pixels in the image\nTRI_RED = 0xF800\nMEMLIST = 0x0B7BE8D4 # the UI's memory list, 10 x 0x24\nOV_RECS = 0x0B800D9C # the overview's marker records, 12 bytes\nMK_RECS = 0x0B801258 # the marker records they come from, 16 bytes\nMK_RECORDS = 101\nMKCOPY_LIT = 0x08BC6BF8 # the waveform preparer's literal for memcpy, which fills MK_RECS\nMEMCPY = 0x08C279C4\nHOT_DEFAULT = 0x07E0 # a hot cue without a colour: green, as rekordbox's default\n\n# rekordbox's eight memory-cue colours (ids 1-8), as the player itself draws\n# them: the track-colour dots of the browser, resources 0x14, 0x16, 0x13,\n# 0x18, 0x12, 0x0F, 0x11 and 0x15 (RGB565 there).\nMEMORY_RGB565 = [\n 0xFA55, # 1 pink\n 0xF800, # 2 red\n 0xFC20, # 3 orange\n 0xFFE0, # 4 yellow\n 0x07E0, # 5 green\n 0x079F, # 6 aqua\n 0x22FF, # 7 blue\n 0x881F, # 8 purple\n]\n\n# hot cue letters A-H, 5 x 7, for the overview's bigger marker (bit 7 = left)\nLETTERS5 = [\n ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],\n ['11110', '10001', '10001', '11110', '10001', '10001', '11110'],\n ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],\n ['11110', '10001', '10001', '10001', '10001', '10001', '11110'],\n ['11111', '10000', '10000', '11110', '10000', '10000', '11111'],\n ['11111', '10000', '10000', '11110', '10000', '10000', '10000'],\n ['01110', '10001', '10000', '10111', '10001', '10001', '01111'],\n ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],\n]\n\n# hot cue letters A-H, 3 x 5, rows top down: drawn black into a 9 x 5 marker\nLETTERS = ['010101111101101', '110101110101110', '011100100100011', '110101101101110',\n '111100110100111', '111100110100100', '011100101101011', '101101111101101']\n\n# data after the code (zero in the image)\nDATA = 0x08FD0000 # after the code (checked in patch())\nHOT_TABLE = DATA # 8 x 8: +0 time 1/150 s (-1 none), +4 RGB565, +6 hot cue number;\n # entry 0's +7 counts the loads (cue_get)\nSLOTS = 18 # triangles: memory list index 0-9, hot cues 10-17\nTRI_BUF = DATA + 0x40 # SLOTS x 160 bytes\nHOTBOX_W, HOTBOX_H = 16, 9 # the overview's hot cue marker: 11 x 9, then transparent; the\nHOTBOX_SIZE = 288 # blitter wants lines of a multiple of 16 bytes, as all the firmware's\nHOTBOX = (TRI_BUF + SLOTS * 160 + 31) & ~31 # 8 x HOTBOX_SIZE\nFAKE_RECS = HOTBOX + 8 * HOTBOX_SIZE # one 44-byte record, the detail waveform's\nDATA_END = FAKE_RECS + 44\nassert DATA_END <= CC_END\n\n\ndef rgb565(r, g, b):\n return (r & 0xF8) << 8 | (g & 0xFC) << 3 | b >> 3\n\n\nSOURCE = r'''\n; ---- cue_get: dbcl_GetUsbSdCueInfo(r4 connection, r5 track, r6 reply) plus colours\ncue_get:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-20,r15 ; +0 wait's slot, +4/+8 message, +12 block, +16 size\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov.l K_cueinfo,r1\n jsr @r1\n nop\n mov r0,r12 ; the stock result, returned as it is\n mov.l K_hot,r1 ; no hot cues known\n mov #8,r2\n mov #-1,r3\ncg_clear:\n mov.l r3,@r1\n dt r2\n bf/s cg_clear\n add #8,r1\n mov.l K_hot,r1 ; one more load: entry 0's spare byte counts\n mov.b @(7,r1),r0 ; them (hotcuekeys reloads its slots on it)\n add #1,r0\n mov.b r0,@(7,r1)\n cmp/pl r12\n bf cg_early\n mov.l @r8,r11 ; the connection's socket side\n tst r11,r11\n bt cg_early\n mov.l K_check,r1\n jsr @r1\n mov r11,r4\n tst r0,r0\n bt cg_early\n mov.l K_alloc,r1\n jsr @r1\n mov #72,r4\n tst r0,r0\n bt cg_early\n mov.l r0,@(4,r15)\n mov.l r0,@(8,r15)\n mov #0,r1\n mov.l r1,@r15\n mov.l r1,@(12,r15)\n mov.l r1,@(16,r15)\n mov r8,r4\n mov r0,r5\n mov.l K_type,r6\n mov.l K_init,r1\n jsr @r1\n mov #8,r7 ; as dbcl_GetUsbSdCueInfo\n mov.l @(4,r15),r2\n mov.l r9,@(24,r2) ; the track\n mov #0,r1\n mov.l r1,@(28,r2)\n mov r11,r4\n mov.l @(8,r15),r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bf cg_early\n mov r15,r1\n add #12,r1\n mov.l r1,@-r15 ; &block\n mov.l @(12,r15),r4 ; the message\n mov.l K_reply,r5\n mov r15,r6\n add #4,r6\n mov r15,r7\n mov.l K_wait,r1\n jsr @r1\n add #20,r7 ; &size\n add #4,r15\n mov.l @(12,r15),r13 ; the reply's entries\n tst r13,r13\n bt cg_early\n cmp/pz r0\n bf cg_free\n mov.l @(16,r15),r14\n add r13,r14 ; their end\n bra cg_entry\n nop\ncg_early:\n bra cg_out\n nop\ncg_entry:\n mov r13,r1\n add #16,r1\n cmp/hi r14,r1\n bt cg_free ; not even a header left\n mov.l @r13,r11 ; length\n mov #64,r1\n cmp/hs r1,r11\n bf cg_free\n mov r13,r1\n add r11,r1\n cmp/hi r14,r1\n bt cg_free ; runs past the end\n mov.l @(12,r13),r1 ; ms -> 1/150 s: ms * 3 / 20\n mov r1,r2\n add r1,r2\n add r1,r2\n mov.l K_div20,r1\n dmulu.l r1,r2\n sts mach,r9\n shlr2 r9\n shlr2 r9 ; r9 = time\n mov.b @(4,r13),r0\n extu.b r0,r6 ; r6 = hot cue number, 0 = memory cue\n tst r6,r6\n bf cg_hot\n mov #58,r0\n mov.b @(r0,r13),r0 ; colour id\n extu.b r0,r0\n tst r0,r0\n bt cg_next\n mov #8,r1\n cmp/hi r1,r0\n bt cg_next\n add r0,r0\n mov.l K_pal,r1\n add r1,r0\n mov.w @r0,r7\n bra cg_match\n extu.w r7,r7\ncg_hot:\n mov.l K_green,r7 ; no colour: rekordbox's default\n mov #82,r1 ; colour code, R, G, B, 2 bytes: 0x52 at least\n cmp/hs r1,r11\n bf cg_hstore\n mov #72,r0 ; the comment: its length in bytes at +0x48, text from +0x4A,\n mov.w @(r0,r13),r2 ; then 4 bytes, then colour code, R, G, B\n extu.w r2,r2 ; (the server's own entries: not checked against the length)\n add r13,r2\n add #78,r2\n mov.b @r2,r0 ; colour code; 0 = none\n tst r0,r0\n bt cg_hstore\n mov.b @(1,r2),r0 ; R\n extu.b r0,r7\n mov #-8,r0\n and r0,r7\n shll8 r7\n mov.b @(2,r2),r0 ; G\n extu.b r0,r0\n shlr2 r0\n shll2 r0\n shll r0\n shll2 r0\n or r0,r7\n mov.b @(3,r2),r0 ; B\n extu.b r0,r0\n shlr2 r0\n shlr r0\n or r0,r7\ncg_hstore:\n mov #9,r1 ; hot cues 1-8 into our table\n cmp/hs r1,r6\n bt cg_match\n mov r6,r1\n add #-1,r1\n shll2 r1\n shll r1\n mov.l K_hot,r2\n add r1,r2\n mov.l r9,@r2\n mov r7,r0\n mov.w r0,@(4,r2)\n mov r6,r0\n mov.b r0,@(6,r2)\ncg_match:\n ; mark the stock reply's entry with the same hot cue number and time\n mov.l @(24,r10),r1 ; entries\n tst r1,r1\n bt cg_next\n mov.l @(16,r10),r2\n mov.l @(20,r10),r3\n add r3,r2 ; memory + hot cue entries\n mov #16,r3\n cmp/hi r3,r2\n bf cg_m_ok\n mov r3,r2\ncg_m_ok:\n tst r2,r2\n bt cg_next\ncg_m:\n mov.b @(2,r1),r0\n extu.b r0,r0\n cmp/eq r6,r0\n bf cg_m_next\n mov.l @(12,r1),r0\n sub r9,r0 ; time difference, within one unit\n add #1,r0\n mov #2,r3\n cmp/hi r3,r0\n bt cg_m_next\n mov r7,r0\n mov.w r0,@(16,r1)\n mov r1,r3\n add #18,r3\n mov #1,r0\n mov.b r0,@r3\ncg_m_next:\n dt r2\n bf/s cg_m\n add #36,r1\ncg_next:\n bra cg_entry\n add r11,r13\ncg_free:\n mov.l @(12,r15),r4\n tst r4,r4\n bt cg_out\n mov.l K_free,r1\n jsr @r1\n nop\ncg_out:\n mov r12,r0\n add #20,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nK_alloc: .long 0x089A791E\nK_check: .long 0x089736F2\nK_cueinfo: .long CUEINFO\nK_div20: .long 0xCCCCCCCD\nK_free: .long 0x08A5EBC2\nK_green: .long HOT_DEFAULT\nK_hot: .long HOT_TABLE\nK_init: .long 0x08973C50\nK_pal: .long PALETTE\nK_reply: .long 0x00004E02\nK_send: .long 0x089738F0\nK_type: .long 0x00002B04\nK_wait: .long 0x08974198\n\n; ---- colour_for: r4 time (1/150 s), r5 tolerance -> r0 RGB565 or -1, r1 slot\ncolour_for:\n mov.l L_memlist,r1\n mov #0,r2 ; slot\ncf_loop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt cf_hot\n mov #18,r0\n mov.b @(r0,r1),r0\n cmp/eq #1,r0\n bf cf_skip\n mov.l @(12,r1),r0\n sub r4,r0\n add r5,r0\n mov r5,r3\n add r5,r3\n cmp/hi r3,r0\n bt cf_skip\n mov.w @(16,r1),r0\n extu.w r0,r0\n rts\n mov r2,r1\ncf_skip:\n add #1,r2\n mov #10,r0\n cmp/hs r0,r2\n bf/s cf_loop\n add #36,r1\ncf_hot:\n mov.l L_hot,r1 ; then the hot cues: slots 10-17\n mov #10,r2\ncf_hloop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt cf_hskip\n sub r4,r0\n add r5,r0\n mov r5,r3\n add r5,r3\n cmp/hi r3,r0\n bt cf_hskip\n mov.w @(4,r1),r0\n extu.w r0,r0\n rts\n mov r2,r1\ncf_hskip:\n add #1,r2\n mov #18,r0\n cmp/hs r0,r2\n bf/s cf_hloop\n add #8,r1\n rts\n mov #-1,r0\n\n; ---- make_tri: r4 slot, r5 RGB565 -> r0 its triangle (uncached address)\nmake_tri:\n mov r4,r0\n shll2 r0\n add r4,r0\n shll2 r0\n shll2 r0\n shll r0 ; slot * 160\n mov.l L_tribuf,r1\n add r0,r1 ; destination, through P2\n mov r1,r6\n mov #10,r0\n cmp/hs r0,r4\n bt mt_box\n mov.l L_tripix,r2\n mov.l L_red,r3\n mov #80,r7\nmt_loop:\n mov.w @r2+,r0\n extu.w r0,r0\n cmp/eq r3,r0\n bf mt_keep\n mov r5,r0\nmt_keep:\n mov.w r0,@r1\n dt r7\n bf/s mt_loop\n add #2,r1\n rts\n mov r6,r0\nmt_box:\n mov r4,r0 ; the letter, left-aligned in a word\n add #-10,r0\n shll2 r0\n mov.l L_font,r2\n mov.l @(r0,r2),r3\n mov.l L_key,r2 ; transparent\n mov #5,r7\nmb_row:\n mov #0,r4\nmb_col:\n mov #9,r0\n cmp/hs r0,r4\n bt mb_t ; past the marker\n mov #3,r0\n cmp/hs r0,r4\n bf mb_c\n mov #6,r0\n cmp/hs r0,r4\n bt mb_c\n shll r3 ; the letter's next pixel into T\n bf mb_c\n bra mb_st\n mov #0,r0 ; black\nmb_c:\n bra mb_st\n mov r5,r0\nmb_t:\n mov r2,r0\nmb_st:\n mov.w r0,@r1\n add #2,r1\n add #1,r4\n mov #16,r0\n cmp/hs r0,r4\n bf mb_col\n dt r7\n bf mb_row\n rts\n mov r6,r0\n\n; ---- mk_copy: memcpy(r4, r5, r6) for the waveform preparer; filling the\n; marker records, it adds the hot cues to them as memory-cue markers.\nmk_copy:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l L_memcpy,r1\n jsr @r1\n nop\n mov.l L_mkrecs,r1\n cmp/eq r1,r8\n bf mk_out\n mov.l r0,@-r15 ; memcpy's result\n mov r8,r1\n mov #101,r2\n mov #-1,r3\n extu.b r3,r3 ; 0xff ends the list\nmk_find:\n mov.l @r1,r0\n cmp/eq r3,r0\n bt mk_add\n dt r2\n bf/s mk_find\n add #16,r1\n bra mk_done\n nop\nmk_add:\n mov.l L_hot,r3\n mov #8,r7\nmk_h:\n mov.l @r3,r0 ; time\n cmp/eq #-1,r0\n bt mk_hn\n tst r2,r2\n bt mk_done ; the list is full\n mov.l r0,@(8,r1)\n mov #0,r0\n mov.l r0,@r1 ; a memory cue\n mov.l r0,@(4,r1)\n mov #-1,r0\n mov.l r0,@(12,r1)\n add #16,r1\n add #-1,r2\nmk_hn:\n dt r7\n bf/s mk_h\n add #8,r3\nmk_done:\n mov.l @r15+,r0\nmk_out:\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- det_res: the resource resolver, for the detail waveform (r9 = its column).\n; Every 0xC5 it is asked for becomes a 16 x 8 marker of ours, as the renderer\n; (patched) copies 11 x 8: a memory cue's triangle in rows 3-7 (coloured, or\n; the stock red one), a hot cue's box with its letter in rows 0-7.\ndet_res:\n mov.l L_resolve,r1\n mov.l L_triid,r0\n cmp/eq r0,r4\n bt dr_mine\n jmp @r1\n nop\ndr_mine:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r9,r8\n jsr @r1\n nop\n mov r0,r10 ; the record\n tst r10,r10\n bt dr_out\n mov r8,r4\n bsr colour_for\n mov #16,r5 ; zoomed out, a column stands for up to 16\n mov r1,r9 ; slot\n cmp/eq #-1,r0\n bt dr_plain\n mov r0,r5\n mov r9,r4\n mov #10,r2\n cmp/hs r2,r4\n bt dr_hot\n bsr make_tri\n nop\n mov #3,r11 ; triangle: rows 3-7\n bra dr_copy\n mov #5,r6\ndr_hot:\n bsr make_hotbox\n nop\n mov #0,r11 ; box: rows 0-7\n bra dr_copy\n mov #8,r6\ndr_plain:\n mov #18,r9 ; no colour: the stock triangle\n mov.l L_tripix,r0\n mov #3,r11\n mov #5,r6\ndr_copy:\n mov r0,r3 ; source, 16 pixels a line\n mov.l L_detbuf,r2\n mov r9,r0\n shll8 r0\n add r0,r2\n mov r2,r8 ; this slot's 16 x 8\n mov.l L_key,r0\n mov #64,r7\n shll r7\n mov r2,r1\ndr_fill:\n mov.w r0,@r1\n dt r7\n bf/s dr_fill\n add #2,r1\n mov r11,r0\n shll2 r0\n shll2 r0\n shll r0\n add r0,r2\ndr_row:\n mov #16,r7\ndr_px:\n mov.w @r3+,r0\n mov.w r0,@r2\n dt r7\n bf/s dr_px\n add #2,r2\n dt r6\n bf dr_row\n mov.l L_fake,r2 ; the record, with our size and pixels\n mov r10,r3\n mov #11,r4\ndr_rec:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r4\n bf/s dr_rec\n add #4,r2\n add #-44,r2\n mov.l L_detsize,r1\n mov.l r1,@(4,r2)\n mov.l r8,@(12,r2)\n mov r2,r10\ndr_out:\n mov r10,r0\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- ov_draw: draw_bitmap(r4, r5 x, r6 y, r7 resource) for the overview (r13 = its record)\nov_draw:\n mov.l L_draw,r1\n mov.l L_triid,r0\n cmp/eq r0,r7\n bt od_mine\n jmp @r1\n nop\nod_mine:\n mov.l r13,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov r7,r11\n mov.l L_ovrecs,r1 ; which record: 12 bytes each, 16 in the other list\n mov r13,r2\n sub r1,r2\n mov #0,r3\n mov #101,r4\nod_idx:\n cmp/pl r2\n bf od_found\n add #-12,r2\n dt r4\n bf/s od_idx\n add #16,r3\n bra od_plain\n nop\nod_found:\n tst r2,r2\n bf od_plain ; not on a record\n mov.l L_mkrecs,r1\n add r3,r1\n mov.l @(8,r1),r4\n bsr colour_for\n mov #0,r5\n cmp/eq #-1,r0\n bt od_plain\n mov r1,r4\n mov #10,r2\n cmp/hs r2,r4\n bt od_hot\n bsr make_tri\n mov r0,r5\n mov.l L_trisize,r13 ; 16 x 5, the triangle's own size\n bra od_pix\n nop\nod_hot:\n bsr make_hotbox ; a hot cue: the bigger box\n mov r0,r5\n mov.l L_boxsize,r13\nod_pix:\n mov.l L_restab,r1 ; the triangle's record\n mov.l @r1,r12\n mov.l L_trirec,r1\n add r1,r12\n mov.l @(32,r12),r14 ; its pixel offset and size, put back below\n mov.l @(4,r12),r1\n mov.l r13,@(4,r12)\n mov r1,r13\n mov.l L_p0mask,r1 ; the blitter gets the plain address\n and r1,r0\n mov.l L_imgbase,r1\n sub r1,r0\n mov.l r0,@(32,r12)\n mov r8,r4\n mov r9,r5\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\n mov.l r13,@(4,r12)\n bra od_out\n mov.l r14,@(32,r12)\nod_plain:\n mov r8,r4\n mov r9,r5\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\nod_out:\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n rts\n mov.l @r15+,r13\n\n .align 4\nL_cueinfo: .long CUEINFO\nL_check: .long 0x089736F2\nL_alloc: .long 0x089A791E\nL_init: .long 0x08973C50\nL_send: .long 0x089738F0\nL_wait: .long 0x08974198\nL_free: .long 0x08A5EBC2\nL_type: .long 0x00002B04\nL_reply: .long 0x00004E02\nL_div20: .long 0xCCCCCCCD\nL_hot: .long HOT_TABLE\nL_memlist: .long MEMLIST\nL_tribuf: .long TRI_BUF_P2\nL_tripix: .long TRI_PIXELS\nL_red: .long TRI_RED\nL_resolve: .long RESOLVE\nL_draw: .long DRAW\nL_triid: .long TRI_ID\nL_fake: .long FAKE_RECS\nL_detbuf: .long DET_BUF\nL_detsize: .long 0x00080010\nL_ovrecs: .long OV_RECS\nL_mkrecs: .long MK_RECS\nL_restab: .long RES_TABLE_PTR\nL_trirec: .long TRI_REC_OFF\nL_imgbase: .long 0x08100000\nL_p0mask: .long 0x1FFFFFFF\nL_pal: .long PALETTE\nL_green: .long HOT_DEFAULT\nL_font: .long FONT\nL_key: .long 0xF81F\nL_memcpy: .long MEMCPY\nL_trisize: .long 0x00050010\nL_boxsize: .long BOXSIZE\n\n; ---- make_hotbox: r4 slot (10-17), r5 RGB565 -> r0 the overview's hot cue\n; marker, 11 x 9 in its colour with the letter in black (uncached address)\nmake_hotbox:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov r4,r6\n add #-10,r6 ; 0 = A\n mov r6,r1 ; HOTBOX_SIZE (288) * index\n shll8 r1\n mov r6,r2\n shll2 r2\n shll2 r2\n shll r2\n add r2,r1\n mov.l M_hotbox,r2\n add r2,r1\n mov r1,r9 ; returned\n mov r6,r2 ; its letter: 7 bytes from FONT5 + 7 * index\n shll2 r2\n shll r2\n sub r6,r2\n mov.l M_font5,r3\n add r2,r3\n mov.l M_key,r2\n mov #0,r6 ; row\nhb_row:\n mov #0,r4 ; the letter's bits in this row\n mov r6,r0\n cmp/eq #0,r0\n bt hb_cols\n cmp/eq #8,r0\n bt hb_cols\n add #-1,r0\n mov.b @(r0,r3),r4\n extu.b r4,r4\nhb_cols:\n mov #0,r8 ; column\nhb_col:\n mov #11,r0\n cmp/hs r0,r8\n bt hb_t\n mov #3,r0\n cmp/hs r0,r8\n bf hb_c\n mov #8,r0\n cmp/hs r0,r8\n bt hb_c\n mov r4,r0\n tst #128,r0\n bt/s hb_c\n shll r4\n bra hb_st\n mov #0,r0 ; black\nhb_c:\n bra hb_st\n mov r5,r0\nhb_t:\n mov r2,r0\nhb_st:\n mov.w r0,@r1\n add #2,r1\n add #1,r8\n mov #16,r0\n cmp/hs r0,r8\n bf hb_col\n add #1,r6\n mov #9,r0\n cmp/hs r0,r6\n bf hb_row\n mov r9,r0\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nM_hotbox: .long HOTBOX_P2\nM_font5: .long FONT5\nM_key: .long 0xF81F\nFONT:\n'''\n\n\ndef routine():\n \"\"\"The code at CC_BASE, the memory-cue palette after it, and the labels.\"\"\"\n syms = {\n 'CUEINFO': CUEINFO, 'RESOLVE': RESOLVE, 'DRAW': DRAW, 'HOT_TABLE': HOT_TABLE,\n 'MEMLIST': MEMLIST, 'TRI_BUF_P2': TRI_BUF | 0xA0000000, 'TRI_PIXELS': TRI_PIXELS,\n 'TRI_RED': TRI_RED, 'TRI_ID': TRI_ID, 'FAKE_RECS': FAKE_RECS, 'OV_RECS': OV_RECS,\n 'MK_RECS': MK_RECS, 'RES_TABLE_PTR': RES_TABLE_PTR, 'TRI_REC_OFF': TRI_ID * 44,\n 'HOT_DEFAULT': HOT_DEFAULT, 'MEMCPY': MEMCPY, 'HOTBOX_P2': HOTBOX | 0xA0000000,\n 'BOXSIZE': HOTBOX_W | HOTBOX_H << 16, 'DET_BUF': DET_BUF,\n }\n font = b''.join(struct.pack(' r0 the named cue to show (CN_TABLE entry) or 0; r1-r7 used\ncn_pick:\n mov.l P_hot,r6\n mov.b @(7,r6),r0 ; the table is this load's?\n mov.l P_load,r1\n mov.b @r1,r1\n cmp/eq r1,r0\n bf pk_none\n mov.l P_count,r1\n mov.l @r1,r3\n tst r3,r3\n bt pk_none\n mov.l P_pos,r1\n mov.l @r1,r4\n mov.l P_behind,r1\n sub r1,r4 ; from a second behind\n mov.l P_table,r2\n mov #0,r0 ; best so far\npk_loop:\n mov.l @r2,r1 ; ms\n cmp/ge r4,r1\n bf pk_next ; behind\n mov.l @(8,r2),r5 ; a hot cue still where it was?\n tst r5,r5\n bt pk_ok\n mov #9,r7\n cmp/hs r7,r5\n bt pk_next\n add #-1,r5\n shll2 r5\n shll r5\n add r6,r5\n mov.l @r5,r5 ; HOT_TABLE's time, 1/150 s\n mov.l @(4,r2),r7\n sub r7,r5\n add #1,r5\n mov #2,r7\n cmp/hi r7,r5\n bt pk_next\npk_ok:\n tst r0,r0\n bt pk_take\n mov.l @r0,r7\n cmp/ge r7,r1\n bt pk_next ; not nearer\npk_take:\n mov r2,r0\npk_next:\n dt r3\n bf/s pk_loop\n add #64,r2\n rts\n nop\npk_none:\n rts\n mov #0,r0\n\n; ---- cn_draw: the text widget's draw method (r4 renderer, r5 widget)\ncn_draw:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov.l @(4,r5),r1 ; the title?\n mov.l D_title,r2\n cmp/eq r2,r1\n bf dr_plain\n mov.l D_depth,r1 ; BROWSE closed?\n mov.b @r1,r1\n tst r1,r1\n bf dr_plain\n mov.l @(40,r9),r1 ; the title white? (dimmed behind a popup, as MENU's: no name)\n mov #-1,r2\n cmp/eq r2,r1\n bf dr_plain\n bsr cn_pick\n nop\n tst r0,r0\n bt dr_plain\n mov r0,r10 ; the cue\n mov.l D_label,r2 ; the label: \"A \" for a hot cue, then the name\n mov.l @(8,r10),r1\n tst r1,r1\n bt dr_name\n add #64,r1 ; 'A' - 1 + number\n mov r1,r0\n mov.w r0,@r2\n mov #32,r0\n mov.w r0,@(2,r2)\n add #4,r2\ndr_name:\n mov r10,r1\n add #16,r1\ndr_ncopy:\n mov.w @r1+,r0\n mov.w r0,@r2\n tst r0,r0\n bf/s dr_ncopy\n add #2,r2\n mov.l @(28,r9),r11 ; the title, narrowed\n mov r11,r1\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_tw,r2\n or r2,r1\n mov.l r1,@(28,r9)\n mov r8,r4\n mov r9,r5\n mov.l D_next,r1\n jsr @r1\n nop\n mov.l r11,@(28,r9)\n mov.l D_clone,r2 ; the copy: our place, text, colour\n mov r9,r1\n mov #24,r3\ndr_clone:\n mov.l @r1+,r0\n mov.l r0,@r2\n dt r3\n bf/s dr_clone\n add #4,r2\n mov.l D_clone,r5\n mov.l @(24,r9),r1 ; y stays, x ours\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_lx,r2\n or r2,r1\n mov.l r1,@(24,r5)\n mov r11,r1\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_lw,r2\n or r2,r1\n mov.l r1,@(28,r5)\n mov.l @(12,r10),r1\n mov.l r1,@(40,r5)\n mov.l D_label,r1\n mov.l r1,@(52,r5)\n mov r8,r4\n mov.l D_next,r1\n jsr @r1\n nop\n bra dr_out\n nop\ndr_plain:\n mov r8,r4\n mov r9,r5\n mov.l D_next,r1\n jsr @r1\n nop\ndr_out:\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nP_behind: .long BEHIND_MS\nP_count: .long CN_COUNT\nP_hot: .long HOT_TABLE\nP_load: .long CN_LOAD\nP_pos: .long POS_MS\nP_table: .long CN_TABLE\nD_clone: .long CN_CLONE\nD_depth: .long BROWSE_DEPTH\nD_hmask: .long 0xFFFF0000\nD_label: .long CN_LABEL\nD_lw: .long LABEL_W\nD_lx: .long LABEL_X\nD_next: .long NEXT_DRAW\nD_title: .long TITLE_ID\nD_tw: .long TITLE_W\nMEMORY_TAB:\n'''\n\n\ndef routine(next_draw):\n src = SOURCE + ''.join(' .long 0x%08X\\n' % c for c in MEMORY_ABGR)\n return sh4asm.assemble(src, CN_CODE, {\n 'CN_COUNT': CN_COUNT, 'CN_LOAD': CN_LOAD, 'CN_TABLE': CN_TABLE, 'CN_LABEL': CN_LABEL,\n 'CN_CLONE': CN_CLONE, 'CN_MAX': CN_MAX, 'CN_NAME': CN_NAME, 'FREE': FREE,\n 'HOT_TABLE': HOT_TABLE, 'POS_MS': POS_MS, 'BROWSE_DEPTH': BROWSE_DEPTH,\n 'TITLE_ID': TITLE_ID, 'TITLE_W': TITLE_W, 'LABEL_X': LABEL_X, 'LABEL_W': LABEL_W,\n 'BEHIND_MS': BEHIND_MS, 'NO_COLOUR': NO_COLOUR, 'NEXT_DRAW': next_draw})\n\n\ndef patch(app, stock):\n \"\"\"The application with CUE NAMES added (after cuecolours and, if chosen,\n keynotation); `stock` is the unmodified 1.31 application.\"\"\"\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' 0xC00487A4)\nTRAMP_TARGET = 0xC00487A4\nMB = 0x1183E000\nDP = 0x11806900\nHEADS = 0x1182E0E0\nLINKS = 0xC1F38B90\nPLAY_HEAD_DP = 969 # byte: the playing head's index\nMARGIN = 750 # slots (10 s) a forward jump leaves decoded ahead (2 s: EMERGENCY LOOP\n # after rapid 8-beat jumps, emulator too; 5 s was fine there)\nBLOCKS = ((0xC0000040, 0xC0000080), (0xC00000A0, 0xC0000100), (0xC0000120, 0xC00001E0))\nFREE_PACKETS = (struct.pack('<8I', *([0x0c6e0c6e] * 7 + [0xefe00000])), # compact nops only\n struct.pack('<8I', *([0x0c6e0c6e] * 6 + [0, 0xe7e00000])))\n\n\ndef code():\n \"\"\"[(addr, word)] of dj_poll.\"\"\"\n a = c6asm.Asm(BLOCKS[0][0])\n o = a.op\n # block 1: the mailbox; e = heads + 30 * h\n o('const', MB, 'a3')\n o('ldw', 'a4', 'a3', 0)\n o('nop', 4)\n o('cmpeq', 1, 'a4', 'a1')\n o('b', 'tail', p='!a1')\n o('nop', 5)\n o('ldw', 'b4', 'a3', 4) # n\n o('lddp', 'ldbu', 'b5', PLAY_HEAD_DP) # h\n o('nop', 4)\n o('shl', 'b5', 5, 'b6')\n o('shl', 'b5', 1, 'b7')\n o('sub', 'b6', 'b7', 'b6') # 30 h\n o('b', 'blk2')\n o('nop', 5)\n # block 2: the head's fields, the direction\n a.org(BLOCKS[1][0])\n a.label('blk2')\n o('const', HEADS, 'b8')\n o('add', 'b8', 'b6', 'b6') # e\n o('ldhu', 'a5', 'b6', 2) # cur\n o('ldh', 'a6', 'b6', 6) # behind\n o('ldh', 'a7', 'b6', 8) # ahead\n o('nop', 4)\n o('stw', 'a5', 'a3', 16)\n o('stw', 'a6', 'a3', 24)\n o('stw', 'a7', 'a3', 28)\n o('mvk', 2, 'a9') # refused unless done\n o('const', LINKS, 'a4')\n o('cmplt', 0, 'b4', 'b0') # n > 0\n o('b', 'fwd', p='b0')\n o('nop', 5)\n o('cmpgt', 0, 'b4', 'b1') # n < 0\n o('b', 'ok', p='!b1') # n = 0\n o('nop', 5)\n o('sub', 0, 'b4', 'b2') # m = -n\n o('mv', 'b2', 'a8')\n o('sub', 'a6', 'a8', 'a8') # behind - m\n o('b', 'blk3')\n o('nop', 5)\n # block 3: the walk\n a.org(BLOCKS[2][0])\n a.label('blk3')\n o('cmplt', 0, 'a8', 'a1') # at least one slot left behind\n o('b', 'done', p='!a1')\n o('nop', 5)\n a.label('loopb')\n o('addaw', 'a4', 'a5', 'a2')\n o('ldhu', 'a5', 'a2', 0) # prev\n o('nop', 4)\n o('add', -1, 'b2', 'b2')\n o('b', 'loopb', p='b2')\n o('nop', 5)\n o('sub', 0, 'b4', 'b7')\n o('mv', 'b7', 'a8') # m\n o('sub', 'a6', 'a8', 'a6') # behind - m\n o('add', 'a8', 'a7', 'a7') # ahead + m\n o('b', 'store')\n o('nop', 5)\n a.label('fwd')\n o('mv', 'b4', 'a8')\n o('sub', 'a7', 'a8', 'a8') # ahead - n\n o('mvk', MARGIN, 'b7')\n o('cmplt', 'b7', 'a8', 'b1') # more than MARGIN left ahead\n o('b', 'done', p='!b1')\n o('nop', 5)\n o('mv', 'b4', 'b2')\n a.label('loopf')\n o('addaw', 'a4', 'a5', 'a2')\n o('ldhu', 'a5', 'a2', 2) # next\n o('nop', 4)\n o('add', -1, 'b2', 'b2')\n o('b', 'loopf', p='b2')\n o('nop', 5)\n o('mv', 'b4', 'a8')\n o('add', 'a8', 'a6', 'a6') # behind + n\n o('sub', 'a7', 'a8', 'a7') # ahead - n\n a.label('store')\n o('sth', 'a5', 'b6', 2)\n o('sth', 'a6', 'b6', 6)\n o('sth', 'a7', 'b6', 8)\n o('sth', 'a5', 'b6', 14) # the point the decoder fills from (set on a\n o('sth', 'a6', 'b6', 16) # pause): moved along, so standing it decodes\n o('sth', 'a7', 'b6', 18) # on from the new place\n o('stw', 'a5', 'a3', 20)\n a.label('ok')\n o('mvk', 1, 'a9')\n a.label('done')\n o('stw', 'a9', 'a3', 8)\n o('ldw', 'a8', 'a3', 12)\n o('nop', 4)\n o('add', 1, 'a8', 'a8')\n o('stw', 'a8', 'a3', 12)\n o('mvk', 0, 'a8')\n o('stw', 'a8', 'a3', 0)\n a.label('tail')\n o('b', TRAMP_TARGET)\n o('nop', 5)\n words = a.assemble()\n for at, _ in words:\n assert any(lo <= at < hi for lo, hi in BLOCKS), 'dspjump: 0x%08x is past its blocks' % at\n return words\n\n\ndef packets(words):\n \"\"\"{packet addr: 8 words}: our words, the rest of each packet nop.\"\"\"\n out = {}\n for at, w in words:\n fp = at & ~31\n out.setdefault(fp, [0] * 8)[(at - fp) // 4] = w\n return out\n\n\ndef set_cst16(word, value):\n return (word & ~(0xFFFF << 7)) | (value & 0xFFFF) << 7\n\n\ndef patch(app, stock):\n out = bytearray(app)\n for fp, ws in packets(code()).items():\n off = B_IMG - BASE + (fp - 0xC0000000)\n if stock[off:off + 32] not in FREE_PACKETS or out[off:off + 32] != stock[off:off + 32]:\n raise SystemExit('patch_cdj900nxs: dspjump: the vector packet 0x%08x is not the expected one' % fp)\n out[off:off + 32] = struct.pack('<8I', *ws)\n entry = BLOCKS[0][0]\n for at, want, value in ((TRAMP_MVK, 0x0543d22a, entry), (TRAMP_MVKH, 0x0560026a, entry >> 16)):\n off = A_IMG - BASE + (at - A_AT)\n w = struct.unpack_from(' red, grey -> dark red\n\nSOURCE = r'''\n; ---- ew_draw: draw_bitmap(r4, r5 x, r6 y, r7 resource) for the playing-address bar\new_draw:\n mov.l E_draw,r0\n mov.l E_res,r1\n cmp/eq r1,r7\n bt ew_bar\n jmp @r0\n nop\new_bar:\n mov.l E_len,r1 ; length in ms: 1/150 s * 20 / 3\n mov.l @r1,r1\n tst r1,r1\n bt ew_plain\n mov r1,r2\n shll2 r2\n shll2 r2\n shll2 r1\n add r2,r1 ; * 20\n mov.l E_div3,r2\n dmulu.l r2,r1\n sts mach,r1\n shlr r1 ; / 3\n mov.l E_pos,r2\n mov.l @r2,r2\n sub r2,r1 ; left\n cmp/pl r1\n bf ew_plain\n mov.l E_warn,r2\n cmp/hs r2,r1\n bt ew_plain\n mov.l r8,@-r15 ; red: the record pointed at RED_BAR for this call\n mov.l r9,@-r15\n sts.l pr,@-r15\n mov.l E_tab,r8\n mov.l @r8,r8\n mov.l E_recoff,r1\n add r1,r8\n mov.l @(32,r8),r9\n mov.l E_redoff,r1\n mov.l r1,@(32,r8)\n jsr @r0\n nop\n mov.l r9,@(32,r8)\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\new_plain:\n jmp @r0\n nop\n\n .align 4\nE_div3: .long 0xAAAAAAAB\nE_draw: .long DRAW\nE_len: .long TRACK_LEN\nE_pos: .long POS_MS\nE_recoff: .long REC_OFF\nE_redoff: .long RED_OFF\nE_res: .long RES_BAR\nE_tab: .long RES_TABLE_PTR\nE_warn: .long WARN_MS\n'''\n\n\ndef routine():\n return sh4asm.assemble(SOURCE, EW_CODE, {\n 'DRAW': DRAW, 'TRACK_LEN': TRACK_LEN, 'POS_MS': POS_MS, 'REC_OFF': RES_BAR * 44,\n 'RED_OFF': RED_BAR - RES_BASE, 'RES_BAR': RES_BAR, 'RES_TABLE_PTR': RES_TABLE_PTR,\n 'WARN_MS': WARN_MS})\n\n\ndef patch(app, stock):\n \"\"\"The application with END WARNING added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n word = lambda buf, a: struct.unpack_from(' dbcl_RegUdbSdCue, +0x1C delete 0x0897A0A6\n -> dbcl_DelUsbSdCue) handles it with the cue entry at request +112 (+1\n kind 1, +2 hot cue number, +4 ms, +12 1/150 s). hk_dbop queues (ST4) a\n set or delete; he_dbq sends 0x7D6 once MEMORY's own checks pass and no\n request of the UI is under way; hk_dbreg (the table's register entry)\n makes the entry ours and sends a delete to the delete handler. Right\n after a load the DB may not take it yet (as MEMORY there): the request\n is let go after DBQ_TICKS. The queue is dropped on a track load.\n * A slot loaded while heard plays in state 0x17, in which the deck takes\n no LOOP IN / OUT; once it plays on from the slot, PLAY with that slot\n (he_play) puts it into state 1 without a jump, as after a recall.\n * The engine also makes beatjump's jumps (he_bj; beatjump writes the\n target and the jump's length to BJ_REQ/BJ_DELTA): the DSP moves its\n playing head in the audio it has decoded (he_dj, he_djw; dspjump.py),\n playing, paused or at cue standby; refused (not enough decoded ahead)\n it is dropped, the press ignored (DJ_RETRY 0). The older ways (a load into BJ_SLOT\n while playing, a memory CALL standing; hj_old and after, he_gap,\n he_rate) are kept but not used: the first left the deck standing in\n 0x17, the second froze the UI after a few. Its core (loads, beat jumps,\n the queue) runs with the item OFF too; the preload only with the item ON\n and the mode on.\n * After a hot cue is set or deleted, 1 at 0x0A4DE7C4 has the waveform\n preparer copy its markers again (0x08BD1224), so they show at once.\n * Not done: a slip hot cue. The player's slip return (command 1, 9, as a\n slip loop's release sends it) does nothing after a CALL; the slip\n position is kept by the player for its own slip actions only.\n * Settings are kept in a journal in flash: the Backup task (0x08A68A96)\n takes a type-5 message (+8 id, +10 value; sent as 0x08A681A0 does, block\n from the pool *0x0A11D7A4, mailbox *0x0A11D7A0) and appends the pair to\n sector 0x7F6000 (0x08A66578). At start-up it keeps the sector if its last\n word is \"FBOK\", else erases it. hk_save sends SAVE_ID; hk_load, on the\n first poll, takes the last SAVE_ID it finds there.\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport cuecolours # noqa: E402\n\nVERSION = '1.3.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nHK_BASE = 0x08FD1800 # after cuecolours' 0x08FCFA00..0x08FD1800\nHK_END = 0x08FD3800\n# a second area after cuecolours' (0x08FD3800..0x08FD4B00): the hot cue\n# engine's state, then the HOT KEYS boxes' pixels\nHK2_BASE = 0x08FD4C00\nHK2_END = 0x08FD6C00\n# a third area after keynotation's (0x08FD7C00..0x08FD8C00): what holds the\n# screen still while hot cues load (he_uf, pub_hook, he_shadow)\nSHADOW = 0x08FD8C00\nSHADOW_END = 0x08FD9C00\nLOAD_PHASE = SHADOW + 0xD0 # byte: 1 while the box shows LOADING (he_lock), +1 the last one drawn\nPIX_LOAD = SHADOW + 0x400 # the LOADING box's pixels (88 x 15, aligned for the blitter)\n# the decoded key bits locked while loading: CALL -/+, DELETE, MEMORY (0x0B2E82C4 0x78); TRACK +,\n# SEARCH -/+ (+1 0xE0); TRACK -, LOOP IN/OUT, RELOOP, 4 BEAT, 0x12:0x02 (+2 0x3F); PLAY, CUE\n# (+3 0x06); SLIP (+7 0x10); hot cue keys 3/4..1/8 (+10 0xF8). Found by holding each panel key.\nLOCK_BASE = 0x0B2E82C4\nLOCK_MASK = (0x78, 0xE0, 0x3F, 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0xF8)\nSH_AFAC = SHADOW # word: where the UI's player-event handler now puts UI_AFAC\nPUB_HELD = SHADOW + 4 # 2 words: the player's position (1/150 s) and its ms as last published free\nSH_OVW = SHADOW + 0x10 # 0x90 bytes: a copy of OVW_SRC for the overview's playhead (he_shadow)\nOVW_SRC = 0x0994488C # PLAYER_CTX + 0x2B0, the player status the overview's playhead reads: +0x14 the position\n # (1/150 s, = PLAYER_CTX + 0x2C4), +0x88 the length\nOVW_LIT = 0x08BD4AC0 # its literal in the overview's updater (0x08BD49A0, 0x08BD4A08)\nUI_STATE = 0x0AF3AF20 # byte: the UI's copy of the player's state (0x17 while a CALL runs: QUANTIZE\n # goes grey and the BPM box white then, as after a CUE press)\nUI_KIND = 0x0AF3B134 # byte: the kind of the last player event (1-7)\nUI_STALE = 0x0B2E3950 # byte: 0xFF = the track's info is stale: the UI fetches it again, blanking BPM,\nUI_STALE3 = 0x0B2E39D9 # QUANTIZE and the memory list meanwhile; three more such bytes\n# The UI's player-event handler writes those six on every state message\n# (0x08B3BCC0-0x08B3BCE6); evt_hook does it instead, or not while the screen\n# is held: its literal of UI_STATE (0x08B3BE1C) now names evt_hook, and its\n# four instructions at 0x08B3BCC0 (r1 = that literal, r7 = -1, r13 = its\n# 0x0AF3AF28 literal, r5 = its 0x0AF3B138 literal) become r13, r1, jsr @r1\n# with r7 = -1 in the delay slot (a literal load there is illegal); evt_hook\n# sets r5. The other five literals name SH_SINK: the handler's own stores\n# go nowhere.\nEVT_HOOK_LIT = 0x08B3BE1C # 0x0AF3AF20\nEVT_HOOK_AT = 0x08B3BCC0\nEVT_HOOK_OLD = bytes.fromhex('56d1 ffe7 56dd 57d5'.replace(' ', '')) # mov.l r1; mov #-1,r7; mov.l r13; mov.l r5\nEVT_HOOK_NEW = bytes.fromhex('57dd 56d1 0b41 ffe7'.replace(' ', '')) # mov.l r13; mov.l r1; jsr @r1; mov #-1,r7\nEVT_R5 = 0x0AF3B138 # what r5 is after the hook\nEVT_SINK_LITS = ((0x08B3BE2C, UI_STALE), (0x08B3BE28, UI_STALE3), (0x08B3BE38, UI_STALE3 + 1),\n (0x08B3BE34, UI_STALE3 + 2), (0x08B3BE30, UI_KIND))\nSH_SINK = SHADOW + 0xB0 # 8 bytes: where those stores land\nGONE_ST = SHADOW + 0xD8 # 2 words: the loaded track is on this player's USB (he_gone), the load\nHK_API = SHADOW + 0xC0 # 2 words: hk_save2, hk_load2 (beatjump calls them for its setting)\nDJ_RETRY = 0 # ticks: a DSP jump refused while playing is asked for again until then (0: dropped, the press ignored)\nHOLD_FLAG = HK2_BASE + 128 + 60 # byte: ST4 +60, 1 while the screen is held\nUI_AFAC = 0x0AF3AFAC # word: the memory a CALL is going to (0 none): the overview's line and\n # marker go there; set by the UI's player-event handler 0x08B3B6D4\n# The handler's switch on the event's kind (0x08B3B762-0x08B3B7C1, r11 =\n# the kind) that writes it becomes a call of afac_hook (its literal in the\n# switch's own room), which writes the same unless the screen is held. The\n# other writers of UI_AFAC (0x08B4E244, 0x08B550E6, 0x08B5F242 ...) are not\n# touched.\nAFAC_SWITCH = 0x08B3B762\nAFAC_SWITCH_END = 0x08B3B7C2\nPUB_CONV = 0x08A28826 # the player's units -> ms conversion (r4 units, r5 speed -> r0 ms)\nPUB_CONV_LIT = 0x089EE07C # its literal in the position publisher (0x089EDE70 -> jsr at 0x089EDE8A),\n # which stores the position at PLAYER_CTX + 0x2C4, +0x2C8 and the ms at +0x2CC\nST2 = HK2_BASE # the engine's state, 128 bytes (see hk_engine)\nST2_SILENT = ST2 + 2 # byte: 1 while a hot cue is loaded silently\nST3 = ST2 + 64 # its second half (beat jumps), past @(disp)'s reach\nBJ_REQ = ST3 # word: a beat jump's target in 1/150 s | 1 << 31, for hk_engine\nBJ_TICK = ST3 + 4 # word: the tick its key was pressed\nBJ_DELTA = 0x08FCF818 # word: the jump's length in ms (beatjump's bj_poll)\nBJX = 0x08FCF820 # the DSP jump's state (see he_dj), 8 words; +32 a jump\n # waiting at cue standby, +36 bj_poll's position meanwhile (he_bj)\nBJ_POSV = BJX + 36\nBJ_PEND = BJX + 40 # word: presses beatjump counted meanwhile (+1 forward, -1 back)\nBJ_SETTLE = 50 # ticks after a memory CALL before he_bj sends the next (BJX +44: when it ended)\nDSPJ_MB = 0x1183E000 # dspjump's mailbox in the DSP's L2\nDJ_TICKS = 30 # an unanswered DSP jump is given up after 0.3 s\nDJ_MAX = 1500 # slots (20 s): longer jumps are not tried\nBJ_GAP0 = 80 # ticks from a playing jump's press to its slot playing, until measured\n\n# state other mods read\nHK_SETTING = HK_BASE # byte: UTILITY > HOT CUE KEYS, 0 OFF, 1 ON\nHK_MODE = HK_BASE + 1 # byte: hot cue key mode, 1 = on\nHK_PRESS = HK_BASE + 2 # byte: 0 idle, 1 QUANTIZE held (hidden), 2 held long (passed on)\nHK_LOADED = HK_BASE + 3 # byte: 1 once the saved setting was read\nHK_T0 = HK_BASE + 4 # word: tick the press began\nHK_QWIDGET = HK_BASE + 8 # word: the QUANTIZE widget, once drawn\nHK_KEYS_PREV = HK_BASE + 12 # byte: the five beat keys' bits last poll\nHK_LEDS_OURS = HK_BASE + 13 # byte: 1 while our LED fields are in the LED state\nHK_RESUME = HK_BASE + 14 # byte: polls since a recall, 0 = none waiting to play\nHK_BLINK = HK_BASE + 15 # byte: the key blinking, A = 1, 0 none\nHK_LEVELS = HK_BASE + 16 # 5 bytes: LED level 0-3 per key, A-E\nHK_HOLD = HK_BASE + 21 # byte: a key without a hot cue held, A = 1, 0 none\nHK_DEL = HK_BASE + 22 # byte: DELETE 0 up, 1 held (hidden), 2 held and used, 3 passed on\nHK_GATE = HK_BASE + 23 # byte: the recalled key, A = 1, watched for its release\nHK_BLINK_T0 = HK_BASE + 24 # word: tick of the blinking key's press\nHK_HOLD_T0 = HK_BASE + 28 # word: tick that key was pressed\n\n# the five BEAT DIVIDE keys left of the jog, top to bottom 3/4 1/3 1/2 1/4\n# 1/8 = hot cues A-E: the panel decoder (0x08B630D0, second layout at\n# 0x08B63510: frame byte 0x11 bits 0x08-0x80) puts them in 0x0B2E82CE bits\n# 0x80 0x40 0x20 0x10 0x08. (The emulator's panel sends the first layout:\n# byte 0x10 bits 0x80 0x40 0x20 = 3/4 1/2 1/8 only.) With SLIP on, held,\n# they show the slip loop (blue LOOP 3/4 ...). PLAY and CUE are 0x0B2E82C7\n# bits 0x04 and 0x02.\nKEY_BEATS = 0x0B2E82CE\n# DELETE (panel 0x13:0x40 in the emulator) is 0x0B2E82C4 bit 0x20 (CALL 0x10,\n# MEMORY 0x40, CALL forward 0x08)\nKEY_DELETE = 0x0B2E82C4\nDEL_BIT = 0x20\nBEAT_BITS = 0xF8\n# The keys' LEDs are 2-bit fields (levels 0-3) of the 24-byte frame the deck\n# sends the panel. SLIP lights them by LED state +11 fields 0-2 and +10\n# fields 2-3 (0x08B38FF0); the five-key panel's frame builder (0x08B654F0)\n# puts those in frame byte 4 (bits 4-5, 2-3, 0-1 <- +11 fields 0, 1, 2; bits\n# 6-7 <- +10 field 3) and byte 5 bits 0-1 (<- +10 field 2). (The emulator's\n# panel takes the other builder, 0x08B656B0, which does not send them.)\n# The firmware rewrites the LED state all the time, so hk_send sets the\n# fields in the finished frame: the panel task's last step jumps to the\n# sender 0x08AF488E (r6 = the frame) through literal 0x08B653A4, its only\n# use; frame byte 22 is the 8-bit end-around-carry sum of bytes 0-21.\n# Which field is which key is for the LED test on a player to tell.\nLED_STATE = 0x0B2E83E8\nSENDER_LIT = 0x08B653A4\nSENDER = 0x08AF488E\nFRAME_LEDS = [(4, 0), (4, 2), (4, 4), (4, 6), (5, 0)] # A-E: frame byte, bit shift of the 2-bit field\nHK_CODE = HK_BASE + 0x20\n\nLONG_TICKS = 80 # 0.8 s\nGATE_TICKS = 50 # a recall held this long goes back to the hot cue when let go\nBLINK_TICKS = 40 # a recalled key's LED: bright, dim, bright, dim, 0.1 s each\nRESUME_POLLS = 127 # how long a recall waits for the cue standby to send PLAY\n\n# recall: the CALL key's handler and the record its message builder reads\nCALL_KEY = 0x08B4F2F2 # the UI's key handler, r4 = 0x100: memory CALL\nCALL_REC = 0x0AF3B28C # +0 event, +4 time in ms, +8 -1, +12 time in 1/150 s\nBUILD = 0x08B3B666 # the UI's player-message builder\nPLAYER_CTX = 0x099445DC # +0x10C play state: 1 playing, 0x17 cue standby\n# 1 here has the waveform preparer (0x08BC670A, through 0x08BD1224) copy the\n# cue markers again, and so cuecolours' mk_copy add the hot cues as they are now\nMARKERS_DIRTY = 0x0A4DE7C4\nSLIP_ON = 0x0AF3AF24 # word: 1 while SLIP is on\nHOT_KEYS = 3 # hot cue keys A-C (3/4, 1/3, 1/2); 1/4 and 1/8 do nothing, dark\nBJ_SLOT = 4 # the DSP's fifth cue slot, unused by the 900NXS: beat jumps only\n# the hot cue engine (hk_engine)\nKEY_PLAYCUE = 0x0B2E82C7 # decoded PLAY 0x04, CUE 0x02\nHPI_READ = 0x089F8ABC # the DSP driver's host port read: r4 = DSP address -> r0\nHPI_WRITE = 0x089F8B20 # its write: r4 = DSP address, r5 = the word\nWV_MID = 0x08BCFBCC # in the detail waveform's draw function 0x08BCFA34, after its memset\nWV_STOCK = bytes.fromhex('6a0540e04fdc05e14fdd9e0e') # sts fpscr,r5 .. mov.l @(r0,r9),r14\nWV_LITS = ((0x08BCFD10, 0x0B7D0760), (0x08BCFD14, 0x08C26780)) # the two it loads\nWV_R12, WV_R13 = 0x0B7D0760, 0x08C26780\nWV_OUT = 0x08BD01EE # where it copies the buffer to the screen\nWV_TICKS = 250 # after a load the waveform waits this long at most\n# The DSP's cue segments (L2, set by the DSP): row s (0 the CUE point, 1-3 hot\n# cues A-C, 4 the beat jumps') at 0x11837CE0 + s * 0x84, four 8-word segments\n# (pre-roll, point, post-roll, ...; +0 their start in 1/75 s) and +0x80 the\n# row's word, 1 once loaded (seen in the emulator's host port log).\nSLOT_ST = 0x11837D60 # + s * 0x84: row s loaded (1)\nSLOT_PT = 0x11837D00 # + s * 0x84: row s's point, 1/75 s\nTRANSPORT = 0x089F8FCA # the DSP manager's transport command (2 = play)\nTRANSPORT_LIT = 0x089F29FC # its literal, used at 0x089F27C6/CA only\nPLAYCMD = 0x089F9306 # the DSP manager's play command\nPLAYCMD_LIT = 0x089F24FC # its literal, used at 0x089F2418 only\nCYCLIC = 0x089F9610 # the DSP manager's cyclic block (0x11837BC0)\nCYCLIC_LIT = 0x089F31F4 # its literal, used at 0x089F309A only\nBACK_TICKS = 2 # the CUE back this long after silent 2 (he_mute)\nMUTE_TICKS = 8 # silent this long at least after the CUE back (he_mute)\nMUTE_MAX = 100 # and 1 s at most\nPRELOAD_TICKS = 10 # the deck stands this long (0.1 s; the DSP's CUE row is checked too)\nPRELOAD_NEAR = 8 # within this many 1/150 s of its CUE point\nPEND_EVERY = 10 # a recall's PLAY again every 0.1 s ...\n# a hot cue set here gets a colour on the USB (the DB task's PCP2 writer,\n# through its only literal) and on the screen: per number, rekordbox's\n# (colour code, R, G, B) pairs as on a rekordbox stick\nPCP2_LIT = 0x08B0FD04\nPCP2 = 0x08B10BE0 # (r4 entry out, r5 cue): +4 hot cue number, +52 extra length, +56 extra\nPCBUF_LEN = 0x60\nPCBLOB_LEN = 28\nPCBLOB_USED = 26\nHOT_COLOURS = [(0x13, 0x00, 0xFF, 0x30), (0x26, 0xFF, 0x5E, 0x00), (0x08, 0x00, 0xC4, 0xFF),\n (0x3C, 0x4D, 0x00, 0xFF), (0x13, 0x00, 0xFF, 0x30), (0x17, 0x33, 0xFF, 0x00),\n (0x2B, 0xFF, 0x00, 0x17), (0x26, 0xFF, 0x5E, 0x00)] # A green, B orange, C blue, ...\n# hot cues kept on the USB: the UI's request sender, and the DB task's cue\n# handlers (its table 0x0899313C: +0x10 register, +0x1C delete)\nUI_SENDER = 0x08B3B03C # (request, device, 0, 0); MEMORY sends 0x7D6, DELETE 0x7D7\nDB_REQ_REG = 0x7D6\nUI_DEVICE = 0x09944900 # word: the device MEMORY sends (0x00010300 with the USB)\nUI_DB_BUSY = 0x0B7C144C # word: a request of the UI's under way\nUI_DB_MODE = 0x08AFCF58 # () -> 1: no cue requests now\nUI_DB_WHICH = 0x08AFD940 # () -> 1 or not: the argument of ...\nUI_DB_READY = 0x08A26340 # (player context, 0/1) -> 1: the track takes cues\nUI_MEDIA = 0x08BAF3F4 # () -> 4: a USB\nUSB_STATE = 0x0A4DE78C # word: the USB port, 3 = a stick mounted (0 once it is gone)\nSTATUS_TAB = 0x0A4F05C8 # every player's last status (player n at + n * STATUS_LEN, words little-endian)\nSTATUS_LEN = 0x11C\nSTATUS_BUF = 0x0B7C9634 # the Pro DJ Link status packet being built (+0x21 this\n # player, +0x28/+0x29 the loaded track's player and slot)\nUI_USB_OK = 0x0A4DE794 + 0x2E0 # word: 1 = cues may be kept on it\nST4 = ST2 + 128 # the USB queue (64 bytes), then the box pixels\nDBQ_N = 6\nDBQ_TICKS = 1000 # a request not taken in 10 s is let go (a USB stick\n # that still writes the last one takes a while)\nDB_REG_LIT = 0x0899314C\nDB_REG = 0x08979D34 # -> dbcl_RegUdbSdCue 0x089764EC\nDB_DEL = 0x0897A0A6 # -> dbcl_DelUsbSdCue 0x08976758\nINJ_WAIT = 10 # a recall from a VINYL pause: the PLAY key 0.1 s after the CUE\nINJ_UNTIL = 14 # ... held 0.04 s\nPEND_TICKS = 250 # ... until the deck plays, 2.5 s at most (a load on the player takes a while)\nLOAD_TICKS = 400 # a hot cue load given up after 4 s\n# The CUE point (= loop in) in 1/150 s: player context +0x41C. The player\n# keeps the audio there ready, so CUE and RELOOP go to it at once; a CALL\n# moves it to the memory (or hot cue) called.\nCUE_POS = PLAYER_CTX + 0x41C\n\n# the setting is kept in the firmware's settings journal in flash: (id, value)\n# pairs of 16-bit words, written by the Backup task on a type-5 message\nJOURNAL = 0xA07F6000 # flash sector 0x7F6000, 8 KiB, read uncached\nJOURNAL_LEN = 0x2000\nSAVE_ID = 0x83E1 # ours; the firmware's are 0x7001-0x9101\nBACKUP_POOL = 0x0A11D7A4 # -> the Backup task's message pool\nBACKUP_MBX = 0x0A11D7A0 # -> its mailbox\nGET_BLOCK = 0x08C0D6DC # (pool, &block, 0) -> 0 ok\nSEND = 0x08C0BA50 # (mailbox, block) -> 0 ok\nRELEASE = 0x08C0D814 # (pool, block)\nMEMSET = 0x08C28038 # (dst, byte, len)\n\nUT_TABLE = 0x08C6C8DC # UTILITY items in the image (.data)\nUT_ITEMS = 17\nUT_ITEM = 52\nUT_COUNT = 0x08C6CC50 # their count, right after them\nUT_RAM = 0x11106D1C # where start-up copies them\nUT_LITS = (0x08BDA97C, 0x08BDACA8, 0x08BDAFD4, 0x08BDB06C)\nONOFF_LIST = 0x11106478 # OFF, ON (SCREEN SAVER's list, still in RAM)\nSHOW_SAVER = 0x08BD9C7C # SCREEN SAVER's +28\nGENERIC = (0x08BD912A, 0x08BD9F44, 0x08BDA04A, 0x08BDA658, 0x08BDA6F2) # +24 +32 +36 +44 +48\n\nDECODER_LIT = 0x08B622A8 # the UI poller's literal for the panel decoder\nDECODER = 0x08B63278\nKEY_QUANTIZE = 0x0B2E82CB # bit 0x80\nTICK = 0x0B3252A8 # 10 ms\n\nDRAW_METHOD = 0x080B27DC # bitmap widgets' draw method\nDRAW = 0x08B2F12A\nDRAW_RES_LIT = 0x08B2F384 # its literal for the resource resolver\nRESOLVE = 0x08B2E2E4\nRES_TABLE = 0x00100000 # resource records in the image (offset), 44 bytes\nRES_QUANTIZE = 0x1A8 # 88 x 15, red\nRES_QUANTIZE_OFF = 0x1A9 # its grey outline\nRES_ANCHOR = 0x12A # REMAIN: its siblings hold the QUANTIZE widget\nRES_DIGITS = (0xCC, 0xDF) # the time display's digits, drawn in REMAIN and ELAPSED\nRES_HK = 0x7F00 # ours, only hk_res knows it\nBOX_DX = 92\n\nLABEL = 'HOT CUE KEYS'\n\n# 7 x 7 letters for HOT KEYS, in the QUANTIZE label's style\nGLYPHS = {\n 'H': ['X.....X', 'X.....X', 'X.....X', 'XXXXXXX', 'X.....X', 'X.....X', 'X.....X'],\n 'O': ['.XXXXX.', 'X.....X', 'X.....X', 'X.....X', 'X.....X', 'X.....X', '.XXXXX.'],\n 'T': ['XXXXXXX', '...X...', '...X...', '...X...', '...X...', '...X...', '...X...'],\n 'K': ['X....X.', 'X...X..', 'X..X...', 'XXX....', 'X..X...', 'X...X..', 'X....X.'],\n 'E': ['XXXXXX', 'X.....', 'X.....', 'XXXXX.', 'X.....', 'X.....', 'XXXXXX'],\n 'Y': ['X.....X', '.X...X.', '..X.X..', '...X...', '...X...', '...X...', '...X...'],\n 'S': ['.XXXXX.', 'X......', 'X......', '.XXXXX.', '......X', '......X', '.XXXXX.'],\n 'L': ['X.....', 'X.....', 'X.....', 'X.....', 'X.....', 'X.....', 'XXXXXX'],\n 'A': ['..XXX..', '.X...X.', 'X.....X', 'X.....X', 'XXXXXXX', 'X.....X', 'X.....X'],\n 'D': ['XXXXX..', 'X....X.', 'X.....X', 'X.....X', 'X.....X', 'X....X.', 'XXXXX..'],\n 'I': ['XXX', '.X.', '.X.', '.X.', '.X.', '.X.', 'XXX'],\n 'N': ['X.....X', 'XX....X', 'X.X...X', 'X..X..X', 'X...X.X', 'X....XX', 'X.....X'],\n 'G': ['.XXXXX.', 'X......', 'X......', 'X...XXX', 'X.....X', 'X.....X', '.XXXXX.'],\n}\n\n\ndef box_pixels(stock, res, background, ink, text='HOT KEYS'):\n \"\"\"88 x 15 RGB565: resource `res` of the stock image with its text taken\n out and `text` written in `ink`.\"\"\"\n rec = RES_TABLE + res * 44\n w, h = struct.unpack_from(' HK_SETTING, +1 mode, +2 press, +3 loaded, +4 t0\n mov.b @(3,r1),r0\n tst r0,r0\n bf hp_loaded\n mov.l P_load,r2 ; the first time: the saved setting (r1 is\n jsr @r2 ; kept: hk_load takes it, r2 it sets anew)\n nop\nhp_loaded:\n mov.b @r1,r2\n tst r2,r2\n bt hp_off\n mov.l P_key,r3\n mov.b @r3,r2\n extu.b r2,r2\n mov.l P_tick,r6\n mov.l @r6,r6 ; now\n mov.b @(2,r1),r0\n extu.b r0,r0\n cmp/eq #2,r0\n bt hp_long\n cmp/eq #1,r0\n bt hp_held\n ; idle: a press starts\n mov r2,r0\n tst #128,r0\n bt hp_ret\n mov #1,r0\n mov.b r0,@(2,r1)\n mov.l r6,@(4,r1)\n bra hp_hide\n nop\nhp_held:\n mov r2,r0\n tst #128,r0\n bt hp_short ; let go before LONG_TICKS\n mov.l @(4,r1),r0\n sub r0,r6\n mov #LONG_TICKS,r0\n cmp/hs r0,r6\n bf hp_hide ; still short\n mov #2,r0 ; long: QUANTIZE from now on\n bra hp_ret\n mov.b r0,@(2,r1)\nhp_short:\n mov.b @(1,r1),r0 ; the mode flips\n xor #1,r0\n mov.b r0,@(1,r1)\n mov #0,r0\n mov.b r0,@(2,r1)\n mov.l @(8,r1),r4 ; and the screen is redrawn, as the widgets'\nhp_root: ; visibility setter (0x08B2AFB4) asks for it:\n tst r4,r4 ; the root (type 20) gets +0x58 = 1\n bt hp_ret\n mov.b @(4,r4),r0\n cmp/eq #20,r0\n bt hp_mark\n bra hp_root\n mov.l @(8,r4),r4\nhp_mark:\n mov #1,r2\n mov #88,r0\n bra hp_ret\n mov.l r2,@(r0,r4)\nhp_long:\n mov r2,r0\n tst #128,r0\n bf hp_ret ; still held: the deck has it\n mov #0,r0\n bra hp_ret\n mov.b r0,@(2,r1)\nhp_hide:\n mov r2,r0\n and #127,r0\n mov.b r0,@r3\nhp_ret:\n bsr hk_keys ; the five beat keys, in the hot cue key mode\n nop\n mov r7,r0\n lds.l @r15+,pr\n rts\n nop\nhp_off:\n mov #0,r0 ; item OFF: no press of ours is under way\n bra hp_ret\n mov.b r0,@(2,r1)\n\n\n .align 4\nP_key: .long KEY_QUANTIZE\nP_prev: .long PREV_DECODER\nP_setting: .long HK_SETTING\nP_tick: .long TICK\nP_load: .long hk_load\n\n; ---- hk_keys: with the mode on, the five BEAT DIVIDE keys are ours (hidden\n; from the deck): a press recalls its hot cue, and the LEDs show which keys\n; have one (hk_send puts the levels in the frame while +13 is 1). r7 is kept.\nhk_keys:\n sts.l pr,@-r15\n mov.l r7,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l K_setting,r10\n bsr hk_resume ; a recall waiting to play\n nop\n bsr hk_engine ; the hot cue slots: preload, loads under way\n nop\n mov.b @r10,r0\n mov r0,r1\n mov.b @(1,r10),r0\n and r1,r0 ; item ON and the mode on\n tst r0,r0\n bf hkk_on\n bra hkk_off\n nop\nhkk_on:\n ; DELETE: hidden while held; a hot cue key pressed meanwhile loses its\n ; hot cue; let go without one, the deck gets the press then (one poll)\n mov.l K_keydel,r3\n mov.b @r3,r2\n mov r10,r1\n add #22,r1 ; HK_DEL\n mov.b @r1,r0\n extu.b r0,r7\n mov r7,r0\n cmp/eq #3,r0\n bt hkd_clr ; passed on last poll: done\n mov r2,r0\n tst #DEL_BIT,r0\n bt hkd_up\n and #DEL_KEEP,r0\n mov.b r0,@r3 ; the deck does not see it\n tst r7,r7\n bf hkd_done\n mov #1,r0 ; held\n bra hkd_done\n mov.b r0,@r1\nhkd_up:\n mov r7,r0\n cmp/eq #1,r0\n bf hkd_clr\n mov r2,r0 ; let go, no hot cue deleted: the deck's press\n or #DEL_BIT,r0\n mov.b r0,@r3\n mov #3,r0\n bra hkd_done\n mov.b r0,@r1\nhkd_clr:\n mov #0,r0\n mov.b r0,@r1\nhkd_done:\n mov.l K_beats,r3\n mov.b @r3,r2\n extu.b r2,r2\n mov r2,r0\n and #BEAT_BITS,r0\n mov r0,r4 ; held now\n mov r2,r0\n and #HIDE_MASK,r0\n mov.b r0,@r3 ; the deck does not see them\n mov.b @(12,r10),r0\n extu.b r0,r5\n mov r4,r0\n mov.b r0,@(12,r10)\n not r5,r5\n and r4,r5 ; pressed since the last poll\n mov.l K_st2,r1 ; none while hot cues load (steps 1-6): the\n mov.b @(1,r1),r0 ; keys are locked as PLAY and CUE are\n extu.b r0,r0\n add #-1,r0\n mov #6,r1\n cmp/hs r1,r0\n bt hkk_free\n mov #0,r5\nhkk_free:\n mov #-128,r6\n extu.b r6,r6 ; 0x80 = A\n mov.l K_hot,r8 ; its hot cue: +0 time in 1/150 s, -1 none\n mov #1,r9 ; key number, A = 1\nhkk_key:\n tst r6,r5\n bt hkk_next\n mov #HOT_KEYS,r0 ; 1/4 and 1/8 (D, E): nothing\n cmp/hi r0,r9\n bt hkk_next\n mov r10,r1\n add #22,r1\n mov.b @r1,r0 ; DELETE held (1, or 2 once used)?\n cmp/eq #1,r0\n bt hkk_del\n cmp/eq #2,r0\n bf hkk_cue\nhkk_del:\n mov #2,r0\n mov.b r0,@r1\n mov.l @r8,r0 ; one there: deleted on the USB too\n cmp/eq #-1,r0\n bt hkk_del2\n mov.l r5,@-r15\n mov.l r6,@-r15\n mov r0,r6\n mov #2,r4\n bsr hk_dbop\n mov r9,r5\n mov.l @r15+,r6\n mov.l @r15+,r5\nhkk_del2:\n mov #-1,r0\n mov.l r0,@r8 ; no hot cue on this key now\n mov.l K_mkdirty,r1 ; the waveforms' markers made again\n mov #1,r0\n mov.l r0,@r1\n mov r9,r0\n mov.b r0,@(15,r10) ; it blinks, then stays off\n mov.l K_tick,r1\n mov.l @r1,r1\n bra hkk_next\n mov.l r1,@(24,r10)\nhkk_cue:\n mov.l @r8,r4\n mov r4,r0\n cmp/eq #-1,r0\n bf hkk_call\n mov r10,r1 ; none: held long while paused, it is set\n add #21,r1\n mov r9,r0\n mov.b r0,@r1\n mov.l K_tick,r1\n mov.l @r1,r1\n bra hkk_next\n mov.l r1,@(28,r10)\nhkk_call:\n mov r9,r0 ; the first one pressed with a hot cue:\n mov.b r0,@(15,r10) ; its LED blinks from now\n mov r10,r1\n add #23,r1\n mov.b r0,@r1 ; and its release is watched (gate)\n mov.l K_tick,r1\n mov.l @r1,r1\n bsr hk_hot ; r4 = its time, r9 = the key\n mov.l r1,@(24,r10)\n bra hkk_leds\n nop\nhkk_next:\n add #8,r8\n add #1,r9\n shlr r6\n mov #4,r0\n cmp/hi r0,r6\n bt hkk_key ; 0x80 down to 0x08\n ; gate: the recalled key let go after GATE_TICKS: back to its hot cue,\n ; standing there (a CALL while playing stops at the cue)\n mov r10,r1\n add #23,r1\n mov.b @r1,r0\n extu.b r0,r9 ; the key, A = 1, 0 none\n tst r9,r9\n bt hkk_hold\n mov #-128,r6\n extu.b r6,r6\n mov #1,r0\n sub r9,r0\n shld r0,r6 ; its bit\n mov.b @(12,r10),r0\n tst r6,r0\n bf hkk_hold ; still held\n mov #0,r0\n mov.b r0,@r1\n mov.l K_tick,r4\n mov.l @r4,r4\n mov.l @(24,r10),r0\n sub r0,r4\n mov #GATE_TICKS,r0\n cmp/hs r0,r4\n bf hkk_hold ; a short press: it plays on\n mov.l K_hot,r8\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r8),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt hkk_hold\n bsr hk_gate ; r4 = its time, r9 = the key\n nop\nhkk_hold:\n ; a key without a hot cue held: at LONG_TICKS, paused, it gets one here\n mov r10,r1\n add #21,r1\n mov.b @r1,r0\n extu.b r0,r9 ; the key, A = 1, 0 none\n tst r9,r9\n bt hkk_leds\n mov #-128,r6\n extu.b r6,r6\n mov #1,r0\n sub r9,r0\n shld r0,r6 ; its bit: 0x80 >> (key - 1)\n mov.b @(12,r10),r0\n tst r6,r0\n bt hkk_drop ; let go before\n mov.l K_tick,r4\n mov.l @r4,r4\n mov.l @(28,r10),r0\n sub r0,r4\n mov #LONG_TICKS,r0\n cmp/hs r0,r4\n bf hkk_leds ; not yet\n mov.l K_ctx,r1\n mov #67,r0\n shll2 r0 ; 0x10C: play state\n mov.l @(r0,r1),r0\n cmp/eq #1,r0\n bt hkk_drop ; playing: nothing\n mov #-79,r0\n extu.b r0,r0\n shll2 r0 ; 0x2C4: the position in 1/150 s\n mov.l @(r0,r1),r4\n mov.l K_hot,r8\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n add r0,r8 ; the key's entry\n mov.l r4,@r8\n mov.l K_hotcol,r0 ; the colour it gets on the USB too\n add r9,r0\n add r9,r0\n add #-2,r0\n mov.w @r0,r0\n mov.w r0,@(4,r8)\n mov r9,r0\n mov.b r0,@(6,r8)\n mov.b r0,@(15,r10) ; blinks\n mov.l K_mkdirty,r1 ; the waveforms' markers made again\n mov #1,r0\n mov.l r0,@r1\n mov.l K_tick,r1\n mov.l @r1,r1\n mov.l r1,@(24,r10)\n mov.l @r8,r6 ; and saved on the USB\n mov #1,r4\n bsr hk_dbop\n mov r9,r5\n mov.l K_st4,r1 ; and loaded into its slot right away (he_step)\n add #61,r1\n mov r9,r0\n mov.b r0,@r1\nhkk_drop:\n mov r10,r1\n add #21,r1\n mov #0,r0\n mov.b r0,@r1\nhkk_leds:\n ; LED levels: 0 no hot cue, 1 one; the key blinking: 3 1 3 1, 0.1 s each\n mov.l K_tick,r4\n mov.l @r4,r4\n mov.l @(24,r10),r0\n sub r0,r4 ; since the press\n mov.b @(15,r10),r0\n extu.b r0,r7 ; the key blinking, 0 none\n mov #BLINK_TICKS,r0\n cmp/hs r0,r4\n bf hkk_bl\n mov #0,r7 ; done\n mov #0,r0\n mov.b r0,@(15,r10)\nhkk_bl:\n mov #3,r8 ; bright in the 1st and 3rd tenth of a second\n mov #10,r0\n cmp/hs r0,r4\n bf hkk_lv\n mov #1,r8\n mov #20,r0\n cmp/hs r0,r4\n bf hkk_lv\n mov #3,r8\n mov #30,r0\n cmp/hs r0,r4\n bf hkk_gl\n mov #1,r8\nhkk_gl:\n ; a gate: its key held GATE_TICKS or longer (let go, the deck goes back\n ; to the hot cue) blinks slowly, bright and dim, until it is let go\n mov r10,r1\n add #23,r1\n mov.b @r1,r0\n extu.b r0,r1 ; the gate's key, 0 none\n tst r1,r1\n bt hkk_lv\n mov #GATE_TICKS,r0\n cmp/hs r0,r4\n bf hkk_lv\n mov #-128,r2\n extu.b r2,r2\n mov #1,r0\n sub r1,r0\n shld r0,r2 ; its bit: 0x80 >> (key - 1)\n mov.b @(12,r10),r0\n tst r2,r0\n bt hkk_lv ; let go already\n mov r1,r7\n mov.l K_tick,r0\n mov.l @r0,r0\n tst #32,r0\n bt/s hkk_lv\n mov #3,r8\n mov #1,r8\nhkk_lv:\n mov.l K_hot,r2\n mov r10,r3\n add #16,r3 ; the levels for hk_send\n mov #1,r9\n mov #5,r6\nhkk_led:\n mov.l @r2,r0\n cmp/eq #-1,r0\n bt hkk_none\n mov.l r2,@-r15 ; a hot cue: dim when its slot holds it,\n mov.l r3,@-r15 ; else blinking slowly (not ready yet)\n mov r0,r4\n bsr he_ledlv\n mov r9,r5\n mov.l @r15+,r3\n bra hkk_lvl\n mov.l @r15+,r2\nhkk_none:\n mov #0,r0\nhkk_lvl:\n cmp/eq r9,r7\n bf hkk_st\n mov r8,r0\nhkk_st:\n mov #HOT_KEYS,r1 ; D and E stay dark\n cmp/hi r1,r9\n bf hkk_st2\n mov #0,r0\nhkk_st2:\n mov.b r0,@r3\n add #8,r2\n add #1,r3\n dt r6\n bf/s hkk_led\n add #1,r9\n bra hkk_out\n mov #1,r0\nhkk_off:\n mov #0,r0 ; the mode is off: the LEDs are the deck's\n mov.b r0,@(12,r10)\n mov r10,r1\n add #22,r1\n mov.b r0,@r1 ; no DELETE or gate of ours under way\n mov.b r0,@(1,r1)\nhkk_out:\n mov.b r0,@(13,r10)\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n mov.l @r15+,r7\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_recall: r4 = a hot cue's time in 1/150 s. The memory CALL, as the\n; CALL key sends it (handler 0x08B4F2F2, event 0x100), with the time in ms\n; put where the handler's message builder reads the memory's: the UI's CALL\n; record +4 (and +8 = -1, +12 the time in 1/150 s, as the UI leaves them).\n; The deck then stands at the cue; hk_resume starts it if r5 is 1.\n; hk_callat: the CALL always (a beat jump's: right after a CALL the CUE point\n; word still holds the one before, which may be this target).\nhk_callat:\n sts.l pr,@-r15\n bra hrc_call\n nop\nhk_recall:\n sts.l pr,@-r15\n mov.l K_cuepos,r1 ; the CUE point is this hot cue already (a CALL\n mov.l @r1,r0 ; leaves it there): CUE goes to it at once, as\n cmp/eq r4,r0 ; the CUE key does while playing, then PLAY\n bf hrc_call\n mov.l r5,@-r15\n bsr hk_cue\n nop\n mov.l @r15+,r0\n tst r0,r0\n bt hrc_out ; a gate's release: standing there is it\n bsr hr_play\n nop\n bra hrc_out\n nop\nhrc_call:\n mov r5,r0\n mov.b r0,@(14,r10) ; 1: play once it stands at the cue\n mov.l K_callrec,r2\n mov.l r4,@(12,r2)\n mov r4,r1\n shll2 r1\n mov r1,r3\n shll2 r3\n add r3,r1 ; 20 * units\n add #2,r1 ; rounded up, so the builder gets the units back\n mov.l K_third,r3\n dmulu.l r3,r1\n sts mach,r1\n shlr r1 ; / 3: ms\n mov.l r1,@(4,r2)\n mov #-1,r0\n mov.l r0,@(8,r2)\n mov #1,r4\n shll8 r4 ; 0x100: CALL\n mov.l r4,@r2\n mov.l K_callkey,r1\n jsr @r1\n nop\n mov.l K_fpmask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\nhrc_out:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_cue: what the CUE key sends while playing (0x08B47250-0x08B47290):\n; 0x0AF3AF28 = 7, 0x0AF3AEEC and +4 = 0, command 3, 2, 0, 1 with the byte at\n; 0x0AF3AF3C on the stack. The deck goes back to its CUE point and stands.\nhk_cue:\n sts.l pr,@-r15\n mov #0,r0\n mov.b r0,@(14,r10) ; no PLAY of a CALL pending\n mov.l K_af28,r1\n mov #7,r0\n mov.l r0,@r1\n mov.l K_aeec,r1\n mov #0,r0\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov.l K_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #3,r4\n mov #2,r5\n mov.l K_build,r1\n jsr @r1\n mov #1,r7\n add #8,r15\n mov.l K_fpmask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_resume: r10 -> HK_SETTING. After a recall the player stops at the\n; cue (state 0x17 at context +0x10C); then PLAY goes as the PLAY key's\n; handler sends it from cue standby (0x08B49834): 0x0AF3AF28 = 3, command 1,\n; 2 (1 if 0x0AF3B1D3 is 1), the byte at 0x0AF3AF3C on the stack.\nhk_resume:\n mov.b @(14,r10),r0\n extu.b r0,r0\n tst r0,r0\n bt hr_none\n mov.l K_ctx,r1\n mov #67,r0\n shll2 r0 ; 0x10C\n mov.l @(r0,r1),r0\n cmp/eq #23,r0\n bt hr_play\n mov.b @(14,r10),r0 ; not yet: give up after RESUME_POLLS\n add #1,r0\n cmp/eq #RESUME_POLLS,r0\n bf hr_keep\n mov #0,r0\nhr_keep:\n rts\n mov.b r0,@(14,r10)\nhr_play:\n sts.l pr,@-r15\n mov #0,r0\n mov.b r0,@(14,r10)\n mov.l K_af28,r1\n mov #3,r0\n mov.l r0,@r1\n mov.l K_b1d3,r1\n mov.b @r1,r0\n cmp/eq #1,r0\n bt/s hr_sub\n mov #1,r5\n mov #2,r5\nhr_sub:\n mov.l K_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #1,r4\n mov.l K_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n mov.l K_fpmask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\n lds.l @r15+,pr\nhr_none:\n rts\n nop\n\n .align 4\nK_af28: .long 0x0AF3AF28\nK_af3c: .long 0x0AF3AF3C\nK_b1d3: .long 0x0AF3B1D3\nK_beats: .long KEY_BEATS\nK_build: .long BUILD\nK_callkey: .long CALL_KEY\nK_callrec: .long CALL_REC\nK_ctx: .long PLAYER_CTX\nK_fpmask: .long 0xFFE7FFFF\nK_hot: .long HOT\nK_cuepos: .long CUE_POS\nK_aeec: .long 0x0AF3AEEC\nK_keydel: .long KEY_DELETE\nK_mkdirty: .long MARKERS_DIRTY\nK_green: .long GREEN\nK_hotcol: .long HOTCOL\nK_key: .long KEY_QUANTIZE\nK_prev: .long PREV_DECODER\nK_setting: .long HK_SETTING\nK_third: .long 0xAAAAAAAB\nK_tick: .long TICK\nK_st4: .long ST4\nK_st2: .long ST2\n\n\n; ==== the hot cue engine ====================================================\n; The firmware has the CDJ-2000NXS's hot cues, unused on the 900NXS: the DSP\n; keeps up to three hot cue segments (DSP slots 1-3, beside slot 0, the CUE\n; point), decoded and ready, and plays one at once:\n; load: CALL (commands 22, 23) with slot 4+s and the UI's CALL record\n; event (3+s) << 16 | 0x100 (0x100: load), the time as for a\n; memory CALL. The player loads DSP slot s and plays from it.\n; recall: CUE (command 3) with slot 4+s, then PLAY (command 1) with slot\n; 4+s: the deck is at the hot cue and plays at once, the stream\n; carried on behind it as after CUE and PLAY.\n; After a track loads (cuecolours counts its loads at HOT+7) hot cues A-C\n; are loaded silently (hk_wrap_tr and hk_wrap_pc keep the player's PLAY from\n; the DSP meanwhile), then CUE takes the deck back to its CUE point at once.\n; A hot cue set later loads into its slot on its first press.\n; ST2: +0 loads seen, +1 step (0 idle, 0x80 waiting to preload, 1-3 loading\n; slot 1-3, 4 back, 5 going back), +2 silent, +3 busy (0, slot 1-3, 9 the\n; CALL back), +4 PLAY pressed meanwhile, +5 use counter, +6..+8 slot 1-3's\n; last use, +9..+11 slot 1-3's key (0 none), +12 tick the load began, +16\n; its target (1/150 s), +20 the position to go back to, +24/+28/+32 slot\n; 1-3's time, +36 1 once a preload moved the deck, +40 the position last\n; poll, +44 the key of the load under way, +48 the slot a gate left the deck\n; standing at (0 none), +52 the PLAY bit we took, hidden until let go, +56\n; the tick the gate parked it, +60 a key pressed during a load (0 none).\n\n; ---- hk_engine: r10 -> HK_SETTING, every poll\nhk_engine:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r11,@-r15\n mov.l H_st2,r11\n mov.l H_hot,r1 ; a new load?\n mov.b @(7,r1),r0\n mov r0,r2\n mov.b @r11,r0\n cmp/eq r2,r0\n bt he_same\n mov r2,r0\n mov.b r0,@r11\n mov #0,r0\n mov.b r0,@(2,r11) ; not silent\n mov.b r0,@(3,r11) ; nothing under way\n mov.b r0,@(4,r11)\n mov.b r0,@(9,r11) ; no slot holds anything\n mov.b r0,@(10,r11)\n mov.b r0,@(11,r11)\n mov.l r0,@(36,r11)\n mov.l r0,@(48,r11) ; not parked\n mov.l r0,@(60,r11) ; no key waiting\n mov r11,r1\n add #64,r1\n mov.l r0,@r1 ; no beat jump waiting\n mov.l r0,@(20,r1) ; speed: measured again\n mov.l r0,@(24,r1)\n mov.l r0,@(44,r1) ; no PLAY to repeat\n add #64,r1\n mov.l r0,@r1 ; nothing queued for the USB (another track)\n mov.l H_bjx,r1 ; no standing jump waiting either\n mov.l r0,@(32,r1)\n mov.l r0,@(36,r1)\n mov.l r0,@(40,r1)\n mov #-128,r0\n mov.b r0,@(1,r11) ; preload once the deck stands\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov r11,r1 ; ST4 +56: the tick it loaded (he_wv)\n add #127,r1\n add #57,r1\n mov.l r0,@r1\nhe_same:\n bsr he_gone ; the track's USB stick gone: its hot cues too\n nop\n mov.l H_lock,r0 ; loading: the deck's keys locked, LOADING\n jsr @r0\n nop\n mov.l H_shadow,r0 ; the CALL the player reports, unless held\n jsr @r0\n nop\n bsr he_uf ; the UI's position held while preloading\n nop\n bsr he_wv ; the big waveform hidden meanwhile\n nop\n bsr he_busy ; a load or the CALL back under way\n nop\n bsr he_step ; the preload\n nop\n bsr he_mute ; silent until the deck stands again\n nop\n bsr he_rate ; the deck's speed\n nop\n bsr he_pend ; a recall's PLAY, again until it plays\n nop\n bsr he_inj ; a recall from a VINYL pause: the PLAY key\n nop\n bsr he_dbq ; hot cues set or deleted, to the USB\n nop\n ; a PLAY we took stays hidden from the deck until it is let go\n mov.l H_keypc,r3\n mov.l @(52,r11),r0\n tst r0,r0\n bt he_park\n mov r0,r2\n mov.b @r3,r0\n tst r2,r0\n bf he_hide\n mov #0,r0\n bra he_park\n mov.l r0,@(52,r11)\nhe_hide:\n not r2,r2\n and r2,r0\n mov.b r0,@r3\nhe_park:\n ; a gate left the deck standing at slot s's hot cue: PLAY plays slot s\n ; (the deck's own PLAY would start from the CUE point)\n mov.l @(48,r11),r0\n tst r0,r0\n bt he_pre\n mov r0,r9\n bsr he_now\n nop\n mov.l @(56,r11),r1\n sub r1,r0\n mov r0,r8 ; ticks since it parked\n bsr he_pos\n nop\n mov r0,r2\n mov r9,r0\n shll2 r0\n add #20,r0\n mov.l @(r0,r11),r0\n cmp/eq r2,r0\n bf he_away\n mov r1,r0\n cmp/eq #1,r0\n bf he_parked ; standing at it\nhe_away:\n mov #50,r1 ; on its way for 0.5 s; then playing or moved\n cmp/hs r1,r8 ; on: the deck's PLAY again\n bt he_unpark\n bra he_pre\n nop\nhe_parked:\n mov.l H_keypc,r3\n mov.b @r3,r0\n tst #4,r0\n bt he_pre\n and #0xFB,r0\n mov.b r0,@r3\n mov #4,r0\n mov.l r0,@(52,r11)\n bsr he_play\n mov r9,r5\nhe_unpark:\n mov #0,r0\n mov.l r0,@(48,r11)\nhe_pre:\n mov.l H_djw,r0 ; a DSP jump's result\n jsr @r0\n nop\n bsr he_bj ; a beat jump\n nop\n ; a key pressed while a load was under way: now\n mov.l @(60,r11),r0\n tst r0,r0\n bt he_nq\n mov r0,r9\n mov.b @(3,r11),r0\n tst r0,r0\n bf he_nq\n mov.b @(1,r11),r0\n extu.b r0,r0\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bt he_q\n tst r0,r0\n bf he_nq\nhe_q:\n mov #0,r0\n mov.l r0,@(60,r11)\n mov.l H_hot,r1\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r1),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt he_nq\n bsr hk_hot\n nop\nhe_nq:\n ; while preloading, the deck's PLAY and CUE do nothing (locked)\n mov.b @(1,r11),r0\n extu.b r0,r0\n tst r0,r0\n bt he_out\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bt he_out\n mov.l H_keypc,r1\n mov.b @r1,r0\n and #0xF9,r0 ; PLAY 0x04, CUE 0x02 hidden\n mov.b r0,@r1\nhe_out:\n mov.l @r15+,r11\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nH_st2: .long ST2\nH_hot: .long HOT\nH_keypc: .long KEY_PLAYCUE\nH_djw: .long he_djw\nH_shadow: .long he_shadow\nH_lock: .long he_lock\nH_bjx: .long BJX\n\n; ---- he_gone: r11 -> ST2. The loaded track's USB stick gone (USB STOP, pulled\n; out): its hot cues go with it, they would play the little the DSP still\n; holds and then EMERGENCY LOOP. HOT_TABLE emptied, no slot holds anything,\n; no preload, the markers drawn again. Where the track is from is learnt once\n; per load (GONE_ST: +0 0 not yet, 1 this player's USB, 0x100 | n player n's\n; USB over LINK; +4 the load it is for) from UI_DEVICE (player << 16 | slot\n; << 8, slot 3 a USB) and the status packet's +0x21 (this player; 0 without\n; a LAN cable, the packet is not built then: the track is ours). Gone: ours\n; once USB_STATE is no longer 3; player n's once its last status (STATUS_TAB)\n; reports its USB not there (+0x6C not 0, 4 when pulled or stopped).\nhe_gone:\n sts.l pr,@-r15\n mov.l N_gst,r7\n mov.l N_hot,r1 ; a new load: not known yet\n mov.b @(7,r1),r0\n extu.b r0,r0\n mov.l @(4,r7),r2\n cmp/eq r0,r2\n bt hg_known\n mov.l r0,@(4,r7)\n mov #0,r2\n mov.l r2,@r7\nhg_known:\n mov.l @r7,r0\n tst r0,r0\n bf hg_check\n mov.l N_dev,r1 ; the track's device: player << 16 | slot << 8\n mov.l @r1,r3\n mov r3,r0\n shlr8 r0\n extu.b r0,r0\n cmp/eq #3,r0 ; from a USB\n bf hg_ret\n shlr16 r3\n extu.b r3,r3 ; whose\n mov.l N_sbuf,r1\n mov #33,r0\n mov.b @(r0,r1),r2 ; this player\n extu.b r2,r2\n tst r2,r2\n bt hg_mine\n cmp/eq r2,r3\n bf hg_link\nhg_mine:\n mov.l N_usb,r1 ; ours, with the stick there\n mov.l @r1,r0\n cmp/eq #3,r0\n bf hg_ret\n mov #1,r0\n bra hg_ret\n mov.l r0,@r7\nhg_link:\n bsr hg_rec ; player n's, its USB there\n mov r3,r0\n tst r0,r0\n bf hg_ret\n mov #1,r0\n shll8 r0\n or r3,r0\n bra hg_ret\n mov.l r0,@r7\nhg_check:\n cmp/eq #1,r0\n bf hg_chlink\n mov.l N_usb,r1 ; ours: gone?\n mov.l @r1,r0\n cmp/eq #3,r0\n bt hg_ret\n bra hg_clear\n nop\nhg_chlink:\n bsr hg_rec ; player n's: gone?\n extu.b r0,r0\n tst r0,r0\n bt hg_ret\nhg_clear:\n mov.l N_hot,r1\n mov #8,r2\n mov #-1,r3\n mov #0,r4\nhg_loop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt hg_next\n mov.l r3,@r1 ; a hot cue: gone\n mov #1,r4\nhg_next:\n dt r2\n bf/s hg_loop\n add #8,r1\n tst r4,r4\n bt hg_ret ; none left already\n mov #0,r0\n mov.b r0,@(1,r11) ; no preload\n mov.b r0,@(2,r11) ; not silent\n mov.b r0,@(3,r11) ; nothing under way\n mov.b r0,@(9,r11) ; no slot holds anything\n mov.b r0,@(10,r11)\n mov.b r0,@(11,r11)\n mov.l r0,@(48,r11) ; not parked\n mov.l r0,@(60,r11) ; no key waiting\n mov.l N_dirty,r1 ; the markers drawn again\n mov #1,r0\n mov.l r0,@r1\nhg_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; hg_rec: r0 = player n -> r0 = its last status's USB field (+0x6C, low byte; 0 there)\nhg_rec:\n mov.l N_tab,r1\n mov.l N_len,r2\n mulu.w r0,r2\n sts macl,r2\n add r2,r1\n mov #0x6C,r0\n mov.l @(r0,r1),r0\n rts\n extu.b r0,r0\n\n .align 4\nN_tab: .long STATUS_TAB\nN_len: .long STATUS_LEN\nN_sbuf: .long STATUS_BUF\nN_usb: .long USB_STATE\nN_hot: .long HOT\nN_dirty: .long MARKERS_DIRTY\nN_gst: .long GONE_ST\nN_dev: .long UI_DEVICE\n\n; ---- he_pos: r0 = the position (1/150 s), r1 = the play state\nhe_pos:\n mov.l F_ctx,r1\n mov #-79,r0\n extu.b r0,r0\n shll2 r0 ; 0x2C4\n mov.l @(r0,r1),r2\n mov #67,r0\n shll2 r0 ; 0x10C\n mov.l @(r0,r1),r1\n rts\n mov r2,r0\n\n; ---- he_fp: the firmware's fpscr fix after a call (r0 kept)\nhe_fp:\n mov.l F_fpmask,r1\n sts fpscr,r2\n and r1,r2\n rts\n lds r2,fpscr\n\n; ---- he_row: r0 = s -> r0 = s * 0x84, the offset of the DSP's row s (r1 used)\nhe_row:\n mov r0,r1\n shll2 r0\n shll2 r0\n shll2 r0\n shll r0 ; s * 128\n shll2 r1\n rts\n add r1,r0 ; + s * 4\n\n; ---- he_busy: r11 -> ST2. A slot load is done once 0.2 s have passed, the\n; DSP has the slot (its row's word, cleared by he_load, is 1 and its point\n; is the target) and, heard, the deck plays on from it (up to 2 s past it).\n; The way back is done once the deck stands at its target. LOAD_TICKS at\n; most.\nhe_busy:\n sts.l pr,@-r15\n mov.b @(3,r11),r0\n extu.b r0,r9\n tst r9,r9\n bt hb_ret\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov r0,r8 ; ticks since it began\n mov.l F_loadt,r1\n cmp/hs r1,r0\n bf hb_intime\n bra hb_fail\n nop\nhb_intime:\n bsr he_pos\n nop\n mov.l @(16,r11),r2\n sub r2,r0 ; position - target\n mov #9,r3\n cmp/eq r3,r9\n bt hb_back\n mov r0,r2\n mov.b @(2,r11),r0\n tst r0,r0\n bf hb_moving ; silent: the DSP's word alone\n mov r2,r0\n bsr he_bjat ; a beat jump's slot standing at the target\n nop ; (he_play plays it then, hb_bjslot)\n bt hb_moving\n mov.l F_win,r2 ; 0 .. 2 s past it\n cmp/hs r2,r0\n bt hb_ret\n tst r0,r0 ; heard: once it plays on from there (the deck\n bt hb_ret ; takes no CUE or PLAY before)\nhb_moving:\n mov #20,r0\n cmp/hs r0,r8\n bf hb_ret ; not before 0.2 s\n mov #BJ_SLOT,r0 ; a beat jump's slot: the player reports its\n cmp/eq r0,r9 ; point as 0x91, no row word goes to 1; that\n bt hb_loaded ; it plays on from the target is it\n bsr he_row ; the DSP's row s\n mov r9,r0\n mov.l F_slotst,r4\n mov.l F_hpird,r1\n jsr @r1\n add r0,r4\n bsr he_fp\n nop\n cmp/eq #1,r0\n bf hb_ret\n bsr he_row\n mov r9,r0\n mov.l F_slotpt,r4\n mov.l F_hpird,r1\n jsr @r1\n add r0,r4\n bsr he_fp\n nop\n shll r0 ; its point in 1/150 s, rounded down\n mov.l @(16,r11),r1\n sub r0,r1\n mov #3,r0\n cmp/hs r0,r1\n bt hb_ret ; not the target (yet)\nhb_loaded:\n ; loaded: slot s holds the key's hot cue\n mov #BJ_SLOT,r0\n cmp/eq r0,r9\n bt hb_bjslot ; (slot 4: a beat jump's, no hot cue's)\n mov r11,r1\n add #8,r1\n add r9,r1\n mov.l @(44,r11),r0\n and #127,r0\n mov.b r0,@r1 ; its key\n mov r9,r0\n shll2 r0\n add #20,r0\n mov.l @(16,r11),r2\n mov.l r2,@(r0,r11) ; its time\nhb_bjslot:\n bsr he_gap ; a beat jump's: how long it took\n nop\n mov.b @(2,r11),r0 ; heard: PLAY on the slot now that it plays,\n tst r0,r0 ; so the deck is in its playing state (1)\n bf hb_nopl ; again, as after a recall; in 0x17 it takes\n bsr he_play ; no LOOP IN / OUT\n mov r9,r5\n bsr he_bjpend ; and again until it plays\n nop\nhb_nopl:\n mov.b @(1,r11),r0\n extu.b r0,r0\n cmp/eq #6,r0\n bf hb_n6\n mov #0,r0 ; a hot cue just set: loaded; CUE on its slot\n mov.b r0,@(1,r11) ; has the deck stand at it (the player would\n mov #2,r0 ; go on from there), parked; silent 2\n mov.b r0,@(2,r11) ; (he_mute)\n bsr he_cue\n mov r9,r5\n mov.l r9,@(48,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n bra hb_done\n mov.l r0,@(56,r11)\nhb_n6:\n cmp/eq r9,r0\n bf hb_done\n add #1,r0 ; preloading: the next slot\n mov.l @(60,r11),r1\n tst r1,r1\n bt hb_next\n mov #2,r1 ; a key waits: straight back, the rest later\n mov.l r1,@(36,r11)\n mov #4,r0\nhb_next:\n mov.b r0,@(1,r11)\nhb_done:\n mov #0,r0\n mov.b r0,@(3,r11)\nhb_ret:\n lds.l @r15+,pr\n rts\n nop\nhb_back:\n tst r0,r0 ; the CALL back: standing at its target\n bf hb_ret\n mov #23,r0\n cmp/eq r0,r1\n bt hb_back2\n mov #3,r0\n cmp/eq r0,r1\n bf hb_ret\nhb_back2:\n mov #0,r0\n mov.b r0,@(3,r11)\n bsr he_now\n nop\n mov.l F_bjx,r1\n mov.l r0,@(44,r1) ; when it ended: he_bj's next CALL waits BJ_SETTLE\n mov.b @(1,r11),r0\n cmp/eq #5,r0\n bf hb_ret ; a beat jump's CALL\n mov #0,r0\n mov.b r0,@(1,r11) ; the preload is over\n mov.l @(36,r11),r0\n cmp/eq #2,r0\n bf hb_over\n mov #0,r0 ; cut short: again once the deck stands\n mov.l r0,@(36,r11)\n mov #-128,r0\n mov.b r0,@(1,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\nhb_over:\n mov.b @(4,r11),r0\n tst r0,r0\n bt hb_ret\n mov #0,r0\n mov.b r0,@(4,r11)\n mov.b r0,@(2,r11) ; heard now\n mov.l F_play,r1 ; PLAY pressed meanwhile\n jsr @r1\n nop\n bra hb_ret\n nop\nhb_fail:\n mov #0,r0 ; given up: silent off; a preload goes back\n mov.b r0,@(2,r11)\n mov.b r0,@(3,r11)\n mov r11,r1 ; no beat jump to measure\n add #64,r1\n mov.l r0,@(24,r1)\n mov.b @(1,r11),r0\n extu.b r0,r0\n cmp/eq #6,r0\n bt hf_end ; a hot cue just set: on its first press then\n mov #6,r1\n cmp/hs r1,r0\n bt hb_ret ; none under way, or waiting\n mov #4,r1\n cmp/hs r1,r0\n bt hf_end\n tst r0,r0\n bt hb_ret\n mov #4,r0\n bra hb_ret\n mov.b r0,@(1,r11)\nhf_end:\n mov #0,r0\n bra hb_ret\n mov.b r0,@(1,r11)\n\n; ---- he_step: r11 -> ST2. The preload's next step.\nhe_step:\n sts.l pr,@-r15\n mov.b @(1,r11),r0\n extu.b r0,r9\n tst r9,r9\n bf hs2_on\n bra hs2_one\n nop\nhs2_on:\n mov.b @(3,r11),r0\n tst r0,r0\n bt hs2_free\n bra hs2_ret ; something under way\n nop\nhs2_free:\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r9\n bf hs2_load\n ; waiting: the deck must stand in cue standby or paused (not while a\n ; track still loads), within PRELOAD_NEAR of its CUE point, for\n ; PRELOAD_TICKS; the CALL back then goes to the CUE point itself, which\n ; so stays where it was\n bsr he_pos\n nop\n mov r0,r3\n mov.b @r10,r0 ; with the item ON, hot key mode or not\n tst r0,r0\n bt hs2_moved\n mov r1,r0\n cmp/eq #23,r0 ; cue standby or paused\n bt hs2_st\n cmp/eq #3,r0\n bf hs2_moved\nhs2_st:\n mov.l F_cuepos,r1\n mov.l @r1,r1\n mov r3,r0\n sub r1,r0\n add #PRELOAD_NEAR,r0\n mov #PRELOAD_NEAR,r1\n shll r1\n cmp/hi r1,r0\n bt hs2_moved ; not at its CUE point\n mov.l @(40,r11),r0\n mov.l r3,@(40,r11)\n cmp/eq r3,r0\n bt hs2_still\nhs2_moved:\n mov.l r3,@(40,r11)\n bsr he_now ; moved: wait again\n nop\n bra hs2_ret\n mov.l r0,@(12,r11)\nhs2_still:\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov #PRELOAD_TICKS,r1\n cmp/hs r1,r0\n bf hs2_ret\n ; and the track is loaded: the DSP has its CUE point's row again (the\n ; player clears the rows when a track loads; standing at position 0\n ; while the track still loads looks like cue standby)\n mov.l F_slotst,r4\n mov.l F_hpird,r1\n jsr @r1\n nop\n bsr he_fp\n nop\n cmp/eq #1,r0\n bf hs2_moved\n mov.l F_slotpt,r4\n mov.l F_hpird,r1\n jsr @r1\n nop\n bsr he_fp\n nop\n shll r0\n mov.l F_cuepos,r1\n mov.l @r1,r1\n sub r0,r1\n mov #3,r0\n cmp/hs r0,r1\n bt hs2_moved\n mov.l F_cuepos,r1 ; where to go back to: the CUE point\n mov.l @r1,r1\n mov.l r1,@(20,r11)\n mov #1,r0\n bra hs2_ret\n mov.b r0,@(1,r11)\nhs2_load:\n mov r9,r0\n cmp/eq #5,r0\n bt hs2_ret ; going back: he_busy ends it\n cmp/eq #4,r0\n bt hs2_back\n mov #3,r1\n cmp/hi r1,r0\n bt hs2_ret ; (no such step)\n ; slot r9 (1-3) gets hot cue r9 (A-C), if there is one\n mov.l F_hot,r1\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r1),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt hs2_skip\n bsr he_slot ; in a slot already\n mov r9,r5\n tst r0,r0\n bt hs2_go\nhs2_skip:\n mov r9,r0\n add #1,r0\n bra hs2_ret\n mov.b r0,@(1,r11)\nhs2_go:\n mov r11,r1\n add #127,r1\n add #61,r1 ; ST4 + 60: held from before the CALL\n mov #1,r0\n mov.b r0,@r1\n mov.l F_b26d,r1\n mov.b r0,@r1\n mov.b r0,@(2,r11) ; silent\n mov.l r0,@(36,r11) ; the deck will move\n mov r9,r0\n or #-128,r0 ; key = slot, 0x80: the preload's (its LED does\n mov.l r0,@(44,r11) ; not blink, he_ledlv)\n mov r9,r0\n bsr he_use\n nop\n bsr he_load ; r4 = time, r5 = slot\n mov r9,r5\n bra hs2_ret\n nop\nhs2_back:\n mov.b @(2,r11),r0 ; silent 2 (he_mute): the CUE back sends PLAY,\n cmp/eq #2,r0 ; then the hold, a moment later; BACK_TICKS\n bt hs2_b2 ; after it is set\n mov #2,r0\n mov.b r0,@(2,r11)\n bsr he_now\n nop\n bra hs2_ret\n mov.l r0,@(12,r11)\nhs2_b2:\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov #BACK_TICKS,r1\n cmp/hs r1,r0\n bt hs2_b3\n bra hs2_ret\n nop\nhs2_b3:\n mov.l @(36,r11),r0\n tst r0,r0\n bf hs2_call\n mov #0,r0 ; nothing loaded: the deck never moved\n bra hs2_ret\n mov.b r0,@(1,r11)\nhs2_call:\n mov #5,r0\n mov.b r0,@(1,r11)\n mov #9,r0\n mov.b r0,@(3,r11)\n mov.l @(20,r11),r4\n mov.l r4,@(16,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov.l F_cue,r1 ; back to the CUE point at once: its row is\n jsr @r1 ; still loaded (CUE as the CUE key sends it\n nop ; while playing; the deck stands there)\nhs2_ret:\n lds.l @r15+,pr\n rts\n nop\nhs2_one:\n ; a hot cue just set (ST4 +61, its key): loaded silently into its slot\n ; (step 6) while the deck stands, so it is ready at once; the deck then\n ; stands at it, parked (PLAY plays from there, see he_park)\n mov.b @(3,r11),r0\n tst r0,r0\n bf hs2_ret ; something under way\n mov.l F_st4,r3\n add #61,r3\n mov.b @r3,r0\n extu.b r0,r9\n tst r9,r9\n bt hs2_ret\n mov #0,r0\n mov.b r0,@r3\n bsr he_pos\n nop\n mov r1,r0\n cmp/eq #3,r0 ; paused or in cue standby only\n bt hs2_o1\n cmp/eq #23,r0\n bf hs2_ret ; (else on its first press)\nhs2_o1:\n mov.l F_hot,r1\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r1),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt hs2_ret\n mov.l r4,@-r15\n bsr he_slot ; in a slot already\n mov r9,r5\n tst r0,r0\n bf/s hs2_ret\n mov.l @r15+,r4\n mov #6,r0\n bra hs2_go\n mov.b r0,@(1,r11)\n\n; ---- he_uf: r11 -> ST2. While the preload runs (steps 1-6) the screen is\n; held at the CUE point (ST4 +60, HOLD_FLAG, is 1): the UI does not take the\n; player's position (0x0AF3B26D set, as the firmware does; see 0x08B39092),\n; so the time and the detail waveform stay; the player's own copies of its\n; position at PLAYER_CTX + 0x2C8 (units) and +0x2CC (ms), which the overview,\n; the phase meter and others read, are kept at their last free values by\n; pub_hook; and the CALL the player reports (UI_AFAC: the overview's line and\n; marker go to that memory) stays at 0 (he_shadow). Nothing moves. Let go,\n; 0x0AF3B22D (the other flag there) is cleared too: our silent CALLs set it,\n; and after a hot cue's single load nothing else would (the screen stood\n; still until the next track).\nhe_uf:\n mov r11,r3\n add #127,r3\n add #61,r3 ; ST4 + 60\n mov.b @(1,r11),r0\n extu.b r0,r0\n add #-1,r0\n mov #6,r1\n cmp/hs r1,r0\n bt huf_off\n mov.l F_b26d,r1\n mov #1,r0\n mov.b r0,@r1\n rts\n mov.b r0,@r3\nhuf_off:\n mov.b @r3,r0\n tst r0,r0\n bt huf_ret\n mov #0,r0\n mov.b r0,@r3\n mov.l F_b26d,r1\n mov.b r0,@r1\n mov.l F_b22d,r1 ; and the CALL flag he_load set as the CALL key\n mov.b r0,@r1 ; does: nothing clears it after a silent load\nhuf_ret:\n rts\n nop\n\n; ---- he_wv: r11 -> ST2. ST4 +62 is 1 while the big (detail) waveform is not\n; drawn (hk_wvmid): after a track load, while the deck waits to preload\n; (WV_TICKS at most, not playing). While the hot cues load it is drawn, held\n; at the CUE point (he_uf).\nhe_wv:\n mov r11,r3\n add #127,r3\n add #63,r3 ; ST4 + 62\n mov.b @(1,r11),r0\n extu.b r0,r0\n mov r0,r2\n add #-1,r0\n mov #5,r1\n cmp/hs r1,r0\n bf hwv_show ; steps 1-5: held\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r2\n bf hwv_show\n mov.l F_ctx,r1 ; waiting to preload: not while it plays\n mov #67,r0\n shll2 r0\n mov.l @(r0,r1),r0\n cmp/eq #1,r0\n bt hwv_show\n mov r11,r1\n add #127,r1\n add #57,r1\n mov.l @r1,r1 ; and for WV_TICKS after the load at most\n mov.l F_tick,r2\n mov.l @r2,r2\n sub r1,r2\n mov #WV_TICKS-256,r1\n extu.b r1,r1\n cmp/hs r1,r2\n bt hwv_show\nhwv_hide:\n mov #1,r0\n bra hwv_set\n nop\nhwv_show:\n mov #0,r0\nhwv_set:\n mov.b @r3,r1\n cmp/eq r0,r1\n bt hwv_ret\n mov.b r0,@r3 ; changed: the waveform drawn again (the UI\n mov.l F_mkdirty,r1 ; redraws it only when something changed)\n mov #1,r2\n mov.l r2,@r1\nhwv_ret:\n rts\n nop\n\n; ---- hk_wvmid: inside the detail waveform's draw function (0x08BCFA34),\n; right after it cleared its pixel buffer (memset 0x0BA7DEF4, at 0x08BCFBC8);\n; six instructions (0x08BCFBCC-D6) replaced by a jump here (r0 used, as the\n; first of them sets it). ST4 +62 set: nothing drawn into the buffer, on to\n; where it is copied to the screen (0x08BD01EE), so the area is black; else\n; the six and on (r7 is set anew at 0x08BCFBF4).\nhk_wvmid:\n mov.l W_flag,r7\n mov.b @r7,r7\n tst r7,r7\n bf wvm_blank\n sts fpscr,r5\n mov #64,r0\n mov.l W_r12,r12\n mov #5,r1\n mov.l W_r13,r13\n mov.l @(r0,r9),r14\n mov.l W_back,r7\n jmp @r7\n nop\nwvm_blank:\n sts fpscr,r5 ; the firmware's fpscr fix after the memset\n and r11,r5 ; (r11 holds its mask here)\n lds r5,fpscr\n mov.l W_out,r7\n jmp @r7\n nop\n .align 4\nW_flag: .long ST4+62\nW_r12: .long WV_R12\nW_r13: .long WV_R13\nW_back: .long WV_MID+12\nW_out: .long WV_OUT\n\n; ---- he_now: r0 = the tick\nhe_now:\n mov.l F_tick,r0\n rts\n mov.l @r0,r0\n\n .align 4\nF_ctx: .long PLAYER_CTX\nF_bjx: .long BJX\nF_b26d: .long 0x0AF3B26D\nF_b22d: .long 0x0AF3B22D\nF_st4: .long ST4\nF_mkdirty: .long MARKERS_DIRTY\nF_cuepos: .long CUE_POS\nF_fpmask: .long 0xFFE7FFFF\nF_hot: .long HOT\nF_hpird: .long HPI_READ\nF_keypc: .long KEY_PLAYCUE\nF_loadt: .long LOAD_TICKS\nF_play: .long hr_play\nF_recall: .long hk_recall\nF_slotst: .long SLOT_ST\nF_slotpt: .long SLOT_PT\nF_hpiwr: .long HPI_WRITE\nF_cue: .long hk_cue\nF_st2: .long ST2\nF_tick: .long TICK\nF_win: .long 300\n\n; ---- he_bjpend: r9 = the beat jumps' slot, r11 -> ST2: its PLAY again until\n; the deck plays (he_pend): right at the target the player takes no PLAY yet\nhe_bjpend:\n mov r11,r1\n add #64,r1\n mov.l r9,@(44,r1)\n mov.l Z_tick,r0\n mov.l @r0,r0\n mov.l r0,@(48,r1)\n rts\n mov.l r0,@(52,r1)\n .align 4\nZ_tick: .long TICK\n\n; ---- he_bjat: r0 = position - target, r9 = the slot -> T: the beat jumps'\n; slot, the deck standing within 2 of the target (it lands a unit short at\n; times and, in 0x17 there, does not play on by itself)\nhe_bjat:\n mov #BJ_SLOT,r1\n cmp/eq r1,r9\n bf hba_no ; (T is 0 then)\n mov r0,r1\n add #2,r1\n mov #5,r2\n cmp/hi r1,r2 ; 5 > r1: within -2 .. +2\nhba_no:\n rts\n nop\n\n; ---- he_mute: r11 -> ST2. Once a silent load's point is in, the player\n; sends the DSP its own PLAY on the slot (play command mode 3, then\n; transport 2) as for any CALL; our CUE back stopped it only a moment later,\n; and that moment was heard (the 0.1 s blip on a player, ~25 ms in the\n; emulator's capture). So the way back runs with silent 2 (hk_wrap_pc and\n; hk_wrap_tr drop that PLAY, let the CUE through; hs2_back, the hot cue just\n; set in hb_loaded), and silent (ST2 +2) stays until nothing is under way,\n; the deck stands (3, 0x17) and MUTE_TICKS have passed since the CUE (+12),\n; or MUTE_MAX at most.\nhe_mute:\n mov.b @(2,r11),r0\n tst r0,r0\n bt hm_ret\n mov.b @(3,r11),r0\n tst r0,r0\n bf hm_ret\n mov.b @(1,r11),r0\n tst r0,r0\n bt hm_idle\n cmp/eq #-128,r0 ; 0x80: waiting to preload again\n bf hm_ret\nhm_idle:\n sts.l pr,@-r15\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov r0,r3\n mov #MUTE_TICKS,r1\n cmp/hs r1,r3\n bf hm_pop\n mov #MUTE_MAX,r1\n cmp/hs r1,r3\n bt hm_off\n bsr he_pos\n nop\n mov r1,r0\n cmp/eq #3,r0\n bt hm_off\n cmp/eq #23,r0\n bf hm_pop\nhm_off:\n mov #0,r0\n mov.b r0,@(2,r11)\nhm_pop:\n lds.l @r15+,pr\nhm_ret:\n rts\n nop\n\n; ---- he_load: r4 = a hot cue's time (1/150 s), r5 = slot 1-3, r11 -> ST2.\n; Busy from now; the slot holds nothing until he_busy finds it loaded (the\n; DSP's word for the row cleared, so an old one is not taken for it).\nhe_load:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov r4,r8\n mov r5,r9\n mov #BJ_SLOT,r0\n cmp/eq r0,r9\n bt hl_nort ; (see he_busy)\n bsr he_row\n mov r9,r0\n mov.l E_slotst,r4\n add r0,r4\n mov.l E_hpiwr,r1\n jsr @r1\n mov #0,r5\n bsr he_fp\n nop\nhl_nort:\n mov r11,r1 ; the position jumps: speed measured anew\n add #64,r1\n mov #0,r0\n mov.l r0,@(20,r1)\n mov r9,r0\n mov.b r0,@(3,r11) ; busy: slot s\n mov.l r8,@(16,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov #BJ_SLOT,r0\n cmp/eq r0,r9\n bt hl_ui ; the beat jumps' slot holds no hot cue\n mov r11,r1\n add #8,r1\n add r9,r1\n mov #0,r0\n mov.b r0,@r1\nhl_ui:\n mov #0,r0\n ; the UI as the CALL key leaves it (0x08B4F326-0x08B4F384)\n mov.l E_b21d,r1\n mov.b r0,@r1\n mov.b r0,@(1,r1)\n mov.l E_af08,r1\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov.l E_af28,r1\n mov #7,r0\n mov.l r0,@r1\n mov.l E_b22d,r1\n mov #1,r0\n mov.b r0,@r1\n ; the UI's CALL record: event (3+s) << 16 | 0x100, ms, -1, 1/150 s\n mov.l E_callrec,r2\n mov r9,r0\n add #3,r0\n shll16 r0\n mov #1,r1\n shll8 r1\n or r1,r0\n mov.l r0,@r2\n mov.l r8,@(12,r2)\n mov #-1,r0\n mov.l r0,@(8,r2)\n bsr he_ms\n mov r8,r4\n mov.l E_callrec,r2\n mov.l r0,@(4,r2)\n ; CALL: 22 and 23 with slot 4+s\n mov #0,r1\n mov.l r1,@-r15\n mov.l r0,@-r15\n mov #22,r4\n mov r9,r5\n add #4,r5\n mov #0,r6\n mov.l E_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n bsr he_fp\n nop\n mov #0,r1\n mov.l r1,@-r15\n mov #-1,r1\n mov.l r1,@-r15\n mov #23,r4\n mov r9,r5\n add #4,r5\n mov #0,r6\n mov.l E_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n bsr he_fp\n nop\n ; while the screen is held (the preload): the record back to the CUE\n ; point, the overview's line and marker show its time while a CALL is\n ; under way (0x0AF3AF28 7); the message is built. Not otherwise: the\n ; player reads it again later, and would not play the slot on.\n mov r11,r1\n add #127,r1\n add #61,r1 ; ST4 + 60\n mov.b @r1,r0\n tst r0,r0\n bt hl_rec\n mov.l E_cuepos,r1\n mov.l @r1,r4\n mov.l E_callrec,r2\n mov.l r4,@(12,r2)\n bsr he_ms\n nop\n mov.l E_callrec,r2\n mov.l r0,@(4,r2)\nhl_rec:\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_ms: r0 = r4 (1/150 s) in ms, rounded up so the builder gets it back\nhe_ms:\n mov r4,r1\n shll2 r1\n mov r1,r3\n shll2 r3\n add r3,r1 ; 20 * units\n add #2,r1\n mov.l E_third,r3\n dmulu.l r3,r1\n sts mach,r0\n rts\n shlr r0\n\n; ---- he_slot: r4 = time, r5 = key -> r0 = the slot holding it (1-3), 0 none\nhe_slot:\n mov.l E_st2,r3\n mov #1,r2\nhs_loop:\n mov r3,r1\n add #8,r1\n add r2,r1\n mov.b @r1,r0\n extu.b r0,r0\n cmp/eq r5,r0\n bf hs_next\n mov r2,r0\n shll2 r0\n add #20,r0\n mov.l @(r0,r3),r0\n cmp/eq r4,r0\n bf hs_next\n rts\n mov r2,r0\nhs_next:\n add #1,r2\n mov #4,r0\n cmp/eq r0,r2\n bf hs_loop\n rts\n mov #0,r0\n\n; ---- he_use: r0 = slot (1-3): used now\nhe_use:\n mov.l E_st2,r3\n mov r0,r2\n mov.b @(5,r3),r0\n add #1,r0\n mov.b r0,@(5,r3)\n add #5,r2\n add r3,r2\n rts\n mov.b r0,@r2\n\n; ---- he_div: r0 = r4 / r5, unsigned (r1, r2, r4 used)\nhe_div:\n mov #0,r0\n mov #0,r1\n mov #32,r2\nhd_loop:\n shll r4\n rotcl r1\n shll r0\n cmp/hs r5,r1\n bf hd_next\n sub r5,r1\n add #1,r0\nhd_next:\n dt r2\n bf hd_loop\n rts\n nop\n\n; ---- he_cue: r5 = slot 1-3. CUE with slot 4+s, as the CUE key sends it.\nhe_cue:\n sts.l pr,@-r15\n mov.l E_st3,r1 ; the position jumps: speed measured anew\n mov #0,r0\n mov.l r0,@(20,r1)\n mov.l E_af28,r1\n mov #7,r0\n mov.l r0,@r1\n mov.l E_aeec,r1\n mov #0,r0\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov.l E_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #3,r4\n add #4,r5\n mov.l E_build,r1\n jsr @r1\n mov #1,r7\n add #8,r15\n bsr he_fp\n nop\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_play: r5 = slot 1-3. PLAY with slot 4+s.\nhe_play:\n sts.l pr,@-r15\n mov.l E_af28,r1\n mov #3,r0\n mov.l r0,@r1\n mov.l E_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #1,r4\n add #4,r5\n mov.l E_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n bsr he_fp\n nop\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_hot: r4 = a hot cue's time, r9 = its key, r10 -> HK_SETTING.\n; In a slot: CUE and PLAY on it, at once. Else it is loaded into its own\n; slot (hot cue s: slot s; and plays, as a CALL with a slot does).\nhk_hot:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r11,@-r15\n mov.l E_st2,r11\n mov r4,r8\n mov.b @(1,r11),r0 ; a preload under way: the key waits\n extu.b r0,r0\n tst r0,r0\n bt hh_free\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bf hh_wait ; (not started yet: the key acts)\nhh_free:\n mov.b @(3,r11),r0\n tst r0,r0\n bt hh_now\nhh_wait:\n mov r9,r0 ; a load under way: the key waits for it\n bra hh_ret ; (hk_engine; the last key pressed wins)\n mov.l r0,@(60,r11)\nhh_now:\n bsr he_slot\n mov r9,r5\n tst r0,r0\n bt hh_load\n mov.l r0,@-r15\n bsr he_use\n nop\n mov.l E_af28,r1 ; a VINYL mode pause: the UI pauses (0x0AF3AF28\n mov.l @r1,r0 ; 5) but the player stays in state 1, braked to\n cmp/eq #5,r0 ; a stop; a PLAY on the slot does not start it.\n bf hh_playing ; CUE on the slot, the UI left paused, then\n mov.l E_ctx,r1 ; the PLAY key for the UI (he_inj): it plays\n mov #67,r0 ; on from there as from any VINYL pause\n shll2 r0\n mov.l @(r0,r1),r0\n cmp/eq #1,r0\n bf hh_playing ; (a CDJ mode pause: the player state 3)\n mov.l @r15+,r5\n bsr he_cue\n nop\n mov.l E_af28,r1\n mov #5,r0\n mov.l r0,@r1\n mov.l E_st3,r1\n mov.l E_tick,r2\n mov.l @r2,r2\n mov.l r2,@(48,r1)\n mov.l E_st4,r1\n add #63,r1\n mov #1,r0\n bra hh_ret\n mov.b r0,@r1\nhh_playing:\n mov.l @r15,r5\n bsr he_cue\n nop\n mov.l @r15,r5\n bsr he_play\n nop\n mov.l E_st3,r1 ; from pause the player may take PLAY only\n mov.l @r15+,r0 ; once it is at the hot cue: he_pend sends\n mov.l r0,@(44,r1) ; it again until the deck plays\n mov.l E_tick,r2\n mov.l @r2,r2\n mov.l r2,@(48,r1)\n mov.l r2,@(52,r1)\n bra hh_ret\n nop\nhh_load:\n mov r9,r5 ; hot cue s: slot s, always\n mov r5,r0\n mov.l r5,@-r15\n bsr he_use\n nop\n mov r9,r0\n mov.l r0,@(44,r11)\n mov #0,r0\n mov.b r0,@(2,r11) ; not silent: it plays\n mov.l @r15+,r5\n bsr he_load\n mov r8,r4\nhh_ret:\n mov.l @r15+,r11\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_gate: r4 = the time, r9 = the key: a gate's release. In a slot: CUE\n; on it (the deck stands at the hot cue); else the memory CALL as before.\nhk_gate:\n sts.l pr,@-r15\n mov.l r4,@-r15\n bsr he_slot\n mov r9,r5\n tst r0,r0\n bt hg_call\n mov.l E_st3,r1 ; no recall's PLAY to repeat now\n mov #0,r2\n mov.l r2,@(44,r1)\n mov.l E_st2,r1\n mov.l r0,@(48,r1) ; parked there: PLAY plays the slot\n mov.l E_tick,r2\n mov.l @r2,r2\n mov.l r2,@(56,r1)\n mov r0,r5\n bsr he_cue\n add #4,r15\n lds.l @r15+,pr\n rts\n nop\nhg_call:\n mov.l @r15+,r4\n mov.l E_recall,r1\n jsr @r1\n mov #0,r5\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_wrap_tr / hk_wrap_pc: the DSP manager's transport and play\n; commands (0x089F8FCA, 0x089F9306, through its literals 0x089F29FC and\n; 0x089F24FC). While a hot cue is loaded silently (silent 1), its PLAY\n; (transport 2, the play command) does not reach the DSP; anything else\n; passes. Silent 2 (the CUE back, see he_mute) lets the play command with\n; mode 0 through (r5: 3 plays, 0 the CUE), not the PLAY the player sends\n; once the last slot's point is in.\nhk_wrap_tr:\n mov.l E_silent,r1\n mov.b @r1,r0\n tst r0,r0\n bt wt_pass\n mov r4,r0\n cmp/eq #2,r0\n bt wt_drop\nwt_pass:\n mov.l E_trans,r1\n jmp @r1\n nop\nwt_drop:\n rts\n nop\nhk_wrap_pc:\n mov.l E_silent,r1\n mov.b @r1,r0\n tst r0,r0\n bt wp_pass\n cmp/eq #2,r0\n bf wp_drop\n mov r5,r0\n cmp/eq #3,r0\n bf wp_pass\nwp_drop:\n rts\n nop\nwp_pass:\n mov.l E_playc,r1\n jmp @r1\n nop\n\n; ---- hk_wrap_cy: the DSP manager's cyclic block (0x089F9610, r4 -> its 8\n; words for 0x11837BC0, through its literal 0x089F31F4). The CUE that takes\n; the deck back after a silent load sends PLAY, then the hold a moment\n; later; while silent the block carries 0x10000000 in its word 1 all along\n; (tested with the mute fix: nothing is heard meanwhile, the loads go on).\n; The DSP reads that bit as the MAIN's \"playing\" flag (re/dsp900nxs), not\n; as a hold of its own.\nhk_wrap_cy:\n mov.l E_silent,r1\n mov.b @r1,r0\n tst r0,r0\n bt wc_pass\n mov.l @(4,r4),r0\n mov.l E_hold,r1\n or r1,r0\n mov.l r0,@(4,r4)\nwc_pass:\n mov.l E_cyc,r1\n jmp @r1\n nop\n\n .align 4\nE_st2: .long ST2\nE_st3: .long ST3\nE_silent: .long ST2_SILENT\nE_hot: .long HOT\nE_tick: .long TICK\nE_ctx: .long PLAYER_CTX\nE_keypc: .long KEY_PLAYCUE\nE_loadt: .long LOAD_TICKS\nE_win: .long 300\nE_hpird: .long HPI_READ\nE_hpiwr: .long HPI_WRITE\nE_st4: .long ST4\nE_slotst: .long SLOT_ST\nE_fpmask: .long 0xFFE7FFFF\nE_play: .long hr_play\nE_recall: .long hk_recall\nE_cuepos: .long CUE_POS\nE_b21d: .long 0x0AF3B21D\nE_af08: .long 0x0AF3AF08\nE_af28: .long 0x0AF3AF28\nE_b22d: .long 0x0AF3B22D\nE_callrec: .long CALL_REC\nE_build: .long BUILD\nE_third: .long 0xAAAAAAAB\nE_aeec: .long 0x0AF3AEEC\nE_af3c: .long 0x0AF3AF3C\nE_trans: .long TRANSPORT\nE_playc: .long PLAYCMD\nE_cyc: .long CYCLIC\nE_hold: .long 0x10000000\n\n; ---- he_rate: r11 -> ST2. Whether the deck plays (ST3 +40: state 1, or\n; any state but paused (3) while it moved in the last 0.25 s: a loop is 7,\n; a slip loop 0x14, a slot it loaded 0x17; +32 the last position, +36\n; polls left that count as moving), and while it plays, its speed in 1/150 s * 256 per tick over a second or more (ST3 +12; +16 and\n; +20 where and when the measure began, +20 = 0: anew), kept only between\n; 0.5 and 1.5 times 1.0.\nhe_rate:\n sts.l pr,@-r15\n bsr he_pos\n nop\n mov r0,r3 ; the position\n mov r11,r2\n add #64,r2\n mov.l @(32,r2),r0\n mov.l r3,@(32,r2)\n cmp/eq r3,r0\n bt hr2_same\n bra hr2_cnt\n mov #25,r0 ; it moves in steps: 0.25 s\nhr2_same:\n mov.l @(36,r2),r0\n tst r0,r0\n bt hr2_cnt\n add #-1,r0\nhr2_cnt:\n mov.l r0,@(36,r2)\n mov r0,r4\n mov r1,r0\n cmp/eq #1,r0\n bt hr2_on\n cmp/eq #3,r0\n bt hr2_stop ; paused\n tst r4,r4 ; any other state while it moves: a loop (7),\n bf hr2_on ; a slip loop (0x14), a slot it loaded (0x17)\nhr2_stop:\n mov #0,r0 ; not playing: anew\n mov.l r0,@(40,r2)\n bra hr2_ret\n mov.l r0,@(20,r2)\nhr2_on:\n mov #1,r0\n mov.l r0,@(40,r2)\nhr2_play:\n bsr he_now\n nop\n mov.l @(20,r2),r1\n tst r1,r1\n bt hr2_anchor\n mov r0,r5\n sub r1,r5 ; ticks since\n mov #100,r1\n cmp/hs r1,r5\n bf hr2_ret\n mov.l @(16,r2),r1\n mov r3,r4\n sub r1,r4 ; how far it went\n cmp/pl r4\n bf hr2_anchor ; back: it jumped\n mov.l r0,@-r15\n mov.l r3,@-r15\n shll8 r4\n bsr he_div\n nop\n mov r11,r2\n add #64,r2\n mov #96,r1\n shll r1 ; 192\n cmp/hs r1,r0\n bf hr2_bad\n mov #72,r1\n shll2 r1\n shll r1 ; 576\n cmp/hi r1,r0\n bt hr2_bad ; on: it jumped\n mov.l r0,@(12,r2)\nhr2_bad:\n mov.l @r15+,r3\n mov.l @r15+,r0\nhr2_anchor:\n mov.l r3,@(16,r2)\n mov.l r0,@(20,r2)\nhr2_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_bj: r11 -> ST2, r10 -> HK_SETTING. The beat jump beatjump asks for\n; (ST3 +0: its target in 1/150 s | 1 << 31, +4 the tick of its key).\n; First the DSP jump (he_dj: at once, in the decoded audio; playing, paused\n; or at cue standby). Where the DSP refuses or is not asked, the old way:\n; playing, the target moves on by the time a slot load takes (ST3 +8,\n; measured by he_gap) at the deck's speed, and is loaded into BJ_SLOT, the\n; beat jumps' own: the deck plays on in phase, A-C keep theirs, its CUE point\n; stays; standing, the memory CALL there (the CUE point goes with it), busy\n; till the deck stands there. A load under way, or the preload: dropped;\n; standing, also while the preload still waits.\nhe_bj:\n sts.l pr,@-r15\n mov.l G_bjx,r0 ; a DSP jump under way: the next press waits\n mov.l @r0,r0\n tst r0,r0\n bf hj_ret\n mov r11,r2\n add #64,r2\n mov.l @r2,r0\n cmp/pz r0\n bt hj_q ; none: one queued at cue standby?\n shll r0\n shlr r0\n mov r0,r8 ; the target\n mov #0,r0\n mov.l r0,@r2 ; taken\n mov.b @(3,r11),r0\n tst r0,r0\n bf hj_busy ; a load under way\n bsr hj_settled\n nop\n bf hj_wait ; a CALL just ended: this one waits too\n mov.b @(1,r11),r0\n extu.b r0,r0\n tst r0,r0\n bt hj_free\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bf hj_ret ; preloading\nhj_free:\n mov.l G_dj,r0 ; the DSP jump: at once, in the decoded audio;\n jsr @r0 ; nothing else (a slot load or a memory CALL\n nop ; instead left the deck standing or the UI\n bra hj_ret ; frozen: hj_old and after are not used)\n nop\nhj_old:\n mov.l G_pos,r0 ; standing unless the player plays (right\n jsr @r0 ; after a CALL he_rate still sees its move)\n nop\n mov r1,r0\n cmp/eq #1,r0\n bf hj_stand\n mov r11,r2\n add #64,r2\n mov.l @(40,r2),r0 ; playing (he_rate)\n tst r0,r0\n bf hj_play\nhj_stand:\n mov #9,r0 ; standing: the memory CALL\n mov.b r0,@(3,r11)\n mov.l r8,@(16,r11)\n bsr hj_posv\n nop\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov r8,r4\n mov.l G_callat,r1\n jsr @r1\n mov #0,r5\n bra hj_ret\n nop\nhj_play:\n mov r11,r2\n add #64,r2\n mov.l @(8,r2),r4 ; a load's time, ticks\n tst r4,r4\n bf hj_g\n mov #BJ_GAP0,r4\nhj_g:\n mov.l @(12,r2),r1 ; the speed\n tst r1,r1\n bf hj_r\n mov #96,r1\n shll2 r1 ; 384: 1.5 per tick\nhj_r:\n mul.l r1,r4\n sts macl,r4\n shlr8 r4\n add r4,r8 ; where the deck would be by then\n mov.l r8,@(28,r2)\n mov #1,r0\n mov.l r0,@(24,r2) ; he_gap measures it\n mov #0,r0\n mov.b r0,@(2,r11) ; heard\n mov #BJ_SLOT,r5 ; the beat jumps' own slot\n bsr he_load\n mov r8,r4\nhj_ret:\n lds.l @r15+,pr\n rts\n nop\n; A press while the memory CALL of one at cue standby is under way: its\n; target (bj_poll reckons from the CALL's target meanwhile, BJX +36, as the\n; deck's position reads 0 then) waits in BJX +32 (| 1 << 31) and is jumped to\n; once the CALL is done and BJ_SETTLE has passed (hj_settled), by another\n; CALL; a later press replaces it. A hot cue's load under way: dropped.\nhj_busy:\n cmp/eq #9,r0\n bf hj_ret\nhj_wait:\n bsr hj_posv\n nop\n mov #1,r0\n rotr r0\n or r8,r0\n mov.l G_bjx,r1\n bra hj_ret\n mov.l r0,@(32,r1)\nhj_q:\n mov.b @(3,r11),r0\n tst r0,r0\n bf hj_ret ; still under way\n bsr hj_settled\n nop\n bf hj_ret ; just ended\n mov.l G_bjx,r1\n mov.l @(32,r1),r0\n tst r0,r0\n bf hj_qgo\n bra hj_ret\n mov.l r0,@(36,r1) ; none: bj_poll reads the deck's position again\nhj_qgo:\n mov #0,r2\n mov.l r2,@(32,r1)\n shll r0\n shlr r0\n mov r0,r8\n mov.l G_pos,r0 ; the deck plays (PLAY meanwhile): dropped\n jsr @r0\n nop\n mov r1,r0\n cmp/eq #1,r0\n bt hj_ret\n bra hj_stand ; the memory CALL again (he_rate still sees\n nop ; the last one's move)\n\n; hj_settled: T = 1 once BJ_SETTLE ticks have passed since the last memory\n; CALL ended (BJX +44, he_busy). A CALL sent right after one (the deck at its\n; target and standing, the firmware not done with it yet) could leave the\n; UI's task waiting for good: no key worked any more.\nhj_settled:\n sts.l pr,@-r15\n bsr he_now\n nop\n mov.l G_bjx,r1\n mov.l @(44,r1),r1\n sub r1,r0\n mov #BJ_SETTLE,r1\n cmp/hs r1,r0\n lds.l @r15+,pr\n rts\n nop\n\n; hj_posv: r8 = a target (1/150 s) -> BJX +36 = it in ms (rounded)\nhj_posv:\n mov r8,r0\n shll2 r0\n mov r0,r1\n shll2 r0\n add r1,r0 ; 20 t\n add #1,r0\n mov.l G_third,r1\n dmulu.l r1,r0\n sts mach,r0\n shlr r0 ; / 3\n mov.l G_bjx,r1\n rts\n mov.l r0,@(36,r1)\n\n; ---- he_ledlv: r4 = a hot cue's time, r5 = its key -> r0 = its LED level:\n; 1, and 1 and 0 by turns (0.32 s each) while it is being loaded by a press\n; (the preload's loads carry 0x80 in the key: they show nothing)\nhe_ledlv:\n sts.l pr,@-r15\n mov.l G_st2,r1\n mov.b @(3,r1),r0\n extu.b r0,r0\n tst r0,r0\n bt/s hlv_ret ; no load under way\n mov #1,r0\n mov.l @(44,r1),r0\n cmp/eq r5,r0\n bf/s hlv_ret ; another key's\n mov #1,r0\n mov.l G_tick,r1\n mov.l @r1,r1\n mov #32,r0\n tst r0,r1\n movt r0\n xor #1,r0\nhlv_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_dbop: r4 = 1 save / 2 delete, r5 = hot cue number (A = 1), r6 =\n; its time (1/150 s): queued for the USB (ST4: +0 count, +4.. 6 x (op |\n; number << 8, time)); he_dbq sends them when MEMORY could. A delete of a\n; hot cue still queued takes that entry's place (its save is not needed\n; any more). A full queue drops the oldest.\nhk_dbop:\n mov.l G_st4,r1\n mov r5,r7\n shll8 r5\n or r4,r5\n mov.l @r1,r3\n mov r1,r2\n add #4,r2\n mov r4,r0\n cmp/eq #2,r0\n bf hd_add ; a save: always one more\n tst r3,r3\n bt hd_add\nhd_find:\n mov.l @r2,r0\n shlr8 r0\n extu.b r0,r0\n cmp/eq r7,r0\n bt hd_put\n dt r3\n bf/s hd_find\n add #8,r2\nhd_add:\n mov.l @r1,r0\n cmp/eq #DBQ_N,r0\n bf hd_room\n mov r1,r2 ; full: the oldest goes\n add #4,r2\n mov #DBQ_N-1,r3\nhd_shift:\n mov.l @(8,r2),r0\n mov.l r0,@r2\n mov.l @(12,r2),r0\n mov.l r0,@(4,r2)\n dt r3\n bf/s hd_shift\n add #8,r2\n mov #DBQ_N-1,r0\nhd_room:\n mov r0,r2\n shll2 r2\n shll r2\n add r1,r2\n mov.l r5,@(4,r2)\n mov.l r6,@(8,r2)\n add #1,r0\n rts\n mov.l r0,@r1\nhd_put:\n mov.l r5,@r2\n rts\n mov.l r6,@(4,r2)\n\n; ---- he_dbq: r11 -> ST2, each poll. The first queued hot cue goes to the DB\n; once the last one was taken (ST3 +56 clear; let go after DBQ_TICKS) and the\n; UI would send MEMORY's request: the checks of its handler (0x08B3CE08-\n; 0x08B3CE6E): the track ready (0x08A26340), not in the mode 0x08AFCF58 bars,\n; no request of the UI's under way (0x0B7C144C), on a USB only when its\n; setting at 0x0A4DE794 +0x2E0 allows; the device from 0x09944900.\nhe_dbq:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l G_st3,r8\n mov.l @(56,r8),r0\n tst r0,r0\n bt hq_free\n bsr he_now ; one sent, not taken yet\n nop\n mov.l G_st4,r1\n mov.l @(52,r1),r2\n sub r2,r0\n mov.l G_dbqt,r2\n cmp/hs r2,r0\n bf hq_ret\n mov #0,r0 ; never taken: let go, MEMORY is MEMORY again\n mov.l r0,@(56,r8)\nhq_free:\n mov.l G_st4,r1\n mov.l @r1,r0\n tst r0,r0\n bt hq_ret\n mov.l G_ctx,r1 ; not while a track loads (0 none, 2 and 4\n mov #67,r0 ; loading)\n shll2 r0\n mov.l @(r0,r1),r0\n tst r0,r0\n bt hq_ret\n cmp/eq #2,r0\n bt hq_ret\n cmp/eq #4,r0\n bt hq_ret\n mov.l G_busy,r1 ; the UI's request under way\n mov.l @r1,r0\n tst r0,r0\n bf hq_ret\n mov.l G_mode,r1\n jsr @r1\n nop\n tst r0,r0\n bf hq_fp\n mov.l G_which,r1\n jsr @r1\n nop\n cmp/eq #1,r0\n movt r5\n mov.l G_ctx,r4\n mov.l G_ready,r1\n jsr @r1\n nop\n cmp/eq #1,r0\n bf hq_fp\n mov.l G_media,r1\n jsr @r1\n nop\n cmp/eq #4,r0\n bf hq_send\n mov.l G_usbok,r1\n mov.l @r1,r0\n cmp/eq #1,r0\n bf hq_fp\nhq_send:\n mov.l G_st4,r1 ; the first one\n mov.l @(8,r1),r0\n mov.l r0,@(60,r8)\n mov.l @(4,r1),r0\n mov.l r0,@(56,r8)\n bsr he_now\n nop\n mov.l G_st4,r1\n mov.l r0,@(52,r1)\n mov.l @r1,r3 ; out of the queue\n add #-1,r3\n mov.l r3,@r1\n mov r1,r2\n add #4,r2\n tst r3,r3\n bt hq_go\nhq_mv:\n mov.l @(8,r2),r0\n mov.l r0,@r2\n mov.l @(12,r2),r0\n mov.l r0,@(4,r2)\n dt r3\n bf/s hq_mv\n add #8,r2\nhq_go:\n mov.l G_dev,r1\n mov.l @r1,r5\n mov.l G_dbreq,r4\n mov #0,r6\n mov.l G_sender,r1\n jsr @r1\n mov #0,r7\nhq_fp:\n mov.l G_mask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\nhq_ret:\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_dbreg: the DB task's handler of a cue registration (table entry\n; 0x0899314C, r4-r6 as given, r6 -> the request, its cue entry at +112: +1\n; kind, +2 hot cue number, +4 ms, +12 1/150 s). Ours (ST3 +56): the entry\n; becomes our hot cue's, and a delete goes to the delete handler instead.\nhk_dbreg:\n mov.l G_st3,r1\n mov.l @(56,r1),r0\n tst r0,r0\n bt hd_reg ; MEMORY's own\n mov r0,r2\n mov #0,r0\n mov.l r0,@(56,r1) ; taken\n mov.l @(60,r1),r3\n mov r6,r7\n add #112,r7\n mov #1,r0\n mov.b r0,@(1,r7) ; one cue, not a loop\n mov r2,r0\n shlr8 r0\n mov.w r0,@(2,r7) ; its hot cue number\n mov.l r3,@(12,r7)\n mov r3,r0 ; ms = units * 20 / 3, rounded up\n shll2 r0\n mov r0,r1\n shll2 r1\n add r1,r0\n add #2,r0\n mov.l G_third,r1\n dmulu.l r1,r0\n sts mach,r0\n shlr r0\n mov.l r0,@(4,r7)\n extu.b r2,r0\n cmp/eq #2,r0\n bt hd_del\nhd_reg:\n mov.l G_dbregf,r1\n jmp @r1\n nop\nhd_del:\n mov.l G_dbdelf,r1\n jmp @r1\n nop\n\n; ---- hk_pcp2: the DB task's PCP2 writer (0x08B10BE0, r4 entry, r5 cue). A\n; hot cue with no extra data (one set here, not read from the file) is\n; copied to PCBUF with the extra data rekordbox writes, its colour by number\n; (PCBLOB), so its entry gets the colour block; anything else passes.\nhk_pcp2:\n mov.l @(52,r5),r0\n tst r0,r0\n bf hq2_go\n mov.w @(4,r5),r0\n extu.w r0,r0\n add #-1,r0\n mov #8,r1\n cmp/hs r1,r0\n bt hq2_go ; a memory cue (0), or no such hot cue\n mov r0,r7 ; number - 1\n mov.l G_pcbuf,r2\n mov r5,r3\n mov #14,r6 ; 56 bytes\nhq2_cp:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r6\n bf/s hq2_cp\n add #4,r2\n mov.l G_pcblob,r3 ; r2 -> PCBUF + 56: its extra data\n mov r7,r0\n mov #PCBLOB_LEN,r1\n mulu.w r1,r0\n sts macl,r0\n add r0,r3\n mov #7,r6\nhq2_bl:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r6\n bf/s hq2_bl\n add #4,r2\n mov.l G_pcbuf,r5\n mov #PCBLOB_USED,r0\n mov.l r0,@(52,r5)\nhq2_go:\n mov.l G_pcp2,r1\n jmp @r1\n nop\n\n; ---- he_pend: r11 -> ST2. A recall's PLAY (ST3 +44 its slot, +48 the tick\n; of the key, +52 of the last PLAY): again every PEND_EVERY ticks until the\n; deck plays (he_rate), PEND_TICKS at most.\nhe_pend:\n sts.l pr,@-r15\n mov r11,r2\n add #64,r2\n mov.l @(44,r2),r0\n tst r0,r0\n bt hp2_ret\n mov.l G_ctx,r1 ; it plays: the player's state 1 (not he_rate's\n mov #67,r0 ; moving: the jump to the hot cue looks like\n shll2 r0 ; that, and a PLAY sent too early on the\n mov.l @(r0,r1),r0 ; player is lost; the UI then thinks it plays)\n cmp/eq #1,r0\n bt hp2_done\n bsr he_now\n nop\n mov r11,r2\n add #64,r2\n mov.l @(48,r2),r1\n mov r0,r3\n sub r1,r3\n mov #PEND_TICKS-256,r1\n extu.b r1,r1\n cmp/hs r1,r3\n bt hp2_done ; given up\n mov.l @(52,r2),r1\n mov r0,r3\n sub r1,r3\n mov #PEND_EVERY,r1\n cmp/hs r1,r3\n bf hp2_ret\n mov.l r0,@(52,r2)\n bsr he_play\n mov.l @(44,r2),r5\n bra hp2_ret\n nop\nhp2_done:\n mov #0,r0\n mov.l r0,@(44,r2)\nhp2_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_inj: r11 -> ST2. A recall from a VINYL pause (ST4 +63: 1 waiting,\n; 2 pressing; ST3 +48 the tick of the key): INJ_WAIT ticks later the PLAY\n; key is pressed for the UI (its bit set in the decoded keys) until\n; INJ_UNTIL, then let go, as a player's own press: the UI's PLAY logic\n; (0x08B45028) plays on from the hot cue where the CUE on the slot left the\n; deck, as after any VINYL pause.\nhe_inj:\n mov.l G_st4,r3\n add #63,r3\n mov.b @r3,r0\n extu.b r0,r2\n tst r2,r2\n bt hi_ret\n mov.l G_tick,r1\n mov.l @r1,r1\n mov r11,r0\n add #64,r0\n mov.l @(48,r0),r0\n sub r0,r1 ; ticks since the key\n mov r2,r0\n cmp/eq #2,r0\n bt hi_press\n mov #INJ_WAIT,r0\n cmp/hs r0,r1\n bf hi_ret\n mov #2,r0\n mov.b r0,@r3\nhi_press:\n mov #INJ_UNTIL,r0\n cmp/hs r0,r1\n bt hi_end ; let go\n mov.l G_keypc,r1\n mov.b @r1,r0\n or #4,r0 ; PLAY\n rts\n mov.b r0,@r1\nhi_end:\n mov #0,r0\n mov.b r0,@r3\nhi_ret:\n rts\n nop\n\n; ---- he_gap: r11 -> ST2, from he_busy when a slot load is done. A playing\n; beat jump's: the ticks from its key until its slot played, less how far it\n; has played since, averaged into ST3 +8 (0.1 .. 3 s). r8, r9 kept.\nhe_gap:\n sts.l pr,@-r15\n mov r11,r3\n add #64,r3\n mov.l @(24,r3),r0\n tst r0,r0\n bt hg2_ret\n mov #0,r0\n mov.l r0,@(24,r3)\n bsr he_pos\n nop\n mov r11,r3\n add #64,r3\n mov.l @(28,r3),r1\n sub r1,r0 ; played since, 1/150 s\n shll8 r0\n mov r0,r4\n mov.l @(12,r3),r5\n tst r5,r5\n bf hg2_r\n mov #96,r5\n shll2 r5\nhg2_r:\n bsr he_div\n nop\n mov r0,r6 ; ticks\n bsr he_now\n nop\n mov r11,r3\n add #64,r3\n mov.l @(4,r3),r1\n sub r1,r0\n sub r6,r0 ; key to playing\n mov.l @(8,r3),r1\n tst r1,r1\n bf hg2_avg\n mov r0,r1\nhg2_avg:\n add r1,r0\n shar r0\n mov #10,r1\n cmp/ge r1,r0\n bt hg2_lo\n mov r1,r0\nhg2_lo:\n mov #75,r1\n shll2 r1 ; 300\n cmp/gt r1,r0\n bf hg2_hi\n mov r1,r0\nhg2_hi:\n mov.l r0,@(8,r3)\nhg2_ret:\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nG_recall: .long hk_recall\nG_callat: .long hk_callat\nG_bjx: .long BJX\nG_dj: .long he_dj\nG_pos: .long he_pos\nG_tick: .long TICK\nG_st2: .long ST2\nG_st3: .long ST3\nG_dbreq: .long DB_REQ_REG\nG_st4: .long ST4\nG_dbqt: .long DBQ_TICKS\nG_busy: .long UI_DB_BUSY\nG_mode: .long UI_DB_MODE\nG_which: .long UI_DB_WHICH\nG_ready: .long UI_DB_READY\nG_ctx: .long PLAYER_CTX\nG_keypc: .long KEY_PLAYCUE\nG_media: .long UI_MEDIA\nG_usbok: .long UI_USB_OK\nG_dev: .long UI_DEVICE\nG_sender: .long UI_SENDER\nG_mask: .long 0xFFE7FFFF\nG_third: .long 0xAAAAAAAB\nG_dbregf: .long DB_REG\nG_pcbuf: .long PCBUF\nG_pcblob: .long PCBLOB\nG_pcp2: .long PCP2\nG_dbdelf: .long DB_DEL\n\n; ---- hk_send: the panel sender (r4-r7 as given, r6 = the frame): in the hot\n; cue key mode our five LEDs go into the frame, and its sum is made again.\nhk_send:\n mov.l L_setting,r1\n mov.b @(13,r1),r0\n tst r0,r0\n bt hs_go\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l L_ledtab,r2 ; (frame byte, field shift) for A-E\n mov r1,r3\n add #16,r3 ; on/off for A-E\n mov #5,r8\nhs_led:\n mov.b @r2,r0\n extu.b r0,r0\n mov r6,r9\n add r0,r9 ; the frame byte\n mov.b @(1,r2),r0\n extu.b r0,r1 ; the field's shift\n mov #3,r0\n shld r1,r0\n not r0,r0 ; mask out the field\n mov r0,r10\n mov.b @r3,r0 ; our level\n and #3,r0\n shld r1,r0\n mov r0,r1\n mov.b @r9,r0\n and r10,r0\n or r1,r0\n mov.b r0,@r9\n add #2,r2\n dt r8\n bf/s hs_led\n add #1,r3\n mov r6,r2 ; byte 22: sum of 0-21, carries added back\n mov #22,r8\n mov #0,r1\nhs_sum:\n mov.b @r2+,r0\n extu.b r0,r0\n add r0,r1\n mov r1,r0\n shlr8 r0\n tst r0,r0\n bt hs_nc\n extu.b r1,r1\n add #1,r1\nhs_nc:\n dt r8\n bf hs_sum\n mov r1,r0\n mov.b r0,@r2 ; r2 is at byte 22 now\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\nhs_go:\n mov.l L_sender,r1\n jmp @r1\n nop\n\n; ---- hk_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nhk_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt hs_ours\n mov.l L_show,r1\n jmp @r1\n nop\nhs_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l L_setting,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r4) ; shown as if being chosen\n mov.l L_show,r1\n jsr @r1\n nop\n mov.l L_setting,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- hk_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nhk_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt ht_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt ht_none\n mov.l r5,@(12,r4)\n mov.l L_setting,r1\n mov r5,r0\n mov.b r0,@r1\n tst r0,r0\n bf ht_save\n mov.b r0,@(1,r1) ; OFF: the mode goes with it\nht_save:\n sts.l pr,@-r15\n bsr hk_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nht_none:\n rts\n mov #0,r0\n\n; ---- hk_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; the way 0x08A681A0 sends the firmware's own\nhk_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l L_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l L_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf sv_out\n mov.l @r15,r9\n tst r9,r9\n bt sv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l L_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l L_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l L_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l L_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt sv_out\n mov.l L_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l L_release,r1\n jsr @r1\n nop\nsv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- hk_load: r1 -> HK_SETTING; the last SAVE_ID in the journal, if any\nhk_load:\n mov #1,r0\n mov.b r0,@(3,r1)\n mov.l L_journal,r2\n mov.l L_jend,r3\n mov.l L_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nhl_loop:\n cmp/hs r3,r2\n bt hl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt hl_out\n cmp/eq r5,r0\n bf hl_next\n mov.w @(2,r2),r0\n and #1,r0\n mov.b r0,@r1\nhl_next:\n bra hl_loop\n add #4,r2\nhl_out:\n rts\n nop\n\n; ---- hk_render: the bitmap widgets' draw method; the box after the anchor\nhk_render:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov r7,r11\n mov.l L_draw,r1\n jsr @r1\n nop\n mov.l L_setting,r1\n mov.b @r1,r0\n tst r0,r0\n bt hr_out\n mov #68,r0\n mov.l @(r0,r9),r1\n mov.l L_quant,r2 ; QUANTIZE itself: draw after it\n cmp/eq r2,r1\n bt/s hr_found\n mov r9,r4\n mov.l L_digit0,r2 ; a digit of the time display, drawn as it\n cmp/hs r2,r1 ; changes, in REMAIN and ELAPSED alike: after\n bf hr_rem ; it, with the QUANTIZE widget found before\n mov.l L_digit1,r2\n cmp/hi r2,r1\n bt hr_rem\n mov.l L_setting,r2\n mov.l @(8,r2),r4\n tst r4,r4\n bf hr_found\n bra hr_out\n nop\nhr_rem:\n mov.l L_anchor,r2 ; REMAIN: finds QUANTIZE among its siblings\n cmp/eq r2,r1\n bf hr_out\n mov.l @(8,r9),r1 ; the container\n tst r1,r1\n bt hr_out\n mov #40,r0\n mov.w @(r0,r1),r2\n extu.w r2,r2 ; children\n mov #36,r0\n mov.l @(r0,r1),r3\n tst r3,r3\n bt hr_out\n mov.l L_quant,r6\nhr_find:\n tst r2,r2\n bt hr_out\n mov.l @r3+,r4\n tst r4,r4\n bt hr_next\n mov #68,r0\n mov.l @(r0,r4),r0\n cmp/eq r6,r0\n bt hr_found\nhr_next:\n bra hr_find\n add #-1,r2\nhr_found:\n mov.l L_setting,r1\n mov.l r4,@(8,r1) ; remembered for hk_poll\n mov.l L_widget,r5 ; a copy, visible, further right, our resource\n mov r5,r2\n mov #20,r7\nhr_copy:\n mov.l @r4+,r0\n mov.l r0,@r2\n dt r7\n bf/s hr_copy\n add #4,r2\n mov #1,r0\n mov.l r0,@(12,r5)\n mov.l @(24,r5),r0\n add #BOX_DX,r0\n mov.l r0,@(24,r5)\n mov.l @(28,r5),r0\n add #BOX_DX,r0\n mov.l r0,@(28,r5)\n mov.l L_reshk,r1\n mov #68,r0\n mov.l r1,@(r0,r5)\n mov r8,r4\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\nhr_out:\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- hk_res: the resolver for the draw method; HK_RES is 0x1A8 with our pixels\nhk_res:\n mov.l L_resolve,r1\n mov.l L_reshk,r0\n cmp/eq r0,r4\n bt hq_ours\n jmp @r1\n nop\nhq_ours:\n sts.l pr,@-r15\n mov.l L_quant,r4\n jsr @r1\n nop\n tst r0,r0\n bt hq_out\n mov.l L_rec,r2\n mov r2,r3\n mov #11,r7\nhq_copy:\n mov.l @r0+,r1\n mov.l r1,@r3\n dt r7\n bf/s hq_copy\n add #4,r3\n mov.l L_setting,r1\n mov.b @(1,r1),r0\n tst r0,r0\n mov.l L_pix_on,r1\n bf hq_on\n mov.l L_pix_off,r1\nhq_on:\n mov.l L_phase,r0 ; loading: LOADING by turns (he_lock)\n mov.b @r0,r0\n tst r0,r0\n bt hq_set\n mov.l L_pixload,r1\nhq_set:\n mov.l r1,@(12,r2)\n mov r2,r0\nhq_out:\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nL_prev: .long PREV_DECODER\nL_setting: .long HK_SETTING\nL_key: .long KEY_QUANTIZE\nL_tick: .long TICK\nL_show: .long SHOW_SAVER\nL_draw: .long DRAW\nL_resolve: .long RESOLVE\nL_anchor: .long RES_ANCHOR\nL_digit0: .long DIGIT0\nL_digit1: .long DIGIT1\nL_quant: .long RES_QUANTIZE\nL_reshk: .long RES_HK\nL_widget: .long WIDGET\nL_rec: .long REC\nL_pix_on: .long PIX_ON\nL_pix_off: .long PIX_OFF\nL_phase: .long LOAD_PHASE\nL_pixload: .long PIX_LOAD\nL_pool: .long BACKUP_POOL\nL_mbx: .long BACKUP_MBX\nL_getblk: .long GET_BLOCK\nL_send: .long SEND\nL_release: .long RELEASE\nL_memset: .long MEMSET\nL_saveid: .long SAVE_ID\nL_beats: .long KEY_BEATS\nL_ledst: .long LED_STATE\nL_ledtab: .long LEDTAB\nL_sender: .long SENDER\nL_journal: .long JOURNAL\nL_jend: .long JOURNAL_END\nCODE_END:\n; ---- he_dj: r8 = a beat jump's target (1/150 s), r11 -> ST2. The jump made\n; by the DSP itself (dspjump): its playing head moves by n slots of 1/75 s in\n; the audio it has decoded, at once and without a gap, the CUE point stays.\n; n = the jump (BJ_DELTA, ms) * 75 / 1000, rounded, the rest carried to the\n; next jump (BJX +16), so jumps never drift. Playing (1), paused (3) or at\n; cue standby (0x17, or 3 after some seconds there): standing, he_djw pauses\n; the UI afterwards, so PLAY plays on from the new place and the CUE point\n; stays. Any other state, or a jump past DJ_MAX: dropped. (A jump within the\n; 0.1 s before the preload starts is undone by its CUE back: rare.)\n; -> r0 = 1 always (he_djw takes the result). BJX: +0 under way, +4 target,\n; +8 tick, +12 n, +16 rest (1/75 000 s), +20 the rest before, +28 x, +48 1 =\n; standing.\n .align 2\nhe_dj:\n sts.l pr,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l J_bjx,r9\ndj_try:\n mov.l J_pos,r0\n jsr @r0\n nop\n mov r1,r0\n mov #0,r2\n cmp/eq #1,r0\n bt dj_st\n mov #1,r2 ; standing: paused (3), cue standby (0x17, 3\n cmp/eq #3,r0 ; after some seconds there)\n bt dj_st\n cmp/eq #23,r0\n bf dj_no\ndj_st:\n mov.l r2,@(48,r9) ; he_djw pauses the UI then\n mov.l J_delta,r1\n mov.l @r1,r0\n mov #75,r1\n mul.l r1,r0\n sts macl,r4\n mov.l @(16,r9),r0\n mov.l r0,@(20,r9) ; the rest, should the DSP refuse\n add r0,r4 ; x = ms * 75 + rest\n mov.l r4,@(28,r9)\n mov #0,r10\n cmp/pz r4\n bt dj_abs\n neg r4,r4\n mov #1,r10\ndj_abs:\n mov.l J_500,r1\n add r1,r4\n mov.l J_recip,r1\n dmulu.l r1,r4\n sts mach,r0\n shlr2 r0\n shlr2 r0\n shlr2 r0 ; |n| = (|x| + 500) / 1000\n mov.l J_max,r1\n cmp/hi r1,r0\n bt dj_no\n tst r10,r10\n bt dj_sgn\n neg r0,r0\ndj_sgn:\n mov r0,r10 ; n\n mov.l r10,@(12,r9)\n mov.l J_1000,r1\n mul.l r1,r0\n sts macl,r1\n mov.l @(28,r9),r0\n sub r1,r0\n mov.l r0,@(16,r9) ; rest = x - 1000 n\n mov.l J_mb,r4\n add #4,r4\n mov.l J_wr,r0\n jsr @r0\n mov r10,r5\n mov.l J_mb,r4\n mov.l J_wr,r0\n jsr @r0\n mov #1,r5\n mov.l r8,@(4,r9)\n mov.l J_tick,r0\n mov.l @r0,r0\n mov.l r0,@(8,r9)\n mov #1,r0\n bra dj_out\n mov.l r0,@r9\ndj_no:\n mov #1,r0 ; not started: dropped (as if taken care of)\ndj_out:\n mov.l @r15+,r10\n mov.l @r15+,r9\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_djw: each poll, a DSP jump's result: done (the mailbox's command\n; back to 0, result 1), refused (2) or no answer in DJ_TICKS. Refused while\n; playing (the DSP keeps a margin of decoded audio ahead, which it fills on),\n; it is asked for again, the same n, until DJ_RETRY has passed; then, and\n; refused standing or unanswered, the jump is dropped and the rest goes back.\n; (A slot load or a memory CALL instead left the deck standing or the UI\n; frozen, hence none.)\nhe_djw:\n sts.l pr,@-r15\n mov.l r9,@-r15\n mov.l J_bjx,r9\n mov.l @r9,r0\n tst r0,r0\n bt djw_ret\n mov.l J_mb,r4\n mov.l J_rd,r0\n jsr @r0\n nop\n tst r0,r0\n bt djw_done\n mov.l J_tick,r0\n mov.l @r0,r0\n mov.l @(8,r9),r1\n sub r1,r0\n mov #DJ_TICKS,r1\n cmp/hs r1,r0\n bf djw_ret\n mov.l J_mb,r4 ; no answer: called off\n mov.l J_wr,r0\n jsr @r0\n mov #0,r5\n bra djw_fail\n nop\ndjw_done:\n mov.l J_mb,r4\n add #8,r4\n mov.l J_rd,r0\n jsr @r0\n nop\n cmp/eq #1,r0\n bt djw_fine\n mov.l @(48,r9),r0 ; refused: playing, the DSP decodes on ahead,\n tst r0,r0 ; so ask again (the same n) for DJ_RETRY\n bf djw_fail\n mov.l J_tick,r0\n mov.l @r0,r0\n mov.l @(8,r9),r1\n sub r1,r0\n mov.l J_retry,r1\n cmp/hs r1,r0\n bt djw_fail\n mov.l J_mb,r4\n mov.l J_wr,r0\n jsr @r0\n mov #1,r5\n bra djw_ret\n nop\ndjw_fine:\n mov.l @(48,r9),r0\n tst r0,r0\n bt djw_ok\n mov.l J_af28,r1 ; standing: the UI paused (0x0AF3AF28 5), so\n mov #5,r0 ; PLAY plays on from where the deck now is\n mov.l r0,@r1 ; (from cue standby it plays the CUE point)\ndjw_ok:\n mov #0,r0\n bra djw_ret\n mov.l r0,@r9\ndjw_fail:\n mov #0,r0 ; dropped: the rest as it was\n mov.l r0,@r9\n mov.l @(20,r9),r0\n mov.l r0,@(16,r9)\ndjw_ret:\n mov.l @r15+,r9\n lds.l @r15+,pr\n rts\n nop\n .align 4\nJ_bjx: .long BJX\nJ_delta: .long BJ_DELTA\nJ_pos: .long he_pos\nJ_500: .long 500\nJ_recip: .long 0x10624DD3\nJ_max: .long DJ_MAX\nJ_1000: .long 1000\nJ_mb: .long DSPJ_MB\nJ_wr: .long HPI_WRITE\nJ_rd: .long HPI_READ\nJ_tick: .long TICK\nJ_bjreq: .long BJ_REQ\nJ_bit31: .long 0x80000000\nJ_retry: .long DJ_RETRY\nJ_af28: .long 0x0AF3AF28\n\n; ---- he_shadow: r11 -> ST2, every poll. While the screen is held, UI_AFAC\n; is kept 0 (no CALL shows; other UI code than the event handler writes it\n; during a CALL too); otherwise it is left alone. The overview's playhead\n; reads SH_OVW, a copy of the player status block OVW_SRC, whose position\n; word stays while held.\nhe_shadow:\n mov r11,r3\n add #127,r3\n add #61,r3 ; ST4 + 60\n mov.b @r3,r4 ; held\n mov.l S_ctx,r1 ; but not while a track loads (player state 0,\n mov #67,r0 ; 2, 4): the UI must learn of the new track\n shll2 r0 ; (the engine starts over once it did)\n mov.l @(r0,r1),r0\n tst r0,r0\n bt hsh_free\n cmp/eq #2,r0\n bt hsh_free\n cmp/eq #4,r0\n bf hsh_held\nhsh_free:\n mov #0,r4\nhsh_held:\n tst r4,r4\n bt hsh_blk\n mov #0,r0\n mov.l S_afac,r1\n mov.l r0,@r1\nhsh_blk:\n mov.l S_ovsrc,r1\n mov.l S_ovsh,r2\n mov #72,r6\n shll r6 ; 0x90\n mov #0,r3\nhsh_loop:\n mov r3,r0\n cmp/eq #20,r0 ; the position's word\n bf hsh_copy\n tst r4,r4\n bf hsh_next ; held: as it was\nhsh_copy:\n mov.l @(r0,r1),r5\n mov.l r5,@(r0,r2)\nhsh_next:\n add #4,r3\n cmp/eq r6,r3\n bf hsh_loop\n rts\n nop\n\n; ---- pub_hook: in the player's position publisher, in place of its units ->\n; ms conversion (PUB_CONV: r4 = the position in 1/150 s, r5 = the speed ->\n; r0 = ms, stored at PLAYER_CTX + 0x2CC right after; +0x2C8 got the units\n; just before). Free, both are remembered (PUB_HELD); held, the remembered\n; ones are put back: +0x2C8 and the ms returned (+0x2C4, which the player's\n; CALL itself needs, is left alone; the UI does not take it while held).\npub_hook:\n sts.l pr,@-r15\n mov.l r4,@-r15\n mov.l Q_conv,r1\n jsr @r1\n nop\n mov.l @r15+,r4\n mov.l Q_hold,r1\n mov.b @r1,r1\n tst r1,r1\n bf pq_held\n mov.l Q_held,r1 ; free: remembered\n mov.l r4,@r1\n mov.l r0,@(4,r1)\n lds.l @r15+,pr\n rts\n nop\npq_held:\n mov.l Q_held,r1\n mov.l @r1,r2 ; the position as it was\n mov.l @(4,r1),r3 ; its ms\n mov.l Q_ctx,r1\n mov #-78,r0\n extu.b r0,r0\n shll2 r0 ; 0x2C8\n mov.l r2,@(r0,r1)\n mov r3,r0\n lds.l @r15+,pr\n rts\n nop\n .align 4\nS_ovsrc: .long OVW_SRC\nS_afac: .long UI_AFAC\nS_ctx: .long PLAYER_CTX\nS_ovsh: .long SH_OVW\nQ_conv: .long PUB_CONV\nQ_hold: .long HOLD_FLAG\nQ_held: .long PUB_HELD\nQ_ctx: .long PLAYER_CTX\n\n; ---- evt_hook: called from the UI's player-event handler (0x08B3BCC2) in\n; place of 'mov #-1,r7', r12 = the player's state, r11 = the event's kind.\n; Unless the screen is held (HOLD_FLAG; nor while a track loads, player\n; state 0, 2, 4) the six bytes the handler would write are written here:\n; UI_STATE, UI_KIND, and 0xFF into UI_STALE and the three at UI_STALE3 (the\n; UI then fetches the track's info again). Held, they are dropped: the UI\n; keeps the state and info it has. The handler's own stores land in\n; SH_SINK (r1, and its other literals). r7 = -1 as the instruction was.\nevt_hook:\n mov #-1,r7\n mov.l V_hold,r1\n mov.b @r1,r0\n tst r0,r0\n bt ev_write\n mov.l V_ctx,r1\n mov #67,r0\n shll2 r0\n mov.l @(r0,r1),r0 ; the player's state\n tst r0,r0\n bt ev_write\n cmp/eq #2,r0\n bt ev_write\n cmp/eq #4,r0\n bt ev_write\n bra ev_sink\n nop\nev_write:\n mov.l V_state,r1\n mov.b r12,@r1\n mov.l V_kind,r1\n mov.b r11,@r1\n mov.l V_stale,r1\n mov.b r7,@r1\n mov.l V_stale3,r1\n mov.b r7,@r1\n add #1,r1\n mov.b r7,@r1\n add #1,r1\n mov.b r7,@r1\nev_sink:\n mov.l V_sink,r1\n mov.l V_r5,r5\n rts\n nop\n\n; ---- hk_save2: r4 = value (low 16 bits) | id << 16: appended to the\n; settings journal by the Backup task (hk_save's way)\nhk_save2:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l V_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l V_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf sv2_out\n mov.l @r15,r9\n tst r9,r9\n bt sv2_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l V_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov r8,r0\n shlr16 r0\n mov.w r0,@(8,r9) ; the id\n mov r8,r0\n mov.w r0,@(10,r9) ; the value\n mov.l V_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l V_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt sv2_out\n mov.l V_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l V_relblk,r1\n jsr @r1\n nop\nsv2_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- hk_load2: r4 = an id, r5 = its default -> r0 = the last value of that\n; id in the settings journal, else the default\nhk_load2:\n mov.l V_journal,r2\n mov.l V_jend,r3\n mov #-1,r6\n extu.w r6,r6\n mov r5,r7\nhl2_loop:\n cmp/hs r3,r2\n bt hl2_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt hl2_out\n cmp/eq r4,r0\n bf hl2_next\n mov.w @(2,r2),r0\n extu.w r0,r0\n mov r0,r7\nhl2_next:\n bra hl2_loop\n add #4,r2\nhl2_out:\n rts\n mov r7,r0\n .align 4\nV_hold: .long HOLD_FLAG\nV_ctx: .long PLAYER_CTX\nV_state: .long UI_STATE\nV_kind: .long UI_KIND\nV_stale: .long UI_STALE\nV_stale3: .long UI_STALE3\nV_sink: .long SH_SINK\nV_r5: .long EVT_R5\nV_pool: .long BACKUP_POOL\nV_getblk: .long GET_BLOCK\nV_memset: .long MEMSET\nV_mbx: .long BACKUP_MBX\nV_send: .long SEND\nV_relblk: .long RELEASE\nV_journal: .long JOURNAL\nV_jend: .long JOURNAL_END\n\n; ---- afac_hook: r11 = a player event's kind, called from the UI's\n; player-event handler in place of its switch (AFAC_SWITCH): UI_AFAC as the\n; switch set it (2, 3, 4 and others 0; 5 -> 1, 6 -> 2, 7 -> 3; 1 and 9\n; leave it), unless the screen is held (then no CALL shows). r0-r2 used.\nafac_hook:\n mov.l A_hold,r1\n mov.b @r1,r0\n tst r0,r0\n bf ah_ret\n mov r11,r0\n cmp/eq #1,r0\n bt ah_ret\n cmp/eq #9,r0\n bt ah_ret\n mov #1,r2\n cmp/eq #5,r0\n bt ah_set\n mov #2,r2\n cmp/eq #6,r0\n bt ah_set\n mov #3,r2\n cmp/eq #7,r0\n bt ah_set\n mov #0,r2\nah_set:\n mov.l A_afac,r1\n mov.l r2,@r1\nah_ret:\n rts\n nop\n .align 4\nA_hold: .long HOLD_FLAG\nA_afac: .long UI_AFAC\n\n; ---- he_lock: r11 -> ST2, every engine pass. Loading: the screen held for\n; the preload (ST4 +60) or the waveform hidden while the deck waits to\n; preload after a track load (ST4 +62). Then the deck's keys (LOCK_MASK at\n; LOCK_BASE) are taken out of the decoded keys, a beat jump waiting is\n; dropped, and LOAD_PHASE is 1 every other 0.32 s: the HOT KEYS box shows\n; LOADING then (hk_res). When the phase changes the screen is redrawn, as\n; hk_poll does for the mode (the root of the QUANTIZE widget remembered at\n; HK_SETTING +8 gets +0x58 = 1).\nhe_lock:\n mov r11,r3\n add #127,r3\n add #61,r3 ; ST4 + 60\n mov.b @r3,r0\n mov r0,r2\n mov.b @(2,r3),r0 ; ST4 + 62\n or r0,r2\n mov #0,r0\n tst r2,r2\n bt hl_phase\n mov.l K2_base,r1 ; the keys\n mova K2_mask,r0\n mov r0,r3\n mov #11,r2\nhl_keys:\n mov.b @r3+,r0\n not r0,r0\n mov.b @r1,r4\n and r0,r4\n mov.b r4,@r1\n dt r2\n bf/s hl_keys\n add #1,r1\n mov.l K2_bjreq,r1 ; no beat jump\n mov #0,r0\n mov.l r0,@r1\n mov.l K2_tick,r1 ; the phase: 0.32 s on, 0.32 s off\n mov.l @r1,r0\n tst #32,r0\n movt r0\n xor #1,r0\nhl_phase:\n mov.l K2_phase,r1\n mov.b r0,@r1\n mov r0,r2\n mov.b @(1,r1),r0\n cmp/eq r2,r0\n bt hl_ret\n mov r2,r0\n mov.b r0,@(1,r1) ; changed: redraw\n mov.l K2_setting,r1\n mov.l @(8,r1),r4\nhl_root:\n tst r4,r4\n bt hl_ret\n mov.b @(4,r4),r0\n cmp/eq #20,r0\n bt hl_mark\n bra hl_root\n mov.l @(8,r4),r4\nhl_mark:\n mov #1,r2\n mov #88,r0\n mov.l r2,@(r0,r4)\nhl_ret:\n rts\n nop\n .align 4\nK2_base: .long LOCK_BASE\nK2_bjreq: .long BJ_REQ\nK2_tick: .long TICK\nK2_phase: .long LOAD_PHASE\nK2_setting: .long HK_SETTING\nK2_mask: .long LOCK_W0\n .long LOCK_W1\n .long LOCK_W2\n .align 4\n\n'''\n\n\ndef layout(code_len, box_len):\n \"\"\"Addresses after the code: the new UTILITY table, the label, the two\n boxes, the widget copy and the resource record.\"\"\"\n at = (HK_CODE + code_len + 3) & ~3\n table = at\n at += (UT_ITEMS + 1) * UT_ITEM\n label = at\n at += (len(LABEL) + 1) * 2\n at = (at + 3) & ~3\n widget = at\n at += 0x50\n rec = at\n at += 44\n ledtab = at\n at += 2 * len(FRAME_LEDS)\n at = (at + 3) & ~3\n nohot = at # no hot cues, for a build without cuecolours\n at += 8 * len(FRAME_LEDS)\n assert at <= HK_END, 'hotcuekeys: past its area'\n # the boxes in the second area, aligned as the blitter wants\n pix_on = (ST4 + 64 + 31) & ~31\n pix_off = (pix_on + box_len + 31) & ~31\n # the PCP2 writer's copy of a new hot cue, its extra data per number,\n # and the colours set hot cues get on the screen\n pcbuf = (pix_off + box_len + 3) & ~3\n pcblob = pcbuf + PCBUF_LEN\n hotcol = pcblob + 8 * PCBLOB_LEN\n assert hotcol + 16 <= HK2_END, 'hotcuekeys: past its second area'\n return dict(TABLE=table, LABEL=label, PIX_ON=pix_on, PIX_OFF=pix_off, WIDGET=widget, REC=rec,\n LEDTAB=ledtab, NOHOT=nohot, PCBUF=pcbuf, PCBLOB=pcblob, HOTCOL=hotcol, END=at)\n\n\ndef pcp2_blob(n):\n \"\"\"The extra data the PCP2 writer (0x08B10BE0) takes from a cue read from\n a file, as rekordbox writes it for hot cue n with no comment: its fields\n +0x0C (high half), +0x1C, +0x1D, +0x1E..+0x2A, a block length (4) and\n the colour block (code, R, G, B).\"\"\"\n code, r, g, b = HOT_COLOURS[(n - 1) % len(HOT_COLOURS)]\n blob = bytes(2) + bytes((0, 1)) + bytes(14) + struct.pack('> 3) << 11 | (g >> 2) << 5 | b >> 3\n\n\ndef routine(prev_decoder, hot=None, box_len=88 * 15 * 2):\n syms = {\n 'HK_SETTING': HK_SETTING, 'KEY_QUANTIZE': KEY_QUANTIZE, 'TICK': TICK, 'LONG_TICKS': LONG_TICKS,\n 'SHOW_SAVER': SHOW_SAVER, 'DRAW': DRAW, 'RESOLVE': RESOLVE, 'RES_ANCHOR': RES_ANCHOR, 'DIGIT0': RES_DIGITS[0], 'DIGIT1': RES_DIGITS[1],\n 'RES_QUANTIZE': RES_QUANTIZE, 'RES_HK': RES_HK, 'BOX_DX': BOX_DX, 'PREV_DECODER': prev_decoder,\n 'WIDGET': 0, 'REC': 0, 'PIX_ON': 0, 'PIX_OFF': 0, 'LEDTAB': 0, 'PCBUF': 0, 'PCBLOB': 0, 'HOTCOL': 0,\n 'PCP2': PCP2, 'PCBLOB_LEN': PCBLOB_LEN, 'PCBLOB_USED': PCBLOB_USED,\n 'BACKUP_POOL': BACKUP_POOL, 'BACKUP_MBX': BACKUP_MBX, 'GET_BLOCK': GET_BLOCK, 'SEND': SEND,\n 'RELEASE': RELEASE, 'MEMSET': MEMSET, 'SAVE_ID': SAVE_ID, 'JOURNAL': JOURNAL,\n 'JOURNAL_END': JOURNAL + JOURNAL_LEN, 'KEY_BEATS': KEY_BEATS, 'LED_STATE': LED_STATE,\n 'BEAT_BITS': BEAT_BITS, 'SENDER': SENDER, 'HIDE_MASK': 0xFF & ~BEAT_BITS,\n 'HOT': 0, 'CALL_REC': CALL_REC, 'CALL_KEY': CALL_KEY, 'PLAYER_CTX': PLAYER_CTX, 'BUILD': BUILD,\n 'BLINK_TICKS': BLINK_TICKS, 'GATE_TICKS': GATE_TICKS, 'CUE_POS': CUE_POS, 'ST2': ST2, 'ST2_SILENT': ST2_SILENT, 'UI_AFAC': UI_AFAC, 'SH_AFAC': SH_AFAC, 'PUB_HELD': PUB_HELD, 'HOLD_FLAG': HOLD_FLAG, 'PUB_CONV': PUB_CONV, 'SH_OVW': SH_OVW, 'LOAD_PHASE': LOAD_PHASE, 'PIX_LOAD': PIX_LOAD, 'LOCK_BASE': LOCK_BASE, 'LOCK_W0': int.from_bytes(bytes(LOCK_MASK[0:4]), 'little'), 'LOCK_W1': int.from_bytes(bytes(LOCK_MASK[4:8]), 'little'), 'LOCK_W2': int.from_bytes(bytes(LOCK_MASK[8:11]) + bytes(1), 'little'), 'UI_STATE': UI_STATE, 'UI_KIND': UI_KIND, 'UI_STALE': UI_STALE, 'UI_STALE3': UI_STALE3, 'SH_SINK': SH_SINK, 'EVT_R5': EVT_R5, 'DJ_RETRY': DJ_RETRY, 'OVW_SRC': OVW_SRC, 'KEY_PLAYCUE': KEY_PLAYCUE,\n 'INJ_WAIT': INJ_WAIT, 'INJ_UNTIL': INJ_UNTIL, 'HPI_READ': HPI_READ, 'HPI_WRITE': HPI_WRITE, 'WV_MID': WV_MID, 'WV_R12': WV_R12, 'WV_R13': WV_R13, 'WV_OUT': WV_OUT, 'WV_TICKS': WV_TICKS, 'SLOT_ST': SLOT_ST, 'SLOT_PT': SLOT_PT, 'TRANSPORT': TRANSPORT, 'PLAYCMD': PLAYCMD, 'CYCLIC': CYCLIC, 'PRELOAD_TICKS': PRELOAD_TICKS, 'BJX': BJX, 'BJ_SETTLE': BJ_SETTLE, 'BJ_POSV': BJ_POSV, 'BJ_REQ': BJ_REQ, 'BJ_DELTA': BJ_DELTA, 'DSPJ_MB': DSPJ_MB, 'DJ_TICKS': DJ_TICKS, 'DJ_MAX': DJ_MAX, 'MUTE_TICKS': MUTE_TICKS, 'BACK_TICKS': BACK_TICKS, 'MUTE_MAX': MUTE_MAX,\n 'LOAD_TICKS': LOAD_TICKS, 'PRELOAD_NEAR': PRELOAD_NEAR, 'PEND_EVERY': PEND_EVERY, 'PEND_TICKS': PEND_TICKS,\n 'UI_SENDER': UI_SENDER, 'DB_REQ_REG': DB_REQ_REG, 'DB_REG': DB_REG, 'DB_DEL': DB_DEL,\n 'UI_DEVICE': UI_DEVICE, 'UI_DB_BUSY': UI_DB_BUSY, 'UI_DB_MODE': UI_DB_MODE, 'UI_DB_WHICH': UI_DB_WHICH,\n 'UI_DB_READY': UI_DB_READY, 'UI_MEDIA': UI_MEDIA, 'USB_STATE': USB_STATE, 'GONE_ST': GONE_ST, 'STATUS_TAB': STATUS_TAB, 'STATUS_LEN': STATUS_LEN, 'STATUS_BUF': STATUS_BUF, 'UI_USB_OK': UI_USB_OK, 'ST4': ST4, 'DBQ_N': DBQ_N,\n 'DBQ_TICKS': DBQ_TICKS, 'HOT_KEYS': HOT_KEYS, 'BJ_SLOT': BJ_SLOT, 'ST3': ST3, 'BJ_GAP0': BJ_GAP0, 'MARKERS_DIRTY': MARKERS_DIRTY, 'KEY_DELETE': KEY_DELETE,\n 'DEL_BIT': DEL_BIT, 'DEL_KEEP': 0xFF & ~DEL_BIT, 'GREEN': cuecolours.HOT_DEFAULT, 'RESUME_POLLS': RESUME_POLLS,\n }\n code, labels = sh4asm.assemble(SOURCE, HK_CODE, syms)\n lay = layout(len(code), box_len)\n syms.update({k: lay[k] for k in ('WIDGET', 'REC', 'PIX_ON', 'PIX_OFF', 'LEDTAB', 'PCBUF', 'PCBLOB', 'HOTCOL')})\n syms['HOT'] = hot or lay['NOHOT']\n code, labels = sh4asm.assemble(SOURCE, HK_CODE, syms)\n assert layout(len(code), box_len) == lay\n labels.update(lay)\n return code, labels\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes.\"\"\"\n g24, g32, g36, g44, g48 = GENERIC\n return struct.pack('<13I', labels['LABEL'], ONOFF_LIST, 2, 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['hk_show'], g32, g36, labels['hk_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with HOT CUE KEYS added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n prev = struct.unpack_from(' KEY shows from the firmware's own table) is drawn from KX_BUF,\n rewritten by kn_xlate; one key also in its colour if the text is white\n (a highlighted row's black stays). The widget's +52 and +40 are put\n back after the draw. So the title bar, INFO and BROWSE > KEY all follow\n KEY NOTATION. The header line (HEADER_TEXT: the title, BROWSE's header,\n SEARCH's typed letters, where \"A\" would read 11B) is left alone. (A text\n that is only a key name by chance, an artist called \"B\" say, is taken\n for one too.)\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.2.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nKN_BASE = 0x08FD7C00 # after ledsaver's 0x08FD6C00..0x08FD7C00\nKN_END = 0x08FD8C00\nKN_SETTING = KN_BASE # byte: 0 as exported, 1 Camelot, 2 Open Key, 3 classic\nDEFAULT = 1\nKN_LOADED = KN_BASE + 1 # byte: the saved setting read\nKX_TMP = KN_BASE + 4 # 8 bytes: one key of a text, zero-padded (kn_xlate)\nKX_BUF = KN_END - 0x40 # 64 bytes: a text rewritten in our notation (kn_xlate)\nKN_CODE = KN_BASE + 0x10\n\nWCSNCPY = 0x08C0586E\nCOPY_LIT = 0x0897EB3C # 0x0897E5E2's literal of it\nKEY_RET = 0x0897E980 # the return of its copy of the key's name\nDRAW_LIT = 0x080B27F4 # the text widget's draw method in its class table\nDRAW_TEXT = 0x08B303BC # that method\nHEADER_TEXT = 0x0B7CE62E # the header line's text (title bar title, BROWSE's header, SEARCH's input)\nSAVE_ID = 0x83E3 # ours (hotcuekeys 0x83E1, ledsaver 0x83E2)\nLABEL = 'KEY NOTATION'\nVALUES = ('OFF', 'CAMELOT', 'OPEN KEY', 'CLASSIC')\n\n# Camelot 1A, 1B, 2A, ... 12B in rekordbox's classic spelling (the\n# firmware's table 0x08C46FE4 in the same order)\nCLASSIC = ('Abm', 'B', 'Ebm', 'F#', 'Bbm', 'Db', 'Fm', 'Ab', 'Cm', 'Eb', 'Gm', 'Bb',\n 'Dm', 'F', 'Am', 'C', 'Em', 'G', 'Bm', 'D', 'F#m', 'A', 'Dbm', 'E')\nALIASES = {'G#m': 0, 'D#m': 2, 'Gb': 3, 'A#m': 4, 'C#': 5, 'G#': 7, 'D#': 9, 'A#': 11,\n 'Gbm': 20, 'C#m': 22}\n\n\n# per key 1A, 1B, 2A, ... 12B: its colour on the user's wheel picture (RGB)\nCOLOURS = (0x02B399, 0x01E7CB, 0x06A956, 0x00CC65, 0x9AFE66, 0x66FF33, 0xFFDD67, 0xFFCC01,\n 0xFFAA67, 0x3C81F6, 0x3C81F6, 0x12B981, 0xFF99CB, 0x3C81F6, 0xFEAADC, 0xFF00CD,\n 0xCC99FE, 0xCC33FF, 0x99A9FF, 0x9966FF, 0x66BBFE, 0x6599FF, 0x00CCCB, 0x03FFFF)\n\n\ndef abgr(rgb):\n \"\"\"0xRRGGBB as the widget's colour word, 0xAABBGGRR, opaque.\"\"\"\n return 0xFF000000 | (rgb & 0xFF) << 16 | (rgb & 0xFF00) | rgb >> 16\n\n\ndef camelot(i):\n \"\"\"i = 0..23 (1A, 1B, 2A, ...): its Camelot spelling.\"\"\"\n return '%d%s' % (i // 2 + 1, 'AB'[i % 2])\n\n\ndef openkey(i):\n \"\"\"i = 0..23: its Open Key spelling (8A = 1m, 8B = 1d).\"\"\"\n return '%d%s' % ((i // 2 + 1 + 4) % 12 + 1, 'md'[i % 2])\n\n\ndef spellings():\n \"\"\"[(text, i)]: every spelling taken, per key its Camelot, Open Key and\n classic one, then the sharps (interleaved, so no run of the table looks\n like the firmware's own lists).\"\"\"\n out = []\n for i in range(24):\n out += [(camelot(i), i), (openkey(i), i), (CLASSIC[i], i)]\n out += sorted(ALIASES.items(), key=lambda kv: kv[1])\n return out\n\n\ndef entry(text):\n \"\"\"8 bytes: up to three UTF-16 characters, zero-padded.\"\"\"\n assert 1 <= len(text) <= 3\n return text.encode('utf-16-le').ljust(8, b'\\0')\n\n\ndef tables():\n \"\"\"(MATCH, INDEX, OUT): the spellings and their key numbers, then per\n key its Camelot, Open Key and classic spelling.\"\"\"\n sp = spellings()\n match = b''.join(entry(t) for t, _ in sp)\n index = bytes(i for _, i in sp)\n out = b''.join(entry(f(i)) for i in range(24) for f in (camelot, openkey, CLASSIC.__getitem__))\n return match, index, out\n\n\nSOURCE = r'''\n; ---- kn_copy: wcsncpy (r4 dst, r5 src, r6 n) through 0x0897E5E2's literal;\n; its copy of the key's name (returning to KEY_RET) then in our notation\nkn_copy:\n sts.l pr,@-r15\n mov.l r4,@-r15\n mov.l K_wcsncpy,r0\n jsr @r0\n nop\n mov.l @r15+,r4\n lds.l @r15+,pr\n mov.l K_ret,r1\n sts pr,r2\n cmp/eq r1,r2\n bt kn_key\n rts\n nop\nkn_key:\n mov.l r0,@-r15 ; wcsncpy's result, returned as it is\n sts.l pr,@-r15\n bsr kn_conv\n nop\n lds.l @r15+,pr\n rts\n mov.l @r15+,r0\n\n; ---- kn_conv: r4 -> a UTF-16 key name, rewritten in place in the chosen\n; notation if it is one of the spellings in MATCH (r0-r7 used)\nkn_conv:\n mov.l K_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf kc_loaded\n sts.l pr,@-r15\n bsr kn_load ; the first time: the saved setting\n nop\n lds.l @r15+,pr\n mov.l K_state,r1\nkc_loaded:\n mov.b @r1,r0\n extu.b r0,r7 ; 1 Camelot, 2 Open Key, 3 classic\n tst r7,r7\n bt kc_ret ; 0: as exported\n sts.l pr,@-r15\n bsr kn_lookup\n nop\n lds.l @r15+,pr\n cmp/pz r0\n bt kc_found\nkc_ret:\n rts\n nop\nkc_found:\n mov r0,r1\n shll r0\n add r1,r0 ; key * 3\n add #-1,r7\n add r7,r0 ; + notation\n shll2 r0\n shll r0 ; * 8\n mov.l K_out,r1\n add r1,r0\n mov #4,r6\nkc_put:\n mov.w @r0+,r1\n mov.w r1,@r4\n dt r6\n bf/s kc_put\n add #2,r4\n rts\n nop\n\n; ---- kn_lookup: r4 -> a UTF-16 key name; r0 = its key 0..23 (1A, 1B, 2A,\n; ...) if it is one of the spellings in MATCH, else -1 (r1-r3, r5, r6 used)\nkn_lookup:\n mov r4,r5 ; the name, zero-padded: r2 = chars 0-1, r3 = 2-3\n mov #0,r2\n mov #0,r3\n mov.w @r5+,r0\n extu.w r0,r0\n tst r0,r0\n bt kl_none ; empty\n mov r0,r2\n mov.w @r5+,r0\n extu.w r0,r0\n tst r0,r0\n bt kc_find\n shll16 r0\n or r0,r2\n mov.w @r5+,r0\n extu.w r0,r0\n tst r0,r0\n bt kc_find\n mov r0,r3\n mov.w @r5+,r0\n tst r0,r0\n bf kl_none ; longer than 3: no key\nkc_find:\n mov.l K_match,r5\n mov #NMATCH,r6\n mov #0,r0\nkc_loop:\n mov.l @r5+,r1\n cmp/eq r1,r2\n bf/s kc_next\n mov.l @r5+,r1\n cmp/eq r1,r3\n bt kc_found2\nkc_next:\n add #1,r0\n cmp/eq r6,r0\n bf kc_loop\nkl_none:\n rts\n mov #-1,r0\nkc_found2:\n mov.l K_index,r1\n mov.b @(r0,r1),r0\n rts\n extu.b r0,r0 ; 0..23: 1A, 1B, 2A, ...\n\n; ---- kn_draw: the text widgets' draw method (DRAW_TEXT) through its class\n; table entry; r4 the renderer, r5 the widget. A text of keys only is drawn\n; in our notation (kn_xlate), one key in its wheel colour if the text is\n; white; +52 and +40 put back after. Anything else: the method as it is.\nkn_draw:\n mov.l @(52,r5),r0\n tst r0,r0\n bt kd_go\n mov.l D_header,r1 ; not the header line (SEARCH's typed text)\n cmp/eq r1,r0\n bt kd_go\n mov.w @r0,r0 ; a key starts with 1-9 or A-G\n extu.w r0,r0\n add #-49,r0\n mov #22,r1\n cmp/hi r1,r0\n bt kd_go\n mov.l D_state,r1\n mov.b @r1,r0\n tst r0,r0\n bt kd_go ; OFF\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov r4,r8\n mov r5,r9\n mov.l @(52,r9),r10 ; the widget's text and colour, put back after\n mov.l @(40,r9),r11\n bsr kn_xlate\n mov r10,r4\n tst r0,r0\n bt kd_plain ; not keys\n mov.l r0,@(52,r9)\n cmp/pz r1\n bf kd_draw ; a list: no colour\n mov #-1,r0\n cmp/eq r0,r11\n bf kd_draw ; not white: as it is\n shll2 r1\n mov.l D_colours,r0\n mov.l @(r0,r1),r0\n mov.l r0,@(40,r9)\nkd_draw:\n mov r8,r4\n mov.l D_draw,r0\n jsr @r0\n mov r9,r5\n mov.l r10,@(52,r9)\n bra kd_out\n mov.l r11,@(40,r9)\nkd_plain:\n mov r8,r4\n mov.l D_draw,r0\n jsr @r0\n mov r9,r5\nkd_out:\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\nkd_go:\n mov.l D_draw,r0\n jmp @r0\n nop\n\n; ---- kn_xlate: r4 -> a UTF-16 text. Keys only (one, or several separated\n; by \",\" and spaces): r0 = KX_BUF holding it in the chosen notation, r1 =\n; the key (0..23) if it is one, else -1. Anything else: r0 = 0.\nkn_xlate:\n sts.l pr,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n add #-8,r15 ; @r15 the key's end (0 or ','), +4 the key\n mov r4,r12\n mov.l D_buf,r13\n mov #0,r14 ; keys\nkx_tok:\n mov.l D_tmp,r1 ; the next key into KX_TMP, up to 3 characters\n mov #0,r0\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov #4,r3\nkx_ch:\n mov.w @r12,r0\n extu.w r0,r0\n tst r0,r0\n bt kx_eot\n cmp/eq #44,r0 ; ','\n bt kx_eot\n dt r3\n bt kx_fail ; longer than 3: not a key\n mov.w r0,@r1\n add #2,r1\n bra kx_ch\n add #2,r12\nkx_eot:\n mov.l r0,@r15\n mov #4,r1\n cmp/eq r1,r3\n bt kx_fail ; empty\n mov.l D_tmp,r4\n bsr kn_lookup\n nop\n cmp/pz r0\n bf kx_fail\n mov.l r0,@(4,r15)\n add #1,r14\n mov r0,r1 ; OUT + (key * 3 + notation - 1) * 8\n shll r0\n add r1,r0\n mov.l D_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n add #-1,r1\n add r1,r0\n shll2 r0\n shll r0\n mov.l D_out,r1\n add r1,r0\n mov #4,r3\nkx_put:\n mov.w @r0+,r1\n extu.w r1,r1\n tst r1,r1\n bt kx_put_end\n mov.w r1,@r13\n dt r3\n bf/s kx_put\n add #2,r13\nkx_put_end:\n mov.l D_bufend,r1 ; room for one more key and \", \" at least\n cmp/hs r1,r13\n bt kx_fail\n mov.l @r15,r0\n tst r0,r0\n bt kx_done\n mov.w r0,@r13 ; ',' and the spaces after it\n add #2,r13\n add #2,r12\nkx_sp:\n mov.w @r12,r0\n extu.w r0,r0\n cmp/eq #32,r0\n bf kx_tok\n mov.w r0,@r13\n add #2,r13\n bra kx_sp\n add #2,r12\nkx_done:\n mov #0,r0\n mov.w r0,@r13\n mov #-1,r1\n mov r14,r0\n cmp/eq #1,r0\n bf kx_list\n mov.l @(4,r15),r1\nkx_list:\n mov.l D_buf,r0 ; (not in the delay slot: PC-relative)\n bra kx_ret\n nop\nkx_fail:\n mov #0,r0\n mov #-1,r1\nkx_ret:\n add #8,r15\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n lds.l @r15+,pr\n rts\n nop\n .align 4\nD_state: .long KN_SETTING\nD_header: .long HEADER_TEXT\nD_colours: .long COLOURS\nD_draw: .long DRAW_TEXT\nD_buf: .long KX_BUF\nD_bufend: .long KX_BUFEND\nD_tmp: .long KX_TMP\nD_out: .long OUT\n\n; ---- kn_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nkn_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt kw_ours\n mov.l K_show,r1\n jmp @r1\n nop\nkw_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l K_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf kw_loaded\n mov.l r5,@-r15 ; the row the caller passed in r5: the load uses r5\n bsr kn_load\n nop\n mov.l @r15+,r5\nkw_loaded:\n mov.l K_state,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r8) ; shown as if being chosen\n mov.l K_show,r1\n jsr @r1\n mov r8,r4\n mov.l K_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- kn_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nkn_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt kt_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt kt_none\n mov.l r5,@(12,r4)\n mov.l K_state,r1\n mov r5,r0\n mov.b r0,@r1\n mov #1,r0\n mov.b r0,@(1,r1)\n sts.l pr,@-r15\n bsr kn_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nkt_none:\n rts\n mov #0,r0\n\n; ---- kn_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; as ledsaver's ls_save\nkn_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l K_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l K_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf kv_out\n mov.l @r15,r9\n tst r9,r9\n bt kv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l K_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l K_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l K_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt kv_out\n mov.l K_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l K_release,r1\n jsr @r1\n nop\nkv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- kn_load: the last SAVE_ID in the journal, if any, into KN_SETTING\n; (r0-r3, r5, r6 used)\nkn_load:\n mov.l K_state,r1\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l K_journal,r2\n mov.l K_jend,r3\n mov.l K_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nkl_loop:\n cmp/hs r3,r2\n bt kl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt kl_out\n cmp/eq r5,r0\n bf kl_next\n mov.w @(2,r2),r0\n and #3,r0\n mov.b r0,@r1\nkl_next:\n bra kl_loop\n add #4,r2\nkl_out:\n rts\n nop\n\n .align 4\nK_wcsncpy: .long WCSNCPY\nK_ret: .long KEY_RET\nK_state: .long KN_SETTING\nK_match: .long MATCH\nK_index: .long INDEX\nK_out: .long OUT\nK_show: .long SHOW_SAVER\nK_pool: .long BACKUP_POOL\nK_mbx: .long BACKUP_MBX\nK_getblk: .long GET_BLOCK\nK_send: .long SEND\nK_release: .long RELEASE\nK_memset: .long MEMSET\nK_saveid: .long SAVE_ID\nK_journal: .long JOURNAL\nK_jend: .long JOURNAL_END\n'''\n\n\ndef routine(at=None):\n \"\"\"(code, labels): the code at KN_CODE, then MATCH, INDEX, OUT, the\n item's label, its values and their list, and room for the items.\"\"\"\n h = hotcuekeys\n match, index, out = tables()\n syms = {\n 'WCSNCPY': WCSNCPY, 'KEY_RET': KEY_RET, 'KN_SETTING': KN_SETTING,\n 'MATCH': 0, 'INDEX': 0, 'OUT': 0, 'COLOURS': 0, 'NMATCH': len(index),\n 'HEADER_TEXT': HEADER_TEXT, 'KX_TMP': KX_TMP, 'KX_BUF': KX_BUF, 'KX_BUFEND': KX_BUF + 0x40 - 16, 'DRAW_TEXT': DRAW_TEXT,\n 'SHOW_SAVER': h.SHOW_SAVER, 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX,\n 'GET_BLOCK': h.GET_BLOCK, 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET,\n 'SAVE_ID': SAVE_ID, 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN,\n }\n assert len(index) < 128\n code, labels = sh4asm.assemble(SOURCE, KN_CODE, syms)\n lay = {}\n a = (KN_CODE + len(code) + 3) & ~3\n for name, size in (('MATCH', len(match)), ('INDEX', len(index)), ('OUT', len(out)),\n ('COLOURS', 4 * len(COLOURS))):\n a = (a + 3) & ~3\n lay[name] = a\n a += size\n a = (a + 3) & ~3\n lay['LABEL'] = a\n a += 2 * len(LABEL) + 2\n lay['VALUE'] = []\n for v in VALUES:\n a = (a + 3) & ~3\n lay['VALUE'].append(a)\n a += 2 * len(v) + 2\n a = (a + 3) & ~3\n lay['LIST'] = a\n a += 4 * len(VALUES)\n lay['ITEMS'] = (a + 3) & ~3\n syms.update(MATCH=lay['MATCH'], INDEX=lay['INDEX'], OUT=lay['OUT'], COLOURS=lay['COLOURS'])\n code2, labels2 = sh4asm.assemble(SOURCE, KN_CODE, syms)\n assert len(code2) == len(code)\n labels2.update(lay)\n return code2, labels2, (match, index, out)\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as SCREEN SAVER's with our 4 values.\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], labels['LIST'], len(VALUES), 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['kn_show'], g32, g36, labels['kn_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with KEY NOTATION; its item goes after the UTILITY\n items the other mods left.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' %-4s %-4s %s' % (t, camelot(i), openkey(i), CLASSIC[i]))\n","mods/cdj900nxs/ledsaver.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"LED ANIMATION for the CDJ-900NXS 1.31: while the player's screen saver is\non (its display dimmed), the buttons' LEDs play an animation in a loop. Our\nown mod; mods/patch_cdj900nxs.py applies it as `ledsaver`.\n\n * UTILITY gets one more item, LED ANIMATION (OFF / ON, ON until chosen:\n the image holds LS_SETTING = 1, which a saved choice overrides),\n after the others (after HOT CUE KEYS when hotcuekeys is in). The choice\n is kept in the firmware's settings journal in flash, as HOT CUE KEYS'.\n * The screen saver is the firmware's own (0x08BB7D64): it counts at\n 0x0B7C95D8 (+0 with no track loaded, +4 paused or at the cue) and dims\n the backlight (0x08BD5D2C, event 6) past 0x927C or 0xB71B0; any key\n clears both. ls_poll reads the two counters with those thresholds, so\n the animation runs exactly while the display is dimmed; the dimming\n itself is unchanged.\n * The LEDs are 2-bit fields and bits of the 24-byte frame the deck sends\n the panel (sender 0x08AF488E, through its literal 0x08B653A4; byte 22\n the end-around-carry sum of 0-21). Mapped on a player with the LED test\n builds: byte 0 bits PLAY 0x01, CUE 0x02, REV 0x04, SLIP LOOP 0x08,\n RELOOP/EXIT 0x40, SLIP 0x80 (on/off only); byte 1 fields DISC EJECT\n (bits 0-1) and MASTER TEMPO (2-3), STANDBY (4-5); byte 3 SYNC (0-1) and\n MASTER (2-3); byte 4 BEAT DIVIDE 3/4 1/3 1/2 1/4, byte 5 bits 0-1 1/8\n (levels 0-3); the jog's rings: byte 8 bits 0x30 the white inner circle\n (0x40 there is the firmware's), byte 7 0xFF the red middle ring, byte 9\n a code for the big white outer ring (0x03: the ring with a small gap;\n 0xFF: none). Byte 11 is no LED: 0xFF there put the player into standby.\n Only the fields above (not STANDBY) are rewritten, field by field;\n clearing whole bytes of the frame had the deck drop the panel's replies.\n * ls_send rewrites them from FRAMES (one row per FRAME_TICKS) while the\n animation runs; with hotcuekeys in, its LED levels (HK_LEVELS' flag at\n HK_BASE +13) are kept out of the frame meanwhile.\n\"\"\"\nimport math\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.0.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nLS_BASE = 0x08FD6C00 # after hotcuekeys' second area\nLS_END = 0x08FD7C00\nLS_SETTING = LS_BASE # byte: 1 = UTILITY item ON (DEFAULT until a choice is saved)\nDEFAULT = 1\nLS_LOADED = LS_BASE + 1 # byte: the saved setting read\nLS_ACTIVE = LS_BASE + 2 # byte: the animation runs\nLS_FRAME = LS_BASE + 4 # word: the row of FRAMES\nLS_NEXT = LS_BASE + 8 # word: the tick of the next row\nLS_CODE = LS_BASE + 0x20\n\nSAVER_COUNT = 0x0B7C95D8 # +0, +4: the screen saver's counters\nSAVER_T0 = 0x927C # its thresholds (0x08BB7EA0, 0x08BB7EDA)\nSAVER_T1 = 0xB71B0\nSAVE_ID = 0x83E2 # ours (hotcuekeys has 0x83E1)\nLABEL = 'LED ANIMATION'\nFRAME_TICKS = 5 # 50 ms a row\nROWS = 90 # 4.5 s a loop (3 s was hectic on a player)\nOFFS = (0, 1, 3, 4, 5, 7, 8, 9) # the frame bytes it rewrites\n# per byte the bits it owns: cleared, then set from the row\nOWN = {0: 0xCF, 1: 0x0F, 3: 0x0F, 4: 0xFF, 5: 0x03, 7: 0xFF, 8: 0x30, 9: 0xFF}\nJOG_INNER = (8, 0x30) # the white inner circle\nJOG_MIDDLE = (7, 0xFF) # the red ring, all of it\nJOG_OUTER = (9, 0x03) # the big white ring (byte 9 is a code: 0x03 = the ring, a small gap)\n\n\ndef _tri(x, width):\n \"\"\"0..1..0 over width rows, 0 outside.\"\"\"\n if not 0 <= x < width:\n return 0.0\n return math.sin(math.pi * (x + 0.5) / width)\n\n\ndef frames():\n \"\"\"The animation, ROWS rows (4.5 s): a fade wave down the BEAT DIVIDE keys\n and back up, SYNC / MASTER and MASTER TEMPO / DISC EJECT breathing\n against each other, the on/off keys lit one after another and back, the\n jog's rings in turn from the inside out and back in.\"\"\"\n rows = []\n n = ROWS\n chase = (0x02, 0x01, 0x04, 0x80, 0x08, 0x40) # CUE PLAY REV SLIP SLIPLOOP RELOOP\n for t in range(n):\n x = t * 60 / n # drawn in 60 steps a loop\n lv = {}\n # dividers: a pulse 18 steps wide, 5 steps apart, down then up\n for k in range(5):\n down = _tri(x - 5 * k, 18)\n up = _tri(x - 30 - 5 * (4 - k), 18)\n lv['div%d' % k] = round(3 * max(down, up))\n br = 0.5 - 0.5 * math.cos(2 * math.pi * t / n)\n lv['sync'] = round(3 * br)\n lv['master'] = round(3 * (1 - br))\n lv['mt'] = round(3 * (0.5 - 0.5 * math.cos(2 * math.pi * (t / n + 0.25))))\n lv['eject'] = round(3 * (0.5 - 0.5 * math.cos(2 * math.pi * (t / n + 0.75))))\n b0 = 0\n slot = int(x // 5) # 12 slots of 5 steps\n if slot < 6:\n b0 = chase[slot]\n else:\n b0 = chase[11 - slot] # and back\n row = {\n 0: b0,\n 1: lv['eject'] | lv['mt'] << 2,\n 3: lv['sync'] | lv['master'] << 2,\n 4: lv['div0'] | lv['div1'] << 2 | lv['div2'] << 4 | lv['div3'] << 6,\n 5: lv['div4'],\n }\n # the jog's three rings, one at a time: inner, middle, outer, middle, ...\n ring = (JOG_INNER, JOG_MIDDLE, JOG_OUTER, JOG_MIDDLE)[t * 4 // n]\n row[7] = row[8] = row[9] = 0\n row[ring[0]] = ring[1]\n rows.append(row)\n return rows\n\n\ndef table():\n \"\"\"FRAMES: per row, (and, or) for each byte of OFFS.\"\"\"\n out = bytearray()\n for row in frames():\n for b in OFFS:\n assert row[b] & ~OWN[b] & 0xFF == 0\n out += bytes((~OWN[b] & 0xFF, row[b]))\n return bytes(out)\n\n\nSOURCE = r'''\n; ---- ls_poll: the panel decoder (or whatever ran there), then the animation's\n; state: on while the item is ON and the screen saver is on\nls_poll:\n sts.l pr,@-r15\n mov.l K_prev,r0\n jsr @r0\n nop\n mov.l r0,@-r15 ; the decoder's result, returned as it is\n mov.l K_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf lp_loaded\n bsr ls_load ; the first time: the saved setting\n nop\nlp_loaded:\n mov.l K_state,r1\n mov.b @r1,r0\n tst r0,r0\n bt lp_off\n mov.l K_count,r2\n mov.l @r2,r3\n mov.l K_t0,r0\n cmp/hi r0,r3\n bt lp_on\n mov.l @(4,r2),r3\n mov.l K_t1,r0\n cmp/hi r0,r3\n bt lp_on\nlp_off:\n mov #0,r0\n bra lp_out\n mov.b r0,@(2,r1)\nlp_on:\n mov.l K_tick,r2\n mov.l @r2,r2 ; now\n mov.b @(2,r1),r0\n tst r0,r0\n bf lp_adv\n mov #1,r0 ; it starts: the first row, now\n mov.b r0,@(2,r1)\n mov #0,r0\n mov.l r0,@(4,r1)\n mov.l r2,@(8,r1)\nlp_adv:\n mov.l @(8,r1),r3\n mov r2,r0\n sub r3,r0\n cmp/pz r0\n bf lp_hk ; not yet\n add #FRAME_TICKS,r3\n mov.l r3,@(8,r1)\n mov.l @(4,r1),r0\n add #1,r0\n mov #NROWS,r3\n cmp/hs r3,r0\n bf lp_row\n mov #0,r0\nlp_row:\n bra lp_adv\n mov.l r0,@(4,r1)\nlp_hk:\n mov.l K_hkleds,r2 ; hotcuekeys' LEDs out of the frame meanwhile\n tst r2,r2\n bt lp_out\n mov #0,r0\n mov.b r0,@r2\nlp_out:\n mov.l @r15+,r0\n lds.l @r15+,pr\n rts\n nop\n\n; ---- ls_send: the panel sender (r4-r7 as given, r6 = the frame): while the\n; animation runs, its row goes into the frame and the sum is made again\nls_send:\n mov.l K_state,r1\n mov.b @(2,r1),r0\n tst r0,r0\n bt ls_go\n mov.l r4,@-r15\n mov.l r5,@-r15\n mov.l r7,@-r15\n mov.l @(4,r1),r0 ; the row: 16 bytes each\n shll2 r0\n shll2 r0\n mov.l K_table,r2\n add r0,r2 ; -> (and, or) x 8\n mov.l K_offs,r5\n mov #NOFFS,r7\nls_byte:\n mov.b @r5+,r0\n extu.b r0,r0\n mov r6,r3\n add r0,r3 ; the frame byte\n mov.b @r2+,r4 ; and\n mov.b @r3,r0\n and r4,r0\n mov.b @r2+,r4 ; or\n or r4,r0\n dt r7\n bf/s ls_byte\n mov.b r0,@r3\n mov r6,r2 ; byte 22: sum of 0-21, carries added back\n mov #22,r7\n mov #0,r4\nls_sum:\n mov.b @r2+,r0\n extu.b r0,r0\n add r0,r4\n mov r4,r0\n shlr8 r0\n tst r0,r0\n bt ls_nc\n extu.b r4,r4\n add #1,r4\nls_nc:\n dt r7\n bf ls_sum\n mov r4,r0\n mov.b r0,@r2 ; r2 is at byte 22 now\n mov.l @r15+,r7\n mov.l @r15+,r5\n mov.l @r15+,r4\nls_go:\n mov.l K_sender,r1\n jmp @r1\n nop\n\n; ---- ls_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nls_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt lw_ours\n mov.l K_show,r1\n jmp @r1\n nop\nlw_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l K_state,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r4) ; shown as if being chosen\n mov.l K_show,r1\n jsr @r1\n nop\n mov.l K_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- ls_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nls_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt lt_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt lt_none\n mov.l r5,@(12,r4)\n mov.l K_state,r1\n mov r5,r0\n mov.b r0,@r1\n sts.l pr,@-r15\n bsr ls_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nlt_none:\n rts\n mov #0,r0\n\n; ---- ls_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; the way 0x08A681A0 sends the firmware's own (as hotcuekeys' hk_save)\nls_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l K_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l K_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf lv_out\n mov.l @r15,r9\n tst r9,r9\n bt lv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l K_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l K_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l K_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt lv_out\n mov.l K_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l K_release,r1\n jsr @r1\n nop\nlv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- ls_load: r1 -> LS_SETTING; the last SAVE_ID in the journal, if any\nls_load:\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l K_journal,r2\n mov.l K_jend,r3\n mov.l K_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nll_loop:\n cmp/hs r3,r2\n bt ll_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt ll_out\n cmp/eq r5,r0\n bf ll_next\n mov.w @(2,r2),r0\n and #1,r0\n mov.b r0,@r1\nll_next:\n bra ll_loop\n add #4,r2\nll_out:\n rts\n nop\n\n .align 4\nK_prev: .long PREV_DECODER\nK_sender: .long PREV_SENDER\nK_state: .long LS_SETTING\nK_count: .long SAVER_COUNT\nK_t0: .long SAVER_T0\nK_t1: .long SAVER_T1\nK_tick: .long TICK\nK_hkleds: .long HK_LEDS\nK_table: .long TABLE\nK_offs: .long OFFS_AT\nK_show: .long SHOW_SAVER\nK_pool: .long BACKUP_POOL\nK_mbx: .long BACKUP_MBX\nK_getblk: .long GET_BLOCK\nK_send: .long SEND\nK_release: .long RELEASE\nK_memset: .long MEMSET\nK_saveid: .long SAVE_ID\nK_journal: .long JOURNAL\nK_jend: .long JOURNAL_END\n'''\n\n\ndef routine(prev_decoder, prev_sender, hk_leds):\n \"\"\"(code, labels) at LS_CODE, then the table, OFFS and our label; hk_leds\n is hotcuekeys' LED flag (HK_BASE + 13), or 0 without it.\"\"\"\n h = hotcuekeys\n syms = {\n 'PREV_DECODER': prev_decoder, 'PREV_SENDER': prev_sender, 'LS_SETTING': LS_SETTING,\n 'SAVER_COUNT': SAVER_COUNT, 'SAVER_T0': SAVER_T0, 'SAVER_T1': SAVER_T1, 'TICK': h.TICK,\n 'HK_LEDS': hk_leds, 'TABLE': 0, 'OFFS_AT': 0, 'SHOW_SAVER': h.SHOW_SAVER,\n 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX, 'GET_BLOCK': h.GET_BLOCK,\n 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET, 'SAVE_ID': SAVE_ID,\n 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN,\n 'FRAME_TICKS': FRAME_TICKS, 'NROWS': len(frames()), 'NOFFS': len(OFFS),\n }\n code, labels = sh4asm.assemble(SOURCE, LS_CODE, syms)\n at = (LS_CODE + len(code) + 3) & ~3\n labels['TABLE'] = at\n at += len(table())\n labels['OFFS'] = at\n at = (at + len(OFFS) + 3) & ~3\n labels['LABEL'] = at\n at += 2 * len(LABEL) + 2\n labels['ITEMS'] = (at + 3) & ~3\n syms.update(TABLE=labels['TABLE'], OFFS_AT=labels['OFFS'])\n code2, labels2 = sh4asm.assemble(SOURCE, LS_CODE, syms)\n assert len(code2) == len(code)\n labels2.update({k: labels[k] for k in ('TABLE', 'OFFS', 'LABEL', 'ITEMS')})\n return code2, labels2\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as hotcuekeys' (OFF / ON).\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], hotcuekeys.ONOFF_LIST, 2, 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['ls_show'], g32, g36, labels['ls_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with LED ANIMATION added, after whatever other mods\n are in: it chains after the decoder and sender literals as it finds them\n and adds its item after the UTILITY items there are.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' PHASE METER,\nREVERSE, the line meter with its markers running from right to left; and the\nbeat countdown right of the meter moved BARS_DX pixels to the right, as the\nline meter reaches further than the stock boxes and ran into its digits.\nOurs; not in the released builds yet (patch(public=True) leaves it out).\n\n * PHASE METER is the firmware's SLIP FLASHING item (UTILITY item 3; its\n value at 0x0A4DEA1C, stored and saved by the firmware: 0 OFF, 1 ON).\n Its value list becomes OFF / ON / REVERSE (VALUES; OFF and ON the\n firmware's own strings), 2 being REVERSE.\n * arnoldasr's meter (phase_screen_update, the playback screen's callback\n table entry SCREEN_CB) shows the lines only for 1. pr_update takes the\n entry: with 2 it calls the meter with the value 1 for that call, and\n mirrors both markers' x on the line (x' = MIRROR - x, his markers sit at\n 0xD9 + 0..195), first back to his own before the call (so he still\n writes, and marks the screen for a redraw, only when a marker moved),\n then mirrored again after it.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '0.2.0' # the extras; arnoldasr's meter itself is 1.0.0 # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nPR_BASE = 0x08FDAC00 # after quicksearch's 0x08FDA400..0x08FDAC00\nPR_END = 0x08FDB000\nPR_CODE = PR_BASE + 0x10\n\nSETTING = 0x0A4DEA1C # SLIP FLASHING's value, PHASE METER's\nSCREEN_CB = 0x08C6CCD0 # the playback screen's callback (arnoldasr's phase_screen_update)\nSTOCK_CB = 0x08BD02E8\nMARKERS = (0x08FC2C60, 0x08FC2C64) # arnoldasr's two marker bitmaps (his state +0x60/+0x64)\nX0, SPAN = 0xD9, 195 # his marker x: X0 + 0..SPAN\nMIRROR = 2 * X0 + SPAN\nITEM = 3 # SLIP FLASHING in the UTILITY table\nITEM_LABEL = 0xA80CFF66 # ... its label (P2 address of the copy the phasemeter manifest renames)\nREVERSE = 'REVERSE'\n\nSOURCE = r'''\n; ---- pr_update: the playback screen's callback, arnoldasr's phase meter in\n; it; with PHASE METER REVERSE (2) the meter runs with 1 and its markers mirrored\npr_update:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov r4,r10\n mov r5,r11\n mov r6,r12\n mov r7,r13\n mov.l P_setting,r8\n mov.l @r8,r9\n mov r9,r0\n cmp/eq #2,r0\n bf pu_call\n mov #1,r0 ; the line meter for this call\n mov.l r0,@r8\n bsr pr_mirror ; his own x back\n nop\npu_call:\n mov r10,r4\n mov r11,r5\n mov r12,r6\n mov r13,r7\n mov.l P_meter,r0\n jsr @r0\n nop\n mov r0,r10 ; its result, returned as it is\n mov r9,r0\n cmp/eq #2,r0\n bf pu_ret\n mov.l r9,@r8 ; REVERSE again\n bsr pr_mirror\n nop\npu_ret:\n mov r10,r0\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- pr_mirror: both markers' x (+0x18 x0, +0x1C x1 = x0 + 8) mirrored on the line\npr_mirror:\n mov.l P_markers,r1\n mov #2,r3\npm_loop:\n mov.l @r1+,r2 ; where his marker pointer is\n mov.l @r2,r2 ; the marker (0: not installed)\n tst r2,r2\n bt pm_next\n mov.w @(24,r2),r0\n mov.l P_mirror,r4\n sub r0,r4\n mov r4,r0\n mov.w r0,@(24,r2)\n add #8,r0\n mov.w r0,@(28,r2)\npm_next:\n dt r3\n bf pm_loop\n rts\n nop\n\n .align 4\nP_setting: .long SETTING\nP_meter: .long METER\nP_markers: .long MARKERS_AT\nP_mirror: .long MIRROR\n'''\n\n\ndef routine(meter):\n syms = {'SETTING': SETTING, 'METER': meter, 'MIRROR': MIRROR, 'MARKERS_AT': 0}\n code, labels = sh4asm.assemble(SOURCE, PR_CODE, syms)\n at = (PR_CODE + len(code) + 3) & ~3\n labels['MARKERS_AT'] = at\n at += 4 * len(MARKERS)\n labels['TEXT'] = at\n at = (at + 2 * len(REVERSE) + 2 + 3) & ~3\n labels['LIST'] = at\n syms['MARKERS_AT'] = labels['MARKERS_AT']\n code2, l2 = sh4asm.assemble(SOURCE, PR_CODE, syms)\n assert len(code2) == len(code)\n l2.update({k: labels[k] for k in ('MARKERS_AT', 'TEXT', 'LIST')})\n return code2, l2\n\n\n# the beat countdown's layout records (x, y int16, resource id, ...) in .data:\n# master row y 36-55, this deck's row y 62-80; digits x 415 / 430 / 449,\n# point 444, \"Bars\" 463 (the meter's boxes, x 132 and 217, stay)\nBARS_TABLE = (0x08C45440, 0x08C458C8)\nBARS_X = (415, 430, 444, 449, 463)\nBARS_DX = 8\n\n\ndef shift_bars(app, stock):\n \"\"\"The beat countdown BARS_DX pixels further right.\"\"\"\n out = bytearray(app)\n n = 0\n for a in range(BARS_TABLE[0], BARS_TABLE[1], 2):\n x, y = struct.unpack_from('= 0x11000000 \\\n else struct.unpack_from('<2I', out, values - BASE)\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(PR_CODE, code)\n place(labels['MARKERS_AT'], struct.pack('<%dI' % len(MARKERS), *MARKERS))\n place(labels['TEXT'], REVERSE.encode('utf-16-le') + b'\\0\\0')\n place(labels['LIST'], struct.pack('<3I', off_on[0], off_on[1], labels['TEXT']))\n struct.pack_into('<2I', out, item + 4 - BASE, labels['LIST'], 3)\n struct.pack_into(' r0 FNV-1a of its UTF-16 characters (never 0); r1-r3 used\npl_hash:\n mov.l Q_fnv,r0\n mov.l Q_prime,r2\n mov #PL_LEN,r3\nph_char:\n mov.w @r4+,r1\n extu.w r1,r1\n tst r1,r1\n bt ph_done\n xor r1,r0\n mul.l r2,r0\n dt r3\n bf/s ph_char\n sts macl,r0\nph_done:\n tst r0,r0\n bf ph_out\n mov #1,r0\nph_out:\n rts\n nop\n\n; ---- pl_find(r4 text) -> r0 1 if it is in the list, else 0; r1-r7 used\npl_find:\n mov.l Q_count,r1\n mov.l @r1,r7\n tst r7,r7\n bt pf_no\n mov.l Q_list,r6\npf_entry:\n mov r4,r1\n mov r6,r2\npf_char:\n mov.w @r1+,r3\n mov.w @r2+,r5\n cmp/eq r3,r5\n bf pf_next\n tst r3,r3\n bf pf_char\n rts\n mov #1,r0\npf_next:\n mov.l Q_stride,r1\n dt r7\n bf/s pf_entry\n add r1,r6\npf_no:\n rts\n mov #0,r0\n\n .align 4\nQ_buf: .long PL_BUF\nQ_count: .long PL_COUNT\nQ_cur: .long PL_CUR\nQ_depth: .long BROWSE_DEPTH\nQ_fnv: .long 0x811C9DC5\nQ_hash: .long PL_HASH\nQ_list: .long PL_LIST\nQ_next: .long PL_NEXT\nQ_next_draw: .long NEXT_DRAW\nQ_ocount: .long PL_OCOUNT\nQ_oth: .long PL_OTH\nQ_prime: .long 0x01000193\nQ_sbuf: .long STATUS_BUF\nQ_stride: .long PL_STRIDE\nQ_title: .long TITLE_ID\nQ_green: .long GREEN\n\n; ---- pl_send: memcpy(r4, r5, r6) for the status packet's copy to send\n .align 4\npl_send:\n mov.l T_sbuf,r0\n cmp/eq r0,r5\n bf ps_go\n mov.l T_len,r0\n cmp/eq r0,r6\n bf ps_go\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov r5,r1 ; our tag: \"PM\", 1, 0, the hash big-endian\n add #120,r1\n add #120,r1\n mov #80,r0\n mov.b r0,@r1\n mov #77,r0\n mov.b r0,@(1,r1)\n mov #1,r0\n mov.b r0,@(2,r1)\n mov #0,r0\n mov.b r0,@(3,r1)\n mov.l T_hash,r2\n mov.l @r2,r2\n mov #40,r0 ; no track loaded: no hash\n mov.b @(r0,r5),r0\n tst r0,r0\n bf ps_loaded\n mov #0,r2\nps_loaded:\n mov r2,r0\n mov.b r0,@(7,r1)\n shlr8 r2\n mov r2,r0\n mov.b r0,@(6,r1)\n shlr8 r2\n mov r2,r0\n mov.b r0,@(5,r1)\n shlr8 r2\n mov r2,r0\n mov.b r0,@(4,r1)\n mov #33,r0 ; this player\n mov.b @(r0,r5),r9\n extu.b r9,r9\n mov.l T_tab,r8 ; the others' tags\n mov.l T_cur,r10\n mov #1,r11\nps_pl:\n mov.l T_len,r0\n add r0,r8\n add #4,r10\n mov #0,r2 ; none\n cmp/eq r11,r9\n bt ps_store\n mov r8,r1 ; the table keeps every word turned to little-endian\n add #120,r1\n add #120,r1\n mov.l @r1,r0\n mov.l T_magic,r3\n cmp/eq r3,r0\n bf ps_store\n mov.l @(4,r1),r2\nps_store:\n mov.l r2,@r10\n tst r2,r2\n bt ps_next\n mov.l T_ocount,r1 ; seen before?\n mov.l @r1,r3\n mov.l T_oth,r7\n tst r3,r3\n bt ps_add\nps_find:\n mov.l @r7+,r0\n cmp/eq r0,r2\n bt ps_next\n dt r3\n bf ps_find\nps_add:\n mov.l T_onext,r1 ; into the next slot\n mov.l @r1,r3\n mov r3,r0\n add #1,r0\n mov #PL_OMAX,r7\n cmp/hs r7,r0\n bf ps_wrap\n mov #0,r0\nps_wrap:\n mov.l r0,@r1\n shll2 r3\n mov.l T_oth,r7\n add r7,r3\n mov.l r2,@r3\n mov.l T_ocount,r1\n mov.l @r1,r3\n mov #PL_OMAX,r7\n cmp/hs r7,r3\n bt ps_next\n add #1,r3\n mov.l r3,@r1\nps_next:\n add #1,r11\n mov #PLAYERS,r0\n cmp/hi r0,r11\n bf ps_pl\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\nps_go:\n mov.l T_memcpy,r0\n jmp @r0\n nop\n\n .align 4\nT_cur: .long PL_CUR\nT_hash: .long PL_HASH\nT_len: .long STATUS_LEN\nT_magic: .long 0x504D0100\nT_memcpy: .long MEMCPY\nT_ocount: .long PL_OCOUNT\nT_onext: .long PL_ONEXT\nT_oth: .long PL_OTH\nT_sbuf: .long STATUS_BUF\nT_tab: .long STATUS_TAB\n'''\n\n\ndef routine(next_draw):\n return sh4asm.assemble(SOURCE, PL_CODE, {\n 'PL_BUF': PL_BUF, 'PL_COUNT': PL_COUNT, 'BROWSE_DEPTH': BROWSE_DEPTH, 'PL_LIST': PL_LIST,\n 'PL_NEXT': PL_NEXT, 'NEXT_DRAW': next_draw, 'TITLE_ID': TITLE_ID, 'GREEN': GREEN,\n 'PL_MAX': PL_MAX, 'PL_LEN': PL_LEN, 'PL_STRIDE': PL_LEN * 2, 'PL_HASH': PL_HASH,\n 'PL_CUR': PL_CUR, 'PL_OCOUNT': PL_OCOUNT, 'PL_ONEXT': PL_ONEXT, 'PL_OTH': PL_OTH,\n 'PL_OMAX': PL_OMAX, 'PLAYERS': PLAYERS, 'STATUS_BUF': STATUS_BUF, 'STATUS_LEN': STATUS_LEN,\n 'STATUS_TAB': STATUS_TAB, 'MEMCPY': MEMCPY})\n\n\ndef patch(app, stock):\n \"\"\"The application with PLAYED added (after keynotation and cuenames, if\n chosen); `stock` is the unmodified 1.31 application.\"\"\"\n assert PL_LEN * 2 == 128, 'pl_draw computes slot * 128'\n assert TAG_OFF == 240, 'pl_send adds 120 twice'\n out = bytearray(app)\n word = lambda buf, a: struct.unpack_from(' our state, r13 now. The way to SEARCH; where the\n; browse screen stood is kept for the next BROWSE (unless it is kept already)\nqs_search:\n sts.l pr,@-r15\n mov.b @(5,r14),r0\n tst r0,r0\n bf qsr_go\n mov.b @(10,r14),r0\n tst r0,r0\n bt qsr_go\n mov.b r0,@(12,r14)\n mov r0,r6\n mov #1,r0\n mov.b r0,@(5,r14)\n mov r14,r4\n add #65,r4\n mov r14,r5\n add #80,r5\n bsr qs_copy\n nop\nqsr_go:\n mov #1,r0\n mov.b r0,@(2,r14) ; kind 1: SEARCH\n bra qs_start\n lds.l @r15+,pr\n\n; ---- qs_goto: r4 -> a path (cursors of levels 1..r6), r14, r13. The way there\nqs_goto:\n sts.l pr,@-r15\n mov r6,r0\n mov.b r0,@(9,r14)\n mov r14,r5\n add #112,r5\n bsr qs_copy\n nop\n mov #2,r0\n mov.b r0,@(2,r14) ; kind 2: a path\n lds.l @r15+,pr\nqs_start: ; first the source's top list\n mov #1,r0\n mov.b r0,@(1,r14)\n mov.b r0,@(15,r14)\n mov #0,r0\n mov.b r0,@(3,r14)\n mov.b r0,@(8,r14)\n mov.b r0,@(13,r14)\n mov.b r0,@(14,r14)\n rts\n mov.l r13,@(28,r14)\n\n; ---- qs_step: r14, r13 now, r12 level, r11 cursor (index). The way's next\n; op, sent once the browse task has no DB answer due; then its effect awaited\nqs_step:\n mov.b @(1,r14),r0\n tst r0,r0\n bf qs_k1\n rts\n nop\nqs_k1:\n sts.l pr,@-r15\n mov.l @(28,r14),r1 ; one op that long (busy or stuck): the way ends\n mov r13,r9\n sub r1,r9 ; r9: ticks since the op was sent (or the way began)\n mov #GIVEUP_8,r1\n shll2 r1\n add r1,r1\n cmp/hs r1,r9\n bt qs_endj\n mov.l S_ctx,r1 ; a DB answer due: wait\n mov.l @(20,r1),r0\n mov.l @(28,r1),r2\n or r2,r0\n mov.l @(32,r1),r2\n or r2,r0\n add #24,r1\n mov.w @r1,r2\n extu.w r2,r2\n or r2,r0\n tst r0,r0\n bf qs_busy\n mov.b @(8,r14),r0\n tst r0,r0\n bf qs_k2\n bra qs_send\n nop\nqs_k2:\n bsr qs_done ; sent: shown yet, and for SETTLE_TICKS (the task idle all the\n nop ; time: its list in, not only its level)\n tst r0,r0\n bt qs_nyet\n mov.l @(32,r14),r1\n tst r1,r1\n bf qs_since\n bra qs_retx\n mov.l r13,@(32,r14)\nqs_since:\n mov r13,r2\n sub r1,r2 ; r2: shown that long\n mov #SETTLE_TICKS,r1 ; a new list slides in: SETTLE_TICKS; the cursor moved: CUR_TICKS\n mov.b @(15,r14),r0\n cmp/eq #2,r0\n bf qs_s2\n mov #CUR_TICKS,r1\nqs_s2:\n cmp/hs r1,r2\n bt qs_next\n bra qs_retx\n nop\nqs_nyet:\n mov #0,r0\n mov.l r0,@(32,r14)\n mov #OP_TICKS,r1 ; not after OP_TICKS: once more\n mov.b @(15,r14),r0 ; (the top list asked for with the screen closed only opens\n cmp/eq #1,r0 ; it where it was: asked again as soon as it shows)\n bf qs_k3\n mov #2,r0\n cmp/hs r0,r12\n bf qs_k3\n mov #20,r1\nqs_k3:\n cmp/hs r1,r9\n bf qs_retx\n mov.b @(13,r14),r0\n add #1,r0\n mov.b r0,@(13,r14)\n mov #MAX_TRIES,r1\n cmp/hs r1,r0\n bt qs_endj\n mov #0,r0 ; (the op again)\n bra qs_ret\n mov.b r0,@(8,r14)\nqs_endj:\n bra qs_end\n nop\nqs_busy:\n mov #0,r0 ; busy: settled anew afterwards\n mov.l r0,@(32,r14)\nqs_retx:\n bra qs_ret\n nop\n\n; the op shown: the next one\nqs_next:\n mov #0,r0\n mov.l r0,@(32,r14)\n mov.b r0,@(8,r14)\n mov.b r0,@(13,r14)\n mov.l r13,@(28,r14)\n mov.b @(15,r14),r0\n cmp/eq #1,r0\n bt qn_top\n cmp/eq #2,r0\n bt qn_cursor\n cmp/eq #3,r0 ; opened: one level further\n bf qs_endj\n mov.b @(3,r14),r0\n add #1,r0\n mov.b r0,@(3,r14)\n mov.b @(2,r14),r0\n cmp/eq #1,r0\n bt qs_endj ; SEARCH open: done\n bra qn_level\n nop\nqn_top:\n mov #1,r0 ; the top list there: level 1\n mov.b r0,@(3,r14)\n mov.b @(2,r14),r0\n cmp/eq #1,r0\n bf qn_level\n mov.l @(44,r14),r0 ; SEARCH: where it was last time, if anywhere\n tst r0,r0\n bt qn_look\n add #-1,r0\n mov.l r0,@(24,r14)\n mov #2,r0 ; the cursor there, then checked (qn_c2)\n mov.b r0,@(14,r14)\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_look:\n mov #4,r0 ; looked for\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_level:\n mov.b @(3,r14),r0 ; a path: the cursor onto this level's entry\n mov r14,r1\n add #111,r1\n add r0,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(24,r14)\n mov #2,r0\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_cursor:\n mov.b @(14,r14),r0 ; moved on to look further: look again\n tst r0,r0\n bt qn_c2\n cmp/eq #2,r0 ; onto where SEARCH was: is it there?\n bf qn_c1\n mov #0,r0\n mov.b r0,@(14,r14)\n mov.l S_rowids,r1 ; the cursor's row's entry code\n mov.l S_cursor,r2\n mov.l @r2,r2\n mov.l S_rowlen,r3\n mulu.w r2,r3\n sts macl,r3\n add r3,r1\n mov.l @r1,r0\n mov #SEARCH_ID,r2\n extu.b r2,r2\n cmp/eq r2,r0\n bt qn_c2 ; yes: open it\n mov #0,r0 ; no: forget it, look for it\n mov.l r0,@(44,r14)\n mov #4,r0\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_c1:\n mov #0,r0\n mov.b r0,@(14,r14)\n mov #4,r0\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_c2:\n mov.b @(2,r14),r0\n cmp/eq #1,r0\n bt qn_open ; SEARCH: open it\n mov.b @(9,r14),r0 ; a path: its last level only the cursor (kind 3: opened too)\n mov r0,r1\n mov.b @(3,r14),r0\n cmp/hs r1,r0\n bf qn_open\n mov.b @(2,r14),r0\n cmp/eq #3,r0\n bf qs_endj\n mov #5,r0 ; the track: open, and the way ends (the screen closes)\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_open:\n mov #3,r0\n bra qs_ret\n mov.b r0,@(15,r14)\n\n; the op sent (or, for 4, done at once)\nqs_send:\n mov.l r13,@(28,r14)\n mov #1,r0\n mov.b r0,@(8,r14)\n mov.b @(15,r14),r0\n cmp/eq #1,r0\n bt qd_top\n cmp/eq #2,r0\n bt qd_cursor\n cmp/eq #3,r0\n bt qd_open\n cmp/eq #5,r0\n bf qd_look\n mov #PULSE,r0 ; 5: the rotary pushed on the track (the UI task loads it, as\n add r13,r0 ; for a press: its \"Load Track Request\"), the way done\n bra qs_end\n mov.l r0,@(52,r14)\nqd_look:\n bra qs_look ; 4: SEARCH among the rows shown\n nop\nqd_top:\n mov.l @(48,r14),r5 ; the top list of the source last browsed (UI_STATE +4)\n tst r5,r5\n bf qd_t1\n mov #2,r5 ; (none seen yet: the USB)\nqd_t1:\n mov.l S_ctop,r4\n bra qd_cmd\n mov #0,r6\nqd_open:\n mov.l S_copen,r4\n mov #0,r5\n bra qd_cmd\n mov #0,r6\nqd_cursor:\n mov.l @(24,r14),r2 ; the target; the window moved as the UI moves it: kept while\n mov.l S_top,r1 ; the target is in it, else the target at its top or bottom\n mov.l @r1,r0 ; r0: the first row shown now\n cmp/gt r2,r0\n bf qc_k1\n bra qc_clamp ; above it: at the top\n mov r2,r0\nqc_k1:\n mov r0,r1\n add #5,r1\n cmp/gt r1,r2\n bf qc_clamp ; in it: as it is\n mov r2,r0 ; below it: at the bottom\n add #-5,r0\nqc_clamp:\n mov.l S_count,r1 ; not past count - 6, not below 0\n mov.l @r1,r1\n add #-6,r1\n cmp/gt r1,r0\n bf qc_k2\n mov r1,r0\nqc_k2:\n cmp/pz r0\n bt qc_k3\n mov #0,r0\nqc_k3:\n mov r0,r5\n mov r2,r6\n sub r0,r6\n mov.l S_ccursor,r4\nqd_cmd:\n bsr qs_cmd\n nop\n bra qs_ret\n nop\n\n; qs_look: SEARCH (entry code SEARCH_ID) among the rows shown: the cursor onto it;\n; not there: onto the first row after them (the next six shown), or the end\nqs_look:\n mov.l S_count,r1\n mov.l @r1,r3 ; r3: entries\n mov.l S_top,r1\n mov.l @r1,r4 ; r4: the first shown\n mov r3,r5\n sub r4,r5 ; r5: rows shown = min(6, entries - first)\n mov #6,r1\n cmp/hs r1,r5\n bf ql_n\n mov r1,r5\nql_n:\n cmp/pl r5\n bf ql_end\n mov.l S_rowids,r1\n mov #0,r6\n mov #SEARCH_ID,r7\n extu.b r7,r7\nql_loop:\n mov.l @r1,r0\n cmp/eq r7,r0\n bt ql_found\n mov.l S_rowlen,r2\n add r2,r1\n add #1,r6\n cmp/hs r5,r6\n bf ql_loop\n mov r4,r0 ; not shown: the next rows\n add r5,r0\n cmp/hs r3,r0\n bt ql_end ; no SEARCH in this list\n mov.l r0,@(24,r14)\n mov #1,r0\n mov.b r0,@(14,r14)\n bra ql_cur\n nop\nql_found:\n mov r4,r0\n add r6,r0\n mov.l r0,@(24,r14)\n add #1,r0 ; kept for next time (+1: 0 none)\n mov.l r0,@(44,r14)\nql_cur:\n mov #2,r0 ; the cursor there, at once\n mov.b r0,@(15,r14)\n mov #0,r0\n bra qs_ret\n mov.b r0,@(8,r14)\nql_end:\n bra qs_end\n nop\n\n; qs_done -> r0 1 when the op shows: the top list (level 1), the cursor on its\n; target, the next level open; r1-r3\nqs_done:\n mov.b @(15,r14),r0\n cmp/eq #1,r0\n bt qe_top\n cmp/eq #2,r0\n bt qe_cursor\n mov.b @(3,r14),r0 ; opened: level + 1\n add #1,r0\n cmp/eq r0,r12\n rts\n movt r0\nqe_top:\n mov r12,r0\n cmp/eq #1,r0\n rts\n movt r0\nqe_cursor:\n mov.l @(24,r14),r1\n mov.b @(3,r14),r0 ; on the level it is for\n cmp/eq r12,r0\n bf qe_no\n cmp/eq r1,r11\n rts\n movt r0\nqe_no:\n rts\n mov #0,r0\n\nqs_end:\n mov #0,r0\n mov.b r0,@(1,r14)\n mov.b r0,@(15,r14)\nqs_ret:\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nS_ctx: .long BR_CTX\nS_state: .long UI_STATE\nS_count: .long COUNT\nS_pane: .long PANE\nS_top: .long TOP\nS_cursor: .long CURSOR\nS_rowids: .long ROW_IDS\nS_rowlen: .long ROW_LEN\nS_ctop: .long CMD_TOP\nS_copen: .long CMD_OPEN\nS_ccursor: .long CMD_CURSOR\nS_fpmask: .long 0xFFE7FFFF\n\n; ---- qs_cmd: r4 command, r5 a1, r6 a2: a command to the browse task as the UI\n; task sends it (browse_cmd, but the sender the UI task: the browse task takes\n; commands only from it, and qs_poll runs in the panel poller's task)\nqs_cmd:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n add #-4,r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov #0,r0\n mov.l r0,@r15\n mov.l C_pool,r1 ; a message block from the UI's pool\n mov.l @r1,r4\n mov r15,r5\n mov.l C_size,r6\n mov.l C_getmpl,r0\n jsr @r0\n nop\n tst r0,r0\n bf cm_out\n mov.l @r15,r4\n mov #0,r5\n mov #112,r6\n mov.l C_memset,r0\n jsr @r0\n nop\n mov.l @r15,r1\n mov.l C_a80c,r2\n mov.l @r2,r2\n mov.l r2,@(4,r1)\n mov.l C_code,r2\n mov.l r2,@(8,r1)\n mov.l C_uitask,r2 ; from the UI task\n mov.l @r2,r2\n mov.l r2,@(16,r1)\n mov.l C_pool,r2\n mov.l @r2,r2\n mov.l r2,@(24,r1)\n mov #112,r2\n mov.l r2,@(32,r1)\n mov.l r8,@(36,r1)\n mov.l r9,@(40,r1)\n mov.l r10,@(44,r1)\n mov.l C_pool,r1 ; stamped with the time, as browse_cmd does\n mov.l @r1,r4\n mov #112,r5\n mov.l C_time,r0\n jsr @r0\n nop\n mov.l @r15,r1\n mov r0,r2\n mov #108,r0\n mov.l r2,@(r0,r1)\n mov.l C_mbx,r1 ; to the browse task's mailbox\n mov.l @r1,r4\n mov.l @r15,r5\n mov.l C_send,r0\n jsr @r0\n nop\n tst r0,r0\n bt cm_out\n mov.l C_pool,r1 ; not sent: the block back\n mov.l @r1,r4\n mov.l @r15,r5\n mov.l C_release,r0\n jsr @r0\n nop\ncm_out:\n mov.l C_fpmask,r1 ; FPSCR as the firmware leaves it after a call\n sts fpscr,r2\n and r1,r2\n lds r2,fpscr\n add #4,r15\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nC_pool: .long UI_POOL\nC_size: .long 0x1808\nC_getmpl: .long GET_MPL\nC_memset: .long MEMSET\nC_a80c: .long 0xA80CF854\nC_code: .long 0x32C9\nC_uitask: .long UI_TASK\nC_time: .long MSG_TIME\nC_mbx: .long BR_MBX\nC_send: .long SND_MBX\nC_release: .long REL_MPL\nC_fpmask: .long 0xFFE7FFFF\n'''\n\n\ndef routine(prev_decoder):\n syms = {\n 'PREV_DECODER': prev_decoder, 'QS_BASE': QS_BASE, 'RELOAD': RELOAD, 'TICK': hotcuekeys.TICK,\n 'KEY_BROWSE_AT': KEY_BROWSE[0], 'KEY_D6': KEY_D6, 'PUSH_BIT': PUSH_BIT,\n 'DEPTH': DEPTH, 'CURSOR': CURSOR, 'TOP': TOP, 'COUNT': COUNT, 'PANE': PANE, 'UI_STATE': UI_STATE,\n 'ROW_IDS': ROW_IDS, 'ROW_LEN': ROW_LEN, 'SEARCH_ID': SEARCH_ID - 256 if SEARCH_ID > 127 else SEARCH_ID,\n 'BR_CTX': BR_CTX, 'UI_POOL': UI_POOL, 'UI_TASK': UI_TASK, 'BR_MBX': BR_MBX,\n 'GET_MPL': GET_MPL, 'REL_MPL': REL_MPL, 'SND_MBX': SND_MBX, 'MSG_TIME': MSG_TIME, 'MEMSET': MEMSET,\n 'CMD_TOP': CMD_TOP, 'CMD_OPEN': CMD_OPEN, 'CMD_CURSOR': CMD_CURSOR,\n 'LONG_TICKS': LONG_TICKS, 'DOUBLE_TICKS': DOUBLE_TICKS, 'LOAD_TICKS': LOAD_TICKS,\n 'PULSE': PULSE, 'STABLE_TICKS': STABLE_TICKS, 'PUSH_QUIET': PUSH_QUIET,\n 'OP_TICKS': OP_TICKS, 'SETTLE_TICKS': SETTLE_TICKS, 'CUR_TICKS': CUR_TICKS, 'MAX_TRIES': MAX_TRIES, 'GIVEUP_8': GIVEUP_8,\n }\n return sh4asm.assemble(SOURCE, QS_CODE, syms)\n\n\ndef patch(app, stock):\n \"\"\"The application with QUICK SEARCH: chained after the decoder literal\n as it finds it.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' (bytes, labels). `symbols` gives names for\n.long values (addresses in the firmware). Delay slots are the writer's job.\"\"\"\nimport re\nimport struct\n\nR = {('r%d' % i): i for i in range(16)}\n\n\nclass AsmError(Exception):\n pass\n\n\ndef _reg(s):\n s = s.strip().lower()\n if s not in R:\n raise AsmError('not a register: %r' % s)\n return R[s]\n\n\ndef _num(s, syms):\n s = s.strip()\n if s.startswith('#'):\n s = s[1:]\n try:\n return int(s, 0)\n except ValueError:\n pass\n if s in syms:\n return syms[s]\n folded = {k.lower(): v for k, v in syms.items()}\n if s.lower() in folded:\n return folded[s.lower()]\n m = re.match(r'^(\\w+)\\s*([+-])\\s*(\\w+)$', s)\n if m:\n a, b = _num(m.group(1), syms), _num(m.group(3), syms)\n return a + b if m.group(2) == '+' else a - b\n raise AsmError('unknown value %r' % s)\n\n\ndef _split_ops(s):\n out, depth, cur = [], 0, ''\n for ch in s:\n if ch == '(':\n depth += 1\n elif ch == ')':\n depth -= 1\n if ch == ',' and depth == 0:\n out.append(cur.strip())\n cur = ''\n else:\n cur += ch\n if cur.strip():\n out.append(cur.strip())\n return out\n\n\n# two-register forms: mnemonic -> (template, 'mn' meaning Rm=first, Rn=second)\nRR = {\n 'mov': 0x6003, 'add': 0x300C, 'sub': 0x3008, 'and': 0x2009, 'or': 0x200B,\n 'xor': 0x200A, 'tst': 0x2008, 'cmp/eq': 0x3000, 'cmp/hs': 0x3002,\n 'cmp/ge': 0x3003, 'cmp/hi': 0x3006, 'cmp/gt': 0x3007, 'extu.b': 0x600C,\n 'extu.w': 0x600D, 'exts.b': 0x600E, 'exts.w': 0x600F, 'neg': 0x600B,\n 'not': 0x6007, 'swap.b': 0x6008, 'swap.w': 0x6009, 'mul.l': 0x0007,\n 'shad': 0x400C, 'shld': 0x400D, 'dmulu.l': 0x3005, 'dmuls.l': 0x300D,\n 'addc': 0x300E, 'subc': 0x300A, 'xtrct': 0x200D, 'mulu.w': 0x200E,\n}\nR1 = {\n 'shll': 0x4000, 'shlr': 0x4001, 'shll2': 0x4008, 'shlr2': 0x4009,\n 'shll8': 0x4018, 'shlr8': 0x4019, 'shll16': 0x4028, 'shlr16': 0x4029,\n 'shar': 0x4021, 'shal': 0x4020, 'dt': 0x4010, 'cmp/pz': 0x4011,\n 'cmp/pl': 0x4015, 'movt': 0x0029, 'rotl': 0x4004, 'rotr': 0x4005, 'rotcl': 0x4024, 'rotcr': 0x4025,\n}\nIMM0 = {'and': 0xC900, 'or': 0xCB00, 'xor': 0xCA00, 'tst': 0xC800, 'cmp/eq': 0x8800}\nSIZE = {'b': 0, 'w': 1, 'l': 2}\nNOARG = {'nop': 0x0009, 'rts': 0x000B, 'clrt': 0x0008, 'sett': 0x0018}\n\n\ndef _encode(mn, ops, pc, labels, syms, final):\n def lab(name):\n if name in labels:\n return labels[name]\n if final:\n raise AsmError('unknown label %r' % name)\n return pc + 4\n\n if mn in NOARG and not ops:\n return NOARG[mn]\n if mn in ('bt', 'bf', 'bt/s', 'bf/s', 'bt.s', 'bf.s'):\n base = {'bt': 0x8900, 'bf': 0x8B00}[mn[:2]] | (0x0400 if len(mn) > 2 else 0)\n d = (lab(ops[0]) - (pc + 4)) // 2\n if final and not -128 <= d < 128:\n raise AsmError('%s out of reach to %s' % (mn, ops[0]))\n return base | (d & 0xFF)\n if mn in ('bra', 'bsr'):\n d = (lab(ops[0]) - (pc + 4)) // 2\n if final and not -2048 <= d < 2048:\n raise AsmError('%s out of reach' % mn)\n return (0xA000 if mn == 'bra' else 0xB000) | (d & 0xFFF)\n if mn in ('jsr', 'jmp'):\n n = _reg(ops[0].lstrip('@'))\n return (0x400B if mn == 'jsr' else 0x402B) | n << 8\n if mn == 'sts.l' and ops[0] == 'pr':\n return 0x4022 | _reg(ops[1][2:]) << 8\n if mn == 'lds.l' and ops[1] == 'pr':\n return 0x4026 | _reg(ops[0][1:-1]) << 8\n if mn == 'sts':\n src = ops[0].lower()\n n = _reg(ops[1])\n return {'macl': 0x001A, 'mach': 0x000A, 'pr': 0x002A, 'fpscr': 0x006A}[src] | n << 8\n if mn == 'lds':\n m = _reg(ops[0])\n return {'pr': 0x402A, 'fpscr': 0x406A}[ops[1].lower()] | m << 8\n if mn == 'mova':\n tgt = lab(ops[0])\n d = (tgt - ((pc & ~3) + 4)) // 4\n if final and (not 0 <= d < 256 or tgt % 4):\n raise AsmError('mova out of reach')\n return 0xC700 | (d & 0xFF)\n if mn in R1:\n return R1[mn] | _reg(ops[0]) << 8\n if len(ops) == 2 and ops[0].startswith('#'):\n imm = _num(ops[0], syms)\n if mn == 'mov':\n if final and not -128 <= imm < 128:\n raise AsmError('mov #%d out of range' % imm)\n return 0xE000 | _reg(ops[1]) << 8 | (imm & 0xFF)\n if mn == 'add':\n if final and not -128 <= imm < 128:\n raise AsmError('add #%d out of range' % imm)\n return 0x7000 | _reg(ops[1]) << 8 | (imm & 0xFF)\n if mn in IMM0:\n if _reg(ops[1]) != 0:\n raise AsmError('%s #imm needs r0' % mn)\n if final and not (-128 <= imm < 256):\n raise AsmError('%s #%d out of range' % (mn, imm))\n return IMM0[mn] | (imm & 0xFF)\n if mn in RR and len(ops) == 2 and ops[0].lower() in R and ops[1].lower() in R:\n return RR[mn] | _reg(ops[1]) << 8 | _reg(ops[0]) << 4\n m = re.match(r'^mov\\.([bwl])$', mn)\n if m:\n sz = SIZE[m.group(1)]\n src, dst = ops\n # pc-relative literal: mov.l name,Rn / mov.w name,Rn\n if src.lower() not in R and not src.startswith('@') and not src.startswith('#'):\n tgt = lab(src)\n n = _reg(dst)\n if sz == 2:\n d = (tgt - ((pc & ~3) + 4)) // 4\n if final and (not 0 <= d < 256 or tgt % 4):\n raise AsmError('mov.l %s out of reach' % src)\n return 0xD000 | n << 8 | (d & 0xFF)\n if sz == 1:\n d = (tgt - (pc + 4)) // 2\n if final and not 0 <= d < 256:\n raise AsmError('mov.w %s out of reach' % src)\n return 0x9000 | n << 8 | (d & 0xFF)\n raise AsmError('no mov.b literal')\n if src.lower() in R: # stores\n mreg = _reg(src)\n if re.match(r'^@-r\\d+$', dst):\n return 0x2004 | sz | _reg(dst[2:]) << 8 | mreg << 4\n if re.match(r'^@r\\d+$', dst):\n return 0x2000 | sz | _reg(dst[1:]) << 8 | mreg << 4\n mm = re.match(r'^@\\((.+),(r\\d+)\\)$', dst.replace(' ', ''))\n if mm and mm.group(1) == 'r0':\n return 0x0004 | sz | _reg(mm.group(2)) << 8 | mreg << 4\n if mm:\n disp = _num(mm.group(1), syms)\n n = _reg(mm.group(2))\n if disp % (1 << sz) or not 0 <= disp >> sz < 16:\n raise AsmError('bad store displacement %d' % disp)\n if sz == 2:\n return 0x1000 | n << 8 | mreg << 4 | disp >> 2\n if mreg != 0:\n raise AsmError('mov.%s Rm,@(d,Rn) needs r0' % m.group(1))\n return (0x8000 if sz == 0 else 0x8100) | n << 4 | disp >> sz\n raise AsmError('bad store %r' % dst)\n n = _reg(dst) # loads\n if re.match(r'^@r\\d+\\+$', src):\n return 0x6004 | sz | n << 8 | _reg(src[1:-1]) << 4\n if re.match(r'^@r\\d+$', src):\n return 0x6000 | sz | n << 8 | _reg(src[1:]) << 4\n mm = re.match(r'^@\\((.+),(r\\d+)\\)$', src.replace(' ', ''))\n if mm and mm.group(1) == 'r0':\n return 0x000C | sz | n << 8 | _reg(mm.group(2)) << 4\n if mm:\n disp = _num(mm.group(1), syms)\n mreg = _reg(mm.group(2))\n if disp % (1 << sz) or not 0 <= disp >> sz < 16:\n raise AsmError('bad load displacement %d' % disp)\n if sz == 2:\n return 0x5000 | n << 8 | mreg << 4 | disp >> 2\n if n != 0:\n raise AsmError('mov.%s @(d,Rm),Rn needs r0' % m.group(1))\n return (0x8400 if sz == 0 else 0x8500) | mreg << 4 | disp >> sz\n raise AsmError('bad load %r' % src)\n raise AsmError('cannot encode %s %s' % (mn, ', '.join(ops)))\n\n\ndef _delayed(op):\n \"\"\"A branch with a delay slot: bra bsr bt/s bf/s jmp jsr rts rte braf bsrf.\"\"\"\n return (op >> 12 in (0xA, 0xB) or op >> 8 in (0x8D, 0x8F) or op & 0xF0FF in (0x402B, 0x400B, 0x0023, 0x0003)\n or op in (0x000B, 0x002B))\n\n\ndef _slot_illegal(op):\n \"\"\"Not allowed in a delay slot (the CPU raises a slot illegal instruction\n exception): branches, and PC-relative loads (mov.w/mov.l @(disp,PC), mova).\"\"\"\n return (_delayed(op) or op >> 8 in (0x89, 0x8B, 0xC3, 0xC7) or op >> 12 in (0x9, 0xD))\n\n\ndef assemble(text, base, syms=None):\n syms = dict(syms or {})\n lines = []\n for raw in text.splitlines():\n line = raw.split(';', 1)[0].strip()\n while True:\n m = re.match(r'^([A-Za-z_.][\\w.]*):\\s*(.*)$', line)\n if not m:\n break\n lines.append(('label', m.group(1), raw))\n line = m.group(2).strip()\n if line:\n parts = line.split(None, 1)\n lines.append(('ins', (parts[0].lower(), _split_ops(parts[1]) if len(parts) > 1 else []), raw))\n labels = {}\n for final in (False, True):\n pc, out = base, bytearray()\n prev = None # the last instruction's opcode (None after data)\n for kind, arg, raw in lines:\n if kind == 'label':\n if not final and arg in labels and labels[arg] != pc:\n raise AsmError('label %s twice' % arg)\n labels[arg] = pc\n continue\n mn, ops = arg\n try:\n if mn.startswith('.'):\n prev = None\n if mn == '.align':\n n = int(ops[0], 0)\n while pc % n:\n out += struct.pack(' UA_SETTING; the last SAVE_ID in the journal, if any\nua_load:\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l K_journal,r2\n mov.l K_jend,r3\n mov.l K_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nul_loop:\n cmp/hs r3,r2\n bt ul_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt ul_out\n cmp/eq r5,r0\n bf ul_next\n mov.w @(2,r2),r0\n and #1,r0\n mov.b r0,@r1\nul_next:\n bra ul_loop\n add #4,r2\nul_out:\n rts\n nop\n\n .align 4\nK_prev: .long PREV_DECODER\nK_state: .long UA_SETTING\nK_popup: .long POPUP\nK_tick: .long TICK\nK_menu: .long KEY_MENU_AT\nK_push: .long KEY_PUSH_AT\nK_giveup: .long GIVE_UP_AT\nK_show: .long SHOW_SAVER\nK_pool: .long BACKUP_POOL\nK_mbx: .long BACKUP_MBX\nK_getblk: .long GET_BLOCK\nK_send: .long SEND\nK_release: .long RELEASE\nK_memset: .long MEMSET\nK_saveid: .long SAVE_ID\nK_journal: .long JOURNAL\nK_jend: .long JOURNAL_END\n'''\n\n\ndef routine(prev_decoder):\n \"\"\"(code, labels) at UA_CODE, then our label and room for the items.\"\"\"\n h = hotcuekeys\n syms = {\n 'PREV_DECODER': prev_decoder, 'UA_SETTING': UA_SETTING, 'POPUP': POPUP,\n 'POPUP_FOUND': POPUP_FOUND, 'TICK': h.TICK,\n 'KEY_MENU_AT': KEY_MENU[0], 'KEY_MENU_BIT': KEY_MENU[1],\n 'KEY_PUSH_AT': KEY_PUSH[0], 'KEY_PUSH_BIT': KEY_PUSH[1],\n 'PRESS_TICKS': PRESS_TICKS, 'START_WAIT': START_WAIT, 'MENU_WAIT': MENU_WAIT, 'GIVE_UP_AT': MENU_WAIT + GIVE_UP,\n 'SHOW_SAVER': h.SHOW_SAVER, 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX,\n 'GET_BLOCK': h.GET_BLOCK, 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET,\n 'SAVE_ID': SAVE_ID, 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN,\n }\n code, labels = sh4asm.assemble(SOURCE, UA_CODE, syms)\n at = (UA_CODE + len(code) + 3) & ~3\n labels['LABEL'] = at\n at += 2 * len(LABEL) + 2\n labels['ITEMS'] = (at + 3) & ~3\n return code, labels\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as hotcuekeys' (OFF / ON).\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], hotcuekeys.ONOFF_LIST, 2, 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['ua_show'], g32, g36, labels['ua_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with USB AUTO LOAD added, after whatever other mods\n are in: it chains after the decoder literal as it finds it and adds its\n item after the UTILITY items there are.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' \"MY SETTINGS DATA was found.\\nPush the MENU button to LOAD.\"\n","mods/cdj900nxs/wavefix.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"advanced_waveforms 1.0.1: rekordbox's colour detail taken also when it is a\nlittle shorter than the waveform. Part of `advanced_waveforms` (based on\narnoldasr's rgb_waveform, now updated by us); mods/patch_cdj900nxs.py\napplies it with his code, released and unreleased builds alike.\n\n * His wave_fetch_hook asks for PWV5 after the stock PWV3 and finds it with\n wave_find_pwv5 (FIND_AT): the reply must hold at least N entries, N the\n PWV3 columns. rekordbox's PWV5 is often a few entries shorter (50 714\n against 50 734 on the test stick), and then the whole track kept the\n whiteness colours.\n * FIND_AT now jumps to wf5_find (same contract: r4 the reply, r5 its size,\n r6 N -> r0 the N entries, or 0): PWV5 with N or more entries as before;\n with fewer, down to N - TOLERANCE, a copy of ours padded with its last\n entry (WF5_BUF, malloc'd, freed at the next track), so his code reads N\n entries as it always did.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\n\nVERSION = '1.0.1'\nBASE = 0x08000000\nWF_BASE = 0x08FDD800 # after wavemodes' 0x08FDB000..0x08FDD800\nWF_END = 0x08FDDC00\nWF5_BUF = WF_BASE # word: our padded copy (0: none)\nWF_CODE = WF_BASE + 0x10\nFIND_AT = 0x08FC2018 # arnoldasr's wave_find_pwv5 (sts.l pr,@-r15; mov r4,r1; mov #0x40,r2 ...)\nFIND_STOCK = bytes.fromhex('224f4361 40e24363 1473')\nTOLERANCE = 1500 # columns (10 s) PWV5 may be short of PWV3\nMALLOC, FREE = 0x08A5C6EC, 0x08A5EBC2\n\nSOURCE = r'''\n; ---- wf5_find: r4 the reply, r5 its size, r6 N -> r0 N two-byte entries of\n; PWV5 (big-endian header within the first 0x40 bytes), or 0\nwf5_find:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n sts.l pr,@-r15\n mov r6,r12 ; N\n mov r4,r10\n add #20,r10\n add r5,r10 ; the reply's end\n mov r4,r8\n mov #64,r9\nf5_loop:\n mov.b @r8,r0\n cmp/eq #0x50,r0\n bf f5_next\n mov.b @(1,r8),r0\n cmp/eq #0x57,r0\n bf f5_next\n mov.b @(2,r8),r0\n cmp/eq #0x56,r0\n bf f5_next\n mov.b @(3,r8),r0\n cmp/eq #0x35,r0\n bf f5_next\n mov r8,r7 ; entry size 2\n bsr f5_be32\n add #12,r7\n cmp/eq #2,r0\n bf f5_next\n mov r8,r7 ; header length, at most 0x40\n bsr f5_be32\n add #4,r7\n mov #64,r1\n cmp/hi r1,r0\n bt f5_next\n mov r8,r11\n add r0,r11 ; the entries\n mov r8,r7\n bsr f5_be32\n add #16,r7 ; r0: how many\n mov r0,r1\n shll r1\n add r11,r1\n cmp/hi r10,r1\n bt f5_next ; past the reply\n cmp/hs r12,r0\n bt f5_whole ; N or more: as they are\n tst r0,r0\n bt f5_next\n mov.l W_tol,r1 ; short by at most TOLERANCE: a padded copy\n add r0,r1\n cmp/hs r12,r1\n bf f5_next\n bra f5_pad\n mov r0,r9 ; (how many)\nf5_next:\n dt r9\n bf/s f5_loop\n add #1,r8\n bra f5_out\n mov #0,r0\nf5_whole:\n bra f5_out\n mov r11,r0\nf5_pad:\n mov.l W_buf,r1 ; the copy of the track before, freed\n mov.l @r1,r4\n mov #0,r0\n mov.l r0,@r1\n tst r4,r4\n bt f5_alloc\n mov.l W_free,r0\n jsr @r0\n nop\nf5_alloc:\n mov r12,r4\n shll r4\n mov.l W_malloc,r0\n jsr @r0\n nop\n tst r0,r0\n bt f5_out\n mov r0,r8\n mov.l W_buf,r1\n mov.l r8,@r1\n mov r8,r2 ; the entries there are\n mov r11,r1\n mov r9,r3\nf5_copy:\n mov.b @r1+,r0\n mov.b r0,@r2\n mov.b @r1+,r0\n mov.b r0,@(1,r2)\n dt r3\n bf/s f5_copy\n add #2,r2\n add #-2,r1 ; then the last one up to N\n mov.b @r1+,r4\n mov.b @r1,r5\n mov r12,r3\n sub r9,r3\nf5_fill:\n mov r4,r0\n mov.b r0,@r2\n mov r5,r0\n mov.b r0,@(1,r2)\n dt r3\n bf/s f5_fill\n add #2,r2\n mov r8,r0\nf5_out:\n lds.l @r15+,pr\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- f5_be32: r0 = the big-endian word at r7 (any alignment); r5, r7 used\nf5_be32:\n mov.b @r7+,r0\n extu.b r0,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n rts\n or r5,r0\n\n .align 4\nW_tol: .long TOLERANCE\nW_buf: .long WF5_BUF\nW_free: .long FREE\nW_malloc: .long MALLOC\n'''\n\n\ndef patch(app, stock):\n out = bytearray(app)\n if bytes(out[FIND_AT - BASE:FIND_AT - BASE + len(FIND_STOCK)]) != FIND_STOCK:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: wave_find_pwv5 is not where it was')\n code, labels = sh4asm.assemble(SOURCE, WF_CODE, {\n 'TOLERANCE': TOLERANCE, 'WF5_BUF': WF5_BUF, 'FREE': FREE, 'MALLOC': MALLOC})\n assert WF_CODE + len(code) <= WF_END, 'wavefix: past its area'\n if len(out) < WF_END - BASE:\n out += bytes(WF_END - BASE - len(out))\n if any(out[WF_BASE - BASE:WF_END - BASE]):\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: its fix area 0x%08x.. is not free' % WF_BASE)\n out[WF_CODE - BASE:WF_CODE - BASE + len(code)] = code\n # FIND_AT: mov.l @(lit),r0; jmp @r0; nop; nop; lit = wf5_find\n out[FIND_AT - BASE:FIND_AT - BASE + 12] = struct.pack('<4HI', 0xD001, 0x402B, 0x0009, 0x0009, labels['wf5_find'])\n return bytes(out)\n","mods/cdj900nxs/wavemodes.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"WAVEFORM modes for advanced_waveforms on the CDJ-900NXS 1.31: UTILITY >\nWAVEFORM chooses how the scrolling waveform is drawn -- BLUE (the player's\nown blues), RGB (arnoldasr's rekordbox colours) or 3BAND (lows blue, mids\namber, highs white in layers, as the newest players draw it). Our own part\nof `advanced_waveforms` (mods/patch_cdj900nxs.py applies it with arnoldasr's\ncode, unless only the published part is asked for).\n\n * UTILITY gets one more item, WAVEFORM (BLUE / RGB / 3BAND, RGB until\n chosen: WM_SETTING), kept in the settings journal (SAVE_ID), as LED\n ANIMATION's.\n * arnoldasr's code puts a palette index in byte 2 of each column of the\n renderer's working buffer: 8..127 his 120 rekordbox colours (palette at\n his literal PALETTE_LIT), 0..7 the PWV3 whiteness where rekordbox's\n colour detail (PWV5) did not come.\n * The renderer 0x08BCFA34 draws a column as 46 rows up from the centre\n line (the lower half is mirrored by the firmware), every row below the\n column's height in one colour, @r8. Here r8 runs over a 46-entry colour\n column (ROW_LOAD mov.w @r8,r2 -> @r8+), and the 16 instructions at\n COL_AT that pick the palette entry call wm_col, which fills the column\n for the mode: BLUE the player's blue for the index (BLUEMAP: the index's\n whiteness, for rekordbox colours taken from their green and blue), RGB\n his palette entry, 3BAND the bands -- each band's height the column's\n height times its share (BANDS: red the lows, green the mids, blue the\n highs, each against the strongest; for 0..7 the lows in full, the mids\n by whiteness, the highs by its square) -- white up to the highs',\n amber up to the mids', blue up to the lows'. The pixel pointer, row\n count and FPSCR are left as the stock code leaves them.\n * The real 3 bands: rekordbox puts them in ANLZ*.2EX (PWV7: 3 bytes a\n column, lows, mids, highs; PWV6 the same for 1200 overview entries,\n but every band normalised alone). The 1.31 firmware never opens that\n file, but its DB server answers the NXS2's analysis-tag request 0x2c04\n for it as for EXT: wm_fetch (in the wave-fetch task's literal FETCH_LIT,\n after arnoldasr's hook there) asks for PWV7 in 2EX (wm_get_tag, his\n wave_get_tag with the tag and file as arguments), scales it to 0..46 px\n (the track's highest value full height) into a block of ours (WM_BANDS,\n WM_COUNT). In 3BAND a column with these is drawn from them -- highs\n white over mids amber (at MID_SCALE, so the lows show round them) over\n lows blue, as rekordbox layers them, the column as high as its highest\n band; without them the estimate above.\n * The overview: its loop (500 columns, 0x08BC9FB4) draws a column bottom\n up in one colour of its 12-entry table; its call of 0x08B03C5E once a\n column (literal OV_PLAYED_LIT) goes to wm_ov, which fills OVCOL with a\n colour per row and returns index 0, the table literal pointing at OVCOL\n and the three row loads made @r14+. In 3BAND with the real bands the\n column shows them (wm_ovbuild: each band's sum over the column's span\n of the detail, the highest sum OV_ROWS px; PWV6 is not used, its bands\n being normalised one by one), the part played dimmed (PLAYED_DIM);\n otherwise the firmware's colour for the column, from RAM OVERVIEW: the\n player's blues for BLUE, arnoldasr's gradient otherwise (written when\n the mode changes).\n * The firmware draws the overview only when its surface is new (a load);\n a mode changed with a track loaded showed on the detail at once but on\n the overview only after the next load. wm_ovtick (in the overview\n callers' two literals of the playing-address bar's draw, BAR_LITS, run\n every frame right after) draws it again once (OV_DRAW) after a change.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.2.0' # advanced_waveforms with the modes (Semantic Versioning)\nBASE = 0x08000000\nWM_BASE = 0x08FDB000 # after phasereverse's 0x08FDAC00..0x08FDB000\nWM_END = 0x08FDD800\nWM_SETTING = WM_BASE # byte: 0 BLUE, 1 RGB, 2 3BAND (DEFAULT until a choice is saved)\nDEFAULT = 1\nWM_LOADED = WM_BASE + 1 # byte: the saved setting read\nWM_APPLIED = WM_BASE + 2 # byte: the mode the overview's colours are for (0xFF: none yet)\nWM_REDRAW = WM_BASE + 3 # byte: 1 = the overview to be drawn again (the mode changed)\nOV_DRAW = 0x08BC9DAC # draws the overview (no arguments; its callers call it when its surface is new)\nBAR_LITS = (0x08BCBCD8, 0x08BCBEF0) # the overview's callers' literals of the bar's draw, right after it\nBAR_DRAW = 0x08BCA34E\nWM_BANDS = WM_BASE + 4 # word: the track's 3-band detail, 3 bytes a column (lows, mids,\nWM_COUNT = WM_BASE + 8 # highs, 0..46 px); word: its columns (0: none)\nWM_OVBANDS = WM_BASE + 12 # word: the overview's 3-band columns in OVB (wm_ovbuild), 0: none\nWM_TAG = WM_BASE + 20 # words: the tag and file asked for by wm_get_tag\nWM_EXT = WM_BASE + 24\nWM_CODE = WM_BASE + 0x40\nFETCH_LIT = 0x089A061C # the wave-fetch task's literal of dbcl_GetParWaveData (arnoldasr's hook in it)\nWORK = 0x0B7D0760 # the renderer's working buffer: *(WORK + WORK_OFF), 2 bytes a column\nWORK_OFF = 0x0002EE18\nMALLOC, FREE = 0x08A5C6EC, 0x08A5EBC2\nTAG_CHECK, TAG_ALLOC, TAG_INIT, TAG_SEND, TAG_WAIT = 0x089736F2, 0x089A791E, 0x08973C50, 0x089738F0, 0x08974198\nPWV7, EXT_2EX = 0x37565750, 0x00584532 # 'PWV7', '2EX' as the server's tag lookup reads them\nOV_COLUMNS = 500 # the overview's columns (0x08BC9FB4: 125 << 2)\nOV_PLAYED_LIT = 0x08BCA208 # the overview loop's literal of 0x08B03C5E (called once a column)\nOV_PLAYED = 0x08B03C5E\nOV_TABLE_LIT = 0x08BCA210 # ... and of its colour table (RAM 0x11106784)\nOV_LOADS = (0x08BCA050, 0x08BCA07A, 0x08BCA0B0) # mov.w @r14,r2 -> @r14+ (a colour per row)\nOV_ROWS = 34\nMAX_COLUMNS = 270000 # 30 minutes\n\nCOL_AT = 0x08BCFD70 # mov.b @(1,r14),r0 ... mov r0,r8: the palette entry of a column\nCOL_END = 0x08BCFD90\nROW_LOAD = 0x08BCFDA2 # mov.w @r8,r2 -> mov.w @r8+,r2\nPALETTE_LIT = 0x08BCFF50\nFF70_LIT = 0x08BCFF4C\nSTOCK_DETAIL = 0x08C6C75C # the player's 8 blues (image)\nOVERVIEW_IMG = 0x08C6C344 # the overview's 12 colours (image), copied to\nOVERVIEW = 0x11106784 # RAM at start-up\nSAVE_ID = 0x83E5 # ours (hotcuekeys 0x83E1, ledsaver 0x83E2, keynotation 0x83E3, usbautoload 0x83E4)\nLABEL = 'WAVEFORM'\nVALUES = ('BLUE', 'RGB', '3BAND')\nLOW = (40, 110, 255) # the newest players' look: lows blue, mids amber, highs white\nMID = (255, 150, 30)\nHIGH = (255, 255, 255)\nPLAYED_DIM = 0.6 # the overview's part played, 3BAND: colours at 60 %\nMID_SCALE = 218 # the real mids drawn at 85 %: as tall as the lows they hid them\n\n\ndef rgb565(c):\n r, g, b = c\n return (r >> 3) << 11 | (g >> 2) << 5 | b >> 3\n\n\nSOURCE = r'''\n; ---- wm_col: called in place of the renderer's palette pick (COL_AT): r14 ->\n; the column (height, colour index), r9 its height 1..46, r8 its x, r10 the\n; canvas. Out as the stock code: r13 the pixel, r14 46, r12 FPSCR; and r8 ->\n; the colour column (one entry per row up from the centre).\nwm_col:\n mov.l W_state,r7\n mov.b @(1,r7),r0\n tst r0,r0\n bf wc_loaded\n sts.l pr,@-r15\n bsr wm_load ; the first time: the saved setting\n mov r7,r1\n lds.l @r15+,pr\n mov.l W_state,r7\nwc_loaded:\n mov.b @r7,r0 ; the mode\n mov r0,r3\n mov.b @(2,r7),r0 ; the overview's colours for it?\n cmp/eq r3,r0\n bt wc_ov\n sts.l pr,@-r15\n bsr wm_overview\n nop\n lds.l @r15+,pr\nwc_ov:\n mov.b @(1,r14),r0 ; the palette index (0..127, negative: 0)\n cmp/pz r0\n bt wc_pos\n mov #0,r0\nwc_pos:\n mov r0,r6\n mov r3,r0\n cmp/eq #2,r0\n bt wc_bands\n shll r6 ; BLUE or RGB: one colour for the column\n mov.l W_bluemap,r1\n tst r0,r0\n bt wc_one\n mov.l W_palette,r1\nwc_one:\n add r6,r1\n mov.w @r1,r2\n mov.l W_col,r5\n mov r9,r6\nwc_fill:\n mov.w r2,@r5\n dt r6\n bf/s wc_fill\n add #2,r5\n bra wc_done\n nop\nwc_bands:\n mov.l W_count,r1 ; the track's real 3-band detail?\n mov.l @r1,r2\n tst r2,r2\n bt wc_est\n mov.l W_work,r1\n mov.l W_workoff,r0\n mov.l @(r0,r1),r1\n mov r14,r0 ; this column's index\n sub r1,r0\n shlr r0\n cmp/hs r2,r0\n bt wc_est\n mov r0,r1\n shll r0\n add r1,r0\n mov.l W_bandsp,r1\n mov.l @r1,r1\n add r0,r1\n mov.b @r1+,r2\n extu.b r2,r2 ; lows' height\n mov.b @r1+,r3\n extu.b r3,r3 ; mids', at MID_SCALE/256 (so the lows show round them)\n mov #MID_SCALE,r0\n extu.b r0,r0\n mulu.w r0,r3\n sts macl,r3\n shlr8 r3\n mov.b @r1,r4\n extu.b r4,r4 ; highs'\n mov.l W_colours,r7 ; as rekordbox layers them: highs over mids over lows\n mov r2,r9 ; the column: as high as its highest band\n cmp/hi r9,r3\n bf wc_m1\n mov r3,r9\nwc_m1:\n cmp/hi r9,r4\n bf wc_m2\n mov r4,r9\nwc_m2:\n tst r9,r9\n bf wc_layer2\n bra wc_layer2\n mov #1,r9\nwc_est:\n shll2 r6\n mov.l W_bands,r1\n add r6,r1\n mov.b @r1+,r2\n extu.b r2,r2 ; lows' share 0..255\n mov.b @r1+,r3\n extu.b r3,r3 ; mids'\n mov.b @r1,r4\n extu.b r4,r4 ; highs'\n add #1,r2 ; height * (share + 1) >> 8\n mulu.w r9,r2\n sts macl,r2\n shlr8 r2\n add #1,r3\n mulu.w r9,r3\n sts macl,r3\n shlr8 r3\n add #1,r4\n mulu.w r9,r4\n sts macl,r4\n shlr8 r4\n mov.l W_colours,r7 ; white, amber, blue: highs over mids over lows\nwc_layer2:\n mov.l W_col,r5\n mov #0,r6 ; the row up from the centre\nwc_row:\n cmp/ge r9,r6\n bt wc_done\n cmp/ge r4,r6\n bt wc_nohigh\n mov.w @r7,r0 ; inside the highs: white\n bra wc_put\n nop\nwc_nohigh:\n cmp/ge r3,r6\n bt wc_nomid\n mov.w @(2,r7),r0 ; the second band\n bra wc_put\n nop\nwc_nomid:\n mov.w @(4,r7),r0 ; the third (and the rest of the height)\nwc_put:\n mov.w r0,@r5\n add #2,r5\n bra wc_row\n add #1,r6\nwc_done:\n shll r8 ; the pixel, as the stock code makes it\n add r10,r8\n mov.l W_ff70,r1\n add r1,r8\n mov r8,r13\n mov.l W_col,r8\n mov #46,r14\n rts\n sts fpscr,r12\n\n; ---- wm_overview: r7 -> our state, r3 the mode: the overview's 12 colours\n; for it (the player's blues for BLUE, arnoldasr's otherwise)\nwm_overview:\n mov #1,r0 ; the overview drawn again (wm_ovtick)\n mov.b r0,@(3,r7)\n mov r3,r0\n mov.b r0,@(2,r7)\n mov.l W_ovrgb,r1\n tst r0,r0\n bf wo_src\n mov.l W_ovblue,r1\nwo_src:\n mov.l W_ovram,r2\n mov #12,r0\nwo_loop:\n mov.w @r1+,r4\n mov.w r4,@r2\n dt r0\n bf/s wo_loop\n add #2,r2\n rts\n nop\n\n; ---- wm_ovtick: through the two literals the overview's callers have for the\n; playing-address bar (drawn every frame, right after the overview is drawn\n; when its surface is new, i.e. on a load): once the mode changed (WM_REDRAW,\n; set by wm_overview), the overview is drawn again first, so a WAVEFORM\n; changed with a track loaded shows on both waveforms at once; then the bar\nwm_ovtick:\n mov.l T_state,r1\n mov.b @(3,r1),r0\n tst r0,r0\n bt wt_bar\n mov #0,r0\n mov.b r0,@(3,r1)\n sts.l pr,@-r15\n mov.l r4,@-r15\n mov.l r5,@-r15\n mov.l r6,@-r15\n mov.l r7,@-r15\n mov.l T_ovdraw,r0\n jsr @r0\n nop\n mov.l T_fpmask,r1 ; FPSCR as the callers leave it after the overview\n sts fpscr,r2\n and r1,r2\n lds r2,fpscr\n mov.l @r15+,r7\n mov.l @r15+,r6\n mov.l @r15+,r5\n mov.l @r15+,r4\n lds.l @r15+,pr\nwt_bar:\n mov.l T_bar,r0\n jmp @r0\n nop\n\n .align 4\nT_state: .long WM_SETTING\nT_ovdraw: .long OV_DRAW\nT_fpmask: .long 0xFFE7FFFF\nT_bar: .long PREV_BAR\n\n; ---- wm_load: r1 -> WM_SETTING; the last SAVE_ID in the journal, if any\nwm_load:\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l W_journal,r2\n mov.l W_jend,r3\n mov.l W_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nwl_loop:\n cmp/hs r3,r2\n bt wl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt wl_out\n cmp/eq r5,r0\n bf wl_next\n mov.w @(2,r2),r0\n and #3,r0\n cmp/eq #3,r0\n bt wl_next ; (no such value)\n mov.b r0,@r1\nwl_next:\n bra wl_loop\n add #4,r2\nwl_out:\n rts\n nop\n\n; ---- wm_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nwm_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt ww_ours\n mov.l W_show,r1\n jmp @r1\n nop\nww_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l W_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf ww_loaded\n mov.l r5,@-r15 ; the row the caller passed in r5: the load uses r5\n bsr wm_load\n nop\n mov.l @r15+,r5\nww_loaded:\n mov.l W_state,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r8) ; shown as if being chosen\n mov.l W_show,r1\n jsr @r1\n mov r8,r4\n mov.l W_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- wm_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nwm_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt wt_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt wt_none\n mov.l r5,@(12,r4)\n mov.l W_state,r1\n mov r5,r0\n mov.b r0,@r1\n mov #1,r0\n mov.b r0,@(1,r1)\n sts.l pr,@-r15\n bsr wm_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nwt_none:\n rts\n mov #0,r0\n\n; ---- wm_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5)\nwm_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l W_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l W_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf wv_out\n mov.l @r15,r9\n tst r9,r9\n bt wv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l W_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l W_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l W_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l W_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt wv_out\n mov.l W_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l W_release,r1\n jsr @r1\n nop\nwv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nW_state: .long WM_SETTING\nW_count: .long WM_COUNT\nW_bandsp: .long WM_BANDS\nW_work: .long WORK\nW_workoff: .long WORK_OFF\nW_bands: .long BANDS\nW_bluemap: .long BLUEMAP\nW_palette: .long PALETTE\nW_col: .long COLUMN\nW_colours: .long COLOURS\nW_ff70: .long 0xFF70\nW_ovrgb: .long OV_RGB\nW_ovblue: .long OV_BLUE\nW_ovram: .long OVERVIEW\nW_journal: .long JOURNAL\nW_jend: .long JOURNAL_END\nW_saveid: .long SAVE_ID\nW_show: .long SHOW_SAVER\nW_pool: .long BACKUP_POOL\nW_mbx: .long BACKUP_MBX\nW_getblk: .long GET_BLOCK\nW_send: .long SEND\nW_release: .long RELEASE\nW_memset: .long MEMSET\n\n; ---- wm_fetch: the wave-fetch task's dbcl_GetParWaveData through its literal\n; (r4 connection, r5 track, r6 &size, r7 &block), arnoldasr's hook first;\n; then the track's 3-band detail (PWV7 in ANLZ*.2EX) by the analysis-tag\n; request, scaled to 0..46 px (the track's highest value full height) into\n; a block of ours (WM_BANDS, WM_COUNT). Returns what the call returned.\nwm_fetch:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-8,r15\n mov r4,r8\n mov r5,r9\n mov.l F_prev,r0\n jsr @r0\n nop\n mov r0,r12\n cmp/pz r12\n bf wf_out\n mov.l F_state,r1 ; ask for PWV7 in 2EX\n mov.l F_pwv7,r0\n mov.l r0,@(20,r1)\n mov.l F_2ex,r0\n mov.l r0,@(24,r1)\n mov #0,r0\n mov.l r0,@r15\n mov.l r0,@(4,r15)\n mov r8,r4\n mov r9,r5\n mov r15,r6\n mov r15,r7\n bsr wm_get_tag\n add #4,r7\n mov.l @(4,r15),r10 ; the reply\n tst r10,r10\n bt wf_out\n cmp/pz r0\n bf wf_free\n mov r10,r1 ; 'PWV7' in its first 0x40 bytes\n mov #64,r2\nff_loop:\n mov.b @r1,r0\n cmp/eq #0x50,r0\n bf ff_next\n mov.b @(1,r1),r0\n cmp/eq #0x57,r0\n bf ff_next\n mov.b @(2,r1),r0\n cmp/eq #0x56,r0\n bf ff_next\n mov.b @(3,r1),r0\n cmp/eq #0x37,r0\n bt ff_found\nff_next:\n dt r2\n bf/s ff_loop\n add #1,r1\n bra wf_free\n nop\nff_found:\n mov r1,r7 ; its header's length, entry size 3, entries\n bsr wm_be32\n add #4,r7\n mov r0,r11\n mov r1,r7\n bsr wm_be32\n add #12,r7\n cmp/eq #3,r0\n bf wf_free\n mov r1,r7\n bsr wm_be32\n add #16,r7\n mov r0,r13 ; entries\n tst r13,r13\n bt wf_free\n mov.l F_max,r2\n cmp/hi r2,r13\n bt wf_free\n add r11,r1 ; the entries\n mov r1,r14\n mov r13,r0 ; within the reply (its size + 0x14)\n shll r0\n add r13,r0\n add r14,r0\n mov.l @r15,r2\n add r10,r2\n add #20,r2\n cmp/hi r2,r0\n bt wf_free\n mov r13,r3 ; the highest value\n shll r3\n add r13,r3\n mov r14,r1\n mov #1,r2\nfm_loop:\n mov.b @r1+,r0\n extu.b r0,r0\n cmp/hi r2,r0\n bf fm_next\n mov r0,r2\nfm_next:\n dt r3\n bf fm_loop\n mov.l F_scale46,r4 ; scale = (46 << 16) / highest\n bsr wm_div\n mov r2,r5\n mov r0,r11\n mov.l F_state,r1 ; the old block out of use, then freed\n mov #0,r0\n mov.l r0,@(8,r1)\n mov.l r0,@(12,r1)\n mov.l @(4,r1),r4\n mov.l r0,@(4,r1)\n tst r4,r4\n bt fa_new\n mov.l F_free,r0\n jsr @r0\n nop\nfa_new:\n mov r13,r4\n shll r4\n add r13,r4\n mov.l F_malloc,r0\n jsr @r0\n nop\n tst r0,r0\n bt wf_free\n mov r0,r9\n mov r13,r3\n shll r3\n add r13,r3\n mov r14,r1\n mov r9,r2\nfc_loop:\n mov.b @r1+,r0\n extu.b r0,r0\n mul.l r11,r0\n sts macl,r0\n shlr16 r0\n mov.b r0,@r2\n dt r3\n bf/s fc_loop\n add #1,r2\n mov.l F_state,r1\n mov.l r9,@(4,r1)\n mov.l r13,@(8,r1)\n mov r9,r4 ; the overview's bands from it\n bsr wm_ovbuild\n mov r13,r5\nwf_free:\n mov r10,r4\n mov.l F_free,r0\n jsr @r0\n nop\nwf_out:\n mov r12,r0\n add #8,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- wm_ovbuild: r4 the detail's bands (3 bytes a column, 0..46 px), r5 its\n; columns: the overview's OV_COLUMNS columns into OVB, each band's sum over\n; the column's span (OVSUM), then all at OV_ROWS for the highest sum;\n; WM_OVBANDS = OV_COLUMNS\nwm_ovbuild:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov.l B_sum,r10\n mov #0,r11 ; the column\n mov #1,r12 ; the highest sum\n mov #0,r13 ; the column's first entry\nob_col:\n mov r11,r4 ; its end: (column + 1) * entries / OV_COLUMNS\n add #1,r4\n mul.l r9,r4\n sts macl,r4\n mov.l B_cols,r5\n bsr wm_div\n nop\n mov r0,r14\n cmp/hi r13,r14\n bt ob_span\n mov r13,r14\n add #1,r14\nob_span:\n mov #0,r5\n mov #0,r6\n mov #0,r7\n mov r13,r1\n shll r1\n add r13,r1\n add r8,r1\n mov r14,r3\n sub r13,r3\nob_sum:\n mov.b @r1+,r0\n extu.b r0,r0\n add r0,r5\n mov.b @r1+,r0\n extu.b r0,r0\n add r0,r6\n mov.b @r1+,r0\n extu.b r0,r0\n dt r3\n bf/s ob_sum\n add r0,r7\n mov.w r5,@r10 ; (u16 sums)\n mov r6,r0\n mov.w r0,@(2,r10)\n mov r7,r0\n mov.w r0,@(4,r10)\n add #6,r10\n cmp/hi r12,r5\n bf ob_h1\n mov r5,r12\nob_h1:\n cmp/hi r12,r6\n bf ob_h2\n mov r6,r12\nob_h2:\n cmp/hi r12,r7\n bf ob_h3\n mov r7,r12\nob_h3:\n mov r14,r13\n add #1,r11\n mov.l B_cols,r0\n cmp/eq r0,r11\n bf ob_col\n mov.l B_scale,r4 ; scale = (OV_ROWS << 16) / highest sum\n bsr wm_div\n mov r12,r5\n mov r0,r2\n mov.l B_sum,r1\n mov.l B_ovb,r3\n mov.l B_cols3,r6\nob_scale:\n mov.w @r1+,r0\n extu.w r0,r0\n mul.l r2,r0\n sts macl,r0\n shlr16 r0\n mov.b r0,@r3\n dt r6\n bf/s ob_scale\n add #1,r3\n mov.l B_state,r1\n mov.l B_cols,r0\n mov.l r0,@(12,r1)\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n .align 4\nB_ovb: .long OVB\nB_sum: .long OVSUM\nB_cols: .long OV_COLUMNS\nB_cols3: .long OV_COLUMNS3\nB_scale: .long OV_SCALE\nB_state: .long WM_SETTING\n\n; ---- wm_ov: called in place of 0x08B03C5E by the overview's loop, once a\n; column: r11 the column (0..499), r13 its height (0..OV_ROWS), r14 its\n; colour index. Fills OVCOL (one colour per row from the bottom) and returns\n; r0 0 and r14 0, so the loop reads OVCOL (its table literal points there);\n; in 3BAND with the real bands the column's height becomes its highest (r13).\nwm_ov:\n sts.l pr,@-r15\n mov.l O_played,r0 ; the firmware's own call (+6 to the index when set)\n jsr @r0\n nop\n mov r0,r3\n mov.l O_state,r7\n mov.b @r7,r0\n cmp/eq #2,r0\n bf ov_plain\n mov.l @(12,r7),r2\n tst r2,r2\n bt ov_plain\n mov r11,r0 ; the column's entry\n cmp/hs r2,r0\n bt ov_plain\n mov r0,r1\n shll r0\n add r1,r0\n mov.l O_ovb,r1\n add r0,r1\n mov.b @r1+,r2\n extu.b r2,r2 ; lows\n mov.b @r1+,r4\n extu.b r4,r4 ; mids, at MID_SCALE/256\n mov #MID_SCALE,r0\n extu.b r0,r0\n mulu.w r0,r4\n sts macl,r4\n shlr8 r4\n mov.b @r1,r5\n extu.b r5,r5 ; highs\n mov r2,r13 ; the column's height: its highest band\n cmp/hi r13,r4\n bf ov_m1\n mov r4,r13\nov_m1:\n cmp/hi r13,r5\n bf ov_m2\n mov r5,r13\nov_m2:\n mov.l O_colours,r7\n tst r3,r3\n bt ov_dimmed\n add #8,r7 ; the part played: dimmed\nov_dimmed:\n mov.l O_col,r1\n mov #0,r6\nov_row:\n cmp/ge r13,r6\n bt ov_out\n cmp/ge r5,r6\n bt ov_nohigh\n mov.w @r7,r0 ; highs: white\n bra ov_put\n nop\nov_nohigh:\n cmp/ge r4,r6\n bt ov_nomid\n mov.w @(2,r7),r0 ; mids: amber\n bra ov_put\n nop\nov_nomid:\n mov.w @(4,r7),r0 ; lows: blue\nov_put:\n mov.w r0,@r1\n add #2,r1\n bra ov_row\n add #1,r6\nov_plain:\n mov r14,r0 ; the firmware's colour for the whole column\n tst r3,r3\n bt ov_idx\n add #6,r0\nov_idx:\n extu.b r0,r0\n shll r0\n mov.l O_table,r1\n add r0,r1\n mov.w @r1,r2\n mov.l O_col,r1\n mov #OV_ROWS,r6\nov_fill:\n mov.w r2,@r1\n dt r6\n bf/s ov_fill\n add #2,r1\nov_out:\n mov #0,r14\n lds.l @r15+,pr\n rts\n mov #0,r0\n\n .align 4\nO_played: .long OV_PLAYED\nO_state: .long WM_SETTING\nO_ovb: .long OVB\nO_colours: .long COLOURS\nO_col: .long OVCOL\nO_table: .long OVERVIEW\n\n; ---- wm_be32: r0 = the big-endian word at r7 (any alignment); r5, r7 used\nwm_be32:\n mov.b @r7+,r0\n extu.b r0,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n rts\n or r5,r0\n\n; ---- wm_div: r0 = r4 / r5, unsigned (r1, r2, r4 used)\nwm_div:\n mov #0,r0\n mov #0,r1\n mov #32,r2\nwd_loop:\n shll r4\n rotcl r1\n shll r0\n cmp/hs r5,r1\n bf wd_next\n sub r5,r1\n add #1,r0\nwd_next:\n dt r2\n bf wd_loop\n rts\n nop\n\n; ---- wm_get_tag: arnoldasr's wave_get_tag (dbcl_GetParWaveData with the NXS2\n; analysis-tag request 0x2c04) for the tag and file at WM_TAG/WM_EXT: r4\n; connection, r5 track, r6 &size, r7 &block -> r0 the wait's result (>= 0\n; ok), -1 when nothing was sent\nwm_get_tag:\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-12,r15\n mov r4,r14\n mov r5,r12\n mov r6,r9\n mov r7,r10\n mov #-1,r13\n tst r14,r14\n bt gt_out\n mov.l @r14,r11\n tst r11,r11\n bt gt_out\n mov.l G_check,r1\n jsr @r1\n mov r11,r4\n tst r0,r0\n bt gt_out\n mov.l G_alloc,r1\n jsr @r1\n mov #72,r4\n tst r0,r0\n bt gt_out\n mov.l r0,@(4,r15)\n mov.l r0,@(8,r15)\n mov #0,r1\n mov.l r1,@r15\n mov r14,r4\n mov r0,r5\n mov.l G_type,r6\n mov.l G_init,r1\n jsr @r1\n mov #1,r7\n mov.l @(4,r15),r2\n mov.l r12,@(24,r2)\n mov.l G_state,r3\n mov.l @(20,r3),r1\n mov.l r1,@(28,r2)\n mov.l @(24,r3),r1\n mov.l r1,@(32,r2)\n mov r11,r4\n mov.l @(8,r15),r5\n mov.l G_send,r1\n jsr @r1\n nop\n tst r0,r0\n bf gt_out\n mov.l r10,@-r15\n mov.l @(12,r15),r4\n mov.l G_reply,r5\n mov r15,r6\n add #4,r6\n mov r9,r7\n mov.l G_wait,r1\n jsr @r1\n nop\n mov r0,r13\n add #4,r15\ngt_out:\n mov r13,r0\n add #12,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n rts\n mov.l @r15+,r9\n\n .align 4\nF_prev: .long PREV_FETCH\nF_state: .long WM_SETTING\nF_pwv7: .long TAG_PWV7\nF_2ex: .long EXT_2EX\nF_max: .long MAX_COLUMNS\nF_scale46: .long 0x2E0000\nF_free: .long FREE\nF_malloc: .long MALLOC\nG_check: .long TAG_CHECK\nG_alloc: .long TAG_ALLOC\nG_init: .long TAG_INIT\nG_send: .long TAG_SEND\nG_wait: .long TAG_WAIT\nG_type: .long 0x2C04\nG_reply: .long 0x4F02\nG_state: .long WM_SETTING\n'''\n\nLAYOUT = (('COLOURS', 16), ('COLUMN', 2 * 48), ('OVCOL', 2 * 36), ('OVB', 3 * 500), ('OVSUM', 6 * 500), ('BANDS', 4 * 128), ('BLUEMAP', 2 * 128),\n ('OV_RGB', 24), ('OV_BLUE', 24), ('LABEL', 2 * len(LABEL) + 2)) + \\\n tuple(('VALUE%d' % i, 2 * len(v) + 2) for i, v in enumerate(VALUES)) + (('LIST', 4 * len(VALUES)),)\n\n\ndef routine(palette_at, prev_fetch=0, prev_bar=BAR_DRAW):\n h = hotcuekeys\n syms = {k: 0 for k, _ in LAYOUT}\n syms['COLOURS2'] = 0\n syms.update({\n 'PREV_FETCH': prev_fetch, 'PREV_BAR': prev_bar, 'OV_DRAW': OV_DRAW, 'MID_SCALE': MID_SCALE - 256 if MID_SCALE > 127 else MID_SCALE, 'WM_COUNT': WM_COUNT, 'WM_BANDS': WM_BANDS, 'WORK': WORK,\n 'WORK_OFF': WORK_OFF, 'MAX_COLUMNS': MAX_COLUMNS, 'FREE': FREE, 'MALLOC': MALLOC,\n 'TAG_CHECK': TAG_CHECK, 'TAG_ALLOC': TAG_ALLOC, 'TAG_INIT': TAG_INIT, 'TAG_SEND': TAG_SEND,\n 'TAG_WAIT': TAG_WAIT, 'TAG_PWV7': PWV7, 'EXT_2EX': EXT_2EX,\n 'OV_COLUMNS': OV_COLUMNS, 'OV_COLUMNS3': 3 * OV_COLUMNS, 'OV_SCALE': OV_ROWS << 16,\n 'OV_PLAYED': OV_PLAYED, 'OV_ROWS': OV_ROWS,\n 'WM_SETTING': WM_SETTING, 'PALETTE': palette_at, 'OVERVIEW': OVERVIEW, 'SAVE_ID': SAVE_ID,\n 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN, 'SHOW_SAVER': h.SHOW_SAVER,\n 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX, 'GET_BLOCK': h.GET_BLOCK,\n 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET,\n })\n code, _ = sh4asm.assemble(SOURCE, WM_CODE, syms)\n lay = {}\n at = WM_CODE + len(code)\n for name, size in LAYOUT:\n at = (at + 3) & ~3\n lay[name] = at\n at += size\n lay['ITEMS'] = (at + 3) & ~3\n syms.update({k: lay[k] for k, _ in LAYOUT})\n syms['COLOURS2'] = lay['COLOURS'] + 8\n code2, labels = sh4asm.assemble(SOURCE, WM_CODE, syms)\n assert len(code2) == len(code)\n labels.update(lay)\n return code2, labels\n\n\ndef bands(palette):\n \"\"\"Per palette entry the three bands' shares 0..255.\"\"\"\n out = bytearray()\n for i, c in enumerate(palette):\n r, g, b = (c >> 11) / 31, (c >> 5 & 63) / 63, (c & 31) / 31\n top = max(r, g, b)\n if i < 8: # no colour detail: by whiteness\n w = i / 7 # (more white, more mids and highs)\n out += bytes((255, round(230 * w), round(220 * w * w), 0))\n elif top == 0:\n out += bytes((255, 0, 0, 0))\n else:\n out += bytes((round(255 * r / top), round(255 * g / top), round(255 * b / top), 0))\n return bytes(out)\n\n\ndef bluemap(palette, blues):\n \"\"\"Per palette entry the player's own blue: 0..7 the whiteness itself,\n rekordbox colours by their green and blue (the higher bands).\"\"\"\n out = []\n for i, c in enumerate(palette):\n if i < 8:\n w = i\n else:\n g, b = (c >> 5 & 63) / 63, (c & 31) / 31\n w = min(7, round(7 * (0.45 * g + 0.55 * b)))\n out.append(blues[w])\n return struct.pack('<128H', *out)\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as SCREEN SAVER's with our 3 values.\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], labels['LIST'], len(VALUES), 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['wm_show'], g32, g36, labels['wm_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"advanced_waveforms' modes, on arnoldasr's code (its palette and 0..127\n clamp must be in); the item goes after the UTILITY items there are.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' r0 its record\nww_res:\n mov.l Q_zoom,r0\n cmp/eq r0,r4\n bt wq_zoom\n mov.l Q_grid,r0\n cmp/eq r0,r4\n bt wq_grid\n mov.l Q_prev,r0\n jmp @r0\n nop\nwq_zoom:\n mov.l Q_reczoom,r0\n rts\n nop\nwq_grid:\n mov.l Q_recgrid,r0\n rts\n nop\n\n .align 4\nM_size: .long BUF_SIZE\nM_alloc: .long BUF_SIZE + 32\nM_vars: .long WW_VARS\nM_malloc: .long MALLOC\nM_memset: .long PREV_MEMSET\nM_lit0: .long BUF_LIT0\nM_lit1: .long BUF_LIT1\nM_lit2: .long BUF_LIT2\nM_mask: .long FPSCR_MASK\nM_end: .long RENDER_END\nR_zoomw: .long RES_ZOOM_WHEEL\nR_gridw: .long RES_GRID_WHEEL\nR_zoom: .long RES_ZOOM\nR_grid: .long RES_GRID\nR_prev: .long PREV_DRAW\nR_widget: .long WW_WIDGET\nR_tl: .long BOX_TL\nR_br: .long BOX_BR\nQ_zoom: .long RES_ZOOM\nQ_grid: .long RES_GRID\nQ_prev: .long PREV_RES\nQ_reczoom: .long REC_ZOOM\nQ_recgrid: .long REC_GRID\n'''\n\n\ndef end_code():\n \"\"\"The last column, in the stock's eleven instructions: r6 = the columns\n ahead of the position (r6 - r5) + 267 while they are under WIDTH - 268,\n else WIDTH (both as the stock's 267 and 536).\"\"\"\n ahead = WIDTH - PLAY_COL\n assert 256 + 127 >= ahead > 256 and -128 <= (PLAY_COL - 1) - ahead < 0\n assert -128 <= WIDTH - 2 * (PLAY_COL - 1) <= 127\n ops = [\n 0x3658, # sub r5,r6 ahead\n 0xE501, # mov #1,r5\n 0x4518, # shll8 r5\n 0x7500 | (ahead - 256), # add #..,r5 WIDTH - 268\n 0x3652, # cmp/hs r5,r6\n 0x7500 | (((PLAY_COL - 1) - ahead) & 0xFF), # add #..,r5 267\n 0x365C, # add r5,r6 ahead + 267\n 0x8B02, # bf past the three below\n 0x6653, # mov r5,r6\n 0x4600, # shll r6\n 0x7600 | ((WIDTH - 2 * (PLAY_COL - 1)) & 0xFF), # add #..,r6 WIDTH\n ]\n return struct.pack('<11H', *ops)\n\n\ndef split(value):\n \"\"\"(hi, lo), both signed bytes, with hi * 256 + lo == value.\"\"\"\n hi = (value + 128) >> 8\n lo = value - hi * 256\n assert -128 <= hi <= 127 and -128 <= lo <= 127, value\n return hi, lo\n\n\ndef letters(stock, text):\n \"\"\"The wheel's lettering for `text` as rows of alpha 0..31 (its red).\"\"\"\n res, (y0, y1), x0 = LETTERS[text]\n rec = RES_TABLE + res * 44\n w, h = struct.unpack_from('> 11) if px[y * w + x] != KEY else 0 for x in range(x0, w)]\n for y in range(y0, y1)]\n cols = [x for x in range(len(rows[0])) if any(r[x] > 3 for r in rows)]\n return [r[cols[0]:cols[-1] + 1] for r in rows]\n\n\ndef box(stock, text):\n \"\"\"BOX_W x BOX_H RGB565: the grey frame, `text` in the wheel's letters.\"\"\"\n px = [[0] * BOX_W for _ in range(BOX_H)]\n for y in range(BOX_H):\n for x in range(BOX_SHOWN, BOX_W):\n px[y][x] = KEY\n last = BOX_SHOWN - 1\n for x in range(1, last):\n px[0][x] = px[BOX_H - 1][x] = FRAME\n for y in range(1, BOX_H - 1):\n px[y][0] = px[y][last] = FRAME\n glyphs = letters(stock, text)\n gh, gw = len(glyphs), len(glyphs[0])\n top, left = (BOX_H - gh + 1) // 2, (BOX_SHOWN - gw + 1) // 2\n for gy, row in enumerate(glyphs):\n for gx, a in enumerate(row):\n if a:\n r, g, b = (c * min(a, 31) // 31 for c in INK)\n px[top + gy][left + gx] = (r >> 3) << 11 | (g >> 2) << 5 | b >> 3\n return struct.pack('<%dH' % (BOX_W * BOX_H), *(p for row in px for p in row))\n\n\ndef record(stock, pixels_at):\n \"\"\"A resolved resource record (as RESOLVE leaves them) for a box at `pixels_at`.\"\"\"\n rec = bytearray(stock[RES_TABLE + RES_OFF_BOX * 44:RES_TABLE + RES_OFF_BOX * 44 + 44])\n struct.pack_into('> 8) & 15\n if mov >> 12 != 0xE or add >> 12 != 0x7 or (add >> 8) & 15 != n or \\\n half(out, mov_at) != mov or half(out, add_at) != add or \\\n 0x4018 | n << 8 not in [half(stock, a) for a in range(mov_at + 2, add_at, 2)]:\n fail('the constant at 0x%08x/0x%08x is not the expected one' % (mov_at, add_at))\n old = struct.unpack('b', bytes([mov & 0xFF]))[0] * 256 + struct.unpack('b', bytes([add & 0xFF]))[0]\n if old not in (STOCK_W, STOCK_W - 1) and old % (STOCK_W * 2):\n fail('the constant at 0x%08x is %d' % (mov_at, old))\n hi, lo = split(value)\n struct.pack_into('= hi - BASE:\n found = [a for a in range(lo, hi, 4) if word(out, a) == 61 * STOCK_W * 2]\n if len(found) > 1:\n fail('advanced_waveforms has more than one centre-row literal')\n for a in found:\n struct.pack_into('[,...]\n patch_cdj900nxs.py --list\n\nThe mods come from https://github.com/arnoldasr/cdj900nxs, read as data from a\ncheckout of it (CDJ900NXS_MODS_SRC, default mods/cdj900nxs/upstream, which is\nnot committed: that repository carries no licence). Nothing of its Python is\nimported; what its build_firmware.py does is redone here:\n\n extended_bpm the BPM with hundredths: the stock tenths moved into a new\n small digit above, the lower one showing hundredths.\n phasemeter the two-line phase meter (orange master, blue this deck, a\n marker each) in place of the four boxes, chosen in UTILITY >\n PHASE METER (SLIP FLASHING relabelled; mods/phase-meter-\n utility.cdjmod.json).\n advanced_waveforms (was rgb_waveform; based on arnoldasr's, updated and\n maintained by us) the big waveform in rekordbox's RGB colours\n (1.0.1: also when its colour detail is a little short,\n cdj900nxs/wavefix.py), and the overview's colour table as a red-to-white\n gradient (mods/waveform-colour.cdjmod.json); with our\n UTILITY > WAVEFORM: BLUE / RGB / 3BAND (cdj900nxs/wavemodes.py,\n left out with public=True).\n\n The three are arnoldasr's code mod (patches/mods.json): hooks\n in the application redirected to SH-4 code appended after it\n (0x08FC1000), plus an asset blob at 0x08FC2D00 generated from\n the stock image (the resource table extended by four phase-meter\n images, then the RGB waveform palette and LUT). Upstream tested\n it as one build, and as one without the RGB waveform\n (patches/bpm-phase-meter.json); one of those two is built and\n checked against its hash first, then the hooks of a feature not\n chosen get their original bytes back. Their shared startup hook\n (0x08C071FC) installs the BPM digits and the phase markers: the\n call of the one not chosen is taken out there.\n beatjump ours, not arnoldasr's (cdj900nxs/beatjump.py): TRACK SEARCH\n jumps 1/2/4/8 beats along the grid, chosen in UTILITY > BEAT JUMP,\n made at once in the DSP's decoded audio (cdj900nxs/dspjump.py, a\n patch of the audio DSP's code),\n which replaces SCREEN SAVER. hotcuekeys' engine makes the jumps,\n so it needs hotcuekeys and cuecolours. Needs no upstream checkout.\n cuecolours ours (cdj900nxs/cuecolours.py): memory cues drawn in their\n rekordbox colour on the overview and the detail waveform, the\n rekordbox hot cues shown as lettered boxes.\n Needs no upstream checkout.\n hotcuekeys ours (cdj900nxs/hotcuekeys.py): UTILITY > HOT CUE KEYS (a new,\n 18th item); with it ON, QUANTIZE briefly pressed switches a hot\n cue key mode shown in a HOT KEYS box, held it is QUANTIZE. In\n the mode the top three BEAT DIVIDE keys play hot cues A-C (held: a\n gate), set one (empty key held while paused) or delete one\n (DELETE held). The hot cues come from cuecolours.\n ledsaver ours (cdj900nxs/ledsaver.py): UTILITY > LED ANIMATION (one more\n item, after the others); with it ON, while the screen saver dims\n the display the button LEDs and the jog's rings play a loop.\n keynotation ours (cdj900nxs/keynotation.py): UTILITY > KEY NOTATION (one\n more item): every track's key in Camelot, Open Key or classic\n notation, whatever rekordbox exported.\n usbautoload ours (cdj900nxs/usbautoload.py): UTILITY > USB AUTO LOAD (one\n more item): a stick's MY SETTINGS are loaded when the player finds\n them, without waiting for MENU.\n quicksearch ours (cdj900nxs/quicksearch.py): BROWSE held opens SEARCH,\n BROWSE twice the list the playing track came from.\n (phasemeter, in unreleased builds, also gets ours from cdj900nxs/phasereverse.py:\n UTILITY > PHASE METER OFF / ON / REVERSE, and the beat countdown\n moved clear of the line meter; `phasereverse` is an old name for it.)\n wide_waveform ours (cdj900nxs/widewave.py): the big waveform runs on to the\n right edge (624 columns, the extra ones ahead of the position); the\n ZOOM/GRID wheel gives way to a small grey ZOOM / GRID box.\n credit ours (cdj900nxs/credit.py): the boot screen says \"Pionaarr Mods\n 1.0\" where it said \"Ver 1.31\".\n\nEvery patch is applied only where the image holds the expected original\nbytes, and arnoldasr's code is first built as one of the builds he tested\nand checked against its hash.\n\nOur lzss_decode.py runs 54 bytes past the end of the packed stream, so\nmain_unpacked.bin is the 1.31 application plus a tail the firmware never\nreads; the application is cut back to its own length first.\n\"\"\"\nimport hashlib\nimport json\nimport os\nimport struct\nimport sys\n\nHERE = os.path.dirname(os.path.abspath(__file__))\nsys.path.insert(0, os.path.join(HERE, 'cdj900nxs'))\nimport beatjump # noqa: E402\nimport credit # noqa: E402\nimport dspjump # noqa: E402\nimport cuecolours # noqa: E402\nimport hotcuekeys # noqa: E402\nimport keynotation # noqa: E402\nimport ledsaver # noqa: E402\nimport usbautoload # noqa: E402\nimport quicksearch # noqa: E402\nimport phasereverse # noqa: E402\nimport wavemodes # noqa: E402\nimport wavefix # noqa: E402\nimport cuenames # noqa: E402\nimport endwarn # noqa: E402\nimport played # noqa: E402\nimport widewave # noqa: E402\n\nBASE = 0x08000000\nAPP_SIZE = 16519056\nAPP_SHA256 = '1ac3e30cde292865413950fe159663b3958e85939ed0a5d0122f796809a5f8ef'\n\nMODS = {\n 'extended_bpm': 'BPM with hundredths: tenths as a small digit above (arnoldasr)',\n 'phasemeter': 'two-line phase meter, UTILITY > PHASE METER (SLIP FLASHING) chooses it (arnoldasr); '\n 'OFF / ON / REVERSE and the beat countdown moved clear of it (ours: cdj900nxs/phasereverse.py)',\n 'advanced_waveforms': 'big waveform in RGB colours, overview as a red-to-white gradient (arnoldasr); '\n 'UTILITY > WAVEFORM: BLUE / RGB / 3BAND (ours: cdj900nxs/wavemodes.py)',\n 'beatjump': 'TRACK SEARCH jumps 1/2/4/8 beats, UTILITY > BEAT JUMP (ours: cdj900nxs/beatjump.py)',\n 'cuecolours': 'memory cues in their rekordbox colours, hot cues shown (ours: cdj900nxs/cuecolours.py)',\n 'hotcuekeys': 'hot cues A-C on the BEAT DIVIDE keys: play, gate, set, delete; UTILITY > HOT CUE KEYS, '\n 'QUANTIZE short = hot key mode (ours: cdj900nxs/hotcuekeys.py)',\n 'ledsaver': 'the button and jog LEDs play an animation while the screen saver dims the display; '\n 'UTILITY > LED ANIMATION (ours: cdj900nxs/ledsaver.py)',\n 'keynotation': 'every key in one notation (Camelot / Open Key / classic), UTILITY > KEY NOTATION '\n '(ours: cdj900nxs/keynotation.py)',\n 'usbautoload': 'MY SETTINGS on a USB stick loaded at once, UTILITY > USB AUTO LOAD '\n '(ours: cdj900nxs/usbautoload.py)',\n 'quicksearch': 'BROWSE held: SEARCH; BROWSE twice: the playing track in its list '\n '(ours: cdj900nxs/quicksearch.py)',\n 'cuenames': 'the rekordbox comment of the next cue (\"Drop\") in the title bar, in its colour '\n '(ours: cdj900nxs/cuenames.py, needs cuecolours)',\n 'endwarn': 'the playing-address bar blinks red in the last 30 seconds (ours: cdj900nxs/endwarn.py)',\n 'played': 'BROWSE ticks the tracks loaded on this player since it was switched on (ours: cdj900nxs/played.py)',\n 'wide_waveform': 'the big waveform to the right edge, the ZOOM/GRID wheel replaced by a small ZOOM / GRID box '\n '(ours: cdj900nxs/widewave.py)',\n 'credit': 'boot screen says \"Pionaarr Mods 1.0\" instead of \"Ver 1.31\" (ours: cdj900nxs/credit.py)',\n}\n# every mod's version (Semantic Versioning); ours from their modules, arnoldasr's\n# split-up mods as packaged here\nVERSIONS = {\n 'extended_bpm': '1.0.0', 'phasemeter': '1.0.1', 'advanced_waveforms': wavemodes.VERSION, # with its WAVEFORM modes (wavefix: wavefix.VERSION)\n 'beatjump': beatjump.VERSION, 'cuecolours': cuecolours.VERSION, 'hotcuekeys': hotcuekeys.VERSION,\n 'ledsaver': ledsaver.VERSION, 'keynotation': keynotation.VERSION, 'usbautoload': usbautoload.VERSION,\n 'quicksearch': quicksearch.VERSION,\n 'cuenames': cuenames.VERSION, 'endwarn': endwarn.VERSION, 'played': played.VERSION,\n 'wide_waveform': widewave.VERSION, 'credit': credit.VERSION,\n}\nMANIFESTS = {\n 'phasemeter': 'mods/phase-meter-utility.cdjmod.json',\n 'advanced_waveforms': 'mods/waveform-colour.cdjmod.json',\n}\n# arnoldasr's code mod split up: each feature's hooks in the application\nUPSTREAM = ('extended_bpm', 'phasemeter', 'advanced_waveforms')\nHOOKS = {\n 'extended_bpm': (0x08BD3E50, 0x08BD3ED8, 0x08BD3A48, 0x08BCBA60, 0x08BCBCFC),\n 'phasemeter': (0x08B2E2EE, 0x08C6A6CC, 0x08C6CCD0),\n 'advanced_waveforms': (0x089A061C, 0x08BC6830, 0x08BC6A7C, 0x08BCFD72, 0x08BCFF50),\n}\nSHARED_HOOKS = (0x08C071FC, 0x08B27BC8) # startup binding, arena reset: BPM and phase\nBPM_INSTALL_CALL = 0x08FC2444 # startup_bind's jsr of the BPM digits' installer\nPHASE_MARKERS_CALL = 0x08FC2450 # ... and of the phase markers' (startup_markers)\n\n\ndef source_dir():\n return os.environ.get('CDJ900NXS_MODS_SRC') or os.path.join(HERE, 'cdj900nxs', 'upstream')\n\n\ndef read_json(rel):\n path = os.path.join(source_dir(), rel)\n if not os.path.isfile(path):\n raise SystemExit('patch_cdj900nxs: %s missing -- check out '\n 'https://github.com/arnoldasr/cdj900nxs into %s'\n % (path, source_dir()))\n with open(path, encoding='utf-8') as f:\n return json.load(f)\n\n\ndef sha(data):\n return hashlib.sha256(data).hexdigest()\n\n\n# The asset blob, as tools/phase_assets.py builds it (same constants).\nTABLE, DESCRIPTOR, ORIGINAL_COUNT, TEMPLATE = 0x08100000, 0x2C, 0x2A1, 0x1C8\nDATA_BASE = 0x08FC2D00\nWIDTH, HEIGHT, MARKER_WIDTH, BAR = 200, 24, 8, 5\nKEY = 0xF81F\nORANGE_LINE, ORANGE_MARK = (120, 65, 20), (255, 140, 0)\nBLUE_LINE, BLUE_MARK = (40, 90, 120), (60, 170, 255)\nWAVE_COLOURS = [\n 448, 457, 47, 458, 449, 111, 495, 466, 175, 467, 487, 239, 303, 479, 367, 459, 456, 431,\n 468, 465, 475, 476, 474, 39, 477, 31, 471, 46, 469, 45, 23, 460, 109, 484, 110, 485, 103,\n 478, 483, 493, 494, 486, 95, 15, 450, 167, 492, 173, 174, 231, 115, 470, 238, 44, 237, 159,\n 295, 87, 108, 302, 51, 301, 491, 482, 359, 223, 366, 365, 236, 300, 179, 423, 461, 287, 430,\n 429, 172, 364, 473, 428, 351, 114, 151, 463, 299, 363, 415, 215, 427, 178, 490, 50, 279, 235,\n 79, 462, 451, 343, 511, 298, 362, 400, 52, 116, 426, 234, 407, 7, 143, 180, 207, 113, 233,\n 243, 481, 57, 271, 452, 417, 177]\nWHITENESS_COLOURS = [(150, 20, 20), (220, 40, 30), (255, 110, 20), (255, 190, 30), (170, 230, 60),\n (40, 210, 170), (60, 160, 255), (225, 240, 255)]\n\n\ndef rgb565(rgb):\n r, g, b = rgb\n return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)\n\n\ndef line_image(colour):\n px = [KEY] * (WIDTH * HEIGHT)\n for x in range(2, WIDTH - 2):\n px[12 * WIDTH + x] = rgb565(colour)\n for i in range(5):\n x = 2 + i * (WIDTH - 5) // 4\n for y in range(7, 18):\n px[y * WIDTH + x] = rgb565(colour)\n return px\n\n\ndef marker_image(colour):\n return [rgb565(colour) if 3 <= y <= 21 and x < BAR else KEY\n for y in range(HEIGHT) for x in range(MARKER_WIDTH)]\n\n\ndef rgb9(code):\n return tuple(((code >> shift) & 7) * 255 // 7 for shift in (6, 3, 0))\n\n\ndef wave_palette():\n colours = WHITENESS_COLOURS + [rgb9(c) for c in WAVE_COLOURS]\n return struct.pack('<128H', *[rgb565(c) for c in colours])\n\n\ndef wave_lut():\n def nearest(code):\n r, g, b = (code >> 6) & 7, (code >> 3) & 7, code & 7\n return min(range(len(WAVE_COLOURS)), key=lambda i: (\n (((WAVE_COLOURS[i] >> 6) & 7) - r) ** 2 + (((WAVE_COLOURS[i] >> 3) & 7) - g) ** 2\n + ((WAVE_COLOURS[i] & 7) - b) ** 2, i))\n return bytes(8 + nearest(code) for code in range(512))\n\n\ndef assets(app):\n images = [(WIDTH, line_image(ORANGE_LINE)), (WIDTH, line_image(BLUE_LINE)),\n (MARKER_WIDTH, marker_image(ORANGE_MARK)), (MARKER_WIDTH, marker_image(BLUE_MARK))]\n original = app[TABLE - BASE:TABLE - BASE + ORIGINAL_COUNT * DESCRIPTOR]\n template = original[TEMPLATE * DESCRIPTOR:(TEMPLATE + 1) * DESCRIPTOR]\n count = ORIGINAL_COUNT + len(images)\n table_bytes = count * DESCRIPTOR\n pixels_base = DATA_BASE + ((table_bytes + 31) & ~31)\n table, pixels = bytearray(original), bytearray()\n for i, (width, data) in enumerate(images):\n entry = bytearray(template)\n struct.pack_into(' mov #0,r0 (not installed)\n if 'phasemeter' not in names:\n call(PHASE_MARKERS_CALL, 0x410B, 0x0009) # jsr @r1 -> nop\n return bytes(out)\n\n\ndef apply_manifest(app, stock, name):\n doc = read_json(MANIFESTS[name])\n if doc['target']['application_sha256'] != APP_SHA256:\n raise SystemExit('patch_cdj900nxs: %s targets another application' % name)\n out = bytearray(app)\n for p in doc['patches']:\n off = int(p['offset'], 16) if isinstance(p['offset'], str) else p['offset']\n before, after = bytes.fromhex(p['expect']), bytes.fromhex(p['replace'])\n if len(before) != len(after) or off + len(before) > APP_SIZE:\n raise SystemExit('patch_cdj900nxs: %s: bad patch at 0x%x' % (name, off))\n if stock[off:off + len(before)] != before or app[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: %s: bytes at 0x%x are not the expected original'\n % (name, off))\n out[off:off + len(before)] = after\n return bytes(out)\n\n\n# old names still taken\nALIASES = {'rgb_waveform': 'advanced_waveforms', 'waveform3band': 'advanced_waveforms',\n 'phasereverse': 'phasemeter'}\n\n\ndef check_names(names):\n \"\"\"The names with old ones taken, every mod known and what each needs.\"\"\"\n names = list(dict.fromkeys(ALIASES.get(n, n) for n in names))\n unknown = [n for n in names if n not in MODS]\n if unknown:\n raise SystemExit('patch_cdj900nxs: unknown mod(s) %s; known: %s'\n % (', '.join(unknown), ', '.join(MODS)))\n if 'cuenames' in names and 'cuecolours' not in names:\n raise SystemExit('patch_cdj900nxs: cuenames needs cuecolours (its cue reader brings the comments)')\n if 'beatjump' in names and not {'cuecolours', 'hotcuekeys'} <= set(names):\n raise SystemExit('patch_cdj900nxs: beatjump needs cuecolours and hotcuekeys '\n '(the jumps are made by hotcuekeys)')\n return names\n\n\ndef patch_upstream(stock, names):\n \"\"\"Stage 1: arnoldasr's mods (his code and assets, his manifests, and\n advanced_waveforms' fix that sits on his code). The website's builder\n has this stage as data (tools/make_builder_data.py).\"\"\"\n upstream = [n for n in UPSTREAM if n in names]\n app = apply_code(stock, upstream) if upstream else stock\n for name in ('phasemeter', 'advanced_waveforms'):\n if name in names:\n app = apply_manifest(app, stock, name)\n if 'advanced_waveforms' in names:\n app = wavefix.patch(app, stock) # 1.0.1: a slightly short PWV5 is taken too\n return app\n\n\ndef patch_ours(app, stock, names):\n \"\"\"Stage 2: our mods, on the application stage 1 made (the website's\n builder runs this function itself, in the browser).\"\"\"\n if 'beatjump' in names:\n app = beatjump.patch(app, stock)\n app = dspjump.patch(app, stock) # the jumps in the DSP's decoded audio\n if 'cuecolours' in names:\n app = cuecolours.patch(app, stock)\n if 'hotcuekeys' in names:\n app = hotcuekeys.patch(app, stock)\n if 'ledsaver' in names:\n app = ledsaver.patch(app, stock)\n if 'keynotation' in names:\n app = keynotation.patch(app, stock)\n if 'cuenames' in names:\n app = cuenames.patch(app, stock) # after keynotation: chains its text draw\n if 'played' in names:\n app = played.patch(app, stock) # after keynotation and cuenames: chains their text draw\n if 'endwarn' in names:\n app = endwarn.patch(app, stock)\n if 'usbautoload' in names:\n app = usbautoload.patch(app, stock)\n if 'quicksearch' in names:\n app = quicksearch.patch(app, stock)\n if 'phasemeter' in names:\n app = phasereverse.patch(app, stock) # PHASE METER: OFF / ON / REVERSE\n app = phasereverse.shift_bars(app, stock) # the beat countdown clear of the line meter\n if 'advanced_waveforms' in names:\n app = wavemodes.patch(app, stock) # UTILITY > WAVEFORM: BLUE / RGB / 3BAND\n if 'wide_waveform' in names:\n app = widewave.patch(app, stock) # after advanced_waveforms (its centre-row literal) and hotcuekeys (the draw method)\n if 'credit' in names:\n app = credit.patch(app, stock)\n return app\n\n\ndef patch(data, names, public=False):\n \"\"\"The patched application for the mods named, in a fixed order: stage 1\n (arnoldasr's) and stage 2 (ours). Everything is released now; `public`\n is still taken and changes nothing.\"\"\"\n names = check_names(names)\n stock = data[:APP_SIZE]\n if sha(stock) != APP_SHA256:\n raise SystemExit('patch_cdj900nxs: not the CDJ-900NXS 1.31 application')\n return patch_ours(patch_upstream(stock, names), stock, names)\n\n\ndef main():\n args = sys.argv[1:]\n if args == ['--list']:\n for name, what in MODS.items():\n print('%-13s %-6s %s' % (name, VERSIONS[name], what))\n return\n if len(args) != 3:\n raise SystemExit(__doc__)\n names = [n for n in args[2].replace(' ', ',').split(',') if n]\n with open(args[0], 'rb') as f:\n out = patch(f.read(), names)\n with open(args[1], 'wb') as f:\n f.write(out)\n print('patch_cdj900nxs: %s (%s), %d bytes, sha256 %s'\n % (args[1], ','.join(names), len(out), sha(out)))\n\n\nif __name__ == '__main__':\n main()\n","mods/cdj900nxs/beatjump.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"BEAT JUMP for the CDJ-900NXS 1.31: TRACK SEARCH jumps N beats along the\ntrack's beat grid, N set in UTILITY > BEAT JUMP (OFF/1/2/4/8, default 4).\n\nOur own mod, not arnoldasr's; mods/patch_cdj900nxs.py applies it as `beatjump`.\nEverything here was found by tracing the firmware in the emulator:\n\n * The UI's poller calls the panel decoder 0x08B63278 (through its literal\n at 0x08B622A8), which turns report byte 0x12 bits 0x04 / 0x08 of the\n frame at 0x0B2E8280 into the key bits TRACK - (0x0B2E82C6 bit 0x01) and\n TRACK + (0x0B2E82C5 bit 0x80); later readers (0x08B630D0) turn those\n into the track search. The literal now points at bj_poll, which runs the\n decoder and then, with BEAT JUMP on and a beat grid loaded, takes both\n key bits out again: the track search never starts, so neither the UI nor\n the player changes track, and a press is a jump instead. (Clearing the\n report byte before the decoder instead lost a race with the panel task\n refilling the frame.)\n * The jump is made by hotcuekeys' engine (hk_engine's he_bj, which is\n why beatjump needs hotcuekeys and cuecolours): bj_poll writes the\n target in 1/150 s with bit 31 set to hotcuekeys.BJ_REQ, the tick of\n the press to BJ_TICK and the jump's length in ms to BJ_DELTA, unless a\n jump is still waiting. The engine has the DSP move its playing head by\n that many 1/75 s slots inside the audio it has already decoded\n (dspjump.py, a patch of the audio DSP): at once, gapless, the CUE point\n stays, playing, paused or at cue standby (standing, the UI is left\n paused, so PLAY plays on from there). Only where the DSP has not that\n much audio decoded does it refuse: playing, the engine then loads the\n target into the beat jumps' own DSP cue slot (hotcuekeys.BJ_SLOT) the\n old way, moved on by the time such a load takes so the deck comes back\n in phase; standing, the press is dropped (the old way there, a memory\n CALL, left the UI waiting for good after the third or fourth).\n [Since: no fallback at all; the slot load left the deck standing. A\n press the DSP cannot jump now (it keeps dspjump.MARGIN decoded ahead, so\n the time needed grows with the setting) or made while a jump still\n waits is ignored: nothing is put off, the next press counts again.]\n * The setting is kept in the player's settings journal (hotcuekeys'\n hk_save2/hk_load2 through HK_API, id SAVE_ID): the first poll takes the\n last saved value (else DEFAULT, 4 beats) into the setting's word, a\n change is written back. The firmware itself kept SCREEN SAVER's ON/OFF\n only, so BEAT JUMP came back as 1 after a restart.\n * The grid is read as arnoldasr's phase meter reads it, from the player\n context 0x099445DC: track *(ctx+0x20), X = *(track+0xC4), grid\n *(X+0x104), timestamp adjustment *(X+0x10C); 0x08A27D4C(grid, adj,\n pos_ms) gives 1 + the index of the beat at or before pos_ms, 0x08A0EFB6\n (record, adj) a beat's time in ms. The position in ms is ctx+0x2CC.\n * A jump keeps the position's offset within its beat: target = pos +\n t[i +/- N] - t[i]. Before the first beat (a track just loaded stands at\n its CUE, often a hair before beat 1) i is beat 0. A press whose target\n would leave the grid does nothing. With the setting OFF, or no grid, TRACK SEARCH is untouched.\n\nUTILITY > SCREEN SAVER (item 14, a word at 0x0A4DD270 + 0x1760, values\nOFF/ON, factory default ON = 1) becomes BEAT JUMP: its label in all 19\nlanguages, five values OFF/1/2/4/8 (the existing \"OFF\" and MIDI CHANNEL's digit\nstrings), so the old ON is now \"1\". The screen saver itself no longer reads\nthe setting: its getter literal (0x08BB7E60) points at a firmware tail that\nreturns 1, so it stays on as it was by default.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.0.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nBJ_BASE = 0x08FCF400 # after arnoldasr's code mod (ends 0x08FCF260)\n\nPLAYER_CTX = 0x099445DC\nDECODER_LIT = 0x08B622A8 # the UI poller's literal of the panel decoder\nDECODER = 0x08B63278\nKEYS_TRACK = 0x0B2E82C5 # decoded keys: TRACK + is bit 0x80 here, TRACK - 0x01 of the next byte\nSEND = 0x08B3B666 # the UI's player-message builder\nGET_SETTING = 0x08AFD9E0 # SCREEN SAVER getter: *(0x0A4DD270 + 0x1760)\nGRID_LOOKUP = 0x08A27D4C\nGRID_TIME = 0x08A0EFB6\nFPSCR_MASK = 0xFFE7FFFF # what the firmware does to fpscr after every call\n\nSAVER_ITEM = 0x08C6CBB4 # UTILITY item 14 (copied to RAM at start-up)\nSAVER_CONSUMER_LIT = 0x08BB7E60 # the screen saver's literal of the getter\nRETURN_ONE = 0x08BDA1B0 # 'rts; mov #1,r0' at the end of a firmware function\nVALUE_STRINGS = [0xA80D013C, 0xA80D0144, 0xA80D0148, 0xA80D0150, 0xA80D0160] # OFF 1 2 4 8\nLABEL_OLD = 'SCREEN SAVER\\0'.encode('utf-16-le')\nLABEL_NEW = 'BEAT JUMP'.encode('utf-16-le').ljust(len(LABEL_OLD), b'\\0')\nLABEL_COPIES = 19\n\nVALUE_LIST = BJ_BASE + 0x400 # the five value-string pointers, after the code\nPREV_BITS = BJ_BASE + 0x3FC # last frame's TRACK bits, for press edges\nLAST_SET = BJ_BASE + 0x3F8 # word: the setting last seen (-1 at start: taken from the journal then)\nSETTING = 0x0A4DD270 + 0x1760 # the setting's word (the SCREEN SAVER's)\nSAVE_ID = 0x83E2 # ours in the settings journal (hotcuekeys' is 0x83E1)\nDEFAULT = 3 # 4 beats\nBJ_DELTA = 0x08FCF818 # word: the last jump's length in ms (signed), for hotcuekeys' DSP jump\nBJ_POSV = hotcuekeys.BJ_POSV # word: the position (ms) to reckon from, if not 0\nBJ_PEND = hotcuekeys.BJ_PEND # word: presses counted while hotcuekeys' CALL had no grid\n\n\nclass Asm:\n \"\"\"Just enough SH-4 to write bj_poll: two passes over a list of\n instructions, labels, and a literal pool of .long/.word constants.\"\"\"\n\n def __init__(self, base):\n self.base, self.items, self.labels = base, [], {}\n\n def label(self, name):\n self.items.append(('label', name))\n\n def op(self, fn):\n self.items.append(('op', fn))\n\n def long(self, name, value):\n self.items.append(('align4', None))\n self.label(name)\n self.items.append(('long', value))\n\n def word(self, name, value):\n self.label(name)\n self.items.append(('word', value))\n\n def assemble(self):\n for final in (False, True):\n pc, out = self.base, bytearray()\n for kind, arg in self.items:\n if kind == 'label':\n self.labels[arg] = pc\n continue\n if kind == 'align4':\n while pc % 4:\n out += struct.pack('> 36 = q / 20\n a.long('L_req', hotcuekeys.BJ_REQ)\n a.long('L_tick', hotcuekeys.TICK)\n a.long('L_bit31', 0x80000000)\n a.long('L_delta', BJ_DELTA)\n a.long('L_posv', BJ_POSV)\n a.long('L_pend', BJ_PEND)\n a.long('L_third', 0xAAAAAAAB) # q * this >> 33 = q / 3\n a.long('L_last', LAST_SET)\n a.long('L_setting', SETTING)\n a.long('L_bjid', SAVE_ID)\n a.long('L_bjid16', SAVE_ID << 16)\n a.long('L_api', hotcuekeys.HK_API)\n code = a.assemble()\n return code, None\n\n\ndef patch(app, stock):\n \"\"\"The application with BEAT JUMP added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n code, _ = routine()\n assert len(code) <= LAST_SET - BJ_BASE\n out = bytearray(app)\n end = VALUE_LIST + 4 * len(VALUE_STRINGS) - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[BJ_BASE - BASE:end]):\n raise SystemExit('patch_cdj900nxs: beatjump: its area 0x%08x.. is not free' % BJ_BASE)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + len(before)] != before or out[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: beatjump: bytes at 0x%08x are not the expected original'\n % addr)\n out[off:off + len(after)] = after\n\n out[BJ_BASE - BASE:BJ_BASE - BASE + len(code)] = code\n out[LAST_SET - BASE:LAST_SET - BASE + 4] = b'\\xff\\xff\\xff\\xff'\n out[VALUE_LIST - BASE:end] = b''.join(struct.pack('= 0:\n put(BASE + at, LABEL_OLD, LABEL_NEW)\n found += 1\n at = stock.find(LABEL_OLD, at + 2)\n if found != LABEL_COPIES:\n raise SystemExit('patch_cdj900nxs: beatjump: %d SCREEN SAVER labels, expected %d'\n % (found, LABEL_COPIES))\n return bytes(out)\n","mods/cdj900nxs/c6asm.py":"#!/usr/bin/env python3\n\"\"\"casm.py: a minimal TI C674x assembler for our DSP patches (32-bit, serial\ninstructions only: every p bit 0, no compact forms). Every encoding below is\nchecked against words of the CDJ-900NXS DSP image (selftest()).\n\nRegisters: 'a0'..'a31', 'b0'..'b31'. Predicates: '[b0]', '[!a1]' prefix\n(A1, A2, B0, B1, B2 only). Delay slots are the caller's business: loads need\n4 slots before their result is used, branches 5.\n\n asm = Asm(base)\n asm.label('x'); asm.op('mvk', 0x1234, 'a3'); asm.op('b', 'x') ...\n words = asm.assemble()\n\"\"\"\nimport struct\n\nCREG = {'b0': 1, 'b1': 2, 'b2': 3, 'a1': 4, 'a2': 5, 'a0': 6}\n\n\ndef reg(r):\n side = 1 if r[0] == 'b' else 0\n return side, int(r[1:])\n\n\ndef pred(p):\n if not p:\n return 0\n neg = p.startswith('!')\n return CREG[p.lstrip('!')] << 29 | (1 << 28 if neg else 0)\n\n\n# .L unit 3-operand ops (op field bits 11-5): (si,xsi) form and (s5,xsi) form\nL_OPS = {\n 'add': (0x03, 0x02), 'sub': (0x07, 0x06), 'cmpeq': (0x53, 0x52),\n 'cmpgt': (0x47, 0x46), 'cmplt': (0x57, 0x56), 'cmpgtu': (0x4F, 0x4E),\n 'cmpltu': (0x5F, 0x5E), 'or': (0x7F, 0x7E), 'and': (0x7B, 0x7A),\n}\n# .D load/store op field (bits 6-4)\nD_OPS = {'ldhu': 0, 'ldbu': 1, 'ldb': 2, 'stb': 3, 'ldh': 4, 'sth': 5, 'ldw': 6, 'stw': 7}\nD_SCALE = {'ldhu': 2, 'ldbu': 1, 'ldb': 1, 'stb': 1, 'ldh': 2, 'sth': 2, 'ldw': 4, 'stw': 4}\n\n\ndef enc_l(op, src1, src2, dst, p=''):\n \"\"\"op src1,src2,dst on .L (unit side = dst side; src2 may cross).\"\"\"\n ds, dn = reg(dst)\n s2s, s2n = reg(src2)\n x = 1 if s2s != ds else 0\n if isinstance(src1, int):\n assert -16 <= src1 < 16\n code = L_OPS[op][1]\n s1 = src1 & 31\n else:\n s1s, s1 = reg(src1)\n assert s1s == ds, 'src1 must be on the unit side'\n code = L_OPS[op][0]\n return pred(p) | dn << 23 | s2n << 18 | s1 << 13 | x << 12 | code << 5 | 0b110 << 2 | ds << 1\n\n\ndef enc_mvk(hi, val, dst, p=''):\n ds, dn = reg(dst)\n cst = (val >> 16 if hi else val) & 0xFFFF\n return pred(p) | dn << 23 | cst << 7 | (0b11010 if hi else 0b01010) << 2 | ds << 1\n\n\ndef enc_d(op, data, base, off, p=''):\n \"\"\"ld/st *+base(off) (byte offset, scaled by the access size).\"\"\"\n ss, sn = reg(data)\n bs, bn = reg(base)\n sc = D_SCALE[op]\n assert off % sc == 0 and 0 <= off // sc < 32, (op, off)\n return pred(p) | sn << 23 | bn << 18 | (off // sc) << 13 | 0b0001 << 9 | bs << 7 | D_OPS[op] << 4 | 0b01 << 2 | ss << 1\n\n\ndef enc_b_imm(at, target, side=0, p=''):\n disp = (target - (at & ~31)) // 4\n assert -(1 << 20) <= disp < (1 << 20)\n return pred(p) | (disp & 0x1FFFFF) << 7 | 0b00100 << 2 | side << 1\n\n\ndef enc_b_reg(r, p=''):\n rs, rn = reg(r)\n return pred(p) | rn << 18 | (1 if rs == 0 else 0) << 12 | 0x362 # .S2 (X for an A register)\n\n\ndef enc_nop(n):\n assert 1 <= n <= 9\n return (n - 1) << 13\n\n\nDINT, RINT = 0x10004000, 0x10006000\n\n\ndef enc_shl(src, cst, dst, p=''):\n \"\"\"shl .S src,ucst5,dst\"\"\"\n ds, dn = reg(dst)\n ss, sn = reg(src)\n assert ss == ds and 0 <= cst < 32\n return pred(p) | dn << 23 | sn << 18 | cst << 13 | 0x32 << 6 | 0b1000 << 2 | ds << 1\n\n\ndef enc_dp(op, data, off, p=''):\n \"\"\"ld/st *+b14(ucst15) (byte offset, scaled), .D2\"\"\"\n ds, dn = reg(data)\n sc = D_SCALE[op]\n assert off % sc == 0 and 0 <= off // sc < 32768\n return pred(p) | dn << 23 | (off // sc) << 8 | 0 << 7 | D_OPS[op] << 4 | 0b11 << 2 | ds << 1\n\n\ndef enc_extu(src, csta, cstb, dst, p=''):\n \"\"\"extu .S src,csta,cstb,dst (field form, bits 7-6 = 00).\"\"\"\n ds, dn = reg(dst)\n ss, sn = reg(src)\n assert ss == ds\n return pred(p) | dn << 23 | sn << 18 | csta << 13 | cstb << 8 | 0b00 << 6 | 0b0010 << 2 | ds << 1\n\n\ndef enc_addaw(src2, src1, dst, p=''):\n \"\"\"addaw .D dst = src2 + src1*4 (src1 register or ucst5).\"\"\"\n ds, dn = reg(dst)\n s2s, s2n = reg(src2)\n assert s2s == ds\n if isinstance(src1, int):\n return pred(p) | dn << 23 | s2n << 18 | (src1 & 31) << 13 | 0x3A << 7 | 0b10000 << 2 | ds << 1\n s1s, s1n = reg(src1)\n return pred(p) | dn << 23 | s2n << 18 | s1n << 13 | 0x38 << 7 | 0b10000 << 2 | ds << 1\n\n\nclass Asm:\n def __init__(self, base):\n self.base, self.items, self.labels = base, [], {}\n\n def label(self, name):\n self.items.append(('label', name))\n\n def org(self, addr):\n \"\"\"continue at addr (a new block; the caller branches there)\"\"\"\n self.items.append(('org', addr))\n\n def op(self, mn, *a, p=''):\n self.items.append(('op', (mn, a, p)))\n\n def word(self, w):\n self.items.append(('word', w))\n\n def _enc(self, at, mn, a, p):\n if mn == 'mvk':\n return enc_mvk(0, a[0], a[1], p)\n if mn == 'mvkh':\n return enc_mvk(1, a[0], a[1], p)\n if mn == 'mvkl32': # helper: emitted as two words by assemble()\n raise AssertionError\n if mn in L_OPS:\n return enc_l(mn, a[0], a[1], a[2], p)\n if mn in D_OPS:\n return enc_d(mn, a[0], a[1], a[2], p)\n if mn == 'b':\n t = a[0]\n if isinstance(t, str) and t[0] in 'ab' and t[1:].isdigit():\n return enc_b_reg(t, p)\n t = self.labels[t] if isinstance(t, str) else t\n return enc_b_imm(at, t, a[1] if len(a) > 1 else 0, p)\n if mn == 'nop':\n return enc_nop(a[0] if a else 1)\n if mn == 'dint':\n return DINT\n if mn == 'rint':\n return RINT\n if mn == 'extu':\n return enc_extu(a[0], a[1], a[2], a[3], p)\n if mn == 'addaw':\n return enc_addaw(a[0], a[1], a[2], p)\n if mn == 'shl':\n return enc_shl(a[0], a[1], a[2], p)\n if mn == 'lddp': # ldbu/ldw/... *+b14(off)\n return enc_dp(a[0], a[1], a[2], p)\n if mn == 'mv':\n return enc_l('add', 0, a[0], a[1], p)\n raise KeyError(mn)\n\n def assemble(self):\n \"\"\"-> [(addr, word)]\"\"\"\n out = []\n for final in (False, True):\n pc, out = self.base, []\n for kind, arg in self.items:\n if kind == 'label':\n self.labels[arg] = pc\n continue\n if kind == 'org':\n pc = arg\n continue\n if kind == 'word':\n out.append((pc, arg))\n pc += 4\n continue\n mn, a, p = arg\n if mn == 'const': # mvk + mvkh of a 32-bit value\n v = self.labels.get(a[0], a[0]) if isinstance(a[0], str) else a[0]\n out.append((pc, enc_mvk(0, v if final or not isinstance(a[0], str) else 0, a[1], p)))\n out.append((pc + 4, enc_mvk(1, v if final or not isinstance(a[0], str) else 0, a[1], p)))\n pc += 8\n continue\n try:\n w = self._enc(pc, mn, a, p)\n except KeyError:\n if final:\n raise\n w = 0\n out.append((pc, w))\n pc += 4\n return out\n\n\ndef selftest():\n ref = [\n (enc_d('ldw', 'a4', 'a3', 0), 0x020c0264),\n (enc_d('stw', 'a3', 'a5', 0), 0x01940274),\n (enc_d('sth', 'a4', 'a3', 2), 0x020c2254),\n (enc_d('ldh', 'a4', 'a3', 6), 0x020c6244),\n (enc_d('ldhu', 'a4', 'a3', 0), 0x020c0204),\n (enc_l('cmpeq', 1, 'a4', 'a0'), 0x00102a58),\n (enc_l('or', 0, 'a4', 'a0'), 0x00100fd8),\n (enc_l('add', 'b10', 'b4', 'b6'), 0x0311407b & ~1),\n (enc_l('cmplt', 0, 'b4', 'b0'), 0x00100adb & ~1),\n (enc_l('cmpgt', 'a3', 'a10', 'a0'), 0x002868f8),\n (enc_l('sub', 'a3', 'a4', 'a10'), 0x051060f8),\n (enc_l('add', 'a4', 'a3', 'a3'), 0x018c8078),\n (enc_mvk(0, -7576, 'b4'), 0x0271342a),\n (enc_mvk(1, 0x1182e268, 'b4'), 0x0208c16a),\n (enc_b_reg('b4'), 0x00100362),\n (enc_addaw('a4', 'a6', 'a3'), 0x0190dc41 & ~1),\n (enc_nop(5), 0x00008000),\n (enc_shl('a14', 3, 'a8'), 0x04386ca1 & ~1),\n (enc_dp('ldbu', 'b6', 969), 0x0303c91e),\n ]\n bad = [(hex(a), hex(b)) for a, b in ref if a != b]\n assert not bad, bad\n return True\n\n\nif __name__ == '__main__':\n print('selftest', selftest())\n","mods/cdj900nxs/credit.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"The mods' credit on the CDJ-900NXS 1.31 boot screen: \"Pionaarr Mods\"\nand the release (RELEASE, e.g. \"Pionaarr Mods 1.8.1\") where the splash\nshows \"Ver 1.31\". Our own; mods/patch_cdj900nxs.py\napplies it as `credit` (the patcher ticks it at the start).\n\nThe splash's text is made by 0x08BDCC14: a 50-byte buffer gets strcpy\n(0x08C28460) of the string the .data word at RAM 0x1110766C points to\n(image 0x08C6D22C, \"Ver \" at 0xA80D12BC), then strcat (the same function)\nof the version the getter 0x08BE9A8C returns (\"1.31\" at 0xA8000740), and is\nshown. The .data word now points at our text, and the function's own\nliteral of the getter (0x08BDCD8C, read only at 0x08BDCC34) at a stub that\nreturns an empty string, so nothing is added to it. The text's box (.data\n0x08C841E0: 139,363 - 619,385, right-aligned) is 8 lines taller, so the\ndescenders of p and y show. The getter itself and\neverything else that shows the version (the LINK screen's \"Ver %s\",\nUTILITY, the updater, the boot loader) are untouched.\"\"\"\nimport struct\n\nVERSION = '1.1.0' # Semantic Versioning: MAJOR.MINOR.PATCH\nRELEASE = '1.9.1' # the Pionaarr Mods release, ALWAYS the latest: the website's generator\n # (tools/make_builder_data.py) writes its version here\n\nBASE = 0x08000000\nDATA_WORD = 0x08C6D22C # .data: the splash's \"Ver \" pointer (RAM 0x1110766C)\nDATA_VER = 0xA80D12BC # \"Ver \"\nGETTER_LIT = 0x08BDCD8C # the splash function's literal of the version getter\nGETTER = 0x08BE9A8C\nTEXT = 'Pionaarr Mods ' + RELEASE\nCREDIT = 0x08FCF900 # between beatjump's 0x08FCF400..0x08FCF814 and cuecolours' 0x08FCFA00\nCREDIT_LEN = 0x40\nSTUB = CREDIT + 0x20 # mov.l @(4,pc),r0; rts; nop; nop; .long the text's final NUL\nBOX = 0x08C841E0 # .data: the splash text's box, x1 y1 x2 y2 (16 bits each)\nBOX_STOCK = (139, 363, 619, 385)\nBOX_NEW = (139, 363, 619, 393) # \"Ver 1.31\" has no descender; p and y need 8 more lines\n\n\ndef area():\n \"\"\"The credit area's bytes: the text, then the stub returning \"\".\"\"\"\n text = TEXT.encode('ascii') + b'\\0' # the splash takes bytes as Latin-1: no UTF-8\n # the splash's buffer is 50 bytes, its UTF-16 copy 100\n assert len(text) <= STUB - CREDIT and len(text) <= 50\n empty = CREDIT + len(text) - 1\n stub = struct.pack('<4HI', 0xD001, 0x000B, 0x0009, 0x0009, empty | 0xA0000000)\n return text.ljust(STUB - CREDIT, b'\\0') + stub\n\n\ndef patch(app, stock):\n \"\"\"The application with the credit; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n end = CREDIT + CREDIT_LEN - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[CREDIT - BASE:end]):\n raise SystemExit('patch_cdj900nxs: credit: its area 0x%08x.. is not free' % CREDIT)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + 4] != struct.pack(' the 44-byte resource records\nTRI_ID = 0xC5 # the red memory-cue triangle, 16 x 5, RGB565\nTRI_PIXELS = 0x0815A1B0 # its pixels in the image\nTRI_RED = 0xF800\nMEMLIST = 0x0B7BE8D4 # the UI's memory list, 10 x 0x24\nOV_RECS = 0x0B800D9C # the overview's marker records, 12 bytes\nMK_RECS = 0x0B801258 # the marker records they come from, 16 bytes\nMK_RECORDS = 101\nMKCOPY_LIT = 0x08BC6BF8 # the waveform preparer's literal for memcpy, which fills MK_RECS\nMEMCPY = 0x08C279C4\nHOT_DEFAULT = 0x07E0 # a hot cue without a colour: green, as rekordbox's default\n\n# rekordbox's eight memory-cue colours (ids 1-8), as the player itself draws\n# them: the track-colour dots of the browser, resources 0x14, 0x16, 0x13,\n# 0x18, 0x12, 0x0F, 0x11 and 0x15 (RGB565 there).\nMEMORY_RGB565 = [\n 0xFA55, # 1 pink\n 0xF800, # 2 red\n 0xFC20, # 3 orange\n 0xFFE0, # 4 yellow\n 0x07E0, # 5 green\n 0x079F, # 6 aqua\n 0x22FF, # 7 blue\n 0x881F, # 8 purple\n]\n\n# hot cue letters A-H, 5 x 7, for the overview's bigger marker (bit 7 = left)\nLETTERS5 = [\n ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],\n ['11110', '10001', '10001', '11110', '10001', '10001', '11110'],\n ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],\n ['11110', '10001', '10001', '10001', '10001', '10001', '11110'],\n ['11111', '10000', '10000', '11110', '10000', '10000', '11111'],\n ['11111', '10000', '10000', '11110', '10000', '10000', '10000'],\n ['01110', '10001', '10000', '10111', '10001', '10001', '01111'],\n ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],\n]\n\n# hot cue letters A-H, 3 x 5, rows top down: drawn black into a 9 x 5 marker\nLETTERS = ['010101111101101', '110101110101110', '011100100100011', '110101101101110',\n '111100110100111', '111100110100100', '011100101101011', '101101111101101']\n\n# data after the code (zero in the image)\nDATA = 0x08FD0000 # after the code (checked in patch())\nHOT_TABLE = DATA # 8 x 8: +0 time 1/150 s (-1 none), +4 RGB565, +6 hot cue number;\n # entry 0's +7 counts the loads (cue_get)\nSLOTS = 18 # triangles: memory list index 0-9, hot cues 10-17\nTRI_BUF = DATA + 0x40 # SLOTS x 160 bytes\nHOTBOX_W, HOTBOX_H = 16, 9 # the overview's hot cue marker: 11 x 9, then transparent; the\nHOTBOX_SIZE = 288 # blitter wants lines of a multiple of 16 bytes, as all the firmware's\nHOTBOX = (TRI_BUF + SLOTS * 160 + 31) & ~31 # 8 x HOTBOX_SIZE\nFAKE_RECS = HOTBOX + 8 * HOTBOX_SIZE # one 44-byte record, the detail waveform's\nDATA_END = FAKE_RECS + 44\nassert DATA_END <= CC_END\n\n\ndef rgb565(r, g, b):\n return (r & 0xF8) << 8 | (g & 0xFC) << 3 | b >> 3\n\n\nSOURCE = r'''\n; ---- cue_get: dbcl_GetUsbSdCueInfo(r4 connection, r5 track, r6 reply) plus colours\ncue_get:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-20,r15 ; +0 wait's slot, +4/+8 message, +12 block, +16 size\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov.l K_cueinfo,r1\n jsr @r1\n nop\n mov r0,r12 ; the stock result, returned as it is\n mov.l K_hot,r1 ; no hot cues known\n mov #8,r2\n mov #-1,r3\ncg_clear:\n mov.l r3,@r1\n dt r2\n bf/s cg_clear\n add #8,r1\n mov.l K_hot,r1 ; one more load: entry 0's spare byte counts\n mov.b @(7,r1),r0 ; them (hotcuekeys reloads its slots on it)\n add #1,r0\n mov.b r0,@(7,r1)\n cmp/pl r12\n bf cg_early\n mov.l @r8,r11 ; the connection's socket side\n tst r11,r11\n bt cg_early\n mov.l K_check,r1\n jsr @r1\n mov r11,r4\n tst r0,r0\n bt cg_early\n mov.l K_alloc,r1\n jsr @r1\n mov #72,r4\n tst r0,r0\n bt cg_early\n mov.l r0,@(4,r15)\n mov.l r0,@(8,r15)\n mov #0,r1\n mov.l r1,@r15\n mov.l r1,@(12,r15)\n mov.l r1,@(16,r15)\n mov r8,r4\n mov r0,r5\n mov.l K_type,r6\n mov.l K_init,r1\n jsr @r1\n mov #8,r7 ; as dbcl_GetUsbSdCueInfo\n mov.l @(4,r15),r2\n mov.l r9,@(24,r2) ; the track\n mov #0,r1\n mov.l r1,@(28,r2)\n mov r11,r4\n mov.l @(8,r15),r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bf cg_early\n mov r15,r1\n add #12,r1\n mov.l r1,@-r15 ; &block\n mov.l @(12,r15),r4 ; the message\n mov.l K_reply,r5\n mov r15,r6\n add #4,r6\n mov r15,r7\n mov.l K_wait,r1\n jsr @r1\n add #20,r7 ; &size\n add #4,r15\n mov.l @(12,r15),r13 ; the reply's entries\n tst r13,r13\n bt cg_early\n cmp/pz r0\n bf cg_free\n mov.l @(16,r15),r14\n add r13,r14 ; their end\n bra cg_entry\n nop\ncg_early:\n bra cg_out\n nop\ncg_entry:\n mov r13,r1\n add #16,r1\n cmp/hi r14,r1\n bt cg_free ; not even a header left\n mov.l @r13,r11 ; length\n mov #64,r1\n cmp/hs r1,r11\n bf cg_free\n mov r13,r1\n add r11,r1\n cmp/hi r14,r1\n bt cg_free ; runs past the end\n mov.l @(12,r13),r1 ; ms -> 1/150 s: ms * 3 / 20\n mov r1,r2\n add r1,r2\n add r1,r2\n mov.l K_div20,r1\n dmulu.l r1,r2\n sts mach,r9\n shlr2 r9\n shlr2 r9 ; r9 = time\n mov.b @(4,r13),r0\n extu.b r0,r6 ; r6 = hot cue number, 0 = memory cue\n tst r6,r6\n bf cg_hot\n mov #58,r0\n mov.b @(r0,r13),r0 ; colour id\n extu.b r0,r0\n tst r0,r0\n bt cg_next\n mov #8,r1\n cmp/hi r1,r0\n bt cg_next\n add r0,r0\n mov.l K_pal,r1\n add r1,r0\n mov.w @r0,r7\n bra cg_match\n extu.w r7,r7\ncg_hot:\n mov.l K_green,r7 ; no colour: rekordbox's default\n mov #82,r1 ; colour code, R, G, B, 2 bytes: 0x52 at least\n cmp/hs r1,r11\n bf cg_hstore\n mov #72,r0 ; the comment: its length in bytes at +0x48, text from +0x4A,\n mov.w @(r0,r13),r2 ; then 4 bytes, then colour code, R, G, B\n extu.w r2,r2 ; (the server's own entries: not checked against the length)\n add r13,r2\n add #78,r2\n mov.b @r2,r0 ; colour code; 0 = none\n tst r0,r0\n bt cg_hstore\n mov.b @(1,r2),r0 ; R\n extu.b r0,r7\n mov #-8,r0\n and r0,r7\n shll8 r7\n mov.b @(2,r2),r0 ; G\n extu.b r0,r0\n shlr2 r0\n shll2 r0\n shll r0\n shll2 r0\n or r0,r7\n mov.b @(3,r2),r0 ; B\n extu.b r0,r0\n shlr2 r0\n shlr r0\n or r0,r7\ncg_hstore:\n mov #9,r1 ; hot cues 1-8 into our table\n cmp/hs r1,r6\n bt cg_match\n mov r6,r1\n add #-1,r1\n shll2 r1\n shll r1\n mov.l K_hot,r2\n add r1,r2\n mov.l r9,@r2\n mov r7,r0\n mov.w r0,@(4,r2)\n mov r6,r0\n mov.b r0,@(6,r2)\ncg_match:\n ; mark the stock reply's entry with the same hot cue number and time\n mov.l @(24,r10),r1 ; entries\n tst r1,r1\n bt cg_next\n mov.l @(16,r10),r2\n mov.l @(20,r10),r3\n add r3,r2 ; memory + hot cue entries\n mov #16,r3\n cmp/hi r3,r2\n bf cg_m_ok\n mov r3,r2\ncg_m_ok:\n tst r2,r2\n bt cg_next\ncg_m:\n mov.b @(2,r1),r0\n extu.b r0,r0\n cmp/eq r6,r0\n bf cg_m_next\n mov.l @(12,r1),r0\n sub r9,r0 ; time difference, within one unit\n add #1,r0\n mov #2,r3\n cmp/hi r3,r0\n bt cg_m_next\n mov r7,r0\n mov.w r0,@(16,r1)\n mov r1,r3\n add #18,r3\n mov #1,r0\n mov.b r0,@r3\ncg_m_next:\n dt r2\n bf/s cg_m\n add #36,r1\ncg_next:\n bra cg_entry\n add r11,r13\ncg_free:\n mov.l @(12,r15),r4\n tst r4,r4\n bt cg_out\n mov.l K_free,r1\n jsr @r1\n nop\ncg_out:\n mov r12,r0\n add #20,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nK_alloc: .long 0x089A791E\nK_check: .long 0x089736F2\nK_cueinfo: .long CUEINFO\nK_div20: .long 0xCCCCCCCD\nK_free: .long 0x08A5EBC2\nK_green: .long HOT_DEFAULT\nK_hot: .long HOT_TABLE\nK_init: .long 0x08973C50\nK_pal: .long PALETTE\nK_reply: .long 0x00004E02\nK_send: .long 0x089738F0\nK_type: .long 0x00002B04\nK_wait: .long 0x08974198\n\n; ---- colour_for: r4 time (1/150 s), r5 tolerance -> r0 RGB565 or -1, r1 slot\ncolour_for:\n mov.l L_memlist,r1\n mov #0,r2 ; slot\ncf_loop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt cf_hot\n mov #18,r0\n mov.b @(r0,r1),r0\n cmp/eq #1,r0\n bf cf_skip\n mov.l @(12,r1),r0\n sub r4,r0\n add r5,r0\n mov r5,r3\n add r5,r3\n cmp/hi r3,r0\n bt cf_skip\n mov.w @(16,r1),r0\n extu.w r0,r0\n rts\n mov r2,r1\ncf_skip:\n add #1,r2\n mov #10,r0\n cmp/hs r0,r2\n bf/s cf_loop\n add #36,r1\ncf_hot:\n mov.l L_hot,r1 ; then the hot cues: slots 10-17\n mov #10,r2\ncf_hloop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt cf_hskip\n sub r4,r0\n add r5,r0\n mov r5,r3\n add r5,r3\n cmp/hi r3,r0\n bt cf_hskip\n mov.w @(4,r1),r0\n extu.w r0,r0\n rts\n mov r2,r1\ncf_hskip:\n add #1,r2\n mov #18,r0\n cmp/hs r0,r2\n bf/s cf_hloop\n add #8,r1\n rts\n mov #-1,r0\n\n; ---- make_tri: r4 slot, r5 RGB565 -> r0 its triangle (uncached address)\nmake_tri:\n mov r4,r0\n shll2 r0\n add r4,r0\n shll2 r0\n shll2 r0\n shll r0 ; slot * 160\n mov.l L_tribuf,r1\n add r0,r1 ; destination, through P2\n mov r1,r6\n mov #10,r0\n cmp/hs r0,r4\n bt mt_box\n mov.l L_tripix,r2\n mov.l L_red,r3\n mov #80,r7\nmt_loop:\n mov.w @r2+,r0\n extu.w r0,r0\n cmp/eq r3,r0\n bf mt_keep\n mov r5,r0\nmt_keep:\n mov.w r0,@r1\n dt r7\n bf/s mt_loop\n add #2,r1\n rts\n mov r6,r0\nmt_box:\n mov r4,r0 ; the letter, left-aligned in a word\n add #-10,r0\n shll2 r0\n mov.l L_font,r2\n mov.l @(r0,r2),r3\n mov.l L_key,r2 ; transparent\n mov #5,r7\nmb_row:\n mov #0,r4\nmb_col:\n mov #9,r0\n cmp/hs r0,r4\n bt mb_t ; past the marker\n mov #3,r0\n cmp/hs r0,r4\n bf mb_c\n mov #6,r0\n cmp/hs r0,r4\n bt mb_c\n shll r3 ; the letter's next pixel into T\n bf mb_c\n bra mb_st\n mov #0,r0 ; black\nmb_c:\n bra mb_st\n mov r5,r0\nmb_t:\n mov r2,r0\nmb_st:\n mov.w r0,@r1\n add #2,r1\n add #1,r4\n mov #16,r0\n cmp/hs r0,r4\n bf mb_col\n dt r7\n bf mb_row\n rts\n mov r6,r0\n\n; ---- mk_copy: memcpy(r4, r5, r6) for the waveform preparer; filling the\n; marker records, it adds the hot cues to them as memory-cue markers.\nmk_copy:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l L_memcpy,r1\n jsr @r1\n nop\n mov.l L_mkrecs,r1\n cmp/eq r1,r8\n bf mk_out\n mov.l r0,@-r15 ; memcpy's result\n mov r8,r1\n mov #101,r2\n mov #-1,r3\n extu.b r3,r3 ; 0xff ends the list\nmk_find:\n mov.l @r1,r0\n cmp/eq r3,r0\n bt mk_add\n dt r2\n bf/s mk_find\n add #16,r1\n bra mk_done\n nop\nmk_add:\n mov.l L_hot,r3\n mov #8,r7\nmk_h:\n mov.l @r3,r0 ; time\n cmp/eq #-1,r0\n bt mk_hn\n tst r2,r2\n bt mk_done ; the list is full\n mov.l r0,@(8,r1)\n mov #0,r0\n mov.l r0,@r1 ; a memory cue\n mov.l r0,@(4,r1)\n mov #-1,r0\n mov.l r0,@(12,r1)\n add #16,r1\n add #-1,r2\nmk_hn:\n dt r7\n bf/s mk_h\n add #8,r3\nmk_done:\n mov.l @r15+,r0\nmk_out:\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- det_res: the resource resolver, for the detail waveform (r9 = its column).\n; Every 0xC5 it is asked for becomes a 16 x 8 marker of ours, as the renderer\n; (patched) copies 11 x 8: a memory cue's triangle in rows 3-7 (coloured, or\n; the stock red one), a hot cue's box with its letter in rows 0-7.\ndet_res:\n mov.l L_resolve,r1\n mov.l L_triid,r0\n cmp/eq r0,r4\n bt dr_mine\n jmp @r1\n nop\ndr_mine:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r9,r8\n jsr @r1\n nop\n mov r0,r10 ; the record\n tst r10,r10\n bt dr_out\n mov r8,r4\n bsr colour_for\n mov #16,r5 ; zoomed out, a column stands for up to 16\n mov r1,r9 ; slot\n cmp/eq #-1,r0\n bt dr_plain\n mov r0,r5\n mov r9,r4\n mov #10,r2\n cmp/hs r2,r4\n bt dr_hot\n bsr make_tri\n nop\n mov #3,r11 ; triangle: rows 3-7\n bra dr_copy\n mov #5,r6\ndr_hot:\n bsr make_hotbox\n nop\n mov #0,r11 ; box: rows 0-7\n bra dr_copy\n mov #8,r6\ndr_plain:\n mov #18,r9 ; no colour: the stock triangle\n mov.l L_tripix,r0\n mov #3,r11\n mov #5,r6\ndr_copy:\n mov r0,r3 ; source, 16 pixels a line\n mov.l L_detbuf,r2\n mov r9,r0\n shll8 r0\n add r0,r2\n mov r2,r8 ; this slot's 16 x 8\n mov.l L_key,r0\n mov #64,r7\n shll r7\n mov r2,r1\ndr_fill:\n mov.w r0,@r1\n dt r7\n bf/s dr_fill\n add #2,r1\n mov r11,r0\n shll2 r0\n shll2 r0\n shll r0\n add r0,r2\ndr_row:\n mov #16,r7\ndr_px:\n mov.w @r3+,r0\n mov.w r0,@r2\n dt r7\n bf/s dr_px\n add #2,r2\n dt r6\n bf dr_row\n mov.l L_fake,r2 ; the record, with our size and pixels\n mov r10,r3\n mov #11,r4\ndr_rec:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r4\n bf/s dr_rec\n add #4,r2\n add #-44,r2\n mov.l L_detsize,r1\n mov.l r1,@(4,r2)\n mov.l r8,@(12,r2)\n mov r2,r10\ndr_out:\n mov r10,r0\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- ov_draw: draw_bitmap(r4, r5 x, r6 y, r7 resource) for the overview (r13 = its record)\nov_draw:\n mov.l L_draw,r1\n mov.l L_triid,r0\n cmp/eq r0,r7\n bt od_mine\n jmp @r1\n nop\nod_mine:\n mov.l r13,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov r7,r11\n mov.l L_ovrecs,r1 ; which record: 12 bytes each, 16 in the other list\n mov r13,r2\n sub r1,r2\n mov #0,r3\n mov #101,r4\nod_idx:\n cmp/pl r2\n bf od_found\n add #-12,r2\n dt r4\n bf/s od_idx\n add #16,r3\n bra od_plain\n nop\nod_found:\n tst r2,r2\n bf od_plain ; not on a record\n mov.l L_mkrecs,r1\n add r3,r1\n mov.l @(8,r1),r4\n bsr colour_for\n mov #0,r5\n cmp/eq #-1,r0\n bt od_plain\n mov r1,r4\n mov #10,r2\n cmp/hs r2,r4\n bt od_hot\n bsr make_tri\n mov r0,r5\n mov.l L_trisize,r13 ; 16 x 5, the triangle's own size\n bra od_pix\n nop\nod_hot:\n bsr make_hotbox ; a hot cue: the bigger box\n mov r0,r5\n mov.l L_boxsize,r13\nod_pix:\n mov.l L_restab,r1 ; the triangle's record\n mov.l @r1,r12\n mov.l L_trirec,r1\n add r1,r12\n mov.l @(32,r12),r14 ; its pixel offset and size, put back below\n mov.l @(4,r12),r1\n mov.l r13,@(4,r12)\n mov r1,r13\n mov.l L_p0mask,r1 ; the blitter gets the plain address\n and r1,r0\n mov.l L_imgbase,r1\n sub r1,r0\n mov.l r0,@(32,r12)\n mov r8,r4\n mov r9,r5\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\n mov.l r13,@(4,r12)\n bra od_out\n mov.l r14,@(32,r12)\nod_plain:\n mov r8,r4\n mov r9,r5\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\nod_out:\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n rts\n mov.l @r15+,r13\n\n .align 4\nL_cueinfo: .long CUEINFO\nL_check: .long 0x089736F2\nL_alloc: .long 0x089A791E\nL_init: .long 0x08973C50\nL_send: .long 0x089738F0\nL_wait: .long 0x08974198\nL_free: .long 0x08A5EBC2\nL_type: .long 0x00002B04\nL_reply: .long 0x00004E02\nL_div20: .long 0xCCCCCCCD\nL_hot: .long HOT_TABLE\nL_memlist: .long MEMLIST\nL_tribuf: .long TRI_BUF_P2\nL_tripix: .long TRI_PIXELS\nL_red: .long TRI_RED\nL_resolve: .long RESOLVE\nL_draw: .long DRAW\nL_triid: .long TRI_ID\nL_fake: .long FAKE_RECS\nL_detbuf: .long DET_BUF\nL_detsize: .long 0x00080010\nL_ovrecs: .long OV_RECS\nL_mkrecs: .long MK_RECS\nL_restab: .long RES_TABLE_PTR\nL_trirec: .long TRI_REC_OFF\nL_imgbase: .long 0x08100000\nL_p0mask: .long 0x1FFFFFFF\nL_pal: .long PALETTE\nL_green: .long HOT_DEFAULT\nL_font: .long FONT\nL_key: .long 0xF81F\nL_memcpy: .long MEMCPY\nL_trisize: .long 0x00050010\nL_boxsize: .long BOXSIZE\n\n; ---- make_hotbox: r4 slot (10-17), r5 RGB565 -> r0 the overview's hot cue\n; marker, 11 x 9 in its colour with the letter in black (uncached address)\nmake_hotbox:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov r4,r6\n add #-10,r6 ; 0 = A\n mov r6,r1 ; HOTBOX_SIZE (288) * index\n shll8 r1\n mov r6,r2\n shll2 r2\n shll2 r2\n shll r2\n add r2,r1\n mov.l M_hotbox,r2\n add r2,r1\n mov r1,r9 ; returned\n mov r6,r2 ; its letter: 7 bytes from FONT5 + 7 * index\n shll2 r2\n shll r2\n sub r6,r2\n mov.l M_font5,r3\n add r2,r3\n mov.l M_key,r2\n mov #0,r6 ; row\nhb_row:\n mov #0,r4 ; the letter's bits in this row\n mov r6,r0\n cmp/eq #0,r0\n bt hb_cols\n cmp/eq #8,r0\n bt hb_cols\n add #-1,r0\n mov.b @(r0,r3),r4\n extu.b r4,r4\nhb_cols:\n mov #0,r8 ; column\nhb_col:\n mov #11,r0\n cmp/hs r0,r8\n bt hb_t\n mov #3,r0\n cmp/hs r0,r8\n bf hb_c\n mov #8,r0\n cmp/hs r0,r8\n bt hb_c\n mov r4,r0\n tst #128,r0\n bt/s hb_c\n shll r4\n bra hb_st\n mov #0,r0 ; black\nhb_c:\n bra hb_st\n mov r5,r0\nhb_t:\n mov r2,r0\nhb_st:\n mov.w r0,@r1\n add #2,r1\n add #1,r8\n mov #16,r0\n cmp/hs r0,r8\n bf hb_col\n add #1,r6\n mov #9,r0\n cmp/hs r0,r6\n bf hb_row\n mov r9,r0\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nM_hotbox: .long HOTBOX_P2\nM_font5: .long FONT5\nM_key: .long 0xF81F\nFONT:\n'''\n\n\ndef routine():\n \"\"\"The code at CC_BASE, the memory-cue palette after it, and the labels.\"\"\"\n syms = {\n 'CUEINFO': CUEINFO, 'RESOLVE': RESOLVE, 'DRAW': DRAW, 'HOT_TABLE': HOT_TABLE,\n 'MEMLIST': MEMLIST, 'TRI_BUF_P2': TRI_BUF | 0xA0000000, 'TRI_PIXELS': TRI_PIXELS,\n 'TRI_RED': TRI_RED, 'TRI_ID': TRI_ID, 'FAKE_RECS': FAKE_RECS, 'OV_RECS': OV_RECS,\n 'MK_RECS': MK_RECS, 'RES_TABLE_PTR': RES_TABLE_PTR, 'TRI_REC_OFF': TRI_ID * 44,\n 'HOT_DEFAULT': HOT_DEFAULT, 'MEMCPY': MEMCPY, 'HOTBOX_P2': HOTBOX | 0xA0000000,\n 'BOXSIZE': HOTBOX_W | HOTBOX_H << 16, 'DET_BUF': DET_BUF,\n }\n font = b''.join(struct.pack(' r0 the named cue to show (CN_TABLE entry) or 0; r1-r7 used\ncn_pick:\n mov.l P_hot,r6\n mov.b @(7,r6),r0 ; the table is this load's?\n mov.l P_load,r1\n mov.b @r1,r1\n cmp/eq r1,r0\n bf pk_none\n mov.l P_count,r1\n mov.l @r1,r3\n tst r3,r3\n bt pk_none\n mov.l P_pos,r1\n mov.l @r1,r4\n mov.l P_behind,r1\n sub r1,r4 ; from a second behind\n mov.l P_table,r2\n mov #0,r0 ; best so far\npk_loop:\n mov.l @r2,r1 ; ms\n cmp/ge r4,r1\n bf pk_next ; behind\n mov.l @(8,r2),r5 ; a hot cue still where it was?\n tst r5,r5\n bt pk_ok\n mov #9,r7\n cmp/hs r7,r5\n bt pk_next\n add #-1,r5\n shll2 r5\n shll r5\n add r6,r5\n mov.l @r5,r5 ; HOT_TABLE's time, 1/150 s\n mov.l @(4,r2),r7\n sub r7,r5\n add #1,r5\n mov #2,r7\n cmp/hi r7,r5\n bt pk_next\npk_ok:\n tst r0,r0\n bt pk_take\n mov.l @r0,r7\n cmp/ge r7,r1\n bt pk_next ; not nearer\npk_take:\n mov r2,r0\npk_next:\n dt r3\n bf/s pk_loop\n add #64,r2\n rts\n nop\npk_none:\n rts\n mov #0,r0\n\n; ---- cn_draw: the text widget's draw method (r4 renderer, r5 widget)\ncn_draw:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov.l @(4,r5),r1 ; the title?\n mov.l D_title,r2\n cmp/eq r2,r1\n bf dr_plain\n mov.l D_depth,r1 ; BROWSE closed?\n mov.b @r1,r1\n tst r1,r1\n bf dr_plain\n mov.l @(40,r9),r1 ; the title white? (dimmed behind a popup, as MENU's: no name)\n mov #-1,r2\n cmp/eq r2,r1\n bf dr_plain\n bsr cn_pick\n nop\n tst r0,r0\n bt dr_plain\n mov r0,r10 ; the cue\n mov.l D_label,r2 ; the label: \"A \" for a hot cue, then the name\n mov.l @(8,r10),r1\n tst r1,r1\n bt dr_name\n add #64,r1 ; 'A' - 1 + number\n mov r1,r0\n mov.w r0,@r2\n mov #32,r0\n mov.w r0,@(2,r2)\n add #4,r2\ndr_name:\n mov r10,r1\n add #16,r1\ndr_ncopy:\n mov.w @r1+,r0\n mov.w r0,@r2\n tst r0,r0\n bf/s dr_ncopy\n add #2,r2\n mov.l @(28,r9),r11 ; the title, narrowed\n mov r11,r1\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_tw,r2\n or r2,r1\n mov.l r1,@(28,r9)\n mov r8,r4\n mov r9,r5\n mov.l D_next,r1\n jsr @r1\n nop\n mov.l r11,@(28,r9)\n mov.l D_clone,r2 ; the copy: our place, text, colour\n mov r9,r1\n mov #24,r3\ndr_clone:\n mov.l @r1+,r0\n mov.l r0,@r2\n dt r3\n bf/s dr_clone\n add #4,r2\n mov.l D_clone,r5\n mov.l @(24,r9),r1 ; y stays, x ours\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_lx,r2\n or r2,r1\n mov.l r1,@(24,r5)\n mov r11,r1\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_lw,r2\n or r2,r1\n mov.l r1,@(28,r5)\n mov.l @(12,r10),r1\n mov.l r1,@(40,r5)\n mov.l D_label,r1\n mov.l r1,@(52,r5)\n mov r8,r4\n mov.l D_next,r1\n jsr @r1\n nop\n bra dr_out\n nop\ndr_plain:\n mov r8,r4\n mov r9,r5\n mov.l D_next,r1\n jsr @r1\n nop\ndr_out:\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nP_behind: .long BEHIND_MS\nP_count: .long CN_COUNT\nP_hot: .long HOT_TABLE\nP_load: .long CN_LOAD\nP_pos: .long POS_MS\nP_table: .long CN_TABLE\nD_clone: .long CN_CLONE\nD_depth: .long BROWSE_DEPTH\nD_hmask: .long 0xFFFF0000\nD_label: .long CN_LABEL\nD_lw: .long LABEL_W\nD_lx: .long LABEL_X\nD_next: .long NEXT_DRAW\nD_title: .long TITLE_ID\nD_tw: .long TITLE_W\nMEMORY_TAB:\n'''\n\n\ndef routine(next_draw):\n src = SOURCE + ''.join(' .long 0x%08X\\n' % c for c in MEMORY_ABGR)\n return sh4asm.assemble(src, CN_CODE, {\n 'CN_COUNT': CN_COUNT, 'CN_LOAD': CN_LOAD, 'CN_TABLE': CN_TABLE, 'CN_LABEL': CN_LABEL,\n 'CN_CLONE': CN_CLONE, 'CN_MAX': CN_MAX, 'CN_NAME': CN_NAME, 'FREE': FREE,\n 'HOT_TABLE': HOT_TABLE, 'POS_MS': POS_MS, 'BROWSE_DEPTH': BROWSE_DEPTH,\n 'TITLE_ID': TITLE_ID, 'TITLE_W': TITLE_W, 'LABEL_X': LABEL_X, 'LABEL_W': LABEL_W,\n 'BEHIND_MS': BEHIND_MS, 'NO_COLOUR': NO_COLOUR, 'NEXT_DRAW': next_draw})\n\n\ndef patch(app, stock):\n \"\"\"The application with CUE NAMES added (after cuecolours and, if chosen,\n keynotation); `stock` is the unmodified 1.31 application.\"\"\"\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' 0xC00487A4)\nTRAMP_TARGET = 0xC00487A4\nMB = 0x1183E000\nDP = 0x11806900\nHEADS = 0x1182E0E0\nLINKS = 0xC1F38B90\nPLAY_HEAD_DP = 969 # byte: the playing head's index\nMARGIN = 750 # slots (10 s) a forward jump leaves decoded ahead (2 s: EMERGENCY LOOP\n # after rapid 8-beat jumps, emulator too; 5 s was fine there)\nBLOCKS = ((0xC0000040, 0xC0000080), (0xC00000A0, 0xC0000100), (0xC0000120, 0xC00001E0))\nFREE_PACKETS = (struct.pack('<8I', *([0x0c6e0c6e] * 7 + [0xefe00000])), # compact nops only\n struct.pack('<8I', *([0x0c6e0c6e] * 6 + [0, 0xe7e00000])))\n\n\ndef code():\n \"\"\"[(addr, word)] of dj_poll.\"\"\"\n a = c6asm.Asm(BLOCKS[0][0])\n o = a.op\n # block 1: the mailbox; e = heads + 30 * h\n o('const', MB, 'a3')\n o('ldw', 'a4', 'a3', 0)\n o('nop', 4)\n o('cmpeq', 1, 'a4', 'a1')\n o('b', 'tail', p='!a1')\n o('nop', 5)\n o('ldw', 'b4', 'a3', 4) # n\n o('lddp', 'ldbu', 'b5', PLAY_HEAD_DP) # h\n o('nop', 4)\n o('shl', 'b5', 5, 'b6')\n o('shl', 'b5', 1, 'b7')\n o('sub', 'b6', 'b7', 'b6') # 30 h\n o('b', 'blk2')\n o('nop', 5)\n # block 2: the head's fields, the direction\n a.org(BLOCKS[1][0])\n a.label('blk2')\n o('const', HEADS, 'b8')\n o('add', 'b8', 'b6', 'b6') # e\n o('ldhu', 'a5', 'b6', 2) # cur\n o('ldh', 'a6', 'b6', 6) # behind\n o('ldh', 'a7', 'b6', 8) # ahead\n o('nop', 4)\n o('stw', 'a5', 'a3', 16)\n o('stw', 'a6', 'a3', 24)\n o('stw', 'a7', 'a3', 28)\n o('mvk', 2, 'a9') # refused unless done\n o('const', LINKS, 'a4')\n o('cmplt', 0, 'b4', 'b0') # n > 0\n o('b', 'fwd', p='b0')\n o('nop', 5)\n o('cmpgt', 0, 'b4', 'b1') # n < 0\n o('b', 'ok', p='!b1') # n = 0\n o('nop', 5)\n o('sub', 0, 'b4', 'b2') # m = -n\n o('mv', 'b2', 'a8')\n o('sub', 'a6', 'a8', 'a8') # behind - m\n o('b', 'blk3')\n o('nop', 5)\n # block 3: the walk\n a.org(BLOCKS[2][0])\n a.label('blk3')\n o('cmplt', 0, 'a8', 'a1') # at least one slot left behind\n o('b', 'done', p='!a1')\n o('nop', 5)\n a.label('loopb')\n o('addaw', 'a4', 'a5', 'a2')\n o('ldhu', 'a5', 'a2', 0) # prev\n o('nop', 4)\n o('add', -1, 'b2', 'b2')\n o('b', 'loopb', p='b2')\n o('nop', 5)\n o('sub', 0, 'b4', 'b7')\n o('mv', 'b7', 'a8') # m\n o('sub', 'a6', 'a8', 'a6') # behind - m\n o('add', 'a8', 'a7', 'a7') # ahead + m\n o('b', 'store')\n o('nop', 5)\n a.label('fwd')\n o('mv', 'b4', 'a8')\n o('sub', 'a7', 'a8', 'a8') # ahead - n\n o('mvk', MARGIN, 'b7')\n o('cmplt', 'b7', 'a8', 'b1') # more than MARGIN left ahead\n o('b', 'done', p='!b1')\n o('nop', 5)\n o('mv', 'b4', 'b2')\n a.label('loopf')\n o('addaw', 'a4', 'a5', 'a2')\n o('ldhu', 'a5', 'a2', 2) # next\n o('nop', 4)\n o('add', -1, 'b2', 'b2')\n o('b', 'loopf', p='b2')\n o('nop', 5)\n o('mv', 'b4', 'a8')\n o('add', 'a8', 'a6', 'a6') # behind + n\n o('sub', 'a7', 'a8', 'a7') # ahead - n\n a.label('store')\n o('sth', 'a5', 'b6', 2)\n o('sth', 'a6', 'b6', 6)\n o('sth', 'a7', 'b6', 8)\n o('sth', 'a5', 'b6', 14) # the point the decoder fills from (set on a\n o('sth', 'a6', 'b6', 16) # pause): moved along, so standing it decodes\n o('sth', 'a7', 'b6', 18) # on from the new place\n o('stw', 'a5', 'a3', 20)\n a.label('ok')\n o('mvk', 1, 'a9')\n a.label('done')\n o('stw', 'a9', 'a3', 8)\n o('ldw', 'a8', 'a3', 12)\n o('nop', 4)\n o('add', 1, 'a8', 'a8')\n o('stw', 'a8', 'a3', 12)\n o('mvk', 0, 'a8')\n o('stw', 'a8', 'a3', 0)\n a.label('tail')\n o('b', TRAMP_TARGET)\n o('nop', 5)\n words = a.assemble()\n for at, _ in words:\n assert any(lo <= at < hi for lo, hi in BLOCKS), 'dspjump: 0x%08x is past its blocks' % at\n return words\n\n\ndef packets(words):\n \"\"\"{packet addr: 8 words}: our words, the rest of each packet nop.\"\"\"\n out = {}\n for at, w in words:\n fp = at & ~31\n out.setdefault(fp, [0] * 8)[(at - fp) // 4] = w\n return out\n\n\ndef set_cst16(word, value):\n return (word & ~(0xFFFF << 7)) | (value & 0xFFFF) << 7\n\n\ndef patch(app, stock):\n out = bytearray(app)\n for fp, ws in packets(code()).items():\n off = B_IMG - BASE + (fp - 0xC0000000)\n if stock[off:off + 32] not in FREE_PACKETS or out[off:off + 32] != stock[off:off + 32]:\n raise SystemExit('patch_cdj900nxs: dspjump: the vector packet 0x%08x is not the expected one' % fp)\n out[off:off + 32] = struct.pack('<8I', *ws)\n entry = BLOCKS[0][0]\n for at, want, value in ((TRAMP_MVK, 0x0543d22a, entry), (TRAMP_MVKH, 0x0560026a, entry >> 16)):\n off = A_IMG - BASE + (at - A_AT)\n w = struct.unpack_from(' red, grey -> dark red\n\nSOURCE = r'''\n; ---- ew_draw: draw_bitmap(r4, r5 x, r6 y, r7 resource) for the playing-address bar\new_draw:\n mov.l E_draw,r0\n mov.l E_res,r1\n cmp/eq r1,r7\n bt ew_bar\n jmp @r0\n nop\new_bar:\n mov.l E_len,r1 ; length in ms: 1/150 s * 20 / 3\n mov.l @r1,r1\n tst r1,r1\n bt ew_plain\n mov r1,r2\n shll2 r2\n shll2 r2\n shll2 r1\n add r2,r1 ; * 20\n mov.l E_div3,r2\n dmulu.l r2,r1\n sts mach,r1\n shlr r1 ; / 3\n mov.l E_pos,r2\n mov.l @r2,r2\n sub r2,r1 ; left\n cmp/pl r1\n bf ew_plain\n mov.l E_warn,r2\n cmp/hs r2,r1\n bt ew_plain\n mov.l r8,@-r15 ; red: the record pointed at RED_BAR for this call\n mov.l r9,@-r15\n sts.l pr,@-r15\n mov.l E_tab,r8\n mov.l @r8,r8\n mov.l E_recoff,r1\n add r1,r8\n mov.l @(32,r8),r9\n mov.l E_redoff,r1\n mov.l r1,@(32,r8)\n jsr @r0\n nop\n mov.l r9,@(32,r8)\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\new_plain:\n jmp @r0\n nop\n\n .align 4\nE_div3: .long 0xAAAAAAAB\nE_draw: .long DRAW\nE_len: .long TRACK_LEN\nE_pos: .long POS_MS\nE_recoff: .long REC_OFF\nE_redoff: .long RED_OFF\nE_res: .long RES_BAR\nE_tab: .long RES_TABLE_PTR\nE_warn: .long WARN_MS\n'''\n\n\ndef routine():\n return sh4asm.assemble(SOURCE, EW_CODE, {\n 'DRAW': DRAW, 'TRACK_LEN': TRACK_LEN, 'POS_MS': POS_MS, 'REC_OFF': RES_BAR * 44,\n 'RED_OFF': RED_BAR - RES_BASE, 'RES_BAR': RES_BAR, 'RES_TABLE_PTR': RES_TABLE_PTR,\n 'WARN_MS': WARN_MS})\n\n\ndef patch(app, stock):\n \"\"\"The application with END WARNING added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n word = lambda buf, a: struct.unpack_from(' dbcl_RegUdbSdCue, +0x1C delete 0x0897A0A6\n -> dbcl_DelUsbSdCue) handles it with the cue entry at request +112 (+1\n kind 1, +2 hot cue number, +4 ms, +12 1/150 s). hk_dbop queues (ST4) a\n set or delete; he_dbq sends 0x7D6 once MEMORY's own checks pass and no\n request of the UI is under way; hk_dbreg (the table's register entry)\n makes the entry ours and sends a delete to the delete handler. Right\n after a load the DB may not take it yet (as MEMORY there): the request\n is let go after DBQ_TICKS. The queue is dropped on a track load.\n * A slot loaded while heard plays in state 0x17, in which the deck takes\n no LOOP IN / OUT; once it plays on from the slot, PLAY with that slot\n (he_play) puts it into state 1 without a jump, as after a recall.\n * The engine also makes beatjump's jumps (he_bj; beatjump writes the\n target and the jump's length to BJ_REQ/BJ_DELTA): the DSP moves its\n playing head in the audio it has decoded (he_dj, he_djw; dspjump.py),\n playing, paused or at cue standby; refused (not enough decoded ahead)\n it is dropped, the press ignored (DJ_RETRY 0). The older ways (a load into BJ_SLOT\n while playing, a memory CALL standing; hj_old and after, he_gap,\n he_rate) are kept but not used: the first left the deck standing in\n 0x17, the second froze the UI after a few. Its core (loads, beat jumps,\n the queue) runs with the item OFF too; the preload only with the item ON\n and the mode on.\n * After a hot cue is set or deleted, 1 at 0x0A4DE7C4 has the waveform\n preparer copy its markers again (0x08BD1224), so they show at once.\n * Not done: a slip hot cue. The player's slip return (command 1, 9, as a\n slip loop's release sends it) does nothing after a CALL; the slip\n position is kept by the player for its own slip actions only.\n * Settings are kept in a journal in flash: the Backup task (0x08A68A96)\n takes a type-5 message (+8 id, +10 value; sent as 0x08A681A0 does, block\n from the pool *0x0A11D7A4, mailbox *0x0A11D7A0) and appends the pair to\n sector 0x7F6000 (0x08A66578). At start-up it keeps the sector if its last\n word is \"FBOK\", else erases it. hk_save sends SAVE_ID; hk_load, on the\n first poll, takes the last SAVE_ID it finds there.\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport cuecolours # noqa: E402\n\nVERSION = '1.3.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nHK_BASE = 0x08FD1800 # after cuecolours' 0x08FCFA00..0x08FD1800\nHK_END = 0x08FD3800\n# a second area after cuecolours' (0x08FD3800..0x08FD4B00): the hot cue\n# engine's state, then the HOT KEYS boxes' pixels\nHK2_BASE = 0x08FD4C00\nHK2_END = 0x08FD6C00\n# a third area after keynotation's (0x08FD7C00..0x08FD8C00): what holds the\n# screen still while hot cues load (he_uf, pub_hook, he_shadow)\nSHADOW = 0x08FD8C00\nSHADOW_END = 0x08FD9C00\nLOAD_PHASE = SHADOW + 0xD0 # byte: 1 while the box shows LOADING (he_lock), +1 the last one drawn\nPIX_LOAD = SHADOW + 0x400 # the LOADING box's pixels (88 x 15, aligned for the blitter)\n# the decoded key bits locked while loading: CALL -/+, DELETE, MEMORY (0x0B2E82C4 0x78); TRACK +,\n# SEARCH -/+ (+1 0xE0); TRACK -, LOOP IN/OUT, RELOOP, 4 BEAT, 0x12:0x02 (+2 0x3F); PLAY, CUE\n# (+3 0x06); SLIP (+7 0x10); hot cue keys 3/4..1/8 (+10 0xF8). Found by holding each panel key.\nLOCK_BASE = 0x0B2E82C4\nLOCK_MASK = (0x78, 0xE0, 0x3F, 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0xF8)\nSH_AFAC = SHADOW # word: where the UI's player-event handler now puts UI_AFAC\nPUB_HELD = SHADOW + 4 # 2 words: the player's position (1/150 s) and its ms as last published free\nSH_OVW = SHADOW + 0x10 # 0x90 bytes: a copy of OVW_SRC for the overview's playhead (he_shadow)\nOVW_SRC = 0x0994488C # PLAYER_CTX + 0x2B0, the player status the overview's playhead reads: +0x14 the position\n # (1/150 s, = PLAYER_CTX + 0x2C4), +0x88 the length\nOVW_LIT = 0x08BD4AC0 # its literal in the overview's updater (0x08BD49A0, 0x08BD4A08)\nUI_STATE = 0x0AF3AF20 # byte: the UI's copy of the player's state (0x17 while a CALL runs: QUANTIZE\n # goes grey and the BPM box white then, as after a CUE press)\nUI_KIND = 0x0AF3B134 # byte: the kind of the last player event (1-7)\nUI_STALE = 0x0B2E3950 # byte: 0xFF = the track's info is stale: the UI fetches it again, blanking BPM,\nUI_STALE3 = 0x0B2E39D9 # QUANTIZE and the memory list meanwhile; three more such bytes\n# The UI's player-event handler writes those six on every state message\n# (0x08B3BCC0-0x08B3BCE6); evt_hook does it instead, or not while the screen\n# is held: its literal of UI_STATE (0x08B3BE1C) now names evt_hook, and its\n# four instructions at 0x08B3BCC0 (r1 = that literal, r7 = -1, r13 = its\n# 0x0AF3AF28 literal, r5 = its 0x0AF3B138 literal) become r13, r1, jsr @r1\n# with r7 = -1 in the delay slot (a literal load there is illegal); evt_hook\n# sets r5. The other five literals name SH_SINK: the handler's own stores\n# go nowhere.\nEVT_HOOK_LIT = 0x08B3BE1C # 0x0AF3AF20\nEVT_HOOK_AT = 0x08B3BCC0\nEVT_HOOK_OLD = bytes.fromhex('56d1 ffe7 56dd 57d5'.replace(' ', '')) # mov.l r1; mov #-1,r7; mov.l r13; mov.l r5\nEVT_HOOK_NEW = bytes.fromhex('57dd 56d1 0b41 ffe7'.replace(' ', '')) # mov.l r13; mov.l r1; jsr @r1; mov #-1,r7\nEVT_R5 = 0x0AF3B138 # what r5 is after the hook\nEVT_SINK_LITS = ((0x08B3BE2C, UI_STALE), (0x08B3BE28, UI_STALE3), (0x08B3BE38, UI_STALE3 + 1),\n (0x08B3BE34, UI_STALE3 + 2), (0x08B3BE30, UI_KIND))\nSH_SINK = SHADOW + 0xB0 # 8 bytes: where those stores land\nGONE_ST = SHADOW + 0xD8 # 2 words: the loaded track is on this player's USB (he_gone), the load\nHK_API = SHADOW + 0xC0 # 2 words: hk_save2, hk_load2 (beatjump calls them for its setting)\nDJ_RETRY = 0 # ticks: a DSP jump refused while playing is asked for again until then (0: dropped, the press ignored)\nHOLD_FLAG = HK2_BASE + 128 + 60 # byte: ST4 +60, 1 while the screen is held\nUI_AFAC = 0x0AF3AFAC # word: the memory a CALL is going to (0 none): the overview's line and\n # marker go there; set by the UI's player-event handler 0x08B3B6D4\n# The handler's switch on the event's kind (0x08B3B762-0x08B3B7C1, r11 =\n# the kind) that writes it becomes a call of afac_hook (its literal in the\n# switch's own room), which writes the same unless the screen is held. The\n# other writers of UI_AFAC (0x08B4E244, 0x08B550E6, 0x08B5F242 ...) are not\n# touched.\nAFAC_SWITCH = 0x08B3B762\nAFAC_SWITCH_END = 0x08B3B7C2\nPUB_CONV = 0x08A28826 # the player's units -> ms conversion (r4 units, r5 speed -> r0 ms)\nPUB_CONV_LIT = 0x089EE07C # its literal in the position publisher (0x089EDE70 -> jsr at 0x089EDE8A),\n # which stores the position at PLAYER_CTX + 0x2C4, +0x2C8 and the ms at +0x2CC\nST2 = HK2_BASE # the engine's state, 128 bytes (see hk_engine)\nST2_SILENT = ST2 + 2 # byte: 1 while a hot cue is loaded silently\nST3 = ST2 + 64 # its second half (beat jumps), past @(disp)'s reach\nBJ_REQ = ST3 # word: a beat jump's target in 1/150 s | 1 << 31, for hk_engine\nBJ_TICK = ST3 + 4 # word: the tick its key was pressed\nBJ_DELTA = 0x08FCF818 # word: the jump's length in ms (beatjump's bj_poll)\nBJX = 0x08FCF820 # the DSP jump's state (see he_dj), 8 words; +32 a jump\n # waiting at cue standby, +36 bj_poll's position meanwhile (he_bj)\nBJ_POSV = BJX + 36\nBJ_PEND = BJX + 40 # word: presses beatjump counted meanwhile (+1 forward, -1 back)\nBJ_SETTLE = 50 # ticks after a memory CALL before he_bj sends the next (BJX +44: when it ended)\nDSPJ_MB = 0x1183E000 # dspjump's mailbox in the DSP's L2\nDJ_TICKS = 30 # an unanswered DSP jump is given up after 0.3 s\nDJ_MAX = 1500 # slots (20 s): longer jumps are not tried\nBJ_GAP0 = 80 # ticks from a playing jump's press to its slot playing, until measured\n\n# state other mods read\nHK_SETTING = HK_BASE # byte: UTILITY > HOT CUE KEYS, 0 OFF, 1 ON\nHK_MODE = HK_BASE + 1 # byte: hot cue key mode, 1 = on\nHK_PRESS = HK_BASE + 2 # byte: 0 idle, 1 QUANTIZE held (hidden), 2 held long (passed on)\nHK_LOADED = HK_BASE + 3 # byte: 1 once the saved setting was read\nHK_T0 = HK_BASE + 4 # word: tick the press began\nHK_QWIDGET = HK_BASE + 8 # word: the QUANTIZE widget, once drawn\nHK_KEYS_PREV = HK_BASE + 12 # byte: the five beat keys' bits last poll\nHK_LEDS_OURS = HK_BASE + 13 # byte: 1 while our LED fields are in the LED state\nHK_RESUME = HK_BASE + 14 # byte: polls since a recall, 0 = none waiting to play\nHK_BLINK = HK_BASE + 15 # byte: the key blinking, A = 1, 0 none\nHK_LEVELS = HK_BASE + 16 # 5 bytes: LED level 0-3 per key, A-E\nHK_HOLD = HK_BASE + 21 # byte: a key without a hot cue held, A = 1, 0 none\nHK_DEL = HK_BASE + 22 # byte: DELETE 0 up, 1 held (hidden), 2 held and used, 3 passed on\nHK_GATE = HK_BASE + 23 # byte: the recalled key, A = 1, watched for its release\nHK_BLINK_T0 = HK_BASE + 24 # word: tick of the blinking key's press\nHK_HOLD_T0 = HK_BASE + 28 # word: tick that key was pressed\n\n# the five BEAT DIVIDE keys left of the jog, top to bottom 3/4 1/3 1/2 1/4\n# 1/8 = hot cues A-E: the panel decoder (0x08B630D0, second layout at\n# 0x08B63510: frame byte 0x11 bits 0x08-0x80) puts them in 0x0B2E82CE bits\n# 0x80 0x40 0x20 0x10 0x08. (The emulator's panel sends the first layout:\n# byte 0x10 bits 0x80 0x40 0x20 = 3/4 1/2 1/8 only.) With SLIP on, held,\n# they show the slip loop (blue LOOP 3/4 ...). PLAY and CUE are 0x0B2E82C7\n# bits 0x04 and 0x02.\nKEY_BEATS = 0x0B2E82CE\n# DELETE (panel 0x13:0x40 in the emulator) is 0x0B2E82C4 bit 0x20 (CALL 0x10,\n# MEMORY 0x40, CALL forward 0x08)\nKEY_DELETE = 0x0B2E82C4\nDEL_BIT = 0x20\nBEAT_BITS = 0xF8\n# The keys' LEDs are 2-bit fields (levels 0-3) of the 24-byte frame the deck\n# sends the panel. SLIP lights them by LED state +11 fields 0-2 and +10\n# fields 2-3 (0x08B38FF0); the five-key panel's frame builder (0x08B654F0)\n# puts those in frame byte 4 (bits 4-5, 2-3, 0-1 <- +11 fields 0, 1, 2; bits\n# 6-7 <- +10 field 3) and byte 5 bits 0-1 (<- +10 field 2). (The emulator's\n# panel takes the other builder, 0x08B656B0, which does not send them.)\n# The firmware rewrites the LED state all the time, so hk_send sets the\n# fields in the finished frame: the panel task's last step jumps to the\n# sender 0x08AF488E (r6 = the frame) through literal 0x08B653A4, its only\n# use; frame byte 22 is the 8-bit end-around-carry sum of bytes 0-21.\n# Which field is which key is for the LED test on a player to tell.\nLED_STATE = 0x0B2E83E8\nSENDER_LIT = 0x08B653A4\nSENDER = 0x08AF488E\nFRAME_LEDS = [(4, 0), (4, 2), (4, 4), (4, 6), (5, 0)] # A-E: frame byte, bit shift of the 2-bit field\nHK_CODE = HK_BASE + 0x20\n\nLONG_TICKS = 80 # 0.8 s\nGATE_TICKS = 50 # a recall held this long goes back to the hot cue when let go\nBLINK_TICKS = 40 # a recalled key's LED: bright, dim, bright, dim, 0.1 s each\nRESUME_POLLS = 127 # how long a recall waits for the cue standby to send PLAY\n\n# recall: the CALL key's handler and the record its message builder reads\nCALL_KEY = 0x08B4F2F2 # the UI's key handler, r4 = 0x100: memory CALL\nCALL_REC = 0x0AF3B28C # +0 event, +4 time in ms, +8 -1, +12 time in 1/150 s\nBUILD = 0x08B3B666 # the UI's player-message builder\nPLAYER_CTX = 0x099445DC # +0x10C play state: 1 playing, 0x17 cue standby\n# 1 here has the waveform preparer (0x08BC670A, through 0x08BD1224) copy the\n# cue markers again, and so cuecolours' mk_copy add the hot cues as they are now\nMARKERS_DIRTY = 0x0A4DE7C4\nSLIP_ON = 0x0AF3AF24 # word: 1 while SLIP is on\nHOT_KEYS = 3 # hot cue keys A-C (3/4, 1/3, 1/2); 1/4 and 1/8 do nothing, dark\nBJ_SLOT = 4 # the DSP's fifth cue slot, unused by the 900NXS: beat jumps only\n# the hot cue engine (hk_engine)\nKEY_PLAYCUE = 0x0B2E82C7 # decoded PLAY 0x04, CUE 0x02\nHPI_READ = 0x089F8ABC # the DSP driver's host port read: r4 = DSP address -> r0\nHPI_WRITE = 0x089F8B20 # its write: r4 = DSP address, r5 = the word\nWV_MID = 0x08BCFBCC # in the detail waveform's draw function 0x08BCFA34, after its memset\nWV_STOCK = bytes.fromhex('6a0540e04fdc05e14fdd9e0e') # sts fpscr,r5 .. mov.l @(r0,r9),r14\nWV_LITS = ((0x08BCFD10, 0x0B7D0760), (0x08BCFD14, 0x08C26780)) # the two it loads\nWV_R12, WV_R13 = 0x0B7D0760, 0x08C26780\nWV_OUT = 0x08BD01EE # where it copies the buffer to the screen\nWV_TICKS = 250 # after a load the waveform waits this long at most\n# The DSP's cue segments (L2, set by the DSP): row s (0 the CUE point, 1-3 hot\n# cues A-C, 4 the beat jumps') at 0x11837CE0 + s * 0x84, four 8-word segments\n# (pre-roll, point, post-roll, ...; +0 their start in 1/75 s) and +0x80 the\n# row's word, 1 once loaded (seen in the emulator's host port log).\nSLOT_ST = 0x11837D60 # + s * 0x84: row s loaded (1)\nSLOT_PT = 0x11837D00 # + s * 0x84: row s's point, 1/75 s\nTRANSPORT = 0x089F8FCA # the DSP manager's transport command (2 = play)\nTRANSPORT_LIT = 0x089F29FC # its literal, used at 0x089F27C6/CA only\nPLAYCMD = 0x089F9306 # the DSP manager's play command\nPLAYCMD_LIT = 0x089F24FC # its literal, used at 0x089F2418 only\nCYCLIC = 0x089F9610 # the DSP manager's cyclic block (0x11837BC0)\nCYCLIC_LIT = 0x089F31F4 # its literal, used at 0x089F309A only\nBACK_TICKS = 2 # the CUE back this long after silent 2 (he_mute)\nMUTE_TICKS = 8 # silent this long at least after the CUE back (he_mute)\nMUTE_MAX = 100 # and 1 s at most\nPRELOAD_TICKS = 10 # the deck stands this long (0.1 s; the DSP's CUE row is checked too)\nPRELOAD_NEAR = 8 # within this many 1/150 s of its CUE point\nPEND_EVERY = 10 # a recall's PLAY again every 0.1 s ...\n# a hot cue set here gets a colour on the USB (the DB task's PCP2 writer,\n# through its only literal) and on the screen: per number, rekordbox's\n# (colour code, R, G, B) pairs as on a rekordbox stick\nPCP2_LIT = 0x08B0FD04\nPCP2 = 0x08B10BE0 # (r4 entry out, r5 cue): +4 hot cue number, +52 extra length, +56 extra\nPCBUF_LEN = 0x60\nPCBLOB_LEN = 28\nPCBLOB_USED = 26\nHOT_COLOURS = [(0x13, 0x00, 0xFF, 0x30), (0x26, 0xFF, 0x5E, 0x00), (0x08, 0x00, 0xC4, 0xFF),\n (0x3C, 0x4D, 0x00, 0xFF), (0x13, 0x00, 0xFF, 0x30), (0x17, 0x33, 0xFF, 0x00),\n (0x2B, 0xFF, 0x00, 0x17), (0x26, 0xFF, 0x5E, 0x00)] # A green, B orange, C blue, ...\n# hot cues kept on the USB: the UI's request sender, and the DB task's cue\n# handlers (its table 0x0899313C: +0x10 register, +0x1C delete)\nUI_SENDER = 0x08B3B03C # (request, device, 0, 0); MEMORY sends 0x7D6, DELETE 0x7D7\nDB_REQ_REG = 0x7D6\nUI_DEVICE = 0x09944900 # word: the device MEMORY sends (0x00010300 with the USB)\nUI_DB_BUSY = 0x0B7C144C # word: a request of the UI's under way\nUI_DB_MODE = 0x08AFCF58 # () -> 1: no cue requests now\nUI_DB_WHICH = 0x08AFD940 # () -> 1 or not: the argument of ...\nUI_DB_READY = 0x08A26340 # (player context, 0/1) -> 1: the track takes cues\nUI_MEDIA = 0x08BAF3F4 # () -> 4: a USB\nUSB_STATE = 0x0A4DE78C # word: the USB port, 3 = a stick mounted (0 once it is gone)\nSTATUS_TAB = 0x0A4F05C8 # every player's last status (player n at + n * STATUS_LEN, words little-endian)\nSTATUS_LEN = 0x11C\nSTATUS_BUF = 0x0B7C9634 # the Pro DJ Link status packet being built (+0x21 this\n # player, +0x28/+0x29 the loaded track's player and slot)\nUI_USB_OK = 0x0A4DE794 + 0x2E0 # word: 1 = cues may be kept on it\nST4 = ST2 + 128 # the USB queue (64 bytes), then the box pixels\nDBQ_N = 6\nDBQ_TICKS = 1000 # a request not taken in 10 s is let go (a USB stick\n # that still writes the last one takes a while)\nDB_REG_LIT = 0x0899314C\nDB_REG = 0x08979D34 # -> dbcl_RegUdbSdCue 0x089764EC\nDB_DEL = 0x0897A0A6 # -> dbcl_DelUsbSdCue 0x08976758\nINJ_WAIT = 10 # a recall from a VINYL pause: the PLAY key 0.1 s after the CUE\nINJ_UNTIL = 14 # ... held 0.04 s\nPEND_TICKS = 250 # ... until the deck plays, 2.5 s at most (a load on the player takes a while)\nLOAD_TICKS = 400 # a hot cue load given up after 4 s\n# The CUE point (= loop in) in 1/150 s: player context +0x41C. The player\n# keeps the audio there ready, so CUE and RELOOP go to it at once; a CALL\n# moves it to the memory (or hot cue) called.\nCUE_POS = PLAYER_CTX + 0x41C\n\n# the setting is kept in the firmware's settings journal in flash: (id, value)\n# pairs of 16-bit words, written by the Backup task on a type-5 message\nJOURNAL = 0xA07F6000 # flash sector 0x7F6000, 8 KiB, read uncached\nJOURNAL_LEN = 0x2000\nSAVE_ID = 0x83E1 # ours; the firmware's are 0x7001-0x9101\nBACKUP_POOL = 0x0A11D7A4 # -> the Backup task's message pool\nBACKUP_MBX = 0x0A11D7A0 # -> its mailbox\nGET_BLOCK = 0x08C0D6DC # (pool, &block, 0) -> 0 ok\nSEND = 0x08C0BA50 # (mailbox, block) -> 0 ok\nRELEASE = 0x08C0D814 # (pool, block)\nMEMSET = 0x08C28038 # (dst, byte, len)\n\nUT_TABLE = 0x08C6C8DC # UTILITY items in the image (.data)\nUT_ITEMS = 17\nUT_ITEM = 52\nUT_COUNT = 0x08C6CC50 # their count, right after them\nUT_RAM = 0x11106D1C # where start-up copies them\nUT_LITS = (0x08BDA97C, 0x08BDACA8, 0x08BDAFD4, 0x08BDB06C)\nONOFF_LIST = 0x11106478 # OFF, ON (SCREEN SAVER's list, still in RAM)\nSHOW_SAVER = 0x08BD9C7C # SCREEN SAVER's +28\nGENERIC = (0x08BD912A, 0x08BD9F44, 0x08BDA04A, 0x08BDA658, 0x08BDA6F2) # +24 +32 +36 +44 +48\n\nDECODER_LIT = 0x08B622A8 # the UI poller's literal for the panel decoder\nDECODER = 0x08B63278\nKEY_QUANTIZE = 0x0B2E82CB # bit 0x80\nTICK = 0x0B3252A8 # 10 ms\n\nDRAW_METHOD = 0x080B27DC # bitmap widgets' draw method\nDRAW = 0x08B2F12A\nDRAW_RES_LIT = 0x08B2F384 # its literal for the resource resolver\nRESOLVE = 0x08B2E2E4\nRES_TABLE = 0x00100000 # resource records in the image (offset), 44 bytes\nRES_QUANTIZE = 0x1A8 # 88 x 15, red\nRES_QUANTIZE_OFF = 0x1A9 # its grey outline\nRES_ANCHOR = 0x12A # REMAIN: its siblings hold the QUANTIZE widget\nRES_DIGITS = (0xCC, 0xDF) # the time display's digits, drawn in REMAIN and ELAPSED\nRES_HK = 0x7F00 # ours, only hk_res knows it\nBOX_DX = 92\n\nLABEL = 'HOT CUE KEYS'\n\n# 7 x 7 letters for HOT KEYS, in the QUANTIZE label's style\nGLYPHS = {\n 'H': ['X.....X', 'X.....X', 'X.....X', 'XXXXXXX', 'X.....X', 'X.....X', 'X.....X'],\n 'O': ['.XXXXX.', 'X.....X', 'X.....X', 'X.....X', 'X.....X', 'X.....X', '.XXXXX.'],\n 'T': ['XXXXXXX', '...X...', '...X...', '...X...', '...X...', '...X...', '...X...'],\n 'K': ['X....X.', 'X...X..', 'X..X...', 'XXX....', 'X..X...', 'X...X..', 'X....X.'],\n 'E': ['XXXXXX', 'X.....', 'X.....', 'XXXXX.', 'X.....', 'X.....', 'XXXXXX'],\n 'Y': ['X.....X', '.X...X.', '..X.X..', '...X...', '...X...', '...X...', '...X...'],\n 'S': ['.XXXXX.', 'X......', 'X......', '.XXXXX.', '......X', '......X', '.XXXXX.'],\n 'L': ['X.....', 'X.....', 'X.....', 'X.....', 'X.....', 'X.....', 'XXXXXX'],\n 'A': ['..XXX..', '.X...X.', 'X.....X', 'X.....X', 'XXXXXXX', 'X.....X', 'X.....X'],\n 'D': ['XXXXX..', 'X....X.', 'X.....X', 'X.....X', 'X.....X', 'X....X.', 'XXXXX..'],\n 'I': ['XXX', '.X.', '.X.', '.X.', '.X.', '.X.', 'XXX'],\n 'N': ['X.....X', 'XX....X', 'X.X...X', 'X..X..X', 'X...X.X', 'X....XX', 'X.....X'],\n 'G': ['.XXXXX.', 'X......', 'X......', 'X...XXX', 'X.....X', 'X.....X', '.XXXXX.'],\n}\n\n\ndef box_pixels(stock, res, background, ink, text='HOT KEYS'):\n \"\"\"88 x 15 RGB565: resource `res` of the stock image with its text taken\n out and `text` written in `ink`.\"\"\"\n rec = RES_TABLE + res * 44\n w, h = struct.unpack_from(' HK_SETTING, +1 mode, +2 press, +3 loaded, +4 t0\n mov.b @(3,r1),r0\n tst r0,r0\n bf hp_loaded\n mov.l P_load,r2 ; the first time: the saved setting (r1 is\n jsr @r2 ; kept: hk_load takes it, r2 it sets anew)\n nop\nhp_loaded:\n mov.b @r1,r2\n tst r2,r2\n bt hp_off\n mov.l P_key,r3\n mov.b @r3,r2\n extu.b r2,r2\n mov.l P_tick,r6\n mov.l @r6,r6 ; now\n mov.b @(2,r1),r0\n extu.b r0,r0\n cmp/eq #2,r0\n bt hp_long\n cmp/eq #1,r0\n bt hp_held\n ; idle: a press starts\n mov r2,r0\n tst #128,r0\n bt hp_ret\n mov #1,r0\n mov.b r0,@(2,r1)\n mov.l r6,@(4,r1)\n bra hp_hide\n nop\nhp_held:\n mov r2,r0\n tst #128,r0\n bt hp_short ; let go before LONG_TICKS\n mov.l @(4,r1),r0\n sub r0,r6\n mov #LONG_TICKS,r0\n cmp/hs r0,r6\n bf hp_hide ; still short\n mov #2,r0 ; long: QUANTIZE from now on\n bra hp_ret\n mov.b r0,@(2,r1)\nhp_short:\n mov.b @(1,r1),r0 ; the mode flips\n xor #1,r0\n mov.b r0,@(1,r1)\n mov #0,r0\n mov.b r0,@(2,r1)\n mov.l @(8,r1),r4 ; and the screen is redrawn, as the widgets'\nhp_root: ; visibility setter (0x08B2AFB4) asks for it:\n tst r4,r4 ; the root (type 20) gets +0x58 = 1\n bt hp_ret\n mov.b @(4,r4),r0\n cmp/eq #20,r0\n bt hp_mark\n bra hp_root\n mov.l @(8,r4),r4\nhp_mark:\n mov #1,r2\n mov #88,r0\n bra hp_ret\n mov.l r2,@(r0,r4)\nhp_long:\n mov r2,r0\n tst #128,r0\n bf hp_ret ; still held: the deck has it\n mov #0,r0\n bra hp_ret\n mov.b r0,@(2,r1)\nhp_hide:\n mov r2,r0\n and #127,r0\n mov.b r0,@r3\nhp_ret:\n bsr hk_keys ; the five beat keys, in the hot cue key mode\n nop\n mov r7,r0\n lds.l @r15+,pr\n rts\n nop\nhp_off:\n mov #0,r0 ; item OFF: no press of ours is under way\n bra hp_ret\n mov.b r0,@(2,r1)\n\n\n .align 4\nP_key: .long KEY_QUANTIZE\nP_prev: .long PREV_DECODER\nP_setting: .long HK_SETTING\nP_tick: .long TICK\nP_load: .long hk_load\n\n; ---- hk_keys: with the mode on, the five BEAT DIVIDE keys are ours (hidden\n; from the deck): a press recalls its hot cue, and the LEDs show which keys\n; have one (hk_send puts the levels in the frame while +13 is 1). r7 is kept.\nhk_keys:\n sts.l pr,@-r15\n mov.l r7,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l K_setting,r10\n bsr hk_resume ; a recall waiting to play\n nop\n bsr hk_engine ; the hot cue slots: preload, loads under way\n nop\n mov.b @r10,r0\n mov r0,r1\n mov.b @(1,r10),r0\n and r1,r0 ; item ON and the mode on\n tst r0,r0\n bf hkk_on\n bra hkk_off\n nop\nhkk_on:\n ; DELETE: hidden while held; a hot cue key pressed meanwhile loses its\n ; hot cue; let go without one, the deck gets the press then (one poll)\n mov.l K_keydel,r3\n mov.b @r3,r2\n mov r10,r1\n add #22,r1 ; HK_DEL\n mov.b @r1,r0\n extu.b r0,r7\n mov r7,r0\n cmp/eq #3,r0\n bt hkd_clr ; passed on last poll: done\n mov r2,r0\n tst #DEL_BIT,r0\n bt hkd_up\n and #DEL_KEEP,r0\n mov.b r0,@r3 ; the deck does not see it\n tst r7,r7\n bf hkd_done\n mov #1,r0 ; held\n bra hkd_done\n mov.b r0,@r1\nhkd_up:\n mov r7,r0\n cmp/eq #1,r0\n bf hkd_clr\n mov r2,r0 ; let go, no hot cue deleted: the deck's press\n or #DEL_BIT,r0\n mov.b r0,@r3\n mov #3,r0\n bra hkd_done\n mov.b r0,@r1\nhkd_clr:\n mov #0,r0\n mov.b r0,@r1\nhkd_done:\n mov.l K_beats,r3\n mov.b @r3,r2\n extu.b r2,r2\n mov r2,r0\n and #BEAT_BITS,r0\n mov r0,r4 ; held now\n mov r2,r0\n and #HIDE_MASK,r0\n mov.b r0,@r3 ; the deck does not see them\n mov.b @(12,r10),r0\n extu.b r0,r5\n mov r4,r0\n mov.b r0,@(12,r10)\n not r5,r5\n and r4,r5 ; pressed since the last poll\n mov.l K_st2,r1 ; none while hot cues load (steps 1-6): the\n mov.b @(1,r1),r0 ; keys are locked as PLAY and CUE are\n extu.b r0,r0\n add #-1,r0\n mov #6,r1\n cmp/hs r1,r0\n bt hkk_free\n mov #0,r5\nhkk_free:\n mov #-128,r6\n extu.b r6,r6 ; 0x80 = A\n mov.l K_hot,r8 ; its hot cue: +0 time in 1/150 s, -1 none\n mov #1,r9 ; key number, A = 1\nhkk_key:\n tst r6,r5\n bt hkk_next\n mov #HOT_KEYS,r0 ; 1/4 and 1/8 (D, E): nothing\n cmp/hi r0,r9\n bt hkk_next\n mov r10,r1\n add #22,r1\n mov.b @r1,r0 ; DELETE held (1, or 2 once used)?\n cmp/eq #1,r0\n bt hkk_del\n cmp/eq #2,r0\n bf hkk_cue\nhkk_del:\n mov #2,r0\n mov.b r0,@r1\n mov.l @r8,r0 ; one there: deleted on the USB too\n cmp/eq #-1,r0\n bt hkk_del2\n mov.l r5,@-r15\n mov.l r6,@-r15\n mov r0,r6\n mov #2,r4\n bsr hk_dbop\n mov r9,r5\n mov.l @r15+,r6\n mov.l @r15+,r5\nhkk_del2:\n mov #-1,r0\n mov.l r0,@r8 ; no hot cue on this key now\n mov.l K_mkdirty,r1 ; the waveforms' markers made again\n mov #1,r0\n mov.l r0,@r1\n mov r9,r0\n mov.b r0,@(15,r10) ; it blinks, then stays off\n mov.l K_tick,r1\n mov.l @r1,r1\n bra hkk_next\n mov.l r1,@(24,r10)\nhkk_cue:\n mov.l @r8,r4\n mov r4,r0\n cmp/eq #-1,r0\n bf hkk_call\n mov r10,r1 ; none: held long while paused, it is set\n add #21,r1\n mov r9,r0\n mov.b r0,@r1\n mov.l K_tick,r1\n mov.l @r1,r1\n bra hkk_next\n mov.l r1,@(28,r10)\nhkk_call:\n mov r9,r0 ; the first one pressed with a hot cue:\n mov.b r0,@(15,r10) ; its LED blinks from now\n mov r10,r1\n add #23,r1\n mov.b r0,@r1 ; and its release is watched (gate)\n mov.l K_tick,r1\n mov.l @r1,r1\n bsr hk_hot ; r4 = its time, r9 = the key\n mov.l r1,@(24,r10)\n bra hkk_leds\n nop\nhkk_next:\n add #8,r8\n add #1,r9\n shlr r6\n mov #4,r0\n cmp/hi r0,r6\n bt hkk_key ; 0x80 down to 0x08\n ; gate: the recalled key let go after GATE_TICKS: back to its hot cue,\n ; standing there (a CALL while playing stops at the cue)\n mov r10,r1\n add #23,r1\n mov.b @r1,r0\n extu.b r0,r9 ; the key, A = 1, 0 none\n tst r9,r9\n bt hkk_hold\n mov #-128,r6\n extu.b r6,r6\n mov #1,r0\n sub r9,r0\n shld r0,r6 ; its bit\n mov.b @(12,r10),r0\n tst r6,r0\n bf hkk_hold ; still held\n mov #0,r0\n mov.b r0,@r1\n mov.l K_tick,r4\n mov.l @r4,r4\n mov.l @(24,r10),r0\n sub r0,r4\n mov #GATE_TICKS,r0\n cmp/hs r0,r4\n bf hkk_hold ; a short press: it plays on\n mov.l K_hot,r8\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r8),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt hkk_hold\n bsr hk_gate ; r4 = its time, r9 = the key\n nop\nhkk_hold:\n ; a key without a hot cue held: at LONG_TICKS, paused, it gets one here\n mov r10,r1\n add #21,r1\n mov.b @r1,r0\n extu.b r0,r9 ; the key, A = 1, 0 none\n tst r9,r9\n bt hkk_leds\n mov #-128,r6\n extu.b r6,r6\n mov #1,r0\n sub r9,r0\n shld r0,r6 ; its bit: 0x80 >> (key - 1)\n mov.b @(12,r10),r0\n tst r6,r0\n bt hkk_drop ; let go before\n mov.l K_tick,r4\n mov.l @r4,r4\n mov.l @(28,r10),r0\n sub r0,r4\n mov #LONG_TICKS,r0\n cmp/hs r0,r4\n bf hkk_leds ; not yet\n mov.l K_ctx,r1\n mov #67,r0\n shll2 r0 ; 0x10C: play state\n mov.l @(r0,r1),r0\n cmp/eq #1,r0\n bt hkk_drop ; playing: nothing\n mov #-79,r0\n extu.b r0,r0\n shll2 r0 ; 0x2C4: the position in 1/150 s\n mov.l @(r0,r1),r4\n mov.l K_hot,r8\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n add r0,r8 ; the key's entry\n mov.l r4,@r8\n mov.l K_hotcol,r0 ; the colour it gets on the USB too\n add r9,r0\n add r9,r0\n add #-2,r0\n mov.w @r0,r0\n mov.w r0,@(4,r8)\n mov r9,r0\n mov.b r0,@(6,r8)\n mov.b r0,@(15,r10) ; blinks\n mov.l K_mkdirty,r1 ; the waveforms' markers made again\n mov #1,r0\n mov.l r0,@r1\n mov.l K_tick,r1\n mov.l @r1,r1\n mov.l r1,@(24,r10)\n mov.l @r8,r6 ; and saved on the USB\n mov #1,r4\n bsr hk_dbop\n mov r9,r5\n mov.l K_st4,r1 ; and loaded into its slot right away (he_step)\n add #61,r1\n mov r9,r0\n mov.b r0,@r1\nhkk_drop:\n mov r10,r1\n add #21,r1\n mov #0,r0\n mov.b r0,@r1\nhkk_leds:\n ; LED levels: 0 no hot cue, 1 one; the key blinking: 3 1 3 1, 0.1 s each\n mov.l K_tick,r4\n mov.l @r4,r4\n mov.l @(24,r10),r0\n sub r0,r4 ; since the press\n mov.b @(15,r10),r0\n extu.b r0,r7 ; the key blinking, 0 none\n mov #BLINK_TICKS,r0\n cmp/hs r0,r4\n bf hkk_bl\n mov #0,r7 ; done\n mov #0,r0\n mov.b r0,@(15,r10)\nhkk_bl:\n mov #3,r8 ; bright in the 1st and 3rd tenth of a second\n mov #10,r0\n cmp/hs r0,r4\n bf hkk_lv\n mov #1,r8\n mov #20,r0\n cmp/hs r0,r4\n bf hkk_lv\n mov #3,r8\n mov #30,r0\n cmp/hs r0,r4\n bf hkk_gl\n mov #1,r8\nhkk_gl:\n ; a gate: its key held GATE_TICKS or longer (let go, the deck goes back\n ; to the hot cue) blinks slowly, bright and dim, until it is let go\n mov r10,r1\n add #23,r1\n mov.b @r1,r0\n extu.b r0,r1 ; the gate's key, 0 none\n tst r1,r1\n bt hkk_lv\n mov #GATE_TICKS,r0\n cmp/hs r0,r4\n bf hkk_lv\n mov #-128,r2\n extu.b r2,r2\n mov #1,r0\n sub r1,r0\n shld r0,r2 ; its bit: 0x80 >> (key - 1)\n mov.b @(12,r10),r0\n tst r2,r0\n bt hkk_lv ; let go already\n mov r1,r7\n mov.l K_tick,r0\n mov.l @r0,r0\n tst #32,r0\n bt/s hkk_lv\n mov #3,r8\n mov #1,r8\nhkk_lv:\n mov.l K_hot,r2\n mov r10,r3\n add #16,r3 ; the levels for hk_send\n mov #1,r9\n mov #5,r6\nhkk_led:\n mov.l @r2,r0\n cmp/eq #-1,r0\n bt hkk_none\n mov.l r2,@-r15 ; a hot cue: dim when its slot holds it,\n mov.l r3,@-r15 ; else blinking slowly (not ready yet)\n mov r0,r4\n bsr he_ledlv\n mov r9,r5\n mov.l @r15+,r3\n bra hkk_lvl\n mov.l @r15+,r2\nhkk_none:\n mov #0,r0\nhkk_lvl:\n cmp/eq r9,r7\n bf hkk_st\n mov r8,r0\nhkk_st:\n mov #HOT_KEYS,r1 ; D and E stay dark\n cmp/hi r1,r9\n bf hkk_st2\n mov #0,r0\nhkk_st2:\n mov.b r0,@r3\n add #8,r2\n add #1,r3\n dt r6\n bf/s hkk_led\n add #1,r9\n bra hkk_out\n mov #1,r0\nhkk_off:\n mov #0,r0 ; the mode is off: the LEDs are the deck's\n mov.b r0,@(12,r10)\n mov r10,r1\n add #22,r1\n mov.b r0,@r1 ; no DELETE or gate of ours under way\n mov.b r0,@(1,r1)\nhkk_out:\n mov.b r0,@(13,r10)\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n mov.l @r15+,r7\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_recall: r4 = a hot cue's time in 1/150 s. The memory CALL, as the\n; CALL key sends it (handler 0x08B4F2F2, event 0x100), with the time in ms\n; put where the handler's message builder reads the memory's: the UI's CALL\n; record +4 (and +8 = -1, +12 the time in 1/150 s, as the UI leaves them).\n; The deck then stands at the cue; hk_resume starts it if r5 is 1.\n; hk_callat: the CALL always (a beat jump's: right after a CALL the CUE point\n; word still holds the one before, which may be this target).\nhk_callat:\n sts.l pr,@-r15\n bra hrc_call\n nop\nhk_recall:\n sts.l pr,@-r15\n mov.l K_cuepos,r1 ; the CUE point is this hot cue already (a CALL\n mov.l @r1,r0 ; leaves it there): CUE goes to it at once, as\n cmp/eq r4,r0 ; the CUE key does while playing, then PLAY\n bf hrc_call\n mov.l r5,@-r15\n bsr hk_cue\n nop\n mov.l @r15+,r0\n tst r0,r0\n bt hrc_out ; a gate's release: standing there is it\n bsr hr_play\n nop\n bra hrc_out\n nop\nhrc_call:\n mov r5,r0\n mov.b r0,@(14,r10) ; 1: play once it stands at the cue\n mov.l K_callrec,r2\n mov.l r4,@(12,r2)\n mov r4,r1\n shll2 r1\n mov r1,r3\n shll2 r3\n add r3,r1 ; 20 * units\n add #2,r1 ; rounded up, so the builder gets the units back\n mov.l K_third,r3\n dmulu.l r3,r1\n sts mach,r1\n shlr r1 ; / 3: ms\n mov.l r1,@(4,r2)\n mov #-1,r0\n mov.l r0,@(8,r2)\n mov #1,r4\n shll8 r4 ; 0x100: CALL\n mov.l r4,@r2\n mov.l K_callkey,r1\n jsr @r1\n nop\n mov.l K_fpmask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\nhrc_out:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_cue: what the CUE key sends while playing (0x08B47250-0x08B47290):\n; 0x0AF3AF28 = 7, 0x0AF3AEEC and +4 = 0, command 3, 2, 0, 1 with the byte at\n; 0x0AF3AF3C on the stack. The deck goes back to its CUE point and stands.\nhk_cue:\n sts.l pr,@-r15\n mov #0,r0\n mov.b r0,@(14,r10) ; no PLAY of a CALL pending\n mov.l K_af28,r1\n mov #7,r0\n mov.l r0,@r1\n mov.l K_aeec,r1\n mov #0,r0\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov.l K_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #3,r4\n mov #2,r5\n mov.l K_build,r1\n jsr @r1\n mov #1,r7\n add #8,r15\n mov.l K_fpmask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_resume: r10 -> HK_SETTING. After a recall the player stops at the\n; cue (state 0x17 at context +0x10C); then PLAY goes as the PLAY key's\n; handler sends it from cue standby (0x08B49834): 0x0AF3AF28 = 3, command 1,\n; 2 (1 if 0x0AF3B1D3 is 1), the byte at 0x0AF3AF3C on the stack.\nhk_resume:\n mov.b @(14,r10),r0\n extu.b r0,r0\n tst r0,r0\n bt hr_none\n mov.l K_ctx,r1\n mov #67,r0\n shll2 r0 ; 0x10C\n mov.l @(r0,r1),r0\n cmp/eq #23,r0\n bt hr_play\n mov.b @(14,r10),r0 ; not yet: give up after RESUME_POLLS\n add #1,r0\n cmp/eq #RESUME_POLLS,r0\n bf hr_keep\n mov #0,r0\nhr_keep:\n rts\n mov.b r0,@(14,r10)\nhr_play:\n sts.l pr,@-r15\n mov #0,r0\n mov.b r0,@(14,r10)\n mov.l K_af28,r1\n mov #3,r0\n mov.l r0,@r1\n mov.l K_b1d3,r1\n mov.b @r1,r0\n cmp/eq #1,r0\n bt/s hr_sub\n mov #1,r5\n mov #2,r5\nhr_sub:\n mov.l K_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #1,r4\n mov.l K_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n mov.l K_fpmask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\n lds.l @r15+,pr\nhr_none:\n rts\n nop\n\n .align 4\nK_af28: .long 0x0AF3AF28\nK_af3c: .long 0x0AF3AF3C\nK_b1d3: .long 0x0AF3B1D3\nK_beats: .long KEY_BEATS\nK_build: .long BUILD\nK_callkey: .long CALL_KEY\nK_callrec: .long CALL_REC\nK_ctx: .long PLAYER_CTX\nK_fpmask: .long 0xFFE7FFFF\nK_hot: .long HOT\nK_cuepos: .long CUE_POS\nK_aeec: .long 0x0AF3AEEC\nK_keydel: .long KEY_DELETE\nK_mkdirty: .long MARKERS_DIRTY\nK_green: .long GREEN\nK_hotcol: .long HOTCOL\nK_key: .long KEY_QUANTIZE\nK_prev: .long PREV_DECODER\nK_setting: .long HK_SETTING\nK_third: .long 0xAAAAAAAB\nK_tick: .long TICK\nK_st4: .long ST4\nK_st2: .long ST2\n\n\n; ==== the hot cue engine ====================================================\n; The firmware has the CDJ-2000NXS's hot cues, unused on the 900NXS: the DSP\n; keeps up to three hot cue segments (DSP slots 1-3, beside slot 0, the CUE\n; point), decoded and ready, and plays one at once:\n; load: CALL (commands 22, 23) with slot 4+s and the UI's CALL record\n; event (3+s) << 16 | 0x100 (0x100: load), the time as for a\n; memory CALL. The player loads DSP slot s and plays from it.\n; recall: CUE (command 3) with slot 4+s, then PLAY (command 1) with slot\n; 4+s: the deck is at the hot cue and plays at once, the stream\n; carried on behind it as after CUE and PLAY.\n; After a track loads (cuecolours counts its loads at HOT+7) hot cues A-C\n; are loaded silently (hk_wrap_tr and hk_wrap_pc keep the player's PLAY from\n; the DSP meanwhile), then CUE takes the deck back to its CUE point at once.\n; A hot cue set later loads into its slot on its first press.\n; ST2: +0 loads seen, +1 step (0 idle, 0x80 waiting to preload, 1-3 loading\n; slot 1-3, 4 back, 5 going back), +2 silent, +3 busy (0, slot 1-3, 9 the\n; CALL back), +4 PLAY pressed meanwhile, +5 use counter, +6..+8 slot 1-3's\n; last use, +9..+11 slot 1-3's key (0 none), +12 tick the load began, +16\n; its target (1/150 s), +20 the position to go back to, +24/+28/+32 slot\n; 1-3's time, +36 1 once a preload moved the deck, +40 the position last\n; poll, +44 the key of the load under way, +48 the slot a gate left the deck\n; standing at (0 none), +52 the PLAY bit we took, hidden until let go, +56\n; the tick the gate parked it, +60 a key pressed during a load (0 none).\n\n; ---- hk_engine: r10 -> HK_SETTING, every poll\nhk_engine:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r11,@-r15\n mov.l H_st2,r11\n mov.l H_hot,r1 ; a new load?\n mov.b @(7,r1),r0\n mov r0,r2\n mov.b @r11,r0\n cmp/eq r2,r0\n bt he_same\n mov r2,r0\n mov.b r0,@r11\n mov #0,r0\n mov.b r0,@(2,r11) ; not silent\n mov.b r0,@(3,r11) ; nothing under way\n mov.b r0,@(4,r11)\n mov.b r0,@(9,r11) ; no slot holds anything\n mov.b r0,@(10,r11)\n mov.b r0,@(11,r11)\n mov.l r0,@(36,r11)\n mov.l r0,@(48,r11) ; not parked\n mov.l r0,@(60,r11) ; no key waiting\n mov r11,r1\n add #64,r1\n mov.l r0,@r1 ; no beat jump waiting\n mov.l r0,@(20,r1) ; speed: measured again\n mov.l r0,@(24,r1)\n mov.l r0,@(44,r1) ; no PLAY to repeat\n add #64,r1\n mov.l r0,@r1 ; nothing queued for the USB (another track)\n mov.l H_bjx,r1 ; no standing jump waiting either\n mov.l r0,@(32,r1)\n mov.l r0,@(36,r1)\n mov.l r0,@(40,r1)\n mov #-128,r0\n mov.b r0,@(1,r11) ; preload once the deck stands\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov r11,r1 ; ST4 +56: the tick it loaded (he_wv)\n add #127,r1\n add #57,r1\n mov.l r0,@r1\nhe_same:\n bsr he_gone ; the track's USB stick gone: its hot cues too\n nop\n mov.l H_lock,r0 ; loading: the deck's keys locked, LOADING\n jsr @r0\n nop\n mov.l H_shadow,r0 ; the CALL the player reports, unless held\n jsr @r0\n nop\n bsr he_uf ; the UI's position held while preloading\n nop\n bsr he_wv ; the big waveform hidden meanwhile\n nop\n bsr he_busy ; a load or the CALL back under way\n nop\n bsr he_step ; the preload\n nop\n bsr he_mute ; silent until the deck stands again\n nop\n bsr he_rate ; the deck's speed\n nop\n bsr he_pend ; a recall's PLAY, again until it plays\n nop\n bsr he_inj ; a recall from a VINYL pause: the PLAY key\n nop\n bsr he_dbq ; hot cues set or deleted, to the USB\n nop\n ; a PLAY we took stays hidden from the deck until it is let go\n mov.l H_keypc,r3\n mov.l @(52,r11),r0\n tst r0,r0\n bt he_park\n mov r0,r2\n mov.b @r3,r0\n tst r2,r0\n bf he_hide\n mov #0,r0\n bra he_park\n mov.l r0,@(52,r11)\nhe_hide:\n not r2,r2\n and r2,r0\n mov.b r0,@r3\nhe_park:\n ; a gate left the deck standing at slot s's hot cue: PLAY plays slot s\n ; (the deck's own PLAY would start from the CUE point)\n mov.l @(48,r11),r0\n tst r0,r0\n bt he_pre\n mov r0,r9\n bsr he_now\n nop\n mov.l @(56,r11),r1\n sub r1,r0\n mov r0,r8 ; ticks since it parked\n bsr he_pos\n nop\n mov r0,r2\n mov r9,r0\n shll2 r0\n add #20,r0\n mov.l @(r0,r11),r0\n cmp/eq r2,r0\n bf he_away\n mov r1,r0\n cmp/eq #1,r0\n bf he_parked ; standing at it\nhe_away:\n mov #50,r1 ; on its way for 0.5 s; then playing or moved\n cmp/hs r1,r8 ; on: the deck's PLAY again\n bt he_unpark\n bra he_pre\n nop\nhe_parked:\n mov.l H_keypc,r3\n mov.b @r3,r0\n tst #4,r0\n bt he_pre\n and #0xFB,r0\n mov.b r0,@r3\n mov #4,r0\n mov.l r0,@(52,r11)\n bsr he_play\n mov r9,r5\nhe_unpark:\n mov #0,r0\n mov.l r0,@(48,r11)\nhe_pre:\n mov.l H_djw,r0 ; a DSP jump's result\n jsr @r0\n nop\n bsr he_bj ; a beat jump\n nop\n ; a key pressed while a load was under way: now\n mov.l @(60,r11),r0\n tst r0,r0\n bt he_nq\n mov r0,r9\n mov.b @(3,r11),r0\n tst r0,r0\n bf he_nq\n mov.b @(1,r11),r0\n extu.b r0,r0\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bt he_q\n tst r0,r0\n bf he_nq\nhe_q:\n mov #0,r0\n mov.l r0,@(60,r11)\n mov.l H_hot,r1\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r1),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt he_nq\n bsr hk_hot\n nop\nhe_nq:\n ; while preloading, the deck's PLAY and CUE do nothing (locked)\n mov.b @(1,r11),r0\n extu.b r0,r0\n tst r0,r0\n bt he_out\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bt he_out\n mov.l H_keypc,r1\n mov.b @r1,r0\n and #0xF9,r0 ; PLAY 0x04, CUE 0x02 hidden\n mov.b r0,@r1\nhe_out:\n mov.l @r15+,r11\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nH_st2: .long ST2\nH_hot: .long HOT\nH_keypc: .long KEY_PLAYCUE\nH_djw: .long he_djw\nH_shadow: .long he_shadow\nH_lock: .long he_lock\nH_bjx: .long BJX\n\n; ---- he_gone: r11 -> ST2. The loaded track's USB stick gone (USB STOP, pulled\n; out): its hot cues go with it, they would play the little the DSP still\n; holds and then EMERGENCY LOOP. HOT_TABLE emptied, no slot holds anything,\n; no preload, the markers drawn again. Where the track is from is learnt once\n; per load (GONE_ST: +0 0 not yet, 1 this player's USB, 0x100 | n player n's\n; USB over LINK; +4 the load it is for) from UI_DEVICE (player << 16 | slot\n; << 8, slot 3 a USB) and the status packet's +0x21 (this player; 0 without\n; a LAN cable, the packet is not built then: the track is ours). Gone: ours\n; once USB_STATE is no longer 3; player n's once its last status (STATUS_TAB)\n; reports its USB not there (+0x6C not 0, 4 when pulled or stopped).\nhe_gone:\n sts.l pr,@-r15\n mov.l N_gst,r7\n mov.l N_hot,r1 ; a new load: not known yet\n mov.b @(7,r1),r0\n extu.b r0,r0\n mov.l @(4,r7),r2\n cmp/eq r0,r2\n bt hg_known\n mov.l r0,@(4,r7)\n mov #0,r2\n mov.l r2,@r7\nhg_known:\n mov.l @r7,r0\n tst r0,r0\n bf hg_check\n mov.l N_dev,r1 ; the track's device: player << 16 | slot << 8\n mov.l @r1,r3\n mov r3,r0\n shlr8 r0\n extu.b r0,r0\n cmp/eq #3,r0 ; from a USB\n bf hg_ret\n shlr16 r3\n extu.b r3,r3 ; whose\n mov.l N_sbuf,r1\n mov #33,r0\n mov.b @(r0,r1),r2 ; this player\n extu.b r2,r2\n tst r2,r2\n bt hg_mine\n cmp/eq r2,r3\n bf hg_link\nhg_mine:\n mov.l N_usb,r1 ; ours, with the stick there\n mov.l @r1,r0\n cmp/eq #3,r0\n bf hg_ret\n mov #1,r0\n bra hg_ret\n mov.l r0,@r7\nhg_link:\n bsr hg_rec ; player n's, its USB there\n mov r3,r0\n tst r0,r0\n bf hg_ret\n mov #1,r0\n shll8 r0\n or r3,r0\n bra hg_ret\n mov.l r0,@r7\nhg_check:\n cmp/eq #1,r0\n bf hg_chlink\n mov.l N_usb,r1 ; ours: gone?\n mov.l @r1,r0\n cmp/eq #3,r0\n bt hg_ret\n bra hg_clear\n nop\nhg_chlink:\n bsr hg_rec ; player n's: gone?\n extu.b r0,r0\n tst r0,r0\n bt hg_ret\nhg_clear:\n mov.l N_hot,r1\n mov #8,r2\n mov #-1,r3\n mov #0,r4\nhg_loop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt hg_next\n mov.l r3,@r1 ; a hot cue: gone\n mov #1,r4\nhg_next:\n dt r2\n bf/s hg_loop\n add #8,r1\n tst r4,r4\n bt hg_ret ; none left already\n mov #0,r0\n mov.b r0,@(1,r11) ; no preload\n mov.b r0,@(2,r11) ; not silent\n mov.b r0,@(3,r11) ; nothing under way\n mov.b r0,@(9,r11) ; no slot holds anything\n mov.b r0,@(10,r11)\n mov.b r0,@(11,r11)\n mov.l r0,@(48,r11) ; not parked\n mov.l r0,@(60,r11) ; no key waiting\n mov.l N_dirty,r1 ; the markers drawn again\n mov #1,r0\n mov.l r0,@r1\nhg_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; hg_rec: r0 = player n -> r0 = its last status's USB field (+0x6C, low byte; 0 there)\nhg_rec:\n mov.l N_tab,r1\n mov.l N_len,r2\n mulu.w r0,r2\n sts macl,r2\n add r2,r1\n mov #0x6C,r0\n mov.l @(r0,r1),r0\n rts\n extu.b r0,r0\n\n .align 4\nN_tab: .long STATUS_TAB\nN_len: .long STATUS_LEN\nN_sbuf: .long STATUS_BUF\nN_usb: .long USB_STATE\nN_hot: .long HOT\nN_dirty: .long MARKERS_DIRTY\nN_gst: .long GONE_ST\nN_dev: .long UI_DEVICE\n\n; ---- he_pos: r0 = the position (1/150 s), r1 = the play state\nhe_pos:\n mov.l F_ctx,r1\n mov #-79,r0\n extu.b r0,r0\n shll2 r0 ; 0x2C4\n mov.l @(r0,r1),r2\n mov #67,r0\n shll2 r0 ; 0x10C\n mov.l @(r0,r1),r1\n rts\n mov r2,r0\n\n; ---- he_fp: the firmware's fpscr fix after a call (r0 kept)\nhe_fp:\n mov.l F_fpmask,r1\n sts fpscr,r2\n and r1,r2\n rts\n lds r2,fpscr\n\n; ---- he_row: r0 = s -> r0 = s * 0x84, the offset of the DSP's row s (r1 used)\nhe_row:\n mov r0,r1\n shll2 r0\n shll2 r0\n shll2 r0\n shll r0 ; s * 128\n shll2 r1\n rts\n add r1,r0 ; + s * 4\n\n; ---- he_busy: r11 -> ST2. A slot load is done once 0.2 s have passed, the\n; DSP has the slot (its row's word, cleared by he_load, is 1 and its point\n; is the target) and, heard, the deck plays on from it (up to 2 s past it).\n; The way back is done once the deck stands at its target. LOAD_TICKS at\n; most.\nhe_busy:\n sts.l pr,@-r15\n mov.b @(3,r11),r0\n extu.b r0,r9\n tst r9,r9\n bt hb_ret\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov r0,r8 ; ticks since it began\n mov.l F_loadt,r1\n cmp/hs r1,r0\n bf hb_intime\n bra hb_fail\n nop\nhb_intime:\n bsr he_pos\n nop\n mov.l @(16,r11),r2\n sub r2,r0 ; position - target\n mov #9,r3\n cmp/eq r3,r9\n bt hb_back\n mov r0,r2\n mov.b @(2,r11),r0\n tst r0,r0\n bf hb_moving ; silent: the DSP's word alone\n mov r2,r0\n bsr he_bjat ; a beat jump's slot standing at the target\n nop ; (he_play plays it then, hb_bjslot)\n bt hb_moving\n mov.l F_win,r2 ; 0 .. 2 s past it\n cmp/hs r2,r0\n bt hb_ret\n tst r0,r0 ; heard: once it plays on from there (the deck\n bt hb_ret ; takes no CUE or PLAY before)\nhb_moving:\n mov #20,r0\n cmp/hs r0,r8\n bf hb_ret ; not before 0.2 s\n mov #BJ_SLOT,r0 ; a beat jump's slot: the player reports its\n cmp/eq r0,r9 ; point as 0x91, no row word goes to 1; that\n bt hb_loaded ; it plays on from the target is it\n bsr he_row ; the DSP's row s\n mov r9,r0\n mov.l F_slotst,r4\n mov.l F_hpird,r1\n jsr @r1\n add r0,r4\n bsr he_fp\n nop\n cmp/eq #1,r0\n bf hb_ret\n bsr he_row\n mov r9,r0\n mov.l F_slotpt,r4\n mov.l F_hpird,r1\n jsr @r1\n add r0,r4\n bsr he_fp\n nop\n shll r0 ; its point in 1/150 s, rounded down\n mov.l @(16,r11),r1\n sub r0,r1\n mov #3,r0\n cmp/hs r0,r1\n bt hb_ret ; not the target (yet)\nhb_loaded:\n ; loaded: slot s holds the key's hot cue\n mov #BJ_SLOT,r0\n cmp/eq r0,r9\n bt hb_bjslot ; (slot 4: a beat jump's, no hot cue's)\n mov r11,r1\n add #8,r1\n add r9,r1\n mov.l @(44,r11),r0\n and #127,r0\n mov.b r0,@r1 ; its key\n mov r9,r0\n shll2 r0\n add #20,r0\n mov.l @(16,r11),r2\n mov.l r2,@(r0,r11) ; its time\nhb_bjslot:\n bsr he_gap ; a beat jump's: how long it took\n nop\n mov.b @(2,r11),r0 ; heard: PLAY on the slot now that it plays,\n tst r0,r0 ; so the deck is in its playing state (1)\n bf hb_nopl ; again, as after a recall; in 0x17 it takes\n bsr he_play ; no LOOP IN / OUT\n mov r9,r5\n bsr he_bjpend ; and again until it plays\n nop\nhb_nopl:\n mov.b @(1,r11),r0\n extu.b r0,r0\n cmp/eq #6,r0\n bf hb_n6\n mov #0,r0 ; a hot cue just set: loaded; CUE on its slot\n mov.b r0,@(1,r11) ; has the deck stand at it (the player would\n mov #2,r0 ; go on from there), parked; silent 2\n mov.b r0,@(2,r11) ; (he_mute)\n bsr he_cue\n mov r9,r5\n mov.l r9,@(48,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n bra hb_done\n mov.l r0,@(56,r11)\nhb_n6:\n cmp/eq r9,r0\n bf hb_done\n add #1,r0 ; preloading: the next slot\n mov.l @(60,r11),r1\n tst r1,r1\n bt hb_next\n mov #2,r1 ; a key waits: straight back, the rest later\n mov.l r1,@(36,r11)\n mov #4,r0\nhb_next:\n mov.b r0,@(1,r11)\nhb_done:\n mov #0,r0\n mov.b r0,@(3,r11)\nhb_ret:\n lds.l @r15+,pr\n rts\n nop\nhb_back:\n tst r0,r0 ; the CALL back: standing at its target\n bf hb_ret\n mov #23,r0\n cmp/eq r0,r1\n bt hb_back2\n mov #3,r0\n cmp/eq r0,r1\n bf hb_ret\nhb_back2:\n mov #0,r0\n mov.b r0,@(3,r11)\n bsr he_now\n nop\n mov.l F_bjx,r1\n mov.l r0,@(44,r1) ; when it ended: he_bj's next CALL waits BJ_SETTLE\n mov.b @(1,r11),r0\n cmp/eq #5,r0\n bf hb_ret ; a beat jump's CALL\n mov #0,r0\n mov.b r0,@(1,r11) ; the preload is over\n mov.l @(36,r11),r0\n cmp/eq #2,r0\n bf hb_over\n mov #0,r0 ; cut short: again once the deck stands\n mov.l r0,@(36,r11)\n mov #-128,r0\n mov.b r0,@(1,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\nhb_over:\n mov.b @(4,r11),r0\n tst r0,r0\n bt hb_ret\n mov #0,r0\n mov.b r0,@(4,r11)\n mov.b r0,@(2,r11) ; heard now\n mov.l F_play,r1 ; PLAY pressed meanwhile\n jsr @r1\n nop\n bra hb_ret\n nop\nhb_fail:\n mov #0,r0 ; given up: silent off; a preload goes back\n mov.b r0,@(2,r11)\n mov.b r0,@(3,r11)\n mov r11,r1 ; no beat jump to measure\n add #64,r1\n mov.l r0,@(24,r1)\n mov.b @(1,r11),r0\n extu.b r0,r0\n cmp/eq #6,r0\n bt hf_end ; a hot cue just set: on its first press then\n mov #6,r1\n cmp/hs r1,r0\n bt hb_ret ; none under way, or waiting\n mov #4,r1\n cmp/hs r1,r0\n bt hf_end\n tst r0,r0\n bt hb_ret\n mov #4,r0\n bra hb_ret\n mov.b r0,@(1,r11)\nhf_end:\n mov #0,r0\n bra hb_ret\n mov.b r0,@(1,r11)\n\n; ---- he_step: r11 -> ST2. The preload's next step.\nhe_step:\n sts.l pr,@-r15\n mov.b @(1,r11),r0\n extu.b r0,r9\n tst r9,r9\n bf hs2_on\n bra hs2_one\n nop\nhs2_on:\n mov.b @(3,r11),r0\n tst r0,r0\n bt hs2_free\n bra hs2_ret ; something under way\n nop\nhs2_free:\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r9\n bf hs2_load\n ; waiting: the deck must stand in cue standby or paused (not while a\n ; track still loads), within PRELOAD_NEAR of its CUE point, for\n ; PRELOAD_TICKS; the CALL back then goes to the CUE point itself, which\n ; so stays where it was\n bsr he_pos\n nop\n mov r0,r3\n mov.b @r10,r0 ; with the item ON, hot key mode or not\n tst r0,r0\n bt hs2_moved\n mov r1,r0\n cmp/eq #23,r0 ; cue standby or paused\n bt hs2_st\n cmp/eq #3,r0\n bf hs2_moved\nhs2_st:\n mov.l F_cuepos,r1\n mov.l @r1,r1\n mov r3,r0\n sub r1,r0\n add #PRELOAD_NEAR,r0\n mov #PRELOAD_NEAR,r1\n shll r1\n cmp/hi r1,r0\n bt hs2_moved ; not at its CUE point\n mov.l @(40,r11),r0\n mov.l r3,@(40,r11)\n cmp/eq r3,r0\n bt hs2_still\nhs2_moved:\n mov.l r3,@(40,r11)\n bsr he_now ; moved: wait again\n nop\n bra hs2_ret\n mov.l r0,@(12,r11)\nhs2_still:\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov #PRELOAD_TICKS,r1\n cmp/hs r1,r0\n bf hs2_ret\n ; and the track is loaded: the DSP has its CUE point's row again (the\n ; player clears the rows when a track loads; standing at position 0\n ; while the track still loads looks like cue standby)\n mov.l F_slotst,r4\n mov.l F_hpird,r1\n jsr @r1\n nop\n bsr he_fp\n nop\n cmp/eq #1,r0\n bf hs2_moved\n mov.l F_slotpt,r4\n mov.l F_hpird,r1\n jsr @r1\n nop\n bsr he_fp\n nop\n shll r0\n mov.l F_cuepos,r1\n mov.l @r1,r1\n sub r0,r1\n mov #3,r0\n cmp/hs r0,r1\n bt hs2_moved\n mov.l F_cuepos,r1 ; where to go back to: the CUE point\n mov.l @r1,r1\n mov.l r1,@(20,r11)\n mov #1,r0\n bra hs2_ret\n mov.b r0,@(1,r11)\nhs2_load:\n mov r9,r0\n cmp/eq #5,r0\n bt hs2_ret ; going back: he_busy ends it\n cmp/eq #4,r0\n bt hs2_back\n mov #3,r1\n cmp/hi r1,r0\n bt hs2_ret ; (no such step)\n ; slot r9 (1-3) gets hot cue r9 (A-C), if there is one\n mov.l F_hot,r1\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r1),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt hs2_skip\n bsr he_slot ; in a slot already\n mov r9,r5\n tst r0,r0\n bt hs2_go\nhs2_skip:\n mov r9,r0\n add #1,r0\n bra hs2_ret\n mov.b r0,@(1,r11)\nhs2_go:\n mov r11,r1\n add #127,r1\n add #61,r1 ; ST4 + 60: held from before the CALL\n mov #1,r0\n mov.b r0,@r1\n mov.l F_b26d,r1\n mov.b r0,@r1\n mov.b r0,@(2,r11) ; silent\n mov.l r0,@(36,r11) ; the deck will move\n mov r9,r0\n or #-128,r0 ; key = slot, 0x80: the preload's (its LED does\n mov.l r0,@(44,r11) ; not blink, he_ledlv)\n mov r9,r0\n bsr he_use\n nop\n bsr he_load ; r4 = time, r5 = slot\n mov r9,r5\n bra hs2_ret\n nop\nhs2_back:\n mov.b @(2,r11),r0 ; silent 2 (he_mute): the CUE back sends PLAY,\n cmp/eq #2,r0 ; then the hold, a moment later; BACK_TICKS\n bt hs2_b2 ; after it is set\n mov #2,r0\n mov.b r0,@(2,r11)\n bsr he_now\n nop\n bra hs2_ret\n mov.l r0,@(12,r11)\nhs2_b2:\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov #BACK_TICKS,r1\n cmp/hs r1,r0\n bt hs2_b3\n bra hs2_ret\n nop\nhs2_b3:\n mov.l @(36,r11),r0\n tst r0,r0\n bf hs2_call\n mov #0,r0 ; nothing loaded: the deck never moved\n bra hs2_ret\n mov.b r0,@(1,r11)\nhs2_call:\n mov #5,r0\n mov.b r0,@(1,r11)\n mov #9,r0\n mov.b r0,@(3,r11)\n mov.l @(20,r11),r4\n mov.l r4,@(16,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov.l F_cue,r1 ; back to the CUE point at once: its row is\n jsr @r1 ; still loaded (CUE as the CUE key sends it\n nop ; while playing; the deck stands there)\nhs2_ret:\n lds.l @r15+,pr\n rts\n nop\nhs2_one:\n ; a hot cue just set (ST4 +61, its key): loaded silently into its slot\n ; (step 6) while the deck stands, so it is ready at once; the deck then\n ; stands at it, parked (PLAY plays from there, see he_park)\n mov.b @(3,r11),r0\n tst r0,r0\n bf hs2_ret ; something under way\n mov.l F_st4,r3\n add #61,r3\n mov.b @r3,r0\n extu.b r0,r9\n tst r9,r9\n bt hs2_ret\n mov #0,r0\n mov.b r0,@r3\n bsr he_pos\n nop\n mov r1,r0\n cmp/eq #3,r0 ; paused or in cue standby only\n bt hs2_o1\n cmp/eq #23,r0\n bf hs2_ret ; (else on its first press)\nhs2_o1:\n mov.l F_hot,r1\n mov r9,r0\n add #-1,r0\n shll2 r0\n shll r0\n mov.l @(r0,r1),r4\n mov r4,r0\n cmp/eq #-1,r0\n bt hs2_ret\n mov.l r4,@-r15\n bsr he_slot ; in a slot already\n mov r9,r5\n tst r0,r0\n bf/s hs2_ret\n mov.l @r15+,r4\n mov #6,r0\n bra hs2_go\n mov.b r0,@(1,r11)\n\n; ---- he_uf: r11 -> ST2. While the preload runs (steps 1-6) the screen is\n; held at the CUE point (ST4 +60, HOLD_FLAG, is 1): the UI does not take the\n; player's position (0x0AF3B26D set, as the firmware does; see 0x08B39092),\n; so the time and the detail waveform stay; the player's own copies of its\n; position at PLAYER_CTX + 0x2C8 (units) and +0x2CC (ms), which the overview,\n; the phase meter and others read, are kept at their last free values by\n; pub_hook; and the CALL the player reports (UI_AFAC: the overview's line and\n; marker go to that memory) stays at 0 (he_shadow). Nothing moves. Let go,\n; 0x0AF3B22D (the other flag there) is cleared too: our silent CALLs set it,\n; and after a hot cue's single load nothing else would (the screen stood\n; still until the next track).\nhe_uf:\n mov r11,r3\n add #127,r3\n add #61,r3 ; ST4 + 60\n mov.b @(1,r11),r0\n extu.b r0,r0\n add #-1,r0\n mov #6,r1\n cmp/hs r1,r0\n bt huf_off\n mov.l F_b26d,r1\n mov #1,r0\n mov.b r0,@r1\n rts\n mov.b r0,@r3\nhuf_off:\n mov.b @r3,r0\n tst r0,r0\n bt huf_ret\n mov #0,r0\n mov.b r0,@r3\n mov.l F_b26d,r1\n mov.b r0,@r1\n mov.l F_b22d,r1 ; and the CALL flag he_load set as the CALL key\n mov.b r0,@r1 ; does: nothing clears it after a silent load\nhuf_ret:\n rts\n nop\n\n; ---- he_wv: r11 -> ST2. ST4 +62 is 1 while the big (detail) waveform is not\n; drawn (hk_wvmid): after a track load, while the deck waits to preload\n; (WV_TICKS at most, not playing). While the hot cues load it is drawn, held\n; at the CUE point (he_uf).\nhe_wv:\n mov r11,r3\n add #127,r3\n add #63,r3 ; ST4 + 62\n mov.b @(1,r11),r0\n extu.b r0,r0\n mov r0,r2\n add #-1,r0\n mov #5,r1\n cmp/hs r1,r0\n bf hwv_show ; steps 1-5: held\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r2\n bf hwv_show\n mov.l F_ctx,r1 ; waiting to preload: not while it plays\n mov #67,r0\n shll2 r0\n mov.l @(r0,r1),r0\n cmp/eq #1,r0\n bt hwv_show\n mov r11,r1\n add #127,r1\n add #57,r1\n mov.l @r1,r1 ; and for WV_TICKS after the load at most\n mov.l F_tick,r2\n mov.l @r2,r2\n sub r1,r2\n mov #WV_TICKS-256,r1\n extu.b r1,r1\n cmp/hs r1,r2\n bt hwv_show\nhwv_hide:\n mov #1,r0\n bra hwv_set\n nop\nhwv_show:\n mov #0,r0\nhwv_set:\n mov.b @r3,r1\n cmp/eq r0,r1\n bt hwv_ret\n mov.b r0,@r3 ; changed: the waveform drawn again (the UI\n mov.l F_mkdirty,r1 ; redraws it only when something changed)\n mov #1,r2\n mov.l r2,@r1\nhwv_ret:\n rts\n nop\n\n; ---- hk_wvmid: inside the detail waveform's draw function (0x08BCFA34),\n; right after it cleared its pixel buffer (memset 0x0BA7DEF4, at 0x08BCFBC8);\n; six instructions (0x08BCFBCC-D6) replaced by a jump here (r0 used, as the\n; first of them sets it). ST4 +62 set: nothing drawn into the buffer, on to\n; where it is copied to the screen (0x08BD01EE), so the area is black; else\n; the six and on (r7 is set anew at 0x08BCFBF4).\nhk_wvmid:\n mov.l W_flag,r7\n mov.b @r7,r7\n tst r7,r7\n bf wvm_blank\n sts fpscr,r5\n mov #64,r0\n mov.l W_r12,r12\n mov #5,r1\n mov.l W_r13,r13\n mov.l @(r0,r9),r14\n mov.l W_back,r7\n jmp @r7\n nop\nwvm_blank:\n sts fpscr,r5 ; the firmware's fpscr fix after the memset\n and r11,r5 ; (r11 holds its mask here)\n lds r5,fpscr\n mov.l W_out,r7\n jmp @r7\n nop\n .align 4\nW_flag: .long ST4+62\nW_r12: .long WV_R12\nW_r13: .long WV_R13\nW_back: .long WV_MID+12\nW_out: .long WV_OUT\n\n; ---- he_now: r0 = the tick\nhe_now:\n mov.l F_tick,r0\n rts\n mov.l @r0,r0\n\n .align 4\nF_ctx: .long PLAYER_CTX\nF_bjx: .long BJX\nF_b26d: .long 0x0AF3B26D\nF_b22d: .long 0x0AF3B22D\nF_st4: .long ST4\nF_mkdirty: .long MARKERS_DIRTY\nF_cuepos: .long CUE_POS\nF_fpmask: .long 0xFFE7FFFF\nF_hot: .long HOT\nF_hpird: .long HPI_READ\nF_keypc: .long KEY_PLAYCUE\nF_loadt: .long LOAD_TICKS\nF_play: .long hr_play\nF_recall: .long hk_recall\nF_slotst: .long SLOT_ST\nF_slotpt: .long SLOT_PT\nF_hpiwr: .long HPI_WRITE\nF_cue: .long hk_cue\nF_st2: .long ST2\nF_tick: .long TICK\nF_win: .long 300\n\n; ---- he_bjpend: r9 = the beat jumps' slot, r11 -> ST2: its PLAY again until\n; the deck plays (he_pend): right at the target the player takes no PLAY yet\nhe_bjpend:\n mov r11,r1\n add #64,r1\n mov.l r9,@(44,r1)\n mov.l Z_tick,r0\n mov.l @r0,r0\n mov.l r0,@(48,r1)\n rts\n mov.l r0,@(52,r1)\n .align 4\nZ_tick: .long TICK\n\n; ---- he_bjat: r0 = position - target, r9 = the slot -> T: the beat jumps'\n; slot, the deck standing within 2 of the target (it lands a unit short at\n; times and, in 0x17 there, does not play on by itself)\nhe_bjat:\n mov #BJ_SLOT,r1\n cmp/eq r1,r9\n bf hba_no ; (T is 0 then)\n mov r0,r1\n add #2,r1\n mov #5,r2\n cmp/hi r1,r2 ; 5 > r1: within -2 .. +2\nhba_no:\n rts\n nop\n\n; ---- he_mute: r11 -> ST2. Once a silent load's point is in, the player\n; sends the DSP its own PLAY on the slot (play command mode 3, then\n; transport 2) as for any CALL; our CUE back stopped it only a moment later,\n; and that moment was heard (the 0.1 s blip on a player, ~25 ms in the\n; emulator's capture). So the way back runs with silent 2 (hk_wrap_pc and\n; hk_wrap_tr drop that PLAY, let the CUE through; hs2_back, the hot cue just\n; set in hb_loaded), and silent (ST2 +2) stays until nothing is under way,\n; the deck stands (3, 0x17) and MUTE_TICKS have passed since the CUE (+12),\n; or MUTE_MAX at most.\nhe_mute:\n mov.b @(2,r11),r0\n tst r0,r0\n bt hm_ret\n mov.b @(3,r11),r0\n tst r0,r0\n bf hm_ret\n mov.b @(1,r11),r0\n tst r0,r0\n bt hm_idle\n cmp/eq #-128,r0 ; 0x80: waiting to preload again\n bf hm_ret\nhm_idle:\n sts.l pr,@-r15\n bsr he_now\n nop\n mov.l @(12,r11),r1\n sub r1,r0\n mov r0,r3\n mov #MUTE_TICKS,r1\n cmp/hs r1,r3\n bf hm_pop\n mov #MUTE_MAX,r1\n cmp/hs r1,r3\n bt hm_off\n bsr he_pos\n nop\n mov r1,r0\n cmp/eq #3,r0\n bt hm_off\n cmp/eq #23,r0\n bf hm_pop\nhm_off:\n mov #0,r0\n mov.b r0,@(2,r11)\nhm_pop:\n lds.l @r15+,pr\nhm_ret:\n rts\n nop\n\n; ---- he_load: r4 = a hot cue's time (1/150 s), r5 = slot 1-3, r11 -> ST2.\n; Busy from now; the slot holds nothing until he_busy finds it loaded (the\n; DSP's word for the row cleared, so an old one is not taken for it).\nhe_load:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov r4,r8\n mov r5,r9\n mov #BJ_SLOT,r0\n cmp/eq r0,r9\n bt hl_nort ; (see he_busy)\n bsr he_row\n mov r9,r0\n mov.l E_slotst,r4\n add r0,r4\n mov.l E_hpiwr,r1\n jsr @r1\n mov #0,r5\n bsr he_fp\n nop\nhl_nort:\n mov r11,r1 ; the position jumps: speed measured anew\n add #64,r1\n mov #0,r0\n mov.l r0,@(20,r1)\n mov r9,r0\n mov.b r0,@(3,r11) ; busy: slot s\n mov.l r8,@(16,r11)\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov #BJ_SLOT,r0\n cmp/eq r0,r9\n bt hl_ui ; the beat jumps' slot holds no hot cue\n mov r11,r1\n add #8,r1\n add r9,r1\n mov #0,r0\n mov.b r0,@r1\nhl_ui:\n mov #0,r0\n ; the UI as the CALL key leaves it (0x08B4F326-0x08B4F384)\n mov.l E_b21d,r1\n mov.b r0,@r1\n mov.b r0,@(1,r1)\n mov.l E_af08,r1\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov.l E_af28,r1\n mov #7,r0\n mov.l r0,@r1\n mov.l E_b22d,r1\n mov #1,r0\n mov.b r0,@r1\n ; the UI's CALL record: event (3+s) << 16 | 0x100, ms, -1, 1/150 s\n mov.l E_callrec,r2\n mov r9,r0\n add #3,r0\n shll16 r0\n mov #1,r1\n shll8 r1\n or r1,r0\n mov.l r0,@r2\n mov.l r8,@(12,r2)\n mov #-1,r0\n mov.l r0,@(8,r2)\n bsr he_ms\n mov r8,r4\n mov.l E_callrec,r2\n mov.l r0,@(4,r2)\n ; CALL: 22 and 23 with slot 4+s\n mov #0,r1\n mov.l r1,@-r15\n mov.l r0,@-r15\n mov #22,r4\n mov r9,r5\n add #4,r5\n mov #0,r6\n mov.l E_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n bsr he_fp\n nop\n mov #0,r1\n mov.l r1,@-r15\n mov #-1,r1\n mov.l r1,@-r15\n mov #23,r4\n mov r9,r5\n add #4,r5\n mov #0,r6\n mov.l E_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n bsr he_fp\n nop\n ; while the screen is held (the preload): the record back to the CUE\n ; point, the overview's line and marker show its time while a CALL is\n ; under way (0x0AF3AF28 7); the message is built. Not otherwise: the\n ; player reads it again later, and would not play the slot on.\n mov r11,r1\n add #127,r1\n add #61,r1 ; ST4 + 60\n mov.b @r1,r0\n tst r0,r0\n bt hl_rec\n mov.l E_cuepos,r1\n mov.l @r1,r4\n mov.l E_callrec,r2\n mov.l r4,@(12,r2)\n bsr he_ms\n nop\n mov.l E_callrec,r2\n mov.l r0,@(4,r2)\nhl_rec:\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_ms: r0 = r4 (1/150 s) in ms, rounded up so the builder gets it back\nhe_ms:\n mov r4,r1\n shll2 r1\n mov r1,r3\n shll2 r3\n add r3,r1 ; 20 * units\n add #2,r1\n mov.l E_third,r3\n dmulu.l r3,r1\n sts mach,r0\n rts\n shlr r0\n\n; ---- he_slot: r4 = time, r5 = key -> r0 = the slot holding it (1-3), 0 none\nhe_slot:\n mov.l E_st2,r3\n mov #1,r2\nhs_loop:\n mov r3,r1\n add #8,r1\n add r2,r1\n mov.b @r1,r0\n extu.b r0,r0\n cmp/eq r5,r0\n bf hs_next\n mov r2,r0\n shll2 r0\n add #20,r0\n mov.l @(r0,r3),r0\n cmp/eq r4,r0\n bf hs_next\n rts\n mov r2,r0\nhs_next:\n add #1,r2\n mov #4,r0\n cmp/eq r0,r2\n bf hs_loop\n rts\n mov #0,r0\n\n; ---- he_use: r0 = slot (1-3): used now\nhe_use:\n mov.l E_st2,r3\n mov r0,r2\n mov.b @(5,r3),r0\n add #1,r0\n mov.b r0,@(5,r3)\n add #5,r2\n add r3,r2\n rts\n mov.b r0,@r2\n\n; ---- he_div: r0 = r4 / r5, unsigned (r1, r2, r4 used)\nhe_div:\n mov #0,r0\n mov #0,r1\n mov #32,r2\nhd_loop:\n shll r4\n rotcl r1\n shll r0\n cmp/hs r5,r1\n bf hd_next\n sub r5,r1\n add #1,r0\nhd_next:\n dt r2\n bf hd_loop\n rts\n nop\n\n; ---- he_cue: r5 = slot 1-3. CUE with slot 4+s, as the CUE key sends it.\nhe_cue:\n sts.l pr,@-r15\n mov.l E_st3,r1 ; the position jumps: speed measured anew\n mov #0,r0\n mov.l r0,@(20,r1)\n mov.l E_af28,r1\n mov #7,r0\n mov.l r0,@r1\n mov.l E_aeec,r1\n mov #0,r0\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov.l E_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #3,r4\n add #4,r5\n mov.l E_build,r1\n jsr @r1\n mov #1,r7\n add #8,r15\n bsr he_fp\n nop\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_play: r5 = slot 1-3. PLAY with slot 4+s.\nhe_play:\n sts.l pr,@-r15\n mov.l E_af28,r1\n mov #3,r0\n mov.l r0,@r1\n mov.l E_af3c,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@-r15\n mov #0,r6\n mov.l r6,@-r15\n mov #1,r4\n add #4,r5\n mov.l E_build,r1\n jsr @r1\n mov #0,r7\n add #8,r15\n bsr he_fp\n nop\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_hot: r4 = a hot cue's time, r9 = its key, r10 -> HK_SETTING.\n; In a slot: CUE and PLAY on it, at once. Else it is loaded into its own\n; slot (hot cue s: slot s; and plays, as a CALL with a slot does).\nhk_hot:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r11,@-r15\n mov.l E_st2,r11\n mov r4,r8\n mov.b @(1,r11),r0 ; a preload under way: the key waits\n extu.b r0,r0\n tst r0,r0\n bt hh_free\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bf hh_wait ; (not started yet: the key acts)\nhh_free:\n mov.b @(3,r11),r0\n tst r0,r0\n bt hh_now\nhh_wait:\n mov r9,r0 ; a load under way: the key waits for it\n bra hh_ret ; (hk_engine; the last key pressed wins)\n mov.l r0,@(60,r11)\nhh_now:\n bsr he_slot\n mov r9,r5\n tst r0,r0\n bt hh_load\n mov.l r0,@-r15\n bsr he_use\n nop\n mov.l E_af28,r1 ; a VINYL mode pause: the UI pauses (0x0AF3AF28\n mov.l @r1,r0 ; 5) but the player stays in state 1, braked to\n cmp/eq #5,r0 ; a stop; a PLAY on the slot does not start it.\n bf hh_playing ; CUE on the slot, the UI left paused, then\n mov.l E_ctx,r1 ; the PLAY key for the UI (he_inj): it plays\n mov #67,r0 ; on from there as from any VINYL pause\n shll2 r0\n mov.l @(r0,r1),r0\n cmp/eq #1,r0\n bf hh_playing ; (a CDJ mode pause: the player state 3)\n mov.l @r15+,r5\n bsr he_cue\n nop\n mov.l E_af28,r1\n mov #5,r0\n mov.l r0,@r1\n mov.l E_st3,r1\n mov.l E_tick,r2\n mov.l @r2,r2\n mov.l r2,@(48,r1)\n mov.l E_st4,r1\n add #63,r1\n mov #1,r0\n bra hh_ret\n mov.b r0,@r1\nhh_playing:\n mov.l @r15,r5\n bsr he_cue\n nop\n mov.l @r15,r5\n bsr he_play\n nop\n mov.l E_st3,r1 ; from pause the player may take PLAY only\n mov.l @r15+,r0 ; once it is at the hot cue: he_pend sends\n mov.l r0,@(44,r1) ; it again until the deck plays\n mov.l E_tick,r2\n mov.l @r2,r2\n mov.l r2,@(48,r1)\n mov.l r2,@(52,r1)\n bra hh_ret\n nop\nhh_load:\n mov r9,r5 ; hot cue s: slot s, always\n mov r5,r0\n mov.l r5,@-r15\n bsr he_use\n nop\n mov r9,r0\n mov.l r0,@(44,r11)\n mov #0,r0\n mov.b r0,@(2,r11) ; not silent: it plays\n mov.l @r15+,r5\n bsr he_load\n mov r8,r4\nhh_ret:\n mov.l @r15+,r11\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_gate: r4 = the time, r9 = the key: a gate's release. In a slot: CUE\n; on it (the deck stands at the hot cue); else the memory CALL as before.\nhk_gate:\n sts.l pr,@-r15\n mov.l r4,@-r15\n bsr he_slot\n mov r9,r5\n tst r0,r0\n bt hg_call\n mov.l E_st3,r1 ; no recall's PLAY to repeat now\n mov #0,r2\n mov.l r2,@(44,r1)\n mov.l E_st2,r1\n mov.l r0,@(48,r1) ; parked there: PLAY plays the slot\n mov.l E_tick,r2\n mov.l @r2,r2\n mov.l r2,@(56,r1)\n mov r0,r5\n bsr he_cue\n add #4,r15\n lds.l @r15+,pr\n rts\n nop\nhg_call:\n mov.l @r15+,r4\n mov.l E_recall,r1\n jsr @r1\n mov #0,r5\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_wrap_tr / hk_wrap_pc: the DSP manager's transport and play\n; commands (0x089F8FCA, 0x089F9306, through its literals 0x089F29FC and\n; 0x089F24FC). While a hot cue is loaded silently (silent 1), its PLAY\n; (transport 2, the play command) does not reach the DSP; anything else\n; passes. Silent 2 (the CUE back, see he_mute) lets the play command with\n; mode 0 through (r5: 3 plays, 0 the CUE), not the PLAY the player sends\n; once the last slot's point is in.\nhk_wrap_tr:\n mov.l E_silent,r1\n mov.b @r1,r0\n tst r0,r0\n bt wt_pass\n mov r4,r0\n cmp/eq #2,r0\n bt wt_drop\nwt_pass:\n mov.l E_trans,r1\n jmp @r1\n nop\nwt_drop:\n rts\n nop\nhk_wrap_pc:\n mov.l E_silent,r1\n mov.b @r1,r0\n tst r0,r0\n bt wp_pass\n cmp/eq #2,r0\n bf wp_drop\n mov r5,r0\n cmp/eq #3,r0\n bf wp_pass\nwp_drop:\n rts\n nop\nwp_pass:\n mov.l E_playc,r1\n jmp @r1\n nop\n\n; ---- hk_wrap_cy: the DSP manager's cyclic block (0x089F9610, r4 -> its 8\n; words for 0x11837BC0, through its literal 0x089F31F4). The CUE that takes\n; the deck back after a silent load sends PLAY, then the hold a moment\n; later; while silent the block carries 0x10000000 in its word 1 all along\n; (tested with the mute fix: nothing is heard meanwhile, the loads go on).\n; The DSP reads that bit as the MAIN's \"playing\" flag (re/dsp900nxs), not\n; as a hold of its own.\nhk_wrap_cy:\n mov.l E_silent,r1\n mov.b @r1,r0\n tst r0,r0\n bt wc_pass\n mov.l @(4,r4),r0\n mov.l E_hold,r1\n or r1,r0\n mov.l r0,@(4,r4)\nwc_pass:\n mov.l E_cyc,r1\n jmp @r1\n nop\n\n .align 4\nE_st2: .long ST2\nE_st3: .long ST3\nE_silent: .long ST2_SILENT\nE_hot: .long HOT\nE_tick: .long TICK\nE_ctx: .long PLAYER_CTX\nE_keypc: .long KEY_PLAYCUE\nE_loadt: .long LOAD_TICKS\nE_win: .long 300\nE_hpird: .long HPI_READ\nE_hpiwr: .long HPI_WRITE\nE_st4: .long ST4\nE_slotst: .long SLOT_ST\nE_fpmask: .long 0xFFE7FFFF\nE_play: .long hr_play\nE_recall: .long hk_recall\nE_cuepos: .long CUE_POS\nE_b21d: .long 0x0AF3B21D\nE_af08: .long 0x0AF3AF08\nE_af28: .long 0x0AF3AF28\nE_b22d: .long 0x0AF3B22D\nE_callrec: .long CALL_REC\nE_build: .long BUILD\nE_third: .long 0xAAAAAAAB\nE_aeec: .long 0x0AF3AEEC\nE_af3c: .long 0x0AF3AF3C\nE_trans: .long TRANSPORT\nE_playc: .long PLAYCMD\nE_cyc: .long CYCLIC\nE_hold: .long 0x10000000\n\n; ---- he_rate: r11 -> ST2. Whether the deck plays (ST3 +40: state 1, or\n; any state but paused (3) while it moved in the last 0.25 s: a loop is 7,\n; a slip loop 0x14, a slot it loaded 0x17; +32 the last position, +36\n; polls left that count as moving), and while it plays, its speed in 1/150 s * 256 per tick over a second or more (ST3 +12; +16 and\n; +20 where and when the measure began, +20 = 0: anew), kept only between\n; 0.5 and 1.5 times 1.0.\nhe_rate:\n sts.l pr,@-r15\n bsr he_pos\n nop\n mov r0,r3 ; the position\n mov r11,r2\n add #64,r2\n mov.l @(32,r2),r0\n mov.l r3,@(32,r2)\n cmp/eq r3,r0\n bt hr2_same\n bra hr2_cnt\n mov #25,r0 ; it moves in steps: 0.25 s\nhr2_same:\n mov.l @(36,r2),r0\n tst r0,r0\n bt hr2_cnt\n add #-1,r0\nhr2_cnt:\n mov.l r0,@(36,r2)\n mov r0,r4\n mov r1,r0\n cmp/eq #1,r0\n bt hr2_on\n cmp/eq #3,r0\n bt hr2_stop ; paused\n tst r4,r4 ; any other state while it moves: a loop (7),\n bf hr2_on ; a slip loop (0x14), a slot it loaded (0x17)\nhr2_stop:\n mov #0,r0 ; not playing: anew\n mov.l r0,@(40,r2)\n bra hr2_ret\n mov.l r0,@(20,r2)\nhr2_on:\n mov #1,r0\n mov.l r0,@(40,r2)\nhr2_play:\n bsr he_now\n nop\n mov.l @(20,r2),r1\n tst r1,r1\n bt hr2_anchor\n mov r0,r5\n sub r1,r5 ; ticks since\n mov #100,r1\n cmp/hs r1,r5\n bf hr2_ret\n mov.l @(16,r2),r1\n mov r3,r4\n sub r1,r4 ; how far it went\n cmp/pl r4\n bf hr2_anchor ; back: it jumped\n mov.l r0,@-r15\n mov.l r3,@-r15\n shll8 r4\n bsr he_div\n nop\n mov r11,r2\n add #64,r2\n mov #96,r1\n shll r1 ; 192\n cmp/hs r1,r0\n bf hr2_bad\n mov #72,r1\n shll2 r1\n shll r1 ; 576\n cmp/hi r1,r0\n bt hr2_bad ; on: it jumped\n mov.l r0,@(12,r2)\nhr2_bad:\n mov.l @r15+,r3\n mov.l @r15+,r0\nhr2_anchor:\n mov.l r3,@(16,r2)\n mov.l r0,@(20,r2)\nhr2_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_bj: r11 -> ST2, r10 -> HK_SETTING. The beat jump beatjump asks for\n; (ST3 +0: its target in 1/150 s | 1 << 31, +4 the tick of its key).\n; First the DSP jump (he_dj: at once, in the decoded audio; playing, paused\n; or at cue standby). Where the DSP refuses or is not asked, the old way:\n; playing, the target moves on by the time a slot load takes (ST3 +8,\n; measured by he_gap) at the deck's speed, and is loaded into BJ_SLOT, the\n; beat jumps' own: the deck plays on in phase, A-C keep theirs, its CUE point\n; stays; standing, the memory CALL there (the CUE point goes with it), busy\n; till the deck stands there. A load under way, or the preload: dropped;\n; standing, also while the preload still waits.\nhe_bj:\n sts.l pr,@-r15\n mov.l G_bjx,r0 ; a DSP jump under way: the next press waits\n mov.l @r0,r0\n tst r0,r0\n bf hj_ret\n mov r11,r2\n add #64,r2\n mov.l @r2,r0\n cmp/pz r0\n bt hj_q ; none: one queued at cue standby?\n shll r0\n shlr r0\n mov r0,r8 ; the target\n mov #0,r0\n mov.l r0,@r2 ; taken\n mov.b @(3,r11),r0\n tst r0,r0\n bf hj_busy ; a load under way\n bsr hj_settled\n nop\n bf hj_wait ; a CALL just ended: this one waits too\n mov.b @(1,r11),r0\n extu.b r0,r0\n tst r0,r0\n bt hj_free\n mov #-128,r1\n extu.b r1,r1\n cmp/eq r1,r0\n bf hj_ret ; preloading\nhj_free:\n mov.l G_dj,r0 ; the DSP jump: at once, in the decoded audio;\n jsr @r0 ; nothing else (a slot load or a memory CALL\n nop ; instead left the deck standing or the UI\n bra hj_ret ; frozen: hj_old and after are not used)\n nop\nhj_old:\n mov.l G_pos,r0 ; standing unless the player plays (right\n jsr @r0 ; after a CALL he_rate still sees its move)\n nop\n mov r1,r0\n cmp/eq #1,r0\n bf hj_stand\n mov r11,r2\n add #64,r2\n mov.l @(40,r2),r0 ; playing (he_rate)\n tst r0,r0\n bf hj_play\nhj_stand:\n mov #9,r0 ; standing: the memory CALL\n mov.b r0,@(3,r11)\n mov.l r8,@(16,r11)\n bsr hj_posv\n nop\n bsr he_now\n nop\n mov.l r0,@(12,r11)\n mov r8,r4\n mov.l G_callat,r1\n jsr @r1\n mov #0,r5\n bra hj_ret\n nop\nhj_play:\n mov r11,r2\n add #64,r2\n mov.l @(8,r2),r4 ; a load's time, ticks\n tst r4,r4\n bf hj_g\n mov #BJ_GAP0,r4\nhj_g:\n mov.l @(12,r2),r1 ; the speed\n tst r1,r1\n bf hj_r\n mov #96,r1\n shll2 r1 ; 384: 1.5 per tick\nhj_r:\n mul.l r1,r4\n sts macl,r4\n shlr8 r4\n add r4,r8 ; where the deck would be by then\n mov.l r8,@(28,r2)\n mov #1,r0\n mov.l r0,@(24,r2) ; he_gap measures it\n mov #0,r0\n mov.b r0,@(2,r11) ; heard\n mov #BJ_SLOT,r5 ; the beat jumps' own slot\n bsr he_load\n mov r8,r4\nhj_ret:\n lds.l @r15+,pr\n rts\n nop\n; A press while the memory CALL of one at cue standby is under way: its\n; target (bj_poll reckons from the CALL's target meanwhile, BJX +36, as the\n; deck's position reads 0 then) waits in BJX +32 (| 1 << 31) and is jumped to\n; once the CALL is done and BJ_SETTLE has passed (hj_settled), by another\n; CALL; a later press replaces it. A hot cue's load under way: dropped.\nhj_busy:\n cmp/eq #9,r0\n bf hj_ret\nhj_wait:\n bsr hj_posv\n nop\n mov #1,r0\n rotr r0\n or r8,r0\n mov.l G_bjx,r1\n bra hj_ret\n mov.l r0,@(32,r1)\nhj_q:\n mov.b @(3,r11),r0\n tst r0,r0\n bf hj_ret ; still under way\n bsr hj_settled\n nop\n bf hj_ret ; just ended\n mov.l G_bjx,r1\n mov.l @(32,r1),r0\n tst r0,r0\n bf hj_qgo\n bra hj_ret\n mov.l r0,@(36,r1) ; none: bj_poll reads the deck's position again\nhj_qgo:\n mov #0,r2\n mov.l r2,@(32,r1)\n shll r0\n shlr r0\n mov r0,r8\n mov.l G_pos,r0 ; the deck plays (PLAY meanwhile): dropped\n jsr @r0\n nop\n mov r1,r0\n cmp/eq #1,r0\n bt hj_ret\n bra hj_stand ; the memory CALL again (he_rate still sees\n nop ; the last one's move)\n\n; hj_settled: T = 1 once BJ_SETTLE ticks have passed since the last memory\n; CALL ended (BJX +44, he_busy). A CALL sent right after one (the deck at its\n; target and standing, the firmware not done with it yet) could leave the\n; UI's task waiting for good: no key worked any more.\nhj_settled:\n sts.l pr,@-r15\n bsr he_now\n nop\n mov.l G_bjx,r1\n mov.l @(44,r1),r1\n sub r1,r0\n mov #BJ_SETTLE,r1\n cmp/hs r1,r0\n lds.l @r15+,pr\n rts\n nop\n\n; hj_posv: r8 = a target (1/150 s) -> BJX +36 = it in ms (rounded)\nhj_posv:\n mov r8,r0\n shll2 r0\n mov r0,r1\n shll2 r0\n add r1,r0 ; 20 t\n add #1,r0\n mov.l G_third,r1\n dmulu.l r1,r0\n sts mach,r0\n shlr r0 ; / 3\n mov.l G_bjx,r1\n rts\n mov.l r0,@(36,r1)\n\n; ---- he_ledlv: r4 = a hot cue's time, r5 = its key -> r0 = its LED level:\n; 1, and 1 and 0 by turns (0.32 s each) while it is being loaded by a press\n; (the preload's loads carry 0x80 in the key: they show nothing)\nhe_ledlv:\n sts.l pr,@-r15\n mov.l G_st2,r1\n mov.b @(3,r1),r0\n extu.b r0,r0\n tst r0,r0\n bt/s hlv_ret ; no load under way\n mov #1,r0\n mov.l @(44,r1),r0\n cmp/eq r5,r0\n bf/s hlv_ret ; another key's\n mov #1,r0\n mov.l G_tick,r1\n mov.l @r1,r1\n mov #32,r0\n tst r0,r1\n movt r0\n xor #1,r0\nhlv_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_dbop: r4 = 1 save / 2 delete, r5 = hot cue number (A = 1), r6 =\n; its time (1/150 s): queued for the USB (ST4: +0 count, +4.. 6 x (op |\n; number << 8, time)); he_dbq sends them when MEMORY could. A delete of a\n; hot cue still queued takes that entry's place (its save is not needed\n; any more). A full queue drops the oldest.\nhk_dbop:\n mov.l G_st4,r1\n mov r5,r7\n shll8 r5\n or r4,r5\n mov.l @r1,r3\n mov r1,r2\n add #4,r2\n mov r4,r0\n cmp/eq #2,r0\n bf hd_add ; a save: always one more\n tst r3,r3\n bt hd_add\nhd_find:\n mov.l @r2,r0\n shlr8 r0\n extu.b r0,r0\n cmp/eq r7,r0\n bt hd_put\n dt r3\n bf/s hd_find\n add #8,r2\nhd_add:\n mov.l @r1,r0\n cmp/eq #DBQ_N,r0\n bf hd_room\n mov r1,r2 ; full: the oldest goes\n add #4,r2\n mov #DBQ_N-1,r3\nhd_shift:\n mov.l @(8,r2),r0\n mov.l r0,@r2\n mov.l @(12,r2),r0\n mov.l r0,@(4,r2)\n dt r3\n bf/s hd_shift\n add #8,r2\n mov #DBQ_N-1,r0\nhd_room:\n mov r0,r2\n shll2 r2\n shll r2\n add r1,r2\n mov.l r5,@(4,r2)\n mov.l r6,@(8,r2)\n add #1,r0\n rts\n mov.l r0,@r1\nhd_put:\n mov.l r5,@r2\n rts\n mov.l r6,@(4,r2)\n\n; ---- he_dbq: r11 -> ST2, each poll. The first queued hot cue goes to the DB\n; once the last one was taken (ST3 +56 clear; let go after DBQ_TICKS) and the\n; UI would send MEMORY's request: the checks of its handler (0x08B3CE08-\n; 0x08B3CE6E): the track ready (0x08A26340), not in the mode 0x08AFCF58 bars,\n; no request of the UI's under way (0x0B7C144C), on a USB only when its\n; setting at 0x0A4DE794 +0x2E0 allows; the device from 0x09944900.\nhe_dbq:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l G_st3,r8\n mov.l @(56,r8),r0\n tst r0,r0\n bt hq_free\n bsr he_now ; one sent, not taken yet\n nop\n mov.l G_st4,r1\n mov.l @(52,r1),r2\n sub r2,r0\n mov.l G_dbqt,r2\n cmp/hs r2,r0\n bf hq_ret\n mov #0,r0 ; never taken: let go, MEMORY is MEMORY again\n mov.l r0,@(56,r8)\nhq_free:\n mov.l G_st4,r1\n mov.l @r1,r0\n tst r0,r0\n bt hq_ret\n mov.l G_ctx,r1 ; not while a track loads (0 none, 2 and 4\n mov #67,r0 ; loading)\n shll2 r0\n mov.l @(r0,r1),r0\n tst r0,r0\n bt hq_ret\n cmp/eq #2,r0\n bt hq_ret\n cmp/eq #4,r0\n bt hq_ret\n mov.l G_busy,r1 ; the UI's request under way\n mov.l @r1,r0\n tst r0,r0\n bf hq_ret\n mov.l G_mode,r1\n jsr @r1\n nop\n tst r0,r0\n bf hq_fp\n mov.l G_which,r1\n jsr @r1\n nop\n cmp/eq #1,r0\n movt r5\n mov.l G_ctx,r4\n mov.l G_ready,r1\n jsr @r1\n nop\n cmp/eq #1,r0\n bf hq_fp\n mov.l G_media,r1\n jsr @r1\n nop\n cmp/eq #4,r0\n bf hq_send\n mov.l G_usbok,r1\n mov.l @r1,r0\n cmp/eq #1,r0\n bf hq_fp\nhq_send:\n mov.l G_st4,r1 ; the first one\n mov.l @(8,r1),r0\n mov.l r0,@(60,r8)\n mov.l @(4,r1),r0\n mov.l r0,@(56,r8)\n bsr he_now\n nop\n mov.l G_st4,r1\n mov.l r0,@(52,r1)\n mov.l @r1,r3 ; out of the queue\n add #-1,r3\n mov.l r3,@r1\n mov r1,r2\n add #4,r2\n tst r3,r3\n bt hq_go\nhq_mv:\n mov.l @(8,r2),r0\n mov.l r0,@r2\n mov.l @(12,r2),r0\n mov.l r0,@(4,r2)\n dt r3\n bf/s hq_mv\n add #8,r2\nhq_go:\n mov.l G_dev,r1\n mov.l @r1,r5\n mov.l G_dbreq,r4\n mov #0,r6\n mov.l G_sender,r1\n jsr @r1\n mov #0,r7\nhq_fp:\n mov.l G_mask,r1\n sts fpscr,r0\n and r1,r0\n lds r0,fpscr\nhq_ret:\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- hk_dbreg: the DB task's handler of a cue registration (table entry\n; 0x0899314C, r4-r6 as given, r6 -> the request, its cue entry at +112: +1\n; kind, +2 hot cue number, +4 ms, +12 1/150 s). Ours (ST3 +56): the entry\n; becomes our hot cue's, and a delete goes to the delete handler instead.\nhk_dbreg:\n mov.l G_st3,r1\n mov.l @(56,r1),r0\n tst r0,r0\n bt hd_reg ; MEMORY's own\n mov r0,r2\n mov #0,r0\n mov.l r0,@(56,r1) ; taken\n mov.l @(60,r1),r3\n mov r6,r7\n add #112,r7\n mov #1,r0\n mov.b r0,@(1,r7) ; one cue, not a loop\n mov r2,r0\n shlr8 r0\n mov.w r0,@(2,r7) ; its hot cue number\n mov.l r3,@(12,r7)\n mov r3,r0 ; ms = units * 20 / 3, rounded up\n shll2 r0\n mov r0,r1\n shll2 r1\n add r1,r0\n add #2,r0\n mov.l G_third,r1\n dmulu.l r1,r0\n sts mach,r0\n shlr r0\n mov.l r0,@(4,r7)\n extu.b r2,r0\n cmp/eq #2,r0\n bt hd_del\nhd_reg:\n mov.l G_dbregf,r1\n jmp @r1\n nop\nhd_del:\n mov.l G_dbdelf,r1\n jmp @r1\n nop\n\n; ---- hk_pcp2: the DB task's PCP2 writer (0x08B10BE0, r4 entry, r5 cue). A\n; hot cue with no extra data (one set here, not read from the file) is\n; copied to PCBUF with the extra data rekordbox writes, its colour by number\n; (PCBLOB), so its entry gets the colour block; anything else passes.\nhk_pcp2:\n mov.l @(52,r5),r0\n tst r0,r0\n bf hq2_go\n mov.w @(4,r5),r0\n extu.w r0,r0\n add #-1,r0\n mov #8,r1\n cmp/hs r1,r0\n bt hq2_go ; a memory cue (0), or no such hot cue\n mov r0,r7 ; number - 1\n mov.l G_pcbuf,r2\n mov r5,r3\n mov #14,r6 ; 56 bytes\nhq2_cp:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r6\n bf/s hq2_cp\n add #4,r2\n mov.l G_pcblob,r3 ; r2 -> PCBUF + 56: its extra data\n mov r7,r0\n mov #PCBLOB_LEN,r1\n mulu.w r1,r0\n sts macl,r0\n add r0,r3\n mov #7,r6\nhq2_bl:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r6\n bf/s hq2_bl\n add #4,r2\n mov.l G_pcbuf,r5\n mov #PCBLOB_USED,r0\n mov.l r0,@(52,r5)\nhq2_go:\n mov.l G_pcp2,r1\n jmp @r1\n nop\n\n; ---- he_pend: r11 -> ST2. A recall's PLAY (ST3 +44 its slot, +48 the tick\n; of the key, +52 of the last PLAY): again every PEND_EVERY ticks until the\n; deck plays (he_rate), PEND_TICKS at most.\nhe_pend:\n sts.l pr,@-r15\n mov r11,r2\n add #64,r2\n mov.l @(44,r2),r0\n tst r0,r0\n bt hp2_ret\n mov.l G_ctx,r1 ; it plays: the player's state 1 (not he_rate's\n mov #67,r0 ; moving: the jump to the hot cue looks like\n shll2 r0 ; that, and a PLAY sent too early on the\n mov.l @(r0,r1),r0 ; player is lost; the UI then thinks it plays)\n cmp/eq #1,r0\n bt hp2_done\n bsr he_now\n nop\n mov r11,r2\n add #64,r2\n mov.l @(48,r2),r1\n mov r0,r3\n sub r1,r3\n mov #PEND_TICKS-256,r1\n extu.b r1,r1\n cmp/hs r1,r3\n bt hp2_done ; given up\n mov.l @(52,r2),r1\n mov r0,r3\n sub r1,r3\n mov #PEND_EVERY,r1\n cmp/hs r1,r3\n bf hp2_ret\n mov.l r0,@(52,r2)\n bsr he_play\n mov.l @(44,r2),r5\n bra hp2_ret\n nop\nhp2_done:\n mov #0,r0\n mov.l r0,@(44,r2)\nhp2_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_inj: r11 -> ST2. A recall from a VINYL pause (ST4 +63: 1 waiting,\n; 2 pressing; ST3 +48 the tick of the key): INJ_WAIT ticks later the PLAY\n; key is pressed for the UI (its bit set in the decoded keys) until\n; INJ_UNTIL, then let go, as a player's own press: the UI's PLAY logic\n; (0x08B45028) plays on from the hot cue where the CUE on the slot left the\n; deck, as after any VINYL pause.\nhe_inj:\n mov.l G_st4,r3\n add #63,r3\n mov.b @r3,r0\n extu.b r0,r2\n tst r2,r2\n bt hi_ret\n mov.l G_tick,r1\n mov.l @r1,r1\n mov r11,r0\n add #64,r0\n mov.l @(48,r0),r0\n sub r0,r1 ; ticks since the key\n mov r2,r0\n cmp/eq #2,r0\n bt hi_press\n mov #INJ_WAIT,r0\n cmp/hs r0,r1\n bf hi_ret\n mov #2,r0\n mov.b r0,@r3\nhi_press:\n mov #INJ_UNTIL,r0\n cmp/hs r0,r1\n bt hi_end ; let go\n mov.l G_keypc,r1\n mov.b @r1,r0\n or #4,r0 ; PLAY\n rts\n mov.b r0,@r1\nhi_end:\n mov #0,r0\n mov.b r0,@r3\nhi_ret:\n rts\n nop\n\n; ---- he_gap: r11 -> ST2, from he_busy when a slot load is done. A playing\n; beat jump's: the ticks from its key until its slot played, less how far it\n; has played since, averaged into ST3 +8 (0.1 .. 3 s). r8, r9 kept.\nhe_gap:\n sts.l pr,@-r15\n mov r11,r3\n add #64,r3\n mov.l @(24,r3),r0\n tst r0,r0\n bt hg2_ret\n mov #0,r0\n mov.l r0,@(24,r3)\n bsr he_pos\n nop\n mov r11,r3\n add #64,r3\n mov.l @(28,r3),r1\n sub r1,r0 ; played since, 1/150 s\n shll8 r0\n mov r0,r4\n mov.l @(12,r3),r5\n tst r5,r5\n bf hg2_r\n mov #96,r5\n shll2 r5\nhg2_r:\n bsr he_div\n nop\n mov r0,r6 ; ticks\n bsr he_now\n nop\n mov r11,r3\n add #64,r3\n mov.l @(4,r3),r1\n sub r1,r0\n sub r6,r0 ; key to playing\n mov.l @(8,r3),r1\n tst r1,r1\n bf hg2_avg\n mov r0,r1\nhg2_avg:\n add r1,r0\n shar r0\n mov #10,r1\n cmp/ge r1,r0\n bt hg2_lo\n mov r1,r0\nhg2_lo:\n mov #75,r1\n shll2 r1 ; 300\n cmp/gt r1,r0\n bf hg2_hi\n mov r1,r0\nhg2_hi:\n mov.l r0,@(8,r3)\nhg2_ret:\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nG_recall: .long hk_recall\nG_callat: .long hk_callat\nG_bjx: .long BJX\nG_dj: .long he_dj\nG_pos: .long he_pos\nG_tick: .long TICK\nG_st2: .long ST2\nG_st3: .long ST3\nG_dbreq: .long DB_REQ_REG\nG_st4: .long ST4\nG_dbqt: .long DBQ_TICKS\nG_busy: .long UI_DB_BUSY\nG_mode: .long UI_DB_MODE\nG_which: .long UI_DB_WHICH\nG_ready: .long UI_DB_READY\nG_ctx: .long PLAYER_CTX\nG_keypc: .long KEY_PLAYCUE\nG_media: .long UI_MEDIA\nG_usbok: .long UI_USB_OK\nG_dev: .long UI_DEVICE\nG_sender: .long UI_SENDER\nG_mask: .long 0xFFE7FFFF\nG_third: .long 0xAAAAAAAB\nG_dbregf: .long DB_REG\nG_pcbuf: .long PCBUF\nG_pcblob: .long PCBLOB\nG_pcp2: .long PCP2\nG_dbdelf: .long DB_DEL\n\n; ---- hk_send: the panel sender (r4-r7 as given, r6 = the frame): in the hot\n; cue key mode our five LEDs go into the frame, and its sum is made again.\nhk_send:\n mov.l L_setting,r1\n mov.b @(13,r1),r0\n tst r0,r0\n bt hs_go\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l L_ledtab,r2 ; (frame byte, field shift) for A-E\n mov r1,r3\n add #16,r3 ; on/off for A-E\n mov #5,r8\nhs_led:\n mov.b @r2,r0\n extu.b r0,r0\n mov r6,r9\n add r0,r9 ; the frame byte\n mov.b @(1,r2),r0\n extu.b r0,r1 ; the field's shift\n mov #3,r0\n shld r1,r0\n not r0,r0 ; mask out the field\n mov r0,r10\n mov.b @r3,r0 ; our level\n and #3,r0\n shld r1,r0\n mov r0,r1\n mov.b @r9,r0\n and r10,r0\n or r1,r0\n mov.b r0,@r9\n add #2,r2\n dt r8\n bf/s hs_led\n add #1,r3\n mov r6,r2 ; byte 22: sum of 0-21, carries added back\n mov #22,r8\n mov #0,r1\nhs_sum:\n mov.b @r2+,r0\n extu.b r0,r0\n add r0,r1\n mov r1,r0\n shlr8 r0\n tst r0,r0\n bt hs_nc\n extu.b r1,r1\n add #1,r1\nhs_nc:\n dt r8\n bf hs_sum\n mov r1,r0\n mov.b r0,@r2 ; r2 is at byte 22 now\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\nhs_go:\n mov.l L_sender,r1\n jmp @r1\n nop\n\n; ---- hk_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nhk_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt hs_ours\n mov.l L_show,r1\n jmp @r1\n nop\nhs_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l L_setting,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r4) ; shown as if being chosen\n mov.l L_show,r1\n jsr @r1\n nop\n mov.l L_setting,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- hk_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nhk_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt ht_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt ht_none\n mov.l r5,@(12,r4)\n mov.l L_setting,r1\n mov r5,r0\n mov.b r0,@r1\n tst r0,r0\n bf ht_save\n mov.b r0,@(1,r1) ; OFF: the mode goes with it\nht_save:\n sts.l pr,@-r15\n bsr hk_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nht_none:\n rts\n mov #0,r0\n\n; ---- hk_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; the way 0x08A681A0 sends the firmware's own\nhk_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l L_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l L_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf sv_out\n mov.l @r15,r9\n tst r9,r9\n bt sv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l L_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l L_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l L_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l L_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt sv_out\n mov.l L_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l L_release,r1\n jsr @r1\n nop\nsv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- hk_load: r1 -> HK_SETTING; the last SAVE_ID in the journal, if any\nhk_load:\n mov #1,r0\n mov.b r0,@(3,r1)\n mov.l L_journal,r2\n mov.l L_jend,r3\n mov.l L_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nhl_loop:\n cmp/hs r3,r2\n bt hl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt hl_out\n cmp/eq r5,r0\n bf hl_next\n mov.w @(2,r2),r0\n and #1,r0\n mov.b r0,@r1\nhl_next:\n bra hl_loop\n add #4,r2\nhl_out:\n rts\n nop\n\n; ---- hk_render: the bitmap widgets' draw method; the box after the anchor\nhk_render:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov r7,r11\n mov.l L_draw,r1\n jsr @r1\n nop\n mov.l L_setting,r1\n mov.b @r1,r0\n tst r0,r0\n bt hr_out\n mov #68,r0\n mov.l @(r0,r9),r1\n mov.l L_quant,r2 ; QUANTIZE itself: draw after it\n cmp/eq r2,r1\n bt/s hr_found\n mov r9,r4\n mov.l L_digit0,r2 ; a digit of the time display, drawn as it\n cmp/hs r2,r1 ; changes, in REMAIN and ELAPSED alike: after\n bf hr_rem ; it, with the QUANTIZE widget found before\n mov.l L_digit1,r2\n cmp/hi r2,r1\n bt hr_rem\n mov.l L_setting,r2\n mov.l @(8,r2),r4\n tst r4,r4\n bf hr_found\n bra hr_out\n nop\nhr_rem:\n mov.l L_anchor,r2 ; REMAIN: finds QUANTIZE among its siblings\n cmp/eq r2,r1\n bf hr_out\n mov.l @(8,r9),r1 ; the container\n tst r1,r1\n bt hr_out\n mov #40,r0\n mov.w @(r0,r1),r2\n extu.w r2,r2 ; children\n mov #36,r0\n mov.l @(r0,r1),r3\n tst r3,r3\n bt hr_out\n mov.l L_quant,r6\nhr_find:\n tst r2,r2\n bt hr_out\n mov.l @r3+,r4\n tst r4,r4\n bt hr_next\n mov #68,r0\n mov.l @(r0,r4),r0\n cmp/eq r6,r0\n bt hr_found\nhr_next:\n bra hr_find\n add #-1,r2\nhr_found:\n mov.l L_setting,r1\n mov.l r4,@(8,r1) ; remembered for hk_poll\n mov.l L_widget,r5 ; a copy, visible, further right, our resource\n mov r5,r2\n mov #20,r7\nhr_copy:\n mov.l @r4+,r0\n mov.l r0,@r2\n dt r7\n bf/s hr_copy\n add #4,r2\n mov #1,r0\n mov.l r0,@(12,r5)\n mov.l @(24,r5),r0\n add #BOX_DX,r0\n mov.l r0,@(24,r5)\n mov.l @(28,r5),r0\n add #BOX_DX,r0\n mov.l r0,@(28,r5)\n mov.l L_reshk,r1\n mov #68,r0\n mov.l r1,@(r0,r5)\n mov r8,r4\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\nhr_out:\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- hk_res: the resolver for the draw method; HK_RES is 0x1A8 with our pixels\nhk_res:\n mov.l L_resolve,r1\n mov.l L_reshk,r0\n cmp/eq r0,r4\n bt hq_ours\n jmp @r1\n nop\nhq_ours:\n sts.l pr,@-r15\n mov.l L_quant,r4\n jsr @r1\n nop\n tst r0,r0\n bt hq_out\n mov.l L_rec,r2\n mov r2,r3\n mov #11,r7\nhq_copy:\n mov.l @r0+,r1\n mov.l r1,@r3\n dt r7\n bf/s hq_copy\n add #4,r3\n mov.l L_setting,r1\n mov.b @(1,r1),r0\n tst r0,r0\n mov.l L_pix_on,r1\n bf hq_on\n mov.l L_pix_off,r1\nhq_on:\n mov.l L_phase,r0 ; loading: LOADING by turns (he_lock)\n mov.b @r0,r0\n tst r0,r0\n bt hq_set\n mov.l L_pixload,r1\nhq_set:\n mov.l r1,@(12,r2)\n mov r2,r0\nhq_out:\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nL_prev: .long PREV_DECODER\nL_setting: .long HK_SETTING\nL_key: .long KEY_QUANTIZE\nL_tick: .long TICK\nL_show: .long SHOW_SAVER\nL_draw: .long DRAW\nL_resolve: .long RESOLVE\nL_anchor: .long RES_ANCHOR\nL_digit0: .long DIGIT0\nL_digit1: .long DIGIT1\nL_quant: .long RES_QUANTIZE\nL_reshk: .long RES_HK\nL_widget: .long WIDGET\nL_rec: .long REC\nL_pix_on: .long PIX_ON\nL_pix_off: .long PIX_OFF\nL_phase: .long LOAD_PHASE\nL_pixload: .long PIX_LOAD\nL_pool: .long BACKUP_POOL\nL_mbx: .long BACKUP_MBX\nL_getblk: .long GET_BLOCK\nL_send: .long SEND\nL_release: .long RELEASE\nL_memset: .long MEMSET\nL_saveid: .long SAVE_ID\nL_beats: .long KEY_BEATS\nL_ledst: .long LED_STATE\nL_ledtab: .long LEDTAB\nL_sender: .long SENDER\nL_journal: .long JOURNAL\nL_jend: .long JOURNAL_END\nCODE_END:\n; ---- he_dj: r8 = a beat jump's target (1/150 s), r11 -> ST2. The jump made\n; by the DSP itself (dspjump): its playing head moves by n slots of 1/75 s in\n; the audio it has decoded, at once and without a gap, the CUE point stays.\n; n = the jump (BJ_DELTA, ms) * 75 / 1000, rounded, the rest carried to the\n; next jump (BJX +16), so jumps never drift. Playing (1), paused (3) or at\n; cue standby (0x17, or 3 after some seconds there): standing, he_djw pauses\n; the UI afterwards, so PLAY plays on from the new place and the CUE point\n; stays. Any other state, or a jump past DJ_MAX: dropped. (A jump within the\n; 0.1 s before the preload starts is undone by its CUE back: rare.)\n; -> r0 = 1 always (he_djw takes the result). BJX: +0 under way, +4 target,\n; +8 tick, +12 n, +16 rest (1/75 000 s), +20 the rest before, +28 x, +48 1 =\n; standing.\n .align 2\nhe_dj:\n sts.l pr,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l J_bjx,r9\ndj_try:\n mov.l J_pos,r0\n jsr @r0\n nop\n mov r1,r0\n mov #0,r2\n cmp/eq #1,r0\n bt dj_st\n mov #1,r2 ; standing: paused (3), cue standby (0x17, 3\n cmp/eq #3,r0 ; after some seconds there)\n bt dj_st\n cmp/eq #23,r0\n bf dj_no\ndj_st:\n mov.l r2,@(48,r9) ; he_djw pauses the UI then\n mov.l J_delta,r1\n mov.l @r1,r0\n mov #75,r1\n mul.l r1,r0\n sts macl,r4\n mov.l @(16,r9),r0\n mov.l r0,@(20,r9) ; the rest, should the DSP refuse\n add r0,r4 ; x = ms * 75 + rest\n mov.l r4,@(28,r9)\n mov #0,r10\n cmp/pz r4\n bt dj_abs\n neg r4,r4\n mov #1,r10\ndj_abs:\n mov.l J_500,r1\n add r1,r4\n mov.l J_recip,r1\n dmulu.l r1,r4\n sts mach,r0\n shlr2 r0\n shlr2 r0\n shlr2 r0 ; |n| = (|x| + 500) / 1000\n mov.l J_max,r1\n cmp/hi r1,r0\n bt dj_no\n tst r10,r10\n bt dj_sgn\n neg r0,r0\ndj_sgn:\n mov r0,r10 ; n\n mov.l r10,@(12,r9)\n mov.l J_1000,r1\n mul.l r1,r0\n sts macl,r1\n mov.l @(28,r9),r0\n sub r1,r0\n mov.l r0,@(16,r9) ; rest = x - 1000 n\n mov.l J_mb,r4\n add #4,r4\n mov.l J_wr,r0\n jsr @r0\n mov r10,r5\n mov.l J_mb,r4\n mov.l J_wr,r0\n jsr @r0\n mov #1,r5\n mov.l r8,@(4,r9)\n mov.l J_tick,r0\n mov.l @r0,r0\n mov.l r0,@(8,r9)\n mov #1,r0\n bra dj_out\n mov.l r0,@r9\ndj_no:\n mov #1,r0 ; not started: dropped (as if taken care of)\ndj_out:\n mov.l @r15+,r10\n mov.l @r15+,r9\n lds.l @r15+,pr\n rts\n nop\n\n; ---- he_djw: each poll, a DSP jump's result: done (the mailbox's command\n; back to 0, result 1), refused (2) or no answer in DJ_TICKS. Refused while\n; playing (the DSP keeps a margin of decoded audio ahead, which it fills on),\n; it is asked for again, the same n, until DJ_RETRY has passed; then, and\n; refused standing or unanswered, the jump is dropped and the rest goes back.\n; (A slot load or a memory CALL instead left the deck standing or the UI\n; frozen, hence none.)\nhe_djw:\n sts.l pr,@-r15\n mov.l r9,@-r15\n mov.l J_bjx,r9\n mov.l @r9,r0\n tst r0,r0\n bt djw_ret\n mov.l J_mb,r4\n mov.l J_rd,r0\n jsr @r0\n nop\n tst r0,r0\n bt djw_done\n mov.l J_tick,r0\n mov.l @r0,r0\n mov.l @(8,r9),r1\n sub r1,r0\n mov #DJ_TICKS,r1\n cmp/hs r1,r0\n bf djw_ret\n mov.l J_mb,r4 ; no answer: called off\n mov.l J_wr,r0\n jsr @r0\n mov #0,r5\n bra djw_fail\n nop\ndjw_done:\n mov.l J_mb,r4\n add #8,r4\n mov.l J_rd,r0\n jsr @r0\n nop\n cmp/eq #1,r0\n bt djw_fine\n mov.l @(48,r9),r0 ; refused: playing, the DSP decodes on ahead,\n tst r0,r0 ; so ask again (the same n) for DJ_RETRY\n bf djw_fail\n mov.l J_tick,r0\n mov.l @r0,r0\n mov.l @(8,r9),r1\n sub r1,r0\n mov.l J_retry,r1\n cmp/hs r1,r0\n bt djw_fail\n mov.l J_mb,r4\n mov.l J_wr,r0\n jsr @r0\n mov #1,r5\n bra djw_ret\n nop\ndjw_fine:\n mov.l @(48,r9),r0\n tst r0,r0\n bt djw_ok\n mov.l J_af28,r1 ; standing: the UI paused (0x0AF3AF28 5), so\n mov #5,r0 ; PLAY plays on from where the deck now is\n mov.l r0,@r1 ; (from cue standby it plays the CUE point)\ndjw_ok:\n mov #0,r0\n bra djw_ret\n mov.l r0,@r9\ndjw_fail:\n mov #0,r0 ; dropped: the rest as it was\n mov.l r0,@r9\n mov.l @(20,r9),r0\n mov.l r0,@(16,r9)\ndjw_ret:\n mov.l @r15+,r9\n lds.l @r15+,pr\n rts\n nop\n .align 4\nJ_bjx: .long BJX\nJ_delta: .long BJ_DELTA\nJ_pos: .long he_pos\nJ_500: .long 500\nJ_recip: .long 0x10624DD3\nJ_max: .long DJ_MAX\nJ_1000: .long 1000\nJ_mb: .long DSPJ_MB\nJ_wr: .long HPI_WRITE\nJ_rd: .long HPI_READ\nJ_tick: .long TICK\nJ_bjreq: .long BJ_REQ\nJ_bit31: .long 0x80000000\nJ_retry: .long DJ_RETRY\nJ_af28: .long 0x0AF3AF28\n\n; ---- he_shadow: r11 -> ST2, every poll. While the screen is held, UI_AFAC\n; is kept 0 (no CALL shows; other UI code than the event handler writes it\n; during a CALL too); otherwise it is left alone. The overview's playhead\n; reads SH_OVW, a copy of the player status block OVW_SRC, whose position\n; word stays while held.\nhe_shadow:\n mov r11,r3\n add #127,r3\n add #61,r3 ; ST4 + 60\n mov.b @r3,r4 ; held\n mov.l S_ctx,r1 ; but not while a track loads (player state 0,\n mov #67,r0 ; 2, 4): the UI must learn of the new track\n shll2 r0 ; (the engine starts over once it did)\n mov.l @(r0,r1),r0\n tst r0,r0\n bt hsh_free\n cmp/eq #2,r0\n bt hsh_free\n cmp/eq #4,r0\n bf hsh_held\nhsh_free:\n mov #0,r4\nhsh_held:\n tst r4,r4\n bt hsh_blk\n mov #0,r0\n mov.l S_afac,r1\n mov.l r0,@r1\nhsh_blk:\n mov.l S_ovsrc,r1\n mov.l S_ovsh,r2\n mov #72,r6\n shll r6 ; 0x90\n mov #0,r3\nhsh_loop:\n mov r3,r0\n cmp/eq #20,r0 ; the position's word\n bf hsh_copy\n tst r4,r4\n bf hsh_next ; held: as it was\nhsh_copy:\n mov.l @(r0,r1),r5\n mov.l r5,@(r0,r2)\nhsh_next:\n add #4,r3\n cmp/eq r6,r3\n bf hsh_loop\n rts\n nop\n\n; ---- pub_hook: in the player's position publisher, in place of its units ->\n; ms conversion (PUB_CONV: r4 = the position in 1/150 s, r5 = the speed ->\n; r0 = ms, stored at PLAYER_CTX + 0x2CC right after; +0x2C8 got the units\n; just before). Free, both are remembered (PUB_HELD); held, the remembered\n; ones are put back: +0x2C8 and the ms returned (+0x2C4, which the player's\n; CALL itself needs, is left alone; the UI does not take it while held).\npub_hook:\n sts.l pr,@-r15\n mov.l r4,@-r15\n mov.l Q_conv,r1\n jsr @r1\n nop\n mov.l @r15+,r4\n mov.l Q_hold,r1\n mov.b @r1,r1\n tst r1,r1\n bf pq_held\n mov.l Q_held,r1 ; free: remembered\n mov.l r4,@r1\n mov.l r0,@(4,r1)\n lds.l @r15+,pr\n rts\n nop\npq_held:\n mov.l Q_held,r1\n mov.l @r1,r2 ; the position as it was\n mov.l @(4,r1),r3 ; its ms\n mov.l Q_ctx,r1\n mov #-78,r0\n extu.b r0,r0\n shll2 r0 ; 0x2C8\n mov.l r2,@(r0,r1)\n mov r3,r0\n lds.l @r15+,pr\n rts\n nop\n .align 4\nS_ovsrc: .long OVW_SRC\nS_afac: .long UI_AFAC\nS_ctx: .long PLAYER_CTX\nS_ovsh: .long SH_OVW\nQ_conv: .long PUB_CONV\nQ_hold: .long HOLD_FLAG\nQ_held: .long PUB_HELD\nQ_ctx: .long PLAYER_CTX\n\n; ---- evt_hook: called from the UI's player-event handler (0x08B3BCC2) in\n; place of 'mov #-1,r7', r12 = the player's state, r11 = the event's kind.\n; Unless the screen is held (HOLD_FLAG; nor while a track loads, player\n; state 0, 2, 4) the six bytes the handler would write are written here:\n; UI_STATE, UI_KIND, and 0xFF into UI_STALE and the three at UI_STALE3 (the\n; UI then fetches the track's info again). Held, they are dropped: the UI\n; keeps the state and info it has. The handler's own stores land in\n; SH_SINK (r1, and its other literals). r7 = -1 as the instruction was.\nevt_hook:\n mov #-1,r7\n mov.l V_hold,r1\n mov.b @r1,r0\n tst r0,r0\n bt ev_write\n mov.l V_ctx,r1\n mov #67,r0\n shll2 r0\n mov.l @(r0,r1),r0 ; the player's state\n tst r0,r0\n bt ev_write\n cmp/eq #2,r0\n bt ev_write\n cmp/eq #4,r0\n bt ev_write\n bra ev_sink\n nop\nev_write:\n mov.l V_state,r1\n mov.b r12,@r1\n mov.l V_kind,r1\n mov.b r11,@r1\n mov.l V_stale,r1\n mov.b r7,@r1\n mov.l V_stale3,r1\n mov.b r7,@r1\n add #1,r1\n mov.b r7,@r1\n add #1,r1\n mov.b r7,@r1\nev_sink:\n mov.l V_sink,r1\n mov.l V_r5,r5\n rts\n nop\n\n; ---- hk_save2: r4 = value (low 16 bits) | id << 16: appended to the\n; settings journal by the Backup task (hk_save's way)\nhk_save2:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l V_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l V_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf sv2_out\n mov.l @r15,r9\n tst r9,r9\n bt sv2_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l V_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov r8,r0\n shlr16 r0\n mov.w r0,@(8,r9) ; the id\n mov r8,r0\n mov.w r0,@(10,r9) ; the value\n mov.l V_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l V_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt sv2_out\n mov.l V_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l V_relblk,r1\n jsr @r1\n nop\nsv2_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- hk_load2: r4 = an id, r5 = its default -> r0 = the last value of that\n; id in the settings journal, else the default\nhk_load2:\n mov.l V_journal,r2\n mov.l V_jend,r3\n mov #-1,r6\n extu.w r6,r6\n mov r5,r7\nhl2_loop:\n cmp/hs r3,r2\n bt hl2_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt hl2_out\n cmp/eq r4,r0\n bf hl2_next\n mov.w @(2,r2),r0\n extu.w r0,r0\n mov r0,r7\nhl2_next:\n bra hl2_loop\n add #4,r2\nhl2_out:\n rts\n mov r7,r0\n .align 4\nV_hold: .long HOLD_FLAG\nV_ctx: .long PLAYER_CTX\nV_state: .long UI_STATE\nV_kind: .long UI_KIND\nV_stale: .long UI_STALE\nV_stale3: .long UI_STALE3\nV_sink: .long SH_SINK\nV_r5: .long EVT_R5\nV_pool: .long BACKUP_POOL\nV_getblk: .long GET_BLOCK\nV_memset: .long MEMSET\nV_mbx: .long BACKUP_MBX\nV_send: .long SEND\nV_relblk: .long RELEASE\nV_journal: .long JOURNAL\nV_jend: .long JOURNAL_END\n\n; ---- afac_hook: r11 = a player event's kind, called from the UI's\n; player-event handler in place of its switch (AFAC_SWITCH): UI_AFAC as the\n; switch set it (2, 3, 4 and others 0; 5 -> 1, 6 -> 2, 7 -> 3; 1 and 9\n; leave it), unless the screen is held (then no CALL shows). r0-r2 used.\nafac_hook:\n mov.l A_hold,r1\n mov.b @r1,r0\n tst r0,r0\n bf ah_ret\n mov r11,r0\n cmp/eq #1,r0\n bt ah_ret\n cmp/eq #9,r0\n bt ah_ret\n mov #1,r2\n cmp/eq #5,r0\n bt ah_set\n mov #2,r2\n cmp/eq #6,r0\n bt ah_set\n mov #3,r2\n cmp/eq #7,r0\n bt ah_set\n mov #0,r2\nah_set:\n mov.l A_afac,r1\n mov.l r2,@r1\nah_ret:\n rts\n nop\n .align 4\nA_hold: .long HOLD_FLAG\nA_afac: .long UI_AFAC\n\n; ---- he_lock: r11 -> ST2, every engine pass. Loading: the screen held for\n; the preload (ST4 +60) or the waveform hidden while the deck waits to\n; preload after a track load (ST4 +62). Then the deck's keys (LOCK_MASK at\n; LOCK_BASE) are taken out of the decoded keys, a beat jump waiting is\n; dropped, and LOAD_PHASE is 1 every other 0.32 s: the HOT KEYS box shows\n; LOADING then (hk_res). When the phase changes the screen is redrawn, as\n; hk_poll does for the mode (the root of the QUANTIZE widget remembered at\n; HK_SETTING +8 gets +0x58 = 1).\nhe_lock:\n mov r11,r3\n add #127,r3\n add #61,r3 ; ST4 + 60\n mov.b @r3,r0\n mov r0,r2\n mov.b @(2,r3),r0 ; ST4 + 62\n or r0,r2\n mov #0,r0\n tst r2,r2\n bt hl_phase\n mov.l K2_base,r1 ; the keys\n mova K2_mask,r0\n mov r0,r3\n mov #11,r2\nhl_keys:\n mov.b @r3+,r0\n not r0,r0\n mov.b @r1,r4\n and r0,r4\n mov.b r4,@r1\n dt r2\n bf/s hl_keys\n add #1,r1\n mov.l K2_bjreq,r1 ; no beat jump\n mov #0,r0\n mov.l r0,@r1\n mov.l K2_tick,r1 ; the phase: 0.32 s on, 0.32 s off\n mov.l @r1,r0\n tst #32,r0\n movt r0\n xor #1,r0\nhl_phase:\n mov.l K2_phase,r1\n mov.b r0,@r1\n mov r0,r2\n mov.b @(1,r1),r0\n cmp/eq r2,r0\n bt hl_ret\n mov r2,r0\n mov.b r0,@(1,r1) ; changed: redraw\n mov.l K2_setting,r1\n mov.l @(8,r1),r4\nhl_root:\n tst r4,r4\n bt hl_ret\n mov.b @(4,r4),r0\n cmp/eq #20,r0\n bt hl_mark\n bra hl_root\n mov.l @(8,r4),r4\nhl_mark:\n mov #1,r2\n mov #88,r0\n mov.l r2,@(r0,r4)\nhl_ret:\n rts\n nop\n .align 4\nK2_base: .long LOCK_BASE\nK2_bjreq: .long BJ_REQ\nK2_tick: .long TICK\nK2_phase: .long LOAD_PHASE\nK2_setting: .long HK_SETTING\nK2_mask: .long LOCK_W0\n .long LOCK_W1\n .long LOCK_W2\n .align 4\n\n'''\n\n\ndef layout(code_len, box_len):\n \"\"\"Addresses after the code: the new UTILITY table, the label, the two\n boxes, the widget copy and the resource record.\"\"\"\n at = (HK_CODE + code_len + 3) & ~3\n table = at\n at += (UT_ITEMS + 1) * UT_ITEM\n label = at\n at += (len(LABEL) + 1) * 2\n at = (at + 3) & ~3\n widget = at\n at += 0x50\n rec = at\n at += 44\n ledtab = at\n at += 2 * len(FRAME_LEDS)\n at = (at + 3) & ~3\n nohot = at # no hot cues, for a build without cuecolours\n at += 8 * len(FRAME_LEDS)\n assert at <= HK_END, 'hotcuekeys: past its area'\n # the boxes in the second area, aligned as the blitter wants\n pix_on = (ST4 + 64 + 31) & ~31\n pix_off = (pix_on + box_len + 31) & ~31\n # the PCP2 writer's copy of a new hot cue, its extra data per number,\n # and the colours set hot cues get on the screen\n pcbuf = (pix_off + box_len + 3) & ~3\n pcblob = pcbuf + PCBUF_LEN\n hotcol = pcblob + 8 * PCBLOB_LEN\n assert hotcol + 16 <= HK2_END, 'hotcuekeys: past its second area'\n return dict(TABLE=table, LABEL=label, PIX_ON=pix_on, PIX_OFF=pix_off, WIDGET=widget, REC=rec,\n LEDTAB=ledtab, NOHOT=nohot, PCBUF=pcbuf, PCBLOB=pcblob, HOTCOL=hotcol, END=at)\n\n\ndef pcp2_blob(n):\n \"\"\"The extra data the PCP2 writer (0x08B10BE0) takes from a cue read from\n a file, as rekordbox writes it for hot cue n with no comment: its fields\n +0x0C (high half), +0x1C, +0x1D, +0x1E..+0x2A, a block length (4) and\n the colour block (code, R, G, B).\"\"\"\n code, r, g, b = HOT_COLOURS[(n - 1) % len(HOT_COLOURS)]\n blob = bytes(2) + bytes((0, 1)) + bytes(14) + struct.pack('> 3) << 11 | (g >> 2) << 5 | b >> 3\n\n\ndef routine(prev_decoder, hot=None, box_len=88 * 15 * 2):\n syms = {\n 'HK_SETTING': HK_SETTING, 'KEY_QUANTIZE': KEY_QUANTIZE, 'TICK': TICK, 'LONG_TICKS': LONG_TICKS,\n 'SHOW_SAVER': SHOW_SAVER, 'DRAW': DRAW, 'RESOLVE': RESOLVE, 'RES_ANCHOR': RES_ANCHOR, 'DIGIT0': RES_DIGITS[0], 'DIGIT1': RES_DIGITS[1],\n 'RES_QUANTIZE': RES_QUANTIZE, 'RES_HK': RES_HK, 'BOX_DX': BOX_DX, 'PREV_DECODER': prev_decoder,\n 'WIDGET': 0, 'REC': 0, 'PIX_ON': 0, 'PIX_OFF': 0, 'LEDTAB': 0, 'PCBUF': 0, 'PCBLOB': 0, 'HOTCOL': 0,\n 'PCP2': PCP2, 'PCBLOB_LEN': PCBLOB_LEN, 'PCBLOB_USED': PCBLOB_USED,\n 'BACKUP_POOL': BACKUP_POOL, 'BACKUP_MBX': BACKUP_MBX, 'GET_BLOCK': GET_BLOCK, 'SEND': SEND,\n 'RELEASE': RELEASE, 'MEMSET': MEMSET, 'SAVE_ID': SAVE_ID, 'JOURNAL': JOURNAL,\n 'JOURNAL_END': JOURNAL + JOURNAL_LEN, 'KEY_BEATS': KEY_BEATS, 'LED_STATE': LED_STATE,\n 'BEAT_BITS': BEAT_BITS, 'SENDER': SENDER, 'HIDE_MASK': 0xFF & ~BEAT_BITS,\n 'HOT': 0, 'CALL_REC': CALL_REC, 'CALL_KEY': CALL_KEY, 'PLAYER_CTX': PLAYER_CTX, 'BUILD': BUILD,\n 'BLINK_TICKS': BLINK_TICKS, 'GATE_TICKS': GATE_TICKS, 'CUE_POS': CUE_POS, 'ST2': ST2, 'ST2_SILENT': ST2_SILENT, 'UI_AFAC': UI_AFAC, 'SH_AFAC': SH_AFAC, 'PUB_HELD': PUB_HELD, 'HOLD_FLAG': HOLD_FLAG, 'PUB_CONV': PUB_CONV, 'SH_OVW': SH_OVW, 'LOAD_PHASE': LOAD_PHASE, 'PIX_LOAD': PIX_LOAD, 'LOCK_BASE': LOCK_BASE, 'LOCK_W0': int.from_bytes(bytes(LOCK_MASK[0:4]), 'little'), 'LOCK_W1': int.from_bytes(bytes(LOCK_MASK[4:8]), 'little'), 'LOCK_W2': int.from_bytes(bytes(LOCK_MASK[8:11]) + bytes(1), 'little'), 'UI_STATE': UI_STATE, 'UI_KIND': UI_KIND, 'UI_STALE': UI_STALE, 'UI_STALE3': UI_STALE3, 'SH_SINK': SH_SINK, 'EVT_R5': EVT_R5, 'DJ_RETRY': DJ_RETRY, 'OVW_SRC': OVW_SRC, 'KEY_PLAYCUE': KEY_PLAYCUE,\n 'INJ_WAIT': INJ_WAIT, 'INJ_UNTIL': INJ_UNTIL, 'HPI_READ': HPI_READ, 'HPI_WRITE': HPI_WRITE, 'WV_MID': WV_MID, 'WV_R12': WV_R12, 'WV_R13': WV_R13, 'WV_OUT': WV_OUT, 'WV_TICKS': WV_TICKS, 'SLOT_ST': SLOT_ST, 'SLOT_PT': SLOT_PT, 'TRANSPORT': TRANSPORT, 'PLAYCMD': PLAYCMD, 'CYCLIC': CYCLIC, 'PRELOAD_TICKS': PRELOAD_TICKS, 'BJX': BJX, 'BJ_SETTLE': BJ_SETTLE, 'BJ_POSV': BJ_POSV, 'BJ_REQ': BJ_REQ, 'BJ_DELTA': BJ_DELTA, 'DSPJ_MB': DSPJ_MB, 'DJ_TICKS': DJ_TICKS, 'DJ_MAX': DJ_MAX, 'MUTE_TICKS': MUTE_TICKS, 'BACK_TICKS': BACK_TICKS, 'MUTE_MAX': MUTE_MAX,\n 'LOAD_TICKS': LOAD_TICKS, 'PRELOAD_NEAR': PRELOAD_NEAR, 'PEND_EVERY': PEND_EVERY, 'PEND_TICKS': PEND_TICKS,\n 'UI_SENDER': UI_SENDER, 'DB_REQ_REG': DB_REQ_REG, 'DB_REG': DB_REG, 'DB_DEL': DB_DEL,\n 'UI_DEVICE': UI_DEVICE, 'UI_DB_BUSY': UI_DB_BUSY, 'UI_DB_MODE': UI_DB_MODE, 'UI_DB_WHICH': UI_DB_WHICH,\n 'UI_DB_READY': UI_DB_READY, 'UI_MEDIA': UI_MEDIA, 'USB_STATE': USB_STATE, 'GONE_ST': GONE_ST, 'STATUS_TAB': STATUS_TAB, 'STATUS_LEN': STATUS_LEN, 'STATUS_BUF': STATUS_BUF, 'UI_USB_OK': UI_USB_OK, 'ST4': ST4, 'DBQ_N': DBQ_N,\n 'DBQ_TICKS': DBQ_TICKS, 'HOT_KEYS': HOT_KEYS, 'BJ_SLOT': BJ_SLOT, 'ST3': ST3, 'BJ_GAP0': BJ_GAP0, 'MARKERS_DIRTY': MARKERS_DIRTY, 'KEY_DELETE': KEY_DELETE,\n 'DEL_BIT': DEL_BIT, 'DEL_KEEP': 0xFF & ~DEL_BIT, 'GREEN': cuecolours.HOT_DEFAULT, 'RESUME_POLLS': RESUME_POLLS,\n }\n code, labels = sh4asm.assemble(SOURCE, HK_CODE, syms)\n lay = layout(len(code), box_len)\n syms.update({k: lay[k] for k in ('WIDGET', 'REC', 'PIX_ON', 'PIX_OFF', 'LEDTAB', 'PCBUF', 'PCBLOB', 'HOTCOL')})\n syms['HOT'] = hot or lay['NOHOT']\n code, labels = sh4asm.assemble(SOURCE, HK_CODE, syms)\n assert layout(len(code), box_len) == lay\n labels.update(lay)\n return code, labels\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes.\"\"\"\n g24, g32, g36, g44, g48 = GENERIC\n return struct.pack('<13I', labels['LABEL'], ONOFF_LIST, 2, 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['hk_show'], g32, g36, labels['hk_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with HOT CUE KEYS added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n prev = struct.unpack_from(' KEY shows from the firmware's own table) is drawn from KX_BUF,\n rewritten by kn_xlate; one key also in its colour if the text is white\n (a highlighted row's black stays). The widget's +52 and +40 are put\n back after the draw. So the title bar, INFO and BROWSE > KEY all follow\n KEY NOTATION. The header line (HEADER_TEXT: the title, BROWSE's header,\n SEARCH's typed letters, where \"A\" would read 11B) is left alone. (A text\n that is only a key name by chance, an artist called \"B\" say, is taken\n for one too.)\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.2.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nKN_BASE = 0x08FD7C00 # after ledsaver's 0x08FD6C00..0x08FD7C00\nKN_END = 0x08FD8C00\nKN_SETTING = KN_BASE # byte: 0 as exported, 1 Camelot, 2 Open Key, 3 classic\nDEFAULT = 1\nKN_LOADED = KN_BASE + 1 # byte: the saved setting read\nKX_TMP = KN_BASE + 4 # 8 bytes: one key of a text, zero-padded (kn_xlate)\nKX_BUF = KN_END - 0x40 # 64 bytes: a text rewritten in our notation (kn_xlate)\nKN_CODE = KN_BASE + 0x10\n\nWCSNCPY = 0x08C0586E\nCOPY_LIT = 0x0897EB3C # 0x0897E5E2's literal of it\nKEY_RET = 0x0897E980 # the return of its copy of the key's name\nDRAW_LIT = 0x080B27F4 # the text widget's draw method in its class table\nDRAW_TEXT = 0x08B303BC # that method\nHEADER_TEXT = 0x0B7CE62E # the header line's text (title bar title, BROWSE's header, SEARCH's input)\nSAVE_ID = 0x83E3 # ours (hotcuekeys 0x83E1, ledsaver 0x83E2)\nLABEL = 'KEY NOTATION'\nVALUES = ('OFF', 'CAMELOT', 'OPEN KEY', 'CLASSIC')\n\n# Camelot 1A, 1B, 2A, ... 12B in rekordbox's classic spelling (the\n# firmware's table 0x08C46FE4 in the same order)\nCLASSIC = ('Abm', 'B', 'Ebm', 'F#', 'Bbm', 'Db', 'Fm', 'Ab', 'Cm', 'Eb', 'Gm', 'Bb',\n 'Dm', 'F', 'Am', 'C', 'Em', 'G', 'Bm', 'D', 'F#m', 'A', 'Dbm', 'E')\nALIASES = {'G#m': 0, 'D#m': 2, 'Gb': 3, 'A#m': 4, 'C#': 5, 'G#': 7, 'D#': 9, 'A#': 11,\n 'Gbm': 20, 'C#m': 22}\n\n\n# per key 1A, 1B, 2A, ... 12B: its colour on the user's wheel picture (RGB)\nCOLOURS = (0x02B399, 0x01E7CB, 0x06A956, 0x00CC65, 0x9AFE66, 0x66FF33, 0xFFDD67, 0xFFCC01,\n 0xFFAA67, 0x3C81F6, 0x3C81F6, 0x12B981, 0xFF99CB, 0x3C81F6, 0xFEAADC, 0xFF00CD,\n 0xCC99FE, 0xCC33FF, 0x99A9FF, 0x9966FF, 0x66BBFE, 0x6599FF, 0x00CCCB, 0x03FFFF)\n\n\ndef abgr(rgb):\n \"\"\"0xRRGGBB as the widget's colour word, 0xAABBGGRR, opaque.\"\"\"\n return 0xFF000000 | (rgb & 0xFF) << 16 | (rgb & 0xFF00) | rgb >> 16\n\n\ndef camelot(i):\n \"\"\"i = 0..23 (1A, 1B, 2A, ...): its Camelot spelling.\"\"\"\n return '%d%s' % (i // 2 + 1, 'AB'[i % 2])\n\n\ndef openkey(i):\n \"\"\"i = 0..23: its Open Key spelling (8A = 1m, 8B = 1d).\"\"\"\n return '%d%s' % ((i // 2 + 1 + 4) % 12 + 1, 'md'[i % 2])\n\n\ndef spellings():\n \"\"\"[(text, i)]: every spelling taken, per key its Camelot, Open Key and\n classic one, then the sharps (interleaved, so no run of the table looks\n like the firmware's own lists).\"\"\"\n out = []\n for i in range(24):\n out += [(camelot(i), i), (openkey(i), i), (CLASSIC[i], i)]\n out += sorted(ALIASES.items(), key=lambda kv: kv[1])\n return out\n\n\ndef entry(text):\n \"\"\"8 bytes: up to three UTF-16 characters, zero-padded.\"\"\"\n assert 1 <= len(text) <= 3\n return text.encode('utf-16-le').ljust(8, b'\\0')\n\n\ndef tables():\n \"\"\"(MATCH, INDEX, OUT): the spellings and their key numbers, then per\n key its Camelot, Open Key and classic spelling.\"\"\"\n sp = spellings()\n match = b''.join(entry(t) for t, _ in sp)\n index = bytes(i for _, i in sp)\n out = b''.join(entry(f(i)) for i in range(24) for f in (camelot, openkey, CLASSIC.__getitem__))\n return match, index, out\n\n\nSOURCE = r'''\n; ---- kn_copy: wcsncpy (r4 dst, r5 src, r6 n) through 0x0897E5E2's literal;\n; its copy of the key's name (returning to KEY_RET) then in our notation\nkn_copy:\n sts.l pr,@-r15\n mov.l r4,@-r15\n mov.l K_wcsncpy,r0\n jsr @r0\n nop\n mov.l @r15+,r4\n lds.l @r15+,pr\n mov.l K_ret,r1\n sts pr,r2\n cmp/eq r1,r2\n bt kn_key\n rts\n nop\nkn_key:\n mov.l r0,@-r15 ; wcsncpy's result, returned as it is\n sts.l pr,@-r15\n bsr kn_conv\n nop\n lds.l @r15+,pr\n rts\n mov.l @r15+,r0\n\n; ---- kn_conv: r4 -> a UTF-16 key name, rewritten in place in the chosen\n; notation if it is one of the spellings in MATCH (r0-r7 used)\nkn_conv:\n mov.l K_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf kc_loaded\n sts.l pr,@-r15\n bsr kn_load ; the first time: the saved setting\n nop\n lds.l @r15+,pr\n mov.l K_state,r1\nkc_loaded:\n mov.b @r1,r0\n extu.b r0,r7 ; 1 Camelot, 2 Open Key, 3 classic\n tst r7,r7\n bt kc_ret ; 0: as exported\n sts.l pr,@-r15\n bsr kn_lookup\n nop\n lds.l @r15+,pr\n cmp/pz r0\n bt kc_found\nkc_ret:\n rts\n nop\nkc_found:\n mov r0,r1\n shll r0\n add r1,r0 ; key * 3\n add #-1,r7\n add r7,r0 ; + notation\n shll2 r0\n shll r0 ; * 8\n mov.l K_out,r1\n add r1,r0\n mov #4,r6\nkc_put:\n mov.w @r0+,r1\n mov.w r1,@r4\n dt r6\n bf/s kc_put\n add #2,r4\n rts\n nop\n\n; ---- kn_lookup: r4 -> a UTF-16 key name; r0 = its key 0..23 (1A, 1B, 2A,\n; ...) if it is one of the spellings in MATCH, else -1 (r1-r3, r5, r6 used)\nkn_lookup:\n mov r4,r5 ; the name, zero-padded: r2 = chars 0-1, r3 = 2-3\n mov #0,r2\n mov #0,r3\n mov.w @r5+,r0\n extu.w r0,r0\n tst r0,r0\n bt kl_none ; empty\n mov r0,r2\n mov.w @r5+,r0\n extu.w r0,r0\n tst r0,r0\n bt kc_find\n shll16 r0\n or r0,r2\n mov.w @r5+,r0\n extu.w r0,r0\n tst r0,r0\n bt kc_find\n mov r0,r3\n mov.w @r5+,r0\n tst r0,r0\n bf kl_none ; longer than 3: no key\nkc_find:\n mov.l K_match,r5\n mov #NMATCH,r6\n mov #0,r0\nkc_loop:\n mov.l @r5+,r1\n cmp/eq r1,r2\n bf/s kc_next\n mov.l @r5+,r1\n cmp/eq r1,r3\n bt kc_found2\nkc_next:\n add #1,r0\n cmp/eq r6,r0\n bf kc_loop\nkl_none:\n rts\n mov #-1,r0\nkc_found2:\n mov.l K_index,r1\n mov.b @(r0,r1),r0\n rts\n extu.b r0,r0 ; 0..23: 1A, 1B, 2A, ...\n\n; ---- kn_draw: the text widgets' draw method (DRAW_TEXT) through its class\n; table entry; r4 the renderer, r5 the widget. A text of keys only is drawn\n; in our notation (kn_xlate), one key in its wheel colour if the text is\n; white; +52 and +40 put back after. Anything else: the method as it is.\nkn_draw:\n mov.l @(52,r5),r0\n tst r0,r0\n bt kd_go\n mov.l D_header,r1 ; not the header line (SEARCH's typed text)\n cmp/eq r1,r0\n bt kd_go\n mov.w @r0,r0 ; a key starts with 1-9 or A-G\n extu.w r0,r0\n add #-49,r0\n mov #22,r1\n cmp/hi r1,r0\n bt kd_go\n mov.l D_state,r1\n mov.b @r1,r0\n tst r0,r0\n bt kd_go ; OFF\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov r4,r8\n mov r5,r9\n mov.l @(52,r9),r10 ; the widget's text and colour, put back after\n mov.l @(40,r9),r11\n bsr kn_xlate\n mov r10,r4\n tst r0,r0\n bt kd_plain ; not keys\n mov.l r0,@(52,r9)\n cmp/pz r1\n bf kd_draw ; a list: no colour\n mov #-1,r0\n cmp/eq r0,r11\n bf kd_draw ; not white: as it is\n shll2 r1\n mov.l D_colours,r0\n mov.l @(r0,r1),r0\n mov.l r0,@(40,r9)\nkd_draw:\n mov r8,r4\n mov.l D_draw,r0\n jsr @r0\n mov r9,r5\n mov.l r10,@(52,r9)\n bra kd_out\n mov.l r11,@(40,r9)\nkd_plain:\n mov r8,r4\n mov.l D_draw,r0\n jsr @r0\n mov r9,r5\nkd_out:\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\nkd_go:\n mov.l D_draw,r0\n jmp @r0\n nop\n\n; ---- kn_xlate: r4 -> a UTF-16 text. Keys only (one, or several separated\n; by \",\" and spaces): r0 = KX_BUF holding it in the chosen notation, r1 =\n; the key (0..23) if it is one, else -1. Anything else: r0 = 0.\nkn_xlate:\n sts.l pr,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n add #-8,r15 ; @r15 the key's end (0 or ','), +4 the key\n mov r4,r12\n mov.l D_buf,r13\n mov #0,r14 ; keys\nkx_tok:\n mov.l D_tmp,r1 ; the next key into KX_TMP, up to 3 characters\n mov #0,r0\n mov.l r0,@r1\n mov.l r0,@(4,r1)\n mov #4,r3\nkx_ch:\n mov.w @r12,r0\n extu.w r0,r0\n tst r0,r0\n bt kx_eot\n cmp/eq #44,r0 ; ','\n bt kx_eot\n dt r3\n bt kx_fail ; longer than 3: not a key\n mov.w r0,@r1\n add #2,r1\n bra kx_ch\n add #2,r12\nkx_eot:\n mov.l r0,@r15\n mov #4,r1\n cmp/eq r1,r3\n bt kx_fail ; empty\n mov.l D_tmp,r4\n bsr kn_lookup\n nop\n cmp/pz r0\n bf kx_fail\n mov.l r0,@(4,r15)\n add #1,r14\n mov r0,r1 ; OUT + (key * 3 + notation - 1) * 8\n shll r0\n add r1,r0\n mov.l D_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n add #-1,r1\n add r1,r0\n shll2 r0\n shll r0\n mov.l D_out,r1\n add r1,r0\n mov #4,r3\nkx_put:\n mov.w @r0+,r1\n extu.w r1,r1\n tst r1,r1\n bt kx_put_end\n mov.w r1,@r13\n dt r3\n bf/s kx_put\n add #2,r13\nkx_put_end:\n mov.l D_bufend,r1 ; room for one more key and \", \" at least\n cmp/hs r1,r13\n bt kx_fail\n mov.l @r15,r0\n tst r0,r0\n bt kx_done\n mov.w r0,@r13 ; ',' and the spaces after it\n add #2,r13\n add #2,r12\nkx_sp:\n mov.w @r12,r0\n extu.w r0,r0\n cmp/eq #32,r0\n bf kx_tok\n mov.w r0,@r13\n add #2,r13\n bra kx_sp\n add #2,r12\nkx_done:\n mov #0,r0\n mov.w r0,@r13\n mov #-1,r1\n mov r14,r0\n cmp/eq #1,r0\n bf kx_list\n mov.l @(4,r15),r1\nkx_list:\n mov.l D_buf,r0 ; (not in the delay slot: PC-relative)\n bra kx_ret\n nop\nkx_fail:\n mov #0,r0\n mov #-1,r1\nkx_ret:\n add #8,r15\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n lds.l @r15+,pr\n rts\n nop\n .align 4\nD_state: .long KN_SETTING\nD_header: .long HEADER_TEXT\nD_colours: .long COLOURS\nD_draw: .long DRAW_TEXT\nD_buf: .long KX_BUF\nD_bufend: .long KX_BUFEND\nD_tmp: .long KX_TMP\nD_out: .long OUT\n\n; ---- kn_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nkn_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt kw_ours\n mov.l K_show,r1\n jmp @r1\n nop\nkw_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l K_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf kw_loaded\n mov.l r5,@-r15 ; the row the caller passed in r5: the load uses r5\n bsr kn_load\n nop\n mov.l @r15+,r5\nkw_loaded:\n mov.l K_state,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r8) ; shown as if being chosen\n mov.l K_show,r1\n jsr @r1\n mov r8,r4\n mov.l K_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- kn_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nkn_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt kt_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt kt_none\n mov.l r5,@(12,r4)\n mov.l K_state,r1\n mov r5,r0\n mov.b r0,@r1\n mov #1,r0\n mov.b r0,@(1,r1)\n sts.l pr,@-r15\n bsr kn_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nkt_none:\n rts\n mov #0,r0\n\n; ---- kn_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; as ledsaver's ls_save\nkn_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l K_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l K_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf kv_out\n mov.l @r15,r9\n tst r9,r9\n bt kv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l K_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l K_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l K_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt kv_out\n mov.l K_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l K_release,r1\n jsr @r1\n nop\nkv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- kn_load: the last SAVE_ID in the journal, if any, into KN_SETTING\n; (r0-r3, r5, r6 used)\nkn_load:\n mov.l K_state,r1\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l K_journal,r2\n mov.l K_jend,r3\n mov.l K_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nkl_loop:\n cmp/hs r3,r2\n bt kl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt kl_out\n cmp/eq r5,r0\n bf kl_next\n mov.w @(2,r2),r0\n and #3,r0\n mov.b r0,@r1\nkl_next:\n bra kl_loop\n add #4,r2\nkl_out:\n rts\n nop\n\n .align 4\nK_wcsncpy: .long WCSNCPY\nK_ret: .long KEY_RET\nK_state: .long KN_SETTING\nK_match: .long MATCH\nK_index: .long INDEX\nK_out: .long OUT\nK_show: .long SHOW_SAVER\nK_pool: .long BACKUP_POOL\nK_mbx: .long BACKUP_MBX\nK_getblk: .long GET_BLOCK\nK_send: .long SEND\nK_release: .long RELEASE\nK_memset: .long MEMSET\nK_saveid: .long SAVE_ID\nK_journal: .long JOURNAL\nK_jend: .long JOURNAL_END\n'''\n\n\ndef routine(at=None):\n \"\"\"(code, labels): the code at KN_CODE, then MATCH, INDEX, OUT, the\n item's label, its values and their list, and room for the items.\"\"\"\n h = hotcuekeys\n match, index, out = tables()\n syms = {\n 'WCSNCPY': WCSNCPY, 'KEY_RET': KEY_RET, 'KN_SETTING': KN_SETTING,\n 'MATCH': 0, 'INDEX': 0, 'OUT': 0, 'COLOURS': 0, 'NMATCH': len(index),\n 'HEADER_TEXT': HEADER_TEXT, 'KX_TMP': KX_TMP, 'KX_BUF': KX_BUF, 'KX_BUFEND': KX_BUF + 0x40 - 16, 'DRAW_TEXT': DRAW_TEXT,\n 'SHOW_SAVER': h.SHOW_SAVER, 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX,\n 'GET_BLOCK': h.GET_BLOCK, 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET,\n 'SAVE_ID': SAVE_ID, 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN,\n }\n assert len(index) < 128\n code, labels = sh4asm.assemble(SOURCE, KN_CODE, syms)\n lay = {}\n a = (KN_CODE + len(code) + 3) & ~3\n for name, size in (('MATCH', len(match)), ('INDEX', len(index)), ('OUT', len(out)),\n ('COLOURS', 4 * len(COLOURS))):\n a = (a + 3) & ~3\n lay[name] = a\n a += size\n a = (a + 3) & ~3\n lay['LABEL'] = a\n a += 2 * len(LABEL) + 2\n lay['VALUE'] = []\n for v in VALUES:\n a = (a + 3) & ~3\n lay['VALUE'].append(a)\n a += 2 * len(v) + 2\n a = (a + 3) & ~3\n lay['LIST'] = a\n a += 4 * len(VALUES)\n lay['ITEMS'] = (a + 3) & ~3\n syms.update(MATCH=lay['MATCH'], INDEX=lay['INDEX'], OUT=lay['OUT'], COLOURS=lay['COLOURS'])\n code2, labels2 = sh4asm.assemble(SOURCE, KN_CODE, syms)\n assert len(code2) == len(code)\n labels2.update(lay)\n return code2, labels2, (match, index, out)\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as SCREEN SAVER's with our 4 values.\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], labels['LIST'], len(VALUES), 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['kn_show'], g32, g36, labels['kn_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with KEY NOTATION; its item goes after the UTILITY\n items the other mods left.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' %-4s %-4s %s' % (t, camelot(i), openkey(i), CLASSIC[i]))\n","mods/cdj900nxs/ledsaver.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"LED ANIMATION for the CDJ-900NXS 1.31: while the player's screen saver is\non (its display dimmed), the buttons' LEDs play an animation in a loop. Our\nown mod; mods/patch_cdj900nxs.py applies it as `ledsaver`.\n\n * UTILITY gets one more item, LED ANIMATION (OFF / ON, ON until chosen:\n the image holds LS_SETTING = 1, which a saved choice overrides),\n after the others (after HOT CUE KEYS when hotcuekeys is in). The choice\n is kept in the firmware's settings journal in flash, as HOT CUE KEYS'.\n * The screen saver is the firmware's own (0x08BB7D64): it counts at\n 0x0B7C95D8 (+0 with no track loaded, +4 paused or at the cue) and dims\n the backlight (0x08BD5D2C, event 6) past 0x927C or 0xB71B0; any key\n clears both. ls_poll reads the two counters with those thresholds, so\n the animation runs exactly while the display is dimmed; the dimming\n itself is unchanged.\n * The LEDs are 2-bit fields and bits of the 24-byte frame the deck sends\n the panel (sender 0x08AF488E, through its literal 0x08B653A4; byte 22\n the end-around-carry sum of 0-21). Mapped on a player with the LED test\n builds: byte 0 bits PLAY 0x01, CUE 0x02, REV 0x04, SLIP LOOP 0x08,\n RELOOP/EXIT 0x40, SLIP 0x80 (on/off only); byte 1 fields DISC EJECT\n (bits 0-1) and MASTER TEMPO (2-3), STANDBY (4-5); byte 3 SYNC (0-1) and\n MASTER (2-3); byte 4 BEAT DIVIDE 3/4 1/3 1/2 1/4, byte 5 bits 0-1 1/8\n (levels 0-3); the jog's rings: byte 8 bits 0x30 the white inner circle\n (0x40 there is the firmware's), byte 7 0xFF the red middle ring, byte 9\n a code for the big white outer ring (0x03: the ring with a small gap;\n 0xFF: none). Byte 11 is no LED: 0xFF there put the player into standby.\n Only the fields above (not STANDBY) are rewritten, field by field;\n clearing whole bytes of the frame had the deck drop the panel's replies.\n * ls_send rewrites them from FRAMES (one row per FRAME_TICKS) while the\n animation runs; with hotcuekeys in, its LED levels (HK_LEVELS' flag at\n HK_BASE +13) are kept out of the frame meanwhile.\n\"\"\"\nimport math\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.0.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nLS_BASE = 0x08FD6C00 # after hotcuekeys' second area\nLS_END = 0x08FD7C00\nLS_SETTING = LS_BASE # byte: 1 = UTILITY item ON (DEFAULT until a choice is saved)\nDEFAULT = 1\nLS_LOADED = LS_BASE + 1 # byte: the saved setting read\nLS_ACTIVE = LS_BASE + 2 # byte: the animation runs\nLS_FRAME = LS_BASE + 4 # word: the row of FRAMES\nLS_NEXT = LS_BASE + 8 # word: the tick of the next row\nLS_CODE = LS_BASE + 0x20\n\nSAVER_COUNT = 0x0B7C95D8 # +0, +4: the screen saver's counters\nSAVER_T0 = 0x927C # its thresholds (0x08BB7EA0, 0x08BB7EDA)\nSAVER_T1 = 0xB71B0\nSAVE_ID = 0x83E7 # ours (0.x used 0x83E2, which beatjump uses too: a restart mixed them up)\nLABEL = 'LED ANIMATION'\nFRAME_TICKS = 5 # 50 ms a row\nROWS = 90 # 4.5 s a loop (3 s was hectic on a player)\nOFFS = (0, 1, 3, 4, 5, 7, 8, 9) # the frame bytes it rewrites\n# per byte the bits it owns: cleared, then set from the row\nOWN = {0: 0xCF, 1: 0x0F, 3: 0x0F, 4: 0xFF, 5: 0x03, 7: 0xFF, 8: 0x30, 9: 0xFF}\nJOG_INNER = (8, 0x30) # the white inner circle\nJOG_MIDDLE = (7, 0xFF) # the red ring, all of it\nJOG_OUTER = (9, 0x03) # the big white ring (byte 9 is a code: 0x03 = the ring, a small gap)\n\n\ndef _tri(x, width):\n \"\"\"0..1..0 over width rows, 0 outside.\"\"\"\n if not 0 <= x < width:\n return 0.0\n return math.sin(math.pi * (x + 0.5) / width)\n\n\ndef frames():\n \"\"\"The animation, ROWS rows (4.5 s): a fade wave down the BEAT DIVIDE keys\n and back up, SYNC / MASTER and MASTER TEMPO / DISC EJECT breathing\n against each other, the on/off keys lit one after another and back, the\n jog's rings in turn from the inside out and back in.\"\"\"\n rows = []\n n = ROWS\n chase = (0x02, 0x01, 0x04, 0x80, 0x08, 0x40) # CUE PLAY REV SLIP SLIPLOOP RELOOP\n for t in range(n):\n x = t * 60 / n # drawn in 60 steps a loop\n lv = {}\n # dividers: a pulse 18 steps wide, 5 steps apart, down then up\n for k in range(5):\n down = _tri(x - 5 * k, 18)\n up = _tri(x - 30 - 5 * (4 - k), 18)\n lv['div%d' % k] = round(3 * max(down, up))\n br = 0.5 - 0.5 * math.cos(2 * math.pi * t / n)\n lv['sync'] = round(3 * br)\n lv['master'] = round(3 * (1 - br))\n lv['mt'] = round(3 * (0.5 - 0.5 * math.cos(2 * math.pi * (t / n + 0.25))))\n lv['eject'] = round(3 * (0.5 - 0.5 * math.cos(2 * math.pi * (t / n + 0.75))))\n b0 = 0\n slot = int(x // 5) # 12 slots of 5 steps\n if slot < 6:\n b0 = chase[slot]\n else:\n b0 = chase[11 - slot] # and back\n row = {\n 0: b0,\n 1: lv['eject'] | lv['mt'] << 2,\n 3: lv['sync'] | lv['master'] << 2,\n 4: lv['div0'] | lv['div1'] << 2 | lv['div2'] << 4 | lv['div3'] << 6,\n 5: lv['div4'],\n }\n # the jog's three rings, one at a time: inner, middle, outer, middle, ...\n ring = (JOG_INNER, JOG_MIDDLE, JOG_OUTER, JOG_MIDDLE)[t * 4 // n]\n row[7] = row[8] = row[9] = 0\n row[ring[0]] = ring[1]\n rows.append(row)\n return rows\n\n\ndef table():\n \"\"\"FRAMES: per row, (and, or) for each byte of OFFS.\"\"\"\n out = bytearray()\n for row in frames():\n for b in OFFS:\n assert row[b] & ~OWN[b] & 0xFF == 0\n out += bytes((~OWN[b] & 0xFF, row[b]))\n return bytes(out)\n\n\nSOURCE = r'''\n; ---- ls_poll: the panel decoder (or whatever ran there), then the animation's\n; state: on while the item is ON and the screen saver is on\nls_poll:\n sts.l pr,@-r15\n mov.l K_prev,r0\n jsr @r0\n nop\n mov.l r0,@-r15 ; the decoder's result, returned as it is\n mov.l K_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf lp_loaded\n bsr ls_load ; the first time: the saved setting\n nop\nlp_loaded:\n mov.l K_state,r1\n mov.b @r1,r0\n tst r0,r0\n bt lp_off\n mov.l K_count,r2\n mov.l @r2,r3\n mov.l K_t0,r0\n cmp/hi r0,r3\n bt lp_on\n mov.l @(4,r2),r3\n mov.l K_t1,r0\n cmp/hi r0,r3\n bt lp_on\nlp_off:\n mov #0,r0\n bra lp_out\n mov.b r0,@(2,r1)\nlp_on:\n mov.l K_tick,r2\n mov.l @r2,r2 ; now\n mov.b @(2,r1),r0\n tst r0,r0\n bf lp_adv\n mov #1,r0 ; it starts: the first row, now\n mov.b r0,@(2,r1)\n mov #0,r0\n mov.l r0,@(4,r1)\n mov.l r2,@(8,r1)\nlp_adv:\n mov.l @(8,r1),r3\n mov r2,r0\n sub r3,r0\n cmp/pz r0\n bf lp_hk ; not yet\n add #FRAME_TICKS,r3\n mov.l r3,@(8,r1)\n mov.l @(4,r1),r0\n add #1,r0\n mov #NROWS,r3\n cmp/hs r3,r0\n bf lp_row\n mov #0,r0\nlp_row:\n bra lp_adv\n mov.l r0,@(4,r1)\nlp_hk:\n mov.l K_hkleds,r2 ; hotcuekeys' LEDs out of the frame meanwhile\n tst r2,r2\n bt lp_out\n mov #0,r0\n mov.b r0,@r2\nlp_out:\n mov.l @r15+,r0\n lds.l @r15+,pr\n rts\n nop\n\n; ---- ls_send: the panel sender (r4-r7 as given, r6 = the frame): while the\n; animation runs, its row goes into the frame and the sum is made again\nls_send:\n mov.l K_state,r1\n mov.b @(2,r1),r0\n tst r0,r0\n bt ls_go\n mov.l r4,@-r15\n mov.l r5,@-r15\n mov.l r7,@-r15\n mov.l @(4,r1),r0 ; the row: 16 bytes each\n shll2 r0\n shll2 r0\n mov.l K_table,r2\n add r0,r2 ; -> (and, or) x 8\n mov.l K_offs,r5\n mov #NOFFS,r7\nls_byte:\n mov.b @r5+,r0\n extu.b r0,r0\n mov r6,r3\n add r0,r3 ; the frame byte\n mov.b @r2+,r4 ; and\n mov.b @r3,r0\n and r4,r0\n mov.b @r2+,r4 ; or\n or r4,r0\n dt r7\n bf/s ls_byte\n mov.b r0,@r3\n mov r6,r2 ; byte 22: sum of 0-21, carries added back\n mov #22,r7\n mov #0,r4\nls_sum:\n mov.b @r2+,r0\n extu.b r0,r0\n add r0,r4\n mov r4,r0\n shlr8 r0\n tst r0,r0\n bt ls_nc\n extu.b r4,r4\n add #1,r4\nls_nc:\n dt r7\n bf ls_sum\n mov r4,r0\n mov.b r0,@r2 ; r2 is at byte 22 now\n mov.l @r15+,r7\n mov.l @r15+,r5\n mov.l @r15+,r4\nls_go:\n mov.l K_sender,r1\n jmp @r1\n nop\n\n; ---- ls_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nls_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt lw_ours\n mov.l K_show,r1\n jmp @r1\n nop\nlw_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l K_state,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r4) ; shown as if being chosen\n mov.l K_show,r1\n jsr @r1\n nop\n mov.l K_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- ls_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nls_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt lt_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt lt_none\n mov.l r5,@(12,r4)\n mov.l K_state,r1\n mov r5,r0\n mov.b r0,@r1\n sts.l pr,@-r15\n bsr ls_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nlt_none:\n rts\n mov #0,r0\n\n; ---- ls_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; the way 0x08A681A0 sends the firmware's own (as hotcuekeys' hk_save)\nls_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l K_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l K_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf lv_out\n mov.l @r15,r9\n tst r9,r9\n bt lv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l K_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l K_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l K_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt lv_out\n mov.l K_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l K_release,r1\n jsr @r1\n nop\nlv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- ls_load: r1 -> LS_SETTING; the last SAVE_ID in the journal, if any\nls_load:\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l K_journal,r2\n mov.l K_jend,r3\n mov.l K_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nll_loop:\n cmp/hs r3,r2\n bt ll_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt ll_out\n cmp/eq r5,r0\n bf ll_next\n mov.w @(2,r2),r0\n and #1,r0\n mov.b r0,@r1\nll_next:\n bra ll_loop\n add #4,r2\nll_out:\n rts\n nop\n\n .align 4\nK_prev: .long PREV_DECODER\nK_sender: .long PREV_SENDER\nK_state: .long LS_SETTING\nK_count: .long SAVER_COUNT\nK_t0: .long SAVER_T0\nK_t1: .long SAVER_T1\nK_tick: .long TICK\nK_hkleds: .long HK_LEDS\nK_table: .long TABLE\nK_offs: .long OFFS_AT\nK_show: .long SHOW_SAVER\nK_pool: .long BACKUP_POOL\nK_mbx: .long BACKUP_MBX\nK_getblk: .long GET_BLOCK\nK_send: .long SEND\nK_release: .long RELEASE\nK_memset: .long MEMSET\nK_saveid: .long SAVE_ID\nK_journal: .long JOURNAL\nK_jend: .long JOURNAL_END\n'''\n\n\ndef routine(prev_decoder, prev_sender, hk_leds):\n \"\"\"(code, labels) at LS_CODE, then the table, OFFS and our label; hk_leds\n is hotcuekeys' LED flag (HK_BASE + 13), or 0 without it.\"\"\"\n h = hotcuekeys\n syms = {\n 'PREV_DECODER': prev_decoder, 'PREV_SENDER': prev_sender, 'LS_SETTING': LS_SETTING,\n 'SAVER_COUNT': SAVER_COUNT, 'SAVER_T0': SAVER_T0, 'SAVER_T1': SAVER_T1, 'TICK': h.TICK,\n 'HK_LEDS': hk_leds, 'TABLE': 0, 'OFFS_AT': 0, 'SHOW_SAVER': h.SHOW_SAVER,\n 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX, 'GET_BLOCK': h.GET_BLOCK,\n 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET, 'SAVE_ID': SAVE_ID,\n 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN,\n 'FRAME_TICKS': FRAME_TICKS, 'NROWS': len(frames()), 'NOFFS': len(OFFS),\n }\n code, labels = sh4asm.assemble(SOURCE, LS_CODE, syms)\n at = (LS_CODE + len(code) + 3) & ~3\n labels['TABLE'] = at\n at += len(table())\n labels['OFFS'] = at\n at = (at + len(OFFS) + 3) & ~3\n labels['LABEL'] = at\n at += 2 * len(LABEL) + 2\n labels['ITEMS'] = (at + 3) & ~3\n syms.update(TABLE=labels['TABLE'], OFFS_AT=labels['OFFS'])\n code2, labels2 = sh4asm.assemble(SOURCE, LS_CODE, syms)\n assert len(code2) == len(code)\n labels2.update({k: labels[k] for k in ('TABLE', 'OFFS', 'LABEL', 'ITEMS')})\n return code2, labels2\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as hotcuekeys' (OFF / ON).\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], hotcuekeys.ONOFF_LIST, 2, 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['ls_show'], g32, g36, labels['ls_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with LED ANIMATION added, after whatever other mods\n are in: it chains after the decoder and sender literals as it finds them\n and adds its item after the UTILITY items there are.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' PHASE METER,\nREVERSE, the line meter with its markers running from right to left; and the\nbeat countdown right of the meter moved BARS_DX pixels to the right, as the\nline meter reaches further than the stock boxes and ran into its digits.\nOurs; not in the released builds yet (patch(public=True) leaves it out).\n\n * PHASE METER is the firmware's SLIP FLASHING item (UTILITY item 3; its\n value at 0x0A4DEA1C, stored and saved by the firmware: 0 OFF, 1 ON).\n Its value list becomes OFF / ON / REVERSE (VALUES; OFF and ON the\n firmware's own strings), 2 being REVERSE.\n * arnoldasr's meter (phase_screen_update, the playback screen's callback\n table entry SCREEN_CB) shows the lines only for 1. pr_update takes the\n entry: with 2 it calls the meter with the value 1 for that call, and\n mirrors both markers' x on the line (x' = MIRROR - x, his markers sit at\n 0xD9 + 0..195), first back to his own before the call (so he still\n writes, and marks the screen for a redraw, only when a marker moved),\n then mirrored again after it.\n * Kept over a restart: the firmware packs SLIP FLASHING into one bit of\n its settings as value & 1 (PACK_AT, unpacked at 0x08B7C65C), so REVERSE\n (2) came back as OFF. PACK_AT tests value & 3 instead: REVERSE is kept\n as ON. pr_take (the item's +40, in front of the firmware's) keeps every\n value chosen in UTILITY in the settings journal too, as SAVE_ID (as\n advanced_waveforms' WAVEFORM); pr_update, on its first call, takes the\n last one from there (PR_STATE), and while it is REVERSE an ON from the\n firmware (its own setting, or MY SETTINGS from a USB) is REVERSE. OFF\n stays OFF.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '0.3.0' # the extras; arnoldasr's meter itself is 1.0.0 (Semantic Versioning)\n\nBASE = 0x08000000\nPR_BASE = 0x08FDAC00 # after quicksearch's 0x08FDA400..0x08FDAC00\nPR_END = 0x08FDB000\nPR_CODE = PR_BASE + 0x10\nPR_STATE = PR_BASE # bytes: +0 the value last chosen in UTILITY (0xFF none), +1 1: taken from the journal\nPACK_AT = 0x08B7C1AA # the settings packer's tst #1,r0 of SLIP FLASHING's value (base 0x0A4DE9C0 +92)\nPACK_STOCK, PACK_NEW = 0xC801, 0xC803 # tst #1,r0 -> tst #3,r0\nSAVE_ID = 0x83E6 # ours (hotcuekeys 0x83E1, beatjump 0x83E2, keynotation 0x83E3, usbautoload\n # 0x83E4, advanced_waveforms 0x83E5, ledsaver 0x83E7)\n\nSETTING = 0x0A4DEA1C # SLIP FLASHING's value, PHASE METER's\nSCREEN_CB = 0x08C6CCD0 # the playback screen's callback (arnoldasr's phase_screen_update)\nSTOCK_CB = 0x08BD02E8\nMARKERS = (0x08FC2C60, 0x08FC2C64) # arnoldasr's two marker bitmaps (his state +0x60/+0x64)\nX0, SPAN = 0xD9, 195 # his marker x: X0 + 0..SPAN\nMIRROR = 2 * X0 + SPAN\nITEM = 3 # SLIP FLASHING in the UTILITY table\nITEM_LABEL = 0xA80CFF66 # ... its label (P2 address of the copy the phasemeter manifest renames)\nREVERSE = 'REVERSE'\n\nSOURCE = r'''\n; ---- pr_update: the playback screen's callback, arnoldasr's phase meter in\n; it; with PHASE METER REVERSE (2) the meter runs with 1 and its markers mirrored\npr_update:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov r4,r10\n mov r5,r11\n mov r6,r12\n mov r7,r13\n mov.l P_state,r1 ; the value chosen last, from the journal the first time\n mov.b @(1,r1),r0\n tst r0,r0\n bf pu_known\n bsr pr_load\n nop\npu_known:\n mov.l P_setting,r8\n mov.l P_state,r1 ; REVERSE chosen: the firmware's ON is REVERSE\n mov.b @r1,r0\n cmp/eq #2,r0\n bf pu_set\n mov.l @r8,r0\n cmp/eq #1,r0\n bf pu_set\n mov #2,r0\n mov.l r0,@r8\npu_set:\n mov.l @r8,r9\n mov r9,r0\n cmp/eq #2,r0\n bf pu_call\n mov #1,r0 ; the line meter for this call\n mov.l r0,@r8\n bsr pr_mirror ; his own x back\n nop\npu_call:\n mov r10,r4\n mov r11,r5\n mov r12,r6\n mov r13,r7\n mov.l P_meter,r0\n jsr @r0\n nop\n mov r0,r10 ; its result, returned as it is\n mov r9,r0\n cmp/eq #2,r0\n bf pu_ret\n mov.l r9,@r8 ; REVERSE again\n bsr pr_mirror\n nop\npu_ret:\n mov r10,r0\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n; ---- pr_take: the item's +40 (r4 the item: +12 the value, +16 the one chosen): the\n; value chosen kept in the journal as ours too, then the firmware's\npr_take:\n mov.l @(16,r4),r5\n mov #3,r0\n cmp/hs r0,r5\n bt pt_go ; (none chosen)\n mov.l P_state,r1\n mov r5,r0\n mov.b r0,@r1\n mov #1,r0\n mov.b r0,@(1,r1)\n sts.l pr,@-r15\n mov.l r4,@-r15\n bsr pr_save\n mov r5,r4\n mov.l @r15+,r4\n lds.l @r15+,pr\npt_go:\n mov.l P_take,r0\n jmp @r0\n nop\n\n; ---- pr_load: PR_STATE +0 = the last SAVE_ID in the journal (0xFF none), +1 = 1\npr_load:\n mov.l P_state,r1\n mov #1,r0\n mov.b r0,@(1,r1)\n mov #-1,r0\n mov.b r0,@r1\n mov.l P_journal,r2\n mov.l P_jend,r3\n mov.l P_saveid,r5\n mov #-1,r6\n extu.w r6,r6\npl_loop:\n cmp/hs r3,r2\n bt pl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt pl_out\n cmp/eq r5,r0\n bf pl_next\n mov.w @(2,r2),r0\n extu.w r0,r0\n mov #3,r7\n cmp/hs r7,r0\n bt pl_next ; (no such value)\n mov.b r0,@r1\npl_next:\n bra pl_loop\n add #4,r2\npl_out:\n rts\n nop\n\n; ---- pr_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5)\npr_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l P_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l P_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf ps_out\n mov.l @r15,r9\n tst r9,r9\n bt ps_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l P_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l P_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l P_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l P_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt ps_out\n mov.l P_pool,r1 ; not taken: the block given back\n mov.l @r1,r4\n mov r9,r5\n mov.l P_release,r1\n jsr @r1\n nop\nps_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- pr_mirror: both markers' x (+0x18 x0, +0x1C x1 = x0 + 8) mirrored on the line\npr_mirror:\n mov.l P_markers,r1\n mov #2,r3\npm_loop:\n mov.l @r1+,r2 ; where his marker pointer is\n mov.l @r2,r2 ; the marker (0: not installed)\n tst r2,r2\n bt pm_next\n mov.w @(24,r2),r0\n mov.l P_mirror,r4\n sub r0,r4\n mov r4,r0\n mov.w r0,@(24,r2)\n add #8,r0\n mov.w r0,@(28,r2)\npm_next:\n dt r3\n bf pm_loop\n rts\n nop\n\n .align 4\nP_setting: .long SETTING\nP_meter: .long METER\nP_markers: .long MARKERS_AT\nP_mirror: .long MIRROR\nP_state: .long PR_STATE\nP_take: .long TAKE\nP_journal: .long JOURNAL\nP_jend: .long JOURNAL_END\nP_saveid: .long SAVE_ID\nP_pool: .long BACKUP_POOL\nP_mbx: .long BACKUP_MBX\nP_getblk: .long GET_BLOCK\nP_send: .long SEND\nP_release: .long RELEASE\nP_memset: .long MEMSET\n'''\n\n\ndef routine(meter, take=0):\n h = hotcuekeys\n syms = {'SETTING': SETTING, 'METER': meter, 'MIRROR': MIRROR, 'MARKERS_AT': 0, 'PR_STATE': PR_STATE,\n 'TAKE': take, 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN, 'SAVE_ID': SAVE_ID,\n 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX, 'GET_BLOCK': h.GET_BLOCK, 'SEND': h.SEND,\n 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET}\n code, labels = sh4asm.assemble(SOURCE, PR_CODE, syms)\n at = (PR_CODE + len(code) + 3) & ~3\n labels['MARKERS_AT'] = at\n at += 4 * len(MARKERS)\n labels['TEXT'] = at\n at = (at + 2 * len(REVERSE) + 2 + 3) & ~3\n labels['LIST'] = at\n syms['MARKERS_AT'] = labels['MARKERS_AT']\n code2, l2 = sh4asm.assemble(SOURCE, PR_CODE, syms)\n assert len(code2) == len(code)\n l2.update({k: labels[k] for k in ('MARKERS_AT', 'TEXT', 'LIST')})\n return code2, l2\n\n\n# the beat countdown's layout records (x, y int16, resource id, ...) in .data:\n# master row y 36-55, this deck's row y 62-80; digits x 415 / 430 / 449,\n# point 444, \"Bars\" 463 (the meter's boxes, x 132 and 217, stay)\nBARS_TABLE = (0x08C45440, 0x08C458C8)\nBARS_X = (415, 430, 444, 449, 463)\nBARS_DX = 8\n\n\ndef shift_bars(app, stock):\n \"\"\"The beat countdown BARS_DX pixels further right.\"\"\"\n out = bytearray(app)\n n = 0\n for a in range(BARS_TABLE[0], BARS_TABLE[1], 2):\n x, y = struct.unpack_from('= 0x11000000 \\\n else struct.unpack_from('<2I', out, values - BASE)\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(PR_CODE, code)\n place(labels['MARKERS_AT'], struct.pack('<%dI' % len(MARKERS), *MARKERS))\n place(labels['TEXT'], REVERSE.encode('utf-16-le') + b'\\0\\0')\n place(labels['LIST'], struct.pack('<3I', off_on[0], off_on[1], labels['TEXT']))\n struct.pack_into('<2I', out, item + 4 - BASE, labels['LIST'], 3)\n struct.pack_into(' r0 FNV-1a of its UTF-16 characters (never 0); r1-r3 used\npl_hash:\n mov.l Q_fnv,r0\n mov.l Q_prime,r2\n mov #PL_LEN,r3\nph_char:\n mov.w @r4+,r1\n extu.w r1,r1\n tst r1,r1\n bt ph_done\n xor r1,r0\n mul.l r2,r0\n dt r3\n bf/s ph_char\n sts macl,r0\nph_done:\n tst r0,r0\n bf ph_out\n mov #1,r0\nph_out:\n rts\n nop\n\n; ---- pl_find(r4 text) -> r0 1 if it is in the list, else 0; r1-r7 used\npl_find:\n mov.l Q_count,r1\n mov.l @r1,r7\n tst r7,r7\n bt pf_no\n mov.l Q_list,r6\npf_entry:\n mov r4,r1\n mov r6,r2\npf_char:\n mov.w @r1+,r3\n mov.w @r2+,r5\n cmp/eq r3,r5\n bf pf_next\n tst r3,r3\n bf pf_char\n rts\n mov #1,r0\npf_next:\n mov.l Q_stride,r1\n dt r7\n bf/s pf_entry\n add r1,r6\npf_no:\n rts\n mov #0,r0\n\n .align 4\nQ_buf: .long PL_BUF\nQ_count: .long PL_COUNT\nQ_cur: .long PL_CUR\nQ_depth: .long BROWSE_DEPTH\nQ_fnv: .long 0x811C9DC5\nQ_hash: .long PL_HASH\nQ_list: .long PL_LIST\nQ_next: .long PL_NEXT\nQ_next_draw: .long NEXT_DRAW\nQ_ocount: .long PL_OCOUNT\nQ_oth: .long PL_OTH\nQ_prime: .long 0x01000193\nQ_sbuf: .long STATUS_BUF\nQ_stride: .long PL_STRIDE\nQ_title: .long TITLE_ID\nQ_green: .long GREEN\n\n; ---- pl_send: memcpy(r4, r5, r6) for the status packet's copy to send\n .align 4\npl_send:\n mov.l T_sbuf,r0\n cmp/eq r0,r5\n bf ps_go\n mov.l T_len,r0\n cmp/eq r0,r6\n bf ps_go\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov r5,r1 ; our tag: \"PM\", 1, 0, the hash big-endian\n add #120,r1\n add #120,r1\n mov #80,r0\n mov.b r0,@r1\n mov #77,r0\n mov.b r0,@(1,r1)\n mov #1,r0\n mov.b r0,@(2,r1)\n mov #0,r0\n mov.b r0,@(3,r1)\n mov.l T_hash,r2\n mov.l @r2,r2\n mov #40,r0 ; no track loaded: no hash\n mov.b @(r0,r5),r0\n tst r0,r0\n bf ps_loaded\n mov #0,r2\nps_loaded:\n mov r2,r0\n mov.b r0,@(7,r1)\n shlr8 r2\n mov r2,r0\n mov.b r0,@(6,r1)\n shlr8 r2\n mov r2,r0\n mov.b r0,@(5,r1)\n shlr8 r2\n mov r2,r0\n mov.b r0,@(4,r1)\n mov #33,r0 ; this player\n mov.b @(r0,r5),r9\n extu.b r9,r9\n mov.l T_tab,r8 ; the others' tags\n mov.l T_cur,r10\n mov #1,r11\nps_pl:\n mov.l T_len,r0\n add r0,r8\n add #4,r10\n mov #0,r2 ; none\n cmp/eq r11,r9\n bt ps_store\n mov r8,r1 ; the table keeps every word turned to little-endian\n add #120,r1\n add #120,r1\n mov.l @r1,r0\n mov.l T_magic,r3\n cmp/eq r3,r0\n bf ps_store\n mov.l @(4,r1),r2\nps_store:\n mov.l r2,@r10\n tst r2,r2\n bt ps_next\n mov.l T_ocount,r1 ; seen before?\n mov.l @r1,r3\n mov.l T_oth,r7\n tst r3,r3\n bt ps_add\nps_find:\n mov.l @r7+,r0\n cmp/eq r0,r2\n bt ps_next\n dt r3\n bf ps_find\nps_add:\n mov.l T_onext,r1 ; into the next slot\n mov.l @r1,r3\n mov r3,r0\n add #1,r0\n mov #PL_OMAX,r7\n cmp/hs r7,r0\n bf ps_wrap\n mov #0,r0\nps_wrap:\n mov.l r0,@r1\n shll2 r3\n mov.l T_oth,r7\n add r7,r3\n mov.l r2,@r3\n mov.l T_ocount,r1\n mov.l @r1,r3\n mov #PL_OMAX,r7\n cmp/hs r7,r3\n bt ps_next\n add #1,r3\n mov.l r3,@r1\nps_next:\n add #1,r11\n mov #PLAYERS,r0\n cmp/hi r0,r11\n bf ps_pl\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\nps_go:\n mov.l T_memcpy,r0\n jmp @r0\n nop\n\n .align 4\nT_cur: .long PL_CUR\nT_hash: .long PL_HASH\nT_len: .long STATUS_LEN\nT_magic: .long 0x504D0100\nT_memcpy: .long MEMCPY\nT_ocount: .long PL_OCOUNT\nT_onext: .long PL_ONEXT\nT_oth: .long PL_OTH\nT_sbuf: .long STATUS_BUF\nT_tab: .long STATUS_TAB\n'''\n\n\ndef routine(next_draw):\n return sh4asm.assemble(SOURCE, PL_CODE, {\n 'PL_BUF': PL_BUF, 'PL_COUNT': PL_COUNT, 'BROWSE_DEPTH': BROWSE_DEPTH, 'PL_LIST': PL_LIST,\n 'PL_NEXT': PL_NEXT, 'NEXT_DRAW': next_draw, 'TITLE_ID': TITLE_ID, 'GREEN': GREEN,\n 'PL_MAX': PL_MAX, 'PL_LEN': PL_LEN, 'PL_STRIDE': PL_LEN * 2, 'PL_HASH': PL_HASH,\n 'PL_CUR': PL_CUR, 'PL_OCOUNT': PL_OCOUNT, 'PL_ONEXT': PL_ONEXT, 'PL_OTH': PL_OTH,\n 'PL_OMAX': PL_OMAX, 'PLAYERS': PLAYERS, 'STATUS_BUF': STATUS_BUF, 'STATUS_LEN': STATUS_LEN,\n 'STATUS_TAB': STATUS_TAB, 'MEMCPY': MEMCPY})\n\n\ndef patch(app, stock):\n \"\"\"The application with PLAYED added (after keynotation and cuenames, if\n chosen); `stock` is the unmodified 1.31 application.\"\"\"\n assert PL_LEN * 2 == 128, 'pl_draw computes slot * 128'\n assert TAG_OFF == 240, 'pl_send adds 120 twice'\n out = bytearray(app)\n word = lambda buf, a: struct.unpack_from(' our state, r13 now. The way to SEARCH; where the\n; browse screen stood is kept for the next BROWSE (unless it is kept already)\nqs_search:\n sts.l pr,@-r15\n mov.b @(5,r14),r0\n tst r0,r0\n bf qsr_go\n mov.b @(10,r14),r0\n tst r0,r0\n bt qsr_go\n mov.b r0,@(12,r14)\n mov r0,r6\n mov #1,r0\n mov.b r0,@(5,r14)\n mov r14,r4\n add #65,r4\n mov r14,r5\n add #80,r5\n bsr qs_copy\n nop\nqsr_go:\n mov #1,r0\n mov.b r0,@(2,r14) ; kind 1: SEARCH\n bra qs_start\n lds.l @r15+,pr\n\n; ---- qs_goto: r4 -> a path (cursors of levels 1..r6), r14, r13. The way there\nqs_goto:\n sts.l pr,@-r15\n mov r6,r0\n mov.b r0,@(9,r14)\n mov r14,r5\n add #112,r5\n bsr qs_copy\n nop\n mov #2,r0\n mov.b r0,@(2,r14) ; kind 2: a path\n lds.l @r15+,pr\nqs_start: ; first the source's top list\n mov #1,r0\n mov.b r0,@(1,r14)\n mov.b r0,@(15,r14)\n mov #0,r0\n mov.b r0,@(3,r14)\n mov.b r0,@(8,r14)\n mov.b r0,@(13,r14)\n mov.b r0,@(14,r14)\n rts\n mov.l r13,@(28,r14)\n\n; ---- qs_step: r14, r13 now, r12 level, r11 cursor (index). The way's next\n; op, sent once the browse task has no DB answer due; then its effect awaited\nqs_step:\n mov.b @(1,r14),r0\n tst r0,r0\n bf qs_k1\n rts\n nop\nqs_k1:\n sts.l pr,@-r15\n mov.l @(28,r14),r1 ; one op that long (busy or stuck): the way ends\n mov r13,r9\n sub r1,r9 ; r9: ticks since the op was sent (or the way began)\n mov #GIVEUP_8,r1\n shll2 r1\n add r1,r1\n cmp/hs r1,r9\n bt qs_endj\n mov.l S_ctx,r1 ; a DB answer due: wait\n mov.l @(20,r1),r0\n mov.l @(28,r1),r2\n or r2,r0\n mov.l @(32,r1),r2\n or r2,r0\n add #24,r1\n mov.w @r1,r2\n extu.w r2,r2\n or r2,r0\n tst r0,r0\n bf qs_busy\n mov.b @(8,r14),r0\n tst r0,r0\n bf qs_k2\n bra qs_send\n nop\nqs_k2:\n bsr qs_done ; sent: shown yet, and for SETTLE_TICKS (the task idle all the\n nop ; time: its list in, not only its level)\n tst r0,r0\n bt qs_nyet\n mov.l @(32,r14),r1\n tst r1,r1\n bf qs_since\n bra qs_retx\n mov.l r13,@(32,r14)\nqs_since:\n mov r13,r2\n sub r1,r2 ; r2: shown that long\n mov #SETTLE_TICKS,r1 ; a new list slides in: SETTLE_TICKS; the cursor moved: CUR_TICKS\n mov.b @(15,r14),r0\n cmp/eq #2,r0\n bf qs_s2\n mov #CUR_TICKS,r1\nqs_s2:\n cmp/hs r1,r2\n bt qs_next\n bra qs_retx\n nop\nqs_nyet:\n mov #0,r0\n mov.l r0,@(32,r14)\n mov #OP_TICKS,r1 ; not after OP_TICKS: once more\n mov.b @(15,r14),r0 ; (the top list asked for with the screen closed only opens\n cmp/eq #1,r0 ; it where it was: asked again as soon as it shows)\n bf qs_k3\n mov #2,r0\n cmp/hs r0,r12\n bf qs_k3\n mov #20,r1\nqs_k3:\n cmp/hs r1,r9\n bf qs_retx\n mov.b @(13,r14),r0\n add #1,r0\n mov.b r0,@(13,r14)\n mov #MAX_TRIES,r1\n cmp/hs r1,r0\n bt qs_endj\n mov #0,r0 ; (the op again)\n bra qs_ret\n mov.b r0,@(8,r14)\nqs_endj:\n bra qs_end\n nop\nqs_busy:\n mov #0,r0 ; busy: settled anew afterwards\n mov.l r0,@(32,r14)\nqs_retx:\n bra qs_ret\n nop\n\n; the op shown: the next one\nqs_next:\n mov #0,r0\n mov.l r0,@(32,r14)\n mov.b r0,@(8,r14)\n mov.b r0,@(13,r14)\n mov.l r13,@(28,r14)\n mov.b @(15,r14),r0\n cmp/eq #1,r0\n bt qn_top\n cmp/eq #2,r0\n bt qn_cursor\n cmp/eq #3,r0 ; opened: one level further\n bf qs_endj\n mov.b @(3,r14),r0\n add #1,r0\n mov.b r0,@(3,r14)\n mov.b @(2,r14),r0\n cmp/eq #1,r0\n bt qs_endj ; SEARCH open: done\n bra qn_level\n nop\nqn_top:\n mov #1,r0 ; the top list there: level 1\n mov.b r0,@(3,r14)\n mov.b @(2,r14),r0\n cmp/eq #1,r0\n bf qn_level\n mov.l @(44,r14),r0 ; SEARCH: where it was last time, if anywhere\n tst r0,r0\n bt qn_look\n add #-1,r0\n mov.l r0,@(24,r14)\n mov #2,r0 ; the cursor there, then checked (qn_c2)\n mov.b r0,@(14,r14)\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_look:\n mov #4,r0 ; looked for\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_level:\n mov.b @(3,r14),r0 ; a path: the cursor onto this level's entry\n mov r14,r1\n add #111,r1\n add r0,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(24,r14)\n mov #2,r0\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_cursor:\n mov.b @(14,r14),r0 ; moved on to look further: look again\n tst r0,r0\n bt qn_c2\n cmp/eq #2,r0 ; onto where SEARCH was: is it there?\n bf qn_c1\n mov #0,r0\n mov.b r0,@(14,r14)\n mov.l S_rowids,r1 ; the cursor's row's entry code\n mov.l S_cursor,r2\n mov.l @r2,r2\n mov.l S_rowlen,r3\n mulu.w r2,r3\n sts macl,r3\n add r3,r1\n mov.l @r1,r0\n mov #SEARCH_ID,r2\n extu.b r2,r2\n cmp/eq r2,r0\n bt qn_c2 ; yes: open it\n mov #0,r0 ; no: forget it, look for it\n mov.l r0,@(44,r14)\n mov #4,r0\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_c1:\n mov #0,r0\n mov.b r0,@(14,r14)\n mov #4,r0\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_c2:\n mov.b @(2,r14),r0\n cmp/eq #1,r0\n bt qn_open ; SEARCH: open it\n mov.b @(9,r14),r0 ; a path: its last level only the cursor (kind 3: opened too)\n mov r0,r1\n mov.b @(3,r14),r0\n cmp/hs r1,r0\n bf qn_open\n mov.b @(2,r14),r0\n cmp/eq #3,r0\n bf qs_endj\n mov #5,r0 ; the track: open, and the way ends (the screen closes)\n bra qs_ret\n mov.b r0,@(15,r14)\nqn_open:\n mov #3,r0\n bra qs_ret\n mov.b r0,@(15,r14)\n\n; the op sent (or, for 4, done at once)\nqs_send:\n mov.l r13,@(28,r14)\n mov #1,r0\n mov.b r0,@(8,r14)\n mov.b @(15,r14),r0\n cmp/eq #1,r0\n bt qd_top\n cmp/eq #2,r0\n bt qd_cursor\n cmp/eq #3,r0\n bt qd_open\n cmp/eq #5,r0\n bf qd_look\n mov #PULSE,r0 ; 5: the rotary pushed on the track (the UI task loads it, as\n add r13,r0 ; for a press: its \"Load Track Request\"), the way done\n bra qs_end\n mov.l r0,@(52,r14)\nqd_look:\n bra qs_look ; 4: SEARCH among the rows shown\n nop\nqd_top:\n mov.l @(48,r14),r5 ; the top list of the source last browsed (UI_STATE +4)\n tst r5,r5\n bf qd_t1\n mov #2,r5 ; (none seen yet: the USB)\nqd_t1:\n mov.l S_ctop,r4\n bra qd_cmd\n mov #0,r6\nqd_open:\n mov.l S_copen,r4\n mov #0,r5\n bra qd_cmd\n mov #0,r6\nqd_cursor:\n mov.l @(24,r14),r2 ; the target; the window moved as the UI moves it: kept while\n mov.l S_top,r1 ; the target is in it, else the target at its top or bottom\n mov.l @r1,r0 ; r0: the first row shown now\n cmp/gt r2,r0\n bf qc_k1\n bra qc_clamp ; above it: at the top\n mov r2,r0\nqc_k1:\n mov r0,r1\n add #5,r1\n cmp/gt r1,r2\n bf qc_clamp ; in it: as it is\n mov r2,r0 ; below it: at the bottom\n add #-5,r0\nqc_clamp:\n mov.l S_count,r1 ; not past count - 6, not below 0\n mov.l @r1,r1\n add #-6,r1\n cmp/gt r1,r0\n bf qc_k2\n mov r1,r0\nqc_k2:\n cmp/pz r0\n bt qc_k3\n mov #0,r0\nqc_k3:\n mov r0,r5\n mov r2,r6\n sub r0,r6\n mov.l S_ccursor,r4\nqd_cmd:\n bsr qs_cmd\n nop\n bra qs_ret\n nop\n\n; qs_look: SEARCH (entry code SEARCH_ID) among the rows shown: the cursor onto it;\n; not there: onto the first row after them (the next six shown), or the end\nqs_look:\n mov.l S_count,r1\n mov.l @r1,r3 ; r3: entries\n mov.l S_top,r1\n mov.l @r1,r4 ; r4: the first shown\n mov r3,r5\n sub r4,r5 ; r5: rows shown = min(6, entries - first)\n mov #6,r1\n cmp/hs r1,r5\n bf ql_n\n mov r1,r5\nql_n:\n cmp/pl r5\n bf ql_end\n mov.l S_rowids,r1\n mov #0,r6\n mov #SEARCH_ID,r7\n extu.b r7,r7\nql_loop:\n mov.l @r1,r0\n cmp/eq r7,r0\n bt ql_found\n mov.l S_rowlen,r2\n add r2,r1\n add #1,r6\n cmp/hs r5,r6\n bf ql_loop\n mov r4,r0 ; not shown: the next rows\n add r5,r0\n cmp/hs r3,r0\n bt ql_end ; no SEARCH in this list\n mov.l r0,@(24,r14)\n mov #1,r0\n mov.b r0,@(14,r14)\n bra ql_cur\n nop\nql_found:\n mov r4,r0\n add r6,r0\n mov.l r0,@(24,r14)\n add #1,r0 ; kept for next time (+1: 0 none)\n mov.l r0,@(44,r14)\nql_cur:\n mov #2,r0 ; the cursor there, at once\n mov.b r0,@(15,r14)\n mov #0,r0\n bra qs_ret\n mov.b r0,@(8,r14)\nql_end:\n bra qs_end\n nop\n\n; qs_done -> r0 1 when the op shows: the top list (level 1), the cursor on its\n; target, the next level open; r1-r3\nqs_done:\n mov.b @(15,r14),r0\n cmp/eq #1,r0\n bt qe_top\n cmp/eq #2,r0\n bt qe_cursor\n mov.b @(3,r14),r0 ; opened: level + 1\n add #1,r0\n cmp/eq r0,r12\n rts\n movt r0\nqe_top:\n mov r12,r0\n cmp/eq #1,r0\n rts\n movt r0\nqe_cursor:\n mov.l @(24,r14),r1\n mov.b @(3,r14),r0 ; on the level it is for\n cmp/eq r12,r0\n bf qe_no\n cmp/eq r1,r11\n rts\n movt r0\nqe_no:\n rts\n mov #0,r0\n\nqs_end:\n mov #0,r0\n mov.b r0,@(1,r14)\n mov.b r0,@(15,r14)\nqs_ret:\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nS_ctx: .long BR_CTX\nS_state: .long UI_STATE\nS_count: .long COUNT\nS_pane: .long PANE\nS_top: .long TOP\nS_cursor: .long CURSOR\nS_rowids: .long ROW_IDS\nS_rowlen: .long ROW_LEN\nS_ctop: .long CMD_TOP\nS_copen: .long CMD_OPEN\nS_ccursor: .long CMD_CURSOR\nS_fpmask: .long 0xFFE7FFFF\n\n; ---- qs_cmd: r4 command, r5 a1, r6 a2: a command to the browse task as the UI\n; task sends it (browse_cmd, but the sender the UI task: the browse task takes\n; commands only from it, and qs_poll runs in the panel poller's task)\nqs_cmd:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n add #-4,r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov #0,r0\n mov.l r0,@r15\n mov.l C_pool,r1 ; a message block from the UI's pool\n mov.l @r1,r4\n mov r15,r5\n mov.l C_size,r6\n mov.l C_getmpl,r0\n jsr @r0\n nop\n tst r0,r0\n bf cm_out\n mov.l @r15,r4\n mov #0,r5\n mov #112,r6\n mov.l C_memset,r0\n jsr @r0\n nop\n mov.l @r15,r1\n mov.l C_a80c,r2\n mov.l @r2,r2\n mov.l r2,@(4,r1)\n mov.l C_code,r2\n mov.l r2,@(8,r1)\n mov.l C_uitask,r2 ; from the UI task\n mov.l @r2,r2\n mov.l r2,@(16,r1)\n mov.l C_pool,r2\n mov.l @r2,r2\n mov.l r2,@(24,r1)\n mov #112,r2\n mov.l r2,@(32,r1)\n mov.l r8,@(36,r1)\n mov.l r9,@(40,r1)\n mov.l r10,@(44,r1)\n mov.l C_pool,r1 ; stamped with the time, as browse_cmd does\n mov.l @r1,r4\n mov #112,r5\n mov.l C_time,r0\n jsr @r0\n nop\n mov.l @r15,r1\n mov r0,r2\n mov #108,r0\n mov.l r2,@(r0,r1)\n mov.l C_mbx,r1 ; to the browse task's mailbox\n mov.l @r1,r4\n mov.l @r15,r5\n mov.l C_send,r0\n jsr @r0\n nop\n tst r0,r0\n bt cm_out\n mov.l C_pool,r1 ; not sent: the block back\n mov.l @r1,r4\n mov.l @r15,r5\n mov.l C_release,r0\n jsr @r0\n nop\ncm_out:\n mov.l C_fpmask,r1 ; FPSCR as the firmware leaves it after a call\n sts fpscr,r2\n and r1,r2\n lds r2,fpscr\n add #4,r15\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nC_pool: .long UI_POOL\nC_size: .long 0x1808\nC_getmpl: .long GET_MPL\nC_memset: .long MEMSET\nC_a80c: .long 0xA80CF854\nC_code: .long 0x32C9\nC_uitask: .long UI_TASK\nC_time: .long MSG_TIME\nC_mbx: .long BR_MBX\nC_send: .long SND_MBX\nC_release: .long REL_MPL\nC_fpmask: .long 0xFFE7FFFF\n'''\n\n\ndef routine(prev_decoder):\n syms = {\n 'PREV_DECODER': prev_decoder, 'QS_BASE': QS_BASE, 'RELOAD': RELOAD, 'TICK': hotcuekeys.TICK,\n 'KEY_BROWSE_AT': KEY_BROWSE[0], 'KEY_D6': KEY_D6, 'PUSH_BIT': PUSH_BIT,\n 'DEPTH': DEPTH, 'CURSOR': CURSOR, 'TOP': TOP, 'COUNT': COUNT, 'PANE': PANE, 'UI_STATE': UI_STATE,\n 'ROW_IDS': ROW_IDS, 'ROW_LEN': ROW_LEN, 'SEARCH_ID': SEARCH_ID - 256 if SEARCH_ID > 127 else SEARCH_ID,\n 'BR_CTX': BR_CTX, 'UI_POOL': UI_POOL, 'UI_TASK': UI_TASK, 'BR_MBX': BR_MBX,\n 'GET_MPL': GET_MPL, 'REL_MPL': REL_MPL, 'SND_MBX': SND_MBX, 'MSG_TIME': MSG_TIME, 'MEMSET': MEMSET,\n 'CMD_TOP': CMD_TOP, 'CMD_OPEN': CMD_OPEN, 'CMD_CURSOR': CMD_CURSOR,\n 'LONG_TICKS': LONG_TICKS, 'DOUBLE_TICKS': DOUBLE_TICKS, 'LOAD_TICKS': LOAD_TICKS,\n 'PULSE': PULSE, 'STABLE_TICKS': STABLE_TICKS, 'PUSH_QUIET': PUSH_QUIET,\n 'OP_TICKS': OP_TICKS, 'SETTLE_TICKS': SETTLE_TICKS, 'CUR_TICKS': CUR_TICKS, 'MAX_TRIES': MAX_TRIES, 'GIVEUP_8': GIVEUP_8,\n }\n return sh4asm.assemble(SOURCE, QS_CODE, syms)\n\n\ndef patch(app, stock):\n \"\"\"The application with QUICK SEARCH: chained after the decoder literal\n as it finds it.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' (bytes, labels). `symbols` gives names for\n.long values (addresses in the firmware). Delay slots are the writer's job.\"\"\"\nimport re\nimport struct\n\nR = {('r%d' % i): i for i in range(16)}\n\n\nclass AsmError(Exception):\n pass\n\n\ndef _reg(s):\n s = s.strip().lower()\n if s not in R:\n raise AsmError('not a register: %r' % s)\n return R[s]\n\n\ndef _num(s, syms):\n s = s.strip()\n if s.startswith('#'):\n s = s[1:]\n try:\n return int(s, 0)\n except ValueError:\n pass\n if s in syms:\n return syms[s]\n folded = {k.lower(): v for k, v in syms.items()}\n if s.lower() in folded:\n return folded[s.lower()]\n m = re.match(r'^(\\w+)\\s*([+-])\\s*(\\w+)$', s)\n if m:\n a, b = _num(m.group(1), syms), _num(m.group(3), syms)\n return a + b if m.group(2) == '+' else a - b\n raise AsmError('unknown value %r' % s)\n\n\ndef _split_ops(s):\n out, depth, cur = [], 0, ''\n for ch in s:\n if ch == '(':\n depth += 1\n elif ch == ')':\n depth -= 1\n if ch == ',' and depth == 0:\n out.append(cur.strip())\n cur = ''\n else:\n cur += ch\n if cur.strip():\n out.append(cur.strip())\n return out\n\n\n# two-register forms: mnemonic -> (template, 'mn' meaning Rm=first, Rn=second)\nRR = {\n 'mov': 0x6003, 'add': 0x300C, 'sub': 0x3008, 'and': 0x2009, 'or': 0x200B,\n 'xor': 0x200A, 'tst': 0x2008, 'cmp/eq': 0x3000, 'cmp/hs': 0x3002,\n 'cmp/ge': 0x3003, 'cmp/hi': 0x3006, 'cmp/gt': 0x3007, 'extu.b': 0x600C,\n 'extu.w': 0x600D, 'exts.b': 0x600E, 'exts.w': 0x600F, 'neg': 0x600B,\n 'not': 0x6007, 'swap.b': 0x6008, 'swap.w': 0x6009, 'mul.l': 0x0007,\n 'shad': 0x400C, 'shld': 0x400D, 'dmulu.l': 0x3005, 'dmuls.l': 0x300D,\n 'addc': 0x300E, 'subc': 0x300A, 'xtrct': 0x200D, 'mulu.w': 0x200E,\n}\nR1 = {\n 'shll': 0x4000, 'shlr': 0x4001, 'shll2': 0x4008, 'shlr2': 0x4009,\n 'shll8': 0x4018, 'shlr8': 0x4019, 'shll16': 0x4028, 'shlr16': 0x4029,\n 'shar': 0x4021, 'shal': 0x4020, 'dt': 0x4010, 'cmp/pz': 0x4011,\n 'cmp/pl': 0x4015, 'movt': 0x0029, 'rotl': 0x4004, 'rotr': 0x4005, 'rotcl': 0x4024, 'rotcr': 0x4025,\n}\nIMM0 = {'and': 0xC900, 'or': 0xCB00, 'xor': 0xCA00, 'tst': 0xC800, 'cmp/eq': 0x8800}\nSIZE = {'b': 0, 'w': 1, 'l': 2}\nNOARG = {'nop': 0x0009, 'rts': 0x000B, 'clrt': 0x0008, 'sett': 0x0018}\n\n\ndef _encode(mn, ops, pc, labels, syms, final):\n def lab(name):\n if name in labels:\n return labels[name]\n if final:\n raise AsmError('unknown label %r' % name)\n return pc + 4\n\n if mn in NOARG and not ops:\n return NOARG[mn]\n if mn in ('bt', 'bf', 'bt/s', 'bf/s', 'bt.s', 'bf.s'):\n base = {'bt': 0x8900, 'bf': 0x8B00}[mn[:2]] | (0x0400 if len(mn) > 2 else 0)\n d = (lab(ops[0]) - (pc + 4)) // 2\n if final and not -128 <= d < 128:\n raise AsmError('%s out of reach to %s' % (mn, ops[0]))\n return base | (d & 0xFF)\n if mn in ('bra', 'bsr'):\n d = (lab(ops[0]) - (pc + 4)) // 2\n if final and not -2048 <= d < 2048:\n raise AsmError('%s out of reach' % mn)\n return (0xA000 if mn == 'bra' else 0xB000) | (d & 0xFFF)\n if mn in ('jsr', 'jmp'):\n n = _reg(ops[0].lstrip('@'))\n return (0x400B if mn == 'jsr' else 0x402B) | n << 8\n if mn == 'sts.l' and ops[0] == 'pr':\n return 0x4022 | _reg(ops[1][2:]) << 8\n if mn == 'lds.l' and ops[1] == 'pr':\n return 0x4026 | _reg(ops[0][1:-1]) << 8\n if mn == 'sts':\n src = ops[0].lower()\n n = _reg(ops[1])\n return {'macl': 0x001A, 'mach': 0x000A, 'pr': 0x002A, 'fpscr': 0x006A}[src] | n << 8\n if mn == 'lds':\n m = _reg(ops[0])\n return {'pr': 0x402A, 'fpscr': 0x406A}[ops[1].lower()] | m << 8\n if mn == 'mova':\n tgt = lab(ops[0])\n d = (tgt - ((pc & ~3) + 4)) // 4\n if final and (not 0 <= d < 256 or tgt % 4):\n raise AsmError('mova out of reach')\n return 0xC700 | (d & 0xFF)\n if mn in R1:\n return R1[mn] | _reg(ops[0]) << 8\n if len(ops) == 2 and ops[0].startswith('#'):\n imm = _num(ops[0], syms)\n if mn == 'mov':\n if final and not -128 <= imm < 128:\n raise AsmError('mov #%d out of range' % imm)\n return 0xE000 | _reg(ops[1]) << 8 | (imm & 0xFF)\n if mn == 'add':\n if final and not -128 <= imm < 128:\n raise AsmError('add #%d out of range' % imm)\n return 0x7000 | _reg(ops[1]) << 8 | (imm & 0xFF)\n if mn in IMM0:\n if _reg(ops[1]) != 0:\n raise AsmError('%s #imm needs r0' % mn)\n if final and not (-128 <= imm < 256):\n raise AsmError('%s #%d out of range' % (mn, imm))\n return IMM0[mn] | (imm & 0xFF)\n if mn in RR and len(ops) == 2 and ops[0].lower() in R and ops[1].lower() in R:\n return RR[mn] | _reg(ops[1]) << 8 | _reg(ops[0]) << 4\n m = re.match(r'^mov\\.([bwl])$', mn)\n if m:\n sz = SIZE[m.group(1)]\n src, dst = ops\n # pc-relative literal: mov.l name,Rn / mov.w name,Rn\n if src.lower() not in R and not src.startswith('@') and not src.startswith('#'):\n tgt = lab(src)\n n = _reg(dst)\n if sz == 2:\n d = (tgt - ((pc & ~3) + 4)) // 4\n if final and (not 0 <= d < 256 or tgt % 4):\n raise AsmError('mov.l %s out of reach' % src)\n return 0xD000 | n << 8 | (d & 0xFF)\n if sz == 1:\n d = (tgt - (pc + 4)) // 2\n if final and not 0 <= d < 256:\n raise AsmError('mov.w %s out of reach' % src)\n return 0x9000 | n << 8 | (d & 0xFF)\n raise AsmError('no mov.b literal')\n if src.lower() in R: # stores\n mreg = _reg(src)\n if re.match(r'^@-r\\d+$', dst):\n return 0x2004 | sz | _reg(dst[2:]) << 8 | mreg << 4\n if re.match(r'^@r\\d+$', dst):\n return 0x2000 | sz | _reg(dst[1:]) << 8 | mreg << 4\n mm = re.match(r'^@\\((.+),(r\\d+)\\)$', dst.replace(' ', ''))\n if mm and mm.group(1) == 'r0':\n return 0x0004 | sz | _reg(mm.group(2)) << 8 | mreg << 4\n if mm:\n disp = _num(mm.group(1), syms)\n n = _reg(mm.group(2))\n if disp % (1 << sz) or not 0 <= disp >> sz < 16:\n raise AsmError('bad store displacement %d' % disp)\n if sz == 2:\n return 0x1000 | n << 8 | mreg << 4 | disp >> 2\n if mreg != 0:\n raise AsmError('mov.%s Rm,@(d,Rn) needs r0' % m.group(1))\n return (0x8000 if sz == 0 else 0x8100) | n << 4 | disp >> sz\n raise AsmError('bad store %r' % dst)\n n = _reg(dst) # loads\n if re.match(r'^@r\\d+\\+$', src):\n return 0x6004 | sz | n << 8 | _reg(src[1:-1]) << 4\n if re.match(r'^@r\\d+$', src):\n return 0x6000 | sz | n << 8 | _reg(src[1:]) << 4\n mm = re.match(r'^@\\((.+),(r\\d+)\\)$', src.replace(' ', ''))\n if mm and mm.group(1) == 'r0':\n return 0x000C | sz | n << 8 | _reg(mm.group(2)) << 4\n if mm:\n disp = _num(mm.group(1), syms)\n mreg = _reg(mm.group(2))\n if disp % (1 << sz) or not 0 <= disp >> sz < 16:\n raise AsmError('bad load displacement %d' % disp)\n if sz == 2:\n return 0x5000 | n << 8 | mreg << 4 | disp >> 2\n if n != 0:\n raise AsmError('mov.%s @(d,Rm),Rn needs r0' % m.group(1))\n return (0x8400 if sz == 0 else 0x8500) | mreg << 4 | disp >> sz\n raise AsmError('bad load %r' % src)\n raise AsmError('cannot encode %s %s' % (mn, ', '.join(ops)))\n\n\ndef _delayed(op):\n \"\"\"A branch with a delay slot: bra bsr bt/s bf/s jmp jsr rts rte braf bsrf.\"\"\"\n return (op >> 12 in (0xA, 0xB) or op >> 8 in (0x8D, 0x8F) or op & 0xF0FF in (0x402B, 0x400B, 0x0023, 0x0003)\n or op in (0x000B, 0x002B))\n\n\ndef _slot_illegal(op):\n \"\"\"Not allowed in a delay slot (the CPU raises a slot illegal instruction\n exception): branches, and PC-relative loads (mov.w/mov.l @(disp,PC), mova).\"\"\"\n return (_delayed(op) or op >> 8 in (0x89, 0x8B, 0xC3, 0xC7) or op >> 12 in (0x9, 0xD))\n\n\ndef assemble(text, base, syms=None):\n syms = dict(syms or {})\n lines = []\n for raw in text.splitlines():\n line = raw.split(';', 1)[0].strip()\n while True:\n m = re.match(r'^([A-Za-z_.][\\w.]*):\\s*(.*)$', line)\n if not m:\n break\n lines.append(('label', m.group(1), raw))\n line = m.group(2).strip()\n if line:\n parts = line.split(None, 1)\n lines.append(('ins', (parts[0].lower(), _split_ops(parts[1]) if len(parts) > 1 else []), raw))\n labels = {}\n for final in (False, True):\n pc, out = base, bytearray()\n prev = None # the last instruction's opcode (None after data)\n for kind, arg, raw in lines:\n if kind == 'label':\n if not final and arg in labels and labels[arg] != pc:\n raise AsmError('label %s twice' % arg)\n labels[arg] = pc\n continue\n mn, ops = arg\n try:\n if mn.startswith('.'):\n prev = None\n if mn == '.align':\n n = int(ops[0], 0)\n while pc % n:\n out += struct.pack(' UA_SETTING; the last SAVE_ID in the journal, if any\nua_load:\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l K_journal,r2\n mov.l K_jend,r3\n mov.l K_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nul_loop:\n cmp/hs r3,r2\n bt ul_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt ul_out\n cmp/eq r5,r0\n bf ul_next\n mov.w @(2,r2),r0\n and #1,r0\n mov.b r0,@r1\nul_next:\n bra ul_loop\n add #4,r2\nul_out:\n rts\n nop\n\n .align 4\nK_prev: .long PREV_DECODER\nK_state: .long UA_SETTING\nK_popup: .long POPUP\nK_tick: .long TICK\nK_menu: .long KEY_MENU_AT\nK_push: .long KEY_PUSH_AT\nK_giveup: .long GIVE_UP_AT\nK_show: .long SHOW_SAVER\nK_pool: .long BACKUP_POOL\nK_mbx: .long BACKUP_MBX\nK_getblk: .long GET_BLOCK\nK_send: .long SEND\nK_release: .long RELEASE\nK_memset: .long MEMSET\nK_saveid: .long SAVE_ID\nK_journal: .long JOURNAL\nK_jend: .long JOURNAL_END\n'''\n\n\ndef routine(prev_decoder):\n \"\"\"(code, labels) at UA_CODE, then our label and room for the items.\"\"\"\n h = hotcuekeys\n syms = {\n 'PREV_DECODER': prev_decoder, 'UA_SETTING': UA_SETTING, 'POPUP': POPUP,\n 'POPUP_FOUND': POPUP_FOUND, 'TICK': h.TICK,\n 'KEY_MENU_AT': KEY_MENU[0], 'KEY_MENU_BIT': KEY_MENU[1],\n 'KEY_PUSH_AT': KEY_PUSH[0], 'KEY_PUSH_BIT': KEY_PUSH[1],\n 'PRESS_TICKS': PRESS_TICKS, 'START_WAIT': START_WAIT, 'MENU_WAIT': MENU_WAIT, 'GIVE_UP_AT': MENU_WAIT + GIVE_UP,\n 'SHOW_SAVER': h.SHOW_SAVER, 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX,\n 'GET_BLOCK': h.GET_BLOCK, 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET,\n 'SAVE_ID': SAVE_ID, 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN,\n }\n code, labels = sh4asm.assemble(SOURCE, UA_CODE, syms)\n at = (UA_CODE + len(code) + 3) & ~3\n labels['LABEL'] = at\n at += 2 * len(LABEL) + 2\n labels['ITEMS'] = (at + 3) & ~3\n return code, labels\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as hotcuekeys' (OFF / ON).\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], hotcuekeys.ONOFF_LIST, 2, 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['ua_show'], g32, g36, labels['ua_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"The application with USB AUTO LOAD added, after whatever other mods\n are in: it chains after the decoder literal as it finds it and adds its\n item after the UTILITY items there are.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' \"MY SETTINGS DATA was found.\\nPush the MENU button to LOAD.\"\n","mods/cdj900nxs/wavefix.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"advanced_waveforms 1.0.1: rekordbox's colour detail taken also when it is a\nlittle shorter than the waveform. Part of `advanced_waveforms` (based on\narnoldasr's rgb_waveform, now updated by us); mods/patch_cdj900nxs.py\napplies it with his code, released and unreleased builds alike.\n\n * His wave_fetch_hook asks for PWV5 after the stock PWV3 and finds it with\n wave_find_pwv5 (FIND_AT): the reply must hold at least N entries, N the\n PWV3 columns. rekordbox's PWV5 is often a few entries shorter (50 714\n against 50 734 on the test stick), and then the whole track kept the\n whiteness colours.\n * FIND_AT now jumps to wf5_find (same contract: r4 the reply, r5 its size,\n r6 N -> r0 the N entries, or 0): PWV5 with N or more entries as before;\n with fewer, down to N - TOLERANCE, a copy of ours padded with its last\n entry (WF5_BUF, malloc'd, freed at the next track), so his code reads N\n entries as it always did.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\n\nVERSION = '1.0.1'\nBASE = 0x08000000\nWF_BASE = 0x08FDD800 # after wavemodes' 0x08FDB000..0x08FDD800\nWF_END = 0x08FDDC00\nWF5_BUF = WF_BASE # word: our padded copy (0: none)\nWF_CODE = WF_BASE + 0x10\nFIND_AT = 0x08FC2018 # arnoldasr's wave_find_pwv5 (sts.l pr,@-r15; mov r4,r1; mov #0x40,r2 ...)\nFIND_STOCK = bytes.fromhex('224f4361 40e24363 1473')\nTOLERANCE = 1500 # columns (10 s) PWV5 may be short of PWV3\nMALLOC, FREE = 0x08A5C6EC, 0x08A5EBC2\n\nSOURCE = r'''\n; ---- wf5_find: r4 the reply, r5 its size, r6 N -> r0 N two-byte entries of\n; PWV5 (big-endian header within the first 0x40 bytes), or 0\nwf5_find:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n sts.l pr,@-r15\n mov r6,r12 ; N\n mov r4,r10\n add #20,r10\n add r5,r10 ; the reply's end\n mov r4,r8\n mov #64,r9\nf5_loop:\n mov.b @r8,r0\n cmp/eq #0x50,r0\n bf f5_next\n mov.b @(1,r8),r0\n cmp/eq #0x57,r0\n bf f5_next\n mov.b @(2,r8),r0\n cmp/eq #0x56,r0\n bf f5_next\n mov.b @(3,r8),r0\n cmp/eq #0x35,r0\n bf f5_next\n mov r8,r7 ; entry size 2\n bsr f5_be32\n add #12,r7\n cmp/eq #2,r0\n bf f5_next\n mov r8,r7 ; header length, at most 0x40\n bsr f5_be32\n add #4,r7\n mov #64,r1\n cmp/hi r1,r0\n bt f5_next\n mov r8,r11\n add r0,r11 ; the entries\n mov r8,r7\n bsr f5_be32\n add #16,r7 ; r0: how many\n mov r0,r1\n shll r1\n add r11,r1\n cmp/hi r10,r1\n bt f5_next ; past the reply\n cmp/hs r12,r0\n bt f5_whole ; N or more: as they are\n tst r0,r0\n bt f5_next\n mov.l W_tol,r1 ; short by at most TOLERANCE: a padded copy\n add r0,r1\n cmp/hs r12,r1\n bf f5_next\n bra f5_pad\n mov r0,r9 ; (how many)\nf5_next:\n dt r9\n bf/s f5_loop\n add #1,r8\n bra f5_out\n mov #0,r0\nf5_whole:\n bra f5_out\n mov r11,r0\nf5_pad:\n mov.l W_buf,r1 ; the copy of the track before, freed\n mov.l @r1,r4\n mov #0,r0\n mov.l r0,@r1\n tst r4,r4\n bt f5_alloc\n mov.l W_free,r0\n jsr @r0\n nop\nf5_alloc:\n mov r12,r4\n shll r4\n mov.l W_malloc,r0\n jsr @r0\n nop\n tst r0,r0\n bt f5_out\n mov r0,r8\n mov.l W_buf,r1\n mov.l r8,@r1\n mov r8,r2 ; the entries there are\n mov r11,r1\n mov r9,r3\nf5_copy:\n mov.b @r1+,r0\n mov.b r0,@r2\n mov.b @r1+,r0\n mov.b r0,@(1,r2)\n dt r3\n bf/s f5_copy\n add #2,r2\n add #-2,r1 ; then the last one up to N\n mov.b @r1+,r4\n mov.b @r1,r5\n mov r12,r3\n sub r9,r3\nf5_fill:\n mov r4,r0\n mov.b r0,@r2\n mov r5,r0\n mov.b r0,@(1,r2)\n dt r3\n bf/s f5_fill\n add #2,r2\n mov r8,r0\nf5_out:\n lds.l @r15+,pr\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- f5_be32: r0 = the big-endian word at r7 (any alignment); r5, r7 used\nf5_be32:\n mov.b @r7+,r0\n extu.b r0,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n rts\n or r5,r0\n\n .align 4\nW_tol: .long TOLERANCE\nW_buf: .long WF5_BUF\nW_free: .long FREE\nW_malloc: .long MALLOC\n'''\n\n\ndef patch(app, stock):\n out = bytearray(app)\n if bytes(out[FIND_AT - BASE:FIND_AT - BASE + len(FIND_STOCK)]) != FIND_STOCK:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: wave_find_pwv5 is not where it was')\n code, labels = sh4asm.assemble(SOURCE, WF_CODE, {\n 'TOLERANCE': TOLERANCE, 'WF5_BUF': WF5_BUF, 'FREE': FREE, 'MALLOC': MALLOC})\n assert WF_CODE + len(code) <= WF_END, 'wavefix: past its area'\n if len(out) < WF_END - BASE:\n out += bytes(WF_END - BASE - len(out))\n if any(out[WF_BASE - BASE:WF_END - BASE]):\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: its fix area 0x%08x.. is not free' % WF_BASE)\n out[WF_CODE - BASE:WF_CODE - BASE + len(code)] = code\n # FIND_AT: mov.l @(lit),r0; jmp @r0; nop; nop; lit = wf5_find\n out[FIND_AT - BASE:FIND_AT - BASE + 12] = struct.pack('<4HI', 0xD001, 0x402B, 0x0009, 0x0009, labels['wf5_find'])\n return bytes(out)\n","mods/cdj900nxs/wavemodes.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"WAVEFORM modes for advanced_waveforms on the CDJ-900NXS 1.31: UTILITY >\nWAVEFORM chooses how the scrolling waveform is drawn -- BLUE (the player's\nown blues), RGB (arnoldasr's rekordbox colours) or 3BAND (lows blue, mids\namber, highs white in layers, as the newest players draw it). Our own part\nof `advanced_waveforms` (mods/patch_cdj900nxs.py applies it with arnoldasr's\ncode, unless only the published part is asked for).\n\n * UTILITY gets one more item, WAVEFORM (BLUE / RGB / 3BAND, RGB until\n chosen: WM_SETTING), kept in the settings journal (SAVE_ID), as LED\n ANIMATION's.\n * arnoldasr's code puts a palette index in byte 2 of each column of the\n renderer's working buffer: 8..127 his 120 rekordbox colours (palette at\n his literal PALETTE_LIT), 0..7 the PWV3 whiteness where rekordbox's\n colour detail (PWV5) did not come.\n * The renderer 0x08BCFA34 draws a column as 46 rows up from the centre\n line (the lower half is mirrored by the firmware), every row below the\n column's height in one colour, @r8. Here r8 runs over a 46-entry colour\n column (ROW_LOAD mov.w @r8,r2 -> @r8+), and the 16 instructions at\n COL_AT that pick the palette entry call wm_col, which fills the column\n for the mode: BLUE the player's blue for the index (BLUEMAP: the index's\n whiteness, for rekordbox colours taken from their green and blue), RGB\n his palette entry, 3BAND the bands -- each band's height the column's\n height times its share (BANDS: red the lows, green the mids, blue the\n highs, each against the strongest; for 0..7 the lows in full, the mids\n by whiteness, the highs by its square) -- white up to the highs',\n amber up to the mids', blue up to the lows'. The pixel pointer, row\n count and FPSCR are left as the stock code leaves them.\n * The real 3 bands: rekordbox puts them in ANLZ*.2EX (PWV7: 3 bytes a\n column, lows, mids, highs; PWV6 the same for 1200 overview entries,\n but every band normalised alone). The 1.31 firmware never opens that\n file, but its DB server answers the NXS2's analysis-tag request 0x2c04\n for it as for EXT: wm_fetch (in the wave-fetch task's literal FETCH_LIT,\n after arnoldasr's hook there) asks for PWV7 in 2EX (wm_get_tag, his\n wave_get_tag with the tag and file as arguments), scales it to 0..46 px\n (the track's highest value full height) into a block of ours (WM_BANDS,\n WM_COUNT). In 3BAND a column with these is drawn from them -- highs\n white over mids amber (at MID_SCALE, so the lows show round them) over\n lows blue, as rekordbox layers them, the column as high as its highest\n band; without them the estimate above.\n * The overview: its loop (500 columns, 0x08BC9FB4) draws a column bottom\n up in one colour of its 12-entry table; its call of 0x08B03C5E once a\n column (literal OV_PLAYED_LIT) goes to wm_ov, which fills OVCOL with a\n colour per row and returns index 0, the table literal pointing at OVCOL\n and the three row loads made @r14+. In 3BAND with the real bands the\n column shows them (wm_ovbuild: each band's sum over the column's span\n of the detail, the highest sum OV_ROWS px; PWV6 is not used, its bands\n being normalised one by one), the part played dimmed (PLAYED_DIM);\n otherwise the firmware's colour for the column, from RAM OVERVIEW: the\n player's blues for BLUE, arnoldasr's gradient otherwise (written when\n the mode changes).\n * The firmware draws the overview only when its surface is new (a load);\n a mode changed with a track loaded showed on the detail at once but on\n the overview only after the next load. wm_ovtick (in the overview\n callers' two literals of the playing-address bar's draw, BAR_LITS, run\n every frame right after) draws it again once (OV_DRAW) after a change.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport sh4asm # noqa: E402\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.2.0' # advanced_waveforms with the modes (Semantic Versioning)\nBASE = 0x08000000\nWM_BASE = 0x08FDB000 # after phasereverse's 0x08FDAC00..0x08FDB000\nWM_END = 0x08FDD800\nWM_SETTING = WM_BASE # byte: 0 BLUE, 1 RGB, 2 3BAND (DEFAULT until a choice is saved)\nDEFAULT = 1\nWM_LOADED = WM_BASE + 1 # byte: the saved setting read\nWM_APPLIED = WM_BASE + 2 # byte: the mode the overview's colours are for (0xFF: none yet)\nWM_REDRAW = WM_BASE + 3 # byte: 1 = the overview to be drawn again (the mode changed)\nOV_DRAW = 0x08BC9DAC # draws the overview (no arguments; its callers call it when its surface is new)\nBAR_LITS = (0x08BCBCD8, 0x08BCBEF0) # the overview's callers' literals of the bar's draw, right after it\nBAR_DRAW = 0x08BCA34E\nWM_BANDS = WM_BASE + 4 # word: the track's 3-band detail, 3 bytes a column (lows, mids,\nWM_COUNT = WM_BASE + 8 # highs, 0..46 px); word: its columns (0: none)\nWM_OVBANDS = WM_BASE + 12 # word: the overview's 3-band columns in OVB (wm_ovbuild), 0: none\nWM_TAG = WM_BASE + 20 # words: the tag and file asked for by wm_get_tag\nWM_EXT = WM_BASE + 24\nWM_CODE = WM_BASE + 0x40\nFETCH_LIT = 0x089A061C # the wave-fetch task's literal of dbcl_GetParWaveData (arnoldasr's hook in it)\nWORK = 0x0B7D0760 # the renderer's working buffer: *(WORK + WORK_OFF), 2 bytes a column\nWORK_OFF = 0x0002EE18\nMALLOC, FREE = 0x08A5C6EC, 0x08A5EBC2\nTAG_CHECK, TAG_ALLOC, TAG_INIT, TAG_SEND, TAG_WAIT = 0x089736F2, 0x089A791E, 0x08973C50, 0x089738F0, 0x08974198\nPWV7, EXT_2EX = 0x37565750, 0x00584532 # 'PWV7', '2EX' as the server's tag lookup reads them\nOV_COLUMNS = 500 # the overview's columns (0x08BC9FB4: 125 << 2)\nOV_PLAYED_LIT = 0x08BCA208 # the overview loop's literal of 0x08B03C5E (called once a column)\nOV_PLAYED = 0x08B03C5E\nOV_TABLE_LIT = 0x08BCA210 # ... and of its colour table (RAM 0x11106784)\nOV_LOADS = (0x08BCA050, 0x08BCA07A, 0x08BCA0B0) # mov.w @r14,r2 -> @r14+ (a colour per row)\nOV_ROWS = 34\nMAX_COLUMNS = 270000 # 30 minutes\n\nCOL_AT = 0x08BCFD70 # mov.b @(1,r14),r0 ... mov r0,r8: the palette entry of a column\nCOL_END = 0x08BCFD90\nROW_LOAD = 0x08BCFDA2 # mov.w @r8,r2 -> mov.w @r8+,r2\nPALETTE_LIT = 0x08BCFF50\nFF70_LIT = 0x08BCFF4C\nSTOCK_DETAIL = 0x08C6C75C # the player's 8 blues (image)\nOVERVIEW_IMG = 0x08C6C344 # the overview's 12 colours (image), copied to\nOVERVIEW = 0x11106784 # RAM at start-up\nSAVE_ID = 0x83E5 # ours (hotcuekeys 0x83E1, ledsaver 0x83E2, keynotation 0x83E3, usbautoload 0x83E4)\nLABEL = 'WAVEFORM'\nVALUES = ('BLUE', 'RGB', '3BAND')\nLOW = (40, 110, 255) # the newest players' look: lows blue, mids amber, highs white\nMID = (255, 150, 30)\nHIGH = (255, 255, 255)\nPLAYED_DIM = 0.6 # the overview's part played, 3BAND: colours at 60 %\nMID_SCALE = 218 # the real mids drawn at 85 %: as tall as the lows they hid them\n\n\ndef rgb565(c):\n r, g, b = c\n return (r >> 3) << 11 | (g >> 2) << 5 | b >> 3\n\n\nSOURCE = r'''\n; ---- wm_col: called in place of the renderer's palette pick (COL_AT): r14 ->\n; the column (height, colour index), r9 its height 1..46, r8 its x, r10 the\n; canvas. Out as the stock code: r13 the pixel, r14 46, r12 FPSCR; and r8 ->\n; the colour column (one entry per row up from the centre).\nwm_col:\n mov.l W_state,r7\n mov.b @(1,r7),r0\n tst r0,r0\n bf wc_loaded\n sts.l pr,@-r15\n bsr wm_load ; the first time: the saved setting\n mov r7,r1\n lds.l @r15+,pr\n mov.l W_state,r7\nwc_loaded:\n mov.b @r7,r0 ; the mode\n mov r0,r3\n mov.b @(2,r7),r0 ; the overview's colours for it?\n cmp/eq r3,r0\n bt wc_ov\n sts.l pr,@-r15\n bsr wm_overview\n nop\n lds.l @r15+,pr\nwc_ov:\n mov.b @(1,r14),r0 ; the palette index (0..127, negative: 0)\n cmp/pz r0\n bt wc_pos\n mov #0,r0\nwc_pos:\n mov r0,r6\n mov r3,r0\n cmp/eq #2,r0\n bt wc_bands\n shll r6 ; BLUE or RGB: one colour for the column\n mov.l W_bluemap,r1\n tst r0,r0\n bt wc_one\n mov.l W_palette,r1\nwc_one:\n add r6,r1\n mov.w @r1,r2\n mov.l W_col,r5\n mov r9,r6\nwc_fill:\n mov.w r2,@r5\n dt r6\n bf/s wc_fill\n add #2,r5\n bra wc_done\n nop\nwc_bands:\n mov.l W_count,r1 ; the track's real 3-band detail?\n mov.l @r1,r2\n tst r2,r2\n bt wc_est\n mov.l W_work,r1\n mov.l W_workoff,r0\n mov.l @(r0,r1),r1\n mov r14,r0 ; this column's index\n sub r1,r0\n shlr r0\n cmp/hs r2,r0\n bt wc_est\n mov r0,r1\n shll r0\n add r1,r0\n mov.l W_bandsp,r1\n mov.l @r1,r1\n add r0,r1\n mov.b @r1+,r2\n extu.b r2,r2 ; lows' height\n mov.b @r1+,r3\n extu.b r3,r3 ; mids', at MID_SCALE/256 (so the lows show round them)\n mov #MID_SCALE,r0\n extu.b r0,r0\n mulu.w r0,r3\n sts macl,r3\n shlr8 r3\n mov.b @r1,r4\n extu.b r4,r4 ; highs'\n mov.l W_colours,r7 ; as rekordbox layers them: highs over mids over lows\n mov r2,r9 ; the column: as high as its highest band\n cmp/hi r9,r3\n bf wc_m1\n mov r3,r9\nwc_m1:\n cmp/hi r9,r4\n bf wc_m2\n mov r4,r9\nwc_m2:\n tst r9,r9\n bf wc_layer2\n bra wc_layer2\n mov #1,r9\nwc_est:\n shll2 r6\n mov.l W_bands,r1\n add r6,r1\n mov.b @r1+,r2\n extu.b r2,r2 ; lows' share 0..255\n mov.b @r1+,r3\n extu.b r3,r3 ; mids'\n mov.b @r1,r4\n extu.b r4,r4 ; highs'\n add #1,r2 ; height * (share + 1) >> 8\n mulu.w r9,r2\n sts macl,r2\n shlr8 r2\n add #1,r3\n mulu.w r9,r3\n sts macl,r3\n shlr8 r3\n add #1,r4\n mulu.w r9,r4\n sts macl,r4\n shlr8 r4\n mov.l W_colours,r7 ; white, amber, blue: highs over mids over lows\nwc_layer2:\n mov.l W_col,r5\n mov #0,r6 ; the row up from the centre\nwc_row:\n cmp/ge r9,r6\n bt wc_done\n cmp/ge r4,r6\n bt wc_nohigh\n mov.w @r7,r0 ; inside the highs: white\n bra wc_put\n nop\nwc_nohigh:\n cmp/ge r3,r6\n bt wc_nomid\n mov.w @(2,r7),r0 ; the second band\n bra wc_put\n nop\nwc_nomid:\n mov.w @(4,r7),r0 ; the third (and the rest of the height)\nwc_put:\n mov.w r0,@r5\n add #2,r5\n bra wc_row\n add #1,r6\nwc_done:\n shll r8 ; the pixel, as the stock code makes it\n add r10,r8\n mov.l W_ff70,r1\n add r1,r8\n mov r8,r13\n mov.l W_col,r8\n mov #46,r14\n rts\n sts fpscr,r12\n\n; ---- wm_overview: r7 -> our state, r3 the mode: the overview's 12 colours\n; for it (the player's blues for BLUE, arnoldasr's otherwise)\nwm_overview:\n mov #1,r0 ; the overview drawn again (wm_ovtick)\n mov.b r0,@(3,r7)\n mov r3,r0\n mov.b r0,@(2,r7)\n mov.l W_ovrgb,r1\n tst r0,r0\n bf wo_src\n mov.l W_ovblue,r1\nwo_src:\n mov.l W_ovram,r2\n mov #12,r0\nwo_loop:\n mov.w @r1+,r4\n mov.w r4,@r2\n dt r0\n bf/s wo_loop\n add #2,r2\n rts\n nop\n\n; ---- wm_ovtick: through the two literals the overview's callers have for the\n; playing-address bar (drawn every frame, right after the overview is drawn\n; when its surface is new, i.e. on a load): once the mode changed (WM_REDRAW,\n; set by wm_overview), the overview is drawn again first, so a WAVEFORM\n; changed with a track loaded shows on both waveforms at once; then the bar\nwm_ovtick:\n mov.l T_state,r1\n mov.b @(3,r1),r0\n tst r0,r0\n bt wt_bar\n mov #0,r0\n mov.b r0,@(3,r1)\n sts.l pr,@-r15\n mov.l r4,@-r15\n mov.l r5,@-r15\n mov.l r6,@-r15\n mov.l r7,@-r15\n mov.l T_ovdraw,r0\n jsr @r0\n nop\n mov.l T_fpmask,r1 ; FPSCR as the callers leave it after the overview\n sts fpscr,r2\n and r1,r2\n lds r2,fpscr\n mov.l @r15+,r7\n mov.l @r15+,r6\n mov.l @r15+,r5\n mov.l @r15+,r4\n lds.l @r15+,pr\nwt_bar:\n mov.l T_bar,r0\n jmp @r0\n nop\n\n .align 4\nT_state: .long WM_SETTING\nT_ovdraw: .long OV_DRAW\nT_fpmask: .long 0xFFE7FFFF\nT_bar: .long PREV_BAR\n\n; ---- wm_load: r1 -> WM_SETTING; the last SAVE_ID in the journal, if any\nwm_load:\n mov #1,r0\n mov.b r0,@(1,r1)\n mov.l W_journal,r2\n mov.l W_jend,r3\n mov.l W_saveid,r5\n mov #-1,r6\n extu.w r6,r6\nwl_loop:\n cmp/hs r3,r2\n bt wl_out\n mov.w @r2,r0\n extu.w r0,r0\n cmp/eq r6,r0 ; erased: the end\n bt wl_out\n cmp/eq r5,r0\n bf wl_next\n mov.w @(2,r2),r0\n and #3,r0\n cmp/eq #3,r0\n bt wl_next ; (no such value)\n mov.b r0,@r1\nwl_next:\n bra wl_loop\n add #4,r2\nwl_out:\n rts\n nop\n\n; ---- wm_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nwm_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt ww_ours\n mov.l W_show,r1\n jmp @r1\n nop\nww_ours:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l W_state,r1\n mov.b @(1,r1),r0\n tst r0,r0\n bf ww_loaded\n mov.l r5,@-r15 ; the row the caller passed in r5: the load uses r5\n bsr wm_load\n nop\n mov.l @r15+,r5\nww_loaded:\n mov.l W_state,r1\n mov.b @r1,r0\n extu.b r0,r0\n mov.l r0,@(16,r8) ; shown as if being chosen\n mov.l W_show,r1\n jsr @r1\n mov r8,r4\n mov.l W_state,r1\n mov.b @r1,r1\n extu.b r1,r1\n mov.l r1,@(12,r8)\n mov #-1,r1\n mov.l r1,@(16,r8)\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- wm_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nwm_take:\n mov.l @(16,r4),r5\n mov.w @(8,r4),r0\n extu.w r5,r6\n extu.w r0,r2\n cmp/ge r2,r6\n bt wt_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt wt_none\n mov.l r5,@(12,r4)\n mov.l W_state,r1\n mov r5,r0\n mov.b r0,@r1\n mov #1,r0\n mov.b r0,@(1,r1)\n sts.l pr,@-r15\n bsr wm_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nwt_none:\n rts\n mov #0,r0\n\n; ---- wm_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5)\nwm_save:\n mov.l r8,@-r15\n mov.l r9,@-r15\n sts.l pr,@-r15\n add #-4,r15\n mov r4,r8\n mov.l W_pool,r1\n mov.l @r1,r4\n mov r15,r5\n mov #0,r6\n mov.l W_getblk,r1\n jsr @r1\n nop\n tst r0,r0\n bf wv_out\n mov.l @r15,r9\n tst r9,r9\n bt wv_out\n mov r9,r4\n mov #0,r5\n mov #-116,r6\n mov.l W_memset,r1\n jsr @r1\n extu.b r6,r6 ; 0x8C bytes\n mov #5,r0\n mov.b r0,@(4,r9)\n mov.l W_saveid,r0\n mov.w r0,@(8,r9)\n mov r8,r0\n mov.w r0,@(10,r9)\n mov.l W_mbx,r1\n mov.l @r1,r4\n mov r9,r5\n mov.l W_send,r1\n jsr @r1\n nop\n tst r0,r0\n bt wv_out\n mov.l W_pool,r1 ; not taken: give the block back\n mov.l @r1,r4\n mov r9,r5\n mov.l W_release,r1\n jsr @r1\n nop\nwv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nW_state: .long WM_SETTING\nW_count: .long WM_COUNT\nW_bandsp: .long WM_BANDS\nW_work: .long WORK\nW_workoff: .long WORK_OFF\nW_bands: .long BANDS\nW_bluemap: .long BLUEMAP\nW_palette: .long PALETTE\nW_col: .long COLUMN\nW_colours: .long COLOURS\nW_ff70: .long 0xFF70\nW_ovrgb: .long OV_RGB\nW_ovblue: .long OV_BLUE\nW_ovram: .long OVERVIEW\nW_journal: .long JOURNAL\nW_jend: .long JOURNAL_END\nW_saveid: .long SAVE_ID\nW_show: .long SHOW_SAVER\nW_pool: .long BACKUP_POOL\nW_mbx: .long BACKUP_MBX\nW_getblk: .long GET_BLOCK\nW_send: .long SEND\nW_release: .long RELEASE\nW_memset: .long MEMSET\n\n; ---- wm_fetch: the wave-fetch task's dbcl_GetParWaveData through its literal\n; (r4 connection, r5 track, r6 &size, r7 &block), arnoldasr's hook first;\n; then the track's 3-band detail (PWV7 in ANLZ*.2EX) by the analysis-tag\n; request, scaled to 0..46 px (the track's highest value full height) into\n; a block of ours (WM_BANDS, WM_COUNT). Returns what the call returned.\nwm_fetch:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-8,r15\n mov r4,r8\n mov r5,r9\n mov.l F_prev,r0\n jsr @r0\n nop\n mov r0,r12\n cmp/pz r12\n bf wf_out\n mov.l F_state,r1 ; ask for PWV7 in 2EX\n mov.l F_pwv7,r0\n mov.l r0,@(20,r1)\n mov.l F_2ex,r0\n mov.l r0,@(24,r1)\n mov #0,r0\n mov.l r0,@r15\n mov.l r0,@(4,r15)\n mov r8,r4\n mov r9,r5\n mov r15,r6\n mov r15,r7\n bsr wm_get_tag\n add #4,r7\n mov.l @(4,r15),r10 ; the reply\n tst r10,r10\n bt wf_out\n cmp/pz r0\n bf wf_free\n mov r10,r1 ; 'PWV7' in its first 0x40 bytes\n mov #64,r2\nff_loop:\n mov.b @r1,r0\n cmp/eq #0x50,r0\n bf ff_next\n mov.b @(1,r1),r0\n cmp/eq #0x57,r0\n bf ff_next\n mov.b @(2,r1),r0\n cmp/eq #0x56,r0\n bf ff_next\n mov.b @(3,r1),r0\n cmp/eq #0x37,r0\n bt ff_found\nff_next:\n dt r2\n bf/s ff_loop\n add #1,r1\n bra wf_free\n nop\nff_found:\n mov r1,r7 ; its header's length, entry size 3, entries\n bsr wm_be32\n add #4,r7\n mov r0,r11\n mov r1,r7\n bsr wm_be32\n add #12,r7\n cmp/eq #3,r0\n bf wf_free\n mov r1,r7\n bsr wm_be32\n add #16,r7\n mov r0,r13 ; entries\n tst r13,r13\n bt wf_free\n mov.l F_max,r2\n cmp/hi r2,r13\n bt wf_free\n add r11,r1 ; the entries\n mov r1,r14\n mov r13,r0 ; within the reply (its size + 0x14)\n shll r0\n add r13,r0\n add r14,r0\n mov.l @r15,r2\n add r10,r2\n add #20,r2\n cmp/hi r2,r0\n bt wf_free\n mov r13,r3 ; the highest value\n shll r3\n add r13,r3\n mov r14,r1\n mov #1,r2\nfm_loop:\n mov.b @r1+,r0\n extu.b r0,r0\n cmp/hi r2,r0\n bf fm_next\n mov r0,r2\nfm_next:\n dt r3\n bf fm_loop\n mov.l F_scale46,r4 ; scale = (46 << 16) / highest\n bsr wm_div\n mov r2,r5\n mov r0,r11\n mov.l F_state,r1 ; the old block out of use, then freed\n mov #0,r0\n mov.l r0,@(8,r1)\n mov.l r0,@(12,r1)\n mov.l @(4,r1),r4\n mov.l r0,@(4,r1)\n tst r4,r4\n bt fa_new\n mov.l F_free,r0\n jsr @r0\n nop\nfa_new:\n mov r13,r4\n shll r4\n add r13,r4\n mov.l F_malloc,r0\n jsr @r0\n nop\n tst r0,r0\n bt wf_free\n mov r0,r9\n mov r13,r3\n shll r3\n add r13,r3\n mov r14,r1\n mov r9,r2\nfc_loop:\n mov.b @r1+,r0\n extu.b r0,r0\n mul.l r11,r0\n sts macl,r0\n shlr16 r0\n mov.b r0,@r2\n dt r3\n bf/s fc_loop\n add #1,r2\n mov.l F_state,r1\n mov.l r9,@(4,r1)\n mov.l r13,@(8,r1)\n mov r9,r4 ; the overview's bands from it\n bsr wm_ovbuild\n mov r13,r5\nwf_free:\n mov r10,r4\n mov.l F_free,r0\n jsr @r0\n nop\nwf_out:\n mov r12,r0\n add #8,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- wm_ovbuild: r4 the detail's bands (3 bytes a column, 0..46 px), r5 its\n; columns: the overview's OV_COLUMNS columns into OVB, each band's sum over\n; the column's span (OVSUM), then all at OV_ROWS for the highest sum;\n; WM_OVBANDS = OV_COLUMNS\nwm_ovbuild:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov.l B_sum,r10\n mov #0,r11 ; the column\n mov #1,r12 ; the highest sum\n mov #0,r13 ; the column's first entry\nob_col:\n mov r11,r4 ; its end: (column + 1) * entries / OV_COLUMNS\n add #1,r4\n mul.l r9,r4\n sts macl,r4\n mov.l B_cols,r5\n bsr wm_div\n nop\n mov r0,r14\n cmp/hi r13,r14\n bt ob_span\n mov r13,r14\n add #1,r14\nob_span:\n mov #0,r5\n mov #0,r6\n mov #0,r7\n mov r13,r1\n shll r1\n add r13,r1\n add r8,r1\n mov r14,r3\n sub r13,r3\nob_sum:\n mov.b @r1+,r0\n extu.b r0,r0\n add r0,r5\n mov.b @r1+,r0\n extu.b r0,r0\n add r0,r6\n mov.b @r1+,r0\n extu.b r0,r0\n dt r3\n bf/s ob_sum\n add r0,r7\n mov.w r5,@r10 ; (u16 sums)\n mov r6,r0\n mov.w r0,@(2,r10)\n mov r7,r0\n mov.w r0,@(4,r10)\n add #6,r10\n cmp/hi r12,r5\n bf ob_h1\n mov r5,r12\nob_h1:\n cmp/hi r12,r6\n bf ob_h2\n mov r6,r12\nob_h2:\n cmp/hi r12,r7\n bf ob_h3\n mov r7,r12\nob_h3:\n mov r14,r13\n add #1,r11\n mov.l B_cols,r0\n cmp/eq r0,r11\n bf ob_col\n mov.l B_scale,r4 ; scale = (OV_ROWS << 16) / highest sum\n bsr wm_div\n mov r12,r5\n mov r0,r2\n mov.l B_sum,r1\n mov.l B_ovb,r3\n mov.l B_cols3,r6\nob_scale:\n mov.w @r1+,r0\n extu.w r0,r0\n mul.l r2,r0\n sts macl,r0\n shlr16 r0\n mov.b r0,@r3\n dt r6\n bf/s ob_scale\n add #1,r3\n mov.l B_state,r1\n mov.l B_cols,r0\n mov.l r0,@(12,r1)\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n .align 4\nB_ovb: .long OVB\nB_sum: .long OVSUM\nB_cols: .long OV_COLUMNS\nB_cols3: .long OV_COLUMNS3\nB_scale: .long OV_SCALE\nB_state: .long WM_SETTING\n\n; ---- wm_ov: called in place of 0x08B03C5E by the overview's loop, once a\n; column: r11 the column (0..499), r13 its height (0..OV_ROWS), r14 its\n; colour index. Fills OVCOL (one colour per row from the bottom) and returns\n; r0 0 and r14 0, so the loop reads OVCOL (its table literal points there);\n; in 3BAND with the real bands the column's height becomes its highest (r13).\nwm_ov:\n sts.l pr,@-r15\n mov.l O_played,r0 ; the firmware's own call (+6 to the index when set)\n jsr @r0\n nop\n mov r0,r3\n mov.l O_state,r7\n mov.b @r7,r0\n cmp/eq #2,r0\n bf ov_plain\n mov.l @(12,r7),r2\n tst r2,r2\n bt ov_plain\n mov r11,r0 ; the column's entry\n cmp/hs r2,r0\n bt ov_plain\n mov r0,r1\n shll r0\n add r1,r0\n mov.l O_ovb,r1\n add r0,r1\n mov.b @r1+,r2\n extu.b r2,r2 ; lows\n mov.b @r1+,r4\n extu.b r4,r4 ; mids, at MID_SCALE/256\n mov #MID_SCALE,r0\n extu.b r0,r0\n mulu.w r0,r4\n sts macl,r4\n shlr8 r4\n mov.b @r1,r5\n extu.b r5,r5 ; highs\n mov r2,r13 ; the column's height: its highest band\n cmp/hi r13,r4\n bf ov_m1\n mov r4,r13\nov_m1:\n cmp/hi r13,r5\n bf ov_m2\n mov r5,r13\nov_m2:\n mov.l O_colours,r7\n tst r3,r3\n bt ov_dimmed\n add #8,r7 ; the part played: dimmed\nov_dimmed:\n mov.l O_col,r1\n mov #0,r6\nov_row:\n cmp/ge r13,r6\n bt ov_out\n cmp/ge r5,r6\n bt ov_nohigh\n mov.w @r7,r0 ; highs: white\n bra ov_put\n nop\nov_nohigh:\n cmp/ge r4,r6\n bt ov_nomid\n mov.w @(2,r7),r0 ; mids: amber\n bra ov_put\n nop\nov_nomid:\n mov.w @(4,r7),r0 ; lows: blue\nov_put:\n mov.w r0,@r1\n add #2,r1\n bra ov_row\n add #1,r6\nov_plain:\n mov r14,r0 ; the firmware's colour for the whole column\n tst r3,r3\n bt ov_idx\n add #6,r0\nov_idx:\n extu.b r0,r0\n shll r0\n mov.l O_table,r1\n add r0,r1\n mov.w @r1,r2\n mov.l O_col,r1\n mov #OV_ROWS,r6\nov_fill:\n mov.w r2,@r1\n dt r6\n bf/s ov_fill\n add #2,r1\nov_out:\n mov #0,r14\n lds.l @r15+,pr\n rts\n mov #0,r0\n\n .align 4\nO_played: .long OV_PLAYED\nO_state: .long WM_SETTING\nO_ovb: .long OVB\nO_colours: .long COLOURS\nO_col: .long OVCOL\nO_table: .long OVERVIEW\n\n; ---- wm_be32: r0 = the big-endian word at r7 (any alignment); r5, r7 used\nwm_be32:\n mov.b @r7+,r0\n extu.b r0,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n or r5,r0\n mov.b @r7+,r5\n extu.b r5,r5\n shll8 r0\n rts\n or r5,r0\n\n; ---- wm_div: r0 = r4 / r5, unsigned (r1, r2, r4 used)\nwm_div:\n mov #0,r0\n mov #0,r1\n mov #32,r2\nwd_loop:\n shll r4\n rotcl r1\n shll r0\n cmp/hs r5,r1\n bf wd_next\n sub r5,r1\n add #1,r0\nwd_next:\n dt r2\n bf wd_loop\n rts\n nop\n\n; ---- wm_get_tag: arnoldasr's wave_get_tag (dbcl_GetParWaveData with the NXS2\n; analysis-tag request 0x2c04) for the tag and file at WM_TAG/WM_EXT: r4\n; connection, r5 track, r6 &size, r7 &block -> r0 the wait's result (>= 0\n; ok), -1 when nothing was sent\nwm_get_tag:\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-12,r15\n mov r4,r14\n mov r5,r12\n mov r6,r9\n mov r7,r10\n mov #-1,r13\n tst r14,r14\n bt gt_out\n mov.l @r14,r11\n tst r11,r11\n bt gt_out\n mov.l G_check,r1\n jsr @r1\n mov r11,r4\n tst r0,r0\n bt gt_out\n mov.l G_alloc,r1\n jsr @r1\n mov #72,r4\n tst r0,r0\n bt gt_out\n mov.l r0,@(4,r15)\n mov.l r0,@(8,r15)\n mov #0,r1\n mov.l r1,@r15\n mov r14,r4\n mov r0,r5\n mov.l G_type,r6\n mov.l G_init,r1\n jsr @r1\n mov #1,r7\n mov.l @(4,r15),r2\n mov.l r12,@(24,r2)\n mov.l G_state,r3\n mov.l @(20,r3),r1\n mov.l r1,@(28,r2)\n mov.l @(24,r3),r1\n mov.l r1,@(32,r2)\n mov r11,r4\n mov.l @(8,r15),r5\n mov.l G_send,r1\n jsr @r1\n nop\n tst r0,r0\n bf gt_out\n mov.l r10,@-r15\n mov.l @(12,r15),r4\n mov.l G_reply,r5\n mov r15,r6\n add #4,r6\n mov r9,r7\n mov.l G_wait,r1\n jsr @r1\n nop\n mov r0,r13\n add #4,r15\ngt_out:\n mov r13,r0\n add #12,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n rts\n mov.l @r15+,r9\n\n .align 4\nF_prev: .long PREV_FETCH\nF_state: .long WM_SETTING\nF_pwv7: .long TAG_PWV7\nF_2ex: .long EXT_2EX\nF_max: .long MAX_COLUMNS\nF_scale46: .long 0x2E0000\nF_free: .long FREE\nF_malloc: .long MALLOC\nG_check: .long TAG_CHECK\nG_alloc: .long TAG_ALLOC\nG_init: .long TAG_INIT\nG_send: .long TAG_SEND\nG_wait: .long TAG_WAIT\nG_type: .long 0x2C04\nG_reply: .long 0x4F02\nG_state: .long WM_SETTING\n'''\n\nLAYOUT = (('COLOURS', 16), ('COLUMN', 2 * 48), ('OVCOL', 2 * 36), ('OVB', 3 * 500), ('OVSUM', 6 * 500), ('BANDS', 4 * 128), ('BLUEMAP', 2 * 128),\n ('OV_RGB', 24), ('OV_BLUE', 24), ('LABEL', 2 * len(LABEL) + 2)) + \\\n tuple(('VALUE%d' % i, 2 * len(v) + 2) for i, v in enumerate(VALUES)) + (('LIST', 4 * len(VALUES)),)\n\n\ndef routine(palette_at, prev_fetch=0, prev_bar=BAR_DRAW):\n h = hotcuekeys\n syms = {k: 0 for k, _ in LAYOUT}\n syms['COLOURS2'] = 0\n syms.update({\n 'PREV_FETCH': prev_fetch, 'PREV_BAR': prev_bar, 'OV_DRAW': OV_DRAW, 'MID_SCALE': MID_SCALE - 256 if MID_SCALE > 127 else MID_SCALE, 'WM_COUNT': WM_COUNT, 'WM_BANDS': WM_BANDS, 'WORK': WORK,\n 'WORK_OFF': WORK_OFF, 'MAX_COLUMNS': MAX_COLUMNS, 'FREE': FREE, 'MALLOC': MALLOC,\n 'TAG_CHECK': TAG_CHECK, 'TAG_ALLOC': TAG_ALLOC, 'TAG_INIT': TAG_INIT, 'TAG_SEND': TAG_SEND,\n 'TAG_WAIT': TAG_WAIT, 'TAG_PWV7': PWV7, 'EXT_2EX': EXT_2EX,\n 'OV_COLUMNS': OV_COLUMNS, 'OV_COLUMNS3': 3 * OV_COLUMNS, 'OV_SCALE': OV_ROWS << 16,\n 'OV_PLAYED': OV_PLAYED, 'OV_ROWS': OV_ROWS,\n 'WM_SETTING': WM_SETTING, 'PALETTE': palette_at, 'OVERVIEW': OVERVIEW, 'SAVE_ID': SAVE_ID,\n 'JOURNAL': h.JOURNAL, 'JOURNAL_END': h.JOURNAL + h.JOURNAL_LEN, 'SHOW_SAVER': h.SHOW_SAVER,\n 'BACKUP_POOL': h.BACKUP_POOL, 'BACKUP_MBX': h.BACKUP_MBX, 'GET_BLOCK': h.GET_BLOCK,\n 'SEND': h.SEND, 'RELEASE': h.RELEASE, 'MEMSET': h.MEMSET,\n })\n code, _ = sh4asm.assemble(SOURCE, WM_CODE, syms)\n lay = {}\n at = WM_CODE + len(code)\n for name, size in LAYOUT:\n at = (at + 3) & ~3\n lay[name] = at\n at += size\n lay['ITEMS'] = (at + 3) & ~3\n syms.update({k: lay[k] for k, _ in LAYOUT})\n syms['COLOURS2'] = lay['COLOURS'] + 8\n code2, labels = sh4asm.assemble(SOURCE, WM_CODE, syms)\n assert len(code2) == len(code)\n labels.update(lay)\n return code2, labels\n\n\ndef bands(palette):\n \"\"\"Per palette entry the three bands' shares 0..255.\"\"\"\n out = bytearray()\n for i, c in enumerate(palette):\n r, g, b = (c >> 11) / 31, (c >> 5 & 63) / 63, (c & 31) / 31\n top = max(r, g, b)\n if i < 8: # no colour detail: by whiteness\n w = i / 7 # (more white, more mids and highs)\n out += bytes((255, round(230 * w), round(220 * w * w), 0))\n elif top == 0:\n out += bytes((255, 0, 0, 0))\n else:\n out += bytes((round(255 * r / top), round(255 * g / top), round(255 * b / top), 0))\n return bytes(out)\n\n\ndef bluemap(palette, blues):\n \"\"\"Per palette entry the player's own blue: 0..7 the whiteness itself,\n rekordbox colours by their green and blue (the higher bands).\"\"\"\n out = []\n for i, c in enumerate(palette):\n if i < 8:\n w = i\n else:\n g, b = (c >> 5 & 63) / 63, (c & 31) / 31\n w = min(7, round(7 * (0.45 * g + 0.55 * b)))\n out.append(blues[w])\n return struct.pack('<128H', *out)\n\n\ndef item(labels):\n \"\"\"Our UTILITY item, 52 bytes, as SCREEN SAVER's with our 3 values.\"\"\"\n g24, g32, g36, g44, g48 = hotcuekeys.GENERIC\n return struct.pack('<13I', labels['LABEL'], labels['LIST'], len(VALUES), 0xFFFFFFFF, 0xFFFFFFFF, 1,\n g24, labels['wm_show'], g32, g36, labels['wm_take'], g44, g48)\n\n\ndef patch(app, stock):\n \"\"\"advanced_waveforms' modes, on arnoldasr's code (its palette and 0..127\n clamp must be in); the item goes after the UTILITY items there are.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' r0 its record\nww_res:\n mov.l Q_zoom,r0\n cmp/eq r0,r4\n bt wq_zoom\n mov.l Q_grid,r0\n cmp/eq r0,r4\n bt wq_grid\n mov.l Q_prev,r0\n jmp @r0\n nop\nwq_zoom:\n mov.l Q_reczoom,r0\n rts\n nop\nwq_grid:\n mov.l Q_recgrid,r0\n rts\n nop\n\n .align 4\nM_size: .long BUF_SIZE\nM_alloc: .long BUF_SIZE + 32\nM_vars: .long WW_VARS\nM_malloc: .long MALLOC\nM_memset: .long PREV_MEMSET\nM_lit0: .long BUF_LIT0\nM_lit1: .long BUF_LIT1\nM_lit2: .long BUF_LIT2\nM_mask: .long FPSCR_MASK\nM_end: .long RENDER_END\nR_zoomw: .long RES_ZOOM_WHEEL\nR_gridw: .long RES_GRID_WHEEL\nR_zoom: .long RES_ZOOM\nR_grid: .long RES_GRID\nR_prev: .long PREV_DRAW\nR_widget: .long WW_WIDGET\nR_tl: .long BOX_TL\nR_br: .long BOX_BR\nQ_zoom: .long RES_ZOOM\nQ_grid: .long RES_GRID\nQ_prev: .long PREV_RES\nQ_reczoom: .long REC_ZOOM\nQ_recgrid: .long REC_GRID\n'''\n\n\ndef end_code():\n \"\"\"The last column, in the stock's eleven instructions: r6 = the columns\n ahead of the position (r6 - r5) + 267 while they are under WIDTH - 268,\n else WIDTH (both as the stock's 267 and 536).\"\"\"\n ahead = WIDTH - PLAY_COL\n assert 256 + 127 >= ahead > 256 and -128 <= (PLAY_COL - 1) - ahead < 0\n assert -128 <= WIDTH - 2 * (PLAY_COL - 1) <= 127\n ops = [\n 0x3658, # sub r5,r6 ahead\n 0xE501, # mov #1,r5\n 0x4518, # shll8 r5\n 0x7500 | (ahead - 256), # add #..,r5 WIDTH - 268\n 0x3652, # cmp/hs r5,r6\n 0x7500 | (((PLAY_COL - 1) - ahead) & 0xFF), # add #..,r5 267\n 0x365C, # add r5,r6 ahead + 267\n 0x8B02, # bf past the three below\n 0x6653, # mov r5,r6\n 0x4600, # shll r6\n 0x7600 | ((WIDTH - 2 * (PLAY_COL - 1)) & 0xFF), # add #..,r6 WIDTH\n ]\n return struct.pack('<11H', *ops)\n\n\ndef split(value):\n \"\"\"(hi, lo), both signed bytes, with hi * 256 + lo == value.\"\"\"\n hi = (value + 128) >> 8\n lo = value - hi * 256\n assert -128 <= hi <= 127 and -128 <= lo <= 127, value\n return hi, lo\n\n\ndef letters(stock, text):\n \"\"\"The wheel's lettering for `text` as rows of alpha 0..31 (its red).\"\"\"\n res, (y0, y1), x0 = LETTERS[text]\n rec = RES_TABLE + res * 44\n w, h = struct.unpack_from('> 11) if px[y * w + x] != KEY else 0 for x in range(x0, w)]\n for y in range(y0, y1)]\n cols = [x for x in range(len(rows[0])) if any(r[x] > 3 for r in rows)]\n return [r[cols[0]:cols[-1] + 1] for r in rows]\n\n\ndef box(stock, text):\n \"\"\"BOX_W x BOX_H RGB565: the grey frame, `text` in the wheel's letters.\"\"\"\n px = [[0] * BOX_W for _ in range(BOX_H)]\n for y in range(BOX_H):\n for x in range(BOX_SHOWN, BOX_W):\n px[y][x] = KEY\n last = BOX_SHOWN - 1\n for x in range(1, last):\n px[0][x] = px[BOX_H - 1][x] = FRAME\n for y in range(1, BOX_H - 1):\n px[y][0] = px[y][last] = FRAME\n glyphs = letters(stock, text)\n gh, gw = len(glyphs), len(glyphs[0])\n top, left = (BOX_H - gh + 1) // 2, (BOX_SHOWN - gw + 1) // 2\n for gy, row in enumerate(glyphs):\n for gx, a in enumerate(row):\n if a:\n r, g, b = (c * min(a, 31) // 31 for c in INK)\n px[top + gy][left + gx] = (r >> 3) << 11 | (g >> 2) << 5 | b >> 3\n return struct.pack('<%dH' % (BOX_W * BOX_H), *(p for row in px for p in row))\n\n\ndef record(stock, pixels_at):\n \"\"\"A resolved resource record (as RESOLVE leaves them) for a box at `pixels_at`.\"\"\"\n rec = bytearray(stock[RES_TABLE + RES_OFF_BOX * 44:RES_TABLE + RES_OFF_BOX * 44 + 44])\n struct.pack_into('> 8) & 15\n if mov >> 12 != 0xE or add >> 12 != 0x7 or (add >> 8) & 15 != n or \\\n half(out, mov_at) != mov or half(out, add_at) != add or \\\n 0x4018 | n << 8 not in [half(stock, a) for a in range(mov_at + 2, add_at, 2)]:\n fail('the constant at 0x%08x/0x%08x is not the expected one' % (mov_at, add_at))\n old = struct.unpack('b', bytes([mov & 0xFF]))[0] * 256 + struct.unpack('b', bytes([add & 0xFF]))[0]\n if old not in (STOCK_W, STOCK_W - 1) and old % (STOCK_W * 2):\n fail('the constant at 0x%08x is %d' % (mov_at, old))\n hi, lo = split(value)\n struct.pack_into('= hi - BASE:\n found = [a for a in range(lo, hi, 4) if word(out, a) == 61 * STOCK_W * 2]\n if len(found) > 1:\n fail('advanced_waveforms has more than one centre-row literal')\n for a in found:\n struct.pack_into('Datenschutzerklärung · Pionaarr Mods
-
-
-
+
+
+
diff --git a/docs.html b/docs.html
index 74850dc..ed99d83 100644
--- a/docs.html
+++ b/docs.html
@@ -6,9 +6,9 @@
Pionaarr Mods Docs
-
-
-
+
+
+
@@ -219,8 +219,8 @@
UTILITY › KEY NOTATION.
credit Pionaarr mod
-
The boot screen says Pionaarr Mods 1.9.0 where it said Ver 1.31. Ticked at the start; untick it to keep the stock text. UTILITY and the LINK screen still show the real version, 1.31.
- The boot screen.
+
The boot screen says Pionaarr Mods 1.9.1 where it said Ver 1.31. Ticked at the start; untick it to keep the stock text. UTILITY and the LINK screen still show the real version, 1.31.
+ The boot screen.
cuenames Pionaarr modexperimental
The comment a memory cue or hot cue has in rekordbox (the cue list's comment field, exported with the stick) appears in the title bar, right of the title, in the cue's colour: always the next cue ahead that has one, a hot cue with its letter ("A DROP"). A cue just passed shows for a second more, so a cue you CALL shows its name while you stand on it. Hidden while BROWSE is open; a long title is cut short before the name. Needs cuecolours.
@@ -283,7 +283,7 @@
Do I need rekordbox?
Cue colours and hot cues come from a USB stick exported with rekordbox. Without them the mods show what the stock player shows.
Why is the version still 1.31?
-
The update file says 1.32 so the player installs it; the application inside still reports 1.31 (in UTILITY and on the LINK screen). Only the boot screen says Pionaarr Mods 1.9.0 instead of Ver 1.31.
+
The update file says 1.32 so the player installs it; the application inside still reports 1.31 (in UTILITY and on the LINK screen). Only the boot screen says Pionaarr Mods 1.9.1 instead of Ver 1.31.
diff --git a/issues.html b/issues.html
index 9dc0442..915935b 100644
--- a/issues.html
+++ b/issues.html
@@ -6,9 +6,9 @@
Pionaarr Mods Known Issues
-
-
-
+
+
+
diff --git a/tools/changelog.json b/tools/changelog.json
index d75c452..ca73ff1 100644
--- a/tools/changelog.json
+++ b/tools/changelog.json
@@ -1,6 +1,14 @@
{
"_about": "The site's changelog, newest first. Versions follow Semantic Versioning (MAJOR.MINOR.PATCH), dates ISO 8601 (DIN EN 28601). make_builder_data.py adds every mod's current version from its module and writes assets/versions.js.",
"site": [
+ {
+ "version": "1.9.1",
+ "date": "2026-10-21",
+ "changes": [
+ "Phase meter 1.0.1: PHASE METER REVERSE is kept when the player is switched off and on (it came back as OFF).",
+ "LED animation 1.0.1: its setting no longer shares a slot with BEAT JUMP's, which could mix the two up after a restart."
+ ]
+ },
{
"version": "1.9.0",
"date": "2026-10-21",
@@ -389,6 +397,24 @@
"The big waveform 624 columns wide instead of 536, to the right edge; the extra columns are ahead of the playing position. The ZOOM/GRID wheel replaced by a grey ZOOM / GRID box over the BPM display."
]
}
+ ],
+ "phasemeter": [
+ {
+ "version": "1.0.1",
+ "date": "2026-10-21",
+ "changes": [
+ "PHASE METER REVERSE kept over a restart: the player stores the setting in one bit, so REVERSE came back as OFF. REVERSE is now stored as ON and remembered as REVERSE; OFF stays OFF, ON stays ON, also with MY SETTINGS from a USB."
+ ]
+ }
+ ],
+ "ledsaver": [
+ {
+ "version": "1.0.1",
+ "date": "2026-10-21",
+ "changes": [
+ "Its setting in a slot of its own (it shared BEAT JUMP's, so a restart could give one the other's value). LED ANIMATION is back at its default once after the update."
+ ]
+ }
]
}
}
diff --git a/upcoming.html b/upcoming.html
index bdeb6c9..16308b3 100644
--- a/upcoming.html
+++ b/upcoming.html
@@ -6,9 +6,9 @@
Pionaarr Mods Upcoming
-
-
-
+
+
+