/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions 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 the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.sound;
import java.util.ArrayList;
import java.util.HashMap;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.scenegraph.BackgroundSound;
import org.xith3d.scenegraph.BranchGroup;
import org.xith3d.scenegraph.PointSound;
import org.xith3d.scenegraph.Sound;
import org.xith3d.scenegraph.View;
import org.xith3d.utility.logging.X3DLog;
/**
* A SoundProcessor is responsible for processing Sound Nodes in relation to a View.<br>
* <br>
* Most parts are taken from other classes originally coded by David Yazel.
*
* @author Marvin Froehlich (aka Qudus)
*/
public class SoundProcessor
{
private static final SoundProcessor INSTANCE = new SoundProcessor();
private SoundDriver soundDriver = new org.xith3d.sound.drivers.javasound.SoundDriverImpl();
private final HashMap<BranchGroup, ArrayList<Sound>> soundNodes = new HashMap<BranchGroup, ArrayList<Sound>>();
private long lastFrameId = -1L;
/**
* Returns the soundProcessor's singleton instance.
* @return the soundProcessor's singleton instance.
*/
public static final SoundProcessor getInstance()
{
return ( INSTANCE );
}
/**
* Sets the SoundDriver to be used by this SoundProcessor.
*
* @param soundDriver
*/
public void setSoundDriver( SoundDriver soundDriver )
{
this.soundDriver = soundDriver;
}
/**
* Returns the used SoundDriver (default=javasound).
* @return the used SoundDriver.
*/
public final SoundDriver getSoundDriver()
{
return ( soundDriver );
}
/**
* Adds a Sound to the SoundProcessor.
*
* @param sound the Sound Node to be added
*/
public void addSound( Sound sound, BranchGroup rootBranch )
{
ArrayList<Sound> list = soundNodes.get( rootBranch );
if ( list == null )
{
list = new ArrayList<Sound>();
soundNodes.put( rootBranch, list );
}
list.add( sound );
X3DLog.debug( "A ", sound.getClass().getSimpleName(), " has been added to the SoundProcesor ", this );
}
/**
* Removes a Sound Node from the SoundProcessor.
*
* @param sound the Sound Node to be removed
*
* @return true, if the Sound was presend before removing
*/
public boolean removeSound( Sound sound )
{
for ( ArrayList<Sound> list : soundNodes.values() )
{
if ( list.remove( sound ) )
{
X3DLog.debug( "A ", sound.getClass().getSimpleName(), " has been removed from the SoundProcesor ", this );
return ( true );
}
}
return ( false );
}
/**
* Removes all stored Sound Nodes from the list.
*/
public void clearSoundList()
{
X3DLog.debug( "Sound list have been cleared in the SoundProcesor ", this );
soundNodes.clear();
}
/**
* Processes a Sound Node to let it play.
*
* @param sound the Sound Node to be processed
*/
private void process( Sound sound, Point3f soundLoc, View view, Point3f listenerPosition )
{
if ( ( soundDriver == null ) || !soundDriver.isOnline() )
return;
if ( !sound.isEnabled() && !sound.isPlaying() )
return;
if ( sound.isDisabled() )
return;
final boolean isBackgroundSound = ( sound instanceof BackgroundSound );
// first determine if the sound should be active
boolean setSoundLoc = false;
boolean activate;
if ( !sound.isEnabled() )
{
activate = false;
}
else if ( isBackgroundSound )
{
activate = true;
}
else
{
setSoundLoc = true;
sound.getWorldTransform().get( soundLoc );
float squaredDist = soundLoc.distanceSquared( listenerPosition );
activate = ( squaredDist < ( view.getSoundActivationRadius() * view.getSoundActivationRadius() ) );
}
try
{
SoundSource ss = sound.getSource();
// if the sound is not active then make sure it is deactivated
if ( !activate )
{
if ( ss == null )
return;
if ( ss.isPlaying() )
{
ss.pause();
sound.setDeactivated( true );
}
return;
}
// if the sound should be active then make sure it is
if ( ss == null )
{
SoundContainer sc = sound.getSoundContainer();
// set up the sound source
try
{
ss = soundDriver.allocateSoundSource();
ss.setContainer( sc );
ss.setLoop( sound.isContinuousEnabled() );
if ( isBackgroundSound )
{
ss.setRolloffFactor( 0 );
ss.setRelative( true );
ss.setPosition( 0f, 0f, 0f );
}
else
{
PointSound ps = (PointSound)sound;
float max = ps.getMaxDistance();
if ( max == -1 )
max = view.getSoundActivationRadius();
float ref = ps.getReferenceDistance();
if ( ref == -1 )
ref = 0.17f * max;
ss.setMaxDistance( max * 0.9f );
ss.setReferenceDistance( ref );
ss.setMaxVolume( ps.getMaxVolume() );
ss.setMinVolume( ps.getMinVolume() );
ss.setRolloffFactor( ps.getRolloffFactor() );
ss.setRelative( false );
X3DLog.debug( "max distance = ", ( max * 0.9f ), ", ", "ref = ", ref, ", ", "max vol = ", ps.getMaxVolume(), ", ", "min vol = ", ps.getMinVolume(), ", ", "vol = ", sound.getInitialGain() );
}
ss.setVolume( sound.getInitialGain() );
sound.setSource( ss );
}
catch ( Throwable t )
{
X3DLog.print( t );
t.printStackTrace();
sound.setDisabled( true );
return;
}
}
// if the sound node is not a background node then set the source position
if ( setSoundLoc )
ss.setPosition( soundLoc );
// ok now we have a legitimate sound source. We need to make sure it is
// configured with the current state of the node
if ( sound.isPaused() )
{
if ( sound.isPlaying() )
ss.pause();
}
else if ( sound.isEnabled() && !sound.isPlaying() )
{
if ( sound.wasRestarted() || sound.wasDeactivated() )
{
ss.rewind();
ss.play();
sound.setRestarted( false );
sound.setDeactivated( false );
}
}
else if ( !sound.isEnabled() && sound.isPlaying() )
{
ss.pause();
}
}
catch ( Throwable t )
{
X3DLog.print( t );
t.printStackTrace();
soundDriver = null;
}
}
/**
* Processes a Sound Node to let it play.
*
* @param sound the Sound Node to be processed
*/
/**
* This method is called once per frame to update the
* positional and rotational data needed to process a Sound Node.
*
* @param viewTransform the Transform3D of the current View
*/
private void update( View view, Point3f listenerPosition )
{
if ( ( soundDriver != null ) && soundDriver.isOnline() )
{
Vector3f facingDirection = Vector3f.fromPool();
Vector3f upDirection = Vector3f.fromPool();
soundDriver.setListenerPosition( listenerPosition );
view.getFacingDirection( facingDirection );//.normalize();
view.getUpDirection( upDirection );//.normalize();
soundDriver.setListenerOrientation( facingDirection, upDirection );
Vector3f.toPool( upDirection );
Vector3f.toPool( facingDirection );
}
}
/**
* Process all Sound Nodes, that have been collected from the scenegraph.
*/
public void processAll( BranchGroup rootBranch, View view, long frameId, boolean force )
{
ArrayList<Sound> soundList = soundNodes.get( rootBranch );
if ( soundList == null )
return;
Point3f listenerPosition = Point3f.fromPool();
view.getPosition( listenerPosition );
if ( ( frameId > lastFrameId ) || force )
{
if ( !force )
lastFrameId = frameId;
if ( soundNodes.size() > 0)
update( view, listenerPosition );
}
X3DLog.debug( "We have ", soundNodes.size(), " Sound Nodes to process in the sound processor ", this );
Point3f soundLoc = Point3f.fromPool();
for ( int i = 0; i < soundNodes.size(); i++ )
{
process( soundList.get( i ), soundLoc, view, listenerPosition );
}
Point3f.toPool( soundLoc );
Point3f.toPool( listenerPosition );
}
/**
* Process all Sound Nodes, that have been collected from the scenegraph.
*/
public void processAll( BranchGroup rootBranch, View view, long frameId )
{
processAll( rootBranch, view, frameId, false );
}
/**
* Creates a new SoundProcessor.
*/
private SoundProcessor()
{
// Nothing to do here
}
}