475 lines
18 KiB
JavaScript
475 lines
18 KiB
JavaScript
// 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 = 20;
|
|
const MAXENUMS = 20;
|
|
const MAXENUMPOINTS = 10;
|
|
|
|
|
|
|
|
const ViewModes = Object.freeze({
|
|
MOD: 0,
|
|
ENUM: 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 MasterLfoHandler(){
|
|
|
|
let initVisArr = Array(MAXLFOS).fill(false);
|
|
initVisArr[0] = true;
|
|
|
|
const [viewMode, setViewMode] = React.useState(ViewModes.MOD);
|
|
const toggleViewMode = () => {
|
|
if (viewMode === ViewModes.MOD)
|
|
setViewMode(ViewModes.ENUM);
|
|
else
|
|
setViewMode(ViewModes.MOD);
|
|
};
|
|
|
|
/// MODULATOR ARRAYS
|
|
|
|
const [userDefinedWave, setUserDefinedWave] = React.useState(Array(50).fill(0));
|
|
|
|
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 [cachedNoiseValueArr, setCachedNoiseValueArr] = React.useState(Array(MAXLFOS).fill([0, 0]));
|
|
|
|
|
|
|
|
const allModArrays = [modVisibleArr, modTypeArr, modInstanceNumArr, shapeArr, noiseTypeArr, djParamArr, freqArr, minArr, maxArr, initPhaseArr, lastPhaseArr, cachedNoiseValueArr];
|
|
const allModSetters = [setModVisibleArr, setModTypeArr, setModInstanceNumArr, setShapeArr, setNoiseTypeArr, setDjParamArr, setFreqArr, setMinArr, setMaxArr, setInitPhaseArr, lastPhaseArr, cachedNoiseValueArr];
|
|
const modBlankVals = [true, 'LFO', '1', SHAPETYPES[0], MODPARAMOPTIONS[0], "Sine Int.", '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('attenuation'));
|
|
|
|
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);
|
|
|
|
const getBlankEnumBreakPointRow = () => {
|
|
let arr = []
|
|
for (let i=0; i< MAXENUMPOINTS + 1; i++)
|
|
arr.push(i)
|
|
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<allModArrays.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, cachedNoiseValueArr, setCachedNoiseValueArr};
|
|
operateModulators(modVisibleArr, modTypeArr, modInstanceNumArr, djParamArr, modCenterVals, freqArr, minArr, maxArr, shapeArr, initPhaseArr, noiseData, userDefinedWave, time, beatsInMeasure, ticks);
|
|
}
|
|
|
|
function handleTimeSig(event) {
|
|
setBeatsInMeasure(parseFloat(event.detail[0]) * parseFloat(event.detail[1])/ 4);
|
|
}
|
|
|
|
function handleChangeUserWave(event){
|
|
setUserDefinedWave(event.detail);
|
|
}
|
|
|
|
function handleMaxTicks(event){
|
|
setTicks(event.detail);
|
|
}
|
|
|
|
|
|
window.addEventListener('loadDict', handleLoad);
|
|
window.addEventListener('saveDict', handleSave);
|
|
window.addEventListener('tick', handleTick);
|
|
window.addEventListener('param', handleParam);
|
|
window.addEventListener('enum', handleEnum);
|
|
window.addEventListener('timesig', handleTimeSig);
|
|
window.addEventListener('userWave', handleChangeUserWave);
|
|
window.addEventListener('maxTicks', handleMaxTicks);
|
|
|
|
return () => {
|
|
window.removeEventListener('loadDict', handleLoad);
|
|
window.removeEventListener('saveDict', handleSave);
|
|
window.removeEventListener('tick', handleTick);
|
|
window.removeEventListener('param', handleParam);
|
|
window.removeEventListener('enum', handleEnum);
|
|
window.removeEventListener('timesig', handleTimeSig);
|
|
window.removeEventListener('userWave', handleChangeUserWave);
|
|
window.removeEventListener('maxTicks', handleMaxTicks);
|
|
};
|
|
}, [...allModArrays, ...allEnumArrays, ...allEnumMats, 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, {
|
|
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 {
|
|
log("adding lfo");
|
|
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,
|
|
|
|
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 title;
|
|
var labels;
|
|
if (viewMode === ViewModes.MOD){
|
|
grid = modContents;
|
|
title = "MODULATORS";
|
|
labels = MODULATORLABELS;
|
|
}
|
|
else {
|
|
grid = enumContents;
|
|
title = "ENUMERATORS";
|
|
labels = ENUMERATORLABELS;
|
|
}
|
|
|
|
return e('div', null,
|
|
e(Switch, {ontoggle: toggleViewMode}, null),
|
|
e('h5', null, title),
|
|
e('ul', null, ...labels.map(x => ListItem(Label(x)))),
|
|
e('div', {id: 'grid'}, ...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("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", (...points) => {
|
|
window.dispatchEvent(new CustomEvent('userWave', {'detail' : points}));
|
|
});
|
|
|
|
|
|
/* window.max.binInlet("userWave", (...points) => {
|
|
window.dispatchEvent(new CustomEvent('userWave', {'detail' : [points]}));
|
|
log("received user points");
|
|
}); */
|
|
|
|
setInterval(() => {
|
|
window.dispatchEvent(new CustomEvent('tick'));
|
|
}, 200);
|
|
}
|
|
|
|
|
|
const root = ReactDOM.createRoot(document.getElementById('lfo-container'));
|
|
root.render(e(MasterLfoHandler, null, null)); |