SpectrumSnapshot.java
package org.hammer.audio.analysis;
/**
* Immutable spectrum-analysis snapshot produced by {@link SpectrumAnalyzer}.
*
* <p>Contains the one-sided magnitude spectrum (DC ... Nyquist) computed from one channel of an
* audio block, the FFT size that produced it, and the sample rate (for bin → frequency conversion).
*
* @author refactoring
*/
public final class SpectrumSnapshot implements AnalysisSnapshot {
private final long sourceFrameIndex;
private final long sourceTimestampNanos;
private final int fftSize;
private final float sampleRate;
private final int channel;
private final float[] magnitudes;
/**
* Create a new spectrum snapshot. The magnitudes array is defensively copied.
*
* @param sourceFrameIndex frame index from the analyzed block
* @param sourceTimestampNanos timestamp from the analyzed block
* @param channel channel index that was analyzed
* @param sampleRate sample rate of the source audio
* @param fftSize FFT size that produced the magnitudes
* @param magnitudes one-sided magnitude spectrum of length {@code fftSize/2 + 1}
* @throws IllegalArgumentException if {@code magnitudes.length != fftSize/2 + 1}
*/
public SpectrumSnapshot(
long sourceFrameIndex,
long sourceTimestampNanos,
int channel,
float sampleRate,
int fftSize,
float[] magnitudes) {
if (magnitudes.length != fftSize / 2 + 1) {
throw new IllegalArgumentException(
"magnitudes.length must be fftSize/2+1, was " + magnitudes.length);
}
this.sourceFrameIndex = sourceFrameIndex;
this.sourceTimestampNanos = sourceTimestampNanos;
this.channel = channel;
this.sampleRate = sampleRate;
this.fftSize = fftSize;
this.magnitudes = magnitudes.clone();
}
@Override
public long sourceFrameIndex() {
return sourceFrameIndex;
}
@Override
public long sourceTimestampNanos() {
return sourceTimestampNanos;
}
/**
* @return analyzed channel index
*/
public int channel() {
return channel;
}
/**
* @return sample rate of the source audio (Hz)
*/
public float sampleRate() {
return sampleRate;
}
/**
* @return the FFT size that produced the magnitudes
*/
public int fftSize() {
return fftSize;
}
/**
* @return frequency resolution per bin in Hz, i.e. {@code sampleRate / fftSize}
*/
public float binWidthHz() {
return sampleRate / fftSize;
}
/**
* @param bin bin index in {@code [0, magnitudes().length)}
* @return centre frequency of the given bin in Hz
*/
public float frequencyOfBin(int bin) {
return bin * binWidthHz();
}
/**
* @return defensive copy of the one-sided magnitude spectrum
*/
public float[] magnitudes() {
return magnitudes.clone();
}
/**
* Read-only access to the internal magnitudes array. Callers must not mutate the returned array.
* Intended for hot rendering paths and downstream analyzers that need to avoid per-frame
* allocations.
*
* @return the internal magnitudes array (do not mutate)
*/
public float[] magnitudesView() {
return magnitudes;
}
/**
* @return number of frequency bins in the one-sided spectrum
*/
public int binCount() {
return magnitudes.length;
}
/**
* @param bin bin index
* @return magnitude at that bin
*/
public float magnitude(int bin) {
return magnitudes[bin];
}
}