TweetFollow Us on Twitter

Catching a WAVE

Volume Number: 13 (1997)
Issue Number: 11
Column Tag: develop

Catching a WAVE

by Tim Monroe, Apple Computer, Inc.

Playing WAVE Files on the MacOS

By far the most common type of sound file on Windows computers (and hence on personal computers in general) is the .WAV format file, also called a WAVE file or a waveform file. Collections of WAVE files are easily accessible on the Internet and elsewhere, and the number of WAVE files available for downloading far outstrips the number of Macintosh sound files. So what's a loyal Macintosh programmer to do when confronted with audio content in WAVE format? The Sound Manager's SndStartFilePlay function won't play WAVE files (not yet, at least), but it's easy to use other Sound Manager capabilities to play the data contained in a WAVE file.

In this article, I'll show how to open, parse, and play a WAVE file. In fact, this is a surprisingly simple thing to do, largely because the digitized sound data in a WAVE file is stored in pretty much the same format as digitized sound data on the Mac. So, all we need to do is extract the sound data from the WAVE file and pass it to standard Sound Manager routines. If you've never used the low-level Sound Manager interfaces, this will be a good introduction. Along the way, you'll also learn how to deal with the WAVE file's chunk architecture and the little-endian byte ordering used on Windows.

If you're not inclined to work with sound files at this low level, don't despair. At the end of this article, I'll show an alternate strategy for playing WAVE files that uses the QuickTime APIs instead of the Sound Manager. Indeed, if you're really averse to low-level coding, you should skip straight to the section "Surfing with QuickTime" and read about the high-level method. Otherwise, strap on your wet suit, and let's go!

The Chunk Architecture

A WAVE file contains digitized (that is, sampled) sound data, just like most sound files and resources on the Macintosh. A WAVE file also contains information about the format of the sound data, such as the number of bits per sample and the number of channels of audio data (mono vs. stereo). The various kinds of data in a WAVE file are isolated from one another using an architecture based on chunks. A chunk is simply a block of data together with a chunk header, which specifies both the type of the chunk and the size of the chunk's data. Figure 1 illustrates the basic structure of a chunk.

Figure 1. The basic structure of a chunk.

A WAVE file always contains at least two chunks, a data chunk that contains the sampled sound data, and a format chunk that contains information about the format of the sound data. These two chunks (and any others that might occur in the file) are packaged together inside of another chunk, called a container chunk or a RIFF chunk. Like any chunk, the RIFF chunk has a header (whose chunk type is 'RIFF') and some chunk data. For RIFF chunks, the chunk data begins with a data format specifier followed by all the other chunks in the file. The format specifier for a WAVE file is 'WAVE'. Figure 2 shows the general structure of any WAVE file.

Figure 2. The basic structure of a WAVE file.

The first thing we'll do is define some data structures that will help us extract the information from a chunk header or from the chunk data. In C, we can represent a chunk header like this:

typedef struct ChunkHeader {
  FOURCC    fChunkType;      // the type of this chunk
  DWORD    fChunkSize;      // the size (in bytes) of the chunk data
} ChunkHeader, *ChunkHeaderPtr;

Here, we're using standard Windows data types; on the Macintosh, these types are #define'd to more familiar types:

#define WORD    UInt16
#define DWORD    UInt32
#define FOURCC  OSType

We can represent a format chunk like this:

typedef struct FormatChunk {
  ChunkHeader  fChunkHeader;    // the chunk header; fChunkType == 'fmt '
  WORD    fFormatType;        // format type
  WORD    fNumChannels;        // number of channels
  DWORD  fSamplesPerSec;    // sample rate
  DWORD  fAvgBytesPerSec;  // byte rate (for buffer estimation)
  WORD    fBlockAlign;        // data block size
  WORD    fAdditionalData;    // additional data
} FormatChunk, *FormatChunkPtr;

And, finally, we can represent the relevant parts of a RIFF chunk like this:

typedef struct RIFFChunk {
  ChunkHeader  fChunkHeader;    // the chunk header; fChunkType == 'RIFF'
  FOURCC    fFormType;        // the data format; 'WAVE' for waveform files
                          // additional chunk data follows here
} RIFFChunk, *RIFFChunkPtr;

It's very easy to parse a file that is structured into chunks. You simply begin at the start of the file data, which is guaranteed to be the beginning of the container chunk. You can find the first subchunk by skipping over the container chunk header and any additional container chunk data. And you can find any succeeding subchunks by skipping over the subchunk header and the number of bytes occupied by the subchunk data. Listing 1 shows how to find the beginning of a chunk of a specific type.

Listing 1: Finding a chunk of a specific type

OSErr GetChunkType (short theFile, OSType theType, 
                    long theStartPos, long *theChunkPos)
{
  OSErr        myErr = noErr;
  long          myLength = sizeof(ChunkHeader);
  UInt32        myOffset;
  ChunkHeader    myHeader;
  Boolean      isFound = false;
  // set file mark relative to start of file
  myErr = SetFPos(theFile, fsFromStart, theStartPos);
  if (myErr != noErr)
    return(myErr);
    // search the file for the specified chunk type
  while (!isFound && (myErr == noErr)) {
    // load the chunk header
    myErr = FSRead(theFile, &myLength, &myHeader);
    if (myErr == noErr) {
      if (myHeader.fChunkType == theType) {
        isFound = true;        // we found the desired chunk type
        myErr = GetFPos(theFile, theChunkPos);
        *theChunkPos -= myLength;
      } else {
        if (myHeader.fChunkType == kChunkType_RIFF)
          myOffset = sizeof(FOURCC);
        else
          myOffset = Swap_32(myHeader.fChunkSize);
        if (myOffset % 2 == 1)  // make sure chunk size is even
          myOffset++;
          myErr = SetFPos(theFile, fsFromMark, myOffset);
      }
    }
  }
  return(myErr);
}

The function GetChunkType starts searching the file data at a location (theStartPos) passed to it, which is assumed to be the start of a chunk. GetChunkType reads in the chunk header and looks to see if it has found the chunk of the desired type. If so, it returns the file position of the first byte of the chunk header. Otherwise, GetChunkType figures out where in the file the next chunk begins. If the current chunk is a container chunk, then the next chunk begins after the data format specifier; otherwise, the next chunk is to be found after the current chunk's data, whose size is specified in the chunk header. (Notice that in determining the size of the current chunk, we need to use the macro Swap_32; see "Which End Is Which?" for an explanation of why this is necessary.)

Once we know how to find the beginning of a particular chunk, it's easy to get the chunk's data. Listing 2 defines a function GetChunkData that returns a pointer to a buffer of memory containing both the chunk header and the data in the chunk.

Listing 2: Getting a chunk's data

ChunkHeaderPtr GetChunkData
        (short theFile, OSType theType, long theStartPos)
{
  long            myFPos;
  ChunkHeader      myHeader;
  Ptr            myDataPtr = NULL;
  OSErr          myErr = noErr;
  long            myLength;
  // get position of desired chunk type in file
  myErr = GetChunkType(theFile, theType, 
                          theStartPos, &myFPos);
  // set file mark at the start of the chunk
  if (myErr == noErr)
    myErr = SetFPos(theFile, fsFromStart, myFPos);
if (myErr == noErr) {
    myLength = sizeof(myHeader);
    // load the chunk header
    myErr = FSRead(theFile, &myLength, &myHeader);
    if (myErr != noErr)
      return(NULL);
    // set file mark at the start of the chunk header
    myLength += Swap_32(myHeader.fChunkSize);
    myErr = SetFPos(theFile, fsFromStart, myFPos);
  }
  if (myErr == noErr) {
    myDataPtr = NewPtrClear(myLen);
    myErr = MemError();
    if (myDataPtr != NULL)
      myErr = FSRead(theFile, &myLength, myDataPtr);
  }
  if (myErr != noErr) {
    DisposePtr(myDataPtr);
    myDataPtr = NULL;
  }
  return((ChunkHeaderPtr)myDataPtr);
}

GetChunkData calls GetChunkType to find the beginning of the chunk of the desired kind; then it reads the chunk header to find the size of the chunk data. (Once again, we've used the macro Swap_32 to massage the chunk data length as it's stored in the file.) Finally, GetChunkData allocates a buffer large enough to hold the entire chunk (header and data) and returns the pointer to the caller.

Here's a pleasant surprise: the functions GetChunkType and GetChunkData are simply slightly modified C language translations of the functions MyFindChunk and MyGetChunkData found in Inside Macintosh: Sound (pages 2-63 and 2-65, respectively). We could use those functions, suitably modified, because the AIFF format (defined by Apple and described in Inside Macintosh: Sound) and the RIFF format (defined by Microsoft and used for WAVE files) are both chunk-based formats, which descend from a common parent. See the sidebar "A Brief History of IFF" for details.

Which End Is Which?

Now we know how to find a chunk in a RIFF file and read the data in that chunk into memory. We've already seen, however, that we sometimes need to play with that data before we can use it. That's because of a difference in the way multi-byte data is accessed on Motorola and Intel processors. Motorola processors in the 680x0 family expect multi-byte data to be stored with the most significant byte lowest in memory. This is known as "big-endian" byte ordering (because the "big" end of the data value comes first in memory). Intel processors used for Windows machines expect multi-byte data to be stored with the least significant byte lowest in memory; this is known as "little-endian" byte ordering. (See Figure 3, which shows the value 0x12345678 stored in both Motorola 680x0 and Intel formats.) The PowerPC family of processors supports both big- and little-endian byte orderings, but uses big-endian when running the MacOS.

Figure 3. Big- and little-endian byte ordering.

The data stored in a WAVE file is little-endian. As a result, to use that data in a Macintosh application, we need to convert the little-endian data to big-endian data -- but only for data that is larger than 8 bits. For instance, the chunk data size field in a chunk header is 4 bytes long, so we need to swap the bytes using our macro Swap_32. Later, we'll also need to swap the two bytes in a 16-bit field, so we can define these macros:

#define Swap_32(value)         \
    (((((UInt32)value)<<24) & 0xFF000000) | \
     ((((UInt32)value)<< 8) & 0x00FF0000) | \
     ((((UInt32)value)>> 8) & 0x0000FF00) | \
     ((((UInt32)value)>>24) & 0x000000FF))

#define Swap_16(value)         \
    (((((UInt16)value)>> 8) & 0x000000FF) | \
     ((((UInt16)value)<< 8) & 0x0000FF00))

You might be wondering why we didn't need to swap bytes when reading the chunk type from a file. That's because a chunk type is a sequence of four (8-bit) characters. When reading individual characters, no byte swapping is necessary. A byte in little-endian byte ordering is the same as a byte in big-endian byte ordering. For this same reason, we don't need to do any work when reading 8-bit audio samples from the WAVE file and (eventually) passing them to the Sound Manager.

For 16-bit audio samples, however, the bytes do need to be swapped before they can be passed to the Sound Manager. You could do this yourself, by loading all the data into a buffer and then running through the buffer swapping every pair of bytes. Better yet, if you're using Sound Manager version 3.1 or later, you can have the Sound Manager do the byte swapping for you. You do this by invoking the 'sowt' data decompressor on the (uncompressed) 16-bit audio data. (Notice that 'sowt' is 'twos' with the bytes swapped; 16-bit data is stored in a two's-complement format.) See Listing 6 for the exact details of invoking this codec on 16-bit audio data.

It's worth mentioning that RIFF has a counterpart, RIFX, that uses Motorola byte ordering. A RIFX file is exactly like a RIFF file except that the container chunk has the ID 'RIFX' and all multi-byte values are stored in big-endian format. Naturally, if you encounter a RIFX file, you can dispense with all the byte swapping.

Opening the WAVE File

Of course, before we can start reading the data in a WAVE file, we need to open the file. On the Macintosh Operating System, a WAVE file is contained entirely in a file's data fork. Listing 3 defines a simple function that lets the user select a WAVE file for playing and then calls FSpOpenDF to open the file for reading.

