All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.media.audiofx.DynamicsProcessing Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.
 * 

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. *

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. *

The basic channel configuration is: *

 *
 *    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
 * 
* *

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. * *

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. *

See each stage documentation for further details. *

If no Config is specified during creation, a default configuration is chosen. *

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()}). * *

To attach the DynamicsProcessing to a particular AudioTrack or MediaPlayer, specify the audio * session ID of this AudioTrack or MediaPlayer when constructing the DynamicsProcessing. *

See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions. *

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 = 3; // ms private static final float MBC_DEFAULT_RELEASE_TIME = 80; // ms private static final float MBC_DEFAULT_RATIO = 1; // N:1 private static final float MBC_DEFAULT_THRESHOLD = -45; // dB private static final float MBC_DEFAULT_KNEE_WIDTH = 0; // dB private static final float MBC_DEFAULT_NOISE_GATE_THRESHOLD = -90; // dB private static final float MBC_DEFAULT_EXPANDER_RATIO = 1; // 1:N private static final float MBC_DEFAULT_PRE_GAIN = 0; // dB private static final float MBC_DEFAULT_POST_GAIN = 0; // 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 = 1; // ms private static final float LIMITER_DEFAULT_RELEASE_TIME = 60; // ms private static final float LIMITER_DEFAULT_RATIO = 10; // N:1 private static final float LIMITER_DEFAULT_THRESHOLD = -2; // dB private static final float LIMITER_DEFAULT_POST_GAIN = 0; // 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 (N:1) (input:output) * @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 (1:N) (input:output) 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 (N:1) * @return compressor ratio (N:1) */ public float getRatio() { return mRatio; } /** * sets compressor ratio (N:1) * @param ratio desired for the compressor (N:1) */ 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 (1:N) for signals below the Noise Gate Threshold. * @return Expander ratio (1:N) */ public float getExpanderRatio() { return mExpanderRatio; } /** * sets Expander ratio (1:N) for signals below the Noise Gate Threshold. * @param expanderRatio desired expander ratio (1:N) */ 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. *

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 (N:1) (input:output) * @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 (N:1) * @return limiter compressor ratio (N:1) */ public float getRatio() { return mRatio; } /** * sets limiter compressor ratio (N:1) * @param ratio desired for the limiter compressor (N:1) */ 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]); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy