Builder, upcoming page, mods page for the current mods
- builder.html: build C900NXS.UPD in the browser (official 1.31 update in, mods ticked with their dependencies, no-liability and Claude Code confirmations, download). Ports of upd.py, the LZSS packer and the mods' application; the result is checked against the SHA-256 the Python patcher gives and is byte for byte the patcher's file. - builder/data.js from tools/make_builder_data.py: the mods' bytes per combination; firmware parts (UTILITY table, HOT KEYS box, the asset blob's resource table) are made from the user's own update. - index.html: rgb_waveform, extended_bpm, phasemeter, cuecolours, hotcuekeys (experimental), ledsaver, credit; stable/experimental and a green "ran on a player" dot. - upcoming.html: beat jump, stems, uniform Camelot / Open Key, 3-band waveform. - docs.html: the builder, the new mod names, hot cue loading and locks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f931ca0423
commit
e4728b16ea
12 files changed
+1347
-145
No files matched your search
+118
@@ -0,0 +1,118 @@
|
||||
// The builder page: file, mods with their dependencies, the two
|
||||
// confirmations, build and download (the work is in builder.js).
|
||||
(function () {
|
||||
'use strict';
|
||||
var MODS = [
|
||||
{ id: 'extended_bpm', name: 'Extended BPM', by: 'arnoldasr', state: 'stable',
|
||||
what: 'BPM with two decimals: the tenths as a small digit above, the hundredths below.' },
|
||||
{ id: 'phasemeter', name: 'Phase meter', by: 'arnoldasr', state: 'stable',
|
||||
what: 'Two-line phase meter instead of the four boxes; UTILITY › PHASE METER (was SLIP FLASHING) switches it.' },
|
||||
{ id: 'rgb_waveform', name: 'RGB waveform', by: 'arnoldasr', state: 'stable',
|
||||
what: 'The big waveform in RGB colours, the overview as a red-to-white gradient.' },
|
||||
{ id: 'cuecolours', name: 'Cue colours', by: 'ours', state: 'stable',
|
||||
what: 'Memory cues in their rekordbox colours; rekordbox hot cues shown as lettered boxes.' },
|
||||
{ id: 'hotcuekeys', name: 'Hot cue keys', by: 'ours', state: 'experimental', needs: 'cuecolours',
|
||||
what: 'Hot cues A–C on the BEAT DIVIDE keys: play, gate, set and delete, saved on the USB, loaded in the background.' },
|
||||
{ id: 'ledsaver', name: 'LED animation', by: 'ours', state: 'stable',
|
||||
what: 'While the screen saver dims the display, the button LEDs and the jog rings play an animation. UTILITY › LED ANIMATION.' },
|
||||
{ id: 'credit', name: 'Boot text', by: 'ours', state: 'stable',
|
||||
what: 'The boot screen says “Pionaarr Mods 1.0” instead of “Ver 1.31”.' }
|
||||
];
|
||||
var chosen = {};
|
||||
MODS.forEach(function (m) { chosen[m.id] = m.state === 'stable'; });
|
||||
var file = null;
|
||||
|
||||
function byId(id) { return document.getElementById(id); }
|
||||
function needsOf(id) { var m = MODS.filter(function (x) { return x.id === id; })[0]; return m && m.needs; }
|
||||
|
||||
function toggle(id, on) {
|
||||
chosen[id] = on;
|
||||
var note = '';
|
||||
if (on && needsOf(id) && !chosen[needsOf(id)]) { chosen[needsOf(id)] = true; note = 'Ticked ' + needsOf(id) + ' too: ' + id + ' needs it.'; }
|
||||
if (!on) MODS.forEach(function (m) {
|
||||
if (m.needs === id && chosen[m.id]) { chosen[m.id] = false; note = 'Unticked ' + m.id + ' too: it needs ' + id + '.'; }
|
||||
});
|
||||
byId('pick-note').textContent = note;
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
var box = byId('picks');
|
||||
box.innerHTML = '';
|
||||
MODS.forEach(function (m) {
|
||||
var row = document.createElement('label');
|
||||
row.className = 'pick' + (chosen[m.id] ? ' on' : '');
|
||||
row.innerHTML =
|
||||
'<input type="checkbox"' + (chosen[m.id] ? ' checked' : '') + '>' +
|
||||
'<span class="pick-main"><span class="pick-name">' + m.name + ' <code>' + m.id + '</code></span>' +
|
||||
'<span class="pick-what">' + m.what + '</span>' +
|
||||
'<span class="pick-by">' + (m.by === 'ours' ? 'ours · made with Claude Code' : 'by arnoldasr') +
|
||||
(m.needs ? ' · needs <code>' + m.needs + '</code>' : '') + '</span></span>' +
|
||||
'<span class="state ' + m.state + '">' + m.state + '</span>';
|
||||
row.querySelector('input').addEventListener('change', function (e) { toggle(m.id, e.target.checked); });
|
||||
box.appendChild(row);
|
||||
});
|
||||
ready();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var why = !file ? 'Choose the update file first.'
|
||||
: !byId('agree-risk').checked || !byId('agree-ai').checked ? 'Confirm both points above to build.' : '';
|
||||
byId('build').disabled = !!why;
|
||||
byId('build-why').textContent = why;
|
||||
}
|
||||
|
||||
function setFile(f) {
|
||||
file = f;
|
||||
byId('file-state').textContent = f ? f.name + ' · ' + (f.size / 1048576).toFixed(1) + ' MB (checked when you build)' : 'No file yet';
|
||||
byId('drop').classList.toggle('has', !!f);
|
||||
byId('result').hidden = true;
|
||||
ready();
|
||||
}
|
||||
|
||||
function log(msg, cls) {
|
||||
var l = byId('log');
|
||||
l.hidden = false;
|
||||
var p = document.createElement('div');
|
||||
if (cls) p.className = cls;
|
||||
p.textContent = msg;
|
||||
l.appendChild(p);
|
||||
l.scrollTop = l.scrollHeight;
|
||||
}
|
||||
|
||||
var url = null;
|
||||
function run() {
|
||||
var names = MODS.map(function (m) { return m.id; }).filter(function (id) { return chosen[id]; });
|
||||
byId('build').disabled = true;
|
||||
byId('result').hidden = true;
|
||||
byId('log').innerHTML = '';
|
||||
log('mods: ' + (names.length ? names.join(', ') : 'none (stock)'));
|
||||
file.arrayBuffer().then(function (buf) {
|
||||
return window.CDJBuilder.build(new Uint8Array(buf), names, log);
|
||||
}).then(function (r) {
|
||||
log('done: every check passed', 'ok');
|
||||
if (url) URL.revokeObjectURL(url);
|
||||
url = URL.createObjectURL(new Blob([r.upd], { type: 'application/octet-stream' }));
|
||||
byId('download').href = url;
|
||||
byId('sum').innerHTML = 'SHA-256 of the file <code>' + r.updSha + '</code><br>of the application <code>' + r.appSha + '</code>';
|
||||
var ul = byId('checks');
|
||||
ul.innerHTML = '';
|
||||
r.checks.forEach(function (c) { var li = document.createElement('li'); li.textContent = c; ul.appendChild(li); });
|
||||
byId('result').hidden = false;
|
||||
}).catch(function (e) {
|
||||
log('stopped: ' + e.message, 'bad');
|
||||
}).then(ready);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
render();
|
||||
byId('file').addEventListener('change', function (e) { setFile(e.target.files[0] || null); });
|
||||
var drop = byId('drop');
|
||||
['dragenter', 'dragover'].forEach(function (t) { drop.addEventListener(t, function (e) { e.preventDefault(); drop.classList.add('over'); }); });
|
||||
['dragleave', 'drop'].forEach(function (t) { drop.addEventListener(t, function (e) { e.preventDefault(); drop.classList.remove('over'); }); });
|
||||
drop.addEventListener('drop', function (e) { if (e.dataTransfer.files[0]) setFile(e.dataTransfer.files[0]); });
|
||||
byId('agree-risk').addEventListener('change', ready);
|
||||
byId('agree-ai').addEventListener('change', ready);
|
||||
byId('build').addEventListener('click', run);
|
||||
});
|
||||
})();
|
||||
Reference in new issue
Block a user