+/**
+ * @file wavfile.cpp
+ * @author Shinichiro Nakamura
+ * @brief libcベースのWAVファイルモジュール。
+ */
+
+/*
+ * ===============================================================
+ * 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; /**< infoを確認したら真になるフラグ。 */
+ bool data_checked; /**< dataを確認したら真になるフラグ。 */
+ uint32_t data_byte_count; /**< データバイトカウント。 */
+ wavfile_info_t info; /**< 対象ファイルの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)
+{
+ /*
+ * ファイル名を検証する。
+ */
+ if (filename == NULL) {
+ *result = WavFileResultErrorInvalidFileName;
+ return NULL;
+ }
+
+ /*
+ * ファイルを開く。
+ */
+ 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;
+ }
+
+ /*
+ * ハンドラ領域を確保する。
+ */
+ WAVFILE *p = (WAVFILE *)malloc(sizeof(WAVFILE));
+ if (p == NULL) {
+ *result = WavFileResultErrorMemoryAllocation;
+ return NULL;
+ }
+
+ /*
+ * ハンドラ内部フィールドを設定する。
+ */
+ 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;
+
+ /*
+ * チャンクIDを取得する。
+ */
+ if (READ_U32_BE(p->fp, &chunk_id) != 0) {
+ if (feof(p->fp)) {
+ /*
+ * このループはデータチャンクのデータ先頭に到達して離脱する仕掛け。
+ * チャンクIDを確認するところでEOFになるのは所望のデータに到達できなかった証拠である。
+ */
+ result = WavFileResultErrorNoDataChunk;
+ goto finalize;
+ } else {
+ result = WavFileResultErrorBrokenChunkId;
+ goto finalize;
+ }
+ }
+
+ /*
+ * チャンクサイズを取得する。
+ */
+ if (READ_U32_LE(p->fp, &chunk_size) != 0) {
+ result = WavFileResultErrorBrokenChunkSize;
+ goto finalize;
+ }
+
+#if WAVFILE_DEBUG_ENABLED
+ /*
+ * チャンクIDとチャンクサイズのデバッグ用出力。
+ */
+ 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
+
+ /*
+ * チャンクIDに従って読み込みを実行する。
+ */
+ 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
+ /*
+ * フォーマットIDのデバッグ用出力。
+ */
+ 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;
+}
+
+/**
+ * @details 音声データは、どんな形式でも常に0.0から1.0の範囲で正規化して出力される。
+ */
+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:
+ {
+ 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;
+ }
+ p->data_byte_count-=2;
+ break;
+ case BITS_PER_SAMPLE_24:
+ {
+ 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;
+ }
+ 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;
+}
+
+/**
+ * @details 音声データは、どんな形式でも常に0.0から1.0の範囲で正規化して入力される。
+ */
+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;
+ }
+}
+