kopia lustrzana https://github.com/jameshball/osci-render
Add comments for audio classes
rodzic
455d9c2ffd
commit
fec55fabd7
|
@ -2,6 +2,10 @@ package sh.ball.audio.effect;
|
|||
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
// The Effect interface is intended for audio effects, but can be used for any
|
||||
// effect that manipulates a Vector2 overtime. Audio effects iterate their
|
||||
// frame count, which can result in different results according to the count,
|
||||
// along with the vector can apply an audio effect.
|
||||
public interface Effect {
|
||||
Vector2 apply(int count, Vector2 vector);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import sh.ball.shapes.Vector2;
|
|||
|
||||
import static sh.ball.math.Math.round;
|
||||
|
||||
// Used for creating various audio effects
|
||||
public class EffectFactory {
|
||||
public static Effect vectorCancelling(int frequency) {
|
||||
return (count, v) -> count % frequency == 0 ? v.scale(-1) : v;
|
||||
|
|
|
@ -3,7 +3,6 @@ package sh.ball.audio.effect;
|
|||
public enum EffectType {
|
||||
VECTOR_CANCELLING,
|
||||
BIT_CRUSH,
|
||||
SCALE,
|
||||
ROTATE,
|
||||
TRANSLATE,
|
||||
VERTICAL_DISTORT,
|
||||
|
|
|
@ -2,6 +2,8 @@ package sh.ball.audio.effect;
|
|||
|
||||
public abstract class PhaseEffect implements Effect {
|
||||
|
||||
// sufficiently large so that nextTheta doesn't commonly reach it, otherwise
|
||||
// there are audio artifacts
|
||||
private static final double LARGE_VAL = 2 << 20;
|
||||
|
||||
protected final int sampleRate;
|
||||
|
|
|
@ -2,6 +2,7 @@ package sh.ball.audio.effect;
|
|||
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
// rotates the vector about (0,0)
|
||||
public class RotateEffect extends PhaseEffect {
|
||||
|
||||
public RotateEffect(int sampleRate, double speed) {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
package sh.ball.audio.effect;
|
||||
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
public class ScaleEffect implements Effect {
|
||||
|
||||
private double scale;
|
||||
|
||||
public ScaleEffect(double scale) {
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public ScaleEffect() {
|
||||
this(1);
|
||||
}
|
||||
|
||||
public void setScale(double scale) {
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector2 apply(int count, Vector2 vector) {
|
||||
return vector.scale(scale);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package sh.ball.audio.effect;
|
||||
|
||||
import sh.ball.audio.FrequencyListener;
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
// Plays a sine wave at the given frequency and volume
|
||||
public class SineEffect extends PhaseEffect {
|
||||
|
||||
private static final double DEFAULT_VOLUME = 1;
|
||||
|
|
|
@ -2,6 +2,7 @@ package sh.ball.audio.effect;
|
|||
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
// Translates the given vector in a sinusoidal fashion
|
||||
public class TranslateEffect extends PhaseEffect {
|
||||
|
||||
private Vector2 translation;
|
||||
|
|
|
@ -3,6 +3,9 @@ package sh.ball.audio.effect;
|
|||
import sh.ball.audio.FrequencyListener;
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
// Plays a sine wave at the same frequency as provided by the FrequencyListener
|
||||
// to apply an audio effect that when visualised makes the image slightly wobble
|
||||
// because it is played at a similar frequency
|
||||
public class WobbleEffect extends PhaseEffect implements FrequencyListener {
|
||||
|
||||
private static final double DEFAULT_VOLUME = 0.2;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package sh.ball.audio.engine;
|
||||
|
||||
// defines the different kinds of support audio samples for different audio
|
||||
// devices
|
||||
public enum AudioSample {
|
||||
UINT8,
|
||||
INT8,
|
||||
|
|
|
@ -7,8 +7,12 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
// combines all types of AudioEngines into a single AudioEngine to allow for
|
||||
// maximum compatibility and support for all audio drivers that have been
|
||||
// implemented
|
||||
public class ConglomerateAudioEngine implements AudioEngine {
|
||||
|
||||
// used to determine which OS we are on
|
||||
private static final String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
|
||||
private static final boolean MAC_OS = OS.contains("mac") || OS.contains("darwin");
|
||||
|
||||
|
@ -55,6 +59,7 @@ public class ConglomerateAudioEngine implements AudioEngine {
|
|||
}
|
||||
|
||||
if (xtDevices == null) {
|
||||
// XtAudio does not support MacOS so we should ignore all devices on XtAudio if on a mac
|
||||
if (MAC_OS) {
|
||||
xtDevices = new ArrayList<>();
|
||||
} else {
|
||||
|
|
|
@ -11,9 +11,11 @@ import java.util.concurrent.Callable;
|
|||
public class JavaAudioEngine implements AudioEngine {
|
||||
|
||||
private static final int DEFAULT_SAMPLE_RATE = 192000;
|
||||
// stereo audio
|
||||
private static final int NUM_CHANNELS = 2;
|
||||
private static final int LATENCY_MS = 10;
|
||||
private static final int MAX_FRAME_LATENCY = 512;
|
||||
// java sound doesn't support anything more than 16 bit :(
|
||||
private static final int BIT_DEPTH = 16;
|
||||
private static final int FRAME_SIZE = NUM_CHANNELS * BIT_DEPTH / 8;
|
||||
private static final boolean BIG_ENDIAN = false;
|
||||
|
@ -36,6 +38,7 @@ public class JavaAudioEngine implements AudioEngine {
|
|||
|
||||
AudioFormat format = new AudioFormat((float) device.sampleRate(), BIT_DEPTH, NUM_CHANNELS, SIGNED_SAMPLE, BIG_ENDIAN);
|
||||
|
||||
// connects to a device that can support the format above (i.e. default audio device)
|
||||
this.source = AudioSystem.getSourceDataLine(format);
|
||||
source.open(format);
|
||||
|
||||
|
@ -58,6 +61,8 @@ public class JavaAudioEngine implements AudioEngine {
|
|||
for (int i = 0; i < requiredSamples; i++) {
|
||||
try {
|
||||
Vector2 channels = channelGenerator.call();
|
||||
// converting doubles from Vector2 into shorts and then bytes so
|
||||
// that the byte buffer supports them
|
||||
short left = (short) (channels.getX() * Short.MAX_VALUE);
|
||||
short right = (short) (channels.getY() * Short.MAX_VALUE);
|
||||
buffer[i * 4] = (byte) left;
|
||||
|
@ -88,7 +93,7 @@ public class JavaAudioEngine implements AudioEngine {
|
|||
|
||||
@Override
|
||||
public AudioDevice getDefaultDevice() {
|
||||
return new DefaultAudioDevice("default", "default", DEFAULT_SAMPLE_RATE, AudioSample.INT16);
|
||||
return new SimpleAudioDevice("default", "default", DEFAULT_SAMPLE_RATE, AudioSample.INT16);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,14 +2,14 @@ package sh.ball.audio.engine;
|
|||
|
||||
import java.util.Objects;
|
||||
|
||||
public class DefaultAudioDevice implements AudioDevice {
|
||||
public class SimpleAudioDevice implements AudioDevice {
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final int sampleRate;
|
||||
final AudioSample sample;
|
||||
|
||||
public DefaultAudioDevice(String id, String name, int sampleRate, AudioSample sample) {
|
||||
public SimpleAudioDevice(String id, String name, int sampleRate, AudioSample sample) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.sampleRate = sampleRate;
|
||||
|
@ -47,7 +47,7 @@ public class DefaultAudioDevice implements AudioDevice {
|
|||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DefaultAudioDevice that = (DefaultAudioDevice) o;
|
||||
SimpleAudioDevice that = (SimpleAudioDevice) o;
|
||||
return sampleRate == that.sampleRate && Objects.equals(id, that.id) && Objects.equals(name, that.name) && sample == that.sample;
|
||||
}
|
||||
|
|
@ -9,8 +9,11 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
// Audio engine that connects to devices using the XtAudio library
|
||||
public class XtAudioEngine implements AudioEngine {
|
||||
|
||||
private static final int DEFAULT_SAMPLE_RATE = 192000;
|
||||
private static final Enums.XtSample DEFAULT_AUDIO_SAMPLE = Enums.XtSample.FLOAT32;
|
||||
// Stereo audio
|
||||
private static final int NUM_OUTPUTS = 2;
|
||||
|
||||
|
@ -35,6 +38,8 @@ public class XtAudioEngine implements AudioEngine {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// fully compatible function to write a Vector2 (i.e. stereo) channels to
|
||||
// any AudioSample. This ensures maximum compatibility for audio devices.
|
||||
private void writeChannels(Vector2 channels, Object output, int frame) {
|
||||
int index = frame * NUM_OUTPUTS;
|
||||
switch (device.sample()) {
|
||||
|
@ -88,6 +93,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
return playing;
|
||||
}
|
||||
|
||||
// XtAudio boilerplate for connecting to an audio device and playing audio
|
||||
@Override
|
||||
public void play(Callable<Vector2> channelGenerator, AudioDevice device) {
|
||||
this.playing = true;
|
||||
|
@ -128,6 +134,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
stopped = true;
|
||||
}
|
||||
|
||||
// XtAudio boilerplate for getting a list of connected audio devices
|
||||
@Override
|
||||
public List<AudioDevice> devices() {
|
||||
List<AudioDevice> devices = new ArrayList<>();
|
||||
|
@ -148,14 +155,14 @@ public class XtAudioEngine implements AudioEngine {
|
|||
Optional<Structs.XtMix> mix = xtDevice.getMix();
|
||||
|
||||
if (mix.isEmpty()) {
|
||||
mix = Optional.of(new Structs.XtMix(192000, Enums.XtSample.FLOAT32));
|
||||
mix = Optional.of(new Structs.XtMix(DEFAULT_SAMPLE_RATE, DEFAULT_AUDIO_SAMPLE));
|
||||
}
|
||||
|
||||
Structs.XtChannels channels = new Structs.XtChannels(0, 0, NUM_OUTPUTS, 0);
|
||||
Structs.XtFormat format = new Structs.XtFormat(mix.get(), channels);
|
||||
|
||||
if (xtDevice.supportsFormat(format)) {
|
||||
devices.add(new DefaultAudioDevice(deviceId, deviceName, mix.get().rate, XtSampleToAudioSample(mix.get().sample)));
|
||||
devices.add(new SimpleAudioDevice(deviceId, deviceName, mix.get().rate, XtSampleToAudioSample(mix.get().sample)));
|
||||
}
|
||||
} catch (XtException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -170,6 +177,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
return devices;
|
||||
}
|
||||
|
||||
// XtAudio boilerplate for getting default device
|
||||
@Override
|
||||
public AudioDevice getDefaultDevice() {
|
||||
try (XtPlatform platform = XtAudio.init(null, null)) {
|
||||
|
@ -180,7 +188,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
Optional<Structs.XtMix> mix = xtDevice.getMix();
|
||||
|
||||
if (mix.isEmpty()) {
|
||||
mix = Optional.of(new Structs.XtMix(192000, Enums.XtSample.FLOAT32));
|
||||
mix = Optional.of(new Structs.XtMix(DEFAULT_SAMPLE_RATE, DEFAULT_AUDIO_SAMPLE));
|
||||
}
|
||||
|
||||
String deviceName = service.openDeviceList(EnumSet.of(Enums.XtEnumFlags.OUTPUT)).getName(deviceId);
|
||||
|
@ -189,7 +197,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
Structs.XtFormat format = new Structs.XtFormat(mix.get(), channels);
|
||||
|
||||
if (xtDevice.supportsFormat(format)) {
|
||||
return new DefaultAudioDevice(deviceId, deviceName, mix.get().rate, XtSampleToAudioSample(mix.get().sample));
|
||||
return new SimpleAudioDevice(deviceId, deviceName, mix.get().rate, XtSampleToAudioSample(mix.get().sample));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -203,6 +211,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
}
|
||||
|
||||
|
||||
// connects to an XtAudio XtService in order of lowest latency to highest latency
|
||||
private XtService getService(XtPlatform platform) {
|
||||
XtService service = platform.getService(platform.setupToSystem(Enums.XtSetup.SYSTEM_AUDIO));
|
||||
if (service == null) {
|
||||
|
@ -222,6 +231,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
return service;
|
||||
}
|
||||
|
||||
// helper for converting XtSamples into AudioSamples
|
||||
private AudioSample XtSampleToAudioSample(Enums.XtSample sample) {
|
||||
return switch (sample) {
|
||||
case UINT8 -> AudioSample.UINT8;
|
||||
|
@ -232,6 +242,7 @@ public class XtAudioEngine implements AudioEngine {
|
|||
};
|
||||
}
|
||||
|
||||
// helper for converting AudioSamples into XtSamples
|
||||
private Enums.XtSample AudioSampleToXtSample(AudioSample sample) {
|
||||
return switch (sample) {
|
||||
case UINT8 -> Enums.XtSample.UINT8;
|
||||
|
|
Ładowanie…
Reference in New Issue