/* * Copyright (c) 2006-2011 Karsten Schmidt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * http://creativecommons.org/licenses/LGPL/2.1/ * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * Modified and updated by Martin Prout December 2015 */ package toxi.audio; import java.io.IOException; import java.io.InputStream; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.logging.Logger; import javax.sound.sampled.UnsupportedAudioFileException; import com.jogamp.openal.AL; import com.jogamp.openal.ALC; import com.jogamp.openal.ALCcontext; import com.jogamp.openal.ALCdevice; import com.jogamp.openal.ALException; import com.jogamp.openal.ALFactory; import com.jogamp.openal.eax.EAX; import com.jogamp.openal.eax.EAXConstants; import com.jogamp.openal.eax.EAXFactory; import com.jogamp.openal.util.WAVData; import com.jogamp.openal.util.WAVLoader; import java.util.logging.Level; /** * JOAL convenience wrapper. Full * documentation forthcoming. Please see the attached Processing demo & source * distribution of this package for basic usage. */ public class JOALUtil { public static String HARDWARE = "Generic Hardware"; public static String SOFTWARE = "Generic Software"; public static final Logger logger = Logger.getLogger(JOALUtil.class .getName()); protected static JOALUtil instance; public static JOALUtil getInstance() { synchronized (JOALUtil.class) { if (instance == null) { instance = new JOALUtil(); } } return instance; } protected ArrayList buffers; protected ArrayList sources; protected SoundListener listener; protected AL al; protected ALC alc; protected ALCcontext context; protected ALCdevice device; protected EAX eax; protected boolean isInited; protected boolean isEAX; protected JOALUtil() { } /** * Deletes & releases all sources and buffers created via this class. */ public void deleteAll() { logger.info("deleting all sources & buffers..."); while (sources.size() > 0) { deleteSource(sources.get(0), true); } sources.clear(); buffers.clear(); } public boolean deleteBuffer(AudioBuffer b) { if (b != null) { sources.stream().filter((s) -> (s.getBuffer() == b)).map((s) -> { s.stop(); return s; }).forEach((s) -> { logger.log(Level.FINE, "forced stopping source: {0}", s); }); boolean result = b.delete(); if (buffers.remove(b)) { logger.log(Level.INFO, "deleted buffer: {0}", b); } return result; } else { logger.warning("attempted to delete null buffer"); return true; } } public boolean deleteSource(AudioSource src) { return deleteSource(src, false); } public boolean deleteSource(AudioSource src, boolean killBuffer) { AudioBuffer buffer = src.getBuffer(); boolean result = src.delete(); if (sources.remove(src)) { logger.log(Level.INFO, "deleted source: {0}", src); } else { logger.log(Level.WARNING, "deleted unmanaged source: {0}", src); } if (killBuffer && buffer != null) { result = result && deleteBuffer(buffer); } return result; } /** * Creates the specified number of audio sample buffers and returns an array * of {@link AudioBuffer} wrappers. * * @param numBuffers * number of requested buffers * @return array */ public AudioBuffer[] generateBuffers(int numBuffers) { if (!isInited) { init(); } AudioBuffer[] result = new AudioBuffer[numBuffers]; int[] arr = new int[numBuffers]; al.alGenBuffers(numBuffers, arr, 0); for (int i = 0; i < numBuffers; i++) { result[i] = new AudioBuffer(al, arr[i]); buffers.add(result[i]); } return result; } /** * Convenience wrapper for {@link #generateSources(int)} to create a single * {@link AudioSource}. * * @return audio source instance */ public AudioSource generateSource() { return generateSources(1)[0]; } /** * Convenience wrapper bundling {@link #loadBuffer(String)} & * {@link #generateSource()} in a single method call. Generates a new * {@link AudioSource} and assigns the sample buffer created from the given * WAV file. * * @param file * absolute path to WAV file * @return configured audio source instance */ public AudioSource generateSourceFromFile(String file) { if (!isInited) { init(); } AudioSource source = null; AudioBuffer buffer = loadBuffer(file); if (buffer != null) { source = generateSource(); source.setBuffer(buffer); } return source; } /** * Creates the specified number of hardware audio sources required to * actually play the sample data stored in {@link AudioBuffer}s. * * @param numSources * number of sources required * @return array */ public AudioSource[] generateSources(int numSources) { if (!isInited) { init(); } AudioSource[] result = new AudioSource[numSources]; int[] arr = new int[numSources]; al.alGenSources(numSources, arr, 0); for (int i = 0; i < numSources; i++) { result[i] = new AudioSource(al, arr[i]); sources.add(result[i]); } return result; } /** * Returns a direct reference to the OpenAL API. * * @return JOAL context */ public AL getAL() { if (!isInited) { init(); } return al; } /** * Retrieves a list of available OpenAL compatible audio devices. This * method can be called before a call to {@link #init()}. * * @return array of device names */ public String[] getDeviceList() { if (alc == null) { alc = ALFactory.getALC(); } return alc.alcGetDeviceSpecifiers(); } /** * Returns the {@link SoundListener} instance for the associated OpenAL * context. * * @return listener object */ public SoundListener getListener() { if (!isInited) { init(); } if (listener == null) { listener = new SoundListener(this); } return listener; } /** * Initializes the OpenAL context. Safe to be called multiple times (only * first time is executed). * * @return true, if successful */ public boolean init() { return init(null, false); } /** * Initializes the OpenAL context and if parameter is true, will attempt to * also setup an EAX environment. The method does nothing if it had been * called previously and not been {@link #shutdown()} meanwhile. * * @param deviceName * @param attemptEAX * @return true, if successful (does not care if EAX is supported & has * succeeded). */ public boolean init(String deviceName, boolean attemptEAX) { if (context != null) { throw new ALException("OpenAL already initialized"); } if (al == null) { al = ALFactory.getAL(); } if (alc == null) { alc = ALFactory.getALC(); } ALCdevice d = alc.alcOpenDevice(deviceName); if (d == null) { throw new ALException("Error opening default OpenAL device"); } ALCcontext c = alc.alcCreateContext(d, null); if (c == null) { alc.alcCloseDevice(d); throw new ALException("Error creating OpenAL context"); } alc.alcMakeContextCurrent(c); if (alc.alcGetError(d) != 0) { alc.alcDestroyContext(c); alc.alcCloseDevice(d); throw new ALException("Error making OpenAL context current"); } // Fully initialized; finish setup device = d; context = c; isInited = (al.alGetError() == AL.AL_NO_ERROR); buffers = new ArrayList<>(); sources = new ArrayList<>(); listener = new SoundListener(this); isEAX = al.alIsExtensionPresent("EAX2.0"); if (isEAX && attemptEAX) { initEAX(); } return isInited; } protected void initEAX() { eax = EAXFactory.getEAX(); IntBuffer b = IntBuffer.allocate(1); b.put(EAXConstants.EAX_ENVIRONMENT_HANGAR); eax.setListenerProperty( EAXConstants.DSPROPERTY_EAXLISTENER_ENVIRONMENT, b); } /** * Checks if EAX are supported by the underlying hardware. * * @return true, if supported. */ public boolean isEAXSupported() { return isEAX; } /** * Loads a WAV file from the given {@link InputStream}. * * @param is * input stream * @return buffer wrapper instance * @throws UnsupportedAudioFileException * @throws IOException */ public AudioBuffer loadBuffer(InputStream is) throws UnsupportedAudioFileException, IOException { AudioBuffer result; AudioBuffer[] tmp = generateBuffers(1); result = tmp[0]; WAVData wd = WAVLoader.loadFromStream(is); result.configure(wd.data, wd.format, wd.freq); return result; } /** * Loads a WAV file (mono/stereo) from the specified file name * * @param fileName * audio file name * @return buffer wrapper instance */ public AudioBuffer loadBuffer(String fileName) { AudioBuffer result = null; try { WAVData wd = WAVLoader.loadFromFile(fileName); AudioBuffer[] tmp = generateBuffers(1); result = tmp[0]; result.configure(wd.data, wd.format, wd.freq); } catch (IOException e) { logger.severe(e.getMessage()); } return result; } /** * Destroys all objects, sources, buffers, contexts created by this class. */ public void shutdown() { if (isInited) { logger.info("shutting down JOAL"); deleteAll(); alc.alcMakeContextCurrent(null); alc.alcDestroyContext(context); alc.alcCloseDevice(device); context = null; device = null; alc = null; al = null; buffers = null; sources = null; isInited = false; } } }