Previous Book Contents Book Index Next

Inside Macintosh: Sound /
Chapter 2 - Sound Manager / Using the Sound Manager


Legacy Documentclose button

Important: Inside Macintosh: Sound is deprecated as of Mac OS X v10.5. For new audio development in Mac OS X, use Core Audio. See the Audio page in the ADC Reference Library.

Playing Notes

You can play notes one at a time by using the SndDoCommand or SndDoImmediate function to issue freqDurationCmd sound commands. A sound plays for a specified duration at a specified frequency. You can play sounds defined by any of the three sound data formats. If you play wave-table data or sampled-sound data, then a voice must previously have been installed in the channel. (See "Installing Voices Into Channels" on page 2-43 for instructions on installing wave tables and sampled sounds as voices.)

You can also play notes by issuing the freqCmd command, which is identical to the freqDurationCmd command, except that no duration is specified when you issue freqCmd.

Note
A freqDurationCmd command might in certain cases continue playing until another command is available in the sound channel. Therefore, to play a single note for a specified duration, you should issue freqDurationCmd followed immediately by quietCmd. See "Stopping Sound Channels" on page 2-28 for further details on quietCmd.
The structure of a freqDurationCmd command is slightly different from that of most other sound commands. The param1 field contains the duration of the sound, specified in half-milliseconds. A value of 2000 represents a duration of 1 second. The maximum duration is 32,767, or about 16 seconds, in Sound Manager versions 2.0 and earlier; the maximum duration in Sound Manager version 3.0 and later is 65,536, or about 32 seconds. The param2 field specifies the frequency of the sound. The frequency is specified as a MIDI note value (that is, a value defined by the established MIDI standard). Listing 2-15 uses the freqDurationCmd command in a way that ensures the sound stops after the specified duration.

Listing 2-15 Using the freqDurationCmd command

PROCEDURE MyPlayFrequencyOnce (mySndChan: SndChannelPtr;
                                 myMIDIValue: Integer;
                                 milliseconds: Integer);
CONST
   kNoWait = TRUE;                  {add now to full queue?}
VAR
   mySndCmd:   SndCommand;          {a sound command}
   myErr:      OSErr;
BEGIN
   {Start the sound playing.}
   WITH mySndCmd DO
   BEGIN
      cmd := freqDurationCmd;       {play for period of time}
      param1 := milliseconds * 2;   {half-milliseconds}
      param2 := myMIDIValue;        {MIDI value to play}
   END;
   myErr := SndDoCommand(mySndChan, mySndCmd, NOT kNoWait);
   IF myErr <> noErr THEN
      DoError(myErr)
   ELSE
   BEGIN                            {ensure that sound stops}
      WITH mySndCmd DO
      BEGIN
         cmd := quietCmd;           {stop playing sound}
         param1 := 0;               {unused with quietCmd}
         param2 := 0;               {unused with quietCmd}
      END;
      myErr := SndDoCommand(mySndChan, mySndCmd, NOT kNoWait);
      IF myErr <> noErr THEN
         DoError(myErr);
   END;
END;
Table 2-2 shows the decimal values that can be sent with a freqDurationCmd or freqCmd command. Middle C is represented by a value of 60 and is defined by a special Sound Manager constant.

CONST
   kMiddleC       = 60;       {MIDI note value for middle C}
Other specifiable frequencies correspond to MIDI note values.
Table 2-2 Frequencies expressed as MIDI note values
 AA#BCC#DD#EFF#GG#
Octave 1   012345678
Octave 291011121314151617181920
Octave 3212223242526272829303132
Octave 4333435363738394041424344
Octave 5454647484950515253545556
Octave 6575859606162636465666768
Octave 7697071727374757677787980
Octave 8818283848586878889909192
Octave 993949596979899100101102103104
Octave 10105106107108109110111112113114115 116
Octave 11117118119120121122123124125126127 

You can play square-wave and wave-table data at these frequencies only. If you are playing a sampled sound, however, you can modify the sampleRate field of the sound header to play a sound at an arbitrary frequency. To do so, use the following formula:

new sample rate = (new frequency / original frequency) * original sample rate

where the new and original frequencies are measured in hertz. To convert a MIDI value to hertz for use in this formula, note that middle C is defined as 261.625 Hz and that the ratio between the frequencies of consecutive MIDI values equals the twelfth root of 2, defined by the constant twelfthRootTwo.

CONST
   twelfthRootTwo          = 1.05946309434;
IMPORTANT
When calculating with numbers of type Fixed, pay attention to possible overflows. The maximum value of a number of type Fixed is 65,535.0. As a result, some sample rates and pitches cannot be specified. Sound Manager version 3.0 fixes these overflow problems.
You can rest a channel for a specified duration by issuing a restCmd command. The duration, specified in half-milliseconds, is passed in the param1 field of the sound command.

Installing Voices Into Channels

You can play frequencies defined by any of the three sound data types. By playing a frequency defined by wave-table or sampled-sound data, you can achieve a different sound than by playing that same frequency using square-wave data. For example, you might wish to play the sound of a dog's barking at a variety of frequencies. To do that, however, you need to install a voice of the barking into the sound channel to which you want to send freqCmd or freqDurationCmd commands.

