var NumarkN4 = {}; NumarkN4.scratchSettings = { "alpha": 1.0 / 8, "beta": 1.0 / 8 / 32, "jogResolution": 600, "vinylSpeed": 33 + 1 / 3, }; NumarkN4.searchAmplification = 5; // multiplier for the jogwheel when the search button is held down. NumarkN4.warnAfterTime = 30; // Acts like the "End of Track warning" setting within the waveform settings. NumarkN4.blinkInterval=1000; //blinkInterval for the triangular Leds over the channels in milliseconds. NumarkN4.encoderResolution=0.05; // 1/encoderResolution = number of steps going from 0% to 100% NumarkN4.resetHotCuePageOnTrackLoad=true; // resets the page of the Hotcue back to 1 after loading a new track. NumarkN4.cueReverseRoll=true; // enables the ability to do a reverse roll while shift-pressing the cue button // true = wrap around => scrolling past 4 will reset the page to the first page and vice versa // false = clamp the the pages to the [1:4] range NumarkN4.hotcuePageIndexBehavior=true; // possible ranges (0.0..3.0 where 0.06=6%) NumarkN4.rateRanges = [0, // default (gets set via script later; don't modify) 0.06, // one semitone 0.24, // for maximum freedom ]; // // CONSTANTS DO NOT CHANGE (if you don't know what you are doing) // NumarkN4.QueryStatusMessage=[0xF0, 0x00, 0x01, 0x3F, 0x7F, 0x47, 0x60, 0x00, 0x01, 0x54, 0x01, 0x00, 0x00, 0x00, 0x00, 0xF7]; //NumarkN4.ShutoffSequence=[0xF0,0x00,0x01,0x3F,0x7F,0x47,0xB0,0x39,0x00,0x01,0xF7]; // Invalid Midibyte? NumarkN4.vinylTouched = [false, false, false, false]; NumarkN4.globalShift = false; NumarkN4.scratchXFader = { xFaderMode: 0, // fast cut (additive) xFaderCurve: 999.60, xFaderCalibration: 1.0 }; components.Encoder.prototype.input = function(_channel, _control, value, _status, _group) { this.inSetParameter( this.inGetParameter()+( (value===0x01)? NumarkN4.encoderResolution: -NumarkN4.encoderResolution ) ); }; components.Component.prototype.send = function(value) { // This Override is supposed to make integration automatic assignment of elements easier. // Right now it just allows specifying the input and output bytes (even though the input bytes dont do anything right now.) if (this.midi === undefined || this.midi[0] === undefined || this.midi[1] === undefined) { return; } if (this.midi[2]===undefined) { //check if output channel/type not explicitly defined this.midi[2]=this.midi[0]; } if (this.midi[3]===undefined) { //check if output control not explicitly defined this.midi[3]=this.midi[1]; } midi.sendShortMsg(this.midi[2], this.midi[3], value); if (this.sendShifted) { if (this.shiftChannel) { midi.sendShortMsg(this.midi[2] + this.shiftOffset, this.midi[3], value); } else if (this.shiftControl) { midi.sendShortMsg(this.midi[2], this.midi[3] + this.shiftOffset, value); } } }; // gets filled via trigger of the callbacks in NumarkN4.crossfaderCallbackConnections NumarkN4.storedCrossfaderParams = {}; NumarkN4.crossfaderCallbackConnections = []; NumarkN4.CrossfaderChangeCallback = function(value, group, control) { // indicates that the crossfader settings were changed while during session this.changed = true; NumarkN4.storedCrossfaderParams[control] = value; }; NumarkN4.init = function() { NumarkN4.rateRanges[0]=engine.getValue("[Channel1]", "rateRange"); NumarkN4.Decks=[]; for (var i=1; i<=4; i++) { // Array is based on 1 because it makes more sense in the XML NumarkN4.Decks[i] = new NumarkN4.Deck(i); } // create xFader callbacks and trigger them to fill NumarkN4.storedCrossfaderParams _.forEach(NumarkN4.scratchXFader, function(value, control) { var connectionObject = engine.makeConnection("[Mixer Profile]", control, NumarkN4.CrossfaderChangeCallback.bind(this)); connectionObject.trigger(); NumarkN4.crossfaderCallbackConnections.push(connectionObject); }); NumarkN4.Mixer = new NumarkN4.MixerTemplate(); //query controller for component status midi.sendSysexMsg(NumarkN4.QueryStatusMessage, NumarkN4.QueryStatusMessage.length); }; NumarkN4.topContainer = function(channel) { this.group = "[Channel"+channel+"]"; var theContainer = this; this.btnEffect1 = new components.Button({ midi: [0x90+channel, 0x13, 0xB0+channel, 0x0B], shift: function() { this.group="[EffectRack1_EffectUnit1]"; this.type=components.Button.prototype.types.toggle; this.inKey="group_[Channel"+channel+"]_enable"; this.outKey="group_[Channel"+channel+"]_enable"; }, unshift: function() { this.group=theContainer.group; this.type=components.Button.prototype.types.push; this.inKey="loop_in"; this.outKey="loop_in"; }, }); this.btnEffect2 = new components.Button({ midi: [0x90+channel, 0x14, 0xB0+channel, 0x0C], shift: function() { this.group="[EffectRack1_EffectUnit2]"; this.type=components.Button.prototype.types.toggle; this.inKey="group_[Channel"+channel+"]_enable"; this.outKey="group_[Channel"+channel+"]_enable"; }, unshift: function() { this.group=theContainer.group; this.type=components.Button.prototype.types.push; this.inKey="loop_out"; this.outKey="loop_out"; }, }); this.btnSample3 = new components.Button({ midi: [0x90+channel, 0x15, 0xB0+channel, 0x0D], shift: function() { this.type=components.Button.prototype.types.toggle; this.inKey="slip_enabled"; this.outKey="slip_enabled"; }, unshift: function() { this.type=components.Button.prototype.types.push; this.inKey="beatloop_activate"; this.outKey="beatloop_activate"; }, }); this.btnSample4 = new components.Button({ midi: [0x90+channel, 0x16, 0xB0+channel, 0x0E], outKey: "loop_enabled", shift: function() { this.type=components.Button.prototype.types.toggle; this.inKey="reloop_andstop"; }, unshift: function() { this.type=components.Button.prototype.types.push; this.inKey="reloop_toggle"; }, }); // custom Hotcue Buttons this.hotcueButtons=[]; for (var counter=0; counter<=3; counter++) { this.hotcueButtons[counter] = new components.HotcueButton({ midi: [0x90+channel, 0x27+counter, 0xB0+channel, 0x18+counter], number: counter+1, }); } this.encFxParam1 = new components.Encoder({ midi: [0xB0+channel, 0x57], group: "[EffectRack1_EffectUnit1]", shift: function() { this.inKey="mix"; }, unshift: function() { this.inKey="super1"; }, }); this.encFxParam2 = new components.Encoder({ midi: [0xB0+channel, 0x58], group: "[EffectRack1_EffectUnit2]", shift: function() { this.inKey="mix"; }, unshift: function() { this.inKey="super1"; }, }); this.encSample3 = new components.Encoder({ midi: [0xB0+channel, 0x5A], hotCuePage: 0, applyHotcuePage: function(layer, displayFeedback) { // ES3 doesn't allow default values in the function signature // Could be replaced after migration to QJSEngine by "displayFeedback=true" // in the function arguments. if (displayFeedback === undefined) { displayFeedback = true; } // when the layer becommes negative, the (layer+4) will force a positive/valid page indexOf layer = NumarkN4.hotcuePageIndexBehavior ? (layer+4)%4 : Math.max(Math.min(layer, 3), 0); // clamp layer value to [0;3] range this.hotCuePage = layer; if (this.timer !== 0) { engine.stopTimer(this.timer); this.timer = 0; } var number = 0; for (var i=0; i