2018年6月20日水曜日

SMPTEタイムコードのデコード(オフライン)

SMPTE Time Codeというレガシーな仕組みをご存知ですか?

映像の再生位置を示す「タイムコード("時間:分:秒:フレーム数"で表現される時間情報)」が,「オーディオ信号」として伝送されるという方式のものです.

SMPTEタイムコードのオーディオデータを生成してダウンロードできるサイトです.まずは,このサイトでオーディオデータを生成して聞いてみましょう.(※音量注意です.聴感上,非常に音が大きく感じられますし,結構不快な音かもしれません.

http://elteesee.pehrhovey.net/


このデータは後で使いますので,Bit-depthを"16-bit signed int"にするのをお忘れなく.

どうでしょうか?「プルルルルルルルルル・・・」という音が聞こえると思います.この音が,実はタイムコードを表しているのです.

例えば,iPhoneでSMPTEタイムコードを再生して,別のiPhoneのマイクロホンでそれを拾い,デコードしている例があります.

http://blog.livedoor.jp/cpiblog00465/archives/52419598.html

このように,音を出力し,それを拾うだけで時間情報の同期ができてしまうのです.

この技術は,現在でも音響だけでなく,照明や映像等が複雑に絡み合うシステムを構築する際によく使われます.例えば,音響は,Pro ToolsのようなDAWでマルチトラック再生し,同時にサウンドカードの別系統からSMPTEタイムコードを再生し出力,それを映像機器や照明機器に入力し,時間を同期して映像の再生や照明演出が進んでいくシステムを構築できます.

ディズニーランドではパレードやアトラクションでの演出の同期に,SMPTEタイムコードによる同期のテクニックが使われています.

https://ggsoku.com/2012/03/projection-mapping/

SMPTEタイムコードの音声信号の波形を拡大してみると,このようになっています.


矩形波のような,正弦波のような波形になっています.本当は,もっとしっかり矩形波になっている筈なんですが,上記ページから入手したものの波形はこんな感じになっています.しかし,実用上問題ありません.

この波形を見ると,幅の長い波と短い波があることがわかります.SMPTEタイムコードでは,幅の短い上下の変化2回で "1" ,幅の長い上下の変化1回で "0" とみなします.


こうして音声波形から0と1を読み取っていくと,二進数列ができます.この二進数列を以下のように読んでいきます.


このデコード処理をMATLABで実装してみたいと思います.(MATLABだと音声ファイルの扱いが簡単なので)

波形のステップ幅の検出は,ステップ時に信号の値が"0"を通過することに着目し,0通過時から次の0通過時までの幅を計測します.0通過は,今回のような矩形波の場合,現在のサンプルと.1つ前のサンプルとの積を求め,その符号を調べ,マイナスになれば0を通過したことがわかります.(プラス x プラスはプラスの値,マイナス x マイナスもプラスの値になりますが,プラス x マイナス,或いはマイナス x プラスはマイナスの値になります.)

Sync wordを基準にして,それが出現したら過去にバッファしたビット列の中から,必要な部分を参照し,タイムコードをデコードしていきます.

ゆくゆくリアルタイムで実装することを見越して,for文の中で1サンプルずつ参照しながらデコードを行う設計になっています.

clear;
s=audioread('smpte.wav');
del=0;%1サンプル前のオーディオデータ
isshort=0;%短いステップ内に入ったことを示すフラグ
count=0;%ステップ内のサンプル数を計測
bitcount=1;%ビットを記録する位置
bitbuf=zeros(16,1);%sync word検出用バッファ
synccode=[1;0;1;1;1;1;1;1;1;1;1;1;1;1;0;0];%sync word
timecode=zeros(1,16*4*2);%検出したビットのバッファ(いつまでもsync wordが検出できない場合に備えてバッファを必要数の2倍にしておく)
%行ベクトルにしているのは、bin2decの引数にする2進数の文字列をint2strによって生成するため
hour=0;
min=0;
sec=0;
frame=0;
for k=1:length(s)
    sgn=sign(s(k)*del);%現在のオーディオデータと1サンプル前のオーディオデータの積の符号
    if (sgn<0) %ステップ内に入った(信号が '0' を通過した)
        if isshort==0 %短いステップ内ではない
            for n=16:-1:2
                bitbuf(n)=bitbuf(n-1);%過去の15ビットをバッファリング
            end
            if count<11 %短いステップであれば現在のビットは '1'(閾値は適当)
                isshort=1;%次にもう一回短いステップがあるはずなのでフラグを '1' にしておく
                bitbuf(1)=1;
            else %ステップが長ければ現在のビットは '0'
                bitbuf(1)=0;
            end
            timecode(bitcount)=bitbuf(1);%ビットのバッファに現在のビットを格納
            bitcount=bitcount+1;%次回の格納先を1ずらす
            if bitcount>16*4*2 %次回の格納先がバッファサイズを超えたら一旦格納先をリセット
                bitcount=1;
            end
        else %2回目の短いステップ検知時は何もしない
            isshort=0;
        end
        if isequal(bitbuf,synccode)==1 %syncwordを検知したら、timecodeにはSMPTE規格のビット列が格納されている筈
            %各値を10進数に変換
            frame=bin2dec(int2str(timecode(4:-1:1)))+10*bin2dec(int2str(timecode(10:-1:9)));
            sec=bin2dec(int2str(timecode(20:-1:17)))+10*bin2dec(int2str(timecode(27:-1:25)));
            min=bin2dec(int2str(timecode(36:-1:33)))+10*bin2dec(int2str(timecode(43:-1:41)));
            hour=bin2dec(int2str(timecode(52:-1:49)))+10*bin2dec(int2str(timecode(58:-1:57)));
            sprintf('%d:%d:%d:%d',hour,min,sec,frame)
            bitcount=1;%バッファの格納先をリセット
        end
        count=0;%ステップ数をリセット
    end
    count=count+1;
    del=s(k);%現在のオーディオデータを保持
end

2018年6月5日火曜日

OpenMusicの基礎 番外編 〜ワンスモード〜

※この記事は「はじめての<脱>音楽 やさしい現代音楽の作曲法」における私執筆の「OpenMusic」の基礎及び,前回までのOpenMusicの基礎 番外編シリーズをお読みください.

前回はomloopの非常に基礎的な考え方をご紹介しました.少し難しい(と私は感じるの)ですが,慣れてくるとだんだん意図通りの繰り返し制御ができるようになります.ここからもう少しアルゴリズム作曲に活かせるようなomloopの使い方を紹介したいのですが,その前にご紹介しておきたい話題があります.これを知らないと,複雑なプログラムを意図通りに動作させることができません.

まず,いつも引き合いに出しているMaxで例題となる処理を作ってみます.説明のためだけに作る,全く必然性のない処理ですが,こんな処理です.

①乱数 r(0~10) を発生させる
②乱数 r の値を表示する
③r+r を計算する
④r+rの値を表示する

※「乱数 r 」というのは,勿論便宜的に設定した変数であって,MaxやOpenMusicのようなビジュアルプログラミング環境では,実際にはこのような変数を設定して代入することはしません.

乱数 r の値がいくつであっても,必ずrの2倍の値が表示されるはずです.

こんなパッチになります.Maxのrandomオブジェクトは,アーギュメントの値-1が乱数の最大値となることに注意してください.


このパッチの処理手順は図のようになります.順番が左右あちこちに飛んでいて分かりづらいですが,順番をしっかり確認しましょう.


ここで押さえておきたいのは,一回のbangで①〜⑥の全ての処理が一気に行われるということです.randomオブジェクトは,第1インレットにbangが入力されるまで乱数を新たに発生させませんから,このパッチの+オブジェクトで,まさに r+r の計算が行われることになります.

OpenMusicでも同じように作ってみましょう.これで良いでしょうか?Maxの時のように,1回のEvalBoxごとに1回乱数が発生して,その乱数の2倍の値が出力されるでしょうか?

OM+オブジェクトをEval Boxしてみましょう.


"OM >"となっている行がprintオブジェクトに入力された値,"OM =>"となっている行がOM+オブジェクトをEval Boxした処理結果です.ということは,この処理は,乱数が2回発生して,その和を求めているということになります.
これは,OpenMusicの制御構造を思い出すと,納得できると思います.OpenMusicはMaxとは逆に下から上のオブジェクトへデータを要求していく制御構造になっています.これを踏まえると,このパッチはこのような処理手順で処理を実行しています.図に書き込むと行ったり来たり,非常にややこしくなるので箇条書きにしますが,パッチと見比べながら処理手順を追ってみてください.

① OM+オブジェクトがEval Boxされる(ユーザ操作)
② OM+オブジェクトの第1インレットにデータが未到着なので,上のオブジェクトにデータを要求.
③ PRINTオブジェクトは更に上のオブジェクトにデータを要求
④ OM-RANDOMオブジェクトはデータを要求されたので,乱数を生成してアウトレットから出力
⑤ PRINTオブジェクトは乱数を表示してアウトレットからそのまま出力
⑥ 今回はあくまでOM+オブジェクトの第1インレットからの要求なので,OM+オブジェクトの第1インレットにのみ出力する
⑦ OM+オブジェクトの第2インレットにデータが未到着なので,上のオブジェクトにデータを要求.
⑧ PRINTオブジェクトは更に上のオブジェクトにデータを要求
⑨ OM-RANDOMオブジェクトはデータを要求されたので,乱数を生成してアウトレットから出力
(10) PRINTオブジェクトは乱数を表示してアウトレットからそのまま出力
(12) 今回はあくまでOM+オブジェクトの第2インレットからの要求なので,OM+オブジェクトの第2インレットにのみ出力する
(13) 両方のインレットにデータが到着したので和を計算して表示

PRINTオブジェクトのアウトレットから出ている線の数だけ同じ処理が繰り返されているのです.データが到着していないインレットの数だけ要求が発生するので当然です.そしてそのたびに,OM-RANDOMオブジェクトは乱数を生成しているのです.
これは,1つの要求が終わって,次の要求が始まるまでの間に,それまでのオブジェクトの状況が保持されていないために起こります.
だったら保持するようにしようというのが「ワンスモード」です.

OM-RANDOMオブジェクトを選択してキーボードの"1"キーを押すと,OM-RANDOMオブジェクトの左上に"1"というアイコンがオーバーラップ表示されました.これは,OM-RANDOMオブジェクトがワンスモードに入ったことを意味します.


ワンスモードになったオブジェクトは,1回のEval Boxの間,一回要求に答えたら,処理結果を保持しておき,その後新たな要求が来てもその処理結果をアウトレットから出力するだけになります.このパッチをEval Boxするとこうなります.乱数による偶然ではなく,ちゃんと意図通りの動作をしていることを示すために何度かEval Boxしてみます.

OM-RANDOMオブジェクトが2回とも同じ値を出力していて,r+r が計算できていることがわかります.この時の処理手順は以下のようになります.

① OM+オブジェクトがEval Boxされる(ユーザ操作)
② OM+オブジェクトの第1インレットにデータが未到着なので,上のオブジェクトにデータを要求.
③ PRINTオブジェクトは更に上のオブジェクトにデータを要求
④ OM-RANDOMオブジェクトはデータを要求されたので,乱数を生成してアウトレットから出力.ワンスモードなのでこの値は最後まで保持しておく.
⑤ PRINTオブジェクトは乱数を表示してアウトレットからそのまま出力
⑥ 今回はあくまでOM+オブジェクトの第1インレットからの要求なので,OM+オブジェクトの第1インレットにのみ出力する
⑦ OM+オブジェクトの第2インレットにデータが未到着なので,上のオブジェクトにデータを要求.
⑧ PRINTオブジェクトは更に上のオブジェクトにデータを要求
⑨ OM-RANDOMオブジェクトはデータを要求されたが,ワンスモードなので乱数を生成せず, ④で生成した値をアウトレットから出力
(10) PRINTオブジェクトは乱数を表示してアウトレットからそのまま出力
(12) 今回はあくまでOM+オブジェクトの第2インレットからの要求なので,OM+オブジェクトの第2インレットにのみ出力する
(13) 両方のインレットにデータが到着したので和を計算して表示

これで,r+r の値を計算することはできたのですが,OM-RANDOMオブジェクトが2回動作しているのは変わりません.そこで,PRINTオブジェクトをワンスモードにすることで,2回目の要求でOM-RANDOMオブジェクトの動作を省略することができます.

この時の手順は以下のようになります.

① OM+オブジェクトがEval Boxされる(ユーザ操作)
② OM+オブジェクトの第1インレットにデータが未到着なので,上のオブジェクトにデータを要求.
③ PRINTオブジェクトは更に上のオブジェクトにデータを要求
④ OM-RANDOMオブジェクトはデータを要求されたので,乱数を生成してアウトレットから出力.
⑤ PRINTオブジェクトは乱数を表示してアウトレットからそのまま出力.ワンスモードなのでこの値は最後まで保持しておく.
⑥ 今回はあくまでOM+オブジェクトの第1インレットからの要求なので,OM+オブジェクトの第1インレットにのみ出力する
⑦ OM+オブジェクトの第2インレットにデータが未到着なので,上のオブジェクトにデータを要求.
⑧ PRINTオブジェクトは,ワンスモードなのでOM-RANDOMオブジェクトに値を要求せず, ⑤で生成した値をアウトレットから出力
⑨ PRINTオブジェクトは乱数を表示してアウトレットからそのまま出力
(10) 今回はあくまでOM+オブジェクトの第2インレットからの要求なので,OM+オブジェクトの第2インレットにのみ出力する
(11) 両方のインレットにデータが到着したので和を計算して表示

いかがでしょうか?複雑なプログラムを作ろうとすると,ワンスモードを使いこなす必要があります.特にomloopを使う時は,非常に重要なテクニックになります.

2018年5月24日木曜日

OpenMusicの基礎 番外編 〜omloopの基礎〜

※この記事は「はじめての<脱>音楽 やさしい現代音楽の作曲法」における私執筆の「OpenMusic」の基礎及び,前回のOpenMusicの基礎 番外編をお読みください.


はじめての<脱>音楽 やさしい現代音楽の作曲法」,すでに重版になったようで,ご好評いただいているようです.私が担当したのはほんの一部だけですが,非常に嬉しく思います.

OpenMusicの特徴は,ソースコードを書かなくても良いグラフィカル・プログラミングですが,グラフィカル・プログラミングは見た目の難しさが少なく,直感的にプログラムを作成しても何らかの結果を得やすいという利点がありますが,一方で,プログラムの制御構造が,グラフィカル・プログラミングという名前とは裏腹に,見ただけでは分からないという欠点もあります.(その制御構造の分かりづらさは前回の記事でお分かりいただけたと思います.)なので,込み入ったプログラムを書こうとすると途端に難しくなってきます.今日ご紹介する繰り返し処理もその一つ.パッチの見た目からこの制御構造を理解するのはほぼ不可能だと思います.

プログラミングでよく使うテクニックに,繰り返し処理があります.その名の通り,同じ処理を指定した回数繰り返す処理です.例えば,10000個の整数全部に100を足す等の処理です.正攻法でいけば,10000回同じ処理を書かないといけないように思えますが,繰り返し処理を使えば,一回だけ処理を書いておいて,それを10000回繰り返させる,といった書き方が可能です.この処理はプログラミングの常識的なテクニックなので,何らかのプログラミング言語を学んだことがある人ならご存知のはずです.

OpenMusicでも繰り返し処理を記述することができます.繰り返し処理を行うオブジェクトが"omloop"オブジェクトです.

パッチ内でomloopオブジェクトを出して,ダブルクリックすると,画像の右側のようなウィンドウが出てきます.このウィンドウが,omloopの中身のパッチを作成するウィンドウで,このウィンドウで作った処理が,繰り返し処理されることになります.


"eachTime"と"finally"というオブジェクトが既にあります.この2つのオブジェクトは繰り返し処理に必須のオブジェクトなので,最初から出ています.

omloopの処理がどのように行われるか知るために,このようなパッチを作りました.Eval Boxすると,OM Listenerウィンドウにこのように表示されます."forloop"オブジェクトは,所謂for文を実装するオブジェクトで,第1インレットの値から第2インレットの値まで内部変数の値を1ずつ増やしながら繰り返しを行うオブジェクトです.


前回の記事で,OpenMusicは,パッチの下から上のオブジェクトにデータの要求を行うという制御構造を説明しました.その考え方でこのパッチを見ると,以下のようなことが起きています.重要なのは,eachTimeの使い方です.eachTimeは,ループの各回の開始時点でデータの要求を行い,データが到着すると,ループが終了するまで,次のデータ要求を繰り返します.

① forloopオブジェクトの内部変数は0(第1インレットで設定したので)
② eachTimeオブジェクトがprintオブジェクトに値を要求
③ printオブジェクトはデータを持っていないのでさらに上のテキストボックスにデータを要求
④ テキストボックスは"hello"を出力
⑤ printオブジェクトはOM Listenerウィンドウに"hello"を表示し,アウトレットからも出力
⑥ eachTimeオブジェクトに"hello"が入力されたので,この回は終了.forloopオブジェクトの内部変数が1増える
⑦ ②〜⑥をforloopオブジェクトの内部変数が9になるまで繰り返す
⑧ forloopオブジェクトの内部変数が9になったのでループは終了.finallyオブジェクトがデータ要求を行い,データを受け取るとomloopオブジェクトの処理は終結.(この時,finallyオブジェクトに入力された値が,omloopオブジェクトのアウトレットから出力される

このようになります.この処理の結果が,上の画像のOM Listenerウィンドウの表示です.尚,最後,"finish"が2回表示されているのは,1個目は,omloop内のprintオブジェクトに入力された時点の"finish",2個目は,omloopをEval Boxした結果としての"finish"です.

また,forloopオブジェクトの内部変数は,forloopオブジェクトのアウトレットから整数で出力されます.



値の範囲を変えると,この通り.


また,omloopウィンドウ左上にある,緑色の矢印ボタンをクリックすると,インレットオブジェクトが出てきます.そして,パッチウィンドウのomloopオブジェクトには,インレットが増えています.この操作を繰り返すと,インレットをいくつでも増やすことができます.このようにして,omloopオブジェクトの外からomloopオブジェクトに入力した値を,omloopオブジェクトの処理で使うことができるのです.



この他によく使うループの方法として,listloopがあります.listloopオブジェクトは,インレットに入力されたリストの要素をループ毎に順番に1つずつ出力していくオブジェクトです.


長くなりそうなので今回はここまで.

2018年5月17日木曜日

コンテンポラリーミュージックユニットAffine始めました.

Twitterではちょこちょこ言っていますが,今年の初めから,これまでずっと活動してきたmacaroomに加え,「Affine(アファイン)」というユニットを始めました.macaroomでは主にポップな歌モノをやっていますが,こちらはコンテンポラリーミュージック.完全なる実験音楽です.
macaroomが去年行った"Cage Out"という一連の企画で出会ったSaKi 汐音さん(名前暫定)という作曲家(?)にお誘いいただきまして始めることになりました.始めはこんなにがっつりやるつもりではなかったのですが,以前から興味のあった実験音楽や,本格的なライブ・エレクトロニクスの実践ができると思い,また,相方の常人離れした感性に惹かれ,何か面白いことができるのではないかと思い,今は,このユニットで私のできるだけのことをやってみようという気持ちになっています.

そんなAffineの頭のおかしい相方が,Affineのことを記録するブログを始めました.

http://affinedrawing.blogspot.jp/

ブログを始める際,「私が手間をとってブログをやるのだからお前も同じだけの手間を取れ」という非常に日本人的な発想丸出しの要望が来たので(頭おかしいくせにこういう時だけ日本人根性丸出し),このブログにたまにAffineのことも書いていきます.(複数ブログをやるのはかったるい)

ユニット名「Affine」は,私が提案しました.ユニット名について,この一案だけ出て,他に対案が全く出なかった(主宰者からも出ないという有様)ので,無投票当選となりました.

Affine.数学や工学に詳しい人であれば馴染み深い言葉です.画像処理の基本・アフィン変換のアフィンです.しかし,古い教科書は参考書では,このAffineをアファインと読む例もあり,我々はその読み方を採用することにしました.それについて,深い理由はなく,2音節より3音節の方が良いかなと思っただけです.

Affineとは,非常に基礎的なベクトルの演算だけで幾何学を構築した空間のことです.
多くの場合,幾何学というと,あの直線の長さがどうこう,ここの角度が云々と,その空間にあるものの「形」をベースにして議論がされます.一方Affine幾何学の場合,空間にあるものの形を考えることなく,ベクトルとその操作だけで幾何学の基本的な性質を構築します.逆に言うと,そのような抽象的な幾何学で明らかになった諸性質を実際的な空間で議論し直すのが,いわゆる通常の幾何学と言えるかもしれません.(この理解は数弱の私の理解であり,非常に信憑性が薄いです

最初は,"Orthogonal" というユニット名を考えました.Orthogonalとは直交という意味です.直交とは,ある直線と別の直線が90度に交わっていること.

2つのベクトルが直交している時,その内積が0になることは数学の常識です.ある量とある量が独立して変化する時,それらの量は直交していると言えます.例えば,バスに乗っている時の心拍数と,その人の移動する速さは直交しています.バスが早く走ろうがゆっくり走ろうが,その人は座っているだけであり,運動による心拍数の上下がないからです.しかし,歩きまたは走って移動している時の心拍数と,その人の移動する速さは,直交していない場合が多いでしょう.ゆっくり歩いている時よりも早く走っている時の方が心拍数が上がると考えられ,移動する速さの上昇に応じて心拍数が上がっていきます.このような関係の場合,それらの量は直交ではありません.

直交について,図で描くと,このようになるでしょうか?


上の図は,x軸とy軸が直交している場合です.(0,2)にあった点Pがx軸に平行に移動する場合,どこまで移動してもyの値は変わりません.yの値に関係なく,xの値を変化させ雨事ができるわけです.


しかし,x軸とy軸が直交していない場合,点Pをx軸に平行に移動すると,yの値も変わっていることがわかります.(すごく微妙な図になってしまいましたね・・・

このように,互いに作用せずに変化する量を扱えるという性質が直交した座標系にはあるのです.

ちなみに,ステレオのアナログレコードは,左右チャネルの振動がそれぞれ直角になるようなVの字に刻まれており,これによって,お互いの影響を受けず,2チャネルの振動を1本の針が読み取ることができるのです.


さて,何故ユニット名を直交を意味するOrthogonalにしようと思ったかというと,ユニット開始時点のスタイルとして想像していた,互いに互いのやっていることは気にせず,即興的にパフォーマンスしていくというスタイルが,まさに直交系のイメージに重なったからです.(※最近は互いに無干渉だと表現が発展しないということが分かってきたので,徐々に互いに干渉するようにしています.)

しかし,直交はともかく,Orthogonalは言い慣れないし,変な場所にアクセントがあるので,あんまり語感が気に入らないということで,類似の良い単語は無いかと考え,Affineにしました.

ですから,私個人としては,ユニット名はOrthogonalにしたかったのであり,Affineというユニット名自体には,殆ど意味は込められていません.

今後とも当ブログ・macaroom共々Affineもよろしくお願いいたします.

どんなユニットなのか書くのを忘れていました.長くなるので書きませんが,気になる方は,Affineが企画する最初のイベントにぜひお越しください.


2018年5月2日水曜日

「はじめての<脱>音楽 やさしい現代音楽の作曲法」にて付録記事を掲載しました 〜OpenMusicの基礎 番外編〜

随分久しぶりの投稿になってしまいました.仕事がバタバタし(これは毎年のこと),音楽活動もこれまでのmacaroomに加え,新しい活動も始めました.また,他にもいくつか個人的に引き受けたこともあり,そろそろ何足ものわらじを履くのがキツくなってきました.

そんな中の1つ,この度自由現代社から発売された「はじめての<脱>音楽 やさしい現代音楽の作曲法」において,巻末の付録部分に「OpenMusicの基礎」と題してIRCAMによる作曲支援ソフトウエアOpenMusicの初歩的な使い方を掲載する部分を執筆させていただきました.




OpenMusicは,現代音楽の作曲において多く用いられていながら,日本国内においては紹介される機会も少なく,あまり武器として使っている作曲家は見かけません.しかし,現代音楽史上でも存在感のある作曲家が多く使っていることも事実で,彼らの足跡を辿る良い手がかりにもなり得るソフトウエアです.

しかし,付録として掲載したので,簡単な操作方法の説明と,私が例題として作ったプログラム例を載せるのみにとどまってしまっています.正直な話,あの記事を読んだ結果,OpenMusicの使い方がわかったとして,それを使いこなして創作を行えるかというと,かなり難しいかもしれません.そこで,本ブログで,少しずつではありますが,本付録の補填記事を掲載していきたいと思います.(本記事を読む前に,「はじめての<脱>音楽 やさしい現代音楽の作曲法」を買って読んでくださいね〜

今回は,プログラマであれば一番気になるところ「OpenMusicプログラミングにおける制御構造」です.本来,プログラミング言語や環境を紹介するときは,これを真っ先に説明しなければならないのですが,本書は作曲法を説明する本であり,プログラミングの本ではないので,省略しました.

まず,比較のために,Maxの制御構造も確認してみましょう.今回作ったのは,非常に簡単なプログラム「100に1を足して100+1=101をコンソールに表示するプログラム」です.こんな感じ.


実践的なMaxプログラミングに慣れている方は状況に応じてもっと違った作り方をするかもしれませんが,今回は,制御構造を説明しやすいように画像のように作っています.

Maxのプログラム制御で押さえておきたいのが,

・データが第1インレットに入力されたらアウトレットからデータを出力し,次のオブジェクトに入力される(これがオブジェクトの繋がっている分だけ繰り返される)
・あるオブジェクトからの出力は,右→左の順番でデータが出力される

の2点です.これらを踏まえてこのパッチを見てみると,このような順で処理が行われているということになります.


上記2点の法則に従った順番になっていることを確認してみてください.

Maxはインタラクティブ操作を前提としたイベントドリブンプログラミングになっており,そのため,入力→出力というデータの流れが視覚的にうまく表現されている,非常に優れたプログラミング環境だと思います.

さて,同様の処理をOpenMusicでやると以下のようになります.



このパッチのprintオブジェクトを右クリックして,"Eval Box"をクリックすると,OM Listenerウィンドウに計算結果が表示されるわけです.



この,Eval Boxをクリックした時に,プログラムが実行されるのですが(Maxのインタラクティブなイベントドリブンプログラミングに慣れているとここが分かりづらい),この時,次のようなことが起こります.

・あるオブジェクトがEval Boxされると,そのオブジェクトのインレットに接続されているオブジェクトにデータを要求する
・データを要求されたオブジェクトがデータを持っていればそれを出力する
・データを要求されたオブジェクトがデータを持っていなければ,さらにそのオブジェクトのインレットに接続されているオブジェクトにデータを要求する
・要求の結果,データがインレットに入力されれば,処理してアウトレットから出力
・これをEval Boxしたオブジェクトから上のオブジェクトのつながりの数だけ繰り返す

この箇条書きで分かりましたでしょうか?とても難しいですね.これも,図に処理手順を書くと下のようになります.




Maxとは違い,下から上に要求が伝わり,データが処理されていることに注意してください.

尚,上の図において③と④の順番は,左から右の順でデータが到着しており,Maxとは逆です.この点については,私も現状そこまで詳しくないので,もしかしたら逆かもしれません.しかし,他の処理での挙動を見ていると,どうやら左から右の順でデータが処理されているように思います.

ごく簡単なパッチでのご紹介になりましたが,これがOpenMusicにおけるプログラミングの制御構造になります.このような面倒なことを知らなくても,冒頭紹介した本の付録で紹介した程度のプログラムを作成することは可能です.しかし,omloopや条件分岐等,プログラミングの醍醐味と言える処理を取り入れたパッチを作成しようと思うと,こうした制御構造の理解なしでは自分の意図通りの処理を実装することはかなり困難です.

それではよいアルゴリズム作曲ライフを!!

2017年12月31日日曜日

12/16 CAGE INにおけるVariationsⅡの演奏について

私が技術全般をサポートしているエレクトロニカユニットmacaroomが今年の8月にリリースしたミニアルバム"cage out"は,アメリカの現代音楽作曲家ジョン・ケージの楽曲を楽譜通りに,尚且つポップな「歌モノ」として演奏するという試みを行った画期的な内容となったアルバムでした.このアルバムの発売記念イベントとして,このプロジェクトのアドバイザをしていただいた現代音楽作曲家の川島素晴先生と,先生に紹介していただいたインプロビゼーション系の演奏ユニット木漏れ日エレキに,本格的なジョン・ケージ楽曲の演奏を行っていただくという事も行いました.





このイベントのアンサーイベントとして川島先生が企画したのが,先日行われたCAGE IN.このイベントは,最初から最後まで全てジョン・ケージの楽曲,しかも初期から晩年まで時系列順に聴けるという日本では非常に珍しい内容でした.私はこのイベントで音響を担当し,様々な楽曲の音源の準備や機材のプランニング等を行い,当日はそのオペレーションを行なったのですが,何曲かは出演者として演奏を行いました.その曲の1つが,VariationsⅡという曲です.この曲はイベントの第2部「ミュージサーカス」の中で演奏した曲です.ミュージサーカスとは,同時多発的に色々な楽曲や演目を行うというケージが提唱したコンサート形式で,本イベントではケージの様々な楽曲を全出演者で同時に演奏しました.その中でVariationsⅡは出演者それぞれが独自に準備し,ミュージサーカスの中で演奏することになっており,出演者それぞれのVariationsⅡが同時に演奏されていたのです.

私は,VariationsⅡで指示されている作業を全てコンピュータ・プログラムによって行い,演奏もプログラムによって自動演奏するという形で演奏しました.この試みは,川島先生から「VariationsⅡの新しいアプローチかもしれない」というご評価をいただきました.個人的には,過去にも似たことを考え実践した人は沢山いただろうと思うのですが,とにかく,当日はミュージサーカスということで音場がぐちゃぐちゃでどの音がそれだったのか分からなかったと思いますし,せっかく作ったのでその内容をご紹介します.尚,本プログラムはイベントの準備に追われ,自分のことに構う優先順位がどんどん下がる中,とにかく「16日に上演する」ことだけを目的としたため,ほとんど全てハードコーディングになっており,また,実際には特定の条件でしかありえないようなことを動作の前提としているため,あまり他の場面で再利用できるような段階ではありませんので,その点はご了承ください.

VariationsⅡの譜面には,点と直線が書かれた5枚の透明シート,直線のみが書かれた1枚の透明シートがついてきます.これをテーブルの上にばらまくと,6本の直線,5つの点からなる図形が生成されます.これは,楽譜に指示されたやり方のひとつで,実際には,6本の直線,5つの点からなる図形が生成されればそのやり方はなんでもいいのです.


例えば,ペンで直線と点を描いてもいいですし,ということは,コンピュータで自動生成してもいい訳です.

次に生成された図形から,それぞれの点と線の距離を測り,表にしていきます.この表から,どのような音を,いつ,どれくらいの時間出すべきか,以下のように決定します.点1つにつき,1つの音として,パラメータを当てはめていきます.つまり,6個のパラメータを持つ音を5つ,考えるわけです.



ここで少し難しいのは6番目の直線と点の距離.これは,音をいくつ増やすかということを決定します.例えば,点1と直線6の距離が3.1cmであったら,音1は3個の音を出さなければならない訳です.この時,3回同音連打をするのではなく,新しく上記の図形を生成し,表を作ってその表の数字を順に当てはめていきます.

ここまでの作業をMATLABで行い,それぞれのパラメータをテキストファイルとして保持するプログラムを作成しました.高校までの数学の知識のみで実現できます.尚,実際には,2回目以降の図形の生成は,実際に生成された点と直線6の距離によって何回行うかが異なるはずです.ですから,本当は2回目以降の図形の生成は無限ループにして,必要な値の数が揃ったらループを抜けるという具合にするのが正解と思われます.しかし,今回のプログラムの範囲では,3回以上生成を行う必要がほぼない(各点と直線6との距離の和が(5x6)-5=25を超えない)と判断したので,1回行うのみになっています.

----------------------------------------------------------------------------------------------------------------------

clear;
close all;
rng('shuffle');
pointx=zeros(1,5);%5点のx座標
pointy=zeros(1,5);%5点のy座標
%直線ax+by+c=0
a=zeros(1,6);
b=zeros(1,6);
c=zeros(1,6);
d=zeros(5,6);%直線と点の距離
 
%点を乱数で決定
for k=1:5
    pointx(k)=rand(1,1)*20-10;
    pointy(k)=rand(1,1)*20-10;
end
figure(1);
hold on;
plot(pointx,pointy,'x');
plot(pointx,pointy,'o');
%直線を乱数で決定
for k=1:6
    a(k)=rand(1,1)*4-2;
    b(k)=rand(1,1)*4-2;
    c(k)=rand(1,1)*4-2;
    %ax+by+c=0
    %y=(-ax-c)/b
    plot([min(pointx) max(pointx)],[(-a(k)*min(pointx)-c(k))/b(k),(-a(k)*max(pointx)-c(k))/b(k)]);
end
xlim([min(pointx)-2 max(pointx)+2]);
ylim([min(pointy)-2 max(pointy)+2]);
%距離を求める
for k=1:5
    for n=1:6
        d(k,n)=abs(a(n)*pointx(k)+b(n)*pointy(k)+c(n))/sqrt(a(n)*a(n)+b(n)*b(n));
    end
end
d
 
%6列目の数に応じてピッチの数を決定
times=floor(d(:,6))+1
%決定された数だけピッチの変数を確保
pitch1=zeros(times(1),1);
pitch1(1)=d(1,1);
pitch2=zeros(times(2),1);
pitch2(1)=d(2,1);
pitch3=zeros(times(3),1);
pitch3(1)=d(3,1);
pitch4=zeros(times(4),1);
pitch4(1)=d(4,1);
pitch5=zeros(times(5),1);
pitch5(1)=d(5,1);
 
%もう一度点と線のオペレーションを実行
rng('shuffle');
fig=2;
for k=1:5
    pointx(k)=rand(1,1)*20-10;
    pointy(k)=rand(1,1)*20-10;
end
figure(fig);
hold on;
plot(pointx,pointy,'x');
plot(pointx,pointy,'o');
for k=1:6
    a(k)=rand(1,1)*4-2;
    b(k)=rand(1,1)*4-2;
    c(k)=rand(1,1)*4-2;
    %ax+by+c=0
    %y=(-ax-c)/b
    plot([min(pointx) max(pointx)],[(-a(k)*min(pointx)-c(k))/b(k),(-a(k)*max(pointx)-c(k))/b(k)]);
end
d2=zeros(5,6);
for k=1:5
    for n=1:6
        d2(k,n)=abs(a(n)*pointx(k)+b(n)*pointy(k)+c(n))/sqrt(a(n)*a(n)+b(n)*b(n));
    end
end
d2=reshape(d2,30,1);%d2を1次元に変形
%d2の値を順にそれぞれのピッチに追加していく
index=1;
for k=2:length(pitch1)
    if index<=30
        pitch1(k)=d2(index);
        index=index+1;
    end
end
for k=2:length(pitch2)
    if index<=30
        pitch2(k)=d2(index);
        index=index+1;
    end
end
for k=2:length(pitch3)
    if index<=30
        pitch3(k)=d2(index);
        index=index+1;
    end
end
for k=2:length(pitch4)
    if index<=30
        pitch4(k)=d2(index);
        index=index+1;
    end
end
for k=2:length(pitch5)
    if index<=30
        pitch5(k)=d2(index);
        index=index+1;
    end
end
%値をピッチ(Hz)として正規化(範囲は適当に決めている)
pitch1=400+600*pitch1/max(d(:,1))
pitch2=400+600*pitch2/max(d(:,1))
pitch3=400+600*pitch3/max(d(:,1))
pitch4=400+600*pitch4/max(d(:,1))
pitch5=400+600*pitch5/max(d(:,1))
%ピッチを保存
fp=fopen('pitch1.txt','w');
fprintf(fp,'%f\r\n',pitch1);
fclose(fp);
fp=fopen('pitch2.txt','w');
fprintf(fp,'%f\r\n',pitch2);
fclose(fp);
fp=fopen('pitch3.txt','w');
fprintf(fp,'%f\r\n',pitch3);
fclose(fp);
fp=fopen('pitch4.txt','w');
fprintf(fp,'%f\r\n',pitch4);
fclose(fp);
fp=fopen('pitch5.txt','w');
fprintf(fp,'%f\r\n',pitch5);
fclose(fp);
%音量を0-1に正規化して保存%
fp=fopen('dynamics.txt','w');
fprintf(fp,'%f ',d(:,2)/max(d(:,2)));
fclose(fp);
%音色パラメータを1-100にスケーリングして保存
fp=fopen('sound.txt','w');
fprintf(fp,'%f ',(99*d(:,3)/max(d(:,3)))+1);
fclose(fp);
%持続時間を0-1に正規化して保存
fp=fopen('tlength.txt','w');
fprintf(fp,'%f ',d(:,4)/max(d(:,4)));
fclose(fp);
%タイミングを最大45分になるようにスケーリングして保存
fp=fopen('timing.txt','w');
fprintf(fp,'%f ',1000*45*60*(d(:,5)/max(d(:,5))));
fclose(fp);
----------------------------------------------------------------------------------------------------------------------


これによって,各パラメータが決まりました.
このテキストファイルをMaxで読み込んで,演奏します.まず,テキストファイルを読み込んで,それぞれのタイミングで音を生成します.今回,タイミングの最大値を45分(2700000ms)にしたので,delayオブジェクトがそんな大きな値の遅延を行えるのか,少し心配でしたが,どうやら大丈夫なようです.


音の生成は"p pulse"で行なっています.その中身がこちら.

根本的には,パルスにBPFをかけたものを音源として使っています.パルスは,全周波数のパワーが均等な信号であるため,これをフィルタに通すと,そのフィルタの特性そのままの音色となって出力されます.今回はBPFをフィルタとして使っています.ですので,フィルタのQがせまくなると,その中心周波数を周波数とする正弦波に近い音色が得られます.コンピュータ音楽で音程感のある音を得る時によく使う方法の1つです.
今回の演奏では,音色パラメータをフィルタのQとして適用しています.つまり,音色パラメータの値が小さければ,周波数帯域の広いノイズに近い音,音色パラメータの値が大き切れば,周波数帯域の狭い音程感の強い音となって演奏されます.
更に,この音にリバーブをかけています.本当は自作のリバーブを使いたかったのですが,これまで作った自作リバーブの中に,この音に合うものがなかったので,とはいえ,新しいリバーブを作っている時間がなかったので,コンピュータ音楽家の松本昭彦さんのForeverbを使わせていただきました.このリバーブの最大の残響時間を最大の持続時間として,適用します.測定したところ,このリバーブの最大の残響時間は約20秒でしたので,持続時間が1の時(MATLABでは0-1に正規化して書き出しました),Maxで20000倍して適用しています.この適用方は,Foreverbの残響時間が線形に増えていく(Foreverbで設定したReverb Timeが0.5であれば残響時間は10秒になる)という仮定を前提とした適用方ですが,実際にはそのような増え方はしていません.ですので,厳密に行うならば,残響時間を様々な値で測定し,その増え方を反映した曲線で値を適用しなければなりません.今回のプログラムでは線形に残響時間が増えると仮定したため,下の動画でお聞きいただければ分かりますが,音から次の音がなるまでの間に音が持続しておらず,音に隙間ができてしまっています.
同様に他のパラメータも適用し,あとはスタートボタンとなるトグルをクリックすれば演奏が開始されます.
以上が今回のCAGE INで行なったVariationsⅡの全容です.ここから,更に発展する方向としては,今回MATLABで行なった図形の生成をjavascriptで実装し,Maxのjsオブジェクトを使って生成から演奏までの1連の流れを1つのプログラムで行うようにする等が考えられます.

これは,タイミングの最大値を3分にして楽譜を生成した様子です.音の雰囲気だけでも感じていただけると幸いです.(3分過ぎから音量注意です)




2018/01/06追記

webから今見つけている範囲で,本件と類似の取り組みもご紹介しておきます.

Variations 10b: A digital realization of John Cage's Variations II

Pythonで演奏上々の生成を行って,OSC経由で何らかの音響合成プログラムに演奏させるものです.

ジョン・ケージ《VARIATINOS II》への 演奏ソフトウェアアートによるアプローチ

VariationsⅡに着想を得て,手法は同じでも,独自のパラメータ設定により演奏を行うプログラム.openFrameworksのaddonとして開発したそうです.