Remapping of Twitch questions thread.

Wow! The Twitch script file is over 8500 lines long!! And that’s with basically zero annotation! :astonished: Surely not much of that is really required. Looked at a few other similar size controllers and their scripts are more between 200-2000 including annotations…

Something doesn’t seem right and that makes me wonder if maybe I should just try and work from the ground up, at least with the script side, even if not with the entire mapping…

How do I have two accessible mapping for the same controller? I tried this in the past by editing the name field within the xml file for my QuNeo but it didn’t work. Sure it must be simple and I just did something wrong though…

As noted on the Twitch wiki page, the mapping is made with the Mixco mapping framework which autogenerates those 8500 lines of JavaScript. The source for the code that is processed by Mixco is in mixco folder in the res/controllers folder in the Mixxx Git repository. If you want to modify that mapping, you’d have to learn how Mixco works and modify that code. If you don’t want to bother with that you could start from scratch.

You can make an alternate mapping by changing the name of mapping in a new XML file.

Yeah I had a brief look at it some time ago, seems the whole midi mapping xml is generated from the mixco js… I think I will edited the existing xml but likely try and do the scripting myself from scratch rather than edit the mixco script.

That’s what I thought. I must have made some basic mistake last time. I will try again.

Sorry being slow but I’m trying to at least get some of the background reading done at times I haven’t got the controller to physically work on it. As such I’ve been reading the MIDI Scripting and the Components JS pages (even if it’s taking a bit long for bits of it to sync in…)

Before I even start trying to make the Deck object can you clarify: As the shift buttons are sending MIDI Note On/Off can I still map the normal functions via the normal MIDI Mapping method (IE saved in the xml file)? Or will everything have to be done through the JS file? I’m assuming through JS as it will need an IF statement and JS can’t currently intercept the MIDI directly (if I understand what I’ve been reading.)

Although as it seems you are looking to completely update the mapping system and get rid of control mappings within the xml file it feels this method might be more future proof even if not technically required…

SOLVED

(Deleted)
and has been renamed to: kazas_twitch-midi.xml

What am I doing wrong? SOLVED

SOLUTION: Ahhh needs to be (name).midi.xml and had used a hyphen.

OK why isn’t this working? Only trying to get the Hot Cues working for now as a starting point. Going with the Components JS method. As my controller uses a separate MIDI Channel for each Deck channel I really don’t understand why I would need the leftDeck, rightDeck and currentDeck constructs but for the moment I’m trying to get it working as per the guide and then tinker from there, but can’t even get that working…

Decks 1 & 2 should hopefully have the syntax for how it’s set up. There is a comments out line in the script file to work out the Group I was going to uncomment and try 3 & 4 once I had the initial ones working.

midi.xml

<?xml version='1.0' encoding='utf-8'?>
<MixxxControllerPreset mixxxVersion="2.1.x" schemaVersion="1">
   <info>
       <name>Kazas Twitch</name>
       <author>Kazakore &lt;dj_kaza@hotmail.com&gt;</author>
       <description>Revised mapping for Novation Twitch (in basic mode)</description>
       <wiki></wiki>
       <forum></forum>
   </info>
   <controller id="kazas_twitch">
       <scriptfiles>
       		<file filename="lodash.mixxx.js"/>
			<file filename="midi-components-0.0.js"/>
         	<file functionprefix="KazasTwitch" filename="kazas_twitch-scripts.js"/>
       </scriptfiles>
       <controls>
        	<control>
	 			<group>[Channel1]</group>
				<key>KazasTwitch.leftDeck.hotcueButtons.input</key>
	    		<status>0x97</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group>[Channel2]</group>
				<key>KazasTwitch.rightDeck.hotcueButtons.input</key>
	    		<status>0x98</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.Deck.hotcueButtons.input</key>
	    		<status>0x99</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.Deck.hotcueButtons.input</key>
	    		<status>0x9a</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
       </controls>
       <outputs>
          
       </outputs>
   </controller>
</MixxxControllerPreset>

script file:

[code]
/*

  • Script file for Novation Twitch by Kazakore for Mixxx 2.1

*/

var KazasTwitch = {};

KazasTwitch.init = function (id, debugging) {
midi.sendShortMsg(0xb7,0x00,0x00); // init Basic Mode
// midi.sendShortMsg(0xb7,0x00,0x01); // Read current controler staus (should’t be needed as done during init 0 above)
KazasTwitch.leftDeck = new KazasTwitch.Deck ([1, 3], 1);
KazasTwitch.rightDeck = new KazasTwitch.Deck ([2, 4], 2);

};

