From fec55fabd7422bc736c08f6a26b68960d68b7ad5 Mon Sep 17 00:00:00 2001 From: James Ball Date: Wed, 1 Sep 2021 22:06:02 +0100 Subject: [PATCH] Add comments for audio classes --- .../java/sh/ball/audio/effect/Effect.java | 4 +++ .../sh/ball/audio/effect/EffectFactory.java | 1 + .../java/sh/ball/audio/effect/EffectType.java | 1 - .../sh/ball/audio/effect/PhaseEffect.java | 2 ++ .../sh/ball/audio/effect/RotateEffect.java | 1 + .../sh/ball/audio/effect/ScaleEffect.java | 25 ------------------- .../java/sh/ball/audio/effect/SineEffect.java | 2 +- .../sh/ball/audio/effect/TranslateEffect.java | 1 + .../sh/ball/audio/effect/WobbleEffect.java | 3 +++ .../sh/ball/audio/engine/AudioSample.java | 2 ++ .../audio/engine/ConglomerateAudioEngine.java | 5 ++++ .../sh/ball/audio/engine/JavaAudioEngine.java | 7 +++++- ...udioDevice.java => SimpleAudioDevice.java} | 6 ++--- .../sh/ball/audio/engine/XtAudioEngine.java | 19 +++++++++++--- 14 files changed, 44 insertions(+), 35 deletions(-) delete mode 100644 src/main/java/sh/ball/audio/effect/ScaleEffect.java rename src/main/java/sh/ball/audio/engine/{DefaultAudioDevice.java => SimpleAudioDevice.java} (85%) diff --git a/src/main/java/sh/ball/audio/effect/Effect.java b/src/main/java/sh/ball/audio/effect/Effect.java index b9b5df1..e661680 100644 --- a/src/main/java/sh/ball/audio/effect/Effect.java +++ b/src/main/java/sh/ball/audio/effect/Effect.java @@ -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); } diff --git a/src/main/java/sh/ball/audio/effect/EffectFactory.java b/src/main/java/sh/ball/audio/effect/EffectFactory.java index 6b15621..b170555 100644 --- a/src/main/java/sh/ball/audio/effect/EffectFactory.java +++ b/src/main/java/sh/ball/audio/effect/EffectFactory.java @@ -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; diff --git a/src/main/java/sh/ball/audio/effect/EffectType.java b/src/main/java/sh/ball/audio/effect/EffectType.java index 843541d..f86ffbd 100644 --- a/src/main/java/sh/ball/audio/effect/EffectType.java +++ b/src/main/java/sh/ball/audio/effect/EffectType.java @@ -3,7 +3,6 @@ package sh.ball.audio.effect; public enum EffectType { VECTOR_CANCELLING, BIT_CRUSH, - SCALE, ROTATE, TRANSLATE, VERTICAL_DISTORT, diff --git a/src/main/java/sh/ball/audio/effect/PhaseEffect.java b/src/main/java/sh/ball/audio/effect/PhaseEffect.java index 2775fdc..a605eb7 100644 --- a/src/main/java/sh/ball/audio/effect/PhaseEffect.java +++ b/src/main/java/sh/ball/audio/effect/PhaseEffect.java @@ -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; diff --git a/src/main/java/sh/ball/audio/effect/RotateEffect.java b/src/main/java/sh/ball/audio/effect/RotateEffect.java index 30ed9a1..178c8fe 100644 --- a/src/main/java/sh/ball/audio/effect/RotateEffect.java +++ b/src/main/java/sh/ball/audio/effect/RotateEffect.java @@ -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) { diff --git a/src/main/java/sh/ball/audio/effect/ScaleEffect.java b/src/main/java/sh/ball/audio/effect/ScaleEffect.java deleted file mode 100644 index 94f50fd..0000000 --- a/src/main/java/sh/ball/audio/effect/ScaleEffect.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/sh/ball/audio/effect/SineEffect.java b/src/main/java/sh/ball/audio/effect/SineEffect.java index 08d8736..647ed26 100644 --- a/src/main/java/sh/ball/audio/effect/SineEffect.java +++ b/src/main/java/sh/ball/audio/effect/SineEffect.java @@ -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; diff --git a/src/main/java/sh/ball/audio/effect/TranslateEffect.java b/src/main/java/sh/ball/audio/effect/TranslateEffect.java index d7c4ec2..0ec3990 100644 --- a/src/main/java/sh/ball/audio/effect/TranslateEffect.java +++ b/src/main/java/sh/ball/audio/effect/TranslateEffect.java @@ -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; diff --git a/src/main/java/sh/ball/audio/effect/WobbleEffect.java b/src/main/java/sh/ball/audio/effect/WobbleEffect.java index acff3d5..8a469f0 100644 --- a/src/main/java/sh/ball/audio/effect/WobbleEffect.java +++ b/src/main/java/sh/ball/audio/effect/WobbleEffect.java @@ -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; diff --git a/src/main/java/sh/ball/audio/engine/AudioSample.java b/src/main/java/sh/ball/audio/engine/AudioSample.java index cab5c3c..d407806 100644 --- a/src/main/java/sh/ball/audio/engine/AudioSample.java +++ b/src/main/java/sh/ball/audio/engine/AudioSample.java @@ -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, diff --git a/src/main/java/sh/ball/audio/engine/ConglomerateAudioEngine.java b/src/main/java/sh/ball/audio/engine/ConglomerateAudioEngine.java index c2376ee..70a2673 100644 --- a/src/main/java/sh/ball/audio/engine/ConglomerateAudioEngine.java +++ b/src/main/java/sh/ball/audio/engine/ConglomerateAudioEngine.java @@ -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 { diff --git a/src/main/java/sh/ball/audio/engine/JavaAudioEngine.java b/src/main/java/sh/ball/audio/engine/JavaAudioEngine.java index 78b45f4..81ff493 100644 --- a/src/main/java/sh/ball/audio/engine/JavaAudioEngine.java +++ b/src/main/java/sh/ball/audio/engine/JavaAudioEngine.java @@ -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 diff --git a/src/main/java/sh/ball/audio/engine/DefaultAudioDevice.java b/src/main/java/sh/ball/audio/engine/SimpleAudioDevice.java similarity index 85% rename from src/main/java/sh/ball/audio/engine/DefaultAudioDevice.java rename to src/main/java/sh/ball/audio/engine/SimpleAudioDevice.java index 9f2dbf2..0717eb6 100644 --- a/src/main/java/sh/ball/audio/engine/DefaultAudioDevice.java +++ b/src/main/java/sh/ball/audio/engine/SimpleAudioDevice.java @@ -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; } diff --git a/src/main/java/sh/ball/audio/engine/XtAudioEngine.java b/src/main/java/sh/ball/audio/engine/XtAudioEngine.java index ea5565a..21e72b2 100644 --- a/src/main/java/sh/ball/audio/engine/XtAudioEngine.java +++ b/src/main/java/sh/ball/audio/engine/XtAudioEngine.java @@ -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 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 devices() { List devices = new ArrayList<>(); @@ -148,14 +155,14 @@ public class XtAudioEngine implements AudioEngine { Optional 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 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;