Skip to content

Commit 4bb8b38

Browse files
author
benelliott
committed
Started to implement filtering of captured audio.
1 parent 4c4e5d2 commit 4bb8b38

12 files changed

Lines changed: 130 additions & 36 deletions
946 Bytes
Binary file not shown.

SpectrogramAndroid/bin/classes.dex

2.01 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
0 Bytes
Binary file not shown.

SpectrogramAndroid/src/uk/co/benjaminelliott/spectrogramandroid/AudioBitmapConverter.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,27 @@ public class AudioBitmapConverter {
2424
private final int width;
2525
private final int height;
2626
private CapturedBitmapAudio cba;
27+
private Bitmap bitmap;
2728

2829
AudioBitmapConverter(String filename, String directory, Bitmap bitmap, short[] rawWavAudio, Location loc, int sampleRate) {
2930
this.filename = filename;
3031
this.directory = directory;
3132
this.SAMPLE_RATE = sampleRate;
33+
this.bitmap = bitmap;
3234
decLatitude = loc.getLatitude();
3335
decLongitude = loc.getLongitude();
3436
wavAudio = wavAsByteArray(rawWavAudio);
35-
writeBitmapToJPEG(bitmap);
36-
geotagJPEG();
37-
writeAudioToWAV();
3837
width = bitmap.getWidth();
3938
height = bitmap.getHeight();
4039
bitmapAsIntArray = new int[width*height];
4140
bitmap.getPixels(bitmapAsIntArray, 0, width, 0, 0, width, height);
4241
cba = new CapturedBitmapAudio(filename, bitmapAsIntArray, wavAudio, width, height, decLatitude, decLongitude);
4342
}
4443

44+
void storeJPEGandWAV() {
45+
writeBitmapToJPEG(bitmap);
46+
writeAudioToWAV();
47+
}
4548

4649

4750
private byte[] wavAsByteArray(short[] rawWavAudio) {
@@ -88,6 +91,7 @@ private void writeBitmapToJPEG(Bitmap bitmap) {
8891
}
8992
}
9093
}
94+
geotagJPEG();
9195
}
9296

