// const { createElement } = require("./react"); const DEBUG = false; var log; if (DEBUG) log = console.log; else log = (msg) => { window.max.outlet("debug " + msg) }; const e = React.createElement; let lfos = []; const MAXLFOS = 200; const MAXENUMS = 200; const MAXENUMPOINTS = 10; const MAXUSERDEFINED = 4; const ViewModes = Object.freeze({ MOD: 0, ENUM: 1 }); const LockModes = Object.freeze({ 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"]; function parseLfoTimeNonMusical(lfoTime) { if (lfoTime.slice(-2) == "hz") { return parseFloat(lfoTime.slice(0, -2)); } else if (lfoTime.slice(-2) == "ms") { return 1000 / parseFloat(lfoTime.slice(0, -2)); } else if (lfoTime.slice(-1) == "s") { return 1 / parseFloat(lfoTime.slice(0, -1)); } else if ((lfoTime.match(/:/g) || []).length == 2) { return 1 / moment.duration(lfoTime).asSeconds(); } else if ((lfoTime.match(/\./g) || []).length == 2) { return 0; // ignore musical timings } else { return 0; } } function nnFreqToHzString(num) { return `${num}hz`; } function MasterLfoHandler() { let initVisArr = Array(MAXLFOS).fill(false); initVisArr[0] = true; const [viewMode, setViewMode] = React.useState(ViewModes.MOD); const [lockMode, setLockMode] = React.useState(LockModes.UNLOCK); const toggleLockMode = () => { if (lockMode === LockModes.UNLOCK) setLockMode(LockModes.LOCK); else setLockMode(LockModes.UNLOCK); }; const [enabled, setEnabled] = React.useState(false); const toggleEnabled = () => { setEnabled(!enabled); }; const displayIfEnabled = (content) => { if (enabled) return content } let toggleEnabledText = enabled ? "Hide" : "Show"; /// MODULATOR ARRAYS let userDefinedWavesBase = []; let userDefinedFunctionsBase = []; let userDefinedTypesBase = [0, 0, 0, 0]; //0 = wave, 1 = function for (let i = 0; i < MAXUSERDEFINED; i++) { userDefinedWavesBase.push(Array(50).fill(0)); userDefinedFunctionsBase.push(Array(101).fill(0)); } const [userDefinedWaves, setUserDefinedWaves] = React.useState(userDefinedWavesBase); const [userDefinedFunctions, setUserDefinedFunctions] = React.useState(userDefinedFunctionsBase); const [userDefinedTypes, setUserDefinedTypes] = React.useState(userDefinedTypesBase); const [modVisibleArr, setModVisibleArr] = React.useState(initVisArr); const [modTypeArr, setModTypeArr] = React.useState(Array(MAXLFOS).fill('LFO')); const [modInstanceNumArr, setModInstanceNumArr] = React.useState(Array(MAXLFOS).fill('1')); const [modCenterVals, setModCenterVals] = React.useState({ '1': {}, '2': {}, '3': {}, '4': {} }); const [ticks, setTicks] = React.useState(0); const [beatsInMeasure, setBeatsInMeasure] = React.useState(4); const [noiseTypeArr, setNoiseTypeArr] = React.useState(Array(MAXLFOS).fill('Sine Int.')); const [shapeArr, setShapeArr] = React.useState(Array(MAXLFOS).fill('Sine')); const [djParamArr, setDjParamArr] = React.useState(Array(MAXLFOS).fill('NONE')); const [freqArr, setFreqArr] = React.useState(Array(MAXLFOS).fill('1hz')); // const [ampArr, setAmpArr] = React.useState(Array(MAXLFOS).fill('1')); const [minArr, setMinArr] = React.useState(Array(MAXLFOS).fill('0')); const [maxArr, setMaxArr] = React.useState(Array(MAXLFOS).fill('1')); const [initPhaseArr, setInitPhaseArr] = React.useState(Array(MAXLFOS).fill('0')); const [lastPhaseArr, setLastPhaseArr] = React.useState(Array(MAXLFOS).fill(0)); const [cachedNoiseValueArr1, setCachedNoiseValueArr1] = React.useState(Array(MAXLFOS).fill(0)); const [cachedNoiseValueArr2, setCachedNoiseValueArr2] = React.useState(Array(MAXLFOS).fill(0)); const allModArrays = [modVisibleArr, modTypeArr, modInstanceNumArr, shapeArr, noiseTypeArr, djParamArr, freqArr, minArr, maxArr, initPhaseArr, lastPhaseArr, cachedNoiseValueArr1, cachedNoiseValueArr2]; const allModSetters = [setModVisibleArr, setModTypeArr, setModInstanceNumArr, setShapeArr, setNoiseTypeArr, setDjParamArr, setFreqArr, setMinArr, setMaxArr, setInitPhaseArr, setLastPhaseArr, setCachedNoiseValueArr1, setCachedNoiseValueArr2]; const modBlankVals = [true, 'LFO', '1', SHAPETYPES[0], NOISETYPES[0], MODPARAMOPTIONS[0], '1hz', '0', '1', '0', 0, 0, 0]; /// ENUMERATOR ARRAYS const [enumVisibleArr, setEnumVisibleArr] = React.useState(initVisArr); const [enumInstanceNumArr, setEnumInstanceNumArr] = React.useState(Array(MAXLFOS).fill('1')); const [enumItemCounts, setEnumItemCounts] = React.useState(Array(MAXENUMPOINTS).fill('2')); const [enumDjParamArr, setEnumDjParamArr] = React.useState(Array(MAXENUMPOINTS).fill('NONE')); 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 getBlankEnumBreakPointRow = () => { let arr = [] for (let i = 0; i < MAXENUMPOINTS + 1; i++) arr.push(i - 0.5) return arr; } 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 allEnumArrays = [enumVisibleArr, enumInstanceNumArr, enumItemCounts, enumDjParamArr]; const allEnumArrSetters = [setEnumVisibleArr, setEnumInstanceNumArr, setEnumItemCounts, setEnumDjParamArr]; const allEnumMats = [enumBreakPoints, enumNames]; const allEnumMatSetters = [setEnumBreakPoints, setEnumNames]; const allGetEnumMatBlankVals = [getBlankEnumBreakPointRow, getBlankEnumNameRow] const enumBlankVals = [true, '1', 2, MODPARAMOPTIONS[0]]; const [render, rerender] = React.useState(false); // BAD. I SHOULDN'T BE DOING THIS React.useEffect(() => { function handleLoad(event) { window.max.getDict(event.detail, (dict) => { for (let i = 0; i < dict.data.modArrays.length; i++) { allModSetters[i](dict.data.modArrays[i]); } for (let i = 0; i < allEnumArrays.length; i++) { allEnumArrSetters[i](dict.data.enumArrays[i]); } for (let i = 0; i < allEnumMats.length; i++) { allEnumMatSetters[i](dict.data.enumMats[i]); } }) } function handleSave(event) { let data = { 'modArrays': allModArrays, 'enumArrays': allEnumArrays, 'enumMats': allEnumMats } window.max.setDict(event.detail, { "data": data }); window.max.outlet("saved"); } // only called internally by 1. Handler after modulator processing 2. LFO outputs function handleEnum(event) { let inst = event.detail[0]; let name = event.detail[1]; let val = event.detail[2]; // if none of the Enums use this param, then we output it let i = 0; while (i < MAXENUMS) { if (enumVisibleArr[i] && enumDjParamArr[i] == name && enumInstanceNumArr[i] == inst) break; i++ } if (i == MAXENUMS) { window.max.outlet(inst + ' ' + name + ' ' + val); } else { enumerate(name, inst, val, enumItemCounts[i], enumBreakPoints[i], enumNames[i]); } } function handleParam(event) { let inst = event.detail[0]; // djster instance let name = event.detail[1]; let val = event.detail[2]; // CHECK FOR INDEX OF THIS NAME IN ENUM MATRIX, AND IF IT IS THERE DENUMERATE let index = -1; for (let i = 0; i < MAXENUMS; i++) { if (enumDjParamArr[i] == name && enumInstanceNumArr[i] == inst) index = i; } if (index != -1) { val = denumerate(val, enumItemCounts[index], enumBreakPoints[index], enumNames[index]); } // if none of the LFOs use this param, then we send it straight to the enum let i = 0; while (i < MAXLFOS) { if (modVisibleArr[i] && djParamArr[i] == name && modInstanceNumArr[i] == inst) break; i++; } if (i == MAXLFOS) { window.dispatchEvent(new CustomEvent('enum', { 'detail': [inst, name, val] })); } modCenterVals[inst][name] = val; setModCenterVals(modCenterVals); rerender(!render); // BAD! SHOULD NOT BE DOING THIS! } function handleTick(event) { let time = (Date.now() - firstUpdateTime) / 1000; let noiseData = { lastPhaseArr, setLastPhaseArr, cachedNoiseValueArr1, setCachedNoiseValueArr1, cachedNoiseValueArr2, setCachedNoiseValueArr2, noiseTypeArr }; operateModulators(modVisibleArr, modTypeArr, modInstanceNumArr, djParamArr, modCenterVals, freqArr, minArr, maxArr, shapeArr, initPhaseArr, noiseData, userDefinedWaves, userDefinedFunctions, userDefinedTypes, time, beatsInMeasure, ticks); } function handleTimeSig(event) { setBeatsInMeasure(parseFloat(event.detail[0]) * parseFloat(event.detail[1]) / 4); } function handleChangeUserWave(event) { userDefinedWaves[event.detail.index] = event.detail.points; setUserDefinedWaves(userDefinedWaves); } function handleChangeUserFunction(event) { userDefinedFunctions[event.detail.index] = event.detail.points; setUserDefinedFunctions(userDefinedFunctions); } function handleChangeUserDefinedType(event) { userDefinedTypes[event.detail.index - 1] = event.detail.type; setUserDefinedTypes(userDefinedTypes); } function handleMaxTicks(event) { setTicks(event.detail); } function setNN(event) { for (let i = 0; i < MAXLFOS; i++) { freqArr[i] = nnFreqToHzString(event.detail[i]); } setFreqArr(freqArr) for (let i = MAXLFOS; i < MAXLFOS * 2; i++) { minArr[i - MAXLFOS] = event.detail[i]; } setMinArr(minArr); for (let i = MAXLFOS * 2; i < MAXLFOS * 3; i++) { maxArr[i - MAXLFOS * 2] = event.detail[i]; } setMaxArr(maxArr); for (let i = MAXLFOS * 3; i < MAXLFOS * 4; i++) { initPhaseArr[i - MAXLFOS * 3] = parseFloat(event.detail[i]); } setInitPhaseArr(initPhaseArr); for (let i = MAXLFOS * 4; i < MAXLFOS * 5; i++) { let index = i - MAXLFOS * 4; let inst = modInstanceNumArr[index]; let param = djParamArr[index]; modCenterVals[inst][param] = parseFloat(event.detail[i]); } setModCenterVals(modCenterVals); rerender(!render); // BAD! SHOULD NOT BE DOING THIS! } function dumpNN(event) { let allNNData = []; freqArr.forEach(element => { allNNData.push(parseLfoTimeNonMusical(element)); }); allNNData = allNNData.concat(minArr); allNNData = allNNData.concat(maxArr); allNNData = allNNData.concat(initPhaseArr); let lfoMatchedCenterVals = []; for (let i = 0; i < MAXLFOS; i++) { let inst = modInstanceNumArr[i]; let param = djParamArr[i]; lfoMatchedCenterVals.push(modCenterVals[inst][param]); if (!lfoMatchedCenterVals[i]) lfoMatchedCenterVals[i] = 0 } allNNData = allNNData.concat(lfoMatchedCenterVals); window.max.outlet("NNdata " + allNNData.join(" ")); } window.addEventListener('loadDict', handleLoad); window.addEventListener('saveDict', handleSave); window.addEventListener('dumpNN', dumpNN); window.addEventListener('setNN', setNN); window.addEventListener('tick', handleTick); window.addEventListener('param', handleParam); window.addEventListener('enum', handleEnum); window.addEventListener('timesig', handleTimeSig); window.addEventListener('userWave', handleChangeUserWave); window.addEventListener('userFunction', handleChangeUserFunction); window.addEventListener('userDefinedType', handleChangeUserDefinedType); window.addEventListener('maxTicks', handleMaxTicks); return () => { window.removeEventListener('loadDict', handleLoad); window.removeEventListener('saveDict', handleSave); window.removeEventListener('dumpNN', dumpNN); window.removeEventListener('setNN', setNN); window.removeEventListener('tick', handleTick); window.removeEventListener('param', handleParam); window.removeEventListener('enum', handleEnum); window.removeEventListener('timesig', handleTimeSig); window.removeEventListener('userWave', handleChangeUserWave); window.removeEventListener('userFunction', handleChangeUserFunction); window.removeEventListener('userDefinedType', handleChangeUserDefinedType); window.removeEventListener('maxTicks', handleMaxTicks); }; }, [...allModArrays, ...allEnumArrays, ...allEnumMats, userDefinedWaves, userDefinedFunctions, userDefinedTypes, modCenterVals, render, beatsInMeasure, ticks]); function CheckLinked(inst, param, checkInstArr, checkParamArr) { for (let i = 0; i < checkInstArr.length; i++) { if (checkInstArr[i] == inst && checkParamArr[i] == param) return true; } return false; } /////// // Generate Modulators /////// let modContents = [] for (var i = 0; i < MAXLFOS; i++) { let id = i; modContents.push( e(LfoRow, { locked: lockMode, instanceNum: modInstanceNumArr[i], setInstanceNum: CreateParamChanger(modInstanceNumArr, setModInstanceNumArr, i), type: modTypeArr[i], setType: CreateParamChanger(modTypeArr, setModTypeArr, i), shape: shapeArr[i], setShape: CreateParamChanger(shapeArr, setShapeArr, i), noise: noiseTypeArr[i], setNoise: CreateParamChanger(noiseTypeArr, setNoiseTypeArr, i), djParam: djParamArr[i], setDjParam: CreateParamChanger(djParamArr, setDjParamArr, i), centerVals: modCenterVals, freq: freqArr[i], setFreq: CreateParamChanger(freqArr, setFreqArr, i), //amp: ampArr[i], //setAmp: CreateParamChanger(ampArr, setAmpArr, i), min: minArr[i], setMin: CreateParamChanger(minArr, setMinArr, i), max: maxArr[i], setMax: CreateParamChanger(maxArr, setMaxArr, i), phase: initPhaseArr[i], setPhase: CreateParamChanger(initPhaseArr, setInitPhaseArr, i), visible: modVisibleArr[i], linked: CheckLinked(modInstanceNumArr[i], djParamArr[i], enumInstanceNumArr, enumDjParamArr), addLfo: () => { 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++) { let array = allModArrays[j]; // remove from all arrays array.splice(emptyIndex, 1); // add empty item at opened index array.splice(id + 1, 0, modBlankVals[j]); allModSetters[j](array); } } } else { 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) { let newArr = modVisibleArr.slice(); newArr[id] = false; setModVisibleArr(newArr); } } }, null)); } /////// // Generate Enumerators /////// let enumContents = [] for (var i = 0; i < MAXLFOS; i++) { let id = i; enumContents.push( e(EnumeratorRow, { index: i, locked: lockMode, instanceNum: enumInstanceNumArr[i], setInstanceNum: CreateParamChanger(enumInstanceNumArr, setEnumInstanceNumArr, i), enumItems: enumItemCounts[i], setEnumItemCounts: CreateParamChanger(enumItemCounts, setEnumItemCounts, i), enumBreakPoints: enumBreakPoints, setEnumBreakPoints: setEnumBreakPoints, enumNames: enumNames, setEnumNames: setEnumNames, visible: enumVisibleArr[i], djParam: enumDjParamArr[i], setDjParam: CreateParamChanger(enumDjParamArr, setEnumDjParamArr, i), linked: CheckLinked(enumInstanceNumArr[i], enumDjParamArr[i], modInstanceNumArr, djParamArr), addEnum: () => { 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++) { let array = allEnumArrays[j]; // remove from all arrays array.splice(emptyIndex, 1); // add empty item at opened index array.splice(id + 1, 0, enumBlankVals[j]); allEnumArrSetters[j](array); } // Now do the same with matrices for (var j = 0; j < allEnumMats.length; j++) { let mat = allEnumMats[j]; mat.splice(emptyIndex, 1); mat.splice(id + 1, 0, 0); mat[id + 1] = allGetEnumMatBlankVals[j](); allEnumMatSetters[j](mat); } } } else { for (var j = 0; j < allEnumArrays.length; j++) { let array = allEnumArrays[j]; array[id + 1] = enumBlankVals[j]; allEnumArrSetters[j](array); } // Now do the same with matrices for (var j = 0; j < allEnumMats.length; j++) { let mat = allEnumMats[j]; mat[id + 1] = allGetEnumMatBlankVals[j](); allEnumMatSetters[j](mat); } } rerender(!render); } }, removeEnum: () => { if (enumVisibleArr.filter(x => x).length > 1) { let newArr = enumVisibleArr.slice(); newArr[id] = false; setEnumVisibleArr(newArr); } } }, null) ); } var grid; var modButtonClass; var enumButtonClass; var labels; if (viewMode === ViewModes.MOD) { grid = modContents; modButtonClass = "highlighted-button"; enumButtonClass = "unhighlighted-button"; labels = MODULATORLABELS; } else { grid = enumContents; modButtonClass = "unhighlighted-button"; enumButtonClass = "highlighted-button"; labels = ENUMERATORLABELS; } let lockClass; if (lockMode == LockModes.LOCK) { lockClass = 'lock'; } else { lockClass = 'lock unlocked'; } return e('div', null, e('div', { className: 'header' }, displayIfEnabled(e('button', { onClick: () => setViewMode(ViewModes.MOD), className: modButtonClass}, 'Modulators')), displayIfEnabled(e('button', { onClick: () => setViewMode(ViewModes.ENUM), className: enumButtonClass }, 'Enumerators')), e('button', { onClick: toggleEnabled, }, toggleEnabledText), //allows lock mode //e('span', { className: lockClass, onClick: toggleLockMode }, null) ), displayIfEnabled( e('table', { id: 'table' }, e('thead', null, e('tr', { id: 'headers' }, ...labels.map(x => e('th', null, x)))), e('tbody', null, ...grid) )) ); } if (!DEBUG) { window.max.bindInlet("load", (dictId) => { window.dispatchEvent(new CustomEvent('loadDict', { 'detail': dictId })); }); window.max.bindInlet("save", (dictId) => { window.dispatchEvent(new CustomEvent('saveDict', { 'detail': dictId })); }); window.max.bindInlet("dumpNN", () => { window.dispatchEvent(new CustomEvent('dumpNN')); }); window.max.bindInlet("setNN", (...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.max.bindInlet("timesig", (top, bottom) => { window.dispatchEvent(new CustomEvent('timesig', { 'detail': [top, bottom] })); }); window.max.bindInlet("ticks", (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 })); }); window.max.bindInlet("userFunction", (index, ...points) => { //list of 101 points between 0-100 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 })); }) setInterval(() => { window.dispatchEvent(new CustomEvent('tick')); }, 200); } const root = ReactDOM.createRoot(document.getElementById('lfo-container')); root.render(e(MasterLfoHandler, null, null));