B4J Question Array memory lapsus

moster67

Expert
Licensed User
Longtime User
I have a memory lapsus:

I have this declaration:
B4X:
Dim totalSamplesPerChannel() As Int = Array As Int(channelcount)

then within a loop I need to increment it
In java it is:
B4X:
totalSamplesPerChannel[ch]++;
and in VB.Net it would be (I think):
B4X:
totalSamplesPerChannel(ch) += 1

ch is an int

Can someone help me to show in B4X how to write:
B4X:
totalSamplesPerChannel[ch]++;

Thanks.
 

DonManfred

Expert
Licensed User
Longtime User
you can not redim.
You can create a new array and replace the global var.
 
Upvote 0

moster67

Expert
Licensed User
Longtime User
Well, this is the important part of the java-code which I am trying to translate into B4X(B4J):
B4X:
// Generate 10 seconds of 4 different sine wave frequencies
        final float c6NoteFreq = 1046.50f;
        final float e6NoteFreq = 1318.51f;
        final float g6NoteFreq = 1567.98f;
        final float c7NoteFreq = 2093.00f;
        final float[] frequencyPerChannel = {c6NoteFreq, e6NoteFreq, g6NoteFreq, c7NoteFreq};
        // Thirty seconds of samples
        final int totalSamplesToSend = sampleRate * 30;
        int[] totalSamplesPerChannel = new int[channelCount];

        // Fill each buffer and send it until totalSamplesToSend is reached
        for(int i = 0; i < totalSamplesToSend; i += sampleCount) {
            data.position(0);
            for (int sample = 0; sample < sampleCount; sample++) {
                for (int ch = 0; ch < channelCount; ch++) {
                    short val = (short) (Math.sin(totalSamplesPerChannel[ch] * Math.PI * frequencyPerChannel[ch] * (1f / sampleRate)) * Short.MAX_VALUE);
                    data.putShort(val);
                    totalSamplesPerChannel[ch]++;
                }
            }
            data.flip();

            sender.sendAudioFrameInterleaved16s(audioFrame);
        }

At least for me, it is a rather complicated loop :)
This is my corresponding B4X code which I probably have written completely wrong:

B4X:
    'Generate 10 seconds of 4 different sine wave frequencies
    Dim c6NoteFreq As Float = 1046.50
    Dim e6NoteFreq As Float = 1318.51
    Dim g6NoteFreq As Float = 1567.98
    Dim c7NoteFreq As Float = 2093.00
    Dim frequencyPerChannel() As Float = Array As Float(c6NoteFreq, e6NoteFreq, g6NoteFreq, c7NoteFreq)
    '30 seconds of samples
    Dim totalSamplesToSend As Int = samplerate * 30
    Dim totalSamplesPerChannel() As Int = Array As Int(channelcount)
   

    For i = 0 To totalSamplesToSend - 1 Step samplecount
        data.Position = 0
        Dim sample As Int
        For sample  = 0 To samplecount - 1
            Dim ch As Int
            For ch = 0 To channelcount - 1
                Dim val As Short
                Dim mydblval As Double
                Dim one As Float = 1
                mydblval =(Sin(totalSamplesPerChannel(ch) * cPI * frequencyPerChannel(ch) * (one/samplerate)) * 32767)
                val = mydblval 'casting
                data.putShort(val)
                totalSamplesPerChannel(ch) = totalSamplesPerChannel(ch) + 1
            Next
        Next
        data.flip()
   
        TheSender.sendAudioFrameInterleaved16s(audioframe)
    Next

Some of the objects are from a wrapper of NDI I am writing but now I am testing it in B4J.

When the most inner loop executes for the 2nd time the "mydblval = ...." statement, it generates a "java.lang.ArrayIndexOutOfBoundsException" error.

Any ideas?
Thanks.
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
This line:
B4X:
Dim totalSamplesPerChannel() As Int = Array As Int(channelcount)

Is creating a one element array that holds the value of channelcount.

What you probably meant to do was create an array the size of channelcount which would be:

B4X:
Dim totalSamplesPerChannel(channelcount) As Int
 
Upvote 0

moster67

Expert
Licensed User
Longtime User
@stevel05
Thank you! Now, the loop runs fine. I will check better the complete code later this evening.

Many thanks also to @LucaMs for his help. I had actually written as you suggested but always got an error and somehow I was convinced that was the error.
 
