プログラミングおよび演習 NO.12

Last-Modified: 2016.11.25

ファイル処理

今まで、データの入出力関数として、

  printf() (画面へデータを出力する)と
  scanf() (キーボードからデータを入力する)

の2つの関数を使ってきました。
上記の2つの関数に対応して、

  fprintf() (ファイルへデータを書き込む)
  fscanf() (ファイルからデータを読みこむ)

は、ファイルに対してデータを読み書き(入出力)します。
先週の演習問題11-3では、コンソール画面のデータをコピーし、エクセルに貼り付けてグラフを描くという作業をしていました。
fprintf()を使ってファイルにデータを書き込んでおき、エクセルのファイル読込操作で読み込むようにすれば、そのようなやっかいな作業をしないで済みます。また、fscanf()を使えば、書き込んだデータをプログラム内に読み込んで使うことができます。ファイル操作を行うには、オペレーティングシステム(WindowsやMAC)が扱っているファイル名をファイルポインタに予め設定しておいて、ファイルポインタ(プログラムの中だけで使うファイル名のようなもの)を介して目的とするファイルに読み書きします。このために、予めファイルオープンを行い、そこで操作したいファイルとファイルポインタを関連付ける作業を行います。これらのファイル操作の手順は以下のようになります。
  1. ファイルオープン:ファイルポインタをオペレーティングシステムが使っているファイル名に関連づける作業を行う。
  2. ファイル入出力:ファイルポインタを介して、指定したファイルに対するデータの読み書きを行う。
  3. ファイルクローズ:ファイルポインタとファイル名との関連づけを解除する。
以上の1、2、3の処理を行うためのファイル入出力関数をコンパイラが用意しています。
その使い方は次のようになります。

(0)ヘッダファイルのインクルード、ファイルポインタの宣言
入出力関数用のヘッダファイル<stdio.h>を

  #include <stdio.h>

のようにインクルードします。次に、以下のようにファイルポインタを宣言します。

  FILE *fp; /* fpという名前のファイルポインタの宣言 */

ここで、FILEはファイルポインタのために定義した構造体変数の型を表します。(コンパイラの、_iobufという構造体タグ名の別名定義になっています。)

(1)ファイルオープン(ファイルの読み書きの前には必ず必要)。

  fp = fopen("data1.dat","r");

第1引数:ファイル名(文字列定数)
第2引数:読み込み用のファイルの時はr、書込みの用の時はwを指定する

上記の例では、data1.datという名前のファイルを読みこみ用にオープンし、ファイルポインタfpに関連付ける(ファイルポインタfpに、fopenの戻り値を代入)という処理を行っています。
ファイル名を間違えて、ファイルオープンに失敗した場合は、戻り値は0(NULL)になります。

(2)データの読み書き:次のように、第1引数に(1)でオープンしたファイルポインタを指定します。第2引数、第3引数以下の約束は、printf()やscanf()関数の場合と同じです。例えば、

  fprintf(fp,"%e",a);

は、ファイルポインタfpのファイルにfloat型変数 aのデータを、"%e"の書式で書き込むという意味になります。同様に、

  fscanf(fp,"%e",&a);

は、ファイルポインタfpのファイルから、"%e"の書式でデータを読み込んでfloat型変数aに代入します。
ファイルの終わりに到達して読み込むデータがない場合、関数の戻り値(int 型)にEOF(ファイルの終り)を返します。また、読み込みに失敗した場合はNULLを返します。

(3)ファイルクローズ:ファイルの読み書きが終わってファイルポインタfpの関連づけを解除したい場合は、

  fclose(fp);

で、(1)でオープンしたファイルをクローズします。


以下の例題は、ファイル出力関数fprintf()を使って、ファイルに波形データを書き込むプログラムです。途中の波形データを計算するところまでは、演習問題11-4と同じです。ファイル出力関数fprintf()は、キーボードから入力したファイル名のファイルに、計算した波形データを書き込みます。
ファイル名の拡張子は後で、エクセルに読み込むときの都合を考えて、

  txt

にしてください(例:coswave.txt など)。

例12-1(波形データのファイル書き込み)
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

