Home | History | Annotate | Download | only in audiofx
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media.audiofx;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.media.AudioTrack;
     22 import android.media.MediaPlayer;
     23 import android.media.audiofx.AudioEffect;
     24 import android.util.Log;
     25 
     26 import java.nio.ByteBuffer;
     27 import java.nio.ByteOrder;
     28 import java.util.StringTokenizer;
     29 
     30 /**
     31  * DynamicsProcessing is an audio effect for equalizing and changing dynamic range properties of the
     32  * sound. It is composed of multiple stages including equalization, multi-band compression and
     33  * limiter.
     34  * <p>The number of bands and active stages is configurable, and most parameters can be controlled
     35  * in realtime, such as gains, attack/release times, thresholds, etc.
     36  * <p>The effect is instantiated and controlled by channels. Each channel has the same basic
     37  * architecture, but all of their parameters are independent from other channels.
     38  * <p>The basic channel configuration is:
     39  * <pre>
     40  *
     41  *    Channel 0          Channel 1       ....       Channel N-1
     42  *      Input              Input                       Input
     43  *        |                  |                           |
     44  *   +----v----+        +----v----+                 +----v----+
     45  *   |inputGain|        |inputGain|                 |inputGain|
     46  *   +---------+        +---------+                 +---------+
     47  *        |                  |                           |
     48  *  +-----v-----+      +-----v-----+               +-----v-----+
     49  *  |   PreEQ   |      |   PreEQ   |               |   PreEQ   |
     50  *  +-----------+      +-----------+               +-----------+
     51  *        |                  |                           |
     52  *  +-----v-----+      +-----v-----+               +-----v-----+
     53  *  |    MBC    |      |    MBC    |               |    MBC    |
     54  *  +-----------+      +-----------+               +-----------+
     55  *        |                  |                           |
     56  *  +-----v-----+      +-----v-----+               +-----v-----+
     57  *  |  PostEQ   |      |  PostEQ   |               |  PostEQ   |
     58  *  +-----------+      +-----------+               +-----------+
     59  *        |                  |                           |
     60  *  +-----v-----+      +-----v-----+               +-----v-----+
     61  *  |  Limiter  |      |  Limiter  |               |  Limiter  |
     62  *  +-----------+      +-----------+               +-----------+
     63  *        |                  |                           |
     64  *     Output             Output                      Output
     65  * </pre>
     66  *
     67  * <p>Where the stages are:
     68  * inputGain: input gain factor in decibels (dB). 0 dB means no change in level.
     69  * PreEQ:  Multi-band Equalizer.
     70  * MBC:    Multi-band Compressor
     71  * PostEQ: Multi-band Equalizer
     72  * Limiter: Single band compressor/limiter.
     73  *
     74  * <p>An application creates a DynamicsProcessing object to instantiate and control this audio
     75  * effect in the audio framework. A DynamicsProcessor.Config and DynamicsProcessor.Config.Builder
     76  * are available to help configure the multiple stages and each band parameters if desired.
     77  * <p>See each stage documentation for further details.
     78  * <p>If no Config is specified during creation, a default configuration is chosen.
     79  * <p>To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer,
     80  * specify the audio session ID of this AudioTrack or MediaPlayer when constructing the effect
     81  * (see {@link AudioTrack#getAudioSessionId()} and {@link MediaPlayer#getAudioSessionId()}).
     82  *
     83  * <p>To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer, specify the audio
     84  * session ID of this AudioTrack or MediaPlayer when constructing the DynamicsProcessing.
     85  * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
     86  * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
     87  * effects.
     88  */
     89 
     90 public final class DynamicsProcessing extends AudioEffect {
     91 
     92     private final static String TAG = "DynamicsProcessing";
     93 
     94     // These parameter constants must be synchronized with those in
     95     // /system/media/audio_effects/include/audio_effects/effect_dynamicsprocessing.h
     96     private static final int PARAM_GET_CHANNEL_COUNT = 0x10;
     97     private static final int PARAM_INPUT_GAIN = 0x20;
     98     private static final int PARAM_ENGINE_ARCHITECTURE = 0x30;
     99     private static final int PARAM_PRE_EQ = 0x40;
    100     private static final int PARAM_PRE_EQ_BAND = 0x45;
    101     private static final int PARAM_MBC = 0x50;
    102     private static final int PARAM_MBC_BAND = 0x55;
    103     private static final int PARAM_POST_EQ = 0x60;
    104     private static final int PARAM_POST_EQ_BAND = 0x65;
    105     private static final int PARAM_LIMITER = 0x70;
    106 
    107     /**
    108      * Index of variant that favors frequency resolution. Frequency domain based implementation.
    109      */
    110     public static final int VARIANT_FAVOR_FREQUENCY_RESOLUTION  = 0;
    111 
    112     /**
    113      * Index of variant that favors time resolution resolution. Time domain based implementation.
    114      */
    115     public static final int VARIANT_FAVOR_TIME_RESOLUTION       = 1;
    116 
    117     /**
    118      * Maximum expected channels to be reported by effect
    119      */
    120     private static final int CHANNEL_COUNT_MAX = 32;
    121 
    122     /**
    123      * Number of channels in effect architecture
    124      */
    125     private int mChannelCount = 0;
    126 
    127     /**
    128      * Registered listener for parameter changes.
    129      */
    130     private OnParameterChangeListener mParamListener = null;
    131 
    132     /**
    133      * Listener used internally to to receive raw parameter change events
    134      * from AudioEffect super class
    135      */
    136     private BaseParameterListener mBaseParamListener = null;
    137 
    138     /**
    139      * Lock for access to mParamListener
    140      */
    141     private final Object mParamListenerLock = new Object();
    142 
    143     /**
    144      * Class constructor.
    145      * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
    146      * will be attached to the MediaPlayer or AudioTrack in the same audio session.
    147      */
    148     public DynamicsProcessing(int audioSession) {
    149         this(0 /*priority*/, audioSession);
    150     }
    151 
    152     /**
    153      * @hide
    154      * Class constructor for the DynamicsProcessing audio effect.
    155      * @param priority the priority level requested by the application for controlling the
    156      * DynamicsProcessing engine. As the same engine can be shared by several applications,
    157      * this parameter indicates how much the requesting application needs control of effect
    158      * parameters. The normal priority is 0, above normal is a positive number, below normal a
    159      * negative number.
    160      * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
    161      * will be attached to the MediaPlayer or AudioTrack in the same audio session.
    162      */
    163     public DynamicsProcessing(int priority, int audioSession) {
    164         this(priority, audioSession, null);
    165     }
    166 
    167     /**
    168      * Class constructor for the DynamicsProcessing audio effect
    169      * @param priority the priority level requested by the application for controlling the
    170      * DynamicsProcessing engine. As the same engine can be shared by several applications,
    171      * this parameter indicates how much the requesting application needs control of effect
    172      * parameters. The normal priority is 0, above normal is a positive number, below normal a
    173      * negative number.
    174      * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
    175      * will be attached to the MediaPlayer or AudioTrack in the same audio session.
    176      * @param cfg Config object used to setup the audio effect, including bands per stage, and
    177      * specific parameters for each stage/band. Use
    178      * {@link android.media.audiofx.DynamicsProcessing.Config.Builder} to create a
    179      * Config object that suits your needs. A null cfg parameter will create and use a default
    180      * configuration for the effect
    181      */
    182     public DynamicsProcessing(int priority, int audioSession, @Nullable Config cfg) {
    183         super(EFFECT_TYPE_DYNAMICS_PROCESSING, EFFECT_TYPE_NULL, priority, audioSession);
    184         if (audioSession == 0) {
    185             Log.w(TAG, "WARNING: attaching a DynamicsProcessing to global output mix is"
    186                     + "deprecated!");
    187         }
    188         final Config config;
    189         mChannelCount = getChannelCount();
    190         if (cfg == null) {
    191             //create a default configuration and effect, with the number of channels this effect has
    192             DynamicsProcessing.Config.Builder builder =
    193                     new DynamicsProcessing.Config.Builder(
    194                             CONFIG_DEFAULT_VARIANT,
    195                             mChannelCount,
    196                             CONFIG_DEFAULT_USE_PREEQ,
    197                             CONFIG_DEFAULT_PREEQ_BANDS,
    198                             CONFIG_DEFAULT_USE_MBC,
    199                             CONFIG_DEFAULT_MBC_BANDS,
    200                             CONFIG_DEFAULT_USE_POSTEQ,
    201                             CONFIG_DEFAULT_POSTEQ_BANDS,
    202                             CONFIG_DEFAULT_USE_LIMITER);
    203             config = builder.build();
    204         } else {
    205             //validate channels are ok. decide what to do: replicate channels if more
    206             config = new DynamicsProcessing.Config(mChannelCount, cfg);
    207         }
    208 
    209         //configure engine
    210         setEngineArchitecture(config.getVariant(),
    211                 config.getPreferredFrameDuration(),
    212                 config.isPreEqInUse(),
    213                 config.getPreEqBandCount(),
    214                 config.isMbcInUse(),
    215                 config.getMbcBandCount(),
    216                 config.isPostEqInUse(),
    217                 config.getPostEqBandCount(),
    218                 config.isLimiterInUse());
    219         //update all the parameters
    220         for (int ch = 0; ch < mChannelCount; ch++) {
    221             updateEngineChannelByChannelIndex(ch, config.getChannelByChannelIndex(ch));
    222         }
    223     }
    224 
    225     /**
    226      * Returns the Config object used to setup this effect.
    227      * @return Config Current Config object used to setup this DynamicsProcessing effect.
    228      */
    229     public Config getConfig() {
    230         //Query engine architecture to create config object
    231         Number[] params = { PARAM_ENGINE_ARCHITECTURE };
    232         Number[] values = { 0 /*0 variant */,
    233                 0.0f /* 1 preferredFrameDuration */,
    234                 0 /*2 preEqInUse */,
    235                 0 /*3 preEqBandCount */,
    236                 0 /*4 mbcInUse */,
    237                 0 /*5 mbcBandCount*/,
    238                 0 /*6 postEqInUse */,
    239                 0 /*7 postEqBandCount */,
    240                 0 /*8 limiterInUse */};
    241         byte[] paramBytes = numberArrayToByteArray(params);
    242         byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
    243         getParameter(paramBytes, valueBytes);
    244         byteArrayToNumberArray(valueBytes, values);
    245         DynamicsProcessing.Config.Builder builder =
    246                 new DynamicsProcessing.Config.Builder(
    247                         values[0].intValue(),
    248                         mChannelCount,
    249                         values[2].intValue() > 0 /*use preEQ*/,
    250                         values[3].intValue() /*pre eq bands*/,
    251                         values[4].intValue() > 0 /*use mbc*/,
    252                         values[5].intValue() /*mbc bands*/,
    253                         values[6].intValue() > 0 /*use postEQ*/,
    254                         values[7].intValue()/*postEq bands*/,
    255                         values[8].intValue() > 0 /*use Limiter*/).
    256                 setPreferredFrameDuration(values[1].floatValue());
    257         Config config = builder.build();
    258         for (int ch = 0; ch < mChannelCount; ch++) {
    259             Channel channel = queryEngineByChannelIndex(ch);
    260             config.setChannelTo(ch, channel);
    261         }
    262         return config;
    263     }
    264 
    265 
    266     private static final int CONFIG_DEFAULT_VARIANT = VARIANT_FAVOR_FREQUENCY_RESOLUTION;
    267     private static final boolean CONFIG_DEFAULT_USE_PREEQ = true;
    268     private static final int CONFIG_DEFAULT_PREEQ_BANDS = 6;
    269     private static final boolean CONFIG_DEFAULT_USE_MBC = true;
    270     private static final int CONFIG_DEFAULT_MBC_BANDS = 6;
    271     private static final boolean CONFIG_DEFAULT_USE_POSTEQ = true;
    272     private static final int CONFIG_DEFAULT_POSTEQ_BANDS = 6;
    273     private static final boolean CONFIG_DEFAULT_USE_LIMITER = true;
    274 
    275     private static final float CHANNEL_DEFAULT_INPUT_GAIN = 0; // dB
    276     private static final float CONFIG_PREFERRED_FRAME_DURATION_MS = 10.0f; //milliseconds
    277 
    278     private static final float EQ_DEFAULT_GAIN = 0; // dB
    279     private static final boolean PREEQ_DEFAULT_ENABLED = true;
    280     private static final boolean POSTEQ_DEFAULT_ENABLED = true;
    281 
    282     private static final boolean MBC_DEFAULT_ENABLED = true;
    283     private static final float MBC_DEFAULT_ATTACK_TIME = 3; // ms
    284     private static final float MBC_DEFAULT_RELEASE_TIME = 80; // ms
    285     private static final float MBC_DEFAULT_RATIO = 1; // N:1
    286     private static final float MBC_DEFAULT_THRESHOLD = -45; // dB
    287     private static final float MBC_DEFAULT_KNEE_WIDTH = 0; // dB
    288     private static final float MBC_DEFAULT_NOISE_GATE_THRESHOLD = -90; // dB
    289     private static final float MBC_DEFAULT_EXPANDER_RATIO = 1; // 1:N
    290     private static final float MBC_DEFAULT_PRE_GAIN = 0; // dB
    291     private static final float MBC_DEFAULT_POST_GAIN = 0; // dB
    292 
    293     private static final boolean LIMITER_DEFAULT_ENABLED = true;
    294     private static final int LIMITER_DEFAULT_LINK_GROUP = 0;//;
    295     private static final float LIMITER_DEFAULT_ATTACK_TIME = 1; // ms
    296     private static final float LIMITER_DEFAULT_RELEASE_TIME = 60; // ms
    297     private static final float LIMITER_DEFAULT_RATIO = 10; // N:1
    298     private static final float LIMITER_DEFAULT_THRESHOLD = -2; // dB
    299     private static final float LIMITER_DEFAULT_POST_GAIN = 0; // dB
    300 
    301     private static final float DEFAULT_MIN_FREQUENCY = 220; // Hz
    302     private static final float DEFAULT_MAX_FREQUENCY = 20000; // Hz
    303     private static final float mMinFreqLog = (float)Math.log10(DEFAULT_MIN_FREQUENCY);
    304     private static final float mMaxFreqLog = (float)Math.log10(DEFAULT_MAX_FREQUENCY);
    305 
    306     /**
    307      * base class for the different stages.
    308      */
    309     public static class Stage {
    310         private boolean mInUse;
    311         private boolean mEnabled;
    312         /**
    313          * Class constructor for stage
    314          * @param inUse true if this stage is set to be used. False otherwise. Stages that are not
    315          * set "inUse" at initialization time are not available to be used at any time.
    316          * @param enabled true if this stage is currently used to process sound. When disabled,
    317          * the stage is bypassed and the sound is copied unaltered from input to output.
    318          */
    319         public Stage(boolean inUse, boolean enabled) {
    320             mInUse = inUse;
    321             mEnabled = enabled;
    322         }
    323 
    324         /**
    325          * returns enabled state of the stage
    326          * @return true if stage is enabled for processing, false otherwise
    327          */
    328         public boolean isEnabled() {
    329             return mEnabled;
    330         }
    331         /**
    332          * sets enabled state of the stage
    333          * @param enabled true for enabled, false otherwise
    334          */
    335         public void setEnabled(boolean enabled) {
    336             mEnabled = enabled;
    337         }
    338 
    339         /**
    340          * returns inUse state of the stage.
    341          * @return inUse state of the stage. True if this stage is currently used to process sound.
    342          * When false, the stage is bypassed and the sound is copied unaltered from input to output.
    343          */
    344         public boolean isInUse() {
    345             return mInUse;
    346         }
    347 
    348         @Override
    349         public String toString() {
    350             StringBuilder sb = new StringBuilder();
    351             sb.append(String.format(" Stage InUse: %b\n", isInUse()));
    352             if (isInUse()) {
    353                 sb.append(String.format(" Stage Enabled: %b\n", mEnabled));
    354             }
    355             return sb.toString();
    356         }
    357     }
    358 
    359     /**
    360      * Base class for stages that hold bands
    361      */
    362     public static class BandStage extends Stage{
    363         private int mBandCount;
    364         /**
    365          * Class constructor for BandStage
    366          * @param inUse true if this stage is set to be used. False otherwise. Stages that are not
    367          * set "inUse" at initialization time are not available to be used at any time.
    368          * @param enabled true if this stage is currently used to process sound. When disabled,
    369          * the stage is bypassed and the sound is copied unaltered from input to output.
    370          * @param bandCount number of bands this stage will handle. If stage is not inUse, bandcount
    371          * is set to 0
    372          */
    373         public BandStage(boolean inUse, boolean enabled, int bandCount) {
    374             super(inUse, enabled);
    375             mBandCount = isInUse() ? bandCount : 0;
    376         }
    377 
    378         /**
    379          * gets number of bands held in this stage
    380          * @return number of bands held in this stage
    381          */
    382         public int getBandCount() {
    383             return mBandCount;
    384         }
    385 
    386         @Override
    387         public String toString() {
    388             StringBuilder sb = new StringBuilder();
    389             sb.append(super.toString());
    390             if (isInUse()) {
    391                 sb.append(String.format(" Band Count: %d\n", mBandCount));
    392             }
    393             return sb.toString();
    394         }
    395     }
    396 
    397     /**
    398      * Base class for bands
    399      */
    400     public static class BandBase {
    401         private boolean mEnabled;
    402         private float mCutoffFrequency;
    403         /**
    404          * Class constructor for BandBase
    405          * @param enabled true if this band is currently used to process sound. When false,
    406          * the band is effectively muted and sound set to zero.
    407          * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
    408          * effective bandwidth for the band is then computed using this and the previous band
    409          * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
    410          * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
    411          */
    412         public BandBase(boolean enabled, float cutoffFrequency) {
    413             mEnabled = enabled;
    414             mCutoffFrequency = cutoffFrequency;
    415         }
    416 
    417         @Override
    418         public String toString() {
    419             StringBuilder sb = new StringBuilder();
    420             sb.append(String.format(" Enabled: %b\n", mEnabled));
    421             sb.append(String.format(" CutoffFrequency: %f\n", mCutoffFrequency));
    422             return sb.toString();
    423         }
    424 
    425         /**
    426          * returns enabled state of the band
    427          * @return true if bands is enabled for processing, false otherwise
    428          */
    429         public boolean isEnabled() {
    430             return mEnabled;
    431         }
    432         /**
    433          * sets enabled state of the band
    434          * @param enabled true for enabled, false otherwise
    435          */
    436         public void setEnabled(boolean enabled) {
    437             mEnabled = enabled;
    438         }
    439 
    440         /**
    441          * gets cutoffFrequency for this band in Hertz (Hz)
    442          * @return cutoffFrequency for this band in Hertz (Hz)
    443          */
    444         public float getCutoffFrequency() {
    445             return mCutoffFrequency;
    446         }
    447 
    448         /**
    449          * sets topmost frequency number (in Hz) this band will process. The
    450          * effective bandwidth for the band is then computed using this and the previous band
    451          * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
    452          * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
    453          * @param frequency
    454          */
    455         public void setCutoffFrequency(float frequency) {
    456             mCutoffFrequency = frequency;
    457         }
    458     }
    459 
    460     /**
    461      * Class for Equalizer Bands
    462      * Equalizer bands have three controllable parameters: enabled/disabled, cutoffFrequency and
    463      * gain
    464      */
    465     public final static class EqBand extends BandBase {
    466         private float mGain;
    467         /**
    468          * Class constructor for EqBand
    469          * @param enabled true if this band is currently used to process sound. When false,
    470          * the band is effectively muted and sound set to zero.
    471          * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
    472          * effective bandwidth for the band is then computed using this and the previous band
    473          * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
    474          * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
    475          * @param gain of equalizer band in decibels (dB). A gain of 0 dB means no change in level.
    476          */
    477         public EqBand(boolean enabled, float cutoffFrequency, float gain) {
    478             super(enabled, cutoffFrequency);
    479             mGain = gain;
    480         }
    481 
    482         /**
    483          * Class constructor for EqBand
    484          * @param cfg copy constructor
    485          */
    486         public EqBand(EqBand cfg) {
    487             super(cfg.isEnabled(), cfg.getCutoffFrequency());
    488             mGain = cfg.mGain;
    489         }
    490 
    491         @Override
    492         public String toString() {
    493             StringBuilder sb = new StringBuilder();
    494             sb.append(super.toString());
    495             sb.append(String.format(" Gain: %f\n", mGain));
    496             return sb.toString();
    497         }
    498 
    499         /**
    500          * gets current gain of band in decibels (dB)
    501          * @return current gain of band in decibels (dB)
    502          */
    503         public float getGain() {
    504             return mGain;
    505         }
    506 
    507         /**
    508          * sets current gain of band in decibels (dB)
    509          * @param gain desired in decibels (db)
    510          */
    511         public void setGain(float gain) {
    512             mGain = gain;
    513         }
    514     }
    515 
    516     /**
    517      * Class for Multi-Band compressor bands
    518      * MBC bands have multiple controllable parameters: enabled/disabled, cutoffFrequency,
    519      * attackTime, releaseTime, ratio, threshold, kneeWidth, noiseGateThreshold, expanderRatio,
    520      * preGain and postGain.
    521      */
    522     public final static class MbcBand extends BandBase{
    523         private float mAttackTime;
    524         private float mReleaseTime;
    525         private float mRatio;
    526         private float mThreshold;
    527         private float mKneeWidth;
    528         private float mNoiseGateThreshold;
    529         private float mExpanderRatio;
    530         private float mPreGain;
    531         private float mPostGain;
    532         /**
    533          * Class constructor for MbcBand
    534          * @param enabled true if this band is currently used to process sound. When false,
    535          * the band is effectively muted and sound set to zero.
    536          * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
    537          * effective bandwidth for the band is then computed using this and the previous band
    538          * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
    539          * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
    540          * @param attackTime Attack Time for compressor in milliseconds (ms)
    541          * @param releaseTime Release Time for compressor in milliseconds (ms)
    542          * @param ratio Compressor ratio (N:1) (input:output)
    543          * @param threshold Compressor threshold measured in decibels (dB) from 0 dB Full Scale
    544          * (dBFS).
    545          * @param kneeWidth Width in decibels (dB) around compressor threshold point.
    546          * @param noiseGateThreshold Noise gate threshold in decibels (dB) from 0 dB Full Scale
    547          * (dBFS).
    548          * @param expanderRatio Expander ratio (1:N) (input:output) for signals below the Noise Gate
    549          * Threshold.
    550          * @param preGain Gain applied to the signal BEFORE the compression.
    551          * @param postGain Gain applied to the signal AFTER compression.
    552          */
    553         public MbcBand(boolean enabled, float cutoffFrequency, float attackTime, float releaseTime,
    554                 float ratio, float threshold, float kneeWidth, float noiseGateThreshold,
    555                 float expanderRatio, float preGain, float postGain) {
    556             super(enabled, cutoffFrequency);
    557             mAttackTime = attackTime;
    558             mReleaseTime = releaseTime;
    559             mRatio = ratio;
    560             mThreshold = threshold;
    561             mKneeWidth = kneeWidth;
    562             mNoiseGateThreshold = noiseGateThreshold;
    563             mExpanderRatio = expanderRatio;
    564             mPreGain = preGain;
    565             mPostGain = postGain;
    566         }
    567 
    568         /**
    569          * Class constructor for MbcBand
    570          * @param cfg copy constructor
    571          */
    572         public MbcBand(MbcBand cfg) {
    573             super(cfg.isEnabled(), cfg.getCutoffFrequency());
    574             mAttackTime = cfg.mAttackTime;
    575             mReleaseTime = cfg.mReleaseTime;
    576             mRatio = cfg.mRatio;
    577             mThreshold = cfg.mThreshold;
    578             mKneeWidth = cfg.mKneeWidth;
    579             mNoiseGateThreshold = cfg.mNoiseGateThreshold;
    580             mExpanderRatio = cfg.mExpanderRatio;
    581             mPreGain = cfg.mPreGain;
    582             mPostGain = cfg.mPostGain;
    583         }
    584 
    585         @Override
    586         public String toString() {
    587             StringBuilder sb = new StringBuilder();
    588             sb.append(super.toString());
    589             sb.append(String.format(" AttackTime: %f (ms)\n", mAttackTime));
    590             sb.append(String.format(" ReleaseTime: %f (ms)\n", mReleaseTime));
    591             sb.append(String.format(" Ratio: 1:%f\n", mRatio));
    592             sb.append(String.format(" Threshold: %f (dB)\n", mThreshold));
    593             sb.append(String.format(" NoiseGateThreshold: %f(dB)\n", mNoiseGateThreshold));
    594             sb.append(String.format(" ExpanderRatio: %f:1\n", mExpanderRatio));
    595             sb.append(String.format(" PreGain: %f (dB)\n", mPreGain));
    596             sb.append(String.format(" PostGain: %f (dB)\n", mPostGain));
    597             return sb.toString();
    598         }
    599 
    600         /**
    601          * gets attack time for compressor in milliseconds (ms)
    602          * @return attack time for compressor in milliseconds (ms)
    603          */
    604         public float getAttackTime() { return mAttackTime; }
    605         /**
    606          * sets attack time for compressor in milliseconds (ms)
    607          * @param attackTime desired for compressor in milliseconds (ms)
    608          */
    609         public void setAttackTime(float attackTime) { mAttackTime = attackTime; }
    610         /**
    611          * gets release time for compressor in milliseconds (ms)
    612          * @return release time for compressor in milliseconds (ms)
    613          */
    614         public float getReleaseTime() { return mReleaseTime; }
    615         /**
    616          * sets release time for compressor in milliseconds (ms)
    617          * @param releaseTime desired for compressor in milliseconds (ms)
    618          */
    619         public void setReleaseTime(float releaseTime) { mReleaseTime = releaseTime; }
    620         /**
    621          * gets the compressor ratio (N:1)
    622          * @return compressor ratio (N:1)
    623          */
    624         public float getRatio() { return mRatio; }
    625         /**
    626          * sets compressor ratio (N:1)
    627          * @param ratio desired for the compressor (N:1)
    628          */
    629         public void setRatio(float ratio) { mRatio = ratio; }
    630         /**
    631          * gets the compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
    632          * Thresholds are negative. A threshold of 0 dB means no compression will take place.
    633          * @return compressor threshold in decibels (dB)
    634          */
    635         public float getThreshold() { return mThreshold; }
    636         /**
    637          * sets the compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
    638          * Thresholds are negative. A threshold of 0 dB means no compression will take place.
    639          * @param threshold desired for compressor in decibels(dB)
    640          */
    641         public void setThreshold(float threshold) { mThreshold = threshold; }
    642         /**
    643          * get Knee Width in decibels (dB) around compressor threshold point. Widths are always
    644          * positive, with higher values representing a wider area of transition from the linear zone
    645          * to the compression zone. A knee of 0 dB means a more abrupt transition.
    646          * @return Knee Width in decibels (dB)
    647          */
    648         public float getKneeWidth() { return mKneeWidth; }
    649         /**
    650          * sets knee width in decibels (dB). See
    651          * {@link android.media.audiofx.DynamicsProcessing.MbcBand#getKneeWidth} for more
    652          * information.
    653          * @param kneeWidth desired in decibels (dB)
    654          */
    655         public void setKneeWidth(float kneeWidth) { mKneeWidth = kneeWidth; }
    656         /**
    657          * gets the noise gate threshold in decibels (dB) from 0 dB Full Scale (dBFS). Noise gate
    658          * thresholds are negative. Signals below this level will be expanded according the
    659          * expanderRatio parameter. A Noise Gate Threshold of -75 dB means very quiet signals might
    660          * be effectively removed from the signal.
    661          * @return Noise Gate Threshold in decibels (dB)
    662          */
    663         public float getNoiseGateThreshold() { return mNoiseGateThreshold; }
    664         /**
    665          * sets noise gate threshod in decibels (dB). See
    666          * {@link android.media.audiofx.DynamicsProcessing.MbcBand#getNoiseGateThreshold} for more
    667          * information.
    668          * @param noiseGateThreshold desired in decibels (dB)
    669          */
    670         public void setNoiseGateThreshold(float noiseGateThreshold) {
    671             mNoiseGateThreshold = noiseGateThreshold; }
    672         /**
    673          * gets Expander ratio (1:N) for signals below the Noise Gate Threshold.
    674          * @return Expander ratio (1:N)
    675          */
    676         public float getExpanderRatio() { return mExpanderRatio; }
    677         /**
    678          * sets Expander ratio (1:N) for signals below the Noise Gate Threshold.
    679          * @param expanderRatio desired expander ratio (1:N)
    680          */
    681         public void setExpanderRatio(float expanderRatio) { mExpanderRatio = expanderRatio; }
    682         /**
    683          * gets the gain applied to the signal BEFORE the compression. Measured in decibels (dB)
    684          * where 0 dB means no level change.
    685          * @return preGain value in decibels (dB)
    686          */
    687         public float getPreGain() { return mPreGain; }
    688         /**
    689          * sets the gain to be applied to the signal BEFORE the compression, measured in decibels
    690          * (dB), where 0 dB means no level change.
    691          * @param preGain desired in decibels (dB)
    692          */
    693         public void setPreGain(float preGain) { mPreGain = preGain; }
    694         /**
    695          * gets the gain applied to the signal AFTER compression. Measured in decibels (dB) where 0
    696          * dB means no level change
    697          * @return postGain value in decibels (dB)
    698          */
    699         public float getPostGain() { return mPostGain; }
    700         /**
    701          * sets the gain to be applied to the siganl AFTER the compression. Measured in decibels
    702          * (dB), where 0 dB means no level change.
    703          * @param postGain desired value in decibels (dB)
    704          */
    705         public void setPostGain(float postGain) { mPostGain = postGain; }
    706     }
    707 
    708     /**
    709      * Class for Equalizer stage
    710      */
    711     public final static class Eq extends BandStage {
    712         private final EqBand[] mBands;
    713         /**
    714          * Class constructor for Equalizer (Eq) stage
    715          * @param inUse true if Eq stage will be used, false otherwise.
    716          * @param enabled true if Eq stage is enabled/disabled. This can be changed while effect is
    717          * running
    718          * @param bandCount number of bands for this Equalizer stage. Can't be changed while effect
    719          * is running
    720          */
    721         public Eq(boolean inUse, boolean enabled, int bandCount) {
    722             super(inUse, enabled, bandCount);
    723             if (isInUse()) {
    724                 mBands = new EqBand[bandCount];
    725                 for (int b = 0; b < bandCount; b++) {
    726                     float freq = DEFAULT_MAX_FREQUENCY;
    727                     if (bandCount > 1) {
    728                         freq = (float)Math.pow(10, mMinFreqLog +
    729                                 b * (mMaxFreqLog - mMinFreqLog)/(bandCount -1));
    730                     }
    731                     mBands[b] = new EqBand(true, freq, EQ_DEFAULT_GAIN);
    732                 }
    733             } else {
    734                 mBands = null;
    735             }
    736         }
    737         /**
    738          * Class constructor for Eq stage
    739          * @param cfg copy constructor
    740          */
    741         public Eq(Eq cfg) {
    742             super(cfg.isInUse(), cfg.isEnabled(), cfg.getBandCount());
    743             if (isInUse()) {
    744                 mBands = new EqBand[cfg.mBands.length];
    745                 for (int b = 0; b < mBands.length; b++) {
    746                     mBands[b] = new EqBand(cfg.mBands[b]);
    747                 }
    748             } else {
    749                 mBands = null;
    750             }
    751         }
    752 
    753         @Override
    754         public String toString() {
    755             StringBuilder sb = new StringBuilder();
    756             sb.append(super.toString());
    757             if (isInUse()) {
    758                 sb.append("--->EqBands: " + mBands.length + "\n");
    759                 for (int b = 0; b < mBands.length; b++) {
    760                     sb.append(String.format("  Band %d\n", b));
    761                     sb.append(mBands[b].toString());
    762                 }
    763             }
    764             return sb.toString();
    765         }
    766         /**
    767          * Helper function to check if band index is within range
    768          * @param band index to check
    769          */
    770         private void checkBand(int band) {
    771             if (mBands == null || band < 0 || band >= mBands.length) {
    772                 throw new IllegalArgumentException("band index " + band +" out of bounds");
    773             }
    774         }
    775         /**
    776          * Sets EqBand object for given band index
    777          * @param band index of band to be modified
    778          * @param bandCfg EqBand object.
    779          */
    780         public void setBand(int band, EqBand bandCfg) {
    781             checkBand(band);
    782             mBands[band] = new EqBand(bandCfg);
    783         }
    784         /**
    785          * Gets EqBand object for band of interest.
    786          * @param band index of band of interest
    787          * @return EqBand Object
    788          */
    789         public EqBand getBand(int band) {
    790             checkBand(band);
    791             return mBands[band];
    792         }
    793     }
    794 
    795     /**
    796      * Class for Multi-Band Compressor (MBC) stage
    797      */
    798     public final static class Mbc extends BandStage {
    799         private final MbcBand[] mBands;
    800         /**
    801          * Constructor for Multi-Band Compressor (MBC) stage
    802          * @param inUse true if MBC stage will be used, false otherwise.
    803          * @param enabled true if MBC stage is enabled/disabled. This can be changed while effect
    804          * is running
    805          * @param bandCount number of bands for this MBC stage. Can't be changed while effect is
    806          * running
    807          */
    808         public Mbc(boolean inUse, boolean enabled, int bandCount) {
    809             super(inUse, enabled, bandCount);
    810             if (isInUse()) {
    811                 mBands = new MbcBand[bandCount];
    812                 for (int b = 0; b < bandCount; b++) {
    813                     float freq = DEFAULT_MAX_FREQUENCY;
    814                     if (bandCount > 1) {
    815                         freq = (float)Math.pow(10, mMinFreqLog +
    816                                 b * (mMaxFreqLog - mMinFreqLog)/(bandCount -1));
    817                     }
    818                     mBands[b] = new MbcBand(true, freq, MBC_DEFAULT_ATTACK_TIME,
    819                             MBC_DEFAULT_RELEASE_TIME, MBC_DEFAULT_RATIO,
    820                             MBC_DEFAULT_THRESHOLD, MBC_DEFAULT_KNEE_WIDTH,
    821                             MBC_DEFAULT_NOISE_GATE_THRESHOLD, MBC_DEFAULT_EXPANDER_RATIO,
    822                             MBC_DEFAULT_PRE_GAIN, MBC_DEFAULT_POST_GAIN);
    823                 }
    824             } else {
    825                 mBands = null;
    826             }
    827         }
    828         /**
    829          * Class constructor for MBC stage
    830          * @param cfg copy constructor
    831          */
    832         public Mbc(Mbc cfg) {
    833             super(cfg.isInUse(), cfg.isEnabled(), cfg.getBandCount());
    834             if (isInUse()) {
    835                 mBands = new MbcBand[cfg.mBands.length];
    836                 for (int b = 0; b < mBands.length; b++) {
    837                     mBands[b] = new MbcBand(cfg.mBands[b]);
    838                 }
    839             } else {
    840                 mBands = null;
    841             }
    842         }
    843 
    844         @Override
    845         public String toString() {
    846             StringBuilder sb = new StringBuilder();
    847             sb.append(super.toString());
    848             if (isInUse()) {
    849                 sb.append("--->MbcBands: " + mBands.length + "\n");
    850                 for (int b = 0; b < mBands.length; b++) {
    851                     sb.append(String.format("  Band %d\n", b));
    852                     sb.append(mBands[b].toString());
    853                 }
    854             }
    855             return sb.toString();
    856         }
    857         /**
    858          * Helper function to check if band index is within range
    859          * @param band index to check
    860          */
    861         private void checkBand(int band) {
    862             if (mBands == null || band < 0 || band >= mBands.length) {
    863                 throw new IllegalArgumentException("band index " + band +" out of bounds");
    864             }
    865         }
    866         /**
    867          * Sets MbcBand object for given band index
    868          * @param band index of band to be modified
    869          * @param bandCfg MbcBand object.
    870          */
    871         public void setBand(int band, MbcBand bandCfg) {
    872             checkBand(band);
    873             mBands[band] = new MbcBand(bandCfg);
    874         }
    875         /**
    876          * Gets MbcBand object for band of interest.
    877          * @param band index of band of interest
    878          * @return MbcBand Object
    879          */
    880         public MbcBand getBand(int band) {
    881             checkBand(band);
    882             return mBands[band];
    883         }
    884     }
    885 
    886     /**
    887      * Class for Limiter Stage
    888      * Limiter is a single band compressor at the end of the processing chain, commonly used to
    889      * protect the signal from overloading and distortion. Limiters have multiple controllable
    890      * parameters: enabled/disabled, linkGroup, attackTime, releaseTime, ratio, threshold, and
    891      * postGain.
    892      * <p>Limiters can be linked in groups across multiple channels. Linked limiters will trigger
    893      * the same limiting if any of the linked limiters starts compressing.
    894      */
    895     public final static class Limiter extends Stage {
    896         private int mLinkGroup;
    897         private float mAttackTime;
    898         private float mReleaseTime;
    899         private float mRatio;
    900         private float mThreshold;
    901         private float mPostGain;
    902 
    903         /**
    904          * Class constructor for Limiter Stage
    905          * @param inUse true if MBC stage will be used, false otherwise.
    906          * @param enabled true if MBC stage is enabled/disabled. This can be changed while effect
    907          * is running
    908          * @param linkGroup index of group assigned to this Limiter. Only limiters that share the
    909          * same linkGroup index will react together.
    910          * @param attackTime Attack Time for limiter compressor in milliseconds (ms)
    911          * @param releaseTime Release Time for limiter compressor in milliseconds (ms)
    912          * @param ratio Limiter Compressor ratio (N:1) (input:output)
    913          * @param threshold Limiter Compressor threshold measured in decibels (dB) from 0 dB Full
    914          * Scale (dBFS).
    915          * @param postGain Gain applied to the signal AFTER compression.
    916          */
    917         public Limiter(boolean inUse, boolean enabled, int linkGroup, float attackTime,
    918                 float releaseTime, float ratio, float threshold, float postGain) {
    919             super(inUse, enabled);
    920             mLinkGroup = linkGroup;
    921             mAttackTime = attackTime;
    922             mReleaseTime = releaseTime;
    923             mRatio = ratio;
    924             mThreshold = threshold;
    925             mPostGain = postGain;
    926         }
    927 
    928         /**
    929          * Class Constructor for Limiter
    930          * @param cfg copy constructor
    931          */
    932         public Limiter(Limiter cfg) {
    933             super(cfg.isInUse(), cfg.isEnabled());
    934             mLinkGroup = cfg.mLinkGroup;
    935             mAttackTime = cfg.mAttackTime;
    936             mReleaseTime = cfg.mReleaseTime;
    937             mRatio = cfg.mRatio;
    938             mThreshold = cfg.mThreshold;
    939             mPostGain = cfg.mPostGain;
    940         }
    941 
    942         @Override
    943         public String toString() {
    944             StringBuilder sb = new StringBuilder();
    945             sb.append(super.toString());
    946             if (isInUse()) {
    947                 sb.append(String.format(" LinkGroup: %d (group)\n", mLinkGroup));
    948                 sb.append(String.format(" AttackTime: %f (ms)\n", mAttackTime));
    949                 sb.append(String.format(" ReleaseTime: %f (ms)\n", mReleaseTime));
    950                 sb.append(String.format(" Ratio: 1:%f\n", mRatio));
    951                 sb.append(String.format(" Threshold: %f (dB)\n", mThreshold));
    952                 sb.append(String.format(" PostGain: %f (dB)\n", mPostGain));
    953             }
    954             return sb.toString();
    955         }
    956         /**
    957          * Gets the linkGroup index for this Limiter Stage. Only limiters that share the same
    958          * linkGroup index will react together.
    959          * @return linkGroup index.
    960          */
    961         public int getLinkGroup() { return mLinkGroup; }
    962         /**
    963          * Sets the linkGroup index for this limiter Stage.
    964          * @param linkGroup desired linkGroup index
    965          */
    966         public void setLinkGroup(int linkGroup) { mLinkGroup = linkGroup; }
    967         /**
    968          * gets attack time for limiter compressor in milliseconds (ms)
    969          * @return attack time for limiter compressor in milliseconds (ms)
    970          */
    971         public float getAttackTime() { return mAttackTime; }
    972         /**
    973          * sets attack time for limiter compressor in milliseconds (ms)
    974          * @param attackTime desired for limiter compressor in milliseconds (ms)
    975          */
    976         public void setAttackTime(float attackTime) { mAttackTime = attackTime; }
    977         /**
    978          * gets release time for limiter compressor in milliseconds (ms)
    979          * @return release time for limiter compressor in milliseconds (ms)
    980          */
    981         public float getReleaseTime() { return mReleaseTime; }
    982         /**
    983          * sets release time for limiter compressor in milliseconds (ms)
    984          * @param releaseTime desired for limiter compressor in milliseconds (ms)
    985          */
    986         public void setReleaseTime(float releaseTime) { mReleaseTime = releaseTime; }
    987         /**
    988          * gets the limiter compressor ratio (N:1)
    989          * @return limiter compressor ratio (N:1)
    990          */
    991         public float getRatio() { return mRatio; }
    992         /**
    993          * sets limiter compressor ratio (N:1)
    994          * @param ratio desired for the limiter compressor (N:1)
    995          */
    996         public void setRatio(float ratio) { mRatio = ratio; }
    997         /**
    998          * gets the limiter compressor threshold measured in decibels (dB) from 0 dB Full Scale
    999          * (dBFS). Thresholds are negative. A threshold of 0 dB means no limiting will take place.
   1000          * @return limiter compressor threshold in decibels (dB)
   1001          */
   1002         public float getThreshold() { return mThreshold; }
   1003         /**
   1004          * sets the limiter compressor threshold measured in decibels (dB) from 0 dB Full Scale
   1005          * (dBFS). Thresholds are negative. A threshold of 0 dB means no limiting will take place.
   1006          * @param threshold desired for limiter compressor in decibels(dB)
   1007          */
   1008         public void setThreshold(float threshold) { mThreshold = threshold; }
   1009         /**
   1010          * gets the gain applied to the signal AFTER limiting. Measured in decibels (dB) where 0
   1011          * dB means no level change
   1012          * @return postGain value in decibels (dB)
   1013          */
   1014         public float getPostGain() { return mPostGain; }
   1015         /**
   1016          * sets the gain to be applied to the siganl AFTER the limiter. Measured in decibels
   1017          * (dB), where 0 dB means no level change.
   1018          * @param postGain desired value in decibels (dB)
   1019          */
   1020         public void setPostGain(float postGain) { mPostGain = postGain; }
   1021     }
   1022 
   1023     /**
   1024      * Class for Channel configuration parameters. It is composed of multiple stages, which can be
   1025      * used/enabled independently. Stages not used or disabled will be bypassed and the sound would
   1026      * be unaffected by them.
   1027      */
   1028     public final static class Channel {
   1029         private float   mInputGain;
   1030         private Eq      mPreEq;
   1031         private Mbc     mMbc;
   1032         private Eq      mPostEq;
   1033         private Limiter mLimiter;
   1034 
   1035         /**
   1036          * Class constructor for Channel configuration.
   1037          * @param inputGain value in decibels (dB) of level change applied to the audio before
   1038          * processing. A value of 0 dB means no change.
   1039          * @param preEqInUse true if PreEq stage will be used, false otherwise. This can't be
   1040          * changed later.
   1041          * @param preEqBandCount number of bands for PreEq stage. This can't be changed later.
   1042          * @param mbcInUse true if Mbc stage will be used, false otherwise. This can't be changed
   1043          * later.
   1044          * @param mbcBandCount number of bands for Mbc stage. This can't be changed later.
   1045          * @param postEqInUse true if PostEq stage will be used, false otherwise. This can't be
   1046          * changed later.
   1047          * @param postEqBandCount number of bands for PostEq stage. This can't be changed later.
   1048          * @param limiterInUse true if Limiter stage will be used, false otherwise. This can't be
   1049          * changed later.
   1050          */
   1051         public Channel (float inputGain,
   1052                 boolean preEqInUse, int preEqBandCount,
   1053                 boolean mbcInUse, int mbcBandCount,
   1054                 boolean postEqInUse, int postEqBandCount,
   1055                 boolean limiterInUse) {
   1056             mInputGain = inputGain;
   1057             mPreEq = new Eq(preEqInUse, PREEQ_DEFAULT_ENABLED, preEqBandCount);
   1058             mMbc = new Mbc(mbcInUse, MBC_DEFAULT_ENABLED, mbcBandCount);
   1059             mPostEq = new Eq(postEqInUse, POSTEQ_DEFAULT_ENABLED,
   1060                     postEqBandCount);
   1061             mLimiter = new Limiter(limiterInUse,
   1062                     LIMITER_DEFAULT_ENABLED, LIMITER_DEFAULT_LINK_GROUP,
   1063                     LIMITER_DEFAULT_ATTACK_TIME, LIMITER_DEFAULT_RELEASE_TIME,
   1064                     LIMITER_DEFAULT_RATIO, LIMITER_DEFAULT_THRESHOLD, LIMITER_DEFAULT_POST_GAIN);
   1065         }
   1066 
   1067         /**
   1068          * Class constructor for Channel configuration
   1069          * @param cfg copy constructor
   1070          */
   1071         public Channel(Channel cfg) {
   1072             mInputGain = cfg.mInputGain;
   1073             mPreEq = new Eq(cfg.mPreEq);
   1074             mMbc = new Mbc(cfg.mMbc);
   1075             mPostEq = new Eq(cfg.mPostEq);
   1076             mLimiter = new Limiter(cfg.mLimiter);
   1077         }
   1078 
   1079         @Override
   1080         public String toString() {
   1081             StringBuilder sb = new StringBuilder();
   1082             sb.append(String.format(" InputGain: %f\n", mInputGain));
   1083             sb.append("-->PreEq\n");
   1084             sb.append(mPreEq.toString());
   1085             sb.append("-->MBC\n");
   1086             sb.append(mMbc.toString());
   1087             sb.append("-->PostEq\n");
   1088             sb.append(mPostEq.toString());
   1089             sb.append("-->Limiter\n");
   1090             sb.append(mLimiter.toString());
   1091             return sb.toString();
   1092         }
   1093         /**
   1094          * Gets inputGain value in decibels (dB). 0 dB means no change;
   1095          * @return gain value in decibels (dB)
   1096          */
   1097         public float getInputGain() {
   1098             return mInputGain;
   1099         }
   1100         /**
   1101          * Sets inputGain value in decibels (dB). 0 dB means no change;
   1102          * @param inputGain desired gain value in decibels (dB)
   1103          */
   1104         public void setInputGain(float inputGain) {
   1105             mInputGain = inputGain;
   1106         }
   1107 
   1108         /**
   1109          * Gets PreEq configuration stage
   1110          * @return PreEq configuration stage
   1111          */
   1112         public Eq getPreEq() {
   1113             return mPreEq;
   1114         }
   1115         /**
   1116          * Sets PreEq configuration stage. New PreEq stage must have the same number of bands than
   1117          * original PreEq stage.
   1118          * @param preEq configuration
   1119          */
   1120         public void setPreEq(Eq preEq) {
   1121             if (preEq.getBandCount() != mPreEq.getBandCount()) {
   1122                 throw new IllegalArgumentException("PreEqBandCount changed from " +
   1123                         mPreEq.getBandCount() + " to " + preEq.getBandCount());
   1124             }
   1125             mPreEq = new Eq(preEq);
   1126         }
   1127         /**
   1128          * Gets EqBand for PreEq stage for given band index.
   1129          * @param band index of band of interest from PreEq stage
   1130          * @return EqBand configuration
   1131          */
   1132         public EqBand getPreEqBand(int band) {
   1133             return mPreEq.getBand(band);
   1134         }
   1135         /**
   1136          * Sets EqBand for PreEq stage for given band index
   1137          * @param band index of band of interest from PreEq stage
   1138          * @param preEqBand configuration to be set.
   1139          */
   1140         public void setPreEqBand(int band, EqBand preEqBand) {
   1141             mPreEq.setBand(band, preEqBand);
   1142         }
   1143 
   1144         /**
   1145          * Gets Mbc configuration stage
   1146          * @return Mbc configuration stage
   1147          */
   1148         public Mbc getMbc() {
   1149             return mMbc;
   1150         }
   1151         /**
   1152          * Sets Mbc configuration stage. New Mbc stage must have the same number of bands than
   1153          * original Mbc stage.
   1154          * @param mbc
   1155          */
   1156         public void setMbc(Mbc mbc) {
   1157             if (mbc.getBandCount() != mMbc.getBandCount()) {
   1158                 throw new IllegalArgumentException("MbcBandCount changed from " +
   1159                         mMbc.getBandCount() + " to " + mbc.getBandCount());
   1160             }
   1161             mMbc = new Mbc(mbc);
   1162         }
   1163         /**
   1164          * Gets MbcBand configuration for Mbc stage, for given band index.
   1165          * @param band index of band of interest from Mbc stage
   1166          * @return MbcBand configuration
   1167          */
   1168         public MbcBand getMbcBand(int band) {
   1169             return mMbc.getBand(band);
   1170         }
   1171         /**
   1172          * Sets MbcBand for Mbc stage for given band index
   1173          * @param band index of band of interest from Mbc Stage
   1174          * @param mbcBand configuration to be set
   1175          */
   1176         public void setMbcBand(int band, MbcBand mbcBand) {
   1177             mMbc.setBand(band, mbcBand);
   1178         }
   1179 
   1180         /**
   1181          * Gets PostEq configuration stage
   1182          * @return PostEq configuration stage
   1183          */
   1184         public Eq getPostEq() {
   1185             return mPostEq;
   1186         }
   1187         /**
   1188          * Sets PostEq configuration stage. New PostEq stage must have the same number of bands than
   1189          * original PostEq stage.
   1190          * @param postEq configuration
   1191          */
   1192         public void setPostEq(Eq postEq) {
   1193             if (postEq.getBandCount() != mPostEq.getBandCount()) {
   1194                 throw new IllegalArgumentException("PostEqBandCount changed from " +
   1195                         mPostEq.getBandCount() + " to " + postEq.getBandCount());
   1196             }
   1197             mPostEq = new Eq(postEq);
   1198         }
   1199         /**
   1200          * Gets EqBand for PostEq stage for given band index.
   1201          * @param band index of band of interest from PostEq stage
   1202          * @return EqBand configuration
   1203          */
   1204         public EqBand getPostEqBand(int band) {
   1205             return mPostEq.getBand(band);
   1206         }
   1207         /**
   1208          * Sets EqBand for PostEq stage for given band index
   1209          * @param band index of band of interest from PostEq stage
   1210          * @param postEqBand configuration to be set.
   1211          */
   1212         public void setPostEqBand(int band, EqBand postEqBand) {
   1213             mPostEq.setBand(band, postEqBand);
   1214         }
   1215 
   1216         /**
   1217          * Gets Limiter configuration stage
   1218          * @return Limiter configuration stage
   1219          */
   1220         public Limiter getLimiter() {
   1221             return mLimiter;
   1222         }
   1223         /**
   1224          * Sets Limiter configuration stage.
   1225          * @param limiter configuration stage.
   1226          */
   1227         public void setLimiter(Limiter limiter) {
   1228             mLimiter = new Limiter(limiter);
   1229         }
   1230     }
   1231 
   1232     /**
   1233      * Class for Config object, used by DynamicsProcessing to configure and update the audio effect.
   1234      * use Builder to instantiate objects of this type.
   1235      */
   1236     public final static class Config {
   1237         private final int mVariant;
   1238         private final int mChannelCount;
   1239         private final boolean mPreEqInUse;
   1240         private final int mPreEqBandCount;
   1241         private final boolean mMbcInUse;
   1242         private final int mMbcBandCount;
   1243         private final boolean mPostEqInUse;
   1244         private final int mPostEqBandCount;
   1245         private final boolean mLimiterInUse;
   1246         private final float mPreferredFrameDuration;
   1247         private final Channel[] mChannel;
   1248 
   1249         /**
   1250          * @hide
   1251          * Class constructor for config. None of these parameters can be changed later.
   1252          * @param variant index of variant used for effect engine. See
   1253          * {@link #VARIANT_FAVOR_FREQUENCY_RESOLUTION} and {@link #VARIANT_FAVOR_TIME_RESOLUTION}.
   1254          * @param frameDurationMs preferred frame duration in milliseconds (ms).
   1255          * @param channelCount Number of channels to be configured.
   1256          * @param preEqInUse true if PreEq stage will be used, false otherwise.
   1257          * @param preEqBandCount number of bands for PreEq stage.
   1258          * @param mbcInUse true if Mbc stage will be used, false otherwise.
   1259          * @param mbcBandCount number of bands for Mbc stage.
   1260          * @param postEqInUse true if PostEq stage will be used, false otherwise.
   1261          * @param postEqBandCount number of bands for PostEq stage.
   1262          * @param limiterInUse true if Limiter stage will be used, false otherwise.
   1263          * @param channel array of Channel objects to be used for this configuration.
   1264          */
   1265         public Config(int variant, float frameDurationMs, int channelCount,
   1266                 boolean preEqInUse, int preEqBandCount,
   1267                 boolean mbcInUse, int mbcBandCount,
   1268                 boolean postEqInUse, int postEqBandCount,
   1269                 boolean limiterInUse,
   1270                 Channel[] channel) {
   1271             mVariant = variant;
   1272             mPreferredFrameDuration = frameDurationMs;
   1273             mChannelCount = channelCount;
   1274             mPreEqInUse = preEqInUse;
   1275             mPreEqBandCount = preEqBandCount;
   1276             mMbcInUse = mbcInUse;
   1277             mMbcBandCount = mbcBandCount;
   1278             mPostEqInUse = postEqInUse;
   1279             mPostEqBandCount = postEqBandCount;
   1280             mLimiterInUse = limiterInUse;
   1281 
   1282             mChannel = new Channel[mChannelCount];
   1283             //check if channelconfig is null or has less channels than channel count.
   1284             //options: fill the missing with default options.
   1285             // or fail?
   1286             for (int ch = 0; ch < mChannelCount; ch++) {
   1287                 if (ch < channel.length) {
   1288                     mChannel[ch] = new Channel(channel[ch]); //copy create
   1289                 } else {
   1290                     //create a new one from scratch? //fail?
   1291                 }
   1292             }
   1293         }
   1294         //a version that will scale to necessary number of channels
   1295         /**
   1296          * @hide
   1297          * Class constructor for Configuration.
   1298          * @param channelCount limit configuration to this number of channels. if channelCount is
   1299          * greater than number of channels in cfg, the constructor will duplicate the last channel
   1300          * found as many times as necessary to create a Config with channelCount number of channels.
   1301          * If channelCount is less than channels in cfg, the extra channels in cfg will be ignored.
   1302          * @param cfg copy constructor paremter.
   1303          */
   1304         public Config(int channelCount, Config cfg) {
   1305             mVariant = cfg.mVariant;
   1306             mPreferredFrameDuration = cfg.mPreferredFrameDuration;
   1307             mChannelCount = cfg.mChannelCount;
   1308             mPreEqInUse = cfg.mPreEqInUse;
   1309             mPreEqBandCount = cfg.mPreEqBandCount;
   1310             mMbcInUse = cfg.mMbcInUse;
   1311             mMbcBandCount = cfg.mMbcBandCount;
   1312             mPostEqInUse = cfg.mPostEqInUse;
   1313             mPostEqBandCount = cfg.mPostEqBandCount;
   1314             mLimiterInUse = cfg.mLimiterInUse;
   1315 
   1316             if (mChannelCount != cfg.mChannel.length) {
   1317                 throw new IllegalArgumentException("configuration channel counts differ " +
   1318                         mChannelCount + " !=" + cfg.mChannel.length);
   1319             }
   1320             if (channelCount < 1) {
   1321                 throw new IllegalArgumentException("channel resizing less than 1 not allowed");
   1322             }
   1323 
   1324             mChannel = new Channel[channelCount];
   1325             for (int ch = 0; ch < channelCount; ch++) {
   1326                 if (ch < mChannelCount) {
   1327                     mChannel[ch] = new Channel(cfg.mChannel[ch]);
   1328                 } else {
   1329                     //duplicate last
   1330                     mChannel[ch] = new Channel(cfg.mChannel[mChannelCount-1]);
   1331                 }
   1332             }
   1333         }
   1334 
   1335         /**
   1336          * @hide
   1337          * Class constructor for Config
   1338          * @param cfg Configuration object copy constructor
   1339          */
   1340         public Config(@NonNull Config cfg) {
   1341             this(cfg.mChannelCount, cfg);
   1342         }
   1343 
   1344         @Override
   1345         public String toString() {
   1346             StringBuilder sb = new StringBuilder();
   1347             sb.append(String.format("Variant: %d\n", mVariant));
   1348             sb.append(String.format("PreferredFrameDuration: %f\n", mPreferredFrameDuration));
   1349             sb.append(String.format("ChannelCount: %d\n", mChannelCount));
   1350             sb.append(String.format("PreEq inUse: %b, bandCount:%d\n",mPreEqInUse,
   1351                     mPreEqBandCount));
   1352             sb.append(String.format("Mbc inUse: %b, bandCount: %d\n",mMbcInUse, mMbcBandCount));
   1353             sb.append(String.format("PostEq inUse: %b, bandCount: %d\n", mPostEqInUse,
   1354                     mPostEqBandCount));
   1355             sb.append(String.format("Limiter inUse: %b\n", mLimiterInUse));
   1356             for (int ch = 0; ch < mChannel.length; ch++) {
   1357                 sb.append(String.format("==Channel %d\n", ch));
   1358                 sb.append(mChannel[ch].toString());
   1359             }
   1360             return sb.toString();
   1361         }
   1362         private void checkChannel(int channelIndex) {
   1363             if (channelIndex < 0 || channelIndex >= mChannel.length) {
   1364                 throw new IllegalArgumentException("ChannelIndex out of bounds");
   1365             }
   1366         }
   1367 
   1368         //getters and setters
   1369         /**
   1370          * Gets variant for effect engine See {@link #VARIANT_FAVOR_FREQUENCY_RESOLUTION} and
   1371          * {@link #VARIANT_FAVOR_TIME_RESOLUTION}.
   1372          * @return variant of effect engine
   1373          */
   1374         public int getVariant() {
   1375             return mVariant;
   1376         }
   1377         /**
   1378          * Gets preferred frame duration in milliseconds (ms).
   1379          * @return preferred frame duration in milliseconds (ms)
   1380          */
   1381         public float getPreferredFrameDuration() {
   1382             return mPreferredFrameDuration;
   1383         }
   1384         /**
   1385          * Gets if preEq stage is in use
   1386          * @return true if preEq stage is in use;
   1387          */
   1388         public boolean isPreEqInUse() {
   1389             return mPreEqInUse;
   1390         }
   1391         /**
   1392          * Gets number of bands configured for the PreEq stage.
   1393          * @return number of bands configured for the PreEq stage.
   1394          */
   1395         public int getPreEqBandCount() {
   1396             return mPreEqBandCount;
   1397         }
   1398         /**
   1399          * Gets if Mbc stage is in use
   1400          * @return true if Mbc stage is in use;
   1401          */
   1402         public boolean isMbcInUse() {
   1403             return mMbcInUse;
   1404         }
   1405         /**
   1406          * Gets number of bands configured for the Mbc stage.
   1407          * @return number of bands configured for the Mbc stage.
   1408          */
   1409         public int getMbcBandCount() {
   1410             return mMbcBandCount;
   1411         }
   1412         /**
   1413          * Gets if PostEq stage is in use
   1414          * @return true if PostEq stage is in use;
   1415          */
   1416         public boolean isPostEqInUse() {
   1417             return mPostEqInUse;
   1418         }
   1419         /**
   1420          * Gets number of bands configured for the PostEq stage.
   1421          * @return number of bands configured for the PostEq stage.
   1422          */
   1423         public int getPostEqBandCount() {
   1424             return mPostEqBandCount;
   1425         }
   1426         /**
   1427          * Gets if Limiter stage is in use
   1428          * @return true if Limiter stage is in use;
   1429          */
   1430         public boolean isLimiterInUse() {
   1431             return mLimiterInUse;
   1432         }
   1433 
   1434         //channel
   1435         /**
   1436          * Gets the Channel configuration object by using the channel index
   1437          * @param channelIndex of desired Channel object
   1438          * @return Channel configuration object
   1439          */
   1440         public Channel getChannelByChannelIndex(int channelIndex) {
   1441             checkChannel(channelIndex);
   1442             return mChannel[channelIndex];
   1443         }
   1444 
   1445         /**
   1446          * Sets the chosen Channel object in the selected channelIndex
   1447          * Note that all the stages should have the same number of bands than the existing Channel
   1448          * object.
   1449          * @param channelIndex index of channel to be replaced
   1450          * @param channel Channel configuration object to be set
   1451          */
   1452         public void setChannelTo(int channelIndex, Channel channel) {
   1453             checkChannel(channelIndex);
   1454             //check all things are compatible
   1455             if (mMbcBandCount != channel.getMbc().getBandCount()) {
   1456                 throw new IllegalArgumentException("MbcBandCount changed from " +
   1457                         mMbcBandCount + " to " + channel.getPreEq().getBandCount());
   1458             }
   1459             if (mPreEqBandCount != channel.getPreEq().getBandCount()) {
   1460                 throw new IllegalArgumentException("PreEqBandCount changed from " +
   1461                         mPreEqBandCount + " to " + channel.getPreEq().getBandCount());
   1462             }
   1463             if (mPostEqBandCount != channel.getPostEq().getBandCount()) {
   1464                 throw new IllegalArgumentException("PostEqBandCount changed from " +
   1465                         mPostEqBandCount + " to " + channel.getPostEq().getBandCount());
   1466             }
   1467             mChannel[channelIndex] = new Channel(channel);
   1468         }
   1469 
   1470         /**
   1471          * Sets ALL channels to the chosen Channel object. Note that all the stages should have the
   1472          * same number of bands than the existing ones.
   1473          * @param channel Channel configuration object to be set.
   1474          */
   1475         public void setAllChannelsTo(Channel channel) {
   1476             for (int ch = 0; ch < mChannel.length; ch++) {
   1477                 setChannelTo(ch, channel);
   1478             }
   1479         }
   1480 
   1481         //===channel params
   1482         /**
   1483          * Gets inputGain value in decibels (dB) for channel indicated by channelIndex
   1484          * @param channelIndex index of channel of interest
   1485          * @return inputGain value in decibels (dB). 0 dB means no change.
   1486          */
   1487         public float getInputGainByChannelIndex(int channelIndex) {
   1488             checkChannel(channelIndex);
   1489             return mChannel[channelIndex].getInputGain();
   1490         }
   1491         /**
   1492          * Sets the inputGain value in decibels (dB) for the channel indicated by channelIndex.
   1493          * @param channelIndex index of channel of interest
   1494          * @param inputGain desired value in decibels (dB).
   1495          */
   1496         public void setInputGainByChannelIndex(int channelIndex, float inputGain) {
   1497             checkChannel(channelIndex);
   1498             mChannel[channelIndex].setInputGain(inputGain);
   1499         }
   1500         /**
   1501          * Sets the inputGain value in decibels (dB) for ALL channels
   1502          * @param inputGain desired value in decibels (dB)
   1503          */
   1504         public void setInputGainAllChannelsTo(float inputGain) {
   1505             for (int ch = 0; ch < mChannel.length; ch++) {
   1506                 mChannel[ch].setInputGain(inputGain);
   1507             }
   1508         }
   1509 
   1510         //=== PreEQ
   1511         /**
   1512          * Gets PreEq stage from channel indicated by channelIndex
   1513          * @param channelIndex index of channel of interest
   1514          * @return PreEq stage configuration object
   1515          */
   1516         public Eq getPreEqByChannelIndex(int channelIndex) {
   1517             checkChannel(channelIndex);
   1518             return mChannel[channelIndex].getPreEq();
   1519         }
   1520         /**
   1521          * Sets the PreEq stage configuration for the channel indicated by channelIndex. Note that
   1522          * new preEq stage must have the same number of bands than original preEq stage
   1523          * @param channelIndex index of channel to be set
   1524          * @param preEq desired PreEq configuration to be set
   1525          */
   1526         public void setPreEqByChannelIndex(int channelIndex, Eq preEq) {
   1527             checkChannel(channelIndex);
   1528             mChannel[channelIndex].setPreEq(preEq);
   1529         }
   1530         /**
   1531          * Sets the PreEq stage configuration for ALL channels. Note that new preEq stage must have
   1532          * the same number of bands than original preEq stages.
   1533          * @param preEq desired PreEq configuration to be set
   1534          */
   1535         public void setPreEqAllChannelsTo(Eq preEq) {
   1536             for (int ch = 0; ch < mChannel.length; ch++) {
   1537                 mChannel[ch].setPreEq(preEq);
   1538             }
   1539         }
   1540         public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) {
   1541             checkChannel(channelIndex);
   1542             return mChannel[channelIndex].getPreEqBand(band);
   1543         }
   1544         public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) {
   1545             checkChannel(channelIndex);
   1546             mChannel[channelIndex].setPreEqBand(band, preEqBand);
   1547         }
   1548         public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) {
   1549             for (int ch = 0; ch < mChannel.length; ch++) {
   1550                 mChannel[ch].setPreEqBand(band, preEqBand);
   1551             }
   1552         }
   1553 
   1554         //=== MBC
   1555         public Mbc getMbcByChannelIndex(int channelIndex) {
   1556             checkChannel(channelIndex);
   1557             return mChannel[channelIndex].getMbc();
   1558         }
   1559         public void setMbcByChannelIndex(int channelIndex, Mbc mbc) {
   1560             checkChannel(channelIndex);
   1561             mChannel[channelIndex].setMbc(mbc);
   1562         }
   1563         public void setMbcAllChannelsTo(Mbc mbc) {
   1564             for (int ch = 0; ch < mChannel.length; ch++) {
   1565                 mChannel[ch].setMbc(mbc);
   1566             }
   1567         }
   1568         public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) {
   1569             checkChannel(channelIndex);
   1570             return mChannel[channelIndex].getMbcBand(band);
   1571         }
   1572         public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) {
   1573             checkChannel(channelIndex);
   1574             mChannel[channelIndex].setMbcBand(band, mbcBand);
   1575         }
   1576         public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) {
   1577             for (int ch = 0; ch < mChannel.length; ch++) {
   1578                 mChannel[ch].setMbcBand(band, mbcBand);
   1579             }
   1580         }
   1581 
   1582         //=== PostEQ
   1583         public Eq getPostEqByChannelIndex(int channelIndex) {
   1584             checkChannel(channelIndex);
   1585             return mChannel[channelIndex].getPostEq();
   1586         }
   1587         public void setPostEqByChannelIndex(int channelIndex, Eq postEq) {
   1588             checkChannel(channelIndex);
   1589             mChannel[channelIndex].setPostEq(postEq);
   1590         }
   1591         public void setPostEqAllChannelsTo(Eq postEq) {
   1592             for (int ch = 0; ch < mChannel.length; ch++) {
   1593                 mChannel[ch].setPostEq(postEq);
   1594             }
   1595         }
   1596         public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) {
   1597             checkChannel(channelIndex);
   1598             return mChannel[channelIndex].getPostEqBand(band);
   1599         }
   1600         public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) {
   1601             checkChannel(channelIndex);
   1602             mChannel[channelIndex].setPostEqBand(band, postEqBand);
   1603         }
   1604         public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) {
   1605             for (int ch = 0; ch < mChannel.length; ch++) {
   1606                 mChannel[ch].setPostEqBand(band, postEqBand);
   1607             }
   1608         }
   1609 
   1610         //Limiter
   1611         public Limiter getLimiterByChannelIndex(int channelIndex) {
   1612             checkChannel(channelIndex);
   1613             return mChannel[channelIndex].getLimiter();
   1614         }
   1615         public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
   1616             checkChannel(channelIndex);
   1617             mChannel[channelIndex].setLimiter(limiter);
   1618         }
   1619         public void setLimiterAllChannelsTo(Limiter limiter) {
   1620             for (int ch = 0; ch < mChannel.length; ch++) {
   1621                 mChannel[ch].setLimiter(limiter);
   1622             }
   1623         }
   1624 
   1625         public final static class Builder {
   1626             private int mVariant;
   1627             private int mChannelCount;
   1628             private boolean mPreEqInUse;
   1629             private int mPreEqBandCount;
   1630             private boolean mMbcInUse;
   1631             private int mMbcBandCount;
   1632             private boolean mPostEqInUse;
   1633             private int mPostEqBandCount;
   1634             private boolean mLimiterInUse;
   1635             private float mPreferredFrameDuration = CONFIG_PREFERRED_FRAME_DURATION_MS;
   1636             private Channel[] mChannel;
   1637 
   1638             public Builder(int variant, int channelCount,
   1639                     boolean preEqInUse, int preEqBandCount,
   1640                     boolean mbcInUse, int mbcBandCount,
   1641                     boolean postEqInUse, int postEqBandCount,
   1642                     boolean limiterInUse) {
   1643                 mVariant = variant;
   1644                 mChannelCount = channelCount;
   1645                 mPreEqInUse = preEqInUse;
   1646                 mPreEqBandCount = preEqBandCount;
   1647                 mMbcInUse = mbcInUse;
   1648                 mMbcBandCount = mbcBandCount;
   1649                 mPostEqInUse = postEqInUse;
   1650                 mPostEqBandCount = postEqBandCount;
   1651                 mLimiterInUse = limiterInUse;
   1652                 mChannel = new Channel[mChannelCount];
   1653                 for (int ch = 0; ch < mChannelCount; ch++) {
   1654                     this.mChannel[ch] = new Channel(CHANNEL_DEFAULT_INPUT_GAIN,
   1655                             this.mPreEqInUse, this.mPreEqBandCount,
   1656                             this.mMbcInUse, this.mMbcBandCount,
   1657                             this.mPostEqInUse, this.mPostEqBandCount,
   1658                             this.mLimiterInUse);
   1659                 }
   1660             }
   1661 
   1662             private void checkChannel(int channelIndex) {
   1663                 if (channelIndex < 0 || channelIndex >= mChannel.length) {
   1664                     throw new IllegalArgumentException("ChannelIndex out of bounds");
   1665                 }
   1666             }
   1667 
   1668             public Builder setPreferredFrameDuration(float frameDuration) {
   1669                 if (frameDuration < 0) {
   1670                     throw new IllegalArgumentException("Expected positive frameDuration");
   1671                 }
   1672                 mPreferredFrameDuration = frameDuration;
   1673                 return this;
   1674             }
   1675 
   1676             public Builder setInputGainByChannelIndex(int channelIndex, float inputGain) {
   1677                 checkChannel(channelIndex);
   1678                 mChannel[channelIndex].setInputGain(inputGain);
   1679                 return this;
   1680             }
   1681             public Builder setInputGainAllChannelsTo(float inputGain) {
   1682                 for (int ch = 0; ch < mChannel.length; ch++) {
   1683                     mChannel[ch].setInputGain(inputGain);
   1684                 }
   1685                 return this;
   1686             }
   1687 
   1688             public Builder setChannelTo(int channelIndex, Channel channel) {
   1689                 checkChannel(channelIndex);
   1690                 //check all things are compatible
   1691                 if (mMbcBandCount != channel.getMbc().getBandCount()) {
   1692                     throw new IllegalArgumentException("MbcBandCount changed from " +
   1693                             mMbcBandCount + " to " + channel.getPreEq().getBandCount());
   1694                 }
   1695                 if (mPreEqBandCount != channel.getPreEq().getBandCount()) {
   1696                     throw new IllegalArgumentException("PreEqBandCount changed from " +
   1697                             mPreEqBandCount + " to " + channel.getPreEq().getBandCount());
   1698                 }
   1699                 if (mPostEqBandCount != channel.getPostEq().getBandCount()) {
   1700                     throw new IllegalArgumentException("PostEqBandCount changed from " +
   1701                             mPostEqBandCount + " to " + channel.getPostEq().getBandCount());
   1702                 }
   1703                 mChannel[channelIndex] = new Channel(channel);
   1704                 return this;
   1705             }
   1706             public Builder setAllChannelsTo(Channel channel) {
   1707                 for (int ch = 0; ch < mChannel.length; ch++) {
   1708                     setChannelTo(ch, channel);
   1709                 }
   1710                 return this;
   1711             }
   1712 
   1713             public Builder setPreEqByChannelIndex(int channelIndex, Eq preEq) {
   1714                 checkChannel(channelIndex);
   1715                 mChannel[channelIndex].setPreEq(preEq);
   1716                 return this;
   1717             }
   1718             public Builder setPreEqAllChannelsTo(Eq preEq) {
   1719                 for (int ch = 0; ch < mChannel.length; ch++) {
   1720                     setPreEqByChannelIndex(ch, preEq);
   1721                 }
   1722                 return this;
   1723             }
   1724 
   1725             public Builder setMbcByChannelIndex(int channelIndex, Mbc mbc) {
   1726                 checkChannel(channelIndex);
   1727                 mChannel[channelIndex].setMbc(mbc);
   1728                 return this;
   1729             }
   1730             public Builder setMbcAllChannelsTo(Mbc mbc) {
   1731                 for (int ch = 0; ch < mChannel.length; ch++) {
   1732                     setMbcByChannelIndex(ch, mbc);
   1733                 }
   1734                 return this;
   1735             }
   1736 
   1737             public Builder setPostEqByChannelIndex(int channelIndex, Eq postEq) {
   1738                 checkChannel(channelIndex);
   1739                 mChannel[channelIndex].setPostEq(postEq);
   1740                 return this;
   1741             }
   1742             public Builder setPostEqAllChannelsTo(Eq postEq) {
   1743                 for (int ch = 0; ch < mChannel.length; ch++) {
   1744                     setPostEqByChannelIndex(ch, postEq);
   1745                 }
   1746                 return this;
   1747             }
   1748 
   1749             public Builder setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
   1750                 checkChannel(channelIndex);
   1751                 mChannel[channelIndex].setLimiter(limiter);
   1752                 return this;
   1753             }
   1754             public Builder setLimiterAllChannelsTo(Limiter limiter) {
   1755                 for (int ch = 0; ch < mChannel.length; ch++) {
   1756                     setLimiterByChannelIndex(ch, limiter);
   1757                 }
   1758                 return this;
   1759             }
   1760 
   1761             public Config build() {
   1762                 return new Config(mVariant, mPreferredFrameDuration, mChannelCount,
   1763                         mPreEqInUse, mPreEqBandCount,
   1764                         mMbcInUse, mMbcBandCount,
   1765                         mPostEqInUse, mPostEqBandCount,
   1766                         mLimiterInUse, mChannel);
   1767             }
   1768         }
   1769     }
   1770     //=== CHANNEL
   1771     public Channel getChannelByChannelIndex(int channelIndex) {
   1772         return queryEngineByChannelIndex(channelIndex);
   1773     }
   1774 
   1775     public void setChannelTo(int channelIndex, Channel channel) {
   1776         updateEngineChannelByChannelIndex(channelIndex, channel);
   1777     }
   1778 
   1779     public void setAllChannelsTo(Channel channel) {
   1780         for (int ch = 0; ch < mChannelCount; ch++) {
   1781             setChannelTo(ch, channel);
   1782         }
   1783     }
   1784 
   1785     //=== channel params
   1786     public float getInputGainByChannelIndex(int channelIndex) {
   1787         return getTwoFloat(PARAM_INPUT_GAIN, channelIndex);
   1788     }
   1789     public void setInputGainbyChannel(int channelIndex, float inputGain) {
   1790         setTwoFloat(PARAM_INPUT_GAIN, channelIndex, inputGain);
   1791     }
   1792     public void setInputGainAllChannelsTo(float inputGain) {
   1793         for (int ch = 0; ch < mChannelCount; ch++) {
   1794             setInputGainbyChannel(ch, inputGain);
   1795         }
   1796     }
   1797 
   1798     //=== PreEQ
   1799     public Eq getPreEqByChannelIndex(int channelIndex) {
   1800         return queryEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex);
   1801     }
   1802     public void setPreEqByChannelIndex(int channelIndex, Eq preEq) {
   1803         updateEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex, preEq);
   1804     }
   1805     public void setPreEqAllChannelsTo(Eq preEq) {
   1806         for (int ch = 0; ch < mChannelCount; ch++) {
   1807             setPreEqByChannelIndex(ch, preEq);
   1808         }
   1809     }
   1810     public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) {
   1811         return queryEngineEqBandByChannelIndex(PARAM_PRE_EQ_BAND, channelIndex, band);
   1812     }
   1813     public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) {
   1814         updateEngineEqBandByChannelIndex(PARAM_PRE_EQ_BAND, channelIndex, band, preEqBand);
   1815     }
   1816     public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) {
   1817         for (int ch = 0; ch < mChannelCount; ch++) {
   1818             setPreEqBandByChannelIndex(ch, band, preEqBand);
   1819         }
   1820     }
   1821 
   1822     //=== MBC
   1823     public Mbc getMbcByChannelIndex(int channelIndex) {
   1824         return queryEngineMbcByChannelIndex(channelIndex);
   1825     }
   1826     public void setMbcByChannelIndex(int channelIndex, Mbc mbc) {
   1827         updateEngineMbcByChannelIndex(channelIndex, mbc);
   1828     }
   1829     public void setMbcAllChannelsTo(Mbc mbc) {
   1830         for (int ch = 0; ch < mChannelCount; ch++) {
   1831             setMbcByChannelIndex(ch, mbc);
   1832         }
   1833     }
   1834     public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) {
   1835         return queryEngineMbcBandByChannelIndex(channelIndex, band);
   1836     }
   1837     public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) {
   1838         updateEngineMbcBandByChannelIndex(channelIndex, band, mbcBand);
   1839     }
   1840     public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) {
   1841         for (int ch = 0; ch < mChannelCount; ch++) {
   1842             setMbcBandByChannelIndex(ch, band, mbcBand);
   1843         }
   1844     }
   1845 
   1846     //== PostEq
   1847     public Eq getPostEqByChannelIndex(int channelIndex) {
   1848         return queryEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex);
   1849     }
   1850     public void setPostEqByChannelIndex(int channelIndex, Eq postEq) {
   1851         updateEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex, postEq);
   1852     }
   1853     public void setPostEqAllChannelsTo(Eq postEq) {
   1854         for (int ch = 0; ch < mChannelCount; ch++) {
   1855             setPostEqByChannelIndex(ch, postEq);
   1856         }
   1857     }
   1858     public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) {
   1859         return queryEngineEqBandByChannelIndex(PARAM_POST_EQ_BAND, channelIndex, band);
   1860     }
   1861     public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) {
   1862         updateEngineEqBandByChannelIndex(PARAM_POST_EQ_BAND, channelIndex, band, postEqBand);
   1863     }
   1864     public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) {
   1865         for (int ch = 0; ch < mChannelCount; ch++) {
   1866             setPostEqBandByChannelIndex(ch, band, postEqBand);
   1867         }
   1868     }
   1869 
   1870     //==== Limiter
   1871     public Limiter getLimiterByChannelIndex(int channelIndex) {
   1872         return queryEngineLimiterByChannelIndex(channelIndex);
   1873     }
   1874     public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
   1875         updateEngineLimiterByChannelIndex(channelIndex, limiter);
   1876     }
   1877     public void setLimiterAllChannelsTo(Limiter limiter) {
   1878         for (int ch = 0; ch < mChannelCount; ch++) {
   1879             setLimiterByChannelIndex(ch, limiter);
   1880         }
   1881     }
   1882 
   1883     /**
   1884      * Gets the number of channels in the effect engine
   1885      * @return number of channels currently in use by the effect engine
   1886      */
   1887     public int getChannelCount() {
   1888         return getOneInt(PARAM_GET_CHANNEL_COUNT);
   1889     }
   1890 
   1891     //=== Engine calls
   1892     private void setEngineArchitecture(int variant, float preferredFrameDuration,
   1893             boolean preEqInUse, int preEqBandCount, boolean mbcInUse, int mbcBandCount,
   1894             boolean postEqInUse, int postEqBandCount, boolean limiterInUse) {
   1895 
   1896         Number[] params = { PARAM_ENGINE_ARCHITECTURE };
   1897         Number[] values = { variant /* variant */,
   1898                 preferredFrameDuration,
   1899                 (preEqInUse ? 1 : 0),
   1900                 preEqBandCount,
   1901                 (mbcInUse ? 1 : 0),
   1902                 mbcBandCount,
   1903                 (postEqInUse ? 1 : 0),
   1904                 postEqBandCount,
   1905                 (limiterInUse ? 1 : 0)};
   1906         setNumberArray(params, values);
   1907     }
   1908 
   1909     private void updateEngineEqBandByChannelIndex(int param, int channelIndex, int bandIndex,
   1910             @NonNull EqBand eqBand) {
   1911         Number[] params = {param,
   1912                 channelIndex,
   1913                 bandIndex};
   1914         Number[] values = {(eqBand.isEnabled() ? 1 : 0),
   1915                 eqBand.getCutoffFrequency(),
   1916                 eqBand.getGain()};
   1917         setNumberArray(params, values);
   1918     }
   1919     private Eq queryEngineEqByChannelIndex(int param, int channelIndex) {
   1920 
   1921         Number[] params = {param == PARAM_PRE_EQ ? PARAM_PRE_EQ : PARAM_POST_EQ,
   1922                 channelIndex};
   1923         Number[] values = {0 /*0 in use */,
   1924                             0 /*1 enabled*/,
   1925                             0 /*2 band count */};
   1926         byte[] paramBytes = numberArrayToByteArray(params);
   1927         byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
   1928         getParameter(paramBytes, valueBytes);
   1929         byteArrayToNumberArray(valueBytes, values);
   1930         int bandCount = values[2].intValue();
   1931         Eq eq = new Eq(values[0].intValue() > 0 /* in use */,
   1932                 values[1].intValue() > 0 /* enabled */,
   1933                 bandCount /*band count*/);
   1934         for (int b = 0; b < bandCount; b++) {
   1935             EqBand eqBand = queryEngineEqBandByChannelIndex(param == PARAM_PRE_EQ ?
   1936                     PARAM_PRE_EQ_BAND : PARAM_POST_EQ_BAND, channelIndex, b);
   1937             eq.setBand(b, eqBand);
   1938         }
   1939         return eq;
   1940     }
   1941     private EqBand queryEngineEqBandByChannelIndex(int param, int channelIndex, int bandIndex) {
   1942         Number[] params = {param,
   1943                 channelIndex,
   1944                 bandIndex};
   1945         Number[] values = {0 /*0 enabled*/,
   1946                             0.0f /*1 cutoffFrequency */,
   1947                             0.0f /*2 gain */};
   1948 
   1949         byte[] paramBytes = numberArrayToByteArray(params);
   1950         byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
   1951         getParameter(paramBytes, valueBytes);
   1952 
   1953         byteArrayToNumberArray(valueBytes, values);
   1954 
   1955         return new EqBand(values[0].intValue() > 0 /* enabled */,
   1956                 values[1].floatValue() /* cutoffFrequency */,
   1957                 values[2].floatValue() /* gain*/);
   1958     }
   1959     private void updateEngineEqByChannelIndex(int param, int channelIndex, @NonNull Eq eq) {
   1960         int bandCount = eq.getBandCount();
   1961         Number[] params = {param,
   1962                 channelIndex};
   1963         Number[] values = { (eq.isInUse() ? 1 : 0),
   1964                 (eq.isEnabled() ? 1 : 0),
   1965                 bandCount};
   1966         setNumberArray(params, values);
   1967         for (int b = 0; b < bandCount; b++) {
   1968             EqBand eqBand = eq.getBand(b);
   1969             updateEngineEqBandByChannelIndex(param == PARAM_PRE_EQ ?
   1970                     PARAM_PRE_EQ_BAND : PARAM_POST_EQ_BAND, channelIndex, b, eqBand);
   1971         }
   1972     }
   1973 
   1974     private Mbc queryEngineMbcByChannelIndex(int channelIndex) {
   1975         Number[] params = {PARAM_MBC,
   1976                 channelIndex};
   1977         Number[] values = {0 /*0 in use */,
   1978                             0 /*1 enabled*/,
   1979                             0 /*2 band count */};
   1980         byte[] paramBytes = numberArrayToByteArray(params);
   1981         byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
   1982         getParameter(paramBytes, valueBytes);
   1983         byteArrayToNumberArray(valueBytes, values);
   1984         int bandCount = values[2].intValue();
   1985         Mbc mbc = new Mbc(values[0].intValue() > 0 /* in use */,
   1986                 values[1].intValue() > 0 /* enabled */,
   1987                 bandCount /*band count*/);
   1988         for (int b = 0; b < bandCount; b++) {
   1989             MbcBand mbcBand = queryEngineMbcBandByChannelIndex(channelIndex, b);
   1990             mbc.setBand(b, mbcBand);
   1991         }
   1992         return mbc;
   1993     }
   1994     private MbcBand queryEngineMbcBandByChannelIndex(int channelIndex, int bandIndex) {
   1995         Number[] params = {PARAM_MBC_BAND,
   1996                 channelIndex,
   1997                 bandIndex};
   1998         Number[] values = {0 /*0 enabled */,
   1999                 0.0f /*1 cutoffFrequency */,
   2000                 0.0f /*2 AttackTime */,
   2001                 0.0f /*3 ReleaseTime */,
   2002                 0.0f /*4 Ratio */,
   2003                 0.0f /*5 Threshold */,
   2004                 0.0f /*6 KneeWidth */,
   2005                 0.0f /*7 NoiseGateThreshold */,
   2006                 0.0f /*8 ExpanderRatio */,
   2007                 0.0f /*9 PreGain */,
   2008                 0.0f /*10 PostGain*/};
   2009 
   2010         byte[] paramBytes = numberArrayToByteArray(params);
   2011         byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
   2012         getParameter(paramBytes, valueBytes);
   2013 
   2014         byteArrayToNumberArray(valueBytes, values);
   2015 
   2016         return new MbcBand(values[0].intValue() > 0 /* enabled */,
   2017                 values[1].floatValue() /* cutoffFrequency */,
   2018                 values[2].floatValue()/*2 AttackTime */,
   2019                 values[3].floatValue()/*3 ReleaseTime */,
   2020                 values[4].floatValue()/*4 Ratio */,
   2021                 values[5].floatValue()/*5 Threshold */,
   2022                 values[6].floatValue()/*6 KneeWidth */,
   2023                 values[7].floatValue()/*7 NoiseGateThreshold */,
   2024                 values[8].floatValue()/*8 ExpanderRatio */,
   2025                 values[9].floatValue()/*9 PreGain */,
   2026                 values[10].floatValue()/*10 PostGain*/);
   2027     }
   2028     private void updateEngineMbcBandByChannelIndex(int channelIndex, int bandIndex,
   2029             @NonNull MbcBand mbcBand) {
   2030         Number[] params = { PARAM_MBC_BAND,
   2031                 channelIndex,
   2032                 bandIndex};
   2033         Number[] values = {(mbcBand.isEnabled() ? 1 : 0),
   2034                 mbcBand.getCutoffFrequency(),
   2035                 mbcBand.getAttackTime(),
   2036                 mbcBand.getReleaseTime(),
   2037                 mbcBand.getRatio(),
   2038                 mbcBand.getThreshold(),
   2039                 mbcBand.getKneeWidth(),
   2040                 mbcBand.getNoiseGateThreshold(),
   2041                 mbcBand.getExpanderRatio(),
   2042                 mbcBand.getPreGain(),
   2043                 mbcBand.getPostGain()};
   2044         setNumberArray(params, values);
   2045     }
   2046 
   2047     private void updateEngineMbcByChannelIndex(int channelIndex, @NonNull Mbc mbc) {
   2048         int bandCount = mbc.getBandCount();
   2049         Number[] params = { PARAM_MBC,
   2050                 channelIndex};
   2051         Number[] values = {(mbc.isInUse() ? 1 : 0),
   2052                 (mbc.isEnabled() ? 1 : 0),
   2053                 bandCount};
   2054         setNumberArray(params, values);
   2055         for (int b = 0; b < bandCount; b++) {
   2056             MbcBand mbcBand = mbc.getBand(b);
   2057             updateEngineMbcBandByChannelIndex(channelIndex, b, mbcBand);
   2058         }
   2059     }
   2060 
   2061     private void updateEngineLimiterByChannelIndex(int channelIndex, @NonNull Limiter limiter) {
   2062         Number[] params = { PARAM_LIMITER,
   2063                 channelIndex};
   2064         Number[] values = {(limiter.isInUse() ? 1 : 0),
   2065                 (limiter.isEnabled() ? 1 : 0),
   2066                 limiter.getLinkGroup(),
   2067                 limiter.getAttackTime(),
   2068                 limiter.getReleaseTime(),
   2069                 limiter.getRatio(),
   2070                 limiter.getThreshold(),
   2071                 limiter.getPostGain()};
   2072         setNumberArray(params, values);
   2073     }
   2074 
   2075     private Limiter queryEngineLimiterByChannelIndex(int channelIndex) {
   2076         Number[] params = {PARAM_LIMITER,
   2077                 channelIndex};
   2078         Number[] values = {0 /*0 in use (int)*/,
   2079                 0 /*1 enabled (int)*/,
   2080                 0 /*2 link group (int)*/,
   2081                 0.0f /*3 attack time (float)*/,
   2082                 0.0f /*4 release time (float)*/,
   2083                 0.0f /*5 ratio (float)*/,
   2084                 0.0f /*6 threshold (float)*/,
   2085                 0.0f /*7 post gain(float)*/};
   2086 
   2087         byte[] paramBytes = numberArrayToByteArray(params);
   2088         byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
   2089         getParameter(paramBytes, valueBytes);
   2090         byteArrayToNumberArray(valueBytes, values);
   2091 
   2092         return new Limiter(values[0].intValue() > 0 /*in use*/,
   2093                 values[1].intValue() > 0 /*enabled*/,
   2094                 values[2].intValue() /*linkGroup*/,
   2095                 values[3].floatValue() /*attackTime*/,
   2096                 values[4].floatValue() /*releaseTime*/,
   2097                 values[5].floatValue() /*ratio*/,
   2098                 values[6].floatValue() /*threshold*/,
   2099                 values[7].floatValue() /*postGain*/);
   2100     }
   2101 
   2102     private Channel queryEngineByChannelIndex(int channelIndex) {
   2103         float inputGain = getTwoFloat(PARAM_INPUT_GAIN, channelIndex);
   2104         Eq preEq = queryEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex);
   2105         Mbc mbc = queryEngineMbcByChannelIndex(channelIndex);
   2106         Eq postEq = queryEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex);
   2107         Limiter limiter = queryEngineLimiterByChannelIndex(channelIndex);
   2108 
   2109         Channel channel = new Channel(inputGain,
   2110                 preEq.isInUse(), preEq.getBandCount(),
   2111                 mbc.isInUse(), mbc.getBandCount(),
   2112                 postEq.isInUse(), postEq.getBandCount(),
   2113                 limiter.isInUse());
   2114         channel.setInputGain(inputGain);
   2115         channel.setPreEq(preEq);
   2116         channel.setMbc(mbc);
   2117         channel.setPostEq(postEq);
   2118         channel.setLimiter(limiter);
   2119         return channel;
   2120     }
   2121 
   2122     private void updateEngineChannelByChannelIndex(int channelIndex, @NonNull Channel channel) {
   2123         //send things with as few calls as possible
   2124         setTwoFloat(PARAM_INPUT_GAIN, channelIndex, channel.getInputGain());
   2125         Eq preEq = channel.getPreEq();
   2126         updateEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex, preEq);
   2127         Mbc mbc = channel.getMbc();
   2128         updateEngineMbcByChannelIndex(channelIndex, mbc);
   2129         Eq postEq = channel.getPostEq();
   2130         updateEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex, postEq);
   2131         Limiter limiter = channel.getLimiter();
   2132         updateEngineLimiterByChannelIndex(channelIndex, limiter);
   2133     }
   2134 
   2135     //****** convenience methods:
   2136     //
   2137     private int getOneInt(int param) {
   2138         final int[] params = { param };
   2139         final int[] result = new int[1];
   2140 
   2141         checkStatus(getParameter(params, result));
   2142         return result[0];
   2143     }
   2144 
   2145     private void setTwoFloat(int param, int paramA, float valueSet) {
   2146         final int[] params = { param, paramA };
   2147         final byte[] value;
   2148 
   2149         value = floatToByteArray(valueSet);
   2150         checkStatus(setParameter(params, value));
   2151     }
   2152 
   2153     private byte[] numberArrayToByteArray(Number[] values) {
   2154         int expectedBytes = 0;
   2155         for (int i = 0; i < values.length; i++) {
   2156             if (values[i] instanceof Integer) {
   2157                 expectedBytes += Integer.BYTES;
   2158             } else if (values[i] instanceof Float) {
   2159                 expectedBytes += Float.BYTES;
   2160             } else {
   2161                 throw new IllegalArgumentException("unknown value type " +
   2162                         values[i].getClass());
   2163             }
   2164         }
   2165         ByteBuffer converter = ByteBuffer.allocate(expectedBytes);
   2166         converter.order(ByteOrder.nativeOrder());
   2167         for (int i = 0; i < values.length; i++) {
   2168             if (values[i] instanceof Integer) {
   2169                 converter.putInt(values[i].intValue());
   2170             } else if (values[i] instanceof Float) {
   2171                 converter.putFloat(values[i].floatValue());
   2172             }
   2173         }
   2174         return converter.array();
   2175     }
   2176 
   2177     private void byteArrayToNumberArray(byte[] valuesIn, Number[] valuesOut) {
   2178         int inIndex = 0;
   2179         int outIndex = 0;
   2180         while (inIndex < valuesIn.length && outIndex < valuesOut.length) {
   2181             if (valuesOut[outIndex] instanceof Integer) {
   2182                 valuesOut[outIndex++] = byteArrayToInt(valuesIn, inIndex);
   2183                 inIndex += Integer.BYTES;
   2184             } else if (valuesOut[outIndex] instanceof Float) {
   2185                 valuesOut[outIndex++] = byteArrayToFloat(valuesIn, inIndex);
   2186                 inIndex += Float.BYTES;
   2187             } else {
   2188                 throw new IllegalArgumentException("can't convert " +
   2189                         valuesOut[outIndex].getClass());
   2190             }
   2191         }
   2192         if (outIndex != valuesOut.length) {
   2193             throw new IllegalArgumentException("only converted " + outIndex +
   2194                     " values out of "+ valuesOut.length + " expected");
   2195         }
   2196     }
   2197 
   2198     private void setNumberArray(Number[] params, Number[] values) {
   2199         byte[] paramBytes = numberArrayToByteArray(params);
   2200         byte[] valueBytes = numberArrayToByteArray(values);
   2201         checkStatus(setParameter(paramBytes, valueBytes));
   2202     }
   2203 
   2204     private float getTwoFloat(int param, int paramA) {
   2205         final int[] params = { param, paramA };
   2206         final byte[] result = new byte[4];
   2207 
   2208         checkStatus(getParameter(params, result));
   2209         return byteArrayToFloat(result);
   2210     }
   2211 
   2212     /**
   2213      * @hide
   2214      * The OnParameterChangeListener interface defines a method called by the DynamicsProcessing
   2215      * when a parameter value has changed.
   2216      */
   2217     public interface OnParameterChangeListener {
   2218         /**
   2219          * Method called when a parameter value has changed. The method is called only if the
   2220          * parameter was changed by another application having the control of the same
   2221          * DynamicsProcessing engine.
   2222          * @param effect the DynamicsProcessing on which the interface is registered.
   2223          * @param param ID of the modified parameter. See {@link #PARAM_GENERIC_PARAM1} ...
   2224          * @param value the new parameter value.
   2225          */
   2226         void onParameterChange(DynamicsProcessing effect, int param, int value);
   2227     }
   2228 
   2229     /**
   2230      * helper method to update effect architecture parameters
   2231      */
   2232     private void updateEffectArchitecture() {
   2233         mChannelCount = getChannelCount();
   2234     }
   2235 
   2236     /**
   2237      * Listener used internally to receive unformatted parameter change events from AudioEffect
   2238      * super class.
   2239      */
   2240     private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
   2241         private BaseParameterListener() {
   2242 
   2243         }
   2244         public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
   2245             // only notify when the parameter was successfully change
   2246             if (status != AudioEffect.SUCCESS) {
   2247                 return;
   2248             }
   2249             OnParameterChangeListener l = null;
   2250             synchronized (mParamListenerLock) {
   2251                 if (mParamListener != null) {
   2252                     l = mParamListener;
   2253                 }
   2254             }
   2255             if (l != null) {
   2256                 int p = -1;
   2257                 int v = Integer.MIN_VALUE;
   2258 
   2259                 if (param.length == 4) {
   2260                     p = byteArrayToInt(param, 0);
   2261                 }
   2262                 if (value.length == 4) {
   2263                     v = byteArrayToInt(value, 0);
   2264                 }
   2265                 if (p != -1 && v != Integer.MIN_VALUE) {
   2266                     l.onParameterChange(DynamicsProcessing.this, p, v);
   2267                 }
   2268             }
   2269         }
   2270     }
   2271 
   2272     /**
   2273      * @hide
   2274      * Registers an OnParameterChangeListener interface.
   2275      * @param listener OnParameterChangeListener interface registered
   2276      */
   2277     public void setParameterListener(OnParameterChangeListener listener) {
   2278         synchronized (mParamListenerLock) {
   2279             if (mParamListener == null) {
   2280                 mBaseParamListener = new BaseParameterListener();
   2281                 super.setParameterListener(mBaseParamListener);
   2282             }
   2283             mParamListener = listener;
   2284         }
   2285     }
   2286 
   2287     /**
   2288      * @hide
   2289      * The Settings class regroups the DynamicsProcessing parameters. It is used in
   2290      * conjunction with the getProperties() and setProperties() methods to backup and restore
   2291      * all parameters in a single call.
   2292      */
   2293 
   2294     public static class Settings {
   2295         public int channelCount;
   2296         public float[] inputGain;
   2297 
   2298         public Settings() {
   2299         }
   2300 
   2301         /**
   2302          * Settings class constructor from a key=value; pairs formatted string. The string is
   2303          * typically returned by Settings.toString() method.
   2304          * @throws IllegalArgumentException if the string is not correctly formatted.
   2305          */
   2306         public Settings(String settings) {
   2307             StringTokenizer st = new StringTokenizer(settings, "=;");
   2308             //int tokens = st.countTokens();
   2309             if (st.countTokens() != 3) {
   2310                 throw new IllegalArgumentException("settings: " + settings);
   2311             }
   2312             String key = st.nextToken();
   2313             if (!key.equals("DynamicsProcessing")) {
   2314                 throw new IllegalArgumentException(
   2315                         "invalid settings for DynamicsProcessing: " + key);
   2316             }
   2317             try {
   2318                 key = st.nextToken();
   2319                 if (!key.equals("channelCount")) {
   2320                     throw new IllegalArgumentException("invalid key name: " + key);
   2321                 }
   2322                 channelCount = Short.parseShort(st.nextToken());
   2323                 if (channelCount > CHANNEL_COUNT_MAX) {
   2324                     throw new IllegalArgumentException("too many channels Settings:" + settings);
   2325                 }
   2326                 if (st.countTokens() != channelCount*1) { //check expected parameters.
   2327                     throw new IllegalArgumentException("settings: " + settings);
   2328                 }
   2329                 //check to see it is ok the size
   2330                 inputGain = new float[channelCount];
   2331                 for (int ch = 0; ch < channelCount; ch++) {
   2332                     key = st.nextToken();
   2333                     if (!key.equals(ch +"_inputGain")) {
   2334                         throw new IllegalArgumentException("invalid key name: " + key);
   2335                     }
   2336                     inputGain[ch] = Float.parseFloat(st.nextToken());
   2337                 }
   2338              } catch (NumberFormatException nfe) {
   2339                 throw new IllegalArgumentException("invalid value for key: " + key);
   2340             }
   2341         }
   2342 
   2343         @Override
   2344         public String toString() {
   2345             String str = new String (
   2346                     "DynamicsProcessing"+
   2347                     ";channelCount="+Integer.toString(channelCount));
   2348                     for (int ch = 0; ch < channelCount; ch++) {
   2349                         str = str.concat(";"+ch+"_inputGain="+Float.toString(inputGain[ch]));
   2350                     }
   2351             return str;
   2352         }
   2353     };
   2354 
   2355 
   2356     /**
   2357      * @hide
   2358      * Gets the DynamicsProcessing properties. This method is useful when a snapshot of current
   2359      * effect settings must be saved by the application.
   2360      * @return a DynamicsProcessing.Settings object containing all current parameters values
   2361      */
   2362     public DynamicsProcessing.Settings getProperties() {
   2363         Settings settings = new Settings();
   2364 
   2365         //TODO: just for testing, we are calling the getters one by one, this is
   2366         // supposed to be done in a single (or few calls) and get all the parameters at once.
   2367 
   2368         settings.channelCount = getChannelCount();
   2369 
   2370         if (settings.channelCount > CHANNEL_COUNT_MAX) {
   2371             throw new IllegalArgumentException("too many channels Settings:" + settings);
   2372         }
   2373 
   2374         { // get inputGainmB per channel
   2375             settings.inputGain = new float [settings.channelCount];
   2376             for (int ch = 0; ch < settings.channelCount; ch++) {
   2377 //TODO:with config                settings.inputGain[ch] = getInputGain(ch);
   2378             }
   2379         }
   2380         return settings;
   2381     }
   2382 
   2383     /**
   2384      * @hide
   2385      * Sets the DynamicsProcessing properties. This method is useful when bass boost settings
   2386      * have to be applied from a previous backup.
   2387      * @param settings a DynamicsProcessing.Settings object containing the properties to apply
   2388      */
   2389     public void setProperties(DynamicsProcessing.Settings settings) {
   2390 
   2391         if (settings.channelCount != settings.inputGain.length ||
   2392                 settings.channelCount != mChannelCount) {
   2393                 throw new IllegalArgumentException("settings invalid channel count: "
   2394                 + settings.channelCount);
   2395             }
   2396 
   2397         //TODO: for now calling multiple times.
   2398         for (int ch = 0; ch < mChannelCount; ch++) {
   2399 //TODO: use config            setInputGain(ch, settings.inputGain[ch]);
   2400         }
   2401     }
   2402 }
   2403