From 07c4826bd921d0452fe363b38f2618b176dfd6cc Mon Sep 17 00:00:00 2001 From: Eveline-97 Date: Sun, 29 Jun 2025 17:53:12 +0200 Subject: [PATCH] table layout for modulators --- lfogui.css | 26 ++++- lfogui.js | 268 +++++++++++++++++++++++++------------------------- modulators.js | 36 +++---- 3 files changed, 178 insertions(+), 152 deletions(-) diff --git a/lfogui.css b/lfogui.css index 17f5ca3..1552124 100644 --- a/lfogui.css +++ b/lfogui.css @@ -3,12 +3,31 @@ --unlocked-color: #ff5153; } +:root { + --background: ivory; + --active: royalblue; + --nonactive: lightsteelblue; + --alert: red; + --textcolor: #737373; +} + +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +html { + background-color: var(--background); +} + +body { + font-family: "Poppins", sans-serif; +} + html, body { width: 100%; height: 100%; margin: 0px; border: 0; - overflow: hidden; /* Disable scrollbars */ + overflow-x: hidden; /* no horizontal scrollbar*/ + overflow-y: scroll; display: block; /* No floating content on sides */ } @@ -24,6 +43,9 @@ li { float: left; } +table { + overflow: scroll; +} input[type=number] { width: 50px; @@ -192,7 +214,7 @@ h5 { color : white; } -.container { +.header { display: flex; width: 100%; justify-content: space-between; diff --git a/lfogui.js b/lfogui.js index c14bb67..a3376ba 100644 --- a/lfogui.js +++ b/lfogui.js @@ -4,7 +4,7 @@ var log; if (DEBUG) log = console.log; else - log = (msg) => {window.max.outlet("debug " + msg)}; + log = (msg) => { window.max.outlet("debug " + msg) }; const e = React.createElement; @@ -17,37 +17,37 @@ const MAXUSERDEFINED = 4; const ViewModes = Object.freeze({ - MOD: 0, - ENUM: 1 + MOD: 0, + ENUM: 1 }); const LockModes = Object.freeze({ - UNLOCK: 0, - LOCK: 1 + UNLOCK: 0, + LOCK: 1 }); var modPhases = Array(MAXLFOS).fill(0); var firstUpdateTime = Date.now(); -const MODULATORLABELS = ["inst", "-type-", "---shape---", "-------param-------", "--timebase--", "-min-", "-max", "-phase-", "center"]; -const ENUMERATORLABELS = ["inst", "---parameter---", "-# points-"]; +const MODULATORLABELS = ["inst", "type", "shape", "param", "timebase", "min", "max", "phase", "center"]; +const ENUMERATORLABELS = ["inst", "parameter", "# points"]; -function parseLfoTimeNonMusical(lfoTime){ - if (lfoTime.slice(-2) == "hz"){ +function parseLfoTimeNonMusical(lfoTime) { + if (lfoTime.slice(-2) == "hz") { return parseFloat(lfoTime.slice(0, -2)); } - else if (lfoTime.slice(-2) == "ms"){ + else if (lfoTime.slice(-2) == "ms") { return 1000 / parseFloat(lfoTime.slice(0, -2)); } - else if (lfoTime.slice(-1) == "s"){ + else if (lfoTime.slice(-1) == "s") { return 1 / parseFloat(lfoTime.slice(0, -1)); } - else if ((lfoTime.match(/:/g) || []).length == 2){ + else if ((lfoTime.match(/:/g) || []).length == 2) { return 1 / moment.duration(lfoTime).asSeconds(); } - else if ((lfoTime.match(/\./g) || []).length == 2){ + else if ((lfoTime.match(/\./g) || []).length == 2) { return 0; // ignore musical timings } else { @@ -55,12 +55,12 @@ function parseLfoTimeNonMusical(lfoTime){ } } -function nnFreqToHzString(num){ +function nnFreqToHzString(num) { return `${num}hz`; } -function MasterLfoHandler(){ +function MasterLfoHandler() { let initVisArr = Array(MAXLFOS).fill(false); initVisArr[0] = true; @@ -84,8 +84,8 @@ function MasterLfoHandler(){ let userDefinedWavesBase = []; let userDefinedFunctionsBase = []; let userDefinedTypesBase = [0, 0, 0, 0]; //0 = wave, 1 = function - - for (let i=0; i Array(MAXENUMPOINTS+ 1).fill(0)); - for (let i = 0; i < MAXENUMS; i++){ - for (let j=0; j < MAXENUMPOINTS + 1; j++){ + let baseEnumBreakpoints = Array(MAXENUMS).fill(0).map(x => Array(MAXENUMPOINTS + 1).fill(0)); + for (let i = 0; i < MAXENUMS; i++) { + for (let j = 0; j < MAXENUMPOINTS + 1; j++) { baseEnumBreakpoints[i][j] = j - 0.5; } } - const [enumBreakPoints, setEnumBreakPoints] = React.useState(baseEnumBreakpoints); + const [enumBreakPoints, setEnumBreakPoints] = React.useState(baseEnumBreakpoints); const getBlankEnumBreakPointRow = () => { let arr = [] - for (let i=0; i< MAXENUMPOINTS + 1; i++) + for (let i = 0; i < MAXENUMPOINTS + 1; i++) arr.push(i - 0.5) return arr; } - const getBlankEnumNameRow = () => {return Array(MAXENUMPOINTS).fill('param')}; + const getBlankEnumNameRow = () => { return Array(MAXENUMPOINTS).fill('param') }; let baseEnumNames = Array(MAXENUMS).fill(0).map(x => Array(MAXENUMPOINTS).fill('param')); - const [enumNames, setEnumNames] = React.useState(baseEnumNames); + const [enumNames, setEnumNames] = React.useState(baseEnumNames); const allEnumArrays = [enumVisibleArr, enumInstanceNumArr, enumItemCounts, enumDjParamArr]; const allEnumArrSetters = [setEnumVisibleArr, setEnumInstanceNumArr, setEnumItemCounts, setEnumDjParamArr]; @@ -165,14 +165,14 @@ function MasterLfoHandler(){ React.useEffect(() => { function handleLoad(event) { window.max.getDict(event.detail, (dict) => { - - for (let i = 0; i { allNNData.push(parseLfoTimeNonMusical(element)); - }); + }); allNNData = allNNData.concat(minArr); allNNData = allNNData.concat(maxArr); allNNData = allNNData.concat(initPhaseArr); let lfoMatchedCenterVals = []; - for (let i=0; i { - - if (id < MAXLFOS - 1){ - - if (modVisibleArr[id + 1]){ - + + if (id < MAXLFOS - 1) { + + if (modVisibleArr[id + 1]) { + let emptyIndex = modVisibleArr.findIndex((item) => !item); - if (emptyIndex != -1){ - for (var j = 0; j < allModArrays.length; j++){ + if (emptyIndex != -1) { + for (var j = 0; j < allModArrays.length; j++) { let array = allModArrays[j]; // remove from all arrays array.splice(emptyIndex, 1); @@ -432,27 +432,27 @@ function MasterLfoHandler(){ } } else { - - for (var j = 0; j < allModArrays.length; j++){ // no space below, easy. + + for (var j = 0; j < allModArrays.length; j++) { // no space below, easy. let array = allModArrays[j]; array[id + 1] = modBlankVals[j]; allModSetters[j](array); } } rerender(!render); - + } }, removeLfo: () => { - if (modVisibleArr.filter(x=>x).length > 1){ + if (modVisibleArr.filter(x => x).length > 1) { let newArr = modVisibleArr.slice(); newArr[id] = false; setModVisibleArr(newArr); } - + } - }, - null)); + }, + null)); } @@ -460,13 +460,13 @@ function MasterLfoHandler(){ // Generate Enumerators /////// let enumContents = [] - for (var i = 0; i { - if (id < MAXLFOS - 1){ - if (enumVisibleArr[id + 1]){ // if we need to open up space + if (id < MAXLFOS - 1) { + if (enumVisibleArr[id + 1]) { // if we need to open up space let emptyIndex = enumVisibleArr.findIndex((item) => !item); - if (emptyIndex != -1){ - for (var j = 0; j < allEnumArrays.length; j++){ + if (emptyIndex != -1) { + for (var j = 0; j < allEnumArrays.length; j++) { let array = allEnumArrays[j]; // remove from all arrays array.splice(emptyIndex, 1); @@ -494,7 +494,7 @@ function MasterLfoHandler(){ // Now do the same with matrices - for (var j = 0; j < allEnumMats.length; j++){ + for (var j = 0; j < allEnumMats.length; j++) { let mat = allEnumMats[j]; mat.splice(emptyIndex, 1); mat.splice(id + 1, 0, 0); @@ -504,15 +504,15 @@ function MasterLfoHandler(){ } } else { - for (var j = 0; j < allEnumArrays.length; j++){ + for (var j = 0; j < allEnumArrays.length; j++) { let array = allEnumArrays[j]; - array[id+1] = enumBlankVals[j]; + array[id + 1] = enumBlankVals[j]; allEnumArrSetters[j](array); } // Now do the same with matrices - for (var j = 0; j < allEnumMats.length; j++){ + for (var j = 0; j < allEnumMats.length; j++) { let mat = allEnumMats[j]; mat[id + 1] = allGetEnumMatBlankVals[j](); allEnumMatSetters[j](mat); @@ -522,7 +522,7 @@ function MasterLfoHandler(){ } }, removeEnum: () => { - if (enumVisibleArr.filter(x=>x).length > 1){ + if (enumVisibleArr.filter(x => x).length > 1) { let newArr = enumVisibleArr.slice(); newArr[id] = false; setEnumVisibleArr(newArr); @@ -536,7 +536,7 @@ function MasterLfoHandler(){ var grid; var title; var labels; - if (viewMode === ViewModes.MOD){ + if (viewMode === ViewModes.MOD) { grid = modContents; title = "MODULATORS"; labels = MODULATORLABELS; @@ -548,31 +548,33 @@ function MasterLfoHandler(){ } let lockClass; - if (lockMode == LockModes.LOCK){ + if (lockMode == LockModes.LOCK) { lockClass = 'lock'; - } + } else { lockClass = 'lock unlocked'; } - return e('div', null, - e('div', {className: 'container'}, - e(Switch, {ontoggle: toggleViewMode}, null), - e('span', {className: lockClass, onClick: toggleLockMode}, null)), - - e('h5', null, title), - e('ul', null, ...labels.map(x => ListItem(Label(x)))), - e('div', {id: 'grid'}, ...grid) + return e('div', null, + e('div', { className: 'header' }, + e(Switch, { ontoggle: toggleViewMode }, null), + e('span', { className: lockClass, onClick: toggleLockMode }, null)), + + //e('h5', null, title), + e('table', { id: 'table' }, + e('thead', null, e('tr', { id: 'headers' }, ...labels.map(x => e('th', null, x)))), + e('tbody', null, ...grid) + ) ); } -if (!DEBUG){ +if (!DEBUG) { window.max.bindInlet("load", (dictId) => { - window.dispatchEvent(new CustomEvent('loadDict', {'detail' : dictId})); + window.dispatchEvent(new CustomEvent('loadDict', { 'detail': dictId })); }); - + window.max.bindInlet("save", (dictId) => { - window.dispatchEvent(new CustomEvent('saveDict', {'detail' : dictId})); + window.dispatchEvent(new CustomEvent('saveDict', { 'detail': dictId })); }); window.max.bindInlet("dumpNN", () => { @@ -580,35 +582,35 @@ if (!DEBUG){ }); window.max.bindInlet("setNN", (...data) => { - window.dispatchEvent(new CustomEvent('setNN', {'detail' : data})); + window.dispatchEvent(new CustomEvent('setNN', { 'detail': data })); }); window.max.bindInlet("param", (inst, paramName, val) => { - window.dispatchEvent(new CustomEvent('param', {'detail' : [inst, paramName, val]})); + window.dispatchEvent(new CustomEvent('param', { 'detail': [inst, paramName, val] })); }); window.max.bindInlet("timesig", (top, bottom) => { - window.dispatchEvent(new CustomEvent('timesig', {'detail' : [top, bottom]})); + window.dispatchEvent(new CustomEvent('timesig', { 'detail': [top, bottom] })); }); window.max.bindInlet("ticks", (val) => { - window.dispatchEvent(new CustomEvent('maxTicks', {'detail' : val})); + window.dispatchEvent(new CustomEvent('maxTicks', { 'detail': val })); }); window.max.bindInlet("userWave", (index, ...points) => { - let data = {points, index}; - window.dispatchEvent(new CustomEvent('userWave', {'detail' : data})); + let data = { points, index }; + window.dispatchEvent(new CustomEvent('userWave', { 'detail': data })); }); - + window.max.bindInlet("userFunction", (index, ...points) => { //list of 101 points between 0-100 - let data = {points, index}; - window.dispatchEvent(new CustomEvent('userFunction', {'detail': data})); + let data = { points, index }; + window.dispatchEvent(new CustomEvent('userFunction', { 'detail': data })); }); window.max.bindInlet("userDefinedType", (index, type) => { - let data = {index, type}; - window.dispatchEvent(new CustomEvent('userDefinedType', {'detail': data})); + let data = { index, type }; + window.dispatchEvent(new CustomEvent('userDefinedType', { 'detail': data })); }) setInterval(() => { diff --git a/modulators.js b/modulators.js index 453e0a6..4342df8 100644 --- a/modulators.js +++ b/modulators.js @@ -22,7 +22,9 @@ function ControlType(){ return e('select', {className: 'control-type'}, Option("LFO")); } - +function DataCell(element) { + return e('td', null, element); +} function LfoRow(props){ @@ -34,27 +36,27 @@ function LfoRow(props){ let typeOption = null; if (props.type == "LFO"){ - typeOption = ListItem(DropDown({locked:props.locked, onChange: props.setShape, value:props.shape, options: SHAPETYPES})); + typeOption = DataCell(DropDown({locked:props.locked, onChange: props.setShape, value:props.shape, options: SHAPETYPES})); } else if (props.type == "Noise"){ - typeOption = ListItem(DropDown({locked:props.locked, onChange: props.setNoise, value:props.noise, options: NOISETYPES})); + typeOption = DataCell(DropDown({locked:props.locked, onChange: props.setNoise, value:props.noise, options: NOISETYPES})); } - let content = e('ul', {className: 'lfo-item'}, - ListItem(DropDown({locked:props.locked, onChange: props.setInstanceNum, value:props.instanceNum, options: INSTANCEOPTIONS})), - ListItem(DropDown({locked:props.locked, options: TYPEOPTIONS, onChange: props.setType, value:props.type})), + let content = e('tr', {className: 'lfo-item'}, + DataCell(DropDown({locked:props.locked, onChange: props.setInstanceNum, value:props.instanceNum, options: INSTANCEOPTIONS})), + DataCell(DropDown({locked:props.locked, options: TYPEOPTIONS, onChange: props.setType, value:props.type})), typeOption, - ListItem(DropDown({locked:props.locked, onChange: props.setDjParam, value: props.djParam, options: MODPARAMOPTIONS})), - ListItem(e("input", {onChange:props.setFreq, value:props.freq, className:"timeInput"}, null)), - ListItem(e(NumberBox, {onChange:props.setMin, value:props.min, step:0.1}, null)), - ListItem(e(NumberBox, {onChange:props.setMax, value:props.max, step:0.1}, null)), - //ListItem(e(NumberBox, {onChange:props.setAmp, value:props.amp, step:0.1}, null)), - ListItem(e(NumberBox, {onChange:props.setPhase, value:props.phase, step:0.1}, null)), - ListItem(e("div", {className:"base-val"}, center.toString())), - ListItem(e("input", {type: 'range', min: 0, max: 1, step: 0.01, readonly: true, id: `slider-${props.instanceNum}-${props.djParam}`})), - ListItem(e(Button, {text:'+', onClick: props.addLfo, locked: props.locked}, null)), - ListItem(e(Button, {text:'-', onClick: props.removeLfo, locked: props.locked}, null)), - ListItem(e("div", {className:"linked"}, linkedText)), + DataCell(DropDown({locked:props.locked, onChange: props.setDjParam, value: props.djParam, options: MODPARAMOPTIONS})), + DataCell(e("input", {onChange:props.setFreq, value:props.freq, className:"timeInput"}, null)), + DataCell(e(NumberBox, {onChange:props.setMin, value:props.min, step:0.1}, null)), + DataCell(e(NumberBox, {onChange:props.setMax, value:props.max, step:0.1}, null)), + //DataCell(e(NumberBox, {onChange:props.setAmp, value:props.amp, step:0.1}, null)), + DataCell(e(NumberBox, {onChange:props.setPhase, value:props.phase, step:0.1}, null)), + DataCell(e("div", {className:"base-val"}, center.toString())), + DataCell(e("input", {type: 'range', min: 0, max: 1, step: 0.01, readonly: true, id: `slider-${props.instanceNum}-${props.djParam}`})), + DataCell(e(Button, {text:'+', onClick: props.addLfo, locked: props.locked}, null)), + DataCell(e(Button, {text:'-', onClick: props.removeLfo, locked: props.locked}, null)), + DataCell(e("div", {className:"linked"}, linkedText)), ); if (props.visible){ return content;