max-mod-enum/modulators.js
2024-09-24 14:35:01 +02:00

147 lines
5.6 KiB
JavaScript

/////////////////////////
// MODULATORS
/////////////////////////
var TYPEOPTIONS = ["LFO", "Noise"];
var SHAPETYPES = ["Sine", "SawUp", "SawDown", "Tri", "Square", "Custom"];
var NOISETYPES = ["Sine Int."]
var INSTANCEOPTIONS = ["1", "2", "3", "4"];
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 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 = ListItem(DropDown({onChange: props.setShape, value:props.shape, options: SHAPETYPES}));
}
else if (props.type == "Noise"){
typeOption = ListItem(DropDown({onChange: props.setNoise, value:props.noise, options: NOISETYPES}));
}
let content = e('ul', {className: 'lfo-item'},
ListItem(DropDown({onChange: props.setInstanceNum, value:props.instanceNum, options: INSTANCEOPTIONS})),
ListItem(DropDown({options: TYPEOPTIONS, onChange: props.setType, value:props.type})),
typeOption,
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)),
);
if (props.visible){
return content
};
}
function indexWave(type, phase, userDefinedWave){
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":
return parseFloat(userDefinedWave[Math.floor(phase * 50)]) / 127
}
}
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]){
let name = paramNames[i];
let inst = instanceNumArr[i];
let center = 0;
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, beatsInMeasure, ticks);
if (name !== "NONE")
window.dispatchEvent(new CustomEvent('enum', {'detail' : [inst, name, output]}));
}
}
}
function operateModulator(center, inst, timeBaseStr, min, max, waveType, phaseArr, phaseI, userDefinedWave, 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[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}`);
if (el)
el.value = unscaled;
return unscaled * amp + center + parseFloat(min);
}
// 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;
}