Midi-in SysEx time values binding to surface element - midi remote script

Hello,

does anyone know a way to bind sysex values to a surface element and then transfer it to the host?

I have a Roland VM C7x00 and would like to transfer the “time values” (MMC) of the console to Cubase via the MIDI Remote API. Basically, the MMC device control of the Roland VM C7x00 works with Cubase!

Reading the sysex data is not my problem, I have the values in Console.log() and in variables but how do I bind them to a surface element?

Here is my code, including the 16 faders:

// 0. Custom Functions

// global time var's
var tHour = 0;
var tMin = 0; 
var tSec = 0;
var tFrame = 0;

// check time sysex
function isSysexTimeMsg(sysexMsg)
{
    // 12 bytes
    var needLenght = 12;
    // 240  127  127  6   68   6   1
    var sFirst = [0xF0, 0x7F, 0x7F, 0x06, 0x44, 0x06, 0x01] ;     // 7 bytes
    if( Array.isArray(sysexMsg) && sysexMsg.length === needLenght+1 )
    {
        // check last byte, 247DEZ
        if( sysexMsg[needLenght] !== 0xF7 ){
            return false;
        }
        // check first bytes
        for( var i = 0; i< sFirst.length; i++ )
        {
            if( sysexMsg[i] !== sFirst[i] ){
                return false;
            }            
        }
        
        // if allright then: get Time now
        tHour = sysexMsg[sFirst.length+0] - 0x20;   // default 0 is 32Dez (0x20HEX) then sub 32 = result ist hour
        tMin = sysexMsg[sFirst.length+1];
        tSec = sysexMsg[sFirst.length+2];
        tFrame = sysexMsg[sFirst.length+3];

        console.log("Time: " + tHour + ":"+ tMin +":"+ tSec +":"+ tFrame  );
        return true;
    }    
    return false;    
}

//-----------------------------------------------------------------------------
// 1. DRIVER SETUP - create driver object, midi ports and detection information
//-----------------------------------------------------------------------------

// get the api's entry point
var midiremote_api = require('midiremote_api_v1');

// create the device driver main object
var deviceDriver = midiremote_api.makeDeviceDriver('Roland', 'VM-C7x00', 'Pierre Babeck');

// create objects representing the hardware's MIDI ports
var midiInput = deviceDriver.mPorts.makeMidiInput();

// Egal welches Device
deviceDriver.makeDetectionUnit();

//-----------------------------------------------------------------------------
// 2. SURFACE LAYOUT - create control elements and midi bindings
//-----------------------------------------------------------------------------

var knobs = []
var faders = []
//var labels = []
var numChannels = 16

for(var ci = 0; ci < numChannels; ci++) {

    var knob = deviceDriver.mSurface.makeKnob(ci * 2, 0, 2, 2)  // position x : number, y : number, w : number, h : numbe
    knob.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        //.setOutputPort(midiOutput)
        .bindToControlChange (ci, 10)           // Chanel 0-16, CC 10 PAN

    knobs.push(knob)

    var fader = deviceDriver.mSurface.makeFader(ci * 2 + 0.5, 2, 1, 6)  // position    
    // Label direkt an den Fader, muss dann nicht extra aufgerufen werden
    var label = deviceDriver.mSurface.makeLabelField(ci * 2,  8,  2,  1)  // position
    label.relateTo(fader);

    fader.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        //.setOutputPort(midiOutput)
        .bindToControlChange (ci, 7)            // Chanel 0-16, CC 7 VOL

    faders.push(fader)

    //var label = deviceDriver.mSurface.makeLabelField(ci * 2 + 0.5,  8,  2,  1)  // position
    //labels.push(label);

    //console.log("Layout x: "  + (ci * 2) + " y: " +  8);        // 16 *2 => x ist 30
}

// Time knop
var timeKnob = deviceDriver.mSurface.makeKnob(32, 6, 4, 4) ;
//timeKnob.mSurfaceValue.

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

// create at least one mapping page

var pageMixer1_16 = deviceDriver.mMapping.makePage('Mixer Page 1-16')
var locator = pageMixer1_16.mHostAccess.mTransport.mTimeDisplay.mPrimary.mTransportLocator;
//locator.setTime(timeKnob.activeMapping, "0:02:05.000");

console.log("Locator: ");

var output = '';
for (var property in locator) {
  output += property + ': ' + locator[property]+'; ';
}
console.log(output);

// create host accessing objects
// https://steinbergmedia.github.io/midiremote_api_doc/codedoc_api_reference/#mixerbankzone
var hostMixerBankZone = pageMixer1_16.mHostAccess.mMixConsole.makeMixerBankZone()    
    .includeAudioChannels()
    .includeInputChannels()
    .includeOutputChannels()

