#define JFAUD_INTERNAL #include "sysdefs.h" #ifdef SCREWED_UP_CPP # include "watcomhax/cstdlib" # include "watcomhax/cstring" # include "watcomhax/cmath" # include "watcomhax/cstdio" #else # include # include # include # include #endif #ifndef M_PI # define M_PI 3.14159265358979323846 #endif #include "softwaremixer.hpp" #include "waveout.hpp" #include "log.h" #ifndef SCREWED_UP_CPP using namespace std; #endif #define STREAMCHUNK 22050 // samples. 1sec @ 22.05KHz //#define TESTTONE #define CHANGED_BUFFER 1 #define CHANGED_RATE 2 #define CHANGED_POSITION 4 #define SHIFTFACT 8 #define OUTFACT (8+2) //#define SHIFTFACT 8 //#define OUTFACT (8+1) //{{{ Distance attenuation functions float SoftwareMixerChannel::DistanceLinear(float distance, float refdist, float maxdist, float rolloff) { if (rolloff <= 0.0) return 1.0; if (distance <= refdist) return 1.0; if (distance >= maxdist) return 0.0; return 1.0-rolloff*((distance-refdist)/(maxdist-refdist)); } float SoftwareMixerChannel::DistanceInverse(float distance, float refdist, float maxdist, float rolloff) { if (rolloff <= 0.0) return 1.0; if (distance <= refdist) return 1.0; if (distance >= maxdist) return 0.0; float f = refdist + (rolloff * (distance - refdist)); if (f <= 0.0) return 1.0; return refdist/f; } //}}} //{{{ Sample interpolation functions #define COEFTABLESIZE 5 // 1 LSH this many bits static short CoefTable4[1<bufferp[ (chan->samplepos>>32)*stride+offset ]; } int SoftwareMixerChannel::InterpolateLinear(SoftwareMixerChannel *chan, int offset, int stride) { int sample1, sample2; int s; unsigned int sub; s = chan->samplepos>>32; sample1 = chan->bufferp[ s*stride + offset ]; s++; if (s < chan->buffer->GetNumSamples()) { sample2 = chan->bufferp[ s*stride + offset ]; } else { s -= chan->buffer->GetNumSamples(); if (!chan->streamed) { sample2 = chan->bufferp[ s*stride + offset ]; } else { if (!chan->buffer2) return sample1; // no buffer2 yet so the best we can do is nearest if (chan->buffer2->GetNumSamples() <= s) return sample1; // somehow we jumped buffer2 completely sample2 = ((short*)chan->buffer2->GetData())[ s*stride + offset ]; } } sub = chan->samplepos & INT64_C(0xffffffff); return sample1 + ((int)((sample2-sample1)*(sub>>17)) >> 15); } int SoftwareMixerChannel::Interpolate4Point(SoftwareMixerChannel *chan, int offset, int stride) { const int ncoefs = 4; int samples[ncoefs], mixed = 0; unsigned int sub; int s, i; short *bufp[3], *coeftable; unsigned int bufs[3]; s = (chan->samplepos >> 32) - (ncoefs / 2); sub = (chan->samplepos & INT64_C(0xffffffff)) >> (32-COEFTABLESIZE); bufp[0] = bufp[2] = NULL; bufs[0] = bufs[2] = 0; bufp[1] = (short *)chan->buffer->GetData(); bufs[1] = chan->buffer->GetNumSamples(); if (!chan->streamed) { if (chan->loop) { bufp[0] = bufp[2] = bufp[1]; bufs[0] = bufs[2] = bufs[1]; } } else { if (chan->buffer0) { bufp[0] = (short *)chan->buffer0->GetData(); bufs[0] = chan->buffer0->GetNumSamples(); } if (chan->buffer2) { bufp[2] = (short *)chan->buffer2->GetData(); bufs[2] = chan->buffer2->GetNumSamples(); } } memset(samples, 0, sizeof(samples)); for (i=0; i> 16; if (mixed < -32768) return -32768; else if (mixed > 32767) return 32767; return mixed; } //}}} //{{{ n-to-n mixing functions void SoftwareMixerChannel::Mix1To1(SoftwareMixerChannel *chan, int **mmixbuffer, int *mmb, int v1, int v2) { int *mixbuffer = *mmixbuffer, mb = *mmb, s, v = v1+v2; for (; mb >= 0 && (int)(chan->samplepos>>32) < chan->buffer->GetNumSamples(); chan->samplepos += chan->sampleinc, mb--) { s = chan->filterfunc(chan, 0, 1) << SHIFTFACT; *(mixbuffer++) += s * v / 256; } *mmixbuffer = mixbuffer; *mmb = mb; } void SoftwareMixerChannel::Mix1To2(SoftwareMixerChannel *chan, int **mmixbuffer, int *mmb, int v1, int v2) { int *mixbuffer = *mmixbuffer, mb = *mmb, s; for (; mb >= 0 && (int)(chan->samplepos>>32) < chan->buffer->GetNumSamples(); chan->samplepos += chan->sampleinc, mb--) { s = chan->filterfunc(chan, 0, 1) << SHIFTFACT; *(mixbuffer++) += s * v1 / 256; *(mixbuffer++) += s * v2 / 256; } *mmixbuffer = mixbuffer; *mmb = mb; } void SoftwareMixerChannel::Mix2To1(SoftwareMixerChannel *chan, int **mmixbuffer, int *mmb, int v1, int v2) { int *mixbuffer = *mmixbuffer, mb = *mmb, s, v = v1+v2; for (; mb >= 0 && (int)(chan->samplepos>>32) < chan->buffer->GetNumSamples(); chan->samplepos += chan->sampleinc, mb--) { s = ((chan->filterfunc(chan,0,2) + chan->filterfunc(chan,1,2)) << SHIFTFACT) / 2; *(mixbuffer++) += s * v / 256; } *mmixbuffer = mixbuffer; *mmb = mb; } void SoftwareMixerChannel::Mix2To2(SoftwareMixerChannel *chan, int **mmixbuffer, int *mmb, int v1, int v2) { int *mixbuffer = *mmixbuffer, mb = *mmb, s1, s2; for (; mb >= 0 && (int)(chan->samplepos>>32) < chan->buffer->GetNumSamples(); chan->samplepos += chan->sampleinc, mb--) { s1 = chan->filterfunc(chan,0,2) << SHIFTFACT; s2 = chan->filterfunc(chan,1,2) << SHIFTFACT; *(mixbuffer++) += s1 * v1 / 256; *(mixbuffer++) += s2 * v2 / 256; } *mmixbuffer = mixbuffer; *mmb = mb; } void SoftwareMixerChannel::DownMix2To2(SoftwareMixerChannel *chan, int **mmixbuffer, int *mmb, int v1, int v2) { int *mixbuffer = *mmixbuffer, mb = *mmb, s; for (; mb >= 0 && (int)(chan->samplepos>>32) < chan->buffer->GetNumSamples(); chan->samplepos += chan->sampleinc, mb--) { s = ((chan->filterfunc(chan,0,2) + chan->filterfunc(chan,1,2)) << SHIFTFACT) / 2; *(mixbuffer++) += s * v1 / 256; *(mixbuffer++) += s * v2 / 256; } *mmixbuffer = mixbuffer; *mmb = mb; } //}}} void SoftwareMixerChannel::MixSome(int *mixbuffer, int mb) { int i; if (state != Playing || !media) return; // paused or stopped for (mb -= 1; mb >= 0; ) { if (!UpdateProps() || !mixfunc) return; mixfunc(this, &mixbuffer, &mb, volumes[0], volumes[1]); if ((int)(samplepos >> 32) >= buffer->GetNumSamples()) { samplepos -= (int64_t)buffer->GetNumSamples() << 32; if (streamed) { if (buffer0) delete buffer0; buffer0 = buffer; buffer = buffer2; buffer2 = NULL; changed |= CHANGED_BUFFER; } else if (!loop) { state = Stopped; if (stopcallback) stopcallback(stopcallbackid); return; } } } } bool SoftwareMixerChannel::UpdateProps(void) { if (!media) return false; if ((!streamed && !buffer) || (streamed && !buffer2)) { buffer2 = media->ReadSamples(streamed ? STREAMCHUNK : 0, streamed ? loop : false); if (buffer2 && (buffer2->GetNumSamples() == 0 || !buffer2->ConvertToNByte(2))) { delete buffer2; buffer2 = NULL; } if (!buffer) { buffer = buffer2; buffer2 = NULL; changed |= CHANGED_BUFFER; } } if (!buffer) { // error, or out of data state = Stopped; if (stopcallback) stopcallback(stopcallbackid); return false; } if (changed) { if (changed & CHANGED_BUFFER) { bufferp = (short *)buffer->GetData(); if (buffer->GetSampleRate() != oldsamplerate) { changed |= CHANGED_RATE; oldsamplerate = buffer->GetSampleRate(); } if (buffer->GetNumChannels() == 1 && owner->chans == 2) { mixfunc = Mix1To2; } else if (buffer->GetNumChannels() == 2 && owner->chans == 2) { if (follow) mixfunc = Mix2To2; else mixfunc = DownMix2To2; } else if (buffer->GetNumChannels() == 1 && owner->chans == 1) { mixfunc = Mix1To1; } else if (buffer->GetNumChannels() == 2 && owner->chans == 1) { mixfunc = Mix2To1; } } if (changed & CHANGED_RATE) { sampleinc = (int64_t)(((double)buffer->GetSampleRate() * pitch / owner->frequency) * (INT64_C(1)<<32)); } if (changed & CHANGED_POSITION) { float distance, atten; float dx, dy, dz; float t, b; // owner->at[xyz] is a unit vector if (follow) { dx = posx; dy = posy; dz = posz; } else { dx = posx - owner->posx; dy = posy - owner->posy; dz = posz - owner->posz; } distance = sqrt(dx*dx + dy*dy + dz*dz); if (distance >= maxdist) { volumes[0] = volumes[1] = 0; } else { if (distance <= refdist) atten = gain*128.0; else atten = gain*128.0*distfunc(distance,refdist,maxdist,rolloff); b = dx*dx + dz*dz; if (b <= 0.0) { volumes[0] = volumes[1] = (int)(2.0*atten); } else { t = (dx*owner->atz - dz*owner->atx)/sqrt(b); volumes[0] = (int)((1.0+t) * atten); volumes[1] = (int)((1.0-t) * atten); } } //_JFAud_LogMsg("d=%+f,%+f,%+f distance=%+f atten=%+f t=%+f l=%d r=%d\n",dx,dy,dz,distance,atten,t,volumes[1],volumes[0]); } changed = 0; } return true; } void SoftwareMixerChannel::Cleanup(void) { if (stopcallback && state != Stopped) { stopcallback(stopcallbackid); } stopcallback = NULL; stopcallbackid = 0; samplepos = 0; mixfunc = NULL; filterfunc = InterpolateLinear; distfunc = DistanceInverse; state = Stopped; gain = 1.0; pitch = 1.0; posx = posy = posz = 0.0; refdist = 1.0; maxdist = 10.0; rolloff = 1.0; streamed = false; loop = false; follow = false; if (buffer0) delete buffer0; if (buffer) delete buffer; if (buffer2) delete buffer2; if (media) delete media; buffer0 = NULL; buffer = NULL; buffer2 = NULL; oldsamplerate = 0; volumes[0] = volumes[0] = 0; changed = 0; media = NULL; } SoftwareMixerChannel::SoftwareMixerChannel(SoftwareMixer *own) : owner(own), media(NULL), buffer0(NULL), buffer(NULL), buffer2(NULL), oldsamplerate(0), samplepos(0), sampleinc(0), bufferp(NULL), changed(0), mixfunc(NULL), filterfunc(InterpolateLinear), distfunc(DistanceInverse), state(Stopped), gain(1.0), pitch(1.0), posx(0.0), posy(0.0), posz(0.0), refdist(1.0), maxdist(10.0), rolloff(1.0), streamed(false), loop(false), follow(false) { volumes[0] = volumes[1] = 0; } SoftwareMixerChannel::~SoftwareMixerChannel() { Cleanup(); } bool SoftwareMixerChannel::SetMedia(WaveformFile *file) { if (!file) return false; owner->waveout->Lock(); if (media) { // clean up after our predecessor delete media; media = NULL; } media = file; if (file->GetPCMLength() >= (2*STREAMCHUNK)) streamed = true; else streamed = false; changed = -1; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetFilter(Filter which) { switch (which) { case Filter4Point: owner->waveout->Lock(); filterfunc = Interpolate4Point; owner->waveout->Unlock(); return true; case FilterLinear: owner->waveout->Lock(); filterfunc = InterpolateLinear; owner->waveout->Unlock(); return true; case FilterNearest: owner->waveout->Lock(); filterfunc = InterpolateNearest; owner->waveout->Unlock(); return true; default: return false; } } bool SoftwareMixerChannel::SetDistanceModel(DistanceModel which) { switch (which) { case DistanceModelInverse: owner->waveout->Lock(); distfunc = DistanceInverse; owner->waveout->Unlock(); return true; case DistanceModelLinear: owner->waveout->Lock(); distfunc = DistanceLinear; owner->waveout->Unlock(); return true; default: return false; } } bool SoftwareMixerChannel::Play(void) { if (!media) return false; owner->waveout->Lock(); state = Playing; UpdateProps(); owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::Pause(void) { if (!media) return false; owner->waveout->Lock(); state = Paused; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::Update(void) { return true; } bool SoftwareMixerChannel::IsPlaying(void) const { return state == Playing; } bool SoftwareMixerChannel::IsPaused(void) const { return state == Paused; } bool SoftwareMixerChannel::IsStopped(void) const { return state == Stopped; } bool SoftwareMixerChannel::SetGain(float gain) { owner->waveout->Lock(); this->gain = gain; changed |= CHANGED_POSITION; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetPitch(float pitch) { owner->waveout->Lock(); this->pitch = pitch; changed |= CHANGED_RATE; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetPosition(float x, float y, float z) { owner->waveout->Lock(); posx = x; posy = y; posz = z; changed |= CHANGED_POSITION; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetVelocity(float x, float y, float z) { return false; } bool SoftwareMixerChannel::SetDirection(float x, float y, float z) { return false; } bool SoftwareMixerChannel::SetRefDist(float refdist) { owner->waveout->Lock(); this->refdist = refdist; changed |= CHANGED_POSITION; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetMaxDist(float maxdist) { owner->waveout->Lock(); this->maxdist = maxdist; changed |= CHANGED_POSITION; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetRolloff(float rolloff) { owner->waveout->Lock(); this->rolloff = rolloff; changed |= CHANGED_POSITION; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetLoop(bool onf) { owner->waveout->Lock(); loop = onf; owner->waveout->Unlock(); return true; } bool SoftwareMixerChannel::SetFollowListener(bool onf) { owner->waveout->Lock(); follow = onf; changed |= CHANGED_POSITION; owner->waveout->Unlock(); return true; } float SoftwareMixerChannel::GetGain(void) const { return gain; } float SoftwareMixerChannel::GetPitch(void) const { return pitch; } void SoftwareMixerChannel::GetPosition(float *x, float *y, float *z) const { *x = posx; *y = posy; *z = posz; } void SoftwareMixerChannel::GetVelocity(float *x, float *y, float *z) const { *x = *y = *z = 0.0; } void SoftwareMixerChannel::GetDirection(float *x, float *y, float *z) const { *x = *y = *z = 0.0; } float SoftwareMixerChannel::GetRefDist(void) const { return refdist; } float SoftwareMixerChannel::GetMaxDist(void) const { return maxdist; } float SoftwareMixerChannel::GetRolloff(void) const { return rolloff; } bool SoftwareMixerChannel::GetLoop(void) const { return loop; } bool SoftwareMixerChannel::GetFollowListener(void) const { return follow; } SoftwareMixer::SoftwareMixer() : waveout(NULL), voices(NULL), nvoices(0), frequency(0), bytesamp(0), chans(0), mixbuffer(NULL), mixbufferlen(0), mixbufferused(0), mixbufferpos(0), posx(0.0), posy(0.0), posz(0.0), atx(0.0), aty(1.0), atz(0.0), upx(0.0), upy(0.0), upz(1.0), velx(0.0), vely(0.0), velz(0.0), gain(1.0) { // build the 4 coefficient interpolation table const int ncoefs = 4; double sinc, window, xx, sub; int i, j; for (j = 0; j < (1<=0;i--) if (voices[i].chan) delete voices[i].chan; delete [] voices; } if (mixbuffer) delete [] mixbuffer; } void SoftwareMixer::DoMix(void) { int i; if (mixbufferpos >= mixbufferused) { memset(mixbuffer, 0, sizeof(int)*mixbufferlen*chans); for (i=nvoices-1; i>=0; i--) { if (!voices[i].used) continue; voices[i].chan->MixSome(mixbuffer, mixbufferlen); } mixbufferpos = 0; mixbufferused = mixbufferlen; } } bool SoftwareMixer::Setup(WaveOut *dev, int maxvoices) { int i; waveout = dev; frequency = dev->GetSampleRate(); chans = dev->GetChannels(); bytesamp = dev->GetBitsPerSample() / 8; // the size of our internal 32bit mixing buffer should contain at // least enough samples to match what the wave output device is // likely to ask for at any one time mixbufferlen = dev->GetMixBufferLen(); mixbufferpos = mixbufferused = 0; mixbuffer = new int[mixbufferlen*chans]; if (!mixbuffer) return false; voices = new struct _voicestat [maxvoices]; if (!voices) { delete [] mixbuffer; mixbuffer = NULL; return false; } for (i=maxvoices-1;i>=0;i--) { voices[i].used = false; voices[i].chan = new SoftwareMixerChannel(this); if (!voices[i].chan) { for (i++; iLock(); for (i=nvoices-1; i>=0; i--) { if (voices[i].used) continue; voices[i].used = true; waveout->Unlock(); return static_cast(voices[i].chan); } waveout->Unlock(); return NULL; } bool SoftwareMixer::ReleaseChannel(JFAudMixerChannel *ch) { int i; waveout->Lock(); for (i=nvoices-1; i>=0; i--) { if (!voices[i].used) continue; if (voices[i].chan != static_cast(ch)) continue; voices[i].used = false; voices[i].chan->Cleanup(); waveout->Unlock(); return true; } waveout->Unlock(); return false; } bool SoftwareMixer::Update() { return true; } void SoftwareMixer::MixSome(void *buf, int bytes) { #ifdef TESTTONE int s; static int i = 0; unsigned char *c = (unsigned char *)buf; for (; bytes >= bytesamp; bytes -= bytesamp) { s = (int)((float)0x7fffffff * sin((float)i++ * 2.0 * M_PI * 440.0 / (float)frequency)); if (bytesamp == 1) *(c++) = (unsigned char)((s>>24)+128); else if (bytesamp == 2) { *(short *)c = (short)(s>>16); c += 2; } } #else int i, *rp; char *bp = (char *)buf; int samples = bytes / (bytesamp*chans); // FIXME: assumes the caller isn't stupid enough to ask for a partial sample while (samples > 0) { DoMix(); // convert and copy the 32bit samples from our mixing buffer to what is wanted in the output i = min(mixbufferlen - mixbufferpos, samples); switch (bytesamp) { case 1: { int n, *in = &mixbuffer[mixbufferpos*chans]; unsigned char *out = (unsigned char *)bp; for (n = i*chans-1; n>=0; n--) *(out++) = (unsigned char)max(-128,min(127,(*(in++) >> 24))) ^ 128; } break; case 2: { int n, *in = &mixbuffer[mixbufferpos*chans]; short *out = (short *)bp; for (n = i*chans-1; n>=0; n--, in++) *(out++) = (short)max(-32768,min(32767,(*in >> OUTFACT))); } break; } mixbufferpos += i; bp += bytesamp*chans*i; samples -= i; } #endif } bool SoftwareMixer::SetListenerPosition(float x, float y, float z) { int i; waveout->Lock(); posx = x; posy = y; posz = z; for (i=nvoices-1; i>=0; i--) { if (!voices[i].used) continue; voices[i].chan->changed |= CHANGED_POSITION; } waveout->Unlock(); return true; } bool SoftwareMixer::SetListenerOrientation(float atx, float aty, float atz, float upx, float upy, float upz) { int i; // make sure everything passed is a unit vector if (atx == 0.0 && aty == 0.0 && atz == 0.0) aty = 1.0; else { float l = sqrt(atx*atx + aty*aty + atz*atz); atx /= l; aty /= l; atz /= l; } if (upx == 0.0 && upy == 0.0 && upz == 0.0) upz = 1.0; else { float l = sqrt(upx*upx + upy*upy + upz*upz); upx /= l; upy /= l; upz /= l; } waveout->Lock(); this->atx = atx; this->aty = aty; this->atz = atz; this->upx = upx; this->upy = upy; this->upz = upz; for (i=nvoices-1; i>=0; i--) { if (!voices[i].used) continue; voices[i].chan->changed |= CHANGED_POSITION; } waveout->Unlock(); return true; } bool SoftwareMixer::SetListenerVelocity(float x, float y, float z) { waveout->Lock(); velx = x; vely = y; velz = z; waveout->Unlock(); return true; } bool SoftwareMixer::SetListenerGain(float gain) { waveout->Lock(); this->gain = gain; waveout->Unlock(); return true; } void SoftwareMixer::GetListenerPosition(float *x, float *y, float *z) const { *x = posx; *y = posy; *z = posz; } void SoftwareMixer::GetListenerOrientation(float *atx, float *aty, float *atz, float *upx, float *upy, float *upz) const { *atx = this->atx; *aty = this->aty; *atz = this->atz; *upx = this->upx; *upy = this->upy; *upz = this->upz; } void SoftwareMixer::GetListenerVelocity(float *x, float *y, float *z) const { *x = velx; *y = vely; *z = velz; } float SoftwareMixer::GetListenerGain(void) const { return gain; } // vim:fdm=marker: