diff options
-rw-r--r-- | RHMixxx1280x800/MixxController.js | 363 | ||||
-rwxr-xr-x | RHMixxx1280x800/skin.xml | 30 |
2 files changed, 381 insertions, 12 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); + } +} diff --git a/RHMixxx1280x800/skin.xml b/RHMixxx1280x800/skin.xml index 2678747..eb50400 100755 --- a/RHMixxx1280x800/skin.xml +++ b/RHMixxx1280x800/skin.xml @@ -150,7 +150,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <Property>title</Property> <Channel>1</Channel> <Pos>0,1</Pos> - <Size>315f,30</Size> + <Size>275,30</Size> </TrackProperty> <!-- @@ -166,7 +166,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <Property>artist</Property> <Channel>1</Channel> <Pos>3,40</Pos> - <Size>310,23</Size> + <Size>245,23</Size> </TrackProperty> <!-- CHANNEL 1 @@ -239,7 +239,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <Property>title</Property> <Channel>2</Channel> <Pos>0,1</Pos> - <Size>480,30</Size> + <Size>420,30</Size> </TrackProperty> <!-- @@ -274,7 +274,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } </Style> <Channel>1</Channel> <Pos>315,38</Pos> - <Size>150,32</Size> + <Size>105,32</Size> <NumberOfDigits>6</NumberOfDigits> <Connection> <ConfigKey>[Channel2],playposition</ConfigKey> @@ -691,16 +691,22 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <ConfigKey>[Master],PeakIndicatorR</ConfigKey> </Connection> </StatusLight> + </Children> + </WidgetGroup> + <!--Channel 1: VU-Meter Players--> - + <WidgetGroup> + <Pos>455,56</Pos> + <Style>QGroupBox { border: 0px solid red; }</Style> + <Children> <VuMeter> <Tooltip>Channel volume meter Shows the left channel of player 1 volume </Tooltip> <Style></Style> <PathVu>Volume_long_over_player.png</PathVu> <PathBack>Volume_long_player.png</PathBack> - <Pos>455,68</Pos> + <Pos>0,30</Pos> <Horizontal>false</Horizontal> <PeakHoldSize>5</PeakHoldSize> <PeakHoldTime>500</PeakHoldTime> @@ -716,7 +722,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <Style></Style> <PathStatusLight>clipping_long_over_player.png</PathStatusLight> <PathBack>clipping_long_player.png</PathBack> - <Pos>455,38</Pos> + <Pos>0,0</Pos> <Connection> <ConfigKey>[Channel1],PeakIndicator</ConfigKey> </Connection> @@ -727,7 +733,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <Style></Style> <PathStatusLight>clipping_long_over_player.png</PathStatusLight> <PathBack>clipping_long_player.png</PathBack> - <Pos>475,38</Pos> + <Pos>20,0</Pos> <Connection> <ConfigKey>[Channel1],PeakIndicator</ConfigKey> </Connection> @@ -739,7 +745,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <Style></Style> <PathVu>Volume_long_over_player.png</PathVu> <PathBack>Volume_long_player.png</PathBack> - <Pos>475,68</Pos> + <Pos>20,30</Pos> <Horizontal>false</Horizontal> <PeakHoldSize>5</PeakHoldSize> <PeakHoldTime>500</PeakHoldTime> @@ -749,7 +755,8 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <ConfigKey>[Channel1],VuMeterR</ConfigKey> </Connection> </VuMeter> - + </Children> + </WidgetGroup> <!--Channel 2: VU-Meter @@ -797,8 +804,7 @@ WSearchLineEdit { background: #cfdee7; color: #000000; } <ConfigKey>[Channel2],VuMeter</ConfigKey> </Connection> </VuMeter>--> - </Children> - </WidgetGroup> + ********************************************** |