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

11    Palm System Support

Palm OS® Programmer's Companion

Volume I

     

In this chapter, you learn how to work with the miscellaneous supporting functionality that the Palm OS® system provides, such as sound, time, and floating-point operations. Most parts of the Palm OS are controlled by a manager, which is a group of functions that work together to implement a certain functionality. As a rule, all functions that belong to one manager use the same prefix and work together to implement a certain aspect of functionality.

This chapter discusses these topics:

Features ^TOP^

A feature is a 32-bit value that has special meaning to both the feature publisher and to users of that feature. Features can be published by the system or by applications. A complete list of all Palm OS system features can be found under "Feature Constants" of the Palm OS Programmer's API Reference.

Each feature is identified by a feature creator and a feature number:

  • The feature creator is a unique creator registered with PalmSource, Inc. You usually use the creator type of the application that publishes the feature.
  • The feature number is any 16-bit value used to distinguish between different features of a particular creator.

Once a feature is published, it remains present until it is explicitly unregistered or the handheld is reset. A feature published by an application sticks around even after the application quits.

This section introduces the feature manager by discussing these topics:

The System Version Feature ^TOP^

An example for a feature is the system version. This feature is published by the system and contains a 32-bit representation of the system version. The system version has a feature creator of sysFtrCreator and a feature number of sysFtrNumROMVersion). Currently, the different versions of the system software have the following numbers:

0x01003001

Palm OS 1.0

0x02003000

Palm OS 2.0

0x03003000

Palm OS 3.0

0x03103000

Palm OS 3.1

0x03203000

Palm OS 3.2

0x03503000

Palm OS 3.5

0x04003000

Palm OS 4.0

Rather than hard wiring an obscure constant like one of the above into your code, however, you can use the sysMakeROMVersion macro (defined in SystemMgr.h) to construct a version number for comparison purposes. It takes five parameters:

  • Major version number
  • Minor version number
  • Fix level
  • Build stage (either sysROMStageDevelopment,
    sysROMStageAlpha, sysROMStageBeta, or
    sysROMStageRelease)
  • Build number

The fix level and build number parameters are normally set to zero, while build stage is usually set to sysROMStageRelease. Simply check to see whether sysFtrNumROMVersion is greater than or equal to the version number constructed with sysMakeROMVersion, as shown here:


// See if we're on ROM version 3.1 or later. 
FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion); 
if (romVersion >= sysMakeROMVersion(3, 1, 0, 
sysROMStageRelease, 0)) { 
.... 
} 

Other system features are defined in SystemMgr.h. System features are stored in a feature table in the ROM. (In Palm OS 3.1 and higher, the contents of this table are copied into the RAM feature table at system startup.) Checking for the presence of system features allows an application to be compatible with multiple versions of the system by refining its behavior depending on which capabilities are present or not. Future hardware platforms may lack some capabilities present in the first platform, so checking the system version feature is important.


IMPORTANT: For best results, we recommend that you check for specific features rather than relying on the system version number to determine if a specific API is available. For more details on checking for features, see the appendix Compatibility Guide in Palm OS Programmer's API Reference.

Application-Defined Features ^TOP^

Applications may find the feature manager useful for their own private use. For example, an application may want to publish a feature that contains a pointer to some private data it needs for processing launch codes. Because an application's global data is not generally available while it processes launch codes, using the feature manager is usually the easiest way for an application to get to its data.

The feature manager maintains one feature table in the RAM as well as the feature table in the ROM. Application-defined features are stored in the RAM feature table.

Using the Feature Manager ^TOP^

To check whether a particular feature is present, call FtrGet and pass it the feature creator and feature number. If the feature exists, FtrGet returns the 32-bit value of the feature. If the feature doesn't exist, an error code is returned.

To publish a new feature or change the value of an existing one, call FtrSet and pass the feature creator, number, and the 32-bit value of the feature. A published feature remains available until it is explicitly removed by a call to FtrUnregister or until the system resets; simply quitting an application doesn't remove a feature published by that application.

Call FtrUnregister to remove features that were created by calling FtrSet.

You can get a complete list of all published features by calling FtrGetByIndex repeatedly. Passing an index value starting at 0 to FtrGetByIndex and incrementing repeatedly by 1 eventually returns all available features. FtrGetByIndex accepts a parameter that specifies whether to search the ROM feature table or RAM feature table. Note that in Palm OS version 3.1 and higher, the contents of the ROM table are copied into the RAM table at system startup; thus the RAM table serves the entire system.

Feature Memory ^TOP^

Palm OS 3.1 adds support for feature memory. Feature memory provides quick, efficient access to data that persists between invocations of an application. The values stored in feature memory persist until the handheld is reset or until you explicitly free the memory. Feature memory is memory allocated from the storage heap. Thus, you write to feature memory using DmWrite, which means that writing to feature memory is no faster than writing to a database. However, feature memory can provide more efficient access to that data in certain circumstances.

To allocate a chunk of feature memory, call FtrPtrNew, specifying a feature creator, a feature number, the number of bytes to allocate, and a location where the feature manager can return a pointer to the newly allocated memory chunk. For example:


FtrPtrNew(appCreator, 
myFtrMemFtr, 32, &ftrMem); 

Elsewhere in your application, you can obtain the pointer to the feature memory chunk using FtrGet.


NOTE: Starting with Palm OS 3.5 FtrPtrNew allows allocating chunks larger than 64KB. Do keep in mind standard issues with allocating large chunks of memory: there might not be enough contiguous space, and it can impact system performance.

Feature memory is considered a performance optimization. The conditions under which you'd use it are not common, and you probably won't find them in a typical application. You use feature memory in code that:

  • Is executed infrequently
  • Does not have access to global variables
  • Needs access to data whose contents change infrequently and that cannot be stored in a 32-bit feature value

For example, suppose you've written a function that is called in response to a launch code, and you expect to receive this launch code frequently. Suppose that function needs access to the application's preferences database. At the start of the function, you'd need to open the database and read the data from it. If the function is called frequently, opening the database each time can be a drain on performance. Instead, you can allocate a chunk of feature memory and write the values you need to that chunk. Because the chunk persists until the handheld is reset, you only need to open the database once. Listing 11.1 illustrates this example.

Listing 11.1  Using feature memory


MyAppPreferencesType prefs;  
 
if (FtrGet(appCreator, myPrefFtr, (UInt32*)&prefs) != 0) { 
 
// Feature memory doesn't exist, so allocate it. 
FtrPtrNew(appCreator, myPrefFtr, 32, &thePref); 
 
// Load the preferences database.  
PrefGetAppPreferences (appCreator, prefID, &prefs,  
sizeof(prefs), true); 
 
// Write it to feature memory.  
DmWrite(thePref, 0, &prefs, sizeof(prefs)); 
} 
// Now prefs is guaranteed to be defined. 

Another potential use of feature memory is to "publish" data from your application or library to other applications when that data doesn't fit in a normal 32-bit feature value. For example, suppose you are writing a communications library and you want to publish an icon that client applications can use to draw the current connection state. The library can use FtrPtrNew to allocate a feature memory chunk and store an icon representing the current state in that location. Applications can then use FtrGet to access the icon and pass the result to WinDrawBitmap to display the connection state on the screen.

Preferences ^TOP^

The Preferences Manager handles both system-wide preferences and application-specific preferences. The Preferences Manager maintains preferences in two separate databases:

  • The "saved" preferences database contains preferences that are backed up during a HotSync operation. There is one "saved" preferences database that all applications use. This database contains all system-wide preferences as well as application-specific preferences.
  • The "unsaved" preferences database contains application-specific preferences that are not to be backed up during a HotSync operation. There is one "unsaved" preferences database that all application use.

This section describes how to obtain and set values for each of these preferences databases. It covers:

Accessing System Preferences ^TOP^

The system preferences specify how users want their Palm Powered handhelds to behave. For example, system preferences specify how dates and times are displayed and whether the system plays a sound when an alarm fires. These values are typically set using the built-in Preferences or Security application. Applications should, as a rule, respect the values stored in the system preferences.

To obtain the value of a system preference, use the PrefGetPreference function and pass one of the SystemPreferencesChoice enum constants. For example, if an application's user interface displays the current date and time, it could do the following to find out how the user wants the date and time displayed:


TimeFormatType timeFormat = (TimeFormatType) 
PrefGetPreference(prefTimeFormat); 
DateFormatType dateFormat = (DateFormatType) 
PrefGetPreference(prefDateFormat); 


WARNING! Do not confuse PrefGetPreference with PrefGetPreferences. The latter function is obsolete and retrieves the 1.0 version of the system preferences structure.

Note that the PrefGetPreference function by default returns a UInt32 value. This return value must be cast to the appropriate type for the preference being returned.

Also note that the system preferences structure has been updated many times and maintains its own version information. Each Palm OS release that modifies the system preferences structure adds its new values to the end and increments the structure's version number, as shown in Table 11.1.

Table 11.1  System preference version numbers

Palm OS
Version

System Preference Version

2.0

2

3.0

3

3.0

4

3.1

5

3.2

6

3.3

7

3.5

8

4.0

9

Palm OS Garnet version 5.0

10

Palm OS Garnet version 5.1

11

Palm OS Garnet version 5.3SC

12

To learn which preferences were added in which version, as well as the return type expected for each preference, see Table 48.1 on page900 in the Palm OS Programmer's API Reference.

To maintain backward compatibility, check the preference version number before checking the value of any preference added after Palm OS 2.0. For example, Palm OS 4.0 added a preference that allows you to access the handheld's locale information (the country and language) as an LmLocaleType structure. Before you try to access that preference, you should check the preference version, as shown in Listing 11.2.

Listing 11.2  Checking the system preference version


LmLocaleType currentLocale; 
CountryType currentCountry;  
if (PrefGetPreference(prefVersion) >= preferenceDataVer9) { 
currentLocale = (LmLocaleType)
PrefGetPreference(prefLocale); 
} else { /* make do with the country */ 
currentCountry = (CountryType) 
PrefGetPreference(prefCountry); 
} 

In some cases, a newer preference is intended to replace an existing preference. For example, the prefAutoOffDuration preference is replaced by prefAutoOffDurationSecs in version 8 of the preference structure. The older preference stored the auto-off time in minutes, and the newer one stores the time in seconds. If you use prefAutoOffDuration in Palm OS 4.0, the system still returns the current auto-off time in minutes; however, to obtain this value, the system converts the value in seconds to minutes and rounds the result if necessary. You'll receive a more precise value if you use prefAutoOffDurationSecs.

Setting System Preferences ^TOP^

Occasionally, an application may need to set the value of a system-wide preference. It is strongly recommended that you not override the system preferences without user input.

For example, suppose you are writing a replacement for the built-in Address Book application. The Preferences application contains a panel where the user can remap the Address Book hard key to open any application they choose. However, you want to make it more convenient for your users to remap the Address Book button, so you might display an alert that asks first-time users if they want the button remapped. If they tap Yes, then you should call PrefSetPreference with the new value. The code might look like the following:

Listing 11.3  Setting a system preference


if (PrefGetPreference(prefHard2CharAppCreator !=  
myAppCreatorId)) { 
if (FrmAlert(MakeMeTheDefaultAlert) == 0) {  
/* user pressed Yes */ 
PrefSetPreference(prefHard2CharAppCreator,  
myAppCreatorId); 
} 
} 


WARNING! Do not confuse PrefSetPreference with PrefSetPreferences. The latter function is obsolete and sets the entire system preferences structure for version 1.0.

Setting Application-Specific Preferences ^TOP^

You can use the Preferences Manager to set and retrieve preferences specific to your application. You do this by storing the preferences in one of two databases: the "saved" preferences database or the "unsaved" preferences database.

To write application preferences, you use PrefSetAppPreferences. To read them back in, you use PrefGetAppPreferences. Typically, you write the preferences in response to the appStopEvent when control is about to pass to another application. You read the preferences in response to a normal launch.

PrefSetAppPreferences and PrefGetAppPreferences take roughly the same parameters: the application creator ID, a preference ID that uniquely identifies this preference resource, a pointer to a structure that holds the preference values, the size of the preferences structure, and a Boolean that indicates whether the "saved" or the "unsaved" preferences database is to be used. PrefSetAppPreferences also takes a version number for the preference structure. This value is the return value for PrefGetAppPreferences.

The following sections discuss the issues involved in using application-specific preferences:

When to Use Application Preferences

You use application preferences to store state specific to your application that should persist across invocations of your application. For example, the built-in applications store information about the last form and the last record or records displayed before control switched to another application. This way, the user can be returned to the same view when he or she goes back to that application.

You can also use preferences for other values. You might allow the user to customize the way the application behaves and store such information in the preferences database. You might also use the preferences database as a way to share information with other applications.

Make sure that the preference values you choose are as concise as possible. In games, for example, it is often tempting to store a bitmap for the current state of the screen. Such a bitmap is over 25KB on a color handheld, and it is therefore best avoided. Instead, it is better to store items that let you recreate the current state, such as the player's position in pixels and the current level.

There are other ways to store values pertinent to your application. For example, you can store a single value as a feature using the Feature Manager. The differences between storing application value as preferences and storing application values as features are:

  • Preferences (including those stored in the "unsaved" database) survive a soft reset because they reside in the storage heap. Features are deleted upon a soft reset. For this reason, preferences are more appropriate for storing application state than are features.
  • An application preference is a database record, so it has a size limit of 64KB. Multiple application preferences are allowed. The features that are supported by all releases of Palm OS have a maximum size of 4KB.

    In Palm OS 3.1 and higher, you can create features greater than 4KB by using feature memory. Feature memory has a maximum size of 64KB before Palm OS 3.5. Palm OS 3.5 and higher allows allocating a chunk of feature memory that is larger than 64KB. However, feature memory is intended to be used in different situations than an application preference is. See the section "Feature Memory" for more information.

Instead of storing application state values as preferences or features, you could also use a database that your application creates and maintains itself. If you choose this method of storing application preference values, you must write your own functions to read the preferences from the database and write the preferences to the database. If you want the preferences backed up, you need to set the backup bit. However, there may be cases where using your own database has advantages. See "Which Preferences Database to Use".

How to Store Preferences

Most applications store a single preference structure under a single preference resource ID. When the application receives an appStopEvent, it writes the entire structure to the resource using PrefSetAppPreferences. When it receives a sysAppLaunchCmdNormalLaunch, it reads the structure back in using PrefGetAppPreferences.

Storing a single preference structure in the database is a convention that most applications follow because it is convenient to access all preferences at once. The Preferences Manager does allow an application to store more than one preference resource. This requires more calls to PrefSetAppPreferences and PrefGetAppPreferences, but you may find it more convenient to use several preference resources if you have several variable-length preferences.

Which Preferences Database to Use

Both PrefGetAppPreferences and PrefSetAppPreferences take a Boolean value that indicates whether the value is to be read from and written to the "saved" or the "unsaved" preferences database. To write the preference to the "saved" preferences database, use true. To write to the "unsaved" preferences database, use false.

The only difference between the two databases is that the "saved" preferences database is backed up when a user performs the HotSync operation, and the "unsaved" preferences database is not backed up by default. (The user can use a third-party tool to set the backup bit in the "unsaved" preferences database, which would cause it to be backed up.) Both the "saved" and the "unsaved" preferences reside in the storage heap and thus persist across soft resets. The only way that preferences are lost is if a hard reset is performed.

Use the "saved" preferences only for items that must be restored after a hard reset, and use the "unsaved" preferences for the current state of the application. For example, if your application has a registration code, you might write that to the "saved" preferences database so that the user does not have to look up the registration code and re-enter it after a hard reset. However, such items as the current form being displayed and the current database record being displayed can be lost, so they are written to the "unsaved" preferences database. For games, you might write the high score to the "saved" preferences database and any information about the current game to the "unsaved" preferences database.

It is important to use the "saved" preferences database sparingly. Any time that any application stores or changes a preference in the "saved" preferences database, the entire database is backed up during the next HotSync operation. For users with a large number of applications, this practice can seriously impact the time that it takes to perform a HotSync operation.

Listing 11.4 shows the preferences structures and the StopApplication function from the HardBall application. The HardBallPreferenceType, which is written to the "saved" preferences database, only stores the high score information and accumulated time. All other preferences are stored in GameStatusType, which is written to the "unsaved" preferences database.

Listing 11.4  Saving application-specific preferences


typedef struct { 
SavedScore highScore[highScoreMax]; 
UInt8 lastHighScore; 
UInt8 startLevel; 
UInt32accumulatedTime; 
} HardBallPreferenceType; 
 
typedef struct { 
enum gameProgressstatus; 
UInt8periodLength; 
UInt32 nextPeriodTime; 
UInt32 periodsToWait; 
Booleanpaused; 
UInt32pausedTime; 
BrickTypebrick[rowsOfBricks][columnsOfBricks]; 
UInt8bricksRemaining; 
UInt8 level; 
WorldState last; 
WorldState next; 
RemovedBrick brokenBricks[brokenBricksMax]; 
Int16brokenBricksCount; 
UInt8 ballsRemaining; 
Boolean movePaddleLeft; 
Boolean movePaddleRight; 
SoundTypesoundToMake; 
Int8soundPeriodsRemaining; 
Int32scoreToAwardBonusBall; 
Boolean lowestHighScorePassed; 
Boolean highestHighScorePassed; 
Boolean gameSpedUp; 
BooleancheatMode; 
UInt32 startTime; 
} GameStatusType; 
 
HardBallPreferenceType Prefs; 
static GameStatusType GameStatus; 
 
static void StopApplication (void) 
{ 
... 
// Update the time accounting. 
Prefs.accumulatedTime += (TimGetTicks() -  
GameStatus.startTime); 
 
// If we are saving a game resuming (it hasn't started  
// playing yet) then preserve the game status. 
if (GameStatus.status == gameResuming) { 
GameStatus.status = SavedGameStatus; 
} 
 
// Save state/prefs. 
PrefSetAppPreferences (appFileCreator, appPrefID,  
appPrefVersion, &Prefs, sizeof (Prefs), true); 
 
PrefSetAppPreferences (appFileCreator, appSavedGameID,  
appSavedGameVersion, &GameStatus, sizeof (GameStatus),  
false); 
 
// Close all the open forms. 
FrmCloseAllForms (); 
} 

If you have a large amount of preference data that must be backed up during a HotSync operation and is frequently changed, you could, as a performance optimization, store the preferences in a database that your own application creates and maintains rather than in the "saved" preferences database. This saves the user from having to have the entire "saved" preferences database backed up on every HotSync operation. The disadvantage of this method of saving application preferences is that you must write all code to maintain the database and to retrieve information from it.

Updating Preferences Upon a New Release

When you update your application, you may have new items that you want to store in the preferences database. You may choose to write a separate preference record to the database. However, it is better to update the current preference structure, size permitting.

The PrefSetAppPreferences and PrefGetAppPreferences functions use a versioning system that allows you to update an existing preference structure. To use it, keep track of the version number that you pass to PrefSetAppPreferences. Add any new preferences to the end of the preferences structure, and then increment the version number. You might use a macro for this purpose:


#define CurrentPrefsVersion 2 

When a user launches the new version of the application, PrefGetAppPreferences is called before PrefSetAppPreferences. The PrefGetAppPreferences function returns the version number of the preference structure that it retrieved from the database. For example, if the new version is version 2, PrefGetAppPreferences returns 1 the first time that version 2 is run. If the returned version does not match the current version, you know that the user does not have values for the new preferences introduced in version 2. You can then decide to provide default values for those new preferences.

The first time any version of your application is run, PrefGetAppPreferences returns noPreferenceFound. This indicates that the user does not have any preferences for the current application and the application must supply default values for the entire preferences structure. Listing 11.5 shows how the Datebook handles retrieving the version number from PrefGetAppPreferences.

Listing 11.5  Checking the preference version number


#define datebookPrefsVersionNum 4 
 
