ChucK で波形を表示する

ChucK で作業してると波形を直感的に眺められなくて困る…なんてつぶやいてしまうと、ChucK 使うなと怒られそうだが。ChucK のデバッグプリントを使ってコンソールに簡単に波形を表示できるようにしてみた。

fun string display(float gain) {
  50 => int MAX_LINE;

  string line[MAX_LINE];
  line.cap() / 2 => int center;
  for(0 => int i; i<line.cap(); i++) {
    if(i == center) {
      "|" @=> line[i];
    } else {
      if((i - center) % 10 == 0) {
        ":" @=> line[i];
      } else {
        "." @=> line[i];
      }
    }
  }
  (center + gain * 10) $ int => int pos;
  if(pos < 0) {
    "!" @=> line[0];
  } else if(line.cap() <= pos) {
    "!" @=> line[line.cap()-1];
  } else {
    "*" @=> line[pos];
  }
  "" @=> string value;
  for(0 => int i; i<line.cap(); i++) {
    line[i] +=> value;
  }
  return value;
}

// test
SinOsc osc => dac;
440 => osc.freq;

while(true) {
  <<< display(osc.last()) >>>;
  100::samp => now;
}

適当にサイン波を表示してみる。

% chuck display.ck
".....:.........:.........*.........:.........:...." : (string)
".....:.........:.........|....*....:.........:...." : (string)
".....:.........:.........|.......*.:.........:...." : (string)
".....:.........:.........|........*:.........:...." : (string)
".....:.........:.........|.......*.:.........:...." : (string)
".....:.........:.........|...*.....:.........:...." : (string)
".....:.........:........*|.........:.........:...." : (string)
".....:.........:...*.....|.........:.........:...." : (string)
".....:.........:*........|.........:.........:...." : (string)
".....:.........*.........|.........:.........:...." : (string)
".....:.........:*........|.........:.........:...." : (string)
".....:.........:....*....|.........:.........:...." : (string)
".....:.........:.........*.........:.........:...." : (string)

ChucK に文字列を操作するための組み込み関数がほとんどないので、まじめに実装しようと思うと色々とつらいものがある。実際のところ使い物になるのかっつーと激しく疑問ではあるけれど、奇麗な波形を眺めることができたのでひとまずは良しとする。

ChucK による Fuzz の実装

ChucK の触りかたがぼちぼち分かってきたので簡単なエフェクタを実装してみる。まずは Fuzz から。

SndBuf osc => Gain overdrive => Gain limitter => dac;
"lost_control.wav" => osc.read; // <-- 適当な Wave ファイルを指定する

0.5 => float MAX_GAIN;  // <-- 出力時の最大 gain
5.0 => float OVERDRIVE; // <-- どのくらい歪ませるか

MAX_GAIN * OVERDRIVE => overdrive.gain;
for(0 => int i; ; i++) {

  1.0 => limitter.gain;
  if(MAX_GAIN < Math.fabs(overdrive.last())) { // 出力予定の gain より大きかったら余計な振幅を削る 
    if(overdrive.last() != 0) { // ゼロ除算の予防
      MAX_GAIN / Math.fabs(overdrive.last()) / OVERDRIVE => limitter.gain;
    }
  }

  1::samp => now; // サンプルごとにループ
}

ここでは 2 つの Gain を使用している。1 つ目の gain (overdrive) で出力の振幅を大きくしてから、もう 1 つの gain (limitter) で膨らみすぎた振幅の上部を削り取っている。limitter での出力レンジの調整は、overdrive の最後の出力値 (overdrive.last() の値) を元に limitter の gain を調整して、最終的な dac への出力が MAX_GAIN を超えないようにしている。

正しい実装になっている自信はないものの、適当に聞いた感じではそれっぽく歪んでいるようだ。

ChucK : Language Specification > Operators

ChucK : 言語仕様 > 演算子

version: 1.2.x.x (dracula)

ChucK - [Language Specification : Operators & Operations]

演算子と演算

あるデータに対する演算は演算子を通して行なわれます。この節では、各種のデータ型に対して演算子がどのように振る舞うのか解説します。C や Java 等の他のプログラミング言語における演算子についてご存知ならば、それらのうちのいくつかは ChucK でもそのまま使うことができます。ここでは、まずは ChucK における特別な演算子から解説を始めます。

演算子の使用例についてはサンプルコードもご覧ください。

=> (ChucK 演算子)

ChucK 演算子 (=>) は非常に多くの機能を持った演算子であり、引数に与えられた型に応じた様々な動作を行ないます。この演算子は、常に左から順番に処理が行なわれるよう制約が課されており、複数の演算子を重ねることで連続した処理の流れを表現することもできます。ChucK 演算子は、ある時点までにどの処理までが完了しているのかを表しています。また、ChucK 演算子にはいくつかの類似の演算子が存在します。

=> (基本 ChucK 演算子)

まずは最も基本となる ChucK 演算子 (=>) から始めましょう。この演算子は左結合であり (これは ChucK の全ての演算子に共通です)、データや処理およびモジュール (unit generator や外部の機器) などを処理したり接続したりする順番について指定することができます (左から右の順番に接続されます)。=> という演算子が持つ意味は、それが記述されたコンテキストによって異なります。意味は演算子の左辺に位置する要素 (the chucker) と右辺に属する要素 (the chuckee) によって決定され、また時には、要素の種類 (変数であるかどうかなど) によって決定されることもあります。

いくつかの例:

  // unit generator のパッチ - 見たまんま
  // (この例では、=> は 2 つの unit generators を接続しています)
  SinOsc b => Gain g => BiQuad f => dac;

  // foo に 4 を足して、結果を 'int' 型の変数 'bar' に代入します
  // (この例では、=> は変数 (int) への代入を意味しています)
  4 + foo => int bar;

  // 関数への値の引き渡し == 関数の呼び出し
  // (Math.rand2f( 30, 1000) と同じ意味)
  ( 30, 1000 ) => Math.rand2f;
@=> 明示的な代入のための ChucK 演算子

ChucK には他のプログラミング言語に見られる一般的な代入演算子 (=) は存在しません。変数への代入は ChucK 演算子によって行ないます。先ほどの例でも、=> を代入のために使っていました。

  // foo に 4 を代入
  4 => int foo;
  
  // bar に 1.5 を代入
  1.5 => float bar;
  
  // 100ミリ秒の期間を duh に代入
  100::ms => dur duh;
  
  // "今から 5 秒後" を later に代入
  5::second + now => time later;

@=> という代入演算子は、プリミティブ型 (int, float, dur, time) に対しては通常の ChucK 演算子と同じように働きます。しかしながら、=> がプリミティブ型 (int, float, dur, time) の代入にのみ適用可能であるのに対して、@=> はオブジェクトへの参照の割り当て (objects and classes を参照のこと) にも使用することができます。

  // プリミティブ型に対する @=> は => と同じ意味です
  4 @=> int foo;
  
  // bar に 1.5 を代入
  1.5 @=> float bar;
  
  // (@=> を使わないとオブジェクトへの参照の割り当てはできません)
  
  // moe から larry へオブジェクトの参照を割り当てる
  // (つまり moe と larry は同じオブジェクトを指している)
  Object moe @=> Object @ larry;
  
  // 配列の初期化
  [ 1, 2 ] @=> int ar[];
  
  // new を使ってみる
  new Object @=> moe;

このような方法はとても奇妙に見えるかもしれませんが、代入式 (@=> と =>) と等価式 (==) の混乱をなくすことができるという意味で良いものであると考えています。*1 以下の例は ChucK ではエラーになります。

  // ChucK の文としては不正です!
  int foo = 4;
+=> -=> *=> /=> など (算術的 ChucK 演算子)

これらの演算子は既存の変数 ('int' や 'float') と共に使われ、計算と代入を同時に行ないます。

  // foo に 4 を足して結果を foo に代入
  foo + 4 => foo;
  
  // foo に 4 を足して結果を foo に代入
  4 +=> foo;
  
  // foo から 10 を引いて結果を foo に代入
  // この結果は (foo-10) であり、(10-foo) ではないことを忘れずに
  10 -=> foo;
  
  // foo に 2 を掛けて結果を foo に代入
  2 *=> foo;
  
  // foo を 4 で割って結果を foo に代入
  // この結果は (foo/4) であり、(4/foo) ではないことを忘れずに
  4 /=> foo;

減算/除算を行なう代入演算子 (-=> や /=>) を使う場合、これらの演算は交換可能でないため、値と変数の関係性についてよく理解しておく必要があります。

  // foo を % で割って結果を foo に代入
  T %=> foo;
  
  // 0xff と bar のビット積を求め結果を bar に代入
  0xff &=> bar;
  
  // 0xff と bar のビット和を求め結果を bar に代入
  0xff |=> bar;

妙なことしようとしなければこれくらいで十分だよね... *2

+ - * / (算術演算子)

足し算、引き算、かけ算、割り算… 君は全部できる? ChucK ならできるよ!

  // 除算 (と代入)
  16 / 4 => int four;
  
  // 乗算
  2 * 2 => four;
  
  // 加算
  3 + 1 => four;
  
  // 減算
  93 - 89 => four;
型変換 (キャスト)

ChucK では、float を期待している文脈に int が与えられた場合、暗黙の型変換が行なわれます。しかし、他の型ではこのような型変換は行なわれません。データの損失が発生する型変換を行ないたい場合には、明示的な型変換を行なう必要があります。

  // float と int の加算は float になります
  9.1 + 2 => float result;
  
  // flot を int に型変換したい場合はキャストが必要する
  4.8 $ int => int foo;  // foo == 4
  
  // 2 つの float を期待している関数
  Math.rand2f( 30.0, 1000.0 );
  
  // 暗黙の型変換が行なわれるためこれは ok
  Math.rand2f( 30, 1000 );
% (余剰)

余剰演算子 % は整数値、浮動小数点数、期間、時間/期間に対する余剰の演算を行ないます。

  // 7 mod 4 (3 になる)
  7 % 4 => int result;
  
  // 7.3 mod 3.2 浮動小数点数の余剰 (.9 になる)
  7.3 % 3.2 => float resultf;
  
  // 期間の余剰
  5::second % 2::second => dur foo;
  
  // 時間と期間の余剰
  now % 5::second => dur bar;

最後の例 (時間/期間の余剰) はシュレッド間で動的な同期を行なうための方法の一つです。examples にある otf_01.ck から otf_07.ck にあるサンプルでは、on-the-fly で様々なパーツを同期させるためにこの方法を用いています。シュレッドが VM に追加されるタイミングについては気にする必要はありません。

  // 周期の定義 (いくつかのシュレッドで共有される)
  .5::second => dur T;
  
  // 現在のシュレッドのための余剰を求め、
  // その時間の分だけ時間を進める
  T - (now % T) => now;
  
  // ここまで来たらもう T の周期と同期できています
  
  // 残りの部分
  // ...

この方法は、ChucK で時間の流れを制御するための方法の一つです。意図している機能によっては異なる解決方法が必要な場合もあります。がんばって!

&& || == != > >= < <= (論理演算)

論理演算 - どれも 2 つの引数を取ります。返値は整数値の 0 または 1 になります。

  • && : and
  • || : or
  • == : equals
  • != : does not equal
  • > : greater than
  • >= : greater than or equal to
  • < : less than
  • <= : less than or equal to
  // 真との比較を行なう
  if( 1 <= 4 && true )
  <<<"horray">>>;
>> << & | ^ (ビット演算)

これらの演算子は int 値のビット単位での演算を行ないます。ビットマスクの計算などの場合に使用します。

  • >> : ビット右シフト ( 8 >> 1 = 4 )
  • << : ビット左シフト ( 8 << 1 = 16 )
  • & : ビット単位 AND
  • | : ビット単 OR
  • ^ : ビット単位 XOR
++ -- (インクリメント / デクリメント)

変数の後ろに ++ や -- をつけることで値をインクリメント/デクリメントすることができます。

  4 => int foo;
  foo++;
  foo--;
! + - new (単項式)

これらの演算子オペランドの前に配置します。

  // 論理否定
  if( !true == false )
  <<<"yes">>>;
  
  // 反転
  -1 => int foo;
  
  // オブジェクトの生成
  new object @=> object @ bar;

*1:いや変だし。余計な混乱を産むだけで意味ないし。

*2:"That's probably enough operator abuse for now..." の意味がよく分からん。