Listing 3: Opening a WAVE file

short OpenWaveFile (void)
{
  StandardFileReply  myReply;
  SFTypeList        myTypeList = {'WAVE', 'BINA', 0, 0};
  short            myRefNum;
  OSErr            myErr = noErr;
  // elicit a file from the user
  StandardGetFile(NULL, 2, myTypeList, &myReply);
  if (!myReply.sfGood)
    return(-1);
  // open the file's data fork for reading
  myErr = FSpOpenDF(&myReply.sfFile, fsRdPerm, &myRefNum);
  if (myErr != noErr)
    return((short)myErr);
  else
    return(myRefNum);
}

Notice that we're allowing the user to select files whose type is either 'WAVE' or 'BINA'. I've found that files downloaded from the Internet usually have a file type of 'WAVE', while files copied over a local area network from a PC sometimes have the file type 'BINA'. To make sure that we've gotten an actual WAVE file, we can execute this code:

Listing 4: Verifying a WAVE file

myDataPtr = GetChunkData(myRefNum, kChunkType_RIFF, 0);
if (myDataPtr != NULL) {
  RIFFChunkPtr  myRIFFPtr = (RIFFChunkPtr)myDataPtr;
  FOURCC                                  myFormType;
    myFormType = myRIFFPtr->fFormType;
  DisposePtr((Ptr)myDataPtr);
  if (myFormType != kRIFFType_WAVE)
    return(badFileFormat);
}

Using the Sound Manager

Once we've opened a bona fide WAVE file, we can get the sampled sound data from it by calling GetChunkData, like this:

ChunkHeaderPtr  myDataPtr = GetChunkData(myRefNum, kChunkType_Data, 0);

If this call succeeds, it returns a pointer to a buffer that contains the entire sound data chunk, from which we can get the actual sound data by skipping over the chunk header. The natural thing to do is play the sound using the Sound Manager's bufferCmd sound command. The only relevant parameter to bufferCmd is the address of a sound header structure, which tells the Sound Manager where the audio data is and what its properties are. So, we need to get the information about the WAVE sound and put that information into the sound header structure. Listing 5 shows how to read the sound format information from the WAVE file.

Listing 5: Getting the sound format information

myDataPtr = GetChunkData(myRefNum, kChunkType_Format, 0);
if (myDataPtr != NULL) {
  FormatChunkPtr  myFormatPtr = (FormatChunkPtr)myDataPtr;
  myFormat = Swap_16(myFormatPtr->fFormatType);
  myNumChannels = Swap_16(myFormatPtr->fNumChannels);
  mySampleRate = 
            Long2Fix(Swap_32(myFormatPtr->fSamplesPerSec));
  myBitsPerSample = Swap_16(myFormatPtr->fAdditionalData);
  DisposePtr((Ptr)myDataPtr);
  // currently, we support only standard PCM encoding
  if (myFormat != WAVE_FORMAT_PCM)
    return(badFileFormat);
}

As before, we need to swap the bytes on any data read from the file that's longer than 8 bits. The fFormatType field of a format chunk specifies the type of WAVE file. Here we support only uncompressed files, which have the type WAVE_FORMAT_PCM.

Now we need to fill in a sound header with this data. The Sound Manager defines three different sound headers. Which sound header we use depends on the features of the sound to be played. Because we want to invoke the 'sowt' decompressor for 16-bit data, we'll use the sound header of type CmpSoundHeader. Listing 6 shows how to fill in the sound header.

Listing 6: Filling in a sound header

CmpSoundHeader  mySoundHeader;

// fill in a compressed sound header structure, 
mySoundHeader.samplePtr =             // skip the chunk header
                    (Ptr)myDataPtr + sizeof(ChunkHeader);
mySoundHeader.numChannels = myNumChannels;
mySoundHeader.sampleRate = mySampleRate;
mySoundHeader.loopStart = 0;
mySoundHeader.loopEnd = 0;
mySoundHeader.encode = cmpSH;
mySoundHeader.baseFrequency = kMiddleC;
mySoundHeader.numFrames = 
                (Swap_32(myDataPtr->fChunkSize) * 8) / 
                (myNumChannels * myBitsPerSample);
mySoundHeader.markerChunk = NULL;
mySoundHeader.format = kCompressType_None;
mySoundHeader.futureUse2 = 0;
mySoundHeader.stateVars = NULL;
mySoundHeader.leftOverSamples = NULL;
mySoundHeader.compressionID = notCompressed;
mySoundHeader.packetSize = 0;
mySoundHeader.snthID = 0;
mySoundHeader.sampleSize = myBitsPerSample;
// remember that data in a WAVE file is stored in little-endian byte ordering;
// accordingly, for 16-bit sounds, we need to invoke the 'sowt' decompressor
// that will swap bytes for us (available only in Sound Manager 3.1 and later)
if (myBitsPerSample == 16) {
  mySoundHeader.format = 'sowt';
  mySoundHeader.compressionID = fixedCompression;
}

This is all straightforward. Note how easy it is to invoke the 'sowt' decompressor; we just define the compression type and compression ID to the appropriate values. The next step is to send a sound command to a sound channel. We construct a sound command like this:

SndCommand      mySoundCommand;
// now play the sound using a bufferCmd
mySoundCommand.cmd = bufferCmd;
mySoundCommand.param1 = 0;        // unused with bufferCmd
mySoundCommand.param2 = (long)&mySoundHeader;

Finally, Listing 7 shows how to allocate a sound channel and pass the sound command to that channel by calling SndDoImmediate:

Listing 7: Playing a buffer of sound data

SndChannelPtr    mySoundChannel = NULL;
// allocate a sound channel
myErr = SndNewChannel(&mySoundChannel, sampledSynth, 
                                        initMono, NULL);
if (mySoundChannel != NULL)
  myErr = SndDoImmediate(mySoundChannel, &mySoundCommand);

At this point, if everything has gone according to plan, the Sound Manager will begin playing the sound that we've loaded from the WAVE file.

Looking for the Really Big Wave

Here I've shown how to open, parse, and play both 8-bit and 16-bit mono and stereo WAVE files. For commercial products, however, you'll probably want to make a few enhancements to this code. For instance, I've supposed that the sound data can always fit into the available memory. For very large WAVE files, this might not be true. In that case, you can implement a double-buffering scheme by reading small portions of the sampled sound data into one of two (or more) buffers and then playing that data using the bufferCmd sound command. This method for handling large sound files is suggested by Olson, 1995. Note that the use of the SndPlayDoubleBuffer function (as illustrated in Day, 1991) is no longer recommended.

In addition, a more complete WAVE-playing application would want to add support for the standard compression algorithms you're likely to find in compressed WAVE files, as well as beef up the sound management capabilities so that you can adjust the volume and balance of the sound, change its pitch, and so forth. Since you're using the Sound Manager to play the WAVE files, you have access to the full range of its capabilities. For instance, you can install callback routines to trigger the deallocation of the sound buffers when the sounds are finished playing. All of these capabilities are illustrated in a code sample called SndPlayDoubleBuffer written by Mark Cookson of Apple DTS that's included on the Developer CD series. (Look in the folder Sound inside the Snippets folder; better yet, check out the URL listed at the end of this article.)

Surfing with QuickTime

QuickTime, as I've reported elsewhere (Monroe, 1997), has evolved into a cross-platform vehicle for the authoring, delivery, and playback of digital multimedia content. One thing this means is that QuickTime is very, very good at playing sounds. Indeed, current versions of QuickTime are able to handle 8- and 16-bit uncompressed WAVE files, and the forthcoming version 3.0 (on both MacOS and Windows) will handle compressed WAVE data as well. So, you can use the standard QuickTime APIs to open and play WAVE files, unless you absolutely need to use Sound Manager capabilities that are not directly supported by QuickTime.

