WAVEファイルの読み込みと書き込みをC言語を用いて行う。
参考文献:C言語で始める音のプログラミング
WAVEファイルについて#
WAVEファイルはRIFF(Resource Interchange File Format)チャンクと呼ばれるブロック構造により、音のデータを記録している。
RIFFチャンクの中にfmtチャンクとdataチャンクが含まれている。
- fmtチャンク:標本化周波数や量子化精度など音データのプロパティを格納
- dataチャンク:音データそのもの
プログラム#
400Hzの波形を出力する音声ファイルを作成し、それを読み込むプログラム
lang:main.c
# include <stdio.h>
# include <stdlib.h>
# include <math.h>
# include "wave.h"
int main(int argv, char **argc)
{
    MONO_PCM pcmForWrite, pcmForRead;
    int i;
    double amplitude, frequency;
    
    pcmForWrite.fs = 8000;
    pcmForWrite.bits = 16;
    pcmForWrite.length = 8000;
    pcmForWrite.s = calloc(pcmForWrite.length, sizeof(double));
    
    amplitude = 0.25;
    frequency = 400.0;
    
    for(i = 0; i < pcmForWrite.length; i++)
    {
        pcmForWrite.s[i] = amplitude * sin((2.0 * M_PI * frequency * i) / pcmForWrite.fs);
        // A sin(2πft0/fs) + A/2 sine(2πft1/fs) ・・・
    }
    monoWaveWrite(&pcmForWrite, "testWave.wav");
    free(pcmForWrite.s);
    
    monoWaveRead(&pcmForRead, "testWave.wav");
    for(i = 0; i < pcmForRead.length; i++)
    {
        printf("%d:%lf\n", i, pcmForRead.s[i]);
    }
    free(pcmForRead.s);
    
    return 0;
}
lang:wave.h
# ifndef WAVE_H
# define WAVE_H
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# define GROUND 32768.0
# define MAXPLUS 65536.0
# define MAXMINUS 0.0
typedef struct
{
    int fs;
    // 標本化周波数
    int bits;
    // 量子化制度
    int length;
    // データ長
    double *s;
    // 音データ
} MONO_PCM;
// モノラルの音声データの定義
typedef struct
{
    char chunkID[4];
    long chunkSize;
    char chunkFormType[4];
} RIFF_CHUNK;
// RIFFチャンクの定義
typedef struct
{
    char chunkID[4];
    long chunkSize;
    short waveFormatType;
    short formatChannel;
    long samplesPerSec;
    long bytesPerSec;
    short blockSize;
    short bitsPerSample;
} FMT_CHUNK;
// fmtチャンクの定義
typedef struct
{
    char chunkID[4];
    long chunkSize;
    short data;
} DATA_CHUNK;
// dataチャンクの定義
typedef struct
{
    RIFF_CHUNK riffChunk;
    FMT_CHUNK fmtChunk;
    DATA_CHUNK dataChunk;
} WAVE_FORMAT;
// WAVEフォーマット
void monoWaveRead(MONO_PCM *pcm, char *fileName);
// モノラルの音声データ(waveファイル)の読み込み
void monoWaveWrite(MONO_PCM *pcm, char *fileName);
// モノラルの音声データ(waveファイル)書き込み
# endif
wave.c
# include "wave.h"
void monoWaveRead(MONO_PCM *pcm, char *fileName)
{
    FILE *fp;
    int i;
    WAVE_FORMAT waveFormat;
    
    fp = fopen(fileName, "rb");
    
    if(!fp)
    {
        printf("file open error");
        exit(0);
    }
    
    fread(waveFormat.riffChunk.chunkID, 1, 4, fp);
    fread(&waveFormat.riffChunk.chunkSize, 4, 1, fp);
    fread(waveFormat.riffChunk.chunkFormType, 1, 4, fp);
    // RIFFチャンクの読み込み
    
    fread(waveFormat.fmtChunk.chunkID, 1, 4, fp);
    fread(&waveFormat.fmtChunk.chunkSize, 4, 1, fp);
    fread(&waveFormat.fmtChunk.waveFormatType, 2, 1, fp);
    fread(&waveFormat.fmtChunk.formatChannel, 2, 1, fp);
    fread(&waveFormat.fmtChunk.samplesPerSec, 4, 1, fp);
    fread(&waveFormat.fmtChunk.bytesPerSec, 4, 1, fp);
    fread(&waveFormat.fmtChunk.blockSize, 2, 1, fp);
    fread(&waveFormat.fmtChunk.bitsPerSample, 2, 1, fp);
    // fmtチャンクの読み込み
    
    fread(waveFormat.dataChunk.chunkID, 1, 4, fp);
    fread(&waveFormat.dataChunk.chunkSize, 4, 1, fp);
    
    pcm->fs = waveFormat.fmtChunk.samplesPerSec;
    pcm->bits = waveFormat.fmtChunk.bitsPerSample;
    pcm->length = waveFormat.dataChunk.chunkSize;
    pcm->s = calloc(pcm->length, sizeof(double));
    
    short data;
    for(i = 0; i < pcm->length; i++)
    {
        fread(&data, 2, 1, fp);
        pcm->s[i] = (double)data/GROUND;
    }
    // dataチャンクの読み込み
    
    fclose(fp);
}
void monoWaveWrite(MONO_PCM *pcm, char *fileName)
{
    FILE *fp;
    int i;
    WAVE_FORMAT waveFormat;
    
    strcpy(waveFormat.riffChunk.chunkID, "RIFF");
    waveFormat.riffChunk.chunkSize = 36 + pcm->length * 2;
    strcpy(waveFormat.riffChunk.chunkFormType, "WAVE");
    
    strcpy(waveFormat.fmtChunk.chunkID, "fmt ");
    waveFormat.fmtChunk.chunkSize = 16;
    waveFormat.fmtChunk.waveFormatType = 1;
    // PCMの場合は1
    waveFormat.fmtChunk.formatChannel = 1;
    // モノラルの場合は1,ステレオの場合は2
    waveFormat.fmtChunk.samplesPerSec = pcm->fs;
    waveFormat.fmtChunk.bytesPerSec = (pcm->fs * pcm->bits) / 8;
    waveFormat.fmtChunk.blockSize = pcm->bits / 8;
    waveFormat.fmtChunk.bitsPerSample = pcm->bits;
    
    strcpy(waveFormat.dataChunk.chunkID, "data");
    waveFormat.dataChunk.chunkSize = pcm->length * 2;
    
    fp = fopen(fileName, "wb");
    
    if(!fp)
    {
        printf("file open error");
        exit(0);
    }
    
    fwrite(waveFormat.riffChunk.chunkID, 1, 4, fp);
    fwrite(&waveFormat.riffChunk.chunkSize, 4, 1, fp);
    fwrite(waveFormat.riffChunk.chunkFormType, 1, 4, fp);
    // RIFFチャンクの書き込み
    
    fwrite(waveFormat.fmtChunk.chunkID, 1, 4, fp);
    fwrite(&waveFormat.fmtChunk.chunkSize, 4, 1, fp);
    fwrite(&waveFormat.fmtChunk.waveFormatType, 2, 1, fp);
    fwrite(&waveFormat.fmtChunk.formatChannel, 2, 1, fp);
    fwrite(&waveFormat.fmtChunk.samplesPerSec, 4, 1, fp);
    fwrite(&waveFormat.fmtChunk.bytesPerSec, 4, 1, fp);
    fwrite(&waveFormat.fmtChunk.blockSize, 2, 1, fp);
    fwrite(&waveFormat.fmtChunk.bitsPerSample, 2, 1, fp);
    // fmtチャンクの書き込み
    
    fwrite(waveFormat.dataChunk.chunkID, 1, 4, fp);
    fwrite(&waveFormat.dataChunk.chunkSize, 4, 1, fp);
    
    short data;
    double s;
    for(i = 0; i < pcm->length; i++)
    {
        s = ((pcm->s[i] + 1.0) / 2) * (MAXPLUS + 1.0);
        if(s > MAXPLUS)
            s = MAXPLUS;
        else if(s < MAXMINUS)
            s = MAXMINUS;
        
        data = (short)(s + 0.5) - GROUND;
        fwrite(&data, 2, 1, fp);
    }
    
    fclose(fp);
}
まとめ#
- fmtチャンクのchunkIDを書き込む箇所でstrcpyを使っている。strcpyはコピー元の文字列のサイズがコピー先のサイズに満たない場合「¥0」が追記される。これによりWAVEファイルとして認識されず、音が鳴らなかった。
- 次回は様々な波形をプログラムにより作成してみる。
