/*
* Robot control console. Copyright (C) 2010 Darrell Taylor & Eric Hokanson
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.allthingsgeek.celljoust;
import java.util.Arrays;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
/**
* The Class PulseGenerator.
*/
public class PulseGenerator implements Runnable {
private static PulseGenerator instance;
//
/** The sample rate. 44100hz is native on g1 */
private int sampleRate;
/** The MI n_ puls e_ width. */
public int MIN_PULSE_WIDTH;
/** The MA x_ puls e_ width. */
public int MAX_PULSE_WIDTH;
/** The pulse widths, determines speed or position of each servo */
private int pulseWidthArray[];
/** Number of pulses left to send for each servo */
private int pulseCountArray[];
/** dead zone(center) offset in percent */
private int servoOffsetArray[];
/** The pulse interval. Should be 20ms */
private int pulseInterval;
/** The buffer pulses. */
private int bufferPulses = 2;
/** The max volume */
private short volume = Short.MAX_VALUE;
/** Are we playing sound right now? */
private boolean playing = true;
/** Are we paused right now? */
private boolean paused = true;
/** Do we need to update the buffer? */
private boolean bufferChanged = false;
/** The noise audio track. */
private AudioTrack noiseAudioTrack;
/** The bufferlength. */
private int systembufferlength; // 4800
/** The left channel buffer. */
private short[] leftChannelBuffer;
/** The right channel buffer. */
private short[] rightChannelBuffer;
private static String TAG = "Servo Pulse Generator";
private static int NUM_SERVOS = 4;
/**
* Instantiates a new pulse generator.
*/
private PulseGenerator() {
sampleRate = AudioTrack
.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
MIN_PULSE_WIDTH = sampleRate / 1200;
MAX_PULSE_WIDTH = sampleRate / 456;
pulseCountArray = new int[NUM_SERVOS];
servoOffsetArray = new int[NUM_SERVOS];
pulseWidthArray = new int[NUM_SERVOS];
Arrays.fill(pulseWidthArray, ((MAX_PULSE_WIDTH - MIN_PULSE_WIDTH) / 2) + MIN_PULSE_WIDTH);
pulseInterval = sampleRate / 50;
systembufferlength = AudioTrack.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
noiseAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT, systembufferlength,
AudioTrack.MODE_STREAM);
sampleRate = noiseAudioTrack.getSampleRate();
Log.i(TAG, "BufferLength = " + Integer.toString(systembufferlength));
Log.i(TAG, "Sample Rate = " + Integer.toString(sampleRate));
leftChannelBuffer = new short[systembufferlength / 2];
rightChannelBuffer = new short[systembufferlength / 2];
Thread noiseThread;
noiseThread = new Thread(this);
noiseThread.setName("noiseThread");
generatePCM(pulseWidthArray[0], pulseWidthArray[1], pulseInterval,
leftChannelBuffer, pulseInterval * bufferPulses, 1);
generatePCM(pulseWidthArray[2], pulseWidthArray[3], pulseInterval,
rightChannelBuffer, pulseInterval * bufferPulses, 3 );
noiseThread.start();
}
public static PulseGenerator getInstance() {
if (instance == null) {
instance = new PulseGenerator();
}
return instance;
}
/**
* Builds a PWM signal in the buffer
* @param pulseWidth Pulse width for signal > 0
* @param negPulseWidth Pulse width for signal < 0
* @param pulseInterval Length of pulse
* @param buffer Array to store the signal
* @param bufferLength Length of buffer
* @param lastChanged
*/
private void generatePCM(int pulseWidth, int negPulseWidth,
int pulseInterval, short buffer[], int bufferLength, int lastChanged) {
int i = 0;
int j = 0;
int pulseVolume = -volume;
if (lastChanged % 2 == 0)
{
pulseVolume = volume;
}
while (i < bufferLength) {
j = 0;
while (j < pulseWidth && i < bufferLength) {
buffer[i] = (short) ((pulseVolume));
i++;
j++;
}
while (j < pulseInterval && i < bufferLength) {
buffer[i] = (short) ((-pulseVolume));
i++;
j++;
}
}
bufferChanged = true;
}
public void run() {
/** The stero audio buffer. */
short[] audioBuffer = new short[systembufferlength];
int sterobufferlength = pulseInterval * bufferPulses * 2;
noiseAudioTrack.play();
while (playing) {
if (paused) {
for (int i = 0; i < systembufferlength; i++) {
audioBuffer[i] = (short) (0);
}
noiseAudioTrack.write(audioBuffer, 0, systembufferlength);
bufferChanged = true;
continue;
}
/*
for (int i = 0; i < NUM_SERVOS; i += 1) {
if (pulseCountArray[i] <= bufferPulses
&& pulseCountArray[i] > 0) {
pulseWidthArray[i] = servoOffsetArray[i];
if (i < 2) {
generatePCM(pulseWidthArray[0], pulseWidthArray[1],
pulseInterval, leftChannelBuffer, pulseInterval
* bufferPulses, i);
} else {
generatePCM(pulseWidthArray[2], pulseWidthArray[3],
pulseInterval, rightChannelBuffer,
pulseInterval * bufferPulses, i);
}
}
if (pulseCountArray[i] > 0) {
pulseCountArray[i] -= bufferPulses;
}
}
*/
if (bufferChanged) {
for (int i = 0; i < sterobufferlength; i += 2) {
//the pulses are staggerd by 1/2 pulseInterval
audioBuffer[i] = leftChannelBuffer[((i / 2)+(pulseInterval/2)) % (sterobufferlength/2)];
audioBuffer[i + 1] = rightChannelBuffer[i / 2];
}
bufferChanged = false;
}
noiseAudioTrack.write(audioBuffer, 0, sterobufferlength);
}
// Cleanup
for (int i = 0; i < systembufferlength; i++) {
audioBuffer[i] = (short) (0);
}
noiseAudioTrack.write(audioBuffer, 0, systembufferlength);
noiseAudioTrack.stop();
noiseAudioTrack.release();
}
/**
* Stop.
*/
public void stop() {
playing = false;
}
/**
* Pause
*/
public void pause(boolean p) {
paused = p;
}
/**
* Pause
*/
public void pause() {
paused = true;
}
/**
* un Pause
*/
public void unpause() {
paused = false;
}
public boolean isPaused() {
return paused;
}
/**
* Sets the servo pos and runtime
*
* @param percent the new left pulse percent
*/
public synchronized void setServo(int servoNum, int percent, int counts) {
if (servoNum < 0 || servoNum > 4) {
Log.e(TAG, "Servo index out of bounds, should be between 0 and 3");
return;
}
percent += servoOffsetArray[servoNum];
if (percent < 0 || percent > 100) {
Log.e(TAG,
"Servo Position out of bounds, should be between 0 and 100");
return;
}
this.pulseWidthArray[servoNum] = (int)((MAX_PULSE_WIDTH - MIN_PULSE_WIDTH) * ((float)percent / 100) + MIN_PULSE_WIDTH);
this.pulseCountArray[servoNum] = counts;
if (servoNum < 2) {
generatePCM(pulseWidthArray[0], pulseWidthArray[1], pulseInterval,
leftChannelBuffer, pulseInterval * bufferPulses, servoNum);
} else {
generatePCM(pulseWidthArray[2], pulseWidthArray[3], pulseInterval,
rightChannelBuffer, pulseInterval * bufferPulses, servoNum);
}
}
/**
* Sets the offset position of a servo.
*
* @param percent sets the pulse percent offset
* @param servoNum servo number to set
*/
public void setOffsetPulsePercent(int percent, int servoNum) {
if (percent < 0 || percent > 100) {
Log.e(TAG,
"Servo Position out of bounds, should be between 0 and 100");
return;
}
servoOffsetArray[servoNum] = percent - 50;
setServo(servoNum, 50, Integer.MAX_VALUE);
}
/**
* Gets the pulse percent.
*
* @param i the servo number
* @return the pulse percent
*/
public int getPulsePercent(int i) {
return (int)(((float)(pulseWidthArray[i] - MIN_PULSE_WIDTH) / (MAX_PULSE_WIDTH - MIN_PULSE_WIDTH)) * 100);
}
/**
* Gets the pulse ms.
*
* @param i the servo number
* @return the pulse ms
*/
public float getPulseMs(int i) {
return ((float) pulseWidthArray[i] / sampleRate) * 1000;
}
/**
* Gets the pulse samples.
*
* @param i the servo number
* @return the pulse samples
*/
public int getPulseSamples(int i) {
return pulseWidthArray[i];
}
}