142 lines
3.9 KiB
Java
142 lines
3.9 KiB
Java
|
import java.io.*;
|
||
|
import javax.sound.sampled.*;
|
||
|
|
||
|
public enum Sound {
|
||
|
// The available sound filenames
|
||
|
WIN("win.wav"),
|
||
|
TICK("tick.wav"),
|
||
|
EXPLODE("explode.wav"),
|
||
|
SILENT("silent.wav");
|
||
|
|
||
|
// The directory all the audio files are in
|
||
|
public static final String AUDIO_DIR = "Audio";
|
||
|
|
||
|
// Named pipe to send audio requests to on Replit
|
||
|
public static final File REPLIT_PIPE = new File("/tmp/audio");
|
||
|
|
||
|
// Use env vars and the existance of the above pipe to determine if running inside Replit
|
||
|
public static final boolean REPLIT_API = System.getenv("REPL_ID") != null && REPLIT_PIPE.exists();
|
||
|
|
||
|
// Whether the clips have been initialized yet
|
||
|
private static boolean soundsInitialized = false;
|
||
|
|
||
|
// File containing the sound
|
||
|
private final File FILE;
|
||
|
|
||
|
// SoundBackend that will play the sound
|
||
|
private SoundBackend soundBackend;
|
||
|
|
||
|
// Because Replit does audio totally differently from desktop Java (it
|
||
|
// supposedly supports PulseAudio over VNC but I just could not get that
|
||
|
// working), and I would like to still be able to hear things when I play
|
||
|
// the game directly on my laptop, we have a SoundBackend interface that is
|
||
|
// implemented by both a builtin javax.sound.sampled-powered backend and a
|
||
|
// Replit specific one
|
||
|
private interface SoundBackend {
|
||
|
// plays the specific sound
|
||
|
public void play();
|
||
|
}
|
||
|
|
||
|
private class NativeBackend implements SoundBackend {
|
||
|
// The audio clip associated with the sound
|
||
|
private final Clip CLIP;
|
||
|
|
||
|
public NativeBackend() {
|
||
|
// Create the clip, if possible
|
||
|
Clip clip;
|
||
|
try (AudioInputStream audioIn = AudioSystem.getAudioInputStream(FILE)) {
|
||
|
clip = AudioSystem.getClip();
|
||
|
clip.open(audioIn);
|
||
|
} catch (Exception e) {
|
||
|
// If it didn't work, don't leave a broken clip behind that
|
||
|
// would try to get played
|
||
|
clip = null;
|
||
|
}
|
||
|
CLIP = clip;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void play() {
|
||
|
// Don't play if the clip couldn't be initialized
|
||
|
if (CLIP == null)
|
||
|
return;
|
||
|
|
||
|
// Rewind the clip and play it
|
||
|
CLIP.stop();
|
||
|
CLIP.setFramePosition(0);
|
||
|
CLIP.start();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class ReplitBackend implements SoundBackend {
|
||
|
// The JSON format to send requests to Replit with; the volume is 0.1
|
||
|
// because my ears got blown out the first time I exploded when it was
|
||
|
// at 100% volume (Replit is LOUD)
|
||
|
private static final String REQUEST_FORMAT = """
|
||
|
{
|
||
|
"Volume": 0.1,
|
||
|
"Type": "wav",
|
||
|
"Args": {
|
||
|
"Path": "%s"
|
||
|
}
|
||
|
}""";
|
||
|
|
||
|
// The actual request string to send
|
||
|
private final String REQUEST;
|
||
|
|
||
|
public ReplitBackend() {
|
||
|
REQUEST = String.format(REQUEST_FORMAT, FILE);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void play() {
|
||
|
// Attempt to send the request
|
||
|
try (FileWriter writer = new FileWriter(REPLIT_PIPE)) {
|
||
|
writer.write(REQUEST);
|
||
|
} catch (IOException ignore) {
|
||
|
// If it didn't work, no biggie, just don't play anything
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Sound(String fileName) {
|
||
|
FILE = new File(AUDIO_DIR, fileName);
|
||
|
}
|
||
|
|
||
|
// Initialize the backends for all sounds, if they haven't already been
|
||
|
// initialized yet
|
||
|
public static void initSounds() {
|
||
|
if (soundsInitialized)
|
||
|
return;
|
||
|
|
||
|
soundsInitialized = true;
|
||
|
|
||
|
if (REPLIT_API) {
|
||
|
for (Sound sound : values())
|
||
|
sound.soundBackend = sound.new ReplitBackend();
|
||
|
|
||
|
// HACK: Play an audible sound immediately, so the popup message
|
||
|
// happens now rather than in the middle of a game; SILENT doesn't
|
||
|
// work here, but the popup will block the first sound anyways so it
|
||
|
// doesn't matter
|
||
|
TICK.play();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (Sound sound : values())
|
||
|
sound.soundBackend = sound.new NativeBackend();
|
||
|
|
||
|
// HACK: Play a silent sound as soon as possible to eagerly load all the
|
||
|
// sound stuff now to prevent lag when the first real sound is played
|
||
|
// (yes this actually helps, I tested it)
|
||
|
SILENT.play();
|
||
|
}
|
||
|
|
||
|
// The actual method that users outside this file will call to play sound
|
||
|
public void play() {
|
||
|
// Here is the check for if the option for sound is actually enabled
|
||
|
if (Main.hasSound())
|
||
|
soundBackend.play();
|
||
|
}
|
||
|
}
|