aboutsummaryrefslogtreecommitdiff
path: root/site/app/twine/twine_ui.js
diff options
context:
space:
mode:
authorRichard <q@1bpm.net>2025-04-13 18:48:02 +0100
committerRichard <q@1bpm.net>2025-04-13 18:48:02 +0100
commit9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 (patch)
tree291bd79ce340e67affa755a8a6b4f6a83cce93ea /site/app/twine/twine_ui.js
downloadapps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.gz
apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.bz2
apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.zip
initial
Diffstat (limited to 'site/app/twine/twine_ui.js')
-rw-r--r--site/app/twine/twine_ui.js506
1 files changed, 506 insertions, 0 deletions
diff --git a/site/app/twine/twine_ui.js b/site/app/twine/twine_ui.js
new file mode 100644
index 0000000..3a98c75
--- /dev/null
+++ b/site/app/twine/twine_ui.js
@@ -0,0 +1,506 @@
+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 = $("<div />");
+ var x = $("<div />").appendTo(el);
+ var string = "twine";
+ var intervals = [];
+
+ function addChar(c, left) {
+ left = Math.min(Math.max(left, 30), 70);
+ var elC = $("<h2 />").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));
+ }
+
+ $("<br />").appendTo(el);
+ $("<br />").appendTo(el);
+ $("<p />").text("Version " + twine.version.toFixed(1)).appendTo(el);
+ $("<p />").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;
+ }
+ })
+ };
+}; \ No newline at end of file