home *** CD-ROM | disk | FTP | other *** search
/ Planet Source Code Jumbo …e CD Visual Basic 1 to 7 / 5_2007-2008.ISO / data / Zips / Stream_MP32029381162006.psc / DirectSoundStream.cls < prev    next >
Text File  |  2006-11-06  |  14KB  |  452 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4.   Persistable = 0  'NotPersistable
  5.   DataBindingBehavior = 0  'vbNone
  6.   DataSourceBehavior  = 0  'vbNone
  7.   MTSTransactionMode  = 0  'NotAnMTSObject
  8. END
  9. Attribute VB_Name = "DirectSoundStream"
  10. Attribute VB_GlobalNameSpace = False
  11. Attribute VB_Creatable = True
  12. Attribute VB_PredeclaredId = False
  13. Attribute VB_Exposed = False
  14. Option Explicit
  15.  
  16. ' PCM audio streaming with DirectSound
  17.  
  18. Private Declare Sub CpyMem Lib "kernel32" Alias "RtlMoveMemory" ( _
  19.     pDst As Any, pSrc As Any, ByVal cBytes As Long _
  20. )
  21.  
  22. Private Declare Sub FillMem Lib "kernel32" Alias "RtlFillMemory" ( _
  23.     pDst As Any, ByVal cBytes As Long, ByVal value As Byte _
  24. )
  25.  
  26. Public Enum PlaybackStatus
  27.     PlaybackStopped
  28.     PlaybackPlaying
  29.     PlaybackPausing
  30. End Enum
  31.  
  32. Private Type AudioBuffer
  33.     pData       As Long
  34.     DataLen     As Long
  35. End Type
  36.  
  37. Private Const TimerIntervalMs   As Long = 5
  38.  
  39. Private WithEvents m_clsTimer   As WinTimer
  40. Attribute m_clsTimer.VB_VarHelpID = -1
  41.  
  42. Private m_clsBufSec             As DirectSoundSecondaryBuffer8
  43. Private m_udeStatus             As PlaybackStatus
  44. Private m_udtFormat             As WAVEFORMATEX
  45. Private m_lngDSBufBytes         As Long
  46. Private m_lngDSBufMs            As Long
  47. Private m_lngWriteCursor        As Long
  48.  
  49. Private m_blnEndOfStream        As Boolean
  50. Private m_lngEOSPosition        As Long
  51. Private m_lngEOSDataLeft        As Long
  52. Private m_lngEOSDataLeftMarker  As Long
  53.  
  54. Private m_lngMSPlayed           As Long
  55. Private m_lngMSPlayedMarker     As Long
  56.  
  57. Private m_lngAudioQueueLengthMs As Long
  58.  
  59. Private m_clsBuffers            As DataQueue
  60. Private m_clsCurBuf             As DataQueueItem
  61.  
  62. Public Event StatusChanged(ByVal status As PlaybackStatus)
  63. Public Event BufferDone()
  64. Public Event NoDataLeft()
  65.  
  66.  
  67. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  68. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  69. ''' Various
  70.  
  71. Public Function BytesFromMs(ByVal ms As Long) As Long
  72.     BytesFromMs = MsToBytes(m_udtFormat, ms)
  73. End Function
  74.  
  75. ' data left to play in milliseconds
  76. Public Property Get AudioBufferQueueLength() As Long
  77.     Dim udtPos  As DSCURSORS
  78.     Dim lngLen  As Long
  79.     
  80.     On Error Resume Next
  81.         m_clsBufSec.GetCurrentPosition udtPos
  82.     On Error GoTo 0
  83.     
  84.     lngLen = RingDistance(udtPos.lPlay, m_lngWriteCursor)
  85.     lngLen = lngLen + m_lngAudioQueueLengthMs
  86.     lngLen = lngLen + BytesToMs(m_udtFormat, m_clsCurBuf.DataLeft)
  87.     
  88.     AudioBufferQueueLength = lngLen
  89. End Property
  90.  
  91. Public Property Get Elapsed() As Long
  92.     Elapsed = m_lngMSPlayed
  93. End Property
  94.  
  95. Public Property Let Elapsed(ByVal value As Long)
  96.     m_lngMSPlayed = value
  97. End Property
  98.  
  99. Public Property Get Volume() As Long
  100.     Volume = m_clsBufSec.GetVolume
  101. End Property
  102.  
  103. Public Property Let Volume(ByVal value As Long)
  104.     If value < -10000 Then value = -10000
  105.     If value > 0 Then value = 0
  106.  
  107.     m_clsBufSec.SetVolume value
  108. End Property
  109.  
  110. Public Property Get Balance() As Long
  111.     Balance = m_clsBufSec.GetPan
  112. End Property
  113.  
  114. Public Property Let Balance(ByVal value As Long)
  115.     If value < -10000 Then value = -10000
  116.     If value > 10000 Then value = 10000
  117.     
  118.     m_clsBufSec.SetPan value
  119. End Property
  120.  
  121.  
  122. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  123. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  124. ''' Playback Handling
  125.  
  126. Public Function PlaybackStop() As Boolean
  127.     If PlaybackStatus = PlaybackStopped Then
  128.         PlaybackStop = True
  129.     Else
  130.         m_clsTimer.Enabled = False
  131.         m_clsBufSec.Stop
  132.         m_clsBufSec.SetCurrentPosition 0
  133.         m_lngWriteCursor = 0
  134.         m_lngMSPlayed = 0
  135.         m_lngMSPlayedMarker = 0
  136.         m_blnEndOfStream = False
  137.         
  138.         m_clsCurBuf.Free
  139.         Set m_clsCurBuf = Nothing
  140.         m_clsBuffers.Clear
  141.         
  142.         PlaybackStop = True
  143.         SetPlaybackStatus PlaybackStopped
  144.     End If
  145. End Function
  146.  
  147. Public Function PlaybackPause() As Boolean
  148.     If PlaybackStatus = PlaybackPausing Then
  149.         PlaybackPause = True
  150.     Else
  151.         m_clsTimer.Enabled = False
  152.         m_clsBufSec.Stop
  153.         PlaybackPause = True
  154.         SetPlaybackStatus PlaybackPausing
  155.     End If
  156. End Function
  157.  
  158. Public Function PlaybackStart() As Boolean
  159.     If PlaybackStatus = PlaybackPlaying Then
  160.         PlaybackStart = True
  161.     Else
  162.         If m_clsBuffers.Count > 0 Then
  163.             If PlaybackStatus = PlaybackStopped Then
  164.                 m_clsBufSec.SetCurrentPosition 0
  165.                 m_lngWriteCursor = 0
  166.                 m_lngMSPlayedMarker = 0
  167.                 FillDSBuffer m_lngDSBufBytes
  168.             End If
  169.  
  170.             m_clsBufSec.Play DSBPLAY_LOOPING
  171.             m_clsTimer.Enabled = True
  172.         ElseIf PlaybackStatus = PlaybackPausing Then
  173.             m_clsBufSec.Play DSBPLAY_LOOPING
  174.             m_clsTimer.Enabled = True
  175.         End If
  176.         
  177.         SetPlaybackStatus PlaybackPlaying
  178.         PlaybackStart = True
  179.     End If
  180. End Function
  181.  
  182. Public Property Get PlaybackStatus() As PlaybackStatus
  183.     PlaybackStatus = m_udeStatus
  184. End Property
  185.  
  186.  
  187. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  188. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  189. ''' Audio Data Buffering
  190.  
  191. Private Sub m_clsTimer_Tick()
  192.     Dim udtCursors      As DSCURSORS
  193.     Dim lngDistance     As Long
  194.     Dim lngRead         As Long
  195.     
  196.     On Error GoTo ErrorHandler
  197.         m_clsBufSec.GetCurrentPosition udtCursors
  198.     On Error GoTo 0
  199.  
  200.     On Error Resume Next
  201.         Err.Clear
  202.         ' 25 days should fit in m_lngMSPlayed
  203.         m_lngMSPlayed = m_lngMSPlayed + BytesToMs(m_udtFormat, RingDistance(m_lngMSPlayedMarker, udtCursors.lPlay))
  204.         If Err.Number = 6 Then m_lngMSPlayed = 0    ' Overflow
  205.     On Error GoTo 0
  206.     
  207.     m_lngMSPlayedMarker = udtCursors.lPlay
  208.  
  209.     If m_blnEndOfStream Then
  210.         m_lngEOSDataLeft = m_lngEOSDataLeft - RingDistance(m_lngEOSDataLeftMarker, udtCursors.lPlay)
  211.         m_lngEOSDataLeftMarker = udtCursors.lPlay
  212.         
  213.         If m_lngEOSDataLeft <= 0 Then
  214.             m_clsTimer.Enabled = False
  215.             m_clsBufSec.Stop
  216.             RaiseEvent NoDataLeft
  217.         End If
  218.     Else
  219.         lngDistance = RingDistance(m_lngWriteCursor, udtCursors.lPlay)
  220.     
  221.         If lngDistance >= MsToBytes(m_udtFormat, 100) Then
  222.             lngRead = FillDSBuffer(lngDistance)
  223.             If lngRead < lngDistance Then
  224.                 If m_clsBuffers.Count = 0 Then
  225.                     m_blnEndOfStream = True
  226.                     m_lngEOSPosition = m_lngWriteCursor
  227.                     m_lngEOSDataLeft = RingDistance(udtCursors.lPlay, m_lngEOSPosition)
  228.                     m_lngEOSDataLeftMarker = udtCursors.lPlay
  229.                 End If
  230.             End If
  231.         End If
  232.     End If
  233.  
  234. ErrorHandler:
  235. End Sub
  236.  
  237. Public Property Get NoDataLeft() As Boolean
  238.     If m_blnEndOfStream Then
  239.         If m_lngEOSDataLeft <= 0 Then
  240.             NoDataLeft = True
  241.         End If
  242.     End If
  243. End Property
  244.  
  245. Public Function AudioBufferAdd(ByVal DataPointer As Long, ByVal DataSize As Long) As Boolean
  246.     Dim clsBufferItem   As New DataQueueItem
  247.  
  248.     clsBufferItem.Initialize DataPointer, DataSize
  249.     m_clsBuffers.Enqueue clsBufferItem
  250.  
  251.     m_lngAudioQueueLengthMs = m_lngAudioQueueLengthMs + BytesToMs(m_udtFormat, DataSize)
  252.  
  253.     ' if playback is running but there are no buffers,
  254.     ' the DirectSound buffer will be stopped.
  255.     ' When there is data, playback will be resumed
  256.     If m_blnEndOfStream Then
  257.         If m_clsBuffers.Count = 1 Then
  258.             m_clsBufSec.SetCurrentPosition 0
  259.             m_lngWriteCursor = 0
  260.             
  261.             ' idea: > PreBuffer MS?
  262.             If FillDSBuffer(m_lngDSBufBytes) > 0 Then
  263.                 m_blnEndOfStream = False
  264.                 
  265.                 On Error GoTo ErrorHandler
  266.                     m_clsBufSec.Play DSBPLAY_LOOPING
  267.                 On Error GoTo 0
  268.                 
  269.                 m_clsTimer.Enabled = True
  270.             End If
  271.             
  272.         End If
  273.     End If
  274.     
  275.     AudioBufferAdd = True
  276.     Exit Function
  277.     
  278. ErrorHandler:
  279.     AudioBufferAdd = False
  280. End Function
  281.  
  282. Public Sub AudioBuffersClear()
  283.     m_clsCurBuf.Free
  284.     m_clsCurBuf = Nothing
  285.     
  286.     m_clsBuffers.Clear
  287. End Sub
  288.  
  289. Public Property Get AudioBufferCount() As Long
  290.     AudioBufferCount = m_clsBuffers.Count
  291. End Property
  292.  
  293. Public Property Get BufferLengthMs() As Long
  294.     BufferLengthMs = m_lngDSBufMs
  295. End Property
  296.  
  297. ' returns bytes filled
  298. Private Function FillDSBuffer(ByVal bytes As Long) As Long
  299.     Dim btData()    As Byte
  300.     Dim lngRead     As Long
  301.  
  302.     lngRead = GetData(bytes, btData)
  303.  
  304.     m_clsBufSec.WriteBuffer m_lngWriteCursor, bytes, btData(0), DSBLOCK_DEFAULT
  305.  
  306.     m_lngWriteCursor = (m_lngWriteCursor + lngRead) Mod m_lngDSBufBytes
  307.     
  308.     FillDSBuffer = lngRead
  309. End Function
  310.  
  311. ' returns bytes read
  312. Private Function GetData(ByVal bytes As Long, ByRef btData() As Byte) As Long
  313.     ReDim btData(bytes - 1) As Byte
  314.     Dim lngRead             As Long
  315.     
  316.     If FormatBitsPerSample = 8 Then
  317.         ' fill complete buffer with silence
  318.         FillMem btData(0), bytes, &H80
  319.     End If
  320.     
  321.     ' check if there are audio buffers to read data from
  322.     If m_clsCurBuf Is Nothing Then
  323.         If m_clsBuffers.Count > 0 Then
  324.             Set m_clsCurBuf = m_clsBuffers.Dequeue()
  325.             If m_clsCurBuf Is Nothing Then
  326.                 GetData = 0
  327.                 Exit Function
  328.             Else
  329.                 m_lngAudioQueueLengthMs = m_lngAudioQueueLengthMs - BytesToMs(m_udtFormat, m_clsCurBuf.DataSize)
  330.             End If
  331.         Else
  332.             m_lngAudioQueueLengthMs = 0
  333.             GetData = 0
  334.             Exit Function
  335.         End If
  336.     End If
  337.  
  338.     Do While lngRead < bytes
  339.         If m_clsCurBuf.EndOfBuffer Then
  340.             m_clsCurBuf.Free
  341.             
  342.             RaiseEvent BufferDone
  343.             
  344.             ' current audio buffer was completly read, get the next one
  345.             If m_clsBuffers.Count > 0 Then
  346.                 Set m_clsCurBuf = m_clsBuffers.Dequeue
  347.                 If m_clsCurBuf Is Nothing Then
  348.                     Exit Do
  349.                 Else
  350.                     m_lngAudioQueueLengthMs = m_lngAudioQueueLengthMs - BytesToMs(m_udtFormat, m_clsCurBuf.DataSize)
  351.                 End If
  352.             Else
  353.                 m_lngAudioQueueLengthMs = 0
  354.                 Exit Do
  355.             End If
  356.         End If
  357.         
  358.         If lngRead + m_clsCurBuf.DataLeft > bytes Then
  359.             ' more data in the current buffer then we actually need
  360.             CpyMem btData(lngRead), ByVal m_clsCurBuf.DataPointer + m_clsCurBuf.DataPosition, bytes - lngRead
  361.             m_clsCurBuf.DataPosition = m_clsCurBuf.DataPosition + (bytes - lngRead)
  362.             lngRead = bytes
  363.         Else
  364.             ' whole audio buffer will fit in btData
  365.             CpyMem btData(lngRead), ByVal m_clsCurBuf.DataPointer + m_clsCurBuf.DataPosition, m_clsCurBuf.DataLeft
  366.             lngRead = lngRead + m_clsCurBuf.DataLeft
  367.             m_clsCurBuf.DataPosition = m_clsCurBuf.DataSize
  368.         End If
  369.     Loop
  370.     
  371.     GetData = lngRead
  372. End Function
  373.  
  374.  
  375. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  376. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  377. ''' Audio Format of Secondary Buffer
  378.  
  379. Public Property Get FormatSamplerate() As Long
  380.     FormatSamplerate = m_udtFormat.lSamplesPerSec
  381. End Property
  382.  
  383. Public Property Get FormatChannels() As Integer
  384.     FormatChannels = m_udtFormat.nChannels
  385. End Property
  386.  
  387. Public Property Get FormatBitsPerSample() As Integer
  388.     FormatBitsPerSample = m_udtFormat.nBitsPerSample
  389. End Property
  390.  
  391.  
  392. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  393. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  394. ''' Helpers
  395.  
  396. Private Function RingDistance(ByVal FromByte As Long, ByVal ToByte As Long) As Long
  397.     If ToByte < FromByte Then
  398.         RingDistance = ToByte + m_lngDSBufBytes - FromByte
  399.     Else
  400.         RingDistance = ToByte - FromByte
  401.     End If
  402. End Function
  403.  
  404. Private Sub SetPlaybackStatus(ByVal stat As PlaybackStatus, Optional ByVal silent As Boolean = False)
  405.     m_udeStatus = stat
  406.     If Not silent Then RaiseEvent StatusChanged(m_udeStatus)
  407. End Sub
  408.  
  409. Private Function MsToBytes(fmt As WAVEFORMATEX, ByVal ms As Long) As Long
  410.     MsToBytes = ms / 1000 * fmt.lAvgBytesPerSec
  411. End Function
  412.  
  413. Private Function BytesToMs(fmt As WAVEFORMATEX, ByVal bytes As Long) As Long
  414.     BytesToMs = bytes / fmt.lAvgBytesPerSec * 1000
  415. End Function
  416.  
  417.  
  418. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  419. ''''''''''''''''''''''''''''''''''''''''''''''''''''
  420. ''' Constructor(s)
  421.  
  422. Private Sub Class_Initialize()
  423.     Set m_clsBuffers = New DataQueue
  424.     Set m_clsTimer = New WinTimer
  425.  
  426.     m_clsTimer.Interval = TimerIntervalMs
  427.     m_udeStatus = PlaybackStopped
  428.     m_lngWriteCursor = 0
  429. End Sub
  430.  
  431. Private Sub Class_Terminate()
  432.     PlaybackStop
  433.     
  434.     Set m_clsCurBuf = Nothing
  435.     Set m_clsBuffers = Nothing
  436.     Set m_clsBufSec = Nothing
  437.     Set m_clsTimer = Nothing
  438. End Sub
  439.  
  440. ' Called by the class "DirectSound"
  441. ' Has to be the last member of this interface!
  442. Private Sub CustomConstructor(ByVal BufferSize As Long, dsound As DirectSoundSecondaryBuffer8)
  443.     Set m_clsBufSec = dsound
  444.     
  445.     If Not m_clsBufSec Is Nothing Then
  446.         m_clsBufSec.GetFormat m_udtFormat
  447.         
  448.         m_lngDSBufBytes = BufferSize
  449.         m_lngDSBufMs = BytesToMs(m_udtFormat, BufferSize)
  450.     End If
  451. End Sub
  452.