The tiny wav I/O module is the most simple wav file I/O module you've ever seen.

Dependents:   SimpleWaveRecorderPlayer USBMSD_SD_HelloWorld_FRDM-KL25Z Application-SimpleWaveRecorderPlayerGenerator MbedClock ... more

wavfile.cpp

Committer:
shintamainjp
Date:
2012-04-14
Revision:
0:c853ba46d0b9

File content as of revision 0:c853ba46d0b9:

/**
 * @file wavfile.cpp
 * @author Shinichiro Nakamura
 */

/*
 * ===============================================================
 *  Tiny WAV I/O Module
 *  Version 0.0.1
 * ===============================================================
 * Copyright (c) 2011-2012 Shinichiro Nakamura
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * ===============================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wavfile.h"

#define DEBUG printf

#define CHUNK_ID_RIFF           (('R' << 24) | ('I' << 16) | ('F' << 8) | ('F' << 0))
#define CHUNK_ID_FMT            (('f' << 24) | ('m' << 16) | ('t' << 8) | (' ' << 0))
#define CHUNK_ID_DATA           (('d' << 24) | ('a' << 16) | ('t' << 8) | ('a' << 0))

#define RIFF_CHUNK_FORMAT_WAVE  (('W' << 24) | ('A' << 16) | ('V' << 8) | ('E' << 0))

#define BITS_PER_SAMPLE_8           (8)
#define BITS_PER_SAMPLE_16          (16)
#define BITS_PER_SAMPLE_24          (24)

#define CHUNK_SIZE_FMT_PCM          (16)
#define CHUNK_SIZE_FMT_EXTENSIBLE   (40)

#define CHANNEL_MASK_SPEAKER_FRONT_LEFT             (0x00000001)
#define CHANNEL_MASK_SPEAKER_FRONT_RIGHT            (0x00000002)
#define CHANNEL_MASK_SPEAKER_FRONT_CENTER           (0x00000004)
#define CHANNEL_MASK_SPEAKER_LOW_FREQUENCY          (0x00000008)
#define CHANNEL_MASK_SPEAKER_BACK_LEFT              (0x00000010)
#define CHANNEL_MASK_SPEAKER_BACK_RIGHT             (0x00000020)
#define CHANNEL_MASK_SPEAKER_FRONT_LEFT_OF_CENTER   (0x00000040)
#define CHANNEL_MASK_SPEAKER_FRONT_RIGHT_OF_CENTER  (0x00000080)
#define CHANNEL_MASK_SPEAKER_BACK_CENTER            (0x00000100)
#define CHANNEL_MASK_SPEAKER_SIDE_LEFT              (0x00000200)
#define CHANNEL_MASK_SPEAKER_SIDE_RIGHT             (0x00000400)
#define CHANNEL_MASK_SPEAKER_TOP_CENTER             (0x00000800)
#define CHANNEL_MASK_SPEAKER_TOP_FRONT_LEFT         (0x00001000)
#define CHANNEL_MASK_SPEAKER_TOP_FRONT_CENTER       (0x00002000)
#define CHANNEL_MASK_SPEAKER_TOP_FRONT_RIGHT        (0x00004000)
#define CHANNEL_MASK_SPEAKER_TOP_BACK_LEFT          (0x00008000)
#define CHANNEL_MASK_SPEAKER_TOP_BACK_CENTER        (0x00010000)
#define CHANNEL_MASK_SPEAKER_TOP_BACK_RIGHT         (0x00020000)
#define CHANNEL_MASK_SPEAKER_RESERVED               (0x80000000)

struct WAVFILE {
    FILE *fp;
    char filename[BUFSIZ];
    WavFileMode mode;
    bool info_checked;
    bool data_checked;
    uint32_t data_byte_count;
    wavfile_info_t info;
};

static int WRITE_U32_BE(FILE *fp, const uint32_t value) {
    for (int i = 0; i < 4; i++) {
        if (fputc((value >> (8 * (3 - i))), fp) == EOF) {
            return -1;
        }
    }
    return 0;
}

static int WRITE_U32_LE(FILE *fp, const uint32_t value) {
    for (int i = 0; i < 4; i++) {
        if (fputc((value >> (8 * i)), fp) == EOF) {
            return -1;
        }
    }
    return 0;
}

static int WRITE_U16_LE(FILE *fp, const uint16_t value) {
    for (int i = 0; i < 2; i++) {
        if (fputc((value >> (8 * i)), fp) == EOF) {
            return -1;
        }
    }
    return 0;
}

static int READ_U32_BE(FILE *fp, uint32_t *value) {
    int raw[4];
    for (int i = 0; i < (int)(sizeof(raw) / sizeof(raw[0])); i++) {
        raw[i] = fgetc(fp);
        if (raw[i] == EOF) {
            *value = 0x00000000;
            return -1;
        }
    }
    *value =
        ((uint32_t)raw[0] << 24) |
        ((uint32_t)raw[1] << 16) |
        ((uint32_t)raw[2] <<  8) |
        ((uint32_t)raw[3] <<  0);
    return 0;
}

static int READ_U32_LE(FILE *fp, uint32_t *value) {
    int raw[4];
    for (int i = 0; i < (int)(sizeof(raw) / sizeof(raw[0])); i++) {
        raw[i] = fgetc(fp);
        if (raw[i] == EOF) {
            *value = 0x00000000;
            return -1;
        }
    }
    *value =
        ((uint32_t)raw[3] << 24) |
        ((uint32_t)raw[2] << 16) |
        ((uint32_t)raw[1] <<  8) |
        ((uint32_t)raw[0] <<  0);
    return 0;
}

static int READ_U16_LE(FILE *fp, uint16_t *value) {
    int raw[2];
    for (int i = 0; i < (int)(sizeof(raw) / sizeof(raw[0])); i++) {
        raw[i] = fgetc(fp);
        if (raw[i] == EOF) {
            *value = 0x00000000;
            return -1;
        }
    }
    *value =
        ((uint16_t)raw[1] <<  8) |
        ((uint16_t)raw[0] <<  0);
    return 0;
}

static WavFileResult chunk_reader_unknown(
    const uint32_t chunk_id,
    const uint32_t chunk_size,
    FILE *fp) {
    for (int i = 0; i < (int)chunk_size; i++) {
        int c = fgetc(fp);
        if (c == EOF) {
            return WavFileResultErrorBrokenChunkData;
        }
    }
    return WavFileResultOK;
}

static WavFileResult chunk_reader_riff(
    const uint32_t chunk_id,
    const uint32_t chunk_size,
    FILE *fp,
    uint32_t *format_id) {
    if (READ_U32_BE(fp, format_id) != 0) {
        return WavFileResultErrorBrokenFormatId;
    }
    return WavFileResultOK;
}

static WavFileResult chunk_reader_fmt(
    const uint32_t chunk_id,
    const uint32_t chunk_size,
    FILE *fp,
    uint16_t *audio_format,
    uint16_t *num_channels,
    uint32_t *sample_rate,
    uint32_t *byte_rate,
    uint16_t *block_align,
    uint16_t *bits_per_sample) {
    uint32_t read_byte_count = 0;

    /*
     * 2
     */
    if (read_byte_count < chunk_size) {
        if (READ_U16_LE(fp, audio_format) != 0) {
            return WavFileResultErrorBrokenAudioFormat;
        }
    }
    read_byte_count+=2;

    /*
     * 2 + 2
     */
    if (read_byte_count < chunk_size) {
        if (READ_U16_LE(fp, num_channels) != 0) {
            return WavFileResultErrorBrokenNumChannels;
        }
    }
    read_byte_count+=2;

    /*
     * 2 + 2 + 4
     */
    if (read_byte_count < chunk_size) {
        if (READ_U32_LE(fp, sample_rate) != 0) {
            return WavFileResultErrorBrokenSampleRate;
        }
    }
    read_byte_count+=4;

    /*
     * 2 + 2 + 4 + 4
     */
    if (read_byte_count < chunk_size) {
        if (READ_U32_LE(fp, byte_rate) != 0) {
            return WavFileResultErrorBrokenByteRate;
        }
    }
    read_byte_count+=4;

    /*
     * 2 + 2 + 4 + 4 + 2
     */
    if (read_byte_count < chunk_size) {
        if (READ_U16_LE(fp, block_align) != 0) {
            return WavFileResultErrorBrokenBlockAlign;
        }
    }
    read_byte_count+=2;

    /*
     * 2 + 2 + 4 + 4 + 2 + 2
     */
    if (read_byte_count < chunk_size) {
        if (READ_U16_LE(fp, bits_per_sample) != 0) {
            return WavFileResultErrorBrokenBitsPerSample;
        }
    }
    read_byte_count+=2;

    /*
     * 2 + 2 + 4 + 4 + 2 + 2
     */
    while (read_byte_count < chunk_size) {
        if (fgetc(fp) == EOF) {
            return WavFileResultErrorBrokenChunkData;
        }
        read_byte_count++;
    }

    return WavFileResultOK;
}