void make_coswave(int,float,float,float,float *);
void make_sinwave(int,float,float,float,float *);
void add_data(int, float*,float*); 
void file_write(int,float *);

int main(void)
{
    float x1[10000], x2[10000];/* 配列x1,x2の宣言 */
    int len=10000; /*データの数*/
    float SampFrq=5500.0;/*サンプリング周波数*/
    float freq1=440; /*1番目の信号の周波数*/
    float freq2=444; /*2番目の信号の周波数*/
    float amp=0.5; /*信号振幅*/
    float tdel; /*時間きざみ*/
      
    tdel=1.0/SampFrq;
      
    make_coswave(len,freq1,tdel,amp,x1); /*コサイン波1の生成*/
    make_sinwave(len,freq2,tdel,amp,x2); /*サイン波2の生成*/
    add_data(len,x1,x2);/*波1,2の加算*/
      
    file_write(len,x1);/*ファイル書き込み*/

    return(0);
}

/*コサイン波の波形データを 配列xに入れる*/
void make_coswave(int len, float freq,float tdel,float amp,float *x) 
{ 
    int i;
    /* 未完成の部分は自分で作る */
    ‥‥
    for(i=0;i<len;i++){
        ‥‥
        x[i]=‥‥; 
    }
}

/*サイン波の波形データを 配列xに入れる*/
void make_sinwave(int len, float freq,float tdel,float amp,float *x) 
{ 
    int i;

    /* 未完成の部分は自分で作る */
    ‥‥
    for(i=0;i<len;i++){
        ‥‥
        x[i]=‥‥; 
    }
}

void add_data(int len,float *x1,float *x2)
{
    int i;

    for(i=0;i<len;i++)
        x1[i]=x1[i]+x2[i];
}

/* 配列xのデータをファイルに書き込む。*/
void file_write(int len, float *x)
{
    int i;
    FILE *fp; /* ファイルポインタの宣言 */
    char name[30];
      
    printf("\nfilename?=");
    fflush(0);scanf("%s",name); /*ファイル名のキーボード入力*/
      
    if((fp=fopen(name,"w"))==NULL){/*ファイルオープン*/
        printf("\nCan't open the source file\n");
        /* ファイルオープンに失敗した時のエラーメッセージの出力 */
        exit(1);
    }
      
    for(i=0; i<len; i++)
        fprintf(fp,"%4.3f\n",x[i]); /*データx[i]の書き込み*/
      
    fclose(fp); /*ファイルクローズ */
}


補足(どこのフォルダにファイルが書き込まれるか)


例12.1のプログラムをEclipseの画面上で実行すると、ワークスペースフォルダ内の下記の場所(project名フォルダ)にファイルが書き込まれます。

 z:\workspace\project名

ただし、Eclipseのワークスペースフォルダ(Eclipse の初期設定の際に、設定したフォルダ)を、z:\workspace以外のフォルダに設定している場合は、これとは違う場所になります。



演習問題12-1 (Revised : 2016/11/25)

(1)例12-1を参考にして、振幅が同じで周波数が異なる2つのサイン波データを足し合わせて、結果をファイルに書き込むプロブラムを作ってください。

ここで、振幅と周波数の組合せは、1.0と 1.0 kHz、および、1.0と2.0 kHzとします。
ただし、サンプリング周波数は10 kHzとしてください。

プログラムが正常に動作していれば、指定した名前のファイルが出来上がります。エクスプローラで自分の作業フォルダを開いて確認してください。

また、先週の演習問題11-3と同じやり方でエクセルにデータを読み込み、波形をグラフに描いてみてください。 実行前に予測した波形と一致しているかの確認は必ず行って下さい。ここでは考察として、プログラム全体の動作説明と、file_write関数の中で何を行っているかについて説明をしてください。

(2)次に、上で作ったプログラムの下に、(振幅,周波数)=(1.0,1.0kHz)のサイン波と、(振幅,周波数)=(1.0,2.0kHz)のコサイン波の足し算の結果を別の名前のファイルに書き込むプログラムを追加してください。

サンプリング周波数は(1)と同じとします。また、エクセルを用いてグラフにしてください。
最後に、ファイル名として(1)と同じ名前を指定して実行した場合の結果についても示し、考察してください。


注:ファイルに書き込んでいますので、先週のようにコンソール画面の出力リストをコピーしなくても済みます。エクセルで[ファイルを開く]を選び、ファイルの種類のところで、*.txt形式のファイルを選んで読み込んでください(今日の問題は、データ数を10000に増やしています。 この場合、先週のように画面をコピーするやり方は難しいことがわかります)。


以下のプログラムは演習12-1と逆の働きをするプログラムです。このプログラムでは、始めに演習12-1で作成したデータファイルのファイル名をキーボードから入力して、指定したファイルを開きます。更に、指定ファイルから読み込んだデータを配列xに収納します。その後、配列xに読み込まれたデータの最初の500個だけを画面表示します。

例12-2(ファイルから読み込んだデータの画面表示)(Revised : 2012/12/3)
#include <stdio.h>
#include <stdlib.h>

void file_read(int *,float *);
void disp_data(int,float *);

int main(void)
{
    int len;
    float x[10000]; /* データ配列xを宣言 */
      
    file_read(&len,x); /* 配列x[]に指定のファイルからデータを読み込む */
    disp_data(500,x);     /* 配列x[]のデータを画面表示する:データの確認。*/

    return(0);
}

void file_read(int *len, float *x) /* ファイル読み込み */
{
 
    FILE *fp;/* ファイルポインタの宣言 */
    char name[30];
      
    printf("\nfilename?="); /* ファイル名の入力 */
    fflush(0);scanf("%s",name);
    if((fp=fopen(name,"r"))==NULL){ /* ファイルオープン */
        printf("\nCan't open the source file\n"); 
        exit(1);/* プログラム終了 */
    }
    *len=0;
    while(1){
        /* 配列x[]にファイルからデータを読み込む.データが最後になったら終了する */
        if(fscanf(fp,"%f",&x[*len])==EOF) break; 
        *len=*len+1; /*データの数を数える */ 
    }
    fclose(fp); /* ファイルのクローズ */
}

void disp_data(int len, float *x) /* データの画面表示 */
{
    int i;
    for(i=0; i<len; i++)
        printf("%f\n",x[i]);
}

演習問題12-2(Revised : 2012/12/4)

例12-2 のプログラムを、配列xに読み込んだ任意の長さのデータがすべて画面に表示されるように修正して下さい。また、演習12-1で作成した波形データのファイルを読み込んで画面表示してください。なお、表示結果をレポートで送付する際は、最初の10行と最後の10行のみを添付し、途中は省略すること。考察では、file_read関数の各々の所で何をやっているか説明してください。

[補足説明]
演習12-1で名前を付けて作成した波形データは、z:\workspace\"演習12-1のproject名" のフォルダに入っています。この波形データファイルを、z:\workspace\"演習12-2のproject名" のフォルダにコピーしてから、実行してください。プログラムを実行すると、
 filename?=
のメッセージがでますので、コピーした波形データファイル名を入力してください。
 Can't open the source file
というエラーが出る場合は、ファイル名が間違っている、または"演習12-2のproject名"のフォルダに波形データが存在しない、の何れかの原因ですのでよく確認してください。


今日はここまでにします。


データの内部表現

今日は、コンピュータの中でデータがどのように扱われているか。さらに、それに伴ってファイルにどんな形でデータを読み書きするか、ということについて勉強します。

キーワードとして、
 ・バイナリデータ v.s. テキストデータ(アスキーコード)
 ・バイナリファイル v.s. テキストファイル

の違いを理解してもらいます(簡単に言うと、バイナリデータは演算のために2進数(1.0のデータ)で表現されているデータ、テキストデータは文字として表示するためのデータです)。

■文字データの内部表現

文字データ(半角文字)は、char 型 8bit(1Byte)のデータです。
(補足:Unicodeの場合、全角文字(漢字)1文字を24bit(3Byte)で表現します)
0~255までの数に、アルファベットや数字を対応させて、文字として表す約束で使います(アスキーコードと呼んでいます)。

まず、コンピュータの中で実際に使われているアスキーコードの約束を調べてみるために、次の例題をやってみてください。

任意の文字定数('a','b', '0','1'など)を、%dで画面表示した時に現れる数字を確認してください。反対に、48 49‥‥などの数を、%cで表示してみてください。どんな文字が表示されますか? 文字データは、コンピュータの中では、アスキーコードで入っていることがわかります。

例 12-3 /* 文字データのコード出力*/
#include<stdio.h>

int main(void)
{
    printf("a:%d b:%d c:%d\n",'a','b','c');
    printf("0:%d 1:%d 2:%d\n",'0','1','2');
    printf("0:%c 1:%c 2:%c\n",48,49,50);

    return(0);
}

次の例題は、48から122までのコードデータを%cで画面表示しています。
どの文字が、アスキーコードの何番に対応しているかがわかります。

例 12-4 /* アスキーコードの出力*/
#include<stdio.h>

int main(void)
{
    unsigned char i;

    for(i=48;i<=122;i++)
        printf("%d:%c\n",i,i);

    return(0);
}

■数値データの内部表現

次に、数値データをコンピュータがどんな形で扱っているかについて説明します。

・データの型とビット数
長さ範囲
short int2Byte=16bit0~(2^16-1) (=65,535)
long int4Byte=32bit0~(2^32-1) (=4,294,967,295)
float4Byte=32bit仮数部(23ビット=8,388,608  6~7桁)
指数部(8ビット=256), 符号1bit


・2進数表示、16進数表示
コンピュータの中では数を2進数で表現します。プログラムを作る際に、通常の10進数でなく、2進数を意識して作る必要がある場合が出てきます。その場合、2進数を直接記述するのは大変なので、代わりに16進数で表記してプログラムを作ります。
0~15の数の16進数、2進数、10進数各々の対応関係を以下に示します。

10進数
0
1
2
3
4
5
6
7
16進数
0
1
2
3
4
5
6
7
2進数00000001001000110100010101100111

10進数
8
9
10
11
12
13
14
15
16進数
8
9
A
B
C
D
E
F
2進数10001001101010111100110111101111

プログラムの中で16進定数を使うには、たとえば、0Aならば、

 0x0A

のように、先頭に0xを付けます。
整数型のデータを16進表示する際、例えば、
short型整数の16進表示(1バイトを2桁で表示、2バイトで4桁)

 0x2716

は、2×2^12+7×2^8+1×2^4+6×2^0=10000  を表します。

また、long(int)型整数の16進表示(4バイトで8桁)は、

 0x3b9aca00

のようになり、この場合10進で1,000,000,000を表わします。

・整数型データの表現(2の補数表現)
コンピュータの中では、正の整数値しか表現できません。そのために、負の数を次のように約束して表現します。(これを2の補数表現という)

まず、n=4bitの簡単な場合について説明すると、2の補数表現は以下のようになります。

 10進数  -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7
 2の補数  8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7


実際の、short型整数(n=16bit)の場合に対する、2の補数表現は以下のようになります。

 10進数  -32768 ‥‥   -2    -1  0  1  2 ‥‥ 32767
 2の補数   32768 ‥‥ 65534  65535  0  1  2 ‥‥ 32767


n bitデータの2の補数表現データxは、次のルールで、実際の符号付の数に変換できます(負の数は、2^nを引き算すれば計算できる)。

 if x >= 2^(n-1)
 then x=x-2^n


以上のことを踏まえた上で、整数データの内部表現を確かめるための、次の例題をやってください。

例 12-5(整数の%d、%x出力,上位バイト、下位バイト取り出し)
#include<stdio.h>
int main(void)
{
    short ds;
    int dl;
    
    ds=10000;
    //ds=0x2710;
    printf("\nds(10進)=%hd",ds);
    printf("\nds(16進)=%04hx ",ds);
    printf("\nds(下位)=%02hx ",(ds&0xFF));
    printf("\nds(上位)=%02hx\n",(ds>>8)&0xFF);
    
    dl=1000000000;
    //dl=0x3b9aca00;
    printf("\ndl(10進)%d ",dl);
    printf("\ndl(16進)%08x\n",dl);
    printf("\ndl(最下位)%02x ",(dl&0xFF));
    printf("\ndl(中下位)%02x",(dl>>8)&0xFF);
    printf("\ndl(中上位)%02x",(dl>>16)&0xFF);
    printf("\ndl(最上位)%02x",(dl>>24)&0xFF);

    return(0);
}

