非公式チュートリアル04 omif条件文とサブパッチ
ここでは「もしも」で判断する条件文omifおよびサブパッチの使い方を学ぶ。
BASICやC言語、あるいはMax/MSPの経験のある方は、 IF THEN ELSE という条件文をご存知だろう。
「もしも(if)〇〇なら、それから(then)〇〇せよ、そうでなければ(else)〇〇せよ」というのが、プログラム言語におけるif条件文の構造である。
「もしも(if)〇〇なら、それから(then)〇〇せよ、そうでなければ(else)〇〇せよ」というのが、プログラム言語におけるif条件文の構造である。
OpenMusicでこれにあたる条件文はomifというファンクションで行う。
まずomifファンクションを作ると、インプットが2つあらわれる。左側のインプットには「論理演算子」を接続する。右側のインプットには、その条件文がt(真)である場合、つまりその条件を満たした場合に、そこに接続したコードがevaluateされる。(BASICやMax/MSPで言うTHENにあたる)
>キーでインプットを3つに増やすことが出来る。3つめのインプットには、その条件がnil(偽)である場合、つまりその条件を満たさない場合に、そこに接続したコードがevaluateされる。(BASICやMax/MSPで言うELSEにあたる)
>キーでインプットを3つに増やすことが出来る。3つめのインプットには、その条件がnil(偽)である場合、つまりその条件を満たさない場合に、そこに接続したコードがevaluateされる。(BASICやMax/MSPで言うELSEにあたる)
論理演算子にはたくさんの種類がある。
これらの論理演算子はすべて、その条件を満たせばt、満たさなければnilというシンボルを返す。
これらの論理演算子はすべて、その条件を満たせばt、満たさなければnilというシンボルを返す。
まず2段目は特に良く使うものである。
om= 左右のインプットの値が等しいか。
om/= 左右のインプットの値が等しくないか。
om< 左のインプットの値が右より小さい値であるか。
om> 左のインプットの値が右より大きい値であるか。
om<= 左のインプットの値が右と同じか、または右より小さい値であるか。
om>= 左のインプットの値が右と同じか、または右より大きい値であるか。
om/= 左右のインプットの値が等しくないか。
om< 左のインプットの値が右より小さい値であるか。
om> 左のインプットの値が右より大きい値であるか。
om<= 左のインプットの値が右と同じか、または右より小さい値であるか。
om>= 左のインプットの値が右と同じか、または右より大きい値であるか。
3段目に挙げたものは、論理演算子を複数扱う場合に用いるものである。
omand 左右のインプット(どちらも論理演算子を接続すること)のいずれもtを返しているか。>キーでインプットを増やせる。
omor 左右のインプット(どちらも論理演算子を接続すること)のうち1つがtを返しているか。>キーでインプットを増やせる。
conditional 左のインプット(論理演算子を接続すること)を判断し、tであるたびに一番右のインプットに接続されたコードをevaluateする。>キーでインプットを増やせる。
omor 左右のインプット(どちらも論理演算子を接続すること)のうち1つがtを返しているか。>キーでインプットを増やせる。
conditional 左のインプット(論理演算子を接続すること)を判断し、tであるたびに一番右のインプットに接続されたコードをevaluateする。>キーでインプットを増やせる。
4段目を見てみよう。これらはom=、om<などの「伝統的な」プログラム言語で使われた論理演算子以外にも扱うことの出来る演算子である。ここでは代表的なものを挙げている。
このうちの幾つかは、OpenMusicの公式リファレンスマニュアルに記述されていない。なぜなら、OpenMusicの母体となっているLispWorksで使用可能な論理演算子の幾つかがOpenMusicでも使用可能だからである。LISPについては必要最低限の知識をこの非公式チュートリアルでも後に扱う予定である。
このうちの幾つかは、OpenMusicの公式リファレンスマニュアルに記述されていない。なぜなら、OpenMusicの母体となっているLispWorksで使用可能な論理演算子の幾つかがOpenMusicでも使用可能だからである。LISPについては必要最低限の知識をこの非公式チュートリアルでも後に扱う予定である。
ここでは画像内に挙げたものを見てみよう。
prime? 与えられた数値が素数かどうかを判断する。
atom 与えられた値がアトム(単体の値)かどうかを判断する。アトムでなければリストなので、リストが来たらnil(偽)を返す。(アトムやリストという言葉はLISPの用語なので、いずれLISPを扱う時に詳しく触れる。)
oddp 与えられた数値が奇数かどうかを判断する。
evenp 与えられた数値が偶数かどうかを判断する。
null 与えられた値がnil(偽)かどうかを判断する。nilであればtを返す。論理演算子では常にtかnilのいずれかが返ってくるが、nilの他にも数値やリストなど様々な値が返ってくる場合があるので、そうした条件を判断するのには有効である。
numberp 与えられた値が数値かどうかを判断する。
symbolp 与えられた値がシンボルかどうかを判断する。
atom 与えられた値がアトム(単体の値)かどうかを判断する。アトムでなければリストなので、リストが来たらnil(偽)を返す。(アトムやリストという言葉はLISPの用語なので、いずれLISPを扱う時に詳しく触れる。)
oddp 与えられた数値が奇数かどうかを判断する。
evenp 与えられた数値が偶数かどうかを判断する。
null 与えられた値がnil(偽)かどうかを判断する。nilであればtを返す。論理演算子では常にtかnilのいずれかが返ってくるが、nilの他にも数値やリストなど様々な値が返ってくる場合があるので、そうした条件を判断するのには有効である。
numberp 与えられた値が数値かどうかを判断する。
symbolp 与えられた値がシンボルかどうかを判断する。
他にもたくさんの論理演算子がある。特にLisp由来のものも多い。これらはLISPを扱う時に詳しく触れる。
さて、簡単な条件文によるパッチを作ってみよう。(画像左下)
まずom-randomを作り、1から10までの乱数を発生させてみよう。
画像ではすでにワンスモードが指定されているが、まずは試しにワンスモードにせずそのままにしておこう。理由は後述。
画像ではすでにワンスモードが指定されているが、まずは試しにワンスモードにせずそのままにしておこう。理由は後述。
その下にあるom//(オーエム・ダブルスラッシュと読む)は、剰余つまり「あまりの出る割り算」を求めている。例えば 7 ÷ 2 = 3...1 となる。左側のアウトプットからは剰(この例では3)が、右側のアウトプットからは余(この例では1)が出力される。
ここではom//の左側のインプットにom-randomのアウトプットからのコードを接続し、右側のインプットには2を入力しておく。
その下に論理演算子om=を作成する。om=の左側のインプットには、om//の右側のアウトプットからのコードを接続する。右側のインプットには0を入力しておく。
さていよいよomifを作ってみよう。ここではELSEについても触れるので、>キーでインプットを3つに増やしておく。
omifの一番左のインプットに、先ほどのom=のアウトプットからのコードを接続する。
この意味は、「もしも、om-randomで生成した乱数をom//にて2で割った数値のあまりが0だったら」という条件文になる。2で割ったあまりが0ということはつまり偶数(英語でeven)なので、ここではevenという文字列を返すことにしよう。omifの左から2番目のインプットにevenという文字を入力しておこう。
この意味は、「もしも、om-randomで生成した乱数をom//にて2で割った数値のあまりが0だったら」という条件文になる。2で割ったあまりが0ということはつまり偶数(英語でeven)なので、ここではevenという文字列を返すことにしよう。omifの左から2番目のインプットにevenという文字を入力しておこう。
この文字列をOpenMusicではシンボルと呼ぶ。
(厳密に言えば、LISPでは「(単体の)文字 char」「(連続した複数の文字からなる)文字列 string」「シンボル symbol」は別々に扱われるものだが、OpenMusicでは特に/#cや/#sという指定が無い限り、全てシンボル/#pとして扱われる。これは後に文字について扱う予定なので、その時に詳しく述べることとする。)
さらに、2で割ったあまりが0でなければ、それはつまり奇数(英語でodd)なので、oddという文字列を返すことにしよう。omifの左から3番目のインプット(先ほど>キーで追加したもの)にoddという文字を入力しておこう。
さて、ここでomifをevaluateすると、そのたびにevenかoddが結果としてLISPウィンドウに出力されるのであるが、どの数値に対してevenないしoddと言っているのか判断し難い。そこで、まずom-randomの出力結果を確認し、その次にevenまたはoddが出力されるようにしたい。
そこでx-appendを用い、om-randomの出力結果と、omifの出力結果をまとめてみることにする。これを何回かevaluateしてみると、このようになる。
OM => (8 even)
OM => (4 odd)
OM => (7 even)
OM => (2 odd)
OM => (5 even)
OM => (2 even)
OM => (7 even)
OM => (4 odd)
OM => (7 even)
OM => (2 odd)
OM => (5 even)
OM => (2 even)
OM => (7 even)
これを見ると、出力結果が思わしくないことになっているのに気づく。まず 8 even これは正しい。しかし 4 odd これは正しくない。 7 even これも正しくない。以下同様である。
どうしてこうなるかというと、x-appendによってom-randomが2回evaluateされているからである。om-randomはevaluateされるたびに異なる乱数を返す。つまりomifによって参照している数値と、x-appendに直接入力した数値は、違うものが出力されていることとなる。
これを改善するには、om-randomをワンスモードにする必要がある。(画像参照)
これによって出力結果は正しくなる。x-appendを何度かevaluateしてみよう。
OM => (2 even)
OM => (7 odd)
OM => (2 even)
OM => (4 even)
OM => (3 odd)
OM => (5 odd)
OM => (5 odd)
OM => (9 odd)
OM => (7 odd)
OM => (2 even)
OM => (4 even)
OM => (3 odd)
OM => (5 odd)
OM => (5 odd)
OM => (9 odd)
ところで、ここでのom//とom=による判断は、今回は分かりやすいようにこう書いたが、実はevenp一つで代用できる。このように便利な論理演算子がある場合は、面倒で冗長な条件判断を行わず、代用できるものは代用するのが、スマートなプログラムを作る秘訣である。
今作った一連の条件判断パッチを丸ごとコピーしてみよう。ドラッグして囲んで選択し、command + dキーで全体がコピーされる。コピーされた一群が選択されたままの状態で、どれか一つをドラッグし、見やすい位置に配置しよう。
コピーし終わったら、論理演算子を置き換えてみる。先ほどのom//とom=、及びそれらに入力していた数値のボックスをdeleteキーで消す。
新たにevenpを作成し、om-randomのアウトプットからevenpのインプットへ、evenpのアウトプットからomifの左側のインプット(input 0)へとそれぞれ接続する。
試しにこの状態でx-appendをevaluateしてみると、結果は同じである。
新たにevenpを作成し、om-randomのアウトプットからevenpのインプットへ、evenpのアウトプットからomifの左側のインプット(input 0)へとそれぞれ接続する。
試しにこの状態でx-appendをevaluateしてみると、結果は同じである。
さて今度は、ここにrepeat-nを作って複数回evaluateさせてみよう。結果はどうなるかというと、 ((5 odd) (5 odd) (5 odd) (5 odd) (5 odd) (5 odd) (5 odd) (5 odd) (5 odd) (5 odd)) というように、常に同じ結果が10回繰り返されることになる。これはom-randomをワンスモードにしているからである。
ではrepeat-nを用いながら毎回異なる結果が得られるようにするには、どうしたら良いか。
そこでサブパッチというものを作成する。
command + クリックでオブジェクト新規作成の入力ボックスを表示させる。ここでpatchと入力すると、mypatchという赤いオブジェクトが現れる。(mypatchと入力しても受け付けない。)
このパッチを開き、先ほどと同じ手順でパッチをコピーしよう。
先ほど直前に作ったrepeat-nはコピーしない。それも含めてコピーした場合は、後で消しておく。
先ほど直前に作ったrepeat-nはコピーしない。それも含めてコピーした場合は、後で消しておく。
サブパッチ内にコピーをペーストした時、元ウィンドウの下や右のほうから持って来た場合には、コピーで貼付けた結果が見えないことがある。この場合はウィンドウを広げると見える。慌てて何度もペーストすると、何重にもオブジェクトが重なってしまうので注意!
サブパッチの画面左上にある二つの下向き矢印のうち、青いほうをクリックすると、outputがあらわれる。これをx-appendの下に繋ごう。
メインパッチのウィンドウに戻ると、サブパッチの下に先ほど作ったoutputが現れている。ここの下にrepeat-nを作って繋ぎ、10回繰り返してみよう。
これの出力結果は、例えば ((8 even) (7 odd) (5 odd) (2 even) (2 even) (4 even) (5 odd) (3 odd) (3 odd) (10 even)) となり、偶数・奇数の結果は正しく出力され、かつ毎回異なる出力となっている。(同じ数値が複数回連続することも有り得る)
これはつまり、サブパッチ内でワンスモードにしたオブジェクトは、サブパッチ内で複数回evaluateされれば2回目以降は1回目と同じ数値を返すが、メインパッチに置いたrepeat-nなどから連続でevaluateされると、毎回異なる出力を返す。
試しにこのサブパッチをワンスモードにして、その下のrepeat-nをevaluateすると、その結果は ((9 odd) (9 odd) (9 odd) (9 odd) (9 odd) (9 odd) (9 odd) (9 odd) (9 odd) (9 odd)) というように、出力は1回目で固定となる。
このことをローカルとグローバルと呼ぶ。
これについてはLISPを扱う時に詳しく触れることとする。
参照
当ウィキ内
公式チュートリアル
- Tutorial 8 (サブパッチ)
- Tutorial 14 (omif条件文)