mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58: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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.MissingResourceException;
|
import java.util.MissingResourceException;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@@ -35,9 +37,10 @@ import java.util.function.Supplier;
|
|||||||
* @author Agetian
|
* @author Agetian
|
||||||
*/
|
*/
|
||||||
public class AudioClip implements IAudioClip {
|
public class AudioClip implements IAudioClip {
|
||||||
private Clip clip;
|
private final int maxSize = 16;
|
||||||
private boolean started;
|
private String filename;
|
||||||
private boolean looping;
|
private List<ClipWrapper> clips;
|
||||||
|
private boolean failed;
|
||||||
|
|
||||||
public static boolean fileExists(String fileName) {
|
public static boolean fileExists(String fileName) {
|
||||||
File fSound = new File(ForgeConstants.SOUND_DIR, fileName);
|
File fSound = new File(ForgeConstants.SOUND_DIR, fileName);
|
||||||
@@ -45,6 +48,132 @@ public class AudioClip implements IAudioClip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AudioClip(final String filename) {
|
public AudioClip(final String filename) {
|
||||||
|
this.filename = filename;
|
||||||
|
clips = new ArrayList<>(maxSize);
|
||||||
|
addClip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void play() {
|
||||||
|
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() {
|
||||||
|
getIdleClip().loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void stop() {
|
||||||
|
for (ClipWrapper clip: clips) {
|
||||||
|
clip.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isDone() {
|
||||||
|
return clips.stream().noneMatch(ClipWrapper::isRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 static boolean waitSoundSystemDelay() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(SoundSystem.DELAY);
|
||||||
|
return true;
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
File fSound = new File(ForgeConstants.SOUND_DIR, filename);
|
||||||
if (!fSound.exists()) {
|
if (!fSound.exists()) {
|
||||||
throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it");
|
throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it");
|
||||||
@@ -54,79 +183,22 @@ public class AudioClip implements IAudioClip {
|
|||||||
AudioInputStream stream = AudioSystem.getAudioInputStream(fSound);
|
AudioInputStream stream = AudioSystem.getAudioInputStream(fSound);
|
||||||
AudioFormat format = stream.getFormat();
|
AudioFormat format = stream.getFormat();
|
||||||
DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat(), ((int) stream.getFrameLength() * format.getFrameSize()));
|
DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat(), ((int) stream.getFrameLength() * format.getFrameSize()));
|
||||||
clip = (Clip) AudioSystem.getLine(info);
|
Clip clip = (Clip) AudioSystem.getLine(info);
|
||||||
clip.addLineListener(this::lineStatusChanged);
|
|
||||||
clip.open(stream);
|
clip.open(stream);
|
||||||
return;
|
return clip;
|
||||||
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
System.err.println("Unable to load sound file: " + filename);
|
System.err.println("Unable to load sound file: " + filename);
|
||||||
} catch (LineUnavailableException ex) {
|
} catch (LineUnavailableException ex) {
|
||||||
System.err.println("Error initializing sound system: " + ex);
|
System.err.println("Error initializing sound system: " + ex);
|
||||||
} catch (UnsupportedAudioFileException ex) {
|
} catch (UnsupportedAudioFileException ex) {
|
||||||
System.err.println("Unsupported file type of the sound file: " + fSound.toString() + " - " + ex.getMessage());
|
System.err.println("Unsupported file type of the sound file: " + fSound.toString() + " - " + ex.getMessage());
|
||||||
clip = null;
|
return null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
throw new MissingResourceException("Sound clip failed to load", this.getClass().getName(), filename);
|
throw new MissingResourceException("Sound clip failed to load", this.getClass().getName(), filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void clipStateChanged(LineEvent lineEvent) {
|
||||||
public final void play() {
|
started |= lineEvent.getType() == LineEvent.Type.START;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void stop() {
|
|
||||||
if (null == clip) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
synchronized (this) {
|
|
||||||
clip.stop();
|
|
||||||
this.looping = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean isDone() {
|
|
||||||
if (null == clip) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !clip.isRunning();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void wait(Supplier<Boolean> completed) {
|
private void wait(Supplier<Boolean> completed) {
|
||||||
@@ -137,19 +209,5 @@ public class AudioClip implements IAudioClip {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean waitSoundSystemDelay() {
|
|
||||||
try {
|
|
||||||
Thread.sleep(SoundSystem.DELAY);
|
|
||||||
return true;
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void lineStatusChanged(LineEvent line) {
|
|
||||||
LineEvent.Type status = line.getType();
|
|
||||||
this.started |= status == LineEvent.Type.START;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user