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

com.jogamp.audio.windows.waveout.Mixer Maven / Gradle / Ivy

/*
 * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */

package com.jogamp.audio.windows.waveout;

import java.io.*;
import java.nio.*;
import java.util.*;

// Needed only for NIO workarounds on CVM
import java.lang.reflect.*;

public class Mixer {
    // This class is a singleton
    private static Mixer mixer;

    private volatile boolean shutdown;
    private volatile Object shutdownLock = new Object();
    private volatile boolean shutdownDone;

    // Windows Event object
    private final long event;

    private volatile ArrayList tracks = new ArrayList();

    private final Vec3f leftSpeakerPosition  = new Vec3f(-1, 0, 0);
    private final Vec3f rightSpeakerPosition = new Vec3f( 1, 0, 0);

    private float falloffFactor = 1.0f;

    static {
        mixer = new Mixer();
    }

    private Mixer() {
        event = CreateEvent();
        new FillerThread().start();
        final MixerThread m = new MixerThread();
        m.setPriority(Thread.MAX_PRIORITY - 1);
        m.start();
    }

    public static Mixer getMixer() {
        return mixer;
    }

    synchronized void add(final Track track) {
        final ArrayList newTracks = new ArrayList(tracks);
        newTracks.add(track);
        tracks = newTracks;
    }

    synchronized void remove(final Track track) {
        final ArrayList newTracks = new ArrayList(tracks);
        newTracks.remove(track);
        tracks = newTracks;
    }

    // NOTE: due to a bug on the APX device, we only have mono sounds,
    // so we currently only pay attention to the position of the left
    // speaker
    public void setLeftSpeakerPosition(final float x, final float y, final float z) {
        leftSpeakerPosition.set(x, y, z);
    }

    // NOTE: due to a bug on the APX device, we only have mono sounds,
    // so we currently only pay attention to the position of the left
    // speaker
    public void setRightSpeakerPosition(final float x, final float y, final float z) {
        rightSpeakerPosition.set(x, y, z);
    }

    /** This defines a scale factor of sorts -- the higher the number,
        the larger an area the sound will affect. Default value is
        1.0f. Valid values are [1.0f, ...]. The formula for the gain
        for each channel is
     falloffFactor
  -------------------
  falloffFactor + r^2
*/ public void setFalloffFactor(final float factor) { falloffFactor = factor; } public void shutdown() { synchronized(shutdownLock) { shutdown = true; SetEvent(event); try { shutdownLock.wait(); } catch (final InterruptedException e) { } } } class FillerThread extends Thread { FillerThread() { super("Mixer Thread"); } @Override public void run() { while (!shutdown) { final List curTracks = tracks; for (final Iterator iter = curTracks.iterator(); iter.hasNext(); ) { final Track track = iter.next(); try { track.fill(); } catch (final IOException e) { e.printStackTrace(); remove(track); } } try { // Run ten times per second Thread.sleep(100); } catch (final InterruptedException e) { e.printStackTrace(); } } } } class MixerThread extends Thread { // Temporary mixing buffer // Interleaved left and right channels float[] mixingBuffer; private final Vec3f temp = new Vec3f(); MixerThread() { super("Mixer Thread"); if (!initializeWaveOut(event)) { throw new InternalError("Error initializing waveout device"); } } @Override public void run() { while (!shutdown) { // Get the next buffer final long mixerBuffer = getNextMixerBuffer(); if (mixerBuffer != 0) { ByteBuffer buf = getMixerBufferData(mixerBuffer); if (buf == null) { // This is happening on CVM because // JNI_NewDirectByteBuffer isn't implemented // by default and isn't compatible with the // JSR-239 NIO implementation (apparently) buf = newDirectByteBuffer(getMixerBufferDataAddress(mixerBuffer), getMixerBufferDataCapacity(mixerBuffer)); } if (buf == null) { throw new InternalError("Couldn't wrap the native address with a direct byte buffer"); } // System.out.println("Mixing buffer"); // If we don't have enough samples in our mixing buffer, expand it // FIXME: knowledge of native output rendering format if ((mixingBuffer == null) || (mixingBuffer.length < (buf.capacity() / 2 /* bytes / sample */))) { mixingBuffer = new float[buf.capacity() / 2]; } else { // Zap it for (int i = 0; i < mixingBuffer.length; i++) { mixingBuffer[i] = 0.0f; } } // This assertion should be in place if we have stereo if ((mixingBuffer.length % 2) != 0) { final String msg = "FATAL ERROR: odd number of samples in the mixing buffer"; System.out.println(msg); throw new InternalError(msg); } // Run down all of the registered tracks mixing them in final List curTracks = tracks; for (final Iterator iter = curTracks.iterator(); iter.hasNext(); ) { final Track track = iter.next(); // Consider only playing tracks if (track.isPlaying()) { // First recompute its gain final Vec3f pos = track.getPosition(); final float leftGain = gain(pos, leftSpeakerPosition); final float rightGain = gain(pos, rightSpeakerPosition); // Now mix it in int i = 0; while (i < mixingBuffer.length) { if (track.hasNextSample()) { final float sample = track.nextSample(); mixingBuffer[i++] = sample * leftGain; mixingBuffer[i++] = sample * rightGain; } else { // This allows tracks to stall without being abruptly cancelled if (track.done()) { remove(track); } break; } } } } // Now that we have our data, send it down to the card int outPos = 0; for (int i = 0; i < mixingBuffer.length; i++) { final short val = (short) mixingBuffer[i]; buf.put(outPos++, (byte) val); buf.put(outPos++, (byte) (val >> 8)); } if (!prepareMixerBuffer(mixerBuffer)) { throw new RuntimeException("Error preparing mixer buffer"); } if (!writeMixerBuffer(mixerBuffer)) { throw new RuntimeException("Error writing mixer buffer to device"); } } else { // System.out.println("No mixer buffer available"); // Wait for a buffer to become available if (!WaitForSingleObject(event)) { throw new RuntimeException("Error while waiting for event object"); } /* try { Thread.sleep(10); } catch (InterruptedException e) { } */ } } // Need to shut down shutdownWaveOut(); synchronized(shutdownLock) { shutdownLock.notifyAll(); } } // This defines the 3D spatialization gain function. // The function is defined as: // falloffFactor // ------------------- // falloffFactor + r^2 private float gain(final Vec3f pos, final Vec3f speakerPos) { temp.sub(pos, speakerPos); final float dotp = temp.dot(temp); return (falloffFactor / (falloffFactor + dotp)); } } // Initializes waveout device private static native boolean initializeWaveOut(long eventObject); // Shuts down waveout device private static native void shutdownWaveOut(); // Gets the next (opaque) buffer of data to fill from the native // code, or 0 if none was available yet (it should not happen that // none is available the way the code is written). private static native long getNextMixerBuffer(); // Gets the next ByteBuffer to fill out of the mixer buffer. It // requires interleaved left and right channel samples, 16 signed // bits per sample, little endian. Implicit 44.1 kHz sample rate. private static native ByteBuffer getMixerBufferData(long mixerBuffer); // We need these to work around the lack of // JNI_NewDirectByteBuffer in CVM + the JSR 239 NIO classes private static native long getMixerBufferDataAddress(long mixerBuffer); private static native int getMixerBufferDataCapacity(long mixerBuffer); // Prepares this mixer buffer for writing to the device. private static native boolean prepareMixerBuffer(long mixerBuffer); // Writes this mixer buffer to the device. private static native boolean writeMixerBuffer(long mixerBuffer); // Helpers to prevent mixer thread from busy waiting private static native long CreateEvent(); private static native boolean WaitForSingleObject(long event); private static native void SetEvent(long event); private static native void CloseHandle(long handle); // We need a reflective hack to wrap a direct ByteBuffer around // the native memory because JNI_NewDirectByteBuffer doesn't work // in CVM + JSR-239 NIO private static Class directByteBufferClass; private static Constructor directByteBufferConstructor; private static Map createdBuffers = new HashMap(); // Map Long, ByteBuffer private static ByteBuffer newDirectByteBuffer(final long address, final long capacity) { final Long key = Long.valueOf(address); ByteBuffer buf = (ByteBuffer) createdBuffers.get(key); if (buf == null) { buf = newDirectByteBufferImpl(address, capacity); if (buf != null) { createdBuffers.put(key, buf); } } return buf; } private static ByteBuffer newDirectByteBufferImpl(final long address, final long capacity) { if (directByteBufferClass == null) { try { directByteBufferClass = Class.forName("java.nio.DirectByteBuffer"); final byte[] tmp = new byte[0]; directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(new Class[] { Integer.TYPE, tmp.getClass(), Integer.TYPE }); directByteBufferConstructor.setAccessible(true); } catch (final Exception e) { e.printStackTrace(); } } if (directByteBufferConstructor != null) { try { return (ByteBuffer) directByteBufferConstructor.newInstance(new Object[] { Integer.valueOf((int) capacity), null, Integer.valueOf((int) address) }); } catch (final Exception e) { e.printStackTrace(); } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy