museaikit
Version:
A powerful music-focused AI toolkit
245 lines • 10.7 kB
JavaScript
import { BaseSVGVisualizer } from './base_svg_visualizer';
import { MIN_NOTE_LENGTH } from '../constants';
export class WaterfallSVGVisualizer extends BaseSVGVisualizer {
NOTES_PER_OCTAVE = 12;
WHITE_NOTES_PER_OCTAVE = 7;
LOW_C = 24;
firstDrawnOctave = 0;
lastDrawnOctave = 6;
svgPiano;
constructor(sequence, parentElement, config = {}) {
super(sequence, config);
if (!(parentElement instanceof HTMLDivElement)) {
throw new Error('This visualizer requires a <div> element to display the visualization');
}
this.config.whiteNoteWidth = config.whiteNoteWidth || 20;
this.config.blackNoteWidth =
config.blackNoteWidth || this.config.whiteNoteWidth * 2 / 3;
this.config.whiteNoteHeight = config.whiteNoteHeight || 70;
this.config.blackNoteHeight = config.blackNoteHeight || (2 * 70 / 3);
this.config.showOnlyOctavesUsed = config.showOnlyOctavesUsed;
this.setupDOM(parentElement);
const size = this.getSize();
this.width = size.width;
this.height = size.height;
this.svg.style.width = `${this.width}px`;
this.svg.style.height = `${this.height}px`;
this.svgPiano.style.width = `${this.width}px`;
this.svgPiano.style.height = `${this.config.whiteNoteHeight}px`;
this.parentElement.style.width =
`${this.width + this.config.whiteNoteWidth}px`;
this.parentElement.scrollTop = this.parentElement.scrollHeight;
this.clear();
this.drawPiano();
this.draw();
}
setupDOM(container) {
this.parentElement = document.createElement('div');
this.parentElement.classList.add('waterfall-notes-container');
const height = Math.max(container.getBoundingClientRect().height, 200);
this.parentElement.style.paddingTop =
`${height - this.config.whiteNoteHeight}px`;
this.parentElement.style.height =
`${height - this.config.whiteNoteHeight}px`;
this.parentElement.style.boxSizing = 'border-box';
this.parentElement.style.overflowX = 'hidden';
this.parentElement.style.overflowY = 'auto';
this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.svgPiano =
document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.svg.classList.add('waterfall-notes');
this.svgPiano.classList.add('waterfall-piano');
this.parentElement.appendChild(this.svg);
container.innerHTML = '';
container.appendChild(this.parentElement);
container.appendChild(this.svgPiano);
}
redraw(activeNote, scrollIntoView) {
if (!this.drawn) {
this.draw();
}
if (!activeNote) {
return null;
}
this.clearActiveNotes();
this.parentElement.style.paddingTop = this.parentElement.style.height;
for (let i = 0; i < this.noteSequence.notes.length; i++) {
const note = this.noteSequence.notes[i];
const isActive = activeNote && this.isPaintingActiveNote(note, activeNote);
if (!isActive) {
continue;
}
const el = this.svg.querySelector(`rect[data-index="${i}"]`);
this.fillActiveRect(el, note);
const key = this.svgPiano.querySelector(`rect[data-pitch="${note.pitch}"]`);
this.fillActiveRect(key, note);
if (note === activeNote) {
const y = parseFloat(el.getAttribute('y'));
const height = parseFloat(el.getAttribute('height'));
if (y < (this.parentElement.scrollTop - height)) {
this.parentElement.scrollTop = y + height;
}
return y;
}
}
return null;
}
getSize() {
this.updateMinMaxPitches(true);
let whiteNotesDrawn = 52;
if (this.config.showOnlyOctavesUsed) {
let foundFirst = false, foundLast = false;
for (let i = 1; i < 7; i++) {
const c = this.LOW_C + this.NOTES_PER_OCTAVE * i;
if (!foundFirst && c > this.config.minPitch) {
this.firstDrawnOctave = i - 1;
foundFirst = true;
}
if (!foundLast && c > this.config.maxPitch) {
this.lastDrawnOctave = i - 1;
foundLast = true;
}
}
whiteNotesDrawn = (this.lastDrawnOctave - this.firstDrawnOctave + 1) *
this.WHITE_NOTES_PER_OCTAVE;
}
const width = whiteNotesDrawn * this.config.whiteNoteWidth;
const endTime = this.noteSequence.totalTime;
if (!endTime) {
throw new Error('The sequence you are using with the visualizer does not have a ' +
'totalQuantizedSteps or totalTime ' +
'field set, so the visualizer can\'t be horizontally ' +
'sized correctly.');
}
const height = Math.max(endTime * this.config.pixelsPerTimeStep, MIN_NOTE_LENGTH);
return { width, height };
}
getNotePosition(note, noteIndex) {
const rect = this.svgPiano.querySelector(`rect[data-pitch="${note.pitch}"]`);
if (!rect) {
return null;
}
const len = this.getNoteEndTime(note) - this.getNoteStartTime(note);
const x = Number(rect.getAttribute('x'));
const w = Number(rect.getAttribute('width'));
const h = Math.max(this.config.pixelsPerTimeStep * len - this.config.noteSpacing, MIN_NOTE_LENGTH);
const y = this.height -
(this.getNoteStartTime(note) * this.config.pixelsPerTimeStep) - h;
return { x, y, w, h };
}
drawPiano() {
this.svgPiano.innerHTML = '';
const blackNoteOffset = this.config.whiteNoteWidth - this.config.blackNoteWidth / 2;
const blackNoteIndexes = [1, 3, 6, 8, 10];
let x = 0;
let currentPitch = 0;
if (this.config.showOnlyOctavesUsed) {
currentPitch =
(this.firstDrawnOctave * this.NOTES_PER_OCTAVE) + this.LOW_C;
}
else {
currentPitch = this.LOW_C - 3;
this.drawWhiteKey(currentPitch, x);
this.drawWhiteKey(currentPitch + 2, x + this.config.whiteNoteWidth);
currentPitch += 3;
x = 2 * this.config.whiteNoteWidth;
}
for (let o = this.firstDrawnOctave; o <= this.lastDrawnOctave; o++) {
for (let i = 0; i < this.NOTES_PER_OCTAVE; i++) {
if (blackNoteIndexes.indexOf(i) === -1) {
this.drawWhiteKey(currentPitch, x);
x += this.config.whiteNoteWidth;
}
currentPitch++;
}
}
if (this.config.showOnlyOctavesUsed) {
currentPitch =
(this.firstDrawnOctave * this.NOTES_PER_OCTAVE) + this.LOW_C;
x = 0;
}
else {
this.drawWhiteKey(currentPitch, x);
currentPitch = this.LOW_C - 3;
this.drawBlackKey(currentPitch + 1, blackNoteOffset);
currentPitch += 3;
x = this.config.whiteNoteWidth;
x = 2 * this.config.whiteNoteWidth;
}
if (this.config.showOnlyOctavesUsed) {
x = 0;
}
else {
x = (this.config.showOnlyOctavesUsed ? 0 : 2 * this.config.whiteNoteWidth);
}
currentPitch = (this.config.showOnlyOctavesUsed ?
(this.firstDrawnOctave * this.NOTES_PER_OCTAVE) + this.LOW_C :
this.LOW_C - 3 + 3);
let blackKey_x = 0;
if (this.config.showOnlyOctavesUsed) {
currentPitch = (this.firstDrawnOctave * this.NOTES_PER_OCTAVE) + this.LOW_C;
}
else {
this.drawBlackKey(this.LOW_C - 3 + 1, this.config.whiteNoteWidth - this.config.blackNoteWidth / 2);
currentPitch = this.LOW_C;
blackKey_x = 2 * this.config.whiteNoteWidth;
}
for (let o = this.firstDrawnOctave; o <= this.lastDrawnOctave; o++) {
for (let i = 0; i < this.NOTES_PER_OCTAVE; i++) {
if (blackNoteIndexes.indexOf(i) !== -1) {
this.drawBlackKey(currentPitch, blackKey_x - (this.config.blackNoteWidth / 2));
}
else {
blackKey_x += this.config.whiteNoteWidth;
}
currentPitch++;
}
}
}
drawWhiteKey(index, x) {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.dataset.pitch = String(index);
rect.setAttribute('x', String(x));
rect.setAttribute('y', '0');
rect.setAttribute('width', String(this.config.whiteNoteWidth));
rect.setAttribute('height', String(this.config.whiteNoteHeight));
rect.setAttribute('fill', 'white');
rect.setAttribute('original-fill', 'white');
rect.setAttribute('stroke', 'black');
rect.setAttribute('stroke-width', '3px');
rect.classList.add('white');
this.svgPiano.appendChild(rect);
return rect;
}
drawBlackKey(index, x) {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.dataset.pitch = String(index);
rect.setAttribute('x', String(x));
rect.setAttribute('y', '0');
rect.setAttribute('width', String(this.config.blackNoteWidth));
rect.setAttribute('height', String(this.config.blackNoteHeight));
rect.setAttribute('fill', 'black');
rect.setAttribute('original-fill', 'black');
rect.setAttribute('stroke', 'black');
rect.setAttribute('stroke-width', '3px');
rect.classList.add('black');
this.svgPiano.appendChild(rect);
return rect;
}
clearActiveNotes() {
super.unfillActiveRect(this.svg);
const els = this.svgPiano.querySelectorAll('rect.active');
for (let i = 0; i < els.length; ++i) {
const el = els[i];
el.setAttribute('fill', el.getAttribute('original-fill'));
el.classList.remove('active');
}
}
clear() {
super.clear();
if (this.svgPiano) {
this.svgPiano.innerHTML = '';
}
}
}
//# sourceMappingURL=waterfall_visualizer.js.map