Commit 8274981a authored by linus_lee's avatar linus_lee
Browse files

Eleven: Add equalizer visualization

Change-Id: I9a3112cf4138e916ed53571236e54b67c30b53c4
parent a81b94d5
......@@ -9,6 +9,7 @@ LOCAL_SRC_FILES += $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v8-renderscript \
android-common \
android-visualizer \
eleven_support_v4 \
eleven_recyclerview
......
......@@ -44,6 +44,10 @@
<!-- Allows Apollo to read from External Storage -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- Audio Visualizer Permissions -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application
android:name=".ElevenApplication"
android:allowBackup="true"
......
......@@ -32,6 +32,23 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/loading_empty_container" />
<View
android:id="@+id/equalizerGradient"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/equalizer_background"
android:layout_gravity="bottom"/>
<com.cyngn.eleven.widgets.EqualizerView
android:id="@+id/equalizerView"
android:gravity="bottom"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:visibility="visible" />
</com.cyngn.eleven.widgets.SquareFrame>
<RelativeLayout
......
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-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.
-->
<resources>
<!-- Height of each filled in block in each eq bar -->
<dimen name="eqalizer_path_effect_1">8dp</dimen>
<!-- Height of each empty block in each eq bar -->
<dimen name="eqalizer_path_effect_2">1dp</dimen>
<!-- Width of each eq bar -->
<dimen name="eqalizer_path_stroke_width">22dp</dimen>
<!-- The amount of divisions to make for eq bars -->
<integer name="equalizer_divisions">12</integer>
<!-- fudge factors to tweak display for various configs
ends up being dB = ((dB * fuzz_factor) + db_fuzz) -->
<integer name="equalizer_db_fuzz_factor">15</integer>
<integer name="equalizer_db_fuzz">0</integer>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-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.
-->
<resources>
<!-- Height of each filled in block in each eq bar -->
<dimen name="eqalizer_path_effect_1">8dp</dimen>
<!-- Height of each empty block in each eq bar -->
<dimen name="eqalizer_path_effect_2">1dp</dimen>
<!-- Width of each eq bar -->
<dimen name="eqalizer_path_stroke_width">20dp</dimen>
<!-- The amount of divisions to make for eq bars -->
<integer name="equalizer_divisions">16</integer>
<!-- fudge factors to tweak display for various configs
ends up being dB = ((dB * fuzz_factor) + db_fuzz) -->
<integer name="equalizer_db_fuzz_factor">35</integer>
<integer name="equalizer_db_fuzz">1</integer>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012-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.
-->
<resources>
<!-- Height of each filled in block in each eq bar -->
<dimen name="eqalizer_path_effect_1">8dp</dimen>
<!-- Height of each empty block in each eq bar -->
<dimen name="eqalizer_path_effect_2">1dp</dimen>
<!-- Width of each eq bar -->
<dimen name="eqalizer_path_stroke_width">20dp</dimen>
<!-- Color for the Equalizer tile -->
<color name="equalizer_fill_color">#32ffffff</color>
<!-- The amount of divisions to make for eq bars -->
<integer name="equalizer_divisions">16</integer>
<!-- fudge factors to tweak display for various configs
ends up being dB = (dB * fuzz_factor + db_fuzz) -->
<integer name="equalizer_db_fuzz_factor">0</integer>
<integer name="equalizer_db_fuzz">0</integer>
</resources>
\ No newline at end of file
......@@ -136,6 +136,8 @@
<string name="settings_download_only_on_wifi_summary">To reduce carrier charges, don\'t download over mobile networks</string>
<string name="settings_download_missing_artwork_title">Download missing album art</string>
<string name="settings_download_artist_images_title">Download missing artist images</string>
<string name="settings_general_category">General</string>
<string name="settings_show_music_visualization_title">Show music visualization</string>
<!-- App widget -->
<string name="app_widget_small">Music: 4 \u00d7 1</string>
......
......@@ -36,6 +36,14 @@
<!--android:key="download_missing_artist_images"-->
<!--android:title="@string/settings_download_artist_images_title" />-->
<!--</PreferenceCategory>-->
<PreferenceCategory android:title="@string/settings_general_category" >
<!-- Music visualizer -->
<CheckBoxPreference
android:defaultValue="true"
android:key="music_visualization"
android:title="@string/settings_show_music_visualization_title" />
</PreferenceCategory>
<!-- Storage catetory -->
<PreferenceCategory android:title="@string/settings_storage_category" >
......
......@@ -28,6 +28,8 @@ import com.cyngn.eleven.utils.ApolloUtils;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.widgets.BlurScrimImage;
import java.util.HashSet;
/**
* This class is used to display the {@link ViewPager} used to swipe between the
* main {@link Fragment}s used to browse the user's music.
......@@ -43,9 +45,16 @@ public abstract class SlidingPanelActivity extends BaseActivity {
None,
}
public static interface ISlidingPanelListener {
public void onBeginSlide();
public void onFinishSlide(SlidingPanelActivity.Panel visiblePanel);
}
private SlidingUpPanelLayout mFirstPanel;
private SlidingUpPanelLayout mSecondPanel;
protected Panel mTargetNavigatePanel;
private HashSet<ISlidingPanelListener> mSlidingPanelListeners
= new HashSet<ISlidingPanelListener>();
private final ShowPanelClickListener mShowBrowse = new ShowPanelClickListener(Panel.Browse);
private final ShowPanelClickListener mShowMusicPlayer = new ShowPanelClickListener(Panel.MusicPlayer);
......@@ -107,6 +116,8 @@ public abstract class SlidingPanelActivity extends BaseActivity {
} else if (slideOffset < 0.75f) {
getActionBar().show();
}
onSlide();
}
@Override
......@@ -131,6 +142,8 @@ public abstract class SlidingPanelActivity extends BaseActivity {
if (mTargetNavigatePanel == Panel.None) {
mFirstPanel.setSlidingEnabled(false);
}
onSlide();
}
@Override
......@@ -213,6 +226,11 @@ public abstract class SlidingPanelActivity extends BaseActivity {
}
public void showPanel(Panel panel) {
// if we are already at our target panel, then don't do anything
if (panel == getCurrentPanel()) {
return;
}
// TODO: Add ability to do this instantaneously as opposed to animate
switch (panel) {
case Browse:
......@@ -236,13 +254,28 @@ public abstract class SlidingPanelActivity extends BaseActivity {
}
}
protected void onSlide() {
for (ISlidingPanelListener listener : mSlidingPanelListeners) {
listener.onBeginSlide();
}
}
/**
* This checks if we are at our target panel and resets our flag if we are there
*/
protected void checkTargetNavigation() {
if (mTargetNavigatePanel == getCurrentPanel()) {
final Panel currentPanel = getCurrentPanel();
// This checks if we are at our target panel and resets our flag if we are there
if (mTargetNavigatePanel == currentPanel) {
mTargetNavigatePanel = Panel.None;
}
// if we are at the target panel
if (mTargetNavigatePanel == Panel.None) {
for (ISlidingPanelListener listener : mSlidingPanelListeners) {
listener.onFinishSlide(currentPanel);
}
}
}
protected Panel getCurrentPanel() {
......@@ -309,4 +342,12 @@ public abstract class SlidingPanelActivity extends BaseActivity {
showPanel(mTargetPanel);
}
}
public void addSlidingPanelListener(final ISlidingPanelListener listener) {
mSlidingPanelListeners.add(listener);
}
public void removeSlidingPanelListener(final ISlidingPanelListener listener) {
mSlidingPanelListeners.remove(listener);
}
}
......@@ -10,14 +10,10 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.provider.MediaStore.Audio.Albums;
import android.provider.MediaStore.Audio.Artists;
import android.provider.MediaStore.Audio.Playlists;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.util.Log;
......@@ -41,10 +37,13 @@ import com.cyngn.eleven.loaders.QueueLoader;
import com.cyngn.eleven.menu.CreateNewPlaylist;
import com.cyngn.eleven.menu.DeleteDialog;
import com.cyngn.eleven.menu.FragmentMenuItems;
import com.cyngn.eleven.ui.activities.SlidingPanelActivity;
import com.cyngn.eleven.utils.ApolloUtils;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.utils.NavUtils;
import com.cyngn.eleven.utils.PreferenceUtils;
import com.cyngn.eleven.widgets.BrowseButton;
import com.cyngn.eleven.widgets.EqualizerView;
import com.cyngn.eleven.widgets.LoadingEmptyContainer;
import com.cyngn.eleven.widgets.NoResultsContainer;
import com.cyngn.eleven.widgets.PlayPauseProgressButton;
......@@ -58,7 +57,8 @@ import java.lang.ref.WeakReference;
import static com.cyngn.eleven.utils.MusicUtils.mService;
public class AudioPlayerFragment extends Fragment implements ServiceConnection {
public class AudioPlayerFragment extends Fragment implements ServiceConnection,
SlidingPanelActivity.ISlidingPanelListener {
private static final String TAG = AudioPlayerFragment.class.getSimpleName();
/**
......@@ -115,6 +115,12 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
// Total time
private TextView mTotalTime;
// Equalizer View
private EqualizerView mEqualizerView;
// Equalizer Gradient
private View mEqualizerGradient;
// Broadcast receiver
private PlaybackStatus mPlaybackStatus;
......@@ -151,6 +157,9 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
// Initialize the broadcast receiver
mPlaybackStatus = new PlaybackStatus(this);
// add a listener for the sliding
((SlidingPanelActivity)getActivity()).addSlidingPanelListener(this);
}
/**
......@@ -166,6 +175,11 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
initHeaderBar();
initPlaybackControls();
mEqualizerView = (EqualizerView) mRootView.findViewById(R.id.equalizerView);
mEqualizerView.initialize(getActivity());
mEqualizerGradient = mRootView.findViewById(R.id.equalizerGradient);
return mRootView;
}
......@@ -210,6 +224,8 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
// resumes the update callback for the play pause progress button
mPlayPauseProgressButton.resume();
mEqualizerView.onStart();
}
@Override
......@@ -220,6 +236,8 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
mPlayPauseProgressButton.pause();
mImageFetcher.flush();
mEqualizerView.onStop();
}
@Override
......@@ -234,6 +252,8 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
mToken = null;
}
((SlidingPanelActivity)getActivity()).removeSlidingPanelListener(this);
// Unregister the receiver
try {
getActivity().unregisterReceiver(mPlaybackStatus);
......@@ -423,10 +443,18 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
if(queueSize == 0) {
mAlbumArtViewPager.setVisibility(View.GONE);
mQueueEmpty.showNoResults();
mEqualizerGradient.setVisibility(View.GONE);
mEqualizerView.checkStateChanged();
mAddToPlaylistButton.setVisibility(View.GONE);
} else {
mAlbumArtViewPager.setVisibility(View.VISIBLE);
mQueueEmpty.hideAll();
if (PreferenceUtils.getInstance(getActivity()).getShowVisualizer()) {
mEqualizerGradient.setVisibility(View.VISIBLE);
} else {
mEqualizerGradient.setVisibility(View.GONE);
}
mEqualizerView.checkStateChanged();
mAddToPlaylistButton.setVisibility(View.VISIBLE);
}
}
......@@ -657,6 +685,18 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection {
return super.onContextItemSelected(item);
}
@Override
public void onBeginSlide() {
mEqualizerView.setPanelVisible(false);
}
@Override
public void onFinishSlide(SlidingPanelActivity.Panel visiblePanel) {
if (visiblePanel == SlidingPanelActivity.Panel.MusicPlayer) {
mEqualizerView.setPanelVisible(true);
}
}
/**
* Used to update the current time string
*/
......
......@@ -69,6 +69,9 @@ public final class PreferenceUtils {
// datetime cutoff for determining which songs go in last added playlist
public static final String LAST_ADDED_CUTOFF = "last_added_cutoff";
// show visualizer flag
public static final String SHOW_VISUALIZER = "music_visualization";
private static PreferenceUtils sInstance;
private final SharedPreferences mPreferences;
......@@ -303,4 +306,8 @@ public final class PreferenceUtils {
public long getLastAddedCutoff() {
return mPreferences.getLong(LAST_ADDED_CUTOFF, 0L);
}
public boolean getShowVisualizer() {
return mPreferences.getBoolean(SHOW_VISUALIZER, true);
}
}
/*
* Copyright (C) 2014 Cyanogen, Inc.
*/
package com.cyngn.eleven.widgets;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import com.cyngn.eleven.R;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.utils.PreferenceUtils;
import com.pheelicks.visualizer.AudioData;
import com.pheelicks.visualizer.FFTData;
import com.pheelicks.visualizer.VisualizerView;
import com.pheelicks.visualizer.renderer.Renderer;
public class EqualizerView extends VisualizerView {
private boolean mLinked = false;
private boolean mStarted = false;
private boolean mPanelVisible = false;
private final Runnable mLinkVisualizer = new Runnable() {
@Override
public void run() {
if (!mLinked) {
animate().alpha(1).setDuration(300);
link(0);
mLinked = true;
}
}
};
private final Runnable mUnlinkVisualizer = new Runnable() {
@Override
public void run() {
if (mLinked) {
animate().alpha(0).setDuration(300);
unlink();
mLinked = false;
}
}
};
private static class TileBarGraphRenderer extends Renderer {
private int mDivisions;
private Paint mPaint;
private int mDbFuzz;
private int mDbFuzzFactor;
/**
* Renders the FFT data as a series of lines, in histogram form
*
* @param divisions - must be a power of 2. Controls how many lines to draw
* @param paint - Paint to draw lines with
* @param dbfuzz - final dB display adjustment
* @param dbFactor - dbfuzz is multiplied by dbFactor.
*/
public TileBarGraphRenderer(int divisions, Paint paint, int dbfuzz, int dbFactor) {
super();
mDivisions = divisions;
mPaint = paint;
mDbFuzz = dbfuzz;
mDbFuzzFactor = dbFactor;
}
@Override
public void onRender(Canvas canvas, AudioData data, Rect rect) {
// Do nothing, we only display FFT data
}
@Override
public void onRender(Canvas canvas, FFTData data, Rect rect) {
for (int i = 0; i < data.bytes.length / mDivisions; i++) {
mFFTPoints[i * 4] = i * 4 * mDivisions;
mFFTPoints[i * 4 + 2] = i * 4 * mDivisions;
byte rfk = data.bytes[mDivisions * i];
byte ifk = data.bytes[mDivisions * i + 1];
float magnitude = (rfk * rfk + ifk * ifk);
int dbValue = (int) (10 * Math.log10(magnitude));
mFFTPoints[i * 4 + 1] = rect.height();
mFFTPoints[i * 4 + 3] = rect.height() - (dbValue * mDbFuzzFactor + mDbFuzz);
}
canvas.drawLines(mFFTPoints, mPaint);
}
}
public EqualizerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public EqualizerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EqualizerView(Context context) {
this(context, null, 0);
}
public void initialize(Context context) {
setEnabled(false);
Resources res = mContext.getResources();
Paint paint = new Paint();
paint.setStrokeWidth(res.getDimensionPixelSize(R.dimen.eqalizer_path_stroke_width));
paint.setAntiAlias(true);
paint.setColor(res.getColor(R.color.equalizer_fill_color));
paint.setPathEffect(new DashPathEffect(new float[]{
res.getDimensionPixelSize(R.dimen.eqalizer_path_effect_1),
res.getDimensionPixelSize(R.dimen.eqalizer_path_effect_2)
}, 0));
int bars = res.getInteger(R.integer.equalizer_divisions);
addRenderer(new TileBarGraphRenderer(bars, paint,
res.getInteger(R.integer.equalizer_db_fuzz),
res.getInteger(R.integer.equalizer_db_fuzz_factor)));
}
/**
* Follows Fragment onStart to determine if the containing fragment/activity is started
*/
public void onStart() {
mStarted = true;
checkStateChanged();
}
/**
* Follows Fragment onStop to determine if the containing fragment/activity is stopped
*/
public void onStop() {
mStarted = false;
checkStateChanged();
}
/**
* Separate method to toggle panel visibility - currently used when the user slides to
* improve performance of the sliding panel
*/
public void setPanelVisible(boolean panelVisible) {
if (mPanelVisible != panelVisible) {
mPanelVisible = panelVisible;
checkStateChanged();
}
}
/**
* Checks the state of the EqualizerView to determine whether we want to link up the equalizer
*/
public void checkStateChanged() {
if (mPanelVisible && mStarted
&& PreferenceUtils.getInstance(mContext).getShowVisualizer()
&& MusicUtils.getQueueSize() > 0) {
mLinkVisualizer.run();
} else {
mUnlinkVisualizer.run();
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment