1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VJ などで利用可能な汎用的なビートジェネレーターを作ってみた

Last updated at Posted at 2025-12-10

はじめに

この記事は TouchDesigner Advent Calendar 2025 🎅 11日目の記事です

2025年10月からTouchDesignerを使ったVJとして活動を開始しはじめためろちだです。
TouchDesigner どころか、VJの経験もまだまだ浅いです。
個人的に、TouchDesigner上に汎用的な VJ アプリケーションを構築する上で
疎結合な単一責務なコンポーネントをたくさん実装し、それの組み合わせで
VJアプリケーションを実現したいと考えています。
その上で、今回は汎用的なビートジェネレーターを実装してみました。

できたもの

Adobe Express - 2025-12-10 21-56-38.gif

DAW の ドラムマシンのような UI で四つ打ち以外のビートを気軽に生成できます
また、TAP ボタンを押すこと あるいは、フェーダーで BPM を調節することもできます
Reset ボタンを押すと拍の頭出しができます

📋 全体構成

まず、BeatGenerater は大きく2つの COMP から構成されています

BeatGenerater/
├── BPMController/
└── StepController/

Part 1: BPMコントローラー部分

BPMを設定する方法が2つ用意しています

  • スライダーで直接設定 - 数値を見ながら正確に設定
  • タップテンポ - 曲に合わせてタップしてBPMを検出

1. スライダーによるBPM設定

これは シンプルに Slider COMP と CHOP を繋いで BPM の Range を指定しただけのものです
image.png

2. タップテンポ機能

タップテンポは「曲を聴きながらボタンをタップすると、その間隔からBPMを自動計算する」機能です。

image.png

2秒以上間隔が空くとリセットされ、タップ間隔の平均からBPMを計算しています。
CHOP Execute DAT の Python スクリプトで実装しました。

tap_times = []  # タップ時刻を保持するリスト

def onOffToOn(channel, sampleIndex, val, prev):
    current_time = time.time()

    # 2秒以上経過したらリセット
    if len(tap_times) > 0 and (current_time - tap_times[-1]) > 2.0:
        tap_times.clear()
        op('bpm_data').par.value0 = 120.0  # デフォルトBPMに戻す
        op('bpm_data').par.value1 = 0      # タップ数リセット

    # タップ時刻を記録
    tap_times.append(current_time)

    # 最大8タップまで保持(古いものは削除)
    if len(tap_times) > 8:
        tap_times.pop(0)

    # 2回以上タップしたらBPM計算
    if len(tap_times) >= 2:
        # 各タップ間隔を計算
        intervals = [tap_times[i] - tap_times[i-1]
                     for i in range(1, len(tap_times))]
        # 平均間隔を算出
        avg_interval = sum(intervals) / len(intervals)
        # BPMに変換(60秒 ÷ 平均間隔)
        bpm = 60.0 / avg_interval if avg_interval > 0 else 120.0

        op('bpm_data').par.value0 = round(bpm, 1)
        op('bpm_data').par.value1 = len(tap_times)

3. 二種類の BPM の設定方法から後勝ちで BPM を決める

スライダーとタップテンポ、どちらの値を使うかを自動判定するようにしています
基本的に最後に操作されたほうの BPM が選ばれます

image.png

last_values = {}
last_changed = None
last_time = 0

def onCook(scriptOp):
    global last_values, last_changed, last_time

    # 全入力をチェック
    for i, inp in enumerate(scriptOp.inputs):
        current = inp[0].eval()
        key = f"input_{i}"

        # 値が変わった入力を検出
        if key not in last_values or last_values[key] != current:
            last_values[key] = current
            last_changed = i
            last_time = time.time()

    # 最後に変更された入力の値を出力
    if last_changed is not None:
        scriptOp.clear()
        scriptOp.appendChan('value')[0] = scriptOp.inputs[last_changed][0].eval()
        scriptOp.appendChan('source')[0] = last_changed  # どの入力が選ばれたか

Part 2: ビート生成部分

Timer CHOPでビートを刻む

BPMから実際のビート信号を生成する心臓部です。
BPM Controller から 指定された BPM を受け取り、実際にそれに従い Beatを生成している場所です

今回は1小節を16分割(16分音符)で刻みたいので、1拍あたりの長さをさらに4分割しています。
例えば BPM 128 の場合、計算式は以下のようになります:

60秒 / 128 BPM / 4 = 約0.117秒(16分音符1つ分の長さ)

image.png

生成された Beat を Count CHOP で数え、 count の数に応じてシーケンサーを進めます
シーケンサーを進めたとき該当の場所の ボタンが ON になっていれば Beat として output します

image.png

ボタンの入力と16分のビートから実際に欲しいビートを生成する

image.png

in1 が Timer CHOP で刻んだビートです, in2 が ボタン16個の On/Off の状態です
これらを Script CHOP で演算し、ボタンが押されていたら Beat を生成するようにしています

def onCook(scriptOp):
    scriptOp.clear()

    # 入力1: step値 (0-15)
    step_input = scriptOp.inputs[0]
    # 入力2: Value0-Value15のフラグ (0 or 1)
    flags_input = scriptOp.inputs[1]

    # stepの現在値を取得
    step = int(step_input[0][0])  # 最初のチャンネルの最初のサンプル

    # 出力チャンネルを作成
    output_chan = scriptOp.appendChan('output')
    step_chan = scriptOp.appendChan('step')

    # 結果を計算
    result = 0

    # step が 0-15 の範囲内かチェック
    if 0 <= step <= 15:
        # 対応するフラグをチェック (Value{step} のフラグ)
        flag = flags_input[step][0]  # step番目のチャンネルの値

        # フラグが1の場合のみ1を出力
        if flag == 1:
            result = 1

    # stepが整数のときのみビートを出力(遷移中は出力しない)
    if step_input[0][0] == int(step_input[0][0]):
        output_chan[0] = result
    else:
        output_chan[0] = 0

    step_chan[0] = step

    return

Beat ジェネレーターの用途

この生成された Beat を使って、四つ打ちじゃないドラムパターンのジャンルに対応した、ステップシーケンサーが簡単に作れます

通常、VJでは音声入力からビートを検出することが多いですが、AudioAnalyzer の場合、LPF/HPF で特定の音域から Trigger としきい値で Beat の有無を取得することになると思います。しかし、ジャンルによってキックの音域やSustain、Atack などが異なります。
また、イベントの制約によっては、Line がもらえないこともあると思います。
そういった場面で役立つのじゃないかなと思い実装してみました。
汎用的な tox としておくことで、いつでも気軽に利用もできます。

実際の利用例

Beat をトリガーとして、ステップシーケンサーを使ってもよいですが
オーディオリアクティブではなく、汎用的なプリレンダのVJ用ループ素材を作るときにも便利です
2025-12-10 22-47-40.gif

おわりに

今回は、いくつか自分が作って使っている小さめのコンポーネントのうちの1つである ビートジェネレーター を紹介しました
他にも、標準の Tools とは少し違った AudioAnalyzer や、Midi のコントローラーなど色々なコンポーネントを UI 付きで実装している段階です
最終的に、この小さな コンポーネントをイベントごとに組み替えて臨機応変に対応できるようしたいなと構想しています

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?