aboutsummaryrefslogtreecommitdiffstats
path: root/src/jake2/sound
diff options
context:
space:
mode:
authorHolger Zickner <[email protected]>2004-07-09 06:50:52 +0000
committerHolger Zickner <[email protected]>2004-07-09 06:50:52 +0000
commit20a66a892a3f0704ef37f1eebb681edfee6fc165 (patch)
tree118e0e5ea00eecf450e4c63edc88c421d52a7db2 /src/jake2/sound
parent6b36f9e0380b7c80aecdc78ef07a0cf473712416 (diff)
import of Jake2
Diffstat (limited to 'src/jake2/sound')
-rw-r--r--src/jake2/sound/S.java6
-rw-r--r--src/jake2/sound/WaveLoader.java302
-rw-r--r--src/jake2/sound/joal/Channel.java84
-rw-r--r--src/jake2/sound/joal/JOALSoundImpl.java793
-rw-r--r--src/jake2/sound/jsound/JSoundImpl.java98
-rw-r--r--src/jake2/sound/jsound/SND_DMA.java1197
-rw-r--r--src/jake2/sound/jsound/SND_JAVA.java181
-rw-r--r--src/jake2/sound/jsound/SND_MIX.java491
8 files changed, 3151 insertions, 1 deletions
diff --git a/src/jake2/sound/S.java b/src/jake2/sound/S.java
index c696501..55588b5 100644
--- a/src/jake2/sound/S.java
+++ b/src/jake2/sound/S.java
@@ -2,7 +2,7 @@
* S.java
* Copyright (C) 2003
*
- * $Id: S.java,v 1.1 2004-07-08 20:56:49 hzi Exp $
+ * $Id: S.java,v 1.2 2004-07-09 06:50:48 hzi Exp $
*/
/*
Copyright (C) 1997-2001 Id Software, Inc.
@@ -184,4 +184,8 @@ public class S {
public static void StopAllSounds() {
impl.StopAllSounds();
}
+
+ public static String getDriverName() {
+ return impl.getName();
+ }
}
diff --git a/src/jake2/sound/WaveLoader.java b/src/jake2/sound/WaveLoader.java
new file mode 100644
index 0000000..020ab52
--- /dev/null
+++ b/src/jake2/sound/WaveLoader.java
@@ -0,0 +1,302 @@
+/*
+ * SND_MEM.java
+ * Copyright (C) 2004
+ *
+ * $Id: WaveLoader.java,v 1.1 2004-07-09 06:50:48 hzi Exp $
+ */
+/*
+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.
+
+*/
+package jake2.sound;
+
+import jake2.Defines;
+import jake2.qcommon.Com;
+import jake2.qcommon.FS;
+import jake2.sys.Sys;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import javax.sound.sampled.*;
+
+/**
+ * SND_MEM
+ */
+public class WaveLoader {
+
+ private static final AudioFormat sampleFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 16, 1, 2, 22050, false);
+
+ /*
+ ==============
+ S_LoadSound
+ ==============
+ */
+ public static sfxcache_t LoadSound(sfx_t s) {
+ String namebuffer;
+ byte[] data;
+ wavinfo_t info;
+ int len;
+ float stepscale;
+ sfxcache_t sc = null;
+ int size;
+ String name;
+
+ if (s.name.charAt(0) == '*')
+ return null;
+
+ // see if still in memory
+ sc = s.cache;
+ if (sc != null)
+ return sc;
+
+ // load it in
+ if (s.truename != null)
+ name = s.truename;
+ else
+ name = s.name;
+
+ if (name.charAt(0) == '#')
+ namebuffer = name.substring(1);
+
+ else
+ namebuffer = "sound/" + name;
+
+ data = FS.LoadFile(namebuffer);
+
+ if (data == null) {
+ Com.DPrintf("Couldn't load " + namebuffer + "\n");
+ return null;
+ }
+ size = data.length;
+
+ info = GetWavinfo(s.name, data, size);
+
+ AudioInputStream in = null;
+ AudioInputStream out = null;
+ try {
+ in = AudioSystem.getAudioInputStream(new ByteArrayInputStream(data));
+ if (in.getFormat().getSampleSizeInBits() == 8) {
+ in = convertTo16bit(in);
+ }
+ out = AudioSystem.getAudioInputStream(sampleFormat, in);
+ int l = (int)out.getFrameLength();
+ sc = s.cache = new sfxcache_t(l*2);
+ sc.length = l;
+ int c = out.read(sc.data, 0, l * 2);
+ out.close();
+ in.close();
+ } catch (Exception e) {
+ Com.Printf("Couldn't load " + namebuffer + "\n");
+ return null;
+ }
+
+ sc.loopstart = info.loopstart * ((int)sampleFormat.getSampleRate() / info.rate);
+ sc.speed = (int)sampleFormat.getSampleRate();
+ sc.width = sampleFormat.getSampleSizeInBits() / 8;
+ sc.stereo = 0;
+
+ data = null;
+
+ return sc;
+ }
+
+ static AudioInputStream convertTo16bit(AudioInputStream in) throws IOException {
+ AudioFormat format = in.getFormat();
+ int length = (int)in.getFrameLength();
+ byte[] samples = new byte[2*length];
+
+ for (int i = 0; i < length; i++) {
+ in.read(samples, 2*i+1, 1);
+ samples[2*i+1] -= 128;
+ }
+ in.close();
+
+ AudioFormat newformat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate(), 16, format.getChannels(), 2, format.getFrameRate(), false);
+ return new AudioInputStream(new ByteArrayInputStream(samples), newformat, length);
+ }
+
+ /*
+ ===============================================================================
+
+ WAV loading
+
+ ===============================================================================
+ */
+
+ static byte[] data_b;
+ static int data_p;
+ static int iff_end;
+ static int last_chunk;
+ static int iff_data;
+ static int iff_chunk_len;
+
+
+ static short GetLittleShort() {
+ int val = 0;
+ val = data_b[data_p] & 0xFF;
+ data_p++;
+ val |= ((data_b[data_p] & 0xFF) << 8);
+ data_p++;
+ return (short)val;
+ }
+
+ static int GetLittleLong() {
+ int val = 0;
+ val = data_b[data_p] & 0xFF;
+ data_p++;
+ val |= ((data_b[data_p] & 0xFF) << 8);
+ data_p++;
+ val |= ((data_b[data_p] & 0xFF) << 16);
+ data_p++;
+ val |= ((data_b[data_p] & 0xFF) << 24);
+ data_p++;
+ return val;
+ }
+
+ static void FindNextChunk(String name) {
+ while (true) {
+ data_p = last_chunk;
+
+ if (data_p >= iff_end) { // didn't find the chunk
+ data_p = 0;
+ return;
+ }
+
+ data_p += 4;
+
+ iff_chunk_len = GetLittleLong();
+
+ if (iff_chunk_len < 0) {
+ data_p = 0;
+ return;
+ }
+ if (iff_chunk_len > 1024*1024)
+ Sys.Error("FindNextChunk: length is past the 1 meg sanity limit");
+
+ data_p -= 8;
+ last_chunk = data_p + 8 + ((iff_chunk_len + 1) & ~1);
+ String s = new String(data_b, data_p, 4);
+ if (s.equals(name))
+ return;
+ }
+ }
+
+ static void FindChunk(String name) {
+ last_chunk = iff_data;
+ FindNextChunk(name);
+ }
+
+ /*
+ ============
+ GetWavinfo
+ ============
+ */
+ static wavinfo_t GetWavinfo(String name, byte[] wav, int wavlength) {
+ wavinfo_t info = new wavinfo_t();
+ int i;
+ int format;
+ int samples;
+
+ if (wav == null)
+ return info;
+
+ iff_data = 0;
+ iff_end = wavlength;
+ data_b = wav;
+
+ // find "RIFF" chunk
+ FindChunk("RIFF");
+ String s = new String(data_b, data_p + 8, 4);
+ if (!s.equals("WAVE")) {
+ Com.Printf("Missing RIFF/WAVE chunks\n");
+ return info;
+ }
+
+ // get "fmt " chunk
+ iff_data = data_p + 12;
+ // DumpChunks ();
+
+ FindChunk("fmt ");
+ if (data_p == 0) {
+ Com.Printf("Missing fmt chunk\n");
+ return info;
+ }
+ data_p += 8;
+ format = GetLittleShort();
+ if (format != 1) {
+ Com.Printf("Microsoft PCM format only\n");
+ return info;
+ }
+
+ info.channels = GetLittleShort();
+ info.rate = GetLittleLong();
+ data_p += 4 + 2;
+ info.width = GetLittleShort() / 8;
+
+ // get cue chunk
+ FindChunk("cue ");
+ if (data_p != 0) {
+ data_p += 32;
+ info.loopstart = GetLittleLong();
+ // Com_Printf("loopstart=%d\n", sfx->loopstart);
+
+ // if the next chunk is a LIST chunk, look for a cue length marker
+ FindNextChunk("LIST");
+ if (data_p != 0) {
+ s = new String(data_b, data_p + 28, 4);
+ if (s.equals("MARK")) { // this is not a proper parse, but it works with cooledit...
+ data_p += 24;
+ i = GetLittleLong(); // samples in loop
+ info.samples = info.loopstart + i;
+ // Com_Printf("looped length: %i\n", i);
+ }
+ }
+ } else
+ info.loopstart = -1;
+
+ // find data chunk
+ FindChunk("data");
+ if (data_p == 0) {
+ Com.Printf("Missing data chunk\n");
+ return info;
+ }
+
+ data_p += 4;
+ samples = GetLittleLong() / info.width;
+
+ if (info.samples != 0) {
+ if (samples < info.samples)
+ Com.Error(Defines.ERR_DROP, "Sound " + name + " has a bad loop length");
+ } else
+ info.samples = samples;
+
+ info.dataofs = data_p;
+
+ return info;
+ }
+
+ static class wavinfo_t {
+ int rate;
+ int width;
+ int channels;
+ int loopstart;
+ int samples;
+ int dataofs; // chunk starts this many bytes from file start
+ }
+}
diff --git a/src/jake2/sound/joal/Channel.java b/src/jake2/sound/joal/Channel.java
new file mode 100644
index 0000000..ced496f
--- /dev/null
+++ b/src/jake2/sound/joal/Channel.java
@@ -0,0 +1,84 @@
+/*
+ * Created on Jun 19, 2004
+ *
+ * Copyright (C) 2003
+ *
+ * $Id: Channel.java,v 1.1 2004-07-09 06:50:52 hzi Exp $
+ */
+/*
+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.
+
+*/
+package jake2.sound.joal;
+
+/**
+ * Channel
+ *
+ * @author cwei
+ */
+public class Channel {
+
+ final static int LISTENER = 0;
+ final static int FIXED = 1;
+ final static int DYNAMIC = 2;
+
+ int entnum;
+ int entchannel;
+ int bufferId;
+ float rolloff;
+ boolean autosound = false;
+ int sourceId;
+ boolean active = false;
+ boolean modified = false;
+ boolean bufferChanged = false;
+
+ // sound attributes
+ int type;
+ int entity;
+ float[] origin = {0, 0, 0};
+
+ Channel(int sourceId) {
+ this.sourceId = sourceId;
+ clear();
+ }
+
+ void addListener() {
+ type = LISTENER;
+ }
+
+ void addFixed(float[] origin) {
+ type = FIXED;
+ this.origin = origin;
+ }
+
+ void addDynamic(int entity) {
+ type = DYNAMIC;
+ this.entity = entity;
+ }
+
+ void clear() {
+ entnum = -1;
+ entchannel = -1;
+ bufferId = -1;
+ bufferChanged = false;
+ rolloff = 0;
+ autosound = false;
+ active = false;
+ modified = false;
+ }
+}
diff --git a/src/jake2/sound/joal/JOALSoundImpl.java b/src/jake2/sound/joal/JOALSoundImpl.java
new file mode 100644
index 0000000..8a005ce
--- /dev/null
+++ b/src/jake2/sound/joal/JOALSoundImpl.java
@@ -0,0 +1,793 @@
+/*
+ * JOALSoundImpl.java
+ * Copyright (C) 2004
+ *
+ * $Id: JOALSoundImpl.java,v 1.1 2004-07-09 06:50:52 hzi Exp $
+ */
+package jake2.sound.joal;
+
+
+import jake2.Defines;
+import jake2.Globals;
+import jake2.client.CL;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.sound.*;
+import jake2.util.Math3D;
+import jake2.util.Vargs;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.*;
+
+import net.java.games.joal.*;
+
+/**
+ * JOALSoundImpl
+ */
+public final class JOALSoundImpl implements Sound {
+
+ static {
+ S.register(new JOALSoundImpl());
+ };
+
+ static AL al;
+ static ALC alc;
+
+ cvar_t s_volume;
+
+ private static final int MAX_SFX = Defines.MAX_SOUNDS * 2;
+ private static final int MAX_CHANNELS = 32;
+
+ private int[] buffers = new int[MAX_SFX];
+ private int[] sources = new int[MAX_CHANNELS];
+ private Channel[] channels = null;
+
+ private JOALSoundImpl() {
+ }
+
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#Init()
+ */
+ public boolean Init() {
+
+ try {
+ initOpenAL();
+ al = ALFactory.getAL();
+ checkError();
+ } catch (OpenALException e) {
+ Com.Printf(e.getMessage() + '\n');
+ return false;
+ }
+
+ checkError();
+ al.alGenBuffers(MAX_SFX, buffers);
+ al.alGenSources(MAX_CHANNELS, sources);
+ checkError();
+ s_volume = Cvar.Get("s_volume", "0.7", Defines.CVAR_ARCHIVE);
+ initChannels();
+ al.alDistanceModel(AL.AL_INVERSE_DISTANCE_CLAMPED);
+// al.alDistanceModel(AL.AL_INVERSE_DISTANCE);
+ Cmd.AddCommand("play", new xcommand_t() {
+ public void execute() {
+ Play();
+ }
+ });
+ Cmd.AddCommand("stopsound", new xcommand_t() {
+ public void execute() {
+ StopAllSounds();
+ }
+ });
+ Cmd.AddCommand("soundlist", new xcommand_t() {
+ public void execute() {
+ SoundList();
+ }
+ });
+ Cmd.AddCommand("soundinfo", new xcommand_t() {
+ public void execute() {
+ SoundInfo_f();
+ }
+ });
+
+ num_sfx = 0;
+
+
+ Com.Printf("sound sampling rate: 44100Hz\n");
+
+ StopAllSounds();
+ Com.Printf("------------------------------------\n");
+ return true;
+ }
+
+
+ private void initOpenAL() throws OpenALException {
+ ALFactory.initialize();
+ alc = ALFactory.getALC();
+ String deviceName = null;
+
+ String os = System.getProperty("os.name");
+ if (os.startsWith("Windows")) {
+ deviceName = "DirectSound3D";
+ }
+ ALC.Device device = alc.alcOpenDevice(deviceName);
+ String deviceSpecifier = alc.alcGetString(device, ALC.ALC_DEVICE_SPECIFIER);
+ String defaultSpecifier = alc.alcGetString(device, ALC.ALC_DEFAULT_DEVICE_SPECIFIER);
+
+ Com.Printf(os + " using " + ((deviceName == null) ? defaultSpecifier : deviceName) + '\n');
+
+ ALC.Context context = alc.alcCreateContext(device, new int[] {0});
+ alc.alcMakeContextCurrent(context);
+ // Check for an error.
+ if (alc.alcGetError(device) != ALC.ALC_NO_ERROR) {
+ Com.DPrintf("Error with SoundDevice");
+ }
+ }
+
+ void exitOpenAL() {
+ // Get the current context.
+ ALC.Context curContext = alc.alcGetCurrentContext();
+ // Get the device used by that context.
+ ALC.Device curDevice = alc.alcGetContextsDevice(curContext);
+ // Reset the current context to NULL.
+ alc.alcMakeContextCurrent(null);
+ // Release the context and the device.
+ alc.alcDestroyContext(curContext);
+ alc.alcCloseDevice(curDevice);
+ }
+
+ private void initChannels() {
+
+ // create channels
+ channels = new Channel[MAX_CHANNELS];
+
+ int sourceId;
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ sourceId = sources[i];
+ channels[i] = new Channel(sourceId);
+
+ // set default values for AL sources
+ al.alSourcef (sourceId, AL.AL_GAIN, s_volume.value);
+ al.alSourcef (sourceId, AL.AL_PITCH, 1.0f);
+ al.alSourcei (sourceId, AL.AL_SOURCE_ABSOLUTE, AL.AL_TRUE);
+ al.alSourcefv(sourceId, AL.AL_VELOCITY, NULLVECTOR);
+ al.alSourcei (sourceId, AL.AL_LOOPING, AL.AL_FALSE);
+ al.alSourcef (sourceId, AL.AL_REFERENCE_DISTANCE, 300.0f);
+ al.alSourcef (sourceId, AL.AL_MIN_GAIN, 0.0005f);
+ al.alSourcef (sourceId, AL.AL_MAX_GAIN, 1.0f);
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#RegisterSound(jake2.sound.sfx_t)
+ */
+ private void initBuffer(sfx_t sfx)
+ {
+ if (sfx.cache == null ) {
+ //System.out.println(sfx.name + " " + sfx.cache.length+ " " + sfx.cache.loopstart + " " + sfx.cache.speed + " " + sfx.cache.stereo + " " + sfx.cache.width);
+ return;
+ }
+
+ int format = AL.AL_FORMAT_MONO16;
+ byte[] data = sfx.cache.data;
+ int freq = sfx.cache.speed;
+ int size = data.length;
+
+// if (buffers[sfx.id] != 0)
+// al.alDeleteBuffers(1, new int[] {buffers[sfx.id] });
+//
+// int[] bid = new int[1];
+// al.alBufferData( bid[0], format, data, size, freq);
+// buffers[sfx.id] = bid[0];
+// al.alBufferData( bid[0], format, data, size, freq);
+
+ al.alBufferData( buffers[sfx.id], format, data, size, freq);
+// int error;
+// if ((error = al.alGetError()) != AL.AL_NO_ERROR) {
+// String message;
+// switch(error) {
+// case AL.AL_INVALID_OPERATION: message = "invalid operation"; break;
+// case AL.AL_INVALID_VALUE: message = "invalid value"; break;
+// case AL.AL_INVALID_ENUM: message = "invalid enum"; break;
+// case AL.AL_INVALID_NAME: message = "invalid name"; break;
+// default: message = "" + error;
+// }
+// Com.DPrintf("Error Buffer " + sfx.id + ": " + sfx.name + " (" + size + ") --> " + message + '\n');
+// }
+ }
+
+ private void checkError() {
+ int error;
+ if ((error = al.alGetError()) != AL.AL_NO_ERROR) {
+ String message;
+ switch(error) {
+ case AL.AL_INVALID_OPERATION: message = "invalid operation"; break;
+ case AL.AL_INVALID_VALUE: message = "invalid value"; break;
+ case AL.AL_INVALID_ENUM: message = "invalid enum"; break;
+ case AL.AL_INVALID_NAME: message = "invalid name"; break;
+ default: message = "" + error;
+ }
+ Com.DPrintf("AL Error: " + message +'\n');
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#Shutdown()
+ */
+ public void Shutdown() {
+ StopAllSounds();
+ al.alDeleteSources(sources.length, sources);
+ al.alDeleteBuffers(buffers.length, buffers);
+ exitOpenAL();
+ //ALut.alutExit();
+ Cmd.RemoveCommand("play");
+ Cmd.RemoveCommand("stopsound");
+ Cmd.RemoveCommand("soundlist");
+ Cmd.RemoveCommand("soundinfo");
+
+ // free all sounds
+ for (int i = 0; i < num_sfx; i++) {
+ if (known_sfx[i].name == null)
+ continue;
+ known_sfx[i].clear();
+ }
+ num_sfx = 0;
+ }
+
+ private final static float[] NULLVECTOR = {0, 0, 0};
+ private float[] entityOrigin = {0, 0, 0};
+ private float[] sourceOrigin = {0, 0, 0};
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#StartSound(float[], int, int, jake2.sound.sfx_t, float, float, float)
+ */
+ public void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs) {
+
+ if (sfx == null)
+ return;
+
+ if (sfx.name.charAt(0) == '*')
+ sfx = RegisterSexedSound(Globals.cl_entities[entnum].current, sfx.name);
+
+ if (LoadSound(sfx) == null)
+ return; // can't load sound
+
+ if (attenuation != Defines.ATTN_STATIC)
+ attenuation *= 0.5f;
+
+ Channel ch = pickChannel(entnum, entchannel, buffers[sfx.id], attenuation);
+
+ if (ch == null) return;
+
+ if (entnum == Globals.cl.playernum + 1) {
+ ch.addListener();
+ } else if (origin != null) {
+ ch.addFixed(origin);
+ } else {
+ ch.addDynamic(entnum);
+ }
+ }
+
+ Channel pickChannel(int entnum, int entchannel, int bufferId, float rolloff) {
+
+ Channel ch = null;
+ int state;
+ int i;
+
+ for (i = 0; i < MAX_CHANNELS; i++) {
+ ch = channels[i];
+
+ if (entchannel != 0 && ch.entnum == entnum && ch.entchannel == entchannel) {
+ // always override sound from same entity
+ break;
+ }
+
+ // don't let monster sounds override player sounds
+ if ((ch.entnum == Globals.cl.playernum+1) && (entnum != Globals.cl.playernum+1) && ch.bufferId != -1)
+ continue;
+
+ // looking for a free AL source
+ if (!ch.active) {
+ break;
+ }
+ }
+
+ if (i == MAX_CHANNELS)
+ return null;
+
+ ch.entnum = entnum;
+ ch.entchannel = entchannel;
+ if (ch.bufferId != bufferId) {
+ ch.bufferId = bufferId;
+ ch.bufferChanged = true;
+ }
+ ch.rolloff = rolloff * 2;
+ ch.active = true;
+ ch.modified = true;
+
+ return ch;
+ }
+
+ private float[] listenerOrigin = {0, 0, 0};
+ private float[] listenerOrientation = {0, 0, 0, 0, 0, 0};
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#Update(float[], float[], float[], float[])
+ */
+ public void Update(float[] origin, float[] forward, float[] right, float[] up) {
+
+ convertVector(origin, listenerOrigin);
+ al.alListenerfv(AL.AL_POSITION, listenerOrigin);
+
+ convertOrientation(forward, up, listenerOrientation);
+ al.alListenerfv(AL.AL_ORIENTATION, listenerOrientation);
+
+ AddLoopSounds(origin);
+ playChannels(listenerOrigin);
+ }
+
+ Map looptable = new Hashtable(2 * MAX_CHANNELS);
+
+ /*
+ ==================
+ S_AddLoopSounds
+
+ 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
+ ==================
+ */
+ void AddLoopSounds(float[] listener) {
+
+ if (Globals.cl_paused.value != 0.0f) {
+ removeUnusedLoopSounds();
+ return;
+ }
+
+ if (Globals.cls.state != Globals.ca_active) {
+ removeUnusedLoopSounds();
+ return;
+ }
+
+ if (!Globals.cl.sound_prepped) {
+ removeUnusedLoopSounds();
+ return;
+ }
+
+ Channel ch;
+ sfx_t sfx;
+ sfxcache_t sc;
+ int num;
+ entity_state_t ent;
+ Object key;
+ int sound = 0;
+
+ for (int i=0 ; i<Globals.cl.frame.num_entities ; i++) {
+ num = (Globals.cl.frame.parse_entities + i)&(Defines.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;
+ ch.origin = ent.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 = pickChannel(0, 0, buffers[sfx.id], 6);
+ if (ch == null)
+ break;
+
+ ch.addFixed(ent.origin);
+ ch.autosound = true;
+
+ looptable.put(key, ch);
+ al.alSourcei(ch.sourceId, AL.AL_LOOPING, AL.AL_TRUE);
+ }
+
+ removeUnusedLoopSounds();
+
+ }
+
+ void removeUnusedLoopSounds() {
+ Channel ch;
+ // stop unused loopsounds
+ for (Iterator iter = looptable.values().iterator(); iter.hasNext();) {
+ ch = (Channel)iter.next();
+ if (!ch.autosound) {
+ al.alSourceStop(ch.sourceId);
+ al.alSourcei(ch.sourceId, AL.AL_LOOPING, AL.AL_FALSE);
+ iter.remove();
+ ch.clear();
+ }
+ }
+ }
+
+ void playChannels(float[] listenerOrigin) {
+
+ float[] sourceOrigin = {0, 0, 0};
+ float[] entityOrigin = {0, 0, 0};
+ Channel ch;
+ int sourceId;
+ int state;
+
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ ch = channels[i];
+ if (ch.active) {
+ sourceId = ch.sourceId;
+
+ switch (ch.type) {
+ case Channel.LISTENER:
+ Math3D.VectorCopy(listenerOrigin, sourceOrigin);
+ break;
+ case Channel.DYNAMIC:
+ CL.GetEntitySoundOrigin(ch.entity, entityOrigin);
+ convertVector(entityOrigin, sourceOrigin);
+ break;
+ case Channel.FIXED:
+ convertVector(ch.origin, sourceOrigin);
+ break;
+ }
+
+ if (ch.modified) {
+ if (ch.bufferChanged)
+ al.alSourcei (sourceId, AL.AL_BUFFER, ch.bufferId);
+
+ al.alSourcef (sourceId, AL.AL_GAIN, s_volume.value);
+ al.alSourcef (sourceId, AL.AL_ROLLOFF_FACTOR, ch.rolloff);
+ al.alSourcefv(sourceId, AL.AL_POSITION, sourceOrigin);
+ al.alSourcePlay(sourceId);
+ ch.modified = false;
+ } else {
+ state = al.alGetSourcei(ch.sourceId, AL.AL_SOURCE_STATE);
+ if (state == AL.AL_PLAYING) {
+ al.alSourcefv(sourceId, AL.AL_POSITION, sourceOrigin);
+ } else {
+ ch.clear();
+ }
+ }
+ ch.autosound = false;
+ }
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#StopAllSounds()
+ */
+ public void StopAllSounds() {
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ al.alSourceStop(sources[i]);
+ al.alSourcei(sources[i], AL.AL_BUFFER, 0);
+ channels[i].clear();
+ }
+ }
+
+ static void convertVector(float[] from, float[] to) {
+ to[0] = from[0];
+ to[1] = from[2];
+ to[2] = -from[1];
+ }
+
+ static void convertOrientation(float[] forward, float[] up, float[] orientation) {
+ orientation[0] = forward[0];
+ orientation[1] = forward[2];
+ orientation[2] = -forward[1];
+ orientation[3] = up[0];
+ orientation[4] = up[2];
+ orientation[5] = -up[1];
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#getName()
+ */
+ public String getName() {
+ return "joal";
+ }
+
+
+ int s_registration_sequence;
+ boolean s_registering;
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#BeginRegistration()
+ */
+ public void BeginRegistration() {
+ s_registration_sequence++;
+ s_registering = true;
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#RegisterSound(java.lang.String)
+ */
+ public sfx_t RegisterSound(String name) {
+ sfx_t sfx = FindName(name, true);
+ sfx.registration_sequence = s_registration_sequence;
+
+ if (!s_registering)
+ LoadSound(sfx);
+
+ return sfx;
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#EndRegistration()
+ */
+ public void EndRegistration() {
+ int i;
+ sfx_t sfx;
+ int size;
+
+ // free any sounds not from this registration sequence
+ for (i = 0; i < num_sfx; i++) {
+ sfx = known_sfx[i];
+ if (sfx.name == null)
+ continue;
+ if (sfx.registration_sequence != s_registration_sequence) {
+ // don't need this sound
+ sfx.clear();
+ }
+ }
+
+ // load everything in
+ for (i = 0; i < num_sfx; i++) {
+ sfx = known_sfx[i];
+ if (sfx.name == null)
+ continue;
+ LoadSound(sfx);
+ }
+
+ s_registering = false;
+ }
+
+ sfx_t RegisterSexedSound(entity_state_t ent, String base) {
+
+ sfx_t sfx = null;
+
+ // determine what model the client is using
+ String model = "male";
+ int n = Globals.CS_PLAYERSKINS + ent.number - 1;
+ if (Globals.cl.configstrings[n] != null) {
+ int p = Globals.cl.configstrings[n].indexOf('\\');
+ if (p >= 0) {
+ p++;
+ model = Globals.cl.configstrings[n].substring(p);
+ //strcpy(model, p);
+ p = model.indexOf('/');
+ if (p > 0)
+ model = model.substring(0, p);
+ }
+ }
+ // if we can't figure it out, they're male
+ if (model == null || model.length() == 0)
+ model = "male";
+
+ // see if we already know of the model specific sound
+ String sexedFilename = "#players/" + model + "/" + base.substring(1);
+ //Com_sprintf (sexedFilename, sizeof(sexedFilename), "#players/%s/%s", model, base+1);
+ sfx = FindName(sexedFilename, false);
+
+ if (sfx == null) {
+ // no, so see if it exists
+ RandomAccessFile f = null;
+ try {
+ f = FS.FOpenFile(sexedFilename.substring(1));
+ } catch (IOException e) {}
+ if (f != null) {
+ // yes, close the file and register it
+ try {
+ FS.FCloseFile(f);
+ } catch (IOException e1) {}
+ sfx = RegisterSound(sexedFilename);
+ } else {
+ // no, revert to the male sound in the pak0.pak
+ //Com_sprintf (maleFilename, sizeof(maleFilename), "player/%s/%s", "male", base+1);
+ String maleFilename = "player/male/" + base.substring(1);
+ sfx = AliasName(sexedFilename, maleFilename);
+ }
+ }
+
+ //System.out.println(sfx.name);
+ return sfx;
+ }
+
+
+ static sfx_t[] known_sfx = new sfx_t[MAX_SFX];
+ static {
+ for (int i = 0; i< known_sfx.length; i++)
+ known_sfx[i] = new sfx_t();
+ }
+ static int num_sfx;
+
+ sfx_t FindName(String name, boolean create) {
+ int i;
+ sfx_t sfx = null;
+
+ if (name == null)
+ Com.Error(Defines.ERR_FATAL, "S_FindName: NULL\n");
+ if (name.length() == 0)
+ Com.Error(Defines.ERR_FATAL, "S_FindName: empty name\n");
+
+ if (name.length() >= Defines.MAX_QPATH)
+ Com.Error(Defines.ERR_FATAL, "Sound name too long: " + name);
+
+ // see if already loaded
+ for (i = 0; i < num_sfx; i++)
+ if (name.equals(known_sfx[i].name)) {
+ return known_sfx[i];
+ }
+
+ if (!create)
+ return null;
+
+ // find a free sfx
+ for (i = 0; i < num_sfx; i++)
+ if (known_sfx[i].name == null)
+ // registration_sequence < s_registration_sequence)
+ break;
+
+ if (i == num_sfx) {
+ if (num_sfx == MAX_SFX)
+ Com.Error(Defines.ERR_FATAL, "S_FindName: out of sfx_t");
+ num_sfx++;
+ }
+
+ sfx = known_sfx[i];
+ sfx.clear();
+ sfx.name = name;
+ sfx.registration_sequence = s_registration_sequence;
+ // cwei
+ sfx.id = i;
+
+ return sfx;
+ }
+
+ /*
+ ==================
+ S_AliasName
+
+ ==================
+ */
+ sfx_t AliasName(String aliasname, String truename)
+ {
+ sfx_t sfx = null;
+ String s;
+ int i;
+
+ s = new String(truename);
+
+ // find a free sfx
+ for (i=0 ; i < num_sfx ; i++)
+ if (known_sfx[i].name == null)
+ break;
+
+ if (i == num_sfx)
+ {
+ if (num_sfx == MAX_SFX)
+ Com.Error(Defines.ERR_FATAL, "S_FindName: out of sfx_t");
+ num_sfx++;
+ }
+
+ sfx = known_sfx[i];
+ sfx.clear();
+ sfx.name = new String(aliasname);
+ sfx.registration_sequence = s_registration_sequence;
+ sfx.truename = s;
+ // cwei
+ sfx.id = i;
+
+ return sfx;
+ }
+
+ /*
+ ==============
+ S_LoadSound
+ ==============
+ */
+ public sfxcache_t LoadSound(sfx_t s) {
+ sfxcache_t sc = WaveLoader.LoadSound(s);
+ initBuffer(s);
+ return sc;
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#StartLocalSound(java.lang.String)
+ */
+ public void StartLocalSound(String sound) {
+ sfx_t sfx;
+
+ sfx = RegisterSound(sound);
+ if (sfx == null) {
+ Com.Printf("S_StartLocalSound: can't cache " + sound + "\n");
+ return;
+ }
+ StartSound(null, Globals.cl.playernum + 1, 0, sfx, 1, 1, 0);
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#RawSamples(int, int, int, int, byte[])
+ */
+ public void RawSamples(int samples, int rate, int width, int channels, byte[] data) {
+ // TODO implement RawSamples
+ }
+
+ /*
+ ===============================================================================
+
+ console functions
+
+ ===============================================================================
+ */
+
+ void Play() {
+ int i;
+ String name;
+ sfx_t sfx;
+
+ i = 1;
+ while (i < Cmd.Argc()) {
+ name = new String(Cmd.Argv(i));
+ if (name.indexOf('.') == -1)
+ name += ".wav";
+
+ sfx = RegisterSound(name);
+ StartSound(null, Globals.cl.playernum + 1, 0, sfx, 1.0f, 1.0f, 0.0f);
+ i++;
+ }
+ }
+
+ void SoundList() {
+ int i;
+ sfx_t sfx;
+ sfxcache_t sc;
+ int size, total;
+
+ total = 0;
+ for (i = 0; i < num_sfx; i++) {
+ sfx = known_sfx[i];
+ if (sfx.registration_sequence == 0)
+ continue;
+ sc = sfx.cache;
+ if (sc != null) {
+ size = sc.length * sc.width * (sc.stereo + 1);
+ total += size;
+ if (sc.loopstart >= 0)
+ Com.Printf("L");
+ else
+ Com.Printf(" ");
+ Com.Printf("(%2db) %6i : %s\n", new Vargs(3).add(sc.width * 8).add(size).add(sfx.name));
+ } else {
+ if (sfx.name.charAt(0) == '*')
+ Com.Printf(" placeholder : " + sfx.name + "\n");
+ else
+ Com.Printf(" not loaded : " + sfx.name + "\n");
+ }
+ }
+ Com.Printf("Total resident: " + total + "\n");
+ }
+
+ void SoundInfo_f() {
+
+ Com.Printf("%5d stereo\n", new Vargs(1).add(1));
+ Com.Printf("%5d samples\n", new Vargs(1).add(22050));
+ Com.Printf("%5d samplebits\n", new Vargs(1).add(16));
+ Com.Printf("%5d speed\n", new Vargs(1).add(44100));
+ }
+
+}
diff --git a/src/jake2/sound/jsound/JSoundImpl.java b/src/jake2/sound/jsound/JSoundImpl.java
new file mode 100644
index 0000000..3a3cc46
--- /dev/null
+++ b/src/jake2/sound/jsound/JSoundImpl.java
@@ -0,0 +1,98 @@
+/*
+ * JSoundImpl.java
+ * Copyright (C) 2004
+ *
+ * $Id: JSoundImpl.java,v 1.1 2004-07-09 06:50:48 hzi Exp $
+ */
+package jake2.sound.jsound;
+
+import jake2.sound.*;
+import jake2.sound.Sound;
+import jake2.sound.sfx_t;
+
+/**
+ * JSoundImpl
+ */
+public class JSoundImpl implements Sound {
+
+ static {
+ S.register(new JSoundImpl());
+ };
+
+ public boolean Init() {
+ SND_DMA.Init();
+ if (SND_DMA.sound_started) return true;
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#Shutdown()
+ */
+ public void Shutdown() {
+ SND_DMA.Shutdown();
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#StartSound(float[], int, int, jake2.sound.sfx_t, float, float, float)
+ */
+ public void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs) {
+ SND_DMA.StartSound(origin, entnum, entchannel, sfx, fvol, attenuation, timeofs);
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#StopAllSounds()
+ */
+ public void StopAllSounds() {
+ SND_DMA.StopAllSounds();
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.SoundImpl#Update(float[], float[], float[], float[])
+ */
+ public void Update(float[] origin, float[] forward, float[] right, float[] up) {
+ SND_DMA.Update(origin, forward, right, up);
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#getName()
+ */
+ public String getName() {
+ return "jsound";
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#BeginRegistration()
+ */
+ public void BeginRegistration() {
+ SND_DMA.BeginRegistration();
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#RegisterSound(java.lang.String)
+ */
+ public sfx_t RegisterSound(String sample) {
+ return SND_DMA.RegisterSound(sample);
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#EndRegistration()
+ */
+ public void EndRegistration() {
+ SND_DMA.EndRegistration();
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#StartLocalSound(java.lang.String)
+ */
+ public void StartLocalSound(String sound) {
+ SND_DMA.StartLocalSound(sound);
+ }
+
+ /* (non-Javadoc)
+ * @see jake2.sound.Sound#RawSamples(int, int, int, int, byte[])
+ */
+ public void RawSamples(int samples, int rate, int width, int channels, byte[] data) {
+ SND_DMA.RawSamples(samples, rate, width, channels, data);
+ }
+
+}
diff --git a/src/jake2/sound/jsound/SND_DMA.java b/src/jake2/sound/jsound/SND_DMA.java
new file mode 100644
index 0000000..9fe7930
--- /dev/null
+++ b/src/jake2/sound/jsound/SND_DMA.java
@@ -0,0 +1,1197 @@
+/*
+ * S_DMA.java
+ * Copyright (C) 2004
+ *
+ * $Id: SND_DMA.java,v 1.1 2004-07-09 06:50:48 hzi Exp $
+ */
+/*
+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.
+
+*/
+
+// Created on 26.01.2004 by RST.
+
+package jake2.sound.jsound;
+
+import jake2.Defines;
+import jake2.client.CL;
+import jake2.game.*;
+import jake2.qcommon.*;
+import jake2.sound.*;
+import jake2.util.Vargs;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+
+/**
+ * SND_DMA
+ * TODO implement sound system
+ */
+public class SND_DMA extends SND_MIX {
+
+//// =======================================================================
+//// Internal sound data & structures
+//// =======================================================================
+//
+//// only begin attenuating sound volumes when outside the FULLVOLUME range
+ static final int SOUND_FULLVOLUME = 80;
+ static final float SOUND_LOOPATTENUATE = 0.003f;
+ static int s_registration_sequence;
+
+ static boolean sound_started = false;
+
+ static float[] listener_origin = {0, 0, 0};
+ static float[] listener_forward = {0, 0, 0};
+ static float[] listener_right = {0, 0, 0};
+ static float[] listener_up = {0, 0, 0};
+
+ static boolean s_registering;
+
+ static int soundtime; // sample PAIRS
+
+ // during registration it is possible to have more sounds
+ // than could actually be referenced during gameplay,
+ // because we don't want to free anything until we are
+ // sure we won't need it.
+ static final int MAX_SFX = (MAX_SOUNDS*2);
+ static sfx_t[] known_sfx = new sfx_t[MAX_SFX];
+ static {
+ for (int i = 0; i< known_sfx.length; i++)
+ known_sfx[i] = new sfx_t();
+ }
+ static int num_sfx;
+
+ static final int MAX_PLAYSOUNDS = 128;
+ static playsound_t[] s_playsounds = new playsound_t[MAX_PLAYSOUNDS];
+ static {
+ for( int i = 0; i < MAX_PLAYSOUNDS; i++) {
+ s_playsounds[i] = new playsound_t();
+ }
+ }
+ static playsound_t s_freeplays = new playsound_t();
+
+ static int s_beginofs;
+
+ static cvar_t s_testsound;
+ static cvar_t s_loadas8bit;
+ static cvar_t s_khz;
+ static cvar_t s_show;
+ static cvar_t s_mixahead;
+ static cvar_t s_primary;
+//
+//
+// int s_rawend;
+// portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
+//
+//
+// ====================================================================
+// User-setable variables
+// ====================================================================
+
+
+ static void SoundInfo_f() {
+ if (!sound_started) {
+ Com.Printf("sound system not started\n");
+ return;
+ }
+
+ Com.Printf("%5d stereo\n", new Vargs(1).add(dma.channels - 1));
+ Com.Printf("%5d samples\n", new Vargs(1).add(dma.samples));
+ //Com.Printf("%5d samplepos\n", new Vargs(1).add(dma.samplepos));
+ Com.Printf("%5d samplebits\n", new Vargs(1).add(dma.samplebits));
+ Com.Printf("%5d submission_chunk\n", new Vargs(1).add(dma.submission_chunk));
+ Com.Printf("%5d speed\n", new Vargs(1).add(dma.speed));
+ }
+
+ /*
+ ================
+ S_Init
+ ================
+ */
+ public static void Init() {
+ cvar_t cv;
+
+ Com.Printf("\n------- sound initialization -------\n");
+
+ cv = Cvar.Get("s_initsound", "0", 0);
+ if (cv.value == 0.0f)
+ Com.Printf("not initializing.\n");
+ else {
+ s_volume = Cvar.Get("s_volume", "0.7", CVAR_ARCHIVE);
+ s_khz = Cvar.Get("s_khz", "11", CVAR_ARCHIVE);
+ s_loadas8bit = Cvar.Get("s_loadas8bit", "1", CVAR_ARCHIVE);
+ s_mixahead = Cvar.Get("s_mixahead", "0.2", CVAR_ARCHIVE);
+ s_show = Cvar.Get("s_show", "0", 0);
+ s_testsound = Cvar.Get("s_testsound", "0", 0);
+ s_primary = Cvar.Get("s_primary", "0", CVAR_ARCHIVE); // win32 specific
+
+ Cmd.AddCommand("play", new xcommand_t() {
+ public void execute() {
+ Play();
+ }
+ });
+ Cmd.AddCommand("stopsound", new xcommand_t() {
+ public void execute() {
+ StopAllSounds();
+ }
+ });
+ Cmd.AddCommand("soundlist", new xcommand_t() {
+ public void execute() {
+ SoundList();
+ }
+ });
+ Cmd.AddCommand("soundinfo", new xcommand_t() {
+ public void execute() {
+ SoundInfo_f();
+ }
+ });
+
+ if (!SNDDMA_Init())
+ return;
+
+ InitScaletable();
+
+ sound_started = true;
+ num_sfx = 0;
+
+ soundtime = 0;
+ paintedtime = 0;
+
+ Com.Printf("sound sampling rate: " + dma.speed + "\n");
+
+ StopAllSounds();
+ }
+ Com.Printf("------------------------------------\n");
+ }
+
+
+// =======================================================================
+// Shutdown sound engine
+// =======================================================================
+
+ public static void Shutdown() {
+ int i;
+ sfx_t[] sfx;
+
+ if (!sound_started)
+ return;
+
+ SNDDMA_Shutdown();
+
+ sound_started = false;
+
+ Cmd.RemoveCommand("play");
+ Cmd.RemoveCommand("stopsound");
+ Cmd.RemoveCommand("soundlist");
+ Cmd.RemoveCommand("soundinfo");
+
+ // free all sounds
+ for (i = 0, sfx = known_sfx; i < num_sfx; i++) {
+ if (sfx[i].name == null)
+ continue;
+
+ //memset (sfx, 0, sizeof(*sfx));
+ sfx[i].clear();
+ }
+
+ num_sfx = 0;
+ }
+
+// =======================================================================
+// Load a sound
+// =======================================================================
+
+ /*
+ ==================
+ S_FindName
+
+ ==================
+ */
+ static sfx_t FindName(String name, boolean create) {
+ int i;
+ sfx_t sfx = null;
+
+ if (name == null)
+ Com.Error(ERR_FATAL, "S_FindName: NULL\n");
+ if (name.length() == 0)
+ Com.Error(ERR_FATAL, "S_FindName: empty name\n");
+
+ if (name.length() >= MAX_QPATH)
+ Com.Error(ERR_FATAL, "Sound name too long: " + name);
+
+ // see if already loaded
+ for (i = 0; i < num_sfx; i++)
+ if (name.equals(known_sfx[i].name)) {
+ return known_sfx[i];
+ }
+
+ if (!create)
+ return null;
+
+ // find a free sfx
+ for (i = 0; i < num_sfx; i++)
+ if (known_sfx[i].name == null)
+ // registration_sequence < s_registration_sequence)
+ break;
+
+ if (i == num_sfx) {
+ if (num_sfx == MAX_SFX)
+ Com.Error(ERR_FATAL, "S_FindName: out of sfx_t");
+ num_sfx++;
+ }
+
+ sfx = known_sfx[i];
+ //memset (sfx, 0, sizeof(*sfx));
+ sfx.clear();
+ sfx.name = name;
+ sfx.registration_sequence = s_registration_sequence;
+
+ return sfx;
+ }
+
+ /*
+ ==================
+ S_AliasName
+
+ ==================
+ */
+ static sfx_t AliasName(String aliasname, String truename)
+ {
+ sfx_t sfx = null;
+// char *s;
+ int i;
+
+// s = Z_Malloc (MAX_QPATH);
+// strcpy (s, truename);
+
+ // find a free sfx
+ for (i=0 ; i < num_sfx ; i++)
+ if (known_sfx[i].name == null)
+ break;
+
+ if (i == num_sfx)
+ {
+ if (num_sfx == MAX_SFX)
+ Com.Error(ERR_FATAL, "S_FindName: out of sfx_t");
+ num_sfx++;
+ }
+
+ sfx = known_sfx[i];
+ //memset (sfx, 0, sizeof(*sfx));
+ //strcpy (sfx->name, aliasname);
+ sfx.name = aliasname;
+ sfx.registration_sequence = s_registration_sequence;
+ sfx.truename = truename;
+
+ return sfx;
+ }
+
+
+ /*
+ =====================
+ S_BeginRegistration
+
+ =====================
+ */
+ public static void BeginRegistration() {
+ s_registration_sequence++;
+ s_registering = true;
+ }
+
+ /*
+ ==================
+ S_RegisterSound
+
+ ==================
+ */
+ public static sfx_t RegisterSound(String name) {
+ sfx_t sfx = null;
+
+ if (!sound_started)
+ return null;
+
+ sfx = FindName(name, true);
+ sfx.registration_sequence = s_registration_sequence;
+
+ if (!s_registering)
+ WaveLoader.LoadSound(sfx);
+
+ return sfx;
+ }
+
+
+ /*
+ =====================
+ S_EndRegistration
+
+ =====================
+ */
+ public static void EndRegistration() {
+ int i;
+ sfx_t sfx;
+ int size;
+
+ // free any sounds not from this registration sequence
+ for (i = 0; i < num_sfx; i++) {
+ sfx = known_sfx[i];
+ if (sfx.name == null)
+ continue;
+ if (sfx.registration_sequence != s_registration_sequence) { // don't need this sound
+ //memset (sfx, 0, sizeof(*sfx));
+ sfx.clear();
+ } else {
+ // make sure it is paged in
+ // if (sfx->cache)
+ // {
+ // size = sfx->cache->length*sfx->cache->width;
+ // Com_PageInMemory ((byte *)sfx->cache, size);
+ // }
+ }
+
+ }
+
+ // load everything in
+ for (i = 0; i < num_sfx; i++) {
+ sfx = known_sfx[i];
+ if (sfx.name == null)
+ continue;
+ WaveLoader.LoadSound(sfx);
+ }
+
+ s_registering = false;
+ }
+
+
+// =============================================================================
+
+ /*
+ =================
+ S_PickChannel
+ =================
+ */
+ static channel_t PickChannel(int entnum, int entchannel)
+ {
+ int ch_idx;
+ int first_to_die;
+ int life_left;
+ channel_t ch;
+
+ if (entchannel<0)
+ Com.Error(ERR_DROP, "S_PickChannel: entchannel<0");
+
+ // Check for replacement sound, or find the best one to replace
+ first_to_die = -1;
+ life_left = 0x7fffffff;
+ for (ch_idx=0 ; ch_idx < MAX_CHANNELS ; ch_idx++)
+ {
+ if (entchannel != 0 // channel 0 never overrides
+ && channels[ch_idx].entnum == entnum
+ && channels[ch_idx].entchannel == entchannel)
+ { // always override sound from same entity
+ first_to_die = ch_idx;
+ break;
+ }
+
+ // don't let monster sounds override player sounds
+ if ((channels[ch_idx].entnum == cl.playernum+1) && (entnum != cl.playernum+1) && channels[ch_idx].sfx != null)
+ continue;
+
+ if (channels[ch_idx].end - paintedtime < life_left)
+ {
+ life_left = channels[ch_idx].end - paintedtime;
+ first_to_die = ch_idx;
+ }
+ }
+
+ if (first_to_die == -1)
+ return null;
+
+ ch = channels[first_to_die];
+ //memset (ch, 0, sizeof(*ch));
+ ch.clear();
+
+ return ch;
+ }
+
+ /*
+ =================
+ S_SpatializeOrigin
+
+ Used for spatializing channels and autosounds
+ =================
+ */
+ static void SpatializeOrigin(float[] origin, float master_vol, float dist_mult, channel_t ch)
+ {
+ float dot;
+ float dist;
+ float lscale, rscale, scale;
+ float[] source_vec = {0, 0, 0};
+
+ if (cls.state != ca_active)
+ {
+ ch.leftvol = ch.rightvol = 255;
+ return;
+ }
+
+// calculate stereo seperation and distance attenuation
+ VectorSubtract(origin, listener_origin, source_vec);
+
+ dist = VectorNormalize(source_vec);
+ dist -= SOUND_FULLVOLUME;
+ if (dist < 0)
+ dist = 0; // close enough to be at full volume
+ dist *= dist_mult; // different attenuation levels
+
+ dot = DotProduct(listener_right, source_vec);
+
+ if (dma.channels == 1 || dist_mult == 0.0f)
+ { // no attenuation = no spatialization
+ rscale = 1.0f;
+ lscale = 1.0f;
+ }
+ else
+ {
+ rscale = 0.5f * (1.0f + dot);
+ lscale = 0.5f * (1.0f - dot);
+ }
+
+ // add in distance effect
+ scale = (1.0f - dist) * rscale;
+ ch.rightvol = (int) (master_vol * scale);
+ if (ch.rightvol < 0)
+ ch.rightvol = 0;
+
+ scale = (1.0f - dist) * lscale;
+ ch.leftvol = (int) (master_vol * scale);
+ if (ch.leftvol < 0)
+ ch.leftvol = 0;
+ }
+
+ /*
+ =================
+ S_Spatialize
+ =================
+ */
+ static void Spatialize(channel_t ch)
+ {
+ float[] origin = {0, 0, 0};
+
+ // anything coming from the view entity will always be full volume
+ if (ch.entnum == cl.playernum+1)
+ {
+ ch.leftvol = ch.master_vol;
+ ch.rightvol = ch.master_vol;
+ return;
+ }
+
+ if (ch.fixed_origin)
+ {
+ VectorCopy(ch.origin, origin);
+ }
+ else
+ CL.GetEntitySoundOrigin(ch.entnum, origin);
+
+ SpatializeOrigin(origin, (float)ch.master_vol, ch.dist_mult, ch);
+ }
+
+ /*
+ =================
+ S_AllocPlaysound
+ =================
+ */
+ static playsound_t AllocPlaysound ()
+ {
+ playsound_t ps;
+
+ ps = s_freeplays.next;
+ if (ps == s_freeplays)
+ return null; // no free playsounds
+
+ // unlink from freelist
+ ps.prev.next = ps.next;
+ ps.next.prev = ps.prev;
+
+ return ps;
+ }
+
+
+ /*
+ =================
+ S_FreePlaysound
+ =================
+ */
+ static void FreePlaysound(playsound_t ps)
+ {
+ // unlink from channel
+ ps.prev.next = ps.next;
+ ps.next.prev = ps.prev;
+
+ // add to free list
+ ps.next = s_freeplays.next;
+ s_freeplays.next.prev = ps;
+ ps.prev = s_freeplays;
+ s_freeplays.next = ps;
+ }
+
+ /*
+ ===============
+ S_IssuePlaysound
+
+ Take the next playsound and begin it on the channel
+ This is never called directly by S_Play*, but only
+ by the update loop.
+ ===============
+ */
+ static void IssuePlaysound (playsound_t ps)
+ {
+ channel_t ch;
+ sfxcache_t sc;
+
+ if (s_show.value != 0.0f)
+ Com.Printf("Issue " + ps.begin + "\n");
+ // pick a channel to play on
+ ch = PickChannel(ps.entnum, ps.entchannel);
+ if (ch == null)
+ {
+ FreePlaysound(ps);
+ return;
+ }
+
+ // spatialize
+ if (ps.attenuation == ATTN_STATIC)
+ ch.dist_mult = ps.attenuation * 0.001f;
+ else
+ ch.dist_mult = ps.attenuation * 0.0005f;
+ ch.master_vol = (int)ps.volume;
+ ch.entnum = ps.entnum;
+ ch.entchannel = ps.entchannel;
+ ch.sfx = ps.sfx;
+ VectorCopy (ps.origin, ch.origin);
+ ch.fixed_origin = ps.fixed_origin;
+
+ Spatialize(ch);
+
+ ch.pos = 0;
+ sc = WaveLoader.LoadSound(ch.sfx);
+ ch.end = paintedtime + sc.length;
+
+ // free the playsound
+ FreePlaysound(ps);
+ }
+
+ static sfx_t RegisterSexedSound(entity_state_t ent, String base) {
+ sfx_t sfx = null;
+
+ // determine what model the client is using
+ String model = "male";
+ int n = CS_PLAYERSKINS + ent.number - 1;
+ if (cl.configstrings[n] != null) {
+ int p = cl.configstrings[n].indexOf('\\');
+ if (p >= 0) {
+ p++;
+ model = cl.configstrings[n].substring(p);
+ //strcpy(model, p);
+ p = model.indexOf('/');
+ if (p > 0)
+ model = model.substring(0, p - 1);
+ }
+ }
+ // if we can't figure it out, they're male
+ if (model == null || model.length() == 0)
+ model = "male";
+
+ // see if we already know of the model specific sound
+ String sexedFilename = "#players/" + model + "/" + base.substring(1);
+ //Com_sprintf (sexedFilename, sizeof(sexedFilename), "#players/%s/%s", model, base+1);
+ sfx = FindName(sexedFilename, false);
+
+ if (sfx == null) {
+ // no, so see if it exists
+ RandomAccessFile f = null;
+ try {
+ f = FS.FOpenFile(sexedFilename.substring(1));
+ } catch (IOException e) {}
+ if (f != null) {
+ // yes, close the file and register it
+ try {
+ FS.FCloseFile(f);
+ } catch (IOException e1) {}
+ sfx = RegisterSound(sexedFilename);
+ } else {
+ // no, revert to the male sound in the pak0.pak
+ //Com_sprintf (maleFilename, sizeof(maleFilename), "player/%s/%s", "male", base+1);
+ String maleFilename = "player/male/" + base.substring(1);
+ sfx = AliasName(sexedFilename, maleFilename);
+ }
+ }
+
+ return sfx;
+ }
+
+
+// =======================================================================
+// Start a sound effect
+// =======================================================================
+
+ /*
+ ====================
+ S_StartSound
+
+ Validates the parms and ques the sound up
+ if pos is NULL, the sound will be dynamically sourced from the entity
+ Entchannel 0 will never override a playing sound
+ ====================
+ */
+ public static void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs) {
+
+ if (!sound_started)
+ return;
+
+ if (sfx == null)
+ return;
+
+ if (sfx.name.charAt(0) == '*')
+ sfx = RegisterSexedSound(cl_entities[entnum].current, sfx.name);
+
+ // make sure the sound is loaded
+ sfxcache_t sc = WaveLoader.LoadSound(sfx);
+ if (sc == null)
+ return; // couldn't load the sound's data
+
+ int vol = (int) (fvol * 255);
+
+ // make the playsound_t
+ playsound_t ps = AllocPlaysound();
+ if (ps == null)
+ return;
+
+ if (origin != null) {
+ VectorCopy(origin, ps.origin);
+ ps.fixed_origin = true;
+ } else
+ ps.fixed_origin = false;
+
+ ps.entnum = entnum;
+ ps.entchannel = entchannel;
+ ps.attenuation = attenuation;
+ ps.volume = vol;
+ ps.sfx = sfx;
+
+ // drift s_beginofs
+ int start = (int) (cl.frame.servertime * 0.001f * dma.speed + s_beginofs);
+ if (start < paintedtime) {
+ start = paintedtime;
+ s_beginofs = (int) (start - (cl.frame.servertime * 0.001f * dma.speed));
+ } else if (start > paintedtime + 0.3f * dma.speed) {
+ start = (int) (paintedtime + 0.1f * dma.speed);
+ s_beginofs = (int) (start - (cl.frame.servertime * 0.001f * dma.speed));
+ } else {
+ s_beginofs -= 10;
+ }
+
+ if (timeofs == 0.0f)
+ ps.begin = paintedtime;
+ else
+ ps.begin = (long) (start + timeofs * dma.speed);
+
+ // sort into the pending sound list
+ playsound_t sort;
+ for (sort = s_pendingplays.next; sort != s_pendingplays && sort.begin < ps.begin; sort = sort.next);
+
+ ps.next = sort;
+ ps.prev = sort.prev;
+
+ ps.next.prev = ps;
+ ps.prev.next = ps;
+ }
+
+ /*
+ ==================
+ S_StartLocalSound
+ ==================
+ */
+ public static void StartLocalSound(String sound) {
+ sfx_t sfx;
+
+ if (!sound_started)
+ return;
+
+ sfx = RegisterSound(sound);
+ if (sfx == null) {
+ Com.Printf("S_StartLocalSound: can't cache " + sound + "\n");
+ return;
+ }
+ StartSound(null, cl.playernum + 1, 0, sfx, 1, 1, 0);
+ }
+
+
+ /*
+ ==================
+ S_ClearBuffer
+ ==================
+ */
+ static void ClearBuffer()
+ {
+ int clear;
+
+ if (!sound_started)
+ return;
+
+ s_rawend = 0;
+
+ if (dma.samplebits == 8)
+ clear = 0x80;
+ else
+ clear = 0;
+
+ SNDDMA_BeginPainting ();
+ if (dma.buffer != null)
+ //memset(dma.buffer, clear, dma.samples * dma.samplebits/8);
+ //Arrays.fill(dma.buffer, (byte)clear);
+ SNDDMA_Submit ();
+ }
+
+ /*
+ ==================
+ S_StopAllSounds
+ ==================
+ */
+ public static void StopAllSounds()
+ {
+ int i;
+
+ if (!sound_started)
+ return;
+
+ // clear all the playsounds
+ //memset(s_playsounds, 0, sizeof(s_playsounds));
+ s_freeplays.next = s_freeplays.prev = s_freeplays;
+ s_pendingplays.next = s_pendingplays.prev = s_pendingplays;
+
+ for (i=0 ; i<MAX_PLAYSOUNDS ; i++)
+ {
+ s_playsounds[i].clear();
+ s_playsounds[i].prev = s_freeplays;
+ s_playsounds[i].next = s_freeplays.next;
+ s_playsounds[i].prev.next = s_playsounds[i];
+ s_playsounds[i].next.prev = s_playsounds[i];
+ }
+
+ // clear all the channels
+ //memset(channels, 0, sizeof(channels));
+ for (i = 0; i < MAX_CHANNELS; i++)
+ channels[i].clear();
+
+ ClearBuffer();
+ }
+
+ /*
+ ==================
+ S_AddLoopSounds
+
+ 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
+ ==================
+ */
+ static void AddLoopSounds()
+ {
+ int i, j;
+ int[] sounds = new int[Defines.MAX_EDICTS];
+ int left, right, left_total, right_total;
+ channel_t ch;
+ sfx_t sfx;
+ sfxcache_t sc;
+ int num;
+ entity_state_t ent;
+
+ if (cl_paused.value != 0.0f)
+ return;
+
+ if (cls.state != ca_active)
+ return;
+
+ if (!cl.sound_prepped)
+ return;
+
+ for (i=0 ; i<cl.frame.num_entities ; i++)
+ {
+ num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
+ ent = cl_parse_entities[num];
+ sounds[i] = ent.sound;
+ }
+
+ for (i=0 ; i<cl.frame.num_entities ; i++)
+ {
+ if (sounds[i] == 0)
+ continue;
+
+ sfx = cl.sound_precache[sounds[i]];
+ if (sfx == null)
+ continue; // bad sound effect
+ sc = sfx.cache;
+ if (sc == null)
+ continue;
+
+ num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
+ ent = cl_parse_entities[num];
+
+ channel_t tch = new channel_t();
+ // find the total contribution of all sounds of this type
+ SpatializeOrigin(ent.origin, 255.0f, SOUND_LOOPATTENUATE, tch);
+ left_total = tch.leftvol;
+ right_total = tch.rightvol;
+ for (j=i+1 ; j<cl.frame.num_entities ; j++)
+ {
+ if (sounds[j] != sounds[i])
+ continue;
+ sounds[j] = 0; // don't check this again later
+
+ num = (cl.frame.parse_entities + j)&(MAX_PARSE_ENTITIES-1);
+ ent = cl_parse_entities[num];
+
+ SpatializeOrigin(ent.origin, 255.0f, SOUND_LOOPATTENUATE, tch);
+ left_total += tch.leftvol;
+ right_total += tch.rightvol;
+ }
+
+ if (left_total == 0 && right_total == 0)
+ continue; // not audible
+
+ // allocate a channel
+ ch = PickChannel(0, 0);
+ if (ch == null)
+ return;
+
+ if (left_total > 255)
+ left_total = 255;
+ if (right_total > 255)
+ right_total = 255;
+ ch.leftvol = left_total;
+ ch.rightvol = right_total;
+ ch.autosound = true; // remove next frame
+ ch.sfx = sfx;
+ ch.pos = paintedtime % sc.length;
+ ch.end = paintedtime + sc.length - ch.pos;
+ }
+ }
+
+// =============================================================================
+
+ /*
+ ============
+ S_RawSamples
+
+ Cinematic streaming and voice over network
+ ============
+ */
+ static void RawSamples(int samples, int rate, int width, int channels, byte[] data)
+ {
+ //TODO RawSamples
+ int i;
+ int src, dst;
+ float scale;
+
+ if (!sound_started)
+ return;
+
+ if (s_rawend < paintedtime)
+ s_rawend = paintedtime;
+ scale = (float)rate / dma.speed;
+
+// Com_Printf ("%i < %i < %i\n", soundtime, paintedtime, s_rawend);
+ if (channels == 2 && width == 2)
+ {
+ if (scale == 1.0)
+ { // optimized case
+// for (i=0 ; i<samples ; i++)
+// {
+// dst = s_rawend&(MAX_RAW_SAMPLES-1);
+// s_rawend++;
+// s_rawsamples[dst].left =
+// LittleShort(((short *)data)[i*2]) << 8;
+// s_rawsamples[dst].right =
+// LittleShort(((short *)data)[i*2+1]) << 8;
+// }
+ }
+ else
+ {
+ for (i=0 ; ; i++)
+ {
+// src = i*scale;
+// if (src >= samples)
+// break;
+// dst = s_rawend&(MAX_RAW_SAMPLES-1);
+// s_rawend++;
+// s_rawsamples[dst].left =
+// LittleShort(((short *)data)[src*2]) << 8;
+// s_rawsamples[dst].right =
+// LittleShort(((short *)data)[src*2+1]) << 8;
+ }
+ }
+ }
+ else if (channels == 1 && width == 2)
+ {
+ for (i=0 ; ; i++)
+ {
+// src = i*scale;
+// if (src >= samples)
+// break;
+// dst = s_rawend&(MAX_RAW_SAMPLES-1);
+// s_rawend++;
+// s_rawsamples[dst].left =
+// LittleShort(((short *)data)[src]) << 8;
+// s_rawsamples[dst].right =
+// LittleShort(((short *)data)[src]) << 8;
+ }
+ }
+ else if (channels == 2 && width == 1)
+ {
+ for (i=0 ; ; i++)
+ {
+// src = i*scale;
+// if (src >= samples)
+// break;
+// dst = s_rawend&(MAX_RAW_SAMPLES-1);
+// s_rawend++;
+// s_rawsamples[dst].left =
+// ((char *)data)[src*2] << 16;
+// s_rawsamples[dst].right =
+// ((char *)data)[src*2+1] << 16;
+ }
+ }
+ else if (channels == 1 && width == 1)
+ {
+ for (i=0 ; ; i++)
+ {
+// src = i*scale;
+// if (src >= samples)
+// break;
+// dst = s_rawend&(MAX_RAW_SAMPLES-1);
+// s_rawend++;
+// s_rawsamples[dst].left =
+// (((byte *)data)[src]-128) << 16;
+// s_rawsamples[dst].right = (((byte *)data)[src]-128) << 16;
+ }
+ }
+ }
+
+//// =============================================================================
+
+ /*
+ ============
+ S_Update
+
+ Called once each time through the main loop
+ ============
+ */
+ public static void Update(float[] origin, float[] forward, float[] right, float[] up) {
+
+ if (!sound_started)
+ return;
+
+ // if the laoding plaque is up, clear everything
+ // out to make sure we aren't looping a dirty
+ // dma buffer while loading
+ if (cls.disable_screen != 0.0f) {
+ ClearBuffer();
+ return;
+ }
+
+ // rebuild scale tables if volume is modified
+ if (s_volume.modified)
+ InitScaletable();
+
+ VectorCopy(origin, listener_origin);
+ VectorCopy(forward, listener_forward);
+ VectorCopy(right, listener_right);
+ VectorCopy(up, listener_up);
+
+ channel_t combine = null;
+
+ // update spatialization for dynamic sounds
+ channel_t ch;
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ ch = channels[i];
+ if (ch.sfx == null)
+ continue;
+ if (ch.autosound) { // autosounds are regenerated fresh each frame
+ //memset (ch, 0, sizeof(*ch));
+ ch.clear();
+ continue;
+ }
+ Spatialize(ch); // respatialize channel
+ if (ch.leftvol == 0 && ch.rightvol == 0) {
+ //memset (ch, 0, sizeof(*ch));
+ ch.clear();
+ continue;
+ }
+ }
+
+ // add loopsounds
+ AddLoopSounds();
+
+ //
+ // debugging output
+ //
+ if (s_show.value != 0.0f) {
+ int total = 0;
+
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ ch = channels[i];
+ if (ch.sfx != null && (ch.leftvol != 0 || ch.rightvol != 0)) {
+ Com.Printf(ch.leftvol + " " + ch.rightvol + " " + ch.sfx.name + "\n");
+ total++;
+ }
+ }
+
+ //Com.Printf("----(" + total + ")---- painted: " + paintedtime + "\n");
+ }
+
+ // mix some sound
+ Update_();
+ }
+
+ static int buffers = 0;
+ static int oldsamplepos = 0;
+ static void GetSoundtime()
+ {
+ int samplepos;
+ //static int buffers;
+ //static int oldsamplepos;
+ int fullsamples;
+
+ fullsamples = dma.samples / dma.channels;
+
+// it is possible to miscount buffers if it has wrapped twice between
+// calls to S_Update. Oh well.
+ samplepos = SNDDMA_GetDMAPos();
+
+ if (samplepos < oldsamplepos)
+ {
+ buffers++; // buffer wrapped
+
+ if (paintedtime > 0x40000000)
+ { // time to chop things off to avoid 32 bit limits
+ buffers = 0;
+ paintedtime = fullsamples;
+ StopAllSounds();
+ }
+ }
+ oldsamplepos = samplepos;
+
+ soundtime = buffers*fullsamples + samplepos/dma.channels;
+ }
+
+ static void Update_()
+ {
+ int endtime;
+ int samps;
+
+ if (!sound_started)
+ return;
+
+ SNDDMA_BeginPainting();
+
+ if (dma.buffer == null)
+ return;
+
+ // Updates DMA time
+ GetSoundtime();
+
+ // check to make sure that we haven't overshot
+ if (paintedtime < soundtime)
+ {
+ Com.DPrintf("S_Update_ : overflow\n");
+ paintedtime = soundtime;
+ }
+
+ // mix ahead of current position
+ endtime = (int)(soundtime + s_mixahead.value * dma.speed);
+ // endtime = (soundtime + 4096) & ~4095;
+
+ // mix to an even submission block size
+ endtime = (endtime + dma.submission_chunk-1)
+ & ~(dma.submission_chunk-1);
+ samps = dma.samples >> (dma.channels-1);
+ if (endtime - soundtime > samps)
+ endtime = soundtime + samps;
+
+ PaintChannels(endtime);
+
+ SNDDMA_Submit();
+ }
+
+ /*
+ ===============================================================================
+
+ console functions
+
+ ===============================================================================
+ */
+
+ static void Play() {
+ int i;
+ String name;
+ sfx_t sfx;
+
+ i = 1;
+ while (i < Cmd.Argc()) {
+ name = new String(Cmd.Argv(i));
+ if (name.indexOf('.') == -1)
+ name += ".wav";
+
+ sfx = RegisterSound(name);
+ StartSound(null, cl.playernum + 1, 0, sfx, 1.0f, 1.0f, 0.0f);
+ i++;
+ }
+ }
+
+ static void SoundList() {
+ int i;
+ sfx_t sfx;
+ sfxcache_t sc;
+ int size, total;
+
+ total = 0;
+ for (i = 0; i < num_sfx; i++) {
+ sfx = known_sfx[i];
+ if (sfx.registration_sequence == 0)
+ continue;
+ sc = sfx.cache;
+ if (sc != null) {
+ size = sc.length * sc.width * (sc.stereo + 1);
+ total += size;
+ if (sc.loopstart >= 0)
+ Com.Printf("L");
+ else
+ Com.Printf(" ");
+ Com.Printf("(%2db) %6i : %s\n", new Vargs(3).add(sc.width * 8).add(size).add(sfx.name));
+ } else {
+ if (sfx.name.charAt(0) == '*')
+ Com.Printf(" placeholder : " + sfx.name + "\n");
+ else
+ Com.Printf(" not loaded : " + sfx.name + "\n");
+ }
+ }
+ Com.Printf("Total resident: " + total + "\n");
+ }
+
+}
diff --git a/src/jake2/sound/jsound/SND_JAVA.java b/src/jake2/sound/jsound/SND_JAVA.java
new file mode 100644
index 0000000..679c64e
--- /dev/null
+++ b/src/jake2/sound/jsound/SND_JAVA.java
@@ -0,0 +1,181 @@
+/*
+ * SND_JAVA.java
+ * Copyright (C) 2004
+ *
+ * $Id: SND_JAVA.java,v 1.1 2004-07-09 06:50:48 hzi Exp $
+ */
+/*
+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.
+
+*/
+package jake2.sound.jsound;
+
+import jake2.Globals;
+import jake2.game.cvar_t;
+import jake2.qcommon.Cvar;
+
+import javax.sound.sampled.*;
+
+/**
+ * SND_JAVA
+ */
+public class SND_JAVA extends Globals {
+
+ static boolean snd_inited= false;
+
+ static cvar_t sndbits;
+ static cvar_t sndspeed;
+ static cvar_t sndchannels;
+
+ static class dma_t {
+ int channels;
+ int samples; // mono samples in buffer
+ int submission_chunk; // don't mix less than this #
+ //int samplepos; // in mono samples
+ int samplebits;
+ int speed;
+ byte[] buffer;
+ }
+ static SND_DMA.dma_t dma = new dma_t();
+
+ static class SoundThread extends Thread {
+ byte[] b;
+ SourceDataLine l;
+ int pos = 0;
+ boolean running = false;
+ public SoundThread(byte[] buffer, SourceDataLine line) {
+ b = buffer;
+ l = line;
+ }
+ public void run() {
+ running = true;
+ while (running) {
+ line.write(b, pos, 512);
+ pos = (pos+512) % b.length;
+ }
+ }
+ public synchronized void stopLoop() {
+ running = false;
+ }
+ public int getSamplePos() {
+ return pos >> 1;
+ }
+ }
+ static SoundThread thread;
+ static SourceDataLine line;
+ static AudioFormat format;
+
+
+ static boolean SNDDMA_Init() {
+
+ if (snd_inited)
+ return true;
+
+ if (sndbits == null) {
+ sndbits = Cvar.Get("sndbits", "16", CVAR_ARCHIVE);
+ sndspeed = Cvar.Get("sndspeed", "0", CVAR_ARCHIVE);
+ sndchannels = Cvar.Get("sndchannels", "1", CVAR_ARCHIVE);
+ }
+
+// byte[] sound = FS.LoadFile("sound/misc/menu1.wav");
+// AudioInputStream stream;
+// try {
+// stream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(sound));
+// } catch (UnsupportedAudioFileException e) {
+// return false;
+// } catch (IOException e) {
+// return false;
+// }
+ //format = stream.getFormat();
+ format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 22050, 16, 1, 2, 22050, false);
+ DataLine.Info dinfo = new DataLine.Info(SourceDataLine.class, format);
+
+ try {
+ line = (SourceDataLine)AudioSystem.getLine(dinfo);
+ } catch (LineUnavailableException e4) {
+ return false;
+ }
+
+ dma.buffer = new byte[65536];
+ dma.channels = format.getChannels();
+ dma.samplebits = format.getSampleSizeInBits();
+ dma.samples = dma.buffer.length / format.getFrameSize();
+ dma.speed = (int)format.getSampleRate();
+ //dma.samplepos = 0;
+ dma.submission_chunk = 1;
+
+ try {
+ line.open(format, 4096);
+ } catch (LineUnavailableException e5) {
+ return false;
+ }
+
+ line.start();
+ thread = new SoundThread(dma.buffer, line);
+ //thread.setPriority(Thread.MAX_PRIORITY);
+ thread.start();
+
+ snd_inited = true;
+ return true;
+
+ }
+
+ static int SNDDMA_GetDMAPos() {
+ //dma.samplepos = line.getFramePosition() % dma.samples;
+ return thread.getSamplePos(); //dma.samplepos;
+ }
+
+ static void SNDDMA_Shutdown() {
+ thread.stopLoop();
+ line.stop();
+ line.flush();
+ line.close();
+ line=null;
+ snd_inited = false;
+ }
+
+ /*
+ ==============
+ SNDDMA_Submit
+
+ Send sound to device if buffer isn't really the dma buffer
+ ===============
+ */
+ public static void SNDDMA_Submit() {
+// runLine();
+ }
+
+ static void SNDDMA_BeginPainting() {}
+
+// private static int pos = 0;
+// static void runLine() {
+//
+// int p = line.getFramePosition() * format.getFrameSize() % dma.buffer.length;
+// if (p == 0) {
+// writeLine();
+// }
+// else if (pos - p < 4096 ) writeLine();
+// }
+//
+// static void writeLine() {
+// line.write(dma.buffer, pos, 4096);
+// pos+=4096;
+// if (pos>=dma.buffer.length) pos = 0;
+// }
+
+}
diff --git a/src/jake2/sound/jsound/SND_MIX.java b/src/jake2/sound/jsound/SND_MIX.java
new file mode 100644
index 0000000..c3aae2c
--- /dev/null
+++ b/src/jake2/sound/jsound/SND_MIX.java
@@ -0,0 +1,491 @@
+/*
+ * SND_MIX.java
+ * Copyright (C) 2004
+ *
+ * $Id: SND_MIX.java,v 1.1 2004-07-09 06:50:48 hzi Exp $
+ */
+/*
+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.
+
+*/
+package jake2.sound.jsound;
+
+import java.nio.*;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+
+import jake2.game.cvar_t;
+import jake2.sound.*;
+import jake2.sound.sfx_t;
+import jake2.sound.sfxcache_t;
+import jake2.util.Math3D;
+
+/**
+ * SND_MIX
+ */
+public class SND_MIX extends SND_JAVA {
+
+ static final int MAX_CHANNELS = 32;
+ static final int MAX_RAW_SAMPLES = 8192;
+
+ static class playsound_t {
+ playsound_t prev, next;
+ sfx_t sfx;
+ float volume;
+ float attenuation;
+ int entnum;
+ int entchannel;
+ boolean fixed_origin; // use origin field instead of entnum's origin
+ float[] origin = { 0, 0, 0 };
+ long begin; // begin on this sample
+
+ public void clear() {
+ prev = next = null;
+ sfx = null;
+ volume = attenuation = begin = entnum = entchannel = 0;
+ fixed_origin = false;
+ Math3D.VectorClear(origin);
+ }
+ };
+
+ static class channel_t {
+ sfx_t sfx; // sfx number
+ int leftvol; // 0-255 volume
+ int rightvol; // 0-255 volume
+ int end; // end time in global paintsamples
+ int pos; // sample position in sfx
+ int looping; // where to loop, -1 = no looping OBSOLETE?
+ int entnum; // to allow overriding a specific sound
+ int entchannel; //
+ float[] origin = { 0, 0, 0 }; // only use if fixed_origin is set
+ float dist_mult; // distance multiplier (attenuation/clipK)
+ int master_vol; // 0-255 master volume
+ boolean fixed_origin; // use origin instead of fetching entnum's origin
+ boolean autosound; // from an entity->sound, cleared each frame
+
+ void clear() {
+ sfx = null;
+ dist_mult = leftvol = rightvol = end = pos = looping = entnum = entchannel = master_vol = 0;
+ Math3D.VectorClear(origin);
+ fixed_origin = autosound = false;
+ }
+ };
+
+ static class portable_samplepair_t {
+ int left;
+ int right;
+ };
+
+ static cvar_t s_volume;
+ static int s_rawend;
+//// snd_mix.c -- portable code to mix sounds for snd_dma.c
+//
+// #include "client.h"
+// #include "snd_loc.h"
+//
+ static final int PAINTBUFFER_SIZE = 2048;
+ //static portable_samplepair_t[] paintbuffer = new portable_samplepair_t[PAINTBUFFER_SIZE];
+ static IntBuffer paintbuffer = IntBuffer.allocate(PAINTBUFFER_SIZE*2);
+ static int[][] snd_scaletable = new int[32][256];
+// int *snd_p, snd_linear_count, snd_vol;
+// short *snd_out;
+ static IntBuffer snd_p;
+ static ShortBuffer snd_out;
+ static int snd_linear_count;
+ static int snd_vol;
+
+ static int paintedtime; // sample PAIRS
+ static playsound_t s_pendingplays = new playsound_t();
+
+ //static portable_samplepair_t[] s_rawsamples = new portable_samplepair_t[MAX_RAW_SAMPLES];
+ static IntBuffer s_rawsamples = IntBuffer.allocate(MAX_RAW_SAMPLES*2);
+ static channel_t[] channels = new channel_t[MAX_CHANNELS];
+ static {
+ for(int i=0; i < MAX_CHANNELS; i++)
+ channels[i] = new channel_t();
+ }
+
+ static void WriteLinearBlastStereo16()
+ {
+ int i;
+ int val;
+
+ for (i=0 ; i<snd_linear_count ; i+=2)
+ {
+ val = snd_p.get(i)>>8;
+ if (val > 0x7fff)
+ snd_out.put(i, (short)0x7fff);
+ else if (val < (short)0x8000)
+ snd_out.put(i,(short)0x8000);
+ else
+ snd_out.put(i, (short)val);
+
+ val = snd_p.get(i+1)>>8;
+ if (val > 0x7fff)
+ snd_out.put(i+1, (short)0x7fff);
+ else if (val < (short)0x8000)
+ snd_out.put(i+1, (short)0x8000);
+ else
+ snd_out.put(i+1, (short)val);
+ }
+ }
+
+ static void TransferStereo16(ByteBuffer pbuf, int endtime)
+ {
+ int lpos;
+ int lpaintedtime;
+
+ snd_p = paintbuffer;
+ lpaintedtime = paintedtime;
+
+ while (lpaintedtime < endtime)
+ {
+ // handle recirculating buffer issues
+ lpos = lpaintedtime & ((dma.samples>>1)-1);
+
+// snd_out = (short *) pbuf + (lpos<<1);
+ snd_out = pbuf.asShortBuffer();
+ snd_out.position(lpos<<1);
+ snd_out = snd_out.slice();
+
+ snd_linear_count = (dma.samples>>1) - lpos;
+ if (lpaintedtime + snd_linear_count > endtime)
+ snd_linear_count = endtime - lpaintedtime;
+
+ snd_linear_count <<= 1;
+
+ // write a linear blast of samples
+ WriteLinearBlastStereo16();
+
+ //snd_p += snd_linear_count;
+ paintbuffer.position(snd_linear_count);
+ snd_p = paintbuffer.slice();
+
+ lpaintedtime += (snd_linear_count>>1);
+ }
+ }
+
+ /*
+ ===================
+ S_TransferPaintBuffer
+
+ ===================
+ */
+ static void TransferPaintBuffer(int endtime)
+ {
+ int out_idx;
+ int count;
+ int out_mask;
+ int p;
+ int step;
+ int val;
+ //unsigned long *pbuf;
+
+ ByteBuffer pbuf = ByteBuffer.wrap(dma.buffer);
+ pbuf.order(ByteOrder.LITTLE_ENDIAN);
+
+ if (SND_DMA.s_testsound.value != 0.0f)
+ {
+ int i;
+ int count2;
+
+ // write a fixed sine wave
+ count2 = (endtime - paintedtime)*2;
+ int v;
+ for (i=0 ; i<count2 ; i+=2) {
+ v = (int)(Math.sin((paintedtime+i)*0.1)*20000*256);
+ paintbuffer.put(i, v);
+ paintbuffer.put(i+1, v);
+ }
+ }
+
+
+ if (dma.samplebits == 16 && dma.channels == 2)
+ { // optimized case
+ TransferStereo16(pbuf, endtime);
+ }
+ else
+ { // general case
+ p = 0;
+ count = (endtime - paintedtime) * dma.channels;
+ out_mask = dma.samples - 1;
+ out_idx = paintedtime * dma.channels & out_mask;
+ step = 3 - dma.channels;
+
+ if (dma.samplebits == 16)
+ {
+// short *out = (short *) pbuf;
+ ShortBuffer out = pbuf.asShortBuffer();
+ while (count-- > 0)
+ {
+ val = paintbuffer.get(p) >> 8;
+ p+= step;
+ if (val > 0x7fff)
+ val = 0x7fff;
+ else if (val < (short)0x8000)
+ val = (short)0x8000;
+ out.put(out_idx, (short)val);
+//System.out.println(out_idx + " " + val);
+ out_idx = (out_idx + 1) & out_mask;
+ }
+ }
+ else if (dma.samplebits == 8)
+ {
+// unsigned char *out = (unsigned char *) pbuf;
+ ByteBuffer out = pbuf;
+ while (count-- > 0)
+ {
+ val = paintbuffer.get(p) >> 8;
+ p += step;
+ if (val > 0x7fff)
+ val = 0x7fff;
+ else if (val < (short)0x8000)
+ val = (short)0x8000;
+ out.put(out_idx,(byte)(val>>>8));
+ out_idx = (out_idx + 1) & out_mask;
+ }
+ }
+ }
+ }
+
+
+ /*
+ ===============================================================================
+
+ CHANNEL MIXING
+
+ ===============================================================================
+ */
+ static void PaintChannels(int endtime)
+ {
+ int i;
+ int end;
+ channel_t ch;
+ sfxcache_t sc;
+ int ltime, count;
+ playsound_t ps;
+
+ snd_vol = (int)(s_volume.value*256);
+
+// Com_Printf ("%i to %i\n", paintedtime, endtime);
+ while (paintedtime < endtime)
+ {
+ // if paintbuffer is smaller than DMA buffer
+ end = endtime;
+ if (endtime - paintedtime > PAINTBUFFER_SIZE)
+ end = paintedtime + PAINTBUFFER_SIZE;
+
+ // start any playsounds
+ while (true)
+ {
+ ps = s_pendingplays.next;
+ if (ps == s_pendingplays)
+ break; // no more pending sounds
+ if (ps.begin <= paintedtime)
+ {
+ SND_DMA.IssuePlaysound(ps);
+ continue;
+ }
+
+ if (ps.begin < end)
+ end = (int)ps.begin; // stop here
+ break;
+ }
+
+ // clear the paint buffer
+ if (s_rawend < paintedtime)
+ {
+// Com_Printf ("clear\n");
+ for (i = 0; i < (end-paintedtime)*2; i++) {
+ paintbuffer.put(i, 0);
+ }
+ //memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));
+ }
+ else
+ { // copy from the streaming sound source
+ int s;
+ int stop;
+
+ stop = (end < s_rawend) ? end : s_rawend;
+
+ for (i=paintedtime ; i<stop ; i++)
+ {
+ s = i&(MAX_RAW_SAMPLES-1);
+ //paintbuffer[i-paintedtime] = s_rawsamples[s];
+ paintbuffer.put((i-paintedtime)*2, s_rawsamples.get(2*s));
+ paintbuffer.put((i-paintedtime)*2+1, s_rawsamples.get(2*s)+1);
+ }
+// if (i != end)
+// Com_Printf ("partial stream\n");
+// else
+// Com_Printf ("full stream\n");
+ for ( ; i<end ; i++)
+ {
+ //paintbuffer[i-paintedtime].left =
+ //paintbuffer[i-paintedtime].right = 0;
+ paintbuffer.put((i-paintedtime)*2, 0);
+ paintbuffer.put((i-paintedtime)*2+1, 0);
+ }
+ }
+
+
+ // paint in the channels.
+ //ch = channels;
+ for (i=0; i<MAX_CHANNELS ; i++)
+ {
+ ch = channels[i];
+ ltime = paintedtime;
+
+ while (ltime < end)
+ {
+ if (ch.sfx == null || (ch.leftvol == 0 && ch.rightvol == 0))
+ break;
+
+ // max painting is to the end of the buffer
+ count = end - ltime;
+
+ // might be stopped by running out of data
+ if (ch.end - ltime < count)
+ count = ch.end - ltime;
+
+ sc = WaveLoader.LoadSound(ch.sfx);
+ if (sc == null)
+ break;
+
+ if (count > 0 && ch.sfx != null)
+ {
+ if (sc.width == 1)// FIXME; 8 bit asm is wrong now
+ PaintChannelFrom8(ch, sc, count, ltime - paintedtime);
+ else
+ PaintChannelFrom16(ch, sc, count, ltime - paintedtime);
+
+ ltime += count;
+ }
+
+ // if at end of loop, restart
+ if (ltime >= ch.end)
+ {
+ if (ch.autosound)
+ { // autolooping sounds always go back to start
+ ch.pos = 0;
+ ch.end = ltime + sc.length;
+ }
+ else if (sc.loopstart >= 0)
+ {
+ ch.pos = sc.loopstart;
+ ch.end = ltime + sc.length - ch.pos;
+ }
+ else
+ { // channel just stopped
+ ch.sfx = null;
+ }
+ }
+ }
+
+ }
+
+ // transfer out according to DMA format
+ TransferPaintBuffer(end);
+ paintedtime = end;
+ }
+ }
+
+ static void InitScaletable ()
+ {
+ int i, j;
+ int scale;
+
+ s_volume.modified = false;
+ for (i=0 ; i<32 ; i++)
+ {
+ scale = (int)(i * 8 * 256 * s_volume.value);
+ for (j=0 ; j<256 ; j++)
+ snd_scaletable[i][j] = ((byte)j) * scale;
+ }
+ }
+
+ static void PaintChannelFrom8(channel_t ch, sfxcache_t sc, int count, int offset)
+ {
+ int data;
+ int[] lscale;
+ int[] rscale;
+ int sfx;
+ int i;
+ portable_samplepair_t samp;
+
+ if (ch.leftvol > 255)
+ ch.leftvol = 255;
+ if (ch.rightvol > 255)
+ ch.rightvol = 255;
+
+ //ZOID-- >>11 has been changed to >>3, >>11 didn't make much sense
+ //as it would always be zero.
+ lscale = snd_scaletable[ ch.leftvol >> 3];
+ rscale = snd_scaletable[ ch.rightvol >> 3];
+ sfx = ch.pos;
+
+ //samp = paintbuffer[offset];
+
+ for (i=0 ; i<count ; i++, offset++)
+ {
+ int left = paintbuffer.get(offset*2);
+ int right = paintbuffer.get(offset*2+1);
+ data = sc.data[sfx+i];
+ left += lscale[data];
+ right += rscale[data];
+ paintbuffer.put(offset*2, left);
+ paintbuffer.put(offset*2+1, right);
+ }
+
+ ch.pos += count;
+ }
+
+ private static ByteBuffer bb;
+ private static ShortBuffer sb;
+ static void PaintChannelFrom16(channel_t ch, sfxcache_t sc, int count, int offset)
+ {
+ int data;
+ int left, right;
+ int leftvol, rightvol;
+ int sfx;
+ int i;
+ portable_samplepair_t samp;
+
+ leftvol = ch.leftvol*snd_vol;
+ rightvol = ch.rightvol*snd_vol;
+ ByteBuffer bb = ByteBuffer.wrap(sc.data);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ sb = bb.asShortBuffer();
+ sfx = ch.pos;
+
+ //samp = paintbuffer[offset];
+ for (i=0 ; i<count ; i++, offset++)
+ {
+ left = paintbuffer.get(offset*2);
+ right = paintbuffer.get(offset*2+1);
+ data = sb.get(sfx+i);
+ left += (data * leftvol)>>8;
+ right += (data * rightvol)>>8;
+ paintbuffer.put(offset*2, left);
+ paintbuffer.put(offset*2+1, right);
+ }
+
+ ch.pos += count;
+ }
+
+} \ No newline at end of file