var OperationWatchdog = function(twist) {
var self = this;
var active = false;
var lastValues = [true, true];
var firstActive = true;
var checkInterval;
var timeoutTime = 30000;
var alivetimeoutTime = 3500;
var context;
function crash() {
self.stop();
twirl.sendErrorState({text: "Unhandled exception in " + context});
var el = $("#twist_crash").show();
var elSr = $("#twist_crash_recovery");
function doomed() {
elSr.empty().append($("
").text("Sorry, unfortunately your work cannot be saved."));
}
var doomedTimeout = setTimeout(doomed, 6000);
var cbid = app.createCallback(function(ndata) {
if (doomedTimeout) clearTimeout(doomedTimeout);
if (!ndata.left && !ndata.right) {
return doomed();
}
elSr.empty();
var text;
var linkLeft = $("").attr("href", "#").text("Download").click(function(e){
e.preventDefault();
twist.downloadFile("/crashL.wav");
});
if (ndata.left && !ndata.right) {
elSr.append($("").text("Your work has been recovered:"));
elSr.append(linkLeft);
} else {
elSr.append($("").text("Your work has been recovered as separate left/right channels:"));
linkLeft.text("Download left channel").appendTo(elSr);
elSr.append(" ");
var linkRight = $("").attr("href", "#").text("Download right channel").click(function(e){
e.preventDefault();
twist.downloadFile("/crashR.wav");
}).appendTo(elSr);
}
});
app.getCsound().compileOrc("iwrittenL = 0\niwrittenR = 0\nif (gitwst_bufferL[gitwst_instanceindex] > 0) then\niwrittenL ftaudio gitwst_bufferL[gitwst_instanceindex], \"/crashL.wav\", 14\nendif\nif (gitwst_bufferR[gitwst_instanceindex] > 0) then\niwrittenR ftaudio gitwst_bufferR[gitwst_instanceindex], \"/crashR.wav\", 14\nendif\nio_sendstring(\"callback\", sprintf(\"{\\\"cbid\\\":" + cbid + ",\\\"left\\\":%d,\\\"right\\\":%d}\", iwrittenL, iwrittenR))\n");
}
function checkAlive() {
var alive = false;
var aliveTimeout = setTimeout(crash, alivetimeoutTime);
var cbid = app.createCallback(function(){
clearTimeout(aliveTimeout);
alive = true;
});
app.insertScore("twst_checkalive", [0, 1, cbid]);
}
this.start = function(startContext) {
active = true;
context = startContext;
firstActive = true;
lastValues = [true, true];
if (checkInterval) clearInterval(checkInterval);
checkInterval = setInterval(function() {
if (lastValues[0] === lastValues[1]) {
checkAlive();
}
}, timeoutTime);
};
this.setActive = function(value) {
if (!active) return;
if (firstActive) {
firstActive = false;
} else {
lastValues[0] = lastValues[1];
}
lastValues[1] = value;
};
this.stop = function() {
active = false;
firstActive = true;
lastValues = [true, true];
if (checkInterval) clearInterval(checkInterval);
};
};
var Twist = function() {
twirl.init();
var self = this; // TODO deprecate this in favour of below
var twist = this;
this.storage = localStorage.getItem("twist");
if (self.storage) {
self.storage = JSON.parse(self.storage);
} else {
self.storage = {
dcblockoutputs: 1,
tanhoutputs: 1,
maxundo: 2,
showShortcuts: 1,
commitHistoryLevel: 16,
scopeType: 0
};
}
twist.version = 1.1;
this.currentTransform = null;
var errorState;
var instanceIndex = 0;
this.waveforms = [];
var waveformFiles = [];
var waveformTabs = [];
var waveformLoaded = [];
this.playheadInterval = null;
var playing = false;
var auditioning = false;
var recording = false;
this.onPlays = [];
this.onInstanceChangeds = [];
this.operationLog = [];
var sr = 44100;
var undoLevels = [];
var onSave;
this.visible = false;
this.playbackLoop = false;
this.twine = null;
this.hasClipboard = false;
this.watchdog = new OperationWatchdog(twist);
this.ui = new TwistUI(twist);
this.setPlaying = function(state) {
if (playing == state) return;
playing = state;
for (var o of twist.onPlays) {
o(playing, auditioning, recording);
}
if (twist.currentTransform) {
twist.currentTransform.setPlaying(state);
}
twist.ui.setPlaying(state);
if (!state) {
twist.watchdog.stop();
twist.waveform.movePlayhead(0);
if (twist.playheadInterval) {
clearInterval(twist.playheadInterval);
}
}
};
this.saveStorage = function() {
localStorage.setItem("twist", JSON.stringify(twist.storage));
};
this.lastOperation = function() {
return twist.operationLog[twist.operationLog.length - 1];
};
this.clearOperationLog = function() {
twist.operationLog = [];
};
async function pushOperationLog(operation, logChannels) {
var max = twist.storage.commitHistoryLevel;
if (!max) {
twist.storage.commitHistoryLevel = max = 16;
}
if (twist.operationLog.length + 1 >= max) {
twist.operationLog.shift();
}
if (logChannels) {
if (!operation.channels) operation.channels = {};
for (let c of logChannels) {
operation.channels[c] = await app.getControlChannel(c);
}
}
twist.operationLog.push(operation);
}
this.createNewInstance = function(noShowLoadNew) {
var element = $("").addClass("waveform").appendTo("#twist_waveforms");
let index = waveformFiles.length;
if (index < 0) index = 0;
waveformTabs.push(
$("
").text("New file").click(function() {
if (twist.isPlaying()) return;
twist.waveform = index;
}).addClass("wtab_selected").appendTo("#twist_waveform_tabs")
);
undoLevels.push(0);
var waveform = new Waveform({
target: element,
latencyCorrection: twirl.latencyCorrection,
showcrossfades: true,
crossFadeWidth: 1,
timeBar: true,
markers: [
{preset: "selectionstart"},
{preset: "selectionend"},
]
})
waveform.onRegionChange = function(region) {
if (twist.currentTransform) {
twist.currentTransform.redraw(region);
}
};
twist.waveforms.push(waveform);
if (!noShowLoadNew) twist.ui.showLoadNewPrompt();
twist.waveform = index;
for (let o of twist.onInstanceChangeds) {
o(true, index);
}
};
function removeInstance(i) {
if (!i) i = instanceIndex;
if (twist.waveforms.length == 1 || i < 0 || i > twist.waveforms.length - 1) {
return;
}
twist.waveforms[i].destroy();
delete twist.waveforms[i];
waveformTabs[i].remove();
waveformLoaded[instanceIndex] = false;
delete waveformTabs[i]
if (instanceIndex == i) {
instanceIndex = i + ((i == 0) ? 1 : -1);
twist.waveform.show();
}
for (let o of twist.onInstanceChangeds) {
o(false, i);
}
}
this.closeInstance = function(i) {
removeInstance(i);
};
this.errorHandler = async function(text, onComplete) {
var errorObj = {
lastOperation: twist.lastOperation()
};
if (twist.currentTransform) {
var state = await twist.currentTransform.getState();
errorObj.transformState = state;
}
twirl.errorHandler(text, onComplete, errorObj);
twist.setPlaying(false);
};
function playPositionHandler(noPlayhead, onComplete, monitorChannels) {
function callback(ndata) {
if (ndata.status == 1) { // playing
twist.setPlaying(true);
if (!noPlayhead) {
twist.watchdog.start("audition");
if (twist.playheadInterval) {
clearInterval(twist.playheadInterval);
}
twist.playheadInterval = setInterval(async function(){
var val = await app.getControlChannel("twst_playposratio");
twist.watchdog.setActive(val);
if (val < 0 || val > 1) {
clearInterval(twist.playheadInterval);
}
var monitorValues;
if (monitorChannels) {
monitorValues = [];
monitorValues.push((monitorChannels[0]) ? await app.getControlChannel(monitorChannels[0]) : null);
monitorValues.push((monitorChannels[1]) ? await app.getControlChannel(monitorChannels[1]) : null);
} else {
monitorValues = null;
}
twist.waveform.movePlayhead(val, monitorValues);
}, 50);
}
return;
}
// stopped
app.removeCallback(ndata.cbid);
if (twist.playbackLoop && ndata.status == 0 && onComplete) {
return onComplete(ndata);
}
twist.setPlaying(false);
if (ndata.status == -1) {
var container = $("");
$("").text("Not enough processing power to transform in realtime").appendTo(container);
var lagHintHtml = twist.currentTransform.getLagHints();
if (lagHintHtml) {
$("").html(lagHintHtml).appendTo(container);
}
return twirl.prompt.show(container);
} else if (ndata.status == 2) { // record complete
globalCallbackHandler(ndata);
}
if (onComplete) onComplete(ndata);
}
return app.createCallback(callback, true);
}
function operation(options) {
var s = (options.selection) ? options.selection : twist.waveform.selected;
errorState = "Operation error";
if (options.showLoading) {
twist.ui.setLoadingStatus(true);
}
var cbid;
if (!options.onComplete || typeof(options.onComplete) == "function") {
cbid = app.createCallback(function(ndata) {
twist.waveform.cover(false);
if (options.onComplete) {
options.onComplete(ndata);
} else if (ndata.status && ndata.status <= 0) {
var text;
if (ndata.status == -2) {
text = "Resulting file would be too large";
}
twist.errorHandler(text);
}
if (options.showLoading) {
twist.ui.setLoadingStatus(false);
}
});
} else {
cbid = options.onComplete;
}
if (!options.noLogScript) {
pushOperationLog({
type: "operation",
instr: options.instr,
name: options.name,
selection: s,
instanceIndex: instanceIndex
}, options.logScriptChannels);
}
app.insertScore(options.instr, [0, 1, cbid, s[0], s[1], s[2], (options.noCheckpoint) ? 1 : 0]);
}
this.isPlaying = function() {
return playing;
};
this.redraw = function() {
if (twist.currentTransform) {
twist.currentTransform.redraw();
}
for (let w of twist.waveforms) {
w.redraw();
}
};
this.undo = function() {
if (playing) return;
twist.waveform.cover(true);
operation({
instr: "twst_undo",
name: "Undo",
onComplete: globalCallbackHandler,
showLoading: true,
noLogScript: true
});
};
this.cut = function() {
if (playing) return;
twist.waveform.cover(true);
operation({
instr: "twst_cut",
name: "Cut",
onComplete: globalCallbackHandler,
showLoading: true,
});
twist.hasClipboard = true;
};
this.trim = function() {
if (playing) return;
twist.waveform.cover(true);
operation({
instr: "twst_trim",
name: "Trim",
onComplete: globalCallbackHandler,
showLoading: true,
});
};
this.delete = function() {
if (playing) return;
twist.waveform.cover(true);
operation({
instr: "twst_delete",
name: "Delete",
onComplete: globalCallbackHandler,
showLoading: true,
});
};
this.copy = function() {
if (playing) return;
twist.waveform.cover(true);
operation({
instr: "twst_copy",
name: "Copy",
showLoading: true,
});
twist.hasClipboard = true;
};
this.paste = function() {
if (playing) return;
twist.waveform.cover(true);
operation({
instr: "twst_paste",
name: "Paste",
onComplete: globalCallbackHandler,
showLoading: true,
});
};
this.moveToNextTransient = function() {
if (playing) return;
var cbid = app.createCallback(globalCallbackHandler);
var s = twist.waveform.selected;
app.insertScore("twst_nexttransient",
[0, 1, cbid, s[1], s[1], s[2]]
);
};
this.selectToNextTransient = function() {
if (playing) return;
var cbid = app.createCallback(globalCallbackHandler);
var s = twist.waveform.selected;
var selend = (s[0] == s[1]) ? s[1] + 0.000001 : s[1];
app.insertScore("twst_nexttransient",
[0, 1, cbid, s[0], selend, s[2]]
);
};
this.moveToStart = function() {
if (playing) return;
twist.waveform.setSelection(0);
};
this.moveToEnd = function() {
if (playing) return;
twist.waveform.setSelection(1);
};
this.selectAll = function() {
if (playing) return;
twist.waveform.setSelection(0, 1);
};
this.selectNone = function() {
if (playing) return;
twist.waveform.setSelection(0);
};
this.selectToEnd = function() {
if (playing) return;
twist.waveform.alterSelection(null, 1);
}
this.selectFromStart = function() {
if (playing) return;
twist.waveform.alterSelection(0, null);
}
this.pasteSpecial = function() {
if (playing) return;
var elPasteSpecial = $("");
elPasteSpecial.append($("").text("Paste special"));
var def = {
instr: "twst_pastespecial",
parameters: [
{name: "Repetitions", channel: "repetitions", min: 1, max: 40, step: 1, dfault: 1, automatable: false},
{name: "Mix paste", channel: "mixpaste", step: 1, dfault: 0, automatable: false}
]
};
var tf = new twirl.transform.Transform({
element: elPasteSpecial,
definition: def,
host: twist
});
$("").text("Paste").click(function(){
twist.ui.hidePrompt();
twist.waveform.cover(true);
operation({
instr: "twst_pastespecial",
name: "Paste special",
onComplete: globalCallbackHandler,
showLoading: true,
logScriptChannels: ["twst_pastespecial_repetitions", "twst_pastespecial_mixpaste"]
});
}).appendTo(elPasteSpecial);
$("").text("Cancel").click(function(){
twist.ui.hidePrompt();
}).appendTo(elPasteSpecial);
twist.ui.showPrompt(elPasteSpecial, null, true);
};
this.play = function(playOverride) {
if (!waveformLoaded[instanceIndex] || (playing && !playOverride)) return;
auditioning = false;
recording = false;
operation({
instr: "twst_play",
name: "Play",
onComplete: playPositionHandler(false, function(ndata){
if (ndata.status != 3 && twist.playbackLoop) { // 3 = user-stopped
twist.play(true);
}
}),
noLogScript: true
});
};
this.stop = function() {
if (!playing || !waveformLoaded[instanceIndex]) return;
twist.waveform.cover(false);
app.insertScore("twst_stop");
};
var saveNumber = 1;
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("."));
// 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);
};
this.saveFile = function(name, onComplete) {
if (playing) return;
if (onSave) {
twirl.loading.show("Processing");
var cbid = app.createCallback(function(ndata){
twirl.loading.hide();
onSave(ndata.tables);
});
app.insertScore("twst_getbuffers", [0, 1, cbid]);
return;
}
if (!name) name = formatFileName(name);
var cbid = app.createCallback(async function(ndata){
await self.downloadFile("/" + name, name);
if (onComplete) onComplete();
self.ui.setLoadingStatus(false);
});
self.ui.setLoadingStatus(true, true, "Saving");
app.insertScore("twst_savefile", [0, 1, cbid, name]);
};
function getAutomationData(start, end) {
var calls = [];
if (!self.currentTransform) return calls;
var automations = self.currentTransform.getAutomationData(start, end);
if (automations && automations.length > 0) {
for (let i in automations) {
if (automations[i].type == "modulation") {
calls.push(automations[i].data[0] + " \"" + automations[i].data[1] + "\"");
} else if (automations[i].type == "automation") {
calls.push("chnset linseg:k(" + automations[i].data + "), \"" + automations[i].channel + "\"");
}
}
}
return calls;
}
function handleAutomation(onready, calls) {
if (calls.length == 0) {
return onready(0);
}
var instr = "instr twst_automaterun\n";
for (let c of calls) {
instr += c + "\n";
}
instr += "a_ init 0\nout a_\nendin\n";
app.compileOrc(instr).then(function(status){
if (status < 0) {
self.errorHandler("Cannot parse automation data");
} else {
onready(1);
}
});
/*
var cbid = app.createCallback(function(ndata){
if (ndata.status == 1) {
onready(1);
} else {
self.errorHandler("Cannot parse automation data");
}
});
var call = [0, 1, cbid];
for (let c of calls) {
call.push(c);
}
app.insertScore("twst_automationprepare", call);
*/
}
function fftsizeCheck(selected, duration) {
if (self.currentTransform) {
for (var p in self.currentTransform.parameters) {
if (p.indexOf("fftsize") != -1) {
var val = self.currentTransform.parameters[p].getValue();
var minTime = (val / sr) * 2;
if ((selected[1] - selected[0]) * duration < minTime) {
return false;
}
}
}
}
return true;
}
this.record = async function() {
if (!waveformLoaded[instanceIndex] ||playing) return;
auditioning = false;
recording = true;
await app.enableAudioInput();
errorState = "Recording error";
self.waveform.cover(true);
var s = self.waveform.selected;
var monitorChannels;
if (self.waveform.channels == 1) {
monitorChannels = ["recordmonitorL"];
} else {
if (s[2] == -1) {
monitorChannels = ["recordmonitorL", "recordmonitorR"];
} else if (s[2] == 0) {
monitorChannels = ["recordmonitorL", null];
} else if (s[2] == 1) {
monitorChannels = [null, "recordmonitorR"];
}
}
self.waveform.resetDrawOneValue();
var cbid = playPositionHandler(null, null, monitorChannels);
var items = [0, 1, cbid, s[0], s[1], s[2]];
app.insertScore("twst_record", items);
};
this.audition = function(playOverride) {
if (!waveformLoaded[instanceIndex] || (playing && !playOverride)) return;
if (!self.currentTransform) {
return self.play();
}
self.currentTransform.saveState();
var s = self.waveform.selected;
if (s[0] == s[1]) {
s[0] = 0;
s[1] = 1;
}
if (!fftsizeCheck(s, self.waveform.duration)) {
return self.errorHandler("Length too short for this transform");
}
auditioning = true;
recording = false;
errorState = "Playback error";
handleAutomation(function(automating){
var cbid = playPositionHandler(false, function(ndata){
if (ndata.status != 3 && self.playbackLoop) { // 3 = user-stopped
self.audition(true);
}
});
var xfade = self.ui.getCrossFadeValues();
var items = [
0, 1, cbid, s[0], s[1], s[2],
self.currentTransform.instr, automating,
xfade[0], xfade[1]
];
app.insertScore("twst_audition", items);
}, getAutomationData(s[0], s[1]));
};
var scriptStack = [];
function applyScript(audition, first, lastData) {
if (playing) return;
var noCheckpoint = !first;
var script = scriptStack.shift();
if (!script) {
self.ui.setLoadingStatus(false);
if (lastData) {
globalCallbackHandler(lastData);
}
self.setPlaying(false);
return;
}
if (audition) auditioning = true;
self.setPlaying(true);
if (script.type == "operation") {
if (audition) {
return self.errorHandler("Only transform scripts can be auditioned");
}
self.ui.setLoadingStatus(true);
self.waveform.cover(true);
onComplete = (script.instr == "twst_copy") ? null : globalCallbackHandler;
operation({
instr: script.instr,
name: script.name,
onComplete: function(ndata){
lastData = ndata;
self.setPlaying(false);
applyScript(audition, false, lastData);
},
showLoading: true,
selection: script.selection,
noLogScript: true,
noCheckpoint: noCheckpoint
});
} else if (script.type == "transform") {
errorState = ((audition) ? "Audition" : "Transform" ) + " commit error";
if (!audition) {
self.ui.setLoadingStatus(true, true);
}
for (let channel in script.channels) {
app.setControlChannel(channel, script.channels[channel]);
}
handleAutomation(function(automating){
if (audition) {
var cbid = playPositionHandler();
} else {
var cbid = app.createCallback(function(ndata) {
lastData = ndata;
self.setPlaying(false);
applyScript(audition, false, lastData);
});
}
var instr = "twst_" + ((audition) ? "audition" : "commit");
app.insertScore(instr, [
0, -1, cbid, script.selection[0], script.selection[1], script.selection[2], script.instr, automating, script.crossfades[0], script.crossfades[1], (noCheckpoint) ? 1 : 0
]);
}, script.automation);
}
}
this.applyScript = async function(script, audition) {
if (playing) return;
scriptStack = [];
if (Array.isArray(script)) {
if (audition) {
return self.errorHandler("Only single scripts can be auditioned");
}
scriptStack = script;
} else {
scriptStack = [script];
}
if (self.storage.autosave && !audition) {
self.saveFile(null, function() {
applyScript(audition, true);
});
} else {
applyScript(audition, true);
}
};
async function innerCommit() {
if (playing) return;
if (!self.currentTransform) return;
var s = self.waveform.selected;
if (s[0] == s[1]) {
s[0] = 0;
s[1] = 1;
}
if (!fftsizeCheck(s, self.waveform.duration)) {
return self.errorHandler("Length too short for this transform");
}
self.watchdog.start("commit");
self.setPlaying(true);
self.ui.setLoadingStatus(true, true, null);
var calls = getAutomationData(s[0], s[1]);
self.currentTransform.saveState();
var state = await self.currentTransform.getState();
state.type = "transform";
state.automation = calls;
state.crossfades = self.ui.getCrossFadeValues();
state.selection = [s[0], s[1], s[2]];
state.instanceIndex = instanceIndex;
pushOperationLog(state);
handleAutomation(function(automating){
var cbid = app.createCallback(function(ndata) {
self.watchdog.stop();
self.ui.setLoadingStatus(false);
self.setPlaying(false);
if (ndata.status > 0) {
globalCallbackHandler(ndata);
} else {
var text;
if (ndata.status == -2) {
text = "Resulting file is too large";
}
self.errorHandler(text);
}
});
errorState = "Transform commit error";
app.insertScore("twst_commit", [0, -1, cbid, s[0], s[1], s[2], self.currentTransform.instr, automating, state.crossfades[0],state.crossfades[1]]);
}, calls);
}
this.commit = async function() {
if (!waveformLoaded[instanceIndex]) return;
if (self.storage.autosave) {
self.saveFile(null, function() {
innerCommit();
});
} else {
innerCommit();
}
};
this.loadTransforms = function(transform) {
if (transform) {
var developObj;
for (var t in twirl.appdata.transforms) {
if (twirl.appdata.transforms[t].name == "Develop") {
developObj = twirl.appdata.transforms[t];
break;
}
}
if (!developObj) {
developObj = {name: "Develop", contents: []};
twirl.appdata.transforms.push(developObj);
} else {
for (var c in developObj.contents) {
if (developObj.contents[c].name == transform.name) {
delete developObj.contents[c];
}
}
}
developObj.contents.push(transform);
}
$("#twist_panetree").empty();
var ttv = new twirl.transform.TreeView({
element: $("#twist_panetree"),
items: twirl.appdata.transforms,
click: function(definition, path) {
if (twist.currentTransform) {
twist.currentTransform.remove();
}
twist.currentTransform = new twirl.transform.Transform({
element: $("#twist_controls_inner"),
definition: definition,
splineElement: $("#twist_splines"),
useStorage: true,
path: path,
otherInstanceNamesFunc: function() {
return twist.otherInstanceNames;
},
instancesFunc: function() {
return twist.waveforms;
},
getRegionFunc: function() {
return twist.waveform.getRegion();
},
getDurationFunc: function() {
return twist.waveform.getDuration();
},
onHideAutomation: function() {
twist.ui.deleteSupressed = false;
console.log("twist.ui.deleteSupressed", twist.ui.deleteSupressed);
},
onShowAutomation: function() {
twist.ui.deleteSupressed = true;
console.log("twist.ui.deleteSupressed", twist.ui.deleteSupressed);
},
host: twist
});
}
});
};
this.createEmpty = function(name, duration, channels) {
if (name.trim() == "") {
name = "New file";
}
var cbid = app.createCallback(async function(ndata) {
twist.waveformTab.text(name);
await globalCallbackHandler(ndata);
if (twist.currentTransform) {
twist.currentTransform.refresh();
}
waveformFiles[instanceIndex] = name;
waveformLoaded[instanceIndex] = true;
twist.ui.setLoadingStatus(false);
});
twist.ui.hidePrompt();
twist.ui.setLoadingStatus(true, false, "Creating");
app.insertScore("twst_createempty", [0, 1, cbid, duration, channels]);
};
this.setVisible = function(state) {
twist.visible = state;
var el = $("#twist");
if (state) {
el.show();
} else {
el.hide();
}
};
this.editInTwigs = function() {
if (!window.twigs) {
return twirl.prompt.show("twigs is unavailable in this session");
}
twirl.loading.show("Processing");
var cbid = app.createCallback(function(ndata){
twirl.loading.hide();
twigs.loadFileFromFtable(waveformFiles[instanceIndex], ndata.tables, function(ldata){
if (ldata.status > 0) {
self.setVisible(false);
twigs.setVisible(true);
}
}, onSave);
});
app.insertScore("twst_getbuffers", [0, 1, cbid]);
};
this.loadFileFromURL = async function(url, name, onComplete) {
if (!name) name = url.split("/").reverse()[0];
await app.copyUrlToLocal(url, name);
var cbid = app.createCallback(async function(ndata){
twirl.loading.hide();
if (ndata.status > 0) {
if (waveformTabs.length == 0) {
self.createNewInstance(true);
instanceIndex = 0;
}
self.waveformTab.text(name);
await globalCallbackHandler(ndata);
waveformFiles[instanceIndex] = name;
waveformLoaded[instanceIndex] = true;
} 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);
}
});
app.insertScore("twst_loadfile", [0, 1, name]);
};
this.loadFileFromClipboard = function() {
if (!twist.hasClipboard) {
return twirl.prompt.show("Cannot create: clipboard is empty");
}
errorState = "File loading error";
twirl.loading.show("Loading");
var cbid = app.createCallback(async function(ndata){
self.waveformTab.text("Clipboard");
await globalCallbackHandler(ndata);
waveformFiles[instanceIndex] = "Clipboard";
waveformLoaded[instanceIndex] = true;
twirl.loading.hide();
});
app.insertScore("twst_loadclipboard", [0, 1, cbid]);
};
this.loadFileFromFtable = function(name, tables, onComplete, onSaveFunc) {
errorState = "File loading error";
twirl.loading.show("Loading file");
var cbid = app.createCallback(async function(ndata){
twirl.loading.hide();
if (ndata.status > 0) {
if (waveformTabs.length == 0) {
self.createNewInstance(true);
instanceIndex = 0;
}
self.waveformTab.text(name);
waveformLoaded[instanceIndex] = true;
await globalCallbackHandler(ndata);
if (self.currentTransform) {
self.currentTransform.refresh();
}
waveformFiles[instanceIndex] = name;
self.ui.hidePrompt();
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];
for (let t of tables) {
call.push(t);
}
app.insertScore("twst_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;
}
self.ui.hidePrompt();
self.ui.setLoadingStatus(true, true, "Loading");
for (const item of e.originalEvent.dataTransfer.files) {
if (!twirl.audioTypes.includes(item.type)) {
return self.errorHandler("Unsupported file type", self.ui.showLoadNewPrompt);
}
if (item.size > twirl.maxFileSize) {
return self.errorHandler("File too large", self.ui.showLoadNewPrompt);
}
errorState = "File loading error";
var content = await item.arrayBuffer();
const buffer = new Uint8Array(content);
await app.writeFile(item.name, buffer);
var cbid = app.createCallback(async function(ndata){
await app.unlinkFile(item.name);
if (ndata.status == -1) {
return self.errorHandler("File not valid", self.ui.showLoadNewPrompt);
} else if (ndata.status == -2) {
return self.errorHandler("File too large", self.ui.showLoadNewPrompt);
} else {
self.waveformTab.text(item.name);
await globalCallbackHandler(ndata);
if (self.currentTransform) {
self.currentTransform.refresh();
}
waveformFiles[instanceIndex] = item.name;
waveformLoaded[instanceIndex] = true;
self.ui.hidePrompt();
self.ui.setLoadingStatus(false);
onSave = false;
}
});
app.insertScore("twst_loadfile", [0, 1, cbid, item.name]);
}
}
async function globalCallbackHandler(ndata) {
if (ndata.status && ndata.status <= 0) {
var text;
if (ndata.status == -2) {
text = "Resulting file would be too large";
}
self.errorHandler(text);
return;
}
self.watchdog.start("refresh");
if (ndata.hasOwnProperty("undolevel")) {
self.undoLevel = ndata.undolevel;
}
if (ndata.hasOwnProperty("delete")) {
if (typeof(ndata.delete) == "string") {
app.unlinkFile(ndata.delete);
} else {
for (let d of ndata.delete) {
app.unlinkFile(d);
}
}
}
if (ndata.hasOwnProperty("selstart")) {
self.waveform.setSelection(ndata.selstart, ndata.selend);
}
if (ndata.hasOwnProperty("waveL")) {
self.waveform.cover(true);
errorState = "Overview refresh error";
setTimeout(async function(){
var wavedata = [];
var tbL = await app.getTable(ndata.waveL);
wavedata.push(tbL);
if (ndata.hasOwnProperty("waveR")) {
var tbR = app.getTable(ndata.waveR);
wavedata.push(tbR);
}
self.waveform.setData(wavedata, ndata.duration);
self.waveform.cover(false);
}, 10);
}
self.watchdog.stop();
}
this.bootAudio = async function(twine) {
var channelDefaultItems = ["dcblockoutputs", "tanhoutputs", "maxundo"];
for (var i of channelDefaultItems) {
if (self.storage.hasOwnProperty(i)) {
app.setControlChannel("twst_" + i, self.storage[i]);
}
}
sr = await app.getCsound().getSr();
if (!twine) self.ui.postBoot();
};
var booted = false;
this.boot = function(twine) {
if (booted) return;
booted = true;
twirl.boot();
self.ui.boot();
Object.defineProperty(this, "waveformTab", {
get: function() { return waveformTabs[instanceIndex]; },
set: function(x) {}
});
Object.defineProperty(this, "otherInstanceNames", {
get: function() {
var data = {};
for (var i in waveformTabs) {
if (i != instanceIndex) {
data[i] = waveformTabs[i].text();
}
}
return data
},
set: function(x) {}
});
Object.defineProperty(this, "instanceIndex", {
get: function() {
return instanceIndex
},
set: function(x) {}
});
Object.defineProperty(this, "undoLevel", {
get: function() {
return undoLevels[instanceIndex];
},
set: function(x) {
undoLevels[instanceIndex] = x;
}
});
Object.defineProperty(this, "waveform", {
get: function() { return self.waveforms[instanceIndex]; },
set: function(x) {
if (instanceIndex != x) {
if (self.waveformTab) {
self.waveformTab.removeClass("wtab_selected").addClass("wtab_unselected");
}
if (self.waveform) {
self.waveform.hide();
}
var cbid = app.createCallback(function(ndata){
if (ndata.status == 1) {
instanceIndex = x;
self.waveformTab.removeClass("wtab_unselected").addClass("wtab_selected");
self.waveform.show();
if (self.currentTransform) {
self.currentTransform.refresh();
self.currentTransform.redraw(self.waveform.getRegion());
}
} else {
self.ui.showPrompt("Error changing instance");
}
});
app.insertScore("twst_setinstance", [0, 1, cbid, x]);
}
}
});
if (!twine) {
$("