for(var ci = 0; ci < numChannels; ci++) {

    // wird automatisch iteriert
    var channelBankItem = hostMixerBankZone.makeMixerBankChannel();

    var knobSurfaceValue = knobs[ci].mSurfaceValue;
    var faderSurfaceValue = faders[ci].mSurfaceValue;

    pageMixer1_16.makeValueBinding(knobSurfaceValue, channelBankItem.mValue.mPan).setValueTakeOverModePickup();
    pageMixer1_16.makeValueBinding(faderSurfaceValue, channelBankItem.mValue.mVolume).setValueTakeOverModePickup();

    // labels wenn nicht "relateTo to fader"
    // pageMixer1_16.setLabelFieldHostObject(labels[ci], channelBankItem);
    console.log("Mapping " + (ci) );
}

// Time Knob
//pageMixer1_16
// ex:  page.makeCommandBinding(surfaceElements.transport.prevBnk.mSurfaceValue, 'Transport', 'Locate Previous Marker')

// Sysex Midi In Event, Callback Funktion !?
midiInput.mOnSysex = function (activeDevice, sysexMsg) 
{
    // set
    activeDevice.setState('getSysex', 'true');

    if(isSysexTimeMsg(sysexMsg) === true){
        console.log("Sysex TIME OK " );

        //pageMixer1_16
    }

    // get
    activeDevice.getState("getSysex"); 
}

The “isSysexTimeMsg(sysexMsg)” function checks a sysex Msg for “time” information and writes the values to global variables like “tHour”, “tMin” etc. The function is called by “midiInput.mOnSysex = function (activeDevice, sysexMsg)”. How can the values now be transferred to the “timeKnob”, for example?

Hi, you can give this code a try:

var midiremote_api=require('midiremote_api_v1')

var deviceDriver=midiremote_api.makeDeviceDriver('Roland','VM-C7x00','Pierre Babeck')

var midiInput=deviceDriver.mPorts.makeMidiInput()

var detectionUnit=deviceDriver.makeDetectionUnit()
detectionUnit.detectSingleInput(midiInput)
    .expectInputNameEquals("your MIDI In Port Name")

var knobs=[]
var faders=[]

var numChannels=16

for(var ci=0;ci<numChannels;ci++){

    var knob=deviceDriver.mSurface.makeKnob(ci*2,0,2,2)
    knob.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(ci,10)   //Channel 0-16, CC 10 PAN

    knobs.push(knob)

    var fader=deviceDriver.mSurface.makeFader(ci*2+0.5,2,1,6)  //position    
    // Label direkt an den Fader, muss dann nicht extra aufgerufen werden
    var label=deviceDriver.mSurface.makeLabelField(ci*2,8,2,1)  //position
    label.relateTo(fader)

    fader.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(ci,7)  //Channel 0-16, CC 7 VOL

    faders.push(fader)

}

var pageMixer1_16=deviceDriver.mMapping.makePage('Mixer Page 1-16')

var hostMixerBankZone=pageMixer1_16.mHostAccess.mMixConsole.makeMixerBankZone()    
    .includeAudioChannels()
    .includeInputChannels()
    .includeOutputChannels()
    //sure about these includes?

for(var ci=0;ci<numChannels;ci++){

    var channelBankItem=hostMixerBankZone.makeMixerBankChannel()

    var knobSurfaceValue=knobs[ci].mSurfaceValue
    var faderSurfaceValue=faders[ci].mSurfaceValue

    pageMixer1_16.makeValueBinding(knobSurfaceValue,channelBankItem.mValue.mPan).setValueTakeOverModePickup()
    pageMixer1_16.makeValueBinding(faderSurfaceValue,channelBankItem.mValue.mVolume).setValueTakeOverModePickup()

}

midiInput.mOnSysex=function(activeDevice,sysexMsg){
    
    if(isSysexTimeMsg(sysexMsg)===true){
        
        console.log("Sysex TIME OK")
    
    }

}

function isSysexTimeMsg(sysexMsg){

    var timeMessageLength=12
    if(sysexMsg.length!=timeMessageLength+1){
        
        return false 
    
    }
    // no need to check for a 0xF7, since a sysex always ends with that

    // time sysex header: 240 127 127 6 68 6 1
    var sFirst=[0xF0,0x7F,0x7F,0x06,0x44,0x06,0x01]
    for(var i=0;i<sFirst.length;i++){
        
        if(sysexMsg[i]!=sFirst[i]){
    
            return false
    
        }            
    
    }
    
    var tHour=sysexMsg[sFirst.length]-0x20  //default 0 is 32Dez (0x20HEX) then sub 32 = result ist hour
    var tMin=sysexMsg[sFirst.length+1]
    var tSec=sysexMsg[sFirst.length+2]
    var tFrame=sysexMsg[sFirst.length+3]

    var timeString=tHour+":"+tMin+":"+tSec+":"+tFrame

    console.log("Time: "+timeString)
    
    //no need to bind to a surfaceValue,call this, having previously set the activeMapping abstract (see page.mOnActivate event)   
    pageMixer1_16.mHostAccess.mTransport.mTimeDisplay.mPrimary.mTransportLocator.setTime(currentMapping,timeString)
    
    return true
    
}

var currentMapping

pageMixer1_16.mOnActivate=function(activeDevice,activeMapping){

    currentMapping=activeMapping

}
1 Like

