Android Open Source - SoundBox Sound Box






From Project

Back to project page SoundBox.

License

The source code is released under:

GNU General Public License

If you think the Android project SoundBox listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

// Copyright (c) 2011 Szymon Jakubczak. All rights reserved.
// Use of this source code is governed by a license that can be found in
// the LICENSE file.
package net.szym.soundbox;
//from   w  w  w  .ja v  a2  s. c  om
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.AsyncTask;
import android.util.Log;

/**
 * SoundBox manages a bunch of sound clips. Load them up front, then tell the
 * SoudBox which clip to play, at what position and volume. The clip will loop
 * by default, but it can loop any smaller part of it too.
 * 
 * Each clip can only be played once at a time. To play the same sound multiple
 * times with overlap you need to load the sound bit as multiple clips.
 * 
 * Supports PCM in 8-bit, 16-bit, mono, stereo. Support for sampling rates (and
 * 8-bit) varies by device. Samsung Galaxy S cracks at 48kHz.
 */
public class SoundBox {
  public static final String TAG = "SoundBox";

  class Track {
    final String filename;
    final AudioTrack track;
    final WaveFile wave;
    final int numSamples;

    Track(String filename) throws IOException {
      this.filename = filename;
      wave = new WaveFile(new FileInputStream(filename));
      // for memory consideration I highly recommend mono!
      track = new AudioTrack(AudioManager.STREAM_MUSIC,
          wave.sampleRate,
          wave.numChannels == 2 ? AudioFormat.CHANNEL_CONFIGURATION_STEREO
              : AudioFormat.CHANNEL_CONFIGURATION_MONO,
          wave.bitsPerSample == 8 ? AudioFormat.ENCODING_PCM_8BIT
              : AudioFormat.ENCODING_PCM_16BIT,
          wave.data.length, AudioTrack.MODE_STATIC);
      // load the data into the track
      track.write(wave.data, 0, wave.data.length);
      // loop the whole track
      numSamples = wave.getLength();
      track.setLoopPoints(0, numSamples, -1);
    }
  }

  /**
   * Array of the loaded clips. When AsyncLoad is executed it makes room in
   * |clips| and stores the track once it's fully loaded.
   */
  private ArrayList<Track> clips = new ArrayList<Track>();

  /**
   * load a clip from the given filename and store it Note: executes the task at
   * creation time use .id to later refer to the loaded clip
   */
  class AsyncLoad extends AsyncTask<String, Void, Track> {
    static final String TAG = "SoundBox.AsyncLoad";
    final int id;
    private final String filename;

    AsyncLoad(String filename) {
      this.id = clips.size();
      clips.add(null); // placeholder
      this.filename = filename;
      execute(); // auto-execute
    }

    @Override
    protected void onPostExecute(Track track) {
      if (track != null)
        clips.set(id, track);
      // else it remains null
    }

    @Override
    protected Track doInBackground(String... params) {
      Track track;
      try {
        track = new Track(filename);
      } catch (IOException e) {
        // bam!
        Log.e(TAG, String.format("Loading %s failed", filename), e);
        return null;
      }
      return track;
    }
  }

  public SoundBox() {
  }

  /**
   * Load asynchronously: at some point in the future the clip will be
   * available. Rather than notifying anybody once the clip is available, we
   * simply ignore all requests (below) if the clip is not there yet (or failed
   * to load).
   * 
   * @param filename
   * @return clip id
   */
  public int load(String filename) {
    return new AsyncLoad(filename).id;
  }

  public boolean isLoaded(int id) {
    return clips.get(id) != null;
  }

  // get rate (in samples/s)
  public int getRate(int id) {
    Track track = clips.get(id);
    if (track == null)
      return 0;
    return track.track.getSampleRate();
  }

  /**
   * @param clip
   *          id
   * @return length (in samples)
   */
  public int getLength(int id) {
    Track track = clips.get(id);
    if (track == null)
      return 0;
    return track.numSamples;
  }

  // resume from current position
  public void play(int id) {
    Track track = clips.get(id);
    if (track == null)
      return;
    track.track.play();
  }

  /**
   * seek to specific position
   * 
   * @param clip
   *          id
   * @param offset
   *          (in samples)
   */
  public void seek(int id, int offset) {
    Track track = clips.get(id);
    if (track == null)
      return;
    AudioTrack atrack = track.track;
    int state = atrack.getPlayState();
    atrack.pause();
    atrack.setPlaybackHeadPosition(offset);
    // resume if necessary
    if (state == AudioTrack.PLAYSTATE_PLAYING)
      atrack.play();
  }

  public void pause(int id) {
    Track track = clips.get(id);
    if (track == null)
      return;
    track.track.pause();
  }

  // not sure what the difference between pause and stop
  // after stop it probably automatically seeks to 0
  public void stop(int id) {
    Track track = clips.get(id);
    if (track == null)
      return;
    track.track.stop();
  }

  /**
   * volume normalized to 0-1
   * 
   * @param id
   * @param left
   * @param right
   */
  public void setVolume(int id, float left, float right) {
    Track track = clips.get(id);
    if (track == null)
      return;
    float maxvol = AudioTrack.getMaxVolume();
    track.track.setStereoVolume(left * maxvol, right * maxvol);
  }
}




Java Source Code List

net.szym.soundbox.SoundBoxActivity.java
net.szym.soundbox.SoundBox.java
net.szym.soundbox.WaveFile.java