The basic idea to using QuickTime to play WAVE files is to call the NewMovieFromFile function. If the specified file does not contain a movie resource, NewMovieFromFile tries to locate a movie data import component that converts the file data into a movie, which can then be played using standard QuickTime functions. Listing 8 shows a routine that opens and plays a waveform file using the QuickTime API.

Listing 8: Playing WAVE files with QuickTime

void PlayWaveUsingQuickTime (void)
{
  StandardFileReply    myReply;
  SFTypeList          myTypeList = {'WAVE', 0, 0, 0};
  short              myRefNum;
  Movie              myMovie;
    // elicit a file from the user
  StandardGetFile(NULL, 2, myTypeList, &myReply);
  if (!myReply.sfGood)
    return;
    // use QuickTime routines to play the sound
  EnterMovies();
  OpenMovieFile(&myReply.sfFile, &myRefNum, fsRdPerm);
  NewMovieFromFile(&myMovie, myRefNum, NULL, NULL, 0, NULL);
  if (myRefNum != 0)
    CloseMovieFile(myRefNum);
  StartMovie(myMovie);
  while (!IsMovieDone(myMovie))
    MoviesTask(myMovie, 0);
  DisposeMovie(myMovie);
}

PlayWaveUsingQuickTime begins by calling StandardGetFile to elicit a file from the user. Then it opens the file and calls NewMovieFromFile to create a movie from the sampled sound data in the file. The remainder of the function simply starts the movie playing and waits until it's finished.

Now I suspect you're wondering why we bothered at all with parsing chunks and swapping bytes, if QuickTime can do it all for us? That's a good question. First, your application might be concerned primarily with playing sounds, and you might already have developed an architecture for tracking and disposing of buffers of sound data. In that case, the techniques shown earlier in this article are likely to integrate into your existing code more easily than the QuickTime technique shown in Listing 8. Also, as noted earlier, using the Sound Manager directly gives you access to a large set of tools that you can use to modify the sounds being played. QuickTime supplies some of these capabilities, but not all of them. For instance, you can use the Sound Manager's rateCmd to alter the sample rate of a sound already being played. To my knowledge, there is no way to do this using just the QuickTime APIs. Nevertheless, for the vast majority of cases, where you simply want to play a WAVE file and perhaps pause it at various times, the QuickTime solution is simpler and far more elegant.

A Brief History of IFF

The chunk architecture was developed in the mid 1980's by Electronic Arts, an interactive entertainment software developer, in conjunction with Commodore-Amiga. The goal was to be able to store data (particularly multimedia data such as sounds, images, and animation controls) in a format that makes the data easy to move from one operating system to another. A chunk is just a block of data that has both a type and a length. (The representation of a chunk type as a four character sequence was borrowed directly from the Macintosh's use of four character file types, resource types, and so forth.) Electronic Arts defined the structure of chunks and the means of storing chunks in files. This simple structure was designed to make these files easy to parse and create. See Morrison, 1985 for a description of the Interchange File Format (IFF) standard.

Electronic Arts' IFF standard was soon used by Apple as the basis for the Audio Interchange File Format (AIFF) and the Audio Interchange File Format for Compression (AIFF-C) specifications. As the names suggest, Apple used these formats primarily to store audio data such as sampled sound data or MIDI data, along with associated information about that data. In System 7.0, Apple introduced an enhanced Sound Manager that provides system software support for reading and writing AIFF files. Other manufacturers, SGI for example, have also adopted AIFF as a standard sound file format.

In Windows 3.1, Microsoft introduced its own version of the IFF standard: the Resource Interchange File Format (RIFF). RIFF supports a wide range of data types, including bitmaps, color palettes, audio-video interlaced (AVI) data, MIDI data, and waveform data. The Win32 programming interfaces include support for reading and writing RIFF files.

Bibliography and References

  • Day, Neil. "Around and Around: Multi-Buffering Sounds". develop, The Apple Technical Journal, issue 11 (August 1992), pp. 38-58.
  • Inside Macintosh: QuickTime, by Apple Computer, Inc. (Addison-Wesley, 1993).
  • Inside Macintosh: QuickTime Components, by Apple Computer, Inc. (Addison-Wesley, 1993).
  • Inside Macintosh: Sound, by Apple Computer, Inc. (Addison-Wesley, 1994).
  • Monroe, Tim. "The QuickTime Media Layer". MacTech Magazine, volume 13, no. 7 (July 1997), pp. 51-54.
  • Morrison, Jerry. EA IFF 85 Standard for Interchange Format Files (Electronic Arts, 1985).
  • Olson, Kip. "Sound Secrets". develop, The Apple Technical Journal, issue 24 (December 1995), pp. 45-55.

URLs

You can find the IFF specification (Morrison, 1985) at many sites on the World Wide Web; try http://www.sprog.auc.dk/~motr96/sirius/neuro/dev/extrefs/iff_spec.txt. For the RIFF specification, look at http://www.seanet.com/HTML/Users/matts/riffmci/riffmci.htm. For the sample double buffering application SndPlayDoubleBuffer by Mark Cookson, see http://devworld.apple.com/techsupport/source/SSound.html.

Acknowledgements

Thanks to Mark Cookson, Peter Hoddie, and Jim Reekes for reviewing this article.


Tim Monroe is a software engineer on Apple's QuickTime VR team. In his first eight years at Apple, he worked on the Inside Macintosh team, where he wrote developer documentation for QuickDraw 3D, QuickTime VR, the sound and speech technologies, and a host of other APIs. You can contact him at monroe@apple.com.

 
AAPL
$104.83
Apple Inc.
+1.84
MSFT
$45.02
Microsoft Corpora
+0.64
GOOG
$543.98
Google Inc.
+11.27

MacTech Search:
Community Search:

Software Updates via MacUpdate

Delicious Library 3.3.2 - Import, browse...
Delicious Library allows you to import, browse, and share all your books, movies, music, and video games with Delicious Library. Run your very own library from your home or office using our... Read more
Art Text 2.4.8 - Create high quality hea...
Art Text is an OS X application for creating high quality textual graphics, headings, logos, icons, Web site elements, and buttons. Thanks to multi-layer support, creating complex graphics is no... Read more
Live Interior 3D Pro 2.9.6 - Powerful an...
Live Interior 3D Pro is a powerful yet very intuitive interior designing application. View Video Tutorials It has every feature of Live Interior 3D Standard, plus some exclusive ones: Create multi... Read more
The Hit List 1.1.7 - Advanced reminder a...
The Hit List manages the daily chaos of your modern life. It's easy to learn - it's as easy as making lists. And it's powerful enough to let you plan, then forget, then act when the time is right.... Read more
jAlbum Pro 12.2.4 - Organize your digita...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code!... Read more
jAlbum 12.2.4 - Create custom photo gall...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results Simply drag and drop photos into groups, choose a design... Read more
ExpanDrive 4.1.7 - Access remote files o...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
OmniOutliner Pro 4.1.3 - Pro version of...
OmniOutliner Pro is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually designed to help you think. It'... Read more
Evernote 5.6.2 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
OmniOutliner 4.1.3 - Organize your ideas...
OmniOutliner is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually designed to help you think. It's... Read more

Latest Forum Discussions

See All

Toca Boo (Education)
Toca Boo 1.0.2 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0.2 (iTunes) Description: BOO! Did I scare you!? My name is Bonnie and my family loves to spook! Do you want to scare them back? Follow me and I'll... | Read more »
Intuon (Games)
Intuon 1.1 Device: iOS Universal Category: Games Price: $.99, Version: 1.1 (iTunes) Description: Join the battle with your intuition in a new hardcore game Intuon! How well do you trust your intuition? Can you find a needle in a... | Read more »
Ravenous Rampage (Games)
Ravenous Rampage 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: | Read more »
Partia 2 (Games)
Partia 2 1.0.1 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0.1 (iTunes) Description: Partia 2 is a SRPG (Strategy Role-playing) video game inspired by Fire Emblem and Tear Ring Saga series. In a high fantasy... | Read more »
Puzzle to the Center of the Earth Review
Puzzle to the Center of the Earth Review By Campbell Bird on October 23rd, 2014 Our Rating: :: SPELUNKING PUZZLESUniversal App - Designed for iPhone and iPad Do some puzzles to make some platforms in this smart and fun free-to-play... | Read more »
Sleep Attack TD Review
Sleep Attack TD Review By Jennifer Allen on October 23rd, 2014 Our Rating: :: A TRUE TWISTUniversal App - Designed for iPhone and iPad Sleep Attack TD is a tower defense game with a difference – you can rotate the layout – and it’s... | Read more »
Mecanic (Education)
Mecanic 1.0 Device: iOS Universal Category: Education Price: $1.99, Version: 1.0 (iTunes) Description: Plates, screws, wheels ... Everything you need to achieve whatever you want... MECHANICWith 'MECANIC' kids will have fun... | Read more »
Earn Your Master Camper Badge in Camp Po...
Earn Your Master Camper Badge in Camp Pokemon Posted by Jessica Fisher on October 23rd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Garruk Gets His Revenge in a New Magic 2...
Garruk Gets His Revenge in a New Magic 2015 Expansion, Coming This November Posted by Jessica Fisher on October 23rd, 2014 [ permalink ] | Read more »
Sentinels of the Multiverse Review
Sentinels of the Multiverse Review By Rob Thomas on October 23rd, 2014 Our Rating: :: SENTINELS ASSEMBLEiPad Only App - Designed for the iPad Greater Than Games’ tabletop classic, Sentinels of the Multiverse swoops in to save the... | Read more »

Price Scanner via MacPrices.net

Save up to $125 on Retina MacBook Pros
B&H Photo has the new 2014 13″ and 15″ Retina MacBook Pros on sale for up to $125 off MSRP. Shipping is free, and B&H charges NY sales tax only. They’ll also include free copies of Parallels... Read more
Apple refurbished Time Capsules available sta...
The Apple Store has certified refurbished Time Capsules available for up to $60 off MSRP. Apple’s one-year warranty is included with each Time Capsule, and shipping is free: - 2TB Time Capsule: $255... Read more
Textilus New Word, Notes and PDF Processor fo...
Textilus is new word-crunching, notes, and PDF processor designed exclusively for the iPad. I haven’t had time to thoroughly check it out yet, but it looks great and early reviews are positive.... Read more
WD My Passport Pro Bus-Powered Thunderbolt RA...
WD’s My Passport Pro RAID solution is powered by an integrated Thunderbolt cable for true portability and speeds as high as 233 MB/s. HighlightsOverviewSpecifications Transfer, Back Up And Edit In... Read more
Save with Best Buy’s College Student Deals
Take an additional $50 off all MacBooks and iMacs at Best Buy Online with their College Students Deals Savings, valid through November 1st. Anyone with a valid .EDU email address can take advantage... Read more
iPad Air 2 & iPad mini 3 Best Tablets Yet...
The new iPads turned out to be pretty much everything I’d been hoping for and more than I’d expected.”More” particularly in terms of a drinking-from-a-firehose choice of models and configurations,... Read more
Drafts 4 Reinvents iOS Productivity App
N Richland Hills, Texas based Agile Tortoise has announced the release of Drafts 4 for iPhone and iPad. Drafts is a quick capture note taking app with flexible output actions. Drafts 4 scales from... Read more
AT&T accepting preorders for new iPads fo...
AT&T Wireless is accepting preorders for the new iPad Air 2 and iPad mini 3, cellular models, for $100 off MSRP with a 2-year service agreement: - 16GB iPad Air 2 WiFi + Cellular: $529.99 - 64GB... Read more
Apple offering refurbished Mac Pros for up to...
The Apple Store is offering Apple Certified Refurbished 2013 Mac Pros for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more
Select MacBook Airs $100 off MSRP, free shipp...
B&H Photo has 2014 a couple of MacBook Airs on sale for $100 off MSRP. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels Desktop and LoJack for... Read more

Jobs Board

Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.