### Creating Audio Devices

The following sections discuss various ways of using the audio devices provided with the Open Class Library Multimedia Classes.

#### Playing a Waveform

If you want to design an application that can record and play back an audio signal from the user's desktop, create an IMMWaveAudio object.

To play or record audio data, a device must be able to start and stop. Also, you might want fast-forward, reverse, and pause functions. Because most people are familiar with the control panel of the typical stereo, choose that model as your application's user interface model.

An example that creates a user interface with an audio device and its controls follows.

1. Define the wave player device in the .hpp file as follows:

       class MainWindow;
       class WavePlayer : public IMultiCellCanvas,
                            public ICommandHandler,
                            public ISliderArmHandler
       {
       public:
         WavePlayer(  unsigned long     windowid,
                      IWindow*         parent,
                      IWindow*         owner);
       protected:
         virtual bool
             command( ICommandEvent& evt ),
             moving( IControlEvent& evt );
       private:
         IStaticText                infoText;
         IAnimatedButton            playbtn,
                                    stopbtn,
                                    ffbtn,
                                    rewbtn,
                                    pausebtn,
                                    recordbtn;
         ICircularSlider            volume;
         IMMWaveAudio           wavePlayer;

       };

2. Create the main window and the wave player as follows:

       /*-------------------------------------------------------------
       | MainWindow::MainWindow
       --------------------------------------------------------------*/
       MainWindow::MainWindow( unsigned long windowId)
                 : IFrameWindow("Playing/Recording Waveform Example",windowId),
                   menuBar(windowId, this)
       {
          wave = new WavePlayer(WAVEID, this, this);
          setClient( wave );
          sizeTo(ISize(550, 300));
          setFocus().show();

       /*-------------------------------------------------------------
       | WavePlayer::WavePlayer
       --------------------------------------------------------------*/
       WavePlayer::WavePlayer(
                     unsigned long windowid,
                     IWindow*          parent,
                     IWindow*          owner)
          : IMultiCellCanvas(windowid,parent,owner),
            volume(VOLID, this, this, IRectangle(), ICircularSlider::defaultStyle() |
                ICircularSlider::proportionalTicks),
            playbtn(PLAYID,  this,   this, IRectangle(),
               ICustomButton::latchable | ICustomButton::autoLatch | IControl::group |
               IWindow::visible | IAnimatedButton::animateWhenLatched),
            stopbtn(STOPID,  this,   this, IRectangle(),
               IWindow::visible | IAnimatedButton::animateWhenLatched),
            pausebtn(PAUSEID, this,  this, IRectangle(),
               ICustomButton::latchable | ICustomButton::autoLatch | IControl::group |
               IWindow::visible | IAnimatedButton::animateWhenLatched),
            ffbtn(FFID,           this,  this,  IRectangle(),
               IWindow::visible | IAnimatedButton::animateWhenLatched),
            rewbtn(REWID,       this,   this, IRectangle(),
               IWindow::visible | IAnimatedButton::animateWhenLatched),
            recordbtn(RECID,   this,  this, IRectangle(),
               IWindow::visible | IAnimatedButton::animateWhenLatched),
            infoText(INFOTXT, this, this),
            wavePlayer()

       {
          IFont("System Proportional",4).setWindowFont(this);

          infoText.setText("Welcome to Stereo Sound");
          IFont("Helv",16).setWindowFont(&infoText);

          volume.setArmRange     (IRange(0,100));
          volume.setRotationIncrement(10);
          volume.setText      ("Volume");
          volume.setValue( 100 );

          addToCell(&rewbtn,    2, 2);
          addToCell(&stopbtn,   3, 2);
          addToCell(&pausebtn,  4, 2);
          addToCell(&playbtn,   5, 2);
          addToCell(&ffbtn,     6, 2);
          addToCell(&recordbtn, 8, 2);
          addToCell(&volume,    5, 4);
          addToCell(&infoText,  2, 8, 8, 1);
          setColumnWidth( 9, 0, true );

          rewbtn.setBitmaps  (IAnimatedButton::rewind);
          ffbtn.setBitmaps   (IAnimatedButton::fastForward);
          pausebtn.setBitmaps(IAnimatedButton::pause);
          stopbtn.setBitmaps (IAnimatedButton::stop);
          playbtn.setBitmaps (IAnimatedButton::play);
          recordbtn.setBitmaps(IAnimatedButton::record);

          playbtn.setText("Play");
          rewbtn.setText  ("Rewind");
          ffbtn.setText   ("FFwd");
          pausebtn.setText("Pause" );
          stopbtn.setText ("Stop"  );
          recordbtn.setText("Record");

          playbtn.enable( false );
          rewbtn.enable( false );
          ffbtn.enable( false );
          pausebtn.enable( false );
          stopbtn.enable( false );
          recordbtn.enable( false );

          ICommandHandler::handleEventsFor(this);
          ISliderArmHandler::handleEventsFor( &volume );
       }

3. Handle the player panel events as follows:

    /*-------------------------------------------------------------
    | WavePlayer::moving
    --------------------------------------------------------------*/
    bool WavePlayer::moving( IControlEvent& evt )
    {
       bool
          result = false;
       ICircularSlider
         *pSld = (ICircularSlider*)( evt.controlWindow() );
       short
          val = pSld->value();
       switch( evt.controlId() )
       {
          case VOLID:
             wavePlayer.setVolume( val );
             result = true;
             break;
       }
       return result;
    }
    /*-------------------------------------------------------------
    | WavePlayer::command
    --------------------------------------------------------------*/
    bool WavePlayer::command(ICommandEvent& evt)
    {
      bool rv = false;
      switch (evt.commandId())
       {
         case MI_OPEN:
         {
            IFileDialog::Settings fdSettings;
            fdSettings.setTitle("Load file");
            fdSettings.setFileName("*.wav");
            IFileDialog fd(desktopWindow(), this, fdSettings);
            if (fd.pressedOK())
            {
               wavePlayer.loadOnThread(fd.fileName());
               wavePlayer.setVolume( 100 );

               // Now that a file is loaded, enable buttons.
               playbtn.enable();
               rewbtn.enable();
               ffbtn.enable();
               pausebtn.enable();
               stopbtn.enable();
               recordbtn.enable();
            }
            rv=true;
            break;
         }

         case PLAYID:
            wavePlayer.play();
            infoText.setText("Play Mode");
            rv=true;
            break;
         case STOPID:
            wavePlayer.stop();
            infoText.setText("Stop Mode");
            playbtn.unlatch();
            rv=true;
            break;
         case REWID:
            wavePlayer.seekToStart();
            infoText.setText("Rewind Mode");
            rv=true;
            break;
         case FFID:
            wavePlayer.seekToEnd();
            infoText.setText("FF Mode");
            rv=true;
            break;
         case PAUSEID:
            if (IMMDevice::paused == wavePlayer.mode())
              playbtn.latch();
            wavePlayer.pause();
            infoText.setText("Pause Mode");
            rv=true;
            break;
      case RECID:
            // No loading should be done here. It is done after
            // the user selects a file from the file dialog.

            // Explicitly enable the mike input. It should not be assumed
            // that the mike input is already enabled.

            wavePlayer.enableConnector(IMMDevice::microphones);
            wavePlayer.setVolume( 100 );
            recordbtn.latch();
            wavePlayer.record();
            infoText.setText("Record Mode");
            rv=true;
            break;
        }
      return rv;
    }

The following figure demonstrates a wave player interface.

#### Recording a Waveform

As stated earlier, a waveform is a digital representation of a sound wave. Different formats of a waveform, such as pulse code modulation (PCM), encode sound into digital data that can be sent to an amplifier-mixer device for subsequent conversion into audio. This signal can be played through conventional speakers or earphones.

The average waveform audio driver uses PCM, 22 kiloHertz, 16 bits-per-second, and monaural as the default for 16-bit adapters. If the adapter does not support 16-bit PCM, then the resolution (bits-per-second) is downgraded to 8 bits. The types of audio resolution are 8 (multimedia), 16 (CD audio) and 24 (high-end) digital bits-per-sample. Red Book audio is a music industry term technically known as the CD digital audio standard for music CD audio. Yellow Book audio is 16-bit or 8-bit digital audio played back by the sound card. Typically, yellow book audio is stored on the personal computer as .wav files.

One of the typical uses of the waveform audio device is to digitize an input signal or sound into discrete samples for storage in a file. An example of this is recording an electronic audio mail message to tell someone about an idea, as opposed to typing a memo. An electronic audio mail application would provide the user with a simple control panel to allow the message to be recorded. Recording digitally means you get flawless sound quality that does not deteriorate.

You can record digital audio information in the format that fits your specific needs, such as for space or quality. For example, assume that a new wave audio file is created with the following command:

    #include
    wavePlayer = new IMMWaveAudio(true);        // Create the object.
    wavePlayer.record(10, 20);             // Enter begin and end time values.

When you create the file, you might want a file that is compatible with mu-law (the compression scheme used by a telephone system). The compression scheme can change the frequency range from a telephone to CD quality.

The attributes you need to consider when recording a file are the following:

- Format and its compression algorithm (pcm, adpcm, ibmcvcsd, okiadpcm, dviadpcm, digistd, digifix, or alaw)
- Bits per sample (16-bit is considered CD quality)
- Sampling rate (for example, 22 kiloHertz)
- Number of channels (stereo or monaural)

These attributes determine the audio quality. You can even make the decision to use low-bit resolution, a low sample rate, or even monaural versus stereo on the basis of disc space and bandwidth considerations. Always set the waveform format, sampling rate, resolution, and number of channels to ensure that the waveform is created with the desired parameters.

The following is an example of code that sets these values.

    #include
    wavePlayer = new IMMWaveAudio();          // Create the object.
    wavePlayer.setBitsPerSample(Value);
    wavePlayer.setSamplesPerSecond(Value);    // Set sampling rate
    wavePlayer.setChannels(1);                // Monaural is 1 (stereo is 2)

Your application needs to define or select the recording source. The microphone is the default input device for recording waveforms.

The IMMWaveAudio class inherits the record function. An example of playing and recording a wave file follows.

1. Define the wave player device in the .hpp file as follows:

       class WavePlayer : public IMultiCellCanvas,
                            public ICommandHandler
                            public ISliderArmHandler,
       {
       //****************************************************************
       // Class:   WavePlayer                                           *
       //                                                               *
       // Purpose: Provide a WavePlayer for use by all of the devices.  *
       //          It is derived from IMultiCell.                       *
       //                                                               *
       //****************************************************************

       public:
          WavePlayer( unsigned long    windowid,
                      IWindow*         parent,
                      IWindow*         owner);

       protected:
          virtual bool
             command( ICommandEvent& evt ),
             moving( IControlEvent& evt);

       private:
         IStaticText                infoText;
         IAnimatedButton            playbtn,          // Player panel
                                    stopbtn,
                                    ffbtn,
                                    rewbtn,
                                    pausebtn,
                                    recordbtn;
         ICircularSlider            volume;            // Volume control
         IMMWaveAudio               wavePlayer;
       };

       //***************************************************************
       // Class:   MainWindow                                          *
       //                                                              *
       // Purpose: Provide a WavePlayer for use by all of the devices. *
       //          It is derived from IMultiCell.                      *
       //                                                              *
       //***************************************************************
       class MainWindow : public IFrameWindow {
       public:
         MainWindow( unsigned long windowId);
       private:
             IMenuBar             menuBar;
             WavePlayer           *wave;
       };

2. Create the main window and the wave player as follows:

       /*-----------------------------------------------------------
       | MainWindow::MainWindow
       -------------------------------------------------------------
       MainWindow::MainWindow( unsigned long windowId)
                 : IFrameWindow("Playing/Recording Waveform Example",windowId),
                   menuBar(windowId, this),
                   myWavePlayer( WINDOWID, this, this )
       {
          setClient( &myWavePlayer );
          setFocus().show();

       /*-----------------------------------------------------------
       | WavePlayer::WavePlayer
       -------------------------------------------------------------*/
       WavePlayer::WavePlayer( unsigned long windowid,
                     IWindow*          parent,
                     IWindow*          owner)
            :IMultiCellCanvas  ( windowid, parent, owner ),
            volume            ( VOLID, this, this, IRectangle(),
                                ICircularSlider::defaultStyle() |
                                ICircularSlider::proportionalTicks ),

            infoText(INFOTXT, this, this),
            mono(MONOID, this, this, IRectangle(),
                 IRadioButton::defaultStyle() | IControl::group),
            stereo(STEREOID, this, this),
            formatText(FORTEXTID, this, this),
            wavePlayer()

3. Handle events as follows:

    /*-----------------------------------------------------------
    | WavePlayer::selected
    -------------------------------------------------------------*/
    bool WavePlayer::selected(IControlEvent& evt)
    {
      bool rv = false;
      switch (evt.controlId())          // Handle the radio button controls
         {
         case MONOID:
          wavePlayer.setChannels(1);
          rv = true;
          break;
         case STEREOID:
          wavePlayer.setChannels(2);
          rv = true;
          break;
        }
       return rv;
     }
    bool WavePlayer::moving( IControlEvent& evt )
    {
       bool
          result = false;
       ICircularSlider
         *pSld = (ICircularSlider*)( evt.controlWindow() );
       short
          val = pSld->value();
       switch( evt.controlId() )
       {
          case VOLID:
             wavePlayer.setVolume( val );
             result = true;
             break;
       }
       return result;
    }
    bool WavePlayer::command( ICommandEvent& evt )
    {
       bool
          rv = false;
       switch( evt.commandId() )
       {
          case MI_OPEN:
          {
             IFileDialog::Settings
                fdSettings;
             fdSettings.setTitle( "Load file" );
             fdSettings.setFileName( "*.wav" );
             IFileDialog
                fd( desktopWindow(), this, fdSettings );
             if ( fd.pressedOK() )
             {
                wavePlayer.loadOnThread( fd.fileName() );
             }
             rv=true;
             break;
          }
          case PLAYID:
          {
             wavePlayer.play();
             infoText.setText( "Play Mode" );
             rv=true;
             break;
          }
          case STOPID:
          {
             wavePlayer.stop();
             playbtn.enable();
             pausebtn.enable();
             ffbtn.enable();
             rewbtn.enable();
             infoText.setText( "Stop Mode" );
             playbtn.unlatch();
             rv=true;
             break;
          }
          case REWID:
          {
             wavePlayer.seekToStart();
             infoText.setText( "Rewind Mode" );
             rv=true;
             break;
          }
          case FFID:
          {
             wavePlayer.seekToEnd();
             infoText.setText( "FF Mode" );
             rv=true;
             break;
          }
          case PAUSEID:
          {
             if ( IMMDevice::paused == wavePlayer.mode() )
                playbtn.latch();
             wavePlayer.pause();
             infoText.setText( "Pause Mode" );
             rv=true;
             break;
          }
          case RECID:
          {
             recordbtn.latch();
             playbtn.enable( false );
             pausebtn.enable( false );
             ffbtn.enable( false );
             rewbtn.enable( false );
             wavePlayer.record();
             infoText.setText( "Record Mode" );
             rv=true;
             break;
          }
       }
       return rv;
    }

The following figure demonstrates playing and recording a wave player. You can select a format and number of channels.

#### Loading the Audio or Video Device Data Files (IMMFileMedia)

There are three ways to load a file:

- Load with the wait calltype. The call does not return until the system loads the file into memory. This ties up the operating system's windowing system until this call returns.

- Load with the nowait calltype. This creates a thread. The thread then loads the data and then notifies the attached observers when it is done. This call returns without waiting for the thread to complete. This will not tie up the operating system's windowing system.

- Use LoadOnThread. This creates a thread. The thread then loads the data. This call does not return until the thread finishes. This does not tie up the operating system's windowing system.

#### Using the Default Device Player (IMMPlayerPanel)

The interface for play, pausing, and stopping should appear similar to your system at home.

You can use Open Class Library's custom player panel. The IMMPlayerPanel class creates and manages a player panel. If you create the IMMPlayerPanel without passing in a device type then you get the default buttons, which follows:

- Play
- Stop
- Pause
- Rewind
- Fast forward

If you pass in an overlay, videoDisk animation, or digital video, you get step forward and step backward buttons.

The base player panel is sufficient to control most multimedia devices.

The buttons are added to an IMultiCellCanvas in the following coordinates:

Play
  4,1 or at 5,1 if the step buttons are enabled

Pause
  3,1 or at 4,1 if the step buttons are enabled

fastForward
  5,1 or at 6,1 if step buttons are enabled

rewind
  1,1 or at 2,1 if step buttons are enabled

stop
  2,1 or at 3,1 if step buttons are enabled

stepForward
  7,1 if step buttons are enabled

stepBackward
  1,1 if step buttons are enabled

An example of creating the custom player panel follows. See the other examples in this chapter for additional samples of using a player panel.

    // Create a playable wave device
    IMMWaveAudio player;
    // Create the player panel and set it to control the wave audio player
    IMMPlayerPanel panel(0x8008, &mainWindow, &mainWindow,
         IMMDevice::waveAudio);
    panel.setPlayableDevice(player);

The following figure shows a custom player panel.

Note:

> The stop and pause buttons are disabled when starting up the application.

#### Editing a Waveform

The IMMWaveAudio class edits wave behavior. You can cut, copy, and paste to and from a memory buffer. A wave editor program allows you to record, edit, combine, and add special effects to a digital audio file. The file is not actually modified until the original file is saved. The editor allows you to mix tracks. You can use the musical editing process, for example, to correct mistakes in an artist's original interpretation or to change certain points of style before playback or final recording.

#### Using Save and Save As

The IMMRecordable class provides all the common behavior for devices that support recordable media. When you save a file the binary information is stored in addition to all of the wave's attributes. For example, if you are saving a waveform, some of the attributes that are saved follow:

- Sampling rate
- Resolution
- Waveform format
- Number of channels

####

Typical user interfaces that play MIDI files have a player panel containing a MIDI device object, a menu option to load a file via a file dialog, or both. An example with a player panel follows.

1. Define the MIDI device in the .hpp file as follows:

       class MIDI  : public IMultiCellCanvas,
                      public ICommandHandler,
                      public ISliderArmHandler  {
       //************************************************************
       // Class:   MIDI                                             *
       //                                                           *
       // Purpose: Provide a MIDI Player.                           *
       //          It is derived from IMultiCell.                   *
       //                                                           *
       //************************************************************
       public:
       MIDI( unsigned long     windowid,
             IWindow*          parent,
             IWindow*          owner);
       protected:
       virtual bool
         command( ICommandEvent& evt ),
         moving (IControlEvent& evt);
       private:
       IMMSequencer
         midiPlayer;
       IMMPlayerPanel
         baseButtons;
       IAnimatedButton
         loadbtn,
         rec;
       ICircularSlider
         volume;
       IStaticText
        name;
       };

2. Create the main window and the MIDI player as follows:

       /*-----------------------------------------------------------
       | MainWindow::MainWindow
       -------------------------------------------------------------*/
       MainWindow::MainWindow( unsigned long windowId)
                 : IFrameWindow   ( "Example MIDI Window", windowId ),
                   clientCanvas   ( CLIENTCANVASID, this, this ),
                   midi           ( MIDI_ID, &clientCanvas, this ),
                   menuBar        ( windowId, this )
       {
         clientCanvas.setBackgroundColor( IColor( IColor::kPaleGray ) );
         setBackgroundColor( IColor( IColor::kPaleGray ) );
         IFont( "Helv", 8 ).setWindowFont( this );
         sizeTo( ISize( 500, 300 ) );
         setClient( &midi );
         clientCanvas.setDeckOrientation( ISetCanvas::vertical );
         setFocus().show();
       }

       MainWindow::~MainWindow()
       {
       }

       /*-----------------------------------------------------------
       | MIDI::MIDI
       -------------------------------------------------------------*/
       MIDI::MIDI( unsigned long windowid,
               IWindow*          parent,
               IWindow*          owner)
          : IMultiCellCanvas(windowid,parent,owner),
            name        (MIDINAMEID, this, this),
            baseButtons (BASEBUTTONID, this,this),
            volume   (VOLID, this, this, IRectangle(),
                         ICircularSlider::defaultStyle() |
                         ICircularSlider::proportionalTicks),
            rec      ( RECID, &baseButtons, this, IRectangle(),
                       ICustomButton::latchable |
                       ICustomButton::latchable |
                       IWindow::visible |
                       IAnimatedButton::animateWhenLatched ),
            loadbtn   (LOADID, this, this, IRectangle(),
                            IWindow::visible |
                                    IAnimatedButton::animateWhenLatched),
            midiPlayer()
       {
          baseButtons.setPlayableDevice(&midiPlayer);   // Add button to panel

          //Put the bitmaps on the buttons.
          rec.setBitmaps( IAnimatedButton::record );
          loadbtn.setBitmaps( IAnimatedButton::eject );

          //Put text on the buttons.
          loadbtn.setText( "Load" );
          rec.setText( "Record" );

          //Set up the title
          name.setText( "MIDI Player" );
          name.setForegroundColor( IColor( IColor::kRed ) );

          volume.setArmRange( IRange( 0,100 ) );
          volume.setRotationIncrement( 1 );
          volume.setText( "Volume" );

          //Add the controls to the multicell
          addToCell( &loadbtn,     1, 9, 1, 1 );
          addToCell( &name,        2, 1, 1, 1 );
          addToCell( &volume,      3, 4, 1, 3 );
          addToCell( &baseButtons, 2, 9, 5, 1 );

          // Handle events
          ICommandHandler::handleEventsFor( this );
          ISliderArmHandler::handleEventsFor( this );
       }

3. Handle events for the sliders as follows:

       /*-----------------------------------------------------------
       | MIDI::moving
       -------------------------------------------------------------*/
       bool MIDI::moving (IControlEvent& evt)
       {
         bool result = false;
         ICircularSlider
           *pSld = (ICircularSlider*)( evt.controlWindow() );
         short
             val = pSld->value();
         switch(evt.controlId())
          {
            case VOLID:
             midiPlayer.setVolume(val);
             result = true;
             break;
           }
         return result;
       }

4. Handle events for the radio buttons as follows:

    /*-----------------------------------------------------------
    | MIDI::command
    -------------------------------------------------------------*/
    bool MIDI::command(ICommandEvent& evt)
    {
      bool rv = false;
      switch (evt.commandId())               // Load the midi file to play
      {
         case MI_OPEN:
         case LOADID:
            IFileDialog::Settings fdSettings;
            fdSettings.setTitle("Load file");
            fdSettings.setFileName("*.mid");
            IFileDialog fd(desktopWindow(), this, fdSettings);
            if (fd.pressedOK())
              midiPlayer.loadOnThread(fd.fileName());
            rv=true;
          case PLAYID:
          {
             midiPlayer.play();
             rv=true;
             break;
          }
        }
      return rv;
    }

The following figure displays a MIDI interface. Note that when you select the file menu option, a file dialog appears.

Multimedia Devices

Creating Master Devices
Playing Audio Compact Discs
Creating Video Devices
Adding Animated Buttons and Circular Sliders

IAnimatedButton
ICircularSlider
IMMPlayerPanel
IMMRecordable
IMMWaveAudio
IMultiCellCanvas
ISliderArmHandler
IStaticText