Task 022: .AIFF File Format

Task 022: .AIFF File Format

File Format Specifications for .AIFF

The Audio Interchange File Format (.AIFF) is a standard for storing digital audio data, developed by Apple Computer based on the Electronic Arts Interchange File Format (EA IFF 85). It uses a chunk-based structure with big-endian byte ordering for headers and metadata. The file begins with a FORM chunk identifying it as 'AIFF' (or 'AIFC' for the compressed variant). Required chunks include COMM (common audio parameters) and SSND (sound data). Optional chunks provide additional metadata such as markers, loops, comments, and text fields. Audio data is typically uncompressed PCM, but AIFC supports compression types like A-law, μ-law, and floating-point formats.

List of all the properties of this file format intrinsic to its file system:

  • Form Type: The identifier specifying 'AIFF' or 'AIFC'.
  • Format Version: A timestamp (seconds since January 1, 1904, UTC) for AIFC files, from the FVER chunk.
  • Number of Channels: The count of audio channels (e.g., 1 for mono, 2 for stereo).
  • Number of Sample Frames: The total number of sample frames in the audio data.
  • Sample Size: The bit depth per sample (1-32 bits, left-justified and zero-padded to multiples of 8 bits).
  • Sample Rate: The sampling frequency in Hz, stored as an 80-bit IEEE extended floating-point value.
  • Compression Type: A 4-byte code (e.g., 'NONE' for uncompressed PCM, 'sowt' for little-endian PCM, 'fl32' for 32-bit float).
  • Compression Name: A Pascal string describing the compression (optional for AIFC).
  • Sound Data Offset: Byte offset within the SSND chunk to the start of audio samples.
  • Sound Data Block Size: Nominal block size for aligned data (typically 0).
  • Markers: A list of markers, each with an ID (unique positive integer), position (sample frame index), and name (Pascal string).
  • Sustain Loop: Loop details including play mode (0: none, 1: forward, 2: forward/backward), begin marker ID, and end marker ID.
  • Release Loop: Similar to sustain loop, for the release phase.
  • Base Note: MIDI note number (0-127) for the sample's base pitch.
  • Detune: Fine tuning in cents (-50 to +50).
  • Low Note: Lowest MIDI note for playback range.
  • High Note: Highest MIDI note for playback range.
  • Low Velocity: Lowest MIDI velocity for playback range (1-127).
  • High Velocity: Highest MIDI velocity for playback range (1-127).
  • Gain: Volume adjustment in dB.
  • Comments: A list of comments, each with a timestamp (seconds since January 1, 1904, UTC), associated marker ID, and text.
  • Name: The file or audio name (from NAME chunk).
  • Author: The creator's name (from AUTH chunk).
  • Copyright: Copyright notice (from (c) chunk).
  • Annotations: A list of annotation strings (from ANNO chunks).
  • AES Channel Status Data: 24-byte array per AES standard (e.g., emphasis information).
  • MIDI Data: Raw MIDI bytes (e.g., SysEx data) from MIDI chunks.
  • Application Specific Data: Custom data with a 4-byte application signature (from APPL chunks).

Two direct download links for files of format .AIFF:

HTML/JavaScript for drag-and-drop .AIFF file property dump:

AIFF Property Dumper
Drag and drop .AIFF file here
  1. Python class for .AIFF handling:
import struct
import sys
from typing import Dict, List, Any

