/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Modifications Copyright 2003-2004 Bytonic Software Copyright 2010 Google Inc. */ package com.googlecode.gwtquake.shared.sound; import static com.googlecode.gwtquake.shared.common.Constants.ca_active; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import com.googlecode.gwtquake.shared.client.ClientEntities; import com.googlecode.gwtquake.shared.common.Com; import com.googlecode.gwtquake.shared.common.Constants; import com.googlecode.gwtquake.shared.common.Globals; import com.googlecode.gwtquake.shared.game.EntityState; import com.googlecode.gwtquake.shared.util.Lib; import com.googlecode.gwtquake.shared.util.Math3D; /** * Channel * * @author dsanders/cwei */ public class Channel { final static int LISTENER = 0; final static int FIXED = 1; final static int DYNAMIC = 2; final static int MAX_CHANNELS = 32; private final static FloatBuffer NULLVECTOR = Lib.newFloatBuffer(3); private static Channel[] channels = new Channel[MAX_CHANNELS]; private static IntBuffer sources = Lib.newIntBuffer(MAX_CHANNELS); // a reference of L:WJGLSoundImpl.buffers private static IntBuffer buffers; private static Map looptable = new HashMap(MAX_CHANNELS); private static boolean isInitialized = false; private static int numChannels; // stream handling private static boolean streamingEnabled = false; private static int streamQueue = 0; // sound attributes private int type; private int entnum; private int entchannel; private int bufferId; private int sourceId; private float volume; private float rolloff; private float[] origin = {0, 0, 0}; // update flags private boolean autosound; private boolean active; private boolean modified; private boolean bufferChanged; private boolean volumeChanged; private Channel(int sourceId) { this.sourceId = sourceId; clear(); volumeChanged = false; volume = 1.0f; } private void clear() { entnum = entchannel = bufferId = -1; bufferChanged = false; rolloff = 0; autosound = false; active = false; modified = false; } private static IntBuffer tmp = Lib.newIntBuffer(1); public static int init(IntBuffer buffers) { Channel.buffers = buffers; // create channels int sourceId; for (int i = 0; i < MAX_CHANNELS; i++) { ALAdapter.impl.alGenSources(tmp); sourceId = tmp.get(0); // can't generate more sources if (sourceId <= 0) break; sources.put(i, sourceId); channels[i] = new Channel(sourceId); numChannels++; // set default values for AL sources ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_GAIN, 1.0f); ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_PITCH, 1.0f); ALAdapter.impl.alSourcei (sourceId, ALAdapter.AL_SOURCE_ABSOLUTE, ALAdapter.AL_TRUE); // ALAdapter.impl.nalSourcefv(sourceId, ALAdapter.impl.AL_VELOCITY, NULLVECTOR, 0); // TODO: jgw ALAdapter.impl.alSourcei (sourceId, ALAdapter.AL_LOOPING, ALAdapter.AL_FALSE); ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_REFERENCE_DISTANCE, 200.0f); ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_MIN_GAIN, 0.0005f); ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_MAX_GAIN, 1.0f); } isInitialized = true; return numChannels; } public static void reset() { for (int i = 0; i < numChannels; i++) { ALAdapter.impl.alSourceStop(sources.get(i)); ALAdapter.impl.alSourcei(sources.get(i), ALAdapter.AL_BUFFER, 0); channels[i].clear(); } } public static void shutdown() { ALAdapter.impl.alDeleteSources(sources); numChannels = 0; isInitialized = false; } static void enableStreaming() { if (streamingEnabled) return; // use the last source numChannels--; streamingEnabled = true; streamQueue = 0; int source = channels[numChannels].sourceId; ALAdapter.impl.alSourcei (source, ALAdapter.impl.AL_SOURCE_RELATIVE, ALAdapter.AL_TRUE); ALAdapter.impl.alSourcef(source, ALAdapter.AL_GAIN, 1.0f); channels[numChannels].volumeChanged = true; Com.DPrintf("streaming enabled\n"); } public static void disableStreaming() { if (!streamingEnabled) return; unqueueStreams(); int source = channels[numChannels].sourceId; ALAdapter.impl.alSourcei (source, ALAdapter.AL_SOURCE_ABSOLUTE, ALAdapter.AL_TRUE); // free the last source numChannels++; streamingEnabled = false; Com.DPrintf("streaming disabled\n"); } static void unqueueStreams() { if (!streamingEnabled) return; int source = channels[numChannels].sourceId; // stop streaming ALAdapter.impl.alSourceStop(source); int count = ALAdapter.impl.alGetSourcei(source, ALAdapter.AL_BUFFERS_QUEUED); Com.DPrintf("unqueue " + count + " buffers\n"); while (count-- > 0) { ALAdapter.impl.alSourceUnqueueBuffers(source, tmp); } streamQueue = 0; } public static void updateStream(ByteBuffer samples, int count, int format, int rate) { enableStreaming(); int source = channels[numChannels].sourceId; int processed = ALAdapter.impl.alGetSourcei(source, ALAdapter.AL_BUFFERS_PROCESSED); boolean playing = (ALAdapter.impl.alGetSourcei(source, ALAdapter.AL_SOURCE_STATE) == ALAdapter.AL_PLAYING); boolean interupted = !playing && streamQueue > 2; IntBuffer buffer = tmp; if (interupted) { unqueueStreams(); buffer.put(0, buffers.get(SoundImpl.MAX_SFX + streamQueue++)); Com.DPrintf("queue " + (streamQueue - 1) + '\n'); } else if (processed < 2) { // check queue overrun if (streamQueue >= SoundImpl.STREAM_QUEUE) return; buffer.put(0, buffers.get(SoundImpl.MAX_SFX + streamQueue++)); Com.DPrintf("queue " + (streamQueue - 1) + '\n'); } else { // reuse the buffer ALAdapter.impl.alSourceUnqueueBuffers(source, buffer); } samples.position(0); samples.limit(count); ALAdapter.impl.alBufferData(buffer.get(0), format, samples, rate); ALAdapter.impl.alSourceQueueBuffers(source, buffer); if (streamQueue > 1 && !playing) { Com.DPrintf("start sound\n"); ALAdapter.impl.alSourcePlay(source); } } public static void addPlaySounds() { while (Channel.assign(PlaySound.nextPlayableSound())); } private static boolean assign(PlaySound ps) { if (ps == null) return false; Channel ch = null; int i; for (i = 0; i < numChannels; i++) { ch = channels[i]; if (ps.entchannel != 0 && ch.entnum == ps.entnum && ch.entchannel == ps.entchannel) { // always override sound from same entity if (ch.bufferId != ps.bufferId) { ALAdapter.impl.alSourceStop(ch.sourceId); } break; } // don't let monster sounds override player sounds if ((ch.entnum == Globals.cl.playernum+1) && (ps.entnum != Globals.cl.playernum+1) && ch.bufferId != -1) continue; // looking for a free AL source if (!ch.active) { break; } } if (i == numChannels) return false; ch.type = ps.type; if (ps.type == Channel.FIXED) Math3D.VectorCopy(ps.origin, ch.origin); ch.entnum = ps.entnum; ch.entchannel = ps.entchannel; ch.bufferChanged = (ch.bufferId != ps.bufferId); ch.bufferId = ps.bufferId; ch.rolloff = ps.attenuation * 2; ch.volumeChanged = (ch.volume != ps.volume); ch.volume = ps.volume; ch.active = true; ch.modified = true; return true; } private static Channel pickForLoop(int bufferId, float attenuation) { Channel ch; for (int i = 0; i < numChannels; i++) { ch = channels[i]; // looking for a free AL source if (!ch.active) { ch.entnum = 0; ch.entchannel = 0; ch.bufferChanged = (ch.bufferId != bufferId); ch.bufferId = bufferId; ch.volumeChanged = (ch.volume != 1.0f); ch.volume = 1.0f; ch.rolloff = attenuation * 2; ch.active = true; ch.modified = true; return ch; } } return null; } private static FloatBuffer sourceOriginBuffer = Lib.newFloatBuffer(3); //stack variable private static float[] entityOrigin = {0, 0, 0}; public static void playAllSounds(FloatBuffer listenerOrigin) { FloatBuffer sourceOrigin = sourceOriginBuffer; Channel ch; int sourceId; int state; for (int i = 0; i < numChannels; i++) { ch = channels[i]; if (ch.active) { sourceId = ch.sourceId; switch (ch.type) { case Channel.LISTENER: sourceOrigin.put(0, listenerOrigin.get(0)); sourceOrigin.put(1, listenerOrigin.get(1)); sourceOrigin.put(2, listenerOrigin.get(2)); break; case Channel.DYNAMIC: ClientEntities.GetEntitySoundOrigin(ch.entnum, entityOrigin); convertVector(entityOrigin, sourceOrigin); break; case Channel.FIXED: convertVector(ch.origin, sourceOrigin); break; } if (ch.modified) { if (ch.bufferChanged) { ALAdapter.impl.alSourcei(sourceId, ALAdapter.AL_BUFFER, ch.bufferId); } if (ch.volumeChanged) { ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_GAIN, ch.volume); } ALAdapter.impl.alSourcef (sourceId, ALAdapter.AL_ROLLOFF_FACTOR, ch.rolloff); ALAdapter.impl.alSource(sourceId, ALAdapter.AL_POSITION, sourceOrigin); ALAdapter.impl.alSourcePlay(sourceId); ch.modified = false; } else { state = ALAdapter.impl.alGetSourcei(sourceId, ALAdapter.AL_SOURCE_STATE); if (state == ALAdapter.AL_PLAYING) { ALAdapter.impl.alSource(sourceId, ALAdapter.AL_POSITION, sourceOrigin); } else { ch.clear(); } } ch.autosound = false; } } } /* * adddLoopSounds * Entities with a ->sound field will generated looped sounds * that are automatically started, stopped, and merged together * as the entities are sent to the client */ public static void addLoopSounds() { if ((Globals.cl_paused.value != 0.0f) || (Globals.cls.state != ca_active) || !Globals.cl.sound_prepped) { removeUnusedLoopSounds(); return; } Channel ch; Sfx sfx; SfxCache sc; int num; EntityState ent; Object key; int sound = 0; for (int i=0 ; i < Globals.cl.frame.num_entities ; i++) { num = (Globals.cl.frame.parse_entities + i)&(Constants.MAX_PARSE_ENTITIES-1); ent = Globals.cl_parse_entities[num]; sound = ent.sound; if (sound == 0) continue; key = new Integer(ent.number); ch = (Channel)looptable.get(key); if (ch != null) { // keep on looping ch.autosound = true; Math3D.VectorCopy(ent.origin, ch.origin); continue; } sfx = Globals.cl.sound_precache[sound]; if (sfx == null) continue; // bad sound effect sc = sfx.cache; if (sc == null) continue; // allocate a channel ch = Channel.pickForLoop(buffers.get(sfx.bufferId), 6); if (ch == null) break; ch.type = FIXED; Math3D.VectorCopy(ent.origin, ch.origin); ch.autosound = true; looptable.put(key, ch); ALAdapter.impl.alSourcei(ch.sourceId, ALAdapter.AL_LOOPING, ALAdapter.AL_TRUE); } removeUnusedLoopSounds(); } private static void removeUnusedLoopSounds() { Channel ch; // stop unused loopsounds for (Iterator iter = looptable.values().iterator(); iter.hasNext();) { ch = (Channel)iter.next(); if (!ch.autosound) { ALAdapter.impl.alSourceStop(ch.sourceId); ALAdapter.impl.alSourcei(ch.sourceId, ALAdapter.AL_LOOPING, ALAdapter.AL_FALSE); iter.remove(); ch.clear(); } } } public static void convertVector(float[] from, FloatBuffer to) { to.put(0, from[0]); to.put(1, from[2]); to.put(2, -from[1]); } public static void convertOrientation(float[] forward, float[] up, FloatBuffer orientation) { orientation.put(0, forward[0]); orientation.put(1, forward[2]); orientation.put(2, -forward[1]); orientation.put(3, up[0]); orientation.put(4, up[2]); orientation.put(5, -up[1]); } }