B4J Question Convert an array of Bytes to an array of Ints

max123

Well-Known Member
Licensed User
Longtime User
Hi guys,

I've a code where MIDI input come from serial port and the received array of bytes need to be converted to an array of integers to be routed on internal system Midi loopback.

I do not like my piece of code I wrote, even it should execute as fast possible, it is important because thit is a Serial-Midi bridge where Midi latency is very important to be as small possible.
Here my code, is there a better way to do it ?
Thanks

B4X:
Sub AStream_NewData (Buffer() As Byte)
    If Midi.attachedOutputs.Length > 0 Then SendMidiMessage(Buffer)
End Sub

Sub SendMidiMessage(Buffer() As Byte)  ' <<< RECEIVE AN ARRAY OF BYTES
    Dim debug As Boolean = ckDebug.Checked
  
    If debug Then
        Dim sb As StringBuilder
        sb.Initialize
    End If
  
    Dim bf(Buffer.Length) As Int
  
    For i = 0 To bf.Length -1
        bf(i) = Buffer(i)
        If debug Then
            If bf(i) >= 0 Then
                sb.Append(bf(i))
            Else
                sb.Append(bf(i)+256)
            End If
            If i < bf.Length -1 Then sb.Append(", ")
        End If  
    Next
  
    If debug Then LogMessage("Send Midi Message: " & bf.Length & " Bytes [" & sb.ToString & "]")
    Midi.SendMessage(bf)  ' <<< SEND AN ARRAY OF INTEGERS
End Sub
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Yes sorry but here is my english not good.
Yes Processing MidiBus library is already prepared because java use it this way, the javadocs explain it and a cast applied.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
So I've used this as @OliverA advice
B4X:
    Dim bc As ByteConverter
    Midi.SendMessage(bc.IntsFromBytes(Buffer))  ' <<< SEND AN ARRAY OF INTEGERS
now I will try if works. Thanks Oliver
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
int[] dataint = new int[data.length]; for(int i = 0; i < data.length; i++) dataint = (int)(data & 0xFF);
This code will convert each individual byte array element to an integer array element. ByteConverter will convert two byte array elements into one integer array element.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Unless I'm overlooking something, that code does not convert the byte array to an int array. What it does is take an individual byte from the array and convert it to int. Those are two totally different operations. What you are looking for is just
B4X:
Value as int = bit.and(data(0), 0xff)
But even that is unnecessary if you just code your NOTE_OFF, NOTE_ON and CONTROL_CHANGE as a byte value (I doubt they are larger than byte values, else the Java code would not work)
Yes Oliver there are larger than signed byte, these can fit on a signed byte but on B4X side I prefer have as integers.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I mixed both advices but cannot get it to work, what is wrong here guys ? bf always is zero bytes....
B4X:
Sub SendMidiMessage(Buffer() As Byte)  ' <<< RECEIVE AN ARRAY OF BYTES
    If Buffer.Length > 0 Then
        Dim bc As ByteConverter
        Dim bf(Buffer.Length) As Int
        bf = bc.IntsFromBytes(Buffer)
  
        Log("Buffer() len: " & Buffer.Length)
        Log("bf len: " & bf.Length)
      
        If ckDebug.Checked Then
            Log("Send Midi Message: " & bf.Length & " Bytes [" & IntsToString(bf, ", ") & "]")
'        LogMessage("Send Midi Message: " & bf.Length & " Bytes [" & IntsToString(bf, ", ") & "]")
        End If
      
        Midi.SendMessage(bf)  ' <<< SEND AN ARRAY OF INTEGERS
'    Midi.SendMessage(bc.IntsFromBytes(Buffer))  ' <<< OR JUST THIS BUT WITHOUT DEBUG
    Else
        Log("LENGTH ZERO")
    End If
End Sub

Sub IntsToString(Ints() As Int, Separator As String) As String
    Dim sb As StringBuilder
    sb.Initialize

    If Ints.Length <> 0 Then
        sb.Append(Ints(0))
        For i = 1 To Ints.Length - 1
            sb.Append(Separator)
            sb.Append(Ints(i))
        Next
    End If
  
    Return sb.ToString
End Sub
and this is the log, error happen on line Midi.SendMessage(bf) because bf always is zero bytes, but Buffer is 3 bytes:
Waiting for debugger to connect...
Program started.

Available MIDI Devices:

---------- INPUTS ----------
[0] "Real Time Sequencer"
[1] "loopMIDI Port 1"
[2] "loopMIDI Port 2"
[3] "loopMIDI Port 3"
[4] "loopMIDI Port 4"
----------------------------

--------- OUTPUTS ---------
[0] "Gervill"
[1] "Real Time Sequencer"
[2] "Microsoft MIDI Mapper"
[3] "Microsoft GS Wavetable Synth"
[4] "loopMIDI Port 1"
[5] "loopMIDI Port 2"
[6] "loopMIDI Port 3"
[7] "loopMIDI Port 4"
---------------------------

MidiBus: ClearInputs
MidiBus: Add Input device: "loopMIDI Port 1"
MidiBus: ClearOutputs
MidiBus: Add Output device: "loopMIDI Port 2"
Buffer() len: 3 <<< real app log start from here
bf len: 0
Send Midi Message: 0 Bytes []
Error occurred on line: 311 (Main)
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at b4j.midibus.maxer73bis.MidiBus.SendMessage(MidiBus.java:1053)
at b4j.example.main._sendmidimessage(main.java:444)
at b4j.example.main._astream_newdata(main.java:403)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:629)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:234)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:100)
at anywheresoftware.b4a.BA$2.run(BA.java:250)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:834)
Error occurred on line: 378 (Main)
java.lang.NullPointerException
at anywheresoftware.b4a.shell.Shell.runGoodChain(Shell.java:370)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:181)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:100)
at anywheresoftware.b4a.BA$2.run(BA.java:250)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:834)
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Buffer() len: 3

For IntsFromBytes to work, the input length must be a multiple of two
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
For IntsFromBytes to work, the input length must be a multiple of two
Mmmmmm. I'm not sure that ByteConverter is a solution here..... I don't need to package 2 bytes to an integer, I just need to convert an array of bytes in an array of ints with same value.

So instead of have a byte array have an int array with same number of elements.
So in this case the final result should have an int array of 3 elements filled from Buffer().

I suppose I'm completely in wrong direction here.
 
Last edited:
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Change
B4X:
        Dim bc As ByteConverter
        Dim bf(Buffer.Length) As Int
        bf = bc.IntsFromBytes(Buffer)
  
        Log("Buffer() len: " & Buffer.Length)
        Log("bf len: " & bf.Length)
To
B4X:
        Dim bf(Buffer.Length) As Int
        for x = 0 to Buffer.Length - 1
               bf(x) = Bit.And(Buffer(x), 0xFF)
        next
  
        Log("Buffer() len: " & Buffer.Length)
        Log("bf len: " & bf.Length)

It's pretty much the same way the Java code does it.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Yes this code works as original, bu I want to avoid to declare the Int array every message will arrive, eve avoid to put it as global variable.

Is there a better way to cast a byte array to an int array with same size ?

I Tried with AS keyword to cast as Integer, but this don't work with arrays, need to iterate the array and declare bf.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Not in Java
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Byte converter won't do what you want. Why do you want to avoid declaring an int array every time? It seems you may be suffering from premature optimisation. Have you tried the straightforward iteration from a byte to an int array and does it cause latency problems in practice in release mode? Note that debug mode will cripple the performance.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Not in Java
Not 100% true. @Magma published a B4J implementation of L4Z based on a GirHub Java project implementing L4Z. That project has an option to use Java Unsafe API for some memory manipulation. This API may make it possible to "cast" a byte array to an int at the cost of Java's safety net. But again, this would convert 2 bytes to an int. Not what you are doing. As @agraham wonders, why worry? My question: why even convert it to an int array? All you are doing is doubling the memory footprint of the data.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Thanks to both,

@agraham , :D maybe yes, it is possible I suffer of a Optimization virus, this is because I even develop for microcontrollers where optimization do the difference, I know that computers are fast now but I will always search to optimize, expecially critacal part of code that need to execute 500, 1000 and probably more times every second.

@OliverA , I need to convert it because Midi.SendMessage(message) only accept integer array. Please see screenshot.
 

Attachments

  • Screen Shot 12-27-22 at 07.32 PM.PNG
    Screen Shot 12-27-22 at 07.32 PM.PNG
    54.1 KB · Views: 99
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Midi.SendMessage(message) only accept integer array.
Then you are stuck converting either with a for loop in Java or a For loop in B4X. Casting alone won't do it, since you are changing the size of each array element and keeping the number of elements the same. Either loop should be fast enough.
Edit: fixing an auto correct
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Thanks, if there is no other way I will do it that way for sure.
By declare an Int array and fill it by iterating in a loop.
 
Upvote 0
Top