The dBASE Audio CD Player
by Jean-Pierre Martel, editor of the dBulletin
 
 
   



Introduction

With Win95 and Win98, Microsoft offered a CD Player application whose engine was WinMM.dll.  That dll is not available after a clean installation of WinME and WinXP, but is still available when the older versions of Windows are upgraded to WinMe/XP.

A document available from the Microsoft web site explains the bugs that might be encountered when the old CD player application is used under WinMe/XP.

Fortunately, the original WinMM.dll can be used by dBASE to create a pure dBL application that doesn’t exhibit those bugs.  That’s what Gary White did in May 2001. All the functionalities of his application were encapsulated in an extraordinary CDAudio class that was largely overlooked at the time his message was posted in a dBASE newsgroup.

Features

The Audio CD Player has the following characteristics:

  1. the title bar displays the track number, the name of the artist and the title of the album.
  2. the marquee displays  the status of the application or, like in the image above,  the name of the track that is played (that name is moved from right to left).
  3. the main display window gives the following information:  the number of the track that is played, the time played from that track, the time remaining to be played, the length of the current track, then, on the bottom line, the number of tracks on that disk and the length of the Audio CD.
  4. a combobox to jump to the selected track.
  5. eight navigation buttons:
  6. a button to quit the application
  7. a slider to control the volume of the music.
  8. a checkbox to indicate if the music should be played continuously or if the application should stop when all the tracks have been played.
Contrary to the impression it gives, this CD player is quite basic.  It contains all the code that most beginners would have trouble with.  The missing features should be relatively easy to implement:  building a menu, creating keyboard shortcuts, adding a button allowing the user to write the track information in an ini file, etc.

Why did we used so many custom classes?

When we prepared this article, we revisited Gary’s custom class (adding a few functions here and there) and we created a new application that is a showcase for many dBL technologies:

All this, wrapped in a user interface that reminds me of the stereo receiver I had thirty years ago.

All these custom classes were needed to expand the possibilities offered by dBASE stock classes.  Together, they illustrate that dBASE is more than a database management software:  it is a development platform.

The CDAudio Class

The CDAudio class is the engine of our application.  It is subclassed from the Paintbox object in order to be drag’n dropped from the Component Palette onto a form in the Form Designer.  Nearly all the stock properties, events and functions of the parent Paintbox object are protected and thus can’t be inspected.  On the other hand, all the custom properties and methods of the CD Audio class are available through the Inspector:
 
  Custom properties Description
  Open Set to false by default.  Becomes true when the CDAudio object is initialized successfully.
  LoopStartTime Stores the beginning of a musical loop.
  LoopEndTime Stores the end of a musical loop.
Custom methods Description
  Close_Door Closes the CD drive drawer.
  CurrentTrack Returns a two character string that is the number of the current track.
  DiskTime Returns the total playing time of the compact disk (as a string).
  Eject Opens the CD drive drawer.
  Fast_Forward By default, it skips 5 seconds of music.  The method is called every 0.1 second when one instance of the WhileMouseDown class is pushed and held down.
  Fast_Rewind By default, it rewinds 5 seconds of music.  The method is called every 0.1 second when the other instance of the WhileMouseDown class is pushed and held down.
  Init Initializes the CDAudio object and sets its custom properties.
  IsDiskInserted Returns "true" (a string) when a compact disk is present in the CD drive drawer.
  Jump Jumps to the track number passed as a parameter.
  Mode Returns the playing mode ("not ready", "open", "playing", or "stopped").
  Pause Pauses the CD playing.
  Play Plays the track number (passed as a parameter).
  PositionInTrack Returns the time elapsed from the begining of the current track (as a string).
  Release Clears the CDAudio object from memory (?)
  SendCommand Sends a command string to an MCI device
  SetTimeMilliseconds Sets the CDAudio time format to milliseconds
  SetTimeMinSec Sets the CDAudio time format to minutes:seconds
  Stop Stops playing and moves the CDAudio "cursor" to the begining of the current track.
  TotalTracks Returns the number of tracks on the compact disk (as a string).
  TrackLength Returns the length (as a string) of a track number (passed as a parameter).
     

For example, once the CDAudio object is instantiated and initialized, a single line of code is needed to jump to the seventh track of the compact disk:
 
 
form.CDAudio1.Jump(7)
   

