4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

#環境

  • MacOS
  • DTM : GrageBand
  • 言語 : C/C++

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

#概要

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

#GarageBandでwavファイルを作成
まずはとりあえず開いてみて新規プロジェクトを作成します.
スクリーンショット 2018-12-24 14.06.49.png

次に,こんな画面が出てくるのでピアノの横の領域を右クリックし,
「空のMIDIリージョンを作成」を選択します.
スクリーンショット 2018-12-24 14.07.29.png

すると,緑色の物(リージョン)が出てくるので,
ダブルクリックで編集します.
編集では,下の画面に鍵盤が出てくるので適当にCommand+クリックで音符を入力してみてください.
スクリーンショット 2018-12-24 14.08.57.png

左上のプラス(+)ボタンで楽器を足してみると面白いです.
ドラムも自動演奏があるので良さげなやつを選択してみます.
完成しましたら
「共有」->「曲をディスクに書き出す」
で好きな場所にwavファイルを書き出します.
スクリーンショット 2018-12-24 14.09.30.png

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

#C/C++でファイル読み込み&書き込み
作った曲をC/C++で読み込みます.
以下のサイトを参考(というか丸パクリ)にしました.
C言語を使ったエフェクター 第一回:wavファイルの入出力
拡張子はcppですが,C言語でもできます.

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;
}

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

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

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

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);
}

これも変更点は

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

4
4
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
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?