Merge pull request 'musical-timing-phase' (#2) from musical-timing-phase into main

Reviewed-on: https://gitea.lz-storage.synology.me/kieran-mcauliffe/max-mod-enum/pulls/2
This commit is contained in:
kieran-mcauliffe 2024-09-03 11:37:51 +02:00
commit 37a65058bc
3 changed files with 108 additions and 58 deletions

View File

@ -10,7 +10,7 @@
}
,
"classnamespace" : "box",
"rect" : [ 34.0, 76.0, 981.0, 763.0 ],
"rect" : [ 34.0, 76.0, 1155.0, 763.0 ],
"bglocked" : 0,
"openinpresentation" : 0,
"default_fontsize" : 12.0,
@ -39,6 +39,44 @@
"subpatcher_template" : "",
"assistshowspatchername" : 0,
"boxes" : [ {
"box" : {
"fontname" : "Arial",
"fontsize" : 13.0,
"id" : "obj-29",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "bang" ],
"patching_rect" : [ 465.0, 141.0, 210.0, 23.0 ],
"text" : "metro @interval 40 ticks @active 1"
}
}
, {
"box" : {
"id" : "obj-28",
"maxclass" : "button",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "bang" ],
"parameter_enable" : 0,
"patching_rect" : [ 826.0, 79.0, 24.0, 24.0 ]
}
}
, {
"box" : {
"id" : "obj-3",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 817.0, 147.0, 80.0, 22.0 ],
"text" : "prepend ticks"
}
}
, {
"box" : {
"id" : "obj-54",
"maxclass" : "newobj",
@ -156,7 +194,7 @@
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 385.0, 715.0, 84.0, 36.0 ],
"text" : "metriclarity 22.176645"
"text" : "metriclarity 0.5"
}
}
@ -244,18 +282,6 @@
"text" : "prepend timesig"
}
}
, {
"box" : {
"id" : "obj-52",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 545.0, 135.0, 89.0, 22.0 ],
"text" : "prepend tempo"
}
}
, {
"box" : {
@ -299,8 +325,7 @@
"numinlets" : 1,
"numoutlets" : 5,
"outlettype" : [ "preset", "int", "preset", "int", "" ],
"patching_rect" : [ 934.0, 27.0, 100.0, 40.0 ],
"pattrstorage" : "storage"
"patching_rect" : [ 934.0, 27.0, 100.0, 40.0 ]
}
}
@ -784,6 +809,27 @@
"source" : [ "obj-27", 1 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-47", 0 ],
"source" : [ "obj-28", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-47", 0 ],
"source" : [ "obj-29", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-2", 0 ],
"source" : [ "obj-3", 0 ]
}
}
, {
"patchline" : {
@ -886,8 +932,8 @@
}
, {
"patchline" : {
"destination" : [ "obj-52", 0 ],
"source" : [ "obj-47", 4 ]
"destination" : [ "obj-3", 0 ],
"source" : [ "obj-47", 7 ]
}
}
@ -904,13 +950,6 @@
"source" : [ "obj-5", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-2", 0 ],
"source" : [ "obj-52", 0 ]
}
}
, {
"patchline" : {

View File

@ -52,7 +52,7 @@ function MasterLfoHandler(){
const [modCenterVals, setModCenterVals] = React.useState({'1':{}, '2':{}, '3':{}, '4':{}});
const [bpm, setBpm] = React.useState(100);
const [ticks, setTicks] = React.useState(0);
const [beatsInMeasure, setBeatsInMeasure] = React.useState(4);
const [shapeArr, setShapeArr] = React.useState(Array(MAXLFOS).fill('Sine'));
@ -200,11 +200,8 @@ function MasterLfoHandler(){
function handleTick(event) {
let time = (Date.now() - firstUpdateTime) / 1000;
operateModulators(modVisibleArr, modInstanceNumArr, djParamArr, modCenterVals, freqArr, minArr, maxArr, shapeArr, phaseArr, userDefinedWave, time, bpm, beatsInMeasure);
}
function handleBpm(event) {
setBpm(event.detail);
operateModulators(modVisibleArr, modInstanceNumArr, djParamArr, modCenterVals, freqArr, minArr, maxArr, shapeArr, phaseArr, userDefinedWave, time, beatsInMeasure, ticks);
}
function handleTimeSig(event) {
@ -215,15 +212,19 @@ function MasterLfoHandler(){
setUserDefinedWave(event.detail);
}
function handleMaxTicks(event){
setTicks(event.detail);
}
window.addEventListener('loadDict', handleLoad);
window.addEventListener('saveDict', handleSave);
window.addEventListener('tick', handleTick);
window.addEventListener('param', handleParam);
window.addEventListener('enum', handleEnum);
window.addEventListener('tempo', handleBpm);
window.addEventListener('timesig', handleTimeSig);
window.addEventListener('userWave', handleChangeUserWave);
window.addEventListener('maxTicks', handleMaxTicks);
return () => {
window.removeEventListener('loadDict', handleLoad);
@ -231,11 +232,11 @@ function MasterLfoHandler(){
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('maxTicks', handleMaxTicks);
};
}, [...allModArrays, ...allEnumArrays, ...allEnumMats, modCenterVals, render, bpm, beatsInMeasure]);
}, [...allModArrays, ...allEnumArrays, ...allEnumMats, modCenterVals, render, beatsInMeasure, ticks]);
function CheckLinked(inst, param, checkInstArr, checkParamArr){
@ -432,14 +433,14 @@ if (!DEBUG){
window.dispatchEvent(new CustomEvent('param', {'detail' : [inst, paramName, val]}));
});
window.max.bindInlet("tempo", (val) => {
window.dispatchEvent(new CustomEvent('tempo', {'detail' : val}));
});
window.max.bindInlet("timesig", (top, bottom) => {
window.dispatchEvent(new CustomEvent('timesig', {'detail' : [top, bottom]}));
});
window.max.bindInlet("ticks", (val) => {
window.dispatchEvent(new CustomEvent('maxTicks', {'detail' : val}));
});
window.max.bindInlet("userWave", (...points) => {
window.dispatchEvent(new CustomEvent('userWave', {'detail' : points}));
});

View File

@ -10,6 +10,11 @@ const MODPARAMOPTIONS = ["NONE", "stream", "pulse_length", "eventfulness", "even
"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"));
}
@ -61,7 +66,7 @@ function indexWave(type, phase, userDefinedWave){
}
}
function operateModulators(visibleArr, instanceNumArr, paramNames, centers, freqs, mins, maxs, waveTypes, phaseArr, userDefinedWave, currTime, bpm, beatsInMeasure){
function operateModulators(visibleArr, instanceNumArr, paramNames, centers, freqs, mins, maxs, waveTypes, phaseArr, userDefinedWave, currTime, beatsInMeasure, ticks){
for (let i=0; i<paramNames.length; i++){
if (visibleArr[i]){
@ -71,18 +76,26 @@ 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, userDefinedWave, name, currTime, bpm, beatsInMeasure);
let output = operateModulator(center, inst, freqs[i], mins[i], maxs[i], waveTypes[i], phaseArr, i, userDefinedWave, name, 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, userDefinedWave, name, currTime, bpm, beatsInMeasure){
let amp = parseFloat(max) - parseFloat(min);
function operateModulator(center, inst, timeBaseStr, min, max, waveType, phaseArr, phaseI, userDefinedWave, name, currTime, beatsInMeasure, maxTicks){
freq = parseLfoTime(freq, bpm, beatsInMeasure);
let phase = (currTime * freq + parseFloat(phaseArr[phaseI])) % 1.00;
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[phaseI])) % 1.00;
else if (phaseType === PhaseTypes.MUSICAL)
phase = (maxTicks % timeBase) / timeBase;
let unscaled = indexWave(waveType, phase, userDefinedWave);
let el = document.getElementById(`slider-${inst}-${name}`);
@ -92,33 +105,30 @@ function operateModulator(center, inst, freq, min, max, waveType, phaseArr, phas
return unscaled * amp + center + parseFloat(min);
}
function parseLfoTime(lfoTime, bpm, beatsInMeasure){
// 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;
}