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

| Introduction Features Why did we use so many custom classes? The CDAudio Class The Ini Class The NoDoubleClick Class The WhileMouseDown Class The VolumeControl Class Problems and Solutions The marquee vs the getTextExtend() Method The TrackLength() Method vs Enhanced CDs How to Create Musical Loops Using the CDAudio Class during Multimedia Presentations Conclusion

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:

- the first button is used to jump to the beginning of the current track or to the beginning of the previous one.
- the second does fast rewind.
- the third one is a toggle button (pause/play).
- the fourth is used to stop playing: it moves the disk pointer to the beginning of the current track (rather than the beginning of the disk).
- the fifth is the fast forward button.
- the sixth is used to jump to the next track.
- the seventh is a toggle button (to start a musical loop/to mark the end of that loop/to kill the loop).
- the eighth is also a toggle button (to eject the compact disk/to close the drawer of the CD drive).

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:

- Marc Van den Berghen’s new VolumeControl class,
- Keith Chuvala, Bowen Moursund and Kenneth Chan’s Ini class,
- Ronnie MacGregor’s NoDoubleClick class,
- Romain Strieff and Ronnie MacGregor’s WhileMouseDownButton class,
- and Gary White’s CDAudio class.

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:

- creates an array of all the CD drives installed on the computer.
- selects the first element of that array (since the main shortcomming of `WinMM.dll` is that it only works with the CD drive with the lowest drive letter assignment.)
- reads the serial number of the compact disk in that CD drive.
- parses the disk serial number into its equivalent Ini Section number.
- uses the Ini class to read `CDPlayer.ini`.
- if the section is found, displays the current track number, the artist and the title of the album as the form’s text, displays the current track name on the marquee of the application. And finally stores the entries related to the track names into an array that will be used to update the marquee when the application will starts reading another track.

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:

| `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.