summaryrefslogtreecommitdiff
path: root/android/media/audiofx
diff options
context:
space:
mode:
Diffstat (limited to 'android/media/audiofx')
-rw-r--r--android/media/audiofx/AudioEffect.java44
-rw-r--r--android/media/audiofx/DynamicsProcessing.java2402
-rw-r--r--android/media/audiofx/Visualizer.java25
3 files changed, 2464 insertions, 7 deletions
diff --git a/android/media/audiofx/AudioEffect.java b/android/media/audiofx/AudioEffect.java
index 7dbca3b9..21d68737 100644
--- a/android/media/audiofx/AudioEffect.java
+++ b/android/media/audiofx/AudioEffect.java
@@ -39,6 +39,7 @@ import java.util.UUID;
* <li> {@link android.media.audiofx.BassBoost}</li>
* <li> {@link android.media.audiofx.PresetReverb}</li>
* <li> {@link android.media.audiofx.EnvironmentalReverb}</li>
+ * <li> {@link android.media.audiofx.DynamicsProcessing}</li>
* </ul>
* <p>To apply the audio effect to a specific AudioTrack or MediaPlayer instance,
* the application must specify the audio session ID of that instance when creating the AudioEffect.
@@ -126,6 +127,12 @@ public class AudioEffect {
.fromString("fe3199be-aed0-413f-87bb-11260eb63cf1");
/**
+ * UUID for Dynamics Processing
+ */
+ public static final UUID EFFECT_TYPE_DYNAMICS_PROCESSING = UUID
+ .fromString("7261676f-6d75-7369-6364-28e2fd3ac39e");
+
+ /**
* Null effect UUID. Used when the UUID for effect type of
* @hide
*/
@@ -203,7 +210,8 @@ public class AudioEffect {
* {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
* {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
* {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
- * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}, {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.
+ * {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}, {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
+ * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
* </li>
* <li>uuid: UUID for this particular implementation</li>
* <li>connectMode: {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}</li>
@@ -224,7 +232,8 @@ public class AudioEffect {
* {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
* {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
* {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB},
- * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.
+ * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
+ * {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.
* @param uuid UUID for this particular implementation
* @param connectMode {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}
* @param name human readable effect name
@@ -246,7 +255,8 @@ public class AudioEffect {
* {@link AudioEffect#EFFECT_TYPE_AGC}, {@link AudioEffect#EFFECT_TYPE_BASS_BOOST},
* {@link AudioEffect#EFFECT_TYPE_ENV_REVERB}, {@link AudioEffect#EFFECT_TYPE_EQUALIZER},
* {@link AudioEffect#EFFECT_TYPE_NS}, {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}
- * or {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.<br>
+ * {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}
+ * or {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.<br>
* For reverberation, bass boost, EQ and virtualizer, the UUID
* corresponds to the OpenSL ES Interface ID.
*/
@@ -1344,6 +1354,34 @@ public class AudioEffect {
/**
* @hide
*/
+ public static float byteArrayToFloat(byte[] valueBuf) {
+ return byteArrayToFloat(valueBuf, 0);
+
+ }
+
+ /**
+ * @hide
+ */
+ public static float byteArrayToFloat(byte[] valueBuf, int offset) {
+ ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+ converter.order(ByteOrder.nativeOrder());
+ return converter.getFloat(offset);
+
+ }
+
+ /**
+ * @hide
+ */
+ public static byte[] floatToByteArray(float value) {
+ ByteBuffer converter = ByteBuffer.allocate(4);
+ converter.order(ByteOrder.nativeOrder());
+ converter.putFloat(value);
+ return converter.array();
+ }
+
+ /**
+ * @hide
+ */
public static byte[] concatArrays(byte[]... arrays) {
int len = 0;
for (byte[] a : arrays) {
diff --git a/android/media/audiofx/DynamicsProcessing.java b/android/media/audiofx/DynamicsProcessing.java
new file mode 100644
index 00000000..4c17ae1d
--- /dev/null
+++ b/android/media/audiofx/DynamicsProcessing.java
@@ -0,0 +1,2402 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiofx;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.audiofx.AudioEffect;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.StringTokenizer;
+
+/**
+ * DynamicsProcessing is an audio effect for equalizing and changing dynamic range properties of the
+ * sound. It is composed of multiple stages including equalization, multi-band compression and
+ * limiter.
+ * <p>The number of bands and active stages is configurable, and most parameters can be controlled
+ * in realtime, such as gains, attack/release times, thresholds, etc.
+ * <p>The effect is instantiated and controlled by channels. Each channel has the same basic
+ * architecture, but all of their parameters are independent from other channels.
+ * <p>The basic channel configuration is:
+ * <pre>
+ *
+ * Channel 0 Channel 1 .... Channel N-1
+ * Input Input Input
+ * | | |
+ * +----v----+ +----v----+ +----v----+
+ * |inputGain| |inputGain| |inputGain|
+ * +---------+ +---------+ +---------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | PreEQ | | PreEQ | | PreEQ |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | MBC | | MBC | | MBC |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | PostEQ | | PostEQ | | PostEQ |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * +-----v-----+ +-----v-----+ +-----v-----+
+ * | Limiter | | Limiter | | Limiter |
+ * +-----------+ +-----------+ +-----------+
+ * | | |
+ * Output Output Output
+ * </pre>
+ *
+ * <p>Where the stages are:
+ * inputGain: input gain factor in decibels (dB). 0 dB means no change in level.
+ * PreEQ: Multi-band Equalizer.
+ * MBC: Multi-band Compressor
+ * PostEQ: Multi-band Equalizer
+ * Limiter: Single band compressor/limiter.
+ *
+ * <p>An application creates a DynamicsProcessing object to instantiate and control this audio
+ * effect in the audio framework. A DynamicsProcessor.Config and DynamicsProcessor.Config.Builder
+ * are available to help configure the multiple stages and each band parameters if desired.
+ * <p>See each stage documentation for further details.
+ * <p>If no Config is specified during creation, a default configuration is chosen.
+ * <p>To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer,
+ * specify the audio session ID of this AudioTrack or MediaPlayer when constructing the effect
+ * (see {@link AudioTrack#getAudioSessionId()} and {@link MediaPlayer#getAudioSessionId()}).
+ *
+ * <p>To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer, specify the audio
+ * session ID of this AudioTrack or MediaPlayer when constructing the DynamicsProcessing.
+ * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
+ * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
+ * effects.
+ */
+
+public final class DynamicsProcessing extends AudioEffect {
+
+ private final static String TAG = "DynamicsProcessing";
+
+ // These parameter constants must be synchronized with those in
+ // /system/media/audio_effects/include/audio_effects/effect_dynamicsprocessing.h
+ private static final int PARAM_GET_CHANNEL_COUNT = 0x10;
+ private static final int PARAM_INPUT_GAIN = 0x20;
+ private static final int PARAM_ENGINE_ARCHITECTURE = 0x30;
+ private static final int PARAM_PRE_EQ = 0x40;
+ private static final int PARAM_PRE_EQ_BAND = 0x45;
+ private static final int PARAM_MBC = 0x50;
+ private static final int PARAM_MBC_BAND = 0x55;
+ private static final int PARAM_POST_EQ = 0x60;
+ private static final int PARAM_POST_EQ_BAND = 0x65;
+ private static final int PARAM_LIMITER = 0x70;
+
+ /**
+ * Index of variant that favors frequency resolution. Frequency domain based implementation.
+ */
+ public static final int VARIANT_FAVOR_FREQUENCY_RESOLUTION = 0;
+
+ /**
+ * Index of variant that favors time resolution resolution. Time domain based implementation.
+ */
+ public static final int VARIANT_FAVOR_TIME_RESOLUTION = 1;
+
+ /**
+ * Maximum expected channels to be reported by effect
+ */
+ private static final int CHANNEL_COUNT_MAX = 32;
+
+ /**
+ * Number of channels in effect architecture
+ */
+ private int mChannelCount = 0;
+
+ /**
+ * Registered listener for parameter changes.
+ */
+ private OnParameterChangeListener mParamListener = null;
+
+ /**
+ * Listener used internally to to receive raw parameter change events
+ * from AudioEffect super class
+ */
+ private BaseParameterListener mBaseParamListener = null;
+
+ /**
+ * Lock for access to mParamListener
+ */
+ private final Object mParamListenerLock = new Object();
+
+ /**
+ * Class constructor.
+ * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
+ * will be attached to the MediaPlayer or AudioTrack in the same audio session.
+ */
+ public DynamicsProcessing(int audioSession) {
+ this(0 /*priority*/, audioSession);
+ }
+
+ /**
+ * @hide
+ * Class constructor for the DynamicsProcessing audio effect.
+ * @param priority the priority level requested by the application for controlling the
+ * DynamicsProcessing engine. As the same engine can be shared by several applications,
+ * this parameter indicates how much the requesting application needs control of effect
+ * parameters. The normal priority is 0, above normal is a positive number, below normal a
+ * negative number.
+ * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
+ * will be attached to the MediaPlayer or AudioTrack in the same audio session.
+ */
+ public DynamicsProcessing(int priority, int audioSession) {
+ this(priority, audioSession, null);
+ }
+
+ /**
+ * Class constructor for the DynamicsProcessing audio effect
+ * @param priority the priority level requested by the application for controlling the
+ * DynamicsProcessing engine. As the same engine can be shared by several applications,
+ * this parameter indicates how much the requesting application needs control of effect
+ * parameters. The normal priority is 0, above normal is a positive number, below normal a
+ * negative number.
+ * @param audioSession system-wide unique audio session identifier. The DynamicsProcessing
+ * will be attached to the MediaPlayer or AudioTrack in the same audio session.
+ * @param cfg Config object used to setup the audio effect, including bands per stage, and
+ * specific parameters for each stage/band. Use
+ * {@link android.media.audiofx.DynamicsProcessing.Config.Builder} to create a
+ * Config object that suits your needs. A null cfg parameter will create and use a default
+ * configuration for the effect
+ */
+ public DynamicsProcessing(int priority, int audioSession, @Nullable Config cfg) {
+ super(EFFECT_TYPE_DYNAMICS_PROCESSING, EFFECT_TYPE_NULL, priority, audioSession);
+ if (audioSession == 0) {
+ Log.w(TAG, "WARNING: attaching a DynamicsProcessing to global output mix is"
+ + "deprecated!");
+ }
+ final Config config;
+ mChannelCount = getChannelCount();
+ if (cfg == null) {
+ //create a default configuration and effect, with the number of channels this effect has
+ DynamicsProcessing.Config.Builder builder =
+ new DynamicsProcessing.Config.Builder(
+ CONFIG_DEFAULT_VARIANT,
+ mChannelCount,
+ CONFIG_DEFAULT_USE_PREEQ,
+ CONFIG_DEFAULT_PREEQ_BANDS,
+ CONFIG_DEFAULT_USE_MBC,
+ CONFIG_DEFAULT_MBC_BANDS,
+ CONFIG_DEFAULT_USE_POSTEQ,
+ CONFIG_DEFAULT_POSTEQ_BANDS,
+ CONFIG_DEFAULT_USE_LIMITER);
+ config = builder.build();
+ } else {
+ //validate channels are ok. decide what to do: replicate channels if more
+ config = new DynamicsProcessing.Config(mChannelCount, cfg);
+ }
+
+ //configure engine
+ setEngineArchitecture(config.getVariant(),
+ config.getPreferredFrameDuration(),
+ config.isPreEqInUse(),
+ config.getPreEqBandCount(),
+ config.isMbcInUse(),
+ config.getMbcBandCount(),
+ config.isPostEqInUse(),
+ config.getPostEqBandCount(),
+ config.isLimiterInUse());
+ //update all the parameters
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ updateEngineChannelByChannelIndex(ch, config.getChannelByChannelIndex(ch));
+ }
+ }
+
+ /**
+ * Returns the Config object used to setup this effect.
+ * @return Config Current Config object used to setup this DynamicsProcessing effect.
+ */
+ public Config getConfig() {
+ //Query engine architecture to create config object
+ Number[] params = { PARAM_ENGINE_ARCHITECTURE };
+ Number[] values = { 0 /*0 variant */,
+ 0.0f /* 1 preferredFrameDuration */,
+ 0 /*2 preEqInUse */,
+ 0 /*3 preEqBandCount */,
+ 0 /*4 mbcInUse */,
+ 0 /*5 mbcBandCount*/,
+ 0 /*6 postEqInUse */,
+ 0 /*7 postEqBandCount */,
+ 0 /*8 limiterInUse */};
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
+ getParameter(paramBytes, valueBytes);
+ byteArrayToNumberArray(valueBytes, values);
+ DynamicsProcessing.Config.Builder builder =
+ new DynamicsProcessing.Config.Builder(
+ values[0].intValue(),
+ mChannelCount,
+ values[2].intValue() > 0 /*use preEQ*/,
+ values[3].intValue() /*pre eq bands*/,
+ values[4].intValue() > 0 /*use mbc*/,
+ values[5].intValue() /*mbc bands*/,
+ values[6].intValue() > 0 /*use postEQ*/,
+ values[7].intValue()/*postEq bands*/,
+ values[8].intValue() > 0 /*use Limiter*/).
+ setPreferredFrameDuration(values[1].floatValue());
+ Config config = builder.build();
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ Channel channel = queryEngineByChannelIndex(ch);
+ config.setChannelTo(ch, channel);
+ }
+ return config;
+ }
+
+
+ private static final int CONFIG_DEFAULT_VARIANT = VARIANT_FAVOR_FREQUENCY_RESOLUTION;
+ private static final boolean CONFIG_DEFAULT_USE_PREEQ = true;
+ private static final int CONFIG_DEFAULT_PREEQ_BANDS = 6;
+ private static final boolean CONFIG_DEFAULT_USE_MBC = true;
+ private static final int CONFIG_DEFAULT_MBC_BANDS = 6;
+ private static final boolean CONFIG_DEFAULT_USE_POSTEQ = true;
+ private static final int CONFIG_DEFAULT_POSTEQ_BANDS = 6;
+ private static final boolean CONFIG_DEFAULT_USE_LIMITER = true;
+
+ private static final float CHANNEL_DEFAULT_INPUT_GAIN = 0; // dB
+ private static final float CONFIG_PREFERRED_FRAME_DURATION_MS = 10.0f; //milliseconds
+
+ private static final float EQ_DEFAULT_GAIN = 0; // dB
+ private static final boolean PREEQ_DEFAULT_ENABLED = true;
+ private static final boolean POSTEQ_DEFAULT_ENABLED = true;
+
+
+ private static final boolean MBC_DEFAULT_ENABLED = true;
+ private static final float MBC_DEFAULT_ATTACK_TIME = 50; // ms
+ private static final float MBC_DEFAULT_RELEASE_TIME = 120; // ms
+ private static final float MBC_DEFAULT_RATIO = 2; // 1:N
+ private static final float MBC_DEFAULT_THRESHOLD = -30; // dB
+ private static final float MBC_DEFAULT_KNEE_WIDTH = 0; // dB
+ private static final float MBC_DEFAULT_NOISE_GATE_THRESHOLD = -80; // dB
+ private static final float MBC_DEFAULT_EXPANDER_RATIO = 1.5f; // N:1
+ private static final float MBC_DEFAULT_PRE_GAIN = 0; // dB
+ private static final float MBC_DEFAULT_POST_GAIN = 10; // dB
+
+ private static final boolean LIMITER_DEFAULT_ENABLED = true;
+ private static final int LIMITER_DEFAULT_LINK_GROUP = 0;//;
+ private static final float LIMITER_DEFAULT_ATTACK_TIME = 50; // ms
+ private static final float LIMITER_DEFAULT_RELEASE_TIME = 120; // ms
+ private static final float LIMITER_DEFAULT_RATIO = 2; // 1:N
+ private static final float LIMITER_DEFAULT_THRESHOLD = -30; // dB
+ private static final float LIMITER_DEFAULT_POST_GAIN = 10; // dB
+
+ private static final float DEFAULT_MIN_FREQUENCY = 220; // Hz
+ private static final float DEFAULT_MAX_FREQUENCY = 20000; // Hz
+ private static final float mMinFreqLog = (float)Math.log10(DEFAULT_MIN_FREQUENCY);
+ private static final float mMaxFreqLog = (float)Math.log10(DEFAULT_MAX_FREQUENCY);
+
+ /**
+ * base class for the different stages.
+ */
+ public static class Stage {
+ private boolean mInUse;
+ private boolean mEnabled;
+ /**
+ * Class constructor for stage
+ * @param inUse true if this stage is set to be used. False otherwise. Stages that are not
+ * set "inUse" at initialization time are not available to be used at any time.
+ * @param enabled true if this stage is currently used to process sound. When disabled,
+ * the stage is bypassed and the sound is copied unaltered from input to output.
+ */
+ public Stage(boolean inUse, boolean enabled) {
+ mInUse = inUse;
+ mEnabled = enabled;
+ }
+
+ /**
+ * returns enabled state of the stage
+ * @return true if stage is enabled for processing, false otherwise
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+ /**
+ * sets enabled state of the stage
+ * @param enabled true for enabled, false otherwise
+ */
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ /**
+ * returns inUse state of the stage.
+ * @return inUse state of the stage. True if this stage is currently used to process sound.
+ * When false, the stage is bypassed and the sound is copied unaltered from input to output.
+ */
+ public boolean isInUse() {
+ return mInUse;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(" Stage InUse: %b\n", isInUse()));
+ if (isInUse()) {
+ sb.append(String.format(" Stage Enabled: %b\n", mEnabled));
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Base class for stages that hold bands
+ */
+ public static class BandStage extends Stage{
+ private int mBandCount;
+ /**
+ * Class constructor for BandStage
+ * @param inUse true if this stage is set to be used. False otherwise. Stages that are not
+ * set "inUse" at initialization time are not available to be used at any time.
+ * @param enabled true if this stage is currently used to process sound. When disabled,
+ * the stage is bypassed and the sound is copied unaltered from input to output.
+ * @param bandCount number of bands this stage will handle. If stage is not inUse, bandcount
+ * is set to 0
+ */
+ public BandStage(boolean inUse, boolean enabled, int bandCount) {
+ super(inUse, enabled);
+ mBandCount = isInUse() ? bandCount : 0;
+ }
+
+ /**
+ * gets number of bands held in this stage
+ * @return number of bands held in this stage
+ */
+ public int getBandCount() {
+ return mBandCount;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append(String.format(" Band Count: %d\n", mBandCount));
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Base class for bands
+ */
+ public static class BandBase {
+ private boolean mEnabled;
+ private float mCutoffFrequency;
+ /**
+ * Class constructor for BandBase
+ * @param enabled true if this band is currently used to process sound. When false,
+ * the band is effectively muted and sound set to zero.
+ * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ */
+ public BandBase(boolean enabled, float cutoffFrequency) {
+ mEnabled = enabled;
+ mCutoffFrequency = cutoffFrequency;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(" Enabled: %b\n", mEnabled));
+ sb.append(String.format(" CutoffFrequency: %f\n", mCutoffFrequency));
+ return sb.toString();
+ }
+
+ /**
+ * returns enabled state of the band
+ * @return true if bands is enabled for processing, false otherwise
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+ /**
+ * sets enabled state of the band
+ * @param enabled true for enabled, false otherwise
+ */
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ /**
+ * gets cutoffFrequency for this band in Hertz (Hz)
+ * @return cutoffFrequency for this band in Hertz (Hz)
+ */
+ public float getCutoffFrequency() {
+ return mCutoffFrequency;
+ }
+
+ /**
+ * sets topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ * @param frequency
+ */
+ public void setCutoffFrequency(float frequency) {
+ mCutoffFrequency = frequency;
+ }
+ }
+
+ /**
+ * Class for Equalizer Bands
+ * Equalizer bands have three controllable parameters: enabled/disabled, cutoffFrequency and
+ * gain
+ */
+ public final static class EqBand extends BandBase {
+ private float mGain;
+ /**
+ * Class constructor for EqBand
+ * @param enabled true if this band is currently used to process sound. When false,
+ * the band is effectively muted and sound set to zero.
+ * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ * @param gain of equalizer band in decibels (dB). A gain of 0 dB means no change in level.
+ */
+ public EqBand(boolean enabled, float cutoffFrequency, float gain) {
+ super(enabled, cutoffFrequency);
+ mGain = gain;
+ }
+
+ /**
+ * Class constructor for EqBand
+ * @param cfg copy constructor
+ */
+ public EqBand(EqBand cfg) {
+ super(cfg.isEnabled(), cfg.getCutoffFrequency());
+ mGain = cfg.mGain;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ sb.append(String.format(" Gain: %f\n", mGain));
+ return sb.toString();
+ }
+
+ /**
+ * gets current gain of band in decibels (dB)
+ * @return current gain of band in decibels (dB)
+ */
+ public float getGain() {
+ return mGain;
+ }
+
+ /**
+ * sets current gain of band in decibels (dB)
+ * @param gain desired in decibels (db)
+ */
+ public void setGain(float gain) {
+ mGain = gain;
+ }
+ }
+
+ /**
+ * Class for Multi-Band compressor bands
+ * MBC bands have multiple controllable parameters: enabled/disabled, cutoffFrequency,
+ * attackTime, releaseTime, ratio, threshold, kneeWidth, noiseGateThreshold, expanderRatio,
+ * preGain and postGain.
+ */
+ public final static class MbcBand extends BandBase{
+ private float mAttackTime;
+ private float mReleaseTime;
+ private float mRatio;
+ private float mThreshold;
+ private float mKneeWidth;
+ private float mNoiseGateThreshold;
+ private float mExpanderRatio;
+ private float mPreGain;
+ private float mPostGain;
+ /**
+ * Class constructor for MbcBand
+ * @param enabled true if this band is currently used to process sound. When false,
+ * the band is effectively muted and sound set to zero.
+ * @param cutoffFrequency topmost frequency number (in Hz) this band will process. The
+ * effective bandwidth for the band is then computed using this and the previous band
+ * topmost frequency (or 0 Hz for band number 0). Frequencies are expected to increase with
+ * band number, thus band 0 cutoffFrequency <= band 1 cutoffFrequency, and so on.
+ * @param attackTime Attack Time for compressor in milliseconds (ms)
+ * @param releaseTime Release Time for compressor in milliseconds (ms)
+ * @param ratio Compressor ratio (1:N)
+ * @param threshold Compressor threshold measured in decibels (dB) from 0 dB Full Scale
+ * (dBFS).
+ * @param kneeWidth Width in decibels (dB) around compressor threshold point.
+ * @param noiseGateThreshold Noise gate threshold in decibels (dB) from 0 dB Full Scale
+ * (dBFS).
+ * @param expanderRatio Expander ratio (N:1) for signals below the Noise Gate Threshold.
+ * @param preGain Gain applied to the signal BEFORE the compression.
+ * @param postGain Gain applied to the signal AFTER compression.
+ */
+ public MbcBand(boolean enabled, float cutoffFrequency, float attackTime, float releaseTime,
+ float ratio, float threshold, float kneeWidth, float noiseGateThreshold,
+ float expanderRatio, float preGain, float postGain) {
+ super(enabled, cutoffFrequency);
+ mAttackTime = attackTime;
+ mReleaseTime = releaseTime;
+ mRatio = ratio;
+ mThreshold = threshold;
+ mKneeWidth = kneeWidth;
+ mNoiseGateThreshold = noiseGateThreshold;
+ mExpanderRatio = expanderRatio;
+ mPreGain = preGain;
+ mPostGain = postGain;
+ }
+
+ /**
+ * Class constructor for MbcBand
+ * @param cfg copy constructor
+ */
+ public MbcBand(MbcBand cfg) {
+ super(cfg.isEnabled(), cfg.getCutoffFrequency());
+ mAttackTime = cfg.mAttackTime;
+ mReleaseTime = cfg.mReleaseTime;
+ mRatio = cfg.mRatio;
+ mThreshold = cfg.mThreshold;
+ mKneeWidth = cfg.mKneeWidth;
+ mNoiseGateThreshold = cfg.mNoiseGateThreshold;
+ mExpanderRatio = cfg.mExpanderRatio;
+ mPreGain = cfg.mPreGain;
+ mPostGain = cfg.mPostGain;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ sb.append(String.format(" AttackTime: %f (ms)\n", mAttackTime));
+ sb.append(String.format(" ReleaseTime: %f (ms)\n", mReleaseTime));
+ sb.append(String.format(" Ratio: 1:%f\n", mRatio));
+ sb.append(String.format(" Threshold: %f (dB)\n", mThreshold));
+ sb.append(String.format(" NoiseGateThreshold: %f(dB)\n", mNoiseGateThreshold));
+ sb.append(String.format(" ExpanderRatio: %f:1\n", mExpanderRatio));
+ sb.append(String.format(" PreGain: %f (dB)\n", mPreGain));
+ sb.append(String.format(" PostGain: %f (dB)\n", mPostGain));
+ return sb.toString();
+ }
+
+ /**
+ * gets attack time for compressor in milliseconds (ms)
+ * @return attack time for compressor in milliseconds (ms)
+ */
+ public float getAttackTime() { return mAttackTime; }
+ /**
+ * sets attack time for compressor in milliseconds (ms)
+ * @param attackTime desired for compressor in milliseconds (ms)
+ */
+ public void setAttackTime(float attackTime) { mAttackTime = attackTime; }
+ /**
+ * gets release time for compressor in milliseconds (ms)
+ * @return release time for compressor in milliseconds (ms)
+ */
+ public float getReleaseTime() { return mReleaseTime; }
+ /**
+ * sets release time for compressor in milliseconds (ms)
+ * @param releaseTime desired for compressor in milliseconds (ms)
+ */
+ public void setReleaseTime(float releaseTime) { mReleaseTime = releaseTime; }
+ /**
+ * gets the compressor ratio (1:N)
+ * @return compressor ratio (1:N)
+ */
+ public float getRatio() { return mRatio; }
+ /**
+ * sets compressor ratio (1:N)
+ * @param ratio desired for the compressor (1:N)
+ */
+ public void setRatio(float ratio) { mRatio = ratio; }
+ /**
+ * gets the compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
+ * Thresholds are negative. A threshold of 0 dB means no compression will take place.
+ * @return compressor threshold in decibels (dB)
+ */
+ public float getThreshold() { return mThreshold; }
+ /**
+ * sets the compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
+ * Thresholds are negative. A threshold of 0 dB means no compression will take place.
+ * @param threshold desired for compressor in decibels(dB)
+ */
+ public void setThreshold(float threshold) { mThreshold = threshold; }
+ /**
+ * get Knee Width in decibels (dB) around compressor threshold point. Widths are always
+ * positive, with higher values representing a wider area of transition from the linear zone
+ * to the compression zone. A knee of 0 dB means a more abrupt transition.
+ * @return Knee Width in decibels (dB)
+ */
+ public float getKneeWidth() { return mKneeWidth; }
+ /**
+ * sets knee width in decibels (dB). See
+ * {@link android.media.audiofx.DynamicsProcessing.MbcBand#getKneeWidth} for more
+ * information.
+ * @param kneeWidth desired in decibels (dB)
+ */
+ public void setKneeWidth(float kneeWidth) { mKneeWidth = kneeWidth; }
+ /**
+ * gets the noise gate threshold in decibels (dB) from 0 dB Full Scale (dBFS). Noise gate
+ * thresholds are negative. Signals below this level will be expanded according the
+ * expanderRatio parameter. A Noise Gate Threshold of -75 dB means very quiet signals might
+ * be effectively removed from the signal.
+ * @return Noise Gate Threshold in decibels (dB)
+ */
+ public float getNoiseGateThreshold() { return mNoiseGateThreshold; }
+ /**
+ * sets noise gate threshod in decibels (dB). See
+ * {@link android.media.audiofx.DynamicsProcessing.MbcBand#getNoiseGateThreshold} for more
+ * information.
+ * @param noiseGateThreshold desired in decibels (dB)
+ */
+ public void setNoiseGateThreshold(float noiseGateThreshold) {
+ mNoiseGateThreshold = noiseGateThreshold; }
+ /**
+ * gets Expander ratio (N:1) for signals below the Noise Gate Threshold.
+ * @return Expander ratio (N:1)
+ */
+ public float getExpanderRatio() { return mExpanderRatio; }
+ /**
+ * sets Expander ratio (N:1) for signals below the Noise Gate Threshold.
+ * @param expanderRatio desired expander ratio (N:1)
+ */
+ public void setExpanderRatio(float expanderRatio) { mExpanderRatio = expanderRatio; }
+ /**
+ * gets the gain applied to the signal BEFORE the compression. Measured in decibels (dB)
+ * where 0 dB means no level change.
+ * @return preGain value in decibels (dB)
+ */
+ public float getPreGain() { return mPreGain; }
+ /**
+ * sets the gain to be applied to the signal BEFORE the compression, measured in decibels
+ * (dB), where 0 dB means no level change.
+ * @param preGain desired in decibels (dB)
+ */
+ public void setPreGain(float preGain) { mPreGain = preGain; }
+ /**
+ * gets the gain applied to the signal AFTER compression. Measured in decibels (dB) where 0
+ * dB means no level change
+ * @return postGain value in decibels (dB)
+ */
+ public float getPostGain() { return mPostGain; }
+ /**
+ * sets the gain to be applied to the siganl AFTER the compression. Measured in decibels
+ * (dB), where 0 dB means no level change.
+ * @param postGain desired value in decibels (dB)
+ */
+ public void setPostGain(float postGain) { mPostGain = postGain; }
+ }
+
+ /**
+ * Class for Equalizer stage
+ */
+ public final static class Eq extends BandStage {
+ private final EqBand[] mBands;
+ /**
+ * Class constructor for Equalizer (Eq) stage
+ * @param inUse true if Eq stage will be used, false otherwise.
+ * @param enabled true if Eq stage is enabled/disabled. This can be changed while effect is
+ * running
+ * @param bandCount number of bands for this Equalizer stage. Can't be changed while effect
+ * is running
+ */
+ public Eq(boolean inUse, boolean enabled, int bandCount) {
+ super(inUse, enabled, bandCount);
+ if (isInUse()) {
+ mBands = new EqBand[bandCount];
+ for (int b = 0; b < bandCount; b++) {
+ float freq = DEFAULT_MAX_FREQUENCY;
+ if (bandCount > 1) {
+ freq = (float)Math.pow(10, mMinFreqLog +
+ b * (mMaxFreqLog - mMinFreqLog)/(bandCount -1));
+ }
+ mBands[b] = new EqBand(true, freq, EQ_DEFAULT_GAIN);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+ /**
+ * Class constructor for Eq stage
+ * @param cfg copy constructor
+ */
+ public Eq(Eq cfg) {
+ super(cfg.isInUse(), cfg.isEnabled(), cfg.getBandCount());
+ if (isInUse()) {
+ mBands = new EqBand[cfg.mBands.length];
+ for (int b = 0; b < mBands.length; b++) {
+ mBands[b] = new EqBand(cfg.mBands[b]);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append("--->EqBands: " + mBands.length + "\n");
+ for (int b = 0; b < mBands.length; b++) {
+ sb.append(String.format(" Band %d\n", b));
+ sb.append(mBands[b].toString());
+ }
+ }
+ return sb.toString();
+ }
+ /**
+ * Helper function to check if band index is within range
+ * @param band index to check
+ */
+ private void checkBand(int band) {
+ if (mBands == null || band < 0 || band >= mBands.length) {
+ throw new IllegalArgumentException("band index " + band +" out of bounds");
+ }
+ }
+ /**
+ * Sets EqBand object for given band index
+ * @param band index of band to be modified
+ * @param bandCfg EqBand object.
+ */
+ public void setBand(int band, EqBand bandCfg) {
+ checkBand(band);
+ mBands[band] = new EqBand(bandCfg);
+ }
+ /**
+ * Gets EqBand object for band of interest.
+ * @param band index of band of interest
+ * @return EqBand Object
+ */
+ public EqBand getBand(int band) {
+ checkBand(band);
+ return mBands[band];
+ }
+ }
+
+ /**
+ * Class for Multi-Band Compressor (MBC) stage
+ */
+ public final static class Mbc extends BandStage {
+ private final MbcBand[] mBands;
+ /**
+ * Constructor for Multi-Band Compressor (MBC) stage
+ * @param inUse true if MBC stage will be used, false otherwise.
+ * @param enabled true if MBC stage is enabled/disabled. This can be changed while effect
+ * is running
+ * @param bandCount number of bands for this MBC stage. Can't be changed while effect is
+ * running
+ */
+ public Mbc(boolean inUse, boolean enabled, int bandCount) {
+ super(inUse, enabled, bandCount);
+ if (isInUse()) {
+ mBands = new MbcBand[bandCount];
+ for (int b = 0; b < bandCount; b++) {
+ float freq = DEFAULT_MAX_FREQUENCY;
+ if (bandCount > 1) {
+ freq = (float)Math.pow(10, mMinFreqLog +
+ b * (mMaxFreqLog - mMinFreqLog)/(bandCount -1));
+ }
+ mBands[b] = new MbcBand(true, freq, MBC_DEFAULT_ATTACK_TIME,
+ MBC_DEFAULT_RELEASE_TIME, MBC_DEFAULT_RATIO,
+ MBC_DEFAULT_THRESHOLD, MBC_DEFAULT_KNEE_WIDTH,
+ MBC_DEFAULT_NOISE_GATE_THRESHOLD, MBC_DEFAULT_EXPANDER_RATIO,
+ MBC_DEFAULT_PRE_GAIN, MBC_DEFAULT_POST_GAIN);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+ /**
+ * Class constructor for MBC stage
+ * @param cfg copy constructor
+ */
+ public Mbc(Mbc cfg) {
+ super(cfg.isInUse(), cfg.isEnabled(), cfg.getBandCount());
+ if (isInUse()) {
+ mBands = new MbcBand[cfg.mBands.length];
+ for (int b = 0; b < mBands.length; b++) {
+ mBands[b] = new MbcBand(cfg.mBands[b]);
+ }
+ } else {
+ mBands = null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append("--->MbcBands: " + mBands.length + "\n");
+ for (int b = 0; b < mBands.length; b++) {
+ sb.append(String.format(" Band %d\n", b));
+ sb.append(mBands[b].toString());
+ }
+ }
+ return sb.toString();
+ }
+ /**
+ * Helper function to check if band index is within range
+ * @param band index to check
+ */
+ private void checkBand(int band) {
+ if (mBands == null || band < 0 || band >= mBands.length) {
+ throw new IllegalArgumentException("band index " + band +" out of bounds");
+ }
+ }
+ /**
+ * Sets MbcBand object for given band index
+ * @param band index of band to be modified
+ * @param bandCfg MbcBand object.
+ */
+ public void setBand(int band, MbcBand bandCfg) {
+ checkBand(band);
+ mBands[band] = new MbcBand(bandCfg);
+ }
+ /**
+ * Gets MbcBand object for band of interest.
+ * @param band index of band of interest
+ * @return MbcBand Object
+ */
+ public MbcBand getBand(int band) {
+ checkBand(band);
+ return mBands[band];
+ }
+ }
+
+ /**
+ * Class for Limiter Stage
+ * Limiter is a single band compressor at the end of the processing chain, commonly used to
+ * protect the signal from overloading and distortion. Limiters have multiple controllable
+ * parameters: enabled/disabled, linkGroup, attackTime, releaseTime, ratio, threshold, and
+ * postGain.
+ * <p>Limiters can be linked in groups across multiple channels. Linked limiters will trigger
+ * the same limiting if any of the linked limiters starts compressing.
+ */
+ public final static class Limiter extends Stage {
+ private int mLinkGroup;
+ private float mAttackTime;
+ private float mReleaseTime;
+ private float mRatio;
+ private float mThreshold;
+ private float mPostGain;
+
+ /**
+ * Class constructor for Limiter Stage
+ * @param inUse true if MBC stage will be used, false otherwise.
+ * @param enabled true if MBC stage is enabled/disabled. This can be changed while effect
+ * is running
+ * @param linkGroup index of group assigned to this Limiter. Only limiters that share the
+ * same linkGroup index will react together.
+ * @param attackTime Attack Time for limiter compressor in milliseconds (ms)
+ * @param releaseTime Release Time for limiter compressor in milliseconds (ms)
+ * @param ratio Limiter Compressor ratio (1:N)
+ * @param threshold Limiter Compressor threshold measured in decibels (dB) from 0 dB Full
+ * Scale (dBFS).
+ * @param postGain Gain applied to the signal AFTER compression.
+ */
+ public Limiter(boolean inUse, boolean enabled, int linkGroup, float attackTime,
+ float releaseTime, float ratio, float threshold, float postGain) {
+ super(inUse, enabled);
+ mLinkGroup = linkGroup;
+ mAttackTime = attackTime;
+ mReleaseTime = releaseTime;
+ mRatio = ratio;
+ mThreshold = threshold;
+ mPostGain = postGain;
+ }
+
+ /**
+ * Class Constructor for Limiter
+ * @param cfg copy constructor
+ */
+ public Limiter(Limiter cfg) {
+ super(cfg.isInUse(), cfg.isEnabled());
+ mLinkGroup = cfg.mLinkGroup;
+ mAttackTime = cfg.mAttackTime;
+ mReleaseTime = cfg.mReleaseTime;
+ mRatio = cfg.mRatio;
+ mThreshold = cfg.mThreshold;
+ mPostGain = cfg.mPostGain;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString());
+ if (isInUse()) {
+ sb.append(String.format(" LinkGroup: %d (group)\n", mLinkGroup));
+ sb.append(String.format(" AttackTime: %f (ms)\n", mAttackTime));
+ sb.append(String.format(" ReleaseTime: %f (ms)\n", mReleaseTime));
+ sb.append(String.format(" Ratio: 1:%f\n", mRatio));
+ sb.append(String.format(" Threshold: %f (dB)\n", mThreshold));
+ sb.append(String.format(" PostGain: %f (dB)\n", mPostGain));
+ }
+ return sb.toString();
+ }
+ /**
+ * Gets the linkGroup index for this Limiter Stage. Only limiters that share the same
+ * linkGroup index will react together.
+ * @return linkGroup index.
+ */
+ public int getLinkGroup() { return mLinkGroup; }
+ /**
+ * Sets the linkGroup index for this limiter Stage.
+ * @param linkGroup desired linkGroup index
+ */
+ public void setLinkGroup(int linkGroup) { mLinkGroup = linkGroup; }
+ /**
+ * gets attack time for limiter compressor in milliseconds (ms)
+ * @return attack time for limiter compressor in milliseconds (ms)
+ */
+ public float getAttackTime() { return mAttackTime; }
+ /**
+ * sets attack time for limiter compressor in milliseconds (ms)
+ * @param attackTime desired for limiter compressor in milliseconds (ms)
+ */
+ public void setAttackTime(float attackTime) { mAttackTime = attackTime; }
+ /**
+ * gets release time for limiter compressor in milliseconds (ms)
+ * @return release time for limiter compressor in milliseconds (ms)
+ */
+ public float getReleaseTime() { return mReleaseTime; }
+ /**
+ * sets release time for limiter compressor in milliseconds (ms)
+ * @param releaseTime desired for limiter compressor in milliseconds (ms)
+ */
+ public void setReleaseTime(float releaseTime) { mReleaseTime = releaseTime; }
+ /**
+ * gets the limiter compressor ratio (1:N)
+ * @return limiter compressor ratio (1:N)
+ */
+ public float getRatio() { return mRatio; }
+ /**
+ * sets limiter compressor ratio (1:N)
+ * @param ratio desired for the limiter compressor (1:N)
+ */
+ public void setRatio(float ratio) { mRatio = ratio; }
+ /**
+ * gets the limiter compressor threshold measured in decibels (dB) from 0 dB Full Scale
+ * (dBFS). Thresholds are negative. A threshold of 0 dB means no limiting will take place.
+ * @return limiter compressor threshold in decibels (dB)
+ */
+ public float getThreshold() { return mThreshold; }
+ /**
+ * sets the limiter compressor threshold measured in decibels (dB) from 0 dB Full Scale
+ * (dBFS). Thresholds are negative. A threshold of 0 dB means no limiting will take place.
+ * @param threshold desired for limiter compressor in decibels(dB)
+ */
+ public void setThreshold(float threshold) { mThreshold = threshold; }
+ /**
+ * gets the gain applied to the signal AFTER limiting. Measured in decibels (dB) where 0
+ * dB means no level change
+ * @return postGain value in decibels (dB)
+ */
+ public float getPostGain() { return mPostGain; }
+ /**
+ * sets the gain to be applied to the siganl AFTER the limiter. Measured in decibels
+ * (dB), where 0 dB means no level change.
+ * @param postGain desired value in decibels (dB)
+ */
+ public void setPostGain(float postGain) { mPostGain = postGain; }
+ }
+
+ /**
+ * Class for Channel configuration parameters. It is composed of multiple stages, which can be
+ * used/enabled independently. Stages not used or disabled will be bypassed and the sound would
+ * be unaffected by them.
+ */
+ public final static class Channel {
+ private float mInputGain;
+ private Eq mPreEq;
+ private Mbc mMbc;
+ private Eq mPostEq;
+ private Limiter mLimiter;
+
+ /**
+ * Class constructor for Channel configuration.
+ * @param inputGain value in decibels (dB) of level change applied to the audio before
+ * processing. A value of 0 dB means no change.
+ * @param preEqInUse true if PreEq stage will be used, false otherwise. This can't be
+ * changed later.
+ * @param preEqBandCount number of bands for PreEq stage. This can't be changed later.
+ * @param mbcInUse true if Mbc stage will be used, false otherwise. This can't be changed
+ * later.
+ * @param mbcBandCount number of bands for Mbc stage. This can't be changed later.
+ * @param postEqInUse true if PostEq stage will be used, false otherwise. This can't be
+ * changed later.
+ * @param postEqBandCount number of bands for PostEq stage. This can't be changed later.
+ * @param limiterInUse true if Limiter stage will be used, false otherwise. This can't be
+ * changed later.
+ */
+ public Channel (float inputGain,
+ boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse) {
+ mInputGain = inputGain;
+ mPreEq = new Eq(preEqInUse, PREEQ_DEFAULT_ENABLED, preEqBandCount);
+ mMbc = new Mbc(mbcInUse, MBC_DEFAULT_ENABLED, mbcBandCount);
+ mPostEq = new Eq(postEqInUse, POSTEQ_DEFAULT_ENABLED,
+ postEqBandCount);
+ mLimiter = new Limiter(limiterInUse,
+ LIMITER_DEFAULT_ENABLED, LIMITER_DEFAULT_LINK_GROUP,
+ LIMITER_DEFAULT_ATTACK_TIME, LIMITER_DEFAULT_RELEASE_TIME,
+ LIMITER_DEFAULT_RATIO, LIMITER_DEFAULT_THRESHOLD, LIMITER_DEFAULT_POST_GAIN);
+ }
+
+ /**
+ * Class constructor for Channel configuration
+ * @param cfg copy constructor
+ */
+ public Channel(Channel cfg) {
+ mInputGain = cfg.mInputGain;
+ mPreEq = new Eq(cfg.mPreEq);
+ mMbc = new Mbc(cfg.mMbc);
+ mPostEq = new Eq(cfg.mPostEq);
+ mLimiter = new Limiter(cfg.mLimiter);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(" InputGain: %f\n", mInputGain));
+ sb.append("-->PreEq\n");
+ sb.append(mPreEq.toString());
+ sb.append("-->MBC\n");
+ sb.append(mMbc.toString());
+ sb.append("-->PostEq\n");
+ sb.append(mPostEq.toString());
+ sb.append("-->Limiter\n");
+ sb.append(mLimiter.toString());
+ return sb.toString();
+ }
+ /**
+ * Gets inputGain value in decibels (dB). 0 dB means no change;
+ * @return gain value in decibels (dB)
+ */
+ public float getInputGain() {
+ return mInputGain;
+ }
+ /**
+ * Sets inputGain value in decibels (dB). 0 dB means no change;
+ * @param inputGain desired gain value in decibels (dB)
+ */
+ public void setInputGain(float inputGain) {
+ mInputGain = inputGain;
+ }
+
+ /**
+ * Gets PreEq configuration stage
+ * @return PreEq configuration stage
+ */
+ public Eq getPreEq() {
+ return mPreEq;
+ }
+ /**
+ * Sets PreEq configuration stage. New PreEq stage must have the same number of bands than
+ * original PreEq stage.
+ * @param preEq configuration
+ */
+ public void setPreEq(Eq preEq) {
+ if (preEq.getBandCount() != mPreEq.getBandCount()) {
+ throw new IllegalArgumentException("PreEqBandCount changed from " +
+ mPreEq.getBandCount() + " to " + preEq.getBandCount());
+ }
+ mPreEq = new Eq(preEq);
+ }
+ /**
+ * Gets EqBand for PreEq stage for given band index.
+ * @param band index of band of interest from PreEq stage
+ * @return EqBand configuration
+ */
+ public EqBand getPreEqBand(int band) {
+ return mPreEq.getBand(band);
+ }
+ /**
+ * Sets EqBand for PreEq stage for given band index
+ * @param band index of band of interest from PreEq stage
+ * @param preEqBand configuration to be set.
+ */
+ public void setPreEqBand(int band, EqBand preEqBand) {
+ mPreEq.setBand(band, preEqBand);
+ }
+
+ /**
+ * Gets Mbc configuration stage
+ * @return Mbc configuration stage
+ */
+ public Mbc getMbc() {
+ return mMbc;
+ }
+ /**
+ * Sets Mbc configuration stage. New Mbc stage must have the same number of bands than
+ * original Mbc stage.
+ * @param mbc
+ */
+ public void setMbc(Mbc mbc) {
+ if (mbc.getBandCount() != mMbc.getBandCount()) {
+ throw new IllegalArgumentException("MbcBandCount changed from " +
+ mMbc.getBandCount() + " to " + mbc.getBandCount());
+ }
+ mMbc = new Mbc(mbc);
+ }
+ /**
+ * Gets MbcBand configuration for Mbc stage, for given band index.
+ * @param band index of band of interest from Mbc stage
+ * @return MbcBand configuration
+ */
+ public MbcBand getMbcBand(int band) {
+ return mMbc.getBand(band);
+ }
+ /**
+ * Sets MbcBand for Mbc stage for given band index
+ * @param band index of band of interest from Mbc Stage
+ * @param mbcBand configuration to be set
+ */
+ public void setMbcBand(int band, MbcBand mbcBand) {
+ mMbc.setBand(band, mbcBand);
+ }
+
+ /**
+ * Gets PostEq configuration stage
+ * @return PostEq configuration stage
+ */
+ public Eq getPostEq() {
+ return mPostEq;
+ }
+ /**
+ * Sets PostEq configuration stage. New PostEq stage must have the same number of bands than
+ * original PostEq stage.
+ * @param postEq configuration
+ */
+ public void setPostEq(Eq postEq) {
+ if (postEq.getBandCount() != mPostEq.getBandCount()) {
+ throw new IllegalArgumentException("PostEqBandCount changed from " +
+ mPostEq.getBandCount() + " to " + postEq.getBandCount());
+ }
+ mPostEq = new Eq(postEq);
+ }
+ /**
+ * Gets EqBand for PostEq stage for given band index.
+ * @param band index of band of interest from PostEq stage
+ * @return EqBand configuration
+ */
+ public EqBand getPostEqBand(int band) {
+ return mPostEq.getBand(band);
+ }
+ /**
+ * Sets EqBand for PostEq stage for given band index
+ * @param band index of band of interest from PostEq stage
+ * @param postEqBand configuration to be set.
+ */
+ public void setPostEqBand(int band, EqBand postEqBand) {
+ mPostEq.setBand(band, postEqBand);
+ }
+
+ /**
+ * Gets Limiter configuration stage
+ * @return Limiter configuration stage
+ */
+ public Limiter getLimiter() {
+ return mLimiter;
+ }
+ /**
+ * Sets Limiter configuration stage.
+ * @param limiter configuration stage.
+ */
+ public void setLimiter(Limiter limiter) {
+ mLimiter = new Limiter(limiter);
+ }
+ }
+
+ /**
+ * Class for Config object, used by DynamicsProcessing to configure and update the audio effect.
+ * use Builder to instantiate objects of this type.
+ */
+ public final static class Config {
+ private final int mVariant;
+ private final int mChannelCount;
+ private final boolean mPreEqInUse;
+ private final int mPreEqBandCount;
+ private final boolean mMbcInUse;
+ private final int mMbcBandCount;
+ private final boolean mPostEqInUse;
+ private final int mPostEqBandCount;
+ private final boolean mLimiterInUse;
+ private final float mPreferredFrameDuration;
+ private final Channel[] mChannel;
+
+ /**
+ * @hide
+ * Class constructor for config. None of these parameters can be changed later.
+ * @param variant index of variant used for effect engine. See
+ * {@link #VARIANT_FAVOR_FREQUENCY_RESOLUTION} and {@link #VARIANT_FAVOR_TIME_RESOLUTION}.
+ * @param frameDurationMs preferred frame duration in milliseconds (ms).
+ * @param channelCount Number of channels to be configured.
+ * @param preEqInUse true if PreEq stage will be used, false otherwise.
+ * @param preEqBandCount number of bands for PreEq stage.
+ * @param mbcInUse true if Mbc stage will be used, false otherwise.
+ * @param mbcBandCount number of bands for Mbc stage.
+ * @param postEqInUse true if PostEq stage will be used, false otherwise.
+ * @param postEqBandCount number of bands for PostEq stage.
+ * @param limiterInUse true if Limiter stage will be used, false otherwise.
+ * @param channel array of Channel objects to be used for this configuration.
+ */
+ public Config(int variant, float frameDurationMs, int channelCount,
+ boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse,
+ Channel[] channel) {
+ mVariant = variant;
+ mPreferredFrameDuration = frameDurationMs;
+ mChannelCount = channelCount;
+ mPreEqInUse = preEqInUse;
+ mPreEqBandCount = preEqBandCount;
+ mMbcInUse = mbcInUse;
+ mMbcBandCount = mbcBandCount;
+ mPostEqInUse = postEqInUse;
+ mPostEqBandCount = postEqBandCount;
+ mLimiterInUse = limiterInUse;
+
+ mChannel = new Channel[mChannelCount];
+ //check if channelconfig is null or has less channels than channel count.
+ //options: fill the missing with default options.
+ // or fail?
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ if (ch < channel.length) {
+ mChannel[ch] = new Channel(channel[ch]); //copy create
+ } else {
+ //create a new one from scratch? //fail?
+ }
+ }
+ }
+ //a version that will scale to necessary number of channels
+ /**
+ * @hide
+ * Class constructor for Configuration.
+ * @param channelCount limit configuration to this number of channels. if channelCount is
+ * greater than number of channels in cfg, the constructor will duplicate the last channel
+ * found as many times as necessary to create a Config with channelCount number of channels.
+ * If channelCount is less than channels in cfg, the extra channels in cfg will be ignored.
+ * @param cfg copy constructor paremter.
+ */
+ public Config(int channelCount, Config cfg) {
+ mVariant = cfg.mVariant;
+ mPreferredFrameDuration = cfg.mPreferredFrameDuration;
+ mChannelCount = cfg.mChannelCount;
+ mPreEqInUse = cfg.mPreEqInUse;
+ mPreEqBandCount = cfg.mPreEqBandCount;
+ mMbcInUse = cfg.mMbcInUse;
+ mMbcBandCount = cfg.mMbcBandCount;
+ mPostEqInUse = cfg.mPostEqInUse;
+ mPostEqBandCount = cfg.mPostEqBandCount;
+ mLimiterInUse = cfg.mLimiterInUse;
+
+ if (mChannelCount != cfg.mChannel.length) {
+ throw new IllegalArgumentException("configuration channel counts differ " +
+ mChannelCount + " !=" + cfg.mChannel.length);
+ }
+ if (channelCount < 1) {
+ throw new IllegalArgumentException("channel resizing less than 1 not allowed");
+ }
+
+ mChannel = new Channel[channelCount];
+ for (int ch = 0; ch < channelCount; ch++) {
+ if (ch < mChannelCount) {
+ mChannel[ch] = new Channel(cfg.mChannel[ch]);
+ } else {
+ //duplicate last
+ mChannel[ch] = new Channel(cfg.mChannel[mChannelCount-1]);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Class constructor for Config
+ * @param cfg Configuration object copy constructor
+ */
+ public Config(@NonNull Config cfg) {
+ this(cfg.mChannelCount, cfg);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("Variant: %d\n", mVariant));
+ sb.append(String.format("PreferredFrameDuration: %f\n", mPreferredFrameDuration));
+ sb.append(String.format("ChannelCount: %d\n", mChannelCount));
+ sb.append(String.format("PreEq inUse: %b, bandCount:%d\n",mPreEqInUse,
+ mPreEqBandCount));
+ sb.append(String.format("Mbc inUse: %b, bandCount: %d\n",mMbcInUse, mMbcBandCount));
+ sb.append(String.format("PostEq inUse: %b, bandCount: %d\n", mPostEqInUse,
+ mPostEqBandCount));
+ sb.append(String.format("Limiter inUse: %b\n", mLimiterInUse));
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ sb.append(String.format("==Channel %d\n", ch));
+ sb.append(mChannel[ch].toString());
+ }
+ return sb.toString();
+ }
+ private void checkChannel(int channelIndex) {
+ if (channelIndex < 0 || channelIndex >= mChannel.length) {
+ throw new IllegalArgumentException("ChannelIndex out of bounds");
+ }
+ }
+
+ //getters and setters
+ /**
+ * Gets variant for effect engine See {@link #VARIANT_FAVOR_FREQUENCY_RESOLUTION} and
+ * {@link #VARIANT_FAVOR_TIME_RESOLUTION}.
+ * @return variant of effect engine
+ */
+ public int getVariant() {
+ return mVariant;
+ }
+ /**
+ * Gets preferred frame duration in milliseconds (ms).
+ * @return preferred frame duration in milliseconds (ms)
+ */
+ public float getPreferredFrameDuration() {
+ return mPreferredFrameDuration;
+ }
+ /**
+ * Gets if preEq stage is in use
+ * @return true if preEq stage is in use;
+ */
+ public boolean isPreEqInUse() {
+ return mPreEqInUse;
+ }
+ /**
+ * Gets number of bands configured for the PreEq stage.
+ * @return number of bands configured for the PreEq stage.
+ */
+ public int getPreEqBandCount() {
+ return mPreEqBandCount;
+ }
+ /**
+ * Gets if Mbc stage is in use
+ * @return true if Mbc stage is in use;
+ */
+ public boolean isMbcInUse() {
+ return mMbcInUse;
+ }
+ /**
+ * Gets number of bands configured for the Mbc stage.
+ * @return number of bands configured for the Mbc stage.
+ */
+ public int getMbcBandCount() {
+ return mMbcBandCount;
+ }
+ /**
+ * Gets if PostEq stage is in use
+ * @return true if PostEq stage is in use;
+ */
+ public boolean isPostEqInUse() {
+ return mPostEqInUse;
+ }
+ /**
+ * Gets number of bands configured for the PostEq stage.
+ * @return number of bands configured for the PostEq stage.
+ */
+ public int getPostEqBandCount() {
+ return mPostEqBandCount;
+ }
+ /**
+ * Gets if Limiter stage is in use
+ * @return true if Limiter stage is in use;
+ */
+ public boolean isLimiterInUse() {
+ return mLimiterInUse;
+ }
+
+ //channel
+ /**
+ * Gets the Channel configuration object by using the channel index
+ * @param channelIndex of desired Channel object
+ * @return Channel configuration object
+ */
+ public Channel getChannelByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex];
+ }
+
+ /**
+ * Sets the chosen Channel object in the selected channelIndex
+ * Note that all the stages should have the same number of bands than the existing Channel
+ * object.
+ * @param channelIndex index of channel to be replaced
+ * @param channel Channel configuration object to be set
+ */
+ public void setChannelTo(int channelIndex, Channel channel) {
+ checkChannel(channelIndex);
+ //check all things are compatible
+ if (mMbcBandCount != channel.getMbc().getBandCount()) {
+ throw new IllegalArgumentException("MbcBandCount changed from " +
+ mMbcBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPreEqBandCount != channel.getPreEq().getBandCount()) {
+ throw new IllegalArgumentException("PreEqBandCount changed from " +
+ mPreEqBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPostEqBandCount != channel.getPostEq().getBandCount()) {
+ throw new IllegalArgumentException("PostEqBandCount changed from " +
+ mPostEqBandCount + " to " + channel.getPostEq().getBandCount());
+ }
+ mChannel[channelIndex] = new Channel(channel);
+ }
+
+ /**
+ * Sets ALL channels to the chosen Channel object. Note that all the stages should have the
+ * same number of bands than the existing ones.
+ * @param channel Channel configuration object to be set.
+ */
+ public void setAllChannelsTo(Channel channel) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setChannelTo(ch, channel);
+ }
+ }
+
+ //===channel params
+ /**
+ * Gets inputGain value in decibels (dB) for channel indicated by channelIndex
+ * @param channelIndex index of channel of interest
+ * @return inputGain value in decibels (dB). 0 dB means no change.
+ */
+ public float getInputGainByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getInputGain();
+ }
+ /**
+ * Sets the inputGain value in decibels (dB) for the channel indicated by channelIndex.
+ * @param channelIndex index of channel of interest
+ * @param inputGain desired value in decibels (dB).
+ */
+ public void setInputGainByChannelIndex(int channelIndex, float inputGain) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setInputGain(inputGain);
+ }
+ /**
+ * Sets the inputGain value in decibels (dB) for ALL channels
+ * @param inputGain desired value in decibels (dB)
+ */
+ public void setInputGainAllChannelsTo(float inputGain) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setInputGain(inputGain);
+ }
+ }
+
+ //=== PreEQ
+ /**
+ * Gets PreEq stage from channel indicated by channelIndex
+ * @param channelIndex index of channel of interest
+ * @return PreEq stage configuration object
+ */
+ public Eq getPreEqByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPreEq();
+ }
+ /**
+ * Sets the PreEq stage configuration for the channel indicated by channelIndex. Note that
+ * new preEq stage must have the same number of bands than original preEq stage
+ * @param channelIndex index of channel to be set
+ * @param preEq desired PreEq configuration to be set
+ */
+ public void setPreEqByChannelIndex(int channelIndex, Eq preEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPreEq(preEq);
+ }
+ /**
+ * Sets the PreEq stage configuration for ALL channels. Note that new preEq stage must have
+ * the same number of bands than original preEq stages.
+ * @param preEq desired PreEq configuration to be set
+ */
+ public void setPreEqAllChannelsTo(Eq preEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPreEq(preEq);
+ }
+ }
+ public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPreEqBand(band);
+ }
+ public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPreEqBand(band, preEqBand);
+ }
+ public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPreEqBand(band, preEqBand);
+ }
+ }
+
+ //=== MBC
+ public Mbc getMbcByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getMbc();
+ }
+ public void setMbcByChannelIndex(int channelIndex, Mbc mbc) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setMbc(mbc);
+ }
+ public void setMbcAllChannelsTo(Mbc mbc) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setMbc(mbc);
+ }
+ }
+ public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getMbcBand(band);
+ }
+ public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setMbcBand(band, mbcBand);
+ }
+ public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setMbcBand(band, mbcBand);
+ }
+ }
+
+ //=== PostEQ
+ public Eq getPostEqByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPostEq();
+ }
+ public void setPostEqByChannelIndex(int channelIndex, Eq postEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPostEq(postEq);
+ }
+ public void setPostEqAllChannelsTo(Eq postEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPostEq(postEq);
+ }
+ }
+ public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getPostEqBand(band);
+ }
+ public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPostEqBand(band, postEqBand);
+ }
+ public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setPostEqBand(band, postEqBand);
+ }
+ }
+
+ //Limiter
+ public Limiter getLimiterByChannelIndex(int channelIndex) {
+ checkChannel(channelIndex);
+ return mChannel[channelIndex].getLimiter();
+ }
+ public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setLimiter(limiter);
+ }
+ public void setLimiterAllChannelsTo(Limiter limiter) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setLimiter(limiter);
+ }
+ }
+
+ public final static class Builder {
+ private int mVariant;
+ private int mChannelCount;
+ private boolean mPreEqInUse;
+ private int mPreEqBandCount;
+ private boolean mMbcInUse;
+ private int mMbcBandCount;
+ private boolean mPostEqInUse;
+ private int mPostEqBandCount;
+ private boolean mLimiterInUse;
+ private float mPreferredFrameDuration = CONFIG_PREFERRED_FRAME_DURATION_MS;
+ private Channel[] mChannel;
+
+ public Builder(int variant, int channelCount,
+ boolean preEqInUse, int preEqBandCount,
+ boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount,
+ boolean limiterInUse) {
+ mVariant = variant;
+ mChannelCount = channelCount;
+ mPreEqInUse = preEqInUse;
+ mPreEqBandCount = preEqBandCount;
+ mMbcInUse = mbcInUse;
+ mMbcBandCount = mbcBandCount;
+ mPostEqInUse = postEqInUse;
+ mPostEqBandCount = postEqBandCount;
+ mLimiterInUse = limiterInUse;
+ mChannel = new Channel[mChannelCount];
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ this.mChannel[ch] = new Channel(CHANNEL_DEFAULT_INPUT_GAIN,
+ this.mPreEqInUse, this.mPreEqBandCount,
+ this.mMbcInUse, this.mMbcBandCount,
+ this.mPostEqInUse, this.mPostEqBandCount,
+ this.mLimiterInUse);
+ }
+ }
+
+ private void checkChannel(int channelIndex) {
+ if (channelIndex < 0 || channelIndex >= mChannel.length) {
+ throw new IllegalArgumentException("ChannelIndex out of bounds");
+ }
+ }
+
+ public Builder setPreferredFrameDuration(float frameDuration) {
+ if (frameDuration < 0) {
+ throw new IllegalArgumentException("Expected positive frameDuration");
+ }
+ mPreferredFrameDuration = frameDuration;
+ return this;
+ }
+
+ public Builder setInputGainByChannelIndex(int channelIndex, float inputGain) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setInputGain(inputGain);
+ return this;
+ }
+ public Builder setInputGainAllChannelsTo(float inputGain) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ mChannel[ch].setInputGain(inputGain);
+ }
+ return this;
+ }
+
+ public Builder setChannelTo(int channelIndex, Channel channel) {
+ checkChannel(channelIndex);
+ //check all things are compatible
+ if (mMbcBandCount != channel.getMbc().getBandCount()) {
+ throw new IllegalArgumentException("MbcBandCount changed from " +
+ mMbcBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPreEqBandCount != channel.getPreEq().getBandCount()) {
+ throw new IllegalArgumentException("PreEqBandCount changed from " +
+ mPreEqBandCount + " to " + channel.getPreEq().getBandCount());
+ }
+ if (mPostEqBandCount != channel.getPostEq().getBandCount()) {
+ throw new IllegalArgumentException("PostEqBandCount changed from " +
+ mPostEqBandCount + " to " + channel.getPostEq().getBandCount());
+ }
+ mChannel[channelIndex] = new Channel(channel);
+ return this;
+ }
+ public Builder setAllChannelsTo(Channel channel) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setChannelTo(ch, channel);
+ }
+ return this;
+ }
+
+ public Builder setPreEqByChannelIndex(int channelIndex, Eq preEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPreEq(preEq);
+ return this;
+ }
+ public Builder setPreEqAllChannelsTo(Eq preEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setPreEqByChannelIndex(ch, preEq);
+ }
+ return this;
+ }
+
+ public Builder setMbcByChannelIndex(int channelIndex, Mbc mbc) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setMbc(mbc);
+ return this;
+ }
+ public Builder setMbcAllChannelsTo(Mbc mbc) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setMbcByChannelIndex(ch, mbc);
+ }
+ return this;
+ }
+
+ public Builder setPostEqByChannelIndex(int channelIndex, Eq postEq) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setPostEq(postEq);
+ return this;
+ }
+ public Builder setPostEqAllChannelsTo(Eq postEq) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setPostEqByChannelIndex(ch, postEq);
+ }
+ return this;
+ }
+
+ public Builder setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
+ checkChannel(channelIndex);
+ mChannel[channelIndex].setLimiter(limiter);
+ return this;
+ }
+ public Builder setLimiterAllChannelsTo(Limiter limiter) {
+ for (int ch = 0; ch < mChannel.length; ch++) {
+ setLimiterByChannelIndex(ch, limiter);
+ }
+ return this;
+ }
+
+ public Config build() {
+ return new Config(mVariant, mPreferredFrameDuration, mChannelCount,
+ mPreEqInUse, mPreEqBandCount,
+ mMbcInUse, mMbcBandCount,
+ mPostEqInUse, mPostEqBandCount,
+ mLimiterInUse, mChannel);
+ }
+ }
+ }
+ //=== CHANNEL
+ public Channel getChannelByChannelIndex(int channelIndex) {
+ return queryEngineByChannelIndex(channelIndex);
+ }
+
+ public void setChannelTo(int channelIndex, Channel channel) {
+ updateEngineChannelByChannelIndex(channelIndex, channel);
+ }
+
+ public void setAllChannelsTo(Channel channel) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setChannelTo(ch, channel);
+ }
+ }
+
+ //=== channel params
+ public float getInputGainByChannelIndex(int channelIndex) {
+ return getTwoFloat(PARAM_INPUT_GAIN, channelIndex);
+ }
+ public void setInputGainbyChannel(int channelIndex, float inputGain) {
+ setTwoFloat(PARAM_INPUT_GAIN, channelIndex, inputGain);
+ }
+ public void setInputGainAllChannelsTo(float inputGain) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setInputGainbyChannel(ch, inputGain);
+ }
+ }
+
+ //=== PreEQ
+ public Eq getPreEqByChannelIndex(int channelIndex) {
+ return queryEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex);
+ }
+ public void setPreEqByChannelIndex(int channelIndex, Eq preEq) {
+ updateEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex, preEq);
+ }
+ public void setPreEqAllChannelsTo(Eq preEq) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setPreEqByChannelIndex(ch, preEq);
+ }
+ }
+ public EqBand getPreEqBandByChannelIndex(int channelIndex, int band) {
+ return queryEngineEqBandByChannelIndex(PARAM_PRE_EQ_BAND, channelIndex, band);
+ }
+ public void setPreEqBandByChannelIndex(int channelIndex, int band, EqBand preEqBand) {
+ updateEngineEqBandByChannelIndex(PARAM_PRE_EQ_BAND, channelIndex, band, preEqBand);
+ }
+ public void setPreEqBandAllChannelsTo(int band, EqBand preEqBand) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setPreEqBandByChannelIndex(ch, band, preEqBand);
+ }
+ }
+
+ //=== MBC
+ public Mbc getMbcByChannelIndex(int channelIndex) {
+ return queryEngineMbcByChannelIndex(channelIndex);
+ }
+ public void setMbcByChannelIndex(int channelIndex, Mbc mbc) {
+ updateEngineMbcByChannelIndex(channelIndex, mbc);
+ }
+ public void setMbcAllChannelsTo(Mbc mbc) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setMbcByChannelIndex(ch, mbc);
+ }
+ }
+ public MbcBand getMbcBandByChannelIndex(int channelIndex, int band) {
+ return queryEngineMbcBandByChannelIndex(channelIndex, band);
+ }
+ public void setMbcBandByChannelIndex(int channelIndex, int band, MbcBand mbcBand) {
+ updateEngineMbcBandByChannelIndex(channelIndex, band, mbcBand);
+ }
+ public void setMbcBandAllChannelsTo(int band, MbcBand mbcBand) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setMbcBandByChannelIndex(ch, band, mbcBand);
+ }
+ }
+
+ //== PostEq
+ public Eq getPostEqByChannelIndex(int channelIndex) {
+ return queryEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex);
+ }
+ public void setPostEqByChannelIndex(int channelIndex, Eq postEq) {
+ updateEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex, postEq);
+ }
+ public void setPostEqAllChannelsTo(Eq postEq) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setPostEqByChannelIndex(ch, postEq);
+ }
+ }
+ public EqBand getPostEqBandByChannelIndex(int channelIndex, int band) {
+ return queryEngineEqBandByChannelIndex(PARAM_POST_EQ_BAND, channelIndex, band);
+ }
+ public void setPostEqBandByChannelIndex(int channelIndex, int band, EqBand postEqBand) {
+ updateEngineEqBandByChannelIndex(PARAM_POST_EQ_BAND, channelIndex, band, postEqBand);
+ }
+ public void setPostEqBandAllChannelsTo(int band, EqBand postEqBand) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setPostEqBandByChannelIndex(ch, band, postEqBand);
+ }
+ }
+
+ //==== Limiter
+ public Limiter getLimiterByChannelIndex(int channelIndex) {
+ return queryEngineLimiterByChannelIndex(channelIndex);
+ }
+ public void setLimiterByChannelIndex(int channelIndex, Limiter limiter) {
+ updateEngineLimiterByChannelIndex(channelIndex, limiter);
+ }
+ public void setLimiterAllChannelsTo(Limiter limiter) {
+ for (int ch = 0; ch < mChannelCount; ch++) {
+ setLimiterByChannelIndex(ch, limiter);
+ }
+ }
+
+ /**
+ * Gets the number of channels in the effect engine
+ * @return number of channels currently in use by the effect engine
+ */
+ public int getChannelCount() {
+ return getOneInt(PARAM_GET_CHANNEL_COUNT);
+ }
+
+ //=== Engine calls
+ private void setEngineArchitecture(int variant, float preferredFrameDuration,
+ boolean preEqInUse, int preEqBandCount, boolean mbcInUse, int mbcBandCount,
+ boolean postEqInUse, int postEqBandCount, boolean limiterInUse) {
+
+ Number[] params = { PARAM_ENGINE_ARCHITECTURE };
+ Number[] values = { variant /* variant */,
+ preferredFrameDuration,
+ (preEqInUse ? 1 : 0),
+ preEqBandCount,
+ (mbcInUse ? 1 : 0),
+ mbcBandCount,
+ (postEqInUse ? 1 : 0),
+ postEqBandCount,
+ (limiterInUse ? 1 : 0)};
+ setNumberArray(params, values);
+ }
+
+ private void updateEngineEqBandByChannelIndex(int param, int channelIndex, int bandIndex,
+ @NonNull EqBand eqBand) {
+ Number[] params = {param,
+ channelIndex,
+ bandIndex};
+ Number[] values = {(eqBand.isEnabled() ? 1 : 0),
+ eqBand.getCutoffFrequency(),
+ eqBand.getGain()};
+ setNumberArray(params, values);
+ }
+ private Eq queryEngineEqByChannelIndex(int param, int channelIndex) {
+
+ Number[] params = {param == PARAM_PRE_EQ ? PARAM_PRE_EQ : PARAM_POST_EQ,
+ channelIndex};
+ Number[] values = {0 /*0 in use */,
+ 0 /*1 enabled*/,
+ 0 /*2 band count */};
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
+ getParameter(paramBytes, valueBytes);
+ byteArrayToNumberArray(valueBytes, values);
+ int bandCount = values[2].intValue();
+ Eq eq = new Eq(values[0].intValue() > 0 /* in use */,
+ values[1].intValue() > 0 /* enabled */,
+ bandCount /*band count*/);
+ for (int b = 0; b < bandCount; b++) {
+ EqBand eqBand = queryEngineEqBandByChannelIndex(param == PARAM_PRE_EQ ?
+ PARAM_PRE_EQ_BAND : PARAM_POST_EQ_BAND, channelIndex, b);
+ eq.setBand(b, eqBand);
+ }
+ return eq;
+ }
+ private EqBand queryEngineEqBandByChannelIndex(int param, int channelIndex, int bandIndex) {
+ Number[] params = {param,
+ channelIndex,
+ bandIndex};
+ Number[] values = {0 /*0 enabled*/,
+ 0.0f /*1 cutoffFrequency */,
+ 0.0f /*2 gain */};
+
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
+ getParameter(paramBytes, valueBytes);
+
+ byteArrayToNumberArray(valueBytes, values);
+
+ return new EqBand(values[0].intValue() > 0 /* enabled */,
+ values[1].floatValue() /* cutoffFrequency */,
+ values[2].floatValue() /* gain*/);
+ }
+ private void updateEngineEqByChannelIndex(int param, int channelIndex, @NonNull Eq eq) {
+ int bandCount = eq.getBandCount();
+ Number[] params = {param,
+ channelIndex};
+ Number[] values = { (eq.isInUse() ? 1 : 0),
+ (eq.isEnabled() ? 1 : 0),
+ bandCount};
+ setNumberArray(params, values);
+ for (int b = 0; b < bandCount; b++) {
+ EqBand eqBand = eq.getBand(b);
+ updateEngineEqBandByChannelIndex(param == PARAM_PRE_EQ ?
+ PARAM_PRE_EQ_BAND : PARAM_POST_EQ_BAND, channelIndex, b, eqBand);
+ }
+ }
+
+ private Mbc queryEngineMbcByChannelIndex(int channelIndex) {
+ Number[] params = {PARAM_MBC,
+ channelIndex};
+ Number[] values = {0 /*0 in use */,
+ 0 /*1 enabled*/,
+ 0 /*2 band count */};
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
+ getParameter(paramBytes, valueBytes);
+ byteArrayToNumberArray(valueBytes, values);
+ int bandCount = values[2].intValue();
+ Mbc mbc = new Mbc(values[0].intValue() > 0 /* in use */,
+ values[1].intValue() > 0 /* enabled */,
+ bandCount /*band count*/);
+ for (int b = 0; b < bandCount; b++) {
+ MbcBand mbcBand = queryEngineMbcBandByChannelIndex(channelIndex, b);
+ mbc.setBand(b, mbcBand);
+ }
+ return mbc;
+ }
+ private MbcBand queryEngineMbcBandByChannelIndex(int channelIndex, int bandIndex) {
+ Number[] params = {PARAM_MBC_BAND,
+ channelIndex,
+ bandIndex};
+ Number[] values = {0 /*0 enabled */,
+ 0.0f /*1 cutoffFrequency */,
+ 0.0f /*2 AttackTime */,
+ 0.0f /*3 ReleaseTime */,
+ 0.0f /*4 Ratio */,
+ 0.0f /*5 Threshold */,
+ 0.0f /*6 KneeWidth */,
+ 0.0f /*7 NoiseGateThreshold */,
+ 0.0f /*8 ExpanderRatio */,
+ 0.0f /*9 PreGain */,
+ 0.0f /*10 PostGain*/};
+
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
+ getParameter(paramBytes, valueBytes);
+
+ byteArrayToNumberArray(valueBytes, values);
+
+ return new MbcBand(values[0].intValue() > 0 /* enabled */,
+ values[1].floatValue() /* cutoffFrequency */,
+ values[2].floatValue()/*2 AttackTime */,
+ values[3].floatValue()/*3 ReleaseTime */,
+ values[4].floatValue()/*4 Ratio */,
+ values[5].floatValue()/*5 Threshold */,
+ values[6].floatValue()/*6 KneeWidth */,
+ values[7].floatValue()/*7 NoiseGateThreshold */,
+ values[8].floatValue()/*8 ExpanderRatio */,
+ values[9].floatValue()/*9 PreGain */,
+ values[10].floatValue()/*10 PostGain*/);
+ }
+ private void updateEngineMbcBandByChannelIndex(int channelIndex, int bandIndex,
+ @NonNull MbcBand mbcBand) {
+ Number[] params = { PARAM_MBC_BAND,
+ channelIndex,
+ bandIndex};
+ Number[] values = {(mbcBand.isEnabled() ? 1 : 0),
+ mbcBand.getCutoffFrequency(),
+ mbcBand.getAttackTime(),
+ mbcBand.getReleaseTime(),
+ mbcBand.getRatio(),
+ mbcBand.getThreshold(),
+ mbcBand.getKneeWidth(),
+ mbcBand.getNoiseGateThreshold(),
+ mbcBand.getExpanderRatio(),
+ mbcBand.getPreGain(),
+ mbcBand.getPostGain()};
+ setNumberArray(params, values);
+ }
+
+ private void updateEngineMbcByChannelIndex(int channelIndex, @NonNull Mbc mbc) {
+ int bandCount = mbc.getBandCount();
+ Number[] params = { PARAM_MBC,
+ channelIndex};
+ Number[] values = {(mbc.isInUse() ? 1 : 0),
+ (mbc.isEnabled() ? 1 : 0),
+ bandCount};
+ setNumberArray(params, values);
+ for (int b = 0; b < bandCount; b++) {
+ MbcBand mbcBand = mbc.getBand(b);
+ updateEngineMbcBandByChannelIndex(channelIndex, b, mbcBand);
+ }
+ }
+
+ private void updateEngineLimiterByChannelIndex(int channelIndex, @NonNull Limiter limiter) {
+ Number[] params = { PARAM_LIMITER,
+ channelIndex};
+ Number[] values = {(limiter.isInUse() ? 1 : 0),
+ (limiter.isEnabled() ? 1 : 0),
+ limiter.getLinkGroup(),
+ limiter.getAttackTime(),
+ limiter.getReleaseTime(),
+ limiter.getRatio(),
+ limiter.getThreshold(),
+ limiter.getPostGain()};
+ setNumberArray(params, values);
+ }
+
+ private Limiter queryEngineLimiterByChannelIndex(int channelIndex) {
+ Number[] params = {PARAM_LIMITER,
+ channelIndex};
+ Number[] values = {0 /*0 in use (int)*/,
+ 0 /*1 enabled (int)*/,
+ 0 /*2 link group (int)*/,
+ 0.0f /*3 attack time (float)*/,
+ 0.0f /*4 release time (float)*/,
+ 0.0f /*5 ratio (float)*/,
+ 0.0f /*6 threshold (float)*/,
+ 0.0f /*7 post gain(float)*/};
+
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values); //just interest in the byte size.
+ getParameter(paramBytes, valueBytes);
+ byteArrayToNumberArray(valueBytes, values);
+
+ return new Limiter(values[0].intValue() > 0 /*in use*/,
+ values[1].intValue() > 0 /*enabled*/,
+ values[2].intValue() /*linkGroup*/,
+ values[3].floatValue() /*attackTime*/,
+ values[4].floatValue() /*releaseTime*/,
+ values[5].floatValue() /*ratio*/,
+ values[6].floatValue() /*threshold*/,
+ values[7].floatValue() /*postGain*/);
+ }
+
+ private Channel queryEngineByChannelIndex(int channelIndex) {
+ float inputGain = getTwoFloat(PARAM_INPUT_GAIN, channelIndex);
+ Eq preEq = queryEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex);
+ Mbc mbc = queryEngineMbcByChannelIndex(channelIndex);
+ Eq postEq = queryEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex);
+ Limiter limiter = queryEngineLimiterByChannelIndex(channelIndex);
+
+ Channel channel = new Channel(inputGain,
+ preEq.isInUse(), preEq.getBandCount(),
+ mbc.isInUse(), mbc.getBandCount(),
+ postEq.isInUse(), postEq.getBandCount(),
+ limiter.isInUse());
+ channel.setInputGain(inputGain);
+ channel.setPreEq(preEq);
+ channel.setMbc(mbc);
+ channel.setPostEq(postEq);
+ channel.setLimiter(limiter);
+ return channel;
+ }
+
+ private void updateEngineChannelByChannelIndex(int channelIndex, @NonNull Channel channel) {
+ //send things with as few calls as possible
+ setTwoFloat(PARAM_INPUT_GAIN, channelIndex, channel.getInputGain());
+ Eq preEq = channel.getPreEq();
+ updateEngineEqByChannelIndex(PARAM_PRE_EQ, channelIndex, preEq);
+ Mbc mbc = channel.getMbc();
+ updateEngineMbcByChannelIndex(channelIndex, mbc);
+ Eq postEq = channel.getPostEq();
+ updateEngineEqByChannelIndex(PARAM_POST_EQ, channelIndex, postEq);
+ Limiter limiter = channel.getLimiter();
+ updateEngineLimiterByChannelIndex(channelIndex, limiter);
+ }
+
+ //****** convenience methods:
+ //
+ private int getOneInt(int param) {
+ final int[] params = { param };
+ final int[] result = new int[1];
+
+ checkStatus(getParameter(params, result));
+ return result[0];
+ }
+
+ private void setTwoFloat(int param, int paramA, float valueSet) {
+ final int[] params = { param, paramA };
+ final byte[] value;
+
+ value = floatToByteArray(valueSet);
+ checkStatus(setParameter(params, value));
+ }
+
+ private byte[] numberArrayToByteArray(Number[] values) {
+ int expectedBytes = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] instanceof Integer) {
+ expectedBytes += Integer.BYTES;
+ } else if (values[i] instanceof Float) {
+ expectedBytes += Float.BYTES;
+ } else {
+ throw new IllegalArgumentException("unknown value type " +
+ values[i].getClass());
+ }
+ }
+ ByteBuffer converter = ByteBuffer.allocate(expectedBytes);
+ converter.order(ByteOrder.nativeOrder());
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] instanceof Integer) {
+ converter.putInt(values[i].intValue());
+ } else if (values[i] instanceof Float) {
+ converter.putFloat(values[i].floatValue());
+ }
+ }
+ return converter.array();
+ }
+
+ private void byteArrayToNumberArray(byte[] valuesIn, Number[] valuesOut) {
+ int inIndex = 0;
+ int outIndex = 0;
+ while (inIndex < valuesIn.length && outIndex < valuesOut.length) {
+ if (valuesOut[outIndex] instanceof Integer) {
+ valuesOut[outIndex++] = byteArrayToInt(valuesIn, inIndex);
+ inIndex += Integer.BYTES;
+ } else if (valuesOut[outIndex] instanceof Float) {
+ valuesOut[outIndex++] = byteArrayToFloat(valuesIn, inIndex);
+ inIndex += Float.BYTES;
+ } else {
+ throw new IllegalArgumentException("can't convert " +
+ valuesOut[outIndex].getClass());
+ }
+ }
+ if (outIndex != valuesOut.length) {
+ throw new IllegalArgumentException("only converted " + outIndex +
+ " values out of "+ valuesOut.length + " expected");
+ }
+ }
+
+ private void setNumberArray(Number[] params, Number[] values) {
+ byte[] paramBytes = numberArrayToByteArray(params);
+ byte[] valueBytes = numberArrayToByteArray(values);
+ checkStatus(setParameter(paramBytes, valueBytes));
+ }
+
+ private float getTwoFloat(int param, int paramA) {
+ final int[] params = { param, paramA };
+ final byte[] result = new byte[4];
+
+ checkStatus(getParameter(params, result));
+ return byteArrayToFloat(result);
+ }
+
+ /**
+ * @hide
+ * The OnParameterChangeListener interface defines a method called by the DynamicsProcessing
+ * when a parameter value has changed.
+ */
+ public interface OnParameterChangeListener {
+ /**
+ * Method called when a parameter value has changed. The method is called only if the
+ * parameter was changed by another application having the control of the same
+ * DynamicsProcessing engine.
+ * @param effect the DynamicsProcessing on which the interface is registered.
+ * @param param ID of the modified parameter. See {@link #PARAM_GENERIC_PARAM1} ...
+ * @param value the new parameter value.
+ */
+ void onParameterChange(DynamicsProcessing effect, int param, int value);
+ }
+
+ /**
+ * helper method to update effect architecture parameters
+ */
+ private void updateEffectArchitecture() {
+ mChannelCount = getChannelCount();
+ }
+
+ /**
+ * Listener used internally to receive unformatted parameter change events from AudioEffect
+ * super class.
+ */
+ private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
+ private BaseParameterListener() {
+
+ }
+ public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
+ // only notify when the parameter was successfully change
+ if (status != AudioEffect.SUCCESS) {
+ return;
+ }
+ OnParameterChangeListener l = null;
+ synchronized (mParamListenerLock) {
+ if (mParamListener != null) {
+ l = mParamListener;
+ }
+ }
+ if (l != null) {
+ int p = -1;
+ int v = Integer.MIN_VALUE;
+
+ if (param.length == 4) {
+ p = byteArrayToInt(param, 0);
+ }
+ if (value.length == 4) {
+ v = byteArrayToInt(value, 0);
+ }
+ if (p != -1 && v != Integer.MIN_VALUE) {
+ l.onParameterChange(DynamicsProcessing.this, p, v);
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Registers an OnParameterChangeListener interface.
+ * @param listener OnParameterChangeListener interface registered
+ */
+ public void setParameterListener(OnParameterChangeListener listener) {
+ synchronized (mParamListenerLock) {
+ if (mParamListener == null) {
+ mBaseParamListener = new BaseParameterListener();
+ super.setParameterListener(mBaseParamListener);
+ }
+ mParamListener = listener;
+ }
+ }
+
+ /**
+ * @hide
+ * The Settings class regroups the DynamicsProcessing parameters. It is used in
+ * conjunction with the getProperties() and setProperties() methods to backup and restore
+ * all parameters in a single call.
+ */
+
+ public static class Settings {
+ public int channelCount;
+ public float[] inputGain;
+
+ public Settings() {
+ }
+
+ /**
+ * Settings class constructor from a key=value; pairs formatted string. The string is
+ * typically returned by Settings.toString() method.
+ * @throws IllegalArgumentException if the string is not correctly formatted.
+ */
+ public Settings(String settings) {
+ StringTokenizer st = new StringTokenizer(settings, "=;");
+ //int tokens = st.countTokens();
+ if (st.countTokens() != 3) {
+ throw new IllegalArgumentException("settings: " + settings);
+ }
+ String key = st.nextToken();
+ if (!key.equals("DynamicsProcessing")) {
+ throw new IllegalArgumentException(
+ "invalid settings for DynamicsProcessing: " + key);
+ }
+ try {
+ key = st.nextToken();
+ if (!key.equals("channelCount")) {
+ throw new IllegalArgumentException("invalid key name: " + key);
+ }
+ channelCount = Short.parseShort(st.nextToken());
+ if (channelCount > CHANNEL_COUNT_MAX) {
+ throw new IllegalArgumentException("too many channels Settings:" + settings);
+ }
+ if (st.countTokens() != channelCount*1) { //check expected parameters.
+ throw new IllegalArgumentException("settings: " + settings);
+ }
+ //check to see it is ok the size
+ inputGain = new float[channelCount];
+ for (int ch = 0; ch < channelCount; ch++) {
+ key = st.nextToken();
+ if (!key.equals(ch +"_inputGain")) {
+ throw new IllegalArgumentException("invalid key name: " + key);
+ }
+ inputGain[ch] = Float.parseFloat(st.nextToken());
+ }
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("invalid value for key: " + key);
+ }
+ }
+
+ @Override
+ public String toString() {
+ String str = new String (
+ "DynamicsProcessing"+
+ ";channelCount="+Integer.toString(channelCount));
+ for (int ch = 0; ch < channelCount; ch++) {
+ str = str.concat(";"+ch+"_inputGain="+Float.toString(inputGain[ch]));
+ }
+ return str;
+ }
+ };
+
+
+ /**
+ * @hide
+ * Gets the DynamicsProcessing properties. This method is useful when a snapshot of current
+ * effect settings must be saved by the application.
+ * @return a DynamicsProcessing.Settings object containing all current parameters values
+ */
+ public DynamicsProcessing.Settings getProperties() {
+ Settings settings = new Settings();
+
+ //TODO: just for testing, we are calling the getters one by one, this is
+ // supposed to be done in a single (or few calls) and get all the parameters at once.
+
+ settings.channelCount = getChannelCount();
+
+ if (settings.channelCount > CHANNEL_COUNT_MAX) {
+ throw new IllegalArgumentException("too many channels Settings:" + settings);
+ }
+
+ { // get inputGainmB per channel
+ settings.inputGain = new float [settings.channelCount];
+ for (int ch = 0; ch < settings.channelCount; ch++) {
+//TODO:with config settings.inputGain[ch] = getInputGain(ch);
+ }
+ }
+ return settings;
+ }
+
+ /**
+ * @hide
+ * Sets the DynamicsProcessing properties. This method is useful when bass boost settings
+ * have to be applied from a previous backup.
+ * @param settings a DynamicsProcessing.Settings object containing the properties to apply
+ */
+ public void setProperties(DynamicsProcessing.Settings settings) {
+
+ if (settings.channelCount != settings.inputGain.length ||
+ settings.channelCount != mChannelCount) {
+ throw new IllegalArgumentException("settings invalid channel count: "
+ + settings.channelCount);
+ }
+
+ //TODO: for now calling multiple times.
+ for (int ch = 0; ch < mChannelCount; ch++) {
+//TODO: use config setInputGain(ch, settings.inputGain[ch]);
+ }
+ }
+}
diff --git a/android/media/audiofx/Visualizer.java b/android/media/audiofx/Visualizer.java
index 0fe7246e..f2b4fe09 100644
--- a/android/media/audiofx/Visualizer.java
+++ b/android/media/audiofx/Visualizer.java
@@ -546,22 +546,39 @@ public class Visualizer {
/**
* Method called when a new waveform capture is available.
* <p>Data in the waveform buffer is valid only within the scope of the callback.
- * Applications which needs access to the waveform data after returning from the callback
+ * Applications which need access to the waveform data after returning from the callback
* should make a copy of the data instead of holding a reference.
* @param visualizer Visualizer object on which the listener is registered.
* @param waveform array of bytes containing the waveform representation.
- * @param samplingRate sampling rate of the audio visualized.
+ * @param samplingRate sampling rate of the visualized audio.
*/
void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate);
/**
* Method called when a new frequency capture is available.
* <p>Data in the fft buffer is valid only within the scope of the callback.
- * Applications which needs access to the fft data after returning from the callback
+ * Applications which need access to the fft data after returning from the callback
* should make a copy of the data instead of holding a reference.
+ *
+ * <p>In order to obtain magnitude and phase values the following formulas can
+ * be used:
+ * <pre class="prettyprint">
+ * for (int i = 0; i &lt; fft.size(); i += 2) {
+ * float magnitude = (float)Math.hypot(fft[i], fft[i + 1]);
+ * float phase = (float)Math.atan2(fft[i + 1], fft[i]);
+ * }</pre>
* @param visualizer Visualizer object on which the listener is registered.
* @param fft array of bytes containing the frequency representation.
- * @param samplingRate sampling rate of the audio visualized.
+ * The fft array only contains the first half of the actual
+ * FFT spectrum (frequencies up to Nyquist frequency), exploiting
+ * the symmetry of the spectrum. For each frequencies bin <code>i</code>:
+ * <ul>
+ * <li>the element at index <code>2*i</code> in the array contains
+ * the real part of a complex number,</li>
+ * <li>the element at index <code>2*i+1</code> contains the imaginary
+ * part of the complex number.</li>
+ * </ul>
+ * @param samplingRate sampling rate of the visualized audio.
*/
void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate);
}