JSyn fails in Android mode on MacOS M4

import com.jsyn.JSyn;

This code will create a gray window on an Android device when run on Windows 11, but will not run in Android mode on MacOS (M4 Tahoe 26.5) and displays this error message:

Exception in thread "Thread-105" java.lang.NullPointerException: Cannot read the array length because "list" is null
at processing.app.Library.wrapFiles(Library.java:429)
at processing.app.Library.getApplicationExports(Library.java:439)
at processing.mode.android.AndroidBuild.copyImportedLibs(AndroidBuild.java:839)
at processing.mode.android.AndroidBuild.createAppModule(AndroidBuild.java:481)
at processing.mode.android.AndroidBuild.createProject(AndroidBuild.java:262)
at processing.mode.android.AndroidBuild.build(AndroidBuild.java:223)
at processing.mode.android.AndroidMode.handleRunDevice(AndroidMode.java:294)
at processing.mode.android.AndroidEditor$17.run(AndroidEditor.java:421)

This is the source code for class JSyn:

package com.jsyn;

import com.jsyn.devices.AudioDeviceManager;
import com.jsyn.engine.SynthesisEngine;
import java.sql.Date;
import java.util.GregorianCalendar;

public class JSyn {
    private static final int VERSION_MAJOR = 17;
    private static final int VERSION_MINOR = 1;
    private static final int VERSION_REVISION = 0;
    public static final int BUILD_NUMBER = 465;
    private static final long BUILD_TIME = (new GregorianCalendar(2023, 3, 10)).getTime().getTime();
    public static final String VERSION = "17.1.0";
    public static final int VERSION_CODE = 1114368;
    public static final String VERSION_TEXT;

    public static Synthesizer createSynthesizer() {
        return new SynthesisEngine();
    }

    public static Synthesizer createSynthesizer(AudioDeviceManager audioDeviceManager) {
        return new SynthesisEngine(audioDeviceManager);
    }

    static {
        VERSION_TEXT = "V17.1.0 (build 465, " + new Date(BUILD_TIME) + ")";
    }
}

The first thing it does is import com.jsyn.devices.AudioDeviceManager; which is likely where the real problem lies.

This is the source code for class JSynAndroidAudioDeviceManager:

package processing.sound;

import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.AudioTrack;
import com.jsyn.devices.AudioDeviceInputStream;
import com.jsyn.devices.AudioDeviceManager;
import com.jsyn.devices.AudioDeviceOutputStream;

class JSynAndroidAudioDeviceManager implements AudioDeviceManager {
    private static String[] ANDROIDDEVICENAMES = new String[]{"default", "mic", "voice uplink", "voice downlink", "voice call", "camcorder", "voice recognition", "voice communication", "remote submix", "unprocessed", "voice performance"};
    private double suggestedOutputLatency = 0.1;
    private double suggestedInputLatency = 0.1;

    public String getName() {
        return "JSyn Android Audio for Processing Sound";
    }

    public AudioDeviceOutputStream createOutputStream(int var1, int var2, int var3) {
        return new AndroidAudioOutputStream(var1, var2, var3);
    }

    public AudioDeviceInputStream createInputStream(int var1, int var2, int var3) {
        return new AndroidAudioInputStream(var1, var2, var3);
    }

    public double getDefaultHighInputLatency(int var1) {
        return 0.3;
    }

    public double getDefaultHighOutputLatency(int var1) {
        return 0.3;
    }

    public int getDefaultInputDeviceID() {
        return 0;
    }

    public int getDefaultOutputDeviceID() {
        return 0;
    }

    public double getDefaultLowInputLatency(int var1) {
        return 0.1;
    }

    public double getDefaultLowOutputLatency(int var1) {
        return 0.1;
    }

    public int getDeviceCount() {
        return ANDROIDDEVICENAMES.length;
    }

    public String getDeviceName(int var1) {
        return "Android " + ANDROIDDEVICENAMES[var1] + " input";
    }

    public int getMaxInputChannels(int var1) {
        return 1;
    }

    public int getMaxOutputChannels(int var1) {
        return 2;
    }

    public int setSuggestedOutputLatency(double var1) {
        this.suggestedOutputLatency = var1;
        return 0;
    }

    public int setSuggestedInputLatency(double var1) {
        this.suggestedInputLatency = var1;
        return 0;
    }

    private class AndroidAudioInputStream extends AndroidAudioStream implements AudioDeviceInputStream {
        private AudioRecord audioRecord;

        public AndroidAudioInputStream(int var2, int var3, int var4) {
            super(var2, var3, var4);
        }

        public void start() {
            this.minBufferSize = AudioRecord.getMinBufferSize(this.frameRate, 12, 2);
            this.bufferSize = 3 * (this.minBufferSize / 2) & -4;

            try {
                this.audioRecord = (new AudioRecord.Builder()).setAudioSource(this.deviceID).setAudioFormat((new AudioFormat.Builder()).setChannelMask(16).setEncoding(2).setSampleRate(this.frameRate).build()).setBufferSizeInBytes(this.bufferSize).build();
                this.audioRecord.startRecording();
            } catch (UnsupportedOperationException var2) {
            }

        }

        public double read() {
            double[] var1 = new double[1];
            this.read(var1, 0, 1);
            return var1[0];
        }

        public int read(double[] var1) {
            return this.read(var1, 0, var1.length);
        }

        public int read(double[] var1, int var2, int var3) {
            if (this.audioRecord == null) {
                return 0;
            } else {
                if (this.shortBuffer == null || this.shortBuffer.length < var3) {
                    this.shortBuffer = new short[var3];
                }

                int var4 = this.audioRecord.read(this.shortBuffer, 0, var3, 1);
                if (var4 < 0) {
                    switch (var4) {
                        case -6:
                            throw new RuntimeException("AudioRecord ERROR_DEAD_OBJECT: Object must be recreated");
                        case -5:
                        case -4:
                        default:
                            break;
                        case -3:
                            throw new RuntimeException("AudioRecord ERROR_INVALID_OPERATION: Device not properly initialized");
                        case -2:
                            throw new RuntimeException("AudioRecord ERROR_BAD_VALUE: Paramters don't resolve to valid data and indices");
                        case -1:
                            throw new RuntimeException("AudioRecord ERROR: Unknown error");
                    }
                }

                for(int var5 = 0; var5 < var4; ++var5) {
                    var1[var5 + var2] = (double)this.shortBuffer[var5] / (double)32767.0F;
                }

                return var4;
            }
        }

        public void stop() {
            if (this.audioRecord != null) {
                this.audioRecord.stop();
                this.audioRecord.release();
            }

        }

        public int available() {
            return this.bufferSize;
        }

        public void close() {
        }
    }

    private class AndroidAudioOutputStream extends AndroidAudioStream implements AudioDeviceOutputStream {
        AudioTrack audioTrack;

        public AndroidAudioOutputStream(int var2, int var3, int var4) {
            super(var2, var3, var4);
        }

        public void start() {
            this.minBufferSize = AudioTrack.getMinBufferSize(this.frameRate, 12, 2);
            this.bufferSize = 3 * (this.minBufferSize / 2) & -4;
            this.audioTrack = (new AudioTrack.Builder()).setAudioAttributes((new AudioAttributes.Builder()).setUsage(1).setContentType(2).build()).setAudioFormat((new AudioFormat.Builder()).setChannelMask(12).setEncoding(2).setSampleRate(this.frameRate).build()).setBufferSizeInBytes(this.bufferSize).setTransferMode(1).build();
            this.audioTrack.play();
        }

        public void write(double var1) {
            double[] var3 = new double[]{var1};
            this.write(var3, 0, 1);
        }

        public void write(double[] var1) {
            this.write(var1, 0, var1.length);
        }

