ref: 7110aba73616e4f84b933a0db1ff96c4c22a9d6f
dir: /i_sound.c/
//************************************************************************** //** //** i_soundpi.c: unix sound driver using a plugin interface //** //** $Revision: 512 $ //** $Date: 2009-06-04 18:00:34 +0300 (Thu, 04 Jun 2009) $ //** //************************************************************************** #include "h2stdinc.h" #include "doomdef.h" #include "sounds.h" #include "i_sound.h" #include "audio_plugin.h" #define SAMPLE_ZERO 0 #define SAMPLE_RATE 11025 /* Hz */ #define SAMPLE_CHANNELS 2 #define TARGET_RATE 44100 #define SAMPLE_TYPE short /* * SOUND HEADER & DATA */ int snd_Channels; int snd_MaxVolume, /* maximum volume for sound */ snd_MusicVolume; /* maximum volume for music */ boolean snd_MusicAvail, /* whether music is available */ snd_SfxAvail; /* whether sfx are available */ /* * SOUND FX API */ typedef struct { unsigned char *begin; /* pointers into Sample.firstSample */ unsigned char *end; SAMPLE_TYPE *lvol_table; /* point into vol_lookup */ SAMPLE_TYPE *rvol_table; unsigned int pitch_step; unsigned int step_remainder; /* 0.16 bit remainder of last step. */ int pri; unsigned int time; } Channel; #pragma pack on typedef struct { /* Sample data is a lump from a wad: byteswap the a, freq * and the length fields before using them */ short a; /* always 3 */ short freq; /* always 11025 */ int32_t length; /* sample length */ unsigned char firstSample; } Sample; #pragma pack off static int audio_exit_thread = 1; #define CHAN_COUNT 8 static Channel channel[CHAN_COUNT]; #define MAX_VOL 64 /* 64 keeps our table down to 16Kb */ static SAMPLE_TYPE vol_lookup[MAX_VOL * 256]; static int steptable[256]; /* Pitch to stepping lookup */ #define BUF_LEN (256 * 2 * 4) static int audiofd; static int audiopid = -1; static QLock audiolk; boolean mus_paused = false; static int mpfd[2] = {-1, -1}; void I_ShutdownMusic(void); static void audioproc(void) { Channel* chan; Channel* cend; static char buf[BUF_LEN]; SAMPLE_TYPE *begin; SAMPLE_TYPE *end; unsigned int sample; register int dl, ml; register int dr, mr; int i; end = (SAMPLE_TYPE *) (buf + BUF_LEN); cend = channel + CHAN_COUNT; for(;;){ memset(buf, 0, sizeof buf); if(mpfd[0]>=0 && !mus_paused && readn(mpfd[0], buf, sizeof buf) < 0){ fprint(2, "I_UpdateSound: disabling music: %r\n"); I_ShutdownMusic(); } begin = (SAMPLE_TYPE *) buf; while (begin < end){ // Mix all the channels together. dl = SAMPLE_ZERO; dr = SAMPLE_ZERO; qlock(&audiolk); chan = channel; for ( ; chan < cend; chan++){ if(!chan->begin) continue; // Get the sample from the channel. sample = *chan->begin; // Adjust volume accordingly. dl += chan->lvol_table[sample]; dr += chan->rvol_table[sample]; // Increment sample pointer with pitch adjustment. chan->step_remainder += chan->pitch_step; chan->begin += chan->step_remainder >> 16; chan->step_remainder &= 65535; // Check whether we are done. if (chan->begin >= chan->end) { chan->begin = NULL; //printf (" channel done %d\n", chan); } } qunlock(&audiolk); for(i=0; i < TARGET_RATE/SAMPLE_RATE; i++){ ml = dl + *begin; if (ml > 0x7fff) ml = 0x7fff; else if (ml < -0x8000) ml = -0x8000; *begin++ = ml; mr = dr + *begin; if (mr > 0x7fff) mr = 0x7fff; else if (mr < -0x8000) mr = -0x8000; *begin++ = mr; } } write(audiofd, buf, BUF_LEN); } } void I_SetSfxVolume(int volume) { USED(volume); } // Gets lump nums of the named sound. Returns pointer which will be // passed to I_StartSound() when you want to start an SFX. Must be // sure to pass this to UngetSoundEffect() so that they can be // freed! int I_GetSfxLumpNum(sfxinfo_t *sound) { if (sound->name[0] == 0) return 0; if (sound->link) sound = sound->link; return W_GetNumForName(sound->name); } // Id is unused. // Data is a pointer to a Sample structure. // Volume ranges from 0 to 127. // Separation (orientation/stereo) ranges from 0 to 255. 128 is balanced. // Pitch ranges from 0 to 255. Normal is 128. // Priority looks to be unused (always 0). int I_StartSound(int id, void *data, int vol, int sep, int pitch, int priority) { // Relative time order to find oldest sound. static unsigned int soundTime = 0; int chanId; Sample *sample; Channel *chan; int oldest; int i; USED(id); // Find an empty channel, the oldest playing channel, or default to 0. // Currently ignoring priority. chanId = 0; oldest = soundTime; for (i = 0; i < CHAN_COUNT; i++) { if (! channel[ i ].begin) { chanId = i; break; } if (channel[ i ].time < oldest) { chanId = i; oldest = channel[ i ].time; } } sample = (Sample *) data; chan = &channel[chanId]; I_UpdateSoundParams(chanId + 1, vol, sep, pitch); // begin must be set last because the audio thread will access the channel // once it is non-zero. Perhaps this should be protected by a mutex. chan->pri = priority; chan->time = soundTime; chan->end = &sample->firstSample + LONG(sample->length); chan->begin = &sample->firstSample; soundTime++; #if 0 printf ("I_StartSound %d: v:%d s:%d p:%d pri:%d | %d %d %d %d\n", id, vol, sep, pitch, priority, chanId, chan->pitch_step, SHORT(sample->a), SHORT(sample->freq)); #endif return chanId + 1; } void I_StopSound(int handle) { handle--; handle &= 7; channel[handle].begin = NULL; } int I_SoundIsPlaying(int handle) { handle--; handle &= 7; return (channel[ handle ].begin != NULL); } void I_UpdateSoundParams(int handle, int vol, int sep, int pitch) { int lvol, rvol; Channel *chan; qlock(&audiolk); // Set left/right channel volume based on seperation. sep += 1; // range 1 - 256 lvol = vol - ((vol * sep * sep) >> 16); // (256*256); sep = sep - 257; rvol = vol - ((vol * sep * sep) >> 16); // Sanity check, clamp volume. if (rvol < 0) { // printf ("rvol out of bounds %d, id %d\n", rvol, handle); rvol = 0; } else if (rvol > 127) { // printf ("rvol out of bounds %d, id %d\n", rvol, handle); rvol = 127; } if (lvol < 0) { // printf ("lvol out of bounds %d, id %d\n", lvol, handle); lvol = 0; } else if (lvol > 127) { // printf ("lvol out of bounds %d, id %d\n", lvol, handle); lvol = 127; } // Limit to MAX_VOL (64) lvol >>= 1; rvol >>= 1; handle--; handle &= 7; chan = &channel[handle]; chan->pitch_step = steptable[pitch]; chan->step_remainder = 0; chan->lvol_table = &vol_lookup[lvol * 256]; chan->rvol_table = &vol_lookup[rvol * 256]; qunlock(&audiolk); } /* * SOUND STARTUP STUFF */ // inits all sound stuff void I_StartupSound (void) { snd_SfxAvail = false; if (M_CheckParm("--nosound") || M_CheckParm("-s") || M_CheckParm("-nosound")) { fprintf(stderr, "I_StartupSound: Sound Disabled.\n"); return; } audiofd = open("/dev/audio", OWRITE); if(audiofd < 0){ fprintf(stderr, "I_StartupSound: /dev/audio could not be opened\n"); return; } snd_SfxAvail = true; if((audiopid = rfork(RFPROC|RFMEM)) == 0){ audioproc(); exits(nil); } } // shuts down all sound stuff void I_ShutdownSound (void) { snd_SfxAvail = false; if(audiopid != -1){ postnote(PNPROC, audiopid, "shutdown"); audiopid = -1; } I_ShutdownMusic(); } void I_SetChannels(int channels) { int v, j; int *steptablemid; // We always have CHAN_COUNT channels. USED(channels); for (j = 0; j < CHAN_COUNT; j++) { channel[j].begin = NULL; channel[j].end = NULL; channel[j].time = 0; } // This table provides step widths for pitch parameters. steptablemid = steptable + 128; for (j = -128; j < 128; j++) { steptablemid[j] = (int) (pow(2.0, (j/64.0)) * 65536.0); } // Generate the volume lookup tables. for (v = 0; v < MAX_VOL; v++) { for (j = 0; j < 256; j++) { // vol_lookup[v*256+j] = 128 + ((v * (j-128)) / (MAX_VOL-1)); // Turn the unsigned samples into signed samples. vol_lookup[v*256+j] = (v * (j-128) * 256) / (MAX_VOL-1); // printf ("vol_lookup[%d*256+%d] = %d\n", v, j, vol_lookup[v*256+j]); } } } /* * SONG API */ static int didgen = 0; static void genmidi(void) { int fd, n, sz; char name[64]; uchar *gm; n = W_GetNumForName("GENMIDI"); sz = W_LumpLength(n); gm = (uchar *)W_CacheLumpNum(n, PU_STATIC); snprint(name, sizeof(name), "/tmp/genmidi.%d", getpid()); if((fd = create(name, ORDWR|ORCLOSE, 0666)) < 0) sysfatal("create: %r"); if(write(fd, gm, sz) != sz) sysfatal("write: %r"); Z_Free(gm); } void I_ShutdownMusic(void) { if(mpfd[0] >= 0){ close(mpfd[0]); mpfd[0] = -1; waitpid(); } } /* In theory this allows register step allows * the use of external files in place of internal ones. */ static void *currentsong = nil; static int currentsize = 0; int I_RegisterSong(void *data, int siz) { if(!didgen){ genmidi(); didgen++; } if(currentsong != nil) return 0; currentsong = data; currentsize = siz; return 1; } int I_RegisterExternalSong(const char *nm) { USED(nm); return 0; } void I_UnRegisterSong(int handle) { USED(handle); currentsong = nil; } void I_PauseSong(int handle) { if(handle <= 0) return; mus_paused = true; } void I_ResumeSong(int handle) { if(handle <= 0) return; mus_paused = false; } void I_SetMusicVolume(int volume) { USED(volume); } int I_QrySongPlaying(int handle) { USED(handle); return 0; } // Stops a song. MUST be called before I_UnregisterSong(). void I_StopSong(int handle) { if(handle <= 0) return; I_ShutdownMusic(); } void I_PlaySong(int handle, boolean loop) { char name[64]; int n; if(M_CheckParm("-nomusic") || audiofd < 0 || handle <= 0) return; I_ShutdownMusic(); if(pipe(mpfd) < 0) return; switch(rfork(RFPROC|RFFDG|RFNAMEG)){ case -1: fprint(2, "I_PlaySong: %r\n"); break; case 0: dup(mpfd[1], 1); for(n=3; n<20; n++) close(n); close(0); snprint(name, sizeof(name), "/tmp/heretic.%d", getpid()); if(create(name, ORDWR|ORCLOSE, 0666) != 0) sysfatal("create: %r"); if(write(0, currentsong, currentsize) != currentsize) sysfatal("write: %r"); if(seek(0, 0, 0) != 0) sysfatal("seek: %r"); if(bind("/fd/1", "/dev/audio", MREPL) == -1) sysfatal("bind: %r"); while(loop && fork() > 0){ if(waitpid() < 0 || write(1, "", 0) < 0) exits(nil); } execl("/bin/dmus", "dmus", name, nil); execl("/bin/play", "play", name, nil); sysfatal("execl: %r"); default: close(mpfd[1]); } }