Merge branch 'audiorecorder-1.1.34'
This commit is contained in:
commit
6ba0d22ff9
28 changed files with 776 additions and 234 deletions
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
4
app/src/main/res/values/attrs.xml
Normal file
4
app/src/main/res/values/attrs.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="secondBackground" format="color" />
|
||||
</resources>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
80
docs/fft.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue