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);
});
});
}