From 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 13 Apr 2025 18:48:02 +0100 Subject: initial --- site/app/base/waveform.js | 1076 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1076 insertions(+) create mode 100644 site/app/base/waveform.js (limited to 'site/app/base/waveform.js') 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 = $("
").css({position: "absolute", width: "100%", height: "100%"}).appendTo(elTarget); + var elContainer = $("
").css({cursor: "text", position: "absolute", width: "100%", bottom: "0px", top: "0px", left: "0px"}).appendTo(elContainerOuter); + var elTip = $("
").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 = $("
").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 = $("
").appendTo(elContainer).addClass("waveform_marker").css({position: "absolute", + height: "100%", top: "0px", width: "2px", "background-color": "var(--waveformMarkerColor)", "z-index": 11 + }); + } + if (!elHeader) { + elHeader = $("
").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 = $("
").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 = $("
").appendTo(elContainerOuter).css({position: "absolute", width: "100%", height: "20px", bottom: "0px", left: "0px", "background-color": "var(--waveformTimeBarBgColor)"}); + var elTimeBarIcons = $("
").appendTo(elTimeBarOuter).css({position: "absolute", width: "80px", height: "100%", bottom: "0px", left: "0px"}); + var elTimeBarContainer = $("
").appendTo(elTimeBarOuter).css({position: "absolute", right: "0px", height: "100%", bottom: "0px", left: "80px", "background-color": "var(--waveformTimeBarBgColor)"}).click(handleTimeBarTrackClick); + + elTimeBar = $("
").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($("
").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 = $("
") + .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 = $("
") + .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 = $("
") + .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] = $("").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); + } +} -- cgit v1.2.3