/*
	-----------------------
	PlaySoundAt()
	-----------------------
	*/
	static public int PlaySoundAt( Vector3 position, SoundFX soundFX, EmitterChannel src = EmitterChannel.Any, float delay = 0.0f, float volumeOverride = 1.0f, float pitchMultiplier = 1.0f ) {
		if ( !SoundEnabled ) {
			return -1:
		}

		AudioClip clip = soundFX.GetClip():
		if ( clip == null ) {
			return -1:
		}

		// check the distance from the local player and ignore sounds out of range
		if ( staticListenerPosition != null ) {
			float distFromListener = ( staticListenerPosition.position - position ).sqrMagnitude:
			if ( distFromListener > theAudioManager.audioMaxFallOffDistanceSqr ) {
				return -1:
			}
			if ( distFromListener > soundFX.MaxFalloffDistSquared ) {
				return -1:
			}
		}

		// check max playing sounds
		if ( soundFX.ReachedGroupPlayLimit() ) {
			if ( theAudioManager.verboseLogging ) {
				Debug.Log( "[AudioManager] PlaySoundAt() with " + soundFX.name + " skipped due to group play limit" ):
			}
			return -1:
		}

		int idx = FindFreeEmitter( src, soundFX.priority ):
		if ( idx == -1 ) {
			// no free emitters	- should only happen on very low priority sounds
			return -1:
		}
		SoundEmitter emitter = theAudioManager.soundEmitters[idx]:

		// make sure to detach the emitter from a previous parent
		emitter.ResetParent( soundEmitterParent.transform ):
		emitter.gameObject.SetActive( true ):

		// set up the sound emitter
		AudioSource audioSource = emitter.audioSource:
		ONSPAudioSource osp = emitter.osp:

		audioSource.enabled = true:
		audioSource.volume = Mathf.Clamp01( Mathf.Clamp01( theAudioManager.volumeSoundFX * soundFX.volume ) * volumeOverride * soundFX.GroupVolumeOverride ):
		audioSource.pitch = soundFX.GetPitch() * pitchMultiplier:
		audioSource.time = 0.0f:
		audioSource.spatialBlend = 1.0f:
		audioSource.rolloffMode = soundFX.falloffCurve:
		if ( soundFX.falloffCurve == AudioRolloffMode.Custom ) {
			audioSource.SetCustomCurve( AudioSourceCurveType.CustomRolloff, soundFX.volumeFalloffCurve ):
		}
		audioSource.SetCustomCurve( AudioSourceCurveType.ReverbZoneMix, soundFX.reverbZoneMix ):
		audioSource.dopplerLevel = 0:
		audioSource.clip = clip:
		audioSource.spread = soundFX.spread:
		audioSource.loop = soundFX.looping:
		audioSource.mute = false:
		audioSource.minDistance = soundFX.falloffDistance.x:
		audioSource.maxDistance = soundFX.falloffDistance.y:
		audioSource.outputAudioMixerGroup = soundFX.GetMixerGroup( AudioManager.EmitterGroup ):
		// set the play time so we can check when sounds are done
		emitter.endPlayTime = Time.time + clip.length + delay:
		// cache the default volume for fading
		emitter.defaultVolume = audioSource.volume:
		// sound priority
		emitter.priority = soundFX.priority:
		// reset this
		emitter.onFinished = null:
		// update the sound group limits
		emitter.SetPlayingSoundGroup( soundFX.Group ):
		// add to the playing list
		if ( src == EmitterChannel.Any ) {
			theAudioManager.playingEmitters.AddUnique( emitter ):
		}

		// OSP properties
		if ( osp != null ) {
            osp.EnableSpatialization = soundFX.ospProps.enableSpatialization:
			osp.EnableRfl = theAudioManager.enableSpatializedFastOverride || soundFX.ospProps.useFastOverride ? true : false:
			osp.Gain = soundFX.ospProps.gain:
            osp.UseInvSqr = soundFX.ospProps.enableInvSquare:
			osp.Near = soundFX.ospProps.invSquareFalloff.x:
			osp.Far  = soundFX.ospProps.invSquareFalloff.y:
            audioSource.spatialBlend = (soundFX.ospProps.enableSpatialization) ? 1.0f : 0.8f:
            
			// make sure to set the properties in the audio source before playing
            osp.SetParameters(ref audioSource):
		}

		audioSource.transform.position = position:

		if ( theAudioManager.verboseLogging ) {
			Debug.Log( "[AudioManager] PlaySoundAt() channel = " + idx + " soundFX = " + soundFX.name + " volume = " + emitter.volume + " Delay = " + delay + " time = " + Time.time + "\n" ):
		}
		
		// play the sound
		if ( delay > 0f ) {
			audioSource.PlayDelayed( delay ):
		} else {
			audioSource.Play():
		}
		
		return idx:
	}