ltgcgo/octavia

View on GitHub
src/disp/disp_sc8850.mjs

Summary

Maintainability
Test Coverage
"use strict";
 
import {OctaviaDevice, allocated} from "../state/index.mjs";
import {RootDisplay, ccToPos} from "../basic/index.mjs";
import {MxFont40, MxFont176, MxBmDef} from "../basic/mxReader.js";
 
import {getDebugState} from "../state/utils.js";
 
import {
backlight,
inactivePixel,
activePixel,
lcdPixel,
lcdCache
} from "./colour.js";
 
const exExhaust = 400, exDuration = 1000;
 
let totalWidth = 160, totalHeight = 64, totalPixelCount = totalWidth * totalHeight;
 
let flipBitsInBuffer = (buf, bWidth, startX, startY, width, height) => {
let endX = startX + width, endY = startY + height;
for (let pY = startY; pY < endY; pY ++) {
let lineOff = pY * bWidth;
for (let pX = startX; pX < endX; pX ++) {
buf[lineOff + pX] = buf[lineOff + pX] ? 0 : 255;
};
};
};
let fillBitsInBuffer = (buf, bWidth, startX, startY, width, height, bit = 255) => {
let endX = startX + width, endY = startY + height;
for (let pY = startY; pY < endY; pY ++) {
let lineOff = pY * bWidth;
for (let pX = startX; pX < endX; pX ++) {
buf[lineOff + pX] = bit;
};
};
};
 
let twoLetterMode = {
"?": " ",
"mt32": "mt",
"doc": "dc",
"qy10": "qy",
"qy20": "qy",
"ns5r": "n5",
"x5d": "xd",
"05rw": "rw",
"k11": "kg",
"krs": "kr",
"s90es": "es",
"motif": "es",
"trin": "tr"
};
 
