451 lines
12 KiB
JavaScript
451 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
var WaveformDrawer = function() {
|
|
|
|
};
|
|
|
|
WaveformDrawer.prototype.init = function(container, config) {
|
|
|
|
makePublisher(this);
|
|
|
|
this.config = config;
|
|
this.container = container;
|
|
this.channels = []; //array of canvases, contexts, 1 for each channel displayed.
|
|
|
|
var theme = this.config.getUITheme();
|
|
|
|
if (this.loaderStates[theme] !== undefined) {
|
|
this.loaderStates = this.loaderStates[theme];
|
|
}
|
|
else {
|
|
this.loaderStates = this.loaderStates["default"];
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.loaderStates = {
|
|
"bootstrap": {
|
|
"downloading": "progress progress-warning",
|
|
"decoding": "progress progress-success progress-striped active",
|
|
"loader": "bar"
|
|
},
|
|
|
|
"jQueryUI": {
|
|
"downloading": "ui-progressbar ui-widget ui-widget-content ui-corner-all",
|
|
"decoding": "ui-progressbar ui-widget ui-widget-content ui-corner-all",
|
|
"loader": "ui-progressbar-value ui-widget-header ui-corner-left"
|
|
},
|
|
|
|
"default": {
|
|
"downloading": "progress",
|
|
"decoding": "decoding",
|
|
"loader": "bar"
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.getPeaks = function(buffer, cues) {
|
|
|
|
// Frames per pixel
|
|
var res = this.config.getResolution(),
|
|
peaks = [],
|
|
i, c, p, l,
|
|
chanLength = cues.cueout - cues.cuein,
|
|
pixels = Math.ceil(chanLength / res),
|
|
numChan = buffer.numberOfChannels,
|
|
weight = 1 / (numChan),
|
|
makeMono = this.config.isDisplayMono(),
|
|
chan,
|
|
start,
|
|
end,
|
|
vals,
|
|
max,
|
|
min,
|
|
maxPeak = -Infinity; //used to scale the waveform on the canvas.
|
|
|
|
for (i = 0; i < pixels; i++) {
|
|
|
|
peaks[i] = [];
|
|
|
|
for (c = 0; c < numChan; c++) {
|
|
|
|
chan = buffer.getChannelData(c);
|
|
chan = chan.subarray(cues.cuein, cues.cueout);
|
|
|
|
start = i * res;
|
|
end = (i + 1) * res > chanLength ? chanLength : (i + 1) * res;
|
|
vals = chan.subarray(start, end);
|
|
max = -Infinity;
|
|
min = Infinity;
|
|
|
|
for (p = 0, l = vals.length; p < l; p++) {
|
|
if (vals[p] > max){
|
|
max = vals[p];
|
|
}
|
|
if (vals[p] < min){
|
|
min = vals[p];
|
|
}
|
|
}
|
|
peaks[i].push({max:max, min:min});
|
|
maxPeak = Math.max.apply(Math, [maxPeak, Math.abs(max), Math.abs(min)]);
|
|
}
|
|
|
|
if (makeMono) {
|
|
max = min = 0;
|
|
|
|
for (c = 0 ; c < numChan; c++) {
|
|
max = max + weight * peaks[i][c].max;
|
|
min = min + weight * peaks[i][c].min;
|
|
}
|
|
|
|
peaks[i] = []; //need to clear out old stuff (maybe we should keep it for toggling views?).
|
|
peaks[i].push({max:max, min:min});
|
|
}
|
|
}
|
|
|
|
this.maxPeak = maxPeak;
|
|
this.peaks = peaks;
|
|
};
|
|
|
|
WaveformDrawer.prototype.setTimeShift = function(pixels) {
|
|
var i, len;
|
|
|
|
for (i = 0, len = this.channels.length; i < len; i++) {
|
|
this.channels[i].div.style.left = pixels+"px";
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.updateLoader = function(percent) {
|
|
this.loader.style.width = percent+"%";
|
|
};
|
|
|
|
WaveformDrawer.prototype.setLoaderState = function(state) {
|
|
this.progressDiv.className = this.loaderStates[state];
|
|
};
|
|
|
|
WaveformDrawer.prototype.drawLoading = function() {
|
|
var div,
|
|
loader;
|
|
|
|
this.height = this.config.getWaveHeight();
|
|
|
|
div = document.createElement("div");
|
|
div.style.height = this.height+"px";
|
|
|
|
loader = document.createElement("div");
|
|
loader.style.height = "10px";
|
|
loader.className = this.loaderStates["loader"];
|
|
|
|
div.appendChild(loader);
|
|
|
|
this.progressDiv = div;
|
|
this.loader = loader;
|
|
|
|
this.setLoaderState("downloading");
|
|
this.updateLoader(0);
|
|
|
|
this.container.appendChild(div);
|
|
};
|
|
|
|
WaveformDrawer.prototype.drawBuffer = function(buffer, pixelOffset, cues) {
|
|
var canv,
|
|
div,
|
|
i,
|
|
top = 0,
|
|
left = 0,
|
|
makeMono = this.config.isDisplayMono(),
|
|
res = this.config.getResolution(),
|
|
numChan = makeMono? 1 : buffer.numberOfChannels,
|
|
numSamples = cues.cueout - cues.cuein + 1,
|
|
fragment = document.createDocumentFragment(),
|
|
wrapperHeight;
|
|
|
|
this.container.innerHTML = "";
|
|
this.channels = [];
|
|
|
|
//width and height is per waveform canvas.
|
|
this.width = Math.ceil(numSamples / res);
|
|
this.height = this.config.getWaveHeight();
|
|
|
|
for (i = 0; i < numChan; i++) {
|
|
|
|
div = document.createElement("div");
|
|
div.classList.add("channel");
|
|
div.classList.add("channel-"+i);
|
|
div.style.width = this.width+"px";
|
|
div.style.height = this.height+"px";
|
|
div.style.top = top+"px";
|
|
div.style.left = left+"px";
|
|
|
|
canv = document.createElement("canvas");
|
|
canv.setAttribute('width', this.width);
|
|
canv.setAttribute('height', this.height);
|
|
|
|
this.channels.push({
|
|
canvas: canv,
|
|
context: canv.getContext('2d'),
|
|
div: div
|
|
});
|
|
|
|
div.appendChild(canv);
|
|
fragment.appendChild(div);
|
|
|
|
top = top + this.height;
|
|
}
|
|
|
|
wrapperHeight = numChan * this.height;
|
|
this.container.style.height = wrapperHeight+"px";
|
|
this.container.appendChild(fragment);
|
|
|
|
|
|
this.getPeaks(buffer, cues);
|
|
this.updateEditor();
|
|
|
|
this.setTimeShift(pixelOffset);
|
|
};
|
|
|
|
WaveformDrawer.prototype.drawFrame = function(chanNum, index, peaks, maxPeak, cursorPos, pixelOffset) {
|
|
var x, y, w, h, max, min,
|
|
h2 = this.height / 2,
|
|
cc = this.channels[chanNum].context,
|
|
colors = this.config.getColorScheme();
|
|
|
|
max = (peaks.max / maxPeak) * h2;
|
|
min = (peaks.min / maxPeak) * h2;
|
|
|
|
w = 1;
|
|
x = index * w;
|
|
y = Math.round(h2 - max);
|
|
h = Math.ceil(max - min);
|
|
|
|
//to prevent blank space when there is basically silence in the track.
|
|
h = h === 0 ? 1 : h;
|
|
|
|
if (cursorPos >= (x + pixelOffset)) {
|
|
cc.fillStyle = colors.progressColor;
|
|
}
|
|
else {
|
|
cc.fillStyle = colors.waveColor;
|
|
}
|
|
|
|
cc.fillRect(x, y, w, h);
|
|
};
|
|
|
|
/*
|
|
start, end are optional parameters to only redraw part of the canvas.
|
|
*/
|
|
WaveformDrawer.prototype.draw = function(cursorPos, pixelOffset, start, end) {
|
|
var that = this,
|
|
peaks = this.peaks,
|
|
i = (start) ? start - pixelOffset : 0,
|
|
len = (end) ? end - pixelOffset + 1 : peaks.length;
|
|
|
|
if (i < 0 && len < 0) {
|
|
return;
|
|
}
|
|
|
|
if (i < 0) {
|
|
i = 0;
|
|
}
|
|
|
|
if (len > peaks.length) {
|
|
len = peaks.length;
|
|
}
|
|
|
|
this.clear(i, len);
|
|
|
|
for (; i < len; i++) {
|
|
|
|
peaks[i].forEach(function(peak, chanNum) {
|
|
that.drawFrame(chanNum, i, peak, that.maxPeak, cursorPos, pixelOffset);
|
|
});
|
|
}
|
|
};
|
|
|
|
/*
|
|
If start/end are set clear only part of the canvas.
|
|
*/
|
|
WaveformDrawer.prototype.clear = function(start, end) {
|
|
var i, len,
|
|
width = end - start;
|
|
|
|
for (i = 0, len = this.channels.length; i < len; i++) {
|
|
this.channels[i].context.clearRect(start, 0, width, this.height);
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.updateEditor = function(cursorPos, pixelOffset, start, end, highlighted, selected) {
|
|
var i, len,
|
|
fragment = document.createDocumentFragment();
|
|
|
|
this.container.innerHTML = "";
|
|
|
|
this.draw(cursorPos, pixelOffset, start, end);
|
|
|
|
if (highlighted === true && selected !== undefined) {
|
|
var border = (selected.end - selected.start === 0) ? true : false;
|
|
this.drawHighlight(selected.start, selected.end, border);
|
|
}
|
|
|
|
for (i = 0, len = this.channels.length; i < len; i++) {
|
|
fragment.appendChild(this.channels[i].div);
|
|
}
|
|
|
|
this.container.appendChild(fragment);
|
|
};
|
|
|
|
/*
|
|
start, end in pixels.
|
|
*/
|
|
WaveformDrawer.prototype.drawHighlight = function(start, end, isBorder) {
|
|
var i, len,
|
|
colors = this.config.getColorScheme(),
|
|
fillStyle,
|
|
ctx,
|
|
width = end - start + 1;
|
|
|
|
fillStyle = (isBorder) ? colors.selectBorderColor : colors.selectBackgroundColor;
|
|
|
|
for (i = 0, len = this.channels.length; i < len; i++) {
|
|
ctx = this.channels[i].context;
|
|
ctx.fillStyle = fillStyle;
|
|
ctx.fillRect(start, 0, width, this.height);
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.sCurveFadeIn = function sCurveFadeIn(ctx, width) {
|
|
return Curves.createSCurveBuffer(width, (Math.PI/2));
|
|
};
|
|
|
|
WaveformDrawer.prototype.sCurveFadeOut = function sCurveFadeOut(ctx, width) {
|
|
return Curves.createSCurveBuffer(width, -(Math.PI/2));
|
|
};
|
|
|
|
WaveformDrawer.prototype.logarithmicFadeIn = function logarithmicFadeIn(ctx, width) {
|
|
return Curves.createLogarithmicBuffer(width, 10, 1);
|
|
};
|
|
|
|
WaveformDrawer.prototype.logarithmicFadeOut = function logarithmicFadeOut(ctx, width) {
|
|
return Curves.createLogarithmicBuffer(width, 10, -1);
|
|
};
|
|
|
|
WaveformDrawer.prototype.exponentialFadeIn = function exponentialFadeIn(ctx, width) {
|
|
return Curves.createExponentialBuffer(width, 1);
|
|
};
|
|
|
|
WaveformDrawer.prototype.exponentialFadeOut = function exponentialFadeOut(ctx, width) {
|
|
return Curves.createExponentialBuffer(width, -1);
|
|
};
|
|
|
|
WaveformDrawer.prototype.linearFadeIn = function linearFadeIn(ctx, width) {
|
|
return Curves.createLinearBuffer(width, 1);
|
|
};
|
|
|
|
WaveformDrawer.prototype.linearFadeOut = function linearFadeOut(ctx, width) {
|
|
return Curves.createLinearBuffer(width, -1);
|
|
};
|
|
|
|
WaveformDrawer.prototype.drawFadeCurve = function(ctx, shape, type, width) {
|
|
var method = shape+type,
|
|
fn = this[method],
|
|
colors = this.config.getColorScheme(),
|
|
curve,
|
|
i, len,
|
|
cHeight = this.height,
|
|
y;
|
|
|
|
ctx.strokeStyle = colors.fadeColor;
|
|
|
|
curve = fn.call(this, ctx, width);
|
|
|
|
y = cHeight - curve[0] * cHeight;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y);
|
|
|
|
for (i = 1, len = curve.length; i < len; i++) {
|
|
y = cHeight - curve[i] * cHeight;
|
|
ctx.lineTo(i, y);
|
|
}
|
|
ctx.stroke();
|
|
};
|
|
|
|
WaveformDrawer.prototype.removeFade = function(id) {
|
|
var fadeClass = "playlist-fade-"+id,
|
|
el, els,
|
|
i,len;
|
|
|
|
els = this.container.getElementsByClassName(fadeClass);
|
|
len = els.length;
|
|
|
|
//DOM NodeList is live, use a decrementing counter.
|
|
if (len > 0) {
|
|
for (i = len-1; i >= 0; i--) {
|
|
el = els[i];
|
|
el.parentNode.removeChild(el);
|
|
}
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.drawFade = function(id, type, shape, start, end) {
|
|
var div,
|
|
canv,
|
|
width,
|
|
left,
|
|
fragment = document.createDocumentFragment(),
|
|
i, len,
|
|
dup,
|
|
ctx,
|
|
tmpCtx;
|
|
|
|
if ((end - start) === 0) {
|
|
return;
|
|
}
|
|
|
|
width = ~~(end - start + 1);
|
|
left = start;
|
|
|
|
div = document.createElement("div");
|
|
div.classList.add("playlist-fade");
|
|
div.classList.add("playlist-fade-"+id);
|
|
div.style.width = width+"px";
|
|
div.style.height = this.height+"px";
|
|
div.style.top = 0;
|
|
div.style.left = left+"px";
|
|
|
|
canv = document.createElement("canvas");
|
|
canv.setAttribute('width', width);
|
|
canv.setAttribute('height', this.height);
|
|
ctx = canv.getContext('2d');
|
|
|
|
this.drawFadeCurve(ctx, shape, type, width);
|
|
|
|
div.appendChild(canv);
|
|
fragment.appendChild(div);
|
|
|
|
for (i = 0, len = this.channels.length; i < len; i++) {
|
|
dup = fragment.cloneNode(true);
|
|
tmpCtx = dup.querySelector('canvas').getContext('2d');
|
|
tmpCtx.drawImage(canv, 0, 0);
|
|
|
|
this.channels[i].div.appendChild(dup);
|
|
}
|
|
};
|
|
|
|
WaveformDrawer.prototype.drawFades = function(fades) {
|
|
var id,
|
|
fade,
|
|
startPix,
|
|
endPix,
|
|
SR = this.config.getSampleRate(),
|
|
res = this.config.getResolution();
|
|
|
|
for (id in fades) {
|
|
fade = fades[id];
|
|
|
|
if (fades.hasOwnProperty(id)) {
|
|
startPix = fade.start * SR / res;
|
|
endPix = fade.end * SR / res;
|
|
this.drawFade(id, fade.type, fade.shape, startPix, endPix);
|
|
}
|
|
}
|
|
};
|
|
|