Great, it works perfectly. Thanks :wink:

However, I have changed the ‘mTimeDisplay.mPrimary’ to ‘mTimeDisplay.mSecondary’.

Is there a way to query the format of the ‘mPrimary’ and ‘mSecondary’ time displays?
Bar or time?

So like:

if ‘mTimeDisplay.mPrimary’ is “bars/beats” then
“mSecondary”

Or if ‘mTimeDisplay.mPrimary’ is “time” then
‘mPrimary’

?

Yes. Check the following altered code, it’s designed to set the time to whichever display (primary or secondary) has the timeCode format.

var midiremote_api=require('midiremote_api_v1')

var deviceDriver=midiremote_api.makeDeviceDriver('Roland','VM-C7x00','Pierre Babeck')

var midiInput=deviceDriver.mPorts.makeMidiInput()

var detectionUnit=deviceDriver.makeDetectionUnit()
detectionUnit.detectSingleInput(midiInput)
    .expectInputNameEquals("your MIDI In Port Name")

var knobs=[]
var faders=[]

var numChannels=16

for(var ci=0;ci<numChannels;ci++){

    var knob=deviceDriver.mSurface.makeKnob(ci*2,0,2,2)
    knob.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(ci,10)   //Channel 0-16, CC 10 PAN

    knobs.push(knob)

    var fader=deviceDriver.mSurface.makeFader(ci*2+0.5,2,1,6)  //position    
    // Label direkt an den Fader, muss dann nicht extra aufgerufen werden
    var label=deviceDriver.mSurface.makeLabelField(ci*2,8,2,1)  //position
    label.relateTo(fader)

    fader.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(ci,7)  //Channel 0-16, CC 7 VOL

    faders.push(fader)

}

var pageMixer1_16=deviceDriver.mMapping.makePage('Mixer Page 1-16')

var hostMixerBankZone=pageMixer1_16.mHostAccess.mMixConsole.makeMixerBankZone()    
    .includeAudioChannels()
    .includeInputChannels()
    .includeOutputChannels()
    //sure about these includes?

for(var ci=0;ci<numChannels;ci++){

    var channelBankItem=hostMixerBankZone.makeMixerBankChannel()

    var knobSurfaceValue=knobs[ci].mSurfaceValue
    var faderSurfaceValue=faders[ci].mSurfaceValue

    pageMixer1_16.makeValueBinding(knobSurfaceValue,channelBankItem.mValue.mPan).setValueTakeOverModePickup()
    pageMixer1_16.makeValueBinding(faderSurfaceValue,channelBankItem.mValue.mVolume).setValueTakeOverModePickup()

}

midiInput.mOnSysex=function(activeDevice,sysexMsg){
    
    if(isSysexTimeMsg(activeDevice,sysexMsg)===true){
        
        console.log("Sysex TIME OK")
    
    }

}

function isSysexTimeMsg(activeDevice,sysexMsg){

    var timeMessageLength=12
    if(sysexMsg.length!=timeMessageLength+1){
        
        return false 
    
    }
    // no need to check for a 0xF7, since a sysex always ends with that

    // time sysex header: 240 127 127 6 68 6 1
    var sFirst=[0xF0,0x7F,0x7F,0x06,0x44,0x06,0x01]
    for(var i=0;i<sFirst.length;i++){
        
        if(sysexMsg[i]!=sFirst[i]){
    
            return false
    
        }            
    
    }
    
    var tHour=sysexMsg[sFirst.length]-0x20  //default 0 is 32Dez (0x20HEX) then sub 32 = result ist hour
    var tMin=sysexMsg[sFirst.length+1]
    var tSec=sysexMsg[sFirst.length+2]
    var tFrame=sysexMsg[sFirst.length+3]

    var timeString=tHour+":"+tMin+":"+tSec+":"+tFrame

    console.log("Time: "+timeString)
    
    //no need to bind to a surfaceValue,call this, having previously set the activeMapping abstract (see page.mOnActivate event)   
    if(activeDevice.getState("primaryUnit")=="Timecode"){

        pageMixer1_16.mHostAccess.mTransport.mTimeDisplay.mPrimary.mTransportLocator.setTime(currentMapping,timeString)
    

    } else if (activeDevice.getState("secondaryUnit")=="Timecode"){

        pageMixer1_16.mHostAccess.mTransport.mTimeDisplay.mSecondary.mTransportLocator.setTime(currentMapping,timeString)
    
    }
    
    return true
    
}

var currentMapping

pageMixer1_16.mOnActivate=function(activeDevice,activeMapping){

    currentMapping=activeMapping

}

pageMixer1_16.mHostAccess.mTransport.mTimeDisplay.mPrimary.mTransportLocator.mOnChange=function(activeDevice,activeMapping,cursor,unit){

    activeDevice.setState("primaryUnit",unit)

}

pageMixer1_16.mHostAccess.mTransport.mTimeDisplay.mSecondary.mTransportLocator.mOnChange=function(activeDevice,activeMapping,cursor,unit){

    activeDevice.setState("secondaryUnit",unit)

}
1 Like