Ensoniq Mirage MASOS template

I am working on a Ensoniq Mirage Masos template.
Masos supports sysex but does not support parameter changes by sysex.
The way to do it is to use sysex that sends key presses on the front panel.

0F 01 Ensoniq, Mirage
01 Mirage command
then up to 5 bytes of front panel messages, terminated with enter if the command requires it.
7F End of the command marker

Here is the decoding table

Hex front panel Hex front panel
00 0/prog 14 rec seq
01 1 15 play seq
02 2 16 load seq
03 3 17 save seq
04 4 10 load upper
05 5 11 load lower
06 6 12 sample upper
07 7 13 sample lower
08 8 0C PARAM
09 9 0D Value
0A Enter/Start 0E UP ARROW
0B Cancel/Stop 0F DOWN ARROW

So here parameter 25 1 value up.
0F 01 01 0c 02 05 0D 0E 7F

parameter 25 1 value decrement (arrow down)
0F 01 01 0c 02 05 0D 0F 7F

parameter 36, filter cutoff value 1 up.
hex syx 0F 01 01 0c 03 06 0D 0E 7F

The next increaments you only have to send “arrow up”, but again max 5 bytes
So here value +5

hex syx 0F 01 01 0E 0E 0E 0E 0E 7F

What I need is eone to:

  • know what Parameter is going to be changed. so it only sends once the command to jump to that value on the front panel
  • Then it needs to know if its an increment or decrement and sends this in max 5bytes of commands per message: see 0E up and 0F down
    I should be also smart enough to not send a value if a up and down or down and up are requested. 1 - 1 = 0… nothing to change
  • the Mirage device cannot deal with to much data

So here the hint from Martin:

  • optional
  • numeric
  • min = 10
  • max = 1000
"devices": [
   {
      "id": 10,
      "name": "Ensoniq Mirage Masos",
      "instrumentId": "ensoniq-mirage-masos",
      "port": 1,
      "channel": 1,
      "rate": 20
   }
]

… sends messages in 20ms intervals.

I know the eone has touch sensitive pots.
But I did not see any lua callback for that.
So I could store the last used control ID, if it differs send the full parameter select message and store the last used control ID.

But how to send the sysex for the increments or decrements?

Some hints will be more then welcome.
Once al the basis synth Parameters like envelopes, filter, modulation are working I am thinking to make the template in a way so you can easily create multisamples.

Summary

This text will be hidden

1 Like

Keeping my fingers crossed for this!!! Looking forward to get deeper into my Mirage! Thank you for exploring!

1 Like

Sure, I hope to make a preset that will make The Mirage a bit more userfriendly.
And the eone is the best controller that can do that.

I bought a second Mirage. Hope I will be able to fix it.

1 Like

@Flyweight Sure thing! Keeping my fingers crossed for your repairs too…
@martin might be able to chime in some more in the future when time allows, I think… :wink:

I had mentioned at some point that for me of course I’d love if the sample editing part could be implemented in these E1 presets too… The old machines couldn’t handle big sound files and having all those abilities in one spot would be a dream for me! (but obviously way beyond my abilities…) :stuck_out_tongue:

Yeah you can send samples using sysex.
So I am thinking to find out how-to encode the sample in sysex using a script (python) and then the control template could help.
You could pre-define different sampling setups.
Send the needed samples from a computer to the Mirage on the correct memory locations etc.

But thirst getting the parameters to work.

1 Like

Great! Looking forward to see where this goes! :slight_smile:

I faced something similar when creating the tc 2290 preset. They use a string of sysex commands to simulate pushing front panel buttons.
I started an implementation of a few different ways to solve it.

I know, I know - my answer to every question is – “I used some LUA coding to do it”. :face_with_raised_eyebrow:

but for me it’s the best way to gain complete control over what is being sent when you have all these non-standard needs.

One way is to use a short list control with the values -1, 0, and +1. So as you turn the encoder clockwise, it sends out a string to increase the value by 1 and counterclockwise decreases by 1.

The other way is to implement a timer and an up arrow and down arrow and have it keep sending increments or decrements as long as you are holding down the key.

The third way is to use a keypad on screen and just type in the number you want.

Unfortunately, my time to do any music stuff is very very limited and very unpredictable, so often I start some of these presets, get most of the way done, then stop working on them for long periods of time. Hopefully I can get back to them and get them polished up and finished.

1 Like

hey thanks a lot for your input.
yeah i have to definitely use LUA.
the Mirage is a pain in the but to use due to its interface.
so the third option to make an onscreen keypad wil not improve the interface.

wil look at your preset and see if i can adapt your approach.

thanks a lot!

cheers
Tim

edit: great I think this is going into the direction I need.
I will try to work on something like this.
Just an idea is to make 3 functions, pseudoCodeKeyCommand, adjValueUp, adjValueDown ( range to be defined my midi min max)


function adjValueUp()
  --send up to 5bytes of arrow up messages in a specific time. after timeout send 
  --whatever we have. 
  --if value is Max do nothing 
end 

function adjValueDown()
--send up to 5bytes of arrow down messages in a specific time. after timeout send -- -----whatever we have. 
--if value is Min do nothing 
end 

function pseudoCodeKeyCommand (ValueObject, Value)
-- global lastUsedControlid = id of the last used control, or store it in a hidden virtual parameter in the parameter map.... not sure

-- only send once
If controlId != lastUsedControlid
 then 
  lastUsedControlid = controlId
  --send SYSEX to select parameter
 end
if value > currentValue
  adjValueUp()
end
if value <  currentValue
  adjValueDown()
end
end
-- extend adj, arrow, type to all entries - feedback, mod, delay time, etc
-- define range to be 0 to 2 ??
function adjValue(ValueObject, Value)
   local highLow = {}

   local message = ValueObject:getMessage()
   local keyNum = message:getParameterNumber ()
   print(string.format("param number = %d", keyNum))
   highLow = splitByte(keyNum)
   print(string.format("High byte = %d, low byte = %d", highLow[1], highLow[2]))


   if (Value == 0) then
      dir = highLow[2] + 1
   elseif (Value == 2) then
      dir = highLow[2]
   end

--   dir = keyVal[2] + Value
   if (Value ~= 1) then
      midi.sendSysex(MIDI_IO, PORT_1, {0x33, 0x00, 0x05, highLow[1], dir})
      midi.sendSysex(MIDI_IO, PORT_1, {0x33, 0x00, 0x05, 0x0F, 0x0F})
   end
end
1 Like

Yeah, I think that’s a good start. As you know - this kind of stuff is heavily dependent on the sysex implementation. The tc 2290 manuals are very detailed about how key values are sent and how they implement a new ‘HOLD’ code if a key is held down for 20ms or more. They envisioned having one 2290 slaved to another and didn’t want the receiving device to get overwhelmed.

I was trying to keep the function generic so that it would use the correct keycode based on the parameter number of the control. The 2290 uses 2 bytes for everything and in the sysex mapping, the Delay Time Up Arrow key maps to 40 (so two bytes = 0x02, 0x08) and Down maps to 41 (0x02, 0x09) and that’s why I used an array for each entry.

Other manufacturers are not so detailed so you have to use a lot of trial and error.

Suggestion is to get it working for one control, then take the time to figure out how to make it more generic by using the parameter number for each control as some kind of index.

I did have problems in the past trying to use a list. The values sent would not always correspond to the direction turned (or be off by 1 or something). I think Martin has tightened these issues up considerably over time, but it helps at first to add some print() statements and look at the log to verify what is being sent out.

2 Likes

The other thing to note is that I tend to use a lot of virtual controls to be able to better handle the output flow. With a standard CC/Note control, the value is sent every time to control is moved. With a virtual control, you can determine the rate and content.

But - the function attached to the virtual control is still called for every movement. So keeping some globals around to track the current state can be useful.

If I turn a control quickly and the E1 screen shows an increment of 12, the function will be called 12 times. You do not know when the user stops turning, so you have to either spit out ‘increment by 1’ commands or do something more complicated.

You can try implementing the simple case first and see if the Mirage can get overwhelmed. It may not.

1 Like

coffee really kicking in here …

If you use a standard fader/list control, you can implement essentially a queue system. The control simply increments the total count and sets a ‘time_to_increment’ flag to true.
A separate timer function running every x milliseconds checks the ‘time_to_increment’ flag and if true, sends out an increment_by_one sysex string and decrements the total count. When the total count is zero it sets the ‘time_to_increment’ flag to false.

You can do additional things like if the user turns it to decrement, the increment flag can be set to false, the increment counter can be zeroed out, etc.

Alternately (and what I did to handle continually pressing an up/down arrow) is to use the timer to send an increment or decrement as long as the arrow button was being pressed. This forces you to hold the button a bit scrolling from 0 to 127 (or in my case 0 seconds to 32 seconds in millisecond increments and why I implemented a direct keypad to enter values). Again, you can do things like create an acceleration by either jumping values after a few iterations or by changing the interrupt time.

I need a week off from work to try these things out…

1 Like

Hey @oldergearguy thanks so much for your input on this.
I really appreciate this a lot.
I remember you and @martin teaching me a lot about sysex and midi.

Thanks for that!

Its also good to know @martin has a Mirage as well :slight_smile:

Yes i was planning to use virtual parameter for the most.
I wanted to write some pseudo code with your suggestion but my head starts spinning.

I am thinking if I can use negative numbers, so the counter would go to -5 for 5x down arrow or + 5 for 5x up arrow.
Like this you can just check every timer tick what to send.

1 Like

The beauty of the E1 is that you can start very simple and experiment very quickly.
I would create a control, decide how to send +1 and -1, and see what happens. Don’t try to design the whole thing at once. Also don’t worry about edge cases, switching things around, etc. Get the basic case working first and then see what needs to be done next.

2 Likes

I did a quick edit to my 2290 preset to highlight just a couple controls on one page.
The preset is here: tc 2290 example

There are two rotary controls that separately adjust two values (displayed in the buttons above the controls) and a momentary up/down pair that continually changes the value as long as you hold it (or it maxes out)

It is a stand-alone example, no need to hook it to anything

2 Likes

Thanks I will check it and try if I can get it to work for the Mirage.

Just for far future reference: Although in the sourcecode also a lot of documentation for the sysex implementation.

There is a Mirage Editor for windows written in C++

https://sourceforge.net/p/ensoniqmirageed/code/ci/master/tree/

This might give the hints to convert a wav file to sysex.

I image the Eone template would guide you through the sample setup.
Different scenarios pre-pared.

example scenario:

  1. select: make a multi sample with 4 samples per keyboard half. ( it sets multisampling to on, sampling frequency, sets key alloc, and divides the available memory. )
  2. select L1 ( lower half )
  3. select sample 1
  4. now send ( via sysex ) the sample 1, it tels you the exact frequency the sample should be.
    4.2 now send ( via sysex ) the sample 2, it tels you the exact frequency the sample should be.
    4.3 …
  5. set looppoints
  6. Ask to insert a data disk.
  7. save
		case WAVE_DUMP_DATA:
			sysex_ptr = ((unsigned char *)&WaveSample.SampleData);
			memset(sysex_ptr,0, sizeof(WaveSample.SampleData));
			ptr = LongMessage; 
//			sysexlength = LongMsg.GetLength();
			if ( *(LongMessage) == 0xF0 )
			{
				LongMessage += 4; /* First 4 bytes are the sysex header */
				/* Next two bytes are the pagecount */
				WaveSample.samplepages = de_nybblify(*(LongMessage),*(LongMessage+1));
				byte_counter += 6;
				LongMessage += 2;
			}
			while ( byte_counter < (sysexlength - 2) )
			{
				/* Reconstruct the byte from the nybbles and copy it to the correct structure*/
				sysex_byte = de_nybblify(*(LongMessage),*(LongMessage+1));
				memcpy(sysex_ptr++, &sysex_byte,1);
				LongMessage += 2;
				byte_counter += 2;
			}
			LongMessage = ptr;
			WaveSample.checksum = (unsigned char)*(LongMessage + (sysexlength - 2 ));
			return;
			break;
		case SMP_PARM_MSG:
			sysex_ptr = ((unsigned char *)&ParmCurValue);
			memset(sysex_ptr,0,9);
			ptr = LongMessage; 
//			sysexlength = LongMsg.GetLength();
			if ( *(LongMessage) == 0xF0 )
			{
				/* the 5th byte is the parameter number */
				ReceivedParmNumber = *(LongMessage+5);
				ReceivedParmValue[ReceivedParmNumber] = de_nybblify(*(LongMessage+6),*(LongMessage+7));
			}
			return;
			break;
1 Like

Its a longway to go… But this is what I got so far.
…it kind of works but as wel it doesn’t.
So yeah its ugly.
Shouldn’t I fetch this local curValue = message:getValue () from the parameterMap?
Al parameters now use one global. Tracking all of them separately looks like a lot of work.
It works if you do not twist the knob too fast :slight_smile: Then it still works only it sends some uparrow bytes when you turn the knob down. But the total of ups and downs is ok.
So you go from 100 to 0 … you see 110 downarrow bytes and 10 up.

