content: /*
   * Copyright (C) 2010 The Android Open Source 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.
   */
  
  package android.media.audiofx;
  
  import android.media.audiofx.AudioEffect;
  import android.util.Log;
  
  import java.util.StringTokenizer;
  
  
  /**
   * An Equalizer is used to alter the frequency response of a particular music source or of the main
   * output mix.
   * <p>An application creates an Equalizer object to instantiate and control an Equalizer engine
   * in the audio framework. The application can either simply use predefined presets or have a more
   * precise control of the gain in each frequency band controlled by the equalizer.
   * <p>The methods, parameter types and units exposed by the Equalizer implementation are directly
   * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/)
   * for the SLEqualizerItf interface. Please refer to this specification for more details.
   * <p>To attach the Equalizer to a particular AudioTrack or MediaPlayer, specify the audio session
   * ID of this AudioTrack or MediaPlayer when constructing the Equalizer.
   * <p>NOTE: attaching an Equalizer to the global audio output mix by use of session 0 is deprecated.
   * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
   * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
   * effects.
   */
  
  public class Equalizer extends AudioEffect {
  
      private final static String TAG = "Equalizer";
  
      // These constants must be synchronized with those in
      // frameworks/base/include/media/EffectEqualizerApi.h
      /**
       * Number of bands. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_NUM_BANDS = 0;
      /**
       * Band level range. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_LEVEL_RANGE = 1;
      /**
       * Band level. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_BAND_LEVEL = 2;
      /**
       * Band center frequency. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_CENTER_FREQ = 3;
      /**
       * Band frequency range. Parameter ID for
       * {@link android.media.audiofx.Equalizer.OnParameterChangeListener}
       */
      public static final int PARAM_BAND_FREQ_RANGE = 4;
      /**
       * Band for a given frequency. Parameter ID for OnParameterChangeListener
       *
       */
      public static final int PARAM_GET_BAND = 5;
      /**
       * Current preset. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_CURRENT_PRESET = 6;
      /**
       * Request number of presets. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_GET_NUM_OF_PRESETS = 7;
      /**
       * Request preset name. Parameter ID for OnParameterChangeListener
       */
      public static final int PARAM_GET_PRESET_NAME = 8;
      // used by setProperties()/getProperties
      private static final int PARAM_PROPERTIES = 9;
      /**
       * Maximum size for preset name
       */
      public static final int PARAM_STRING_SIZE_MAX = 32;
  
      /**
       * Number of bands implemented by Equalizer engine
       */
      private short mNumBands = 0;
  
      /**
       * Number of presets implemented by Equalizer engine
       */
      private int mNumPresets;
      /**
       * Names of presets implemented by Equalizer engine
       */
      private String[] mPresetNames;
  
      /**
       * Registered listener for parameter changes.
       */
      private OnParameterChangeListener mParamListener = null;
  
      /**
       * Listener used internally to to receive raw parameter change event from AudioEffect super class
       */
      private BaseParameterListener mBaseParamListener = null;
  
      /**
       * Lock for access to mParamListener
       */
      private final Object mParamListenerLock = new Object();
  
      /**
       * Class constructor.
       * @param priority the priority level requested by the application for controlling the Equalizer
       * engine. As the same engine can be shared by several applications, this parameter indicates
       * how much the requesting application needs control of effect parameters. The normal priority
       * is 0, above normal is a positive number, below normal a negative number.
       * @param audioSession  system wide unique audio session identifier. The Equalizer will be
       * attached to the MediaPlayer or AudioTrack in the same audio session.
       *
       * @throws java.lang.IllegalStateException
       * @throws java.lang.IllegalArgumentException
       * @throws java.lang.UnsupportedOperationException
       * @throws java.lang.RuntimeException
       */
      public Equalizer(int priority, int audioSession)
      throws IllegalStateException, IllegalArgumentException,
             UnsupportedOperationException, RuntimeException {
          super(EFFECT_TYPE_EQUALIZER, EFFECT_TYPE_NULL, priority, audioSession);
  
          if (audioSession == 0) {
              Log.w(TAG, "WARNING: attaching an Equalizer to global output mix is deprecated!");
          }
  
          getNumberOfBands();
  
          mNumPresets = (int)getNumberOfPresets();
  
          if (mNumPresets != 0) {
              mPresetNames = new String[mNumPresets];
              byte[] value = new byte[PARAM_STRING_SIZE_MAX];
              int[] param = new int[2];
              param[0] = PARAM_GET_PRESET_NAME;
              for (int i = 0; i < mNumPresets; i++) {
                  param[1] = i;
                  checkStatus(getParameter(param, value));
                  int length = 0;
                  while (value[length] != 0) length++;
                  try {
                      mPresetNames[i] = new String(value, 0, length, "ISO-8859-1");
                  } catch (java.io.UnsupportedEncodingException e) {
                      Log.e(TAG, "preset name decode error");
                  }
              }
          }
      }
  
      /**
       * Gets the number of frequency bands supported by the Equalizer engine.
       * @return the number of bands
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public short getNumberOfBands()
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          if (mNumBands != 0) {
              return mNumBands;
          }
          int[] param = new int[1];
          param[0] = PARAM_NUM_BANDS;
          short[] result = new short[1];
          checkStatus(getParameter(param, result));
          mNumBands = result[0];
          return mNumBands;
      }
  
      /**
       * Gets the level range for use by {@link #setBandLevel(short,short)}. The level is expressed in
       * milliBel.
       * @return the band level range in an array of short integers. The first element is the lower
       * limit of the range, the second element the upper limit.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public short[] getBandLevelRange()
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          short[] result = new short[2];
          checkStatus(getParameter(PARAM_LEVEL_RANGE, result));
          return result;
      }
  
      /**
       * Sets the given equalizer band to the given gain value.
       * @param band frequency band that will have the new gain. The numbering of the bands starts
       * from 0 and ends at (number of bands - 1).
       * @param level new gain in millibels that will be set to the given band. getBandLevelRange()
       * will define the maximum and minimum values.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       * @see #getNumberOfBands()
       */
      public void setBandLevel(short band, short level)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          int[] param = new int[2];
          short[] value = new short[1];
  
          param[0] = PARAM_BAND_LEVEL;
          param[1] = (int)band;
          value[0] = level;
          checkStatus(setParameter(param, value));
      }
  
      /**
       * Gets the gain set for the given equalizer band.
       * @param band frequency band whose gain is requested. The numbering of the bands starts
       * from 0 and ends at (number of bands - 1).
       * @return the gain in millibels of the given band.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public short getBandLevel(short band)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          int[] param = new int[2];
          short[] result = new short[1];
  
          param[0] = PARAM_BAND_LEVEL;
          param[1] = (int)band;
          checkStatus(getParameter(param, result));
  
          return result[0];
      }
  
  
      /**
       * Gets the center frequency of the given band.
       * @param band frequency band whose center frequency is requested. The numbering of the bands
       * starts from 0 and ends at (number of bands - 1).
       * @return the center frequency in milliHertz
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public int getCenterFreq(short band)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          int[] param = new int[2];
          int[] result = new int[1];
  
          param[0] = PARAM_CENTER_FREQ;
          param[1] = (int)band;
          checkStatus(getParameter(param, result));
  
          return result[0];
      }
  
      /**
       * Gets the frequency range of the given frequency band.
       * @param band frequency band whose frequency range is requested. The numbering of the bands
       * starts from 0 and ends at (number of bands - 1).
       * @return the frequency range in millHertz in an array of integers. The first element is the
       * lower limit of the range, the second element the upper limit.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public int[] getBandFreqRange(short band)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          int[] param = new int[2];
          int[] result = new int[2];
          param[0] = PARAM_BAND_FREQ_RANGE;
          param[1] = (int)band;
          checkStatus(getParameter(param, result));
  
          return result;
      }
  
      /**
       * Gets the band that has the most effect on the given frequency.
       * @param frequency frequency in milliHertz which is to be equalized via the returned band.
       * @return the frequency band that has most effect on the given frequency.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public short getBand(int frequency)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          int[] param = new int[2];
          short[] result = new short[1];
  
          param[0] = PARAM_GET_BAND;
          param[1] = frequency;
          checkStatus(getParameter(param, result));
  
          return result[0];
      }
  
      /**
       * Gets current preset.
       * @return the preset that is set at the moment.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public short getCurrentPreset()
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          short[] result = new short[1];
          checkStatus(getParameter(PARAM_CURRENT_PRESET, result));
          return result[0];
      }
  
      /**
       * Sets the equalizer according to the given preset.
       * @param preset new preset that will be taken into use. The valid range is [0,
       * number of presets-1].
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       * @see #getNumberOfPresets()
       */
      public void usePreset(short preset)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          checkStatus(setParameter(PARAM_CURRENT_PRESET, preset));
      }
  
      /**
       * Gets the total number of presets the equalizer supports. The presets will have indices
       * [0, number of presets-1].
       * @return the number of presets the equalizer supports.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public short getNumberOfPresets()
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          short[] result = new short[1];
          checkStatus(getParameter(PARAM_GET_NUM_OF_PRESETS, result));
          return result[0];
      }
  
      /**
       * Gets the preset name based on the index.
       * @param preset index of the preset. The valid range is [0, number of presets-1].
       * @return a string containing the name of the given preset.
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public String getPresetName(short preset)
      {
          if (preset >= 0 && preset < mNumPresets) {
              return mPresetNames[preset];
          } else {
              return "";
          }
      }
  
      /**
       * The OnParameterChangeListener interface defines a method called by the Equalizer when a
       * parameter value has changed.
       */
      public interface OnParameterChangeListener  {
          /**
           * Method called when a parameter value has changed. The method is called only if the
           * parameter was changed by another application having the control of the same
           * Equalizer engine.
           * @param effect the Equalizer on which the interface is registered.
           * @param status status of the set parameter operation.
           * @param param1 ID of the modified parameter. See {@link #PARAM_BAND_LEVEL} ...
           * @param param2 additional parameter qualifier (e.g the band for band level parameter).
           * @param value the new parameter value.
           */
          void onParameterChange(Equalizer effect, int status, int param1, int param2, int value);
      }
  
      /**
       * Listener used internally to receive unformatted parameter change events from AudioEffect
       * super class.
       */
      private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
          private BaseParameterListener() {
  
          }
          public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
              OnParameterChangeListener l = null;
  
              synchronized (mParamListenerLock) {
                  if (mParamListener != null) {
                      l = mParamListener;
                  }
              }
              if (l != null) {
                  int p1 = -1;
                  int p2 = -1;
                  int v = -1;
  
                  if (param.length >= 4) {
                      p1 = byteArrayToInt(param, 0);
                      if (param.length >= 8) {
                          p2 = byteArrayToInt(param, 4);
                      }
                  }
                  if (value.length == 2) {
                      v = (int)byteArrayToShort(value, 0);;
                  } else if (value.length == 4) {
                      v = byteArrayToInt(value, 0);
                  }
  
                  if (p1 != -1 && v != -1) {
                      l.onParameterChange(Equalizer.this, status, p1, p2, v);
                  }
              }
          }
      }
  
      /**
       * Registers an OnParameterChangeListener interface.
       * @param listener OnParameterChangeListener interface registered
       */
      public void setParameterListener(OnParameterChangeListener listener) {
          synchronized (mParamListenerLock) {
              if (mParamListener == null) {
                  mParamListener = listener;
                  mBaseParamListener = new BaseParameterListener();
                  super.setParameterListener(mBaseParamListener);
              }
          }
      }
  
      /**
       * The Settings class regroups all equalizer parameters. It is used in
       * conjuntion with getProperties() and setProperties() methods to backup and restore
       * all parameters in a single call.
       */
      public static class Settings {
          public short curPreset;
          public short numBands = 0;
          public short[] bandLevels = null;
  
          public Settings() {
          }
  
          /**
           * Settings class constructor from a key=value; pairs formatted string. The string is
           * typically returned by Settings.toString() method.
           * @throws IllegalArgumentException if the string is not correctly formatted.
           */
          public Settings(String settings) {
              StringTokenizer st = new StringTokenizer(settings, "=;");
              int tokens = st.countTokens();
              if (st.countTokens() < 5) {
                  throw new IllegalArgumentException("settings: " + settings);
              }
              String key = st.nextToken();
              if (!key.equals("Equalizer")) {
                  throw new IllegalArgumentException(
                          "invalid settings for Equalizer: " + key);
              }
              try {
                  key = st.nextToken();
                  if (!key.equals("curPreset")) {
                      throw new IllegalArgumentException("invalid key name: " + key);
                  }
                  curPreset = Short.parseShort(st.nextToken());
                  key = st.nextToken();
                  if (!key.equals("numBands")) {
                      throw new IllegalArgumentException("invalid key name: " + key);
                  }
                  numBands = Short.parseShort(st.nextToken());
                  if (st.countTokens() != numBands*2) {
                      throw new IllegalArgumentException("settings: " + settings);
                  }
                  bandLevels = new short[numBands];
                  for (int i = 0; i < numBands; i++) {
                      key = st.nextToken();
                      if (!key.equals("band"+(i+1)+"Level")) {
                          throw new IllegalArgumentException("invalid key name: " + key);
                      }
                      bandLevels[i] = Short.parseShort(st.nextToken());
                  }
               } catch (NumberFormatException nfe) {
                  throw new IllegalArgumentException("invalid value for key: " + key);
              }
          }
  
          @Override
          public String toString() {
  
              String str = new String (
                      "Equalizer"+
                      ";curPreset="+Short.toString(curPreset)+
                      ";numBands="+Short.toString(numBands)
                      );
              for (int i = 0; i < numBands; i++) {
                  str = str.concat(";band"+(i+1)+"Level="+Short.toString(bandLevels[i]));
              }
              return str;
          }
      };
  
  
      /**
       * Gets the equalizer properties. This method is useful when a snapshot of current
       * equalizer settings must be saved by the application.
       * @return an Equalizer.Settings object containing all current parameters values
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public Equalizer.Settings getProperties()
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          byte[] param = new byte[4 + mNumBands * 2];
          checkStatus(getParameter(PARAM_PROPERTIES, param));
          Settings settings = new Settings();
          settings.curPreset = byteArrayToShort(param, 0);
          settings.numBands = byteArrayToShort(param, 2);
          settings.bandLevels = new short[mNumBands];
          for (int i = 0; i < mNumBands; i++) {
              settings.bandLevels[i] = byteArrayToShort(param, 4 + 2*i);
          }
          return settings;
      }
  
      /**
       * Sets the equalizer properties. This method is useful when equalizer settings have to
       * be applied from a previous backup.
       * @param settings an Equalizer.Settings object containing the properties to apply
       * @throws IllegalStateException
       * @throws IllegalArgumentException
       * @throws UnsupportedOperationException
       */
      public void setProperties(Equalizer.Settings settings)
      throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
          if (settings.numBands != settings.bandLevels.length ||
              settings.numBands != mNumBands) {
              throw new IllegalArgumentException("settings invalid band count: " +settings.numBands);
          }
  
          byte[] param = concatArrays(shortToByteArray(settings.curPreset),
                                      shortToByteArray(mNumBands));
          for (int i = 0; i < mNumBands; i++) {
              param = concatArrays(param,
                                   shortToByteArray(settings.bandLevels[i]));
          }
          checkStatus(setParameter(PARAM_PROPERTIES, param));
      }
  }
  