diff options
Diffstat (limited to 'src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java')
-rw-r--r-- | src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java b/src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java new file mode 100644 index 0000000..03e9853 --- /dev/null +++ b/src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java @@ -0,0 +1,634 @@ +package com.cyngn.audiofx.activity; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.CompoundButton; + +import com.cyngn.audiofx.Constants; +import com.cyngn.audiofx.Preset; +import com.cyngn.audiofx.R; +import com.cyngn.audiofx.eq.EqUtils; +import com.cyngn.audiofx.service.AudioFxService; +import com.cyngn.audiofx.stats.UserSession; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +public class EqualizerManager { + + private static final String TAG = EqualizerManager.class.getSimpleName(); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final MasterConfigControl mConfig; + private final Context mContext; + + private float mMinFreq; + private float mMaxFreq; + + private float mMinDB; + private float mMaxDB; + private int mNumBands; + private CompoundButton.OnCheckedChangeListener mLockChangeListener; + + /* + * presets from the library custom preset. + */ + private int mPredefinedPresets; + private float[] mCenterFreqs; + private float[] mGlobalLevels; + + private AtomicBoolean mAnimatingToCustom = new AtomicBoolean(false); + + // whether we are in between presets, animating them and such + private boolean mChangingPreset = false; + + private int mCurrentPreset; + + private final ArrayList<Preset> mEqPresets = new ArrayList<Preset>(); + private int mEQCustomPresetPosition; + + private String mZeroedBandString; + + private static final int MSG_SAVE_PRESETS = 1; + private static final int MSG_SEND_EQ_OVERRIDE = 2; + + private Handler mHandler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_SAVE_PRESETS: + Constants.saveCustomPresets(mContext, mEqPresets); + break; + case MSG_SEND_EQ_OVERRIDE: + mConfig.overrideEqLevels((short)msg.arg1, (short) msg.arg2); + break; + } + return true; + }}, true); + + public EqualizerManager(Context context, MasterConfigControl config) { + mContext = context; + mConfig = config; + + applyDefaults(); + } + + public void applyDefaults() { + mEqPresets.clear(); + // setup eq + int bands = Integer.parseInt(getGlobalPref("equalizer.number_of_bands", "5")); + final int[] centerFreqs = Constants.getCenterFreqs(mContext, bands); + final int[] bandLevelRange = Constants.getBandLevelRange(mContext); + + float[] centerFreqsKHz = new float[centerFreqs.length]; + for (int i = 0; i < centerFreqs.length; i++) { + centerFreqsKHz[i] = (float) centerFreqs[i] / 1000.0f; + } + + mMinDB = bandLevelRange[0] / 100; + mMaxDB = bandLevelRange[1] / 100; + + mNumBands = centerFreqsKHz.length; + mGlobalLevels = new float[mNumBands]; + for (int i = 0; i < mGlobalLevels.length; i++) { + mGlobalLevels[i] = 0; + } + + mZeroedBandString = EqUtils.getZeroedBandsString(getNumBands()); + + 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; + + // setup equalizer presets + final int numPresets = Integer.parseInt(getGlobalPref("equalizer.number_of_presets", "0")); + + if (numPresets > 0) { + // add library-provided presets + String[] presetNames = getGlobalPref("equalizer.preset_names", "").split("\\|"); + mPredefinedPresets = presetNames.length + 1; // we consider first EQ to be part of predefined + for (int i = 0; i < numPresets; i++) { + mEqPresets.add(new Preset.StaticPreset(presetNames[i], getPersistedPresetLevels(i))); + } + } else { + mPredefinedPresets = 1; // custom is predefined + } + // add custom preset + mEqPresets.add(new Preset.PermCustomPreset(mContext.getString(R.string.user), + getPersistedCustomLevels())); + mEQCustomPresetPosition = mEqPresets.size() - 1; + + // restore custom prefs + mEqPresets.addAll(Constants.getCustomPresets(mContext, mNumBands)); + + // setup default preset for speaker + mCurrentPreset = Integer.parseInt(getPref(Constants.DEVICE_AUDIOFX_EQ_PRESET, "0")); + if (mCurrentPreset > mEqPresets.size() - 1) { + mCurrentPreset = 0; + } + setPreset(mCurrentPreset); + } + + public boolean isUserPreset() { + boolean result = mCurrentPreset >= mPredefinedPresets; + /*if (DEBUG) { + Log.i(TAG, "isUserPreset(), current preset: " + mCurrentPreset); + Log.i(TAG, "----> predefined presets: " + mPredefinedPresets); + Log.d(TAG, "----> RESULT: " + result); + }*/ + return result; + } + + public boolean isCustomPreset() { + return mCurrentPreset == mEQCustomPresetPosition; + } + + public CompoundButton.OnCheckedChangeListener getLockChangeListener() { + if (mLockChangeListener == null) { + mLockChangeListener = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isUserPreset()) { + ((Preset.CustomPreset) mEqPresets.get(mCurrentPreset)).setLocked(isChecked); + } + } + }; + } + return mLockChangeListener; + } + + public boolean isChangingPresets() { + return mChangingPreset; + } + + public void setChangingPresets(boolean changing) { + if (mChangingPreset != changing) { + mChangingPreset = changing; + if (changing) { + mConfig.getCallbacks().notifyEqControlStateChanged(false, false, false, false); + } else { + updateEqControls(); + } + } + } + + public boolean isAnimatingToCustom() { + return mAnimatingToCustom.get(); + } + + public void setAnimatingToCustom(boolean animating) { + mAnimatingToCustom.set(animating); + if (!animating) { + // finished animation + updateEqControls(); + } + } + + private void savePresetsDelayed() { + mHandler.sendEmptyMessageDelayed(MSG_SAVE_PRESETS, 500); + } + + public int indexOf(Preset p) { + return mEqPresets.indexOf(p); + } + + void onPreDeviceChanged() { + // need to update the current preset based on the device here. + int newPreset = Integer.parseInt(getPref(Constants.DEVICE_AUDIOFX_EQ_PRESET, "0")); + if (newPreset > mEqPresets.size() - 1) { + newPreset = 0; + } + + // this should be ready to go for callbacks to query the new device preset below + mCurrentPreset = newPreset; + + } + + void onPostDeviceChanged() { + setPreset(mCurrentPreset, false); + } + + public Preset getCurrentPreset() { + return mEqPresets.get(mCurrentPreset); + } + + /** + * Copy current config levels from the current preset into custom values since the user has + * initiated some change. Then update the current preset to 'custom'. + */ + public int copyToCustom() { + updateGlobalLevels(mCurrentPreset); + if (DEBUG) { + Log.w(TAG, "using levels from preset: " + mCurrentPreset + ": " + Arrays.toString(mGlobalLevels)); + } + + String levels = EqUtils.floatLevelsToString( + EqUtils.convertDecibelsToMillibels( + mEqPresets.get(mCurrentPreset).getLevels())); + setGlobalPref("custom", levels); + + ((Preset.PermCustomPreset) mEqPresets.get(mEQCustomPresetPosition)).setLevels(mGlobalLevels); + if (DEBUG) + Log.i(TAG, "copyToCustom() wrote current preset levels to index: " + mEQCustomPresetPosition); + setPreset(mEQCustomPresetPosition); + savePresetsDelayed(); + return mEQCustomPresetPosition; + } + + public int addPresetFromCustom() { + updateGlobalLevels(mEQCustomPresetPosition); + if (DEBUG) { + Log.w(TAG, "using levels from preset: " + mCurrentPreset + ": " + Arrays.toString(mGlobalLevels)); + } + + int writtenToIndex = addPreset(mGlobalLevels); + if (DEBUG) + Log.i(TAG, "addPresetFromCustom() wrote current preset levels to index: " + writtenToIndex); + setPreset(writtenToIndex); + savePresetsDelayed(); + return writtenToIndex; + } + + /** + * Loops through all presets. And finds the first preset that can be written to. + * If one is not found, then one is inserted, and that new index is returned. + * @return the index that the levels were copied to + */ + private int addPreset(float[] levels) { + if (UserSession.getInstance() != null) { + UserSession.getInstance().presetCreated(); + } + + final int customPresets = Constants.getCustomPresets(mContext, mNumBands).size(); + // format the name so it's like "Custom <N>", start with "Custom 2" + final String name = String.format(mContext.getString(R.string.user_n), customPresets + 2); + + Preset.CustomPreset customPreset = new Preset.CustomPreset(name, levels, false); + mEqPresets.add(customPreset); + + mConfig.getCallbacks().notifyPresetsChanged(); + + return mEqPresets.size() - 1; + } + + /** + * Set a new level! + * <p/> + * This call will be propogated to all listeners registered with addEqStateChangeCallback(). + * + * @param band the band index the band index which changed + * @param dB the new decibel value + * @param systemChange is this change generated by the system? + */ + public void setLevel(final int band, final float dB, final boolean fromSystem) { + if (DEBUG) Log.i(TAG, "setLevel(" + band + ", " + dB + ", " + fromSystem + ")"); + + mGlobalLevels[band] = dB; + + if (fromSystem && !mConfig.isUserDeviceOverride()) { + // quickly convert decibel to millibel and send away to the service + mHandler.obtainMessage(MSG_SEND_EQ_OVERRIDE, band, (short) (dB * 100)).sendToTarget(); + } + + mConfig.getCallbacks().notifyBandLevelChangeChanged(band, dB, fromSystem); + + if (!fromSystem) { // user is touching + // persist + + final Preset preset = mEqPresets.get(mCurrentPreset); + if (preset instanceof Preset.CustomPreset) { + if (mAnimatingToCustom.get()) { + if (DEBUG) { + Log.d(TAG, "setLevel() not persisting new custom band becuase animating."); + } + } else { + ((Preset.CustomPreset) preset).setLevel(band, dB); + if (preset instanceof Preset.PermCustomPreset) { + // store these as millibels + String levels = EqUtils.floatLevelsToString( + EqUtils.convertDecibelsToMillibels( + preset.getLevels())); + setGlobalPref("custom", levels); + } + } + // needs to be updated immediately here for the service. + final String levels = EqUtils.floatLevelsToString(preset.getLevels()); + setPref(Constants.DEVICE_AUDIOFX_EQ_PRESET_LEVELS, levels); + + mConfig.updateService(AudioFxService.EQ_CHANGED); + } + savePresetsDelayed(); + } + } + + /** + * Set a new preset index. + * <p/> + * This call will be propogated to all listeners registered with addEqStateChangeCallback(). + * + * @param newPresetIndex the new preset index. + */ + public void setPreset(final int newPresetIndex, boolean updateBackend) { + mCurrentPreset = newPresetIndex; + updateEqControls(); // do this before callback is propogated + + mConfig.getCallbacks().notifyPresetChanged(newPresetIndex); + + // persist + setPref(Constants.DEVICE_AUDIOFX_EQ_PRESET, String.valueOf(newPresetIndex)); + + // update mGlobalLevels + float[] newlevels = getPresetLevels(newPresetIndex); + for (int i = 0; i < newlevels.length; i++) { + setLevel(i, newlevels[i], true); + } + + setPref(Constants.DEVICE_AUDIOFX_EQ_PRESET_LEVELS, EqUtils.floatLevelsToString(newlevels)); + + if (updateBackend) { + mConfig.updateService(AudioFxService.EQ_CHANGED); + } + } + + public void setPreset(final int newPresetIndex) { + setPreset(newPresetIndex, true); + } + + private void updateEqControls() { + final boolean userPreset = isUserPreset(); + mConfig.getCallbacks().notifyEqControlStateChanged(mEQCustomPresetPosition == mCurrentPreset, + userPreset, userPreset, userPreset); + } + + /** + * @return Get the current preset index + */ + public int getCurrentPresetIndex() { + return mCurrentPreset; + } + + /*=============== + * eq methods + *===============*/ + + public 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)); + } + + public double reverseProjectX(float pos) { + double minPos = Math.log(mMinFreq); + double maxPos = Math.log(mMaxFreq); + return Math.exp(pos * (maxPos - minPos) + minPos); + } + + public float projectY(double dB) { + double pos = (dB - mMinDB) / (mMaxDB - mMinDB); + return (float) (1 - pos); + } + + public static double lin2dB(double rho) { + return rho != 0 ? Math.log(rho) / Math.log(10) * 20 : -99.9; + } + + public float getMinFreq() { + return mMinFreq; + } + + public float getMaxFreq() { + return mMaxFreq; + } + + public float getMinDB() { + return mMinDB; + } + + public float getMaxDB() { + return mMaxDB; + } + + public int getNumBands() { + return mNumBands; + } + + public float getCenterFreq(int band) { + return mCenterFreqs[band]; + } + + public float[] getCenterFreqs() { + return mCenterFreqs; + } + + public float[] getLevels() { + return mGlobalLevels; + } + + public float getLevel(int band) { + return mGlobalLevels[band]; + } + + /*=============== + * preset methods + *===============*/ + + public float[] getPersistedPresetLevels(int presetIndex) { + String newLevels = null; + + if (mEqPresets.size() > presetIndex + && mEqPresets.get(presetIndex) instanceof Preset.PermCustomPreset) { + return getPersistedCustomLevels(); + } else { + newLevels = getGlobalPref("equalizer.preset." + presetIndex, mZeroedBandString); + } + + // stored as millibels, convert to decibels + float[] levels = EqUtils.stringBandsToFloats(newLevels); + return EqUtils.convertMillibelsToDecibels(levels); + } + + private float[] getPersistedCustomLevels() { + String newLevels = getGlobalPref("custom", mZeroedBandString); + // stored as millibels, convert to decibels + float[] levels = EqUtils.stringBandsToFloats(newLevels); + return EqUtils.convertMillibelsToDecibels(levels); + } + + /** + * Get preset levels in decibels for a given index + * + * @param presetIndex index which to fetch preset levels for + * @return an array of floats[] with the given index's preset levels + */ + public float[] getPresetLevels(int presetIndex) { + return mEqPresets.get(presetIndex).getLevels(); + } + + /** + * Helper method which maps a preset index to a color value. + * + * @param index the preset index which to fetch a color for + * @return a color which is associated with this preset. + */ + public int getAssociatedPresetColorHex(int index) { + int r = -1; + index = index % mEqPresets.size(); + if (mEqPresets.get(index) instanceof Preset.CustomPreset) { + r = R.color.preset_custom; + } else { + switch (index) { + case 0: + r = R.color.preset_normal; + break; + case 1: + r = R.color.preset_classical; + break; + case 2: + r = R.color.preset_dance; + break; + case 3: + r = R.color.preset_flat; + break; + case 4: + r = R.color.preset_folk; + break; + case 5: + r = R.color.preset_metal; + break; + case 6: + r = R.color.preset_hiphop; + break; + case 7: + r = R.color.preset_jazz; + break; + case 8: + r = R.color.preset_pop; + break; + case 9: + r = R.color.preset_rock; + break; + case 10: + r = R.color.preset_electronic; + break; + case 11: + r = R.color.preset_small_speakers; + break; + default: + return r; + } + } + return mContext.getResources().getColor(r); + } + + /** + * Get total number of presets + * + * @return int value with total number of presets + */ + public int getPresetCount() { + return mEqPresets.size(); + } + + public Preset getPreset(int index) { + return mEqPresets.get(index); + } + + public String getLocalizedPresetName(int index) { + // already localized + return localizePresetName(mEqPresets.get(index).getName()); + } + + private final String localizePresetName(final String name) { + // missing electronic, multimedia, small speakers, custom + final String[] names = { + "Normal", "Classical", "Dance", "Flat", "Folk", + "Heavy Metal", "Hip Hop", "Jazz", "Pop", "Rock", + "Electronic", "Small speakers", "Multimedia", + "Custom" + }; + final int[] ids = { + R.string.normal, R.string.classical, R.string.dance, R.string.flat, R.string.folk, + R.string.heavy_metal, R.string.hip_hop, R.string.jazz, R.string.pop, R.string.rock, + R.string.ci_extreme, R.string.small_speakers, R.string.multimedia, + R.string.user + }; + + for (int i = names.length - 1; i >= 0; --i) { + if (names[i].equalsIgnoreCase(name)) { + return mContext.getString(ids[i]); + } + } + return name; + } + + public boolean isEqualizerLocked() { + return getCurrentPreset() instanceof Preset.CustomPreset + && !(getCurrentPreset() instanceof Preset.PermCustomPreset) + && ((Preset.CustomPreset) getCurrentPreset()).isLocked(); + } + + public void renameCurrentPreset(String s) { + if (UserSession.getInstance() != null) { + UserSession.getInstance().presetRenamed(); + } + + if (isUserPreset()) { + ((Preset.CustomPreset) getCurrentPreset()).setName(s); + } + + mConfig.getCallbacks().notifyPresetsChanged(); + + savePresetsDelayed(); + } + + public boolean removePreset(int index) { + if (UserSession.getInstance() != null) { + UserSession.getInstance().presetRemoved(); + } + + if (index > mEQCustomPresetPosition) { + mEqPresets.remove(index); + mConfig.getCallbacks().notifyPresetsChanged(); + + if (mCurrentPreset == index) { + if (DEBUG) { + Log.w(TAG, "removePreset() called on current preset, changing preset"); + } + updateGlobalLevels(mCurrentPreset - 1); + setPreset(mCurrentPreset - 1); + } + savePresetsDelayed(); + return true; + } + return false; + } + + private void updateGlobalLevels(int presetIndexToCopy) { + final float[] presetLevels = getPresetLevels(presetIndexToCopy); + for (int i = 0; i < mGlobalLevels.length; i++) { + mGlobalLevels[i] = presetLevels[i]; + } + } + + // I AM SO LAZY! + private String getGlobalPref(String key, String defValue) { + return mConfig.getGlobalPrefs().getString(key, defValue); + } + + private void setGlobalPref(String key, String value) { + mConfig.getGlobalPrefs().edit().putString(key, value).apply(); + } + + private String getPref(String key, String defValue) { + return mConfig.getPrefs().getString(key, defValue); + } + + private void setPref(String key, String value) { + mConfig.getPrefs().edit().putString(key, value).apply(); + } +} |