# The Media Kit: BSoundFile

Derived from: none

Declared in: be/media/SoundFile.h

Library: libmedia.so

## Overview

BSoundFile objects give you access to files that contain sound data. The BSoundFile functions let you examine the format of the data in the sound file, read the data, and position a "frame pointer" in the file. BSoundFile does not, however, include functions for playing from and recording into a sound file. You can play a BSoundFile's data, or write data into a BSoundFile, but you'll have to set up code to do this yourself, using a BSubscriber object and a BDACStream object.

- At this time, BSoundFile does not support writing to AIFF files. All other supported file formats can be written to.

To use a BSoundFile, you set its `entry_ref`, and then you open the file using the `SetTo()` call (there's also a version of the BSoundFile constructor that does this when the object is constructed). If you want to read the sound file (to play it, for example), open the file with `B_READ_ONLY` access. To write into the sound file (while recording, perhaps), use `B_WRITE_ONLY` access. `B_READ_WRITE` access is not allowed.

### Sound File Formats

The BSoundFile class understands AIFF, WAVE, and "standard" UNIX sound files (.snd and .au). When you give BSoundFile a reference to a file, it opens the file and determines the format of the data it contains--you can't force it to assume a particular format. If the file is not in a format it understands ("unknown" format), it assumes that the file contains 44.1kHz, 16-bit stereo data, and that the file doesn't have a header (it assumes that the entire file is sound data). The admission of the unknown format means that any file can act as sound data. BSoundFile doesn't know the meaning of "inappropriate data."

The file formats are represented by the constants `B_AIFF_FILE`, `B_WAVE_FILE`, `B_UNIX_FILE`, and `B_UNKNOWN_FILE`. You can determine the format of the file represented by a BSoundFile object by using its `FileFormat()` function.

|  | 8-bit WAVE data is, by definition, unsigned. When you use the `ReadFrames()` function to read data in this format, you must be sure to convert each sample into signed format before manipulating it further, since all other sound formats supported by BeOS are signed (including both the ADC and DAC streams).

To convert an 8-bit WAVE sample into a signed 8-bit sample, just subtract 128 from the sample.

### Sound Data Parameters

Once you've opened your BSoundFile, you can ask for information about the format of the data by calling the various format-retrieving functions (`SamplingRate()`, `CountChannels()`, `SampleSize()`, and so forth). There are also functions for setting these parameters (`SetSamplingRate()`, `SetChannelCount()`, `SetSampleSize()`, and so on). Note that these functions don't actually modify the data in the file (or in the BSoundFile object). They simply set the object's impression of what sort of data is in the file. This should only be neecessary if the file format is unknown, or if you're creating a new BSoundFile object for a sound you're preparing to save to disk.

### Playing a Sound File

There are two methods for playing a sound file:

- The easy way is to call the `play_sound()` function. This function takes an `entry_ref` argument (among others) and plays the data that it finds in that file--you don't have to createa BSoundFile object to use `play_sound()`. Complete documentation for `play_sound()` can be found in the last section of this chapter.

- A much more fun way to play sound files is to create a BSoundFile object, read the frames contained within, and add the data into the DAC stream. Although this is clearly somewhat more involved than using `play_sound()`, you can exert a lot more control over the playback. Since you're reading the sound data yourself, you can manipulate the sound as you're tossing it into the stream.

The next section provides a demonstration of the second approach. To follow the example, you should first familiarize yourself with the features of the BSubscriber and BDACStream classes.

### An Example

In this example, we show how to read data from a BSoundFile and add it to the DAC stream for playback. To keep the example brief, this class only plays 16-bit sounds, so that we don't have to handle converting data from 8-bit formats.

First, we define a class called SoundPlayer that will be used to coordinate the Media Kit objects (BDACStream, BSubscriber, and BSoundFile). Notice that in this example we don't derive from any of these Media Kit classes, although we could.

       class SoundPlayer {
          public:
             status_t         SetSoundFile(entry_ref *ref);
             void         Play(void);
             void         Stop(void);

          private:
             static bool      _play_back(void *userData,
                                  char *buffer,
                                  size_t count,
                                  void *header);
             bool         Playback(char *buffer, size_t count);

             BDACStream         stream;
             BSubscriber         subscriber;
             BSoundFile         soundFile;
             char         transfer_buf[B_PAGE_SIZE];
       };

There are three public functions: `SetSoundFile()` lets you specify the file you want to play, `Play()` starts playing the sound file, and `Stop()` immediately stops the playback.

The private `_play_back()` function is the stream function that's passed to `EnterStream()`. It will call `Playback()` to do the actual stream manipulations. The private `transfer_buf` will be used when we read data from the audio stream; once it's read into the buffer, we can mix it into the sound stream (a page at a time).

#### Preparing for Playback

The `SetSoundFile()` function handles setting the BSoundFile to reference the desired file. It also checks to be sure the file is one that our code is capable of playing (our example only plays 16-bit sounds):

       status_t SoundPlayer::SetSoundFile(entry_ref *ref) {
          status_t   err;
          err = soundFile.SetTo(ref, B_READ_ONLY);
          if (err == B_OK) {
             if (soundFile.SampleSize() != 2) {
                err = B_ERROR;      // Reject file if not 16-bit stereo
             }

If the sample size is in fact 16 bits, we continue by subscribing to the DAC stream and setting the stream's sampling rate to match that of the file we're playing. We also set the size of the stream's buffers to match the size of our transfer buffer. We specify eight stream buffers (the default for the Audio Server). Finally, if an error occurred at any point during this function, we return an appropriate error code. Otherwise, `B_OK` is returned.

             else {
                err = subscriber.Subscribe(&stream);
                if (err == B_OK) {
                   stream.SetSamplingRate(soundFile.SamplingRate());
                   stream.SetStreamBuffers(B_PAGE_SIZE, 8);
                }
             }
          }
          return err;
       }

Note that we're making a lot of assumptions in this example--assumptions we really shouldn't make. We're assuming that nobody changes the size of the stream buffers behind our back, and that nobody changes the sampling rate of the stream. In a real-world application, these are not safe assumptions to make, and you have to be prepared for the possibility that these parameters might be changed while your sound is playing.

#### Entering the Stream

The `Play()` function enters the BSubscriber into the DAC stream. This causes buffers to begin arriving at the stream function, which we'll implement in a moment.

       void SoundPlayer::Play(void) {
             subscriber.EnterStream(NULL, false, this, _play_back,
                               NULL, true);
       }

#### Reading and Playing the File

Now we get to the really fun part. First, let's implement the literal stream function, `_play_back()`.

       bool SoundPlayer::_play_back(void *userData, char *buffer,
                             size_t count, void *header) {
          return (((SoundPlayer *) userData)->Playback(buffer, count));
       }

As `_play_back()` receives buffers from the DAC stream, it forwards them to the `Playback()` function to do the real work. At each invokation, `Playback()` reads the appropriate number of frames from the file and mixes the samples into the DAC stream buffer. First we establish our local variables and create pointers to the `transfer_buf` and the stream buffer, cast to pointers to 16-bit integers.

       bool SoundPlayer::Playback(char *buffer, size_t count) {
          int32         frameCount;      // Number of frames to mix
          int32         framesRead;      // Number of frames read from disk
          int32         channelCount;   // Number of channels in sound
          int32         counter;      // Loop counter for mixing
          int16         *soundData;      // Pointer to the sample to mix
          int16         *tbuf;         // Short pointer to transfer buffer
          int32         sample;         // Temporary value of sample while mixing

          soundData = (int16 *) buffer;
          tbuf = (int16 *) transfer_buf;

Then we figure out how many frames of data will fit in the stream buffer by dividing count (the number of bytes in the buffer) by 4 (the size of a 16-bit stereo frame of sound). We also determine the number of channels in the sound we're playing, since we'll need to know that when we mix the sound into the stream buffer. Then we read the next batch of frames from the file into memory.

`ReadFrames()` returns as a result the number of frames of sound data read from the file. If this value is less than we requested, the batch of frames read is the last batch in the file. If it returns zero, there were no frames left to read. A negative result means an error occurred. If the result is either zero or negative, we exit, returning `FALSE` so that we are removed from the stream.

- We should check to make sure that the transfer buffer can hold the number of frames we're reading from disk, but in our example we're making the assumption that the stream buffer size never changes. In the real world, you should check this very carefully.

          frameCount = count/4;
          channelCount = soundFile.CountChannels();

          framesRead = soundFile.ReadFrames(transfer_buf, frameCount);

          if (framesRead <= 0) {
             return false;         // Either error or done with file
          }

Now it's time to mix the sound into the sound buffer. For each sample (both left and right channels), we do the following:

- Add the next sample from the sound file to the sample in the stream buffer, and store the result in a 32-bit variable called (called sample in our example code).

- If the result is greater than 32,767, we need to clip the sound to 32,767. That's because this is the largest value a 16-bit number can represent, and we'll get weird audio glitches if we don't clip the sound.

- Likewise, if the result was less then -32,768, we have to clip the sound to -32,768.

- Then the sample can be stored into the output stream.

We only mix the right channel if it exists in the sound we're playing. If it doesn't, we skip over it in the stream buffer, leaving the original sample data intact.

          counter = 0;
          do {
             sample = *soundData + *tbuf++;      // Add the old and the new
             if (sample > 32767) {            // Is the result too high?
                sample = 32767;
             }
             else if (sample < -32768) {         // How about too low?
                sample = 32768;
             }
             *soundData++ = sample;            // Now save the clipped value
             if (channelCount == 2) {         // If there's another channel,
                sample = *soundData + *tbuf++;   //  do the same thing again
                if (sample > 32767) {
                   sample = 32767;
                }
                else if (sample < -32768) {
                   sample = 32768;
                }
                *soundData++ = sample;
             }
             else {
                soundData++;               // Just skip the right channel
             }
          } while (++counter < framesRead);

          // If we're done, return false.  Otherwise,
          // return true.

          if (framesRead < frameCount) {
             return false;
          }
          return true;
       }

Clearly, this example isn't as robust or efficient as it could be. If you're writing code to play sound files, the following should be considered:

- You shouldn't assume that the sampling rate will never change. It will.

- You shouldn't assume the stream buffer size will never change. It will.

- You shouldn't read only one page of sound data at a time. The disk thrashing this sample could cause--especially if you're playing multiple sound files at once--can really hurt performance, and probably isn't good for your hard drive, either.

## Constructor and Destructor

### BSoundFile()

          BSoundFile()
          BSoundFile(const entry_ref *soundRef, uint32 openMode)

Creates and returns a new BSoundFile object, which represents a sound file. The first form of the constructor must be followed by a call to `SetTo()`. In the second form of the constructor, you establish a reference to a sound file to open and the file open mode. The open mode can be either `B_READ_ONLY` or `B_WRITE_ONLY`; you can't open a sound file for read/write access.

After constructing your BSoundFile object, you should call `InitCheck()` to determine if any errors occurred.

### ~BSoundFile()

          virtual ~BSoundFile()

Closes the sound file and destroys the BSoundFile object.

## Member Functions

### ByteOrder(), SetByteOrder()

          int32 ByteOrder() const

          virtual int32 SetByteOrder(int32 newByteOrder)

`ByteOrder()` returns the byte ordering of the sound data. If the data is in a big-endian format, this function returns `B_BIG_ENDIAN`; otherwise, `B_LITTLE_ENDIAN` is returned. The default value for new or uninitialized sound files is `B_BIG_ENDIAN`.

`SetByteOrder()` lets you change the byte order of for the sound file to newByteOrder. It returns newByteOrder.

- Changing the byte order of an existing sound file doesn't reorder the existing data; it only affects the object's impression of the sound. If you want to change the byte ordering of a sound, do it before you record the sound.

These functions never fail.

### CompressionName(), SetCompressionName()

          char *CompressionName() const

          virtual char *SetCompressionName(char *newCompressionName)

`CompressionName()` returns the name of the compression format used in the sound file object. The default for new or uninitialized sound files is `NULL`, since there is no compression.

`SetCompressionName()` currently does nothing, and returns `NULL`.

|  | The pointer returned to you by these functions belongs to the BSoundFile object; you must not change or delete it.

This function never fails.

### CompressionType(), SetCompressionType()

          int32 CountChannels() const

          virtual int32 SetCompressionType(int32 newCompressionType)

`CompressionType()` returns the type of compression used for the sound file object. The default for new or uninitialized sound files is -1 (no compression).

`SetCompressionType()` currently does nothing and returns 0.

These functions never fail.

### CountChannels(), SetChannelCount()

          int32 CountChannels() const

          virtual int32 SetChannelCount(int32 newChannelCount)

`CountChannels()` returns the number of channels in the sound file object. For stereo sound files, this is 2; for monaural sound files, this is 1. The default for new or uninitialized sound files is 2.

`SetChannelCount()` lets you change the number of channels in the sound file to newChannelCount; it returns the new value.

- Changing the number of channels in an existing sound file doesn't alter the existing data; it only affects the object's impression of the sound--you can't use `SetChannelCount()` to reformat the sound into a new format. Use this function when creating a new sound file.

The channel count can be thought of as not only the number of audio channels in the sound, but as the number of audio samples in a single frame of audio data; see `FrameSize()` and the Overview for a more in-depth discussion of this relationship.

These functions never fail.

See also: `CountFrames()`, `FrameSize()`

### CountFrames(), SetFrameCount()

          off_t CountFrames() const

          virtual off_t SetFrameCount(off_t newFrameCount)

`CountFrames()` returns the number of frames in the sound file object. For new or uninitialized sound files, this is 0.

`SetFrameCount()` lets you set the number of frames in the sound file to newFrameCount, and returns the new count.

- Changing the frame count of an existing sound file doesn't alter the existing data; it only affects the object's impression of the sound--you can't use `SetFrameCount()` to reformat the sound into a new format. Use this function when creating a new sound file.

These functions never fail.

### FileFormat(), SetFileFormat()

          int32 FileFormat() const

          virtual int32 SetFileFormat(int32 newFormat)

These functions get and set the file format of the sound file object. The format can be one of the following:

- `B_UNKNOWN_FILE` The sound file's format is not known.
- `B_AIFF_FILE` AIFF sound file.
- `B_WAVE_FILE` WAVE sound file.
- `B_UNIX_FILE` UNIX sound file.

`FileFormat()` returns the current file format; for new or uninitialized sound files, the default format is `B_UNKNOWN_FILE`.

`SetFileFormat()` sets the format of the file to newFormat and returns newFormat.

- Changing the file format of an existing sound file doesn't alter the existing data; it only affects the object's impression of the sound--you can't use `SetFileFormat()` to reformat the sound into a new format. Use this function when creating a new sound file.

These functions never fail.

### FrameSize()

          int32 FrameSize() const

Returns the number of bytes needed to represent a single frame of sound data. A frame is a single "instant" of sound, and consists of the data necessary to represent a single sound sample for all channels in the sound.

For example, if the sound is 8-bit monaural, the frame size will be 1 byte. For 16-bit stereo (the default for new or uninitialized files), the frame size will be 4 bytes. The frame size is determined through the following complicated mathematical formula:

|  | frameSize = sampleSize * channelCount

Because this is very technically challenging, you should always use the `FrameSize()` function to determine the size of a frame (additionally, this protects you from your code breaking on odd file formats). This function never fails.

### FrameIndex()

          off_t FrameIndex() const

`FrameIndex()` returns the current frame offset within the sound file; this is the frame number that will be read the next time `ReadFrames()` is called or written the next time `WriteFrames()` is called.

This function never fails.

### FramesRemaining()

          off_t FramesRemaining() const

`FramesRemaining()` returns the number of frames left to access in the file; this is the difference between the highest frame number in the file and `FrameIndex()`.

This function never fails.

### InitCheck()

          status_t InitCheck() const

Reports the result of the BSoundFile constructor. You should call this after constructing the object, to ensure that no errors occurred.

RETURN CODES

``

- B_OK. The object was successfully initialized.
- `B_NO_INIT`. The object hasn't been initialized properly.
- Errors returned by `SetTo()`.

### IsCompressed(), SetIsCompressed()

          bool IsCompressed() const

          virtual bool SetIsCompressed(bool newIsCompressed)

`IsCompressed()` returns `TRUE` if the sound file's data is compressed, or `FALSE` otherwise. For new or uninitialized sound files, this is `FALSE`.

`SetIsCompressed()` currently does nothing and always returns `FALSE`.

These functions never fail.

### ReadFrames()

          size_t ReadFrames(char *buffer, size_t frameCount)

Reads frames of audio data into the specified buffer. Up to frameCount frames of data are read; fewer frames will be read if the end of the sound file is reached.

Once the frames have been read into the buffer, this function returns the number of frames of audio data read into memory.

If an error occurs while reading the data, a negative value will be returned; this value will be one of the return values specified below.

RETURN CODES

|  | Values 0 and greater: the number of frames successfully read.
|  | `B_BAD_FILE`. An error occurred while reading the file.

### SampleFormat(), SetSampleFormat()

          int32 SampleFormat() const

          virtual int32 SetSampleFormat(int32 newFormat)

`SampleFormat()` returns sound file's sample format; for new or uninitialized files, this is `B_LINEAR_SAMPLES`. The possible sample formats are:

- `B_UNDEFINED_SAMPLES` Unknown sample format.
- `B_LINEAR_SAMPLES` Samples are linear integers.
- `B_FLOAT_SAMPLES` Samples are floating-point values.
- `B_MULAW_SAMPLES` Samples are in \xb5 Law format.

`SetSampleFormat()` sets the sample format of a sound sample to newFormat and returns newFormat to the caller.

- Changing the sample format of an existing sound file doesn't alter the existing data; it only affects the object's impression of the sound--you can't use `SetSampleFormat()` to reformat the sound into a new format. Use this function when creating a new sound file.

These functions never fail.

### SampleSize(), SetSampleSize()

          int32 SampleSize() const

          virtual int32 SetSampleSize(int32 newSampleSize)

`SampleSize()` returns the number of bytes used to represent each sample in the sound file; this is 1 for 8-bit sound, and 2 for 16-bit sound; the default for new or uninitialized sound files is 2.

`SetSampleSize()` lets you change the sound sample size to newSampleSize, and returns the new size.

- Changing the sample size of an existing sound file doesn't alter the existing data; it only affects the object's impression of the sound--you can't use `SetSampleSize()` to reformat the sound into a new format. Use this function when creating a new sound file.

The sample size is used to determine the size of a frame of sound data; see `FrameSize()` and the Overview for details on how this relationship works.

See also: `CountChannels()`, `FrameSize()`

These functions never fail.

### SamplingRate(), SetSamplingRate

          int32 SamplingRate() const

          virtual int32 SetSamplingRate(int32 newRate)

`SamplingRate()` returns the sampling rate of the sound file object, in frames per second; for new or uninitialized files, this is 44,100 Hz.

`SetSamplingRate()` sets the sampling rate of the file to newRate, and returns the new value.

- Changing the sampling rate of an existing sound file doesn't alter the existing data; it only affects the object's impression of the sound--you can't use `SetSamplingRate()` to reformat the sound into a new format. Use this function when creating a new sound file.

These functions never fail.

### SeekToFrame()

          virtual off_t SeekToFrame(off_t frame)

Sets the current frame to the specified number. This is the next frame of audio data that will be read from using `ReadFrames()` or written to using `WriteFrames()`.

The new current frame number will be returned (it will be different from the value you passed in only if there are fewer than frame frames in the file, in which case the number of the last frame in the file will be returned.

If an error occurs, a negative value will be returned; this value will be one of the return values specified below.

RETURN CODES

``

|  | Values 0 and greater: the number of frames successfully written.
|  | `B_BAD_FILE`. An error occurred while writing the file.

- `B_NOT_ALLOWED`. The file lives on a read-only volume.
- `B_DEVICE_FULL`. No more room on the file's device.

### SetByteOrder() see ByteOrder()

### SetChannelCount() see CountChannels()

### SetCompressionName() see SetCompressionName()

### SetCompressionType() see CompressionType()

### SetDataLocation()

          virtual off_t SetDataLocation(off_t dataOffset)

Lets you set the offset, in bytes, into an audio frame at which the actual sound data is stored. This allows you to deal with audio formats in which each frame of audio has a header or other data added to the frame. This function never fails.

### SetFileFormat() see FileFormat()

### SetFrameCount() see CountFrames()

### SetIsCompressed() see IsCompressed()

### SetSampleFormat() see SampleFormat()

### SetSampleSize() see SampleSize()

### SetSamplingRate() see SamplingRate()

### SetTo()

          status_t SetTo(const entry_ref *soundRef, uint32 openMode)

Sets the current file and open mode referred to by the BSoundFile object and prepares the file for reading or writing, as appropriate:

- If openMode is `B_READ_ONLY`, the file is opened and its header is read into memory in preparation for playback.

- If openMode is `B_WRITE_ONLY`, the file is opened and a new header is created for the file.

openMode must be either `B_READ_ONLY` or `B_WRITE_ONLY`; read/write access is not permitted and attempting to open the file in this mode will result in a `B_BAD_VALUE` error.

- At this time, the BSoundFile class does not support writing to AIFF files; you'll get a `B_BAD_VALUE` error if soundRef refers to an AIFF file and you specify `B_WRITE_ONLY` for openMode.

RETURN CODES

- `B_OK`. The operation was a success.
- `B_BAD_VALUE`. Invalid openMode; only `B_READ_ONLY` and `B_WRITE_ONLY` are allowed, or writing has been requested to a read-only sound file format.
- `B_ERROR`. The file could not be opened, or the read or write operation failed.

### WriteFrames()

          size_t WriteFrames(char *buffer, size_t frameCount)

Writes frames of audio data from the specified buffer into the sound file. frameCount frames of data are written; fewer frames will be written only if an error occurs.

Once the frames have been written into the file, this function returns the number of frames of audio data successfully written.

If an error occurs while writing the data, a negative value will be returned; this value will be one of the return values specified below.

RETURN CODES

``

|  | Values 0 and greater: the number of frames successfully written.
|  | `B_BAD_FILE`. An error occurred while writing the file.

- `B_NOT_ALLOWED`. The file lives on a read-only volume.
- `B_DEVICE_FULL`. No more room on the file's device.

The Be Book, in lovely HTML, for BeOS Release 3.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified March 26, 1998.