Pārlūkot izejas kodu

add Environment field

Wyatt Gillette 4 mēneši atpakaļ
vecāks
revīzija
cf3de683e5

+ 209 - 75
jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java

@@ -44,6 +44,9 @@ import java.util.ArrayList;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+/**
+ * ALAudioRenderer is the backend implementation for OpenAL audio rendering.
+ */
 public class ALAudioRenderer implements AudioRenderer, Runnable {
 
     private static final Logger logger = Logger.getLogger(ALAudioRenderer.class.getName());
@@ -66,11 +69,12 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
     // Channel management
     private int[] channels; // OpenAL source IDs
     private AudioSource[] channelSources; // jME source associated with each channel
-    private int nextChan = 0; // Next available channel index
+    private int nextChannelIndex = 0; // Next available channel index
     private final ArrayList<Integer> freeChannels = new ArrayList<>(); // Pool of freed channels
 
     // Listener and environment
     private Listener listener;
+    private Environment environment;
     private int reverbFx = -1; // EFX reverb effect ID
     private int reverbFxSlot = -1; // EFX effect slot ID
 
@@ -110,7 +114,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
                 alc.createALC();
             }
         } catch (UnsatisfiedLinkError ex) {
-            logger.log(Level.SEVERE, "Failed to load audio library", ex);
+            logger.log(Level.SEVERE, "Failed to load audio library (OpenAL). Audio will be disabled.", ex);
             audioDisabled = true;
             return;
         }
@@ -156,6 +160,9 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
                 });
     }
 
+    /**
+     * Generates OpenAL sources to determine the maximum number supported.
+     */
     private void enumerateAvailableChannels() {
         // Find maximum # of sources supported by this implementation
         ArrayList<Integer> channelList = new ArrayList<>();
@@ -177,39 +184,52 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         channelSources = new AudioSource[channels.length];
     }
 
+    /**
+     * Initializes the EFX extension if supported.
+     */
     private void initEfx() {
         supportEfx = alc.alcIsExtensionPresent("ALC_EXT_EFX");
         if (supportEfx) {
-            ib.position(0).limit(1);
+            ib.clear().limit(1);
             alc.alcGetInteger(EFX.ALC_EFX_MAJOR_VERSION, ib, 1);
             int major = ib.get(0);
-            ib.position(0).limit(1);
+
+            ib.clear().limit(1);
             alc.alcGetInteger(EFX.ALC_EFX_MINOR_VERSION, ib, 1);
             int minor = ib.get(0);
             logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor});
 
+            ib.clear().limit(1);
             alc.alcGetInteger(EFX.ALC_MAX_AUXILIARY_SENDS, ib, 1);
             auxSends = ib.get(0);
             logger.log(Level.INFO, "Audio max auxiliary sends: {0}", auxSends);
 
-            // create slot
-            ib.position(0).limit(1);
+            // Create reverb effect slot
+            ib.clear().limit(1);
             efx.alGenAuxiliaryEffectSlots(1, ib);
             reverbFxSlot = ib.get(0);
 
-            // create effect
-            ib.position(0).limit(1);
+            // Create reverb effect
+            ib.clear().limit(1);
             efx.alGenEffects(1, ib);
             reverbFx = ib.get(0);
+
+            // Configure effect type
             efx.alEffecti(reverbFx, EFX.AL_EFFECT_TYPE, EFX.AL_EFFECT_REVERB);
+            checkAlError("setting reverb effect type");
 
             // attach reverb effect to effect slot
             efx.alAuxiliaryEffectSloti(reverbFxSlot, EFX.AL_EFFECTSLOT_EFFECT, reverbFx);
+            checkAlError("attaching reverb effect to slot");
+
         } else {
             logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work.");
         }
     }
 
+    /**
+     * Destroys the OpenAL context, deleting sources, buffers, filters, and effects.
+     */
     private void destroyOpenAL() {
         if (audioDisabled) {
             alc.destroyALC();
@@ -228,20 +248,28 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         ib.put(channels);
         ib.flip();
         al.alDeleteSources(channels.length, ib);
+        checkAlError("deleting sources");
 
-        // delete audio buffers and filters
+        // Delete audio buffers and filters managed by NativeObjectManager
         objManager.deleteAllObjects(this);
 
+        // Delete EFX objects if they were created
         if (supportEfx) {
-            ib.position(0).limit(1);
-            ib.put(0, reverbFx);
-            efx.alDeleteEffects(1, ib);
+            if (reverbFx != -1) {
+                ib.position(0).limit(1);
+                ib.put(0, reverbFx);
+                efx.alDeleteEffects(1, ib);
+                checkAlError("deleting reverbFx effect " + reverbFx);
+                reverbFx = -1;
+            }
 
-            // If this is not allocated, why is it deleted?
-            // Commented out to fix native crash in OpenAL.
-            ib.position(0).limit(1);
-            ib.put(0, reverbFxSlot);
-            efx.alDeleteAuxiliaryEffectSlots(1, ib);
+            if (reverbFxSlot != -1) {
+                ib.position(0).limit(1);
+                ib.put(0, reverbFxSlot);
+                efx.alDeleteAuxiliaryEffectSlots(1, ib);
+                checkAlError("deleting effect reverbFxSlot " + reverbFxSlot);
+                reverbFxSlot = -1;
+            }
         }
 
         alc.destroyALC();
@@ -263,12 +291,19 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         decoderThread.start();
     }
 
+    /**
+     * Checks if the audio thread has terminated unexpectedly.
+     * @throws IllegalStateException if the decoding thread is terminated.
+     */
     private void checkDead() {
         if (decoderThread.getState() == Thread.State.TERMINATED) {
             throw new IllegalStateException("Decoding thread is terminated");
         }
     }
 
+    /**
+     * Main loop for the audio decoder thread. Updates streaming sources.
+     */
     @Override
     public void run() {
         long updateRateNanos = (long) (UPDATE_RATE * 1_000_000_000);
@@ -305,27 +340,38 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         logger.info("Audio decoder thread finished.");
     }
 
+    /**
+     * Shuts down the audio decoder thread and destroys the OpenAL context.
+     */
     @Override
     public void cleanup() {
         // kill audio thread
-        if (!decoderThread.isAlive()) {
-            return;
+        if (decoderThread.isAlive()) {
+            decoderThread.interrupt();
+            try {
+                decoderThread.join();
+            } catch (InterruptedException ex) {
+                Thread.currentThread().interrupt(); // Re-interrupt thread
+                logger.log(Level.WARNING, "Interrupted while waiting for audio thread to finish.", ex);
+            }
         }
 
-        decoderThread.interrupt();
-        try {
-            decoderThread.join();
-        } catch (InterruptedException ex) {
+        // Destroy OpenAL context (only if initialized and not disabled)
+        if (!audioDisabled && alc.isCreated()) {
+            destroyOpenAL();
         }
-
-        // destroy OpenAL context
-        destroyOpenAL();
     }
 
+    /**
+     * Updates an OpenAL filter object based on the jME Filter properties.
+     * Generates the AL filter ID if necessary.
+     * @param f The Filter object.
+     */
     private void updateFilter(Filter f) {
         int id = f.getId();
         if (id == -1) {
-            ib.position(0).limit(1);
+            // Generate OpenAL filter ID
+            ib.clear().limit(1);
             efx.alGenFilters(1, ib);
             id = ib.get(0);
             f.setId(id);
@@ -334,16 +380,22 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         }
 
         if (f instanceof LowPassFilter) {
-            LowPassFilter lpf = (LowPassFilter) f;
+            LowPassFilter lowPass = (LowPassFilter) f;
             efx.alFilteri(id, EFX.AL_FILTER_TYPE, EFX.AL_FILTER_LOWPASS);
-            efx.alFilterf(id, EFX.AL_LOWPASS_GAIN, lpf.getVolume());
-            efx.alFilterf(id, EFX.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume());
-        } else {
-            throw new UnsupportedOperationException("Filter type unsupported: "
-                    + f.getClass().getName());
+            efx.alFilterf(id, EFX.AL_LOWPASS_GAIN, lowPass.getVolume());
+            efx.alFilterf(id, EFX.AL_LOWPASS_GAINHF, lowPass.getHighFreqVolume());
+        }
+        // ** Add other filter types (HighPass, BandPass) here if implemented **
+        else {
+            logger.log(Level.WARNING, "Unsupported filter type: {0}", f.getClass().getName());
         }
 
-        f.clearUpdateNeeded();
+        if (checkAlError("updating filter " + id)) {
+            deleteFilter(f); // Try to clean up
+            f.resetObject();
+        } else {
+            f.clearUpdateNeeded(); // Mark as updated in AL
+        }
     }
 
     @Override
@@ -621,9 +673,21 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         }
     }
 
+    /**
+     * Updates a specific parameter for the listener.
+     *
+     * @param listener The listener object.
+     * @param param    The parameter to update.
+     */
     @Override
     public void updateListenerParam(Listener listener, ListenerParam param) {
         checkDead();
+        // Check if this listener is the active one
+        if (this.listener != listener) {
+            logger.warning("updateListenerParam called on inactive listener.");
+            return;
+        }
+
         synchronized (threadLock) {
             if (audioDisabled) {
                 return;
@@ -631,61 +695,79 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
 
             switch (param) {
                 case Position:
-                    Vector3f pos = listener.getLocation();
-                    al.alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
+                    applyListenerPosition(listener);
                     break;
-
                 case Rotation:
-                    Vector3f dir = listener.getDirection();
-                    Vector3f up = listener.getUp();
-                    fb.rewind();
-                    fb.put(dir.x).put(dir.y).put(dir.z);
-                    fb.put(up.x).put(up.y).put(up.z);
-                    fb.flip();
-                    al.alListener(AL_ORIENTATION, fb);
+                    applyListenerRotation(listener);
                     break;
-
                 case Velocity:
-                    Vector3f vel = listener.getVelocity();
-                    al.alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z);
+                    applyListenerVelocity(listener);
                     break;
-
                 case Volume:
-                    al.alListenerf(AL_GAIN, listener.getVolume());
+                    applyListenerVolume(listener);
+                    break;
+                default:
+                    logger.log(Level.WARNING, "Unhandled listener parameter: {0}", param);
                     break;
             }
         }
     }
 
+    /**
+     * Applies all parameters from the listener object to OpenAL.
+     * @param listener The listener object.
+     */
     private void setListenerParams(Listener listener) {
+        applyListenerPosition(listener);
+        applyListenerRotation(listener);
+        applyListenerVelocity(listener);
+        applyListenerVolume(listener);
+    }
+
+    // --- Listener Parameter Helper Methods ---
+
+    private void applyListenerPosition(Listener listener) {
         Vector3f pos = listener.getLocation();
-        Vector3f vel = listener.getVelocity();
+        al.alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
+        checkAlError("setting listener position");
+    }
+
+    private void applyListenerRotation(Listener listener) {
         Vector3f dir = listener.getDirection();
         Vector3f up = listener.getUp();
-
-        al.alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
-        al.alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z);
+        // Use the shared FloatBuffer fb
         fb.rewind();
         fb.put(dir.x).put(dir.y).put(dir.z);
         fb.put(up.x).put(up.y).put(up.z);
         fb.flip();
         al.alListener(AL_ORIENTATION, fb);
+        checkAlError("setting listener orientation");
+    }
+
+    private void applyListenerVelocity(Listener listener) {
+        Vector3f vel = listener.getVelocity();
+        al.alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z);
+        checkAlError("setting listener velocity");
+    }
+
+    private void applyListenerVolume(Listener listener) {
         al.alListenerf(AL_GAIN, listener.getVolume());
+        checkAlError("setting listener volume");
     }
 
     private int newChannel() {
         if (!freeChannels.isEmpty()) {
             return freeChannels.remove(0);
-        } else if (nextChan < channels.length) {
-            return nextChan++;
+        } else if (nextChannelIndex < channels.length) {
+            return nextChannelIndex++;
         } else {
             return -1;
         }
     }
 
     private void freeChannel(int index) {
-        if (index == nextChan - 1) {
-            nextChan--;
+        if (index == nextChannelIndex - 1) {
+            nextChannelIndex--;
         } else {
             freeChannels.add(index);
         }
@@ -703,6 +785,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
                 return;
             }
 
+            // Apply reverb properties from the Environment object
             efx.alEffectf(reverbFx, EFX.AL_REVERB_DENSITY, env.getDensity());
             efx.alEffectf(reverbFx, EFX.AL_REVERB_DIFFUSION, env.getDiffusion());
             efx.alEffectf(reverbFx, EFX.AL_REVERB_GAIN, env.getGain());
@@ -723,6 +806,8 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
             // (Re)attach the configured reverb effect to the slot
             efx.alAuxiliaryEffectSloti(reverbFxSlot, EFX.AL_EFFECTSLOT_EFFECT, reverbFx);
             checkAlError("attaching reverb effect to slot");
+
+            this.environment = env;
         }
     }
 
@@ -859,17 +944,23 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
 
             int sourceId = channels[index];
             al.alSourceStop(sourceId);
+            checkAlError("stopping source " + sourceId + " on clearChannel");
 
             // For streaming sources, this will clear all queued buffers.
             al.alSourcei(sourceId, AL_BUFFER, 0);
+            checkAlError("detaching buffer from source " + sourceId);
 
-            if (src.getDryFilter() != null && supportEfx) {
-                // detach filter
-                al.alSourcei(sourceId, EFX.AL_DIRECT_FILTER, EFX.AL_FILTER_NULL);
-            }
-            if (src.isPositional()) {
-                if (src.isReverbEnabled() && supportEfx) {
+            if (supportEfx) {
+                if (src.getDryFilter() != null) {
+                    // detach direct filter
+                    al.alSourcei(sourceId, EFX.AL_DIRECT_FILTER, EFX.AL_FILTER_NULL);
+                    checkAlError("detaching direct filter from source " + sourceId);
+                }
+
+                if (src.isPositional() && src.isReverbEnabled()) {
+                    // Detach auxiliary send filter (reverb)
                     al.alSource3i(sourceId, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL);
+                    checkAlError("detaching aux filter from source " + sourceId);
                 }
             }
 
@@ -916,8 +1007,35 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
     }
 
     private void restartAudioRenderer() {
+        // Preserve internal state variables
+        Listener currentListener = this.listener;
+        Environment currentEnvironment = this.environment;
+
+        // Destroy existing OpenAL resources
         destroyOpenAL();
+
+        // Re-initialize OpenAL
+        // Creates new context, enumerates channels, checks caps, inits EFX
         initOpenAL();
+
+        // Restore Listener and Environment (if possible and successful init)
+        if (!audioDisabled) {
+            if (currentListener != null) {
+                setListener(currentListener); // Re-apply listener params
+            }
+            if (currentEnvironment != null) {
+                setEnvironment(currentEnvironment); // Re-apply environment
+            }
+            // TODO: What about existing AudioSource objects?
+            // Their state (Playing/Paused/Stopped) is lost.
+            // Their AudioData (buffers/streams) needs re-uploading/re-preparing.
+            // This requires iterating through all known AudioNodes, which the renderer doesn't track.
+            // The application layer would need to handle re-playing sounds after a device reset.
+            logger.warning("Audio renderer restarted. Application may need to re-play active sounds.");
+
+        } else {
+            logger.severe("Audio remained disabled after attempting restart.");
+        }
     }
 
     public void updateInRenderThread(float tpf) {
@@ -1051,8 +1169,13 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
             }
 
             this.listener = listener;
-            this.listener.setRenderer(this);
-            setListenerParams(listener);
+
+            if (this.listener != null) {
+                this.listener.setRenderer(this);
+                setListenerParams(listener);
+            } else {
+                logger.info("Listener set to null.");
+            }
         }
     }
 
@@ -1263,14 +1386,18 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
         as.clearUpdateNeeded();
     }
 
-    private void updateAudioData(AudioData ad) {
-        if (ad instanceof AudioBuffer) {
-            updateAudioBuffer((AudioBuffer) ad);
-        } else if (ad instanceof AudioStream) {
-            updateAudioStream((AudioStream) ad);
+    private void updateAudioData(AudioData audioData) {
+        if (audioData instanceof AudioBuffer) {
+            updateAudioBuffer((AudioBuffer) audioData);
+        } else if (audioData instanceof AudioStream) {
+            updateAudioStream((AudioStream) audioData);
         }
     }
 
+    /**
+     * Deletes the OpenAL filter object associated with the Filter.
+     * @param filter The Filter object.
+     */
     @Override
     public void deleteFilter(Filter filter) {
         int id = filter.getId();
@@ -1278,33 +1405,40 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
             ib.position(0).limit(1);
             ib.put(id).flip();
             efx.alDeleteFilters(1, ib);
+            checkAlError("deleting filter " + id);
             filter.resetObject();
         }
     }
 
+    /**
+     * Deletes the OpenAL objects associated with the AudioData.
+     * @param audioData The AudioData to delete.
+     */
     @Override
-    public void deleteAudioData(AudioData ad) {
+    public void deleteAudioData(AudioData audioData) {
         synchronized (threadLock) {
             if (audioDisabled) {
                 return;
             }
 
-            if (ad instanceof AudioBuffer) {
-                AudioBuffer ab = (AudioBuffer) ad;
+            if (audioData instanceof AudioBuffer) {
+                AudioBuffer ab = (AudioBuffer) audioData;
                 int id = ab.getId();
                 if (id != -1) {
                     ib.put(0, id);
                     ib.position(0).limit(1);
                     al.alDeleteBuffers(1, ib);
+                    checkAlError("deleting buffer " + id);
                     ab.resetObject();
                 }
-            } else if (ad instanceof AudioStream) {
-                AudioStream as = (AudioStream) ad;
+            } else if (audioData instanceof AudioStream) {
+                AudioStream as = (AudioStream) audioData;
                 int[] ids = as.getIds();
                 if (ids != null) {
                     ib.clear();
                     ib.put(ids).flip();
                     al.alDeleteBuffers(ids.length, ib);
+                    checkAlError("deleting " + ids.length + " buffers");
                     as.resetObject();
                 }
             }