16

Trying to run MIDI on my Android app. I'm following the midisuite example to configure my app and it works fine with the exception of aftertouch. Whenever I try to trigger aftertouch, I run into a threading exception type InteruptedException. How should I prevent this threading issue? My knowledge on multithreading isn't the best or else I would've figured this out already. All I can really tell right now is that the message is sending too fast and the thread hasn't woken up yet from its sleep call.

I followed the github repo with my code as follows:

MidiReceiver subclass:

@TargetApi(Build.VERSION_CODES.M)
public class MidiEngine extends MidiReceiver {

    public  AudioActivity activity;
    private MidiEventScheduler eventScheduler;
    private MidiFramer midiFramer;
    private MidiReceiver midiReceiver = new MyReceiver();

    private Thread mThread;
    private boolean go;
    private int mProgram;

    public MidiEngine() {
        this(new AudioActivity());
    }

    public MidiEngine(AudioActivity activity) {
        this.activity = activity;
        midiReceiver = new MyReceiver();
        midiFramer = new MidiFramer(midiReceiver);
    }

    public AudioActivity getActivity() {
        return this.activity;
    }

    /* This will be called when MIDI data arrives. */
    @Override
    public void onSend(byte[] data, int offset, int count, long timestamp)
            throws IOException {
        if (eventScheduler != null) {
            if (!MidiConstants.isAllActiveSensing(data, offset, count)) {
                eventScheduler.getReceiver().send(data, offset, count,
                        timestamp);
            }
        }
    }

    // Custom Listener to send to correct methods
    private class MyReceiver extends MidiReceiver {
        @Override
        public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
            byte command    = (byte)(msg[0] & MidiConstants.STATUS_COMMAND_MASK);
            int channel     = (byte)(msg[0] & MidiConstants.STATUS_CHANNEL_MASK);

            switch (command) {
                case MidiConstants.STATUS_NOTE_ON:
                    activity.keyDown(i, msg[1], msg[2]);
                    break;

                case MidiConstants.STATUS_NOTE_OFF:
                    activity.keyUp(channel, msg[1]);
                    break;

                case MidiConstants.STATUS_POLYPHONIC_AFTERTOUCH:
                    activity.keyDown(channel, msg[1], msg[2]);
                    break;

                case MidiConstants.STATUS_PITCH_BEND:
                    activity.pitchBendAction(channel, (msg[2] << 7) + msg[1]);
                    break;

                case MidiConstants.STATUS_CONTROL_CHANGE:
                    activity.ccAction(channel, msg[1], msg[2]);
                    break;

                case MidiConstants.STATUS_PROGRAM_CHANGE:
                    mProgram = msg[1];
                    break;

                default:
                    break;
            }
        }
    }

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            do {
                try {
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                processMidiEvents();
                            }
                            catch (Exception e) {
                                Log.e("Java", "SynthEngine background thread exception.", e);
                            }
                        }
                    });
                    Thread.sleep(100);
                }
                catch (InterruptedException e) {
                    Log.e("Java", "Threading exception", e);
                }
            }
            while (go);
        }
    }

    /**
     * @throws IOException
     *
     */
    private void processMidiEvents() throws IOException {
        long now = System.nanoTime();
        MidiEventScheduler.MidiEvent event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
        while (event != null) {
            midiFramer.send(event.data, 0, event.count, event.getTimestamp());
            eventScheduler.addEventToPool(event);
            event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
        }
    }

    public void start() {
        stop();
        go = true;
        mThread = new Thread(new MyRunnable());
        mThread.setPriority(6);
        eventScheduler = new MidiEventScheduler();
        mThread.start();
    }

    public void stop() {
        go = false;
        if (mThread != null) {
            try {
                mThread.interrupt();
                mThread.join(500);
            }
            catch (Exception e) {

            }
            mThread = null;
            eventScheduler = null;
        }
    }

}

Stack Trace Error (line 154 refers to the Thread.sleep part in my custom Runnable class):

Java: Threading exception
      java.lang.InterruptedException
          at java.lang.Thread.sleep(Native Method)
          at java.lang.Thread.sleep(Thread.java:1031)
          at java.lang.Thread.sleep(Thread.java:985)
          at com.rfoo.midiapp.communication.MidiEngineInput$MyRunnable.run(MidiEngineInput.java:154)
                                                                     at java.lang.Thread.run(Thread.java:818)

Thanks!

EDIT: Thread start

Midi Device Service subclass (thread will start whenever a device has connected or disconnected).

