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.

:)