DTMで作曲してC/C++で遊びたい

メリークリスマス！
何も予定のない同類のために1時間くらいでできる遊びを紹介します．
（はじめて書くのであしからず）

#環境
+ MacOS
+ DTM : GrageBand
+ 言語 : C/C++

Macユーザなら何も準備しないでできると思います．
GrageBandはMacOS用のDTMソフトです．
フリーで使用できます．

#概要
1. DTMでwavファイルを生成
2. 信号処理向けにiTunesでwavファイルを変換
3. C/C++でファイル読み込み&書き込み
4. 遊び要素を紹介


#GarageBandでwavファイルを作成
まずはとりあえず開いてみて新規プロジェクトを作成します．
<img width="1008" alt="スクリーンショット 2018-12-24 14.06.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/330510/6383648d-5be9-36d0-bcb6-e5f83325024c.png">

次に，こんな画面が出てくるのでピアノの横の領域を右クリックし，
「空のMIDIリージョンを作成」を選択します．
<img width="1430" alt="スクリーンショット 2018-12-24 14.07.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/330510/1be4a004-5a62-4cce-0af5-05b232edad1a.png">

すると，緑色の物（リージョン）が出てくるので，
ダブルクリックで編集します．
編集では，下の画面に鍵盤が出てくるので適当にCommand+クリックで音符を入力してみてください．
<img width="1429" alt="スクリーンショット 2018-12-24 14.08.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/330510/135baa13-0262-4f55-a528-300229d02d49.png">

左上のプラス（＋）ボタンで楽器を足してみると面白いです．
ドラムも自動演奏があるので良さげなやつを選択してみます．
完成しましたら
「共有」->「曲をディスクに書き出す」
で好きな場所にwavファイルを書き出します．
<img width="1057" alt="スクリーンショット 2018-12-24 14.09.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/330510/32f80b29-3b79-ae15-a581-339a561103be.png">




#iTunesでwavファイルを変換
まず，iTunes->「環境設定」->「読み込み設定」->「設定：カスタム」を選択します．
ここで，サンプリングレートを8.000kHz，量子化bit数を16bit，そしてモノラルに設定します．
次に，再びiTunesで
「ファイル」->optionを押しながら「変換」->「wavに変換」を選択し，
作った曲を保存しているディレクトリを選択します．


#C/C++でファイル読み込み&書き込み
作った曲をC/C++で読み込みます．
以下のサイトを参考（というか丸パクリ）にしました．
[C言語を使ったエフェクター 第一回：wavファイルの入出力](https://hwswsgps.hatenablog.com/entry/2018/08/19/172401)
拡張子はcppですが，C言語でもできます．

```cpp:wave.cpp
#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;
}
```

変更点は以下だけですね．

```cpp
data_out = (double *)calloc(prm_out.L, sizeof(double));
```

ヘッダファイルはこんな感じです．

```cpp:audioio.h
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);
}
```

これも変更点は

```cpp
data = (double *)calloc(prm->L,sizeof(double));
```

だけです．

実行するときは，以下のようにします．
a.outの後の引数が読み込みたいwavファイルです．

```
$ g++ wave.cpp
$ ./a.out a.wav
```

#遊び要素
遊ぶときには，wave.cppの

```cpp
for (n = 0;n < prm_out.L;n++){
    data_out[n] = data_in[n]; 
}
```

を使います．これを例えば
「増幅」する場合は，基本波成分に定数倍を掛けたり，
高調波を発生さえて非線形増幅したりすると曲が歪みます．

```cpp
data_out[n] = 2.0 * data_in[n] + 1.5 * pow(data_in[n],3);
```

「畳み込み」する場合は，いきなりvectorが登場して申し訳ないですが，
以下の感じにベクタ$h$を生成します．
これによって$l$サンプル分前の情報が減衰して加算されます．

```cpp
#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に気をつけてください．

```cpp
#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