14

私はHaskellでインタラクティブなリアルタイムのオーディオ合成を書き込もうとしていますが、時間を表すために「怠惰な数字」が切実に必要です。

私のプログラムは「信号」の概念に基づいており、これらの信号は「信号プロセッサ」によって変換されます。しかし、FaustやChucKのような他の同様のプロジェクトとは異なり、私は厳密に純粋関数で作業し、それでも時間に明示的にアクセスできるようにしたいと思います。

アイデアは、Haksellで純粋な「遅延ストリームプロセッサ」を表現することが可能であり、遅延評価により、インタラクティブでリアルタイムに機能するというものです。

たとえば、「MIDI信号」を音符を変えるイベントのストリームとして表すことができます。

type Signal = [ (Time, Notes->Notes) ]

すべて非インタラクティブモードで非常にうまく機能しますが、実際にリアルタイムで操作したい場合、大きな障害にぶつかります。ある時点で、出力信号は次の入力の時間に依存します。イベント。したがって、私の合成エンジンは実際には次のイベントまで停止します。

説明させてください。サウンドカードが出力信号のサンプルを要求すると、遅延評価者は信号プロセッサの依存関係グラフを調べ、最終的に入力(midi)信号の一部を要求します。しかし、入力信号がローカルで次のようになっているとしましょう。

input :: Signal
input = [ ..., (1, noteOn 42), (2, noteOff 42), ... ]

時間1.5で出力(オーディオ)信号を計算する必要がある場合、次のようなものが必要になります。

notesAt :: Signal -> Time -> Notes
notesAt = notesAt' noNotes where
    notesAt' n ((st,sf):ss) t
            | st > t = n
            | otherwise = notesAt' (sf n) ss t

...そして「notesAtinput1.5」を評価すると、戻る前に「2>1.5」を計算する必要があります。ただし、イベント(2、NoteOff 42)はさらに0.5秒間発生しません。したがって、私の出力は、将来発生する入力イベントに依存しているため、停止します。

私はこの効果を「逆説的な因果関係」と呼んでいます。

私はこれをどのように処理するかをかなり前から考えていましたが、必要なのは「a>b」を怠惰に評価できる何らかの形の数値であるという結論に達しました。まあ言ってみれば:

bar :: LazyNumber
bar = 1 + bar

foo :: Bool
foo = bar > 100

...次に、「foo」をTrueと評価します。

そのためにペアノ番号を使用でき、実際に機能することに注意してください。

しかし、効率的にするために、私は自分の番号を次のように表現したいと思います。

data LazyNumber = MoreThan Double | Exactly Double

... LazyNumbersのすべての関数(例: ">")は純粋ですが、これは機能するように変更可能である必要があります...

この時点で、私はちょっと迷っています。したがって、問題は次のとおりです。インタラクティブなリアルタイムアプリケーションで時間を表すために効率的な遅延数を実装することは可能ですか?

編集

私がやっていることには、関数型リアクティブプログラミングという名前があることが指摘されています。エドワード・アムスデンによる論文「関数型リアクティブプログラミングの調査」は良い紹介です。ここに抜粋があります:

これまでのすべての信号関数の実装を含むほとんどのFRP実装は、システムが出力用にFRP式を継続的にリサンプリングする「プルベース」の実装により、イベントの非発生の継続的な再評価に屈します。Reactiveの作業(セクション3.1および4.4)は、Classic FRPのこの問題を解決することを目的としていますが、この作業を信号関数に拡張することはまだ検討されておらず、発生時間の比較の簡単な操作は、プログラマーがチェックし、間違いなく困難であることに依存しています。参照透過性を維持するためにアイデンティティを証明します。

これが問題の核心であるように思われます。私の「ダミーイベント」アプローチとDarkOtterの提案は、「イベントの非発生の継続的な再評価」カテゴリに分類されます。

ナイーブなプログラマーなので、「怠惰な数字を使って、foo/barの例を機能させましょう」と言います。/meは手を振る。その間、YampaSynthを見ていきます。

