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:05:27 +00:00
|
|
|
|
process.stdout.write('\x1B[?25l\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;
|