Building a CD Player - Part 3

From EDM2
Jump to: navigation, search

Written by Stephane Bessette

Part 1

Part 2

Part 3

Part 4

Options

We'll now take a look at the various options that can be set before moving on to the CD commands.

Connector

The CD device supports two connectors: headphones and stream. The headphones connector refers to the headphone jack that is usually found on the front panel of the CD-ROM. And the stream connector refers to the stream of data that is moving from the CD device to the sound card. This second option is not available to all CD devices. To select the stream connector:

  mciConnectorParameters.ulConnectorType =
                        MCI_CD_STREAM_CONNECTOR;

  ulrc = mciSendCommand(usDeviceID,
                        MCI_CONNECTOR,
                        MCI_ENABLE_CONNECTOR |
                        MCI_CONNECTOR_TYPE |
                        MCI_WAIT,
                        &mciConnectorParameters,
                        0);

And to select the headphones connector, you'd set the ulConnectorType member of the MCI_CONNECTOR_PARMS structure to MCI_HEADPHONES_CONNECTOR.

When the connector is changed, we also have to query the connected-to device in order to send volume commands to the appropriate device (the one that is now in charge of setting the volume level):

  mciConnectionParameters.ulConnectorType =
                        MCI_CD_STREAM_CONNECTOR;

  ulrc = mciSendCommand(usDeviceID,
                        MCI_CONNECTION,
                        MCI_QUERY_CONNECTION |
                        MCI_WAIT,
                        &mciConnectionParameters,
                        0);

  if(LOUSHORT(ulrc) == MCIERR_SUCCESS) {
      // The command was successful

      // Obtain the current device's ID, the one in
      // charge of setting the volume
      VolumeDeviceID =
             mciConnectionParameters.usToDeviceID;
  }
  else {
      // The command was not successful

      // We'll use the ID of the CD device
      VolumeDeviceID = usDeviceID;
  }

Commands to set the Volume level, Mute, and Unmute the device will have to use this VolumeDeviceID.

Time Display

Four time displays have been implemented:

Time played in the current track
current time - start time of the current track
Time remaining in the current track
end time of the current track - current time
Time played in the CD
current time - start time of first track
Time remaining in the CD
end time of last track - current time - start time of first track

The current time refers to the current time location that is being played, or the time location that would be played if a Play command was issued. If you realize that the audio tracks are continuous, one after the other, then the calculations presented above will make sense. (If it's not clear, start the program and have a look at the Table of Contents of an audio CD.) Also, we have to subtract the start time of the first track when calculating the time played/remaining in the CD because that first track does not necessarily start at time 00:00:00. In fact, it often starts at 00:00:02.

Updating the Time Display

When do we update the time display? Well, when it needs to be updated. Ok, so how do we know when it needs to be updated? There are two possible ways. The first would be to start a timer when the Play or Resume commands are issued and stop that timer when the Stop or Pause commands are issued. We'd also have to manipulate the timer when we seek. That seems like a lot of work, especially once we've seen the alternative.

We can request to be notified every time the CD plays a certain amount of time. In our case, we'd like to be notified every time the CD plays one second. To request the notification:

  mciPositionParameters.ulUnits = MSECTOMM(1000);

  ulrc = mciSendCommand(usDeviceID,
                        MCI_SET_POSITION_ADVISE,
                        MCI_WAIT | MCI_SET_POSITION_ADVISE_ON,
                        &mciPositionParameters,
                        0);

Every time the CD plays one second, we'll receive an MM_MCIPOSITIONCHANGE message. mp2 will contain the current time position in MMTIME format. We'll use this information to properly keep track of the current time.

The MSECTOMM() macro converts a time unit specified in milliseconds to a time unit in MMTIME. MMTIME is the time format used by the CD device by default. Although we could instruct it to use a different time format, I prefer to work in MMTIME. One reason is that it often doesn't make a difference which time format is being used. And another reason is that some API work only in MMTIME. (Note to C++ users. This macro generates an error message but nevertheless appears to work correctly.)

Muting and Unmuting the Volume

Both of these commands have the same syntax:

  mciSetParameters.ulAudio = MCI_SET_AUDIO_ALL;

  ulrc = mciSendCommand(VolumeDeviceID,
                        MCI_SET,
                        MCI_WAIT | MCI_SET_AUDIO | MCI_SET_OFF,
                        &mciSetParameters,
                        0);

When using the MCI_SET_OFF flag, we are muting. If we'd used the MCI_SET_ON flag, then we would have unmuted the volume level. Also, we could have targeted an individual channel, left (MCI_SET_AUDIO_LEFT) or right (MCI_SET_AUDIO_RIGHT) to mute/unmute only one channel. And the same holds true for setting the volume: we could modify one channel and leave the other channel unaffected.

Volume Level

To change the volume level, we first have to compound the left and right volume levels into a single ULONG variable, and then we specify the channels involved:

  mciSetParameters.ulLevel =
                (ULONG)MPFROM2SHORT(left, right);

  mciSetParameters.ulAudio = MCI_SET_AUDIO_ALL;

  ulrc = mciSendCommand(VolumeDeviceID,
                        MCI_SET,
                        MCI_WAIT | MCI_SET_AUDIO |
                        MCI_SET_VOLUME | MCI_SET_ON,
                        &mciSetParameters,
                        NULL);

Now that we've covered most of the necessary pieces, let's put them together as we finally cover the audio CD commands.

Play

This could be considered to be the most essential command of an audio CD player: if we can't play, then there's not much point in having the program. As mentioned before, the MCI_NOTIFY flag must be specified. If you specify the MCI_WAIT flag, you'll wait until the last audio track has been played before being able to process another message. In this example, we'll play from the first track:

  mciPlayParameters.ulFrom =
                     TOC.tocHeader[0].ulStartAddr;

  ulrc = mciSendCommand(usDeviceID,
                        MCI_PLAY,
                        MCI_NOTIFY | MCI_FROM,
                        &mciPlayParameters,
                        0);

After this command has been issued, we'll start receiving MM_MCIPOSITIONCHANGE messages since we previously requested notifications for every 1 second of playback. We'll also receive an MM_MCINOTIFY message from the MCI_PLAY command when the last track has been played. When that message is received, two actions could be taken. Either we implement a continuous play feature, where we restart playback at the next track, or we stop the CD by issuing the Stop command.

Stop

Stopping playback is fairly straightforward:

  ulrc = mciSendCommand(usDeviceID,
                        MCI_STOP,
                        MCI_WAIT,
                        &mciGenericParameters,
                        0);

Pause and Resume

When pausing playback, the current time position is noted, so that when the Resume command is issued, we resume playback from that paused location. So, to pause:

  ulrc = mciSendCommand(usDeviceID,
                        MCI_PAUSE,
                        MCI_WAIT,
                        &mciGenericParameters,
                        0);

And to resume from the paused time location:

  ulrc = mciSendCommand(usDeviceID,
                        MCI_RESUME,
                        MCI_WAIT,
                        &mciGenericParameters,
                        0);

Although Pause and Resume are simple to use, there is a little gotcha. If we paused playback, seeked to a different location or changed track, we wouldn't want to resume playback from the location where playback was paused. Rather, we'd want playback to resume from the time location we specified by seeking or changing the current track. One solution is to Stop the CD if we seek or change track while we're paused. The option to pause would be disabled, and the only recourse to continue playback would be via the Play command. The second solution is to Pause playback from this new time location (this is how I've done ).

That's it for today; so long until next time!