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); } }