Index: /trunk/src/VBox/Devices/Audio/DevIchAc97.cpp
===================================================================
--- /trunk/src/VBox/Devices/Audio/DevIchAc97.cpp	(revision 76178)
+++ /trunk/src/VBox/Devices/Audio/DevIchAc97.cpp	(revision 76179)
@@ -51,5 +51,8 @@
 
 /** Default timer frequency (in Hz). */
-#define AC97_TIMER_HZ_DEFAULT 200
+#define AC97_TIMER_HZ_DEFAULT 100
+
+/** Maximum number of streams we support. */
+#define AC97_MAX_STREAMS      3
 
 /** Maximum FIFO size (in bytes). */
@@ -219,4 +222,10 @@
 
 #ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/**
+ * Enumeration of AC'97 source indices.
+ *
+ * Note: The order of this indices is fixed (also applies for saved states) for the moment.
+ *       So make sure you know what you're done when altering this.
+ */
 typedef enum
 {
@@ -256,5 +265,8 @@
     uint32_t addr;
     uint32_t ctl_len;
-} AC97BDLE, *PAC97BDLE;
+} AC97BDLE;
+AssertCompileSize(AC97BDLE, 8);
+/** Pointer to BDLE. */
+typedef AC97BDLE *PAC97BDLE;
 
 /**
@@ -299,4 +311,7 @@
 #endif
 
+/** The ICH AC'97 (Intel) controller. */
+typedef struct AC97STATE *PAC97STATE;
+
 /**
  * Structure for keeping the internal state of an AC'97 stream.
@@ -318,11 +333,28 @@
     AC97STREAMSTATEAIO    AIO;
 #endif
-    /** Timestamp (in ns) of last DMA transfer.
-     *  For output streams this is the last DMA read,
-     *  for input streams this is the last DMA write. */
-    uint64_t              tsLastTransferNs;
-    /** Timestamp (in ns) of last DMA buffer read / write. */
-    uint64_t              tsLastReadWriteNs;
-    /** Timestamp (in ns) of last stream update. */
+    /** Timestamp of the last DMA data transfer. */
+    uint64_t              tsTransferLast;
+    /** Timestamp of the next DMA data transfer.
+     *  Next for determining the next scheduling window.
+     *  Can be 0 if no next transfer is scheduled. */
+    uint64_t              tsTransferNext;
+    /** Total transfer size (in bytes) of a transfer period. */
+    uint32_t              cbTransferSize;
+    /** Transfer chunk size (in bytes) of a transfer period. */
+    uint32_t              cbTransferChunk;
+    /** How many bytes already have been processed in within
+     *  the current transfer period. */
+    uint32_t              cbTransferProcessed;
+    /** The stream's timer Hz rate.
+     *  This value can can be different from the device's default Hz rate,
+     *  depending on the rate the stream expects (e.g. for 5.1 speaker setups).
+     *  Set in R3StreamInit(). */
+    uint16_t              uTimerHz;
+    uint8_t               Padding3[2];
+    /** (Virtual) clock ticks per byte. */
+    uint64_t              cTicksPerByte;
+    /** (Virtual) clock ticks per transfer. */
+    uint64_t              cTransferTicks;
+   /** Timestamp (in ns) of last stream update. */
     uint64_t              tsLastUpdateNs;
 } AC97STREAMSTATE;
@@ -364,12 +396,14 @@
 {
     /** Stream number (SDn). */
-    uint8_t           u8SD;
-    uint8_t           abPadding[7];
+    uint8_t               u8SD;
+    uint8_t               abPadding[7];
     /** Bus master registers of this stream. */
-    AC97BMREGS        Regs;
+    AC97BMREGS            Regs;
     /** Internal state of this stream. */
-    AC97STREAMSTATE   State;
+    AC97STREAMSTATE       State;
+    /** Pointer to parent (AC'97 state). */
+    R3PTRTYPE(PAC97STATE) pAC97State;
     /** Debug information. */
-    AC97STREAMDBGINFO Dbg;
+    AC97STREAMDBGINFO     Dbg;
 } AC97STREAM, *PAC97STREAM;
 AssertCompileSizeAlignment(AC97STREAM, 8);
@@ -464,27 +498,14 @@
     uint32_t                last_samp;
     uint8_t                 mixer_data[256];
-    /** AC'97 stream for line-in. */
-    AC97STREAM              StreamLineIn;
-    /** AC'97 stream for microphone-in. */
-    AC97STREAM              StreamMicIn;
-    /** AC'97 stream for output. */
-    AC97STREAM              StreamOut;
-    /** Number of active (running) SDn streams. */
-    uint8_t                 cStreamsActive;
-    /** Flag indicating whether the timer is active or not. */
-    bool                    fTimerActive;
+    /** Array of AC'97 streams. */
+    AC97STREAM              aStreams[AC97_MAX_STREAMS];
     /** The device timer Hz rate. Defaults to AC97_TIMER_HZ_DEFAULT_DEFAULT. */
     uint16_t                uTimerHz;
     /** The timer for pumping data thru the attached LUN drivers - RCPtr. */
-    PTMTIMERRC              pTimerRC;
+    PTMTIMERRC              pTimerRC[AC97_MAX_STREAMS];
     /** The timer for pumping data thru the attached LUN drivers - R3Ptr. */
-    PTMTIMERR3              pTimerR3;
+    PTMTIMERR3              pTimerR3[AC97_MAX_STREAMS];
     /** The timer for pumping data thru the attached LUN drivers - R0Ptr. */
