var TwistInstance = function(index, twist) { var self = this; this.appdata = appdata; this.waveform = null; var waveformFile; var waveformTab; this.onPlays = []; var sr = 44100; var undoLevel; function pushOperationLog(operation) { var max = twist.storage.commitHistoryLevel; if (!max) { twist.storage.commitHistoryLevel = max = 16; } if (operationLog.length + 1 >= max) { operationLog.shift(); } operationLog.push(operation); } this.redraw = function() { self.waveform.redraw(); }; this.close = function() { self.waveform.destroy(); delete self.waveform; }; this.show = function() { self.waveform.show(); }; this.movePlayhead = function() { }; function removeInstance(i) { if (i < 0 || i > this.waveforms.length - 2) { return; } self.waveform.destroy(); if (instanceIndex == i) { instanceIndex = i + ((i == 0) ? 1 : -1); self.waveform.show(); } } this.undo = function() { if (playing) return; self.waveform.cover(true); operation("twst_undo", globalCallbackHandler, true, null, true); }; this.cut = function() { if (playing) return; self.waveform.cover(true); operation("twst_cut", globalCallbackHandler, true); }; this.delete = function() { if (playing) return; self.waveform.cover(true); operation("twst_delete", globalCallbackHandler, true); }; this.copy = function() { if (playing) return; self.waveform.cover(true); operation("twst_copy", null, true); }; this.paste = function() { if (playing) return; self.waveform.cover(true); operation("twst_paste", globalCallbackHandler, true); // keep original play position / offset new }; this.moveToStart = function() { if (playing) return; self.waveform.setSelection(0); }; this.moveToEnd = function() { if (playing) return; self.waveform.setSelection(1); }; this.selectAll = function() { if (playing) return; self.waveform.setSelection(0, 1); }; this.selectNone = function() { if (playing) return; self.waveform.setSelection(0); }; this.selectToEnd = function() { if (playing) return; self.waveform.alterSelection(null, 1); } this.selectFromStart = function() { if (playing) return; self.waveform.alterSelection(0, null); } this.play = function() { if (playing) return; auditioning = false; recording = false; operation("twst_play", playPositionHandler(), false, null, true); }; this.stop = function() { if (!playing) return; self.waveform.cover(false); app.insertScore("twst_stop"); }; 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 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 compileVariScript(script, onComplete) { var cbid = app.createCallback(function(ndata){ onComplete(ndata.status == 1); // should maybe automatically refresh }); } 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 (playing) return; auditioning = false; recording = true; await app.enableAudioInput(); errorState = "Recording error"; self.waveform.cover(true); var cbid = playPositionHandler(); var s = self.waveform.selected; var items = [0, 1, cbid, s[0], s[1], s[2]]; app.insertScore("twst_record", items); }; this.audition = function() { if (playing) return; if (!self.currentTransform) { return self.play(); } self.currentTransform.saveState(); var s = self.waveform.selected; 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(); var items = [ 0, 1, cbid, s[0], s[1], s[2], self.currentTransform.instr, automating, elCrossfades[0].val(), elCrossfades[1].val() ]; app.insertScore("twst_audition", items); }, getAutomationData(s[0], s[1])); }; var scriptStack = []; function applyScript(audition) { if (playing) return; var lastData; var script = scriptStack.shift(); if (!script) { setLoadingStatus(false); if (lastData) { console.log("ass", lastData); globalCallbackHandler(lastData); } twist.setPlaying(false); return; } if (audition) auditioning = true; twist.setPlaying(true); if (script.type == "operation") { if (audition) { return self.errorHandler("Only transform scripts can be auditioned"); } self.waveform.cover(true); onComplete = (script.instr == "twst_copy") ? null : globalCallbackHandler; operation(script.instr, function(ndata){ lastData = ndata; self.setPlaying(false); applyScript(audition); }, true, script.selection); } else if (script.type == "transform") { errorState = ((audition) ? "Audition" : "Transform" ) + " commit error"; if (!audition) { 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); }); } 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] ]); }, 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); }); } else { applyScript(audition); } }; async function innerCommit() { if (playing) return; if (!self.currentTransform) return; var s = self.waveform.selected; if (!fftsizeCheck(s, self.waveform.duration)) { return self.errorHandler("Length too short for this transform"); } watchdog.start("commit"); self.setPlaying(true); setLoadingStatus(true, true); var calls = getAutomationData(s[0], s[1]); self.currentTransform.saveState(); var state = await self.currentTransform.getState(); state.type = "transform"; state.automation = calls; state.crossfades = [elCrossfades[0].val(), elCrossfades[1].val()]; state.selection = [s[0], s[1], s[2]]; state.instanceIndex = instanceIndex; pushOperationLog(state); handleAutomation(function(automating){ var cbid = app.createCallback(function(ndata) { watchdog.stop(); 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 (self.storage.autosave) { self.saveFile(null, function() { innerCommit(); }); } else { innerCommit(); } }; }; // end twist