import XebraNode from "./base.js";
/*
* Unfortunately Max doesn't set the values for gradient related parameters
* properly when an object is created and the values are set to defaults.
* That's why we set some default values here
* see #10136
*/
const PARAM_DEFAULT_VALUES = {
bgfillcolor_pt1: [0.5, 0.05],
bgfillcolor_pt2: [0.5, 0.95]
};
/**
* @private
*/
function _getDefaultParamValue(type, value) {
if ((!value || (value.constructor === Array && !value.length)) &&
PARAM_DEFAULT_VALUES.hasOwnProperty(type)
) {
return PARAM_DEFAULT_VALUES[type];
}
return value;
}
/*
* Communication between xebra-state and Max uses a modified form of OSC,
* which passes values along with types (h for integer, d for float, etc.).
* Javascript doesn't differentiate between integers and floats. So,
* xebra-state needs to store the types of each parameter when Max
* updates the value of that parameter. There are some special parameters,
* however, that are not initialized by Max. These types must be known
* beforehand and are hardcoded.
*/
const HARDCODED_TYPES = {
moved_touch: ["h", "h", "h", "h", "h", "h", "d", "d"],
up_down_cancelled_touch: ["h", "h", "h", "h", "h", "h", "d", "d"],
pinch: ["h", "h", "h", "d", "d", "h"],
region: ["h", "h", "h", "h", "h", "h"],
rotate: ["h", "h", "h", "d", "d", "h"],
swipe: ["h", "h", "h", "h", "h"],
tap: ["h", "h", "h", "d", "d", "h"],
rawsend: ["h", "h"],
rotationrate: ["h", "h", "d", "d", "d", "d"],
gravity: ["h", "h", "d", "d", "d", "d"],
accel: ["h", "h", "d", "d", "d", "d"],
orientation: ["h", "h", "d", "d", "d", "d"],
rawaccel: ["h", "h", "d", "d", "d", "d"],
touchy: ["s", "s", "h", "h"],
setcell: ["h", "h", "h"],
directions: "h*",
constraint: "h*"
};
function _getHardcodedOSCTypes(type) {
if (HARDCODED_TYPES.hasOwnProperty(type)) return HARDCODED_TYPES[type];
return null;
}
/**
* @class
* @desc <strong>Constructor for internal use only</strong>
*
* Representation of a Max object parameter. Usually, a parameter is simply a Max attribute. Setting the value of the
* parameter will update the Max object attribute with the same name. Some parameters do not map to attributes, for
* example the "distance" parameter of a slider object, which controls the value of the slider.
* @extends XebraNode
*/
class ParamNode extends XebraNode {
/**
* @param {Number} id - The id of the node
* @param {String} type - Type identifier of the node
* @param {Number} creationSeq - The sequence number for the creation of this node
*/
constructor(id, type, creationSeq) {
super(id, type, creationSeq);
this._sequence = 0;
this._currentRemoteSequence = 0;
this._value = _getDefaultParamValue(type, null);
// Not beautiful but given the way we have to mirror all OSC types across Max and the client
// we hardcode the types for params that don't receive an initial value here
this._types = _getHardcodedOSCTypes(type);
}
/**
* The sequence number associated with the most recent modification. Whenever the value of the parameter is updated
* in Max or some other remote endpoint, this sequence number will increase.
* @type {number}
*/
get remoteSequence() {
return this._currentRemoteSequence;
}
// Bound callbacks using fat arrow notation
/**
* @private
* @param {ParamNode} param - The changed parameter
*/
_onParamChange = (param) => {
this.emit("change", this);
}
/**
* @private
* @param {ParamNode} param - The changed parameter
*/
_onParamSet = (param) => {
this.emit("set", this);
}
// End of bound callbacks
/**
* @private
*/
_storeValue(value) {
const val = _getDefaultParamValue(this.type, value);
if (val && val.length === 1) {
this._value = val[0];
} else {
this._value = val;
}
}
/**
* getter for the OSC value types
* @ignore
* @readonly
* @type {string[]}
*/
get types() {
if (typeof this._types === "string") {
if (this._types.charAt(1) === "*") {
if (this._value === null) return [];
return new Array(this._value.length).fill(this._types.charAt(0));
}
}
return this._types;
}
/**
* The client modification sequence number
* @ignore
* @readonly
* @type {number}
*/
get sequence() {
return this._sequence;
}
/**
* The current value of this parameter. Setting the value will trigger an update in Max, if connected. This will not
* cause an ObjectNode.param_changed event to fire, however, since this is only fired on changes that come from Max.
* @type {Xebra.ParamValueType}
* @fires ParamNode#set
*/
get value() {
// handle enums
if (this.getParamValue("style") === "enumindex") {
const values = this.getParamValue("enumvals");
if (!values) return null;
return values[this._value];
}
return this._value;
}
set value(value) {
this._storeValue(value);
this._sequence++;
/**
* Parameter set event
* @event ParamNode#set
* @ignore
* @param {ParamNode} param this
*/
this.emit("set", this);
}
/**
* Inits the node with the given value.
* @ignore
* @param {Xebra.ParamValueType} value [description]
*/
init(value) {
this._storeValue(value);
}
/**
* Modifies the value of the parameter. This is used in order to apply remote modifications. Use the param.value
* getter/setter if you want to read/change the value.
* @ignore
* @param {Xebra.ParamValueType} value - The new value
* @param {string[]} value - The OSC types
* @param {number} remoteSequence - The remote sequence number
* @fires ParamNode.change
*/
modify(value, types, remoteSequence) {
if (this._currentRemoteSequence && this._currentRemoteSequence >= remoteSequence) return;
this._currentRemoteSequence = remoteSequence;
this._storeValue(value);
// don't overwrite types for certain value
if (!HARDCODED_TYPES.hasOwnProperty(this.type)) {
this._types = types;
}
/**
* Parameter change event
* @event ParamNode.change
* @param {ParamNode} param this
*/
this.emit("change", this);
}
}
export default ParamNode;