6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Houdiniで物理演算ベースのシーケンサーを作る

6
Posted at

この記事はHoudiniアドベントカレンダー2025の9日目の記事です。

はじめに

普段3Dの形状を扱う自分にとって、3D空間の中でおきている「動き」とか「状態」をそのまま音にフィードバックしたいなという欲求がありました。頭の中でおぼろげにこうやったらできるのかなというイメージのままでいたのですが、最近XでKatsuhiro Chibaさんの下記の投稿を拝見し、「あ、これをやってみたい!」と火がつきました。

Advent Calendarのテーマとしても面白い内容かもということで、今回はHoudiniを物理演算ベースの音楽シーケンサーにするという試みを行ってみたいと思います。

具体的には、HoudiniのSOP Solverでボールが壁に跳ね返るシミュレーションを作り、その衝突のタイミングや強さを音に変換してみます。イメージとしては、Teenage Engineeringの「OP-1」というシンセサイザーに搭載されている 「Tombola(トンボラ)」 というシーケンサーの3D版です。多角形の枠の中でボールが跳ね回って、まるで福引のガラガラ抽選機のような、予測不能なリズムとメロディを奏でる仕組みを3次元で再現したら楽しいかもと思って始めました。

本来Houdiniはリアルタイムパフォーマンス用のツールではないのですが、SOPで細かいロジックが作れる強みはどのツールにも負けないと思っているので、そこを最大限活かせたら良いだろうなと思いました。

ただ、Houdini単体でリッチな音を出すのは難しいので、音源部分はAbleton Liveに任せることとします。音に関わる通信は、通常はMIDIを使うのだと思いますが、macOSのHoudiniだとリアルタイムのMIDI受信・送信がうまくいかなかったので(MIDIファイルは使えた)、今回はOSC(Open Sound Control)というプロトコルを使ってHoudiniからAbleton Liveへ衝突情報を送るワークフローを構築していきます。

記事の最後にプロジェクトファイルも用意していますので、この記事を見ていただいて興味を持っていただいた方は自分の環境でも試してみてください。

推奨環境

  • Houdini 21 Indie(Apprenticeでもたぶんできる)
  • Ableton Live 12 Suite (OSCが扱えれば別DAWでも可)
  • Scaler 3(適当に打ったキーを音楽理論に沿って整えてくれるVSTプラグイン)
  • 音源(DAW付属のもので大丈夫。自分はChromaphone 3というモーダルシンセを使った)

ワークフロー

  1. [Houdini] ボールのバウンスシミュレーションをSOP Solverで作る
  2. [Houdini] シミュレーション結果を可視化する
  3. [Houdini] バウンス時の情報をOSCで送信する
  4. [Ableton Live] Max for Liveを使いOSCをMIDIに変換する
  5. [Ableton Live] Scaler 3を使いMIDIノートを整える
  6. [Ableton Live] MIDIを音源を使って音を鳴らす

Houdiniではボールのバウンスシミュレーションをまず作り、そのシミュレーション結果を可視化しつつ、ボールがくるくるまわるガラガラ抽選機のような容れ物の壁に当たった時、その時のメロディ情報や強さをOSCでAbleton Liveに送ります。

Ableton Live側では、Max for Live (M4L) を使ってOSCデータをMIDIに変換します。ポイントは、その後にScaler 3というプラグインを通すことです。これによって、物理演算から生まれるランダムなノート情報を「音楽理論的に正しいコード進行」に整え、不協和音を防ぐことができます。

最終的な音源には、物理的な衝突のイメージに合わせて、物理モデリングシンセ(モーダルシンセ)の一つであるChromaphone 3を使用しました。でもピアノのような音源を使っても不協和音にならないので心地よく聴こえます。

Untitled (2).png

最終的に完成したのが、次の動画のようなものです。

プロジェクトファイル

Houdiniパート

2025-12-06_23-54-42.png

今回シミュレーションのコントロールに使いたいパラメータは、CONTROLLERという名前のNullノードに次のようにまとめています。

