summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Kondik <shade@chemlab.org>2014-04-07 12:21:50 -0700
committerSteve Kondik <shade@chemlab.org>2014-04-07 12:21:50 -0700
commit6ae11e2ccdb97317e29f2ebfa373e3fc744d7fcc (patch)
treeff3dba4ffa67cc6e1aec47b9992ff13966f9ec80
parent40a438303affbc782368dc704da10329a79fb4c4 (diff)
downloadandroid_packages_apps_AudioFX-6ae11e2ccdb97317e29f2ebfa373e3fc744d7fcc.tar.gz
android_packages_apps_AudioFX-6ae11e2ccdb97317e29f2ebfa373e3fc744d7fcc.tar.bz2
android_packages_apps_AudioFX-6ae11e2ccdb97317e29f2ebfa373e3fc744d7fcc.zip
UX update, first pass.
-rw-r--r--res/layout-sw330dp/music_main.xml2
-rw-r--r--res/layout/equalizer.xml14
-rw-r--r--res/layout/music_eq.xml29
-rw-r--r--res/layout/music_main.xml2
-rw-r--r--res/values/color.xml14
-rw-r--r--res/values/dimens.xml4
-rw-r--r--res/values/strings.xml2
-rw-r--r--src/com/android/musicfx/ActivityMusic.java46
-rw-r--r--src/com/android/musicfx/widget/Biquad.java31
-rw-r--r--src/com/android/musicfx/widget/Complex.java94
-rw-r--r--src/com/android/musicfx/widget/EqualizerSurface.java401
11 files changed, 589 insertions, 50 deletions
diff --git a/res/layout-sw330dp/music_main.xml b/res/layout-sw330dp/music_main.xml
index 3585239..76d2bf0 100644
--- a/res/layout-sw330dp/music_main.xml
+++ b/res/layout-sw330dp/music_main.xml
@@ -42,7 +42,7 @@
android:layout_height="match_parent"
android:layout_marginTop="48dip">
- <include layout="@layout/music_eq"
+ <include layout="@layout/equalizer"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="26" />
diff --git a/res/layout/equalizer.xml b/res/layout/equalizer.xml
new file mode 100644
index 0000000..0ec8fe5
--- /dev/null
+++ b/res/layout/equalizer.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center">
+
+ <com.android.musicfx.widget.EqualizerSurface
+ android:id="@+id/frequencyResponse"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
+
+</LinearLayout>
diff --git a/res/layout/music_eq.xml b/res/layout/music_eq.xml
index a831f4e..a7a5f36 100644
--- a/res/layout/music_eq.xml
+++ b/res/layout/music_eq.xml
@@ -22,33 +22,4 @@
android:orientation="horizontal"
android:gravity="center_horizontal">
- <RelativeLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dip"
- android:layout_marginBottom="32dip"
- android:layout_marginStart="3dip">
-
- <TextView
- android:id="@+id/maxLevelText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:layout_alignParentTop="true"
- />
- <TextView
- android:id="@+id/centerLevelText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:layout_centerVertical="true"
- />
- <TextView
- android:id="@+id/minLevelText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:layout_alignParentBottom="true"
- />
- </RelativeLayout>
</LinearLayout>
diff --git a/res/layout/music_main.xml b/res/layout/music_main.xml
index 80dedca..b2bb4c5 100644
--- a/res/layout/music_main.xml
+++ b/res/layout/music_main.xml
@@ -42,7 +42,7 @@
android:layout_height="match_parent"
android:layout_marginTop="48dip">
- <include layout="@layout/music_eq"
+ <include layout="@layout/equalizer"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="26" />
diff --git a/res/values/color.xml b/res/values/color.xml
index 1bc53b4..a845c98 100644
--- a/res/values/color.xml
+++ b/res/values/color.xml
@@ -35,4 +35,18 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<color name="disabled">#525252</color>
<color name="disabled_gallery">#888888</color>
<color name="disabled_knob">#575757</color>
+
+ <color name="white">#90dedede</color>
+ <color name="black">#ff000000</color>
+ <color name="grid_lines">#22ffffff</color>
+ <color name="cb">#8880dede</color>
+ <color name="freq_hl">#2000ddff</color>
+ <color name="freq_hl2">#4033b5e5</color>
+ <color name="eq_red">#80ff0000</color>
+ <color name="eq_yellow">#80f0ff00</color>
+ <color name="eq_holo_bright">#8000ddff</color>
+ <color name="eq_holo_blue">#8033b5e5</color>
+ <color name="eq_holo_dark">#800099cc</color>
+ <color name="cb_shader">#ff88ffff</color>
+ <color name="cb_shader_alpha">#2288ffff</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 76cbbb8..ab9f0b8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -18,7 +18,11 @@
<dimen name="eq_slider_margin">5dip</dimen>
<dimen name="eq_slider_height">180dip</dimen>
<dimen name="eq_text_height">20dip</dimen>
+
<dimen name="action_bar_switch_padding">16dip</dimen>
<dimen name="action_bar_button_width">96dip</dimen>
<dimen name="action_bar_button_height">34dip</dimen>
+
+ <dimen name="eq_bar_width">20dp</dimen>
+ <dimen name="eq_label_text_size">9dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 86dbd32..5fe8d08 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -20,7 +20,7 @@
<!-- ControlPanelMusic strings -->
<string name="no_effects">Effects not available.</string>
<string name="main_toggle_effects_title">Audio effects</string>
- <string name="eq_dialog_title">Snapdragon Audio+</string>
+ <string name="eq_dialog_title">CyanogenAudio</string>
<string name="headset_plug">Plug in headphones for these effects.</string>
<string name="bass_boost_strength">Bass boost</string>
<string name="virtualizer_strength">Surround sound</string>
diff --git a/src/com/android/musicfx/ActivityMusic.java b/src/com/android/musicfx/ActivityMusic.java
index 268fb51..7189181 100644
--- a/src/com/android/musicfx/ActivityMusic.java
+++ b/src/com/android/musicfx/ActivityMusic.java
@@ -18,6 +18,7 @@ package com.android.musicfx;
import com.android.audiofx.OpenSLESConstants;
import com.android.musicfx.seekbar.SeekBar;
+import com.android.musicfx.widget.EqualizerSurface;
import com.android.musicfx.widget.Gallery;
import com.android.musicfx.widget.InterceptableLinearLayout;
import com.android.musicfx.widget.Knob;
@@ -99,9 +100,7 @@ public class ActivityMusic extends Activity {
private boolean mPresetReverbSupported;
// Equalizer fields
- private final Visualizer[] mEqualizerVisualizer = new Visualizer[EQUALIZER_MAX_BANDS];
private int mNumberEqualizerBands;
- private int mEqualizerMinBandLevel;
private int mEQPresetUserPos = 1;
private int mEQPreset;
private int[] mEQPresetUserBandLevelsPrev;
@@ -358,7 +357,7 @@ public class ActivityMusic extends Activity {
mEQPreset = 0;
}
equalizerPresetsInit((Gallery)findViewById(R.id.eqPresets));
- equalizerBandsInit((LinearLayout)findViewById(R.id.eqcontainer));
+ equalizerBandsInit();
}
// Initialize the Preset Reverb elements.
@@ -488,7 +487,6 @@ public class ActivityMusic extends Activity {
@Override
public void onItemSelected(int position) {
mEQPreset = position;
- showSeekBar(position == mEQPresetUserPos);
equalizerSetPreset(position);
}
});
@@ -507,7 +505,7 @@ public class ActivityMusic extends Activity {
final int count = viewGroup.getChildCount();
final View bb = findViewById(R.id.bBStrengthKnob);
final View virt = findViewById(R.id.vIStrengthKnob);
- final View eq = findViewById(R.id.eqcontainer);
+ final View eq = findViewById(R.id.frequencyResponse);
boolean on = true;
for (int i = 0; i < count; i++) {
@@ -526,7 +524,6 @@ public class ActivityMusic extends Activity {
mAudioSession, ControlPanelEffect.Key.bb_enabled);
view.setEnabled(on);
} else if (enabled && view == eq) {
- showSeekBar(mEQPreset == mEQPresetUserPos);
view.setEnabled(true);
} else {
view.setEnabled(enabled);
@@ -619,7 +616,7 @@ public class ActivityMusic extends Activity {
/**
* Initializes the equalizer elements. Set the SeekBars and Spinner listeners.
*/
- private void equalizerBandsInit(LinearLayout eqcontainer) {
+ private void equalizerBandsInit() {
// Initialize the N-Band Equalizer elements.
mNumberEqualizerBands = ControlPanelEffect.getParameterInt(mContext, mCallingPackageName,
mAudioSession, ControlPanelEffect.Key.eq_num_bands);
@@ -630,8 +627,27 @@ public class ActivityMusic extends Activity {
mCallingPackageName, mAudioSession, ControlPanelEffect.Key.eq_center_freq);
final int[] bandLevelRange = ControlPanelEffect.getParameterIntArray(mContext,
mCallingPackageName, mAudioSession, ControlPanelEffect.Key.eq_level_range);
- mEqualizerMinBandLevel = bandLevelRange[0];
- final int mEqualizerMaxBandLevel = bandLevelRange[1];
+ final EqualizerSurface eq = (EqualizerSurface)findViewById(R.id.frequencyResponse);
+ float[] centerFreqsKHz = new float[centerFreqs.length];
+ for (int i = 0; i < centerFreqs.length; i++) {
+ centerFreqsKHz[i] = (float)centerFreqs[i] / 1000.0f;
+ }
+ eq.setCenterFreqs(centerFreqsKHz);
+ eq.setBandLevelRange(bandLevelRange[0] / 100, bandLevelRange[1] / 100);
+
+ final EqualizerSurface.BandUpdatedListener listener = new EqualizerSurface.BandUpdatedListener() {
+ @Override
+ public void onBandUpdated(int band, float dB) {
+ if (mEQPreset != mEQPresetUserPos) {
+
+ }
+ equalizerBandUpdate(band, (int)(dB * 100));
+
+ }
+ };
+ eq.registerBandUpdatedListener(listener);
+
+ /*
final OnSeekBarChangeListener listener = new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(final Visualizer v, final int progress,
@@ -656,7 +672,6 @@ public class ActivityMusic extends Activity {
equalizerUpdateDisplay();
}
};
-
final OnTouchListener tl = new OnTouchListener() {
@Override
public boolean onTouch(final View v, final MotionEvent event) {
@@ -709,6 +724,7 @@ public class ActivityMusic extends Activity {
tv = (TextView) findViewById(R.id.minLevelText);
tv.setText("-15 dB");
equalizerUpdateDisplay();
+ */
}
private String format(String format, Object... args) {
@@ -717,12 +733,6 @@ public class ActivityMusic extends Activity {
return mFormatBuilder.toString();
}
- private void showSeekBar(boolean show) {
- for (int i = 0; i < mNumberEqualizerBands; ++i) {
- mEqualizerVisualizer[i].setShowSeekBar(show);
- }
- }
-
/**
* Updates the EQ by getting the parameters.
*/
@@ -730,10 +740,10 @@ public class ActivityMusic extends Activity {
// Update and show the active N-Band Equalizer bands.
final int[] bandLevels = ControlPanelEffect.getParameterIntArray(mContext,
mCallingPackageName, mAudioSession, ControlPanelEffect.Key.eq_band_level);
+ EqualizerSurface eq = (EqualizerSurface)findViewById(R.id.frequencyResponse);
for (short band = 0; band < mNumberEqualizerBands; band++) {
final int level = bandLevels[band];
- final int progress = level - mEqualizerMinBandLevel;
- mEqualizerVisualizer[band].setProgress(progress);
+ eq.setBand(band, (float)level / 100.0f);
}
}
diff --git a/src/com/android/musicfx/widget/Biquad.java b/src/com/android/musicfx/widget/Biquad.java
new file mode 100644
index 0000000..7241f8d
--- /dev/null
+++ b/src/com/android/musicfx/widget/Biquad.java
@@ -0,0 +1,31 @@
+package com.android.musicfx.widget;
+
+/**
+ * Evaluate transfer functions of biquad filters in direct form 1.
+ *
+ * @author alankila
+ */
+class Biquad {
+ private Complex mB0, mB1, mB2, mA0, mA1, mA2;
+
+ protected void setHighShelf(double centerFrequency, double samplingFrequency,
+ double dbGain, double slope) {
+ double w0 = 2 * Math.PI * centerFrequency / samplingFrequency;
+ double a = Math.pow(10, dbGain/40);
+ double alpha = Math.sin(w0) / 2 * Math.sqrt((a + 1 / a) * (1 / slope - 1) + 2);
+
+ mB0 = new Complex(a*((a+1) + (a-1) *Math.cos(w0) + 2*Math.sqrt(a)*alpha), 0);
+ mB1 = new Complex(-2*a*((a-1) + (a+1)*Math.cos(w0)), 0);
+ mB2 = new Complex(a*((a+1) + (a-1) *Math.cos(w0) - 2*Math.sqrt(a)*alpha), 0);
+ mA0 = new Complex((a+1) - (a-1) *Math.cos(w0) + 2*Math.sqrt(a)*alpha, 0);
+ mA1 = new Complex(2*((a-1) - (a+1) *Math.cos(w0)), 0);
+ mA2 = new Complex((a+1) - (a-1) *Math.cos(w0) - 2*Math.sqrt(a)*alpha, 0);
+ }
+
+ protected Complex evaluateTransfer(Complex z) {
+ Complex zSquared = z.mul(z);
+ Complex nom = mB0.add(mB1.div(z)).add(mB2.div(zSquared));
+ Complex den = mA0.add(mA1.div(z)).add(mA2.div(zSquared));
+ return nom.div(den);
+ }
+}
diff --git a/src/com/android/musicfx/widget/Complex.java b/src/com/android/musicfx/widget/Complex.java
new file mode 100644
index 0000000..5de96b5
--- /dev/null
+++ b/src/com/android/musicfx/widget/Complex.java
@@ -0,0 +1,94 @@
+package com.android.musicfx.widget;
+
+/**
+ * Java support for complex numbers.
+ *
+ * @author alankila
+ */
+class Complex {
+ private final double mReal, mIm;
+
+ protected Complex(double real, double im) {
+ mReal = real;
+ mIm = im;
+ }
+
+ /**
+ * Length of complex number
+ *
+ * @return length
+ */
+ protected double rho() {
+ return Math.sqrt(mReal * mReal + mIm * mIm);
+ }
+
+ /**
+ * Argument of complex number
+ *
+ * @return angle in radians
+ */
+ protected double theta() {
+ return Math.atan2(mIm, mReal);
+ }
+
+ /**
+ * Complex conjugate
+ *
+ * @return conjugate
+ */
+ protected Complex con() {
+ return new Complex(mReal, -mIm);
+ }
+
+ /**
+ * Complex addition
+ *
+ * @param other
+ * @return sum
+ */
+ protected Complex add(Complex other) {
+ return new Complex(mReal + other.mReal, mIm + other.mIm);
+ }
+
+ /**
+ * Complex multipply
+ *
+ * @param other
+ * @return multiplication result
+ */
+ protected Complex mul(Complex other) {
+ return new Complex(mReal * other.mReal - mIm * other.mIm,
+ mReal * other.mIm + mIm * other.mReal);
+ }
+
+ /**
+ * Complex multiply with real value
+ *
+ * @param a
+ * @return multiplication result
+ */
+ protected Complex mul(double a) {
+ return new Complex(mReal * a, mIm * a);
+ }
+
+ /**
+ * Complex division
+ *
+ * @param other
+ * @return division result
+ */
+ protected Complex div(Complex other) {
+ double lengthSquared = other.mReal * other.mReal + other.mIm * other.mIm;
+ return mul(other.con()).div(lengthSquared);
+ }
+
+ /**
+ * Complex division with real value
+ *
+ * @param a
+ * @return division result
+ */
+ protected Complex div(double a) {
+ return new Complex(mReal / a, mIm / a);
+ }
+}
diff --git a/src/com/android/musicfx/widget/EqualizerSurface.java b/src/com/android/musicfx/widget/EqualizerSurface.java
new file mode 100644
index 0000000..4ab25c7
--- /dev/null
+++ b/src/com/android/musicfx/widget/EqualizerSurface.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * - Original code by Antti S. Lankila for DSPManager
+ * - Modified extensively by cyanogen for multi-band support
+ */
+
+package com.android.musicfx.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Shader;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+
+import com.android.musicfx.R;
+import com.android.musicfx.widget.EqualizerSurface.BandUpdatedListener;
+
+import java.util.Arrays;
+
+public class EqualizerSurface extends SurfaceView {
+
+ private static int SAMPLING_RATE = 44100;
+
+ private int mWidth;
+ private int mHeight;
+
+ private float mMinFreq = 10;
+ private float mMaxFreq = 21000;
+
+ private float mMinDB = -15;
+ private float mMaxDB = 15;
+
+ private int mNumBands = 5;
+
+ private boolean mReadOnly = false;
+
+ private float[] mLevels = new float[mNumBands];
+ private float[] mCenterFreqs = new float[mNumBands];
+ private final Paint mWhite, mGridLines, mControlBarText, mControlBar;
+ private final Paint mFrequencyResponseBg;
+ private final Paint mFrequencyResponseHighlight, mFrequencyResponseHighlight2;
+
+ private BandUpdatedListener mBandUpdatedListener;
+
+ public EqualizerSurface(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ setWillNotDraw(false);
+
+ mWhite = new Paint();
+ mWhite.setColor(getResources().getColor(R.color.white));
+ mWhite.setStyle(Style.STROKE);
+ mWhite.setTextSize(context.getResources().getDimensionPixelSize(R.dimen.eq_label_text_size));
+ mWhite.setTypeface(Typeface.DEFAULT_BOLD);
+ mWhite.setAntiAlias(true);
+
+ mGridLines = new Paint();
+ mGridLines.setColor(getResources().getColor(R.color.grid_lines));
+ mGridLines.setStyle(Style.STROKE);
+
+ mControlBarText = new Paint(mWhite);
+ mControlBarText.setTextAlign(Paint.Align.CENTER);
+ mControlBarText.setShadowLayer(2, 0, 0, getResources().getColor(R.color.cb));
+
+ mControlBar = new Paint();
+ mControlBar.setStyle(Style.STROKE);
+ mControlBar.setColor(getResources().getColor(R.color.cb));
+ mControlBar.setAntiAlias(true);
+ mControlBar.setStrokeCap(Cap.ROUND);
+ mControlBar.setShadowLayer(2, 0, 0, getResources().getColor(R.color.black));
+
+ mFrequencyResponseBg = new Paint();
+ mFrequencyResponseBg.setStyle(Style.FILL);
+ mFrequencyResponseBg.setAntiAlias(true);
+
+ mFrequencyResponseHighlight = new Paint();
+ mFrequencyResponseHighlight.setStyle(Style.STROKE);
+ mFrequencyResponseHighlight.setStrokeWidth(6);
+ mFrequencyResponseHighlight.setColor(getResources().getColor(R.color.freq_hl));
+ mFrequencyResponseHighlight.setAntiAlias(true);
+
+ mFrequencyResponseHighlight2 = new Paint();
+ mFrequencyResponseHighlight2.setStyle(Style.STROKE);
+ mFrequencyResponseHighlight2.setStrokeWidth(3);
+ mFrequencyResponseHighlight2.setColor(getResources().getColor(R.color.freq_hl2));
+ mFrequencyResponseHighlight2.setAntiAlias(true);
+ }
+
+ /**
+ * Listener for bands being modified via touch events
+ *
+ * Invoked with the index of the modified band, and the
+ * new value in dB.
+ */
+ public interface BandUpdatedListener {
+ public void onBandUpdated(int band, float dB);
+ }
+
+ public void setBandLevelRange(float minDB, float maxDB) {
+ mMinDB = minDB;
+ mMaxDB = maxDB;
+ }
+
+ public void setCenterFreqs(float[] centerFreqsKHz) {
+ mNumBands = centerFreqsKHz.length;
+ mLevels = new float[mNumBands];
+ mCenterFreqs = Arrays.copyOf(centerFreqsKHz, mNumBands);
+ System.arraycopy(centerFreqsKHz, 0, mCenterFreqs, 0, mNumBands);
+ mMinFreq = mCenterFreqs[0] / 2;
+ mMaxFreq = (float) Math.pow(mCenterFreqs[mNumBands - 1], 2) / mCenterFreqs[mNumBands -2] / 2;
+ }
+
+ /*
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle b = new Bundle();
+ b.putParcelable("super", super.onSaveInstanceState());
+ b.putFloatArray("levels", mLevels);
+ return b;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable p) {
+ Bundle b = (Bundle) p;
+ super.onRestoreInstanceState(b.getBundle("super"));
+ mLevels = b.getFloatArray("levels");
+ }
+ */
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ buildLayer();
+ }
+
+ /**
+ * Returns a color that is assumed to be blended against black background,
+ * assuming close to sRGB behavior of screen (gamma 2.2 approximation).
+ *
+ * @param intensity desired physical intensity of color component
+ * @param alpha alpha value of color component
+ */
+ private static int gamma(float intensity, float alpha) {
+ /* intensity = (component * alpha)^2.2
+ * <=>
+ * intensity^(1/2.2) / alpha = component
+ */
+
+ double gamma = Math.round(255 * Math.pow(intensity, 1 / 2.2) / alpha);
+ return (int) Math.min(255, Math.max(0, gamma));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ final Resources res = getResources();
+ mWidth = right - left;
+ mHeight = bottom - top;
+
+ float barWidth = res.getDimensionPixelSize(R.dimen.eq_bar_width);
+ mControlBar.setStrokeWidth(barWidth);
+
+ /**
+ * red > +7
+ * yellow > +3
+ * holo_blue_bright > 0
+ * holo_blue < 0
+ * holo_blue_dark < 3
+ */
+ int[] responseColors = new int[] {
+ res.getColor(R.color.eq_red),
+ res.getColor(R.color.eq_yellow),
+ res.getColor(R.color.eq_holo_bright),
+ res.getColor(R.color.eq_holo_blue),
+ res.getColor(R.color.eq_holo_dark)
+ };
+ float[] responsePositions = new float[] {
+ 0, 0.2f, 0.45f, 0.6f, 1f
+ };
+
+ mFrequencyResponseBg.setShader(new LinearGradient(0, 0, 0, mHeight,
+ responseColors, responsePositions, Shader.TileMode.CLAMP));
+
+ int[] barColors = new int[] {
+ res.getColor(R.color.cb_shader),
+ res.getColor(R.color.cb_shader_alpha)
+ };
+ float[] barPositions = new float[] {
+ 0, 1
+ };
+
+ mControlBar.setShader(new LinearGradient(0, 0, 0, mHeight,
+ barColors, barPositions, Shader.TileMode.CLAMP));
+ }
+
+ public void setBand(int i, float value) {
+ mLevels[i] = value;
+ postInvalidate();
+ }
+
+ public float getBand(int i) {
+ return mLevels[i];
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ /* clear canvas */
+ canvas.drawRGB(0, 0, 0);
+
+ Biquad[] biquads = new Biquad[mNumBands - 1];
+ for (int i = 0; i < (mNumBands - 1); i++) {
+ biquads[i] = new Biquad();
+ }
+
+ /* The filtering is realized with 2nd order high shelf filters, and each band
+ * is realized as a transition relative to the previous band. The center point for
+ * each filter is actually between the bands.
+ *
+ * 1st band has no previous band, so it's just a fixed gain.
+ */
+ double gain = Math.pow(10, mLevels[0] / 20);
+ for (int i = 0; i < biquads.length; i++) {
+ biquads[i].setHighShelf(mCenterFreqs[i], SAMPLING_RATE, mLevels[i + 1] - mLevels[i], 1);
+ }
+
+ Path freqResponse = new Path();
+ Complex[] zn = new Complex[biquads.length];
+ for (int i = 0; i < 71; i ++) {
+ double freq = reverseProjectX(i / 70f);
+ double omega = freq / SAMPLING_RATE * Math.PI * 2;
+ Complex z = new Complex(Math.cos(omega), Math.sin(omega));
+
+ /* Evaluate the response at frequency z */
+ /* Complex z1 = z.mul(gain); */
+ double lin = gain;
+ for (int j = 0; j < biquads.length; j++) {
+ zn[j] = biquads[j].evaluateTransfer(z);
+ lin *= zn[j].rho();
+ }
+
+ /* Magnitude response, dB */
+ double dB = lin2dB(lin);
+ float x = projectX(freq) * mWidth;
+ float y = projectY(dB) * mHeight;
+
+ /* Set starting point at first point */
+ if (i == 0) {
+ freqResponse.moveTo(x, y);
+ } else {
+ freqResponse.lineTo(x, y);
+ }
+ }
+
+ Path freqResponseBg = new Path();
+ freqResponseBg.addPath(freqResponse);
+ freqResponseBg.offset(0, -4);
+ freqResponseBg.lineTo(mWidth, mHeight);
+ freqResponseBg.lineTo(0, mHeight);
+ freqResponseBg.close();
+ canvas.drawPath(freqResponseBg, mFrequencyResponseBg);
+
+ canvas.drawPath(freqResponse, mFrequencyResponseHighlight);
+ canvas.drawPath(freqResponse, mFrequencyResponseHighlight2);
+
+ /* draw vertical lines */
+ for (float freq = mMinFreq; freq < mMaxFreq;) {
+ float x = projectX(freq) * mWidth;
+ canvas.drawLine(x, 0, x, mHeight - 1, mGridLines);
+ if (freq < 100) {
+ freq += 10;
+ } else if (freq < 1000) {
+ freq += 100;
+ } else if (freq < 10000) {
+ freq += 1000;
+ } else {
+ freq += 10000;
+ }
+ }
+
+ /* draw horizontal lines */
+ for (float dB = mMinDB + 3; dB <= mMaxDB - 3; dB += 3) {
+ float y = projectY(dB) * mHeight;
+ canvas.drawLine(0, y, mWidth - 1, y, mGridLines);
+ canvas.drawText(String.format("%+d", (int)dB), 1, (y - 1), mWhite);
+ }
+
+ for (int i = 0; i < mNumBands; i ++) {
+ float freq = mCenterFreqs[i];
+ float x = projectX(freq) * mWidth;
+ float y = projectY(mLevels[i]) * mHeight;
+ String frequencyText = String.format(freq < 1000 ? "%.0f" : "%.0fk",
+ freq < 1000 ? freq : freq / 1000);
+
+ canvas.drawLine(x, mHeight, x, y, mControlBar);
+ canvas.drawText(String.format("%+1.1f", mLevels[i]), x, mHeight - 2, mControlBarText);
+ canvas.drawText(frequencyText, x, mWhite.getTextSize(), mControlBarText);
+ }
+ }
+
+ public void registerBandUpdatedListener(BandUpdatedListener listener) {
+ mBandUpdatedListener = listener;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+
+ if (mReadOnly)
+ return false;
+
+ float x = event.getX();
+ float y = event.getY();
+
+ /* Which band is closest to the position user pressed? */
+ int band = findClosest(x);
+
+ int wy = getHeight();
+ float level = (y / wy) * (mMinDB - mMaxDB) - mMinDB;
+ if (level < mMinDB) {
+ level = mMinDB;
+ } else if (level > mMaxDB) {
+ level = mMaxDB;
+ }
+
+ setBand(band, level);
+
+ if (mBandUpdatedListener != null) {
+ mBandUpdatedListener.onBandUpdated(band, level);
+ }
+
+ return true;
+ }
+
+ private float projectX(double freq) {
+ double pos = Math.log(freq);
+ double minPos = Math.log(mMinFreq);
+ double maxPos = Math.log(mMaxFreq);
+ return (float) ((pos - minPos) / (maxPos - minPos));
+ }
+
+ private double reverseProjectX(float pos) {
+ double minPos = Math.log(mMinFreq);
+ double maxPos = Math.log(mMaxFreq);
+ return Math.exp(pos * (maxPos - minPos) + minPos);
+ }
+
+ private float projectY(double dB) {
+ double pos = (dB - mMinDB) / (mMaxDB - mMinDB);
+ return (float) (1 - pos);
+ }
+
+ private double lin2dB(double rho) {
+ return rho != 0 ? Math.log(rho) / Math.log(10) * 20 : -99.9;
+ }
+
+ /**
+ * Find the closest control to given horizontal pixel for adjustment
+ *
+ * @param px
+ * @return index of best match
+ */
+ public int findClosest(float px) {
+ int idx = 0;
+ float best = 1e9f;
+ for (int i = 0; i < mNumBands; i ++) {
+ float freq = mCenterFreqs[i];
+ float cx = projectX(freq) * mWidth;
+ float distance = Math.abs(cx - px);
+
+ if (distance < best) {
+ idx = i;
+ best = distance;
+ }
+ }
+
+ return idx;
+ }
+}