KazasTwitch.shutdown = function () {
midi.sendShortMsg(0xb7,0x00,0x00); // init Basic Mode
midi.sendShortMsg(0xb7,0x00,0x70); // turn off all LEDs on exit
};

KazasTwitch.Deck = function (deckNumbers, midiChannel) {
components.Deck.call(this, deckNumbers);
this.hotcueButtons = [];
for (var i = 1; i <= 8; i++) {
this.hotcueButtons[i] = new components.HotcueButton({
midi: [0x90 + midiChannel, 0x5f + i],
// group: “[Channel”+midiChannel-6+"]",
number: i,
});
}
this.reconnectComponents(function © {
if (c.group === undefined) {
c.group = this.currentDeck;
}
});
};

KazasTwitch.Deck.prototype = new components.Deck();[/code]

Error message:
Uncaught exception at line 1 in passed code: TypeError: Result of expression ‘(KazasTwitch.leftDeck.hotcueButtons.input)’ [undefined] is not a function.

Also your manual clearly states I should be able to edit the files and Mixxx will automatically update. This does not work! It now shows kazas_twitch in the list twice and to get the changes seen by Mixxx I have to try them both and work out which is the one with the latest edits (currently by checking MIDI Mappings as there’s only a handful but this would become hard in the future.) These even seem to stay there after restarting the software even though there is no second file saves I can find!

The idea is that the leftDeck and rightDeck objects represent the physical controls, not virtual decks. This is necessary for controllers that do not send separate MIDI messages for decks 3 & 4. For the case of the Twitch, you could do it that way, or you could create an instance of the Deck object for every virtual deck.

KazasTwitch.leftDeck.hotcueButtons is an array of Components. You need to access members of the array like KazasTwitch.leftDeck.hotcueButtons[1].input.

Ahh that makes sense. The only example in the guide is for a quantise button, so not in an array. It seems to give a similar error for the leftDeck object now though.

Uncaught exception at line 1 in passed code: TypeError: Result of expression 'KazasTwitch.leftDeck' [undefined] is not an object.

leftDeck[1] doesn’t work. When declared it is passed an array as an argument so thought I should be able to access them with [0] and [1] but seems not… :frowning:

leftDeck and rightDeck are not arrays. I am guessing you are getting that error because there is a syntax error or something in your code preventing the KazasTwitch.Deck constructor from running. If the JS code is still what you posted on the last page, it looks like you try to reference “this.currentDeck” but never declare that variable anywhere. When you try to reference an undefined variable the JS interpreter will give not so helpful errors.

Makes sense and I actually thought the same about quite a lot of variables in the script and wondered if they were somehow inherited from the Components script. But it seems the example code on the wiki page does the same thing and I was just copying that as it’s all I have to go on (plus some annotations within the script file itself but it’s much harder to read through and find useful bits there and it’s far from expansive.

To me that made it sound like I didn’t have to declare them myself.

mixxx.org/wiki/doku.php/components_js


Is there a full, preferably fairly basic, complete mapping using the Components method you can point me to so I can study it?
The Pioneer DDJ-SX mapping has it listed in the scripts to load but as the word “prototype” isn’t found anywhere in the document I don’t think it actually uses these methods…

Using grep there is only one single file which contains the string “components.Deck” so hopefully this will be written in a way to give me some pointers. The mapping in question is the Hercules P32 (thanks Be.)


EDIT: and that also doesn’t seem to actually declare deckNumbers anywhere. This variable shows up on three lines and in all of them it is an argument:

[code]P32.Deck = function (deckNumbers, channel) {
components.Deck.call(this, deckNumbers);

this.effectUnit = new components.EffectUnit(deckNumbers);[/code]

I think my brain is starting to melt now though!

The declaration is in the function signature. “function (deckNumbers, channel)” declares “deckNumbers” and “channel” as local variables in the scope of that function.

Yeah I realised that on the way home on the bus. Think I had been staring at a screen too long!

Still don’t see what is different with it in my code though…

First buttons working! At some point I had somehow entered the word prototype in with the Call method. Don’t know if that was me trying multiple things or not used to some of the strange shortcuts on the editor I’m using (I’m using Sublime Text Editor which I think has some of the old Vim functionality, disabled it from going into Command mode so easy though but still occasional do I’m not sure what by accident…)

SOLVED The pads are not lighting on my controller for the corresponding set hotcue. I thought for standard On/Off the defaults were to send 127/0 to the same midi channel and number as the control is configured for, therefore this should work. Or is there something additional I need to do with the connect method somewhere?

– Deleted code for neatness as now irrelevant –

Solution: I had thought from the guide it said the second argument in the left/rightDeck declaration was the default channel meaning the deck, but then realised this was the midi channel of the deck and once this is set to the channel the commands to that deck come on the LEDs worked as expected.

Second current issue:

I added another control in the xml to try and play Deck C from the other layer, as previous mentioned these transmit on a different channel. No matter what it still only triggers Deck A to play though, even though the Group in the xml is set to the correct Channel!

Looking at your mapping for the P32 and you don’t even have the Group set differently for the two Play buttons per controller side! Yet like my controller it is transmitting on a different channel so there should be no reason to be toggling between A/B and C/D like you do with controllers which don’t have unique outputs for each Deck.

Or preferably how do I get rid of the whole leftDeck/rightDeck constructs and just use querying the MIDI Channel to get it to work on the right deck within Mixxx? This seems like it would be far far simpler! Especially as using leftDeck it then ignores what the real channel number received was and replaces it with the one from the second argument from where you create that function! How is that in any way useful? There’s got to be a better way than separate Decks A/B/C/D when all controls have a unique midi value surely…

Sorry for talking to myself so much. Well for now I’ve gone with setting up four Decks, I had hoped I would be able to simplify it but as I’ve managed to simplify the call method I guess it’s not overly needed. I did ask on Zulip whether this would be possible and I guess it was partially from B assuming I really wanted to put it in the xml rather than the javascript code. Anyway I’ve only added the first two for all four decks so far but as you can see from this code they can all have exactly the same Key and no Group set and still work, which is going to make it a lot quicker to generate the xml file. This might be a trick of interest to others so putting it here. :slight_smile:

script:

[code]/*

  • Script file for Novation Twitch by Kazakore for Mixxx 2.1

*/

var KazasTwitch = function() {};

KazasTwitch.init = function (id, debugging) {
midi.sendShortMsg(0xb7,0x00,0x00); // init Basic Mode
// midi.sendShortMsg(0xb7,0x00,0x01); // Read current controler staus (should’t be needed as done during init 0 above)

KazasTwitch.deckA = new KazasTwitch.Deck ([1], 7);
KazasTwitch.deckB = new KazasTwitch.Deck ([2], 8);
KazasTwitch.deckC = new KazasTwitch.Deck ([3], 9);
KazasTwitch.deckD = new KazasTwitch.Deck ([4], 10);

};

KazasTwitch.shutdown = function() {
midi.sendShortMsg(0xb7,0x00,0x00); // init Basic Mode
midi.sendShortMsg(0xb7,0x00,0x70); // turn off all LEDs on exit
};

KazasTwitch.Deck = function(deckNumbers, channel) {
components.Deck.call(this, deckNumbers);

this.playButton = new components.PlayButton([0x90 + channel, 0x17]);

this.hotcueButtons = [];
for (var i = 1; i <= 8; i++) {
    this.hotcueButtons[i] = new components.HotcueButton({
        midi: [0x90 + channel, 0x5f + i],
        group: "[Channel" + (channel-6) + "]",
        number: i,
    });
}

this.reconnectComponents(function(c) {
    if (c.group === undefined) {
        c.group = this.currentDeck;
    }
	});

};

KazasTwitch.cues = function(channel, control, value, status, group) {
var cueNo = (control - 0x5f) ;
var deckNo
if (channel == 7) {group = “[Channel1]” , deckNo = “deckA” ;
}
else if (channel == 8) {group = “[Channel2]” , deckNo = “deckB” ;
}
else if (channel == 9) {group = “[Channel3]” , deckNo = “deckC” ;
}
else {group = “[Channel4]” , deckNo = “deckD” ;
}
KazasTwitch[deckNo].hotcueButtons[cueNo].input(channel, control, value, status, group);
};

KazasTwitch.Deck.prototype = new components.Deck();[/code]

xml

<?xml version='1.0' encoding='utf-8'?>
<MixxxControllerPreset mixxxVersion="2.1.x" schemaVersion="1">
   <info>
       <name>Kazas Twitch</name>
       <author>Kazakore &lt;dj_kaza@hotmail.com&gt;</author>
       <description>Revised mapping for Novation Twitch (in basic mode)</description>
       <wiki></wiki>
       <forum></forum>
   </info>
   <controller id="kazas_twitch">
       <scriptfiles>
       		<file filename="lodash.mixxx.js"/>
			<file filename="midi-components-0.0.js"/>
         	<file functionprefix="KazasTwitch" filename="kazas_twitch-scripts.js"/>
       </scriptfiles>
       <controls>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x97</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x98</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x99</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x9a</status>
	    		<midino>0x60</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
			        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x97</status>
	    		<midino>0x61</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x98</status>
	    		<midino>0x61</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x99</status>
	    		<midino>0x61</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
        	<control>
	 			<group></group>
				<key>KazasTwitch.cues</key>
	    		<status>0x9a</status>
	    		<midino>0x61</midino>
	    		<options>
	       			<script-binding/>
	  			</options>
			</control>
			
			<control>
			    <group></group>
			    <status>0x97</status>
			    <midino>0x17</midino>
			    <key>KazasTwitch.deckA.playButton.input</key>
			    <options>
			        <script-binding/>
			    </options>
			</control>
        	<control>
			    <group>[Channel3]</group>
			    <status>0x99</status>
			    <midino>0x17</midino>
			    <key>KazasTwitch.deckC.playButton.input</key>
			    <options>
			        <script-binding/>
			    </options>
			</control></controls>

       <outputs>
          
       </outputs>
   </controller>
</MixxxControllerPreset>

You are correct, strictly speaking, there is not a need to have two Deck objects in the Hercules P32 mapping that toggle between decks 1/3 and decks 2/4. I did it that way to ensure that the library would work that way for controllers that do not use separate MIDI channels for each deck.

The Components library ignores the “group” XML element. I’m not sure if Mixxx requires the “group” element to exist. I just haven’t tested deleting it so I kept in in the XML, but maybe it’ll work without that.

The approach of the library is that the “group” should not depend on the MIDI signal or the XML file, but instead the group is a property of each Component. Components with the same “group” are organized into Deck objects. This way, the Deck can easily iterate over all those Components and manipulate their “group” properties. This is what

this.reconnectComponents(function (c) { if (c.group === undefined) { // 'this' inside a function passed to reconnectComponents refers to the ComponentContainer // so 'this' refers to the custom Deck object being constructed c.group = this.currentDeck; } });

in the template on the wiki does. That way, you do not need to define the “group” property when initializing each Component, which would be redundant.

Ahh thanks for letting me know. I am planning on adding a long-press function to the Cues at some point so I think I will need the Group when I come to implement that but it’s good to know I didn’t actually need it for the current code to work.

I can’t deny that bit of code still confuses me, I just copied from the guide…

Two basic usability questions:

  1. Is there a accepted “normal” order for a 4x2 grid of pads when in Loop mode? I feel it would be better to follow the norm that make up my own if there is one…

  2. I’ve been wondering how best to map the effects, after seeing the wiki/components page that there is a standard for four knob and four button controllers I changed from my initial thoughts to be closer to this. The controller has 5 pages of four knobs and four buttons (so page selection doesn’t need to be long press of the button like I think I remember the fx standard mapping using.) Also the middle two pots are actually encoders with a press button function.

I had initially though so control microphone/aux input on the fourth know/button before I read the page on the standard you are trying to implement. Then I considered maybe using the centre two encoders with the push function to adjust the input levels. Any thoughts on what would be best to do with these?..

This works for giving me a encoder which also has push button control built in working for the library navigation with fast mode when pressed. But the thing I don’t like about it is that the libShift variable is global from what I can tell.

[code]// Library Navigation knob and press for fast scroll
var libShift = 1 ;
KazasTwitch.libShiftKey = function (channel, control, value, status, group) {
if (value > 0x40) {libShift = 10 ;
}
else {libShift = 1 ;
};
};
KazasTwitch.browseEnc = function (channel, control, value, status, group) {

if (value > 64) {engine.setValue('[Playlist]', 'SelectTrackKnob', ((-1) * (0x80-value) * libShift) ) ;
} 
else {engine.setValue('[Playlist]', 'SelectTrackKnob', (value * libShift) ) ;
};

};[/code]

So I try adding an object layer to contain all the above with “var KazasTwitch.lib = {} ;” (that should create an empty object right?) and then renaming all the above to be part of this object. Then Mixxx complains even on functions completely unrelated that were previously working! Error message that KazasTwitch is not a function, which is what I generally get from syntax error pretty much anywhere in the code…

Should I worry about not having the variable global to the function and if so how should I do it?

EDIT: Realised when lying in bed last night that I was confusing myself, creating it as a property of an object is not going to stop it being global if it’s still not declared within a function, so what I was trying above was useless anyway.