*read *audio.1.sg *bytecodeparsers *_runtime_helpers

audio.1.sg updated: see https://github.com/Kaens/audio1sg/commits/

chunkparsers: cosmetic changes
bytecodeparsers:
 - functions repositioned
 - MUAP98CMDStr debugged
 - parseMDGYM improved
 - parseYM3812RegLog sped up a bit
 - cosmetics

_runtime_helpers:
 - startsWithCI & endsWithCI improved
 - cosmetics

read:
 - "var" removed from global constants
 - certain popular PETSCII encoding flavours added
 - function descriptions made compatible
 - decEncoding & decAnsi now support full 256-char layouts
 - isWithinRanges added: sugar combining multiple isWithin
 - 80-bit float value reader readFloat80 added here, until it's in the C++ codebase
 - debugged firstNotOf, isAllZeroes, outArray, secondsToTimeStr
 - funSampleName improved
 - profiler CheckpointTimer shows more details

.gitignore now ignores IDEA's folder
This commit is contained in:
Kaens 2026-02-23 16:12:43 +01:00
commit 9ba8a3bec5
6 changed files with 1183 additions and 776 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
DIE-engine
.idea

File diff suppressed because it is too large Load diff

View file

@ -59,23 +59,29 @@ String.prototype.addIfNone = function (substring) {
if(!String.prototype.startsWith) String.prototype.startsWith = function (s, min) {
// the default case-sensitive version of startsWith
var m = (typeof min == "number") ? Math.min(s.length, min) : 0;
if (s.length > this.length - m) return false; else return this.slice(m, s.length) == s;
if (s.length > this.length - m) return false;
return this.slice(m, s.length) == s;
}
String.prototype.startsWithCI = function (s, min) {
// case-insensitive version of startsWith
return this.toLowerCase().startsWith(s.toLowerCase(), min);
// the built-in clearly just checks for arguments length instead of max being undefined, so we copy that...
if(arguments.length < 2) return this.toLowerCase().startsWith(s.toLowerCase());
else return this.toLowerCase().startsWith(s.toLowerCase(), min);
}
if(!String.prototype.endsWith) String.prototype.endsWith = function (s, max) {
// the default case-sensitive version of endsWith
var m = (typeof max == "number") ? Math.min(this.length, max) : this.length;
if (s.length > m) return false; else return this.slice(m - s.length, m) == s;
if (s.length > m) return false; if(!s.length) return true;
return this.slice(m - s.length, m) == s;
}
String.prototype.endsWithCI = function (s, max) {
// the case-insensitive version of endsWith
return this.toLowerCase().endsWith(s.toLowerCase(), max);
// the built-in clearly just checks for arguments length instead of max being undefined, so we copy that...
if(arguments.length < 2) return this.toLowerCase().endsWith(s.toLowerCase());
else return this.toLowerCase().endsWith(s.toLowerCase(), max);
}
if (!String.prototype.repeat) String.prototype.repeat = function (num) {

View file

@ -23,207 +23,10 @@ BCInvalidFormat = -1;
const debug = 0;
// -= Yamaha YM2151 FM Operator Type-M (OPM) related functionality =-
function isYM2151Reg(a) {
//The OPM doesn't use these registers so we break off if we hit one:
return [0, 2, 3, 4, 5, 6, 7, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0x10,
0x13, 0x15, 0x16, 0x17, 0x1A, 0x1C, 0x1D, 0x1E, 0x1F].indexOf(a) < 0;
}
// -= PC98xx MUAP parsing =-
function YM2151RegStr(a, b) {
//from https://retrocdn.net/images/9/9c/YM2151_Application_Manual.pdf
// & https://cx5m.file-hunter.com/fmunit.htm - this one contains errors!
if (!isYM2151Reg(a)) return '!bad#' + Hex(a);
if (a == 1) if ((b & 2) == b) return 'LFOR'; else return 'TEST' + Bin(b);
if (a == 8) return 'keyon ch' + (b & 7) + ' slot' + Bin((b >> 3) & 0xF);
if (a == 0xF) return 'noise' + ['off', 'on'][b >> 7] + ' freq' + Hex(b >> 0x1F);
if (a == 0x11) return 'CLKA MSB freq' + Hex(b);
if (a == 0x12) return 'CLKA LSB freq' + Hex(b & 3);
if (a == 0x13) return 'CLKB freq' + Hex(b);
if (a == 0x14) return 'Clk CSM' + (b >> 7) + ' FResetBA' + Bin((b >> 4) & 3, 2)
+ ' IRQEnBA' + Bin((b >> 2) & 3, 2) + ' LoadBA' + Bin(b & 3, 2);
if (a == 0x18) return 'LowOscFreq ' + Hex(b);
if (a == 0x19) return ['Amp', 'Phase'][b >> 7] + 'Mod depth' + Hex(b & 0x7F);
if (a == 0x1B) return 'LFOWave ctl' + (b >> 6) + ' ' + ['saw', 'sqr', 'tri', 'noise'][b & 3];
if (a <= 0x27) return 'Ch ' + (a & 0x7) + ' ctl ' + (b & 0x80 ? 'R' : '') + (b & 0x40 ? 'L' : '') + ' FB' + ((b >> 3) & 7) + ' con' + (b & 7);
if (a <= 0x2F) {
o = ((b >> 4) & 7);
return 'KC/prep note-on ch' + (a & 0x7) + ' '
+ (o ? ['C#', 'D', 'D#', '', 'E', 'F', 'F#', '', 'G', 'G#', 'A', '', 'A#', 'B', 'C', ''][b & 0xF] + o : '--');
}
if (a <= 0x37) return 'KF/prep p.bend ch' + (a & 0x7) + ' kf' + (b >> 2);
if (a <= 0x3F) return 'ModSensy. ch' + (a & 0x7) + ' phase' + ((b >> 4) & 7) + ' amp' + (b & 3);
if (a <= 0x47) return 'OP1 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x4F) return 'OP3 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x57) return 'OP2 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x5F) return 'OP4 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x67) return 'OP1 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x6F) return 'OP3 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x77) return 'OP2 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x7F) return 'OP4 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x87) return 'OP1 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0x8F) return 'OP3 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0x97) return 'OP2 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0x9F) return 'OP4 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0xA7) return 'OP1 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xAF) return 'OP3 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xB7) return 'OP2 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xBF) return 'OP4 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xC7) return 'OP1 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xCF) return 'OP3 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xD7) return 'OP2 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xDF) return 'OP4 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xE7) return 'OP1 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
if (a <= 0xEF) return 'OP3 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
if (a <= 0xF7) return 'OP2 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
return 'OP4 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
}
// -= MDX/MXDRV command explainer, useful for loggers
function MDXCmdStr(ch, o) {
const C = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'];
const notes = ['D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'B#', 'C', 'C#', 'D']; var c = X.U8(o);
if (c < 0x80) return C[ch] + ': rest ' + (c + 1);
else if (c <= 0xDF) {
c -= 0x80;
if (ch > 8) return C[ch] + ': smp#' + c;
else return C[ch] + ': ' + notes[c % 12] + (Util.divu64(c, 12)) + ' ~' + (X.U8(o + 1) + 1);
}
else switch (c) {
case 0xFF: return C[ch] + ': bpm ' + X.U8(o + 1); case 0xFE: return C[ch] + ': R ' + YM2151RegStr(X.U8(o + 1), X.U8(o + 2));
case 0xFD: return C[ch] + ': voicedata ' + X.U8(o + 1); case 0xFC: return C[ch] + ': pan ' + X.U8(o + 1);
case 0xFB: if (X.U8(o + 1) & 0x80) return C[ch] + ': @vol ' + (X.U8(o + 1) & 0x7F); else return C[ch] + ': vol ' + X.U8(o + 1);
case 0xFA: return C[ch] + ': vol-'; case 0xF9: return C[ch] + ': vol+';
case 0xF8: return C[ch] + ': staccato ' + X.U8(o + 1); case 0xF7: return C[ch] + ': legato';
case 0xF6: return C[ch] + ': rep.' + X.U8(o + 1) + ' [' + (X.U8(o + 2) ? '/' + X.U8(o + 2) : '') + '...';
case 0xF5: return C[ch] + ': ...]rep.,ret→' + Hex(o + X.I16(o + 1, _BE));
case 0xF4: return C[ch] + ': .../rep.esc→' + Hex(o + X.I16(o + 1, _BE));
case 0xF3: return C[ch] + ': detune ' + X.I16(o + 1, _BE) / 0x40;
case 0xF2: return C[ch] + ': portamento ' + X.I16(o + 1, _BE) / 0x4000 + ' ↓';
case 0xF1: if (X.U8(o + 1)) return C[ch] + ': loop from ' + Hex(o + 3 + X.I16(o + 1, _BE)) + '.'; else return C[ch] + ' ends.';
case 0xF0: return C[ch] + ': delay key-on ' + X.U8(o + 1); case 0xEF: return C[ch] + ': sync send on ch' + X.U8(o + 1);
case 0xEE: return C[ch] + ': sync wait on ch' + X.U8(o + 1); case 0xED: return C[ch] + ': noise/smp freq ' + X.U8(o + 1);
case 0xEC: if (X.U8(o + 1) == 0x80) return C[ch] + ': pitch LFO off';
else if (X.U8(o + 1) == 0x81) return C[ch] + ': pitch LFO on';
else return C[ch] + ': LFO pitch wf ' + X.U8(o + 1) + ' freq ' + X.U16(o + 2, _BE) + ' amp ' + X.U16(o + 4, _BE);
case 0xEB: if (X.U8(o + 1) == 0x80) return C[ch] + ': vol LFO off';
else if (X.U8(o + 1) == 0x81) return C[ch] + ': vol LFO on';
else return C[ch] + ': LFO vol wf ' + X.U8(o + 1) + ' freq ' + X.U16(o + 2, _BE) + ' amp ' + X.U16(o + 4, _BE);
case 0xEA: if (X.U8(o + 1) == 0x80) return C[ch] + ': OPM LFO off';
else if (X.U8(o + 1) == 0x81) return C[ch] + ': OPM LFO on';
else return C[ch] + ': LFO OPM syn/wf ' + X.U8(o + 1) + ' lfrq ' + X.U8(o + 2) + ' PMD ' + X.U8(o + 3) + ' AMD ' + X.U8(o + 4) + ' P/AMS ' + X.U8(o + 5);
case 0xE9: return C[ch] + ': LFO key-on dly ' + X.U8(o + 1); case 0xE8: return C[ch] + ': PCM8 on';
case 0xE7: return C[ch] + ': Fadeout' + (X.U8(o + 1) == 1 ? '' : Hex(X.U8(o + 1))) + ' spd ' + X.U8(o + 2);
default: return C[ch] + ': unknown command ' + Hex(X.U8(o));
}
}
/** OPM/YM2151 register log detector. If <= 0, consider invalid.
* no custom data or end marker to be expected.
*/
function parseYM2151RegLog(p, len) {
//ref https://retrocdn.net/images/9/9c/YM2151_Application_Manual.pdf
len = len || BCParseToReasonable; p = p || 0;
var max = (len == BCParseToEoF) ? X.Sz() : Math.min(X.Sz(), p + 0x2000),
notes = 0, ic = 0, confirmed = false,
v = [0, 0, 0, 0, 0], chinits = [],
r, x;
for(var i=0; i < 8; chinits[i++]=0);
function re(p, t) { if(debug>1)_l2r('opm', p, t); return [BCInvalidFormat,p,0]; }
function iC() { if(debug>0)_l2r('opm', p-2, Hex(r)+' - '+Hex(x)+': invalid value'); ic++; }
while(!X.U8(p) && p < 0x2000) p++; //skip zeroes if any
if(!X.U8(p)) return [BCInvalidFormat,p,0]; //sanity, heuristics, just reasonable they'd cut off the meaningless zeroes
while (p < max && ic < 10) {
r = X.U8(p++); if (!r) continue; x = X.U8(p++);
if (!isYM2151Reg(r)) iC();
if(debug>1)_logIt(YM2151RegStr(r,x));
/* The working YM2151 regs are:
01: TEST | 08: x111 1222: (SM)KON, CH № | 0F: 1xx2 2222: NE, NFRQ
10: CLKA1 | 11: xxxx xx11: CLKA2 | 12: CLKAB
14: 1x22 3344: CSM, FLAG RESET B&A, IRQ-EN B&A, LOAD B&A
18: LFRQ | 19: PMD/AMD | 1B: 11xx xx22: CT, W | 20~27: 1122 23333: RL, FB, CONECT
28~2F: x111 2222: KC { OCT, NOTE } | 30~37: 1111 11xx: KF | 38~3F: x111 xx22: PMS, AMS
40~5F: x111 2222: DT1, MUL | 60~7F: x111 1111: TL | 80~9F: 11x2 2222: KS, AR
A0~BF: 1xx2 2222: AMS-EN, D1R | C0~DF: 11x2 2222: DT2, D2R | E0~FF: 1111 2222: D1L, RR
*/
if (r <= 0x27) {
if (r == 1) { if (x & 0xFD) break; }
else if (r == 8) { if (x & 0x78) { notes += bitCount((x >> 3) & 0xF); v[0]++; } else if (x & 0x80) iC();
// if(chinits[r & 7] > 5) chok[r & 7]++ - but adapt to bitCount!
}
else if (r == 0xF) { if (x & 0x60) iC(); }
else if (r == 0x11) { if (x > 3) iC(); }
else if (r == 0x14) { if (x & 0x40) iC(); }
else if (r == 0x1B) { if (x & 0x3C) iC(); }
}
else if (r <= 0x2F) { if ((x & 0x80) || [3,7,0xB,0xF].indexOf(x&0xF) >= 0) iC(); }
else if (r <= 0x37) {} //{ if (x & 3) iC(); } // should be a filter but some tunes want it that way
else if (r <= 0x3F) { if (x & 0x8C) iC(); }
else if (r <= 0x7F) { if (x & 0x80) iC(); v[1]++; chinits[r & 7]++; } // TL set
else if (r <= 0x9F) { if (x & 0x20) iC(); v[2]++; chinits[r & 7]++; } // KS/AR set
else if (r <= 0xBF) { if (x & 0x60) iC(); v[3]++; chinits[r & 7]++; } // AMS/D1R set
else if (r <= 0xDF) { if (x & 0x20) iC(); v[4]++; chinits[r & 7]++; } // DT2/D2R set
else { v[4]++; chinits[r & 7]++; } // D1L/RR set
if(!confirmed)
if(p > 0x1000)
if (notes < 20 || v[0] < 24 || v[1] < 24 || v[2] < 24 || v[3] < 24 || v[4] < 24)
return [BCInvalidFormat]; // false positives can be pretty long!
else confirmed = true
}
var chok = 0; for (var i=0; i < 8; i++) if (chinits[i] > 5) chok++;
if(debug>0)_logIt(outArray([notes, v, chok, p], 16))
if (ic >= 40 || chok < 5 || notes < 20 || v[0] < 24 || v[1] < 24 || v[2] < 24 || v[3] < 24 || v[4] < 24 )
return [BCInvalidFormat, p, chok];
return [notes, p, chok];
}
// -= Yamaha YM2612(OPN2) related functionality =-
function isYM2612Reg(a) {
//The OPN2 doesn't these registers:
return !( a < 0x22 || a == 0x23 || isWithin(a, 0x2C, 0x2F) || a > 0xB6
|| (isWithin(a, 0x30, 0xAF) && (a & 3) == 3) // per-op registers from 3x to Ax must not have x3,x7, xB, xF
);
}
/** Mega Drive GYM bytecode detector. If <= 0, consider invalid.
* no custom data or end marker to be expected.
*/
function parseMDGYM(p, len) {
//ref https://plutiedev.com/ym2612-registers
var tmr; if(debug>0){ tmr = new CheckpointTimer(); tmr.init(300); }
len = len || BCParseToReasonable; p = p || 0;
var max = (len == BCParseToEoF) ? X.Sz() : Math.min(X.Sz(), p + 0x2000),
notes = 0, v = [0, 0, 0, 0],
c, r, x;
function re(p, t) { if (debug>0)_l2r('gym', p, t); return [BCInvalidFormat, p, 0]; }
while (p < max) switch (c = X.U8(p++)) {
case 0: break;
case 1: case 2: r = X.U8(p++), x = X.U8(p++); if (!isYM2612Reg(r)) return re(p - 3, c + ': R ' + Hex(r));
if (r == 0x28 && (x >> 4)) notes += bitCount(x >> 4);
if ((r & 0xF0) == 0x30) v[0]++; // MUL/DT set
if ((r & 0xF0) == 0x40 && X.U8(p) > 0) v[1]++; // TL set
if ((r & 0xF0) == 0x50) v[2]++; // AR/RS set
if ((r & 0xF0) == 0x60) v[3]++; // DR/AM set
break;
case 3: p++; break;
default: return re(p - 1, '!cmd' + Hex(c));
}
if(debug>0)tmr.next('GYM: end of tested area')
if(debug>0)_logIt(outArray([notes, v, p], 16))
if (!notes || v[0] < 24 || v[1] < 24 || v[2] < 24 || v[3] < 24) return [BCInvalidFormat, p, 0];
return [notes, p, 0];
}
function MUAP98CmdStr(ch, o, recurse) {
function MUAP98CmdStr(ch, o, recurse) {
if (typeof recurse != 'number') recurse = 0; if (recurse > 2) return '…';
const
C = [/*0~2: FM:*/'FM1: ', 'FM2: ', 'FM3: ', /*3~5:*/'SSGA: ', 'SSGB: ', 'SSGC: ',
@ -278,7 +81,7 @@ function MUAP98CmdStr(ch, o, recurse) {
case 0xDB: return C[ch] + 'cmt: ' + Hex(X.U8(o + 1)) + ' ' + Hex(X.U8(o + 2)) + ': "' + X.SC(o + 4, X.U8(o + 3), 'SJIS') + '"';
case 0xDA: return C[ch] + 'set X: ' + outArray(X.readBytes(o + 1, 3), 16);
case 0xD9: return C[ch] + 'set LFO pars. ' + outArray(X.readBytes(o + 1, 6), 16);
case 0xD8: return C[ch] + 'LFO start(p,a)/stop ' + Hex(X.U8(o + 1)); break; //LFO start(pmd,amd)/stop
case 0xD8: return C[ch] + 'LFO start(p,a)/stop ' + Hex(X.U8(o + 1)); //LFO start(pmd,amd)/stop
case 0xD7: return C[ch] + 'vol += ' + Hex(X.U8(o + 1));
case 0xD6: return C[ch] + 'vol -= ' + Hex(X.U8(o + 1));
case 0xD5: return C[ch] + (ch == 9 || ch == 10 ? 'PCM play ' : cht == 'ssg' ? 'Start vol/Attack rate ' : 'x3 nop ') + X.U16(o);
@ -305,8 +108,8 @@ function parseMUAP98(p, len, ch) {
var cht = (ch == 9) ? 'rhy' : ch == 10 ? 'pcm' : 3 <= ch && ch <= 5 ? 'ssg' : 'fm',
c, notes = 0, stop = false, cmtlen = -1, cmt = "", /*stack depths:*/ lpd = 1, calld = ifd = mp = ic = 0;
var visited = [];
for (i = p0; i < max; i++) visited[i] = false;
function re(p, t) { if (debug > 1) _l2r('muap98', p, 'ch' + ch + ': ' + t); delete visited; return [BCInvalidFormat, p, 0]; }
for (var i = p0; i < max; i++) visited[i] = false;
function re(p, t) { if (debug > 1) _l2r('muap98', p, 'ch' + ch + ': ' + t); return [BCInvalidFormat, p, 0]; }
while (p0 <= p && p < max && !stop) {
if (ifd < 0) sus++;
visited[p] = true; if (p > mp) mp = p;
@ -385,16 +188,226 @@ function parseMUAP98(p, len, ch) {
}
// -= MDX/MXDRV command explainer, useful for loggers =-
function MDXCmdStr(ch, o) {
const C = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'];
const notes = ['D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'B#', 'C', 'C#', 'D']; var c = X.U8(o);
if (c < 0x80) return C[ch] + ': rest ' + (c + 1);
else if (c <= 0xDF) {
c -= 0x80;
if (ch > 8) return C[ch] + ': smp#' + c;
else return C[ch] + ': ' + notes[c % 12] + (Util.divu64(c, 12)) + ' ~' + (X.U8(o + 1) + 1);
}
else switch (c) {
case 0xFF: return C[ch] + ': bpm ' + X.U8(o + 1); case 0xFE: return C[ch] + ': R ' + YM2151RegStr(X.U8(o + 1), X.U8(o + 2));
case 0xFD: return C[ch] + ': voicedata ' + X.U8(o + 1); case 0xFC: return C[ch] + ': pan ' + X.U8(o + 1);
case 0xFB: if (X.U8(o + 1) & 0x80) return C[ch] + ': @vol ' + (X.U8(o + 1) & 0x7F); else return C[ch] + ': vol ' + X.U8(o + 1);
case 0xFA: return C[ch] + ': vol-'; case 0xF9: return C[ch] + ': vol+';
case 0xF8: return C[ch] + ': staccato ' + X.U8(o + 1); case 0xF7: return C[ch] + ': legato';
case 0xF6: return C[ch] + ': rep.' + X.U8(o + 1) + ' [' + (X.U8(o + 2) ? '/' + X.U8(o + 2) : '') + '...';
case 0xF5: return C[ch] + ': ...]rep.,ret→' + Hex(o + X.I16(o + 1, _BE));
case 0xF4: return C[ch] + ': .../rep.esc→' + Hex(o + X.I16(o + 1, _BE));
case 0xF3: return C[ch] + ': detune ' + X.I16(o + 1, _BE) / 0x40;
case 0xF2: return C[ch] + ': portamento ' + X.I16(o + 1, _BE) / 0x4000 + ' ↓';
case 0xF1: if (X.U8(o + 1)) return C[ch] + ': loop from ' + Hex(o + 3 + X.I16(o + 1, _BE)) + '.'; else return C[ch] + ' ends.';
case 0xF0: return C[ch] + ': delay key-on ' + X.U8(o + 1); case 0xEF: return C[ch] + ': sync send on ch' + X.U8(o + 1);
case 0xEE: return C[ch] + ': sync wait on ch' + X.U8(o + 1); case 0xED: return C[ch] + ': noise/smp freq ' + X.U8(o + 1);
case 0xEC: if (X.U8(o + 1) == 0x80) return C[ch] + ': pitch LFO off';
else if (X.U8(o + 1) == 0x81) return C[ch] + ': pitch LFO on';
else return C[ch] + ': LFO pitch wf ' + X.U8(o + 1) + ' freq ' + X.U16(o + 2, _BE) + ' amp ' + X.U16(o + 4, _BE);
case 0xEB: if (X.U8(o + 1) == 0x80) return C[ch] + ': vol LFO off';
else if (X.U8(o + 1) == 0x81) return C[ch] + ': vol LFO on';
else return C[ch] + ': LFO vol wf ' + X.U8(o + 1) + ' freq ' + X.U16(o + 2, _BE) + ' amp ' + X.U16(o + 4, _BE);
case 0xEA: if (X.U8(o + 1) == 0x80) return C[ch] + ': OPM LFO off';
else if (X.U8(o + 1) == 0x81) return C[ch] + ': OPM LFO on';
else return C[ch] + ': LFO OPM syn/wf ' + X.U8(o + 1) + ' lfrq ' + X.U8(o + 2) + ' PMD ' + X.U8(o + 3) + ' AMD ' + X.U8(o + 4) + ' P/AMS ' + X.U8(o + 5);
case 0xE9: return C[ch] + ': LFO key-on dly ' + X.U8(o + 1); case 0xE8: return C[ch] + ': PCM8 on';
case 0xE7: return C[ch] + ': Fadeout' + (X.U8(o + 1) == 1 ? '' : Hex(X.U8(o + 1))) + ' spd ' + X.U8(o + 2);
default: return C[ch] + ': unknown command ' + Hex(X.U8(o));
}
}
// -= Yamaha YM2151 FM Operator Type-M (OPM) related functionality =-
function isYM2151Reg(a) {
//The OPM doesn't use these registers so we break off if we hit one:
return [0, 2, 3, 4, 5, 6, 7, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0x10,
0x13, 0x15, 0x16, 0x17, 0x1A, 0x1C, 0x1D, 0x1E, 0x1F].indexOf(a) < 0;
}
function YM2151RegStr(a, b) {
//from https://retrocdn.net/images/9/9c/YM2151_Application_Manual.pdf
// & https://cx5m.file-hunter.com/fmunit.htm - this one contains errors!
if (!isYM2151Reg(a)) return '!bad#' + Hex(a);
if (a == 1) if ((b & 2) == b) return 'LFOR'; else return 'TEST' + Bin(b);
if (a == 8) return 'keyon ch' + (b & 7) + ' slot' + Bin((b >> 3) & 0xF);
if (a == 0xF) return 'noise' + ['off', 'on'][b >> 7] + ' freq' + Hex(b >> 0x1F);
if (a == 0x11) return 'CLKA MSB freq' + Hex(b);
if (a == 0x12) return 'CLKA LSB freq' + Hex(b & 3);
if (a == 0x13) return 'CLKB freq' + Hex(b);
if (a == 0x14) return 'Clk CSM' + (b >> 7) + ' FResetBA' + Bin((b >> 4) & 3, 2)
+ ' IRQEnBA' + Bin((b >> 2) & 3, 2) + ' LoadBA' + Bin(b & 3, 2);
if (a == 0x18) return 'LowOscFreq ' + Hex(b);
if (a == 0x19) return ['Amp', 'Phase'][b >> 7] + 'Mod depth' + Hex(b & 0x7F);
if (a == 0x1B) return 'LFOWave ctl' + (b >> 6) + ' ' + ['saw', 'sqr', 'tri', 'noise'][b & 3];
if (a <= 0x27) return 'Ch ' + (a & 0x7) + ' ctl ' + (b & 0x80 ? 'R' : '') + (b & 0x40 ? 'L' : '') + ' FB' + ((b >> 3) & 7) + ' con' + (b & 7);
if (a <= 0x2F) {
o = ((b >> 4) & 7);
return 'KC/prep note-on ch' + (a & 0x7) + ' '
+ (o ? ['C#', 'D', 'D#', '', 'E', 'F', 'F#', '', 'G', 'G#', 'A', '', 'A#', 'B', 'C', ''][b & 0xF] + o : '--');
}
if (a <= 0x37) return 'KF/prep p.bend ch' + (a & 0x7) + ' kf' + (b >> 2);
if (a <= 0x3F) return 'ModSensy. ch' + (a & 0x7) + ' phase' + ((b >> 4) & 7) + ' amp' + (b & 3);
if (a <= 0x47) return 'OP1 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x4F) return 'OP3 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x57) return 'OP2 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x5F) return 'OP4 ch' + (a & 0x7) + ' dt1:' + ((b >> 4) & 7) + ' mul' + (b & 0xF);
if (a <= 0x67) return 'OP1 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x6F) return 'OP3 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x77) return 'OP2 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x7F) return 'OP4 ch' + (a & 0x7) + ' TL' + (b & 0x7F);
if (a <= 0x87) return 'OP1 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0x8F) return 'OP3 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0x97) return 'OP2 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0x9F) return 'OP4 ch' + (a & 0x7) + ' KeyScl' + (b >> 6) + ' atk' + (b & 0x1F);
if (a <= 0xA7) return 'OP1 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xAF) return 'OP3 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xB7) return 'OP2 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xBF) return 'OP4 ch' + (a & 0x7) + ' AMS' + ['off', 'on'][b >> 7] + ' dcy1R:' + (b & 0x1F);
if (a <= 0xC7) return 'OP1 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xCF) return 'OP3 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xD7) return 'OP2 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xDF) return 'OP4 ch' + (a & 0x7) + ' dt2:' + (b >> 6) + ' dcy2R:' + (b & 0x1F);
if (a <= 0xE7) return 'OP1 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
if (a <= 0xEF) return 'OP3 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
if (a <= 0xF7) return 'OP2 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
return 'OP4 ch' + (a & 0x7) + ' dcy2L:' + (b >> 4) + ' rel:' + (b & 0xF);
}
/** OPM/YM2151 register log detector. If <= 0, consider invalid.
* no custom data or end marker to be expected.
*/
function parseYM2151RegLog(p, len) {
//ref https://retrocdn.net/images/9/9c/YM2151_Application_Manual.pdf
len = len || BCParseToReasonable; p = p || 0;
var max = (len == BCParseToEoF) ? X.Sz() : Math.min(X.Sz(), p + 0x2000),
notes = 0, ic = 0, confirmed = false,
v = [0, 0, 0, 0, 0], chinits = [],
r, x;
for(var i=0; i < 8; chinits[i++]=0);
function re(p, t) { if(debug>1)_l2r('opm', p, t); return [BCInvalidFormat,p,0]; }
function iC() { if(debug>0)_l2r('opm', p-2, Hex(r)+' - '+Hex(x)+': invalid value'); ic++; }
while(!X.U8(p) && p < 0x800) p++; //skip zeroes unless too many
if(!X.U8(p)) return [BCInvalidFormat,p,0]; //sanity, heuristics, just reasonable they'd cut off the meaningless zeroes
while (p < max && ic < 10) {
r = X.U8(p++); if (!r) continue; x = X.U8(p++);
if (!isYM2151Reg(r)) iC();
if(debug>1)_logIt(YM2151RegStr(r,x));
/* The working YM2151 regs are:
01: TEST | 08: x111 1222: (SM)KON, CH № | 0F: 1xx2 2222: NE, NFRQ
10: CLKA1 | 11: xxxx xx11: CLKA2 | 12: CLKAB
14: 1x22 3344: CSM, FLAG RESET B&A, IRQ-EN B&A, LOAD B&A
18: LFRQ | 19: PMD/AMD | 1B: 11xx xx22: CT, W | 20~27: 1122 23333: RL, FB, CONECT
28~2F: x111 2222: KC { OCT, NOTE } | 30~37: 1111 11xx: KF | 38~3F: x111 xx22: PMS, AMS
40~5F: x111 2222: DT1, MUL | 60~7F: x111 1111: TL | 80~9F: 11x2 2222: KS, AR
A0~BF: 1xx2 2222: AMS-EN, D1R | C0~DF: 11x2 2222: DT2, D2R | E0~FF: 1111 2222: D1L, RR
*/
if (r <= 0x27) {
if (r == 1) { if (x & 0xFD) break; }
else if (r == 8) {
if (x & 0x78) { notes += bitCount((x >> 3) & 0xF); v[0]++; }
else if (x & 0x80) iC();
// TODO check if the notes played are initialised properly. It won't sound right if not so expect goodness!
// if(chinits[r & 7] > 5) chok[r & 7]++ // adapt to bitCount!
}
else if (r == 0xF) { if (x & 0x60) iC(); }
else if (r == 0x11) { if (x > 3) iC(); }
else if (r == 0x14) { if (x & 0x40) iC(); }
else if (r == 0x1B) { if (x & 0x3C) iC(); }
}
else if (r <= 0x2F) { if ((x & 0x80) || [3,7,0xB,0xF].indexOf(x&0xF) >= 0) iC(); }
else if (r <= 0x37) {} //{ if (x & 3) iC(); } // should be a filter but some tunes want it that way
else if (r <= 0x3F) { if (x & 0x8C) iC(); }
else if (r <= 0x7F) { if (x & 0x80) iC(); v[1]++; chinits[r & 7]++; } // TL set
else if (r <= 0x9F) { if (x & 0x20) iC(); v[2]++; chinits[r & 7]++; } // KS/AR set
else if (r <= 0xBF) { if (x & 0x60) iC(); v[3]++; chinits[r & 7]++; } // AMS/D1R set
else if (r <= 0xDF) { if (x & 0x20) iC(); v[4]++; chinits[r & 7]++; } // DT2/D2R set
else { v[4]++; chinits[r & 7]++; } // D1L/RR set
if(!confirmed)
if(p > 0x1000)
if (ic >= 40 || notes < 20 || v[0] < 24 || v[1] < 24 || v[2] < 24 || v[3] < 24 || v[4] < 24)
return [BCInvalidFormat]; // false positives can be pretty long!
else confirmed = true
}
var chok = 0; for (var i=0; i < 8; i++) if (chinits[i] > 5) chok++;
if(debug>0)_logIt(outArray([notes, v, chok, p], 16))
if (confirmed && chok > 0) return [notes, p, chok];
return [BCInvalidFormat, p, chok];
}
// -= Yamaha YM2612(OPN2) related functionality =-
function isYM2612Reg(a) {
//The OPN2 doesn't have these registers:
return !( a < 0x22 || a == 0x23 || a > 0xB7/*B6*/ || isWithin(a, 0x2C,0x2F)
// || (isWithin(a, 0x30, 0xAF) && (a & 3) == 3) // per-op registers from 3x to Ax must not have x3,x7, xB, xF. Some files do tho'.
);
}
/** Mega Drive GYM bytecode detector. Returns BCInvalidFormat in resut[0] if invalid;
* no custom data or end marker to be expected.
*/
function parseMDGYM(p, len) {
//ref https://plutiedev.com/ym2612-registers
//& https://github.com/ValleyBell/libvgm/blob/master/player/gymplayer.cpp
len = len || BCParseToReasonable; p = p || 0;
var tmr; if(debug>0){ tmr = new CheckpointTimer(); tmr.init(300); }
var max = (len == BCParseToEoF) ? X.Sz() : Math.min(X.Sz(), p+0x2000),
notes = 0, v = [0, 0, 0, 0, 0], ir = 0, //notes, validation, invalid registers
c, r, x, doPSGFreq2nd = false;
function re(p, t) { if (debug>0)_l2r('gym', p, t); return [BCInvalidFormat, p, 0]; }
while(!X.U8(p) && p < 0x800) p++; //skip zeroes unless too many
while (p < max && ir < 10) switch (c = X.U8(p++)) {
case 0: doPSGFreq2nd = false; break;
case 1: case 2: r = X.U8(p++); if(c == 2 && r < 0x21) return;
x = X.U8(p++); if (!isYM2612Reg(r)) { ir++; if(debug>0)_l2r('gym',p-2,c + ': R ' + Hex(r)) }
if (r == 0x28 && (x >> 4)) { notes += bitCount(x >> 4); if(debug>0)_l2r('gym',p-2,'#') }
else if (r == 0x2A) v[4]++; //PCM
else if ((r & 0xF0) == 0x30) { v[0]++; if(debug>0)_l2r('gym',p-2,'ML/DT') } // MUL/DT set
else if ((r & 0xF0) == 0x40 && X.U8(p) > 0) { v[1]++; if(debug>0)_l2r('gym',p-2,'TL') } // TL set
else if ((r & 0xF0) == 0x50) { v[2]++; if(debug>0)_l2r('gym',p-2,'AR/RS') } // AR/RS set
else if ((r & 0xF0) == 0x60) { v[3]++; if(debug>0)_l2r('gym',p-2,'DR/AM') }// DR/AM set
else if(debug>0)_l2r('gym',p-2,c+': R '+Hex(r))
// can't check other reg pushes for validity
break;
case 3: r = X.U8(p++); if(debug>0)_l2r('gym',p-2,'PSG');
if(r & 0x80) doPSGFreq2nd = (!(r&0x10) && r < 0xE0);
else if(doPSGFreq2nd && r < 0x40) doPSGFreq2nd = false;
else return [BCInvalidFormat, p, 0];
break;
default: return re(p-1, '!cmd' + Hex(c));
}
if(debug>0)tmr.next('GYM: end of tested area @'+Hex(p)+' ir='+ir)
if(debug>0)_l2r('gym',p,outArray([notes, v], 16));
if ((!notes || v[0] < 8 || v[1] < 8 || v[2] < 8 || v[3] < 8) && v[0]+v[1]+v[2]+v[3]+v[4] < 100) return [BCInvalidFormat, p, 0];
return [notes, p, 0];
}
// -= AdLib/Sound Blaster YM3812/OPL2 related functionality =-
//ref https://web.archive.org/web/20050205055453/http://www.gamedev.net/reference/articles/article446.asp
//The AdLib/OPL2 uses these registers:
function isYM3812Reg(a) {
return [1,2,3,4,8,0xBD].includes(a) || isWithin(a, 0x20,0x35)
|| ( isWithin(a, 0x40,0x55) || isWithin(a, 0x60,0x75) || isWithin(a, 0x80,0x95)
|| isWithin(a, 0xE0,0xF5) ) && [6,7,14,15].indexOf(a & 0x1F) < 0
|| isWithin(a, 0xA0,0xA8) || isWithin(a, 0xB0,0xB8) || isWithin(a, 0xC0,0xC8);
return isWithinRanges(a, [1,2,3,4,8,0xBD, [0x20,0x35]])
|| ( isWithinRanges(a, [[0x40,0x55], [0x60,0x75], [0x80,0x95], [0xE0,0xF5]] ) && [6,7,14,15].indexOf(a & 0x1F) < 0
|| isWithinRanges(a, [[0xA0,0xA8], [0xB0,0xB8], [0xC0,0xC8]]));
}
var __adlibnote = []; for(var _0=0; _0 < 9; _0++) __adlibnote.push([-1,-1,-1]); //channel: key-on, octave, F-num
@ -433,7 +446,7 @@ function parseYM3812RegLog(p, len) {
c, r, x, tmr;
if(debug>0){ tmr = new CheckpointTimer(); tmr.init(300); }
function re(p, t) { if(debug>1)_l2r('adlib',p,t); return [BCInvalidFormat, p, 0]; }
while(!X.U8(p) && p < 0x2000) p++; //skip zeroes if any
while(!X.U8(p) && p < 0x800) p++; //skip zeroes unless too many
if(!X.U8(p)) return [BCInvalidFormat,p,0]; //sanity, heuristics, just reasonable they'd cut off the meaningless zeroes
while (p < max) {
r = X.U8(p++); x = X.U8(p++); if (!isYM3812Reg(r)) return re(p - 2, 'R ' + Hex(r) + ' : '+Hex(x));

View file

@ -38,13 +38,13 @@ function parseAmigaHunks(baseofs) {
/**
* If it's an Atari DOS block file, parses it and tries to be strict in case of garbage past EoF.
* @param {Int} baseofs - base offset from which to start parsing
* @param {Int} [baseofs] - base offset from which to start parsing
* @returns {Array} [nTotalSize,aBlockInfo] where:
* - nTotalSize: -1 if the file is not an Atari DOS "binary save", otherwise the total expected size (including baseofs);
* - aBlockInfo: an Array of arrays with each block's offsets and sizes, its length reflecting the number of blocks
*/
function parseAtariBinary(baseofs) {
if (typeof baseofs === "undefined") baseofs = 0;
if (typeof baseofs !== "number") baseofs = 0;
if (!X.c("FFFF", baseofs) || X.Sz() < baseofs + 6) return [-1, []];
var hksz = 0,
RAM = [],
@ -54,8 +54,9 @@ function parseAtariBinary(baseofs) {
var ptr = X.U16(p);
if (!hkinfo.length && ptr == 0xFFFF) { p += 2; ptr = X.U16(p); } // the subsequent blocks don't have to have a sig
var eptr = X.U16(p + 2); if (eptr < ptr || (ptr <= 0xD7FF && eptr >= 0xD000)) break;
if (!hksz) break;
//_l2r('atrbin',p,'@'+Hex(ptr)+':'+outArray(hkinfo,16))
hksz = eptr + 1 - ptr; if (p + hksz > X.Sz()) if (!hkinfo.length) break;
if (!hksz) break;
RAM.push(ptr, hksz); if (findIntersections(RAM, true).length) break;
p += 4; hkinfo.push([p, hksz]); p += hksz;
}

349
db/read
View file

@ -7,57 +7,58 @@
/* beautify ignore:start */
var _BE = true, _LE = false; // endianness for read_int16+
// don't make the following var or const; they need to be global constants
_BE = true; _LE = false; // endianness for read_int16+
// little-endian = reversed notation (Intel, ZX Spectrum),
// big-endian = direct notation (TCP/IP, Motorola, Amiga)
// For the BitReader Object, BE is MSB and LE is LSB (intuitively)
var CS_ALL = true, CS_BEST = false; // charStat needall
var FINT_QUICK = FINT_FAST = FINT_1 = true; // findIntersections: find just one for the speed's sake
var TOEOF = -1; // use for the size parameter in findSignature
CS_ALL = true; CS_BEST = false; // charStat needall
FINT_QUICK = FINT_FAST = FINT_1 = FXSEC1 = true; // findIntersections: find just one for the speed's sake
TOEOF = -1; // use for the size parameter in findSignature
// The encoding tables start with 7F, not 80! 7F is undefined in many charsets but it's good to have something
// The N(on)B(reakable)SP(ace) and S(oft)HY(phen) are kept as actual A0 and AD characters in this file
var CP437 = "⌂"+
CP437 = "⌂"+
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ"+
"áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐"+
"└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀"+
"αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ",
CP866 = "⌂"+ //DOS Cyrillic
"αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ";
CP866 = "⌂"+ //DOS Cyrillic
'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'+
'абвгдежзийклмноп░▒▓│┤╡╢╖╕╣║╗╝╜╛┐'+
'└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀'+
'рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙·√№¤■ ',
CP1251 = "⌂"+
'рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙·√№¤■ ';
CP1251 = "⌂"+
"ЂЃ‚ѓ„…†‡€‰Љ‹ЊЌЋЏђ‘’“”•–—・™љ›њќћџ"+
" ЎўЈ¤Ґ¦§Ё©Є«¬­®Ї°±Ііґµ¶·ё№є»јЅѕї"+
"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"+
"абвгдежзийклмнопрстуфхцчшщъыьэюя",
CP1252 = "⌂"+ //aka. Western aka. ISO-8859-1
"абвгдежзийклмнопрстуфхцчшщъыьэюя";
CP1252 = "⌂"+ //aka. Western aka. ISO-8859-1
"€・‚ƒ„…†‡ˆ‰Š‹Œ・Ž・・‘’“”•–—˜™š›œ・žŸ"+
" ¡¢£¤¥¦§¨©ª«¬・®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
KOI8R = "⌂"+ //aka. RFC 1489, Morse code based
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
KOI8R = "⌂"+ //aka. RFC 1489, Morse code based
'─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥ ⌡°²·÷'+
'═║╒ё╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡Ё╢╣╤╥╦╧╨╩╪╫╬©'+
'юабцдефгхийклмнопярстужвьызшэщчъ'+
'ЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ',
JISX0201 = "⌂"+
'ЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ';
JISX0201 = "⌂"+
"→-‚ƒ„…†‡ˆ‰Š‹Œ↑޳™‘’“”•–—˜™š›œ¢žŸ"+ //decided to mix it with cp1252
"→。「」、・ヲァィゥェォャュョッーアイウエカキクケコサシスセソタ"+
"チツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
CPAmiga = "⫽"+ // alternatively, "▒""
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
CPAmiga = "⫽"+ // alternatively, "▒"
"абвгдежзийклмнопрстуфхцчшщъыьэюя"+ //0x80~0x9F display Cyrillics, just to fill the void
" ¡¢£¤¥¦§¨©ª«¬–®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
CPRISCOS = "⌂"+
" ¡¢£¤¥¦§¨©ª«¬–®¯°±²³´µ¶·¸¹º»¼½¾¿"+ // Filling the void is important because we still want to see
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+ // differences in neighbouring values, for non-reading purposes
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
CPRISCOS = "⌂"+
"€Ŵŵ◰﯀Ŷŷ<C5B6>⇦⇨⇩⇧…™‰•“”„Œœ†‡fifl"+
" ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿"+
"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß"+
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
CPATASCII = ['▶',
"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ";
CPATASCII = ['▶',
'🖤','├','▊','┘','┤','┐','','╲','◤','▛','◥','▙','▟','▆',' ̄',
'▜','♣','┌','─','┼','◘','▀','▐','┬','┴','▐','└','\n','↑','↓','←','→',
'█','','”','','','','','','','','','','','ー','',
@ -65,49 +66,74 @@ var CP437 = "⌂"+
'','𝙰','𝙱','𝙲','𝙳','𝙴','𝙵','𝙶','𝙷','𝙸','𝙹','𝙺','𝙻','𝙼','𝙽','𝙾',
'𝙿','𝚀','𝚁','𝚂','𝚃','𝚄','𝚅','𝚆','𝚇','𝚈','𝚉','【','\\','】','','_',
'♦','𝚊','𝚋','𝚌','𝚍','𝚎','𝚏','𝚐','𝚑','𝚒','𝚓','𝚔','𝚕','𝚖','𝚗','𝚘',
'𝚙','𝚚','𝚛','𝚜','𝚝','𝚞','𝚟','𝚠','𝚡','𝚢','𝚣','♠','-','↰','◁','▷'], //arrow up then NW (🢰) not included for UTF limitations
CPAtariST = "⌂"+
'𝚙','𝚚','𝚛','𝚜','𝚝','𝚞','𝚟','𝚠','𝚡','𝚢','𝚣','♠','-','↰','◁','▷']; //arrow up then NW (🢰) not included for UTF limitations
CPAtariST = "⌂"+
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥ßƒ"+
"áíóúñѪº¿⌐¬½¼¡«»ãõØøœŒÀÃÕ¨´†¶©®™"+
"ijIJאבגדהוזחטיכלמנסעפצקרשתןךםףץ§∧∞"+
"αβΓπΣσµτΦΘΩδ∮φ∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²³¯",
CPSpeccy = ['©', //too SPECIAL with all the tokens-for-characters, gotta use lists
"αβΓπΣσµτΦΘΩδ∮φ∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²³¯";
CPFullCPETshifted = [ //all 256 ch. Commodore PET 2001 version. PETSCII UNSHIFTED and 8032 are different.
// Multiple rather approximate values, until Unicode fonts catch up; ref.Wiki
// It's an array because of unicode shenanigans representing some characters with multiple codepoints
"\x00","\x01","_","⛔️","\x04","⚪","\x06","🔔","⇪","\t","\x0A","↘","⇪","\x0D","↘️","💥",
"\x10","⬇️","↩️","⇱","⌫","🗑️","🗯️","\x17","⇆","↟","?","⎋","🟥","➡️","🟩","🟦",
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "↑", "←",
" ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "▧",
"\x80", "🟣", "💥", "🚀", "🕶️", "[F1]", "[F3]", "[F5]", "[F7]", "[F2]", "[F4]", "[F6]", "[F8]", "↵", "⇪", "🕶️",
"⬛️", "⬆️", "↪️", "🗑", "🗯", "🟫", "🌸","🧪" , "⚫️", "🟢", "🔷", "🔳", "🟪", "⬅️", "🟨", "🧪",
"\xA0", "▌", "▄", "▔", "▁", "▏", "░", "▕", "▄", "▒", "▕", "├", "▗", "└", "┐", "▂",
"┌", "┴", "┬", "┤", "▎", "▍", "▕", "▔", "▔", "▃", "✓", "▖", "▝", "┘", "▘", "▚",
"─", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "┼", "▏", "│", "▓", "█",
"\xA0", "▌", "▄", "▔", "▁", "▏", "░", "▕", "▄", "▒", "▕", "├", "▗", "└", "┐", "▂",
"┌", "┴", "┬", "┤", "▎", "▍", "▕", "▔", "▔", "▃", "✓", "▖", "▝", "┘", "▘", "▚"];
CPSpeccy = ['©', //too SPECIAL with all the tokens-for-characters, gotta use an array
' ',' ▀','▀ ','▀▀',' ▄',' █','▀▄','▄█', '▄ ','▄▀','█ ','█▀','▄▄','▄█','█▄','██', //80~F
'𝘼','𝘽','𝘾','𝘿','𝙀','𝙁','𝙂','𝙃','𝙄','𝙅','𝙆','𝙇','𝙈','𝙉','𝙊','𝙋', //90~F
'𝙌','𝙍','𝙎','𝚉𝚇¹²⁸','⏯️','𝚁𝙽𝙳','𝙸𝙽𝙺𝙴𝚈$','π', '𝙵𝙽 ','𝙿𝙾𝙸𝙽𝚃 ','𝚂𝙲𝚁𝙴𝙴𝙽$ ','𝙰𝚃𝚃𝚁 ','𝙰𝚃 ','𝚃𝙰𝙱 ','𝚅𝙰𝙻$ ','𝙲𝙾𝙳𝙴' , //A0~F
'𝙌','𝙍','𝙎','𝚉𝚇¹²⁸','⏯','𝚁𝙽𝙳','𝙸𝙽𝙺𝙴𝚈$','π', '𝙵𝙽 ','𝙿𝙾𝙸𝙽𝚃 ','𝚂𝙲𝚁𝙴𝙴𝙽$ ','𝙰𝚃𝚃𝚁 ','𝙰𝚃 ','𝚃𝙰𝙱 ','𝚅𝙰𝙻$ ','𝙲𝙾𝙳𝙴' , //A0~F
'𝚅𝙰𝙻 ','𝙻𝙴𝙽 ','𝚂𝙸𝙽 ','𝙲𝙾𝚂 ','𝚃𝙰𝙽 ','𝙰𝚂𝙽 ','𝙰𝙲𝚂 ','𝙰𝚃𝙽 ', '𝙻𝙽 ','𝙴𝚇𝙿 ','𝙸𝙽𝚃 ','𝚂𝚀𝚁 ','𝚂𝙶𝙽 ','𝙰𝙱𝚂 ','𝙿𝙴𝙴𝙺 ','𝙸𝙽 ', //B0~F
'𝚄𝚂𝚁 ','𝚂𝚃𝚁$ ','𝙲𝙷𝚁$ ','𝙽𝙾𝚃 ','𝙱𝙸𝙽 ','𝙾𝚁 ','𝙰𝙽𝙳 ','≤', '≥','≠','𝙻𝙸𝙽𝙴 ','𝚃𝙷𝙴𝙽 ','𝚃𝙾 ','𝚂𝚃𝙴𝙿 ','𝙳𝙴𝙵 𝙵𝙽 ','𝙲𝙰𝚃 ', //C0~F
'𝙵𝙾𝚁𝙼𝙰𝚃 ','𝙼𝙾𝚅𝙴 ','𝙴𝚁𝙰𝚂𝙴 ','𝙾𝙿𝙴𝙽 # ','𝙲𝙻𝙾𝚂𝙴 # ','𝙼𝙴𝚁𝙶𝙴 ','𝚅𝙴𝚁𝙸𝙵𝚈 ', '𝙱𝙴𝙴𝙿 ','𝙲𝙸𝚁𝙲𝙻𝙴 ','𝙸𝙽𝙺 ','𝙿𝙰𝙿𝙴𝚁 ','𝙵𝙻𝙰𝚂𝙷 ','𝙱𝚁𝙸𝙶𝙷𝚃 ','𝙸𝙽𝚅𝙴𝚁𝚂𝙴 ','𝙾𝚅𝙴𝚁 ','𝙾𝚄𝚃 ', //D0~F
'𝙻𝙿𝚁𝙸𝙽𝚃 ','𝙻𝙻𝙸𝚂𝚃 ','𝚂𝚃𝙾𝙿 ','𝚁𝙴𝙰𝙳 ','𝙳𝙰𝚃𝙰 ','𝚁𝙴𝚂𝚃𝙾𝚁𝙴 ','𝙽𝙴𝚆 ', '𝙱𝙾𝚁𝙳𝙴𝚁 ','𝙲𝙾𝙽𝚃𝙸𝙽𝚄𝙴 ','𝙳𝙸𝙼 ','𝚁𝙴𝙼 ','𝙵𝙾𝚁 ','𝙶𝙾 𝚃𝙾 ','𝙶𝙾 𝚂𝚄𝙱 ','𝙸𝙽𝙿𝚄𝚃 ','𝙻𝙾𝙰𝙳 ', //E0~F
'𝙻𝙸𝚂𝚃 ','𝙻𝙴𝚃 ','𝙿𝙰𝚄𝚂𝙴 ','𝙽𝙴𝚇𝚃 ','𝙿𝙾𝙺𝙴 ','𝙿𝚁𝙸𝙽𝚃 ','𝙿𝙻𝙾𝚃 ', '𝚁𝚄𝙽 ','𝚂𝙰𝚅𝙴 ','𝚁𝙰𝙽𝙳𝙾𝙼𝙸𝚉𝙴 ','𝙸𝙵 ','𝙲𝙻𝚂','𝙳𝚁𝙰𝚆 ','𝙲𝙻𝙴𝙰𝚁 ','𝚁𝙴𝚃𝚄𝚁𝙽', '𝙲𝙾𝙿𝚈'], //F0~F
Chars0to1F = "・☺☻♥♦♣♠•◘○◙♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼", //#0 is a small dot from Japanese
Chars0to1FLF = "・☺☻♥♦♣♠•◘○\x0A♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼",
Chars0to1FCRLF = "・☺☻♥♦♣♠•◘○\x0A♂♀\x0D♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼",
Chars0to1FSpeccy = "\x00\x01\x02\x03\x04\x05📝⬅➡⬇⬆⌫\x0A№\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", //not mixing...
Chars0to1FATASCII = "♥├◨┘┤┐/╲◢▗◣▝▘ ̄▂▖♣┌─┼•▄▎┬┴▌└␛↑↓←→",
Chars0to1FATASCII2 = "áùÑÉçôòì£ïüäöúóöÜâûîéèñêȧàȦ␛↑↓←→",
Chars0to1FATASCII_PL = "ŹąźćŚėöÖ£üߣłŃÓ√ĘśäÜĆĄŻÄż␛↑↓←→",
Chars0to1FAtariX = "áùÑÉçôòì£ïüäÖúóöÜâûîéèñêȧàȦË↑↓←→";
'𝙻𝙸𝚂𝚃 ','𝙻𝙴𝚃 ','𝙿𝙰𝚄𝚂𝙴 ','𝙽𝙴𝚇𝚃 ','𝙿𝙾𝙺𝙴 ','𝙿𝚁𝙸𝙽𝚃 ','𝙿𝙻𝙾𝚃 ', '𝚁𝚄𝙽 ','𝚂𝙰𝚅𝙴 ','𝚁𝙰𝙽𝙳𝙾𝙼𝙸𝚉𝙴 ','𝙸𝙵 ','𝙲𝙻𝚂','𝙳𝚁𝙰𝚆 ','𝙲𝙻𝙴𝙰𝚁 ','𝚁𝙴𝚃𝚄𝚁𝙽', '𝙲𝙾𝙿𝚈']; //F0~F
Chars0to1F = "・☺☻♥♦♣♠•◘○◙♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼"; //#0 is a small dot from Japanese; could've used ␀ but readability
Chars0to1FLF = "・☺☻♥♦♣♠•◘○\x0A♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼";
Chars0to1FCRLF = "・☺☻♥♦♣♠•◘○\x0A♂♀\x0D♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼";
Chars0to1FSpeccy = "\x00\x01\x02\x03\x04\x05📝///⬅➡⬇⬆⌫\x0A№\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; //not mixing...
Chars0to1FATASCII = "♥├◨┘┤┐/╲◢▗◣▝▘ ̄▂▖♣┌─┼•▄▎┬┴▌└␛↑↓←→";
Chars0to1FATASCII2 = "áùÑÉçôòì£ïüäöúóöÜâûîéèñêȧàȦ␛↑↓←→";
Chars0to1FATASCII_PL = "ŹąźćŚėöÖ£üߣłŃÓ√ĘśäÜĆĄŻÄż␛↑↓←→";
Chars0to1FAtariX = "áùÑÉçôòì£ïüäÖúóöÜâûîéèñêȧàȦË↑↓←→";
Chars0to1FPETSCII = "\x00\x01_⛔\x04⚪\x06🔔⇪\t\x0A↘⇪\x0D↘💥\x10↓🔲⇱⌫🗑🗯\x17⇆↟?⎋🟥→🟩🟦"; //took the ctrl codes that meant something for each char
/**
* Decode a 1-byte-per-character encoding from a byte array using the 129-byte-long table given,
* as well as a table to display the first 32 characters.
* @param {[uint8]} ansi - an array of uint8 (returned by readBytes).
* @param {String[0x81]} dectbl - a decoding table; just make a const here in db/read for that
* @param {bool} [zstop =true] - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {Array} [tbl01F=Chars0to1FCRLF] - which table to use for the first 32 characters
* @param {number[]} ansi - an array of uint8 (returned by readBytes).
* @param {string[]} dectbl - a decoding table[0x81], String or Array; just make a const here in db/read for that
* @param {boolean} [zstop = true] - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {string[]} [tbl01F = Chars0to1FCRLF] - which table to use for the first 32 characters
* @returns {String} a string value usable with js, or an empty line if the decoding table wasn't found.
* @example
* 𝑓([7, 0x7F, 0, 0x32], true, Chars0to1FSpeccy, CPSpeccy]) === "📝©"
* 𝑓([7, 0x7F, 0, 0x32], true, Chars0to1FSpeccy, CPSpeccy]) === "📝©" ("2" will be lost)
* @example
* 𝑓("\x07\x7F\x00\x32", false, Chars0to1FSpeccy, CPSpeccy]) === "📝© 2"
* 𝑓("\x07\x7F\x00\x32", false, Chars0to1FSpeccy, CPSpeccy]) === "📝© 2"
*/
function decEncoding(ansi, dectbl, zstop, tbl01F) {
if(typeof dectbl === 'undefined') return '';
if(typeof zstop === 'undefined') zstop = true;
if(dectbl.length > 129) { // full 256-byte tables :) Nothing fancy, no control bytes.
var s = ""; for(var i=0; i < ansi.length; i++) { if(!ansi[i] && zstop) break; else s += dectbl[ansi[i]] } return s
}
if(typeof tbl01F === 'undefined')
if(dectbl == CPSpeccy) tbl01F = Chars0to1FSpeccy;
else if(dectbl == CPATASCII) tbl01F = Chars0to1FATASCII;
else tbl01F = Chars0to1FCRLF;
var s = "", bit8 = 0;
for(var i=0; i < ansi.length; i++) {
@ -120,13 +146,13 @@ function decEncoding(ansi, dectbl, zstop, tbl01F) {
else s += tbl01F[0xF]; break;
case 0x5C: if(dectbl == JISX0201) s += '¥'; else s += '\\'; break;
case 0x5E: if(dectbl == CPSpeccy) s += '↑'; else s += '^'; break;
case 0x60: if(dectbl == CPSpeccy) s += '£'; else s += '`'; break;
case 0x60: if(dectbl == CPSpeccy) s += '£'; else if(dectbl == CPPETSCIIshifted) s += ' '; else s += '`'; break;
case 0x7B: if(dectbl == CPATASCII)
if(tbl01F == Chars0to1FATASCII2) s += 'Ä';
else if(tbl01F == Chars0to1FATASCII_PL) s += ' '; //the Poles didn't need the spades without the other 3 :D
else s += '♠';
else s += '{'; break;
case 0x7D: if(dectbl == CPATASCII) s += '↖'; else s += '}'; break; //arrow up then NW (🢰) not included for UTF limitations
case 0x7D: if(dectbl == CPATASCII) s += '↖'; else s += '}'; break; //arrow N-NW (🢰) not included for UTF limitations
case 0x7E: if(dectbl == JISX0201) s += '‾'; else if(dectbl == CPATASCII) s += '◀'; else s += '~'; break;
case 0x7F:
if(dectbl != JISX0201) s += dectbl[0]; else s += String.fromCharCode(bit8+ansi[i]); break;
@ -143,12 +169,12 @@ function decEncoding(ansi, dectbl, zstop, tbl01F) {
/**
* Decode a 1-byte encoding from file using the 129-byte-long table given, as well as a table to display
* the first 32 characters. Analogous to decEncoding but reads the file directly.
* @param {UInt} ofs - the offset to start from.
* @param {UInt} len - the amount of bytes to read.
* @param {String[0x81]} dectbl - a decoding table; just make a const here in db/read for that
* @param {bool} zstop (optional, default=true) - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {Array} tbl01F (optional, default=Chars0to1FCRLF) - which table to use for the first 32 characters
* @returns {String} a string value usable with js, or an empty line if the decoding table wasn't found.
* @param {number} ofs - the offset to start from.
* @param {number} len - the amount of bytes to read.
* @param {string[]} dectbl - a decoding table[0x81[, String or Array; just make a const here in db/read for that
* @param {boolean} [zstop = true] - whether to stop reading on 0 (ASCIIZ behaviour)
* @param {array} [tbl01F = Chars0to1FCRLF] - which table to use for the first 32 characters
* @returns {string} a string value usable with js, or an empty line if the decoding table wasn't found.
*/
function decAnsi(ofs, len, dectbl, zstop, tbl01F) {
return decEncoding(X.readBytes(ofs,len), dectbl, zstop, tbl01F);
@ -157,10 +183,10 @@ function decAnsi(ofs, len, dectbl, zstop, tbl01F) {
/**
* Checks for whether a value fits the limits using <=, as opposed to isInside.
* @param {Number} a - the value to check.
* @param {Number} mina
* @param {Number} maxa
* @returns {Bool}
* @param {number} a - the value to check.
* @param {number} mina
* @param {number} maxa
* @returns {boolean}
* @example
* 𝑓(20, 10, 40) === true; 𝑓(20,10,20) === true
*/
@ -170,10 +196,10 @@ function isWithin(a, mina, maxa) {
/**
* Checks for whether a value fits the limits using <, as opposed to isWithin. Useful for floats.
* @param {Number} a - the value to check.
* @param {Number} mina
* @param {Number} maxa
* @returns {Bool}
* @param {number} a - the value to check.
* @param {number} mina
* @param {number} maxa
* @returns {boolean}
* @example
* 𝑓(20.1, 10.0, 40.0) === true; 𝑓(20.1, 10.0, 20.0) === false
*/
@ -182,11 +208,30 @@ function isInside(a, mina, maxa) {
}
/**
* isWithin but for a list of ranges. Useful for heuristic byte-has-a-possible-value checks
* @param {Array} a - Can contain either values like [min,max] or singular values to compare (strictly) against
* @returns {boolean}
*/
function isWithinRanges(a, rr) {
if (isNaN(a) || !Array.isArray(rr)) return;
var i = 0, found = false;
for (; i < rr.length; i++) {
if (Array.isArray(rr[i])) {
if (!rr[i].length) return;
if (rr[i].length > 1) { if (rr[i][0] <= a && a <= rr[i][1]) { found = true; break } }
else if (a === rr[i][0]) { found = true; break }
}
else if (a === rr[i]) { found = true; break}
}
return found
}
/**
* Derive a string hexadecimal value, zero-padded.
* @param {Int} a - the numerical value.
* @param {UInt} [padz=2] - how many characters to zero-pad to.
* @returns {String} The hex value, capital letters A~F, ending with "h".
* @param {number} a - the numerical value.
* @param {number} [padz=2] - how many characters to zero-pad to.
* @returns {string} The hex value, capital letters A~F, ending with "h".
*/
function Hex(a, padz) {
if(typeof a === 'undefined') return "!Hex("+a+")";
@ -200,9 +245,9 @@ function Hex(a, padz) {
/**
* Derive a string binary value, zero-padded.
* @param {Int} a - the numerical value.
* @param {UInt} [padz=4] - how many characters to zero-pad to.
* @returns {String} The bin value, characters '0' or '1', ending with "b".
* @param {number} a - the numerical value.
* @param {number} [padz=4] - how many characters to zero-pad to.
* @returns {string} The bin value, characters '0' or '1', ending with "b".
*/
function Bin(a, padz) {
if(typeof a === 'undefined') return "!Bin("+a+")";
@ -216,9 +261,9 @@ function Bin(a, padz) {
/**
* Derive a string octal value, zero-padded.
* @param {Int} a - the numerical value.
* @param {UInt} [padz=3] - how many characters to zero-pad to.
* @returns {String} The octal value, characters 0~7, ending with "o".
* @param {number} a - the numerical value.
* @param {number} [padz=3] - how many characters to zero-pad to.
* @returns {string} The octal value, characters 0~7, ending with "o".
*/
function Oct(a, padz) {
if(typeof a === 'undefined') return "!Oct("+a+")";
@ -232,8 +277,8 @@ function Oct(a, padz) {
/**
* Read a variable-length quantity, an unsigned integer like in MIDI files, from the file.
* @param {UInt} ofs - the offset to start from.
* @returns {List} [length,value] - if length (in physical bytes) = 0, the value had a problem.
* @param {number} ofs - the offset to start from.
* @returns {Array} [length,value] - if length (in physical bytes) = 0, the value had a problem.
* @example
* //For file containing '81 80 01 81 83 7F':
* 𝑓(0) === 0x4001; 𝑓(3) === 0x41FF
@ -250,19 +295,58 @@ function readVarUInt(ofs) {
}
function readFloat80(ofs, e) {
if (e != _BE) e = _LE;
// Normalize into little-endian order
var b = [];
if (e == _LE) {
for (var i = 0; i < 10; i++) b[i] = X.U8(ofs + i);
} else {
for (var i = 0; i < 10; i++) b[9 - i] = X.U8(ofs + i);
}
// Mantissa: 8 bytes, little-endian
var mantissa = 0;
for (var i = 0; i < 8; i++) {
mantissa += b[i] * Math.pow(256, i);
}
// Exponent+sign: 2 bytes, little-endian
var expWord = b[8] + (b[9] << 8);
var sign = (expWord & 0x8000) ? -1 : 1;
var exponent = expWord & 0x7FFF;
if (exponent === 0 && mantissa === 0) return 0.0;
if (exponent === 0x7FFF) {
if (mantissa === 0) return sign * Infinity;
return NaN;
}
// Bias 16383
var eVal = exponent - 16383;
// Integer bit is explicit (bit 63 of mantissa)
var intBit = Math.floor(mantissa / Math.pow(2, 63));
var frac = (mantissa % Math.pow(2, 63)) / Math.pow(2, 63);
return sign * (intBit + frac) * Math.pow(2, eVal);
}
/**
* This object facilitates reading a file as a sequence of bits
* @init {UInt} [nOffset = 0] - provide the file offset
* @param {UInt} nBits - bits to read, autolimits to 32 (so read little by little!)
* @returns {UInt} read value as integer, -1 if EoF reached
* @constructor {number} [nOffset = 0] - initialise: provide the file offset
* @param {number} nBits - bits to read, autolimits to 32 (so read little by little!)
* @returns {number} read value as integer, -1 if EoF reached
* @example
* First create an instance with the file object: var bits = new BitReader(10);
* Then call the readBits method with the number of bits you want: var value = bits.read(5)
* Or put the reader towards a different place: bits.init(10)
* Receive the current bit-file offset: bits.offset
* Set the bit-file's offset, in bytes, without changing state: bits.seek(10)
* Set the bit-file's offset in bits: bits.bseek(14)
* Skip some bytes without changing state: bits.consume(2)
* var bits = new BitReader(10); // First create an instance with the file object
* var value = bits.read(5); // Then call the readBits method with the number of bits you want
* bits.init(10); // Or put the reader towards a different place
* p = bits.offset; // Receive the current bit-file offset
* bits.seek(10); // Set the bit-file's offset, in bytes, without changing state
* bits.bseek(14); // Set the bit-file's offset in bits
* bits.consume(2); // Skip some bytes without changing state
**/
function BitReader(nOffset, nEndian) {
this.n = 0; // the number of bits in the buffer
@ -270,10 +354,10 @@ function BitReader(nOffset, nEndian) {
this.offset = nOffset ? nOffset : 0; // the file offset
this.endian = nEndian ? nEndian : _LE; // for different mechanics of bitstreaming; ogg/flac use _BE
// use this to change the pointer, which will reinit the reader, but not the logger
// Change the pointer, which will reinit the reader, but not the logger
this.init = function(nOffset) { this.ofs = nOffset ? nOffset : 0; this.n = this.buf = 0 }
// the method to read b bits from the file
// Read b bits from the file
this.read = function(nBits) {
if(nBits > 64) nBits = 64; if(nBits < 0) return 0;
if(this.endian === _LE) {
@ -295,7 +379,7 @@ function BitReader(nOffset, nEndian) {
return v; // return the value even if the file is exhausted
}
// Skip some bytes without changing state: bits.consume(2)
// Skip some bytes without changing state:
this.consume = function(nBytes) { this.offset += nBytes; }
// Set the bit-file's offset, in bytes, without changing state:
@ -306,19 +390,23 @@ function BitReader(nOffset, nEndian) {
}
/**
* Count set bits in an integer.
* @param {number} n
*/
function bitCount(n) {
var c = 0; n = !!n; while(n) { if((n&1)) c++; n >>= 1 } return c;
var c = 0; n = !!n; while(n) { if(n&1) c++; n >>= 1 } return c;
}
/**
* Check a file slice for being all one of a lineup of specified characters.
* @param {UInt} ofs - the offset to start from.
* @param {UInt} len - the amount of bytes to check.
* @param {[Array(UInt)] | UInt} bl = [0] - the list of possible u_int8 codes, or just one u_int8. 0 by default.
* @returns {UInt} - offset if a byte in the slice doesn't belong to the list. If you go beyond EoF or all good, -1.
* @param {number} ofs - the offset to start from.
* @param {number} len - the amount of bytes to check.
* @param {Array | number} [bl = 0x00] - the list of possible uint8 codes, or just one uint8.
* @returns {number} - offset if a byte in the slice doesn't belong to the list. If you go beyond EoF or all good, -1.
* @example
* //For '00 01 02 01' at offset 6 in the file:
* firstNotOf (6, 4, [0,1]) === 8; firstNotOf(6, 4, 1) === 6
* 𝑓(6, 4, [0,1]) === 8; 𝑓(6, 4, 1) === 6
*/
function firstNotOf(ofs, len, bl) {
if(ofs+len > X.Sz()) return -1; var c = i = 0;
@ -326,15 +414,15 @@ function firstNotOf(ofs, len, bl) {
for(i = 0; i < bl.length; i++) if(typeof bl[i] !== 'number' || bl[i] < 0 || bl[i] % 1 != 0) break;
if(i < bl.length) throw new Error('firstNotOf cannot parse: '+outArray(bl));
}
else if(typeof bl === 'number' && bl > 0 && bl % 1 == 0) bl = [bl]; else bl = [0];
len = Math.min(len+ofs-X.Sz(),len);
else if(typeof bl === 'number' && bl > 0 && (bl % 1) == 0 || typeof bl === 'string') bl = [bl]; else bl = [0];
len = Math.min(len, X.Sz()-ofs);
// and now test the slice
for(i = 0; i < len; i++) if(bl.indexOf(X.U8(ofs+i)) < 0) break;
for(i = 0; i < len; i++) if(!bl.includes(X.U8(ofs+i))) break;
return i < len? ofs+i : -1;
}
function isAllZeroes(ofs, len) { return firstNotOf(ofs, len, 0) < 0 } //a subcase for whether a slice is all zeroes
//A subcase of firstNotOf for whether a slice is all zeroes.
function isAllZeroes(ofs, len) { return firstNotOf(ofs, len) < 0 }
/**
@ -342,15 +430,15 @@ function isAllZeroes(ofs, len) { return firstNotOf(ofs, len, 0) < 0 } //a subcas
* complete word, to avoid cut-off words. If `space characters' are not detected,
* replaces the last character with an ellipsis. Does NOT do the trim() unless the whole string fits the limit.
* Mostly usable for lengthy multiline comments/messages.
* @param {String} a - the original incomplete string.
* @param {Number} trim - the buffer size; if a.length == trim, we decide it was cropped.
* @param {Number} [mintrim = 78] - don't try searching for spaces below this point.
* @returns {String} - the resulting string.
* @param {string} a - the original incomplete string.
* @param {number} trim - the buffer size; if a.length == trim, we decide it was cropped.
* @param {number} [mintrim = 78] - don't try searching for spaces below this point.
* @returns {string} - the resulting string.
* @example
* // Full string fits the limits, no need to search for a good cut-off place, and it's trimmed for pretty:
* 𝑓(" 12345 7890, 34 678. 1 3'56789", 35, 15) === "12345 7890, 34 678. 1 3'56789"
* @example
* // The length is a full match but it might be trimmed right along that length, so a good cut-off place is found:
* // The length is a full match, but it might be trimmed right along that length, so a good cut-off place is found:
* 𝑓(" 12345 7890, 34 678. 1 3'56789", 30, 15) === " 12345 7890, 34 678. 1 3…"
* @example
* // The length is a full match and last 5 characters don't have spaces, so it's treated as trimmed but it's not cut:
@ -371,7 +459,7 @@ function addEllipsis(a, trim, mintrim) {
if((i < mintrim && c < 2) //we conclude this language doesn't really have that many spaces...
|| !c) //...or none at all in the trimmable slice...
return a.slice(0,trim)+'…';
else //this language has some spaces and we can use the last one to trim
else //this language has some spaces, and we can use the last one to trim
return a.slice(0,Math.max(ci),mintrim)+'…';
}
@ -396,12 +484,12 @@ function sOptionT(a, prefix, suffix, sep) {
/**
* sOptions.appendS a string (optionally prefixed) if the string is not empty.
* @param {variant} a - the string to output (safe to accidentally drop a non-string in)
* @param {String} [prefix=''] - what to put in front of the output string
* @param {String} [suffix=''] - what to put after the output string
* @param {String} [sep=', '] - what to put between the previous string and this addition
* @param {string} [prefix = ''] - what to put in front of the output string
* @param {string} [suffix = ''] - what to put after the output string
* @param {string} [sep = ', '] - what to put between the previous string and this addition
* @example
* //for sOptions === ' ch:2':
* 𝑓('hello world ', 'msg:"', '"', ', '); //sOptions === 'ch:2, msg:"hello world\"'
* 𝑓('hello world ', 'msg:"', '"', ', ') === 'ch:2, msg:"hello world\"'
*/
function sOption(a, prefix, suffix) {
if (typeof prefix === 'undefined') prefix = ""; if (typeof suffix === 'undefined') suffix = "";
@ -415,7 +503,7 @@ function sOption(a, prefix, suffix) {
* taking into account and visualising the difference from the actual file size.
* If some of the reported sizes match, the value will only be displayed once.
* It's still a good idea to add "/malformed!short" to the version string — it's visible without isVerbose.
* @param {...Number} sizes - numerical values
* @param {...number} sizes - numerical values
* @example
* // If a file is 100 bytes long:
* outSz(90,100,105) === "90(+10)/100/105(-5!)"
@ -427,8 +515,8 @@ function outSz() { if(!arguments.length || typeof arguments[0] === 'undefined')
if(arguments[i] >= 0) if(!origs.length || origs.indexOf(arguments[i]) < 0) {
origs.push(arguments[i]);
sizes.push(
arguments[i] < File.getSize() ? arguments[i]+"(+"+(File.getSize()-arguments[i])+")"
: arguments[i] > File.getSize() ? arguments[i]+"(-"+(arguments[i]-File.getSize())+"!)"
arguments[i] < X.Sz() ? arguments[i]+"(+"+(X.Sz()-arguments[i])+")"
: arguments[i] > X.Sz() ? arguments[i]+"(-"+(arguments[i]-X.Sz())+"!)"
: arguments[i]
)
} else; else sizes.push("?");
@ -448,12 +536,13 @@ function outSz() { if(!arguments.length || typeof arguments[0] === 'undefined')
* 𝑓([ 1, [5, [10,30]], [[23],'test'] ], 16) == "[01, [05, [0A, 1E]], [[17], "test"]]"
*/
function outArray(a,base,pad) {
// if(!Array.isArray(a)) return a;
if(typeof base !== 'number' || base % 1 !== 0) base = 10;
if(typeof pad !== 'number' || pad % 1 !== 0) //not integer
if(typeof pad === 'undefined') switch(base) {
case 8: pad = 3; break; case 16: pad = 2; break; case 2: pad = 4; break; default: pad = 0
}
if(typeof a === 'number') return a.toString(base).toUpperCase().padStart(pad,'0');
if(typeof a === 'string') return '"'+a+'"';
for(var i=0, s = []; i < a.length; i++) {
if(Array.isArray(a[i])) s.push(outArray(a[i],base,pad)); else
if(typeof a[i] === 'number' && a[i] % 1 === 0) //integer
@ -498,9 +587,9 @@ function slashTag(a, b) {
/**
* createOrderlyHuffmanTable is just for detections but it does return the table for further checks. Or it returns false.
* @param {Array} lent - the lengths table
* @param {String} btl - bit table length
* @param {string} btl - bit table length
* @param {BitReader} br - a BitReader object pointing somewhere at the right position for this. The provided BitReader WILL change state.
* @returns {Array or false}
* @returns {Array | false}
*/
function createOrderlyHuffmanTable(lent, btl, br) {
var md = 32, Md = reall = code = 0; var _t = [], fi = [], li = [], ni = [];
@ -531,8 +620,8 @@ function createOrderlyHuffmanTable(lent, btl, br) {
/**
* Outputs time in seconds as short human-readable, with a "h:mm:ss" alternative when < 1 day.
* Millenia, centuries and years as sidereal years, a "month" duration is 1/12 of such year.
* @param {Number} seconds
* @returns {String}
* @param {number} s - seconds
* @returns {string}
* @example
* 𝑓(123456789) === "31Y10M4w21h33m9s"; 𝑓(1234567) === "2w6h56m7s"; 𝑓(12345) === "3:25:45"
*/
@ -543,7 +632,7 @@ function secondsToTimeStr(s) {
yy = Util.div64(s%mul[1],mul[2]), cc = Util.div64(s%mul[0],mul[1]), mi = Util.div64(s,mul[0]);
if(s < 86400) { r = mm.padStart(2,'0')+":"+ss.padStart(2,'0'); if(hh) r = hh+":"+r; return r }
if(ss) r = ss+"s"+r; if(mm) r = mm+"m"+r; if(hh) r = hh+"h"+r; if(dd) r = dd+"d"+r; if(ww) r = ww+"w"+r;
if(mn) r = mn+"M"+r; if(yy) r = yy+"Y"+r; if(cc) r = cc+"C"+r; if(mi) r = ci+"Mil"+r; return r
if(mn) r = mn+"M"+r; if(yy) r = yy+"Y"+r; if(cc) r = cc+"C"+r; if(mi) r = mi+"Mil"+r; return r
}
@ -684,8 +773,8 @@ function findIntersections(lst,detectone) {
/**
* Finds several signatures one after the other, using an array of pairs of [signature, range] and starting from a specified offset. The range to search across is in the 0-th element of aList.
* Maxlength is to be interpreted the same as in findSignature. (It has to be at least the length of the signature.)
* @param {UInt} nOffset - Where to start searching from (or 0 if bad data).
* @param {UInt} nLength - Maxlength: the same as in findSignature. (It has to be at least the length of the signature.)
* @param {Array of arrays[2]} aList - List to process consisting of pairs of [signature, range].
* @param {UInt} [nStep=1] - In case subsequent searches fail, skip this many bytes before searching for more (this handles alignments too). If 0, stop if not found immediately.
* @returns -1 if not found, offset otherwise.
@ -718,16 +807,16 @@ function funSampleName(n, ctx) {
var fun = 1, bore = 0; //by default, we're curious about the new line just a little
n = n.toString();
if(n.trim() == '') return false;
if(n.endsWithCI('.wav') || n.endsWithCI('.smp') || n.endsWithCI('.iff')) bore++;
if(n.endsWithCI('.wav') || n.endsWithCI('.smp') || n.endsWithCI('.ins') || n.endsWithCI('.iff')) bore++;
if(n.startsWithCI('ST-') || n.startsWithCI('df0:')) bore++;
if(/^\d+$/.test(n)) bore++;
if(n.startsWithCI('#')) fun += 5;
if(n.startsWith('#')) fun += 5;
if(/^\s*(unnamed|dr[u]?m\d*|loop\s*\d*|strings?\s*\d*|bass\s*\d*|guitar\s*\d*|snare\s*\d*|piano)\s*$/i.test(n)
|| /^\s*(trumpet\s*\d*|bells\s*|synth\s*[0-9iv]*|shaker|banjo|lead|syn\d+|\w*\s*hihat|organ\s*\d*)\s*$/i.test(n)
|| /^\s*(voice\s*\d+|crash|cymbal\s*\d*|wonderpad\s*[0-9iv]+|tambourine\s*[0-9iv]*|tamb\d+)\s*$/i.test(n)
|| /^\s*((hard|echo)\s?tom|splash|cymbhit|orchm(in|aj)|orch\s?hit|chimes?|kick|scratch)\s*$/i.test(n)
|| /^\s*((ghost\s?|brass\s?)pad\s*(min|maj|\(sus4\))?|blip|bleep|hhc|hho|clv|bd|sd|pad)\s*$/i.test(n)
|| /^\s*(m(aj|in)or\s*[0-9iv]*|sawsynth|synbrass|hihat\sclosed|)\s*$/i.test(n))
|| /^\s*(m(aj|in)or\s*[0-9iv]*|sawsynth|synbrass|hihat\sclosed)\s*$/i.test(n))
bore++;
//_setResult('fsn',n,'fun='+fun+' bore='+bore,'')
return fun > bore;
@ -810,21 +899,21 @@ function _l2r(name, pos, issue) { _setResult('debug', issue, '@'+Hex(pos), name)
// Returns the current line of the callee (ie. where you call this function from)
function _currentLine() {
const e = new Error();
const stackLine = e.stack.split("\n")[1];
const match = stackLine.match(/:(\d+)?$/);
return match ? parseInt(match[1], 10) : null;
const e = new Error();
const stackLine = e.stack.split("\n")[1];
const match = stackLine.match(/:(\d+)?$/);
return match ? parseInt(match[1], 10) : null;
}
// Simply logs the calling function and the current line. All hail the printf debugging!
function _logIt(msg) {
if (typeof msg === "undefined") msg = "";
const callerLine = new Error().stack.split("\n")[1] || "",
fnmatch = callerLine.match(/^(\w+)@/), // extract function name
fnName = fnmatch ? fnmatch[1] : "<anon>",
lineMatch = callerLine.match(/:(\d+)(?!.*:)/),
lineNum = lineMatch ? lineMatch[1] : "?";
_log(fnName + (lineNum == "?" ? "" : ": " + lineNum) + (!msg.length ? "" : ": " + msg));
if (typeof msg === "undefined") msg = "";
const callerLine = new Error().stack.split("\n")[1] || "",
fnmatch = callerLine.match(/^(\w+)@/), // extract function name
fnName = fnmatch ? fnmatch[1] : "<anon>",
lineMatch = callerLine.match(/:(\d+)(?!.*:)/),
lineNum = lineMatch ? lineMatch[1] : "?";
_log(fnName + (lineNum == "?" ? "" : ": " + lineNum) + (!msg.length ? "" : ": " + msg));
}
/** A class that'll help profile overly long scripts until Qt offers better tools!
@ -849,7 +938,9 @@ function CheckpointTimer() {
this.next = function(msg) {
var now = new Date().getTime();
var delta = now - this.last;
if (delta >= this.min) _setResult('prof', msg, '', delta + " ms passed");
if (delta >= this.min) _setResult('prof', msg, '', delta + " ms passed. "
+(X.isOverlay()?' overlay':'')+(X.isResource()?' resource':'')+(X.isFilePart()?' file_part':'')+" Hex at [0]: "
+X.getSignature(0,32));
this.last = now;
}
}