WAVFILE *wavfile_open(const char *filename, WavFileMode mode, WavFileResult *result) {
    /*
     * Verify the filename.
     */
    if (filename == NULL) {
        *result = WavFileResultErrorInvalidFileName;
        return NULL;
    }

    /*
     * Open the file.
     */
    FILE *fp = NULL;
    switch (mode) {
        case WavFileModeRead:
            fp = fopen(filename, "rb");
            break;
        case WavFileModeWrite:
            fp = fopen(filename, "wb");
            break;
        default:
            fp = NULL;
            break;
    }
    if (fp == NULL) {
        *result = WavFileResultErrorFileOpen;
        return NULL;
    }

    /*
     * Allocate the handler.
     */
    WAVFILE *p = (WAVFILE *)malloc(sizeof(WAVFILE));
    if (p == NULL) {
        *result = WavFileResultErrorMemoryAllocation;
        return NULL;
    }

    /*
     * Fill the fields.
     */
    p->fp = fp;
    strcpy(p->filename, filename);
    p->mode = mode;
    p->info_checked = false;
    p->data_checked = false;
    p->data_byte_count = 0;
    WAVFILE_INFO_AUDIO_FORMAT(&(p->info)) = 0;
    WAVFILE_INFO_NUM_CHANNELS(&(p->info)) = 0;
    WAVFILE_INFO_SAMPLE_RATE(&(p->info)) = 0;
    WAVFILE_INFO_BYTE_RATE(&(p->info)) = 0;
    WAVFILE_INFO_BLOCK_ALIGN(&(p->info)) = 0;
    WAVFILE_INFO_BITS_PER_SAMPLE(&(p->info)) = 0;

    *result = WavFileResultOK;
    return p;
}

WavFileResult wavfile_read_info(WAVFILE *p, wavfile_info_t *info) {
    WavFileResult result = WavFileResultOK;

    if (p == NULL) {
        result = WavFileResultErrorInvalidHandler;
        goto finalize;
    }

    if (p->info_checked) {
        result = WavFileResultErrorAlreadyInfoChecked;
        goto finalize;
    }
    if (p->data_checked) {
        result = WavFileResultErrorAlreadyDataChecked;
        goto finalize;
    }
    if (p->mode != WavFileModeRead) {
        result = WavFileResultErrorInvalidMode;
        goto finalize;
    }

    while (1) {
        uint32_t chunk_id;
        uint32_t chunk_size;

        /*
         * Get the chunk ID.
         */
        if (READ_U32_BE(p->fp, &chunk_id) != 0) {
            if (feof(p->fp)) {
                /*
                 * 
                 */
                result = WavFileResultErrorNoDataChunk;
                goto finalize;
            } else {
                result = WavFileResultErrorBrokenChunkId;
                goto finalize;
            }
        }

        /*
         * Verify the chunk size.
         */
        if (READ_U32_LE(p->fp, &chunk_size) != 0) {
            result = WavFileResultErrorBrokenChunkSize;
            goto finalize;
        }

#if WAVFILE_DEBUG_ENABLED
        /*
         * 
         */
        DEBUG("chunk_id(0x%04X-%c%c%c%c), chunk_size(%d bytes)\n",
              chunk_id,
              (chunk_id >> (8 * 3)),
              (chunk_id >> (8 * 2)),
              (chunk_id >> (8 * 1)),
              (chunk_id >> (8 * 0)),
              chunk_size);
#endif

        /*
         */
        switch (chunk_id) {
            case CHUNK_ID_RIFF: {
                uint32_t format_id;
                result = chunk_reader_riff(
                             chunk_id,
                             chunk_size,
                             p->fp,
                             &format_id);

#if WAVFILE_DEBUG_ENABLED
                /*
                 */
                DEBUG("\tformat_id(%d)\n", format_id);
#endif

                if (format_id != RIFF_CHUNK_FORMAT_WAVE) {
                    return WavFileResultErrorInvalidFormatId;
                }
                if (result != WavFileResultOK) {
                    goto finalize;
                }
            }
            break;
            case CHUNK_ID_FMT: {
                result = chunk_reader_fmt(
                             chunk_id,
                             chunk_size,
                             p->fp,
                             &(p->info.audio_format),
                             &(p->info.num_channels),
                             &(p->info.sample_rate),
                             &(p->info.byte_rate),
                             &(p->info.block_align),
                             &(p->info.bits_per_sample));

                info->audio_format = p->info.audio_format;
                info->num_channels = p->info.num_channels;
                info->sample_rate = p->info.sample_rate;
                info->byte_rate = p->info.byte_rate;
                info->block_align = p->info.block_align;
                info->bits_per_sample = p->info.bits_per_sample;

#if WAVFILE_DEBUG_ENABLED
                /*
                 */
                DEBUG("\taudio_format(%d)\n", p->info.audio_format);
                DEBUG("\tnum_channels(%d)\n", p->info.num_channels);
                DEBUG("\tsample_rate(%d)\n", p->info.sample_rate);
                DEBUG("\tbyte_rate(%d)\n", p->info.byte_rate);
                DEBUG("\tblock_align(%d)\n", p->info.block_align);
                DEBUG("\tbits_per_sample(%d)\n", p->info.bits_per_sample);
#endif

                if ((p->info.audio_format != WAVFILE_AUDIO_FORMAT_PCM) && (info->audio_format != WAVFILE_AUDIO_FORMAT_EXTENSIBLE)) {
                    return WavFileResultErrorInvalidAudioFormat;
                }
                if (result != WavFileResultOK) {
                    goto finalize;
                }
            }
            break;
            case CHUNK_ID_DATA: {
                p->info_checked = true;
                p->data_byte_count = chunk_size;
                goto finalize;
            }
            break;
            default: {
                result = chunk_reader_unknown(chunk_id, chunk_size, p->fp);
                if (result != WavFileResultOK) {
                    goto finalize;
                }
            }
            break;
        }
    }

finalize:
    return result;
}