Also 1 byte is not send when you touch another pot/parameter.

Any suggestions would be more then welcome.

I am trying to make it work but it looks quite above my skill set.
The bytes are just bogus now.
Will send real data when it looks a bit better.

Edit: just removed the timer… seem to work better without.

masostest-2-working.epr (1.6 KB)

mirageDeviceId = 9
previousParameterNumber = nil
lastValue = nil
maxBytes = 5

function mainWorker(ValueObject, Value)
   local message = ValueObject:getMessage()
   local currentParameterNumber = message:getParameterNumber ()
   local curValue = message:getValue ()

  if ( currentParameterNumber ~= previousParameterNumber ) then
    --global, used to send parameter select once, they are not the same so we send a parameter select message 
    --
    midi.sendSysex(PORT_1, {0x33, 0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x04, 0x04})

    previousParameterNumber = currentParameterNumber
    --We store the parameter values as well only once per fresh call
    _G.lastValue = message:getValue ()
  end

  if ( currentParameterNumber == previousParameterNumber ) then

    arrowValue = curValue - _G.lastValue

    print ("arrowValue =", arrowValue)

  if( arrowValue <0 )
    then 
    print ('arrowValue is a negative number, sending message')
    -- for loop, for 5 bytes max
    arrowValue = (0 - arrowValue)
    
    
    exp1 = 0 
    exp2 = arrowValue

    print (arrowValue, "Total bytes to send")
    for var = exp1,exp2,maxBytes do

  if ( arrowValue < 5 ) then
   print (arrowValue, "Sending remaining bytes")
   sysexMsg = {0x33, 0x00, 0x05, 0x05, 0x05, arrowValue}
   midi.sendSysex(PORT_1, sysexMsg)
   break
   end
   
   print("sending 5 arrowBytes,")
   --we just send 5 bytes so adjust the counter
   arrowValue = arrowValue - 5

--   sysexMsg = {0x33, 0x00, 0x05, 0x05, 0x05, arrowValue}
    -- just sending static 5 bytes
    sysexMsg = {0x33, 0x00, 0x05, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05}
    midi.sendSysex(PORT_1, sysexMsg)
end

    elseif ( arrowValue >0)
     then 
    --print ('arrowValue is a postive number, sending message')

    exp1 = 0 
    exp2 = arrowValue

print (arrowValue, "Total bytes to send")
for var = exp1,exp2,maxBytes do

   if ( arrowValue < 5 ) then
   print (arrowValue, "Sending remaining bytes")
   sysexMsg = {0x33, 0x00, 0x05, 0x07, 0x07, arrowValue}
   midi.sendSysex(PORT_1, sysexMsg)
   break
   end
   
   print("sending 5 arrowBytes,")
   --we just send 5 bytes so adjust the counter
   arrowValue = arrowValue - 5

    -- just sending static 5 bytes
    sysexMsg = {0x33, 0x00, 0x05, 0x07, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05}
    midi.sendSysex(PORT_1, sysexMsg)
    end
     
    else 
     --store last value in global
     --_G.lastValue = curValue
     --print ('arrowValue is equal to zero, do not send anything:')
     timer.disable()
    end
   _G.lastValue = curValue
   
  end
end


1 Like

Overall, it looks like a fine start. One thing I like to do for my sanity check is to put something atthe top of the mainWorker() function to see exactly how many times it is called when I turn a control.
So, use a global counter = 0 and then just after: if ( currentParameterNumber == previousParameterNumber ) then add the lines:

counter = counter + 1
print("Counter: " .. counter)

just to see the raw number of times for each twist of the encoder.

In the two “for” loops, If the initial arrowValue is exactly 5 or a multiple of 5, then you will send a sysex message here with arrowValue = 0.
I do not know what this does to the Mirage or if it is simply just an extra sysex message.
Changing the “if” check to if (arrowValue < 5) and (arrowValue > 0) then
fixes that edge case.

Also – the “break” statement only breaks out of the “if - then”, so it’s not doing exactly what you think. Better to remove the break and change the structure to “if - then - else” so that the arrowValue = arrowvalue - 5, sysexMsg = , and midi.sendSysex() lines are all inside the “else” (also remember to move the “end” statement to the proper place)

That should fix the extra 5 up and down

2 Likes

LUA arrays are sparse and dynamic by nature. So instead of declaring lastValue = nil,
try this: lastValue = {}

