diff options
author | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
---|---|---|
committer | Richard <q@1bpm.net> | 2025-04-13 18:48:02 +0100 |
commit | 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 (patch) | |
tree | 291bd79ce340e67affa755a8a6b4f6a83cce93ea /site/app/twigs/twigs_ui.js | |
download | apps.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/twigs/twigs_ui.js')
-rw-r--r-- | site/app/twigs/twigs_ui.js | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/site/app/twigs/twigs_ui.js b/site/app/twigs/twigs_ui.js new file mode 100644 index 0000000..1186dac --- /dev/null +++ b/site/app/twigs/twigs_ui.js @@ -0,0 +1,577 @@ +var twigsTopMenuData = [
+ {name: "File", contents: [
+ {name: "New", disableOnPlay: true, shortcut: {name: "Ctrl N", ctrlKey: true, key: "n"}, click: function(twigs) {
+ twigs.createNewInstance();
+ }},
+ {name: "Save", disableOnPlay: true, shortcut: {name: "Ctrl S", ctrlKey: true, key: "s"}, click: function(twigs) {
+ twigs.saveFile();
+ }},
+ {name: "Close", disableOnPlay: true, shortcut: {name: "Ctrl W", ctrlKey: true, key: "w"}, click: function(twigs) {
+ twigs.closeInstance();
+ }, condition: function(twist) {
+ return (!twist.twine);
+ }},
+ {name: "Edit in twist", click: function(twigs) {
+ twigs.editInTwist();
+ }, condition: function(twigs) {
+ return window.hasOwnProperty("Twist");
+ }}
+ ]},
+ {name: "Edit", contents: [
+ {name: "Undo", disableOnPlay: true, shortcut: {name: "Ctrl Z", ctrlKey: true, key: "z"}, click: function(twigs) {
+ twigs.undo();
+ }, condition: function(twigs) {
+ return (twigs.storage.maxundo > 0 && twigs.undoLevel > 0);
+ }}
+ ]},
+ {name: "View", contents: [
+ {name: "Contract channels", shortcut: {name: "C", key: "c"}, click: function(twigs) {
+ twigs.timeline.contractChannels();
+ }}
+ ]},
+ {name: "Action", contents: []},
+ {name: "Options", contents: [
+ {name: "Settings", click: function(twigs) {
+ twigs.ui.showSettings();
+ }}
+ ]},
+ {name: "Help", contents: [
+ {name: "Help", click: function(twigs){
+ $("#twigs_documentation")[0].click();
+ }},
+ {name: "Report bug", click: function(twist){
+ $("#twigs_reportbug")[0].click();
+ }},
+ {name: "Contact owner", click: function(twist){
+ $("#twigs_contact")[0].click();
+ }},
+ {name: "About", click: function(twigs) {
+ twigs.ui.showAbout();
+ }},
+ ]}
+];
+
+var TwigsUI = function(twigs) {
+ var ui = this;
+ var el = $("#twigs_sidebar");
+ var elEditor = $("#twigs_editor_inner");
+
+ ui.showLoadFileFFTPrompt = function(onComplete) {
+ var t = $("<table />");
+ var tb = $("<tbody />").appendTo(t);
+ var ksmps = 64;
+
+ var tr = $("<tr />").appendTo(tb);
+ $("<td />").text("FFT size").appendTo(tr);
+
+ var fftSize = $("<select />").change(function(){
+ updateDecimation();
+ });
+ $("<td />").appendTo(tr).append(fftSize);
+ for (let o of [256, 512, 1024, 2048]) {
+ $("<option />").val(o).text(o).appendTo(fftSize);
+ }
+
+ tr = $("<tr />").appendTo(tb);
+ $("<td />").text("FFT decimation").appendTo(tr);
+ var fftDecim = $("<select />");
+ $("<td />").appendTo(tr).append(fftDecim);
+
+ function updateDecimation() {
+ fftDecim.empty();
+ var max = fftSize.val() / 64;
+ var min = max / 2;
+ for (let o of [min, max]) {
+ $("<option />").val(o).text(o).appendTo(fftDecim);
+ }
+ }
+ twirl.prompt.show(t, function() {
+ onComplete(fftSize.val(), fftDecim.val());
+ });
+ fftSize.val(512);
+ updateDecimation();
+ };
+
+
+ function addActionMenu(menu, item) {
+ for (let i in twigsTopMenuData) {
+ if (twigsTopMenuData[i].name.toLowerCase() == menu.toLowerCase()) {
+ twigsTopMenuData[i].contents.push(item);
+ }
+ }
+ }
+
+ var zoomIcons = [
+ {
+ label: "Zoom in frequency",
+ icon: "zoomIn",
+ size: 20,
+ click: function() {
+ twigs.vzoomIn();
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_vzoom"
+ },
+ {
+ label: "Zoom out frequency",
+ icon: "zoomOut",
+ size: 20,
+ click: function() {
+ twigs.vzoomOut();
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_vzoom"
+ },
+ {
+ label: "Show all frequency",
+ icon: "showAll",
+ size: 20,
+ click: function() {
+ twigs.setFrequencyRegion(0, 1);
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_vzoom"
+ },
+ {
+ label: "Zoom in time",
+ icon: "zoomIn",
+ size: 20,
+ click: function() {
+ twigs.hzoomIn();
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_hzoom"
+ },
+ {
+ label: "Zoom out time",
+ icon: "zoomOut",
+ size: 20,
+ click: function() {
+ twigs.hzoomOut();
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_hzoom"
+ },
+ {
+ label: "Show all time",
+ icon: "showAll",
+ size: 20,
+ click: function() {
+ twigs.setTimeRegion(0, 1);
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_hzoom"
+ },
+ {
+ label: "Show all",
+ icon: "showAll",
+ size: 20,
+ click: function() {
+ twigs.setFrequencyRegion(0, 1, true);
+ twigs.setTimeRegion(0, 1);
+ },
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "view",
+ target: "#twigs_editor_hzoom"
+ }
+
+ ];
+
+
+ this.showAbout = function() {
+ var el = $("<div />");
+ var x = $("<div />").appendTo(el);
+ var string = "twigs";
+ var intervals = [];
+
+ function addChar(c) {
+ var elC = $("<h2 />").text(c).css("display", "inline-block").appendTo(x);
+ var rate = (Math.random() * 0.005) + 0.001;
+ var scale = 1;
+ var scaleDirection = false;
+ return setInterval(function(){
+ if (scaleDirection) {
+ if (scale < 1) {
+ scale += rate;
+ } else {
+ scaleDirection = false;
+ }
+ } else {
+ if (scale > 0.05) {
+ scale -= rate;
+ } else {
+ scaleDirection = true;
+ }
+ }
+ elC.css("transform", "scaleX(" + scale + ")");
+ }, (Math.random() * 10) + 8);
+ }
+ for (let c of string) {
+ intervals.push(addChar(c));
+ }
+
+ $("<p />").text("Version " + twigs.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);
+ });
+ };
+
+ this.showSettings = function() {
+ var settings = [
+ {
+ name: "Undo levels",
+ description: "Number of undo levels stored. Large numbers may affect memory usage",
+ min: 0, max: 32, step: 1, dfault: 2, storageKey: "maxundo",
+ onChange: function(val) {
+ app.setControlChannel("twgs_maxundo", val);
+ }
+ },
+ {
+ name: "Resynthesis type",
+ description: "Type of resynthesis to be used",
+ options: ["Overlap-add", "Additive"],
+ dfault: 0,
+ storageKey: "resynthType"
+ },
+ {
+ name: "Colour type",
+ description: "Type of colouration to use in graphing",
+ options: ["Monochrome", "Colour"],
+ dfault: 0,
+ storageKey: "colourType",
+ onChange: function() {
+ twigs.redraw();
+ }
+ },
+ {
+ name: "Graph type",
+ description: "Approach to graphing partials used",
+ options: ["Joined line", "Separate lines"],
+ dfault: 0,
+ storageKey: "graphType",
+ onChange: function() {
+ twigs.redraw();
+ }
+ },
+ {
+ name: "Basic lines",
+ description: "Show thin, basic lines in graphing",
+ bool: true,
+ dfault: 0,
+ storageKey: "basicLines",
+ onChange: function() {
+ twigs.redraw();
+ }
+ },
+ {
+ name: "Frequency gridlines",
+ description: "Draw the vertical frequency grid",
+ bool: true,
+ dfault: 1,
+ storageKey: "drawFrequencyGrid",
+ onChange: function() {
+ twigs.redraw();
+ }
+ },
+ {
+ name: "Time gridlines",
+ description: "Draw the horizontal time grid",
+ bool: true,
+ dfault: 1,
+ storageKey: "drawTimeGrid",
+ onChange: function() {
+ twigs.redraw();
+ }
+ },
+ {
+ name: "Zoom to start on load",
+ description: "Zoom to the start portion of time and frequency when loading a new file",
+ bool: true,
+ dfault: 1,
+ storageKey: "zoomOnLoad"
+ }
+ ];
+ twirl.showSettings(twigs, settings);
+ };
+
+ var icon_groups = {};
+
+ ui.setSelectionMode = function(mode, iconObj) {
+ for (let s of icon_groups.selection) s.setActive(false);
+ iconObj.setActive(true);
+ elEditor.css("cursor", iconObj.definition.cursor);
+ twigs.setSelectionMode(mode);
+ }
+
+ function setOptionsArea(el) {
+ var o = $("#twigs_options").empty();
+ if (el) o.append(el);
+ }
+
+ ui.icons = {};
+
+ var icons = [
+ [{
+ label: "Select single bin",
+ icon: "pointer",
+ shortcut: {name: "Q", key: "q"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "default",
+ bgColor: 1,
+ clickOnInactive: true,
+ click: function(obj) {
+ ui.setSelectionMode(twigs.SELECTIONMODE.singleBin, obj);
+ }
+ },
+ {
+ label: "Select area",
+ icon: "areaSelect",
+ shortcut: {name: "W", key: "w"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "crosshair",
+ bgColor: 1,
+ clickOnInactive: true,
+ click: function(obj) {
+ ui.setSelectionMode(twigs.SELECTIONMODE.dragArea, obj);
+ }
+ }],
+ [{
+ label: "Select bins",
+ icon: "verticalArrows",
+ shortcut: {name: "A", key: "a"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "vertical-text",
+ bgColor: 1,
+ clickOnInactive: true,
+ click: function(obj) {
+ ui.setSelectionMode(twigs.SELECTIONMODE.dragBins, obj);
+ }
+ },
+ {
+ label: "Free select",
+ icon: "lasso",
+ shortcut: {name: "S", key: "s"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "crosshair",
+ bgColor: 1,
+ clickOnInactive: true,
+ click: function(obj) {
+ ui.setSelectionMode(twigs.SELECTIONMODE.lasso, obj);
+ }
+ }],
+ [{
+ label: "Bin append select",
+ icon: "waves",
+ shortcut: {name: "Z", key: "z"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "copy",
+ bgColor: 1,
+ clickOnInactive: true,
+ click: function(obj) {
+ ui.setSelectionMode(twigs.SELECTIONMODE.binAppend, obj);
+ }
+ },
+ {
+ label: "Play selection",
+ icon: "ear",
+ shortcut: {name: "X", key: "x"},
+ menuAdd: "action",
+ bgColor: 2,
+ click: function(obj) {
+ twigs.play(true);
+ }
+ }],
+ [{
+ label: "Move",
+ icon: "move",
+ shortcut: {name: "E", key: "e"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "grab",
+ bgColor: 3,
+ clickOnInactive: true,
+ click: function(obj) {
+ ui.setSelectionMode(twigs.SELECTIONMODE.move, obj);
+ },
+ optionsArea: function() {
+ if (!twigs.storage.movementType) twigs.storage.movementType = 2;
+ if (!twigs.storage.interpolateVoid) twigs.storage.interpolateVoid = 1;
+ if (!twigs.storage.interpolateRatio) twigs.storage.interpolateRatio = 0;
+ var el = $("<div />");
+ var typeOptions = new twirl.transform.Transform({host: twigs, element: el, definition: {
+ name: "Movement",
+ instr: "twgs_movement",
+ parameters: [
+ {name: "Movement type", description: "Type of movement to apply", channel: "twgs_movementtype", absolutechannel: true, options: ["Copy", "Leave void", "Retain amp/freq in void", "Retain amp in void", "Retain freq in void"], automatable: false, dfault: twigs.storage.movementType, onChange: function(val){
+ twigs.storage.movementType = val;
+ twigs.saveStorage();
+ }},
+ {name: "Interpolate void", description: "Interpolate values in the void created by the movement", channel: "twgs_interpolatevoid", absolutechannel: true, min: 0, max: 1, step: 1, automatable: false, dfault: twigs.storage.interpolateVoid, onChange: function(val){
+ twigs.storage.interpolateVoid = val;
+ twigs.saveStorage();
+ }, conditions: [{channel: "twgs_movementtype", absolutechannel: true, operator: "ge", value: 2}]},
+ {name: "Interpolation ratio", description: "Ratio of interpolation to integrate with target position", channel: "twgs_interpolateratio", absolutechannel: true, min: 0, max: 0.45, automatable: false, dfault: twigs.storage.interpolateRatio, onChange: function(val){
+ twigs.storage.interpolateRatio = val;
+ twigs.saveStorage();
+ }}
+ ]
+ }});
+ return el;
+ }
+ },
+ {
+ label: "Transpose",
+ icon: "arrowsUpDown",
+ shortcut: {name: "R", key: "r"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "row-resize",
+ bgColor: 3,
+ clickOnInactive: true,
+ click: function(obj) {
+ setSelectionMode(twigs.SELECTIONMODE.transpose, obj);
+ }
+ }],
+ [{
+ label: "Amplify",
+ icon: "fileVolume",
+ shortcut: {name: "D", key: "d"},
+ menuAdd: "action",
+ bgColor: 3,
+ click: function(obj) {
+ if (!twigs.hasSelection) return;
+ var el = $("<div />");
+ $("<h4 />").text("Amplitude scale").appendTo(el);
+ var amp = $("<input />").attr("type", "range").attr("max", 10).attr("min", 0).attr("step", 0.000001).val(1).appendTo(el);
+ twirl.prompt.show(el, function(){
+ twigs.selectionOperation.amplify(amp.val());
+ });
+ }
+ },
+ {
+ label: "Draw",
+ icon: "pencil",
+ shortcut: {name: "F", key: "f"},
+ menuAdd: "action",
+ group: "selection",
+ cursor: "row-resize",
+ bgColor: 3,
+ clickOnInactive: true,
+ click: function(obj) {
+ setSelectionMode(twigs.SELECTIONMODE.draw, obj);
+ }
+ }],
+ [{
+ label: "Play",
+ icon: "play",
+ shortcut: {name: "Space", key: "space"},
+ menuAdd: "action",
+ bgColor: 2,
+ click: function(obj) {
+ twigs.play();
+ }
+ },
+ {
+ label: "Stop",
+ icon: "stop",
+ bgColor: 2,
+ click: function(obj) {
+ twigs.stop();
+ }
+ }],
+ [{
+ label: "Zoom in amplitude",
+ icon: "brightnessIncrease",
+ shortcut: {name: "C", key: "c"},
+ menuAdd: "action",
+ bgColor: 1,
+ click: function(obj) {
+ twigs.increaseAmpScaling();
+ }
+ },
+ {
+ label: "Zoom out amplitude",
+ icon: "brightnessDecrease",
+ shortcut: {name: "V", key: "v"},
+ menuAdd: "action",
+ bgColor: 1,
+ click: function(obj) {
+ twigs.decreaseAmpScaling();
+ }
+ }]
+ ];
+
+ function addIcon(def) {
+ let ops = {};
+ Object.assign(ops, def);
+ if (ops.shortcut) {
+ ops.label += " (" + ops.shortcut.name + ")";
+ }
+ ops.click = function(obj) {
+ def.click(obj);
+ if (def.optionsArea) {
+ setOptionsArea(def.optionsArea())
+ } else {
+ setOptionsArea();
+ }
+ }
+ let icon = twirl.createIcon(ops);
+ if (ops.menuAdd) {
+ let menuData = {
+ name: ops.label,
+ click: function() {
+ icon.el.click();
+ }
+ };
+ if (ops.shortcut) {
+ menuData.shortcut = ops.shortcut;
+ }
+ addActionMenu(ops.menuAdd, menuData);
+ }
+ return icon;
+ }
+
+ function create() {
+ for (let z of zoomIcons) {
+ var icon = addIcon(z);
+ $(z.target).append(icon.el);
+ }
+
+ var tb = $("<tbody />").appendTo($("<table />").appendTo(el));
+ var icol = 0;
+ var first;
+ for (let row of icons) {
+ var tr = $("<tr />").appendTo(tb);
+ for (let col of row) {
+ let icon = addIcon(col);
+ if (!first) first = icon;
+ if (col.group) {
+ if (!icon_groups[col.group]) icon_groups[col.group] = [];
+ icon_groups[col.group].push(icon);
+ }
+ var td = $("<td />").append(icon.el).appendTo(tr);
+ if (col.bgColor) {
+ td.css("background-color", "var(--bgColor" + col.bgColor + ")");
+ }
+ icons[col.key] = icon;
+ }
+ }
+ first.click();
+ }
+
+ create();
+ var topMenu = new twirl.TopMenu(twigs, twigsTopMenuData, $("#twigs_menubar"));
+};
\ No newline at end of file |