diff options
Diffstat (limited to 'site/app/twigs/twigs.js')
-rw-r--r-- | site/app/twigs/twigs.js | 1165 |
1 files changed, 1165 insertions, 0 deletions
diff --git a/site/app/twigs/twigs.js b/site/app/twigs/twigs.js new file mode 100644 index 0000000..7567dd0 --- /dev/null +++ b/site/app/twigs/twigs.js @@ -0,0 +1,1165 @@ +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 = $("<canvas />").css({
+ position: "absolute", width: "100%", height: "100%"
+ }).attr("width", width)
+ .attr("height", height);
+ elCanvas = $("<canvas />").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 = $("<canvas />").css({
+ position: "absolute", width: "100%", height: "100%", top: "0px", left: "0px", "z-index": 12
+ }).attr("width", width).attr("height", height).appendTo(elContainer);
+
+ elSelectCanvas = $("<canvas />").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 = $("<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);
+ });
+ });
+}
\ No newline at end of file |