Audacity Nyquist(XLISP 2.0(Common Lisp(Lisp言語)))
Nyquistについて
Nyquistは、Audacityで扱えるプラグイン形式のひとつで拡張子はny。音声合成と音楽作曲のための言語でもある。 名称的にはナイキスト周波数を連想するが、それを命名した物理学者Harry Nyquistにちなんでいる。 2024年現在でも標準プラグインのいくつかはNyquistで提供されている。
Audacityでは他にVST、LADSPA、LV2なども扱える。これらはいずれもコンパイルが必要で、開発にはそれ相応の開発環境が必要。 一方NyquistはLisp言語で作られているため、インタプリタで実行出来き、別途コンパイルしておく必要ない。つまりテキストファイルの記述だけで成立するため、開発環境を用意する必要がなく、とてもお手軽である。 Lisp言語は古いため、多くの方言がある。Nyquistは、Common Lisp言語のXLISP 2.0という処理系の流れにある。さらにそれを音声処理用に独自拡張したものと考えてよいと思う。 Lispのハードルが高いと思ったのか、SALという言語もサポートしていて、より通常プログラミング言語に近いコードを書くことも可能になっている。
NyquistプロンプトでHello World!
またAudacityには、あらかじめNyquistプロンプトも用意されているので、下のようにメニューから開いて、直接コードを書くことも可能。インタプリタの強みである。
「ツール」から「Nyquistプロンプト」を選択すると以下ウィンドウが開く。 ここに直接Lisp言語でコードを入力し実行することができる。 ここでは、お決まりの(print "Hello World!")と打ってみた。
上記を実行(適用)すると、以下のポップアップウィンドウにHello World!と表示される。
ファイルから実行する方法
基本的なNyquistプラグインの実行方法で、Audacityのエフェクトから選択できるようになる。 テキストファイルとして作って、拡張子を.nyにすればよい。
Windowsの場合、以下のフォルダに作ったNyquistプラグインを入れておけばAudacityを起動したときに自動的に読み込まれるようになる。
C:\Program Files\Audacity\Plug-Ins\
下は参考までに自作Nyquistプラグイン achapi_fadeout.ny の中身。 普通のプレーンテキストとなっている。
$nyquist plug-in
$version 1
$type process
$name (_ "Achapi Fade Out")
$author (_ "Achapi")
$release
$copyright (_ "")
(defun r-cos (dur)
(abs-env
(mult 0.5
(sum 1
(osc (hz-to-step (/ (* dur 2))) dur *table* 90)
)))
)
(let ((dur (get-duration 1)))
(cond
((< dur 0.2) (mult s (r-cos dur)))
(t (mult s (r-cos dur)))
)
)
実行するときはエフェクトから選択する。
このエフェクトは、ウィンドウを開かず即適用される。
NyquistとLispの学習
Nyquistのプログラミング学習は、カーネギーメロン大学の公式マニュアルを見れば、なんとかなると思う。
https://www.cs.cmu.edu/~rbd/doc/nyquist/
ただし、Lispのルールをある程度知らないと、どうやって作ってよいか分からないと思う。 Lispを知らない人は、以下のLisp方言であるXLISP 2.0のマニュアルも見ておいた方が良い。 Common Lisp系の学習でもよい。
https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-index.htm
Lisp言語について
かなり古い言語で、1958年頃からMITのJohn McCarthyによって開発が始まり、1960年に論文が発表されている。 2024年現在からみると64年も前ということになる。 今も使われている高水準言語としてはFortran、COBOLに次いで古い。 言語の分類としては「関数型言語」だが、副作用ありという感じ。 特にCommon Lispは「手続き型言語」の機能も取り込まれているため、純粋な関数型言語ではない。 それでもプログラミング言語の主流である「手続き型言語」や「オブジェクト指向言語」に慣れている人からすると、かなり異質なプログラミング言語に感じる。
高水準言語
高水準言語の定義について、改めて調べてみると、意外とあやふやだった。
- 人間にとってわかりやすいこと
- プロセッサに依存した処理を書かなくてもよいこと
- 低水準操作を意識なくてよいこと
基本的にハード依存から離れる方向で、抽象度が高まったプログラミング言語というところだろうか。 またEdsger Dijkstraが1968年に提唱した「構造化プログラミング」が扱いやすいかどうかも大きいように思う。
- 順次(sequence)
- 選択(selection)
- 反復(repetition)
プログラミングは、上記で記述すべきというもので、goto文を悪とみなしたものだが、プログラミング言語を習得する上で、この3個は、まず意識すべきことだと思う。 Lispにとっての「選択」は、cond、ifが代表だろうか。「反復」は、再帰とLoop、do、dotimes辺りかな? Lispは他言語とは違った発想だったりするので、これらの機能をどう実現しているのかという観点で学習するとよさそうだ。
S式(symbolic expression)
Lispの名称は「list processor」の略で、式も文も区別されずリストとして扱うところが、他言語と大きく違う。
WikiによるとLispは「式指向言語」と呼ぶらしい。
下は(* 2 (+ 3 4))を木構造で表したもの。これをS式(symbolic expression)と呼ぶ。
なぜこんなS式にしたのかというと、Lisp自身がLispを評価するためらしい。 元々実用性よりも計算論を扱う柔軟なデータ構造を持つ言語として開発された経緯がある。 再帰プログラミングを支援するような作りや、インタプリタという構造から、 人工知能の研究に向いていたので積極的に使われるようになる。 開発当初は人の記述用としてはM式(meta expression)が提案されていたが、McCarthyの院生であったSteve RussellがS式を処理できるインタプリタを実装したことでLisp誕生につながったようだ。 この流れから見ても、学びやすい実用的な言語というよりは、趣味性が高いとっつきにくい孤高の言語のように思う。 だからこそ、今でも根強いファンが多いわけだ。
cons、car、bdr
下図のような単純なリストを例に、2通りの書き方を試してみる。
(cons 1 (cons 2 (cons 3 nil)))
(list 1 2 3)
cons(construct)ひとつは二分木構造をイメージするといいかもしれない。 下にふたつの値がぶら下がっている感じ。 ただ面倒なので、通常はlistを使う。 上記を評価すると、いずれも下のようになり、同じ意味だということが分かる。
(1 2 3)
次にもっともシンプルなconsを試してみる。 左側はcar(カー)と呼ばれている。 右側はcdr(クダ-)と呼ばれている。 carとcdrの名称はIBM由来のようだが特に意味はないようだ。
(cons 1 2)
これを評価すると以下のようになる。
(1 . 2)
carを使って以下の式を評価する。
(car (cons 1 2))
1
consの左側carを指定したので、1となった。右側cdrにすれば2となる。
古い言語ということもあり、方言も多い。 現在はCommon Lisp(ANSI)、Scheme、Emacs Lisp、Clojureに集約されているように思う。 AudacityのNyquistは、XLISP2.0を採用しているようなので、Common Lispがベースになっているようだ。
現在Lisp言語は、実用プログラミング言語としては、脚光を浴びない日陰の存在であるが、こっそりアプリの中に入っていたりする。 有名どころではAutoCADのAutoLISPだろうか。 またEmacsにも採用されているのでUNIX系では馴染みがある。 そしてAudacity、Maximaなどにも内蔵されている。
Lisp言語には独自性と文化があり、世界中にLisperと呼ばれる信者が多いのも納得できる。
個人的にはAudacityのエフェクトを簡単に作れる便利さもあり、実用面と趣味性(言語の構造)の両面で楽しめている。
ポーランド記法
式の記述がポーランド記法というところも特徴のひとつ。 人間よりも機械に歩み寄っている部分。 演算子の位置が独特なので、慣れないと厄介かもしれない。 個人的にはHewlett-Packardの逆ポーランド記法(RPN)の電卓を使っていたので、 違和感がないどころか、結構好きだったりする。 RPNは括弧がない世界だが、Lispは括弧だらけの世界・・・
(1 + 2) * (3 + 4)
上記の普通の表記をポーランド記法で書くと以下のようになる。
(*(+ 1 2)(+ 3 4))
順次読むには不向きであり、文字を図として見ようとしても括弧が多すぎてイマイチ。 しかし、整理して図形にすると、操作に内包されるという感じで論理構造は見やすくなる。 アルゴリズムを作るときは、紙に数学の集合のような図を書いていけば、そのままLispのコードに落とせると思う。
Nyquistでテスト
下記はCommon Lisp(clisp)とNyquist(print)でテストしてみた。
式 | 評価 Common Lisp | 評価 Nyquist | メモ |
(+ 1 2) | 3 | 3 | |
(+ 1 2 3) | 6 | 6 | |
(+ 1/3 1/3 1/3) | 1 | 無反応分数無理? | |
(+ 2/3 1/2) | 7/6 | 無反応分数無理? | |
(- 2 1) | 1 | 1 | |
(- 3 2 1) | 0 | 0 | |
(* 2 3) | 6 | 6 | |
(* 2 3 4) | 24 | 24 | |
(/ 4 2) | 2 | 2 | |
(/ 4 2 2) | 1 | 1 | |
(/ 4 3) | 4/3 | 1 | |
(/ 4 3.0) | 1.3333333 | 1.3333333 | |
(+(* 2 2)(* 2 3)) | 10 | 10 | |
(< 1 2) | T | 下記参照 | true |
(> 1 2) | NIL | 下記参照 | Null |
(<= 1 2) | T | 下記参照 | |
(>= 1 2) | NIL | 下記参照 | |
(= 1 1) | T | 下記参照 | |
(/= 1 1) | NIL | 下記参照 | 異なる場合 |
(atom 1) | T | ||
(atom 'Japan) | T | クオートでシンボル化 | |
(rem 4 3) | 1 | 1 | 剰余 |
(expt 3.0 2) | 9.0 | 9.0 | 3^2 小数にすること |
(sqrt 2.0) | 1.4142135 | 1.414214 | 小数にすること |
(sqrt -1.0) | #C(0 1.0) | 無反応 | 複素数 |
(sin 1.0) | 0.84147096 | 0.84147096 | ラジアン |
(1+ 2) | 3 | 3 | インクリメント |
(1- 2) | 1 | 1 | デクリメント |
(max 1 2 3 4) | 4 | 4 | |
(min 4 3 2 1) | 1 | 1 |
他にもいろいろ使える関数はある。 上記比較演算子の場合、Audacityでは以下のように書かないとポップアップには表示されない。 下記はifを使って条件分岐させている。 基本的な書き方はCommon Lispの書き方でおおむね通用する。
(if (= 3 3)(print "T")(print "NIL"))
記述内容は、以下のようになっている。
(if 条件式 真の場合に評価する式 偽の場合に評価する式)
3と3が同じかどうか比較し、同じであればT、違えばNILが表示される。
現代的なモダンな考え方
Lisp言語は上記が最も印象的だが、ほかにも多くの特徴があり、動的型付け、ガベージコレクションなどは、モダンな印象すらある。 現代の様々な高水準言語は、見た目はC言語的でありながら、オブジェクト指向言語と共にLispの思想を取り入れて開発されてきた。 その原点のひとつであるLispを知ることは他のプログラミング言語を学ぶ上でも有用であると思う。 C言語、Lisp言語、オブジェクト指向言語は、学んでおいて損はしないプログラミング言語だ。