com.github.bloodshura.x.assets.sound.codec.DefaultSoundCodec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shurax-assets Show documentation
Show all versions of shurax-assets Show documentation
An API for reading, writing and manipulating fonts, images and sounds.
/*
* Copyright (c) 2013-2018, João Vitor Verona Biazibetti - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* https://www.github.com/BloodShura
*/
package com.github.bloodshura.x.assets.sound.codec;
import com.github.bloodshura.x.assets.sound.Sound;
import com.github.bloodshura.x.assets.sound.Sound.PlayMode;
import com.github.bloodshura.x.assets.sound.exception.SoundException;
import com.github.bloodshura.x.sys.XSystem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class DefaultSoundCodec implements SoundCodec {
private Clip clip;
private AudioFormat format;
private long justPlayed;
private final Sound sound;
private AudioInputStream stream;
public DefaultSoundCodec(@Nonnull Sound sound) {
this.sound = sound;
}
public boolean gainSupported() {
return clip.isControlSupported(FloatControl.Type.MASTER_GAIN);
}
@Nonnull
@Override
public Duration getDuration() {
return Duration.ofNanos(TimeUnit.MICROSECONDS.toNanos(clip.getMicrosecondLength()));
}
@Nullable
public AudioFormat getFormat() {
return format;
}
@Override
public double getGain() {
return gainSupported() ? getGainControl().getValue() / 100 : 0.0D;
}
@Nullable
public FloatControl getGainControl() {
if (gainSupported()) {
return (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
}
return null;
}
@Override
public double getPitch() {
return pitchSupported() ? getPitchControl().getValue() / 100 : 0.0D;
}
@Nullable
public FloatControl getPitchControl() {
if (pitchSupported()) {
return (FloatControl) clip.getControl(FloatControl.Type.SAMPLE_RATE);
}
return null;
}
@Nonnull
@Override
public Duration getPosition() {
if (isPlaying()) {
return Duration.ofNanos(TimeUnit.MICROSECONDS.toNanos(clip.getMicrosecondPosition()));
}
return null;
}
@Nullable
public AudioInputStream getStream() {
return stream;
}
@Override
// TODO Gambiarra...
public boolean isPlaying() {
/* Workaround, since after clip.start() the sound have a small delay before starting to act. Used on Sound.waitUntilDone(). */
if (justPlayed != 0L) {
if (XSystem.millis() - justPlayed <= 150L) {
return true;
}
this.justPlayed = 0L;
}
return clip != null && clip.isActive();
}
@Override
public boolean isSetUp() {
return clip != null && format != null && stream != null;
}
public boolean pitchSupported() {
return clip.isControlSupported(FloatControl.Type.SAMPLE_RATE);
}
@Override
public void play(@Nonnull PlayMode playMode) throws SoundException {
try {
clip.open(getStream());
}
catch (IOException | LineUnavailableException exception) {
throw new SoundException(exception);
}
clip.setFramePosition(0);
if (playMode == PlayMode.LOOP) {
clip.loop(Clip.LOOP_CONTINUOUSLY);
}
else {
clip.start();
}
this.justPlayed = XSystem.millis();
}
@Override
public void setGain(double gain) {
if (gainSupported()) {
double value = Math.max(getGainControl().getMinimum(), Math.min(gain, getGainControl().getMaximum()));
getGainControl().setValue((float) value);
}
}
@Override
public void setPitch(double pitch) {
if (pitchSupported()) {
double value = Math.max(getPitchControl().getMinimum(), Math.min(pitch, getPitchControl().getMaximum()));
getPitchControl().setValue((float) value);
}
}
@Override
public void setPosition(@Nonnull Duration duration) {
clip.setMicrosecondPosition(TimeUnit.NANOSECONDS.toMicros(duration.toNanos()));
}
@Override
public void setup() throws IOException {
try {
InputStream audioSource = sound.newInputStream();
BufferedInputStream bufferedAudio = new BufferedInputStream(audioSource);
AudioInputStream origStream = AudioSystem.getAudioInputStream(bufferedAudio);
AudioFormat origFormat = origStream.getFormat();
AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, // Encoding
origFormat.getSampleRate(), // Sample Rate
16, // Sample Size
origFormat.getChannels(), // Channels
origFormat.getChannels() * 2, // Frame Size
origFormat.getSampleRate(), // Frame Rate
false // Big Endian
);
AudioInputStream stream = AudioSystem.getAudioInputStream(decodedFormat, origStream);
this.clip = (Clip) AudioSystem.getLine(new DataLine.Info(Clip.class, decodedFormat));
this.format = decodedFormat;
this.stream = stream;
}
catch (IllegalArgumentException | LineUnavailableException | UnsupportedAudioFileException exception) {
throw new IOException(exception);
}
}
@Override
public void stop() {
if (isPlaying()) {
clip.stop();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy