バイオメディア・アート
ArduinoとProcessingの連携3:「植物シンセ」を作る
今日の内容
- Arduinoからのデータを、Procssingを使用して「可聴化」する
- データを音に変換する方法 – Processing minimライブラリについて
- サウンドの生成
- サウンドファイルを再生
- 可変抵抗の値でサウンドをコントロール
- 植物にとりつけたタッチセンサーで、音を鳴らす – 「植物シンセ」簡易版
Minimライブラリについて
- Minimライブラリ – Processingで音を扱うためのライブラリ、Java Sound APIを使用している
- 音に関する様々な機能が利用できる
- サウンドの再生 – WAV, AIFF, AU, SND, MP3形式のサウンドファイルを読み込んで再生する
- 録音:入力された音を、ディスクに直接、またはメモリー上のバッファーに録音可能
- オーディオ入力:モノラル、ステレオのオーディオ入力の値を取得
- オーディオ出力:モノラル、ステレオでサウンドを出力
- 音響合成:音響合成のためのシンプルなインタフェイスを提供
- エフェクト:サウンドエフェクトのためのインタフェイスを提供
- FFT:高速フーリエ変換で、リアルタイムにスペクトル解析が可能
- 今日の授業では、シンプルなサイン波の生成と、サウンドファイルの再生を使用
Minimライブラリを使用してみる
サイン波の生成
- ステレオのサイン波を生成する
- マウスのX座標で周波数を変化させる
- マウスのY座標で音量を変化させる
import ddf.minim.*; import ddf.minim.signals.*; //Minimクラスのインスタンスminimを定義 Minim minim; //オーディオをoutに出力する AudioOutput out; //サイン波の波形をsineに生成 SineWave sine; void setup() { size(800, 400); //Minimクラスをインスタンス化 minim = new Minim(this); //モノラルのオーディオ出力として、outを準備 out = minim.getLineOut(Minim.MONO); //440Hz、音量1.0でサイン波を出力 sine = new SineWave(440, 1.0, out.sampleRate()); //ポルタメント(音程変化のなめらかさ)を200msecに sine.portamento(200); //生成したサイン波をオーディオ出力に追加 out.addSignal(sine); } void draw() { background(0); } void mouseMoved() { //マウスのX座標を周波数に設定 //X座標の(0, width)を、周波数(40, 2000)にマップ float freq = map(mouseX, 0, width, 40, 2000); sine.setFreq(freq); //マウスのY座標を音量に設定 //Y座標の(0, height)を、周波数(1.0, 0.0)にマップ float amp = map(mouseY, 0, height, 1.0, 0.0); sine.setAmp(amp); } void stop() { //プログラムの終了処理 out.close(); minim.stop(); super.stop(); }
波形の表示
- 波形を表示する
import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; SineWave sine; void setup() { size(800, 400); frameRate(60); smooth(); strokeWeight(2); minim = new Minim(this); out = minim.getLineOut(Minim.MONO); sine = new SineWave(440, 1.0, out.sampleRate()); sine.portamento(200); out.addSignal(sine); } void draw() { //波形を表示 background(0); stroke(0,255,0); //Y座標の原点を画面の中心に移動 translate(0, height/2); //バッファーに格納されたサンプル数だけくりかえし for(int i = 0; i < out.bufferSize() - 1; i++) { //サンプル数から、画面の幅いっぱいに波形を表示するようにマッピング float x = map(i, 0, out.bufferSize(), 0, width); //画面の高さいっぱになるように、サンプルの値をマッピング float y = map(out.mix.get(i), 0, 1.0, 0, height/2); //値をプロット point(x, y); } } void mouseMoved() { float freq = map(mouseX, 0, width, 20, 1000); sine.setFreq(freq); float amp = map(mouseY, 0, height, 1, 0); sine.setAmp(amp); } void stop() { out.close(); minim.stop(); super.stop(); }
複数の音を鳴らす
- 2種類のsin波を生成し、同時に鳴らす
- 波形の表示を工夫してみる – 1つめのSin波のレベルをX軸に、2つめのSin波のレベルをY軸にして波形をプロットとするとどうなるか?
- リサージュ図形 – 互いに直交する二つの単振動を順序対として得られる点の軌跡が描く平面図形
import ddf.minim.*; import ddf.minim.signals.*; Minim minim; AudioOutput out; //2つのサイン波を定義 SineWave sine1, sine2; void setup() { size(600, 600); frameRate(30); smooth(); strokeWeight(2); minim = new Minim(this); //ステレオで出力 out = minim.getLineOut(Minim.STEREO); //1つめのサイン波 sine1 = new SineWave(440, 0.8, out.sampleRate()); sine1.portamento(200); //左から出力 sine1.setPan(-1); //オーディオ出力に追加 out.addSignal(sine1); //2つめのサイン波 sine2 = new SineWave(440, 0.8, out.sampleRate()); sine2.portamento(200); //右から出力 sine2.setPan(1); //オーディオ出力に追加 out.addSignal(sine2); background(0); } void draw() { //背景を黒にフェードさせる fill(0, 0, 0, 15); noStroke(); rect(0, 0, width, height); //色の設定 noFill(); stroke(0,255,0); //画面の中心へ原点を移動 translate(width/2, height/2); //波形を描画 for(int i = 0; i < out.bufferSize() - 1; i++) { //X座標を右チャンネンル、Y座標を左チャンネルにする point(out.right.get(i) * width/2, out.left.get(i) * height/2); } } void mouseMoved() { float freq1 = map(mouseX, 0, width, 20, 1000); sine1.setFreq(freq1); float freq2 = map(mouseY, 0, height, 20, 1000); sine2.setFreq(freq2); } void stop() { out.close(); minim.stop(); super.stop(); }
サウンドファイルの読み込み、再生
- サウンドファイルを読み込んで再生する
import ddf.minim.*; Minim minim; //サウンドファイルのプレーヤーを定義 AudioPlayer player; void setup() { size(800, 400, P3D); minim = new Minim(this); //サウンドファイルを読み込む player = minim.loadFile("anton.aif", 2048); //サウンドファイルを再生 player.play(); } void draw() { //波形を描く background(0); stroke(0,255,0); //左チャンネルを上半分に描く //y座標の原点を移動 translate(0, height/4); for(int i = 0; i < player.bufferSize() - 1; i++) { //サンプルの値を画面にあわせてマッピング float x = map(i, 0, player.bufferSize(), 0, width); float y = map(player.left.get(i), 0, 1, 0, height/4); point(x, y); } //右チャンネルを上半分に //y座標の原点を移動 translate(0, height/2); for(int i = 0; i < player.bufferSize() - 1; i++) { //サンプルの値を画面にあわせてマッピング float x = map(i, 0, player.bufferSize(), 0, width); float y = map(player.right.get(i), 0, 1, 0, height/4); point(x, y); } } //マウスボタンを押すとサンプルを再生 void mousePressed() { //サウンドを巻き戻し player.rewind(); //サウンドを再生する player.play(); } void stop() { player.close(); minim.stop(); super.stop(); }
ArduinoからProcessing+Minimをコントロールしてみる
センサーの値でSin波をコントロール
- 「複数の音を鳴らす」のサンプルを改造
- 2つのアナログ入力の値を、2つのSin波のそれぞれの周波数に対応させてみる
- リサージュ図形の表示の部分はそのまま
Arduino + ブレッドボード配線図
Arduino側コード
//アナログ入力の数を定義する #define NUM 2 //アナログ入力の値を格納する配列 int val[NUM]; void setup() { //シリアル通信の開始 Serial.begin(9600); } void loop() { //アナログ入力の数だけ繰り返し for(int i=0; i<NUM; i++){ //アナログ入力の値を読み込み val[i] = analogRead(i); } //Processingからの読み込み完了通知を待つ if(Serial.available() > 0){ //アナログ入力の数だけ繰り返し for(int i=0; i<NUM; i++){ //上位 8bit を送出 Serial.print(val[i] >> 8, BYTE); //下位 8bit を送出 Serial.print(val[i] & 255, BYTE); } //合図用データを読み込んでバッファ領域を空にする Serial.read(); } }
Processing側コード
import processing.opengl.*; import ddf.minim.*; import ddf.minim.signals.*; import processing.serial.*; Minim minim; AudioOutput out; SineWave sine1, sine2; //アナログ入力の数を指定 int NUM = 2; //Serialクラスのインスタンス Serial myPort; //Serialより読み込んだデータを格納する配列 int[] val = new int[NUM]; void setup() { size(600, 600, OPENGL); smooth(); strokeWeight(2); //ポートの名前を取得 String portName = Serial.list()[1]; //Serialクラスをインスタンス化 myPort = new Serial(this, portName, 9600); minim = new Minim(this); out = minim.getLineOut(Minim.STEREO); sine1 = new SineWave(440, 0.8, out.sampleRate()); sine1.portamento(200); sine1.setPan(-1); out.addSignal(sine1); sine2 = new SineWave(440, 0.8, out.sampleRate()); sine2.portamento(200); sine2.setPan(1); out.addSignal(sine2); background(0); } void draw() { fill(0, 0, 0, 15); noStroke(); rect(0, 0, width, height); noFill(); stroke(0,255,0); translate(width/2, height/2); for(int i = 0; i < out.bufferSize() - 1; i++) { float x = map(i, 0, out.bufferSize(), 0, width); point(out.right.get(i) * width/2, out.left.get(i) * height/2); } } //シリアル入力を検出した際に発生するイベント void serialEvent(Serial p) { //もし、バッファーにアナログ入力の数の2倍(上位8bitと下位8bit)あれば if(myPort.available() > NUM * 2 - 1) { //入力の数だけ繰り返し for(int i=0; i<NUM; i++) { //上位8bitを取得 int val_high = myPort.read(); //下位8bitを取得 int val_low = myPort.read(); //2つのbit列を合成 val[i] = ( val_high << 8 ) + val_low; //もし全ての入力を処理したら if(i == NUM-1) { //読み込み完了の合図を送る myPort.write(255); } } } //取得したシリアルの値を周波数にマッピング //サイン波1 float freq1 = map(val[0], 0, 1023, 20, 1000); sine1.setFreq(freq1); //サイン波2 float freq2 = map(val[1], 0, 1023, 20, 1000); sine2.setFreq(freq2); } //マウスが押されたら通信開始 void mousePressed() { //バッファ領域を空にする myPort.clear(); //合図用データを送る myPort.write(255); } void stop() { out.close(); minim.stop(); super.stop(); }
「植物シンセ」タッチセンサーでサウンドファイルを再生
まずはスイッチで実験
- タクトスイッチで簡単な回路を作成して実験する
Arduino+ブレッドボード回路図
Arduino側コード
//スイッチの入力ピンを定義 const int buttonPin = 2; //ボタンの状態(初期値:0) int buttonState = 0; void setup() { //シリアル通信開始 Serial.begin(9600); //スイッチの入力を定義 pinMode(buttonPin, INPUT); } void loop(){ //デジタル入力の状態を取得 buttonState = digitalRead(buttonPin); //もしスイッチが押されていたら if (buttonState == HIGH) { //シリアルから値255を出力 Serial.print(255, BYTE); } }
Processing側コード
import ddf.minim.*; import processing.serial.*; Minim minim; AudioPlayer player; Serial myPort; int val; void setup() { size(800, 400, P3D); //ポートの名前を取得 String portName = Serial.list()[1]; //Serialクラスをインスタンス化 myPort = new Serial(this, portName, 9600); minim = new Minim(this); player = minim.loadFile("anton.aif", 2048); } void draw() { //波形を描く background(0); stroke(0,255,0); for(int i = 0; i < player.bufferSize() - 1; i++) { float x = map(i, 0, player.bufferSize(), 0, width); point(x, height / 4 + player.left.get(i) * height/4); } for(int i = 0; i < player.bufferSize() - 1; i++) { float x = map(i, 0, player.bufferSize(), 0, width); point(x, height / 4 * 3 + player.right.get(i) * height/4); } } //シリアル入力を検出した際に発生するイベント void serialEvent(Serial p) { //ポートの状態を確認 if(myPort.available() > 1) { //シリアルからの入力値を取得 val = myPort.read(); } //もし値が0より大きく、まだ音を再生していなかったら if(val > 0 && player.isPlaying() == false){ //再生位置を巻き戻して、サウンドを再生 player.rewind(); player.play(); } } void stop() { player.close(); minim.stop(); super.stop(); }
植物にとりつけたタッチセンサーをスイッチにしてみる
- 矢坂先生の前回と前々回の講義を参考にする
- タッチセンサーでスイッチを切り替えるArduinoのプログラムを利用
Arduino+ブレッドボード回路図
- 8番、9番のデジタル入力にタッチセンサを接続
- 8番ピンの手前に1MΩの抵抗を入れる
Arduino側コード
#define POWER 8 #define METAL 9 int filter = 0; int counter = 0; void setup(){ Serial.begin(9600); pinMode(POWER,OUTPUT); pinMode(METAL,INPUT); } void loop() { counter = 0; digitalWrite(POWER, HIGH); while (digitalRead(METAL)!=HIGH){ counter++; } delay(1); digitalWrite(POWER, LOW); filter += (counter - filter) / 2; if(filter > 5){ Serial.print(255, BYTE); } }
Processing側コード
import ddf.minim.*; import processing.serial.*; Minim minim; AudioPlayer player; Serial myPort; int val; void setup() { size(800, 400, P3D); //ポートの名前を取得 String portName = Serial.list()[1]; //Serialクラスをインスタンス化 myPort = new Serial(this, portName, 9600); minim = new Minim(this); player = minim.loadFile("anton.aif", 2048); } void draw() { //波形を描く background(0); stroke(0,255,0); for(int i = 0; i < player.bufferSize() - 1; i++) { float x = map(i, 0, player.bufferSize(), 0, width); point(x, height / 4 + player.left.get(i) * height/4); } for(int i = 0; i < player.bufferSize() - 1; i++) { float x = map(i, 0, player.bufferSize(), 0, width); point(x, height / 4 * 3 + player.right.get(i) * height/4); } } //シリアル入力を検出した際に発生するイベント void serialEvent(Serial p) { //ポートの状態を確認 if(myPort.available() > 1) { //シリアルからの入力値を取得 val = myPort.read(); } //もし値が0より大きく、まだ音を再生していなかったら if(val > 0 && player.isPlaying() == false){ //再生位置を巻き戻して、サウンドを再生 player.rewind(); player.play(); } } void stop() { player.close(); minim.stop(); super.stop(); }
音をランダムに再生する (Processing側コードを変更)
import ddf.minim.*; import processing.serial.*; Minim minim; AudioPlayer player; Serial myPort; int val; String [] soundfile = new String[8]; void setup() { size(800, 400, P3D); soundfile[0] = "anton.aif"; soundfile[1] = "cello-f2.aif"; soundfile[2] = "cherokee.aif"; soundfile[3] = "drumLoop.aif"; soundfile[4] = "jongly.aif"; soundfile[5] = "rainstick.aif"; soundfile[6] = "sho0630.aif"; soundfile[7] = "vibes-a1.aif"; //ポートの名前を取得 String portName = Serial.list()[1]; //Serialクラスをインスタンス化 myPort = new Serial(this, portName, 9600); minim = new Minim(this); player = minim.loadFile(soundfile[int(random(7))], 2048); } void draw() { //波形を描く background(0); stroke(0,255,0); for(int i = 0; i < player.bufferSize() - 1; i++) { float x = map(i, 0, player.bufferSize(), 0, width); point(x, height / 4 + player.left.get(i) * height/4); } for(int i = 0; i < player.bufferSize() - 1; i++) { float x = map(i, 0, player.bufferSize(), 0, width); point(x, height / 4 * 3 + player.right.get(i) * height/4); } } //シリアル入力を検出した際に発生するイベント void serialEvent(Serial p) { //ポートの状態を確認 if(myPort.available() > 1) { //シリアルからの入力値を取得 val = myPort.read(); } //もし値が0より大きく、まだ音を再生していなかったら //if(val > 0 && player.isPlaying() == false){ if(val > 0){ //再生位置を巻き戻して、サウンドを再生 player = minim.loadFile(soundfile[int(random(7))], 2048); player.rewind(); player.play(); } } void stop() { player.close(); minim.stop(); super.stop(); }