Tidalリズムの基本
前回の「Tidalでライブコーディング! – セットアップ編 」に引き続いて、こんどはいよいよ実践編です。
Tidalのリズムの組み方の基本はとてもシンプルです。例えば以下の例。
d1 $sound "bd"
これをcommand-enterで評価すると、バスドラムが一定間隔のリズムを刻みます。この1行の命令にいくつかTidalの命令のポイントが詰まっています。
まず先頭の”d1″の”d”は、Dirtシンセサイザーを演奏するということを意味します。Tidalでは、d1〜d9まで8つのDirtシンセのためのトラックが用意されていて、それぞれ別個にパターンを組んだものをリズムを同期させて演奏することが可能となっています。次の”$sound”でDirtのサンプルを再生する意味になり、最後の”bd”がサンプル名になっています。
Dirtに格納されているサンプルは、Dirtをインストールいたフォルダ以下の”samples”に入っています。ここにフォルダに分かれて整理されていて、例えば”bd”の場合は、”bd”フォルダ内の最初のサンプルファイル”BT0A0A7.wav”が演奏されます。もし、bdフォルダ内の別のファイルを演奏したい場合はコロン「:」で区切って”bd:1, bd:2, bd:3…”と指定していきます。
現在のリズムは、各小節の先頭、1拍目でbdが演奏されています。これを以下のように変更してみましょう。
d1 $sound "bd bd"
すると、1拍目と3拍目に演奏されるようになります。つまり、1行に書いた楽器がその小節の中で均等な間隔で演奏される仕組みです。これと同じリズムを以下のように書くこともできます。
d1 $sound "bd*2"
さらに
d1 $sound "bd*4"
というようにどんどん分割していけるわけです。
もちろん複数の音を鳴らすことも可能です。バスドラムとスネアを交互に鳴らしてみます。
d1 $ sound "bd:1 sn:3"
休符は ~ で表現します。
d1 $ sound "bd:1 sn:3 ~ sn:3"
演奏している音を止めるときはsilenceを指定します。
d1 silence
リズムを分割、ネスト
もう少し複雑な例をみてみましょう。
d1 $ sound "bd:3 [sn:1*2] ~ bd:3"
ここで新たに大括弧 [] が出てきています。これは、分割されたリズムの中でさらに分割することを意味します。この例では2拍目でスネアを2回演奏することを意味します。さらにこれを入れ子状に複雑に組み合せていくことも可能です。
d1 $ sound "bd [[sn:1*2] bd] [sn:1 [bd*2]]"
音を混ぜる
“stack”という命令を使うと、1つのトラックの中に複数のリズムを重ねていくことが可能です。
stack[patten1, pattern2, pattern3....]
というように書いていきます。実際には、例えば以下のようになります。コンマで区切ってどんどんパターンを追加してMixしていけます。
d1 $ stack[
sound "bd:3 [sn:1*2] ~ bd:3",
sound "bass ~ bass:3 ~",
sound "[~ cp]*4"
]
トラックごとに、音量や定位(パン)を指定することも可能です。|+| という記号で繋いで指定していきます。最後のトラックはランダムにパンを指定しています。
d1 $
stack[
sound "bd:3 [sn:1*2] ~ bd:3" |+| gain "1.0",
sound "bass ~ bass:3 ~" |+| gain "1.2",
sound "[~ cp]*4"|+| gain "0.9" |+| pan rand
]
モジューレーション
Tidalではパート全体に対して、様々な変化を加える関数が多数用意されています。これらを組み合わせるととても複雑なパターンが生まれます。例えば、jux という関数は左右で音をずらしたり反転させることで、複雑な定位を生成します。
d1 $
jux (rev) $
stack[
sound "bd:3 [sn:1*2] ~ bd:3" |+| gain "1.0",
sound "bass ~ bass:3 ~" |+| gain "1.2",
sound "[~ cp]*4"|+| gain "0.8" |+| pan rand
]
gap や chop を使うと、音が細かくぶつ切りになります。
d1 $
jux (rev) $
chop 16 $
stack[
sound "bd:3 [sn:1*2] ~ bd:3" |+| gain "1.0",
sound "bass ~ bass:3 ~" |+| gain "1.2",
sound "[~ cp]*4"|+| gain "0.8" |+| pan rand
]
毎回モジュレーションをかけるのではなく、特定のタイミングのみで適用することも可能です。たとえば、毎回4小節ごとにchopをかけ、6小節ごとにgapをかけてみましょう。everyという命令を使います。
d1 $
jux (rev) $
every 4 (chop 16) $
every 6 (gap 16) $
stack[
sound "bd:3 [sn:1*2] ~ bd:3" |+| gain "1.0",
sound "bass ~ bass:3 ~" |+| gain "1.2",
sound "[~ cp]*4"|+| gain "0.8" |+| pan rand
]
さらに、juxのずらし方をすこしずつ変化させてみます。iterという命令をつかうと、数が小節ごとに増加していき指定した数でサイクルします。例えば、iter 4と指定すると、0, 1, 2, 3, 0, 1, 2, 3… というパターンが生まれます。
d1 $
jux (iter 4) $
every 4 (chop 16) $
every 6 (gap 16) $
stack[
sound "bd:3 [sn:1*2] ~ bd:3" |+| gain "1.0",
sound "bass ~ bass:3 ~" |+| gain "1.2",
sound "[~ cp]*4"|+| gain "0.8" |+| pan rand
]
基本から徐々に積み上げていき、最終的にはとても複雑なリズムが生成されました!
次回につづく! (予定)
注意!! : TidalCyclesがver 0.8にアップデートされ、インストールが大幅に簡略化されました。新しいバージョンでのインストール方法はこちらを参照してください。
Tidalとは?
Tidalは、Alex McLean氏によって開発されたHaskellを拡張したライブコーディング(Live Coding)環境です。
そもそもライブコーディングとは何かというと、ざっくりと言うと、プログラムをリアルタイムに実行しながらコーディングする行為自体をパフォーマンスするジャンルです。主に、音楽や映像を生成することが多いですが、Webや詩などその適用範囲は拡がっています。
Tidalは、こうしたライブコーディングの環境の中でも、リズムパターンの生成に特化した環境となっています。シンプルな文法で複雑なリズムパターンをリアルタイムに生成し変奏していくことが可能となっています。どんなことができるのか理解するには、まずはデモ映像を観てみるのが早いかもしれません。
VIDEO
Tidalは、Mac OS X、Linux、Windowsで動きます。
Tidalのインストール
ここでは、OS Xの環境に絞って説明します。その他のOSのインストール方法は、オフィシャルのドキュメント を参照してください。
OS XのインストールではHomebrew を使用します。Homebrewが入っていない場合はまずインストールします。(既にHomebrewを使用している場合は必要ありません)。
% /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Tidalは、大きく分けて2つのパートから構成されています。1つはリズムを生成するTidal本体。これはHaskellのパッケージとしてインストールします。2つ目は“Dirt”というサンプラーです。様々なリズムやシンセサイザー、効果音などがプリセットされていてHaskellから呼び出されてサウンドを生成します。ここでは触れませんが、応用として、TidalはDirtを使わずに外部のMIDI音源やソフトシンセ、さらには、Max/MSPやSuperColliderなどの言語とOSCで連携することも可能です。
Dirtをインストールしていきます。まずは必要なライブラリーをインストール。
% brew install liblo libsndfile libsamplerate
その後、TidalとDirtを連携するため、Jack をインストールします。
% brew install jack
Dirtの本体をGithubからダウンロードしてインストールします。まずインストールしたいディレクトリに移動してから以下のコマンドでインストールします。
% git clone --recursive https://github.com/tidalcycles/Dirt.git
% cd ~/Dirt
% make clean; make
次にHaskellの実行環境を構築します。TidalではGHC というHaskellのコンパイラとインタプリタを利用します。これもHomebrewから。
% brew install ghc
次にGHCのパッケージ管理システムであるcabalを利用して、Tidalの本体をインストールします。
% cabal update
% cabal install cabal-install
% cabal install tidal
これでインストールの完了です!
エディターのインストール
しかし、まだこれだけではTidalを使うことはできません。Tidalでは既存のテキストエディターからHaskellのインタプリタを呼び出して使用する仕組みになっています。オフィシャルに対応しているテキストエディターは、現状では、EmacsとAtomです。(Haskellを動作させることができるエディターであれば他でも使用できるものがあるかもしれません。)
ここでは、導入が簡単なAtomを使用する方法を紹介します。
Atomエディターを持っていない場合は、下記からダウンロードします。
Atomを起動して、settings > install の検索欄で「Tidal」を検索します。おそらくトップにTidalのプラグインが表示されるので、インストールします。
実行してみる!
では、いよいよ実行してみましょう!
まず、Dirtをインストールしたディレクトリに移動して、Jackのデーモンを起動します。
% jackd -d coreaudio &
次にDirtを起動します。
% ./dirt &
これで、準備完了です。次にAtomで新規ファイルを開き「xxx.tidal」というファイル名で保存します。するとファイルタイプが「Tidal」になるはずです。
この状態で、メニューバーから Package > Tidal > Boot Tidal を選択します。
試しに以下のパターンを入力してみます。
d1 $ sound “909 ~ bass bass:2”
リズムパターンが再生されるはず!? (実践編につづく予定)
ディープラーニングとofxMSATensorFlow
最近、何かと話題のディープラーニング(Deep Learning / 深層学習 / 機械学習)。TensorFlow、Chainer、Caffeなど様々な便利なライブラリもフリーで入手可能で、ずいぶんと敷居が下がってきた印象だ。
そんな中、イケメン天才アーティストのMemo Akten 氏によって、openFrameworksでTensorFlowが使用できる、ofxMSATensorFlow というアドオンが公開された。早速、OS Xで動かしてみたので、設定方法をシェア。
※ 動作を確認したOSは、OS X 10.11 (El Capitan) のみです。
インストール
まずは、Githubからアドオンの本体をダウンロード。従来のアドオンと同様に、openFrameworksのルート/addons/ofxMSATensorFlow に配置する。
https://github.com/memo/ofxMSATensorFlow
本来は、C++でTensorFlowを動かすためにいろいろ設定が必要なのだが、様々なプラットフォームごとにTensorFlowが環境構築されたシェアードライブラリが配布されている(親切!!)。ありがたく、これを使用する。OS Xの場合は、下記のリンクから ofxMSATensorFlow_lib_OSX_2016_02_21_v0.7_CPU.zip をダウンロードして解凍する。
https://github.com/memo/ofxMSATensorFlow/releases
中には libtensorflow_cc.so というライブラリが入っている。ofxMSATensorFlowではこれをホームディレクトリ直下のlibフォルダに配置して使用するように設定されている。以下のように設定
% cd ~
% mkdir lib
% cp Downloads/osx/libtensorflow_cc.so lib/.
Xcodeでサンプルを動かしてみる
MemoさんはQTCreatorで動かしているようなのだが、できることなら手に馴染んでいるXcodeで動かしたい。以下のように設定して無事サンプルを動かすことができた。まずは、基本サンプルの example-basic で試してみる。
ProjectGeneratorで、example-basicフォルダを選択してインポートする。自動的にaddonにはofxMSATensorFlowが入っているはずなので、そのままプロジェクトをUpdateする。(少し時間がかかる)
TensorFlowのライブラリは既にシェアドライブラリとして持っているので、addonフォルダ内のものは必要ない。addons/ofxMSATensorFlow/libs/tensorflow をフォルダごと参照を削除。
また、このサンプルは、ofApp.hやofApp.cpp、main.cppを使用していない。srcフォルダ内で赤くなっているファイルを削除する。
ここに、先程のライブラリを追加する。libsフォルダを右クリックして、”Add Files to …” を選択。ファイルダイアログから、libフォルダ内の libtensorflow_cc.so を選択する。結果以下のようになるはず。
あとは、Buildするだけ。こんな感じのサンプルが実行される(はず)!!
MNISTを試してみる
MNIST(Mixed National Institute of Standards and Technology) とは、28x28pxの60000枚の学習サンプル、10000枚のテストサンプルからなる手書き数字画像データベースで、機械学習の世界ではこれを使って手書きの数字を認識させるのが “Hello World” 的な定番サンプルとなっている。ofxMSATensorFlowでもMNISTのサンプル example-mnist があるので、試してみる。
ただし、まず学習データが必要。こちらは別途ダウンロードする
https://github.com/memo/ofxMSATensorFlow/releases/download/v0.0/example-inception3-data.tar.gz
解凍したフォルダ model-deep と model-deep の2つを、example-mnist/bin/data以下に配置する。
あとは、先程の example-basic と同じ手順でビルドできるはず。マウスで描いた数字を瞬時に判別する。ディープラーニングすごい!!
openFrameworksをワークショップや授業などで教えていると、ポインタの概念にさしかかった時に突然抽象的になってしまい、挫折する人が出てきてしまう。自分のためのメモも兼ねて、なぜポインタを使うのか、抽象的な解説ではなくopenFrameworksでの分かりやすい実例をベースにして考えてみた。
ダメな例
例えば、以下のように画像をランダムな場所に表示するShowImageという簡単なクラスをつくったとする。
ShowImage.hpp
#pragma once
#include "ofMain.h"
class ShowImage {
public:
void draw();
ofImage image; //表示する画像
float x = ofRandom(ofGetWidth());
float y = ofRandom(ofGetHeight());
float size = 40;
};
ShowImage.cpp
#include "ShowImage.hpp"
void ShowImage::draw(){
//ランダムな場所に指定したサイズで画像を表示
image.draw(x, y, size, size);
}
これを、ofAppから繰り返し生成して配列に格納し、表示していく。画像ファイルの読み込みはofAppで行い、ShowImageクラスに読み込んだ画像を代入していく。
ofApp.h
#pragma once
#include "ofMain.h"
#include "ShowImage.hpp"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
ofImage srcImage; // ソース画像
vector showImages; //ShowImageの配列
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup(){
ofBackground(0);
// 画像ファイルを読み込み
srcImage.load("sorceImage.jpg");
}
void ofApp::update(){
ShowImage si; // ShowImageをインスタンス化
si.image = srcImage; //画像を代入
showImages.push_back(si); //配列に追加
}
void ofApp::draw(){
ofSetColor(255);
// ShowImageの配列の数だけ表示
for (int i = 0; i < showImages.size(); i++) {
showImages[i].draw();
}
}
これを実行するとどうなるか。画像のファイルサイズにもよるが、みるみるうちにメモリを消費していき、FPSも極端に落ちていく。実行しているマシンのメモリサイズにもよるが、ずっと起動していると最終的にはアプリごと落ちる。全然ダメダメなプログラムだ。
ポインタで指し示す
では、どのようにすればパフォーマンスが改善するのか? 「ダメな例」の最大の問題は、ShowImageにイメージを代入しているので、ShowImageクラスをインスタンス化して画像を代入する毎に画像ファイルの容量だけメモリを消費している。代入しているということは、つまりは画像のデータをそのままコピーしている状態になっている。
si.image = srcImage; //画像を代入
ここで、ポインタを活用する。ポインタのざっくりしたイメージは、データの実態ではなく、その参照先を「指し示して(Point)」いる機能だ。例えば、先程の例だと巨大な画像データの実態そのものをコピーしてくるのではなく、あそこに画像のデータがあるよと「指し示す」ことができる。
si.image = &srcImage; //画像の参照先を指定
ShowImageから画像を描画する際には、imageは実態ではなくその参照先を指し示すポインタ *image になったので、以下のようにアロー演算子 “->” を使用するように変更する。このアロー演算子の形が、まさに指し示す矢印の形になっている。
image->draw(x, y, w, h);
ざっくりとしたイメージを図示するとこんな感じか?
改良したプログラム
「ダメな例」を修正して、画像像を直接コピーするのではなく、ポインタとして指し示すように変更したプログラムは以下のようになる。
ShowImage.hpp
#pragma once
#include "ofMain.h"
class ShowImage {
public:
void draw();
ofImage *image; //イメージへのポインタ
float x = ofRandom(ofGetWidth());
float y = ofRandom(ofGetHeight());
float size = 40;
};
ShowImage.cpp
#include "ShowImage.hpp"
void ShowImage::draw(){
//ポインタが参照している画像を描画
image->draw(x, y, size, size);
}
ofApp.h
#pragma once
#include "ofMain.h"
#include "ShowImage.hpp"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
ofImage srcImage; //ソース画像
vector showImages; //ShowImageの配列
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup(){
ofBackground(0);
// 画像ファイルを読み込み
srcImage.load("srcImage.jpg");
}
void ofApp::update(){
ShowImage si; // ShowImageをインスタンス化
si.image = &srcImage; //画像への参照先を指定
showImages.push_back(si); //配列に追加
}
void ofApp::draw(){
ofSetColor(255);
// ShowImageの配列の数だけ表示
for (int i = 0; i < showImages.size(); i++) {
showImages[i].draw();
}
}
これで、ずっと起動していてもイメージ1個だけのメモリしか消費せず、ShowImageからはそのイメージのデータを参照しているだけなので、安定して稼動するプログラムになるはず! (とはいえ、永久にオブジェクトを複製し続けるので、ずっと起動してると落ちるかも…)
追記: 参照渡し
何人かの方から、この例ではポインタではなく参照をつかったほうが良いのではないかという指摘がありました。どちらが適切なのか、ポインタと参照のどちらをまず覚えるべきなのか、いろいろ難しい議論ですが、とりあえず、ポインタ渡しではなく参照渡しをして描画する例を掲載します。(間違いあればご指摘を)。
ShowImage.hpp
#pragma once
#include "ofMain.h"
class ShowImage {
public:
// draw関数は画像データの参照を受けとる
void draw(ofImage &image);
float x = ofRandom(ofGetWidth());
float y = ofRandom(ofGetHeight());
float size = 40;
};
ShowImage.cpp
#include "ShowImage.hpp"
void ShowImage::draw(ofImage &image){
//参照で渡された画像を描画
image.draw(x, y, size, size);
}
ofApp.h
#pragma once
#include "ofMain.h"
#include "ShowImage.hpp"
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
ofImage srcImage; //ソース画像
vector showImages; //ソース画像を格納する配列
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup(){
ofBackground(0);
// 画像ファイルを読み込み
srcImage.load("srcImage.jpg");
}
void ofApp::update(){
ShowImage si; // ShowImageをインスタンス化
showImages.push_back(si); //配列に追加
}
void ofApp::draw(){
ofSetColor(255);
// ShowImageの配列の数だけ表示
for (int i = 0; i < showImages.size(); i++) {
//描画の際に画像の参照渡しをする
showImages[i].draw(srcImage);
}
}
たしかに、参照渡しを使うほうが、わかりやすいような気もしてきた… C++は奥深い…