9397
private void geotagJPEG() {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package uk.co.benjaminelliott.spectrogramandroid;
2+
3+
import android.util.Log;
4+
import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;
5+
6+
public class BandpassButterworth {
7+
8+
private final int sampleRate;
9+
private final int order;
10+
private final double minFreq;
11+
private final double maxFreq;
12+
private final double dcGain; //DC gain
13+
private DoubleFFT_1D dfft1d; //DoubleFFT_1D constructor must be supplied with an 'n' value, where n = data size
14+
15+
16+
BandpassButterworth(int sampleRate, int order, double minFreq, double maxFreq, double dcGain) {
17+
this.sampleRate = sampleRate;
18+
this.order = order;
19+
this.minFreq = minFreq;
20+
this.maxFreq = maxFreq;
21+
this.dcGain = dcGain;
22+
23+
}
24+
25+
private double[] generateGain(int length) {
26+
//find low-pass filter with cut-off at (maxFreq-minFreq)/2 then shift its
27+
//centre to (minFreq+maxFreq)/2
28+
double[] gain = new double[length];
29+
double cutoff = (maxFreq-minFreq); // by not dividing by 2, effectively multiply by 2 because of imaginary component for each bin
30+
int bins = length; //number of frequency bins
31+
double binWidth = ((double)sampleRate) / ((double)bins);
32+
double centre = (minFreq+maxFreq)/(2*binWidth);
33+
Log.d("","Bin width: "+binWidth+" bins: "+bins+" cutoff: "+cutoff+" centre: "+centre+" order: "+order+" length: "+length);
34+
for (int i = 0; i < length; i++) {
35+
gain[i] = dcGain/Math.sqrt((1+Math.pow((double)binWidth*((double)i-centre)/cutoff, 2.0*order)));
36+
}
37+
return gain;
38+
39+
}
40+
41+
42+
void applyBandpassFilter(short[] samples) {
43+
int length = samples.length;
44+
System.out.println();
45+
System.out.println();
46+
dfft1d = new DoubleFFT_1D(length);
47+
double[] fftSamples = new double[length*2];
48+
double[] gain = generateGain(length);
49+
int d = 0; while (true){for (double g : gain) {System.out.print(" "+g+" "); d++; if (d% 50 == 0) {System.out.println(); System.out.print(d+" ");}} break;}
50+
for (int i = 0; i < length; i++) fftSamples[i] = samples[i];
51+
dfft1d.realForward(fftSamples);
52+
for (int i = 0; i < length; i++) fftSamples[i] *= gain[i];
53+
dfft1d.realInverse(fftSamples, true);
54+
for (int i = 0; i < length; i++) samples[i] = (short)fftSamples[i];
55+
56+
}
57+
58+
59+
}

SpectrogramAndroid/src/uk/co/benjaminelliott/spectrogramandroid/BitmapGenerator.java

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ public class BitmapGenerator {
6565
private int lastBitmapRequested = 0; //keeps track of the most recently requested bitmap window
6666
private int oldestBitmapAvailable = 0;
6767
private Context context;
68+
private HammingWindow hammingWindow;
6869

6970
//allocate memory here rather than in performance-affecting methods:
7071
private int samplesRead = 0;
7172
private double[] fftSamples;
7273
private double[] previousWindow; //keep a handle on the previous audio sample window so that values can be averaged across them
7374
private double[] combinedWindow;
74-
private double[] hammingWindow;
7575
private DoubleFFT_1D dfft1d; //DoubleFFT_1D constructor must be supplied with an 'n' value, where n = data size
7676
private int val = 0;
7777

@@ -91,9 +91,9 @@ public BitmapGenerator(Context context) {
9191
fftSamples = new double[SAMPLES_PER_WINDOW*2];
9292
previousWindow = new double[SAMPLES_PER_WINDOW]; //keep a handle on the previous audio sample window so that values can be averaged across them
9393
combinedWindow = new double[SAMPLES_PER_WINDOW];
94-
hammingWindow = new double[SAMPLES_PER_WINDOW];
9594
dfft1d = new DoubleFFT_1D(SAMPLES_PER_WINDOW); //DoubleFFT_1D constructor must be supplied with an 'n' value, where n = data size
9695

96+
hammingWindow = new HammingWindow(SAMPLES_PER_WINDOW);
9797

9898
String colMapString = prefs.getString(PREF_COLOURMAP_KEY, "NULL");
9999
int colourMap = 0;
@@ -109,8 +109,6 @@ public BitmapGenerator(Context context) {
109109
case 6: colours = HeatMap.PuOr_Backwards_ColorBrewer(); break;
110110
}
111111

112-
generateHammingWindow();
113-
114112
float newContrast = prefs.getFloat(PREF_CONTRAST_KEY, Float.MAX_VALUE);
115113
if (newContrast != Float.MAX_VALUE) contrast = newContrast * 3.0f + 1.0f; //slider value must be between 0 and 1, so multiply by 3 and add 1
116114

@@ -234,7 +232,7 @@ private void processAudioWindow(short[] samples, int[] destArray) { //TODO prev
234232
for (int i = 0; i < SAMPLES_PER_WINDOW; i++) {
235233
fftSamples[i] = (double)(samples[i]);
236234
}
237-
applyHammingWindow(fftSamples); //apply Hamming window before performing STFT
235+
hammingWindow.applyHammingWindow(fftSamples); //apply Hamming window before performing STFT
238236
spectroTransform(fftSamples); //do the STFT on the copied data
239237

240238
for (int i = 0; i < SAMPLES_PER_WINDOW; i++) {
@@ -264,25 +262,6 @@ private int cappedValue(double d) {
264262
}
265263
return (int)(255*Math.pow((Math.log1p(d)/Math.log1p(maxAmplitude)),contrast));
266264
}
267-
268-
private void applyHammingWindow(double[] samples) {
269-
270-
//apply windowing function through multiplication with time-domain samples
271-
for (int i = 0; i < SAMPLES_PER_WINDOW; i++) {
272-
samples[i] *= hammingWindow[i];
273-
}
274-
}
275-
276-
private void generateHammingWindow() {
277-
/*
278-
* This method generates an appropriately-sized Hamming window to be used later.
279-
*/
280-
int m = SAMPLES_PER_WINDOW/2;
281-
double r = Math.PI/(m+1);
282-
for (int i = -m; i < m; i++) {
283-
hammingWindow[m + i] = 0.5 + 0.5 * Math.cos(i * r);
284-
}
285-
}
286265

287266
private void spectroTransform(double[] paddedSamples) {
288267
/*
@@ -367,8 +346,6 @@ protected Bitmap createEntireBitmap(int startWindow, int endWindow, int bottomFr
367346
startWindow %= WINDOW_LIMIT;
368347
endWindow %= WINDOW_LIMIT;
369348

370-
//TODO filter
371-
372349
Bitmap ret;
373350
Canvas retCanvas;
374351
int bitmapWidth;
@@ -482,13 +459,14 @@ public Bitmap scaleBitmap(Bitmap bitmapToScale, float newWidth, float newHeight)
482459
return Bitmap.createBitmap(bitmapToScale, 0, 0, bitmapToScale.getWidth(), bitmapToScale.getHeight(), matrix, true);
483460
}
484461

485-
protected short[] getAudioChunk(int startWindow, int endWindow) {
462+
protected short[] getAudioChunk(int startWindow, int endWindow, int bottomFreq, int topFreq) {
486463
/*
487464
* Returns an array of PCM audio data based on the window interval supplied to the function.
488465
*/
489466
//convert windows into array indices
490467
startWindow %= WINDOW_LIMIT;
491468
endWindow %= WINDOW_LIMIT;
469+
492470

493471
short[] toReturn;
494472

@@ -498,13 +476,13 @@ protected short[] getAudioChunk(int startWindow, int endWindow) {
498476
for (int i = startWindow; i < WINDOW_LIMIT; i++) {
499477
for (int j = 0; j < SAMPLES_PER_WINDOW; j++) {
500478
//Log.d("Audio chunk","i: "+i+", j: "+j+" i*SAMPLES_PER_WINDOW+j: "+(i*SAMPLES_PER_WINDOW+j));
501-
toReturn[(i-startWindow)*SAMPLES_PER_WINDOW+j] = Short.reverseBytes(audioWindows[i][j]); //must be little-endian for WAV
479+
toReturn[(i-startWindow)*SAMPLES_PER_WINDOW+j] = audioWindows[i][j];
502480
}
503481
}
504482
for (int i = 0; i < endWindow; i++) {
505483
for (int j = 0; j < SAMPLES_PER_WINDOW; j++) {
506484
//Log.d("Audio chunk","i: "+i+", j: "+j+" i*SAMPLES_PER_WINDOW+j: "+(i*SAMPLES_PER_WINDOW+j));
507-
toReturn[(WINDOW_LIMIT-startWindow+i)*SAMPLES_PER_WINDOW+j] = Short.reverseBytes(audioWindows[i][j]); //must be little-endian for WAV
485+
toReturn[(WINDOW_LIMIT-startWindow+i)*SAMPLES_PER_WINDOW+j] = audioWindows[i][j];
508486
}
509487
}
510488
}
@@ -514,10 +492,16 @@ protected short[] getAudioChunk(int startWindow, int endWindow) {
514492

515493
for (int j = 0; j < SAMPLES_PER_WINDOW; j++) {
516494
//Log.d("Audio chunk","i: "+i+", j: "+j+" i*SAMPLES_PER_WINDOW+j: "+(i*SAMPLES_PER_WINDOW+j));
517-
toReturn[(i-startWindow)*SAMPLES_PER_WINDOW+j] = Short.reverseBytes(audioWindows[i][j]); //must be little-endian for WAV
495+
toReturn[(i-startWindow)*SAMPLES_PER_WINDOW+j] = audioWindows[i][j];
518496
}
519497
}
520498
}
499+
500+
Log.d("","Filtering capture from "+bottomFreq+"Hz to "+topFreq+"Hz.");
501+
BandpassButterworth butter = new BandpassButterworth(SAMPLE_RATE, 4, (double)bottomFreq, (double)topFreq, 1.0);
502+
butter.applyBandpassFilter(toReturn);
503+
504+
for (int i = 0; i < toReturn.length; i++) toReturn[i] = Short.reverseBytes(toReturn[i]); //must be little-endian for WAV
521505
return toReturn;
522506
}
523507

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package uk.co.benjaminelliott.spectrogramandroid;
2+
3+
public class HammingWindow {
4+
5+
private final int windowSize;
6+
7+
private double[] hammingWindow;
8+
9+
HammingWindow(int windowSize) {
10+
this.windowSize = windowSize;
11+
hammingWindow = new double[windowSize];
12+
generateHammingWindow();
13+
}
14+
15+
16+
17+
private void generateHammingWindow() {
18+
/*
19+
* This method generates an appropriately-sized Hamming window to be used later.
20+
*/
21+
int m = windowSize/2;
22+
double r = Math.PI/(m+1);
23+
for (int i = -m; i < m; i++) {
24+
hammingWindow[m + i] = 0.5 + 0.5 * Math.cos(i * r);
25+
}
26+
}
27+
28+
void applyHammingWindow(double[] samples) {
29+
30+
//apply windowing function through multiplication with time-domain samples
31+
for (int i = 0; i < windowSize; i++) {
32+
samples[i] *= hammingWindow[i];
33+
}
34+
}
35+
36+
}

SpectrogramAndroid/src/uk/co/benjaminelliott/spectrogramandroid/LiveSpectrogramSurfaceView.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public void run() {
131131
sd.drawSelectRect(selectRectL,selectRectR,selectRectT,selectRectB);
132132
//moveCaptureButtonContainer();
133133
selectRectTextView.setVisibility(View.VISIBLE);
134-
//captureButtonContainer.setVisibility(View.VISIBLE); TODO removed only for user test!!
134+
captureButtonContainer.setVisibility(View.VISIBLE); //TODO removed only for user test!!
135135
selectionConfirmButton.setEnabled(true);
136136
selectionCancelButton.setEnabled(true);
137137
updateSelectRectText();
@@ -556,6 +556,7 @@ protected Void doInBackground(Void... arg0) {
556556
short[] audioToStore = sd.getAudioToStore(selectRectL, selectRectR, selectRectT, selectRectB);
557557
AudioBitmapConverter abc = new AudioBitmapConverter(filename, STORE_DIR_NAME, bitmapToStore,audioToStore,lc.getLastLocation(),sd.getBitmapGenerator().getSampleRate());
558558
abc.writeCBAToFile(filename, STORE_DIR_NAME);
559+
abc.storeJPEGandWAV();
559560
return null;
560561
}
561562

0 commit comments

Comments
 (0)