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:
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 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 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.
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:
|
Event(s) fired |
|
|
|
onClick |
|
|
|
onClick, onClick |
|
|
|
onClick, onDoubleClick |
|
|
|
onClick, onDoubleClick, onClick |
|
|
|
onClick, onClick, onDoubleClick |
|
|
|
onClick, onDoubleClick, onClick, onClick |
|
|
|
onClick, onClick, onDoubleClick, onClick |
|
|
|
onClick, onClick, onClick, onDoubleClick |
|
|
|
onClick, onDoubleClick, onClick, onDoubleClick |
|
|
|
onClick, onClick, onClick, onClick, onClick |
|
|
|
onClick, onDoubleClick, onClick, onClick, onClick |
|
|
|
onClick, onClick, onDoubleClick, onClick, onClick |
|
|
|
onClick, onClick, onClick, onDoubleClick, onClick |
|
|
|
onClick, onClick, onClick, onClick, onDoubleClick |
|
|
|
onClick, onClick, onDoubleClick, onClick, onDoubleClick |
|
|
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:
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 |
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
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 |
|
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)