bouncy/render.js

259 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const readline = require('readline');
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
const modes = ['classic', 'bottom', 'top'];
let mode = 0;
let modePopup = 0;
const modePopupDuration = 2;
process.stdin.on('keypress', (str, key) => {
if (key.name === 'space') {
mode++;
mode = mode % modes.length;
modePopup = modePopupDuration;
}
if (key.sequence === '\x03' || key.sequence === '\x04') process.exit(0); // ctrl+c, ctrl+d
});
process.on('exit', () => {
process.stdout.write('\x1B[?25h\033[0m'); // show cursor
})
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 = '';
// type: 0 = error, 1 = warning, 2 = info
let popups = [];
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;
modePopup -= 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 = [];
switch (mode) {
case 0:
texts.push({
value: ' ' + displaytitle + ' ',
x: Math.round(w / 2 - displaytitle.length / 2 + Math.sin(time) * 5),
y: Math.floor(h / 2) - 1,
color: '\033[7;1m'
});
texts.push({
value: ' ' + displayalbum + ' ',
x: Math.round(w / 2 - displayalbum.length / 2 - Math.sin(time) * 5),
y: Math.floor(h / 2) + 1,
color: '\033[7m'
});
break;
case 1:
texts.push({
value: ' ' + displaytitle + ' '.repeat(w),
x: 0,
y: h - 3,
color: '\033[7;1m'
});
texts.push({
value: ' ' + displayalbum + ' '.repeat(w),
x: 0,
y: h - 2,
color: '\033[7m'
});
break;
case 2:
texts.push({
value: ' ' + displaytitle + ' '.repeat(w),
x: 0,
y: 0,
color: '\033[7;1m'
});
texts.push({
value: ' ' + displayalbum + ' '.repeat(w),
x: 0,
y: 1,
color: '\033[7m'
});
break;
}
let i = 0;
for (let popup of popups) {
popup.timer -= dt;
if (popup.timer < 0) {
popups.splice(i, 1);
} else {
let symbol = ['⚠️', '⚠️', ''][popup.type];
let color = ['\033[41;37m', '\033[43;37m', '\033[44;37m'][popup.type];
let text = ` ${symbol} ${popup.text} `;
texts.push({
value: ' '.repeat(text.length) + text,
x: -text.length * 2 + Math.round((text.length + 3) * outCirc(Math.min(popup.timer, 1))),
y: 3 + i,
color: color
});
i++;
}
}
if (modePopup > 0) {
texts.push({
value: modes[mode],
x: Math.floor(w - ((modes[mode].length + 2) * outCirc(Math.min(modePopup, 1)))),
y: 0,
color: '\033[40;37m'
});
}
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) {popups.push({text: content, timer: errorDuration, type: 0});}
function warning(content) {popups.push({text: content, timer: errorDuration, type: 1});}
function info(content) {popups.push({text: content, timer: errorDuration, type: 2});}
module.exports.render = render;
module.exports.refreshrate = refreshrate;
module.exports.error = error;
module.exports.warning = warning;
module.exports.info = info;