summaryrefslogtreecommitdiff
path: root/RHMixxx1280x800/MixxController.js
diff options
context:
space:
mode:
Diffstat (limited to 'RHMixxx1280x800/MixxController.js')
-rw-r--r--RHMixxx1280x800/MixxController.js363
1 files changed, 363 insertions, 0 deletions
diff --git a/RHMixxx1280x800/MixxController.js b/RHMixxx1280x800/MixxController.js
new file mode 100644
index 0000000..0a26df2
--- /dev/null
+++ b/RHMixxx1280x800/MixxController.js
@@ -0,0 +1,363 @@
+function MixxxController(id) {
+ this.midiMappings = new Object();
+ this.namedMappings = new Object();
+ this.controls = new Object();
+ this.id = id;
+
+ /*
+ * Convenience method that returns a Mixxx value for the given control
+ */
+ this.getMixxxValue = function(control) {
+ return engine.getValue(control["group"]["name"], control["name"]);
+ }
+
+ /*
+ * Sets a Mixxx value for the given control. If a coversion has been
+ * registered, it is taken into account. This method also takes care
+ * of softTakeover, if enabled.
+ */
+ this.setMixxxValue = function(control, value) {
+ var conversion = control["conversion"];
+ if (conversion) {
+ if (conversion["name"] != "none") {
+ value = script[conversion["name"]](value, conversion["min"], conversion["max"], conversion["mid"]);
+ }
+ }
+ else {
+ value = script["absoluteNonLin"](value, 0, 1, 4);
+ }
+ if (control["softTakeover"] && (Math.abs(this.getMixxxValue(control) - value) < 0.1))
+ {
+ control["softTakeover"] = false;
+ }
+ else if (!control["softTakeover"]) {
+ engine.setValue(control["group"]["name"], control["name"], value);
+ control["value"] = value;
+ }
+ }
+
+ /*
+ * Convenience method that returns a control based on its midi number.
+ */
+ this.getByMidiNo = function (midiNo)
+ {
+ return this.midiMappings[midiNo];
+ }
+
+ /*
+ * Convenience method that returns a control based on its group and control.
+ */
+ this.getByNames = function(group, control)
+ {
+ return this.namedMappings[group+control];
+ }
+
+ /*
+ * Creates a multi button that supports klicking and turning.
+ * Parameters are:
+ * # Mixxx value that is set if button is klicked (may be empty if button cannot be pressed)
+ * # Midi number
+ * # Mixxx value that is set if button is turned left
+ * # Mixxx value that is set if button is turned right
+ */
+ this.createMultiButton = function(name, id, left, right)
+ {
+ var button = this.createControl(name, id);
+ button.setConversion("none");
+ button["isMultiButton"] = true;
+ button["left"] = left;
+ button["right"] = right;
+ return button;
+ }
+
+ /*
+ * Creates a button that executed a Mixxx function without having a concrete
+ * state (e.g. the beatsync button).
+ * Parameters are:
+ * # Mixxx value that is set if button is klicked
+ * # Midi number
+ */
+ this.createButton = function(name, id)
+ {
+ var button = this.createControl(name, id);
+ button.setConversion("none");
+ button["isButton"] = true;
+ return button;
+ }
+
+ /*
+ * Creates a switch that can be switches on and off (e.g. the play button).
+ * Parameters are:
+ * # Mixxx value that is toggled if switch is pressed
+ * # Midi number
+ */
+ this.createSwitch = function(name, id)
+ {
+ var button = this.createControl(name, id);
+ button.setConversion("none");
+ button["activated"] = false;
+ button["isSwitch"] = true;
+ return button;
+ }
+
+ /*
+ * Creates a control that can be used for volume sliders, filter knobs, etc.
+ * Parameters are:
+ * # Mixxx value that is changed if the control is used
+ * # Midi number
+ * Various behaviours can be enabled on a control:
+ * # enableFireOnKeyUp() (control is fired if released, not if pressed. Useful for buttons/switches)
+ * # enableSoftTakeover() (custom softTakeover function that is some smarter than Mixxx's default implementation)
+ * # addCallback() (add a MixxxController callback. Currently available is only "kill" to emulate a kill switch for equalizers)
+ * # addExternalCallback() (add a callback to a custom function specific for a certain controller)
+ * # setConversion(name, min, max, mid) (supported: absoluteLin, absoluteNonLin)
+ * headVolume, Master volume, crossfader, headMix and volume are automatically enhanced with a fitting conversion.
+ */
+ this.createControl = function(name, id)
+ {
+ var control = new Object();
+ control["name"] = name;
+ control["midiNo"] = id;
+ control["ledMidiNo"] = id;
+ control["callbacks"] = new Object();
+ control["fireOnKeyUp"] = false;
+ this.midiMappings[id] = control;
+ control.enableFireOnKeyUp = function() {
+ control["fireOnKeyUp"] = true;
+ return control;
+ }
+ control.enableSoftTakeover = function() {
+ control["softTakeover"] = true;
+ return control;
+ }
+ control.addCallback = function(name) {
+ control["callbacks"][name] = name;
+ return control;
+ }
+ control.addCallback = function(name) {
+ control["callbacks"][name] = name;
+ return control;
+ }
+ control.addExternalCallback = function(name) {
+ control["externalCallbacks"][name] = name;
+ return control;
+ }
+ control.setConversion = function(name, min, max, mid) {
+ control["conversion"] = new Object();
+ control["conversion"]["name"] = name;
+ control["conversion"]["min"] = min;
+ control["conversion"]["max"] = max;
+ control["conversion"]["mid"] = mid;
+ return control;
+ }
+ control.setLEDMidiNo = function(id) {
+ control["ledMidiNo"] = id;
+ return control;
+ }
+ return control;
+ }
+
+ /*
+ * Create a new control group to which multiple controls can be added. A control must have a name that matches
+ * the Mixxx group ([Master], [Channel1], ...)
+ */
+ this.createGroup = function(name, id)
+ {
+ var group = new Object();
+ group["name"] = name;
+ if (name.indexOf("[Channel") == 0) {
+ group["id"] = name.substring(8, 9);
+ }
+ group["controller"] = this;
+ group["scratching"] = false;
+ group["scratchingEnabled"] = false;
+ group.addControl = function(control) {
+ group[control["name"]] = control;
+ control["group"] = this;
+ group["controller"].namedMappings[group["name"]+control["name"]] = control;
+
+ if (control["softTakeover"]) {
+ control["value"] = group["controller"].getMixxxValue(control);
+ engine.connectControl(group["name"], control["name"], this["controller"].id+".softTakeover");
+ }
+ if (control["name"] == "play") {
+ engine.connectControl(group["name"], control["name"], this["controller"].id+".playListener");
+ }
+ for (var callback in control["callbacks"]) {
+ engine.connectControl(group["name"], control["name"], this["controller"].id+"."+callback);
+ }
+ for (var callback in control["externalCallbacks"]) {
+ engine.connectControl(group["name"], control["name"], callback);
+ }
+
+ var name = control["name"];
+ if (name == "headVolume") {
+ control.setConversion("absoluteNonLin", 0, 5, 1);
+ } else if ((group["name"] == "[Master]") && (name == "volume")) {
+ control.setConversion("absoluteNonLin", 0, 5, 1);
+ } else if (name == "crossfader") {
+ control.setConversion("absoluteLin", -1, 1);
+ } else if (name == "headMix") {
+ control.setConversion("absoluteLin", -1, 1);
+ } else if (name == "volume") {
+ control.setConversion("absoluteLin", 0, 1);
+ }
+
+ return group;
+ }
+ this[name] = group;
+ return group;
+ }
+
+ /*
+ * Enabled softTakeover on a control if the controllers value differs to much from Mixxx's state.
+ * This can be the case on startup of mix or if a control has been modified inside of Mixxx,
+ * if beatsync has been used, ...
+ */
+ this.softTakeover = function(value, group, control) {
+ var controller = this["controller"];
+ var control = controller.getByNames(group, control);
+ if (Math.abs(control["value"] - value) > 0.1) {
+ control["softTakeover"] = true;
+ }
+ }
+
+ /*
+ * If a controller has no eq kill switch, this method can be used to kill frequencies
+ * if a knob has been moved to zero. To enable this feature, use .addCallback("kill")
+ * on the created control before adding it to its group.
+ */
+ this.kill = function(value, group, control) {
+ var controller = this["controller"];
+ var control = controller.getByNames(group, control);
+ if (controller.getMixxxValue(control) == 0) {
+ engine.setValue(control["group"]["name"], control["name"]+"Kill", 1);
+ } else if (engine.getValue(control["group"]["name"], control["name"]+"Kill") == 1) {
+ engine.setValue(control["group"]["name"], control["name"]+"Kill", 0);
+ }
+ }
+
+ /*
+ * This callback is added to the play state and toggles of the play buttons led if needed.
+ */
+ this.playListener = function (value, group, control) {
+ var controller = this["controller"];
+ var control = controller.getByNames(group, control);
+ controller.led(controller.getMixxxValue(control), control["group"]["name"], control["name"]);
+ control["activated"] = (controller.getMixxxValue(control) == 1);
+ }
+
+ /*
+ * Switch a led for a control on or off.
+ */
+ this.led = function (value, group, control) {
+ var led = this.getByNames(group, control)["ledMidiNo"];
+ if (value == 1) {
+ midi.sendShortMsg(0x90, led, 0x7F);
+ } else if (value == 0) {
+ midi.sendShortMsg(0x90, led, 0x0);
+ }
+ }
+
+ /*
+ * This method must be called from a concrete controller script to get the cow flying.
+ */
+ this.dispatch = function (c, midi, value, status) {
+ var control = this.getByMidiNo(midi);
+ if (control["isButton"]) {
+ this.handleButton(control, value);
+ } else if (control["isSwitch"]) {
+ this.handleSwitch(control, value);
+ } else if (control["isMultiButton"]) {
+ this.handleMultiButton(control, value);
+ } else if (control["name"] == "jogWheel") {
+ this.wheelTurn(c, midi, value, status);
+ } else {
+ this.setMixxxValue(control, value);
+ }
+ }
+
+ /*
+ * This method handles multiButtons.
+ */
+ this.handleMultiButton = function (control, value) {
+ if (value == 127) {
+ engine.setValue(control["group"]["name"], control["name"], 1);
+ } else if (value == 0x3f) {
+ engine.setValue(control["group"]["name"], control["left"], 1);
+ } else {
+ engine.setValue(control["group"]["name"], control["right"], 1);
+ }
+ }
+
+ /*
+ * This method handles buttons.
+ */
+ this.handleButton = function (control, value) {
+ if (value == 0) {
+ this.led(0, control["group"]["name"], control["name"]);
+ } else {
+ this.led(1, control["group"]["name"], control["name"]);
+ }
+ if ((control["fireOnKeyUp"] && (value == 0)) || (!control["fireOnKeyUp"] && (value == 127))) {
+ this.setMixxxValue(control, 1);
+ } else {
+ this.setMixxxValue(control, 0);
+ }
+ }
+
+ /*
+ * This method handles switches.
+ */
+ this.handleSwitch = function (control, value) {
+ if ((control["fireOnKeyUp"] && (value == 0)) || (!control["fireOnKeyUp"] && (value == 127))) {
+ if (control["name"] == "scratch") {
+ var group = control["group"];
+ group["scratchingEnabled"] = !group["scratchingEnabled"];
+ if (group["scratchingEnabled"]) {
+ this.led(1, control["group"]["name"], control["name"]);
+ } else {
+ this.led(0, control["group"]["name"], control["name"]);
+ }
+ } else if (control["activated"]) {
+ this.setMixxxValue(control, 0);
+ this.led(0, control["group"]["name"], control["name"]);
+ } else {
+ this.setMixxxValue(control, 1);
+ this.led(1, control["group"]["name"], control["name"]);
+ }
+ control["activated"] = !control["activated"];
+ }
+ }
+
+ /*
+ * This method handles a jog wheel for scratching.
+ */
+ this.wheelTurn = function (channel, control, value, status) {
+ var control = Mixage.controls.getByMidiNo(control);
+ var group = control["group"];
+ // See if we're scratching. If not, just return.
+ if (!group["scratchingEnabled"]) return;
+
+ if (!group["scratching"]) {
+ var alpha = 1.0/8;
+ var beta = alpha/32;
+ engine.scratchEnable(group["id"], 128, 100, alpha, beta);
+ }
+
+ // Register the movement
+ var ramp = (value - 64);
+ engine.scratchTick(group["id"], ramp);
+ group["scratching"] = true;
+ // This two timers check if you are still scratching and disable
+ // the function if not.
+ engine.beginTimer(20, function() {
+ group["scratching"] = false;
+ engine.beginTimer(20, function() {
+ if (!group["scratching"]) {
+ engine.scratchDisable(group["id"]);
+ }
+ }, true);
+ }, true);
+ }
+}