
The following sections discuss various ways of using the audio devices provided with the Open Class Library Multimedia Classes.
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.
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;
};
/*-------------------------------------------------------------
| 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 );
}
/*-------------------------------------------------------------
| 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.
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:
#includewavePlayer = 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.
#includewavePlayer = 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.
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;
};
/*-----------------------------------------------------------
| 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()
/*-----------------------------------------------------------
| 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.
There are three ways to load a file:
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:
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.
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.
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.
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;
};
/*-----------------------------------------------------------
| 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 );
}
/*-----------------------------------------------------------
| 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;
}
/*-----------------------------------------------------------
| 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.
![]()
Creating Master Devices
Playing Audio Compact Discs
Creating Video Devices
Adding Animated Buttons and
Circular Sliders
![]()
IAnimatedButton
ICircularSlider
IMMPlayerPanel
IMMRecordable
IMMWaveAudio
IMultiCellCanvas
ISliderArmHandler
IStaticText