You can install a wave table into a channel as a voice by issuing the waveTableCmd command. The param1 field of the sound command specifies the length of the wave table, and the param2 field is a pointer to the wave-table data itself. Note that the Sound Manager resamples the wave table so that it is exactly 512 bytes long.

You can install a sampled sound into a channel as a voice by issuing the soundCmd command. You can either issue this command from your application or put it into an 'snd ' resource. If your application sends this command, param2 is a pointer to the sampled sound locked in memory. If soundCmd is contained within an 'snd ' resource, the high bit of the command must be set. To use a sampled-sound 'snd ' as a voice, first obtain a pointer to the sampled sound header locked in memory. Then pass this pointer in param2 of a soundCmd command. After using the sound, your application is expected to unlock this resource and allow it to be purged.

Listing 2-16 demonstrates how you can use the soundCmd command to install a sampled sound in memory as a voice in a channel.

Listing 2-16 Installing a sampled sound as a voice in a channel

FUNCTION MyInstallSampledVoice (mySndHandle: Handle;
                                 mySndChan: SndChannelPtr): OSErr;
VAR
   mySndCmd:         SndCommand;          {a sound command}
   mySndHeader:      SoundHeaderPtr;      {sound header from resource}
BEGIN
                                          {get pointer to sound header}
   mySndHeader := MyGetSoundHeader(mySndHandle);
   WITH mySndCmd DO
   BEGIN
      cmd := soundCmd;                    {install sampled voice}
      param1 := 0;                        {ignored with soundCmd}
      param2 := LongInt(mySndHeader);     {store sound header location}
   END;
   IF mySndHeader = NIL THEN              {check for defective handle}
      MyInstallSampledVoice := badFormat
   ELSE                                   {install sound as voice}
      MyInstallSampledVoice := SndDoImmediate(mySndChan, mySndCmd);
END;
Listing 2-16 relies on the MyGetSoundHeader function to obtain a pointer to the sound header within the sound handle. That function is defined in "Obtaining a Pointer to a Sound Header" on page 2-57 and returns NIL if the sound handle does not include a sound header. Note that the MyGetSoundHeader function locks the sound handle in memory so that the pointer to the sound header remains valid. When you are done with the sound channel in which you have installed the sampled sound, you should unlock the sound handle and make it purgeable so that it does not waste memory.

Looping a Sound Indefinitely

If you install a sampled sound as a voice in a channel and then play the sound using a freqCmd or freqDurationCmd command that lasts longer than the sound, the sound will ordinarily stop before the end of the time specified by the freqCmd or freqDurationCmd command. Sometimes, however, this might not be what you'd like to have happen. For example, you might have recorded the sound of a violin playing and then stored that sound in a resource so that you could play the sound of a violin at a number of different frequencies. Although you could record the sound so that it is long enough to continue playing through the longest freqCmd or freqDurationCmd command that your application might require, this might not be practical. Fortunately, the Sound Manager provides a mechanism that allows you to repeat sections of sampled sound after the sound has finished playing once completely.

When you use the freqDurationCmd command with a sampled sound as the voice, freqDurationCmd starts at the beginning of the sampled sound. If necessary to achieve the desired duration of sound, the command replays that part of the sound that is between the loop points specified in the sampled sound header. Note that any sound preceding or following the loop points will not be replayed. There must be an ending point for the loop specified in the header in order for freqDurationCmd to work properly.

Listing 2-17 Looping an entire sampled sound

PROCEDURE MyDoLoopEntireSound (sndHandle: Handle);
VAR
   mySndHeader:   SoundHeaderPtr;      {sound header from resource}
   myTotalBytes:  LongInt;             {bytes of data to loop}
BEGIN
   mySndHeader := MyGetSoundHeader(sndHandle);
   IF mySndHeader <> NIL THEN
   BEGIN                               {compute bytes of sound data}
      CASE mySndHeader^.encode OF
         stdSH:                        {standard sound header}
            WITH mySndHeader^ DO
               myTotalBytes := mySndHeader^.length;
         extSH:                        {extended sound header}
            WITH ExtSoundHeaderPtr(mySndHeader)^ DO
               myTotalBytes := numChannels * numFrames * (sampleSize DIV 8);
         cmpSH:                        {compressed sound header}
            WITH CmpSoundHeaderPtr(mySndHeader)^ DO
               myTotalBytes := numChannels * numFrames * (sampleSize DIV 8);
      END;
      WITH mySndHeader^ DO
      BEGIN                            {set loop points}
         loopStart := 0;               {start with first byte}
         loopEnd := myTotalBytes - 1;  {end with last byte}
      END;
   END;
END;
Listing 2-17 uses the MyGetSoundHeader function defined in "Obtaining a Pointer to a Sound Header" on page 2-57. Note that the formula for computing the length of a sound depends on the type of sound header. Also, while the formula is the same for both an extended and a compressed sound header, you must write code that differentiates between the two types of sound headers because the sampleSize field is not stored in the same location in both sound headers.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
2 JUL 1996