2016年6月21日火曜日

KONTAKT KSPメモ play_note, note_off

今回は、かなりマニアックな内容です。
ちなみに気になったことなどあれば、ツイッターのほうにでも連絡ください。

レガートを制作する上で、KONTAKT内でグループ単位のサンプルのディレイが必要になった。
しかし、グループ単位で音を遅らせる方法がこれといって無い。

そこで、waitとplay_noteの検証をした。
そして現在押されているキーの数を数えるための$Stackを用意した。
KMG7を制作中、これにかなり悩まされたので、改めて検証。

on note
    inc($Stack)
    wait(1000000)
    $Eve_ID := play_note(60,127,0,-1)
end on

キー50をonにしたとする。
この場合
50キーオンから1秒たつ前にキーを放すと
60の音は鳴らない。
・50キーを放したとき60のon releaseはコールバックされない。(50のon releaseだけ働くっぽい)
50キーオンから1秒経過させると
60の音が鳴る。
・50キーを放したとき60のon releaseはコールバックされる。(2つ分on releaseが働くっぽい)

###########################

on note
    inc($Stack)
    wait(1000000)
    $Eve_ID := play_note(60,127,0,0)
end on

キー50をonにしたとする。
この場合
50キーオンから1秒たつ前にキーを放しても
60の音は鳴る。(鳴りっぱなし)
・50キーを放したとき60のon releaseはコールバックされない。(50のon releaseだけ働くっぽい)
50キーオンから1秒経過させると
60の音が鳴る。(鳴りっぱなし)
・50キーを放したとき60のon releaseはコールバックされない。(50のon releaseのみ働くっぽい)
・60の音は、サンプルが終わるか、fade_outまたはnote_offを使用しなければ止まらない。

ここで、後者の場合、
on release
    dec($Stack)
    note_off($Eve_ID)
end on
とすると、
50キーオフで、音は50も60も止まる。
on releaseは1つ分だけ働き、note_offによる重複はないっぽい。
ただし、キーオンを和音にすると、$Eve_IDが順次上書きされ、note_offは最後の一つの音しか消えない。

これを回避するには、
play_note時の$Eve_IDを
%Eve_ID[$EVENT_NOTE]
にするなどの必要がある。

release時は
$i := 0
while($i<128)
    if($i=$EVENT_NOTE)
         note_off(%Eve_ID[$i])
    end if

    inc($i)
end while
とすれば、オフにしたキーのみ消えてくれて、全部放すと全部消えてくれる。
しかし、play_noteが開始になってからでないと消えない(1秒経過前にキーオフすると1秒経過後に音が鳴り出し、なり続ける)

###########################

もう一つ注意があるようです。
on controller
    message($Stack)
end on
をセットし、
音が鳴っている間、CC1を動かしまわって、$Stackの動きを観察した。

on note
    inc($Stack)
    wait(1000000)
    %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,-1)
end on

on noteの中でplay_noteは、循環しないらしい。
というのは、50キーを押しっぱなしにしたとき、$stackが1以上増えないからである。

on note
    inc($Stack)
    wait(1000000)
    %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,0)
end on
の場合も同じであった。

そして先ほどの例のように、
on release
    dec($Stack)
    $i := 0
    while($i<128)
        if($i=$EVENT_NOTE)
            note_off(%Eve_ID[$i])
        end if

        inc($i)
    end while
end on
relaese内でのnote_offも循環しないようである。

注意すべきは、on note内でのnote_offはon releaseがコールバックされるようで、$Stackが減ります。on release内でのplay_noteは$Stackが増えなかったので、そもそもplay_noteはon noteをコールバックしないっぽい?です。

on controller
    message($Stack)
end on

on note
    inc($Stack)
    wait(1000000)
    %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,0)
    wait(1000000)
    $i := 0
    while($i<128)
        if($i=$EVENT_NOTE)
            note_off(%Eve_ID[$i])
        end if

        inc($i)
    end while
end on

↑これで50キーを押しっぱなしにして2秒以上の間CC1を動かしまわってメッセージを見ると1秒後に1になって2秒後に0になるのがわかります。

実験中気になったのは
%Eve_ID[$EVENT_NOTE] := play_note(60,127,0,0)
%Eve_ID[$EVENT_NOTE] := play_note(60,127,0,-1)
on note内だと、前者は無条件にplay_noteが起動し、後者はキーがオンになっている場合にplay_noteが起動しましたが、on release内で使うと、共にキーオフ後でもplay_noteの音がなりっぱなしになるようです

それをふまえて、
つまり、ややこしいのだが、

on note
    inc($Stack)
    wait(1000000)
    %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,-1)
end on

on release
    dec($Stack)

end on

これは、note_offがなくても、1秒以後にキーを放せば、play_noteの音も止まるが
releaseが2個ずつ働くため、3和音を鳴らすと、1秒以後にキーを放すと$Stackは-3になってしまう。(1音だと-1)

on note
    inc($Stack)
    wait(1000000)
    %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,0)
end on

on release
    dec($Stack)
    $i := 0

    while($i<128)

         if($i=$EVENT_NOTE)
            note_off(%Eve_ID[$i])
        end if
    

        inc($i)
    end while

end on


こちらは、note_offをセットしなければ1秒以後に音が消えてくれないが、1秒以後にキーを放せば、play_noteの音はnote_offにより止まる。
そして、on note内のplay_noteやon release内のnote_offは循環しないようなので、3和音を鳴らし、1秒以後にキーを放しても$Stackは正常に0に戻る。
ただし問題は、一度キーをオンにすると、1秒以内にキーをオフにしても、1秒後に60の音が鳴り始めてしまうことである(オンにしたキーが生きていればplay_noteを起動するという方法も取れるかもしれないがややこしくなる)

つまり前者の場合、play_noteの音が鳴り始めたあとにキーオフすると、$stackが-(マイナス)に
後者の場合、$Stackに異常は発生しないが、無条件に音が鳴ってしまうという。

そこで偶然良い結果を見つけたので、記しておきます。

前者を使い、on release内でnote_offをセットします。
すると、3和音キーオフ時にreleaseが重複し$stackが結果的に-3になるかと思いきや、
かなりめちゃくちゃな演奏をしても、$Stackが必ず0に戻りました。

最終的にplay_noteを使ったディレイ効果としてよさそうなのは、少し改造を加えて、

on note
    inc($Stack)
    disallow_group($ALL_GROUPS)
    wait(1000000)

    allow_group(find_group("Legato"))
    %Eve_ID[$EVENT_NOTE] := play_note($EVENT_NOTE,$EVENT_VELOCITY,0,-1)

end on

on release
    dec($Stack)
    $i := 0

    while($i<128)

        if($i=$EVENT_NOTE)
            note_off(%Eve_ID[$i])
        end if   

    inc($i)
    end while

end on

これで、1秒後にグループ"Legato"のキーが鳴り、キーオフで遅れた音も消える、そして$Stackに異常も発生しないプログラムのできあがりです。(保障しませんが)これを応用すると、1つのキーオンで2つ以上の任意の音をつないでレガートを作れるはずです。
$Stackが0のときは通常のグループを鳴らし、$Stack>0のときは、このディレイを使って、2つ以上の音をつなげるLegatoグループを鳴らすというわけです。


KSPの説明書を読むと、コールバック関係のことはしっかり書いてあるようなのですが、英語なので正確につかめないというのが、全ての元凶でした。

しかし、説明書にディレイの作り方を書いてあるわけではないので、良しとしませんか・・・。

実は現在制作中の音源には別の方法をとっています。ひと段落したら今回の方法を試したいと思います。そして不具合があればまた記事を修正なり、更新したいと思います。