sucks or rocks

ChucK の言語仕様について調べているんだが、このなんとも言えない仕様の汚らしさはどうにかならんものか…。単に俺の目が曇ってるだけなら良いんだけど、(昔の) PHP でも見てるみたいでいやんな気分。

ま、VMコンパイラに分割してあるみたいだし、どうしても気に食わないってなら時前でコンパイラを作っちまうのが良いかも知れないな。

ChucK : Language Specification > Arrays

ChucK : 言語仕様 > 配列

version: 1.2.x.x (dracula)

ChucK - [Language Specification : Arrays]

配列

配列とは、N 次元の整列した (同じ型の) データの集合を表したものです。
この節では、ChucK において配列がどのように宣言され、また扱われるのかを述べます。
以下はいくつかの簡単なメモです:

  • 配列は整数 (0 から数えられます) により順序付けされます。
  • 全ての配列は連想配列 (文字列を使った辞書) として扱うこともできます。
  • 整数によって順序付けされる部分と連想配列とは、異なる名前空間に保存されることを忘れないでください。
  • 配列はオジェクト (objects and classes を参照のこと) であり、その他の参照型と同様に代入や操作が可能です。

配列についてはサンプルコードも参照してください。

宣言

配列は以下のようにして宣言されます。

// 10 個の int 型の要素を持つ foo と言う名前の配列変数を宣言する
int foo[10];

// int (プリミティブ型) の配列であるため、配列の中身は
// 自動的に 0 に初期化されます

以下のようにして配列を初期化することもできます。

// chuck による配列の参照の初期化
[ 1, 1, 2, 3, 5, 8 ] @=> int foo[];

上記のコードには、いくつかの特記事項があります。

  • 配列の初期化式には、同じ型または類似の型の値を含む必要があります。それら全ての要素の最も基底となる型を探し出そうとします。もし、要素の間に基底となる型が存在しない場合はエラーとなります。
  • 初期化式 [ 1, 1, 2, 3, 5, 8] の型は int[] になります。初期化式は配列そのものであり、配列を使いたい場所に直接記述することができます。
  • at-chuck 演算子 (@=>) は参照の割り当てを意味しており、その詳細については operators and operations にて記載があります。
  • int foo[] では空っぽの配列の参照を宣言しています。この文は、配列 foo に初期化式の評価値を割り当てています。
  • 配列はオブジェクトです。

オブジェクトの配列を宣言した場合は、自動的に作成されるインスタンスで配列が初期化されます。

// 配列に自動的に作成されたインスタンスが割り当てられます
Object group[10];

オブジェクトへの参照の配列のみが欲しい場合には以下のようにします:

// null オブジェクトへの参照の配列
Object @ group[10]

Chuck におけるオブジェクトの代入とインスタンス作成の詳細については Check here。

上記の例では一次元の配列 (またはベクタ) を宣言しました。次節では多次元配列について述べます。

多次元配列

以下のようにすることで多次元の配列を宣言することもできます:

// float 型の 4 * 6 * 8 の配列を宣言
float foo3D[4][6][8];

同様に初期化式も使えます:

// int 型の 2 * 2 の配列を宣言
[ [1,3], [2,4] ] @=> int bar;

上記のコードでは、行列 (matrix) を作っているため として宣言しています。

参照

配列中の要素には [] を使ってアクセスできます。(宣言した大きさを越えない範囲で)

// float 型の配列を宣言
[ 3.2, 5.0, 7 ] @=> float foo[];

// 0 番目の要素にアクセス (デバッグ表示させます)
<<< foo[0] >>>; // きっと 3.2

// 2 番目の要素をセットします
8.5 => foo[2];

配列中の全ての要素を取り出します:

// またもや float 型の配列
[ 1, 2, 3, 4, 5, 6 ] @=> float foo[];

// 配列全体をループします
for( 0 => int i; i < foo.cap(); i++ )
{
    // 何かします (デバッグ表示)
    <<< foo[i] >>>;
}

多次元配列にアクセスします:

// 2 次元配列
int foo[4][4];

// 要素をセットします
10 => foo[2][2];

もしも配列のインデックスがその次元における配列の大きさを越えてしまった場合、例外が発生して現在のシュレッド (shred) が停止します。

