diff options
author | Steve Kondik <shade@chemlab.org> | 2014-04-07 12:21:50 -0700 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2014-04-07 12:21:50 -0700 |
commit | 6ae11e2ccdb97317e29f2ebfa373e3fc744d7fcc (patch) | |
tree | ff3dba4ffa67cc6e1aec47b9992ff13966f9ec80 | |
parent | 40a438303affbc782368dc704da10329a79fb4c4 (diff) | |
download | android_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.xml | 2 | ||||
-rw-r--r-- | res/layout/equalizer.xml | 14 | ||||
-rw-r--r-- | res/layout/music_eq.xml | 29 | ||||
-rw-r--r-- | res/layout/music_main.xml | 2 | ||||
-rw-r--r-- | res/values/color.xml | 14 | ||||
-rw-r--r-- | res/values/dimens.xml | 4 | ||||
-rw-r--r-- | res/values/strings.xml | 2 | ||||
-rw-r--r-- | src/com/android/musicfx/ActivityMusic.java | 46 | ||||
-rw-r--r-- | src/com/android/musicfx/widget/Biquad.java | 31 | ||||
-rw-r--r-- | src/com/android/musicfx/widget/Complex.java | 94 | ||||
-rw-r--r-- | src/com/android/musicfx/widget/EqualizerSurface.java | 401 |
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; + } +} |