001: /*
002: * Copyright (c) 2000 Silvere Martin-Michiellot All Rights Reserved.
003: *
004: * Silvere Martin-Michiellot grants you ("Licensee") a non-exclusive,
005: * royalty free, license to use, modify and redistribute this
006: * software in source and binary code form,
007: * provided that i) this copyright notice and license appear on all copies of
008: * the software; and ii) Licensee does not utilize the software in a manner
009: * which is disparaging to Silvere Martin-Michiellot.
010: *
011: * This software is provided "AS IS," without a warranty of any kind. ALL
012: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
013: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
014: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. Silvere Martin-Michiellot
015: * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
016: * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
017: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
018: * Silvere Martin-Michiellot OR ITS LICENSORS BE LIABLE
019: * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
020: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
021: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
022: * OR INABILITY TO USE SOFTWARE, EVEN IF Silvere Martin-Michiellot HAS BEEN
023: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
024: *
025: * This software is not designed or intended for use in on-line control of
026: * aircraft, air traffic, aircraft navigation or aircraft communications; or in
027: * the design, construction, operation or maintenance of any nuclear
028: * facility. Licensee represents and warrants that it will not use or
029: * redistribute the Software for such purposes.
030: *
031: */
032:
033: // This code is repackaged after the code from Craig A. Lindley, from Digital Audio with Java
034: // Site ftp://ftp.prenhall.com/pub/ptr/professional_computer_science.w-022/digital_audio/
035: // Email
036: package com.db.media.audio.dsp.processors;
037:
038: public class CompExp extends AbstractAudio {
039:
040: // Set to true to output debug messages to the console.
041: private static final boolean DEBUG = false;
042:
043: // Finals for soft transitions. TRANSITIONTIME is the time (in seconds)
044: // allowed for the gain to change from the non-compression level to
045: // the compression level and vise versa. DELTA is how close the compression
046: // level must be to the ramping value to be considered equal.
047: private static final double TRANSITIONTIME = 0.1;
048: private static final double DELTA = 0.025;
049:
050: private static final double MAXTHRESHOLDDB = 0;
051: private static final double MINTHRESHOLDDB = -60;
052: public static final double THRESHOLDDEF = -16;
053:
054: private static final double MAXBTRATIO = 1.0;
055: public static final double MINBTRATIO = 25.0;
056: public static final double BTRATIODEF = 1.0;
057:
058: public static final double MAXATRATIO = +11.0;
059: public static final double MINATRATIO = -11.0;
060: public static final double ATRATIODEF = 0.0;
061:
062: private static final double MAXATTACKMS = 500;
063: private static final double MINATTACKMS = 0;
064: public static final double ATTACKMSDEF = 50;
065:
066: private static final double MAXRELEASEMS = 2000;
067: private static final double MINRELEASEMS = 0;
068: public static final double RELEASEMSDEF = 100;
069:
070: public static final double MAXGAININDB = +12.0;
071: private static final double MINGAININDB = -12.0;
072: public static final double GAINDBDEF = 0.0;
073:
074: // Private class data
075: private boolean initializationComplete;
076: private int sampleRate = 0;
077: private int channels = 1;
078: private double thresholdValue = 32767.0;
079: private double btRatio = 1.0;
080: private double atRatio = 1.0;
081: private double attackInMs = 0;
082: private double releaseInMs = 0;
083: private double attackCount = 0;
084: private double releaseCount = 0;
085: private double gain = 1.0;
086: private boolean limiting = false;
087: private boolean gating = false;
088:
089: private int calcAttackCount = 0;
090: private int calcReleaseCount = 0;
091: private int transitionCount = 0;
092: private boolean attackExpired = false;
093:
094: private double gain1 = 1.0;
095: private double gain2 = 1.0;
096: private double transitionStep = 0.001;
097:
098: // Class constructor
099: public CompExp() {
100:
101: super ("Compressor/Expander/Limiter/Noise Gate Processor",
102: AbstractAudio.PROCESSOR);
103:
104: // Initialization will take place after sample rate is known
105: initializationComplete = false;
106:
107: }
108:
109: public int getSamples(short[] buffer, int length) {
110:
111: // Get samples from previous stage
112: int len = previous.getSamples(buffer, length);
113:
114: // If bypass is enabled, short circuit processing
115: if (getByPass() || !initializationComplete)
116: return len;
117:
118: // We have samples to process
119: for (int i = 0; i < len; i++) {
120:
121: // Process gain adjustment counters every sample
122:
123: // Ramp above threshold gain
124: if (Math.abs(atRatio - gain1) > DELTA) {
125:
126: if ((atRatio > 1.0) && (gain1 < atRatio))
127: gain1 += transitionStep;
128: else if ((atRatio < 1.0) && (gain1 > atRatio))
129: gain1 -= transitionStep;
130: }
131: // Ramp unity gain value
132: if (Math.abs(gain2 - 1.0) > DELTA) {
133: if ((atRatio > 1.0) && (gain2 > 1.0))
134: gain2 -= transitionStep;
135: else
136: gain2 += transitionStep;
137: }
138:
139: // Get a sample
140: double sample = (double) buffer[i];
141:
142: if (Math.abs(sample) >= thresholdValue) {
143: // Sample value exceeds threshold
144:
145: releaseCount++;
146: releaseCount %= (calcReleaseCount + 1);
147:
148: if (attackExpired) {
149: // Attack satisfied, process sample
150: if (!limiting)
151: sample *= gain1;
152: else
153: sample = (sample < 0) ? -thresholdValue
154: : thresholdValue;
155:
156: } else {
157: // Attack count has not expired. Process sample
158: // using default gain
159: sample *= gain2;
160:
161: // Update attack counter
162: attackCount--;
163: if (attackCount <= 0) {
164: // Attack count exhausted
165: attackExpired = true;
166: releaseCount = calcReleaseCount;
167: gain1 = gain2;
168: }
169: }
170:
171: } else {
172: // Sample value did not exceed threshold
173: if (attackExpired) {
174: // Release time has not expired, so process as if the
175: // sample did exceed threshold.
176: if (!limiting)
177: sample *= gain1;
178:
179: // Update release counter
180: releaseCount--;
181: if (releaseCount <= 0) {
182: // Release count exhausted
183: attackExpired = false;
184: attackCount = calcAttackCount;
185: gain2 = gain1;
186: }
187: } else {
188: // No compression/expansion. Process sample
189: // using default gain
190: sample *= gain2;
191:
192: // Update attack count
193: attackCount++;
194: attackCount %= (calcAttackCount + 1);
195: }
196: // Now process below threshold noise gating
197: sample *= btRatio;
198: }
199: // Apply gain
200: sample *= gain;
201:
202: // Range check results
203: if (sample > 32767.0)
204: sample = 32767.0;
205: else if (sample < -32768.0)
206: sample = -32768.0;
207:
208: // Store sample back into buffer
209: buffer[i] = (short) sample;
210: }
211:
212: // Return count of sample processed
213: return len;
214:
215: }
216:
217: // These methods called when UI controls are manipulated
218: public void setThreshold(double thresholdInDB) {
219:
220: // thresholdValue is the sample value which is thresholdInDB
221: // below the maximum value of 32767.0
222: thresholdValue = Math.pow(10, thresholdInDB / 20.0) * 32767.0;
223:
224: }
225:
226: public void setBelowThresholdRatio(double ratio) {
227:
228: // Check for noise gating function
229: gating = (ratio >= this .MINBTRATIO);
230:
231: // A noise gate clamps output to zero
232: if (gating)
233: btRatio = 0.0;
234: else
235: btRatio = 1.0 / ratio;
236:
237: }
238:
239: public void setAboveThresholdRatio(double dBRatio) {
240:
241: limiting = (dBRatio <= this .MINATRATIO);
242:
243: atRatio = Math.pow(10, dBRatio / 20);
244:
245: // Calculate step size for gain ramps. That is, the rate at which
246: // the gain transitions from 1.0 (0 dB) to the expansion or
247: // compression level.
248: transitionStep = Math.abs(atRatio - 1.0) / transitionCount;
249:
250: gain2 = 1.0;
251:
252: }
253:
254: public void setAttack(double attackInMs) {
255:
256: this .attackInMs = attackInMs;
257: calcAttackCount = (int) (channels * attackInMs * sampleRate / 1000);
258: attackCount = calcAttackCount;
259:
260: }
261:
262: public void setRelease(double releaseInMs) {
263:
264: this .releaseInMs = releaseInMs;
265: calcReleaseCount = (int) (channels * releaseInMs * sampleRate / 1000);
266: releaseCount = calcReleaseCount;
267:
268: }
269:
270: public void setGain(double gainInDb) {
271:
272: this .gain = Math.pow(10, gainInDb / 20);
273:
274: }
275:
276: // Perform calculations that require a known sample rate
277: private void doInitialization() {
278:
279: calcAttackCount = (int) (channels * attackInMs * sampleRate / 1000);
280: attackCount = calcAttackCount;
281:
282: calcReleaseCount = (int) (channels * releaseInMs * sampleRate / 1000);
283: releaseCount = calcReleaseCount;
284:
285: // Calculate transition time in samples
286: transitionCount = (int) (sampleRate * TRANSITIONTIME);
287:
288: gain2 = 1.0;
289:
290: // Indicate initialization is complete
291: initializationComplete = true;
292:
293: }
294:
295: public void minMaxSamplingRate(int min, int max, int preferred) {
296:
297: super .minMaxSamplingRate(min, max, preferred);
298: sampleRate = preferred;
299: doInitialization();
300:
301: }
302:
303: // Negotiate the number of channels
304: public void minMaxChannels(int min, int max, int preferred) {
305:
306: super.minMaxChannels(min, max, preferred);
307: channels = preferred;
308:
309: }
310:
311: }
|