0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語で正弦波を生成してドレミファソラシドを再生・WAV保存する方法

Posted at

目的

正弦波を音として再生し、ドレミファソラシドの順番に再生及び保存します。

基本の正弦波関数:

$$
y(t) = A \sin(2\pi f t)
$$

ここで:
A​ = 振幅(音量)
f​ = 周波数(Hz)
t​ = 時間(秒)
y(t)は-1〜1の範囲で振幅を返す。

この振幅を1秒間あたり44100個集めた集合で音を表現することが出来ます。

ドレミファソラシドを再生

test@test-fujitsu:~/kaihatsu/butsuri$ gcc sinedoremi.c -o sinedoremi -lasound -lm
test@test-fujitsu:~/kaihatsu/butsuri$ ./sinedoremi 
sinedoremi.c
#include <alsa/asoundlib.h>
#include <math.h>
#include <unistd.h>

int main() {
    unsigned int rate = 44100; // 標本化周波数(1秒間に何個標本を取るか)
    snd_pcm_t *handle;
    snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
    snd_pcm_set_params(handle,
                       SND_PCM_FORMAT_S16_LE,
                       SND_PCM_ACCESS_RW_INTERLEAVED,
                       1,          // モノラル(単音声)
                       rate,
                       1,          // チャネル数
                       500000);    // 500msの遅延

    // ドレミファソラシドの周波数(Hz)
    double frequencies[] = {
        261.63, // ド(C4)
        293.66, // レ(D4)
        329.63, // ミ(E4)
        349.23, // ファ(F4)
        392.00, // ソ(G4)
        440.00, // ラ(A4)
        493.88, // シ(B4)
        523.25  // ド(C5)
    };
    
    int num_notes = sizeof(frequencies) / sizeof(frequencies[0]); // ドレミファソラシドの個数 8
    double note_duration = 0.5; // 各音の長さ(秒)
    int samples_per_note = rate * note_duration; // 1つの音階を表現するのに必要な標本数
    
    int16_t buffer[samples_per_note];
    
    // 各音階を順番に演奏
    for(int note = 0; note < num_notes; note++) {
        double freq = frequencies[note];
        
        // 現在の音階のバッファを生成(それぞれの瞬間の振幅を計算してバッファに格納)
        // sin()関数の戻り値は -1.0 ~ +1.0​ の範囲だが16ビット符号付き整数に変換する必要がある
        // -1.0 ~ +1.0 に32767をかけると、-32767 ~ +32767の範囲となる。
        for(int i = 0; i < samples_per_note; i++) {
            buffer[i] = (int16_t)(sin(2 * M_PI * freq * i / rate) * 32767);
        }
        
        // 再生
        snd_pcm_writei(handle, buffer, samples_per_note);
        
        // 音の区切りを少し空ける
        usleep(50000); // 50ms
    }
    
    snd_pcm_close(handle);
    return 0;
}

ドレミファソラシドをWAV形式で保存

分かり辛いですが実際に再生出来てます。
图片.png

test@test-fujitsu:~/kaihatsu/butsuri$ gcc wav.c -o wav -lasound -lm
test@test-fujitsu:~/kaihatsu/butsuri$ ./wav 
WAVファイル生成中: sample.wav
音階: ドレミファソラシド
各音の長さ: 0.5秒
合計時間: 4.0秒
音階 1/8: 261.63 Hz
音階 2/8: 293.66 Hz
音階 3/8: 329.63 Hz
音階 4/8: 349.23 Hz
音階 5/8: 392.00 Hz
音階 6/8: 440.00 Hz
音階 7/8: 493.88 Hz
音階 8/8: 523.25 Hz
生成完了!ファイル: sample.wav
wav.c
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <string.h>

// WAVヘッダ構造体
typedef struct {
    char     chunk_id[4];     // "RIFF"
    uint32_t chunk_size;
    char     format[4];       // "WAVE"
    char     subchunk1_id[4]; // "fmt "
    uint32_t subchunk1_size;
    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;
    char     subchunk2_id[4]; // "data"
    uint32_t subchunk2_size;
} wav_header_t;

void write_wav_header(FILE *file, int num_samples, int sample_rate) {
    wav_header_t header;
    
    // RIFFヘッダ
    strncpy(header.chunk_id, "RIFF", 4);
    strncpy(header.format, "WAVE", 4);
    
    // fmtチャンク
    strncpy(header.subchunk1_id, "fmt ", 4);
    header.subchunk1_size = 16;
    header.audio_format = 1; // PCM
    header.num_channels = 1; // モノラル
    header.sample_rate = sample_rate;
    header.bits_per_sample = 16;
    header.byte_rate = sample_rate * header.num_channels * header.bits_per_sample / 8;
    header.block_align = header.num_channels * header.bits_per_sample / 8;
    
    // dataチャンク
    strncpy(header.subchunk2_id, "data", 4);
    header.subchunk2_size = num_samples * header.num_channels * header.bits_per_sample / 8;
    
    // 全体のサイズ
    header.chunk_size = 36 + header.subchunk2_size;
    
    // ヘッダ書き込み
    fwrite(&header, sizeof(header), 1, file);
}

int main() {
    unsigned int rate = 44100;
    const char *filename = "sample.wav";
    
    // WAVファイルを開く
    FILE *wav_file = fopen(filename, "wb");
    if (!wav_file) {
        fprintf(stderr, "ファイルを開けませんでした: %s\n", filename);
        return 1;
    }
    
    // ドレミファソラシドの周波数(Hz)
    double frequencies[] = {
        261.63, // ド(C4)
        293.66, // レ(D4)
        329.63, // ミ(E4)
        349.23, // ファ(F4)
        392.00, // ソ(G4)
        440.00, // ラ(A4)
        493.88, // シ(B4)
        523.25  // ド(C5)
    };
    
    int num_notes = sizeof(frequencies) / sizeof(frequencies[0]);
    double note_duration = 0.5; // 各音の長さ(秒)
    int samples_per_note = rate * note_duration;
    int total_samples = samples_per_note * num_notes;
    
    // 先にヘッダを書き込み(後で更新するため)
    write_wav_header(wav_file, total_samples, rate);
    
    int16_t buffer[samples_per_note];
    
    printf("WAVファイル生成中: %s\n", filename);
    printf("音階: ドレミファソラシド\n");
    printf("各音の長さ: %.1f秒\n", note_duration);
    printf("合計時間: %.1f秒\n", note_duration * num_notes);
    
    // 各音階を順番に生成して書き込み
    for(int note = 0; note < num_notes; note++) {
        double freq = frequencies[note];
        
        // 現在の音階のバッファを生成
        for(int i = 0; i < samples_per_note; i++) {
            buffer[i] = (int16_t)(sin(2 * M_PI * freq * i / rate) * 32767 * 0.8);
        }
        
        // WAVファイルに書き込み
        // 1音階につき0.5秒鳴らすので、22050(=44100/2)回書き込んでいる
        fwrite(buffer, sizeof(int16_t), samples_per_note, wav_file);
        
        printf("音階 %d/%d: %.2f Hz\n", note + 1, num_notes, freq);
    }
    
    fclose(wav_file);
    printf("生成完了!ファイル: %s\n", filename);
    
    return 0;
}

白色雑音(おまけ)

振幅を乱数にすると白色雑音になります。
実行するとザーと雑音が流れます。

test@test-fujitsu:~/kaihatsu/butsuri$ gcc white_noise.c -o white_noise -lasound -lm
test@test-fujitsu:~/kaihatsu/butsuri$ ./white_noise 
white_noise.c
#include <alsa/asoundlib.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main() {
    unsigned int rate = 44100;
    snd_pcm_t *handle;
    
    // ALSA初期化
    snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
    snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED,
                       1, rate, 1, 500000);
    
    srand(time(NULL));
    
    double duration = 3.0; // 3秒間
    int total_samples = rate * duration;
    int16_t *buffer = malloc(total_samples * sizeof(int16_t));
    
    // 白色雑音生成
    for(int i = 0; i < total_samples; i++) {
        buffer[i] = (rand() % 65536) - 32768;
    }
    
    // 再生
    snd_pcm_writei(handle, buffer, total_samples);
    
    free(buffer);
    snd_pcm_close(handle);
    return 0;
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?