class AIFFHandler:
    def __init__(self, filename: str):
        self.filename = filename
        self.properties: Dict[str, Any] = {}
        self.audio_data: bytes = b''
        self._read()

    def _read_extended(self, data: bytes) -> float:
        # Simplified 80-bit to float64
        return struct.unpack('>d', data[:8])[0]  # Approximate

    def _read_pstring(self, f) -> str:
        len_byte = f.read(1)
        if not len_byte:
            return ''
        length = struct.unpack('>B', len_byte)[0]
        s = f.read(length).decode('ascii')
        if (length + 1) % 2 != 0:
            f.read(1)  # Pad
        return s

    def _read(self):
        with open(self.filename, 'rb') as f:
            chunk_id = f.read(4).decode('ascii')
            if chunk_id != 'FORM':
                raise ValueError('Not an AIFF file')
            form_size = struct.unpack('>I', f.read(4))[0]
            self.properties['Form Type'] = f.read(4).decode('ascii')

            while f.tell() < form_size + 8:
                chunk_id = f.read(4).decode('ascii')
                chunk_size = struct.unpack('>I', f.read(4))[0]
                pos = f.tell()

                if chunk_id == 'FVER':
                    self.properties['Format Version'] = struct.unpack('>I', f.read(4))[0]
                elif chunk_id == 'COMM':
                    self.properties['Number of Channels'] = struct.unpack('>H', f.read(2))[0]
                    self.properties['Number of Sample Frames'] = struct.unpack('>I', f.read(4))[0]
                    self.properties['Sample Size'] = struct.unpack('>H', f.read(2))[0]
                    ext = f.read(10)
                    self.properties['Sample Rate'] = self._read_extended(ext)
                    if self.properties['Form Type'] == 'AIFC':
                        self.properties['Compression Type'] = f.read(4).decode('ascii')
                        self.properties['Compression Name'] = self._read_pstring(f)
                elif chunk_id == 'SSND':
                    self.properties['Sound Data Offset'] = struct.unpack('>I', f.read(4))[0]
                    self.properties['Sound Data Block Size'] = struct.unpack('>I', f.read(4))[0]
                    f.seek(self.properties['Sound Data Offset'], 1)
                    data_size = chunk_size - 8 - self.properties['Sound Data Offset']
                    self.audio_data = f.read(data_size)
                elif chunk_id == 'MARK':
                    self.properties['Markers'] = []
                    num_markers = struct.unpack('>H', f.read(2))[0]
                    for _ in range(num_markers):
                        mid = struct.unpack('>H', f.read(2))[0]
                        pos = struct.unpack('>I', f.read(4))[0]
                        name = self._read_pstring(f)
                        self.properties['Markers'].append({'id': mid, 'position': pos, 'name': name})
                elif chunk_id == 'INST':
                    self.properties['Base Note'] = struct.unpack('>b', f.read(1))[0]
                    self.properties['Detune'] = struct.unpack('>b', f.read(1))[0]
                    self.properties['Low Note'] = struct.unpack('>b', f.read(1))[0]
                    self.properties['High Note'] = struct.unpack('>b', f.read(1))[0]
                    self.properties['Low Velocity'] = struct.unpack('>b', f.read(1))[0]
                    self.properties['High Velocity'] = struct.unpack('>b', f.read(1))[0]
                    self.properties['Gain'] = struct.unpack('>h', f.read(2))[0]
                    sl_pm = struct.unpack('>h', f.read(2))[0]
                    sl_begin = struct.unpack('>h', f.read(2))[0]
                    sl_end = struct.unpack('>h', f.read(2))[0]
                    self.properties['Sustain Loop'] = {'playMode': sl_pm, 'begin': sl_begin, 'end': sl_end}
                    rl_pm = struct.unpack('>h', f.read(2))[0]
                    rl_begin = struct.unpack('>h', f.read(2))[0]
                    rl_end = struct.unpack('>h', f.read(2))[0]
                    self.properties['Release Loop'] = {'playMode': rl_pm, 'begin': rl_begin, 'end': rl_end}
                elif chunk_id == 'COMT':
                    self.properties['Comments'] = []
                    num_comments = struct.unpack('>H', f.read(2))[0]
                    for _ in range(num_comments):
                        ts = struct.unpack('>I', f.read(4))[0]
                        mid = struct.unpack('>H', f.read(2))[0]
                        count = struct.unpack('>H', f.read(2))[0]
                        text = f.read(count).decode('ascii')
                        if (count % 2 != 0):
                            f.read(1)
                        self.properties['Comments'].append({'timestamp': ts, 'markerID': mid, 'text': text})
                elif chunk_id in ['NAME', 'AUTH', '(c) ', 'ANNO']:
                    key = {'NAME': 'Name', 'AUTH': 'Author', '(c) ': 'Copyright', 'ANNO': 'Annotations'}[chunk_id]
                    val = f.read(chunk_size).decode('ascii').rstrip('\x00')
                    if key == 'Annotations':
                        if key not in self.properties:
                            self.properties[key] = []
                        self.properties[key].append(val)
                    else:
                        self.properties[key] = val
                    if chunk_size % 2 != 0:
                        f.read(1)
                elif chunk_id == 'AESD':
                    self.properties['AES Channel Status Data'] = list(f.read(24))
                elif chunk_id == 'MIDI':
                    self.properties['MIDI Data'] = list(f.read(chunk_size))
                elif chunk_id == 'APPL':
                    sig = f.read(4).decode('ascii')
                    data = list(f.read(chunk_size - 4))
                    self.properties['Application Specific Data'] = {'signature': sig, 'data': data}

                if (f.tell() - pos) % 2 != 0:
                    f.read(1)  # Pad

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, output_filename: str):
        with open(output_filename, 'wb') as f:
            # Simplified write: assumes minimal AIFF with COMM and SSND
            f.write(b'FORM')
            # Placeholder size, update later
            f.write(struct.pack('>I', 0))
            f.write(self.properties['Form Type'].encode('ascii'))
            # COMM chunk
            comm_data = struct.pack('>H', self.properties['Number of Channels'])
            comm_data += struct.pack('>I', self.properties['Number of Sample Frames'])
            comm_data += struct.pack('>H', self.properties['Sample Size'])
            # Extended sample rate (simplified)
            ext_sr = struct.pack('>Q', int(self.properties['Sample Rate'])) + b'\x00\x00'
            comm_data += ext_sr
            if self.properties['Form Type'] == 'AIFC':
                comm_data += self.properties['Compression Type'].encode('ascii')
                pstr = bytes([len(self.properties['Compression Name'])]) + self.properties['Compression Name'].encode('ascii')
                if len(pstr) % 2 == 0:
                    pstr += b'\x00'
                comm_data += pstr
            f.write(b'COMM')
            f.write(struct.pack('>I', len(comm_data)))
            f.write(comm_data)
            # SSND chunk
            ssnd_header = struct.pack('>II', self.properties.get('Sound Data Offset', 0), self.properties.get('Sound Data Block Size', 0))
            f.write(b'SSND')
            f.write(struct.pack('>I', len(ssnd_header) + len(self.audio_data)))
            f.write(ssnd_header)
            f.write(self.audio_data)
            # Update form size
            total_size = f.tell() - 8
            f.seek(4)
            f.write(struct.pack('>I', total_size))
            # TODO: Add other chunks for full write

# Example usage:
# handler = AIFFHandler('input.aiff')
# handler.print_properties()
# handler.write('output.aiff')
  1. Java class for .AIFF handling:
import java.io.*;
import java.nio.*;
import java.util.*;

public class AIFFHandler {
    private String filename;
    private Map<String, Object> properties = new HashMap<>();
    private byte[] audioData = new byte[0];

    public AIFFHandler(String filename) throws IOException {
        this.filename = filename;
        read();
    }

    private double readExtended(ByteBuffer bb) {
        // Approximate 80-bit to double
        return bb.getDouble();
    }

    private String readPString(ByteBuffer bb) {
        int len = bb.get() & 0xFF;
        byte[] strBytes = new byte[len];
        bb.get(strBytes);
        if ((len + 1) % 2 != 0) bb.get(); // Pad
        return new String(strBytes);
    }

    private void read() throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
            ByteBuffer bb = ByteBuffer.allocate((int) raf.length()).order(ByteOrder.BIG_ENDIAN);
            raf.getChannel().read(bb);
            bb.flip();

            String chunkId = new String(new byte[]{bb.get(), bb.get(), bb.get(), bb.get()});
            if (!"FORM".equals(chunkId)) throw new IOException("Not AIFF");
            int formSize = bb.getInt();
            properties.put("Form Type", new String(new byte[]{bb.get(), bb.get(), bb.get(), bb.get()}));

