AIFF: Complete Guide to Audio Interchange File Format
AIFF (Audio Interchange File Format) was developed by Apple in 1988, based on the IFF (Interchange File Format) container from Electronic Arts. AIFF stores uncompressed PCM audio — the same raw waveform data as WAV — making it a lossless format used extensively in Apple's professional audio ecosystem (Logic Pro, Final Cut Pro, GarageBand). Its compressed variant AIFF-C (also called AIFC) supports codec-compressed audio including ALAC (Apple Lossless).
IFF-Based Chunk Architecture
Like WAV/RIFF, AIFF uses a chunk-based structure. However, AIFF is derived from IFF which uses big-endian byte order (most significant byte first), while WAV/RIFF uses little-endian. This distinction matters when writing binary parsers.
FORM chunk (top level):
Chunk ID: "FORM" (4 bytes ASCII)
Chunk Size: total size − 8 (4 bytes, big-endian uint32)
Form Type: "AIFF" or "AIFC" (4 bytes ASCII)
Chunks (in any order, except COMM must precede SSND):
COMM — Common Chunk (audio format parameters)
SSND — Sound Data Chunk (PCM samples)
MARK — Markers Chunk (named timestamps)
INST — Instrument Chunk (MIDI playback parameters)
MIDI — MIDI Data Chunk
AESD — Audio Recording Chunk (recording session metadata)
APPL — Application Specific Chunk
NAME — Name Chunk (track title)
AUTH — Author Chunk
(c) — Copyright Chunk
ANNO — Annotation Chunks (arbitrary comments)
For AIFF-C (AIFC), the COMM chunk is extended with a compression type field and a human-readable compression name.
COMM Chunk (18 bytes for AIFF, 24+ bytes for AIFC)
numChannels: int16 — number of channels
numSampleFrames: uint32 — total number of sample frames
sampleSize: int16 — bits per sample (8, 16, 24, 32)
sampleRate: 80-bit extended float — IEEE 754 80-bit (10 bytes!)
// AIFF-C only:
compressionType: OSType — 'NONE', 'sowt', 'alaw', 'ulaw', 'ALAC', 'fl32'...
compressionName: Pascal string — human readable
Important: The sample rate is stored as an 80-bit extended precision float — a format not natively supported by most languages. You must implement the decoder manually or use a library.
SSND Chunk
SSND header (8 bytes for the chunk, then):
offset: uint32 — bytes to skip in data block (usually 0)
blockSize: uint32 — block alignment size (usually 0)
sampleData: raw interleaved PCM bytes
PCM samples in AIFF are stored big-endian and signed for all bit depths (including 8-bit, unlike WAV which uses unsigned 8-bit).
Python AIFF Processing
Standard Library aifc Module
import aifc
import struct
# ---- Read an AIFF file ----
with aifc.open('input.aiff', 'r') as af:
n_channels = af.getnchannels()
sample_width = af.getsampwidth() # bytes per sample
frame_rate = af.getframerate()
n_frames = af.getnframes()
compression = af.getcomptype() # b'NONE', b'ulaw', etc.
duration = n_frames / frame_rate
print(f"Channels : {n_channels}")
print(f"Sample width: {sample_width} bytes ({sample_width * 8}-bit)")
print(f"Sample rate : {frame_rate} Hz")
print(f"Duration : {duration:.3f} s")
print(f"Compression : {compression}")
# Read raw frames
raw_data = af.readframes(n_frames)
# Unpack 16-bit stereo samples (big-endian)
n_samples = n_frames * n_channels
samples = struct.unpack(f'>{n_samples}h', raw_data) # Note: '>' for big-endian
# ---- Write an AIFF file ----
import numpy as np
sample_rate = 44100
freq = 440 # A4
duration_s = 2.0
t = np.linspace(0, duration_s, int(sample_rate * duration_s), endpoint=False)
waveform = (np.sin(2 * np.pi * freq * t) * 32767).astype(np.int16)
with aifc.open('sine_440.aiff', 'w') as af:
af.setnchannels(1)
af.setsampwidth(2) # 2 bytes = 16-bit
af.setframerate(sample_rate)
af.setcomptype(b'NONE', b'not compressed')
# Convert little-endian numpy int16 to big-endian for AIFF
af.writeframes(waveform.astype('>i2').tobytes())
soundfile for Advanced AIFF Support
import soundfile as sf
import numpy as np
# Read AIFF / AIFC
data, samplerate = sf.read('input.aiff')
print(f"Shape: {data.shape}, rate: {samplerate}, dtype: {data.dtype}")
# Write AIFF-24bit
sf.write(
'output_24bit.aiff',
data,
samplerate,
subtype='PCM_24',
format='AIFF'
)
# Available AIFF subtypes:
print(sf.available_subtypes('AIFF'))
# {'PCM_U8', 'PCM_16', 'PCM_24', 'PCM_32', 'FLOAT', 'DOUBLE', ...}
# Convert WAV to AIFF
data_wav, rate_wav = sf.read('input.wav')
sf.write('output.aiff', data_wav, rate_wav, subtype='PCM_24', format='AIFF')
FFmpeg AIFF Conversion
# Convert WAV to AIFF (lossless, same quality)
ffmpeg -i input.wav -c:a pcm_s24be output.aiff
# pcm_s24be = 24-bit signed PCM big-endian (AIFF native)
# Convert MP3/FLAC to AIFF
ffmpeg -i input.flac -c:a pcm_s16be output.aiff
ffmpeg -i input.mp3 -c:a pcm_s24be output.aiff
# Batch convert AIFF to FLAC (smaller, lossless)
for f in *.aiff; do
ffmpeg -i "$f" -c:a flac "${f%.aiff}.flac"
done
# Convert AIFF to WAV (studio exchange)
ffmpeg -i input.aiff -c:a pcm_s24le output.wav
# Note: pcm_s24le = 24-bit little-endian (WAV native)
# Inspect AIFF file info
ffprobe -v quiet -print_format json -show_streams input.aiff
Parsing the 80-Bit Extended Float (AIFF Sample Rate)
The 80-bit IEEE 754 extended precision format used for AIFF sample rate requires custom decoding:
import struct
def read_80bit_float(data: bytes) -> float:
"""Decode an 80-bit IEEE 754 extended precision float (big-endian)."""
# Format: 1 sign bit, 15 exponent bits, 64 mantissa bits
assert len(data) >= 10
sign = (data[0] & 0x80) >> 7
exponent = ((data[0] & 0x7F) << 8) | data[1]
mantissa = int.from_bytes(data[2:10], 'big')
if exponent == 0 and mantissa == 0:
return 0.0
if exponent == 0x7FFF:
return float('inf') if mantissa == 0 else float('nan')
# Unbias the exponent (bias = 16383)
f = mantissa * (2 ** (exponent - 16383 - 63))
return -f if sign else f
def parse_aiff_comm(path: str) -> dict:
"""Extract AIFF COMM chunk fields."""
with open(path, 'rb') as f:
form_id = f.read(4) # FORM
form_size = struct.unpack('>I', f.read(4))[0]
form_type = f.read(4) # AIFF or AIFC
while True:
chunk_id = f.read(4)
if not chunk_id or len(chunk_id) < 4: break
chunk_size = struct.unpack('>I', f.read(4))[0]
data = f.read(chunk_size + (chunk_size % 2)) # aligned to 2 bytes
if chunk_id == b'COMM':
n_ch = struct.unpack('>h', data[0:2])[0]
n_fr = struct.unpack('>I', data[2:6])[0]
bps = struct.unpack('>h', data[6:8])[0]
rate = read_80bit_float(data[8:18])
return {'channels': n_ch, 'frames': n_fr, 'bps': bps, 'rate': rate}
return {}
info = parse_aiff_comm('track.aiff')
print(info) # {'channels': 2, 'frames': 4410000, 'bps': 24, 'rate': 44100.0}
AIFF vs WAV vs FLAC
| Feature | AIFF | WAV | FLAC |
|---|---|---|---|
| Byte order | Big-endian | Little-endian | Variable (FLAC native) |
| Max bit depth | 32-bit float | 32-bit float | 32-bit integer |
| Compression | AIFC (various) | Internal (various) | DEFLATE lossless |
| Apple native | Yes | Limited (via QuickTime) | Via add-ons |
| Metadata richness | Moderate (MARK/NAME/AUTH) | BWF extension | Vorbis Comment |
| File size | Large (uncompressed) | Large (uncompressed) | ~55% of WAV |
| Professional use | Mac/Logic Pro | Windows/Pro Tools | Archival/delivery |
| Max size | ~4 GB (32-bit FORM size) | ~4 GB (RF64 workaround exists) | No practical limit |
Common Pitfalls
- Big-endian byte order surprises. Every multi-byte field in AIFF is big-endian. Writing
struct.pack('<h', value)(little-endian) when you needstruct.pack('>h', value)is a silent error that produces valid-looking but incorrect sample values. - The 80-bit float sample rate. No mainstream language has native 80-bit float support. Always use a dedicated decoder — the
aifcmodule handles it internally;soundfilehandles it via libsndfile. - AIFF-C vs AIFF confusion. Many applications write
AIFCfiles even for uncompressed audio (withcompressionType='NONE'). Parsers that checkForm Type == "AIFF"strictly will reject these files. Check for bothAIFFandAIFCform types. - Logic Pro writes AIFF; Pro Tools prefers WAV. When exchanging sessions between Mac-centric and Windows-centric studios, convert AIFF to WAV to avoid codec detection issues in Pro Tools.
Related conversions
Audio format pairs that come up most often: