From afb386e13fd00fde1401d4551ee4790b1f6d5e09 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Tue, 23 May 2023 02:05:11 +0200
Subject: Sound3D: Further OO wrapper to be used in ALAudioSink: Context
 locking, ALCcontext recreation, ..

Context
- Recursive context locking (only 1st shall do native makeCurrent, only last shall do native release)
- Access to the current Context instance (thread local storage)
- Obey "One context can only be current on one thread,
        and one thread can only have one context current!"
- ALCcontext recreation within lock, allowing to change native OpenAL specifics via attr list
- ALCcontext creation (initial) w/ attr list

Device
- Retrieve name if default name null has been given
- Expose device name
- Allow to open() again

Source
- Allow lazy creation w/ invalid ID
- Allow create() post instantiation (for a single source)
- Throw ALException in all queued buffer methods as they are crucial
  in multithreading streaming.
- Add queue buffer with OpenAL buffer-id int[] arrays variant
  to be used w/o Buffer

Listener
- Fix (get|set)Orientation() API doc: It's 'at' vector, then 'up' vector.

General:
- Have toString()
- Added versatile AudioSystem3D.check*Error(..)

Earlier Sound3D changes
- 7f73d50c90d05cf7388f23977ca956a4933019ad
- 64b40bd4359cad46ebf62751ea342d80205bd98b
---
 .../com/jogamp/openal/sound3d/AudioSystem3D.java   | 137 ++++++++++--
 src/java/com/jogamp/openal/sound3d/Buffer.java     |   9 +-
 src/java/com/jogamp/openal/sound3d/Context.java    | 240 +++++++++++++++++----
 src/java/com/jogamp/openal/sound3d/Device.java     |  60 ++++--
 src/java/com/jogamp/openal/sound3d/Listener.java   |  17 +-
 src/java/com/jogamp/openal/sound3d/Source.java     |  78 ++++++-
 6 files changed, 450 insertions(+), 91 deletions(-)

(limited to 'src/java')

diff --git a/src/java/com/jogamp/openal/sound3d/AudioSystem3D.java b/src/java/com/jogamp/openal/sound3d/AudioSystem3D.java
index 72b777c..0d35da6 100644
--- a/src/java/com/jogamp/openal/sound3d/AudioSystem3D.java
+++ b/src/java/com/jogamp/openal/sound3d/AudioSystem3D.java
@@ -40,6 +40,10 @@ import java.io.InputStream;
 
 import com.jogamp.openal.AL;
 import com.jogamp.openal.ALC;
+import com.jogamp.openal.ALCConstants;
+import com.jogamp.openal.ALCcontext;
+import com.jogamp.openal.ALCdevice;
+import com.jogamp.openal.ALConstants;
 import com.jogamp.openal.ALException;
 import com.jogamp.openal.ALExt;
 import com.jogamp.openal.ALFactory;
@@ -53,7 +57,7 @@ import jogamp.openal.Debug;
  * The AudioSystem3D class provides a set of methods for creating and
  * manipulating a 3D audio environment.
  *
- * @author Athomas Goldberg
+ * @author Athomas Goldberg, Sven Gothel, et al.
  */
 public class AudioSystem3D {
   static boolean DEBUG = Debug.debug("AudioSystem3D");
@@ -99,37 +103,144 @@ public class AudioSystem3D {
    */
   public static boolean isAvailable() { return staticAvailable; }
 
-  public int getALError() {
+  /** Return OpenAL global {@link AL}. */
+  public static final AL getAL() { return al; }
+  /** Return OpenAL global {@link ALC}. */
+  public static final ALC getALC() { return alc; }
+  /** Return OpenAL global {@link ALExt}. */
+  public static final ALExt getALExt() { return alExt; }
+
+  public static int getALError() {
       return al.alGetError();
   }
 
   /**
-   * Creates a new Sound3D Context for a specified device.
-   *
-   * @param device The device the Context is being created for.
+   * Returns true if an OpenAL ALC or AL error occurred, otherwise false
+   * @param device referencing an {@link ALCdevice}, may be null
+   * @param prefix prefix to print on error and if `verbose`
+   * @param verbose pass true to show errors
+   * @param throwException true to throw an ALException on error
+   * @return true if an error occurred, otherwise false
+   */
+  public static boolean checkError(final Device device, final String prefix, final boolean verbose, final boolean throwException) {
+      if( !checkALCError(device, prefix, verbose, throwException) ) {
+          return checkALError(prefix, verbose, throwException);
+      }
+      return false; // no error
+  }
+
+  /**
+   * Returns true if an OpenAL AL error occurred, otherwise false
+   * @param prefix prefix to print on error and if `verbose`
+   * @param verbose pass true to show errors
+   * @param throwException true to throw an ALException on error
+   * @return true if an error occurred, otherwise false
+   */
+  public static boolean checkALError(final String prefix, final boolean verbose, final boolean throwException) {
+      final int alErr = al.alGetError();
+      if( ALConstants.AL_NO_ERROR != alErr ) {
+          final String msg = prefix+": AL error 0x"+Integer.toHexString(alErr)+", '"+al.alGetString(alErr);
+          if( verbose ) {
+              System.err.println(msg);
+          }
+          if( throwException ) {
+              throw new ALException(msg);
+          }
+          return true;
+      }
+      return false;
+  }
+  /**
+   * Returns true if an OpenAL ALC error occurred, otherwise false
+   * @param device referencing an {@link ALCdevice}, may be null
+   * @param prefix prefix to print on error and if `verbose`
+   * @param verbose pass true to show errors
+   * @param throwException true to throw an ALException on error
+   * @return true if an error occurred, otherwise false
+   */
+  public static boolean checkALCError(final Device device, final String prefix, final boolean verbose, final boolean throwException) {
+      final ALCdevice alcDevice = null != device ? device.getALDevice() : null;
+      final int alcErr = alc.alcGetError( alcDevice );
+      if( ALCConstants.ALC_NO_ERROR != alcErr ) {
+          final String msg = prefix+": ALC error 0x"+Integer.toHexString(alcErr)+", "+alc.alcGetString(alcDevice, alcErr);
+          if( verbose ) {
+              System.err.println(msg);
+          }
+          if( throwException ) {
+              throw new ALException(msg);
+          }
+          return true;
+      }
+      return false;
+  }
+
+  /**
+   * Creates a new Sound3D Context for a specified device including native {@link ALCcontext} creation.
    *
+   * @param device The device the Context is being created for, must be valid
    * @return The new Sound3D context.
    */
   public static Context createContext(final Device device) {
-      return new Context(device);
+      return new Context(device, null);
   }
 
   /**
-   * Makes the specified context the current context.
+   * Creates a new Sound3D Context for a specified device including native {@link ALCcontext} creation.
    *
-   * @param context the context to make current.
+   * @param device The device the Context is being created for, must be valid.
+   * @param attributes list of {@link ALCcontext} attributes for context creation, maybe empty or null
+   * @return The new Sound3D context.
    */
-  public static boolean makeContextCurrent(final Context context) {
-    return context.makeCurrent();
+  public static Context createContext(final Device device, final int[] attributes) {
+      return new Context(device, attributes);
   }
 
   /**
-   * Release the specified context.
+   * Returns this thread current context.
+   * If no context is current, returns null.
    *
+   * @return the context current on this thread, or null if no context is current.
+   * @see Context#getCurrentContext()
+   * @see #makeContextCurrent(Context)
+   * @see #releaseContext(Context)
+   */
+  public static Context getCurrentContext() {
+      return Context.getCurrentContext();
+  }
+
+  /**
+   * Makes the audio context current on the calling thread.
+   * <p>
+   * Recursive calls are supported.
+   * </p>
+   * <p>
+   * At any point in time one context can only be current by one thread,
+   * and one thread can only have one context current.
+   * </p>
+   * @param context the context to make current.
+   * @param throwException if true, throws ALException if {@link #getALContext()} is null, current thread holds another context or failed to natively make current
+   * @return true if current thread holds no other context and context successfully made current, otherwise false
+   * @see Context#makeCurrent()
+   * @see #releaseContext(Context)
+   */
+  public static boolean makeContextCurrent(final Context context, final boolean throwException) {
+    return context.makeCurrent(throwException);
+  }
+
+  /**
+   * Releases control of this audio context from the current thread, if implementation utilizes context locking.
+   * <p>
+   * Recursive calls are supported.
+   * </p>
    * @param context the context to release.
+   * @param throwException if true, throws ALException if context has not been previously made current on current thread
+   *                       or native release failed.
+   * @return true if context has previously been made current on the current thread and successfully released, otherwise false
+   * @see Context#release()
+   * @see #makeContextCurrent(Context)
    */
-  public static boolean releaseContext(final Context context) {
-    return context.release();
+  public static boolean releaseContext(final Context context, final boolean throwException) {
+    return context.release(throwException);
   }
 
   /**
diff --git a/src/java/com/jogamp/openal/sound3d/Buffer.java b/src/java/com/jogamp/openal/sound3d/Buffer.java
index 52a17bd..0b6db38 100644
--- a/src/java/com/jogamp/openal/sound3d/Buffer.java
+++ b/src/java/com/jogamp/openal/sound3d/Buffer.java
@@ -43,9 +43,9 @@ import java.nio.ByteBuffer;
  * The Sound3D Buffer is a container for audio data used in the Sound3D
  * environment.
  *
- * @author Athomas Goldberg
+ * @author Athomas Goldberg, Sven Gothel, et al.
  */
-public class Buffer {
+public final class Buffer {
     public final static int FORMAT_MONO8 = AL.AL_FORMAT_MONO8;
 
     public final static int FORMAT_MONO16 = AL.AL_FORMAT_MONO16;
@@ -149,4 +149,9 @@ public class Buffer {
 
         return i[0];
     }
+
+    @Override
+    public String toString() {
+        return "ALBuffer[id "+alBufferID+"]";
+    }
 }
diff --git a/src/java/com/jogamp/openal/sound3d/Context.java b/src/java/com/jogamp/openal/sound3d/Context.java
index 28e033e..2884f0c 100644
--- a/src/java/com/jogamp/openal/sound3d/Context.java
+++ b/src/java/com/jogamp/openal/sound3d/Context.java
@@ -34,6 +34,7 @@
 
 package com.jogamp.openal.sound3d;
 
+import com.jogamp.common.ExceptionUtils;
 import com.jogamp.common.util.locks.LockFactory;
 import com.jogamp.common.util.locks.RecursiveLock;
 import com.jogamp.openal.*;
@@ -43,25 +44,32 @@ import com.jogamp.openal.util.ALHelpers;
 /**
  * This class provides a Sound3D Context associated with a specified device.
  *
- * @author Athomas Goldberg
+ * @author Athomas Goldberg, Sven Gothel, et al.
  */
-public class Context {
+public final class Context {
     private final RecursiveLock lock = LockFactory.createRecursiveLock();
-    private Device device;
-    private ALCcontext alCtx;
+    private final Device device;
+    private volatile ALCcontext alCtx;
     private boolean threadContextLocked;
     private boolean hasALC_thread_local_context;
+    private static final ThreadLocal<Context> currentContext = new ThreadLocal<Context>();
 
+    /**
+     * Creates a new Context for a given {@link ALCcontext} for the specified device.
+     *
+     * @param realContext {@link ALCcontext} instance, maybe null
+     * @param device The device the Context belongs to, must be valid
+     */
     public Context(final ALCcontext realContext, final Device device) {
         this.device = device;
         this.alCtx = realContext;
         {
             hasALC_thread_local_context = false;
             final boolean v;
-            if( makeCurrent() ) {
+            if( makeCurrent(false) ) {
                 v = AudioSystem3D.alc.alcIsExtensionPresent(null, ALHelpers.ALC_EXT_thread_local_context) ||
                     AudioSystem3D.alc.alcIsExtensionPresent(device.getALDevice(), ALHelpers.ALC_EXT_thread_local_context);
-                release();
+                release(false);
             } else {
                 v = false;
             }
@@ -70,26 +78,84 @@ public class Context {
     }
 
     /**
-     * Creates a new Context for a specified device.
+     * Creates a new Context for a specified device including native {@link ALCcontext} creation.
+     *
+     * @param device The device the Context is being created for, must be valid.
+     * @param attributes list of {@link ALCcontext} attributes for context creation, maybe empty or null
+     */
+    public Context(final Device device, final int[] attributes) {
+        this( createImpl(device.getALDevice(), attributes), device);
+    }
+
+    /**
+     * Creates a new {@link ALCcontext}.
      *
-     * @param device The device the Context is being created for.
+     * @param alDevice a valid {@link ALCdevice}
+     * @param attributes lost of {@link ALCcontext} attributes for context creation
      */
-    public Context(final Device device) {
-        this(AudioSystem3D.alc.alcCreateContext(device.getALDevice(), null), device);
+    private static ALCcontext createImpl(final ALCdevice alDevice, final int[] attributes) {
+        if( null != attributes && attributes.length > 0 ) {
+            return AudioSystem3D.alc.alcCreateContext(alDevice, attributes, 0);
+        } else {
+            return AudioSystem3D.alc.alcCreateContext(alDevice, null);
+        }
+    }
+
+    /**
+     * Creates the internal {@link ALCcontext} instance if {@link #getALContext()} is null
+     * @param attributes lost of {@link ALCcontext} attributes for context creation
+     * @return true if the internal context has been successfully created, otherwise false
+     */
+    public boolean create(final int[] attributes) {
+        lock.lock();
+        try {
+            if( null == alCtx ) {
+                alCtx = createImpl(device.getALDevice(), attributes);
+                return null != alCtx;
+            }
+            return false;
+        } finally {
+            lock.unlock();
+        }
     }
 
     /**
-     * Returns the OpenAL context.
+     * Recreates the internal {@link ALCcontext} instance, i.e. destroys it first if {@link #getALContext()} not null.
+     * <p>
+     * Context is made current again if it was current before.
+     * </p>
+     * @param attributes lost of {@link ALCcontext} attributes for context creation
+     * @return true if the internal context has been successfully recreated and made current again if was current before, otherwise false
      */
-    public ALCcontext getALContext() {
-        return alCtx;
+    public boolean recreate(final int[] attributes) {
+        lock.lock();
+        try {
+            final boolean wasCurrent = this == getCurrentContext();
+            destroyImpl();
+            alCtx = createImpl(device.getALDevice(), attributes);
+            if( null != alCtx ) {
+                if( wasCurrent ) {
+                    return makeCurrentImpl();
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        } finally {
+            lock.unlock();
+        }
     }
 
+    /** Returns the OpenAL {@link ALCcontext}. */
+    public ALCcontext getALContext() { return alCtx; }
+
     /** Returns whether {@link #getALContext()} is valid, i.e. not null, e.g. not {@link #destroy()}'ed. */
     public boolean isValid() { return null != alCtx; }
 
+    /** Return {@link ALC#alcGetError(ALCdevice)} using {@link #getDevice()}. */
     public int getALCError() {
-        return AudioSystem3D.alc.alcGetError(device.getALDevice());
+        return device.getALCError();
     }
 
     /**
@@ -98,16 +164,8 @@ public class Context {
     public void destroy() {
         lock.lock();
         try {
-            if( null != alCtx ) {
-                if( threadContextLocked ) {
-                    AudioSystem3D.alExt.alcSetThreadContext(null);
-                } else {
-                    AudioSystem3D.alc.alcMakeContextCurrent(null);
-                }
-                AudioSystem3D.alc.alcDestroyContext(alCtx);
-                alCtx = null;
-            }
-            device = null;
+            destroyImpl();
+            currentContext.set(null);
             // unroll lock !
             while(lock.getHoldCount() > 1) {
                 lock.unlock();
@@ -116,38 +174,132 @@ public class Context {
             lock.unlock();
         }
     }
+    private void destroyImpl() {
+        if( null != alCtx ) {
+            if( threadContextLocked ) {
+                AudioSystem3D.alExt.alcSetThreadContext(null);
+            } else {
+                AudioSystem3D.alc.alcMakeContextCurrent(null);
+            }
+            AudioSystem3D.alc.alcDestroyContext(alCtx);
+            alCtx = null;
+        }
+    }
+
+    /**
+     * Returns this thread current context.
+     * If no context is current, returns null.
+     *
+     * @return the context current on this thread, or null if no context is current.
+     * @see #makeCurrent()
+     * @see #release()
+     */
+    public static Context getCurrentContext() {
+        return currentContext.get();
+    }
+
+    /** Return the lock count of this context, i.e. 0 if not locked, 1 if locked once, >1 for recursive locks. */
+    public int getLockCount() {
+        return lock.getHoldCount();
+    }
 
     /**
-     * Makes this context current.
+     * Makes the audio context current on the calling thread.
+     * <p>
+     * Recursive call to {@link #makeCurrent()} and hence {@link #release()} are supported.
+     * </p>
+     * <p>
+     * At any point in time one context can only be current on one thread,
+     * and one thread can only have one context current.
+     * </p>
+     * @param throwException if true, throws ALException if {@link #getALContext()} is null, current thread holds another context or failed to natively make current
+     * @return true if {@link #getALContext()} is valid, current thread holds no other context and context successfully made current, otherwise false
+     * @see #release()
      */
-    public boolean makeCurrent() {
-        final boolean r;
+    public boolean makeCurrent(final boolean throwException) throws ALException {
         lock.lock();
+
+        if( null == alCtx ) {
+            lock.unlock();
+            if( throwException ) {
+                throw new ALException("Invalid "+this);
+            }
+            return false;
+        }
+
+        // One context can only be current on one thread,
+        // and one thread can only have one context current!
+        final Context current = getCurrentContext();
+        if (current != null) {
+            if (current == this) { // implicit recursive locking, lock.getHoldCount() > 1
+                return true;
+            } else {
+                lock.unlock();
+                if( throwException ) {
+                    throw new ALException("Current thread "+Thread.currentThread()+" holds another "+current+" while claiming this "+this);
+                }
+                return false;
+            }
+        }
+        final boolean r = makeCurrentImpl();
+        if( r ) {
+            currentContext.set(this);
+        } else {
+            lock.unlock();
+            if( throwException ) {
+                throw new ALException("Context make current failed "+this);
+            }
+        }
+        return r;
+    }
+    private boolean makeCurrentImpl() {
         if( hasALC_thread_local_context ) {
             threadContextLocked = true;
-            r = AudioSystem3D.alExt.alcSetThreadContext(alCtx);
+            return AudioSystem3D.alExt.alcSetThreadContext(alCtx);
         } else {
             threadContextLocked = false;
-            r = AudioSystem3D.alc.alcMakeContextCurrent(alCtx);
+            return AudioSystem3D.alc.alcMakeContextCurrent(alCtx);
         }
-        if( !r ) {
-            lock.unlock();
-        }
-        return r;
     }
 
     /**
-     * Release this context.
+     * Releases control of this audio context from the current thread, if implementation utilizes context locking.
+     * <p>
+     * Recursive call to {@link #makeCurrent()} and hence {@link #release()} are supported.
+     * </p>
+     * <p>
+     * If native release fails, internal lock is not released.
+     * </p>
+     * @param throwException if true, throws ALException if context has not been previously made current on current thread
+     *                       or native release failed.
+     * @return true if context has previously been made current on the current thread and successfully released, otherwise false
+     * @see #makeCurrent()
      */
-    public boolean release() {
-        final boolean r;
-        if( threadContextLocked ) {
-            r = AudioSystem3D.alExt.alcSetThreadContext(null);
-        } else {
-            r = AudioSystem3D.alc.alcMakeContextCurrent(null);
+    public boolean release(final boolean throwException) throws ALException {
+        if( !lock.isOwner( Thread.currentThread() ) )  {
+            if( throwException ) {
+                throw new ALException("Context not held on current thread "+Thread.currentThread()+", "+this);
+            }
+            return false;
+        }
+        if( lock.getHoldCount() == 1 ) {
+            final boolean r;
+            if( threadContextLocked ) {
+                r = AudioSystem3D.alExt.alcSetThreadContext(null);
+            } else {
+                r = AudioSystem3D.alc.alcMakeContextCurrent(null);
+            }
+            if( r ) {
+                currentContext.set(null);
+            } else {
+                if( throwException ) {
+                    throw new ALException("Context release failed "+this);
+                }
+                return false; // skip unlock!
+            }
         }
         lock.unlock();
-        return r;
+        return true;
     }
 
     /**
@@ -165,4 +317,10 @@ public class Context {
     public Device getDevice() {
         return device;
     }
+
+    @Override
+    public String toString() {
+        final String alCtxStr = null != alCtx ? "0x"+Integer.toHexString(alCtx.hashCode()) : "null";
+        return "ALContext[this 0x"+Integer.toHexString(hashCode())+", alCtx "+alCtxStr+" lockCount "+lock.getHoldCount()+", on "+device+"]";
+    }
 }
diff --git a/src/java/com/jogamp/openal/sound3d/Device.java b/src/java/com/jogamp/openal/sound3d/Device.java
index 749d606..052d6a2 100644
--- a/src/java/com/jogamp/openal/sound3d/Device.java
+++ b/src/java/com/jogamp/openal/sound3d/Device.java
@@ -40,39 +40,67 @@ import com.jogamp.openal.*;
 /**
  * This class provides a handle to a specific audio device.
  *
- * @author Athomas Goldberg
+ * @author Athomas Goldberg, Sven Gothel, et al.
  */
-public class Device {
+public final class Device {
+    private String name;
     private ALCdevice alDev;
 
-    public Device(final ALCdevice realDevice) {
-        this.alDev = realDevice;
-    }
-
     /**
-     * Create a new device by opening the named audio device.
+     * Create a new device by {@link #open()}'ing the named audio device.
      *
      * @param deviceName The specified device name, null for default.
      */
     public Device(final String deviceName) {
-        this.alDev = AudioSystem3D.alc.alcOpenDevice(deviceName);
+        this.name = deviceName;
+        this.alDev = null;
+        open();
     }
 
-    /**
-     * Returns the OpenAL context.
-     */
-    public ALCdevice getALDevice() {
-        return alDev;
+    /** Returns the device name. */
+    public String getName() { return name; }
+
+    /** Returns the OpenAL {@link ALCdevice}. */
+    public ALCdevice getALDevice() { return alDev; }
+
+    /** Return {@link ALC#alcGetError(ALCdevice)} */
+    public int getALCError() {
+        return AudioSystem3D.alc.alcGetError(alDev);
     }
 
-    /** Returns whether {@link #getALDevice()} is valid, i.e. not null, e.g. not {@link #close()}. */
+    /** Returns whether {@link #getALDevice()} is open and valid, i.e. not null, e.g. not {@link #close()}. */
     public boolean isValid() { return null != alDev; }
 
+    /**
+     * Opens the device if not yet opened
+     * @return true if already open or newly opened
+     * @see #isValid()
+     * @see #clone()
+     */
+    public boolean open() {
+        if( null == alDev ) {
+            alDev = AudioSystem3D.alc.alcOpenDevice(name);
+            if( null != alDev && null == name ) {
+                name = AudioSystem3D.alc.alcGetString(alDev, ALCConstants.ALC_DEVICE_SPECIFIER);
+            }
+        }
+        return isValid();
+    }
+
     /**
      * closes the device, freeing its resources.
      */
     public void close() {
-        AudioSystem3D.alc.alcCloseDevice(alDev);
-        alDev = null;
+        if( null != alDev ) {
+            AudioSystem3D.alc.alcCloseDevice(alDev);
+            alDev = null;
+        }
     }
+
+    @Override
+    public String toString() {
+        final String alStr = null != alDev ? "0x"+Integer.toHexString(alDev.hashCode()) : "null";
+        return "ALDevice[this 0x"+Integer.toHexString(hashCode())+", name '"+name+"', alDev "+alStr+"]";
+    }
+
 }
diff --git a/src/java/com/jogamp/openal/sound3d/Listener.java b/src/java/com/jogamp/openal/sound3d/Listener.java
index 86a9307..5fc9e43 100644
--- a/src/java/com/jogamp/openal/sound3d/Listener.java
+++ b/src/java/com/jogamp/openal/sound3d/Listener.java
@@ -41,9 +41,9 @@ import com.jogamp.openal.ALConstants;
  * provides methods for controlling the position, orientation as well as other
  * properties associated with the listener.
  *
- * @author Athomas Goldberg
+ * @author Athomas Goldberg, Sven Gothel, et al.
  */
-public class Listener {
+public final class Listener {
     public Listener() {
     }
 
@@ -141,11 +141,10 @@ public class Listener {
 
     /**
      * Sets the orientation of the Listener in the Sound3D environment.
-     * Orientation is expressed as "up" and "at" vectors.
+     * Orientation is expressed as `at` and `up` vectors.
      *
      * @param orientation The first 3 elements of the array should contain
-     * the x,y,z up-vector, the second 3 elements should contain the x,z,z
-     * look-at vector.
+     * the `at` vector, the second 3 elements should contain the `up` vector.
      */
     public void setOrientation(final float[] orientation) {
         AudioSystem3D.al.alListenerfv(ALConstants.AL_ORIENTATION, orientation, 0);
@@ -153,12 +152,10 @@ public class Listener {
 
     /**
      * Gets the orientation of the Listener in the Sound3D environment.
-     * Orientation is expressed as "up" and "at" vectors.
+     * Orientation is expressed as `at` and `up` vectors.
      *
-     * @return an array containing the orientation of the listener.
-     * The first 3 elements of the array contain
-     * the x,y,z up-vector, the second 3 elements contain the x,z,z
-     * look-at vector.
+     * @return an array containing the orientation of the listener, a pair of 3-float vectors for x,y,z.
+     * The first 3 elements of the array contain the `at` vector, the second 3 elements contain the `up` vector.
      */
     public float[] getOrientation() {
         final float[] tmp = new float[6];
diff --git a/src/java/com/jogamp/openal/sound3d/Source.java b/src/java/com/jogamp/openal/sound3d/Source.java
index b3b0da9..09b255a 100644
--- a/src/java/com/jogamp/openal/sound3d/Source.java
+++ b/src/java/com/jogamp/openal/sound3d/Source.java
@@ -35,6 +35,7 @@
 package com.jogamp.openal.sound3d;
 
 import com.jogamp.openal.ALConstants;
+import com.jogamp.openal.ALException;
 
 /**
  * This class is used to represent sound-producing objects in the Sound3D
@@ -42,16 +43,44 @@ import com.jogamp.openal.ALConstants;
  * gain and other properties along with methods for starting, pausing, rewinding
  * and stopping sudio projecting from a source.
  *
- * @author Athomas Goldberg
+ * @author Athomas Goldberg, Sven Gothel, et al.
  */
 public final class Source {
     private int sourceID;
     private Buffer buffer;
 
+    /** Create a new instance with an invalid OpenAL source ID */
+    public Source() {
+        sourceID = -1;
+    }
+
+    /**
+     * Create a new instance with a given OpenAL source ID
+     * @param sourceID an OpenAL source ID, pass -1 for an invalid value for lazy creation
+     */
     public Source(final int sourceID) {
         this.sourceID = sourceID;
     }
 
+    /**
+     * Creates a new OpenAL source ID if {@link #isValid()} == false.
+     * @return true if a new ID has been successfully created, otherwise false
+     */
+    public boolean create() {
+        if( isValid() ) {
+            return false;
+        }
+        final int[] val = { -1 };
+        AudioSystem3D.al.alGenSources(1, val, 0);
+        if( 0 <= val[0] && AudioSystem3D.al.alIsSource(val[0]) ) {
+            sourceID = val[0];
+            return true;
+        } else {
+            sourceID = -1;
+            return false;
+        }
+    }
+
     /** Return the OpenAL source ID, -1 if invalid. */
     public int getID() { return sourceID; }
 
@@ -466,22 +495,24 @@ public final class Source {
     /**
      * Gets the number of buffers currently queued on this source.
      * @return the number of buffers currently queued on this source.
+     * @throws ALException on AL error
      */
-    public int getBuffersQueued() {
+    public int getBuffersQueued() throws ALException {
         final int[] result = new int[1];
         AudioSystem3D.al.alGetSourcei(sourceID, ALConstants.AL_BUFFERS_QUEUED, result, 0);
-
+        AudioSystem3D.checkALError("Query AL_BUFFERS_QUEUED", true, true);
         return result[0];
     }
 
     /**
      * Gets the number of buffers already processed on this source.
      * @return the number of buffers already processed on this source.
+     * @throws ALException on AL error
      */
-    public int getBuffersProcessed() {
+    public int getBuffersProcessed() throws ALException {
         final int[] result = new int[1];
         AudioSystem3D.al.alGetSourcei(sourceID, ALConstants.AL_BUFFERS_PROCESSED, result, 0);
-
+        AudioSystem3D.checkALError("Query AL_BUFFERS_PROCESSED", true, true);
         return result[0];
     }
 
@@ -515,31 +546,60 @@ public final class Source {
      * buffers will be played in the order they are queued.
      *
      * @param buffers a set of initialized (loaded) buffers.
+     * @throws ALException on AL error
      */
-    public void queueBuffers(final Buffer[] buffers) {
+    public void queueBuffers(final Buffer[] buffers) throws ALException {
         final int numBuffers = buffers.length;
         final int[] arr = new int[numBuffers];
 
         for (int i = 0; i < numBuffers; i++) {
             arr[i] = buffers[i].getID();
         }
-
         AudioSystem3D.al.alSourceQueueBuffers(sourceID, numBuffers, arr, 0);
+        AudioSystem3D.checkALError("alSourceQueueBuffers", true, true);
+    }
+
+    /**
+     * Queues `bufferIDs.length` OpenAL buffers on a source.
+     *
+     * @param bufferIDs array of to be queued OpenAL buffer IDs
+     * @throws ALException on AL error
+     */
+    public void queueBuffers(final int[] bufferIDs) throws ALException {
+        AudioSystem3D.al.alSourceQueueBuffers(sourceID, bufferIDs.length, bufferIDs, 0);
+        AudioSystem3D.checkALError("alSourceQueueBuffers", true, true);
     }
 
     /**
      * Unqueues one or more buffers on a source.
      *
      * @param buffers a set of previously queued buffers.
+     * @throws ALException on AL error
      */
-    public void unqueueBuffers(final Buffer[] buffers) {
+    public void unqueueBuffers(final Buffer[] buffers) throws ALException {
         final int numBuffers = buffers.length;
         final int[] arr = new int[numBuffers];
 
         for (int i = 0; i < numBuffers; i++) {
             arr[i] = buffers[i].getID();
         }
-
         AudioSystem3D.al.alSourceUnqueueBuffers(sourceID, numBuffers, arr, 0);
+        AudioSystem3D.checkALError("alSourceUnqueueBuffers", true, true);
+    }
+
+    /**
+     * Unqueues `bufferIDs.length` OpenAL buffers on a source.
+     *
+     * @param bufferIDs array of resulting unqueued OpenAL buffer IDs of previously queued buffers.
+     * @throws ALException on AL error
+     */
+    public void unqueueBuffers(final int[] bufferIDs) throws ALException {
+        AudioSystem3D.al.alSourceUnqueueBuffers(sourceID, bufferIDs.length, bufferIDs, 0);
+        AudioSystem3D.checkALError("alSourceUnqueueBuffers", true, true);
+    }
+
+    @Override
+    public String toString() {
+        return "ALSource[id "+sourceID+", buffer "+buffer+"]";
     }
 }
-- 
cgit v1.2.3