UNPKG

museaikit

Version:

A powerful music-focused AI toolkit

245 lines 10.7 kB
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