とChucKを紹介したところで

自分でちょこちょこと遊んだり、ドキュメントおよびMLを読んだりして分かったのがwavとかaiffとかの音源ファイルを読んで出すことができること。これはおもろい。というわけでターミナルからリズムマシンを動かすことを思いついた。しかもChucKのソース書くのがめんどいので今っぽくYAMLで書いた音源ファイルをrubyで変換してChucKのソースにするスクリプトを書いた。

ChucK自体がC++で書かれているようなのでrubyの拡張ライブラリもがんばれば書けるだろうけれども、いかんせん早く遊びたいのでテキスト処理で叩く方針。

ちなみにChucKで音源ファイルを鳴らすスクリプトはこんなん。

sndbuf note => dac;
"notes/kick.aiff" => note.read;
fun void taktpos(dur module, dur offset){
 module - ((now - offset) % module) => now;
}

60::second / 120 => dur t4;
t4 / 2.0 => dur t8;
t4 / 4.0 => dur t16;
t4 / 8.0 => dur t32;
t4 * 2.0 => dur t2;
t4 * 4.0 => dur t1;

while(true){

  0 => note.gain;
  taktpos(t8, 0::samp);
  0 => note.pos;

  0.5 => note.gain;
  t8 => now;
  0 => note.pos;

  0 => note.gain;
  t4 => now;
  0 => note.pos;

  0.5 => note.gain;
  t8 => now;
  0 => note.pos;

  0.5 => note.gain;
  t8 => now;
  0 => note.pos;

  0 => note.gain;
  t4 => now;
  0 => note.pos;

}

ループの中身が発音。note.gainで音量を設定。音量が0の場合は休符。「t4」「t8」ってのはそれぞれ4分音符と8分音符になる。上のほうは音源ファイルの定義と音源ファイル内のスタート位置指定など。詳しい説明は要望があれば。

準備するトラック定義用YAMLは次のようなかんじ。

NOTE:
  FILE: po.aiff
  GAIN: 0.5
LOOP:
  - t8*
  - t8
  - t4*
  - t8
  - t8
  - t4*

アスタがついてるとこは「t4ぶんの休符」ってこと。あとはなんとなく想像つくでしょ。

スクリプトは以下。ChucKだけにruby内で「チャック・ウィルソン」と書きたいし、あとからmoduleとしてつかうために「Chuck::Wilson」というふうにした。しかしながら自分で遊ぶために20分ほどでダダ書きしたので美しくない部分もあったりすると思います。つかいながらリファクタリます。

require 'yaml'
require 'pathname'

data = <<WILSON
#--sample data--#
NOTE:
  FILE: po.aiff
  GAIN: 0.5
LOOP:
  - t8*
  - t8
  - t4*
  - t8
  - t8
  - t4*

WILSON

module Chuck
  LENGTH = ["t4 / 2.0 => dur t8;","t4 / 4.0 => dur t16;","t4 / 8.0 => dur t32;","t4 * 2.0 => dur t2;","t4 * 4.0 => dur t1;"]
  FUNC = "fun void taktpos(dur module, dur offset){\n module - ((now - offset) % module) => now;\n}\n\n"  

  class Wilson
    def initialize(str,*bpm)
      @data = YAML::load(str)
      @note_name = @data["NOTE"]["NAME"]
      @bpm = bpm.to_s
      @buf = Array.new
    end
    
    def set
      if @data["NOTE"] != nil
        note = Array.new
        note << "sndbuf note => dac;\n"
        
        if @data["NOTE"]["FILE"] != nil
          note.push('"notes/'+@data["NOTE"]["FILE"]+"\" => note.read;\n")
        end
        
        @buf << note.join('')
        @buf << FUNC
        @buf << "60::second / #{@bpm} => dur t4;\n"+LENGTH.join("\n")+"\n\n" if @bpm != ''
      end
      
      if @data["LOOP"] != nil
        ctrl = Array.new
        process = Array.new
        
        @data["LOOP"].each_with_index do |itm, idx|
          if itm.to_s =~ /\*$/
            process << "  0 => note.gain;\n"
          else
            process << "  "+@data["NOTE"]["GAIN"].to_s+" => note.gain;\n"
          end
          
          if idx == 0
            process << "  taktpos("+itm.to_s.gsub("*","")+", 0::samp);\n"
          else
            process << "  "+itm.to_s.gsub("*","")+" => now;\n"
          end
          
          process << "  0 => note.pos;\n\n"
        end 
        
        ctrl.push("while(true){\n\n",process.join(''),"}\n")
        @buf << ctrl.join('')
      end
      
    end
    
    def morikawa
      puts @buf.join('')
    end
    
    def yukari
      p @data.to_a
    end
    
    def notes
      Pathname.new("./notes/").find{|f| puts f}
    end
    
  end
end

if __FILE__ == $0
  arg = ARGV.shift
  ck = Chuck::Wilson.new(data,120)
  ck.set
  
  if arg == "--list"
    ck.notes
  else
    ck.morikawa
  end
  
end

なお「morikawa」「yukari」というメソッドはdebug用。以前見知らぬ外人にソースをパクられた経験上debug用メソッド「showme」は「morikawa」「yukari」をつかっている。外人が「morikawa」「yukari」とか書いてるソースは壮観ですな!!

単音の音源準備してYAML書いて(イマントコ)リダイレクトしてファイル吐いて「chuck + xxx.ck」で食わせてとてもおもしろいことになります。