/* Copyright (c) 1997-1999 Miller Puckette. Updated 2019 Dan Wilcox. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ /* ref: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html */ #include "d_soundfile.h" /* WAVE (Waveform Audio File Format) * RIFF variant with sections split into data "chunks" * chunk sizes do not include the chunk id or size (- 8) * chunk and sound data are little endian * format and sound data chunks are required * format tags: - PCM linear PCM - FLOAT 32-bit float - EXT extended format -> described in subformat fields - the rest are not relevant to Pd... * extended format must be used when: - PCM data has more than 16 bits per sample - number of channels is more than 2 - mapping of channels to speakers is required (not relevant to Pd) * a fact chunk is required when the format is non-PCM * if sound data length is odd, a 0 pad byte should appended * limited to ~4 GB files as sizes are unsigned 32 bit ints * there are variants with 64-bit sizes (W64 and RF64) as well as extension formats which can split sound data across multiple files (BWF) this implementation: * supports basic and extended format chunks (WAVE Rev. 3) * implicitly writes extended format for 32 or 64 bit float (see below) * implements chunks: format, fact, sound data * ignores chunks: info, cset, cue, playlist, associated data, instrument, sample, display, junk, pad, time code, digitization time * assumes format chunk is always before sound data chunk * assumes there is only 1 sound data chunk * does not support 64-bit size variants or BWF file-splitting * sample format: 16 and 24 bit lpcm, 32 and 64 bit float, no 32 bit lpcm Pd versions < 0.55 did not read or write 64 bit float. Pd versions < 0.51 did *not* read or write extended format explicitly, but ignored the format chunk format tag and interpreted the sample type based on the bits per sample: 2 : int 16, 3 : int 24, 4 : float 32. This means files created by newer Pd versions are currently more or less compatible with older Pd versions. This may change if 32 bit int support is introduced. */ /* explicit byte sizes, sizeof(struct) may return alignment-padded values */ #define WAVECHUNKSIZE … #define WAVEHEADSIZE … #define WAVEFORMATSIZE … #define WAVEFACTSIZE … #define WAVEMAXBYTES … #define WAVE_FORMAT_PCM … #define WAVE_FORMAT_FLOAT … #define WAVE_FORMAT_EXT … /** extended format header and data length */ #define WAVE_EXT_SIZE … /** 14 byte extended subformat GUID */ #define WAVE_EXT_GUID … /** basic chunk header, 8 bytes */ t_chunk; /** file head container chunk, 12 bytes */ t_head; /** format chunk, 24 (basic) or 48 (extended) */ t_formatchunk; /** fact chunk, 12 bytes */ t_factchunk; /* ----- helpers ----- */ /** returns 1 if format requires extended format and fact chunk */ static int wave_isextended(const t_soundfile *sf) { … } /** read first chunk, returns filled chunk and offset on success or -1 */ static off_t wave_firstchunk(const t_soundfile *sf, t_chunk *chunk) { … } /** read next chunk, chunk should be filled when calling returns filled chunk offset on success or -1 */ static off_t wave_nextchunk(const t_soundfile *sf, off_t offset, t_chunk *chunk) { … } #ifdef DEBUG_SOUNDFILE /** post chunk info for debugging */ static void wave_postchunk(const t_chunk *chunk, int swap) { post("%.4s %d", chunk->c_id, swap4s(chunk->c_size, swap)); } /** post head info for debugging */ static void wave_posthead(const t_head *head, int swap) { wave_postchunk((const t_chunk *)head, swap); post(" %.4s", head->h_formtype); } /** post format info for debugging */ static void wave_postformat(const t_formatchunk *format, int swap) { uint16_t formattag = swap2(format->fc_fmttag, swap); wave_postchunk((const t_chunk *)format, swap); switch (formattag) { case WAVE_FORMAT_PCM: post(" format %d (PCM)", formattag); break; case WAVE_FORMAT_FLOAT: post(" format %d (FLOAT)", formattag); break; case WAVE_FORMAT_EXT: post(" format %d (EXT)", formattag); break; default: post(" format %d (unsupported)", formattag); break; } post(" channels %d", swap2(format->fc_nchannels, swap)); post(" sample rate %d", swap4(format->fc_samplerate, swap)); post(" bytes per sec %d", swap4(format->fc_bytespersecond, swap)); post(" block align %d", swap2(format->fc_blockalign, swap)); post(" bits per sample %u", swap2(format->fc_bitspersample, swap)); if (formattag == WAVE_FORMAT_EXT) { formattag = swap2(*(uint16_t *)&format->fc_subformat, swap); post(" ext size %d", swap2(format->fc_extsize, swap)); post(" ext valid bits per sample %d", swap2(format->fc_validbitspersample, swap)); post(" ext channel mask 0x%04x", swap4(format->fc_channelmask, swap)); switch (formattag) { case WAVE_FORMAT_PCM: post(" ext format %d (PCM)", formattag); break; case WAVE_FORMAT_FLOAT: post(" ext format %d (FLOAT)", formattag); break; default: post(" ext format %d (unsupported)", formattag); break; } } } /** post fact info for debugging */ static void wave_postfact(const t_factchunk *fact, int swap) { wave_postchunk((const t_chunk *)fact, swap); post(" sample length %d", swap4(fact->fc_samplelength, swap)); } #endif /* DEBUG_SOUNDFILE */ /* ------------------------- WAVE ------------------------- */ static int wave_isheader(const char *buf, size_t size) { … } static int wave_readheader(t_soundfile *sf) { … } static int wave_writeheader(t_soundfile *sf, size_t nframes) { … } /** assumes chunk order: * basic: head format data * extended: head format+ext fact data */ static int wave_updateheader(t_soundfile *sf, size_t nframes) { … } static int wave_hasextension(const char *filename, size_t size) { … } static int wave_addextension(char *filename, size_t size) { … } /* force little endian */ static int wave_endianness(int endianness, int bytespersample) { … } /* ------------------------- setup routine ------------------------ */ t_soundfile_type wave = …; void soundfile_wave_setup( void) { … }