package com.openrobot.common;
// spiritplumber@gmail.com
//THIS IS CURRENTLY BEING OPTIMIZED
import java.util.Arrays;
import java.util.LinkedList;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.SystemClock;
public class AudioSerialOutMono {
// idea from http://marblemice.blogspot.com/2010/04/generate-and-play-tone-in-android.html as modified by Steve Pomeroy <steve@staticfree.info>
private static Thread audiothread = null;
private static AudioTrack audiotrk = null;
private static byte generatedSnd[] = null;
// set that can be edited externally
public static int max_sampleRate = 48000;
public static int min_sampleRate = 4000;
public static int new_baudRate = 4800; // assumes N,8,1 right now
public static int new_sampleRate = 48000; // min 4000 max 48000
public static int new_characterdelay = 0; // in audio frames, so depends on the sample rate. Useful to work with some microcontrollers.
// set that is actually used: this is so they get upadted all in one go (safer)
private static int baudRate = 4800;
private static int sampleRate = 48000;
private static int characterdelay = 0;
public static LinkedList<byte[]> playque = new LinkedList<byte[]>();
public static boolean active = false;
public static void UpdateParameters(boolean AutoSampleRate){
baudRate = new_baudRate; // we're not forcing standard baud rates here specifically because we want to allow odd ones
if (AutoSampleRate == true)
{
new_sampleRate = new_baudRate;
while(new_sampleRate <= (max_sampleRate-1))
{
new_sampleRate *=2;//+= new_baudRate;
}
new_sampleRate/=2;
}
if (new_sampleRate > max_sampleRate)
new_sampleRate = max_sampleRate;
if (new_sampleRate < min_sampleRate)
new_sampleRate = min_sampleRate;
sampleRate = new_sampleRate; // min 4000 max 48000
if (new_characterdelay < 0)
new_characterdelay = 0;
characterdelay = new_characterdelay;
minbufsize=AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_8BIT);
}
public static void output(String sendthis)
{
playque.add(SerialDAC(sendthis.getBytes()));
audiothread.interrupt();
}
public static void output(byte[] sendthis)
{
playque.add(SerialDAC(sendthis));
audiothread.interrupt();
}
private static int bytesinframe=10+characterdelay;
private static int i=0; // counter
private static int j=0; // counter
private static int k=0; // counter
private static int m=0; // counter
private static int n=sampleRate / baudRate;
private static boolean[] bits;
private static byte[] waveform;
private static final byte l=3; // intentional jitter used to prevent the DAC from flattening the waveform prematurely
private static final byte logichigh = (byte) (-128);
private static final byte logiclow = (byte) (64);
public static byte[] SerialDAC(byte[] sendme)
{
bytesinframe=10+characterdelay;
j=0; // counter
m=0; // counter
n=sampleRate / baudRate;
bits = new boolean[sendme.length*bytesinframe];
waveform = new byte[(sendme.length*bytesinframe*sampleRate /
baudRate)]; // 8 bit, no parity, 1 stop
//Arrays.fill(waveform, (byte) 0);
Arrays.fill(bits, true); // slight opti to decide what to do with stop bits
// generate bit array first: makes it easier to understand what's going on
for (i=0;i<sendme.length;++i)
{
m=i*bytesinframe;
bits[m]=false;
bits[++m]=((sendme[i]&1)==1);//==0)?false:true;
bits[++m]=((sendme[i]&2)==2);//==0)?false:true;
bits[++m]=((sendme[i]&4)==4);//==0)?false:true;
bits[++m]=((sendme[i]&8)==8);//==0)?false:true;
bits[++m]=((sendme[i]&16)==16);//==0)?false:true;
bits[++m]=((sendme[i]&32)==32);//==0)?false:true;
bits[++m]=((sendme[i]&64)==64);//==0)?false:true;
bits[++m]=((sendme[i]&128)==128);//==0)?false:true;
// cheaper to prefill to true
// now we need a stop bit, BUT we want to be able to add more (character delay) to play-nice with some microcontrollers such as the Picaxe or BS1 that need it in order to do decimal conversion natively.
// for(k=0;k<bytesinframe-9;k++)
// bits[++m]=true;
}
// now generate the actual waveform using l to wiggle the DAC and prevent it from zeroing out
for (i=0;i<bits.length;i++)
{
for (k=0;k<n;k++)
{
waveform[j++]=(bits[i])?((byte) (logichigh+(j&l))):((byte)
(logiclow-(j&l)));
//if (bits[i])
// waveform[j]= (byte) (logichigh+(j&l)); // the +l / -l is to fool the DAC into not having a flat waveform, which it might reject
//else
// waveform[j]= (byte) (logiclow-(j&l)); // the +l / -l is to fool the DAC into not having a flat waveform, which it might reject
//++j;
}
}
bits=null;
return waveform;
}
// essentially a constructor, but i prefer to do a manual call.
public static void activate() {
UpdateParameters(true);
// Use a new tread as this can take a while
audiothread = new Thread(new Runnable() {
public void run() {
playSound();
}
}
);
audiothread.start();
while(active == false) // wait for the thread to actually turn on
SystemClock.sleep(50);
}
public static void deactivate() // allows the audio thread to die
{
active=false;
audiothread.interrupt();
}
public static boolean isPlaying()
{
try{return audiotrk.getPlaybackHeadPosition() <
(generatedSnd.length);}catch(Exception e){return false;}
}
private static int minbufsize;
private static int length;
private static void playSound(){
active = true;
while(active)
{
try {Thread.sleep(Long.MAX_VALUE);} catch (InterruptedException e) {
while (playque.isEmpty() == false)
{
if (audiotrk != null)
{
if (generatedSnd != null)
{
while (audiotrk.getPlaybackHeadPosition() < (generatedSnd.length))
SystemClock.sleep(50); // let existing sample finish first: this can probably be set to a smarter number using the information above
}
audiotrk.release();
}
UpdateParameters(false); // might as well do it at every iteration, it's cheap
generatedSnd = playque.poll();
length = generatedSnd.length;
if (minbufsize<length)
minbufsize=length;
audiotrk = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_8BIT, minbufsize,
AudioTrack.MODE_STATIC);
audiotrk.setStereoVolume(2,2);
audiotrk.write(generatedSnd, 0, length);
audiotrk.play();
}
}
}
}
}