diff --git a/assets/bpmx2.png b/assets/bpmx2.png deleted file mode 100644 index 4e54207..0000000 Binary files a/assets/bpmx2.png and /dev/null differ diff --git a/assets/versions.js b/assets/versions.js index 03a11e2..9ff7751 100644 --- a/assets/versions.js +++ b/assets/versions.js @@ -1,15 +1,8 @@ // Generated by tools/make_builder_data.py from tools/changelog.json and the mods' versions. window.CDJ_VERSIONS = { - "site": "1.10.0", + "site": "1.9.0", "date": "2026-10-21", "log": [ - { - "version": "1.10.0", - "date": "2026-10-21", - "changes": [ - "New mod: BPM ×2 / ÷2 (bpmhalfdouble), the track's BPM and beat grid doubled or halved from the deck, kept on the USB stick." - ] - }, { "version": "1.9.0", "date": "2026-10-21", @@ -205,7 +198,7 @@ window.CDJ_VERSIONS = { "cuenames": "0.1.1", "endwarn": "0.1.0", "played": "0.3.1", - "bpmhalfdouble": "0.2.0", + "bpmhalfdouble": "0.1.0", "wide_waveform": "0.1.0", "credit": "1.1.0" }, @@ -416,15 +409,6 @@ window.CDJ_VERSIONS = { "The big waveform 624 columns wide instead of 536, to the right edge; the extra columns are ahead of the playing position. The ZOOM/GRID wheel replaced by a grey ZOOM / GRID box over the BPM display." ] } - ], - "bpmhalfdouble": [ - { - "version": "0.2.0", - "date": "2026-10-21", - "changes": [ - "TAG TRACK held + CALL ◀ / ▶ halves or doubles the loaded track's BPM and beat grid; the track is loaded again with it, so display, beat ticks, bars, quantize and Pro DJ Link follow. Kept on the USB stick in PIONEER/PIONAARR.BPM. Results under 60 or over 240 BPM refused." - ] - } ] } }; diff --git a/builder.html b/builder.html index 14450c1..aedcc9f 100644 --- a/builder.html +++ b/builder.html @@ -6,12 +6,12 @@ Pionaarr Mods Builder - - - - - - + + + + + + diff --git a/builder/py/bundle.json b/builder/py/bundle.json index a9acad2..339cff2 100644 --- a/builder/py/bundle.json +++ b/builder/py/bundle.json @@ -1 +1 @@ -{"mods/patch_cdj900nxs.py":"#!/usr/bin/env python3\n# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"Apply arnoldasr's CDJ-900NXS 1.31 mods to the unpacked MAIN image.\n\n usage: patch_cdj900nxs.py [,...]\n patch_cdj900nxs.py --list\n\nThe mods come from https://github.com/arnoldasr/cdj900nxs, read as data from a\ncheckout of it (CDJ900NXS_MODS_SRC, default mods/cdj900nxs/upstream, which is\nnot committed: that repository carries no licence). Nothing of its Python is\nimported; what its build_firmware.py does is redone here:\n\n extended_bpm the BPM with hundredths: the stock tenths moved into a new\n small digit above, the lower one showing hundredths.\n phasemeter the two-line phase meter (orange master, blue this deck, a\n marker each) in place of the four boxes, chosen in UTILITY >\n PHASE METER (SLIP FLASHING relabelled; mods/phase-meter-\n utility.cdjmod.json).\n advanced_waveforms (was rgb_waveform; based on arnoldasr's, updated and\n maintained by us) the big waveform in rekordbox's RGB colours\n (1.0.1: also when its colour detail is a little short,\n cdj900nxs/wavefix.py), and the overview's colour table as a red-to-white\n gradient (mods/waveform-colour.cdjmod.json); with our\n UTILITY > WAVEFORM: BLUE / RGB / 3BAND (cdj900nxs/wavemodes.py,\n left out with public=True).\n\n The three are arnoldasr's code mod (patches/mods.json): hooks\n in the application redirected to SH-4 code appended after it\n (0x08FC1000), plus an asset blob at 0x08FC2D00 generated from\n the stock image (the resource table extended by four phase-meter\n images, then the RGB waveform palette and LUT). Upstream tested\n it as one build, and as one without the RGB waveform\n (patches/bpm-phase-meter.json); one of those two is built and\n checked against its hash first, then the hooks of a feature not\n chosen get their original bytes back. Their shared startup hook\n (0x08C071FC) installs the BPM digits and the phase markers: the\n call of the one not chosen is taken out there.\n beatjump ours, not arnoldasr's (cdj900nxs/beatjump.py): TRACK SEARCH\n jumps 1/2/4/8 beats along the grid, chosen in UTILITY > BEAT JUMP,\n made at once in the DSP's decoded audio (cdj900nxs/dspjump.py, a\n patch of the audio DSP's code),\n which replaces SCREEN SAVER. hotcuekeys' engine makes the jumps,\n so it needs hotcuekeys and cuecolours. Needs no upstream checkout.\n cuecolours ours (cdj900nxs/cuecolours.py): memory cues drawn in their\n rekordbox colour on the overview and the detail waveform, the\n rekordbox hot cues shown as lettered boxes.\n Needs no upstream checkout.\n hotcuekeys ours (cdj900nxs/hotcuekeys.py): UTILITY > HOT CUE KEYS (a new,\n 18th item); with it ON, QUANTIZE briefly pressed switches a hot\n cue key mode shown in a HOT KEYS box, held it is QUANTIZE. In\n the mode the top three BEAT DIVIDE keys play hot cues A-C (held: a\n gate), set one (empty key held while paused) or delete one\n (DELETE held). The hot cues come from cuecolours.\n ledsaver ours (cdj900nxs/ledsaver.py): UTILITY > LED ANIMATION (one more\n item, after the others); with it ON, while the screen saver dims\n the display the button LEDs and the jog's rings play a loop.\n keynotation ours (cdj900nxs/keynotation.py): UTILITY > KEY NOTATION (one\n more item): every track's key in Camelot, Open Key or classic\n notation, whatever rekordbox exported.\n usbautoload ours (cdj900nxs/usbautoload.py): UTILITY > USB AUTO LOAD (one\n more item): a stick's MY SETTINGS are loaded when the player finds\n them, without waiting for MENU.\n quicksearch ours (cdj900nxs/quicksearch.py): BROWSE held opens SEARCH,\n BROWSE twice the list the playing track came from.\n (phasemeter, in unreleased builds, also gets ours from cdj900nxs/phasereverse.py:\n UTILITY > PHASE METER OFF / ON / REVERSE, and the beat countdown\n moved clear of the line meter; `phasereverse` is an old name for it.)\n wide_waveform ours (cdj900nxs/widewave.py): the big waveform runs on to the\n right edge (624 columns, the extra ones ahead of the position); the\n ZOOM/GRID wheel gives way to a small grey ZOOM / GRID box.\n credit ours (cdj900nxs/credit.py): the boot screen says \"Pionaarr Mods\n 1.0\" where it said \"Ver 1.31\".\n\nEvery patch is applied only where the image holds the expected original\nbytes, and arnoldasr's code is first built as one of the builds he tested\nand checked against its hash.\n\nOur lzss_decode.py runs 54 bytes past the end of the packed stream, so\nmain_unpacked.bin is the 1.31 application plus a tail the firmware never\nreads; the application is cut back to its own length first.\n\"\"\"\nimport hashlib\nimport json\nimport os\nimport struct\nimport sys\n\nHERE = os.path.dirname(os.path.abspath(__file__))\nsys.path.insert(0, os.path.join(HERE, 'cdj900nxs'))\nimport beatjump # noqa: E402\nimport credit # noqa: E402\nimport dspjump # noqa: E402\nimport cuecolours # noqa: E402\nimport hotcuekeys # noqa: E402\nimport keynotation # noqa: E402\nimport ledsaver # noqa: E402\nimport usbautoload # noqa: E402\nimport quicksearch # noqa: E402\nimport phasereverse # noqa: E402\nimport wavemodes # noqa: E402\nimport wavefix # noqa: E402\nimport cuenames # noqa: E402\nimport endwarn # noqa: E402\nimport played # noqa: E402\nimport bpmhalfdouble # 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 'bpmhalfdouble': 'BPM x2 / /2: TAG TRACK held + CALL < halves, CALL > doubles the track\\'s grid and BPM, '\n 'kept on the USB (needs quicksearch; ours: cdj900nxs/bpmhalfdouble.py)',\n 'wide_waveform': 'the big waveform to the right edge, the ZOOM/GRID wheel replaced by a small ZOOM / GRID box '\n '(ours: cdj900nxs/widewave.py)',\n 'credit': 'boot screen says \"Pionaarr Mods 1.0\" instead of \"Ver 1.31\" (ours: cdj900nxs/credit.py)',\n}\n# every mod's version (Semantic Versioning); ours from their modules, arnoldasr's\n# split-up mods as packaged here\nVERSIONS = {\n 'extended_bpm': '1.0.0', 'phasemeter': '1.0.0', 'advanced_waveforms': wavemodes.VERSION, # with its WAVEFORM modes (wavefix: wavefix.VERSION)\n 'beatjump': beatjump.VERSION, 'cuecolours': cuecolours.VERSION, 'hotcuekeys': hotcuekeys.VERSION,\n 'ledsaver': ledsaver.VERSION, 'keynotation': keynotation.VERSION, 'usbautoload': usbautoload.VERSION,\n 'quicksearch': quicksearch.VERSION,\n 'cuenames': cuenames.VERSION, 'endwarn': endwarn.VERSION, 'played': played.VERSION, 'bpmhalfdouble': bpmhalfdouble.VERSION,\n 'wide_waveform': widewave.VERSION, 'credit': credit.VERSION,\n}\nMANIFESTS = {\n 'phasemeter': 'mods/phase-meter-utility.cdjmod.json',\n 'advanced_waveforms': 'mods/waveform-colour.cdjmod.json',\n}\n# arnoldasr's code mod split up: each feature's hooks in the application\nUPSTREAM = ('extended_bpm', 'phasemeter', 'advanced_waveforms')\nHOOKS = {\n 'extended_bpm': (0x08BD3E50, 0x08BD3ED8, 0x08BD3A48, 0x08BCBA60, 0x08BCBCFC),\n 'phasemeter': (0x08B2E2EE, 0x08C6A6CC, 0x08C6CCD0),\n 'advanced_waveforms': (0x089A061C, 0x08BC6830, 0x08BC6A7C, 0x08BCFD72, 0x08BCFF50),\n}\nSHARED_HOOKS = (0x08C071FC, 0x08B27BC8) # startup binding, arena reset: BPM and phase\nBPM_INSTALL_CALL = 0x08FC2444 # startup_bind's jsr of the BPM digits' installer\nPHASE_MARKERS_CALL = 0x08FC2450 # ... and of the phase markers' (startup_markers)\n\n\ndef source_dir():\n return os.environ.get('CDJ900NXS_MODS_SRC') or os.path.join(HERE, 'cdj900nxs', 'upstream')\n\n\ndef read_json(rel):\n path = os.path.join(source_dir(), rel)\n if not os.path.isfile(path):\n raise SystemExit('patch_cdj900nxs: %s missing -- check out '\n 'https://github.com/arnoldasr/cdj900nxs into %s'\n % (path, source_dir()))\n with open(path, encoding='utf-8') as f:\n return json.load(f)\n\n\ndef sha(data):\n return hashlib.sha256(data).hexdigest()\n\n\n# The asset blob, as tools/phase_assets.py builds it (same constants).\nTABLE, DESCRIPTOR, ORIGINAL_COUNT, TEMPLATE = 0x08100000, 0x2C, 0x2A1, 0x1C8\nDATA_BASE = 0x08FC2D00\nWIDTH, HEIGHT, MARKER_WIDTH, BAR = 200, 24, 8, 5\nKEY = 0xF81F\nORANGE_LINE, ORANGE_MARK = (120, 65, 20), (255, 140, 0)\nBLUE_LINE, BLUE_MARK = (40, 90, 120), (60, 170, 255)\nWAVE_COLOURS = [\n 448, 457, 47, 458, 449, 111, 495, 466, 175, 467, 487, 239, 303, 479, 367, 459, 456, 431,\n 468, 465, 475, 476, 474, 39, 477, 31, 471, 46, 469, 45, 23, 460, 109, 484, 110, 485, 103,\n 478, 483, 493, 494, 486, 95, 15, 450, 167, 492, 173, 174, 231, 115, 470, 238, 44, 237, 159,\n 295, 87, 108, 302, 51, 301, 491, 482, 359, 223, 366, 365, 236, 300, 179, 423, 461, 287, 430,\n 429, 172, 364, 473, 428, 351, 114, 151, 463, 299, 363, 415, 215, 427, 178, 490, 50, 279, 235,\n 79, 462, 451, 343, 511, 298, 362, 400, 52, 116, 426, 234, 407, 7, 143, 180, 207, 113, 233,\n 243, 481, 57, 271, 452, 417, 177]\nWHITENESS_COLOURS = [(150, 20, 20), (220, 40, 30), (255, 110, 20), (255, 190, 30), (170, 230, 60),\n (40, 210, 170), (60, 160, 255), (225, 240, 255)]\n\n\ndef rgb565(rgb):\n r, g, b = rgb\n return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)\n\n\ndef line_image(colour):\n px = [KEY] * (WIDTH * HEIGHT)\n for x in range(2, WIDTH - 2):\n px[12 * WIDTH + x] = rgb565(colour)\n for i in range(5):\n x = 2 + i * (WIDTH - 5) // 4\n for y in range(7, 18):\n px[y * WIDTH + x] = rgb565(colour)\n return px\n\n\ndef marker_image(colour):\n return [rgb565(colour) if 3 <= y <= 21 and x < BAR else KEY\n for y in range(HEIGHT) for x in range(MARKER_WIDTH)]\n\n\ndef rgb9(code):\n return tuple(((code >> shift) & 7) * 255 // 7 for shift in (6, 3, 0))\n\n\ndef wave_palette():\n colours = WHITENESS_COLOURS + [rgb9(c) for c in WAVE_COLOURS]\n return struct.pack('<128H', *[rgb565(c) for c in colours])\n\n\ndef wave_lut():\n def nearest(code):\n r, g, b = (code >> 6) & 7, (code >> 3) & 7, code & 7\n return min(range(len(WAVE_COLOURS)), key=lambda i: (\n (((WAVE_COLOURS[i] >> 6) & 7) - r) ** 2 + (((WAVE_COLOURS[i] >> 3) & 7) - g) ** 2\n + ((WAVE_COLOURS[i] & 7) - b) ** 2, i))\n return bytes(8 + nearest(code) for code in range(512))\n\n\ndef assets(app):\n images = [(WIDTH, line_image(ORANGE_LINE)), (WIDTH, line_image(BLUE_LINE)),\n (MARKER_WIDTH, marker_image(ORANGE_MARK)), (MARKER_WIDTH, marker_image(BLUE_MARK))]\n original = app[TABLE - BASE:TABLE - BASE + ORIGINAL_COUNT * DESCRIPTOR]\n template = original[TEMPLATE * DESCRIPTOR:(TEMPLATE + 1) * DESCRIPTOR]\n count = ORIGINAL_COUNT + len(images)\n table_bytes = count * DESCRIPTOR\n pixels_base = DATA_BASE + ((table_bytes + 31) & ~31)\n table, pixels = bytearray(original), bytearray()\n for i, (width, data) in enumerate(images):\n entry = bytearray(template)\n struct.pack_into(' mov #0,r0 (not installed)\n if 'phasemeter' not in names:\n call(PHASE_MARKERS_CALL, 0x410B, 0x0009) # jsr @r1 -> nop\n return bytes(out)\n\n\ndef apply_manifest(app, stock, name):\n doc = read_json(MANIFESTS[name])\n if doc['target']['application_sha256'] != APP_SHA256:\n raise SystemExit('patch_cdj900nxs: %s targets another application' % name)\n out = bytearray(app)\n for p in doc['patches']:\n off = int(p['offset'], 16) if isinstance(p['offset'], str) else p['offset']\n before, after = bytes.fromhex(p['expect']), bytes.fromhex(p['replace'])\n if len(before) != len(after) or off + len(before) > APP_SIZE:\n raise SystemExit('patch_cdj900nxs: %s: bad patch at 0x%x' % (name, off))\n if stock[off:off + len(before)] != before or app[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: %s: bytes at 0x%x are not the expected original'\n % (name, off))\n out[off:off + len(before)] = after\n return bytes(out)\n\n\n# old names still taken\nALIASES = {'rgb_waveform': 'advanced_waveforms', 'waveform3band': 'advanced_waveforms',\n 'phasereverse': 'phasemeter'}\n\n\ndef check_names(names):\n \"\"\"The names with old ones taken, every mod known and what each needs.\"\"\"\n names = list(dict.fromkeys(ALIASES.get(n, n) for n in names))\n unknown = [n for n in names if n not in MODS]\n if unknown:\n raise SystemExit('patch_cdj900nxs: unknown mod(s) %s; known: %s'\n % (', '.join(unknown), ', '.join(MODS)))\n if 'cuenames' in names and 'cuecolours' not in names:\n raise SystemExit('patch_cdj900nxs: cuenames needs cuecolours (its cue reader brings the comments)')\n if 'bpmhalfdouble' in names and 'quicksearch' not in names:\n raise SystemExit('patch_cdj900nxs: bpmhalfdouble needs quicksearch (it loads the track again for a change)')\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 'bpmhalfdouble' in names:\n app = bpmhalfdouble.patch(app, stock) # after the text draw chain (cuenames, played): first in it\n if 'phasemeter' in names:\n app = phasereverse.patch(app, stock) # PHASE METER: OFF / ON / REVERSE\n app = phasereverse.shift_bars(app, stock) # the beat countdown clear of the line meter\n if 'advanced_waveforms' in names:\n app = wavemodes.patch(app, stock) # UTILITY > WAVEFORM: BLUE / RGB / 3BAND\n if 'wide_waveform' in names:\n app = widewave.patch(app, stock) # after advanced_waveforms (its centre-row literal) and hotcuekeys (the draw method)\n if 'credit' in names:\n app = credit.patch(app, stock)\n return app\n\n\ndef patch(data, names, public=False):\n \"\"\"The patched application for the mods named, in a fixed order: stage 1\n (arnoldasr's) and stage 2 (ours). Everything is released now; `public`\n is still taken and changes nothing.\"\"\"\n names = check_names(names)\n stock = data[:APP_SIZE]\n if sha(stock) != APP_SHA256:\n raise SystemExit('patch_cdj900nxs: not the CDJ-900NXS 1.31 application')\n return patch_ours(patch_upstream(stock, names), stock, names)\n\n\ndef main():\n args = sys.argv[1:]\n if args == ['--list']:\n for name, what in MODS.items():\n print('%-13s %-6s %s' % (name, VERSIONS[name], what))\n return\n if len(args) != 3:\n raise SystemExit(__doc__)\n names = [n for n in args[2].replace(' ', ',').split(',') if n]\n with open(args[0], 'rb') as f:\n out = patch(f.read(), names)\n with open(args[1], 'wb') as f:\n f.write(out)\n print('patch_cdj900nxs: %s (%s), %d bytes, sha256 %s'\n % (args[1], ','.join(names), len(out), sha(out)))\n\n\nif __name__ == '__main__':\n main()\n","mods/cdj900nxs/beatjump.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"BEAT JUMP for the CDJ-900NXS 1.31: TRACK SEARCH jumps N beats along the\ntrack's beat grid, N set in UTILITY > BEAT JUMP (OFF/1/2/4/8, default 4).\n\nOur own mod, not arnoldasr's; mods/patch_cdj900nxs.py applies it as `beatjump`.\nEverything here was found by tracing the firmware in the emulator:\n\n * The UI's poller calls the panel decoder 0x08B63278 (through its literal\n at 0x08B622A8), which turns report byte 0x12 bits 0x04 / 0x08 of the\n frame at 0x0B2E8280 into the key bits TRACK - (0x0B2E82C6 bit 0x01) and\n TRACK + (0x0B2E82C5 bit 0x80); later readers (0x08B630D0) turn those\n into the track search. The literal now points at bj_poll, which runs the\n decoder and then, with BEAT JUMP on and a beat grid loaded, takes both\n key bits out again: the track search never starts, so neither the UI nor\n the player changes track, and a press is a jump instead. (Clearing the\n report byte before the decoder instead lost a race with the panel task\n refilling the frame.)\n * The jump is made by hotcuekeys' engine (hk_engine's he_bj, which is\n why beatjump needs hotcuekeys and cuecolours): bj_poll writes the\n target in 1/150 s with bit 31 set to hotcuekeys.BJ_REQ, the tick of\n the press to BJ_TICK and the jump's length in ms to BJ_DELTA, unless a\n jump is still waiting. The engine has the DSP move its playing head by\n that many 1/75 s slots inside the audio it has already decoded\n (dspjump.py, a patch of the audio DSP): at once, gapless, the CUE point\n stays, playing, paused or at cue standby (standing, the UI is left\n paused, so PLAY plays on from there). Only where the DSP has not that\n much audio decoded does it refuse: playing, the engine then loads the\n target into the beat jumps' own DSP cue slot (hotcuekeys.BJ_SLOT) the\n old way, moved on by the time such a load takes so the deck comes back\n in phase; standing, the press is dropped (the old way there, a memory\n CALL, left the UI waiting for good after the third or fourth).\n [Since: no fallback at all; the slot load left the deck standing. A\n press the DSP cannot jump now (it keeps dspjump.MARGIN decoded ahead, so\n the time needed grows with the setting) or made while a jump still\n waits is ignored: nothing is put off, the next press counts again.]\n * The setting is kept in the player's settings journal (hotcuekeys'\n hk_save2/hk_load2 through HK_API, id SAVE_ID): the first poll takes the\n last saved value (else DEFAULT, 4 beats) into the setting's word, a\n change is written back. The firmware itself kept SCREEN SAVER's ON/OFF\n only, so BEAT JUMP came back as 1 after a restart.\n * The grid is read as arnoldasr's phase meter reads it, from the player\n context 0x099445DC: track *(ctx+0x20), X = *(track+0xC4), grid\n *(X+0x104), timestamp adjustment *(X+0x10C); 0x08A27D4C(grid, adj,\n pos_ms) gives 1 + the index of the beat at or before pos_ms, 0x08A0EFB6\n (record, adj) a beat's time in ms. The position in ms is ctx+0x2CC.\n * A jump keeps the position's offset within its beat: target = pos +\n t[i +/- N] - t[i]. Before the first beat (a track just loaded stands at\n its CUE, often a hair before beat 1) i is beat 0. A press whose target\n would leave the grid does nothing. With the setting OFF, or no grid, TRACK SEARCH is untouched.\n\nUTILITY > SCREEN SAVER (item 14, a word at 0x0A4DD270 + 0x1760, values\nOFF/ON, factory default ON = 1) becomes BEAT JUMP: its label in all 19\nlanguages, five values OFF/1/2/4/8 (the existing \"OFF\" and MIDI CHANNEL's digit\nstrings), so the old ON is now \"1\". The screen saver itself no longer reads\nthe setting: its getter literal (0x08BB7E60) points at a firmware tail that\nreturns 1, so it stays on as it was by default.\n\"\"\"\nimport os\nimport struct\nimport sys\n\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nimport hotcuekeys # noqa: E402\n\nVERSION = '1.0.1' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nBJ_BASE = 0x08FCF400 # after arnoldasr's code mod (ends 0x08FCF260)\n\nPLAYER_CTX = 0x099445DC\nDECODER_LIT = 0x08B622A8 # the UI poller's literal of the panel decoder\nDECODER = 0x08B63278\nKEYS_TRACK = 0x0B2E82C5 # decoded keys: TRACK + is bit 0x80 here, TRACK - 0x01 of the next byte\nSEND = 0x08B3B666 # the UI's player-message builder\nGET_SETTING = 0x08AFD9E0 # SCREEN SAVER getter: *(0x0A4DD270 + 0x1760)\nGRID_LOOKUP = 0x08A27D4C\nGRID_TIME = 0x08A0EFB6\nFPSCR_MASK = 0xFFE7FFFF # what the firmware does to fpscr after every call\n\nSAVER_ITEM = 0x08C6CBB4 # UTILITY item 14 (copied to RAM at start-up)\nSAVER_CONSUMER_LIT = 0x08BB7E60 # the screen saver's literal of the getter\nRETURN_ONE = 0x08BDA1B0 # 'rts; mov #1,r0' at the end of a firmware function\nVALUE_STRINGS = [0xA80D013C, 0xA80D0144, 0xA80D0148, 0xA80D0150, 0xA80D0160] # OFF 1 2 4 8\nLABEL_OLD = 'SCREEN SAVER\\0'.encode('utf-16-le')\nLABEL_NEW = 'BEAT JUMP'.encode('utf-16-le').ljust(len(LABEL_OLD), b'\\0')\nLABEL_COPIES = 19\n\nVALUE_LIST = BJ_BASE + 0x400 # the five value-string pointers, after the code\nPREV_BITS = BJ_BASE + 0x3FC # last frame's TRACK bits, for press edges\nLAST_SET = BJ_BASE + 0x3F8 # word: the setting last seen (-1 at start: taken from the journal then)\nSETTING = 0x0A4DD270 + 0x1760 # the setting's word (the SCREEN SAVER's)\nSAVE_ID = 0x83E2 # ours in the settings journal (hotcuekeys' is 0x83E1)\nDEFAULT = 3 # 4 beats\nBJ_DELTA = 0x08FCF818 # word: the last jump's length in ms (signed), for hotcuekeys' DSP jump\nBJ_POSV = hotcuekeys.BJ_POSV # word: the position (ms) to reckon from, if not 0\nBJ_PEND = hotcuekeys.BJ_PEND # word: presses counted while hotcuekeys' CALL had no grid\n\n\nclass Asm:\n \"\"\"Just enough SH-4 to write bj_poll: two passes over a list of\n instructions, labels, and a literal pool of .long/.word constants.\"\"\"\n\n def __init__(self, base):\n self.base, self.items, self.labels = base, [], {}\n\n def label(self, name):\n self.items.append(('label', name))\n\n def op(self, fn):\n self.items.append(('op', fn))\n\n def long(self, name, value):\n self.items.append(('align4', None))\n self.label(name)\n self.items.append(('long', value))\n\n def word(self, name, value):\n self.label(name)\n self.items.append(('word', value))\n\n def assemble(self):\n for final in (False, True):\n pc, out = self.base, bytearray()\n for kind, arg in self.items:\n if kind == 'label':\n self.labels[arg] = pc\n continue\n if kind == 'align4':\n while pc % 4:\n out += struct.pack('> 36 = q / 20\n a.long('L_req', hotcuekeys.BJ_REQ)\n a.long('L_tick', hotcuekeys.TICK)\n a.long('L_bit31', 0x80000000)\n a.long('L_delta', BJ_DELTA)\n a.long('L_posv', BJ_POSV)\n a.long('L_pend', BJ_PEND)\n a.long('L_third', 0xAAAAAAAB) # q * this >> 33 = q / 3\n a.long('L_last', LAST_SET)\n a.long('L_setting', SETTING)\n a.long('L_bjid', SAVE_ID)\n a.long('L_bjid16', SAVE_ID << 16)\n a.long('L_api', hotcuekeys.HK_API)\n code = a.assemble()\n return code, None\n\n\ndef patch(app, stock):\n \"\"\"The application with BEAT JUMP added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n code, _ = routine()\n assert len(code) <= LAST_SET - BJ_BASE\n out = bytearray(app)\n end = VALUE_LIST + 4 * len(VALUE_STRINGS) - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[BJ_BASE - BASE:end]):\n raise SystemExit('patch_cdj900nxs: beatjump: its area 0x%08x.. is not free' % BJ_BASE)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + len(before)] != before or out[off:off + len(before)] != before:\n raise SystemExit('patch_cdj900nxs: beatjump: bytes at 0x%08x are not the expected original'\n % addr)\n out[off:off + len(after)] = after\n\n out[BJ_BASE - BASE:BJ_BASE - BASE + len(code)] = code\n out[LAST_SET - BASE:LAST_SET - BASE + 4] = b'\\xff\\xff\\xff\\xff'\n out[VALUE_LIST - BASE:end] = b''.join(struct.pack('= 0:\n put(BASE + at, LABEL_OLD, LABEL_NEW)\n found += 1\n at = stock.find(LABEL_OLD, at + 2)\n if found != LABEL_COPIES:\n raise SystemExit('patch_cdj900nxs: beatjump: %d SCREEN SAVER labels, expected %d'\n % (found, LABEL_COPIES))\n return bytes(out)\n","mods/cdj900nxs/bpmhalfdouble.py":"# SPDX-License-Identifier: GPL-2.0-or-later\n\"\"\"BPM x2 / /2 (half / double) for the CDJ-900NXS 1.31: the loaded track's\nbeat grid and BPM doubled or halved from the deck, as rekordbox's grid editor\ndoes it, kept on the USB stick. Our own mod; mods/patch_cdj900nxs.py applies\nit as `bpmhalfdouble`, after quicksearch (which loads the track again for it).\n\n * TAG TRACK/REMOVE (panel 0x14:0x20, decoded 0x0B2E82D6 bit 0x40) is the\n shift while a track is loaded and BROWSE is closed: held, CALL < (decoded\n 0x0B2E82C4 bit 0x10) halves the BPM, CALL > (bit 0x08) doubles it; the\n CALL keys are hidden from the firmware meanwhile. TAG TRACK pressed\n briefly alone (under SHORT_TICKS) is handed to the firmware as a press of\n its own once let go, so tagging works as before. A hold stays ours till\n TAG is let go, also once the reload has opened BROWSE (the firmware\n would tag the track there).\n * As rekordbox: a result under MIN_BPM or over MAX_BPM is not done; the\n title bar says so (\"/2 makes no sense: 42.50 BPM\") in red. Playing,\n nothing is changed (\"Pause first\"). A change shows in yellow for\n MSG_TICKS once the track is loaded again (\"BPM x2 -> 170.00 BPM\",\n \"BPM as analysed: 85.00 BPM\" back at the track's own).\n * The factor is 2^k, k -2..2, always of the track's original grid and BPM:\n x2 after /2 gives the original back exactly (/2 keeps the beats on the\n first downbeat's parity, x2 puts one in the middle of every interval;\n the bars stay on rekordbox's downbeats).\n * The player keeps a record per track it has loaded (a cache; the loaded\n one at ctx +0x20 -> +0xC4: Y): its BPM (Y +0xF8, +0x116, +0x126) and its\n grid object (Y +0x104), filled by 0x08A0A724 from the DB task's load\n reply (BPM at +48, the grid at +124). Loading a track (also the same one\n again) copies the record into the deck's info (TRACK_INFO: +0 device,\n +4 rekordbox id, +8 the number in its list, +0x18 BPM, +0x1C grid), and\n from there come the BPM shown, the beat ticks, the bar counters, quantize\n and the Link status. So a change (bd_live) puts the grid for 2^k and the\n BPM into the record and has quicksearch load the track again by its\n browse path (quicksearch.RELOAD): the firmware rebuilds all of it. The\n deck stands at the track's cue point then, as after any load.\n * A grid object in a record is the player's: once no record and no deck\n has it, the player frees it (0x08A0DEA6). The original is therefore the\n table's own copy: made from the record's grid at the first change, or\n the DB's object kept when a fresh load is scaled.\n * A fresh load (the DB task fetches the grid: dbcl_GetQuantizeData\n 0x089771FA, rmif, rekordbox id, &size, &object, a fifth argument on the\n stack; the device at +4 of the caller's frame) of a track with a k:\n bd_qtz keeps the DB's grid as the original and hands on the one for 2^k\n (noted in BD_RING); bd_rec, in the player's receive of the load reply\n (REC_LIT, 0x08C0BB70), scales the reply's BPM where its grid is one of\n those. The grid object: +0 0x80000, +4 beats, +8 their bytes, +0xC 1,\n +0x10 the entries (obj + 20); 16 bytes an entry: +0 the beat in its bar\n 1-4, +2 the tempo x 100, +4 the time in ms, 8 bytes 0xFF.\n * Kept on the USB: C:/PIONEER/PIONAARR.BPM (FILE_MAGIC, FILE_N entries of\n rekordbox id and k), through the DB task's stdio (FOPEN .. FSEEK, the\n calls it reads export.pdb with). Read in the DB task at the first grid\n fetched from this player's USB after the stick came (bd_fload); written\n anew from the table after every change of a track on it (bd_save, \"r+\",\n a new file \"a+\"). The stick gone (USB_STATE not 3): its tracks' entries\n are dropped (bd_usb), the file read again for the next one. Tracks from\n other players' USBs (Pro DJ Link) keep their k while this player is on.\n * The table: TABLE_N entries (device, id, the original, its BPM, k), the\n slots taken round robin.\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\nimport quicksearch # noqa: E402\n\nVERSION = '0.2.0' # Semantic Versioning: MAJOR.MINOR.PATCH\n\nBASE = 0x08000000\nBD_BASE = 0x08FE4000 # after played's 0x08FE0800..0x08FE4000\nBD_END = 0x08FE5000\n# state at BD_BASE: bytes +0 TAG down, +1 a CALL used in this hold, +2 the CALL keys last, +3 k;\n# words +4 TAG down at, +8 TAG handed back till, +12 the grid seen (the load complete), +20 1: a\n# change's message waits for its load, +24 1: the USB's file read (0: to be read at the next\n# grid from it), +28 USB_STATE last seen, +40 the message shown till, +44 its colour, +60 the\n# table's next slot\nBD_MSG = BD_BASE + 0x80 # the message (UTF-16, up to 63 characters)\nBD_CLONE = BD_BASE + 0x100 # the copy of the title widget (96 bytes)\nBD_TABLE = BD_BASE + 0x200 # TABLE_N x 16: +0 device, +4 rekordbox id, +8 the original grid (ours,\nTABLE_N = 128 # 0: none kept), +12 the original BPM x 100 (u16), +14 k (s8)\nBD_STR = BD_TABLE + TABLE_N * 16\nBD_PATH = BD_BASE + 0x160 # the file on the USB (ASCII, 24 bytes)\nBD_RING = BD_BASE + 0x180 # 8 x 8: grids bd_qtz scaled (+0 the object, +4 its table entry), for bd_rec\nBD_RING_I = BD_BASE + 0x1C0 # byte: the next slot\nBD_CODE = 0x08FE8000 # after wide_waveform's 0x08FE5000..0x08FE8000\nBD_CODE_END = 0x08FEC000\nBD_FBUF = BD_CODE_END - 0x1000 # the file's bytes when read or written (FILE_SIZE)\nPATH = 'C:/PIONEER/PIONAARR.BPM' # this player's USB is C:\nFILE_MAGIC = b'PNRBPM01'\nFILE_N = 128 # entries: +0 rekordbox id, +4 k (s8), 3 bytes 0\nFILE_SIZE = 8 + FILE_N * 8\n\nPLAYER_CTX = 0x099445DC\nPLAY_STATE = PLAYER_CTX + 0x10C # word: 1 playing\nTRACK_INFO = 0x09944900 # +0 device (player << 16 | slot << 8), +4 rekordbox id, +8 the number in its list, +0x18 BPM x 100, +0x1C grid\nDEPTH = 0x0B7CC618 # the browse screen's level (0 closed)\nKEY_TAG = 0x0B2E82D6 # decoded TAG TRACK/REMOVE: bit 0x40\nTAG_BIT = 0x40\nKEY_CALL = 0x0B2E82C4 # decoded CALL <: 0x10, CALL >: 0x08\nDRAW_LIT = 0x080B27F4 # the text widgets' draw method in its class table\nTITLE_ID = 0x00210009\nQTZ_LIT = 0x08987974 # the DB task's track loader's literal of dbcl_GetQuantizeData\nQTZ = 0x089771FA\nREC_LIT = 0x08A0A8CC # the player's record filler (0x08A0A724): its literal of the receive\nRECEIVE = 0x08C0BB70 # (r4 the mailbox, r5 &the message) -> 0: the DB's load reply,\nREPLY_BPM = 48 # its BPM x 100 at +48 (to the record's Y +0xF8)\nREPLY_GRID = 124 # its grid at +124 (Y +0x104)\nMALLOC, FREE = 0x08A5C6EC, 0x08A5EBC2\nMEMSET = 0x08C28038\nUSB_STATE = 0x0A4DE78C # word: 3 while this player's USB stick is mounted\nSTATUS_BUF = 0x0B7C9634 # +0x21: this player's number (0 without LAN)\n# the DB task's files (stdio as the DB uses them for export.pdb; its error context added)\nFOPEN = 0x089A07A4 # (path, mode) -> FILE (0: none)\nFCLOSE = 0x089A0834 # (FILE)\nFREAD = 0x089A0844 # (buffer, 1, n, FILE) -> n read\nFWRITE = 0x089A0856 # (buffer, 1, n, FILE) -> n written\nFSEEK = 0x089A0868 # (FILE, offset, whence)\nMODE_R, MODE_RW, MODE_A = 0x080EEAE4, 0x080EEAE8, 0x080EEAEC # \"r\", \"r+\", \"a+\" of the DB's\n\nSHORT_TICKS = 60 # TAG TRACK let go before 0.6 s, no CALL: the firmware's press\nPULSE = 8\nMSG_TICKS = 250 # a message shown 2.5 s\nMIN_BPM = 6000 # x 100: rekordbox's sensible range\nMAX_BPM = 24000\nOK_ABGR = 0xFF00E6FF # yellow\nNO_ABGR = 0xFF3030FF # red\n\nSTRINGS = [('S_X2', 'BPM \u00d72 \u2192 '), ('S_D2', 'BPM /2 \u2192 '), ('S_ORIG', 'BPM as analysed: '),\n ('S_FAST', '\u00d72 makes no sense: '), ('S_SLOW', '/2 makes no sense: '),\n ('S_PAUSE', 'Pause first: '), ('S_UNIT', ' BPM'), ('S_LATER', ' BPM, load it again')]\n\nSOURCE = r'''\n; ---- bd_poll: the panel decoder (or whatever ran there), then ours\nbd_poll:\n sts.l pr,@-r15\n mov.l BA_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 BA_base,r14\n mov.l BA_tick,r13\n mov.l @r13,r13 ; now\n bsr bd_usb\n nop\n bsr bd_track\n nop\n bsr bd_keys\n 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; ---- bd_grid -> r0 the loaded track's grid (0 none); r1, r2\nbd_grid:\n mov.l BA_ctx,r1\n mov.l @(32,r1),r1 ; the track\n tst r1,r1\n bt bg_none\n mov #-60,r0\n extu.b r0,r0 ; +0xC4: X\n mov.l @(r0,r1),r1\n tst r1,r1\n bt bg_none\n mov #65,r0\n shll2 r0 ; +0x104: the grid\n mov.l @(r0,r1),r0\n tst r0,r0\n bt bg_ret\n mov.l @(4,r0),r1 ; with beats\n mov #2,r2\n cmp/hs r2,r1\n bt bg_ret\nbg_none:\n mov #0,r0\nbg_ret:\n rts\n nop\n\n; ---- bd_track: a new track (another grid, the load complete: the deck's info has it\n; too): its kept factor\nbd_track:\n sts.l pr,@-r15\n bsr bd_grid\n nop\n mov.l @(12,r14),r1\n cmp/eq r0,r1\n bt bt_ret\n mov.l BT_info,r1\n mov.l @(28,r1),r1\n cmp/eq r0,r1\n bf bt_ret ; still loading\n mov.l r0,@(12,r14)\n mov r0,r8\n mov.l @(20,r14),r0 ; a change's message waiting for its load: from now\n tst r0,r0\n bt bt_k\n mov #0,r0\n mov.l r0,@(20,r14)\n mov.w BT_msg,r0\n add r13,r0\n mov.l r0,@(40,r14)\nbt_k:\n mov #0,r0\n mov.b r0,@(3,r14)\n tst r8,r8\n bt bt_ret\n bsr bd_find_info\n nop\n tst r0,r0\n bt bt_ret\n mov.b @(14,r0),r0\n mov.b r0,@(3,r14)\nbt_ret:\n lds.l @r15+,pr\n rts\n nop\nBT_msg: .word MSG_TICKS\n .align 4\nBT_info: .long TRACK_INFO\n\n; ---- bd_find_info -> r0 the table's entry for the loaded track (TRACK_INFO), or 0; r1-r5\nbd_find_info:\n mov.l BA_info,r1\n mov.l @r1,r2 ; device\n mov.l @(4,r1),r3 ; rekordbox id (+8 is the track's number in its list)\n; ---- bd_find: r2 device, r3 id -> r0 the table's entry, or 0; r4, r5\nbd_find:\n mov.l BA_table,r4\n mov.w BA_n,r5\nbf_loop:\n mov.l @r4,r0\n cmp/eq r2,r0\n bf bf_next\n mov.l @(4,r4),r0\n cmp/eq r3,r0\n bf bf_next\n rts\n mov r4,r0\nbf_next:\n dt r5\n bf/s bf_loop\n add #16,r4\n rts\n mov #0,r0\n\n; ---- bd_entry -> r0 the loaded track's entry, a new one if it has none; r1-r7\nbd_entry:\n sts.l pr,@-r15\n bsr bd_find_info\n nop\n tst r0,r0\n bf be_ret\n bsr bd_slot ; (r2, r3 as bd_find_info leaves them)\n nop\nbe_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- bd_slot: r2 device, r3 id -> r0 a new entry for them (the slots round robin; the\n; original grid of a slot taken over freed); r1-r7. Needs r14 = BD_BASE\nbd_slot:\n sts.l pr,@-r15\n mov.l r2,@-r15\n mov.l r3,@-r15\n mov.l @(60,r14),r0 ; the next slot\n mov r0,r1\n add #1,r1\n mov.w BA_n,r2\n cmp/hs r2,r1\n bf bs_n\n mov #0,r1\nbs_n:\n mov.l r1,@(60,r14)\n shll2 r0\n shll2 r0 ; 16 i\n mov.l BA_table,r1\n add r1,r0\n mov.l r0,@-r15\n mov.l @(8,r0),r4\n tst r4,r4\n bt bs_new\n mov.l BA_free,r1\n jsr @r1\n nop\nbs_new:\n mov.l @r15+,r0\n mov.l @r15+,r3\n mov.l @r15+,r2\n mov.l r2,@r0\n mov.l r3,@(4,r0)\n mov #0,r1\n mov.l r1,@(8,r0)\n mov.l r1,@(12,r0) ; no original, BPM 0, k 0\n lds.l @r15+,pr\n rts\n nop\n\n; ---- bd_mine: r2 a device -> T: this player's USB (slot 3, the player this one; any\n; without LAN); r0, r1\nbd_mine:\n mov r2,r0\n shlr8 r0\n extu.b r0,r0\n cmp/eq #3,r0\n bf bm_out\n mov.l BA_sbuf,r1\n mov #33,r0\n mov.b @(r0,r1),r0\n extu.b r0,r0\n tst r0,r0\n bt bm_out ; (T set)\n mov r2,r1\n shlr16 r1\n extu.b r1,r1\n cmp/eq r0,r1\nbm_out:\n rts\n nop\n\n; ---- bd_usb: this player's USB gone: its file to be read again, its tracks' entries\n; dropped (another stick has other tracks under the same ids)\nbd_usb:\n sts.l pr,@-r15\n mov.l BA_usb,r1\n mov.l @r1,r0\n mov.l @(28,r14),r1\n cmp/eq r0,r1\n bt bu_ret\n mov.l r0,@(28,r14)\n cmp/eq #3,r0\n bt bu_ret ; there: read at its first grid (bd_qtz)\n mov #0,r0\n mov.l r0,@(24,r14)\n mov.l BA_table,r8\n mov.w BA_n,r9\nbu_loop:\n mov.l @r8,r2\n tst r2,r2\n bt bu_next\n bsr bd_mine\n nop\n bf bu_next\n mov.l @(8,r8),r4\n mov #0,r0\n mov.l r0,@r8\n mov.l r0,@(4,r8)\n mov.l r0,@(8,r8)\n mov.l r0,@(12,r8)\n tst r4,r4\n bt bu_next\n mov.l BA_free,r1\n jsr @r1\n nop\nbu_next:\n dt r9\n bf/s bu_loop\n add #16,r8\nbu_ret:\n lds.l @r15+,pr\n rts\n nop\n\nBA_n: .word TABLE_N\n .align 4\nBA_prev: .long PREV_DECODER\nBA_base: .long BD_BASE\nBA_tick: .long TICK\nBA_ctx: .long PLAYER_CTX\nBA_info: .long TRACK_INFO\nBA_table: .long BD_TABLE\nBA_free: .long FREE\nBA_sbuf: .long STATUS_BUF\nBA_usb: .long USB_STATE\n\n; ---- bd_keys: TAG TRACK the shift, CALL < /2, CALL > x2\nbd_keys:\n sts.l pr,@-r15\n mov.b @r14,r0 ; a hold under way stays ours till TAG is let go (the\n tst r0,r0 ; reload opens BROWSE meanwhile: the firmware would tag)\n bf bk_ours\n mov.l @(12,r14),r0 ; a track loaded and BROWSE closed: ours\n tst r0,r0\n bt bk_free\n mov.l BK_depth,r1\n mov.l @r1,r0\n tst r0,r0\n bf bk_free\nbk_ours:\n mov.l BK_tag,r9\n mov.b @r9,r0\n mov r0,r10\n and #TAG_BIT,r0\n mov r0,r11 ; r11: TAG down\n mov r10,r0\n and #0xBF,r0 ; hidden from the firmware\n mov.b r0,@r9\n mov.b @r14,r0\n tst r0,r0\n bf bk_was\n tst r11,r11\n bt bk_out\n mov #1,r0 ; down now\n mov.b r0,@r14\n mov #0,r0\n mov.b r0,@(1,r14)\n mov.l r13,@(4,r14)\n mov.l BK_call,r1\n mov.b @r1,r0\n and #0x18,r0\n bra bk_hide\n mov.b r0,@(2,r14)\nbk_was:\n tst r11,r11\n bt bk_up\n mov.l BK_call,r1 ; held: a CALL key pressed now?\n mov.b @r1,r0\n and #0x18,r0\n mov r0,r2\n mov.b @(2,r14),r0\n not r0,r0\n and r2,r0\n mov r0,r3 ; r3: CALL keys down now\n mov r2,r0\n mov.b r0,@(2,r14)\n mov r3,r0\n tst #0x10,r0\n bt bk_k1\n mov #1,r0 ; CALL <: /2\n mov.b r0,@(1,r14)\n mov.b @(3,r14),r0\n add #-1,r0\n bsr bd_apply\n mov r0,r4\n bra bk_hide\n nop\nbk_k1:\n mov r3,r0\n tst #0x08,r0\n bt bk_hide\n mov #1,r0 ; CALL >: x2\n mov.b r0,@(1,r14)\n mov.b @(3,r14),r0\n add #1,r0\n bsr bd_apply\n mov r0,r4\nbk_hide:\n mov.l BK_call,r1 ; the CALL keys hidden while TAG is held\n mov.b @r1,r0\n and #0xE7,r0\n bra bk_out\n mov.b r0,@r1\nbk_up:\n mov #0,r0 ; let go: a short press alone is the firmware's\n mov.b r0,@r14\n mov.b @(1,r14),r0\n tst r0,r0\n bf bk_out\n mov.l @(4,r14),r1\n mov r13,r2\n sub r1,r2\n mov #SHORT_TICKS,r1\n cmp/hs r1,r2\n bt bk_out\n mov #PULSE,r0\n add r13,r0\n bra bk_out\n mov.l r0,@(8,r14)\nbk_free:\n mov #0,r0\n mov.b r0,@r14\nbk_out:\n mov.l @(8,r14),r0 ; TAG handed back\n sub r13,r0\n cmp/pl r0\n bf bk_ret\n mov.l BK_tag,r1\n mov.b @r1,r0\n or #TAG_BIT,r0\n mov.b r0,@r1\nbk_ret:\n lds.l @r15+,pr\n rts\n nop\n\n; ---- bd_apply: r4 the new k. Ignored while a change is loading; checked against the\n; range and the deck; the player's record changed (bd_live), the track loaded again\n; (through quicksearch), the title bar told\nbd_apply:\n sts.l pr,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov r4,r8 ; r8: the new k\n bsr bd_grid ; a change still loading (the deck's info has not the\n nop ; record's grid yet): nothing\n mov.l BK_info,r1\n mov.l @(28,r1),r1\n cmp/eq r0,r1\n bf ba_ret\n bsr bd_find_info ; r9: the BPM it makes: the original's x 2^new k\n nop\n mov.l BK_info,r1\n mov.l @(24,r1),r9 ; (no original kept: k is 0, the track's own)\n tst r0,r0\n bt ba_b\n mov.l @(8,r0),r1\n tst r1,r1\n bt ba_b\n mov.w @(12,r0),r0\n extu.w r0,r9\nba_b:\n shad r8,r9\n mov.b @(3,r14),r0\n cmp/gt r8,r0 ; down: not under MIN_BPM\n bf ba_up\n mov.l BK_min,r1\n cmp/hs r1,r9\n bt ba_ok\n mov.l BK_sslow,r4\n bra ba_no\n nop\nba_up:\n mov.l BK_max,r1 ; up: not over MAX_BPM\n cmp/hi r1,r9\n bf ba_ok\n mov.l BK_sfast,r4\nba_no:\n mov r9,r5\n mov.l BK_red,r6\n mov.l BK_sunit,r7\n bsr bd_msg\n nop\n bra ba_ret\n nop\nba_ok:\n mov.l BK_state,r1 ; playing: nothing changed\n mov.l @r1,r0\n cmp/eq #1,r0\n bf ba_still\n mov.l BK_spause,r4\n bra ba_no\n nop\nba_still:\n bsr bd_live ; the player's record: the grid for 2^k, the BPM\n nop\n tst r0,r0\n bt ba_ret ; (no memory)\n bsr bd_save ; kept on this player's USB\n nop\n mov.l BK_sorig,r4 ; what the title bar says\n mov r8,r0\n tst r0,r0\n bt ba_say\n mov.l BK_sx2,r4\n cmp/pz r0\n bt ba_say\n mov.l BK_sd2,r4\nba_say:\n mov.l BK_sunit,r7\n mov.l BK_qsre,r1 ; the track loaded again by its path, if quicksearch knows it\n tst r1,r1\n bt ba_later\n mov.l BK_qslev,r2\n mov.b @r2,r0\n tst r0,r0\n bt ba_later\n mov #1,r0\n mov.l r0,@r1\n bra ba_tell\n mov.l r0,@(20,r14) ; the message shown once it is loaded (bd_track)\nba_later:\n mov.l BK_slater,r7 ; else: at the next load\nba_tell:\n mov r9,r5\n mov.l BK_yellow,r6\n bsr bd_msg\n nop\nba_ret:\n mov.l @r15+,r9\n mov.l @r15+,r8\n lds.l @r15+,pr\n rts\n nop\n\n .align 4\nBK_depth: .long DEPTH\nBK_info: .long TRACK_INFO\nBK_tag: .long KEY_TAG\nBK_call: .long KEY_CALL\nBK_state: .long PLAY_STATE\nBK_min: .long MIN_BPM\nBK_max: .long MAX_BPM\nBK_red: .long NO_ABGR\nBK_yellow: .long OK_ABGR\nBK_sslow: .long S_SLOW\nBK_sfast: .long S_FAST\nBK_spause: .long S_PAUSE\nBK_sorig: .long S_ORIG\nBK_sx2: .long S_X2\nBK_sd2: .long S_D2\nBK_sunit: .long S_UNIT\nBK_slater: .long S_LATER\nBK_qsre: .long QS_RELOAD\nBK_qslev: .long QS_LEVELS\n\n; ---- bd_live: r8 the new k, r9 the BPM it makes -> r0 1 done (0: no memory). The\n; player's record of the track (ctx +0x20 -> +0xC4: Y) gets the original grid for 2^k at\n; Y +0x104 and the BPM at Y +0xF8, +0x116, +0x126. The original is the table's own copy\n; (the first change makes it from the record's grid, with the track's BPM). The grid the\n; record had is the player's: it frees it once no record and no deck has it any more\nbd_live:\n sts.l pr,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l BL_ctx,r1\n mov.l @(32,r1),r1\n tst r1,r1\n bt bl_fail\n mov #-60,r0\n extu.b r0,r0\n mov.l @(r0,r1),r10 ; r10: Y\n tst r10,r10\n bt bl_fail\n mov.w BL_grid,r0\n mov.l @(r0,r10),r11 ; r11: the record's grid now\n tst r11,r11\n bt bl_fail\n bsr bd_entry\n nop\n mov r0,r12 ; r12: the table's entry\n mov.l @(8,r12),r0\n tst r0,r0\n bf bl_from\n mov r11,r4 ; the first change: the original copied (2^0)\n bsr bd_scale\n mov #0,r5\n tst r0,r0\n bt bl_fail\n mov.l r0,@(8,r12)\n mov.l BL_info,r1 ; and the track's BPM\n mov.l @(24,r1),r0\n mov.w r0,@(12,r12)\nbl_from:\n mov.l @(8,r12),r4\n bsr bd_scale\n mov r8,r5\n tst r0,r0\n bt bl_fail\n mov r0,r1\n mov.w BL_grid,r0\n mov.l r1,@(r0,r10)\n mov r9,r1\n mov.w BL_b0,r0\n mov.w r1,@(r0,r10)\n mov.w BL_b1,r0\n mov.w r1,@(r0,r10)\n mov.w BL_b2,r0\n mov.w r1,@(r0,r10)\n mov r8,r0\n mov.b r0,@(14,r12) ; k kept\n bra bl_ret\n mov #1,r0\nbl_fail:\n mov #0,r0\nbl_ret:\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n lds.l @r15+,pr\n rts\n nop\nBL_grid: .word 0x104\nBL_b0: .word 0xF8\nBL_b1: .word 0x116\nBL_b2: .word 0x126\n .align 4\nBL_ctx: .long PLAYER_CTX\nBL_info: .long TRACK_INFO\n\n; ---- bd_save: the loaded track on this player's USB (with it there): the file written\n; anew there from the table (every track of that USB with a k not 0)\nbd_save:\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 BV_info,r1\n mov.l @r1,r11 ; r11: the device\n bsr bd_mine\n mov r11,r2\n bf bv_ret\n mov.l BV_usb,r1\n mov.l @r1,r0\n cmp/eq #3,r0\n bf bv_ret\n mov.l BV_buf,r4 ; the file's bytes: the magic, the entries\n mov #0,r5\n mov.w BV_size,r6\n mov.l BV_memset,r0\n jsr @r0\n nop\n mov.l BV_buf,r10\n mov.l BV_magic0,r0\n mov.l r0,@r10\n mov.l BV_magic1,r0\n mov.l r0,@(4,r10)\n add #8,r10 ; r10: the next file entry\n mov.l BV_table,r8\n mov.w BV_n,r9\nbv_loop:\n mov.l @r8,r0\n cmp/eq r11,r0\n bf bv_next\n mov.b @(14,r8),r0\n tst r0,r0\n bt bv_next\n mov.l r0,@(4,r10)\n mov.l @(4,r8),r0\n mov.l r0,@r10\n add #8,r10\nbv_next:\n dt r9\n bf/s bv_loop\n add #16,r8\n mov.l BV_path,r4 ; written: \"r+\", a new file \"a+\"\n mov.l BV_rw,r5\n mov.l BV_fopen,r0\n jsr @r0\n nop\n tst r0,r0\n bf bv_open\n mov.l BV_path,r4\n mov.l BV_a,r5\n mov.l BV_fopen,r0\n jsr @r0\n nop\n tst r0,r0\n bt bv_ret\nbv_open:\n mov r0,r8 ; r8: the FILE\n mov r0,r4\n mov #0,r5\n mov.l BV_fseek,r0\n jsr @r0\n mov #0,r6\n mov.l BV_buf,r4\n mov #1,r5\n mov.w BV_size,r6\n mov.l BV_fwrite,r0\n jsr @r0\n mov r8,r7\n mov.l BV_fclose,r0\n jsr @r0\n mov r8,r4\nbv_ret:\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\nBV_size: .word FILE_SIZE\nBV_n: .word TABLE_N\n .align 4\nBV_info: .long TRACK_INFO\nBV_usb: .long USB_STATE\nBV_buf: .long BD_FBUF\nBV_memset: .long MEMSET\nBV_magic0: .long MAGIC0\nBV_magic1: .long MAGIC1\nBV_table: .long BD_TABLE\nBV_path: .long BD_PATH\nBV_rw: .long MODE_RW\nBV_a: .long MODE_A\nBV_fopen: .long FOPEN\nBV_fseek: .long FSEEK\nBV_fwrite: .long FWRITE\nBV_fclose: .long FCLOSE\n\n; ---- bd_msg: r4 the prefix, r5 a BPM x 100, r6 the colour, r7 the ending (UTF-16):\n; the title bar shows \"\" for MSG_TICKS\nbd_msg:\n sts.l pr,@-r15\n mov.l r7,@-r15\n mov.l r6,@(44,r14)\n mov.l BM_msg,r7 ; the message\nbm_cp:\n mov.w @r4+,r0\n tst r0,r0\n bt bm_num\n mov.w r0,@r7\n bra bm_cp\n add #2,r7\nbm_num:\n mov.l BM_r100,r1 ; integer part and hundredths\n dmulu.l r1,r5\n sts mach,r2\n shlr2 r2\n shlr2 r2\n shlr r2 ; / 100\n mov #100,r1\n mulu.w r1,r2\n sts macl,r3\n mov r5,r6\n sub r3,r6 ; r6: hundredths\n mov r2,r5 ; r5: integer part (< 1000)\n mov #100,r1 ; hundreds, if any\n mov #0,r3\nbm_h:\n cmp/hs r1,r5\n bf bm_hd\n sub r1,r5\n bra bm_h\n add #1,r3\nbm_hd:\n mov r3,r0\n tst r0,r0\n bt bm_t\n add #48,r0\n mov.w r0,@r7\n add #2,r7\nbm_t:\n mov #10,r1 ; tens (always once there are hundreds)\n mov #0,r2\nbm_t2:\n cmp/hs r1,r5\n bf bm_td\n sub r1,r5\n bra bm_t2\n add #1,r2\nbm_td:\n mov r2,r0\n or r3,r0\n tst r0,r0\n bt bm_u\n mov r2,r0\n add #48,r0\n mov.w r0,@r7\n add #2,r7\nbm_u:\n mov r5,r0\n add #48,r0\n mov.w r0,@r7\n add #2,r7\n mov #46,r0 ; \".\"\n mov.w r0,@r7\n add #2,r7\n mov #10,r1\n mov #0,r2\nbm_f:\n cmp/hs r1,r6\n bf bm_fd\n sub r1,r6\n bra bm_f\n add #1,r2\nbm_fd:\n mov r2,r0\n add #48,r0\n mov.w r0,@r7\n add #2,r7\n mov r6,r0\n add #48,r0\n mov.w r0,@r7\n add #2,r7\n mov.l @r15+,r4 ; the ending\nbm_cp2:\n mov.w @r4+,r0\n mov.w r0,@r7\n tst r0,r0\n bf/s bm_cp2\n add #2,r7\n mov.l BM_ticks,r0\n add r13,r0\n mov.l r0,@(40,r14)\n lds.l @r15+,pr\n rts\n nop\n\n; ---- bd_draw: the text widgets' draw method (r4 the renderer, r5 the widget):\n; while a message is shown, the title bar's title is drawn as a copy with it\nbd_draw:\n mov.l BM_base,r1\n mov.l @(40,r1),r0\n mov.l BM_tick,r2\n mov.l @r2,r2\n sub r2,r0\n cmp/pl r0\n bf bd_go\n mov.l @(4,r5),r0\n mov.l BM_title,r2\n cmp/eq r2,r0\n bf bd_go\n mov.l BM_clone,r6 ; the copy\n mov r5,r2\n mov r6,r3\n mov #24,r7\nbd_cp:\n mov.l @r2+,r0\n mov.l r0,@r3\n dt r7\n bf/s bd_cp\n add #4,r3\n mov #0,r0\n mov.l r0,@(4,r6) ; (not the title any more for the hooks after us)\n mov.l @(44,r1),r0\n mov.l r0,@(40,r6) ; the colour\n mov.l BM_msg,r0\n mov.l r0,@(52,r6) ; the message\n mov r6,r5\nbd_go:\n mov.l BM_prev,r0\n jmp @r0\n nop\n\n .align 4\nBM_r100: .long 0x51EB851F\nBM_ticks: .long MSG_TICKS\nBM_base: .long BD_BASE\nBM_tick: .long TICK\nBM_title: .long TITLE_ID\nBM_prev: .long PREV_DRAW\nBM_msg: .long BD_MSG\nBM_clone: .long BD_CLONE\n\n; ---- bd_qtz: dbcl_GetQuantizeData (r4 rmif, r5 rekordbox id, r6 &the object's size, r7\n; &the object, a fifth argument on the stack; 1 returned when it came; the device at +4 of the caller's frame), in the DB task. A track with a factor kept gets its\n; grid built for it, in a block of its own; the one the DB gave freed\nbd_qtz:\n mov.l @(4,r15),r1 ; the device (the caller's frame, before ours)\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 r1,r8\n mov r5,r9\n mov r6,r10\n mov r7,r11\n mov.l @(24,r15),r0 ; its fifth argument (on the stack) on top again\n mov.l r0,@-r15\n mov.l BQ_prev,r0\n jsr @r0\n nop\n add #4,r15\n mov r0,r12 ; its result (1: the grid came), returned as it is\n tst r0,r0\n bt bq_ret\n bsr bd_fload ; this player's USB: its file read, once a stick\n mov r8,r2\n mov r8,r2 ; an entry for it?\n bsr bd_find\n mov r9,r3\n tst r0,r0\n bt bq_ret\n mov r0,r8 ; r8: the entry\n mov.l @(8,r8),r4 ; an original kept from before: a new one came, freed\n tst r4,r4\n bt bq_k\n mov #0,r0\n mov.l r0,@(8,r8)\n mov.l BQ_free,r0\n jsr @r0\n nop\nbq_k:\n mov.b @(14,r8),r0\n tst r0,r0\n bt bq_ret ; k 0: the DB's as it is\n mov r0,r5\n mov.l @r11,r4 ; (r7: &object, r6: &its size)\n tst r4,r4\n bt bq_ret\n bsr bd_scale\n nop\n tst r0,r0\n bt bq_ret ; no memory: the track's own grid\n mov.l @r11,r1 ; the DB's kept as the original, ours handed on\n mov.l r1,@(8,r8)\n mov.l r0,@r11\n mov.l @(8,r0),r1\n add #20,r1\n mov.l r1,@r10\n mov.l BQ_ringi,r1 ; and noted for bd_rec (the reply's BPM)\n mov.b @r1,r2\n mov r2,r3\n add #1,r3\n mov.b r3,@r1\n mov #7,r1\n and r1,r2\n shll2 r2\n shll r2\n mov.l BQ_ring,r1\n add r1,r2\n mov.l r0,@r2\n mov.l r8,@(4,r2)\nbq_ret:\n mov r12,r0\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; ---- bd_fload: r2 the device of a grid the DB fetches, in the DB task: this player's\n; USB, its file not read since it came: read; its tracks' k into the table (each its\n; entry, a new one if it has none)\nbd_fload:\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 r14,@-r15\n mov.l BF_base,r14\n mov r2,r11 ; r11: the device\n mov.l @(24,r14),r0\n tst r0,r0\n bf bfl_ret ; read already\n bsr bd_mine\n nop\n bf bfl_ret\n mov.l BF_usb,r1\n mov.l @r1,r0\n cmp/eq #3,r0\n bf bfl_ret\n mov #1,r0\n mov.l r0,@(24,r14)\n mov.l BF_path,r4\n mov.l BF_r,r5\n mov.l BF_fopen,r0\n jsr @r0\n nop\n tst r0,r0\n bt bfl_ret ; no file (no track changed on this stick yet)\n mov r0,r8\n mov.l BF_buf,r4\n mov #1,r5\n mov.w BF_size,r6\n mov.l BF_fread,r0\n jsr @r0\n mov r8,r7\n mov r0,r9 ; r9: bytes read\n mov.l BF_fclose,r0\n jsr @r0\n mov r8,r4\n mov.w BF_size,r0\n cmp/eq r0,r9\n bf bfl_ret ; not one of ours\n mov.l BF_buf,r10\n mov.l @r10,r0\n mov.l BF_magic0,r1\n cmp/eq r1,r0\n bf bfl_ret\n mov.l @(4,r10),r0\n mov.l BF_magic1,r1\n cmp/eq r1,r0\n bf bfl_ret\n add #8,r10\n mov.w BF_n,r9\nbfl_loop:\n mov.l @r10,r3 ; an id with a k\n tst r3,r3\n bt bfl_next\n mov.b @(4,r10),r0\n tst r0,r0\n bt bfl_next\n mov r11,r2\n bsr bd_find\n nop\n tst r0,r0\n bf bfl_k\n mov r11,r2\n mov.l @r10,r3\n bsr bd_slot\n nop\nbfl_k:\n mov r0,r1\n mov.b @(4,r10),r0\n mov.b r0,@(14,r1)\nbfl_next:\n dt r9\n bf/s bfl_loop\n add #8,r10\nbfl_ret:\n mov.l @r15+,r14\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\nBF_size: .word FILE_SIZE\nBF_n: .word FILE_N\n .align 4\nBF_base: .long BD_BASE\nBF_usb: .long USB_STATE\nBF_path: .long BD_PATH\nBF_r: .long MODE_R\nBF_buf: .long BD_FBUF\nBF_fopen: .long FOPEN\nBF_fread: .long FREAD\nBF_fclose: .long FCLOSE\nBF_magic0: .long MAGIC0\nBF_magic1: .long MAGIC1\n\n; ---- bd_rec: the player's receive of the DB's load reply (r4 the mailbox, r5 &the\n; message; 0 returned when it came). A reply whose grid bd_qtz scaled gets its BPM scaled\n; too (the slot then emptied), before the player fills its record from it\nbd_rec:\n sts.l pr,@-r15\n mov.l r5,@-r15\n mov.l BR_prev,r0\n jsr @r0\n nop\n mov.l @r15+,r5\n tst r0,r0\n bf br_ret\n mov.l @r5,r1 ; the message\n tst r1,r1\n bt br_ok\n mov #REPLY_GRID,r0\n mov.l @(r0,r1),r2\n tst r2,r2\n bt br_ok\n mov.l BR_ring,r3\n mov #8,r4\nbr_find:\n mov.l @r3,r6\n cmp/eq r2,r6\n bt br_hit\n dt r4\n bf/s br_find\n add #8,r3\n bra br_ok\n nop\nbr_hit:\n mov #0,r6\n mov.l r6,@r3 ; (used)\n mov.l @(4,r3),r6 ; the entry\n mov #REPLY_BPM,r0\n mov.l @(r0,r1),r7 ; the track's own BPM\n mov r7,r0\n mov.w r0,@(12,r6) ; the original's\n mov.b @(14,r6),r0\n shad r0,r7\n mov #REPLY_BPM,r0\n mov.l r7,@(r0,r1)\nbr_ok:\n mov #0,r0\nbr_ret:\n lds.l @r15+,pr\n rts\n nop\n .align 4\nBR_prev: .long PREV_REC\nBR_ring: .long BD_RING\n\n; ---- bd_scale: r4 a grid object (entries at +20), r5 k -> r0 a new one for 2^k (0: no\n; memory). r1-r7, r8-r13 used (saved)\nbd_scale:\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 r5,r8 ; r8: k\n mov r4,r9 ; r9: the object\n mov r4,r10\n add #20,r10 ; r10: its entries\n mov.l @(4,r4),r11 ; r11: their count\n mov r10,r1 ; the first downbeat: its index in r12\n mov #0,r12\nbs_anchor:\n mov.w @r1,r0\n cmp/eq #1,r0\n bt bs_anc\n add #16,r1\n add #1,r12\n cmp/hs r11,r12\n bf bs_anchor\n mov #0,r12\nbs_anc:\n mov #1,r7 ; r7: f = 2^|k|\n mov r8,r0\n cmp/pz r0\n bt bs_f\n neg r0,r0\nbs_f:\n shld r0,r7\n mov r8,r0\n cmp/pz r0\n bf bs_half\n mov r11,r5 ; x2: (n - 1) f + 1 entries\n add #-1,r5\n mulu.w r7,r5\n sts macl,r5\n add #1,r5\n bra bs_alloc\n nop\nbs_half:\n mov r7,r0 ; /2: i0 = anchor mod f, (n - 1 - i0) / f + 1 entries\n add #-1,r0\n and r12,r0\n mov r11,r5\n add #-1,r5\n sub r0,r5\n shad r8,r5\n add #1,r5\nbs_alloc:\n mov r5,r13 ; r13: the new count\n mov r5,r4\n shll2 r4\n shll2 r4\n add #20,r4 ; header + 16 bytes an entry\n mov.l r7,@-r15\n mov.l BS_malloc,r0\n jsr @r0\n nop\n mov.l @r15+,r7\n tst r0,r0\n bt bs_ret\n mov r0,r6 ; r6: the new object\n mov.l @r9,r1 ; its header: the old one's, the count, bytes and entries its own\n mov.l r1,@r6\n mov.l @(12,r9),r1\n mov.l r1,@(12,r6)\n mov.l r13,@(4,r6)\n mov r13,r1\n shll2 r1\n shll2 r1\n mov.l r1,@(8,r6)\n mov r6,r1\n add #20,r1\n mov.l r1,@(16,r6)\n mov.l r6,@-r15\n mov r6,r0\n add #20,r0\n mov r0,r6 ; r6: the new entries (bs_put)\n mov r8,r0\n cmp/pz r0\n bf bs_fillh\n mov r12,r3 ; x2: the new downbeat's index anchor x f\n mulu.w r7,r3\n sts macl,r3\n mov #0,r2 ; r2: j\n mov #0,r1 ; r1: i\nbs_iloop:\n mov r1,r0\n shll2 r0\n shll2 r0\n add r10,r0\n mov.l @(4,r0),r4 ; t0\n mov.w @(2,r0),r0\n extu.w r0,r5\n shld r8,r5 ; the tempo x f\n mov r1,r0\n add #1,r0\n cmp/hs r11,r0 ; the last: no interval after it\n bt bs_last\n shll2 r0\n shll2 r0\n add r10,r0\n mov.l @(4,r0),r0\n sub r4,r0 ; d\n mov.l r0,@-r15\n mov #0,r0 ; s\nbs_sloop:\n mov.l r0,@-r15\n mov.l @(4,r15),r12 ; d (the anchor is in r3 now)\n mulu.w r0,r12\n sts macl,r12\n neg r8,r0\n shld r0,r12 ; d s / f\n add r4,r12 ; the time\n bsr bs_put\n nop\n mov.l @r15+,r0\n add #1,r0\n cmp/hs r7,r0\n bf bs_sloop\n add #4,r15\n bra bs_inext\n nop\nbs_last:\n mov r4,r12\n bsr bs_put\n nop\nbs_inext:\n add #1,r1\n cmp/hs r11,r1\n bf bs_iloop\n bra bs_done\n nop\nbs_fillh:\n mov r7,r0 ; /2: every f-th from i0; the new downbeat at (anchor - i0) / f\n add #-1,r0\n and r12,r0\n mov r0,r1 ; r1: i = i0\n mov r12,r3\n sub r0,r3\n shad r8,r3\n mov #0,r2\nbs_hloop:\n mov r1,r0\n shll2 r0\n shll2 r0\n add r10,r0\n mov.l @(4,r0),r12 ; the time\n mov.w @(2,r0),r0\n extu.w r0,r5\n shad r8,r5 ; the tempo / f\n bsr bs_put\n nop\n add r7,r1\n cmp/hs r11,r1\n bf bs_hloop\nbs_done:\n mov.l @r15+,r0 ; the new object\nbs_ret:\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; bs_put: entry r2 at r6: r5 tempo, r12 time, r3 the downbeat's index; r2 += 1 (r0, r12)\nbs_put:\n mov r2,r0\n shll2 r0\n shll2 r0\n add r6,r0\n mov.l r12,@(4,r0)\n mov r0,r12\n mov r5,r0\n mov.w r0,@(2,r12)\n mov r2,r0 ; the beat in its bar: ((j - downbeat) & 3) + 1\n sub r3,r0\n and #3,r0\n add #1,r0\n mov.w r0,@r12\n mov #-1,r0\n mov.l r0,@(8,r12)\n mov.l r0,@(12,r12)\n rts\n add #1,r2\n\n .align 4\nBQ_prev: .long PREV_QTZ\nBQ_ring: .long BD_RING\nBQ_ringi: .long BD_RING_I\nBQ_free: .long FREE\nBS_malloc: .long MALLOC\n'''\n\n\ndef strings():\n \"\"\"{name: address}, bytes of the UTF-16 strings at BD_STR.\"\"\"\n at, out, addr = BD_STR, b'', {}\n for name, text in STRINGS:\n addr[name] = at\n b = text.encode('utf-16-le') + b'\\0\\0'\n out += b\n at += len(b)\n assert at <= BD_CODE, 'bpmhalfdouble: strings past their room'\n return addr, out\n\n\ndef routine(prev_decoder, prev_draw, prev_qtz=QTZ, have_qs=True, prev_rec=RECEIVE):\n addr, _ = strings()\n syms = {\n 'PREV_DECODER': prev_decoder, 'PREV_DRAW': prev_draw, 'PREV_QTZ': prev_qtz, 'PREV_REC': prev_rec,\n 'BD_RING': BD_RING, 'BD_RING_I': BD_RING_I, 'BD_PATH': BD_PATH, 'BD_FBUF': BD_FBUF,\n 'FILE_SIZE': FILE_SIZE, 'FILE_N': FILE_N, 'MEMSET': MEMSET, 'USB_STATE': USB_STATE,\n 'STATUS_BUF': STATUS_BUF, 'FOPEN': FOPEN, 'FCLOSE': FCLOSE, 'FREAD': FREAD, 'FWRITE': FWRITE,\n 'FSEEK': FSEEK, 'MODE_R': MODE_R, 'MODE_RW': MODE_RW, 'MODE_A': MODE_A,\n 'MAGIC0': struct.unpack('= quicksearch.QS_END - BASE and \\\n any(out[quicksearch.QS_CODE - BASE:quicksearch.QS_CODE - BASE + 16])\n code, labels = routine(word(h.DECODER_LIT), word(DRAW_LIT), word(QTZ_LIT), have_qs, word(REC_LIT))\n assert BD_CODE + len(code) <= BD_CODE_END, 'bpmhalfdouble: past its area'\n if len(out) < BD_CODE_END - BASE:\n out += bytes(BD_CODE_END - BASE - len(out))\n for lo, hi in ((BD_BASE, BD_END), (BD_CODE, BD_CODE_END)):\n if any(out[lo - BASE:hi - BASE]):\n raise SystemExit('patch_cdj900nxs: bpmhalfdouble: its area 0x%08x.. is not free' % lo)\n _, sdata = strings()\n out[BD_STR - BASE:BD_STR - BASE + len(sdata)] = sdata\n path = PATH.encode('ascii') + b'\\0'\n assert len(path) <= BD_RING - BD_PATH\n out[BD_PATH - BASE:BD_PATH - BASE + len(path)] = path\n for at, mode in ((MODE_R, b'r\\0'), (MODE_RW, b'r+\\0'), (MODE_A, b'a+\\0')):\n if stock[at - BASE:at - BASE + len(mode)] != mode:\n raise SystemExit('patch_cdj900nxs: bpmhalfdouble: the DB\\'s file mode strings are not where expected')\n out[BD_CODE - BASE:BD_CODE - BASE + len(code)] = code\n struct.pack_into('> 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.10.0' # the Pionaarr Mods release, ALWAYS the latest: the website's generator\n # (tools/make_builder_data.py) writes its version here\n\nBASE = 0x08000000\nDATA_WORD = 0x08C6D22C # .data: the splash's \"Ver \" pointer (RAM 0x1110766C)\nDATA_VER = 0xA80D12BC # \"Ver \"\nGETTER_LIT = 0x08BDCD8C # the splash function's literal of the version getter\nGETTER = 0x08BE9A8C\nTEXT = 'Pionaarr Mods ' + RELEASE\nCREDIT = 0x08FCF900 # between beatjump's 0x08FCF400..0x08FCF814 and cuecolours' 0x08FCFA00\nCREDIT_LEN = 0x40\nSTUB = CREDIT + 0x20 # mov.l @(4,pc),r0; rts; nop; nop; .long the text's final NUL\nBOX = 0x08C841E0 # .data: the splash text's box, x1 y1 x2 y2 (16 bits each)\nBOX_STOCK = (139, 363, 619, 385)\nBOX_NEW = (139, 363, 619, 393) # \"Ver 1.31\" has no descender; p and y need 8 more lines\n\n\ndef area():\n \"\"\"The credit area's bytes: the text, then the stub returning \"\".\"\"\"\n text = TEXT.encode('ascii') + b'\\0' # the splash takes bytes as Latin-1: no UTF-8\n # the splash's buffer is 50 bytes, its UTF-16 copy 100\n assert len(text) <= STUB - CREDIT and len(text) <= 50\n empty = CREDIT + len(text) - 1\n stub = struct.pack('<4HI', 0xD001, 0x000B, 0x0009, 0x0009, empty | 0xA0000000)\n return text.ljust(STUB - CREDIT, b'\\0') + stub\n\n\ndef patch(app, stock):\n \"\"\"The application with the credit; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n end = CREDIT + CREDIT_LEN - BASE\n if len(out) < end:\n out += bytes(end - len(out))\n if any(out[CREDIT - BASE:end]):\n raise SystemExit('patch_cdj900nxs: credit: its area 0x%08x.. is not free' % CREDIT)\n\n def put(addr, before, after):\n off = addr - BASE\n if stock[off:off + 4] != struct.pack(' the 44-byte resource records\nTRI_ID = 0xC5 # the red memory-cue triangle, 16 x 5, RGB565\nTRI_PIXELS = 0x0815A1B0 # its pixels in the image\nTRI_RED = 0xF800\nMEMLIST = 0x0B7BE8D4 # the UI's memory list, 10 x 0x24\nOV_RECS = 0x0B800D9C # the overview's marker records, 12 bytes\nMK_RECS = 0x0B801258 # the marker records they come from, 16 bytes\nMK_RECORDS = 101\nMKCOPY_LIT = 0x08BC6BF8 # the waveform preparer's literal for memcpy, which fills MK_RECS\nMEMCPY = 0x08C279C4\nHOT_DEFAULT = 0x07E0 # a hot cue without a colour: green, as rekordbox's default\n\n# rekordbox's eight memory-cue colours (ids 1-8), as the player itself draws\n# them: the track-colour dots of the browser, resources 0x14, 0x16, 0x13,\n# 0x18, 0x12, 0x0F, 0x11 and 0x15 (RGB565 there).\nMEMORY_RGB565 = [\n 0xFA55, # 1 pink\n 0xF800, # 2 red\n 0xFC20, # 3 orange\n 0xFFE0, # 4 yellow\n 0x07E0, # 5 green\n 0x079F, # 6 aqua\n 0x22FF, # 7 blue\n 0x881F, # 8 purple\n]\n\n# hot cue letters A-H, 5 x 7, for the overview's bigger marker (bit 7 = left)\nLETTERS5 = [\n ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],\n ['11110', '10001', '10001', '11110', '10001', '10001', '11110'],\n ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],\n ['11110', '10001', '10001', '10001', '10001', '10001', '11110'],\n ['11111', '10000', '10000', '11110', '10000', '10000', '11111'],\n ['11111', '10000', '10000', '11110', '10000', '10000', '10000'],\n ['01110', '10001', '10000', '10111', '10001', '10001', '01111'],\n ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],\n]\n\n# hot cue letters A-H, 3 x 5, rows top down: drawn black into a 9 x 5 marker\nLETTERS = ['010101111101101', '110101110101110', '011100100100011', '110101101101110',\n '111100110100111', '111100110100100', '011100101101011', '101101111101101']\n\n# data after the code (zero in the image)\nDATA = 0x08FD0000 # after the code (checked in patch())\nHOT_TABLE = DATA # 8 x 8: +0 time 1/150 s (-1 none), +4 RGB565, +6 hot cue number;\n # entry 0's +7 counts the loads (cue_get)\nSLOTS = 18 # triangles: memory list index 0-9, hot cues 10-17\nTRI_BUF = DATA + 0x40 # SLOTS x 160 bytes\nHOTBOX_W, HOTBOX_H = 16, 9 # the overview's hot cue marker: 11 x 9, then transparent; the\nHOTBOX_SIZE = 288 # blitter wants lines of a multiple of 16 bytes, as all the firmware's\nHOTBOX = (TRI_BUF + SLOTS * 160 + 31) & ~31 # 8 x HOTBOX_SIZE\nFAKE_RECS = HOTBOX + 8 * HOTBOX_SIZE # one 44-byte record, the detail waveform's\nDATA_END = FAKE_RECS + 44\nassert DATA_END <= CC_END\n\n\ndef rgb565(r, g, b):\n return (r & 0xF8) << 8 | (g & 0xFC) << 3 | b >> 3\n\n\nSOURCE = r'''\n; ---- cue_get: dbcl_GetUsbSdCueInfo(r4 connection, r5 track, r6 reply) plus colours\ncue_get:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r13,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n add #-20,r15 ; +0 wait's slot, +4/+8 message, +12 block, +16 size\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov.l K_cueinfo,r1\n jsr @r1\n nop\n mov r0,r12 ; the stock result, returned as it is\n mov.l K_hot,r1 ; no hot cues known\n mov #8,r2\n mov #-1,r3\ncg_clear:\n mov.l r3,@r1\n dt r2\n bf/s cg_clear\n add #8,r1\n mov.l K_hot,r1 ; one more load: entry 0's spare byte counts\n mov.b @(7,r1),r0 ; them (hotcuekeys reloads its slots on it)\n add #1,r0\n mov.b r0,@(7,r1)\n cmp/pl r12\n bf cg_early\n mov.l @r8,r11 ; the connection's socket side\n tst r11,r11\n bt cg_early\n mov.l K_check,r1\n jsr @r1\n mov r11,r4\n tst r0,r0\n bt cg_early\n mov.l K_alloc,r1\n jsr @r1\n mov #72,r4\n tst r0,r0\n bt cg_early\n mov.l r0,@(4,r15)\n mov.l r0,@(8,r15)\n mov #0,r1\n mov.l r1,@r15\n mov.l r1,@(12,r15)\n mov.l r1,@(16,r15)\n mov r8,r4\n mov r0,r5\n mov.l K_type,r6\n mov.l K_init,r1\n jsr @r1\n mov #8,r7 ; as dbcl_GetUsbSdCueInfo\n mov.l @(4,r15),r2\n mov.l r9,@(24,r2) ; the track\n mov #0,r1\n mov.l r1,@(28,r2)\n mov r11,r4\n mov.l @(8,r15),r5\n mov.l K_send,r1\n jsr @r1\n nop\n tst r0,r0\n bf cg_early\n mov r15,r1\n add #12,r1\n mov.l r1,@-r15 ; &block\n mov.l @(12,r15),r4 ; the message\n mov.l K_reply,r5\n mov r15,r6\n add #4,r6\n mov r15,r7\n mov.l K_wait,r1\n jsr @r1\n add #20,r7 ; &size\n add #4,r15\n mov.l @(12,r15),r13 ; the reply's entries\n tst r13,r13\n bt cg_early\n cmp/pz r0\n bf cg_free\n mov.l @(16,r15),r14\n add r13,r14 ; their end\n bra cg_entry\n nop\ncg_early:\n bra cg_out\n nop\ncg_entry:\n mov r13,r1\n add #16,r1\n cmp/hi r14,r1\n bt cg_free ; not even a header left\n mov.l @r13,r11 ; length\n mov #64,r1\n cmp/hs r1,r11\n bf cg_free\n mov r13,r1\n add r11,r1\n cmp/hi r14,r1\n bt cg_free ; runs past the end\n mov.l @(12,r13),r1 ; ms -> 1/150 s: ms * 3 / 20\n mov r1,r2\n add r1,r2\n add r1,r2\n mov.l K_div20,r1\n dmulu.l r1,r2\n sts mach,r9\n shlr2 r9\n shlr2 r9 ; r9 = time\n mov.b @(4,r13),r0\n extu.b r0,r6 ; r6 = hot cue number, 0 = memory cue\n tst r6,r6\n bf cg_hot\n mov #58,r0\n mov.b @(r0,r13),r0 ; colour id\n extu.b r0,r0\n tst r0,r0\n bt cg_next\n mov #8,r1\n cmp/hi r1,r0\n bt cg_next\n add r0,r0\n mov.l K_pal,r1\n add r1,r0\n mov.w @r0,r7\n bra cg_match\n extu.w r7,r7\ncg_hot:\n mov.l K_green,r7 ; no colour: rekordbox's default\n mov #82,r1 ; colour code, R, G, B, 2 bytes: 0x52 at least\n cmp/hs r1,r11\n bf cg_hstore\n mov #72,r0 ; the comment: its length in bytes at +0x48, text from +0x4A,\n mov.w @(r0,r13),r2 ; then 4 bytes, then colour code, R, G, B\n extu.w r2,r2 ; (the server's own entries: not checked against the length)\n add r13,r2\n add #78,r2\n mov.b @r2,r0 ; colour code; 0 = none\n tst r0,r0\n bt cg_hstore\n mov.b @(1,r2),r0 ; R\n extu.b r0,r7\n mov #-8,r0\n and r0,r7\n shll8 r7\n mov.b @(2,r2),r0 ; G\n extu.b r0,r0\n shlr2 r0\n shll2 r0\n shll r0\n shll2 r0\n or r0,r7\n mov.b @(3,r2),r0 ; B\n extu.b r0,r0\n shlr2 r0\n shlr r0\n or r0,r7\ncg_hstore:\n mov #9,r1 ; hot cues 1-8 into our table\n cmp/hs r1,r6\n bt cg_match\n mov r6,r1\n add #-1,r1\n shll2 r1\n shll r1\n mov.l K_hot,r2\n add r1,r2\n mov.l r9,@r2\n mov r7,r0\n mov.w r0,@(4,r2)\n mov r6,r0\n mov.b r0,@(6,r2)\ncg_match:\n ; mark the stock reply's entry with the same hot cue number and time\n mov.l @(24,r10),r1 ; entries\n tst r1,r1\n bt cg_next\n mov.l @(16,r10),r2\n mov.l @(20,r10),r3\n add r3,r2 ; memory + hot cue entries\n mov #16,r3\n cmp/hi r3,r2\n bf cg_m_ok\n mov r3,r2\ncg_m_ok:\n tst r2,r2\n bt cg_next\ncg_m:\n mov.b @(2,r1),r0\n extu.b r0,r0\n cmp/eq r6,r0\n bf cg_m_next\n mov.l @(12,r1),r0\n sub r9,r0 ; time difference, within one unit\n add #1,r0\n mov #2,r3\n cmp/hi r3,r0\n bt cg_m_next\n mov r7,r0\n mov.w r0,@(16,r1)\n mov r1,r3\n add #18,r3\n mov #1,r0\n mov.b r0,@r3\ncg_m_next:\n dt r2\n bf/s cg_m\n add #36,r1\ncg_next:\n bra cg_entry\n add r11,r13\ncg_free:\n mov.l @(12,r15),r4\n tst r4,r4\n bt cg_out\n mov.l K_free,r1\n jsr @r1\n nop\ncg_out:\n mov r12,r0\n add #20,r15\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r13\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nK_alloc: .long 0x089A791E\nK_check: .long 0x089736F2\nK_cueinfo: .long CUEINFO\nK_div20: .long 0xCCCCCCCD\nK_free: .long 0x08A5EBC2\nK_green: .long HOT_DEFAULT\nK_hot: .long HOT_TABLE\nK_init: .long 0x08973C50\nK_pal: .long PALETTE\nK_reply: .long 0x00004E02\nK_send: .long 0x089738F0\nK_type: .long 0x00002B04\nK_wait: .long 0x08974198\n\n; ---- colour_for: r4 time (1/150 s), r5 tolerance -> r0 RGB565 or -1, r1 slot\ncolour_for:\n mov.l L_memlist,r1\n mov #0,r2 ; slot\ncf_loop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt cf_hot\n mov #18,r0\n mov.b @(r0,r1),r0\n cmp/eq #1,r0\n bf cf_skip\n mov.l @(12,r1),r0\n sub r4,r0\n add r5,r0\n mov r5,r3\n add r5,r3\n cmp/hi r3,r0\n bt cf_skip\n mov.w @(16,r1),r0\n extu.w r0,r0\n rts\n mov r2,r1\ncf_skip:\n add #1,r2\n mov #10,r0\n cmp/hs r0,r2\n bf/s cf_loop\n add #36,r1\ncf_hot:\n mov.l L_hot,r1 ; then the hot cues: slots 10-17\n mov #10,r2\ncf_hloop:\n mov.l @r1,r0\n cmp/eq #-1,r0\n bt cf_hskip\n sub r4,r0\n add r5,r0\n mov r5,r3\n add r5,r3\n cmp/hi r3,r0\n bt cf_hskip\n mov.w @(4,r1),r0\n extu.w r0,r0\n rts\n mov r2,r1\ncf_hskip:\n add #1,r2\n mov #18,r0\n cmp/hs r0,r2\n bf/s cf_hloop\n add #8,r1\n rts\n mov #-1,r0\n\n; ---- make_tri: r4 slot, r5 RGB565 -> r0 its triangle (uncached address)\nmake_tri:\n mov r4,r0\n shll2 r0\n add r4,r0\n shll2 r0\n shll2 r0\n shll r0 ; slot * 160\n mov.l L_tribuf,r1\n add r0,r1 ; destination, through P2\n mov r1,r6\n mov #10,r0\n cmp/hs r0,r4\n bt mt_box\n mov.l L_tripix,r2\n mov.l L_red,r3\n mov #80,r7\nmt_loop:\n mov.w @r2+,r0\n extu.w r0,r0\n cmp/eq r3,r0\n bf mt_keep\n mov r5,r0\nmt_keep:\n mov.w r0,@r1\n dt r7\n bf/s mt_loop\n add #2,r1\n rts\n mov r6,r0\nmt_box:\n mov r4,r0 ; the letter, left-aligned in a word\n add #-10,r0\n shll2 r0\n mov.l L_font,r2\n mov.l @(r0,r2),r3\n mov.l L_key,r2 ; transparent\n mov #5,r7\nmb_row:\n mov #0,r4\nmb_col:\n mov #9,r0\n cmp/hs r0,r4\n bt mb_t ; past the marker\n mov #3,r0\n cmp/hs r0,r4\n bf mb_c\n mov #6,r0\n cmp/hs r0,r4\n bt mb_c\n shll r3 ; the letter's next pixel into T\n bf mb_c\n bra mb_st\n mov #0,r0 ; black\nmb_c:\n bra mb_st\n mov r5,r0\nmb_t:\n mov r2,r0\nmb_st:\n mov.w r0,@r1\n add #2,r1\n add #1,r4\n mov #16,r0\n cmp/hs r0,r4\n bf mb_col\n dt r7\n bf mb_row\n rts\n mov r6,r0\n\n; ---- mk_copy: memcpy(r4, r5, r6) for the waveform preparer; filling the\n; marker records, it adds the hot cues to them as memory-cue markers.\nmk_copy:\n mov.l r8,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov.l L_memcpy,r1\n jsr @r1\n nop\n mov.l L_mkrecs,r1\n cmp/eq r1,r8\n bf mk_out\n mov.l r0,@-r15 ; memcpy's result\n mov r8,r1\n mov #101,r2\n mov #-1,r3\n extu.b r3,r3 ; 0xff ends the list\nmk_find:\n mov.l @r1,r0\n cmp/eq r3,r0\n bt mk_add\n dt r2\n bf/s mk_find\n add #16,r1\n bra mk_done\n nop\nmk_add:\n mov.l L_hot,r3\n mov #8,r7\nmk_h:\n mov.l @r3,r0 ; time\n cmp/eq #-1,r0\n bt mk_hn\n tst r2,r2\n bt mk_done ; the list is full\n mov.l r0,@(8,r1)\n mov #0,r0\n mov.l r0,@r1 ; a memory cue\n mov.l r0,@(4,r1)\n mov #-1,r0\n mov.l r0,@(12,r1)\n add #16,r1\n add #-1,r2\nmk_hn:\n dt r7\n bf/s mk_h\n add #8,r3\nmk_done:\n mov.l @r15+,r0\nmk_out:\n lds.l @r15+,pr\n rts\n mov.l @r15+,r8\n\n; ---- det_res: the resource resolver, for the detail waveform (r9 = its column).\n; Every 0xC5 it is asked for becomes a 16 x 8 marker of ours, as the renderer\n; (patched) copies 11 x 8: a memory cue's triangle in rows 3-7 (coloured, or\n; the stock red one), a hot cue's box with its letter in rows 0-7.\ndet_res:\n mov.l L_resolve,r1\n mov.l L_triid,r0\n cmp/eq r0,r4\n bt dr_mine\n jmp @r1\n nop\ndr_mine:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r9,r8\n jsr @r1\n nop\n mov r0,r10 ; the record\n tst r10,r10\n bt dr_out\n mov r8,r4\n bsr colour_for\n mov #16,r5 ; zoomed out, a column stands for up to 16\n mov r1,r9 ; slot\n cmp/eq #-1,r0\n bt dr_plain\n mov r0,r5\n mov r9,r4\n mov #10,r2\n cmp/hs r2,r4\n bt dr_hot\n bsr make_tri\n nop\n mov #3,r11 ; triangle: rows 3-7\n bra dr_copy\n mov #5,r6\ndr_hot:\n bsr make_hotbox\n nop\n mov #0,r11 ; box: rows 0-7\n bra dr_copy\n mov #8,r6\ndr_plain:\n mov #18,r9 ; no colour: the stock triangle\n mov.l L_tripix,r0\n mov #3,r11\n mov #5,r6\ndr_copy:\n mov r0,r3 ; source, 16 pixels a line\n mov.l L_detbuf,r2\n mov r9,r0\n shll8 r0\n add r0,r2\n mov r2,r8 ; this slot's 16 x 8\n mov.l L_key,r0\n mov #64,r7\n shll r7\n mov r2,r1\ndr_fill:\n mov.w r0,@r1\n dt r7\n bf/s dr_fill\n add #2,r1\n mov r11,r0\n shll2 r0\n shll2 r0\n shll r0\n add r0,r2\ndr_row:\n mov #16,r7\ndr_px:\n mov.w @r3+,r0\n mov.w r0,@r2\n dt r7\n bf/s dr_px\n add #2,r2\n dt r6\n bf dr_row\n mov.l L_fake,r2 ; the record, with our size and pixels\n mov r10,r3\n mov #11,r4\ndr_rec:\n mov.l @r3+,r1\n mov.l r1,@r2\n dt r4\n bf/s dr_rec\n add #4,r2\n add #-44,r2\n mov.l L_detsize,r1\n mov.l r1,@(4,r2)\n mov.l r8,@(12,r2)\n mov r2,r10\ndr_out:\n mov r10,r0\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n; ---- ov_draw: draw_bitmap(r4, r5 x, r6 y, r7 resource) for the overview (r13 = its record)\nov_draw:\n mov.l L_draw,r1\n mov.l L_triid,r0\n cmp/eq r0,r7\n bt od_mine\n jmp @r1\n nop\nod_mine:\n mov.l r13,@-r15\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n mov.l r12,@-r15\n mov.l r14,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov r6,r10\n mov r7,r11\n mov.l L_ovrecs,r1 ; which record: 12 bytes each, 16 in the other list\n mov r13,r2\n sub r1,r2\n mov #0,r3\n mov #101,r4\nod_idx:\n cmp/pl r2\n bf od_found\n add #-12,r2\n dt r4\n bf/s od_idx\n add #16,r3\n bra od_plain\n nop\nod_found:\n tst r2,r2\n bf od_plain ; not on a record\n mov.l L_mkrecs,r1\n add r3,r1\n mov.l @(8,r1),r4\n bsr colour_for\n mov #0,r5\n cmp/eq #-1,r0\n bt od_plain\n mov r1,r4\n mov #10,r2\n cmp/hs r2,r4\n bt od_hot\n bsr make_tri\n mov r0,r5\n mov.l L_trisize,r13 ; 16 x 5, the triangle's own size\n bra od_pix\n nop\nod_hot:\n bsr make_hotbox ; a hot cue: the bigger box\n mov r0,r5\n mov.l L_boxsize,r13\nod_pix:\n mov.l L_restab,r1 ; the triangle's record\n mov.l @r1,r12\n mov.l L_trirec,r1\n add r1,r12\n mov.l @(32,r12),r14 ; its pixel offset and size, put back below\n mov.l @(4,r12),r1\n mov.l r13,@(4,r12)\n mov r1,r13\n mov.l L_p0mask,r1 ; the blitter gets the plain address\n and r1,r0\n mov.l L_imgbase,r1\n sub r1,r0\n mov.l r0,@(32,r12)\n mov r8,r4\n mov r9,r5\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\n mov.l r13,@(4,r12)\n bra od_out\n mov.l r14,@(32,r12)\nod_plain:\n mov r8,r4\n mov r9,r5\n mov r10,r6\n mov.l L_draw,r1\n jsr @r1\n mov r11,r7\nod_out:\n lds.l @r15+,pr\n mov.l @r15+,r14\n mov.l @r15+,r12\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n mov.l @r15+,r8\n rts\n mov.l @r15+,r13\n\n .align 4\nL_cueinfo: .long CUEINFO\nL_check: .long 0x089736F2\nL_alloc: .long 0x089A791E\nL_init: .long 0x08973C50\nL_send: .long 0x089738F0\nL_wait: .long 0x08974198\nL_free: .long 0x08A5EBC2\nL_type: .long 0x00002B04\nL_reply: .long 0x00004E02\nL_div20: .long 0xCCCCCCCD\nL_hot: .long HOT_TABLE\nL_memlist: .long MEMLIST\nL_tribuf: .long TRI_BUF_P2\nL_tripix: .long TRI_PIXELS\nL_red: .long TRI_RED\nL_resolve: .long RESOLVE\nL_draw: .long DRAW\nL_triid: .long TRI_ID\nL_fake: .long FAKE_RECS\nL_detbuf: .long DET_BUF\nL_detsize: .long 0x00080010\nL_ovrecs: .long OV_RECS\nL_mkrecs: .long MK_RECS\nL_restab: .long RES_TABLE_PTR\nL_trirec: .long TRI_REC_OFF\nL_imgbase: .long 0x08100000\nL_p0mask: .long 0x1FFFFFFF\nL_pal: .long PALETTE\nL_green: .long HOT_DEFAULT\nL_font: .long FONT\nL_key: .long 0xF81F\nL_memcpy: .long MEMCPY\nL_trisize: .long 0x00050010\nL_boxsize: .long BOXSIZE\n\n; ---- make_hotbox: r4 slot (10-17), r5 RGB565 -> r0 the overview's hot cue\n; marker, 11 x 9 in its colour with the letter in black (uncached address)\nmake_hotbox:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov r4,r6\n add #-10,r6 ; 0 = A\n mov r6,r1 ; HOTBOX_SIZE (288) * index\n shll8 r1\n mov r6,r2\n shll2 r2\n shll2 r2\n shll r2\n add r2,r1\n mov.l M_hotbox,r2\n add r2,r1\n mov r1,r9 ; returned\n mov r6,r2 ; its letter: 7 bytes from FONT5 + 7 * index\n shll2 r2\n shll r2\n sub r6,r2\n mov.l M_font5,r3\n add r2,r3\n mov.l M_key,r2\n mov #0,r6 ; row\nhb_row:\n mov #0,r4 ; the letter's bits in this row\n mov r6,r0\n cmp/eq #0,r0\n bt hb_cols\n cmp/eq #8,r0\n bt hb_cols\n add #-1,r0\n mov.b @(r0,r3),r4\n extu.b r4,r4\nhb_cols:\n mov #0,r8 ; column\nhb_col:\n mov #11,r0\n cmp/hs r0,r8\n bt hb_t\n mov #3,r0\n cmp/hs r0,r8\n bf hb_c\n mov #8,r0\n cmp/hs r0,r8\n bt hb_c\n mov r4,r0\n tst #128,r0\n bt/s hb_c\n shll r4\n bra hb_st\n mov #0,r0 ; black\nhb_c:\n bra hb_st\n mov r5,r0\nhb_t:\n mov r2,r0\nhb_st:\n mov.w r0,@r1\n add #2,r1\n add #1,r8\n mov #16,r0\n cmp/hs r0,r8\n bf hb_col\n add #1,r6\n mov #9,r0\n cmp/hs r0,r6\n bf hb_row\n mov r9,r0\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nM_hotbox: .long HOTBOX_P2\nM_font5: .long FONT5\nM_key: .long 0xF81F\nFONT:\n'''\n\n\ndef routine():\n \"\"\"The code at CC_BASE, the memory-cue palette after it, and the labels.\"\"\"\n syms = {\n 'CUEINFO': CUEINFO, 'RESOLVE': RESOLVE, 'DRAW': DRAW, 'HOT_TABLE': HOT_TABLE,\n 'MEMLIST': MEMLIST, 'TRI_BUF_P2': TRI_BUF | 0xA0000000, 'TRI_PIXELS': TRI_PIXELS,\n 'TRI_RED': TRI_RED, 'TRI_ID': TRI_ID, 'FAKE_RECS': FAKE_RECS, 'OV_RECS': OV_RECS,\n 'MK_RECS': MK_RECS, 'RES_TABLE_PTR': RES_TABLE_PTR, 'TRI_REC_OFF': TRI_ID * 44,\n 'HOT_DEFAULT': HOT_DEFAULT, 'MEMCPY': MEMCPY, 'HOTBOX_P2': HOTBOX | 0xA0000000,\n 'BOXSIZE': HOTBOX_W | HOTBOX_H << 16, 'DET_BUF': DET_BUF,\n }\n font = b''.join(struct.pack(' r0 the named cue to show (CN_TABLE entry) or 0; r1-r7 used\ncn_pick:\n mov.l P_hot,r6\n mov.b @(7,r6),r0 ; the table is this load's?\n mov.l P_load,r1\n mov.b @r1,r1\n cmp/eq r1,r0\n bf pk_none\n mov.l P_count,r1\n mov.l @r1,r3\n tst r3,r3\n bt pk_none\n mov.l P_pos,r1\n mov.l @r1,r4\n mov.l P_behind,r1\n sub r1,r4 ; from a second behind\n mov.l P_table,r2\n mov #0,r0 ; best so far\npk_loop:\n mov.l @r2,r1 ; ms\n cmp/ge r4,r1\n bf pk_next ; behind\n mov.l @(8,r2),r5 ; a hot cue still where it was?\n tst r5,r5\n bt pk_ok\n mov #9,r7\n cmp/hs r7,r5\n bt pk_next\n add #-1,r5\n shll2 r5\n shll r5\n add r6,r5\n mov.l @r5,r5 ; HOT_TABLE's time, 1/150 s\n mov.l @(4,r2),r7\n sub r7,r5\n add #1,r5\n mov #2,r7\n cmp/hi r7,r5\n bt pk_next\npk_ok:\n tst r0,r0\n bt pk_take\n mov.l @r0,r7\n cmp/ge r7,r1\n bt pk_next ; not nearer\npk_take:\n mov r2,r0\npk_next:\n dt r3\n bf/s pk_loop\n add #64,r2\n rts\n nop\npk_none:\n rts\n mov #0,r0\n\n; ---- cn_draw: the text widget's draw method (r4 renderer, r5 widget)\ncn_draw:\n mov.l r8,@-r15\n mov.l r9,@-r15\n mov.l r10,@-r15\n mov.l r11,@-r15\n sts.l pr,@-r15\n mov r4,r8\n mov r5,r9\n mov.l @(4,r5),r1 ; the title?\n mov.l D_title,r2\n cmp/eq r2,r1\n bf dr_plain\n mov.l D_depth,r1 ; BROWSE closed?\n mov.b @r1,r1\n tst r1,r1\n bf dr_plain\n mov.l @(40,r9),r1 ; the title white? (dimmed behind a popup, as MENU's: no name)\n mov #-1,r2\n cmp/eq r2,r1\n bf dr_plain\n bsr cn_pick\n nop\n tst r0,r0\n bt dr_plain\n mov r0,r10 ; the cue\n mov.l D_label,r2 ; the label: \"A \" for a hot cue, then the name\n mov.l @(8,r10),r1\n tst r1,r1\n bt dr_name\n add #64,r1 ; 'A' - 1 + number\n mov r1,r0\n mov.w r0,@r2\n mov #32,r0\n mov.w r0,@(2,r2)\n add #4,r2\ndr_name:\n mov r10,r1\n add #16,r1\ndr_ncopy:\n mov.w @r1+,r0\n mov.w r0,@r2\n tst r0,r0\n bf/s dr_ncopy\n add #2,r2\n mov.l @(28,r9),r11 ; the title, narrowed\n mov r11,r1\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_tw,r2\n or r2,r1\n mov.l r1,@(28,r9)\n mov r8,r4\n mov r9,r5\n mov.l D_next,r1\n jsr @r1\n nop\n mov.l r11,@(28,r9)\n mov.l D_clone,r2 ; the copy: our place, text, colour\n mov r9,r1\n mov #24,r3\ndr_clone:\n mov.l @r1+,r0\n mov.l r0,@r2\n dt r3\n bf/s dr_clone\n add #4,r2\n mov.l D_clone,r5\n mov.l @(24,r9),r1 ; y stays, x ours\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_lx,r2\n or r2,r1\n mov.l r1,@(24,r5)\n mov r11,r1\n mov.l D_hmask,r2\n and r2,r1\n mov.l D_lw,r2\n or r2,r1\n mov.l r1,@(28,r5)\n mov.l @(12,r10),r1\n mov.l r1,@(40,r5)\n mov.l D_label,r1\n mov.l r1,@(52,r5)\n mov r8,r4\n mov.l D_next,r1\n jsr @r1\n nop\n bra dr_out\n nop\ndr_plain:\n mov r8,r4\n mov r9,r5\n mov.l D_next,r1\n jsr @r1\n nop\ndr_out:\n lds.l @r15+,pr\n mov.l @r15+,r11\n mov.l @r15+,r10\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\n\n .align 4\nP_behind: .long BEHIND_MS\nP_count: .long CN_COUNT\nP_hot: .long HOT_TABLE\nP_load: .long CN_LOAD\nP_pos: .long POS_MS\nP_table: .long CN_TABLE\nD_clone: .long CN_CLONE\nD_depth: .long BROWSE_DEPTH\nD_hmask: .long 0xFFFF0000\nD_label: .long CN_LABEL\nD_lw: .long LABEL_W\nD_lx: .long LABEL_X\nD_next: .long NEXT_DRAW\nD_title: .long TITLE_ID\nD_tw: .long TITLE_W\nMEMORY_TAB:\n'''\n\n\ndef routine(next_draw):\n src = SOURCE + ''.join(' .long 0x%08X\\n' % c for c in MEMORY_ABGR)\n return sh4asm.assemble(src, CN_CODE, {\n 'CN_COUNT': CN_COUNT, 'CN_LOAD': CN_LOAD, 'CN_TABLE': CN_TABLE, 'CN_LABEL': CN_LABEL,\n 'CN_CLONE': CN_CLONE, 'CN_MAX': CN_MAX, 'CN_NAME': CN_NAME, 'FREE': FREE,\n 'HOT_TABLE': HOT_TABLE, 'POS_MS': POS_MS, 'BROWSE_DEPTH': BROWSE_DEPTH,\n 'TITLE_ID': TITLE_ID, 'TITLE_W': TITLE_W, 'LABEL_X': LABEL_X, 'LABEL_W': LABEL_W,\n 'BEHIND_MS': BEHIND_MS, 'NO_COLOUR': NO_COLOUR, 'NEXT_DRAW': next_draw})\n\n\ndef patch(app, stock):\n \"\"\"The application with CUE NAMES added (after cuecolours and, if chosen,\n keynotation); `stock` is the unmodified 1.31 application.\"\"\"\n out = bytearray(app)\n word = lambda a: struct.unpack_from(' 0xC00487A4)\nTRAMP_TARGET = 0xC00487A4\nMB = 0x1183E000\nDP = 0x11806900\nHEADS = 0x1182E0E0\nLINKS = 0xC1F38B90\nPLAY_HEAD_DP = 969 # byte: the playing head's index\nMARGIN = 750 # slots (10 s) a forward jump leaves decoded ahead (2 s: EMERGENCY LOOP\n # after rapid 8-beat jumps, emulator too; 5 s was fine there)\nBLOCKS = ((0xC0000040, 0xC0000080), (0xC00000A0, 0xC0000100), (0xC0000120, 0xC00001E0))\nFREE_PACKETS = (struct.pack('<8I', *([0x0c6e0c6e] * 7 + [0xefe00000])), # compact nops only\n struct.pack('<8I', *([0x0c6e0c6e] * 6 + [0, 0xe7e00000])))\n\n\ndef code():\n \"\"\"[(addr, word)] of dj_poll.\"\"\"\n a = c6asm.Asm(BLOCKS[0][0])\n o = a.op\n # block 1: the mailbox; e = heads + 30 * h\n o('const', MB, 'a3')\n o('ldw', 'a4', 'a3', 0)\n o('nop', 4)\n o('cmpeq', 1, 'a4', 'a1')\n o('b', 'tail', p='!a1')\n o('nop', 5)\n o('ldw', 'b4', 'a3', 4) # n\n o('lddp', 'ldbu', 'b5', PLAY_HEAD_DP) # h\n o('nop', 4)\n o('shl', 'b5', 5, 'b6')\n o('shl', 'b5', 1, 'b7')\n o('sub', 'b6', 'b7', 'b6') # 30 h\n o('b', 'blk2')\n o('nop', 5)\n # block 2: the head's fields, the direction\n a.org(BLOCKS[1][0])\n a.label('blk2')\n o('const', HEADS, 'b8')\n o('add', 'b8', 'b6', 'b6') # e\n o('ldhu', 'a5', 'b6', 2) # cur\n o('ldh', 'a6', 'b6', 6) # behind\n o('ldh', 'a7', 'b6', 8) # ahead\n o('nop', 4)\n o('stw', 'a5', 'a3', 16)\n o('stw', 'a6', 'a3', 24)\n o('stw', 'a7', 'a3', 28)\n o('mvk', 2, 'a9') # refused unless done\n o('const', LINKS, 'a4')\n o('cmplt', 0, 'b4', 'b0') # n > 0\n o('b', 'fwd', p='b0')\n o('nop', 5)\n o('cmpgt', 0, 'b4', 'b1') # n < 0\n o('b', 'ok', p='!b1') # n = 0\n o('nop', 5)\n o('sub', 0, 'b4', 'b2') # m = -n\n o('mv', 'b2', 'a8')\n o('sub', 'a6', 'a8', 'a8') # behind - m\n o('b', 'blk3')\n o('nop', 5)\n # block 3: the walk\n a.org(BLOCKS[2][0])\n a.label('blk3')\n o('cmplt', 0, 'a8', 'a1') # at least one slot left behind\n o('b', 'done', p='!a1')\n o('nop', 5)\n a.label('loopb')\n o('addaw', 'a4', 'a5', 'a2')\n o('ldhu', 'a5', 'a2', 0) # prev\n o('nop', 4)\n o('add', -1, 'b2', 'b2')\n o('b', 'loopb', p='b2')\n o('nop', 5)\n o('sub', 0, 'b4', 'b7')\n o('mv', 'b7', 'a8') # m\n o('sub', 'a6', 'a8', 'a6') # behind - m\n o('add', 'a8', 'a7', 'a7') # ahead + m\n o('b', 'store')\n o('nop', 5)\n a.label('fwd')\n o('mv', 'b4', 'a8')\n o('sub', 'a7', 'a8', 'a8') # ahead - n\n o('mvk', MARGIN, 'b7')\n o('cmplt', 'b7', 'a8', 'b1') # more than MARGIN left ahead\n o('b', 'done', p='!b1')\n o('nop', 5)\n o('mv', 'b4', 'b2')\n a.label('loopf')\n o('addaw', 'a4', 'a5', 'a2')\n o('ldhu', 'a5', 'a2', 2) # next\n o('nop', 4)\n o('add', -1, 'b2', 'b2')\n o('b', 'loopf', p='b2')\n o('nop', 5)\n o('mv', 'b4', 'a8')\n o('add', 'a8', 'a6', 'a6') # behind + n\n o('sub', 'a7', 'a8', 'a7') # ahead - n\n a.label('store')\n o('sth', 'a5', 'b6', 2)\n o('sth', 'a6', 'b6', 6)\n o('sth', 'a7', 'b6', 8)\n o('sth', 'a5', 'b6', 14) # the point the decoder fills from (set on a\n o('sth', 'a6', 'b6', 16) # pause): moved along, so standing it decodes\n o('sth', 'a7', 'b6', 18) # on from the new place\n o('stw', 'a5', 'a3', 20)\n a.label('ok')\n o('mvk', 1, 'a9')\n a.label('done')\n o('stw', 'a9', 'a3', 8)\n o('ldw', 'a8', 'a3', 12)\n o('nop', 4)\n o('add', 1, 'a8', 'a8')\n o('stw', 'a8', 'a3', 12)\n o('mvk', 0, 'a8')\n o('stw', 'a8', 'a3', 0)\n a.label('tail')\n o('b', TRAMP_TARGET)\n o('nop', 5)\n words = a.assemble()\n for at, _ in words:\n assert any(lo <= at < hi for lo, hi in BLOCKS), 'dspjump: 0x%08x is past its blocks' % at\n return words\n\n\ndef packets(words):\n \"\"\"{packet addr: 8 words}: our words, the rest of each packet nop.\"\"\"\n out = {}\n for at, w in words:\n fp = at & ~31\n out.setdefault(fp, [0] * 8)[(at - fp) // 4] = w\n return out\n\n\ndef set_cst16(word, value):\n return (word & ~(0xFFFF << 7)) | (value & 0xFFFF) << 7\n\n\ndef patch(app, stock):\n out = bytearray(app)\n for fp, ws in packets(code()).items():\n off = B_IMG - BASE + (fp - 0xC0000000)\n if stock[off:off + 32] not in FREE_PACKETS or out[off:off + 32] != stock[off:off + 32]:\n raise SystemExit('patch_cdj900nxs: dspjump: the vector packet 0x%08x is not the expected one' % fp)\n out[off:off + 32] = struct.pack('<8I', *ws)\n entry = BLOCKS[0][0]\n for at, want, value in ((TRAMP_MVK, 0x0543d22a, entry), (TRAMP_MVKH, 0x0560026a, entry >> 16)):\n off = A_IMG - BASE + (at - A_AT)\n w = struct.unpack_from(' red, grey -> dark red\n\nSOURCE = r'''\n; ---- ew_draw: draw_bitmap(r4, r5 x, r6 y, r7 resource) for the playing-address bar\new_draw:\n mov.l E_draw,r0\n mov.l E_res,r1\n cmp/eq r1,r7\n bt ew_bar\n jmp @r0\n nop\new_bar:\n mov.l E_len,r1 ; length in ms: 1/150 s * 20 / 3\n mov.l @r1,r1\n tst r1,r1\n bt ew_plain\n mov r1,r2\n shll2 r2\n shll2 r2\n shll2 r1\n add r2,r1 ; * 20\n mov.l E_div3,r2\n dmulu.l r2,r1\n sts mach,r1\n shlr r1 ; / 3\n mov.l E_pos,r2\n mov.l @r2,r2\n sub r2,r1 ; left\n cmp/pl r1\n bf ew_plain\n mov.l E_warn,r2\n cmp/hs r2,r1\n bt ew_plain\n mov.l r8,@-r15 ; red: the record pointed at RED_BAR for this call\n mov.l r9,@-r15\n sts.l pr,@-r15\n mov.l E_tab,r8\n mov.l @r8,r8\n mov.l E_recoff,r1\n add r1,r8\n mov.l @(32,r8),r9\n mov.l E_redoff,r1\n mov.l r1,@(32,r8)\n jsr @r0\n nop\n mov.l r9,@(32,r8)\n lds.l @r15+,pr\n mov.l @r15+,r9\n rts\n mov.l @r15+,r8\new_plain:\n jmp @r0\n nop\n\n .align 4\nE_div3: .long 0xAAAAAAAB\nE_draw: .long DRAW\nE_len: .long TRACK_LEN\nE_pos: .long POS_MS\nE_recoff: .long REC_OFF\nE_redoff: .long RED_OFF\nE_res: .long RES_BAR\nE_tab: .long RES_TABLE_PTR\nE_warn: .long WARN_MS\n'''\n\n\ndef routine():\n return sh4asm.assemble(SOURCE, EW_CODE, {\n 'DRAW': DRAW, 'TRACK_LEN': TRACK_LEN, 'POS_MS': POS_MS, 'REC_OFF': RES_BAR * 44,\n 'RED_OFF': RED_BAR - RES_BASE, 'RES_BAR': RES_BAR, 'RES_TABLE_PTR': RES_TABLE_PTR,\n 'WARN_MS': WARN_MS})\n\n\ndef patch(app, stock):\n \"\"\"The application with END WARNING added; `stock` is the unmodified 1.31\n application, which every replaced byte is checked against.\"\"\"\n out = bytearray(app)\n word = lambda buf, a: struct.unpack_from('