var twineTopMenuData = [ {name: "File", contents: [ {name: "Save", disableOnPlay: true, shortcut: {name: "Ctrl S", ctrlKey: true, key: "s"}, click: function(twine) { twine.downloadExportData(); }}, ]}, {name: "Edit", contents: [ {shortcut: {name: "Ctrl Z", ctrlKey: true, key: "z"}, click: function(twine) { twine.undo.apply(); }, condition: function(twine) { return twine.undo.has(); }, name: function(twine) { return "Undo " + twine.undo.lastName(); }}, {name: "Copy", shortcut: {name: "Ctrl C", ctrlKey: true, key: "c"}, click: function(twine) { twine.copySelected(); }, condition: function(twine) { return twine.timeline.selectedClips.length > 0; }} ]}, {name: "View", contents: [ {name: "Zoom in", shortcut: {name: ",", key: ","}, click: function(twine){ twine.timeline.zoomIn(); }}, {name: "Zoom out", shortcut: {name: ".", key: "."}, click: function(twine){ twine.timeline.zoomOut(); }}, {name: "Show all", shortcut: {name: "/", key: "/"}, click: function(twine){ twine.timeline.showAll(); }}, {preset: "divider"}, {name: "Mixer", shortcut: {name: "M", key: "m"}, click: function(twine){ twine.showMixer(); }}, {name: "Contract channels", shortcut: {name: "C", key: "c"}, click: function(twine) { twine.timeline.contractChannels(); }} ]}, {name: "Action", contents: [ {name: "Play/stop", shortcut: {name: "Space", key: "space"}, click: function(twine) { twine.ui.head.play.element.click(); }}, {preset: "divider"}, {name: "Add channel", shortcut: {name: "A", key: "a"}, click: function(twine) { twine.timeline.addChannel(); }}, {name: "Add script clip", click: function(twine) { twine.timeline.addScriptClip(); }}, {name: "Add blank clip", click: function(twine) { twine.timeline.addBlankClip(); }}, {preset: "divider"}, {name: "Delete clip(s)", shortcut: {name: "Del", key: "delete"}, click: function(twine) { twine.timeline.selectedClips.forEach(function(clip){ clip.destroy(); }); twine.ui.showPane(twine.ui.pane.NONE); }, condition: function(twine){ return twine.timeline.selectedClips.length > 0; }}, {preset: "divider"}, {name: "Render to file", click: function(twine) { twine.renderToFile(); }}, {name: "Bounce", shortcut: {name: "A", key: "a"}, click: function(twine) { twine.renderToClip(); }} ]}, {name: "Options", contents: [ {name: "Settings", click: function(twine) { twine.ui.showSettings(); }} ]}, {name: "Help", contents: [ {name: "Help", click: function(twine){ $("#twist_documentation")[0].click(); }}, {name: "About", click: function(twine) { twine.ui.showAbout(); }}, ]} ]; var TwineUI = function(twine) { var ui = this; ui.topMenu = new twirl.TopMenu(twine, twineTopMenuData, $("#twine_menubar")); ui.showSettings = function() { var settings = [ { name: "Show master VU meter", description: "Show the master VU mixer in the mixer view", bool: true, storageKey: "showMasterVu" }, { name: "Show clip waveforms", description: "Show waveforms in clips", bool: true, storageKey: "showClipWaveforms" } ]; twirl.showSettings(twine, settings); }; this.pane = {NONE: -1, MIXER: 0, CHANNEL: 1, CLIPAUDIO: 2, CLIPSCRIPT: 3}; this.showPane = function(pane) { var chd = $("#twine_channeldetails"); var cld = $("#twine_clipdetails"); if (pane == ui.pane.MIXER) { twine.mixer.show(); chd.hide(); cld.hide(); } else if (pane == ui.pane.CHANNEL) { twine.mixer.hide(); chd.show(); cld.hide(); } else if (pane >= 2) { twine.mixer.hide(); chd.hide(); cld.show(); var cda = $("#twine_clipdetailsaudio"); var cds = $("#twine_clipdetailsscript"); if (pane == ui.pane.CLIPAUDIO) { cda.show(); cds.hide(); } else { cda.hide(); cds.show(); } } else if (pane == ui.pane.NONE) { twine.mixer.hide(); chd.hide(); cld.hide(); }; }; ui.showAbout = function() { var el = $("
"); var x = $("
").appendTo(el); var string = "twine"; var intervals = []; function addChar(c, left) { left = Math.min(Math.max(left, 30), 70); var elC = $("

").text(c).css({position: "absolute", left: left + "%"}).appendTo(x); var rate = (Math.random() * 0.1) + 0.15; var leftDirection = Boolean(Math.round(Math.random())); setTimeout(function(){ intervals.push(setInterval(function(){ if (leftDirection) { if (left < 70) { left += rate; } else { leftDirection = false; } } else { if (left > 30) { left -= rate; } else { leftDirection = true; } } //console.log(left, rate, leftDirection); elC.css("left", left + "%"); }, (Math.random() * 20) + 20)); }, (Math.random() * 1000) + 500); } var widthPercent = (40 / (string.length)); for (let c in string) { intervals.push(addChar(string[c], (widthPercent * c) + 30)); } $("
").appendTo(el); $("
").appendTo(el); $("

").text("Version " + twine.version.toFixed(1)).appendTo(el); $("

").css("font-size", "12px").text("By Richard Knight 2024").appendTo(el); twirl.prompt.show(el, function(){ for (let i of intervals) clearInterval(i); }); }; function refreshWarpParams() { var warp = ui.clip.warp.element.val(); var warpMode = ui.clip.warpMode.element.val(); ui.clip.warpMode.hide(); ui.clip.fftSize.hide(); ui.clip.phaseLock.hide(); ui.clip.txtWinSize.hide(); ui.clip.txtRandom.hide(); ui.clip.txtOverlap.hide(); ui.clip.txtWinType.hide(); if (!warp) return; ui.clip.warpMode.show(); if (warpMode == 1) { ui.clip.txtWinSize.show(); ui.clip.txtRandom.show(); ui.clip.txtOverlap.show(); ui.clip.txtWinType.show(); } else if (warpMode > 1) { ui.clip.fftSize.show(); ui.clip.phaseLock.show(); } } function applyValToSelectedFunc(dataKey, applyFunc, undoName, noApplyUndo) { if (!undoName) undoName = dataKey; var func = function(val) { if (!noApplyUndo) { var selected = [...twine.timeline.selectedClips]; var values = []; } twine.timeline.selectedClips.forEach(async function(clip){ if (!noApplyUndo) { values.push(clip.data[dataKey]); } applyFunc(clip, val); }); if (!noApplyUndo) { twine.undo.add("clip " + undoName, function(){ for (var i in selected) { applyFunc(selected[i], values[i]); if (twine.timeline.selectedClip == selected[i]) { ui.clip[dataKey].setValue(values[i]); } } }); } }; return func; } ui.clip = { scriptEdit: new twirl.stdui.TextArea({ target: "twine_clip_scriptedit", height: "100%", width: "100%" }), scriptAudition: new twirl.stdui.PlayButton({ target: "twine_clip_scriptaudition", change: function(v, obj) { if (obj.state == true) { } else { app.insertScore("twine_scriptstop"); } } }), scriptApply: new twirl.stdui.StandardButton({ target: "twine_clip_scriptapply", label: "Apply script", change: function() { twine.timeline.selectedClip.setScript( ui.clip.scriptEdit.element.val(), function() { twirl.prompt.show("Script successfully compiled"); } ); } }), audition: new twirl.stdui.PlayButton({ target: "twine_clip_audition", tooltip: "Audition clip", change: function(v, obj) { if (obj.state == true) { twine.timeline.selectedClip.play(function(ndata) { if (ndata.status == 0) { obj.setValue(false); } }); } else { twine.timeline.selectedClip.stop(); } } }), name: new twirl.stdui.TextInput({ target: "twine_clip_name", change: applyValToSelectedFunc("name", function(clip, val){ clip.setData("name", val); }), css: { border: "none" } }), colour: new twirl.stdui.ColourInput({ target: "twine_clip_colour", change: applyValToSelectedFunc("colour", function(clip, val){ clip.colour = val; }), css: { border: "none" } }), editTwist: new twirl.stdui.StandardButton({ label: "Twist", target: "twine_clip_edittwist", change: function(e) { twirl.contextMenu.show(e, [ {name: "Edit all references", click: function(){ twine.timeline.selectedClip.editInTwist(); }}, {name: "Edit as unique", click: function(){ twine.timeline.selectedClip.editInTwist(true); }} ]); } }), editTwigs: new twirl.stdui.StandardButton({ label: "Twigs", target: "twine_clip_edittwigs", change: function(e) { twirl.contextMenu.show(e, [ {name: "Edit all references", click: function(){ twine.timeline.selectedClip.editInTwigs(); }}, {name: "Edit as unique", click: function(){ twine.timeline.selectedClip.editInTwigs(true); }} ]); } }), warp: new twirl.stdui.StandardToggle({ label: "Warp", target: "twine_clip_warp", change: applyValToSelectedFunc("warp", function(clip, val){ clip.setWarp(val); }), stateAlter: function(val) { refreshWarpParams(); } }),/* loop: new twirl.stdui.StandardToggle({ label: "Loop", target: "twine_clip_loop", change: function(val) { twine.timeline.selectedClip.setLoop(val); } }),*/ warpMode: new twirl.stdui.ComboBox({ target: "twine_clip_warpmode", options: [ "Repitch", "Grain", "Mince" //, "FFTab" ], change: applyValToSelectedFunc("warpMode", function(clip, val){ clip.setWarpMode(val); }, "warp mode"), stateAlter: function(val) { refreshWarpParams(); } }), amp: new twirl.stdui.Slider({ label: "Gain", valueLabel: true, value: 1, min: 0, max: 2, size: 32, target: "twine_clipparamsbottom", input: applyValToSelectedFunc("amp", function(clip, val){ clip.setData("amp", val); }, "gain", true), change: applyValToSelectedFunc("amp", function(clip, val){ clip.setData("amp", val); }, "gain") }), pitch: new twirl.stdui.Slider({ label: "Pitch", valueLabel: true, min: -12, max: 12, step: 1, value: 0, size: 32, target: "twine_clipparamsbottom", input: applyValToSelectedFunc("pitch", function(clip, val){ clip.setPitch(val); }, null, true), change: applyValToSelectedFunc("pitch", function(clip, val){ clip.setPitch(val); }) }), fftSize: new twirl.stdui.ComboBox({ label: "FFT Size", asRow: true, target: "twine_clipparamsbottom", options: [ "256", "512", "1024", "2048" ], asValue: true, change: applyValToSelectedFunc("fftSize", function(clip, val){ clip.setData("fftSize", val); }, "FFT size") }), phaseLock: new twirl.stdui.StandardToggle({ label: "Phase lock", target: "twine_clipparamsbottom", change: applyValToSelectedFunc("phaseLock", function(clip, val){ clip.setData("phaseLock", val); }, "phase lock"), stateAlter: function(val) { refreshWarpParams(); } }), txtWinSize: new twirl.stdui.Slider({ label: "Window size", valueLabel: true, min: 44, max: 4410, step: 1, value: 4410, size: 32, target: "twine_clipparamsbottom", change: applyValToSelectedFunc("txtWinSize", function(clip, val){ clip.setData("txtWinSize", val); }, "window size") }), txtRandom: new twirl.stdui.Slider({ label: "Window random", valueLabel: true, min: 0, max: 441, step: 1, value: 441, size: 32, target: "twine_clipparamsbottom", change: applyValToSelectedFunc("txtRandom", function(clip, val){ clip.setData("txtRandom", val); }, "window random") }), txtOverlap: new twirl.stdui.Slider({ label: "Window overlap", valueLabel: true, min: 0, max: 16, step: 1, value: 4, size: 32, target: "twine_clipparamsbottom", change: applyValToSelectedFunc("txtOverlap", function(clip, val){ clip.setData("txtOverlap", val); }, "window overlap") }), txtWinType: new twirl.stdui.ComboBox({ label: "Window type", asRow: true, target: "twine_clipparamsbottom", options: [ "Hanning", "Hamming", "Half sine" ], change: applyValToSelectedFunc("txtWinType", function(clip, val){ clip.setData("txtWinType", val); }, "window type") }) }; ui.head = { play: new twirl.stdui.PlayButton({ target: "twine_head_play", fontsize: "14pt", change: function(v, obj) { if (obj.state == true) { twine.play(); } else { twine.stop(); obj.setValue(false); } } }), snap: new twirl.stdui.StandardToggle({ label: "Snap", target: "twine_head_snap", change: function(val) { val = (val) ? 4 : 0; twine.timeline.data.snapToGrid = val; } }), grid: new twirl.stdui.StandardToggle({ label: "Grid", target: "twine_head_showgrid", change: function(val) { twine.timeline.data.gridVisible = val; twine.timeline.redraw(); } }), name: new twirl.stdui.TextInput({ target: "twine_head_name", css: { border: "none", "font-family": "var(--fontFace)", "font-size": "var(--fontSizeLarge)", }, change: function(val) { twine.timeline.arrangementName = val; } }) }; };