All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bloodshura.x.assets.sound.codec.DefaultSoundCodec Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * 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