Documentation  |   Table of Contents   |  < Previous   |  Next >   |  Index

1    Sound Manager

Multimedia

Exploring Palm OS®

The Palm OS® Sound Manager controls two independent sound facilities:

  • Simple sound: Single voice, monophonic, square-wave sound synthesis, useful for system beeps and the like. This works on all version of Palm OS.
  • Sampled sound: Stereo, multi-format, sampled data recording and playback. Sampled sounds can be generated programmatically or read from a sound file.

These facilities are independent of each other. Although you can play a simple sound and a sampled sound at the same time, their respective APIs have no effect on each other. For example, you can't use the sampled sound volume-setting function (SndStreamSetVolume()) to change the volume of a simple sound.

The following sections take a look at the concepts introduced by the Sound Manager. For detailed API descriptions, and for more guidance with regard to the sampled data concepts presented here, see Chapter 2, "Sound Manager Reference."

Note that the Multimedia Library documented in Part II, "Multimedia Library," can also play sounds. Compared to the Sound Manager, the Multimedia Library is more complex to use, but provides more robust features and supports more sound formats, such as MP3, which is not supported by the current Sound Manager implementation. The Sound Manager is probably better to use if you want to play short or frequent sounds with a lightweight facility.

Simple Sound ^TOP^

There are three ways to play a simple sound:

  • You can play a single tone of a given pitch, amplitude, and duration by calling SndDoCmd().
  • You can play a pre-defined system sound ("Information," "Warning," "Error," and so on) with SndPlaySystemSound().
  • You can play a tune by passing in a Level 0 Standard MIDI File (SMF) through the SndPlaySmf() function. For example, the alarm sounds used in the built-in Date Book application are MIDI records stored in the System MIDI database. For information on MIDI and the SMF format, go to the official MIDI website, http://www.midi.org/ .

Sampled Sound ^TOP^

In the sampled sound facilities, there are two fundamental functions:

  • SndStreamCreate() opens and configures a new sampled sound "stream" from/into which you record/playback buffers of "raw" data. An alternate function, SndStreamCreateExtended(), lets you use variable-sized buffers.
  • SndPlayResource() plays sound data that's read from a (formatted) sound file. The function configures the playback stream for you, based on the format information in the sound file header. Currently, only uncompressed WAV and IMA ADPCM WAV formats are recognized. (Note that IMA ADPCM is also known as DVI ADPCM). SndPlayResource() is only used to play back sound; it can't be used for recording.

The Sound Manager also provides functions that let you set the volume (SndStreamSetVolume()) and stereo panning (SndStreamSetPan()) for individual recording and playback streams.

Simple vs. Sampled Sound ^TOP^

Comparing the two facilities, simple sound is easy to understand and requires very little programming: In most cases, you load up a structure, call a function, and out pops a beep. Unfortunately, the sound itself is primitive. (An example of simple sound programming is given in "Sound Preferences," below.)

Sampled sound, on the other hand, is (or can be) much more satisfying, but requires more planning than simple sound. How much more depends on what you're doing. Playing samples from a sound file isn't much more difficult than playing a simple sound, but you have to supply a sound file. Generating samples programmatically—and recording sound—requires more work: You have to implement a callback function that knows something about sound data.


IMPORTANT: One significant difference between simple sounds and sampled sounds is that they use different volume scales: Simple sound volumes are in the range [0, 64]; sampled sound volumes are in the range [0, 1024].

Sound Preferences ^TOP^

If you're adding short, "informative" sounds to your application, such as system beeps, alarms, and the like, you should first consider using the (simple) system sounds that are defined by Palm OS in the SndSysBeepType enum and played by SndPlaySystemSound().

If you want to create your own system-like sounds, you should at least respect the user's preferences settings with regard to sound volume. There are three sound preference constants:

  • prefSysSoundVolume is the default system volume.
  • prefGameSoundVolume is used for game sounds.
  • prefAlarmSoundVolume is used for alarms.

To apply a sound preference setting to a simple sound volume, you have to retrieve the setting and apply it yourself. For example, in Listing 1.1 we retrieve the alarm sound and use it to set the volume of a simple sound.

Listing 1.1  Playing a simple sound with the alarm volume


// Create a 'sound command' structure. This will encode the parameters of the 
// tone we want to generate. 
SndCommandType sndCommand; 
 
// Ask for the 'play a tone' command. 
sndCommand.cmd = sndCmdFreqDurationAmp; 
 
// Set the frequency and duration. 
sndCommand.param1 = 1760; 
sndCommand.param2 = 500; 
 
// Now get the alarm volume and set it in the struct. 
sndCommand.param3 = PrefGetPreference (prefAlarmSoundVolume); 
 
// Play the tone. 
SndDoCmd( 0, &sndCommand, true); 

The sampled sound API, on the other hand, provides volume constants (sndSystemVolume, sndGameVolume, and sndSysVolume) that look up a preference setting for you, as shown in Listing 1.2.

Listing 1.2  Playing a sampled sound with the alarm volume


// Point our sound data pointer to a record that contains WAV data (record 
// retrieval isn't shown).  
SndPtr soundData = MemHandleLock(...); 
 
// Play the data using the default alarm volume setting. 
SndPlayResource(soundData, sndAlarmVolume, sndFlagNormal); 
 
// Unlock the data. 
MemPtrUnlock(soundData); 

Standard MIDI Files ^TOP^

Although you can use a Level 0 Standard MIDI File to control simple sound generation, this doesn't imply broad support for MIDI messages; only key down, key up, and tempo change messages are recognized.

You can store your MIDI data in a MIDI database:

  • The database type sysFileTMidi identifies MIDI record databases.
  • The system MIDI database is further identified by the creator sysFileCSystem. The database holds a number of system alarm sounds.

You can add MIDI records to the system MIDI database, or you can store them in your own.

Each record in a MIDI database is a concatenation of a PalmSource-defined MIDI record header, the human-readable name of the MIDI data, and then the MIDI data itself. Figure 1.1 depicts a complete Palm OS MIDI record.

Figure 1.1  Palm OS Midi Record

To get to the track name, use an expression like this:


pName = (char*)hdrP + sndMidiRecHdrSize; 

The MIDI track name is null-terminated, even if it's empty. It's at least one byte long and at most sndMidiNameLength bytes long.

The code in Listing 1.3 creates a new MIDI record and adds it to the system MIDI database.

Listing 1.3  Adding a new MIDI record


// We need three things: A header, a name, and some data. We'll get the name 
// and data from somewhere, and create the header ourselves. 
char *midiName = ...; 
MemHandle midiData = ...; 
SndMidiRecHdrType midiHeader; 
 
// Database and record gadgetry. 
DmOpenRef database; 
MemHandle record; 
uint16_t *recordIndex = dmMaxRecordIndex; 
uint8_t* recordPtr; 
uint8_t* midiPtr; 
 
// MIDI header values: Always set the signature to sndMidiRecSignature, and 
// reserved to 0. bDataOffset is an offset from the beginning of the header to 
// the first byte of actual MIDI data. The name includes a null-terminator, 
// hence the '+ 1'. 
midiHeader.signature = sndMidiRecSignature; 
midiHeader.reserved = 0; 
midiHeader.bDataOffset = sizeof(SndMidiRecHdrType) + StrLen(midiName) + 1; 
 
// Open the database and allocate a record. 
database = DmOpenDatabaseByTypeCreator( sysFileTMidi, sysFileCSystem, 
               dmModeReadWrite | dmModeExclusive); 
record = DmNewRecord( database, &recordIndex,  
               midiHeader.bDataOffset + MemHandleSize(midiData)); 
 
// Lock the data and the record. 
midiDataPtr = MemHandleLock(midiData); 
recordPtr = MemHandleLock(record); 
 
// Write the MIDI header. 
DmWrite( recordPtr, 0, &smidiHeader, sizeof(midiHeader)); 
 
// Write the track name. 
DmStrCopy( recordPtr, sndMidiRecHrdSize, midiName); 
 
// Write the MIDI data. 
DmWrite( recordPtr, midiHeader.bDataOffset, midiDataPtr, MemHandleSize(midiData)); 
 
// Unlock the handles, release the record, close the database. 
MemHandleUnlock( midiData); 
MemHandleUnlock( record); 
DmReleaseRecord( database, recordIndex, 1); 
DmCloseDatabase( database); 

To retrieve a MIDI record, you can use the SndCreateMidiList() function if you know the record's creator, or you can use the Data Manager functions to iterate through all MIDI records.

Creating a Sound Stream ^TOP^

The sound stream API, part of the sampled sound facility, is the most flexible part of the Sound Manager. A sound stream sends sampled data to or reads sampled data from the sound hardware. There are several sound output streams and one input stream, all running (or potentially running) concurrently.


NOTE: The maximum number of output streams is dependent on system resources. The default number is 18, but device manufacturers can change that.

To use a sound stream, you have to tell it what sort of data you're going to give it or that you expect to get from it. All of the sound format information that you need to supply to set up the stream—data quantization, sampling rate, channel count, and so on—is passed in the SndStreamCreate() or SndStreamCreateExtended() function.

You also have to pass the function a pointer to a callback function (see SndStreamBufferCallback() or SndStreamVariableBufferCallback()); implementing this function is where you'll be doing most of your work. When you tell your stream to start running (SndStreamStart()), the callback function is called automatically, once per buffer of data. If you're operating on an input stream (in other words, if you're recording), your callback function can do something with the data and then should return before the next buffer shows up. Output stream callbacks do the opposite—they fill the buffer with data.

Because of the real-time nature of audio playback, the callbacks must operate as quickly as possible. There can be more than one stream competing for attention. Note that all callbacks run in their own threads.

The formats that are supported by the sampled sound functions are described in the functions themselves.

Summary of Sound Manager ^TOP^