From: Nathaniel Wesley Filardo Date: Fri, 1 Jul 2016 00:38:04 +0000 (-0400) Subject: Another refectoring pass X-Git-Url: https://hydra-www.ietfng.org/gitweb/?a=commitdiff_plain;h=861836569a1dc6f63d91dfdd14ed5e968a3a628d;p=acmetensortoys-watchviz Another refectoring pass In preparation for multiple audio consumers (display and... :) ) Simplify AudioCanvas to its core, push other functionality out to other classes. Some renaming elsewhere in the tree to hopefully improve clarity. --- diff --git a/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java b/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java index b2ec4ff..914c6b4 100644 --- a/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java +++ b/app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java @@ -36,6 +36,8 @@ import java.util.Date; import java.util.Locale; import com.acmetensortoys.watchviz.vizlib.AudioCanvas; +import com.acmetensortoys.watchviz.vizlib.AudioProvider; +import com.acmetensortoys.watchviz.vizlib.RenderingSelector; public class MainActivity extends WearableActivity { @@ -47,15 +49,19 @@ public class MainActivity extends WearableActivity private TextView mTextView, mClockView, mDebugView; private SurfaceView mACSurfaceView; private AudioCanvas mAudioCanvas; + private AudioProvider mAudioProvider = new AudioProvider(); private void createSurface() { Log.d("createSurface", "top"); mACSurfaceView = new SurfaceView(this); - mAudioCanvas = new AudioCanvas(mDebugView, mACSurfaceView); + mAudioCanvas = new AudioCanvas(mACSurfaceView, mAudioProvider); + + final RenderingSelector rs = new RenderingSelector(mDebugView, mAudioCanvas); + mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mAudioCanvas.prevCyclerCB(); + rs.prevCyclerCB(); } }); mACSurfaceView.setOnClickListener(new View.OnClickListener(){ @@ -65,7 +71,7 @@ public class MainActivity extends WearableActivity mACSurfaceView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - mAudioCanvas.nextCyclerCB(); + rs.nextCyclerCB(); return true; } }); diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java index f498ff9..0011e98 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java @@ -2,68 +2,29 @@ package com.acmetensortoys.watchviz.vizlib; import android.graphics.Canvas; import android.graphics.Color; -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.MediaRecorder; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.view.View; -import android.widget.TextView; -import com.acmetensortoys.watchviz.vizlib.render.Grid; -import com.acmetensortoys.watchviz.vizlib.render.WholeMax; - -import org.jtransforms.fft.FloatFFT_1D; - -import java.util.ArrayDeque; -import java.util.Deque; - -public class AudioCanvas { - private TextView mDebugView; +public class AudioCanvas implements Renderer { + private AudioProvider mAudioProvider; private SurfaceView mSurfaceView; - - private RenderCB renderCB; - private Deque> cbq = new ArrayDeque<>(); - private Thread cycler; + private Rendering rendering; - public AudioCanvas(TextView debugView, SurfaceView surfaceView) { - this.mDebugView = debugView; + public AudioCanvas(SurfaceView surfaceView, AudioProvider ap) { this.mSurfaceView = surfaceView; + this.mAudioProvider = ap; mSurfaceView.getHolder().addCallback(shc); - - cbq.add(Grid.class); - cbq.add(WholeMax.class); - nextCyclerCB(); } - private void _setCyclerCB(Class next) { - try { - renderCB = next.getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - String name = renderCB.getClass().getSimpleName(); - mDebugView.setText(name.substring(0,Math.min(10,name.length()))); - } - public void nextCyclerCB() { - synchronized(this) { - Class next = cbq.removeFirst(); - cbq.addLast(next); - _setCyclerCB(next); - } - } - public void prevCyclerCB() { - synchronized(this) { - Class next = cbq.removeLast(); - cbq.addFirst(next); - _setCyclerCB(next); - } + public void setRendering(Rendering r) { + synchronized(this) { rendering = r; } } + public void onClick() { - final RenderCB rcb; - synchronized(this) { rcb = renderCB; } + final Rendering rcb; + synchronized(this) { rcb = rendering; } rcb.onClick(); } @@ -71,9 +32,7 @@ public class AudioCanvas { // have been checked. We therefore can simply start in the created callback. Stopping // happens in onPause below, which fires before the surface is destroyed. private SurfaceHolder.Callback shc = new SurfaceHolder.Callback() { - - private static final int AUDIO_RECORDER_BUFFER_SIZE = 2048; - private static final int AUDIO_SAMPLES = 512; + private AudioReceiver ar; @Override public void surfaceCreated(final SurfaceHolder h) { @@ -82,60 +41,30 @@ public class AudioCanvas { c.drawColor(Color.RED); h.unlockCanvasAndPost(c); - final AudioRecord ar = new AudioRecord( - MediaRecorder.AudioSource.MIC, - 11025, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT, - AUDIO_RECORDER_BUFFER_SIZE); - Log.d("shc", "Audio session ID:" + ar.getAudioSessionId()); - - cycler = new Thread() { - public void run() { - // Raw audio samples - float[] samples = new float[AUDIO_SAMPLES]; - - // FFT data and engine - float[] fft = new float[AUDIO_SAMPLES]; - FloatFFT_1D fftc = new FloatFFT_1D(AUDIO_SAMPLES); - - ar.startRecording(); - while (!Thread.interrupted()) { - final RenderCB rcb; - ar.read(samples, 0, samples.length, AudioRecord.READ_BLOCKING); - System.arraycopy(samples,0,fft,0,AUDIO_SAMPLES); - /* - * Debug: triangle wave - */ - /* - for(int i = 0; i < samples.length; i++) { - samples[i] = (float)((i % 16) - 8) / 8; - if (i % 32 >= 16) { samples[i] *= -1; } - } - */ - - fftc.realForward(fft); - - synchronized(this) { - rcb = renderCB; - } - + synchronized(this) { + if (ar != null) { + Log.d("shc", "Create with existing ar?"); + mAudioProvider.remove(ar); + } + ar = new AudioReceiver() { + @Override + public void onAudio(float[] audio, float[] fft) { + final Rendering rcb; + synchronized (this) { rcb = rendering; } Canvas cv = h.lockCanvas(); if (cv == null) { // the surface must have been destroyed out from under us; - // just stop here. - break; + // just stop here, on the presumption that we will be + // removed from the provider by the hook below + return; } cv.drawColor(Color.BLACK); - rcb.render(cv,samples,fft); + rcb.render(cv, audio, fft); h.unlockCanvasAndPost(cv); - - // try { Thread.sleep(100); } catch (InterruptedException e) { break; } } - ar.stop(); - ar.release(); - Log.d("cycler", "exit"); - } - }; - cycler.start(); + }; + mAudioProvider.add(ar); + } } @Override @@ -147,10 +76,9 @@ public class AudioCanvas { @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d("shc", "Surface Destroyed"); - if(cycler != null) { - cycler.interrupt(); - try { cycler.join(); } - catch (InterruptedException e) { Log.d("shc", "IE while join cycler"); } + synchronized(this) { + mAudioProvider.remove(ar); + ar = null; } } }; diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java new file mode 100644 index 0000000..fcbef8b --- /dev/null +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java @@ -0,0 +1,85 @@ +package com.acmetensortoys.watchviz.vizlib; + +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.util.Log; + +import org.jtransforms.fft.FloatFFT_1D; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class AudioProvider { + private static final int AUDIO_RECORDER_BUFFER_SIZE = 2048; + private static final int AUDIO_SAMPLES = 512; + + private Set audioReceivers; + private Thread audioSourceThread; + + public void add(AudioReceiver ar) { + synchronized(this) { + boolean empty = audioReceivers.isEmpty(); + audioReceivers.add(ar); + + if (empty) { + this.start(); + } + } + } + + public void remove(AudioReceiver ar) { + synchronized(this) { + audioReceivers.remove(ar); + if(audioReceivers.isEmpty()) { + this.stop(); + } + } + } + + public AudioProvider() { + audioReceivers = Collections.synchronizedSet(new HashSet()); + } + + private void start() { + final AudioRecord ar = new AudioRecord( + MediaRecorder.AudioSource.MIC, + 11025, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT, + AUDIO_RECORDER_BUFFER_SIZE); + Log.d("shc", "Audio session ID:" + ar.getAudioSessionId()); + + audioSourceThread = new Thread() { + public void run() { + // Raw audio samples + float[] samples = new float[AUDIO_SAMPLES]; + + // FFT data and engine + float[] fft = new float[AUDIO_SAMPLES]; + FloatFFT_1D fftc = new FloatFFT_1D(AUDIO_SAMPLES); + + ar.startRecording(); + while (!Thread.interrupted()) { + final Rendering rcb; + ar.read(samples, 0, samples.length, AudioRecord.READ_BLOCKING); + System.arraycopy(samples, 0, fft, 0, AUDIO_SAMPLES); + fftc.realForward(fft); + + for(AudioReceiver ar : audioReceivers) { + ar.onAudio(samples, fft); + } + } + } + }; + } + + private void stop() { + synchronized(this) { + audioSourceThread.interrupt(); + + try { audioSourceThread.join(); } + catch (InterruptedException ie) { ; } + finally { audioSourceThread = null; } + } + } +} diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioReceiver.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioReceiver.java new file mode 100644 index 0000000..29e09c1 --- /dev/null +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioReceiver.java @@ -0,0 +1,5 @@ +package com.acmetensortoys.watchviz.vizlib; + +public interface AudioReceiver { + void onAudio(float[] audio, float[] fft); +} diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Meta.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Meta.java index f90b9b8..a6b1e6e 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Meta.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Meta.java @@ -1,6 +1,6 @@ package com.acmetensortoys.watchviz.vizlib; -public interface Meta { - float get(); - void update(float last); +public abstract class Meta { + abstract public float get(); + abstract public void update(float last); } diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Renderer.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Renderer.java new file mode 100644 index 0000000..6cbc206 --- /dev/null +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Renderer.java @@ -0,0 +1,5 @@ +package com.acmetensortoys.watchviz.vizlib; + +public interface Renderer { + void setRendering(Rendering r); +} diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderCB.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java similarity index 84% rename from vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderCB.java rename to vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java index ace217b..5045750 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderCB.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java @@ -2,7 +2,7 @@ package com.acmetensortoys.watchviz.vizlib; import android.graphics.Canvas; -public abstract class RenderCB { +public abstract class Rendering { abstract public void render(Canvas c, float[] audio, float[] fft); public void onClick() { } } \ No newline at end of file diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java new file mode 100644 index 0000000..e401ca5 --- /dev/null +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java @@ -0,0 +1,69 @@ +package com.acmetensortoys.watchviz.vizlib; + +import android.widget.TextView; + +import com.acmetensortoys.watchviz.vizlib.rendering.Grid; +import com.acmetensortoys.watchviz.vizlib.rendering.WholeMax; + +import java.util.ArrayList; + +public class RenderingSelector { + private TextView mDebugView; + private Renderer mRenderer; + + private int renderCBix; + private ArrayList> cbs = new ArrayList<>(); + + public RenderingSelector(TextView dv, Renderer r) { + mDebugView = dv; + mRenderer = r; + cbs.add(Grid.class); + cbs.add(WholeMax.class); + renderCBix = 0; + _setCyclerCBByIx(); + } + + private void _setCyclerCB(Class next) { + Rendering rendering; + try { + rendering = next.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + mRenderer.setRendering(rendering); + String name = rendering.getClass().getSimpleName(); + mDebugView.setText(name.substring(0,Math.min(10,name.length()))); + } + private void _setCyclerCBByIx() { + _setCyclerCB(cbs.get(renderCBix)); + } + public void nextCyclerCB() { + synchronized(this) { + if (renderCBix == cbs.size()-1) { + renderCBix = 0; + } else { + renderCBix += 1; + } + _setCyclerCBByIx(); + } + } + public void prevCyclerCB() { + synchronized(this) { + if (renderCBix == 0) { + renderCBix = cbs.size()-1; + } else { + renderCBix -= 1; + } + _setCyclerCBByIx(); + } + } + public void setCBIx(int ix) { + if ((ix < 0) || (ix >= cbs.size())) { + throw new ArrayIndexOutOfBoundsException(ix); + } + synchronized(this) { + renderCBix = ix; + _setCyclerCBByIx(); + } + } +} diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Avg.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Avg.java index 09424bf..d9ec606 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Avg.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Avg.java @@ -2,7 +2,7 @@ package com.acmetensortoys.watchviz.vizlib.meta; import com.acmetensortoys.watchviz.vizlib.Meta; -public class Avg implements Meta { +public class Avg extends Meta { public float window = 0f; public int ix = 0; public float[] list; diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Max.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Max.java index e2b3088..57c211e 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Max.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Max.java @@ -2,7 +2,7 @@ package com.acmetensortoys.watchviz.vizlib.meta; import com.acmetensortoys.watchviz.vizlib.Meta; -public class Max implements Meta { +public class Max extends Meta { public float window = Float.NEGATIVE_INFINITY; public int ix = 0; final public float[] list; diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/Grid.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/Grid.java similarity index 91% rename from vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/Grid.java rename to vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/Grid.java index cdd64cd..1ccc53a 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/Grid.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/Grid.java @@ -1,13 +1,13 @@ -package com.acmetensortoys.watchviz.vizlib.render; +package com.acmetensortoys.watchviz.vizlib.rendering; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import com.acmetensortoys.watchviz.vizlib.RenderCB; +import com.acmetensortoys.watchviz.vizlib.Rendering; import com.acmetensortoys.watchviz.vizlib.meta.Avg; -public final class Grid extends RenderCB { +public final class Grid extends Rendering { private boolean doDebug; private final Paint p = new Paint(); private final Paint dbp = new Paint(); diff --git a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/WholeMax.java b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/WholeMax.java similarity index 89% rename from vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/WholeMax.java rename to vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/WholeMax.java index 77dba4f..30b8c6d 100644 --- a/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/WholeMax.java +++ b/vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/WholeMax.java @@ -1,15 +1,15 @@ -package com.acmetensortoys.watchviz.vizlib.render; +package com.acmetensortoys.watchviz.vizlib.rendering; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; -import com.acmetensortoys.watchviz.vizlib.RenderCB; +import com.acmetensortoys.watchviz.vizlib.Rendering; import java.util.Locale; -public final class WholeMax extends RenderCB { +public final class WholeMax extends Rendering { private boolean doDebug; private final Paint dbp = new Paint(); private float[] hsv = new float[]{0.0f, 1.0f, 1.0f};