]> hydra-www.ietfng.org Git - acmetensortoys-watchviz/commitdiff
Another refectoring pass
authorNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Fri, 1 Jul 2016 00:38:04 +0000 (20:38 -0400)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Fri, 1 Jul 2016 00:38:04 +0000 (20:38 -0400)
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.

12 files changed:
app/src/main/java/com/acmetensortoys/watchviz/MainActivity.java
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioCanvas.java
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioProvider.java [new file with mode: 0644]
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/AudioReceiver.java [new file with mode: 0644]
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Meta.java
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Renderer.java [new file with mode: 0644]
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/Rendering.java [moved from vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderCB.java with 84% similarity]
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/RenderingSelector.java [new file with mode: 0644]
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Avg.java
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/meta/Max.java
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/Grid.java [moved from vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/Grid.java with 91% similarity]
vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/rendering/WholeMax.java [moved from vizlib/src/main/java/com/acmetensortoys/watchviz/vizlib/render/WholeMax.java with 89% similarity]

index b2ec4ffff8dfe7146715dee3a051649b4f016bb2..914c6b4760217dbb539064efc84b226fdc842ca2 100644 (file)
@@ -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;
             }
         });
index f498ff94da70a944e63b7ee22bb5fe824045e7ae..0011e98942fcfe022eaa245022475353d907c45f 100644 (file)
@@ -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<Class<? extends RenderCB>> 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<? extends RenderCB> 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 <? extends RenderCB> next = cbq.removeFirst();
-            cbq.addLast(next);
-            _setCyclerCB(next);
-        }
-    }
-    public void prevCyclerCB() {
-        synchronized(this) {
-            Class <? extends RenderCB> 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 (file)
index 0000000..fcbef8b
--- /dev/null
@@ -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<AudioReceiver> 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<AudioReceiver>());
+    }
+
+    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 (file)
index 0000000..29e09c1
--- /dev/null
@@ -0,0 +1,5 @@
+package com.acmetensortoys.watchviz.vizlib;
+
+public interface AudioReceiver {
+    void onAudio(float[] audio, float[] fft);
+}
index f90b9b8ca26a85e4fcedbd0a9c04b839ebd3a8e9..a6b1e6e23e0ec09faed386f56f7939817eef75f6 100644 (file)
@@ -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 (file)
index 0000000..6cbc206
--- /dev/null
@@ -0,0 +1,5 @@
+package com.acmetensortoys.watchviz.vizlib;
+
+public interface Renderer {
+    void setRendering(Rendering r);
+}
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 ace217b51bf048e0ea28a0dc1da43c47249d78ab..504575065118d844a86277f7fb537e68ce1ffbe5 100644 (file)
@@ -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 (file)
index 0000000..e401ca5
--- /dev/null
@@ -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<Class<? extends Rendering>> 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<? extends Rendering> 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();
+        }
+    }
+}
index 09424bf10758352bfb274a8ddeb69e64d09422b5..d9ec6067a19d6b628e32feace236628ee2aa7ec6 100644 (file)
@@ -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;
index e2b30887ab01137341363cf9b297b09f27d02987..57c211eeba8fda8e815629203a56a368e090381b 100644 (file)
@@ -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;
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 cdd64cda6be40c6fbac8c2aa9bcfcc5c0f42db43..1ccc53a0011ff8b8b82569800ba0ed2d6d7c45b4 100644 (file)
@@ -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();
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 77dba4f6bcdf5d64b9556a50219b3ac8f7064610..30b8c6dab57a2e15a31fab23f0f08b8c1fdde1d1 100644 (file)
@@ -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};