例12-5のプログラムの補足説明
・16進数表示のための書式


  %d:10進数表示
  %x:16進数表示
  %02x 16進数を2桁で表示(数字の先頭が0の場合、0を詰めて表示)
  %02hx hはshort型整数を表示

・上位バイト、下位バイト取り出し方法

  short型データdsの下位バイトの取り出しは、

  ds & 0xFF

とすることにより実現できます。ここで、

  &

はand(論理積)演算子を表します(両方のビットデータが1のとき、1にする)。従って、

  0xFF (=11111111)

とdsのandをとると、下位バイトはそのまま、上位バイトは0になります。

次に、上位バイトの取り出しは

  (ds>>8)&0xFF

の演算で実現しています。ここで、

  >>

は右シフト演算子です。例えば、

  ds>>8

は、dsを8ビット右にシフトさせます。
この結果、上位バイトが下位バイトの桁の位置に移動します。その後、0xFFとandをとることにより、目的とする上位バイトを取り出すことができます。

演習問題12-3 (Revised : 2013/12/02)

例12-3,12-4, 12-5のプログラムを次のように変更してください。

(1)例12-3の3つのprintf()文を以下のものに差し替え、「??」に適切な「2桁~4桁の数字」(注:文字ではない)を書き入れてプログラムを完成せよ。

 printf("%c%c%c %c%c %c%c%c%c %c%c%c %c%c%c%c\n",??,??,??,??,??,??,??,??,??,??,??,??,??,??,??,??);

なお、実行結果は以下の通りとなるようにせよ。

 :^) :( :'-( :-@ (:-&

(※海外で使われることの多い顔文字で、左から「ハッピー」「悲しい」「泣いている」「叫び」「怒り」を表す)

(2)例12-4のプログラムを変更して、キーボードから1文字を入力したら、そのアスキーコード(整数)を出力するプログラムを作ってください。反対に、アスキーコード(整数)を入力したら、文字を出力するプログラムを作ってください。

(3)例12-5のプログラムの変数ds,dlの値を次のように変更してください。プログラムを実行し、実行結果を説明してください。

ds=65534; dl=4294967294;




バイナリファイル

以上の準備ができたところで、バイナリファイルの読み書きの方法を説明します。バイナリファイルは、数値データを2進数のバイナリデータのままで書き込んだデータです。バイナリファイルはエディタやワープロで呼び出しても中味を見ることができません。しかし、テキストファイル(アスキーコード)で書き込む場合に比べて、ファイル容量が小さくできます。このため、オペレーティングシステムのアプリケーションプログラムが扱うデータファイル(音楽データの *.wav、画像データの *.bmpなど)の、殆ど全てがバイナリファイルになっています。

■バイナリデータのファイル書込み
バイナリファイルは、今まで習ってきたテキストファイルと殆ど同じようにして、プログラムすることができます。その手順を以下に示します。

①ファイルオープン


  fp=fopen("file.bin","wb")

 第1引数:オープンするファイル名
 第2引数:wbの、wは書き込み用、bはバイナリファイルを表す。

上記の例の場合は、file.binの書き込み用のバイナリファイルをオープンして、ファイルポインタfpと関連づける処理を行います。

②fpに1バイト書き込み

  putc(d,fp)

 第1引数:書き込むデータの変数名
 第2引数:ファイルポインタ。

は、ファイルポインタfpに、dを1バイト整数とみなして、1バイト分のデータを書き込みます。

次の例題は、short型整数ds=10000をバイナリファイル(data.binの名前のファイル)に書き込んでいます。short型整数は2バイトデータのため、 最初に下位バイト、次に上位バイトの順番で2回 putc()関数を呼び出して書き込んでいます。

例 12-6 /*バイナリデータの書き込み */
#include<stdio.h>
int main(void)
{
    short ds;
    FILE *fp;
    
    fp=fopen("data.bin","wb");
    ds=10000;
    putc(ds&0xFF,fp);
    putc((ds>>8)&0xFF,fp);
    
    fclose(fp);

    return(0);
}

■バイナリデータの読み込み
以下は、上記例題12-6で書き込んだバイナリデータを読み込むためのプログラムです。

例 12-7 /* バイナリデータの読み込み*/
#include<stdio.h>

int main(void)
{
    short ds;
    FILE *fp;
    
    fp=fopen("data.bin","rb");
    ds= getc(fp);
    ds=ds | getc(fp)<<8;
    printf("ds=%d(10進) %x(16進)\n",ds,ds);

    fclose(fp);

    return(0);
}

補足:

fp=fopen("data.bin","rb");
 第2引数の"rb"のrは読み込み、bはバイナリファイルを表す。

ds=getc(fp)
getc(fp)は、ファイルポインタfpのファイルから、1バイトのデータを読み込むための関数です。 読み込んだデータ(short int 型)は、戻り値で返されます。 上記の場合、1回目に読み込んだデータ(下位バイトデータ)を、dsに代入しています。

getc(fp)<<8  で

  <<

の記号は左シフト演算子です。ここでは、getc(fp)で読み込んだデータ(上位バイトデータ) を8ビット左シフトして、上位バイトに桁移動させています。

ds | getc(fp)<<8  で

  |

の記号は、ビットごとのor演算(論理和演算子)を表します。
ds(その前のところで下位バイトデータが入っている)と 2回目にgetc(fp)で読み込んだ上位バイトデータのorをとる演算を行っています。 これにより、上位バイトデータと下位バイトデータを繋ぎ合わせています。

演習問題12-4 (Revised : 2014/12/08)

例12-6、12-7のプログラムを次のように変更してください。プログラムを実行すると同時に、プログラムの動作を説明してください。

(1)例12-6を参考にして、300~350までの連続した整数値を、バイナリファイ ルに書き込むプログラムを作ってください。

  ・ヒント:書き込み部分を以下のように作ってください。ただし、「?」には適切な数字、文字、記号を書き入れること。

    for(ds=???; ds<???;ds??){
       putc(ds&0x??,fp);
       putc((ds>>?)&0x??,fp);
    }


  ・出来上がったバイナリファイルを、試しにエディタプログラム(端末室のWindowsPCの場合は”notepad(メモ帳)”や”サクラエディタ”を使ってください)で読み込んでみてください(notepadで開いた場合は文字化けしてしまい読むことができないことを確認してください)。

(2)例12-7を参考にして、上記(1)で作成したバイナリファイル(300~350までの数のバイナリデータ)を読み込んだのち、データを画面に表示するプログラムを作ってください。


[補足説明]

12-4(1)の課題で、保存されたバイナリファイルを、notepadで読み込んだ後、農工大メールに貼り付けて送信しようとすると、

選択された文字コードでは利用できない文字を含んだテキストを送信しようとしています

のエラーが出て送信できないことがあります。この場合は次のいずれかの方法を試してください。

方法1:メール送信のテキストのエンコード方法を、日本語ISO-2022-JPではなく、Unicode(UTF8) に変更してください。
方法2: notepadの代わりに、サクラエディタで開いてください(実は、サクラエディタで開くと文字化けしません)。




■サウンドファイルの作成

上記のバイナリデータを応用すると、サウンドファイルを作ることができます。
wavファイル(拡張子がwavのファイル、正確にはRIFFファイルと呼ばれている) は音声データ記述のためのファイル形式です。 元々、Windowsの標準サウンドファイル形式として作られましたが、Macでも使うことができます。 詳細は省略しますが、wavファイルは以下のような構造のバイナリファイルです。

 ID(4バイト)
 サイズ(4バイト)
 フォームサイズ
 fmtチャンク
 dataチャンク (ここにサウンドファイルをバイナリ形式で書き込む)

演習問題12-5 (余裕のある人へ)

演習問題12-1では、テキスト形式のファイルを書き込みました。
以下のプログラムは、wavフォーマットのサウンドファイルを作成するプログラムです。

(1)プログラムを実行することによって、サウンドファイルを作成してみてください (この例では、2つのサイン波の合成音(うなり音)を作成しています)。

注:プログラムを実行すると、ファイル名の入力を求めてきますので、 ファイル名を指定してください。その際、ファイル名には、拡張子wavを付けてください。

(2)作成したサウンドファイルを実行して、実際に音を鳴らしてみてください。

注:作成したサウンドファイルを音楽プレーヤ(Windowsの場合はWindows Media Player、MACの場合はQuickTime Playerなど)で聞いてみてください。

(3)他の波形についても行って見てください。いろいろ聞き比べてみると面白いと思います。


/* サウンドファイルの書き込み*/
#include <stdio.h>
#include <math.h>
#include <limits.h>

#define PI 3.14159265358979

void wave_write(int, float,float *);
void add_data(int,float *,float *);
void make_sinwave(int,float,float,float,float *);
float max_data(int,float *);
void fputc2hl( unsigned short int, FILE *);
void fputc4hl( unsigned long int, FILE *);
void fputc2lh( unsigned short int, FILE *);
void fputc4lh( unsigned long int, FILE *);

int main(void)
{
    long len=10000;
    long SampFrq=5500;
    float freq1=440.0,freq2=444.0;
    float amp=0.5;
    float x1[10000],x2[10000];
    
    make_sinwave(len,freq1,1.0/SampFrq,amp,x1);
    make_sinwave(len,freq2,1.0/SampFrq,amp,x2);
    add_data(len,x1,x2);
    
    wave_write(len,SampFrq,x1);

    return(0);
}

void wave_write(int len, float SampFrq,float *x)
{
    int i;
    short dd;
    float xmax;
    FILE *fp;
    char name[30];
    
    xmax=max_data(len,x);
    
    printf("\nfilename?="); fflush(0); scanf("%s",name);
    if((fp=fopen(name,"wb"))==NULL){
        printf("\nCan't open the source file\n");
        exit(1);
    }
    
    fputs("RIFF",fp);
    fputc4lh(len*2+36,fp);
    fputs("WAVE",fp);
    fputs("fmt ",fp);
    fputc4lh(16,fp);
    fputc2lh(1,fp);
    fputc2lh(1,fp);
    fputc4lh(SampFrq,fp);
    fputc4lh(SampFrq*2,fp);
    fputc2lh(2,fp);
    fputc2lh(16,fp);
    fputs("data",fp);
    fputc4lh(len*2,fp);
    
    for(i=0; i<len; i++){
        dd=x[i]*32767/xmax;
        fputc2lh(dd,fp);
    }
    fclose(fp);
}

float max_data(int len,float *x)
{
    int i;
    float xmax,ax;
    
    xmax=0.0;
    for(i=0;i<len;i++){
        ax=fabs(x[i]);
        if(ax>xmax)
            xmax=ax;
    }
    return(xmax);
}

void add_data(int len,float *x1,float *x2)
{
    int i;

    for(i=0;i<len;i++)
        x1[i]=x1[i]+x2[i];
}

void make_sinwave(int len, float freq,float tdel,float amp,float *x) 
{
    int i;
    float time;
    
    for(i=0;i<len;i++){
        time=tdel*i;
        x[i]=amp*sin(2.0*PI*freq*time);
    }
}

void fputc2hl(unsigned short int d, FILE *fp)
{
    putc(d>>CHAR_BIT,fp);
    putc(d &0xFF,fp);
}

void fputc4hl(unsigned long int d, FILE *fp)
{
    putc((d>>CHAR_BIT*3)&0xFF,fp);
    putc((d>>CHAR_BIT*2)&0xFF,fp);
    putc((d>>CHAR_BIT )&0xFF,fp);
    putc(d &0xFF,fp);
}

void fputc2lh(unsigned short int d, FILE *fp)
{
    putc(d &0xFF,fp);
    putc(d>>CHAR_BIT,fp);
}

void fputc4lh(unsigned long int d, FILE *fp)
{
    putc(d &0xFF,fp);
    putc((d>>CHAR_BIT )&0xFF,fp);

    putc((d>>CHAR_BIT*2)&0xFF,fp);
    putc((d>>CHAR_BIT*3)&0xFF,fp);
}