raze/polymer/eduke32/source/jfaud_sounds.cpp

768 lines
18 KiB
C++

/*
* Audio support for JFDuke3D using JFAud
* by Jonathon Fowler (jonof@edgenetwork.org)
*
* Duke Nukem 3D 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.
*
* Original Source: 1996 - Todd Replogle
* Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms
*/
#include "types.h"
#include "duke3d.h"
extern "C" {
#ifdef RENDERTYPEWIN
# include "winlayer.h"
#endif
#include "osd.h"
long numenvsnds;
}
typedef uint64 uint64_t;
#ifdef __APPLE__
# include <jfaud/jfaud.hpp>
#else
# include "jfaud.hpp"
#endif
#define SOUNDM_LOOP 1
#define SOUNDM_MSFX 2
#define SOUNDM_DUKE 4
#define SOUNDM_PARENT 8
#define SOUNDM_GLOBAL 16
#define SOUNDM_NICE 64 // Added for JFDuke3D so JFAud doesn't use nearest filtering for the sound
#define SOUNDM_PLAYER 128
#define UNITSPERMETRE 1024.0
#define DEFAULTREFDIST (6720.0/1024.0) // in the original code, ((255-150)<<6) == 6720
#define DEFAULTMAXDIST (31444.0/1024.0) // in the original code, 31444
#define DEFAULTROLLOFF 1.0//0.75
#define OCCLUDEDFACTOR 0.8
static JFAudMixerChannel::Filter DefaultFilter = JFAudMixerChannel::FilterNearest;
static int osdcmd_setsoundfilter(const osdfuncparm_t *parm);
#ifdef SCREWED_UP_CPP
# include "watcomhax/cmath"
#else
# include <cmath>
#endif
#define MAXCACHE1DSIZE (16*1048576)
class KenFile : public JFAudFile {
private:
int fh;
public:
KenFile(const char *filename, const char *subfilename)
: JFAudFile(filename, subfilename)
{
fh = kopen4load(const_cast<char*>(filename), 0);
}
virtual ~KenFile()
{
if (fh >= 0) kclose(fh);
}
virtual bool IsOpen(void) const { return fh >= 0; }
virtual long Read(long nbytes, void *buf)
{
if (fh < 0) return -1;
return kread(fh, buf, nbytes);
}
virtual long Seek(long pos, SeekFrom where)
{
int when;
if (fh < 0) return -1;
switch (where) {
case JFAudFile::Set: when = SEEK_SET; break;
case JFAudFile::Cur: when = SEEK_CUR; break;
case JFAudFile::End: when = SEEK_END; break;
default: return -1;
}
return klseek(fh, pos, when);
}
virtual long Tell(void) const
{
if (fh < 0) return -1;
return klseek(fh, 0, SEEK_CUR);
}
virtual long Length(void) const
{
if (fh < 0) return -1;
return kfilelength(fh);
}
};
static JFAudFile *openfile(const char *fn, const char *subfn)
{
return static_cast<JFAudFile*>(new KenFile(fn,subfn));
}
static void logfunc(const char *s) { initprintf("jfaud: %s", s); }
#define PITCHRANGE 2 // octave range in each direction
#define PITCHSTEPS 24 // pitch points per octave
static float pitchtable[PITCHRANGE*PITCHSTEPS*2+1];
static void buildpitchtable(void)
{
int i,j;
for (i=-PITCHRANGE*PITCHSTEPS; i<=PITCHRANGE*PITCHSTEPS; i++) {
pitchtable[i+PITCHRANGE*PITCHSTEPS] = pow(1.0005777895, (1200.0/PITCHSTEPS)*(float)i);
}
}
static float translatepitch(int p)
{
float t;
int x;
x = (p * PITCHSTEPS / 1200) + PITCHRANGE*PITCHSTEPS;
if (x < 0) x = 0;
else if (x > (int)(sizeof(pitchtable)/sizeof(float))) x = sizeof(pitchtable)/sizeof(float);
t = pitchtable[x];
/*if (t > 2.0) {
initprintf("translatepitch(%d) > 2.0\n", p);
t = 2.0;
}*/
return t;
}
class SoundChannel {
public:
JFAudMixerChannel *chan;
int owner; // sprite number
int soundnum; // sound number
bool done;
SoundChannel()
{
chan = NULL;
owner = -1;
soundnum = -1;
done = false;
}
};
static SoundChannel *chans = NULL;
static JFAud *jfaud = NULL;
static bool havemidi = false, havewave = false;
static void stopcallback(int r)
{
chans[r].done = true;
}
void testcallback(unsigned long num)
{
}
static int keephandle(JFAudMixerChannel *handle, int soundnum, int owner)
{
int i, freeh=-1;
for (i=NumVoices-1;i>=0;i--) {
if (!chans[i].chan && freeh<0) freeh=i;
else if (chans[i].chan == handle) { freeh=i; break; }
}
if (freeh<0) {
initprintf("Warning: keephandle() exhausted handle space!\n");
return -1;
}
chans[freeh].chan = handle;
chans[freeh].soundnum = soundnum;
chans[freeh].owner = owner;
chans[freeh].done = false;
return freeh;
}
void SoundStartup(void)
{
int i;
if (FXDevice < 0) return;
if (jfaud) return;
buildpitchtable();
JFAud_SetLogFunc(logfunc);
jfaud = new JFAud();
if (!jfaud) return;
jfaud->SetUserOpenFunc(openfile);
#ifdef _WIN32
jfaud->SetWindowHandle((void*)win_gethwnd());
#endif
havewave = havemidi = false;
if (!jfaud->InitWave("software", NumVoices, MixRate)) {
delete jfaud;
jfaud = NULL;
return;
}
{
// the engine will take 60% of the system memory size for cache1d if there
// is less than the 16MB asked for in loadpics(), so we'll
// take 30% of what's left for the sound cache if that happened, or
// 50% of the system memory sans the 16MB maximum otherwise
unsigned k;
if (Bgetsysmemsize() <= MAXCACHE1DSIZE)
k = Bgetsysmemsize()/100*30;
else
k = Bgetsysmemsize()/100*50 - MAXCACHE1DSIZE;
jfaud->SetCacheSize(k,k/2);
jfaud->SetCacheItemAge(24*120); // 24 movements per second, 120 seconds max lifetime
}
chans = new SoundChannel[NumVoices];
if (!chans) {
delete jfaud;
jfaud = NULL;
return;
}
havewave = true;
if (jfaud->InitMIDI(NULL)) havemidi = true;
OSD_RegisterFunction("setsoundfilter","setsoundfilter: 0=nearest 1=linear 2=4point",osdcmd_setsoundfilter);
}
void SoundShutdown(void)
{
if (jfaud) delete jfaud;
if (chans) delete [] chans;
jfaud = NULL;
chans = NULL;
havewave = havemidi = false;
}
void MusicStartup(void)
{
}
void MusicShutdown(void)
{
}
void AudioUpdate(void)
{
int i;
if (!jfaud) return;
if (havewave)
for (i=NumVoices-1; i>=0; i--) {
if (!chans[i].done) continue;
if (chans[i].chan) jfaud->FreeSound(chans[i].chan);
chans[i].chan = NULL;
chans[i].done = false;
if (chans[i].owner >= 0 &&
sprite[chans[i].owner].picnum == MUSICANDSFX &&
sector[sprite[chans[i].owner].sectnum].lotag < 3 &&
sprite[chans[i].owner].lotag < 999)
hittype[chans[i].owner].temp_data[0] = 0;
}
jfaud->Update(false); // don't age the cache here
}
static char menunum = 0;
void intomenusounds(void)
{
short i;
short menusnds[] = {
LASERTRIP_EXPLODE, DUKE_GRUNT, DUKE_LAND_HURT,
CHAINGUN_FIRE, SQUISHED, KICK_HIT,
PISTOL_RICOCHET, PISTOL_BODYHIT, PISTOL_FIRE,
SHOTGUN_FIRE, BOS1_WALK, RPG_EXPLODE,
PIPEBOMB_BOUNCE, PIPEBOMB_EXPLODE, NITEVISION_ONOFF,
RPG_SHOOT, SELECT_WEAPON
};
sound(menusnds[menunum++]);
menunum %= sizeof(menusnds)/sizeof(menusnds[0]);
}
void playmusic(char *fn)
{
char dafn[BMAX_PATH], *dotpos;
int i;
const char *extns[] = { ".ogg",".mp3",".mid", NULL };
if (!MusicToggle) return;
if (!jfaud) return;
dotpos = Bstrrchr(fn,'.');
if (dotpos && Bstrcasecmp(dotpos,".mid")) {
// has extension but isn't midi
jfaud->PlayMusic(fn, NULL);
} else {
Bstrcpy(dafn,fn);
dotpos = Bstrrchr(dafn,'.');
if (!dotpos) dotpos = dafn+strlen(dafn);
for (i=0; extns[i]; i++) {
Bstrcpy(dotpos, extns[i]);
if (jfaud->PlayMusic(dafn, NULL)) return;
}
}
}
char loadsound(unsigned short num) { return 1; }
int isspritemakingsound(short i, int num) // if num<0, check if making any sound at all
{
int j,n=0;
if (!jfaud || !havewave) return 0;
for (j=NumVoices-1; j>=0; j--) {
if (chans[j].done || !chans[j].chan) continue;
if (chans[j].owner != i) continue;
if (num < 0 || chans[j].soundnum == num) n++;
}
return n;
}
int issoundplaying(short i, int num)
{
int j,n=0;
if (!jfaud || !havewave) return 0;
for (j=NumVoices-1; j>=0; j--) {
if (chans[j].done || !chans[j].chan) continue;
if (chans[j].soundnum == num) n++;
}
return n;
}
int xyzsound(short num, short i, long x, long y, long z)
{
JFAudMixerChannel *chan;
int r, global = 0;
float gain = 1.0, pitch = 1.0, refdist = DEFAULTREFDIST, maxdist = DEFAULTMAXDIST, rolloff = DEFAULTROLLOFF;
if (!jfaud || !havewave ||
num >= NUM_SOUNDS ||
((soundm[num] & SOUNDM_PARENT) && ud.lockout) || // parental mode
SoundToggle == 0 ||
(ps[myconnectindex].timebeforeexit > 0 && ps[myconnectindex].timebeforeexit <= 26*3) ||
(ps[myconnectindex].gm & MODE_MENU)
) return -1;
if (soundm[num] & SOUNDM_PLAYER) {
sound(num);
return 0;
}
if (soundm[num] & SOUNDM_DUKE) {
// Duke speech, one at a time only
int j;
if (VoiceToggle == 0 ||
(ud.multimode > 1 && PN == APLAYER && sprite[i].yvel != screenpeek && ud.coop != 1)
) return -1;
for (j=NumVoices-1; j>=0; j--) {
if (chans[j].done || !chans[j].chan || chans[j].owner < 0) continue;
if (soundm[ chans[j].soundnum ] & SOUNDM_DUKE) return -1;
}
}
if( i >= 0 && !(soundm[num] & SOUNDM_GLOBAL) && PN == MUSICANDSFX && SLT < 999 && (sector[SECT].lotag&0xff) < 9) {
float d = (float)SHT/UNITSPERMETRE;
refdist = d / 2.0;
maxdist = d;
rolloff = 1.0;
}
{
int ps = soundps[num], pe = soundpe[num], cx;
cx = labs(pe-ps);
if (cx) {
if (ps < pe) pitch = translatepitch(ps + rand()%cx);
else pitch = translatepitch(pe + rand()%cx);
} else pitch = translatepitch(ps);
}
{
float d = 1.0-(float)soundvo[num]/(12.0*1024.0);
maxdist *= d;
refdist *= d;
}
if (PN != MUSICANDSFX &&
!cansee(ps[screenpeek].oposx,ps[screenpeek].oposy,ps[screenpeek].oposz-(24<<8),
ps[screenpeek].cursectnum,SX,SY,SZ-(24<<8),SECT) )
gain *= OCCLUDEDFACTOR;
switch(num)
{
case PIPEBOMB_EXPLODE:
case LASERTRIP_EXPLODE:
case RPG_EXPLODE:
gain = 1.0;
global = 1;
if (sector[ps[screenpeek].cursectnum].lotag == 2) pitch -= translatepitch(1024);
break;
default:
if(sector[ps[screenpeek].cursectnum].lotag == 2 && (soundm[num]&SOUNDM_DUKE) == 0)
pitch = translatepitch(-768);
//if( sndist > 31444 && PN != MUSICANDSFX)
// return -1;
break;
}
/*
// XXX: this is shit
if( Sound[num].num > 0 && PN != MUSICANDSFX )
{
if( SoundOwner[num][0].i == i ) stopsound(num);
else if( Sound[num].num > 1 ) stopsound(num);
else if( badguy(&sprite[i]) && sprite[i].extra <= 0 ) stopsound(num);
}
*/
chan = jfaud->PlaySound(sounds[num], NULL, soundpr[num]);
if (!chan) return -1;
chan->SetGain(gain);
chan->SetPitch(pitch);
chan->SetLoop((soundm[num] & SOUNDM_LOOP) == SOUNDM_LOOP);
if (soundm[num] & SOUNDM_GLOBAL) global = 1;
chan->SetRefDist(refdist);
chan->SetMaxDist(maxdist);
chan->SetFilter((soundm[num]&SOUNDM_NICE) ? JFAudMixerChannel::Filter4Point : DefaultFilter);
chan->SetDistanceModel(JFAudMixerChannel::DistanceModelLinear);
if (PN == APLAYER && sprite[i].yvel == screenpeek) {
chan->SetRolloff(0.0);
chan->SetFollowListener(true);
chan->SetPosition(0.0, 0.0, 0.0);
} else {
chan->SetRolloff(global ? 0.0 : rolloff);
chan->SetFollowListener(false);
chan->SetPosition((float)x/UNITSPERMETRE, (float)(-z>>4)/UNITSPERMETRE, (float)y/UNITSPERMETRE);
}
r = keephandle(chan, num, i);
if (r >= 0) chan->SetStopCallback(stopcallback, r);
chan->Play();
return 0;
}
void sound(short num)
{
JFAudMixerChannel *chan;
int r;
float pitch = 1.0;
if (!jfaud || !havewave ||
num >= NUM_SOUNDS ||
SoundToggle == 0 ||
((soundm[num] & SOUNDM_DUKE) && VoiceToggle == 0) ||
((soundm[num] & SOUNDM_PARENT) && ud.lockout) // parental mode
) return;
{
int ps = soundps[num], pe = soundpe[num], cx;
cx = labs(pe-ps);
if (cx) {
if (ps < pe) pitch = translatepitch(ps + rand()%cx);
else pitch = translatepitch(pe + rand()%cx);
} else pitch = translatepitch(ps);
}
chan = jfaud->PlaySound(sounds[num], NULL, soundpr[num]);
if (!chan) return;
chan->SetGain(1.0);
chan->SetPitch(pitch);
chan->SetLoop((soundm[num] & SOUNDM_LOOP) == SOUNDM_LOOP);
chan->SetRolloff(0.0);
chan->SetRefDist(DEFAULTREFDIST);
chan->SetFollowListener(true);
chan->SetPosition(0.0, 0.0, 0.0);
chan->SetFilter((soundm[num]&SOUNDM_NICE) ? JFAudMixerChannel::Filter4Point : DefaultFilter);
chan->SetDistanceModel(JFAudMixerChannel::DistanceModelLinear);
r = keephandle(chan, num, -1);
if (r >= 0) chan->SetStopCallback(stopcallback, r);
chan->Play();
}
int spritesound(unsigned short num, short i)
{
if (num >= NUM_SOUNDS) return -1;
return xyzsound(num,i,SX,SY,SZ);
}
void stopsound(short num)
{
int j;
if (!jfaud || !havewave) return;
for (j=NumVoices-1;j>=0;j--) {
if (chans[j].done || !chans[j].chan || chans[j].soundnum != num) continue;
jfaud->FreeSound(chans[j].chan);
chans[j].chan = NULL;
chans[j].done = false;
}
}
void stopspritesound(short num, short i)
{
int j;
if (!jfaud || !havewave) return;
for (j=NumVoices-1;j>=0;j--) {
if (chans[j].done || !chans[j].chan || chans[j].owner != i || chans[j].soundnum != num) continue;
jfaud->FreeSound(chans[j].chan);
chans[j].chan = NULL;
chans[j].done = false;
return;
}
}
void stopenvsound(short num, short i)
{
int j;
if (!jfaud || !havewave) return;
for (j=NumVoices-1;j>=0;j--) {
if (chans[j].done || !chans[j].chan || chans[j].owner != i) continue;
jfaud->FreeSound(chans[j].chan);
chans[j].chan = NULL;
chans[j].done = false;
}
}
void pan3dsound(void)
{
JFAudMixer *mix;
int j, global;
short i;
long cx, cy, cz, sx,sy,sz;
short ca,cs;
float gain, rolloff;
numenvsnds = 0;
if (!jfaud || !havewave) return;
mix = jfaud->GetWave();
if (!mix) return;
jfaud->AgeCache();
if(ud.camerasprite == -1) {
cx = ps[screenpeek].oposx;
cy = ps[screenpeek].oposy;
cz = ps[screenpeek].oposz;
cs = ps[screenpeek].cursectnum;
ca = ps[screenpeek].ang+ps[screenpeek].look_ang;
} else {
cx = sprite[ud.camerasprite].x;
cy = sprite[ud.camerasprite].y;
cz = sprite[ud.camerasprite].z;
cs = sprite[ud.camerasprite].sectnum;
ca = sprite[ud.camerasprite].ang;
}
mix->SetListenerPosition((float)cx/UNITSPERMETRE, (float)(-cz>>4)/UNITSPERMETRE, (float)cy/UNITSPERMETRE);
mix->SetListenerOrientation((float)sintable[(ca+512)&2047]/16384.0, 0.0, (float)sintable[ca&2047]/16384.0,
0.0, 1.0, 0.0);
for (j=NumVoices-1; j>=0; j--) {
if (chans[j].done || !chans[j].chan || chans[j].owner < 0) continue;
global = 0;
gain = 1.0;
rolloff = DEFAULTROLLOFF;
i = chans[j].owner;
sx = sprite[i].x;
sy = sprite[i].y;
sz = sprite[i].z;
if (PN != MUSICANDSFX && !cansee(cx,cy,cz-(24<<8),cs,sx,sy,sz-(24<<8),SECT) )
gain *= OCCLUDEDFACTOR;
if(PN == MUSICANDSFX && SLT < 999) rolloff = 1.0;
if( soundm[ chans[j].soundnum ]&SOUNDM_GLOBAL ) global = 1;
switch(chans[j].soundnum) {
case PIPEBOMB_EXPLODE:
case LASERTRIP_EXPLODE:
case RPG_EXPLODE:
gain = 1.0;
global = 1;
break;
default:
//if( sndist > 31444 && PN != MUSICANDSFX) {
// stopsound(j);
// continue;
//}
break;
}
// A sound may move from player-relative 3D if the viewpoint shifts from the player
// through a viewscreen or viewpoint switching
chans[j].chan->SetGain(gain);
if (PN == APLAYER && sprite[i].yvel == screenpeek) {
chans[j].chan->SetRolloff(0.0);
chans[j].chan->SetFollowListener(true);
chans[j].chan->SetPosition(0.0, 0.0, 0.0);
} else {
chans[j].chan->SetRolloff(global ? 0.0 : rolloff);
chans[j].chan->SetFollowListener(false);
chans[j].chan->SetPosition((float)sx/UNITSPERMETRE, (float)(-sz>>4)/UNITSPERMETRE, (float)sy/UNITSPERMETRE);
}
}
}
void clearsoundlocks(void)
{
}
void FX_SetVolume( int volume )
{
}
void FX_SetReverseStereo( int setting )
{
}
void FX_SetReverb( int reverb )
{
}
void FX_SetReverbDelay( int delay )
{
}
int FX_VoiceAvailable( int priority )
{
if (!jfaud) return 0;
return 1;
}
int FX_PlayVOC3D( char *ptr, int pitchoffset, int angle, int distance,
int priority, unsigned long callbackval )
{
printf("FX_PlayVOC3D()\n");
return 0;
}
int FX_PlayWAV3D( char *ptr, int pitchoffset, int angle, int distance,
int priority, unsigned long callbackval )
{
printf("FX_PlayWAV3D()\n");
return 0;
}
int FX_StopSound( int handle )
{
printf("FX_StopSound()\n");
return 0;
}
int FX_StopAllSounds( void )
{
int j;
if (!jfaud || !havewave) return 0;
for (j=NumVoices-1; j>=0; j--) {
if (!chans[j].chan || !jfaud->IsValidSound(chans[j].chan)) continue;
jfaud->FreeSound(chans[j].chan);
chans[j].chan = NULL;
chans[j].owner = -1;
}
return 0;
}
void MUSIC_SetVolume( int volume )
{
}
void MUSIC_Pause( void )
{
if (jfaud) jfaud->PauseMusic(true);
}
void MUSIC_Continue( void )
{
if (jfaud) jfaud->PauseMusic(false);
}
int MUSIC_StopSong( void )
{
if (jfaud) jfaud->StopMusic();
return 0;
}
void MUSIC_RegisterTimbreBank( unsigned char *timbres )
{
}
static int osdcmd_setsoundfilter(const osdfuncparm_t *parm)
{
int filt = 0;
if (parm->numparms < 1) return OSDCMD_SHOWHELP;
filt = Batol(parm->parms[0]);
if (filt < JFAudMixerChannel::FilterNearest) filt = JFAudMixerChannel::FilterNearest;
else if (filt > JFAudMixerChannel::Filter4Point) filt = JFAudMixerChannel::Filter4Point;
DefaultFilter = (JFAudMixerChannel::Filter)filt;
return OSDCMD_OK;
}
int EnumAudioDevs(struct audioenumdrv **wave, struct audioenumdev **midi, struct audioenumdev **cda)
{
char **enumerdrv, *defdrv;
int i;
bool isdefdrv, isdefdev;
struct audioenumdev **d;
*wave = NULL;
//*midi = *cda = NULL;
enumerdrv = JFAud::EnumerateWaveDevices(NULL, &defdrv);
if (enumerdrv) {
*wave = (struct audioenumdrv *)calloc(1,sizeof(struct audioenumdrv));
(*wave)->def = defdrv;
(*wave)->drvs = enumerdrv;
(*wave)->devs = NULL;
d = &(*wave)->devs;
for (i=0; enumerdrv[i]; i++) {
*d = (struct audioenumdev *)calloc(1,sizeof(struct audioenumdev));
(*d)->devs = JFAud::EnumerateWaveDevices(enumerdrv[i], &(*d)->def);
(*d)->next = NULL;
d = &(*d)->next;
}
}
return 0;
}