MetaColon
MetaColon

Reputation: 2921

Play short generated sound

I want to play a generated sound that is shorter than 1 second. However, the minBufferSize of the AudioTrack always seems to be 1 second or longer. On some devices I can set the bufferSize smaller than the value evaluated with AudioTrack.getMinBufferSize, however this is not possible on all devices. I'd like to know wether it's possible to generate a shorter sound for the AudioTrack. I'm currently using this code (it contains some smoothing, because I'm getting constantly new frequences):

int buffSize = AudioTrack.getMinBufferSize(sampleRate,
                                           AudioFormat.CHANNEL_OUT_MONO,
                                           AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
                                       AudioFormat.CHANNEL_OUT_MONO,
                                       AudioFormat.ENCODING_PCM_16BIT, buffSize,
                                       AudioTrack.MODE_STREAM);
short samples[] = new short[buffSize];
int amp = 10000;
double twopi = 8. * Math.atan(1.);
double phase = 0.0;
audioTrack.play();
double currentFrequency = getFrequency();
double smoothing = 300;
double deltaTime = buffSize / 500;
while (playing && PreferenceManager.getDefaultSharedPreferences(
        MainActivity.this).getBoolean("effect_switch", true))
{
    double newFrequency = getFrequency();
    for (int i = 0; i < buffSize; i++)
    {
        currentFrequency += deltaTime * (newFrequency - currentFrequency) / smoothing;
        samples[i] = (short) (amp * Math.sin(phase));
        phase += twopi * currentFrequency / sampleRate;
    }
    audioTrack.write(samples, 0, buffSize);
}
audioTrack.stop();
audioTrack.release();

In fact, I want the sounds to be updated more frequently, which is the reason for me needing shorter samples.

Upvotes: 1

Views: 296

Answers (1)

Gary99
Gary99

Reputation: 1770

I think I have a solution for you. Since my min buffer seems to be much smaller than 1 sec, I simulated your problem by loading a buffer with 5 sec of data but only play 0.5 sec of it immediately followed by another frequency. This tone I also created 5 sec of data but only played 0.5 sec & repeated this for several tones. It all works for me.

Also, since I jammed this into a current project I'm working on, it's difficult for me to just cut and paste my code. While I've tested my solution, what I've posted here is not tested exactly as written. Some of it is cut & paste, some pseudocode.

The key feature is using the OnPlaybackPositionUpdateListener.

private AudioTrack.OnPlaybackPositionUpdateListener audioTrackListener = new AudioTrack.OnPlaybackPositionUpdateListener() {
    @Override
    public void onMarkerReached(AudioTrack audioTrack) {
        int marker = audioTrack.getNotificationMarkerPosition();
        // I just used 8 tones of 0.5 sec each to determine when to stop but you could make 
        //      the condition based on a button click or whatever is best for you
        if(marker < MAX_FRAME_POSITION) {
            audioTrack.pause();
            newSamples();
            audioTrack.play();
        } else {
            audioTrack.stop();
        }
        audioTrack.setNotificationMarkerPosition(marker + FRAME_MARKER);
        Log.d(TAG, "MarkerReached");
    }

    @Override
    public void onPeriodicNotification(AudioTrack audioTrack) {
        int position = audioTrack.getPlaybackHeadPosition();
        if(position < MAX_FRAME_POSITION) {
            audioTrack.pause();
            newSamples();
            audioTrack.play();
        } else {
            audioTrack.stop();
        }
        Log.d(TAG, "PeriodNotification");
    }
};

Then

    audioTrack.setPlaybackPositionUpdateListener(AudioTrackListener);

I used the marker (which has to be re-initialized repeatedly) for my tests...

    audioTrack.setNotificationMarkerPosition(MARKER_FRAMES);    

but you should be able to use the periodic notification too.

    audioTrack.setPositionNotificationPeriod(PERIODIC_FRAMES);

And the newSamples() method called from the listener

public void newSamples() {
    /*
    * generate buffer, I'm doing similar to you, without the smoothing
    */

    // AudioTrack write is a blocking operation so I've moved it off to it's own Thread.
    //      Could also be done with an AsyncTask.
    Thread thread = new Thread(writeSamples);
    thread.start();
}

private Runnable writeSamples = new Runnable() {
    @Override
    public void run() {
        audioTrack.write(samples, 0, buffSize);
    }
};

Upvotes: 1

Related Questions