nuclide/src/shared/sound.qc

945 lines
20 KiB
Plaintext

/*
* Copyright (c) 2016-2024 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
static void pointsound_proper(vector origin, string sample, float volume, float attenuation, float pitch) = #483;
#ifndef SOUNDSHADER_DYNAMIC
#ifndef SOUNDSHADER_MAX
/** The max amount of soundDef entries. Can override this in your progs.src. */
#define SOUNDSHADER_MAX 512
#endif
#endif
/*
#define SOUNDSHADER_DYNAMIC
Your game can define SOUNDSHADER_DYNAMIC in its progs.src if you want an unpredictable amount of sound shaders.
Other than that, you can increase the value of SOUNDSHADER_MAX.
We switched to up-front allocation because QCLIB fragments memory like hell as there's
no real garbage collector to speak of
*/
void
Sound_Shutdown(void)
{
if (g_sounds) {
memfree(g_sounds);
}
g_sounds_count = 0;
g_hashsounds = 0;
}
void SoundSource_Init(void);
void
Sound_Init(void)
{
InitStart();
/* make sure it's all reset */
Sound_Shutdown();
#ifndef SOUNDSHADER_DYNAMIC
g_sounds = (snd_t *)memalloc(sizeof(snd_t) * SOUNDSHADER_MAX);
NSLog("...allocated %d bytes for SoundDef", sizeof(snd_t) * SOUNDSHADER_MAX);
#endif
/* Source Engine conventions */
SoundSource_Init();
precache_sound("common/null.wav");
InitEnd();
}
static void
Sound_ParseField(int i, int a, string keyName, string setValue)
{
bool precacheSound = true;
switch (keyName) {
case "attenuation":
if (a == 2) {
switch(setValue) {
case "idle":
g_sounds[i].dist_max = 1000 / ATTN_IDLE;
break;
case "static":
g_sounds[i].dist_max = 1000 / ATTN_STATIC;
break;
case "none":
g_sounds[i].dist_max = 0;
break;
case "normal":
g_sounds[i].dist_max = 1000 / ATTN_NORM;
default:
break;
}
}
break;
case "soundlevel":
if (a == 2) {
switch(setValue) {
case "ATTN_IDLE":
g_sounds[i].dist_max = 1000 / ATTN_IDLE;
break;
case "ATTN_RICOCHET":
case "ATTN_STATIC":
g_sounds[i].dist_max = 1000 / ATTN_STATIC;
break;
case "ATTN_NONE":
g_sounds[i].dist_max = 0;
break;
case "ATTN_GUNFIRE":
case "ATTN_NORM":
g_sounds[i].dist_max = 1000 / ATTN_NORM;
default:
break;
}
}
break;
case "dist_min":
if (a == 2) {
dprint("\tMin distance set\n");
g_sounds[i].dist_min = stof(setValue);
}
break;
case "dist_max":
if (a == 2) {
dprint("\tMax distance set\n");
g_sounds[i].dist_max = stof(setValue);
}
break;
case "volume":
if (a == 2) {
dprint("\tVolume set\n");
g_sounds[i].volume = stof(setValue);
}
break;
case "shakes":
if (a == 2) {
dprint("\tShake set\n");
g_sounds[i].shakes = stof(setValue);
}
break;
case "pitch":
if (a == 2) {
dprint("\tPitch set\n");
int comma = tokenizebyseparator(setValue, ",");
if (comma == 2) {
g_sounds[i].pitch_min = stof(argv(0));
g_sounds[i].pitch_max = stof(argv(1));
} else {
g_sounds[i].pitch_min = fabs(stof(setValue)) * 100;
g_sounds[i].pitch_max = g_sounds[i].pitch_min;
}
}
break;
case "pitch_min":
if (a == 2) {
dprint("\tMinimum pitch set\n");
g_sounds[i].pitch_min = fabs(stof(setValue)) * 100;
}
break;
case "pitch_max":
if (a == 2) {
dprint("\tMaximum pitch set\n");
g_sounds[i].pitch_max = fabs(stof(setValue)) * 100;
}
break;
case "offset":
if (a == 2) {
dprint("\tOffset set\n");
g_sounds[i].offset = stof(setValue);
}
break;
case "looping":
dprint("\tSound set to loop\n");
g_sounds[i].flags |= SNDFL_LOOPING;
break;
case "nodups":
dprint("\tSound set to not play duplicate samples\n");
g_sounds[i].flags |= SNDFL_NODUPS;
break;
case "global":
dprint("\tSound set to play everywhere\n");
g_sounds[i].flags |= SNDFL_GLOBAL;
break;
case "private":
dprint("\tSound set to play privately\n");
g_sounds[i].flags |= SNDFL_PRIVATE;
break;
case "no_reverb":
dprint("\tSound set to ignore reverb\n");
g_sounds[i].flags |= SNDFL_NOREVERB;
break;
case "omnidirectional":
dprint("\tSound set to be omnidirectional\n");
g_sounds[i].flags |= SNDFL_OMNI;
break;
case "follow":
dprint("\tSound set to follow\n");
g_sounds[i].flags |= SNDFL_FOLLOW;
break;
case "footstep":
g_sounds[i].flags |= SNDFL_STEP;
break;
case "distshader":
g_sounds[i].distshader = setValue;
break;
case "pointparticle":
g_sounds[i].pointparticle = particleeffectnum(setValue);
break;
case "alerts":
dprint("\tSound set to alert enemy AI\n");
g_sounds[i].flags |= SNDFL_ALERTS;
break;
case "wave":
string randomPrefix = substring(setValue, 0, 1);
/* why? what is any of this about? */
if (randomPrefix == "^" || randomPrefix == ")" || randomPrefix == "*") {
setValue = substring(setValue, 1, -1);
}
/* hack to deal with all the stuff we don't yet support */
//if not (whichpack(strcat("sound/", setValue)))
precacheSound = false;
case "sample":
if (a == 2) {
dprint("\tAdded sample ");
dprint(setValue);
dprint("\n");
if (precacheSound)
precache_sound(setValue);
g_sounds[i].samples = sprintf("%s%s\n", g_sounds[i].samples, setValue);
g_sounds[i].sample_count++;
}
break;
}
}
static float
Sound_GetAttenuation(int i)
{
if (g_sounds[i].dist_max == 0)
return 0;
else
return cvar("s_nominaldistance") / g_sounds[i].dist_max;
}
static int
Sound_Parse(int i, string line, string shader)
{
int c;
static string t_name;
static int braced;
c = tokenize_console(line);
switch(argv(0)) {
case "{":
/* skip broken syntax */
if (braced == TRUE || t_name == "") {
break;
}
dprint("{\n");
braced = TRUE;
break;
case "}":
/* skip broken syntax */
if (braced == FALSE) {
break;
}
dprint("}\n");
braced = FALSE;
t_name = "";
return (1);
break;
default:
if (braced == TRUE) {
Sound_ParseField(i, c, argv(0), argv(1));
} else {
/* name/identifer of our message */
t_name = line;
if (t_name == shader) {
/* I guess it's what we want */
dprint("Found shader ");
dprint(shader);
dprint(":\n");
g_sounds[i].name = shader;
} else {
/* not what we're looking for */
t_name = "";
}
}
}
return (0);
}
bool
Sound_PrecacheFile(string fileName)
{
filestream scriptFile;
string lineStream;
int braceDepth = 0i;
string sndDefName = "";
int startIndex = g_sounds_count;
if (g_sounds_count >= SOUNDSHADER_MAX)
return false;
scriptFile = fopen(fileName, FILE_READ);
if (scriptFile < 0) {
return false;
}
/* create the hash-table if it doesn't exist */
if (!g_hashsounds) {
g_hashsounds = hash_createtab(2, HASH_ADD);
}
SndLog("Precaching SOURCE ENGINE file %S", fileName);
while ((lineStream = fgets(scriptFile))) {
/* when we found it, quit */
int lineSegments = tokenize_console(lineStream);
/* word for word */
for (int i = 0i; i < lineSegments; i++) {
string word = argv(i);
switch (word) {
case "{":
if (braceDepth == 0) {
g_sounds[startIndex].volume = 1.0f;
g_sounds[startIndex].dist_max = 1000;
g_sounds[startIndex].pitch_min = g_sounds[startIndex].pitch_max = 100;
g_sounds[startIndex].offset = 0;
}
braceDepth++;
break;
case "}":
braceDepth--;
/* sound definition done */
if (braceDepth == 0) {
//if (substring(sndDefName, 0, 11) == "Weapon_SMG1")
// error("");
hash_add(g_hashsounds, sndDefName, startIndex);
// print(sprintf("sfx add %i %S\n", startIndex, sndDefName));
sndDefName = "";
startIndex++;
g_sounds_count++;
if (g_sounds_count >= SOUNDSHADER_MAX)
return false;
}
break;
default:
if (braceDepth == 0) {
sndDefName = word;
} else {
//print(sprintf("field %S %S\n", argv(i), argv(i+1)));
Sound_ParseField(startIndex, 2, argv(i), argv(i+1));
i++;
}
break;
}
}
}
return true;
}
int
Sound_Precache(string shader)
{
searchhandle sh;
filestream fh;
string line;
int index;
if (!shader)
return -1;
index = g_sounds_count;
SndLog("Precaching SoundDef %S", shader);
/* create the hash-table if it doesn't exist */
if (!g_hashsounds) {
g_hashsounds = hash_createtab(2, HASH_ADD);
}
/* check if it's already cached */
{
int cache;
cache = (int)hash_get(g_hashsounds, shader, -1);
if (cache >= 0) {
return cache;
}
}
g_sounds_count++;
#ifdef SOUNDSHADER_DYNAMIC
g_sounds = (snd_t *)memrealloc(g_sounds, sizeof(snd_t), index, g_sounds_count);
#else
if (g_sounds_count >= SOUNDSHADER_MAX) {
NSError("Reached SOUNDSHADER_MAX (%d)", SOUNDSHADER_MAX);
return -1;
}
#endif
g_sounds[index].volume = 1.0f;
g_sounds[index].dist_max = 1000;
g_sounds[index].pitch_min = g_sounds[index].pitch_max = 100;
g_sounds[index].offset = 0;
sh = search_begin("sound/*.sndshd", TRUE, TRUE);
for (int i = 0; i < search_getsize(sh); i++) {
fh = fopen(search_getfilename(sh, i), FILE_READ);
if (fh < 0) {
continue;
}
while ((line = fgets(fh))) {
/* when we found it, quit */
if (Sound_Parse(index, line, shader) == TRUE) {
search_end(sh);
fclose(fh);
hash_add(g_hashsounds, shader, (int)index);
/* distant shader */
if (g_sounds[index].distshader) {
Sound_Precache(g_sounds[index].distshader);
}
return index;
}
}
fclose(fh);
}
NSError("No SoundDef %S found.", shader);
g_sounds_count--;
search_end(sh);
return -1;
}
static void
Sound_Distance(entity target, string shader)
{
int r;
float volume;
float radius;
float pitch;
int flag;
int sample;
flag = 0;
sample = (int)hash_get(g_hashsounds, shader, -1);
if (sample < 0) {
NSError("SoundDef %S is not precached", shader);
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* set pitch */
pitch = random(g_sounds[sample].pitch_min, g_sounds[sample].pitch_max);
radius = Sound_GetAttenuation(sample);
volume = g_sounds[sample].volume;
/* flags */
if (g_sounds[sample].flags & SNDFL_NOREVERB) {
flag |= SOUNDFLAG_NOREVERB;
}
if (g_sounds[sample].flags & SNDFL_LOOPING) {
flag |= SOUNDFLAG_FORCELOOP;
}
if (g_sounds[sample].flags & SNDFL_NODUPS) {
if (g_sounds[sample].playc >= g_sounds[sample].sample_count) {
g_sounds[sample].playc = 0;
}
r = g_sounds[sample].playc++;
}
#ifdef CLIENT
if (g_sounds[sample].flags & SNDFL_OMNI) {
flag |= SOUNDFLAG_NOSPACIALISE;
}
#endif
sound(
target,
5,
argv(r),
volume,
ATTN_NONE,
pitch,
flag,
g_sounds[sample].offset
);
}
static void
Sound_DistancePos(vector pos, string shader)
{
int r;
float volume;
float radius;
float pitch;
int flag;
int sample;
flag = 0;
sample = (int)hash_get(g_hashsounds, shader, -1);
if (sample < 0) {
NSError("SoundDef %S is not precached", shader);
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* set pitch */
pitch = random(g_sounds[sample].pitch_min, g_sounds[sample].pitch_max);
radius = Sound_GetAttenuation(sample);
volume = g_sounds[sample].volume;
/* flags */
if (g_sounds[sample].flags & SNDFL_NOREVERB) {
flag |= SOUNDFLAG_NOREVERB;
}
if (g_sounds[sample].flags & SNDFL_LOOPING) {
flag |= SOUNDFLAG_FORCELOOP;
}
if (g_sounds[sample].flags & SNDFL_NODUPS) {
if (g_sounds[sample].playc >= g_sounds[sample].sample_count) {
g_sounds[sample].playc = 0;
}
r = g_sounds[sample].playc++;
}
#ifdef CLIENT
if (g_sounds[sample].flags & SNDFL_OMNI) {
flag |= SOUNDFLAG_NOSPACIALISE;
}
#endif
SndLog("Playing %S", argv(r));
pointsound_proper(pos, argv(r), volume, ATTN_NONE, pitch);
}
void
Sound_Stop(entity target, int chan)
{
sound(target, chan, "common/null.wav", 1.0f, ATTN_NORM, 100, SOUNDFLAG_FOLLOW, 0 );
}
void
Sound_Play(entity target, int chan, string shader)
{
int r;
float volume;
float radius;
float pitch;
int flag;
int sample;
if (shader == "")
return;
flag = 0;
sample = (int)hash_get(g_hashsounds, shader, -1);
if (sample < 0) {
NSError("SoundDef %S is not precached", shader);
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* set pitch */
pitch = random(g_sounds[sample].pitch_min, g_sounds[sample].pitch_max);
radius = Sound_GetAttenuation(sample);
volume = g_sounds[sample].volume;
/* flags */
if (g_sounds[sample].flags & SNDFL_NOREVERB) {
flag |= SOUNDFLAG_NOREVERB;
}
if (g_sounds[sample].flags & SNDFL_GLOBAL) {
radius = ATTN_NONE;
}
if (g_sounds[sample].flags & SNDFL_LOOPING) {
flag |= SOUNDFLAG_FORCELOOP;
}
if (g_sounds[sample].flags & SNDFL_NODUPS) {
if (g_sounds[sample].playc >= g_sounds[sample].sample_count) {
g_sounds[sample].playc = 0;
}
r = g_sounds[sample].playc++;
}
if (g_sounds[sample].flags & SNDFL_FOLLOW) {
flag |= SOUNDFLAG_FOLLOW;
}
if (g_sounds[sample].flags & SNDFL_STEP) {
float s = vlen(target.velocity);
/*if (target.flags & FL_CROUCHING)
s *= 2.0f;*/
if (s < PMOVE_STEP_WALKSPEED) {
return;
} else if (s < PMOVE_STEP_RUNSPEED) {
volume *= 0.35f;
} else {
volume *= 0.75f;
}
}
#ifdef CLIENT
if (g_sounds[sample].flags & SNDFL_OMNI) {
flag |= SOUNDFLAG_NOSPACIALISE;
}
#else
if (g_sounds[sample].flags & SNDFL_PRIVATE) {
flag |= SOUNDFLAG_UNICAST;
msg_entity = target;
}
if (g_sounds[sample].flags & SNDFL_ALERTS) {
NSMonster_AlertEnemyAlliance(target.origin, g_sounds[sample].dist_max, target.m_iAlliance);
BotLib_Alert(target.origin, g_sounds[sample].dist_max, target.team);
}
#endif
SndLog("Playing %S", argv(r));
#ifdef SERVER
if (g_sounds[sample].shakes > 0.0) {
Client_ShakeOnce(target.origin, 512, 2.5, 1.0, 1.0f);
}
#else
if (g_sounds[sample].shakes > 0.0) {
float srad = 512;
float dist = vlen(pSeat->m_vecPredictedOrigin - target.origin);
if (dist < srad) {
float dif = 1.0 - (dist/srad);
pSeat->m_flShakeFreq = (1.0 * dif) * g_sounds[sample].shakes;
pSeat->m_flShakeAmp = (1.0 * dif) * g_sounds[sample].shakes;
pSeat->m_flShakeDuration = soundlength(argv(r));
pSeat->m_flShakeTime = pSeat->m_flShakeDuration;
}
}
#endif
#ifdef CLIENT
if (g_sounds[sample].pointparticle)
pointparticles(g_sounds[sample].pointparticle, target.origin, [0,0,0], 1);
#else
if (g_sounds[sample].pointparticle)
NSWarning("SoundDef %S is attempting to spawn a particle on the server side.", shader);
#endif
sound(
target,
chan,
argv(r),
volume,
radius,
pitch,
flag,
g_sounds[sample].offset
);
if (g_sounds[sample].distshader) {
Sound_Distance(target, g_sounds[sample].distshader);
}
}
void
Sound_PlayAt(vector pos, string shader)
{
int r;
float radius;
float pitch;
int flag;
int sample;
if (shader == "")
return;
flag = 0;
sample = (int)hash_get(g_hashsounds, shader, -1);
if (sample < 0) {
NSError("SoundDef %S is not precached", shader);
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* set pitch */
pitch = random(g_sounds[sample].pitch_min, g_sounds[sample].pitch_max);
/* flags */
if (g_sounds[sample].flags & SNDFL_NOREVERB) {
flag |= SOUNDFLAG_NOREVERB;
}
if (g_sounds[sample].flags & SNDFL_GLOBAL) {
radius = 0;
}
if (g_sounds[sample].flags & SNDFL_LOOPING) {
flag |= SOUNDFLAG_FORCELOOP;
}
if (g_sounds[sample].flags & SNDFL_NODUPS) {
if (g_sounds[sample].playc >= g_sounds[sample].sample_count) {
g_sounds[sample].playc = 0;
}
r = g_sounds[sample].playc++;
}
#ifdef CLIENT
if (g_sounds[sample].flags & SNDFL_OMNI) {
flag |= SOUNDFLAG_NOSPACIALISE;
}
#endif
/* really? this doesn't do any more? */
pointsound_proper(pos, argv(r), g_sounds[sample].volume, Sound_GetAttenuation(sample), pitch);
if (g_sounds[sample].distshader) {
Sound_DistancePos(pos, g_sounds[sample].distshader);
}
}
#ifdef CLIENT
void
Sound_PlayLocal(string shader)
{
int r;
int flag;
int sample;
if (shader == "")
return;
flag = 0;
sample = (int)hash_get(g_hashsounds, shader, -1);
if (sample < 0) {
NSError("SoundDef %S is not precached", shader);
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* really? this doesn't do any more? */
localsound(argv(r));
}
int
Sound_GetID(string defName)
{
if (defName == "")
return -1;
return (int)hash_get(g_hashsounds, defName, -1);
}
void
Sound_Update(entity target, int channel, int sample, float volume)
{
int r;
float radius;
float pitch;
int flag;
if (sample < 0) {
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* set pitch */
pitch = random(g_sounds[sample].pitch_min, g_sounds[sample].pitch_max);
radius = Sound_GetAttenuation(sample);
flag = 0;
/* flags */
if (g_sounds[sample].flags & SNDFL_NOREVERB) {
flag |= SOUNDFLAG_NOREVERB;
}
if (g_sounds[sample].flags & SNDFL_GLOBAL) {
radius = ATTN_NONE;
}
if (g_sounds[sample].flags & SNDFL_LOOPING) {
flag |= SOUNDFLAG_FORCELOOP;
}
if (g_sounds[sample].flags & SNDFL_NODUPS) {
if (g_sounds[sample].playc >= g_sounds[sample].sample_count) {
g_sounds[sample].playc = 0;
}
r = g_sounds[sample].playc++;
}
if (g_sounds[sample].flags & SNDFL_OMNI) {
flag |= SOUNDFLAG_NOSPACIALISE;
}
soundupdate(
target,
channel,
argv(0),
g_sounds[sample].volume * volume,
radius,
pitch,
flag,
g_sounds[sample].offset
);
}
#else
void
Sound_Speak(entity target, string shader)
{
int r;
float radius;
float pitch;
int flag;
int sample;
sample = (int)hash_get(g_hashsounds, shader, -1);
if (sample < 0) {
NSError("SoundDef %S is not precached", shader);
return;
}
/* pick a sample */
r = floor(random(0, g_sounds[sample].sample_count));
tokenizebyseparator(g_sounds[sample].samples, "\n");
/* set pitch */
pitch = random(g_sounds[sample].pitch_min, g_sounds[sample].pitch_max);
radius = g_sounds[sample].dist_max;
flag = 0;
/* flags */
if (g_sounds[sample].flags & SNDFL_NOREVERB) {
flag |= SOUNDFLAG_NOREVERB;
}
if (g_sounds[sample].flags & SNDFL_GLOBAL) {
radius = ATTN_NONE;
}
if (g_sounds[sample].flags & SNDFL_LOOPING) {
flag |= SOUNDFLAG_FORCELOOP;
}
if (g_sounds[sample].flags & SNDFL_NODUPS) {
if (g_sounds[sample].playc >= g_sounds[sample].sample_count) {
g_sounds[sample].playc = 0;
}
r = g_sounds[sample].playc++;
}
if (g_sounds[sample].flags & SNDFL_FOLLOW) {
flag |= SOUNDFLAG_FOLLOW;
}
if (g_sounds[sample].flags & SNDFL_PRIVATE) {
flag |= SOUNDFLAG_UNICAST;
msg_entity = target;
}
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_SPEAK);
WriteEntity(MSG_MULTICAST, target);
WriteString(MSG_MULTICAST, argv(r));
WriteFloat(MSG_MULTICAST, pitch);
msg_entity = target;
multicast(target.origin, MULTICAST_PVS);
}
#endif
/* utterly basic (and probably wrong...) support for Source Engine sound definitions */
void
SoundSource_Init(void)
{
const string manifestStart = "scripts/game_sounds_manifest.txt";
filestream manifestFile;
string lineStream;
manifestFile = fopen(manifestStart, FILE_READ);
/* file doesn't exist. that's okay. */
if (manifestFile < 0) {
//error(sprintf("loading %S failed.\n", manifestStart));
return;
}
while ((lineStream = fgets(manifestFile))) {
/* when we found it, quit */
int c = tokenize(lineStream);
string key = argv(0);
string value = argv(1);
if (c == 2) {
if (key == "precache_file") {
#if 1
Sound_PrecacheFile(value);
#endif
}
}
}
fclose(manifestFile);
}
/** Called by listSoundDef */
void
Sound_DebugList(void)
{
for (int i = 0; i < g_sounds_count; i++) {
print(sprintf("%i: %s (%i samples)\n", i, g_sounds[i].name, g_sounds[i].sample_count));
}
print(sprintf("\t%i total soundDef loaded\n", g_sounds_count));
}