Merge branch 'audiorecorder-1.1.34'

This commit is contained in:
Alexey Kuznetsov 2016-04-02 16:20:17 +03:00
commit 6ba0d22ff9
28 changed files with 776 additions and 234 deletions

View file

@ -8,8 +8,8 @@ android {
applicationId "com.github.axet.audiorecorder"
minSdkVersion 16
targetSdkVersion 23
versionCode 54
versionName "1.1.33"
versionCode 55
versionName "1.1.34"
}
signingConfigs {
release {
@ -36,5 +36,6 @@ dependencies {
compile 'com.android.support:preference-v14:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'com.google.android.gms:play-services-appindexing:8.1.0'
compile 'org.apache.commons:commons-math3:3.6.1'
compile project(":android-library")
}

View file

@ -2,6 +2,9 @@ package com.github.axet.audiorecorder;
import android.app.Application;
import android.test.ApplicationTestCase;
import android.util.Log;
import com.github.axet.audiorecorder.app.RawSamples;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
@ -10,4 +13,7 @@ public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
public void testFFT() {
}
}

View file

@ -14,7 +14,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppThemeLight">
<service android:name=".services.RecordingService" />
<activity
android:name=".activities.SettingsActivity"
@ -23,7 +23,7 @@
android:name=".activities.MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/AppThemeLight.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View file

@ -12,6 +12,8 @@ import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import com.github.axet.audiorecorder.app.MainApplication;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
@ -22,6 +24,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(((MainApplication) getApplication()).getUserTheme());
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);

View file

@ -74,6 +74,7 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
Storage storage;
ListView list;
Handler handler;
PopupShareActionProvider shareProvider;
public static void startActivity(Context context) {
Intent i = new Intent(context, MainActivity.class);
@ -234,7 +235,7 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupShareActionProvider shareProvider = new PopupShareActionProvider(getContext(), share);
shareProvider = new PopupShareActionProvider(getContext(), share);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("audio/mp4a-latm");
@ -435,6 +436,9 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(((MainApplication) getApplication()).getMainTheme());
setContentView(R.layout.activity_main);
// ATTENTION: This was auto-generated to implement the App Indexing API.
@ -533,19 +537,10 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
updateHeader();
final int selected = getLastRecording();
list.setSelection(selected);
if (selected != -1) {
handler.post(new Runnable() {
@Override
public void run() {
recordings.select(selected);
}
});
list.setSelection(selected);
recordings.select(selected);
}
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
SharedPreferences.Editor edit = shared.edit();
edit.putString(MainApplication.PREFERENCE_LAST, "");
edit.commit();
}
int getLastRecording() {
@ -556,8 +551,12 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
for (int i = 0; i < recordings.getCount(); i++) {
File f = recordings.getItem(i);
String n = f.getName().toLowerCase();
if (n.equals(last))
if (n.equals(last)) {
SharedPreferences.Editor edit = shared.edit();
edit.putString(MainApplication.PREFERENCE_LAST, "");
edit.commit();
return i;
}
}
return -1;
}
@ -672,6 +671,6 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
long free = storage.getFree(f);
long sec = storage.average(free);
TextView text = (TextView) findViewById(R.id.space_left);
text.setText(((MainApplication)getApplication()).formatFree(free, sec));
text.setText(((MainApplication) getApplication()).formatFree(free, sec));
}
}

View file

@ -147,6 +147,9 @@ public class RecordingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(((MainApplication) getApplication()).getUserTheme());
setContentView(R.layout.activity_recording);
pitch = (PitchView) findViewById(R.id.recording_pitch);

View file

@ -6,6 +6,7 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.Ringtone;
@ -23,6 +24,7 @@ import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import android.widget.ListView;
import android.widget.Toast;
import com.github.axet.audiorecorder.R;
@ -41,7 +43,7 @@ import java.util.List;
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class SettingsActivity extends AppCompatPreferenceActivity {
public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
@ -120,8 +122,12 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(this);
shared.registerOnSharedPreferenceChangeListener(this);
getFragmentManager().beginTransaction().replace(android.R.id.content, new GeneralPreferenceFragment()).commit();
}
@ -195,6 +201,23 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return true;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals(MainApplication.PREFERENCE_THEME)) {
finish();
startActivity(new Intent(this, SettingsActivity.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(this);
shared.unregisterOnSharedPreferenceChangeListener(this);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
@ -226,6 +249,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
bindPreferenceSummaryToValue(findPreference(MainApplication.PREFERENCE_RATE));
bindPreferenceSummaryToValue(findPreference(MainApplication.PREFERENCE_THEME));
}
@Override

View file

@ -3,6 +3,7 @@ package com.github.axet.audiorecorder.animations;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.ListView;

View file

@ -1,9 +1,14 @@
package com.github.axet.audiorecorder.app;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.preference.PreferenceManager;
import android.util.Log;
import android.util.TypedValue;
import com.github.axet.androidlibrary.widgets.ThemeUtils;
import com.github.axet.audiorecorder.R;
public class MainApplication extends Application {
@ -13,12 +18,37 @@ public class MainApplication extends Application {
public static final String PREFERENCE_SILENT = "silence";
public static final String PREFERENCE_ENCODING = "encoding";
public static final String PREFERENCE_LAST = "last_recording";
public static final String PREFERENCE_THEME = "theme";
@Override
public void onCreate() {
super.onCreate();
PreferenceManager.setDefaultValues(this, R.xml.pref_general, false);
Context context = this;
context.setTheme(getUserTheme());
Log.d("123", "color " + Integer.toHexString(ThemeUtils.getThemeColor(context, android.R.attr.textColorSecondary)));
}
public int getUserTheme() {
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(this);
String theme = shared.getString(MainApplication.PREFERENCE_THEME, "");
if (theme.equals("Theme_Dark")) {
return R.style.AppThemeDark;
} else {
return R.style.AppThemeLight;
}
}
public int getMainTheme() {
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(this);
String theme = shared.getString(MainApplication.PREFERENCE_THEME, "");
if (theme.equals("Theme_Dark")) {
return R.style.AppThemeDark_NoActionBar;
} else {
return R.style.AppThemeLight_NoActionBar;
}
}
static public String formatTime(int tt) {

View file

@ -5,6 +5,12 @@ import android.util.Log;
import com.github.axet.audiorecorder.activities.RecordingActivity;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
import org.apache.commons.math3.util.MathArrays;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
@ -137,7 +143,7 @@ public class RawSamples {
public static double getDB(double amplitude) {
// https://en.wikipedia.org/wiki/Sound_pressure
return 20.0 * Math.log10(amplitude / 32768d);
return 20.0 * Math.log10(amplitude / 0x7FFF);
}
public void close() {

View file

@ -5,8 +5,11 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
@ -16,6 +19,7 @@ import android.widget.RemoteViews;
import com.github.axet.audiorecorder.R;
import com.github.axet.audiorecorder.activities.RecordingActivity;
import com.github.axet.audiorecorder.app.MainApplication;
/**
* RecordingActivity more likly to be removed from memory when paused then service. Notification button
@ -140,6 +144,19 @@ public class RecordingService extends Service {
view.setOnClickPendingIntent(R.id.notification_pause, pe);
view.setImageViewResource(R.id.notification_pause, !recording ? R.drawable.play : R.drawable.pause);
getBaseContext().setTheme(((MainApplication) getApplication()).getUserTheme());
view.apply(new ContextWrapper(getBaseContext()) {
public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
return new ContextWrapper(getBaseContext().createPackageContext(packageName, flags)) {
@Override
public Resources.Theme getTheme() {
return getBaseContext().getTheme();
}
};
}
}, null);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setOngoing(true)
.setContentTitle("Recording")

View file

@ -0,0 +1,105 @@
package com.github.axet.audiorecorder.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.github.axet.audiorecorder.app.RawSamples;
public class FFTBarView extends FFTView {
public static final String TAG = FFTBarView.class.getSimpleName();
int barCount;
float barWidth;
float barDeli;
public FFTBarView(Context context) {
this(context, null);
}
public FFTBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FFTBarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
super.create();
}
public void setBuffer(double[] buf) {
super.setBuffer(buf);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// set initial width
int w = dp2px(15);
int d = dp2px(4);
int s = w + d;
int mw = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
// get count of bars and delimeters
int dc = (mw - w) / s;
int bc = dc + 1;
// get rate
float k = w / d;
// get one part of (bar+del) size
float e = mw / (bc * k + dc);
barCount = bc;
barWidth = e * k;
barDeli = e;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
public void onDraw(Canvas canvas) {
if (barCount == 0)
return;
int h = getHeight() - getPaddingTop() - getPaddingBottom();
float left = getPaddingLeft();
for (int i = 0; i < barCount; i++) {
double max = 0;
if (buffer != null) {
int step = buffer.length / barCount;
int offset = i * step;
int end = Math.min(offset + step, buffer.length);
for (int k = offset; k < end; k++) {
double s = buffer[k];
max = Math.max(max, s);
}
}
float y = getPaddingTop() + h - h * ((float) max / 0x7fff) - dp2px(1);
if (y < getPaddingTop())
y = getPaddingTop();
canvas.drawRect(left, y, left + barWidth, getPaddingTop() + h, paint);
left += barWidth + barDeli;
}
}
}

View file

@ -0,0 +1,94 @@
package com.github.axet.audiorecorder.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.github.axet.audiorecorder.app.RawSamples;
public class FFTChartView extends FFTView {
public static final String TAG = FFTChartView.class.getSimpleName();
public FFTChartView(Context context) {
this(context, null);
}
public FFTChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FFTChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
super.create();
}
public void setBuffer(double[] buf) {
super.setBuffer(buf);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
public void onDraw(Canvas canvas) {
if (buffer == null)
return;
canvas.drawColor(Color.RED);
int h = getHeight();
float startX = 0, startY = h;
int w = getWidth() - getPaddingLeft() - getPaddingRight();
float step = w / (float) buffer.length;
double min = Integer.MAX_VALUE;
double max = Integer.MIN_VALUE;
for (int i = 0; i < buffer.length; i++) {
double v = buffer[i];
min = Math.min(v, min);
max = Math.max(v, max);
v = (RawSamples.MAXIMUM_DB + v) / RawSamples.MAXIMUM_DB;
float endX = startX;
float endY = (float) (h - h * v);
canvas.drawLine(startX, startY, endX, endY, paint);
startX = endX + step;
startY = endY;
}
String tMin = "" + min;
canvas.drawText(tMin, 0, getHeight(), textPaint);
String tMax = "" + max;
textPaint.getTextBounds(tMax, 0, tMax.length(), textBounds);
canvas.drawText("" + max, w - textBounds.width(), getHeight(), textPaint);
}
}

View file

@ -0,0 +1,151 @@
package com.github.axet.audiorecorder.widgets;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.github.axet.audiorecorder.app.RawSamples;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
public class FFTView extends View {
public static final String TAG = FFTView.class.getSimpleName();
Paint paint;
double[] buffer;
Paint textPaint;
Rect textBounds;
public FFTView(Context context) {
this(context, null);
}
public FFTView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FFTView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
paint = new Paint();
paint.setColor(0xff0433AE);
paint.setStrokeWidth(dp2px(1));
textBounds = new Rect();
textPaint = new Paint();
textPaint.setColor(Color.GRAY);
textPaint.setAntiAlias(true);
textPaint.setTextSize(20f);
if (isInEditMode()) {
short[] b = simple();
b = generateSound(16000, 4000, 100);
buffer = fft(b, 0, b.length);
//buffer = RawSamples.generateSound(16000, 4000, 100);
//buffer = RawSamples.fft(buffer, 0, buffer.length);
}
}
public void setBuffer(double[] buf) {
buffer = buf;
}
public static short[] generateSound(int sampleRate, int freqHz, int durationMs) {
int count = sampleRate * durationMs / 1000;
short[] samples = new short[count];
for (int i = 0; i < count; i++) {
short sample = (short) (Math.sin(2 * Math.PI * i / (sampleRate / freqHz)) * 0x7FFF);
samples[i] = sample;
}
return samples;
}
public static double[] asDouble(short[] buffer, int offset, int len) {
double[] dd = new double[len];
for (int i = 0; i < len; i++) {
dd[i] = buffer[offset + i] / (float) 0x7fff;
}
return dd;
}
public static double[] fft(short[] buffer, int offset, int len) {
int len2 = (int) Math.pow(2, Math.ceil(Math.log(len) / Math.log(2)));
final double[][] dataRI = new double[][]{
new double[len2], new double[len2]
};
double[] dataR = dataRI[0];
double[] dataI = dataRI[1];
double powerInput = 0;
for (int i = 0; i < len; i++) {
dataR[i] = buffer[offset + i] / (float) 0x7fff;
powerInput += dataR[i] * dataR[i];
}
powerInput = Math.sqrt(powerInput / len);
FastFourierTransformer.transformInPlace(dataRI, DftNormalization.STANDARD, TransformType.FORWARD);
double[] data = new double[len2 / 2];
data[0] = 10 * Math.log10(Math.pow(new Complex(dataR[0], dataI[0]).abs() / len2, 2));
double powerOutput = 0;
for (int i = 1; i < data.length; i++) {
Complex c = new Complex(dataR[i], dataI[i]);
double p = c.abs();
p = p / len2;
p = p * p;
p = p * 2;
double dB = 10 * Math.log10(p);
powerOutput += p;
data[i] = dB;
}
powerOutput = Math.sqrt(powerOutput);
// if(powerInput != powerOutput) {
// throw new RuntimeException("in " + powerInput + " out " + powerOutput);
// }
return data;
}
public static short[] simple() {
int sampleRate = 1000;
int count = sampleRate;
short[] samples = new short[count];
for (int i = 0; i < count; i++) {
double x = i / (double) count;
double y = 0;
//y += 0.6 * Math.sin(20 * 2 * Math.PI * x);
//y += 0.4 * Math.sin(50 * 2 * Math.PI * x);
//y += 0.2 * Math.sin(80 * 2 * Math.PI * x);
y += Math.sin(100 * 2 * Math.PI * x);
y += Math.sin(200 * 2 * Math.PI * x);
y += Math.sin(300 * 2 * Math.PI * x);
// max = 2.2;
samples[i] = (short) (y / 3 * 0x7fff);
}
return samples;
}
int dp2px(float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}

View file

@ -2,6 +2,7 @@ package com.github.axet.audiorecorder.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@ -15,6 +16,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.github.axet.androidlibrary.widgets.ThemeUtils;
import com.github.axet.audiorecorder.R;
import com.github.axet.audiorecorder.app.MainApplication;
import com.github.axet.audiorecorder.app.RawSamples;
import java.util.LinkedList;
@ -39,8 +43,6 @@ public class PitchView extends ViewGroup {
// in other words how many milliseconds do we need to show whole pitch.
int pitchTime;
Paint paint;
Paint paintRed;
List<Double> data = new LinkedList<>();
// how many pitches we can fit on screen
@ -75,12 +77,54 @@ public class PitchView extends ViewGroup {
Handler handler;
int pitchColor = 0xff0433AE;
Paint cutColor = new Paint();
public static class HandlerUpdate implements Runnable {
long start;
long updateSpeed;
Handler handler;
Runnable run;
public static HandlerUpdate start(Handler handler, Runnable run, long updateSpeed) {
HandlerUpdate r = new HandlerUpdate();
r.run = run;
r.start = System.currentTimeMillis();
r.updateSpeed = updateSpeed;
r.handler = handler;
// post instead of draw.run() so 'start' will measure actual queue time
handler.postDelayed(r, updateSpeed);
return r;
}
public static void stop(Handler handler, Runnable run) {
handler.removeCallbacks(run);
}
@Override
public void run() {
this.run.run();
long cur = System.currentTimeMillis();
long diff = cur - start;
start = cur;
long delay = updateSpeed + (updateSpeed - diff);
if (delay > updateSpeed)
delay = updateSpeed;
if (delay > 0)
this.handler.postDelayed(this, delay);
else
this.handler.post(this);
}
}
public class PitchGraphView extends View {
Paint paint;
Paint paintRed;
Paint editPaint;
Paint playPaint;
Paint cutColor;
public PitchGraphView(Context context) {
this(context, null);
@ -93,12 +137,24 @@ public class PitchView extends ViewGroup {
public PitchGraphView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setColor(getThemeColor(R.attr.colorPrimary));
paint.setStrokeWidth(pitchWidth);
paintRed = new Paint();
paintRed.setColor(Color.RED);
paintRed.setStrokeWidth(pitchWidth);
cutColor = new Paint();
cutColor.setColor(getThemeColor(android.R.attr.textColorHint));
cutColor.setStrokeWidth(pitchWidth);
editPaint = new Paint();
editPaint.setColor(Color.BLACK);
editPaint.setColor(getThemeColor(R.attr.colorPrimaryDark));
editPaint.setStrokeWidth(pitchWidth);
playPaint = new Paint();
playPaint.setColor(Color.BLUE);
playPaint.setColor(getThemeColor(R.attr.colorPrimaryDark));
playPaint.setStrokeWidth(pitchWidth / 2);
}
@ -141,8 +197,7 @@ public class PitchView extends ViewGroup {
tick = 0;
time = cur;
}
data.subList(0, 1).clear();
samples += 1;
fit(data.size() - 1);
}
offset = pitchSize * tick;
@ -192,7 +247,7 @@ public class PitchView extends ViewGroup {
}
// paint play mark
if (playPos != -1) {
if (playPos > 0) {
float x = playPos * pitchSize + pitchSize / 2f;
canvas.drawLine(x, 0, x, getHeight(), playPaint);
}
@ -205,6 +260,8 @@ public class PitchView extends ViewGroup {
String text;
Rect textBounds;
double dB;
public PitchCurrentView(Context context) {
this(context, null);
}
@ -225,19 +282,19 @@ public class PitchView extends ViewGroup {
textPaint.setTextSize(20f);
paint = new Paint();
paint.setColor(pitchColor);
paint.setColor(getThemeColor(R.attr.colorPrimary));
paint.setStrokeWidth(pitchWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = 0;
textPaint.getTextBounds(this.text, 0, this.text.length(), textBounds);
int h = getPaddingTop();
h += textBounds.height();
h += dp2px(2);
h += dp2px(pitchDlimiter) + getPaddingTop() + getPaddingBottom();
h += pitchWidth + getPaddingBottom();
setMeasuredDimension(w, h);
}
@ -252,20 +309,9 @@ public class PitchView extends ViewGroup {
super.onLayout(changed, left, top, right, bottom);
}
public int getEnd() {
int end = data.size() - 1;
void update(int end) {
dB = getDB(end) / RawSamples.MAXIMUM_DB;
if (editPos != -1) {
end = editPos;
}
if (playPos != -1) {
end = (int) playPos;
}
return end;
}
void updateText(int end) {
String str = "";
str = Integer.toString((int) getDB(end)) + " dB";
@ -276,29 +322,25 @@ public class PitchView extends ViewGroup {
@Override
public void onDraw(Canvas canvas) {
if (data.size() > 0) {
int end = getEnd();
updateText(end);
float y = getPaddingTop() + textBounds.height();
int x = getWidth() / 2 - textBounds.width() / 2;
canvas.drawText(text, x, y, textPaint);
y += dp2px(2);
double dB = getDB(end) / RawSamples.MAXIMUM_DB;
float left = (float) dB;
float right = (float) dB;
float mid = getWidth() / 2f;
y = y + dp2px(pitchDlimiter) / 2;
canvas.drawLine(mid, y, mid - mid * left - 1, y, paint);
canvas.drawLine(mid, y, mid + mid * right + 1, y, paint);
current.update(getEnd());
}
float y = getPaddingTop() + textBounds.height();
int x = getWidth() / 2 - textBounds.width() / 2;
canvas.drawText(text, x, y, textPaint);
y += dp2px(2);
float left = (float) dB;
float right = (float) dB;
float mid = getWidth() / 2f;
y += pitchWidth / 2;
canvas.drawLine(mid, y, mid - mid * left - 1, y, paint);
canvas.drawLine(mid, y, mid + mid * right + 1, y, paint);
}
}
@ -325,33 +367,42 @@ public class PitchView extends ViewGroup {
pitchTime = pitchSize * UPDATE_SPEED;
// bg = getThemeColor(android.R.attr.windowBackground);
cutColor.setColor(0xff0443BE); // getThemeColor(android.R.attr.textColorPrimaryDisableOnly));
graph = new PitchGraphView(getContext());
addView(graph);
// fft = new FFTChartView(getContext()) {
// @Override
// public void onDraw(Canvas canvas) {
// if (data.size() > 0) {
// short[] buf = dataSamples.get(getEnd());
// double[] d = FFTView.fft(buf, 0, buf.length);
// //double[] d = asDouble(buf, 0, buf.length);
// fft.setBuffer(d);
// }
//
// super.onDraw(canvas);
// }
// };
// fft.setPadding(0, dp2px(2), 0, 0);
// addView(fft);
current = new PitchCurrentView(getContext());
current.setPadding(0, dp2px(2), 0, 0);
addView(current);
if (isInEditMode()) {
for (int i = 0; i < 3000; i++) {
data.add((Math.random() * RawSamples.MAXIMUM_DB));
data.add(-Math.sin(i) * RawSamples.MAXIMUM_DB);
}
}
paint = new Paint();
paint.setColor(0xff0433AE);
paint.setStrokeWidth(pitchWidth);
paintRed = new Paint();
paintRed.setColor(Color.RED);
paintRed.setStrokeWidth(pitchWidth);
time = System.currentTimeMillis();
}
public int getThemeColor(int id) {
return ThemeUtils.getThemeColor(getContext(), id);
}
public int getMaxPitchCount(int width) {
int pitchScreenCount = width / pitchSize + 1;
@ -390,8 +441,7 @@ public class PitchView extends ViewGroup {
public void drawCalc() {
graph.calc();
graph.invalidate();
current.invalidate();
draw();
}
public void drawEnd() {
@ -400,6 +450,19 @@ public class PitchView extends ViewGroup {
draw();
}
public int getEnd() {
int end = data.size() - 1;
if (editPos != -1) {
end = editPos;
}
if (playPos > 0) {
end = (int) playPos;
}
return end;
}
public double getDB(int i) {
double db = data.get(i);
@ -433,56 +496,46 @@ public class PitchView extends ViewGroup {
return pitchTime;
}
int getThemeColor(int id) {
TypedValue typedValue = new TypedValue();
Context context = getContext();
Resources.Theme theme = context.getTheme();
if (theme.resolveAttribute(id, typedValue, true)) {
if (Build.VERSION.SDK_INT >= 23)
return context.getResources().getColor(typedValue.resourceId, theme);
else
return context.getResources().getColor(typedValue.resourceId);
} else {
return Color.TRANSPARENT;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
graph.measure(widthMeasureSpec, heightMeasureSpec);
current.measure(widthMeasureSpec, heightMeasureSpec);
int ww = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();
int hh = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
current.measure(MeasureSpec.makeMeasureSpec(ww, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(hh, MeasureSpec.AT_MOST));
hh = hh - current.getMeasuredHeight();
graph.measure(MeasureSpec.makeMeasureSpec(ww, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(hh, MeasureSpec.AT_MOST));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int gb = graph.getMeasuredHeight() - current.getMeasuredHeight();
graph.layout(0, 0, graph.getMeasuredWidth(), gb);
current.layout(0, gb, current.getMeasuredWidth(), gb + current.getMeasuredHeight());
graph.layout(getPaddingLeft(), getPaddingTop(),
getPaddingLeft() + graph.getMeasuredWidth(), getPaddingTop() + graph.getMeasuredHeight());
current.layout(getPaddingLeft(), graph.getBottom(),
getPaddingLeft() + current.getMeasuredWidth(), graph.getBottom() + current.getMeasuredHeight());
}
int dp2px(float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
graph.draw(canvas);
current.draw(canvas);
}
public void stop() {
if (edit != null)
handler.removeCallbacks(edit);
HandlerUpdate.stop(handler, edit);
edit = null;
if (draw != null)
handler.removeCallbacks(draw);
HandlerUpdate.stop(handler, draw);
draw = null;
if (play != null)
handler.removeCallbacks(play);
HandlerUpdate.stop(handler, play);
play = null;
draw();
@ -503,12 +556,12 @@ public class PitchView extends ViewGroup {
editPos = data.size() - 1;
if (draw != null) {
handler.removeCallbacks(draw);
HandlerUpdate.stop(handler, draw);
draw = null;
}
if (play != null) {
handler.removeCallbacks(play);
HandlerUpdate.stop(handler, play);
play = null;
}
@ -523,92 +576,45 @@ public class PitchView extends ViewGroup {
if (edit == null) {
editFlash = true;
edit = new Runnable() {
long start = System.currentTimeMillis();
edit = HandlerUpdate.start(handler, new Runnable() {
@Override
public void run() {
draw();
editFlash = !editFlash;
long cur = System.currentTimeMillis();
long diff = cur - start;
long delay = EDIT_UPDATE_SPEED + (EDIT_UPDATE_SPEED - diff);
if (delay > EDIT_UPDATE_SPEED)
delay = EDIT_UPDATE_SPEED;
start = cur;
if (delay > 0)
handler.postDelayed(edit, delay);
else
handler.post(edit);
}
};
// post instead of draw.run() so 'start' will measure actual queue time
handler.postDelayed(edit, EDIT_UPDATE_SPEED);
}, EDIT_UPDATE_SPEED);
}
}
public void record() {
if (edit != null)
handler.removeCallbacks(edit);
HandlerUpdate.stop(handler, edit);
edit = null;
editPos = -1;
if (play != null)
handler.removeCallbacks(play);
HandlerUpdate.stop(handler, play);
play = null;
playPos = -1;
if (draw == null) {
time = System.currentTimeMillis();
draw = new Runnable() {
long start = System.currentTimeMillis();
int stableCount = 0;
draw = HandlerUpdate.start(handler, new Runnable() {
@Override
public void run() {
drawCalc();
long cur = System.currentTimeMillis();
long diff = cur - start;
long delay = UPDATE_SPEED + (UPDATE_SPEED - diff);
if (delay > UPDATE_SPEED)
delay = UPDATE_SPEED;
start = cur;
if (delay > 0)
handler.postDelayed(draw, delay);
else
handler.post(draw);
}
};
// post instead of draw.run() so 'start' will measure actual queue time
handler.postDelayed(draw, UPDATE_SPEED);
}, UPDATE_SPEED);
}
}
// current paying pos in actual samples
public void play(float pos) {
playPos = pos - samples;
editFlash = true;
int max = data.size() - 1;
if (playPos < 0 || playPos > max)
if (pos < 0) {
playPos = -1;
if (playPos < 0) {
if (play != null) {
handler.removeCallbacks(play);
HandlerUpdate.stop(handler, play);
play = null;
}
if (edit == null) {
@ -617,40 +623,31 @@ public class PitchView extends ViewGroup {
return;
}
playPos = pos - samples;
editFlash = true;
int max = data.size() - 1;
if (playPos > max)
playPos = max;
if (edit != null)
handler.removeCallbacks(edit);
HandlerUpdate.stop(handler, edit);
edit = null;
if (draw != null)
handler.removeCallbacks(draw);
HandlerUpdate.stop(handler, draw);
draw = null;
if (play == null) {
time = System.currentTimeMillis();
play = new Runnable() {
long start = System.currentTimeMillis();
play = HandlerUpdate.start(handler, new Runnable() {
@Override
public void run() {
draw();
long cur = System.currentTimeMillis();
long diff = cur - start;
start = cur;
long delay = UPDATE_SPEED + (UPDATE_SPEED - diff);
if (delay > UPDATE_SPEED)
delay = UPDATE_SPEED;
if (delay > 0)
handler.postDelayed(play, delay);
else
handler.post(play);
}
};
// post instead of draw.run() so 'start' will measure actual queue time
handler.postDelayed(play, UPDATE_SPEED);
}, UPDATE_SPEED);
}
}
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorAccent" />
</shape>

View file

@ -10,14 +10,14 @@
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
android:theme="@style/AppThemeLight.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
app:popupTheme="@style/AppThemeLight.PopupOverlay" />
</android.support.design.widget.AppBarLayout>

View file

@ -38,7 +38,8 @@
android:id="@+id/recording_pitch"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_centerInParent="true" />
android:layout_centerInParent="true"
android:padding="5dp" />
<FrameLayout
android:layout_width="match_parent"
@ -51,28 +52,24 @@
android:layout_height="wrap_content"
android:paddingBottom="5dp">
<ImageButton
<com.github.axet.androidlibrary.widgets.RoundButton
android:id="@+id/recording_cut"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/round"
android:gravity="center"
android:src="@drawable/ic_content_cut_24dp"
android:text="Cancel" />
<ImageButton
<com.github.axet.androidlibrary.widgets.RoundButton
android:id="@+id/recording_play"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/round"
android:src="@drawable/play" />
<ImageButton
<com.github.axet.androidlibrary.widgets.RoundButton
android:id="@+id/recording_edit_done"
style="Widget.AppCompat.RoundButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/round"
android:src="@drawable/ic_close_24dp" />
</com.github.axet.androidlibrary.widgets.EqualLinearLayout>
</FrameLayout>
@ -96,28 +93,24 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
<com.github.axet.androidlibrary.widgets.RoundButton
android:id="@+id/recording_cancel"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/round"
android:gravity="center"
android:src="@drawable/ic_close_24dp"
android:text="Cancel" />
<ImageButton
<com.github.axet.androidlibrary.widgets.RoundButton
android:id="@+id/recording_pause"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/round"
android:src="@drawable/ic_pause_24dp" />
<ImageButton
<com.github.axet.androidlibrary.widgets.RoundButton
android:id="@+id/recording_done"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/round"
android:src="@drawable/ic_done" />
</com.github.axet.androidlibrary.widgets.EqualLinearLayout>

View file

@ -43,7 +43,7 @@
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:clipToPadding="false"
android:paddingBottom="61dp"></ListView>

View file

@ -3,13 +3,13 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="@android:color/white">
android:background="?android:windowBackground">
<ImageView
android:layout_width="@dimen/notification_large_icon_width"
android:layout_height="@dimen/notification_large_icon_height"
android:src="@drawable/ic_mic_24dp"
android:tint="@android:color/black" />
android:tint="?android:attr/colorForeground" />
<LinearLayout
android:id="@+id/notification_main_column"
@ -30,18 +30,17 @@
<TextView
android:id="@+id/notification_title"
style="@style/AlertDialog.AppCompat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Audio Recorder"
android:textColor="@android:color/black" />
android:textColor="?android:attr/colorForeground" />
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Audio Recorder"
android:textColor="@android:color/darker_gray"
android:textColor="?android:attr/textColorHint"
android:textSize="@dimen/notification_subtext_size" />
</LinearLayout>

View file

@ -70,8 +70,8 @@
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#dedede"
android:orientation="vertical"
android:background="?attr/secondBackground"
android:padding="5dp">
<LinearLayout
@ -81,6 +81,7 @@
android:orientation="horizontal">
<TextView
android:textColor="?android:textColorSecondary"
android:id="@+id/recording_player_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -113,21 +114,21 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/play"
android:tint="@color/colorAccent" />
android:tint="?attr/colorAccent" />
<ImageView
android:id="@+id/recording_player_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/share"
android:tint="@color/colorAccent" />
android:tint="?attr/colorAccent" />
<ImageView
android:id="@+id/recording_player_trash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/trash"
android:tint="@color/colorAccent" />
android:tint="?attr/colorAccent" />
</com.github.axet.androidlibrary.widgets.EqualLinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -1,6 +1,11 @@
<resources>>
<style name="AppTheme.NoActionBar">
<resources>
<style name="AppThemeLight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="AppThemeDark.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="secondBackground" format="color" />
</resources>

View file

@ -2,6 +2,4 @@
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="button_bg">#606060</color>
</resources>

View file

@ -21,16 +21,26 @@
<item>8000</item>
</string-array>
<string-array name="encodings" >
<string-array name="encodings">
<item>.wav (default)</item>
<item>.m4a</item>
</string-array>
<string-array name="encodings_values" >
<string-array name="encodings_values">
<item>wav</item>
<item>m4a</item>
</string-array>
<string-array name="themes">
<item>Theme White (default)</item>
<item>Theme Dark</item>
</string-array>
<string-array name="themes_values">
<item>Theme_White</item>
<item>Theme_Dark</item>
</string-array>
<string name="title_activity_main">Audio Recorder</string>
<string name="action_settings">Settings</string>

View file

@ -1,20 +1,31 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<style name="AppThemeLight" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccent">#FF4081</item>
<item name="secondBackground">#c2c2c2</item>
</style>
<style name="AppTheme.NoActionBar">
<style name="AppThemeLight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppThemeLight.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="AppThemeLight.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="AppThemeDark" parent="Theme.AppCompat">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">#a4a4a4</item>
<item name="secondBackground">#5a595b</item>
</style>
<style name="AppThemeDark.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>

View file

@ -1,4 +1,5 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.github.axet.androidlibrary.widgets.StoragePathPreference
android:defaultValue="Audio Recorder"
android:key="storage_path"
@ -37,4 +38,14 @@
android:summary="Put phone in 'silence mode' during recording"
android:title="Silence mode" />
<ListPreference
android:defaultValue="Theme_White"
android:entries="@array/themes"
android:entryValues="@array/themes_values"
android:key="theme"
android:negativeButtonText="@null"
android:positiveButtonText="@null"
android:summary="Set application theme (dark / light)"
android:title="Application Theme" />
</PreferenceScreen>

80
docs/fft.py Normal file
View file

@ -0,0 +1,80 @@
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
import random
#
# https://web.archive.org/web/20120615002031/http://www.mathworks.com/support/tech-notes/1700/1702.html
#
def noise(y, amp):
return y + amp * np.random.sample(len(y))
# Fe = sample rate
# N = samples count
def plot(Fe, N, x, y):
plt.subplot(2, 1, 1)
print "power wav = %s" % np.sqrt(np.mean(y**2))
plt.plot(x, y)
plt.subplot(2, 1, 2)
yf = scipy.fftpack.fft(y)
NumUniquePts = np.ceil((N+1)/2)
# Bin 0 contains the value for the DC component that the signal is riding on.
fftx = yf[1:NumUniquePts]
mx = np.abs(fftx)
mx = mx / N
mx = mx**2
if N % 2 > 0:
mx[2:len(mx)] = mx[2:len(mx)]*2
else:
mx[2:len(mx)-1] = mx[2:len(mx)-1]*2
print "power fft = %s" % np.sqrt(np.sum(mx))
end = Fe/2
start = end / (N/2)
xf = np.linspace(start, end, N/2 - 1)
mx = np.sqrt(mx)
plt.plot(xf, mx)
plt.show()
def simple(Fe):
N = Fe
x = np.linspace(0.0, 1.0, N)
y = np.zeros(len(x))
y += 0.9 * np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
y += 0.6 * np.sin(20.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
y += 0.2 * np.sin(80.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
#y = noise(y, 2)
plot(Fe, N, x, y)
def real_sound_weave(durationMs):
Fe = 16000
N = Fe * durationMs / 1000
x = np.linspace(0.0, N, N)
y = np.zeros(len(x))
y += np.sin(2.0 * np.pi * x / (Fe / float(4500)))
y += 0.5 * np.sin(2.0 * np.pi * x / (Fe / float(4000)))
y += 0.5 * np.sin(2.0 * np.pi * x / (Fe / float(1000)))
y += 0.9 * np.sin(2.0 * np.pi * x / (Fe / float(7500)))
y += 1 * np.sin(2.0 * np.pi * x / (Fe / float(3000)))
m = np.max(np.abs(y))
y = y * 0x7fff
# y = y / m
#y = noise(y, 0x7fff)
plot(Fe, N, x, y)
#simple(1000)
real_sound_weave(100)