以下6月25日追加更新
実際に音源に組み込んでみました。
見事バグが発生しました。
理由はwaitです。
上のスクリプトでは1秒だったので気づかなかったのですが、waitをはさんでグチャグチャやると、$Stackに異常が発生しました。
ここでは振れていませんが、消えるはずのノートが消えなかったりと、いろいろ不具合が出ました。
結局何がおきているかというと、おそらくですが、
1.ノートオン
2.wait
3.play_note(同時にID取得)
4.wait
5.play_note(同時にID取得)
となるところが、速い演奏やあるタイミングで、
1.ノートオン
2.wait
3.play_note(同時にID取得)
4.wait前にplay_note(同時にID取得)
5.wait
6.play_note(同時にID取得)
このへんの関係で、note_offで消えないIDが残ってしまい、on releaseで消すことになり、$Stackが余分に減ってしまうのだと思います。

前回の検証で、play_noteはon releaseで消す(止める)と、play_noteを呼び出したノートを止めるreleaseとplay_noteを止めるreleaseの2つが働いてしまうようで、$Stackが1個余分に減ってしまうらしいことがわかりました。
正しく、on release内のnote_offでplay_noteを止めると、on releaseは一つしか働いてないようで$Stackも1個しか減りませんでした。

つまり、waitの前後の関係でplay_noteで取得したIDが%Eve_IDに無い状態になり、on release時に
note_offで止めれてないこととなり、on release自体で止めることとなり、$Stackが減ってしまった、と勝手に解釈しました。

結果的に、$Stackに異常が出ると先に進めないので、
「後者の場合、$Stackに異常は発生しないが、無条件に音が鳴ってしまうという。」
という方を採用しました。

もう一度おさらいです。

on note
    inc($Stack)
    wait(1000000)
    %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,0)
end on

on release
    dec($Stack)
    $i := 0

    while($i<128)

         if($i=$EVENT_NOTE)
            note_off(%Eve_ID[$i])
        end if
    

        inc($i)
    end while

end on


こちらは、note_offをセットしなければ1秒以後に音が消えてくれないが、1秒以後にキーを放せば、play_noteの音はnote_offにより止まる。
そして、on note内のplay_noteやon release内のnote_offは循環しないようなので、3和音を鳴らし、1秒以後にキーを放しても$Stackは正常に0に戻る。
ただし問題は、一度キーをオンにすると、1秒以内にキーをオフにしても、1秒後に60の音が鳴り始めてしまうことである(オンにしたキーが生きていればplay_noteを起動するという方法も取れるかもしれないがややこしくなる)

「たぶん」$Stackに異常はでません。そのかわり、小文字で表示したややこしい方法をとることにしました。といってもめんどくさかっただけでそんなにややこしくはないのですが。

on note
    %Alive_NOTE[$EVENT_NOTE] := 1
    inc($Stack)
    wait(1000000)
    if(%Alive_NOTE[$EVENT_NOTE] = 1)
        %Eve_ID[$EVENT_NOTE] := play_note(60,127,0,0)
    end if
end on

on release
    %Alive_NOTE[$EVENT_NOTE] := 0
    dec($Stack)
    $i := 0

    while($i<128)

         if($i=$EVENT_NOTE)
            note_off(%Eve_ID[$i])
        end if
    

        inc($i)
    end while

end on


ということです。
押したキーが生きてるか死んでるかを判断してやり、生きていればplay_noteするとしてやりました。

これで、やっとwaitがからんでも、$Stackに異常なく正しい動作が可能となりました。(たぶんです)

実際に組み込む際は、waitより前に設定する事項と、あとで設定する事項とがあります。
それをしないと、正しく動作しません。

まだ不具合が出るかもしれませんが、ひとまず安心です。
とにかく、前回の記事(この記事の上のほう)は失敗作です。

条件をつけてplay_noteの最後の引数を0にする方を使うことになりました。

おしまい。

人気の投稿