var Twigs = function() { var twigs = this; twigs.version = 1; twirl.init(); twigs.SELECTIONMODE = { singleBin: 0, dragBins: 1, dragArea: 2, lasso: 3, binAppend: 4, draw: 5, move: -1, transpose: -2 }; twigs.selectionMode = twigs.SELECTIONMODE.singleBin; twigs.undoLevel = 0; var elCanvas; var elHitCanvas; var elSelectCanvas; var elGridCanvas; var elSelection = $("#twigs_selection"); var elContainer; var width; var height; var playing; var loaded = { name: null, channels: null, bins: null, tables: null, length: null, binSelectionTable: null, binTimeSelectionTable: null }; var maxfreq = 22050; var amplitudeScaling = 40; var ctx; var ctxHit; var ctxSelect; var ctxGrid; var xsteps; var xstep; var tabstep; var region = {frequency: [0, 1], time: [0, 1]}; var selection = {bins: {}}; var binColourHash = {}; var colIDs; var onSave; twigs.visible = false; twigs.twine = null; twigs.storage = localStorage.getItem("twigs"); if (twigs.storage) { twigs.storage = JSON.parse(twigs.storage); } else { twigs.storage = { graphType: 0, maxundo: 2, drawFrequencyGrid: 1, drawTimeGrid: 1 }; } this.saveStorage = function() { localStorage.setItem("twigs", JSON.stringify(twigs.storage)); }; function absPosToDisplayPos(x) { var pos = (x - region.time[0]) / (region.time[1] - region.time[0]); return (pos >= 0 && pos <= 1) ? pos : null; } function displayPosToAbsPos(x) { return ((region.time[1] - region.time[0]) * x) + region.time[0]; } function pxToFreq(px, height) { return ((px / height) * ((region.frequency[1] - region.frequency[0]) * maxfreq)); //return (((region.frequency[1] - region.frequency[0]) * maxfreq) / height) * px; } function pxToFrames(px, width) { var frames = Math.round(loaded.length / loaded.bins); console.log("move ", px, "wid", width); return Math.round(((px / width) * (region.time[1] - region.time[0]) * frames)); //return Math.round((((region.time[1] - region.time[0]) * (loaded.length / loaded.bins)) / width) * px); } var playheadInterval; function playPositionHandler(onComplete) { function callback(ndata) { if (ndata.status == 1) { twigs.setPlaying(true); if (playheadInterval) { clearInterval(playheadInterval); } playheadInterval = setInterval(async function(){ var val = await app.getControlChannel("twgs_playposratio"); if (val < 0 || val > 1) { clearInterval(playheadInterval); } movePlayhead(val); }, 50); } else { // stopped for some reason if (ndata.status == -1) { twirl.prompt.show("Not enough processing power to play in realtime"); } twigs.setPlaying(false); app.removeCallback(ndata.cbid); movePlayhead(0); if (playheadInterval) clearInterval(playheadInterval); if (onComplete) onComplete(ndata); } } return app.createCallback(callback, true); } function movePlayhead(xratio) { setTimeout(function() { var p = $("#twigs_playhead"); var displayPos = absPosToDisplayPos(xratio); if (!displayPos || displayPos <= 0 || displayPos >= 1) { return p.hide(); } var width = elContainer.width(); var left = Math.min(width * displayPos, width -1); p.show().css({left: left + "px"}); }, twirl.latencyCorrection); } this.hasSelection = function() { return (Object.keys(selection.bins) != 0); } this.selectionOperation = { move: async function(freqshift, timeshiftframes) { console.log("ts", timeshiftframes, "fs", freqshift); console.log("move duration", (timeshiftframes / Math.round(loaded.length / loaded.bins)) * loaded.duration); twirl.loading.show("Applying"); await setBinSelectionTable(); var cbid1 = app.createCallback(function(ndata1){ var cbid2 = app.createCallback(function(ndata2){ clearSelection(); globalCallbackHandler(ndata2); }); app.insertScore("twgs_movement", [0, 1, cbid2, timeshiftframes]); }); app.insertScore("twgs_freqshift", [0, 1, cbid1, freqshift, 1]); }, shift: async function(freqshift) { twirl.loading.show("Applying"); await setBinSelectionTable(); var cbid = app.createCallback(function(ndata){ clearSelection(); globalCallbackHandler(ndata); }); app.insertScore("twgs_freqshift", [0, 1, cbid, freqshift]); /*for (let b of selection.bins) { for (var i = b; i < loaded.length; i += loaded.bins) { var val = await app.getCsound().tableGet(loaded.tables[1], index); val += freqshift; val = Math.max(Math.min(val, maxfreq), 0); await app.getCsound().tableSet(loaded.tables[1], index, val) } } clearSelection(); twigs.redraw(); */ }, amplify: async function(factor) { twirl.loading.show("Applying"); await setBinSelectionTable(); var cbid = app.createCallback(function(ndata){ globalCallbackHandler(ndata); }); app.insertScore("twgs_amplify", [0, 1, cbid, factor]); } }; async function setBinSelectionTable() { var length = await app.getCsound().tableLength(loaded.binSelectionTable); var selectionData = []; var timeData = []; var region = [1, 0]; for (var i = 0; i < length; i++) { if (selection.bins[i]) { selectionData[i] = 1; timeData[i] = selection.bins[i][0]; timeData[i + length] = selection.bins[i][1]; if (selection.bins[i][0] < region[0]) region[0] = selection.bins[i][0]; if (selection.bins[i][1] > region[1]) region[1] = selection.bins[i][1]; } else { selectionData[i] = 0; timeData[i] = -1; timeData[i + length] = -1; } } await app.getCsound().tableCopyIn(loaded.binSelectionTable, selectionData); await app.getCsound().tableCopyIn(loaded.binTimeSelectionTable, timeData); return region; } this.setSelectionMode = function(mode) { twigs.selectionMode = mode; if (mode >= 0) { selection.bins = {}; clearSelection(); } elSelection.hide(); }; this.setTimeRegion = function(start, end, noRedraw) { region.time[0] = start; region.time[1] = end; var outerWidth = elScrollOuterH.width(); elScrollInnerH.css({ left: (start * outerWidth) + "px", right: ((1 - end) * outerWidth) + "px" }); if (!noRedraw) twigs.redraw(); }; this.setFrequencyRegion = function(start, end, noRedraw) { region.frequency[0] = start; region.frequency[1] = end; var outerHeight = elScrollOuterV.height(); elScrollInnerV.css({ bottom: (start * outerHeight) + "px", top: ((1 - end) * outerHeight) + "px" }); if (!noRedraw) twigs.redraw(); }; function setScrollPositionV(displayTop, displayBottom, setRegion) { if (displayTop >= 0 && displayBottom >= 0) { elScrollInnerV.css({top: displayTop, bottom: displayBottom}); var h = elScrollOuterV.height(); region.frequency[0] = displayTop / h; region.frequency[1] = 1 - (displayBottom / h); } } function handleScrollOuterV(e) { var increment = 20; var apos = event.pageX - elScrollOuterV.offset().left; var top = parseInt(elScrollInnerV.css("bottom")); var bottom = parseInt(elScrollInnerV.css("bottom")); var tbHeight = parseInt(elScrollInnerV.css("height")); if (apos < top) { top -= increment; bottom += increment; } else if (apos > top + tbHeight) { top += increment; bottom -= increment; } else { return; } setScrollPositionV(top, bottom); twigs.redraw(); } function handleScrollInnerV(e) { var pageY = e.pageY; var offset = elScrollOuterV.offset(); var cHeight = elScrollOuterV.height(); var tHeight = elScrollInnerV.height(); var sTop = pageY - offset.top - parseInt(elScrollInnerV.css("top")); function handleDrag(e) { var top = ((e.pageY - pageY) + (pageY - offset.top)); top = top - sTop; var end = top + tHeight; var bottom = cHeight - end; setScrollPositionV(top, cHeight - end); twigs.redraw(20, true); } function handleMouseUp(e) { $("body").off("mousemove", handleDrag).off("mouseup", handleMouseUp); function ensureDraw() { if (drawing) return setTimeout(ensureDraw, 20); twigs.redraw(); } ensureDraw(); } $("body").on("mouseup", handleMouseUp).on("mousemove", handleDrag); } function setScrollPositionH(displayLeft, displayRight) { if (displayLeft >= 0 && displayRight >= 0) { elScrollInnerH.css({left: displayLeft, right: displayRight}); var w = elScrollOuterH.width(); region.time[0] = displayLeft / w; region.time[1] = 1 - (displayRight / w); } } function handleScrollOuterH(e) { var increment = 20; var apos = event.pageX - elScrollOuterH.offset().left; var left = parseInt(elScrollInnerH.css("left")); var right = parseInt(elScrollInnerH.css("right")); var tbWidth = parseInt(elScrollInnerH.css("width")); if (apos < left) { left -= increment; right += increment; } else if (apos > left + tbWidth) { left += increment; right -= increment; } else { return; } setScrollPositionH(left, right); twigs.redraw(); } function handleScrollInnerH(e) { var pageX = e.pageX; var offset = elScrollOuterH.offset(); var cWidth = elScrollOuterH.width(); var tbWidth = elScrollInnerH.width(); var sLeft = pageX - offset.left - parseInt(elScrollInnerH.css("left")); function handleDrag(e) { var left = ((e.pageX - pageX) + (pageX - offset.left)); left = left - sLeft; var end = left + tbWidth; var right = cWidth - end; setScrollPositionH(left, cWidth - end); twigs.redraw(20, true); } function handleMouseUp(e) { $("body").off("mousemove", handleDrag).off("mouseup", handleMouseUp); function ensureDraw() { if (drawing) return setTimeout(ensureDraw, 20); twigs.redraw(); } ensureDraw(); } $("body").on("mouseup", handleMouseUp).on("mousemove", handleDrag); } var elScrollOuterH = $("#twigs_editor_hscrollouter").click(handleScrollOuterH); var elScrollInnerH = $("#twigs_editor_hscrollinner").click(handleScrollInnerH); var elScrollOuterV = $("#twigs_editor_vscrollouter").click(handleScrollOuterV); var elScrollInnerV = $("#twigs_editor_vscrollinner").click(handleScrollInnerV); this.vzoomIn = function() { twigs.setFrequencyRegion(region.frequency[0] * 1.1, region.frequency[1] * 0.9); }; this.vzoomOut = function() { twigs.setFrequencyRegion(region.frequency[0] * 0.9, region.frequency[1] * 1.1); }; this.hzoomIn = function() { twigs.setTimeRegion(region.time[0] * 1.1, region.time[1] * 0.9); }; this.hzoomOut = function() { twigs.setTimeRegion(region.time[0] * 0.9, region.time[1] * 1.1); }; async function withBinPoints(bin, func) { for (var x = 0, i = parseInt(bin); i < loaded.length; i += tabstep, x += xstep) { await func(i, x) } } function clearSelection() { selection.bins = {}; ctxSelect.clearRect(0, 0, width, height); } async function drawSelection() { var startFreq = region.frequency[0] * maxfreq; var endFreq = region.frequency[1] * maxfreq; var freqRange = endFreq - startFreq; var height = elContainer.height(); var width = elContainer.width(); var freqTable = await app.getCsound().getTable(loaded.tables[1]); ctxSelect.clearRect(0, 0, width, height); ctxSelect.strokeStyle = "red"; ctxSelect.lineWidth = 2; async function drawBin(bin, times) { if (twigs.storage.graphType == 0) ctxSelect.beginPath(); var lastfreq = null; await withBinPoints(bin, function(i, x){ var freq = freqTable[i]; var xRatio = x / width; if (xRatio < absPosToDisplayPos(times[0]) || xRatio > absPosToDisplayPos(times[1])) return; if (twigs.storage.graphType == 1) { var yPos = ((freqRange - freq) / freqRange) * height; ctxSelect.beginPath(); ctxSelect.moveTo(x, yPos); ctxSelect.lineTo(x + xstep, yPos); ctxSelect.stroke(); ctxSelect.closePath(); } else { if (lastfreq) { var yPos = [ ((freqRange - lastfreq) / freqRange) * height, ((freqRange - freq) / freqRange) * height ]; ctxSelect.moveTo(x - xstep, yPos[0]); ctxSelect.lineTo(x, yPos[1]); } lastfreq = freq; } }); if (twigs.storage.graphType == 0) { ctxSelect.stroke(); ctxSelect.closePath(); } } for (let b in selection.bins) { await drawBin(b, selection.bins[b]); } } function setup() { elContainer = $("#twigs_editor_inner"); width = elContainer.width(); // deprecate it height = elContainer.height(); var dragStart = []; var dragLast = []; var offset; elHitCanvas = $("").css({ position: "absolute", width: "100%", height: "100%" }).attr("width", width) .attr("height", height); elCanvas = $("").css({position: "absolute", width: "100%", height: "100%", top: "0px", left: "0px", "z-index": 12}) .attr("width", width) .attr("height", height) .appendTo(elContainer); function mouseMove(e){ if (dragStart.length == 0) { return; } if (twigs.selectionMode == twigs.SELECTIONMODE.singleBin || twigs.selectionMode == twigs.SELECTIONMODE.binAppend) { return; } var x = e.clientX - offset.left; var y = e.clientY - offset.top; if (twigs.selectionMode == twigs.SELECTIONMODE.lasso) { //ctxSelect.moveTo(dragLast[0], dragLast[1]); ctxSelect.lineTo(x, y); dragLast[0] = x; dragLast[1] = y; ctxSelect.stroke(); return; } if (twigs.selectionMode == twigs.SELECTIONMODE.move) { var xMovement = x - dragLast[0]; var yMovement = y - dragLast[1]; ctxSelect.globalCompositeOperation = "copy"; ctxSelect.drawImage(ctxSelect.canvas, xMovement, yMovement); ctxSelect.globalCompositeOperation = "source-over"; dragLast[0] = x; dragLast[1] = y; return; } if (twigs.selectionMode == twigs.SELECTIONMODE.transpose) { var xMovement = x - dragLast[0]; } var x, cx, width; if (twigs.selectionMode == twigs.SELECTIONMODE.dragBins) { x = 0; width = elContainer.width(); } else { x = dragStart[0]; cx = e.clientX - offset.left if (x > cx) { width = x - cx; x = cx; } else { width = cx - x; } } var y = dragStart[1]; var cy = e.clientY - offset.top; var height = cy - y; elSelection.css({ left: x + "px", top: y + "px", width: width + "px", height: height + "px" }).show(); } function mouseUp(e){ var x = e.clientX - offset.left; var y = e.clientY - offset.top; var xMovement = x - dragStart[0]; var yMovement = y - dragStart[1]; if (twigs.selectionMode < 0) { twigs.selectionOperation.move( pxToFreq(-yMovement, elContainer.height()), pxToFrames(xMovement, elContainer.width()) ); //twigs.selectionOperation.shift(pxToFreq(-yMovement)); } else if (twigs.selectionMode == twigs.SELECTIONMODE.lasso) { //ctxSelect.moveTo(dragLast[0], dragLast[1]); ctxSelect.lineTo(dragStart[0], dragStart[1]); ctxSelect.stroke(); ctxSelect.fillStyle = "rgb(255, 0, 0)"; ctxSelect.fill(); ctxSelect.closePath(); twirl.loading.show("Finding frequencies"); setTimeout(function(){ var width = elContainer.width(); var id = ctxSelect.getImageData(0, 0, width, elContainer.height()); var x = 0; var y = 0; var bins = {}; for (var i = 0; i < id.data.length; i += 4) { if (id.data[i] == "255") { var pixel = ctxHit.getImageData(x, y, 1, 1); var colour = "rgb(" + pixel.data[0] + "," + pixel.data[1] + "," + pixel.data[2] + ")"; var bin = binColourHash[colour]; if (bin) { if (bins[bin]) { if (x < bins[bin][0]) bins[bin][0] = x; if (x > bins[bin][1]) bins[bin][1] = x; } else { bins[bin] = [x, x]; } } } x ++; if (x >= width) { x = 0; y ++; } } for (var b in bins) { selection.bins[b] = [ displayPosToAbsPos(bins[b][0] / width), displayPosToAbsPos(bins[b][1] / width) ] } ctxSelect.clearRect(0, 0, width, height); drawSelection(); twirl.loading.hide(); }, 10); } else if (twigs.selectionMode == twigs.SELECTIONMODE.dragBins || twigs.selectionMode == twigs.SELECTIONMODE.dragArea) { var selWidth = x - xMovement; var time; if (twigs.selectionMode == twigs.SELECTIONMODE.dragArea) { var containerWidth = elContainer.width(); time = [ dragStart[0] / containerWidth, x / containerWidth ]; } else { time = [0, 1]; } var bins = []; var pixels = ctxHit.getImageData(dragStart[0], dragStart[1], xMovement, yMovement); //x, y, xMovement, yMovement); for (var i = 0; i < pixels.data.length; i += 4) { var colour = "rgb(" + pixels.data[i] + "," + pixels.data[i + 1] + "," + pixels.data[i + 2] + ")"; var bin = binColourHash[colour]; if (bin && bins.indexOf(bin) < 0) { bins.push(bin); selection.bins[bin] = [ displayPosToAbsPos(time[0]), displayPosToAbsPos(time[1]) ] } } drawSelection(); } elSelection.hide(); dragStart = []; dragLast = []; $("body").off("mouseup", mouseUp).off("mousemove", mouseMove); } elGridCanvas = $("").css({ position: "absolute", width: "100%", height: "100%", top: "0px", left: "0px", "z-index": 12 }).attr("width", width).attr("height", height).appendTo(elContainer); elSelectCanvas = $("").css({ position: "absolute", width: "100%", height: "100%", top: "0px", left: "0px", "z-index": 13 }).attr("width", width) .attr("height", height).appendTo(elContainer).on("mousedown", function(e){ offset = $(this).offset(); var x = e.clientX - offset.left; var y = e.clientY - offset.top; dragStart = [x, y]; dragLast = [x, y]; if (twigs.selectionMode == twigs.SELECTIONMODE.singleBin || twigs.selectionMode == twigs.SELECTIONMODE.binAppend) { const pixel = ctxHit.getImageData(x, y, 1, 1); const colour = "rgb(" + pixel.data[0] + "," + pixel.data[1] + "," + pixel.data[2] + ")"; const bin = binColourHash[colour]; if (bin) { var binTime = [ displayPosToAbsPos(0), displayPosToAbsPos(1), ] if (twigs.selectionMode == twigs.SELECTIONMODE.binAppend) { selection.bins[bin] = binTime; } else { clearSelection(); selection.bins[bin] = binTime; } drawSelection(); } } else { if (twigs.selectionMode == twigs.SELECTIONMODE.lasso) { clearSelection(); ctxSelect.strokeStyle = "black"; ctxSelect.fillStyle = "rgb(10, 10, 10, 50)"; ctxSelect.lineWidth = 2; ctxSelect.moveTo(dragStart[0], dragStart[1]); ctxSelect.beginPath(); } $("body").on("mouseup", mouseUp).on("mousemove", mouseMove); } }); ctx = elCanvas[0].getContext("2d"); ctxHit = elHitCanvas[0].getContext("2d", {willReadFrequently: true}); ctxSelect = elSelectCanvas[0].getContext("2d", {willReadFrequently: true}); ctxGrid = elGridCanvas[0].getContext("2d"); } function getNextColour() { if (colIDs[0] < 255) { colIDs[0] += 5; } else if (colIDs[1] < 255) { colIDs[1] += 5; } else { colIDs[2] += 5; } return "rgb(" + colIDs.join(",") + ")"; } this.setPlaying = function(state) { playing = state; }; this.undo = function() { if (playing) return; twirl.loading.show("Applying"); var cbid = app.createCallback(globalCallbackHandler); app.insertScore("twgs_undo", [0, 1, cbid]); }; this.play = async function(selectedOnly) { if (playing) return; if (!twigs.storage.resynthType) { twigs.storage.resynthType = 0; twigs.saveStorage(); } errorState = "Playback error"; var region = [0, 1]; if (selectedOnly) { region = await setBinSelectionTable(); } app.insertScore("twgs_play", [ 0, 1, playPositionHandler(), 0, ((selectedOnly) ? 1 : 0), region[0], region[1], twigs.storage.resynthType ]); }; this.increaseAmpScaling = function() { amplitudeScaling += 20; twigs.redraw(); }; this.decreaseAmpScaling = function() { amplitudeScaling -= 20; if (amplitudeScaling <= 0) amplitudeScaling = 20; twigs.redraw(); }; function spectroColour(value) { var min = 16711680 var max = 255 var colourNumber = parseInt(((max - min) * value) + min); function toHex(n) { n = n.toString(16) + ''; return n.length >= 2 ? n : new Array(2 - n.length + 1).join('0') + n; } var r = toHex(colourNumber % 256), g = toHex(Math.floor(colourNumber / 256 ) % 256), b = toHex(Math.floor(Math.floor(colourNumber / 256) / 256 ) % 256); return '#' + r + g + b; } var drawing = false; this.redraw = async function(efficiency, noLoadingPrompt) { if (drawing) return; drawing = true; if (!efficiency) efficiency = 4; if (!noLoadingPrompt) twirl.loading.show("Drawing"); if (twigs.storage.basicLines == null) twigs.storage.basicLines = 0; var style = getComputedStyle(document.body); var height = elContainer.height(); var width = elContainer.width(); [ctx, ctxSelect, ctxHit, ctxGrid].forEach(function(c){ c.clearRect(0, 0, width, height); }); binColourHash = {}; colIDs = [0, 0, 0]; var startFreq = region.frequency[0] * maxfreq; var endFreq = region.frequency[1] * maxfreq; var freqRange = endFreq - startFreq; var ampTableNum = loaded.tables[0]; var freqTableNum = loaded.tables[1]; var tableLength = await app.getCsound().tableLength(ampTableNum); var ampTable = await app.getCsound().getTable(ampTableNum); var freqTable = await app.getCsound().getTable(freqTableNum); var totalFrames = tableLength / loaded.bins; var startFrame = parseInt(region.time[0] * totalFrames); var endFrame = parseInt(region.time[1] * totalFrames); var frameRegion = endFrame - startFrame; var startIndex = startFrame * loaded.bins; var endIndex = endFrame * loaded.bins; var indexStep = (frameRegion / width) * efficiency; xstep = parseInt(Math.max(1, width / frameRegion) * efficiency); tabstep = loaded.bins * Math.max(1, Math.round(indexStep)); if (!twigs.storage.basicLines) { ctx.lineWidth = 2; ctx.shadowBlur = 2; } else { ctx.lineWidth = 1; ctx.shadowBlur = null; ctx.shadowColor = null; } for (var b = 0; b < loaded.bins; b ++) { var hitColour = getNextColour(); if (!binColourHash[hitColour]) { binColourHash[hitColour] = b; } ctxHit.lineWidth = 4; ctxHit.strokeStyle = hitColour; if (twigs.storage.graphType == 0) ctxHit.beginPath(); var lastfreq = null; for (var x = 0, i = b + startIndex; i < endIndex; i += tabstep, x += xstep) { var freq = freqTable[i]; var colour; if (twigs.storage.colourType == 1) { colour = spectroColour(ampTable[i] * amplitudeScaling); } else { var cval = 255 - Math.round((ampTable[i] * amplitudeScaling) * 255); cval = Math.min(Math.max(0, cval), 255); colour = "rgb(" + cval + "," + cval + "," + cval + ")"; } if (twigs.storage.graphType == 1) { var yPos = ((freqRange - freq) / freqRange) * height; ctx.beginPath(); ctx.moveTo(x, yPos); ctx.lineTo(x + xstep, yPos); ctx.strokeStyle = colour; if (!twigs.storage.basicLines) ctx.shadowColor = colour; ctx.stroke(); ctx.closePath(); ctxHit.beginPath(); ctxHit.moveTo(x, yPos); ctxHit.lineTo(x + xstep, yPos); ctxHit.stroke(); ctxHit.closePath(); } else if (twigs.storage.graphType == 0) { if (lastfreq && lastfreq >= startFreq && lastfreq <= endFreq && freq >= startFreq && freq <= endFreq) { var yPos = [ ((freqRange - lastfreq) / freqRange) * height, ((freqRange - freq) / freqRange) * height ]; ctx.beginPath(); ctx.moveTo(x - xstep, yPos[0]); ctx.lineTo(x, yPos[1]); ctx.strokeStyle = colour; if (!twigs.storage.basicLines) ctx.shadowColor = colour; ctx.stroke(); ctx.closePath(); ctxHit.moveTo(x - xstep, yPos[0]); ctxHit.lineTo(x, yPos[1]); } lastfreq = freq; } } if (twigs.storage.graphType == 0) { ctxHit.stroke(); ctxHit.closePath(); } } /* if (twigs.storage.drawFrequencyGrid) { var lines = 10; var ystep = height / lines; var freqstep = (endFreq - startFreq) / lines; ctxGrid.lineCap = "butt"; ctxGrid.lineWidth = 1; for (var y = 0, freq = endFreq; y += ystep, freq -= freqstep; y < height) { ctxGrid.strokeStyle = ctxGrid.fillStyle = "rgb(100, 0, 0, 40)"; //style.getPropertyValue("--waveformGridColor"); ctxGrid.beginPath(); ctxGrid.moveTo(0, y); ctxGrid.lineTo(width, y); ctxGrid.stroke(); ctxGrid.closePath(); //ctx.strokeStyle = ctx.fillStyle = style.getPropertyValue("--waveformGridTextColor"); ctxGrid.fillText(Math.round(freq), 0, y - 2); } } if (twigs.storage.drawTimeGrid) { var lines = 10; var startTime = region.time[0] * loaded.duration; var endTime = region.time[1] * loaded.duration; var xstep = width / lines; var timestep = (endTime - startTime) / lines; ctxGrid.lineCap = "butt"; ctxGrid.lineWidth = 1; for (var x = 0, time = startTime; x += xstep, time += timestep; x < width) { if (x > width) break; // wtf??????? this is strangely happening ctxGrid.strokeStyle = ctxGrid.fillStyle = "rgb(100, 0, 0, 40)"; //style.getPropertyValue("--waveformGridColor"); ctxGrid.beginPath(); ctxGrid.moveTo(x, 0); ctxGrid.lineTo(x, height); ctxGrid.stroke(); ctxGrid.closePath(); //ctx.strokeStyle = ctx.fillStyle = style.getPropertyValue("--waveformGridTextColor"); ctxGrid.fillText(Math.round(time * 1000) / 1000, x + 2, height - 2); } } */ drawSelection(); if (!noLoadingPrompt) twirl.loading.hide(); drawing = false; } async function globalCallbackHandler(ndata) { if (ndata.status && ndata.status <= 0) { return self.errorHandler(); } if (ndata.hasOwnProperty("undolevel")) { twigs.undoLevel = ndata.undolevel; } if (ndata.channels) { loaded.channels = ndata.channels; } var initialLoad = false; if (ndata.bins) { loaded.bins = ndata.bins; initialLoad = true; } if (ndata.fftdecim) { loaded.fftdecimation = ndata.fftdecim; } if (ndata.duration) { loaded.duration = ndata.duration; } if (ndata.sr) { maxfreq = ndata.sr / 2; } if (ndata.binseltab) { loaded.binSelectionTable = ndata.binseltab; } if (ndata.bintimeseltab) { loaded.binTimeSelectionTable = ndata.bintimeseltab; } if (ndata.tables) { setTimeout(async function() { loaded.tables = ndata.tables; loaded.length = await app.getCsound().tableLength(loaded.tables[0]); if (!twigs.storage.zoomOnLoad) twigs.storage.zoomOnLoad = 1; if (initialLoad && twigs.storage.zoomOnLoad) { twigs.setFrequencyRegion(0, 0.2, true); twigs.setTimeRegion(0, 0.2); } else { twigs.redraw(); } }, 10); // csound may not be ready } } this.editInTwist = function() { if (playing) return; if (!twigs.storage.resynthType) { twigs.storage.resynthType = 0; twigs.saveStorage(); } if (!window.twist) { return twirl.prompt.show("twist is unavailable in this session"); } twirl.loading.show("Processing"); var cbid = app.createCallback(function(ndata){ if (ndata.status == 3) { return twirl.loading.show("Resynthesising"); } twirl.loading.hide(); app.removeCallback(ndata.cbid); twist.loadFileFromFtable(loaded.name, ndata.tables, function(ldata){ if (ldata.status > 0) { self.setVisible(false); twist.setVisible(true); } }, onSave); }, true); app.insertScore("twgs_resynth", [0, 1, cbid, "twgs_getbuffers", twigs.storage.resynthType]); }; this.setVisible = function(state) { twigs.visible = state; var el = $("#twigs"); if (state) { el.show(); } else { el.hide(); } }; function formatFileName(name) { if (!name) name = waveformTabs[instanceIndex].text(); if (!name.toLowerCase().endsWith(".wav")) { name += ".wav"; } // HACK TODO: WASM can't overwrite files name = name.substr(0, name.lastIndexOf(".")) + "." + saveNumber + name.substr(name.lastIndexOf(".")); saveNumber ++; // END HACK return name; } this.downloadFile = async function(path, name) { if (!name) name = formatFileName(name); var content = await app.readFile(path); var blob = new Blob([content], {type: "audio/wav"}); var url = window.URL.createObjectURL(blob); var a = $("").attr("href", url).attr("download", name).appendTo($("body")).css("display", "none"); a[0].click(); setTimeout(function(){ a.remove(); window.URL.revokeObjectURL(url); app.unlinkFile(path); }, 20000); }; var saveNumber = 1; this.saveFile = function(name, onComplete) { if (!twigs.storage.resynthType) { twigs.storage.resynthType = 0; twigs.saveStorage(); } if (playing) return; if (onSave) { twirl.loading.show("Processing"); var cbid = app.createCallback(function(ndata){ if (ndata.status == 3) { return twirl.loading.show("Resynthesising"); } twirl.loading.hide(); app.removeCallback(ndata.cbid); onSave(ndata.tables); }, true); app.insertScore("twgs_resynth", [0, 1, cbid, "twgs_getbuffers", twigs.storage.resynthType]); return; } if (!name) name = formatFileName(name); var cbid = app.createCallback(async function(ndata){ if (ndata.status == 3) { return twirl.loading.show("Resynthesising"); } app.removeCallback(ndata.cbid); twirl.loading.show("Saving"); app.insertScore("twgs_savefile", [0, 1, app.createCallback(async function(ldata){ if (onComplete) onComplete(); await self.downloadFile(ndata.path, ndata.path); twirl.loading.hide(); }), name]); }, true); twist.loading.show("Processing"); app.insertScore("twgs_resynth", [0, 1, cbid, "twgs_resynth_response", twigs.storage.resynthType]); }; this.loadFileFromFtable = function(name, tables, onComplete, onSaveFunc) { errorState = "File loading error"; twigs.ui.showLoadFileFFTPrompt(function(fftSize, fftDecim){ twirl.loading.show("Loading file"); var cbid = app.createCallback(async function(ndata){ twirl.loading.hide(); if (ndata.status > 0) { loaded.name = name; await globalCallbackHandler(ndata); onSave = onSaveFunc; } else if (ndata.status == -1) { twirl.prompt.show("File not valid"); } else if (ndata.status == -2) { twirl.prompt.show("File too large"); } else { twirl.prompt.show("File loading error"); } if (onComplete) { onComplete(ndata); } }); var call = [0, 1, cbid, fftSize, fftDecim]; for (let t of tables) { call.push(t); } app.insertScore("twgs_loadftable", call); }); }; async function handleFileDrop(e, obj) { e.preventDefault(); if (!e.originalEvent.dataTransfer && !e.originalEvent.files) { return; } if (e.originalEvent.dataTransfer.files.length == 0) { return; } twirl.prompt.hide(); twirl.loading.show("Loading"); for (const item of e.originalEvent.dataTransfer.files) { if (!twirl.audioTypes.includes(item.type)) { return twirl.errorHandler("Unsupported file type"); } if (item.size > twirl.maxFileSize) { return twirl.errorHandler("File too large", twigs.ui.showLoadNewPrompt); } errorState = "File loading error"; var content = await item.arrayBuffer(); const buffer = new Uint8Array(content); await app.writeFile(item.name, buffer); twigs.ui.showLoadFileFFTPrompt(function(fftSize, fftDecim){ twirl.loading.show("Loading file"); var cbid = app.createCallback(async function(ndata){ await app.unlinkFile(item.name); if (ndata.status == -1) { return twirl.errorHandler("File not valid", twigs.ui.showLoadNewPrompt); } else if (ndata.status == -2) { return twirl.errorHandler("File too large", twigs.ui.showLoadNewPrompt); } else { loaded.name = item.name; await globalCallbackHandler(ndata); onSave = false; } }); app.insertScore("twgs_loadfile", [0, 1, cbid, item.name, fftSize, fftDecim]); }); } } this.bootAudio = function(twine) { var channelDefaultItems = ["maxundo"]; for (var i of channelDefaultItems) { if (twigs.storage.hasOwnProperty(i)) { app.setControlChannel("twgs_" + i, twigs.storage[i]); } } }; var booted = false; this.boot = function(twine) { if (booted) return; booted = true; setup(); twigs.ui = new TwigsUI(twigs); if (!twine) { $("body").on("dragover", function(e) { e.preventDefault(); e.originalEvent.dataTransfer.effectAllowed = "all"; e.originalEvent.dataTransfer.dropEffect = "copy"; return false; }).on("dragleave", function(e) { e.preventDefault(); }).on("drop", function(e) { handleFileDrop(e, self); }); } else { twigs.twine = twine; } }; }; function twigs_startisolated() { window.twigs = new Twigs(); twigs.setVisible(true); window.app = new CSApplication({ csdUrl: "twigs.csd", onPlay: function() { twigs.bootAudio(); twirl.prompt.show("Drag a file here to begin", null, true); twirl.loading.hide(); }, errorHandler: twirl.errorHandler }); $("#twigs_start").click(function(){ $(this).hide(); twigs.boot(); twirl.loading.show("Preparing audio engine"); app.play(function(text){ twirl.loading.show(text); }); }); }