            while (bb.position() < formSize + 8) {
                chunkId = new String(new byte[]{bb.get(), bb.get(), bb.get(), bb.get()});
                int chunkSize = bb.getInt();
                int pos = bb.position();

                if ("FVER".equals(chunkId)) {
                    properties.put("Format Version", bb.getInt());
                } else if ("COMM".equals(chunkId)) {
                    properties.put("Number of Channels", bb.getShort() & 0xFFFF);
                    properties.put("Number of Sample Frames", bb.getInt());
                    properties.put("Sample Size", bb.getShort() & 0xFFFF);
                    byte[] ext = new byte[10];
                    bb.get(ext);
                    ByteBuffer extBb = ByteBuffer.wrap(ext).order(ByteOrder.BIG_ENDIAN);
                    properties.put("Sample Rate", readExtended(extBb));
                    if ("AIFC".equals(properties.get("Form Type"))) {
                        properties.put("Compression Type", new String(new byte[]{bb.get(), bb.get(), bb.get(), bb.get()}));
                        properties.put("Compression Name", readPString(bb));
                    }
                } else if ("SSND".equals(chunkId)) {
                    properties.put("Sound Data Offset", bb.getInt());
                    properties.put("Sound Data Block Size", bb.getInt());
                    bb.position(bb.position() + (Integer) properties.get("Sound Data Offset"));
                    int dataSize = chunkSize - 8 - (Integer) properties.get("Sound Data Offset");
                    audioData = new byte[dataSize];
                    bb.get(audioData);
                } else if ("MARK".equals(chunkId)) {
                    List<Map<String, Object>> markers = new ArrayList<>();
                    int numMarkers = bb.getShort() & 0xFFFF;
                    for (int i = 0; i < numMarkers; i++) {
                        Map<String, Object> marker = new HashMap<>();
                        marker.put("id", bb.getShort() & 0xFFFF);
                        marker.put("position", bb.getInt());
                        marker.put("name", readPString(bb));
                        markers.add(marker);
                    }
                    properties.put("Markers", markers);
                } else if ("INST".equals(chunkId)) {
                    properties.put("Base Note", bb.get());
                    properties.put("Detune", bb.get());
                    properties.put("Low Note", bb.get());
                    properties.put("High Note", bb.get());
                    properties.put("Low Velocity", bb.get());
                    properties.put("High Velocity", bb.get());
                    properties.put("Gain", bb.getShort());
                    Map<String, Integer> sLoop = new HashMap<>();
                    sLoop.put("playMode", bb.getShort());
                    sLoop.put("begin", bb.getShort());
                    sLoop.put("end", bb.getShort());
                    properties.put("Sustain Loop", sLoop);
                    Map<String, Integer> rLoop = new HashMap<>();
                    rLoop.put("playMode", bb.getShort());
                    rLoop.put("begin", bb.getShort());
                    rLoop.put("end", bb.getShort());
                    properties.put("Release Loop", rLoop);
                } else if ("COMT".equals(chunkId)) {
                    List<Map<String, Object>> comments = new ArrayList<>();
                    int numComments = bb.getShort() & 0xFFFF;
                    for (int i = 0; i < numComments; i++) {
                        Map<String, Object> comment = new HashMap<>();
                        comment.put("timestamp", bb.getInt());
                        comment.put("markerID", bb.getShort() & 0xFFFF);
                        int count = bb.getShort() & 0xFFFF;
                        byte[] textBytes = new byte[count];
                        bb.get(textBytes);
                        comment.put("text", new String(textBytes));
                        if (count % 2 != 0) bb.get();
                        comments.add(comment);
                    }
                    properties.put("Comments", comments);
                } else if (List.of("NAME", "AUTH", "(c) ", "ANNO").contains(chunkId)) {
                    String key = switch (chunkId) {
                        case "NAME" -> "Name";
                        case "AUTH" -> "Author";
                        case "(c) " -> "Copyright";
                        default -> "Annotations";
                    };
                    String val = new String(new byte[chunkSize]);
                    bb.get(new byte[chunkSize]);
                    if ("Annotations".equals(key)) {
                        List<String> annos = (List<String>) properties.getOrDefault(key, new ArrayList<>());
                        annos.add(val.trim());
                        properties.put(key, annos);
                    } else {
                        properties.put(key, val.trim());
                    }
                    if (chunkSize % 2 != 0) bb.get();
                } else if ("AESD".equals(chunkId)) {
                    byte[] aes = new byte[24];
                    bb.get(aes);
                    properties.put("AES Channel Status Data", aes);
                } else if ("MIDI".equals(chunkId)) {
                    byte[] midi = new byte[chunkSize];
                    bb.get(midi);
                    properties.put("MIDI Data", midi);
                } else if ("APPL".equals(chunkId)) {
                    String sig = new String(new byte[]{bb.get(), bb.get(), bb.get(), bb.get()});
                    byte[] data = new byte[chunkSize - 4];
                    bb.get(data);
                    Map<String, Object> appData = new HashMap<>();
                    appData.put("signature", sig);
                    appData.put("data", data);
                    properties.put("Application Specific Data", appData);
                }

                bb.position(pos + chunkSize);
                if (bb.position() % 2 != 0) bb.get(); // Pad
            }
        }
    }

    public void printProperties() {
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String outputFilename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            ByteBuffer bb = ByteBuffer.allocate(1024 * 1024).order(ByteOrder.BIG_ENDIAN); // Buffer
            bb.put("FORM".getBytes());
            bb.putInt(0); // Placeholder size
            bb.put(((String) properties.get("Form Type")).getBytes());
            // COMM
            ByteBuffer commBb = ByteBuffer.allocate(256).order(ByteOrder.BIG_ENDIAN);
            commBb.putShort(((Integer) properties.get("Number of Channels")).shortValue());
            commBb.putInt((Integer) properties.get("Number of Sample Frames"));
            commBb.putShort(((Integer) properties.get("Sample Size")).shortValue());
            commBb.putDouble((Double) properties.get("Sample Rate")); // Approx
            commBb.put(new byte[2]); // Pad for extended
            if ("AIFC".equals(properties.get("Form Type"))) {
                commBb.put(((String) properties.get("Compression Type")).getBytes());
                String cName = (String) properties.get("Compression Name");
                commBb.put((byte) cName.length());
                commBb.put(cName.getBytes());
                if ((cName.length() + 1) % 2 == 0) commBb.put((byte) 0);
            }
            commBb.flip();
            bb.put("COMM".getBytes());
            bb.putInt(commBb.limit());
            bb.put(commBb);
            // SSND
            bb.put("SSND".getBytes());
            int offset = (Integer) properties.getOrDefault("Sound Data Offset", 0);
            int blockSize = (Integer) properties.getOrDefault("Sound Data Block Size", 0);
            bb.putInt(8 + offset + audioData.length);
            bb.putInt(offset);
            bb.putInt(blockSize);
            bb.position(bb.position() + offset);
            bb.put(audioData);
            // Update form size
            int totalSize = bb.position() - 8;
            bb.putInt(4, totalSize);
            bb.flip();
            fos.write(bb.array(), 0, bb.limit());
            // TODO: Add other chunks
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     AIFFHandler handler = new AIFFHandler("input.aiff");
    //     handler.printProperties();
    //     handler.write("output.aiff");
    // }
}
  1. JavaScript class for .AIFF handling:
class AIFFHandler {
    constructor(buffer) {
        this.properties = {};
        this.audioData = new Uint8Array(0);
        this.parse(new DataView(buffer));
    }

    parse(dv) {
        let offset = 0;
        const readID = () => String.fromCharCode(dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++));
        const readUint32 = () => dv.getUint32(offset, false); offset += 4;
        const readUint16 = () => dv.getUint16(offset, false); offset += 2;
        const readExtended = () => {
            // Approximate
            offset += 10;
            return dv.getFloat64(offset - 10, false);
        };
        const readPString = () => {
            const len = dv.getUint8(offset++);
            let str = '';
            for (let i = 0; i < len; i++) str += String.fromCharCode(dv.getUint8(offset++));
            if ((len + 1) % 2 !== 0) offset++;
            return str;
        };

        if (readID() !== 'FORM') throw new Error('Not AIFF');
        const formSize = readUint32();
        this.properties['Form Type'] = readID();

        while (offset < formSize + 8) {
            const chunkID = readID();
            const chunkSize = readUint32();
            const chunkStart = offset;

            if (chunkID === 'FVER') {
                this.properties['Format Version'] = readUint32();
            } else if (chunkID === 'COMM') {
                this.properties['Number of Channels'] = readUint16();
                this.properties['Number of Sample Frames'] = readUint32();
                this.properties['Sample Size'] = readUint16();
                this.properties['Sample Rate'] = readExtended();
                if (this.properties['Form Type'] === 'AIFC') {
                    this.properties['Compression Type'] = readID();
                    this.properties['Compression Name'] = readPString();
                }
            } else if (chunkID === 'SSND') {
                this.properties['Sound Data Offset'] = readUint32();
                this.properties['Sound Data Block Size'] = readUint32();
                offset += this.properties['Sound Data Offset'];
                const dataSize = chunkSize - 8 - this.properties['Sound Data Offset'];
                this.audioData = new Uint8Array(dv.buffer, offset, dataSize);
                offset += dataSize;
            } else if (chunkID === 'MARK') {
                this.properties['Markers'] = [];
                const numMarkers = readUint16();
                for (let i = 0; i < numMarkers; i++) {
                    const id = readUint16();
                    const pos = readUint32();
                    const name = readPString();
                    this.properties['Markers'].push({ id, position: pos, name });
                }
            } else if (chunkID === 'INST') {
                this.properties['Base Note'] = dv.getInt8(offset++);
                this.properties['Detune'] = dv.getInt8(offset++);
                this.properties['Low Note'] = dv.getInt8(offset++);
                this.properties['High Note'] = dv.getInt8(offset++);
                this.properties['Low Velocity'] = dv.getInt8(offset++);
                this.properties['High Velocity'] = dv.getInt8(offset++);
                this.properties['Gain'] = dv.getInt16(offset, false); offset += 2;
                this.properties['Sustain Loop'] = {
                    playMode: dv.getInt16(offset, false), begin: dv.getInt16(offset + 2, false), end: dv.getInt16(offset + 4, false)
                }; offset += 6;
                this.properties['Release Loop'] = {
                    playMode: dv.getInt16(offset, false), begin: dv.getInt16(offset + 2, false), end: dv.getInt16(offset + 4, false)
                }; offset += 6;
            } else if (chunkID === 'COMT') {
                this.properties['Comments'] = [];
                const numComments = readUint16();
                for (let i = 0; i < numComments; i++) {
                    const ts = readUint32();
                    const mid = readUint16();
                    const count = readUint16();
                    let text = '';
                    for (let j = 0; j < count; j++) text += String.fromCharCode(dv.getUint8(offset++));
                    if (count % 2 !== 0) offset++;
                    this.properties['Comments'].push({ timestamp: ts, markerID: mid, text });
                }
            } else if (['NAME', 'AUTH', '(c) ', 'ANNO'].includes(chunkID)) {
                const key = chunkID === 'NAME' ? 'Name' : chunkID === 'AUTH' ? 'Author' : chunkID === '(c) ' ? 'Copyright' : 'Annotations';
                const val = readPString();
                if (key === 'Annotations') {
                    if (!this.properties[key]) this.properties[key] = [];
                    this.properties[key].push(val);
                } else {
                    this.properties[key] = val;
                }
            } else if (chunkID === 'AESD') {
                this.properties['AES Channel Status Data'] = [];
                for (let i = 0; i < 24; i++) this.properties['AES Channel Status Data'].push(dv.getUint8(offset++));
            } else if (chunkID === 'MIDI') {
                this.properties['MIDI Data'] = [];
                for (let i = 0; i < chunkSize; i++) this.properties['MIDI Data'].push(dv.getUint8(offset++));
            } else if (chunkID === 'APPL') {
                const sig = readID();
                this.properties['Application Specific Data'] = { signature: sig, data: [] };
                for (let i = 0; i < chunkSize - 4; i++) this.properties['Application Specific Data'].data.push(dv.getUint8(offset++));
            } else {
                offset += chunkSize;
            }

            offset = chunkStart + chunkSize;
            if (offset % 2 !== 0) offset++;
        }
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 2));
    }

    write() {
        // Returns a Blob for download or save
        const buffer = new ArrayBuffer(1024 * 1024);
        const dv = new DataView(buffer);
        let offset = 0;
        const writeID = (id) => { for (let c of id) dv.setUint8(offset++, c.charCodeAt(0)); };
        const writeUint32 = (val) => { dv.setUint32(offset, val, false); offset += 4; };
        const writeUint16 = (val) => { dv.setUint16(offset, val, false); offset += 2; };
        const writeExtended = (val) => {
            dv.setFloat64(offset, val, false);
            offset += 8;
            dv.setUint16(offset, 0, false);
            offset += 2;
        };
        const writePString = (str) => {
            dv.setUint8(offset++, str.length);
            for (let c of str) dv.setUint8(offset++, c.charCodeAt(0));
            if ((str.length + 1) % 2 === 0) dv.setUint8(offset++, 0);
        };

        writeID('FORM');
        const sizePos = offset;
        writeUint32(0); // Placeholder
        writeID(this.properties['Form Type']);
        // COMM
        let commStart = offset;
        writeID('COMM');
        const commSizePos = offset;
        writeUint32(0);
        writeUint16(this.properties['Number of Channels']);
        writeUint32(this.properties['Number of Sample Frames']);
        writeUint16(this.properties['Sample Size']);
        writeExtended(this.properties['Sample Rate']);
        if (this.properties['Form Type'] === 'AIFC') {
            writeID(this.properties['Compression Type']);
            writePString(this.properties['Compression Name']);
        }
        dv.setUint32(commSizePos, offset - commSizePos - 4, false);
        // SSND
        writeID('SSND');
        const ssndSize = 8 + this.properties['Sound Data Offset'] + this.audioData.length;
        writeUint32(ssndSize);
        writeUint32(this.properties['Sound Data Offset']);
        writeUint32(this.properties['Sound Data Block Size']);
        offset += this.properties['Sound Data Offset'];
        for (let b of this.audioData) dv.setUint8(offset++, b);
        // Update form size
        dv.setUint32(sizePos, offset - 8, false);
        // TODO: Add other chunks
        return new Blob([buffer.slice(0, offset)]);
    }
}

// Example usage:
// fetch('input.aiff').then(res => res.arrayBuffer()).then(buffer => {
//     const handler = new AIFFHandler(buffer);
//     handler.printProperties();
//     const outputBlob = handler.write();
//     // Save blob
// });
  1. C++ class for .AIFF handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
#include <cstring>

class AIFFHandler {
private:
    std::string filename;
    std::map<std::string, std::any> properties;
    std::vector<uint8_t> audioData;

    double readExtended(std::ifstream& f) {
        char buf[10];
        f.read(buf, 10);
        // Approximate to double
        double val;
        std::memcpy(&val, buf, 8);
        return val;
    }

    std::string readPString(std::ifstream& f) {
        uint8_t len;
        f.read(reinterpret_cast<char*>(&len), 1);
        std::string s(len, '\0');
        f.read(&s[0], len);
        if ((len + 1) % 2 != 0) {
            char pad;
            f.read(&pad, 1);
        }
        return s;
    }

public:
    AIFFHandler(const std::string& fn) : filename(fn) {
        std::ifstream f(filename, std::ios::binary);
        if (!f) throw std::runtime_error("File not found");

        char id[5] = {};
        f.read(id, 4);
        if (std::string(id) != "FORM") throw std::runtime_error("Not AIFF");
        uint32_t formSize;
        f.read(reinterpret_cast<char*>(&formSize), 4);
        formSize = __builtin_bswap32(formSize); // To little-endian if needed, but assume big
        f.read(id, 4);
        properties["Form Type"] = std::string(id);

        while (f.tellg() < formSize + 8) {
            f.read(id, 4);
            std::string chunkID(id);
            uint32_t chunkSize;
            f.read(reinterpret_cast<char*>(&chunkSize), 4);
            chunkSize = __builtin_bswap32(chunkSize);
            std::streampos pos = f.tellg();

            if (chunkID == "FVER") {
                uint32_t ver;
                f.read(reinterpret_cast<char*>(&ver), 4);
                ver = __builtin_bswap32(ver);
                properties["Format Version"] = ver;
            } else if (chunkID == "COMM") {
                uint16_t channels;
                f.read(reinterpret_cast<char*>(&channels), 2);
                channels = __builtin_bswap16(channels);
                properties["Number of Channels"] = channels;
                uint32_t frames;
                f.read(reinterpret_cast<char*>(&frames), 4);
                frames = __builtin_bswap32(frames);
                properties["Number of Sample Frames"] = frames;
                uint16_t size;
                f.read(reinterpret_cast<char*>(&size), 2);
                size = __builtin_bswap16(size);
                properties["Sample Size"] = size;
                properties["Sample Rate"] = readExtended(f);
                if (std::any_cast<std::string>(properties["Form Type"]) == "AIFC") {
                    f.read(id, 4);
                    properties["Compression Type"] = std::string(id);
                    properties["Compression Name"] = readPString(f);
                }
            } else if (chunkID == "SSND") {
                uint32_t offsetVal;
                f.read(reinterpret_cast<char*>(&offsetVal), 4);
                offsetVal = __builtin_bswap32(offsetVal);
                properties["Sound Data Offset"] = offsetVal;
                uint32_t blockSize;
                f.read(reinterpret_cast<char*>(&blockSize), 4);
                blockSize = __builtin_bswap32(blockSize);
                properties["Sound Data Block Size"] = blockSize;
                f.seekg(offsetVal, std::ios::cur);
                uint32_t dataSize = chunkSize - 8 - offsetVal;
                audioData.resize(dataSize);
                f.read(reinterpret_cast<char*>(audioData.data()), dataSize);
            } else if (chunkID == "MARK") {
                std::vector<std::map<std::string, std::any>> markers;
                uint16_t num;
                f.read(reinterpret_cast<char*>(&num), 2);
                num = __builtin_bswap16(num);
                for (uint16_t i = 0; i < num; ++i) {
                    std::map<std::string, std::any> m;
                    uint16_t mid;
                    f.read(reinterpret_cast<char*>(&mid), 2);
                    mid = __builtin_bswap16(mid);
                    m["id"] = mid;
                    uint32_t posVal;
                    f.read(reinterpret_cast<char*>(&posVal), 4);
                    posVal = __builtin_bswap32(posVal);
                    m["position"] = posVal;
                    m["name"] = readPString(f);
                    markers.push_back(m);
                }
                properties["Markers"] = markers;
            } else if (chunkID == "INST") {
                int8_t val;
                f.read(reinterpret_cast<char*>(&val), 1);
                properties["Base Note"] = val;
                f.read(reinterpret_cast<char*>(&val), 1);
                properties["Detune"] = val;
                f.read(reinterpret_cast<char*>(&val), 1);
                properties["Low Note"] = val;
                f.read(reinterpret_cast<char*>(&val), 1);
                properties["High Note"] = val;
                f.read(reinterpret_cast<char*>(&val), 1);
                properties["Low Velocity"] = val;
                f.read(reinterpret_cast<char*>(&val), 1);
                properties["High Velocity"] = val;
                int16_t gain;
                f.read(reinterpret_cast<char*>(&gain), 2);
                gain = __builtin_bswap16(gain);
                properties["Gain"] = gain;
                std::map<std::string, int16_t> sLoop;
                int16_t pm;
                f.read(reinterpret_cast<char*>(&pm), 2);
                sLoop["playMode"] = __builtin_bswap16(pm);
                int16_t begin;
                f.read(reinterpret_cast<char*>(&begin), 2);
                sLoop["begin"] = __builtin_bswap16(begin);
                int16_t end;
                f.read(reinterpret_cast<char*>(&end), 2);
                sLoop["end"] = __builtin_bswap16(end);
                properties["Sustain Loop"] = sLoop;
                std::map<std::string, int16_t> rLoop;
                f.read(reinterpret_cast<char*>(&pm), 2);
                rLoop["playMode"] = __builtin_bswap16(pm);
                f.read(reinterpret_cast<char*>(&begin), 2);
                rLoop["begin"] = __builtin_bswap16(begin);
                f.read(reinterpret_cast<char*>(&end), 2);
                rLoop["end"] = __builtin_bswap16(end);
                properties["Release Loop"] = rLoop;
            } else if (chunkID == "COMT") {
                std::vector<std::map<std::string, std::any>> comments;
                uint16_t num;
                f.read(reinterpret_cast<char*>(&num), 2);
                num = __builtin_bswap16(num);
                for (uint16_t i = 0; i < num; ++i) {
                    std::map<std::string, std::any> c;
                    uint32_t ts;
                    f.read(reinterpret_cast<char*>(&ts), 4);
                    ts = __builtin_bswap32(ts);
                    c["timestamp"] = ts;
                    uint16_t mid;
                    f.read(reinterpret_cast<char*>(&mid), 2);
                    mid = __builtin_bswap16(mid);
                    c["markerID"] = mid;
                    uint16_t count;
                    f.read(reinterpret_cast<char*>(&count), 2);
                    count = __builtin_bswap16(count);
                    std::string text(count, '\0');
                    f.read(&text[0], count);
                    if (count % 2 != 0) {
                        char pad;
                        f.read(&pad, 1);
                    }
                    c["text"] = text;
                    comments.push_back(c);
                }
                properties["Comments"] = comments;
            } else if (chunkID == "NAME" || chunkID == "AUTH" || chunkID == "(c) " || chunkID == "ANNO") {
                std::string key = (chunkID == "NAME") ? "Name" : (chunkID == "AUTH") ? "Author" : (chunkID == "(c) ") ? "Copyright" : "Annotations";
                std::string val = readPString(f);
                if (key == "Annotations") {
                    auto annos = std::any_cast<std::vector<std::string>>(properties[key]);
                    annos.push_back(val);
                    properties[key] = annos;
                } else {
                    properties[key] = val;
                }
            } else if (chunkID == "AESD") {
                std::vector<uint8_t> aes(24);
                f.read(reinterpret_cast<char*>(aes.data()), 24);
                properties["AES Channel Status Data"] = aes;
            } else if (chunkID == "MIDI") {
                std::vector<uint8_t> midi(chunkSize);
                f.read(reinterpret_cast<char*>(midi.data()), chunkSize);
                properties["MIDI Data"] = midi;
            } else if (chunkID == "APPL") {
                char sig[5];
                f.read(sig, 4);
                sig[4] = '\0';
                std::vector<uint8_t> data(chunkSize - 4);
                f.read(reinterpret_cast<char*>(data.data()), chunkSize - 4);
                std::map<std::string, std::any> appData;
                appData["signature"] = std::string(sig);
                appData["data"] = data;
                properties["Application Specific Data"] = appData;
            }

            f.seekg(pos + static_cast<std::streamoff>(chunkSize));
            if (f.tellg() % 2 != 0) {
                char pad;
                f.read(&pad, 1);
            }
        }
    }