Warning:  Don’t count on the release() method of this class.  It can’t prevent instances of dBASE runtime from remaining in memory after executables using this class have been closed.  I had this problem until I added an explicit release object form.CDAudio1 in the canClose() event of my application.

The Ini Class
 

     
     
  The CD Player application installed by Win95 and Win98 allows the user to enter manually the album, artist, and song title about a compact disk and to select the tracks he wants to hear.  This data is stored in the ini file CDPlayer.ini, located in the Windows folder.  This permits Windows CD Player to display the information the next time the audio CD is played.  All ini files are limited to 64 Kb.  Once this limit is reached, any additional data is deleted in a First in/First out manner.

Users of the Deluxe CD Player (under Win2K) or the Windows Media Player (under WinMe/WinXP) don’t have to store the data about an audio CD since that information is accessed over the Internet.
 

 

 
     

As reviewed by David Stone in our previous issue, the Ini class is used to read the section about a compact disk written in CDPlayer.ini.  In that ini file, the entries for a given Audio CD are as followed:
 
 
[B79DC79]
artist=Peter Gabriel
title=Up
numtracks=10
0=Darkness
1=Growing Up
2=Sky Blue
3=No Way Out
4=I Grieve
5=The Barry Williams Show
6=My Head Sounds Like That
7=More Than This
8=Signal To Noise
9=The Drop
   

The name of the section, written between square brackets, is related to the CD number.  Each CD has its own number whether you bought it or you burned it yourself.  In this case, the CD number is “6B79-DC79”:  if two characters are taken out — its first number and its dash — this gives us the name of the section, [B79DC79].

In order to accomplish that task, our application:

Our application reads the data stored in CDPlayer.ini, but doesn’t offer any means to edit, delete, or append data in that ini file.  Since storing data in an ini file is so easy to do, why was such a feature not implemented? Simply because you, as a dBL developer, can do it yourself.

Included with our application is a resource dll called CDAudio.dll.   Among its resources is write, the bitmap you might want to use on a pushbutton giving access to the ini file of your choice (or to a dBASE table if you want to avoid the 64 Kb limitation of ini files) in which you would store the data related to your compact disks.


The NoDoubleClick Class

Our application has a button to go back to the previous track and another button to jump to the next track.  Unfortunately, when these buttons are clicked many times in a row over a short period of time, some of these clicks will be interpreted as double-clicks by the operating system.  For example, under Win98, in a brief test I did, these were some of the different types of response I had:
 

No. of click(s)
Event(s) fired
Effectiveness
 
1
onClick
100 %
 
2(a)
onClick, onClick
100 %
 
2(b)
onClick, onDoubleClick
66 %
 
3(a)
onClick, onDoubleClick, onClick
66 %
 
3(b)
onClick, onClick, onDoubleClick
66 %
 
4(a)
onClick, onDoubleClick, onClick, onClick
75 %
 
4(b)
onClick, onClick, onDoubleClick, onClick
75 %
 
4(c)
onClick, onClick, onClick, onDoubleClick
75 %
 
4(d)
onClick, onDoubleClick, onClick,  onDoubleClick
50 %
 
5(a)
onClick, onClick, onClick, onClick, onClick
100 %
 
5(b)
onClick, onDoubleClick, onClick, onClick, onClick
80 %
 
5(c)
onClick, onClick, onDoubleClick, onClick, onClick
80 %
 
5(d) 
onClick, onClick, onClick, onDoubleClick, onClick
80 %
 
5(e)
onClick, onClick, onClick, onClick, onDoubleClick
80 %
 
5(f)
onClick, onClick, onDoubleClick, onClick, onDoubleClick
60 %
       

Consequently, the number of tracks that will be skipped is unpredictable when the onClick() event is used.  The same thing is also true when the onLeftMouseDown() is used.  On the contrary, the onLeftMouseUp() event was more reliable: the onDoubleClick() event is still fired unpredictably but at least, the number of onLeftMouseUp() events is always identical to the number times the button is pushed.

In our application, we could have taken advantage of the more reliable onLeftMouseUp() event.  However, since our tests were done only under Win98, we have decided to use something even more reliable: an instance of an object totally unable to fire onDoubleClick() events, Ronnie MacGregor’s NoDoubleClick class.

That class contains two type of objects:  a NoDoubleClickButton and a NoDoubleClickForm. The latter was added to the custom class because the NoDoubleClickButton needs its speedbar property to be set to false.  When it has to be set to true (as it is the case with the CD Audio Player which uses flat buttons), we had to use the NoDoubleClick custom form, which provides the functionality of the NoDoubleClickButton to any control on the form.

Among other purposes, this class can be used to navigate reliably in a rowset, to skip slides in a desktop presentation, or to skip tracks in an audio player.

The WhileMouseDown Class

When the left mouse button is pushed and held down over an object, the latter’s onLeftMouseDown() event is fired only once.  If we want an action to be repeated while a button is pushed, we could create a loop in its onLeftMouseDown() event and break that loop when the mouse is released.  However, that solution is hazardous:  if the mouse is dragged outside the surface of the button and then released over another object, we could end up with an infinite loop.

For dBL developers, the real solution is to use a free component designed by Romain Strieff and Ronnie MacGregor:  their WhileMouseDown class.  This object does use a loop.  However, in that loop an API call is made to check if the mouse button is still pushed over the button:  if it is not, the loop is broken from within the onLeftMouseDown() code, thus preventing the hazardous situation we’ve just spoken about.

Being a visual component, this class can be made available from the Component palette, and then can be drag’n dropped onto a form under the Form designer.

The Volume Control Class

The Volume control class was developed by Marc Van den Berghen specially for the dBASE Audio CD Player.  That class doesn’t affect the level of the sound when music is played or listened to through earphones connected directly to the CD player drive.  However, when the sound is played through the speakers of a portable computer or obtained from a sound card or from the chip of a motherboard, this class allows the sound level to be changed with any pointing device or to be controlled programmatically.  That brand new class is handy for multimedia applications, desktop presentations, tutorials, etc., where the sound level could have to be changed.

Subclassed from the Slider stock class, the VolumeControl class can be made available in the Component palette and be drag’n dropped onto a form in the Form designer.

Problems and Solutions

The marquee vs the getTextExtend() Method

The marquee is made of three main objects:

  1. a textLabel object that displays the status of the CD drive or the title of the track.
  2. a container that crops the text displayed by the textLabel object.
  3. a timer that moves the text to the left, or makes it flash.
Making the text flash requires in a single line of dBL code:
 
 
form.Container1.T_Status.visible = mod(int(seconds()),2)=0
   

That line means that the text label object form.Container1.T_Status will be visible only when the number of seconds returned by the system clock is a multiple of 2.

Making it flow continuously needs a few more lines.  When the form opens, it creates a timer that fires a form.updateDisplay() function six times a second (Note:  that frequency seems to be high but we needed it to give precision to the musical loops created with this application.)  In return, that function calls the sub-function form.Update_PlayingState() when the Audio CD Player is in playing state.
 
 
01   Function Update_PlayingState  // here, in a simplified version
02      local marqueeText
03      marqueeText = form.Container1.T_Status 
04      marqueeText.left -= 2
05      if marqueeText.left <= - (form.TrackTitlePixelLength + (15 * 3))
06         marqueeText.left = - 2
07      endif
   

Line 03 creates a nickname for the form.Container1.T_Status textLabel object.  Line 04 moves the text two pixels to the left each time that function is called (six times a second).  Near the right end of line 05, 15 is the number of spaces (measuring 3 pixels each at the font size we used) between two instances of the track name displayed in the marquee.  Since the left property of marqueeText is measured from the start of its container object, line 05 says that if the left property of marqueeText is equal or higher than the track name plus 15 spaces (in other words, if it has completely disappeared from view), line 06 brings it back just 2 pixels left of the border of the container, giving the illusion that the next instance of the track name has continued moving to the left.  The net result is that names of the current track seem to flow endlessly on the marquee.

In order to do that, our code needs to know the size of the current track name.  If we use the len() command or the length property of the String class, that will give us the number of characters in that string, not its size in pixels.  To get that size, we have to use the getTextExtent() method.

This method of the Text and TextLabel classes works best with the old Char metric.  However, when the form metric is set to pixel, it works properly for Text objects but not for TextLabel objects; in the latter case, the value is expressed in Char, even when the form metric is set to pixel.

Since the text in the marquee is displayed by a TextLabel object, the workaround (discovered by Marko Mihorko) is to copy the text to a dummy Text object, and to use the method to measure its own text.
 
 
local oT, Size_in_Pixels
oT      = form.T_MeasuringDummy     // a Text object
oTLabel = form.Container1.T_Status  // a TextLabel object
Size_in_Pixels = oT.getTextExtent(oT.text)
* Size_in_Pixels = oT.getTextExtent(oTLabel.text)      // unreliable
* Size_in_Pixels = oTLabel.getTextExtent(oT.text)      // wrong
* Size_in_Pixels = oTLabel.getTextExtent(oTLabel.text) // wrong
   

The TrackLength() Method vs Enhanced CDs

When TrackLength() is applied to an enhanced compact disk, that method of the CDAudio class doesn’t return the length of the current track (as it should) but the total time remaining to be played at the beginning of that track. In the case of Peter Gabriel’s Up CD,  the returned values are:
 

 
Track No.
Returned Track Length
True Track Length
 
 1
66:45
06:51
 
 2
59:54
07:33
 
 3
52:21
06:38
 
 4
45:43
07:54
 
 5
37:49
07:25
 
 6
30:24
07:16
 
 7
23:08
06:29
 
 8
16:39
06:02
 
 9
10:37
07:36
 
10
03:01
02:59
       

When a new disk is read, the Audio CD Player compares the length of the first track to the length of that compact disk: if they are identical, that means an enhanced CD is about to be played.  So the application creates an array in which it stores the difference between the returned value for a track and the one returned for the next track:  that difference becomes the true track length except for the last track.  In the latter case, a little less than two seconds has to be substracted:  the CD never reaches the end indicated by the TrackLength() method.

How to Create Musical Loops?

I bought my first CD player in November 1983, six months after the first CD players appeared on the Canadian market. With that machine, I could create musical loops. That feature was handy to learn the exact pronounciation of a word or a sentence spoken in a Foreign language, or to learn a guitar solo from an old Jimmy Hendrix compact disk. Unfortunately, that first machine broke down a decade later and I couldn’t find the same feature among the affordable CD players since then.

When I worked on the CDAudio class, I realized that this feature could be added in just four easy steps:

  1. store the position of the CD audio “cursor” in a custom property (loopStartTime) when the loop is started and change the bitmap of the pushbutton from Loop_start to Loop_end.
  2. store the position of the CD audio “cursor” in another custom property (loopEndTime) when the loop is ended and change the bitmap of the pushbutton from Loop_end to Loop_stop.
  3. put back the the upbitmap property of the pushbutton to Loop_start when the loop is killed.
  4. add the following code to the application:

  5.  

    if not form.P_Loop.upBitmap = "RESOURCE Loop_start CDAudio.dll"
       form.CDAudio1.setTimeMilliseconds()
       if form.CDAudio1.positionInTrack() >= form.CDAudio1.loopEndTime
          form.CDAudio1.play(form.CDAudio1.loopStartTime)
       endif
       form.CDAudio1.SetTimeMinSec()
    endif
       
Using the CDAudio Class during Multimedia Presentations

If you have to create a multimedia presentation that requires perfect synchronization between audio tracks burned on a compact disk and some images  projected in front of an audience (for example, if an HTML page has to be shown for only a precisely specified period of time — from 01:27 to 02:35 seconds of a given track), the code of the dBASE CD Player can be adapted to fulfill that need.  However this will not be possible if the images have to be read from the compact disk while the music is played:  as soon as the Audio CD “cursor” is moved to read the image file, the music stops.  That explains why you can’t read the lyrics of an enhanced CD and listen to the music at the same time.

The solution is to copy all the images needed during the presentation to the hard disk before the music starts (and erase them when the presentation ends).  Maybe some of you will help the Majors to include wiser applications with their enhanced CDs.  You just have to adapt the code of the dBASE Audio CD Player to transform it into a Control center dealing with sound tracks and HTML pages (Note:  How to deal with the latter was the subject of our article about the MS Web Browser control, featured in our previous issue.)

Conclusion

The dBASE Audio CD Player was made for my own pleasure.  It is an example which illustrates that dBASE is not strictly database management software:  it’s a development platform especially well adapted to database management.

If you want to use that application just for fun or for business, go ahead.  Its different components can be used freely within any dBL application:  no permission is needed.

To download the included application,  click here
(it is a 93 Kb zipped  file)


The author would like to thank Robert W. Newman, Fabian Cevallos and David L. Stone, his proofreaders, for the improvements they brought to this text.