CD


Description:
This provides a few audio type functions via API. Basic play, stop, open, close, next and previous are here. Also, it can compute a unique CD ID based off track length, compatible with the CDDB (this part is based off code available on their webiste)

6/16/1999: Added Get_MCI_ID function that returns an ID compatible with CD Player.

7/30/1999: Added NumTracks, TrackLength and CDLength functions. Renamed Get_MCI_ID to MCI_ID.

11/17/1999: Added PlayRandomTrack
 
Code:
Option Explicit

'--- Functions
'CDLength() As String
'CurrentTrack() As Long
'ID() As String          'CDDB compatible ID
'MCI_ID() As String      'CD Player compatible ID
'NumTracks() As Long
'Position() As String
'Status() As enumStatus
'TrackLength(nTrack As Long) As String

'--- Subroutines
'CloseDrive()
'NextTrack()
'OpenDrive()
'PauseCD()
'PlayCD()
'PlayRandomTrack()
'PreviousTrack()
'SeekToBegining()
'StopCD()

Private Declare Function mciSendString Lib "winmm.dll" Alias _
   "mciSendStringA" (ByVal lpstrCommand As String, ByVal _
   lpstrReturnString As String, ByVal uReturnLength As Long, ByVal _
   hwndCallback As Long) As Long

Private Declare Function mciGetErrorString Lib "winmm.dll" Alias _
   "mciGetErrorStringA" (ByVal dwError As Long, ByVal lpstrBuffer As _
   String, ByVal uLength As Long) As Long


Private Type MCI_OPEN_PARMS
   dwCallback As Long
   wDeviceID As Long
   lpstrDeviceType As String
   lpstrElementName As String
   lpstrAlias As String
End Type

Private Type MCI_SET_PARMS
   dwCallback As Long
   dwTimeFormat As Long
   dwAudio As Long
End Type

Private Type MCI_STATUS_PARMS
   dwCallback As Long
   dwReturn As Long
   dwItem As Long
   dwTrack As Integer
End Type

Private Type MCI_PLAY_PARMS
    dwCallback As Long
    dwFrom As Long
    dwTo As Long
End Type

Private Type MCI_INFO_PARMS
    dwCallback As Long
    lpstrReturn As String
    dwRetSize As Long
End Type

Private Declare Function mciSendCommand Lib "winmm.dll" Alias _
   "mciSendCommandA" _
   (ByVal wDeviceID As Long, ByVal uMessage As Long, ByVal dwParam1 As _
      Long, ByRef dwParam2 As Any) As Long

Private Const MCI_STRING_OFFSET = 512

Public Enum enumStatus
    statusNotReady = MCI_STRING_OFFSET + 12
    statusPause = MCI_STRING_OFFSET + 13
    statusPlay = MCI_STRING_OFFSET + 14
    statusStop = MCI_STRING_OFFSET + 15
    statusOpen = MCI_STRING_OFFSET + 16
    statusRecord = MCI_STRING_OFFSET + 17
    statusSeek = MCI_STRING_OFFSET + 18
End Enum

Private Const MCI_FROM = &H4&
Private Const MCI_STATUS_POSITION = &H2&
Private Const MCI_STATUS_CURRENT_TRACK = &H8&
Private Const MCI_STATUS_MODE = &H4&
Private Const MCI_STOP = &H808
Private Const MCI_PAUSE = &H809
Private Const MCI_SEEK_TO_START = &H100&
Private Const MMSYSERR_NOERROR = 0
Private Const MCI_CLOSE = &H804
Private Const MCI_FORMAT_MILLISECONDS = 0
Private Const MCI_FORMAT_MSF = 2
Private Const MCI_OPEN = &H803
Private Const MCI_OPEN_ELEMENT = &H200&
Private Const MCI_OPEN_SHAREABLE = &H100&
Private Const MCI_OPEN_TYPE = &H2000&
Private Const MCI_SET = &H80D
Private Const MCI_SET_TIME_FORMAT = &H400&
Private Const MCI_SEEK = &H807
Private Const MCI_SEEK_TO_END = &H200&
Private Const MCI_TO = &H8&
Private Const MCI_WAIT = &H2&
Private Const MCI_STATUS_ITEM = &H100&
Private Const MCI_STATUS_LENGTH = &H1&
Private Const MCI_STATUS_NUMBER_OF_TRACKS = &H3&
Private Const MCI_TRACK = &H10&
Private Const MCI_STATUS = &H814
Private Const MCI_SET_DOOR_OPEN = &H100&
Private Const MCI_SET_DOOR_CLOSED = &H200&
Private Const MCI_PLAY = &H806
Private Const MCI_INFO = &H80A
Private Const MCI_INFO_MEDIA_IDENTITY = &H800&
Private mciOpenParms As MCI_OPEN_PARMS
Private mciSetParms As MCI_SET_PARMS
Private mciStatusParms As MCI_STATUS_PARMS
Private mciPlayParms As MCI_PLAY_PARMS
Private mciInfoParms As MCI_INFO_PARMS

Private Type TTrackInfo
   Minutes As Long
   Seconds As Long
   Frames As Long
   FrameOffset As Long
End Type

Private m_Error As Long
Private m_CID As String
Private m_DeviceID As Long
Private m_NTracks As Integer
Private m_Length As Long
Private m_Tracks() As TTrackInfo

Private m_LastPos As Long

Private Sub Class_Initialize()
   m_CID = "(unavailable)"
   m_Error = 0
   m_DeviceID = -1
   m_NTracks = 0
   
   OpenCD
End Sub

Private Sub Class_Terminate()
   If m_DeviceID <> -1 Then
      CloseCD
   End If
End Sub

Public Function Status() As enumStatus

    mciStatusParms.dwItem = MCI_STATUS_MODE

    mciSendCommand m_DeviceID, MCI_STATUS, MCI_WAIT Or MCI_STATUS_ITEM, _
       mciStatusParms

    
    Status = mciStatusParms.dwReturn

End Function

Public Function CurrentTrack() As Long

    mciStatusParms.dwItem = MCI_STATUS_CURRENT_TRACK

    mciSendCommand m_DeviceID, MCI_STATUS, MCI_WAIT Or MCI_STATUS_ITEM, _
       mciStatusParms

    
    CurrentTrack = mciStatusParms.dwReturn

End Function

Public Function Position() As String

    Dim nPos As Long
    Dim nTrackPos As Long
    Dim nIndex As Long
    
    Dim nF As Long
    Dim nS As Long
    Dim nM As Long
    
    mciStatusParms.dwItem = MCI_STATUS_POSITION
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_WAIT Or MCI_STATUS_ITEM, _
       mciStatusParms

    
    m_LastPos = mciStatusParms.dwReturn
    
    nF = (mciStatusParms.dwReturn \ 65536) And &HFF
    nS = (mciStatusParms.dwReturn \ 256) And &HFF
    nM = (mciStatusParms.dwReturn) And &HFF
    
    nPos = (nM * 60 * 75) + (nS * 75) + (nF)
    
    mciStatusParms.dwTrack = CurrentTrack
    mciStatusParms.dwItem = MCI_STATUS_POSITION
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or MCI_TRACK, _
       mciStatusParms

    
    nF = (mciStatusParms.dwReturn \ 65536) And &HFF
    nS = (mciStatusParms.dwReturn \ 256) And &HFF
    nM = (mciStatusParms.dwReturn) And &HFF
    
    nTrackPos = (nM * 60 * 75) + (nS * 75) + (nF)
    
    
    nPos = nPos - nTrackPos
    
    nF = nPos Mod 75
    nPos = nPos \ 75
    nS = nPos Mod 60
    nPos = nPos \ 60
    nM = nPos
    
    
    Position = Format(nM, "0") & ":" & Format(nS, "00")

End Function

Public Function TrackLength(nTrack As Long) As String

    Dim nLength As Long
    Dim nF As Long
    Dim nS As Long
    Dim nM As Long
    
    mciStatusParms.dwItem = MCI_STATUS_LENGTH
    mciStatusParms.dwTrack = nTrack
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or _
      MCI_TRACK, mciStatusParms
    
    nF = (mciStatusParms.dwReturn \ 65536) And &HFF
    nS = (mciStatusParms.dwReturn \ 256) And &HFF
    nM = (mciStatusParms.dwReturn) And &HFF
    
    TrackLength = Format(nM, "0") & ":" & Format(nS, "00")

End Function

Public Function CDLength() As String
    
    Dim dwPos As Long
    Dim dwPosS As Long
    Dim dwPosF As Long
    Dim dwPosM As Long
    Dim dwLenS As Long
    Dim dwLenF As Long
    Dim dwLenM As Long
    Dim nNumTracks As Long
    nNumTracks = NumTracks
    
    mciStatusParms.dwItem = MCI_STATUS_POSITION
    mciStatusParms.dwTrack = nNumTracks
    
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or _
       MCI_TRACK, mciStatusParms
    
    dwPosF = (mciStatusParms.dwReturn \ 65536) And &HFF
    dwPosS = (mciStatusParms.dwReturn \ 256) And &HFF
    dwPosM = (mciStatusParms.dwReturn) And &HFF
    
    mciStatusParms.dwItem = MCI_STATUS_LENGTH
    mciStatusParms.dwTrack = nNumTracks
    
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or _
       MCI_TRACK, mciStatusParms
    
    dwLenM = (mciStatusParms.dwReturn) And &HFF
    dwLenS = (mciStatusParms.dwReturn \ 256) And &HFF
    dwLenF = ((mciStatusParms.dwReturn \ 65536) And &HFF) + 1
    
    dwPos = (dwPosM * 60 * 75) + (dwPosS * 75) + dwPosF + _
            (dwLenM * 60 * 75) + (dwLenS * 75) + dwLenF
    
    dwLenF = dwPos Mod 75
    dwPos = dwPos \ 75
    dwLenS = dwPos Mod 60
    dwPos = dwPos \ 60
    dwLenM = dwPos
    
    CDLength = Format(dwLenM, "0") & ":" & Format(dwLenS, "00")
End Function

Public Sub NextTrack()

    Dim nTracks As Long
    
    mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM, mciStatusParms
    nTracks = mciStatusParms.dwReturn
    
    
    mciStatusParms.dwTrack = CurrentTrack + 1
    
    If mciStatusParms.dwTrack > nTracks Then
        mciStatusParms.dwTrack = nTracks
    End If
    
    mciStatusParms.dwItem = MCI_STATUS_POSITION
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or MCI_TRACK, _
       mciStatusParms

    
    m_LastPos = mciStatusParms.dwReturn
    
    PlayCD

End Sub

Public Sub PreviousTrack()

    mciStatusParms.dwTrack = CurrentTrack - 1
    
    If mciStatusParms.dwTrack = 0 Then
        mciStatusParms.dwTrack = 1
    End If
    
    mciStatusParms.dwItem = MCI_STATUS_POSITION
    mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or MCI_TRACK, _
       mciStatusParms

    
    m_LastPos = mciStatusParms.dwReturn

    PlayCD

End Sub

Public Sub PlayCD()
    
    Dim n As Long
    
    CloseCD
    OpenCD
    
    mciPlayParms.dwFrom = m_LastPos

    If m_LastPos > 0 Then
        mciSendCommand m_DeviceID, MCI_PLAY, MCI_FROM, mciPlayParms
    Else
        mciSendCommand m_DeviceID, MCI_PLAY, 0, mciPlayParms
    End If
        

End Sub

Public Sub PlayRandomTrack()
    
    Dim n As Long
    Dim nTrack As Long

    nTrack = Int(Rnd * m_NTracks)

    CloseCD
    OpenCD

    LoadCDInfo

    Dim nTemp As Long
    Dim nTemp2 As Long
    nTemp = m_Tracks(nTrack).FrameOffset
    nTemp2 = (nTemp Mod 75) * 65536
    nTemp2 = nTemp2 + ((nTemp \ 75) Mod 60) * 256
    nTemp2 = nTemp2 + ((nTemp \ (75 * 60)))
    mciPlayParms.dwFrom = nTemp2
    
    nTemp = m_Tracks(nTrack + 1).Frames + _
            (m_Tracks(nTrack + 1).Seconds * 75) + _
            (m_Tracks(nTrack + 1).Minutes * (75 * 60)) - 100
    nTemp2 = (nTemp Mod 75) * 65536
    nTemp2 = nTemp2 + ((nTemp \ 75) Mod 60) * 256
    nTemp2 = nTemp2 + ((nTemp \ (75 * 60)))
    mciPlayParms.dwTo = nTemp2

    mciSendCommand m_DeviceID, _
        MCI_PLAY, MCI_FROM Or MCI_TO, mciPlayParms

End Sub

Public Sub StopCD()
    
    mciSendCommand m_DeviceID, MCI_STOP, 0, ByVal 0
    
    m_LastPos = 0

End Sub

Public Sub PauseCD()
    
    mciSendCommand m_DeviceID, MCI_PAUSE, 0, ByVal 0

End Sub

Public Sub SeekToBegining()

    mciSendCommand m_DeviceID, MCI_SEEK Or MCI_WAIT, MCI_SEEK_TO_START, _
       ByVal 0
    
    m_LastPos = 0

End Sub

Public Sub OpenDrive()

    mciSendCommand m_DeviceID, MCI_SET, MCI_SET_DOOR_OPEN, ByVal 0

End Sub

Public Sub CloseDrive()

    m_LastPos = 0

    mciSendCommand m_DeviceID, MCI_SET, MCI_SET_DOOR_CLOSED Or MCI_WAIT, _
       ByVal 0

    mciSendCommand m_DeviceID, MCI_SEEK, MCI_SEEK_TO_END, ByVal 0

End Sub

Private Function OpenCD() As Boolean
   Dim scode As Long, wDeviceID As Long
   
   OpenCD = False
   
   mciOpenParms.lpstrDeviceType = "cdaudio"
   
   scode = mciSendCommand(0, MCI_OPEN, (MCI_OPEN_SHAREABLE Or _
      MCI_OPEN_TYPE), mciOpenParms)

   If scode <> MMSYSERR_NOERROR Then
      m_Error = scode
      Exit Function
   End If
   
   m_DeviceID = mciOpenParms.wDeviceID
   
   mciSetParms.dwTimeFormat = MCI_FORMAT_MSF
   scode = mciSendCommand(m_DeviceID, MCI_SET, MCI_SET_TIME_FORMAT, _
      mciSetParms)

   If scode <> MMSYSERR_NOERROR Then
      m_Error = scode
      scode = mciSendCommand(m_DeviceID, MCI_CLOSE, 0, 0)
      Exit Function
   End If

   OpenCD = True
End Function

Private Sub CloseCD()
   
   m_Error = mciSendCommand(m_DeviceID, MCI_CLOSE, 0, 0)
   m_DeviceID = -1

End Sub

Public Function MCI_ID() As String

    mciInfoParms.dwCallback = 0
    mciInfoParms.lpstrReturn = Space(32)
    mciInfoParms.dwRetSize = 32
    
    mciSendCommand m_DeviceID, MCI_INFO, MCI_INFO_MEDIA_IDENTITY, mciInfoParms

    MCI_ID = Hex(mciInfoParms.lpstrReturn)

End Function

Public Function NumTracks() As Long
   
   mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS
   mciSendCommand m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM, _
      mciStatusParms
   
   NumTracks = mciStatusParms.dwReturn

End Function

Private Function LoadCDInfo() As Boolean
   Dim scode As Long
   Dim p1 As Long, dwPosM As Long, dwPosS As Long, dwPosF As Long
   Dim dwLenM As Long, dwLenS As Long, dwLenF As Long, dwPos As Long
   Dim sum As Long, p2 As Long
      
   On Error Resume Next
   
   LoadCDInfo = False
   
   mciSetParms.dwTimeFormat = MCI_FORMAT_MSF
   scode = mciSendCommand(m_DeviceID, MCI_SET, MCI_SET_TIME_FORMAT, _
      mciSetParms)

   ' First get number of tracks
   mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS
   scode = mciSendCommand(m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM, _
      mciStatusParms)

   If scode <> MMSYSERR_NOERROR Then
      m_Error = scode
      Exit Function
   End If
   
   m_NTracks = mciStatusParms.dwReturn
   
   ' Allocate enough room for all the tracks, plus the extra info
   ' saved in the last element
   ReDim m_Tracks(m_NTracks + 1) As TTrackInfo
   
   ' Loop through all the tracks and get starting position
   For p1 = 1 To m_NTracks
      mciStatusParms.dwItem = MCI_STATUS_POSITION
      mciStatusParms.dwTrack = p1
      
      scode = mciSendCommand(m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or _
         MCI_TRACK, mciStatusParms)

      If scode <> MMSYSERR_NOERROR Then
         m_Error = scode
         Exit Function
      End If
      
      ' We right shift the bits here, but I cheat and divide with some
      ' constants instead.
      '
      ' Note that m_Tracks() is zero based!
      '
      m_Tracks(p1 - 1).Frames = (mciStatusParms.dwReturn \ 65536) And &HFF
      m_Tracks(p1 - 1).Seconds = (mciStatusParms.dwReturn \ 256) And &HFF
      m_Tracks(p1 - 1).Minutes = (mciStatusParms.dwReturn) And &HFF
      
      ' I am saving the Frame Offset of the track for easy retrieval in _
      ' the Query string function.
      m_Tracks(p1 - 1).FrameOffset = (m_Tracks(p1 - 1).Minutes * 60 * _
                                    75) + (m_Tracks(p1 - 1).Seconds * _
                                    75) + (m_Tracks(p1 - 1).Frames)
                                   
   Next p1
   
   ' Get total length of CD in seconds
   mciStatusParms.dwItem = MCI_STATUS_LENGTH
   mciStatusParms.dwTrack = m_NTracks
   
   scode = mciSendCommand(m_DeviceID, MCI_STATUS, MCI_STATUS_ITEM Or _
      MCI_TRACK, mciStatusParms)

   If scode <> MMSYSERR_NOERROR Then
      m_Error = scode
      Exit Function
   End If
   
   ' We now have the length of the last track
   dwLenM = (mciStatusParms.dwReturn) And &HFF
   dwLenS = (mciStatusParms.dwReturn \ 256) And &HFF
   dwLenF = ((mciStatusParms.dwReturn \ 65536) And &HFF) + 1
   
   ' Get the starting position of the last track
   dwPosM = m_Tracks(m_NTracks - 1).Minutes
   dwPosS = m_Tracks(m_NTracks - 1).Seconds
   dwPosF = m_Tracks(m_NTracks - 1).Frames
   
   ' Add them together to get the total length of the CD
   dwPos = (dwPosM * 60 * 75) + (dwPosS * 75) + dwPosF + _
           (dwLenM * 60 * 75) + (dwLenS * 75) + dwLenF
           
   
   ' Save it in the last element of m_Tracks() for later retrieval
   m_Tracks(m_NTracks).Frames = dwPos Mod 75
   dwPos = dwPos \ 75
   m_Tracks(m_NTracks).Seconds = dwPos Mod 60
   dwPos = dwPos \ 60
   m_Tracks(m_NTracks).Minutes = dwPos
   
   
   ' Now calculate the length by subtracting the starting position of _
      the first

   ' track and the value calculated above
   m_Length = ((m_Tracks(m_NTracks).Minutes * 60) + _
      (m_Tracks(m_NTracks).Seconds)) - _
      ((m_Tracks(0).Minutes * 60) + (m_Tracks(0).Seconds))
              
   
   ' Start calculating the CDDB Id.
   sum = 0
   For p1 = 0 To m_NTracks - 1
   
      ' Get current track position in seconds
      p2 = m_Tracks(p1).Minutes * 60 + m_Tracks(p1).Seconds
      
      ' Add each digit in P2 together and save in the "sum"
      Do While p2 > 0
         sum = sum + (p2 Mod 10)
         p2 = p2 \ 10
      Loop
   Next p1
   
   ' Now, sum contains the sum of all digits calculated from the
   ' length in seconds of each and every track
   
   ' Finally put the figures together. Once again I cheat to avoid _
      overflow

   ' and other awful things when dealing with VBs Signed longs.
   m_CID = LCase$(LeftZeroPad(Hex$(sum Mod &HFF), 2) & _
      LeftZeroPad(Hex$(m_Length), 4) & LeftZeroPad(Hex$(m_NTracks), _
      2))

   
   LoadCDInfo = True
End Function

Public Function ID() As String
    LoadCDInfo
    ID = m_CID
End Function

Private Function LeftZeroPad(s As String, n As Integer) As String
   If Len(s) < n Then
      LeftZeroPad = String$(n - Len(s), "0") & s
   Else
      LeftZeroPad = s
   End If
End Function
 
Sample Usage:
 
Dim CD As New clsCD

CD.Close
CD.Play