asigalov61's picture
Update javascript/app.js
fb8603d
function gradioApp() {
const elems = document.getElementsByTagName('gradio-app');
const gradioShadowRoot = elems.length === 0 ? null : elems[0].shadowRoot;
return !!gradioShadowRoot ? gradioShadowRoot : document;
}
const uiUpdateCallbacks = [];
const msgReceiveCallbacks = [];
function onUiUpdate(callback) {
uiUpdateCallbacks.push(callback);
}
function onMsgReceive(callback) {
msgReceiveCallbacks.push(callback);
}
function runCallback(x, m) {
try {
x(m);
} catch (e) {
(console.error || console.log).call(console, e.message, e);
}
}
function executeCallbacks(queue, m) {
queue.forEach(function (x) {
runCallback(x, m);
});
}
document.addEventListener('DOMContentLoaded', function () {
const mutationObserver = new MutationObserver(function (m) {
executeCallbacks(uiUpdateCallbacks, m);
});
mutationObserver.observe(gradioApp(), { childList: true, subtree: true });
});
(function () {
let mse_receiver_inited = null;
onUiUpdate(() => {
const app = gradioApp();
const msg_receiver = app.querySelector('#msg_receiver');
if (!!msg_receiver && mse_receiver_inited !== msg_receiver) {
const mutationObserver = new MutationObserver(function (ms) {
ms.forEach((m) => {
m.addedNodes.forEach((node) => {
if (node.nodeName === 'P') {
const obj = JSON.parse(node.innerText);
if (obj instanceof Array) {
obj.forEach((o) => {
executeCallbacks(msgReceiveCallbacks, o);
});
} else {
executeCallbacks(msgReceiveCallbacks, obj);
}
}
});
});
});
mutationObserver.observe(msg_receiver, {
childList: true,
subtree: true,
characterData: true,
});
console.log('receiver init');
mse_receiver_inited = msg_receiver;
}
});
})();
function HSVtoRGB(h, s, v) {
let r, g, b, i, f, p, q, t;
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
(r = v), (g = t), (b = p);
break;
case 1:
(r = q), (g = v), (b = p);
break;
case 2:
(r = p), (g = v), (b = t);
break;
case 3:
(r = p), (g = q), (b = v);
break;
case 4:
(r = t), (g = p), (b = v);
break;
case 5:
(r = v), (g = p), (b = q);
break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
}
class MidiVisualizer extends HTMLElement {
constructor() {
super();
this.midiEvents = [];
this.activeNotes = [];
this.midiTimes = [];
this.wrapper = null;
this.svg = null;
this.timeLine = null;
this.config = {
noteHeight: 4,
beatWidth: 32,
};
this.tickPreBeat = 500;
this.svgWidth = 0;
this.playTime = 0;
this.playTimeMs = 0;
this.colorMap = new Map();
this.playing = false;
this.timer = null;
this.init();
}
init() {
this.innerHTML = '';
const shadow = this.attachShadow({ mode: 'open' });
const style = document.createElement('style');
const wrapper = document.createElement('div');
style.textContent = '.note.active {stroke: black;stroke-width: 0.75;stroke-opacity: 0.75;}';
wrapper.style.overflowX = 'scroll';
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.height = `${this.config.noteHeight * 128}px`;
svg.style.width = `${this.svgWidth}px`;
const timeLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
timeLine.style.stroke = 'green';
timeLine.style.strokeWidth = 2;
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(svg);
svg.appendChild(timeLine);
this.wrapper = wrapper;
this.svg = svg;
this.timeLine = timeLine;
this.setPlayTime(0);
}
clearMidiEvents() {
this.pause();
this.midiEvents = [];
this.activeNotes = [];
this.midiTimes = [];
this.colorMap.clear();
this.setPlayTime(0);
this.playTimeMs = 0;
this.svgWidth = 0;
this.svg.innerHTML = '';
this.svg.style.width = `${this.svgWidth}px`;
this.svg.appendChild(this.timeLine);
}
appendMidiEvent(midiEvent) {
if (midiEvent instanceof Array && midiEvent.length > 0) {
if (midiEvent[0] === 'note') {
const t = midiEvent[1];
const duration = midiEvent[2];
const channel = midiEvent[3];
const pitch = midiEvent[4];
const velocity = midiEvent[5];
const x = (t / this.tickPreBeat) * this.config.beatWidth;
const y = (127 - pitch) * this.config.noteHeight;
const w = (duration / this.tickPreBeat) * this.config.beatWidth;
const h = this.config.noteHeight;
this.svgWidth = Math.ceil(Math.max(x + w, this.svgWidth));
const color = this.getColor(0, channel);
const opacity = (Math.min(1, velocity / 127 + 0.1)).toFixed(2);
const rect = this.drawNote(x, y, w, h, `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`);
midiEvent.push(rect);
this.setPlayTime(t);
this.wrapper.scrollTo(this.svgWidth - this.wrapper.offsetWidth, 0);
}
this.midiEvents.push(midiEvent);
this.svg.style.width = `${this.svgWidth}px`;
}
}
getColor(track, channel) {
const colors = [
[255, 0, 0], // Red
[255, 255, 0], // Yellow
[0, 128, 0], // Green
[0, 255, 255], // Cyan
[0, 0, 255], // Blue
[255, 192, 203], // Pink
[255, 165, 0], // Orange
[128, 0, 128], // Purple
[128, 128, 128], // Gray
[255, 255, 255], // White
[255, 215, 0], // Gold
[192, 192, 192], // Silver
];
// Calculate an index based on the track and channel
const index = (track + channel) % colors.length;
// Get the RGB values from the colors array
const [r, g, b] = colors[index];
// Return the RGB color in the format "rgb(r, g, b)"
return { r, g, b };
}
drawNote(x, y, w, h, fill) {
if (!this.svg) {
return null;
}
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.classList.add('note');
rect.setAttribute('fill', fill);
// Round values to the nearest integer to avoid partially filled pixels.
rect.setAttribute('x', `${Math.round(x)}`);
rect.setAttribute('y', `${Math.round(y)}`);
rect.setAttribute('width', `${Math.round(w)}`);
rect.setAttribute('height', `${Math.round(h)}`);
this.svg.appendChild(rect);
return rect;
}
finishAppendMidiEvent() {
this.pause();
const midiEvents = this.midiEvents.sort((a, b) => a[1] - b[1]);
let tempo = (60 / 120) * 10 ** 3;
let ms = 0;
let lastT = 0;
this.midiTimes.push({ ms: ms, t: 0, tempo: tempo });
midiEvents.forEach((midiEvent) => {
const t = midiEvent[1];
ms += ((t - lastT) / this.tickPreBeat) * tempo;
if (midiEvent[0] === 'set_tempo') {
tempo = midiEvent[2];
this.midiTimes.push({ ms: ms, t: t, tempo: tempo });
}
lastT = t;
});
}
setPlayTime(t) {
this.playTime = t;
const x = Math.round((t / this.tickPreBeat) * this.config.beatWidth);
this.timeLine.setAttribute('x1', `${x}`);
this.timeLine.setAttribute('y1', '0');
this.timeLine.setAttribute('x2', `${x}`);
this.timeLine.setAttribute('y2', `${this.config.noteHeight * 128}`);
this.wrapper.scrollTo(Math.max(0, x - this.wrapper.offsetWidth / 2), 0);
if (this.playing) {
const activeNotes = [];
this.removeActiveNotes(this.activeNotes);
this.midiEvents.forEach((midiEvent) => {
if (midiEvent[0] === 'note') {
const time = midiEvent[1];
const duration = midiEvent[2];
const note = midiEvent[midiEvent.length - 1];
if (time <= this.playTime && time + duration >= this.playTime) {
activeNotes.push(note);
}
}
});
this.addActiveNotes(activeNotes);
}
}
setPlayTimeMs(ms) {
this.playTimeMs = ms;
let playTime = 0;
for (let i = 0; i < this.midiTimes.length; i++) {
const midiTime = this.midiTimes[i];
if (midiTime.ms >= ms) {
break;
}
playTime = midiTime.t + ((ms - midiTime.ms) * this.tickPreBeat) / midiTime.tempo;
}
this.setPlayTime(playTime);
}
addActiveNotes(notes) {
notes.forEach((note) => {
this.activeNotes.push(note);
note.classList.add('active');
});
}
removeActiveNotes(notes) {
notes.forEach((note) => {
const idx = this.activeNotes.indexOf(note);
if (idx > -1) this.activeNotes.splice(idx, 1);
note.classList.remove('active');
});
}
play() {
this.playing = true;
this.timer = setInterval(() => {
this.setPlayTimeMs(this.playTimeMs + 10);
}, 10);
}
pause() {
if (!!this.timer) clearInterval(this.timer);
this.removeActiveNotes(this.activeNotes);
this.timer = null;
this.playing = false;
}
bindAudioPlayer(audio) {
this.pause();
audio.addEventListener('play', (event) => {
this.play();
});
audio.addEventListener('pause', (event) => {
this.pause();
});
audio.addEventListener('timeupdate', (event) => {
this.setPlayTimeMs(event.target.currentTime * 10 ** 3);
});
}
}
customElements.define('midi-visualizer', MidiVisualizer);
(function () {
let midi_visualizer_container_inited = null;
let midi_audio_inited = null;
const midi_visualizer = document.createElement('midi-visualizer');
if (window.innerWidth < 300) {
midi_visualizer.config.noteHeight = 1;
midi_visualizer.config.beatWidth = 8;
} else if (window.innerWidth < 600) {
midi_visualizer.config.noteHeight = 2;
midi_visualizer.config.beatWidth = 16;
} else if (window.innerWidth < 1280) {
midi_visualizer.config.noteHeight = 4;
midi_visualizer.config.beatWidth = 32;
} else {
midi_visualizer.config.noteHeight = 4;
midi_visualizer.config.beatWidth = 64;
}
midi_visualizer.svg.style.height = `${midi_visualizer.config.noteHeight * 128}px`; // Reload svg height
onUiUpdate((m) => {
const app = gradioApp();
const midi_visualizer_container = app.querySelector('#midi_visualizer_container');
if (!!midi_visualizer_container && midi_visualizer_container_inited !== midi_visualizer_container) {
midi_visualizer_container.appendChild(midi_visualizer);
midi_visualizer_container_inited = midi_visualizer_container;
}
const midi_audio = app.querySelector('#midi_audio > audio');
if (!!midi_audio && midi_audio_inited !== midi_audio) {
midi_visualizer.bindAudioPlayer(midi_audio);
midi_audio_inited = midi_audio;
}
});
function createProgressBar(progressbarContainer) {
const parentProgressbar = progressbarContainer.parentNode;
const divProgress = document.createElement('div');
divProgress.className = 'progressDiv';
const rect = progressbarContainer.getBoundingClientRect();
divProgress.style.width = rect.width + 'px';
divProgress.style.background = '#b4c0cc';
divProgress.style.borderRadius = '8px';
const divInner = document.createElement('div');
divInner.className = 'progress';
divInner.style.color = 'white';
divInner.style.background = '#0060df';
divInner.style.textAlign = 'right';
divInner.style.fontWeight = 'bold';
divInner.style.borderRadius = '8px';
divInner.style.height = '20px';
divInner.style.lineHeight = '20px';
divInner.style.paddingRight = '8px';
divInner.style.width = '0%';
divProgress.appendChild(divInner);
parentProgressbar.insertBefore(divProgress, progressbarContainer);
}
function removeProgressBar(progressbarContainer) {
const parentProgressbar = progressbarContainer.parentNode;
const divProgress = parentProgressbar.querySelector('.progressDiv');
parentProgressbar.removeChild(divProgress);
}
function setProgressBar(progressbarContainer, progress, total) {
const parentProgressbar = progressbarContainer.parentNode;
const divProgress = parentProgressbar.querySelector('.progressDiv');
const divInner = parentProgressbar.querySelector('.progress');
if (total === 0) total = 1;
divInner.style.width = `${(progress / total) * 100}%`;
divInner.textContent = `${progress}/${total}`;
}
onMsgReceive((msg) => {
switch (msg.name) {
case 'visualizer_clear':
midi_visualizer.clearMidiEvents();
createProgressBar(midi_visualizer_container_inited);
break;
case 'visualizer_append':
midi_visualizer.appendMidiEvent(msg.data);
break;
case 'progress':
const progress = msg.data[0];
const total = msg.data[1];
setProgressBar(midi_visualizer_container_inited, progress, total);
break;
case 'visualizer_end':
midi_visualizer.finishAppendMidiEvent();
midi_visualizer.setPlayTime(0);
removeProgressBar(midi_visualizer_container_inited);
break;
default:
}
});
})();