Playing Audio Compact Discs

The interface for accessing and playing CDs should look very similiar to what you are used to on your home system. It should allow you to scan, play, stop, pause, and reverse. The IMMAudioCD class works only on discs that contain CD_DA tracks.

The application must ensure that the appropriate compact disc is in the CD drive. For example, a CD player application might simply update its track and time displays if a new disc is inserted and verified. If you try to open an IMMAudioCD object and there is not an audio CD in the CD-ROM drive, then the application sends a message that the medium is not valid. A CD-drive can only be accessed by one player at a time. An example of designing a user interface with a player panel containing a CD player device follows.

  1. Define the device in the .hpp file as follows:
    
    class CD  : public IMultiCellCanvas,
                public ICommandHandler,
                public IObserver,
                public ISliderArmHandler,
                public ISelectHandler
    {
    //**************************************************************************
    // Class:   CD                                                             *
    //                                                                         *
    // Purpose: Provide a CD player.                                           *
    //          It is a subclass of IMultiCell                                 *
    //                                                                         *
    //**************************************************************************
       public:
          CD( unsigned long   windowid,
              IWindow*        parent,
              IWindow*        owner);
    
       protected:
          virtual bool
             command ( ICommandEvent& event ),
             selected( IControlEvent& event ),
             moving  ( IControlEvent& event );
    
          virtual IObserver
             &dispatchNotificationEvent(const INotificationEvent&);
    
       private:
          IMMAudioCD
             cdPlayer;
    
          IMMPlayerPanel
             baseButtons;
    
          IAnimatedButton
             trackF,
             trackB,
             scanF,
             scanB,
             eject;
    
          ICircularSlider
             volume;
    
          IStaticText
             name,
             readout;
    
          IRadioButton
             doorOpen,
             doorClosed;
    
          IMMAmpMixer
            *pAmpMixer;
    };
    
    class MainWindow : public IFrameWindow
    {
    //**************************************************************************
    // Class:   MainWindow                                                     *
    //                                                                         *
    // Purpose: Main Window for C++ MltMedia sample application.               *
    //          It is a subclass of IFrameWindow                               *
    //                                                                         *
    //**************************************************************************
       public:
    
          MainWindow( unsigned long windowId );
    
         ~MainWindow();
    
       private:
          ISetCanvas
             clientCanvas;
    
          CD*
             cd;
    };
    
    
  2. Create the main window and the CD as follows:
    
    /*---------------------------------------------------------
    | MainWindow::MainWindow
    ------------------------------------------------------------
    MainWindow::MainWindow( unsigned long windowId)
              : IFrameWindow("Audio CD Example",windowId),
                clientCanvas(CLIENTCANVASID,this,this)
    {
       cd       = new CD ( CD_ID, &clientCanvas, this);
       setBackgroundColor(IColor(IColor::kPaleGray));
       IFont("Helv",8).setWindowFont(this);
       sizeTo(ISize(600, 300));
       setClient(cd);
       clientCanvas.setDeckOrientation(ISetCanvas::vertical);
    
       show();
       setFocus();
    }
    
    MainWindow::~MainWindow()
    {
       if (cd)
          delete cd;
    }
    
    CD::CD(
            unsigned long windowid,
            IWindow*          parent,
            IWindow*          owner)
       : IMultiCellCanvas(windowid,parent,owner),
         readout     (READOUTID, this,this),
         name        (CDNAMEID, this, this),
         baseButtons (BASEBUTTONID, this,this, IMMDevice::audioCD),
         trackF      (TRACKFID,&baseButtons,&baseButtons,IRectangle(),
                      IWindow::visible | IAnimatedButton::animateWhenLatched),
         trackB      (TRACKBID,&baseButtons,&baseButtons,IRectangle(),
                      IWindow::visible | IAnimatedButton::animateWhenLatched),
         scanF       (SCANFID,&baseButtons,this,IRectangle(),
                      ICustomButton::autoLatch |
                      ICustomButton::latchable |
                      IWindow::visible | IAnimatedButton::animateWhenLatched),
         scanB       (SCANBID,&baseButtons,this,IRectangle(),
                      ICustomButton::autoLatch |
                      ICustomButton::latchable |
                      IWindow::visible | IAnimatedButton::animateWhenLatched),
         eject       (EJECTID,this,this,IRectangle(),
                      ICustomButton::autoLatch |
                      IWindow::visible | IAnimatedButton::animateWhenLatched),
         volume   (VOLID, this, this, IRectangle(),
                      ICircularSlider::defaultStyle() | ICircularSlider::proportionalTicks),
         doorOpen (OPENBTN, this, this, IRectangle(), IRadioButton::defaultStyle() |
                      IControl::group),
         doorClosed(CLOSEDBTN, this, this),
         cdPlayer(),
         pAmpMixer( 0 )
    {
       // Allow the CD to play without a connector.
       cdPlayer.enableConnector(IMMDevice::cdStream);
    
       //Add the additional button to the player panel.
       baseButtons.setPlayableDevice(&cdPlayer);
       baseButtons.addToCell(&trackB  ,     7, 1, 1, 1);
       baseButtons.addToCell(&trackF  ,     8, 1, 1, 1);
       baseButtons.addToCell(&scanB   ,    10, 1, 1, 1);
       baseButtons.addToCell(&scanF   ,    11, 1, 1, 1);
    
       //Put the bitmaps on the buttons.
       trackB.setBitmaps(IAnimatedButton::trackReverse);
       trackF.setBitmaps(IAnimatedButton::trackAdvance);
       scanB.setBitmaps(IAnimatedButton::scanBackward);
       scanF.setBitmaps(IAnimatedButton::scanForward);
       eject.setBitmaps(IAnimatedButton::eject);
    
       //Put text on the buttons. The \n cause the text to be on a new line.
       trackB.setText("Track\nReverse");
       trackF.setText("Track\nAdvance");
       scanB.setText("Scan\nBackward");
       scanF.setText("Scan\nForward");
       eject.setText("Eject");
    
       doorOpen.setText("Open door");
       doorClosed.setText("Close door (if possible)");
       doorOpen.select();
    
       //Set up the title
       name.setText("CD Player");
    
       //Set up the display
       readout.setText("TRACK 00 MIN:SEC 00:00");
       readout.setLimit(24);
    
       volume.setArmRange (IRange(0,100));
       volume.setRotationIncrement(10);
       volume.setText       ("Volume");
       volume.setValue( 100 );
       cdPlayer.setVolume( 100 );
    
       pAmpMixer = new IMMAmpMixer( cdPlayer.connectedDeviceId( IMMDevice::cdStream ) );
       pAmpMixer->enableMonitoring();
       pAmpMixer->setCloseOnDestroy( false );
    
       //Add the controls to the multicell
       addToCell             (&name,        2, 1, 4, 1);
       addToCell             (&readout,     2, 3, 4, 1);
       addToCell             (&doorOpen,    2, 5, 4, 1);
       addToCell             (&doorClosed,  2, 6, 4, 1);
       addToCell             (&volume,      4, 7);
       addToCell             (&baseButtons, 4, 9);
       addToCell             (&eject,       2, 9);
       setColumnWidth        ( 5, 0, true );
    
       ISelectHandler::handleEventsFor(this);
       ICommandHandler::handleEventsFor(this);
       IObserver::handleNotificationsFor(cdPlayer);
       ISliderArmHandler::handleEventsFor(&volume);
    }
    
    
    

  3. Handle the CD track and scan buttons as follows:
    
    bool CD::command( ICommandEvent& evt )
    {
      bool
        rv = false;
      switch ( evt.commandId() )
      {
      case TRACKBID:
           cdPlayer.trackBackward();
           rv=true;
           break;
      case TRACKFID:
           cdPlayer.trackForward();
           rv=true;
           break;
      case SCANFID:
           if (scanF.isLatched())
              cdPlayer.startScanningForward();
           else
              cdPlayer.stop();
           rv=true;
           break;
      case SCANBID:
           if (scanB.isLatched())
             cdPlayer.startScanningBackward();
           else
             cdPlayer.stop();
           rv=true;
           break;
      case EJECTID:
           if (cdPlayer.isMediaPresent())
              cdPlayer.openDoor();
          else
          {
              cdPlayer.closeDoor();
              if (cdPlayer.isMediaPresent())
               eject.unlatch();
          }
           rv = true;
           break;
        }
      return rv;
    }
    

  4. Handle the radio buttons to open and close the CD drive as follows:
    bool CD::selected( IControlEvent& evt )
    {
      bool
        rv = false;
      switch(evt.controlId())
      {
        case OPENBTN:
          // enable open cd
              cdPlayer.openDoor();
              rv = true;
          break;
        case CLOSEDBTN:
          // close cd
          cdPlayer.closeDoor();
          rv = true;
          break;
       }
      return rv;
    }
    

  5. Handle the notification events as follows:
    IObserver& CD::dispatchNotificationEvent(const INotificationEvent& event)
    {
       if (event.notificationId() == IMMAudioCD::positionTimerId)
       {
          IMMTrackMinSecFrameTime* time = (IMMTrackMinSecFrameTime*)(event.eventData().asUnsignedLong());
          readout.setText(IString("TRACK ") +
                          IString(time->track()).rightJustify(2,'0') +
                          IString(" MIN:SEC ") +
                          IString(time->minutes()).rightJustify(2,'0') +
                          IString(":") +
                          IString(time->seconds()).rightJustify(2,'0'));
       }
       else if (event.notificationId() == IMMAudioCD::trackStartedId)
       {
          IMMTrackMinSecFrameTime* time = (IMMTrackMinSecFrameTime*)(event.eventData().asUnsignedLong());
          readout.setText(IString("TRACK ") +
                          IString(time->track()).rightJustify(2,'0') +
                          IString(" MIN:SEC ") +
                          IString(time->minutes()).rightJustify(2,'0') +
                          IString(":") +
                          IString(time->seconds()).rightJustify(2,'0'));
       }
       return *this;
    }
    

  6. Handle the volume slider events as follows:
    /*-------------------------------------------------------------
    | CD::moving                                      |
    --------------------------------------------------------------*/
    bool CD::moving(IControlEvent& evt)
    {
       bool
          result = false;
    
       ICircularSlider
         *pSld = (ICircularSlider*)( evt.controlWindow() );
       short
          val = pSld->value();
    
       switch( evt.controlId() )
       {
          case VOLID:
             pAmpMixer->setVolume( val );
             result = true;
             break;
       }
    
       return result;
    }
    
    

    The following figure shows an audio CD example. Note that the track information is displayed on the top part of the interface. The open and closed radio buttons control the CD door.


    Using a MIDI Sequencer Device (IMMSequencer)

    A sequencer device plays a MIDI file by sending commands to a synthesizer where the commands are converted to the sounds of a specific musical instrument. The sequencer uses the timing commands to sequence the playing of the music.

    Music devices with a sequencer, such as a Casio keyboard or a drum machine (a machine that reproduces percussion sounds), can record what is being played and can play what has been recorded previously. This recording is called a sequence. This sequence of music notes is stored in the MIDI format.

    A sequencer is personal computer software that allows you to record, edit, and arrange multiple tracks of MIDI data. Most sequencers let you edit the messages in a sequence and link different sequences stored in memory. This finished sequence, ready for playback, is called a song. If you do not want to manipulate songs already recorded with a sequencer, you can also create original songs. A sequencer lets you record any style of music you want.

    The MIDI sequencer device plays a MIDI song by sending commands from a MIDI file to a synthesizer where the commands are converted to the sounds of specific instruments. The IMMSequencer class is the base class for handling a MIDI sequencer device, and it supports the MIDI standard. Thus, the sequencer controls the characteristics of the MIDI information. In addition to allowing you to load MIDI files, the IMMSequencer class inherits all of the main functions, such as play, stop, pause, and record.

    You can use the following command to create a MIDI sequencer object:

    #include    // Define the header file
    IMMSequencer midiPlayer; // Define the object
    midiPlayer(true)         // Pass true to the device constructors so
                             // the devices are opened and no additional
                             // functions calls are made before using the
                             // device.
    


    Using a Waveform Audio Player Device (IMMWaveAudio)

    The waveform audio device allows an application to play or record digital audio using files or application memory. Waveform audio devices require some form of input, that is, a file. The file contains the actual sound or waveform. The device can be opened with or without a file. If it is opened without a file, then a file is typically loaded later.

    This device can use files or memory buffers. Buffering data improves performance of multimedia applications that perform numerous file input and output operations when accessing media devices. Applications that are performance-sensitive (that is, slow machines) can optimize file input and performance by buffering their data. If the data is already in the memory buffer, the operating system can transfer the record to the application's area without reading the sector from disk.

    An object instantiated from IMMWaveAudio is capable of performing many tasks with a sound file. It can edit, play back, and record to name a few. In addition, the object inherits up the chain for the functions of play, stop, pause, and setFormat, plus cut, copy, and paste to, and from a memory buffer.

    You can use the following command to create a waveform audio device object:

    #include    // Define the header file
    IMMWaveAudio wavePlayer; // Define the object
    wavePlayer(true)         // Pass true to the device constructors so
                             // the devices are opened and no additional
                             // functions calls are made before using
                             // the device.
    



    Multimedia Devices


    Creating Master Devices
    Creating Audio Devices
    Creating Video Devices
    Adding Animated Buttons and Circular Sliders


    IAnimatedButton
    ICircularSlider
    ICommandHandler
    IMMAudioCD
    IMMPlayerPanel
    IMMRecordable
    IMMSequencer
    IMMWaveAudio
    IMultiCellCanvas
    IObserver
    ISelectHandler
    ISliderArmHandler
    IStaticText