diff options
Diffstat (limited to 'src/jake2/sound')
-rw-r--r-- | src/jake2/sound/S.java | 6 | ||||
-rw-r--r-- | src/jake2/sound/WaveLoader.java | 302 | ||||
-rw-r--r-- | src/jake2/sound/joal/Channel.java | 84 | ||||
-rw-r--r-- | src/jake2/sound/joal/JOALSoundImpl.java | 793 | ||||
-rw-r--r-- | src/jake2/sound/jsound/JSoundImpl.java | 98 | ||||
-rw-r--r-- | src/jake2/sound/jsound/SND_DMA.java | 1197 | ||||
-rw-r--r-- | src/jake2/sound/jsound/SND_JAVA.java | 181 | ||||
-rw-r--r-- | src/jake2/sound/jsound/SND_MIX.java | 491 |
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 |