then near the top of mainWorker(), you can say:
lastValue[currentParameterNumber] = message:getValue()

and

arrowValue = curValue - lastValue[currentParameterNumber]

and at the bottom,

lastValue[currentParameterNumber] = curValue

1 Like

Thanks a lot, I will definitely implement as you suggest.

How can I combine several lists?
No I think i create a list in a list for the remainingArrowBytes.
This doesn’t work with midi.sendSysex.
I tried al sorts of things and got stuck on this.

–125310 error running function ‘runFunction’: ctrlv2/p000.lua:82: bad argument #-1 to ‘sendSysex’ (number expected, got string)

downArrow=66
negativeArrowValue = 4
endCommand=07

if (negativeArrowValue < 5) and (negativeArrowValue > 0) then
   print (negativeArrowValue, "Sending remaining bytes")
   --remainingArrowBytes { for negativeArrowValue in.... add 1 downArrow } 
   remainingArrowBytes = {}
   for i = 1,negativeArrowValue do table.insert(remainingArrowBytes,tonumber(downArrow)) 
       end

   print(table.concat(remainingArrowBytes,", ")) 
   print("negativeArrowValue here we are, should send less then 5")
   --125310 error running function 'runFunction': ctrlv2/p000.lua:82: bad argument #-1 to 'sendSysex' (number expected, got string)

   --sysexMsg = {15, 1, 1, table.concat(remainingArrowBytes,", "), endCommand }
   sysexMsg = {15, 1, 1, table.concat(remainingArrowBytes,", "), endCommand }
   --midi.sendSysex(PORT_1, sysexMsg)
   print(table.concat(sysexMsg,", "))
   print(sysexMsg[4])
   end

Still have to update the if, elseif, else…
With your suggestions and some updates I did I got:

With is I git the error
–125310 error running function ‘runFunction’: ctrlv2/p000.lua:82: bad argument #-1 to ‘sendSysex’ (number expected, got string)
I think because I pass a list to a list… Any hints on how to do this will be great.

mirageDeviceId = 9
previousParameterNumber = nil
lastValue = {}
maxBytes = 5
upArrow= 14
downArrow = 15
parameterSelect = 12
valueSelect = 13
endCommand = 127
arrowValue = nil
mainWorkerCounterCalls = 0
---try this: lastValue = {}

--then near the top of mainWorker(), you can say:
--lastValue[currentParameterNumber] = message:getValue()
--and
--arrowValue = curValue - lastValue[currentParameterNumber]
--and at the bottom,
--lastValue[currentParameterNumber] = curValue

function mainWorker(ValueObject, Value)

   mainWorkerCounterCalls = mainWorkerCounterCalls + 1
   print("mainWorkerCounterCalls:" .. mainWorkerCounterCalls)

   local message = ValueObject:getMessage()
   local currentParameterNumber = message:getParameterNumber ()
   local curValue = message:getValue ()
   --lastValue[currentParameterNumber] = message:getValue()
--
  if ( currentParameterNumber ~= previousParameterNumber ) then
    
    previousParameterNumber = currentParameterNumber
    --We store the parameter values as well only once per fresh call
    --_G.lastValue = message:getValue ()
    lastValue[currentParameterNumber] = message:getValue()
    -- this always results in  print("nothing to send") ?? FIX
    --arrowValue = curValue - _G.lastValue
    arrowValue = curValue - lastValue[currentParameterNumber]
    print("we have parameterNumber: ", currentParameterNumber)
    print("Lets check the lenght: ", string.len(currentParameterNumber))

  if ( string.len(currentParameterNumber) < 2 ) then
    print("string less the 2 in lenght")
    byte1st = 0
    byte2nd = string.sub(currentParameterNumber,1,1)

    elseif ( string.len(currentParameterNumber) == 2 ) then
    byte1st = string.sub(currentParameterNumber,1,1)
    byte2nd = string.sub(currentParameterNumber,2,2)
    else
        print("[ERROR] string lenght out of range, must be between 0-99)")
  end

   parameterNumberPanel = { (byte1st ), (byte2nd .. ", ") }

   print(parameterNumberPanel)
   print(table.concat(parameterNumberPanel,", "))

     sysexMsg = { 15, 1, 1, parameterSelect, byte1st, byte2nd, valueSelect, endCommand }

      --global, used to send parameter select once, they are not the same so we send a parameter select message 
      --sysexMsg = {headerMsg, parameterSelect, table.concat(parameterNumberPanel,", "), valueSelect, endCommand}
      midi.sendSysex(PORT_1, sysexMsg)
   
  end
