質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

3回答

2465閲覧

cで書き出したwavファイルの周波数が思う結果と違う。

taro_nii_chan

総合スコア207

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2017/05/22 21:48

編集2017/05/23 00:42

C言語で音声合成もどき ~WAVファイルを生成する~

を参考に、指定した時間(秒)の内に指定した開始時の周波数から終了時の周波数まで音の高さが変わるコードを書こうとしています。

c

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <math.h> 5 6// 44100Hz, 8bit, 1ch のWAVデータ 7#define SMPL 44100 8#define BIT 8 9 10void wav_write(const char *filename, char *buf, int size) 11{ 12 int filesize = 44 + size; 13 char *work = (char *) malloc(filesize); 14 FILE *fp = fopen(filename, "wb"); 15 16 if (fp == NULL) return; 17 18 /* RIFFヘッダ */ 19 memcpy(work, "RIFF", 4); 20 work[4] = (filesize - 8) >> 0 & 0xff; 21 work[5] = (filesize - 8) >> 8 & 0xff; 22 work[6] = (filesize - 8) >> 16 & 0xff; 23 work[7] = (filesize - 8) >> 24 & 0xff; 24 /* WAVEヘッダ */ 25 memcpy(work+8, "WAVE", 4); 26 /* fmtチャンク */ 27 memcpy(work+12, "fmt ", 4); 28 work[16] = 16; 29 work[17] = work[18] = work[19] = 0; 30 work[20] = 1; 31 work[21] = 0; 32 work[22] = 1; 33 work[23] = 0; 34 work[24] = SMPL >> 0 & 0xff; 35 work[25] = SMPL >> 8 & 0xff; 36 work[26] = SMPL >> 16 & 0xff; 37 work[27] = SMPL >> 24 & 0xff; 38 work[28] = (SMPL * (BIT / 8)) >> 0 & 0xff; 39 work[29] = (SMPL * (BIT / 8)) >> 8 & 0xff; 40 work[30] = (SMPL * (BIT / 8)) >> 16 & 0xff; 41 work[31] = (SMPL * (BIT / 8)) >> 24 & 0xff; 42 work[32] = ((BIT / 8)) >> 0 & 0xff; 43 work[33] = ((BIT / 8)) >> 8 & 0xff; 44 work[34] = BIT >> 0 & 0xff; 45 work[35] = BIT >> 8 & 0xff; 46 /* dataチャンク */ 47 memcpy(work+36, "data", 4); 48 work[40] = size >> 0 & 0xff; 49 work[41] = size >> 8 & 0xff; 50 work[42] = size >> 16 & 0xff; 51 work[43] = size >> 24 & 0xff; 52 memcpy(work + 44, buf, size); 53 54 /* 書き出し */ 55 fwrite(work, filesize, 1, fp); 56 fclose(fp); 57 free(work); 58} 59 60int main(void) 61{ 62 int size; 63 char *buf; 64 int sec; 65 66 sec = 3; 67 size = (SMPL*(BIT/8)) * sec; 68 buf = (char *) malloc(size); 69 70 double freq_start = 886; // 開始時の周波数 71 double freq_end = 443; // 終了時の周波数 72 73 double t; // 時間(秒) 74 double freq; // 周波数 75 double y; // 出力 76 77 for (int i = 0; i < size; i++) { 78 // 時間(秒) 79 t = i / (double)size * sec; 80 81 // 周波数 82 freq = freq_start + (t / sec) * (freq_end - freq_start); 83 84 // 出力 85 y = sin(2.0 * M_PI * freq * t); 86 87 buf[i] = floor(y * 63) + 128; 88 89#ifdef EBUG 90 printf("%f\n", freq); 91#endif 92 } 93 94 wav_write("test.wav", buf, size); 95 96 free(buf); 97 return 0; 98}

この例では周波数を886Hzから443Hzまで変化させようとしていますが、
実際に出力されたtest.wavは886Hzから0Hzまで落ちてしまいます。

ちなみに

c

1 double freq_start = 886; // 開始時の周波数 2 double freq_end = 443; // 終了時の周波数 3

c

1 double freq_start = 443; // 開始時の周波数 2 double freq_end = 886; // 終了時の周波数

とすると意図した通り周波数は443Hzから886Hzに変化します。

何がいけないのでしょうか?

(補足)

printfで出力すると周波数(freq)は思い通りに変化しています。
(自分だけじゃなく、Zuishinさんがダブルチェックをしていただけたのでこれは大丈夫)

所が音源ファイル(test.wav)を実際に聴いてみると、
周波数は0まで落ちているのです。
その確認は「audacity」という、波形を見ながら音を再生できるソフトで出来ますし、
耳で聞いてもおかしいのは分かります。

周波数は正しく変化していて、
その上でwavファイルがその通りに書き出されてない。

freqとtest.wavが一致しないのは何故なのでしょう?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

原因は位相の求め方にあります。

本件ではサンプルごとになだらかに周波数を変えようとしていますね。このときある瞬間の周波数は直前の位相との位相差により決まります。ところが質問コードでは隣り合ったサンプルの位相の差は期待した周波数になりません。そのため意図した周波数とずれた周波数の音になってしまうのです。

y = sin(2 * M_PI * freq * t);

で波形を求めるためには条件が必要で、その条件はこの式を用いる期間で「周波数が不変でt=0の時の位相が2πの倍数」であることです。

以下のようにするとサンプル間の位相差がi番目のサンプルの周波数に従った値になるため期待通りの結果になります。

C

1... 2double phase = 0.0; // 位相 3for (int i = 0; i < num_sample; i++) { 4 double freq = ...; 5 phase += 2 * M_PI * freq / SMPL; 6 double y = sin(phase); 7 ... 8} 9...

他の指摘:

  • forループの繰り返し回数

本来の意味は「サンプル数」であるべきです。しかし、コードでは「バッファーのバイト数」となっています。本コードではたまたま両者が同じ値なので問題は生じませんが正しいコードとは言えず、例えばこの論理を16bit PCMへ流用した場合などにバグの元になると思います。

投稿2017/05/23 00:47

編集2017/05/23 11:43
KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

taro_nii_chan

2017/05/23 01:13

なるほど! って言えたらかっこいいんでしょうけど、 理論がまだ良くわかってません。 この辺のことはどこで勉強できますか? 書籍でもいいしサイトでもいいので 参考になる物があったら教えて下さい。 取り敢えず、思い通りじゃなかったものが動いて すごく爽快な気分です。 ありがとうございます。
KSwordOfHaste

2017/05/23 02:00 編集

人それぞれ気づき方は違うかも知れません。自分は円周上を角速度をだんだん遅くしながら回転する運動を頭に思い描き「直前の点の位置(角度)からどのくらい角度が変化するかが周波数になるのだから位相が連続してないといけないんだな・・・」というふうに気づけました。 勉強という意味では音波=波についての数学(高校や大学初等の数学)や微分的な考え方をよく理解しておくことが役立ちます。理解というのは数式や公式を単に覚えるのではなく幾何的・物理的な意味のイメージを持てるようにすることです。専門家しかわからないような難しい話だと自習は困難ですが、高校などで学んだはずの数学が基礎になっている応用問題ならば、(学校で学んだ当時は公式丸暗記だったとしても)プログラミングで実際に使うとわかったなら「より真剣に深く理解しよう」という気が起きると思います。そのやる気を大事にして関連しそうなサイトを眺めていろいろ考える訓練をすれば、忘れていた数学的思考がよみがえってくると思います。
taro_nii_chan

2017/05/23 02:01

分かりました。 ありがとうございました。 またよろしくお願いします。
ozwk

2017/05/23 02:08 編集

> 質問コードでは隣り合ったサンプルの位相に連続性がありません。 位相が微分可能なので、連続性はあるのでは? (そもそも時間が離散ですが、離散化前の式が。)
KSwordOfHaste

2017/05/23 02:13 編集

>ozwkさん ご指摘のように「連続性」という言葉は不適切でした。 i番目のサンプルの周波数がfならi-1番目のサンプルの位相p-Δとi番目のサンプルの位相pが「周波数fとしての位相の差になってなければならない」というべきでしたね。 それでもtの大きさが大きくなるにつれて位相差のかい離は大きくなっていき連続でないような外観を呈するような気がしたので「連続でない」といっちゃいました。
KSwordOfHaste

2017/05/23 11:44

遅くなりましたが、連続性という用語を回答で使わないよう表現を変更しました。
guest

0

bufを書き出してみました(図は最後の方だけ)
イメージ説明

これ見ればわかるように最後の方で周波数が0になります。

ということは、そもそも根本的に、
質問文にあるようなアルゴリズムで886から443Hzに変化する音は作れません。

というのも、音の瞬間的な周波数(瞬時周波数)は音の波形が
sin(2π*φ(t))で表せるとき、
dφ/dtで表せます。

単純に、sin(2π*f(t)*t)だからf(t)が今の周波数になるわけじゃないです。

これに基づいて計算すると、
今回の波形の瞬時周波数は
fs + 2*(fe-fs)*t/Tとなり、(t=0~T)
t=Tのとき
2fe-f0 = 2443 - 883 =0となります。

逆に、意図通りの周波数変化:f(t)となる音を出すには、
f(t)の積分F(t)を計算して、
sin(2π*F(t))とすれば良いことがわかります

投稿2017/05/23 00:34

編集2017/05/23 01:16
ozwk

総合スコア13521

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

taro_nii_chan

2017/05/23 00:52

ありがとうございます。 質問に追記しましたが、 「audacity」というソフトでこの様な波形が見られます。 実際、ozwkさんが貼っていただけた画像と同様のものが現れています。
ozwk

2017/05/23 01:01

なので、y=...の式がそもそもおかしいんです、と言いたかったんですが 追記します。
taro_nii_chan

2017/05/23 01:31

素晴らしい。納得です。 KSwordOfHasteさんのアプローチも素晴らしくて ベストアンサーを1人しか決められないのが恨めしいくらいです。
guest

0

C

1#include <stdio.h> 2#include <math.h> 3 4int main(void) { 5 int size; 6 int sec; 7 8 sec = 3; 9 size = 30; 10 11 double freq_start = 886; // 開始時の周波数 12 double freq_end = 443; // 終了時の周波数 13 14 double t; // 時間(秒) 15 double freq; // 周波数 16 17 for (int i = 0; i < size; i++) { 18 // 時間(秒) 19 t = i / (double)size * sec; 20 21 // 周波数 22 freq = freq_start + (t / sec) * (freq_end - freq_start); 23 24 printf("%f\n", freq); 25 } 26 return 0; 27}

上記の出力結果は次のようになりました。

C

1886.000000 2871.233333 3856.466667 4841.700000 5826.933333 6812.166667 7797.400000 8782.633333 9767.866667 10753.100000 11738.333333 12723.566667 13708.800000 14694.033333 15679.266667 16664.500000 17649.733333 18634.966667 19620.200000 20605.433333 21590.666667 22575.900000 23561.133333 24546.366667 25531.600000 26516.833333 27502.066667 28487.300000 29472.533333 30457.766667

i < size という条件なので 443 ちょうどにはなりませんが、886 から 443 へ変化しています。

投稿2017/05/22 23:17

Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

taro_nii_chan

2017/05/22 23:58

そうなんです、printfで出力すると周波数(freq)は思い通りに変化しているのです。 所が音源ファイル(test.wav)を実際に聴いてみると、 周波数は0まで落ちているのです。 その確認は「audacity」という、波形を見ながら音を再生できるソフトで出来ますし、 耳で聞いてもおかしいのは分かります。 周波数は正しく変化していて、 その上でwavファイルがその通りに書き出されてない。 全く不思議でしょうがないのですが。 これは何なんでしょう?
Zuishin

2017/05/23 00:00

その情報は質問へ追記してください。freq が正しいのであれば、freq の値を追う作業は無意味でした。
taro_nii_chan

2017/05/23 00:43

質問への追記、しました。 freqは「私の確認した限りでは」だったので、 そもそもそこからしておかしいという事も考えられたので、 freqを追う作業は私にとっては全然無意味ではないです。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問