Building a CD Player - Part 1

From EDM2
Jump to: navigation, search

Written by Stephane Bessette

Part 1

Part 2

Part 3

Part 4

[NOTE: Here is a link to a zip of the source code for this article. Ed.]


This series of articles will present the steps necessary to build a simple application that plays audio CDs. The interface will present menus for all possible actions, buttons for the most common action, and display some information about the currently inserted CD, such as the number of the current track and some form of time display (eg. the time remaining in the current track). Finally, dialogs will present and request various information.

Here's the list of the actions commonly found in audio CD players that will be implemented: Play, Stop, Pause, Resume, Next and Previous track, Backward and Forward seek, Insert, Eject. In addition, Mute, Unmute, and Volume level will also be implemented.

This program will rely on various controls: menu (unchecked/checked, active/disabled), pushbutton (visible/invisible), graphical button, radio button, circular slider, and static text fields. And dialogs will be used to (1) show information about each audio track on the CD (table of contents), (2) select the connector to use: headphones or stream, and (3) select the volume level.

Finally, when communicating with the CD device, two MCI commands will be used: mciSendCommand() to issue various commands to the CD device, and mciGetErrorString() to convert an error number into an error string. Let's start by looking at these two commands more closely.


All commands to the CD device will be issued with the mciSendCommand API. Its syntax is:

ulrc = mciSendCommand(USHORT usDeviceID,  // (1)
                      USHORT usMessage,   // (2)
                      ULONG ulParam1,     // (3)
                      PVOID pParam2,      // (4)
                      USHORT usUserParm); // (5)

  1. The ID of the device
  2. The message to send
  3. Parameters; MCI_WAIT or MCI_NOTIFY, and other flags
  4. The address of a structure containing additional data
  5. User parameter, probably used when there's more than one device in use

When issuing a MCI command, you have to specify the ID of the device that is to receive the command. In this program, we'll obtain the ID of the CD device and send commands to that device. Quite a few commands can be issued, and the most relevant ones will be covered. These commands can function in two modes. By specifying MCI_WAIT, the execution of the program will be stopped until the command is completely processed. This is conceptually similar to the WinSendMsg() API. And by specifying MCI_NOTIFY, the execution of the program will resume immediately. When the command is completely processed, a message will be posted to the window specified in the hwndCallback member of the structure (4) passed along with the command. This is conceptually similar to the WinPostMsg() API.


So how do we determine which to use? It could be argued that all commands should use the MCI_NOTIFY flag, since this would not tie up OS/2's message queue. However, some commands execute quite rapidly, and would not have a great impact on the overall responsiveness of the system. But there are some exceptions. Open (MCI_OPEN) takes some time before returning, so it is better written with the MCI_NOTIFY flag. And in some cases you absolutely have to use MCI_NOTIFY. For instance, the play (MCI_PLAY) command terminates only when the end of the audio CD is reached; using MCI_WAIT would essentially freeze the system since messages would no longer be processed.

Notification Messages

When the MCI_NOTIFY is specified, the command will send an MM_MCINOTIFY message to the window specified in the hwndCallback member of the structure passed as one of the parameters of the mciSendCommand() function. The first short of mp1 will contain the notification message.

The command completed successfully
Another similar command was issued and will be processed (eg. Two successive play commands)
The command was aborted by a second command (eg. A Play command followed by a Stop command)

And if none of these messages are received, then an error occurred. The second short of mp2 contains the ID of the commands, such as MCI_PLAY.


To determine whether the MCI command was successful or not, the low part of the return code must be examined:

      // The command was successful
  else {
      // The command was not successfull

If the command was not successful, you can obtain a textual description of the error code with mciGetErrorString():

ulrc = mciGetErrorString(ULONG ulError,     // (1)
                         PSZ pszBuffer,     // (2)
                         USHORT usLength);  // (3)

  1. The error code from mciSendCommand()
  2. A string to contain the description
  3. The size of this string

MCI Structures

To finish on the topic of the MCI commands, I'll mention that most commands require additional information to be specified in the structure (4) passed as a parameter of the mciSendCommand(). But for this member to be considered, you often need to specify an additional flag in the ulParam1 (3) parameter. We'll see many examples of this in the sections to come, where we actually issue commands. The next section will open and query the CD device's capacities.


Defines two often used macros (InfoBox and ErrorBox)
Defines two user message
Standard file to support a dialog as the main interface
Handles the interaction from the three dialogs:
CD Player dialog
User commands
Notifications from various MCI commands
Connector selection
Volume level selection
Header for the Class_CDROM class
Definition of three supporting structures:
Definition of inline functions
Implementation of the members of the Class_CDROM class
Header for the Class_CDROM class
Implementation of inline functions
Application definition
Header for the various resources used:
Application resources:
accelerator table
Application resources: