@oldgearguy thanks a lot.
I went through your documentation but I already knew this.
I start to doubt on my own code or on the Mirage it self.
I have now a version that sends individual parameter status messages and then these are parsed in the patch.on response.
This seems to work but need to debug further.
Now its just a matter of debugging the timer…and the correct period per situation like large values, parameter sync.
Its all still full of bugs. But I I am getting there.
Now the controls get stuck somehow. even overflow the mirage.
But if I slow down things I miss values.
“rate” does not seem to do much neither looking at the message timestamps in the logs. .
tmrPeriod = 50
timer.setPeriod(tmrPeriod)
timer.disable()
---
--- check and test timer period. above is the default.
--- in the syncProgParameters() we should slow down
--- In the mainworker() we slowdown in some cases
--- In the patchon response we go back to the default after processing
---
-- set to an impossible value to force mainWorker() to process first touched control
previousParameterNumber = -1
-- holds the value and direction (up or down) for each encoder
-- this is what the timer function uses to send to the Mirage
lastValue = {}
lastDirection = {}
-- holds the current value of each encoder as a user changes different controls
-- when a user comes back to a control, we know the current value to use in the next calculations
curEncValue = {}
oldValue = 0
maxBytes = 5
upArrow= 14
downArrow = 15
-- temporary texts to make reading debug statements easier
dbgTxt = {}
dbgTxt[14] = " Up"
dbgTxt[15] = " Down"
--de values used for sysex message
parameterSelect = 12
valueSelect = 13
functionKey = 22
endCommand = 127
--cancel = 11
-- which control is currently being changed
currentControl = 0
--Disables parsing of parameter update messages in patch.on.Response() its enabled when syncProgParameters() is called
--then disabled when the queue is empty.
progDataEnabled = 0
-- Parameters we send using another queue
queME = {36, 60, 61, 62, 63, 64, 68, 66, 69, 70, 71, 73, 76 }
programParameter = { 28, 31, 32, 33, 34, 35, 36, 37, 38, 27, 29, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 91 }
mirageDeviceId = 1
device = devices.get (mirageDeviceId)
queue = {}
queue.first = 0
queue.last = -1
queue.data = {}
function insert(q, val)
q.last = q.last + 1
q.data[q.last] = val
end
function remove(q)
if (q.first > q.last) then
rval = -1
else
print("remove: q.data[q.first]= ", q.data[q.first], " q.first= ", q.first)
local rval = q.data[q.first]
midi.sendSysex(PORT_1, rval)
q.data[q.first] = nil
q.first = q.first + 1
-- print("remove: q.first= ", q.first)
end
return rval
end
function syncProgParameters()
--
-- This sends parameter messages using front panel commands.
-- [91] ext computer should be on. #FIXME test for this
currentControl = 0
-- enable the `progDataEnable` flag, allowing patchon.Reponse to ingest the incomming program parameter messages
local progDataEnable = 1
progDataEnabled = progDataEnable
--print(progDataEnabled)
--slowing doww timer, so we do not overload the mirage
tmrPeriod = 150
timer.setPeriod(tmrPeriod)
timer.enable ()
for progParaCount = 1, 32 do
-- programParameter = global array holding all parameters we want to sync
parameterK = programParameter[progParaCount]
print ("Parameter Number as passed from the for loop: " .. parameterK)
-- common way to separate the tens digit and the ones digit
local byte1st = math.floor(parameterK / 10)
local byte2nd = parameterK % 10
sysexMsg = { 15, 1, 1, parameterSelect, byte1st, byte2nd, valueSelect, endCommand }
insert(queue, sysexMsg)
-- if ( progParaCount == 10 ) then
-- fetchConfig()
-- end
end
end
local function slow_Enable (tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
--dec on the left - hex on the right
function formatHex (valueObject, value)
return value .. string.format("%x", value)
end
function timer.onTick()
--
-- flag here to enable processing of the queue only when needed?
--
if (queue.first < queue.last) then
remove(queue)
end
if (currentControl ~= 0) then
--print("are we here?")
local av = math.abs(lastValue[currentControl])
local ad = lastDirection[currentControl]
print("currentControl = " .. currentControl)
if ( av > 5 ) then
print(" *** Sending 5 arrowBytes, direction is" .. dbgTxt[ad])
lastValue[currentControl] = av - 5
sysexMsg = { 15, 1, 1, ad, ad, ad, ad, ad, endCommand }
midi.sendSysex(PORT_1, sysexMsg)
elseif ( av < 5 ) and ( av > 0) then
print (" *** Sending remaining bytes " .. av .. dbgTxt[ad])
sysexMsg = {15, 1, 1}
for i = 1,av do
sysexMsg[3+i] = ad
end
-- note that the variable i does not exist after the for loop so we cannot use it here
sysexMsg[4+av] = endCommand
midi.sendSysex(PORT_1, sysexMsg)
-- finished sending bytes, zero out the value so we do not send extra bytes if called again
lastValue[currentControl] = 0
currentControl = 0
else
-- catch an edge case
-- somehow we get called for the current control, but with no change in value
print ("Sending nothing - Edge case")
-- lastValue[currentControl] = 0
currentControl = 0
end
end
end
function mainWorker(ValueObject, Value)
local message = ValueObject:getMessage()
local currentParameterNumber = message:getParameterNumber ()
local curValue = message:getValue()
-- worker function called, save off the parameter number
currentControl = currentParameterNumber
if ( currentParameterNumber ~= previousParameterNumber ) then
-- do not have the timer running in case it catches us with partially filled in data
timer.disable()
-- we store the current value as the previous for the next time
previousParameterNumber = currentParameterNumber
-- common way to separate the tens digit and the ones digit
local byte1st = math.floor(currentParameterNumber / 10)
local byte2nd = currentParameterNumber % 10
sysexMsg = { 15, 1, 1, parameterSelect, byte1st, byte2nd, valueSelect, endCommand }
midi.sendSysex(PORT_1, sysexMsg)
-- NOTE - this assumes your controls have a default value of 0
-- if they start with a default of 64 or something else, different code needs to go here
-- get the last value for this control
oldValue = (curEncValue[previousParameterNumber] or 0)
timer.enable()
end
-- we need to keep track of the last value for each encoder
-- there is probably a better way to pull this from the control
-- so we do not have to keep storing it
curEncValue[currentParameterNumber] = curValue
if ( currentParameterNumber == previousParameterNumber ) then
if slow_Enable(queME, currentControl) then
print("yes control on the list lets slowdown : " .. currentControl)
tmrPeriod = 1
else
print("no control not on the list timer to normal : " .. currentControl)
tmrPeriod = 50
end
timer.setPeriod(tmrPeriod)
-- figure out how much the encoder has changed from last time to this time
-- note that the value can be positive or negative at this point
local delta = curValue - oldValue
-- save off the current value
oldValue = curValue
-- declare a local variable and set it to something
local arrowDirection = upArrow
-- the default is upArrow (positive) so only do things for negative or zero cases
if ( delta == 0 ) then
print("nothing to do")
return
elseif ( delta < 0 ) then
arrowDirection = downArrow
end
-- for this parameter, save off the last value sent and the direction
lastValue[currentParameterNumber] = (lastValue[currentParameterNumber] or 0) + delta
lastDirection[currentParameterNumber] = arrowDirection
end
end
function commandMessage(ValueObject, Value)
local message = ValueObject:getMessage()
local currentParameterNumber = message:getParameterNumber ()
local curValue = message:getValue()
local byte1st = math.floor(currentParameterNumber / 10)
local byte2nd = currentParameterNumber % 10
sysexMsg = { 15, 1, 1, parameterSelect, byte1st, byte2nd, endCommand }
midi.sendSysex(PORT_1, sysexMsg)
end
--
-- FIXME... not 100% working.. Key range 0 .. 61 0 = C
--
function noteName(valueObject, value)
local noteArray = { 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#' }
-- this device uses a note range of C(-2) for MIDI note number 0 to G(8) for MIDI note 127
-- if your device uses a different range, like C(0) to C(10) or C(-1) to C(9)
-- adjust the noteOct subtraction value accordingly
noteLetter = (value % 12) + 1
noteOct = math.floor(value / 12) - 2
return (string.format("%s %d", noteArray[noteLetter], noteOct))
end
function makeSwapB(first, second)
-- the AND operation (ampersand) captures only the 4 bits of each byte.
-- the left shift operation moves the 4 bits into the proper place
retVal = ((second & 0x0F) << 4) | (first & 0x0F)
return retVal
end
--
function patch.onResponse (device, responseId, sysexBlock)
-- first thing to do is always check the size and if it is not right, throw it away
local recvSize = sysexBlock:getLength ()
print (string.format("we recieved some data. length = %d", recvSize))
--if it is a valid program message
print("progdata enable is set to : " .. progDataEnabled)
--Swap this arround? first check size and then the `progDataEnabled` flag?
if ( progDataEnabled == 1 ) then
if (recvSize == 9) and (sysexBlock:peek(4) == 13) then
local deSyx = {}
local topBits
local highBit
local k = 0
print (string.format("skipping, Received probably program dump data. length = %d", recvSize))
local skipBytes = 5
sysexBlock:seek(skipBytes)
-- header 4 bytes, byte 4 is paraMeter status message
-- F0h 0Fh 01h 0Dh 00h 2Fh 00h 00h F7h
--skipByes = 5 and for loop for 4 bytes we got:
--14:20:15.215 lua: value 3.0 byte position 0 (program, keyboardhalf)
--14:20:15.216 lua: value 56.0 byte position 1 (parameter)
--14:20:15.216 lua: value 4.0 byte position 2
--14:20:15.216 lua: value 5.0 byte position 3
for i = 1, 4, 1 do
deSyx[k] = sysexBlock:read()
--print("value " .. deSyx[k] .. " byte position " .. k)
--print ("programbyte = " .. deSyx[0] )
k = k + 1
end
-- should change a name of a control so the user knows
-- Paramet 181 should be used for displaying the message
-- U1 16, U2 17, U3 18, U4 19
if (deSyx[0] < 4) then
Lower = deSyx[0] +1
print ("Lower KBD has Program " .. Lower)
elseif (deSyx[0] > 15) then
Upper = deSyx[0] -15
print ("Upper KBD has Program " .. Upper)
else
print ("error program bytes does not make sense byte value = " .. deSyx[0] )
end
print("The message we recieved is for Parameter: " .. deSyx[1])
if (deSyx[1] == 34) then
value = makeSwapB(deSyx[2], deSyx[3]) / 2
print("Value devided by 2 : " .. value )
elseif (deSyx[1] == 35) then
value = makeSwapB(deSyx[2], deSyx[3]) / 2
print("Value devided by 2 : " .. value )
elseif (deSyx[1] == 36) then
value = makeSwapB(deSyx[2], deSyx[3]) / 2
print("Value devided by 2 : " .. value )
elseif (deSyx[1] == 37) then
value = makeSwapB(deSyx[2], deSyx[3]) / 2
print("Value devided by 2 : " .. value )
elseif (deSyx[1] == 45) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 46) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 47) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 48) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 49) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 55) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 56) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 57) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 58) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
elseif (deSyx[1] == 59) then
value = makeSwapB(deSyx[2], deSyx[3]) / 4
print("Value devided by 4 : " .. value )
else
value = makeSwapB(deSyx[2], deSyx[3])
end
parameterMap.set(1, PT_VIRTUAL, deSyx[1], value)
if (deSyx[1] == 59) then
local progDataEnable = 0
progDataEnabled = progDataEnable
--tmrPeriod = 100
--When done setting the timer back to default
timer.setPeriod(tmrPeriod)
timer.disable()
end
--print("The value we got is: " .. value )
end
elseif (recvSize == 63) then
print (string.format("Received configuration data. length = %d", recvSize))
local skipBytes = 5
local deSyx = {}
local topBits
local highBit
local k = 0
--decode the received data, starting after header data
sysexBlock:seek(skipBytes)
-- LUA allows you to use a for loop to process data and allows you to move in steps greater than one.
-- this says - starting at the first data byte after the header, read until the end, and count by 2 while doing it
for i = 1, (sysexBlock:getLength () - skipBytes), 2 do
local firstB = sysexBlock:read()
local secondB = sysexBlock:read()
deSyx[k] = makeSwapB(firstB, secondB)
k = k + 1
end
parameterMap.set(1, PT_VIRTUAL, 21, deSyx[1])
parameterMap.set(1, PT_VIRTUAL, 22, deSyx[2])
parameterMap.set(1, PT_VIRTUAL, 23, deSyx[3])
-- value needs to be devided by 2
deSyxHalf = math.floor(deSyx[4] / 2)
parameterMap.set(1, PT_VIRTUAL, 24, deSyxHalf)
parameterMap.set(1, PT_VIRTUAL, 25, deSyx[5])
parameterMap.set(1, PT_VIRTUAL, 73, deSyx[6])
-- value needs to be devided by 2
deSyxHalf = math.floor(deSyx[7] / 2)
parameterMap.set(1, PT_VIRTUAL, 74, deSyxHalf)
parameterMap.set(1, PT_VIRTUAL, 75, deSyx[8])
-- value needs to be devided by 2
deSyxHalf = math.floor(deSyx[9] / 2)
parameterMap.set(1, PT_VIRTUAL, 76, deSyxHalf)
parameterMap.set(1, PT_VIRTUAL, 77, deSyx[10])
parameterMap.set(1, PT_VIRTUAL, 81, deSyx[11])
parameterMap.set(1, PT_VIRTUAL, 82, deSyx[12])
parameterMap.set(1, PT_VIRTUAL, 83, deSyx[13])
parameterMap.set(1, PT_VIRTUAL, 84, deSyx[14])
-- HERE are other config parameters that are very usefull.
-- left them for later #FIXME#
-- 85 Source start MSB 85
-- parameterMap.set(1, PT_SYSEX, 85, deSyx[15])
-- Source start LSB 86
-- parameterMap.set(1, PT_SYSEX, 86, deSyx[16])
-- Source end MSB 87
-- parameterMap.set(1, PT_SYSEX, 87, deSyx[17])
-- Source end LSB 88
-- parameterMap.set(1, PT_SYSEX, 88, deSyx[18])
-- Destination MSB 89
-- parameterMap.set(1, PT_SYSEX, 89, deSyx[19])
-- Destination LSB 90
-- parameterMap.set(1, PT_SYSEX, 90, deSyx[20])
-- Destination Bank 94 ???
-- parameterMap.set(1, PT_SYSEX, 94, deSyx[21])
-- Scale start factor 95 ??
-- parameterMap.set(1, PT_SYSEX, 95, deSyx[22])
-- Scale end factor 96 ??
-- parameterMap.set(1, PT_SYSEX, 96, deSyx[23])
-- External Computer Switch 91 !!Should be on!!!
-- parameterMap.set(1, PT_SYSEX, 91, deSyx[24])
-- Baudrate Switch 92 ????
-- parameterMap.set(1, PT_SYSEX, 92, deSyx[25])
-- Filter freq ISF-1
-- parameterMap.set(1, PT_SYSEX, 93, deSyx[26])
-- Software Vesrion
-- parameterMap.set(1, PT_SYSEX, 97, deSyx[27])
elseif (recvSize > 950) and (recvSize < 1950) then
print (string.format("Received Program dump data block. length = %d", recvSize))
local skipBytes = 23
local deSyx = {}
local topBits
local highBit
local k = 0
-- decode the received data, starting after header data
sysexBlock:seek(skipBytes)
-- LUA allows you to use a for loop to process data and allows you to move in steps greater than one.
-- this says - starting at the first data byte after the header, read until the end, and count by 2 while doing it
for i = 1, (sysexBlock:getLength () - skipBytes), 2 do
local firstB = sysexBlock:read()
local secondB = sysexBlock:read()
-- note that we are just build our transformed sysex data buffer here.
-- the storing and parsing of the transformed data is done below
deSyx[k] = makeSwapB(firstB, secondB)
print("value " .. deSyx[k] .. " byte position " .. k)
k = k + 1
end
-- waveParameters, where waveSampleSelect() will provide the input for the correct offset where to find
-- wave 1 to 8, waveblock 1 .. 8
parameterOffset = ( waveSelect * 24 - 24 )
waveParameter = { 65, 67, 68, 69, 70, 71, 72, 60, 61, 62, 63, 64 }
for nameCount = 1, 11 do
syxByte = (parameterOffset + ( nameCount -1 ) )
print (string.format("syxByte %d", syxByte ))
if ( syxByte == ( parameterOffset + 4 ) ) or ( syxByte == ( parameterOffset + 5)) then
deSyxHalf = math.floor(deSyx[syxByte] / 2)
parameterMap.set(1, PT_VIRTUAL, waveParameter[nameCount], deSyxHalf)
print (string.format("parameter %s - value / 2 = %d", waveParameter[nameCount], deSyxHalf))
else
parameterMap.set(1, PT_VIRTUAL, waveParameter[nameCount], deSyx[syxByte])
print (string.format("parameter %s - value = %d", waveParameter[nameCount], deSyx[syxByte]))
end
end
else
print("sure nothing here")
end
end
waveSelect = 0
function waveSampleSelect(ValueObject, Value)
local message = ValueObject:getMessage()
local currentParameterNumber = message:getParameterNumber ()
local curValue = message:getValue()
if ( curValue < 9 ) and ( curValue > 0 ) then
-- lower keyboard wave
local curWave = 21
waveSelect = curValue
sysexMsg = { 15, 1, 1, curWave, curValue, endCommand }
midi.sendSysex(PORT_1, { 15, 1, 3 } )
elseif ( curValue > 8 ) then
-- upper keyboard wave
local curWave = 20
local curValue = curValue - 8
waveSelect = curValue
sysexMsg = { 15, 1, 1, curWave, curValue, endCommand }
midi.sendSysex(PORT_1, { 15, 1, 19 } )
end
midi.sendSysex(PORT_1, sysexMsg)
end
function fetchConfig()
sysexMsg = { 15, 1, 0 }
midi.sendSysex(PORT_1, sysexMsg)
end
function dataExec(ValueObject, Value)
local message = ValueObject:getMessage()
-- local currentParameterNumber = message:getParameterNumber ()
local curValue = message:getValue()
local functionNumber = ( message:getParameterNumber () - 200 )
sysexMsg = { 15, 1, 1, functionKey, functionNumber, endCommand }
midi.sendSysex(PORT_1, sysexMsg)
end
Template link: Electra One App