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:

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:


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:

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:


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