diff --git a/example.maxpat b/example.maxpat index 1f3d717..1dc662b 100644 --- a/example.maxpat +++ b/example.maxpat @@ -10,7 +10,7 @@ } , "classnamespace" : "box", - "rect" : [ 2115.0, 234.0, 922.0, 715.0 ], + "rect" : [ 80.0, 96.0, 986.0, 715.0 ], "bglocked" : 0, "openinpresentation" : 0, "default_fontsize" : 12.0, @@ -53,7 +53,7 @@ , { "box" : { "data" : { - + "data" : [ [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine" ], [ "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam", "djParam" ], [ "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], [ "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], [ "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ] ] } , "id" : "obj-4", @@ -228,8 +228,8 @@ "numinlets" : 2, "numoutlets" : 1, "outlettype" : [ "" ], - "patching_rect" : [ 426.0, 69.0, 54.0, 22.0 ], - "text" : "save foo" + "patching_rect" : [ 371.0, 69.0, 104.0, 22.0 ], + "text" : "save localStorage" } } @@ -252,7 +252,7 @@ "numinlets" : 1, "numoutlets" : 1, "outlettype" : [ "" ], - "patching_rect" : [ 443.0, 124.0, 428.0, 246.0 ], + "patching_rect" : [ 319.0, 125.0, 552.0, 245.0 ], "rendermode" : 0, "url" : "file://lfogui.html" } diff --git a/lfogui.html b/lfogui.html index 1e11878..92f2b19 100644 --- a/lfogui.html +++ b/lfogui.html @@ -30,12 +30,19 @@ float: left; } - input { + + input[type=number] { width: 30px; margin: 0; padding: 0; } + input[type=text] { + width: 60px; + margin: 0; + padding: 0; + } + #matrix { background-color: aquamarine; height: 100%; @@ -65,6 +72,78 @@ width: 40%; font-size: 5vw; } + + /* The switch - the box around the slider */ + .switch { + position: relative; + display: inline-block; + width: 30px; + height: 17px; + } + + /* Hide default HTML checkbox */ + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + /* The slider */ + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; + } + + .slider:before { + position: absolute; + content: ""; + height: 13px; + width: 13px; + left: 2px; + bottom: 2px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; + } + + input:checked + .slider { + background-color: #2196F3; + } + + input:focus + .slider { + box-shadow: 0 0 1px #2196F3; + } + + input:checked + .slider:before { + -webkit-transform: translateX(13px); + -ms-transform: translateX(13px); + transform: translateX(13px); + } + + /* Rounded sliders */ + .slider.round { + border-radius: 17px; + } + + .slider.round:before { + border-radius: 50%; + } + + h5 { + margin: 0; + padding: 0; + } + + .enum-count { + background-color: aquamarine; + } diff --git a/lfogui.js b/lfogui.js index a35f3e2..8bce0f7 100644 --- a/lfogui.js +++ b/lfogui.js @@ -7,10 +7,17 @@ const e = React.createElement; let lfos = []; const MAXLFOS = 20; +const MAXENUMS = 20; +const MAXENUMPOINTS = 10; const SHAPETYPES = ["Sine", "SawUp", "SawDown", "Tri", "Square"]; const PARAMOPTIONS = ["attenuation", "melody_scope"]; +const ViewModes = Object.freeze({ + MOD: 0, + ENUM: 1 +}); + function DropDown(props) { @@ -23,13 +30,51 @@ function ListItem(child){ } function NumberBox(props){ - return e('input', {type: "number", onChange: props.onChange, value: props.value}, null); + return e('input', {type: "number", onChange: props.onChange, value: props.value, className: props.className}, null); +} + +function TextBox(props){ + return e('input', {type: "text", value: props.value}); } function Option(str){ return e("option", null, str); } +function Button(props){ + return e('button', {onClick: props.onClick}, props.text); +} + +function Switch(props){ + return e('label', {className: 'switch'}, + e('input', {type: 'checkbox', onClick: props.ontoggle}, null), + e('span', {className: 'slider round'}, null)) +} + +function CreateParamChanger(arr, setArr, index){ + return (event) => { + let newArr = arr.slice(); + newArr[index] = event.target.value; + setArr(newArr); + log(`${index} ${event.target.value}`); + } +} + +function CreateMatrixParamChanger(matrix, setMatrix, i, j){ + return (event) => { + var newMatrix = matrix.map(function(arr) { + return arr.slice(); + }); + newMatrix[i][j] = event.target.value; + setMatrix(newMatrix); + log(`${i}, ${j} ${event.target.value}`); + } +} + +///////////////////////// +// MODULATORS +///////////////////////// + function ControlType(){ return e('select', {className: 'control-type'}, Option("LFO")); } @@ -42,6 +87,8 @@ function ParamName(){ return e('select', {className: 'param-name'}, Option("djParam"), Option("melody_scope")); } + + function LfoRow(props){ let content = e('ul', {className: 'lfo-item'}, ListItem(ControlType()), @@ -55,21 +102,61 @@ function LfoRow(props){ ); if (props.visible){ return content - } - ; - - + }; } -function Button(props){ - return e('button', {onClick: props.onClick}, props.text); +///////////////////////// +// ENUMERATORS +///////////////////////// + + +// NOT A REACT FUNCTIONAL COMPONENT. MERELY RETURNS AN ARRAY WHICH IS UNPACKED +function EnumeratorItems(index, enumBreakPoints, setEnumBreakPoints, enumNames){ + let items = [] + + for (let i = 0; i < MAXENUMPOINTS; i++){ + log("hii") + log(enumNames[i]) + items.push(ListItem(e(TextBox, {value: enumNames[i]}, null))); + // Add 1 to make up for the lower enum bound + items.push(ListItem(e(NumberBox, {onChange: CreateMatrixParamChanger(enumBreakPoints, setEnumBreakPoints, index, i + 1), value:enumBreakPoints[index][i + 1]}, null))); + } + return items; } +function EnumeratorRow(props){ + let content = e('ul', {className: 'lfo-item'}, + ListItem(DropDown({onChange: props.setDjParam, value: props.djParam, options: PARAMOPTIONS})), + ListItem(e(NumberBox, {onChange: props.setEnumItemCounts, value:props.enumItems, className: 'enum-count'}, null)), + ListItem(e(NumberBox, {onChange: CreateMatrixParamChanger(props.enumBreakPoints, props.setEnumBreakPoints, props.index, 0), value:props.enumBreakPoints[props.index][0]}, null)), + ...(EnumeratorItems(props.index, props.enumBreakPoints, props.setEnumBreakPoints, props.enumNames[props.index]).slice(0, props.enumItems * 2)), + ListItem(e(Button, {text:'+', onClick: props.addEnum}, null)), + ListItem(e(Button, {text:'-', onClick: props.removeEnum}, null)) + ); + if (props.visible){ + return content; + }; +} + + + function MasterLfoHandler(){ let initVisArr = Array(MAXLFOS).fill(false); initVisArr[0] = true; - const [visibleArr, setVisibleArr] = React.useState(initVisArr); + + const [viewMode, setViewMode] = React.useState(ViewModes.MOD); + const toggleViewMode = () => { + log("toggle"); + if (viewMode === ViewModes.MOD) + setViewMode(ViewModes.ENUM); + else + setViewMode(ViewModes.MOD); + }; + + /// MODULATOR ARRAYS + + const [modVisibleArr, setModVisibleArr] = React.useState(initVisArr); const [shapeArr, setShapeArr] = React.useState(Array(MAXLFOS).fill('Sine')); const [djParamArr, setDjParamArr] = React.useState(Array(MAXLFOS).fill('djParam')); @@ -78,9 +165,32 @@ function MasterLfoHandler(){ const [ampArr, setAmpArr] = React.useState(Array(MAXLFOS).fill('1')); const [phaseArr, setPhaseArr] = React.useState(Array(MAXLFOS).fill('0')); - const allArrays = [visibleArr, shapeArr, djParamArr, freqArr, ampArr, phaseArr]; - const allSetters = [setVisibleArr, setShapeArr, setDjParamArr, setFreqArr, setAmpArr, setPhaseArr]; - const blankVals = [true, 'Sine', '1', '1', '0']; + const allModArrays = [modVisibleArr, shapeArr, djParamArr, freqArr, ampArr, phaseArr]; + const allModSetters = [setModVisibleArr, setShapeArr, setDjParamArr, setFreqArr, setAmpArr, setPhaseArr]; + const modBlankVals = [true, 'Sine', '1', '1', '0']; + + + /// ENUMERATOR ARRAYS + const [enumItemCounts, setEnumItemCounts] = React.useState(Array(MAXENUMPOINTS).fill('2')); + + 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; + } + } + const [enumBreakPoints, setEnumBreakPoints] = React.useState(baseEnumBreakpoints); + + let baseEnumNames = Array(MAXENUMS).fill(0).map(x => Array(MAXENUMPOINTS).fill('asdf')); + for (let i = 0; i < MAXENUMS; i++){ + for (let j=0; j < MAXENUMPOINTS; j++){ + baseEnumNames[i][j] = j; + } + } + const [enumNames, setEnumNames] = React.useState(baseEnumNames); + + + React.useEffect(() => { function handleLoad(event) { @@ -90,7 +200,7 @@ function MasterLfoHandler(){ } function handleSave(event) { - window.max.setDict(event.detail, {"data" : allArrays}); + window.max.setDict(event.detail, {"data" : allModArrays}); } @@ -105,80 +215,96 @@ function MasterLfoHandler(){ }, []); - createParamChanger = (arr, setArr, index) => { - return (event) => { - let newArr = arr.slice(); - newArr[index] = event.target.value; - setArr(newArr); - log(`${index} ${event.target.value}`); - } - } + /////// + // Generate Modulators + /////// - - log("Rendering") - let contents = [] + let modContents = [] for (var i = 0; i { if (id < MAXLFOS - 1){ - if (visibleArr[id + 1]){ + if (modVisibleArr[id + 1]){ // need to delete the empty item to make room - let emptyIndex = visibleArr.findIndex((item) => !item); + let emptyIndex = modVisibleArr.findIndex((item) => !item); if (emptyIndex != -1){ - log(emptyIndex); - log(id + 1); - - for (var j = 0; j < allArrays.length; j++){ - - let array = allArrays[j]; - console.log(array); + for (var j = 0; j < allModArrays.length; j++){ + let array = allModArrays[j]; // remove from all arrays array.splice(emptyIndex, 1); - // add empty item at opened index - array.splice(id + 1, 0, blankVals[j]); - allSetters[j](array); - console.log(array); - + array.splice(id + 1, 0, modBlankVals[j]); + allModSetters[j](array); } } - - log(allArrays); } - - let newArr = visibleArr.slice(); + let newArr = modVisibleArr.slice(); newArr[id + 1] = true; - setVisibleArr(newArr); - + setModVisibleArr(newArr); } }, removeLfo: () => { - let newArr = visibleArr.slice(); + let newArr = modVisibleArr.slice(); newArr[id] = false; - setVisibleArr(newArr); + setModVisibleArr(newArr); } - }, null)); } - return e('div', null, ...contents); + + /////// + // Generate Enumerators + /////// + let enumContents = [] + for (var i = 0; i