Miraj Shah
Miraj Shah

Reputation: 1

Android Studio App crash when running a thread

I would like to start off with the disclaimer that I am very very new to Android Studio and Java Script, and programming isn't even my forte. However, I am trying to develop an audio synthizer application, and while I can get everything to work, I cannot get the audio to stream. Essentially, I can use the AudioTrack in static mode to play calculated sound waveforms for up to 10 seconds, however, I cannot dynamically play multiple key presses (I have set up a 2 Octave 25 key keyboard on the app), and I cannot hold down a key for a long time either. I have a length slider that adjusts the duration of the note being played from 0.1 to 10 seconds. I would like to be able to press multiple keys at once, as well as hold down a key for an extended period of time and for the app to continuously output sound. I did some research, and it seems that the best way of achieving this is using the "Streaming" mode of the AudioTrack library, and to use threads? I am very unfamiliar with both the library, threads, and even the language.

I was following the audio streaming tutorial provided here, except I was trying out a slightly different approach which I believe would work better in my case. I have posted my test code below (note that this is not all of my code to play different sounds and such, but in stead only the bare minimum with only one key setup (c4), and I am trying to get it to continuously play a sound. When I first hit the key, it plays the sound for as long as I keep holding down the key. However, after I release the key, and press it again, the application just crashes. I am not quite sure why this is happening. I tried different things such as calling the "audioThread.start()" function only once, or trying to stop the thread, but I was unsuccessful. If I called the "audioThread.start()" function only once, the application wouldn't crash when I pressed the key again, but it wouldn't play the sound either. When it does play a sound, it crashes the app if I press the key a second time.

I also gave the full scope of my problem above because I am not sure if using threads is the best way to go about this either. Ideally, I would like it so I can press any of the 25 keys on the keyboard, and the audio library would stream the sounds for the keys pressed simultaneously, and not output anything if nothing was pressed. I don't like the idea of running 25 different threads because my audio buffer is setup in each thread and I think that would overwrite the previous buffer if I played multiple notes at once? Please let me know if you think I am heading in the wrong direction here with using threads for the application I am aiming for. I thought threads would be better because I do have to do a lot of computation before streaming audio (at max, 25 x 6 x 3 = 450 trig operations to calculate one sample of my waveform), and so I thought somehow pipelining the computation would be better?

Either way, please let me know why I cannot get the test setup below to play the sound when I press the key a second time? I am also using Lenovo tabM10 FHD Plus if that's relevant information. This is also my first time using stackOverflow, so I appologize if I have done a terrible job formatting my post. If you think I should Edit my post to be more concise/clear about my issue, please let me know and I will fix it. I apologize for any inconvenience up front.

My Test Code:

package com.example.ece496_simplesynth;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.MotionEventCompat;

import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Build;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private AudioTrack audioTrack;
    private int intBufferSize;
    private short[] shortAudioData;
    private boolean isActive = false;

    private Thread audioThread;

    private boolean keyPress = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final View c4 = (View) findViewById(R.id.c4);

        c4.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                int action = motionEvent.getAction();//.getActionMasked(motionEvent);
                if (action == MotionEvent.ACTION_DOWN) {
                    c4.setBackgroundColor(Color.parseColor("#ed1b24")); //"#81DAF5"
                    keyPress = true;
                    audioThread.start();
                }

                else if (action == MotionEvent.ACTION_UP) {
                    c4.setBackgroundColor(Color.parseColor("#FFFFFF"));
                    keyPress = false;
                    //audioThread.stop();
                }
                return true;
            }
        });

        audioThread = new Thread(new Runnable() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void run() {
                threadLoop();
            }
        });


    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void threadLoop(){
        int intSampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
        intBufferSize = AudioTrack.getMinBufferSize(intSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        shortAudioData = new short[intBufferSize];

        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                intSampleRate,
                AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                intBufferSize,
                AudioTrack.MODE_STREAM);

        audioTrack.setPlaybackRate(intSampleRate);



        int n = 0;
        float x;
        float TS = 1.0f/intSampleRate;
        float freq = 440.0f;
        float PI = 3.14159f;
        float omega = 2.0f*PI*freq*TS;

        if(keyPress){
            audioTrack.play();
        }
        while(keyPress){

            for(int i = 0; i < shortAudioData.length; ++i){
                if(n == Integer.MAX_VALUE | n < 0){
                    n = 0;
                }
                x = (float)(Math.sin((float)(omega*n)));
                ++n;
                shortAudioData[i] = (short)(x * Short.MAX_VALUE);
            }

            audioTrack.write(shortAudioData, 0, shortAudioData.length);
        }

        audioTrack.stop();


    }
}

Upvotes: 0

Views: 354

Answers (1)

Phil Freihofner
Phil Freihofner

Reputation: 7910

I wrote something similar to your project using pure Java. I think there are a number of workable plans. The path I took was to have keyboard triggering (keypress/keyrelease actions) employ loose coupling to a pre-allocated bank of synths. The synths methods that produce the PCM, and a mixer that combined the output of the synths resided on a single, dedicated "audio thread". With Java, I used a SourceDataLine for output. With Android, you can stream raw PCM using AudioTrack.

The number of concurrent notes that you can support likely will be constrained by the complexity of the calculations employed by your synthesis algorithm. I think a common plan is to have the work done in small blocks, e.g., a few milliseconds in length. In the "audio thread" one computes the next array of PCM in each synth that is currently active with a note, then the "audio mixing" step adds the contents of each array into a single array that is written via the AudioTrack. One person I consulted recommended a buffer size of 256 frames of PCM, as a sweet spot for getting high throughput at minimal latency, but it's beyond my knowledge or skills to determine if that is true, or if it would hold in the Android environment.

The code that tracks keypresses for individual keys has to also track which synth has been assigned the task of producing the note, so that it can send a "release" command to the correct synth when the keyrelease occurs.

As a heads up, I discovered that many keyboards don't support truly independent keys. For example, my system won't register a new keypress if some number of neighboring keys are pressed, as in: if you already have QWR down, your keyboard might not be capable of registering the additional keypress of E. I haven't researched the details of this short-fall, and just reconciled myself to a degree of unpredictability.

I haven't tried doing this with a touch screen. The keyboard "buttons" I made respond to mouse clicks, but there is only one mouse, not multiple fingers on a touchscreen.

It's a fun project, and you will learn a lot about managing threads and resources.

Upvotes: 1

Related Questions