Int16 DatebookLoadPrefs (DatebookPreferenceType* prefsP) 
{ 
UInt16 prefsSize; 
Int16 prefsVersion = noPreferenceFound; 
Boolean haveDefaultFont = false; 
UInt32 defaultFont; 
 
ErrNonFatalDisplayIf(!prefsP, "null prefP arg"); 
 
 
// Read the preferences / saved-state information.  Fix-up if no prefs or  
// older/newer version 
prefsSize = sizeof (DatebookPreferenceType); 
prefsVersion = PrefGetAppPreferences (sysFileCDatebook, datebookPrefID,  
prefsP, &prefsSize, true); 
 
// If the preferences version is from a future release (as can happen when  
// going back and syncing to an older version of the device), treat it the  
// same as "not found" because it could be significantly different 
if ( prefsVersion > datebookPrefsVersionNum ) 
prefsVersion = noPreferenceFound; 
 
if ( prefsVersion == noPreferenceFound ) { 
// Version 1 and 2 preferences 
prefsP->dayStartHour = defaultDayStartHour; 
prefsP->dayEndHour = defaultDayEndHour; 
prefsP->alarmPreset.advance = defaultAlarmPresetAdvance; 
prefsP->alarmPreset.advanceUnit = defaultAlarmPresetUnit; 
prefsP->saveBackup = defaultSaveBackup; 
prefsP->showTimeBars = defaultShowTimeBars; 
prefsP->compressDayView = defaultCompressDayView; 
prefsP->showTimedAppts = defaultShowTimedAppts; 
prefsP->showUntimedAppts = defaultShowUntimedAppts; 
prefsP->showDailyRepeatingAppts =  
defaultShowDailyRepeatingAppts; 
 
// We need to set up the note font with a default value for the system. 
FtrGet(sysFtrCreator, sysFtrDefaultFont, &defaultFont); 
haveDefaultFont = true; 
 
prefsP->v20NoteFont = (FontID)defaultFont; 
} 
 
if ((prefsVersion == noPreferenceFound) || (prefsVersion <  
datebookPrefsVersionNum)) { 
// Version 3 preferences 
prefsP->alarmSoundRepeatCount = defaultAlarmSoundRepeatCount; 
prefsP->alarmSoundRepeatInterval = defaultAlarmSoundRepeatInterval; 
prefsP->alarmSoundUniqueRecID = defaultAlarmSoundUniqueRecID; 
prefsP->noteFont = prefsP->v20NoteFont; 
 
// Fix up the note font if we copied from older preferences. 
if ((prefsVersion != noPreferenceFound) && (prefsP->noteFont ==  
largeFont)) 
prefsP->noteFont = largeBoldFont; 
 
if (!haveDefaultFont) 
FtrGet(sysFtrCreator, sysFtrDefaultFont, &defaultFont); 
 
prefsP->apptDescFont = (FontID)defaultFont; 
} 
 
if ((prefsVersion == noPreferenceFound) || (prefsVersion <  
datebookPrefsVersionNum)) { 
// Version 4 preferences 
prefsP->alarmSnooze = defaultAlarmSnooze; 
} 
 
return prefsVersion; 
} 

Sound ^TOP^

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

  • Simple sound: Single voice, monophonic, square-wave sound synthesis, useful for system beeps. This is the traditional (pre-Palm OS Garnet) Palm OS sound.
  • Sampled sound: Stereo, multi-format, sampled data recording and playback (new in Palm OS Garnet). Sampled sounds can be generated programmatically or read from a soundfile.

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 50, "Sound Manager."

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) through 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. MIDI support is included with Palm OS 3.0 and later. For information on MIDI and the SMF format, go to the official MIDI website, http://www.midi.org.

Sampled Sound ^TOP^

Over in the sampled sound facilities, there are two fundamental functions:

  • SndStreamCreate opens a new sampled sound "stream" from/into which you record/playback buffers of "raw" data. The trick is that you first have to configure the stream to tell it how to interpret the data. (An alternate function, SndStreamCreateExtended, lets you declare an encoding format.)
  • SndPlayResource is used to play sound data that's read from a (formatted) soundfile. The function configures the playback stream for you, based on the format information in the soundfile 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 and stereo panning for individual recording and playback streams. See SndStreamSetVolume and SndStreamSetPan.

Simple vs Sampled ^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 soundfile isn't much more difficult than playing a simple sound, but you have to supply a soundfile. 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 [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 the Palm OS, as listed in the reference documentation for 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. In Palm OS 3.0 and later, 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, here we retrieve the alarm sound volume and use it to set the volume of a simple sound:

Listing 11.6  Obtaining the alarm sound volume preference


/* Create a 'sound command' structure. This will encode the parameters of the
tone we want to generate. 
*/ 
SndCommandTypesndCommand; 
 
/* 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:

Listing 11.7  Using volume constants with sampled sound APIs


/* 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); 

For greatest compatibility with multiple versions of the sound preferences mechanism, your application should check the version of Palm OS on which it is running. See "The System Version Feature" for more information.

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 11.1 depicts a complete Palm OS® MIDI record.

Figure 11.1  Palm OS Midi Record

Note that to get to the track name, use a SndMidiRecType structure; it encapsulates both the record header and the track name. 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 following code creates a new MIDI record and adds it to the system MIDI database.

Listing 11.8  Adding a new MIDI record to the system MIDI database


/* 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; 
MemHandler record; 
UInt16 *recordIndex = dmMaxRecordIndex; 
UInt8* recordPtr; 
UInt8* 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, &midiHeader, sizeof(midiHeader)); 
 
/* Write the track name. */ 
DmStrCopy( recordPtr, ((Uint32)(&((SndMidiRecType *)0)->name)), 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 15 sound output streams and one input stream, all running (or potentially running) concurrently.

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 is expected to empty the buffer, do something with the data, and then return before the next buffer shows up. Output stream callbacks do the opposite—they fill the buffer with data.

Because of the amount of data involved, the callbacks must operate as quickly as possible. This is particularly important for output stream callbacks: Not only can there be more than one stream competing for attention, but all output callbacks run in the same task (which is created and managed by the Sound Manager). If the callback for (output) stream A takes too long to fill the stream with data, the callbacks for stream B, C, and so on, will starve. Starving threads make glitchy sounds.

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

System Boot and Reset ^TOP^

Any reset is normally performed by sticking a bent-open paper clip into the small hole in the back of the handheld. This hole, known as the "reset switch" is above and to the right of the serial number sticker (on Palm III handhelds). Depending on additional keys held down, the reset behavior varies, as follows:

Soft Reset ^TOP^

A soft reset clears all of the dynamic heap (Heap 0, Card 0). The storage heaps remain untouched. The operating system restarts from scratch with a new stack, new global variables, restarted drivers, and a reset communication port. All applications on the handheld receive a sysAppLaunchCmdSystemReset launch code.

Soft Reset + Up Arrow ^TOP^

Holding the up-arrow down while pressing the reset switch with a paper clip causes the same soft reset logic with the following two exceptions:

  • The sysAppLaunchCmdSystemReset launch code is not sent to applications. This is useful if there is an application on the handheld that crashes upon receiving this launch code (not uncommon) and therefore prevents the system from booting.
  • The OS won't load any system patches during startup. This is useful if you have to delete or replace a system patch database. If the system patches are loaded and therefore open, they cannot be replaced or deleted from the system.

Hard Reset ^TOP^

A hard reset is performed by pressing the reset switch with a paper clip while holding down the power key. This has all the effects of the soft reset. In addition, the storage heaps are erased. As a result, all programs, data, patches, user information, etc. are lost. A confirmation message is displayed asking the user to confirm the deletion of all data.

The sysAppLaunchCmdSystemReset launch code is sent to the applications at this time. If the user selected the "Delete all data" option, the digitizer calibration screen comes up first. The default databases for the four main applications is copied out of the ROM.

If you hold down the up arrow key when the "Delete all data" message is displayed, and then press the other four application buttons while still holding the up arrow key, the system is booted without reading the default databases for the four main applications out of ROM.

System Reset Calls ^TOP^

The system manager provides support for rebooting the Palm Powered handheld. It calls SysReset to reset the handheld. This call does a soft reset and has the same effect as pressing the reset switch on the unit. Normally applications should not use this call.

SysReset is used, for example, by the Sync application. When the user copies an extension onto the Palm Powered handheld, the Sync application automatically resets the handheld after the sync is completed to allow the extension to install itself.

Hardware Interaction ^TOP^

Palm OS differs from a traditional desktop system in that it's never really turned off. Power is constantly supplied to essential subsystems and the on/off key is merely a way of bringing the handheld in or out of low-power mode. The obvious effect of pressing the on/off key is that the LCD turns on or off. When the user presses the power key to turn the handheld off, the LCD is disabled, which makes it appear as if power to the entire unit is turned off. In fact, the memory system, real-time clock, and the interrupt generation circuitry are still running, though they are consuming little current.

This section looks at Palm OS power management, discussing the following topics:

Palm OS Power Modes ^TOP^

To minimize power consumption, the operating system dynamically switches between three different modes of operation: sleep mode, doze mode, and running mode. The system manager controls transitions between different power modes and provides an API for controlling some aspects of the power management.

  • In sleep mode, the handheld looks like it's turned off: the display is blank, the digitizer is inactive, and the main clock is stopped. The only circuits still active are the real-time clock and interrupt generation circuitry.

    The handheld enters this mode when there is no user activity for a number of minutes or when the user presses the off button. The handheld comes out of sleep mode only when there is an interrupt, for example, when the user presses a button.

    To enter sleep mode, the system puts as many peripherals as possible into low-power mode and sets up the hardware so that an interrupt from any hard key or the real-time clock wakes up the system. When the system gets one of these interrupts while in sleep mode, it quickly checks that the battery is strong enough to complete the wake-up and then takes each of the peripherals, for example, the LCD, serial port, and timers, out of low-power mode.

  • In doze mode, the main clock is running, the handheld appears to be turned on, the LCD is on, and the processor's clock is running but it's not executing instructions (that is, it's halted). When the processor receives an interrupt, it comes out of halt and starts processing the interrupt.

    The handheld enters this mode whenever it's on but has no user input to process.

    The system can come out of doze mode much faster than it can come out of sleep mode since none of the peripherals need to be woken up. In fact, it takes no longer to come out of doze mode than to process an interrupt. Usually, when the system appears on, it is actually in doze mode and goes into running mode only for short periods of time to process an interrupt or respond to user input like a pen tap or key press.

  • In running mode, the processor is actually executing instructions.

    The handheld enters this mode when it detects user input (like a tap on the screen) while in doze mode or when it detects an interrupt while in doze or sleep mode. The handheld stays in running mode only as long as it takes to process the user input (most likely less than a second), then it immediately reenters doze mode. A typical application puts the system into running mode only about 5% of the time.

To maximize battery life, the processor on the Palm Powered handheld is kept out of running mode as much as possible. Any interrupt generated on the handheld must therefore be capable of "waking" up the processor. The processor can receive interrupts from the serial port, the hard buttons on the case, the button on the cradle, the programmable timer, the memory module slot, the real-time clock (for alarms), the low-battery detector, and any built-in peripherals such as a pager or modem.

Guidelines for Application Developers ^TOP^

Normally, applications don't need to be aware of power management except for a few simple guidelines. When an application calls EvtGetEvent to ask the system for the next event to process, the system automatically puts itself into doze mode until there is an event to process. As long as an application uses EvtGetEvent, power management occurs automatically. If there has been no user input for the amount of time determined by the current setting of the auto-off preference, the system automatically enters sleep mode without intervention from the application.

Applications should avoid providing their own delay loops. Instead, they should use SysTaskDelay, which puts the system into doze mode during the delay to conserve as much power as possible. If an application needs to perform periodic work, it can pass a time out to EvtGetEvent; this forces the unit to wake up out of doze mode and to return to the application when the time out expires, even if there is no event to process. Using these mechanisms provides the longest possible battery life.

Power Management Calls ^TOP^

The system calls SysSleep to put itself immediately into low-power sleep mode. Normally, the system puts itself to sleep when there has been no user activity for the minimum auto-off time or when the user presses the power key.

The SysSetAutoOffTime routine changes the auto-off time value. This routine is normally used by the system only during boot, and by the Preferences application. The Preferences application saves the user preference for the auto-off time in a preferences database, and the system initializes the auto-off time to the value saved in the preferences database during boot. While the auto-off feature can be disabled entirely by calling SysSetAutoOffTime with a time-out of 0, doing this depletes the battery.

The current battery level and other information can be obtained through the SysBatteryInfo routine. This call returns information about the battery, including the current battery voltage in hundredths of a volt, the warning thresholds for the low-battery alerts, the battery type, and whether external power is applied to the unit. This call can also change the battery warning thresholds and battery type.

The Microkernel ^TOP^

Palm OS has a preemptive multitasking kernel that provides basic task management.

Most applications don't need the microkernel services because they are handled automatically by the system. This functionality is provided mainly for internal use by the system software or for certain special purpose applications.

In this version of the Palm OS, there is only one user interface application running at a time. The User Interface Application Shell (UIAS) is responsible for managing the current user-interface application. The UIAS launches the current user-interface application as a subroutine and doesn't get control back until that application quits. When control returns to the UIAS, the UIAS immediately launches the next application as another subroutine. See "Power Management Calls" for more information.

Usually, the UIAS is the only task running. Occasionally though, an application launches another task as a part of its normal operation. One example of this is the Sync application, which launches a second task to handle the serial communication with the desktop. The Sync application creates a second task dedicated to the serial communication and gives this task a lower priority than the main user-interface task. The result is optimal performance over the serial port without a delay in response to the user-interface controls.

Normally, there is no user interaction during a sync, so that the serial communication task gets all of the processor's time. However, if the user does tap on the screen, for example, to cancel the sync, the user-interface task immediately processes the tap, since it has a higher priority. Alternatively, the Sync application could have been written to use just one task, but then it would have to periodically poll for user input during the serial communication, which would hamper performance and user-interface response time.


NOTE: Only system software can launch a separate task. The multi-tasking API is not available to developer applications.

Retrieving the ROM Serial Number ^TOP^

Some Palm handhelds, beginning with the Palm III product, hold a 12-digit serial number that identifies the handheld uniquely. (Earlier handhelds do not have this identifier.) The serial number is held in a displayable text buffer with no null terminator. The user can view the serial number in the Application Launcher application. (The pop-up version of the Launcher does not display the serial number.) The Application Launcher also displays to the user a checksum digit that you can use to validate user entry of the serial number.

To retrieve the ROM serial number programmatically, pass the sysROMTokenSnum selector to the SysGetROMToken function. If the SysGetROMToken function returns an error, or if the returned pointer to the buffer is NULL, or if the first byte of the text buffer is 0xFF, then no serial number is available.

The DrawSerialNumOrMessage function shown in Listing 11.9 retrieves the ROM serial number, calculates the checksum, and draws both on the screen at a specified location. If the handheld has no serial number, this function draws a message you specify. This function accepts as its input a pair of coordinates at which it draws output, and a pointer to the message it draws when a serial number is not available.

Listing 11.9  DrawSerialNumOrMessage


static void DrawSerialNumOrMessage(Int16x, Int16y, Char*
noNumberMessage) 
{ 
Char* bufP; 
UInt16* bufLen; 
Err retval; 
Int16count; 
UInt8checkSum; 
CharchecksumStr[2]; 
// holds the dash and the checksum digit 
 
retval = SysGetROMToken (0, sysROMTokenSnum,
(UInt8**) &bufP, 
&bufLen); 
if ((!retval) && (bufP) && ((UInt8) *bufP != 0xFF)) { 
// there's a valid serial number! 
// Calculate the checksum:  Start with zero, add each 
digit,
// then rotate the result one bit to the left and 
repeat. 
checkSum = 0; 
for (count=0; count<bufLen; count++) { 
checkSum += bufP[count]; 
checkSum = (checkSum<<1) | ((checkSum & 0x80) >> 
7); 
            } 
// Add the two hex digits (nibbles) together, +2 
// (range: 2 - 31 ==> 2-9, A-W) 
// By adding 2 to the result before converting to 
ascii,
// we eliminate the numbers 0 and 1, which can be
// difficult to distinguish from the letters O and I.
checkSum = ((checkSum>>4) & 0x0F) + (checkSum & 0x0F) + 
2; 
 
// draw the serial number and find out how wide it was
WinDrawChars(bufP, bufLen, x, y);
x += FntCharsWidth(bufP, bufLen); 
 
// draw the dash and the checksum digit right after it
checksumStr[0] = '-';
checksumStr[1] = 
((checkSum < 10) ? (checkSum +'0'):(checkSum -10 
+'A'));
WinDrawChars (checksumStr, 2, x, y);
} 
else // there's no serial number
// draw a status message if the caller provided one
if (noNumberMessage)
WinDrawChars(noNumberMessage, 
StrLen(noNumberMessage),x, y); 
} 

Time ^TOP^

The Palm Powered handheld has a real-time clock and programmable timer as part of the 68328 processor. The real-time clock maintains the current time even when the system is in sleep mode (turned off). It's capable of generating an interrupt to wake the handheld when an alarm is set by the user. The programmable timer is used to generate the system tick count interrupts (100 times/second) while the processor is in doze or running mode. The system tick interrupts are required for periodic activity such as polling the digitizer for user input, key debouncing, etc.

The date and time manager (called time manager in this chapter) provides access to both the 1-second and 0.01-second timing resources on the Palm Powered handheld.

  • The 1-second timer keeps track of the real-time clock (date and time), even when the unit is in sleep mode.
  • The 0.01-second timer, also referred to as the system ticks, can be used for finer timing tasks. This timer is not updated when the unit is in sleep mode and is reset to 0 each time the unit resets.

The basic time-manager API provides support for setting and getting the real-time clock in seconds and for getting the current system ticks value (but not for setting it). The system manager provides more advanced functionality for setting up a timer task that executes periodically or in a given number of system ticks.

This section discusses the following topics:

Using Real-Time Clock Functions ^TOP^

The real-time clock functions of the time manager include TimSetSeconds and TimGetSeconds. Real time on the Palm Powered handheld is measured in seconds from midnight, Jan. 1, 1904. Call TimSecondsToDateTime and TimDateTimeToSeconds to convert between seconds and a structure specifying year, month, day, hour, minute, and second.

Using System Ticks Functions ^TOP^

The Palm Powered handheld maintains a tick count that starts at 0 when the handheld is reset. This tick increments

  • 100 times per second when running on the Palm Powered handheld
  • 60 times per second when running on the Macintosh under the Simulator

For tick-based timing purposes, applications should use the macro SysTicksPerSecond, which is conditionally compiled for different platforms. Use the function TimGetTicks to read the current tick count.

Although the TimGetTicks function could be used in a loop to implement a delay, it is recommended that applications use the SysTaskDelay function instead. The SysTaskDelay function automatically puts the unit into low-power mode during the delay. Using TimGetTicks in a loop consumes much more current.

Floating-Point ^TOP^

The Palm OS supports IEEE-754 single and double precision floating-point numbers declared with the C types float and double. Numbers of type float occupy four bytes and have an effective range of 1.17549e-38 to 3.40282e+38. Numbers of type double occupy eight bytes and have an effective range of
2.22507e-308 to 1.79769e+308.

You can use basic arithmetic operations to add, subtract, multiply, and divide numbers of type float and double. Higher-level functions such as those in the standard C header file math.h are not part of the core OS; you must either write them yourself, or you must employ a third-party math library.

The standard IEEE-754 special "non-number" values of NaN (not a number), +INF (positive infinity), and -INF (negative infinity) are generated as appropriate if you perform an operation that produces a result outside the range of numbers that can be represented. For instance, dividing a positive number by 0 returns +INF.

The Float Manager contains functions that convert double-precision floating-point numbers to and from a string using scientific notation. It also contains FlpBufferCorrectedAdd and FlpBufferCorrectedSub, which perform the indicated operation and correct the result in those situations where the result should be zero but isn't due to the way that floating-point numbers are represented. All of the Float Manager functions that either accept or return a floating-point number require it to be declared as an FlpDouble. The Float Manager defines a union, FlpCompDouble, that you use to declare values that can be interpreted either as a double or as an FlpDouble. You use this union as shown here:


double dblFlpCorrectedAdd (double d1, double d2, Int16acc) { 
FlpCompDouble fcd1, fcd2, fcdResult; 
 
fcd1.d = d1; 
fcd2.d = d2; 
FlpBufferCorrectedAdd(&fcdResult.fd, fcd1.fd, fcd2.fd,
acc); 
return fcdResult.d; 
} 


NOTE: If you are using CodeWarrior, you have the option of using FlpAToF, FlpCorrectedAdd, and FlpCorrectedSub instead of FlpBufferAToF, FlpBufferCorrectedAdd, and FlpBufferCorrectedSub. These "non-buffer" functions all return their results directly, rather than updating a value pointed to by the first parameter. Because they return an FlpDouble—which is a struct—and because the GCC compiler's convention for returning structures from functions is incompatible with Palm OS, GCC users can only use the FlpBuffer... versions.

In the rare event that you need to work with the binary representation of a double, the Float Manager also contains a number of functions and macros that allow you to obtain and in some cases alter the sign, mantissa, and exponent of a 64-bit floating-point number. See Chapter 36, "Float Manager," of the Palm OS Programmer's API Reference for the functions and macros that make up the Float Manager.

The Float Manager, which was introduced in Palm OS 2.0, is sometimes referred to as the New Float Manager to distinguish it from the Float Manager that was part of Palm OS 1.0. The 1.0 Float Manager, which is less accurate and less convenient to use (simple operations such as addition require a call to a Float Manager function), remains in the ROM solely for backward compatibility; its functions are no longer publicly declared in the Palm OS SDK and should no longer be used. The functions in the old Float Manager all begin with "Fpl" rather than the current "Flp"; see Appendix C, "1.0 Float Manager," of the Palm OS Programmer's API Reference for the functions that make up the original Float Manager.

Summary of System Features ^TOP^