Help with a script using endless encoder for cursor position or zoom

I want to create a script for a device with some endless encoders (push encoders) that will move the project cursor and change the zoom level etc. (i.e. things that are not available in the MIDI Remote mapping assistant). I am using an Arduino and I have the Arduino reading the movement of the encoder and sending MIDI CC data. I have a script that is working, but it is not the “elegant” solution. Basically, I have the Arduino send a value of 127 on CC 77 if I turn the encoder clockwise and send a value of 127 on CC 78 if I turn the encoder counter-clockwise. Then, I wrote a script with two buttons, each bound to the two different CCs and they are mapped to the nudge Cursor Left and Right commands.

This works…

But, of course, on the MIDI Remote display, I have two buttons instead of a knob (encoder really…) and it seems clunky. It seems to me that a more elegant solution would be to have the Arduino send a value of “0” on a single CC if I turn the encoder CCW and a value of “127” on the same CC if I turn the encoder CW. Then, based on the 0 or 127 value, nudge the cursor left or right.

Or, maybe there is an even better way that is generally the “best practice”. I can write the Arduino code to make it send anything I want it to.

I am picturing using the mOnProcessValueChange() function with an if statement inside that basically says if the value is zero, bind the encoder to the nudge left command and if the value is 127, bind it to the nudge right command. I am going to try this but wondered if someone might offer some help or let me know if I am doing it wrong.

I am somewhat familiar with C++, C#, etc., and “properties” and “methods” but not very familiar with JS or the MIDI Remote API. I have been spending some time and trying to learn. One thing that confuses me is this… Say the value changes and the mOnProcessValueChange function is called. Then, I evaluate the conditions and change the binding accordingly… But I wonder if it is then too late for the host to react the new binding mapping.

Thanks in advance for reading and any help you can provide!

JL

Hi,

Here you can find a code to use 1 encoder and 1 button (switcher) to control all 4 Zoom options:

//----------------------------------------------------------------------------------------------------------------------
// 2. SURFACE LAYOUT - create control elements and midi bindings
//----------------------------------------------------------------------------------------------------------------------
var surfaceElements = {}
surfaceElements.bottomLabelField = surface.makeLabelField(0, 1, 5, 1)

surfaceElements.knobA = surface.makeKnob(0, 0, 2, 1)
surfaceElements.knobA.mSurfaceValue.mMidiBinding.setInputPort(midiInput).bindToControlChange(0, 11)

surfaceElements.switcherA = surface.makeButton(2, 0, 1, 1)
surfaceElements.switcherA.mSurfaceValue.mMidiBinding.setInputPort(midiInput).bindToControlChange(0, 1)
surfaceElements.bottomLabelField.relateTo(surfaceElements.switcherA)


//----------------------------------------------------------------------------------------------------------------------
// 3. HOST MAPPING - create mapping pages and host bindings
//----------------------------------------------------------------------------------------------------------------------
function makePageA() {
	var page = makePageWithDefaults('A')

	var subPageAreaZoom = page.makeSubPageArea('subPageAreaZoom')
	var horizontalZoomSubPage = subPageAreaZoom.makeSubPage('Horizontal Zoom')
	var verticalZoomSubPage = subPageAreaZoom.makeSubPage('Vertical Zoom')
	var verticalZoomState = null

	page.makeActionBinding(surfaceElements.switcherA.mSurfaceValue, verticalZoomSubPage.mAction.mActivate)
		.setSubPage(horizontalZoomSubPage)
	page.makeActionBinding(surfaceElements.switcherA.mSurfaceValue, horizontalZoomSubPage.mAction.mActivate)
		.setSubPage(verticalZoomSubPage)
		.mapToValueRange(1, 0)

	verticalZoomSubPage.mOnActivate = function (context) {
		verticalZoomState = true
	}
	
	horizontalZoomSubPage.mOnActivate = function (context) {
		verticalZoomState = false
	}

	var dummyVerticalZoomOut = surface.makeCustomValueVariable('dummyVerticalZoomOut')
	var dummyVerticalZoomIn = surface.makeCustomValueVariable('dummyVerticalZoomIn')
	var dummyHorizontalZoomOut = surface.makeCustomValueVariable('dummyHorizontalZoomOut')
	var dummyHorizontalZoomIn = surface.makeCustomValueVariable('dummyHorizontalZoomIn')

	page.makeCommandBinding(dummyVerticalZoomOut, 'Zoom', 'Zoom Out Vertically')
	page.makeCommandBinding(dummyVerticalZoomIn, 'Zoom', 'Zoom In Vertically')
	page.makeCommandBinding(dummyHorizontalZoomOut, 'Zoom', 'Zoom Out')
	page.makeCommandBinding(dummyHorizontalZoomIn, 'Zoom', 'Zoom In')
	
	var lastValue = -1
	surfaceElements.knobA.mSurfaceValue.mOnProcessValueChange = function (activeDevice, value) {
		var valDiff = value - lastValue
		var dummyValue

		if (verticalZoomState) {
			(valDiff <= 0) ? (dummyValue = dummyVerticalZoomOut) : (dummyValue = dummyVerticalZoomIn)
		} else {
			(valDiff <= 0) ? (dummyValue = dummyHorizontalZoomOut) : (dummyValue = dummyHorizontalZoomIn)
		}

		dummyValue.setProcessValue(activeDevice, 1)
		lastValue = value
	}
	return page
}

//----------------------------------------------------------------------------------------------------------------------

I hope this helps.

Thanks for the reply, Martin!

I will definitely try this out.

This is what I ended up using to get the cursor working in both directions with one knob and one encoder sending relative values. My encoder is sending a value of 0 on CC#77 when turned left and a value of 127 on CC# when turned right.
Credit to “dctsys” in this thread here: https://forums.steinberg.net/t/midi-remote-functions-single-knob-for-forward-backward-cursor/824316/5

I am posting here because there were a few typos in that code there. This code is, of course, after the initial device setup…

var jogWheel = deviceDriver.mSurface.makeKnob(0, 0, 2, 2)

//-----------------------------------------------------------------------------
// 3. HOST MAPPING - create mapping pages and host bindings
//-----------------------------------------------------------------------------


// create at least one mapping page
var page = deviceDriver.mMapping.makePage('Default')

// bind the surface element to the MIDI message
jogWheel.mSurfaceValue.mMidiBinding.setInputPort(midiInput).bindToControlChange(0, 77)
var jogLeft = deviceDriver.mSurface.makeCustomValueVariable('jogLeft')
var jogRight = deviceDriver.mSurface.makeCustomValueVariable('jogRight')
jogWheel.mSurfaceValue.mOnProcessValueChange = function (activeDevice, value) {
    //console.log(value)
    if(value > 0.5)
    {
        jogRight.setProcessValue(activeDevice, 1)
    }
    else
    {
        jogLeft.setProcessValue(activeDevice, 1)
    }
}
page.makeCommandBinding(jogLeft, 'Transport', 'Nudge Cursor Left')
page.makeCommandBinding(jogRight, 'Transport', 'Nudge Cursor Right')