-    PTMTIMERR0              pTimerR0;
-    /** The timer interval for pumping data thru the LUN drivers in timer ticks. */
-    uint64_t                cTimerTicks;
-    /** Timestamp of the last timer callback (ac97Timer).
-     * Used to calculate the time actually elapsed between two timer callbacks. */
-    uint64_t                uTimerTS;
+    PTMTIMERR0              pTimerR0[AC97_MAX_STREAMS];
 #ifdef VBOX_WITH_STATISTICS
     STAMPROFILE             StatTimer;
@@ -517,5 +538,5 @@
     AC97STATEDBGINFO        Dbg;
 } AC97STATE;
-AssertCompileMemberAlignment(AC97STATE, StreamLineIn, 8);
+AssertCompileMemberAlignment(AC97STATE, aStreams, 8);
 /** Pointer to a AC'97 state. */
 typedef AC97STATE *PAC97STATE;
@@ -556,4 +577,15 @@
     } while (0)
 
+#ifdef IN_RC
+/** Retrieves an attribute from a specific audio stream in RC. */
+# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD)      a_Var##RC[a_SD]
+#elif defined(IN_RING0)
+/** Retrieves an attribute from a specific audio stream in R0. */
+# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD)      a_Var##R0[a_SD]
+#else
+/** Retrieves an attribute from a specific audio stream in R3. */
+# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD)      a_Var##R3[a_SD]
+#endif
+
 /**
  * Releases the AC'97 lock.
@@ -565,7 +597,7 @@
  * Acquires the TM lock and AC'97 lock, returns on failure.
  */
-#define DEVAC97_LOCK_BOTH_RETURN_VOID(a_pThis) \
+#define DEVAC97_LOCK_BOTH_RETURN_VOID(a_pThis, a_SD) \
     do { \
-        int rcLock = TMTimerLock((a_pThis)->CTX_SUFF(pTimer), VERR_IGNORED); \
+        int rcLock = TMTimerLock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD), VERR_IGNORED); \
         if (rcLock != VINF_SUCCESS) \
         { \
@@ -577,5 +609,5 @@
         { \
             AssertRC(rcLock); \
-            TMTimerUnlock((a_pThis)->CTX_SUFF(pTimer)); \
+            TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \
             return; \
         } \
@@ -585,7 +617,7 @@
  * Acquires the TM lock and AC'97 lock, returns on failure.
  */
-#define DEVAC97_LOCK_BOTH_RETURN(a_pThis, a_rcBusy) \
+#define DEVAC97_LOCK_BOTH_RETURN(a_pThis, a_SD, a_rcBusy) \
     do { \
-        int rcLock = TMTimerLock((a_pThis)->CTX_SUFF(pTimer), (a_rcBusy)); \
+        int rcLock = TMTimerLock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD), (a_rcBusy)); \
         if (rcLock != VINF_SUCCESS) \
             return rcLock; \
@@ -594,5 +626,5 @@
         { \
             AssertRC(rcLock); \
-            TMTimerUnlock((a_pThis)->CTX_SUFF(pTimer)); \
+            TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \
             return rcLock; \
         } \
@@ -602,8 +634,8 @@
  * Releases the AC'97 lock and TM lock.
  */
-#define DEVAC97_UNLOCK_BOTH(a_pThis) \
+#define DEVAC97_UNLOCK_BOTH(a_pThis, a_SD) \
     do { \
         PDMCritSectLeave(&(a_pThis)->CritSect); \
-        TMTimerUnlock((a_pThis)->CTX_SUFF(pTimer)); \
+        TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \
     } while (0)
 
@@ -636,12 +668,5 @@
 static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns);
 
-static int                ichac97R3TimerStart(PAC97STATE pThis);
-static int                ichac97R3TimerMaybeStart(PAC97STATE pThis);
-static int                ichac97R3TimerStop(PAC97STATE pThis);
-static int                ichac97R3TimerMaybeStop(PAC97STATE pThis);
-static void               ichac97R3TimerMain(PAC97STATE pThis);
 static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
-
-static void               ichac97R3DoTransfers(PAC97STATE pThis);
 
 static int                ichac97R3MixerAddDrv(PAC97STATE pThis, PAC97DRIVER pDrv);
@@ -663,4 +688,8 @@
 
 DECLINLINE(PDMAUDIODIR)   ichac97GetDirFromSD(uint8_t uSD);
+
+# ifdef LOG_ENABLED
+static void               ichac97R3BDLEDumpAll(PAC97STATE pThis, uint64_t u64BDLBase, uint16_t cBDLE);
+# endif
 #endif /* IN_RING3 */
 
@@ -714,13 +743,12 @@
     PAC97BMREGS pRegs   = &pStream->Regs;
 
-    uint32_t u32[2];
-
-    PDMDevHlpPhysRead(pDevIns, pRegs->bdbar + pRegs->civ * 8, &u32[0], sizeof(u32));
+    AC97BDLE BDLE;
+    PDMDevHlpPhysRead(pDevIns, pRegs->bdbar + pRegs->civ * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE));
     pRegs->bd_valid   = 1;
 # ifndef RT_LITTLE_ENDIAN
 #  error "Please adapt the code (audio buffers are little endian)!"
 # else
-    pRegs->bd.addr    = RT_H2LE_U32(u32[0] & ~3);
-    pRegs->bd.ctl_len = RT_H2LE_U32(u32[1]);
+    pRegs->bd.addr    = RT_H2LE_U32(BDLE.addr & ~3);
+    pRegs->bd.ctl_len = RT_H2LE_U32(BDLE.ctl_len);
 # endif
     pRegs->picb       = pRegs->bd.ctl_len & AC97_BD_LEN_MASK;