// 配列の大きさは 5
int foo[5];

// これは ArrayOutOfBoundsException を引き起こします
// 6 番目の要素にアクセス (インデックス 5)
<<< foo[5] >>>;

ちょっと長いプログラム: example から otf_06.ck:

// 周期
.5::second => dur T;
// 周期に同期させる (一括処理の際の同期のため)
T - (now % T) => now;

// パッチの定義
SinOsc s => JCRev r => dac;
// 初期化
.05 => s.gain;
.25 => r.mix;

// スケール (ペンタトニック; 半音づつ)
[ 0, 2, 4, 7, 9 ] @=> int scale[];

// 無限ループ
while( true )
{
    // スケール上からどれか音を選び出す
    scale[ Math.rand2(0,4) ] => float freq;
    // 周波数を求める
    Std.mtof( 69 + (Std.rand2(0,3)*12 + freq) ) => s.freq;
    // reset phase for extra bandwidth
    0 => s.phase;

    // 時間を進めます
    if( Std.randf() > -.5 ) .25::T => now;
    else .5::T => now;
}

連想配列

全ての配列は文字列をキーとした連想配列として使用することも可能です。通常の配列を作成した後、特別な操作は何もせずとも、そのまま連想配列として扱うことができます。

// declare regular array (capacity doesn't matter so much)
// 通常の配列を宣言 (ここで宣言する配列の大きさは連想配列には関係ありません)
float foo[4];

// 通常の配列として使用
2.5 => foo[0];

// 連想配列として使用
4.0 => foo["yoyo"];

// 連想配列にアクセス (デバッグ表示)
<<< foo["yoyo"] >>>;

// 存在しない要素にアクセス
<<< foo["gaga"] >>>;  // -> 0.0 として表示されるはず

大切なことなので再度記載しますが、整数によって順序付けされる部分と連想配列とは完全に異なる名前空間に分割されています。例えば以下のように:

// 配列の宣言
int foo[2];

// 0 番目の要素として何かを代入
10 => foo[0];

// "0" に何かを代入
20 => foo["0"];

// 10 20 が表示されます
<<< foo[0], foo["0"] >>>;

宣言された際の配列の大きさは整数によって順序付けされる配列にのみ適用されます。例えば、長さ 0 の配列を作成した場合でも、連想配列としてはいくつでも要素を持たせることができます。

// 長さ 0 の配列を宣言
int foo[0];

// "here" に何かを代入
20 => foo["here"];

// this should print out 20
// 20 が表示されます
<<< foo["here"] >>>

// 例外が発生します
<<< foo[0] >>>

注意: 通常の配列の場合とは異なり、連想配列に含まれる要素は全て明示的に初期化されるものであるため、連想配列の長さは事前に指定されません。

連想配列の初期化されていない要素は null への参照を返します。ChucK のオブジェクトと参照についての説明は class documentation page を参照してください。

class Item { 
   float weight; 
}

Item box[10]; 

// 整数のインデックスは事前に初期化されています
1.2 => box[1].weight; 

// "lamp" に新しいインスタンスを代入します
new Item @=> box["lamp"]; 

// "lamp" へアクセスすることができるようになりました
2.0 => box["lamp"].weight; 

// オブジェクトが存在しないため NullPointerException が発生します
2.0 => box["sweater"].weight; 

配列への代入

配列はオブジェクトです。そのため、配列を宣言した場合、私たちは実際には (1) 配列への参照 (ポインタ変数) を宣言して、(2) 新しい配列を生成してその参照を変数に割り当てるということを行なっています。空の参照とは、オブジェクトではないものか null を指し示すように宣言された参照です。空の参照を持つ配列変数は以下のようにして宣言できます:

// 配列への参照を宣言します (配列の長さは指定しません)
int foo;

// この時点で foo には int なら何でも代入できます
[ 1, 2, 3 ] @=> foo;

// 0 番目の要素を表示します
<<< foo[0] >>>;

関数の引数や返値として配列を用いることもできます。

// print を定義します
fun void print( int bar[] )
{
    // 表示させます
    for( 0 => int i; i < bar.cap(); i++ )
        <<< bar[0] >>>;
}

