2023-05-21 15:13:45 -07:00
|
|
|
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");
|
|
|
|
|
2023-05-24 17:34:20 -07:00
|
|
|
// Use env vars and the existance of the above pipe to determine if we are
|
|
|
|
// running inside Replit and should use its sound API
|
2023-05-21 15:13:45 -07:00
|
|
|
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
|
2023-05-24 17:34:20 -07:00
|
|
|
// the game directly on my laptop, we have an abstract SoundBackend class
|
|
|
|
// that is extended by both a builtin javax.sound.sampled-powered backend
|
|
|
|
// and a Replit specific one
|
|
|
|
private abstract class SoundBackend {
|
|
|
|
// Plays the specific sound
|
|
|
|
abstract public void play();
|
2023-05-21 15:13:45 -07:00
|
|
|
}
|
|
|
|
|
2023-05-24 17:34:20 -07:00
|
|
|
private class NativeBackend extends SoundBackend {
|
2023-05-21 15:13:45 -07:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 17:34:20 -07:00
|
|
|
private class ReplitBackend extends SoundBackend {
|
2023-05-21 15:13:45 -07:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 17:34:20 -07:00
|
|
|
private Sound(String fileName) {
|
2023-05-21 15:13:45 -07:00
|
|
|
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
|
2023-05-24 17:34:20 -07:00
|
|
|
if (Options.hasSound())
|
2023-05-21 15:13:45 -07:00
|
|
|
soundBackend.play();
|
|
|
|
}
|
|
|
|
}
|