--

if ( currentParameterNumber == previousParameterNumber ) then

    --arrowValue = curValue - _G.lastValue
    arrowValue = curValue - lastValue[currentParameterNumber]

    print ("arrowValue =", arrowValue)

  if ( arrowValue <0 ) then 
    print ('arrowValue is a negative number, sending message')
    -- for loop, for 5 bytes max
    negativeArrowValue = (0 - arrowValue)
    --arrowByte = _G.downArrow
    
    exp1 = 0 
    exp2 = negativeArrowValue

    print (negativeArrowValue, "Total down arrow bytes to send")
    for var = exp1,exp2,maxBytes do

  if ( negativeArrowValue < 5 ) and ( negativeArrowValue > 0) then
   print (negativeArrowValue, "Sending remaining bytes")
   --remainingArrowBytes { for negativeArrowValue in.... add 1 downArrow } 
   remainingArrowBytes = { }
   for i = 1,negativeArrowValue do table.insert(remainingArrowBytes,tonumber(downArrow)) end

   print(table.concat(remainingArrowBytes,", ")) 
   print("negativeArrowValue here we are, should send less then 5")
   --125310 error running function 'runFunction': ctrlv2/p000.lua:82: bad argument #-1 to 'sendSysex' (number expected, got string)


   --sysexMsg = {15, 1, 1, table.concat(remainingArrowBytes,", "), endCommand }
   sysexMsg = {15, 1, 1, endCommand }
   midi.sendSysex(PORT_1, sysexMsg)
   break
   end
   
   print("sending 5 arrowBytes,")
   --we just send 5 bytes so adjust the counter
   negativeArrowValue = arrowValue - 5
   print(negativeArrowValue, "negativeArrowValue here we are sending 5")
    --sysexMsg = {0x33, 0x00, 0x05, 0x05, 0x05, arrowValue}
    -- just sending static 5 bytes
    sysexMsg = { 15, 1, 1, downArrow, downArrow, downArrow, downArrow, downArrow, endCommand }
    midi.sendSysex(PORT_1, sysexMsg)
   end

  elseif ( arrowValue >0) then 
    print ('arrowValue is a postive number, sending message')
    --arrowByte = _G.upArrow
    exp1 = 0 
    exp2 = arrowValue

   print (arrowValue, "Total bytes to send")
   for var = exp1,exp2,maxBytes do

   if ( arrowValue < 5 ) and ( arrowValue > 0) then
   print (arrowValue, "Sending remaining bytes")

   --remainingArrowBytes { for negativeArrowValue in.... add 1 downArrow } 
   --tonumber()
   remainingArrowBytes = { }
   for i = 1,arrowValue do table.insert(remainingArrowBytes,tonumber(upArrow)) end

   print(table.concat(remainingArrowBytes,", ")) 
   --389482 error running function 'runFunction': ctrlv2/p000.lua:115: bad argument #-1 to 'sendSysex' (number expected, got string)
   --sysexMsg = {15, 1, 1, table.concat(remainingArrowBytes,", "), endCommand }
   sysexMsg = {15, 1, 1, endCommand }
   midi.sendSysex(PORT_1, sysexMsg)
   break
   end
   
   print("sending 5 arrowBytes,")
   --we just send 5 bytes so adjust the counter
   _G.arrowValue = arrowValue - 5

    --sysexMsg = {0x33, 0x00, 0x05, 0x05, 0x05, arrowValue}
    -- just sending static 5 bytes
    sysexMsg = { 15, 1, 1, upArrow, upArrow, upArrow, upArrow, upArrow, endCommand }
    midi.sendSysex(PORT_1, sysexMsg)
    end
     
    else 
    print("nothing to do")
    end
    --_G.lastValue = curValue
    lastValue[currentParameterNumber] = curValue
   
  end

end
1 Like

I do not have very much time today to look at this, but here is one suggestion. Instead of all the above code, maybe try this:

sysexMsg = {15, 1, 1}
for i = 1, remainingArrowBytes do
   sysexMsg[3+i] = downArrow
end
sysexMsg[4+remainingArrowBytes] = endCommand

This will give you the sysex message bytes dynamically based on the number of down arrows left to send.

Also - I do not understand why you are trying to process the numbers as strings everywhere in the top part of the code.

1 Like