@@ -779,4 +807,5 @@
     {
         static uint32_t const s_aMasks[] = { AC97_GS_PIINT, AC97_GS_POINT, AC97_GS_MINT };
+        Assert(pStream->u8SD < AC97_MAX_STREAMS);
         if (iIRQL)
             pThis->glob_sta |=  s_aMasks[pStream->u8SD];
@@ -876,11 +905,5 @@
     ichac97R3StreamUnlock(pStream);
 
-    /* Second, see if we need to start or stop the timer. */
-    if (!fEnable)
-        ichac97R3TimerMaybeStop(pThis);
-    else
-        ichac97R3TimerMaybeStart(pThis);
-
-    LogFunc(("[SD%RU8] cStreamsActive=%RU8, fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, pThis->cStreamsActive, fEnable, rc));
+    LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc));
     return rc;
 }
@@ -927,7 +950,7 @@
  * @param   pThis               AC'97 state.
  * @param   pStream             AC'97 stream to create.
- * @param   u8Strm              Stream ID to assign AC'97 stream to.
- */
-static int ichac97R3StreamCreate(PAC97STATE pThis, PAC97STREAM pStream, uint8_t u8Strm)
+ * @param   u8SD                Stream descriptor number to assign.
+ */
+static int ichac97R3StreamCreate(PAC97STATE pThis, PAC97STREAM pStream, uint8_t u8SD)
 {
     RT_NOREF(pThis);
@@ -935,8 +958,9 @@
     /** @todo Validate u8Strm. */
 
-    LogFunc(("[SD%RU8] pStream=%p\n", u8Strm, pStream));
-
-    Assert(u8Strm < 3);
-    pStream->u8SD = u8Strm;
+    LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream));
+
+    AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER);
+    pStream->u8SD       = u8SD;
+    pStream->pAC97State = pThis;
 
     int rc = RTCritSectInit(&pStream->State.CritSect);
@@ -1033,8 +1057,6 @@
      * Destroy all AC'97 streams.
      */
-
-    ichac97R3StreamDestroy(pThis, &pThis->StreamLineIn);
-    ichac97R3StreamDestroy(pThis, &pThis->StreamMicIn);
-    ichac97R3StreamDestroy(pThis, &pThis->StreamOut);
+    for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+        ichac97R3StreamDestroy(pThis, &pThis->aStreams[i]);
 
     /*
@@ -1111,6 +1133,4 @@
     RTCircBufReleaseWriteBlock(pCircBuf, cbRead);
 
-    pDstStream->State.tsLastReadWriteNs = RTTimeNanoTS();
-
     if (pcbWritten)
         *pcbWritten = cbRead;
@@ -1181,6 +1201,4 @@
     }
 
-    pSrcStream->State.tsLastReadWriteNs = RTTimeNanoTS();
-
     if (pcbRead)
         *pcbRead = cbReadTotal;
@@ -1399,6 +1417,33 @@
 }
 #endif
-
 # endif /* VBOX_WITH_AUDIO_AC97_ASYNC_IO */
+
+# ifdef LOG_ENABLED
+static void ichac97R3BDLEDumpAll(PAC97STATE pThis, uint64_t u64BDLBase, uint16_t cBDLE)
+{
+    LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE));
+    if (!u64BDLBase)
+        return;
+
+    uint32_t cbBDLE = 0;
+    for (uint16_t i = 0; i < cBDLE; i++)
+    {
+        AC97BDLE BDLE;
+        PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BDLBase + i * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE));
+
+        BDLE.addr    = RT_H2LE_U32(BDLE.addr & ~3);
+        BDLE.ctl_len = RT_H2LE_U32(BDLE.ctl_len);
+
+        LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32 [%RU32 bytes])\n",
+                  i, BDLE.addr,
+                  BDLE.ctl_len & AC97_BD_LEN_MASK,
+                 (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1)); /** @todo r=andy Assumes 16bit samples. */
+
+        cbBDLE += (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1; /** @todo r=andy Ditto. */
+    }
+
+    LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE));
+}
+# endif /* LOG_ENABLED */
 
 /**
@@ -1444,9 +1489,12 @@
 # endif
         {
+            uint32_t cbTransferChunk = (pStream->State.Cfg.Props.uHz / pStream->State.uTimerHz)
+                                     * DrvAudioHlpPCMPropsBytesPerFrame(&pStream->State.Cfg.Props);
+
             const uint32_t cbStreamFree = ichac97R3StreamGetFree(pStream);
             if (cbStreamFree)
             {
                 /* Do the DMA transfer. */
-                rc2 = ichac97R3StreamTransfer(pThis, pStream, cbStreamFree);
+                rc2 = ichac97R3StreamTransfer(pThis, pStream, RT_MIN(cbStreamFree, cbTransferChunk));
                 AssertRC(rc2);
             }
@@ -1775,21 +1823,24 @@
     int rc = VINF_SUCCESS;
 
-    if (DrvAudioHlpStreamCfgIsValid(&pThis->StreamLineIn.State.Cfg))
-    {
-        int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkLineIn, &pThis->StreamLineIn.State.Cfg, pDrv);
+    if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg))
+    {
+        int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkLineIn,
+                                             &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv);
         if (RT_SUCCESS(rc))
             rc = rc2;
     }
 
-    if (DrvAudioHlpStreamCfgIsValid(&pThis->StreamMicIn.State.Cfg))
-    {
-        int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkMicIn,  &pThis->StreamMicIn.State.Cfg, pDrv);
+    if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg))
+    {
+        int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkOut,
+                                             &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv);
         if (RT_SUCCESS(rc))
             rc = rc2;
     }
 
-    if (DrvAudioHlpStreamCfgIsValid(&pThis->StreamOut.State.Cfg))
-    {
-        int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkOut,    &pThis->StreamOut.State.Cfg, pDrv);
+    if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg))
+    {
+        int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkMicIn,
+                                             &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv);
         if (RT_SUCCESS(rc))
             rc = rc2;
@@ -1979,5 +2030,5 @@
                 }
 
-                rc = RTCircBufCreate(&pStream->State.pCircBuf, DrvAudioHlpMilliToBytes(500 /* ms */, &Cfg.Props)); /** @todo Make this configurable. */
+                rc = RTCircBufCreate(&pStream->State.pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &Cfg.Props)); /** @todo Make this configurable. */
                 if (RT_SUCCESS(rc))
                 {
@@ -1988,4 +2039,50 @@
                         rc = DrvAudioHlpStreamCfgCopy(&pStream->State.Cfg, &Cfg);
                 }
+
+                /*
+                 * Set the stream's timer Hz rate.
+                 *
+                 * Currently we simply apply the global Hz rate.
+                 * This might needs tweaking as we add surround support and/or channel striping later. */
+                pStream->State.uTimerHz = pThis->uTimerHz;
+
+                /*
+                 * Set up data transfer stuff.
+                 */
+#ifdef LOG_ENABLED
+                ichac97R3BDLEDumpAll(pThis, pStream->Regs.bdbar, pStream->Regs.lvi + 1);
+#endif
+                const uint32_t cbFrame = DrvAudioHlpPCMPropsBytesPerFrame(&Cfg.Props);
+
+                /* Calculate the fragment size the guest OS expects interrupt delivery at. */
+                pStream->State.cbTransferSize = 441 * 4;//pStream->u32CBL / cFragments;
+                Assert(pStream->State.cbTransferSize);
+                Assert(pStream->State.cbTransferSize % cbFrame == 0);
+
+                /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration.
+                 * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */
+                pStream->State.cbTransferChunk = (pStream->State.Cfg.Props.uHz / pStream->State.uTimerHz) * cbFrame;
+                Assert(pStream->State.cbTransferChunk);
+                Assert(pStream->State.cbTransferChunk % cbFrame== 0);
+
+                /* Make sure that the transfer chunk does not exceed the overall transfer size. */
+                if (pStream->State.cbTransferChunk > pStream->State.cbTransferSize)
+                    pStream->State.cbTransferChunk = pStream->State.cbTransferSize;
+
+                const uint64_t cTicksPerHz = TMTimerGetFreq((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD))/ pStream->State.uTimerHz;
+
+                /* Calculate the timer ticks per byte for this stream. */
+                pStream->State.cTicksPerByte = cTicksPerHz / pStream->State.cbTransferChunk;
+                Assert(pStream->State.cTicksPerByte);
+
+                /* Calculate timer ticks per transfer. */
+                pStream->State.cTransferTicks     = pStream->State.cbTransferChunk * pStream->State.cTicksPerByte;
+                Assert(pStream->State.cTransferTicks);
+
+                LogFunc(("[SD%RU8] Timer %uHz (%RU64 ticks per Hz), cTicksPerByte=%RU64, cbTransferChunk=%RU32, cTransferTicks=%RU64, " \
+                         "cbTransferSize=%RU32\n",
+                         pStream->u8SD, pStream->State.uTimerHz, cTicksPerHz, pStream->State.cTicksPerByte,
+                         pStream->State.cbTransferChunk, pStream->State.cTransferTicks, pStream->State.cbTransferSize));
+
             }
         }
@@ -2342,27 +2439,9 @@
     }
 
+    AssertFailed();
     return PDMAUDIODIR_UNKNOWN;
 }
 
 #endif /* IN_RING3 */
-
-/**
- * Retrieves an AC'97 audio stream from an AC'97 stream index.
- *
- * @returns Pointer to AC'97 audio stream if found, or NULL if not found / invalid.
- * @param   pThis               AC'97 state.
- * @param   uIdx                AC'97 stream index to retrieve AC'97 audio stream for.
- */
-DECLINLINE(PAC97STREAM) ichac97GetStreamFromIdx(PAC97STATE pThis, uint32_t uIdx)
-{
-    switch (uIdx)
-    {
-        case AC97SOUNDSOURCE_PI_INDEX: return &pThis->StreamLineIn;
-        case AC97SOUNDSOURCE_MC_INDEX: return &pThis->StreamMicIn;
-        case AC97SOUNDSOURCE_PO_INDEX: return &pThis->StreamOut;
-        default:                       return NULL;
-    }
-
-}
 
 #ifdef IN_RING3
@@ -2502,157 +2581,4 @@
 
 /**
- * Starts the internal audio device timer.
- *
- * @return  IPRT status code.
- * @param   pThis               AC'97 state.
- */
-static int  ichac97R3TimerStart(PAC97STATE pThis)
-{
-    LogFlowFuncEnter();
-
-    DEVAC97_LOCK_BOTH_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
-
-    AssertPtr(pThis->CTX_SUFF(pTimer));
-
-    if (!pThis->fTimerActive)
-    {
-        LogRel2(("AC97: Starting transfers\n"));
-
-        pThis->fTimerActive = true;
-
-        /* Start transfers. */
-        ichac97R3TimerMain(pThis);
-    }
-
-    DEVAC97_UNLOCK_BOTH(pThis);
-
-    return VINF_SUCCESS;
-}
-
-/**
- * Starts the internal audio device timer (if not started yet).
- *
- * @return  IPRT status code.
- * @param   pThis               AC'97 state.
- */
-static int ichac97R3TimerMaybeStart(PAC97STATE pThis)
-{
-    LogFlowFuncEnter();
-
-    if (!pThis->CTX_SUFF(pTimer))
-        return VERR_WRONG_ORDER;
-
-    pThis->cStreamsActive++;
-
-    /* Only start the timer at the first active stream. */
-    if (pThis->cStreamsActive == 1)
-        return ichac97R3TimerStart(pThis);
-
-    return VINF_SUCCESS;
-}
-
-/**
- * Stops the internal audio device timer.
- *
- * @return  IPRT status code.
- * @param   pThis               AC'97 state.
- */
-static int ichac97R3TimerStop(PAC97STATE pThis)
-{
-    LogFlowFuncEnter();
-
-    if (!pThis->CTX_SUFF(pTimer)) /* Only can happen on device construction time, so no locking needed here. */
-        return VINF_SUCCESS;
-
-    DEVAC97_LOCK_BOTH_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
-
-    if (pThis->fTimerActive)
-    {
-        LogRel2(("AC97: Stopping transfers ...\n"));
-
-        pThis->fTimerActive = false;
-
-        /* Note: Do not stop the timer via TMTimerStop() here, as there still might
-         *       be queued audio data which needs to be handled (e.g. played back) first
-         *       before actually stopping the timer for good. */
-    }
-
-    DEVAC97_UNLOCK_BOTH(pThis);
-
-    return VINF_SUCCESS;
-}
-
-/**
- * Decreases the active AC'97 streams count by one and
- * then checks if the internal audio device timer can be
- * stopped.
- *
- * @return  IPRT status code.
- * @param   pThis               AC'97 state.
- */
-static int ichac97R3TimerMaybeStop(PAC97STATE pThis)
-{
-    LogFlowFuncEnter();
-
-    if (!pThis->CTX_SUFF(pTimer))
-        return VERR_WRONG_ORDER;
-
-    if (pThis->cStreamsActive) /* Function can be called mupltiple times. */
-    {
-        pThis->cStreamsActive--;
-
-        if (pThis->cStreamsActive == 0)
-            return ichac97R3TimerStop(pThis);
-    }
-
-    return VINF_SUCCESS;
-}
-
-/**
- * Main routine for the device timer.
- *
- * @param   pThis               AC'97 state.
- */
-static void ichac97R3TimerMain(PAC97STATE pThis)
-{
-    STAM_PROFILE_START(&pThis->StatTimer, a);
-
-    DEVAC97_LOCK_BOTH_RETURN_VOID(pThis);
-
-    uint64_t cTicksNow = TMTimerGet(pThis->CTX_SUFF(pTimer));
-
-    /* Update current time timestamp. */
-    pThis->uTimerTS = cTicksNow;
-
-    /* Flag indicating whether to arm the timer again for the next DMA transfer or sink processing. */
-    bool fArmTimer = false;
-
-    ichac97R3DoTransfers(pThis);
-
-    /* Do we need to arm the timer again? */
-    if (   AudioMixerSinkIsActive(ichac97R3IndexToSink(pThis, pThis->StreamLineIn.u8SD))
-        || AudioMixerSinkIsActive(ichac97R3IndexToSink(pThis, pThis->StreamMicIn.u8SD))
-        || AudioMixerSinkIsActive(ichac97R3IndexToSink(pThis, pThis->StreamOut.u8SD)))
-    {
-        fArmTimer = true;
-    }
-
-    if (   ASMAtomicReadBool(&pThis->fTimerActive) /** @todo r=bird: totally unnecessary to do atomic read here, isn't it? */
-        || fArmTimer)
-    {
-        /* Arm the timer again. */
-        uint64_t cTicks = pThis->cTimerTicks;
-        /** @todo adjust cTicks down by now much cbOutMin represents. */
-        TMTimerSet(pThis->CTX_SUFF(pTimer), cTicksNow + cTicks);
-    }
-    else
-        LogRel2(("AC97: Stopped transfers\n"));
-
-    DEVAC97_UNLOCK_BOTH(pThis);
-
-    STAM_PROFILE_STOP(&pThis->StatTimer, a);
-}
-
-/**
  * Timer callback which handles the audio data transfers on a periodic basis.
  *
@@ -2665,23 +2591,72 @@
     RT_NOREF(pDevIns, pTimer);
 
-    PAC97STATE pThis = (PAC97STATE)pvUser;
-    Assert(pThis == PDMINS_2_DATA(pDevIns, PAC97STATE));
-
-    ichac97R3TimerMain(pThis);
-}
-
-/**
- * Main routine to perform the actual audio data transfers from the AC'97 streams
- * to the backend(s) and vice versa.
- *
+    PAC97STREAM pStream = (PAC97STREAM)pvUser;
+    AssertPtr(pStream);
+
+    PAC97STATE  pThis   = pStream->pAC97State;
+    AssertPtr(pThis);
+
+    STAM_PROFILE_START(&pThis->StatTimer, a);
+
+    DEVAC97_LOCK_BOTH_RETURN_VOID(pThis, pStream->u8SD);
+
+    ichac97R3StreamUpdate(pThis, pStream, true /* fInTimer */);
+
+    PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD);
+
+    bool fSinkActive = false;
+    if (pSink)
+        fSinkActive = AudioMixerSinkIsActive(pSink);
+
+    if (fSinkActive)
+    {
+        TMTimerSet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD),
+                   TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks);
+    }
+
+    DEVAC97_UNLOCK_BOTH(pThis, pStream->u8SD);
+
+    STAM_PROFILE_STOP(&pThis->StatTimer, a);
+}
+
+/**
+ * Sets the virtual device timer to a new expiration time.
+ *
+ * @returns Whether the new expiration time was set or not.
  * @param   pThis               AC'97 state.
- */
-static void ichac97R3DoTransfers(PAC97STATE pThis)
-{
-    AssertPtrReturnVoid(pThis);
-
-    ichac97R3StreamUpdate(pThis, &pThis->StreamLineIn, true /* fInTimer */);
-    ichac97R3StreamUpdate(pThis, &pThis->StreamMicIn,  true /* fInTimer */);
-    ichac97R3StreamUpdate(pThis, &pThis->StreamOut,    true /* fInTimer */);
+ * @param   pStream             AC'97 stream to set timer for.
+ * @param   tsExpire            New (virtual) expiration time to set.
+ * @param   fForce              Whether to force setting the expiration time or not.
+ *
+ * @remark  This function takes all active AC'97 streams and their
+ *          current timing into account. This is needed to make sure
+ *          that all streams can match their needed timing.
+ *
+ *          To achieve this, the earliest (lowest) timestamp of all
+ *          active streams found will be used for the next scheduling slot.
+ *
+ *          Forcing a new expiration time will override the above mechanism.
+ */
+bool ichac97R3TimerSet(PAC97STATE pThis, PAC97STREAM pStream, uint64_t tsExpire, bool fForce)
+{
+    AssertPtrReturn(pThis, false);
+    AssertPtrReturn(pStream, false);
+
+    RT_NOREF(fForce);
+
+    uint64_t tsExpireMin = tsExpire;
+
+    AssertPtr((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD));
+
+    const uint64_t tsNow = TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD));
+
+    /* Make sure to not go backwards in time, as this will assert in TMTimerSet(). */
+    if (tsExpireMin < tsNow)
+        tsExpireMin = tsNow;
+
+    int rc = TMTimerSet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD), tsExpireMin);
+    AssertRC(rc);
+
+    return RT_SUCCESS(rc);
 }
 
@@ -2891,6 +2866,4 @@
     }
 
-    pStream->State.tsLastTransferNs = RTTimeNanoTS();
-
     ichac97R3StreamUnlock(pStream);
 
@@ -2925,9 +2898,13 @@
     const uint32_t uPortIdx = uPort - pThis->IOPortBase[1];
 
-    PAC97STREAM pStream = ichac97GetStreamFromIdx(pThis, AC97_PORT2IDX(uPortIdx));
+    PAC97STREAM pStream = NULL;
     PAC97BMREGS pRegs   = NULL;
 
-    if (pStream) /* Can be NULL, depending on the index (port). */
-        pRegs = &pStream->Regs;
+    if (AC97_PORT2IDX(uPortIdx) < AC97_MAX_STREAMS)
+    {
+        pStream = &pThis->aStreams[AC97_PORT2IDX(uPortIdx)];
+        AssertPtr(pStream);
+        pRegs   = &pStream->Regs;
+    }
 
     int rc = VINF_SUCCESS;
@@ -3091,14 +3068,18 @@
     RT_NOREF(pvUser);
 
-    DEVAC97_LOCK_BOTH_RETURN(pThis, VINF_IOM_R3_IOPORT_WRITE);
-
     /* Get the index of the NABMBAR register. */
     const uint32_t uPortIdx = uPort - pThis->IOPortBase[1];
 
-    PAC97STREAM pStream = ichac97GetStreamFromIdx(pThis, AC97_PORT2IDX(uPortIdx));
+    PAC97STREAM pStream = NULL;
     PAC97BMREGS pRegs   = NULL;
 
-    if (pStream) /* Can be NULL, depending on the index (port). */
+    if (AC97_PORT2IDX(uPortIdx) < AC97_MAX_STREAMS)
+    {
+        pStream = &pThis->aStreams[AC97_PORT2IDX(uPortIdx)];
+        AssertPtr(pStream);
         pRegs = &pStream->Regs;
+
+        DEVAC97_LOCK_BOTH_RETURN(pThis, pStream->u8SD, VINF_IOM_R3_IOPORT_WRITE);
+    }
 
     int rc = VINF_SUCCESS;
@@ -3116,4 +3097,6 @@
                 case MC_LVI:
                 {
+                    AssertPtr(pStream);
+                    AssertPtr(pRegs);
                     if (   (pRegs->cr & AC97_CR_RPBM)
                         && (pRegs->sr & AC97_SR_DCH))
@@ -3141,4 +3124,6 @@
                 case MC_CR:
                 {
+                    AssertPtr(pStream);
+                    AssertPtr(pRegs);
 #ifdef IN_RING3
                     Log3Func(("[SD%RU8] CR <- %#x (cr %#x)\n", pStream->u8SD, u32Val, pRegs->cr));
@@ -3178,6 +3163,11 @@
                             /* Fetch the initial BDLE descriptor. */
                             ichac97R3StreamFetchBDLE(pThis, pStream);
-
                             ichac97R3StreamEnable(pThis, pStream, true /* fEnable */);
+
+                            /* Arm the timer for this stream. */
+                            int rc2 = ichac97R3TimerSet(pThis, pStream,
+                                                        TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks,
+                                                        false /* fForce */);
+                            AssertRC(rc2);
                         }
                     }
@@ -3195,4 +3185,6 @@
                 case MC_SR:
                 {
+                    AssertPtr(pStream);
+                    AssertPtr(pRegs);
                     pRegs->sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK);
                     ichac97StreamUpdateSR(pThis, pStream, pRegs->sr & ~(u32Val & AC97_SR_WCLEAR_MASK));
@@ -3215,4 +3207,6 @@
                 case PO_SR:
                 case MC_SR:
+                    AssertPtr(pStream);
+                    AssertPtr(pRegs);
                     /* Status Register */
                     pRegs->sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK);
@@ -3234,4 +3228,6 @@
                 case PO_BDBAR:
                 case MC_BDBAR:
+                    AssertPtr(pStream);
+                    AssertPtr(pRegs);
                     /* Buffer Descriptor list Base Address Register */
                     pRegs->bdbar = u32Val & ~3;
@@ -3266,5 +3262,6 @@
     }
 
-    DEVAC97_UNLOCK_BOTH(pThis);
+    if (pStream)
+        DEVAC97_UNLOCK_BOTH(pThis, pStream->u8SD);
 
     return rc;
@@ -3350,7 +3347,8 @@
     RT_NOREF(pvUser);
 
-    DEVAC97_LOCK_BOTH_RETURN(pThis, VINF_IOM_R3_IOPORT_WRITE);
+    DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_WRITE);
 
     uint32_t uPortIdx = uPort - pThis->IOPortBase[0];
+
     int rc = VINF_SUCCESS;
     switch (cbVal)
@@ -3456,8 +3454,8 @@
                     {
                         ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate, 48000);
-                        ichac97R3StreamReOpen(pThis, &pThis->StreamOut);
+                        ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]);
 
                         ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate,    48000);
-                        ichac97R3StreamReOpen(pThis, &pThis->StreamLineIn);
+                        ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]);
                     }
                     else
@@ -3467,5 +3465,5 @@
                     {
                         ichac97MixerSet(pThis, AC97_MIC_ADC_Rate,       48000);
-                        ichac97R3StreamReOpen(pThis, &pThis->StreamMicIn);
+                        ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]);
                     }
                     else
@@ -3484,5 +3482,5 @@
                         ichac97MixerSet(pThis, uPortIdx, u32Val);
                         LogFunc(("Set front DAC rate to %RU32\n", u32Val));
-                        ichac97R3StreamReOpen(pThis, &pThis->StreamOut);
+                        ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]);
 #else
                         rc = VINF_IOM_R3_IOPORT_WRITE;
@@ -3498,5 +3496,5 @@
                         ichac97MixerSet(pThis, uPortIdx, u32Val);
                         LogFunc(("Set MIC ADC rate to %RU32\n", u32Val));
-                        ichac97R3StreamReOpen(pThis, &pThis->StreamMicIn);
+                        ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]);
 #else
                         rc = VINF_IOM_R3_IOPORT_WRITE;
@@ -3512,5 +3510,5 @@
                         ichac97MixerSet(pThis, uPortIdx, u32Val);
                         LogFunc(("Set front LR ADC rate to %RU32\n", u32Val));
-                        ichac97R3StreamReOpen(pThis, &pThis->StreamLineIn);
+                        ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]);
 #else
                         rc = VINF_IOM_R3_IOPORT_WRITE;
@@ -3540,5 +3538,5 @@
     }
 
-    DEVAC97_UNLOCK_BOTH(pThis);
+    DEVAC97_UNLOCK(pThis);
 
     return rc;
@@ -3643,11 +3641,10 @@
 
     /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */
-    /* Note: The order the streams are saved here is critical, so don't touch. */
-    int rc2 = ichac97R3SaveStream(pDevIns, pSSM, &pThis->StreamLineIn);
-    AssertRC(rc2);
-    rc2 = ichac97R3SaveStream(pDevIns, pSSM, &pThis->StreamOut);
-    AssertRC(rc2);
-    rc2 = ichac97R3SaveStream(pDevIns, pSSM, &pThis->StreamMicIn);
-    AssertRC(rc2);
+    /* Note: The order the streams are loaded here is critical, so don't touch. */
+    for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+    {
+        int rc2 = ichac97R3SaveStream(pDevIns, pSSM, &pThis->aStreams[i]);
+        AssertRC(rc2);
+    }
 
     SSMR3PutMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
@@ -3655,7 +3652,7 @@
     uint8_t active[AC97SOUNDSOURCE_END_INDEX];
 
-    active[AC97SOUNDSOURCE_PI_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->StreamLineIn) ? 1 : 0;
-    active[AC97SOUNDSOURCE_PO_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->StreamOut)    ? 1 : 0;
-    active[AC97SOUNDSOURCE_MC_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->StreamMicIn)  ? 1 : 0;
+    active[AC97SOUNDSOURCE_PI_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]) ? 1 : 0;
+    active[AC97SOUNDSOURCE_PO_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]) ? 1 : 0;
+    active[AC97SOUNDSOURCE_MC_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]) ? 1 : 0;
 
     SSMR3PutMem(pSSM, active, sizeof(active));
@@ -3706,10 +3703,9 @@
     /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */
     /* Note: The order the streams are loaded here is critical, so don't touch. */
-    int rc2 = ichac97R3LoadStream(pSSM, &pThis->StreamLineIn);
-    AssertRCReturn(rc2, rc2);
-    rc2 = ichac97R3LoadStream(pSSM, &pThis->StreamOut);
-    AssertRCReturn(rc2, rc2);
-    rc2 = ichac97R3LoadStream(pSSM, &pThis->StreamMicIn);
-    AssertRCReturn(rc2, rc2);
+    for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+    {
+        int rc2 = ichac97R3LoadStream(pSSM, &pThis->aStreams[i]);
+        AssertRCReturn(rc2, rc2);
+    }
 
     SSMR3GetMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
@@ -3717,5 +3713,5 @@
     /** @todo r=andy Stream IDs are hardcoded to certain streams. */
     uint8_t uaStrmsActive[AC97SOUNDSOURCE_END_INDEX];
-    rc2 = SSMR3GetMem(pSSM, uaStrmsActive, sizeof(uaStrmsActive));
+    int rc2 = SSMR3GetMem(pSSM, uaStrmsActive, sizeof(uaStrmsActive));
     AssertRCReturn(rc2, rc2);
 
@@ -3733,9 +3729,10 @@
 
     /** @todo r=andy Stream IDs are hardcoded to certain streams. */
-    rc2 = ichac97R3StreamEnable(pThis, &pThis->StreamLineIn,    RT_BOOL(uaStrmsActive[AC97SOUNDSOURCE_PI_INDEX]));
-    if (RT_SUCCESS(rc2))
-        rc2 = ichac97R3StreamEnable(pThis, &pThis->StreamMicIn, RT_BOOL(uaStrmsActive[AC97SOUNDSOURCE_MC_INDEX]));
-    if (RT_SUCCESS(rc2))
-        rc2 = ichac97R3StreamEnable(pThis, &pThis->StreamOut,   RT_BOOL(uaStrmsActive[AC97SOUNDSOURCE_PO_INDEX]));
+    for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+    {
+        rc2 = ichac97R3StreamEnable(pThis, &pThis->aStreams[i], RT_BOOL(uaStrmsActive[i]));
+        AssertRC(rc2);
+        /* Keep going. */
+    }
 
     pThis->bup_flag  = 0;
@@ -3809,12 +3806,9 @@
      * Reset all streams.
      */
-    ichac97R3StreamEnable(pThis, &pThis->StreamLineIn, false /* fEnable */);
-    ichac97R3StreamReset(pThis, &pThis->StreamLineIn);
-
-    ichac97R3StreamEnable(pThis, &pThis->StreamMicIn, false /* fEnable */);
-    ichac97R3StreamReset(pThis, &pThis->StreamMicIn);
-
-    ichac97R3StreamEnable(pThis, &pThis->StreamOut, false /* fEnable */);
-    ichac97R3StreamReset(pThis, &pThis->StreamOut);
+    for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+    {
+        ichac97R3StreamEnable(pThis, &pThis->aStreams[i], false /* fEnable */);
+        ichac97R3StreamReset(pThis, &pThis->aStreams[i]);
+    }
 
     /*
@@ -3827,11 +3821,4 @@
     AudioMixerSinkReset(pThis->pSinkMicIn);
     AudioMixerSinkReset(pThis->pSinkOut);
-
-    /*
-     * Stop the timer, if any.
-     */
-    ichac97R3TimerStop(pThis);
-
-    pThis->cStreamsActive = 0;
 }
 
@@ -4096,5 +4083,7 @@
     PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
     pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
-    pThis->pTimerRC  = TMTimerRCPtr(pThis->pTimerR3);
+
+    for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+        pThis->pTimerRC[i] = TMTimerRCPtr(pThis->pTimerR3[i]);
 }
 
@@ -4322,10 +4311,10 @@
          * Create all hardware streams.
          */
-        rc = ichac97R3StreamCreate(pThis, &pThis->StreamLineIn, AC97SOUNDSOURCE_PI_INDEX);
-        if (RT_SUCCESS(rc))
-        {
-            rc = ichac97R3StreamCreate(pThis, &pThis->StreamMicIn, AC97SOUNDSOURCE_MC_INDEX);
+        for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+        {
+            int rc2 = ichac97R3StreamCreate(pThis, &pThis->aStreams[i], i /* SD# */);
+            AssertRC(rc2);
             if (RT_SUCCESS(rc))
-                rc = ichac97R3StreamCreate(pThis, &pThis->StreamOut, AC97SOUNDSOURCE_PO_INDEX);
+                rc = rc2;
         }
 
@@ -4433,24 +4422,26 @@
     if (RT_SUCCESS(rc))
     {
-        /* Create the emulation timer.
-         *
-         * Note:  Use TMCLOCK_VIRTUAL_SYNC here, as the guest's AC'97 driver
-         *        relies on exact (virtual) DMA timing and uses DMA Position Buffers
-         *        instead of the LPIB registers.
-         */
-        rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ichac97R3Timer, pThis,
-                                    TMTIMER_FLAGS_NO_CRIT_SECT, "AC'97 Timer", &pThis->pTimerR3);
-        AssertRCReturn(rc, rc);
-        pThis->pTimerR0 = TMTimerR0Ptr(pThis->pTimerR3);
-        pThis->pTimerRC = TMTimerRCPtr(pThis->pTimerR3);
-
-        /* Use our own critcal section for the device timer.
-         * That way we can control more fine-grained when to lock what. */
-        rc = TMR3TimerSetCritSect(pThis->pTimerR3, &pThis->CritSect);
-        AssertRCReturn(rc, rc);
-
-        pThis->cTimerTicks = TMTimerGetFreq(pThis->pTimerR3) / pThis->uTimerHz;
-        pThis->uTimerTS    = TMTimerGet(pThis->pTimerR3);
-        LogFunc(("Timer ticks=%RU64 (%RU16 Hz)\n", pThis->cTimerTicks, pThis->uTimerHz));
+        for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+        {
+            /* Create the emulation timer (per stream).
+             *
+             * Note:  Use TMCLOCK_VIRTUAL_SYNC here, as the guest's AC'97 driver
+             *        relies on exact (virtual) DMA timing and uses DMA Position Buffers
+             *        instead of the LPIB registers.
+             */
+            char szTimer[16];
+            RTStrPrintf2(szTimer, sizeof(szTimer), "AC97SD%i", i);
+
+            rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ichac97R3Timer, &pThis->aStreams[i],
+                                        TMTIMER_FLAGS_NO_CRIT_SECT, szTimer, &pThis->pTimerR3[i]);
+            AssertRCReturn(rc, rc);
+            pThis->pTimerR0[i] = TMTimerR0Ptr(pThis->pTimerR3[i]);
+            pThis->pTimerRC[i] = TMTimerRCPtr(pThis->pTimerR3[i]);
+
+            /* Use our own critcal section for the device timer.
+             * That way we can control more fine-grained when to lock what. */
+            rc = TMR3TimerSetCritSect(pThis->pTimerR3[i], &pThis->CritSect);
+            AssertRCReturn(rc, rc);
+        }
     }
 
