1 line
448 KiB
JSON
1 line
448 KiB
JSON
{"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 <main_unpacked.bin> <patched.bin> <mod>[,<mod>...]\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('<HH', entry, 4, width, HEIGHT)\n struct.pack_into('<I', entry, 0x1C, (ORIGINAL_COUNT + i) * DESCRIPTOR)\n struct.pack_into('<I', entry, 0x20, pixels_base + len(pixels) - TABLE)\n table += entry\n pixels += struct.pack('<%dH' % len(data), *data)\n pixels += bytes((-len(pixels)) % 32)\n blob = bytes(table) + bytes(pixels_base - DATA_BASE - table_bytes) + bytes(pixels)\n palette_at = DATA_BASE + len(blob)\n blob += wave_palette() + wave_lut()\n return blob, {'dataBase': '%08x' % DATA_BASE, 'end': '%08x' % (DATA_BASE + len(blob)),\n 'wavePalette': '%08x' % palette_at, 'waveLut': '%08x' % (palette_at + 256)}\n\n\ndef apply_code(app, names):\n \"\"\"arnoldasr's code mod with the features in `names` (UPSTREAM): the\n tested build with the RGB waveform (patches/mods.json) or without it\n (patches/bpm-phase-meter.json), checked against its hash, then the hooks\n of the features not chosen back to their original bytes.\"\"\"\n with_rgb = 'advanced_waveforms' in names\n patch = read_json('patches/mods.json' if with_rgb else 'patches/bpm-phase-meter.json')\n end = int(patch['imageEnd'], 16) - BASE\n out = bytearray(app) + bytes(end - len(app))\n original = {}\n for region in patch['regions']:\n off = int(region['address'], 16) - BASE\n data = bytes.fromhex(region['data'])\n expect = bytes.fromhex(region.get('expect', '00' * len(data)))\n if bytes(out[off:off + len(data)]) != expect:\n raise SystemExit('patch_cdj900nxs: %s: bytes at 0x%s are not the expected original'\n % ('/'.join(names), region['address']))\n out[off:off + len(data)] = data\n if 'expect' in region:\n original[int(region['address'], 16)] = expect\n blob, layout = assets(app)\n meta = patch['assets'] if with_rgb else patch['phaseAssets']\n if not with_rgb:\n blob = blob[:int(meta['end'], 16) - DATA_BASE] # no waveform palette and LUT\n keys = ('dataBase', 'wavePalette', 'waveLut') if with_rgb else ('dataBase',)\n if (sha(blob) != meta['blobSha256'] or any(layout[k] != meta[k] for k in keys)\n or DATA_BASE + len(blob) != int(meta['end'], 16)):\n raise SystemExit('patch_cdj900nxs: the asset blob differs from the tested one')\n out[DATA_BASE - BASE:] = blob\n if sha(out) != patch['applicationSha256']:\n raise SystemExit('patch_cdj900nxs: the code mod differs from the tested build')\n # the features not chosen: their hooks as in the stock application\n hooks = [a for name in UPSTREAM if name not in names and (with_rgb or name != 'advanced_waveforms')\n for a in HOOKS[name]]\n if 'extended_bpm' not in names and 'phasemeter' not in names:\n hooks += SHARED_HOOKS\n for addr in hooks:\n if addr not in original:\n raise SystemExit('patch_cdj900nxs: no hook at 0x%08x in the code mod' % addr)\n out[addr - BASE:addr - BASE + len(original[addr])] = original[addr]\n\n def call(addr, before, after):\n off = addr - BASE\n if out[off:off + 2] != struct.pack('<H', before):\n raise SystemExit('patch_cdj900nxs: startup_bind is not the expected one')\n out[off:off + 2] = struct.pack('<H', after)\n if 'extended_bpm' not in names:\n call(BPM_INSTALL_CALL, 0x410B, 0xE000) # jsr @r1 -> 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('<H', 0x0009)\n pc += 2\n continue\n if kind == 'long':\n out += struct.pack('<I', arg)\n pc += 4\n continue\n if kind == 'word':\n out += struct.pack('<H', arg)\n pc += 2\n continue\n out += struct.pack('<H', arg(pc, self.labels) if final else 0)\n pc += 2\n return bytes(out)\n\n\ndef k(v):\n return lambda pc, labels: v\n\n\ndef pcrel_l(n, label):\n def enc(pc, labels):\n disp = (labels[label] - ((pc & ~3) + 4)) // 4\n assert 0 <= disp < 256 and labels[label] % 4 == 0, label\n return 0xD000 | n << 8 | disp\n return enc\n\n\ndef pcrel_w(n, label):\n def enc(pc, labels):\n disp = (labels[label] - (pc + 4)) // 2\n assert 0 <= disp < 256, label\n return 0x9000 | n << 8 | disp\n return enc\n\n\ndef branch(base, label):\n def enc(pc, labels):\n disp = (labels[label] - (pc + 4)) // 2\n assert -128 <= disp < 128, label\n return base | (disp & 0xFF)\n return enc\n\n\ndef bra(label):\n def enc(pc, labels):\n disp = (labels[label] - (pc + 4)) // 2\n assert -2048 <= disp < 2048, label\n return 0xA000 | (disp & 0xFFF)\n return enc\n\n\nBT, BF = 0x8900, 0x8B00\n\n\ndef routine():\n \"\"\"bj_poll, placed at BJ_BASE: called where the panel decoder called the\n frame fetch, returning what the fetch returned.\"\"\"\n a = Asm(BJ_BASE)\n o = a.op\n\n def call(lit, delay=0x0009):\n o(pcrel_l(1, lit)) # mov.l @(lit),r1\n o(k(0x410B)) # jsr @r1\n o(k(delay))\n\n def fpfix(): # fpscr as the firmware leaves it\n o(k(0x066A)) # sts fpscr,r6\n o(pcrel_l(7, 'L_mask')) # mov.l @(L_mask),r7\n o(k(0x2679)) # and r7,r6\n o(k(0x466A)) # lds r6,fpscr\n\n def record(n): # r4 = grid record n (index in rn)\n o(k(0x6403 | n << 4)) # mov rn,r4\n o(k(0x4408)) # shll2 r4\n o(k(0x4408)) # shll2 r4 x16\n o(k(0x34BC)) # add r11,r4\n o(k(0x7414)) # add #0x14,r4\n\n def own_keys(): # remember r5, and hide the keys it has\n o(pcrel_l(2, 'L_prev')) # mov.l @(L_prev),r2\n o(k(0x6053)) # mov r5,r0\n o(k(0x2200)) # mov.b r0,@r2\n o(k(0x2008)) # tst r0,r0\n o(branch(BT, 'done')) # neither key is down\n o(pcrel_l(1, 'L_keys')) # mov.l @(L_keys),r1\n o(k(0x6010)) # mov.b @r1,r0\n o(k(0xC97F)) # and #0x7f,r0\n o(k(0x2100)) # mov.b r0,@r1\n o(k(0x8411)) # mov.b @(1,r1),r0\n o(k(0xC9FE)) # and #0xfe,r0\n o(k(0x8011)) # mov.b r0,@(1,r1) nothing after us sees them\n\n for n in range(8, 15):\n o(k(0x2F06 | n << 4)) # mov.l rn,@-r15\n o(k(0x4F22)) # sts.l pr,@-r15\n call('L_decoder') # r4-r7 passed on untouched\n o(k(0x6803)) # mov r0,r8 the decoder's result\n call('L_get')\n fpfix()\n # The setting: on the first poll (LAST_SET -1) the journal's, else DEFAULT\n # (hotcuekeys' hk_load2); changed since the last poll, into the journal\n # (hk_save2). Both through HK_API.\n o(k(0x6D03)) # mov r0,r13\n o(pcrel_l(1, 'L_last')) # mov.l @(L_last),r1\n o(k(0x6212)) # mov.l @r1,r2\n o(k(0x4211)) # cmp/pz r2\n o(branch(BT, 'have'))\n o(pcrel_l(4, 'L_bjid')) # mov.l @(L_bjid),r4\n o(k(0xE500 | DEFAULT)) # mov #DEFAULT,r5\n o(pcrel_l(1, 'L_api')) # mov.l @(L_api),r1\n o(k(0x5111)) # mov.l @(4,r1),r1 hk_load2\n o(k(0x410B)) # jsr @r1\n o(k(0x0009))\n fpfix()\n o(k(0xE104)) # mov #4,r1\n o(k(0x3016)) # cmp/hi r1,r0\n o(branch(BF, 'inrange'))\n o(k(0xE000 | DEFAULT)) # mov #DEFAULT,r0\n a.label('inrange')\n o(k(0x6D03)) # mov r0,r13\n o(pcrel_l(1, 'L_setting')) # mov.l @(L_setting),r1\n o(k(0x2102)) # mov.l r0,@r1\n o(pcrel_l(1, 'L_last'))\n o(k(0x2102)) # mov.l r0,@r1\n o(bra('setok'))\n o(k(0x0009))\n a.label('have')\n o(k(0x3200)) # cmp/eq r0,r2\n o(branch(BT, 'setok'))\n o(k(0x2102)) # mov.l r0,@r1 remembered\n o(k(0x6403)) # mov r0,r4\n o(pcrel_l(1, 'L_bjid16')) # mov.l @(L_bjid16),r1\n o(k(0x241B)) # or r1,r4 value | id << 16\n o(pcrel_l(1, 'L_api'))\n o(k(0x6112)) # mov.l @r1,r1 hk_save2\n o(k(0x410B)) # jsr @r1\n o(k(0x0009))\n fpfix()\n a.label('setok')\n o(k(0x60D3)) # mov r13,r0\n o(k(0x2008)) # tst r0,r0 BEAT JUMP OFF\n o(branch(BT, 'done'))\n o(k(0xE104)) # mov #4,r1\n o(k(0x3016)) # cmp/hi r1,r0\n o(branch(BT, 'done'))\n o(k(0x70FF)) # add #-1,r0\n o(k(0xE901)) # mov #1,r9\n o(k(0x490D)) # shld r0,r9 r9 = N beats\n # The decoded TRACK keys, read first: r5 gets 0x08 for forward and 0x04\n # for back (as report byte 0x12 has them), r3 the keys of the last pass\n # that was ours.\n o(pcrel_l(1, 'L_keys')) # mov.l @(L_keys),r1\n o(k(0xE500)) # mov #0,r5\n o(k(0x6010)) # mov.b @r1,r0\n o(k(0xC880)) # tst #0x80,r0 TRACK +\n o(branch(BT, 'k1'))\n o(k(0xE508)) # mov #8,r5\n a.label('k1')\n o(k(0x8411)) # mov.b @(1,r1),r0\n o(k(0xC801)) # tst #0x01,r0 TRACK -\n o(branch(BT, 'k2'))\n o(k(0x7504)) # add #4,r5\n a.label('k2')\n o(pcrel_l(2, 'L_prev')) # mov.l @(L_prev),r2\n o(k(0x6320)) # mov.b @r2,r3\n o(pcrel_l(10, 'L_ctx')) # mov.l @(L_ctx),r10\n o(k(0x51A8)) # mov.l @(0x20,r10),r1 track\n o(k(0x2118)) # tst r1,r1\n o(branch(BT, 'nogrid'))\n o(k(0xE0C4)) # mov #-0x3c,r0\n o(k(0x600C)) # extu.b r0,r0 0xc4\n o(k(0x021E)) # mov.l @(r0,r1),r2\n o(k(0x2228)) # tst r2,r2\n o(branch(BT, 'nogrid'))\n o(k(0xE041)) # mov #0x41,r0\n o(k(0x4008)) # shll2 r0 0x104\n o(k(0x0B2E)) # mov.l @(r0,r2),r11 grid\n o(k(0x7008)) # add #8,r0 0x10c\n o(k(0x0C2E)) # mov.l @(r0,r2),r12 adjustment\n o(k(0x2BB8)) # tst r11,r11\n o(branch(BT, 'nogrid'))\n o(k(0x5DB1)) # mov.l @(4,r11),r13 beat count\n o(k(0xE102)) # mov #2,r1\n o(k(0x3D12)) # cmp/hs r1,r13\n o(branch(BF, 'nogrid'))\n o(k(0xE003)) # mov #3,r0\n o(k(0x4018)) # shll8 r0\n o(k(0x70CC)) # add #-52,r0 0x2cc\n o(k(0x0EAE)) # mov.l @(r0,r10),r14 position, ms\n # While hotcuekeys' memory CALL of a jump at cue standby is under way the\n # position reads 0: reckon from that CALL's target instead (BJ_POSV).\n o(pcrel_l(1, 'L_posv')) # mov.l @(L_posv),r1\n o(k(0x6012)) # mov.l @r1,r0\n o(k(0x2008)) # tst r0,r0\n o(branch(BT, 'posok'))\n o(k(0x6E03)) # mov r0,r14\n a.label('posok')\n # Presses counted while hotcuekeys' CALL was under way (no grid then, see\n # 'nogrid'): jumped now, all at once, once that CALL is done and no key\n # is down.\n o(k(0x6053)) # mov r5,r0\n o(k(0x2008)) # tst r0,r0\n o(branch(BF, 'nopend'))\n o(pcrel_l(2, 'L_pend'))\n o(k(0x6022)) # mov.l @r2,r0\n o(k(0x2008)) # tst r0,r0\n o(branch(BT, 'nopend'))\n o(pcrel_l(1, 'L_posv'))\n o(k(0x6112)) # mov.l @r1,r1\n o(k(0x2118)) # tst r1,r1\n o(branch(BF, 'nopend')) # still under way\n o(k(0xE100)) # mov #0,r1\n o(k(0x2212)) # mov.l r1,@r2 taken\n o(pcrel_l(2, 'L_prev'))\n o(k(0x2210)) # mov.b r1,@r2 no keys down\n o(k(0x0907)) # mul.l r0,r9\n o(bra('fwd'))\n o(k(0x091A)) # (delay) sts macl,r9 N * presses\n a.label('nopend')\n # A grid is there: the keys are ours.\n own_keys()\n o(k(0x6337)) # not r3,r3\n o(k(0x2359)) # and r5,r3\n o(k(0x6033)) # mov r3,r0\n o(k(0xC90C)) # and #0x0c,r0 keys that went down now\n o(k(0x2008)) # tst r0,r0\n o(branch(BT, 'done')) # held, not pressed\n o(k(0xC808)) # tst #0x08,r0 forward?\n o(branch(BF, 'fwd'))\n o(k(0x699B)) # neg r9,r9\n a.label('fwd')\n o(k(0x64B3)) # mov r11,r4\n o(k(0x65C3)) # mov r12,r5\n call('L_lookup', 0x66E3) # (delay) mov r14,r6\n fpfix()\n o(k(0x4015)) # cmp/pl r0\n o(branch(BT, 'found'))\n o(bra('found')) # before the first beat (a track just\n o(k(0xE001)) # (delay) mov #1,r0 loaded, at its CUE): from beat 0\n a.label('done') # mid-routine: every bt/bf reaches it\n o(k(0x6083)) # mov r8,r0 the fetch's result\n o(k(0x4F26)) # lds.l @r15+,pr\n for n in range(14, 8, -1):\n o(k(0x60F6 | n << 8)) # mov.l @r15+,rn\n o(k(0x000B)) # rts\n o(k(0x68F6)) # mov.l @r15+,r8 (delay)\n # No grid this pass (the player is busy with our CALL, say): a press we\n # took is still ours until it is let go, so its keys stay hidden. While\n # hotcuekeys' memory CALL of a jump is under way (BJ_POSV set) every press\n # is ours (or the track search would start the track again) and is\n # counted in BJ_PEND, +1 forward, -1 back.\n a.label('nogrid')\n o(pcrel_l(1, 'L_posv'))\n o(k(0x6012)) # mov.l @r1,r0\n o(k(0x2008)) # tst r0,r0\n o(branch(BT, 'ngold'))\n own_keys()\n o(k(0x6337)) # not r3,r3\n o(k(0x2359)) # and r5,r3\n o(k(0x6033)) # mov r3,r0\n o(k(0xC90C)) # and #0x0c,r0\n o(k(0x2008)) # tst r0,r0\n o(branch(BT, 'done')) # held, not pressed\n o(k(0xE101)) # mov #1,r1\n o(k(0xC808)) # tst #0x08,r0\n o(branch(BF, 'ngfwd'))\n o(k(0xE1FF)) # mov #-1,r1\n a.label('ngfwd')\n o(pcrel_l(2, 'L_pend'))\n o(k(0x6022)) # mov.l @r2,r0\n o(k(0x301C)) # add r1,r0\n o(k(0x2202)) # mov.l r0,@r2\n o(bra('done'))\n o(k(0x0009))\n a.label('ngold')\n o(k(0x2338)) # tst r3,r3\n o(branch(BT, 'done')) # not ours: leave it alone\n own_keys()\n o(bra('done'))\n o(k(0x0009))\n a.label('found')\n o(k(0x70FF)) # add #-1,r0\n o(k(0x6103)) # mov r0,r1 i\n o(k(0x6213)) # mov r1,r2\n o(k(0x329C)) # add r9,r2 j = i +/- N\n o(k(0x4211)) # cmp/pz r2\n o(branch(BF, 'done'))\n o(k(0x32D2)) # cmp/hs r13,r2\n o(branch(BT, 'done')) # past the last beat\n o(k(0x6923)) # mov r2,r9 j\n record(1)\n call('L_time', 0x65C3) # (delay) mov r12,r5\n fpfix()\n o(k(0x6A03)) # mov r0,r10 t[i]\n record(9)\n call('L_time', 0x65C3)\n fpfix()\n o(k(0x30A8)) # sub r10,r0 t[j] - t[i]\n o(k(0x6903)) # mov r0,r9 (kept: the jump in ms)\n o(k(0x30EC)) # add r14,r0 target, ms\n o(k(0x4011)) # cmp/pz r0\n o(branch(BF, 'done'))\n o(k(0x6103)) # mov r0,r1\n o(k(0x310C)) # add r0,r1\n o(k(0x310C)) # add r0,r1 q = 3 * ms\n o(k(0x710A)) # add #10,r1 rounded\n o(pcrel_l(2, 'L_div20'))\n o(k(0x3125)) # dmulu.l r2,r1\n o(k(0x030A)) # sts mach,r3\n o(k(0x4309)) # shlr2 r3\n o(k(0x4309)) # shlr2 r3 units = (q + 10) / 20 (1/150 s)\n # hotcuekeys' engine makes the jump (hk_engine's he_bj): the target and\n # the tick of the key. One still waiting: this press is ignored (its key\n # stays ours), as is one the DSP cannot jump yet (hotcuekeys DJ_RETRY 0).\n o(pcrel_l(2, 'L_req')) # mov.l @(L_req),r2\n o(k(0x6022)) # mov.l @r2,r0\n o(k(0x4011)) # cmp/pz r0\n o(branch(BT, 'fresh'))\n o(bra('done')) # one is waiting: ignored\n o(k(0x0009))\n a.label('fresh')\n o(pcrel_l(1, 'L_tick'))\n o(k(0x6112)) # mov.l @r1,r1 now\n o(k(0x1211)) # mov.l r1,@(4,r2)\n o(pcrel_l(1, 'L_delta'))\n o(k(0x2192)) # mov.l r9,@r1 the jump in ms, for the DSP jump\n o(pcrel_l(1, 'L_bit31'))\n o(k(0x231B)) # or r1,r3\n o(k(0x2232)) # mov.l r3,@r2\n o(bra('done'))\n o(k(0x0009))\n a.long('L_mask', FPSCR_MASK)\n a.long('L_decoder', DECODER)\n a.long('L_get', GET_SETTING)\n a.long('L_ctx', PLAYER_CTX)\n a.long('L_keys', KEYS_TRACK)\n a.long('L_prev', PREV_BITS)\n a.long('L_lookup', GRID_LOOKUP)\n a.long('L_time', GRID_TIME)\n a.long('L_div20', 0xCCCCCCCD) # q * this >> 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('<I', p) for p in VALUE_STRINGS)\n put(DECODER_LIT, struct.pack('<I', DECODER), struct.pack('<I', BJ_BASE))\n put(SAVER_CONSUMER_LIT, struct.pack('<I', GET_SETTING), struct.pack('<I', RETURN_ONE))\n put(RETURN_ONE, bytes.fromhex('0b0001e0'), bytes.fromhex('0b0001e0')) # rts; mov #1,r0 is there\n put(SAVER_ITEM + 4, struct.pack('<II', 0x11106478, 2),\n struct.pack('<II', VALUE_LIST, len(VALUE_STRINGS)))\n found = 0\n at = stock.find(LABEL_OLD)\n while at >= 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.4' # 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('<I', before) or out[off:off + 4] != struct.pack('<I', before):\n raise SystemExit('patch_cdj900nxs: credit: bytes at 0x%08x are not the expected original' % addr)\n out[off:off + 4] = struct.pack('<I', after)\n\n if stock[DATA_VER - 0xA0000000 - BASE:DATA_VER - 0xA0000000 - BASE + 5] != b'Ver \\0':\n raise SystemExit('patch_cdj900nxs: credit: the splash text is not the expected one')\n data = area()\n out[CREDIT - BASE:CREDIT - BASE + len(data)] = data\n put(DATA_WORD, DATA_VER, CREDIT | 0xA0000000) # uncached, as \"Ver \" was\n put(GETTER_LIT, GETTER, STUB)\n off = BOX - BASE\n if (struct.unpack_from('<4H', stock, off) != BOX_STOCK\n or struct.unpack_from('<4H', out, off) != BOX_STOCK):\n raise SystemExit('patch_cdj900nxs: credit: the splash text box is not the expected one')\n struct.pack_into('<4H', out, off, *BOX_NEW)\n return bytes(out)\n","mods/cdj900nxs/cuecolours.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"CUE COLOURS for the CDJ-900NXS 1.31: memory cues drawn in their rekordbox\ncolour, and the hot cues A-H (which the 900NXS does not show at all) as\ncoloured markers with their letter, on the overview and over the detail\nwaveform. Display only: the player still has no hot cue buttons. Our own mod,\nnot arnoldasr's; mods/patch_cdj900nxs.py applies it as `cuecolours`.\n\nFound by tracing the firmware in the emulator:\n\n * Loading a track, DbReqUsbSdCue (0x08978D00) asks the player's own\n database server for the cue list with dbcl_GetUsbSdCueInfo (0x08976316,\n request 0x2104, through its literal at 0x08978F50). The reply holds\n 36-byte entries (+2 hot cue number, 0 for a memory cue; +12 time in\n 1/150 s; +16..+35 zero) and carries no colour: the server answers 0x2104\n from PCOB. The memory cues are copied whole into the UI's memory list\n 0x0B7BE8D4 (0x24 bytes each, +0 = -1 past the last).\n * The same server also answers the CDJ-2000NXS2's extended request 0x2B04\n (reply 0x4E02) from the EXT file's PCO2: entries of variable length,\n +0 length, +4 hot cue number, +12 time in ms, +0x3A a memory cue's\n colour id (1-8, 0 none), +0x48 the comment's length in bytes and from\n +0x4A the comment (UTF-16, rekordbox's cue name); then 4 bytes and a hot\n cue's colour code, R, G, B. (1.0.1: the colour is found after the\n comment; 1.0.0 took it 6 bytes before the entry's end, right only when\n the entry had no comment.) Nothing in the 900NXS sends it.\n * cue_get replaces dbcl_GetUsbSdCueInfo: it runs the stock request, then\n the extended one on the same connection (the sequence of arnoldasr's\n wave_get_tag: check 0x089736F2, alloc 0x089A791E, init 0x08973C50, send\n 0x089738F0, wait 0x08974198, free 0x08A5EBC2), and writes each cue's\n colour as RGB565 into its entry at +16, with 1 at +18 when it is set. The\n entries carry it into the memory list. Hot cues go into HOT_TABLE (time,\n RGB565, number), green when rekordbox gave them no colour.\n * The waveform preparer (0x08BC670A) copies the marker records to\n 0x0B801258 with memcpy (literal 0x08BC6BF8): 16 bytes, +0 type (0 memory\n cue, 1 cue point, 0xFF none), +8 time in 1/150 s, 101 of them. From them it\n sets the detail waveform's column flags (bit 0: memory cue) and the\n overview's 12-byte records at 0x0B800D9C (+0 flags, +4 x). mk_copy (the\n literal now) adds the hot cues to the copy as memory-cue records, so both\n views mark them where they draw memory cues.\n * The overview (0x08BCB982) draws flag bit 0 as resource 0xC5, the red 16x5\n triangle, with draw_bitmap 0x08BC5B3C (literal 0x08BCC1E8); record i of\n 0x0B800D9C belongs to record i of 0x0B801258. ov_draw (the literal now)\n draws 0xC5 with its pixel offset (+0x20 of its 44-byte record in the\n table at *(0x11104B0C)) pointed at a marker in the cue's colour for the\n one call, then puts it back.\n * The detail waveform (0x08BCF476) copies the triangle's pixels itself\n after resolving 0xC5 with 0x08B2E2E4 (literal 0x08BCF770), for the column\n in r9 (1/150 s). det_res (the literal now) hands it a copy of the record\n whose pixel pointer (+12) is the coloured marker.\n * colour_for finds a time's colour in the memory list, then in HOT_TABLE:\n a memory cue gets a triangle, a hot cue a 9x5 box with its letter.\n The markers are written through the uncached P2 alias, as the overview's\n blitter reads memory, not the cache.\n\nA memory cue without a colour, or one stored on the player, stays red. The\nmemory colours are the player's own track-colour dots (see MEMORY_RGB565).\"\"\"\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' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nCC_BASE = 0x08FCFA00 # after beatjump's 0x08FCF400..0x08FCF814\nCC_END = 0x08FD1800 # code, then the data below\nCC_DATA2 = 0x08FD3800 # after hotcuekeys' area: the detail waveform's markers\nDET_BUF = CC_DATA2 # 19 x 16 x 8 x 2: memory list 0-9, hot cues 10-17, plain 18\nCC_END2 = DET_BUF + 19 * 256\n# the detail renderer (0x08BCF476) copies its marker 9 wide, 5 lines from line 4;\n# now 11 wide, 8 lines from line 7 (its buffer has 7 lines above the line it\n# starts on, so 8 is the most it can take)\nDETAIL_PATCHES = [(0x08BCF5EE, 0xED04, 0xED07), (0x08BCF5F8, 0xE105, 0xE108),\n (0x08BCF616, 0xED09, 0xED0B)]\n\nCUEINFO_LIT = 0x08978F50 # DbReqUsbSdCue's literal for dbcl_GetUsbSdCueInfo\nCUEINFO = 0x08976316\nDRAW_LIT = 0x08BCC1E8 # the overview's literal for draw_bitmap\nDRAW = 0x08BC5B3C\nRESOLVE_LIT = 0x08BCF770 # the detail waveform's literal for the resource resolver\nRESOLVE = 0x08B2E2E4\nRES_TABLE_PTR = 0x11104B0C # -> 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('<I', int(bits, 2) << 17) for bits in LETTERS)\n palette = struct.pack('<H', TRI_RED) + b''.join(struct.pack('<H', c) for c in MEMORY_RGB565)\n font5 = bytes(int(row, 2) << 3 for letter in LETTERS5 for row in letter)\n # after the code: FONT, PALETTE, FONT5; their addresses follow from the code's length\n syms['PALETTE'] = syms['FONT5'] = 0\n code, labels = sh4asm.assemble(SOURCE, CC_BASE, syms)\n syms['PALETTE'] = labels['FONT'] + len(font)\n syms['FONT5'] = syms['PALETTE'] + len(palette)\n code, labels = sh4asm.assemble(SOURCE, CC_BASE, syms)\n labels['PALETTE'], labels['FONT5'] = syms['PALETTE'], syms['FONT5']\n return code + font + palette + font5, labels\n\n\ndef patch(app, stock):\n \"\"\"The application with CUE COLOURS added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n code, labels = routine()\n assert CC_BASE + len(code) <= DATA, 'cuecolours: code runs into its data'\n out = bytearray(app)\n end = CC_END - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[CC_BASE - BASE:end]):\n raise SystemExit('patch_cdj900nxs: cuecolours: its area 0x%08x.. is not free' % CC_BASE)\n if stock[TRI_PIXELS - BASE:TRI_PIXELS - BASE + 2] != struct.pack('<H', TRI_RED):\n raise SystemExit('patch_cdj900nxs: cuecolours: the memory-cue triangle is not where expected')\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: cuecolours: bytes at 0x%08x are not the expected original'\n % addr)\n out[off:off + len(after)] = after\n\n if len(out) < CC_END2 - BASE:\n out += bytes(CC_END2 - BASE - len(out))\n if any(out[CC_DATA2 - BASE:CC_END2 - BASE]):\n raise SystemExit('patch_cdj900nxs: cuecolours: its area 0x%08x.. is not free' % CC_DATA2)\n out[CC_BASE - BASE:CC_BASE - BASE + len(code)] = code\n for n in range(8): # no hot cues until a track's are read\n out[HOT_TABLE - BASE + 8 * n:HOT_TABLE - BASE + 8 * n + 4] = b'\\xff' * 4\n for addr, old, new in DETAIL_PATCHES:\n put(addr, struct.pack('<H', old), struct.pack('<H', new))\n for lit, old, new in ((CUEINFO_LIT, CUEINFO, 'cue_get'), (DRAW_LIT, DRAW, 'ov_draw'),\n (RESOLVE_LIT, RESOLVE, 'det_res'), (MKCOPY_LIT, MEMCPY, 'mk_copy')):\n put(lit, struct.pack('<I', old), struct.pack('<I', labels[new]))\n return bytes(out)\n","mods/cdj900nxs/cuenames.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"CUE NAMES for the CDJ-900NXS 1.31: the comment rekordbox keeps for a memory\ncue or hot cue (\"Drop\", \"Vocal in\") shown in the title bar, right of the\ntitle, in the cue's colour: always the next cue ahead that has one, a hot\ncue with its letter (\"A DROP\"). Our own mod; needs cuecolours, whose cue\nreader brings the comments in; mods/patch_cdj900nxs.py applies it as\n`cuenames`, after keynotation.\n\n * cuecolours' cue_get asks the player's own DB server for the extended cue\n list (request 0x2B04, reply 0x4E02). Each entry (little-endian) has +0\n its length, +4 the hot cue number (0 a memory cue), +12 the time in ms,\n +0x3A a memory cue's colour id, +0x48 the comment's length in bytes\n (UTF-16 with the closing NUL, 0 none) and from +0x4A the comment; after\n it 4 bytes and a hot cue's colour code, R, G, B. The server reads it from\n the PCO2 entries of the track's ANLZ .EXT file, where rekordbox writes\n the comment at +0x28 / +0x2C.\n * cue_get frees the reply with free (its literal K_free). cn_scan takes\n that literal: it copies every named cue's time, hot cue number, colour\n and comment into CN_TABLE, then frees the reply as before. The size of\n the reply is read from cue_get's frame (+16 there). The load counter\n cuecolours keeps in HOT_TABLE+7 is stored with the table, so a load whose\n extended request failed shows nothing instead of the track before.\n * Text widgets are drawn by the method in the class table entry DRAW_LIT\n (0x08B303BC, or keynotation's kn_draw in front of it): r4 the renderer,\n r5 the widget, +4 its id, +24 x and y, +28 width and height, +40 the\n colour 0xAABBGGRR, +52 the text. The title bar's title is widget id\n TITLE_ID, drawn about five times a second. cn_draw takes the entry: for\n the title, while BROWSE is closed and a named cue lies ahead (or less\n than a second behind), it draws the title narrowed to TITLE_W, then a\n copy of the widget at LABEL_X with the cue's name and colour. Not while\n the title is dimmed (not white: a popup such as MENU's over the screen).\n * A hot cue's name is shown only while HOT_TABLE (which hotcuekeys keeps\n up to date when a hot cue is set or deleted on the deck) still has that\n hot cue at the same time. A memory cue deleted on the deck keeps its\n name until the track is loaded again.\n\"\"\"\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 = '0.1.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nCN_BASE = 0x08FDDC00 # after wavefix's 0x08FDD800..0x08FDDC00\nCN_END = 0x08FDF000\nCN_COUNT = CN_BASE # word: named cues in CN_TABLE\nCN_LOAD = CN_BASE + 4 # byte: cuecolours' load counter when CN_TABLE was filled\nCN_LABEL = CN_BASE + 0x10 # 64 bytes: the text drawn (\"A \" + name)\nCN_CLONE = CN_BASE + 0x50 # 96 bytes: the copy of the title widget\nCN_TABLE = CN_BASE + 0xC0 # CN_MAX x 64: +0 ms, +4 time 1/150 s, +8 hot cue number, +12 colour, +16 name\nCN_MAX = 18\nCN_NAME = 23 # characters kept\nCN_CODE = CN_TABLE + CN_MAX * 64\n\nDRAW_LIT = 0x080B27F4 # the text widget's draw method in its class table\nFREE = 0x08A5EBC2\nHOT_TABLE = cuecolours.HOT_TABLE\nPOS_MS = 0x099448A8 # the player's position in ms (ctx +0x2CC)\nBROWSE_DEPTH = 0x0B7CC618 # 0: BROWSE closed\nTITLE_ID = 0x00210009\nTITLE_W = 330 # the title's width while a name is shown\nLABEL_X = 372\nLABEL_W = 186\nBEHIND_MS = 1000 # a cue just passed still shows this long\n\n# rekordbox's memory cue colours 1-8 (pink, red, orange, yellow, green, aqua, blue, purple) as 0xAABBGGRR\nMEMORY_ABGR = [0xFFFFFFFF, 0xFFB45AFF, 0xFF3030FF, 0xFF1E8CFF, 0xFF32E6FF,\n 0xFF50DC3C, 0xFFE6DC32, 0xFFFF783C, 0xFFFF50A0]\nNO_COLOUR = 0xFF3030FF # a memory cue without a colour: the player's red\n\nSOURCE = r'''\n; ---- cn_scan: free(r4 the extended cue reply), from cue_get; its size at\n; cue_get's frame +16 (here +40, after our 6 saved registers)\ncn_scan:\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 r4,r8 ; the reply, for free\n mov.l S_count,r1\n mov #0,r0\n mov.l r0,@r1\n mov.l S_hot,r2 ; this load's counter\n mov.b @(7,r2),r0\n mov.l S_load,r2\n mov.b r0,@r2\n tst r4,r4\n bt sc_out\n mov.l @(40,r15),r9 ; the size\n mov.l S_max,r1\n cmp/hi r1,r9\n bt sc_out\n add r4,r9 ; the end\n mov r4,r10 ; an entry\n mov #0,r12 ; named cues\nsc_entry:\n mov r10,r1\n add #16,r1\n cmp/hi r9,r1\n bt sc_out\n mov.l @r10,r11 ; its length\n mov #76,r1\n cmp/hs r1,r11\n bf sc_out ; too short for a comment: not this format\n mov r10,r1\n add r11,r1\n cmp/hi r9,r1\n bt sc_out\n mov #72,r0\n mov.w @(r0,r10),r3 ; the comment's length in bytes\n extu.w r3,r3\n mov #4,r1\n cmp/hs r1,r3\n bf sc_next ; none (or only the NUL)\n mov r3,r1\n add #74,r1\n cmp/hi r11,r1\n bt sc_next ; runs past the entry\n mov #CN_MAX,r1\n cmp/hs r1,r12\n bt sc_out\n mov r12,r7 ; our entry: CN_TABLE + 64 n\n shll2 r7\n shll2 r7\n shll2 r7\n mov.l S_table,r1\n add r1,r7\n mov.l @(12,r10),r0 ; ms\n mov.l r0,@r7\n mov r0,r1 ; 1/150 s: ms * 3 / 20\n add r0,r1\n add r0,r1\n mov.l S_div20,r2\n dmulu.l r2,r1\n sts mach,r1\n shlr2 r1\n shlr2 r1\n mov.l r1,@(4,r7)\n mov.b @(4,r10),r0\n extu.b r0,r6 ; hot cue number\n mov r6,r0\n mov.l r0,@(8,r7)\n tst r6,r6\n bf sc_hot\n mov #58,r0 ; a memory cue's colour id\n mov.b @(r0,r10),r0\n extu.b r0,r0\n mov.l S_nocol,r1\n tst r0,r0\n bt sc_col\n mov #8,r1\n cmp/hi r1,r0\n bt sc_nocol\n shll2 r0\n mov.l S_memcol,r1\n mov.l @(r0,r1),r1\n bra sc_col\n nop\nsc_nocol:\n mov.l S_nocol,r1\n bra sc_col\n nop\nsc_hot:\n mov r10,r2 ; colour code, R, G, B after the comment and 4 bytes\n add r3,r2\n add #78,r2\n mov.b @r2,r0\n tst r0,r0\n bt sc_green\n mov.b @(3,r2),r0 ; B\n extu.b r0,r1\n shll8 r1\n mov.b @(2,r2),r0 ; G\n extu.b r0,r0\n or r0,r1\n shll8 r1\n mov.b @(1,r2),r0 ; R\n extu.b r0,r0\n or r0,r1\n mov.l S_alpha,r0\n bra sc_col\n or r0,r1\nsc_green:\n mov.l S_green,r1 ; rekordbox's default hot cue colour\nsc_col:\n mov.l r1,@(12,r7)\n mov r10,r1 ; the name, UTF-16, up to CN_NAME characters\n add #74,r1\n mov r7,r2\n add #16,r2\n shlr r3 ; characters with the NUL\n mov #CN_NAME,r4\n cmp/hi r4,r3\n bf sc_copy\n mov r4,r3\nsc_copy:\n mov.b @r1+,r0 ; byte by byte: +0x4A is not aligned\n mov.b @r1+,r4\n extu.b r0,r0\n shll8 r4\n or r4,r0\n mov.w r0,@r2\n dt r3\n bf/s sc_copy\n add #2,r2\n mov #0,r0\n mov.w r0,@r2\n add #1,r12\n mov.l S_count,r1\n mov.l r12,@r1\nsc_next:\n bra sc_entry\n add r11,r10\nsc_out:\n mov r8,r4\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 mov.l @r15+,r8\n mov.l S_free,r0\n jmp @r0\n nop\n\n .align 4\nS_alpha: .long 0xFF000000\nS_count: .long CN_COUNT\nS_div20: .long 0xCCCCCCCD\nS_free: .long FREE\nS_green: .long 0xFF50DC3C\nS_hot: .long HOT_TABLE\nS_load: .long CN_LOAD\nS_max: .long 0x00010000\nS_memcol: .long MEMORY_TAB\nS_nocol: .long NO_COLOUR\nS_table: .long CN_TABLE\n\n; ---- cn_pick -> 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('<I', out, a - BASE)[0]\n _, cc_labels = cuecolours.routine()\n k_free = cc_labels.get('K_free', cc_labels.get('k_free'))\n if k_free is None or word(k_free) != FREE:\n raise SystemExit('patch_cdj900nxs: cuenames: cuecolours\\' free literal is not where expected')\n next_draw = word(DRAW_LIT)\n code, labels = routine(next_draw)\n assert CN_CODE + len(code) <= CN_END, 'cuenames: past its area'\n if len(out) < CN_END - BASE:\n out += bytes(CN_END - BASE - len(out))\n if any(out[CN_BASE - BASE:CN_END - BASE]):\n raise SystemExit('patch_cdj900nxs: cuenames: its area 0x%08x.. is not free' % CN_BASE)\n out[CN_CODE - BASE:CN_CODE - BASE + len(code)] = code\n out[CN_LOAD - BASE] = 0xFF # no table yet\n struct.pack_into('<I', out, k_free - BASE, labels.get('cn_scan', labels.get('CN_SCAN')))\n struct.pack_into('<I', out, DRAW_LIT - BASE, labels.get('cn_draw', labels.get('CN_DRAW')))\n return bytes(out)\n","mods/cdj900nxs/dspjump.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"DSP jump: a patch of the CDJ-900NXS 1.31 audio DSP (TI C6747) that moves\nthe playing head by a number of 1/75 s pool slots at once, inside the\nalready decoded audio: an instant, gapless jump (beat jump). Our own; the\nfindings are in re/dsp900nxs/NOTES.md.\n\nThe DSP keeps decoded audio in a pool of 6 692 slots of 1/75 s (588 stereo\nsamples) at 0xC0091260 (4 804 bytes each), chained by a link list at\n0xC1F38B90 (u32 per slot: prev | next << 16). The playing head is\nheads[DP+969] (DP = 0x11806900; heads at 0x1182E0E0, 30 bytes each: +2 the\ncurrent slot, +6 slots behind it, +8 slots ahead of it, all decoded). While\nplaying, some 15-20 s behind and up to ~20 s ahead are decoded. The reader\n(in the audio interrupt, INT8) fetches half slots from the head's current\nslot into a ring of four L2 buffers, so a moved head is heard within three\nhalf slots (<= 20 ms), seamlessly.\n\nThe jump runs in the audio interrupt itself (so nothing else touches the\nhead meanwhile): the interrupt's trampoline 0x11803120 (image A, L2) called\n0xC00487A4 each time; it now calls dj_poll, which looks at the mailbox and\ngoes on to 0xC00487A4. Code space: the interrupt vector table's packets of\nvectors 2, 3, 5-7 and 9-14, never enabled (IER gets bits 4, 8 and 15 only;\nb_init copies the table to L2 0x11800000 at start-up).\n\nMailbox (L2 0x1183E000, unused by the firmware), written by MAIN over the\nhost port:\n +0 command: 1 = jump (the DSP sets it back to 0 when done)\n +4 n: signed slots (positive: forward)\n +8 result: 1 done, 2 refused (not that much decoded audio that way)\n +C count of commands done\n +10 the head's current slot before, +14 after, +18 behind, +1C ahead\nA forward jump leaves at least MARGIN slots (10 s) decoded ahead, so the\nreader never runs into the decoder (the player would start its emergency\nloop); a backward one leaves at least one slot behind; n = 0 is done at once.\nThe head's point fields (+0xE slot, +0x10 behind, +0x12 ahead; set when the\ndeck pauses) move with it: standing, the decoder fills on from the point\n(and frees slots far behind), so paused jumps forward do not run dry after\nthe ~30 s it decoded when the deck paused.\n\"\"\"\nimport struct\nimport c6asm\n\nBASE = 0x08000000\nB_IMG = 0x0800E770 # image B (DSP SDRAM 0xC0000000) in the application\nA_IMG = 0x08001010 # image A (DSP L2 0x11801DA0)\nA_AT = 0x11801DA0\nTRAMP_MVK = 0x11803124 # mvk .S2 0x87A4,b10\nTRAMP_MVKH = 0x11803128 # mvkh .S2 0xC004,b10 (-> 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('<I', stock, off)[0]\n if w != want or struct.unpack_from('<I', out, off)[0] != want:\n raise SystemExit('patch_cdj900nxs: dspjump: the trampoline at 0x%08x is not the expected one' % at)\n struct.pack_into('<I', out, off, set_cst16(w, value))\n return bytes(out)\n\n\nif __name__ == '__main__':\n for at, w in code():\n print('%08x %08x' % (at, w))\n","mods/cdj900nxs/endwarn.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"END WARNING for the CDJ-900NXS 1.31: in a track's last 30 seconds the\nwhite playing-address bar under the overview, which the player makes blink\nthere anyway, blinks red. Our own mod; mods/patch_cdj900nxs.py applies it as\n`endwarn`.\n\n * The bar is resource RES_BAR (504 x 5, RGB565: white 0xFFFF and grey\n 0xCE79 in alternate columns, magenta 0xF81F transparent). The function\n 0x08BCA34E draws it onto its surface (0x111067A0) with draw_bitmap\n (0x08BC5B3C, through the literal BAR_LIT, read only there), then paints\n the played part transparent over it; the blinking is the firmware's.\n * ew_draw takes that literal: while the track has less than WARN_MS left\n (its length in 1/150 s at TRACK_LEN, the position in ms at POS_MS), the\n resource's record (44 bytes in the table at *(0x11104B0C); +0x20 the\n pixels' offset from 0x08100000) points at RED_BAR, a red copy built\n into the image, for the one call; then it is put back.\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 = '0.1.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nEW_BASE = 0x08FDF000 # after cuenames' 0x08FDDC00..0x08FDF000\nEW_END = 0x08FE0800\nEW_CODE = EW_BASE\nRED_BAR = EW_BASE + 0x200 # the red copy, 32-byte aligned (lines of 1008 bytes)\n\nBAR_LIT = 0x08BCA58C # 0x08BCA34E's literal for draw_bitmap\nDRAW = 0x08BC5B3C\nRES_BAR = 0x165\nRES_BASE = 0x08100000 # the stock resource table, and the pixels' base\nRES_TABLE_PTR = 0x11104B0C\nTRACK_LEN = 0x0BAB8C30 # the loaded track's length, 1/150 s\nPOS_MS = 0x099448A8 # the player's position in ms\nWARN_MS = 30000\n\nRED = {0xFFFF: 0xF800, 0xCE79: 0xB000} # white -> 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('<I', buf, a - BASE)[0]\n if word(stock, BAR_LIT) != DRAW or word(out, BAR_LIT) != DRAW:\n raise SystemExit('patch_cdj900nxs: endwarn: the literal at 0x%08x is not the expected one' % BAR_LIT)\n rec = RES_BASE + RES_BAR * 44\n w, h = struct.unpack_from('<HH', stock, rec + 4 - BASE)\n pix = RES_BASE + word(stock, rec + 0x20)\n if (w, h) != (504, 5):\n raise SystemExit('patch_cdj900nxs: endwarn: the bar resource is not as expected')\n src = struct.unpack_from('<%dH' % (w * h), stock, pix - BASE)\n red = struct.pack('<%dH' % (w * h), *(RED.get(p, p) for p in src))\n code, labels = routine()\n assert EW_CODE + len(code) <= RED_BAR and RED_BAR + len(red) <= EW_END, 'endwarn: past its area'\n if len(out) < EW_END - BASE:\n out += bytes(EW_END - BASE - len(out))\n if any(out[EW_BASE - BASE:EW_END - BASE]):\n raise SystemExit('patch_cdj900nxs: endwarn: its area 0x%08x.. is not free' % EW_BASE)\n out[EW_CODE - BASE:EW_CODE - BASE + len(code)] = code\n out[RED_BAR - BASE:RED_BAR - BASE + len(red)] = red\n struct.pack_into('<I', out, BAR_LIT - BASE, labels.get('ew_draw', labels.get('EW_DRAW')))\n return bytes(out)\n","mods/cdj900nxs/hotcuekeys.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"HOT CUE KEYS for the CDJ-900NXS 1.31: hot cues A-C on the deck's own\nkeys. Our own mod; mods/patch_cdj900nxs.py applies it as\n`hotcuekeys`.\n\n * UTILITY gets an 18th item, HOT CUE KEYS (OFF / ON); nothing is\n replaced. The choice is kept in the firmware's settings journal in flash.\n * With it ON, QUANTIZE pressed briefly switches the hot cue key mode on and\n off, and a HOT KEYS box beside the QUANTIZE label shows the mode (red: on,\n grey: off). QUANTIZE held for LONG_TICKS switches quantize as before.\n * With it OFF, QUANTIZE is QUANTIZE, and no box is shown.\n * In the mode the top three BEAT DIVIDE keys (3/4, 1/3, 1/2) are hot cues\n A-C (HOT_KEYS); 1/4 and 1/8 do nothing and stay dark (the player has\n three hot cue buffers, and A-C keep them). A press jumps to the hot cue\n and plays, from pause too, at once once the engine below has its\n buffer ready. Their LEDs are\n off without a hot cue, dim with one, and blink bright twice when\n pressed. Held GATE_TICKS or longer, the key is a gate: let go, the deck\n goes back to the hot cue and stands there. A key without a hot cue held\n LONG_TICKS while paused gets one at the position; DELETE held, a key\n pressed loses its hot cue. Both are kept on the USB (rekordbox's\n ANLZ0000.DAT and .EXT of the track, as MEMORY and DELETE keep memory\n cues): see the DB bullet below. DELETE let go without a key pressed is passed on as before.\n The hot cues are cuecolours' HOT_TABLE (read from the USB's rekordbox\n data); without cuecolours the keys have none.\n * State at fixed addresses: HK_SETTING (byte, 1 = UTILITY item ON) and\n HK_MODE (byte, 1 = hot cue key mode on); hot cue keys are live when both\n are 1 (hk_active() in Python terms: *HK_SETTING && *HK_MODE).\n\nFound by tracing the firmware in the emulator:\n\n * UTILITY's items are 52-byte records, copied at start-up with the rest of\n .data from the image (0x08C6C8DC) to RAM (0x11106D1C, image + 0x0849A440);\n the menu code finds them through four literals (0x08BDA97C, 0x08BDACA8,\n 0x08BDAFD4, 0x08BDB06C) and their count in the word after them (image\n 0x08C6CC50, RAM 0x11107090, 17). An item: +0 label (UTF-16), +4 value\n strings, +8 their count, +12 current value, +16 value being chosen (-1\n none), +20 selectable, +24..+48 functions; +28 shows label and value\n (SCREEN SAVER's, 0x08BD9C7C, reads its getter only while +16 is -1), +40\n takes a chosen value (0x08BDA56C for SCREEN SAVER). The new table lives\n here with the 17 items as patched (BEAT JUMP included) and ours last;\n the literals and the count point at it.\n * QUANTIZE is panel report byte 0x12 bit 0x01, which the UI's panel decoder\n (called through literal 0x08B622A8, as for BEAT JUMP) turns into key bit\n 0x0B2E82CB bit 0x80. hk_poll runs the decoder (or whatever the literal\n held: BEAT JUMP's bj_poll, so the two chain) and hides the bit while a\n press is short; past LONG_TICKS it lets it through, and the deck sees an\n ordinary QUANTIZE press. Time is the 10 ms tick at 0x0B3252A8, counted\n by the TMU1 interrupt (0x08B74994).\n * The QUANTIZE label is a bitmap widget (resource 0x1A8, 88x15; +0x0C\n visible, +0x18 top left and +0x1C bottom right as y << 16 | x, +0x44\n resource, 0x50 bytes) among the children of its container (+0x24\n children, +0x28 their count). Hidden when quantize is off, it is not\n drawn then, so the box is drawn after a sibling that always is: the\n TEMPO label (resource 0x196); REMAIN (0x12A) is hidden in ELAPSED. The bitmap widgets' draw method (method\n table entry 0x080B27DC, 0x08B2F12A) becomes hk_render: after the stock\n draw of 0x12A it draws a copy of the QUANTIZE widget, 92 px further right,\n with resource HK_RES, which hk_res (the method's resolver literal\n 0x08B2F384, its only user) turns into a copy of 0x1A8's record with\n our pixels.\n * Recall is the memory CALL: the CALL key's handler 0x08B4F2F2 (event\n 0x100) has the builder 0x08B3B666 send commands 22 and 23; for 22 the\n builder converts the time in ms at the UI's CALL record 0x0AF3B28C +4\n (0x08B0528A) into msg+0x28 in 1/150 s. hk_recall puts the hot cue's time\n there and calls the handler. The deck then stands at the cue (player\n context 0x099445DC +0x10C = 0x17) and hk_resume sends PLAY as the PLAY\n key does from there. As with CALL, the CUE point moves to the hot cue.\n A CALL while playing stops at the cue: the gate's release is one.\n * Fast path: a CALL leaves the CUE point (= loop in, player context\n +0x41C) at the hot cue, and the player keeps that point's audio ready:\n CUE and RELOOP go there at once. So a hot cue that is the CUE point\n already is recalled the way the CUE key goes back while playing\n (hk_cue: command 3, 2, 0, 1), then PLAY; a gate's release only the CUE.\n In the emulator about 0.1 s instead of 0.6 s. Without a hot cue engine\n slot (below) another hot cue takes a CALL: the player loads its audio\n first (about 1.5 s before the target, rolled up).\n * The hot cue engine (hk_engine, its state at ST2): the player and the\n DSP have the CDJ-2000NXS's hot cue segments, unused here. A CALL with\n slot field 4+s and the UI's CALL record event (3+s) << 16 | 0x100 has\n the player load DSP slot s (1-3) and play from it; CUE (command 3) and\n PLAY (command 1) with slot 4+s then jump to that hot cue at once, the\n stream carried on behind it. With the item ON (hot key mode or not),\n once the deck stands at its CUE point for PRELOAD_TICKS after a track\n load, hot cues A-C are\n loaded silently: hk_wrap_tr and hk_wrap_pc, through the DSP manager's\n literals 0x089F29FC and 0x089F24FC (their only users), drop its PLAY\n (transport 2, the play command) meanwhile, and hk_wrap_cy (the DSP\n manager's cyclic block, literal 0x089F31F4) keeps the DSP on hold, so\n nothing is heard; the player's own PLAY once the last point is in is\n dropped too and the deck stays silent until it stands again (he_mute,\n silent 2: only the CUE passes); then CUE, as the CUE key\n sends it while playing, takes the deck back to its CUE point at once\n (that segment stays loaded), and the screen stands still all along\n (he_uf: the UI takes no new position, 0x0AF3B26D; the player's position\n copies at +0x2C8/+0x2CC, which the overview, the phase meter and\n others read, are kept by pub_hook in the player's position publisher;\n the UI's player-event handler's \"track info stale\" marks are dropped\n while held, and its write of the CALL's memory index (UI_AFAC, the\n switch at AFAC_SWITCH replaced by afac_hook) is skipped then, he_shadow\n keeping UI_AFAC 0 against its other writers; never copied over\n otherwise (a copy every poll clobbered search and slip), so neither the\n overview's CALL marker nor the BPM/QUANTIZE blanking of a refetch shows; the overview's\n playhead reads a copy of the player status block with the position\n held; he_load puts the CALL record back to the CUE point once the\n message is built). The DSP decodes one\n segment at a time: it streams from 2 s before the target up to it, then\n the player sends it the point (0x11837C9C/CA0) and the DSP marks the\n row (SLOT_ST, SLOT_PT; found with the emulator's host port log). PLAY\n and CUE do nothing meanwhile (locked). While a track loads (the screen\n held, ST4 +60, or the waveform hidden till the preload, ST4 +62) he_lock\n takes the deck's keys (LOCK_MASK: PLAY, CUE, SEARCH, TRACK, CALL,\n MEMORY, DELETE, LOOP, RELOOP, 4 BEAT, SLIP, the hot cue keys) out of the\n decoded keys and drops a beat jump; the HOT KEYS box blinks LOADING\n (PIX_LOAD, every 0.32 s). BROWSE and the rotary stay free, so another\n track can be loaded. A load is\n done when the DSP's word for row s (cleared before) is 1 and its point\n is the target, heard once it also plays on from it; a beat jump's slot\n has no such word, there the playing on is it. Hot cue s always has slot s; one set later loads on\n its first press (and plays, as a CALL does). A key pressed while a load is under way waits for it (the last one wins);\n one pressed during the preload cuts it short, the rest follows the next\n time the deck stands at its CUE point. After a gate's release the deck\n stands at the slot's hot cue, and PLAY plays that slot (the deck's own\n PLAY would start from the CUE point).\n * Hot cues on the USB: MEMORY's handler (0x08B3CE08) sends request 0x7D6\n through the UI's sender 0x08B3B03C; the DB task's table 0x0899313C\n (+0x10 register 0x08979D34 -> 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('<HH', stock, rec + 4)\n off = RES_TABLE + struct.unpack_from('<I', stock, rec + 0x20)[0]\n px = list(struct.unpack_from('<%dH' % (w * h), stock, off))\n for y in range(3, 13):\n for x in range(3, w - 3):\n px[y * w + x] = background\n gap, space = 3, 5\n width = sum(space if c == ' ' else len(GLYPHS[c][0]) + gap for c in text) - gap\n x0, y0 = (w - width) // 2, 4\n for c in text:\n if c == ' ':\n x0 += space\n continue\n for gy, row in enumerate(GLYPHS[c]):\n for gx, bit in enumerate(row):\n if bit == 'X':\n px[(y0 + gy) * w + x0 + gx] = ink\n x0 += len(GLYPHS[c][0]) + gap\n return w, h, struct.pack('<%dH' % (w * h), *px)\n\n\nSOURCE = r'''\n; ---- hk_poll: the panel decoder (r4-r7 as given), then QUANTIZE's short/long press\nhk_poll:\n sts.l pr,@-r15\n mov.l P_prev,r1\n jsr @r1\n nop\n mov r0,r7 ; the decoder's result, returned as it is\n mov.l P_setting,r1 ; r1 -> 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('<I', 4) + bytes((code, r, g, b))\n assert len(blob) == PCBLOB_USED\n return blob.ljust(PCBLOB_LEN, b'\\x00')\n\n\ndef rgb565(r, g, b):\n return (r >> 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('<I', app, DECODER_LIT - BASE)[0]\n if prev != DECODER and struct.unpack_from('<I', stock, DECODER_LIT - BASE)[0] != DECODER:\n raise SystemExit('patch_cdj900nxs: hotcuekeys: the decoder literal is not the expected one')\n # the hot cues' times: cuecolours' table, when it is in (its cue_get fills it)\n with_cc = struct.unpack_from('<I', app, cuecolours.CUEINFO_LIT - BASE)[0] != cuecolours.CUEINFO\n code, labels = routine(prev, cuecolours.HOT_TABLE if with_cc else None)\n out = bytearray(app)\n end = SHADOW_END - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n for lo, hi in ((HK_BASE, HK_END), (HK2_BASE, HK2_END), (SHADOW, SHADOW_END)):\n if any(out[lo - BASE:hi - BASE]):\n raise SystemExit('patch_cdj900nxs: hotcuekeys: its area 0x%08x.. is not free' % lo)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: hotcuekeys: bytes at 0x%08x are not the expected original'\n % addr)\n out[off:off + len(after)] = after\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(HK_CODE, code)\n # the UTILITY table: the 17 items as the other mods left them, then ours\n table = bytes(out[UT_TABLE - BASE:UT_TABLE - BASE + UT_ITEMS * UT_ITEM]) + item(labels)\n place(labels['TABLE'], table)\n place(labels['LABEL'], LABEL.encode('utf-16-le') + b'\\0\\0')\n _, _, on = box_pixels(stock, RES_QUANTIZE, 0xF800, 0x0000)\n _, _, off = box_pixels(stock, RES_QUANTIZE_OFF, 0x0000, 0x4208)\n _, _, loading = box_pixels(stock, RES_QUANTIZE, 0xFC60, 0x0000, 'LOADING')\n place(PIX_LOAD, loading)\n place(labels['PIX_ON'], on)\n place(labels['PIX_OFF'], off)\n place(labels['LEDTAB'], bytes(b for byte_bit in FRAME_LEDS for b in byte_bit))\n place(labels['NOHOT'], b'\\xff' * 8 * len(FRAME_LEDS))\n put(SENDER_LIT, struct.pack('<I', SENDER), struct.pack('<I', labels['hk_send']))\n for lit in UT_LITS:\n put(lit, struct.pack('<I', UT_RAM), struct.pack('<I', labels['TABLE']))\n put(UT_COUNT, struct.pack('<I', UT_ITEMS), struct.pack('<I', UT_ITEMS + 1))\n off = DECODER_LIT - BASE\n out[off:off + 4] = struct.pack('<I', labels['hk_poll'])\n put(DRAW_METHOD, struct.pack('<I', DRAW), struct.pack('<I', labels['hk_render']))\n sw = stock[AFAC_SWITCH - BASE:AFAC_SWITCH_END - BASE]\n if sw[:2] != bytes.fromhex('b360') or sw[-2:] != bytes.fromhex('2226'):\n raise SystemExit('patch_cdj900nxs: hotcuekeys: the event handler switch is not the expected one')\n new = bytes.fromhex('03d1 0b41 0900 2ba0 0900 0900 0900'.replace(' ', '')) # r1 = hook; jsr; nop; bra end; nop\n new += struct.pack('<I', labels['afac_hook']) # at 0x08B3B770\n new += bytes.fromhex('0900') * ((len(sw) - len(new)) // 2)\n put(AFAC_SWITCH, sw, new)\n put(PUB_CONV_LIT, struct.pack('<I', PUB_CONV), struct.pack('<I', labels['pub_hook']))\n put(OVW_LIT, struct.pack('<I', OVW_SRC), struct.pack('<I', SH_OVW))\n put(EVT_HOOK_LIT, struct.pack('<I', UI_STATE), struct.pack('<I', labels['evt_hook']))\n put(EVT_HOOK_AT, EVT_HOOK_OLD, EVT_HOOK_NEW)\n for lit, addr in EVT_SINK_LITS:\n put(lit, struct.pack('<I', addr), struct.pack('<I', SH_SINK))\n place(HK_API, struct.pack('<II', labels['hk_save2'], labels['hk_load2']))\n put(DRAW_RES_LIT, struct.pack('<I', RESOLVE), struct.pack('<I', labels['hk_res']))\n # the hot cue engine: the DSP manager's transport and play commands\n put(TRANSPORT_LIT, struct.pack('<I', TRANSPORT), struct.pack('<I', labels['hk_wrap_tr']))\n put(PLAYCMD_LIT, struct.pack('<I', PLAYCMD), struct.pack('<I', labels['hk_wrap_pc']))\n put(CYCLIC_LIT, struct.pack('<I', CYCLIC), struct.pack('<I', labels['hk_wrap_cy']))\n # hot cues set or deleted kept on the USB, with their colours\n put(DB_REG_LIT, struct.pack('<I', DB_REG), struct.pack('<I', labels['hk_dbreg']))\n put(PCP2_LIT, struct.pack('<I', PCP2), struct.pack('<I', labels['hk_pcp2']))\n # the big waveform: mov.l @(4,pc),r0; jmp @r0; nop; nop; .long hk_wvmid\n for lit, val in WV_LITS:\n if struct.unpack_from('<I', stock, lit - BASE)[0] != val:\n raise SystemExit('patch_cdj900nxs: hotcuekeys: the waveform function is not the expected one')\n put(WV_MID, WV_STOCK, struct.pack('<4HI', 0xD001, 0x402B, 0x0009, 0x0009, labels['hk_wvmid']))\n place(labels['PCBLOB'], b''.join(pcp2_blob(n) for n in range(1, 9)))\n place(labels['HOTCOL'], struct.pack('<8H', *(rgb565(r, g, b) for _, r, g, b in HOT_COLOURS)))\n if struct.unpack_from('<I', stock, DB_REG_LIT + 12 - BASE)[0] != DB_DEL:\n raise SystemExit('patch_cdj900nxs: hotcuekeys: the cue table of the DB task is not the expected one')\n return bytes(out)\n","mods/cdj900nxs/keynotation.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"KEY NOTATION for the CDJ-900NXS 1.31: every track's key in one notation,\nwhatever rekordbox exported. Our own mod; mods/patch_cdj900nxs.py applies it\nas `keynotation`.\n\n * UTILITY gets one more item, KEY NOTATION: OFF (the key as exported),\n CAMELOT (8A), OPEN KEY (1m) or CLASSIC (Am); CAMELOT until chosen (the\n image holds KN_SETTING = 1, which a saved choice overrides). The choice\n is kept in the firmware's settings journal in flash (id SAVE_ID), as\n HOT CUE KEYS' and LED ANIMATION's.\n * rekordbox exports the key as text, in the notation its user set\n (Webra's \"4A\" on our stick). The player reads it from the DB's key\n table in 0x0897E5E2 (its only caller 0x089883DE, category 5) and copies\n it with wcsncpy (0x08C0586E, called at 0x0897E97C; the function's\n literal 0x0897EB3C, read only there and twice more in it) into the\n track's record, from where it goes to the screen. kn_copy takes that\n literal's place: the copy as before, then, when it returns to\n 0x0897E980 (the key's copy), the text is looked up in a table of all\n spellings (Camelot 1A-12B, Open Key 1m-12d, classic as rekordbox writes\n it, with the sharps the firmware's own table 0x08C46FE4 also accepts)\n and replaced by the chosen one. Anything else stays as it is.\n * The conversion (the Camelot wheel): minor keys A, major B; one step on\n the wheel is a fifth; Open Key = ((Camelot + 4) mod 12) + 1, m / d.\n * Keys anywhere on the screen, in the chosen notation and the wheel colour\n (COLOURS, taken from the user's Camelot wheel picture), with any\n setting but OFF: text widgets are drawn by the method 0x08B303BC (its\n class table entry DRAW_LIT, called with r4 the renderer, r5 the widget:\n +16 style, +40 text colour 0xAABBGGRR, +52 the text). kn_draw takes the\n entry: a text that is keys only (one, or a list \"Abm, B, Dbm, Ebm\" as\n BROWSE > 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('<I', out, a - BASE)[0]\n if struct.unpack_from('<I', stock, COPY_LIT - BASE)[0] != WCSNCPY or word(COPY_LIT) != WCSNCPY:\n raise SystemExit('patch_cdj900nxs: keynotation: the literal at 0x%08x is not the expected one' % COPY_LIT)\n if struct.unpack_from('<I', stock, DRAW_LIT - BASE)[0] != DRAW_TEXT or word(DRAW_LIT) != DRAW_TEXT:\n raise SystemExit('patch_cdj900nxs: keynotation: the text draw entry at 0x%08x is not the expected one'\n % DRAW_LIT)\n code, labels, (match, index, table) = routine()\n count = word(h.UT_COUNT)\n table_at = word(h.UT_LITS[0])\n if any(word(lit) != table_at for lit in h.UT_LITS) or not h.UT_ITEMS <= count < 24:\n raise SystemExit('patch_cdj900nxs: keynotation: the UTILITY table is not as expected')\n src = h.UT_TABLE if table_at == h.UT_RAM else table_at\n items = bytes(out[src - BASE:src - BASE + count * h.UT_ITEM]) + item(labels)\n end = labels['ITEMS'] + len(items)\n assert end <= KX_BUF, 'keynotation: past its area'\n if len(out) < KN_END - BASE:\n out += bytes(KN_END - BASE - len(out))\n if any(out[KN_BASE - BASE:KN_END - BASE]):\n raise SystemExit('patch_cdj900nxs: keynotation: its area 0x%08x.. is not free' % KN_BASE)\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(KN_SETTING, bytes((DEFAULT,)))\n place(KN_CODE, code)\n place(labels['MATCH'], match)\n place(labels['INDEX'], index)\n place(labels['OUT'], table)\n place(labels['COLOURS'], struct.pack('<24I', *(abgr(c) for c in COLOURS)))\n place(labels['LABEL'], LABEL.encode('utf-16-le') + b'\\0\\0')\n for a, v in zip(labels['VALUE'], VALUES):\n place(a, v.encode('utf-16-le') + b'\\0\\0')\n place(labels['LIST'], struct.pack('<%dI' % len(VALUES), *labels['VALUE']))\n place(labels['ITEMS'], items)\n for lit in h.UT_LITS:\n struct.pack_into('<I', out, lit - BASE, labels['ITEMS'])\n struct.pack_into('<I', out, h.UT_COUNT - BASE, count + 1)\n struct.pack_into('<I', out, COPY_LIT - BASE, labels['kn_copy'])\n struct.pack_into('<I', out, DRAW_LIT - BASE, labels['kn_draw'])\n return bytes(out)\n\n\nif __name__ == '__main__':\n for t, i in spellings():\n print('%-4s -> %-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('<I', out, a - BASE)[0]\n for a, stock_value in ((h.DECODER_LIT, h.DECODER), (h.SENDER_LIT, h.SENDER)):\n if struct.unpack_from('<I', stock, a - BASE)[0] != stock_value:\n raise SystemExit('patch_cdj900nxs: ledsaver: the literal at 0x%08x is not the expected one' % a)\n prev_decoder, prev_sender = word(h.DECODER_LIT), word(h.SENDER_LIT)\n with_hk = prev_sender != h.SENDER\n code, labels = routine(prev_decoder, prev_sender, h.HK_BASE + 13 if with_hk else 0)\n # the UTILITY items as the other mods left them\n count = word(h.UT_COUNT)\n table_at = word(h.UT_LITS[0])\n if any(word(lit) != table_at for lit in h.UT_LITS) or not h.UT_ITEMS <= count < 24:\n raise SystemExit('patch_cdj900nxs: ledsaver: the UTILITY table is not as expected')\n src = h.UT_TABLE if table_at == h.UT_RAM else table_at\n items = bytes(out[src - BASE:src - BASE + count * h.UT_ITEM]) + item(labels)\n end = labels['ITEMS'] + len(items)\n assert end <= LS_END, 'ledsaver: past its area'\n if len(out) < LS_END - BASE:\n out += bytes(LS_END - BASE - len(out))\n if any(out[LS_BASE - BASE:LS_END - BASE]):\n raise SystemExit('patch_cdj900nxs: ledsaver: its area 0x%08x.. is not free' % LS_BASE)\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(LS_SETTING, bytes((DEFAULT,)))\n place(LS_CODE, code)\n place(labels['TABLE'], table())\n place(labels['OFFS'], bytes(OFFS))\n place(labels['LABEL'], LABEL.encode('utf-16-le') + b'\\0\\0')\n place(labels['ITEMS'], items)\n for lit in h.UT_LITS:\n struct.pack_into('<I', out, lit - BASE, labels['ITEMS'])\n struct.pack_into('<I', out, h.UT_COUNT - BASE, count + 1)\n struct.pack_into('<I', out, h.DECODER_LIT - BASE, labels['ls_poll'])\n struct.pack_into('<I', out, h.SENDER_LIT - BASE, labels['ls_send'])\n return bytes(out)\n","mods/cdj900nxs/phasereverse.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"PHASE METER extras for the CDJ-900NXS 1.31, part of the mod `phasemeter`\n(arnoldasr's line phase meter, mods/patch_cdj900nxs.py; the old name\n`phasereverse` is taken for it): a third value for UTILITY > 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('<hh', stock, a - BASE)\n res = struct.unpack_from('<I', stock, a + 4 - BASE)[0]\n if x in BARS_X and 30 <= y <= 85 and 420 <= res <= 520:\n if struct.unpack_from('<h', out, a - BASE)[0] != x:\n raise SystemExit('patch_cdj900nxs: phasemeter: the countdown layout at 0x%08x is not stock' % a)\n struct.pack_into('<h', out, a - BASE, x + BARS_DX)\n n += 1\n if n != 46:\n raise SystemExit('patch_cdj900nxs: phasemeter: %d countdown layout records, 46 expected' % n)\n return bytes(out)\n\n\ndef patch(app, stock):\n \"\"\"The application with PHASE METER REVERSE; needs arnoldasr's phasemeter\n (his callback in SCREEN_CB) and goes after the mods that move UTILITY.\"\"\"\n h = hotcuekeys\n out = bytearray(app)\n word = lambda a: struct.unpack_from('<I', out, a - BASE)[0]\n meter = word(SCREEN_CB)\n if struct.unpack_from('<I', stock, SCREEN_CB - BASE)[0] != STOCK_CB or meter == STOCK_CB:\n raise SystemExit('patch_cdj900nxs: phasereverse needs phasemeter (its screen callback)')\n for m in MARKERS:\n if not BASE <= m < BASE + len(out):\n raise SystemExit('patch_cdj900nxs: phasereverse: phasemeter is not where it was')\n # the UTILITY item, wherever the table is now\n table_at = word(h.UT_LITS[0])\n src = h.UT_TABLE if table_at == h.UT_RAM else table_at\n item = src + ITEM * h.UT_ITEM\n take = word(item + 40)\n code, labels = routine(meter, take)\n end = labels['LIST'] + 12\n assert end <= PR_END, 'phasereverse: past its area'\n if len(out) < PR_END - BASE:\n out += bytes(PR_END - BASE - len(out))\n if any(out[PR_BASE - BASE:PR_END - BASE]):\n raise SystemExit('patch_cdj900nxs: phasereverse: its area 0x%08x.. is not free' % PR_BASE)\n label, values, count = struct.unpack_from('<3I', out, item - BASE)\n if label != ITEM_LABEL or count != 2:\n raise SystemExit('patch_cdj900nxs: phasereverse: UTILITY item %d is not PHASE METER' % ITEM)\n off_on = struct.unpack_from('<2I', stock, values - 0x0849A440 - BASE) if values >= 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('<I', out, item + 40 - BASE, labels['pr_take'])\n half = lambda buf, a: struct.unpack_from('<H', buf, a - BASE)[0]\n if half(stock, PACK_AT) != PACK_STOCK or half(out, PACK_AT) != PACK_STOCK or \\\n half(stock, PACK_AT - 2) != 0x004E or half(stock, PACK_AT - 4) != 0xE05C:\n raise SystemExit('patch_cdj900nxs: phasereverse: the settings packer is not the expected one')\n struct.pack_into('<H', out, PACK_AT - BASE, PACK_NEW)\n struct.pack_into('<I', out, SCREEN_CB - BASE, labels['pr_update'])\n return bytes(out)\n","mods/cdj900nxs/played.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"PLAYED for the CDJ-900NXS 1.31: in BROWSE, every track that was loaded on\nthis player since it was switched on is shown in the green the player uses\nfor the loaded track, and so is every track another modded player on Pro DJ\nLink has loaded; a track loaded on other modded players right now has their\nnumbers in front (\"[1][3] Title\"). Our own mod; mods/patch_cdj900nxs.py\napplies it as `played`, after cuenames.\n\n * Text widgets are drawn by the method in the class table entry DRAW_LIT\n (r4 the renderer, r5 the widget: +4 id, +40 colour 0xAABBGGRR, +52\n text). pl_draw takes the entry (in front of cuenames' and keynotation's,\n which it calls).\n * The title bar's title (widget id TITLE_ID) shows the loaded track's\n title while BROWSE is closed: each new one goes into PL_LIST (PL_MAX\n titles of up to PL_LEN characters, the oldest replaced when it is full),\n and its hash (FNV-1a over the UTF-16 characters) into PL_HASH.\n * The player's status packet (Pro DJ Link, UDP 50002, 284 bytes) is built\n in STATUS_BUF by 0x08BB6D4E and copied for sending with memcpy through\n the literal SEND_LIT. pl_send takes that literal: for that copy it first\n writes our tag at TAG_OFF (bytes 0xE1-0x112 are always zero in the\n 900NXS's packets): \"PM\", version 1, 0, then PL_HASH big-endian, 0 while\n no track is loaded (+0x28, the track's source player, 0). Players without\n the mod send no tag and are not marked.\n * The receiving player keeps every player's last status in a table, record\n n at STATUS_TAB + n * 0x11C, with every 32-bit word turned little-endian\n (the tag included). pl_send reads the tags of players 1-6 (not this one,\n STATUS_BUF + 0x21) into PL_CUR, and every hash it sees into PL_OTH\n (PL_OMAX, the oldest replaced).\n * Any other text is looked up: \"[n]\" for every other player whose current\n hash it has (two players with the same track: both), and the colour\n GREEN if it is in PL_LIST or its hash in PL_OTH; a green row with badges\n is drawn a second time with only the badges, in the row's own colour, so\n they stay white (black on the highlighted row) as the row's icon does.\n +52 and +40 are put back after the draw. A track list, a playlist and SEARCH show the title alone,\n so their rows match (two tracks with the same title are marked alike).\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 = '0.3.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nPL_BASE = 0x08FE0800 # after endwarn's 0x08FDF000..0x08FE0800\nPL_END = 0x08FE4000\nPL_COUNT = PL_BASE # word: titles in the list\nPL_NEXT = PL_BASE + 4 # word: the slot the next title goes to\nPL_HASH = PL_BASE + 8 # word: the loaded title's hash\nPL_OCOUNT = PL_BASE + 0xC # word: hashes in PL_OTH\nPL_ONEXT = PL_BASE + 0x10 # word: the slot the next one goes to\nPL_CUR = PL_BASE + 0x20 # 8 words: player n's loaded title hash (0 none)\nPL_BUF = PL_BASE + 0x40 # 192 bytes: a text with what goes in front\nPL_LIST = PL_BASE + 0x100 # PL_MAX x 2 * PL_LEN bytes\nPL_MAX = 64\nPL_LEN = 64 # characters, the NUL included\nPL_OTH = PL_LIST + PL_MAX * 2 * PL_LEN # PL_OMAX words: hashes other players loaded\nPL_OMAX = 64\nPL_CODE = PL_OTH + PL_OMAX * 4\n\nDRAW_LIT = 0x080B27F4\nTITLE_ID = 0x00210009\nBROWSE_DEPTH = 0x0B7CC618\nGREEN = 0xFF009500 # the green of the loaded track's row (0xAABBGGRR)\n\nSEND_LIT = 0x08BB789C # 0x08BB6D4E's literal for memcpy, used for the copy to send\nMEMCPY = 0x08C279C4\nSTATUS_BUF = 0x0B7C9634 # the status packet being built\nSTATUS_LEN = 284\nSTATUS_TAB = 0x0A4F05C8 # player n's last status at + n * STATUS_LEN\nTAG_OFF = 0xF0\nPLAYERS = 6\n\nSOURCE = r'''\n; ---- pl_draw: the text widget's draw method (r4 renderer, r5 widget)\npl_draw:\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 sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov.l @(52,r9),r10 ; the text\n tst r10,r10\n bt pd_p1\n mov.w @r10,r0\n tst r0,r0\n bt pd_p1 ; empty\n mov.l @(4,r9),r1\n mov.l Q_title,r2\n cmp/eq r2,r1\n bf pd_row\n mov.l Q_depth,r1 ; the title bar on the main screen: remember it\n mov.b @r1,r1\n tst r1,r1\n bf pd_p1\n bsr pl_hash\n mov r10,r4\n mov.l Q_hash,r1\n mov.l r0,@r1\n bsr pl_find\n mov r10,r4\n tst r0,r0\n bf pd_p1 ; known\n mov.l Q_next,r1 ; into the next slot\n mov.l @r1,r2\n mov r2,r3\n add #1,r3\n mov #PL_MAX,r0\n cmp/hs r0,r3\n bf pd_wrap\n mov #0,r3\npd_wrap:\n mov.l r3,@r1\n mov.l Q_count,r1\n mov.l @r1,r3\n cmp/hs r0,r3\n bt pd_full\n add #1,r3\n mov.l r3,@r1\npd_full:\n shll8 r2 ; slot * 128\n shlr r2\n mov.l Q_list,r1\n add r1,r2\n mov r10,r1\n mov #PL_LEN,r3\n add #-1,r3\npd_copy:\n mov.w @r1+,r0\n mov.w r0,@r2\n tst r0,r0\n bt pd_p1\n dt r3\n bf/s pd_copy\n add #2,r2\n mov #0,r0\n bra pd_plain\n mov.w r0,@r2\npd_p1: ; (in reach of the checks above)\n bra pd_plain\n nop\npd_row:\n bsr pl_hash\n mov r10,r4\n mov r0,r11 ; its hash\n mov.l Q_buf,r12 ; what goes in front\n tst r11,r11\n bt pd_tick\n mov.l Q_sbuf,r1 ; this player's number\n mov #33,r0\n mov.b @(r0,r1),r13\n extu.b r13,r13\n mov.l Q_cur,r1\n mov #1,r2\npd_pl:\n add #4,r1\n cmp/eq r2,r13\n bt pd_plnext\n mov.l @r1,r0\n cmp/eq r0,r11\n bf pd_plnext\n mov #91,r0 ; \"[n]\"\n mov.w r0,@r12\n mov r2,r0\n add #48,r0\n mov.w r0,@(2,r12)\n mov #93,r0\n mov.w r0,@(4,r12)\n add #6,r12\npd_plnext:\n add #1,r2\n mov #PLAYERS,r0\n cmp/hi r0,r2\n bf pd_pl\npd_tick:\n mov #0,r13 ; where the badges end (0: none)\n mov.l Q_buf,r1 ; badges: a space after them\n cmp/eq r1,r12\n bt pd_nobadge\n mov r12,r13\n mov #32,r0\n mov.w r0,@r12\n add #2,r12\npd_nobadge:\n mov.l @(40,r9),r0 ; the colour, put back after the draw\n mov.l r0,@-r15\n bsr pl_find ; played here\n mov r10,r4\n tst r0,r0\n bf pd_tick_on\n tst r11,r11\n bt pd_front\n mov.l Q_ocount,r1 ; or on another player\n mov.l @r1,r3\n tst r3,r3\n bt pd_front\n mov.l Q_oth,r1\npd_oth:\n mov.l @r1+,r0\n cmp/eq r0,r11\n bt pd_tick_on\n dt r3\n bf pd_oth\n bra pd_front\n nop\npd_tick_on:\n mov.l Q_green,r0 ; played: the player's own green\n mov.l r0,@(40,r9)\npd_front:\n mov.l Q_buf,r1\n cmp/eq r1,r12\n bt pd_notext ; nothing in front\n mov r10,r1\n mov #PL_LEN,r3\npd_tcopy:\n mov.w @r1+,r0\n mov.w r0,@r12\n tst r0,r0\n bt pd_tdone\n dt r3\n bf/s pd_tcopy\n add #2,r12\n mov #0,r0\n mov.w r0,@r12\npd_tdone:\n mov.l Q_buf,r1\n mov.l r1,@(52,r9)\npd_notext:\n mov r8,r4\n mov r9,r5\n mov.l Q_next_draw,r1\n jsr @r1\n nop\n tst r13,r13 ; green with badges: the badges again on top,\n bt pd_restore ; in the row's own colour (as its icon)\n mov.l @r15,r1\n mov.l @(40,r9),r0\n cmp/eq r0,r1\n bt pd_restore\n mov.l r1,@(40,r9)\n mov.w @r13,r11\n mov #0,r0\n mov.w r0,@r13\n mov r8,r4\n mov r9,r5\n mov.l Q_next_draw,r1\n jsr @r1\n nop\n mov.w r11,@r13\npd_restore:\n mov.l r10,@(52,r9)\n mov.l @r15+,r1\n bra pd_out\n mov.l r1,@(40,r9)\npd_plain:\n mov r8,r4\n mov r9,r5\n mov.l Q_next_draw,r1\n jsr @r1\n nop\npd_out:\n lds.l @r15+,pr\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; ---- pl_hash(r4 text) -> 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('<I', buf, a - BASE)[0]\n if word(stock, SEND_LIT) != MEMCPY or word(out, SEND_LIT) != MEMCPY:\n raise SystemExit('patch_cdj900nxs: played: the literal at 0x%08x is not the expected one' % SEND_LIT)\n next_draw = word(out, DRAW_LIT)\n code, labels = routine(next_draw)\n assert PL_CODE + len(code) <= PL_END, 'played: past its area'\n if len(out) < PL_END - BASE:\n out += bytes(PL_END - BASE - len(out))\n if any(out[PL_BASE - BASE:PL_END - BASE]):\n raise SystemExit('patch_cdj900nxs: played: its area 0x%08x.. is not free' % PL_BASE)\n out[PL_CODE - BASE:PL_CODE - BASE + len(code)] = code\n lab = lambda n: labels.get(n, labels.get(n.upper()))\n struct.pack_into('<I', out, DRAW_LIT - BASE, lab('pl_draw'))\n struct.pack_into('<I', out, SEND_LIT - BASE, lab('pl_send'))\n return bytes(out)\n","mods/cdj900nxs/quicksearch.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"QUICK SEARCH for the CDJ-900NXS 1.31: BROWSE held opens SEARCH, BROWSE\npressed twice goes to the list the playing track was loaded from, with the\ntrack picked. Our own mod; mods/patch_cdj900nxs.py applies it as\n`quicksearch`.\n\nThe browse screen is a task of its own (its loop at 0x08BD69A0, its context\nBR_CTX). The UI task turns the keys into commands for it, a message 0x32C9\nsent by browse_cmd (BR_CMD, r4 command, r5-r7 its arguments, at +36..+48 of\nthe message; the browse task takes it only from the UI task, +16 UI_TASK): 0x1001 (source) opens that source's top list (the USB key, BACK\nheld), 0x1004 opens the entry under the cursor (the rotary pushed), 0x1005 is\nBACK, 0x1009 (first row shown, row) puts the cursor on an entry. The task asks\nthe DB task for the list (dbcl_GetRoot and others) and fills the screen when\nthe answer comes (message 0x13BE, 0x08BD81FC: the level at UI_STATE+60, the\ncursor's row at +64). While an answer is still due (BR_CTX +20, +24, +28,\n+32 not all 0) a new command throws the request away: that is why keys\npressed while a list loads from the stick were lost on the real player.\n\nquick search sends these commands itself, one at a time, each once the task\nhas nothing pending and the last one shows on the screen -- no keys pressed\nfor it, no list walked row by row:\n * SEARCH (BROWSE held LONG_TICKS): 0x1001 (the top list of the source the\n screen shows), then SEARCH found among the top list's entries by its\n entry code 0x91 (in the UI's rows, 0x16 bytes before each row's text;\n whatever the language and wherever the export puts it: the rows shown,\n then the next six, ...), 0x1009 onto it, 0x1004. Where the screen stood\n is kept: the next plain BROWSE with the screen closed goes back there.\n * The playing track's list (BROWSE pressed twice within DOUBLE_TICKS):\n while the screen is open the cursor of every level is kept once it stood\n still STABLE_TICKS; a push that closes the screen within LOAD_TICKS\n loaded a track, and that path is kept. Going there: 0x1001, then per\n level 0x1009 onto the kept entry and 0x1004, the last level only 0x1009.\n * A command that shows nothing within OP_TICKS (the task idle) is sent\n again, MAX_TRIES times; GIVEUP_TICKS busy ends the way where it is.\n * BROWSE itself is ours (hidden from the firmware, BROWSE decoded at\n 0x0B2E82D7 bit 0x08) and handed back as a press of its own once let go.\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.4.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nQS_BASE = 0x08FDA400 # after usbautoload's 0x08FD9C00..0x08FDA400\nQS_END = 0x08FDAC00\nQS_CODE = QS_BASE + 0x100\nRELOAD = QS_BASE + 0xF0 # word: set by another mod: the loaded track again, by its\n # browse path (kind 3: the path, the cursor on the track, then the rotary\n # pushed once, the UI task loading it as for a press); taken back to 0 at once\n# state at QS_BASE: bytes +0 BROWSE down, +1 a way under way, +2 its kind (1 SEARCH, 2 a path),\n# +3 its level, +4 held long, +5 a place kept for BROWSE, +6 the level last poll, +7 push down,\n# +8 1: the op sent, its effect awaited, +9 the path's levels, +10 the level the screen stood\n# at, +11 the loaded track's levels, +12 the kept place's levels, +13 tries, +14 1: the cursor\n# moved to look further for SEARCH, +15 the op (1 top list, 2 cursor, 3 open, 4 look for\n# SEARCH); words +16 BROWSE down at, +20 let go at, +24 the cursor's target, +28 the op sent\n# at, +32 its effect shown since, +36 push at, +44 SEARCH's index + 1 last found (0 none),\n# +48 the source last browsed, +52 the rotary pushed till (kind 3's last step), +40 BROWSE held till, +56/+60 level << 8 | cursor last seen and since;\n# +64 cursor per level, +80 the kept place, +96 the loaded track's path, +112 the way's path\n\nDEPTH = 0x0B7CC618 # word: the browse screen's level (0 closed, 1 the top)\nCURSOR = 0x0B7CC61C # word: its cursor's row on the screen (0-5)\nTOP = 0x0B7CA7D0 # word: the list's first row on the screen (cursor = TOP + CURSOR)\nPANE = 0x0B7CC6FC # word: the left pane's state, 2 once its list has come (0 while coming)\nCOUNT = PANE + 4 # word: the list's entries\nUI_STATE = 0x0B7CC5DC # the browse screen's state; +0 1 closed, 2 open; +4 its source (2 the USB)\nROW_IDS = 0x0B7CA9FC # word: the entry code of row 0 shown (then every ROW_LEN)\nROW_LEN = 0x220\nSEARCH_ID = 0x91 # the top list's SEARCH (ARTIST 0x81, TRACK 0x83, PLAYLIST 0x84 ...)\nKEY_BROWSE = (0x0B2E82D7, 0x08)\nKEY_D6 = 0x0B2E82D6 # the rotary's push 0x20\nPUSH_BIT = 0x20\nBR_CTX = 0x0BAB926C # the browse task's context: +20/+24/+28/+32 a DB answer due\nBR_CMD = 0x08BBDE6C # browse_cmd(command, a1, a2, a3): a message to the browse task\nUI_POOL = 0x0B7CC5A4 # word: the pool browse_cmd takes its message from\nUI_TASK = 0x0A4DD1C0 # word: the UI task's id (the only sender the browse task takes)\nBR_MBX = 0x0A4DD228 # word: the browse task's mailbox\nGET_MPL, REL_MPL, SND_MBX, MSG_TIME = 0x08B054DC, 0x08B05508, 0x08B053B0, 0x08B055B8\nMEMSET = 0x08C28038\nCMD_TOP, CMD_OPEN, CMD_CURSOR = 0x1001, 0x1004, 0x1009\n\nLONG_TICKS = 60 # BROWSE held 0.6 s: SEARCH\nDOUBLE_TICKS = 50 # a second press within 0.5 s of letting go: the playing track\nLOAD_TICKS = 150 # a push that closes the screen within 1.5 s: a track loaded\nPULSE = 8 # BROWSE handed back for 80 ms\nSTABLE_TICKS = 10 # a level and cursor kept once that long the same\nPUSH_QUIET = 100 # ... and not within 1 s of a push\nSETTLE_TICKS = 45 # a new list shown and the task idle that long (it slides in): the next op\nCUR_TICKS = 10 # the cursor moved and the task idle that long: the next op\nOP_TICKS = 120 # an op that showed nothing that long (the task idle): sent again\nMAX_TRIES = 3\nGIVEUP_8 = 125 # 1000 ticks / 8: the task busy that long, or the way stuck: it ends\n\nSOURCE = r'''\n; ---- qs_poll: the panel decoder (or whatever ran there), then BROWSE ours\nqs_poll:\n sts.l pr,@-r15\n mov.l QA_prev,r0\n jsr @r0\n nop\n mov.l r0,@-r15 ; the decoder's result, returned as it is\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 mov.l QA_base,r14\n mov.l QA_tick,r13\n mov.l @r13,r13 ; now\n mov.l QA_browse,r1 ; BROWSE as pressed, hidden from the firmware\n mov.b @r1,r0\n mov r0,r10\n and #-9,r0\n mov.b r0,@r1\n mov r10,r0\n and #8,r0\n mov r0,r10 ; r10: BROWSE down\n mov.l QA_d6,r1 ; the rotary's push as pressed: its tick\n mov.b @r1,r0\n and #PUSH_BIT,r0\n mov r0,r2\n mov.b @(7,r14),r0\n tst r0,r0\n bf qp_pushold\n tst r2,r2\n bt qp_pushold\n mov.l r13,@(36,r14)\nqp_pushold:\n mov r2,r0\n mov.b r0,@(7,r14)\n mov.l QA_state,r1 ; the source browsed, kept (0 while switching)\n mov.l @(4,r1),r0\n tst r0,r0\n bt qp_src\n mov.l r0,@(48,r14)\nqp_src:\n mov.l QA_depth,r1 ; the browse screen: level and cursor\n mov.l @r1,r12\n mov.l QA_cursor,r1\n mov.l @r1,r11\n mov.l QA_top,r1 ; + the list's first row shown: its index\n mov.l @r1,r1\n add r1,r11\n mov.b @(1,r14),r0 ; a way of ours under way: nothing kept from it\n tst r0,r0\n bf qp_kept\n mov #16,r1\n cmp/hs r1,r12\n bt qp_kept\n tst r12,r12\n bt qp_closed\n mov r12,r0 ; level << 8 | cursor: kept once STABLE_TICKS the same\n shll8 r0\n or r11,r0\n mov.l @(56,r14),r1\n cmp/eq r1,r0\n bt qp_same\n mov.l r0,@(56,r14)\n bra qp_kept\n mov.l r13,@(60,r14)\nqp_same:\n mov.l @(60,r14),r1\n mov r13,r2\n sub r1,r2\n mov #STABLE_TICKS,r1\n cmp/hs r1,r2\n bf qp_kept\n mov.l @(36,r14),r1 ; not right after a push (the UI puts its row to 0 then)\n mov r13,r2\n sub r1,r2\n mov #PUSH_QUIET,r1\n cmp/hs r1,r2\n bf qp_kept\n mov r14,r1 ; cur[level] = cursor\n add #64,r1\n add r12,r1\n mov r11,r0\n mov.b r0,@r1\n mov r12,r0\n bra qp_kept\n mov.b r0,@(10,r14) ; the level it stood at last\nqp_closed:\n mov.b @(6,r14),r0 ; just closed by a push: a track loaded\n tst r0,r0\n bt qp_kept\n mov r0,r9\n mov.l @(36,r14),r1\n mov r13,r2\n sub r1,r2\n mov.l QA_loadt,r1\n cmp/hs r1,r2\n bt qp_kept\n mov r9,r0\n mov.b r0,@(11,r14) ; its path: the cursors of levels 1..n\n mov r14,r4\n add #65,r4\n mov r14,r5\n add #96,r5\n bsr qs_copy\n mov r9,r6\nqp_kept:\n mov r12,r0\n mov.b r0,@(6,r14)\n mov.l QA_req,r1 ; another mod asks for the loaded track again (RELOAD)\n mov.l @r1,r0\n tst r0,r0\n bt qp_noreq\n mov #0,r0\n mov.l r0,@r1\n mov.b @(1,r14),r0\n tst r0,r0\n bf qp_noreq\n mov.b @(11,r14),r0\n tst r0,r0\n bt qp_noreq\n mov r0,r6\n mov r14,r4\n add #96,r4\n bsr qs_goto\n nop\n mov #3,r0 ; kind 3: the path, then the track opened (loaded)\n mov.b r0,@(2,r14)\nqp_noreq:\n mov.b @r14,r0 ; BROWSE: pressed now?\n tst r0,r0\n bf qp_was\n tst r10,r10\n bt qp_keys_done\n mov.l r13,@(16,r14) ; down: its tick (and a way of ours under way called off)\n mov #0,r0\n mov.b r0,@(1,r14)\n bra qp_keys_done\n mov.b r0,@(4,r14)\nqp_was:\n tst r10,r10\n bt qp_up\n mov.b @(4,r14),r0 ; held\n tst r0,r0\n bf qp_keys_done\n mov.l @(16,r14),r1\n mov r13,r2\n sub r1,r2\n mov #LONG_TICKS,r1\n cmp/hs r1,r2\n bf qp_keys_done\n mov #1,r0 ; held long: SEARCH\n mov.b r0,@(4,r14)\n bsr qs_search\n nop\n bra qp_keys_done\n nop\nqp_up:\n mov.b @(4,r14),r0 ; let go: not after a long hold\n tst r0,r0\n bf qp_keys_done\n mov.l @(20,r14),r1\n mov r13,r2\n sub r1,r2\n mov #DOUBLE_TICKS,r1\n cmp/hs r1,r2\n bt qp_single\n mov.b @(11,r14),r0 ; the second press: the playing track's list\n tst r0,r0\n bt qp_single\n mov r0,r6\n mov #0,r0\n mov.l r0,@(20,r14)\n mov r14,r4\n add #96,r4\n bsr qs_goto\n nop\n bra qp_keys_done\n nop\nqp_single:\n mov.l r13,@(20,r14)\n mov.b @(5,r14),r0 ; back to where it was before SEARCH?\n tst r0,r0\n bt qp_plain\n tst r12,r12\n bf qp_plain\n mov #0,r0\n mov.b r0,@(5,r14)\n mov.b @(12,r14),r0\n tst r0,r0\n bt qp_plain\n mov r0,r6\n mov r14,r4\n add #80,r4\n bsr qs_goto\n nop\n bra qp_keys_done\n nop\nqp_plain:\n mov #PULSE,r0 ; BROWSE as the firmware knows it\n add r13,r0\n mov.l r0,@(40,r14)\nqp_keys_done:\n mov r10,r0\n mov.b r0,@r14\n bsr qs_step ; a way under way\n nop\n mov.l @(40,r14),r0 ; BROWSE handed back\n sub r13,r0\n cmp/pl r0\n bf qp_nob\n mov.l QA_browse,r1\n mov.b @r1,r0\n or #8,r0\n mov.b r0,@r1\nqp_nob:\n mov.l @(52,r14),r0 ; the rotary pushed for a way's last step\n sub r13,r0\n cmp/pl r0\n bf qp_nop\n mov.l QA_d6,r1\n mov.b @r1,r0\n or #PUSH_BIT,r0\n mov.b r0,@r1\nqp_nop:\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 mov.l @r15+,r8\n mov.l @r15+,r0\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nQA_base: .long QS_BASE\nQA_browse: .long KEY_BROWSE_AT\nQA_cursor: .long CURSOR\nQA_d6: .long KEY_D6\nQA_depth: .long DEPTH\nQA_loadt: .long LOAD_TICKS\nQA_prev: .long PREV_DECODER\nQA_tick: .long TICK\nQA_top: .long TOP\nQA_state: .long UI_STATE\nQA_req: .long RELOAD\n\n; ---- qs_copy: r6 bytes from r4 to r5 (r0, r4-r6 used)\nqs_copy:\n tst r6,r6\n bt qc_ret\nqc_loop:\n mov.b @r4+,r0\n mov.b r0,@r5\n dt r6\n bf/s qc_loop\n add #1,r5\nqc_ret:\n rts\n nop\n\n; ---- qs_search: r14 -> 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('<I', out, a - BASE)[0]\n sword = lambda a: struct.unpack_from('<I', stock, a - BASE)[0]\n if sword(h.DECODER_LIT) != h.DECODER:\n raise SystemExit('patch_cdj900nxs: quicksearch: the decoder literal is not the expected one')\n # the browse task's command sender and context as found (see the docstring)\n if stock[BR_CMD - BASE:BR_CMD - BASE + 4] != bytes.fromhex('962fa62f') or sword(0x08BD6984) != BR_CTX:\n raise SystemExit('patch_cdj900nxs: quicksearch: the browse task is not the expected one')\n code, labels = routine(word(h.DECODER_LIT))\n assert QS_CODE + len(code) <= QS_END, 'quicksearch: past its area'\n if len(out) < QS_END - BASE:\n out += bytes(QS_END - BASE - len(out))\n if any(out[QS_BASE - BASE:QS_END - BASE]):\n raise SystemExit('patch_cdj900nxs: quicksearch: its area 0x%08x.. is not free' % QS_BASE)\n out[QS_CODE - BASE:QS_CODE - BASE + len(code)] = code\n struct.pack_into('<I', out, h.DECODER_LIT - BASE, labels['qs_poll'])\n return bytes(out)\n","mods/cdj900nxs/sh4asm.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"A small two-pass SH-4 assembler for the CDJ-900NXS mods, enough for the\ninteger code they need. Syntax as the firmware listings print it:\n\n label:\n mov.l @(8,r4),r1 ; comment\n mov.l L_func,r1 ; pc-relative literal\n jsr @r1\n nop\n .align 4\n L_func: .long 0x08976316 ; or .long other_label\n .word 0x1234\n\nassemble(text, base, symbols) -> (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('<H', 0x0009)\n pc += 2\n continue\n if mn == '.long':\n v = ops[0]\n val = labels.get(v, pc) if (v in labels or (not final and re.match(r'^[A-Za-z_]\\w*$', v) and v not in syms)) else _num(v, syms)\n if final and v in labels:\n val = labels[v]\n if pc % 4:\n raise AsmError('.long not aligned')\n out += struct.pack('<I', val & 0xFFFFFFFF)\n pc += 4\n continue\n if mn == '.word':\n out += struct.pack('<H', _num(ops[0], syms) & 0xFFFF)\n pc += 2\n continue\n if mn == '.space':\n n = int(ops[0], 0)\n out += bytes(n)\n pc += n\n continue\n op = _encode(mn, [o.strip().lower() if not re.match(r'^\\w+$', o.strip()) or o.strip().lower() in R else o.strip() for o in ops],\n pc, labels, {**syms, **labels}, final)\n if final and prev is not None and _delayed(prev) and _slot_illegal(op):\n raise AsmError('not allowed in a delay slot (slot illegal instruction)')\n out += struct.pack('<H', op)\n prev = op\n pc += 2\n except AsmError as e:\n raise AsmError('%s (in: %s)' % (e, raw.strip()))\n return bytes(out), labels\n","mods/cdj900nxs/usbautoload.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"USB AUTO LOAD for the CDJ-900NXS 1.31: the MY SETTINGS on a USB stick are\nloaded as soon as the player finds them, without \"MY SETTINGS DATA was\nfound. Push the MENU button to LOAD.\" waiting for MENU. Our own mod;\nmods/patch_cdj900nxs.py applies it as `usbautoload`.\n\n * UTILITY gets one more item, USB AUTO LOAD (OFF / ON, ON until chosen:\n the image holds UA_SETTING = 1, which a saved choice overrides), after\n the others. The choice is kept in the firmware's settings journal in\n flash (id SAVE_ID), as LED ANIMATION's.\n * The message is the UI's popup 0x7D (message table 0x08FBAF18 entry 98,\n shown by 0x08BDB8FA, the popup up at 0x111070E0). The stock way to load\n is MENU (panel 0x14:0x08), which opens MY SETTINGS with LOAD first,\n then the rotary's push (0x11:0x01): 0x08BEFAB4 reads the files, the\n popup turns to 0x7C, \"Your settings were loaded to PLAYER.\"\n * ua_poll (after the panel decoder, chained through its literal as\n ledsaver does) takes the same way: while popup 0x7D is up it holds,\n START_WAIT after it came up, MENU's decoded bit (0x0B2E82D7 0x01) for\n PRESS_TICKS, then, MENU_WAIT\n later, the rotary push's (0x0B2E82D6 0x20); it waits for the popup to\n change (GIVE_UP at most, then not again until the popup is gone).\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.1.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nUA_BASE = 0x08FD9C00 # after hotcuekeys' SHADOW (0x08FD8C00..0x08FD9C00)\nUA_END = 0x08FDA400\nUA_SETTING = UA_BASE # byte: 1 = ON (DEFAULT until a choice is saved)\nDEFAULT = 1\nUA_LOADED = UA_BASE + 1 # byte: the saved setting read\nUA_STEP = UA_BASE + 2 # byte: 0 idle, 1 MENU, 2 push, 3 waiting, 4 given up\nUA_T0 = UA_BASE + 4 # word: the tick the popup was seen\nUA_CODE = UA_BASE + 0x10\n\nPOPUP = 0x111070E0 # word: the UI's popup up (0 none)\nPOPUP_FOUND = 0x7D # \"MY SETTINGS DATA was found. Push the MENU button to LOAD.\"\nKEY_MENU = (0x0B2E82D7, 0x01) # decoded MENU (panel 0x14:0x08)\nKEY_PUSH = (0x0B2E82D6, 0x20) # decoded rotary push (panel 0x11:0x01)\nSTART_WAIT = 50 # MENU 0.5 s after the popup is up (at once it only took it away)\nPRESS_TICKS = 8 # a key held 80 ms\nMENU_WAIT = 40 # the push 0.4 s after MENU\nGIVE_UP = 300 # 3 s after it: no more tries for this popup\nSAVE_ID = 0x83E4 # ours (hotcuekeys 0x83E1, ledsaver 0x83E2, keynotation 0x83E3)\nLABEL = 'USB AUTO LOAD'\n\nSOURCE = r'''\n; ---- ua_poll: the panel decoder (or whatever ran there), then, with the item\n; ON and popup 0x7D up, MENU and the rotary's push as if pressed\nua_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 up_loaded\n bsr ua_load ; the first time: the saved setting\n nop\nup_loaded:\n mov.l K_state,r1\n mov.l K_popup,r2\n mov.l @r2,r2\n mov #POPUP_FOUND,r0\n cmp/eq r0,r2\n bt up_found\n mov #0,r0 ; no such popup: idle\n bra up_out\n mov.b r0,@(2,r1)\nup_found:\n mov.b @r1,r0\n tst r0,r0\n bt up_out ; OFF: the firmware's way\n mov.l K_tick,r3\n mov.l @r3,r3 ; now\n mov.b @(2,r1),r0\n tst r0,r0\n bf up_going\n mov #START_WAIT,r2 ; seen now: MENU a little later\n add r3,r2\n mov.l r2,@(4,r1)\n mov #1,r0\n mov.b r0,@(2,r1)\nup_going:\n cmp/eq #4,r0\n bt up_out ; given up for this popup\n mov.l @(4,r1),r2\n sub r2,r3 ; ticks since\n cmp/pz r3\n bf up_out ; not yet\n mov #PRESS_TICKS,r2\n cmp/hs r2,r3\n bt up_nomenu\n mov.l K_menu,r2 ; MENU held\n mov.b @r2,r0\n or #KEY_MENU_BIT,r0\n bra up_out\n mov.b r0,@r2\nup_nomenu:\n mov #MENU_WAIT,r2\n cmp/hs r2,r3\n bf up_out ; MENU let go, the menu opens\n add #PRESS_TICKS,r2\n cmp/hs r2,r3\n bt up_nopush\n mov #2,r0\n mov.b r0,@(2,r1)\n mov.l K_push,r2 ; the push held: LOAD\n mov.b @r2,r0\n or #KEY_PUSH_BIT,r0\n bra up_out\n mov.b r0,@r2\nup_nopush:\n mov #3,r0 ; waiting for the popup to change\n mov.l K_giveup,r2\n cmp/hs r2,r3\n bf up_wait\n mov #4,r0 ; still there: no more tries for it\nup_wait:\n mov.b r0,@(2,r1)\nup_out:\n mov.l @r15+,r0\n lds.l @r15+,pr\n rts\n nop\n\n; ---- ua_show: the item's +28, SCREEN SAVER's with our value in place of its getter's\nua_show:\n mov.l @(16,r4),r0\n cmp/eq #-1,r0\n bt uw_ours\n mov.l K_show,r1\n jmp @r1\n nop\nuw_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; ---- ua_take: the item's +40, a chosen value taken (as SCREEN SAVER's)\nua_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 ut_none\n mov.l @(12,r4),r2\n cmp/eq r2,r5\n bt ut_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 ua_save ; into the flash journal\n mov r5,r4\n lds.l @r15+,pr\n rts\n mov #1,r0\nut_none:\n rts\n mov #0,r0\n\n; ---- ua_save: r4 = value, sent to the Backup task as setting SAVE_ID (type 5),\n; as ledsaver's ls_save\nua_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 uv_out\n mov.l @r15,r9\n tst r9,r9\n bt uv_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 uv_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\nuv_out:\n add #4,r15\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- ua_load: r1 -> 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('<I', out, a - BASE)[0]\n if struct.unpack_from('<I', stock, h.DECODER_LIT - BASE)[0] != h.DECODER:\n raise SystemExit('patch_cdj900nxs: usbautoload: the decoder literal is not the expected one')\n stock_word = lambda a: struct.unpack_from('<I', stock, a - BASE)[0]\n if stock_word(POPUP_ENTRY) != POPUP_TEXT:\n raise SystemExit('patch_cdj900nxs: usbautoload: popup 98 is not the expected message')\n code, labels = routine(word(h.DECODER_LIT))\n count = word(h.UT_COUNT)\n table_at = word(h.UT_LITS[0])\n if any(word(lit) != table_at for lit in h.UT_LITS) or not h.UT_ITEMS <= count < 24:\n raise SystemExit('patch_cdj900nxs: usbautoload: the UTILITY table is not as expected')\n src = h.UT_TABLE if table_at == h.UT_RAM else table_at\n items = bytes(out[src - BASE:src - BASE + count * h.UT_ITEM]) + item(labels)\n end = labels['ITEMS'] + len(items)\n assert end <= UA_END, 'usbautoload: past its area'\n if len(out) < UA_END - BASE:\n out += bytes(UA_END - BASE - len(out))\n if any(out[UA_BASE - BASE:UA_END - BASE]):\n raise SystemExit('patch_cdj900nxs: usbautoload: its area 0x%08x.. is not free' % UA_BASE)\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(UA_SETTING, bytes((DEFAULT,)))\n place(UA_CODE, code)\n place(labels['LABEL'], LABEL.encode('utf-16-le') + b'\\0\\0')\n place(labels['ITEMS'], items)\n for lit in h.UT_LITS:\n struct.pack_into('<I', out, lit - BASE, labels['ITEMS'])\n struct.pack_into('<I', out, h.UT_COUNT - BASE, count + 1)\n struct.pack_into('<I', out, h.DECODER_LIT - BASE, labels['ua_poll'])\n return bytes(out)\n\n\nPOPUP_ENTRY = 0x08FBB0A0 # message table entry 98\nPOPUP_TEXT = 0x08F70622 # -> \"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); asked again (TRIES) while the reply comes empty, as it did\n now and then when two players loaded from the same stick at once. In\n 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, r10 the column; r11\n the entry of its own 400 drawn there, every fourth twice -- taken for\n the column before 1.4.0, which drew the first 80 % of the track over the\n whole width) draws a column bottom up in one colour of its 12-entry\n table; its call of 0x08B03C5E once a column (literal OV_PLAYED_LIT) goes\n to wm_ov, which fills OVCOL with a colour per row and returns index 0,\n the table literal pointing at OVCOL and the three row loads made @r14+.\n In 3BAND with the real bands the column shows them (wm_ovbuild: each\n band's sum over the column's span of the detail, the highest sum\n OV_ROWS px; PWV6 is not used, its bands being normalised one by one),\n the part played dimmed (PLAYED_DIM). In RGB the column takes the colours\n of the detail's columns in its span (wm_ovrgb: the working buffer's\n palette entries weighted by height, at full brightness), as rekordbox\n colours its overview; otherwise the firmware's colour for the column,\n from RAM OVERVIEW: the player's blues for BLUE, arnoldasr's gradient\n without a detail (written when the mode changes).\n * The bands are kept per track (SLOTS: device, rekordbox id, block,\n columns; SLOTS_N of them, as the player keeps tracks it has loaded and\n loads one again without fetching its waves): wm_fetch takes the track\n from its caller's request (+1 slot, +2 player; r5 the id) and puts the bands into\n that track's slot (wm_slot); wm_sel, each frame from wm_ovtick, makes\n the loaded track's (TRACK_INFO, its device's low byte left out: 1 for a\n track over LINK, which kept a LINK track's bands from ever being used\n before 1.4.0) the ones in use, builds the overview's\n from them and has the overview drawn again -- before, a track loaded\n again showed the bands of the one fetched last, and an overview drawn\n before its bands came kept arnoldasr's colours.\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 and after the preparer made the working buffer (wm_after, in its\n literal AFTER_LIT after arnoldasr's hook): a load draws the overview\n before the buffer is the new track's.\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.4.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_KEYDEV = WM_BASE + 28 # words: the track the bands in use are for (device, rekordbox id;\nWM_KEYID = WM_BASE + 32 # -1: to be looked up again)\nWM_SLOTI = WM_BASE + 44 # word: the next slot to take\nSLOTS_N = 4 # tracks whose bands are kept (the player keeps two records)\nTRACK_INFO = 0x09944900 # the deck's info: +0 device (player << 16 | slot << 8, | 1 over LINK), +4 rekordbox id\nWM_CODE = WM_BASE + 0x40\nAFTER_LIT = 0x08BC6A7C # the preparer's literal of 0x08BD2A54, after the working buffer is made (arnoldasr's hook in it)\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; +0 its columns\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\nTRIES = 8 # PWV7 asked for up to this often while the reply comes empty\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 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_sel,r0\n jsr @r0\n nop\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\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; ---- wm_after: the preparer's call right after it made the working buffer (AFTER_LIT,\n; arnoldasr's hook first): the overview drawn again (WM_REDRAW), as RGB's is drawn from it\n; and a load draws the overview before the buffer is the new track's\nwm_after:\n sts.l pr,@-r15\n mov.l T_after,r1\n jsr @r1\n nop\n mov r0,r2 ; (what it returned)\n mov.l T_state,r1\n mov #1,r0\n mov.b r0,@(3,r1)\n mov r2,r0\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nT_state: .long WM_SETTING\nT_sel: .long wm_sel\nT_ovdraw: .long OV_DRAW\nT_fpmask: .long 0xFFE7FFFF\nT_bar: .long PREV_BAR\nT_after: .long PREV_AFTER\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 #TRIES,r11 ; asked again while the reply comes empty (the server busy\nwf_ask: ; with another player's request: seen with two decks loading)\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 bf wf_got\n dt r11\n bf wf_ask\n bra wf_out\n nop\nwf_got:\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 bsr wm_slot ; r8: the slot for the track (its old block freed)\n nop\n mov r0,r8\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 r9,@(8,r8) ; the slot: its block and columns; the bands in use looked up again\n mov.l r13,@(12,r8)\n mov.l F_keyid,r1\n mov #-1,r0\n mov.l r0,@r1\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: r10 the column (0..499; r11 is the entry of the firmware's 400 it\n; draws there, every fourth twice), 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 r10,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.b @r7,r0 ; RGB: the colours of the detail's span (wm_ovrgb)\n cmp/eq #1,r0\n bf ov_stock\n mov.l O_rgb,r0\n jsr @r0\n nop\n cmp/pz r0\n bf ov_stock ; (no detail)\n bra ov_one\n mov r0,r2\nov_stock:\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\nov_one:\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\nO_rgb: .long wm_ovrgb\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_keyid: .long WM_KEYID\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 .align 2\n; ---- wm_slot, from wm_fetch (r9 the track's id; its caller's request above wm_fetch's\n; frame: +1 slot, +2 player) -> r0 the slot for the track: its own if it has one, else the\n; next one not in use; its key set, its old block freed (taken out of use first if it is the\n; one in use); r1-r7\nwm_slot:\n sts.l pr,@-r15\n mov r15,r1 ; the request: wm_fetch's 8 words and 8 bytes, our pr\n add #44,r1\n mov.b @(2,r1),r0\n extu.b r0,r2\n shll8 r2\n mov.b @(1,r1),r0\n extu.b r0,r0\n or r0,r2\n shll8 r2 ; device: player << 16 | slot << 8\n mov r9,r3 ; id\n mov.l S_slots,r1\n mov #SLOTS_N,r6\nsl_find:\n mov.l @r1,r0\n cmp/eq r2,r0\n bf sl_fnext\n mov.l @(4,r1),r0\n cmp/eq r3,r0\n bt sl_got\nsl_fnext:\n dt r6\n bf/s sl_find\n add #16,r1\n mov #SLOTS_N,r6 ; none: the next one, not the one in use\nsl_next:\n mov.l S_sloti,r4\n mov.l @r4,r0\n mov r0,r5\n add #1,r5\n mov #SLOTS_N,r1\n cmp/hs r1,r5\n bf sl_ni\n mov #0,r5\nsl_ni:\n mov.l r5,@r4\n shll2 r0\n shll2 r0\n mov.l S_slots,r1\n add r0,r1\n mov.l S_keydev,r4\n mov.l @r4,r0\n mov.l @r1,r5\n cmp/eq r0,r5\n bf sl_take\n mov.l @(4,r4),r0\n mov.l @(4,r1),r5\n cmp/eq r0,r5\n bf sl_take\n dt r6\n bf sl_next\nsl_take:\n mov.l r2,@r1\n mov.l r3,@(4,r1)\nsl_got:\n mov.l S_keydev,r4 ; the one in use: out of use first\n mov.l @r4,r0\n cmp/eq r2,r0\n bf sl_free\n mov.l @(4,r4),r0\n cmp/eq r3,r0\n bf sl_free\n mov.l S_state,r5\n mov #0,r0\n mov.l r0,@(8,r5) ; WM_COUNT\n mov.l r0,@(12,r5) ; WM_OVBANDS\n mov.l r0,@(4,r5) ; WM_BANDS\n mov #-1,r0\n mov.l r0,@(4,r4) ; looked up again\nsl_free:\n mov.l @(8,r1),r4\n mov #0,r0\n mov.l r0,@(8,r1)\n mov.l r0,@(12,r1)\n tst r4,r4\n bt sl_ret\n mov.l r1,@-r15\n mov.l S_free,r0\n jsr @r0\n nop\n mov.l @r15+,r1\nsl_ret:\n mov r1,r0\n lds.l @r15+,pr\n rts\n nop\n\n; ---- wm_sel: each frame (wm_ovtick): the bands in use are the loaded track's (TRACK_INFO):\n; its slot's (the overview's built from them, drawn again), else none (the estimate)\nwm_sel:\n mov.l S_info,r1\n mov.l @r1,r2 ; device (its low byte left out: 1 for a track over LINK,\n mov #-1,r0 ; which the fetch's request does not have)\n shll8 r0\n and r0,r2\n mov.l @(4,r1),r3 ; id\n mov.l S_keydev,r4\n mov.l @r4,r0\n cmp/eq r2,r0\n bf se_new\n mov.l @(4,r4),r0\n cmp/eq r3,r0\n bf se_new\n rts\n nop\nse_new:\n sts.l pr,@-r15\n mov.l r2,@r4\n mov.l r3,@(4,r4)\n mov.l S_state,r5 ; none in use meanwhile\n mov #0,r0\n mov.l r0,@(8,r5)\n mov.l r0,@(12,r5)\n mov.l r0,@(4,r5)\n mov #1,r0 ; the overview drawn again\n mov.b r0,@(3,r5)\n tst r2,r2\n bt se_ret ; no track\n mov.l S_slots,r1\n mov #SLOTS_N,r6\nse_find:\n mov.l @r1,r0\n cmp/eq r2,r0\n bf se_fnext\n mov.l @(4,r1),r0\n cmp/eq r3,r0\n bt se_got\nse_fnext:\n dt r6\n bf/s se_find\n add #16,r1\n bra se_ret\n nop\nse_got:\n mov.l @(8,r1),r4 ; its block, columns\n mov.l @(12,r1),r6\n tst r4,r4\n bt se_ret\n mov.l r1,@-r15\n mov.l r4,@(4,r5)\n bsr wm_ovbuild ; (sets WM_OVBANDS)\n mov r6,r5\n mov.l @r15+,r1\n mov.l @(12,r1),r0\n mov.l S_state,r5\n mov.l r0,@(8,r5) ; in use\nse_ret:\n lds.l @r15+,pr\n rts\n nop\n .align 4\nS_slots: .long SLOTS\nS_sloti: .long WM_SLOTI\nS_keydev: .long WM_KEYDEV\nS_state: .long WM_SETTING\nS_free: .long FREE\nS_info: .long TRACK_INFO\n\n; ---- wm_ovrgb, from wm_ov in RGB: r10 the overview's column, r3 the firmware's flag (as\n; in wm_ov: set, dimmed) -> r0 the column's colour (RGB565), -1 without a detail: the\n; rekordbox colours (palette entries) of the detail's columns in its span (the working\n; buffer: height, palette index), weighted by their height + 1, at full brightness (its\n; highest channel full, so the mix keeps the palette's look); r3, r8-r14 kept\nwm_ovrgb:\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 r3,@-r15\n sts.l pr,@-r15\n mov r10,r8 ; the column\n mov.l R_work,r1\n mov.l @r1,r9 ; the working buffer's columns\n tst r9,r9\n bt or_no\n mov.l R_work,r1\n mov.l R_workoff,r0\n mov.l @(r0,r1),r10 ; the working buffer\n tst r10,r10\n bf or_go\nor_no:\n bra or_out\n mov #-1,r0\nor_go:\n mov r8,r4 ; the span: column * N / OV_COLUMNS ..\n mul.l r9,r4\n sts macl,r4\n mov.l R_cols,r5\n bsr wm_div\n nop\n mov r0,r12\n mov r8,r4 ; .. (column + 1) * N / OV_COLUMNS, at least one\n add #1,r4\n mul.l r9,r4\n sts macl,r4\n mov.l R_cols,r5\n bsr wm_div\n nop\n cmp/hi r12,r0\n bt or_span\n mov r12,r0\n add #1,r0\nor_span:\n sub r12,r0\n mov r0,r11 ; its columns\n shll r12\n add r10,r12 ; its first\n mov #0,r8 ; red, green, blue, weight sums\n mov #0,r9\n mov #0,r10\n mov #0,r3\n mov.l R_pal,r7\nor_loop:\n mov.b @r12+,r0\n extu.b r0,r6\n add #1,r6 ; the weight: height + 1\n mov.b @r12+,r0 ; the palette index (negative: 0)\n cmp/pz r0\n bt or_pos\n mov #0,r0\nor_pos:\n shll r0\n mov.w @(r0,r7),r0\n extu.w r0,r0\n mov r0,r1\n shlr8 r1\n shlr2 r1\n shlr r1 ; red 0..31\n mul.l r6,r1\n sts macl,r1\n add r1,r8\n mov r0,r1\n shlr2 r1\n shlr2 r1\n shlr r1\n mov #63,r2\n and r2,r1 ; green 0..63\n mul.l r6,r1\n sts macl,r1\n add r1,r9\n mov #31,r2\n and r2,r0 ; blue 0..31\n mul.l r6,r0\n sts macl,r0\n add r0,r10\n dt r11\n bf/s or_loop\n add r6,r3\n mov r8,r4 ; the means\n bsr wm_div\n mov r3,r5\n mov r0,r8\n mov r9,r4\n bsr wm_div\n mov r3,r5\n mov r0,r9\n mov r10,r4\n bsr wm_div\n mov r3,r5\n mov r0,r10\n mov r8,r5 ; the highest channel (on green's 0..63)\n shll r5\n cmp/hi r5,r9\n bf or_m1\n mov r9,r5\nor_m1:\n mov r10,r1\n shll r1\n cmp/hi r5,r1\n bf or_m2\n mov r1,r5\nor_m2:\n tst r5,r5\n bf or_m3\n mov #1,r5\nor_m3:\n mov.l R_full,r4 ; f = (63 << 8) / highest\n bsr wm_div\n nop\n mov.l @(4,r15),r1 ; the flag: dimmed\n tst r1,r1\n bt or_bright\n mov #PLAYED_DIM8,r1\n extu.b r1,r1\n mul.l r1,r0\n sts macl,r0\n shlr8 r0\nor_bright:\n mov r0,r11\n mul.l r11,r8 ; red\n sts macl,r8\n shlr8 r8\n mov #31,r1\n cmp/hi r1,r8\n bf or_r\n mov r1,r8\nor_r:\n mul.l r11,r9 ; green\n sts macl,r9\n shlr8 r9\n mov #63,r1\n cmp/hi r1,r9\n bf or_g\n mov r1,r9\nor_g:\n mul.l r11,r10 ; blue\n sts macl,r10\n shlr8 r10\n mov #31,r1\n cmp/hi r1,r10\n bf or_b\n mov r1,r10\nor_b:\n mov r8,r0 ; RGB565\n shll8 r0\n shll2 r0\n shll r0\n shll2 r9\n shll2 r9\n shll r9\n or r9,r0\n or r10,r0\nor_out:\n lds.l @r15+,pr\n mov.l @r15+,r3\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\nR_work: .long WORK\nR_workoff: .long WORK_OFF\nR_cols: .long OV_COLUMNS\nR_pal: .long PALETTE\nR_full: .long 0x3F00\n\n'''\n\nLAYOUT = (('SLOTS', 16 * SLOTS_N), ('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, prev_after=0):\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, 'TRIES': TRIES, '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, 'PREV_AFTER': prev_after,\n 'PLAYED_DIM8': round(PLAYED_DIM * 256) - 256 if PLAYED_DIM * 256 > 127.5 else round(PLAYED_DIM * 256),\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 'WM_KEYDEV': WM_KEYDEV, 'WM_KEYID': WM_KEYID,\n 'WM_SLOTI': WM_SLOTI, 'SLOTS_N': SLOTS_N, 'TRACK_INFO': TRACK_INFO, 'WM_REDRAW': WM_REDRAW,\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('<I', out, a - BASE)[0]\n if struct.unpack_from('<H', stock, ROW_LOAD - BASE)[0] != 0x6281 or word(FF70_LIT) != 0xFF70:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: the renderer is not the expected one')\n pal_at = word(PALETTE_LIT)\n if not BASE <= pal_at < BASE + len(out) or struct.unpack_from('<H', out, COL_AT + 2 - BASE)[0] != 0xE67F:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: arnoldasr\\'s waveform code is not in')\n palette = struct.unpack_from('<128H', out, pal_at - BASE)\n blues = struct.unpack_from('<8H', stock, STOCK_DETAIL - BASE)\n prev_fetch = word(FETCH_LIT)\n if struct.unpack_from('<I', stock, FETCH_LIT - BASE)[0] != 0x08978680 or prev_fetch == 0x08978680:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: arnoldasr\\'s wave fetch hook is not in')\n prev_bar = word(BAR_LITS[0])\n if any(struct.unpack_from('<I', stock, a - BASE)[0] != BAR_DRAW or word(a) != prev_bar for a in BAR_LITS):\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: the overview callers\\' bar literals are not the expected ones')\n prev_after = word(AFTER_LIT)\n if struct.unpack_from('<I', stock, AFTER_LIT - BASE)[0] != 0x08BD2A54 or prev_after == 0x08BD2A54:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: arnoldasr\\'s preparer hook is not in')\n code, labels = routine(pal_at, prev_fetch, prev_bar, prev_after)\n count = word(h.UT_COUNT)\n table_at = word(h.UT_LITS[0])\n if any(word(lit) != table_at for lit in h.UT_LITS) or not h.UT_ITEMS <= count < 24:\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: the UTILITY table is not as expected')\n src = h.UT_TABLE if table_at == h.UT_RAM else table_at\n items = bytes(out[src - BASE:src - BASE + count * h.UT_ITEM]) + item(labels)\n assert labels['ITEMS'] + len(items) <= WM_END, 'advanced_waveforms: past its area'\n if len(out) < WM_END - BASE:\n out += bytes(WM_END - BASE - len(out))\n if any(out[WM_BASE - BASE:WM_END - BASE]):\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: its area 0x%08x.. is not free' % WM_BASE)\n\n def place(addr, data):\n out[addr - BASE:addr - BASE + len(data)] = data\n\n place(WM_SETTING, bytes((DEFAULT, 0, 0xFF))) # (WM_BANDS, WM_COUNT, WM_TAG, WM_EXT 0)\n place(WM_CODE, code)\n dim = lambda c: tuple(round(v * PLAYED_DIM) for v in c)\n place(labels['COLOURS'], struct.pack('<8H', rgb565(HIGH), rgb565(MID), rgb565(LOW), 0,\n rgb565(dim(HIGH)), rgb565(dim(MID)), rgb565(dim(LOW)), 0))\n place(labels['BANDS'], bands(palette))\n place(labels['BLUEMAP'], bluemap(palette, blues))\n place(labels['OV_RGB'], bytes(out[OVERVIEW_IMG - BASE:OVERVIEW_IMG - BASE + 24]))\n place(labels['OV_BLUE'], bytes(stock[OVERVIEW_IMG - BASE:OVERVIEW_IMG - BASE + 24]))\n place(labels['LABEL'], LABEL.encode('utf-16-le') + b'\\0\\0')\n for i, v in enumerate(VALUES):\n place(labels['VALUE%d' % i], v.encode('utf-16-le') + b'\\0\\0')\n place(labels['LIST'], struct.pack('<%dI' % len(VALUES), *(labels['VALUE%d' % i] for i in range(len(VALUES)))))\n place(labels['ITEMS'], items)\n for lit in h.UT_LITS:\n struct.pack_into('<I', out, lit - BASE, labels['ITEMS'])\n struct.pack_into('<I', out, h.UT_COUNT - BASE, count + 1)\n # COL_AT..COL_END: call wm_col, then on past the stock pick\n stub = struct.pack('<6H', 0xD102, 0x410B, 0x0009, 0xA00B, 0x0009, 0x0009) # mov.l @(lit),r1; jsr @r1; nop; bra COL_END; nop; nop\n stub += struct.pack('<I', labels['wm_col'])\n stub += struct.pack('<H', 0x0009) * ((COL_END - COL_AT - len(stub)) // 2)\n assert len(stub) == COL_END - COL_AT\n place(COL_AT, stub)\n struct.pack_into('<H', out, ROW_LOAD - BASE, 0x6285)\n struct.pack_into('<I', out, FETCH_LIT - BASE, labels['wm_fetch'])\n struct.pack_into('<I', out, AFTER_LIT - BASE, labels['wm_after'])\n # the overview: a colour per row from OVCOL, filled by wm_ov once a column\n if word(OV_PLAYED_LIT) != OV_PLAYED or word(OV_TABLE_LIT) != OVERVIEW or \\\n any(struct.unpack_from('<H', out, a - BASE)[0] != 0x62E1 for a in OV_LOADS):\n raise SystemExit('patch_cdj900nxs: advanced_waveforms: the overview loop is not the expected one')\n struct.pack_into('<I', out, OV_PLAYED_LIT - BASE, labels['wm_ov'])\n struct.pack_into('<I', out, OV_TABLE_LIT - BASE, labels['OVCOL'])\n for a in OV_LOADS:\n struct.pack_into('<H', out, a - BASE, 0x62E5)\n for a in BAR_LITS: # the overview drawn again after a mode change\n struct.pack_into('<I', out, a - BASE, labels['wm_ovtick'])\n return bytes(out)\n","mods/cdj900nxs/widewave.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"WIDE WAVEFORM for the CDJ-900NXS 1.31: the detail waveform runs on to the\nright edge of the display, where the ZOOM/GRID wheel was; the wheel is gone,\nand a small box above the waveform, over the BPM display, says which the jog\ndoes (ZOOM or GRID). Our own mod; mods/patch_cdj900nxs.py applies it as\n`wide_waveform`, after advanced_waveforms and hotcuekeys.\n\n * The renderer 0x08BCFA34 draws the waveform into a pixel buffer (stock\n 0x0BA7DEF4, 536 x 124 RGB565, rows of 1072 bytes) and copies it into its\n surface (*0x11106BAC, made once at x 132, y 141, 536 x 124). Every width\n in it, and in the cue marker copy 0x08BCF476, is a constant: the width\n (0x218) and the last column (0x217), the row stride (0x430) and row\n offsets (n * 0x430, as literals or mov/shll8/add). All of them become\n WIDTH (and WIDTH * 2); WIDTH - 536 more columns show on the right.\n * The playing position stays at column 268, so the extra columns are\n ahead of it. The renderer's last column, stock: the columns ahead of\n the position up to the track's end + 267 while that is under 268, else\n 536; now under WIDTH - 268, else WIDTH (END_AT, rewritten in place).\n * The buffer does not fit where it was (variables follow it) nor in the\n free RAM after the image, so it is malloc'd once: ww_memset takes the\n renderer's memset literal (MEMSET_LIT, also used for a 52-byte stack\n clear), and for the buffer's clear (its size) gets the block the first\n time and writes it into the three literals that hold the buffer\n (BUF_LITS, read only after that clear). No memory: nothing is drawn\n (on to the renderer's end, past the copy-out), and it is asked again\n the next time.\n * advanced_waveforms' wm_col has its own literal for the centre row's\n offset (0xFF70 = 61 rows); it is changed with the stock one.\n * The wheel is a container of four bitmap widgets (resource 0x1FE ZOOM,\n 0x1E1-0x1E3 GRID and its arrows), one drawn at a time. ww_render (the\n bitmap widgets' draw method, chained in front of hotcuekeys' hk_render)\n draws a copy of the wheel's widget instead, at BOX_TL..BOX_BR in the\n wheel's coordinates (over the BPM display, above the waveform), with\n resource RES_ZOOM or RES_GRID. ww_res (the draw's resolver literal,\n in front of hk_res) gives their records: built into the image, 88 x 20,\n the disabled QUANTIZE box's grey frame and the wheel's own ZOOM / GRID\n lettering in grey (magenta 0xF81F is transparent).\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 = '0.1.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nWW_BASE = 0x08FE5000 # (0x08FE4000..0x08FE5000 after played's area left free)\nWW_END = 0x08FE8000\nWW_CODE = WW_BASE\nWW_VARS = WW_BASE + 0x400 # +0 the buffer (0: not yet), then the widget copy\nWW_WIDGET = WW_VARS + 0x20 # 0x50 bytes\nWW_RECS = WW_VARS + 0x80 # two resource records of 44 bytes\nWW_PIX = WW_BASE + 0x600 # the two boxes' pixels, 32-byte aligned\n\nSTOCK_W = 536\nWIDTH = 624 # a multiple of 8, as the firmware's surfaces\nROWS = 124\nSTRIDE = WIDTH * 2\nPLAY_COL = 268 # the playing position's column (unchanged)\n\nSTOCK_BUF = 0x0BA7DEF4\nBUF_LITS = (0x08BCF778, 0x08BCFD04, 0x08BD0484)\nMEMSET_LIT = 0x08BCFCF0\nMEMSET = 0x08C28038\nMALLOC = 0x08A5C6EC\nRENDER_END = 0x08BD02C0 # the renderer's end: done, its frame popped\nFPSCR_MASK = 0xFFE7FFFF\n\n# literals holding n rows (n * 1072): n\nROW_LITS = {0x08BCF780: 120, 0x08BCFD08: 124, 0x08BCFF4C: 61, 0x08BD019C: 60,\n 0x08BD01A0: 62, 0x08BD01A4: 61, 0x08BD01AC: 52}\nWM_FF70 = (0x08FDB000, 0x08FDD800) # advanced_waveforms' area, its 0xFF70 literal\n\n# mov #hi,Rn ... shll8 Rn ... add #lo,Rn building a value: (mov, add, value)\nS, W = STRIDE, WIDTH\nPAIRS = [\n # cue marker copy 0x08BCF476: last column, 7 rows down, a row up, 120 rows\n (0x08BCF506, 0x08BCF50A, W - 1), (0x08BCF526, 0x08BCF530, 7 * S), (0x08BCF5A6, 0x08BCF5B0, -S),\n (0x08BCF5D0, 0x08BCF5D4, W - 1), (0x08BCF600, 0x08BCF60C, 7 * S), (0x08BCF670, 0x08BCF67A, -S),\n (0x08BCF69A, 0x08BCF69E, W - 1), (0x08BCF734, 0x08BCF73C, -S),\n # the renderer: its surface's width; a column's rows; beat ticks; loop and\n # cue lines; the mirrored half; the playing position's line\n (0x08BCFA98, 0x08BCFAA2, W), (0x08BCFDDE, 0x08BCFDE2, -S),\n (0x08BCFF0E, 0x08BCFF1A, 8 * S), (0x08BCFF26, 0x08BCFF2E, S),\n (0x08BCFF6A, 0x08BCFF74, 8 * S), (0x08BCFF80, 0x08BCFF88, S),\n (0x08BCFF9C, 0x08BCFFA6, 8 * S), (0x08BCFFB2, 0x08BCFFBA, S),\n (0x08BCFFCE, 0x08BCFFDA, 14 * S), (0x08BCFFD4, 0x08BCFFE4, -S),\n (0x08BD0034, 0x08BD003E, 14 * S), (0x08BD003A, 0x08BD0048, -S),\n (0x08BD0066, 0x08BD0070, 14 * S), (0x08BD006C, 0x08BD0078, -S),\n (0x08BD009A, 0x08BD00A6, 14 * S), (0x08BD00A0, 0x08BD00B0, -S),\n (0x08BD00E6, 0x08BD00F0, S), (0x08BD00F8, 0x08BD0102, -2 * S), (0x08BD00FC, 0x08BD0104, 2 * S),\n (0x08BD0110, 0x08BD0114, 9 * S), (0x08BD0162, 0x08BD0170, S), (0x08BD01C4, 0x08BD01D8, S),\n # the copy into the surface: columns, two rows, a row\n (0x08BD0216, 0x08BD022A, W), (0x08BD021A, 0x08BD022E, 2 * S), (0x08BD021C, 0x08BD0230, S),\n (0x08BD0264, 0x08BD0268, S),\n]\n\n# the renderer's last column (0x08BCFC46..5B): r5 the position's column, r6\n# the track end's; out r6 = the last column + 1\nEND_AT = 0x08BCFC46\n\n# the bitmap widgets' draw method and its resolver (as hotcuekeys knows them)\nDRAW_METHOD = 0x080B27DC\nDRAW = 0x08B2F12A\nDRAW_RES_LIT = 0x08B2F384\nRESOLVE = 0x08B2E2E4\nRES_TABLE = 0x00100000 # resource records in the image (offset), 44 bytes\nRES_BASE = 0x08100000 # their pixels' base\nRES_ZOOM_WHEEL = 0x1FE\nRES_GRID_WHEEL = (0x1E1, 0x1E2, 0x1E3)\nRES_OFF_BOX = 0x1A9 # QUANTIZE's grey outline: the disabled look\nRES_ZOOM = 0x7F10 # ours, only ww_res knows them\nRES_GRID = 0x7F11\nBOX_W, BOX_H = 88, 20 # 85 shown (the BPM display's width), 3 transparent\nBOX_SHOWN = 85\n# in the wheel's coordinates (its widget: 620,30 - 676,200): over the BPM\n# display, beside the second bar counter, 10 px above the waveform\nBOX_TL = (62 << 16) | 582\nBOX_BR = (62 + BOX_H << 16) | (582 + BOX_W)\nFRAME = 0x4208 # the disabled box's grey\nINK = (132, 130, 132) # the lettering's grey\nKEY = 0xF81F\n# where the wheel's bitmaps have their lettering: (resource, rows, first column)\nLETTERS = {'ZOOM': (RES_ZOOM_WHEEL, (46, 57), 8), 'GRID': (0x1E1, (66, 76), 13)}\n\nSOURCE = r'''\n; ---- ww_memset: memset(r4, r5, r6) through the renderer's literal; for its\n; buffer's clear (r6 the buffer's size) the buffer is ours (malloc'd once)\nww_memset:\n mov.l M_size,r0\n cmp/eq r0,r6\n bf wm_plain\n mov.l M_vars,r1\n mov.l @r1,r0\n tst r0,r0\n bf wm_have\n mov.l r4,@-r15 ; the first time: the block, its literals\n mov.l r5,@-r15\n mov.l r6,@-r15\n sts.l pr,@-r15\n mov.l M_alloc,r4\n mov.l M_malloc,r1\n jsr @r1\n nop\n lds.l @r15+,pr\n mov.l @r15+,r6\n mov.l @r15+,r5\n mov.l @r15+,r4\n tst r0,r0\n bt wm_none\n add #31,r0 ; 32-byte aligned\n mov #-32,r1\n and r1,r0\n mov.l M_vars,r1\n mov.l r0,@r1\n mov.l M_lit0,r1\n mov.l r0,@r1\n mov.l M_lit1,r1\n mov.l r0,@r1\n mov.l M_lit2,r1\n mov.l r0,@r1\nwm_have:\n mov r0,r4\nwm_plain:\n mov.l M_memset,r1\n jmp @r1\n nop\nwm_none:\n sts fpscr,r1 ; no memory: on at the renderer's end, as\n mov.l M_mask,r2 ; it goes on after the memset\n and r2,r1\n lds r1,fpscr\n mov.l M_end,r1\n lds r1,pr\n rts\n nop\n\n; ---- ww_render: the bitmap widgets' draw method (r4, r5 the widget, r6, r7);\n; the wheel's bitmaps give way to our box\nww_render:\n mov #68,r0\n mov.l @(r0,r5),r1 ; the widget's resource\n mov.l R_zoom,r3\n mov.l R_zoomw,r2\n cmp/eq r2,r1\n bt wr_box\n mov.l R_grid,r3\n mov.l R_gridw,r2\n mov r1,r0\n sub r2,r0\n mov #3,r2\n cmp/hs r2,r0 ; 0x1E1..0x1E3?\n bf wr_box\n mov.l R_prev,r2\n jmp @r2\n nop\nwr_box:\n mov.l R_widget,r1 ; a copy, visible, our place and resource\n mov #20,r2\nwr_copy:\n mov.l @r5+,r0\n mov.l r0,@r1\n dt r2\n bf/s wr_copy\n add #4,r1\n mov.l R_widget,r5\n mov #1,r0\n mov.l r0,@(12,r5)\n mov.l R_tl,r0\n mov.l r0,@(24,r5)\n mov.l R_br,r0\n mov.l r0,@(28,r5)\n mov #68,r0\n mov.l r3,@(r0,r5)\n mov.l R_prev,r2\n jmp @r2\n nop\n\n; ---- ww_res: the draw's resource resolver: r4 the resource -> 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('<HH', stock, rec + 4)\n off = RES_TABLE + struct.unpack_from('<I', stock, rec + 0x20)[0]\n px = struct.unpack_from('<%dH' % (w * h), stock, off)\n rows = [[(px[y * w + x] >> 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('<HH', rec, 4, BOX_W, BOX_H)\n rec[3] = rec[24] # the format (2: RGB565)\n struct.pack_into('<I', rec, 12, pixels_at) # the pixels\n struct.pack_into('<H', rec, 16, BOX_W * 2) # a row's bytes\n struct.pack_into('<I', rec, 20, 0) # no alpha plane\n struct.pack_into('<I', rec, 0x20, pixels_at - RES_BASE)\n assert rec[24] == 2 and rec[25] == 0\n return bytes(rec)\n\n\ndef routine(prev_memset, prev_draw, prev_res, rec_zoom, rec_grid):\n syms = {\n 'BUF_SIZE': ROWS * STRIDE, 'WW_VARS': WW_VARS, 'WW_WIDGET': WW_WIDGET, 'MALLOC': MALLOC,\n 'PREV_MEMSET': prev_memset, 'BUF_LIT0': BUF_LITS[0], 'BUF_LIT1': BUF_LITS[1], 'BUF_LIT2': BUF_LITS[2],\n 'FPSCR_MASK': FPSCR_MASK, 'RENDER_END': RENDER_END, 'RES_ZOOM_WHEEL': RES_ZOOM_WHEEL,\n 'RES_GRID_WHEEL': RES_GRID_WHEEL[0], 'RES_ZOOM': RES_ZOOM, 'RES_GRID': RES_GRID,\n 'PREV_DRAW': prev_draw, 'PREV_RES': prev_res, 'BOX_TL': BOX_TL, 'BOX_BR': BOX_BR,\n 'REC_ZOOM': rec_zoom, 'REC_GRID': rec_grid,\n }\n return sh4asm.assemble(SOURCE, WW_CODE, syms)\n\n\ndef patch(app, stock):\n \"\"\"The application with WIDE WAVEFORM; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n half = lambda buf, a: struct.unpack_from('<H', buf, a - BASE)[0]\n word = lambda buf, a: struct.unpack_from('<I', buf, a - BASE)[0]\n\n def fail(what):\n raise SystemExit('patch_cdj900nxs: wide_waveform: %s' % what)\n\n # the constants: each pair as in the stock and untouched by other mods\n for mov_at, add_at, value in PAIRS:\n mov, add = half(stock, mov_at), half(stock, add_at)\n n = (mov >> 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('<H', out, mov_at - BASE, 0xE000 | n << 8 | (hi & 0xFF))\n struct.pack_into('<H', out, add_at - BASE, 0x7000 | n << 8 | (lo & 0xFF))\n for at, rows in ROW_LITS.items():\n if word(stock, at) != rows * STOCK_W * 2 or word(out, at) != word(stock, at):\n fail('the literal at 0x%08x is not the expected one' % at)\n struct.pack_into('<I', out, at - BASE, rows * STRIDE)\n lo, hi = WM_FF70\n if len(out) >= 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('<I', out, a - BASE, 61 * STRIDE)\n end = out[END_AT - BASE:END_AT - BASE + 22]\n if end != stock[END_AT - BASE:END_AT - BASE + 22] or \\\n struct.unpack('<11H', end)[1:4] != (0x3642, 0x8904, 0xE501):\n fail('the last-column code is not the expected one')\n out[END_AT - BASE:END_AT - BASE + 22] = end_code()\n for at in BUF_LITS:\n if word(stock, at) != STOCK_BUF or word(out, at) != STOCK_BUF:\n fail('the buffer literal at 0x%08x is not the expected one' % at)\n if word(stock, MEMSET_LIT) != MEMSET or word(stock, DRAW_METHOD) != DRAW or \\\n word(stock, DRAW_RES_LIT) != RESOLVE:\n fail('the memset, draw method or resolver is not the expected one')\n for res in (RES_ZOOM_WHEEL,) + RES_GRID_WHEEL:\n if struct.unpack_from('<HH', stock, RES_TABLE + res * 44 + 4) != (56, 170):\n fail('the wheel resource 0x%x is not as expected' % res)\n\n # our code, records and boxes\n pix_zoom = WW_PIX\n pix_grid = (WW_PIX + BOX_W * BOX_H * 2 + 31) & ~31\n rec_zoom, rec_grid = WW_RECS, WW_RECS + 44\n code, labels = routine(word(out, MEMSET_LIT), word(out, DRAW_METHOD), word(out, DRAW_RES_LIT),\n rec_zoom, rec_grid)\n assert WW_CODE + len(code) <= WW_VARS and pix_grid + BOX_W * BOX_H * 2 <= WW_END, \\\n 'wide_waveform: past its area'\n if len(out) < WW_END - BASE:\n out += bytes(WW_END - BASE - len(out))\n if any(out[WW_BASE - BASE:WW_END - BASE]):\n fail('its area 0x%08x.. is not free' % WW_BASE)\n put = lambda at, data: out.__setitem__(slice(at - BASE, at - BASE + len(data)), data)\n put(WW_CODE, code)\n put(rec_zoom, record(stock, pix_zoom))\n put(rec_grid, record(stock, pix_grid))\n put(pix_zoom, box(stock, 'ZOOM'))\n put(pix_grid, box(stock, 'GRID'))\n struct.pack_into('<I', out, MEMSET_LIT - BASE, labels['ww_memset'])\n struct.pack_into('<I', out, DRAW_METHOD - BASE, labels['ww_render'])\n struct.pack_into('<I', out, DRAW_RES_LIT - BASE, labels['ww_res'])\n return bytes(out)\n"} |