nul.jp > PureDataLab

Pure Data で py オブジェクトを使ってアルペジエータを作る

このページでは Pure Data で py オブジェクトを使ってアルペジエータを作る方法を取り上げます。 py オブジェクトについては「Python で Pd の external を作る」でも取り上げましたが、 この例で Pd のプログラミングに Python のようなスクリプト言語を取り入れることの利点がさらによく分かると思います。 なお、このページの記述は py-0.0.1 を対象としたものです。 現在の最新版は py-0.1.0 です。(2002-08-30 追記)

アルペジエータの仕組み

まず目標とするアルペジエータの仕組みについて考えましょう。 今回作るアルペジエータは次のような動作をするものとします。

これを実現するために必要なプログラミングは次のようなものになるはずです。

Python を使う利点

このようなプログラミングに Pd のパッチングでもなく C 言語でもなく Python を使う利点は何なのでしょうか。

まず Pd のみのプログラミングでは、 「ノートナンバーとベロシティーのペア」のような構造をもったデータを自然に取り扱うことが困難です。 例えば Pd の配列は単一の数値しか収めることが出来ませんから、 「ノートナンバーとベロシティーのペアの配列」を実現するためには 「ノートナンバーの配列」と「ベロシティーの配列」を別に用意するなどの方法を採らざるを得なくなります (「Pd で簡易シーケンサを作る」ではそうしました)。 C 言語の構造体や Python のクラスであればこうした複合的なデータを自然な形で配列に収める事が出来ます。

次に Python のようなスクリプト言語では配列の操作に関しても非常に強力なものが予め用意されています。 今回のプログラミングでは配列を音程順に並べ替えたり配列から要素を取り除いたりする操作が必要になるわけですが、 あとで見るように Python ではこの操作は簡単に実現できます。 Pd のパッチングのみでは配列の並べ替えは実現するのが非常に難しそうです。 標準的なライブラリを使った C 言語の場合でも構造体の配列を並べ替えたり要素を取り除いてサイズを詰めたりするような操作は自分で実装しなければならず、 面倒なプログラミングが必要になります。

クラス Note の定義

まずはノートナンバーとベロシティーのペアを扱うためにクラス Note を定義しましょう。 これは以下のようなコードになります。

class Note:
    def __init__(self, _note, _vel):
        self.note = _note
        self.vel = _vel
    def __cmp__(self, other): # ノートナンバーのみを見て大小を決定
        return cmp(self.note, other.note)

__init__ はクラスのコンストラクタで、 notevel の値を初期化します。 あとで例えば a = Note(60, 100) のようにすると、 note が 60、vel が 100 の値を持った Note のインスタンスが作られて a に代入され、 a.notea.vel として参照できるようになります。

__cmp__ はこのクラスの大小比較を定義している部分です。 これは後で並べ替えをする際に必要なメソッドです。 この定義により、並べ替えをする際に note の値が大きければそちらのほうが大きいという判断がなされることになります。

グローバル変数 seqcur の定義

seq = []
cur = 0

seqNote の配列を保存するための変数です。 初期値は空の配列です。 cur はアルペジエータがノートを出力するときに配列内のどの要素を出力するかを示す値を格納する変数です。 初期値はゼロです。

メッセージハンドラの定義

次にメッセージの入力に対する処理を定義することにしましょう。 既に述べたように、このアルペジエータではノートオンとノートオフに対して処理を行う必要があります。 また指定間隔毎にノートを出力するために metro から bang メッセージを受け取ることにし、 bang に対しても処理を定義します。

ノートオンに対する処理

py オブジェクトではスクリプトへの入力に使用できる inlet は一つしかありません。 ノートオンはノートナンバーとベロシティーの組み合わせなので一見二つの入力が無ければ無理なように見えますが、 予め pack オブジェクトを使ってリスト化しておけば、 入力されたリストの最初の要素が関数の第一引数に、二つ目の要素が第二引数にといった具合に割り当てられるので、 一つの入力から複数の値を受け取ることが可能です。

ノートオンに対する処理は配列への追加と並べ替えなのでコードは次のようなものになります。

def arp(note, vel):
    global seq
    seq.append(Note(note, vel))
    seq.sort() # Note クラスの __cmp__ で定義した大小に基づいて並べ替える

ノートオフに対する処理

MIDI のノートオフというのは通常ベロシティーがゼロのノートオンなので、 ノートオンに対する処理の中で条件分岐をすることが出来ます。 先のコードを以下のように書きなおしましょう。

def arp(note, vel):
    global seq
    if vel == 0: # ノートオフ
        for i in seq:
            if i.note == note:
                seq.remove(i)
    else: # ノートオン
        seq.append(Note(note, vel))
        seq.sort()

for i in seq は配列 seq の内容を一つずつ順番に i に代入して繰り返しを行います。 その際に inote メンバが与えられたノートの値と一致していたらそれを配列から削除します。

bang に対する処理

では bang はどうでしょうか。 C 言語で Pd の external を作るときには addbang 関数を使って bang に対するメソッドを定義しますが、 py オブジェクトにはそういったものは用意されていません。 そこでちょっとした工夫が必要になります。 def arp(note = -1, vel = 0): のようにノートナンバーの引数に -1 というデフォルト値をつけます。 py オブジェクトが bang メッセージを受け取ると、指定された関数が引数無しで呼び出されます。 引数を指定せずに呼び出した場合、予めデフォルト値が設定されていればそれが引数の値として使われます。 つまり bang を受け取った場合には変数 note の値は -1 になります。 MIDI のノートナンバーとして -1 というのは存在しないので、 これによってノートオンと bang を区別することが出来るわけです。

bang に対する処理として配列内のノートを順番に出力するコードを追加すると以下のようになります。

def arp(note = -1, vel = 0):
    global seq, cur
    if note == -1: # bang メッセージが送られた場合の処理
        if len(seq) > 0: # 配列に何もない(キーが押されていない)場合は何もしない
            if cur >= len(seq): # 現在の位置が配列の外に出ていたら最初に戻る
                cur = 0
            ret = [seq[cur].note, seq[cur].vel]
            cur += 1
            return ret
    else:
        if vel == 0: # ノートオフ
            for i in seq:
                if i.note == note:
                    seq.remove(i)
        else: # ノートオン
            seq.append(Note(note, vel))
            seq.sort()

使い方

以上でアルペジエータは完成です。 以下のコードを arp.py として保存し、 Pd のパスの通った場所に置いてください。

# arp.py

seq = []
cur = 0

class Note:
    def __init__(self, _note, _vel):
        self.note = _note
        self.vel = _vel
    def __cmp__(self, other):
        return cmp(self.note, other.note)

def arp(note = -1, vel = 0):
    global seq, cur
    if note == -1:
        if len(seq) > 0:
            if cur >= len(seq):
                cur = 0
            ret = [seq[cur].note, seq[cur].vel]
            cur += 1
            return ret
    else:
        if vel == 0:
            for i in seq:
                if i.note == note:
                    seq.remove(i)
        else:
            seq.append(Note(note, vel))
            seq.sort()

これをパッチから次のようにして使用します。

オブジェクト [py arp arp] の右の入力には [metro] オブジェクトの出力が繋がれ、[notein] の左二つの出力を pack したものも繋がれている。出力は unpack されて [makenote] の左側二つの入力に繋がれる
http://nul.jp/2002/pd_arp
文書作成: 2002-08-25