Skip to main content

DirectAccess

DirectAccess is the low-level host access layer of the MIDI Remote API.
Use it when normal mappings are not enough and you need runtime traversal, object metadata, or parameter-level inspection/write access.

For standard control assignments, prefer normal high-level bindings (makeValueBinding(...)).

Related chapters:

Lifecycle rule (important)
A DirectAccess instance must be activated in `page.mOnActivate` and deactivated in `page.mOnDeactivate`.

1. When to use DirectAccess

Use DirectAccess when you need one of these:

  • Traverse runtime host objects by ObjectID
  • Inspect runtime metadata (getObjectTitle, getObjectTypeName, getObjectUniqueIDString)
  • Enumerate/query parameters dynamically
  • Use plugin slot collection/assignment via mPluginManager

Avoid DirectAccess when the same task can be solved by standard value bindings.

Reference:

2. Creating and wiring DirectAccess

var driver = midiremote_api.makeDeviceDriver('ExampleCompany', 'DirectAccessDevice', 'Example Author')
var page = driver.mMapping.makePage('DirectAccess Page')
var hostObject = page.mHostAccess.mTrackSelection.mMixerChannel

// API 1.2+
if (page.mHostAccess.makeDirectAccess) {
var da = page.mHostAccess.makeDirectAccess(hostObject)

page.mOnActivate = function (activeDevice, activeMapping) {
da.activate(activeMapping)
}

page.mOnDeactivate = function (activeDevice, activeMapping) {
da.deactivate(activeMapping)
}
}

Reference:

3. Runtime object traversal model

DirectAccess methods live on da.
Sub-nodes are addressed by runtime ObjectID values.

Typical traversal flow:

  1. getBaseObjectID(activeMapping)
  2. getNumberOfChildObjects(activeMapping, objectID)
  3. getChildObjectID(activeMapping, objectID, index)
  4. Read metadata for each node

Reference:

function visitSubtree(da, activeMapping, objectID, depth) {
var title = da.getObjectTitle(activeMapping, objectID)
var typeName = da.getObjectTypeName(activeMapping, objectID)
console.log(' '.repeat(depth) + '[' + objectID + '] ' + title + ' (' + typeName + ')')

var childCount = da.getNumberOfChildObjects(activeMapping, objectID)
for (var i = 0; i < childCount; ++i) {
var childObjectID = da.getChildObjectID(activeMapping, objectID, i)
visitSubtree(da, activeMapping, childObjectID, depth + 1)
}
}

function logTree(da, activeMapping) {
var rootObjectID = da.getBaseObjectID(activeMapping)
if (rootObjectID < 0) return
visitSubtree(da, activeMapping, rootObjectID, 0)
}

4. Parameter discovery and conversion

ObjectID and parameterTag are the core lookup keys.

function logParameters(da, activeMapping, objectID) {
var count = da.getNumberOfParameters(activeMapping, objectID)
for (var i = 0; i < count; ++i) {
var parameterTag = da.getParameterTagByIndex(activeMapping, objectID, i)
var parameterTitle = da.getParameterTitle(activeMapping, objectID, parameterTag, 128)
var display = da.getParameterDisplayValue(activeMapping, objectID, parameterTag)
var units = da.getParameterDisplayUnits(activeMapping, objectID, parameterTag)
var typeName = da.getParameterProcessValueType(activeMapping, objectID, parameterTag)
var automatable = da.isParameterAutomatable(activeMapping, objectID, parameterTag)
console.log(parameterTitle + ' = ' + display + units + ' [' + typeName + '] automatable=' + automatable)
}
}

Process value and plain value conversion:

var processValue = 0.5
var plainValue = da.convertParameterProcessValueToPlain(activeMapping, objectID, parameterTag, processValue)
var backToProcess = da.convertParameterPlainToProcessValue(activeMapping, objectID, parameterTag, plainValue)

Reference:

5. Writing parameters safely

function setParameterHalf(da, activeMapping, objectID, parameterTag) {
da.setParameterProcessValue(activeMapping, objectID, parameterTag, 0.5)
}

If your script writes multiple parameters in a compact interaction flow, test and use edit-lock APIs where appropriate:

var wasLocked = da.getParameterEditLockState(activeMapping, objectID, parameterTag)
da.setParameterEditLockState(activeMapping, objectID, parameterTag, true)

// ... perform coordinated changes ...

da.setParameterEditLockState(activeMapping, objectID, parameterTag, wasLocked)

6. DirectAccess callbacks

DirectAccess supports callback hooks for incremental updates:

  • mOnObjectChange
  • mOnObjectWillBeRemoved
  • mOnParameterChange

Reference:

Use them for cache invalidation and targeted refresh logic:

da.mOnObjectWillBeRemoved = function (activeDevice, activeMapping, objectID) {
delete objectCache[objectID]
}

7. Plugin manager (advanced)

The plugin manager is available through da.mPluginManager and enables slot-level plugin browsing and assignment.

var collectionCount = da.mPluginManager.getNumberOfPluginCollections(activeMapping, pluginSlotObjectID)
var activeCollectionIndex = da.mPluginManager.getIndexOfActivePluginCollection(activeMapping, pluginSlotObjectID)

if (activeCollectionIndex >= 0 && activeCollectionIndex < collectionCount) {
var collection = da.mPluginManager.getPluginCollectionByIndex(activeMapping, pluginSlotObjectID, activeCollectionIndex)
if (collection.mEntries.length > 0) {
var pluginUID = collection.mEntries[0].mPluginUID
da.mPluginManager.trySetSlotPlugin(activeMapping, pluginSlotObjectID, pluginUID, true)
}
}

Reference:

8. Compatibility strategy (API 1.2 and 1.3)

Compatibility note
DirectAccess is an API 1.2+ feature. Guard usage with feature checks if your script still supports older host versions.

Example guard pattern:

var driver = midiremote_api.makeDeviceDriver('ExampleCompany', 'DirectAccessDevice', 'Example Author')
var page = driver.mMapping.makePage('Main')
var hostObject = page.mHostAccess.mTrackSelection.mMixerChannel
if (page.mHostAccess.makeDirectAccess) {
var da = page.mHostAccess.makeDirectAccess(hostObject)
}

For API 1.3 additions (for example platform detection filters or extended parameter inspection), check method availability before calling:

if (da.getParameterProcessValueType) {
// API 1.3+ method usage
}

See also: