--- wine-0.9-progressive/dlls/dsound/mixer.c	Mon Oct 31 00:11:53 2005
+++ wine-0.9/dlls/dsound/mixer.c	Wed Nov  2 21:06:41 2005
@@ -98,6 +98,14 @@
 	dsb->writelead = (dsb->freq / 100) * dsb->pwfx->nBlockAlign;
 }
 
+/**
+ * Check for application callback requests for when the play position
+ * reaches certain points.
+ *
+ * The offsets that will be triggered will be those between the recorded
+ * "last played" position for the buffer (i.e. dsb->playpos) and "len" bytes
+ * beyond that position.
+ */
 void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len)
 {
 	int			i;
@@ -163,6 +171,10 @@
     return (s >> 8) ^ (unsigned char)0x80;
 }
 
+/**
+ * Copy a single frame from the given input buffer to the given output buffer.
+ * Translate 8 <-> 16 bits and mono <-> stereo
+ */
 static inline void cp_fields(const IDirectSoundBufferImpl *dsb, BYTE *ibuf, BYTE *obuf )
 {
 	DirectSoundDevice * device = dsb->dsound->device;
@@ -209,13 +221,32 @@
         }
 }
 
-/* Now with PerfectPitch (tm) technology */
+/** 
+ * Mix at most the given amount of data into the given device buffer from the
+ * given secondary buffer, starting from the dsb's first currently unmixed
+ * frame (buf_mixpos), translating frequency (pitch), stereo/mono and
+ * bits-per-sample. The secondary buffer sample is looped if it is not
+ * long enough and it is a looping buffer.
+ * (Doesn't perform any mixing - this is a straight copy operation).
+ *
+ * Now with PerfectPitch (tm) technology
+ *
+ * dsb = the secondary buffer
+ * buf = the device buffer
+ * len = number of bytes to store in the device buffer
+ * 
+ * Returns: the number of bytes read from the secondary buffer
+ *   (ie. len, adjusted for frequency, number of channels and sample size,
+ *    and limited by buffer length for non-looping buffers)
+ */
 static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
 {
 	INT	i, size, ipos, ilen;
 	BYTE	*ibp, *obp;
 	INT	iAdvance = dsb->pwfx->nBlockAlign;
 	INT	oAdvance = dsb->dsound->device->pwfx->nBlockAlign;
+	BOOL    wrap = (dsb->playflags & DSBPLAY_LOOPING) != 0;
+	int     nfiller;
 
 	ibp = dsb->buffer->memory + dsb->buf_mixpos;
 	obp = buf;
@@ -227,7 +258,7 @@
 	    (dsb->pwfx->nChannels == dsb->dsound->device->pwfx->nChannels)) {
 	        INT bytesleft = dsb->buflen - dsb->buf_mixpos;
 		TRACE("(%p) Best case\n", dsb);
-	    	if (len <= bytesleft )
+	    	if (len <= bytesleft || ! wrap)
 			CopyMemory(obp, ibp, len);
 		else { /* wrap */
 			CopyMemory(obp, ibp, bytesleft);
@@ -246,23 +277,25 @@
 			ibp += iAdvance;
 			ilen += iAdvance;
 			obp += oAdvance;
-			if (ibp >= (BYTE *)(dsb->buffer->memory + dsb->buflen))
-				ibp = dsb->buffer->memory;	/* wrap */
+			if (ibp >= (BYTE *)(dsb->buffer->memory + dsb->buflen)) {
+				if (wrap) {
+					ibp = dsb->buffer->memory;	/* wrap */
+				} else {
+					goto pad_remaining;
+				}
+			}
 		}
 		return (ilen);
 	}
 
-	/* Mix in different sample rates */
-	/* */
-	/* New PerfectPitch(tm) Technology (c) 1998 Rob Riggs */
-	/* Patent Pending :-] */
+	/* Mix in different sample rates
+	 *
+	 * New PerfectPitch(tm) Technology (c) 1998 Rob Riggs
+	 * Patent Pending :-] */
 
 	/* Patent enhancements (c) 2000 Ove K�ven,
 	 * TransGaming Technologies Inc. */
 
-	/* FIXME("(%p) Adjusting frequency: %ld -> %ld (need optimization)\n",
-	   dsb, dsb->freq, dsb->dsound->device->pwfx->nSamplesPerSec); */
-
 	size = len / oAdvance;
 	ilen = 0;
 	ipos = dsb->buf_mixpos;
@@ -273,10 +306,30 @@
 		if (dsb->freqAcc >= (1<<DSOUND_FREQSHIFT)) {
 			ULONG adv = (dsb->freqAcc>>DSOUND_FREQSHIFT) * iAdvance;
 			dsb->freqAcc &= (1<<DSOUND_FREQSHIFT)-1;
-			ipos += adv; ilen += adv;
-			ipos %= dsb->buflen;
+			ipos += adv;
+			if (ipos > dsb->buflen) {
+				if (wrap) {
+					ipos -= dsb->buflen;
+				} else {
+					break;
+				}
+			}
+			ilen += adv;
 		}
 	}
+	i *= oAdvance;
+		
+	pad_remaining:
+
+	/* Pad the remaining length, if any, with silence. We expect the
+	 * remaining length to be quite short. */
+	
+	/* the sound of silence */
+	nfiller = dsb->dsound->device->pwfx->wBitsPerSample == 8 ? 128 : 0;
+	for ( ; i < len; i++) {
+		*obp++ = nfiller;
+	}
+	
 	return ilen;
 }
 
@@ -356,6 +409,10 @@
 	}
 }
 
+/**
+ * Make sure the device's tmp_buffer is at least the given size. Return a
+ * pointer to it.
+ */
 static LPBYTE DSOUND_tmpbuffer(DirectSoundDevice *device, DWORD len)
 {
     TRACE("(%p,%ld)\n", device, len);
@@ -372,6 +429,19 @@
     return device->tmp_buffer;
 }
 
+/**
+ * Mix (at most) the given number of bytes into the given position of the
+ * device buffer, from the secondary buffer "dsb" (starting at the current
+ * mix position for that buffer).
+ *
+ * Returns the number of bytes actually mixed into the device buffer. This
+ * will match fraglen unless the end of the secondary buffer is reached
+ * (and it is not looping).
+ *
+ * dsb  = the secondary buffer to mix from
+ * writepos = position (offset) in device buffer to write at
+ * fraglen = number of bytes to mix
+ */
 static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD fraglen)
 {
 	INT	i, len, ilen, field, todo;
@@ -381,8 +451,19 @@
 
 	len = fraglen;
 	if (!(dsb->playflags & DSBPLAY_LOOPING)) {
+		/* This buffer is not looping, so make sure the requested
+		 * length will not take us past the end of the buffer */
 		int secondary_remainder = dsb->buflen - dsb->buf_mixpos;
-		int adjusted_remainder = MulDiv(dsb->dsound->device->pwfx->nAvgBytesPerSec, secondary_remainder, dsb->nAvgBytesPerSec);
+		int blockalign;
+		int adjusted_remainder;
+		
+		if (secondary_remainder == 0)
+			return 0;
+		
+		blockalign = dsb->dsound->device->pwfx->nBlockAlign;
+		adjusted_remainder = MulDiv(dsb->dsound->device->pwfx->nAvgBytesPerSec, secondary_remainder, dsb->nAvgBytesPerSec) + blockalign;
+		adjusted_remainder -= adjusted_remainder % blockalign;
+		
 		assert(adjusted_remainder >= 0);
 		TRACE("secondary_remainder = %d, adjusted_remainder = %d, len = %d\n", secondary_remainder, adjusted_remainder, len);
 		if (adjusted_remainder < len) {
@@ -392,7 +473,7 @@
 		if (len == 0)
 			return 0;
 	}
-
+	
 	if (len % dsb->dsound->device->pwfx->nBlockAlign) {
 		INT nBlockAlign = dsb->dsound->device->pwfx->nBlockAlign;
 		ERR("length not a multiple of block size, len = %d, block size = %d\n", len, nBlockAlign);
@@ -404,12 +485,16 @@
 
 	TRACE("MixInBuffer (%p) len = %d, dest = %ld\n", dsb, len, writepos);
 
+	/* first, copy the data from the DirectSoundBuffer into the temporary
+	   buffer, translating frequency/bits-per-sample/number-of-channels
+	   to match the device settings */
 	ilen = DSOUND_MixerNorm(dsb, ibuf, len);
 	if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
 	    (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
 	    (dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
 		DSOUND_MixerVol(dsb, ibuf, len);
 
+	/* Now mix the temporary buffer into the devices main buffer */
 	if (dsb->dsound->device->pwfx->wBitsPerSample == 8) {
 		BYTE	*obuf = dsb->dsound->device->buffer + writepos;
 
@@ -667,30 +752,52 @@
 	LeaveCriticalSection(&dsb->lock);
 }
 
+/**
+ * Calculate the distance between two buffer offsets, taking wraparound
+ * into account.
+ */
+static inline DWORD DSOUND_BufPtrDiff(DWORD buflen, DWORD ptr1, DWORD ptr2)
+{
+	if (ptr1 >= ptr2) {
+		return ptr1 - ptr2;
+	} else {
+		return buflen + ptr1 - ptr2;
+	}
+}
+
+/**
+ * Mix some frames from the given secondary buffer "dsb" into the device
+ * primary buffer.
+ *
+ * dsb = the secondary buffer
+ * playpos = the current play position in the device buffer (primary buffer)
+ * writepos = the current safe-to-write position in the device buffer
+ * mixlen = the maximum number of bytes in the primary buffer to mix, from the
+ *          current writepos.
+ *
+ * Returns: the number of bytes beyond the writepos that were mixed.
+ */
 static DWORD DSOUND_MixOne(IDirectSoundBufferImpl *dsb, DWORD playpos, DWORD writepos, DWORD mixlen)
 {
+	/* The buffer's primary_mixpos may be before or after the the device
+	 * buffer's mixpos, but both must be ahead of writepos. */
+
 	DWORD len, slen;
 	/* determine this buffer's write position */
 	DWORD buf_writepos = DSOUND_CalcPlayPosition(dsb, writepos, writepos);
-	/* determine how much already-mixed data exists */
-	DWORD buf_done =
-		((dsb->buf_mixpos < buf_writepos) ? dsb->buflen : 0) +
-		dsb->buf_mixpos - buf_writepos;
-	DWORD primary_done =
-		((dsb->primary_mixpos < writepos) ? dsb->dsound->device->buflen : 0) +
-		dsb->primary_mixpos - writepos;
-	DWORD adv_done =
-		((dsb->dsound->device->mixpos < writepos) ? dsb->dsound->device->buflen : 0) +
-		dsb->dsound->device->mixpos - writepos;
+	/* the amount between the buffers current playpos and writepos
+	 * (ie between writepos value from last call and this call) */
 	DWORD played =
-		((buf_writepos < dsb->playpos) ? dsb->buflen : 0) +
-		buf_writepos - dsb->playpos;
+		DSOUND_BufPtrDiff(dsb->buflen, buf_writepos, dsb->playpos);
+	/* the amound of the secondary buffer from the writepos to the end */
 	DWORD buf_left = dsb->buflen - buf_writepos;
-	int still_behind;
+	DWORD primary_done =
+		DSOUND_BufPtrDiff(dsb->dsound->device->buflen,
+			dsb->primary_mixpos, writepos);
 
 	TRACE("(%p,%ld,%ld,%ld)\n",dsb,playpos,writepos,mixlen);
 	TRACE("buf_writepos=%ld, primary_writepos=%ld\n", buf_writepos, writepos);
-	TRACE("buf_done=%ld, primary_done=%ld\n", buf_done, primary_done);
+	TRACE("primary_done=%ld\n", primary_done);
 	TRACE("buf_mixpos=%ld, primary_mixpos=%ld, mixlen=%ld\n", dsb->buf_mixpos, dsb->primary_mixpos,
 	      mixlen);
 	TRACE("looping=%ld, startpos=%ld, leadin=%ld\n", dsb->playflags, dsb->startpos, dsb->leadin);
@@ -704,18 +811,6 @@
 	/* save write position for non-GETCURRENTPOSITION2... */
 	dsb->playpos = buf_writepos;
 
-	/* check whether CalcPlayPosition detected a mixing underrun */
-	if ((buf_done == 0) && (dsb->primary_mixpos != writepos)) {
-		/* it did, but did we have more to play? */
-		if ((dsb->playflags & DSBPLAY_LOOPING) ||
-		    (dsb->buf_mixpos < dsb->buflen)) {
-			/* yes, have to recover */
-			ERR("underrun on sound buffer %p\n", dsb);
-			TRACE("recovering from underrun: primary_mixpos=%ld\n", writepos);
-		}
-		dsb->primary_mixpos = writepos;
-		primary_done = 0;
-	}
 	/* determine how far ahead we should mix */
 	if (((dsb->playflags & DSBPLAY_LOOPING) ||
 	     (dsb->leadin && (dsb->probably_valid_to != 0))) &&
@@ -760,40 +855,35 @@
 	}
 	/* cut mixlen with what's already been mixed */
 	if (mixlen < primary_done) {
-		/* huh? and still CalcPlayPosition didn't
-		 * detect an underrun? */
-		FIXME("problem with underrun detection (mixlen=%ld < primary_done=%ld)\n", mixlen, primary_done);
 		return 0;
 	}
 	len = mixlen - primary_done;
+	
 	TRACE("remaining mixlen=%ld\n", len);
 
+	/* len is now the maximum amount we should mix from dsb's
+	 * primary_mixpos onwards */
 	if (len < dsb->dsound->device->fraglen) {
 		/* smaller than a fragment, wait until it gets larger
 		 * before we take the mixing overhead */
 		TRACE("mixlen not worth it, deferring mixing\n");
-		still_behind = 1;
+		len = 0;
 		goto post_mix;
 	}
 
 	/* ok, we know how much to mix, let's go */
-	still_behind = (adv_done > primary_done);
 	while (len) {
 		slen = dsb->dsound->device->buflen - dsb->primary_mixpos;
 		if (slen > len) slen = len;
 		slen = DSOUND_MixInBuffer(dsb, dsb->primary_mixpos, slen);
 
-		if ((dsb->primary_mixpos < dsb->dsound->device->mixpos) &&
-		    (dsb->primary_mixpos + slen >= dsb->dsound->device->mixpos))
-			still_behind = FALSE;
-
 		dsb->primary_mixpos += slen; len -= slen;
 		dsb->primary_mixpos %= dsb->dsound->device->buflen;
 
 		if ((dsb->state == STATE_STOPPED) || !slen) break;
 	}
 	TRACE("new primary_mixpos=%ld, primary_advbase=%ld\n", dsb->primary_mixpos, dsb->dsound->device->mixpos);
-	TRACE("mixed data len=%ld, still_behind=%d\n", mixlen-len, still_behind);
+	TRACE("mixed data len=%ld\n", mixlen-len);
 
 post_mix:
 	/* check if buffer should be considered complete */
@@ -810,11 +900,8 @@
 
 	/* return how far we think the primary buffer can
 	 * advance its underrun detector...*/
-	if (still_behind) return 0;
-	if ((mixlen - len) < primary_done) return 0;
-	slen = ((dsb->primary_mixpos < dsb->dsound->device->mixpos) ?
-		dsb->dsound->device->buflen : 0) + dsb->primary_mixpos -
-		dsb->dsound->device->mixpos;
+	slen = DSOUND_BufPtrDiff(dsb->dsound->device->buflen,
+		dsb->primary_mixpos, writepos);
 	if (slen > mixlen) {
 		/* the primary_done and still_behind checks above should have worked */
 		FIXME("problem with advancement calculation (advlen=%ld > mixlen=%ld)\n", slen, mixlen);
@@ -823,6 +910,19 @@
 	return slen;
 }
 
+/**
+ * For a DirectSoundDevice, go through all the currently playing buffers and
+ * mix them in to the device buffer.
+ *
+ * playpos = the current play position in the primary buffer
+ * writepos = the current safe-to-write position in the primary buffer
+ * mixlen = the maximum amount to mix into the primary buffer
+ *          (beyond the current writepos)
+ * recover = true if the sound device may have been reset and the write
+ *           position in the device buffer changed
+ *
+ * Returns:  the length beyond the writepos that was mixed to.
+ */
 static DWORD DSOUND_MixToPrimary(DirectSoundDevice *device, DWORD playpos, DWORD writepos, DWORD mixlen, BOOL recover)
 {
 	INT			i, len, maxlen = 0;
@@ -844,6 +944,8 @@
 					dsb->primary_mixpos = writepos;
 					dsb->cvolpan = dsb->volpan;
 					dsb->need_remix = FALSE;
+					if (dsb->state == STATE_STARTING)
+						dsb->state = STATE_PLAYING;
 				}
 				else if (dsb->need_remix) {
 					DSOUND_MixCancel(dsb, writepos, TRUE);
@@ -851,8 +953,6 @@
 					dsb->need_remix = FALSE;
 				}
 				len = DSOUND_MixOne(dsb, playpos, writepos, mixlen);
-				if (dsb->state == STATE_STARTING)
-					dsb->state = STATE_PLAYING;
 				maxlen = (len > maxlen) ? len : maxlen;
 			}
 			LeaveCriticalSection(&(dsb->lock));
@@ -941,11 +1041,17 @@
 
 /* #define SYNC_CALLBACK */
 
+/**
+ * Perform mixing for a Direct Sound device. That is, go through all the
+ * secondary buffers (the sound bites currently playing) and mix them in
+ * to the primary buffer (the device buffer).
+ */
 void DSOUND_PerformMix(DirectSoundDevice *device)
 {
 	int nfiller;
 	BOOL forced;
 	HRESULT hres;
+	BOOL underrun;
 
 	TRACE("(%p)\n", device);
 
@@ -958,31 +1064,39 @@
 	if (device->priolevel != DSSCL_WRITEPRIMARY) {
 		BOOL paused = ((device->state == STATE_STOPPED) || (device->state == STATE_STARTING));
 		/* FIXME: document variables */
- 		DWORD playpos, writepos, inq, maxq, frag;
+ 		DWORD playpos, writepos, inq, playleft, maxq, frag;
+ 		DWORD playlead;
  		if (device->hwbuf) {
 			hres = IDsDriverBuffer_GetPosition(device->hwbuf, &playpos, &writepos);
 			if (hres) {
 			    WARN("IDsDriverBuffer_GetPosition failed\n");
 			    return;
 			}
+			playlead = DSOUND_BufPtrDiff(device->buflen,
+				writepos, playpos);
+		} else {
+ 			playpos = device->pwplay * device->fraglen;
+ 			playlead = ds_hel_margin * device->fraglen;
+
 			/* Well, we *could* do Just-In-Time mixing using the writepos,
 			 * but that's a little bit ambitious and unnecessary... */
 			/* rather add our safety margin to the writepos, if we're playing */
+			writepos = playpos;
+		
 			if (!paused) {
-				writepos += device->writelead;
-				writepos %= device->buflen;
-			} else writepos = playpos;
-		} else {
- 			playpos = device->pwplay * device->fraglen;
- 			writepos = playpos;
- 			if (!paused) {
-	 			writepos += ds_hel_margin * device->fraglen;
- 				writepos %= device->buflen;
-	 		}
+				writepos += playlead;
+				if (writepos > device->buflen) {
+					writepos -= device->buflen;
+				}
+			}
 		}
+		
 		TRACE("primary playpos=%ld, writepos=%ld, clrpos=%ld, mixpos=%ld, buflen=%ld\n",
 		      playpos,writepos,device->playpos,device->mixpos,device->buflen);
 		assert(device->playpos < device->buflen);
+
+		EnterCriticalSection(&(device->mixlock));
+
 		/* wipe out just-played sound data */
 		if (playpos < device->playpos) {
 			FillMemory(device->buffer + device->playpos, device->buflen - device->playpos, nfiller);
@@ -992,36 +1106,67 @@
 		}
 		device->playpos = playpos;
 
-		EnterCriticalSection(&(device->mixlock));
-
 		/* reset mixing if necessary */
 		DSOUND_CheckReset(device, writepos);
 
 		/* check how much prebuffering is left */
-		inq = device->mixpos;
-		if (inq < writepos)
-			inq += device->buflen;
-		inq -= writepos;
-
+		inq = DSOUND_BufPtrDiff(device->buflen, device->mixpos, writepos);
+		
 		/* find the maximum we can prebuffer */
-		if (!paused) {
-			maxq = playpos;
-			if (maxq < writepos)
-				maxq += device->buflen;
-			maxq -= writepos;
-		} else maxq = device->buflen;
+		maxq = DSOUND_BufPtrDiff(device->buflen,
+				playpos, writepos);
+		if (maxq == 0) {
+			maxq = device->buflen;
+		}
 
 		/* clip maxq to device->prebuf */
 		frag = device->prebuf * device->fraglen;
 		if (maxq > frag) maxq = frag;
 
 		/* check for consistency */
-		if (inq > maxq) {
-			/* the playback position must have passed our last
+		underrun = (inq > maxq) || paused;
+		
+		/* do the mixing */
+		frag = DSOUND_MixToPrimary(device, playpos, writepos, maxq, underrun);
+		if (frag != 0 || forced) {
+			device->mixpos = writepos + frag;
+			device->mixpos %= device->buflen;
+		}
+		
+		playleft = DSOUND_BufPtrDiff(device->buflen,
+			device->mixpos, playpos);
+		
+		if (paused) {
+			if (frag > 0 || forced) {
+				/* buffers have been filled, restart playback */
+				if (device->state == STATE_STARTING) {
+					device->state = STATE_PLAYING;
+				}
+				else if (device->state == STATE_STOPPED) {
+					/* stopping just means that play will stop once there's no more to
+					 * play. */
+					device->state = STATE_STOPPING;
+				}
+#ifdef SYNC_CALLBACK
+				LeaveCriticalSection(&(device->mixlock));
+#endif
+				if (paused) {
+					if (DSOUND_PrimaryPlay(device) != DS_OK)
+						WARN("DSOUND_PrimaryPlay failed\n");
+					else
+						TRACE("starting playback\n");
+				}
+#ifdef SYNC_CALLBACK
+				EnterCriticalSection(&(device->mixlock));
+#endif			
+			}
+		}
+		else
+		if (playleft > maxq + playlead) {
+			/* the playback position has passed our last
 			 * mixed position, i.e. it's an underrun, or we have
 			 * nothing more to play */
 			TRACE("reached end of mixed data (inq=%ld, maxq=%ld)\n", inq, maxq);
-			inq = 0;
 			/* stop the playback now, to allow buffers to refill */
 			if (device->state == STATE_PLAYING) {
 				device->state = STATE_STARTING;
@@ -1037,6 +1182,8 @@
 			/* DSOUND_callback may need this lock */
 			LeaveCriticalSection(&(device->mixlock));
 #endif
+			FillMemory(device->buffer, device->buflen, nfiller);
+
 			if (DSOUND_PrimaryStop(device) != DS_OK)
 				WARN("DSOUND_PrimaryStop failed\n");
 #ifdef SYNC_CALLBACK
@@ -1057,39 +1204,9 @@
 			writepos = playpos;
 			device->playpos = playpos;
 			device->mixpos = writepos;
-			inq = 0;
-			maxq = device->buflen;
-			if (maxq > frag) maxq = frag;
-			FillMemory(device->buffer, device->buflen, nfiller);
-			paused = TRUE;
 		}
+		LeaveCriticalSection(&(device->mixlock));
 
-		/* do the mixing */
-		frag = DSOUND_MixToPrimary(device, playpos, writepos, maxq, paused);
-		if (forced) frag = maxq - inq;
-		device->mixpos += frag;
-		device->mixpos %= device->buflen;
-
-		if (frag) {
-			/* buffers have been filled, restart playback */
-			if (device->state == STATE_STARTING) {
-				device->state = STATE_PLAYING;
-			}
-			else if (device->state == STATE_STOPPED) {
-				/* the dsound is supposed to play if there's something to play
-				 * even if it is reported as stopped, so don't let this confuse you */
-				device->state = STATE_STOPPING;
-			}
-			LeaveCriticalSection(&(device->mixlock));
-			if (paused) {
-				if (DSOUND_PrimaryPlay(device) != DS_OK)
-					WARN("DSOUND_PrimaryPlay failed\n");
-				else
-					TRACE("starting playback\n");
-			}
-		}
-		else
-			LeaveCriticalSection(&(device->mixlock));
 	} else {
 		/* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
 		if (device->state == STATE_STARTING) {
--- wine-0.9-progressive/dlls/winmm/winealsa/audio.c	Tue Sep 13 00:12:46 2005
+++ wine-0.9/dlls/winmm/winealsa/audio.c	Wed Nov  2 18:47:37 2005
@@ -3011,6 +3011,10 @@
     snd_pcm_uframes_t         mmap_buflen_frames;
     snd_pcm_channel_area_t *  mmap_areas;
     snd_async_handler_t *     mmap_async_handler;
+    snd_pcm_uframes_t	      mmap_ppos; /* play position */
+    
+    /* Do we have a direct hardware buffer - SND_PCM_TYPE_HW? */
+    int                       mmap_mode;
 };
 
 static void DSDB_CheckXRUN(IDsDriverBufferImpl* pdbi)
@@ -3037,86 +3041,78 @@
     }
 }
 
-static void DSDB_MMAPCopy(IDsDriverBufferImpl* pdbi)
+static void DSDB_MMAPCopy(IDsDriverBufferImpl* pdbi, int mul)
 {
     WINE_WAVEDEV *     wwo = &(WOutDev[pdbi->drv->wDevID]);
-    unsigned int       channels;
-    snd_pcm_format_t   format;
     snd_pcm_uframes_t  period_size;
     snd_pcm_sframes_t  avail;
     int err;
     int dir=0;
 
+    const snd_pcm_channel_area_t *areas;
+    snd_pcm_uframes_t     ofs;
+    snd_pcm_uframes_t     frames;
+    snd_pcm_uframes_t     wanted;
+
     if ( !pdbi->mmap_buffer || !wwo->hw_params || !wwo->pcm)
     	return;
 
-    err = snd_pcm_hw_params_get_channels(wwo->hw_params, &channels);
-    err = snd_pcm_hw_params_get_format(wwo->hw_params, &format);
-    dir=0;
     err = snd_pcm_hw_params_get_period_size(wwo->hw_params, &period_size, &dir);
     avail = snd_pcm_avail_update(wwo->pcm);
 
     DSDB_CheckXRUN(pdbi);
 
-    TRACE("avail=%d format=%s channels=%d\n", (int)avail, snd_pcm_format_name(format), channels );
-
-    while (avail >= period_size)
-    {
-	const snd_pcm_channel_area_t *areas;
-	snd_pcm_uframes_t     ofs;
-	snd_pcm_uframes_t     frames;
-	int                   err;
-
-	frames = avail / period_size * period_size; /* round down to a multiple of period_size */
-
-	EnterCriticalSection(&pdbi->mmap_crst);
-
-	snd_pcm_mmap_begin(wwo->pcm, &areas, &ofs, &frames);
-	if (areas != pdbi->mmap_areas || areas->addr != pdbi->mmap_areas->addr)
-	    FIXME("Can't access sound driver's buffer directly.\n");
-	err = snd_pcm_mmap_commit(wwo->pcm, ofs, frames);
+    TRACE("avail=%d, mul=%d\n", (int)avail, mul);
 
-	LeaveCriticalSection(&pdbi->mmap_crst);
 
-	if ( err != (snd_pcm_sframes_t) frames)
-	    ERR("mmap partially failed.\n");
-
-	avail = snd_pcm_avail_update(wwo->pcm);
+    frames = pdbi->mmap_buflen_bytes;
+	
+    EnterCriticalSection(&pdbi->mmap_crst);
+
+    /* we want to commit the given number of periods, or the whole lot */
+    wanted = mul == 0 ? frames : period_size * 2;
+
+    snd_pcm_mmap_begin(wwo->pcm, &areas, &ofs, &frames);
+    if (areas != pdbi->mmap_areas || areas->addr != pdbi->mmap_areas->addr)
+        FIXME("Can't access sound driver's buffer directly.\n");	
+
+    /* mark our current play position */
+    pdbi->mmap_ppos = ofs;
+        
+    if (frames > wanted)
+        frames = wanted;
+        
+    err = snd_pcm_mmap_commit(wwo->pcm, ofs, frames);
+	
+    /* Check to make sure we committed all we want to commit. ALSA
+     * only gives a contiguous linear region, so we need to check this
+     * in case we've reached the end of the buffer, in which case we
+     * can wrap around back to the beginning. */
+    frames -= wanted;
+    if (frames > 0) {
+        snd_pcm_mmap_begin(wwo->pcm, &areas, &ofs, &frames);
+        snd_pcm_mmap_commit(wwo->pcm, ofs, frames);
     }
 
-    if (avail > 0)
-    {
-	const snd_pcm_channel_area_t *areas;
-	snd_pcm_uframes_t     ofs;
-	snd_pcm_uframes_t     frames;
-	int                   err;
-
-	frames = avail;
-
-	EnterCriticalSection(&pdbi->mmap_crst);
-
-	snd_pcm_mmap_begin(wwo->pcm, &areas, &ofs, &frames);
-	if (areas != pdbi->mmap_areas || areas->addr != pdbi->mmap_areas->addr)
-	    FIXME("Can't access sound driver's buffer directly.\n");
-	err = snd_pcm_mmap_commit(wwo->pcm, ofs, frames);
-
-	LeaveCriticalSection(&pdbi->mmap_crst);
-
-	if ( err != (snd_pcm_sframes_t) frames)
-	    ERR("mmap partially failed.\n");
-
-	avail = snd_pcm_avail_update(wwo->pcm);
-    }
+    LeaveCriticalSection(&pdbi->mmap_crst);
 }
 
 static void DSDB_PCMCallback(snd_async_handler_t *ahandler)
 {
+    int periods;
     /* snd_pcm_t *               handle = snd_async_handler_get_pcm(ahandler); */
     IDsDriverBufferImpl*      pdbi = snd_async_handler_get_callback_private(ahandler);
     TRACE("callback called\n");
-    DSDB_MMAPCopy(pdbi);
+    
+    /* Commit another block (the entire buffer if it's a direct hw buffer) */
+    periods = pdbi->mmap_mode == SND_PCM_TYPE_HW ? 0 : 1;
+    DSDB_MMAPCopy(pdbi, periods);
 }
 
+/**
+ * Allocate the memory-mapped buffer for direct sound, and set up the
+ * callback.
+ */
 static int DSDB_CreateMMAP(IDsDriverBufferImpl* pdbi)
 {
     WINE_WAVEDEV *            wwo = &(WOutDev[pdbi->drv->wDevID]);
@@ -3134,7 +3130,14 @@
     err = snd_pcm_hw_params_get_channels(wwo->hw_params, &channels);
     bits_per_sample = snd_pcm_format_physical_width(format);
     bits_per_frame = bits_per_sample * channels;
-
+    pdbi->mmap_mode = snd_pcm_type(wwo->pcm);
+    
+    if (pdbi->mmap_mode == SND_PCM_TYPE_HW) {
+        TRACE("mmap'd buffer is a hardware buffer.\n");
+    }
+    else {
+        TRACE("mmap'd buffer is an ALSA emulation of hardware buffer.\n");
+    }
 
     if (TRACE_ON(wave))
 	ALSA_TraceParameters(wwo->hw_params, NULL, FALSE);
@@ -3286,6 +3289,7 @@
     WINE_WAVEDEV *      wwo = &(WOutDev[This->drv->wDevID]);
     snd_pcm_uframes_t   hw_ptr;
     snd_pcm_uframes_t   period_size;
+    snd_pcm_state_t     state;
     int dir;
     int err;
 
@@ -3296,17 +3300,19 @@
 
     if (wwo->pcm == NULL) return DSERR_GENERIC;
     /** we need to track down buffer underruns */
-    DSDB_CheckXRUN(This);
+    DSDB_CheckXRUN(This);    
 
     EnterCriticalSection(&This->mmap_crst);
-    /* FIXME: snd_pcm_mmap_hw_ptr() should not be accessed by a user app. */
-    /*        It will NOT return what why want anyway. */
-    hw_ptr = _snd_pcm_mmap_hw_ptr(wwo->pcm);
-    if (hw_ptr >= period_size) hw_ptr -= period_size; else hw_ptr = 0;
+    hw_ptr = This->mmap_ppos;
+    
+    state = snd_pcm_state(wwo->pcm);
+    if (state != SND_PCM_STATE_RUNNING)
+      hw_ptr = 0;
+    
     if (lpdwPlay)
-	*lpdwPlay = snd_pcm_frames_to_bytes(wwo->pcm, hw_ptr/ period_size  * period_size) % This->mmap_buflen_bytes;
+	*lpdwPlay = snd_pcm_frames_to_bytes(wwo->pcm, hw_ptr) % This->mmap_buflen_bytes;
     if (lpdwWrite)
-	*lpdwWrite = snd_pcm_frames_to_bytes(wwo->pcm, (hw_ptr / period_size + 1) * period_size ) % This->mmap_buflen_bytes;
+	*lpdwWrite = snd_pcm_frames_to_bytes(wwo->pcm, hw_ptr + period_size * 2) % This->mmap_buflen_bytes;
     LeaveCriticalSection(&This->mmap_crst);
 
     TRACE("hw_ptr=0x%08x, playpos=%ld, writepos=%ld\n", (unsigned int)hw_ptr, lpdwPlay?*lpdwPlay:-1, lpdwWrite?*lpdwWrite:-1);
@@ -3331,10 +3337,29 @@
         state = snd_pcm_state(wwo->pcm);
     }
     if ( state == SND_PCM_STATE_PREPARED )
-     {
-	DSDB_MMAPCopy(This);
+    {
+	/* If we have a direct hardware buffer, we can commit the whole lot
+	 * immediately (periods = 0), otherwise we prime the queue with only
+	 * 2 periods.
+	 *
+	 * Why 2? We want a small number so that we don't get ahead of the
+	 * DirectSound mixer. But we don't want to ever let the buffer get
+	 * completely empty - having 2 periods gives us time to commit another
+	 * period when the first expires.
+	 *
+	 * The potential for buffer underrun is high, but that's the reality
+	 * of using a translated buffer (the whole point of DirectSound is
+	 * to provide direct access to the hardware).
+         * 
+         * A better implementation would use the buffer Lock() and Unlock()
+         * methods to determine how far ahead we can commit, and to rewind if
+         * necessary.
+	 */
+	int periods = This->mmap_mode == SND_PCM_TYPE_HW ? 0 : 2;
+	
+	DSDB_MMAPCopy(This, periods);
 	err = snd_pcm_start(wwo->pcm);
-     }
+    }
     return DS_OK;
 }
 
