p5.jsは、その設計思想として、構造をあまり複雑にせず創造的な部分に集中できるよう焦点を絞っています。グラフィクスとモーションを作り、マウスやキーボードといった汎用的な入力デバイスに反応する機能が基本です。しかし、それ以上のことを行うために、p5.jsではライブラリー (Libraries) というものが用意されています。ライブラリーとは汎用性の高い複数のプログラムを、再利用可能な形でひとまとまりにしたものです。p5.jsでも用途に応じて様々なライブラリがまとめられ、入手できるようになっています。
今回はp5.jsのライブラリーを使う第1弾として、p5.domを使用します。このライブラリーによって、p5.jsのcanvasの枠を越えて、HTML 5のDOMに直接アクセスすることが可能となります。例えば、Webブラウザで用意されている、ボタンやチェックボックス、スライダーなどのGUIのパーツを利用したり、Webカメラやマイクなどのデバイスへのアクセスしてインタラクションすることが出来るようになります。
スライド資料
サンプルプログラム
今回は、Googleで開発された転移学習のモデルを簡単に学習し活用することができる Teachable Machine 2.0 を使用します。Teachable Machineを活用すると、とても分かりやすいユーザインターフェイスで転移学習のための学習を行うことが可能で、さらに学習させたモデルのデータを書き出すことが可能です。この書き出したモデルは、簡単にml5.js + p5.jsのスケッチに埋め込むことが可能です。Teachable Machine + ml5.jsの組合せで転移学習を活用したコンテンツを作成してみましょう。
スライド資料
サンプルプログラム
TidalCyclesのインストールが (ようやく) 完了したので、いよいよ今回からTidalCyclesを用いたライブコーディングを始めていきます!
TidalCyclesはHaskellというプログラミング言語のライブラリーです。Haskellは「関数型プログラミング言語」に分類されるプログラミング言語で、これまで習ってきたProcessing (JAVA) やJavaScript、C++などとはその言語のパラダイムが大きく異なります。しかし、TidalCyclesを使用する目的に限定すれば、Haskellの言語自体を詳しく習得する必要はありません。あくまでパターンを生成する関数の使用方法に精通すれば良いのです。
今回は、TidalCyclesでのパターン生成の基本をステップバイステップで解説していきます。
スライド資料
事前準備
必ずインストールが必要な項目
Tidalをインストールする前に、以下が最初にインストールされていることを確認してください。
オプション
以下はオプションですがSuperDirtでシンセサイザーを利用する際に必要なので入れておくと良いでしょう。
TidalCyclesをインストールする
コマンドプロンプト(またはPowerShell)を開き、次の2つのコマンドを入力して実行します。
cabal update
cabal install tidal
以前にTidalCyclesをインストールしたことがない場合は、cabal install tidal
ステップに時間がかかる場合があります。
ネットワークライブラリでエラーが発生した場合は、Haskellインストールで何かを修正する必要があります。ここの手順を参照してください。
SuperDirtをインストールする
SuperColliderを起動し、エディターウィンドウで次のコード行を貼り付けます。
Quarks.checkForUpdates({Quarks.install("SuperDirt", "v1.1.3"); thisProcess.recompile()})
コードをクリックしてコードを実行し、カーソルがこの行にあることを確認してから、Controlキーを押しながらEnterキーを押します。
インストールにはしばらく時間がかかります。インストールが完了すると以下のメッセージが出力されます。
Installing SuperDirt
Installing Vowel
Vowel installed
Installing Dirt-Samples
Dirt-Samples installed
SuperDirt installed
compiling class library...
...
(then some blah blah, and finally, something like:)
...
*** Welcome to SuperCollider 3.10.0. *** For help press Ctrl-D.
VisualStudio拡張機能をインストールする
以下のページからTidalCycles for VSCodeをインストールします。「Install」ボタンを押すとVSCodeが起動してインストールされます。
インストールをテストする
チュートリアルのセクションで、TidalCyclesを試してみましょう!
事前準備
必ずインストールが必要な項目
Tidalをインストールする前に、以下のアプリケーションやプログラミング言語をインストールします。
オプション
以下はオプションですがSuperDirtでシンセサイザーを利用する際に必要なので入れておくと良いでしょう。
TidalCyclesをインストールする
ターミナルウィンドウ (アプリケーション > ユーティリティー > ターミナル.app) で、次の行を貼り付けます。
macOS 10.14またはそれ以前のバージョンのmacOS (OS X)を利用している場合は、以下の操作で.bashrcを変更します。
echo '. $HOME/.ghcup/env' >> "$HOME/.bashrc"
macOS10.15 Catalinaを使用している場合は以下の操作で.zshrcを変更します。
echo '. $HOME/.ghcup/env' >> "$HOME/.zshrc"
まずcabalを使用してパッケージディレクトリを更新して、その後でTidalCyclesライブラリをインストールします。TidalCyclesライブラリを最新バージョンに更新したいときも、毎回この2つのコマンドを実行します。
cabal update
cabal install tidal --lib
以前にTidalCyclesをインストールしたことがない場合は、cabal install tidal --lib
ステップに時間がかかる場合があります。コマンド出力の最後に、Installed tidal-x.x.x
(xxxは最新バージョン番号です)と表示されます。
SuperDirtをインストールする
SuperColliderを起動し、エディターウィンドウで次のコード行を貼り付けます。
Quarks.checkForUpdates({Quarks.install("SuperDirt", "v1.1.3"); thisProcess.recompile()})
コードをクリックしてコードを実行し、カーソルがこの行にあることを確認してから、Shiftキーを押しながらEnterキーを押します。
インストールにはしばらく時間がかかります。インストールが完了すると以下のメッセージが出力されます。
Installing SuperDirt
Installing Vowel
Vowel installed
Installing Dirt-Samples
Dirt-Samples installed
SuperDirt installed
compiling class library...
...
(then some blah blah, and finally, something like:)
...
*** Welcome to SuperCollider 3.10.0. *** For help press Ctrl-D.
VS Code Extensionをインストールする
VS Codeを起動し、拡張機能マーケットプレイスを検索してTidalCycles拡張機能をインストールします。ここからの指示に従って、正しく使用する方法を確認できます。
インストールをテストする
チュートリアルのセクションで、TidalCyclesを試してみましょう!
この講義では、ここまで主に2次元平面上での描画による表現を扱ってきました。今回は2次元での表現からさらに(文字通り)次元を越えて、3次元空間での表現について考えていきたいと思います。
openFrameworksで3Dグラフィクスを扱う手法はこれまでとさほど変化はありません。なぜなら、openFrameworksの描画の基本はOpenGLで行っています。OpenGLはそもそも3次元のグラフィクスの描画のために開発されたライブラリであり、最初から3次元空間をとり扱うための様々な機能が備わっています。
しかし、3Dの物体を扱うには、これまでとは違った様々な要素が加わります。カメラ(視点)、ライティング、光と影(シェーディング)、奥行の重なりなどといった2次元の平面には無かった様々な技術や概念の理解が必要となります。
スライド資料
サンプルプログラム
Addon(アドオン)とは、openFrameworksに機能を拡張するためのライブラリーやフレームワークです。processingのLibrariesのように、openFrameworks単体ではできなかった様々な機能を実現することが可能となります。Addonは、oF本体の開発者以外でも独自に開発して追加することが可能であり、繰り返し用いる機能や、CやC++で記述された既存のライブラリーをopenFrameworksに統合することが可能となります。
今回はAddonの導入を、まずofxGuiというプロジェクトにGUIを追加するアドオンで行います。ofxGuiを使用することで、例えば周囲の環境が異なる場所でインスタレーションを設置する時など、すぐにパラメータを最適な状態に調整して保存することができ、とても便利です。
スライド資料
サンプルファイル
p5.jsのp5.Imageクラスは、外部のビットマップ画像(Jpeg, GIF、PNGなど)をデータとしてプログラムに読み込むことができます。読み込んだ画像は単に画面に表示するだけでなく、色や明度やサイズを変更して表示することができます。さらには、画像に含まれる全てのピクセルの色情報を読み取り配列に格納することが可能です。そのデータをもとに別の画像を再生成することが可能となり、読み込んだ画像データの色情報をもとにした多彩な表現が可能となります。
今回はp5.jsに画像を読み込んで、分析再合成することで、様々な表現の可能性について探っていきます。
次回までの課題
課題: p5.jsに読み込んだ画像ファイルのデータで表現する!
- p5.jsに読み込んだ画像ファイルの情報から、新たなイメージを生成する
- 読み込む画像は自由
- sfc-sfsのレポートシステムで提出
締切は次回の授業の前日 (11/28) まで!!
スライド資料
※ 手動インストールの方法は以下を参照してください。
この演習の後半はより本格的なライブコーディングのパフォーマンスを実践します。そのために、これまで使ってきたSonic PiからステップアップしてTidalCyclesというより本格的なライブコーディングの環境を使用していきます。
今回はTidalCyclesのインストールを行います。TidalCyclesはSonic Piと違って全ての開発環境が統合されたアプリケーションは存在しません。プログラミング言語、ライブラリー、音響合成エンジン、テキストエディターをそれぞれ別個に入れていく必要があります。この授業では以下の環境で使用していく予定です。
- SuperCollider (and SuperDirt) : 音響合成に使用
- Atom (and the TidalCycles plugin) : テキストエディターとプラグイン
- ghci (ghcup) : プログラミング言語Haskellのインタープリタ
- The Tidal library : TidalCycles本体 (Haskellのライブラリー)
インストールには、自動インストールと手動インストールの2種類の方法があります。まず自動インストールを試してみて、うまくいかないようであれば手動インストールをしましょう。インストールの方法は下記の資料を参照してください。
Proxy環境下での事前準備
前橋工科大学のネットワークはProxy環境下にあります。そのため自動インストールする前に以下の事前準備が必要です。
macOS
.bashrcに以下の記述を追加
export http_proxy=http://172.20.1.24:8080
export https_proxy=$http_proxy
export HTTP_PROXY=$http_proxy
export HTTPS_PROXY=$http_proxy
さらにターミナルで以下のコマンドを実行
$ export ALL_PROXY=http://172.20.1.24:8080
Windows
以下の記事を参考に、Proxy環境下用にChocolateyをインストールする。
自動インストール
手動インストール
前回は、転移学習 (Transfer Learning) の応用として、画像の特徴を抽出してその結果を別の画像に適用することで回帰分析 (Regression) を行うコードを作成しました。今回はこの仕組みをより実践的に活用して、ゲームのコントローラーとして使用します。今回は簡単なテニスゲームのラケットの左右に移動する動きをジェスチャーにより行うゲームの作成を通して、機械学習の応用について実践します。
スライド資料
サンプルプログラム
let featureExtractor, regressor, loss, predictResult=0.5;
let video;
let status = 'loading...';
let slider, addImageButton, trainButton, predictButton;
let mode = 0; //ゲームモード 0:学習中、1:ゲームプレイ
let game; //ゲーム本体
function setup() {
createCanvas(windowWidth, windowHeight);
//新規にゲームを生成
game = new TennisGame();
//解析関係の設定
video = createCapture(VIDEO);
video.size(320, 240);
video.hide();
featureExtractor = ml5.featureExtractor('MobileNet', modelReady);
regressor = featureExtractor.regression(video);
slider = createSlider(0.0, 1.0, 0.5, 0.01);
slider.position(20, 40);
addImageButton = createButton('add image');
addImageButton.position(slider.x + slider.width + 20, 40);
addImageButton.mousePressed(addImage);
trainButton = createButton('train');
trainButton.position(addImageButton.x + addImageButton.width + 5, 40);
trainButton.mousePressed(train);
predictButton = createButton('start predict');
predictButton.position(trainButton.x + trainButton.width + 5, 40);
predictButton.mousePressed(predict);
}
function draw(){
background(0);
image(video, 0, 0, width, height);
fill(0, 63);
noStroke();
rectMode(CORNER);
rect(0, 0, 400, 80);
fill(255);
textSize(12);
textAlign(LEFT);
text(status, 20, 20);
//もしゲームモードだったら
if(mode == 1){
//ゲームの更新と描画
game.update();
game.draw();
//分析の結果でバーを動かす
let speed = map(predictResult, 0.0, 1.0, -10, 10);
game.bar.location.x += speed;
}
}
function addImage(){
regressor.addImage(slider.value());
}
function train(){
regressor.train(function(lossValue) {
if (lossValue) {
loss = lossValue;
status = 'Loss: ' + loss;
} else {
status = 'Done Training! Final Loss: ' + loss;
}
});
}
function predict(){
regressor.predict(gotResults);
mode = 1;
}
function modelReady() {
status = 'MobileNet Loaded!';
}
function gotResults(err, result) {
if (err) {
console.error(err);
status = err;
}
if (result && result.value) {
predictResult = result.value;
predict();
}
}
//----- ゲームに関するクラス : TennisGame、Ball、Bar ----//
class TennisGame {
constructor(){
this.ball = new Ball();
this.bar = new Bar();
this.score = 0;
}
update(){
this.ball.update();
// バー(コントローラー)でバウンド
if (this.ball.location.y > height - 50) {
if (abs(this.bar.location.x - this.ball.location.x) < 150) {
this.ball.velocity.y *= -1.0;
this.ball.velocity.mult(1.2);
this.score += 100;
} else {
this.initGame();
}
}
}
draw(){
fill(255);
this.ball.draw();
this.bar.draw();
textSize(40);
textAlign(RIGHT);
text(this.score, width-20, 60);
}
initGame() {
// リセット
this.score = 0;
this.ball.location.set(width / 2, 40);
this.ball.velocity = createVector(random(-2, 2), 2);
this.bar.location.x = width / 2;
}
}
class Ball {
constructor() {
this.location = createVector(width / 2, 40);
this.velocity = createVector(random(-2, 2), 2);
}
update() {
this.location.add(this.velocity);
// 壁でバウンド
if (this.location.x < 0 || this.location.x > width) {
this.velocity.x *= -1;
}
if (this.location.y < 0) {
this.velocity.y *= -1;
}
}
draw() {
fill(255);
noStroke();
ellipse(this.location.x, this.location.y, 20);
}
}
class Bar {
constructor() {
this.location = createVector(width / 2, height - 50);
this.speed = 10;
}
draw() {
fill(255);
noStroke();
rectMode(CENTER);
this.location.x = constrain(this.location.x, 0, width);
rect(this.location.x, this.location.y, 300, 20);
}
moveLeft(){
this.location.x -= this.speed;
}
moveRight(){
this.location.x += this.speed;
}
}