bouncy/render.js

259 lines
7.2 KiB
JavaScript
Raw Normal View History

2021-09-02 23:21:16 +00:00
const readline = require('readline');
2021-09-03 03:33:54 +00:00
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
});
2021-09-03 04:04:28 +00:00
process.on('exit', () => {
2021-09-03 04:07:43 +00:00
process.stdout.write('\x1B[?25h\033[0m'); // show cursor
2021-09-03 04:04:28 +00:00
})
2021-09-03 03:33:54 +00:00
2021-09-02 23:21:16 +00:00
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 = '';
2021-09-03 03:59:20 +00:00
// type: 0 = error, 1 = warning, 2 = info
2021-09-03 04:00:03 +00:00
let popups = [];
2021-09-02 23:21:16 +00:00
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;
2021-09-03 03:33:54 +00:00
modePopup -= dt;
2021-09-02 23:21:16 +00:00
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
2021-09-03 03:33:54 +00:00
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;
}
2021-09-02 23:21:16 +00:00
2021-09-03 03:59:20 +00:00
let i = 0;
2021-09-03 04:00:03 +00:00
for (let popup of popups) {
popup.timer -= dt;
if (popup.timer < 0) {
popups.splice(i, 1);
2021-09-03 03:59:20 +00:00
} else {
2021-09-03 04:00:03 +00:00
let symbol = ['⚠️', '⚠️', ''][popup.type];
let color = ['\033[41;37m', '\033[43;37m', '\033[44;37m'][popup.type];
let text = ` ${symbol} ${popup.text} `;
2021-09-02 23:21:16 +00:00
2021-09-03 03:59:20 +00:00
texts.push({
value: ' '.repeat(text.length) + text,
2021-09-03 04:00:03 +00:00
x: -text.length * 2 + Math.round((text.length + 3) * outCirc(Math.min(popup.timer, 1))),
2021-09-03 03:59:20 +00:00
y: 3 + i,
color: color
});
2021-09-02 23:21:16 +00:00
2021-09-03 03:59:20 +00:00
i++;
}
2021-09-02 23:21:16 +00:00
}
2021-09-03 03:33:54 +00:00
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'
});
}
2021-09-02 23:21:16 +00:00
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';
2021-09-03 03:33:54 +00:00
2021-09-02 23:21:16 +00:00
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);
}
}
}
2021-09-03 04:00:03 +00:00
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});}
2021-09-02 23:21:16 +00:00
module.exports.render = render;
module.exports.refreshrate = refreshrate;
module.exports.error = error;
module.exports.warning = warning;
module.exports.info = info;