/**
 */
WavFileResult wavfile_read_data(WAVFILE *p, wavfile_data_t *data) {
    if (p == NULL) {
        return WavFileResultErrorInvalidHandler;
    }

    if (!p->info_checked) {
        return WavFileResultErrorNeedInfoChecked;
    }

    if (p->mode != WavFileModeRead) {
        return WavFileResultErrorInvalidMode;
    }

    if (p->data_byte_count == 0) {
        data->num_channels = 0;
        for (int i = 0; i < p->info.num_channels; i++) {
            data->channel_data[i] = 0.5;
        }
        return WavFileResultOK;
    }

    data->num_channels = p->info.num_channels;
    for (int i = 0; i < p->info.num_channels; i++) {
        switch (p->info.bits_per_sample) {
            case BITS_PER_SAMPLE_8: {
                int c = fgetc(p->fp);
                if (c == EOF) {
                    return WavFileResultErrorBrokenChunkData;
                }
                data->channel_data[i] = (double)c / 0xFF;
            }
            p->data_byte_count-=1;
            break;
            case BITS_PER_SAMPLE_16: {
#if 0
                int c1 = fgetc(p->fp);
                if (c1 == EOF) {
                    return WavFileResultErrorBrokenChunkData;
                }
                int c2 = fgetc(p->fp);
                if (c2 == EOF) {
                    return WavFileResultErrorBrokenChunkData;
                }
                uint16_t n = (((uint16_t)c2 << 8) | ((uint16_t)c1 << 0)) ^ (1 << 15);
                data->channel_data[i] = (double)n / 0xFFFF;
#else
                char tmp[2];
                fread(tmp, 2, 1, p->fp);
                uint16_t n = (((uint16_t)tmp[1] << 8) | ((uint16_t)tmp[0] << 0)) ^ (1 << 15);
                data->channel_data[i] = (double)n / 0xFFFF;
#endif
            }
            p->data_byte_count-=2;
            break;
            case BITS_PER_SAMPLE_24: {
            #if 0
                int c1 = fgetc(p->fp);
                if (c1 == EOF) {
                    return WavFileResultErrorBrokenChunkData;
                }
                int c2 = fgetc(p->fp);
                if (c2 == EOF) {
                    return WavFileResultErrorBrokenChunkData;
                }
                int c3 = fgetc(p->fp);
                if (c3 == EOF) {
                    return WavFileResultErrorBrokenChunkData;
                }
                uint32_t n = (((uint32_t)c3 << 16) | ((uint32_t)c2 << 8) | ((uint32_t)c1 << 0)) ^ (1 << 23);
                data->channel_data[i] = (double)n / 0xFFFFFF;
#else
                char tmp[3];
                fread(tmp, 3, 1, p->fp);
                uint32_t n = (((uint32_t)tmp[2] << 16) | ((uint32_t)tmp[1] << 8) | ((uint32_t)tmp[0] << 0)) ^ (1 << 23);
                data->channel_data[i] = (double)n / 0xFFFFFF;
#endif
            }
            p->data_byte_count-=3;
            break;
            default:
                return WavFileResultErrorUnsupportedBitsPerSample;
        }
    }
    return WavFileResultOK;
}

WavFileResult wavfile_write_info(WAVFILE *p, const wavfile_info_t *info) {
    WavFileResult result = WavFileResultOK;

    if (p == NULL) {
        result = WavFileResultErrorInvalidHandler;
        goto finalize;
    }

    if (p->info_checked) {
        result = WavFileResultErrorAlreadyInfoChecked;
        goto finalize;
    }

    if (p->mode != WavFileModeWrite) {
        result = WavFileResultErrorInvalidMode;
        goto finalize;
    }

    p->info.audio_format = info->audio_format;
    p->info.num_channels = info->num_channels;
    p->info.sample_rate = info->sample_rate;
    p->info.byte_rate = info->byte_rate;
    p->info.block_align = info->block_align;
    p->info.bits_per_sample = info->bits_per_sample;

    /*
     *
     */

    if ((info->audio_format != WAVFILE_AUDIO_FORMAT_PCM) && (info->audio_format != WAVFILE_AUDIO_FORMAT_EXTENSIBLE)) {
        result = WavFileResultErrorInvalidAudioFormat;
        goto finalize;
    }

    if ((info->bits_per_sample != BITS_PER_SAMPLE_8)
            && (info->bits_per_sample != BITS_PER_SAMPLE_16)
            && (info->bits_per_sample != BITS_PER_SAMPLE_24)) {
        result = WavFileResultErrorUnsupportedBitsPerSample;
        goto finalize;
    }

    if ((info->num_channels * info->sample_rate * (info->bits_per_sample / 8)) != info->byte_rate) {
        result = WavFileResultErrorInvalidByteRate;
        goto finalize;
    }

    /*
     * [RIFF]
     * ------------------------------------------
     * Big endian    4 bytes : Chunk ID
     * Little endian 4 bytes : Chunk size
     * Big endian    4 bytes : Format
     * ------------------------------------------
     */
    if (WRITE_U32_BE(p->fp, CHUNK_ID_RIFF) != 0) {
        result = WavFileResultErrorFileWrite;
        goto finalize;
    }
    if (WRITE_U32_LE(p->fp, 0x00000000) != 0) {
        result = WavFileResultErrorFileWrite;
        goto finalize;
    }
    if (WRITE_U32_BE(p->fp, RIFF_CHUNK_FORMAT_WAVE) != 0) {
        result = WavFileResultErrorFileWrite;
        goto finalize;
    }

    /*
     * [fmt]
     * ------------------------------------------
     * Big endian    4 bytes : Sub chunk ID
     * Little endian 4 bytes : Sub chunk size
     * Little endian 2 bytes : Audio format
     * Little endian 2 bytes : Number of channels
     * Little endian 4 bytes : Sample rate
     * Little endian 4 bytes : Byte rate
     * Little endian 2 bytes : Block align
     * Little endian 2 bytes : Bits per sample
     *  .                                      .
     *  .  Additional bytes here (extensible)  .
     *  .                                      .
     * ------------------------------------------
     */
    switch (info->audio_format) {
        case WAVFILE_AUDIO_FORMAT_PCM: {
            if (WRITE_U32_BE(p->fp, CHUNK_ID_FMT) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, CHUNK_SIZE_FMT_PCM) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->audio_format) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->num_channels) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, info->sample_rate) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, info->byte_rate) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->block_align) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->bits_per_sample) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
        }
        break;
        case WAVFILE_AUDIO_FORMAT_EXTENSIBLE: {
            if (WRITE_U32_BE(p->fp, CHUNK_ID_FMT) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, CHUNK_SIZE_FMT_EXTENSIBLE) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->audio_format) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->num_channels) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, info->sample_rate) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, info->byte_rate) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->block_align) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->bits_per_sample) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            /*
             * Additional bytes for the extensible format.
             *
             *  2 bytes : Size of the extension (0 or 22)
             *  2 bytes : Number of valid bits
             *  4 bytes : Speaker position mask
             * 16 bytes : GUID, including the data format code
             */
            if (WRITE_U16_LE(p->fp, 22) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U16_LE(p->fp, info->bits_per_sample) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            if (WRITE_U32_LE(p->fp, 0x00000000) != 0) {
                result = WavFileResultErrorFileWrite;
                goto finalize;
            }
            static const unsigned char sub_format[16] = {
                0x01, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x10, 0x00,
                0x80, 0x00, 0x00, 0xAA,
                0x00, 0x38, 0x9B, 0x71
            };
            for (int i = 0; i < sizeof(sub_format); i++) {
                fputc((char)sub_format[i], p->fp);
            }
        }
        break;
        default:
            result = WavFileResultErrorInvalidAudioFormat;
            goto finalize;
    }

    /*
     * [data]
     * ------------------------------------------
     * Big endian    4 bytes : Sub chunk ID
     * Little endian 4 bytes : Sub chunk size
     * ------------------------------------------
     * Little endian 2 bytes : Sample 1 (Ch.1)
     * Little endian 2 bytes : Sample 1 (Ch.2)
     *                    .
     *                    .
     *                    .
     * Little endian 2 bytes : Sample 1 (Ch.N)
     * ------------------------------------------
     * Little endian 2 bytes : Sample 2 (Ch.1)
     * Little endian 2 bytes : Sample 2 (Ch.2)
     *                    .
     *                    .
     *                    .
     * Little endian 2 bytes : Sample 2 (Ch.N)
     * ------------------------------------------
     */
    if (WRITE_U32_BE(p->fp, CHUNK_ID_DATA) != 0) {
        result = WavFileResultErrorFileWrite;
        goto finalize;
    }
    if (WRITE_U32_LE(p->fp, 0x00000000) != 0) {
        result = WavFileResultErrorFileWrite;
        goto finalize;
    }

finalize:
    if (WavFileResultOK == result) {
        p->info_checked = true;
    }
    return result;
}

/**
 */
WavFileResult wavfile_write_data(WAVFILE *p, const wavfile_data_t *data) {
    WavFileResult result = WavFileResultOK;

    if (p == NULL) {
        result = WavFileResultErrorInvalidHandler;
        goto finalize;
    }

    if (!p->info_checked) {
        result = WavFileResultErrorNeedInfoChecked;
        goto finalize;
    }

    if (p->mode != WavFileModeWrite) {
        result = WavFileResultErrorInvalidMode;
        goto finalize;
    }

    if (p->info.num_channels != data->num_channels) {
        result = WavFileResultErrorInvalidNumChannels;
        goto finalize;
    }

    for (int i = 0; i < p->info.num_channels; i++) {
        switch (p->info.bits_per_sample) {
            case BITS_PER_SAMPLE_8: {
                int n = (int)((double)data->channel_data[i] * 0xFF);
                if (n < 0x00) {
                    n = 0x00;
                }
                if (0xFF < n) {
                    n = 0xFF;
                }
                fputc((char)n, p->fp);
            }
            p->data_byte_count+=1;
            break;
            case BITS_PER_SAMPLE_16: {
                int n = (int)((double)(data->channel_data[i] * 0xFFFF) - 0x8000);
                if (0x7FFF < n) {
                    n = 0x7FFF;
                }
                if (n < -0x8000) {
                    n = -0x8000;
                }
                fputc(((uint16_t)n >> 0) & 0xff, p->fp);
                fputc(((uint16_t)n >> 8) & 0xff, p->fp);
            }
            p->data_byte_count+=2;
            break;
            case BITS_PER_SAMPLE_24: {
                int n = (int)((double)(data->channel_data[i] * 0xFFFFFF) - 0x800000);
                if (0x7FFFFF < n) {
                    n = 0x7FFFFF;
                }
                if (n < -0x800000) {
                    n = -0x800000;
                }
                fputc(((uint32_t)n >>  0) & 0xff, p->fp);
                fputc(((uint32_t)n >>  8) & 0xff, p->fp);
                fputc(((uint32_t)n >> 16) & 0xff, p->fp);
            }
            p->data_byte_count+=3;
            break;
        }
    }
    p->data_checked = true;

finalize:
    return result;
}

WavFileResult wavfile_close(WAVFILE *p) {
    WavFileResult result = WavFileResultOK;

    switch (p->mode) {
        case WavFileModeRead:
            break;
        case WavFileModeWrite:
            if (p->info_checked && p->data_checked) {
                switch (p->info.audio_format) {
                    case WAVFILE_AUDIO_FORMAT_PCM: {
                        /*
                         * Fill the RIFF chunk size.
                         */
                        if (fseek(p->fp, 4L, SEEK_SET) == -1) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }
                        if (WRITE_U32_LE(p->fp, 4 + (8 + CHUNK_SIZE_FMT_PCM) + (8 + p->data_byte_count)) != 0) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }

                        /*
                         * Fill the data sub chunk size.
                         */
                        if (fseek(p->fp, 12 + (8 + CHUNK_SIZE_FMT_PCM) + 4, SEEK_SET) == -1) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }
                        if (WRITE_U32_LE(p->fp, p->data_byte_count) != 0) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }
                    }
                    break;
                    case WAVFILE_AUDIO_FORMAT_EXTENSIBLE: {
                        /*
                         * Fill the RIFF chunk size.
                         */
                        if (fseek(p->fp, 4L, SEEK_SET) == -1) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }
                        if (WRITE_U32_LE(p->fp, 4 + (8 + CHUNK_SIZE_FMT_EXTENSIBLE) + (8 + p->data_byte_count)) != 0) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }

                        /*
                         * Fill the data sub chunk size.
                         */
                        if (fseek(p->fp, 12 + (8 + CHUNK_SIZE_FMT_EXTENSIBLE) + 4, SEEK_SET) == -1) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }
                        if (WRITE_U32_LE(p->fp, p->data_byte_count) != 0) {
                            result = WavFileResultErrorFileWrite;
                            goto finalize;
                        }
                    }
                    break;
                }
            }
            break;
    }

