DemoPresetGenerator.java
package org.hammer.audio.signal;
import org.hammer.audio.DemoSignalType;
import org.hammer.audio.core.AudioBlock;
import org.hammer.audio.core.AudioFormatDescriptor;
/** Synthetic stereo scenarios for reproducible measurement demos without audio hardware. */
public final class DemoPresetGenerator implements SignalGenerator {
private static final double TWO_PI = 2.0 * Math.PI;
private static final double DEFAULT_AMPLITUDE = 0.75;
private static final int STEREO_DELAY_SAMPLES = 6;
private final AudioFormatDescriptor format;
private final DemoSignalType signalType;
private long frameIndex;
public DemoPresetGenerator(AudioFormatDescriptor format, DemoSignalType signalType) {
this.format = format;
this.signalType = signalType;
}
@Override
public AudioFormatDescriptor format() {
return format;
}
@Override
public AudioBlock nextBlock(int frames) {
if (frames < 1) {
throw new IllegalArgumentException("frames must be >= 1");
}
float[][] samples = new float[format.channels()][frames];
for (int frame = 0; frame < frames; frame++) {
long absoluteFrame = frameIndex + frame;
int delay = delaySamples(absoluteFrame);
for (int channel = 0; channel < format.channels(); channel++) {
long sourceFrame = channel == 1 ? absoluteFrame - delay : absoluteFrame;
samples[channel][frame] = (float) sampleAt(sourceFrame, channel);
}
}
AudioBlock block = AudioBlock.wrap(format, samples, frameIndex, System.nanoTime());
frameIndex += frames;
return block;
}
@Override
public void reset() {
frameIndex = 0L;
}
private int delaySamples(long absoluteFrame) {
return switch (signalType) {
case MOVING_CHIRP -> (int) Math.round(6.0 * Math.sin(TWO_PI * seconds(absoluteFrame) / 3.0));
case MOSQUITO_BURST -> 4;
case STEREO_DELAY_TEST -> STEREO_DELAY_SAMPLES;
default -> 0;
};
}
private double sampleAt(long absoluteFrame, int channel) {
return switch (signalType) {
case MOSQUITO_BURST -> mosquitoLikeBurst(absoluteFrame, channel);
case MOVING_CHIRP -> movingChirp(absoluteFrame);
case HUM_HARMONICS -> humWithHarmonics(absoluteFrame, channel);
case CLIPPING_TEST -> clippingTest(absoluteFrame);
case STEREO_DELAY_TEST -> stereoDelayProbe(absoluteFrame);
case SINE -> DEFAULT_AMPLITUDE * Math.sin(TWO_PI * 440.0 * seconds(absoluteFrame));
case SQUARE ->
DEFAULT_AMPLITUDE * Math.signum(Math.sin(TWO_PI * 440.0 * seconds(absoluteFrame)));
case CHIRP -> chirp(absoluteFrame);
};
}
private double mosquitoLikeBurst(long absoluteFrame, int channel) {
double sampleRate = format.sampleRate();
double burstPeriod = 0.42;
double burstPosition = seconds(absoluteFrame) % burstPeriod;
double burstLength = 0.055;
double envelope =
burstPosition < burstLength ? Math.sin(Math.PI * burstPosition / burstLength) : 0.0;
double carrier = Math.sin(TWO_PI * 5_200.0 * absoluteFrame / sampleRate);
double flutter = 0.65 + 0.35 * Math.sin(TWO_PI * 95.0 * absoluteFrame / sampleRate);
double echo = 0.18 * envelope * Math.sin(TWO_PI * 5_200.0 * (absoluteFrame - 18) / sampleRate);
double noise = 0.015 * deterministicNoise(absoluteFrame + channel * 17L);
return 0.52 * envelope * flutter * carrier + echo + noise;
}
private double movingChirp(long absoluteFrame) {
double cycle = 3.0;
double position = (seconds(absoluteFrame) % cycle) / cycle;
double frequency = 350.0 + position * 4_500.0;
return 0.58 * Math.sin(TWO_PI * frequency * absoluteFrame / format.sampleRate());
}
private double chirp(long absoluteFrame) {
double cycle = 2.5;
double position = (seconds(absoluteFrame) % cycle) / cycle;
double frequency = 120.0 + position * 2_680.0;
return DEFAULT_AMPLITUDE * Math.sin(TWO_PI * frequency * absoluteFrame / format.sampleRate());
}
private double humWithHarmonics(long absoluteFrame, int channel) {
double t = seconds(absoluteFrame);
double noise = 0.012 * deterministicNoise(absoluteFrame + channel * 31L);
return 0.45 * Math.sin(TWO_PI * 50.0 * t)
+ 0.18 * Math.sin(TWO_PI * 100.0 * t)
+ 0.09 * Math.sin(TWO_PI * 150.0 * t)
+ noise;
}
private double clippingTest(long absoluteFrame) {
double value = 1.6 * Math.sin(TWO_PI * 440.0 * seconds(absoluteFrame));
return Math.max(-1.0, Math.min(1.0, value));
}
private double stereoDelayProbe(long absoluteFrame) {
double t = seconds(absoluteFrame);
double burst = Math.sin(TWO_PI * 880.0 * t) + 0.45 * Math.sin(TWO_PI * 2_700.0 * t);
double gate = (absoluteFrame / 1_200) % 2 == 0 ? 1.0 : 0.25;
return DEFAULT_AMPLITUDE * gate * burst / 1.45;
}
private double seconds(long absoluteFrame) {
return absoluteFrame / (double) format.sampleRate();
}
private static double deterministicNoise(long value) {
long mixed = value * 6_364_136_223_846_793_005L + 1_442_695_040_888_963_407L;
mixed ^= mixed >>> 33;
return ((mixed & 0xffffL) / 32767.5) - 1.0;
}
}