メリークリスマス!
何も予定のない同類のために1時間くらいでできる遊びを紹介します.
(はじめて書くのであしからず)
#環境
- MacOS
- DTM : GrageBand
- 言語 : C/C++
Macユーザなら何も準備しないでできると思います.
GrageBandはMacOS用のDTMソフトです.
フリーで使用できます.
#概要
- DTMでwavファイルを生成
- 信号処理向けにiTunesでwavファイルを変換
- C/C++でファイル読み込み&書き込み
- 遊び要素を紹介
#GarageBandでwavファイルを作成
まずはとりあえず開いてみて新規プロジェクトを作成します.
次に,こんな画面が出てくるのでピアノの横の領域を右クリックし,
「空のMIDIリージョンを作成」を選択します.
すると,緑色の物(リージョン)が出てくるので,
ダブルクリックで編集します.
編集では,下の画面に鍵盤が出てくるので適当にCommand+クリックで音符を入力してみてください.
左上のプラス(+)ボタンで楽器を足してみると面白いです.
ドラムも自動演奏があるので良さげなやつを選択してみます.
完成しましたら
「共有」->「曲をディスクに書き出す」
で好きな場所にwavファイルを書き出します.
#iTunesでwavファイルを変換
まず,iTunes->「環境設定」->「読み込み設定」->「設定:カスタム」を選択します.
ここで,サンプリングレートを8.000kHz,量子化bit数を16bit,そしてモノラルに設定します.
次に,再びiTunesで
「ファイル」->optionを押しながら「変換」->「wavに変換」を選択し,
作った曲を保存しているディレクトリを選択します.
#C/C++でファイル読み込み&書き込み
作った曲をC/C++で読み込みます.
以下のサイトを参考(というか丸パクリ)にしました.
C言語を使ったエフェクター 第一回:wavファイルの入出力
拡張子はcppですが,C言語でもできます.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "audioio.h"
int main(int argc, char *argv[])
{
//変数宣言
WAV_PRM prm_in, prm_out;
double *data_in, *data_out;
int n;
char filename[64];
// prm_in.fs = ; //サンプリング周波数
// prm_in.bits = 16; //量子化bit数
// prm_in.L; //データ長
//コマンドライン引数が違う場合
if(argc != 2){
printf("引数が違います\n");
exit( 1 );
}
//出力ファイル名入力
printf("output file name : ");
scanf("%s", filename);
//wavファイルの読み込み
data_in = audio_read(&prm_in, argv[1]);
//パラメータコピー
prm_out.fs = prm_in.fs;
prm_out.bits = prm_in.bits;
prm_out.L = prm_in.L;
//データコピー(実際にはこの代わりにエフェクト処理をかける)
data_out = (double *)calloc(prm_out.L, sizeof(double)); //メモリの確保
for (n = 0;n < prm_out.L;n++){
data_out[n] = data_in[n];
}
//書き込み
audio_write(data_out, &prm_out, filename);
//メモリ解放
free(data_in);
free(data_out);
return 0;
}
変更点は以下だけですね.
data_out = (double *)calloc(prm_out.L, sizeof(double));
ヘッダファイルはこんな感じです.
typedef struct
{
int fs; //サンプリング周波数
int bits; //量子化bit数
int L; //データ長
} WAV_PRM;
double *audio_read(WAV_PRM *prm, char *filename)
{
//変数宣言
FILE *fp;
int n;
double *data;
char header_ID[4];
long header_size;
char header_type[4];
char fmt_ID[4];
long fmt_size;
short fmt_format;
short fmt_channel;
long fmt_samples_per_sec;
long fmt_bytes_per_sec;
short fmt_block_size;
short fmt_bits_per_sample;
char data_ID[4];
long data_size;
short data_data;
//wavファイルオープン
fp = fopen(filename, "rb");
//wavデータ読み込み
fread(header_ID, 1, 4, fp);
fread(&header_size, 4, 1, fp);
fread(header_type, 1, 4, fp);
fread(fmt_ID, 1, 4, fp);
fread(&fmt_size, 4, 1, fp);
fread(&fmt_format, 2, 1, fp);
fread(&fmt_channel, 2, 1, fp);
fread(&fmt_samples_per_sec, 4, 1, fp);
fread(&fmt_bytes_per_sec, 4, 1, fp);
fread(&fmt_block_size, 2, 1, fp);
fread(&fmt_bits_per_sample, 2, 1, fp);
fread(data_ID, 1, 4, fp);
fread(&data_size, 4, 1, fp);
//パラメータ代入
prm->fs = fmt_samples_per_sec;
prm->bits = fmt_bits_per_sample;
prm->L = data_size / 2;
//音声データ代入
data = (double *)calloc(prm->L,sizeof(double));
for (n = 0; n < prm->L; n++) {
fread(&data_data, 2, 1, fp);
data[n] = (double)data_data / 32768.0;
std::cout << data[n] << std::endl;
}
fclose(fp);
return data;
}
void audio_write(double *data, WAV_PRM *prm, char *filename)
{
//変数宣言
FILE *fp;
int n;
char header_ID[4];
long header_size;
char header_type[4];
char fmt_ID[4];
long fmt_size;
short fmt_format;
short fmt_channel;
long fmt_samples_per_sec;
long fmt_bytes_per_sec;
short fmt_block_size;
short fmt_bits_per_sample;
char data_ID[4];
long data_size;
short data_data;
//ファイルオープン
fp = fopen(filename, "wb");
//ヘッダー書き込み
header_ID[0] = 'R';
header_ID[1] = 'I';
header_ID[2] = 'F';
header_ID[3] = 'F';
header_size = 36 + prm->L * 2;
header_type[0] = 'W';
header_type[1] = 'A';
header_type[2] = 'V';
header_type[3] = 'E';
fwrite(header_ID, 1, 4, fp);
fwrite(&header_size, 4, 1, fp);
fwrite(header_type, 1, 4, fp);
//フォーマット書き込み
fmt_ID[0] = 'f';
fmt_ID[1] = 'm';
fmt_ID[2] = 't';
fmt_ID[3] = ' ';
fmt_size = 16;
fmt_format = 1;
fmt_channel = 1;
fmt_samples_per_sec = prm->fs;
fmt_bytes_per_sec = prm->fs * prm->bits / 8;
fmt_block_size = prm->bits / 8;
fmt_bits_per_sample = prm->bits;
fwrite(fmt_ID, 1, 4, fp);
fwrite(&fmt_size, 4, 1, fp);
fwrite(&fmt_format, 2, 1, fp);
fwrite(&fmt_channel, 2, 1, fp);
fwrite(&fmt_samples_per_sec, 4, 1, fp);
fwrite(&fmt_bytes_per_sec, 4, 1, fp);
fwrite(&fmt_block_size, 2, 1, fp);
fwrite(&fmt_bits_per_sample, 2, 1, fp);
//データ書き込み
data_ID[0] = 'd';
data_ID[1] = 'a';
data_ID[2] = 't';
data_ID[3] = 'a';
data_size = prm->L * 2;
fwrite(data_ID, 1, 4, fp);
fwrite(&data_size, 4, 1, fp);
//音声データ書き込み
fp = fopen(filename, "wb");
for (n = 0; n < prm->L; n++) {
//リミッター
if (data[n] > 1) {
data_data = 32767;
} else if (data[n] < -1) {
data_data = -32767;
} else {
data_data = (short)(data[n] * 32767.0);
}
fwrite(&data_data, 2, 1, fp);
}
fclose(fp);
}
これも変更点は
data = (double *)calloc(prm->L,sizeof(double));
だけです.
実行するときは,以下のようにします.
a.outの後の引数が読み込みたいwavファイルです.
$ g++ wave.cpp
$ ./a.out a.wav
#遊び要素
遊ぶときには,wave.cppの
for (n = 0;n < prm_out.L;n++){
data_out[n] = data_in[n];
}
を使います.これを例えば
「増幅」する場合は,基本波成分に定数倍を掛けたり,
高調波を発生さえて非線形増幅したりすると曲が歪みます.
data_out[n] = 2.0 * data_in[n] + 1.5 * pow(data_in[n],3);
「畳み込み」する場合は,いきなりvectorが登場して申し訳ないですが,
以下の感じにベクタ$h$を生成します.
これによって$l$サンプル分前の情報が減衰して加算されます.
#include <vector>
std::vector<double> h{1.0, 0.9, 0.8, .../*省略*/}
data_out[n] = 0
for(l = 0;l < h.size();l++){
if(n >= l)data_out[n] += h[l] * data_in[n-l];
}
「変調」するときはcosを掛けます.
サンプリングレートは8kHzに気をつけてください.
#include <cmath>
double f = 100;
for (n = 0;n < prm_out.L;n++){
data_out[n] = data_in[n] * std::cos(2.0 * std::acos(-1.0) * f * n / 8000.0);
}
他にもフーリエ変換などもありますが,
また別の記事にしたいと思います.
@DakoMusen