    void printProperties() {
        for (const auto& [key, value] : properties) {
            // Simplified print; use type checking for real output
            std::cout << key << ": " << /* value */ std::endl;
        }
    }

    void write(const std::string& outputFilename) {
        std::ofstream f(outputFilename, std::ios::binary);
        if (!f) throw std::runtime_error("Cannot write file");

        f.write("FORM", 4);
        uint32_t sizePlaceholder = 0;
        f.write(reinterpret_cast<const char*>(&sizePlaceholder), 4);
        auto formType = std::any_cast<std::string>(properties["Form Type"]);
        f.write(formType.c_str(), 4);
        // COMM
        f.write("COMM", 4);
        uint32_t commSizePlaceholder = 0;
        f.write(reinterpret_cast<const char*>(&commSizePlaceholder), 4);
        std::streampos commStart = f.tellp();
        uint16_t channels = std::any_cast<uint16_t>(properties["Number of Channels"]);
        channels = __builtin_bswap16(channels);
        f.write(reinterpret_cast<const char*>(&channels), 2);
        uint32_t frames = std::any_cast<uint32_t>(properties["Number of Sample Frames"]);
        frames = __builtin_bswap32(frames);
        f.write(reinterpret_cast<const char*>(&frames), 4);
        uint16_t sampleSize = std::any_cast<uint16_t>(properties["Sample Size"]);
        sampleSize = __builtin_bswap16(sampleSize);
        f.write(reinterpret_cast<const char*>(&sampleSize), 2);
        double sr = std::any_cast<double>(properties["Sample Rate"]);
        char ext[10];
        std::memcpy(ext, &sr, 8);
        std::memset(ext + 8, 0, 2);
        f.write(ext, 10);
        if (formType == "AIFC") {
            auto compType = std::any_cast<std::string>(properties["Compression Type"]);
            f.write(compType.c_str(), 4);
            auto compName = std::any_cast<std::string>(properties["Compression Name"]);
            uint8_t len = compName.length();
            f.write(reinterpret_cast<const char*>(&len), 1);
            f.write(compName.c_str(), len);
            if ((len + 1) % 2 == 0) {
                uint8_t pad = 0;
                f.write(reinterpret_cast<const char*>(&pad), 1);
            }
        }
        uint32_t commSize = static_cast<uint32_t>(f.tellp() - commStart);
        f.seekp(commStart - std::streampos(4));
        commSize = __builtin_bswap32(commSize);
        f.write(reinterpret_cast<const char*>(&commSize), 4);
        f.seekp(0, std::ios::end);
        // SSND
        f.write("SSND", 4);
        uint32_t ssndSize = 8 + std::any_cast<uint32_t>(properties["Sound Data Offset"]) + audioData.size();
        ssndSize = __builtin_bswap32(ssndSize);
        f.write(reinterpret_cast<const char*>(&ssndSize), 4);
        uint32_t offsetVal = std::any_cast<uint32_t>(properties["Sound Data Offset"]);
        offsetVal = __builtin_bswap32(offsetVal);
        f.write(reinterpret_cast<const char*>(&offsetVal), 4);
        uint32_t blockSize = std::any_cast<uint32_t>(properties["Sound Data Block Size"]);
        blockSize = __builtin_bswap32(blockSize);
        f.write(reinterpret_cast<const char*>(&blockSize), 4);
        std::streampos dataPos = f.tellp();
        f.seekp(std::any_cast<uint32_t>(properties["Sound Data Offset"]), std::ios::cur);
        f.write(reinterpret_cast<const char*>(audioData.data()), audioData.size());
        // Update form size
        uint32_t totalSize = static_cast<uint32_t>(f.tellp() - std::streampos(8));
        f.seekp(4);
        totalSize = __builtin_bswap32(totalSize);
        f.write(reinterpret_cast<const char*>(&totalSize), 4);
        // TODO: Add other chunks
    }
};

// Example usage:
// int main() {
//     try {
//         AIFFHandler handler("input.aiff");
//         handler.printProperties();
//         handler.write("output.aiff");
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }