目的
正弦波を音として再生し、ドレミファソラシドの順番に再生及び保存します。
基本の正弦波関数:
$$
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形式で保存
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;
}
