Compare commits
68 Commits
save-cente
...
help-patch
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
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
21
common.js
21
common.js
@@ -5,7 +5,8 @@ function isNumeric(str) {
|
||||
}
|
||||
|
||||
function DropDown(props) {
|
||||
return e('select', {type: "number", onChange: props.onChange, value: props.value},
|
||||
let className = props.locked ? 'locked-component' : '';
|
||||
return e('select', {className, type: "number", onChange: props.onChange, value: props.value},
|
||||
...props.options.map((item) => Option(item)));
|
||||
}
|
||||
|
||||
@@ -18,11 +19,13 @@ function Label(text){
|
||||
}
|
||||
|
||||
function NumberBox(props){
|
||||
return e('input', {type: "number", onChange: props.onChange, step: props.step, value: props.value, className: props.className}, null);
|
||||
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){
|
||||
return e('input', {type: "text", value: props.value, onChange: props.onChange, id: props.id});
|
||||
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){
|
||||
@@ -30,7 +33,8 @@ function Option(str, value){
|
||||
}
|
||||
|
||||
function Button(props){
|
||||
return e('button', {onClick: props.onClick}, props.text);
|
||||
let className = props.locked ? 'locked-component' : '';
|
||||
return e('button', {onClick: props.onClick, className}, props.text);
|
||||
}
|
||||
|
||||
function Switch(props){
|
||||
@@ -39,12 +43,20 @@ function Switch(props){
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +68,7 @@ function CreateMatrixParamChanger(matrix, setMatrix, i, j){
|
||||
});
|
||||
newMatrix[i][j] = event.target.value;
|
||||
setMatrix(newMatrix);
|
||||
SendSaveEvent();
|
||||
log(`${i}, ${j} ${event.target.value}`);
|
||||
|
||||
}
|
||||
|
||||
28
enums.js
28
enums.js
@@ -4,14 +4,18 @@
|
||||
|
||||
|
||||
|
||||
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){
|
||||
function EnumeratorItems(index, enumBreakPoints, setEnumBreakPoints, enumNames, setEnumNames, djParam, locked){
|
||||
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)));
|
||||
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(ListItem(e(NumberBox, {onChange: CreateMatrixParamChanger(enumBreakPoints, setEnumBreakPoints, index, i + 1), value:enumBreakPoints[index][i + 1]}, null)));
|
||||
items.push(DataCell(e(NumberBox, {locked, onChange: CreateMatrixParamChanger(enumBreakPoints, setEnumBreakPoints, index, i + 1), value:enumBreakPoints[index][i + 1]}, null)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@@ -19,15 +23,15 @@ function EnumeratorItems(index, enumBreakPoints, setEnumBreakPoints, enumNames,
|
||||
function EnumeratorRow(props){
|
||||
let linkedText = props.linked ? "<- mods" : "";
|
||||
|
||||
let content = e('ul', {className: 'lfo-item', id: `${props.djParam}-enum-row`},
|
||||
ListItem(DropDown({onChange: props.setInstanceNum, value:props.instanceNum, options: INSTANCEOPTIONS})),
|
||||
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)),
|
||||
ListItem(e("div", {className:"linked"}, linkedText))
|
||||
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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
3448
example.maxpat
3448
example.maxpat
File diff suppressed because one or more lines are too long
323
lfogui.css
323
lfogui.css
@@ -1,43 +1,163 @@
|
||||
* {
|
||||
--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: hidden; /* Disable scrollbars */
|
||||
display: block; /* No floating content on sides */
|
||||
overflow-x: scroll;
|
||||
overflow-y: scroll;
|
||||
display: block;
|
||||
/* No floating content on sides */
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
/*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;
|
||||
overflow: hidden;
|
||||
background-color: #333333;
|
||||
line-height: 1.5em;
|
||||
height: 1.5em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
li {
|
||||
float: left;
|
||||
input {
|
||||
padding-left: 5px;
|
||||
/*slight padding on left*/
|
||||
}
|
||||
|
||||
|
||||
input[type=number] {
|
||||
width: 50px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 60px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.timeInput {
|
||||
width: 80px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#matrix {
|
||||
@@ -84,51 +204,38 @@ html,
|
||||
}
|
||||
|
||||
/* 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;
|
||||
input[type="range"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
height: 8px;
|
||||
border-radius: 6px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
-webkit-transition: .2s;
|
||||
transition: opacity .2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
/*slider knob*/
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
-webkit-appearance: none;
|
||||
color: var(--active);
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
@@ -136,10 +243,6 @@ html,
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.enum-count {
|
||||
background-color: aquamarine;
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: aliceblue;
|
||||
padding: 0 4px 0 4px;
|
||||
@@ -149,12 +252,13 @@ html,
|
||||
}
|
||||
|
||||
.base-val {
|
||||
background-color: lightgray;
|
||||
border-color: #333333;
|
||||
border-width: 1px;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
width: 50px;
|
||||
margin-left: 2px;
|
||||
margin-top: 1px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.linked {
|
||||
@@ -164,6 +268,8 @@ html,
|
||||
font-size: small;
|
||||
margin-left: 2px;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
/*hide*/
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +277,7 @@ html,
|
||||
0% {
|
||||
color: black;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: red;
|
||||
}
|
||||
@@ -179,3 +286,93 @@ html,
|
||||
#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);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
-->
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="./lfogui.css">
|
||||
</head>
|
||||
|
||||
@@ -19,10 +20,7 @@
|
||||
<script src="./common.js"></script>
|
||||
<script src="./enums.js"></script>
|
||||
<script src="./modulators.js"></script>
|
||||
<script src="./lfogui.js">
|
||||
|
||||
|
||||
</script>
|
||||
<script src="./lfogui.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
314
lfogui.js
314
lfogui.js
@@ -9,9 +9,10 @@ else
|
||||
const e = React.createElement;
|
||||
|
||||
let lfos = [];
|
||||
const MAXLFOS = 20;
|
||||
const MAXENUMS = 20;
|
||||
const MAXLFOS = 200;
|
||||
const MAXENUMS = 200;
|
||||
const MAXENUMPOINTS = 10;
|
||||
const MAXUSERDEFINED = 4;
|
||||
|
||||
|
||||
|
||||
@@ -20,39 +21,93 @@ const ViewModes = Object.freeze({
|
||||
ENUM: 1
|
||||
});
|
||||
|
||||
const LockModes = Object.freeze({
|
||||
UNLOCK: 0,
|
||||
LOCK: 1
|
||||
});
|
||||
|
||||
|
||||
var modPhases = Array(MAXLFOS).fill(0);
|
||||
var firstUpdateTime = Date.now();
|
||||
|
||||
const MODULATORLABELS = ["inst", "-type-", "---shape---", "-------param-------", "--timebase--", "-min-", "-max", "-phase-", "center"];
|
||||
const ENUMERATORLABELS = ["inst", "---parameter---", "-# points-"];
|
||||
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 toggleViewMode = () => {
|
||||
if (viewMode === ViewModes.MOD)
|
||||
setViewMode(ViewModes.ENUM);
|
||||
|
||||
const [lockMode, setLockMode] = React.useState(LockModes.UNLOCK);
|
||||
const toggleLockMode = () => {
|
||||
if (lockMode === LockModes.UNLOCK)
|
||||
setLockMode(LockModes.LOCK);
|
||||
else
|
||||
setViewMode(ViewModes.MOD);
|
||||
setLockMode(LockModes.UNLOCK);
|
||||
};
|
||||
|
||||
/// MODULATOR ARRAYS
|
||||
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':{}});
|
||||
const [modCenterVals, setModCenterVals] = React.useState({ '1': {}, '2': {}, '3': {}, '4': {}, '5': {}, '6': {}});
|
||||
|
||||
const [bpm, setBpm] = React.useState(100);
|
||||
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'));
|
||||
|
||||
@@ -62,23 +117,28 @@ function MasterLfoHandler(){
|
||||
const [minArr, setMinArr] = React.useState(Array(MAXLFOS).fill('0'));
|
||||
const [maxArr, setMaxArr] = React.useState(Array(MAXLFOS).fill('1'));
|
||||
|
||||
const [phaseArr, setPhaseArr] = React.useState(Array(MAXLFOS).fill('0'));
|
||||
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, modInstanceNumArr, shapeArr, djParamArr, freqArr, minArr, maxArr, phaseArr];
|
||||
const allModSetters = [setModVisibleArr, setModInstanceNumArr, setShapeArr, setDjParamArr, setFreqArr, setMinArr, setMaxArr, setPhaseArr];
|
||||
const modBlankVals = [true, '1', SHAPETYPES[0], MODPARAMOPTIONS[0], '1hz', '0', '1', '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('attenuation'));
|
||||
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;
|
||||
baseEnumBreakpoints[i][j] = j - 0.5;
|
||||
}
|
||||
}
|
||||
const [enumBreakPoints, setEnumBreakPoints] = React.useState(baseEnumBreakpoints);
|
||||
@@ -86,7 +146,7 @@ function MasterLfoHandler(){
|
||||
const getBlankEnumBreakPointRow = () => {
|
||||
let arr = []
|
||||
for (let i = 0; i < MAXENUMPOINTS + 1; i++)
|
||||
arr.push(i)
|
||||
arr.push(i - 0.5)
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -110,17 +170,14 @@ function MasterLfoHandler(){
|
||||
|
||||
React.useEffect(() => {
|
||||
function handleLoad(event) {
|
||||
|
||||
window.max.getDict(event.detail, (dict) => {
|
||||
|
||||
for (let i = 0; i<allModArrays.length; i++) {
|
||||
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]);
|
||||
}
|
||||
@@ -198,36 +255,126 @@ function MasterLfoHandler(){
|
||||
|
||||
function handleTick(event) {
|
||||
let time = (Date.now() - firstUpdateTime) / 1000;
|
||||
operateModulators(modVisibleArr, modInstanceNumArr, djParamArr, modCenterVals, freqArr, minArr, maxArr, shapeArr, phaseArr, time, bpm, beatsInMeasure);
|
||||
}
|
||||
|
||||
function handleBpm(event) {
|
||||
setBpm(event.detail);
|
||||
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('tempo', handleBpm);
|
||||
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('tempo', handleBpm);
|
||||
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, modCenterVals, render, bpm, beatsInMeasure]);
|
||||
}, [...allModArrays, ...allEnumArrays, ...allEnumMats, userDefinedWaves, userDefinedFunctions, userDefinedTypes, modCenterVals, render, beatsInMeasure, ticks]);
|
||||
|
||||
|
||||
function CheckLinked(inst, param, checkInstArr, checkParamArr) {
|
||||
@@ -248,10 +395,17 @@ function MasterLfoHandler(){
|
||||
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,
|
||||
@@ -267,13 +421,15 @@ function MasterLfoHandler(){
|
||||
max: maxArr[i],
|
||||
setMax: CreateParamChanger(maxArr, setMaxArr, i),
|
||||
|
||||
phase: phaseArr[i],
|
||||
setPhase: CreateParamChanger(phaseArr, setPhaseArr, 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);
|
||||
@@ -289,13 +445,16 @@ function MasterLfoHandler(){
|
||||
}
|
||||
}
|
||||
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: () => {
|
||||
@@ -303,6 +462,7 @@ function MasterLfoHandler(){
|
||||
let newArr = modVisibleArr.slice();
|
||||
newArr[id] = false;
|
||||
setModVisibleArr(newArr);
|
||||
SendSaveEvent();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -320,15 +480,21 @@ function MasterLfoHandler(){
|
||||
enumContents.push(
|
||||
e(EnumeratorRow, {
|
||||
index: i,
|
||||
|
||||
locked: lockMode,
|
||||
instanceNum: enumInstanceNumArr[i],
|
||||
setInstanceNum: CreateParamChanger(enumInstanceNumArr, setEnumInstanceNumArr, i),
|
||||
enumItems: enumItemCounts[i],
|
||||
setEnumItemCounts: CreateParamChanger(enumItemCounts, setEnumItemCounts, i),
|
||||
enumBreakPoints: enumBreakPoints,
|
||||
setEnumBreakPoints: setEnumBreakPoints,
|
||||
setEnumBreakPoints: (val) => {
|
||||
setEnumBreakPoints(val);
|
||||
SendSaveEvent
|
||||
},
|
||||
enumNames: enumNames,
|
||||
setEnumNames: setEnumNames,
|
||||
setEnumNames: (val) => {
|
||||
setEnumNames(val);
|
||||
SendSaveEvent
|
||||
},
|
||||
visible: enumVisibleArr[i],
|
||||
djParam: enumDjParamArr[i],
|
||||
setDjParam: CreateParamChanger(enumDjParamArr, setEnumDjParamArr, i),
|
||||
@@ -374,6 +540,7 @@ function MasterLfoHandler(){
|
||||
}
|
||||
}
|
||||
rerender(!render);
|
||||
SendSaveEvent();
|
||||
}
|
||||
},
|
||||
removeEnum: () => {
|
||||
@@ -381,6 +548,7 @@ function MasterLfoHandler(){
|
||||
let newArr = enumVisibleArr.slice();
|
||||
newArr[id] = false;
|
||||
setEnumVisibleArr(newArr);
|
||||
SendSaveEvent();
|
||||
}
|
||||
}
|
||||
}, null)
|
||||
@@ -389,24 +557,47 @@ function MasterLfoHandler(){
|
||||
|
||||
|
||||
var grid;
|
||||
var title;
|
||||
var modButtonClass;
|
||||
var enumButtonClass;
|
||||
var labels;
|
||||
if (viewMode === ViewModes.MOD) {
|
||||
grid = modContents;
|
||||
title = "MODULATORS";
|
||||
modButtonClass = "highlighted-button";
|
||||
enumButtonClass = "unhighlighted-button";
|
||||
labels = MODULATORLABELS;
|
||||
}
|
||||
else {
|
||||
grid = enumContents;
|
||||
title = "ENUMERATORS";
|
||||
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(Switch, {ontoggle: toggleViewMode}, null),
|
||||
e('h5', null, title),
|
||||
e('ul', null, ...labels.map(x => ListItem(Label(x)))),
|
||||
e('div', {id: 'grid'}, ...grid)
|
||||
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)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -419,19 +610,46 @@ if (!DEBUG){
|
||||
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("dumpNN", () => {
|
||||
window.dispatchEvent(new CustomEvent('dumpNN'));
|
||||
});
|
||||
|
||||
window.max.bindInlet("tempo", (val) => {
|
||||
window.dispatchEvent(new CustomEvent('tempo', {'detail' : val}));
|
||||
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);
|
||||
|
||||
186
modulators.js
186
modulators.js
@@ -2,19 +2,29 @@
|
||||
// MODULATORS
|
||||
/////////////////////////
|
||||
|
||||
var SHAPETYPES = ["Sine", "SawUp", "SawDown", "Tri", "Square"];
|
||||
|
||||
var INSTANCEOPTIONS = ["1", "2", "3", "4"];
|
||||
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){
|
||||
|
||||
@@ -23,28 +33,45 @@ function LfoRow(props){
|
||||
if (!center)
|
||||
center = 0;
|
||||
|
||||
let content = e('ul', {className: 'lfo-item'},
|
||||
ListItem(DropDown({onChange: props.setInstanceNum, value:props.instanceNum, options: INSTANCEOPTIONS})),
|
||||
ListItem(ControlType()),
|
||||
ListItem(DropDown({onChange: props.setShape, value:props.shape, options: SHAPETYPES})),
|
||||
ListItem(DropDown({onChange: props.setDjParam, value: props.djParam, options: MODPARAMOPTIONS})),
|
||||
ListItem(e("input", {onChange:props.setFreq, value:props.freq, className:"timeInput"}, null)),
|
||||
ListItem(e(NumberBox, {onChange:props.setMin, value:props.min, step:0.1}, null)),
|
||||
ListItem(e(NumberBox, {onChange:props.setMax, value:props.max, step:0.1}, null)),
|
||||
//ListItem(e(NumberBox, {onChange:props.setAmp, value:props.amp, step:0.1}, null)),
|
||||
ListItem(e(NumberBox, {onChange:props.setPhase, value:props.phase, step:0.1}, null)),
|
||||
ListItem(e("div", {className:"base-val"}, center.toString())),
|
||||
ListItem(e("input", {type: 'range', min: 0, max: 1, step: 0.01, readonly: true, id: `slider-${props.instanceNum}-${props.djParam}`})),
|
||||
ListItem(e(Button, {text:'+', onClick: props.addLfo}, null)),
|
||||
ListItem(e(Button, {text:'-', onClick: props.removeLfo}, null)),
|
||||
ListItem(e("div", {className:"linked"}, linkedText)),
|
||||
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
|
||||
return content;
|
||||
};
|
||||
}
|
||||
|
||||
function indexWave(type, phase){
|
||||
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;
|
||||
@@ -56,10 +83,18 @@ function indexWave(type, phase){
|
||||
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, instanceNumArr, paramNames, centers, freqs, mins, maxs, waveTypes, phaseArr, currTime, bpm, beatsInMeasure){
|
||||
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]){
|
||||
|
||||
@@ -69,54 +104,121 @@ function operateModulators(visibleArr, instanceNumArr, paramNames, centers, freq
|
||||
if (centers[inst].hasOwnProperty(name)){
|
||||
center = centers[inst][name];
|
||||
}
|
||||
let output = operateModulator(center, inst, freqs[i], mins[i], maxs[i], waveTypes[i], phaseArr, i, name, currTime, bpm, beatsInMeasure);
|
||||
|
||||
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 operateModulator(center, inst, freq, min, max, waveType, phaseArr, phaseI, name, currTime, bpm, beatsInMeasure){
|
||||
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;
|
||||
|
||||
freq = parseLfoTime(freq, bpm, beatsInMeasure);
|
||||
let phase = (currTime * freq + parseFloat(phaseArr[phaseI])) % 1.00;
|
||||
let unscaled = indexWave(waveType, phase);
|
||||
let el = document.getElementById(`slider-${inst}-${name}`);
|
||||
[timeBase, phaseType] = parseLfoTime(timeBaseStr, beatsInMeasure);
|
||||
let phase;
|
||||
|
||||
if (el)
|
||||
el.value = unscaled;
|
||||
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}`);
|
||||
|
||||
function parseLfoTime(lfoTime, bpm, beatsInMeasure){
|
||||
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));
|
||||
return [parseFloat(lfoTime.slice(0, -2)), PhaseTypes.TIME];
|
||||
}
|
||||
else if (lfoTime.slice(-2) == "ms"){
|
||||
return 1000 / parseFloat(lfoTime.slice(0, -2));
|
||||
return [1000 / parseFloat(lfoTime.slice(0, -2)), PhaseTypes.TIME];
|
||||
}
|
||||
else if (lfoTime.slice(-1) == "s"){
|
||||
return 1 / parseFloat(lfoTime.slice(0, -1));
|
||||
return [1 / parseFloat(lfoTime.slice(0, -1)), PhaseTypes.TIME];
|
||||
}
|
||||
else if ((lfoTime.match(/:/g) || []).length == 2){
|
||||
return 1 / moment.duration(lfoTime).asSeconds();
|
||||
return [1 / moment.duration(lfoTime).asSeconds(), PhaseTypes.TIME];
|
||||
}
|
||||
else if ((lfoTime.match(/\./g) || []).length == 2){
|
||||
return musicalTimingToFreq(...lfoTime.split('.'), bpm, beatsInMeasure)
|
||||
return [musicalTimingToFreq(...lfoTime.split('.'), beatsInMeasure), PhaseTypes.MUSICAL];
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
return [0, PhaseTypes.TIME];
|
||||
}
|
||||
}
|
||||
|
||||
function musicalTimingToFreq(bars, beats, ticks, bpm, beatsInMeasure){
|
||||
let totalTicks = (parseFloat(bars) * parseFloat(beatsInMeasure) + beats) * 480 + parseFloat(ticks);
|
||||
let tpm = bpm * 480;
|
||||
let cyclesPerMinute = tpm / totalTicks;
|
||||
let hz = cyclesPerMinute / 60;
|
||||
return hz;
|
||||
function musicalTimingToFreq(bars, beats, ticks, beatsInMeasure){
|
||||
let totalTicks = (parseFloat(bars) * parseFloat(beatsInMeasure) + parseFloat(beats)) * 480 + parseFloat(ticks);
|
||||
return totalTicks;
|
||||
}
|
||||
|
||||
|
||||
93
myStorage.json
Normal file
93
myStorage.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user