mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Merge branch 'overlap_same_clip' into 'master'
Support overlapped reproducing of same AudioClip See merge request core-developers/forge!2081
This commit is contained in:
@@ -24,6 +24,8 @@ import forge.properties.ForgeConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -35,9 +37,10 @@ import java.util.function.Supplier;
|
||||
* @author Agetian
|
||||
*/
|
||||
public class AudioClip implements IAudioClip {
|
||||
private Clip clip;
|
||||
private boolean started;
|
||||
private boolean looping;
|
||||
private final int maxSize = 16;
|
||||
private String filename;
|
||||
private List<ClipWrapper> clips;
|
||||
private boolean failed;
|
||||
|
||||
public static boolean fileExists(String fileName) {
|
||||
File fSound = new File(ForgeConstants.SOUND_DIR, fileName);
|
||||
@@ -45,100 +48,62 @@ public class AudioClip implements IAudioClip {
|
||||
}
|
||||
|
||||
public AudioClip(final String filename) {
|
||||
File fSound = new File(ForgeConstants.SOUND_DIR, filename);
|
||||
if (!fSound.exists()) {
|
||||
throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it");
|
||||
}
|
||||
|
||||
try {
|
||||
AudioInputStream stream = AudioSystem.getAudioInputStream(fSound);
|
||||
AudioFormat format = stream.getFormat();
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat(), ((int) stream.getFrameLength() * format.getFrameSize()));
|
||||
clip = (Clip) AudioSystem.getLine(info);
|
||||
clip.addLineListener(this::lineStatusChanged);
|
||||
clip.open(stream);
|
||||
return;
|
||||
|
||||
} catch (IOException ex) {
|
||||
System.err.println("Unable to load sound file: " + filename);
|
||||
} catch (LineUnavailableException ex) {
|
||||
System.err.println("Error initializing sound system: " + ex);
|
||||
} catch (UnsupportedAudioFileException ex) {
|
||||
System.err.println("Unsupported file type of the sound file: " + fSound.toString() + " - " + ex.getMessage());
|
||||
clip = null;
|
||||
return;
|
||||
}
|
||||
throw new MissingResourceException("Sound clip failed to load", this.getClass().getName(), filename);
|
||||
this.filename = filename;
|
||||
clips = new ArrayList<>(maxSize);
|
||||
addClip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void play() {
|
||||
if (null == clip) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (clip.isRunning()) {
|
||||
// introduce small delay to make a batch sounds more granular,
|
||||
// e.g. when you auto-tap 4 lands the 4 tap sounds should
|
||||
// not become completely merged
|
||||
waitSoundSystemDelay();
|
||||
}
|
||||
clip.setMicrosecondPosition(0);
|
||||
if (!this.looping && clip.isRunning()) {
|
||||
return;
|
||||
}
|
||||
this.started = false;
|
||||
clip.start();
|
||||
wait(() -> this.started);
|
||||
if (clips.stream().anyMatch(ClipWrapper::isRunning)) {
|
||||
// introduce small delay to make a batch sounds more granular,
|
||||
// e.g. when you auto-tap 4 lands the 4 tap sounds should
|
||||
// not become completely merged
|
||||
waitSoundSystemDelay();
|
||||
}
|
||||
getIdleClip().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void loop() {
|
||||
if (null == clip) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
clip.setMicrosecondPosition(0);
|
||||
if (this.looping && clip.isRunning()) {
|
||||
return;
|
||||
}
|
||||
this.started = false;
|
||||
clip.loop(Clip.LOOP_CONTINUOUSLY);
|
||||
wait(() -> this.started);
|
||||
this.looping = true;
|
||||
}
|
||||
getIdleClip().loop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void stop() {
|
||||
if (null == clip) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
for (ClipWrapper clip: clips) {
|
||||
clip.stop();
|
||||
this.looping = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isDone() {
|
||||
if (null == clip) {
|
||||
return false;
|
||||
}
|
||||
return !clip.isRunning();
|
||||
return clips.stream().noneMatch(ClipWrapper::isRunning);
|
||||
}
|
||||
|
||||
private void wait(Supplier<Boolean> completed) {
|
||||
final int attempts = 5;
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
if (completed.get() || !waitSoundSystemDelay()) {
|
||||
break;
|
||||
private ClipWrapper getIdleClip() {
|
||||
return clips.stream()
|
||||
.filter(clip -> !clip.isRunning())
|
||||
.findFirst()
|
||||
.orElseGet(this::addClip);
|
||||
}
|
||||
|
||||
private ClipWrapper addClip() {
|
||||
if (clips.size() < maxSize && !failed) {
|
||||
ClipWrapper clip = new ClipWrapper(filename);
|
||||
if (clip.isFailed()) {
|
||||
failed = true;
|
||||
} else {
|
||||
clips.add(clip);
|
||||
}
|
||||
return clip;
|
||||
}
|
||||
return ClipWrapper.Dummy;
|
||||
}
|
||||
|
||||
private boolean waitSoundSystemDelay() {
|
||||
|
||||
|
||||
private static boolean waitSoundSystemDelay() {
|
||||
try {
|
||||
Thread.sleep(SoundSystem.DELAY);
|
||||
return true;
|
||||
@@ -148,8 +113,101 @@ public class AudioClip implements IAudioClip {
|
||||
}
|
||||
}
|
||||
|
||||
private void lineStatusChanged(LineEvent line) {
|
||||
LineEvent.Type status = line.getType();
|
||||
this.started |= status == LineEvent.Type.START;
|
||||
static class ClipWrapper {
|
||||
private final Clip clip;
|
||||
private boolean started;
|
||||
static final ClipWrapper Dummy = new ClipWrapper();
|
||||
|
||||
private ClipWrapper() {
|
||||
clip = null;
|
||||
}
|
||||
|
||||
ClipWrapper(String filename) {
|
||||
clip = createClip(filename);
|
||||
if (clip != null) {
|
||||
clip.addLineListener(this::clipStateChanged);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isFailed() {
|
||||
return null == clip;
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (null == clip) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
clip.setMicrosecondPosition(0);
|
||||
this.started = false;
|
||||
clip.start();
|
||||
// with JRE 1.8.0_211 if another thread called clip.setMicrosecondPosition
|
||||
// just now, it would deadlock. To prevent this we synchronize this method
|
||||
// and wait
|
||||
wait(() -> this.started);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (null == clip) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
clip.setMicrosecondPosition(0);
|
||||
this.started = false;
|
||||
clip.loop(Clip.LOOP_CONTINUOUSLY);
|
||||
wait(() -> this.started);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (null == clip) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
clip.stop();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isRunning() {
|
||||
return clip != null && (clip.isRunning() || clip.isActive());
|
||||
}
|
||||
|
||||
private Clip createClip(String filename) {
|
||||
File fSound = new File(ForgeConstants.SOUND_DIR, filename);
|
||||
if (!fSound.exists()) {
|
||||
throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it");
|
||||
}
|
||||
|
||||
try {
|
||||
AudioInputStream stream = AudioSystem.getAudioInputStream(fSound);
|
||||
AudioFormat format = stream.getFormat();
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat(), ((int) stream.getFrameLength() * format.getFrameSize()));
|
||||
Clip clip = (Clip) AudioSystem.getLine(info);
|
||||
clip.open(stream);
|
||||
return clip;
|
||||
} catch (IOException ex) {
|
||||
System.err.println("Unable to load sound file: " + filename);
|
||||
} catch (LineUnavailableException ex) {
|
||||
System.err.println("Error initializing sound system: " + ex);
|
||||
} catch (UnsupportedAudioFileException ex) {
|
||||
System.err.println("Unsupported file type of the sound file: " + fSound.toString() + " - " + ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
throw new MissingResourceException("Sound clip failed to load", this.getClass().getName(), filename);
|
||||
}
|
||||
|
||||
private void clipStateChanged(LineEvent lineEvent) {
|
||||
started |= lineEvent.getType() == LineEvent.Type.START;
|
||||
}
|
||||
|
||||
private void wait(Supplier<Boolean> completed) {
|
||||
final int attempts = 5;
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
if (completed.get() || !waitSoundSystemDelay()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user