Compare commits
83 Commits
af98d457cd
...
directory-
| Author | SHA1 | Date | |
|---|---|---|---|
| 53a88ca374 | |||
| 7f464366af | |||
| 51420cbd5f | |||
| 533de4668a | |||
| b850440287 | |||
| 7ac026f35f | |||
| 7cb4180d97 | |||
| 51767ff058 | |||
|
|
fe68764e7d | ||
|
|
40d0927a0e | ||
|
|
92b7545e90 | ||
|
|
4912b398dd | ||
|
|
02d5c6e914 | ||
| 13f3a18c4c | |||
| f21220e7b2 | |||
| fe165b77e5 | |||
|
|
a7b0de9e66 | ||
| 43d37f215f | |||
|
|
c1a0a4d919 | ||
| e2f33b8ba7 | |||
| a19717a759 | |||
| 64da80b742 | |||
|
|
34549daf67 | ||
|
|
dd47c0b030 | ||
| 0b7dbbb775 | |||
| 290ff73524 | |||
| 9b1a9f2e03 | |||
| 541ab6d459 | |||
| b96f2aa53b | |||
| edcddde844 | |||
| e41567c101 | |||
| 8002ae8bdb | |||
| 286a43b0b2 | |||
| 229ddb9c37 | |||
| bbcfd8faab | |||
| 5d092a607c | |||
| de28fd87fa | |||
|
|
07c4826bd9 | ||
|
|
501fca079e | ||
|
|
3507899eb3 | ||
|
|
dc2daade1b | ||
| 149d1a0b8d | |||
| 4ee8a93791 | |||
| c706461443 | |||
| 081e33dd07 | |||
| eb02657b4a | |||
| d670aefcbd | |||
| 68baa3c8de | |||
| a35fecafae | |||
| b3ba9b0f2d | |||
| 102a88b242 | |||
| aebfbe5277 | |||
| 2d81a832af | |||
| cdbcf24dfc | |||
| 64f1455f69 | |||
|
|
a855d254cd | ||
| 09a1b5f20f | |||
| 34e99f09fd | |||
| 86a994546e | |||
| d22cdc8401 | |||
|
|
43e517cc4e | ||
| a608b083f3 | |||
| 83cf801ec3 | |||
|
|
45e168ba11 | ||
| 37a65058bc | |||
|
|
45847fbae4 | ||
|
|
c794b5bc2f | ||
|
|
c8c410a38f | ||
| 985b6c832f | |||
|
|
ff0e8443fc | ||
| 446713d5d3 | |||
| 2369ed5ddf | |||
| 071a78dc20 | |||
| 1d8610fa5f | |||
| ebd09dfd26 | |||
| 13cddd8f03 | |||
| 1f3972fc6f | |||
| b860254884 | |||
| a4842b4865 | |||
| 315a8df1a0 | |||
| f9a8c05955 | |||
| 8381daf468 | |||
| e50e4c1e0c |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
1740
ModEnum.maxhelp
Normal file
1740
ModEnum.maxhelp
Normal file
File diff suppressed because it is too large
Load Diff
3345
ModEnum.maxpat
Normal file
3345
ModEnum.maxpat
Normal file
File diff suppressed because one or more lines are too long
56
common.js
56
common.js
@@ -1,56 +0,0 @@
|
||||
function DropDown(props) {
|
||||
return e('select', {type: "number", onChange: props.onChange, value: props.value},
|
||||
...props.options.map((item) => Option(item)));
|
||||
}
|
||||
|
||||
function ListItem(child){
|
||||
return e('li', null, child);
|
||||
}
|
||||
|
||||
function Label(text){
|
||||
return e('div', {className: 'label'}, text);
|
||||
}
|
||||
|
||||
function NumberBox(props){
|
||||
return e('input', {type: "number", onChange: props.onChange, step: props.step, value: props.value, className: props.className}, null);
|
||||
}
|
||||
|
||||
function TextBox(props){
|
||||
return e('input', {type: "text", value: props.value, onChange: props.onChange, id: props.id});
|
||||
}
|
||||
|
||||
function Option(str, value){
|
||||
return e("option", {value: value}, 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, cb=() => {}){
|
||||
return (event) => {
|
||||
let newArr = arr.slice();
|
||||
newArr[index] = event.target.value;
|
||||
setArr(newArr);
|
||||
cb();
|
||||
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}`);
|
||||
|
||||
}
|
||||
}
|
||||
57
enums.js
57
enums.js
@@ -1,57 +0,0 @@
|
||||
/////////////////////////
|
||||
// ENUMERATORS
|
||||
/////////////////////////
|
||||
|
||||
|
||||
|
||||
// NOT A REACT FUNCTIONAL COMPONENT. MERELY RETURNS AN ARRAY WHICH IS UNPACKED
|
||||
function EnumeratorItems(index, enumBreakPoints, setEnumBreakPoints, enumNames, setEnumNames, djParam){
|
||||
let items = [];
|
||||
for (let i = 0; i < MAXENUMPOINTS; i++){
|
||||
|
||||
items.push(ListItem(e(TextBox, {onChange: CreateMatrixParamChanger(enumNames, setEnumNames, index, i), value: enumNames[index][i], id:`text-${djParam}-${enumNames[index][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', id: `${props.djParam}-enum-row`},
|
||||
ListItem(DropDown({onChange: props.setDjParam, value: props.djParam, options: MODPARAMOPTIONS})),
|
||||
ListItem(e(NumberBox, {onChange: props.setEnumItemCounts, step:1, 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], step:0.1}, null)),
|
||||
...(EnumeratorItems(props.index, props.enumBreakPoints, props.setEnumBreakPoints, props.enumNames, props.setEnumNames, props.djParam).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 enumerate(name, inval, count, keys, vals){
|
||||
let output = "OUT OF RANGE";
|
||||
for (let i=0; i < count + 1; i++){
|
||||
if (inval <= keys[i]){
|
||||
if (i > 0)
|
||||
output = vals[i - 1];
|
||||
break
|
||||
}
|
||||
}
|
||||
let highlightedItem = document.getElementById(`text-${name}-${output}`);
|
||||
|
||||
if (highlightedItem){
|
||||
|
||||
highlightedItem.style.animation = "pulse-animation 0.5s normal";
|
||||
|
||||
highlightedItem.addEventListener('animationend', () => {
|
||||
highlightedItem.style.animation = "";
|
||||
});
|
||||
}
|
||||
|
||||
if (name !== "NONE")
|
||||
window.max.outlet(name + " " + output);
|
||||
}
|
||||
|
||||
404
example.maxpat
404
example.maxpat
@@ -1,404 +0,0 @@
|
||||
{
|
||||
"patcher" : {
|
||||
"fileversion" : 1,
|
||||
"appversion" : {
|
||||
"major" : 8,
|
||||
"minor" : 5,
|
||||
"revision" : 6,
|
||||
"architecture" : "x64",
|
||||
"modernui" : 1
|
||||
}
|
||||
,
|
||||
"classnamespace" : "box",
|
||||
"rect" : [ 299.0, 99.0, 1220.0, 715.0 ],
|
||||
"bglocked" : 0,
|
||||
"openinpresentation" : 0,
|
||||
"default_fontsize" : 12.0,
|
||||
"default_fontface" : 0,
|
||||
"default_fontname" : "Arial",
|
||||
"gridonopen" : 1,
|
||||
"gridsize" : [ 15.0, 15.0 ],
|
||||
"gridsnaponopen" : 1,
|
||||
"objectsnaponopen" : 1,
|
||||
"statusbarvisible" : 2,
|
||||
"toolbarvisible" : 1,
|
||||
"lefttoolbarpinned" : 0,
|
||||
"toptoolbarpinned" : 0,
|
||||
"righttoolbarpinned" : 0,
|
||||
"bottomtoolbarpinned" : 0,
|
||||
"toolbars_unpinned_last_save" : 0,
|
||||
"tallnewobj" : 0,
|
||||
"boxanimatetime" : 200,
|
||||
"enablehscroll" : 1,
|
||||
"enablevscroll" : 1,
|
||||
"devicewidth" : 0.0,
|
||||
"description" : "",
|
||||
"digest" : "",
|
||||
"tags" : "",
|
||||
"style" : "",
|
||||
"subpatcher_template" : "",
|
||||
"assistshowspatchername" : 0,
|
||||
"boxes" : [ {
|
||||
"box" : {
|
||||
"id" : "obj-28",
|
||||
"linecount" : 2,
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 493.0, 444.0, 161.0, 35.0 ],
|
||||
"text" : "\"harmoniclarity 0.1955362124045653\""
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-19",
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 541.52631402015686, 69.0, 150.0, 20.0 ],
|
||||
"text" : "self explanatory"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-14",
|
||||
"linecount" : 3,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 817.0, 45.0, 150.0, 47.0 ],
|
||||
"text" : "self explanatory. Warning-will overwrite whatever is saved."
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-11",
|
||||
"linecount" : 4,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 1039.0, 50.0, 150.0, 60.0 ],
|
||||
"text" : "You can use the `phase` control to phase offset two LFOs of the same frequency"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-7",
|
||||
"linecount" : 3,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 109.0, 285.0, 197.0, 47.0 ],
|
||||
"presentation_linecount" : 3,
|
||||
"text" : "This parameter is defined in the enumerators but not the modulators"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-5",
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 146.0, 334.0, 94.0, 22.0 ],
|
||||
"text" : "param scale 1.6"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-25",
|
||||
"maxclass" : "newobj",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "bang" ],
|
||||
"patching_rect" : [ 454.0, 27.5, 48.0, 22.0 ],
|
||||
"text" : "del 200"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-24",
|
||||
"linecount" : 3,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 89.0, 358.0, 254.0, 47.0 ],
|
||||
"text" : "This parameter is not defined by either the Modulators or Enumerators, so it will be passed directly to the output"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-23",
|
||||
"linecount" : 3,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 115.0, 201.0, 197.0, 47.0 ],
|
||||
"text" : "This parameter is defined in the modulators, and when sent will act as the low value for that LFO"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-21",
|
||||
"linecount" : 10,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 89.0, 463.0, 254.0, 141.0 ],
|
||||
"text" : "The operation runs\n\nInput > Modulators > Enumerators > Output\n\nA parameter from the input not established by a Modulator will be passed directly to the Enumerators\n\nLikewise, the Enumerators will pass not established parameters"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-15",
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 135.0, 259.0, 121.0, 22.0 ],
|
||||
"text" : "param metriclarity 40"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-8",
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 115.0, 409.0, 129.0, 22.0 ],
|
||||
"text" : "param attenuation 200"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-16",
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 438.52631402015686, 69.0, 101.0, 22.0 ],
|
||||
"text" : "load localStorage"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"data" : {
|
||||
"data" : {
|
||||
"enumArrays" : [ [ 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ "2", 2, 2, 2, "2", "2", "2", "2", "2", "2" ], [ "event_length", "meter", "stream", "scale", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation" ] ],
|
||||
"enumMats" : [ [ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ "0", "1", "2", 3, 4, 5, 6, 7, 8, 9, 10 ], [ "0", "1", "2", 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ], [ [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "3 4", "7 8", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "0", "1", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "pentatonic", "major", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ], [ "param", "param", "param", "param", "param", "param", "param", "param", "param", "param" ] ] ],
|
||||
"modArrays" : [ [ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ "Sine", "SawUp", "SawDown", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine", "Sine" ], [ "metriclarity", "stream", "meter", "harmoniclarity", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation", "attenuation" ], [ "0.6", "0.3", "0.1", "0.6", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], [ "30", "2", "2", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1" ], [ "0", "0", "0", "0.5", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" ] ]
|
||||
}
|
||||
|
||||
}
|
||||
,
|
||||
"id" : "obj-4",
|
||||
"maxclass" : "newobj",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 4,
|
||||
"outlettype" : [ "dictionary", "", "", "" ],
|
||||
"patching_rect" : [ 22.0, 102.0, 159.0, 22.0 ],
|
||||
"saved_object_attributes" : {
|
||||
"embed" : 1,
|
||||
"parameter_enable" : 0,
|
||||
"parameter_mappable" : 0
|
||||
}
|
||||
,
|
||||
"text" : "dict localStorage @embed 1"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-6",
|
||||
"linecount" : 2,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 504.0, 6.0, 150.0, 33.0 ],
|
||||
"text" : "required due to the asynchronous operation"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-3",
|
||||
"maxclass" : "newobj",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "bang" ],
|
||||
"patching_rect" : [ 374.157894849777222, 27.5, 55.0, 22.0 ],
|
||||
"text" : "del 1000"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-45",
|
||||
"linecount" : 5,
|
||||
"maxclass" : "comment",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 0,
|
||||
"patching_rect" : [ 31.0, 18.0, 150.0, 74.0 ],
|
||||
"text" : "Storage for the matrix. Unfortunately, jsweb dictionary handling isn't great, so we can't use it like a native dict object"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-20",
|
||||
"maxclass" : "newobj",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "bang" ],
|
||||
"patching_rect" : [ 217.0, 6.0, 58.0, 22.0 ],
|
||||
"text" : "loadbang"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-12",
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 700.894735336303711, 69.0, 104.0, 22.0 ],
|
||||
"text" : "save localStorage"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-18",
|
||||
"maxclass" : "message",
|
||||
"numinlets" : 2,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 217.0, 69.0, 106.0, 22.0 ],
|
||||
"text" : "readfile lfogui.html"
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"box" : {
|
||||
"id" : "obj-2",
|
||||
"maxclass" : "jweb",
|
||||
"numinlets" : 1,
|
||||
"numoutlets" : 1,
|
||||
"outlettype" : [ "" ],
|
||||
"patching_rect" : [ 438.52631402015686, 115.0, 643.105266809463501, 318.0 ],
|
||||
"rendermode" : 0,
|
||||
"url" : "file://lfogui.html"
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
"lines" : [ {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-2", 0 ],
|
||||
"source" : [ "obj-12", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-2", 0 ],
|
||||
"source" : [ "obj-15", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-2", 0 ],
|
||||
"source" : [ "obj-16", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-2", 0 ],
|
||||
"source" : [ "obj-18", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-28", 1 ],
|
||||
"source" : [ "obj-2", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-18", 0 ],
|
||||
"order" : 1,
|
||||
"source" : [ "obj-20", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-3", 0 ],
|
||||
"order" : 0,
|
||||
"source" : [ "obj-20", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-15", 0 ],
|
||||
"source" : [ "obj-25", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-16", 0 ],
|
||||
"order" : 1,
|
||||
"source" : [ "obj-3", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-25", 0 ],
|
||||
"order" : 0,
|
||||
"source" : [ "obj-3", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-2", 0 ],
|
||||
"source" : [ "obj-5", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
, {
|
||||
"patchline" : {
|
||||
"destination" : [ "obj-2", 0 ],
|
||||
"source" : [ "obj-8", 0 ]
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
"dependency_cache" : [ ],
|
||||
"autosave" : 0
|
||||
}
|
||||
|
||||
}
|
||||
188
lfogui.html
188
lfogui.html
@@ -1,188 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<!--
|
||||
We start with a basic html 'page' that is the size of the jweb object,
|
||||
but has no scrollbars nor floating content.
|
||||
-->
|
||||
|
||||
<head>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
border: 0;
|
||||
overflow: hidden; /* Disable scrollbars */
|
||||
display: block; /* No floating content on sides */
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
||||
input[type=number] {
|
||||
width: 50px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 60px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#matrix {
|
||||
background-color: aquamarine;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.numbox-unclicked {
|
||||
user-select: none;
|
||||
border: solid;
|
||||
font-size: 12vw;
|
||||
}
|
||||
|
||||
.numbox-clicked {
|
||||
user-select: none;
|
||||
border : solid;
|
||||
font-size: 12vw;
|
||||
}
|
||||
|
||||
.param-input-label {
|
||||
width: 93%;
|
||||
font-size: 5vw;
|
||||
}
|
||||
|
||||
.lfo-input-label {
|
||||
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;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: aliceblue;
|
||||
padding: 0 4px 0 4px;
|
||||
margin: 0 2px 0 2px;
|
||||
border-color: #333333;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
@keyframes pulse-animation {
|
||||
0% {
|
||||
color: black;
|
||||
}
|
||||
100% {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
#pulse {
|
||||
animation: pulse-animation 0.2s normal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="./src/lfogui.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="lfo-container"></div>
|
||||
<div id="lfo-container"></div>
|
||||
|
||||
<script src="./react.js"></script>
|
||||
<script src="./react-dom.js"></script>
|
||||
<script src="./common.js"></script>
|
||||
<script src="./enums.js"></script>
|
||||
<script src="./modulators.js"></script>
|
||||
<script src="./lfogui.js">
|
||||
|
||||
|
||||
</script>
|
||||
<script src="./lib/react.js"></script>
|
||||
<script src="./lib/react-dom.js"></script>
|
||||
<script src="./lib/moment.js"></script>
|
||||
<script src="./src/common.js"></script>
|
||||
<script src="./src/enums.js"></script>
|
||||
<script src="./src/modulators.js"></script>
|
||||
<script src="./src/lfogui.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
379
lfogui.js
379
lfogui.js
@@ -1,379 +0,0 @@
|
||||
// const { createElement } = require("./react");
|
||||
const DEBUG = false;
|
||||
var log;
|
||||
if (DEBUG)
|
||||
log = console.log;
|
||||
else
|
||||
log = window.max.outlet;
|
||||
|
||||
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 = ["-type-", "---shape---", "-------param-------", "--freq--", "--amp--", "--phase--"];
|
||||
const ENUMERATORLABELS = ["---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 [modVisibleArr, setModVisibleArr] = React.useState(initVisArr);
|
||||
|
||||
const [modCenterVals, setModCenterVals] = React.useState({});
|
||||
|
||||
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('1'));
|
||||
const [ampArr, setAmpArr] = React.useState(Array(MAXLFOS).fill('1'));
|
||||
const [phaseArr, setPhaseArr] = React.useState(Array(MAXLFOS).fill('0'));
|
||||
|
||||
const allModArrays = [modVisibleArr, shapeArr, djParamArr, freqArr, ampArr, phaseArr];
|
||||
const allModSetters = [setModVisibleArr, setShapeArr, setDjParamArr, setFreqArr, setAmpArr, setPhaseArr];
|
||||
const modBlankVals = [true, SHAPETYPES[0], MODPARAMOPTIONS[0], '1', '1', '0'];
|
||||
|
||||
|
||||
/// ENUMERATOR ARRAYS
|
||||
const [enumVisibleArr, setEnumVisibleArr] = React.useState(initVisArr);
|
||||
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, enumItemCounts, enumDjParamArr];
|
||||
const allEnumArrSetters = [setEnumVisibleArr, setEnumItemCounts, setEnumDjParamArr];
|
||||
|
||||
const allEnumMats = [enumBreakPoints, enumNames];
|
||||
const allEnumMatSetters = [setEnumBreakPoints, setEnumNames];
|
||||
const allGetEnumMatBlankVals = [getBlankEnumBreakPointRow, getBlankEnumNameRow]
|
||||
|
||||
const enumBlankVals = [true, 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});
|
||||
}
|
||||
|
||||
|
||||
// only called internally by 1. Handler after modulator processing 2. LFO outputs
|
||||
function handleEnum(event){
|
||||
let name = event.detail[0];
|
||||
let val = event.detail[1];
|
||||
|
||||
// if none of the Enums use this param, then we output it
|
||||
let i = 0;
|
||||
while (i < MAXENUMS){
|
||||
if (enumVisibleArr[i] && enumDjParamArr[i] == name)
|
||||
break;
|
||||
i++
|
||||
}
|
||||
if (i == MAXENUMS){
|
||||
window.max.outlet(name + ' ' + val);
|
||||
}
|
||||
else {
|
||||
enumerate(name, val, enumItemCounts[i], enumBreakPoints[i], enumNames[i]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function handleParam(event) {
|
||||
|
||||
let name = event.detail[0];
|
||||
let val = event.detail[1];
|
||||
|
||||
// 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)
|
||||
break;
|
||||
i++
|
||||
}
|
||||
if (i == MAXLFOS){
|
||||
|
||||
window.dispatchEvent(new CustomEvent('enum', {'detail' : [name, val]}));
|
||||
}
|
||||
|
||||
modCenterVals[name] = val;
|
||||
setModCenterVals(modCenterVals);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function handleTick(event) {
|
||||
let time = (Date.now() - firstUpdateTime) / 1000;
|
||||
operateModulators(modVisibleArr, djParamArr, modCenterVals, freqArr, ampArr, shapeArr, phaseArr, time);
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('loadDict', handleLoad);
|
||||
window.addEventListener('saveDict', handleSave);
|
||||
window.addEventListener('tick', handleTick);
|
||||
window.addEventListener('param', handleParam);
|
||||
window.addEventListener('enum', handleEnum);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('loadDict', handleLoad);
|
||||
window.removeEventListener('saveDict', handleSave);
|
||||
window.removeEventListener('tick', handleTick);
|
||||
window.removeEventListener('param', handleParam);
|
||||
window.removeEventListener('enum', handleEnum);
|
||||
};
|
||||
}, [...allModArrays, ...allEnumArrays, ...allEnumMats, modCenterVals]);
|
||||
|
||||
|
||||
|
||||
|
||||
///////
|
||||
// Generate Modulators
|
||||
///////
|
||||
|
||||
let modContents = []
|
||||
for (var i = 0; i<MAXLFOS; i++){
|
||||
let id = i;
|
||||
modContents.push(
|
||||
e(LfoRow, {
|
||||
shape: shapeArr[i],
|
||||
setShape: CreateParamChanger(shapeArr, setShapeArr, i),
|
||||
djParam: djParamArr[i],
|
||||
setDjParam: CreateParamChanger(djParamArr, setDjParamArr, i),
|
||||
|
||||
freq: freqArr[i],
|
||||
setFreq: CreateParamChanger(freqArr, setFreqArr, i),
|
||||
amp: ampArr[i],
|
||||
setAmp: CreateParamChanger(ampArr, setAmpArr, i),
|
||||
phase: phaseArr[i],
|
||||
setPhase: CreateParamChanger(phaseArr, setPhaseArr, i),
|
||||
visible: modVisibleArr[i],
|
||||
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,
|
||||
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),
|
||||
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", (paramName, val) => {
|
||||
window.dispatchEvent(new CustomEvent('param', {'detail' : [paramName, val]}));
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
window.dispatchEvent(new CustomEvent('tick'));
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('lfo-container'));
|
||||
root.render(e(MasterLfoHandler, null, null));
|
||||
2
lib/moment.js
Normal file
2
lib/moment.js
Normal file
File diff suppressed because one or more lines are too long
0
react-dom.js → lib/react-dom.js
vendored
0
react-dom.js → lib/react-dom.js
vendored
0
react.js → lib/react.js
vendored
0
react.js → lib/react.js
vendored
@@ -1,73 +0,0 @@
|
||||
/////////////////////////
|
||||
// MODULATORS
|
||||
/////////////////////////
|
||||
|
||||
var SHAPETYPES = ["Sine", "SawUp", "SawDown", "Tri", "Square"];
|
||||
const MODPARAMOPTIONS = ["NONE", "stream", "pulse_length", "eventfulness", "event_length", "metriclarity",
|
||||
"harmoniclarity", "melodic_cohesion", "melody_scope", "tonic_pitch", "pitch_center", "pitch_range", "dynamics",
|
||||
"attenuation", "chordal_weight", "tonality-profile", "ostinato-buffer", "ostinato", "meter", "scale"];
|
||||
|
||||
function ControlType(){
|
||||
return e('select', {className: 'control-type'}, Option("LFO"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
function LfoRow(props){
|
||||
let content = e('ul', {className: 'lfo-item'},
|
||||
ListItem(ControlType()),
|
||||
ListItem(DropDown({onChange: props.setShape, value:props.shape, options: SHAPETYPES})),
|
||||
ListItem(DropDown({onChange: props.setDjParam, value: props.djParam, options: MODPARAMOPTIONS})),
|
||||
ListItem(e(NumberBox, {onChange:props.setFreq, value:props.freq, 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("input", {type: 'range', min: 0, max: 1, step: 0.01, readonly: true, id: `slider-${props.djParam}`})),
|
||||
ListItem(e(Button, {text:'+', onClick: props.addLfo}, null)),
|
||||
ListItem(e(Button, {text:'-', onClick: props.removeLfo}, null))
|
||||
);
|
||||
if (props.visible){
|
||||
return content
|
||||
};
|
||||
}
|
||||
|
||||
function indexWave(type, phase){
|
||||
switch (type){
|
||||
case "Sine":
|
||||
return (Math.sin(phase * Math.PI * 2) / 2) + 0.5;
|
||||
case "SawUp":
|
||||
return phase;
|
||||
case "SawDown":
|
||||
return 1 - phase;
|
||||
case "Tri":
|
||||
return phase > 0.5? (1-phase) * 2 : phase * 2;
|
||||
case "Square":
|
||||
return +(phase > 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
function operateModulators(visibleArr, paramNames, centers, freqs, amps, waveTypes, phaseArr, time){
|
||||
for (let i=0; i<paramNames.length; i++){
|
||||
if (visibleArr[i]){
|
||||
let name = paramNames[i];
|
||||
let center = 0;
|
||||
if (centers.hasOwnProperty(name)){
|
||||
center = centers[name];
|
||||
}
|
||||
let output = operateModulator(center, freqs[i], amps[i], waveTypes[i], phaseArr, i, name, time);
|
||||
if (name !== "NONE")
|
||||
window.dispatchEvent(new CustomEvent('enum', {'detail' : [name, output]}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function operateModulator(center, freq, amp, waveType, phaseArr, phaseI, name, time){
|
||||
|
||||
let phase = (time * freq + parseFloat(phaseArr[phaseI])) % 1.00;
|
||||
let unscaled = indexWave(waveType, phase);
|
||||
let el = document.getElementById(`slider-${name}`);
|
||||
|
||||
if (el)
|
||||
el.value = unscaled;
|
||||
|
||||
return unscaled * amp + center;
|
||||
}
|
||||
93
myStorage.json
Normal file
93
myStorage.json
Normal file
File diff suppressed because one or more lines are too long
75
src/common.js
Normal file
75
src/common.js
Normal file
@@ -0,0 +1,75 @@
|
||||
function isNumeric(str) {
|
||||
if (typeof str != "string") return false // we only process strings!
|
||||
return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
|
||||
!isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
|
||||
}
|
||||
|
||||
function DropDown(props) {
|
||||
let className = props.locked ? 'locked-component' : '';
|
||||
return e('select', {className, type: "number", onChange: props.onChange, value: props.value},
|
||||
...props.options.map((item) => Option(item)));
|
||||
}
|
||||
|
||||
function ListItem(child){
|
||||
return e('li', null, child);
|
||||
}
|
||||
|
||||
function Label(text){
|
||||
return e('div', {className: 'label'}, text);
|
||||
}
|
||||
|
||||
function NumberBox(props){
|
||||
let extraClassName = props.locked ? ' locked-component' : '';
|
||||
return e('input', {type: "number", onChange: props.onChange, step: props.step, value: props.value, className: props.className + extraClassName}, null);
|
||||
}
|
||||
|
||||
function TextBox(props){
|
||||
let className = props.locked ? 'locked-component' : '';
|
||||
return e('input', {className, type: "text", value: props.value, onChange: props.onChange, id: props.id});
|
||||
}
|
||||
|
||||
function Option(str, value){
|
||||
return e("option", {value: value}, str);
|
||||
}
|
||||
|
||||
function Button(props){
|
||||
let className = props.locked ? 'locked-component' : '';
|
||||
return e('button', {onClick: props.onClick, className}, 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 SendSaveEvent(){
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new CustomEvent('saveDict', {'detail' : "localStorage"}));
|
||||
}, 50)
|
||||
}
|
||||
|
||||
function CreateParamChanger(arr, setArr, index, postCB=() => {}, preCB=(val) => val){
|
||||
return (event) => {
|
||||
let newArr = arr.slice();
|
||||
newArr[index] = preCB(event.target.value);
|
||||
setArr(newArr);
|
||||
postCB();
|
||||
SendSaveEvent();
|
||||
|
||||
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);
|
||||
SendSaveEvent();
|
||||
log(`${i}, ${j} ${event.target.value}`);
|
||||
|
||||
}
|
||||
}
|
||||
76
src/enums.js
Normal file
76
src/enums.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/////////////////////////
|
||||
// ENUMERATORS
|
||||
/////////////////////////
|
||||
|
||||
|
||||
|
||||
function DataCell(element) {
|
||||
return e('td', null, element);
|
||||
}
|
||||
|
||||
// NOT A REACT FUNCTIONAL COMPONENT. MERELY RETURNS AN ARRAY WHICH IS UNPACKED
|
||||
function EnumeratorItems(index, enumBreakPoints, setEnumBreakPoints, enumNames, setEnumNames, djParam, locked){
|
||||
let items = [];
|
||||
for (let i = 0; i < MAXENUMPOINTS; i++){
|
||||
|
||||
items.push(DataCell(e(TextBox, {locked, onChange: CreateMatrixParamChanger(enumNames, setEnumNames, index, i), value: enumNames[index][i], id:`text-${djParam}-${enumNames[index][i]}`}, null)));
|
||||
// Add 1 to make up for the lower enum bound
|
||||
items.push(DataCell(e(NumberBox, {locked, onChange: CreateMatrixParamChanger(enumBreakPoints, setEnumBreakPoints, index, i + 1), value:enumBreakPoints[index][i + 1]}, null)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function EnumeratorRow(props){
|
||||
let linkedText = props.linked ? "<- mods" : "";
|
||||
|
||||
let content = e('tr', {className: 'lfo-item', id: `${props.djParam}-enum-row`},
|
||||
DataCell(DropDown({locked:props.locked, onChange: props.setInstanceNum, value:props.instanceNum, options: INSTANCEOPTIONS})),
|
||||
DataCell(DropDown({locked:props.locked, onChange: props.setDjParam, value: props.djParam, options: MODPARAMOPTIONS})),
|
||||
DataCell(e(NumberBox, {locked:props.locked, onChange: props.setEnumItemCounts, step:1, value:props.enumItems, className: 'enum-count'}, null)),
|
||||
DataCell(e(NumberBox, {locked:props.locked, onChange: CreateMatrixParamChanger(props.enumBreakPoints, props.setEnumBreakPoints, props.index, 0), value:props.enumBreakPoints[props.index][0], step:0.1}, null)),
|
||||
...(EnumeratorItems(props.index, props.enumBreakPoints, props.setEnumBreakPoints, props.enumNames, props.setEnumNames, props.djParam, props.locked).slice(0, props.enumItems * 2)),
|
||||
DataCell(e(Button, {locked:props.locked, text:'+', onClick: props.addEnum}, null)),
|
||||
DataCell(e(Button, {locked:props.locked, text:'-', onClick: props.removeEnum}, null)),
|
||||
DataCell(e("div", {className:"linked"}, linkedText))
|
||||
);
|
||||
if (props.visible){
|
||||
return content;
|
||||
};
|
||||
}
|
||||
|
||||
function denumerate(inval, count, keys, vals){
|
||||
let output = inval;
|
||||
for (let i=0; i < count; i++){
|
||||
log(vals[i]);
|
||||
if (inval == vals[i]){
|
||||
output = (parseFloat(keys[i]) + parseFloat(keys[i+1])) / 2; // linear interpolate
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
function enumerate(name, inst, inval, count, keys, vals){
|
||||
let output = "OUT OF RANGE";
|
||||
for (let i=0; i < count + 1; i++){
|
||||
if (inval <= keys[i]){
|
||||
if (i > 0)
|
||||
output = vals[i - 1];
|
||||
break
|
||||
}
|
||||
}
|
||||
let highlightedItem = document.getElementById(`text-${name}-${output}`);
|
||||
|
||||
if (highlightedItem){
|
||||
|
||||
highlightedItem.style.animation = "pulse-animation 0.5s normal";
|
||||
|
||||
highlightedItem.addEventListener('animationend', () => {
|
||||
highlightedItem.style.animation = "";
|
||||
});
|
||||
}
|
||||
|
||||
if (name !== "NONE")
|
||||
window.max.outlet(inst + " " + name + " " + output);
|
||||
}
|
||||
|
||||
378
src/lfogui.css
Normal file
378
src/lfogui.css
Normal file
@@ -0,0 +1,378 @@
|
||||
* {
|
||||
--locked-color: #5fadbf;
|
||||
--unlocked-color: #ff5153;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: white;
|
||||
--active: royalblue;
|
||||
--nonactive: rgb(205, 205, 205);
|
||||
--alert: red;
|
||||
--textcolor: black;
|
||||
}
|
||||
|
||||
@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-x: scroll;
|
||||
overflow-y: scroll;
|
||||
display: block;
|
||||
/* No floating content on sides */
|
||||
}
|
||||
|
||||
/*navigation*/
|
||||
.header {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 1em;
|
||||
border-bottom: 1px solid var(--active);
|
||||
}
|
||||
|
||||
.header button {
|
||||
border: 1px solid var(--active);
|
||||
color: var(--active);
|
||||
background-color: white;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
padding: 0.5em;
|
||||
margin: 4px;
|
||||
font-size: 0.9em;
|
||||
min-width: 4.5em;
|
||||
}
|
||||
|
||||
td button {
|
||||
background-color: white;
|
||||
color: var(--active);
|
||||
border: 1px solid var(--active);
|
||||
}
|
||||
|
||||
/* :::::::::::::: SELECTING MODULATORS/ENUMERATORS */
|
||||
|
||||
.header button.highlighted-button {
|
||||
color: var(--active);
|
||||
border: 1px solid var(--active);
|
||||
}
|
||||
|
||||
.header button.unhighlighted-button {
|
||||
color: var(--nonactive);
|
||||
border: 1px solid var(--nonactive);
|
||||
}
|
||||
|
||||
/* table */
|
||||
table {
|
||||
margin: 1em;
|
||||
padding: 0em;
|
||||
border-collapse: collapse;
|
||||
background-color: lightsteelblue;
|
||||
}
|
||||
|
||||
/* points datacells should have a min-width*/
|
||||
.enum-count {
|
||||
min-width: 5.6em;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.4em 0.3em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
thead {
|
||||
color: white;
|
||||
background-color: var(--active);
|
||||
}
|
||||
|
||||
tr,
|
||||
td {
|
||||
margin: 0em;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* input types */
|
||||
/* dropdown list */
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
option, select>* {
|
||||
font-size: 0.8em !important;
|
||||
padding: 0em !important;
|
||||
margin: 0em !important;
|
||||
min-height: 0em !important;
|
||||
}
|
||||
*/
|
||||
option {
|
||||
background-color: var(--active);
|
||||
}
|
||||
|
||||
option:not(:checked) {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* input */
|
||||
input,
|
||||
select {
|
||||
border: 1px solid var(--active);
|
||||
color: var(--active);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.5em;
|
||||
height: 1.5em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input {
|
||||
padding-left: 5px;
|
||||
/*slight padding on left*/
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 60px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.timeInput {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
#matrix {
|
||||
background-color: aquamarine;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.numbox-unclicked {
|
||||
user-select: none;
|
||||
border: solid;
|
||||
font-size: 12vw;
|
||||
}
|
||||
|
||||
.numbox-clicked {
|
||||
user-select: none;
|
||||
border: solid;
|
||||
font-size: 12vw;
|
||||
}
|
||||
|
||||
.param-input-label {
|
||||
width: 93%;
|
||||
font-size: 5vw;
|
||||
}
|
||||
|
||||
.lfo-input-label {
|
||||
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 */
|
||||
input[type="range"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
height: 8px;
|
||||
border-radius: 6px;
|
||||
background-color: white;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
-webkit-transition: .2s;
|
||||
transition: opacity .2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*slider knob*/
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
-webkit-appearance: none;
|
||||
color: var(--active);
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--active);
|
||||
box-shadow: -80px 0 0 80px var(--active);
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-progress {
|
||||
background-color: var(--active);
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: aliceblue;
|
||||
padding: 0 4px 0 4px;
|
||||
margin: 0 2px 0 2px;
|
||||
border-color: #333333;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.base-val {
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
width: 50px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.linked {
|
||||
color: red;
|
||||
border-width: 1px;
|
||||
width: 50px;
|
||||
font-size: small;
|
||||
margin-left: 2px;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
/*hide*/
|
||||
}
|
||||
|
||||
|
||||
@keyframes pulse-animation {
|
||||
0% {
|
||||
color: black;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
#pulse {
|
||||
animation: pulse-animation 0.2s normal;
|
||||
}
|
||||
|
||||
/* :::::::::::::: LOCK CSS */
|
||||
|
||||
.locked-component {
|
||||
pointer-events: none;
|
||||
background-color: #333333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Locked */
|
||||
.lock {
|
||||
margin-top: 14px;
|
||||
width: 24px;
|
||||
height: 21px;
|
||||
border: 3px solid var(--locked-color);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
-webkit-transition: all 0.1s ease-in-out;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.lock:after {
|
||||
content: "";
|
||||
display: block;
|
||||
background: var(--locked-color);
|
||||
width: 3px;
|
||||
height: 7px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -3.5px 0 0 -2px;
|
||||
-webkit-transition: all 0.1s ease-in-out;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.lock:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
bottom: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
border: 3px solid var(--locked-color);
|
||||
border-top-right-radius: 50%;
|
||||
border-top-left-radius: 50%;
|
||||
border-bottom: 0;
|
||||
-webkit-transition: all 0.1s ease-in-out;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
/* Locked Hover */
|
||||
.lock:hover:before {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Unlocked */
|
||||
.unlocked {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
|
||||
.unlocked:before {
|
||||
bottom: 130%;
|
||||
left: 31%;
|
||||
margin-left: -11.5px;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.unlocked,
|
||||
.unlocked:before {
|
||||
border-color: var(--unlocked-color);
|
||||
}
|
||||
|
||||
.unlocked:after {
|
||||
background: var(--unlocked-color);
|
||||
}
|
||||
|
||||
/* Unlocked Hover */
|
||||
.unlocked:hover {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
|
||||
.unlocked:hover:before {
|
||||
height: 10px;
|
||||
left: 40%;
|
||||
bottom: 124%;
|
||||
transform: rotate(-30deg);
|
||||
}
|
||||
660
src/lfogui.js
Normal file
660
src/lfogui.js
Normal file
@@ -0,0 +1,660 @@
|
||||
// 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", "result", "", ""];
|
||||
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 \u{25BE}` : `Show \u{25B8}`;
|
||||
|
||||
/// 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': {}, '5': {}, '6': {}});
|
||||
|
||||
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 handleChangeViewMode(event){
|
||||
setEnabled(true);
|
||||
setViewMode(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);
|
||||
window.addEventListener('viewMode', handleChangeViewMode);
|
||||
|
||||
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);
|
||||
window.removeEventListener('viewMode', handleChangeViewMode);
|
||||
};
|
||||
}, [...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);
|
||||
}
|
||||
}
|
||||
SendSaveEvent();
|
||||
rerender(!render);
|
||||
|
||||
}
|
||||
},
|
||||
removeLfo: () => {
|
||||
if (modVisibleArr.filter(x => x).length > 1) {
|
||||
let newArr = modVisibleArr.slice();
|
||||
newArr[id] = false;
|
||||
setModVisibleArr(newArr);
|
||||
SendSaveEvent();
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
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: (val) => {
|
||||
setEnumBreakPoints(val);
|
||||
SendSaveEvent
|
||||
},
|
||||
enumNames: enumNames,
|
||||
setEnumNames: (val) => {
|
||||
setEnumNames(val);
|
||||
SendSaveEvent
|
||||
},
|
||||
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);
|
||||
SendSaveEvent();
|
||||
}
|
||||
},
|
||||
removeEnum: () => {
|
||||
if (enumVisibleArr.filter(x => x).length > 1) {
|
||||
let newArr = enumVisibleArr.slice();
|
||||
newArr[id] = false;
|
||||
setEnumVisibleArr(newArr);
|
||||
SendSaveEvent();
|
||||
}
|
||||
}
|
||||
}, 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' },
|
||||
e('div', {className: 'nav'},
|
||||
displayIfEnabled(e('button', { onClick: () => setViewMode(ViewModes.MOD), className: modButtonClass}, 'Modulators')),
|
||||
displayIfEnabled(e('button', { onClick: () => setViewMode(ViewModes.ENUM), className: enumButtonClass }, 'Enumerators'))
|
||||
),
|
||||
e('button', { onClick: toggleEnabled, id: 'hide-button'}, 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', {id: x == '# points' ? 'points' : x}, 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("viewMode", (mode) => {
|
||||
window.dispatchEvent(new CustomEvent('viewMode', { 'detail': parseInt(mode) }));
|
||||
});
|
||||
|
||||
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));
|
||||
224
src/modulators.js
Normal file
224
src/modulators.js
Normal file
@@ -0,0 +1,224 @@
|
||||
/////////////////////////
|
||||
// MODULATORS
|
||||
/////////////////////////
|
||||
|
||||
|
||||
var TYPEOPTIONS = ["LFO", "Noise"];
|
||||
var SHAPETYPES = ["Sine", "SawUp", "SawDown", "Tri", "Square", "Custom_1", "Custom_2", "Custom_3", "Custom_4"];
|
||||
var NOISETYPES = ["Rand", "Line Int.", "Sine Int."]
|
||||
|
||||
var INSTANCEOPTIONS = ["1", "2", "3", "4", "5", "6"];
|
||||
|
||||
const MODPARAMOPTIONS = ["NONE", "stream", "pulse_length", "eventfulness", "event_length", "metriclarity",
|
||||
"harmoniclarity", "melodic_cohesion", "melody_scope", "tonic_pitch", "pitch_center", "pitch_range", "dynamics",
|
||||
"attenuation", "chordal_weight", "tonality-profile", "ostinato-buffer", "ostinato", "meter", "scale"];
|
||||
|
||||
const PhaseTypes = Object.freeze({
|
||||
MUSICAL: Symbol("musical"),
|
||||
TIME: Symbol("time")
|
||||
});
|
||||
|
||||
function ControlType(){
|
||||
return e('select', {className: 'control-type'}, Option("LFO"));
|
||||
}
|
||||
|
||||
function DataCell(element) {
|
||||
return e('td', null, element);
|
||||
}
|
||||
|
||||
function LfoRow(props){
|
||||
|
||||
let linkedText = props.linked ? "-> enums" : "";
|
||||
let center = props.centerVals[props.instanceNum][props.djParam];
|
||||
if (!center)
|
||||
center = 0;
|
||||
|
||||
let typeOption = null;
|
||||
|
||||
if (props.type == "LFO"){
|
||||
typeOption = DataCell(DropDown({locked:props.locked, onChange: props.setShape, value:props.shape, options: SHAPETYPES}));
|
||||
}
|
||||
else if (props.type == "Noise"){
|
||||
typeOption = DataCell(DropDown({locked:props.locked, onChange: props.setNoise, value:props.noise, options: NOISETYPES}));
|
||||
}
|
||||
|
||||
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,
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
function indexUserWave(phase, index, userDefinedWaves){
|
||||
return parseFloat(userDefinedWaves[index][Math.floor(phase * 50)]) / 127;
|
||||
}
|
||||
|
||||
function indexUserFunction(phase, index, userDefinedFunctions){
|
||||
return parseFloat(userDefinedFunctions[index][Math.floor(phase * 101)]) / 127;
|
||||
}
|
||||
|
||||
function indexWave(type, phase, userDefinedWaves, userDefinedFunctions, userDefinedTypes){
|
||||
switch (type){
|
||||
case "Sine":
|
||||
return (Math.sin(phase * Math.PI * 2) / 2) + 0.5;
|
||||
case "SawUp":
|
||||
return phase;
|
||||
case "SawDown":
|
||||
return 1 - phase;
|
||||
case "Tri":
|
||||
return phase > 0.5? (1-phase) * 2 : phase * 2;
|
||||
case "Square":
|
||||
return +(phase > 0.5);
|
||||
case "Custom_1":
|
||||
return userDefinedTypes[0] == 0 ? indexUserWave(phase, 1, userDefinedWaves) : indexUserFunction(phase, 1, userDefinedFunctions);
|
||||
case "Custom_2":
|
||||
return userDefinedTypes[1] == 0 ? indexUserWave(phase, 2, userDefinedWaves) : indexUserFunction(phase, 2, userDefinedFunctions);
|
||||
case "Custom_3":
|
||||
return userDefinedTypes[2] == 0 ? indexUserWave(phase, 3, userDefinedWaves) : indexUserFunction(phase, 3, userDefinedFunctions);
|
||||
case "Custom_4":
|
||||
return userDefinedTypes[3] == 0 ? indexUserWave(phase, 4, userDefinedWaves) : indexUserFunction(phase, 4, userDefinedFunctions);
|
||||
}
|
||||
}
|
||||
|
||||
function operateModulators(visibleArr, typeArr, instanceNumArr, paramNames, centers, freqs, mins, maxs, waveTypes, phaseArr, noiseData, userDefinedWaves, userDefinedFunctions, userDefinedTypes, currTime, beatsInMeasure, ticks){
|
||||
for (let i=0; i<paramNames.length; i++){
|
||||
if (visibleArr[i]){
|
||||
|
||||
let name = paramNames[i];
|
||||
let inst = instanceNumArr[i];
|
||||
let center = 0;
|
||||
if (centers[inst].hasOwnProperty(name)){
|
||||
center = centers[inst][name];
|
||||
}
|
||||
|
||||
let output = 0;
|
||||
|
||||
if (typeArr[i] == "LFO")
|
||||
output = operateLFO(center, inst, freqs[i], mins[i], maxs[i], waveTypes[i], phaseArr, i, userDefinedWaves, userDefinedFunctions, userDefinedTypes, name, currTime, beatsInMeasure, ticks);
|
||||
else
|
||||
output = operateNoise(center, inst, freqs[i], mins[i], maxs[i], waveTypes[i], phaseArr, i, name, noiseData, currTime, beatsInMeasure, ticks);
|
||||
if (name !== "NONE")
|
||||
window.dispatchEvent(new CustomEvent('enum', {'detail' : [inst, name, output]}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function operateLFO(center, inst, timeBaseStr, min, max, waveType, phaseArr, phaseIndex, userDefinedWaves, userDefinedFunctions, userDefinedTypes, name, currTime, beatsInMeasure, maxTicks){
|
||||
let amp = parseFloat(max) - parseFloat(min);
|
||||
let phaseType;
|
||||
let timeBase;
|
||||
|
||||
[timeBase, phaseType] = parseLfoTime(timeBaseStr, beatsInMeasure);
|
||||
let phase;
|
||||
|
||||
if (phaseType === PhaseTypes.TIME)
|
||||
phase = (currTime * timeBase + parseFloat(phaseArr[phaseIndex])) % 1.00;
|
||||
else if (phaseType === PhaseTypes.MUSICAL)
|
||||
phase = (maxTicks % timeBase) / timeBase;
|
||||
let unscaled = indexWave(waveType, phase, userDefinedWaves, userDefinedFunctions, userDefinedTypes);
|
||||
syncDisplay(inst, name, unscaled);
|
||||
|
||||
return unscaled * amp + center + parseFloat(min);
|
||||
}
|
||||
|
||||
function syncDisplay(inst, name, val) {
|
||||
let el = document.getElementById(`slider-${inst}-${name}`);
|
||||
|
||||
if (el)
|
||||
el.value = val;
|
||||
}
|
||||
|
||||
// For now, we're only using sine interpolation
|
||||
function operateNoise(center, inst, timeBaseStr, min, max, waveType, phaseArr, index, name, noiseData, currTime, beatsInMeasure, maxTicks){
|
||||
|
||||
let amp = parseFloat(max) - parseFloat(min);
|
||||
let phaseType;
|
||||
let timeBase;
|
||||
let noiseType = noiseData.noiseTypeArr[index];
|
||||
|
||||
[timeBase, phaseType] = parseLfoTime(timeBaseStr, beatsInMeasure);
|
||||
let phase;
|
||||
|
||||
if (phaseType === PhaseTypes.TIME)
|
||||
phase = (currTime * timeBase + parseFloat(phaseArr[index])) % 1.00;
|
||||
else if (phaseType === PhaseTypes.MUSICAL)
|
||||
phase = (maxTicks % timeBase) / timeBase;
|
||||
|
||||
if (noiseData.cachedNoiseValueArr1[index] == 0 || noiseData.lastPhaseArr[index] > phase){ // occurs if the phase reset to 0 or at the very start
|
||||
|
||||
noiseData.cachedNoiseValueArr2[index] = noiseData.cachedNoiseValueArr1[index];
|
||||
if (noiseData.cachedNoiseValueArr1[index] == 0)
|
||||
noiseData.cachedNoiseValueArr2[index] = center;
|
||||
|
||||
noiseData.cachedNoiseValueArr1[index] = Math.random();
|
||||
noiseData.setCachedNoiseValueArr1(noiseData.cachedNoiseValueArr1);
|
||||
noiseData.setCachedNoiseValueArr2(noiseData.cachedNoiseValueArr2);
|
||||
}
|
||||
noiseData.lastPhaseArr[index] = phase;
|
||||
noiseData.setLastPhaseArr(noiseData.lastPhaseArr);
|
||||
|
||||
//let unscaled = (noiseData.cachedNoiseValueArr[index][1] - noiseData.cachedNoiseValueArr[index][0]) * sinePhase + noiseData.cachedNoiseValueArr[index][0];
|
||||
let unscaled = interpolateNoise(noiseType, noiseData.cachedNoiseValueArr1[index], noiseData.cachedNoiseValueArr2[index], phase);
|
||||
syncDisplay(inst, name, unscaled);
|
||||
|
||||
return unscaled * amp + center + parseFloat(min);
|
||||
}
|
||||
|
||||
function interpolateNoise(type, cachedVal1, cachedVal2, phase){
|
||||
let interpVal;
|
||||
|
||||
switch (type){
|
||||
case "Sine Int.":
|
||||
interpVal = (Math.sin(Math.PI + Math.PI * phase) + 1) / 2;
|
||||
break;
|
||||
case "Rand":
|
||||
interpVal = 0;
|
||||
break;
|
||||
case "Line Int.":
|
||||
interpVal = phase;
|
||||
break;
|
||||
}
|
||||
return (cachedVal2 - cachedVal1) * interpVal + cachedVal1;
|
||||
}
|
||||
|
||||
// actual returns the period for musical timing, to avoid floating point errors
|
||||
function parseLfoTime(lfoTime, beatsInMeasure){
|
||||
if (lfoTime.slice(-2) == "hz"){
|
||||
return [parseFloat(lfoTime.slice(0, -2)), PhaseTypes.TIME];
|
||||
}
|
||||
else if (lfoTime.slice(-2) == "ms"){
|
||||
return [1000 / parseFloat(lfoTime.slice(0, -2)), PhaseTypes.TIME];
|
||||
}
|
||||
else if (lfoTime.slice(-1) == "s"){
|
||||
return [1 / parseFloat(lfoTime.slice(0, -1)), PhaseTypes.TIME];
|
||||
}
|
||||
else if ((lfoTime.match(/:/g) || []).length == 2){
|
||||
return [1 / moment.duration(lfoTime).asSeconds(), PhaseTypes.TIME];
|
||||
}
|
||||
else if ((lfoTime.match(/\./g) || []).length == 2){
|
||||
return [musicalTimingToFreq(...lfoTime.split('.'), beatsInMeasure), PhaseTypes.MUSICAL];
|
||||
}
|
||||
else {
|
||||
return [0, PhaseTypes.TIME];
|
||||
}
|
||||
}
|
||||
|
||||
function musicalTimingToFreq(bars, beats, ticks, beatsInMeasure){
|
||||
let totalTicks = (parseFloat(bars) * parseFloat(beatsInMeasure) + parseFloat(beats)) * 480 + parseFloat(ticks);
|
||||
return totalTicks;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user