@TargetApi(Build.VERSION_CODES.M)
public class MidiSynthDeviceService extends MidiDeviceService {
    private static final String TAG = "MidiSynthDeviceService";
    private boolean midiStarted = false;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        AudioActivity.midiEngine.stop();
        super.onDestroy();
    }

    @Override
    // Declare the receivers associated with your input ports.
    public MidiReceiver[] onGetInputPortReceivers() {
        return new MidiReceiver[] { AudioActivity.midiEngine };
    }

    /**
     * This will get called when clients connect or disconnect.
     * You can use it to turn on your synth only when needed.
     */
    @Override
    public void onDeviceStatusChanged(MidiDeviceStatus status) {

        if (status.isInputPortOpen(0) && !midiStarted) {
            AudioActivity.midiEngine.start();
            midiStarted = true;
        } else if (!status.isInputPortOpen(0) && midiStarted){
            AudioActivity.midiEngine.stop();
            midiStarted = false;
        }
    }
}

Activity class:

public class AudioActivity extends AppCompatActivity {
    private Thread thread;
    public static MidiEngine midiEngine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Layout inits
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // Setup MIDI:
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
            Toast.makeText(this, "MIDI not supported!", Toast.LENGTH_LONG).show();
        }
        else {
            midiEngine = new MidiEngine(this);
            setupMidi();
        }

        // Setup audio thread:
        if (thread == null) {
            thread = new Thread() {
                public void run() {
                    setPriority(Thread.MAX_PRIORITY);
                    // Runs an Open SL audio thread (C++)
                    // This generates a waveform. 
                    // AudioEngine is a wrapper class connecting C++ to Java
                    AudioEngine.runProcess();
                }
            }
        }
    }

    public void setupMidi() {
        if (activity == null) activity = (AudioActivity) getContext();

        mMidiManager = (MidiManager) activity.getSystemService(AudioActivity.MIDI_SERVICE);
        if (mMidiManager == null) {
            Toast.makeText(activity, "MidiManager is null!", Toast.LENGTH_LONG).show();
            return;
        }
        // Get Device Info
        MidiDeviceInfo deviceInfo = MidiTools.findDevice(mMidiManager, "RFOO", "AudioApp");

        // MIDI Input
        portIndex = 0;
        inputPortSelector = new MidiOutputPortConnectionSelector(mMidiManager, activity, R.id
                .inputListView, deviceInfo, portIndex);
        inputPortSelector.setConnectedListener(new MyPortsConnectedListener());

        midi_ch_input = 0;
        midi_ch_output = 0;
    }

    // Bunch of UI code here....
}
yun
  • 1,243
  • 11
  • 30
  • Please, provide more information where `MyRunnable` is called. There its thread is started, cancelled – Vyacheslav Sep 06 '16 at 13:47
  • @Vyacheslav Certainly - added it just now – yun Sep 06 '16 at 13:54
  • So, how do you start `AudioActivity`? I just can see you have changed code of the example project. – Vyacheslav Sep 06 '16 at 13:59
  • What is `AudioActivity.midiEngine`? – Vyacheslav Sep 06 '16 at 14:00
  • @Vyacheslav `AudioActivity` is just the Main Activity for the Android application. The `midiEngine` variable is a `MidiReceiver` subclass (same as from the example; almost if not all the MIDI stuff is taken from the midisuite example). `midiEngine` is a static variable owned by `AudioActivity`. I'll post that class as well if needed – yun Sep 06 '16 at 14:01
  • Of course post. The problem is about your thread is interrupted by somewhere. But you do not show how your code actually works. – Vyacheslav Sep 06 '16 at 14:05
  • @Vyacheslav There's a lot of code and I only wanted to post parts of the code when needed and I wasn't sure which parts were related quite yet. I do have a thread running for my audio generation code, so in total I guess there are 3 threads (audio, midi, and UI). – yun Sep 06 '16 at 14:06

1 Answers1

0

This is realy old post but I will repond anyway:

While I debug my onSend of MidiReceiver that is used in my MidiDeviceService defined in mainfest I can see clearly that it is running on some thread, no UI thread. S when I see this code I can clearly see multiple related issues.

I check thread by :

val Thread.isMain get() = Looper.getMainLooper().thread == this

or

val Thread.isMain2 get() = Looper.myLooper() == Looper.getMainLooper()

What gives me that same result. I could not find this anywhere in documentation what is crazy, but my debugging gives me clear answer. I cannot see any synchronization or anything in this code so as I dont see code of MidiEventScheduler eather and this is not part of android api is that like thread safe?

I doubt so.

Renetik
  • 5,887
  • 1
  • 47
  • 66