package com.ihunda.android.binauralbeat; import java.util.ArrayList; import com.ihunda.android.binauralbeat.Note.NoteK; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.util.Log; public class VoicesPlayer extends Thread { private static final float DEFAULT_VOLUME = .7f; private static final String LOGVP = "VoiceP"; private static final int MAX_VOICES = 10; AudioTrack track; static int HZ = 16384; private static final int AUDIOBUF_SIZE = HZ/4; float freqs[]; float pitchs[]; float vols[]; int anglesL[] = new int[MAX_VOICES]; int anglesR[] = new int[MAX_VOICES]; int anglesISO[] = new int[MAX_VOICES]; private FloatSinTable sinT; long startTime; int TwoPi; static int ISCALE = 1440; //short[] fakeS; boolean playing = false; boolean want_shutdown; private float fade; private float volume; public VoicesPlayer() { int minSize = AudioTrack.getMinBufferSize(HZ, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT ); assert(AUDIOBUF_SIZE > minSize); track = new AudioTrack( AudioManager.STREAM_MUSIC, HZ, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, AUDIOBUF_SIZE*2, AudioTrack.MODE_STREAM); sinT = new FloatSinTable(ISCALE); TwoPi = ISCALE; want_shutdown = false; Log.e(LOGVP, String.format("minsize %d bufsize %d ", minSize, AUDIOBUF_SIZE*2)); setPriority(Thread.MAX_PRIORITY); volume = DEFAULT_VOLUME; } public void playVoices(ArrayList<BinauralBeatVoice> voices) { synchronized (voices) { freqs = new float[voices.size()]; for (int i =0; i<freqs.length; i++) { freqs[i] = voices.get(i).freqStart; } pitchs = new float[voices.size()]; for (int i =0; i<pitchs.length; i++) { pitchs[i] = voices.get(i).pitch; } vols = new float[voices.size()]; for (int i =0; i<freqs.length; i++) { vols[i] = voices.get(i).volume; } } fade = 1f; playing = true; if (track.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) track.play(); } public void setFreqs(float freqs[]) { assert(freqs.length == this.freqs.length); this.freqs = freqs.clone(); } public void setVolume(float vol) { track.setStereoVolume(vol*fade, vol*fade); volume = vol; } public void stopVoices() { playing = false; track.stop(); anglesL = new int[MAX_VOICES]; anglesR = new int[MAX_VOICES]; anglesISO = new int[MAX_VOICES]; } public void shutdown() { setVolume(0); want_shutdown = true; } public void setFade(float f) { fade = f; setVolume(volume); //Log.v(LOGVP, String.format("Fade %f", fade)); } @Override public synchronized void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); while(!want_shutdown) { // Sleep if no voice loaded if (!playing) { try { sleep(500); //Log.v(LOGVP, "Sleeping"); } catch (InterruptedException e) { // ignore } continue; } short[] samples = fillSamples(); int totalWritten = 0; int total = samples.length; while (totalWritten != total) { int out = track.write(samples, 0, samples.length); boolean sleep = false; //Log.v(LOGVP, String.format("Written %d short out of %d", out, samples.length)); if (out == AudioTrack.ERROR_BAD_VALUE) { Log.v(LOGVP, String.format("Got BAD VALUE")); // It means somehow the audiotrack is not playing anymore, let's just stop break; } else if (out < 0) { Log.v(LOGVP, String.format("Got strange %d", out)); break; } else { totalWritten += out; if (out != samples.length) { sleep = true; short[] sf = new short[samples.length - out]; System.arraycopy(samples, out, sf, 0, samples.length - out); //Log.v(LOGVP, String.format("Shoud be equal %d %d", total - totalWritten, samples.length - out)); samples = sf; } } if (!want_shutdown && sleep) { Log.v(LOGVP, String.format("we're going too fast - throttling")); try { sleep(50); } catch (InterruptedException e) { // ignore } } } } track.release(); Log.v(LOGVP, String.format("Graceful shutdown")); } private short[] fillSamplesIsoChronic() { assert(freqs != null); short samples[] = new short[AUDIOBUF_SIZE]; float ws[] = new float[samples.length]; for (int j=0; j<freqs.length; j++) { //freqs.length float base_freq; if (BinauralBeatVoice.DEFAULT == pitchs[j]) base_freq = voicetoPitch(j); else base_freq = pitchs[j]; float cur_freq = base_freq+freqs[j]; int inc1 = (int) (TwoPi * (cur_freq) / HZ); int inc2 = (int) (TwoPi * (base_freq) / HZ); int inciso = (int) (TwoPi * (freqs[j]) / HZ); int angle1 = anglesL[j]; int angle2 = anglesR[j]; int angleiso = anglesISO[j]; for(int i = 0; i < samples.length; i+=2) { if (angleiso > ISCALE/2) { ws[i] += 0; ws[i+1] += 0; } else { ws[i] += sinT.sinFastInt(angle1) * vols[j]; ws[i+1] += sinT.sinFastInt(angle2) * vols[j]; } angle1 += inc1; angle2 += inc2; angleiso += inciso; angleiso = angleiso % ISCALE; } anglesL[j] = angle1 % ISCALE; anglesR[j] = angle2 % ISCALE; anglesISO[j] = angleiso % ISCALE; } for(int i = 0; i < samples.length; i+=2) { samples[i] = (short) (ws[i]*Short.MAX_VALUE/freqs.length); samples[i+1] = (short) (ws[i+1]*Short.MAX_VALUE/freqs.length); } return samples; } private short[] fillSamples() { if (freqs == null || freqs.length == 0) return new short[AUDIOBUF_SIZE]; short samples[] = new short[AUDIOBUF_SIZE]; float ws[] = new float[samples.length]; for (int j=0; j<freqs.length; j++) { //freqs.length float base_freq; if (BinauralBeatVoice.DEFAULT == pitchs[j]) base_freq = voicetoPitch(j); else base_freq = pitchs[j]; int inc1 = (int) (TwoPi * (base_freq+freqs[j]) / HZ); int inc2 = (int) (TwoPi * (base_freq) / HZ); int angle1 = anglesL[j]; int angle2 = anglesR[j]; for(int i = 0; i < samples.length; i+=2) { ws[i] += sinT.sinFastInt(angle1) * vols[j]; ws[i+1] += sinT.sinFastInt(angle2) * vols[j]; angle1 += inc1; angle2 += inc2; } anglesL[j] = angle1 % ISCALE; anglesR[j] = angle2 % ISCALE; } for(int i = 0; i < samples.length; i+=2) { samples[i] = (short) (ws[i]*Short.MAX_VALUE/freqs.length); samples[i+1] = (short) (ws[i+1]*Short.MAX_VALUE/freqs.length); } return samples; } private Note voicetoNote(int i) { switch (i) { case 0: return new Note(NoteK.A); case 1: return new Note(NoteK.C); case 2: return new Note(NoteK.E); case 3: return new Note(NoteK.G); case 4: return new Note(NoteK.C, 5); case 5: return new Note(NoteK.E, 6); default: return new Note(NoteK.A, 7); } } private float voicetoPitch(int i) { Note n = voicetoNote(i); return (float) n.getPitchFreq(); } }