let Sc8850Display = class extends RootDisplay {
#pixelLit = 255;
#pixelOff = 0;
#lastBg = 0;
#nmdb = new Uint8Array(totalPixelCount);
#dmdb = new Uint8Array(totalPixelCount);
#omdb = new Uint8Array(totalPixelCount);
#bmdb = new Uint8Array(totalPixelCount);
#linger = new Uint8Array(allocated.ch);
#lingerExtra = new Uint8Array(allocated.ch);
#lingerPress = new Uint8Array(allocated.ch);
#ch = 0;
#range = 0;
#start = 255; // start port
#mode = "?";
useBlur = false;
#scheduledEx = false;
#unresolvedEx = false;
#awaitEx = 0;
#promptEx = 0;
#dumpExpire = 0;
#booted = 0;
#bootFrame = 0;
#letterMode;
font55 = new MxFont40("./data/bitmaps/sc/libre55.tsv");
font56 = new MxFont40("./data/bitmaps/sc/libre56.tsv");
bootBm = new MxBmDef();
sysBm = new MxBmDef("./data/bitmaps/sc/system.tsv");
font7a = new MxFont176("./data/bitmaps/sc/libre7a.tsv");
voxBm = new MxBmDef("./data/bitmaps/sc/voices.tsv");
constructor(conf) {
super(new OctaviaDevice(), 0.25, 0.5);
let upThis = this;
upThis.useBlur = !!conf?.useBlur;
upThis.addEventListener("channelactive", (ev) => {
upThis.#ch = ev.data;
});
upThis.addEventListener("portrange", (ev) => {
if (ev && ev.data !== 1 << Math.log2(ev.data)) {
console.debug(`MU display rejected port range value ${ev.data}.`);
};
upThis.#range = ev.data;
});
upThis.addEventListener("portstart", (ev) => {
if (ev !== 255 && ev >= allocated.port) {
console.debug(`MU display rejected port range value ${ev.data}.`);
};
upThis.#start = ev.data;
});
upThis.device.addEventListener("mode", (ev) => {
upThis.#mode = ev.data;
});
upThis.device.addEventListener("mupromptex", () => {
upThis.#scheduledEx = true;
getDebugState() && console.debug(`Scheduled a SysEx prompt.`);
});
upThis.device.addEventListener("reset", (ev) => {
upThis.#dumpExpire = 0;
upThis.#bmdb.fill(0);
});
upThis.device.addEventListener("screen", (ev) => {
let data = ev.data;
if (data.type === "sc8850") {
for (let i = 0; i < data.data.length; i ++) {
upThis.#bmdb[data.offset + i] = data.data[i] ? 255 : 0;
};
};
upThis.#dumpExpire = Date.now() + 5000;
});
upThis.device.addEventListener("letter", (ev) => {
upThis.#letterMode = upThis.#mode;
});
upThis.device.addEventListener("note", (ev) => {
if (ev.data) {
let data = ev.data;
if (data.state === 3 && data.velo) {
upThis.#lingerPress[data.part] = 1;
};
};
});
upThis.bootBm.load(`RsrcName\tBitmap\nboot_mr\t009e003efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffe3ffffffffffffffffffe0007fffffffffffffff8ffffffffffffffffffe0000fffffe3ffffffffffffffffffffffffffff01e01fffff8ffffffffffffffffffffffffffff03ff07f83f007c0787e187f01ffffffffffffff83ffe0f003803800e1f8e3e003fffffffffffffe1fff83838787c18187c70f0607fffffffffffff07ffe0c3f3e3e1f861e3c787e1fffffffffffffc3fff863fff0f1fe1871e1c7f87ffffffffffffe0fffc10fffc787f8e18f8e1fe3fffffffffffff83fff0c7fff1e3fe3847e38ff8fffffffffffffe0fff831fff878ff0e03f0e3fc3fffffffffffffc1ff81c7f3e3e3f8781fc78fe1ffffffffffffff00f01f06070f8301e0fe1e0c07fffffffffffffe0000fe003c7e000f83f8f8003ffffffffffffffc000ffc07e1fe063e1fc3f818fffffffffffffffe01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nbs_0\t000700073880000000000\nbs_1\t000700073888081020000\nbs_2\t0007000738080810288e0\nbs_3\t00070007388a0c18288e0\nbs_4\t00070007000a0c18288e0\nbs_5\t0007000700020408088e0\nbs_6\t000700070002040808000\nbs_7\t000700070000000000000`);
(async () => {
await Promise.all([upThis.font55.loaded.wait(), upThis.font56.loaded.wait(), upThis.font7a.loaded.wait()]);
upThis.#booted = 1;
await Promise.all([upThis.sysBm.loaded.wait(), upThis.voxBm.loaded.wait()]);
upThis.#booted = 2;
})();
};
setCh(ch) {
this.#ch = ch;
};
getCh() {
return this.#ch;
};
reset() {
super.reset();
this.#range = 0;
this.#start = 255;
this.#lingerExtra.fill(0);
};
render(time, ctx) {
let sum = super.render(time);
let upThis = this;
let timeNow = Date.now();
let fullRefresh = false;
let scConf = upThis.device.modelEx.sc;
if (timeNow >= upThis.#dumpExpire) {
upThis.#nmdb.fill(0);
};
// Prepare the canvas
if (timeNow - upThis.#lastBg >= 3600000) {
ctx.fillStyle = backlight.orange;
ctx.fillRect(0, 0, 808, ctx.canvas.height);
// Show text
ctx.fillStyle = "#000";
ctx.fillRect(808, 0, ctx.canvas.width - 808, ctx.canvas.height);
ctx.fillStyle = backlight.orange;
ctx.textAlign = "left";
ctx.font = '400 14px "Work Sans"';
ctx.textRendering = "geometricPrecision";
ctx.fillText("EFX", 810, 121);
ctx.fillText("8850", 810, 166);
ctx.fillText("Pro", 810, 192);
ctx.fillText("88", 810, 216);
ctx.fillText("55", 810, 241);
upThis.#lastBg = timeNow;
fullRefresh = true;
};
// Booted?
if (timeNow < upThis.#dumpExpire) {
upThis.#nmdb.set(upThis.#bmdb);
} else if (upThis.#booted > 0 && upThis.#bootFrame > 99) {
// Test SysEx status
if (upThis.#scheduledEx) {
upThis.#scheduledEx = false;
if (timeNow - upThis.#promptEx > exExhaust) {
upThis.#unresolvedEx = true;
getDebugState() && console.debug(`SysEx prompt submitted.`);
} else {
getDebugState() && console.debug(`SysEx prompt too busy.`);
};
upThis.#awaitEx = timeNow;
};
// Channel test
let alreadyMin = false;
let minCh = 0, maxCh = 0;
sum.chInUse.forEach(function (e, i) {
if (e) {
if (!alreadyMin) {
alreadyMin = true;
minCh = i;
};
maxCh = i;
};
});
let part = minCh >> 4;
minCh = part << 4;
maxCh = ((maxCh >> 4) << 4) + 15;
if (upThis.#ch > maxCh) {
upThis.#ch = minCh + upThis.#ch & 15;
};
if (upThis.#ch < minCh) {
upThis.#ch = maxCh - 15 + (upThis.#ch & 15);
};
if (upThis.#range) {
if (upThis.#start === 255) {
minCh = (Math.floor((upThis.#ch >> 4) / upThis.#range) * upThis.#range) << 4;
} else {
minCh = upThis.#start << 4;
};
maxCh = minCh + upThis.#range * 16 - 1;
};
let chOff = upThis.#ch * ccToPos.length;
let rendMode = Math.ceil(Math.log2(maxCh - minCh + 1) - 4);
//console.debug(minCh, maxCh, rendMode);
// Render current channel
upThis.font56.getStr(`${"ABCDEFGH"[upThis.#ch >> 4]}${`${(upThis.#ch & 15) + 1}`.padStart(2, "0")}`).forEach((e0, i0) => {
let offsetX = i0 * 6 + 1;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX, pY = Math.floor(i1 / 5) + 2;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
// Render bank info and voice name
let voiceObject = upThis.getChVoice(upThis.#ch);
let scLetterNative = upThis.#letterMode === "gs" || upThis.#letterMode === "sc";
let scLetterMode = 0;
if (scLetterNative && (sum.letter.set <= timeNow && timeNow < sum.letter.set + 15000)) {
if (sum.letter.text.length <= 16) {
scLetterMode = timeNow < sum.letter.expire ? 1 : 0;
//console.debug(`SC constant: ${sum.letter.expire - timeNow}`);
} else {
let scLetterDuration = sum.letter.set + Math.min(19 + sum.letter.text.length, 51) * 300;
scLetterMode = timeNow < scLetterDuration ? 2 : 0;
//console.debug(`SC variable: ${scLetterDuration - timeNow}`);
};
};
if (!scLetterNative || scLetterMode === 0) {
upThis.font56.getStr(voiceObject.bank).forEach((e0, i0) => {
let offsetX = i0 * 6 + 21;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX, pY = Math.floor(i1 / 5) + 2;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
upThis.font56.getStr(`${sum.chProgr[this.#ch] + 1}`.padStart(3, "0")).forEach((e0, i0) => {
let offsetX = i0 * 6 + 43;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX, pY = Math.floor(i1 / 5) + 2;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
flipBitsInBuffer(upThis.#nmdb, totalWidth, 42, 1, 19, 8);
upThis.font7a.getStr(upThis.getMapped(voiceObject.name).slice(0, 12).padEnd(12, " ")).forEach((e0, i0) => {
let offsetX = i0 * 8;
e0.forEach((e1, i1) => {
let pX = (i1 % 11) + offsetX + 63, pY = Math.floor(i1 / 11);
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
} else {
let displayText;
switch (scLetterMode) {
case 1: {
displayText = sum.letter.text;
break;
};
case 2: {
let voiceNamePadded = upThis.getMapped(voiceObject.name).padEnd(12, " ");
displayText = ` ${voiceNamePadded}<${sum.letter.text}< ${voiceNamePadded}`;
let cutoffStart = Math.floor((timeNow - sum.letter.set) / 300);
displayText = displayText.substring(cutoffStart, cutoffStart + 16);
break;
};
};
if (scLetterMode !== 0) {
upThis.font7a.getStr(displayText).forEach((e0, i0) => {
let offsetX = i0 * 8;
e0.forEach((e1, i1) => {
let pX = (i1 % 11) + offsetX + 31, pY = Math.floor(i1 / 11);
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
};
};
upThis.getChBm(upThis.#ch, voiceObject)?.render((e, x, y) => {
upThis.#nmdb[(y + 18) * totalWidth + x + 2] = e ? 255 : 0;
});
// Render port selection
switch (rendMode) {
case 0: {
let portStart = (upThis.#ch >> 6) << 2, portNow = upThis.#ch >> 4;
let shiftX = +!((portStart >> 6) & 1);
for (let i = 0; i < 4; i ++) {
let portWork = portStart + i;
let portOffX = i * 40 + 2 + shiftX, portOffY = (portWork === portNow) ? 59 : 58;
upThis.font55.getStr(`PART-${"ABCDEFGH"[portWork]}`).forEach((e0, i0) => {
let offsetX = i0 * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + portOffX, pY = Math.floor(i1 / 5) + portOffY;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
upThis.sysBm.getBm(portWork === portNow ? "tabSel" : "tabIdle")?.render((e, x, y) => {
if (e) {
let pI = (40 * i) + x + shiftX + (y + 57) * totalWidth;
upThis.#nmdb[pI] = upThis.#nmdb[pI] ? 0 : 255;
};
});
};
break;
};
case 1:
case 2:
case 3: {
let portNow = upThis.#ch >> 4;
for (let i = 0; i < 4; i ++) {
let portOffX = i * 40 + 2, portOffY = (rendMode === i) ? 59 : 58;
let tabText = i === 3 ? "128CH" : `${16 << i}CH-${"ABCDEFGH"[minCh >> 4]}`;
upThis.font55.getStr(tabText).forEach((e0, i0) => {
let offsetX = i0 * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + portOffX, pY = Math.floor(i1 / 5) + portOffY;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
upThis.sysBm.getBm(rendMode === i ? "tabSel" : "tabIdle")?.render((e, x, y) => {
if (e) {
let pI = (40 * i) + x + (y + 57) * totalWidth;
upThis.#nmdb[pI] = upThis.#nmdb[pI] ? 0 : 255;
};
});
};
break;
};
};
if (timeNow >= sum.letter.expire || (upThis.#letterMode === "gs" || upThis.#letterMode === "sc")) {
upThis.font56.getStr("123456789\x80\x81\x82\x83\x84\x85\x86").forEach((e0, i0) => {
let offsetX = i0 * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + 49, pY = Math.floor(i1 / 5) + 49;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
};
switch (upThis.#mode) {
case "?":
case "gs":
case "sc":
case "gm": {
break;
};
default: {
let mode = (twoLetterMode[upThis.device.getChMode(upThis.#ch)] || upThis.device.getChMode(upThis.#ch)).toUpperCase();
upThis.font56.getStr(mode).forEach((e0, i0) => {
let offsetX = i0 * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + 148, pY = Math.floor(i1 / 5) + 49;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
if (voiceObject.standard !== "GM" && mode !== voiceObject.standard) {
upThis.font56.getStr(voiceObject.standard).forEach((e0, i0) => {
let offsetX = i0 * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + 148, pY = Math.floor(i1 / 5) + 42;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
};
if (upThis.#unresolvedEx) {
upThis.#unresolvedEx = false;
upThis.#promptEx = timeNow;
getDebugState() && console.debug(`SysEx prompt resolved.`);
};
let exBlink = timeNow - upThis.#promptEx;
if (exBlink <= exDuration && !(Math.floor(exBlink / exDuration * 5) & 1)) {
upThis.font56.getStr("Ex").forEach((e0, i0) => {
let offsetX = i0 * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + 148, pY = Math.floor(i1 / 5) + 35;
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
};
break;
};
};
// Strength calculation
let renderRange = 1 << rendMode,
strengthHeight = (35 - renderRange + 1) / renderRange,
strengthDivider = 256 / strengthHeight;
sum.velo.forEach(function (e, i) {
if (scConf.peakHold === 3 && upThis.#lingerPress[i]) {
upThis.#lingerPress[i] --;
upThis.#lingerExtra[i] = 127;
if (e !== upThis.#linger[i]) {
upThis.#linger[i] = e;
};
};
if (e > upThis.#linger[i]) {
if (scConf.peakHold !== 3 && upThis.#lingerPress[i]) {
upThis.#linger[i] = e;
upThis.#lingerExtra[i] = 127;
};
} else {
let shouldKeep = upThis.#lingerExtra[i] >> 4;
if (shouldKeep) {
upThis.#lingerExtra[i] -= 6;
} else {
let val;
switch (scConf.peakHold) {
case 3: {
if (upThis.#linger[i] === 0) {
break;
};
val = upThis.#linger[i] + 2 * renderRange;
if (val > 255) {
val = 0;
};
break;
};
case 2: {
val = 0;
break;
};
case 1:
case 0: {
val = upThis.#linger[i] - 4 * renderRange;
if (val < 0) {
val = 0;
};
break;
};
};
upThis.#linger[i] = val;
};
};
});
//console.debug(renderRange, strengthHeight, strengthDivider);
// Render meters
if (timeNow < sum.bitmap.expire) {
// Actual bitmap
let colUnit = (sum.bitmap.bitmap.length > 256) ? 1 : 2;
for (let i = 0; i < 512; i += colUnit) {
let x = i & 31, y = i >> 5;
let realX = x * 3 + 49, realY = (y << 1) + 15;
let bit = sum.bitmap.bitmap[i >> (colUnit - 1)] ? upThis.#pixelLit : upThis.#pixelOff;
fillBitsInBuffer(upThis.#nmdb, totalWidth, realX, realY, 2, 2, bit);
if (colUnit === 2) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, realX + 2, realY, 3, 2, bit);
};
};
} else {
for (let i0 = 0; i0 < renderRange; i0 ++) {
let chStart = minCh + (i0 << 4);
for (let i1 = 0; i1 < 16; i1 ++) {
let ch = chStart + i1;
let strength = Math.floor(sum.strength[ch] / strengthDivider) + 1;
if (scConf.showBar) {
if (scConf.invBar) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 13 + i0 * strengthHeight + i0, 5, strength);
} else {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 48 - i0 * strengthHeight - strength - i0, 5, strength);
};
} else {
if (scConf.invBar) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 12 + i0 * strengthHeight + strength + i0, 5, 1);
if (strength) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 13 + i0, 5, 1);
};
} else {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 48 - i0 * strengthHeight - strength - i0, 5, 1);
if (strength) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 47 - i0, 5, 1);
};
};
};
if (scConf.peakHold) {
let linger = Math.floor(upThis.#linger[ch] / strengthDivider) + 1;
if (scConf.invBar) {
if (linger) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 12 + i0 * strengthHeight + linger + i0, 5, 1);
};
} else {
if (linger) {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 49 + 6 * i1, 48 - i0 * strengthHeight - linger - i0, 5, 1);
};
};
};
};
};
if (scConf.invDisp) {
flipBitsInBuffer(upThis.#nmdb, totalWidth, 48, 12, 97, 44);
};
};
// Letter display
if (timeNow < sum.letter.expire) {
switch (upThis.#letterMode) {
case "gs":
case "sc": {
break;
};
default: {
fillBitsInBuffer(upThis.#nmdb, totalWidth, 48, 57, 97, 6, 0);
//fillBitsInBuffer(upThis.#nmdb, totalWidth, 47, 47, 99, 1, 255);
fillBitsInBuffer(upThis.#nmdb, totalWidth, 47, 63, 99, 1, 255);
fillBitsInBuffer(upThis.#nmdb, totalWidth, 47, 47, 1, 16, 255);
fillBitsInBuffer(upThis.#nmdb, totalWidth, 145, 47, 1, 16, 255);
upThis.font56.getStr(sum.letter.text).forEach((e0, i0) => {
let offsetX = (i0 & 15) * 6;
e0.forEach((e1, i1) => {
let pX = (i1 % 5) + offsetX + 49, pY = Math.floor(i1 / 5) + 49 + 7 * (i0 >> 4);
if (e1) {
upThis.#nmdb[pY * totalWidth + pX] = 255;
};
});
});
};
};
};
// EFX and bank?
if (upThis.device.getEffectSink()[upThis.#ch]) {
let cx = 153, cy = 19;
upThis.sysBm.getBm("efxOn")?.render((e, x, y) => {
if (e) {
upThis.#nmdb[cx + x + (y + cy) * totalWidth] = 255;
};
});
};
switch (upThis.device.getChMode(upThis.#ch)) {
case "gs":
case "sc": {
let cc32 = upThis.getChPrimitive(upThis.#ch, 2, true);
if (cc32 > 0 && cc32 < 5) {
let cx = 153;
let cy = 48 - cc32 * 5;
upThis.sysBm.getBm("bankSel")?.render((e, x, y) => {
if (e) {
upThis.#nmdb[cx + x + (y + cy) * totalWidth] = 255;
};
});
};
break;
};
};
} else if (upThis.#bootFrame > 49) {
let bootImage = upThis.bootBm?.getBm("boot_mr");
if (bootImage) {
bootImage.render((e, x, y) => {
upThis.#nmdb[x + 1 + (y + 1) * 160] = e ? 255 : 0;
});
upThis.#bootFrame ++;
};
} else {
upThis.#bootFrame ++;
};
if (upThis.#bootFrame > 99 && upThis.#booted < 2) {
let bootImage = upThis.bootBm?.getBm(`bs_${((upThis.#bootFrame - 50) >> 3) & 7}`);
if (bootImage) {
bootImage.render((e, x, y) => {
if (e) {
upThis.#nmdb[x + 151 + (y + 11) * 160] = upThis.#nmdb[x + 152 + (y + 11) * 160] ? 0 : 255;
};
});
if (upThis.#booted) {
upThis.#bootFrame ++;
};
};
};
// Guide the drawn matrix
upThis.#nmdb.forEach((e, i) => {
if (upThis.#dmdb[i] !== e) {
if (upThis.useBlur) {
let diff = e - upThis.#dmdb[i],
cap = 48;
if (Math.abs(diff) > cap) {
upThis.#dmdb[i] += Math.sign(diff) * cap;
} else {
upThis.#dmdb[i] = e;
};
} else {
upThis.#dmdb[i] = e;
};
};
});
// Do the actual drawing
upThis.#dmdb.forEach((e, i) => {
let pX = i % totalWidth, pY = Math.floor(i / totalWidth);
if (fullRefresh || upThis.#omdb[i] !== e) {
let posX = 4 + 5 * pX, posY = 4 + 5 * pY;
ctx.clearRect(posX, posY, 5, 5);
ctx.fillStyle = backlight.orange;
ctx.fillRect(posX, posY, 5, 5);
if (e <= upThis.#pixelOff) {
ctx.fillStyle = lcdCache.black[3];
} else if (e >= upThis.#pixelLit) {
ctx.fillStyle = lcdCache.black[4];
} else {
ctx.fillStyle = `${lcdPixel.black}${(Math.ceil(e * lcdPixel.range / 255) + lcdPixel.inactive).toString(16)}`
};
ctx.fillRect(posX, posY, 4.5, 4.5);
self.pixelUpdates = (self.pixelUpdates || 0) + 1;
};
});
// Store the historical draws
upThis.#dmdb.forEach((e, i) => {
if (upThis.#omdb[i] !== e) {
upThis.#omdb[i] = e;
};
});
};
};
 
export default Sc8850Display;