You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
music-synthesizer-for-android/android/src/com/levien/synthesizer/android/service/SynthesizerThread.java

230 lines
7.0 KiB

/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.levien.synthesizer.android.service;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import com.levien.synthesizer.core.model.SynthesisTime;
import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer;
/**
* SynthesizerThread is a thread-safe interface to a thread that constantly plays sampled audio data
* from a given SignalProvider.
*/
public class SynthesizerThread {
/**
* Creates a new SynthesizerThread that will play audio from synthesizer.
* @param synthesizer - the source of the audio data to output.
*/
public SynthesizerThread(MultiChannelSynthesizer synthesizer, int sampleRateInHz) {
playingLock_ = new Object();
playing_ = false;
shouldDie_ = false;
audioTrackLock_ = new Object();
audioTrack_ = null;
synthesizer_ = synthesizer;
sampleRateInHz_ = sampleRateInHz;
time_ = new SynthesisTime();
time_.setSampleRate(sampleRateInHz);
}
/**
* Starts the thread taking sampled audio data from the synthesizer and playing it.
*/
public void play() {
synchronized (playingLock_) {
if (playing_) {
// It's already playing.
return;
}
// Set up the initial state.
playing_ = true;
shouldDie_ = false;
initSound();
fillBuffer();
// Start the audio track playing from the buffer.
startSound();
// Start the thread that grabs the output from the synthesizer and fills the output buffer.
speakerThread_ = new Thread(new Runnable() {
public void run() {
while (true) {
synchronized (playingLock_) {
// Check if the thread should die.
if (shouldDie_) {
// This thread has been signalled to stop.
Log.i(getClass().getName(), "Dying now.");
playing_ = false;
shouldDie_ = false;
cleanupSound();
return;
}
}
// Do the actual work.
fillBuffer();
}
}
});
speakerThread_.setName(getClass().getName());
speakerThread_.setPriority(Thread.MAX_PRIORITY);
speakerThread_.setDaemon(true);
speakerThread_.start();
}
}
/**
* Tells the thread it should stop getting sampled data and outputting it as soon as possible.
*/
public void stop() {
Log.i(getClass().getName(), "stop() is waiting for playingLock_.");
synchronized (playingLock_) {
Log.i(getClass().getName(), "stop() received.");
shouldDie_ = true;
}
}
/**
* Blocks until the thread is no longer playing.
* You better call stop() before calling this.
*/
public void waitForStop() {
while (true) {
synchronized (playingLock_) {
if (!playing_) {
return;
}
}
}
}
/**
* Initializes the data structures needed for playing.
*/
private void initSound() {
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
cleanupSound();
}
// Get the smallest buffer to minimize latency.
int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz_,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
Log.i(getClass().getName(), "Output sample rate: " + sampleRateInHz_);
Log.i(getClass().getName(), "Minimum buffer size: " + bufferSizeInBytes);
Log.i(getClass().getName(), "Minimum volume: " + AudioTrack.getMinVolume());
Log.i(getClass().getName(), "Maximum volume: " + AudioTrack.getMaxVolume());
audioTrack_ = new AudioTrack(
AudioManager.STREAM_MUSIC, // int streamType,
sampleRateInHz_, // int sampleRateInHz,
AudioFormat.CHANNEL_OUT_MONO, // int channelConfig,
AudioFormat.ENCODING_PCM_16BIT, // int audioFormat,
bufferSizeInBytes, // int bufferSizeInBytes,
AudioTrack.MODE_STREAM); // int mode);
buffer_ = new short[bufferSizeInBytes / 8];
time_.reset();
}
}
/**
* Starts the AudioTrack playing.
*/
private void startSound() {
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
audioTrack_.play();
}
}
}
/**
* Cleans up the internal data structures.
*/
private void cleanupSound() {
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
audioTrack_.stop();
audioTrack_.release();
audioTrack_ = null;
buffer_ = null;
}
}
}
/**
* Fills the buffer once, and then sends it off to the AudioTrack.
*/
private void fillBuffer() {
for (int i = 0; i < buffer_.length; ++i) {
// Change the output range from [-1, 1] to [-32767, 32767].
// 16-bit signed output is fairly standard, and hard-coded.
double output = synthesizer_.getValue(time_);
// Clamp values out of range.
if (output < -1.0) {
output = -1.0;
}
if (output > 1.0) {
output = 1.0;
}
buffer_[i] = (short)(32767 * output);
time_.advance();
}
synchronized (audioTrackLock_) {
if (audioTrack_ != null) {
// This call will block until the buffer has been copied, but will return before the
// sampled audio data has actually been output.
audioTrack_.write(buffer_, 0, buffer_.length);
}
}
}
// The actual thread that constantly loops, taking the output from the synthesizer and sending it
// to Android's audio output buffer.
Thread speakerThread_;
// Lock guarding playing_, shouldDie_, and speakerThread_.
private Object playingLock_;
// True iff the thread is in the "playing" state.
private boolean playing_;
// Variable used to signal to the speakerThread that playing should stop.
private boolean shouldDie_;
// Lock guarding audioTrack_, buffer_, and time_.
private Object audioTrackLock_;
// Android object for outputting audio.
private AudioTrack audioTrack_;
// Buffer for collecting output from the synthesizer until it is ready to be output.
private short[] buffer_;
// The sample rate of the synthesizer.
private int sampleRateInHz_;
// Tracker for time since synthesis started.
private SynthesisTime time_;
// Module to provide sampled audio data to be output.
private MultiChannelSynthesizer synthesizer_;
}