Sounds Good to Me

In this article, Doug Steele talks about how to play MP3 files from your Access application

How can I play an MP3 in my application?

I’ve read that if you have the latest version of Windows Media Player on your system you can play MP3 files simply by adding the Windows Media Player component onto your form and adding this one line of code:

MediaPlayer1.Open "c:\mp3s\MySong.mp3"

Well, I hate to admit it, but I can’t get it to work. However, I’m not sure that this approach is the most robust solution anyway, as you’ll doubtlessly run into versioning problems as you move your MDB from machine to machine (see my December 2004 column, “Access Answers: But it Worked Yesterday!“) . Instead, let me recommend the Media Control Interface (MCI) that’s part of Windows. I found that the MCI is relatively simple to use for playing MP3s.

The main API that you need is the mciSendString function, contained in winmm.dll, that sends a command string to an MCI device. The declaration that you’ll need to add to your code to use this function is:

Private Declare Function mciSendString _

  Lib "winmm.dll" _

  Alias "mciSendStringA" ( _

  ByVal lpstrCommand As String, _

  ByVal lpstrReturn128String As String, _

  ByVal uReturnLength As Long, _

  ByVal hwndCallback As Long _

) As Long

The “trick,” of course, is that you need to know what to pass as the parameters to that function!

Another API you’ll probably want is the mciGetErrorString function, also contained in winmm.dll, that retrieves a string that describes the specified MCI error code. Its declaration is:

Private Declare Function mciGetErrorString _

  Lib "winmm.dll" _

  Alias "mciGetErrorStringA" ( _

  ByVal dwError As Long, _

  ByVal lpstrBuffer As String, _

  ByVal uLength As Long _

) As Long

I’m not going to go into a long description of how to interact with MCI. I’ll cut straight to the chase and say that to play an MP3, you use can use code like this:

Dim lngReturn As Long

Dim strCommand As String * 255

Dim strReturn128 As String * 128

strCommand = "open c:\mp3s\MySong.mp3" & _

  " Type MPEGVideo Alias MyMP3"

lngReturn = mciSendString(strCommand, 0&, 0&, 0&)

If lngReturn = 0 Then  ' success

  mciSendString "play MyMP3", 0&, 0&, 0&

Else

  mciGetErrorString lngReturn, strReturn128, 128

  MsgBox strReturn128, vbOKOnly + vbCritical

End If

There’s a slight problem, though: This code will only work for file names that are in the old DOS 8.3 format. The sample file name I used in the code (c:\mp3s\MySong.mp3) is in that format, so it would work without an issue. However, your MP3 is probably going to be in a location like C:\Documents and Settings\DJSteele\My Documents\My Music\The Beatles\The Beatles 1\14 Paperback Writer.mp3, and that name won’t work, since it’s a Long File Name.

Fortunately, there’s an easy way to convert a file to its short file name equivalent. The API function that will do the conversion for you is the GetShortPathName function (this function is in kernel32.dll). Its declaration is shown here:

Private Declare Function GetShortPathName _

  Lib "kernel32" _

  Alias "GetShortPathNameA" ( _

  ByVal lpszLongPath As String, _

  ByVal lpszShortPath As String, _

  ByVal cchBuffer As Long _

) As Long

You use the function like this:

Dim str255 As String * 255

Dim strShortPath As String

  lngReturn = _

    GetShortPathName(FileName, str255, 255)

  strShortPath = Left$(strReturn255, lngReturn)

Putting it all together, the complete code to play your MP3 is:

Public Function PlayMP3(FileName As String) _

  As String

Dim lngReturn As Long

Dim strCommand As String * 255

Dim str128 As String * 128

Dim str255 As String * 255

Dim strShortPath As String

' Make sure the file actually exists

If Len(Dir(FileName)) > 0 Then

  lngReturn = _

    GetShortPathName(FileName, str255, 255)

  strShortPath = Left$(str255, lngReturn)

  strCommand = "open " & strShortPath & _

    " Type MPEGVideo Alias MyMP3"

  lngReturn = _

    mciSendString(strCommand, 0&, 0&, 0&)

  If lngReturn = 0 Then  ' success

    PlayMP3 = vbNullString

    mciSendString "play MyMP3", 0&, 0&, 0&

  Else

    Call mciGetErrorString(lngReturn, _

      str128, 128)

    PlayMP3 = str128

    MsgBox str128, vbOKOnly + vbCritical

  End If

Else

  PlayMP3 = "Input File does not exist."

End If

End Function

One thing I need to point out: Look carefully at the command string I use to call the API. It’s “open c:\mp3s\MySong.mp3 Type MPEGVideo Alias MyMP3”. Hopefully most of that string is fairly self-explanatory. The string starts with the verb open, followed by the path to the MP3, and then the details of the file type (MPEGVideo). What follows that, though, is an alternate name for the given device. When specified, that name must be used in subsequent commands. As you can see, I chose MyMP3 to use as the alias (you can use whatever name you want). However, you’ll see my name in the rest of the code that I’m going to show you.

This call pauses the playing of the MP3:

  Call mciSendString("pause MyMP3", 0&, 0&, 0&)

This call resumes playing after a pause:

  Call mciSendString("resume MyMP3", 0&, 0&, 0&)

Finally, when you’re finished playing, you not only stop the MP3, but you close the device with these calls:

  Call mciSendString("stop MyMP3", 0&, 0&, 0&)

  Call mciSendString("close MyMP3", 0&, 0&, 0&)

I could check the return code from the calls to mciSendString (like I did when I used it to open the MP3, so that I knew it was okay to actually play the file), but it’s not strictly necessary.

The downloadable database that accompanies this article has working code that you can look at. If you want to learn more about MCI, start at the Microsoft site at
http://msdn.microsoft.com/en-us/library/ms709461(VS.85).aspx

Is there any way to retrieve the information that’s stored about each song in MP3 format?

Each MP3 is supposed to have 128 bytes of information at the end of the file. This tag is the so-called ID3v1 (or ID3v1.1) tag. There’s also a newer ID3v2 tag at the beginning of the file, but I’m going to ignore that in this discussion. If you’re interested in learning more about both the ID3v1.x and ID3v2.x formats, you can read about them at www.id3.org/id3v1.html.

The 128-byte ID3v1.1 tag has the layout shown in Table 1. (The original tag layout, ID3v1, didn’t include the Track number. Rather, the Comment field could be up to 30 characters.) Doubtlessly the more astute of you have noticed that the size of the fields in the table adds up to 125, not 128. This is because the first three bytes of the ID3 tag are always “TAG”. As well, you might be wondering about the usefulness of a single byte for Genre. It actually corresponds to a value in a predefined list of Genres. The original list of Genres contained 80 values, from 0 to 79. I’ve included a copy as a table in the downloadable database associated with this month’s column, but from what I understand, this list is considered to be obsolete (in other words, the Genre tag isn’t really relevant anymore).

Table 1. MP3 ID3v1.1 tag layout.

Content Size
Song title 30 characters
Artist 30 characters
Album 30 characters
Year 4 characters
Comment 28 characters
Null 1 byte (always hex 0)
Track 1 byte
Genre 1 byte

To get at the ID3V1.x tag, you need to open the MP3 file as a Binary file for read, and then read the last 128 bytes. To open a file for read (or write), you use the VBA Open statement. At this point, let me get on my soapbox and mention something that irks me when I see it in code samples. When using the Open statement, never hard-code the file number. You should always use the FreeFile function to ensure that you use a file number that isn’t in use as I do here:

Dim intFile As Integer

Dim strBuffer As String*128

  intFile = FreeFile

  Open FullPathToFile For Binary _

    Access Read As intFile

  Get #intFile, FileLen(FullPathToFile) - 127, _

    strBuffer

The FileLen function will tell you the total number of bytes in the file, making it easy to only read the last 128 bytes of the file.

Now that you’ve read the last 128 bytes into your buffer, it’s a relatively straightforward process to determine each of the individual components within the tag. The first thing to do is make sure that it’s a valid tag (remember that I said that the last 128 bytes are supposed to be the tag). If the first three characters of the string (that is, Left$(strBuffer, 3)) aren’t the characters “TAG”, then there’s a problem.

Once you know that you have a valid ID3V1 tag, you can use the Mid function to dissect it. The standard says that any byte that isn’t being used should be set to the Null character (Chr(0)), and trying to display strings with Null characters in them can sometimes cause odd effects. Therefore, I recommend using my TrimNull function to throw away everything in the substring from the first Null characters on. My TrimNull function looks like this:

Function TrimNull(InputString As String) _

  As String

Dim intNull As Integer

  intNull = InStr(InputString, Chr(0))

  If intNull > 0 Then

    TrimNull = Left$(InputString, intNull - 1)

  Else

    TrimNull = InputString

  End If

End Function

Armed with this function, pulling the data out of the tag uses this code:

strSongname = TrimNull(Mid$(strBuffer, 4, 30))

strArtist = TrimNull(Mid$(strBuffer, 34, 30))

strAlbumTitle = TrimNull(Mid$(strBuffer, 64, 30))

strAlbumYear = TrimNull(Mid$(strBuffer, 94, 4))

strComment = TrimNull(Mid$(strBuffer, 98, 28))

intTrack = Asc(Mid$(strBuffer, 127, 1))

intGenre = Asc(Right$(strBuffer, 1))

 

Your download file is called Steele_Stupid_date_tricks_mp3.accdb

About Doug Steele

Doug Steele has worked with databases-both mainframe and PC-for many years with a major international oil company. In a previous life, he taught introductory computer programming at the University of Waterloo. Microsoft has recognized him as an Access MVP for his contributions to the user community over the years.

Doug is the author of Access Solutions with Arvin Meyer.

This entry was posted in VBA. Bookmark the permalink.

One Response to "Sounds Good to Me"

Leave a Reply

Your email address will not be published. Required fields are marked *


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.