パラメータ名 範囲 説明
max_pt int 20 1-50 ボールの総数
rt_pt_num float 0.2 0.0-1.0 シミュレーションに使うボール数割合
base_note int 60 0-100 ベースのノート番号
note_range int 30 0-100 ランダムに選ばれるノートの範囲
friction float 1.0 0.0-1.0 空気抵抗率(1.0がなし)
bounce float 0.99 0.0-1.0 バウンス時の速度減衰率
min_speed_thresh float 5.0 0.0-10.0 低速判定のしきい値
min_speed_bounce_impact float 30.0 0.0-100.0 低速になったときに与えるインパクトの強さ
volume int 90 0-127 音量
rot_speed float 0.5 0.0-10.0 ボールの容れ物の回転速度
rad float 1.0 0.0-5.0 ボールの半径

大まかな流れとして、まずはSOPソルバーを使ってボールのバウンスシミュレーションを作り(Step 1)、そのシミュレーションを可視化し(Step 2)、最後にPythonを使ってOSCでバウンス(反射)時の情報を送信します(Step 3)。

Step 1. [Houdini] バウンスシミュレーションを作る

Step 1-1. ボールがバウンスする容れ物を作る

2025-12-07_00-21-12.png

ノード ラベル パラメータ名 説明
Circle -* Orientation YZ Plane YZ平面に多角形を作る
Uniform Scale 20 ボールを容れる容れ物の厚み
Divisions 7 七角形にする
PolyExtrude - Distance 20 容れ物の厚み
Output Side 側面をグループに入れる
PolyFill - Fill Mode Single Polygon 側面を作る
Primitive Wrangle faceid VEXpression コード#1 面に番号をつける
Match Size - - - 形状の中心を原点位置に移動する
Null CONTAINER - - -

*「-」はデフォルトの値の意味

まず厚みをもった七角形の容れ物を作ります。 多角形にしている理由は、ボールが当たった側面に応じて音色を変化させたいからです。七角形にすることで、最大7種類のコード(和音)のバリエーションが出せるようにします。

2025-12-08_03-34-06.png

そのための初期設定として、各面に番号をふっておきます。

コード#1

i@faceid = @primnum;

Step 1-2. ボールの位置を示す点群と容れ物の回転アニメーションを作る。

2025-12-07_00-22-58.png

ノード ラベル パラメータ名 説明
IsoOffset - Offset -ch("../CONTROLLER/rad") ボリュームをオフセットする
Uniform Sampling Divs 50 ボリュームの解像度を設定する
Scatter - Force Total Count ch("../CONTROLLER/max_pt") ボールの最大数を設定する
Point Wrangle init VEXpression コード#2 ボールの初期設定を行う
Point Relax - Max Iterations 100 ポイントリラックスの繰り返し回数
Point Radius Scale ch("../CONTROLLER/rad") ポイント間の最小距離を設定
Transform transform1 Rotate X $FF * ch("../CONTROLLER/rot_speed") 容れ物の回転アニメーションを作る
Null ROTATING_CONTAINER - - -

容れ物ができたら、その中に初期のボールをScatterノードで配置します。 点同士が近すぎると、シミュレーション開始時に衝突判定が暴発して吹き飛んでしまうため、Point Relaxノードを使って位置を調整しておきます。

各ポイントにシミュレーション用の初期設定もしておきましょう。

コード#2

v@v = (rand(@P) - set(0.5, 0.5, 0.5)) * 5; // ボールの初期速度を設定
f@melody = 0.0; // メロディの値を初期化
i@toneindex = -1; // コード種類のインデックスを初期化
i@prevtoneindex = -1; // 前フレームのコード種類のインデックスを初期化

またTransformノードで容れ物を回転させます。

Step 1-3. Solverを使ってバウンスシミュレーションを作る

2025-12-07_00-24-14.png

ノード ラベル パラメータ名 説明
Solver - - - サブネットワーク#1 参照
Delete - Group using 適用グループを指定
Operation Delete Non-Selected 指定のポイントだけ抽出
Entity Points ポイントタイプを指定
Null SIM_POINTS - - -

Solverノード内でバウンスのシミュレーションを行います。 シミュレーション後、使われなかったポイントはDeleteノードで取り除いておきます。

サブネットワーク#1

2025-12-07_00-25-22.png

ノード ラベル パラメータ名 説明
Point Wrangle using VEXpression コード#3 シミュレーションに用いるポイントを指定する
ptn ch("../../../../CONTROLLER/rt_pt_num") シミュレーションに用いるポイント数の割合
Normal - Add Normals to Primitives 面の法線アトリビュートを作る
Point Wrangle bounce Group using シミュレーションに使うポイントのフィルタリング
VEXpression コード#4 バウンスのシミュレーションを行う
bounce ch("../../../../CONTROLLER/bounce") 反射係数
bouncemult ch("../../../../CONTROLLER/min_speed_bounce_impact") 最小速度以下の時に追加する反射時の速度
rad ch("../../../../sphere1/scale") ボール半径
friction ch("../../../../CONTROLLER/friction") 空気抵抗係数
min_speed ch("../../../../CONTROLLER/min_speed_thresh") 最初速度閾値
Null SIM_POINTS - - -

ソルバーネットワークの中に入り、まず1つ目のPoint Wrangleノードでシミュレーションに使うポイントの数をパラメータで設定し、usingグループに入れます。これでリアルタイムにシミュレーションするボールの数を調整できるようになります。

コード#3

float ptn = chf("ptn"); // パラメータから閾値となる数値を取得

if(@ptnum <= ptn * npoints(0)){ // 全ポイント数に対して割合をかけ、その範囲内か判定
    setpointgroup(0, "using", @ptnum, 1); // 範囲内なら "using" グループに追加
}else{
    setpointgroup(0, "using", @ptnum, 0); // 範囲外なら "using" グループから外す
}

次にバウンスシミュレーションのメインとなるアルゴリズムをもう一つのPoint Wrangleノードに書きます。 今回はシンプルなバウンス処理をVEXで実装しています。処理の流れは以下の通りです。

  1. 重力の適用: まず重力加速度分だけ速度を落とします。(v = v - g * t
  2. 壁(容れ物)情報の取得: xyzdist() を使い、一番近い壁の距離・位置・法線を取得します。
  3. 他ボール情報の取得: nearpoints() を使い、近くにある別のボールとの距離などを取得します。
  4. 衝突(めり込み)判定: 距離と方向から「現在めり込んでいるか」または「これから衝突しそうか」を判定します。
  5. 位置の補正: 衝突していた場合、球の中心をサーフェスの外側に押し出してめり込みを解消します。(P = mpos - n * r
  6. 反射と減衰: 壁や相手の法線を使って速度ベクトルを反射させ、反発係数で勢いを減衰させます。(v = v * bounce
  7. 低速時の微調整: 速度が遅くなりすぎた時は、法線方向にランダムなインパルスを足して少し弾かせ、動きが止まらないようにします。
  8. アトリビュート更新: 接触フラグ(contact)や、音階選択用のインデックスなどを更新します。
  9. グラデーション減衰: 接触していないフレームでは、発光表現用のグラデーション値(grad)を徐々に減らします。
  10. 摩擦と位置更新: 空気抵抗(摩擦)で速度を少し落とし、最終的な速度から今のフレームの位置を決定します。(P = P + v * t
  11. 後処理: 可視化やOSC送信のために、速度の大きさを0〜1に正規化して speed に、法線を @N として保存します。

コード#4

float rad = chf("rad"); // 衝突半径

float gravity = 9.8; // 重力加速度
float bounce = chf("bounce"); // 反発係数
float friction = chf("friction"); // 摩擦係数

v@v.y -= gravity * @TimeInc; // 重力でY方向の速度を減少させる

// 入力1の形状(ボールの容れ物)との衝突判定のための情報取得
int prim;
vector uv;
float d = xyzdist(1, @P, prim, uv);    
vector norm = primuv(1, "N", prim, uv);
vector mpos = primuv(1, "P", prim, uv);
vector dir = normalize(@P - mpos);

// 他のボールとの衝突判定情報を取得
int npts[] = nearpoints(0, "using", @P, rad * 2, 2);
if(len(npts) == 2){ // 自分以外の近傍ポイントが見つかった場合
    int npt = npts[1]; // もう一方のポイント番号を取得
    vector npos = point(0, "P", npt);
    d = (distance(@P, npos) - rad); // 2点間距離から半径分を引いた値を距離として使用
    mpos = npos + normalize(@P - npos) * rad; // 半径分だけ離れた位置を接触位置として再計算
    norm = normalize(npos - @P); // 法線を2点の方向から再計算
    dir = normalize(@P - mpos);  // 方向ベクトルを更新
}

// 衝突条件
if(d < rad - 0.1 || (dot(dir, norm) > 0.0 && d > 0.1)){
    v@P = mpos - norm * rad; // 半径分だけ外側の位置に補正
    v@v = reflect(v@v, norm); // 法線で速度ベクトルを反射
    v@v *= bounce; // 反発係数をかける

    // 最低速度より遅い場合は速度を上げる
    if(length(v@v) < chf("min_speed")){ 
        v@v += normalize(-norm) * fit01(rand(@P), 2.0, chf("bouncemult"));
    }
    
    i@contact = 1; // 接触フラグON
    f@grad = 1.0;  // グラデーション用の値をリセット
    f@melody = rand(@P); // 位置ベースでメロディ値を更新
    i@contactprim = prim; // 接触した面の番号を記録
    v@contactuv = uv; // 接触した面上のUVを記録
    
    if(inprimgroup(1, "extrudeSide", prim) == 1){ // 容れ物の側面かどうかチェック
        int faceid = prim(1, "faceid", prim);
        i@prevtoneindex = i@toneindex; // 以前のコード種類のインデックスを保存
        i@toneindex = faceid; // 現在のコード種類のインデックスを面の番号で更新
    }
}else{
    i@contact = 0; // 接触フラグOFF
    f@grad -= 0.05; // グラデーション値を減衰
    f@grad = max(f@grad, 0.0); // 0未満にならないよう制限
    i@prevtoneindex = i@toneindex; // 以前のコード種類のインデックスを更新
}

v@v *= friction; // 速度に空気の摩擦を適用
v@P += v@v * @TimeInc; // 速度から位置を更新

f@speed = fit(length(v@v), 0, 20.0, 0.0, 1.0); // 速度情報を正規化する
@N = norm; // 法線をポイント法線として設定

これにより、ボールが壁や他のボールに接触したとき、反射するようになります。今回は衝突時の情報をOSCで送りたいので、メロディの値、衝突した面の番号、速度などをポイントアトリビュートとして格納しておくのがポイントです。

Step 2. [Houdini] シミュレーション結果を可視化する

Step 2-1. ボールを可視化する

2025-12-07_00-27-45.png

ノード ラベル パラメータ名 説明
Sphere - Uniform Scale ch("../CONTROLLER/rad") ボールの半径を設定
Frequency 3 ボールの解像度を設定
Pack - - - 描画軽量化のためパックする
Point Wrangle color VEXpression コード#5 容れ物との衝突情報に応じて色をつける
Copy to Points - - - ボールをポイントにコピーする
Material - Material /mat/principledshader1 ボールにマテリアルを設定する

次に、結果をビジュアルとして表現します。 ボールが側面に衝突してからの時間(gradアトリビュート)に応じて、ボールが発光するように色をつけます。これによって、ボールが衝突した瞬間ぱっと光り、徐々に暗くなるような表現になります。

コード#5

vector col1 = set(0,0,0); // 黒色(開始色)
vector col2 = hsvtorgb(i@toneindex / 7.0, 1.0, 1.0); // toneindex から色相を計算してRGBに変換
v@Cd = lerp(col1, col2, f@grad); // grad 値に応じて黒→色へ補間

f@speed = length(v@v); // 速度ベクトルの長さを speed として保存

Step 2-2. 容れ物を可視化する

2025-12-07_00-29-07.png

ノード ラベル パラメータ名 説明
Object Merge CONTAINER1 Object 1 ../CONTAINER 容れ物の形状を取得する
Unique Points - - - 面を分離する
Delete - Group extrudeSide 側面グループを指定する
Operation Delete Non-Selected 指定したグループ以外を削除する
Transform transform2 Rotate Y ch("../transform1/ry") 容れ物形状を回転する
Object Merge SIM_POINTS1 Object 1 ../SIM_POINTS シミュレーション結果を取得する
Sort - Point Sort By Attribute アトリビュートに応じてポイントを並び替える
Attribute grad グラデーションアトリビュートに応じて並び替える
Primitive Wrangle alpha_col VEXpression コード#6 衝突情報に応じて面に色をつける
Attribute Promote - Original Name Cd 色のアトリビュートをプロモートする
Original Class Primitive プリミティブのアトリビュートをポイントにプロモートする
Convert Line - - - 面を曲線に変換する
Transform transform3 Rotate Y ch("../transform1/ry") 曲線を回転する
Merge - - - 回転するコンタクト面と曲線をまとめる
Material - Material /mat/principledshader2 透明度のあるマテリアルを設定する

次に容れ物のアウトラインと、衝突した面だけを可視化します。 こちらもボール同様、gradアトリビュートを使って、ボールの当たった面がぽわんと光るような表現にします。

コード#6

int pt = findattribval(1, "point", "toneindex", i@faceid, 0); // 入力1から、toneindex が faceid と一致するポイントを検索
vector col1 = set(0,0,0);
vector col2 = hsvtorgb(i@faceid / 7.0, 1.0, 1.0); // faceid を色相としてRGBに変換

float grad = 0; // 初期のグラデーション値

// 対応するポイントが見つかった場合グラデーションの値を取得
if(pt >= 0){ 
    grad = point(1, "grad", pt);
}

v@Cd = lerp(col1, col2, grad); // grad に応じて色を補間

Step 2-3. 衝突エフェクトを作る

2025-12-07_00-35-14.png

ノード ラベル パラメータ名 説明
Circle - Divisions 12 円の解像度を設定
Arc Type Open Arc 円を曲線にする
Pack - - - 描画軽量化のためパックする
Object Merge SIM_POINTS2 Object 1 ../SIM_POINTS シミュレーションの結果を取得する
Object Merge ROTATING_CONTAINER2 Object 1 ../ROTATING_CONTAINER 回転する容れ物を取得する
Point Wrangle contact_circle VEXpression コード#7 容れ物に衝突した位置と法線を設定
maxscale ch("../CONTROLLER/rad")*2 衝突エフェクトの最大サイズの設定
Color - - - 色をデフォルトの白に設定
Copy to Points - - - 円の曲線を衝突の位置にコピー
Material - Material /mat/principledshader1 マテリアル設定する

衝突の瞬間に広がる波紋のようなエフェクトを作ります。 gradアトリビュートに応じて円のサイズを広げつつ、法線方向に配置します。

コード#7

v@P = v@contactpos; // 接触位置からポイントを配置
f@pscale = lerp(0.0, fit01(pow(f@speed,2), 1.0, chf("maxscale")), pow(f@grad, 2)); // speed と grad に基づいて pscale を計算

vector cpos = primuv(1, "P", i@contactprim, v@contactuv); // 接触していた面上の位置を取得
vector cnorm = primuv(1, "N", i@contactprim, v@contactuv); // 接触していた面の法線を取得

@P = cpos - cnorm * 0.05; // 法線方向に少し押し出して配置
@N = cnorm; // 法線をポイントの法線として設定

// pscale が極小のときポイントを削除
if(f@pscale < 0.0001){ 
    removepoint(0, @ptnum);
}

Step 3. [Houdini] バウンス情報をOSCで送信する

Step 3-1. Pythonを使いOSCでバウンス情報を送信する

2025-12-07_00-36-22.png

ノード ラベル パラメータ名 説明
Object Merge SIM_POINTS3 Object 1 ../SIM_POINTS シミュレーションの結果を取得
Delete - Group @contact==1 衝突したポイントを指定
Operation Delete Non-Selected 指定したポイントだけ残す
Entity Points ポイントに設定
Python osc Python Code コード#8 OSCで衝突したポイントの情報を送信

このステップが今回のキモで、バウンスシミュレーションの衝突情報を、Pythonを使ってOSCで送信します。 外部の便利なライブラリは使わず、Houdini標準のPythonモジュールだけで実装しています。

[送信したいデータ]

  1. コード変更のトリガー: ボールが容れ物の新しい面に当たった瞬間、その面に応じたコード(和音)に変更する信号を送ります(黒鍵盤のMIDIノートとして送信)。
  2. ノート情報: ボールが持っている「メロディ値」と、衝突の「強さ(速度)」を送ります(白鍵盤のMIDIノートとして送信)。

このデータを受け取ったAbleton Live側で、Scaler 3が「指定されたコード」の中で「一番近いキー」を選んで鳴らしてくれる、という仕組みです。

コード#8

node = hou.pwd()
geo = node.geometry()

import socket
import struct
import math
import random

# --- 設定 ---
# OSC送信用のIP・ポート・アドレスなど基本設定
IP = "127.0.0.1"
PORT = 9000
ADDRESS = "/h_signal"

# --- 送信 ---
# Houdiniジオメトリのポイント取得、使用するスケール(音階)・トーンの定義
pts = geo.points()
scale_intervals = [0, 2, 4, 5, 7, 9, 11]
tones = [49, 51, 54, 56, 58, 61, 63]

# --- OSCメッセージの構築 (バイナリパック) ---
# OSCの仕様に合わせ、アドレス文字列と型タグを4バイト境界に揃えるパディングを準備
addr_bytes = ADDRESS.encode('utf-8')
addr_pad = b'\x00' * (4 - (len(addr_bytes) % 4))

# 型タグ(float 2つ)も同様にOSC形式にパディングして準備
type_tag = b',ff'
type_pad = b'\x00' * (4 - (len(type_tag) % 4))

# --- 各ポイントの情報を読み取り、OSCとして送信 ---
# 各ポイントごとに、接触判定やトーン変化を条件にメッセージを送信
for pt in pts:
    num = pt.number()
    melody = pt.attribValue("melody")
    contact = pt.attribValue("contact")
    prev_tone_index = pt.attribValue("prevtoneindex")
    tone_index = pt.attribValue("toneindex")
    speed = math.floor(pt.attribValue("speed") * node.parm("volume").eval())

    # 接触した瞬間にトーンメッセージを送る(トリガー)
    if contact == 1:
        if prev_tone_index != tone_index and tone_index < len(tones):
            tone = tones[tone_index]
            c_val_bytes = struct.pack('>ff', tone, 1)
            c_message = addr_bytes + addr_pad + type_tag + type_pad + c_val_bytes

            # UDP送信(失敗しても落ちないように例外処理)
            c_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            try:
                c_sock.sendto(c_message, (IP, PORT))
            except:
                pass
            finally:
                c_sock.close()

        # メロディ値から音階を計算し、MIDIノート番号を決定
        root_note = node.parm("base_note").eval()
        index = root_note + math.floor(melody * node.parm("note_range").eval())
        octave = index // 12
        note_idx = index % len(scale_intervals)
        midi_value = (octave * 12) + scale_intervals[note_idx]

        # 音階と速度をOSCメッセージにパックして送信
        val_bytes = struct.pack('>ff', midi_value, speed)
        message = addr_bytes + addr_pad + type_tag + type_pad + val_bytes

        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        try:
            sock.sendto(message, (IP, PORT))
        except:
            pass
        finally:
            sock.close()

Step 3-2. すべての可視化メッシュとPythonノードを1つにまとめる

2025-12-07_00-37-20.png

ノード ラベル パラメータ名 説明
Merge - - - すべての可視化した形状とPythonノードをまとめる
Null FINAL - - -

最後に、可視化したモデルとOSC送信をするPythonノードをMergeノードでまとめます。 フレーム数を多めに設定して再生し、シミュレーションが動き、Pythonノードがエラーを吐いていなければHoudini側の準備は完了です。

Ableton Liveパート

Step 4. [Ableton Live + M4L] OSCデータをMIDIデータに変換する

次は受け取り側です。Ableton Live Suiteを使って新しくプロジェクトを作りまずはMax for Live(Ableton Live Suiteに付属)を使って、シンプルな受信デバイスを作ります。テンプレートとして Max for Live > Max MIDI Effect をトラック1に追加して編集します。

2025-12-07_00-44-19.png

パッチはスクリーンショットのように組みます。 Houdiniと同じ9000番ポートから受け取ったOSCメッセージを、MIDIのノート情報に変換して出力します。

2025-12-07_00-40-29.png

この時点で適当な音源(E-Pianoなど)をトラックに挿してHoudiniを再生してみましょう。音が鳴れば通信成功です。

2025-12-07_00-48-00.png

Step 5. [Ableton Live + Scaler 3] MIDIデータのノートを整える

現状、通信が成功しても、このままではボールがぶつかるたびにランダムなキーが鳴るため、ただの不協和音(ノイズ)になってしまっているはずです。

ここで今回の影の主役であるVSTプラグインScaler 3を使います。Scaler 3は音楽理論に則ったコード進行をある程度自動で作れたりする、音楽素養のないような自分にとってはとってもありがたいツールです。このツールを使うことで、物理シミュレーションが生み出すカオスなタイミングはそのまま活かしつつ、音程だけは「音楽的に正しいルール」に強制的に従わせることができるようになります。

もちろんHoudini側で完璧な音楽理論を構築することも可能ですが、今回は専用のプラグインに任せることで、「適当にシミュレーションを回しても、なぜか心地よい音楽になる」という魔法のような状態を簡単に作ります(シミュレーションは愚直に1から書くくせに)。

まずは先に追加したMIDI楽器を削除して、Max for Liveのデバイスの右に Plug-ins > Scaler Music > Scaler 3 をドラッグして追加します。

2025-12-07_00-51-22.png

まずはScaler 3をリアルタイムなツールとして使うための設定を行います。設定画面から、Sync to DawをOffにしておきます。

2025-12-08_19-15-37.png
2025-12-08_19-16-09.png

次に、多角形の辺の数(今回は7つ)分のコードを登録します。これらが、ボールが壁に当たった時に切り替わるコードとなります。

2025-12-08_18-41-45.png

その上でロックボタンをクリックして、特定の黒鍵盤(Houdiniから送られてくるtones)でコードが切り替わるように設定します。

2025-12-08_18-44-11.png

そしてKey LocksオプションをオンにしてChord Notesに設定します。

この設定により、どんなキーが入力されても、「現在鳴っているコードの構成音」に強制的に補正されます。これにより、ボールがどれだけランダムに跳ね回っても、絶対に音が濁らないシステムが完成します。

2025-12-08_18-55-50.png

Step 6. [Ableton Live + 音源] MIDIの音を鳴らす

最後のステップとして、Scaler 3で整えられたMIDI信号を、別のトラックに送って好きなシンセで鳴らします。

トラック2を作成し、MIDI Fromの設定でトラック1のScaler 3からの出力を受け取るようにします。

2025-12-07_00-54-05.png

音源には、ガラス玉のような音を出したかったので、モーダルシンセのChromaphone 3のGlassyというプリセットを使ってみました。ここは各々好きな音源を使ってください。

2025-12-07_00-57-42.png

これで完成です。Houdiniのタイムラインを再生すると、ボールの衝突に合わせてアンビエントな音楽が生成されるはずです。

プロジェクトファイルの保管場所

gravity-sequencer-houdini-abletonlive

おわりに

とりあえず作ってみたかったものはできましたが、やっているうちに色々な欲が出てきました。

例えばChromaphone 3の物理パラメータもHoudiniの値でコントロールしてみたいとか、2Dのシーケンサーで個人的に好きなOrcaの3D版をHoudiniでできないかなとか、HoudiniにDAWからの情報をフィードバックしてシミュレーションに影響を与えてみるとか...(Synplant 2という遺伝的アルゴリズムを使ったシンセサイザーと組み合わせても面白そう)。

あと、そもそもHoudiniじゃなくて良くない!?とは自分でも思います。元々がリアルタイムに適したソフトウェアではないので、UnityやTouchDesignerでやったほうがビジュアルはより派手なものが作れる気はします。

でも、SOPで組んだロジックがそのまま音になるという実験はぜひやってみたかったというのがあり、形にはなってよかったです。Houdiniはやっぱり楽しい...

2025-12-08_19-34-34.png
Chromaphone 3のパラメータをマスターしたい。

Orcaのシーケンサーはずっとみていられる。

変なシンセ

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?