bouncy/render.js

202 lines
5.7 KiB
JavaScript
Raw Normal View History

2021-09-02 23:21:16 +00:00
const readline = require('readline');
const plas = [' ', '.', '*', '/', '0'];
const progressChars = ['▏', '▎', '▍', '▌', '▋', '▊', '▉'];
//const progressChars = ['0', '1', '2', '3', '4', '5', '6', '7'];
const refreshrate = 50;
const errorDuration = 4;
function outCirc(t) {return Math.sqrt(-t * t + 2 * t)}
function plasma(x, y, time, k) {
let v = 0;
let cx = x * k - k / 2;
let cy = y * k - k / 2;
v += Math.sin(cx + time);
v += Math.sin((cy + time) / 2);
v += Math.sin((cx + cy + time) / 2);
cx = cx + k / 2 * Math.sin(time / 3);
cy = cy + k / 2 * Math.cos(time / 2);
v += Math.sin(Math.sqrt(cx * cx + cy * cy + 1) + time);
v /= 2;
return Math.sin(Math.PI * v) * 0.5 + 0.5;
}
let displaytitle = '';
let displayalbum = '';
let errorText = '';
let errorTimer = 0;
let errorType = 0; // 0 = error, 1 = warning, 2 = info
function transform(old, n) {
if (old.length < n.length) {
// old += ' '.repeat(n.length - old.length);
old += ' ';
} else if (old.length > n.length) {
old = old.slice(0, old.length - 1);
}
let changeable = [];
for (let i = 0; i < old.length; i++) {
if (old[i] !== n[i] && n[i] && old[i]) changeable.push(i);
}
let rand = Math.floor(Math.random() * changeable.length);
let i = changeable[rand];
if (n[i]) {
return old.slice(0, i) + n[i] + old.slice(i + 1);
}
return old;
}
let time = 0;
function render(artist, album, title, songStart, songEnd, pauseSpot, paused) {
let dt = refreshrate / 1000
time += dt;
errorTimer -= dt;
let w = process.stdout.columns;
let h = process.stdout.rows;
let newtitle = `${artist.toLowerCase()} - ${title.toLowerCase()}`;
if (newtitle !== displaytitle) {
displaytitle = transform(displaytitle, newtitle);
}
let newalbum = `album / ${album.toLowerCase()}`;
if (newalbum !== displayalbum) {
displayalbum = transform(displayalbum, newalbum);
}
// leaving the coordinates as non-integer values is very undefined behavior
let texts = [
{
value: ' ' + displaytitle + ' ',
x: Math.round(w / 2 - displaytitle.length / 2 + Math.sin(time) * 5),
y: Math.floor(h / 2) - 1,
color: '\033[7;1m'
},
{
value: ' ' + displayalbum + ' ',
x: Math.round(w / 2 - displayalbum.length / 2 - Math.sin(time) * 5),
y: Math.floor(h / 2) + 1,
color: '\033[7m'
}
];
if (errorText && errorTimer > 0) {
let symbol = '⚠️';
if (errorType === 2) symbol = '';
let color = '\033[41;37m';
if (errorType === 1) color = '\033[43;37m';
if (errorType === 2) color = '\033[44;37m';
texts.push({
value: ` ${symbol} ${errorText} `,
x: 4,
y: Math.round(3 * outCirc(Math.min(errorTimer, 1))),
color: color
});
}
let reset = '\033[0m';
let now = paused ? pauseSpot : Date.now();
let timeCurrent = now - songStart;
let timeAll = songEnd - songStart;
let timerCurrent = String(Math.floor(timeCurrent / 60000)).padStart(2, '0') + ':' + String(Math.floor((timeCurrent % 60000) / 1000)).padStart(2, '0');
let timerAll = String(Math.floor(timeAll / 60000)).padStart(2, '0') + ':' + String(Math.floor((timeAll % 60000) / 1000)).padStart(2, '0');
timerString = timerCurrent + ' / ' + timerAll;
let progress = (now - songStart) / (songEnd - songStart);
for (let y = 0; y < h; y++) {
let renderingtext = false;
let renderingtextLast = false;
readline.cursorTo(process.stdout, 0, y);
process.stdout.write('\x1B[?25l\033[0m'); // hide cursor, reset color
for (let x = 0; x < w; x++) {
let sum = '';
let isText = false;
let text;
for (let t of texts) {
if (y === t.y && x >= t.x && x < t.x + t.value.length) {
isText = true;
text = t;
}
}
renderingtext = isText;
if (renderingtext && !renderingtextLast) {
sum += text.color;
} else if (!renderingtext && renderingtextLast) {
sum += reset;
}
if (text) {
sum += text.value[x - text.x] || ' ';
} else {
if (y === h - 1) {
let blockProgress = (progress % (1/w)) / (1/w);
let blocks = progress * w;
let inProgressArea = (x > Math.floor(blocks) - 0.5) && (x < Math.ceil(blocks) - 0.5);
let pChar = progressChars[Math.floor(blockProgress * progressChars.length)];
let color = (x / (w - 1) > (Math.floor(progress * w) / w)) ? '\033[40;37m' : '\033[47;30m';
let pause = (paused && x === (w - 2)) ? '⏸' : null;
sum += color + (pause || (timerString[x - 1] === ' ' ? null : timerString[x - 1]) || (inProgressArea ? pChar : ' ')) + '\033[0m';
} else if (x === 0 || x === w - 1 || y === 0) {
sum += '\033[40m \033[0m';
} else {
let gate = 2.5
let l1 = plasma(x, y * 2, time, 4 / Math.min(w, h));
let l2 = Math.max(plasma(x, y * 2, time * 0.7, 3 / Math.min(w, h)) * gate - (gate - 1), 0);
sum += ((l2 > (1 / plas.length)) ? '\x1B[90m' + plas[Math.floor(l2 * plas.length)] : '\033[30m' + plas[Math.floor(l1 * plas.length)]) + '\033[0m';
//sum += '\033[30m' + plas[Math.floor(plasma(x, y * 2, time, 4 / Math.min(w, h)) * plas.length)] + '\033[0m';
}
}
renderingtextLast = renderingtext;
process.stdout.write(sum);
}
}
}
function error(content) {
errorText = content;
errorTimer = errorDuration;
errorType = 0;
}
function warning(content) {
errorText = content;
errorTimer = errorDuration;
errorType = 1;
}
function info(content) {
errorText = content;
errorTimer = errorDuration;
errorType = 2;
}
module.exports.render = render;
module.exports.refreshrate = refreshrate;
module.exports.error = error;
module.exports.warning = warning;
module.exports.info = info;