Skip to content

Commit 6df6d66

Browse files
Update EMAFilter.java
1 parent e6cb96f commit 6df6d66

File tree

1 file changed

+97
-27
lines changed

1 file changed

+97
-27
lines changed
Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,118 @@
11
package com.thealgorithms.audiofilters;
22

3+
import java.util.Arrays;
4+
import java.util.Objects;
5+
36
/**
4-
* Exponential Moving Average (EMA) Filter for smoothing audio signals.
7+
* EMAFilter - Exponential Moving Average filter for smoothing audio signals.
8+
*
9+
* Think of this as a "smart smoothing tool" for audio: it looks at each new sample
10+
* and gently blends it with the previous smoothed value to reduce sudden spikes or noise.
511
*
6-
* <p>This filter applies an exponential moving average to a sequence of audio
7-
* signal values, making it useful for smoothing out rapid fluctuations.
8-
* The smoothing factor (alpha) controls the degree of smoothing.
12+
* The smoothing factor (alpha) determines how responsive the filter is:
13+
* - High alpha (close to 1) → reacts quickly to new samples (less smoothing)
14+
* - Low alpha (close to 0) → reacts slowly (more smoothing)
915
*
10-
* <p>Based on the definition from
11-
* <a href="https://en.wikipedia.org/wiki/Moving_average">Wikipedia link</a>.
16+
* This class supports both:
17+
* 1. Batch processing (arrays of samples)
18+
* 2. Streaming / real-time processing (sample by sample)
1219
*/
13-
public class EMAFilter {
20+
public final class EMAFilter {
21+
22+
/** How "responsive" the filter is to new data */
1423
private final double alpha;
15-
private double emaValue;
24+
25+
/** Stores the last EMA value for continuous processing */
26+
private double lastEma;
27+
1628
/**
17-
* Constructs an EMA filter with a given smoothing factor.
29+
* Constructor: sets the smoothing factor (alpha) for the filter.
1830
*
19-
* @param alpha Smoothing factor (0 < alpha <= 1)
20-
* @throws IllegalArgumentException if alpha is not in (0, 1]
31+
* @param alpha smoothing factor between 0 (exclusive) and 1 (inclusive)
32+
* @throws IllegalArgumentException if alpha is invalid
2133
*/
2234
public EMAFilter(double alpha) {
23-
if (alpha <= 0 || alpha > 1) {
24-
throw new IllegalArgumentException("Alpha must be between 0 and 1.");
35+
if (alpha <= 0.0 || alpha > 1.0) {
36+
throw new IllegalArgumentException("Alpha must be between 0 (exclusive) and 1 (inclusive). Got: " + alpha);
2537
}
2638
this.alpha = alpha;
27-
this.emaValue = 0.0;
39+
this.lastEma = Double.NaN; // Indicates that no sample has been processed yet
40+
}
41+
42+
/**
43+
* Smooths a whole array of audio samples and returns a new array.
44+
*
45+
* Original samples remain unchanged.
46+
*
47+
* @param samples input audio samples
48+
* @return new array with smoothed samples
49+
*/
50+
public double[] apply(double[] samples) {
51+
Objects.requireNonNull(samples, "Input samples cannot be null.");
52+
if (samples.length == 0) return new double[0];
53+
54+
double[] smoothed = new double[samples.length];
55+
double ema = samples[0]; // Start with the first sample
56+
smoothed[0] = ema;
57+
58+
for (int i = 1; i < samples.length; i++) {
59+
// EMA formula: current = alpha * newSample + (1 - alpha) * previousEMA
60+
ema = alpha * samples[i] + (1 - alpha) * ema;
61+
smoothed[i] = ema;
62+
}
63+
64+
lastEma = ema; // Save last EMA for streaming
65+
return smoothed;
2866
}
67+
2968
/**
30-
* Applies the EMA filter to an audio signal array.
69+
* Smooths the array **in-place** to save memory.
3170
*
32-
* @param audioSignal Array of audio samples to process
33-
* @return Array of processed (smoothed) samples
71+
* Useful for large audio arrays or memory-sensitive applications.
72+
*
73+
* @param samples array to be smoothed (will be overwritten)
3474
*/
35-
public double[] apply(double[] audioSignal) {
36-
if (audioSignal.length == 0) {
37-
return new double[0];
75+
public void applyInPlace(double[] samples) {
76+
Objects.requireNonNull(samples, "Input samples cannot be null.");
77+
if (samples.length == 0) return;
78+
79+
double ema = samples[0];
80+
for (int i = 1; i < samples.length; i++) {
81+
ema = alpha * samples[i] + (1 - alpha) * ema;
82+
samples[i] = ema; // overwrite original array
3883
}
39-
double[] emaSignal = new double[audioSignal.length];
40-
emaValue = audioSignal[0];
41-
emaSignal[0] = emaValue;
42-
for (int i = 1; i < audioSignal.length; i++) {
43-
emaValue = alpha * audioSignal[i] + (1 - alpha) * emaValue;
44-
emaSignal[i] = emaValue;
84+
85+
lastEma = ema;
86+
}
87+
88+
/**
89+
* Returns the last EMA value computed.
90+
*
91+
* Useful for streaming or continuous processing.
92+
*
93+
* @return last EMA value, or NaN if filter hasn't processed any data yet
94+
*/
95+
public double getLastEma() {
96+
return lastEma;
97+
}
98+
99+
/**
100+
* Updates the EMA with a single new sample (streaming / real-time processing).
101+
*
102+
* @param sample next input value
103+
* @return updated EMA value
104+
*/
105+
public double next(double sample) {
106+
if (Double.isNaN(lastEma)) {
107+
lastEma = sample; // Initialize with first sample
108+
} else {
109+
lastEma = alpha * sample + (1 - alpha) * lastEma;
45110
}
46-
return emaSignal;
111+
return lastEma;
112+
}
113+
114+
@Override
115+
public String toString() {
116+
return "EMAFilter{alpha=" + alpha + ", lastEma=" + lastEma + "}";
47117
}
48118
}

0 commit comments

Comments
 (0)