Upvote 0

moster67

Expert
Licensed User
Longtime User
That depends how I write the underlying wrapper (which in this case is a part of ByteBuffer)

If in the Wrapper I write it as :
B4X:
public void setPosition(int NewPosition){
        byteBuffer.position(NewPosition);
    }

then in the B4X IDE I need to write:
B4X:
data.Position = 0

if instead in the wrapper I write it as:
B4X:
public void SetPosition(int NewPosition){
        byteBuffer.position(NewPosition);
    }
then I need in B4X I need to write:
B4X:
data.SetPosition(0)

The B4X IDE distinguishes between "setPosition" and "SetPosition".
If I recall correctly, it has to do something with how the IDE handles getter and setters. If you start your getters and setters method with a lowercase letter, then the IDE will show the method/property just as X.Position and/or read/write properties. If you use a Capital letter, then you will see them as separate methods.

I am not 100% sure but I think that is the way it works "under the hood".
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
There's a fair bit of redundant stuff in there. Where are channelcount and samplecount defined? Are they constant, or incoming parameters? And is the audio actually meant to be four channels, each being a single sinewave? The "interleaved" feels like the audio could be far-more-common stereo ie two channels.

Also, whilst you've done a beautiful translation of the 1.0f use into B4X, you could go one step further in that:

... frequencyPerChannel(ch) * (one/samplerate) ...

can be replaced by:

... frequencyPerChannel(ch) / samplerate ...

ie no need for Float one. Sorry :) I suspect that the reason for the original style was to force optimization of replacing the division by multiplication of a constant reciprocal.

Which you can still do, if you really, truly need to save cycles. Add "Dim DivideBySampleRate As Float = 1 / samplerate" before the loop (so it only gets done once) and then replace the "/ samplerate" with "* DivideBySampleRate"
 
Upvote 0

emexes

Expert
Licensed User
If in the Wrapper I write it as :
B4X:
public void setPosition(int NewPosition){
    byteBuffer.position(NewPosition);
}

if instead in the wrapper I write it as:
B4X:
public void SetPosition(int NewPosition){
    byteBuffer.position(NewPosition);
}
Initially I thought you'd copied the same code twice by mistake. Took me a while to spot the difference. This is a good example of a downside of case-sensitive languages. Thank you for being gentle in pointing out my blindness ;-)
 
Upvote 0

emexes

Expert
Licensed User
It looks like samplecount is the number of samples to send with each call to TheSender.sendAudioFrameInterleaved16s(audioframe)

So 30 seconds of audio at cd rate would be 1.3 million samples per channel.

But rather than generate and send the entire 10.4 million bytes all at once, you are sending them in blocks of say samplecount = 256 which would result in sending 2048 bytes at a time.

Have I got that right?

And what happens if samplecount is not an integral fraction of totalSamplesToSend?
 
Upvote 0

moster67

Expert
Licensed User
Longtime User
Haha - I love reading your posts.
I really admire your knowledge and I feel even more of an amateur than I am (as a matter of fact, I am an accountant :)). I always learn something from your post and they make "a good reading".
Earlier today, I decided to follow you here on the forum. That is to show my appreciation for your posts.

Basically the code I translated was from a java example of an NDI wrapper I am writing. See this thread from @bdunkleysmith. You can find the sample code on GitHub here.
So as you say, the code could perhaps be optimized in B4X (and in Java). The reason for me doing it as outlined was just to simply test that the wrapper itself was working. Once that is done, I guess any code to use it, should be optimized as much as possible when you use the NDI framework. And by the way, the code sample I translated into B4X runs fine after @stevel05 correction.

So for now, I am fine.
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
Anyway, the two easy simplifications o_O are:

1/ For loop variables are Ints by default, so you can remove the Dim sample and Dim ch lines (or add a Dim i line for consistency ;-)

2/ get rid of the multipy-by-one, and thus also the Dim one line

and the bonus round is:

3/ totalSamplesPerChannel doesn't have to be an Array; a simple Int should suffice, if you move the increment down one line to outside the For..Next loop
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
Was taken as a friendly jibe anyway :)

Plus always good to remember that generalizations usually have exceptions. Don't be restricted by dogma. What works, works.

Even GOTO can sometimes be the best way to do something... as long as you can handle the disapproval from those who don't actually have to get things done!
 
Upvote 0
Top