The comments at the end of this code describe testing.
Highlights are:
To work around the issue with moving usb ports changing the name use
.expectOutputNameEndsWith(expectedName)
.expectInputNameEndsWith(expectedName);
ValueChange → encoder is moving (and by how much).
ProcesValueChange with value = previous value → host changed in some unknown way or direction. (it could still be the same as the encoder)
ProcesValueChange with value != previous value → encoder moved.
DisplayValueChange → value is different than it use to be in the host.
There is no Host Changed Event!
const expectedName = "Launch Control XL"; // The end of the name of the device.
const CC = 0x0D; // CC the encoder is on
const MIDIChannel = 0x0F; // midi channel the encoder is on
var midiremote_api_v1_1 = require("midiremote_api_v1");
var deviceDriver = midiremote_api_v1_1.makeDeviceDriver('DeviceName', expectedName, 'MyName');
var MIDIIn = deviceDriver.mPorts.makeMidiInput();
var MIDIOut = deviceDriver.mPorts.makeMidiOutput();
// We have to use Ends With instead of equals to solve multi port multi device issue.
// Even with one device, if you plug it in to a different USB port on windows
// it will have a different name.
deviceDriver
.makeDetectionUnit()
.detectPortPair(MIDIIn, MIDIOut)
.expectOutputNameEndsWith(expectedName)
.expectInputNameEndsWith(expectedName);
// Register for on active event
deviceDriver.mOnActivate = function (activeDevice) {
console.log('Launch Control XL Initialized');
}
var surface = deviceDriver.mSurface;
var knob = surface.makeKnob(0, 0, 2, 2);
knob.mSurfaceValue.mMidiBinding
.setInputPort(MIDIIn)
.setOutputPort(MIDIOut)
.bindToControlChange(MIDIChannel, CC)
var bindable = knob.mSurfaceValue;
// Page 1
var page = deviceDriver.mMapping.makePage("TEST");
var valueBinding = page.makeValueBinding(bindable, page.mHostAccess.mTrackSelection.mMixerChannel.mValue.mVolume)
valueBinding.setValueTakeOverModePickup();
// Page 2
var page2 = deviceDriver.mMapping.makePage("TEST2");
var valueBinding2 = page2.makeValueBinding(bindable, page2.mHostAccess.mTrackSelection.mMixerChannel.mPreFilter.mGain)
valueBinding2.setValueTakeOverModePickup();
//valueBinding2.mapToValue(1);
// testing events
// Notice this is registering with the binding. It is only called when the binding's page is active
// Is NOT register for host event
// This callback is ONLY called every time the encoder is changed.
// It's value will refect the position of the Encoder, NOT the value in the Host.
// Is NOT called when changing tracks etc.
// Processing placed in this event can (and likely will) freez cubase until return!
// Not Useful!!!!!!!!!!!!!!! Don't do this it slows things down.
//
// var savedHostValue = 0;
// valueBinding2.mOnValueChange =
// function(activeDevice, activeMapping, value, diffValue) {
// console.log("==== valueBinding.mOnValueChange ====")
// console.log("---- value: " + value)
// console.log("---- diffValue: " + diffValue)
// console.log("---- savedHostValue: " + savedHostValue);
// // churn(50)
// // console.log("=====================================")
// savedHostValue = value;
// }
// Notice that these are registering with the encoder "surface" bindable
// Is Not register for host display event
// This callback is called every time the encoder is changed AND the value in the Host's display has changed
// This is the display value from the host, but only called when the Encoder is touched or moved, and only
// The first time it is moved once the value in the host has changed.
// This means that if the encoder picks up the host, it will be called continuously.
// Is called when changing tracks etc.
// Processing placed in this event can freez cubase until return!
var savedHostDisplayValue = 0;
bindable.mOnDisplayValueChange =
function(activeDevice, valueString) {
console.log(":::: bindable.mOnDisplayValueChange ::::")
console.log("---- valueString: " + valueString)
console.log("---- savedHostDisplayValue: " + savedHostDisplayValue);
// churn(50)
// console.log("::::::::::::::::::::::::::::::::::::::::")
savedHostDisplayValue = valueString;
}
// Is NOT register for Encoder Event
// This callback is called every time the Host OR Encoder is changed.
// It's value will refect the position of the Encoder. Even though it is called
// when the host changes it is stil always the last known value of the Encoder.
// Is called when changing tracks, etc.
// Processing placed in this event can freez cubase until return!
var savedEncoderValue = 0;
bindable.mOnProcessValueChange =
function(activeDevice, value) {
console.log("[[[[ bindable.mOnProcessValueChange ]]]]")
console.log("---- value: " + value)
console.log("---- savedEncoderValue: " + savedEncoderValue);
// churn(50);
// console.log("[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]")
savedEncoderValue = value;
}
// little primes function to create churn for testing where computation will slow cubase down.
function churn(iterations) {
var primes = [];
for (var i = 0; i < iterations; i++) {
var candidate = i * (1000000000 * Math.random());
var isPrime = true;
for (var c = 2; c <= Math.sqrt(candidate); ++c) {
if (candidate % c === 0) {
// not prime
isPrime = false;
break;
}
}
if (isPrime) {
primes.push(candidate);
}
}
return primes;
}
// Value Binding ONLY methods.
// -----------------------------------------------------------------------------------------------
// setTypeDefault () : this
// Whatever Cubase thinks the default setting should be. (Usualy Jump)
// setTypeToggle () : this
// Every time a non 0 value is sent to Cubase it results in the value of 0 or 127. (0 or 1)
// setValueTakeOverModeJump () : this
// Jumps the Host value when the knob is touched to the value of the encoder
// setValueTakeOverModePickup () : this
// The Host value is only changed when the encoder crosses the Host value.
// setValueTakeOverModeScaled () : this
// Changes the value SENT to Cubase between scaled to the difference until it picks up.
// Aguable more intuitive that pickup mode, but the granularity of movement of the knob
// is dependent on the difference in the Host value and the encoder value.
// Value Binding, Command binding, & Action Binding methods
// -----------------------------------------------------------------------------------------------
// setSubPage (subPage : SubPage) : this
// Sets the sub page in which the binding is active, like making it active on a different page.
// filterByValue (filterValue : number) : this
// Seems to do the same as mapToValue, only, it doesn't seem to send the value. IDK?
// filterByValueRange (from : number, to : number) : this
// Simmilare to MapTovalueRange but differes in the value SENT to Cubase
// is only effective in the range provided. Pickup mode is disabled.
// mapToValue (mapValue : number) : this
// Makes only one single value SENT to Cubase.
// Likely intended to be used with a button.
// mapToValueRange (from : number, to : number) : this
// Changes the value SENT to Cubase does not change the value in the events.
// Can really only be used to limit the range.
// Doing so will cause Cubase UI to stutter.
// ValueChange -> encoder is moving (and by how much).
// ProcesValueChange with value = previous value -> host changed in some unknown way or direction.
// (it could still be the same as the encoder)
// ProcesValueChange with value != previous value -> encoder moved.
// DisplayValueChange -> value is different than it use to be in the host.
// There is no Host Changed Event!