diff options
author | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
---|---|---|
committer | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
commit | 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 (patch) | |
tree | 291bd79ce340e67affa755a8a6b4f6a83cce93ea /site/app/base/waveform.js | |
download | apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.gz apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.bz2 apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.zip |
initial
Diffstat (limited to 'site/app/base/waveform.js')
-rw-r--r-- | site/app/base/waveform.js | 1076 |
1 files changed, 1076 insertions, 0 deletions
diff --git a/site/app/base/waveform.js b/site/app/base/waveform.js new file mode 100644 index 0000000..c6276f1 --- /dev/null +++ b/site/app/base/waveform.js @@ -0,0 +1,1076 @@ +var Waveform = function(options) {
+ var self = this;
+ var elTarget;
+ if (typeof(options.target) == "string") {
+ elTarget = $("#" + options.target);
+ } else {
+ elTarget = options.target;
+ }
+ var elContainerOuter = $("<div />").css({position: "absolute", width: "100%", height: "100%"}).appendTo(elTarget);
+ var elContainer = $("<div />").css({cursor: "text", position: "absolute", width: "100%", bottom: "0px", top: "0px", left: "0px"}).appendTo(elContainerOuter);
+ var elTip = $("<div />").css({position: "fixed", "font-size": "var(--fontSizeLarge)", color: "var(--fgColor1)", "text-shadow": "0px 0px 5px var(--bgColor1)", "z-index": 12}).appendTo(elContainer);
+ var elCanvases = [];
+ var elTimeBar;
+ var elPlayhead;
+ var elCrossfades = [];
+ var elMarkersRunner;
+ var crossFadeRatios = [];
+ var selected = [0, 1, -1];
+ var onSelects = [];
+ var channels;
+ var wavedata = null;
+ var regionStart = 0;
+ var regionEnd = 1;
+ var duration = 1;
+ var stereoSelectRatio = 0.2
+ var dragData = {};
+ this.markers = [];
+ var selectionMarkers = [];
+ var hasContent = false;
+ this.onRegionChange = null;
+
+ this.getRegion = function() {
+ return [regionStart, regionEnd];
+ };
+
+ function absPosToDisplayPos(x) {
+ var pos = (x - regionStart) / (regionEnd - regionStart);
+ return (pos >= 0 && pos <= 1) ? pos : null;
+ }
+
+ function displayPosToAbsPos(x) {
+ return ((regionEnd - regionStart) * x) + regionStart;
+ }
+
+ function getDisplaySelected() {
+ var hasSelection = (selected[0] != selected[1]);
+ var start = absPosToDisplayPos(selected[0]);
+ //if (start == null) start = 0;
+ var end = absPosToDisplayPos(selected[1]);
+ //if (end == null) end = 1;
+
+ if (start && !end) end = 1;
+ if (!start && end) start = 0;
+ if (!start && !end) {
+ if (hasSelection) {
+ start = 0;
+ end = 1;
+ } else {
+ start = end = 0;
+ }
+ }
+ return [
+ start,
+ end,
+ selected[2]
+ ];
+ }
+
+ if (!options) {
+ options = {};
+ }
+
+ if (options.hasOwnProperty("onSelect")) {
+ onSelects.push(options.onSelect);
+ }
+
+ if (options.hasOwnProperty("duration")) {
+ duration = options.duration;
+ }
+
+ if (!options.hasOwnProperty("latencyCorrection")) {
+ options.latencyCorrection = 0;
+ }
+
+ if (!options.hasOwnProperty("showcrossfades")) {
+ options.showcrossfades = false;
+ }
+
+ if (!options.hasOwnProperty("showGrid")) {
+ options.showGrid = true;
+ }
+
+ if (!options.hasOwnProperty("drawStyle")) {
+ options.drawStyle = "bar"; //"linebar";
+ }
+
+ if (!options.hasOwnProperty("allowSelect")) {
+ options.allowSelect = true;
+ }
+
+ if (options.allowSelect) {
+ elContainer.mousedown(mouseDownHandler).dblclick(mouseDoubleClickHandler).on("mousemove", function(e) {
+ if (channels != 2) return;
+ var ratio = (e.clientY - elContainer.offset().top) / parseFloat(elContainer.height());
+ var shown = false;
+ var text;
+ if (ratio > 1 - stereoSelectRatio) {
+ shown = true;
+ text = "R";
+ } else if (ratio < stereoSelectRatio) {
+ shown = true;
+ text = "L";
+ }
+ if (shown) {
+ elTip.show().css({left: (e.clientX + 10) + "px", top: (e.clientY - 5) + "px"}).text(text);
+ } else {
+ elTip.hide();
+ }
+ }).on("mouseleave", function(){
+ elTip.hide();
+ });
+ }
+
+ var elCover = $("<div />").css({position: "absolute", width: "100%", height: "100%", "background-color": "var(--waveformCoverColor)", opacity: "var(--waveformCoverOpacity)", display: "none", "z-index": 10}).appendTo(elContainerOuter);
+
+ var Marker = function(data, identifier) {
+ var mself = this;
+ var elMarkers;
+ var headerWidth = 14;
+ var headerHeight = 15;
+ var elLine;
+ var elHeader;
+ var drag
+ var position;
+ var onMarkerChange;
+
+ Object.defineProperty(this, "position", {
+ get: function() { return position; },
+ set: function(x) {
+ setPosition(x);
+ }
+ });
+
+ Object.defineProperty(this, "identifier", {
+ get: function() { return identifier; },
+ set: function(x) {
+ identifier = x;
+ }
+ });
+
+ if (typeof(data) == "number") {
+ position = data;
+ } else if (data.preset) {
+ if (data.preset == "selectionstart") {
+ identifier = "";
+ selectionMarkers[0] = mself;
+ onMarkerChange = function() {
+ if (position > selectionMarkers[1].position) {
+ self.alterSelection(selectionMarkers[1].position, position, null, true);
+ } else {
+ self.alterSelection(position, null, null, true);
+ }
+ };
+ onSelects.push(function(start, regionStart, end) {
+ setPosition(start);
+ });
+ } else if (data.preset == "selectionend") {
+ identifier = "";
+ selectionMarkers[1] = mself;
+ onMarkerChange = function() {
+ if (position < selectionMarkers[0].position) {
+ self.alterSelection(position, selectionMarkers[0].position, null, true);
+ } else {
+ self.alterSelection(null, position, null, true);
+ }
+ };
+ onSelects.push(function(start, regionStart, end) {
+ setPosition(end);
+ });
+ }
+ } else {
+ position = data.position;
+ if (data.hasOwnProperty("identifier")) {
+ identifier = data.identifier;
+ }
+
+ if (data.hasOwnProperty("onChange")) {
+ onMarkerChange = data.onChange;
+ }
+
+ }
+
+ function setPosition(displayPos) {
+ if (!hasContent) return;
+ if (displayPos != null) {
+ position = displayPosToAbsPos(displayPos);
+ } else {
+ displayPos = absPosToDisplayPos(position);
+ }
+ if (!elLine) {
+ elLine = $("<div />").appendTo(elContainer).addClass("waveform_marker").css({position: "absolute",
+ height: "100%", top: "0px", width: "2px", "background-color": "var(--waveformMarkerColor)", "z-index": 11
+ });
+ }
+ if (!elHeader) {
+ elHeader = $("<div />").appendTo(elMarkersRunner).addClass("waveform_marker").css({position: "absolute",
+ height: "100%", top: "0px", width: headerWidth + "px", "background-color": "var(--waveformMarkerColor)", "border-bottom-left-radius": headerWidth + "px", "border-bottom-right-radius": headerWidth + "px", "z-index": 8,
+ "text-align": "center", "font-family": "sans-serif, Arial", "font-size": "8pt", "font-weight": "bold", cursor: "move", "user-select": "none"
+ }).mousedown(handleMousedown);
+ }
+
+ if (displayPos == null) {
+ elLine.hide();
+ elHeader.hide();
+ } else {
+ var posx = elContainer.width() * displayPos;
+ elLine.show().css("left", posx + "px");
+ posx = (posx - (headerWidth / 2)) + 1;
+ elHeader.show().css("left", posx + "px").text(identifier);
+ }
+ }
+
+ this.redraw = function() {
+ if (!hasContent) return;
+ if (position < regionStart || position > regionEnd) {
+ if (elLine) elLine.hide();
+ if (elHeader) elHeader.hide();
+ } else {
+ setPosition();
+ elLine.show();
+ elHeader.show();
+ }
+ }
+
+
+ function handleMousedown(e) {
+ if (!hasContent) return;
+ var pageX = e.pageX;
+ var offset = elMarkersRunner.offset();
+ var width = elMarkersRunner.width();
+
+ function handleDrag(e) {
+ var pos = ((e.pageX - pageX) + (pageX - offset.left)) / width;
+
+ if (pos <= 1 && pos >= 0) {
+ setPosition(pos);
+ if (onMarkerChange) {
+ onMarkerChange(pos);
+ }
+ }
+ }
+ function handleMouseUp(e) {
+ $("body").off("mousemove", handleDrag).off("mouseup", handleMouseUp);
+ }
+ $("body").on("mouseup", handleMouseUp).on("mousemove", handleDrag);
+ }
+
+ setPosition();
+ }; // end marker
+
+
+ if (options.hasOwnProperty("markers")) {
+ elContainer.css("top", "15px");
+ elMarkersRunner = $("<div />").appendTo(elContainerOuter).css({position: "absolute", width: "100%", height: "15px", top: "0px", left: "0px", "background-color": "var(--waveformMarkerRunnerColor)"});
+ if (typeof(options.markers) == "object") {
+ var id = 1;
+ for (let m of options.markers) {
+ self.markers.push(new Marker(m, id++));
+ }
+ }
+ }
+
+
+ if (options.timeBar) {
+ elContainer.css({overflow: "hidden", bottom: "20px"});
+ var elTimeBarOuter = $("<div />").appendTo(elContainerOuter).css({position: "absolute", width: "100%", height: "20px", bottom: "0px", left: "0px", "background-color": "var(--waveformTimeBarBgColor)"});
+ var elTimeBarIcons = $("<div />").appendTo(elTimeBarOuter).css({position: "absolute", width: "80px", height: "100%", bottom: "0px", left: "0px"});
+ var elTimeBarContainer = $("<div />").appendTo(elTimeBarOuter).css({position: "absolute", right: "0px", height: "100%", bottom: "0px", left: "80px", "background-color": "var(--waveformTimeBarBgColor)"}).click(handleTimeBarTrackClick);
+
+ elTimeBar = $("<div />").appendTo(elTimeBarContainer).css({position: "absolute", right: "0px", height: "16px", top: "2px", left: "0px", "background-color": "var(--waveformTimeBarFgColor)"}).mousedown(handleTimeBarMousedown);
+
+ elTimeBarIcons.append(twirl.createIcon({
+ label: "Zoom selection",
+ size: 20,
+ icon: "zoomSelection",
+ click: function() {
+ self.zoomSelection()
+ }
+ }).el);
+
+ elTimeBarIcons.append(twirl.createIcon({
+ label: "Zoom in",
+ size: 20,
+ icon: "zoomIn",
+ click: function() {
+ self.zoomIn()
+ }
+ }).el);
+
+ elTimeBarIcons.append(twirl.createIcon({
+ label: "Zoom out",
+ size: 20,
+ icon: "zoomOut",
+ click: function() {
+ self.zoomOut()
+ }
+ }).el);
+
+ elTimeBarIcons.append(twirl.createIcon({
+ label: "Show all",
+ size: 20,
+ icon: "showAll",
+ click: function() {
+ self.setRegion(0, 1);
+ }
+ }).el);
+
+
+ function setTimeBarPosition(displayLeft, displayRight, setRegion) {
+ if (displayLeft >= 0 && displayRight >= 0) {
+ elTimeBar.css({left: displayLeft, right: displayRight});
+ var w = elTimeBarContainer.width();
+ if (setRegion) {
+ regionStart = displayLeft / w;
+ regionEnd = 1 - (displayRight / w);
+ if (self.onRegionChange) {
+ self.onRegionChange([regionStart, regionEnd]);
+ }
+ }
+ }
+ }
+
+ function handleTimeBarTrackClick(event) {
+ var increment = 20;
+ var apos = event.pageX - elTimeBarContainer.offset().left;
+ var left = parseInt(elTimeBar.css("left"));
+ var right = parseInt(elTimeBar.css("right"));
+ var tbWidth = parseInt(elTimeBar.css("width"));
+ if (apos < left) {
+ left -= increment;
+ right += increment;
+ } else if (apos > left + tbWidth) {
+ left += increment;
+ right -= increment;
+ } else {
+ return;
+ }
+ setTimeBarPosition(left, right, true);
+ draw();
+ }
+
+ function handleTimeBarMousedown(e) {
+ if (!hasContent) return;
+ var pageX = e.pageX;
+ var offset = elTimeBarContainer.offset();
+ var cWidth = elTimeBarContainer.width();
+ var tbWidth = elTimeBar.width();
+ var sLeft = pageX - offset.left - parseInt(elTimeBar.css("left"));
+
+ function handleDrag(e) {
+ var left = ((e.pageX - pageX) + (pageX - offset.left));
+ left = left - sLeft;
+ var end = left + tbWidth;
+ var right = cWidth - end;
+ setTimeBarPosition(left, cWidth - end, true);
+ draw(4); //draw(15);
+
+ }
+
+ function handleMouseOut(e) {
+ handleMouseUp(e);
+ }
+
+ function handleMouseUp(e) {
+ $("body").off("mousemove", handleDrag).off("mouseup", handleMouseUp).off("mouseleave", handleMouseOut);
+ function ensureDraw() {
+ if (drawing) return setTimeout(ensureDraw, 20);
+ draw();
+ }
+ ensureDraw();
+ }
+ $("body")
+ .on("mouseup", handleMouseUp)
+ .on("mousemove", handleDrag)
+ .on("mouseleave", handleMouseOut);
+ }
+ }
+
+ this.getDuration = function() {
+ return duration;
+ };
+
+ this.destroy = function() {
+ elTarget.remove();
+ };
+
+ this.show = function() {
+ elTarget.show();
+ };
+
+ this.hide = function() {
+ elTarget.hide();
+ };
+
+ this.cover = function(state) {
+ if (state) {
+ elCover.show();
+ } else {
+ elCover.hide();/*
+ setTimeout(function() {
+ elCover.hide();
+ }, options.latencyCorrection);*/
+ }
+ };
+
+ this.setOptions = function(o) {
+ Object.assign(options, o);
+ };
+
+
+ function drawCrossFades() {
+ if (!hasContent) return;
+ if (!options.showcrossfades) return;
+ if (elCrossfades.length == 0) {
+ for (var x = 0; x < 2; x++) {
+ elCrossfades.push($("<div />").css({
+ position: "absolute",
+ width: "1px",
+ "z-index": 9,
+ "line-width": "1px",
+ "height": "var(--waveformCrossfadeWidth)",
+ "background-color": "var(--waveformCrossfadeLineColor)"
+ }).appendTo(elContainer));
+ }
+ }
+ var containerHeight = elContainer.height();
+ var containerWidth = elContainer.width();
+ var displaySelected = getDisplaySelected();
+
+
+ function drawCrossfade(index) {
+ var thickness = 1;
+ var ratio = crossFadeRatios[index];
+ if (ratio == 0) {
+ elCrossfades[index].hide();
+ }
+
+ if (index == 0) {
+ var x1 = displaySelected[0] * containerWidth;
+ var y1 = containerHeight;
+ var x2 = x1 + (ratio * ((displaySelected[1] - displaySelected[0]) * containerWidth));
+ var y2 = 0;
+ } else {
+ var x1 = displaySelected[1] * containerWidth;;
+ var y1 = containerHeight;
+ var x2 = x1 - (ratio * ((displaySelected[1] - displaySelected[0]) * containerWidth));
+ var y2 = 0;
+ }
+ var length = Math.sqrt(((x2-x1) * (x2-x1)) + ((y2-y1) * (y2-y1)));
+ var centrex = ((x1 + x2) / 2) - (length / 2);
+ var centrey = ((y1 + y2) / 2) - (thickness / 2);
+ var angle = Math.atan2((y1-y2),(x1-x2))*(180/Math.PI);
+ elCrossfades[index].show().css({transform: "rotate(" + angle + "deg)", left: centrex, top: centrey, width: length + "px"});
+ }
+ drawCrossfade(0);
+ drawCrossfade(1);
+
+ };
+
+ this.alterSelection = function(start, end, channel, noOnSelects) {
+ if (!hasContent) return;
+ if (start != null) {
+ selected[0] = start;
+ }
+ if (end != null) {
+ selected[1] = end;
+ }
+
+ var displaySelected = getDisplaySelected();
+
+ if (channel == null) {
+ channel = selected[2];
+ } else {
+ selected[2] = channel;
+ }
+
+ var elWidth = elContainer.width();
+ var left = displaySelected[0] * elWidth;
+ var width = (displaySelected[1] * elWidth) - left;
+
+ if (dragData.selection) {
+ dragData.selection.css({
+ left: left,
+ width: width
+ });
+ }
+
+ if (dragData.location) {
+ dragData.location.css({
+ left: left
+ });
+ }
+
+ if (!noOnSelects && onSelects) {
+ for (let onSelect of onSelects) {
+ onSelect(start, regionStart, end, regionEnd, self);
+ }
+ }
+ drawCrossFades();
+ }
+
+ this.setSelection = function(start, end, channel) {
+ if (!hasContent) return;
+ if (!end) {
+ end = start;
+ }
+ self.alterSelection(start, end, channel);
+ };
+
+
+ function selectionMade() {
+ if (!hasContent) return;
+ var cWidth = elContainer.width();
+ var left = parseFloat(dragData.selection.css("left"));
+ var width = parseFloat(dragData.selection.css("width"));
+ var start = left / cWidth;
+ var end = (left + width) / cWidth;
+
+ selected = [
+ displayPosToAbsPos(start),
+ displayPosToAbsPos(end),
+ dragData.channel
+ ];
+
+ if (onSelects) {
+ for (let onSelect of onSelects) {
+ onSelect(start, regionStart, end, regionEnd, self);
+ }
+ }
+ drawCrossFades();
+ }
+
+ function createSelectionArea(e, leftOverride, widthOverride) {
+ var left = (leftOverride != null ) ? leftOverride : e.pageX - dragData.offset.left;
+
+ var containerHeight = parseFloat(elContainer.height());
+ var yratio = (e.pageY - dragData.offset.top) / containerHeight;
+ var heightmult = 1;
+ var topaugment = 0;
+ dragData.channel = -1;
+ if (channels == 2) {
+ if (yratio > 1 - stereoSelectRatio) {
+ heightmult = 0.5;
+ topaugment = containerHeight * 0.5;
+ dragData.channel = 1;
+ } else if (yratio < stereoSelectRatio) {
+ heightmult = 0.5;
+ dragData.channel = 0;
+ }
+ }
+ var width = (widthOverride != null) ? widthOverride : "0px";
+
+ if (dragData && dragData.selection) {
+ dragData.selection.remove();
+ }
+
+ dragData.selection = $("<div />")
+ .addClass("waveformSelection")
+ .css({
+ left: left,
+ top: topaugment,
+ position: "absolute",
+ opacity: "var(--waveformSelectOpacity)",
+ backgroundColor: "var(--waveformSelectColor)",
+ height: (100 * heightmult) + "%", //containerHeight * heightmult,
+ width: width,
+ "pointer-events": "none",
+ "z-index": 10
+ }).appendTo(elContainer);
+
+ if (dragData && dragData.location) {
+ dragData.location.remove();
+ }
+ dragData.location = $("<div />")
+ .addClass("waveformLocation")
+ .css({
+ left: left,
+ top: 0,
+ position: "absolute",
+ opacity: "var(--waveformSelectOpacity)",
+ backgroundColor: "var(--waveformLocationColor)",
+ width: "0px",
+ border: "1px solid black",
+ height: "100%", //elContainer.height(),
+ "pointer-events": "none",
+ "z-index": 11
+ }).appendTo(elContainer);
+ }
+
+ function mouseDoubleClickHandler(e) {
+ if (!hasContent) return;
+ dragData.pageX = 0;
+ dragData.offset.left = 0;
+ //dragData.pageXend = elContainer.width(); // TODO redundant
+ createSelectionArea(e, 0, elContainer.width());
+ selectionMade();
+ }
+
+ function mouseDownHandler(e) {
+ if (!hasContent) return;
+ var tolerancePx = 4;
+ if (!e.shiftKey) {
+ dragData.pageX = e.pageX;
+ dragData.pageY = e.pageY;
+ //dragData.pageXend = 0; // TODO redundant
+ dragData.elem = this;
+ dragData.offset = $(this).offset();
+ createSelectionArea(e);
+ dragData.shifted = false;
+ } else {
+ dragData.shifted = true;
+ handleDrag(e);
+ }
+
+ var elWidth = elContainer.width();
+ /*if (options.showcrossfades && elCrossfades.length != 0) {
+ elCrossfades[0].hide();
+ elCrossfades[1].hide();
+ }*/ // TODO redundant
+
+ function handleDrag(e) {
+ var origin = dragData.pageX - dragData.offset.left;
+ var dragPos = (e.pageX - dragData.pageX);
+ if (dragPos >= 0) {
+ if (dragPos + origin > elWidth) {
+ dragPos = elWidth;
+ }
+ if (dragPos <= tolerancePx) dragPos = 0;
+ dragData.selection.css({
+ left: origin + "px",
+ width: dragPos + "px"
+ });
+ } else {
+ var dpos = dragPos + origin;
+ var left;
+ var width;
+ if (dpos <= 0) {
+ left = 0;
+ width = origin;
+ } else {
+ left = dpos;
+ width = Math.abs(dragPos);
+ }
+ if (width <= tolerancePx) width = 0;
+ dragData.selection.css({
+ left: left + "px",
+ width: width + "px"
+ });
+ }
+ }
+
+ function handleMouseUp(e) {
+ $("body")
+ .off("mousemove", handleDrag)
+ .off("mouseup", handleMouseUp)
+ .off("mouseleave", handleMouseOut);
+ if (!hasContent) return;
+ //dragData.pageXend = e.pageX; // TODO redundant
+ selectionMade();
+ }
+
+ function handleMouseOut(e) {
+ if (e.clientX > $("body").width()) {
+ var left = parseFloat(dragData.selection.css("left"));
+ dragData.selection.css({width: (elContainer.width() - left) + "px"});
+ }
+ handleMouseUp(e);
+ }
+
+ $("body")
+ .on("mouseup", handleMouseUp)
+ .on("mousemove", handleDrag)
+ .on("mouseleave", handleMouseOut);
+ }
+
+ var lastDrawOneValueXpos;
+
+ this.resetDrawOneValue = function() {
+ lastDrawOneValueXpos = null;
+ };
+
+ function drawOneValue(x, values) {
+ var style = getComputedStyle(document.body);
+ var bgColour = (options.hasOwnProperty("bgColor")) ? options.bgColour : style.getPropertyValue("--waveformBgColor");
+ var fgColour = (options.hasOwnProperty("fgColor")) ? options.fgColour : style.getPropertyValue("--waveformFgColor");
+
+ function drawCanvas(canvasIndex, val) {
+ if (!val) return;
+ var elCanvas = elCanvases[canvasIndex];
+ let width = elCanvas.width();
+ let height = elCanvas.height();
+ let ctx = elCanvas[0].getContext("2d");
+ var lineWidth = 1;
+ if (lastDrawOneValueXpos) {
+ lineWidth = x - lastDrawOneValueXpos;
+ ctx.fillStyle = bgColour;
+ ctx.fillRect(lastDrawOneValueXpos, 0, lineWidth, height);
+ ctx.strokeStyle = fgColour;
+ }
+ lastDrawOneValueXpos = x;
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = "round";
+ ctx.fillStyle = fgColour;
+
+ ctx.beginPath();
+ val = (val + 1) * 0.5;
+ var posY0 = (val * height);
+ var posY1 = (height - posY0);
+ ctx.moveTo(x, posY0);
+ ctx.lineTo(x, posY1);
+ ctx.closePath();
+ ctx.stroke();
+ }
+
+ drawCanvas(0, values[0]);
+ if (values.length == 2) {
+ drawCanvas(1, values[1]);
+ }
+ }
+
+ this.movePlayhead = function(xratio, monitorValues) {
+ if (!hasContent) return;
+ setTimeout(function() {
+ var displayPos = absPosToDisplayPos(xratio);
+ if (!displayPos || displayPos < 0 || displayPos > 1) {
+ if (elPlayhead) {
+ elPlayhead.remove();
+ elPlayhead = null;
+ }
+ return;
+ }
+ var width = elContainer.width();
+ var left = Math.min(width * displayPos, width - 1);
+
+ if (monitorValues) {
+ drawOneValue(left, monitorValues);
+ }
+
+ if (!elPlayhead) {
+ elPlayhead = $("<div />")
+ .addClass("waveformPlayhead")
+ .css({
+ left: left,
+ top: 0,
+ position: "absolute",
+ backgroundColor: "var(--waveformPlayheadColor)",
+ width: "0px",
+ border: "1px solid var(--waveformPlayheadColor)",
+ height: "100%",
+ "pointer-events": "none",
+ "z-index": 13
+ }).appendTo(elContainer);
+ } else {
+ elPlayhead.css({left: left + "px"});
+ }
+ }, options.latencyCorrection);
+ };
+
+ var drawing = false;
+ async function draw(efficiency) {
+ if (!hasContent || !wavedata || wavedata.length == 0) return;
+ if (drawing) return;
+ drawing = true;
+
+ if (!efficiency) efficiency = 1;
+
+ if (elCanvases.length == 0) {
+ for (var i in wavedata) {
+ var height;
+ var top;
+ if (wavedata.length == 1) {
+ top = "0px";
+ height = "100%";
+ } else {
+ height = "50%";
+ if (i == 0) {
+ top = "0px";
+ } else {
+ top = "50%";
+ }
+ }
+ elCanvases[i] = $("<canvas />").css({position: "absolute", width: "100%", height: height, top: top, left: "0px"}).addClass("waveform_canvas").appendTo(elContainer);
+ }
+ }
+
+ for (m of self.markers) {
+ m.redraw();
+ }
+ self.alterSelection(null, null, null, true); // redraw selection and xfades
+
+
+ async function drawCanvas(canvasIndex) {
+ var elCanvas = elCanvases[canvasIndex]; //.empty();
+ elCanvas[0].width = elContainer.width();
+ elCanvas[0].height = elContainer.height() / wavedata.length;
+ let width = elCanvas.width();
+ let height = elCanvas.height();
+ let ctx = elCanvas[0].getContext("2d");
+ var wavelength;
+ var access;
+ if (typeof(wavedata[canvasIndex]) == "function") {
+ wavelength = await wavedata[canvasIndex](-1);
+ access = wavedata[canvasIndex];
+ } else {
+ wavelength = wavedata[0].length;
+ access = async function(index) {
+ return wavedata[0][index];
+ };
+ }
+
+ var start = Math.round(regionStart * wavelength);
+ var end = Math.round(regionEnd * wavelength);
+ var regionLength = Math.round((regionEnd - regionStart) * wavelength);
+ var indexStep = (regionLength / width) * efficiency;
+ var widthStep = (indexStep < 1) ? parseInt(width / regionLength) : 1;
+ widthStep = parseInt(Math.max(1, widthStep) * efficiency);
+ indexStep = parseInt(indexStep);
+
+ var style = getComputedStyle(document.body);
+ var bgColour = (options.hasOwnProperty("bgColor")) ? options.bgColor : style.getPropertyValue("--waveformBgColor");
+ var fgColour = (options.hasOwnProperty("fgColor")) ? options.fgColor : style.getPropertyValue("--waveformFgColor");
+ var val;
+ ctx.fillStyle = bgColour;
+ ctx.fillRect(0, 0, width, height);
+ ctx.strokeStyle = fgColour;
+
+ if (options.drawStyle == "line") {
+ ctx.lineCap = "butt";
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(0, height * 0.5);
+ for (var x = 0, i = start; x < width; x+=widthStep, i+=indexStep) {
+ val = await access(i);
+ val = (val + 1) * 0.5;
+ ctx.lineTo(x, (val * height) );
+ }
+ ctx.closePath();
+ ctx.stroke();
+ } else if (options.drawStyle == "bar") {
+ ctx.lineWidth = widthStep;
+ ctx.lineCap = "round";
+ ctx.fillStyle = fgColour;
+ ctx.beginPath();
+
+ for (var x = 0, i = start; x < width; x+=widthStep, i+=indexStep) {
+ val = await access(i);
+ val = (val + 1) * 0.5;
+ var posY0 = (val * height);
+ var posY1 = (height - posY0);
+ ctx.moveTo(x, posY0);
+ ctx.lineTo(x, posY1);
+ }
+ ctx.closePath();
+ ctx.stroke();
+ } else if (options.drawStyle == "linebar") {
+ ctx.lineWidth = 1;
+ ctx.fillStyle = fgColour;
+ ctx.lineCap = "butt";
+
+ ctx.beginPath();
+ ctx.moveTo(0, height * 0.5);
+ xindex = 0;
+
+ var vals = [];
+ for (var x = 0, i = start; x < width; x+=widthStep, i+=indexStep) {
+ val = await access(i);
+ val = (val + 1) * 0.5;
+ vals[i] = val;
+ var posY0 = (val * height);
+ ctx.lineTo(x, posY0);
+ }
+
+ ctx.lineTo(width, (height * 0.5));
+
+ for ( ; x >= 0; x-=widthStep, i-=indexStep) {
+ var posY1 = (height - (vals[i] * height));
+ ctx.lineTo(x, posY1);
+ }
+
+ ctx.lineTo(0, height * 0.5);
+ ctx.fill();
+ ctx.closePath();
+ ctx.stroke();
+ } else {
+ console.log("Invalid drawStyle");
+ } // end drawing waveform
+
+ if (options.showGrid){
+ var lineSpacing = 50;
+ var lineNum = width / lineSpacing;
+ var position;
+
+ ctx.lineCap = "butt";
+ ctx.lineWidth = 1;
+ for (var x = 0; x < lineNum; x++) {
+ ctx.beginPath();
+ var left = x * lineSpacing;
+ ctx.strokeStyle = ctx.fillStyle = style.getPropertyValue("--waveformGridColor");
+ ctx.moveTo(left, 0);
+ ctx.lineTo(left, height);
+ ctx.stroke();
+
+ if ((canvasIndex == 0 && elCanvases.length == 1) ||
+ (canvasIndex == 1 && elCanvases.length == 2)) {
+
+ ctx.strokeStyle = ctx.fillStyle = style.getPropertyValue("--waveformGridTextColor");
+ positionLabel = duration * (regionStart + (((regionEnd - regionStart) / lineNum) * x));
+ ctx.fillText(Math.round(positionLabel * 1000 ) / 1000, left + 2, height - 2);
+ }
+ }
+
+ } // if showgrid
+
+ if (canvasIndex == 0 && elCanvases.length == 2) {
+ ctx.beginPath();
+ ctx.lineCap = "butt";
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = style.getPropertyValue("--waveformChannelLineColor");
+ ctx.fillStyle = ctx.strokeStyle;
+ ctx.moveTo(0, height - 1);
+ ctx.lineTo(width, height - 1);
+ ctx.closePath();
+ ctx.stroke();
+ }
+ } // end drawCanvas
+
+ var drawCompletes = [];
+ for (let i in elCanvases) {
+ drawCompletes.push(false);
+ drawCanvas(i).then(function(){
+ drawCompletes[i] = true;
+ for (let c of drawCompletes) {
+ if (!c) {
+ return;
+ }
+ }
+ drawing = false;
+ });
+ }
+ }
+
+ this.redraw = function() {
+ draw();
+ };
+
+ Object.defineProperty(this, "crossFadeInRatio", {
+ get: function() { return crossFadeRatios[0]; },
+ set: function(v) {
+ crossFadeRatios[0] = v;
+ drawCrossFades();
+ }
+ });
+
+ Object.defineProperty(this, "crossFadeOutRatio", {
+ get: function() { return crossFadeRatios[1]; },
+ set: function(v) {
+ crossFadeRatios[1] = v;
+ drawCrossFades();
+ }
+ });
+
+ Object.defineProperty(this, "selected", {
+ get: function() { return selected; },
+ set: function(x) { }
+ });
+
+ Object.defineProperty(this, "duration", {
+ get: function() { return duration; },
+ set: function(x) { }
+ });
+
+ Object.defineProperty(this, "channels", {
+ get: function() { return channels; },
+ set: function(x) { }
+ });
+
+ Object.defineProperty(this, "regionStart", {
+ get: function() { return regionStart; },
+ set: function(x) {
+ self.setRegion(x, regionEnd);
+ }
+ });
+
+ Object.defineProperty(this, "showGrid", {
+ get: function() { return options.showGrid; },
+ set: function(x) {
+ options.showGrid = x;
+ draw();
+ }
+ });
+
+ Object.defineProperty(this, "regionEnd", {
+ get: function() { return regionEnd; },
+ set: function(x) {
+ self.setRegion(regionStart, x);
+ }
+ });
+
+
+ this.zoomSelection = function() {
+ if (!dragData || !dragData.location) return;
+ dragData.location.css("left", "0px");
+ self.setRegion(selected[0], selected[1]);
+ };
+
+ this.zoomOut = function() {
+ self.setRegion(regionStart * 0.9, regionEnd * 1.1);
+ };
+
+ this.zoomIn = function() {
+ self.setRegion(regionStart * 1.1, regionEnd * 0.9);
+ };
+
+ this.setRegion = function(start, end) {
+ if (!hasContent) return;
+ if (end <= start) return;
+ if (end > 1) end = 1;
+ if (start < 0) start = 0;
+ regionStart = start;
+ regionEnd = end;
+ draw();
+ if (elTimeBar) {
+ var elTbcw = elTimeBarContainer.width();
+ elTimeBar.css({left: (regionStart * elTbcw) + "px", right: ((1 - regionEnd) * elTbcw) + "px"});
+ }
+ if (self.onRegionChange) {
+ self.onRegionChange([regionStart, regionEnd]);
+ }
+ };
+
+ this.setData = function(data, nduration, noRedraw) {
+ hasContent = true;
+ wavedata = data; // should be array
+ if (channels != data.length) {
+ for (var i in elCanvases) {
+ elCanvases[i].remove();
+ }
+ delete elCanvases[i];
+ elCanvases.length = 0;
+ }
+ channels = data.length;
+ duration = (nduration) ? nduration : 1;
+ if (!noRedraw) {
+ draw();
+ }
+ };
+
+ var lastSize = [];
+ function handleResize() {
+ if (!hasContent) return;
+ var width = elContainer.width();
+ var height = elContainer.height();
+
+ if (lastSize[0] = width && lastSize[1] == height) return;
+ lastSize = [width, height];
+
+ if (dragData && dragData.selection) {
+ selectionMade();
+ }
+ draw();
+ }
+
+ if (!options.noResizeHandler) {
+ window.addEventListener("resize", handleResize);
+ }
+}
|