        public void write(double[] var1, int var2, int var3) {
            if (this.shortBuffer == null || this.shortBuffer.length < var3) {
                this.shortBuffer = new short[var3];
            }

            for(int var4 = 0; var4 < var3; ++var4) {
                int var5 = (int)((double)32767.0F * var1[var4 + var2]);
                if (var5 > 32767) {
                    var5 = 32767;
                } else if (var5 < -32768) {
                    var5 = -32768;
                }

                this.shortBuffer[var4] = (short)var5;
            }

            this.audioTrack.write(this.shortBuffer, 0, var3);
        }

        public void stop() {
            if (this.audioTrack != null) {
                this.audioTrack.stop();
                this.audioTrack.release();
            }

        }

        public void close() {
        }
    }

    private class AndroidAudioStream {
        protected short[] shortBuffer;
        protected int frameRate;
        protected int samplesPerFrame;
        protected int minBufferSize;
        protected int bufferSize;
        protected int deviceID;

        public AndroidAudioStream(int var2, int var3, int var4) {
            this.deviceID = var2;
            this.frameRate = var3;
            this.samplesPerFrame = var4;
        }

        public double getLatency() {
            int var1 = this.bufferSize / this.samplesPerFrame;
            return (double)var1 / (double)this.frameRate;
        }
    }
}

I’m not sure which ‘list’ is causing the problem.

Hello,

Sharing my exploration of this…

I test issues as follows:

  • A dedicated sketchbook folder and one just for Android
  • No libraries initially.
  • Add libraries as required.
  • Restart Processing if changes are made to the libraries.
  • Delete the app on the Android for each new build; this may not be necessary but I have been doing this to test with a clean slate.

Results for W10 below…

No libraries:

Processing Sound library 2.4.0:


Processing Sound library 2.4.0 (modified) with ONLY these files:


Processing Sound library 2.4.0 with ONLY these files:


The above was tested with:

  • W10
  • Processing 4.6.5
  • Android Mode 4.6.1

The sound libraries are here for further exploration:

I have been able to successfully use these sound libraries in Android Mode:
processing-sound v2.4.0 using only the sound-android.zip and removing the jportaudio.jar
processing-sound v2.3.1
processing-sound v2.4.0 with modifications (removed the natives along with other jars).

Reference:

Useful for troubleshooting:

Have fun!

:)

It works ‘as is’ with Windows 11; no need to do any of this.

I have a new W11 PC I need to start migrating over to.
This has given me a reason to move forward on this.

I will report back once I have tested this.

:)

Hello @svan,

I get the same results for W10 and W11.
I have a working sound library for Android mode with the previous steps I took.

What is ‘as is’? Please share so I can try this.

Can you show the contents of the “library folder” that is inside the sound library that you are using?

Thanks in advance!

:)

  1. Create an android mode Processing app with an android device attached.
  2. Copy/paste ‘import com.syn.JSyn;’ to the first line of code.
  3. Hit ‘run’ and be patient.
  4. The screen of your android device will turn to a gray color and the console of your editor will indicate that it was ‘successful’.
  5. No tricks are necessary.

Your library folder is the only missing piece of the puzzle for me to compare.

import com.jsyn.JSyn; requires the Sound library.

Unless you have other libraries that have this.
I have a pristine W11 environment and Processing install so there are no other libraries.

I started with a fresh install of the Processing Sound library and repeated my procedure from before.

It did not work until I removed many of the library files that caused the errors.

Final working library folder:

Removed:

Here’s a screenshot of my sound library folder:

Based on the sound library structure of an old Windows 11 box, this fixes my mac (at least for this demo):

This demo now runs without error.

Addendum:

I went back to the Windows 11 box and updated my sound library and it broke the code; it also returned the same error message as seen on the mac shown above. I reinstalled the old sound library, which I had saved, and the code again ran without error after deliberately breaking it.

Hopefully the maintainers can use this information to figure out why the newer version of the sound library fails in some areas. Thanks to @glv for putting us on the right track.