finalize:
    fclose(p->fp);
    free(p);
    return result;
}

void wavfile_result_string(const WavFileResult result, char *buf, size_t siz) {
    switch (result) {
        case WavFileResultOK:
            strcpy(buf, "OK.");
            break;
        case WavFileResultErrorInvalidFileName:
            strcpy(buf, "Invalid file name found.");
            break;
        case WavFileResultErrorMemoryAllocation:
            strcpy(buf, "Memory allocation error.");
            break;
        case WavFileResultErrorFileOpen:
            strcpy(buf, "File open error found.");
            break;
        case WavFileResultErrorFileWrite:
            strcpy(buf, "File write error found.");
            break;
        case WavFileResultErrorBrokenChunkId:
            strcpy(buf, "Broken chunk ID found.");
            break;
        case WavFileResultErrorBrokenChunkSize:
            strcpy(buf, "Borken chunk size found.");
            break;
        case WavFileResultErrorBrokenChunkData:
            strcpy(buf, "Borken chunk data found.");
            break;
        case WavFileResultErrorBrokenFormatId:
            strcpy(buf, "Broken format ID found.");
            break;
        case WavFileResultErrorInvalidFormatId:
            strcpy(buf, "Invalid format ID found.");
            break;
        case WavFileResultErrorBrokenAudioFormat:
            strcpy(buf, "Broken audio format found.");
            break;
        case WavFileResultErrorInvalidAudioFormat:
            strcpy(buf, "Invalid audio format found.");
            break;
        case WavFileResultErrorInvalidNumChannels:
            strcpy(buf, "Invalid number of channels found.");
            break;
        case WavFileResultErrorBrokenNumChannels:
            strcpy(buf, "Broken number of channels found.");
            break;
        case WavFileResultErrorBrokenSampleRate:
            strcpy(buf, "Broken sample rate found.");
            break;
        case WavFileResultErrorBrokenByteRate:
            strcpy(buf, "Broken byte rate found.");
            break;
        case WavFileResultErrorInvalidByteRate:
            strcpy(buf, "Invalid byte rate found.");
            break;
        case WavFileResultErrorBrokenBlockAlign:
            strcpy(buf, "Broken block alignment found.");
            break;
        case WavFileResultErrorBrokenBitsPerSample:
            strcpy(buf, "Broken bits per sample found.");
            break;
        case WavFileResultErrorUnsupportedBitsPerSample:
            strcpy(buf, "Unsupported bits per sample found.");
            break;
        case WavFileResultErrorAlreadyInfoChecked:
            strcpy(buf, "Already checked info.");
            break;
        case WavFileResultErrorAlreadyDataChecked:
            strcpy(buf, "Already checked data.");
            break;
        case WavFileResultErrorNoDataChunk:
            strcpy(buf, "No data chunk.");
            break;
        case WavFileResultErrorInvalidMode:
            strcpy(buf, "Invalid mode.");
            break;
        case WavFileResultErrorNeedInfoChecked:
            strcpy(buf, "Need check info.");
            break;
        case WavFileResultErrorNeedDataChecked:
            strcpy(buf, "Need check data.");
            break;
        case WavFileResultErrorInvalidHandler:
            strcpy(buf, "Invalid handler.");
            break;
        default:
            strcpy(buf, "Unkonwn error found.");
            break;
    }
}