また、私がやろうとしているように、線形時間に関して数値を「怠惰」にすることは、精度に関して(実)数値を「怠惰」にすることと密接に関連しているように思われます(Exact Real Arithmeticを参照)。つまり、「参照透過性を維持する」ために満たすべき特定の法則を前提として、厳密に純粋なコンテキストからの可変オブジェクト(イベント時間の下限と実数の間隔)を使用したいと考えています。もっと手を振ってごめんなさい。

4

2 に答える 2

5

(おおまかに) 最大のレイテンシーを達成するために、このようなことを少し行うことができます。これは、一部の FRP プログラムで既に行われている可能性があると思います。このアイデアは、次のようなタイプを持つ、あなたが提案したものに似ていると思います:

data Improving n = Greater n (Improving n) | Exact n

comonad のように、このためにあらゆる種類の便利なインスタンスを定義できますが、あなたが言っていることの重要な点は、IO プロセスが次の midi イベントの発生を待っている間に、何らかの方法が必要になるということです。時間とイベントの怠惰な約束で、すぐにあなたのペアを生み出します。イベントは実際のイベントが発生したときにのみ利用可能になりますが、その一部が常にある程度の最大待ち時間の後に利用可能になるように、時間を調整する必要があります。つまり、たとえば 100 ミリ秒待機し、イベントが発生した場合、遅延サンクは (100 ミリ秒以上 (サンク)) になり、次のサンクが同じように動作します。これにより、必要に応じて物事を遅延してインターリーブできます。

MVar と unsafeDupablePerformIO の組み合わせを使用して、古いバージョンの FRP ライブラリで同様のことが行われているのを見たことがあります。アイデアは、IO モナド待機スレッドが値を通知するためにプッシュする MVar があり、入れたサンクが unsafeDupablePerformIO を使用して MVar から読み取ることです (スレッドセーフでべき等であるため、安全である必要があります)。することだと思います)。

次に、待機中のスレッドが長すぎると判断した場合は、別の MVar とそれに付随する次のビットのサンクを作成し、古いものに (Greater (100ms) (thunk)) 値をプッシュします。これにより、レイジーでの評価が可能になります。続く部分。

完璧ではありませんが、500 ミリ秒ではなく 100 ミリ秒先まで待つだけでよいことを意味するはずです。

時間表現をいじりたくない場合は、いつでも midi イベントのストリームを (時間、Maybe イベント) のストリームにして、x ごとに少なくとも 1 回はイベントを生成しているものを確実に挿入できると思います。 MS。

編集:

ここでこのアプローチの簡単な例を作成しました: https://gist.github.com/4359477

于 2012-12-22T14:46:00.827 に答える
3

pipesより多くの入力の要求をパラメーター化できる唯一のストリーミング ライブラリである を使用します。メモの怠惰なストリームをサーバーとして構成します。

notes :: (Proxy p) => MaxTime -> Server p MaxTime (Maybe Note) IO r
notes = runIdentityK $ foreverK $ \maxTime -> do
    -- time out an operation waiting for the next note
    -- deadline is the maxTime parameter we just received
    t <- lift $ getCPUTime
    note <- lift $ timeout (maxTime - t) $ getNote
    respond note

これで完了です。このトリックの詳細については、 Control.Proxy.Tutorialpipesのチュートリアルを参照してください。

ボーナス ポイント: を使用する必要はありませunsafePerformIOんが、それでも構成プログラミングを維持できます。たとえば、最初の 10 音を取りたい場合は、次のようにします。

takeB_ 10 <-< notes

その後、指定された期限までにすべてのメモを取りたい場合は、次のようにします。

query deadline = takeWhileD isJust <-< mapU (\() -> deadline) <-< notes

通常、人々が純粋さが欲しいと言うとき、その本当の意味は、彼らが構成性を望んでいるということです。

于 2012-12-23T03:16:03.013 に答える