// リテラルの配列初期化式を直接関数に渡すことができます
print( [ 1, 2, 3, 4, 5 ] );

// もしくは、参照変数として配列を渡すこともできます
int foo[10];
print( foo );

その他のオブジェクトと同様に、ある配列への参照をいくつも作成することができます。その他のオブジェクトと同様に、全ての代入は参照の代入となるため、データのコピーは行なわれずに配列への参照のみがコピーされます。

// 配列を宣言します
int the_array[10];

// 配列の参照を foo と bar に代入します
the_array => int foo => int bar;

// (この時点で the_array と foo と bar は全て同じ配列への参照です)

// the_array に変更を行なってから foo を表示すると...
// 同じ配列への参照であるため、どれかに変更を加えると全てに変更が加えられたように見えます
5 => the_array[0];
<<< foo[0] >>>; // 5 と表示される

多次元配列のうち一部の部分配列への参照を得ることもできます。

// 三次元配列
int foo3D[4][4][4];

// 部分配列への参照を得ることができます
foo3D[2] => int bar;

// (ここで指定する次元は正しいものでなければいけません!)

ChucK : Language Specification > Type

ChucK: 言語仕様 > 型

version: 1.2.x.x (dracula)

ChucK - [Type]

型、値、および変数

ChucK は強い型付けを持った言語であり、コンパイル時に全ての型がチェックされます。しかし、このことは ChucK が静的型の言語であることを意味しません。なぜなら、コンパイラおよび型システムは ChucK 仮想マシンの一部であり、そしてそれらは ChucK の動作環境でもあるためです。型システムはコードが正確さと明瞭さを保つことを助け、また、複雑なプログラムの構造化を行ないやすくさせます。また、ChucK は動的な型にも対応しています。マナーに反さない範囲では実行時に型システムへの変更を行なうこともできます。このような動的プログラミングは、on-the-fly programming を行なう際の基本になっています。

この節では、型、値、代入、および変数の扱いかたについて論じます。型とそれらに関連づけられたデータの扱われかたについては、他の強い型付けを持ったプログラミング言語での場合とほぼ同様に考えることができます。(例: 'int' は整数 (integer) の型であり、2 つの整数の加算を行なった場合には、計算結果である第 3 の整数を返すよう定義されています) クラスとオブジェクトを用いることで、既存の型を独自に定義した型によって拡張することができます。この節では、主にプリミティブ型についてのみを扱うこととし、複雑な型の説明については class and objects にて扱います。

型、値、変数についてはサンプルコードもご覧ください。

プリミティブ型

プリミティブ型は単純なデータ型です。これらの型の値は特殊な属性値を持っていません。オブジェクトはプリミティブ型ではありません。プリミティブ型は値によって渡されます。プリミティブ型を拡張することはできません。ChucK におけるプリミティブ型は以下の通りです:

  • int : 整数 (符号付き)
  • float : 浮動小数点数 (ChucK における float はデフォルトで倍精度です)
  • time : 時間の表現 (ChucK 流)
  • dur : 期間の表現 (ChucK 流)
  • void : (型なし)
  • complex : a + bi の形式で表現される複素数
  • polar : 極座標の形で表現される複素数

値 (もしくはリテラル)

リテラル値とはコード中で明示的に記述される値のことであり、コンパイラによって型が割り当てられます。以下はリテラル値の記述例です。

整数:

42

整数 (16進記法):

0xaf30

浮動小数点数:

1.323

期間:

5.5::second

上記のコードでは、second が期間を表している既存の変数です。期間についての表現の詳細は manipulating time の節を参照してください。

変数

変数とは、メモリ上でデータが保持されている場所を示すために使われるものです。ChucK では変数は使われる前に宣言されている必要があります。例えば、foo という整数型の変数を宣言するには以下のようにします:

// 'foo' という名前の 'int' 型の変数を宣言します
int foo;

宣言された変数に対しては、ChucK 演算子を使うことによって値を代入することができます。この演算子は ChucK で何かをする場合には最もよく使われるもののひとつです。

// 'foo' に 2 を代入
2 => foo;

変数の宣言と代入を同時に行なうこともできます:

// 新しい 'int' 型の変数 'foo' に 2 を代入
2 => int foo;

変数を使うには、変数名を指定します:

// foo の値をデバッグ表示させる
<<< foo >>>;

foo の値を更新したい場合には以下のようにします:

// 'foo' の値に 10 を掛け、再び 'foo' に代入します
foo * 10 => foo;

以下は期間の表現に関する例です:

// 新しい変数 bar に '5 秒' の値を代入します
5::second => dur bar;

既に bar を定義している場合、既存の定義を使って新たな期間を定義することもできます:

// bar の 4 倍でひと区切り?
4::bar => dur measure;

ChucK のプログラミングでは時間が主要な存在となるため、時間 (time) や期間 (dur) の関連性とそれらの扱いかたについて理解しておくことはとても重要です。それらについての詳細は manipulating time 節で述べます。

参照型

参照型とは Object クラスを継承しているもののことです。いくつかのデフォルトの参照型が定義されています:

  • Object : 全てのクラスが直接的/間接的に継承している最も基本的な型です
  • array : N 次元の整列された (同じ型の) データの集合です
  • Event : 基本的な、拡張可能な、同期メカニズム
  • UGen : 拡張可能な unit generator 基底クラス
  • string : 文字の連なり

複素数*1

FFT の出力に見られるような複素数の値を表すため、2 つの特別なプリミティブ型が用意されています。直行形式と極形式 (polar form) です。a + bi の形式で表現される複素数は以下のように代入できます:

#(2,3) => complex cmp; //cmp は 2 + 3i

#(...) という構文は、それが直行形式による複素数の表現であることを示します。複素数同士では直接的に演算を行なうこともできます:

#(5, -1.5) => complex cmp; // cmp は 5 - 1.5i
#(2,3) + #(5,6) + cmp => complex sum; // sum は 12 + 7.5i

複素数の実部 (real parts) と虚部 (imaginary parts) は、それぞれ .re と .im を用いて得ることができます

#(2.0,3.5) => complex cmp;
cmp.re => float x; // x は 2.0
cmp.im => float y; // y は 3.5

極形式もまた複素数の等価な表現形式であり、magnitude と phase value とを用いて表されます。極形式で表現した複素数の代入は以下の様にして行ないます。*2

%(2, .5*pi) => polar pol; // pol は 2∠.5π

magnitude と phase value は .mag と .phase を用いて得ることができます。

%(2, .5*pi) => polar pol;
pol.mag => float m; // m は 2
pol.phase => float p; //p は .5π

極形式複素数との間では互いに型変換を行なうことができ、乗算/加算/代入などが行なえます:

%(2, .5*pi) => polar pol;
#(3, 4) => complex cmp;
pol $ complex + #(10, 3) + cmp => complex cmp2;
cmp $ polar + %(10, .25*pi) - pol => polar pol2;

*1:訳注: 翻訳者の数学的知識の欠落により翻訳が死ぬ程あやしい件

*2:magnitude と phase value の訳語って何だろう? 大きさと偏角で良いの?

ChucK: Language Specification > Overview

ChucK: 言語仕様 > 概要

version: 1.2.x.x (dracula)

ChucK - [Language Specification : Overview]

概要

ChucK は強く型付され、また強く時間に結びつけられた(strongly-timed)、並列実行が可能なマルチメディアプログラミング言語です。ChucK の処理系は仮想命令へのコンパイルを行ない、それらは仮想マシンによって即座に実行されます。

ChucK を実行する

いくつかの簡単なメモ。

  • あなたは ChucK をローカルコンピュータにインストール (build instractions を参照のこと) し、また、実行することができます。
  • ChucK は chuck と呼ばれるコマンドラインアプリケーションです。(Audicle を参照のこと)
  • コマンドプロンプトやターミナルから ChucK を実行することができます (例: OS X なら Terminal や xterm、Windows なら cmd や cygwinLinux なら…あなたはお好きなターミナルをお持ちでしょう?)
  • これらの手順はあくまで概要です。コマンドラインのオプションに関するより詳細な情報が必要な場合には VM Options の頁を参照のこと。

foo.ck というプログラムまたはパッチを実行するには、単純に chuck にファイル名を渡して実行します。

 %> chuck foo.ck 

同時に複数のパッチを実行する(もしくは、特定のパッチを複数実行する)には次のようにします。

 %> chuck foo.ck bar.ck bar.ck boo.ck 

ChucK の実行を制御するため、およびシステムに関するある種の情報を取得するために、いくつかのコマンドラインオプションが用意されています。たとえば、次の例では、システム上で利用可能な全てのオーディオ装置と MIDI 装置を探し出して表示します。あなたはコマンドラインやあなたのプログラムから、何らかの方法 (通常は数字で) それらを指定することができます。(このことについては、VM Options の頁により詳細な情報があります)

 %> chuck --probe 

ChucK はホスト/リスナとして、別のターミナルから送られたパッチを受け取って実行することができます。サーバとする仮想マシンに対して --loop オプションを指定することによって、実行中のプログラムが終了した後に仮想マシンが自動的に終了しないようになります。

 %> chuck --loop 

(詳細な情報については On-the-fly Programming ガイドを参照してください)

ChucK リスナが動作している場合、+ オプションを使うことによってプログラム/パッチをリスナに送り込むことができます。

 %> chuck + foo.ck 

同様に、- と = を使うことによってパッチを削除/置換することや、^ を使って状態を表示することもできます。詳細については On-the-fly Programming を参照してください。

この言語仕様書に記載された多くのコードサンプルは、基本的な chuck のプログラムのみを使って実行することができます。

コメント

コメントとはコードのかたまりのうち、コンパイラによって無視される部分のことです。これらは他のプログラマが (もしくはあなた自身が) コードを読み解くことを助けます。二重のスラッシュは、行中の以降の内容が読み飛ばされるべきものであることをコンパイラに示します。/* と */ はブロックコメントで、コンパイラはこれらの間に記述された内容を読み飛ばします。(追記: ブロックコメントはネストすることができません)

    // this is a comment
    int foo; // another comment

    /* 
       this is a block comment
       still going...
    */

デバッグプリント

現在までのリリースでは、標準出力 (stdout and chout) は一時的に無効にされています。その代わりとして、以下のデバッグプリントの記法が有効です。

    // prints out value of expression
    <<< expression >>>;

この記法によって、括弧内に記述されたあらゆる式の評価値と型が表示されます。このデバッグプリントの記法は、代入式 (non l-value) 以外のあらゆる場所に配置することができ、また、副作用を持ちません。式の評価値がオブジェクトであった場合には、オブジェクトの参照アドレスが表示されます。

    // assign 5 to a newly declared variable
    5 => int i;
    // prints "5 : (int)"
    <<< i >>>;

    // prints "hello! : (string)"
    <<<"hello!">>>; //prints "hello! : (string)"

    // prints "3.5 : (float)"
    <<<1.0 + 2.5 >>>=> float x;

もう少し見栄えの良い出力が欲しいのであれば、複数の式をコンマで区切ることでそれぞれの値を表示することができます。(それぞれの間には 1 つのスペース文字が表示されます)

    // prints "the value of x is 3.5" (x from above)
    <<<"the value of x is" , x >>>;

    // prints "4 + 5 is 9"
    <<<"4 + 5 is", 4 + 5>>>;

    // prints "here are 3 random numbers ? ? ?"
    <<<"here are 3 random numbers", 
        Std.rand2(0,9), 
        Std.rand2(0,9),
        Std.rand2(0,9) >>>; 

予約語

  • (プリミティブ型)
    • int
    • float
    • time
    • dur
    • void
    • same (unimplemented)
  • (制御構文)
    • if
    • else
    • while
    • until
    • for
    • repeat
    • break
    • continue
    • return
    • switch (unimplemented)
  • (class keywords)
    • class
    • extends
    • public
    • static
    • pure
    • this
    • super (unimplemented)
    • interface (unimplemented)
    • implements (unimplemented)
    • protected (unimplemented)
    • private (unimplemented)
  • (その他の chuck キーワード)
    • function
    • fun
    • spork
    • const
    • new
  • (special values)
    • now
    • true
    • false
    • maybe
    • null
    • NULL
    • me
    • pi
  • (special : default durations)
    • samp
    • ms
    • second
    • minute
    • hour
    • day
    • week
  • (special : global ugens)
    • dac
    • adc
    • blackhole