diff --git a/Release/EffectsMenuDefaults.xml b/Release/EffectsMenuDefaults.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6d55e94ce88704bea60dd5061da791559c72779
--- /dev/null
+++ b/Release/EffectsMenuDefaults.xml
@@ -0,0 +1,98 @@
+<EffectMenuList>
+    <Group>
+        <!-- Effects menu group name; audio dynamics compression, not data compression -->
+        <Name>Volume and Compression</Name>
+        <Effects>
+            <Effect>Amplify</Effect>
+            <Effect>Compressor</Effect>
+            <Effect>Limiter</Effect>
+            <Effect>Normalize</Effect>
+            <Effect>Loudness Normalization</Effect>
+            <Effect>Auto Duck</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <!-- Effects menu group name -->
+        <Name>Fading</Name>
+        <Effects>
+            <Effect>Fade In</Effect>
+            <Effect>Fade Out</Effect>
+            <Effect>Studio Fade Out</Effect>
+            <Effect>Adjustable Fade</Effect>
+            <Effect>Crossfade Clips</Effect>
+            <Effect>Crossfade Tracks</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <Name>Pitch and Tempo</Name>
+        <Effects>
+            <Effect>Change Pitch</Effect>
+            <Effect>Change Speed and Pitch</Effect>
+            <Effect>Change Tempo</Effect>
+            <Effect>Paulstretch</Effect>
+            <Effect>Sliding Stretch</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <Name>EQ and Filters</Name>
+        <Effects>
+            <Effect>Bass and Treble</Effect>
+            <Effect>Graphic EQ</Effect>
+            <Effect>Filter Curve EQ</Effect>
+            <Effect>High-Pass Filter</Effect>
+            <Effect>Low-Pass Filter</Effect>
+            <Effect>Shelf Filter</Effect>
+            <Effect>Notch Filter</Effect>
+            <Effect>Classic Filters</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <Name>Noise Removal and Repair</Name>
+        <Effects>
+            <Effect>Click Removal</Effect>
+            <Effect>Noise Reduction</Effect>
+            <Effect>Noise Gate</Effect>
+            <Effect>Repair</Effect>
+            <Effect>Clip Fix</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <Name>Delay and Reverb</Name>
+        <Effects>
+            <Effect>Echo</Effect>
+            <Effect>Reverb</Effect>
+            <Effect>Delay</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <Name>Distortion and Modulation</Name>
+        <Effects>
+            <Effect>Tremolo</Effect>
+            <Effect>Distortion</Effect>
+            <Effect>Wahwah</Effect>
+            <Effect>Phaser</Effect>
+            <Effect>Vocoder</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <!-- Effects menu group name -->
+        <Name>Special</Name>
+        <Effects>
+            <Effect>Repeat</Effect>
+            <Effect>Reverse</Effect>
+            <Effect>Invert</Effect>
+            <Effect>Truncate Silence</Effect>
+            <Effect>Vocal Reduction and Isolation</Effect>
+            <Effect>Vocal Remover</Effect>
+        </Effects>
+    </Group>
+    <Group>
+        <Name>Spectral Tools</Name>
+        <Effects>
+            <Effect>Spectral Delete</Effect>
+            <Effect>Spectral Edit Multi Tool</Effect>
+            <Effect>Spectral Edit Parametric EQ</Effect>
+            <Effect>Spectral Edit Shelves</Effect>
+        </Effects>
+    </Group>
+</EffectMenuList>
diff --git a/Release/Languages/af/audacity.mo b/Release/Languages/af/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..970a3108a5d3c06060accf665d4c44bece03cfa4
Binary files /dev/null and b/Release/Languages/af/audacity.mo differ
diff --git a/Release/Languages/ar/audacity.mo b/Release/Languages/ar/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..145839f308bab09a821b4e99e7c6bad77f749f77
Binary files /dev/null and b/Release/Languages/ar/audacity.mo differ
diff --git a/Release/Languages/be/audacity.mo b/Release/Languages/be/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..1bdba3c3c531919df9817fb767f89704f70cb179
Binary files /dev/null and b/Release/Languages/be/audacity.mo differ
diff --git a/Release/Languages/bg/audacity.mo b/Release/Languages/bg/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c75306e8041b0038954068efafd17061dab8b193
Binary files /dev/null and b/Release/Languages/bg/audacity.mo differ
diff --git a/Release/Languages/bn/audacity.mo b/Release/Languages/bn/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..10302253e6120235c115e80167e71eb6c51c6d33
Binary files /dev/null and b/Release/Languages/bn/audacity.mo differ
diff --git a/Release/Languages/bs/audacity.mo b/Release/Languages/bs/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..d160e5bc7992afb503d05a2b043023a0a90c704e
Binary files /dev/null and b/Release/Languages/bs/audacity.mo differ
diff --git a/Release/Languages/ca/audacity.mo b/Release/Languages/ca/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..fd622b2409055711355f5eddf84a474e402a31b4
Binary files /dev/null and b/Release/Languages/ca/audacity.mo differ
diff --git a/Release/Languages/ca_ES@valencia/audacity.mo b/Release/Languages/ca_ES@valencia/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..d92359b54c6cb1e972ec36ec98010fa9b7dd60e8
Binary files /dev/null and b/Release/Languages/ca_ES@valencia/audacity.mo differ
diff --git a/Release/Languages/co/audacity.mo b/Release/Languages/co/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..bb7e30a6a55c6fb4d2026d814b7f140385109069
Binary files /dev/null and b/Release/Languages/co/audacity.mo differ
diff --git a/Release/Languages/cs/audacity.mo b/Release/Languages/cs/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..05f3b60ae4abf0665b9219406f78484a3d5ce9d4
Binary files /dev/null and b/Release/Languages/cs/audacity.mo differ
diff --git a/Release/Languages/cy/audacity.mo b/Release/Languages/cy/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..d0ad28dd94039c1f4d176cabc939c5e5355f2154
Binary files /dev/null and b/Release/Languages/cy/audacity.mo differ
diff --git a/Release/Languages/da/audacity.mo b/Release/Languages/da/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..3a88d48622acd6df000b0bc5564c83e535bba131
Binary files /dev/null and b/Release/Languages/da/audacity.mo differ
diff --git a/Release/Languages/de/audacity.mo b/Release/Languages/de/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..e2bf17fd1fe76bf33205afb32bbad28451861935
Binary files /dev/null and b/Release/Languages/de/audacity.mo differ
diff --git a/Release/Languages/el/audacity.mo b/Release/Languages/el/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..b5417eefacc193dfb17025964d9e865068309c15
Binary files /dev/null and b/Release/Languages/el/audacity.mo differ
diff --git a/Release/Languages/es/audacity.mo b/Release/Languages/es/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..0b62b6b64e31c1a8050acb06f9afd117dfd6b206
Binary files /dev/null and b/Release/Languages/es/audacity.mo differ
diff --git a/Release/Languages/eu/audacity.mo b/Release/Languages/eu/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..40d5ae1d88b3c0ebfb376ceedb56c18a68ef9871
Binary files /dev/null and b/Release/Languages/eu/audacity.mo differ
diff --git a/Release/Languages/eu_ES/audacity.mo b/Release/Languages/eu_ES/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..473499ac3348f6e7c1e71e6dfd470845a8231782
Binary files /dev/null and b/Release/Languages/eu_ES/audacity.mo differ
diff --git a/Release/Languages/fa/audacity.mo b/Release/Languages/fa/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..4bcabb9d5026e8e341c9c7b2f9bbac834a1c3736
Binary files /dev/null and b/Release/Languages/fa/audacity.mo differ
diff --git a/Release/Languages/fi/audacity.mo b/Release/Languages/fi/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..b3b2eee00e682f941a53a282f439facc6740208f
Binary files /dev/null and b/Release/Languages/fi/audacity.mo differ
diff --git a/Release/Languages/fr/audacity.mo b/Release/Languages/fr/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..8621984b56ce0ef75a4b10674aa88c0ed77b539b
Binary files /dev/null and b/Release/Languages/fr/audacity.mo differ
diff --git a/Release/Languages/ga/audacity.mo b/Release/Languages/ga/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..6c18dbd40550ccdd8011e66679a96ba76c2bf4fc
Binary files /dev/null and b/Release/Languages/ga/audacity.mo differ
diff --git a/Release/Languages/gl/audacity.mo b/Release/Languages/gl/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..0fbf0e0655d66532742295688eb1e5093da37431
Binary files /dev/null and b/Release/Languages/gl/audacity.mo differ
diff --git a/Release/Languages/he/audacity.mo b/Release/Languages/he/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..a5bf9570a24ac12d7ec4f0fbe0cf95bbd900474b
Binary files /dev/null and b/Release/Languages/he/audacity.mo differ
diff --git a/Release/Languages/hi/audacity.mo b/Release/Languages/hi/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..f3b2e08e3ba1ceac7dffeec06e77bce5bca6ae54
Binary files /dev/null and b/Release/Languages/hi/audacity.mo differ
diff --git a/Release/Languages/hr/audacity.mo b/Release/Languages/hr/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..316678e61d8d8371c372141f9d6f2d3e72e3efcd
Binary files /dev/null and b/Release/Languages/hr/audacity.mo differ
diff --git a/Release/Languages/hu/audacity.mo b/Release/Languages/hu/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..005db52746e3cbfc75aa690de671b864ecd0e8fa
Binary files /dev/null and b/Release/Languages/hu/audacity.mo differ
diff --git a/Release/Languages/hy/audacity.mo b/Release/Languages/hy/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..fe91b5386d5db6e80d66af432a93681a2b30c54e
Binary files /dev/null and b/Release/Languages/hy/audacity.mo differ
diff --git a/Release/Languages/id/audacity.mo b/Release/Languages/id/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..49daf2b5ac1f12a6044ad580efc8ae91c1944b4f
Binary files /dev/null and b/Release/Languages/id/audacity.mo differ
diff --git a/Release/Languages/it/audacity.mo b/Release/Languages/it/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..4a53e043bf5b363f9cfe991cd47b8ce544617496
Binary files /dev/null and b/Release/Languages/it/audacity.mo differ
diff --git a/Release/Languages/ja/audacity.mo b/Release/Languages/ja/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c7d338e96a93e35482ac9d3368e072173f4b6ecc
Binary files /dev/null and b/Release/Languages/ja/audacity.mo differ
diff --git a/Release/Languages/ka/audacity.mo b/Release/Languages/ka/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..31cc3243c6ac79c856628862c1ce460340e761a5
Binary files /dev/null and b/Release/Languages/ka/audacity.mo differ
diff --git a/Release/Languages/km/audacity.mo b/Release/Languages/km/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c178d483a6f80dc9ca9c5307761a4899c89c33d2
Binary files /dev/null and b/Release/Languages/km/audacity.mo differ
diff --git a/Release/Languages/ko/audacity.mo b/Release/Languages/ko/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..0391d3dfb07a45814005d20666812008111f7829
Binary files /dev/null and b/Release/Languages/ko/audacity.mo differ
diff --git a/Release/Languages/lt/audacity.mo b/Release/Languages/lt/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..6fdfbe025623b17d0a08f8c8ecaaaf6274cd820f
Binary files /dev/null and b/Release/Languages/lt/audacity.mo differ
diff --git a/Release/Languages/mk/audacity.mo b/Release/Languages/mk/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..2f9d3cfdf1b71585ec0a076ac3d33a2aeb087daa
Binary files /dev/null and b/Release/Languages/mk/audacity.mo differ
diff --git a/Release/Languages/mr/audacity.mo b/Release/Languages/mr/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..931fdb275ce88bc9506645b347ffc3601b444bf7
Binary files /dev/null and b/Release/Languages/mr/audacity.mo differ
diff --git a/Release/Languages/my/audacity.mo b/Release/Languages/my/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..5226392ed5da27b5f7c836bb6f56a27e95e024eb
Binary files /dev/null and b/Release/Languages/my/audacity.mo differ
diff --git a/Release/Languages/nb/audacity.mo b/Release/Languages/nb/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..db3c3c18ba1a4e2a749e7a0de27d806f068a030f
Binary files /dev/null and b/Release/Languages/nb/audacity.mo differ
diff --git a/Release/Languages/nl/audacity.mo b/Release/Languages/nl/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..3e83e1d73e52f322900a698977367171502850c5
Binary files /dev/null and b/Release/Languages/nl/audacity.mo differ
diff --git a/Release/Languages/oc/audacity.mo b/Release/Languages/oc/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..a90bc1eb7f99b6badec05e133bfc0d7e0ad8466e
Binary files /dev/null and b/Release/Languages/oc/audacity.mo differ
diff --git a/Release/Languages/pl/audacity.mo b/Release/Languages/pl/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..95203ebbf1d7af0fc25a33855d3d55f93d69d340
Binary files /dev/null and b/Release/Languages/pl/audacity.mo differ
diff --git a/Release/Languages/pt_BR/audacity.mo b/Release/Languages/pt_BR/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..00d56a97e46d5226ae9fa3565afa04f635dd5cb8
Binary files /dev/null and b/Release/Languages/pt_BR/audacity.mo differ
diff --git a/Release/Languages/pt_PT/audacity.mo b/Release/Languages/pt_PT/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..87cd0b56e1733fbc2c25bd8273a9a3fb228811af
Binary files /dev/null and b/Release/Languages/pt_PT/audacity.mo differ
diff --git a/Release/Languages/ro/audacity.mo b/Release/Languages/ro/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..bcdd446eeea043b75af8e0624e270d833eb4e59f
Binary files /dev/null and b/Release/Languages/ro/audacity.mo differ
diff --git a/Release/Languages/ru/audacity.mo b/Release/Languages/ru/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..d59648b9dfaa5217b50e4ae468e477f09b95c51f
Binary files /dev/null and b/Release/Languages/ru/audacity.mo differ
diff --git a/Release/Languages/sk/audacity.mo b/Release/Languages/sk/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..6c7ba98fd1925616177bb65647b53453555519b4
Binary files /dev/null and b/Release/Languages/sk/audacity.mo differ
diff --git a/Release/Languages/sl/audacity.mo b/Release/Languages/sl/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..eb01b59051512703f5c8c1798c49e7b03653bd75
Binary files /dev/null and b/Release/Languages/sl/audacity.mo differ
diff --git a/Release/Languages/sr_RS/audacity.mo b/Release/Languages/sr_RS/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..be8e9fae57149e61f99f8c597e6e046e5841aa0d
Binary files /dev/null and b/Release/Languages/sr_RS/audacity.mo differ
diff --git a/Release/Languages/sr_RS@latin/audacity.mo b/Release/Languages/sr_RS@latin/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c030cc81c6f1776970921d73d70c5ef0fe47fa1d
Binary files /dev/null and b/Release/Languages/sr_RS@latin/audacity.mo differ
diff --git a/Release/Languages/sv/audacity.mo b/Release/Languages/sv/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..77085ad35ad2dcd886f611079ba9669274ffd1c1
Binary files /dev/null and b/Release/Languages/sv/audacity.mo differ
diff --git a/Release/Languages/ta/audacity.mo b/Release/Languages/ta/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c1b4ab02d8c428ad1a870e517fa778296f632edc
Binary files /dev/null and b/Release/Languages/ta/audacity.mo differ
diff --git a/Release/Languages/tg/audacity.mo b/Release/Languages/tg/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..8badb1cad64b05d562e4a7e9f4dee9192cc247ef
Binary files /dev/null and b/Release/Languages/tg/audacity.mo differ
diff --git a/Release/Languages/tr/audacity.mo b/Release/Languages/tr/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..ed416dc2ec7f8d1620773da5ae1e4bc560d887a1
Binary files /dev/null and b/Release/Languages/tr/audacity.mo differ
diff --git a/Release/Languages/uk/audacity.mo b/Release/Languages/uk/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..278ebe41918685d8be5e15816e6a185538ee23c3
Binary files /dev/null and b/Release/Languages/uk/audacity.mo differ
diff --git a/Release/Languages/vi/audacity.mo b/Release/Languages/vi/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c34aba21c9245d9d4a91fea032992e8853d92d48
Binary files /dev/null and b/Release/Languages/vi/audacity.mo differ
diff --git a/Release/Languages/zh_CN/audacity.mo b/Release/Languages/zh_CN/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..42b2bba6c973650f95ba433e3970ee71709cfc24
Binary files /dev/null and b/Release/Languages/zh_CN/audacity.mo differ
diff --git a/Release/Languages/zh_TW/audacity.mo b/Release/Languages/zh_TW/audacity.mo
new file mode 100644
index 0000000000000000000000000000000000000000..953ec249a1a179e1d5d59fdb604fb0bb5071234f
Binary files /dev/null and b/Release/Languages/zh_TW/audacity.mo differ
diff --git a/Release/nyquist/aud-do-support.lsp b/Release/nyquist/aud-do-support.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..23981f5796a210f89ffd49a30fd9b7a6f6f5c79a
--- /dev/null
+++ b/Release/nyquist/aud-do-support.lsp
@@ -0,0 +1,236 @@
+;;; A collection of helper functions and macros to make scripting Audacity commands
+;;; easier and more Lisp-like.
+;;;
+;;; Copyright 2018 - 2020 Audacity Team
+;;; Steve Daulton
+;;; Released under terms of the GNU General Public License version 2:
+;;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+
+
+(defun char-remove (ch str)
+  ;;; Remove all occurrences of character from string.
+  (do ((out "")
+       (i 0 (1+ i)))
+      ((= i (length str)) out)
+    (if (char/= (char str i) ch)
+        (setf out (format nil "~a~a" out (char str i))))))
+
+(defun number-string-p (str)
+  ;;; like digit-char-p for strings
+  (unless (stringp str)
+    (return-from number-string-p nil))
+  (let ((num (string-to-number str)))
+    (if (numberp num)
+        num
+        nil)))
+
+(defmacro string-append (str &rest strs)
+  ;;; Append one or more strings to 'str'
+  `(setf ,str (strcat ,str ,@strs)))
+
+(defun aud-print-command (cmd)
+  ;;; Print a quick reference for command arguments.
+  (let ((help-data (first (aud-do-command "Help" :command cmd :format "LISP")))
+        (out (format nil "(aud-do-command ~s [:key val ...])~%" (string-downcase cmd))))
+    (cond
+      ((string-equal help-data "Command not found")
+          ;Debug out can be copied on all platforms.
+          (format t "~a~a." out help-data)
+          (format nil "~a~a." out help-data))
+      (t  (setf help-data (eval-string (quote-string help-data)))
+          (let ((params (second (assoc 'params help-data))))
+            (dolist (p params)
+              (setf out (format nil "~a  :~a (~a) default: ~s~%"
+                                out
+                                (string-downcase (second (assoc 'key p)))
+                                (second (assoc 'type p))
+                                (second (assoc 'default p))))
+              (let ((enums (assoc 'enum p)))
+                (when enums
+                  (setf out (format nil "~a    [" out))
+                  (dolist (e (second enums))
+                    (setf out (format nil "~a~s " out e)))
+                  (setf out (format nil "~a]~%" (string-right-trim " " out)))))))
+          (format t "~a" out)
+          out))))
+
+
+(defun aud-do-command (id &rest params)
+  ;; Translate aud-do-command, to (aud-do "command").
+  ;; To avoid unnecessary overhead, only validate when debugging enabled
+  ;; 'aud-import-commands' passes params as a list, so we need to unpack it.
+  (when (and (= (length params) 1)
+             (listp (first params)))
+    (setf params (first params)))
+  (when *tracenable*
+    (aud-check-debug-cache)
+    (let (val-allowed type enums pstr
+          (id-valid (aud-verify-command-id id))
+          (valid-params (aud-get-command-params id))
+          (keystr ""))
+      (if (not id-valid)
+          ; The command may still be valid as 
+          ; "GetInfo: Type=Commands" does not return all valid AUD-DO commands.
+          (format t "Debug data unavailable: ~s.~%" id)
+          ;; Command ID recognised, so check params.
+          (dolist (p params)
+            (setf pstr (format nil "~a" p))
+            (cond
+              ((char= (char pstr 0) #\:) ;keyword
+                (setf keystr (subseq pstr 1))
+                (let ((kf (dolist (vp valid-params nil)
+                            (when (string-equal (second (assoc 'key vp)) keystr)
+                              (return vp)))))
+                  (cond
+                    (kf ;keyword found
+                      (setf type (second (assoc 'type kf)))
+                      (setf enums (second (assoc 'enum kf)))
+                      (cond
+                        ((member type '("int" "float" "double") :test 'string-equal)
+                          (setf val-allowed "number"))
+                        ((string-equal type "enum")
+                          (setf val-allowed enums)) ;a list
+                        (t (setf val-allowed type)))) ;"string" "bool" or NIL
+                    ;; Invalid keyword, so give some helpful hints:
+                    (t (format t "Invalid key in ~s :~a~%" id keystr)
+                       ;; pretty print valid keywords
+                       (format t "Valid keys for ~a are:~%" id)
+                       (dolist (vp valid-params)
+                         (dolist (item vp)
+                           (let ((itype (first item)))
+                             (case itype
+                              ('KEY (format t "   ~a " (second item)))
+                              ('TYPE (when (string-not-equal (second item) "enum")
+                                       (format t "(~a) " (second item))))
+                              ('ENUM (format t "[~a]"
+                                        (string-trim "()"
+                                            (format nil "~a" (second item))))))))
+                         (format t "~%"))))))
+              (t  ;key value
+                (cond
+                  ((not val-allowed)
+                      (format t "Too many arguments: ~s :~a~%" id keystr))
+                  ((listp val-allowed)
+                      (unless (member pstr enums :test 'string=) ;case sensitive
+                        (format t "Invalid enum in ~s :~a - ~s~%" id keystr p)
+                        (format t "Options are:~%  ~a~%" enums)))
+                  ((string= val-allowed "bool")
+                      (unless (or (string= pstr "0") (string= pstr "1"))
+                        (format t "~s :~a value must be 0 or 1~%" id keystr)))
+                  ((string= val-allowed "number")
+                      (unless (or (numberp p) (number-string-p p))
+                        (format t "~s :~a value must be a number: ~s~%" id keystr p)))
+                  ((string= val-allowed "string")
+                      (unless (stringp p)
+                        (format t "~s :~a value must be a string: ~a~%" id keystr p))))
+                (psetq  val-allowed nil
+                        type  nil
+                        enums nil)))))))
+  ;; Send the command
+  (let ((cmd (format nil "~a:" id)))
+    (dolist (p params)
+      (setf p (format nil "~a" p))
+      (string-append cmd
+          (cond
+            ((char= (char p 0) #\:) ;keyword
+              (format nil " ~a=" (subseq p 1)))
+            (t  ;key value
+              (format nil "~s" p)))))
+    (aud-do cmd)))
+
+
+(defun aud-import-commands (&aux cmd)
+  ;; Generate function stubs in the form (aud-<command> [&key arg ...])
+  ;; Call once to make "aud-<command>"s available.
+  ;; We don't call this on load, as we don't want to delay loading Nyquist unnecessarily.
+  (aud-check-debug-cache)
+  (dolist (cmd (aud-get-command))
+    (setf cmd (second (assoc 'id cmd)))
+    (let ((symb (intern (string-upcase (format nil "aud-~a" cmd)))))
+      (eval `(defun ,symb (&rest args)
+              (aud-do-command ,cmd args))))))
+
+
+(defun aud-check-debug-cache ()
+  ;;; Load aud-do-debug-data-cache, updating if necessary.
+  (let ((fqname (format nil "~a~a~a"
+                       (string-right-trim (string *file-separator*) (get-temp-path))
+                       *file-separator*
+                       "aud-do-debug-data-cache.lsp")))
+    (cond ;Update if necessary
+      ((fboundp 'aud-do-version)  ;cache is loaded
+        ;; Refresh cache if versions don't match.
+        ;; 'aud-do-version' tests the interned version.
+        ;; 'autoload-helper' tests the disk version and prevents repeating cache refresh in the initial session.
+        (unless (or (string= (format nil "~a" (aud-do-version))
+                             (format nil "~a" (get '*audacity* 'version)))
+                    (string= (format nil "~a" (autoload-helper fqname 'aud-do-version nil))
+                             (format nil "~a" (get '*audacity* 'version))))
+          (aud-refresh-debug-data-cache fqname)))
+      ;cache not loaded, so try loading and refresh if we can't.
+      ((not (load fqname :verbose t))
+        (aud-refresh-debug-data-cache fqname)))))
+
+
+(defun aud-refresh-debug-data-cache (fqname)
+  ;; Cache the list of command profiles as function "aud-get-command", and load it.
+  (labels ((disable-plugins (typestring &aux oldval)
+            ;; Disable plug-ins of type 'typestring' and return it's previous value.
+            (let ((getcmd (format nil "GetPreference: Name=\"~a/Enable\"" typestring)))
+              (setf oldval (first (aud-do getcmd)))
+              (do-set-val typestring oldval 0) ;Disable all plug-ins
+              oldval))  ;may be 0, 1 or ""
+          (do-set-val (typestring oldval newval)
+            ;; If plug-in type was previously enabled ('oldval = true, "1" or empty), set it to 'newval'.
+            (let ((setcmd (format nil "SetPreference: Name=\"/~a/Enable\" Value=" typestring)))
+              (when (and oldval (or (string= oldval "")(string= oldval "1")))
+                (aud-do (format nil "~a~s" setcmd (if (= newval 0) 0 oldval))))))
+          (get-usable-commands ()
+            ;; Disable plug-ins, get list of remaining commands, then re-enable plug-ins if previously enabled.
+            ;; Return list of commands.
+            (let ((cmds '(("Nyquist" ny)("LADSPA" la)("LV2" lv)("VST" vs)("AudioUnit" au)("Vamp" va)))
+                  info)
+              (dolist (cmd cmds)
+                (setf (nth 1 cmd) (disable-plugins (nth 0 cmd))))
+              (setf info (first (aud-do "getinfo: type=Commands format=LISP"))) ;Get scriptables and built-in effects
+              (dolist (cmd cmds)
+                (do-set-val (nth 0 cmd) (nth 1 cmd) 1))  ;Re-enable plug-ins
+              info)))
+      (let ((fp (open fqname :direction :output)))
+        ;; Write cache file, or return error.
+        (cond
+          (fp (format fp
+";; Intended for internal use by aud-do-command.~%
+(defun aud-do-version ()
+  '~a)~%
+(defun aud-verify-command-id (id)
+  (second (assoc 'id (aud-get-command id))))~%
+(defun aud-get-command-params (id)
+  (second (assoc 'params (aud-get-command id))))~%
+(defun aud-get-command (&optional id &aux cmds)
+  ;; If id supplied, return command profile or nil.
+  ;; Else, return full list.
+  (setf cmds
+  '~a)
+  ;; Return all commands, or one command or nil.
+  (if id
+      (dolist (cmd cmds nil)
+        (when (string-equal (string id) (second (assoc 'id cmd)))
+          (return cmd)))
+      cmds))"
+                      (get '*audacity* 'version)
+                      (get-usable-commands))
+              (format t "Debug data cache refreshed.~%")
+              (close fp)
+              (unless (load fqname :verbose t) ;load the file
+                (error "Unable to load" fqname))) ;assert
+          (t  (format t "Error: ~a cannot be written." fqname))))))
+
+
+;; Try to load AUD- command cache.
+(when (get-temp-path)
+  (let ((fqname (format nil "~a~a~a"
+                        (string-right-trim (string *file-separator*) (get-temp-path))
+                        *file-separator*
+                        "aud-do-debug-data-cache.lsp")))
+    (load fqname :verbose t)))
diff --git a/Release/nyquist/dspprims.lsp b/Release/nyquist/dspprims.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..2085556acfee43c961dd63431fa3b49c763796bc
--- /dev/null
+++ b/Release/nyquist/dspprims.lsp
@@ -0,0 +1,728 @@
+;; dspprims.lsp -- interface to dsp primitives
+
+;; ARESON - notch filter
+;; 
+(defun areson (s c b &optional (n 0))
+  (multichan-expand "ARESON" #'nyq:areson
+    '(((SOUND) nil) ((NUMBER SOUND) "center")
+      ((NUMBER SOUND) "bandwidth") ((INTEGER) nil))
+    s c b n))
+
+(setf areson-implementations
+      (vector #'snd-areson #'snd-aresonvc #'snd-aresoncv #'snd-aresonvv))
+
+;; NYQ:ARESON - notch filter, single channel
+;;
+(defun nyq:areson (signal center bandwidth normalize)
+  (select-implementation-1-2 "ARESON" areson-implementations 
+   signal center bandwidth normalize))
+
+
+;; hp - highpass filter
+;; 
+(defun hp (s c)
+  (multichan-expand "HP" #'nyq:hp
+    '(((SOUND) "snd") ((NUMBER SOUND) "cutoff")) s c))
+
+(setf hp-implementations
+      (vector #'snd-atone #'snd-atonev))
+
+;; NYQ:hp - highpass filter, single channel
+;;
+(defun nyq:hp (s c)
+  (select-implementation-1-1 "HP" hp-implementations s c))
+
+
+;; comb-delay-from-hz -- compute the delay argument
+;;
+(defun comb-delay-from-hz (hz)
+  (recip hz))
+
+;; comb-feedback -- compute the feedback argument
+;;
+(defun comb-feedback (decay delay)
+  (s-exp (mult -6.9087 delay (recip decay))))
+
+;; COMB - comb filter
+;; 
+;; this is just a feedback-delay with different arguments
+;;
+(defun comb (snd decay hz)
+  (multichan-expand "COMB" #'nyq:comb
+    '(((SOUND) "snd") ((NUMBER SOUND) "decay") ((POSITIVE) "hz"))
+    snd decay hz))
+
+
+(defun nyq:comb (snd decay hz)
+  (let (delay feedback len d)
+    ; convert decay to feedback
+    (setf delay (/ (float hz)))
+    (setf feedback (comb-feedback decay delay))
+    (nyq:feedback-delay snd delay feedback "COMB")))
+
+;; ALPASS - all-pass filter
+;; 
+(defun alpass (snd decay hz &optional min-hz)
+  (multichan-expand "ALPASS" #'nyq:alpass
+    '(((SOUND) "snd") ((NUMBER SOUND) "decay")
+      ((POSITIVE SOUND) "hz") ((POSITIVE-OR-NULL) "min-hz"))
+    snd decay hz min-hz))
+  
+(defun nyq:alpass (snd decay hz min-hz)
+  (let (delay feedback len d)
+    ; convert decay to feedback, iterate over array if necessary
+    (setf delay (comb-delay-from-hz hz))
+    (setf feedback (comb-feedback decay delay))
+    (nyq:alpass1 snd delay feedback min-hz)))
+
+
+;; CONST -- a constant at control-srate
+;;
+(defun const (value &optional (dur 1.0))
+  (ny:typecheck (not (numberp value))
+    (ny:error "CONST" 1 '((NUMBER) "value") value))
+  (ny:typecheck (not (numberp dur))
+    (ny:error "CONST" 2 '((NUMBER) "dur") dur))
+  (let ((d (get-duration dur)))
+    (snd-const value *rslt* *CONTROL-SRATE* d)))
+
+
+;; CONVOLVE - fast convolution
+;; 
+(defun convolve (s r)
+  (multichan-expand "CONVOLVE" #'nyq:convolve
+    '(((SOUND) nil) ((SOUND) nil)) s r))
+
+(defun nyq:convolve (s r)
+  (snd-convolve s (force-srate (snd-srate s) r)))
+
+
+;; FEEDBACK-DELAY -- (delay is quantized to sample period)
+;;
+(defun feedback-delay (snd delay feedback)
+  (multichan-expand "FEEDBACK-DELAY" #'nyq:feedback-delay 
+    '(((SOUND) "snd") ((NUMBER) "delay") ((NUMBER SOUND) "feedback"))
+    snd delay feedback))
+  
+
+;; SND-DELAY-ERROR -- report type error
+;;
+(defun snd-delay-error (snd delay feedback)
+  (error "FEEDBACK-DELAY with variable delay is not implemented"))
+
+
+(setf feedback-delay-implementations
+      (vector #'snd-delay #'snd-delay-error #'snd-delaycv #'snd-delay-error))
+
+
+;; NYQ:FEEDBACK-DELAY -- single channel delay
+;;
+(defun nyq:feedback-delay (snd delay feedback &optional (src "FEEDBACK-DELAY"))
+  (select-implementation-1-2 src feedback-delay-implementations 
+                             snd delay feedback))
+
+
+;; SND-ALPASS-ERROR -- report type error
+;;
+(defun snd-alpass-error (snd delay feedback)
+  (error "ALPASS with constant decay and variable hz is not implemented"))
+
+
+(if (not (fboundp 'snd-alpasscv))
+    (defun snd-alpasscv (snd delay feedback min-hz)
+      (error "snd-alpasscv (ALPASS with variable decay) is not implemented")))
+(if (not (fboundp 'snd-alpassvv))
+    (defun snd-alpassvv (snd delay feedback min-hz)
+      (error "snd-alpassvv (ALPASS with variable decay and feedback) is not implemented")))
+      
+
+(defun nyq:alpassvv (the-snd delay feedback min-hz)
+    (let (max-delay)
+      (ny:typecheck (or (not (numberp min-hz)) (<= min-hz 0))
+        (ny:error "ALPASS" 4 '((POSITIVE) "min-hz") min-hz))
+      (setf max-delay (/ (float min-hz)))
+      ; make sure delay is between 0 and max-delay
+      ; use clip function, which is symmetric, with an offset
+      (setf delay (snd-offset (clip (snd-offset delay (* max-delay -0.5))
+                                    (* max-delay 0.5))
+                              (* max-delay 0.5)))
+      ; now delay is between 0 and max-delay, so we won't crash nyquist when
+      ; we call snd-alpassvv, which doesn't test for out-of-range data
+      (snd-alpassvv the-snd delay feedback max-delay)))
+
+
+;; NYQ:SND-ALPASS -- ignores min-hz argument and calls snd-alpass
+;;
+(defun nyq:snd-alpass (snd delay feedback min-hz)
+  (snd-alpass snd delay feedback))
+
+;; NYQ:SND-ALPASSCV -- ignores min-hz argument and calls snd-alpasscv
+;;
+(defun nyq:snd-alpasscv (snd delay feedback min-hz)
+  (snd-alpasscv snd delay feedback))
+
+(setf alpass-implementations
+      (vector #'nyq:snd-alpass #'snd-alpass-error
+              #'nyq:snd-alpasscv #'nyq:alpassvv))
+
+
+;; NYQ:ALPASS1 -- single channel alpass
+;;
+(defun nyq:alpass1 (snd delay feedback min-hz)
+  (select-implementation-1-2 "ALPASS" alpass-implementations
+                              snd delay feedback min-hz))
+
+;; CONGEN -- contour generator, patterned after gated analog env gen
+;;
+(defun congen (gate rise fall)
+  (multichan-expand "CONGEN" #'snd-congen
+    '(((SOUND) "gate") ((NONNEGATIVE) "rise") ((NONNEGATIVE) "fall"))
+    gate rise fall))
+
+
+;; S-EXP -- exponentiate a sound
+;;
+(defun s-exp (s)
+  (multichan-expand "S-EXP" #'nyq:exp
+    '(((NUMBER SOUND) nil)) s))
+
+
+;; NYQ:EXP -- exponentiate number or sound
+;;
+(defun nyq:exp (s) (if (soundp s) (snd-exp s) (exp s)))
+
+;; S-ABS -- absolute value of a sound
+;;
+(defun s-abs (s)
+  (multichan-expand "S-ABS" #'nyq:abs
+    '(((NUMBER SOUND) nil)) s))
+
+;; NYQ:ABS -- absolute value of number or sound
+;;
+(defun nyq:abs (s)
+  (if (soundp s) (snd-abs s) (abs s)))
+
+;; S-AVG -- moving average or peak computation
+;;
+(defun s-avg (s blocksize stepsize operation)
+  (multichan-expand "S-AVG" #'snd-avg
+    '(((SOUND) nil) ((INTEGER) "blocksize") ((INTEGER) "stepsize")
+      ((INTEGER) "operation"))
+    s blocksize stepsize operation))
+
+;; S-SQRT -- square root of a sound
+;;
+(defun s-sqrt (s)
+  (multichan-expand "S-SQRT" #'nyq:sqrt
+    '(((NUMBER SOUND) nil)) s))
+
+
+;; NYQ:SQRT -- square root of a number or sound
+;;
+(defun nyq:sqrt (s)
+  (if (soundp s) (snd-sqrt s) (sqrt s)))
+
+
+;; INTEGRATE -- integration
+;;
+(defun integrate (s)
+  (multichan-expand "INTEGRATE" #'snd-integrate
+    '(((SOUND) nil)) s))
+
+
+;; S-LOG -- natural log of a sound
+;;
+(defun s-log (s)
+  (multichan-expand "S-LOG" #'nyq:log
+    '(((NUMBER SOUND) nil)) s))
+
+
+;; NYQ:LOG -- log of a number or sound
+;;
+(defun nyq:log (s)
+  (if (soundp s) (snd-log s) (log s)))
+
+
+;; NOISE -- white noise
+;;
+(defun noise (&optional (dur 1.0))
+  (ny:typecheck (not (numberp dur))
+    (ny:error "NOISE" 1 number-anon dur))
+  (let ((d (get-duration dur)))
+    (snd-white *rslt* *SOUND-SRATE* d)))
+
+
+(defun noise-gate (snd &optional (lookahead 0.5) (risetime 0.02) (falltime 0.5)
+                       (floor 0.01) (threshold 0.01) &key (rms nil) (link t))
+  (let ((sense (if rms (rms snd 100.0 nil "NOISE-GATE") (s-abs snd))))
+    (cond (link
+           (mult snd (gate sense lookahead risetime falltime floor
+                           threshold "NOISE-GATE")))
+          (t
+           (mult snd (multichan-expand "NOISE-GATE" #'gate
+                      '(((SOUND) "sound") ((NUMBER) "lookahead")
+                        ((NUMBER) "risetime") ((NUMBER) "falltime")
+                        ((NUMBER) "floor") ((NUMBER) "threshold")
+                        ((STRING) "source"))
+                      sense lookahead risetime falltime
+                      floor threshold "NOISE-GATE"))))))
+
+
+;; QUANTIZE -- quantize a sound
+;;
+(defun quantize (s f)
+  (multichan-expand "QUANTIZE" #'snd-quantize
+    '(((SOUND) nil) ((POSITIVE) nil)) s f))
+
+
+;; RECIP -- reciprocal of a sound
+;;
+(defun recip (s)
+  (multichan-expand "RECIP" #'nyq:recip
+    '(((NUMBER SOUND) nil)) s))
+
+
+;; NYQ:RECIP -- reciprocal of a number or sound
+;;
+(defun nyq:recip (s)
+  (if (soundp s) (snd-recip s) (/ (float s))))
+
+
+
+;; RMS -- compute the RMS of a sound
+;;
+(defun rms (s &optional (rate 100.0) window-size (source "RMS"))
+  (multichan-expand "RMS" #'ny:rms
+    '(((SOUND) nil) ((POSITIVE) "rate") ((POSITIVE-OR-NULL) "window-size")
+      ((STRING) "source"))
+    s rate window-size source))
+
+
+;; NY:RMS -- single channel RMS
+;;
+(defun ny:rms (s &optional (rate 100.0) window-size source)
+  (let (rslt step-size)
+    (ny:typecheck (not (or (soundp s) (multichannel-soundp s)))
+      (ny:error source 1 '((SOUND) NIL) s t))
+    (ny:typecheck (not (numberp rate))
+      (ny:error source 2 '((NUMBER) "rate") rate))
+    (setf step-size (round (/ (snd-srate s) rate)))
+    (cond ((null window-size)
+           (setf window-size step-size))
+          ((not (integerp window-size))
+           (ny:error source 3 '((INTEGER) "window-size" window-size))))
+    (setf s (prod s s))
+    (setf result (snd-avg s window-size step-size OP-AVERAGE))
+    ;; compute square root of average
+    (s-exp (scale 0.5 (s-log result)))))
+
+
+;; RESON - bandpass filter
+;; 
+(defun reson (s c b &optional (n 0))
+  (multichan-expand "RESON" #'nyq:reson
+    '(((SOUND) "snd") ((NUMBER SOUND) "center")
+      ((NUMBER SOUND) "bandwidth") ((INTEGER) "n"))
+    s c b n))
+
+
+(setf reson-implementations
+      (vector #'snd-reson #'snd-resonvc #'snd-resoncv #'snd-resonvv))
+
+;; NYQ:RESON - bandpass filter, single channel
+;;
+(defun nyq:reson (signal center bandwidth normalize)
+  (select-implementation-1-2 "RESON" reson-implementations 
+   signal center bandwidth normalize))
+
+
+;; SHAPE -- waveshaper
+;;
+(defun shape (snd shape origin)
+  (multichan-expand "SHAPE" #'snd-shape
+    '(((SOUND) "snd") ((SOUND) "shape") ((NUMBER) "origin"))
+    snd shape origin))
+
+
+;; SLOPE -- calculate the first derivative of a signal
+;;
+(defun slope (s)
+  (multichan-expand "SLOPE" #'nyq:slope
+    '(((SOUND) nil)) s))
+
+
+;; NYQ:SLOPE -- first derivative of single channel
+;;
+(defun nyq:slope (s)
+  (let* ((sr (snd-srate s))
+         (sr-inverse (/ sr)))
+    (snd-xform (snd-slope s) sr 0 sr-inverse MAX-STOP-TIME 1.0)))
+
+
+;; lp - lowpass filter
+;; 
+(defun lp (s c)
+  (multichan-expand "LP" #'nyq:lp
+    '(((SOUND) "snd") ((NUMBER SOUND) "cutoff")) s c))
+
+(setf lp-implementations
+      (vector #'snd-tone #'snd-tonev))
+
+;; NYQ:lp - lowpass filter, single channel
+;;
+(defun nyq:lp (s c)
+  (select-implementation-1-1 "LP" lp-implementations s c))
+
+
+
+;;; fixed-parameter filters based on snd-biquad
+;;; note: snd-biquad is implemented in biquadfilt.[ch],
+;;; while BiQuad.{cpp,h} is part of STK
+
+(setf Pi 3.14159265358979)
+
+(defun square (x) (* x x))
+(defun sinh (x) (* 0.5 (- (exp x) (exp (- x)))))
+
+
+; remember that snd-biquad uses the opposite sign convention for a_i's 
+; than Matlab does.
+; 
+; Stability: Based on courses.cs.washington.edu/courses/cse490s/11au/
+; Readings/Digital_Sound_Generation_2.pdf, the stable region is 
+;   (a2 < 1) and ((a2 + 1) > |a1|)
+; It doesn't look to me like our a0, a1, a2 match the paper's a0, a1, a2,
+; and I'm not convinced the paper's derivation is correct, but at least
+; the predicted region of stability is correct if we swap signs on a1 and
+; a2 (but due to the |a1| term, only the sign of a2 matters). This was
+; tested manually at a number of points inside and outside the stable
+; triangle. Previously, the stability test was (>= a0 1.0) which seems
+; generally wrong. The old test has been removed.
+
+; convenient biquad: normalize a0, and use zero initial conditions.
+(defun nyq:biquad (x b0 b1 b2 a0 a1 a2)
+  (ny:typecheck (<= a0 0.0)
+    (error (format nil "In BIQUAD, a0 < 0 (unstable parameter a0 = ~A)" a0)))
+  (let ((a0r (/ (float a0))))
+    (setf a1 (* a0r a1) 
+          a2 (* a0r a2))
+    (ny:typecheck (or (<= a2 -1.0) (<= (- 1.0 a2) (abs a1)))
+        (error (format nil 
+         "In BIQUAD, (a2 <= -1) or (1 - a2 <= |a1|) (~A a1 = ~A, a2 = ~A)"
+         "unstable parameters" a1 a2)))
+    (snd-biquad x (* a0r b0) (* a0r b1) (* a0r b2) 
+                  a1 a2 0 0)))
+
+
+(defun biquad (x b0 b1 b2 a0 a1 a2 &optional (source "BIQUAD"))
+  (multichan-expand "BIQUAD" #'nyq:biquad
+    '(((SOUND) "snd") ((NUMBER) "b0") ((NUMBER) "b1")
+      ((NUMBER) "b2") ((NUMBER) "a0") ((NUMBER) "a1")
+      ((NUMBER) "a2"))
+    x b0 b1 b2 a0 a1 a2))
+
+
+; biquad with Matlab sign conventions for a_i's.
+(defun biquad-m (x b0 b1 b2 a0 a1 a2)
+  (multichan-expand "BIQUAD-M" #'nyq:biquad-m
+    '(((SOUND) "snd") ((NUMBER) "b0") ((NUMBER) "b1")
+      ((NUMBER) "b2") ((NUMBER) "a0") ((NUMBER) "a1")
+      ((NUMBER) "a2"))
+    x b0 b1 b2 a0 a1 a2))
+
+(defun nyq:biquad-m (x b0 b1 b2 a0 a1 a2 &optional (source "BIQUAD-M"))
+  (nyq:biquad x b0 b1 b2 a0 (- a1) (- a2)))
+
+; two-pole lowpass
+(defun lowpass2 (x hz &optional (q 0.7071) (source "LOWPASS2"))
+  (multichan-expand source #'nyq:lowpass2
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((POSITIVE) "q") ((STRING) "source"))
+    x hz q source))
+
+;; NYQ:LOWPASS2 -- operates on single channel
+(defun nyq:lowpass2 (x hz q source)
+  (if (or (> hz (* 0.5 (snd-srate x)))
+          (< hz 0))
+      (error "cutoff frequency out of range" hz))
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (cw (cos w))
+         (sw (sin w))
+         (alpha (* sw (sinh (/ 0.5 q))))
+         (a0 (+ 1.0 alpha))
+         (a1 (* -2.0 cw))
+         (a2 (- 1.0 alpha))
+         (b1 (- 1.0 cw))
+         (b0 (* 0.5 b1))
+         (b2 b0))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2 source)))
+
+; two-pole highpass
+(defun highpass2 (x hz &optional (q 0.7071) (source "HIGHPASS2"))
+  (multichan-expand source #'nyq:highpass2
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((POSITIVE) "q") ((STRING) "source"))
+    x hz q source))
+
+(defun nyq:highpass2 (x hz q source)
+  (if (or (> hz (* 0.5 (snd-srate x)))
+          (< hz 0))
+      (error "cutoff frequency out of range" hz))
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (cw (cos w))
+         (sw (sin w))
+         (alpha (* sw (sinh (/ 0.5 q))))
+         (a0 (+ 1.0 alpha))
+         (a1 (* -2.0 cw))
+         (a2 (- 1.0 alpha))
+         (b1 (- -1.0 cw))
+         (b0 (* -0.5 b1))
+         (b2 b0))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2 source)))
+
+; two-pole bandpass.  max gain is unity.
+(defun bandpass2 (x hz q)
+  (multichan-expand "BANDPASS2" #'nyq:bandpass2
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((POSITIVE) "q"))
+    x hz q))
+
+(defun nyq:bandpass2 (x hz q)
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (cw (cos w))
+         (sw (sin w))
+         (alpha (* sw (sinh (/ 0.5 q))))
+         (a0 (+ 1.0 alpha))
+         (a1 (* -2.0 cw))
+         (a2 (- 1.0 alpha))
+         (b0 alpha)
+         (b1 0.0)
+         (b2 (- alpha)))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2 "BANDPASS2")))
+
+; two-pole notch.
+(defun notch2 (x hz q)
+  (multichan-expand "NOTCH2" #'nyq:notch2
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((POSITIVE) "q"))
+    x hz q))
+
+(defun nyq:notch2 (x hz q)
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (cw (cos w))
+         (sw (sin w))
+         (alpha (* sw (sinh (/ 0.5 q))))
+         (a0 (+ 1.0 alpha))
+         (a1 (* -2.0 cw))
+         (a2 (- 1.0 alpha))
+         (b0 1.0)
+         (b1 (* -2.0 cw))
+         (b2 1.0))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2 "NOTCH2")))
+
+
+; two-pole allpass.
+(defun allpass2 (x hz q)
+  (multichan-expand "ALLPASS2" #'nyq:allpass
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((POSITIVE) "q"))
+    x hz q))
+
+(defun nyq:allpass (x hz q)
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (cw (cos w))
+         (sw (sin w))
+         (k (exp (* -0.5 w (/ (float q)))))
+         (a0 1.0)
+         (a1 (* -2.0 cw k))
+         (a2 (* k k))
+         (b0 a2)
+         (b1 a1)
+         (b2 1.0))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2 "ALLPASS2")))
+
+
+; bass shelving EQ.  gain in dB; Fc is halfway point.
+; response becomes peaky at slope > 1.
+(defun eq-lowshelf (x hz gain &optional (slope 1.0))
+  (multichan-expand "EQ-LOWSHELF" #'nyq:eq-lowshelf
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((NUMBER) "gain") ((NUMBER) "slope"))
+    x hz gain slope))
+
+
+(defun nyq:eq-lowshelf (x hz gain slope)
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (sw (sin w))
+         (cw (cos w))
+         (A (expt 10.0 (/ gain (* 2.0 20.0))))
+         (b (sqrt (- (/ (+ 1.0 (square A)) slope) (square (- A 1.0)))))
+         (apc (* cw (+ A 1.0)))
+         (amc (* cw (- A 1.0)))
+         (bs (* b sw))
+
+         (b0 (*      A (+ A  1.0 (- amc)    bs  )))
+         (b1 (*  2.0 A (+ A -1.0 (- apc)        )))
+         (b2 (*      A (+ A  1.0 (- amc) (- bs) )))
+         (a0           (+ A  1.0    amc     bs  ))
+         (a1 (* -2.0   (+ A -1.0    apc         )))
+         (a2           (+ A  1.0    amc  (- bs) )))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2)))
+
+
+; treble shelving EQ.  gain in dB; Fc is halfway point.
+; response becomes peaky at slope > 1.
+(defun eq-highshelf (x hz gain &optional (slope 1.0))
+  (multichan-expand "EQ-HIGHSHELF" #'nyq:eq-highshelf
+    '(((SOUND) "snd") ((POSITIVE) "hz") ((NUMBER) "gain") ((NUMBER) "slope"))
+    x hz gain slope))
+
+(defun nyq:eq-highshelf (x hz gain slope)
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (sw (sin w))
+         (cw (cos w))
+         (A (expt 10.0 (/ gain (* 2.0 20.0))))
+         (b (sqrt (- (/ (+ 1.0 (square A)) slope) (square (- A 1.0)))))
+         (apc (* cw (+ A 1.0)))
+         (amc (* cw (- A 1.0)))
+         (bs (* b sw))
+
+         (b0 (*      A (+ A  1.0    amc     bs  )))
+         (b1 (* -2.0 A (+ A -1.0    apc         )))
+         (b2 (*      A (+ A  1.0    amc  (- bs) )))
+         (a0           (+ A  1.0 (- amc)    bs  ))
+         (a1 (*  2.0   (+ A -1.0 (- apc)        )))
+         (a2           (+ A  1.0 (- amc) (- bs) )))
+    (nyq:biquad-m x b0 b1 b2 a0 a1 a2)))
+    
+(defun nyq:eq-band (x hz gain width)
+  (cond ((and (numberp hz) (numberp gain) (numberp width))
+         (eq-band-ccc x hz gain width))
+        ((and (soundp hz) (soundp gain) (soundp width))
+         (snd-eqbandvvv x hz (db-to-linear gain) width))
+        (t (error
+            (strcat
+             "In EQ-BAND, hz, gain, and width must be all numbers"
+             " or all sounds (if any parameter is an array, there"
+             " is a problem with at least one channel), hz is "
+             (param-to-string hz) ", gain is " (param-to-string gain)
+             ", width is " (param-to-string width)) )) ))
+
+; midrange EQ.  gain in dB, width in octaves (half-gain width).
+(defun eq-band (x hz gain width)
+  (multichan-expand "EQ-BAND" #'nyq:eq-band
+    '(((SOUND) "snd") ((POSITIVE SOUND) "hz")
+      ((NUMBER SOUND) "gain") ((POSITIVE SOUND) "width"))
+    x hz gain width))
+  
+  
+(defun eq-band-ccc (x hz gain width)
+  (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
+         (sw (sin w))
+         (cw (cos w))
+         (J (sqrt (expt 10.0 (/ gain 20.0))))
+         ;(dummy (display "eq-band-ccc" gain J))
+         (g (* sw (sinh (* 0.5 (log 2.0) width (/ w sw)))))
+         ;(dummy2 (display "eq-band-ccc" width w sw g))
+         (b0 (+ 1.0 (* g J)))
+         (b1 (* -2.0 cw))
+         (b2 (- 1.0 (* g J)))
+         (a0 (+ 1.0 (/ g J)))
+         (a1 (- b1))
+         (a2 (- (/ g J) 1.0)))
+    (biquad x b0 b1 b2 a0 a1 a2)))
+
+; see failed attempt in eub-reject.lsp to do these with higher-order fns:
+
+; four-pole Butterworth lowpass
+(defun lowpass4 (x hz)
+  (lowpass2 (lowpass2 x hz 0.60492333 "LOWPASS4") 
+                        hz 1.33722126 "LOWPASS4"))
+
+; six-pole Butterworth lowpass
+(defun lowpass6 (x hz)
+  (lowpass2 (lowpass2 (lowpass2 x hz 0.58338080 "LOWPASS6") 
+                                  hz 0.75932572 "LOWPASS6") 
+                                  hz 1.95302407 "LOWPASS6"))
+
+; eight-pole Butterworth lowpass
+(defun lowpass8 (x hz)
+  (lowpass2 (lowpass2 (lowpass2 (lowpass2 x hz 0.57622191 "LOWPASS8")
+                                            hz 0.66045510 "LOWPASS8") 
+                                            hz 0.94276399 "LOWPASS8")
+                                            hz 2.57900101 "LOWPASS8"))
+
+; four-pole Butterworth highpass
+(defun highpass4 (x hz)
+  (highpass2 (highpass2 x hz 0.60492333 "HIGHPASS4") 
+                          hz 1.33722126 "HIGHPASS4"))
+
+; six-pole Butterworth highpass
+(defun highpass6 (x hz)
+  (highpass2 (highpass2 (highpass2 x hz 0.58338080 "HIGHPASS6") 
+                                     hz 0.75932572 "HIGHPASS6")
+                                     hz 1.95302407 "HIGHPASS6"))
+
+; eight-pole Butterworth highpass
+(defun highpass8 (x hz)
+  (highpass2 (highpass2 (highpass2 (highpass2 x hz 0.57622191 "HIGHPASS8")
+                                                hz 0.66045510 "HIGHPASS8")
+                                                hz 0.94276399 "HIGHPASS8")
+                                                hz 2.57900101 "HIGHPASS8"))
+
+; YIN
+; maybe this should handle multiple channels, etc.
+(defun yin (sound minstep maxstep stepsize)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "YIN" 1 '((SOUND) "sound") sound))
+  (ny:typecheck (not (numberp minstep))
+    (ny:error "YIN" 2 '((NUMBER) "minstep") minstep))
+  (ny:typecheck (not (numberp maxstep))
+    (ny:error "YIN" 3 '((NUMBER) "maxstep") maxstep))
+  (ny:typecheck (not (integerp stepsize))
+    (ny:error "YIN" 4 '((INTEGER) "stepsize") stepsize))
+  (snd-yin sound minstep maxstep stepsize))
+
+
+; FOLLOW
+(defun follow (sound floor risetime falltime lookahead)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "FOLLOW" 1 '((SOUND) "sound") sound))
+  (ny:typecheck (not (numberp floor))
+    (ny:error "FOLLOW" 2 '((NUMBER) "floor") floor))
+  (ny:typecheck (not (numberp risetime))
+    (ny:error "FOLLOW" 3 '((NUMBER) "risetime") risetime))
+  (ny:typecheck (not (numberp falltime))
+    (ny:error "FOLLOW" 4 '((NUMBER) "stepsize") falltime))
+  (ny:typecheck (not (numberp lookahead))
+    (ny:error "FOLLOW" 5 '((NUMBER) "lookahead") lookahead))
+  ;; use 10000s as "infinite" -- that's about 2^30 samples at 96K
+  (setf lookahead (round (* lookahead (snd-srate sound))))
+  (extract (/ lookahead (snd-srate sound)) 10000
+           (snd-follow sound floor risetime falltime lookahead)))
+
+
+;; PHASE VOCODER
+(defun phasevocoder (s map &optional (fftsize -1) (hopsize -1) (mode 0))
+  (multichan-expand "PHASEVOCODER" #'snd-phasevocoder
+    '(((SOUND) nil) ((SOUND) "map") ((INTEGER) "fftsize")
+      ((INTEGER) "hopsize") ((INTEGER) "mode"))
+    s map fftsize hopsize mode))
+
+
+;; PV-TIME-PITCH
+;; PV-TIME-PITCH -- control time stretch and transposition 
+;;
+;; stretchfn maps from input time to output time
+;; pitchfn maps from input time to transposition factor (2 means octave up)
+(defun pv-time-pitch (input stretchfn pitchfn dur &optional
+                      (fftsize 2048) (hopsize nil) (mode 0))
+  (multichan-expand "PV-TIME-PITCH" #'nyq:pv-time-pitch
+    '(((SOUND) "input") ((SOUND) "stretchfn") ((SOUND) "pitchfn")
+      ((NUMBER) "dur") ((INTEGER) "fftsize") ((INT-OR-NULL) "hopsize")
+      ((INTEGER) "mode"))
+    input stretchfn pitchfn dur fftsize hopsize mode))
+
+(defun nyq:pv-time-pitch (input stretchfn pitchfn dur fftsize hopsize mode)
+  (let (wrate u v w vinv)
+    (if (null hopsize) (setf hopsize (/ fftsize 8)))
+    (setf wrate (/ 3000  dur))
+    (setf vinv (integrate (prod stretchfn  pitchfn)))
+    (setf v (snd-inverse vinv (local-to-global 0) wrate))
+    (setf w (integrate (snd-recip (snd-compose pitchfn v))))
+    (sound-warp w (phasevocoder input v fftsize hopsize mode) wrate)))
+
diff --git a/Release/nyquist/envelopes.lsp b/Release/nyquist/envelopes.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..18e6a6f6524e4d76aa3fadfe84a521ed6389cd24
--- /dev/null
+++ b/Release/nyquist/envelopes.lsp
@@ -0,0 +1,163 @@
+;; envelopes.lsp -- support functions for envelope editor in NyquistIDE
+
+#| In Nyquist, editable envelopes are saved as one entry in the workspace
+named *envelopes*. The entry is an association list where each element
+looks like this:
+
+(name type parameters... )
+
+where name is a symbol, e.g. MY-ENVELOPE-1,
+      type is a function name, e.g. PWL, PWLV, PWE, etc., and
+      parameters are breakpoint data, e.g. 0.1 1 0.2 0.5 1
+
+Example of two envelopes named FOO and BAR:
+
+((FOO PWL 0.1 1 1) (BAR PWE 0.2 1 1))
+
+To convert envelope data into functions, call (MAKE-ENV-FUNCTIONS).
+This function should be on the workspace's list of functions to call.
+(See ADD-ACTION-TO-WORKSPACE in Nyquist Manual.)
+
+When the NyquistIDE wants to get the envelope data from the workspace, it
+should call (GET-ENV-DATA), which will dump formatted data to Nyquist's
+standard output as follows:
+
+get-env-data: begin
+name (type parameters...) newline
+name (type parameters...) newline
+...
+get-env-data: end
+
+When the IDE wants to save a definition, it should call
+(DEFINE-ENV 'NAME 'EXPRESSION)
+
+To delete a definition, call:
+(DELETE-ENV 'NAME)
+
+Envelope data will be loaded when the editor window is opened and saved
+whenever the user issues a "save" command. If the user switches envelopes
+without saving, there is a prompt to save or ignore.
+
+The user will also be prompted to save when the editor window is closed
+or when Nyquist is exited.
+
+Saving the workspace automatically is something that Nyquist should do
+(or prompt the user to do) when it exits.
+
+|#
+
+;; WORKSPACE -- the workspace is just a set of variables, typically
+;;  with scores as values. These are stored in the file workspace.lsp
+;;  so that you can work on some data and then store it for use later.
+
+(cond ((not (boundp '*workspace*))
+       (setf *workspace* nil)))
+(cond ((not (boundp '*workspace-actions*))
+       (setf *workspace-actions* nil)))
+;; one of the variables in the workspace is *envelopes*
+(cond ((not (boundp '*envelopes*))
+       (setf *envelopes* nil)))
+
+;; DESCRIBE -- add a description to a global variable
+;;
+(defun describe (symbol &optional description)
+  (add-to-workspace symbol)
+  (cond (description
+         (putprop symbol description 'description))
+        (t
+         (get symbol 'description))))
+
+;; ADD-TO-WORKSPACE -- add a global symbol to workspace
+;;
+(defun add-to-workspace (symbol)
+  (cond ((not (symbolp symbol))
+         (format t "add-to-workspace expects a (quoted) symbol~%"))
+        ((not (member symbol *workspace*))
+         (push symbol *workspace*))))
+
+
+;; ADD-ACTION-TO-WORKSPACE -- call function when workspace is loaded
+;;
+(defun add-action-to-workspace (symbol)
+  (cond ((not (symbolp symbol))
+         (format t "add-action-to-workspace expects a (quoted) symbol~%"))
+        ((not (member symbol *workspace-actions*))
+         (push symbol *workspace-actions*))))
+
+;; SAVE-WORKSPACE -- write data to file
+;;
+(defun save-workspace ()
+  (let (val (outf (open "workspace.lsp" :direction :output)))
+    (dolist (sym *workspace*)
+      (format outf "(add-to-workspace '~A)~%" sym)
+      (cond ((get sym 'description)
+             (format outf "(putprop '~A \"~A\" 'description)~%"
+                          sym (get sym 'description))))
+      (format outf "(setf ~A '" sym)
+      (setf val (symbol-value sym))
+      (cond ((listp val)
+             (format outf "(~%")
+             (dolist (elem val)
+               (format outf "  ~A~%" elem))
+             (format outf " ))~%~%"))
+            (t
+             (format outf "~A)~%~%" val))))
+    (dolist (sym *workspace-actions*) ;; call hooks after reading data
+      (format outf "(add-action-to-workspace '~A)~%" sym)
+      (format outf "(if (fboundp '~A) (~A))~%" sym sym))
+    (format outf "(princ \"workspace loaded\\n\")~%")
+    (close outf)
+    (princ "workspace saved\n")
+    nil))
+
+
+;; DEFINE-ENV -- save the env data and make corresponding function
+;;
+(defun define-env (name expression)
+  (delete-env name)
+  (push (cons name expression) *envelopes*)
+  (make-env-function name expression)
+  ; make sure envelopes are redefined when workspace is loaded
+  (add-to-workspace '*envelopes*) ; so *envelopes* will be saved
+  (describe '*envelopes* "data for envelope editor in NyquistIDE")
+  (add-action-to-workspace 'make-env-functions)
+  nil)
+
+
+;; DELETE-ENV -- delete an envelope definition from workspace
+;;
+;; note that this will not undefine the corresponding envelope function
+;;
+(defun delete-env (name)
+  (setf *envelopes* 
+        (remove name *envelopes* 
+                :test #'(lambda (key item) (eql key (car item))))))
+
+
+;; MAKE-ENV-FUNCTION -- convert data to a defined function
+;;
+(defun make-env-function (name expression)
+  (setf (symbol-function name)
+        (eval (list 'lambda '() expression))))
+
+
+;; MAKE-ENV-FUNCTIONS -- convert data to defined functions
+;;
+(defun make-env-functions ()
+  (let (name type parameters)
+    (dolist (env *envelopes*)
+       (setf name (car env))
+       (setf type (cadr env))
+       (setf parameters (cddr env))
+       (make-env-function name (cons type parameters)))))
+
+
+;; GET-ENV-DATA -- print env data for IDE
+;;
+(defun get-env-data ()
+    (princ "get-env-data: begin\n")
+    (dolist (env *envelopes*)
+      (format t "~A ~A~%" (car env) (cdr env)))
+    (princ "get-env-data: end\n")
+    nil)
+
diff --git a/Release/nyquist/equalizer.lsp b/Release/nyquist/equalizer.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..12ff4873c8623860c2e1a89ee8708c0eb776e9f4
--- /dev/null
+++ b/Release/nyquist/equalizer.lsp
@@ -0,0 +1,75 @@
+;; equalizer.lsp -- support functions for equalizer editor in jNyqIDE
+
+#| This is modeled after envelopes.lsp, which details how envelope data is 
+exchanged between Nyquist and jNyqIDE.
+
+The jNyqIDE code needs some work to make it look like the envelope
+editor (which also needs work, but that's another matter). For consistency,
+both should support named envelopes and equalizers.
+
+However, for now, we have equalizers numbered from 0 to 9. The format for
+exchange will be:
+
+get-eq-data: begin
+name parameters newline
+name parameters newline
+...
+get-eq-data: end
+
+and when the IDE wants to save a definition, it should call
+(DEFINE-EQ 'NAME 'PARAMETER-LIST)
+
+|#
+
+(cond ((not (boundp '*equalizers*))
+       (setf *equalizers* nil)))
+
+;; DEFINE-EQ -- save the eq data and make corresponding function
+;;
+(defun define-eq (name expression)
+  (setf *equalizers* (remove name *equalizers* 
+                            :test #'(lambda (key item) (eql key (car item)))))
+  (push (list name expression) *equalizers*)
+  (make-eq-function name expression)
+  ; make sure equalizers are redefined when workspace is loaded
+  (add-to-workspace '*equalizers*)
+  (describe '*equalizers* "data for equalizers in jNyqIDE")
+  (add-action-to-workspace 'make-eq-functions)
+  nil)
+
+
+;; MAKE-EQ-FUNCTION -- convert data to a defined function
+;;
+(defun make-eq-function (name parameters)
+  (cond ((numberp name)
+             (setf name (intern (format nil "EQ-~A" name)))))
+  (if (not (boundp '*grapheq-loaded*)) (load "grapheq.lsp"))
+  (setf (symbol-function name)
+        (eval `(lambda (s) (nband-range s ',parameters 60 14000)))))
+
+
+;; MAKE-EQ-FUNCTIONS -- convert data to defined functions
+;;
+(defun make-eq-functions ()
+  (let (name type parameters)
+    (dolist (eq *equalizers*)
+       (setf name (car eq))
+       (setf parameters (second parameters))
+       (make-eq-function name parameters))))
+
+
+;; GET-EQ-DATA -- print env data for IDE
+;;
+(defun get-eq-data ()
+  (let (parameters)
+    (princ "get-eq-data: begin\n")
+    (dolist (env *equalizers*)
+      (format t "~A" (car env))
+      (setf parameters (second env))
+      (dotimes (i (length parameters))
+        (format t " ~A" (aref parameters i)))
+      (format t "~%"))
+    (princ "get-eq-data: end\n")
+    nil))
+
+
diff --git a/Release/nyquist/evalenv.lsp b/Release/nyquist/evalenv.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..da8ce76896465cd4ff3a928df4992449675ebef7
--- /dev/null
+++ b/Release/nyquist/evalenv.lsp
@@ -0,0 +1,36 @@
+;;
+;; The EVAL function in the original XLISP evaluated in the current lexical
+;; context. This was changed to evaluate in the NIL (global) context to
+;; match Common Lisp. But this created a problem: how do you EVAL an
+;; expression in the current lexical context?
+;;
+;; The answer is you can use the evalhook facility. The evalhook function
+;; will evaluate an expression using an environment given to it as an
+;; argument. But then the problem is "how do you get the current
+;; environment?" Well the getenv macro, below obtains the environment by
+;; using an *evalhook* form.
+;;
+;; The following two macros do the job. Insteading of executing (eval <expr>)
+;; just execute (eval-env <expr>). If you want, you can dispense with the
+;; macros and execute:
+;;
+;;(evalhook <expr> nil nil (let ((*evalhook* (lambda (x env) env)))
+;;                              (eval nil)))
+;;
+;; Tom Almy  10/91
+;;
+
+(defmacro getenv ()
+  '(progv '(*evalhook*) 
+          (list #'(lambda (exp env) env))
+     (eval nil)))
+
+; this didn't work, may be for a later (Almy) version of xlisp?
+;(defmacro getenv ()
+;          '(let ((*evalhook* (lambda (x env) env)))
+;                (eval nil)))    ; hook function evaluates by returning
+                                 ; environment
+
+(defmacro eval-env (arg)        ; evaluate in current environment
+          `(evalhook ,arg nil nil (getenv)))
+
diff --git a/Release/nyquist/fileio.lsp b/Release/nyquist/fileio.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..f09914b84743cafc765416f66058382146a827dd
--- /dev/null
+++ b/Release/nyquist/fileio.lsp
@@ -0,0 +1,417 @@
+;; fileio.lsp
+
+;; if *default-sf-dir* undefined, set it to user's tmp directory
+;;
+(cond ((not (boundp '*default-sf-dir*))
+       ;; it would be nice to use get-temp-path, but when running
+       ;; the Java-based IDE, Nyquist does not get environment
+       ;; variables to tell TMP or TEMP or USERPROFILE
+       ;; We want to avoid the current directory because it may
+       ;; be read-only. Search for some likely paths...
+       ;; Note that since these paths don't work for Unix or OS X,
+       ;; they will not be used, so no system-dependent code is 
+       ;; needed
+       (let ((current (setdir ".")))
+         (setf *default-sf-dir*
+               (or (setdir "c:\\tmp\\" nil)
+                   (setdir "c:\\temp\\" nil)
+                   (setdir "d:\\tmp\\" nil)
+                   (setdir "d:\\temp\\" nil)
+                   (setdir "e:\\tmp\\" nil)
+                   (setdir "e:\\temp\\" nil)
+	           (get-temp-path)))
+         (format t "Set *default-sf-dir* to \"~A\" in fileio.lsp~%" 
+		 *default-sf-dir*)
+	 (setdir current))))
+
+;; if the steps above fail, then *default-sf-dir* might be "" (especially
+;; on windows), and the current directory could be read-only on Vista and
+;; Windows 7. Therefore, the Nyquist IDE will subsequently call
+;; suggest-default-sf-dir with Java's idea of a valid temp directory.
+;; If *default-sf-dir* is the empty string (""), this will set the variable:
+(defun suggest-default-sf-dir (path)
+  (cond ((equal *default-sf-dir* "") (setf *default-sf-dir* path))))
+
+;; s-save -- saves a file
+(setf *in-s-save* nil)
+(setf NY:ALL 576460752303423488)  ; constant for maxlen == 1 << 59
+;; note that at 16-bytes-per-frame, this could generate a file byte offset
+;; that overflows an int64_t. Is this big enough? Time will tell.
+;; What if Nyquist is compiled for 32-bit machines and FIXNUM is 32-bits?
+;; if we don't have 64-bit ints, use 0x7f000000, which is about 10M less
+;; than the maximum signed 32-bit int, giving a lot of "headroom" but still
+;; over 2 billion, or about 13.4 hours at 44.1KHz
+(if (/= 10000000000 (* 100000 100000))
+    (setf NY:ALL 2130706432))
+
+
+;; S-SAVE combines optional and keyword parameters, but this is a really bad
+;; idea because keywords and values are used as optional parameters until
+;; all the optional parameters are used up. Thus if you leave out filename
+;; and progress, but you provide :endian T, then filename becomes :endian and
+;; progress becomes T.  AARRGG!!
+;;     I should have required filename and made everything else keyword, but
+;; rather than breaking compatibility, I'm using &rest to grab everything,
+;; parse the parameters for keywords (giving them priority over optional
+;; parameters, and filling in optional parameters as they are encountered.
+;;
+(defmacro s-save (expression &rest parameters)
+  (prog (parm (format *default-sf-format*)
+              (mode *default-sf-mode*)
+              (bits *default-sf-bits*)
+              ;; endian can be nil, :big, or :little
+              endian play optionals maxlen filename progress swap)
+    loop ;; until all parameters are used
+    (cond ((setf parm (car parameters))
+           (setf parameters (cdr parameters))
+           (case parm
+             (:format (setf format (car parameters)
+                            parameters (cdr parameters)))
+             (:mode   (setf mode (car parameters)
+                            parameters (cdr parameters)))
+             (:bits   (setf bits (car parameters)
+                            parameters (cdr parameters)))
+             (:endian (setf endian (car parameters)
+                            parameters (cdr parameters)))
+             (:play   (setf play (car parameters)
+                            parameters (cdr parameters)))
+             (t (setf optionals (cons parm optionals))))
+           (go loop)))
+    (cond ((> (length optionals) 3)
+           (error "S-SAVE got extra parameter(s)")))
+    (cond ((< (length optionals) 1) ;; need maxlen
+           (setf optionals (list ny:all))))
+    (cond ((< (length optionals) 2) ;; need filename
+           (setf optionals (cons nil optionals))))
+    (cond ((< (length optionals) 3) ;; need progress
+           (setf optionals (cons 0 optionals))))
+    (setf progress (first optionals) ;; note that optionals are in reverse order
+          filename (second optionals)
+          maxlen (third optionals))
+    (cond (*in-s-save*
+           (error "Recursive call to S-SAVE (or maybe PLAY) detected!")))
+
+    ;; finally, we have all the parameters and we can call snd-save
+    (return
+     `(let ((ny:fname ,filename) (ny:swap 0) (ny:endian ,endian)
+            (ny:play ,play)
+            ny:max-sample)     ; return value
+        (progv '(*in-s-save*) '(t)
+          (if (null ny:fname)
+              (setf ny:fname *default-sound-file*))
+
+          (cond ((equal ny:fname "")
+                 (cond ((not ,play)
+                        (format t "S-SAVE: no file to write! ~
+                                  play option is off!\n"))))
+                (t
+                 (setf ny:fname (soundfilename ny:fname))
+                 (format t "Saving sound file to ~A~%" ny:fname)))
+
+          (cond ((eq ny:endian :big)
+                 (setf ny:swap (if (bigendianp) 0 1)))
+                ((eq ny:endian :little)
+                 (setf ny:swap (if (bigendianp) 1 0))))
+
+          ; print device info the first time sound is played
+          (cond (ny:play
+                 (cond ((not (boundp '*snd-list-devices*))
+                        (setf *snd-list-devices* t))))) ; one-time show
+          (setf max-sample
+                (snd-save ',expression ,maxlen ny:fname ,format 
+                       ,mode ,bits ny:swap ny:play ,progress))
+          ; more information if *snd-list-devices* was unbound:
+          (cond (ny:play
+                 (cond (*snd-list-devices*
+                        (format t "\nSet *snd-lfist-devices* = t \n  ~
+                  and call play to see device list again.\n~
+                  Set *snd-device* to a fixnum to select an output device\n  ~
+                  or set *snd-device* to a substring of a device name\n  ~
+                  to select the first device containing the substring.\n")))
+                 (setf *snd-list-devices* nil))) ; normally nil
+          max-sample)))))
+
+
+;; MULTICHANNEL-MAX -- find peak over all channels
+;;
+(defun multichannel-max (snd samples)
+  (cond ((soundp snd)
+	 (snd-max snd samples))
+	((arrayp snd) ;; assume it is multichannel sound
+	 (let ((peak 0.0) (chans (length snd)))
+	   (dotimes (i chans)
+	     (setf peak (max peak (snd-max (aref snd i) (/ samples chans)))))
+	   peak))
+	(t (error "unexpected value in multichannel-max" snd))))
+
+
+
+;; AUTONORM -- look ahead to find peak and normalize sound to 80%
+;;
+(defun autonorm (snd)
+  (let (peak)
+    (cond (*autonormflag*
+	   (cond ((and (not (soundp snd))
+		       (not (eq (type-of snd) 'ARRAY)))
+		  (error "AUTONORM (or PLAY?) got unexpected value" snd))
+		 ((eq *autonorm-type* 'previous)
+		  (scale *autonorm* snd))
+		 ((eq *autonorm-type* 'lookahead)
+		  (setf peak (multichannel-max snd *autonorm-max-samples*))
+		  (setf peak (max 0.001 peak))
+                  (setf *autonorm* (/ *autonorm-target* peak))
+		  (scale *autonorm* snd))
+		 (t
+		  (error "unknown *autonorm-type*"))))
+	  (t snd))))
+	
+
+(init-global *clipping-threshold* (/ 127.0 128.0))
+
+(defmacro s-save-autonorm (expression &rest arglist)
+  `(let ((peak (s-save (autonorm ,expression) ,@arglist)))
+     (when (and *clipping-error* (> peak *clipping-threshold*))
+       (format t "s-save-autonorm peak ~A from ~A~%" peak ,expression)
+       (error "clipping"))
+     (autonorm-update peak)))
+
+;; If the amplitude exceeds *clipping-threshold*, an error will
+;; be raised if *clipping-error* is set.
+;;
+(init-global *clipping-error* nil)
+
+;; The "AutoNorm" facility: when you play something, the Nyquist play
+;; command will automatically compute what normalization factor you
+;; should have used. If you play the same thing again, the normalization
+;; factor is automatically applied.
+;;
+;; Call AUTONORM-OFF to turn off this feature, and AUTONORM-ON to turn
+;; it back on.
+;;
+;; *autonorm-target* is the peak value we're aiming for (it's set below 1
+;; so allow the next signal to get slightly louder without clipping)
+;;
+(init-global *autonorm-target* 0.9)
+;;
+;; *autonorm-type* selects the autonorm algorithm to use
+;;   'previous means normalize according to the last computed sound
+;;   'precompute means precompute *autonorm-max-samples* samples in
+;;       memory and normalize according to the peak
+;;
+(init-global *autonorm-type* 'lookahead)
+(init-global *autonorm-max-samples* 1000000) ; default is 4MB buffer
+
+;;
+(defun autonorm-on ()
+  (setf *autonorm* 1.0)
+  (setf *autonorm-previous-peak* 1.0)
+  (setf *autonormflag* t)
+  (format t "AutoNorm feature is on.~%"))
+
+(if (not (boundp '*autonormflag*)) (autonorm-on))
+
+(defun autonorm-off ()
+  (setf *autonormflag* nil)
+  (setf *autonorm* 1.0)
+  (format t "AutoNorm feature is off.~%"))
+
+(defun explain-why-autonorm-failed ()
+  (format t "~A~A~A~A~A~A"
+          "     *autonorm-type* is LOOKAHEAD and your sound got\n"
+          "       louder after the lookahead period, resulting in\n"
+          "       too large a scale factor and clipping. Consider\n"
+          "       setting *autonorm-type* to 'PREVIOUS. Alternatively,\n"
+          "       try turning off autonorm, e.g. \"exec autonorm-off()\"\n"
+          "       or in Lisp mode, (autonorm-off), and scale your sound\n"
+          "       as follows.\n"))
+
+
+;; AUTONORM-UPDATE -- called with true peak to report and prepare
+;;
+;; after saving/playing a file, we have the true peak. This along
+;; with the autonorm state is printed in a summary and the autonorm
+;; state is updated for next time.
+;;
+;; There are currently two types: PREVIOUS and LOOKAHEAD
+;; With PREVIOUS:
+;;   compute the true peak and print the before and after peak
+;;   along with the scale factor to be used next time
+;; With LOOKAHEAD:
+;;   compute the true peak and print the before and after peak
+;;   along with the "suggested scale factor" that would achieve
+;;   the *autonorm-target*
+;;
+(defun autonorm-update (peak)
+  (cond ((> peak 1.0)
+         (format t "*** CLIPPING DETECTED! ***~%")))
+  (cond ((and *autonormflag* (> peak 0.0))
+         (setf *autonorm-previous-peak* (/ peak *autonorm*))
+         (setf *autonorm* (/ *autonorm-target* *autonorm-previous-peak*))
+         (format t "AutoNorm: peak was ~A,~%" *autonorm-previous-peak*)
+         (format t "     peak after normalization was ~A,~%" peak)
+         (cond ((eq *autonorm-type* 'PREVIOUS)
+                (cond ((zerop *autonorm*)
+                       (setf *autonorm* 1.0)))
+                (format t "     new normalization factor is ~A~%" *autonorm*))
+               ((eq *autonorm-type* 'LOOKAHEAD)
+                (cond ((> peak 1.0)
+                       (explain-why-autonorm-failed)))
+                (format t "     suggested manual normalization factor is ~A~%"
+                          *autonorm*))
+               (t
+                (format t
+                 "     unexpected value for *autonorm-type*, reset to LOOKAHEAD\n")
+                (setf *autonorm-type* 'LOOKAHEAD))))
+        (t
+         (format t "Peak was ~A,~%" peak)
+         (cond ((> peak 0.0)
+                (format t "     suggested normalization factor is ~A~%"
+                        (/ *autonorm-target* peak))))))
+   peak
+  )
+
+
+;; s-read -- reads a file
+(defun s-read (filename &key (time-offset 0) (srate *sound-srate*)
+        (dur 10e20) (nchans 1) (format *default-sf-format*)
+        (mode *default-sf-mode*) (bits *default-sf-bits*) (endian NIL))
+  (let ((swap 0))
+    (cond ((eq endian :big)
+           (setf swap (if (bigendianp) 0 1)))
+          ((eq endian :little)
+           (setf swap (if (bigendianp) 1 0))))
+    (if (minusp dur) (error "s-read :dur is negative" dur))
+    (snd-read (soundfilename filename) time-offset
+            (local-to-global 0) format nchans mode bits swap srate
+            dur)))
+
+
+;; SF-INFO -- print sound file info
+;;
+(defun sf-info (filename)
+  (let (s format channels mode bits swap srate dur flags)
+    (format t "~A:~%" (soundfilename filename))
+    (setf s (s-read filename))
+    (setf format (snd-read-format *rslt*))
+    (setf channels (snd-read-channels *rslt*))
+    (setf mode (snd-read-mode *rslt*))
+    (setf bits (snd-read-bits *rslt*))
+    ; (setf swap (snd-read-swap *rslt*))
+    (setf srate (snd-read-srate *rslt*))
+    (setf dur (snd-read-dur *rslt*))
+    (setf flags (snd-read-flags *rslt*))
+    (format t "Format: ~A~%" 
+            (nth format '("none" "AIFF" "IRCAM" "NeXT" "Wave" "PAF" "SVX"
+                          "NIST" "VOC" "W64" "MAT4" "Mat5" "PVF" "XI" "HTK"
+                          "SDS" "AVR" "SD2" "FLAC" "CAF")))
+    (cond ((setp (logand flags snd-head-channels))
+           (format t "Channels: ~A~%" channels)))
+    (cond ((setp (logand flags snd-head-mode))
+           (format t "Mode: ~A~%"
+                   (nth mode '("ADPCM" "PCM" "uLaw" "aLaw" "Float" "UPCM"
+                               "unknown" "double" "GSM610" "DWVW" "DPCM"
+                               "msadpcm")))))
+    (cond ((setp (logand flags snd-head-bits))
+           (format t "Bits/Sample: ~A~%" bits)))
+    (cond ((setp (logand flags snd-head-srate))
+           (format t "SampleRate: ~A~%" srate)))
+    (cond ((setp (logand flags snd-head-dur))
+           (format t "Duration: ~A~%" dur)))
+    ))
+
+;; SETP -- tests whether a bit is set (non-zero)
+;
+(defun setp (bits) (not (zerop bits)))
+
+;; IS-FILE-SEPARATOR -- is this a file path separation character, e.g. "/"?
+;;
+(defun is-file-separator (c)
+  (or (eq c *file-separator*)
+      (and (eq *file-separator* #\\) ;; if this is windows (indicated by "\")
+           (eq c #\/)))) ;; then "/" is also a file separator
+
+;; SOUNDFILENAME -- add default directory to name to get filename
+;;
+(defun soundfilename (filename)
+  (cond ((= 0 (length filename))
+         (break "filename must be at least one character long" filename))
+        ((full-name-p filename))
+        (t
+         ; if sf-dir nonempty and does not end with filename separator,
+         ; append one
+         (cond ((and (< 0 (length *default-sf-dir*))
+                     (not (is-file-separator
+                           (char *default-sf-dir* 
+                                 (1- (length *default-sf-dir*))))))
+                (setf *default-sf-dir* (strcat *default-sf-dir* (string *file-separator*)))
+                (format t "Warning: appending \"~A\" to *default-sf-dir*~%"
+                        *file-separator*)))
+         (setf filename (strcat *default-sf-dir* (string filename)))))
+  ;; now we have a file name, but it may be relative to current directory, so 
+  ;; expand it with the current directory
+  (cond ((relative-path-p filename)
+         ;; get current working directory and build full name
+         (let ((path (setdir ".")))
+           (cond (path
+                  (setf filename (strcat path (string *file-separator*) 
+                                         (string filename))))))))
+  filename)
+
+
+(setfn snd-read-format car)
+(setfn snd-read-channels cadr)
+(setfn snd-read-mode caddr)
+(setfn snd-read-bits cadddr)
+(defun snd-read-swap (rslt) (car (cddddr rslt)))
+(defun snd-read-srate (rslt) (cadr (cddddr rslt)))
+(defun snd-read-dur (rslt) (caddr (cddddr rslt)))
+(defun snd-read-flags (rslt) (cadddr (cddddr rslt)))
+
+;; round is tricky because truncate rounds toward zero as does C
+;; in other words, rounding is down for positive numbers and up
+;; for negative numbers. You can convert rounding up to rounding
+;; down by subtracting one, but this fails on the integers, so
+;; we need a special test if (- x 0.5) is an integer
+(defun round (x) 
+  (cond ((> x 0) (truncate (+ x 0.5)))
+        ((= (- x 0.5) (truncate (- x 0.5))) (truncate x))
+        (t (truncate (- x 0.5)))))
+
+;; change defaults for PLAY macro:
+(init-global *soundenable* t)
+(defun sound-on () (setf *soundenable* t))
+(defun sound-off () (setf *soundenable* nil))
+
+(defun coterm (snd1 snd2)
+  (multichan-expand #'snd-coterm snd1 snd2))
+
+(defmacro s-add-to (expr maxlen filename
+                    &optional (time-offset 0.0) (progress 0))
+  `(let ((ny:fname (soundfilename ,filename))
+         ny:peak ny:input (ny:offset ,time-offset))
+    (format t "Adding sound to ~A at offset ~A~%" 
+              ny:fname ,time-offset)
+    (setf ny:peak (snd-overwrite '(let ((ny:addend ,expr))
+                                   (sum (coterm
+                                         (s-read ny:fname
+                                          :time-offset ny:offset)
+                                         ny:addend)
+                                    ny:addend))
+                   ,maxlen ny:fname ny:offset ,progress))
+    (format t "Duration written: ~A~%" (car *rslt*))
+    ny:peak))
+
+
+(defmacro s-overwrite (expr maxlen filename
+                       &optional (time-offset 0.0) (progress 0))
+  `(let ((ny:fname (soundfilename ,filename))
+         (ny:peak 0.0)
+         ny:input ny:rslt (ny:offset ,time-offset))
+    (format t "Overwriting ~A at offset ~A~%" ny:fname ny:offset)
+    (setf ny:peak (snd-overwrite `,expr ,maxlen ny:fname ny:offset ,progress))
+    (format t "Duration written: ~A~%" (car *rslt*))
+    ny:peak))
+
+
+
+
diff --git a/Release/nyquist/init.lsp b/Release/nyquist/init.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..102d0ab82fc8f3420b0aac65c3a5160212c29d36
--- /dev/null
+++ b/Release/nyquist/init.lsp
@@ -0,0 +1,88 @@
+; init.lsp -- default Nyquist startup file
+
+(setf *breakenable* t)
+(load "nyinit.lsp" :verbose nil)
+
+; add your customizations here:
+;    e.g. (setf *default-sf-dir* "...")
+
+; (load "test.lsp")
+
+
+
+;; "_" (UNDERSCORE) - translation function
+;;
+;; Third party plug-ins are not translated by gettext in Audacity, but may include a
+;; list of translations named *locale*. The format of *locale* must be:
+;; (LIST (language-list) [(language-list) ...]) 
+;; Each language-list is an a-list in the form:
+;; ("cc" ((list "string" "translated-string") [(list "string" "translated-string") ...]))
+;; where "cc" is the quoted country code.
+;;
+(setfn underscore _)
+;;
+(defun _(txt &aux newtxt)
+  (when (boundp '*locale*)
+    (when (not (listp *locale*))
+          (error "bad argument type" *locale*))
+    (let* ((cc (get '*audacity* 'language))
+           (translations (second (assoc cc *locale* :test 'string-equal))))
+      (if translations
+          (let ((translation (second (assoc txt translations :test 'string=))))
+            (if translation
+                (if (stringp translation)
+                    (setf newtxt translation)
+                    (error "bad argument type" translation))
+                (format t "No ~s translation of ~s.~%" cc txt)))
+          (progn
+            (setf *locale* '*unbound*)
+            (format t "No ~s translations.~%" cc)))))
+  (if newtxt newtxt (underscore txt)))
+
+
+;;; Some helpers for parsing strings returned by (aud-do "GetInfo: ...
+
+(defun eval-string (string)
+  ;;; Evaluate a string as a LISP expression.
+  ;;; If 'string' is not a valid LISP expression, the behaviour is undefined.
+  (eval (read (make-string-input-stream string))))
+
+(defmacro quote-string (string)
+  ;;; Prepend a single quote to a string
+  `(setf ,string (format nil "\'~a" ,string)))
+
+(defun aud-get-info (str)
+  ;;; Return "GetInfo: type=type" as Lisp list, or throw error
+  ;;; Audacity 2.3.0 does not fail if type is not recognised, it 
+  ;;; falls back to a default, so test for valid types.
+  ;;; 'Commands+' is not supported in Audacity 2.3.0
+  (let (type
+        info
+        (types '("Commands" "Menus" "Preferences"
+                "Tracks" "Clips" "Envelopes" "Labels" "Boxes")))
+    ;Case insensitive search, then set 'type' with correct case string, or  NIL.
+    (setf type (first (member str types :test 'string-equal)))
+    (if (not type)
+        (error (format nil "bad argument '~a' in (aud-get-info ~a)" str str)))
+    (setf info (aud-do (format nil "GetInfo: type=~a format=LISP" type)))
+    (if (not (last info))
+        (error (format nil "(aud-get-info ~a) failed.~%" str)))
+    (let* ((info-string (first info))
+           (sanitized ""))
+      ;; Escape backslashes
+      (dotimes (i (length info-string))
+        (setf ch (subseq info-string i (1+ i)))
+        (if (string= ch "\\")
+            (string-append sanitized "\\\\")
+            (string-append sanitized ch)))
+      (eval-string (quote-string sanitized)))))
+
+
+;;; *NYQ-PATH* is not required as path to Nyquist .lsp files
+;;; is already defined (but not previously documented) as *runtime-path*
+;;(setf *NYQ-PATH* (current-path))
+
+;;; Load wrapper functions for aud-do commands.
+;;; If commented out, "aud-do-support.lsp" may be loaded by a plug-in.
+;;; Example: (lisp-loader (strcat *runtime-path* "aud-do-support.lsp"))
+(load "aud-do-support.lsp" :verbose nil)
diff --git a/Release/nyquist/misc.lsp b/Release/nyquist/misc.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..c81726ca80c009373b40f2df34ff59ba540da5d8
--- /dev/null
+++ b/Release/nyquist/misc.lsp
@@ -0,0 +1,235 @@
+;## misc.lsp -- a collection of useful support functions
+
+;; Garbage collection "improvement" -- XLISP will GC without allocation
+;; as long as it does not run out of cells. This can make it very slow
+;; since GC does work proportional to the heap size. If there were
+;; always at least, say, 1/3 of the heap free after GC, then allocating
+;; cells would be more-or-less a constant time operation (amortized).
+;;
+;; So, after GC, we'll expand until we have 1/3 of the heap free.
+;;
+(defun ny:gc-hook (heap-size free-cells)
+  (cond ((< (* free-cells 2) heap-size) ;; free cells is < 1/3 heap
+         ;; expand. Each expansion unit is 2000 cons cells
+         (let* ((how-many-not-free (- heap-size free-cells))
+                (should-be-free (/ how-many-not-free 2))
+                (how-many-more (- should-be-free free-cells))
+                (expand-amount (/ how-many-more 2000)))
+           (cond ((> expand-amount 0)
+                  (if *gc-flag*
+                      (format t
+                       "[ny:gc-hook allocating ~A more cells] "
+                       (* expand-amount 2000)))
+                  (expand expand-amount)))))))
+
+(setf *gc-hook* 'ny:gc-hook)
+
+
+; set global if not already set
+;
+(defmacro init-global (symb expr)
+  `(if (boundp ',symb) ,symb (setf ,symb ,expr)))
+
+; controlling breaks and tracebacks:
+; XLISP and SAL behave differently, so there are four(!) flags:
+; *sal-traceback* -- print SAL traceback on error in SAL mode
+;                    Typically you want this on always.
+; *sal-break* -- allow break (to XLISP prompt) on error when in SAL mode
+;                (overrides *sal-traceback*) Typically, you do not want
+;                this unless you need to see exactly where an error happened
+;                or the bug is in XLISP source code called from SAL.
+; *xlisp-break* -- allow break on error when in XLISP mode
+;                  Typically, you want this on.
+; *xlisp-traceback* -- print XLISP traceback on error in XLISP mode
+;                      Typically, you do not want this because the full
+;                      stack can be long and tedious. Also allow XLISP
+;                      traceback in SAL mode if *sal-break* is true.
+
+(setf *sal-mode* nil)
+
+(setf *sal-traceback* t
+      *sal-break* nil
+      *xlisp-break* t
+      *xlisp-traceback* nil)
+
+(defun sal-tracenable (flag) (setf *sal-traceback* flag))
+(defun sal-breakenable (flag)
+  (setf *sal-break* flag)
+  (if *sal-mode* (setf *breakenable* flag)))
+(defun xlisp-breakenable (flag)
+  (setf *xlisp-break* flag)
+  (if (not *sal-mode*) (setf *breakenable* flag)))
+(defun xlisp-tracenable (flag)
+  (setf *xlisp-traceback* flag)
+  (if flag (setf *xlisp-break* t))
+  (cond ((not *sal-mode*)
+         (if flag (setf *breakenable* t))
+         (setf *tracenable* flag))))
+
+
+; enable or disable breaks
+(defun bkon () (xlisp-breakenable t))
+(defun bkoff () (xlisp-breakenable nil))
+
+
+;; (grindef 'name) - pretty print a function
+;;
+(defun grindef (e) (pprint (get-lambda-expression (symbol-function e))))
+
+;; (args 'name) - print function and its formal arguments
+;;
+(defun args (e) 
+  (pprint (cons e (second (get-lambda-expression (symbol-function e))))))
+
+;; (incf <place>), (decf <place>) - add/sub 1 to/from variable
+;;
+(defmacro incf (symbol) `(setf ,symbol (1+ ,symbol)))
+(defmacro decf (symbol) `(setf ,symbol (1- ,symbol)))
+
+
+;; (push val <place>) - cons val to list
+;;
+(defmacro push (val lis) `(setf ,lis (cons ,val ,lis)))
+(defmacro pop (lis) `(prog1 (car ,lis) (setf ,lis (cdr ,lis))))
+
+;; include this to use RBD's XLISP profiling hooks
+;;(load "/afs/andrew/usr/rbd/lib/xlisp/profile.lsp")
+
+;(cond ((boundp 'application-file-name)
+;       (load application-file-name)))
+
+
+(defun get-input-file-name ()
+  (let (fname)
+    (format t "Input file name: ")
+    (setf fname (read-line))
+    (cond ((equal fname "") (get-input-file-name))
+          (t fname))))
+
+
+(defun open-output-file ()
+  (let (fname)
+    (format t "Output file name: ")
+    (setf fname (read-line))
+    (cond ((equal fname "") t)
+          (t (open fname :direction :output)))))
+
+
+(defmacro while (cond &rest stmts)
+  `(prog () loop (if ,cond () (return)) ,@stmts (go loop)))
+
+
+; when parens/quotes don't match, try this
+; 
+(defun file-sexprs ()
+  (let ((fin (open (get-input-file-name)))
+        inp)
+    (while (setf inp (read fin)) (print inp))))
+
+;; get path for currently loading file (if any)
+;;
+(defun current-path ()
+  (let (fullpath n)
+    (setf n -1)
+    (cond (*loadingfiles*
+           (setf fullpath (car *loadingfiles*))
+           (dotimes (i (length fullpath))
+             ;; search for "/" (and on windows, also "\") in path:
+             (cond ((or (equal (char fullpath i) *file-separator*)
+                        (equal (char fullpath i) #\/))
+                    (setf n i))))
+           ;; trim off filename (after last separator char in path
+           (setf fullpath (subseq fullpath 0 (1+ n)))
+
+;;         REMOVED SUPPORT FOR MAC OS-9 AND BELOW -RBD
+           ;; if this is a Mac, use ':' in place of empty path
+;;           (cond ((and (equal fullpath "") 
+;;                       (equal *file-separator* #\:))
+;;                  (setf fullpath ":")))
+;;         END MAC OS-9 CODE
+
+           ;; Here's an interesting problem: fullpath is now the path
+           ;; specified to LOAD, but it may be relative to the current
+           ;; directory. What if we want to load a sound file from the
+           ;; current directory? It seems that S-READ gives priority to
+           ;; the *DEFAULT-SF-DIR*, so it will follow fullpath STARTING
+           ;; FROM *DEFAULT-SF-DIR*. To fix this, we need to make sure
+           ;; that fullpath is either an absolute path or starts with
+           ;; and explicit ./ which tells s-read to look in the current
+           ;; directory.
+           (cond ((> (length fullpath) 0)
+		  (cond ((full-name-p fullpath))
+			(t ; not absolute, make it explicitly relative
+			 (setf fullpath (strcat "./" fullpath)))))
+                 (t (setf fullpath "./"))) ; use current directory
+           fullpath)
+          (t nil))))
+          
+;; real-random -- pick a random real from a range
+;;
+(defun real-random (from to)
+  (+ (* (rrandom) (- to from)) from))
+
+;; power -- raise a number to some power x^y
+;;
+(defun power (x y)
+  (exp (* (log (float x)) y)))
+  
+;; require-from -- load a file if a function is undefined
+;;
+;; fn-symbol -- the function defined when the file is loaded
+;; file-name -- the name of file to load if fn-symbol is undefined
+;; path -- if t, load from current-path; if a string, prepend string
+;;         to file-name; if nil, ignore it
+;;
+(defmacro require-from (fn-symbol file-name &optional path)
+  (cond ((eq path t)
+         (setf file-name `(strcat (current-path) ,file-name)))
+        (path
+         (setf file-name `(strcat ,path ,file-name))))
+  ; (display "require-from" file-name)
+  `(if (fboundp (quote ,fn-symbol))
+       t
+       ;; search for either .lsp or .sal file
+       (sal-load ,file-name)))
+
+;; COMPUTE-DEFAULT-SOUND-FILE -- construct and set *default-sound-file*
+;;
+;; (this is harder than it might seem because the default place for
+;;  sound files is in /tmp, which is shared by users, so we'd like to
+;;  use a user-specific name to avoid collisions)
+;;
+(defun compute-default-sound-file () 
+  (let (inf user extension)
+      ; the reason for the user name is that if UserA creates a temp file,
+      ; then UserB will not be able to overwrite it. The user name is a
+      ; way to give each user a unique temp file name. Note that we don't
+      ; want each session to generate a unique name because Nyquist doesn't
+      ; delete the sound file at the end of the session.
+   (setf user (get-user))
+#|
+   (cond ((null user)           
+       (format t 
+"Please type your user-id so that I can construct a default 
+sound-file name.  To avoid this message in the future, add
+this to your .login file:
+    setenv USER <your id here>
+or add this to your init.lsp file:
+    (setf *default-sound-file* \"<your filename here>\")
+    (setf *default-sf-dir* \"<full pathname of desired directory here>\")
+
+Your id please: ")
+       (setf user (read))))
+|#
+    ; now compute the extension based on *default-sf-format*
+    (cond ((= *default-sf-format* snd-head-AIFF)
+           (setf extension ".aif"))
+          ((= *default-sf-format* snd-head-Wave)
+           (setf extension ".wav"))
+          (t
+           (setf extension ".snd")))
+    (setf *default-sound-file* 
+      (strcat (string-downcase user) "-temp" extension))
+    (format t "Default sound file is ~A.~%" *default-sound-file*)))
+
+
diff --git a/Release/nyquist/nyinit-dbg.lsp b/Release/nyquist/nyinit-dbg.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..352844575b500bf7dd21c3cc268213467cf8bb05
--- /dev/null
+++ b/Release/nyquist/nyinit-dbg.lsp
@@ -0,0 +1,38 @@
+(expand 5)
+
+(load "xlinit.lsp" :verbose NIL)
+(setf *gc-flag* nil)
+(load "misc.lsp" :verbose NIL)
+(load "evalenv.lsp" :verbose NIL)
+(load "printrec.lsp" :verbose NIL)
+
+(load "sndfnint.lsp" :verbose NIL)
+(load "seqfnint.lsp" :verbose NIL)
+
+(load "velocity.lsp" :verbose NIL) ; linear-to-vel etc
+(load "nyquist-dbg.lsp" :verbose NIL)
+(load "compress.lsp" :verbose NIL)
+
+(load "system.lsp" :verbose NIL)
+
+(load "seqmidi.lsp" :verbose NIL)
+(load "nyqmisc.lsp" :verbose NIL)
+(load "stk.lsp" :verbose NIL)
+(load "envelopes.lsp" :verbose NIL)
+(load "equalizer.lsp" :verbose NIL)
+(load "xm.lsp" :verbose NIL)
+(load "sal.lsp" :verbose NIL)
+
+;; set to T to get ANSI headers and NIL to get antique headers
+(setf *ANSI* NIL)
+
+;; set to T to generate tracing code, NIL to disable tracing code
+(setf *WATCH* NIL)
+
+(format t "~%Nyquist -- A Language for Sound Synthesis and Composition~%")
+(format t "    Copyright (c) 1991,1992,1995,2007-2012 by Roger B. Dannenberg~%")
+(format t "    Version 3.10~%~%")
+
+;(setf *gc-flag* t)
+
+
diff --git a/Release/nyquist/nyinit.lsp b/Release/nyquist/nyinit.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..47b2cbdb5d8f54def8ab690a33e98e7171718680
--- /dev/null
+++ b/Release/nyquist/nyinit.lsp
@@ -0,0 +1,36 @@
+(expand 5)
+
+(load "xlinit.lsp" :verbose NIL)
+(setf *gc-flag* nil)
+(load "misc.lsp" :verbose NIL)
+;; now compute-default-sound-file is defined; needed by system.lsp ...
+(load "evalenv.lsp" :verbose NIL)
+(load "printrec.lsp" :verbose NIL)
+
+(load "sndfnint.lsp" :verbose NIL)
+(load "seqfnint.lsp" :verbose NIL)
+
+(load "velocity.lsp" :verbose NIL) ; linear-to-vel etc
+(if (not (load "system.lsp" :verbose NIL))
+    (error "Nyquist could not load system.lsp - check your installation"))
+;; now *file-separator* is defined, used by nyquist.lsp...
+(load "nyquist.lsp" :verbose NIL)
+
+
+(load "seqmidi.lsp" :verbose NIL)
+(load "nyqmisc.lsp" :verbose NIL)
+(load "stk.lsp" :verbose NIL)
+(load "envelopes.lsp" :verbose NIL)
+(load "equalizer.lsp" :verbose NIL)
+(load "xm.lsp" :verbose NIL)
+(load "sal.lsp" :verbose NIL)
+
+
+(format t "~%Nyquist -- A Language for Sound Synthesis and Composition~%")
+(format t "    Copyright (c) 1991,1992,1995,2007-2020 by Roger B. Dannenberg~%")
+(format t "    Version 3.16~%~%")
+(load "extensions.lsp" :verbose NIL)
+
+;(setf *gc-flag* t)
+
+
diff --git a/Release/nyquist/nyqmisc.lsp b/Release/nyquist/nyqmisc.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..3905d33c5ade1b4b7d9039e2ab11411ad3653612
--- /dev/null
+++ b/Release/nyquist/nyqmisc.lsp
@@ -0,0 +1,27 @@
+;; nyqmisc.lsp -- misc functions for nyquist
+
+(init-global *snd-display-max-samples* 10000)
+(init-global *snd-display-print-samples* 100)
+
+
+; (snd-display sound) -- describe a sound
+(defun snd-display (sound)
+  (let (t0 srate len extent dur samples)
+    (setf srate (snd-srate sound))
+    (setf t0 (snd-t0 sound))
+    (setf len (snd-length sound *snd-display-max-samples*))
+    (cond ((= len *snd-display-max-samples*)
+                 (setf extent (format nil ">~A" (+ t0 (* srate *snd-display-max-samples*))))
+           (setf dur (format nil ">~A" (* srate *snd-display-max-samples*))))
+          (t
+           (setf extent (cadr (snd-extent sound *snd-display-max-samples*)))
+           (setf dur (/ (snd-length sound *snd-display-max-samples*) srate))))
+    (cond ((> len 100)
+           (setf samples (format nil "1st ~A samples" *snd-display-print-samples*))
+           (setf nsamples *snd-display-print-samples*))
+          (t
+           (setf samples (format nil "~A samples" len))
+           (setf nsamples len)))
+    (format t "~A: srate ~A, t0 ~A, extent ~A, dur ~A, ~A: ~A"
+      sound srate t0 extent dur samples (snd-samples sound nsamples))))
+
diff --git a/Release/nyquist/nyquist-plot.txt b/Release/nyquist/nyquist-plot.txt
new file mode 100644
index 0000000000000000000000000000000000000000..003e6e0f457b88eba3c2a3f76a9d94865521678f
--- /dev/null
+++ b/Release/nyquist/nyquist-plot.txt
@@ -0,0 +1,3 @@
+set nokey
+plot "points.dat" with lines
+
diff --git a/Release/nyquist/nyquist.lsp b/Release/nyquist/nyquist.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..dcd30c35969a163292b93020a6754ffe0125ff99
--- /dev/null
+++ b/Release/nyquist/nyquist.lsp
@@ -0,0 +1,2482 @@
+;;;
+;;;   ###########################################################
+;;;   ### NYQUIST-- A Language for Composition and Synthesis. ###
+;;;   ###                                                     ###
+;;;   ### Copyright (c) 1994-2006 by Roger B. Dannenberg      ###
+;;;   ###########################################################
+;;;
+(princ "LOADING NYQUIST RUNTIME DEBUG VERSION\n")
+
+;; #### Error checking and reporting functions ####
+
+(setf *SAL-CALL-STACK* nil) ; because SEQ looks at this
+
+;; MULTICHANNEL-SOUNDP - test for vector of sounds
+(defun multichannel-soundp (v)
+  (prog ((rslt t))
+    (if (not (arrayp v)) (return nil))
+    (dotimes (i (length v))
+      (cond ((not (soundp (aref v i)))
+             (setf rslt nil)
+             (return nil))))
+    (return rslt)))
+
+;; MULTICHANNELP - test for vector of sounds or numbers
+(defun multichannelp (v)
+  (prog ((rslt t))
+    (if (not (arrayp v)) (return nil))
+    (dotimes (i (length v))
+      (cond ((not (or (numberp (aref v i)) (soundp (aref v i))))
+             (setf rslt nil)
+             (return nil))))
+    (return rslt)))
+
+;; NUMBERSP - test for vector of numbers
+(defun numbersp (v)
+  (prog ((rslt t))
+    (if (not (arrayp v)) (return nil))
+    (dotimes (i (length v))
+      (cond ((not (numberp (aref v i)))
+             (setf rslt nil)
+             (return nil))))
+    (return rslt)))
+
+
+;; PARAM-TO-STRING - make printable parameter for error message
+(defun param-to-string (param)
+  (cond ((null param)    (format nil "NIL"))
+        ((soundp param)  (format nil "a SOUND"))
+        ((multichannel-soundp param)
+          (format nil "a ~A-channel SOUND" (length param)))
+        ((eq (type-of param) 'ARRAY) ;; avoid saying "#(1 2), a ARRAY"
+          (format nil "~A, an ARRAY" param))
+        ((stringp param) (format nil "~s, a STRING" param)) ;; add quotes
+        (t
+          (format nil "~A, a ~A" param (symbol-name (type-of param))))))
+
+
+;; NY:TYPECHECK -- syntactic sugar for "if", used for all nyquist typechecks
+(setfn ny:typecheck if)
+
+(defun index-to-string (index)
+  (nth index '("" " 1st" " 2nd" " 3rd" " 4th" " 5th" " 6th" " 7th")))
+
+(setf number-anon '((NUMBER) nil))
+(setf number-sound-anon '((NUMBER SOUND) nil))
+
+;; NY:TYPE-LIST-AS-STRING - convert permissible type list into
+;;   description. E.g. typs = '(NUMBER SOUND) and multi = t returns:
+;;   "number, sound or array thereof"
+(defun ny:type-list-as-string (typs multi)
+  (let (lis last penultimate (string "") multi-clause)
+    (if (member 'NUMBER   typs) (push "number" lis))
+    (if (member 'POSITIVE typs) (push "positive number" lis))
+    (if (member 'NONNEGATIVE typs) (push "non-negative number" lis))
+    (if (member 'INTEGER  typs) (push "integer" lis))
+    (if (member 'STEP     typs) (push "step number" lis))
+    (if (member 'STRING   typs) (push "string" lis))
+    (if (member 'SOUND    typs) (push "sound" lis))
+    (if (member 'NULL     typs) (push "NIL" lis))
+    ;; this should be handled with two entries: INTEGER and NULL, but
+    ;; this complicates multichan-expand, where lists of arbitrary types
+    ;; are not handled and we need INT-OR-NULL for PV-TIME-PITCH's 
+    ;; hopsize parameter.
+    (cond ((member 'INT-OR-NULL typs)
+           (push "integer" lis)
+           (push "NIL" lis)))
+    (cond ((member 'POSITIVE-OR-NULL typs)
+           (push "positive number" lis)
+           (push "NIL" lis)))
+    (cond (multi
+           (setf multi-clause
+                 (cond ((> (length lis) 1) "array thereof")
+                       ((equal (car lis) "sound") "multichannel sound")
+                       (t (strcat "array of " (car lis) "s"))))
+           (push multi-clause lis)))
+    (setf last (first lis))
+    (setf penultimate (second lis))
+    (setf lis (cddr lis))
+    (dolist (item lis)
+      (setf string (strcat item ", " string)))
+    (strcat string (if penultimate (strcat penultimate " or ") "") last)))
+
+
+;; NY:ERROR -- construct an error message and raise an error
+(defun ny:error (src index typ val &optional multi (val2 nil second-val))
+  (let ((types-string (ny:type-list-as-string (first typ) multi)))
+    (error (strcat "In " src "," (index-to-string index) " argument"
+            (if (second typ) (strcat " (" (second typ) ")") "")
+            (if (eq (char types-string 0) #\i) " must be an " " must be a ")
+            types-string
+            ", got " (param-to-string val)
+            (if second-val (strcat ", and" (param-to-string val2)) "")))))
+
+
+(prog ()
+   (setq lppp -12.0) (setq lpp -9.0)  (setq lp -6.0)    (setq lmp -3.0)
+   (setq lfff 12.0) (setq lff 9.0)  (setq lf 6.0)    (setq lmf 3.0)
+   (setq dB0 1.00)  (setq dB1 1.122) (setq dB10 3.1623)
+
+   (setq s 0.25) (setq sd 0.375) (setq st (/ 0.5 3.0))
+   (setq i 0.5)  (setq id 0.75)  (setq it (* st 2.0))
+   (setq q 1.0)  (setq qd 1.5)   (setq qt (* st 4.0))
+   (setq h 2.0)  (setq hd 3.0)   (setq ht (* st 8.0))
+   (setq w 4.0)  (setq wd 6.0)   (setq wt (* st 16.0))
+)
+
+(init-global *A4-Hertz* 440.0)
+
+; next pitch, for initializations below
+; 
+(defun np () (incf nyq:next-pitch))
+
+(defun set-pitch-names ()
+   (setq no-pitch 116.0)
+   ; note: 58.0 is A4 - (C0 - 1) = 69 - (12 - 1)
+   (setf nyq:next-pitch (- (hz-to-step *A4-Hertz*) 58.0))
+
+   (setf nyq:pitch-names
+    '(c0 (cs0 df0) d0 (ds0 ef0) e0 f0 (fs0 gf0) g0 (gs0 af0) a0
+      (as0 bf0) b0
+      c1 (cs1 df1) d1 (ds1 ef1) e1 f1 (fs1 gf1) g1 (gs1 af1) a1
+      (as1 bf1) b1
+      c2 (cs2 df2) d2 (ds2 ef2) e2 f2 (fs2 gf2) g2 (gs2 af2) a2
+      (as2 bf2) b2
+      c3 (cs3 df3) d3 (ds3 ef3) e3 f3 (fs3 gf3) g3 (gs3 af3) a3
+      (as3 bf3) b3
+      c4 (cs4 df4) d4 (ds4 ef4) e4 f4 (fs4 gf4) g4 (gs4 af4) a4
+      (as4 bf4) b4
+      c5 (cs5 df5) d5 (ds5 ef5) e5 f5 (fs5 gf5) g5 (gs5 af5) a5
+      (as5 bf5) b5
+      c6 (cs6 df6) d6 (ds6 ef6) e6 f6 (fs6 gf6) g6 (gs6 af6) a6
+      (as6 bf6) b6
+      c7 (cs7 df7) d7 (ds7 ef7) e7 f7 (fs7 gf7) g7 (gs7 af7) a7
+      (as7 bf7) b7
+      c8 (cs8 df8) d8 (ds8 ef8) e8 f8 (fs8 gf8) g8 (gs8 af8) a8
+      (as8 bf8) b8))
+
+   (dolist (p nyq:pitch-names)
+     (cond ((atom p) (set p (np)))
+       (t (let ((pitch (np)))
+        (dolist (s p) (set s pitch)))))))
+
+
+(set-pitch-names)
+
+(init-global *default-sound-srate* 44100.0)
+(init-global *default-control-srate* 2205.0)
+
+(setf *environment-variables*
+      '(*WARP* *SUSTAIN* *START* *LOUD* *TRANSPOSE* 
+    *STOP* *CONTROL-SRATE* *SOUND-SRATE*))
+
+(setfn environment-time car)
+(setfn environment-stretch cadr)
+
+; ENVIRONMENT-MAP - map virtual time using an environment
+;
+;(defun environment-map (env tim)
+;  (+ (environment-time env)
+;     (* (environment-stretch env) tim)))
+
+
+(defun nyq:the-environment () (mapcar 'eval *environment-variables*))
+
+
+;; GLOBAL ENVIRONMENT VARIABLES and their startup values:
+(defun nyq:environment-init ()
+  (setq *WARP*	    '(0.0 1.0 nil))
+  (setq *LOUD*      0.0)   ; now in dB
+  (setq *TRANSPOSE* 0.0)
+  (setq *SUSTAIN*   1.0)
+  (setq *START*     MIN-START-TIME)
+  (setq *STOP*      MAX-STOP-TIME)
+  (setq *CONTROL-SRATE* *DEFAULT-CONTROL-SRATE*)
+  (setq *SOUND-SRATE* *DEFAULT-SOUND-SRATE*)
+  t)				; return nothing in particular
+
+(nyq:environment-init)
+
+(defun get-duration (dur)
+  (ny:typecheck (not (numberp dur))
+    (ny:error "GET-DURATION" 0 number-anon dur))
+  (let ((duration 
+         (- (local-to-global (* (get-sustain) dur))
+            (setf *rslt* (local-to-global 0)))))
+     (cond ((minusp duration)
+            (error
+"duration is less than zero: perhaps a warp or stretch
+is ill-formed. Nyquist cannot continue because synthesis
+functions assume durations are always positive.")))
+     duration))
+
+
+(defun get-loud ()
+  (cond ((numberp *loud*) *loud*)
+    ((soundp *loud*)
+     (sref *loud* 0))
+    (t
+     (error (format t "*LOUD* should be a number or sound: ~A" *LOUD*)))))
+
+
+(defun get-sustain ()
+  (cond ((numberp *SUSTAIN*) *SUSTAIN*)
+    ((soundp *SUSTAIN*)
+     ;(display "get-sustain: lookup " (local-to-global 0) 0))
+     (sref *SUSTAIN* 0))
+    (t
+     (error (format t "*SUSTAIN* should be a number or sound: ~A" *SUSTAIN*)))))
+
+
+(defun get-tempo ()
+  (if (warp-function *WARP*)
+      (slope (snd-inverse (get-warp) (local-to-global 0)
+                          *control-srate*))
+      (/ 1.0 (warp-stretch *WARP*))))
+
+(defun get-transpose ()
+  (cond ((numberp *TRANSPOSE*) *TRANSPOSE*)
+    ((soundp *TRANSPOSE*)
+     (sref *TRANSPOSE* 0))
+    (t
+     (error (format t "*TRANSPOSE* should be a number or sound: ~A" *TRANSPOSE*)))))
+
+
+(defun get-warp ()
+  (let ((f (warp-function *WARP*)))
+    (ny:typecheck (null f)
+      (error "In GET-WARP, there is no warp function, probably because you are not within WARP or WARP-ABS"))
+    (shift-time (scale-srate f (/ (warp-stretch *WARP*)))
+                (- (warp-time *WARP*)))))
+
+
+(load "dspprims.lsp" :verbose NIL)
+(load "fileio.lsp" :verbose NIL)
+
+
+;;;;;;;;;;;;;;;;;;;;;;
+;; OSCILATORS
+;;;;;;;;;;;;;;;;;;;;;;
+
+(defun build-harmonic (n table-size)
+  (ny:typecheck (not (integerp n))
+    (ny:error "BUILD-HARMONIC" 1 '((INTEGER) "n") n))
+  (ny:typecheck (not (integerp table-size))
+    (ny:error "BUILD-HARMONIC" 2 '((INTEGER) "table-size") table-size))
+  (ny:typecheck (>= n (/ table-size 2))
+    (error "In BUILD-HARMONIC, harmonic number should be less than half the table size"
+    (list n table-size)))
+  (snd-sine 0 n table-size 1))
+
+
+(setf *SINE-TABLE* (list (build-harmonic 1 2048)
+             (hz-to-step 1.0)
+             T))
+(setf *TABLE* *SINE-TABLE*)
+
+
+(defun calculate-hz (pitch what &optional (max-fraction 0.5) maxlength)
+  (let ((hz (step-to-hz (+ pitch (get-transpose))))
+        (octaves 0) original)
+    (setf original hz)
+    (while (>= hz (* *SOUND-SRATE* max-fraction))
+      (setf octaves (1+ octaves)
+            hz (* hz 0.5)))
+    (cond ((> octaves 0)
+           (format t 
+             "Warning: ~A frequency reduced by ~A octaves from ~A to ~A hz to avoid aliasing.\n" 
+             what octaves original hz)
+           (setf octaves 0)))
+    (while (and maxlength (<= hz (/ *SOUND-SRATE* maxlength)))
+      (setf octaves (1+ octaves)
+            hz (* hz 2.0)))
+    (cond ((> octaves 0)
+           (format t 
+             "Warning: ~A frequency increased by ~A octaves from ~A to ~A hz due to restriction on maximum table length.\n" 
+             what octaves original hz)))
+    hz))
+
+
+(defun ny:assert-env-spec (env-spec message)
+  (if (not (ny:env-spec-p env-spec))
+      (error message env-spec)))
+
+
+(defun ny:assert-table (fun-name index formal actual)
+  (if (not (and (listp actual) (= 3 (length actual))))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list of 3 elements, got ~A"
+       fun-name (index-to-string index) formal actual)))
+  (if (not (soundp (car actual)))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list beginning with a sound, got ~A"
+       fun-name (index-to-string index) formal actual)))
+  (if (not (numberp (second actual)))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list whose 2nd element is a step number (pitch), got ~A"
+       fun-name (index-to-string index) formal actual)))
+  (if (not (third actual))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list whose 3rd element is true, got ~A"
+       fun-name (index-to-string index) formal actual))))
+
+
+(defun ny:assert-sample (fun-name index formal actual)
+  (if (not (and (listp actual) (= 3 (length actual))))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list of 3 elements, got ~A"
+       fun-name (index-to-string index) formal actual)))
+  (if (not (soundp (car actual)))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list beginning with a sound, got ~A"
+       fun-name (index-to-string index) formal actual)))
+  (if (not (numberp (second actual)))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list whose 2nd element is a step number (pitch), got ~A"
+       fun-name (index-to-string index) formal actual)))
+  (if (not (numberp (third actual)))
+      (error (format nil
+       "In ~A,~A argument (~A) should be a list whose 3rd element is the sample start time, got ~A"
+       fun-name (index-to-string index) formal actual))))
+
+(defun ny:env-spec-p (env-spec)
+  (prog (len (rslt t))
+    (if (not (listp env-spec)) (return nil))
+    (setf len (length env-spec))
+    (if (< len 6) (return nil))
+    (if (> len 7) (return nil))
+    (dolist (x env-spec)
+      (cond ((not (numberp x))
+             (setf rslt nil)
+             (return nil))))
+    (return rslt)))
+
+
+;; AMOSC
+;;
+(defun amosc (pitch modulation &optional (sound *table*) (phase 0.0))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "AMOSC" 1 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (soundp modulation))
+    (ny:error "AMOSC" 2 '((SOUND) "modulation") modulation))
+  (ny:assert-table "AMOSC" 3 "table" sound)
+  (ny:typecheck (not (numberp phase))
+    (ny:error "AMOSC" 4 '((NUMBER) "phase") phase))
+  (let ((modulation-srate (snd-srate modulation))
+        (hz (calculate-hz pitch "amosc")))
+    (ny:scale-db (get-loud)
+      (snd-amosc
+        (car sound)     ; samples for table
+        (cadr sound)    ; step represented by table
+        *SOUND-SRATE*   ; output sample rate
+        hz              ;  output hz
+        (local-to-global 0)	; starting time
+        modulation      ; modulation
+        phase))))       ; phase
+
+
+;; FMOSC
+;;
+;; modulation rate must be less than or equal to sound-srate, so
+;; force resampling and issue a warning if necessary. snd-fmosc can
+;; handle upsampling cases internally.
+;;
+(defun fmosc (pitch modulation &optional (sound *table*) (phase 0.0))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "FMOSC" 1 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (soundp modulation))
+    (ny:error "FMOSC" 2 '((SOUND) "modulation") modulation))
+  (ny:assert-table "FMOSC" 3 "table" sound)
+  (ny:typecheck (not (numberp phase))
+    (ny:error "FMOSC" 4 '((NUMBER) "phase") phase))
+  (let ((modulation-srate (snd-srate modulation))
+        (hz (calculate-hz pitch "fmosc")))
+    (ny:scale-db (get-loud)
+      (snd-fmosc 
+        (car sound)         ; samples for table
+        (cadr sound)        ; step represented by table
+        *SOUND-SRATE*       ; output sample rate
+        hz                  ;  output hz
+        (local-to-global 0) ; starting time
+        modulation          ; modulation
+        phase))))           ; phase
+
+
+;; FMFB
+;;
+;; this code is based on FMOSC above
+;;
+(defun fmfb (pitch index &optional (dur 1.0))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "FMFB" 1 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (or (numberp index) (soundp index)))
+    (ny:error "FMFB" 2 '((NUMBER SOUND) "index") index))
+  (ny:typecheck (not (numberp dur))
+    (ny:error "FMFB" 3 '((NUMBER) "dur") dur))
+ (let ((hz (calculate-hz pitch "fmfb")))
+   (setf dur (get-duration dur))
+   (cond ((soundp index) (ny:fmfbv hz index))
+          (t
+           (ny:scale-db (get-loud)
+                     (snd-fmfb (local-to-global 0) 
+                               hz *SOUND-SRATE* index dur))))))
+
+;; private variable index version of fmfb
+(defun ny:fmfbv (hz index)
+  (let ((modulation-srate (snd-srate index)))
+    (cond ((< *SOUND-SRATE* modulation-srate)
+           (format t "Warning: down-sampling FM modulation in fmfb~%")
+           (setf index (snd-down *SOUND-SRATE* index))))
+    (ny:scale-db (get-loud)
+              (snd-fmfbv (local-to-global 0) hz *SOUND-SRATE* index))))
+
+
+;; BUZZ
+;;
+;; (ARGUMENTS ("long" "n") ("rate_type" "sr") ("double" "hz")
+;;            ("time_type" "t0") ("sound_type" "s_fm"))
+;; 
+(defun buzz (n pitch modulation)
+  (ny:typecheck (not (integerp n))
+    (ny:error "BUZZ" 1 '((INTEGER) "number of harmonics") n))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "BUZZ" 2 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (soundp modulation))
+    (ny:error "BUZZ" 3 '((SOUND) "modulation") modulation))
+  (let ((modulation-srate (snd-srate modulation))
+	(hz (calculate-hz pitch "buzz nominal")))
+    (cond ((< *SOUND-SRATE* modulation-srate)
+           (format t "Warning: down-sampling modulation in buzz~%")
+           (setf modulation (snd-down *SOUND-SRATE* modulation))))
+    (setf n (max n 1)) ; avoid divide by zero problem
+    (ny:scale-db (get-loud)
+              (snd-buzz n                   ; number of harmonics
+                        *SOUND-SRATE*       ; output sample rate
+                        hz                  ; output hz
+                        (local-to-global 0) ; starting time
+                        modulation))))      ; freq. modulation
+                        
+
+;; (HZOSC hz [table [phase]])
+;;
+;; similar to FMOSC, but without "carrier" frequency parameter
+;; also, hz may be a scalar or a sound
+;;
+(defun hzosc (hz &optional (sound *table*) (phase 0.0))
+  (ny:typecheck (not (or (numberp hz) (soundp hz)))
+    (ny:error "HZOSC" 1 '((NUMBER SOUND) "hz") hz))
+  (ny:assert-table "HZOSC" 2 "table" sound)
+  (ny:typecheck (not (numberp phase))
+    (ny:error "HZOSC" 3 '((NUMBER) "phase") phase))
+  (let (hz-srate)
+    (cond ((numberp hz)
+           (osc (hz-to-step hz) 1.0 sound phase))
+          (t
+           (setf hz-srate (snd-srate hz))
+           (cond ((< *SOUND-SRATE* hz-srate)
+                  (format t "Warning: down-sampling hz in hzosc~%")
+                  (setf hz (snd-down *SOUND-SRATE* hz))))
+           (ny:scale-db (get-loud)
+                     (snd-fmosc (car sound) ; samples for table
+                                (cadr sound) ; step repr. by table
+                                *SOUND-SRATE* ; output sample rate
+                                0.0 ; dummy carrier
+                                (local-to-global 0) ; starting time
+                                hz phase))))))
+
+
+;; (SIOSC-BREAKPOINTS tab0 t1 tab1 ... tn tabn)
+;;   converts times to sample numbers
+;; NOTE: time-warping the spectral envelope seems
+;; like the wrong thing to do (wouldn't it be better
+;; to warp the parameters that control the spectra,
+;; or don't warp at all?). Nominally, a note should
+;; have a "score" or local time duration equal to the
+;; SUSTAIN environment variable. (When sustain is 1.0
+;; and no time-warping is in effect, the duration is 1).
+;; So, scale all times by
+;;		(local-to-global (get-sustain))
+;; so that if the final time tn = 1.0, we get a nominal
+;; length note.
+
+(defun siosc-breakpoints (breakpoints)
+  (prog (sample-count result (last-count 0) time-factor (index 0))
+    (setf time-factor
+      (- (local-to-global (get-sustain))
+         (local-to-global 0.0)))
+    (setf time-factor (* time-factor *SOUND-SRATE*))
+    (ny:typecheck (not (and (listp breakpoints)
+                            (cdr breakpoints)
+                            (cddr breakpoints)))
+      (error "In SIOSC, 3rd argument (breakpoints) must be a list with at least 3 elements"
+             breakpoints))
+loop
+    (ny:typecheck (not (and (listp breakpoints)
+                            (soundp (car breakpoints))))
+      (error (format nil 
+              "In SIOSC, expected a sound in breakpoints list at index ~A" 
+              index)
+             (car breakpoints)))
+    (push (car breakpoints) result)
+    (setf breakpoints (cdr breakpoints))
+    (setf index (1+ index))
+    (cond (breakpoints
+           (ny:typecheck (not (and (listp breakpoints)
+                                   (numberp (car breakpoints))))
+             (error (format nil
+                     "In SIOSC, expected a number (time) in breakpoints list at index ~A"
+                     index)
+                    (car breakpoints)))
+           (setf sample-count (truncate
+                               (+ 0.5 (* time-factor (car breakpoints)))))
+           (cond ((< sample-count last-count)
+                  (setf sample-count (1+ last-count))))
+           (push sample-count result)
+           (setf last-count sample-count)
+           (setf breakpoints (cdr breakpoints))
+           (setf index (1+ index))
+           (cond (breakpoints
+                  (go loop)))))
+    (setf result (reverse result))
+    (return result)))
+
+
+;; SIOSC -- spectral interpolation oscillator
+;;
+;; modulation rate must be less than or equal to sound-srate, so
+;; force resampling and issue a warning if necessary. snd-fmosc can
+;; handle upsampling cases internally.
+;;
+(defun siosc (pitch modulation breakpoints)
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "SIOSC" 1 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (soundp modulation))
+    (ny:error "SIOSC" 2 '((SOUND) "modulation") modulation))
+  (let ((modulation-srate (snd-srate modulation))
+	(hz (calculate-hz pitch "siosc nominal")))
+    (cond ((< *SOUND-SRATE* modulation-srate)
+       (format t "Warning: down-sampling FM modulation in siosc~%")
+       (setf modulation (snd-down *SOUND-SRATE* modulation))))
+    (ny:scale-db (get-loud)
+	      (snd-siosc (siosc-breakpoints breakpoints) ; tables
+			 *SOUND-SRATE*		; output sample rate
+			 hz			;  output hz
+			 (local-to-global 0)	; starting time
+			 modulation))))		; modulation
+
+
+;; LFO -- freq &optional duration sound phase)
+;;
+;; Default duration is 1.0 sec, default sound is *TABLE*, 
+;; default phase is 0.0.
+;;
+(defun lfo (freq &optional (duration 1.0)
+         (sound *SINE-TABLE*) (phase 0.0))
+  (ny:typecheck (not (numberp freq))
+    (ny:error "LFO" 1 '((NUMBER) "freq") freq))
+  (ny:typecheck (not (numberp duration))
+    (ny:error "LFO" 2 '((NUMBER) "duration") duration))
+  (ny:assert-table "LFO" 3 "table" sound)
+  (ny:typecheck (not (numberp phase))
+    (ny:error "LFO" 4 '((NUMBER) "phase") phase))
+  (let ((d (get-duration duration)))
+    (if (minusp d) (setf d 0))
+    (cond ((> freq (/ *CONTROL-SRATE* 2))
+           (format t "Warning: lfo frequency (~A hz) will alias at current control rate (~A hz).\n"
+                     freq *CONTROL-SRATE*)))
+    (ny:set-logical-stop
+      (snd-osc
+        (car sound)		; samples for table
+        (cadr sound)		; step represented by table
+        *CONTROL-SRATE*		; output sample rate
+        freq			; output hz
+        *rslt*			; starting time
+        d			; duration
+        phase)		        ; phase
+      duration)))
+
+
+;; FMLFO -- like LFO but uses frequency modulation
+;;
+(defun fmlfo (freq &optional (sound *SINE-TABLE*) (phase 0.0))
+  (ny:typecheck (not (soundp freq))
+    (ny:error "FMLFO" 1 '((SOUND) "freq") freq))
+  (ny:assert-table "FMLFO" 2 "table" sound)
+  (ny:typecheck (not (numberp phase))
+    (ny:error "FMLFO" 3 '((NUMBER) "phase") phase))
+  (let ()
+    (cond ((numberp freq)
+           (lfo freq 1.0 sound phase))
+          ((soundp freq)
+           (cond ((> (snd-srate freq) *CONTROL-SRATE*)
+                  (setf freq (force-srate *CONTROL-SRATE* freq))))
+           (snd-fmosc (car sound) (cadr sound) *CONTROL-SRATE* 0.0 
+                      (local-to-global 0) freq phase))
+          (t
+           (error "frequency must be a number or sound")))))
+
+
+;; OSC - table lookup oscillator
+;;
+(defun osc (pitch &optional (duration 1.0) 
+            (sound *TABLE*) (phase 0.0))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "OSC" 1 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (numberp duration))
+    (ny:error "OSC" 2 '((NUMBER) "duration") duration))
+  (ny:assert-table "OSC" 3 "table" sound)
+  (ny:typecheck (not (numberp phase))
+    (ny:error "OSC" 4 '((NUMBER) "phase") phase))
+  (let ((d  (get-duration duration))
+        (hz (calculate-hz pitch "osc")))
+    (ny:set-logical-stop
+      (snd-scale (db-to-linear (get-loud))
+        (snd-osc 
+          (car sound)		; samples for table
+          (cadr sound)		; step represented by table
+          *SOUND-SRATE*		; output sample rate
+          hz			;  output hz
+          *rslt*		; starting time
+          d			; duration
+          phase))               ; phase
+      duration)))
+
+
+;; PARTIAL -- sine osc with built-in envelope scaling
+;;
+(defun partial (steps env)
+  (ny:typecheck (not (numberp steps))
+    (ny:error "PARTIAL" 1 '((STEP) "steps") steps))
+  (ny:typecheck (not (soundp env))
+    (ny:error "PARTIAL" 2 '((SOUND) "env") env))
+  (let ((hz (calculate-hz steps "partial")))
+    (ny:scale-db (get-loud)
+      (snd-partial *sound-srate* hz
+                   (force-srate *sound-srate* env)))))
+
+
+(setf *SINE-SAMPLE* (list (first *TABLE*) (second *TABLE*) 0.0))
+
+
+;; SAMPLER -- simple attack + sustain sampler
+;;
+(defun sampler (pitch modulation 
+                &optional (sample *SINE-SAMPLE*) (npoints 2))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "SAMPLER" 1 '((STEP) "pitch") pitch))
+  (ny:typecheck (not (soundp modulation))
+    (ny:error "SAMPLER" 2 '((SOUND) "modulation") modulation))
+  (ny:assert-sample "SAMPLER" 3 "table" sample)
+  (ny:typecheck (not (integerp npoints))
+    (ny:error "SAMPLER" 3 '((INTEGER) "npoints") npoints))
+  (let ((samp (car sample))
+        (samp-pitch (cadr sample))
+        (samp-loop-start (caddr sample))
+        (hz (calculate-hz pitch "sampler nominal")))
+    ; make a waveform table look like a sample with no attack:
+    (cond ((not (numberp samp-loop-start))
+           (setf samp-loop-start 0.0)))
+    (ny:scale-db (get-loud)
+       (snd-sampler 
+        samp		; samples for table
+        samp-pitch	; step represented by table
+        samp-loop-start ; time to start loop
+        *SOUND-SRATE*	; output sample rate
+        hz		;  output hz
+        (local-to-global 0)	; starting time
+        modulation	; modulation
+        npoints))))    	; number of interpolation points
+
+
+;; SINE -- simple sine oscillator
+;;
+(defun sine (steps &optional (duration 1.0))
+  (ny:typecheck (not (numberp steps))
+    (ny:error "SINE" 1 '((STEP) "steps") steps))
+  (ny:typecheck (not (numberp duration))
+    (ny:error "SINE" 2 '((NUMBER) "duration") duration))
+  (let ((hz (calculate-hz steps "sine"))
+        (d (get-duration duration)))
+    (ny:set-logical-stop
+      (ny:scale-db (get-loud)
+        (snd-sine *rslt* hz *sound-srate* d))
+      duration)))
+
+
+;; PLUCK
+;;
+;; (ARGUMENTS ("double" "sr") ("double" "hz") ("time_type" "t0") 
+;;            ("time_type" "d") ("double" "final_amp"))
+;;
+(defun pluck (steps &optional (duration 1.0) (final-amp 0.001))
+  (ny:typecheck (not (numberp steps))
+    (ny:error "PLUCK" 1 '((NUMBER) "steps") steps))
+  (ny:typecheck (not (numberp duration))
+    (ny:error "PLUCK" 2 '((NUMBER) "duration") duration))
+  (ny:typecheck (not (numberp final-amp))
+    (ny:error "PLUCK" 3 '((NUMBER) "final-amp") final-amp))
+  ;; 200000 is MAXLENGTH in nyquist/tran/pluck.alg - the max table length
+  (let ((hz (calculate-hz steps "pluck" (/ 1.0 3) 200000))
+        (d (get-duration duration)))
+    (ny:set-logical-stop
+      (ny:scale-db (get-loud)
+        (snd-pluck *SOUND-SRATE* hz *rslt* d final-amp))
+      duration)))
+
+
+;; abs-env -- restore the standard environment
+;;
+(defmacro abs-env (s)
+  `(progv '(*WARP* *LOUD* *TRANSPOSE* *SUSTAIN* 
+            *START* *STOP*
+            *CONTROL-SRATE* *SOUND-SRATE*)
+          (list '(0.0 1.0 NIL) 0.0 0.0 1.0
+           MIN-START-TIME MAX-STOP-TIME
+           *DEFAULT-CONTROL-SRATE* *DEFAULT-SOUND-SRATE*)
+     ,s))
+
+
+;; (NYQ:TO-ARRAY SOUND N) - duplicate SOUND to N channels
+;
+(defun nyq:to-array (value len)
+  (let ((a (make-array len)))
+    (dotimes (i len)
+      (setf (aref a i) value))
+    a))
+
+
+; nyq:add2 - add two arguments. 
+;
+; Assumes s1 and s2 are numbers, sounds, or multichannel sounds or numbers
+;
+; Semantics: numbers and sounds can be freely mixed and 
+;    add as expected. Arrays (multichannel) arguments are
+;    added channel-by-channel, and if one array is larger,
+;    the "extra" channels are simply copied to the result.
+;    Therefore the result has the channel count of the 
+;    maximum channel count in s1 or s2. When adding a
+;    multichannel sound to a (non-multichannel) sound, the
+;    sound is coerced to a 1-channel multi-channel sound,
+;    and therefore adds to channel 1 of the multi-channel 
+;    sound. However, when adding a multichannel sound to a
+;    number, the number is added to *every* channel.
+; Semantics differ from the normal multichan-expand processing
+;    in that sounds are considered to be a multichannel sound
+;    with 1 channel, and channel counts do not have to match
+;    when processing array arguments.
+; 
+(defun nyq:add2 (s1 s2)
+        ; make number + number as fast as possible:
+  (cond ((and (numberp s1) (numberp s2)) (+ s1 s2))
+        ; if not 2 numbers, the overhead here is amortized by
+        ;    computing samples of at least one sound
+        ((and (arrayp s1) (numberp s2))
+          (sum-of-arrays s1 (nyq:to-array s2 (length s1))))
+        ((and (arrayp s2) (numberp s1))
+          (sum-of-arrays (nyq:to-array s1 (length s2)) s2))
+        ((and (arrayp s1) (soundp s2))
+         (sum-of-arrays s1 (vector s2)))
+        ((and (arrayp s2) (soundp s1))
+         (sum-of-arrays (vector s1) s2))
+        ((and (arrayp s1) (arrayp s2))
+         (sum-of-arrays s1 s2))
+        ((numberp s1)
+         (snd-offset s2 s1))
+        ((numberp s2)
+         (snd-offset s1 s2))
+        (t
+         (nyq:add-2-sounds s1 s2))))
+
+
+; (NYQ:ADD-2-SOUNDS S1 S2) - add two sound arguments
+; 
+; assumes s1 and s2 are sounds
+;
+(defun nyq:add-2-sounds (s1 s2)
+  (let ((s1sr (snd-srate s1))
+        (s2sr (snd-srate s2)))
+    (cond ((> s1sr s2sr)
+           (snd-add s1 (snd-up s1sr s2)))
+          ((< s1sr s2sr)
+           (snd-add (snd-up s2sr s1) s2))
+          (t
+           (snd-add s1 s2)))))
+
+
+(defmacro at (x s)
+ `(progv '(*WARP*)
+         (let ((shift ,x))
+           (ny:typecheck (not (numberp shift))
+               (error "1st argument of AT (or 2nd argument of SAL's @ operator) should be a time offset number" shift))
+           (list (list (+ (warp-time *WARP*) 
+                       (* (warp-stretch *WARP*) shift))
+                       (warp-stretch *WARP*)
+                       (warp-function *WARP*))))
+      ,s))
+
+
+;; (AT-ABS t behavior) evaluate behavior at global time t
+;;
+;; *WARP* is the triple (d s f) denoting the function f(st+d),
+;; a mapping from local to global time.
+;; We want (d' s f) such that f(s*0 + d') = t
+;; (Note that we keep the same s and f, and only change the offset.
+;; To eliminate the warp and stretch use "(abs-env (at t behavior))")
+;; Applying the inverse of f, d' = f-1(t), or (sref (snd-inverse f ...) t)
+;; Rather than invert the entire function just to evaluate at one point,
+;; we use SREF-INVERSE to find d'.
+;;
+(defmacro at-abs (x s)
+ `(progv '(*WARP*)
+         (let ((tim ,x))
+           (ny:typecheck (not (numberp tim))
+               (error "1st argument of AT-ABS (or 2nd argument of SAL's @@ operator) should be a number (start time)" tim))
+           (if (warp-function *WARP*)
+               (list (list (sref-inverse (warp-function *WARP*) tim)
+                           (warp-stretch *WARP*)
+                           (warp-function *WARP*)))
+               (list (list tim (warp-stretch *WARP*) NIL))))
+    ;; issue warning if sound starts in the past
+    (check-t0 ,s ',s)))
+
+
+(defun check-t0 (s src)
+  (let (flag t0 (now (local-to-global 0)))
+    (cond ((arrayp s)
+           (dotimes (i (length s))
+             (setf t0 (snd-t0 (aref s i))))
+             (if (< t0 now) (setf flag t0)))
+          (t
+           (setf t0 (snd-t0 s))
+           (if (< t0 now) (setf flag t0))))
+    (if flag
+        (format t "Warning: cannot go back in time to ~A, sound came from ~A~%"
+                  flag src))
+    ; (display "check-t0" t0 now src)
+    ; return s whether or not warning was reported
+    s))
+
+;; (CLIP S1 VALUE) - clip maximum amplitude to value
+;
+(defun clip (x v)
+  (ny:typecheck (not (or (numberp x) (soundp x) (multichannelp x)))
+    (ny:error "CLIP" 1 number-sound-anon x t))
+  (ny:typecheck (not (numberp v))
+    (ny:error "CLIP" 2 number-anon v))
+  (cond ((numberp x)
+         (max (min x v) (- v)))
+        ((arrayp x)
+         (let* ((len (length x))
+           (result (make-array len)))
+           (dotimes (i len)
+             (setf (aref result i) 
+             (snd-clip (aref x i) v)))
+         result))
+        (t ;; x is a sound
+         (snd-clip x v))))
+
+
+;; (NYQ:COERCE-TO S1 S2) - expand sound s1 to type of s2
+; 
+(defun nyq:coerce-to (s1 s2)
+  (cond ((or (soundp s1) (numberp s1))
+         (cond ((arrayp s2)
+                (nyq:to-array s1 (length s2)))
+               (t s1)))
+         (t s1)))
+
+
+(defmacro continuous-control-warp (beh)
+  `(snd-compose (warp-abs nil ,beh)
+        (snd-inverse (get-warp)
+         (local-to-global 0) *control-srate*)))
+
+(defmacro continuous-sound-warp (beh)
+  `(snd-compose (warp-abs nil ,beh)
+        (snd-inverse (get-warp)
+         (local-to-global 0) *sound-srate*)))
+
+
+(defmacro control-srate-abs (r s)
+  `(let ((rate ,r))
+     (progv '(*CONTROL-SRATE*)
+            (progn (ny:typecheck (not (numberp rate))
+                     (ny:error "CONTROL-SRATE-ABS" 1 '((NUMBER) "sample rate") rate))
+                 (list rate))
+      ,s)))
+
+; db = 20log(ratio)
+; db = 20 ln(ratio)/ln(10)
+; db/20 = ln(ratio)/ln(10)
+; db ln(10)/20 = ln(ratio)
+; e^(db ln(10)/20) = ratio
+;
+(setf ln10over20 (/ (log 10.0) 20))
+
+(defun db-to-linear (x) 
+  (ny:typecheck (not (or (numberp x) (soundp x) (multichannelp x)))
+    (ny:error "DB-TO-LINEAR" 0 number-sound-anon x t))
+  (cond ((numberp x)
+     (exp (* ln10over20 x)))
+    ((arrayp x)
+     (let* ((len (length x))
+        (result (make-array len)))
+        (dotimes (i len)
+          (setf (aref result i) 
+                (snd-exp (snd-scale ln10over20 (aref x i)))))
+        result))
+    (t
+     (snd-exp (snd-scale ln10over20 x)))))
+
+
+(defun linear-to-db (x) 
+  (ny:typecheck (not (or (numberp x) (soundp x) (multichannelp x)))
+    (ny:error "LINEAR-TO-DB" 0 number-sound-anon x t))
+  (cond ((numberp x)
+     (/ (log (float x)) ln10over20))
+    ((arrayp x)
+     (let* ((len (length x))
+        (result (make-array len)))
+        (dotimes (i len)
+          (setf (aref result i) 
+                (snd-scale (/ 1.0 ln10over20) (snd-log (aref x i)))))
+        result))
+    (t
+     (snd-scale (/ 1.0 ln10over20) (snd-log x)))))
+
+
+(cond ((not (fboundp 'scalar-step-to-hz))
+       (setfn scalar-step-to-hz step-to-hz)
+       (setfn scalar-hz-to-step hz-to-step)))
+
+
+(defun step-to-hz (x)
+  (ny:typecheck (not (or (numberp x) (soundp x) (multichannelp x)))
+    (ny:error "STEP-TO-HZ" 0 number-sound-anon x t))
+  (cond ((numberp x)
+         (scalar-step-to-hz x))
+        ((arrayp x)
+         (let* ((len (length x))
+                (result (make-array len)))
+           (dotimes (i len)
+             (setf (aref result i) (step-to-hz (aref x i))))
+           result))
+        (t
+         (s-exp (snd-offset (snd-scale 0.0577622650466621 x) 
+                            2.1011784386926213)))))
+
+(defun hz-to-step (x)
+  (ny:typecheck (not (or (numberp x) (soundp x) (multichannelp x)))
+    (ny:error "HZ-TO-STEP" 0 number-sound-anon x t))
+  (cond ((numberp x)
+         (scalar-hz-to-step x))
+        ((arrayp x)
+         (let* ((len (length x))
+                (result (make-array len)))
+           (dotimes (i len)
+             (setf (aref result i) (hz-to-step (aref x i))))
+           result))
+        (t
+         (snd-scale 17.312340490667565
+                    (snd-offset (s-log x) -2.1011784386926213))))) 
+
+
+; sref - access a sound at a given time point
+;    note that the time is transformed to global
+(defun sref (sound point)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "SREF" 1 '((SOUND) "sound") sound))
+  (ny:typecheck (not (numberp point))
+    (ny:error "SREF" 2 '((NUMBER) "time") point))
+  (snd-sref sound (local-to-global point)))
+
+
+; extract - start is stretched and shifted as is stop
+;  result is shifted to start at local time zero
+(defun extract (start stop sound)
+  (ny:typecheck (not (numberp start))
+    (ny:error "EXTRACT" 1 '((NUMBER) "start") start))
+  (ny:typecheck (not (numberp stop))
+    (ny:error "EXTRACT" 2 '((NUMBER) "stop") stop))
+  (ny:typecheck (< stop start) 
+    (error
+      (format nil "In EXTRACT, stop (~A) must be greater or equal to start (~A)"
+                  stop start)))
+  (ny:typecheck (not (soundp sound))
+    (ny:error "EXTRACT" 3 '((SOUND) "sound") sound))
+  (extract-abs (local-to-global start) (local-to-global stop) sound
+               (local-to-global 0)))
+
+; extract-abs - return sound between start and stop
+;  start-time is optional (to aid the implementation of
+;  extract) and gives the start time of the result, normally 0.
+;  There is a problem if sound t0 is not equal to start-time.
+;  E.g. if sound was created with AT, its t0 might be
+;  in the future, but snd-xform works by first shifting
+;  t0 to local time zero, so we need to be very careful.
+;  The solution is that if t0 > start_time, subtract the difference
+;  from start and stop to shift them appropriately.
+(defun extract-abs (start stop sound &optional (start-time 0))
+  (ny:typecheck (not (numberp start))
+    (ny:error "EXTRACT-ABS" 1 '((NUMBER) "start") start))
+  (ny:typecheck (not (numberp stop))
+    (ny:error "EXTRACT-ABS" 2 '((NUMBER) "stop") stop))
+  (ny:typecheck (< stop start) 
+    (error
+      (format nil
+       "In EXTRACT-ABS, stop (~A) must be greater or equal to start (~A)"
+       stop start)))
+  (ny:typecheck (not (soundp sound))
+    (ny:error "EXTRACT-ABS" 3 '((SOUND) "sound") sound))
+  (ny:typecheck (not (numberp start-time))
+    (ny:error "EXTRACT-ABS" 4 '((NUMBER) "start-time") start-time))
+  (let ((t0 (snd-t0 sound)) offset)
+    (cond ((/= t0 start-time)
+           (setf offset (- t0 start-time))
+           (setf start (- start offset))
+           (setf stop (- stop offset))))
+    (snd-xform sound (snd-srate sound) start-time start stop 1.0)))
+
+
+(defun local-to-global (local-time)
+  (ny:typecheck (not (numberp local-time))
+    (ny:error "LOCAL-TO-GLOBAL" 0 '((NUMBER) "local-time") local-time))
+  (let ((d (warp-time *WARP*))
+    (s (warp-stretch *WARP*))
+    (w (warp-function *WARP*))
+    global-time)
+    (setf global-time (+ (* s local-time) d))
+    (if w (snd-sref w global-time) global-time)))
+
+
+(defmacro loud (x s)
+ `(progv '(*LOUD*)
+         (let ((ld ,x))
+           (ny:typecheck (not (or (numberp ld) (soundp ld)))
+               (ny:error "LOUD" 1 number-sound-anon ld))
+           (list (sum *LOUD* ld)))
+     ,s))
+
+
+(defmacro loud-abs (x s)
+ `(progv '(*LOUD*)
+         (let ((ld ,x))
+           (ny:typecheck (not (or (numberp ld) (soundp ld)))
+                (ny:error "LOUD-ABS" 1 number-anon ld))
+           (list ld))
+     ,s))
+
+
+;(defun must-be-sound (x)
+; (cond ((soundp x) x)
+;       (t
+;        (error "SOUND type expected" x))))
+
+
+;; NY:SCALE-DB -- a "fast" scale-db: no typechecks and
+;;                no multichannel expansion
+(defun ny:scale-db (factor sound)
+  (snd-scale (db-to-linear factor) sound))
+
+
+;; SCALE-DB -- same as scale, but argument is in db
+;;
+(defun scale-db (factor sound)
+;  (ny:typecheck (not (or (numberp factor) (numbersp factor)))
+;    (ny:error "SCALE-DB" 1 '((NUMBER) "dB") factor t))
+;  (ny:typecheck (not (or (soundp sound) (multichannel-soundp sound)))
+;    (ny:error "SCALE-DB" 2 '((SOUND) "sound") sound t))
+  (multichan-expand "SCALE-DB" #'ny:scale-db 
+    '(((NUMBER) "factor") ((SOUND) "sound")) factor sound))
+    
+
+
+(defun set-control-srate (rate)
+  (ny:typecheck (not (numberp rate))
+    (ny:error "SET-CONTROL-SRATE" 0 '((NUMBER) "rate") rate))
+  (setf *default-control-srate* (float rate))
+  (nyq:environment-init))
+
+(defun set-sound-srate (rate) 
+  (ny:typecheck (not (numberp rate))
+    (ny:error "SET-SOUND-SRATE" 0 '((NUMBER) "rate") rate))
+  (setf *default-sound-srate* (float rate))
+  (nyq:environment-init))
+
+
+; s-plot -- compute and write n data points for plotting
+;
+; dur is how many seconds of sound to plot. If necessary, cut the
+;     sample rate to allow plotting dur seconds
+; n is the number of points to plot. If there are more than n points,
+;     cut the sample rate. If there are fewer than n samples, just
+;     plot the points that exist.
+;
+(defun s-plot (snd &optional (dur 2.0) (n 1000))
+  (ny:typecheck (not (soundp snd))
+    (ny:error "S-PLOT (or PLOT command)" 1 '((SOUND) nil) snd))
+  (ny:typecheck (not (numberp dur))
+    (ny:error "S-PLOT (or PLOT command)" 2 '((NUMBER) "dur") dur))
+  (ny:typecheck (not (integerp n))
+    (ny:error "S-PLOT (or PLOT command)" 3 '((INTEGER) nil) n))
+
+  (prog* ((sr (snd-srate snd))
+          (t0 (snd-t0 snd))
+          (filename (soundfilename *default-plot-file*))
+          (s snd) ;; s is either snd or resampled copy of snd
+          (outf (open filename :direction :output)) ;; for plot data
+          (maximum -1000000.0) ;; maximum amplitude
+          (minimum  1000000.0) ;; minimum amplitude
+          actual-dur ;; is the actual-duration of snd
+          sample-count ;; is how many samples to get from s
+          period  ;; is the period of samples to be plotted
+          truncation-flag     ;; true if we didn't get whole sound
+          points) ;; is array of samples
+     ;; If we need more than n samples to get dur seconds, resample
+     (cond ((< n (* dur sr))
+            (setf s (force-srate (/ (float n) dur) snd))))
+     ;; Get samples from the signal
+     (setf points (snd-samples s (1+ n)))
+     ;; If we got fewer than n points, we can at least estimate the
+     ;; actual duration (we might not know exactly if we use a lowered
+     ;; sample rate). If the actual sample rate was lowered to avoid
+     ;; getting more than n samples, we can now raise the sample rate
+     ;; based on our estimate of the actual sample duration.
+     ;(display "test" (length points) n)
+     (cond ((< (length points) n)
+            ;; sound is shorter than dur, estimate actual length
+            (setf actual-dur (/ (length points) (snd-srate s)))
+            (setf sample-count (round (min n (* actual-dur sr))))
+            (cond ((< n (* actual-dur sr))
+                   (setf s (force-srate (/ (float n) actual-dur) snd)))
+                  (t ;; we can use original signal
+                   (setf s snd)))
+            (setf points (snd-samples s sample-count))
+            ;; due to rounding, need to recalculate exact count
+            (setf sample-count (length points)))
+           ((= (length points) n)
+            (setf actual-dur dur)
+            (setf sample-count n))
+           (t ;; greater than n points, so we must have truncated sound
+            (setf actual-dur dur)
+            (setf sample-count n)
+            (setf truncation-flag t)))
+     ;; actual-dur is the duration of the plot
+     ;; sample-count is how many samples we have
+     (setf period (/ 1.0 (snd-srate s)))
+     (cond ((null outf)
+            (format t "s-plot: could not open ~A!~%" filename)
+            (return nil)))
+    (format t "s-plot: writing ~A ... ~%" filename)
+    (cond (truncation-flag
+           (format t "        !!TRUNCATING SOUND TO ~As\n" actual-dur)))
+    (cond ((/= (snd-srate s) (snd-srate snd))
+           (format t "        !!RESAMPLING SOUND FROM ~A to ~Ahz\n"
+                   (snd-srate snd) (snd-srate s))))
+    (cond (truncation-flag
+           (format t "        Plotting ~As, actual sound duration is greater\n"
+                     actual-dur))
+          (t
+           (format t "        Sound duration is ~As~%" actual-dur)))
+    (dotimes (i sample-count)
+      (setf maximum (max maximum (aref points i)))
+      (setf minimum (min minimum (aref points i)))
+      (format outf "~A ~A~%" (+ t0 (* i period)) (aref points i)))
+    (close outf)
+    (format t "        Wrote ~A points from ~As to ~As~%" 
+              sample-count t0 (+ t0 actual-dur))
+    (format t "        Range of values ~A to ~A\n" minimum maximum)
+    (cond ((or (< minimum -1) (> maximum 1))
+           (format t "        !!SIGNAL EXCEEDS +/-1~%")))))
+
+
+; run something like this to plot the points:
+; graph < points.dat | plot -Ttek
+
+(defmacro sound-srate-abs (r s)
+ `(progv '(*SOUND-SRATE*) 
+         (let ((rate ,r))
+            (ny:typecheck (not (numberp rate))
+              (ny:error "SOUND-SRATE-ABS" 1 '((NUMBER) "sample rate") rate))
+            (list rate))
+      ,s))
+
+
+(defmacro stretch (x s)
+ `(progv '(*WARP*)
+         (let ((str ,x))
+           (ny:typecheck (not (numberp str))
+               (error "1st argument of STRETCH (or 2nd argument of SAL's ~ operator) should be a number (stretch factor)" str))
+                (list (list (warp-time *WARP*)
+                            (* (warp-stretch *WARP*) str)
+                            (warp-function *WARP*))))
+     (ny:typecheck (minusp (warp-stretch *WARP*))
+         (error "In STRETCH (or SAL's ~ operator), negative stretch factor is not allowed"
+                (warp-stretch *WARP*)))
+     ,s))
+
+         
+(defmacro stretch-abs (x s)
+ `(progv '(*WARP*)
+         (let ((str ,x))
+           (ny:typecheck (not (numberp str))
+               (error "1st argument of STRETCH-ABS (or 2nd argument of SAL's ~~ operator) should be a number (stretch factor)" str))
+           (list (list (local-to-global 0) str nil)))
+     (ny:typecheck (minusp (warp-stretch *WARP*))
+         (error "In STRETCH-ABS (or SAL's ~~ operator), negative stretch factor is not allowed"
+                (warp-stretch *WARP*)))
+     ,s))
+
+
+(defmacro sustain (x s)
+ `(progv '(*SUSTAIN*)
+         (let ((sus ,x))
+           (ny:typecheck (not (or (numberp sus) (soundp sus)))
+               (ny:error "SUSTAIN" 1 number-sound-anon sus))
+           (list (prod *SUSTAIN* sus)))
+      ,s))
+
+
+(defmacro sustain-abs (x s)
+ `(progv '(*SUSTAIN*)
+         (let ((sus ,x))
+           (ny:typecheck (not (or (numberp sus) (soundp sus)))
+               (ny:error "SUSTAIN-ABS" 1 number-sound-anon sus))
+           (list sus))
+      ,s))
+
+
+;; (WARP-FUNCTION *WARP*) - extracts function field of warp triple
+;;
+(setfn warp-function caddr)
+
+
+;; (WARP-STRETCH *WARP*) - extracts stretch field of warp triple
+;;
+(setfn warp-stretch cadr)
+
+
+;; (WARP-TIME *WARP*) - extracts time field of warp triple
+;;
+(setfn warp-time car)
+
+
+(defmacro transpose (x s)
+ `(progv '(*TRANSPOSE*)
+         (let ((amt ,x))
+           (ny:typecheck (not (or (numberp amt) (soundp amt)))
+                         (ny:error "TRANSPOSE" 1 number-sound-anon amt))
+           (list (sum *TRANSPOSE* amt)))
+      ,s))
+
+
+(defmacro transpose-abs (x s)
+ `(progv '(*TRANSPOSE*)
+         (let ((amt ,x))
+           (ny:typecheck (not (or (numberp amt) (soundp amt)))
+               (ny:error "TRANSPOSE-ABS" 1 number-anon amt))
+           (list amt))
+      ,s))
+
+
+;; CONTROL-WARP -- apply a warp function to a control function
+;; 
+(defun control-warp (warp-fn control &optional wrate)
+  (ny:typecheck (not (soundp warp-fn))
+    (ny:error "CONTROL-WARP" 1 '((SOUND) "warp-fn") warp-fn))
+  (ny:typecheck (not (soundp control))
+    (ny:error "CONTROL-WARP" 2 '((SOUND) "control") control))
+  (cond (wrate
+     (ny:typecheck (not (numberp wrate))
+       (ny:error "CONTROL-WARP" 3 '((NUMBER) "wrate") wrate))
+     (snd-resamplev control *control-srate*
+            (snd-inverse warp-fn (local-to-global 0) wrate)))
+    (t
+     (snd-compose control
+            (snd-inverse warp-fn (local-to-global 0) *control-srate*)))))
+
+
+;; (cue sound)
+;;    Cues the given sound; that is, it applies the current *WARP*, *LOUD*,
+;; *START*, and *STOP* values to the argument.  The logical start time is at
+;; local time 0.
+(defun cue (sound)
+  (ny:typecheck (not (or (soundp sound) (multichannel-soundp sound)))
+    (ny:error "CUE" 0 '((SOUND) nil) sound t))
+  (cond ((arrayp sound)
+     (let* ((len (length sound))
+        (result (make-array len)))
+        (dotimes (i len)
+          (setf (aref result i)
+                (cue-sound (aref sound i))))
+        result))
+    (t
+     (cue-sound sound))))
+
+(defun cue-sound (sound)
+  (snd-xform sound
+         (snd-srate sound)
+         (local-to-global 0) *START* *STOP* (db-to-linear (get-loud))))
+
+;; (sound sound)
+;;    Same as (cue sound), except also warps the sound.
+;; Note that the *WARP* can change the pitch of the
+;; sound as a result of resampling.
+;; Here's the derivation for the warping code:
+;; *WARP* is a triple: (d s f) which denotes that the warp from local to
+;; global time is: f(st+d)
+;; We need to compose sound with the inverse of this to get a function
+;; of global time
+;; Let f-1 be the inverse of f.  Then the inverse of f(st+d) is 
+;; (f-1(t) - d)/s
+;; The composition gives us: (snd-compose sound (f-1(t) - d)/s)
+;; Eliminate the 1/s term by changing the sample rate of sound:
+;;  = (snd-compose (snd-scale-srate sound s) (f-1(t) - d))
+;; Eliminate the -d term by shifting f before taking the inverse:
+;;  = (snd-compose (scale-srate sound s) ((inverse f) - d))
+;;  = (snd-compose (scale-srate sound s) (inverse f(t + d)))
+;;  = (snd-compose (scale-srate sound s) (inverse (shift f -d)))
+;; snd-inverse takes a time and sample rate.  For time, use zero.
+;; The sample rate of inverse determines the final sample rate of
+;; this function, so use *SOUND-SRATE*:
+;;  = (snd-compose (scale-srate sound s) (snd-inverse (shift-time f (- d))
+;;                                              0 *SOUND-SRATE*))
+;;
+(defun nyq:sound (sound)
+   (cond ((null (warp-function *WARP*))
+      (snd-xform sound (/ (snd-srate sound) (warp-stretch *WARP*))
+             (local-to-global 0)
+             *START* *STOP* (db-to-linear (get-loud))))
+     (t
+      (snd-compose (scale-srate sound (warp-stretch *WARP*))
+               (snd-inverse (shift-time (warp-function *WARP*)
+                        (- (warp-time *WARP*)))
+                    0 *SOUND-SRATE*)))))
+
+(defun nyq:sound-of-array (sound)
+  (let* ((n (length sound))
+         (s (make-array n)))
+    (dotimes (i n)
+      (setf (aref s i) (nyq:sound (aref sound i))))
+    s))
+
+
+(defun sound (sound)
+  (ny:typecheck (not (or (soundp sound) (multichannel-soundp sound)))
+    (ny:error "SOUND" 0 '((SOUND) nil) sound t))
+  (cond ((arrayp sound)
+     (nyq:sound-of-array sound))
+    (t
+     (nyq:sound sound))))
+
+
+;; (SCALE-SRATE SOUND SCALE)
+;; multiplies the sample rate by scale
+(defun scale-srate (sound scale)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "SCALE-SRATE" 1 '((SOUND) "sound") sound))
+  (ny:typecheck (not (numberp scale))
+    (ny:error "SCALE-SRATE" 2 '((NUMBER) "scale") scale))
+  (let ((new-srate (* scale (snd-srate sound))))
+    (snd-xform sound new-srate (snd-time sound) 
+           MIN-START-TIME MAX-STOP-TIME 1.0)))
+
+
+;; (SHIFT-TIME SOUND SHIFT)
+;; shift the time of a function by SHIFT, i.e. if SOUND is f(t),
+;; then (shift-time SOUND SHIFT) is f(t - SHIFT).  Note that if
+;; you look at plots, the shifted sound will move *right* when SHIFT
+;; is positive.  
+(defun shift-time (sound shift)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "SHIFT-TIME" 1 '((SOUND) "sound") sound))
+  (ny:typecheck (not (numberp shift))
+    (ny:error "SHIFT-TIME" 2 '((NUMBER) "shift") shift))
+  (snd-xform sound (snd-srate sound) (+ (snd-t0 sound) shift)
+         MIN-START-TIME MAX-STOP-TIME 1.0))
+
+
+;; (control sound)
+;;    Same as (sound sound), except this is used for control signals.  
+;;    This code is identical to sound.
+(defun control (sound)
+  (ny:typecheck (not (or (soundp sound) (multichannel-soundp sound)))
+    (ny:error "CONTROL" 0 '((SOUND) nil) sound t))
+  (cond ((arrayp sound)
+     (nyq:sound-of-array sound))
+    (t
+     (nyq:sound sound))))
+
+
+;; (cue-file string)
+;;    Loads a sound file with the given name, returning a sound which is
+;; transformed to the current environment.
+(defun cue-file (name)
+    (ny:typecheck (not (stringp name))
+      (ny:error "CUE-FILE" 0 '((STRING) "name") name))
+    (cue (force-srate *SOUND-SRATE* (s-read name))))
+
+
+;; (env t1 t2 t4 l1 l2 l3 &optional duration)
+;; Creates a 4-phase envelope.
+;;	tN is the duration of phase N, and lN is the final level of
+;;	phase N.  t3 is implied by the duration, and l4 is 0.0.
+;;	If dur is not supplied, then 1.0 is assumed.  The envelope
+;;	duration is the product of dur, *STRETCH*, and *SUSTAIN*.  If 
+;;	t1 + t2 + 2ms + t4 > duration, then a two-phase envelope is
+;;	substituted that has an attack/release time ratio = t1/t4.
+;;	The sample rate of the returned sound is *CONTROL-SRATE*.
+;;
+;; Time transformation: the envelope is not warped; the start time and
+;; stop times are warped to global time.  Then the value of *SUSTAIN* at
+;; the beginning of the envelope is used to determining absolute duration.
+;; Since PWL is ultimately called to create the envelope, we must use
+;; ABS-ENV to prevent any further transforms inside PWL.  We use
+;; (AT global-start ...) inside ABS-ENV so that the final result has 
+;; the proper starting time.
+;;
+(defun env (t1 t2 t4 l1 l2 l3 &optional (duration 1.0))
+  (ny:typecheck (not (and (numberp t1) (numberp t2) (numberp t4)
+                          (numberp l1) (numberp l2) (numberp l3)))
+    (error "In ENV, expected 6 numbers (t1, t2, t4, l1, l2, l3)"
+           (list t1 t2 t4 l1 l2 l3)))
+  (ny:typecheck (not (numberp duration))
+    (ny:error "ENV" 7 '((NUMBER) "duration") duration))
+  (let (actual-dur min-dur ratio t3
+    (actual-dur (get-duration duration)))
+    (setf min-dur (+ t1 t2 t4 0.002))
+    (cond ((< actual-dur min-dur)
+       (setf ratio (/ t1 (float (+ t1 t4))))
+       (setf t1 (* ratio actual-dur))
+       (setf t2 (- actual-dur t1))
+       (setf t3 0.0)
+       (setf t4 0.0)
+       (setf l2 0.0)
+       (setf l3 0.0))
+      (t
+       (setf t3 (- actual-dur t1 t2 t4))))
+    (ny:set-logical-stop
+      (abs-env (at *rslt*
+                   (pwl t1 l1 (+ t1 t2) l2 (- actual-dur t4) l3 actual-dur)))
+      duration)))
+
+
+(defun to-mono (sound)
+  (ny:typecheck (not (or (soundp sound) (multichannel-soundp sound)))
+    (ny:error "TO-MONO" 1 '((SOUND) NIL) sound t))
+  (let ((s sound))
+    (cond ((arrayp sound)
+           (setf s (aref sound 0))  ;; ANY channel opens the gate
+            (dotimes (i (1- (length sound)))
+             (setf s (nyq:add-2-sounds s (aref sound (1+ i)))))))
+    s))
+
+
+(defun gate (sound lookahead risetime falltime floor threshold 
+             &optional (source "GATE"))
+  ;(ny:typecheck (not (soundp sound))
+  (ny:typecheck (not (or (soundp sound) (multichannel-soundp sound)))
+    (ny:error source 1 '((SOUND) "sound") sound t))
+  (ny:typecheck (not (numberp lookahead))
+    (ny:error source 2 '((NUMBER) "lookahead") lookahead))
+  (ny:typecheck (not (numberp risetime))
+    (ny:error source 3 '((NUMBER) "risetime") risetime))
+  (ny:typecheck (not (numberp falltime))
+    (ny:error source 4 '((NUMBER) "falltime") falltime))
+  (ny:typecheck (not (numberp floor))
+    (ny:error source 5 '((NUMBER) "floor") floor))
+  (ny:typecheck (not (numberp threshold))
+    (ny:error source 6 '((NUMBER) "threshold") threshold))
+  (cond ((< lookahead risetime)
+         (format t "WARNING: lookahead (~A) ~A (~A) in ~A ~A ~A.\n"
+                 lookahead "must be greater than risetime" risetime
+                 source "function; setting lookahead to" risetime)
+         (setf lookahead risetime)))
+  (cond ((< risetime 0)
+         (format t "WARNING: risetime (~A) ~A ~A ~A\n" risetime
+                 "must be greater than zero in" source
+                 "function; setting risetime to 0.01.")
+         (setf risetime 0.01)))
+  (cond ((< falltime 0)
+         (format t "WARNING: ~A ~A function; setting falltime to 0.01.\n"
+                 "falltime must be greater than zero in" source)
+         (setf falltime 0.01)))
+  (cond ((< floor 0.00001)
+         (format t "WARNING: ~A ~A function; setting floor to 0.00001.\n"
+                 "floor must be greater than zero in" source)
+         (setf floor 0.00001)))
+  (let (s) ;; s becomes sound after collapsing to one channel
+    (cond ((arrayp sound)           ;; use s-max over all channels so that
+           (setf s (aref sound 0))  ;; ANY channel opens the gate
+           (dotimes (i (1- (length sound)))
+             (setf s (s-max s (aref sound (1+ i))))))
+          (t (setf s sound)))
+    (setf s (snd-gate (seq (cue s)
+                           (stretch-abs 1.0 (s-rest lookahead)))
+                      lookahead risetime falltime floor threshold))
+    ;; snd-gate delays everything by lookahead, so this will slide the sound
+    ;; earlier by lookahead and delete the first lookahead samples
+    (prog1 (snd-xform s (snd-srate s) (snd-t0 s)
+                      (+ (snd-t0 s) lookahead) MAX-STOP-TIME 1.0)
+           ;; This is *really* tricky. Normally, we would return now and
+           ;; the GC would free s and sound which are local variables. The
+           ;; only references to the sounds once stored in s and sound are
+           ;; lazy unit generators that will free samples almost as soon as
+           ;; they are computed, so no samples will accumulate. But wait! The
+           ;; 2nd SEQ expression with S-REST can reference s and sound because
+           ;; (due to macro magic) a closure is constructed to hold them until
+           ;; the 2nd SEQ expression is evaluated. It's almost as though s and
+           ;; sound are back to being global variables. Since the closure does
+           ;; not actually use either s or sound, we can clear them (we are
+           ;; still in the same environment as the closures packed inside SEQ,
+           ;; so s and sound here are still the same variables as the ones in
+           ;; the closure. Note that the other uses of s and sound already made
+           ;; copies of the sounds, and s and sound are merely references to
+           ;; them -- setting to nil will not alter the immutable lazy sound
+           ;; we are returning. Whew!
+           (setf s nil) (setf sound nil))))
+
+
+;; (osc-note step &optional duration env sust volume sound)
+;;   Creates a note using table-lookup osc, but with an envelope.
+;; The ENV parameter may be a parameter list for the env function,
+;; or it may be a sound.
+;;
+(defun osc-note (pitch &optional (duration 1.0) 
+               (env-spec '(0.02 0.1 0.3 1.0 .8 .7))
+               (volume 0.0)
+               (table *TABLE*))
+  (ny:typecheck (not (numberp pitch))
+    (ny:error "OSC-NOTE" 1 '((STEP) "pitch")  pitch))
+  (ny:typecheck (not (numberp duration))
+    (ny:error "OSC-NOTE" 2 '((NUMBER) "duration") duration))
+  (ny:assert-env-spec env-spec
+    "In OSCNOTE, 3rd argument (env-spec) must be a  list of 6 or 7 numbers to pass as arguments to ENV")
+  (ny:typecheck (not (numberp volume))
+    (ny:error "OSC-NOTE" 4 '((NUMBER) "volume") volume))
+  (ny:assert-table "OSC-NOTE" 5 "table" table)
+    
+  (ny:set-logical-stop
+   (mult (loud volume (osc pitch duration table))
+     (if (listp env-spec)
+       (apply 'env env-spec)
+       env-spec))
+   duration))
+
+
+;; force-srate -- resample snd if necessary to get sample rate
+;
+(defun force-srate (sr snd)
+  (ny:typecheck (not (numberp sr))
+    (ny:error "FORCE-SRATE" 1 '((NUMBER) "sr") sr))
+  (ny:typecheck (not (or (soundp snd) (multichannel-soundp snd)))
+    (ny:error "FORCE-SRATE" 2 '((SOUND) "snd") snd t))
+  (cond ((arrayp snd)
+     (let* ((len (length snd))
+        (result (make-array len)))
+       (dotimes (i len)
+            (setf (aref result i) 
+              (force-srate sr (aref snd i))))
+       result))
+    (t
+     (let ((snd-sr (snd-srate snd)))
+       (cond ((> sr snd-sr) (snd-up sr snd))
+         ((< sr snd-sr) (snd-down sr snd))
+         (t snd))))))
+
+
+(defun force-srates (srs snd)
+  (cond ((and (numberp srs) (soundp snd))
+     (force-srate srs snd))
+    ((and (arrayp srs) (arrayp snd))
+     (let* ((len (length snd))
+        (result (make-array len)))
+       (dotimes (i len)
+            (setf (aref result i) 
+              (force-srate (aref srs i) (aref snd i))))
+       result))
+    (t (error (format nil "In force-srates: arguments not compatible. srs is ~A, snd is ~A. Perhaps you are constructing a sequence using both mono and multi-channel sounds."
+               (type-of srs) (type-of snd))))))
+
+
+;; (breakpoints-convert (t1 x1 t2 x2 ... tn) t0)
+;;   converts times to sample numbers and scales amplitudes
+;;   t0 is the global (after warping) start time
+;;
+;; input list is one or more numbers
+;; result is abs-sample-count, val, abs-sample-count, val, ...
+;;     if the list length is odd, the result length is odd, and
+;;     snd-pwl treats it as if a final value of zero was appended
+;; 
+;; NOTE: there were some stack overflow problems with the original
+;; recursive version (in comments now), so it was rewritten as an
+;; iteration.
+;;
+(defun breakpoints-convert (list t0 source)
+  (prog (sample-count result sust (last-count 0))
+    (setf sust (get-sustain))
+    (ny:typecheck (not (consp list))
+      (error (format nil "In ~A, expected a list of numbers" source) list))
+ loop
+    (ny:typecheck (not (numberp (car list)))
+      (error (format nil "In ~A, expected only numbers in breakpoint list, got ~A"
+              source (car list))))
+    (setf sample-count 
+      (truncate (+ 0.5 (* (- (local-to-global (* (car list) sust)) t0)
+                 *control-srate*))))
+    ; now we have a new sample count to put into result list
+    ; make sure result is non-decreasing
+    (cond ((< sample-count last-count)
+       (setf sample-count last-count)))
+    (setf last-count sample-count)
+    (push sample-count result)
+    (cond ((cdr list)
+       (setf list (cdr list))
+       (ny:typecheck (not (numberp (car list)))
+         (error (format nil "In ~A, expected only numbers in breakpoint list" source)
+                (car list)))
+       (push (float (car list)) result)))
+    (setf list (cdr list))
+    (cond (list
+       (go loop)))
+    (return (reverse result))))
+
+ 
+;; (pwl t1 l1 t2 l2 ... tn)
+;;   Creates a piece-wise linear envelope from breakpoint data.
+;;
+(defun pwl (&rest breakpoints) (pwl-list breakpoints "PWL"))
+
+(defun pwlr (&rest breakpoints) (pwlr-list breakpoints "PWLR"))
+
+;; BREAKPOINTS-RELATIVE list source 
+;;  converts list, which has the form (value dur value dur value ...)
+;;  into the form (value time value time value ...)
+;;  the list may have an even or odd length
+;;
+(defun breakpoints-relative (breakpoints source)
+  (prog (result (sum 0.0))
+    (ny:typecheck (not (consp breakpoints))
+      (error (format nil "In ~A, expected list of numbers, got ~A"
+             source breakpoints)))
+ loop
+    (ny:typecheck (not (numberp (car breakpoints)))
+      (error (format nil 
+              "In ~A, expected only numbers in breakpoints list, got ~A"
+              source (car breakpoints))))
+    (setf sum (+ sum (car breakpoints)))
+    (push sum result)
+    (cond ((cdr breakpoints)
+       (setf breakpoints (cdr breakpoints))
+       (ny:typecheck (not (numberp (car breakpoints)))
+         (error (format nil 
+                 "In ~A, expected only numbers in breakpoints list, got ~A"
+                 source (car breakpoints))))
+       (push (car breakpoints) result)))
+    (setf breakpoints (cdr breakpoints))
+    (cond (breakpoints
+       (go loop)))
+    (return (reverse result))))
+
+
+(defun pwlr-list (breakpoints &optional (source "PWLR-LIST"))
+  (pwl-list (breakpoints-relative breakpoints source) source))
+
+(defun pwl-list (breakpoints &optional (source "PWL-LIST"))
+  (let ((t0 (local-to-global 0)))
+    (snd-pwl t0 *control-srate* (breakpoints-convert breakpoints t0 source))))
+
+;; (pwlv l1 t1 l2 t2 ... ln)
+;; Creates a piece-wise linear envelope from breakpoint data;
+;; the function initial and final values are explicit
+;;
+(defun pwlv (&rest breakpoints)
+  ;use pwl, modify breakpoints with initial and final changes
+  ;need to put initial time of 0, and final time of 0
+  (pwlv-list breakpoints "PWLV"))
+
+(defun pwlv-list (breakpoints &optional (source "PWLV-LIST"))
+  (ny:typecheck (not (consp breakpoints))
+    (error (format nil "In ~A, expected list of numbers, got ~A"
+           source breakpoints)))
+  (pwl-list (cons 0.0 breakpoints) source))
+
+(defun pwlvr (&rest breakpoints) (pwlvr-list breakpoints "PWLVR"))
+
+(defun pwlvr-list (breakpoints &optional (source "PWLVR-LIST"))
+  (ny:typecheck (not (consp breakpoints))
+     (error (format nil "In ~A, expected list of numbers, got ~A"
+            source breakpoints)))
+  (pwlr-list (cons 0.0 breakpoints) source))
+
+(defun pwe (&rest breakpoints)
+  (pwe-list breakpoints "PWE"))
+
+(defun pwe-list (breakpoints &optional (source "PWE-LIST"))
+  (ny:typecheck (not (consp breakpoints))
+     (error (format nil "In ~A, expected list of numbers, got ~A"
+            source breakpoints)))
+  (pwev-list (cons 1.0 breakpoints) source))
+
+(defun pwer (&rest breakpoints)
+  (pwer-list breakpoints "PWER"))
+
+(defun pwer-list (breakpoints &optional (source "PWER-LIST"))
+  (pwe-list (breakpoints-relative breakpoints source) source))
+
+(defun pwev (&rest breakpoints)
+  (pwev-list breakpoints "PWEV"))
+
+(defun pwev-list (breakpoints &optional (source "PWEV-LIST"))
+  (let ((lis (breakpoints-log breakpoints source)))
+    (s-exp (pwl-list lis))))
+
+(defun pwevr (&rest breakpoints) (pwevr-list breakpoints "PWEVR"))
+
+(defun pwevr-list (breakpoints &optional (source "PWEVR-LIST"))
+  (ny:typecheck (not (consp breakpoints))
+     (error (format nil "In ~A, expected list of numbers, got ~A"
+            source breakpoints)))
+  (pwev-list (cdr (breakpoints-relative (cons 0.0 breakpoints) source)) source))
+
+
+;; input is 2 or more numbers representing val, time, val, time, ...
+;; output is odd number of 1 or more numbers representing
+;;     time, val, time, val, ..., time
+;; 
+;;
+(defun breakpoints-log (breakpoints source)
+  (prog ((result '(0.0)) val tim)
+loop
+    (ny:typecheck (not (consp breakpoints))
+      (error (format nil "In ~A, expected list of numbers, got ~A"
+                         source breakpoints)))
+    (ny:typecheck (not (numberp (car breakpoints)))
+      (error (format nil "In ~A, expected number in breakpoint list, got ~A"
+                         source (car breakpoints))))
+
+    (setf val (float (car breakpoints)))
+    (setf breakpoints (cdr breakpoints))
+
+    (cond (breakpoints
+       (ny:typecheck (not (consp breakpoints))
+         (error (format nil "In ~A, expected list of numbers, got ~A"
+                source (car breakpoints))))
+       (setf tim (car breakpoints))
+       (setf breakpoints (cdr breakpoints))
+       (ny:typecheck (not (numberp tim))
+         (error (format nil "In ~A, expected number in breakpoint list, got ~A"
+                source tim)))))
+
+    (setf result (cons tim (cons (log val) result)))
+    (cond ((null breakpoints)
+           (return (reverse result))))
+    (go loop)))
+
+
+;; SOUND-WARP -- apply warp function to a sound
+;; 
+(defun sound-warp (warp-fn signal &optional wrate)
+  (ny:typecheck (not (soundp warp-fn))
+    (ny:error "SOUND-WARP" 1 '((SOUND) "warp-fn") warp-fn))
+  (ny:typecheck (not (soundp signal))
+    (ny:error "SOUND-WARP" 2 '((SOUND) "signal") signal))
+  (cond (wrate
+     (ny:typecheck (not (numberp wrate))
+       (ny:error "SOUND-WARP" 3 '((NUMBER) "wrate") wrate))
+     (snd-resamplev signal *sound-srate*
+            (snd-inverse warp-fn (local-to-global 0) wrate)))
+    (t
+     (snd-compose signal 
+              (snd-inverse warp-fn (local-to-global 0) *sound-srate*)))))
+
+(defun snd-extent (sound maxsamples) 
+    (ny:typecheck (not (soundp sound))
+      (ny:error "SND-EXTENT" 1 '((SOUND) "sound") sound))
+    (ny:typecheck (not (integerp maxsamples))
+      (ny:error "SND-EXTENT" 2 '((INTEGER) "maxsamples") maxsamples))
+    (list (snd-t0 sound)
+      (+ (snd-t0 sound) (/ (snd-length sound maxsamples)
+                   (snd-srate sound)))))
+
+(setfn snd-flatten snd-length)
+
+;; (maketable sound)
+;;   Creates a table for osc, lfo, etc. by assuming that the samples
+;;   in sound represent one period.  The sound must start at time 0.
+
+(defun maketable (sound)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "MAKETABLE" 0 '((SOUND) nil) sound))
+  (list sound
+    (hz-to-step 
+     (/ 1.0
+        (cadr (snd-extent sound 1000000))))
+    T))
+
+
+; simple stereo pan: as where goes from 0 to 1, sound
+; is linearly panned from left to right
+;
+(defun pan (sound where)
+  (ny:typecheck (not (soundp sound))
+    (ny:error "PAN" 1 '((SOUND) "sound") sound))
+  (ny:typecheck (not (or (soundp where) (numberp where)))
+    (ny:error "PAN" 2 '((NUMBER SOUND) "where")  where))
+  (vector (mult sound (sum 1 (mult -1 where)))
+          (mult sound where)))
+
+
+(setf prod-source "PROD (or * in SAL)")
+
+(defun prod (&rest snds)
+  (cond ((null snds)
+     (snd-zero (local-to-global 0) *sound-srate*))
+    ((null (cdr snds))
+     (car snds))
+    ((null (cddr snds))
+     (nyq:prod2 (car snds) (cadr snds) prod-source))
+    (t
+     (nyq:prod2 (car snds) (apply #'prod (cdr snds)) prod-source))))
+
+(setfn mult prod)
+
+
+;; (NYQ:PROD-OF-ARRAYS S1 S2) - form pairwise products
+;
+(defun nyq:prod-of-arrays (s1 s2 source)
+  (let* ((n (length s1))
+     (p (make-array n)))
+    (ny:typecheck (/= n (length s2))
+       (error (strcat "In " source ", unequal number of channels, got "
+               (param-to-string s1) " and " (param-to-string s2))))
+    (dotimes (i n)
+      (setf (aref p i) (nyq:prod2 (aref s1 i) (aref s2 i) source)))
+    p))
+
+
+; nyq:prod2 - multiply two arguments
+; 
+(defun nyq:prod2 (s1 s2 source)
+  (setf s1 (nyq:coerce-to s1 s2))
+  (setf s2 (nyq:coerce-to s2 s1))
+  (cond ((arrayp s1)
+     (nyq:prod-of-arrays s1 s2 source))
+    (t
+     (nyq:prod-2-sounds s1 s2 source))))
+
+
+; (PROD-2-SOUNDS S1 S2) - multiply two sound arguments
+; 
+(defun nyq:prod-2-sounds (s1 s2 source)
+  (cond ((numberp s1)
+         (cond ((numberp s2)
+                (* s1 s2))
+               ((soundp s2)
+                (snd-scale s1 s2))
+               (t
+                (ny:error source 0 number-sound-anon s2 t))))
+        ((numberp s2)
+         (ny:typecheck (not (soundp s1))
+           (ny:error source 0 number-sound-anon s1 t))
+         (snd-scale s2 s1))
+        ((and (soundp s1) (soundp s2))
+         (snd-prod s1 s2))
+        ((soundp s1)
+         (ny:error source 0 number-sound-anon s2 t))
+        (t
+         (ny:error source 0 number-sound-anon s1 t))))
+
+
+;; RAMP -- linear ramp from 0 to x
+;;
+(defun ramp (&optional (x 1))
+  (ny:typecheck (not (numberp x))
+    (ny:error "RAMP" 0 number-anon x))
+  (let* ((duration (get-duration x)))
+    (ny:set-logical-stop
+      (warp-abs nil
+        (at *rslt*
+          (sustain-abs 1
+                       (pwl duration 1 (+ duration (/ *control-srate*))))))
+      x)))
+
+
+(defun resample (snd rate)
+  (ny:typecheck (not (or (soundp snd) (multichannel-soundp snd)))
+    (ny:error "RESAMPLE" 1 '((SOUND) nil) snd t))
+  (ny:typecheck (not (numberp rate))
+    (ny:error "RESAMPLE" 2 '((NUMBER) "rate") rate))
+  (cond ((arrayp snd)
+         (let* ((len (length snd))
+                (result (make-array len)))
+           (dotimes (i len)
+             (setf (aref result i)
+                   (snd-resample (aref snd i) rate)))
+           result))
+        (t
+         (snd-resample snd rate))))
+
+
+(defun scale (amt snd)
+  (multichan-expand "SCALE" #'snd-scale
+    '(((NUMBER) "amt") ((SOUND) "snd")) amt snd))
+
+
+(setfn s-print-tree snd-print-tree)
+
+
+;; (PEAK sound-expression number-of-samples) - find peak amplitude
+;
+; NOTE: this used to be called s-max
+; It is tempting to try using multichan-expand here to get peaks
+; from multichannel sounds, but at this point the argument is just
+; an expression, so we cannot tell if it is multichannel. We could
+; evaluate the expression, but then we'd have a local binding and
+; would retain samples in memory if we called snd-max on each channel.
+;
+(defmacro peak (expression maxlen)
+   `(snd-max ',expression ,maxlen))
+    
+
+;; (S-MAX S1 S2) - return maximum of S1, S2
+;
+(defun s-max (s1 s2)
+  (setf s1 (nyq:coerce-to s1 s2))
+  (setf s2 (nyq:coerce-to s2 s1))
+  (cond ((arrayp s1)
+         (nyq:max-of-arrays s1 s2))
+        (t
+         (nyq:max-2-sounds s1 s2))))
+
+(defun nyq:max-of-arrays (s1 s2)
+  (let* ((n (length s1))
+         (p (make-array n)))
+    (ny:typecheck (/= n (length s2))
+       (error (strcat "In S-MAX, unequal number of channels, got "
+                      (param-to-string s1) " and " (param-to-string s2))))
+    (dotimes (i n)
+      (setf (aref p i) (s-max (aref s1 i) (aref s2 i))))
+    p))
+
+(defun nyq:max-2-sounds (s1 s2)
+  (cond ((numberp s1)
+         (cond ((numberp s2)
+                (max s1 s2))
+               ((soundp s2)
+                (snd-maxv s2
+                          (snd-const s1 (local-to-global 0.0)
+                                     (snd-srate s2) (get-duration 1.0))))
+               (t
+                (ny:error "S-MAX" 2 number-sound-anon s2 t))))
+        ((numberp s2)
+         (ny:typecheck (not (soundp s1))
+           (ny:error "S-MAX" 2 number-sound-anon s2 t))
+         (snd-maxv s1 (snd-const s2 (local-to-global 0.0)
+                       (snd-srate s1) (get-duration 1.0))))
+        ((and (soundp s1) (soundp s2))
+         (snd-maxv s1 s2))
+        ((soundp s1)
+         (ny:error "S-MAX" 2 number-sound-anon s2 t))
+        (t
+         (ny:error "S-MAX" 1 number-sound-anon s1 t))))
+
+
+(defun s-min (s1 s2)
+  (setf s1 (nyq:coerce-to s1 s2))
+  (setf s2 (nyq:coerce-to s2 s1))
+  (cond ((arrayp s1)
+         (nyq:min-of-arrays s1 s2))
+        (t
+         (nyq:min-2-sounds s1 s2))))
+
+(defun nyq:min-of-arrays (s1 s2)
+  (let* ((n (length s1))
+         (p (make-array n)))
+    (ny:typecheck (/= n (length s2))
+       (error (strcat "In S-MIN, unequal number of channels, got "
+                      (param-to-string s1) (param-to-string s2))))
+    (cond ((/= n (length s2))
+       (error "unequal number of channels in max")))
+    (dotimes (i n)
+      (setf (aref p i) (s-min (aref s1 i) (aref s2 i))))
+    p))
+
+(defun nyq:min-2-sounds (s1 s2)
+  (cond ((numberp s1)
+         (cond ((numberp s2)
+                (min s1 s2))
+               ((soundp s2)
+                (snd-minv s2
+                          (snd-const s1 (local-to-global 0.0)
+                                     (snd-srate s2) (get-duration 1.0))))
+               (t
+                (ny:error "S-MIN" 2 number-sound-anon s2 t))))
+        ((numberp s2)
+         (ny:typecheck (not (soundp s1))
+           (ny:error "S-MIN" 2 number-sound-anon s2 t))
+         (snd-minv s1 (snd-const s2 (local-to-global 0.0)
+                   (snd-srate s1) (get-duration 1.0))))
+        ((and (soundp s1) (soundp s2))
+         (snd-minv s1 s2))
+        ((soundp s1)
+         (ny:error "S-MIN" 2 number-sound-anon s2 t))
+        (t
+         (ny:error "S-MIN" 1 number-sound-anon s1 t))))
+
+
+(defun snd-minv (s1 s2)
+  (snd-scale -1.0 (snd-maxv (snd-scale -1.0 s1) (snd-scale -1.0 s2))))
+
+; sequence macros SEQ and SEQREP are now in seq.lsp:
+; 
+(load "seq" :verbose NIL)
+
+
+; set-logical-stop - modify the sound and return it, time is shifted and
+;			 stretched
+(defun set-logical-stop (snd tim)
+  (ny:typecheck (not (numberp tim))
+    (ny:error "SET-LOGICAL-STOP" 2 '((NUMBER) "logical stop time") tim))
+  (ny:typecheck (not (or (soundp snd) (multichannel-soundp snd)))
+    (ny:error "SET-LOGICAL-STOP" 1 '((SOUND) "snd") snd t))
+  (multichan-expand "SET-LOGICAL-STOP" #'ny:set-logical-stop 
+    '(((SOUND) "snd") ((NUMBER) "logical stop time")) snd tim))
+
+
+;; NY:SET-LOGICAL-STOP - "fast" set-logical-stop: no typechecks and no
+;;                       multichannel expansion
+(defun ny:set-logical-stop (snd tim)
+  (let ((d (local-to-global tim)))
+    (snd-set-logical-stop snd d)
+    snd))
+  
+
+; SET-LOGICAL-STOP-ABS - modify the sound and return it
+; 
+(defun set-logical-stop-abs (snd tim)
+  (ny:typecheck (not (numberp tim))
+    (ny:error "SET-LOGICAL-STOP-ABS" 2 '((NUMBER) "logical stop time") tim))
+  (ny:typecheck (not (or (soundp snd) (multichannel-soundp snd)))
+    (ny:error "SET-LOGICAL-STOP-ABS" 1 '((SOUND) "snd") snd t))
+  (multichan-expand "SET-LOGICAL-STOP-ABS" #'ny:set-logical-stop-abs 
+    '(((SOUND) "snd") ((NUMBER) "logical stop time")) snd tim))
+
+
+(defun ny:set-logical-stop-abs (snd tim)
+  (snd-set-logical-stop snd tim)
+  snd)
+  
+
+(defmacro simrep (pair sound)
+  `(let (_snds)
+     (dotimes ,pair (push ,sound _snds))
+       (sim-list _snds "SIMREP")))
+
+(defun sim (&rest snds)
+  (sim-list snds "SIM or SUM (or + in SAL)"))
+
+(setfn sum sim)
+
+(defun sim-list (snds source)
+ (let (a b)
+  (cond ((null snds)
+         (snd-zero (local-to-global 0) *sound-srate*))
+        ((null (cdr snds))
+         (setf a (car snds))
+         (ny:typecheck (not (or (numberp a) (soundp a) (multichannel-soundp a)))
+           (ny:error source 0 number-sound-anon a t))
+         a)
+        ((null (cddr snds))
+         ;; sal-plus does typechecking, then calls nyq:add2
+         (sal-plus (car snds) (cadr snds)))
+        (t
+         (setf a (car snds))
+         (ny:typecheck (not (or (numberp a) (soundp a) (multichannel-soundp a)))
+           (ny:error source 0 number-sound-anon a t))
+         (nyq:add2 a (sim-list (cdr snds) source))))))
+
+
+(defun s-rest (&optional (dur 1.0) (chans 1))
+  (ny:typecheck (not (numberp dur))
+    (ny:error "S-REST" 1 '((NUMBER) "dur") dur))
+  (ny:typecheck (not (integerp chans))
+    (ny:error "S-REST" 2 '((INTEGER) "chans")  chans))
+  (let ((d (get-duration dur))
+        r)
+    (cond ((= chans 1)
+           (snd-const 0.0 *rslt* *SOUND-SRATE* d))
+          (t
+           (setf r (make-array chans))
+           (dotimes (i chans)
+             (setf (aref r i) (snd-const 0.0 *rslt* *SOUND-SRATE* d)))
+           r))))
+
+
+(defun tempo (warpfn)
+  (ny:typecheck (not (soundp warpfn))
+    (ny:error "TEMPO" 0 '((SOUND) "warpfn") warpfn))
+  (slope (snd-inverse warpfn (local-to-global 0) *control-srate*)))
+
+
+;; (SUM-OF-ARRAYS S1 S2) - add multichannel sounds
+; 
+; assumes s1 & s2 are arrays of numbers and sounds
+;
+; result has as many channels the largest of s1, s2
+; corresponding channels are added, extras are copied
+; 
+(defun sum-of-arrays (s1 s2)
+;  (ny:typecheck (not (multichannel-soundp s1))
+;    (error (strcat "In SUM or SIM (or + in SAL), at least one channel in the array contains a non-sound, got " (param-to-string s1))))
+;  (ny:typecheck (not (multichannel-soundp s2))
+;    (error (strcat "In SUM or SIM (or + in SAL), at least one channel in the array contains a non-sound, got " (param-to-string s2))))
+  (let* ((n1 (length s1))
+     (n2 (length s2))
+     (n (min n1 n2))
+     (m (max n1 n2))
+     (result (make-array m))
+     (big-s (if (> n1 n2) s1 s2))
+     v1 v2)
+    
+    (dotimes (i n)
+      (setf v1 (aref s1 i) v2 (aref s2 i))
+      (setf (aref result i) 
+        (cond ((numberp v1)
+               (if (numberp v2) (+ v1 v2) (snd-offset v2 v1)))
+              ((numberp v2)
+               (if (numberp v1) (+ v1 v2) (snd-offset v1 v2)))
+              (t
+               (nyq:add-2-sounds v1 v2)))))
+    (dotimes (i (- m n))
+      (setf (aref result (+ n i)) (aref big-s (+ n i))))
+    result))
+
+
+;; (WARP fn behavior) - warp behavior according to fn
+;;
+;; fn is a map from behavior time to local time, and *WARP* expresses
+;; a map from local to global time.
+;; To produce a new *WARP* for the environment, we want to compose the
+;; effect of the current *WARP* with fn.  Note that fn is also a behavior.
+;; It is evaluated in the current environment first, then it is used to
+;; modify the environment seen by behavior.
+;; *WARP* is a triple: (d s f) denoting the function f(st+d).
+;; Letting g represent the new warp function fn, we want f(st+d) o g, or
+;; f(s*g(t) + d) in the form (d' s' f').
+;; Let's do this one step at a time:
+;; f(s*g(t) + d) = f(scale(s, g) + d)
+;;               = (shift f -d)(scale(s, g))
+;;               = (snd-compose (shift-time f (- d)) (scale s g))
+;;
+;; If f in NIL, it denotes the identity mapping f(t)=t, so we can
+;; simplify:
+;; f(scale(s, g) + d) = scale(s, g) + d
+;;                    = (snd-offset (scale s g) d)
+
+(defmacro warp (x s)
+ `(progv '(*WARP*)
+     (let ((wp ,x))
+       (list (list 0.0 1.0
+              (cond ((warp-function *WARP*)
+                     (ny:typecheck (not (soundp wp))
+                       (ny:error "WARP" 1 '((SOUND) "warp function") wp))
+                     (snd-compose (shift-time (warp-function *WARP*) 
+                                              (- (warp-time *WARP*)))
+                                  (snd-scale (warp-stretch *WARP*) wp)))
+                    (t
+                     (ny:typecheck (not (soundp wp))
+                       (ny:error "WARP" 1 '((SOUND) "warp function") wp))
+                     (snd-offset (snd-scale (warp-stretch *WARP*) wp)
+                                 (warp-time *WARP*)))))))
+     ,s))
+
+
+(defmacro warp-abs (x s)
+ `(progv '(*WARP*)
+     (let ((wp ,x))
+       (ny:typecheck (and wp (not (soundp wp)))
+         (ny:error "WARP-ABS" 1 '((NULL SOUND) NIL) wp))
+       (list (list 0.0 1.0 wp)))
+     ,s))
+
+
+;; MULTICHAN-EXPAND -- construct and return array according to args
+;;
+;; arrays are used in Nyquist to represent multiple channels
+;; if any argument is an array, make sure all array arguments
+;; have the same length.  Then, construct a multichannel result
+;; by calling fn once for each channel.  The arguments passed to
+;; fn for the i'th channel are either the i'th element of an array
+;; argument, or just a copy of a non-array argument.
+;;
+;; types should be a list of type info for each arg, where type info is:
+;;   (arg1-info arg2-info ...), where each arg-info is
+;;   (valid-type-list name-or-nil), where valid-type-list is a list 
+;;      of valid types from among NUMBER, SOUND, POSITIVE (number > 0),
+;;      NONNEGATIVE (number >= 0), INTEGER, STEP, STRING,
+;;      POSITIVE-OR_NULL (a positive number or nil),
+;;      INT-OR-NULL (integer or nil), or NULL (the value can be nil).
+;;      It is implied that arrays of these are valid too.  name-or-nil 
+;;      is the parameter name as a string if the parameter name should 
+;;      be printed, or NIL if the parameter name should not be printed.
+;;      There can be at most 2 elements in valid-type-list, and if 
+;;      there are 2 elements, the 2nd one must be SOUND. For example, 
+;;      arg-info '((NUMBER SOUND) "cutoff") might generate the error
+;;          In LOPASS8, 2nd argument (cutoff) must be a number, sound
+;;          or array thereof, got "bad-value"
+;;
+;; Many existing Nyquist plug-ins require the old version of multichan-expand,
+;; so in Audacity we need to support both the old and new versions.
+(defun multichan-expand (&rest args)
+  (if (stringp (first args))
+      (apply 'multichan-expand-new args)
+      (apply 'multichan-expand-old args)))
+
+;; Legacy version:
+(defun multichan-expand-old (fn &rest args)
+  (let (len newlen result) ; len is a flag as well as a count
+    (dolist (a args)
+        (cond ((arrayp a)
+           (setf newlen (length a))
+           (cond ((and len (/= len newlen))
+              (error (format nil "In ~A, two arguments are vectors of differing length." fn))))
+           (setf len newlen))))
+    (cond (len
+       (setf result (make-array len))
+       ; for each channel, call fn with args
+       (dotimes (i len)
+           (setf (aref result i)
+             (apply fn
+            (mapcar
+                #'(lambda (a)
+                ; take i'th entry or replicate:
+                (cond ((arrayp a) (aref a i))
+                      (t a)))
+                args))))
+       result)
+      (t
+       (apply fn args)))))
+
+;; The new (Nyquist 3.15) version:
+(defun multichan-expand-new (src fn types &rest args)
+  (let (chan len newlen result prev typ (index 0) nonsnd)
+    ; len is a flag as well as a count
+    (dolist (a args)
+      (setf typ (car types) types (cdr types))
+      ;; we only report argument position when there is more than one.
+      ;; index tracks argument position, where 0 means no position to report
+      (if (> (length args) 1) (setf index (1+ index)))
+      (setf nonsnd (caar typ)) ;; if non-sound type allowed, what is it?
+      ;; compute the length of any array argument, and typecheck all of them
+      (cond ((arrayp a)
+             (setf newlen (length a))
+             (ny:typecheck (and len (/= len newlen))
+               (error (strcat "In " src
+                 ", two arguments are multichannels of differing length, got "
+                 (param-to-string prev) ", and " (param-to-string a))))
+             (dotimes (i newlen)
+               (setf chan (aref a i))
+               (cond ((and (eq nonsnd 'NUMBER) (numberp chan)))
+                     ((and (member 'SOUND (car typ)) (soundp chan)))
+                     ((and (eq nonsnd 'STEP) (numberp chan)))
+                     ((and (eq nonsnd 'POSITIVE) (numberp chan) (> chan 0)))
+                     ((and (eq nonsnd 'POSITIVE-OR-NULL)
+                           (or (and (numberp chan) (> chan 0)) (null chan))))
+                     ((and (eq nonsnd 'NONNEGATIVE) (numberp chan) (>= chan 0)))
+                     ((and (eq nonsnd 'INTEGER) (integerp chan)))
+                     ((and (eq nonsnd 'STRING) (stringp chan)))
+                     ((and (eq nonsnd 'NULL) (null chan)))
+                     ((and (eq nonsnd 'INT-OR-NULL)
+                           (or (integerp chan) (null chan))))
+                     (t (ny:error src index typ a t))))
+             (setf prev a)
+             (setf len newlen))
+            ((and (eq nonsnd 'NUMBER) (numberp a)))
+            ((and (member 'SOUND (car typ)) (soundp a)))
+            ((and (eq nonsnd 'STEP) (numberp a)))
+            ((and (eq nonsnd 'POSITIVE) (numberp a) (>= a 0)))
+            ((and (eq nonsnd 'POSITIVE-OR-NULL)
+                  (or (and (numberp a) (> a 0)) (null a))))
+            ((and (eq nonsnd 'NONNEGATIVE) (numberp a) (>= a 0)))
+            ((and (eq nonsnd 'INTEGER) (integerp a)))
+            ((and (eq nonsnd 'STRING) (stringp a)))
+            ((and (eq nonsnd 'NULL) (null a)))
+            ((and (eq nonsnd 'INT-OR-NULL)
+                  (or (integerp a) (null a))))
+            (t
+             (ny:error src index typ a t))))
+    (cond (len
+           (setf result (make-array len))
+           ; for each channel, call fn with args
+           (dotimes (i len)
+             (setf (aref result i)
+                   (apply fn
+                     (mapcar
+                       #'(lambda (a) ; take i'th entry or replicate:
+                           (cond ((arrayp a) (aref a i))
+                                 (t a)))
+                       args))))
+           result)
+          (t
+           (apply fn args)))))
+
+
+;; SELECT-IMPLEMENTATION-? -- apply an implementation according to args
+;;
+;; There is a different Nyquist primitive for each combination of 
+;; constant (NUMBERP) and time-variable (SOUNDP) arguments.  E.g.
+;; a filter with fixed parameters differs from one with varying
+;; parameters.  In most cases, the user just calls one function,
+;; and the arguments are decoded here:
+
+
+;; SELECT-IMPLEMENTATION-1-1 -- 1 sound arg, 1 selector
+;;
+(defun select-implementation-1-1 (source fns snd sel1 &rest others)
+  (ny:typecheck (not (soundp snd))
+    (ny:error source 1 '((SOUND) nil) snd t))
+  (cond ((numberp sel1)
+         (apply (aref fns 0) (cons snd (cons sel1 others))))
+        ((soundp sel1)
+         (apply (aref fns 1) (cons snd (cons sel1 others))))
+        (t
+         (ny:error source 2 number-sound-anon sel1 t))))
+
+
+;; SELECT-IMPLEMENTATION-1-2 -- 1 sound arg, 2 selectors
+;;
+;; choose implementation according to args 2 and 3. In this implementation,
+;; since we have two arguments to test for types, we return from prog
+;; if we find good types. That way, we can fall through the decision tree
+;; and all paths lead to one call to ERROR if good types are not found.
+;;
+(defun select-implementation-1-2 (source fns snd sel1 sel2 &rest others)
+  (prog ()
+    (ny:typecheck (not (soundp snd))
+      (ny:error source 1 '((SOUND) nil) snd t))
+    (cond ((numberp sel2)
+           (cond ((numberp sel1)
+                  (return (apply (aref fns 0)
+                                 (cons snd (cons sel1 (cons sel2 others))))))
+                 ((soundp sel1)
+                  (return (apply (aref fns 1)
+                                 (cons snd (cons sel1 (cons sel2 others))))))))
+          ((soundp sel2)
+           (cond ((numberp sel1)
+                  (return (apply (aref fns 2)
+                          (cons snd (cons sel1 (cons sel2 others))))))
+                 ((soundp sel1)
+                  (return (apply (aref fns 3)
+                          (cons snd (cons sel1 (cons sel2 others)))))))))
+    (ny:typecheck (not (or (numberp sel1) (soundp sel1)))
+      (ny:error src 2 number-sound-anon sel1 t)
+      (ny:error src 3 number-sound-anon sel2 t))))
+
+
+;; some waveforms
+
+(setf *saw-table* (pwlvr -1 1 1))		; eh, creepy way to get 2205 samples.
+(setf *saw-table* (list *saw-table* (hz-to-step 1) T))
+
+(setf *tri-table* (pwlvr -1 0.5 1 0.5 -1))
+(setf *tri-table* (list *tri-table* (hz-to-step 1) T))
+
+(setf *id-shape*  (pwlvr -1 2 1 .01 1))	            ; identity
+
+(setf *step-shape* (seq (const -1) (const 1 1.01)))  ; hard step at zero
+
+(defun exp-dec (hold halfdec length)
+  (ny:typecheck (not (numberp hold))
+    (ny:error "EXP-DEC" 1 '((NUMBER) "hold") hold))
+  (ny:typecheck (not (numberp halfdec))
+    (ny:error "EXP-DEC" 2 '((NUMBER) "halfdec") halfdec))
+  (ny:typecheck (not (numberp length))
+    (ny:error "EXP-DEC" 3 '((NUMBER) "length") length))
+  (let* ((target (expt 0.5 (/ length halfdec)))
+     (expenv (pwev 1 hold 1 length target)))
+    expenv)
+)
+
+;;; operations on sounds
+
+(defun diff (x &rest y) (diff-list x y "DIFF (or - in SAL)"))
+
+(defun diff-list (x y source)
+  (cond ((and (numberp x) (numberp (car y)) (null (cdr y)))
+         (- x (car y))) ;; this is a fast path for the common case
+        (y (sal-plus x (nyq:prod2 -1 (car y) source) source))
+        (t (nyq:prod2 -1 x source))))
+
+
+; compare-shape is a shape table -- origin 1.
+(defun compare (x y &optional (compare-shape *step-shape*))
+  (ny:typecheck (not (or (soundp x) (soundp y)))
+    (error "In COMPARE, either first or second argument must be a sound"))
+  (ny:typecheck (not (soundp compare-shape))
+    (ny:error "COMPARE" 3 '((SOUND) "compare-shape") compare-shape))
+  (ny:typecheck (not (or (soundp x) (numberp x)))
+    (ny:error "COMPARE" 1 '((SOUND NUMBER) nil) x))
+  (ny:typecheck (not (or (soundp y) (numberp y)))
+    (ny:error "COMPARE" 2 '((SOUND NUMBER) nil) y))
+  (let ((xydiff (diff-list x (list y) "COMPARE")))
+    (shape xydiff compare-shape 1)))
+
+;;; oscs
+
+(defun osc-saw (hz) (hzosc hz *saw-table*))
+(defun osc-tri (hz) (hzosc hz *tri-table*))
+
+; bias is [-1, 1] pulse width.  sound or scalar.
+; hz is a sound or scalar
+(defun osc-pulse (hz bias &optional (compare-shape *step-shape*))
+  (compare bias (osc-tri hz) compare-shape))
+  
+;;; tapped delays
+
+;(tapv snd offset vardelay maxdelay)
+(defun tapv (snd offset vardelay maxdelay)
+  (multichan-expand "TAPV" #'snd-tapv
+    '(((SOUND) "snd") ((NUMBER) "offset") 
+      ((SOUND) "vardelay") ((NUMBER) "maxdelay")) 
+    snd offset vardelay maxdelay))
+
+(defun tapf (snd offset vardelay maxdelay)
+  (multichan-expand "TAPF" #'snd-tapf
+    '(((SOUND) "snd") ((NUMBER) "offset") 
+      ((SOUND) "vardelay") ((NUMBER) "maxdelay")) 
+    snd offset vardelay maxdelay))
+
+
+;; autoload functions -- SELF-MODIFYING CODE!
+;; generate functions that replace themselves by loading more files
+;; and then re-calling themselves as if they were already loaded
+;;
+(defun autoload (filename &rest fns)
+  ;; filename is the file to load (a string) from the current path
+  ;; fns are symbols to be defined as function that will load filename
+  ;;     the first time any one is called, and it is assumed that
+  ;;     filename will define each function in fns, so the called
+  ;;     function can be called again to execute the real implementation
+  (let ((cp (current-path)))
+    (cond ((string-equal cp "./") ;; this is the typical case
+           (setf cp (setdir "."))))
+    ;; make sure cp ends in file separator
+    (cond ((not (equal (char cp (1- (length cp))) *file-separator*))
+           (setf cp (strcat cp (string *file-separator*)))))
+    (setf cp (strcat cp filename))
+    (dolist (fn fns)
+      (eval `(defun ,fn (&rest args)
+               (autoload-helper ,cp ',fn args))))))
+
+
+(defun autoload-helper (path fn args)
+  (if (abs-env (sal-load path))
+      (apply fn args)
+      (error (strcat "Could not load " path))))
+
+
+(autoload "spec-plot.lsp" 'spec-plot)
+
+(autoload "spectral-analysis.lsp" 'sa-init)
+
diff --git a/Release/nyquist/printrec.lsp b/Release/nyquist/printrec.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..4ca17bbc9d1ccce28c0cf97b13f93ad4b8e719ef
--- /dev/null
+++ b/Release/nyquist/printrec.lsp
@@ -0,0 +1,30 @@
+; prints recursive list structure
+
+;(let (seen-list)
+(setf seenlist nil)
+  (defun seenp (l) (member l seenlist :test 'eq))
+  (defun make-seen (l) (setf seenlist (cons l seenlist)))
+  (defun printrec (l) (printrec-any l) (setf seenlist nil))
+  (defun printrec-any (l)
+    (cond ((atom l) (prin1 l) (princ " "))
+          ((seenp l) (princ "<...> "))
+          (t
+           (make-seen l)
+           (princ "(")
+           (printrec-list l)
+           (princ ") ")))
+     nil)
+  (defun printrec-list (l)
+    (printrec-any (car l))
+    (cond ((cdr l)
+           (cond ((seenp (cdr l))
+                  (princ "<...> "))
+                 ((atom (cdr l))
+                  (princ ". ")
+                  (prin1 (cdr l))
+                  (princ " "))
+                 (t
+                  (make-seen (cdr l))
+                  (printrec-list (cdr l))))))
+    nil)
+; )
diff --git a/Release/nyquist/profile.lsp b/Release/nyquist/profile.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..0f7038b61db28abede72980faf2da4b45292b1da
--- /dev/null
+++ b/Release/nyquist/profile.lsp
@@ -0,0 +1,27 @@
+
+; profile.lsp -- support for profiling
+
+;## show-profile -- print profile data
+(defun show-profile ()
+  (let ((profile-flag (profile nil)) (total 0))
+    (dolist (name *PROFILE*)
+            (setq total (+ total (get name '*PROFILE*))))
+    (dolist (name *PROFILE*)
+            (format t "~A (~A%): ~A~%"
+                    (get name '*PROFILE*)
+                    (truncate
+                     (+ 0.5 (/ (float (* 100 (get name '*PROFILE*)))
+                               total)))
+                    name))
+    (format t "Total: ~A~%" total)
+    (profile profile-flag)))
+
+
+;## start-profile -- clear old profile data and start profiling
+(defun start-profile ()
+  (profile nil)
+  (dolist (name *PROFILE*)
+          (remprop name '*PROFILE*))
+  (setq *PROFILE* nil)
+  (profile t))
+
diff --git a/Release/nyquist/rawwaves/mand1.raw b/Release/nyquist/rawwaves/mand1.raw
new file mode 100644
index 0000000000000000000000000000000000000000..bc04a0583599515565866453df6016d6d1c27694
Binary files /dev/null and b/Release/nyquist/rawwaves/mand1.raw differ
diff --git a/Release/nyquist/rawwaves/mand10.raw b/Release/nyquist/rawwaves/mand10.raw
new file mode 100644
index 0000000000000000000000000000000000000000..4b35376aeacdfc50cbef89ea76259403ac542b69
Binary files /dev/null and b/Release/nyquist/rawwaves/mand10.raw differ
diff --git a/Release/nyquist/rawwaves/mand11.raw b/Release/nyquist/rawwaves/mand11.raw
new file mode 100644
index 0000000000000000000000000000000000000000..94889be6f0d6dfeaa63449e06a39df1356508dac
Binary files /dev/null and b/Release/nyquist/rawwaves/mand11.raw differ
diff --git a/Release/nyquist/rawwaves/mand12.raw b/Release/nyquist/rawwaves/mand12.raw
new file mode 100644
index 0000000000000000000000000000000000000000..a128642bf6de4fdac74a1fa03bf32bc6003bb685
Binary files /dev/null and b/Release/nyquist/rawwaves/mand12.raw differ
diff --git a/Release/nyquist/rawwaves/mand2.raw b/Release/nyquist/rawwaves/mand2.raw
new file mode 100644
index 0000000000000000000000000000000000000000..62080081d289b7dfa042b787190b99b0399fe81e
Binary files /dev/null and b/Release/nyquist/rawwaves/mand2.raw differ
diff --git a/Release/nyquist/rawwaves/mand3.raw b/Release/nyquist/rawwaves/mand3.raw
new file mode 100644
index 0000000000000000000000000000000000000000..8857f862298c1fd4516047ce8f0daa096a08ebf4
Binary files /dev/null and b/Release/nyquist/rawwaves/mand3.raw differ
diff --git a/Release/nyquist/rawwaves/mand4.raw b/Release/nyquist/rawwaves/mand4.raw
new file mode 100644
index 0000000000000000000000000000000000000000..6058eb10081839bb84810134acd9252f873b8bfc
Binary files /dev/null and b/Release/nyquist/rawwaves/mand4.raw differ
diff --git a/Release/nyquist/rawwaves/mand5.raw b/Release/nyquist/rawwaves/mand5.raw
new file mode 100644
index 0000000000000000000000000000000000000000..9b308a860b4966a4bfa6148f01bdfa70fdc99495
Binary files /dev/null and b/Release/nyquist/rawwaves/mand5.raw differ
diff --git a/Release/nyquist/rawwaves/mand6.raw b/Release/nyquist/rawwaves/mand6.raw
new file mode 100644
index 0000000000000000000000000000000000000000..05f083d8912b7622d17e058ced75e9c06d65f25d
Binary files /dev/null and b/Release/nyquist/rawwaves/mand6.raw differ
diff --git a/Release/nyquist/rawwaves/mand7.raw b/Release/nyquist/rawwaves/mand7.raw
new file mode 100644
index 0000000000000000000000000000000000000000..64941e9f98421aa1b2b353abbfa5a25cd9942295
Binary files /dev/null and b/Release/nyquist/rawwaves/mand7.raw differ
diff --git a/Release/nyquist/rawwaves/mand8.raw b/Release/nyquist/rawwaves/mand8.raw
new file mode 100644
index 0000000000000000000000000000000000000000..52027bf695e8053eb0511e36ac16a38a500d7973
Binary files /dev/null and b/Release/nyquist/rawwaves/mand8.raw differ
diff --git a/Release/nyquist/rawwaves/mand9.raw b/Release/nyquist/rawwaves/mand9.raw
new file mode 100644
index 0000000000000000000000000000000000000000..9e88a0c91317db8a3f2ba7969fe7aa0e0b2e029f
Binary files /dev/null and b/Release/nyquist/rawwaves/mand9.raw differ
diff --git a/Release/nyquist/rawwaves/mandpluk.raw b/Release/nyquist/rawwaves/mandpluk.raw
new file mode 100644
index 0000000000000000000000000000000000000000..162a0da9e1d448ee915c7b4ba441c8da46b6298a
Binary files /dev/null and b/Release/nyquist/rawwaves/mandpluk.raw differ
diff --git a/Release/nyquist/rawwaves/marmstk1.raw b/Release/nyquist/rawwaves/marmstk1.raw
new file mode 100644
index 0000000000000000000000000000000000000000..185b4452613d748bf21d19614a54cd139e7d4b40
Binary files /dev/null and b/Release/nyquist/rawwaves/marmstk1.raw differ
diff --git a/Release/nyquist/rawwaves/sinewave.raw b/Release/nyquist/rawwaves/sinewave.raw
new file mode 100644
index 0000000000000000000000000000000000000000..a5cb34991bc7de10b0f342dc72b9f295a4eff628
Binary files /dev/null and b/Release/nyquist/rawwaves/sinewave.raw differ
diff --git a/Release/nyquist/sal-parse.lsp b/Release/nyquist/sal-parse.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..461ce057dc0792a80c0ac4efc80f1deaae3d7993
--- /dev/null
+++ b/Release/nyquist/sal-parse.lsp
@@ -0,0 +1,1899 @@
+;; SAL parser -- replaces original pattern-directed parser with
+;;    a recursive descent one
+;;
+;; Parse functions either parse correctly and return
+;; compiled code as a lisp expression (which could be nil)
+;; or else they call parse-error, which does not return
+;; (instead, parse-error forces a return from parse)
+;; In the original SAL parser, triples were returned
+;; including the remainder if any of the tokens to be
+;; parsed. In this parser, tokens are on the list
+;; *sal-tokens*, and whatever remains on the list is
+;; the list of unparsed tokens.
+
+;; scanning delimiters.
+
+(setfn nreverse reverse)
+
+(defconstant +quote+ #\")                ; "..." string 
+(defconstant +kwote+ #\')                ; '...' kwoted expr
+(defconstant +comma+ #\,)                ; positional arg delimiter
+(defconstant +pound+ #\#)                ; for bools etc
+(defconstant +semic+ #\;)                ; comment char
+(defconstant +lbrace+ #\{)               ; {} list notation 
+(defconstant +rbrace+ #\})
+(defconstant +lbrack+ #\[)               ; unused for now
+(defconstant +rbrack+ #\])
+(defconstant +lparen+ #\()               ; () expr and arg grouping
+(defconstant +rparen+ #\))
+
+;; these are defined so that SAL programs can name these symbols
+;; note that quote(>) doesn't work, so you need quote(symbol:greater)
+
+(setf symbol:greater '>)
+(setf symbol:less '<)
+(setf symbol:greater-equal '>=)
+(setf symbol:less-equal '<=)
+(setf symbol:equal '=)
+(setf symbol:not '!)
+(setf symbol:not-equal '/=)
+
+
+(defparameter +whites+ (list #\space #\tab #\newline (code-char 13)))
+
+(defparameter +kwstyle+ (list :suffix #\:)) ; let's try dylan
+
+(defparameter +operators+
+  ;; each op is: (<token-class> <sal-name> <lisp-form>)
+  '((:+ "+" sal-plus)
+    (:- "-" diff)
+    (:* "*" mult)
+    (:/ "/" /)
+    (:% "%" rem)
+    (:^ "^" expt)
+    (:= "=" sal-equal)   ; equality and assignment
+    (:!= "!=" not-sal-equal)
+    (:< "<" <)
+    (:> ">" >)
+    (:<= "<=" <=) ; leq and assignment minimization
+    (:>= ">=" >=) ; geq and assignment maximization
+    (:~= "~=" sal-about-equal) ; general equality
+    (:+= "+=" +=) ; assignment increment-and-store
+    (:-= "-=" -=) ; assignment increment-and-store
+    (:*= "*=" *=) ; assignment multiply-and-store
+    (:/= "/=" /=) ; assignment multiply-and-store
+    (:&= "&=" &=) ; assignment list collecting
+    (:@= "@=" @=) ; assignment list prepending
+    (:^= "^=" ^=) ; assignment list appending
+    (:! "!" not)
+    (:& "&" and)
+    (:\| "|" or)
+    (:~ "~" sal-stretch)
+    (:~~ "~~" sal-stretch-abs)
+    (:@ "@" sal-at)
+    (:@@ "@@" sal-at-abs)
+    ))
+
+(setf *sal-local-variables* nil) ;; used to avoid warning about variable
+ ;; names when the variable has been declared as a local
+
+(defparameter *sal-operators*
+  '(:+ :- :* :/ :% :^ := :!= :< :> :<= :>= :~= :+= :*= :&= :@= :^= :! :& :\|
+    :~ :~~ :@ :@@))
+
+(defparameter +delimiters+
+  '((:lp #\()
+    (:rp #\))
+    (:lc #\{)                                ; left curly
+    (:rc #\})
+    (:lb #\[)
+    (:rb #\])
+    (:co #\,)
+    (:kw #\')                                ; kwote
+    (nil #\")                                ; not token
+   ; (nil #\#)
+    (nil #\;)
+    ))
+
+(setf *reserved-words* '((::+ ":+") (::- ":-") (::* ":*") (::/ ":/") (::= ":=")
+                         (::!= ":!=") (::< ":<") (::> ":>") (::<= ":<=")
+                         (::>= ":>=") (::~= ":~=") (::! ":!") (::& ":&")
+                         (::\| ":|") (:IF "if") (:THEN "then") (:ELSE "else")
+                         (:WHEN "when") (:UNLESS "unless") (:SET "set")
+                         (:= "=") (:+= "+=") (:*= "*=") (:&= "&=") (:@= "@=")
+                         (:^= "^=") (:<= "<=") (:>= ">=") (:PRINT "print")
+                         (:LOOP "loop") (:SEQV "seqv") (:SEQREPV "seqrepv")
+                         (:RUN "run") (:REPEAT "repeat") (:FOR "for")
+                         (:FROM "from") (:IN "in") (:BELOW "below") (:TO "to")
+                         (:ABOVE "above") (:DOWNTO "downto") (:BY "by")
+                         (:OVER "over") (:WHILE "while") (:UNTIL "until")
+                         (:FINALLY "finally") (:RETURN "return")
+                         (:WAIT "wait") (:BEGIN "begin") (:WITH "with")
+                         (:END "end") (:VARIABLE "variable")
+                         (:FUNCTION "function")
+                         ; not in nyquist: (:PROCESS "process")
+                         (:CHDIR "chdir")
+                         (:DEFINE "define") (:LOAD "load")
+                         (:PLAY "play") (:PLOT "plot")
+                         (:EXEC "exec") (:exit "exit") (:DISPLAY "display")
+                         (:~ "~") (:~~ "~~") (:@ ":@") (:@@ ":@@")))
+
+
+(setf *sal-fn-name* nil)
+
+(defun make-sal-error (&key type text (line nil) start)
+  ; (error 'make-sal-error-was-called-break)
+  (list 'sal-error type text line start))
+(setfn sal-error-type cadr)
+(setfn sal-error-text caddr)
+(setfn sal-error-line cadddr)
+(defun sal-error-start (x) (cadddr (cdr x)))
+(defun is-sal-error (x) (and x (eq (car x) 'sal-error)))
+(defun sal-tokens-error-start (start)
+  (cond (start 
+         start)
+        (*sal-tokens*
+         (token-start (car *sal-tokens*)))
+        (t
+         (length *sal-input-text*))))
+
+
+(defmacro errexit (message &optional start)
+  `(parse-error (make-sal-error :type "parse"
+                 :line *sal-input-text* :text ,message
+                 :start ,(sal-tokens-error-start start))))
+
+(defmacro sal-warning (message &optional start)
+  `(pperror (make-sal-error :type "parse" :line *sal-input-text*
+                            :text ,message
+                            :start ,(sal-tokens-error-start start))
+            "warning"))
+
+(setf *pos-to-line-source* nil)
+(setf *pos-to-line-pos* nil)
+(setf *pos-to-line-line* nil)
+
+(defun pos-to-line (pos source)
+  ;; this is really inefficient to search every line from
+  ;; the beginning, so cache results and search forward
+  ;; from there if possible
+  (let ((i 0) (line-no 1)) ;; assume no cache
+    ;; see if we can use the cache
+    (cond ((and (eq source *pos-to-line-source*)
+                *pos-to-line-pos* *pos-to-line-line*
+                (>= pos *pos-to-line-pos*))
+           (setf i *pos-to-line-pos*)
+           (setf line-no *pos-to-line-line*)))
+    ;; count newlines up to pos
+    (while (< i pos)
+      (if (char= (char source i) #\newline)
+          (incf line-no))
+      (setf i (1+ i)))
+    ;; save results in cache
+    (setf *pos-to-line-source* source
+          *pos-to-line-pos* pos
+          *pos-to-line-line* line-no)
+    ;; return the line number at pos in source
+    line-no))
+
+
+;; makes a string of n spaces, empty string if n <= 0
+(defun make-spaces (n)
+  (cond ((> n 16)
+         (let* ((half (/ n 2))
+                (s (make-spaces half)))
+           (strcat s s (make-spaces (- n half half)))))
+        (t
+         (subseq "                " 0 (max n 0)))))
+
+
+(defun pperror (x &optional (msg-type "error"))
+  (let* ((source (sal-error-line x))
+         (llen (length source))
+         line-no
+         beg end)
+    ; (display "pperror" x (strcat "|" (sal-error-line x) "|"))
+    ;; isolate line containing error
+    (setf beg (sal-error-start x))
+    (setf beg (min beg (1- llen)))
+    (do ((i beg (- i 1))
+         (n nil)) ; n gets set when we find a newline
+        ((or (< i 0) n)
+         (setq beg (or n 0)))
+      (if (char= (char source i) #\newline)
+          (setq n (+ i 1))))
+    (do ((i (sal-error-start x) (+ i 1))
+         (n nil))
+        ((or (>= i llen) n)
+         (setq end (or n llen)))
+      (if (char= (char source i) #\newline)
+          (setq n i)))
+    (setf line-no (pos-to-line beg source))
+    ; (display "pperror" beg end (sal-error-start x))
+      
+    ;; print the error. include the specific line of input containing
+    ;; the error as well as a line below it marking the error position
+    ;; with an arrow: ^
+    (let* ((pos (- (sal-error-start x) beg))
+           (line (if (and (= beg 0) (= end llen)) 
+                     source
+                     (subseq source beg end)))
+           (mark (make-spaces pos)))
+      (format t "~%>>> ~A ~A: ~A.~%>>> in ~A, line ~A, col ~A.~%~%~A~%~A^~%"
+              (sal-error-type x) msg-type (sal-error-text x)
+              *sal-input-file-name* line-no (1+ pos)
+              line mark)
+;      (format t "~%>>> ~A error in \"~A\", line ~A, col ~A: ~A.~%~%~A~%~A^~%" 
+;              (sal-error-type x) *sal-input-file-name* line-no pos
+;              (sal-error-text x) line mark)
+      x)))
+
+
+;;;
+;;; the lexer. right now it assumes input string is complete and ready
+;;; to be processed as a valid expression.
+;;;
+
+(defun advance-white (str white start end)
+  ;; skip "white" chars, where white can be a char, list of chars
+  ;; or predicate test
+  (do ((i start )
+       (p nil))
+      ((or p (if (< start end)
+                 (not (< -1 i end))
+                 (not (> i end -1))))
+       (or p end))
+    (cond ((consp white)
+           (unless (member (char str i) white :test #'char=)
+             (setq p i)))
+          ((characterp white)
+           (unless (char= (char str i) white)
+             (setq p i)))
+          ((functionp white)
+           (unless (funcall white (char str i))
+             (setq p i))))
+    (if (< start end)
+        (incf i)
+        (decf i))))
+
+
+(defun search-delim (str delim start end)
+  ;; find position of "delim" chars, where delim can be
+  ;; a char, list of chars or predicate test
+  (do ((i start (+ i 1))
+       (p nil))
+      ((or (not (< i end)) p)
+       (or p end))
+    (cond ((consp delim)
+           (if (member (char str i) delim :test #'char=)
+               (setq p i)))
+          ((characterp delim)
+           (if (char= (char str i) delim)
+               (setq p i)))
+          ((functionp delim)
+           (if (funcall delim (char str i))
+               (setq p i))))))
+
+
+;; UNBALANCED-INPUT AND TOKENIZE HAVE BEEN REWRITTEN, SEE BELOW. THIS ONE IS 
+;; OLD AND JUST KEPT HERE FOR REFERENCE
+#|
+(defun unbalanced-input (errf line toks par bra brk kwo)
+  ;; search input for the starting position of some unbalanced
+  ;; delimiter, toks is reversed list of tokens with something
+  ;; unbalanced
+  (let (char text targ othr levl pos)
+    (cond ((> par 0) (setq char #\( targ ':lp othr ':rp levl par))
+          ((< par 0) (setq char #\) targ ':rp othr ':lp levl 0))
+          ((> bra 0) (setq char #\{ targ ':lc othr ':rc levl bra))
+          ((< bra 0) (setq char #\} targ ':rc othr ':lc levl 0))
+          ((> brk 0) (setq char #\[ targ ':ls othr ':rs levl brk))
+          ((< brk 0) (setq char #\] targ ':rs othr ':ls levl 0))
+          ((> kwo 0) (setq char #\' targ ':kw othr ':kw levl kwo)))
+    (setq text (format nil "Unmatched '~A'" char))
+    ;; search for start of error in token list
+    (do ((n levl)
+         (tail toks (cdr tail)))
+        ((or (null tail) pos)
+         (or pos (error (format nil "Shouldn't! can't find op ~A in ~A."
+                                 targ (reverse toks)))))
+      (if (eql (token-type (car tail)) targ)
+          (if (= n levl)
+              (setq pos (token-start (car tail)))
+              (decf n))
+          (if (eql (token-type (car tail)) othr)
+              (incf n))))    
+    (errexit text pos)))
+
+;; REMINDER: THIS IS PART OF A BIG BLOCK COMMENT
+(defun tokenize (str reserved error-fn)
+  ;&key (start 0) (end (length str)) 
+  ;                 (white-space +whites+) (delimiters +delimiters+)
+  ;                 (operators +operators+) (null-ok t)
+  ;              (keyword-style +kwstyle+) (reserved nil) 
+  ;                 (error-fn nil)
+  ;                 &allow-other-keys)
+  ;; return zero or more tokens or a sal-error
+  (let ((toks (list t))
+        (start 0)
+        (end (length str))
+        (all-delimiters +whites+)
+        (errf (or error-fn 
+                  (lambda (x) (pperror x) (return-from tokenize x)))))
+    (dolist (x +delimiters+)
+      (push (cadr x) all-delimiters))
+    (do ((beg start)
+             (pos nil)
+         (all all-delimiters)
+         (par 0)
+         (bra 0)
+         (brk 0)
+         (kwo 0)
+         (tok nil)
+         (tail toks))
+        ((not (< beg end))
+             ;; since input is complete check parens levels.
+         (if (= 0 par bra brk kwo)
+                 (if (null (cdr toks))
+                 (list)
+                 (cdr toks))
+         (unbalanced-input errf str (reverse (cdr toks)) 
+                                       par bra brk kwo)))
+      (setq beg (advance-white str +whites+ beg end))
+      (setf tok
+        (read-delimited str :start beg :end end 
+                        :white +whites+ :delimit all
+                        :skip-initial-white nil :errorf errf))
+      ;; multiple values are returned, so split them here:
+      (setf pos (second tok)) ; pos is the end of the token (!)
+      (setf tok (first tok))
+
+      ;; tok now string, char (delimiter), :eof or token since input
+      ;; is complete keep track of balancing delims
+      (cond ((eql tok +lbrace+) (incf bra))
+            ((eql tok +rbrace+) (decf bra))
+            ((eql tok +lparen+) (incf par))
+            ((eql tok +rparen+) (decf par))
+            ((eql tok +lbrack+) (incf brk))
+            ((eql tok +rbrack+) (decf brk))
+            ((eql tok +kwote+) (setq kwo (mod (+ kwo 1) 2))))
+      (cond ((eql tok ':eof)
+             (setq beg end))
+            
+            (t
+             ;; may have to skip over comments to reach token, so
+             ;; token beginning is computed by backing up from current
+             ;; position (returned by read-delimited) by string length
+             (setf beg (if (stringp tok)
+                           (- pos (length tok))
+                           (1- pos)))
+             (setq tok (classify-token tok beg str errf
+                                       +delimiters+ +operators+
+                                       +kwstyle+ reserved))
+             ;(display "classify-token-result" tok)
+             (setf (cdr tail) (list tok ))
+             (setf tail (cdr tail))
+             (setq beg pos))))))
+|#
+
+
+;; old tokenize (above) counted delimiters to check for balance,
+;; but that does not catch constructions like ({)}. I think
+;; we could just leave this up to the parser, but this rewrite
+;; uses a stack to check balanced parens, braces, quotes, etc.
+;; The checking establishes at least some minimal global properties
+;; of the input before evaluating anything, which might be good
+;; even though it's doing some extra work. In fact, using a
+;; stack rather than counts is doing even more work, but the
+;; problem with counters is that some very misleading or just
+;; plain wrong error messages got generated.
+;;
+;; these five delimiter- functions do checks on balanced parens,
+;; braces, and brackets, leaving delimiter-mismatch set to bad
+;; token if there is a mismatch
+(defun delimiter-init ()
+  (setf delimiter-stack nil)
+  (setf delimiter-mismatch nil))
+(defun delimiter-match (tok what)
+  (cond ((eql (token-string (first delimiter-stack)) what)
+         (pop delimiter-stack))
+        ((null delimiter-mismatch)
+         ;(display "delimiter-mismatch" tok)
+         (setf delimiter-mismatch tok))))
+(defun delimiter-check (tok)
+  (let ((c (token-string tok)))
+    (cond ((member c '(#\( #\{ #\[))
+           (push tok delimiter-stack))
+          ((eql c +rbrace+)
+           (delimiter-match tok +lbrace+))
+          ((eql c +rparen+)
+           (delimiter-match tok +lparen+))
+          ((eql c +rbrack+)
+           (delimiter-match tok +lbrack+)))))
+(defun delimiter-error (tok)
+  (errexit (format nil "Unmatched '~A'" (token-string tok))
+           (token-start tok)))
+(defun delimiter-finish ()
+  (if delimiter-mismatch
+      (delimiter-error delimiter-mismatch))
+  (if delimiter-stack
+      (delimiter-error (car delimiter-stack))))
+(defun tokenize (str reserved error-fn)
+  ;; return zero or more tokens or a sal-error
+  (let ((toks (list t))
+        (start 0)
+        (end (length str))
+        (all-delimiters +whites+)
+        (errf (or error-fn 
+                  (lambda (x) (pperror x) (return-from tokenize x)))))
+    (dolist (x +delimiters+)
+      (push (cadr x) all-delimiters))
+    (delimiter-init)
+    (do ((beg start)
+         (pos nil)
+         (all all-delimiters)
+         (tok nil)
+         (tail toks))
+        ((not (< beg end))
+         ;; since input is complete check parens levels.
+         (delimiter-finish)
+         (if (null (cdr toks)) nil (cdr toks)))
+      (setq beg (advance-white str +whites+ beg end))
+      (setf tok
+        (read-delimited str :start beg :end end 
+                        :white +whites+ :delimit all
+                        :skip-initial-white nil :errorf errf))
+      ;; multiple values are returned, so split them here:
+      (setf pos (second tok)) ; pos is the end of the token (!)
+      (setf tok (first tok))
+
+      (cond ((eql tok ':eof)
+             (setq beg end))
+            (t
+             ;; may have to skip over comments to reach token, so
+             ;; token beginning is computed by backing up from current
+             ;; position (returned by read-delimited) by string length
+             (setf beg (if (stringp tok)
+                           (- pos (length tok))
+                           (1- pos)))
+             (setq tok (classify-token tok beg str errf
+                                       +delimiters+ +operators+
+                                       +kwstyle+ reserved))
+             (delimiter-check tok)
+             ;(display "classify-token-result" tok)
+             (setf (cdr tail) (list tok ))
+             (setf tail (cdr tail))
+             (setq beg pos))))))
+
+
+(defun read-delimited (input &key (start 0) end (null-ok t)
+                       (delimit +delims+) ; includes whites...
+                       (white +whites+)
+                       (skip-initial-white t)
+                       (errorf #'pperror))
+  ;; read a substring from input, optionally skipping any white chars
+  ;; first. reading a comment delim equals end-of-line, input delim
+  ;; reads whole input, pound reads next token. call errf if error
+  ;(FORMAT T "~%READ-DELIMITED: ~S :START ~S :END ~S" input start end)
+  (let ((len (or end (length input))))
+    (while t ;; loop over comment lines
+      (when skip-initial-white
+        (setq start (advance-white input white start len)))
+        (if (< start len)
+          (let ((char (char input start)))
+            (setq end (search-delim input delimit start len))
+            (if (equal start end)                ; have a delimiter
+               (cond ((char= char +semic+)
+                      ;; comment skips to next line and try again...
+                      (while (and (< start len)
+                                  (char/= (char input start) #\newline))
+                        (incf start))
+                      (cond ((< start len) ;; advance past comment and iterate
+                             (incf start)
+                             (setf skip-initial-white t))
+                            (null-ok
+                             (return (list ':eof end)))
+                            (t
+                             (errexit "Unexpected end of input"))))
+;                     ((char= char +pound+)
+;                      ;; read # dispatch
+;                      (read-hash input delimit start len errorf))
+                     ((char= char +quote+)
+                      ;; input delim reads whole input
+                      (return (sal:read-string input delimit start len errorf)))
+                     ((char= char +kwote+)
+                      (errexit "Illegal delimiter" start))
+                     (t ;; all other delimiters are tokens in and of themselves
+                      (return (list char (+ start 1)))))
+            ; else part of (equal start end), so we have token before delimiter
+              (return (list (subseq input start end) end))))
+        ; else part of (< start len)...
+          (if null-ok 
+              (return (list ':eof end))
+              (errexit "Unexpected end of input" start))))))
+
+
+(defparameter hash-readers 
+  '(( #\t sal:read-bool)
+    ( #\f sal:read-bool)
+    ( #\? read-iftok)
+    ))
+
+
+(defun read-hash (str delims pos len errf)
+  (let ((e (+ pos 1)))
+    (if (< e len)
+        (let ((a (assoc (char str e) hash-readers)))
+          (if (not a)
+              (errexit "Illegal # character" e)
+              (funcall (cadr a) str delims e len errf)))
+        (errexit "Missing # character" pos))))
+
+
+(defun read-iftok (str delims pos len errf)
+  str delims len errf
+  (list (make-token :type ':? :string "#?" :lisp 'if
+                         :start (- pos 1))
+        (+ pos 1)))
+
+; (sal:read-string str start len)
+
+(defun sal:read-bool (str delims pos len errf)
+  delims len errf
+  (let ((end (search-delim str delims pos len)))
+    (unless (= end (+ pos 1))
+      (errexit "Illegal # expression" (- pos 1)))
+    (list (let ((t? (char= (char str pos) #\t) ))
+            (make-token :type ':bool 
+                           :string (if t? "#t" "#f")
+                           :lisp t?
+                           :start (- pos 1)))
+          (+ pos 1))))
+
+
+(defun sal:read-string (str delims pos len errf)
+  (let* ((i (1+ pos)) ; i is index into string; start after open quote
+         c c2; c is the character at str[i]
+         (string (make-string-output-stream)))
+    ;; read string, processing escaped characters
+    ;; write the chars to string until end quote is found
+    ;; then retrieve the string. quotes are not included in result token
+
+    ;; in the loop, i is the next character location to examine
+    (while (and (< i len) 
+                (not (char= (setf c (char str i)) +quote+)))
+      (if (char= c #\\) ;; escape character, does another character follow this?
+          (cond ((< (1+ i) len)
+                 (incf i) ;; yes, set i so we'll get the escaped char
+                 (setf c2 (char str i))
+                 (setf c (assoc c2 `((#\n . #\newline) (#\t . #\tab) 
+                                     (#\r . ,(char "\r" 0))
+                                     (#\f . ,(char "\f" 0)))))
+                 (setf c (if c (cdr c) c2))) ;; use c2 if c wasn't listed
+                (t ;; no, we've hit the end of input too early
+                 (errexit "Unmatched \"" i))))
+      ;; we're good to take this character and move on to the next one
+      (write-char c string)
+      (incf i))
+    ;; done with loop, so either we're out of string or we found end quote
+    (if (>= i len) (errexit "Unmatched \"" i))
+    ;; must have found the quote
+    (setf string (get-output-stream-string string))
+    (list (make-token :type :string :start pos :string string :lisp string)
+          (1+ i))))
+
+;;;
+;;; tokens
+;;;
+
+(defun make-token (&key (type nil) (string "") start (info nil) lisp)
+  (list :token type string start info lisp))
+(setfn token-type cadr)
+(setfn token-string caddr)
+(defun token-start (x) (cadddr x))
+(defun token-info (token) (car (cddddr token)))
+(defun token-lisp (token) (cadr (cddddr token)))
+(defmacro set-token-type (tok val) `(setf (car (cdr ,tok)) ,val))
+(defmacro set-token-lisp (tok val) `(setf (car (cdr (cddddr ,tok))) ,val))
+(defun tokenp (tok) (and (consp tok) (eq (car tok) :token)))
+
+(defun token=? (tok op)
+  (if (tokenp tok)
+      (equal (token-type tok) op)
+      (eql tok op)))
+
+(defmethod token-print (obj stream)
+  (let ((*print-case* ':downcase))
+    (format stream "#<~s ~s>" 
+            (token-type obj) 
+            (token-string obj))))
+
+(defun parse-token ()
+  (prog1 (car *sal-tokens*)
+         (setf *sal-tokens* (cdr *sal-tokens*))))
+
+;;;
+;;; token classification. types not disjoint!
+;;;
+
+(defun classify-token (str pos input errf delims ops kstyle res)
+  (let ((tok nil))
+    (cond ((characterp str)
+           ;; normalize char delimiter tokens
+           (setq tok (delimiter-token? str pos input errf delims)))
+          ((stringp str)
+           (setq tok (or (number-token? str pos input errf)
+                         (operator-token? str pos input errf ops)
+                         (keyword-token? str pos input errf kstyle)
+                         (class-token? str pos input errf res)
+                         (reserved-token? str pos input errf res)
+                         (symbol-token? str pos input errf)
+                         ))
+           (unless tok
+             (errexit "Not an expression or symbol" pos)))
+          (t (setq tok str)))
+    tok))
+
+
+(defun delimiter-token? (str pos input errf delims)
+  (let ((typ (member str delims :test (lambda (a b) (char= a (cadr b))))))
+    ;; member returns remainder of the list
+    ;(display "delimiter-token?" str delims typ)
+    (if (and typ (car typ) (caar typ))
+        (make-token :type (caar typ) :string str
+                       :start pos)
+        (+ (break) (errexit "Shouldn't: non-token delimiter" pos)))))
+
+
+(defun string-to-number (s)
+  (read (make-string-input-stream s)))
+
+
+(defun number-token? (str pos input errf)
+  errf input
+  (do ((i 0 (+ i 1))
+       (len (length str))
+       (c nil)
+       (dot 0)
+       (typ ':int)
+       (sig 0)
+       (sla 0)
+       (dig 0)
+       (non nil))
+      ((or (not (< i len)) non)
+       (if non nil
+           (if (> dig 0) 
+               (make-token :type typ :string str
+                              :start pos :lisp (string-to-number str))
+               nil)))
+    (setq c (char str i))
+    (cond ((member c '(#\+ #\-))
+           (if (> i 0) (setq non t)
+               (incf sig)))
+          ((char= c #\.)
+           (if (> dot 0) (setq non t)
+               (if (> sla 0) (setq non t)
+                   (incf dot))))
+; xlisp does not have ratios
+;          ((char= c #\/)
+;           (setq typ ':ratio)
+;           (if (> sla 0) (setq non t)
+;               (if (= dig 0) (setq non t)
+;                   (if (> dot 0) (setq non t)
+;                       (if (= i (1- len)) (setq non t)
+;                           (incf sla))))))
+          ((digit-char-p c)
+           (incf dig)
+           (if (> dot 0) (setq typ ':float)))
+          (t (setq non t)))))
+
+#||
+(number-token? "" 0 "" #'pperror)
+(number-token? " " 0 "" #'pperror)
+(number-token? "a"  0 "" #'pperror)
+(number-token? "1" 0 "" #'pperror)
+(number-token? "+" 0 "" #'pperror)
+(number-token? "-1/2" 0 "" #'pperror)
+(number-token? "1." 0 "" #'pperror)
+(number-token? "1.." 0 "" #'pperror)
+(number-token? ".1." 0 "" #'pperror)
+(number-token? ".1" 0 "" #'pperror)
+(number-token? "-0.1" 0 "" #'pperror)
+(number-token? "1/2" 0 "" #'pperror)
+(number-token? "1//2" 0 "" #'pperror)
+(number-token? "/12" 0 "" #'pperror)
+(number-token? "12/" 0 "" #'pperror)
+(number-token? "12/1" 0 "" #'pperror)
+(number-token? "12./1" 0 "" #'pperror)
+(number-token? "12/.1" 0 "" #'pperror)
+||#
+
+(defun operator-token? (str pos input errf ops)
+  ;; tok can be string or char
+  (let ((typ (member str ops :test (lambda (a b) (equal a (cadr b))))))
+    (cond (typ 
+           (setf typ (car typ)) ;; member returns remainder of list
+           (make-token :type (car typ) :string str
+                       :start pos :lisp (or (third typ)
+                                            (read-from-string str)))))))
+
+(defun str-to-keyword (str)
+  (intern (strcat ":" (string-upcase str))))
+
+
+(defun keyword-token? (tok pos input errf style)
+  (let* ((tlen (length tok))
+         (keys (cdr style))
+         (klen (length keys)))
+    (cond ((not (< klen tlen)) nil)
+          ((eql (car style) ':prefix)
+           (do ((i 0 (+ i 1))
+                (x nil))
+               ((or (not (< i klen)) x)
+                (if (not x)
+                    (let ((sym (symbol-token? (subseq tok i)
+                                              pos input errf )))
+                      (cond (sym
+                             (set-token-type sym ':key)
+                             (set-token-lisp sym
+                                (str-to-keyword (token-string sym)))
+                             sym)))
+                    nil))
+             (unless (char= (char tok i) (nth i keys))
+               (setq x t))))
+          ((eql (car style) ':suffix)
+           (do ((j (- tlen klen) (+ j 1))
+                (i 0 (+ i 1))
+                (x nil))
+               ((or (not (< i klen)) x)
+                (if (not x)
+                    (let ((sym (symbol-token? (subseq tok 0 (- tlen klen))
+                                              pos input errf )))
+                      (cond (sym
+                             (set-token-type sym ':key)
+                             (set-token-lisp sym
+                                (str-to-keyword (token-string sym)))
+                             sym)))
+                    nil))
+             (unless (char= (char tok j) (nth i keys))
+               (setq x t)))))))
+
+
+(setfn alpha-char-p both-case-p)
+
+
+(defun class-token? (str pos input errf res)
+  res
+  (let ((a (char str 0)))
+    (if (char= a #\<)
+        (let* ((l (length str))
+               (b (char str (- l 1))))
+          (if (char= b #\>)
+              (let ((tok (symbol-token? (subseq str 1 (- l 1))
+                                        pos input errf)))
+                ;; class token has <> removed!
+                (if tok (progn (set-token-type tok ':class)
+                               tok)
+                    (errexit "Not a class identifier" pos)))
+              (errexit "Not a class identifer" pos)))
+        nil)))
+
+; (keyword-token? ":asd" '(:prefix #\:))
+; (keyword-token? "asd" KSTYLE)
+; (keyword-token? "asd:"  KSTYLE)
+; (keyword-token? "123:"  KSTYLE)
+; (keyword-token? ":foo" '(:prefix #\:))
+; (keyword-token? "foo=" '(:suffix #\=))
+; (keyword-token? "--foo" '(:prefix #\- #\-))
+; (keyword-token? ":123" '(:suffix #\:))
+; (keyword-token? "--asd" '(:prefix #\-)) ; ok since -asd is legal symbol
+
+
+;; determine if str is a reserved word using reserved as the list of
+;; reserved words, of the form ((id string) (id string) ...) where
+;; id identifies the token, e.g. :to and string is the token, e.g. "to"
+;;
+(defun reserved-token? (str pos input errf reserved)
+  errf input
+  (let ((typ (member str reserved :test 
+                     (lambda (a b) (string-equal a (cadr b))))))
+    (if typ 
+        (make-token :type (caar typ) :string str
+                       :start pos)
+        nil)))
+
+
+(defun sal-string-to-symbol (str)
+  (let ((sym (intern (string-upcase str)))
+        sal-sym)
+    (cond ((and sym ;; (it might be "nil")
+                (setf sal-sym (get sym :sal-name)))
+           sal-sym)
+          (t sym))))
+
+
+(putprop 'simrep 'sal-simrep :sal-name)
+(putprop 'seqrep 'sal-seqrep :sal-name)
+
+(defun contains-op-char (s)
+  ;; assume most identifiers are very short, so we search
+  ;; over identifier letters, not over operator characters
+  ;; Minus (-) is so common, we don't complain about it.
+  (dotimes (i (length s))
+    (if (string-search (subseq s i (1+ i)) "*/+=<>!%^&|")
+        (return t))))
+
+(defun test-for-suspicious-symbol (token)
+  ;; assume token is of type :id
+  (let ((sym (token-lisp token))
+        (str (token-string token))
+        (pos (token-start token)))
+    (cond ((and sym  ; nil is not suspicious, but it's not "boundp"
+                (not (fboundp sym)) ; existing functions not suspicious
+                (not (boundp sym))  ; existing globals not suspicious
+                (not (member sym *sal-local-variables*))
+                (not (eq sym '->))  ; used by make-markov, so let it pass
+                (contains-op-char str)) ; suspicious if embedded operators
+           (sal-warning
+             (strcat "Identifier contains operator character(s).\n"
+                     "        Perhaps you omitted spaces around an operator")
+             pos)))))
+
+
+(defun symbol-token? (str pos input errf)
+  ;; if a potential symbol is preceded by #, drop the #
+  (if (and (> (length str) 1)
+           (char= (char str 0) #\#))
+      ;; there are a couple of special cases: SAL defines #f and #?
+      (cond ((equal str "#f")
+             (return-from symbol-token?
+               (make-token :type ':id :string str :start pos :lisp nil)))
+            ((equal str "#?")
+             (return-from symbol-token?
+               (make-token :type ':id :string str :start pos :lisp 'if)))
+            (t
+             (setf str (subseq str 1)))))
+  ;; let's insist on at least one letter for sanity's sake
+  ;; exception: allow '-> because it is used in markov pattern specs
+  (do ((i 0 (+ i 1))  ; i is index into string
+       (bad "Not an expression or symbol")
+       (chr nil)
+       (ltr 0)        ; ltr is count of alphabetic letters in string
+       (dot nil)      ; dot is index of "."
+       (pkg nil)      ; pkg is index if package name "xxx:" found
+       (len (length str))
+       (err nil))
+      ;; loop ends when i is at end of string or when err is set
+      ((or (not (< i len)) err)
+       (if (or (> ltr 0) ; must be at least one letter, or
+               (equal str "->")) ; symbol can be "->"
+           (let ((info ()) sym)
+             (if pkg (push (cons ':pkg pkg) info))
+             (if dot (push (cons ':slot dot) info))
+             ;(display "in symbol-token?" str)
+             (setf sym (sal-string-to-symbol str))
+             (make-token :type ':id :string str
+                            :info info :start pos
+                            :lisp sym))
+           nil))
+    (setq chr (char str i))
+    (cond ((alpha-char-p chr) (incf ltr))
+; need to allow arbitrary lisp symbols
+;          ((member chr '(#\* #\+)) ;; special variable names can start/end 
+;           (if (< 0 i (- len 2))   ;; with + or *
+;               (errexit bad pos)))
+          ((char= chr #\/) ;; embedded / is not allowed
+           (errexit bad pos))
+          ;((char= chr #\-) ;; hyphens are allowed anywhere in symbol
+          ; (if (= ltr 0) 
+          ;     (errexit errf input bad pos )
+          ;     (setq ltr 0)
+          ;     ))
+          ((char= chr #\$) (incf ltr)) ;; "$" is treated as a letter
+          ((char= chr #\:)
+           ; allowable forms are :foo, foo:bar, :foo:bar
+           (if (> i 0) ;; lisp keyword symbols ok
+               (cond ((= ltr 0)
+                      (errexit bad pos))
+                     ((not pkg)
+                      (setq pkg i))
+                     (t (errexit errf input
+                                 (format nil "Too many colons in ~s" str)
+                                 pos))))
+           (setq ltr 0))
+          ((char= chr #\.)
+           (if (or dot (= i 0) (= i (- len 1)))
+               (errexit bad pos)
+               (progn (setq dot i) (setq ltr 0)))))))
+
+
+; (let ((i "foo")) (symbol-token? i 0 i #'pperror))
+; (let ((i "foo..bar")) (symbol-token? i 0 i #'pperror))
+; (let ((i ".bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "bar.")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "1...")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "a1..." )) (symbol-token? i 0 i #'pperror))
+; (let ((i  "a{b")) (symbol-token? i 0 i #'pperror))
+; (let ((i "foo-bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "123-a")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "1a23-a")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "*foo*")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "+foo+")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "foo+bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "foo/bar")) (symbol-token?  i 0 i #'pperror))
+
+; (let ((i ":bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "::bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "foo:bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "cl-user:bar")) (symbol-token?  i 0 i #'pperror))
+; (let ((i "cl-user::bar")) (symbol-token?  i 0 i #'pperror))
+; (tokenize "aaa + bbb \"asdasdd\" aaa(1,2,3)")
+; (tokenize "aaa+bbb \"asdasdd\" aaa(1,2,3)")
+
+
+(setf *in-sal-parser* nil)
+
+;; line number info for debugging
+(setf *sal-line-number-info* t)
+(setf *sal-line* 0)
+
+(defun add-line-info-to-expression (expr token)
+  (let (line-no)
+    (cond ((and token ;; null token means do not change expr
+                *sal-line-number-info* ;; is this feature enabled?
+                (stringp *sal-input-text*))
+           ;; first, get line number
+           (setf line-no (pos-to-line (token-start token) *sal-input-text*))
+           `(prog2 (setf *sal-line* ,line-no) ,expr))
+          (t expr))))
+
+;; single statement is handled just like an expression
+(setfn add-line-info-to-stmt add-line-info-to-expression)
+
+;; list of statements is simple to handle: prepend SETF
+(defun add-line-info-to-stmts (stmts token)
+  (let (line-no)
+    (cond ((and *sal-line-number-info* ;; is this feature enabled?
+                (stringp *sal-input-text*))
+           (setf line-no (pos-to-line (token-start token) *sal-input-text*))
+           (cons `(setf *sal-line* ,line-no) stmts))
+          (t stmts))))
+
+
+;; PARSE-ERROR -- print error message, return from top-level
+;;
+(defun parse-error (e)
+  (unless (sal-error-line e)
+    (setf (sal-error-line e) *sal-input*))
+  (pperror e)
+  (return-from sal-parse (values nil e *sal-tokens*)))
+
+
+;; SAL-PARSE -- parse string or token input, translate to Lisp
+;;
+;; If input is text, *sal-input-text* is set to the text and
+;;   read later (maybe) by ERREXIT. 
+;; If input is a token list, it is assumed these are leftovers
+;;   from tokenized text, so *sal-input-text* is already valid.
+;; *Therefore*, do not call sal-parse with tokens unless 
+;;   *sal-input-text* is set to the corresponding text.
+;;
+(defun sal-parse (grammar pat input multiple-statements file)
+  (progv '(*sal-input-file-name*) (list file)
+    (let (rslt expr rest)
+      ; ignore grammar and pat (just there for compatibility)
+      ; parse input and return lisp expression
+      (cond ((stringp input)
+             (setf *sal-input-text* input)
+             (setq input (tokenize input *reserved-words* #'parse-error))))
+      (setf *sal-input* input) ;; all input
+      (setf *sal-tokens* input) ;; current input
+      (cond ((null input)
+             (values t nil nil)) ; e.g. comments compile to nil
+            (t
+             (setf rslt (or (maybe-parse-command)
+                            (maybe-parse-block)
+                            (maybe-parse-conditional)
+                            (maybe-parse-assignment)
+                            (maybe-parse-loop)
+                            (maybe-parse-exec)
+                            (maybe-parse-exit)
+                            (errexit "Syntax error")))
+             ;; note: there is a return-from parse in parse-error that
+             ;; returns (values nil error <unparsed-tokens>)
+             (cond ((and *sal-tokens* (not multiple-statements))
+                    (errexit "leftover tokens")))
+                    ;((null rslt)
+                    ; (errexit "nothing to compile")))
+             (values t rslt *sal-tokens*))))))
+
+
+;; TOKEN-IS -- test if the type of next token matches expected type(s)
+;;
+;; type can be a list of possibilities or just a symbol
+;; Usually, suspicious-id-warn is true by default, and any symbol
+;; with embedded operator symbols, e.g. x+y results in a warning
+;; that this is an odd variable name. But if the symbol is declared
+;; as a local, a parameter, a function name, or a global variable,
+;; then the warning is suppressed.
+;;
+(defun token-is (type &optional (suspicious-id-warn t))
+  (let ((token-type
+         (if *sal-tokens* (token-type (car *sal-tokens*)) nil))
+        rslt)
+    ; input can be list of possible types or just a type:
+    (setf rslt (or (and (listp type) 
+                        (member token-type type))
+                   (and (symbolp type) (eq token-type type))))
+    ; test if symbol has embedded operator characters:
+    (cond ((and rslt suspicious-id-warn (eq token-type :id))
+           (test-for-suspicious-symbol (car *sal-tokens*))))
+    rslt))
+
+
+(defun maybe-parse-command ()
+  (if (token-is '(:define :load :chdir :variable :function
+                  ;  :system 
+                  :play :print :display :plot))
+      (parse-command)
+      (if (and (token-is '(:return)) *audacity-top-level-return-flag*)
+          (parse-command))))
+
+
+(defun parse-command ()
+  (cond ((token-is '(:define :variable :function))
+         (parse-declaration))
+        ((token-is :load)
+         (parse-load))
+        ((token-is :chdir)
+         (parse-chdir))
+        ((token-is :play)
+         (parse-play))
+;        ((token-is :system)
+;         (parse-system))
+        ((token-is :print)
+         (parse-print-display :print 'sal-print))
+        ((token-is :display)
+         (parse-print-display :display 'display))
+        ((token-is :plot)
+         (parse-plot))
+        ((and *audacity-top-level-return-flag* (token-is :return))
+         (parse-return))
+;        ((token-is :output)
+;         (parse-output))
+        (t
+         (errexit "Command not found"))))
+
+
+(defun parse-stmt ()
+  (cond ((token-is :begin)
+         (parse-block))
+        ((token-is '(:if :when :unless))
+         (parse-conditional))
+        ((token-is :set)
+         (parse-assignment))
+        ((token-is :loop)
+         (parse-loop))
+        ((token-is :print)
+         (parse-print-display :print 'sal-print))
+        ((token-is :display)
+         (parse-print-display :display 'display))
+        ((token-is :plot)
+         (parse-plot))
+;        ((token-is :output)
+;         (parse-output))
+        ((token-is :exec)
+         (parse-exec))
+        ((token-is :exit)
+         (parse-exit))
+        ((token-is :return)
+         (parse-return))
+        ((token-is :load)
+         (parse-load))
+        ((token-is :chdir)
+         (parse-chdir))
+;        ((token-is :system)
+;         (parse-system))
+        ((token-is :play)
+         (parse-play))
+        (t
+         (errexit "Command not found"))))
+        
+
+;; GET-PARM-NAMES -- given parms like (a b &key (x 1) (y 2)),
+;;   return list of parameters: (a b x y)
+(defun get-parm-names (parms)
+  (let (rslt)
+    (dolist (p parms)
+      (cond ((symbolp p) 
+             (if (eq p '&key) nil (push p rslt)))
+            (t (push (car p) rslt))))
+    (reverse rslt)))
+
+
+;; RETURNIZE -- make a statement (list) end with a sal-return-from
+;;
+;;   SAL returns nil from begin-end statement lists
+;;
+(defun returnize (stmt)
+  (let (rev expr)
+    (setf rev (reverse stmt))
+    (setf expr (car rev)) ; last expression in list
+    (cond ((and (consp expr) (eq (car expr) 'sal-return-from))
+           stmt) ; already ends in sal-return-from
+          (t
+           (reverse (cons (list 'sal-return-from *sal-fn-name* nil)
+                          rev))))))
+
+
+(defun parse-declaration ()
+  (if (token-is :define) (parse-token)) ; SAL extension: "define" is optional
+  (let (bindings setf-args formals parms stmt locals loc)
+    (cond ((token-is :variable)
+           (setf bindings (parse-bindings))
+           (setf loc *rslt*) ; the "variable" token
+           (dolist (b bindings)
+             (cond ((symbolp b)
+                    (push b setf-args)
+                    (push `(if (boundp ',b) ,b) setf-args))
+                   (t
+                    (push (first b) setf-args)
+                    (push (second b) setf-args))))
+           (add-line-info-to-stmt (cons 'setf (reverse setf-args)) loc))
+          ((token-is :function)
+           (parse-token)
+           (if (token-is :id nil)
+               (setf *sal-fn-name* (token-lisp (parse-token)))
+               (errexit "function name expected here"))
+           (setf locals *sal-local-variables*)
+           (setf formals (parse-parms))
+           (setf stmt (parse-stmt))
+           ;; stmt may contain a return-from, so make this a progn or prog*
+           (cond ((and (consp stmt) 
+                       (not (eq (car stmt) 'progn))
+                       (not (eq (car stmt) 'prog*)))
+                  (setf stmt (list 'progn stmt))))
+           ;; need return to pop traceback stack
+           (setf stmt (returnize stmt))
+           ;; get list of parameter names
+           (setf parms (get-parm-names formals))
+           (setf *sal-local-variables* locals)
+           ;; build the defun
+           (prog1 (list 'defun *sal-fn-name* formals 
+                        (list 'sal-trace-enter 
+                              (list 'quote *sal-fn-name*) 
+                              (cons 'list parms)
+                              (list 'quote parms))
+                        stmt)
+                  (setf *sal-fn-name* nil)))
+          (t
+           (errexit "bad syntax")))))
+
+
+(defun parse-one-parm (kargs)
+  ;; kargs is a flag indicating previous parameter was a keyword (all
+  ;;   the following parameters must then also be keyword parameters)
+  ;; returns: (<keyword> <default>) or (nil <identifier>)
+  ;;   where <keyword> is a keyword parameter name (nil if not a keyword parm)
+  ;;         <default> is an expression for the default value
+  ;;         <identifier> is the parameter name (if not a keyword parm)
+  (let (key default-value id)
+    (cond ((and kargs (token-is :id))
+           (errexit "positional parameter not allowed after keyword parameter"))
+          ((token-is :id)
+           ;(display "parse-one-1" (token-is :id) (car *sal-tokens*))
+           (setf id (token-lisp (parse-token)))
+           (push id *sal-local-variables*)
+           (list nil id))
+          ((token-is :key)
+           (setf key (sal-string-to-symbol (token-string (parse-token))))
+           (cond ((or (token-is :co) (token-is :rp))) ; no default value
+                 (t
+                  (setf default-value (parse-sexpr))))
+           (list key default-value)) 
+          (kargs
+           (errexit "expected keyword name"))
+          (t
+           (errexit "expected parameter name")))))
+
+
+(defun parse-parms ()
+  ;(display "parse-parms" *sal-tokens*)
+  (let (parms parm kargs expecting)
+    (if (token-is :lp)
+        (parse-token) ;; eat the left paren
+        (errexit "expected left parenthesis"))
+    (setf expecting (not (token-is :rp)))
+    (while expecting
+      (setf parm (parse-one-parm kargs))
+      ;(display "parm" parm)
+      ;; returns list of (kargs . parm)
+      (if (and (car parm) (not kargs)) ; kargs just set
+          (push '&key parms))
+      (setf kargs (car parm))
+      ;; normally push the <id>; for keyword parms, push id and default value
+      (push (if kargs parm (cadr parm)) parms)
+      (if (token-is :co)
+          (parse-token)
+          (setf expecting nil)))
+    (if (token-is :rp) (parse-token)
+        (errexit "expected right parenthesis"))
+    ;(display "parse-parms" (reverse parms))
+    (reverse parms)))
+
+
+(defun parse-bindings ()
+  (let (bindings bind)
+    (setf *rslt* (parse-token)) ; skip "variable" or "with"
+      ; return token as "extra" return value
+    (setf bind (parse-bind))
+    (push (if (second bind) bind (car bind))
+          bindings)
+    (while (token-is :co)
+      (parse-token)
+      (setf bind (parse-bind))
+      ;; if non-nil initializer, push (id expr)
+      (push (if (second bind) bind (car bind))
+            bindings))
+    (reverse bindings)))
+
+
+(defun parse-bind ()
+  (let (id val)
+    (if (token-is :id nil)
+        (setf id (token-lisp (parse-token)))
+        (errexit "expected a variable name"))
+    (cond ((token-is :=)
+           (parse-token)
+           (setf val (parse-sexpr))))
+    (push id *sal-local-variables*)
+    (list id val)))
+
+
+(defun parse-chdir ()
+  ;; assume next token is :chdir
+  (or (token-is :chdir) (error "parse-chdir internal error"))
+  (let (path loc)
+   (setf loc (parse-token))
+   (setf path (parse-path))
+   (add-line-info-to-stmt (list 'setdir path) loc)))
+
+
+(defun parse-play ()
+ ;; assume next token is :play
+ (or (token-is :play) (error "parse-play internal error"))
+ (let ((loc (parse-token)))
+   (add-line-info-to-stmt (list 'sal-play (parse-sexpr)) loc)))
+
+
+(defun parse-return ()
+  (or (token-is :return) (error "parse-return internal error"))
+  (let (loc expr)
+    ;; this seems to be a redundant test
+    (if (and (null *sal-fn-name*)
+             (not *audacity-top-level-return-flag*))
+        (errexit "Return must be inside a function body"))
+    (setf loc (parse-token))
+    (setf expr (parse-sexpr))
+    (if *sal-fn-name*
+      (add-line-info-to-stmt (list 'sal-return-from *sal-fn-name* expr) loc)
+      (list 'defun 'main '() (list 'sal-trace-enter '(quote main) '() '())
+                             (add-line-info-to-stmt expr loc)))))
+
+
+(defun parse-load ()
+  ;; assume next token is :load
+  (or (token-is :load) (error "parse-load internal error"))
+  (let (path args loc)
+   (setf loc (parse-token))
+   (setf path (parse-path)) ; must return path or raise error
+   (setf args (parse-keyword-args))
+   (add-line-info-to-stmt (cons 'sal-load (cons path args)) loc)))
+
+(defun parse-keyword-args ()
+  (let (args)
+    (while (token-is :co)
+      (parse-token)
+      (cond ((token-is :key)
+             (push (token-value) args)
+             (push (parse-sexpr) args))))
+    (reverse args)))
+
+
+'(defun parse-system ()
+  ;; assume next token is :system
+  (or (token-is :system) (error "parse-system internal error"))
+  (let (path arg args)
+   (parse-token)
+   (setf path (parse-sexpr))
+   (list 'sal-system path)))
+
+
+(defun parse-path ()
+  (if (token-is '(:id :string))
+      (token-lisp (parse-token))
+      (errexit "path not found")))
+
+
+(defun parse-print-display (token function)
+  ;; assumes next token is :print
+  (or (token-is token) (error "parse-print-display internal error"))
+  (let (args arg loc)
+   (setf loc (parse-token))
+   (setf arg (parse-sexpr))
+   (setf args (list arg))
+   (while (token-is :co)
+    (parse-token) ; remove and ignore the comma
+    (setf arg (parse-sexpr))
+    (push arg args))
+   (add-line-info-to-stmt (cons function (reverse args)) loc)))
+
+(defun parse-plot ()
+  ;; assumes next token is :plot
+  (or (token-is :plot) (error "parse-plot internal error"))
+  (let (arg args loc)
+   (setf loc (parse-token))
+   (setf arg (parse-sexpr))
+   (setf args (list arg))
+   (cond ((token-is :co) ; get duration parameter
+          (parse-token) ; remove and ignore the comma
+          (setf arg (parse-sexpr))
+          (push arg args)
+          (cond ((token-is :co) ; get n points parameter
+                 (parse-token) ; remove and ignore the comma
+                 (setf arg (parse-sexpr))))))
+   (add-line-info-to-stmt (cons 's-plot (reverse args)) loc)))
+
+;(defun parse-output ()
+; ;; assume next token is :output
+; (or (token-is :output) (error "parse-output internal error"))
+; (parse-token)
+; (list 'sal-output (parse-sexpr)))
+
+
+(defun maybe-parse-block ()
+  (if (token-is :begin) (parse-block)))
+
+
+(defun parse-block ()
+  ;; assumes next token is :block
+  (or (token-is :begin) (error "parse-block internal error"))
+  (let (args stmts (locals *sal-local-variables*))
+   (parse-token)
+   (cond ((token-is :with)
+          (setf args (parse-bindings))))
+   (while (not (token-is :end))
+    (push (parse-stmt) stmts))
+   (parse-token)
+   (setf stmts (reverse stmts))
+   ;(display "parse-block" args stmts)
+   (setf *sal-local-variables* locals)
+   (cons 'prog* (cons args stmts))))
+
+ 
+;; MAKE-STATEMENT-LIST -- convert stmt to a stmt list
+;;
+;; if it is a (PROGN ...) then return cdr -- it's already a list
+;; otherwise, put single statement into a list
+;;
+(defun make-statement-list (stmt)
+  (cond ((atom stmt)
+         (list stmt))
+        ((eq (car stmt) 'progn)
+         (cdr stmt))
+        (t
+         (list stmt))))
+
+(setf *conditional-tokens* '(:if :when :unless))
+
+
+(defun maybe-parse-conditional ()
+  (if (token-is *conditional-tokens*)
+      (parse-conditional)))
+
+
+(defun parse-conditional ()
+  ;; assumes next token is :if
+  (or (token-is *conditional-tokens*)
+      (error "parse-conditional internal error"))
+  (let (test then-stmt else-stmt if-token)
+    (cond ((token-is :if)
+           (setf if-token (parse-token))
+           (setf test (parse-sexpr if-token))
+           (if (not (token-is :then))
+               (errexit "expected then after if"))
+           (parse-token)
+           (if (not (token-is :else)) ;; no then statement
+               (setf then-stmt (parse-stmt)))
+           (cond ((token-is :else)
+                  (parse-token)
+                  (setf else-stmt (parse-stmt))))
+           ;(display "cond" test then-stmt else-stmt)
+           (if else-stmt
+               (list 'if test then-stmt else-stmt)
+               (list 'if test then-stmt)))
+          ((token-is :when)
+           (parse-token)
+           (setf test (parse-sexpr))
+           (setf then-stmt (parse-stmt))
+           (cons 'when (cons test (make-statement-list then-stmt))))
+          ((token-is :unless)
+           (parse-token)
+           (setf test (parse-sexpr))
+           (setf else-stmt (parse-stmt))
+           (cons 'unless (cons test (make-statement-list else-stmt)))))))
+
+
+(defun maybe-parse-assignment ()
+  (if (token-is :set) (parse-assignment)))
+
+
+(defun parse-assignment ()
+  ;; first token must be set
+  (or (token-is :set) (error "parse-assignment internal error"))
+  (let (assignments rslt vref op expr set-token)
+    (setf set-token (parse-token))
+    (push (parse-assign) assignments) ; returns (target op value)
+    (while (token-is :co)
+      (parse-token) ; skip the comma
+      (push (parse-assign) assignments))
+    ; now assignments is ((target op value) (target op value)...)
+    (dolist (assign assignments)
+      (setf vref (first assign) op (second assign) expr (third assign))
+      (cond ((eq op '=))
+            ((eq op '-=) (setf expr `(diff ,vref ,expr)))
+            ((eq op '+=) (setf expr `(sum ,vref ,expr)))
+            ((eq op '*=) (setq expr `(mult ,vref ,expr)))
+            ((eq op '/=) (setq expr `(/ ,vref ,expr)))
+            ((eq op '&=) (setq expr `(nconc ,vref (list ,expr))))
+            ((eq op '@=) (setq expr `(cons ,expr ,vref)))
+            ((eq op '^=) (setq expr `(nconc ,vref (append ,expr nil))))
+            ((eq op '<=) (setq expr `(min ,vref ,expr)))
+            ((eq op '>=) (setq expr `(max ,vref ,expr)))
+            (t (errexit (format nil "unknown assignment operator ~A" op))))
+      (push (list 'setf vref expr) rslt))
+    (setf rslt (add-line-info-to-stmts rslt set-token))
+    (if (> (length rslt) 1)
+        (cons 'progn rslt)
+        (car rslt))))
+
+    
+;; PARSE-ASSIGN -- based on parse-bind, but with different operators
+;;
+;; allows arbitrary term on left because it could be an array
+;; reference. After parsing, we can check that the target of the
+;; assignment is either an identifier or an (aref ...)
+;;
+(defun parse-assign ()
+  (let ((lhs (parse-term) op val))
+    (cond ((token-is '(:= :-= :+= :*= :/= :&= :@= :^= :<= :>=))
+           (setf op (parse-token))
+           (setf op (if (eq (token-type op) ':=) '= (token-lisp op)))
+           (setf val (parse-sexpr))))
+    (cond ((and (consp lhs) (eq (car lhs) 'aref))) ;; aref good
+          ((symbolp lhs)) ;; id good
+          (t (errexit "expected a variable name or array reference")))
+    (list lhs op val)))
+
+
+(defun maybe-parse-loop ()
+  (if (token-is :loop) (parse-loop)))
+
+
+;; loops are compiled to do*
+;; bindings go next as usual, but bindings include for variables
+;; and repeat is converted to a for +count+ from 0 to <sexpr>
+;; stepping is done after statement
+;; termination clauses are combined with OR and
+;; finally goes after termination
+;; statement goes in do* body
+;;
+(defun parse-loop ()
+  (or (token-is :loop) (error "parse-loop: internal error"))
+  (let (bindings termination-tests stmts sexpr rslt finally
+        loc
+        (locals *sal-local-variables*))
+    (parse-token) ; skip "loop"
+    (if (token-is :with)
+        (setf bindings (reverse (parse-bindings))))
+    (while (token-is '(:repeat :for))
+      (cond ((token-is :repeat)
+             (setf loc (parse-token))
+             (push (list 'sal:loopcount 0 '(1+ sal:loopcount)) bindings)
+             (setf sexpr (parse-sexpr loc)) ; get final count expression
+             (push (list 'sal:loopfinal sexpr) bindings)
+             (push '(>= sal:loopcount sal:loopfinal) termination-tests))
+            ((token-is :for)
+             (setf rslt (parse-for-clause))
+             ; there can be multiple bindings, build bindings in reverse
+             (cond ((first rslt)
+                    (setf bindings (append (reverse (first rslt))
+                                           bindings))))
+             (if (second rslt) (push (second rslt) termination-tests)))))
+    (while (token-is '(:while :until))
+      (cond ((token-is :while)
+             (setf loc (parse-token))
+             (push (list 'not (parse-sexpr loc)) termination-tests))
+            ((token-is :until)
+             (setf loc (parse-token))
+             (push (parse-sexpr loc) termination-tests))))
+    ; (push (parse-stmt) stmts)
+    (while (not (token-is '(:end :finally)))
+      (push (parse-stmt) stmts))
+    (cond ((token-is :finally)
+           (parse-token) ; skip "finally"
+           (setf finally (parse-stmt))))
+    (if (token-is :end)
+        (parse-token)
+        (errexit "expected end after loop"))
+    (setf *sal-local-variables* locals)
+    `(do* ,(reverse bindings)
+          ,(list (or-ize (reverse termination-tests)) finally) 
+          ,@(reverse stmts))))
+
+
+;; OR-IZE -- compute the OR of a list of expressions
+;;
+(defun or-ize (exprs)
+ (if (> (length exprs) 1) (cons 'or exprs)
+     (car exprs)))
+
+
+(defun maybe-parse-exec ()
+  (if (token-is :exec) (parse-exec)))
+
+
+(defun parse-exec ()
+  (or (token-is :exec) (error "parse-exec internal error"))
+  (let ((loc (parse-token))) ;  skip the :exec
+    (parse-sexpr loc)))
+          
+
+(defun maybe-parse-exit ()
+  (if (token-is :exit) (parse-exit)))
+
+
+(defun parse-exit ()
+  (let (tok loc)
+    (or (token-is :exit) (error "parse-exit internal error"))
+    (setf loc (parse-token)) ; skip the :exit
+    (cond ((token-is :id)
+           (setf tok (parse-token))
+           (cond ((eq (token-lisp tok) 'nyquist)
+                  (add-line-info-to-stmt '(exit) loc))
+                 ((eq (token-lisp tok) 'sal)
+                  (add-line-info-to-stmt '(sal-exit) loc))
+                 (t
+                  (errexit "expected \"nyquist\" or \"sal\" after \"exit\""))))
+          (t
+           (add-line-info-to-stmt '(sal-exit) loc)))))
+
+
+;; PARSE-FOR-CLAUSE - returns (bindings term-test)
+;;
+(defun parse-for-clause ()
+  (or (token-is :for) (error "parse-for-clause: internal error"))
+  (let (id init next rslt binding term-test list-id loc)
+    (setf loc (parse-token)) ; skip for
+    (if (token-is :id)
+        (setf id (token-lisp (parse-token)))
+        (errexit "expected identifier after for"))
+    (cond ((token-is :=)
+           ;; if the clause is just for id = expr, then assume that
+           ;; expr depends on something that changes each iteration:
+           ;; recompute and assign expr to id each time around
+           (parse-token) ; skip "="
+           (setf init (parse-sexpr loc))
+           (cond ((token-is :then)
+                  (parse-token) ; skip "then"
+                  (setf binding (list id init (parse-sexpr loc))))
+                 (t
+                  (setf binding (list id init init))))
+           (setf binding (list binding)))
+          ((token-is :in)
+           (setf loc (parse-token)) ; skip "in"
+           (setf list-id (intern (format nil "SAL:~A-LIST" id)))
+           (setf binding 
+                 (list (list list-id (parse-sexpr loc)
+                             (list 'cdr list-id))
+                       (list id (list 'car list-id) (list 'car list-id))))
+           (setf term-test (list 'null list-id)))
+          ((token-is :over)
+           (setf loc (parse-token)) ; skip "over"
+           (setf start (parse-sexpr loc))
+#|         (cond ((token-is :by)
+                  (parse-token) ; skip "by"
+                  (parse-sexpr))) ;-- I don't know what "by" means - RBD |#
+           (setf list-id (intern (format nil "SAL:~A-PATTERN" id)))
+           (setf binding
+                 (list (list list-id start)
+                       (list id (list 'next list-id) (list 'next list-id)))))
+          ((token-is '(:from :below :to :above :downto :by))
+           (cond ((token-is :from)
+                  (setf loc (parse-token)) ; skip "from"
+                  (setf init (parse-sexpr loc)))
+                 (t
+                  (setf init 0)))
+           (cond ((token-is :below)
+                  (setf loc (parse-token)) ; skip "below"
+                  (setf term-test (list '>= id (parse-sexpr loc))))
+                 ((token-is :to)
+                  (setf loc (parse-token)) ; skip "to"
+                  (setf term-test (list '> id (parse-sexpr loc))))
+                 ((token-is :above)
+                  (setf loc (parse-token)) ; skip "above"
+                  (setf term-test (list '<= id (parse-sexpr loc))))
+                 ((token-is :downto)
+                  (setf loc (parse-token)) ; skip "downto"
+                  (setf term-test (list '< id (parse-sexpr loc)))))
+           (cond ((token-is :by)
+                  (setf loc (parse-token)) ; skip "by"
+                  (setf binding (list id init (list '+ id (parse-sexpr loc)))))
+                 ((or (null term-test)
+                      (and term-test (member (car term-test) '(>= >))))
+                  (setf binding (list id init (list '1+ id))))
+                 (t ; loop goes down because of "above" or "downto"
+                    ; (display "for step" term-test)
+                  (setf binding (list id init (list '1- id)))))
+           (setf binding (list binding)))
+          (t
+           (errexit "for statement syntax error")))
+    (list binding term-test)))
+
+    
+;; parse-sexpr works by building a list: (term op term op term ...)
+;; later, the list is parsed again using operator precedence rules
+(defun parse-sexpr (&optional loc)
+  (let (term rslt)
+    (push (parse-term) rslt)
+    (while (token-is *sal-operators*)
+      (push (token-type (parse-token)) rslt)
+      (push (parse-term) rslt))
+    (setf rslt (reverse rslt))
+    ;(display "parse-sexpr before inf->pre" rslt)
+    (setf rslt (if (consp (cdr rslt))
+                (inf->pre rslt)
+                (car rslt)))
+    (if loc
+        (setf rslt (add-line-info-to-expression rslt loc)))
+    rslt))
+
+
+(defun get-lisp-op (op)
+  (third (assoc op +operators+)))
+
+
+;; a term is <unary-op> <term>, or
+;;           ( <sexpr> ), or
+;;           ? ( <sexpr> , <sexpr> , <sexpr> ), or
+;;           <id>, or
+;;           <id> ( <args> ), or
+;;           <term> [ <sexpr> ]
+;; Since any term can be followed by indexing, handle everything
+;; but the indexing here in parse-term-1, then write parse-term
+;; to do term-1 followed by indexing operations
+;;
+(defun parse-term-1 ()
+  (let (sexpr id vars loopvar n)
+    (cond ((token-is '(:- :!))
+           (list (token-lisp (parse-token)) (parse-term)))
+          ((token-is :lp)
+           (parse-token) ; skip left paren
+           (setf sexpr (parse-sexpr))
+           (if (token-is :rp)
+               (parse-token)
+               (errexit "right parenthesis not found"))
+           sexpr)
+          ((token-is :?)
+           (parse-ifexpr))
+          ((token-is :lc)
+           (list 'quote (parse-list)))
+          ((token-is '(:int :float :bool :list :string))
+           ;(display "parse-term int float bool list string" (car *sal-tokens*))
+           (token-lisp (parse-token)))
+          ((token-is :id) ;; aref or funcall
+           (setf id (token-lisp (parse-token)))
+           ;; array indexing was here, but that only allows [x] after
+           ;; identifiers. Move this to expression parsing.
+           (cond ((token-is :lp)
+                  (parse-token)
+                  (setf sexpr (cons id (parse-pargs t)))
+                  (if (token-is :rp)
+                      (parse-token)
+                      (errexit "right paren not found"))
+                  sexpr)
+                 (t id)))
+          ((token-is '(:seqv :seqrepv))
+           (setf id (intern (string-upcase (token-string (parse-token)))))
+           (display "parse-term-1" id)
+           (setf vars (parse-idlist))
+           (if (not (token-is :lp))
+               (errexit "expected list of behaviors"))
+           (parse-token)
+           (setf sexpr (parse-pargs nil))
+           ;; if this is seqrepv, move the first 2 parameters (loop var and
+           ;; count expression) in front of the var list
+           (cond ((eq id 'SEQREPV)
+                  (setf loopvar (pop sexpr))
+                  (if (not (and loopvar (symbolp loopvar)))
+                      (errexit "expected identifier as first \"parameter\""))
+                  (setf n (pop sexpr))
+                  (if (null n)
+                      (errexit "expected repetition count as second parameter"))
+                  (setf vars (cons id (cons n vars)))))
+           (setf sexpr (cons id (cons vars sexpr)))
+           (if (token-is :rp)
+               (parse-token)
+               (errexit "right paren not found"))
+           sexpr)
+          (t
+           (errexit "expression not found")))))
+
+
+(defun parse-idlist ()
+  ; similar to parse-parms, but simpler because no keywords and default vals
+  (let (parms parm kargs expecting)
+    (if (token-is :lp) (parse-token) ;; eat the left paren
+        (errexit "expected left parenthesis"))
+    (setf expecting (not (token-is :rp)))
+    (while expecting
+      (if (token-is :id)
+          (push (token-lisp (parse-token)) parms)
+          (errexit "expected variable name"))
+      (if (token-is :co) (parse-token)
+          (setf expecting nil)))
+    (if (token-is :rp) (parse-token)
+        (errexit "expected right parenthesis"))
+    (reverse parms)))
+
+
+(defun parse-term ()
+  (let ((term (parse-term-1)))
+    ; (display "parse-term" term (token-is :lb))
+    (while (token-is :lb)
+      (parse-token)
+      (setf term (list 'aref term (parse-sexpr)))
+      (if (token-is :rb)
+          (parse-token)
+          (errexit "right bracket not found")))
+    term))
+
+
+(defun parse-ifexpr ()
+  (or (token-is :?) (error "parse-ifexpr internal error"))
+  (let (condition then-sexpr else-sexpr)
+    (parse-token) ;  skip the :?
+    (if (token-is :lp) (parse-token) (errexit "expected left paren"))
+    (setf condition (parse-sexpr))
+    (if (token-is :co) (parse-token) (errexit "expected comma"))
+    (setf then-sexpr (parse-sexpr))
+    (if (token-is :co) (parse-token) (errexit "expected comma"))
+    (setf else-sexpr (parse-sexpr))
+    (if (token-is :rp) (parse-token) (errexit "expected left paren"))
+    (list 'if condition then-sexpr else-sexpr)))
+
+
+(defun keywordp (s)
+  (and (symbolp s) (eq (type-of (symbol-name s)) 'string)
+       (equal (char (symbol-name s) 0) #\:)))
+
+
+(defun functionp (x) (eq (type-of x) 'closure))
+
+
+(defun parse-pargs (keywords-allowed)
+  ;; get a list of sexprs. If keywords-allowed, then at any point
+  ;; the arg syntax can switch from [<co> <sexpr>]* to
+  ;; [<co> <keyword> <sexpr>]*
+  ;; also if keywords-allowed, it's a function call and the
+  ;; list may be empty. Otherwise, it's a list of indices and
+  ;; the list may not be empty
+  (let (pargs keyword-expected sexpr keyword)
+   (if (and keywords-allowed (token-is :rp))
+       nil ; return empty parameter list
+       (loop ; look for one or more [keyword] sexpr
+         ; optional keyword test
+         (setf keyword nil)
+         ; (display "pargs" (car *sal-tokens*))
+         (if (token-is :key)
+             (setf keyword (token-lisp (parse-token))))
+         ; (display "parse-pargs" keyword)
+         ; did we need a keyword?
+         (if (and keyword-expected (not keyword))
+             (errexit "expected keyword"))
+         ; was a keyword legal
+         (if (and keyword (not keywords-allowed))
+             (errexit "keyword not allowed here"))
+         (setf keyword-expected keyword) ; once we get a keyword, we need
+                                         ; one before each sexpr
+         ; now find sexpr
+         (setf sexpr (parse-sexpr))
+         (if keyword (push keyword pargs))
+         (push sexpr pargs)
+         ; (display "parse-pargs" keyword sexpr pargs)
+         (cond ((token-is :co)
+                (parse-token))
+               (t
+                (return (reverse pargs))))))))
+
+
+;; PARSE-LIST -- parse list in braces {}, return list not quoted list
+;;
+(defun parse-list ()
+  (or (token-is :lc) (error "parse-list internal error"))
+  (let (elts)
+    (parse-token)
+    (while (not (token-is :rc))
+           (cond ((token-is '(:int :float :id :bool :key :string))
+                  (push (token-lisp (parse-token)) elts))
+                 ((token-is *sal-operators*)
+                  (push (intern (token-string (parse-token))) elts))
+                 ((token-is :lc)
+                  (push (parse-list) elts))
+                 ((token-is :co)
+                  (errexit "expected list element or right brace; do not use commas inside braces {}"))
+                 (t
+                  (errexit "expected list element or right brace"))))
+    (parse-token)
+    (reverse elts)))
+
+
+(defparameter *op-weights*
+  '(
+    (:\| 1)
+    (:& 2)
+    (:! 3)
+    (:= 4)
+    (:!= 4)
+    (:> 4)
+    (:>= 4)
+    (:< 4)
+    (:<= 4)
+    (:~= 4) ; general equality
+    (:+ 5)
+    (:- 5)
+    (:% 5)
+    (:* 6)
+    (:/ 6)
+    (:^ 7)
+    (:~ 8)
+    (:~~ 8)
+    (:@ 8)
+    (:@@ 8)))
+
+
+(defun is-op? (x)
+  ;; return op weight if x is operator
+  (let ((o (assoc (if (listp x) (token-type x) x)
+                 *op-weights*)))
+    (and o (cadr o))))
+
+
+(defun inf->pre (inf)
+  ;; this does NOT rewrite subexpressions because parser applies rules
+  ;; depth-first so subexprs are already processed
+  (let (op lh rh w1)
+    (if (consp inf)
+        (do ()
+            ((null inf) lh)
+          (setq op (car inf))                ; look at each element of in
+          (pop inf)
+          (setq w1 (is-op? op))
+          (cond ((numberp w1)                ; found op (w1 is precedence)
+                 (do ((w2 nil)
+                      (ok t)
+                      (li (list)))
+                     ((or (not inf) (not ok))
+                      (setq rh (inf->pre (nreverse li)))
+                      (setq lh (if lh (list (get-lisp-op op) lh rh)
+                                   (list (get-lisp-op op) rh nil))))
+                   (setq w2 (is-op? (first inf)))
+                   (cond ((and w2 (<= w2 w1))
+                          (setq ok nil))
+                         (t
+                          (push (car inf) li)
+                          (pop inf)))))
+                (t
+                 (setq lh op))))
+        inf)))
+
diff --git a/Release/nyquist/sal.lsp b/Release/nyquist/sal.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..cbb451b1f268ad81a26ee0641316dcdf2f4c9689
--- /dev/null
+++ b/Release/nyquist/sal.lsp
@@ -0,0 +1,630 @@
+;;; **********************************************************************
+;;; Copyright (C) 2006 Rick Taube
+;;; This program is free software; you can redistribute it and/or   
+;;; modify it under the terms of the Lisp Lesser Gnu Public License.
+;;; See http://www.cliki.net/LLGPL for the text of this agreement.
+;;; **********************************************************************
+
+;;; $Revision: 1.2 $
+;;; $Date: 2009-03-05 17:42:25 $
+
+;; DATA STRUCTURES AND ALGORITHMS (for sal.lsp and parse.lsp)
+;;
+;; TOKENIZE converts source language (a string) into a list of tokens
+;;    each token is represented as follows:
+;;    (:TOKEN <type> <string> <start> <info> <lisp>)
+;;    where <type> is one of:
+;;        :id -- an identifier
+;;        :lp -- left paren
+;;        :rp -- right paren
+;;        :+, etc. -- operators
+;;        :int -- an integer
+;;        :float -- a float
+;;        :print, etc. -- a reserved word
+;;    <string> is the source string for the token
+;;    <start> is the column of the string
+;;    <info> and <lisp> are ??
+;; Tokenize uses a list of reserved words extracted from terminals in
+;;    the grammar. Each reserved word has an associated token type, but
+;;    all other identifiers are simply of type :ID.
+;;
+;; *** WHY REWRITE THE ORIGINAL PARSER? ***
+;; Originally, the code interpreted a grammar using a recursive pattern
+;; matcher, but XLISP does not have a huge stack and there were
+;; stack overflow problems because even relatively small expressions
+;; went through a very deep nesting of productions. E.g. 
+;; "print note(between(30,odds(.5, 60, 90)))" 0 t nil))" was at recursion
+;; level 46 when the stack overflowed. The stack depth is 2000 or 4000,
+;; but all locals and parameters get pushed here, so since PARSE is the
+;; recursive function and it has lots of parameters and locals, it appears
+;; to use 80 elements in the stack per call.
+;; *** END ***
+;;
+;; The grammar for the recursive descent parser:
+;;   note: [ <x> ] means optional <x>, <x>* means 0 or more of <x>
+;;
+;; <number> = <int> | <float>
+;; <atom> = <int> | <float> | <id> | <bool>
+;; <list> = { <elt>* }
+;; <elt> = <atom> | <list> | <string>
+;; <aref> = <id> <lb> <pargs> <rb>
+;; <ifexpr> = ? "(" <sexpr> , <sexpr> [ , <sexpr> ] ")"
+;; <funcall> = <id> <funargs>
+;; <funargs> = "(" [ <args> ] ")"
+;; <args> =  <arg> [ , <arg> ]*
+;; <arg> = <sexpr> | <key> <sexpr>
+;; <op> = + | - | "*" | / | % | ^ | = | != |
+;;        "<" | ">" | "<=" | ">=" | ~= | ! | & | "|"
+;; <mexpr> = <term> [ <op> <term> ]*
+;; <term> = <-> <term> | <!> <term> | "(" <mexpr> ")" |
+;;          <ifexpr> | <funcall> | <aref> | <atom> | <list> | <string>
+;; <sexpr> = <mexpr> | <object> | class
+;; <top> = <command> | <block> | <conditional> | <assignment> | <loop> | <exec>
+;; <exec> = exec <sexpr>
+;; <command> = <define-cmd> | <file-cmd> | <output>
+;; <define-cmd> = define <declaration>
+;; <declaration> = <vardecl> | <fundecl>
+;; <vardecl> = variable <bindings>
+;; <bindings> = <bind> [ , <bind> ]*
+;; <bind> = <id> [ <=> <sexpr> ]
+;; <fundecl> = <function> <id> "(" [ <parms> ] ")" <statement>
+;; <parms> = <parm> [ , <parm> ]*
+;;  this is new: key: expression for keyword parameter
+;; <parm> = <id> | <key> [ <sexpr> ] 
+;; <statement> = <block> | <conditional> | <assignment> |
+;;               <output-stmt> <loop-stmt> <return-from> | <exec>
+;; <block> = begin [ with <bindings> [ <statement> ]* end
+;; <conditional> = if <sexpr> then [ <statement> ] [ else <statement> ] |
+;;                 when <sexpr> <statement> | unless <sexpr> <statement>
+;; <assignment> = set <assign> [ , <assign> ]*
+;; <assign> = ( <aref> | <id> ) <assigner> <sexpr>
+;; <assigner> = = | += | *= | &= | @= | ^= | "<=" | ">="
+;; <file-cmd> = <load-cmd> | chdir <pathref> | 
+;;              system <pathref> | play <sexpr>
+;; (note: system was removed)
+;; <load-cmd> = load <pathref> [ , <key> <sexpr> ]* 
+;; <pathref> = <string> | <id>
+;; <output-stmt> = print <sexpr> [ , <sexpr> ]* |
+;;                 output <sexpr>
+;; <loop-stmt> = loop [ with <bindings> ] [ <stepping> ]* 
+;;               [ <termination> ]* [ <statement> ]+
+;;               [ finally <statement> ] end
+;; <stepping> = repeat <sexpr> |
+;;              for <id> = <sexpr> [ then <sexpr> ] |
+;;              for <id> in <sexpr> |
+;;              for <id> over <sexpr> [ by <sexpr> ] |
+;;              for <id> [ from <sexpr> ]
+;;                       [ ( below | to | above | downto ) <sexpr> ]
+;;                       [ by <sexpr> ] |
+;; <termination> = while <sexpr> | until <sexpr>
+;; <return-from> = return <sexpr>
+
+;(in-package cm)
+
+; (progn (cd "/Lisp/sal/") (load "parse.lisp") (load "sal.lisp"))
+
+(setfn defconstant setf)
+(setfn defparameter setf)
+(setfn defmethod defun)
+(setfn defvar setf)
+(setfn values list)
+(if (not (boundp '*sal-secondary-prompt*))
+    (setf *sal-secondary-prompt* t))
+(if (not (boundp '*sal-xlispbreak*))
+    (setf *sal-xlispbreak* nil))
+
+(defun sal-trace-enter (fn &optional argvals argnames)
+  (push (list fn *sal-line* argvals argnames) *sal-call-stack*))
+
+(defun sal-trace-exit ()
+  (setf *sal-line* (second (car *sal-call-stack*)))
+  (pop *sal-call-stack*))
+
+;; SAL-RETURN-FROM is generated by Sal compiler and
+;;  performs a return as well as a sal-trace-exit()
+;;
+(defmacro sal-return-from (fn val)
+  `(prog ((sal:return-value ,val))
+     (setf *sal-line* (second (car *sal-call-stack*)))
+     (pop *sal-call-stack*)
+     (return-from ,fn sal:return-value)))
+
+
+(setf *sal-traceback* t)
+
+
+(defun sal-traceback (&optional (file t) 
+                      &aux comma name names line)
+  (format file "Call traceback:~%")
+  (setf line *sal-line*)
+  (dolist (frame *sal-call-stack*)
+    (setf comma "")
+    (format file "    ~A" (car frame))
+    (cond ((symbolp (car frame))
+           (format file "(")
+           (setf names (cadddr frame))
+           (dolist (arg (caddr frame))
+             (setf name (car names))
+             (format file "~A~%        ~A = ~A" comma name arg)
+             (setf names (cdr names))
+             (setf comma ","))
+           (format file ") at line ~A~%" line)
+           (setf line (second frame)))
+          (t 
+           (format file "~%")))))
+
+
+'(defmacro defgrammer (sym rules &rest args)
+  `(defparameter ,sym
+     (make-grammer :rules ',rules ,@args)))
+
+'(defun make-grammer (&key rules literals)
+  (let ((g (list 'a-grammer rules literals)))
+    (grammer-initialize g)
+    g))
+
+'(defmethod grammer-initialize (obj)
+  (let (xlist)
+    ;; each literal is (:name "name")
+    (cond ((grammer-literals obj)
+           (dolist (x (grammer-literals obj))
+             (cond ((consp x)
+                    (push x xlist))
+                   (t
+                    (push (list (string->keyword (string-upcase (string x)))
+                                (string-downcase (string x)))
+                          xlist)))))
+          (t
+           (dolist (x (grammer-rules obj))
+             (cond ((terminal-rule? x)
+                    (push (list (car x)
+                                (string-downcase (subseq (string (car x)) 1)))
+                          xlist))))))
+    (set-grammer-literals obj (reverse xlist))))
+
+'(setfn grammer-rules cadr)
+'(setfn grammer-literals caddr)
+'(defun set-grammer-literals (obj val)
+  (setf (car (cddr obj)) val))
+'(defun is-grammer (obj) (and (consp obj) (eq (car obj) 'a-grammer)))
+
+(defun string->keyword (str)
+  (intern (strcat ":" (string-upcase str))))
+
+(defun terminal-rule? (rule)
+  (or (null (cdr rule)) (not (cadr rule))))
+
+(load "sal-parse.lsp" :verbose nil)
+
+(defparameter *sal-print-list* t)
+
+(defun sal-printer (x &key (stream *standard-output*) (add-space t)
+                           (in-list nil))
+  (let ((*print-case* ':downcase))
+    (cond ((and (consp x) *sal-print-list*)
+	   (write-char #\{ stream)
+	   (do ((items x (cdr items)))
+               ((null items))
+	      (sal-printer (car items) :stream stream
+                                       :add-space (cdr items) :in-list t)
+	      (cond ((cdr items)
+                     (cond ((not (consp (cdr items)))
+                            (princ "<list not well-formed> " stream)
+                            (sal-printer (cdr items) :stream stream :add-space nil)
+                            (setf items nil))))))
+	   (write-char #\} stream))
+	  ((not x)     (princ "#f" stream) )
+	  ((eq x t)    (princ "#t" stream))
+          (in-list     (prin1 x stream))
+	  (t           (princ x stream)))
+    (if add-space (write-char #\space stream))))
+
+(defparameter *sal-printer* #'sal-printer)
+
+(defun sal-message (string &rest args)
+  (format t "~&; ")
+  (apply #'format t string args))
+
+
+;; sal-print has been modified from the original SAL to print items separated
+;; by spaces (no final trailing space) and followed by a newline.
+(defun sal-print (&rest args)
+  (do ((items args (cdr items)))
+       ((null items))
+     ;; add space unless we are at the last element
+     (funcall *sal-printer* (car items) :add-space (cdr items)))
+  (terpri)
+  (values))
+
+(defmacro keyword (sym)
+  `(str-to-keyword (symbol-name ',sym)))
+
+(defun plus (&rest nums)
+  (apply #'+ nums))
+
+(defun minus (num &rest nums)
+  (apply #'- num nums))
+
+(defun times (&rest nums)
+  (apply #'* nums))
+
+(defun divide (num &rest nums)
+  (apply #'/ num nums))
+
+;; implementation of infix "!=" operator
+(defun not-eql (x y)
+  (not (eql x y)))
+
+; dir "*.*
+; chdir
+; load "rts.sys"
+
+(defun sal-chdir ( dir)
+  (cd (expand-path-name dir))
+  (sal-message "Directory: ~A" (pwd))
+  (values))
+
+;;; sigh, not all lisps support ~/ directory components.
+
+(defun expand-path-name (path &optional absolute?)
+  (let ((dir (pathname-directory path)))
+    (flet ((curdir ()
+	     (truename 
+	      (make-pathname :directory
+			     (pathname-directory
+			      *default-pathname-defaults*)))))
+      (cond ((null dir)
+	     (if (equal path "~") 
+		 (namestring (user-homedir-pathname))
+		 (if absolute? 
+		     (namestring (merge-pathnames path (curdir)))
+		     (namestring path))))
+	    ((eql (car dir) ':absolute)
+	     (namestring path))
+	    (t
+	     (let* ((tok (second dir))
+		    (len (length tok)))
+	       (if (char= (char tok 0) #\~)
+		   (let ((uhd (pathname-directory (user-homedir-pathname))))
+		     (if (= len 1)
+			 (namestring
+			  (make-pathname :directory (append uhd (cddr dir))
+					 :defaults path))
+			 (namestring
+			  (make-pathname :directory
+					 (append (butlast uhd)
+						 (list (subseq tok 1))
+						 (cddr dir))
+					 :defaults path))))
+		   (if absolute?
+		       (namestring (merge-pathnames  path (curdir)))
+		       (namestring path)))))))))
+
+
+(defun sal-load (filename &key (verbose t) print)
+  (progv '(*sal-input-file-name*) (list filename)
+    (prog (file extended-name)
+      ;; first try to load exact name
+      (cond ((setf file (open filename))
+             (close file) ;; found it: close it and load it
+             (return (generic-loader filename verbose print))))
+      ;; try to load name with ".sal" or ".lsp"
+      (cond ((string-search "." filename) ; already has extension
+             nil) ; don't try to add another extension
+            ((setf file (open (strcat filename ".sal")))
+             (close file)
+             (return (sal-loader (strcat filename ".sal")
+                                 :verbose verbose :print print)))
+            ((setf file (open (strcat filename ".lsp")))
+             (close file)
+             (return (lisp-loader filename :verbose verbose :print print))))
+      ;; search for file as is or with ".lsp" on path
+      (setf fullpath (find-in-xlisp-path filename))
+      (cond ((and (not fullpath) ; search for file.sal on path
+                  (not (string-search "." filename))) ; no extension yet
+             (setf fullpath (find-in-xlisp-path (strcat filename ".sal")))))
+      (cond ((null fullpath)
+             (format t "sal-load: could not find ~A~%" filename))
+            (t
+             (return (generic-loader fullpath verbose print)))))))
+
+
+;; GENERIC-LOADER -- load a sal or lsp file based on extension
+;;
+;; assumes that file exists, and if no .sal extension, type is Lisp
+;;
+(defun generic-loader (fullpath verbose print)
+  (cond ((has-extension fullpath ".sal")
+         (sal-loader fullpath :verbose verbose :print print))
+        (t
+         (lisp-loader fullpath :verbose verbose :print print))))
+
+#|
+(defun sal-load (filename &key (verbose t) print)
+  (progv '(*sal-input-file-name*) (list filename)
+    (let (file extended-name)
+      (cond ((has-extension filename ".sal")
+             (sal-loader filename :verbose verbose :print print))
+            ((has-extension filename ".lsp")
+             (lisp-load filename :verbose verbose :print print))
+            ;; see if we can just open the exact filename and load it
+            ((setf file (open filename))
+             (close file)
+             (lisp-load filename :verbose verbose :print print))
+            ;; if not, then try loading file.sal and file.lsp
+            ((setf file (open (setf *sal-input-file-name*
+                                    (strcat filename ".sal"))))
+             (close file)
+             (sal-loader *sal-input-file-name* :verbose verbose :print print))
+            ((setf file (open (setf *sal-input-file-name* 
+                                    (strcat filename ".lsp"))))
+             (close file)
+             (lisp-load *sal-input-file-name* :verbose verbose :print print))
+            (t
+             (format t "sal-load: could not find ~A~%" filename))))))
+|#
+
+(defun lisp-loader (filename &key (verbose t) print)
+  (if (load filename :verbose verbose :print print)
+      t ; be quiet if things work ok
+      (format t "error loading lisp file ~A~%" filename)))
+
+
+(defun has-extension (filename ext)
+  (let ((loc (string-search ext filename
+                            :start (max 0 (- (length filename)
+                                             (length ext))))))
+    (not (null loc)))) ; coerce to t or nil
+    
+
+(defmacro sal-at (s x) (list 'at x s))
+(defmacro sal-at-abs (s x) (list 'at-abs x s))
+(defmacro sal-stretch (s x) (list 'stretch x s))
+(defmacro sal-stretch-abs (s x) (list 'stretch-abs x s))
+
+;; splice every pair of lines
+(defun strcat-pairs (lines)
+  (let (rslt)
+    (while lines
+      (push (strcat (car lines) (cadr lines)) rslt)
+      (setf lines (cddr lines)))
+    (reverse rslt)))
+
+
+(defun strcat-list (lines)
+  ;; like (apply 'strcat lines), but does not use a lot of stack
+  ;; When there are too many lines, XLISP will overflow the stack
+  ;; because args go on the stack.
+  (let (r)
+    (while (> (setf len (length lines)) 1)
+      (if (oddp len) (setf lines (cons "" lines)))
+      (setf lines (strcat-pairs lines)))
+    ; if an empty list, return "", else list has one string: return it
+    (if (null lines) "" (car lines))))
+
+
+(defun sal-loader (filename &key verbose print)
+  (let ((input "") (file (open filename)) line lines)
+    (cond (file
+           (push filename *loadingfiles*)
+           (while (setf line (read-line file))
+            (push line lines)
+            (push "\n" lines))
+           (close file)
+           (setf input (strcat-list (reverse lines)))
+           (sal-trace-enter (strcat "Loading " filename))
+           (sal-compile input t t filename)
+           (pop *loadingfiles*)
+           (sal-trace-exit))
+          (t
+           (format t "error loading SAL file ~A~%" filename)))))
+
+
+; SYSTEM command is not implemented
+;(defun sal-system (sys &rest pairs)
+;  (apply #'use-system sys pairs))
+
+
+(defun load-sal-file (file)
+  (with-open-file (f file :direction :input)
+    (let ((input (make-array '(512) :element-type 'character
+			     :fill-pointer 0 :adjustable t)))
+      (loop with flag
+	 for char = (read-char f nil ':eof)
+	 until (or flag (eql char ':eof))
+	 do
+	   (when (char= char #\;)
+	     (loop do (setq char (read-char f nil :eof))
+		until (or (eql char :eof)
+			  (char= char #\newline))))
+	   (unless (eql char ':eof)
+	     (vector-push-extend char input)))
+      (sal input :pattern :command-sequence))))
+
+
+(defmacro sal-play (snd)
+  (if (stringp snd) `(play-file ,snd)
+                    `(play ,snd)))
+
+
+(if (not (boundp '*sal-compiler-debug*))
+    (setf *sal-compiler-debug* nil))
+
+
+(defmacro sal-simrep (variable iterations body)
+  `(simrep (,variable ,iterations) ,body))
+
+
+(defmacro sal-seqrep (variable iterations body)
+  `(seqrep (,variable ,iterations) ,body))
+
+
+;; function called in sal programs to exit the sal read-compile-run-print loop
+(defun sal-exit () (setf *sal-exit* t))
+
+(setf *sal-call-stack* nil)
+
+;; read-eval-print loop for sal commands
+(defun sal ()
+  (progv '(*breakenable* *tracenable* *sal-exit* *sal-mode*)
+         (list *sal-break* *xlisp-traceback* nil t)
+    (let (input line)
+      (setf *sal-call-stack* nil)
+      (read-line) ; read the newline after the one the user 
+                  ; typed to invoke this fn
+      (princ "Entering SAL mode ...\n");
+      (while (not *sal-exit*)
+        (princ "\nSAL> ")
+        (sal-trace-enter "SAL top-level command interpreter")
+        ;; get input terminated by two returns
+        (setf input "")
+        (while (> (length (setf line (read-line))) 0)
+          (if *sal-secondary-prompt* (princ " ... "))
+          (setf input (strcat input "\n" line)))
+        ;; input may have an extra return, remaining from previous read
+        ;; if so, trim it because it affects line count in error messages
+        (if (and (> (length input) 0) (char= (char input 0) #\newline))
+            (setf input (subseq input 1)))
+        (sal-compile input t nil "<console>")
+        (sal-trace-exit))
+      (princ "Returning to Lisp ...\n")))
+  ;; in case *xlisp-break* or *xlisp-traceback* was set from SAL, impose
+  ;; them here
+  (cond ((not *sal-mode*) 
+         (setf *breakenable* *xlisp-break*)
+         (setf *tracenable* *xlisp-traceback*)))
+  t)
+
+
+
+(defun sal-error-output (stack)
+  (if *sal-traceback* (sal-traceback))
+  (setf *sal-call-stack* stack)) ;; clear the stack
+
+
+;; when true, top-level return statement is legal and compiled into MAIN
+(setf *audacity-top-level-return-flag* nil)
+
+;; SAL-COMPILE-AUDACITY -- special treatment of RETURN
+;;
+;; This works like SAL-COMPILE, but if there is a top-level
+;; return statement (not normally legal), it is compiled into
+;; a function named MAIN. This is a shorthand for Audacity plug-ins
+;;
+(defun sal-compile-audacity (input eval-flag multiple-statements filename)
+  (progv '(*audacity-top-level-return-flag*) '(t)
+    (sal-compile input eval-flag multiple-statements filename)))
+
+
+;; SAL-COMPILE -- translate string or token list to lisp and eval
+;;
+;; input is either a string or a token list
+;; eval-flag tells whether to evaluate the program or return the lisp
+;; multiple-statements tells whether the input can contain multiple
+;;   top-level units (e.g. from a file) or just one (from command line)
+;; returns:
+;;   if eval-flag, then nothing is returned
+;;   otherwise, returns nil if an error is encountered
+;;   otherwise, returns a list (PROGN p1 p2 p3 ...) where pn are lisp
+;;      expressions
+;;
+;; Note: replaced local variables here with "local" names to avoid
+;; collisions with globals that compiled code might try to use:
+;; eval uses local bindings, not global ones
+;;
+(defun sal-compile (sal:input sal:evflag sal:mult-stmts sal:filename)
+  ;; save some globals because eval could call back recursively
+  (progv '(*sal-tokens* *sal-input* *sal-input-text*) '(nil nil nil)
+    (let (sal:output sal:remainder sal:rslt sal:stack)
+      (setf sal:stack *sal-call-stack*)
+      ;; if first input char is "(", then eval as a lisp expression:
+      ;(display "sal-compile" sal:input)(setf *sal-compiler-debug* t)
+      (cond ((input-starts-with-open-paren sal:input)
+             ;(print "sal:input is lisp expression")
+             (errset
+              (print (eval (read (make-string-input-stream sal:input)))) t))
+            (t ;; compile SAL expression(s):
+             (loop
+                (setf sal:output (sal-parse nil nil sal:input sal:mult-stmts 
+                                        sal:filename))
+                (cond ((first sal:output) ; successful parse
+                       (setf sal:remainder *sal-tokens*)
+                       (setf sal:output (second sal:output))
+                       (when *sal-compiler-debug*
+                         (terpri)
+                         (pprint sal:output))
+                       (cond (sal:evflag ;; evaluate the compiled code
+                              (cond ((null (errset (eval sal:output) t))
+                                     (sal-error-output sal:stack)
+                                     (return)))) ;; stop on error
+                             (t
+                              (push sal:output sal:rslt)))
+                                        ;(display "sal-compile after eval" 
+                                        ;         sal:remainder *sal-tokens*)
+                       ;; if there are statements left over, maybe compile again
+                       (cond ((and sal:mult-stmts sal:remainder)
+                              ;; move sal:remainder to sal:input and iterate
+                              (setf sal:input sal:remainder))
+                             ;; see if we've compiled everything
+                             ((and (not sal:evflag) (not sal:remainder))
+                              (return (cons 'progn (reverse sal:rslt))))
+                             ;; if eval but no more sal:input, return
+                             ((not sal:remainder)
+                              (return))))
+                      (t ; error encountered
+                       (return)))))))))
+
+;; SAL just evaluates lisp expression if it starts with open-paren,
+;; but sometimes reader reads previous newline(s), so here we
+;; trim off initial newlines and check if first non-newline is open-paren
+(defun input-starts-with-open-paren (input)
+  (let ((i 0))
+    (while (and (stringp input)
+                (> (length input) i)
+                (eq (char input i) #\newline))
+      (incf i))
+    (and (stringp input)
+         (> (length input) i)
+         (eq (char input i) #\())))
+
+(defun sal-list-equal (a b)
+  (let ((rslt t)) ;; set to false if any element not equal
+    (dolist (x a) 
+      (if (sal-equal x (car b))
+          t ;; continue comparing
+          (return (setf rslt nil))) ;; break out of loop
+      (setf b (cdr b)))
+    (and rslt (null b)))) ;; make sure no leftovers in b
+
+
+(defun sal-plus(a b &optional (source "+ operation in SAL"))
+  (ny:typecheck (not (or (numberp a) (soundp a) (multichannel-soundp a)))
+    (ny:error source 0 number-sound-anon a t))
+  (ny:typecheck (not (or (numberp b) (soundp b) (multichannel-soundp b)))
+    (ny:error source 0 number-sound-anon b t))
+  (nyq:add2 a b))
+
+
+(defun sal-equal (a b)
+  (or (and (numberp a) (numberp b) (= a b)) 
+      (and (consp a) (consp b) (sal-list-equal a b))
+      (equal a b)))
+
+(defun not-sal-equal (a b)
+  (not (sal-equal a b)))
+
+(defun sal-list-about-equal (a b)
+  (let ((rslt t)) ;; set to false if any element not equal
+    (dolist (x a) 
+      (if (sal-about-equal x (car b))
+          t ;; continue comparing
+          (return (setf rslt nil))) ;; break out of loop
+      (setf b (cdr b)))
+    (and rslt (null b)))) ;; make sure no leftovers in b
+
+(setf *~=tolerance* 0.000001)
+
+(defun sal-about-equal (a b)
+  (or (and (numberp a) (numberp b) (< (abs (- a b)) *~=tolerance*))
+      (and (consp a) (consp b) (sal-list-about-equal a b))
+      (equal a b)))
diff --git a/Release/nyquist/seq.lsp b/Release/nyquist/seq.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..d360256057e98a2ae65ae008424961bdf4a3926d
--- /dev/null
+++ b/Release/nyquist/seq.lsp
@@ -0,0 +1,336 @@
+;; seq.lsp -- sequence control constructs for Nyquist
+
+;; get-srates -- this either returns the sample rate of a sound or a
+;;   vector of sample rates of a vector of sounds
+;;
+(defun get-srates (sounds)
+  (cond ((arrayp sounds)
+         (let ((result (make-array (length sounds))))
+           (dotimes (i (length sounds))
+                    (setf (aref result i) (snd-srate (aref sounds i))))
+           result))
+        (t
+         (snd-srate sounds))))
+
+; These are complex macros that implement sequences of various types.
+; The complexity is due to the fact that a behavior within a sequence
+; can reference the environment, e.g. (let ((p 60)) (seq (osc p) (osc p)))
+; is an example where p must be in the environment of each member of
+; the sequence.  Since the execution of the sequence elements are delayed,
+; the environment must be captured and then used later.  In XLISP, the
+; EVAL function does not execute in the current environment, so a special
+; EVAL, EVALHOOK must be used to evaluate with an environment.  Another
+; feature of XLISP (see evalenv.lsp) is used to capture the environment
+; when the seq is first evaluated, so that the environment can be used
+; later.  Finally, it is also necessary to save the current transformation
+; environment until later.
+;
+; The SEQ implementation passes an environment through closures that
+; are constructed to evaluate expressions. SEQREP is similar, but
+; the loop variable must be incremented and tested.
+;
+; Other considerations are that SEQ can handle multi-channel sounds, but
+; we don't know to call the snd_multiseq primitive until the first
+; SEQ expression is evaluated. Also, there's no real "NIL" for the end
+; of a sequence, so we need several special cases: (1) The sequences
+; is empty at the top level, so return silence, (2) There is one
+; expression, so just evaluate it, (3) there are 2 expressions, so 
+; return the first followed by the second, (4) there are more than
+; 2 expressions, so return the first followed by what is effectively
+; a SEQ consisting of the remaining expressions.
+
+
+;; SEQ-EXPR-EXPAND - helper function, expands expression to push/pop entry 
+;;    on *sal-call-stack* to help debug calls into SAL from lazy evaluation
+;;    of SAL code by SEQ
+(defun seq-expr-expand (expr source)
+  (if *sal-call-stack*
+    `(prog2 (sal-trace-enter '(,(strcat "Expression in " source ":") ,expr))
+            ,expr ;; here is where the seq behavior is evaluated
+            (sal-trace-exit))
+    expr))
+
+
+(defun with%environment (env expr)
+  ;; (progv (var1 ...) (val1 ...) expression-list)
+  `(progv ',*environment-variables* ,env ,expr))
+;(trace with%environment seq-expr-expand)
+
+(defmacro eval-seq-behavior (beh source)
+  ;(tracemacro 'eval-seq-behavior (list beh source)
+  (seq-expr-expand (with%environment 'nyq%environment
+                      `(at-abs t0
+                               (force-srates s%rate ,beh))) source));)
+
+;; Previous implementations grabbed the environment and passed it from
+;; closure to closure so that each behavior in the sequence could be
+;; evaluated in the saved environment using an evalhook trick. This
+;; version precomputes closures, which avoids using evalhook to get or
+;; use the environment. It's still tricky, because each behavior has
+;; to pass to snd-seq a closure that computes the remaining behavior
+;; sequence. To do this, I use a recursive macro to run down the
+;; behavior sequence, then as the recursion unwinds, construct nested
+;; closures that all capture the current environment. We end up with a
+;; closure we can apply to the current time to get a sound to return.
+;;
+(defmacro seq (&rest behlist)
+  ;; if we have no behaviors, return zero
+  (cond ((null behlist)
+         '(snd-zero (local-to-global 0) *sound-srate*))
+        (t  ; we have behaviors. Must evaluate one to see if it is multichan:
+         `(let* ((first%sound ,(seq-expr-expand (car behlist) "SEQ"))
+                 (s%rate (get-srates first%sound))
+                 (nyq%environment (nyq:the-environment)))
+            ; if there's just one behavior, we have it and we're done:
+            ,(progn (setf behlist (cdr behlist))
+                    (if (null behlist) 'first%sound
+                        ; otherwise, start the recursive construction:
+                        `(if (arrayp first%sound)
+                             (seq2-deferred snd-multiseq ,behlist)
+                             (seq2-deferred snd-seq ,behlist))))))))
+
+
+;; seq2-deferred uses seq2 and seq3 to construct nested closures for
+;; snd-seq. It is deferred so that we can first (in seq) determine whether
+;; this is a single- or multi-channel sound before recursively constructing
+;; the closures, since we only want to do it for either snd-seq or
+;; snd-multiseq, but not both. It simply calls seq2 to begin the expansion.
+;;
+(defmacro seq2-deferred (seq-prim behlist)
+  (seq2 seq-prim behlist))
+
+
+#|
+;; for debugging, you can replace references to snd-seq with this
+(defun snd-seq-trace (asound aclosure)
+  (princ "Evaluating SND-SEQ-TRACE instead of SND-SEQ...\n")
+  (format t "  Sound argument is ~A\n" asound)
+  (princ "  Closure argument is:\n")
+  (pprint (get-lambda-expression aclosure))
+  (princ "  Calling SND-SEQ ...\n")
+  (let ((s (snd-seq asound aclosure)))
+    (format t "  SND-SEQ returned ~A\n" s)
+    s))
+
+;; also for debugging, you can uncomment some tracemacro wrappers from
+;; macro definitions. This function prints what the macro expands to
+;; along with name and args (which you add by hand to the call):
+(defun tracemacro (name args expr)
+  (format t "Entered ~A with args:\n" name)
+  (pprint args)
+  (format t "Returned from ~A with expression:\n" name)
+  (pprint expr)
+  expr)
+|#
+
+  
+;; we have at least 2 behaviors so we need the top level call to be
+;; a call to snd-multiseq or snd-seq. This macro constructs the call
+;; and uses recursion with seq3 to construct the remaining closures.
+;;
+(defun seq2 (seq-prim behlist)
+  `(,seq-prim first%sound
+              (prog1 ,(seq3 seq-prim behlist)  ; <- passed to seq-prim
+                     ;; we need to remove first%sound from the closure
+                     ;; to avoid accumulating samples due to an unnecessary
+                     ;; reference:
+                     (setf first%sound nil))))
+
+;; construct a closure that evaluates to a sequence of behaviors.
+;; behlist has at least one behavior in it.
+;;
+(defun seq3 (seq-prim behlist)
+  `(lambda (t0)
+     (setf first%sound (eval-seq-behavior ,(car behlist) "SEQ"))
+     ,(progn (setf behlist (cdr behlist))
+             (if (null behlist) 'first%sound
+                 (seq2 seq-prim behlist)))))
+
+
+; we have to use the real loop variable name since it could be
+; referred to by the sound expression, so we avoid name collisions
+; by using % in all the macro variable names
+;
+(defmacro seqrep (loop-control snd-expr)
+  ;(tracemacro "SEQREP" (list loop-control snd-expr)
+  `(let ((,(car loop-control) 0)
+         (loop%count ,(cadr loop-control))
+         (nyq%environment (nyq:the-environment))
+         s%rate seqrep%closure)
+     ; note: s%rate will tell whether we want a single or multichannel
+     ; sound, and what the sample rates should be.
+     (cond ((not (integerp loop%count))
+            (error "bad argument type" loop%count))
+           ((< loop%count 1)
+            (snd-zero (local-to-global 0) *sound-srate*))
+           ((= loop%count 1)
+            ,snd-expr)
+           (t ; more than 1 iterations
+            (setf loop%count (1- loop%count))
+            (setf first%sound ,snd-expr)
+            (setf s%rate (get-srates first%sound))
+            (setf nyq%environment (nyq:the-environment))
+            (if (arrayp first%sound)
+                (seqrep2 snd-multiseq ,loop-control ,snd-expr)
+                (seqrep2 snd-seq ,loop-control ,snd-expr))))));)
+
+
+(defmacro seqrep2 (seq-prim loop-control snd-expr)
+  ;(tracemacro "SEQREP2" (list seq-prim loop-control snd-expr)
+  `(progn (setf seqrep%closure
+                (lambda (t0) ,(seqrep-iterate seq-prim loop-control snd-expr)))
+          (,seq-prim (prog1 first%sound (setf first%sound nil))
+                     seqrep%closure)));)
+
+
+(defun seqrep-iterate (seq-prim loop-control snd-expr)
+  (setf snd-expr `(eval-seq-behavior ,snd-expr "SEQREP"))
+  `(progn
+     (setf ,(car loop-control) (1+ ,(car loop-control))) ; incr. loop counter
+     (if (>= ,(car loop-control) loop%count) ; last iteration 
+         ,snd-expr
+         (,seq-prim ,snd-expr seqrep%closure))))
+
+
+;; TRIGGER - sums instances of beh which are launched when input becomes
+;;     positive (> 0). New in 2021: input is resampled to *sound-srate*.
+;;     As before, beh sample rates must match, so now they must also be
+;;     *sound-srate*. This implementation uses eval-seq-behavior to create
+;;     a more helpful stack trace for SAL.
+(defmacro trigger (input beh)
+  `(let* ((nyq%environment (nyq:the-environment))
+          (s%rate *sound-srate*))
+     (snd-trigger (force-srate *sound-srate* ,input)
+                  #'(lambda (t0) (eval-seq-behavior ,beh "TRIGGER")))))
+
+
+;; EVENT-EXPRESSION -- the sound of the event
+;;
+(setfn event-expression caddr)
+
+
+;; EVENT-HAS-ATTR -- test if event has attribute
+;;
+(defun event-has-attr (note attr)
+  (expr-has-attr (event-expression note)))
+
+
+;; EXPR-SET-ATTR -- new expression with attribute = value
+;;
+(defun expr-set-attr (expr attr value)
+  (cons (car expr) (list-set-attr-value (cdr expr) attr value)))
+
+(defun list-set-attr-value (lis attr value)
+  (cond ((null lis) (list attr value))
+        ((eq (car lis) attr)
+         (cons attr (cons value (cddr lis))))
+        (t
+         (cons (car lis)
+           (cons (cadr lis) 
+                 (list-set-attr-value (cddr lis) attr value))))))
+
+
+;; EXPAND-AND-EVAL-EXPR -- evaluate a note, chord, or rest for timed-seq
+;;
+(defun expand-and-eval-expr (expr)
+  (let ((pitch (member :pitch expr)))
+    (cond ((and pitch (cdr pitch) (listp (cadr pitch)))
+           (setf pitch (cadr pitch))
+           (simrep (i (length pitch))
+             (eval (expr-set-attr expr :pitch (nth i pitch)))))
+          (t
+           (eval expr)))))
+
+
+;; (timed-seq '((time1 stretch1 expr1) (time2 stretch2 expr2) ...))
+;; a timed-seq takes a list of events as shown above
+;; it sums the behaviors, similar to 
+;;     (sim (at time1 (stretch stretch1 expr1)) ...)
+;; but the implementation avoids starting all expressions at once
+;; 
+;; Notes: (1) the times must be in increasing order
+;;   (2) EVAL is used on each event, so events cannot refer to parameters
+;;        or local variables
+;;
+;; If score events are very closely spaced (< 1020 samples), the block
+;; overlap can cause a ripple effect where to complete one block of the
+;; output, you have to compute part of the next score event, but then
+;; it in turn computes part of the next score event, and so on, until
+;; the stack overflows (if you have 1000's of events).
+;;
+;; This is really a fundamental problem in Nyquist because blocks are
+;; not aligned. To work around the problem (but not totally solve it)
+;; scores are evaluated up to a length of 100. If there are more than
+;; 100 score events, we form a balanced tree of adders so that maybe
+;; we will end up with a lot of sound in memory, but at least the
+;; stack will not overflow. Generally, we should not end up with more
+;; than 100 times as many blocks as we would like, but since the
+;; normal space required is O(1), we're still using constant space +
+;; a small constant * log(score-length).
+;;
+(setf MAX-LINEAR-SCORE-LEN 100)
+(defun timed-seq (score)
+  (must-be-valid-score "TIMED-SEQ" score)
+  (let ((len (length score))
+        pair)
+    (cond ((< len MAX-LINEAR-SCORE-LEN)
+           (timed-seq-linear score))
+          (t ;; split the score -- divide and conquer
+           (setf pair (score-split score (/ len 2)))
+           (sum (timed-seq (car pair)) (timed-seq (cdr pair)))))))
+
+;; score-split -- helper function: split score into two, with n elements
+;;                in the first part; returns a dotted pair
+(defun score-split (score n)
+  ;; do the split without recursion to avoid stack overflow
+  ;; algorithm: modify the list destructively to get the first
+  ;; half. Copy it. Reassemble the list.
+  (let (pair last front back)
+    (setf last (nthcdr (1- n) score))
+    (setf back (cdr last))
+    (rplacd last nil)
+    (setf front (append score nil)) ; shallow copy
+    (rplacd last back)
+    (cons front back)))
+
+ 
+;; TIMED-SEQ-LINEAR - check to insure that times are strictly increasing
+;;                    and >= 0 and stretches are >= 0
+(defun timed-seq-linear (score)
+  (let ((start-time 0) error-msg rslt)
+    (dolist (event score)
+      (cond ((< (car event) start-time)
+             (error (format nil
+                     "Out-of-order time in TIMED-SEQ: ~A, consider using SCORE-SORT"
+                     event)))
+            ((< (cadr event) 0)
+             (error (format nil "Negative stretch factor in TIMED-SEQ: ~A" event)))
+            (t
+             (setf start-time (car event)))))
+    ;; remove rests (a rest has a :pitch attribute of nil)
+    (setf score (score-select score #'(lambda (tim dur evt)
+                                       (expr-get-attr evt :pitch t))))
+    (cond ((and score (car score) 
+                (eq (car (event-expression (car score))) 'score-begin-end))
+           (setf score (cdr score)))) ; skip score-begin-end data
+    (cond ((null score) (s-rest 0))
+          (t
+           (at (caar score)
+               (seqrep (i (length score))
+                 (progn
+                   (cond (*sal-call-stack*
+                          (sal-trace-enter (list "Score event:" (car score)) nil nil)
+                          (setf *sal-line* 0)))
+                   (setf rslt
+                     (cond ((cdr score)
+                            (prog1
+                              (set-logical-stop
+                                (stretch (cadar score)
+                                  (expand-and-eval-expr (caddar score)))
+                                (- (caadr score) (caar score)))
+                              (setf score (cdr score))))
+                           (t
+                            (stretch (cadar score) (expand-and-eval-expr
+                                                    (caddar score))))))
+                   (if *sal-call-stack* (sal-trace-exit))
+                   rslt)))))))
diff --git a/Release/nyquist/seqfnint.lsp b/Release/nyquist/seqfnint.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..1f7b01bdee33ce33626bb32cf72c5bab583ab3fc
--- /dev/null
+++ b/Release/nyquist/seqfnint.lsp
@@ -0,0 +1,31 @@
+
+    (setfn seq-tag first)
+    (setfn seq-time second)
+    (setfn seq-line third)
+    (setfn seq-channel fourth)
+    (defun seq-value1 (e) (nth 4 e))
+    (setfn seq-pitch seq-value1) ; pitch of a note
+    (setfn seq-control seq-value1) ; control number of a control change
+    (setfn seq-program seq-value1) ; program number of a program change
+    (setfn seq-bend seq-value1) ; pitch bend amount
+    (setfn seq-touch seq-value1) ; aftertouch amount
+    (defun seq-value2 (e) (nth 5 e))
+    (setfn seq-velocity seq-value2) ; velocity of a note
+    (setfn seq-value seq-value2) ; value of a control change
+    (defun seq-duration (e) (nth 6 e))
+    
+
+ (setf seq-done-tag 0) 
+
+ (setf seq-other-tag 1) 
+
+ (setf seq-note-tag 2) 
+
+ (setf seq-ctrl-tag 3) 
+
+ (setf seq-prgm-tag 4) 
+
+ (setf seq-touch-tag 5) 
+
+ (setf seq-bend-tag 6) 
+
diff --git a/Release/nyquist/seqmidi.lsp b/Release/nyquist/seqmidi.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..bea71145daf1dd229d0f630ad727a2786fe76f1a
--- /dev/null
+++ b/Release/nyquist/seqmidi.lsp
@@ -0,0 +1,171 @@
+;; seqmidi.lsp -- functions to use MIDI files in Nyquist
+;
+; example call:
+; 
+; (seq-midi my-seq
+;   (note (chan pitch velocity) (= chan 2) (my-note pitch velocity))
+;   (ctrl (chan control value) (...))
+;   (bend (chan value) (...))
+;   (touch (chan value) (...))
+;   (prgm (chan value) (setf (aref my-prgm chan) value))
+
+;; seq-midi - a macro to create a sequence of sounds based on midi file
+;
+; 
+(defmacro seq-midi (the-seq &rest cases)
+  (seq-midi-cases-syntax-check cases)
+  `(let (_the-event _next-time _the-seq _seq-midi-closure _nyq-environment 
+         _the-seq _tag)
+    (setf _the-seq (seq-copy ,the-seq))
+    (setf _nyq-environment (nyq:the-environment))
+    (setf _seq-midi-closure #'(lambda (t0)
+      (format t "_seq_midi_closure: t0 = ~A~%" t0) ;DEBUG
+      (prog (_the-sound)
+loop	; go forward until we find note to play (we may be there)
+        ; then go forward to find time of next note
+        (setf _the-event (seq-get _the-seq))
+        ; (display "seq-midi" _the-event t0)
+        (setf _tag (seq-tag _the-event))
+        (cond ((= _tag seq-ctrl-tag)
+               ,(make-ctrl-handler cases))
+              ((= _tag seq-bend-tag)
+               ,(make-bend-handler cases))
+              ((= _tag seq-touch-tag)
+               ,(make-touch-handler cases))
+              ((= _tag seq-prgm-tag)
+               ,(make-prgm-handler cases))
+              ((= _tag seq-done-tag)
+               ; (format t "_seq_midi_closure: seq-done")
+               (cond (_the-sound ; this is the last sound of sequence
+                      ; (format t "returning _the-sound~%")
+                      (return _the-sound))
+                     (t ; sequence is empty, return silence
+                      ; (format t "returning snd-zero~%")
+                      (return (snd-zero t0 *sound-srate*)))))
+              ((and (= _tag seq-note-tag)
+                    ,(make-note-test cases))
+               (cond (_the-sound ; we now have time of next note
+                      ; (display "note" (seq-time _the-event))
+                      (setf _next-time (/ (seq-time _the-event) 1000.0))
+                      (go exit-loop))
+                     (t
+                      (setf _the-sound ,(make-note-handler cases))))))
+        (seq-next _the-seq)
+        (go loop)
+exit-loop ; here, we know time of next note
+        (display "seq-midi" _next-time) ;DEBUG
+        (format t "seq-midi calling snd-seq\n") ;DEBUG
+        (return (snd-seq
+                  (set-logical-stop-abs _the-sound 
+                        (local-to-global _next-time))
+                  _seq-midi-closure)))))
+    (display "calling closure" (get-lambda-expression _seq-midi-closure)) ; DEBUG
+    (funcall _seq-midi-closure (local-to-global 0))))
+
+
+(defun seq-midi-cases-syntax-check (cases &aux n)
+  (cond ((not (listp cases))
+         (break "syntax error in" cases)))
+  (dolist (case cases)
+    (cond ((or (not (listp case)) 
+               (not (member (car case) '(NOTE CTRL BEND TOUCH PRGM)))
+               (not (listp (cdr case)))
+               (not (listp (cadr case)))
+               (not (listp (cddr case)))
+               (not (listp (last (cddr case)))))
+           (break "syntax error in" case))
+          ((/= (length (cadr case))
+               (setf n (cdr (assoc (car case) 
+                             '((NOTE . 3) (CTRL . 3) (BEND . 2)
+                               (TOUCH . 2) (PRGM . 2))))))
+           (break (format nil "expecting ~A arguments in" n) case))
+          ((and (eq (car case) 'NOTE)
+                (not (member (length (cddr case)) '(1 2))))
+           (break 
+            "note handler syntax is (NOTE (ch pitch vel) [filter] behavior)"
+            case)))))
+
+
+(defun make-ctrl-handler (cases)
+  (let ((case (assoc 'ctrl cases)))
+    (cond (case
+           `(let ((,(caadr case) (seq-channel _the-event))
+                  (,(cadadr case) (seq-control _the-event))
+                  (,(caddar (cdr case)) (seq-value _the-event)))
+              ,@(cddr case)))
+          (t nil))))
+
+(defun make-bend-handler (cases)
+  (let ((case (assoc 'bend cases)))
+    (cond (case
+           `(let ((,(caadr case) (seq-channel _the-event))
+                  (,(cadadr case) (seq-value _the-event)))
+              ,@(cddr case)))
+          (t nil))))
+
+(defun make-touch-handler (cases)
+  (let ((case (assoc 'touch cases)))
+    (cond (case
+           `(let ((,(caadr case) (seq-channel _the-event))
+                  (,(cadadr case) (seq-value _the-event)))
+              ,@(cddr case)))
+          (t nil))))
+
+(defun make-prgm-handler (cases)
+  (let ((case (assoc 'pgrm cases)))
+    (cond (case
+           `(let ((,(caadr case) (seq-channel _the-event))
+                  (,(cadadr case) (seq-value _the-event)))
+              ,@(cddr case)))
+          (t nil))))
+
+(defun make-note-test (cases)
+  (let ((case (assoc 'note cases)))
+    (cond ((and case (cdddr case))
+           (caddr case))
+          (t t))))
+           
+
+(defun make-note-handler (cases)
+  (let ((case (assoc 'note cases))
+        behavior)
+    (cond ((and case (cdddr case))
+           (setf behavior (cadddr case)))
+          (t
+           (setf behavior (caddr case))))
+    `(with%environment _nyq-environment
+        (with-note-args ,(cadr case) _the-event ,behavior))))
+
+
+(defmacro with-note-args (note-args the-event note-behavior)
+  ; (display "with-note-args" the-event)
+  `(let ((,(car note-args) (seq-channel ,the-event))
+         (,(cadr note-args) (seq-pitch ,the-event))
+         (,(caddr note-args) (seq-velocity ,the-event)))
+     (at (/ (seq-time ,the-event) 1000.0)
+      (stretch (/ (seq-duration ,the-event) 1000.0) ,note-behavior))))
+
+
+;(defun seq-next-note-time (the-seq find-first-flag)
+;  (prog (event)
+;    (if find-first-flag nil (seq-next the-seq))
+;loop
+;    (setf event (seq-get the-seq))
+;    (cond ((eq (seq-tag event) seq-done-tag)
+;	   (return (if find-first-flag 0.0 nil)))
+;	  ((eq (seq-tag event) seq-note-tag)
+;	   (return (/ (seq-time event) 1000.0))))
+;    (seq-next the-seq)
+;    (go loop)))
+; 
+
+;; for SAL we can't pass in lisp expressions as arguments, so
+;; we pass in functions instead, using keyword parameters for
+;; ctrl, bend, touch, and prgm. The note parameter is required.
+;;
+(defun seq-midi-sal (seq note &optional ctrl bend touch prgm)
+  (seq-midi seq (note (chan pitch vel) (funcall note chan pitch vel))
+    (ctrl (chan num val) (if ctrl (funcall ctrl chan num val)))
+    (bend (chan val) (if bend (funcall bend chan val)))
+    (touch (chan val) (if touch (funcall touch chan val)))
+    (prgm (chan val) (if prgm (funcall prgm chan val)))))
diff --git a/Release/nyquist/sliders.lsp b/Release/nyquist/sliders.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..292e87c6e1a07457fc25e3e7bee61312317cc765
--- /dev/null
+++ b/Release/nyquist/sliders.lsp
@@ -0,0 +1,196 @@
+;; sliders.lsp -- communicate with NyquistIDE to implement control panels
+;; Roger B. Dannenberg
+;; April 2015
+
+;;    (stop-on-zero s) -- a sound that returns 1 until s goes to zero, then
+;;            the sound terminates. If s comes from a slider and you multiply
+;;            a sound by (stop-on-zero s), you can interactively stop it
+;;    (make-slider-panel "name" color) -- sets panel name for the following
+;;            sliders
+;;    (make-slider "param" [initial [low high]]) -- create slider named 
+;;            "param" with optional range and initial value. Also returns
+;;            a sound.
+;;    (make-button "param" normal) -- create a button named "param" with
+;;            a starting value of normal (either 0 or 1). While the button
+;;            in the panel is pressed, the value changes to 1 or 0.
+;;    (get-slider-value "param") -- when called with a string, this looks up
+;;            the slider value by name
+;;    (slider-panel-close "name") -- close the panel window. Values of any 
+;;            existing sliders become undefined.
+;;    (slider "panel" "name" [dur]) -- make a signal from slider value
+;;    (slider "name" [dur]) -- make a signal from slider in current panel
+;;    (get-slider-value "panel" "name") -- get a float value
+;;    (get-slider-value "name") -- get a float in current panel
+
+;; *active-slider-panel* is the current panel to which sliders are added
+;;
+(if (not (boundp '*active-slider-panel*))
+    (setf *active-slider-panel* nil))
+
+;; *panels-in-use* is an assoc list of panels, where each panel
+;;   is a list of allocated sliders stored as (name number)
+;;
+(if (not (boundp '*panels-in-use*))
+    (setf *panels-in-use* nil))
+
+;; allocate-slider-num -- find an unused slider number
+;;   linear search is used to avoid maintaining a parallel structure
+;;   for faster searching. We search starting at slider #10, leaving
+;;   sliders 0-9 unused; for example, you might want to control them
+;;   via open sound control, so this gives you 10 sliders that are
+;;   off limits to allocation by the SLIDER function.
+;;   
+;;   This code takes advantage of the fact that dotimes and dolist
+;;   return nil when they end normally, so we signal that we found
+;;   or did not find i by explicitly returning. Note that RETURN
+;;   returns from the innermost dotimes or dolist -- they do not
+;;   return from allocate-slider-num.
+;;
+(defun allocate-slider-num ()
+  (dotimes (n 990)
+    (let ((i (+ n 10)))
+      (cond ((not (dolist (panel *panels-in-use*)
+                    (cond ((dolist (pair (cdr panel))
+                             (cond ((eql (second pair) i) (return t))))
+                           (return t)))))
+              (return i))))))
+
+;; remove panel from list of panels
+(defun slider-panel-free (panel)
+  (setf *panels-in-use* (remove panel *panels-in-use* :test #'equal)))
+
+(setfn stop-on-zero snd-stoponzero)
+
+(defun make-slider-panel (name &optional (color 0))
+  (let ((panel (assoc name *panels-in-use* :test #'equal)))
+    ;; first find if panel already exists. If so, free the resources
+    (cond (panel
+           (slider-panel-free panel)))
+    (setf *active-slider-panel* (list name))
+    (setf *panels-in-use* (cons *active-slider-panel* *panels-in-use*))
+    (format t "slider-panel-create: \"~A\" ~A~%" name color)))
+
+(defun make-slider (name &optional (init 0) (low 0) (high 1))
+  (let ((num (allocate-slider-num)))
+    (cond ((null num)
+           (format t "WARNING: MAKE-SLIDER is out of slider numbers. ~A~%"
+                     "No slider created."))
+          ((not (and (stringp name) (numberp init) 
+                     (numberp low) (numberp high)))
+           (display 
+            "WARNING: MAKE-SLIDER called with bad arguments. No slider created"
+            name init low high)))
+    ;; make sure we have an active panel
+    (cond ((null *active-slider-panel*)
+           (make-slider-panel "Controls")))
+    ;; insert new slider into list of sliders in active panel. This
+    ;; is aliased with an element in the assoc list *panels-in-use*.
+    (rplacd *active-slider-panel* (cons (list name num) 
+                                        (cdr *active-slider-panel*)))
+    (format t "slider-create: \"~A\" ~A ~A ~A ~A~%" name num init low high)
+    num))
+
+(defun make-button (name &optional (normal 0))
+  (let ((num (allocate-slider-num)))
+    (cond ((null num)
+           (format t "WARNING: MAKE-BUTTON is out of slider numbers. ~A~%"
+                     "No button created."))
+          ((not (and (stringp name) (numberp normal)))
+           (display 
+            "WARNING: MAKE-BUTTON called with bad arguments. No button created"
+            name normal)))
+    ;; make sure we have an active panel
+    (cond ((null *active-slider-panel*)
+           (slider-panel "Controls")))
+    ;; insert new button into list of controls in active panel. This
+    ;; is aliased with an element in the assoc list *panels-in-use*.
+    (rplacd *active-slider-panel* (cons (list name num) 
+                                        (cdr *active-slider-panel*)))
+    (format t "button-create: \"~A\" ~A ~A~%" name num normal)
+    num))
+
+(defun close-slider-panel (name)
+  (let ((panel (assoc name *panels-in-use* :test #'equal)))
+    (cond ((not (stringp name))
+           (display "WARNING: SLIDER-PANEL-CLOSED called with bad argument."
+                    name)))
+    (cond (panel
+           (slider-panel-free panel)
+           (format t "slider-panel-close: \"~A\"~%" name))
+          (t
+           (format t "WARNING: slider panel ~A not found.~%" name)))))
+
+;; SLIDER-LOOKUP - find the slider by name
+;;
+(defun slider-lookup (name slider)
+  (let ((panel (assoc name *panels-in-use* :test #'equal)) s)
+    (cond ((null panel)
+           (error "Could not find slider panel named" name)))
+    (setf s (assoc slider (cdr panel) :test #'equal))
+    (cond ((null s)
+           (error "Could not find slider named" s)))
+    (second s)))
+
+
+;; SLIDER - creates a signal from real-time slider input
+;; 
+;; options are:
+;;   (SLIDER number [dur])
+;;   (SLIDER "name" [dur]) -- look up slider in current slider panel
+;;   (SLIDER "panel" "name" [dur]) -- look up panel, then look up slider
+;;
+(defun slider (id &optional slider-name dur)
+    (cond ((and (numberp id) (null slider-name))
+           (setf dur 1.0))
+          ((and (numberp id) (numberp slider-name) (null dur))
+           (setf dur slider-name))
+          ((and (stringp id) (null slider-name))
+           (setf dur 1.0)
+           (setf id (slider-lookup (car *active-slider-panel*) id)))
+          ((and (stringp id) (numberp slider-name) (null dur))
+           (setf dur slider-name)
+           (setf id (slider-lookup (car *active-slider-panel*) id)))
+          ((and (stringp id) (stringp slider-name) (null dur))
+           (setf dur 1.0)
+           (setf id (slider-lookup id slider-name)))
+          ((and (stringp id) (stringp slider-name) (numberp dur))
+           (setf id (slider-lookup id slider-name)))
+          (t
+           (error "SLIDER called with invalid arguments")))
+    (setf dur (get-duration dur))
+    (setf id (round id)) ;; just to make sure it's an integer
+    (cond ((or (< id 0) (>= id 1000))
+           (error "SLIDER index out of bounds" id)))
+    (display "slider" id slider-name dur)
+    (snd-slider id *rslt* *sound-srate* dur))
+
+
+(if (not (boundp '*lpslider-cutoff*))
+    (setf *lpslider-cutoff* 20.0))
+
+(defun lpslider (id &optional slider-name dur)
+  (lp (slider id slider-name dur) 20.0))
+
+;; save built-in get-slider-value so we can redefine it
+(if (not (fboundp 'prim-get-slider-value))
+    (setfn prim-get-slider-value get-slider-value))
+
+(defun get-slider-value (id &optional slider-name)
+  (cond ((and (numberp id) (null slider-name)) nil)
+        ((and (stringp id) (null slider-name))
+         (setf id (slider-lookup (car *active-slider-pael*) id)))
+        ((and (stringp id) (stringp slider-name))
+         (setf id (slider-lookup id slider-name)))
+        (t
+         (error "GET-SLIDER-VALUE called with invalid arguments")))
+  ;; further parameter checking is done in get-slider-value:
+  (prim-get-slider-value id))
+
+(autonorm-off)
+(snd-set-latency 0.02)
+(print "**********sliders.lsp************************")
+(print "WARNING: AUTONORM IS NOW TURNED OFF")
+(print "WARNING: AUDIO LATENCY SET TO 20MS")
+(print "To restore settings, execute (autonorm-on) and")
+(print "  (set-audio-latency 0.3)")
+(print "*********************************************")
diff --git a/Release/nyquist/sndfnint.lsp b/Release/nyquist/sndfnint.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..015191b241c368069aaee644c3f52acb095f5672
--- /dev/null
+++ b/Release/nyquist/sndfnint.lsp
@@ -0,0 +1,92 @@
+ (SETF MAX-STOP-TIME 10E20) 
+
+ (SETF MIN-START-TIME -10E20) 
+
+ (setf OP-AVERAGE 1) (setf OP-PEAK 2) 
+
+ (setf snd-head-none 0) 
+
+ (setf snd-head-AIFF 1) 
+
+ (setf snd-head-IRCAM 2) 
+
+ (setf snd-head-NeXT 3) 
+
+ (setf snd-head-Wave 4) 
+
+ (setf snd-head-PAF 5) 
+
+ (setf snd-head-SVX 6) 
+
+ (setf snd-head-NIST 7) 
+
+ (setf snd-head-VOC 8) 
+
+ (setf snd-head-W64 9) 
+
+ (setf snd-head-MAT4 10) 
+
+ (setf snd-head-MAT5 11) 
+
+ (setf snd-head-PVF 12) 
+
+ (setf snd-head-XI 13) 
+
+ (setf snd-head-HTK 14) 
+
+ (setf snd-head-SDS 15) 
+
+ (setf snd-head-AVR 16) 
+
+ (setf snd-head-SD2 17) 
+
+ (setf snd-head-FLAC 18) 
+
+ (setf snd-head-CAF 19) 
+
+ (setf snd-head-raw 20) 
+
+ (setf snd-head-OGG 21) 
+
+ (setf snd-head-WAVEX 22) 
+
+ (setf snd-head-channels 1) 
+
+ (setf snd-head-mode 2) 
+
+ (setf snd-head-bits 4) 
+
+ (setf snd-head-srate 8) 
+
+ (setf snd-head-dur 16) 
+
+ (setf snd-head-latency 32) 
+
+ (setf snd-head-type 64) 
+
+ (setf snd-mode-adpcm 0) 
+
+ (setf snd-mode-pcm 1) 
+
+ (setf snd-mode-ulaw 2) 
+
+ (setf snd-mode-alaw 3) 
+
+ (setf snd-mode-float 4) 
+
+ (setf snd-mode-upcm 5) 
+
+ (setf snd-mode-unknown 6) 
+
+ (setf snd-mode-double 7) 
+
+ (setf snd-mode-GSM610 8) 
+
+ (setf snd-mode-DWVW 9) 
+
+ (setf snd-mode-DPCM 10) 
+
+ (setf snd-mode-msadpcm 11) 
+
+ (setf snd-mode-vorbis 11) 
+
diff --git a/Release/nyquist/spec-plot.lsp b/Release/nyquist/spec-plot.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..a7651fbbb9da77bb5e5fce9aedd93278cf118ab5
--- /dev/null
+++ b/Release/nyquist/spec-plot.lsp
@@ -0,0 +1,47 @@
+;; spec-plot.lsp -- spectral plot function
+;;
+;; Roger B. Dannenberg, May 2016
+;;
+
+(setf *spec-plot-bw* 8000.0) ;; highest frequency to plot (default)
+(setf *spec-plot-res* 20.0) ;; bin size (default)
+(setf *spec-plot-db* nil) ;; plot dB? (default)
+
+;; We want to allow round-number bin-sizes so plot will be more readable
+;; Assuming 20Hz as an example, the FFT size would have to be
+;; 44100/20 = 2205, but that's not a power of 2, so we should resample
+;; the signal down so that the FFT size is 2048 (or up to 4096). This
+;; would result in sample rates of 2048*20 = 40960 or 81120. We should
+;; pick the smaller one if it is at least 2x *spec-plot-bw*.
+
+(defun spec-plot (sound &optional offset &key (res *spec-plot-res*)
+                                              (bw *spec-plot-bw*)
+                                              (db *spec-plot-db*))
+  (ny:typecheck (not (soundp sound))
+    (ny:error "SPEC-PLOT" 1 '((SOUND) nil) sound))
+  (ny:typecheck (not (or (null offset) (numberp offset)))
+    (ny:error "SPEC-PLOT" 2 '((NUMBER NULL) nil) offset))
+  (let (newsr sa fft-size power2)
+    (setf fft-size (/ (snd-srate sound) res))
+    (setf power2 8) ;; find integer size for FFT
+    (while (< power2 fft-size)
+      (setf power2 (* 2 power2)))
+    ;; now power2 >= fft-size
+    (cond ((> power2 fft-size) ;; not equal, must resample
+           ;; if half power2 * res is above 2 * bw,
+           ;; use half power2 as fft size
+           (cond ((> (* power2 res) (* 4 bw))
+                  (setf power2 (/ power2 2))))
+           (setf sound (snd-resample sound (* power2 res)))
+           (setf fft-size power2)))
+    ;; we only need fft-dur samples, but allow an extra second just to
+    ;; avoid any rounding errors
+    (if offset
+        (setf sound (extract offset (+ 1.0 offset (/ (snd-srate sound)
+                                                     fft-size)) sound)))
+    (setf sa (sa-init :resolution res :input sound))
+    (setf mag (sa-magnitude (sa-next sa)))
+    (setf mag (snd-from-array 0 (/ 1.0 res) mag))
+    (if db (setf mag (linear-to-db mag)))
+    (s-plot mag bw (round (/ (float bw) res)))))
+            
diff --git a/Release/nyquist/spectral-analysis.lsp b/Release/nyquist/spectral-analysis.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..38ff748a0bcd0099c1b8dd753d56cf06884a15c3
--- /dev/null
+++ b/Release/nyquist/spectral-analysis.lsp
@@ -0,0 +1,289 @@
+;; spectral-analysis.lsp -- functions to simplify computing
+;;   spectrogram data
+;;
+;; Roger B. Dannenberg and Gus Xia
+;; Jan 2013, modified Oct 2017
+
+;; API:
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; set sa-obj = sa-init(resolution: <nil or Hz>,
+;;                      fft-dur: <nil or seconds>,
+;;                      skip-period: <seconds>,
+;;                      window: <window type>, 
+;;                      input: <filename or sound>)
+;; 
+;; sa-init() creates a spectral-analysis object that can be used
+;; to obtain spectral data from a sound.
+;;
+;; resolution is the width of each spectral bin in Hz. If nil of
+;;     not specified, the resolution is computed from fft-dur. 
+;;     The actual resolution will be finer than the specified 
+;;     resolution because fft sizes are rounded to a power of 2.
+;; fft-dur is the width of the FFT window in seconds. The actual
+;;     FFT size will be rounded up to the nearest power of two
+;;     in samples. If nil, fft-dur will be calculated from 
+;;     resolution. If both fft-size and resolution are nil
+;;     or not specified, the default value of 1024 samples,
+;;     corresponding to a duration of 1024 / signal-sample-rate,
+;;     will be used. If both resolution and fft-dur are
+;;     specified, the resolution parameter will be ignored.
+;;     Note that fft-dur and resolution are reciprocals.
+;; skip-period specifies the time interval in seconds between 
+;;     successive spectra (FFT windows). Overlapping FFTs are
+;;     possible. The default value overlaps windows by 50%. 
+;;     Non-overlapped and widely spaced windows that ignore 
+;;     samples by skipping over them entirely are also acceptable.
+;; window specifies the type of window. The default is raised
+;;     cosine (Hann or "Hanning") window. Options include
+;;     :hann, :hanning, :hamming, :none, nil, where :none and
+;;     nil mean a rectangular window.
+;; input can be a string (which specifies a sound file to read)
+;;     or a Nyquist SOUND to be analyzed.
+;; Return value is an XLISP object that can be called to obtain
+;;     parameters as well as a sequence of spectral frames.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; set sa-frame = sa-next(sa-obj)
+;;
+;; sa-next() fetches the next spectrum from sa-obj.
+;;
+;; sa-obj is a spectral-analysis object returned by sa-init().
+;; Return value is an array of FLONUMS representing the discrete
+;;     spectrum.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; exec sa-info(sa-obj)
+;;
+;; sa-info prints information about the spectral computation.
+;;
+;; sa-obj is a spectral-analysis object returned by sa-init().
+;; Return value is nil, but information is printed.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; set mag = sa-magnitude(frame)
+;;
+;; sa-magnitude computes the magnitude (amplitude) spectrum
+;; from a frame returned by sa-frame.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; exec sa-plot(sa-obj, sa-frame)
+;;
+;; sa-plot plots the amplitude (magnitude) spectrum of sa-frame.
+;;
+;; sa-obj is used to determine the bin width of data in sa-frame.
+;;
+;; sa-frame is a spectral frame (array) returned by sa-next()
+;;
+;; Return value is nil, but a plot is generated and displayed.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; set hz = sa-get-bin-width(sa-obj)
+;; set n = sa-get-fft-size(sa-obj)
+;; set secs = sa-get-fft-dur(sa-obj)
+;; set window = sa-get-fft-window(sa-obj)
+;; set skip-period = sa-get-skip-period(sa-obj)
+;; set m = sa-get-fft-skip-size(sa-obj)
+;; set sr = sa-get-sample-rate(sa-obj)
+;;
+;; These functions retrieve data from the sa-obj created by 
+;; sa-init. The return values are:
+;;   hz - the width of a frequency bin (also the separation
+;;       of bin center frequencies). The center frequency of
+;;       the i'th bin is i * hz.
+;;   n - the size of the FFT, an integer, a power of two. The
+;;       size of a spectral frame (an array returned by sa-next)
+;;       is (n / 2) + 1.
+;;   secs - the duration of an FFT window.
+;;   window - the type of window used (:hann, :hamming, :none)
+;;   skip-period - the time in seconds of the skip (the time
+;;       difference between successive frames
+;;   m - the size of the skip in samples.
+;;   sr - the sample rate of the sound being analyzed (in Hz, a flonum)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;; define the class of spectral analysis objects
+(setf sa-class (send class :new '(sound length skip window window-type)))
+
+(send sa-class :answer :next '() '(
+    (snd-fft sound length skip window)))
+
+(defun sa-raised-cosine (alpha beta)
+  (sum (const alpha)
+       (scale beta (lfo 1.0 1.0 *sine-table* 270))))
+
+(defun sa-fft-window (frame-size alpha beta)
+  (abs-env (control-srate-abs frame-size                
+               (sa-raised-cosine alpha beta))))
+
+(defun hann-window (frame-size) (sa-fft-window frame-size 0.5 0.5))
+(defun hamming-window (frame-size) (sa-fft-window frame-size 0.54 0.46))
+
+(defun sa-get-window-type (win-type)
+  (case win-type
+    ((:hann :hanning)    :hann)
+    ((nil :none)         :none)
+    (:hamming            :hamming)
+    (t (print "Warning: invalid window-type parameter: ~A~%" win-type)
+       (print "    Using :HAMMING instead.~%")
+       :hamming)))
+
+
+(defun sa-compute-window (len win-type)
+  (case win-type
+    (:hann        (hann-window len))
+    (:none        nil)
+    (:hamming     (hamming-window len))
+    (t (print "Warning: invalid window-type parameter: ~A~%" win-type)
+       (print "    Using :HAMMING instead.~%")
+       (hamming-window len))))
+  
+
+(send sa-class :answer :isnew '(snd len skp win-type) '(
+    (setf sound snd)
+    (setf length len)
+    (setf skip skp)
+    (setf window-type (sa-get-window-type win-type))
+    (setf window (sa-compute-window length window-type))))
+
+
+;; sa-to-mono -- sum up the channels in an array
+;;
+(defun sa-to-mono (s)
+  (let ((mono (aref s 0)))
+    (dotimes (i (1- (length s)))
+      (setf mono (sum mono (aref s (1+ i)))))
+    mono))
+
+
+(defun sa-init (&key resolution fft-dur skip-period window input)
+  (let (len sr n skip)
+    (cond ((stringp input)
+           (setf input (s-read input))))
+    (cond ((arrayp input)
+           (format t "Warning: sa-init is converting stereo sound to mono~%")
+           (setf input (sa-to-mono input)))
+          ((soundp input) ;; so that variables are not "consumed" by snd-fft
+           (setf input (snd-copy input))))
+    (cond ((not (soundp input))
+           (error
+            (format nil
+             "Error: sa-init did not get a valid :input parameter~%"))))
+    (setf sr (snd-srate input))
+    (setf len 1024)
+    (cond (fft-dur
+           (setf len (* fft-dur sr)))
+          (resolution
+           (setf len (/ sr resolution))))
+    ;; limit fft size to between 4 and 2^16
+    (cond ((> len 65536)
+           (format t "Warning: fft-size reduced from ~A to 65536~%" len)
+           (setf len 65536))
+          ((< len 4)
+           (format t "Warning: fft-size increased from ~A to 4~%" len)
+           (setf len 4)))
+    ;; round up len to a power of two
+    (setf n 4)
+    (while (< n len)
+      (setf n (* n 2)))
+    (setf length n) ;; len is now an integer power of 2
+    ;(display "sa-init" length)
+    ;; compute skip length - default is len/2
+    (setf skip (if skip-period (round (* skip-period sr))
+                               (/ length 2)))
+    (send sa-class :new input length skip window)))
+
+
+(defun sa-next (sa-obj)
+  (send sa-obj :next))
+
+(defun sa-info (sa-obj)
+  (send sa-obj :info))
+
+(send sa-class :answer :info '() '(
+  (format t "Spectral Analysis object (instance of sa-class):~%")
+  (format t "  resolution (bin width): ~A Hz~%" (/ (snd-srate sound) length))
+  (format t "  fft-dur: ~A s (~A samples)~%" (/ length (snd-srate sound)) length)
+  (format t "  skip-period: ~A s (~A samples)~%" (/ skip (snd-srate sound)) skip)
+  (format t "  window: ~A~%" window-type)
+  nil))
+
+
+(defun sa-plot (sa-obj frame)
+  (send sa-obj :plot frame))
+
+(defun sa-magnitude(frame)
+  (let* ((flen (length frame))
+         (n (/ (length frame) 2)) ; size of amplitude spectrum - 1
+         (as (make-array (1+ n))))  ; amplitude spectrum
+    ;; first compute an amplitude spectrum
+    (setf (aref as 0) (abs (aref frame 0))) ;; DC
+    ;; half_n is actually length/2 - 1, the number of complex pairs
+    ;;    in addition there is the DC and Nyquist terms, which are
+    ;;    real and in the first and last slots of frame
+    (setf half_n (1- n))
+    (dotimes (i half_n)
+      (let* ((i2 (+ i i 2))  ; index of the imag part
+             (i2m1 (1- i2)) ; index of the real part
+             (amp (sqrt (+ (* (aref frame i2m1) (aref frame i2m1))
+                           (* (aref frame i2)   (aref frame i2))))))
+        (setf (aref as (1+ i)) amp)))
+    (setf (aref as n) (aref frame (1- flen)))
+    as)) ;; return the amplitude spectrum
+  
+
+(send sa-class :answer :plot '(frame) '(
+  (let* ((as (sa-magnitude frame))
+         (sr (snd-srate sound)))
+    (s-plot (snd-from-array 0 (/ length sr) as)
+            sr (length as)))))
+
+(defun sa-get-bin-width (sa-obj)
+  (send sa-obj :get-bin-width))
+
+(send sa-class :answer :get-bin-width '()
+      '((/ (snd-srate sound) length)))
+
+(defun sa-get-fft-size (sa-obj)
+  (send sa-obj :get-fft-size))
+
+(send sa-class :answer :get-fft-size '() '(length))
+
+(defun sa-get-fft-dur (sa-obj)
+  (send sa-obj :get-fft-dur))
+
+(send sa-class :answer :get-fft-dur '() '(/ length (snd-srate sound)))
+
+(defun sa-get-fft-window (sa-obj)
+  (send sa-obj :get-fft-window))
+
+(send sa-class :answer :get-fft-window '() '(window-type))
+
+(defun sa-get-fft-skip-period (sa-obj)
+  (send sa-obj :get-skip-period))
+
+(send sa-class :answer :get-skip-period '() '((/ skip (snd-srate sound))))
+
+(defun sa-get-fft-skip-size (sa-obj)
+  (send sa-obj :get-skip-size))
+
+(send sa-class :answer :get-fft-skip-size '() '(skip))
+
+(defun sa-get-sample-rate (sa-obj)
+  (send sa-obj :get-sample-rate))
+
+(send sa-class :answer :get-sample-rate '() '((snd-srate sound)))
+
+
+;;;;;;; TESTS ;;;;;;;;;;
+
+
+(defun plot-test ()
+  (let (frame)
+    (setf sa (sa-init :input "./rpd-cello.wav"))
+    (while t
+      (setf frame (sa-next sa))
+      (if (null sa) (return nil))
+      (sa-plot sa frame))))
+
diff --git a/Release/nyquist/stk.lsp b/Release/nyquist/stk.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..3eae1390851ba037c5ecbe8d6111dd9ee308ff34
--- /dev/null
+++ b/Release/nyquist/stk.lsp
@@ -0,0 +1,200 @@
+;; stk.lsp -- STK-based instruments
+;;
+;; currently clarinet and saxophony are implemented
+
+(defun instr-parameter (parm)
+  ;; coerce parameter into a *sound-srate* signal
+  (cond ((numberp parm)
+         (stretch 30 (control-srate-abs *sound-srate* (const (float parm)))))
+        (t
+         (force-srate *sound-srate* parm))))
+
+
+(defun clarinet (step breath-env)
+  (snd-clarinet (step-to-hz step) (force-srate *sound-srate* breath-env) *sound-srate*))
+
+
+(defun clarinet-freq (step breath-env freq-env)
+  ;; note that the parameters are in a different order -- I defined 
+  ;; clarinet-freq this way so that the first two parameters are always
+  ;; step and breath. I didn't redo snd-clarinet-freq.
+  (snd-clarinet_freq (step-to-hz step) 
+                (instr-parameter breath-env)
+                (instr-parameter freq-env)
+                *sound-srate*))
+
+
+
+(defun clarinet-all (step breath-env freq-env vibrato-freq vibrato-gain reed-stiffness noise)
+  ;; note that the parameters are not in the same order as snd-clarinet-all
+  (setf breath-env (instr-parameter breath-env))
+  (setf freq-env (instr-parameter freq-env))
+  (setf reed-stiffness (instr-parameter reed-stiffness))
+  (setf noise (instr-parameter noise))
+  (snd-clarinet_all (step-to-hz step)
+                    breath-env freq-env 
+                    ;; STK scales 1.0 to 12Hz. Scale here so vibrato-freq is in Hz
+                    (/ vibrato-freq 12.0) vibrato-gain
+                    reed-stiffness noise 
+                    *sound-srate*))
+
+
+(defun sax (step breath-env)
+  (snd-sax (step-to-hz step) (force-srate *sound-srate* breath-env) *sound-srate*))
+
+(defun sax-freq (step breath-env freq-env)
+  (snd-sax_freq (step-to-hz step)
+          (instr-parameter breath-env)
+          (instr-parameter freq-env)
+          *sound-srate*))
+
+(defun sax-all (step breath-env freq-env vibrato-freq vibrato-gain reed-stiffness noise blow-pos reed-table-offset)
+  (snd-sax_all (step-to-hz step)
+	       (instr-parameter freq-env)
+               (instr-parameter breath-env)
+               (instr-parameter (/ vibrato-freq 12.0))
+               (instr-parameter vibrato-gain)
+               (instr-parameter reed-stiffness)
+               (instr-parameter noise)
+               (instr-parameter blow-pos)
+               (instr-parameter reed-table-offset)
+               *sound-srate*)
+)
+
+; instr-parameter already defined in stk.lsp
+
+(defun flute (step breath-env)
+  (snd-flute (step-to-hz step) (force-srate *sound-srate* breath-env) *sound-srate*))
+ 
+(defun flute-freq (step breath-env freq-env)
+  (snd-flute_freq (step-to-hz step) 
+		  (instr-parameter breath-env)
+		  (instr-parameter freq-env)
+		  *sound-srate*))
+
+(defun flute-all (step breath-env freq-env vibrato-freq vibrato-gain jet-delay noise)
+  ;; note that the parameters are not in the same order as snd-clarinet-all
+  (setf breath-env (instr-parameter breath-env))
+  (setf freq-env (instr-parameter freq-env))
+  (setf jet-delay (instr-parameter jet-delay))
+  (setf noise (instr-parameter noise))
+  (snd-flute_all (step-to-hz step)
+                    breath-env freq-env 
+                    ;; STK scales 1.0 to 12Hz. Scale here so vibrato-freq is in Hz
+                    (/ vibrato-freq 12.0) vibrato-gain
+                    jet-delay noise 
+                    *sound-srate*))
+
+
+(defun bowed (step bowpress-env)
+  (snd-bowed (step-to-hz step) (force-srate *sound-srate* bowpress-env) *sound-srate*))
+
+(defun bowed-freq (step bowpress-env freq-env)
+  (snd-bowed_freq (step-to-hz step)
+		  (instr-parameter bowpress-env)
+		  (instr-parameter freq-env)
+		  *sound-srate*))
+
+(defun mandolin (step dur &optional (detune 4.0))
+  (let ((d (get-duration dur)))
+    (snd-mandolin *rslt* (step-to-hz step) d 1.0 detune *sound-srate*)))
+
+(defun wg-uniform-bar (step bowpress-env)
+  (snd-bandedwg (step-to-hz step) (force-srate *sound-srate* bowpress-env) 0 *sound-srate*))
+
+(defun wg-tuned-bar (step bowpress-env)
+  (snd-bandedwg (step-to-hz step) (force-srate *sound-srate* bowpress-env) 1 *sound-srate*))
+
+(defun wg-glass-harm (step bowpress-env)
+  (snd-bandedwg (step-to-hz step) (force-srate *sound-srate* bowpress-env) 2 *sound-srate*))
+
+(defun wg-tibetan-bowl (step bowpress-env)
+  (snd-bandedwg (step-to-hz step) (force-srate *sound-srate* bowpress-env) 3 *sound-srate*))
+ 
+(defun modalbar (preset step duration)
+   (let ((preset (case preset
+			(MARIMBA 0)
+			(VIBRAPHONE 1)
+			(AGOGO 2)
+			(WOOD1 3)
+			(RESO 4)
+			(WOOD2 5)
+			(BEATS 6)
+			(TWO-FIXED 7)
+			(CLUMP 8)
+			(t (error (format nil "Unknown preset for modalbar %A" preset)))))
+	 (d (get-duration duration)))
+     (snd-modalbar *rslt* (step-to-hz step) preset d *sound-srate*)))
+
+(defun sitar (step dur)
+  (let ((d (get-duration dur)))
+    (snd-sitar *rslt* (step-to-hz step) d *sound-srate*)))
+
+(defun nyq:nrev (snd rev-time mix)
+  (snd-stkrev 0 snd rev-time mix))
+
+(defun nyq:jcrev (snd rev-time mix)
+  (snd-stkrev 1 snd rev-time mix))
+
+(defun nyq:prcrev (snd rev-time mix)
+  (snd-stkrev 2 snd rev-time mix))
+
+(defun nrev (snd rev-time mix)
+  (multichan-expand "NREV" #'nyq:nrev 
+    '(((SOUND) "snd") ((NUMBER) "rev-time") ((NUMBER) "mix"))
+    snd rev-time mix))
+
+(defun jcrev (snd rev-time mix)
+  (multichan-expand "JCREV" #'nyq:jcrev 
+    '(((SOUND) "snd") ((NUMBER) "rev-time") ((NUMBER) "mix"))
+    snd rev-time mix))
+
+(defun prcrev (snd rev-time mix)
+  (multichan-expand "PRCREV" #'nyq:prcrev 
+    '(((SOUND) "snd") ((NUMBER) "rev-time") ((NUMBER) "mix"))
+    snd rev-time mix))
+
+(defun nyq:chorus (snd depth freq mix &optional (base-delay 6000))
+  (snd-stkchorus snd base-delay depth freq mix))
+
+(defun stkchorus (snd depth freq mix &optional (base-delay 6000))
+  (multichan-expand "STKCHORUS" #'nyq:chorus 
+    '(((SOUND) "snd") ((NUMBER) "depth") ((NUMBER) "freq") ((NUMBER) "mix")
+      ((INTEGER) "base-delay"))
+    snd depth freq mix base-delay))
+
+(defun nyq:pitshift (snd shift mix)
+  (snd-stkpitshift snd shift mix))
+
+(defun pitshift (snd shift mix)
+  (multichan-expand "PITSHIFT" #'nyq:pitshift 
+    '(((SOUND) "snd") ((NUMBER) "shift") ((NUMBER) "mix"))
+    snd shift mix))
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; HELPER FUNCTIONS
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; pass in rates of increase/decrease in begin/end... this is like noteOn and noteOff
+;
+; STK uses setRate but the actual ramp time is also a function of the sample rate.
+; I will assume the clarinet was run at 44100Hz and fix things so that the envelope
+; is sample-rate independent.
+;
+; STK seemed to always give a very fast release, so I changed the numbers so that
+; note-off values from 0.01 to 1 give an interesting range of articulations.
+;
+; IMPORTANT: the returned envelope is 0.1s longer than dur. There is 0.1s of silence
+; at the end so that the clarinet can "ring" after the driving force is removed.
+;
+(defun stk-breath-env (dur note-on note-off)
+  (let* ((target (+ 0.55 (* 0.3 note-on)))
+         (on-time (/ (* target 0.0045) note-on))
+         (off-time (/ (* target 0.02) note-off)))
+    ;(display "clarinet-breath-env" target on-time off-time)
+    (pwl on-time target
+         (- dur off-time) target
+         dur 0 (+ dur 0.1))))
+
+
diff --git a/Release/nyquist/system.lsp b/Release/nyquist/system.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..b750fe66693d86af2fe6d49e2423aad2fcefde25
--- /dev/null
+++ b/Release/nyquist/system.lsp
@@ -0,0 +1,131 @@
+; machine.lsp -- machine/system-dependent definitions
+;       Windows
+
+;; default behavior is to call SETUP-CONSOLE to get large white typescript
+;;
+;; set *setup-console* to nil in your personal init.lsp to override this behavior 
+;; (this may be necessary to work with emacs)
+;;
+(if (not (boundp '*setup-console*)) (setf *setup-console* t))
+(if *setup-console* (setup-console))
+
+(if (not (boundp '*default-sf-format*))
+    (setf *default-sf-format* snd-head-Wave))
+
+(if (not (boundp '*default-sound-file*))
+    (compute-default-sound-file))
+
+(if (not (boundp '*default-sf-dir*))
+    (setf *default-sf-dir* ""))
+
+(if (not (boundp '*default-sf-mode*))
+    (setf *default-sf-mode* snd-mode-pcm))
+
+(if (not (boundp '*default-sf-bits*))
+    (setf *default-sf-bits* 16))
+
+(if (not (boundp '*default-plot-file*))
+    (setf *default-plot-file* "points.dat"))
+
+;(if (not (boundp '*plotscript-file*))
+;    (setf *plotscript-file* "sys/unix/rs6k/plotscript"))
+
+; local definition for play
+(defmacro play (expr)
+  `(s-save-autonorm ,expr NY:ALL *default-sound-file* :play *soundenable*))
+
+
+(defun r ()
+  (s-save (s-read *default-sound-file*) NY:ALL "" :play t)
+)
+
+
+; PLAY-FILE -- play a file
+(defun play-file (name)
+  (s-save (s-read name) NY:ALL "" :play t))
+
+
+; FULL-NAME-P -- test if file name is a full path or relative path
+;
+; (otherwise the *default-sf-dir* will be prepended
+;
+(defun full-name-p (filename)
+  (or (eq (char filename 0) #\\)
+      (eq (char filename 0) #\/)
+      (eq (char filename 0) #\.)
+      (and (> (length filename) 2)
+           (both-case-p (char filename 0))
+           (equal (char filename 1) #\:))))
+
+; RELATIVE-PATH-P -- test if filename or path is a relative path
+;
+; note that properly converting a Windows path from relative to
+;  absolute is complicated by paths like: E:MYFILE.LSP
+;  Nyquist assumes that if there is a drive letter, the path is
+;  absolute, e.g. E:\TMP\MYFILE.LSP and if there is no drive,
+;  the path is relative, e.g. you cannot have \TMP\MYFILE.LSP
+;
+(defun relative-path-p (filename)
+  (or (< (length filename) 2)
+      (not (both-case-p (char filename 0)))
+      (not (equal (char filename 1) #\:))))
+
+
+(setf *file-separator* #\\)
+
+(defun ny:load-file () (load "*.*"))
+(defun ny:reload-file () (load "*"))
+
+
+; save the standard function to write points to a file
+;
+;(setfn s-plot-points s-plot)
+
+;(defun array-max-abs (points)
+;  (let ((m 0.0))
+;        (dotimes (i (length points))
+;          (setf m (max m (abs (aref points i)))))
+;        m))
+
+;(setf graph-width 600)
+;(setf graph-height 220)
+
+;(defun s-plot (snd &optional (n 600))
+;  (show-graphics)
+;  (clear-graphics)
+;  (cond ((soundp snd)
+;               (s-plot-2 snd n (/ graph-height 2) graph-height))
+;              (t
+;               (let ((gh (/ graph-height (length snd)))
+;                     hs)
+;                 (dotimes (i (length snd))
+;                   (setf hs (s-plot-2 (aref snd i) n (+ (/ gh 2) (* i gh)) gh hs)))))))
+;
+;
+;(defun s-plot-2 (snd n y-offset graph-height horizontal-scale)
+;  (prog ((points (snd-samples snd n))
+;                   maxpoint horizontal-scale vertical-scale)
+;    (setf maxpoint (array-max-abs points))
+;    (moveto 0 y-offset)
+;    (lineto graph-width y-offset)
+;    (moveto 0 y-offset)
+;    (cond ((null horizontal-scale)
+;               (setf horizontal-scale (/ (float graph-width) (length points)))))
+;    (setf vertical-scale (- (/ (float graph-height) 2 maxpoint)))
+;    (dotimes (i (length points))
+;      (lineto (truncate (* horizontal-scale i))
+;              (+ y-offset (truncate (* vertical-scale (aref points i))))))
+;    (format t "X Axis: ~A to ~A (seconds)\n" (snd-t0 snd) (/ (length points) (snd-srate snd)))
+;    (format t "Y Axis: ~A to ~A\n" (- maxpoint) maxpoint)
+;    (format t "~A samples plotted.\n" (length points))
+;    (return horizontal-scale)
+;    ))
+;
+; S-EDIT - run the audio editor on a sound
+;
+;(defmacro s-edit (&optional expr)
+;  `(prog ()
+;         (if ,expr (s-save ,expr 1000000000 *default-sound-file*))
+;         (system (format nil "audio_editor ~A &" 
+;                         (soundfilename *default-sound-file*)))))
+
diff --git a/Release/nyquist/test.lsp b/Release/nyquist/test.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..3bacbc62aba7d5128fddc510c084cf2670472632
--- /dev/null
+++ b/Release/nyquist/test.lsp
@@ -0,0 +1,43 @@
+
+(defun ss () (osc c5))
+
+(defun tt () (stretch 2 (snd-tapv (ss) 1.1 (scale *d* (lfo 10)) 2.2)))
+(setf *d* .01)
+
+(defun g () (play (tt)))
+
+;(set-sound-srate 10)
+;(set-control-srate 10)
+(defun rr () (stretch 10 (ramp)))
+(defun ll () (stretch 10 (lfo .5)))
+(defun xx () (snd-tapv (rr) 1.1 (ll) 2.2))
+(defun h () (snd-samples (xx) 150))
+
+(defun chorus (sound maxdepth depth rate saturation)
+  (let ((modulation (prod depth (stretch-abs 10000.0 (general-lfo rate))))
+        (offset (/ maxdepth 2.0))
+        chor)
+    (setf chor (snd-tapv sound offset modulation maxdepth))
+    (sum (prod chor saturation) (prod (seq (s-rest offset) sound)
+                                          (sum 1.0 (prod -1.0 saturation))))))
+
+
+(set-sound-srate 22050.0)
+
+(defun f ()
+ (chorus (s-read "runtime\\ah.wav") .1 .1 1 .5))
+
+(defun e ()
+ (seq (s-rest .05) (chorus (s-read "rpd.wav") .07 .07 .7 .5)))
+
+(defun d () (sum (e) (f)))
+
+(defun rou () (s-read "round.wav" :time-offset 1.18 :dur (- 8.378 1.18)))
+
+(defun rou4 () (sim (rou)
+                    (at *rd* (rou)) 
+                    (at (* *rd* 2) (rou)) 
+                    (at (* *rd* 3) (rou))))
+
+
+
diff --git a/Release/nyquist/velocity.lsp b/Release/nyquist/velocity.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..aa5226d638d2d92f8a45f7ab595e3e56fce9c5b5
--- /dev/null
+++ b/Release/nyquist/velocity.lsp
@@ -0,0 +1,24 @@
+;; velocity.lsp -- conversion routines for MIDI velocity
+;;
+;; Roger B. Dannenberg
+;; July, 2012
+
+
+(defun db-to-vel (x &optional float)
+  (linear-to-vel (db-to-linear x) float))
+
+
+(defun linear-to-vel (x &optional float)
+  (setf x (/ (- (sqrt (abs x)) 0.0239372) 0.00768553))
+  (cond (float x)
+        (t
+         (setf x (round x))
+         (max 1 (min 127 x)))))
+
+
+(defun vel-to-db (v)
+  (linear-to-db (vel-to-linear v)))
+
+
+(defun vel-to-linear (v)
+  (power (+ (* v 0.00768553) 0.0239372) 2))
diff --git a/Release/nyquist/xlinit.lsp b/Release/nyquist/xlinit.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..ae2cfda222d686d1ab6c9af64924d69fae5e883d
--- /dev/null
+++ b/Release/nyquist/xlinit.lsp
@@ -0,0 +1,67 @@
+;; xlinit.lsp -- standard definitions and setup code for XLisp
+;;
+
+
+(defun bt () (baktrace 6))
+
+(defmacro setfn (a b) 
+  `(setf (symbol-function ',a) (symbol-function ',b)))
+
+(setfn co continue)
+(setfn top top-level)
+(setfn res clean-up)
+(setfn up clean-up)
+
+;## display -- debugging print macro
+;
+; call like this (display "heading" var1 var2 ...)
+; and get printout like this:
+;   "heading : VAR1 = <value> VAR2 = <value> ...<CR>"
+;
+; returns:
+;   (let ()
+;     (format t "~A: " ,label)
+;     (format t "~A = ~A  " ',item1 ,item1)
+;     (format t "~A = ~A  " ',item2 ,item2)
+;     ...)
+;
+(defmacro display-macro (label &rest items)
+  (let ($res$)
+    (dolist ($item$ items)
+            (setq $res$ (cons
+                         `(format t "~A = ~A  " ',$item$ ,$item$)
+                         $res$)))
+    (append (list 'let nil `(format t "~A : " ,label))
+            (reverse $res$)
+            '((terpri)))))
+
+
+(defun display-on () (setfn display display-macro) t)
+(defun display-off () (setfn display or) nil)
+(display-on)
+
+; (objectp expr) - object predicate
+;
+;this is built-in: (defun objectp (x) (eq (type-of x) 'OBJ))
+
+
+; (filep expr) - file predicate
+;
+(defun filep (x) (eq (type-of x) 'FPTR))
+
+(load "profile.lsp" :verbose NIL)
+
+; (setf *breakenable* t) -- good idea, but set it in init.lsp, so user can decide
+(setq *tracenable* nil)
+
+(defmacro defclass (name super locals class-vars)
+  (if (not (boundp name))
+    (if super
+    `(setq ,name (send class :new ',locals ',class-vars ,super))
+    `(setq ,name (send class :new ',locals ',class-vars)))))
+
+;(cond ((boundp 'application-file-name)
+;       (load application-file-name)))
+
+(setq *gc-flag* t)
+
diff --git a/Release/nyquist/xm.lsp b/Release/nyquist/xm.lsp
new file mode 100644
index 0000000000000000000000000000000000000000..75bdea2c1bed61087aaa3159f65e3a63be9cf60b
--- /dev/null
+++ b/Release/nyquist/xm.lsp
@@ -0,0 +1,2767 @@
+;; X-Music, inspired by Commmon Music
+
+#|
+PATTERN SEMANTICS
+
+Patterns are objects that are generally accessed by calling (next
+pattern). Each call returns the next item in an infinite sequence
+generated by the pattern. Items are organized into periods. You can
+access all (remaining) items in the current period using (next pattern
+t). 
+
+Patterns mark the end-of-period with +eop+, a distinguished atom. The
++eop+ markers are filtered out by the next() function but returned by
+the :next method. 
+
+Pattern items may be patterns. This is called a nested pattern.  When
+patterns are nested, you return a period from the innermost pattern,
+i.e. traversal is depth-first. This means when you are using something
+like random, you select a random pattern and get an item from it. The
+next time you handle :next, you get another item from the same pattern
+until the pattern returns +eonp+, which you can read as "end of nested
+pattern". Random would then advance to the next random pattern and get
+an item from it. 
+
+While generating from a nested pattern, you might return many periods
+including +eop+, but you do not advance to the next pattern at any
+given level until that level receives +eonp+ from the next level down. 
+
+With nested patterns, i.e. patterns with items that are patterns, the
+implementation requires that *all* items must be patterns. The
+application does *not* have to make every item a pattern, so the
+implementation "cleans up" the item list: Any item that is not a
+pattern is be replaced with a cycle pattern whose list contains just
+the one item. 
+
+PATTERN LENGTH
+
+There are two sorts of cycles and lengths. The nominal pattern
+behavior, illustrated by cycle patterns, is to cycle through a
+list. There is a "natural" length computed by :start-period and stored
+in count that keeps track of this. 
+
+The second cycle and length is established by the :for parameter,
+which is optional. If a number or pattern is provided, it controls the
+period length and overrides any default periods. When :for is given,
+count is set and used as a counter to count the items remaining in
+a period. 
+
+To summarize, there are 3 ways to determine lengths: 
+
+1) The length is implicit. The length can be computed by :start-period
+and turned into an explicit length stored in count.
+
+2) The length is explicitly set with :for. This overrides the implicit
+length. The explicit length is stored as count that tells how many
+more items to generate in the current period. 
+
+3) The length can be generated by a pattern. The pattern is evaluated
+in :start-period to generate an explicit length. 
+
+In case (1), a pattern object does not return +eonp+ to the next level
+up unless it receives an +eonp+ from one level down *and* is at the
+end of its period. E.g. in the random pattern, if there are three
+nested patterns, random must see +eonp+ three times and make three
+random pattern selections before returning +eonp+ to the next level
+up. This is the basic mechanism for achieving a "depth-first"
+expansion of patterns.
+
+However, there is still the question of periods. When a nested pattern
+returns a period, do the containing pattern return that period or
+merge the period with other periods from other nested patterns? The
+default is to return periods as they are generated by sub-patterns. In
+other words, when a nested pattern returns +eop+ (end of period), that
+token is returned by the :next message. Thus, in one "natural" cycle
+of a pattern of patterns, there can be many periods (+eop+'s) before
++eonp+ is returned, marking the end of the "natural" pattern at this
+level. 
+
+The alternative strategy, which is to filter out all the +eop+'s and
+form one giant pattern that runs up to the natural length (+eonp+) for
+this level, can be selected by setting the :merge parameter to true.
+Note that :merge is ignored if :for is specified because :for says
+exactly how many items are in each period.
+
+The Copier pattern is an interesting case. It's :start-pattern should
+get the next period from its sub-pattern, a repeat count from the
+:repeat pattern, and a boolean from the :merge pattern. Then, it
+should make that many copies, returning them as multiple periods or as
+one merged one, depending on :merge, followed by +eonp+, after which
+:start-pattern is called and the process repeats. But if :for 10 is
+provided, this means we need to return a single period of 10 items. We
+call :start-pattern, then repeat the sub-pattern's period until we
+have 10 items. Thus, we ignore the :merge flag and :repeat count.
+This makes Copier with a :for parameter equivalent to Cycle with a
+single sub-pattern in a list. If you think :for should not override
+these parameters (:repeat and :merge), you can probably get what you
+want by using a Length pattern to regroup the output of a Copier.
+
+IMPLEMENTATION
+
+Most pattern behavior is implemented in a few inherited methods.
+
+:next gets the next item or period.  If there is a length-pattern
+(from :for), :next groups items into periods, filtering out +eop+ and
++eonp+. If there is no length-pattern, :next passes +eop+ through and
+watches for +eonp+ to cause the pattern to re-evaluate pattern
+parameters.
+
+Several methods are implemented by subclasses of pattern-class:
+
+:START-PERIOD is called before the first advance and before the first
+item of a period controlled by :for. It sets count to the "natural"
+length of the period. HAVE-CURRENT will be set to false.
+
+:ADVANCE advances to the next item in the pattern. If there are nested
+patterns, advance is called to select the first nested pattern, then
+items are returned until +eonp+ is seen, then we advance to the next
+pattern, etc. After :ADVANCE, HAVE-CURRENT is true.
+
+CURRENT is set by advance to the current item. If this has nested
+patterns, current is set to a pattern, and  the pattern stays there in
+current until advance is  called, either at the end of period or when
++eonp+ is seen.
+
+HAVE-CURRENT is a boolean to tell when CURRENT is valid.
+
+IS-NESTED - set when there are nested patterns. If there are, make all
+items of any nested pattern be patterns (no mix of patterns and
+non-patterns is allowed; use
+    (MAKE-CYCLE (LIST item)) 
+to convert a non-pattern to a pattern). 
+
+Patterns may be shared, so the state machines may be advanced by more
+than one less-deeply nested pattern. Thus, patterns are really DAGs
+and not trees. Since patterns are hard enough to understand, the
+precise order of evaluation and therefore the behavior of shared
+patterns in DAGs may not be well-defined. In this implementation
+though, we only call on state machines to advance as needed (we try
+not to read out whole periods).
+
+The next() function gets an item or period by calling :next.
+
+The :next method is shared by all pattern sub-classes and behaves
+differently with :for vs. no :for parameter. With the :for parameter,
+we just get items until the count is reached, but getting items is
+a bit tricky, because the normal behavior (without :for) might reach
+the end of the "natural" period (+eonp+) before count is
+reached. So somehow, we need to override count. We could just set
+count the count, but count is going to count items and due to
+empty periods, count could go to zero before count does. We could
+set count = 1000 * count with the idea that we're probably in an
+infinite loop generating empty periods forever if count ever reaches
+zero. 
+
+But then what about the Heap pattern? If count is greater than the
+heap size, what happens when the heap is empty? Or should Heap not
+allow :for? There are other "problem" patterns, and not all Vers. 1
+patterns allowed :for, so let's make list of patterns that could use
+:for: 
+
+:for is OK                      :for is not OK
+----------                      --------------
+cycle                           heap
+line                            accumulation
+random                          copier
+palindrome                      length
+accumulate                      window
+sum
+product
+eval
+markov
+
+It seems that we could use :for for all patterns and just extend the
+behavior a bit, e.g. when the heap runs out, replenish it (without
+getting another period from a sub-pattern, if any; accumulation could
+just start over; copier could cycle as described earlier; length
+really should not allow :for, and window could just generate :for
+items before reevaluating :skip and :pattern-length parameters.
+
+To implement this, the subclass patterns need :advance to do the right
+next thing even if we are beyond the "natural" period. :advance should
+go to the next sub-pattern or item without returning +eop+ or getting
+the next item from any sub-pattern.
+
+state transitions are based on count and something like this:
+count
+nil -> actions: :start-period, don't return, set count
+N -> N-1, actions: :advance if not have-current, return next item
+0 -> -1, actions: return +eop+
+-1 -> nil, actions: return +eonp+
+
+
+def :next()
+    if length-pattern: // the :for parameter value    
+        if null(count): // initial state before every period
+            var forcount = next(length-pattern) // must be a number 
+            // compute forcount first and pass to start-period in case there
+            // is no "natural" period length. If there is a "natural" period,
+            // the forcount parameter is ignored (the usual case)
+            self.:start-period(forcount)
+            have-current = false
+            // :start-period() sets count, but we replace it with :for parameter
+            count = forcount
+        if count == 0:
+            count = -1
+            return +eop+
+        if count == -1:
+            count = nil
+            return +eonp+
+        while true 
+            // otherwise, here is where we return N items
+            if not have-current 
+                self.:advance() 
+            if not is-nested
+                // now current is updated 
+                have-current = false 
+                count = count - 1 
+                return current
+            // nested, so get item from sub-pattern
+            rslt = current.:next
+            if rslt == +eonp+
+                // time to advance because sub-pattern has played out
+                have-current = false
+            elif rslt == +eop+
+                nil // skip ends of periods, we're merging them
+            // we got a real item to return
+            else
+                count = count - 1
+                return rslt
+    // here, we have no length-pattern, so use "natural" periods
+    // count is null, and we use count
+    while true 
+        if null(count):
+            have-current = false 
+            self.:start-period()
+        if is-nested: 
+            if count == 0:
+                if merge-flag: // we filtered out +eop+ so return one here
+                    count == -1
+                    return +eop+
+                else 
+                    count = nil 
+                    return +eonp+
+            if count == -1
+                count = nil
+                return +eonp+
+       else
+           if count = 0:
+               count = -1
+               return +eop+
+           if count == -1:
+               count = nil
+               return +eonp+
+        // count is a number > 0
+        if not have-current:
+            self.:advance
+            have-current = true
+        if not is-nested
+           have-current = false
+           count = count - 1
+           return current
+       // nested, so get sub-pattern's next item or +eonp+ or +eop+
+       rslt = current.:next
+       if rslt == +eonp+
+           have-current = false // force advance next time, don't
+                                // return +eonp+ until count == 0
+       else if rslt == +eop+ and merge-flag:
+           nil // iterate, skipping this +eop+ to merge periods
+       else
+           return rslt // either +eop+ or a real item
+        
+            
+If the input is a list of patterns, then the pattern selects patterns
+from the list, and the internal state advances as each selected
+pattern completes a period. In this case, there is no way to control
+the number of elements drawn from each selected pattern -- the number
+is always the length of the period returned by the selected
+pattern. If :for is specified, this controls the length of the period
+delivered to the next less deeply nested  pattern, but the delivered
+period may be a mix of elements from the more deeply nested patterns.
+|#
+
+(setf SCORE-EPSILON 0.000001)
+
+(setf pattern-class 
+  (send class :new '(current have-current is-nested name count merge-flag
+                     merge-pattern length-pattern trace)))
+
+;; sub-classes should all call (send-super :isnew length-pattern name trace)
+;;
+(send pattern-class :answer :isnew '(mp lp nm tr)
+  '((setf merge-pattern mp length-pattern lp name nm trace tr)
+    (xm-traceif "pattern-class :isnew nm" nm "name" name)))
+
+(defun patternp (x) 
+  (and (objectp x) (send x :isa pattern-class)))
+
+(setf +eop+ '+eop+)
+(setf +eonp+ '+eonp+) ;; end of nested period, this indicates you
+   ;; should advance yourself and call back to get the next element
+
+(defun check-for-list (lis name)
+  (if (not (listp lis))
+      (error (format nil "~A, requires a list of elements" name))))
+
+(defun check-for-list-or-pattern (lis name)
+  (if (not (or (listp lis) (patternp lis)))
+      (error (format nil "~A, requires a list of elements or a pattern" name))))
+
+(defun list-has-pattern (lis)
+  (dolist (e lis) 
+    (if (patternp e) (return t))))
+
+(defun is-homogeneous (lis)
+  (let (type)
+    (dolist (elem lis t)
+      (cond ((null type)
+             (setf type (if (patternp elem) 'pattern 'atom)))
+            ((and (eq type 'pattern)
+                  (not (patternp elem)))
+             (return nil))
+            ((and (eq type 'atom)
+                  (patternp elem))
+             (return nil))))))
+
+(defun make-homogeneous (lis traceflag)
+  (cond ((is-homogeneous lis) lis)
+        (t
+         (mapcar #'(lambda (item)
+                     (if (patternp item) item 
+                         (make-cycle (list item)
+                          ;; help debugging by naming the new pattern
+                          ;; probably, the name could be item, but
+                          ;; here we coerce item to a string to avoid
+                          ;; surprises in code that assumes string names.
+                          :name (format nil "~A" item) :trace traceflag)))
+                 lis))))
+
+
+;; used for both "advanced to" and ":next returns" messages
+;;
+(send pattern-class :answer :write-trace '(verb value)
+  '((format t "pattern ~A ~A ~A~%"
+              (if name name "<no-name>")
+              verb
+              (if (patternp value) 
+                  (if (send value :name)
+                      (send value :name)
+                      "<a-pattern>")
+                  value))))
+
+
+;; :next returns the next value, including +eop+ and +eonp+ markers
+;; 
+(send pattern-class :answer :next '()
+  '((xm-traceif ":next of" name "is-nested" is-nested "length-pattern" length-pattern)
+    (incf xm-next-nesting)
+    (let ((rslt 
+           (cond (length-pattern (send self :next-for))
+                 (t (send self :next-natural)))))
+       (if trace (send self :write-trace ":next returns" rslt))
+       (xm-traceif-return ":next" self rslt))))
+           
+
+;; :next-for returns the next value, including +eop+ and +eonp+ markers
+;;     this code handles the cases where :for is specified, so the length
+;;     of each period is explicitly given, non intrinsic to the pattern
+;;
+(send pattern-class :answer :next-for '()
+  '((block pattern:next-for-block ;; so we can return from inside while loop
+     (cond ((null count)
+            (let ((forcount (next length-pattern))) 
+              ;; in the case of window-class, there is no "natural" length
+              ;; so for that case, we pass in forcount
+              (send self :start-period forcount) ;; :start-period sets count, 
+              (setf count forcount)   ;; but it is replaced here by a number
+              (setf have-current nil))))
+            ;; note that merge-flag is ignored if length-pattern
+     (cond ((zerop count)
+            (setf count -1)
+            (return-from pattern:next-for-block +eop+))
+           ((eql count -1)
+            (setf count nil)
+            (return-from pattern:next-for-block +eonp+)))
+     (while t ;; after rejecting special cases, here is where we return N items
+       (cond ((not have-current)
+              (send self :advance)
+              (setf have-current t)
+              (if trace (send self :write-trace "advanced to" current))))
+       (cond ((not is-nested) ;; now current is updated
+              (setf have-current nil)
+              (decf count)
+              (return-from pattern:next-for-block current)))
+       ;; is-nested, so get item from sub-pattern
+       (let ((rslt (send current :next)))
+         (cond ((eq rslt +eonp+)
+                ;; time to advance because sub-pattern has played out
+                (setf have-current nil))
+               ((eq rslt +eop+)) ;; skip ends of periods; we merge them
+               (t
+                (decf count)
+                (return-from pattern:next-for-block rslt))))))))
+                
+;; :next-natural returns the next value, including +eop+ and +eonp+ markers
+;;     this code handles the cases where :for is not specified, so the length
+;;     of each period is implicitly determined from the pattern
+;;
+(send pattern-class :answer :next-natural '()
+  '((block pattern:next-natural-block ;; so we can return from inside while loop
+     (xm-traceif ":next-natural current" current)
+     (while t
+       (cond ((null count)
+              (setf have-current nil)
+              ;; :merge parameter is not used by every pattern, but it does not
+              ;; hurt to evaluate it here
+              (setf merge-flag (if merge-pattern (next merge-pattern)))
+              (send self :start-period nil))) ;; sets count
+       (xm-traceif "count" count "is-nested" is-nested)
+       (cond (is-nested
+              (cond ((zerop count)
+                     (cond (merge-flag  ;; we filtered out +eop+; return one here
+                            (setf count -1)
+                            (return-from pattern:next-natural-block +eop+))
+                           (t
+                            (setf count nil)
+                            (return-from pattern:next-natural-block +eonp+))))
+                    ((eql count -1)
+                     (setf count nil)
+                     (return-from pattern:next-natural-block +eonp+))))
+             (t
+              (cond ((zerop count)
+                     (setf count -1)
+                     (return-from pattern:next-natural-block +eop+))
+                    ((eql count -1)
+                     (setf count nil)
+                     (return-from pattern:next-natural-block +eonp+)))))
+       (cond ((not have-current)
+              (send self :advance)
+              (setf have-current t)
+              (if trace (send self :write-trace "advanced to" current))
+              (xm-traceif ":advance current" current)))
+       (cond ((not is-nested)
+              (setf have-current nil)
+              (decf count)
+              (return-from pattern:next-natural-block current)))
+       ;; nested, so get sub-pattern's next item or +eonp+ or +eop+
+       (let ((rslt (send current :next)))
+         (xm-traceif "in :next-natural got from sub-pattern " rslt)
+         (cond ((eq rslt +eonp+)
+                (setf have-current nil) ;; force advance next time, don't
+                                        ;; return +eonp+ until count == 0
+                (decf count))
+               ((and (eq rslt +eop+) merge-flag)) ;; iterate, skip +eop+
+               (t
+                (return-from pattern:next-natural-block rslt))))))))
+
+
+
+(send pattern-class :answer :is-nested '() '(is-nested))
+
+
+(send pattern-class :answer :name '() '(name))
+
+
+(send pattern-class :answer :set-current '(c)
+  '((setf current c)
+    (let ((value
+           (if (patternp current) 
+               (send current :name)
+               current)))
+      (xm-traceif ":set-current" name "value" value)
+      )))
+
+
+;; get-pattern-name - used for debugging, handles non-patterns safely
+;;
+(defun get-pattern-name (pattern)
+  (cond ((patternp pattern) (send pattern :name))
+        (t pattern)))
+
+
+;; more debugging support
+(setf xm-next-nesting -1)
+(setf *xm-trace* nil)
+
+;; use xm-traceif for verbose printing. It only prints if *xm-trace*
+;;
+(defun xm-traceif (&rest items)
+    (if *xm-trace* (apply #'xm-trace items)))
+
+;; use xm-traceif-return for verbose printing of return values.
+;; It only prints if *xm-trace*. Includes decrement of xm-next-nesting.
+;;
+(defun xm-traceif-return (method pattern val)
+    (xm-traceif method (get-pattern-name pattern) "returning" val)
+    (decf xm-next-nesting)
+    val)
+
+;; use xm-trace for normal tracing enabled by the trace flag in patterns
+;;
+(defun xm-trace (&rest items)
+  (princ "|")
+  (dotimes (i xm-next-nesting) (princ "   |"))
+  (dolist (item items) (princ item) (princ " "))
+  (terpri))
+
+
+;; next -- get the next element in a pattern
+;;
+;; any non-pattern value is simply returned
+;;
+(defun next (pattern &optional period-flag) 
+  (incf xm-next-nesting)
+  (xm-traceif "next" (get-pattern-name pattern) period-flag)
+  (cond ((and period-flag (patternp pattern))
+         (let (rslt elem)
+           (incf xm-next-nesting)
+           (xm-traceif "next sending :next to" (get-pattern-name pattern))
+           (while (not (eq (setf elem (send pattern :next)) +eop+))
+               (xm-traceif "next got" elem "from" (get-pattern-name pattern))
+               (if (not (eq elem +eonp+)) 
+                   (push elem rslt))
+               (if (null elem) (error "got null elem"))) ;;;;;;;; DEBUG ;;;;;;;;;;;
+           (decf xm-next-nesting)
+           (xm-traceif-return "next" pattern (reverse rslt))))
+        (period-flag
+         (xm-traceif "next with period-flag" (get-pattern-name pattern))
+         (error (format nil "~A, next expected a pattern"
+                            (get-pattern-name pattern))))
+        ((patternp pattern)
+         (xm-traceif "next with pattern" (get-pattern-name pattern) pattern)
+         (let (rslt)
+           (dotimes (i 10000 (error
+                 (format nil
+                  "~A, just retrieved 10000 empty periods -- is there a bug?"
+                  (get-pattern-name pattern))))
+             (if (not (member (setf rslt (send pattern :next)) 
+                              '(+eop+ +eonp+)))
+                 (return (xm-traceif-return "next" pattern rslt))))))
+        (t ;; pattern not a pattern, so just return it:
+         (xm-traceif "next not pattern" pattern)
+         (xm-traceif-return "next" pattern pattern))))
+
+;; ---- LENGTH Class ----
+
+(setf length-class 
+  (send class :new '(pattern length-pattern) '() pattern-class))
+
+(send length-class :answer :isnew '(p l nm tr)
+  '((send-super :isnew nil l nm tr) ;; note: no merge pattern is applicable
+    (setf pattern p)))
+
+;; note that count is used as a flag as well as a counter.
+;; If count is nil, then the pattern-length has not been
+;; determined. Count is nil intitially and again at the 
+;; end of each period. Otherwise, count is an integer
+;; used to count down the number of items remaining in 
+;; the period.
+
+(send length-class :answer :start-period '(forcount)
+  '((setf count (next length-pattern))))
+
+(send length-class :answer :advance '()
+  '((send self :set-current (next pattern))))
+
+(defun make-length (pattern length-pattern &key (name "length") trace)
+  (send length-class :new pattern length-pattern name trace))
+
+;; ---- CYCLE Class ---------
+
+(setf cycle-class (send class :new 
+                        '(lis cursor lis-pattern)
+                        '() pattern-class))
+
+(send cycle-class :answer :isnew '(l mp for nm tr)
+  '((send-super :isnew mp for nm tr)
+    (cond ((patternp l)
+           (setf lis-pattern l))
+          ((listp l)
+           (send self :set-list l tr))
+          (t
+           (error (format nil "~A, expected list" nm) l)))))
+
+
+(send cycle-class :answer :set-list '(l tr)
+  '((setf lis l)
+    (check-for-list lis "cycle-class :set-list")
+    (setf is-nested (list-has-pattern lis))
+    (setf lis (make-homogeneous lis tr))))
+
+
+(send cycle-class :answer :start-period '(forcount)
+  '((xm-traceif "cycle-class :start-period" "lis-pattern" 
+                (get-pattern-name lis-pattern) "lis" lis "count" count
+                "length-pattern" (get-pattern-name length-pattern))
+    (cond (lis-pattern
+           (send self :set-list (next lis-pattern t) trace)))
+    ;; notice that list gets reset at the start of the period
+    (setf cursor lis)
+    (if (null count)
+        (setf count (length lis)))))
+  
+
+(send cycle-class :answer :advance '()
+  '((cond ((and (null cursor) lis)
+           (setf cursor lis))
+          ((null cursor)
+           (error (format nil "~A, :advance - no items" name))))
+    (send self :set-current (car cursor))
+    (pop cursor)))
+
+
+(defun make-cycle (lis &key merge for (name "cycle") trace)
+   (check-for-list-or-pattern lis "make-cycle")
+   (send cycle-class :new lis merge for name trace))
+
+;; ---- LINE class ----
+
+(setf line-class (send class :new '(lis cursor lis-pattern) 
+                       '() pattern-class))
+
+(send line-class :answer :isnew '(l mp for nm tr)
+  '((send-super :isnew mp for nm tr)
+    (cond ((patternp l)
+           (setf lis-pattern l))
+          ((listp l)
+           (send self :set-list l tr))
+          (t
+           (error (format nil "~A, expected list" nm) l)))))
+
+
+(send line-class :answer :set-list '(l tr)
+  '((setf lis l)
+    (check-for-list lis "line-class :set-list")
+    (setf is-nested (list-has-pattern lis))
+    (setf lis (make-homogeneous l tr))
+    (setf cursor lis)))
+
+
+(send line-class :answer :start-period '(forcount)
+  '((cond (lis-pattern
+           (send self :set-list (next lis-pattern t) trace)
+           (setf cursor lis)))
+    (if (null count)
+        (setf count (length lis)))))
+
+
+(send line-class :answer :advance '()
+  '((cond ((null cursor)
+           (error (format nil "~A, :advance - no items" name))))
+    (send self :set-current (car cursor))
+    (if (cdr cursor) (pop cursor))))
+  
+
+(defun make-line (lis &key merge for (name "line") trace)
+   (check-for-list-or-pattern lis "make-line")
+   (send line-class :new lis merge for name trace))
+
+
+;; ---- RANDOM class -----
+
+(setf random-class (send class :new 
+       '(lis lis-pattern len previous repeats mincnt maxcnt) 
+       '() pattern-class))
+
+;; the structure is (value weight weight-pattern max max-pattern min min-pattern)
+(setfn rand-item-value car)
+(defun set-rand-item-value (item value) (setf (car item) value))
+
+(setfn rand-item-weight cadr)
+(defun set-rand-item-weight (item weight) (setf (car (cdr item)) weight))
+(setfn rand-item-weight-pattern caddr)
+
+(setfn rand-item-max cadddr)
+(defun set-rand-item-max (item max) (setf (car (cdddr item)) max))
+(defun rand-item-max-pattern(item) (car (cddddr item)))
+
+(defun rand-item-min (lis) (cadr (cddddr lis)))
+(defun set-rand-item-min (item min) (setf (car (cdr (cddddr item))) min))
+(defun rand-item-min-pattern(item) (car (cddr (cddddr item))))
+
+
+(defun select-random (len lis previous repeats mincnt maxcnt)
+  (let (sum items r)
+    (cond ((zerop len)
+           (break "random-class has no list to choose from")
+           nil)
+          (t
+           (setf sum 0)
+           (dolist (item lis)
+             (setf sum (+ sum (rand-item-weight item))))
+           (setf items lis)
+           (setf r (rrandom))
+           (setf sum (* sum r))
+           (loop
+             (setf sum (- sum (rand-item-weight (car items))))
+             (if (<= sum 0) (return (car items)))
+             (setf items (cdr items)))))))
+
+
+(defun random-convert-spec (item)
+  ;; convert (value :weight wp :min min :max max) to (value nil wp max min)
+  (let (value (wp 1) minpat maxpat lis)
+    (setf value (car item))
+    (setf lis (cdr item))
+    (while lis
+      (cond ((eq (car lis) :weight)
+             (setf wp (cadr lis)))
+            ((eq (car lis) :min)
+             (setf minpat (cadr lis)))
+            ((eq (car lis) :max)
+             (setf maxpat (cadr lis)))
+            (t
+             (error "(make-random) item syntax error" item)))
+      (setf lis (cddr lis)))
+    (list value nil wp nil maxpat nil minpat)))
+
+
+(defun random-atom-to-list (a)
+  (if (atom a)
+      (list a nil 1 nil nil nil nil)
+      (random-convert-spec a)))
+
+
+(send random-class :answer :isnew '(l mp for nm tr)
+  ;; there are two things we have to normalize:
+  ;; (1) make all items lists
+  ;; (2) if any item is a pattern, make all items patterns
+  '((xm-traceif "random :isnew list" l "merge" mp "for" for "name" nm "trace" tr)
+    (send-super :isnew mp for nm tr)
+    (cond ((patternp l)
+           (setf lis-pattern l))
+          ((listp l)
+           (send self :set-list l))
+          (t
+           (error (format nil "~A, expected list") l)))))
+
+
+(send random-class :answer :set-list '(l)
+  '((check-for-list l "random-class :set-list")
+    (setf lis (mapcar #'random-atom-to-list l))
+    ; (display "random set-list" lis)
+    (dolist (item lis)
+      (if (patternp (rand-item-value item))
+          (setf is-nested t)))
+    (if is-nested
+        (mapcar #'(lambda (item)
+                    (if (not (patternp (rand-item-value item)))
+                        (set-rand-item-value item 
+                         (make-cycle (list (rand-item-value item))))))
+                lis))
+    (xm-traceif "random is-new" name lis)
+    (setf repeats 0)
+    (setf len (length lis))))
+
+    
+(send random-class :answer :start-period '(forcount)
+  '((xm-traceif "random-class :start-period" name "count" count "len" len 
+                "lis" lis "lis-pattern" (get-pattern-name lis-pattern))
+    (cond (lis-pattern
+           (send self :set-list (next lis-pattern t))))
+    (if (null count)
+        (setf count len))
+    (dolist (item lis)
+      (set-rand-item-weight item (next (rand-item-weight-pattern item)))
+      (set-rand-item-max item (next (rand-item-max-pattern item)))
+      (set-rand-item-min item (next (rand-item-min-pattern item))))
+    ; (display "random start-period" lis-pattern lis)
+    ))
+
+
+(send random-class :answer :advance '()
+  '((let (selection (iterations 0))
+      (xm-traceif "random-class :advance" name "mincnt" mincnt 
+                  "repeats" repeats)
+      (cond ((and mincnt (< repeats mincnt))
+             (setf selection previous))
+            (t
+             (setf selection
+                   (select-random len lis previous repeats mincnt maxcnt))))
+      (loop ; make sure selection is ok, otherwise try again
+        (cond ((and (eq selection previous)
+                    maxcnt
+                    (>= repeats maxcnt)) ; hit maximum limit, try again
+               (setf selection
+                     (select-random len lis previous repeats mincnt maxcnt))
+               (incf iterations)
+               (cond ((> iterations 10000)
+                      (error
+                        (format nil
+                         "~A, unable to pick next item after 10000 tries"
+                         name)
+                       lis))))
+              (t (return)))) ; break from loop, we found a selection
+
+        ; otherwise, we are ok
+        ; notice that we could have selected based on an older maxcnt and
+        ;   maxcnt may now be smaller. This is allowed. Perhaps another
+        ;   rule would be better, e.g. update maxcnt and check against it
+        ;   with each selection.
+        (if (not (eq selection previous))
+            (setf repeats 1)
+            (incf repeats))
+        (setf mincnt (rand-item-min selection))
+        (setf maxcnt (rand-item-max selection))
+        (setf previous selection)
+        (xm-traceif "new selection" name "repeats" repeats "mincnt" mincnt 
+                    "maxcnt" maxcnt "selection" selection)
+        (send self :set-current (rand-item-value selection)))))
+      
+
+(defun make-random (lis &key merge for (name "random") trace)
+   (check-for-list-or-pattern lis "make-random")
+   (send random-class :new lis merge for name trace))
+
+
+;; ---- PALINDROME class -----
+
+#| Palindrome includes elide, which is either t, nil, :first, or :last.
+The pattern length is the "natural" length of the pattern, which goes
+forward and backward through the list. Thus, if the list is of length N,
+the palindrome length depends on elide as follows:
+    elide   length
+     nil      2N
+     t        2N - 2
+   :first     2N - 1
+   :last      2N - 1
+If elide is a pattern, and if length is not specified, then length should
+be computed based on elide. 
+|#
+
+
+(setf palindrome-class (send class :new 
+                         '(lis revlis lis-pattern 
+                           direction elide-pattern
+                           elide cursor)
+                         '() pattern-class))
+
+(send palindrome-class :answer :set-list '(l tr)
+  '((setf lis l)
+    (check-for-list lis "palindrome-class :start-period")
+    (setf is-nested (list-has-pattern lis))
+    (setf lis (make-homogeneous l tr))
+    (send self :set-cursor)))
+
+(send palindrome-class :answer :set-cursor '()
+    '((setf revlis (reverse lis)
+            direction t
+            cursor lis)))
+
+
+(send palindrome-class :answer :isnew '(l e mp for nm tr)
+  '((send-super :isnew mp for nm tr)
+    (cond ((patternp l)
+           (setf lis-pattern l))
+          ((listp l)
+           (send self :set-list l tr))
+          (t
+           (error (format nil "~A, expected list" nm) l)))
+    (setf elide-pattern e)))
+
+
+(send palindrome-class :answer :start-period '(forcount)
+  '((cond (lis-pattern
+           (send self :set-list (next lis-pattern t) trace)))
+    ;; like cycle, list is reset at the start of the period
+    (send self :set-cursor)
+    (setf elide (next elide-pattern))
+    (if (and elide (null lis))
+        (error (format nil "~A, cannot elide if list is empty" name)))
+    (if (null count)
+        (setf count (- (* 2 (length lis))
+                       (if (member elide '(:first :last)) 
+                           1
+                           (if elide 2 0)))))
+    (if (<= count 0)
+        (error (format nil "palindrome ~A period is <= 0"
+                           (get-pattern-name self))))))
+
+
+(send palindrome-class :answer :next-item '()
+  '((send self :set-current (car cursor))
+    (pop cursor)
+    (cond ((and cursor (not (cdr cursor))
+                (or (and direction (member elide '(:last t)))
+                    (and (not direction) (member elide '(:first t)))))
+           (pop cursor)))))
+
+
+(send palindrome-class :answer :advance '()
+  '(
+    (cond (cursor
+           (send self :next-item))
+          (direction ;; we're going forward
+           (setf direction nil) ;; now going backward
+           (setf cursor revlis)
+           (xm-traceif "palindrome at end" (get-pattern-name self) 
+                       "current" (get-pattern-name (car cursor)))
+           (send self :next-item))
+          (t ;; direction is reverse
+           (setf direction t)
+           (setf cursor lis)
+           (send self :next-item)))))
+
+
+(defun make-palindrome (lis &key elide merge for (name "palindrome") trace)
+  (check-for-list-or-pattern lis "make-palindrome")
+  (send palindrome-class :new lis elide merge for name trace))
+
+
+;; ================= HEAP CLASS ======================
+
+;; to handle the :max keyword, which tells the object to avoid
+;; repeating the last element of the previous period:
+;;
+;; maxcnt = 1 means "avoid the repetition"
+;; check-repeat signals we are at the beginning of the period and must check
+;; prev holds the previous value (initially nil)
+;; after each item is generated, check-repeat is cleared. It is
+;; recalculated when a new period is started.
+
+(setf heap-class (send class :new '(lis used maxcnt maxcnt-pattern prev
+                                    check-repeat lis-pattern len)
+                       '() pattern-class))
+
+(send heap-class :answer :isnew '(l mp for mx nm tr)
+  '((send-super :isnew mp for nm tr)
+    (cond ((patternp l)
+           (setf lis-pattern l))
+          ((listp l)
+           ; make a copy of l to avoid side effects
+           (send self :set-list (append l nil) tr))
+          (t
+           (error (format nil "~A, expected list" nm) l)))
+    (cond ((patternp mx)
+           (setf maxcnt-pattern mx))
+          ((not (numberp mx))
+           (error (format nil "~A, expected number" nm) mx))
+          (t
+           (setf maxcnt mx)))))
+
+
+(send heap-class :answer :set-list '(l tr)
+  '((setf lis l)
+    (check-for-list lis "heap-class :set-list")
+    (setf is-nested (list-has-pattern lis))
+    (setf lis (make-homogeneous lis tr))
+    (setf len (length lis))))
+
+
+(send heap-class :answer :start-period '(forcount)
+  '((xm-traceif "heap-class :start-period" name "lis-pattern" 
+                (get-pattern-name lis-pattern) "count" count "lis" lis)
+    (cond (lis-pattern
+           (send self :set-list (next lis-pattern t) trace)))
+    (cond (maxcnt-pattern
+           (setf maxcnt (next maxcnt-pattern))))
+    ; start of period -- may need to avoid repeating previous item
+    (if (= maxcnt 1) (setf check-repeat t))
+    (if (null count)
+        (setf count len))))
+
+    
+(defun delete-first (elem lis)
+  (cond ((null lis) nil)
+        ((eq elem (car lis))
+         (cdr lis))
+        (t
+         (cons (car lis) (delete-first elem (cdr lis))))))
+
+
+;; NO-DISTINCT-ELEM -- check if any element of list is not val
+;;
+(defun no-distinct-elem (lis val)
+  (not 
+    (dolist (elem lis)
+      (if (not (equal elem val))
+          ;; there is a distinct element, return t from dolist
+          (return t)))))
+    ;; if no distinct element, dolist returns nil, but this is negated
+    ;; by the NOT so the function will return t
+
+
+(send heap-class :answer :advance '()
+  '((cond ((null lis)
+           (setf lis used)
+           (setf used nil)))
+    (let (n elem)
+      (cond ((and check-repeat (no-distinct-elem lis prev))
+             (error (format nil "~A, cannot avoid repetition, but :max is 1"
+                                name))))
+      (loop 
+        (setf n (random (length lis)))
+        (setf elem (nth n lis))
+        (if (or (not check-repeat) (not (equal prev elem))) 
+            (return))) ;; loop until suitable element is chosen
+      (setf lis (delete-first elem lis))
+      (push elem used)
+      (setf check-repeat nil)
+      (setf prev elem)
+      (send self :set-current elem))))
+
+(defun make-heap (lis &key merge for (max 2) (name "heap") trace)
+  (send heap-class :new lis merge for max name trace))
+
+;;================== COPIER CLASS ====================
+
+(setf copier-class (send class :new '(sub-pattern repeat repeat-pattern 
+                                      period cursor) 
+                                    '() pattern-class))
+
+(send copier-class :answer :isnew '(p r m for nm tr)
+  '((send-super :isnew m for nm tr)
+    (setf sub-pattern p repeat-pattern r)))
+
+
+#| copier-class makes copies of periods from sub-pattern
+
+If merge is true, the copies are merged into one big period.
+If merge is false, then repeat separate periods are returned.
+If repeat is negative, then -repeat periods of sub-pattern
+are skipped.
+
+merge-flag and repeat are computed from merge-pattern and 
+repeat-pattern initially and after making repeat copies
+
+To repeat individual items, set the :for keyword parameter of
+the sub-pattern to 1.
+|#
+
+(send copier-class :answer :start-period '(forcount)
+  '((cond ((null count) 
+           (cond ((or (null repeat) (zerop repeat))
+                  (send self :really-start-period))
+                 (t
+                  (setf count (length period))))))))
+
+
+(send copier-class :answer :really-start-period '()
+  '((xm-traceif "copier-class :really-start-period" name "count" count)
+    (setf repeat (next repeat-pattern))
+    (while (minusp repeat)
+      (dotimes (i (- repeat))
+        (setf period (next sub-pattern t)))
+      (setf repeat (next repeat-pattern))
+      (setf merge-flag (next merge-pattern)))
+
+;    (print "** STARTING NEXT PATTERN IN COPIER-CLASS")
+
+    (setf period (next sub-pattern t))
+
+;    (display "copier-class really-start-period got" period)
+;    (print "** ENDING NEXT PATTERN IN COPIER-CLASS")
+
+    (setf cursor nil)
+    (if (null count)
+        (setf count (* (if merge-flag repeat 1)
+                       (length period))))))
+
+
+(send copier-class :answer :advance '()
+  '((let ((loop-count 0))
+      (loop
+        (xm-traceif "copier loop" name "repeat" repeat "cursor" cursor 
+                    "period" period)
+        (cond (cursor
+               (send self :set-current (car cursor))
+               (pop cursor)
+               (return))
+              ((plusp repeat)
+               (decf repeat)
+               (setf cursor period))
+              ((> loop-count 10000)
+               (error (format nil
+                "~A, copier-class :advance encountered 10000 empty periods"
+                name)))
+              (t
+               (send self :really-start-period)))
+        (incf loop-count)))))
+
+
+(defun make-copier (sub-pattern &key for (repeat 1) merge (name "copier") trace)
+  (send copier-class :new sub-pattern repeat merge for name trace))
+   
+;; ================= ACCUMULATE-CLASS ===================
+
+(setf accumulate-class (send class :new '(sub-pattern period cursor sum 
+                                          mini maxi minimum maximum) 
+                                    '() pattern-class))
+
+
+(send accumulate-class :answer :isnew '(p mp for nm tr mn mx)
+  '((send-super :isnew mp for nm tr)
+    (setf sub-pattern p sum 0 mini mn maxi mx)
+    ;(xm-trace "accumulate isnew" self nm)
+    ))
+
+
+#| 
+accumulate-class creates sums of numbers from another pattern
+The output periods are the same as the input periods (by default).
+
+(send accumulate-class :answer :start-period '(forcount)
+  '((cond ((null count)
+           (send self :really-start-period)))))
+
+(send accumulate-class :answer :really-start-period '()
+|#
+
+
+(send accumulate-class :answer :start-period '(forcount)
+  '((setf period (next sub-pattern t))
+    (setf cursor period)
+    (xm-traceif "accumulate-class :start-period" name "period" period 
+                "cursor" cursor "count" count)
+    (if maxi (setf maximum (next maxi)))
+    (if mini (setf minimum (next mini)))
+    (if (null count)
+        (setf count (length period)))))
+
+
+(send accumulate-class :answer :advance '()
+  '((let ((loop-count 0))
+      (loop
+        (cond (cursor
+               (setf sum (+ sum (car cursor)))
+               (cond ((and (numberp minimum) (< sum minimum))
+                      (setf sum minimum)))
+               (cond ((and (numberp maximum) (> sum maximum))
+                      (setf sum maximum)))
+               (send self :set-current sum)
+               (pop cursor)
+               (return))
+              ((> loop-count 10000)
+               (error (format nil
+                "~A, :advance encountered 10000 empty periods" name)))
+              (t
+               (send self :start-period nil)))
+        (incf loop-count)))))
+
+
+(defun make-accumulate (sub-pattern &key merge for min max (name "accumulate") trace)
+  (send accumulate-class :new sub-pattern merge for name trace min max))
+   
+;;================== ACCUMULATION CLASS ===================
+
+;; for each item, generate all items up to and including the item, e.g.
+;; (a b c) -> (a a b a b c)
+
+(setf accumulation-class (send class :new '(lis lis-pattern outer inner len)
+                               '() pattern-class))
+
+(send accumulation-class :answer :isnew '(l mp for nm tr)
+  '((send-super :isnew mp for nm tr)
+    (cond ((patternp l)
+           (setf lis-pattern l))
+          ((listp l)
+           (send self :set-list l))
+          (t
+           (error (format nil "~A, expected list" nm) l)))))
+
+
+(send accumulation-class :answer :set-list '(l)
+  '((setf lis l)
+    (check-for-list lis "heap-class :set-list")
+    (setf lis (make-homogeneous lis trace))
+    (setf inner lis)
+    (setf outer lis)
+    (setf len (length lis))))
+
+(send accumulation-class :answer :start-period '(forcount)
+  '((cond (lis-pattern
+           (send self :set-list (next lis-pattern t))))
+    ; start of period, length = (n^2 + n) / 2
+    (if (null count) (setf count (/ (+ (* len len) len) 2)))))
+
+(send accumulation-class :answer :advance '()
+  ;; inner traverses lis from first to outer
+  ;; outer traverses lis
+  '((let ((elem (car inner)))
+      (cond ((eq inner outer)
+             (setf outer (rest outer))
+             (setf outer (if outer outer lis))
+             (setf inner lis))
+            (t
+             (setf inner (rest inner))))
+      (send self :set-current elem))))
+
+(defun make-accumulation (lis &key merge for (name "accumulation") trace)
+  (send accumulation-class :new lis merge for name trace))
+
+
+;;================== SUM CLASS =================
+
+(setf sum-class (send class :new '(x y period cursor fn) '() pattern-class))
+
+(send sum-class :answer :isnew '(xx yy mp for nm tr)
+  '((send-super :isnew mp for nm tr)
+    (setf x xx y yy fn #'+)))
+
+#|
+sum-class creates pair-wise sums of numbers from 2 streams.
+The output periods are the same as the input periods of the first
+pattern argument (by default).
+|#
+
+(send sum-class :answer :start-period '(forcount)
+  '((cond ((null count)
+           (send self :really-start-period)))))
+
+(send sum-class :answer :really-start-period '()
+  '((setf period (next x t))
+    (setf cursor period)
+    (if (null count)
+        (setf count (length period)))))
+
+(send sum-class :answer :advance '()
+  '((let ((loop-count 0) rslt)
+      (loop
+        (cond (cursor
+               (setf rslt (funcall fn (car cursor) (next y)))
+               (send self :set-current rslt)
+               (pop cursor)
+               (return))
+              ((> loop-count 10000)
+               (error (format nil
+                       "~A, :advance encountered 10000 empty periods" name)))
+              (t
+               (send self :really-start-period)))
+        (incf loop-count)))))
+
+
+(defun make-sum (x y &key merge for (name "sum") trace)
+  (send sum-class :new x y merge for name trace))               
+
+
+;;================== PRODUCT CLASS =================
+
+(setf product-class (send class :new '() '() sum-class))
+
+(send product-class :answer :isnew '(xx yy mp for nm tr)
+  '((send-super :isnew xx yy mp for nm tr)
+    (setf x xx y yy fn #'*)))
+
+(defun make-product (x y &key merge for (name "product") trace)
+  (send product-class :new x y merge for name trace))               
+
+
+;;================== EVAL CLASS =================
+;;
+;; (1) if :for, then period is determined by :for and we should
+;;     just fetch the next item from expr-pattern or use expr
+;;     (this case is length-pattern)
+;; (2) if expr-pattern and not :for, then we should fetch a whole
+;;     period from expr-pattern and use it to determine period len
+;;     (this case is (and expr-pattern (not length-pattern)))
+;; (3) if not expr-pattern and not :for, then the pattern len is 1
+;;     (this case is (and (not expr-pattern) (not length-pattern)))
+
+(setf eval-class (send class :new '(expr expr-pattern) 
+                       '() pattern-class))
+
+(send eval-class :answer :isnew '(e mp for nm tr)
+  '((send-super :isnew mp for nm tr)
+    (cond ((patternp e)
+           (setf expr-pattern e))
+          (t
+           (setf expr e)))))
+
+
+(send eval-class :answer :start-period '(forcount)
+  '((xm-traceif "eval-class :start-period" name "lis-pattern" 
+                (get-pattern-name expr-pattern) "expr" expr "count" count
+                "length-pattern" (get-pattern-name expr-pattern))
+    (cond (length-pattern t) ;; case 1
+          (expr-pattern ;; case 2
+           (setf expr (next expr-pattern t))
+           (setf count (length expr)))
+          (t ;; case 3
+           (setf count 1)))))
+
+
+(send eval-class :answer :advance '()
+  '((send self :set-current
+          (cond ((and length-pattern expr-pattern)
+                 (eval (next expr-pattern)))
+                (length-pattern
+                 (eval expr))
+                (expr-pattern
+                 (let ((item (car expr)))
+                   (setf expr (cdr expr))
+                   item))
+                (t (eval expr))))))
+
+
+(defun make-eval (expr &key merge (for 1) (name "eval") trace)
+   (send eval-class :new expr merge for name trace))
+
+;;================== MARKOV CLASS ====================
+
+(setf markov-class (send class :new 
+      '(rules order state produces pattern len) 
+      '() pattern-class))
+
+
+(defun is-produces-homogeneous (produces)
+  (let (type elem)
+    (setf *rslt* nil)
+    (loop
+      (cond ((or (null produces) (eq produces :eval) (null (cadr produces)))
+             (return t)))
+      (setf elem (cadr produces))
+      (cond ((null type)
+             (setf type (if (patternp elem) 'pattern 'atom))
+             (xm-traceif "is-produces-homogeneous type" type)
+             (setf *rslt* (eq type 'pattern))
+             (xm-traceif "is-produces-homogeneous *rslt*" *rslt*)
+             )
+            ((and (eq type 'pattern) (not (patternp elem)))
+             (return nil))
+            ((and (eq type 'atom)
+                  (patternp elem))
+             (return nil)))
+      (setf produces (cddr produces)))))
+
+
+(defun make-produces-homogeneous (produces)
+  (let (result item)
+    (loop
+      (if (null produces) (return nil))
+      (push (car produces) result)
+      (setf produces (cdr produces))
+      (setf item (car produces))
+      (setf produces (cdr produces))
+      (if (not (patternp item)) 
+        (setf item (make-cycle (list item))))
+      (push item result))
+    (reverse result)))
+
+
+(send markov-class :answer :isnew '(r o s p mp for nm tr)
+  ;; input parameters are rules, order, state, produces, for, name, trace
+  '((send-super :isnew mp for nm tr)
+    (setf order o state s produces p)
+    (setf len (length r))
+    ;; input r looks like this:
+    ;; ((prev1 prev2 -> next1 next2 (next3 weight) ... ) ...)
+    ;; transition table will look like a list of these:
+    ;; ((prev1 prev2 ... prevn) (next1 weight weight-pattern) ...)
+    (dolist (rule r)
+      (let ((targets (cdr (nthcdr order rule)))
+            entry pattern)
+        ;; build entry in reverse order
+        (dolist (target targets)
+          (push (if (atom target)
+                    (list target 1 1) 
+                    (list (first target) 
+                          (next (second target)) 
+                          (second target))) 
+                entry))
+        (xm-traceif "markov-class isnew" name "entry" entry "rule" rule 
+                    "targets" targets "order" order (nthcdr order rule))
+        (dotimes (i order)
+          (push (nth i rule) pattern))
+        (push (cons (reverse pattern) entry) rules)))
+    (setf rules (reverse rules)) ;; keep rules in original order
+    (setf *rslt* nil) ;; in case produces is nil
+    (cond ((and produces (not (is-produces-homogeneous produces)))
+           (setf produces (make-produces-homogeneous produces))))
+    (xm-traceif "markov-class :isnew" name "is-nested" *rslt*)
+    (setf is-nested *rslt*) ;; returned by is-produces-homogeneous
+    ))
+
+
+(defun markov-match (state pattern)
+  (dolist (p pattern t) ;; return true if no mismatch
+    ;; compare element-by-element
+    (cond ((eq p '*)) ; anything matches '*
+          ((eql p (car state)))
+          (t (return nil))) ; a mismatch: return false
+    (setf state (cdr state))))
+
+
+(defun markov-pick-target (sum rule)
+  (let ((total 0.0)
+        ;; want to choose a value in the interval [0, sum)
+        ;; but real-random is not open on the right, so fudge
+        ;; the range by a small amount:
+        (r (real-random 0.0 (- sum SCORE-EPSILON))))
+    (dolist (target (cdr rule))
+      (setf total (+ total (second target)))
+      (cond ((> total r) (return (car target)))))))
+
+
+(defun markov-update-weights (rule)
+  (dolist (target (cdr rule))
+    (setf (car (cdr target)) (next (caddr target)))))
+
+
+(defun markov-map-target (target produces)
+  (while (and produces (not (eq target (car produces))))
+    (setf produces (cddr produces)))
+  (let ((rslt (cadr produces)))
+    (if (not rslt) (setf rslt target)) ;; if lookup fails return target
+    (if (patternp rslt) (setf rslt (next rslt)))
+    rslt))
+
+
+(send markov-class :answer :sum-of-weights '(rule)
+  '((let ((sum 0.0))
+      (dolist (target (cdr rule))
+        (xm-traceif "markov-sum-of-weights" name "target" target)
+        (setf sum (+ sum (second target))))
+      sum)))
+
+
+(send markov-class :answer :find-rule '()
+  '((let (rslt)
+      (xm-traceif "markov-class find-rule" name "rules" rules)
+      (dolist (rule rules)
+        (xm-traceif "markov find-rule" name "state" state "rule" rule)
+        (cond ((markov-match state (car rule))
+               (setf rslt rule)
+               (return rslt))))
+      (cond ((null rslt)
+             (display "Error, no matching rule found" state rules)
+             (error (format nil "~A, (markov-class)" name))))
+      rslt)))
+
+
+(send markov-class :answer :start-period '(forcount)
+  '((if (null count)
+        (setf count len))))
+
+(defun markov-general-rule-p (rule)
+  (let ((pre (car rule)))
+    (cond ((< (length pre) 2) nil) ;; 1st-order mm
+          (t
+           ;; return false if any member not *
+           ;; return t if all members are *
+           (dolist (s pre t)
+             (if (eq s '*) t (return nil)))))))
+
+(defun markov-find-state-leading-to (target rules)
+  (let (candidates)
+    (dolist (rule rules)
+      (let ((targets (cdr rule)))
+        (dolist (targ targets)
+          (cond ((eql (car targ) target)
+                 (push (car rule) candidates))))))
+    (cond (candidates ;; found at least one
+           (nth (random (length candidates)) candidates))
+          (t
+           nil))))
+
+(send markov-class :answer :advance '()
+  '((let (rule sum target rslt new-state)
+      (xm-traceif "markov :advance" name "pattern" pattern "rules" rules)
+      (setf rule (send self :find-rule))
+      (markov-update-weights rule)
+      (xm-traceif "markov sum-of-weights" name "rule" rule)
+      (setf sum (send self :sum-of-weights rule))
+      ;; the target can be a pattern, so apply NEXT to it
+      (setf target (next (markov-pick-target sum rule)))
+      ;; if the matching rule is multiple *'s, then this
+      ;; is a higher-order Markov model, and we may now
+      ;; wander around in parts of the state space that
+      ;; never appeared in the training data. To avoid this
+      ;; we violate the strict interpretation of the rules
+      ;; and pick a random state sequence from the rule set
+      ;; that might have let to the current state. We jam
+      ;; this state sequence into state so that when we
+      ;; append target, we'll have a history that might
+      ;; have a corresponding rule next time.
+      (cond ((markov-general-rule-p rule)
+             (setf new-state (markov-find-state-leading-to target rules))
+             (cond (new-state
+                    (xm-trace "markov state replacement" name 
+                              "new-state" new-state "target" target)
+                    (setf state new-state)))))
+      (setf state (append (cdr state) (list target)))
+      (xm-traceif "markov next" name "rule" rule "sum" sum "target" target
+                  "state" state)
+      ;; target is the symbol for the current state. We can
+      ;; return target (default), the value of target, or a
+      ;; mapped value:
+      (cond ((eq produces :eval)
+             (setf target (eval target)))
+            ((and produces (listp produces))
+             (xm-traceif "markov-produce" name "target" target 
+                         "produces" produces)
+             (setf target (markov-map-target target produces))))
+      (if (not (eq is-nested (patternp target)))
+          (error (format nil 
+         "~A :is-nested keyword (~A) not consistent with result (~A)"
+                  name is-nested target)))
+      (send self :set-current target))))
+
+
+(defun make-markov (rules &key produces past merge for (name "markov") trace)
+  ;; check to make sure past and rules are consistent
+  (let ((order (length past)))
+    (dolist (rule rules)
+      (dotimes (i order)
+        (if (eq (car rule) '->)
+            (error (format nil "~A, a rule does not match the length of :past"
+                               name)))
+        (pop rule))
+      (if (eq (car rule) '->) nil
+          (error (format nil "~A, a rule does not match the length of :past"
+                             name)))))
+  (cond ((null for)
+         (setf for (length rules))))
+  (send markov-class :new rules (length past) past produces merge for name trace))
+
+
+(defun markov-rule-match (rule state)
+  (cond ((null state) t)
+        ((eql (car rule) (car state))
+         (markov-rule-match (cdr rule) (cdr state)))
+        (t nil)))
+
+
+(defun markov-find-rule (rules state)
+  (dolist (rule rules)
+    (xm-traceif "markov find-rule" name "rule" rule)
+    (cond ((markov-rule-match rule state)
+           (return rule)))))
+
+;; ------- functions below are for MARKOV-CREATE-RULES --------
+
+;; MARKOV-FIND-CHOICE -- given a next state, find it in rule
+;;
+;; use state to get the order of the Markov model, e.g. how
+;; many previous states to skip in the rule, (add 1 for '->).
+;; then use assoc to do a quick search
+;;
+;; example:
+;;  (markov-find-choice '(a b -> (c 1) (d 2)) '(a b) 'd)
+;; returns (d 2) from the rule
+;;
+(defun markov-find-choice (rule state next)
+  (assoc next (nthcdr (1+ (length state)) rule)))
+
+(defun markov-update-rule (rule state next)
+  (let ((choice (markov-find-choice rule state next)))
+    (cond (choice
+           (setf (car (cdr choice)) (1+ (cadr choice))))
+          (t
+           (nconc rule (list (list next 1)))))
+    rule))
+
+
+(defun markov-update-rules (rules state next)
+  (let ((rule (markov-find-rule rules state)))
+    (cond (rule
+           (markov-update-rule rule state next))
+          (t
+           (setf rules
+                 (nconc rules 
+                        (list (append state
+                                      (cons '-> (list 
+                                                 (list next 1)))))))))
+    rules))
+
+
+;; MARKOV-UPDATE-HISTOGRAM -- keep a list of symbols and counts
+;; 
+;; This histogram will become the right-hand part of a rule, so
+;; the format is ((symbol count) (symbol count) ...)
+;;
+(defun markov-update-histogram (histogram next)
+  (let ((pair (assoc next histogram)))
+    (cond (pair
+           (setf (car (cdr pair)) (1+ (cadr pair))))
+          (t
+           (setf histogram (cons (list next 1) histogram))))
+    histogram))
+
+
+(defun markov-create-rules (sequence order &optional generalize)
+  (let ((seqlen (length sequence)) state rules next histogram rule)
+    (cond ((<= seqlen order)
+           (error "markov-create-rules: sequence must be longer than order"))
+          ((< order 1)
+           (error "markov-create-rules: order must be 1 or greater")))
+    ; build initial state sequence
+    (dotimes (i order)
+      (setf state (nconc state (list (car sequence))))
+      (setf sequence (cdr sequence)))
+    ; for each symbol, either update a rule or add a rule
+    (while sequence
+      (setf next (car sequence))
+      (setf sequence (cdr sequence))
+      (setf rules (markov-update-rules rules state next))
+      (setf histogram (markov-update-histogram histogram next))
+      ; shift next state onto current state list
+      (setf state (nconc (cdr state) (list next))))
+    ; generalize?
+    (cond (generalize
+           (setf rule (cons '-> histogram))
+           (dotimes (i order)
+             (setf rule (cons '* rule)))
+           (setf rules (nconc rules (list rule)))))
+    rules))
+
+
+;; ----- WINDOW Class ---------
+
+(setf window-class (send class :new 
+                         '(pattern skip-pattern lis cursor)
+                         '() pattern-class))
+
+(send window-class :answer :isnew '(p for sk nm tr)
+  '((send-super :isnew nil for nm tr)
+    (setf pattern p skip-pattern sk)))
+
+
+(send window-class :answer :start-period '(forcount)
+  '((if (null length-pattern)
+        (error (format nil "~A, :start-period -- length-pattern is null"
+                           name)))
+    (setf count forcount)
+    (cond ((null lis) ;; first time
+           (dotimes (i count)
+             (push (next pattern) lis))
+           (setf lis (reverse lis))
+           (setf cursor lis))
+          (t
+           (let ((skip (next skip-pattern)))
+             (dotimes (i skip)
+               (if lis (pop lis) (next pattern))))
+           (setf lis (reverse lis))
+           ;; now lis is in reverse order; if not long enough, push
+           (let ((len (length lis)) rslt)
+             (while (< len count)
+               (incf len)
+               (push (next pattern) lis))
+             (setf lis (reverse lis))
+             ;; lis is in order, copy it to rstl and take what we need
+             (setf rslt (reverse (append lis nil))) ;; copy lis
+             (while (> len count)
+               (decf len)
+               (pop rslt))
+             (setf cursor (reverse rslt)))))
+    (xm-traceif "window start-period cursor" cursor "lis" lis)))
+
+
+(send window-class :answer :advance '()
+  '((send self :set-current (car cursor))
+    (pop cursor)))
+
+(defun make-window (pattern length-pattern skip-pattern
+                    &key (name "window") trace)
+  (send window-class :new pattern length-pattern skip-pattern name trace))
+
+;; SCORE-SORTED -- test if score is sorted
+;;
+(defun score-sorted (score)
+  (let ((result t))
+    (while (cdr score)
+      (cond ((event-before (cadr score) (car score))
+             (setf result nil)
+             (return nil)))
+      (setf score (cdr score)))
+    result))
+    
+
+(defmacro score-gen (&rest args)
+  (let (key val tim dur (name ''note) ioi trace save 
+        score-len score-dur others pre post
+        next-expr (score-begin 0) score-end)
+    (while (and args (cdr args))
+      (setf key (car args))
+      (setf val (cadr args))
+      (setf args (cddr args))       
+      (case key
+        (:time (setf tim val))
+        (:dur (setf dur val))
+        (:name (setf name val))
+        (:ioi (setf ioi val))
+        (:trace (setf trace val))
+        (:save (setf save val))
+        (:pre (setf pre val))
+        (:post (setf post val))
+        (:score-len (setf score-len val))
+        (:score-dur (setf score-dur val))
+        (:begin (setf score-begin val))
+        (:end (setf score-end val))
+        (t (setf others (cons key (cons val others))))))
+    ;; make sure at least one of score-len, score-dur is present
+    (cond ((and (null score-len) (null score-dur))
+           (error
+           "score-gen needs either :score-len or :score-dur to limit length")))
+    ;; compute expression for dur
+    (cond ((null dur)
+           (setf dur 'sg:ioi)))
+    ;; compute expression for ioi
+    (cond ((null ioi)
+           (setf ioi 1)))
+    ;; compute expression for next start time
+    (setf next-expr '(+ sg:start sg:ioi))
+    ; (display "score-gen" others)
+    `(let (sg:seq (sg:start ,score-begin) sg:ioi 
+           (sg:score-len ,score-len) (sg:score-dur ,score-dur)
+           (sg:count 0) (sg:save ,save) 
+           (sg:begin ,score-begin) (sg:end ,score-end) sg:det-end)
+       ;; sg:det-end is a flag that tells us to determine the end time
+       (cond ((null sg:end) (setf sg:end 0 sg:det-end t)))
+       ;; make sure at least one of score-len, score-dur is present
+       (loop
+         (cond ((or (and sg:score-len (<= sg:score-len sg:count))
+                    (and sg:score-dur (<= (+ sg:begin sg:score-dur) sg:start)))
+                (return)))
+         ,pre
+         ,(cond (tim (list 'setf 'sg:start tim)))
+         (setf sg:ioi ,ioi)
+         (setf sg:dur ,dur)
+         (push (list sg:start sg:dur (list ,name ,@others))
+               sg:seq)
+         ,post
+         (cond (,trace
+                (format t "get-seq trace at ~A stretch ~A: ~A~%" 
+                          sg:start sg:dur (car sg:seq))))
+         (incf sg:count)
+         (setf sg:start ,next-expr)
+         ;; end time of score will be max over start times of the next note
+         ;; this bases the score duration on ioi's rather than durs. But
+         ;; if user specified sg:end, sg:det-end is false and we do not
+         ;; try to compute sg:end.
+         (cond ((and sg:det-end (> sg:start sg:end))
+                (setf sg:end sg:start))))
+       (setf sg:seq (reverse sg:seq))
+       ;; avoid sorting a sorted list -- XLisp's quicksort can overflow the
+       ;; stack if the list is sorted because (apparently) the pivot points
+       ;; are not random.
+       (cond ((not (score-sorted sg:seq))
+              (setf sg:seq (bigsort sg:seq #'event-before))))
+       (push (list 0 0 (list 'SCORE-BEGIN-END ,score-begin sg:end)) sg:seq)
+       (cond (sg:save (set sg:save sg:seq)))
+       sg:seq)))
+
+;; ============== score manipulation ===========
+
+(defun must-be-valid-score (caller score)
+  (if (not (score-validp score))
+      (error (strcat "In " caller ", not a valid score") score)))
+
+(defun invalid-score () (return-from validp nil))
+(defun score-validp (score)
+  (block validp
+    (if (listp score) nil (invalid-score)) ;; tricky: return nil if NOT condition
+    (dolist (event score)
+      (if (listp event) nil (invalid-score))
+      (if (and (event-time event) (numberp (event-time event))) nil 
+          (invalid-score))
+      (if (and (event-dur event) (numberp (event-dur event))) nil
+          (invalid-score))
+      (if (and (event-expression event) (consp (event-expression event))) nil
+          (invalid-score)))
+    t))
+
+(defun event-before (a b)
+  (< (car a) (car b)))
+
+;; EVENT-END -- get the ending time of a score event
+;;
+(defun event-end (e) (+ (car e) (cadr e)))
+
+;; EVENT-TIME -- time of an event
+;;
+(setfn event-time car)
+
+;; EVENT-DUR -- duration of an event
+;;
+(setfn event-dur cadr)
+
+;; EVENT-SET-TIME -- new event with new time
+;;
+(defun event-set-time (event time)
+  (cons time (cdr event)))
+
+
+;; EVENT-SET-DUR -- new event with new dur
+;;
+(defun event-set-dur (event dur)
+  (list (event-time event) 
+        dur 
+        (event-expression event)))
+  
+  
+;; EVENT-SET-EXPRESSION -- new event with new expression
+;;
+(defun event-set-expression (event expression)
+  (list (event-time event) 
+        (event-dur event)
+        expression))
+  
+;; EXPR-HAS-ATTR -- test if expression has attribute
+;;
+(defun expr-has-attr (expression attr)
+  (member attr expression))
+
+
+;; EXPR-GET-ATTR -- get value of attribute from expression
+;;
+(defun expr-get-attr (expression attr &optional default)
+  (let ((m (member attr expression)))
+    (if m (cadr m) default)))
+
+
+;; EXPR-SET-ATTR -- set value of an attribute in expression
+;; (returns new expression)
+(defun expr-set-attr (expr attr value)
+  (cons (car expr) (expr-parameters-set-attr (cdr expr) attr value)))
+
+(defun expr-parameters-set-attr (lis attr value)
+  (cond ((null lis) (list attr value))
+        ((eq (car lis) attr) (cons attr (cons value (cddr lis))))
+        (t (cons (car lis) 
+                 (cons (cadr lis) 
+                       (expr-parameters-set-attr (cddr lis) attr value))))))
+
+
+;; EXPR-REMOVE-ATTR -- expression without attribute value pair
+(defun expr-remove-attr (event attr)
+  (cons (car expr) (expr-parameters-remove-attr (cdr expr) attr)))
+
+(defun expr-parameters-remove-attr (lis attr)
+   (cond ((null lis) nil)
+         ((eq (car lis) attr) (cddr lis))
+         (t (cons (car lis)
+                  (cons (cadr lis)
+                        (expr-parameters-remove-attr (cddr lis) attr))))))
+
+
+;; EVENT-GET-ATTR -- get value of attribute from event
+;;
+(defun event-get-attr (note attr &optional default)
+  (expr-get-attr (event-expression note) attr default))
+
+
+;; EVENT-SET-ATTR -- new event with attribute = value
+(defun event-set-attr (event attr value)
+  (event-set-expression 
+    event
+    (expr-set-attr (event-expression event) attr value)))
+
+
+;; EVENT-REMOVE-ATTR -- new event without attribute value pair
+(defun event-remove-attr (event attr)
+  (event-set-expression
+     event
+     (event-remove-attr (event-expression event) attr)))
+
+
+;; SCORE-GET-BEGIN -- get the begin time of a score
+;;
+(defun score-get-begin (score)
+  (setf score (score-must-have-begin-end score))
+  (cadr (event-expression (car score))))
+
+
+;; SCORE-SET-BEGIN -- set the begin time of a score
+;;
+(defun score-set-begin (score time)
+  (setf score (score-must-have-begin-end score))
+  (cons (list 0 0 (list 'score-begin-end time 
+                        (caddr (event-expression (car score)))))
+        (cdr score)))
+
+
+;; SCORE-GET-END -- get the end time of a score
+;;
+(defun score-get-end (score)
+  (setf score (score-must-have-begin-end score))
+  (caddr (event-expression (car score))))
+
+
+;; SCORE-SET-END -- set the end time of a score
+;;
+(defun score-set-end (score time)
+  (setf score (score-must-have-begin-end score))
+  (cons (list 0 0 (list 'score-begin-end 
+                        (cadr (event-expression (car score))) time))
+        (cdr score)))
+
+
+;; FIND-FIRST-NOTE -- use keywords to find index of first selected note
+;;
+(defun find-first-note (score from-index from-time)
+  (let ((s (cdr score)))
+    ;; offset by one because we removed element 0
+    (setf from-index (if from-index (max 0 (- from-index 1)) 0))
+    (setf from-time (if from-time 
+                        (- from-time SCORE-EPSILON)
+                        (- SCORE-EPSILON)))
+    (if s (setf s (nthcdr from-index s)))
+    
+    (while (and s (>= from-time (event-time (car s))))
+      (setf s (cdr s))
+      (incf from-index))
+    (1+ from-index)))
+
+
+;; EVENT-BEFORE -- useful function for sorting scores
+;;
+(defun event-before (a b)
+  (< (car a) (car b)))
+  
+;; bigsort -- a sort routine that avoids recursion in order
+;; to sort large lists without overflowing the evaluation stack
+;;
+;; Does not modify input list. Does not minimize cons-ing.
+;;
+;; Algorithm: first accumulate sorted sub-sequences into lists
+;; Then merge pairs iteratively until only one big list remains
+;; 
+(defun bigsort (lis cmp) ; sort lis using cmp function
+  ;; if (funcall cmp a b) then a and b are in order
+  (prog (rslt sub pairs)
+    ;; first, convert to sorted sublists stored on rslt
+    ;; accumulate sublists in sub
+   get-next-sub
+    (if (null lis) (go done-1))
+    (setf sub (list (car lis)))
+    (setf lis (cdr lis))
+   fill-sub
+    ;; invariant: sub is non-empty, in reverse order
+    (cond ((and lis (funcall cmp (car sub) (car lis)))
+           (setf sub (cons (car lis) sub))
+           (setf lis (cdr lis))
+           (go fill-sub)))
+    (setf sub (reverse sub)) ;; put sub in correct order
+    (setf rslt (cons sub rslt)) ;; build rslt in reverse order
+    (go get-next-sub)
+   done-1
+    ;; invariant: rslt is list of sorted sublists
+    (if (cdr rslt) nil (go done-2))
+    ;; invariant: rslt has at least one list
+    (setf pairs rslt)
+    (setf rslt nil)
+   merge-pairs    ;; merge a pair and save on rslt
+    (if (car pairs) nil (go end-of-pass)) ;; loop until all pairs merged
+    ;; invariant: pairs has at least one list
+    (setf list1 (car pairs)) ;; list1 is non-empty
+    (setf list2 (cadr pairs)) ;; list2 could be empty
+    (setf pairs (cddr pairs))
+    (cond (list2
+           (setf rslt (cons (list-merge list1 list2 cmp) rslt)))
+          (t
+           (setf rslt (cons list1 rslt))))
+    (go merge-pairs)
+   end-of-pass
+    (go done-1)
+   done-2
+    ;; invariant: rslt has one sorted list!
+    (return (car rslt))))
+
+(defun list-merge (list1 list2 cmp)
+  (prog (rslt)
+   merge-loop
+    (cond ((and list1 list2)
+           (cond ((funcall cmp (car list1) (car list2))
+                  (setf rslt (cons (car list1) rslt))
+                  (setf list1 (cdr list1)))
+                 (t
+                  (setf rslt (cons (car list2) rslt))
+                  (setf list2 (cdr list2)))))
+          (list1
+           (return (nconc (reverse rslt) list1)))
+          (t
+           (return (nconc (reverse rslt) list2))))
+    (go merge-loop)))  
+
+
+;; SCORE-SORT -- sort a score into time order
+;;
+;; If begin-end exists, preserve it. If not, compute
+;; it from the sorted score.
+;;
+(defun score-sort (score &optional (copy-flag t)) 
+  (let* ((score1 (score-must-have-begin-end score))
+         (begin-end (car score1))
+         ;; if begin-end already existed, then it will
+         ;; be the first of score. Otherwise, one must
+         ;; have been generated above by score-must-have-begin-end
+         ;; in which case we should create it again after sorting.
+         (needs-begin-end (not (eq begin-end (first score)))))
+    (setf score1 (cdr score1)) ;; don't include begin-end in sort.
+    (if copy-flag (setf score1 (append score1 nil)))
+    (setf score1 (bigsort score1 #'event-before))
+    (if needs-begin-end (score-must-have-begin-end score1)
+                        (cons begin-end score1))
+  ))
+  
+
+;; PUSH-SORT -- insert an event in (reverse) sorted order
+;;
+;; Note: Score should NOT have a score-begin-end expression
+;;
+(defun push-sort (event score)
+  (let (insert-after)
+    (cond ((null score) (list event))
+          ((event-before (car score) event)
+           (cons event score))
+          (t
+           (setf insert-after score)
+           (while (and (cdr insert-after) 
+                       (event-before event (cadr insert-after)))
+             (setf insert-after (cdr insert-after)))
+           (setf (cdr insert-after) (cons event (cdr insert-after)))
+           score))))
+
+
+(setf FOREVER 3600000000.0) ; 1 million hours
+
+;; FIND-LAST-NOTE -- use keywords to find index beyond last selected note
+;;
+;; note that the :to-index keyword is the index of the last note (numbered
+;; from zero), whereas this function returns the index of the last note
+;; plus one, i.e. selected notes have an index *less than* this one
+;;
+(defun find-last-note (score to-index to-time)
+  ;; skip past score-begin-end event
+  (let ((s (cdr score))
+        (n 1))
+    (setf to-index (if to-index (1+ to-index) (length score)))
+    (setf to-time (if to-time (- to-time SCORE-EPSILON)  FOREVER))
+    (while (and s (< n to-index) (< (event-time (car s)) to-time))
+      (setf s (cdr s))
+      (incf n))
+    n))
+
+
+;; SCORE-MUST-HAVE-BEGIN-END -- add score-begin-end event if necessary
+;;
+(defun score-must-have-begin-end (score)
+  (cond ((null score) 
+         (list (list 0 0 (list 'SCORE-BEGIN-END 0 0))))
+        ((eq (car (event-expression (car score))) 'SCORE-BEGIN-END)
+         score)
+        (t (cons (list 0 0 (list 'SCORE-BEGIN-END (event-time (car score))
+                                 (event-end (car (last score)))))
+                 score))))
+
+
+;; SCORE-SHIFT -- add offset to times of score events
+;;
+(defun score-shift (score offset &key from-index to-index from-time to-time)
+  (setf score (score-must-have-begin-end score))
+  (let ((i 1) 
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        (begin (cadr (event-expression (car score))))
+        (end (caddr (event-expression (car score))))
+        result)
+    (dolist (event (cdr score))
+      (cond ((and (<= start i) (< i stop))
+             (setf event (event-set-time 
+                          event (+ (event-time event) offset)))
+             (setf begin (min begin (event-time event)))
+             (setf end (max end (event-end event)))))
+      (setf result (push-sort event result))
+      (incf i))
+    (cons (list 0 0 (list 'SCORE-BEGIN-END begin end))
+          (reverse result))))
+
+
+;; TIME-STRETCH -- map a timestamp according to stretch factor
+;;
+(defun time-stretch (time stretch start-time stop-time)
+  (cond ((< time start-time) time)
+        ((< time stop-time) 
+         (+ start-time (* stretch (- time start-time))))
+        (t ; beyond stop-time
+         (+ (- time stop-time) ; how much beyond stop-time
+            start-time
+            (* stretch (- stop-time start-time))))))
+         
+
+;; EVENT-STRETCH -- apply time warp to an event
+(defun event-stretch (event stretch dur-flag time-flag start-time stop-time)
+  (let* ((new-time (event-time event))
+         (new-dur (event-dur event))
+         (end-time (+ new-time new-dur)))
+    (cond (time-flag
+           (setf new-time (time-stretch new-time stretch 
+                                        start-time stop-time))))
+    (cond ((and time-flag dur-flag)
+           ;; both time and dur are stretched, so map the end time just
+           ;; like the start time, then subtract to get new duration
+           (setf end-time (time-stretch end-time stretch
+                                        start-time stop-time))
+           (setf new-dur (- end-time new-time)))
+          ((and dur-flag (>= new-time start-time) (< new-time stop-time))
+           ;; stretch only duration, not time. If note starts in range
+           ;; scale to get the new duration.
+           (setf new-dur (* stretch new-dur))))
+    (list new-time new-dur (event-expression event))))
+
+
+;; SCORE-STRETCH -- stretch a region of the score
+;;
+(defun score-stretch (score factor &key (dur t) (time t)
+                      from-index to-index (from-time 0) (to-time FOREVER))
+  (if (zerop factor) (print "WARNING: score-stretch called with zero stretch factor."))
+  (setf score (score-must-have-begin-end score))
+  (let ((begin-end (event-expression (car score)))
+        (i 1))
+    (if from-index
+        (setf from-time (max from-time 
+                             (event-time (nth from-index score)))))
+    (if to-index
+        (setf to-time (min to-time 
+                           (event-end (nth to-index score)))))
+    ; stretch from start-time to stop-time
+    (cons (list 0 0 (list 'SCORE-BEGIN-END 
+                          (time-stretch (cadr begin-end) factor 
+                                        from-time to-time)
+                          (time-stretch (caddr begin-end) factor
+                                        from-time to-time)))
+          (mapcar #'(lambda (event) 
+                      (event-stretch event factor dur time
+                                     from-time to-time))
+                  (cdr score)))))
+    
+
+;; Turn a value field into a numeric value if possible 
+;; (by looking up a global variable binding). This 
+;; allows scores to say C4 instead of 60.
+;;
+(defun get-numeric-value (v)
+  (cond ((and v (symbolp v) (boundp v) (numberp (symbol-value v)))
+         (symbol-value v))
+        (t v)))
+
+          
+(defun params-transpose (params keyword amount)
+  (cond ((null params) nil)
+        ((eq keyword (car params))
+         (let ((v (get-numeric-value (cadr params))))
+           (cond ((numberp v)
+                  (setf v (+ v amount)))
+                 ((and (eq keyword :pitch) (listp v))
+                  (setf v (mapcar #'(lambda (x) (setf x (get-numeric-value x))
+                                                (+ x amount)) v))))
+           (cons (car params)
+                 (cons v (cddr params)))))
+        (t (cons (car params)
+                 (cons (cadr params)
+                       (params-transpose (cddr params) keyword amount))))))
+
+
+(defun score-transpose (score keyword amount &key
+                        from-index to-index from-time to-time)
+  (score-apply score 
+               #'(lambda (time dur expression)
+                   (list time dur 
+                         (cons (car expression)
+                               (params-transpose (cdr expression)
+                                                 keyword amount))))
+               :from-index from-index :to-index to-index
+               :from-time from-time :to-time to-time))
+
+
+(defun params-scale (params keyword amount)
+  (cond ((null params) nil)
+        ((eq keyword (car params))
+         (let ((v (get-numeric-value (cadr params))))
+           (cond ((numberp v)
+                  (setf v (* v amount))))
+           (cons (car params)
+                 (cons v (cddr params)))))
+        (t (cons (car params)
+                 (cons (cadr params)
+                       (params-scale (cddr params) keyword amount))))))
+
+
+(defun score-scale (score keyword amount &key
+                    from-index to-index from-time to-time)
+  (score-apply score 
+               #'(lambda (time dur expression)
+                   (list time dur
+                         (cons (car expression)
+                               (params-scale (cdr expression)
+                                             keyword amount))))
+               :from-index from-index :to-index to-index
+               :from-time from-time :to-time to-time))
+
+
+(defun score-sustain (score factor &key
+                      from-index to-index from-time to-time)
+  (setf score (score-must-have-begin-end score))
+  (let ((i 0)
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        result)
+    (dolist (event score)
+      (cond ((and (<= start i) (< i stop))
+             (setf event (event-set-dur
+                          event (* (event-dur event) factor)))))
+      (push event result)
+      (incf i))
+    (reverse result)))
+
+
+;; MAP-VOICE - helper function for SCORE-VOICE
+;; input: a score expression, e.g. '(note :pitch 60 :vel 100)
+;;        a replacement list, e.g. '((note foo) (* bar))
+;; output: the score expression with substitutions, e.g.
+;;              '(foo :pitch 60 :vel 100)
+;;
+(defun map-voice (expression replacement-list)
+  (cond (replacement-list
+         (cond ((or (eq (car expression) (caar replacement-list))
+                    (eq (caar replacement-list) '*))
+                (cons (cadar replacement-list) (cdr expression)))
+               (t (map-voice expression (cdr replacement-list)))))
+        (t expression)))
+
+
+(defun ny:assert-replacement-list (fun-name index formal actual)
+  (let ((lis actual) r)
+    (while lis
+      (if (not (consp actual))
+          (error (format nil "In ~A,~A argument (~A) should be a list, got ~A"
+                             fun-name (index-to-string index) formal actual)))
+      (setf r (car lis))
+      (if (not (and (listp r) (= 2 (length r)) (symbolp (car r)) (symbolp (cadr r))))
+          (error (format nil
+                   "In ~A,~A argument (~A) should be a list of lists of two symbols, got ~A"
+                   fun-name (index-to-string index) formal actual)))
+      (setf lis (cdr lis))  )))
+
+
+(defun score-voice (score replacement-list &key
+                    from-index to-index from-time to-time)
+  (ny:assert-replacement-list 'SCORE-VOICE 2 "replacement-list" replacement-list)
+  (setf score (score-must-have-begin-end score))
+  (let ((i 0) 
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        result)
+    (dolist (event score)
+      (cond ((and (<= start i) (< i stop))
+             (setf event (event-set-expression
+                          event (map-voice (event-expression event)
+                                           replacement-list)))))
+      (push event result)
+      (incf i))
+    (reverse result)))
+
+
+(defun score-merge (&rest scores)
+  ;; scores is a list of scores
+  (cond ((null scores) nil)
+        (t
+         (score-merge-1 (car scores) (cdr scores)))))
+
+
+;; SCORE-MERGE-1 -- merge list of scores into score
+;;
+(defun score-merge-1 (score scores)
+  ;; scores is a list of scores to merge
+  (cond ((null scores) score)
+        (t (score-merge-1 (score-merge-2 score (car scores))
+                          (cdr scores)))))
+
+;; SCORE-MERGE-2 -- merge 2 scores
+;;
+(defun score-merge-2 (score addin)
+  ;(display "score-merge-2 before" score addin)
+  (setf score (score-must-have-begin-end score))
+  (setf addin (score-must-have-begin-end addin))
+  ;(display "score-merge-2" score addin)
+  (let (start1 start2 end1 end2)
+    (setf start1 (score-get-begin score))
+    (setf start2 (score-get-begin addin))
+    (setf end1 (score-get-end score))
+    (setf end2 (score-get-end addin))
+    
+    ;; note: score-sort is destructive, but append copies score
+    ;;       and score-shift copies addin
+    (score-sort
+     (cons (list 0 0 (list 'SCORE-BEGIN-END (min start1 start2)
+                           (max end1 end2)))
+           (append (cdr score) (cdr addin) nil)))))
+
+
+
+;; SCORE-APPEND -- append scores together in sequence
+;;
+(defun score-append (&rest scores)
+  ;; scores is a list of scores
+  (cond ((null scores) nil)
+        (t
+         (score-append-1 (car scores) (cdr scores)))))
+
+
+;; SCORE-APPEND-1 -- append list of scores into score
+;;
+(defun score-append-1 (score scores)
+  ;; scores is a list of scores to append
+  (cond ((null scores) score)
+        (t (score-append-1 (score-append-2 score (car scores))
+                           (cdr scores)))))
+
+
+;; SCORE-APPEND-2 -- append 2 scores
+;;
+(defun score-append-2 (score addin)
+  ;(display "score-append-2" score addin)
+  (setf score (score-must-have-begin-end score))
+  (setf addin (score-must-have-begin-end addin))
+  (let (end1 start2 begin-end1 begin-end2)
+    (setf start1 (score-get-begin score))
+    (setf end1 (score-get-end score))
+    (setf start2 (score-get-begin addin))
+    (setf end2 (score-get-end addin))
+    (setf begin-end1 (event-expression (car score)))
+    (setf begin-end2 (event-expression (car addin)))
+    (setf addin (score-shift addin (- end1 start2)))
+    ;; note: score-sort is destructive, but append copies score
+    ;;       and score-shift copies addin
+    (score-sort
+     (cons (list 0 0 (list 'SCORE-BEGIN-END start1 (+ end1 (- end2 start2))))
+           (append (cdr score) (cdr addin) nil)))))
+
+
+(defun score-select (score predicate &key
+                    from-index to-index from-time to-time reject)
+  (setf score (score-must-have-begin-end score))
+  (let ((begin-end (car score))
+        (i 1) 
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        result)
+    ;; selected if start <= i AND i < stop AND predicate(...)
+    ;; choose if not reject and selected or reject and not selected
+    ;; so in other words choose if reject != selected. Use NULL to
+    ;; coerce into boolean values and then use NOT EQ to compare
+    (dolist (event (cdr score))
+      (cond ((not (eq (null reject)
+                      (null (and (<= start i) (< i stop)
+                                 (or (eq predicate t)
+                                     (funcall predicate 
+                                      (event-time event) 
+                                      (event-dur event) 
+                                      (event-expression event)))))))
+             (push event result)))
+      (incf i))
+    (cons begin-end (reverse result))))
+
+
+;; SCORE-FILTER-LENGTH -- remove notes beyond cutoff time
+;;
+(defun score-filter-length (score cutoff)
+  (let (result)
+    (dolist (event score)
+      (cond ((<= (event-end event) cutoff)
+             (push event result))))
+    (reverse result)))
+
+
+;; SCORE-REPEAT -- make n copies of score in sequence
+;;
+(defun score-repeat (score n)
+  (let (result)
+    (dotimes (i n)
+      (setf result (score-append result score)))
+    result))
+
+
+;; SCORE-STRETCH-TO-LENGTH -- stretch score to have given length
+;;
+(defun score-stretch-to-length (score length)
+  (let ((begin-time (score-get-begin score))
+        (end-time (score-get-end score))
+        duration stretch)
+    (setf duration (- end-time begin-time))
+    (cond ((< 0 duration)
+           (setf stretch (/ length (- end-time begin-time)))
+           (score-stretch score stretch))
+          (t score))))
+
+
+(defun score-filter-overlap (score)
+  (setf score (score-must-have-begin-end score))
+  (prog (event end-time filtered-score
+         (begin-end (car score)))
+    (setf score (cdr score))
+    (cond ((null score) (return (list begin-end))))
+  loop
+    ;; get event from score
+    (setf event (car score))
+    ;; add a note to filtered-score
+    (push event filtered-score)
+    ;; save the end-time of this event: start + duration
+    (setf end-time (+ (car event) (cadr event)))
+    ;; now skip everything until end-time in score
+  loop2
+    (pop score) ;; move to next event in score
+    (cond ((null score) 
+           (return (cons begin-end (reverse filtered-score)))))
+    (setf event (car score)) ;; examine next event
+    (setf start-time (car event))
+    ;(display "overlap" start-time (- end-time SCORE-EPSILON))
+    (cond ((< start-time (- end-time SCORE-EPSILON))
+           ;(display "toss" event start-time end-time)
+           (go loop2)))
+    (go loop)))
+
+
+(defun score-print (score &optional lines)
+  (let ((len (length score))) ;; len will be how many events left
+    (format t "(")
+    (cond (lines 
+           (setf lines (max lines 3))) ;; always allow up to 3 lines
+          (t ;; no limit on lines, pick a conservatively large number
+           (setf lines (+ 100 len))))
+    (dolist (event score)
+      (cond ((or (> lines 2) (= 1 len))
+             ;; print if we have more than 2 lines left to print or
+             ;; if we are at the last line (always printed)
+             (format t "~S~%" event)
+             (setf lines (1- lines)))
+            ((and (= lines 2) (> len 2)) ;; need ellipsis
+             (format t "... skipping ~A events ...~%" (- len lines))
+             (setf lines (1- lines)))
+            (t nil)) ;; print nothing until end if lines is 1
+      (setf len (1- len)))
+    (format t ")~%")))
+
+(defun score-play (score)
+  (play (timed-seq score)))
+
+
+(defun score-adjacent-events (score function &key
+                              from-index to-index from-time to-time)
+  (setf score (score-must-have-begin-end score))
+  (let ((begin-end (car score))
+        (a nil)
+        (b (second score))
+        (c-list (cddr score))
+        r newscore
+        (i 1)
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time)))
+    (dolist (event (cdr score))
+      (setf r b)
+      (cond ((and (<= start i) (< i stop))
+             (setf r (funcall function a b (car c-list)))))
+      (cond (r
+             (push r newscore)
+             (setf a r)))
+      (setf b (car c-list))
+      (setf c-list (cdr c-list))
+      (incf i))
+    (score-sort (cons begin-end newscore))))
+
+
+(defun score-apply (score fn &key
+                    from-index to-index from-time to-time)
+
+  (setf score (score-must-have-begin-end score))
+  (let ((begin-end (car score))
+        (i 1) 
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        result)
+    (dolist (event (cdr score))
+      (push 
+       (cond ((and (<= start i) (< i stop))
+              (funcall fn (event-time event)
+                          (event-dur event) (event-expression event)))
+             (t event))
+       result)
+      (incf i))
+    (score-sort (cons begin-end result))))
+
+
+(defun score-indexof (score fn &key
+                      from-index to-index from-time to-time)
+  (setf score (score-must-have-begin-end score))
+  (let ((i 1) 
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        result)
+    (dolist (event (cdr score))
+      (cond ((and (<= start i) (< i stop)
+                  (funcall fn (event-time event)
+                              (event-dur event)
+                              (event-expression event)))
+             (setf result i)
+             (return)))
+      (incf i))
+    result))
+
+
+(defun score-last-indexof (score fn &key
+                           from-index to-index from-time to-time)
+  (setf score (score-must-have-begin-end score))
+  (let ((i 1) 
+        (start (find-first-note score from-index from-time))
+        (stop (find-last-note score to-index to-time))
+        result)
+    (dolist (event (cdr score))
+      (cond ((and (<= start i) (< i stop)
+                  (funcall fn (event-time event)
+                           (event-dur event)
+                           (event-expression event)))
+             (setf result i)))
+      (incf i))
+    result))
+
+
+;; SCORE-RANDOMIZE-START -- alter start times with offset
+;; keywords: jitter, offset, feel factor
+;;
+(defun score-randomize-start (score amt &key
+                              from-index to-index from-time to-time)
+  (score-apply score
+               (lambda (time dur expr)
+                 (setf time (+ (real-random (- amt) amt) time))
+                 (setf time (max 0.0 time))
+                 (list time dur expr))))
+
+
+;; SCORE-READ-SMF -- read a standard MIDI file to a score
+;;
+(defun score-read-smf (filename)
+  (let ((seq (seq-create))
+        (file (open-binary filename)))
+    (cond (file
+           (seq-read-smf seq file)
+           (close file)
+           (score-from-seq seq))
+          (t nil))))
+
+
+;; SCORE-READ -- read a standard MIDI file to a score
+;;
+(defun score-read (filename)
+  (let ((seq (seq-create))
+        (file (open filename)))
+    (cond (file
+           (seq-read seq file)
+           (close file)
+           (score-from-seq seq))
+          (t nil))))
+
+
+;; SET-PROGRAM-TO -- a helper function to set a list value
+(defun set-program-to (lis index value default)
+  ;; if length or lis <= index, extend the lis with default
+  (while (<= (length lis) index)
+    (setf lis (nconc lis (list default))))
+  ;; set the nth element
+  (setf (nth index lis) value)
+  ;; return the list
+  lis)
+
+
+(defun score-from-seq (seq)
+  (prog (event tag score programs)
+    (seq-reset seq)
+loop
+    (setf event (seq-get seq))
+    (setf tag (seq-tag event))
+    (cond ((= tag seq-done-tag)
+           (go exit))
+          ((= tag seq-prgm-tag)
+           (let ((chan (seq-channel event))
+                 (when (seq-time event))
+                 (program (seq-program event)))
+             (setf programs (set-program-to programs chan program 0))
+             (push (list (* when 0.001) 1
+                         (list 'NOTE :pitch nil :program program))
+                   score)))
+          ((= tag seq-note-tag)
+         (let ((chan (seq-channel event))
+                 (pitch (seq-pitch event))
+                 (vel (seq-velocity event))
+                 (when (seq-time event))
+                 (dur (seq-duration event)))
+             (push (list (* when 0.001) (* dur 0.001)
+                       (list 'NOTE :chan (1- chan) :pitch pitch :vel vel))
+                   score))))
+    (seq-next seq)
+    (go loop)
+exit
+    (setf *rslt* programs) ;; extra return value
+    (return (score-sort score))))
+
+
+(defun score-write (score filename &optional programs absolute)
+  (score-write-smf score filename programs t absolute))
+
+(defun score-write-smf (score filename &optional programs as-adagio absolute)
+  (let ((file (if as-adagio (open filename :direction :output)
+                            (open-binary filename :direction :output)))
+        (seq (seq-create))
+        (chan 1))
+    (cond (file
+           (dolist (program programs)
+             ;; 6 = SEQ_PROGRAM
+             (seq-insert-ctrl seq 0 0 6 chan program)
+             ;(display "insert ctrl" seq 0 0 6 chan program)
+             (incf chan))
+
+           (dolist (event (cdr (score-must-have-begin-end score)))
+             (let ((time (event-time event))
+                   (dur (event-dur event))
+                   (chan (event-get-attr event :chan 0))
+                   (pitch (event-get-attr event :pitch))
+                   (program (event-get-attr event :program))
+                   (vel (event-get-attr event :vel 100)))
+               (cond (program
+                      ;(display "score-write-smf program" chan program)
+                      (seq-insert-ctrl seq (round (* time 1000))
+                                       0 6 (1+ chan)
+                                       (round program))))
+               (cond ((consp pitch)
+                      (dolist (p pitch)
+                        (seq-insert-note seq (round (* time 1000))
+                                         0 (1+ chan) (round p) 
+                                         (round (* dur 1000)) (round vel))))
+                     (pitch
+                      (seq-insert-note seq (round (* time 1000))
+                                       0 (1+ chan) (round pitch)
+                                       (round (* dur 1000)) (round vel))))))
+           (cond (as-adagio
+                  (seq-write seq file absolute)
+                  (close file)) ;; seq-write does not close file, so do it here
+                 (t
+                  (seq-write-smf seq file))))))) ; seq-write-smf closes file
+
+
+
+;; make a default note function for scores
+;;
+(defun note (&key (pitch 60) (vel 100))
+  ;; load the piano if it is not loaded already
+  (if (not (boundp '*piano-srate*)) 
+      (abs-env (load "piano/pianosyn")))
+  (piano-note-2 pitch vel))
+
+;;================================================================
+
+;; WORKSPACE functions have moved to envelopes.lsp
+
+
+;; DESCRIBE -- add a description to a global variable
+;;
+(defun describe (symbol &optional description)
+  (add-to-workspace symbol)
+  (cond (description
+         (putprop symbol description 'description))
+        (t
+         (get symbol 'description))))
+
+;; INTERPOLATE -- linear interpolation function
+;;
+;; compute y given x by interpolating between points (x1, y1) and (x2, y2)
+(defun interpolate (x x1 y1 x2 y2)
+  (cond ((= x1 x2) x1)
+        (t (+ y1 (* (- x x1) (/ (- y2 y1) (- x2 (float x1))))))))
+
+
+;; INTERSECTION -- set intersection
+;;
+;; compute the intersection of two lists
+(defun intersection (a b)
+  (let (result)
+    (dolist (elem a)
+      (if (member elem b) (push elem result)))
+    result))
+
+;; UNION -- set union
+;;
+;; compute the union of two lists
+(defun union (a b)
+  (let (result)
+    (dolist (elem a)
+      (if (not (member elem result)) (push elem result)))
+    (dolist (elem b)
+      (if (not (member elem result)) (push elem result)))
+    result))
+
+;; SET-DIFFERENCE -- set difference
+;;
+;; compute the set difference between two sets
+(defun set-difference (a b)
+  (remove-if (lambda (elem) (member elem b)) a))
+
+;; SUBSETP -- test is list is subset
+;;
+;; test if a is subset of b
+(defun subsetp (a b)
+  (let ((result t))
+    (dolist (elem a)
+      (cond ((not (member elem b))
+             (setf result nil)
+             (return nil))))
+    result))
+
+;; functions to support score editing in NyquistIDE
+
+(if (not (boundp '*default-score-file*))
+    (setf *default-score-file* "score.dat"))
+
+;; SCORE-EDIT -- save a score for editing by NyquistIDE
+;;
+;; file goes to a data file to be read by NyquistIDE
+;; Note that the parameter is a global variable name, not a score,
+;; but you do not quote the global variable name, e.g. call
+;;    (score-edit my-score)
+;;
+(defmacro score-edit (score-name)
+    `(score-edit-symbol (quote ,score-name)))
+
+(defun score-edit-symbol (score-name)
+    (prog ((f (open *default-score-file* :direction :output))
+           score expr)
+      (cond ((symbolp score-name)
+             (setf score (eval score-name)))
+            (t
+             (error "score-edit expects a symbol naming the score to edit")))
+      (cond ((null f)
+        (format t "score-edit: error in output file ~A!~%" *default-score-file*)
+        (return nil)))
+
+      (format t "score-edit: writing ~A ...~%" *default-score-file*)
+      (format f "~A~%" score-name) ; put name on first line
+      (dolist (event score) ;cdr scor
+        (format f "~A " (event-time event))  ; print start time
+        (format f "~A " (event-dur event))   ; print duration
+
+        (setf expr (event-expression event))
+
+        ; print the pitch and the rest of the attributes
+        (format f "~A " (expr-get-attr expr :pitch))
+        (format f "~A~%" (expr-parameters-remove-attr expr :pitch)))
+      (close f)
+      (format t "score-edit: wrote ~A events~%" (length score))))
+
+
+;; Read in a data file stored in the score-edit format and save
+;; it to the global variable it came from
+(defun score-restore ()
+  (prog ((inf (open *default-score-file*))
+         name start dur pitch expr score)
+    (cond ((null inf)
+           (format t "score-restore: could not open ~A~%" *default-score-file*)
+           (return nil)))
+    (setf name (read inf)) ;; score name
+    (loop
+      (setf start (read inf))
+      (cond ((null start) (return)))
+      (setf dur (read inf))
+      (setf pitch (read inf))
+      (setf expr (read inf))
+      (cond (pitch
+             (setf expr (expr-set-attr expr :pitch pitch)))))
+    (close inf)
+    (setf (symbol-value name) score)))
diff --git a/Release/plug-ins/ShelfFilter.ny b/Release/plug-ins/ShelfFilter.ny
new file mode 100644
index 0000000000000000000000000000000000000000..0fa4e95be501f9a1a76097d182839b93baec1445
--- /dev/null
+++ b/Release/plug-ins/ShelfFilter.ny
@@ -0,0 +1,34 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$name (_ "Shelf Filter")
+$debugbutton disabled
+$author (_ "Steve Daulton")
+$release 2.4.0-1
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control TYPE (_ "Filter type") choice (("Low" (_ "Low-shelf"))
+                                        ("High" (_ "High-shelf"))) 0
+$control HZ (_ "Frequency (Hz)") int "" 1000 10 10000
+$control GAIN (_ "Amount (dB)") int "" -6 -72 72
+
+
+(cond ((> HZ (/ *sound-srate* 2))
+          (format nil (_ "Error.~%Frequency set too high for selected track.")))
+      ((> HZ (/ *sound-srate* 2.1))  ;Handle edge case close to Nyquist frequency.
+          (setf *track* (force-srate (* 2 *sound-srate*) *track*))
+          (if (= TYPE 0)
+              (force-srate *sound-srate* (eq-lowshelf *track* HZ GAIN))
+              (force-srate *sound-srate* (eq-highshelf *track* HZ GAIN))))
+      ((= GAIN 0) "")  ; no-op
+      (t  (if (= TYPE 0)
+              (eq-lowshelf *track* HZ GAIN)
+              (eq-highshelf *track* HZ GAIN))))
diff --git a/Release/plug-ins/SpectralEditMulti.ny b/Release/plug-ins/SpectralEditMulti.ny
new file mode 100644
index 0000000000000000000000000000000000000000..6e62a6897a0f74664b8edfbb21c184a428d2afb0
--- /dev/null
+++ b/Release/plug-ins/SpectralEditMulti.ny
@@ -0,0 +1,70 @@
+$nyquist plug-in
+$version 4
+$type process spectral
+$name (_ "Spectral Edit Multi Tool")
+$author (_ "Paul Licameli")
+$release 2.3.0-1
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+;; SpectralEditMulti.ny by Paul Licameli, November 2014.
+;; Updated by Steve Daulton 2014 / 2015.
+
+
+(defun wet (sig f0 f1 fc)
+  (cond
+    ((not f0) (highpass2 sig f1))
+    ((not f1) (lowpass2 sig f0))
+    (T  (let ((q (/ fc (- f1 f0))))
+          (notch2 sig fc q)))))
+
+(defun result (sig)
+  (let* ((f0 (get '*selection* 'low-hz))
+        (f1 (get '*selection* 'high-hz))
+        (fc (get '*selection* 'center-hz))
+        (bw (get '*selection* 'bandwidth))
+        (tn (truncate len))
+        (rate (snd-srate sig))
+        (transition (truncate (* 0.01 rate))) ; 10 ms
+        (t1 (min transition (/ tn 2)))        ; fade in length (samples)
+        (t2 (max (- tn transition) (/ tn 2))) ; length before fade out (samples)
+        (breakpoints (list t1 1.0 t2 1.0 tn))
+        (env (snd-pwl 0.0 rate breakpoints)))
+    (cond
+      ((not (or f0 f1)) ; This should never happen for a 'spectral' effect.
+        (throw 'error-message
+            (format nil (_ "~aPlease select frequencies.") p-err)))
+      ((and f0 f1 (= f0 f1))
+        (throw 'error-message
+          (format nil (_ "~aBandwidth is zero (the upper and lower~%~
+                       frequencies are both ~a Hz).~%~
+                       Please select a frequency range.")
+                  p-err f0)))
+      ;; Biqud filter fails if centre frequency is very low and bandwidth very high.
+      ;; 'Magic numbers' 10 Hz and 10 octaves are experimental.
+      ((and f0 (< f0 10) bw (> bw 10))
+        (throw 'error-message
+          (format nil (_ "~aNotch filter parameters cannot be applied.~%~
+                      Try increasing the low frequency bound~%~
+                      or reduce the filter 'Width'.")
+                  p-err)))
+      ;; low pass frequency is above Nyquist so do nothing
+      ((and (not f1) (>= f0 (/ *sound-srate* 2.0)))
+          nil)
+      ;; notch frequency is above Nyquist so do nothing
+      ((and f0 f1 (>= fc (/ *sound-srate* 2.0)))
+          nil)
+      ;; high-pass above Nyquist so fade to silence
+      ((and (not f0)  (>= f1 (/ *sound-srate* 2.0)))
+          (mult sig (diff 1.0 env)))
+      (T  (sum (prod env (wet sig f0 f1 fc))
+               (prod (diff 1.0 env) sig))))))
+
+(catch 'error-message
+  (setf p-err (format nil (_ "Error.~%")))
+  (multichan-expand #'result *track*))
diff --git a/Release/plug-ins/SpectralEditParametricEQ.ny b/Release/plug-ins/SpectralEditParametricEQ.ny
new file mode 100644
index 0000000000000000000000000000000000000000..8f78788ad1defd0731d70d17a157616b59077216
--- /dev/null
+++ b/Release/plug-ins/SpectralEditParametricEQ.ny
@@ -0,0 +1,70 @@
+$nyquist plug-in
+$version 4
+$type process spectral
+$preview linear
+$name (_ "Spectral Edit Parametric EQ")
+$debugbutton false
+$author (_ "Paul Licameli")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+;; SpectralEditParametricEQ.ny by Paul Licameli, November 2014.
+;; Updated by Steve Daulton 2014 / 2015.
+
+
+$control CONTROL-GAIN (_ "Gain (dB)") real "" 0 -24 24
+
+(defun wet (sig gain fc bw)
+  (eq-band sig fc gain (/ bw 2)))
+
+(defun result (sig)
+  (let*
+      ((f0 (get '*selection* 'low-hz))
+       (f1 (get '*selection* 'high-hz))
+       (fc (get '*selection* 'center-hz))
+       (bw (get '*selection* 'bandwidth))
+       (tn (truncate len))
+       (rate (snd-srate sig))
+       (transition (truncate (* 0.01 rate)))  ; 10 ms
+       (t1 (min transition (/ tn 2)))         ; fade in length (samples)
+       (t2 (max (- tn transition) (/ tn 2)))  ; length before fade out (samples)
+       (breakpoints (list t1 1.0 t2 1.0 tn))
+       (env (snd-pwl 0.0 rate breakpoints)))
+    (cond
+      ((not (or f0 f1)) ; This should never happen for a 'spectral' effect.
+          (throw 'error-message (format nil (_ "~aPlease select frequencies.") p-err)))
+      ((not f0)
+          (throw 'error-message (format nil (_ "~aLow frequency is undefined.") p-err)))
+      ((not f1)
+          (throw 'error-message (format nil (_ "~aHigh frequency is undefined.") p-err)))
+      ((and fc (= fc 0))
+          (throw 'error-message (format nil (_ "~aCenter frequency must be above 0 Hz.") p-err)))
+      ((and f1 (> f1 (/ *sound-srate* 2)))
+          (throw 'error-message
+            (format nil (_ "~aFrequency selection is too high for track sample rate.~%~
+                        For the current track, the high frequency setting cannot~%~
+                        be greater than ~a Hz")
+                    p-err (/ *sound-srate* 2))))
+      ((and bw (= bw 0))
+          (throw 'error-message
+            (format nil (_ "~aBandwidth is zero (the upper and lower~%~
+                         frequencies are both ~a Hz).~%~
+                         Please select a frequency range.")
+                    p-err f0)))
+      ;; If centre frequency band is above Nyquist, do nothing.
+      ((and fc (>= fc (/ *sound-srate* 2.0)))
+          nil)
+      (t  (sum (prod env (wet sig CONTROL-GAIN fc bw))
+               (prod (diff 1.0 env) sig))))))
+
+(catch 'error-message
+  (setf p-err (format nil (_ "Error.~%")))
+  (if (= CONTROL-GAIN 0)
+      "" ; No-op
+      (multichan-expand #'result *track*)))
diff --git a/Release/plug-ins/SpectralEditShelves.ny b/Release/plug-ins/SpectralEditShelves.ny
new file mode 100644
index 0000000000000000000000000000000000000000..9a3135f90ab0201856f8efa83e9569e44672638f
--- /dev/null
+++ b/Release/plug-ins/SpectralEditShelves.ny
@@ -0,0 +1,77 @@
+$nyquist plug-in
+$version 4
+$type process spectral
+$preview linear
+$name (_ "Spectral Edit Shelves")
+$debugbutton false
+$author (_ "Paul Licameli")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+;; SpectralEditShelves.ny by Paul Licameli, November 2014.
+;; Updated by Steve Daulton 2014 / 2015.
+
+
+$control CONTROL-GAIN (_ "Gain (dB)") real "" 0 -24 24
+
+(defmacro validate (hz)
+"If frequency is above Nyquist, don't use it"
+  `(if (or (>= ,hz (/ *sound-srate* 2.0))
+           (<= ,hz 0))
+        (setf ,hz nil)))
+
+(defun mid-shelf (sig lf hf gain)
+  "Combines high shelf and low shelf filters"
+  (let ((invg (- gain)))
+    (scale (db-to-linear gain)
+           (eq-highshelf (eq-lowshelf sig lf invg)
+                         hf invg))))
+
+(defun wet (sig gain f0 f1)
+  "Apply appropriate filter"
+  (cond
+    ((not f0) (eq-lowshelf sig f1 gain))
+    ((not f1) (eq-highshelf sig f0 gain))
+    (t (mid-shelf sig f0 f1 gain))))
+
+(defun result (sig)
+  (let*
+      ((f0 (get '*selection* 'low-hz))
+       (f1 (get '*selection* 'high-hz))
+       (tn (truncate len))
+       (rate (snd-srate sig))
+       (transition (truncate (* 0.01 rate)))  ; 10 ms
+       (t1 (min transition (/ tn 2)))         ; fade in length (samples)
+       (t2 (max (- tn transition) (/ tn 2)))  ; length before fade out (samples)
+       (breakpoints (list t1 1.0 t2 1.0 tn))
+       (env (snd-pwl 0.0 rate breakpoints)))
+    (cond
+      ((not (or f0 f1)) ; This should never happen for a 'spectral' effect.
+          (throw 'error-message (format nil (_ "~aPlease select frequencies.") p-err)))
+      ((and f0 (>= f0 (/ *sound-srate* 2.0)))
+          ; Shelf is above Nyquist frequency so do nothing.
+          nil)
+      ((and f0 f1 (= f0 f1))
+          (throw 'error-message
+            (format nil (_ "~aBandwidth is zero (the upper and lower~%~
+                         frequencies are both ~a Hz).~%~
+                         Please select a frequency range.")
+                    p-err f0)))
+      (T  (if f0 (validate f0))
+          (if f1 (validate f1))
+          (if (not (or f0 f1))  ; 'validate' may return nil
+              nil               ; Do nothing
+              (sum (prod env (wet sig CONTROL-GAIN f0 f1))
+                  (prod (diff 1.0 env) sig)))))))
+
+(catch 'error-message
+  (setf p-err (format nil (_ "Error.~%")))
+  (if (= CONTROL-GAIN 0)
+      "" ; No-op
+      (multichan-expand #'result *track*)))
diff --git a/Release/plug-ins/StudioFadeOut.ny b/Release/plug-ins/StudioFadeOut.ny
new file mode 100644
index 0000000000000000000000000000000000000000..b4812d9b8e36e3c6ab1a04993fd463d1c30efa8d
--- /dev/null
+++ b/Release/plug-ins/StudioFadeOut.ny
@@ -0,0 +1,43 @@
+$nyquist plug-in
+$version 4
+$type process
+$name (_ "Studio Fade Out")
+$author (_ "Steve Daulton")
+$release 3.0.4-1
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; Produce a smooth and musical sounding fade out.
+;; Applies a sinusoidal fade out with a progressive low-pass
+;; filter from full spectrum at start to 100 Hz at end.
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+;;; sweeping low pass filter 
+ (defun filter (sig dur)
+  (abs-env
+    ;; cross-fade the filter
+    (let* ((nyq-hz (/ *sound-srate* 2))
+           (f-out (r-cos (min (/ dur 2.0) 0.5)))
+           (f-in (diff (snd-const 1 0 *sound-srate* dur) f-out)))
+      (sim
+        (mult f-out sig)
+        (mult f-in (lp sig (pwlv nyq-hz dur 100)))))))
+
+;;; raised cosine
+(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
+    ((< len 3) (format nil (_ "Selection too short.~%It must be more than 2 samples.")))
+    ((< dur 0.2) (mult *track* (r-cos dur)))
+    (t (mult (filter *track* dur)(r-cos dur)))))
+  
diff --git a/Release/plug-ins/adjustable-fade.ny b/Release/plug-ins/adjustable-fade.ny
new file mode 100644
index 0000000000000000000000000000000000000000..4ee7bbe355c8c883f5034393fdbe684f14c23412
--- /dev/null
+++ b/Release/plug-ins/adjustable-fade.ny
@@ -0,0 +1,211 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$preview selection
+$name (_ "Adjustable Fade")
+$debugbutton false
+$author (_ "Steve Daulton")
+$release 3.0.4-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control TYPE (_ "Fade Type") choice (("Up" (_ "Fade Up"))
+                                      ("Down" (_ "Fade Down"))
+                                      ("SCurveUp" (_ "S-Curve Up"))
+                                      ("SCurveDown" (_ "S-Curve Down"))) 0
+$control CURVE (_ "Mid-fade Adjust (%)") real "" 0 -100 100
+$control UNITS (_ "Start/End as") choice (("Percent" (_ "% of Original"))
+                                          ("dB" (_ "dB Gain"))) 0 
+$control GAIN0 (_ "Start (or end)") float-text "" 0 nil nil
+$control GAIN1 (_ "End (or start)") float-text "" 100 nil nil
+$control PRESET (_ "Handy Presets (override controls)") choice (("None" (_ "None Selected"))
+                                                                ("LinearIn" (_ "Linear In"))
+                                                                ("LinearOut" (_ "Linear Out"))
+                                                                ("ExponentialIn" (_ "Exponential In"))
+                                                                ("ExponentialOut" (_ "Exponential Out"))
+                                                                ("LogarithmicIn" (_ "Logarithmic In"))
+                                                                ("LogarithmicOut" (_ "Logarithmic Out"))
+                                                                ("RoundedIn" (_ "Rounded In"))
+                                                                ("RoundedOut" (_ "Rounded Out"))
+                                                                ("CosineIn" (_ "Cosine In"))
+                                                                ("CosineOut" (_ "Cosine Out"))
+                                                                ("SCurveIn" (_ "S-Curve In"))
+                                                                ("SCurveOut" (_ "S-Curve Out"))) 0
+
+;;; Preview takes the entire selection so that we know the correct
+;;; selection length, but preview only needs to process preview length."
+(defun get-input (sig)
+  (if *previewp*
+      (multichan-expand #'trim-input sig)
+      sig))
+
+
+;;; Trim input when previewing."
+(defun trim-input (sig)
+  (let ((dur (min (get-duration 1)
+                  (get '*project* 'preview-duration))))
+    (setf sig (extract-abs 0 dur sig))))
+
+
+;;; Check gain values are in valid range.
+(defun validate-gain ()
+  (setf err (format nil (_ "Error~%~%")))
+  (if (= UNITS 0)  ;percentage values
+      (cond
+        ((or (< GAIN0 0)(< GAIN1 0))
+          (throw 'err (format nil (_ "~aPercentage values cannot be negative.") err)))
+        ((or (> GAIN0 1000)(> GAIN1 1000))
+          (throw 'err (format nil (_ "~aPercentage values cannot be more than 1000 %.") err))))
+      (cond   ;dB values
+        ((or (> GAIN0 100)(> GAIN1 100))
+          (throw 'err (format nil (_ "~adB values cannot be more than +100 dB.~%~%~
+                                      Hint: 6 dB doubles the amplitude~%~
+                                      -6 dB halves the amplitude.") err))))))
+
+
+;;; Select and apply fade
+(defun fade (sig)
+  (when (= PRESET 0)
+    ; Can't use widget validation for gain as range depends on units.
+    (validate-gain))
+  (psetq curve-ratio (/ CURVE 100.0)
+         g0 (gainscale GAIN0)
+         g1 (gainscale GAIN1))
+  (mult (get-input sig)
+    (case PRESET
+      (0  (case TYPE  ; Custom fade
+            (0 (simple (min g0 g1) (max g0 g1) curve-ratio))
+            (1 (simple (max g0 g1) (min g0 g1) curve-ratio))
+            (2 (raised-cos (min g0 g1)(max g0 g1) curve-ratio))
+            (T (raised-cos (max g0 g1) (min g0 g1) curve-ratio))))
+      (1  (linear 0 1))               ; Linear In
+      (2  (linear 1 0))               ; Linear Out
+      (3  (log-exp-curve -60 0))      ; Exponential In
+      (4  (log-exp-curve -60 1))      ; ExponentialOut
+      (5  (log-exp-curve 15.311 0))   ; Logarithmic In
+      (6  (log-exp-curve 15.311 1))   ; Logarithmic Out
+      (7  (simple-curve 0 1 0.5))     ; Rounded In
+      (8  (simple-curve 1 0 0.5))     ; Rounded Out
+      (9  (cosine-curve 0 1))         ; Cosine In
+      (10 (cosine-curve 1 0))         ; Cosine Out
+      (11 (raised-cos 0 1 0.0))       ; S-Curve In
+      (t  (raised-cos 1 0 0.0)))))    ; S-Curve Out
+
+
+;;; Simple Curve:
+;;; Use cosine for + values and linear for -ve.
+(defun simple (g0 g1 curve-ratio)
+  (cond 
+    ((= g0 g1) g0)  ; amplify
+    ((and (> curve-ratio 0)(< curve-ratio 0.5))  ; +ve curve less than 0.5, lin to cosine
+      (let ((curve-ratio (* curve-ratio 2)))
+        (sim (mult (scale-curve g0 g1 (linear g0 g1))
+                   (- 1 curve-ratio))  ; linear
+             (mult (scale-curve g0 g1 (cosine-curve g0 g1))
+                   curve-ratio))))  ; cosine curve
+    ((> curve-ratio 0)
+      (cos-curve g0 g1 (- 1.5 curve-ratio)))              ; +ve curve > 0.5
+    (t (simple-curve g0 g1 (- 1 (* 2 curve-ratio))))))    ; -ve curve
+
+
+;;; Linear fade to the power of 'pow'.
+(defun simple-curve (g0 g1 pow)
+  (curve-adjust g0 g1 pow (linear g0 g1)))
+
+
+;;; Cosine fade to the power of 'pow'.
+(defun cos-curve (g0 g1 pow)
+  (curve-adjust g0 g1 pow (cosine-curve g0 g1)))
+
+
+(defun curve-adjust (g0 g1 pow env)
+  (scale-curve g0 g1
+    (if (= pow 1)
+        env
+        (snd-exp (mult pow (snd-log env))))))
+
+
+;;; Scale curves to min, max.
+(defun scale-curve (g0 g1 env)
+  (sum (min g0 g1)
+       (mult (abs (- g0 g1)) env)))
+
+
+;;; Cosine curve.
+(defun cosine-curve (g0 g1)
+  (let ((step (hz-to-step (/ 0.25 (get-duration 1))))
+        (phase (if (> g0 g1) 90 0)))
+    (osc step 1 *sine-table* phase)))
+
+
+;;; Linear fade in, out.
+(defun linear (g0 g1)
+  (control-srate-abs *sound-srate*
+    (if (> g0 g1)         ; g0 = g1 does not occur here.
+        (pwlv 1 1 0)      ; fade out
+        (pwlv 0 1 1))))   ; else fade in
+
+
+;;; Raised cosine fades.
+(defun raised-cos (g0 g1 curve-ratio)
+  (setq curve-ratio
+    (if (> curve-ratio 0)
+        (exp-scale-mid (* 2 curve-ratio))       ; mid-point -3dB @ Adjust = 50%
+        (exp-scale-mid (* 1.63 curve-ratio))))  ; mid-point -12dB @ Adjust = -50%
+  (setf env
+    ;; sound srate required for accuracy.
+    (control-srate-abs *sound-srate*
+      (cond
+        ((= g0 g1) g0)    ; amplify
+        ((> g0 g1)        ; fade down
+          (snd-exp 
+            (mult (pwlv (- 1 curve-ratio) 1 1)
+                  (snd-log (raised-cosin 90)))))
+        (t (snd-exp       ; fade up
+             (mult (pwlv 1 1 (- 1 curve-ratio))
+                   (snd-log (raised-cosin -90))))))))
+  (sum (min g0 g1)
+       (mult (abs (- g0 g1)) env)))
+
+
+;;; Raised cosine curve.
+(defun raised-cosin (phase)
+  (let ((hz (hz-to-step (/ (get-duration 2)))))
+    (mult 0.5 
+          (sum 1 (osc hz 1 *sine-table* phase)))))
+
+
+;;; log or exponential curve scaled 0 to 1
+;;; x is the minimum level in dB before scaling.
+(defun log-exp-curve (x direction)
+  (control-srate-abs *sound-srate*
+    (let ((x (db-to-linear x)))
+      ;; If direction=0 fade-in else fade-out
+      (if (= direction 0)
+          (setf env (pwev x 1 1))
+          (setf env (pwev 1 1 x)))
+      (mult (/ (- 1 x))  ; normalize to 0 dB
+            (diff env x)))))  ; drop down to silence
+
+
+;;; Curve scaling for S-curve.
+(defun exp-scale-mid (x)
+  (let ((e (exp 1.0)))
+    (/ (- (exp (- 1 x)) e)
+       (- 1 e))))
+
+
+(defun gainscale (gain)
+  (if (= UNITS 0)  ; percent
+      (/ gain 100.0)
+      (db-to-linear gain)))
+
+
+(catch 'err (fade *track*))
diff --git a/Release/plug-ins/beat.ny b/Release/plug-ins/beat.ny
new file mode 100644
index 0000000000000000000000000000000000000000..208f7134722131bd7129829211f1c5ec0f5cfe66
--- /dev/null
+++ b/Release/plug-ins/beat.ny
@@ -0,0 +1,43 @@
+$nyquist plug-in
+$version 4
+$type analyze
+$name (_ "Beat Finder")
+$debugbutton false
+$author (_ "Audacity")
+$release 2.3.2-2
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control THRESVAL (_ "Threshold Percentage") int "" 65 5 100
+
+(setf threshold (/ THRESVAL 100.0))
+
+(defun mix-to-mono (sig)
+  (if (arrayp sig)
+      (sum (aref sig 0) (aref sig 1))
+      sig))
+
+(defun bass-tracker (sig)
+  (let* ((bass (lp sig 50))
+         ;(snd-follow sound floor risetime falltime lookahead)
+         (follower (snd-follow bass 0.001 0.01 0.1 512)))
+    (force-srate 1000 (lp follower 10))))
+
+
+(let ((beats (bass-tracker (mix-to-mono *track*))))
+  (setf peak-sig (peak beats ny:all))
+  (setf threshold (* threshold peak-sig))
+  (do ((time 0.0 (+ time 0.001))
+       (val (snd-fetch beats) (snd-fetch beats))
+       (flag T)
+       labels)
+      ((not val) labels)
+    (when (and flag (> val threshold))
+      (push (list time "B") labels))
+    (setf flag (< val threshold))))
diff --git a/Release/plug-ins/clipfix.ny b/Release/plug-ins/clipfix.ny
new file mode 100644
index 0000000000000000000000000000000000000000..d2edf3b8a4709936ae81d31fe874e4096387fdc0
--- /dev/null
+++ b/Release/plug-ins/clipfix.ny
@@ -0,0 +1,108 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview enabled
+$name (_ "Clip Fix")
+$debugbutton false
+$author (_ "Benjamin Schwartz and Steve Daulton")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+;; Algorithm by Benjamin Schwartz
+;; Clip Fix is a simple, stupid (but not blind) digital-clipping-corrector
+;; The algorithm is fairly simple:
+;; 1. Find all clipped regions
+;; 2. Get the slope immediately on either side of the region
+;; 3. Do a cubic spline interpolation.
+;; 4. Go to next region
+
+
+$control THRESHOLD (_ "Threshold of Clipping (%)") float "" 95 0 100
+$control GAIN (_ "Reduce amplitude to allow for restored peaks (dB)") float "" -9 -30 0
+
+(setf thresh-ratio (/ THRESHOLD 100))
+(setf gain-lin (db-to-linear GAIN))
+(setf buffersize 100000)
+(setf slopelength 4)  ; number of samples used to calculate the exit / re-entry slope
+
+
+(defun declip (sig thresh peak)
+  (let* ((thresh (* thresh peak))
+         (ln (truncate len))
+         (finalbufsize (rem ln buffersize)))
+    ;; Calculate the number of buffers we can process.
+    ;; if final buffer is not large enough for de-clipping we
+    ;; will just add it on the end as is.
+    (if (>= finalbufsize slopelength)
+        (setf buffercount (1+ (/ ln buffersize))) 
+        (setf buffercount (/ ln buffersize)))
+    ;;; Make output sequence from processed buffers
+    (setf out
+      (seqrep (i buffercount)
+        (let* ((step (min buffersize (- ln (* i buffersize))))
+               (buffer (snd-fetch-array sig step step))
+               (processed (process buffer thresh step)))
+          (cue (mult gain-lin
+                    (snd-from-array 0 *sound-srate* processed))))))
+    ;;; If there's unprocessed audio remaining, add it to the end
+    (if (and (> finalbufsize 0)(< finalbufsize slopelength))
+        (seq out (cue (getfinalblock sig finalbufsize gain-lin)))
+        out)))
+
+
+(defun getfinalblock (sig step gain-lin)
+  (let ((block (snd-fetch-array sig step step)))
+    (mult gain-lin (snd-from-array 0 *sound-srate* block))))
+
+
+(defun process (buffer thresh bufferlength)
+  ;;; Find threshold crossings
+  (setf exit-list ())         ; list of times when waveform exceeds threshold
+  (setf return-list ())       ; list of times when waveform returns below threshold
+  ;; Limitation of algorithm: the first and last 'slopelength' at ends of buffer are ignored
+  ;; so that we have enough samples beyond the threshold crossing to calculate the slope.
+  (let ((last-sample (- bufferlength slopelength)))
+    (do ((i slopelength (1+ i)))
+        ((>= i last-sample))
+      (if (>= (abs (aref buffer i)) thresh)
+          (when (< (abs (aref buffer (- i 1))) thresh)   ; we just crossed threshold
+            (push (- i 1) exit-list))
+          (when (>= (abs (aref buffer (- i 1))) thresh)  ; we just got back in range
+            (push i return-list)))))
+  ;; Reverse lists back into chronological order.
+  ;; This is faster than appending values in chronological order.
+  (setf exit-list (reverse exit-list))
+  (setf return-list (reverse return-list))
+  ;; If the audio begins in a clipped region, discard the first return
+  (when (>= (abs (aref buffer (1- slopelength))) thresh)
+    (setq return-list (cdr return-list)))
+  ;; Interpolate between each pair of exit / entry points
+  (let ((slopelen (1- slopelength)))
+    (mapc (lambda (t0 t1)
+            (interpolate buffer t0 t1 slopelen))
+          exit-list return-list))
+  buffer)
+
+
+(defun interpolate (buffer t0 t1 dur)
+  "Cubic spline interpolation"
+  (let* ((d0 (/ (- (aref buffer t0) (aref buffer (- t0 dur))) dur)) ; slope at start
+         (d1 (/ (- (aref buffer (+ t1 dur)) (aref buffer t1)) dur)) ; slope at end
+         (m (/ (+ d1 d0) (* (- t1 t0) (- t1 t0))))
+         (b (- (/ d1 (- t1 t0)) (* m t1))))
+    (do ((j (1+ t0) (1+ j)))
+        ((= j t1))
+      (setf (aref buffer j)
+        (+ (* (- t1 j) (/ (aref buffer t0) (- t1 t0)))
+           (* (- j t0) (/ (aref buffer t1) (- t1 t0)))
+           (* (- j t0) (- j t1) (+ (* m j) b)))))))
+
+
+;; (get '*selection* 'peak) introduced in Audacity 2.1.3
+(multichan-expand #'declip *track* thresh-ratio (get '*selection* 'peak))
diff --git a/Release/plug-ins/crossfadeclips.ny b/Release/plug-ins/crossfadeclips.ny
new file mode 100644
index 0000000000000000000000000000000000000000..e8bbe8ccaf839469dd25f6b9d93945ff8ba96290
--- /dev/null
+++ b/Release/plug-ins/crossfadeclips.ny
@@ -0,0 +1,132 @@
+$nyquist plugin
+$version 4
+$type process
+$mergeclips 1
+$restoresplits 0
+$name (_ "Crossfade Clips")
+$author (_ "Steve Daulton")
+$release 3.0.4-1
+$copyright (_ "GNU General Public License v2.0 or later") 
+
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+;; Instructions:
+;; Place two audio clips into the same track.
+;; Select (approximately) the same amount of audio from the
+;; end of one clip and the start of the other.
+;; Apply the effect.
+;; The selected regions will be crossfaded.
+;;
+;; Note, the audio clips do not need to be touching. Any
+;; white-space between the clips is ignored.
+;;
+;; If the selected region is continuous audio (no splits),
+;; the the first and last halves of the selected audio
+;; will be crossfaded.
+;;
+;; Advanced Tip:
+;; A discontinuity in a waveform may be smoothed by applying
+;; a short crossfade across the glitch.
+
+;; Limitations (should not occur in normal usage).
+;; 1) There may be no more than two clips selected in each channel.
+;; 2) The selection may not start or end in white-space.
+
+
+(setf err1 (format nil (_ "Error.~%Invalid selection.~%More than 2 audio clips selected.")))
+(setf err2 (format nil (_ "Error.~%Invalid selection.~%Empty space at start/ end of the selection.")))
+
+
+(defun find-ends (T0 T1 clips)
+"Look for a split or gap within the selection, or return the mid-point"
+  (let ((trk-ends ())    ;starts of clips
+        (trk-starts ())) ;ends of clips
+    (dolist (clip clips)
+      ;; look for clip enclosing the selection.
+      (when (and (>= (second clip) T1) (<= (first clip) T0))
+        (psetq trk-ends   (list (/ (+ T0 T1) 2))
+               trk-starts (list (/ (+ T0 T1) 2)))
+        (return))
+      ;; look for track starts.
+      (when (and (> (first clip) T0) (< (first clip) T1))
+        (push (first clip) trk-starts))
+      ;; look for track ends.
+      (when (and (> (second clip) T0) (< (second clip) T1))
+        (push (second clip) trk-ends))
+      ; stop looking when we have passed end of selection.
+      (when (> (first clip) T1) (return)))
+    ;; if exactly one split position for crossfading,
+    ;; return clip positions, else error.
+    (cond
+      ((and (= (length trk-ends) 1)
+            (= (length trk-starts) 1)
+            (<= (car trk-ends) (car trk-starts)))
+        (list (car trk-ends)(car trk-starts)))
+      ((or (> (length trk-ends) 1)
+           (> (length trk-starts) 1))
+        (throw 'error err1))
+      (T (throw 'error err2)))))
+
+(defun crossfade (sig out-end in-start end)
+"Do the crossfade"
+  (abs-env
+    (control-srate-abs *sound-srate*
+      (let* ((fade-out (mult sig (env out-end 0)))
+             (cflen (max out-end (- end in-start))) ;crossfade length
+             (finstart (max (- out-end (- end in-start)) 0))
+             (fade-in (mult (extract (- end cflen) end sig)
+                            (env (- cflen finstart) 1 finstart))))
+        (sim fade-out fade-in)))))
+
+(defun env (dur direction &optional (offset 0))
+"Generate envelope for crossfade"
+  (abs-env
+    (if (< dur 0.01)            ;make it linear
+        (control-srate-abs *sound-srate*
+          (if (= direction 0)
+              (pwlv 1 dur 0)      ;fade out
+              (pwlv 0 offset 0 (+ offset dur) 1)))  ;fade in
+        (if (= direction 0)     ;cosine curve
+            (cos-curve dur 0)
+            (seq (s-rest offset)
+                 (cos-curve dur 1))))))
+
+(defun cos-curve (dur direction)
+"Generate cosine curve"
+  (if (= direction 0) ;fade out
+      (osc (hz-to-step (/ 0.25 dur)) dur *sine-table* 90)
+      (osc (hz-to-step (/ 0.25 dur)) dur *sine-table* 0)))
+
+(defun process (sig t0 t1 clips)
+"Find the split positions and crossfade"
+  (setf fadeclips
+    (multichan-expand #'find-ends t0 t1 clips))
+  (if (arrayp fadeclips)
+      (prog ((fade-out-end (min (first (aref fadeclips 0))
+                                (first (aref fadeclips 1))))
+             (fade-in-start (max (second (aref fadeclips 0))
+                                 (second (aref fadeclips 1)))))
+        (return
+          (multichan-expand #'crossfade sig 
+                                       (- fade-out-end t0)
+                                       (- fade-in-start t0)
+                                       (- t1 t0))))
+      (crossfade sig
+                 (- (first fadeclips) t0)
+                 (- (second fadeclips) t0)
+                 (- t1 t0))))
+
+
+;;; Run the program.
+(if (= (length (get '*selection* 'tracks)) 1)
+    (catch 'error
+      (process *track*
+               (get '*selection* 'start) 
+               (get '*selection* 'end)
+               (get '*track* 'clips)))
+    (format nil (_ "Error.~%Crossfade Clips may only be applied to one track.")))
diff --git a/Release/plug-ins/crossfadetracks.ny b/Release/plug-ins/crossfadetracks.ny
new file mode 100644
index 0000000000000000000000000000000000000000..8ca26be32f1636b066f50277d5612220bbdb11f1
--- /dev/null
+++ b/Release/plug-ins/crossfadetracks.ny
@@ -0,0 +1,85 @@
+$nyquist plug-in
+$version 4
+$type process
+$name (_ "Crossfade Tracks")
+$debugbutton disabled
+$preview selection
+$author (_ "Steve Daulton")
+$release 3.0.4-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+$control TYPE (_ "Fade TYPE") choice (
+    ("ConstantGain" (_ "Constant Gain"))
+    ("ConstantPower1" (_ "Constant Power 1"))
+    ("ConstantPower2" (_ "Constant Power 2"))
+    ("CustomCurve" (_ "Custom Curve"))) 0
+$control CURVE (_ "Custom curve") real "" 0 0 1
+$control DIRECTION (_ "Fade direction") choice (
+    (_ "Automatic")
+    ("OutIn" (_ "Alternating Out / In"))
+    ("InOut" (_ "Alternating In / Out"))) 0
+
+
+(defun crossfade ()
+  (setf fade-out
+    (case DIRECTION
+      (0 (equal (guessdirection) 'OUT))   ; auto
+      (1 (oddp (get '*track* 'index)))    ; fade out odd
+      (T (evenp (get '*track* 'index))))) ; fade out even
+  ; Set control rate to sound rate to ensure length is exact.
+  (setf *control-srate* *sound-srate*)
+  (mult *track*
+    (cond
+      (fade-out
+        (case TYPE
+          (0 (pwlv 1 1 0))
+          (1 (osc (hz-to-step (/ (get-duration 4))) 1 *sine-table* 90))
+          (2 (s-sqrt (pwlv 1 1 0)))
+          (T (custom 0))))
+      (T  ; else fade in.
+        (case TYPE
+          (0 (pwlv 0 1 1))
+          (1 (osc (hz-to-step (/ (get-duration 4))) 1))
+          (2 (s-sqrt (pwlv 0 1 1)))
+          (T (custom 1)))))))
+
+(defun custom (inout)
+  ;; 'epsilon' defines the curvature of a logarithmc curve.
+  ;; To avoid log 0 or /0 it must be > 0 and < 1.
+  (let* ((ccurve (+ 0.99 (* -0.98 CURVE)))
+         ; magic number 2.7 gives approx 'constant power' curve at 50% setting.
+         (epsilon (power ccurve 2.7)))
+    (if (= inout 0)
+        (setf logcurve (pwev epsilon 1 1))
+        (setf logcurve (pwev 1 1 epsilon)))
+    ; Scale and invert curve for 0 to unity gain.
+    (sum 1
+         (mult (/ -1 (- 1 epsilon))
+               (diff logcurve epsilon)))))
+
+(defun guessdirection ()
+  ;;; If the selection is closer to the start of the
+  ;;; audio clip, fade in, otherwise fade out.
+  ;;; Use `inclips`, i.e., the clip boundaries before the stretch-rendering pre-processing step.
+  (let* ((start (get '*selection* 'start))
+         (end (get '*selection* 'end))
+         (clips (get '*track* 'inclips))
+         (in-dist end)
+         (out-dist end))
+    (if (arrayp clips)
+        (setf clips (append (aref clips 0)(aref clips 1))))
+    (dotimes (i (length clips))
+      (setf in-dist (min in-dist (abs (- start (first (nth i clips))))))
+      (setf out-dist (min out-dist (abs (- end (second (nth i clips)))))))
+    (if (< in-dist out-dist) 'in 'out)))
+
+
+(if (< (length (get '*selection* 'tracks)) 2)
+    (format nil (_ "Error.~%Select 2 (or more) tracks to crossfade."))
+    (crossfade))
diff --git a/Release/plug-ins/delay.ny b/Release/plug-ins/delay.ny
new file mode 100644
index 0000000000000000000000000000000000000000..94bda9db1c72a865d02bdb9de8e238b8cb3a8903
--- /dev/null
+++ b/Release/plug-ins/delay.ny
@@ -0,0 +1,139 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$name (_ "Delay")
+$debugbutton false
+$author (_ "Steve Daulton")
+$release 2.4.2-2
+$copyright (_ "GNU General Public License v2.0")
+
+
+;; License: GPL v2 or later.
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;; based on 'Delay' by David R. Sky
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control DELAY-TYPE (_ "Delay type") choice ((_ "Regular")
+                                             ("BouncingBall" (_ "Bouncing Ball"))
+                                             ("ReverseBouncingBall" (_ "Reverse Bouncing Ball"))) 0
+$control DGAIN (_ "Delay level per echo (dB)") real "" -6 -30 1
+$control DELAY (_ "Delay time (seconds)") real "" 0.3 0 5
+$control PITCH-TYPE (_ "Pitch change effect") choice (("PitchTempo" (_ "Pitch/Tempo"))
+                                                      ("LQPitchShift" (_ "Low-quality Pitch Shift"))
+                                                      ("HQPitchShift" (_ "High-quality Pitch Shift"))) 0
+$control SHIFT (_ "Pitch change per echo (semitones)") real "" 0 -2 2
+$control NUMBER (_ "Number of echoes") int "" 5 1 30
+$control CONSTRAIN (_ "Allow duration to change") choice ((_ "Yes")(_ "No")) 0 
+
+
+;; High-quality Pitch Shift option added, March 2023.
+;;
+;; "High-quality Pitch Shift" is accomplished with a phase vocoder.
+;; "Pitch/Tempo" and "Low-quality Pitch Shift" remain identical
+;; to previous version of Audacity.
+;;
+;; "Pitch/Tempo" is simple resampling, so both pitch and tempo
+;; of the delayed audio will change (as in Audacity's
+;; "Change Speed" effect).
+;;
+;; "Low-quality Pitch Shift" changes the pitch without changing
+;; the tempo, but has relatively poor sound quality.
+
+
+;;; Pitch shift audio.
+(defun p-shift (sig snd-len ratio)
+  (when (= SHIFT 0)
+    ; no-op.
+    (return-from p-shift sig))
+  (case PITCH-TYPE (0 (change-speed sig ratio))
+                   (1 (lq-pitch sig ratio))
+                   (t (hq-pitch sig snd-len ratio))))
+
+
+;;; Change speed.
+(defun change-speed (sig ratio)
+  (force-srate *sound-srate* 
+    (stretch-abs (/ ratio) (sound sig))))
+
+
+;;; Low quality pitch shift.
+;; This uses the ancient "Synthesis Toolkit" pitch shifter.
+;; STK_PITSHIFT: a simple pitch shifter using delay lines.
+;; Filtering and fixed sample rate are used to squeeze slightly
+;; better sound quality out of this old library.
+(defun lq-pitch(sig ratio)
+  ; pitshift quality best at 44100
+  (let ((sig (force-srate 44100 sig))
+          ; anti-alias filter frequency
+          (minrate (* 0.5 (min *sound-srate* 44100))))
+      (force-srate *sound-srate*
+        ; pitshift requires rates to match
+        (progv '(*sound-srate*) (list 44100)
+          (cond 
+            ((> SHIFT 5)  ; reduce aliasing
+              (pitshift (lp-wall sig (/ minrate ratio)) ratio 1))
+            ((< SHIFT -2)  ; reduce sub-sonic frequencies
+              (pitshift (hp sig 20) ratio 1))
+            (T (pitshift sig ratio 1)))))))
+
+
+;;; Anti-alias low pass filter
+(defun lp-wall (sig freq)
+  (do ((count 0 (1+ count))
+       (freq (* 0.94 freq)))
+      ((= count 10) sig)
+    (setf sig (lowpass8 sig freq))))
+
+
+;;; High quality pitch shift.
+(defun hq-pitch(sig snd-len shift-ratio)
+  (let ((stretchfn (const 1))
+        (pitchfn (const shift-ratio)))
+    (pv-time-pitch sig stretchfn pitchfn snd-len)))
+
+
+;;; Apply effects to echo
+(defun modify (sig echo-num snd-len)
+  (let ((gain (db-to-linear (* echo-num DGAIN)))
+        ; convert semitone shift to ratio.
+        (ratio (power 2.0 (/ (* echo-num SHIFT) 12.0))))
+    (if (= PITCH-TYPE 0)
+        (mult gain (change-speed sig ratio))
+        (mult gain (p-shift sig snd-len ratio)))))
+
+
+;;; Compute echoes.
+(defun delays (sig snd-len)
+  (if (>= DELAY-TYPE 1)  ; Bouncing delay.
+      (setf time-shift (/ DELAY NUMBER))
+      (setf time-shift DELAY))
+  ;; The echo loop.
+  (let ((echo (s-rest 0)))
+    (do ((count 1 (1+ count))
+         (dly 0))
+         ((> count NUMBER)(sim echo sig))
+      (let ((modified-sig (modify sig count snd-len)))
+        (setq dly 
+          (case DELAY-TYPE
+            (0 (+ dly time-shift))
+            (1 (+ dly (* time-shift (- (1+ NUMBER) count))))
+            (2 (+ dly (* time-shift count)))))
+        (setf echo (sim
+            (at 0 (cue echo))
+            (at-abs dly
+                (cue modified-sig))))))))
+
+
+(defun constrain-abs (sig dur)
+  (extract-abs 0 dur (cue sig)))
+
+
+(let* ((dur (get-duration 1))
+       (output (multichan-expand #'delays *track* dur)))
+  (if (= CONSTRAIN 1)
+      (multichan-expand #'constrain-abs output dur)
+      output))
diff --git a/Release/plug-ins/equalabel.ny b/Release/plug-ins/equalabel.ny
new file mode 100644
index 0000000000000000000000000000000000000000..7e98b80a27c24eee1b571032ef0267029bc13895
--- /dev/null
+++ b/Release/plug-ins/equalabel.ny
@@ -0,0 +1,167 @@
+$nyquist plug-in
+$version 4
+$type tool analyze
+$debugbutton false
+$debugflags trace
+$name (_ "Regular Interval Labels")
+$author (_ "Steve Daulton")
+$release 2.3.1-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+
+;; Original version by David R. Sky (http://www.garyallendj.com/davidsky/) 2007.
+;; Based on an idea by Sami Jumppanen, with contributions from
+;; Alex S.Brown, Dominic Mazzoni, Pierre M.I., Gale Andrews.
+;;
+;; TODO: Rewrite as an AUD-DO script so as to remove the requirement for
+;; an audio selection.
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+; i18n-hint: Refers to the controls 'Number of labels' and 'Label interval'.
+$control MODE (_ "Create labels based on") choice (("Both" (_ "Number and Interval"))
+                                                   ("Number" (_ "Number of Labels"))
+                                                   ("Interval" (_ "Label Interval"))) 0
+$control TOTALNUM (_ "Number of labels") int-text "" 10 1 1000
+$control INTERVAL (_ "Label interval (seconds)") float-text "" 10 0.001 3600
+$control REGION (_ "Length of label region (seconds)") float-text "" 0 0 3600
+$control ADJUST (_ "Adjust label interval to fit length") choice ((_ "No")
+                                                                  (_ "Yes")) 0
+$control LABELTEXT (_ "Label text") string "" (_ "Label")
+$control ZEROS (_ "Minimum number of digits in label") choice (("TextOnly" (_ "None - Text Only"))
+                                                               ("OneBefore" (_ "1 (Before Label)"))
+                                                               ("TwoBefore" (_ "2 (Before Label)"))
+                                                               ("ThreeBefore" (_ "3 (Before Label)"))
+                                                               ("OneAfter" (_ "1 (After Label)"))
+                                                               ("TwoAfter" (_ "2 (After Label)"))
+                                                               ("ThreeAfter" (_ "3 (After Label)"))) 2
+$control FIRSTNUM (_ "Begin numbering from") int-text "" 1 0 nil
+$control VERBOSE (_ "Message on completion") choice ((_ "Details")
+                                                     ("Warnings" (_ "Warnings only"))
+                                                     (_ "None")) 0
+
+
+(defun make-labels (num-txt zeropad &aux labels)
+"Generate labels at regular intervals"
+  ;; Get parameters
+  (case MODE
+    (1  ; Based on Number
+        (setf intervals
+          (if (= REGION 0)
+              (/ (get-safe-duration) TOTALNUM)
+              (/ (get-safe-duration) (1- TOTALNUM))))
+        (setf total TOTALNUM))
+    (2  ; Based on Interval
+        (setf total (get-interval-count))
+        (if (= ADJUST 1)
+            (setf intervals (/ (get-safe-duration) total))
+            (setf intervals INTERVAL)))
+    (t  ; Number and Interval
+        (psetq total TOTALNUM
+               intervals INTERVAL)))
+  ;; Loop for required number of labels
+  (do* ((count 0 (1+ count))
+        (time 0 (* count intervals)))
+       ((= count total))
+    (push (make-one-label time (+ FIRSTNUM count) num-txt zeropad)
+          labels))
+
+  (when (and (> REGION 0)(= MODE 2)(= ADJUST 1))
+    (push (make-one-label (get-safe-duration)
+                          (+ FIRSTNUM total)
+                          num-txt
+                          zeropad)
+          labels))
+  ;; Create confirmation message
+  (when (< VERBOSE 2)
+    (message total intervals))
+  labels)
+
+
+(defun message (number intervals)
+"Generate output message in debug window."
+  (if (= number 0)
+      (setf msg (format nil( _ "Error: There is insufficient space to create labels.~%")))
+      (if (> REGION intervals)
+         (setf msg (format nil (_ "Warning: Overlapping region labels.~%")))
+         (setf msg "")))
+  (cond
+  ((= VERBOSE 1)  ; Warnings only
+      (format t msg))
+  (t  (if (> REGION 0)
+          ; i18n-hint:  Type of label
+          (setf labeltype (_ "region labels"))
+          (setf labeltype (_ "point labels")))
+      (when (and (> REGION 0)(= MODE 2)(= ADJUST 1))
+        (setf number (1+ number)))
+      (setf msg
+          ; i18n-hint:  Number of labels produced at specified intervals.
+          (format nil (_ "~a~a ~a at intervals of ~a seconds.~%")
+                  msg number labeltype intervals))
+      (if (> REGION 0)
+          (format t (_ "~aRegion length = ~a seconds.")
+                  msg REGION)
+          (format t msg)))))
+
+
+(defun get-interval-count (&aux dur)
+"Number of labels when interval is specified"
+  (setf dur (get-safe-duration))
+  (case ADJUST
+    ;; Interval is user input value
+    (0  (let ((n (truncate (/ dur INTERVAL))))
+          (if (< (* n INTERVAL) dur)
+              (1+ n)
+              n)))
+    ;; Adjust space between labels to fit length
+    (1  (let* ((min-num (truncate (/ dur INTERVAL)))
+               (max-num (1+ min-num)))
+          (if (and (> min-num 0)
+                   (< (abs (- INTERVAL (/ dur min-num)))
+                      (abs (- INTERVAL (/ dur max-num)))))
+              min-num
+              max-num)))))
+
+
+(defun make-one-label (time num num-txt zeropad)
+"Make a single label"
+  (let* ((num-text (format nil "~a" num))
+         (non-zero-digits (length num-text)))
+    (if (= zeropad 0)
+        (setf num-text "")
+        (dotimes (i (max 0 (- zeropad non-zero-digits)))
+          (setf num-text (format nil "~a~a" "0" num-text))))
+    (if num-txt  ; Number before text in label.
+      (setf text (format nil "~a~a" num-text LABELTEXT))
+      (setf text (format nil "~a~a" LABELTEXT num-text)))
+    (list time (+ time REGION) text)))
+
+
+(defun lasttrackp ()
+"True when processing the final selected track"
+  (let ((index (get '*track* 'index))
+        (num (length (get '*selection* 'tracks))))
+    (= index num)))
+
+
+(defun get-safe-duration ()
+   "Returns a safe duration for the labels to be distributed in"
+   (let ((duration (- (get-duration 1) REGION)))
+      (if (< duration 0)
+          0
+          duration)))
+
+
+(setf num-before-text (<= ZEROS 3))
+(setf zeropad (1+ (rem (1- ZEROS) 3)))
+
+;; Analyze plug-ins may return text message per track but
+;; we only want error messages once, and only one set of labels.
+(if (lasttrackp)
+    (make-labels num-before-text zeropad)
+    "") ; No-op
diff --git a/Release/plug-ins/highpass.ny b/Release/plug-ins/highpass.ny
new file mode 100644
index 0000000000000000000000000000000000000000..466c945230736f6accd1b8bc78f8ab7b6b7de4a1
--- /dev/null
+++ b/Release/plug-ins/highpass.ny
@@ -0,0 +1,40 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$name (_ "High-Pass Filter")
+$debugbutton disabled
+$author (_ "Dominic Mazzoni")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control FREQUENCY (_ "Frequency (Hz)") float-text "" 1000 0 nil
+$control ROLLOFF (_ "Roll-off (dB per octave)") choice (("dB6" (_ "6 dB"))
+                                                        ("dB12" (_ "12 dB"))
+                                                        ("dB24" (_ "24 dB"))
+                                                        ("dB36" (_ "36 dB"))
+                                                        ("dB48" (_ "48 dB"))) 0
+
+
+(cond
+  ; Validate minimum frequency at run time so we can give a
+  ; less cryptic error message than built-in widget validation.
+  ((< FREQUENCY 0.1)
+      (_ "Frequency must be at least 0.1 Hz."))
+  ((>= FREQUENCY (/ *sound-srate* 2.0))
+      (format nil
+              (_ "Error:~%~%Frequency (~a Hz) is too high for track sample rate.~%~%~
+                 Track sample rate is ~a Hz~%~
+                 Frequency must be less than ~a Hz.")
+              FREQUENCY
+              *sound-srate*
+              (/ *sound-srate* 2.0)))
+  (T  (funcall (nth ROLLOFF '(hp highpass2 highpass4 highpass6 highpass8))
+               *track* FREQUENCY)))
diff --git a/Release/plug-ins/label-sounds.ny b/Release/plug-ins/label-sounds.ny
new file mode 100644
index 0000000000000000000000000000000000000000..d21d583b76f5166f13abcd6d0f105f1eb238954b
--- /dev/null
+++ b/Release/plug-ins/label-sounds.ny
@@ -0,0 +1,259 @@
+$nyquist plug-in
+$version 4
+$type analyze
+;i18n-hint: Name of effect that labels sounds
+$name (_ "Label Sounds")
+$debugbutton false
+$author (_ "Steve Daulton")
+$release 3.0.4-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control THRESHOLD (_ "Threshold level (dB)") float "" -30 -100 0
+$control MEASUREMENT (_ "Threshold measurement") choice (("peak" (_ "Peak level"))
+                                                         ("avg" (_ "Average level"))
+                                                         ("rms" (_ "RMS level"))) 0
+$control SIL-DUR (_ "Minimum silence duration") time "" 1 0.01 3600
+$control SND-DUR (_ "Minimum label interval") time "" 1 0.01 7200
+$control TYPE (_ "Label type") choice (("before" (_ "Point before sound"))
+                                       ("after" (_ "Point after sound"))
+                                       ("around" (_ "Region around sounds"))
+                                       ("between" (_ "Region between sounds"))) 2
+$control PRE-OFFSET (_ "Maximum leading silence") time "" 0 0 nil
+$control POST-OFFSET (_ "Maximum trailing silence") time "" 0 0 nil
+;i18n-hint: Do not translate '##1'
+$control TEXT (_ "Label text") string "" (_ "Sound ##1")
+
+
+(setf thresh-lin (db-to-linear THRESHOLD))
+(setf max-labels 10000)  ;max number of labels to return
+
+
+(defun format-time (s)
+  ;;; format time in seconds as h m s.
+  ;;; (Only used for error message if selection > 2^31 samples.)
+  (let* ((hh (truncate (/ s 3600)))
+         (mm (truncate (/ s 60))))
+  ;i18n-hint: hours minutes and seconds. Do not translate "~a".
+  (format nil (_ "~ah ~am ~as")
+      hh (- mm (* hh 60)) (rem (truncate s) 60))))
+
+
+(defun parse-label-text ()
+  ;;; Special character '#' represents an incremental digit.
+  ;;; Return '(digits num pre-txt post-txt) for 
+  ;;; (number-of-digits, initial-value, text-before-number, text-after-number),
+  ;;;  or NIL.
+  ;;; 'initial-value' is a positive integer or zero (default).
+  ;;; Only the first instance of #'s are considered 'special'.
+  (let ((hashes 0)
+        (num nil)
+        (negative nil)
+        (pre-txt "")
+        (post-txt "")
+        ch)
+    (dotimes (i (length TEXT))
+      (setf ch (char TEXT i))
+      (cond
+        ((and (string= post-txt "") (char= ch #\#))
+            (incf hashes))
+        ((and (> hashes 0) (string= post-txt ""))
+            (cond
+              ((digit-char-p ch)
+                (if num
+                    (setf num (+ (* num 10) (digit-char-p ch)))
+                    (setf num (digit-char-p ch))))
+              ((and (not num)(char= ch #\-))
+                (setf negative t))
+              (t (setf post-txt (string ch)))))
+        ((= hashes 0) ;special '#' not yet found
+            (string-append pre-txt (string ch)))
+        (t ;run out of #'s and digit characters.
+            (string-append post-txt (string ch)))))
+      (when negative
+        (setf num (- num)))
+      ;; Replace string literal hash characters.
+      (when (and (> hashes 0) (not num))
+        (dotimes (i hashes)
+          (string-append pre-txt "#")))
+      (list hashes num pre-txt post-txt)))
+
+
+(defun pad (n d)
+  ;;; Return string, int 'n' padded to 'd' digits, or empty string.
+  ;;; Used in formatting label text.
+  (cond
+    (n
+      (let ((negative (minusp n))
+            (n (format nil "~a" (abs n))))
+        (while (< (length n) d)
+          (setf n (format nil "0~a" n)))
+        (if negative
+            (format nil "-~a" n)
+            n)))
+    (t "")))
+
+
+(defun to-mono (sig)
+  ;;; Coerce sig to mono.
+  (if (arrayp sig)
+      (s-max (s-abs (aref sig 0))
+             (s-abs (aref sig 1)))
+      sig))
+
+
+(defun to-avg-mono (sig)
+  ;;; Average of stereo channels
+  (if (arrayp sig)
+      (mult 0.5 (sum (aref sig 0)(aref sig 1)))
+      sig))
+
+
+(defun reduce-srate (sig)
+  ;;; Reduce sample rate to (about) 100 Hz.
+  (let ((ratio (round (/ *sound-srate* 100))))
+    (cond
+      ((= MEASUREMENT 0)  ;Peak
+        (let ((sig (to-mono sig)))
+          (snd-avg sig ratio ratio OP-PEAK)))
+      ((= MEASUREMENT 1)  ;Average absolute level
+        (let ((sig (to-avg-mono (s-abs sig))))
+          (snd-avg sig ratio ratio OP-AVERAGE)))
+      (t  ;RMS
+        (if (arrayp sig)
+            ;; Stereo RMS is the root mean of all (samples ^ 2) [both channels]
+            (let* ((sig (mult sig sig))
+                   (left-mean-sq (snd-avg (aref sig 0) ratio ratio OP-AVERAGE))
+                   (right-mean-sq (snd-avg (aref sig 1) ratio ratio OP-AVERAGE)))
+              (s-sqrt (mult 0.5 (sum left-mean-sq right-mean-sq))))
+            (rms sig))))))
+
+
+(defun find-sounds (sig srate)
+  ;;; Return a list of sounds that are at least 'SND-DUR' long,
+  ;;; separated by silences of at least 'SIL-DUR'.
+  (let ((sel-start (get '*selection* 'start))
+        (snd-list ())
+        (sample-count 0)
+        (sil-count 0)
+        (snd-count 0)
+        (snd-start 0)
+        (label-count 0)
+        (snd-samples (* SND-DUR srate))
+        (sil-samples (* SIL-DUR srate)))
+    ;;Ignore samples before time = 0
+    (when (< sel-start 0)
+      (setf sample-count (truncate (* (abs sel-start) srate)))
+      (dotimes (i sample-count)
+        (snd-fetch sig)))
+    ;;Main loop to find sounds.
+    (do ((val (snd-fetch sig) (snd-fetch sig)))
+        ((not val) snd-list)
+      (cond
+        ((< val thresh-lin)
+            (when (and (>= sil-count sil-samples)(>= snd-count snd-samples))
+              ;convert sample counts to seconds and push to list.
+              (push (list (/ snd-start srate)
+                          (/ (- sample-count sil-count) srate))
+                    snd-list)
+              (incf label-count)
+              (when (= label-count max-labels)
+                (format t (_ "Too many silences detected.~%Only the first 10000 labels added."))
+                (return-from find-sounds snd-list))
+              (setf snd-count 0)) ;Pushed to list, so reset sound sample counter.
+            (when (> snd-count 0) ;Sound is shorter than snd-samples, so keep counting.
+              (incf snd-count))
+            (incf sil-count))
+        ;; Above threshold.
+        (t  (when (= snd-count 0) ;previous sound was push, so this is a new sound.
+              (setf snd-start sample-count))
+            (setf sil-count 0)
+            (incf snd-count)))
+      (incf sample-count))
+    ;; Check for final sound
+    (when (> snd-count 0)
+      (push (list (/ snd-start srate)
+                  (/ (- sample-count sil-count) srate))
+            snd-list))
+    snd-list))
+
+
+(defun return-labels (snd-list)
+  (setf textstr (parse-label-text))
+  ; Selection may extend before t=0
+  ; Find t=0 relative to selection so we can ensure 
+  ; that we don't create hidden labels.
+  (setf t0 (- (get '*selection* 'start)))
+  (setf t1 (- (get '*selection* 'end)))
+  (let ((label-start t0)
+        (label-end t1)
+        (label-text "")
+        (labels ())
+        (final-sound (if (= TYPE 3) 1 0)) ;TYPE 3 = regions  between sounds.
+        ;; Assign variables to parsed label text
+        (digits (first textstr))
+        (num (second textstr))
+        (pre-txt (third textstr))
+        (post-txt (fourth textstr)))
+    ;snd-list is in reverse chronological order
+    (do ((i (1- (length snd-list)) (1- i)))
+        ((< i final-sound) labels)
+      (case TYPE
+        (3  ;;label silences.
+            (setf start-time (second (nth i snd-list)))
+            (setf end-time (first (nth (1- i) snd-list)))
+            ;don't overlap next sound
+            (setf label-start (min end-time (+ start-time PRE-OFFSET)))
+            ;don't overlap previous sound
+            (setf label-end (max start-time (- end-time POST-OFFSET)))
+            ;ensure end is not before start
+            (when (< (- label-end label-start) 0)
+              (setf label-start (/ (+ label-end label-start) 2.0))
+              (setf label-end label-start)))
+        (t  ;; labelling sounds
+            (setf start-time (first (nth i snd-list)))
+            (setf end-time (second (nth i snd-list)))
+            ;don't overlap t0 or previous sound.
+            (setf label-start (max t0 label-start (- start-time PRE-OFFSET)))
+            (setf label-end (+ end-time POST-OFFSET))
+            ;; Don't overlap following sounds.
+            (when (> i 0)
+              (setf label-end (min label-end (first (nth (1- i) snd-list)))))))
+      (setf label-text (format nil
+                               "~a~a~a"
+                               pre-txt
+                               (pad num digits)
+                               post-txt))
+      (case TYPE
+        (0 (push (list label-start label-text) labels)) ;point label before sound
+        (1 (push (list label-end label-text) labels))   ;point label after sound
+        (2 (push (list label-start label-end label-text) labels)) ;sound region
+        (t (push (list label-start label-end label-text) labels)));silent region
+      ;Earliest allowed start time for next label.
+      (setf label-start end-time)
+      ;num is either an int or nil
+      (when num (incf num)))))
+
+
+(let ((sig (reduce-srate *track*)))
+  (setf *track* nil)
+  (setf snd-list (find-sounds sig (snd-srate sig)))
+  (cond
+    ((= (length snd-list) 0)
+      (format nil
+              (_ "No sounds found.~%~
+                 Try lowering 'Threshold level (dB)'.")))
+    ((and (= TYPE 3)
+          (= (length snd-list) 1))
+      (format nil
+              (_ "Labelling regions between sounds requires~%~
+                 at least two sounds.~%~
+                 Only one sound detected.")))
+    (t
+      (return-labels snd-list))))
diff --git a/Release/plug-ins/limiter.ny b/Release/plug-ins/limiter.ny
new file mode 100644
index 0000000000000000000000000000000000000000..327331747aae18fe8e3f80e9fcb651a07eb87f81
--- /dev/null
+++ b/Release/plug-ins/limiter.ny
@@ -0,0 +1,135 @@
+$nyquist plug-in
+$version 4
+$type process
+$name (_ "Limiter")
+$debugbutton false
+$preview enabled
+$author (_ "Steve Daulton")
+$release 3.0.4-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; limiter.ny by Steve Daulton November 2011, updated May 2015.
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control TYPE (_ "Type") choice (("SoftLimit" (_ "Soft Limit"))
+                                 ("HardLimit" (_ "Hard Limit"))
+;i18n-hint: clipping of wave peaks and troughs, not division of a track into clips
+                                 ("SoftClip" (_ "Soft Clip"))
+                                 ("HardClip" (_ "Hard Clip"))) 0
+
+;; Translations don't support "\n", and widgets need a literal string,
+;; so the next two controls must be written on two lines.
+$control GAIN-L (_ "Input Gain (dB)
+mono/Left") real "" 0 0 10
+
+$control GAIN-R (_ "Input Gain (dB)
+Right channel") real "" 0 0 10
+
+$control THRESH (_ "Limit to (dB)") real "" -3 -10 0
+$control HOLD (_ "Hold (ms)") real "" 10 1 50
+$control MAKEUP (_ "Apply Make-up Gain") choice ((_ "No") (_ "Yes")) 0
+
+
+(setf gain-left (db-to-linear GAIN-L))
+(setf gain-right (db-to-linear GAIN-R))
+(setf thresh-lin (db-to-linear THRESH))
+(setf bmakeup (= MAKEUP 1))
+
+
+;;; brick wall limiter
+(defun hardlimit (sig limit)   
+  (let* ((time (/ HOLD 3000.0))  ; lookahead time (seconds)
+         (samples (round (* time *sound-srate*)))  ; lookahead in samples
+         (peak-env (get-env sig samples time limit)))
+    (mult sig
+          (snd-exp (mult -1 (snd-log peak-env))))))
+
+
+;;; Envelope follower for brick wall limiter
+(defun get-env (sig step lookahead limit)
+  (let* ((sig (mult (/ limit) sig))
+         (pad-time (* 3 lookahead))       ; padding required at start (seconds)
+         (pad-s (* 3 step))               ; padding samples
+         (padding (snd-const (peak sig pad-s) 0 *sound-srate* pad-time))
+         (peak-env (snd-avg sig (* 4 step) step OP-PEAK)))
+    (extract 0 1
+        (s-max 1 
+               (sim padding
+                    (at-abs pad-time (cue peak-env)))))))
+
+
+(defun softlimit (sig threshold)
+  (let* ((sig (hardlimit sig 1))
+         (step (truncate (* (/ HOLD 3000.0) *sound-srate*)))
+         (waveshape (snd-avg sig (* 4 step) step op-peak))
+         (env (sum threshold (mult threshold (diff 1 waveshape))))
+         (env (clip env 1))
+         (offset (/ (* 3 step) *sound-srate*))
+         (initial (peak sig (* 2 step)))
+         (pause-lev (sum threshold (mult threshold (diff 1 initial))))
+         (pause-lev (clip pause-lev 0.9))
+         (pause (snd-const pause-lev 0 *sound-srate* offset)))
+    (setf env (sim pause
+                   (at-abs offset (cue env))))
+    (mult sig env)))
+
+
+(defun soft-clip-table ()
+  ;;; Lookup table for soft clipping wave-shaper.
+  (abs-env
+    (sound-srate-abs 44100
+      (control-srate-abs 44100
+        (let* ((knee (- 1 (/ 1.0 pi)))
+               (kcurve (mult knee (osc (hz-to-step (/ (* 4 knee))) knee)))
+               (ikcurve (mult knee (osc (hz-to-step (/ (* 4 knee))) knee *sine-table* -90)))
+               (lin (pwlv -0.5 knee -0.5
+                               (+ knee (/ 2.0 pi)) 0.5 
+                               2.0 0.5
+                               2.0 (+ 0.5 knee)
+                               2.1 (+ 0.5 knee))))
+          (mult (/ 2.0 pi)
+                (sim (at-abs 0 (cue ikcurve))
+                     (at-abs 0 (cue lin))
+                     (at-abs (+ knee (/ 2.0 pi)) (cue kcurve)))))))))
+
+
+(defun soft-clip (sig threshold)
+  (let* ((knee (- 1 (/ 1.0 pi)))
+         (clip-level (* (+ 0.5 knee)(/ 2.0 pi)))
+         (sig (mult clip-level (/ threshold) sig)))
+    (if bmakeup
+        ; Allow a little overhead to avoid hitting 0dB.
+        (mult (/ 0.999 clip-level)
+              (shape sig (soft-clip-table) 1.0))
+        (mult (/ threshold clip-level)
+              (shape sig (soft-clip-table) 1.0)))))
+
+
+(defun makeupgain (sig threshold)
+  (if bmakeup
+      (mult (/ 0.999 threshold) sig) ;keep below 0dB
+      sig))
+
+
+;; Pre-gain
+(setf *track*
+  (if (arrayp *track*)
+      (vector (mult (aref *track* 0) gain-left)
+              (mult (aref *track* 1) gain-right))
+      (mult *track* gain-left)))
+
+
+(case TYPE
+  (0 (makeupgain (multichan-expand #'softlimit *track* thresh-lin)
+                 thresh-lin))
+  (1 (makeupgain (multichan-expand #'hardlimit *track* thresh-lin)
+                 thresh-lin))
+  (2 (soft-clip *track* thresh-lin))
+  (T (makeupgain (clip *track* thresh-lin)
+                 thresh-lin)))
diff --git a/Release/plug-ins/lowpass.ny b/Release/plug-ins/lowpass.ny
new file mode 100644
index 0000000000000000000000000000000000000000..a2a44194176f75e8b9c9a1006c32664829600a90
--- /dev/null
+++ b/Release/plug-ins/lowpass.ny
@@ -0,0 +1,40 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$name (_ "Low-Pass Filter")
+$debugbutton disabled
+$author (_ "Dominic Mazzoni")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0")
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control FREQUENCY (_ "Frequency (Hz)") float-text "" 1000 0 nil
+$control ROLLOFF (_ "Roll-off (dB per octave)") choice (("dB6" (_ "6 dB"))
+                                                        ("dB12" (_ "12 dB"))
+                                                        ("dB24" (_ "24 dB"))
+                                                        ("dB36" (_ "36 dB"))
+                                                        ("dB48" (_ "48 dB"))) 0
+
+
+(cond
+  ; Validate minimum frequency at run time so we can give a
+  ; less cryptic error message than built-in widget validation.
+  ((< FREQUENCY 0.1)
+      (_ "Frequency must be at least 0.1 Hz."))
+  ((>= FREQUENCY (/ *sound-srate* 2.0))
+      (format nil
+              (_ "Error:~%~%Frequency (~a Hz) is too high for track sample rate.~%~%~
+                 Track sample rate is ~a Hz~%~
+                 Frequency must be less than ~a Hz.")
+              FREQUENCY
+              *sound-srate*
+              (/ *sound-srate* 2.0)))
+  (T  (funcall (nth ROLLOFF '(lp lowpass2 lowpass4 lowpass6 lowpass8))
+               *track* FREQUENCY)))
diff --git a/Release/plug-ins/noisegate.ny b/Release/plug-ins/noisegate.ny
new file mode 100644
index 0000000000000000000000000000000000000000..480539cb54c8091ff1bc736f324a22b911188f60
--- /dev/null
+++ b/Release/plug-ins/noisegate.ny
@@ -0,0 +1,174 @@
+$nyquist plug-in
+$version 4
+$type process
+$name (_ "Noise Gate")
+$debugbutton false
+$preview enabled
+$author (_ "Steve Daulton")
+$release 3.0.4-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html .
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control MODE (_ "Select Function") choice (("Gate" (_ "Gate"))
+                                            ("Analyze" (_ "Analyze Noise Level"))) 0
+$control STEREO-LINK (_ "Stereo Linking") choice (("LinkStereo" (_ "Link Stereo Tracks"))
+                                                  ("DoNotLink" (_ "Don't Link Stereo"))) 0
+;; Work around bug 2336 - Text after control is not read by screen reader.
+$control THRESHOLD (_ "Gate threshold (dB)") float "" -40 -96 -6
+$control GATE-FREQ (_ "Gate frequencies above (kHz)") float "" 0 0 10
+$control LEVEL-REDUCTION (_ "Level reduction (dB)") float "" -24 -100 0
+$control ATTACK (_ "Attack (ms)") float "" 10 1 1000
+$control HOLD (_ "Hold (ms)") float "" 50 0 2000
+$control DECAY (_ "Decay (ms)") float "" 100 10 4000
+
+
+;; The gain envelope for the noisegate function may be a mono sound (STEREO-LINK = 1, or *track* is mono)
+;; or an array of sounds (STEREO-LINK = 0 and *track* is stereo).
+;; 'Level Reduction' is similar to "Range" or "Floor", but is a (negative) amount of gain
+;; rather than a fixed level.
+;;
+;; To create the gain envelope:
+;; 1. If stereo track and STEREO-LINK = 1, get the max of left and right.
+;; 2. Add 'hold' signal when level > THRESHOLD.
+;;    This adds a high level signal for 'HOLD' seconds when the level
+;;    falls below the THRESHOLD.
+;; 3. Nyquist GATE function to generate exponential rise and decay.
+;;    Unlike analog noise gates, lookahead is used so that the gate
+;;    begins to open before the signal rises above the THRESHOLD.
+;;    When the THRESHOLD is reached, the gate is fully open.
+;;    This prevents the gate from clipping the beginning of words / sounds.
+;; 4. Scale level of envelope and offset so that we have unity gain above
+;;    THRESHOLD, and 'LEVEL-REDUCTION' below the THRESHOLD.
+;;    If SILENCE-FLAG is set (= 1), gain below the THRESHOLD is zero.
+
+
+; Global variables (treat as constants).
+(setf SILENCE-FLAG (if (> LEVEL-REDUCTION -96) 0 1))
+(setf GATE-FREQ (* 1000.0 GATE-FREQ))
+(setf FLOOR (db-to-linear LEVEL-REDUCTION))
+(setf THRESHOLD (db-to-linear THRESHOLD))
+(setf ATTACK (/ ATTACK 1000.0))
+(setf LOOKAHEAD ATTACK)
+(setf DECAY (/ DECAY 1000.0))
+(setf HOLD (/ HOLD 1000.0))
+
+
+(defun error-check ()
+  (let ((max-hz (* *sound-srate* 0.45))  ;10% below Nyquist should be safe maximum.
+        (max-khz (roundn (* 0.00045 *sound-srate*) 1))
+        (gate-freq-khz (roundn (/ GATE-FREQ 1000.0) 1)))
+    (when (>= GATE-FREQ max-hz)
+      (throw 'err (format nil
+                          (_ "Error.~%~
+                             Gate frequencies above: ~s kHz~%~
+                             is too high for selected track.~%~
+                             Set the control below ~a kHz.")
+                          gate-freq-khz
+                          max-khz))))
+  (when (< len 100) ;100 samples required 
+    (throw 'err (format nil
+                        (_ "Error.~%~
+                            Insufficient audio selected.~%~
+                            Make the selection longer than ~a ms.")
+                        (round-up (/ 100000 *sound-srate*))))))
+
+
+;;; Analysis functions:
+;; Measure the peak level (dB) and suggest setting threshold a little higher.
+
+(defun analyze (sig)
+  ; Return analysis text.
+  (let* ((test-length (truncate (min len (/ *sound-srate* 2.0))))
+         (peakdb (peak-db sig test-length))
+         (target (+ 1.0 peakdb))) ;suggest 1 dB above noise level
+    (format nil
+            (_ "Peak based on first ~a seconds ~a dB~%~
+               Suggested Threshold Setting ~a dB.")
+            (roundn (/ test-length *sound-srate*) 2)
+            (roundn peakdb 2)
+            (roundn target 0))))
+
+
+(defun peak-db (sig test-len)
+  ;; Return absolute peak (dB).
+  ;; For stereo tracks, return the maximum of the channels.
+  (if (arrayp sig)
+      (let ((peakL (peak (aref sig 0) test-len))
+            (peakR (peak (aref sig 1) test-len)))
+        (linear-to-db (max peakL peakR)))
+      (linear-to-db (peak sig test-len))))
+
+
+;;; Utility functions
+
+(defun round-up (num)
+  (round (+ num 0.5)))
+
+
+(defun roundn (num places)
+  ;; Return number rounded to specified decimal places.
+  (if (= places 0)
+      (round num)
+      (let* ((x (format NIL "~a" places))
+             (ff (strcat "%#1." x "f")))
+        (setq *float-format* ff)
+        (format NIL "~a" num))))
+
+
+(defun format-time (s)
+  ;;; format time in seconds as h m.
+  (let* ((hh (truncate (/ s 3600)))
+         (mm (truncate (/ s 60))))
+  ;i18n-hint: hours and minutes. Do not translate "~a".
+  (format nil (_ "~ah ~am") hh (- mm (* hh 60)))))
+
+
+;;; Gate Functions
+
+(defun noisegate (sig follow)
+  ;; Takes a sound and a 'follow' sound as arguments.
+  ;; Returns the gated audio.
+  (let ((gain (/ (- 1 (* SILENCE-FLAG FLOOR)))) ; SILENCE-FLAG is 0 or 1.
+        (env (get-env follow)))
+    (if (> GATE-FREQ 20)
+        (let* ((high (highpass8 sig GATE-FREQ))
+               (low  (lowpass8 sig (* 0.91 GATE-FREQ)))) ;magic number 0.91 improves crossover.
+          (sim (mult high gain env) low))
+        (mult sig gain env))))
+
+
+(defun get-env (follow)
+  ;; Return gate's envelope
+  (let* ((gate-env (gate follow LOOKAHEAD ATTACK DECAY FLOOR THRESHOLD))
+         (gate-env (clip gate-env 1.0)))  ;gain must not exceed unity.
+    (diff gate-env (* SILENCE-FLAG FLOOR))))
+
+
+(defun peak-follower (sig)
+  ;; Return signal that gate will follow.
+  (setf sig (multichan-expand #'snd-abs sig))
+  (when (and (arrayp sig)(= STEREO-LINK 0))
+    (setf sig (s-max (aref sig 0) (aref sig 1))))
+  (if (> HOLD 0)
+      (multichan-expand #'snd-oneshot sig THRESHOLD HOLD)
+      sig))
+
+
+(defun process ()
+  (error-check)
+  ;; For stereo tracks, 'peak-follower' may return a sound
+  ;; or array of sounds, so pass it to 'noisegate' rather than
+  ;; calculating in 'noisegate'.
+  (multichan-expand #' noisegate *track* (peak-follower *track*)))
+
+
+;; Run program
+(case MODE
+  (0 (catch 'err (process)))
+  (T (analyze *track*)))
diff --git a/Release/plug-ins/notch.ny b/Release/plug-ins/notch.ny
new file mode 100644
index 0000000000000000000000000000000000000000..e81fd7c4c062220df18f7772af1068c771916007
--- /dev/null
+++ b/Release/plug-ins/notch.ny
@@ -0,0 +1,32 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$name (_ "Notch Filter")
+$debugbutton false
+$author (_ "Steve Daulton and Bill Wharrie")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control FREQUENCY (_ "Frequency (Hz)") float-text "" 60 0 nil
+$control Q (_ "Q (higher value reduces width)") float-text "" 1 0.1 1000
+
+(cond
+  ((< FREQUENCY 0.1) (_ "Frequency must be at least 0.1 Hz."))
+  ((>= FREQUENCY (/ *sound-srate* 2.0))
+    (format nil
+            (_ "Error:~%~%Frequency (~a Hz) is too high for track sample rate.~%~%~
+               Track sample rate is ~a Hz.~%~
+               Frequency must be less than ~a Hz.")
+            FREQUENCY
+            *sound-srate*
+            (/ *sound-srate* 2.0)))
+  (T  (notch2 *track* FREQUENCY Q)))
diff --git a/Release/plug-ins/nyquist-plug-in-installer.ny b/Release/plug-ins/nyquist-plug-in-installer.ny
new file mode 100644
index 0000000000000000000000000000000000000000..8a5502b90ff1ba4d9bdf4c50b33f338fa8e5b5b5
--- /dev/null
+++ b/Release/plug-ins/nyquist-plug-in-installer.ny
@@ -0,0 +1,257 @@
+$nyquist plug-in
+$version 4
+$type tool
+$name (_ "Nyquist Plugin Installer")
+$debugbutton false
+$preview disabled
+$author "Steve Daulton"
+$release 2.4.0-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+;i18n-hint: "Browse..." is text on a button that launches a file browser.
+$control FILES (_ "Select file(s) to install") file (_ "Browse...") "~/Desktop/" (((_ "Plug-in") (ny NY))
+                      ((_ "Lisp file") (lsp LSP))
+                      ((_ "HTML file") (htm HTM html HTML))
+                      ((_ "Text file") (txt TXT))
+                      ((_ "All supported") (ny NY lsp LSP htm HTM html HTML txt TXT))
+                      ((_ "All files") (""))) "open,exists,multiple"
+$control OVERWRITE (_ "Allow overwriting") choice ((_ "Disallow") (_ "Allow")) 0
+
+
+(defun audacity-version-ok (min-version)
+  ;; No longer required as this plug-in is shipped with Audacity.
+  ;; Left in for illustration purposes.
+  ;; min-version is a list of three numbers (the minimum Audacity version number).
+  ;; Example, if the minimum version required is Audacity 2.4.0, then
+  ;; call (audacity-version-ok '(2 4 0))
+  ;; Returns t if plug-in is running on 2.4.0 or later, otherwise nil.
+  (cond
+    ((get '*audacity* 'version)
+      (mapc (lambda (x y)
+              (cond
+                ((boundp 'isok))
+                ((> x y) (setf isok t))
+                ((< x y) (setf isok nil))))
+            (get '*audacity* 'version)
+            min-version)
+      (or (not (boundp 'isok)) isok))
+    (t nil)))
+
+
+(defun get-file-name (fqname &aux (fname ""))
+  ;; Return file name . extension from fully qualified file name.
+  (dotimes (i (length fqname) fname)
+    (if (char= (char fqname i) *file-separator*)
+        (setf fname "")
+        (setf fname (format nil "~a~a" fname (char fqname i))))))
+
+
+(defun isfilename (fname)
+  ;; Return t if fname looks like valid file name, else nil.
+  (let ((ln (length fname)))
+    (cond
+      ((= ln 0) nil)
+      ((char= (char fname (- ln 1)) *file-separator*) nil)
+      (t t))))
+
+
+(defun existsp (fname)
+  ;; Return t if file exists, else nil.
+  (let ((fp (open fname)))
+    (cond
+      (fp (close fp)
+          t)
+      (t nil))))
+
+
+(defun writeablep (fname)
+  ;; Return t if file is writeable.
+  (let ((fp (open fname :direction :output)))
+    (cond
+      (fp (close fp) t)
+      (t nil))))
+
+
+(defun copy-file (input output)
+  ;; Copy from input file to output file.
+  (let ((ifp (open input :direction :input))
+        (ofp (open output :direction :output)))
+    (do ((line (read-line ifp)(read-line ifp)))
+        ((not line))
+      (format ofp "~a~%" line))
+    (close ifp)
+    (close ofp)))
+
+
+(defun issupported (fname)
+  ;; Return true if it looks like a supported file.
+  ;; For .lsp and .html files, we only check the file extension.
+  ;; For .ny files, we have additional sanity checks that it is a
+  ;; plug-in and not just a Nyquist Prompt script.
+  (let ((goodfname (fix-ext fname)))
+    (cond
+      ((check-ext goodfname ".lsp") t)
+      ((check-ext goodfname ".htm") t)
+      ((check-ext goodfname ".html") t)
+      ((check-ext goodfname ".txt") t)
+      ((not (check-ext goodfname ".ny")) nil)
+      ((has-plugin-header fname) t)
+      (t nil))))
+
+
+(defun check-ext (fname ext)
+  ;; Return true if fname has extension ext.
+  (let* ((fnameln (length fname))
+         (extln (length ext))
+         (restln (- fnameln extln)))
+    (cond
+      ((< fnameln (1+ extln)) nil)  ; Too short to be valid.
+      ((string-equal (subseq fname restln fnameln) ext) t)
+      (t nil))))
+
+
+(defun fix-ext (fname)
+  ;; If string ends in ".ny.txt" or ".lsp.txt", strip off ".txt"
+  (macrolet ((striptxt (fname) `(setf ,fname (subseq ,fname 0 (- ln 4)))))
+    (let ((ln (length fname)))
+      (cond
+        ((and (> ln 8) (string-equal (subseq fname (- ln 8) ln) ".lsp.txt"))
+          (striptxt fname))
+        ((and (> ln 7) (string-equal (subseq fname (- ln 7) ln) ".ny.txt"))
+          (striptxt fname)))
+      fname)))
+
+
+(defun has-plugin-header (fname)
+  ;; Return t if file looks like valid Nyquist plug-in, else nil.
+  (let ((fp (open fname))
+        (teststring "nyquist plug-in"))
+    ; First char may be #\; or #\$
+    (setf b (read-byte fp))
+    (cond
+      ((and (/= b (char-code #\;))(/= b (char-code #\$)))
+        (close fp)
+        nil)
+      ((do* ((i 0 (1+ i))
+             (b (read-byte fp) (read-byte fp))
+             (test (char-code (char teststring i))
+                   (char-code (char teststring i))))
+            ((= i (1- (length teststring))) t)
+          (when (/= b test)
+            (return)))
+        (close fp)
+        t)
+      (t
+        (close fp)
+        nil))))
+
+
+(defun get-file-list (file-string)
+  ;; See https://wiki.audacityteam.org/wiki/Nyquist_File-Button_Tutorial#Open_Multiple_Files
+  (let ((path-string (format nil "(list ~s )" (string-trim "\"" file-string))))
+    (eval-string path-string)))
+
+
+(defun install (fname)
+  ;; Install file fname (fully qualified file name).
+  ;; Push result to list install-success or install-fail.
+  (setf out-path (get '*system-dir* 'user-plug-in))
+  (setf short-name (get-file-name fname))
+  (cond
+    ((not (existsp fname))
+      (push (list 3 fname) install-fail))
+    ((not (issupported fname))
+      (push (list 4 fname) install-fail))
+    (t
+      (setf short-name (fix-ext short-name))
+      (setf out-fname
+          (format nil "~a~a~a" out-path *file-separator* short-name))
+      (setf out-file-exists (existsp out-fname))
+      (cond
+        ;; Check for fails
+        ((and out-file-exists
+              (= OVERWRITE 0))
+          (push (list 5 short-name) install-fail))
+        ((not (writeablep out-fname))
+          (push (list 6 short-name) install-fail))
+        ;; Now the successes
+        ((check-ext short-name ".ny")
+            (copy-file fname out-fname)
+            (if (and out-file-exists
+                     (= OVERWRITE 1))
+                (push (list 1 short-name) install-success)
+                (push (list 0 short-name) install-success)))
+        ;; Output file is writeable and did not previously exist.
+        (t  (copy-file fname out-fname)
+            (push (list 2 short-name) install-success))))))
+
+
+(defun print-results (&aux msg results)
+  ;; Format results and display in human readable form.
+  (cond
+    ((isempty install-success)
+      (setf msg (format nil (_ "Error.~%"))))
+    ((isempty install-fail)
+      (setf msg (format nil (_ "Success.~%Files written to:~%~s~%")
+                        (get '*system-dir* 'user-plug-in))))
+    (t (setf msg (format nil (_ "Warning.~%Failed to copy some files:~%")))))
+  (setf results (append install-success install-fail))
+  (setf results (sort-results results))
+  (let ((status -1))
+    (dolist (m results msg)
+      (when (/= (first m) status)
+        (setf msg (format nil "~a~%~a~%" msg (status (first m))))
+        (setf status (first m)))
+      (setf msg (format nil "~a~a~%" msg (second m))))))
+
+
+(defun isempty (x)
+  ;;Return t if x is an empty list.
+  (unless (listp x)
+    (error "Not a list" x))
+  (if (= (length x) 0) t nil))
+
+
+(defun isnotempty (x)
+  (not (isempty x)))
+
+
+(defun status (num)
+  ;; Return status message corresponding to the installation status number.
+  ;; This allows result messages to be grouped according to installation status.
+  (case num ; Success
+    ; Translations fail when strings contain control characters, so
+    ; use FORMAT directive "~%" instead of "\n" for new line.
+    (0 (format nil (_ "Plug-ins installed.~%(Use the Plug-in Manager to enable effects):")))
+    (1 (_ "Plug-ins updated:"))
+    (2 (_ "Files copied to plug-ins folder:"))
+    ;; Fail
+    (3 (_ "Not found or cannot be read:"))
+    (4 (_ "Unsupported file type:"))
+    (5 (_ "Files already installed ('Allow Overwriting' disabled):"))
+    (6 (_ "Cannot be written to plug-ins folder:"))))
+
+
+(defun sort-results (results)
+  ;; 'results' are either 'install-success' or 'install-fail'.
+  ;; Each item in results is (list status file-name).
+  ;; Returns 'results' sorted by status number.
+  (sort results #'(lambda (x y) (< (car x) (car y)))))
+
+
+;; Global lists
+(setf install-success ())
+(setf install-fail ())
+
+(let ((file-list (get-file-list FILES)))
+  (if (= (length file-list) 0)
+      (format nil (_ "Error.~%No file selected."))
+      (dolist (file file-list (print-results))
+        (install file))))
diff --git a/Release/plug-ins/pluck.ny b/Release/plug-ins/pluck.ny
new file mode 100644
index 0000000000000000000000000000000000000000..d95313c9920d1f41cd0c0f2f3f6aee0dc112feba
--- /dev/null
+++ b/Release/plug-ins/pluck.ny
@@ -0,0 +1,48 @@
+$nyquist plug-in
+$version 4
+$type generate
+$name (_ "Pluck")
+$debugbutton false
+$preview linear
+$author (_ "David R.Sky")
+$release 2.4.2
+$copyright (_ "GNU General Public License v2.0")
+
+
+;;MIDI values for C notes: 36, 48, 60 [middle C], 72, 84, 96.
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control PITCH (_ "Pluck MIDI pitch") int "" 60 1 120
+$control FADE (_ "Fade-out type") choice ((_ "Abrupt") (_ "Gradual")) 0
+$control DUR (_ "Duration (60s max)") time "" 1 0 60
+
+
+; set final-amp for abrupt or gradual fade
+(setf final-amp (if (= FADE 1) 0.001 0.000001))
+
+(cond
+  ((> DUR 0)
+    ;; Get length of preview
+    (setq pdur
+      (if *previewp*
+          (get '*project* 'preview-duration)
+          DUR))
+
+    (let* ((pluck-sound (snd-pluck *sound-srate* (step-to-hz PITCH) 0 DUR final-amp))
+           (pluck-sound (extract-abs 0 pdur pluck-sound)) ; shorten if necessary for preview.
+           (max-peak (peak pluck-sound ny:all)))
+      ;; snd-pluck has a random element and will occasionally produce
+      ;; zero amplitude at very high pitch settings. Avoid division by zero.
+      (if (> max-peak 0)
+          (scale (/ 0.8 max-peak) pluck-sound)
+          pluck-sound)))
+  ;; Length of sound is zero!
+  ;; If previewing give Audacity a bit of silence, else return null string.
+  (*previewp* (s-rest 0.1))
+  (t ""))
diff --git a/Release/plug-ins/rhythmtrack.ny b/Release/plug-ins/rhythmtrack.ny
new file mode 100644
index 0000000000000000000000000000000000000000..981188f7bd7a8cc3dfd6229f3c395173aeac0acb
--- /dev/null
+++ b/Release/plug-ins/rhythmtrack.ny
@@ -0,0 +1,247 @@
+$nyquist plug-in
+$version 4
+$type generate
+$name (_ "Rhythm Track")
+$debugbutton false
+$preview linear
+$author (_ "Dominic Mazzoni, David R. Sky and Steve Daulton")
+$release 3.0.0-2
+$copyright (_ "GNU General Public License v2.0")
+
+
+;; Drip sound generator by Paul Beach
+
+;; TODO: add more drum sounds
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control TEMPO (_ "Tempo (bpm)") real (_ "30 - 300 beats/minute") 120 30 300
+$control TIMESIG (_ "Beats per bar") int (_ "1 - 20 beats/measure") 4 1 20
+$control SWING (_ "Swing amount") float (_ "+/- 1") 0 -1 1
+$control text (_ "Set 'Number of bars' to zero to enable the 'Rhythm track duration'.")
+$control BARS (_ "Number of bars") int (_ "1 - 1000 bars") 16 0 1000
+$control CLICK-TRACK-DUR (_ "Rhythm track duration") time (_ "Used if 'Number of bars' = 0") 0 0 nil  
+$control OFFSET (_ "Start time offset") time (_ "Silence before first beat") 0 0 nil
+$control CLICK-TYPE (_ "Beat sound") choice (("Metronome" (_ "Metronome Tick"))
+                                             (_ "Ping (short)")
+                                             (_ "Ping (long)")
+                                             (_ "Cowbell")
+                                             ("ResonantNoise" (_ "Resonant Noise"))
+                                             ("NoiseClick" (_ "Noise Click"))
+                                             (_ "Drip (short)")
+                                             (_ "Drip (long)")) 0
+
+$control HIGH (_ "MIDI pitch of strong beat") int (_ "18 - 116") 84 18 116
+$control LOW (_ "MIDI pitch of weak beat") int (_ "18 - 116") 80 18 116
+
+
+;; Helper functions:
+
+(defun round-up (x)
+  (if (> x (truncate x))
+    (truncate (1+ x))
+    (truncate x)))
+
+
+;; Filtering causes changes amplitude, so we normalize to
+;; achieve a predictable level.
+(defun normalize (sig)
+  (scale (/ (peak sig ny:all)) sig))
+
+
+(defun s-rest-abs (d)
+  (abs-env (s-rest d)))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Click sound synthesis
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;; Drip sound by Paul Beach www.proviewlandscape.com/liss/
+(defun drip (p) ;p is pitch in hz
+  (let* ((maxhz (/ *sound-srate* 2.1))
+        (hz1 (min maxhz (* 2.40483  p)))
+        (hz2 (min maxhz (* 5.52008  p)))
+        (hz3 (min maxhz (* 8.653  p)))
+        (hz4 (min maxhz (* 11.8  p))))
+    (lp 
+      (stretch-abs 1
+        (mult (exp-dec 0 0.015 0.25) 
+          (sim
+            (mult (hzosc hz1) 0.5)
+            (mult (hzosc hz2)  0.25)
+            (mult (hzosc hz3)  0.125)
+            (mult (hzosc hz4)  0.0625))))
+    440)))
+
+
+;; Metronome tick by Steve Daulton.
+(defun metronome-tick (hz peak)
+  (let* ((ln 300)
+         (sig-array (make-array ln))
+         (x 1))
+    ;; generate some 'predictable' white noise
+    (dotimes (i ln)
+      (setf x (rem (* 479 x) 997))
+      (setf (aref sig-array i) (- (/ x 500.0) 1)))
+    (setf sig (sim (s-rest-abs 0.2)
+                   (snd-from-array 0 44100 sig-array)))
+    (setf sig
+      (mult (abs-env (pwev 10 (/ ln 44100.0) 2 1 0))
+            (highpass8  (lowpass2 sig (* 2 hz) 6)
+                        hz)))
+    (let ((gain (/ (peak sig 300))))
+      ; The '1.11' factor makes up for gain reduction in 'resample'
+      (mult (abs-env (pwlv 1.11 0.02 1.11 0.05 0 ))
+        (jcrev (mult peak gain sig) 0.01 0.1)))))
+
+
+;; Cowbell by Steve Daulton.
+(defun cowbell (hz)
+  (sim
+    (mult (pwev 0.3 0.8 0.0005)
+          (hzosc hz *tri-table*)
+          (hzosc (* hz 3.46) *tri-table*))
+    (mult (pwev 0.7 0.2 0.01)
+          (hzosc (* hz 7.3) *tri-table*)
+          (hzosc (* hz 1.52) *tri-table*))))
+
+
+;; Single tick generators:
+
+(defun get-metronome-tick (hz gain)
+  (resample
+    (sound-srate-abs 44100 (metronome-tick hz gain))
+    *sound-srate*))
+
+
+(defun get-ping (pitch ticklen)
+  (stretch-abs ticklen
+    (mult
+      (control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
+      (osc pitch))))
+
+
+(defun get-resonant-noise (pitch)
+  (stretch-abs 0.05 ; 50 milliseconds
+    (mult
+      (control-srate-abs *sound-srate* (pwl 0.05 amp 0.95 amp 1))
+      (normalize (lowpass2 (noise 1) (step-to-hz pitch) 20)))))
+
+
+(defun get-noise-click (pitch)
+  (stretch-abs 0.005
+    (mult
+      (control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
+      (normalize (lowpass2 (noise 1) (step-to-hz pitch) 2)))))
+
+
+(defun get-drip (pitch ticklen)
+  (stretch-abs ticklen
+    (mult
+      (control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
+      (normalize (drip (step-to-hz pitch))))))
+
+
+(defun get-cowbell (pitch)
+  (mult 0.8 (cowbell (step-to-hz pitch))))
+
+
+;; Make selected click
+(defun click (accent)
+  (setq pitch (if (= accent 1) HIGH LOW))
+  (setq amp (if (= accent 1) 0.75 0.5))
+  (case CLICK-TYPE
+    (0 (get-metronome-tick (step-to-hz pitch) amp))
+    (1 (get-ping pitch 0.01))
+    (2 (get-ping pitch 0.08))
+    (3 (get-cowbell pitch))
+    (4 (get-resonant-noise pitch))
+    (5 (get-noise-click pitch))
+    (6 (get-drip pitch 0.007))
+    (t (get-drip pitch 0.1))))
+
+
+(defun swing-adjust (i val)
+  (* val (/ 3.0) (rem (1+ i) 2)))
+
+
+;Make one measure and save it in the global *measure*
+(defun makemeasure ()
+  (setf *measure*
+    (sim
+      (s-rest (* TIMESIG beatlen)) ;required for trailing silence
+      (click 1) ;accented beat
+      (simrep (count (- TIMESIG 1))
+        (at-abs (* beatlen (+ count 1 (swing-adjust count SWING)))
+            (cue (click 0))))))) ;unaccented beat
+
+
+(defun samplecount (total)
+  ;;; Return number of samples required to reach target
+  (defun lastsample (target)
+    (let ((required (- target total)))
+      (setf total target)
+      required))
+  (function lastsample))
+
+
+(defun get-measure (barnum)
+  (let ((end (* (1+ barnum) (* TIMESIG beatlen)))
+        required-samples)
+    ;; Actual end time is integer samples
+    (setf end (round (* end *sound-srate*)))
+    (setf required-samples (funcall addsamples end))
+    (setf *measure* (set-logical-stop (cue *measure*)
+                                      (/ required-samples *sound-srate*))))
+  *measure*)
+
+
+(defun make-click-track (barcount mdur)
+  (seqrep (i barcount) (cue (get-measure i))))
+
+
+;;;;;;;;;;;;;;;;;
+;; MAIN PROGRAM 
+;;;;;;;;;;;;;;;;;
+      
+
+(setf beatlen (/ 60.0 TEMPO))
+
+;call function to make one measure
+(makemeasure)
+
+; If 'Number of bars' = 0, calculate bars from 'Rhythm track duration'.
+(if (= BARS 0)
+    (setq barcount (/ CLICK-TRACK-DUR (* TIMESIG beatlen)))
+    (setf barcount BARS))
+
+;if previewing, restrict number of bars
+(let ((preview (/ (get '*project* 'preview-duration)
+                  (* TIMESIG beatlen))))
+  (if *previewp*
+      (setf barcount (min preview barcount))))
+
+;round up number of bars
+(setf barcount (round-up barcount))
+
+;; Calculate LEN for progress bar.
+(setf len (/ (* 60.0 *sound-srate* TIMESIG barcount) TEMPO))
+
+;; Initialize sample count
+(setf addsamples (samplecount 0))
+
+(if (< barcount 1)
+    (format nil (_ "Set either 'Number of bars' or~%~
+                    'Rhythm track duration' to greater than zero."))
+    (if *previewp*
+        ;; Don't preview the offset (silence).
+        (make-click-track barcount (* TIMESIG beatlen))
+        (seq (s-rest OFFSET)
+             (make-click-track barcount (* TIMESIG beatlen)))))
diff --git a/Release/plug-ins/rissetdrum.ny b/Release/plug-ins/rissetdrum.ny
new file mode 100644
index 0000000000000000000000000000000000000000..e6b1fe95a98050b4ca315675a6b882baad3c40b2
--- /dev/null
+++ b/Release/plug-ins/rissetdrum.ny
@@ -0,0 +1,82 @@
+$nyquist plug-in
+$version 4
+$type generate
+$preview linear
+$i18n-hint named for Jean-Claude Risset (silent t)
+$name (_ "Risset Drum")
+$debugbutton false
+$author (_ "Steven Jones")
+$release 2.3.0-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; rissetdrum.ny by Steven Jones, after Jean Claude Risset.
+;; Updated by Steve Daulton 2012 and May 2015.
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control FREQ (_ "Frequency (Hz)") real "" 100 50 2000
+$control DECAY (_ "Decay (seconds)") real "" 2 0.1 60
+$control CF (_ "Center frequency of noise (Hz)") real "" 500 100 5000
+$control BW (_ "Width of noise band (Hz)") real "" 400 10 1000
+$control NOISE (_ "Amount of noise in mix (percent)") real "" 25 0 100
+$control GAIN (_ "Amplitude (0 - 1)") real "" 0.8 0 1
+
+
+;; Reduce length when previewing.
+(setq pdur
+  (if *previewp*
+      (get '*project* 'preview-duration)
+      DECAY))
+
+
+(setq *rdrum-table* 
+  (list 
+    (mult 0.17
+      (sum
+        (scale 1.00 (build-harmonic 10 2048))
+        (scale 1.50 (build-harmonic 16 2048))
+        (scale 2.00 (build-harmonic 22 2048))
+        (scale 1.50 (build-harmonic 23 2048))))
+    (hz-to-step 1) t))
+
+
+(defun log2 (n)
+  (/ (log (float n))(log 2.0)))
+
+
+(defun percussion-env (dur)
+  (let* ((half-life (expt 2.0 (- (log2 dur) 3))))
+    (exp-dec 0 half-life dur)))
+
+ 
+(defun risset-drum ()
+  (let* ((decay2 (* DECAY 0.50))
+         (low-note (* FREQ 0.10))
+         (tone-gain (- 1 NOISE)))
+    (setf pink (lowpass6 (noise decay2) BW))
+    (setf rdrum 
+      (mult tone-gain 
+        (osc (hz-to-step low-note) decay2 *rdrum-table*)))
+    (setf noise-band 
+      (mult NOISE 
+        (sine (hz-to-step CF) decay2)
+        pink))
+    (sum 
+      (mult 
+        (percussion-env decay2)
+        (sum noise-band rdrum ))
+      (mult tone-gain 
+        (percussion-env DECAY)
+        (sine (hz-to-step FREQ) DECAY)))))
+
+
+;; Generate and normalize
+(let* ((output (risset-drum))
+       (output (extract-abs 0 pdur output)) ; shorten if necessary for preview.
+       (peakval (peak output ny:all)))
+  (scale (/ GAIN peakval) output))
diff --git a/Release/plug-ins/rms.ny b/Release/plug-ins/rms.ny
new file mode 100644
index 0000000000000000000000000000000000000000..a39d61382faa1e622e7fb228f874bfc1b0ba23ea
--- /dev/null
+++ b/Release/plug-ins/rms.ny
@@ -0,0 +1,67 @@
+;nyquist plug-in
+;version 4
+;type analyze
+;name "Measure RMS"
+;debugbutton false
+;author "Steve Daulton"
+;release 2.3.1-1
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+;; This plug-in demonstrates how authors of Nyquist plug-ins may add translations
+;; for output messages. It is not currently possible to provide translations for strings
+;; in the header comments (such as the plug-in name) of 3rd party plug-ins.
+
+
+;; Translations strings:
+;; The "en" translation is not normally required unless the original text is in another
+;; language, but it can make testing easier.
+(setf *locale*
+  '(("en" (("Left" "Left")
+           ("Right" "Right")
+           ("Stereo" "Stereo")
+           ("Mono" "Mono")
+           ("dB" "dB")))
+    ("de" (("Left" "Links")
+           ("Right" "Rechts")
+           ("Stereo" "Stereo")
+           ("Mono" "Mono")
+           ("dB" "dB")))
+    ("es" (("Left" "Izquierda")
+           ("Right" "Derecha")
+           ("Stereo" "Estéreo")
+           ("Mono" "Mono")
+           ("dB" "dB")))
+    ("fr" (("Left" "Gauche")
+           ("Right" "Droite")
+           ("Stereo" "Stéréo")
+           ("Mono" "Mono")
+           ("dB" "dB")))
+    ("ru" (("Left" "Левый")
+           ("Right" "Правый")
+           ("Stereo" "Стерео")
+           ("Mono" "Моно")
+           ("dB" "дБ")))))
+
+
+(defun stereo-rms(ar)
+  ;;; Stereo RMS is the root mean of all (samples ^ 2) [both channels]
+  (let ((left-mean-sq (* (aref ar 0)(aref ar 0)))
+        (right-mean-sq (* (aref ar 1)(aref ar 1))))
+    (sqrt (/ (+ left-mean-sq right-mean-sq) 2.0))))
+
+
+(let ((rms (get '*selection* 'rms)))
+  (if (arrayp rms)
+      (format nil "~a: \t~a ~a~%~
+                  ~a: \t~a ~a~%~
+                  ~a: \t~a ~a"
+                  (_ "Left") (linear-to-db (aref rms 0)) (_ "dB")
+                  (_ "Right") (linear-to-db (aref rms 1)) (_ "dB")
+                  (_ "Stereo") (linear-to-db (stereo-rms rms)) (_ "dB"))
+      (format nil "~a: \t~a ~a" (_ "Mono")(linear-to-db rms)(_ "dB"))))
diff --git a/Release/plug-ins/sample-data-export.ny b/Release/plug-ins/sample-data-export.ny
new file mode 100644
index 0000000000000000000000000000000000000000..0f173b86029392d7bc1a19697451f4172016b020
--- /dev/null
+++ b/Release/plug-ins/sample-data-export.ny
@@ -0,0 +1,510 @@
+$nyquist plug-in
+$version 4
+$type tool analyze
+$name (_ "Sample Data Export")
+$debugbutton false
+$author (_ "Steve Daulton")
+$release 3.0.4-2
+$copyright (_ "GNU General Public License v2.0 or later")
+
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+;; To enable L/R prefix before alternate L/R channels
+;; (text output with header only)
+;; remove the semicolon from the start of the next line:
+;(setq LR-PREFIX '("L: " "R: "))
+
+$control NUMBER (_ "Limit output to first") int-text (_ "samples") 100 1 1000000
+$control UNITS (_ "Measurement scale") choice ((_ "dB") (_ "Linear")) 0
+$control FILENAME (_ "Export data to") file (_ "Select a file") "*default*/sample-data.txt" (((_ "Text file") (txt TXT))
+                           ((_ "CSV files") (csv CSV))
+                           ((_ "HTML files") (html HTML htm HTM))
+                           ((_ "All files") (""))) "save,overwrite"
+$control FILEFORMAT (_ "Index (text files only)") choice ((_ "None")
+                                                          ("Count" (_ "Sample Count"))
+                                                          ("Time" (_ "Time Indexed")))
+$control HEADER (_ "Include header information") choice ((_ "None")
+                                                         (_ "Minimal")
+                                                         (_ "Standard")
+                                                         (_ "All")) 0
+$control OPTEXT (_ "Optional header text") string "" ""
+$control CHANNEL-LAYOUT (_ "Channel layout for stereo") choice (;i18n-hint: Left and Right
+                                                                ("SameLine" (_ "L-R on Same Line"))
+                                                                ("Alternate" (_ "Alternate Lines"))
+                                                                ;i18n-hint: L for Left
+                                                                ("LFirst" (_ "L Channel First"))) 0
+$control MESSAGES (_ "Show messages") choice ((_ "Yes")
+                                              ("Errors" (_ "Errors Only"))
+                                              (_ "None")) 0
+
+
+;; Global constants
+(setf NUMBER (min (truncate len) NUMBER))
+(when (not (boundp 'LR-PREFIX))(setq LR-PREFIX nil))
+(setq *float-format* "%1.5f") ; 5 decimal places
+
+
+;;; Return file extension or empty string
+(defun get-extension (fname)
+  (let ((n (1- (length fname)))
+        (ext ""))
+    (do ((i n (1- i)))
+        ((= i 0) ext)
+      (when (char= (char fname i) #\.)
+        (setf ext (subseq fname (1+ i)))
+        (return ext)))))
+
+
+;;; stereo peak
+(defun stereomax (snd)
+  (if (arrayp *track*)
+      (max (peak (aref *track* 0) NUMBER)
+           (peak (aref *track* 1) NUMBER))
+      (peak *track* NUMBER)))
+
+
+;;; stereo rms
+(defun srms (snd)
+  (if (arrayp snd)
+      (let* ((sql (mult (aref *track* 0)(aref *track* 0)))
+             (sqr (mult (aref *track* 1)(aref *track* 1)))
+             (avgsq (mult 0.5 (sum sql sqr)))
+             (avgsq (snd-avg avgsq NUMBER NUMBER op-average)))
+        (lin-to-db (peak (snd-sqrt avgsq) 1)))
+      (let* ((sndsq (mult snd snd))
+             (avgsq (snd-avg sndsq NUMBER NUMBER op-average)))
+        (lin-to-db (peak (snd-sqrt avgsq) 1)))))
+
+
+;;; DC off-set mono
+(defun dc-off-mon (sig len)
+  (let* ((total 0)
+         (sig (snd-copy sig))
+         (ln (truncate len)))
+    (dotimes (num ln)
+      (setq total (+ total (snd-fetch sig))))
+    (/ total (float len))))
+
+
+;;; DC offset (mono/stereo)
+(defun dc-off (sig)
+  (if (arrayp sig)
+      (let ((lin0 (dc-off-mon (aref sig 0) NUMBER))
+            (lin1 (dc-off-mon (aref sig 1) NUMBER)))
+        (list lin0 (lin-to-db (abs lin0)) lin1 (lin-to-db (abs lin1))))
+      (let ((lin (dc-off-mon sig NUMBER)))
+        (list lin (lin-to-db (abs lin))))))
+
+
+;;; Platform independent representation of negative infinity
+(defun lin-to-db (val)
+  (if (= val 0)
+    ;i18n-hint abbreviates negative infinity
+    (_ "[-inf]")
+    (linear-to-db val)))
+
+
+;;; Get sample and convert to dB if required
+(defun snd-get (snd &optional (dB 0))
+  (if (= dB 0)                              ; dB scale
+      (lin-to-db (abs (snd-fetch snd)))
+      (snd-fetch snd)))                     ; linear scale
+
+
+;; FILEFORMAT  0=Text List, 1=Sample count index, 2=Time index, 3=CSV,
+;; (4=html but not used here).
+;; Optional 'same' [line] argument is either 'true' or 'nil'
+(defun formatprint (val snd &optional same)
+  (case FILEFORMAT
+    (0 (format fp "~a~a"                    ; plain list
+                  (snd-get snd UNITS)
+                  (if same "\t" "\n")))
+    (1 (format fp "~a\t~a~a"                ; count index
+                  val
+                  (snd-get snd UNITS)
+                  (if same "\t" "\n")))
+    (2 (format fp "~a\t~a~a"                ; time index
+                  (/ (1- val) *sound-srate*)
+                  (snd-get snd UNITS)
+                  (if same "\t" "\n")))
+    (3 (format fp "~a~a"                    ; csv
+                  (snd-get snd UNITS)
+                  (if (or (= CHANNEL-LAYOUT 2) same) "," "\n")))))
+
+
+;;; Print sample data to file
+(defun print-text (sig)
+  (do ((n 1 (1+ n)))
+      ((> n NUMBER))
+    (if (arrayp sig)  ; Stereo (alternate lines)
+        (progn
+          ;; option to prefix alternate lines with L/R
+          (when LR-PREFIX
+            (unless (or (= HEADER 0)(= FILEFORMAT 3))
+              (format fp "~a" (first LR-PREFIX))))
+          (if (= CHANNEL-LAYOUT 0)  ; IF 'Same Line' then "True"
+            (formatprint n (aref sig 0) T)
+            (formatprint n (aref sig 0)))
+          (when LR-PREFIX
+            (unless (or (= HEADER 0)(= FILEFORMAT 3))
+              (format fp "~a" (second LR-PREFIX))))
+          (formatprint n (aref sig 1)))
+        (formatprint n sig))))
+
+
+;; Print to file
+(defun printdata ()
+  (case HEADER
+    (0 (format t (normhead))(format fp (nohead)))
+    (1 (format t (normhead))(format fp (minhead)))
+    (2 (format t (normhead))(format fp (normhead)))
+    (3 (format t (normhead))(format fp (fullhead))))
+  (if (and (arrayp *track*)(= CHANNEL-LAYOUT 2))
+      ;; Stereo and left channel first
+      (progn
+        (unless (= HEADER 0)                ; Don't print 'channel' if no header
+          (format fp (_ "Left Channel.~%~%")))
+        (print-text (aref *track* 0))
+        (if (= HEADER 0)                    ; Don't print 'channel' if no header
+            (format fp "~%")
+            (format fp (_ "~%~%Right Channel.~%~%")))
+        (print-text (aref *track* 1)))
+      ;; mono or alternate
+      (print-text *track*))
+  (close fp)
+  (if (= MESSAGES 0)
+      (format nil (_ "~aData written to:~%~a") (normhead) FILENAME)
+      (progn
+        (format t (_ "~aData written to:~%~a") (normhead) FILENAME)
+        "")))
+
+
+;;; Header text
+
+(defun nohead ()
+  (if (> (length OPTEXT) 0)
+      (format nil "~a~%~a~%"
+              OPTEXT
+              (get 'info 'chan-order))
+      ""))
+
+
+(defun minhead ()
+  (format nil (_ "Sample Rate: ~a Hz.  Sample values on ~a scale.~%~a~%~a")
+  (get 'info 'srate)                        ; sample rate
+  (get 'info 'units)                        ; units
+  (get 'info 'chan-order)                   ; Channel Order
+  (if (> (length OPTEXT) 0)
+      (format nil "~a~%~%~%" OPTEXT)        ; optional text
+      (format nil "~%"))))                  ; no optional text
+
+
+(defun normhead ()
+  (if (= FILEFORMAT 4)  ; html
+      (format nil (_ "~a   ~a~%~aSample Rate: ~a Hz.~%Length processed: ~a samples ~a seconds.~a")
+              FILENAME                              ; file name
+              (get 'info 'channels)                 ; mono/stereo
+              (get 'info 'chan-order)               ; Channel Order
+              (get 'info 'srate)                    ; sample rate
+              NUMBER                                ; number of samples
+              (get 'info 'duration)                 ; duration (seconds)
+              (if (> (length OPTEXT)0)
+                  (format nil "~%~a~%~%" OPTEXT)    ; optional text
+                  (format nil "~%~%")))             ; no optional text
+      (format nil (_ "~a   ~a~%~aSample Rate: ~a Hz. Sample values on ~a scale.~%~
+                     Length processed: ~a samples ~a seconds.~a")
+              FILENAME                              ; file name
+              (get 'info 'channels)                 ; mono/stereo
+              (get 'info 'chan-order)               ; Channel Order
+              (get 'info 'srate)                    ; sample rate
+              (get 'info 'units)                    ; units
+              NUMBER                                ; number of samples
+              (get 'info 'duration)                 ; duration (seconds)
+              (if (> (length OPTEXT)0)
+                  (format nil "~%~a~%~%" OPTEXT)    ; optional text
+                  (format nil "~%~%")))))           ; no optional text
+
+
+(defun fullhead ()
+  (format nil (_ "~a~%Sample Rate: ~a Hz. Sample values on ~a scale. ~a.~%~aLength processed: ~a ~
+                  samples, ~a seconds.~%Peak amplitude: ~a (linear) ~a dB.  Unweighted RMS: ~a dB.~%~
+                  DC offset: ~a~a")
+  FILENAME                                  ; file name
+  (get 'info 'srate)                        ; sample rate
+  (get 'info 'units)                        ; units
+  (get 'info 'channels)                     ; mono/stereo
+  (get 'info 'chan-order)                   ; Channel Order
+  NUMBER                                    ; number of samples
+  (get 'info 'duration)                     ; duration (seconds)
+  (setq smax (stereomax *track*))           ; peak amplitude linear
+  (lin-to-db smax)                          ; peak amplitude dB
+  (srms *track*)                            ; rms
+  (let ((vals (dc-off *track*)))            ; DC offset
+    (if (= (length vals) 2) ; mono
+        (format nil (_ "~a linear, ~a dB.")
+                (first vals) (second vals))
+        (format nil (_ "Left: ~a lin, ~a dB | Right: ~a lin, ~a dB.")
+                (first vals) (second vals) (third vals) (fourth vals))))
+  (if (> (length OPTEXT)0)
+      (format nil "~%~a~%~%~%" OPTEXT)      ; optional text
+      (format nil "~%~%~%"))))              ; no optional text
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;        HTML Output         ;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun html-head () (strcat
+"<!DOCTYPE html>
+<html>
+<head>
+<meta name=\"generator\" content=
+\"Sample Data Export by Steve Daulton, (https://www.audionyq.com). Released under GPL v2.0+\">
+<meta name=\"description\" content=\"Sample Printer, Free Audacity plug-in\" />
+<meta name=\"keywords\" content=\"sample printer,Audacity,plug-ins,plugins,effects,audio,audio processing,music,analyze\" />
+<meta name=\"author\" content=\"Steve Daulton\" />
+<meta charset=\"UTF-8\">
+
+<style type=\"text/css\">
+body {
+  margin: 1em 5%;
+  background-color: #dda;
+  font-family:Arial,Helvetica,sans-serif;
+  }
+table,th,td {
+  background-color: #fff;
+  border:1px solid black;
+  text-align: center;
+}
+table {
+  width: auto;
+  border: 2px;
+  border-style:ridge;
+  border-collapse:collapse;
+}
+td {
+  text-align: right;
+  padding-right: 0.5em;
+}
+tr:hover td {
+  background-color:#fcd;
+}
+th {
+  padding: 0 0.5em;
+  background-color: #ddf;
+  border-bottom-width: 2px;
+  border-bottom-style:ridge;
+}
+h1 {
+  font-size: 1.6em;
+  color: #633;
+}
+h2 {
+  font-size: 1.4em;
+  color: #633;
+}
+h3 {
+  font-size: 1em;
+  color: #366;
+}
+h4 {
+  font-size: 1em;
+  color: #000;
+}
+ul {
+  position:relative;
+  top: -0.5em;
+  }
+#footer {
+  font-size: 0.8em;
+  position:relative;
+  top: 0.5em;
+  left: 2%;
+  }
+#footer span {
+  font-style:italic;
+  font-weight: bold;
+  color: #633;
+  }
+#footer a:link,a:visited {
+  color: #639;
+  text-decoration: none;
+  }
+#footer a:hover,a:active {
+  text-decoration: underline;
+  color: blue;
+  }
+</style>
+<title>" (_ "Sample Data Export") "</title>
+</head>
+"))
+
+
+;;; document headings
+(defun doc-head ()
+  (format nil
+(strcat "<body>
+<h1>" (_ "Sample Data Export") " - ~a</h1>
+~a
+<h4>~a. &nbsp;&nbsp;" (_ "~a samples.") " &nbsp;&nbsp; " (_ "~a seconds.") "<br></h4>
+<h3>" (_ "Audio data analysis:") "</h3>
+<ul>
+<li>" (_ "<b>Sample Rate:</b> &nbsp;&nbsp;~a Hz.") "</li>"
+; i18n-hint: abbreviates "decibels"
+"<li>" (_ "<b>Peak Amplitude:</b> &nbsp;&nbsp;~a (linear) &nbsp;&nbsp;~a dB.") "</li>"
+; i18n-hint: RMS abbreviates root-mean-square, a method of averaging a signal; there also "weighted" versions of it but this isn't that
+"<li>" (_ "<b>RMS</b> (unweighted): &nbsp;&nbsp;~a dB.") "</li>"
+; i18n-hint: DC derives from "direct current" in electronics, really means the zero frequency component of a signal
+"<li>" (_ "<b>DC Offset:</b> &nbsp;&nbsp;~a") "</li>
+</ul>
+") ; end concatenated format string with inserted translations
+  (string-right-trim ".html" FILENAME)
+  (format nil "<h2>~a</h2>" OPTEXT)         ; Optional heading
+  (get 'info 'channels)                     ; mono/stereo
+  NUMBER                                    ; number of samples
+  (get 'info 'duration)                     ; duration (seconds)
+  (get 'info 'srate)                        ; sample rate
+  (setq smax (stereomax *track*))                 ; peak amplitude linear
+  (lin-to-db smax)                          ; peak amplitude dB
+  (srms *track*)                            ; rms
+  (let ((vals (dc-off *track*)))            ; DC offset
+    (if (= (length vals) 2) ; mono
+        (format nil (_ "~a linear, &nbsp;&nbsp;~a dB.")
+                (first vals)(second vals))
+        (format nil (_ "Left: ~a lin, ~a dB | Right: ~a linear, &nbsp;&nbsp;~a dB.")
+                (first vals)(second vals)(third vals)(fourth vals))))))
+
+
+;;; table headings  (mono)
+(defun table-head-mono ()
+(strcat "<table title=\"" (_ "sample data") "\">
+<tr>
+<th>" (_ "Sample #") "</th>
+<th>" (_ "Seconds") "</th>
+<th>" (_ "Value (linear)") "</th>
+<th>" (_ "Value (dB)") "</th>
+</tr>"))
+
+
+;;; table headings (stereo)
+(defun table-head-stereo ()
+(strcat "<table title=\"" (_ "audio sample value analysis") "\">
+<tr>
+<th>" (_ "Sample #") "</th>
+<th>" (_ "Seconds") "</th>
+<th>" (_ "Left (linear)") "</th>
+<th>" (_ "Right (linear)") "</th>
+<th>" (_ "Left (dB)") "</th>
+<th>" (_ "Right (dB)") "</th>
+</tr>"))
+
+
+(defun html-foot ()
+  (format nil (strcat
+"</table>
+<p id=\"footer\">" (_ "Produced with <span>Sample Data Export</span> for
+<a href=\"~a\">Audacity</a> by Steve
+Daulton") " (<a href=
+\"https://audionyq.com\">audionyq.com</a>)</p>
+</body>
+</html>") "https://www.audacityteam.org/"))
+
+
+;;; html generator
+(defun make-htm (id val1 &optional val2)
+  (if val2
+      ;; stereo
+      (let ((time (/ (1- id) *sound-srate*))
+            (db1 (lin-to-db (abs val1)))
+            (db2 (lin-to-db (abs val2))))
+        (format fp
+          "<tr>~%<td>~a</td>~%<td>~a</td>~%<td>~a</td>~%<td>~a</td>~%~
+          <td>~a</td>~%<td>~a</td>~%</tr>~%"
+          id time val1 val2 db1 db2))
+      ;; mono
+      (let ((time (/ (1- id) *sound-srate*))
+            (db (lin-to-db (abs val1))))
+        (format fp
+          "<tr>~%<td>~a</td>~%<td>~a</td>~%<td>~a</td>~%<td>~a</td>~%</tr>~%"
+          id time val1 db))))
+
+
+(defun printhtml ()
+  (format fp (html-head))
+  (format fp (doc-head))
+  (if (arrayp *track*)
+      (progn
+        (format fp (table-head-stereo))
+        (do ((i 1 (1+ i)))
+            ((> i NUMBER))
+          (make-htm i
+                    (snd-fetch (aref *track* 0))
+                    (snd-fetch (aref *track* 1)))))
+      (progn
+        (format fp (table-head-mono))
+        (do ((i 1 (1+ i)))
+            ((> i NUMBER))
+          (make-htm i (snd-fetch *track*)))))
+  (format fp (html-foot))
+  (close fp)
+    (if (= MESSAGES 0)
+        (format nil (_ "~aData written to:~%~a") (normhead) FILENAME)
+        (progn
+          (format t (_ "~aData written to:~%~a") (normhead) FILENAME)
+          "")))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;       END OF HTML          ;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;; basic info for headers
+(defun put-head-info ()
+  (putprop 'info (truncate *sound-srate*) 'srate)
+  (putprop 'info (if (= UNITS 0) (_ "dB") (_ "linear")) 'units)
+  (putprop 'info (/ NUMBER *sound-srate*) 'duration)
+  (putprop 'info
+    (if (arrayp *track*)
+        (_ "2 channels (stereo)") (_ "1 channel (mono)"))
+        'channels)
+  ;; stereo sample order
+  (putprop 'info
+    (cond
+      ((and (= FILEFORMAT 3)(= CHANNEL-LAYOUT 0))     ; csv, channel in column
+        (format nil (_ "One column per channel.~%")))
+      ((and (= FILEFORMAT 3)(= CHANNEL-LAYOUT 2))     ; csv, channel in row
+        (format nil (_ "One row per channel.~%")))
+      ((or (soundp *track*)(= FILEFORMAT 4))          ; mono sound or HTML
+        "")
+      ((= CHANNEL-LAYOUT 0) (format nil (_ "Left channel then Right channel on same line.~%")))
+      ((= CHANNEL-LAYOUT 1) (format nil (_ "Left and right channels on alternate lines.~%")))
+      ((= CHANNEL-LAYOUT 2) (format nil (_ "Left channel first then right channel.~%")))
+      (T (_ "Unspecified channel order")))
+    'chan-order))
+
+
+;;; Specifying a CSV or HTML file overrides the (text only) format selection.
+(let ((file-extension (get-extension FILENAME)))
+  (cond
+    ((string-equal file-extension "csv")
+        (setf FILEFORMAT 3))
+    ((string-equal file-extension "html")
+        (setf FILEFORMAT 4))
+    ((string-equal file-extension "htm")
+        (setf FILEFORMAT 4))))
+
+
+(setq fp (open FILENAME :direction :output))
+(cond
+  (fp (put-head-info)
+      (if (= FILEFORMAT 4)
+          (printhtml)       ; html output
+          (printdata)))     ; text output
+  (t  (if (= MESSAGES 2)
+          (format t (_ "Error.~%\"~a\" cannot be written.") FILENAME)
+          (format nil (_ "Error.~%\"~a\" cannot be written.") FILENAME))))
diff --git a/Release/plug-ins/sample-data-import.ny b/Release/plug-ins/sample-data-import.ny
new file mode 100644
index 0000000000000000000000000000000000000000..beb699f327f00b5eff0f9b2ad9df468d4181aeb5
--- /dev/null
+++ b/Release/plug-ins/sample-data-import.ny
@@ -0,0 +1,107 @@
+$nyquist plug-in
+$version 4
+$type tool generate
+$name (_ "Sample Data Import")
+$debugbutton false
+$author (_ "Steve Daulton")
+$release 3.0.4-1
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control FILENAME (_ "Select file") file "" "*default*/sample-data.txt" (((_ "Text file") (txt TXT))
+                        ((_ "All files") (""))) "open,exists"
+$control BAD-DATA (_ "Invalid data handling") choice (("ThrowError" (_ "Throw Error"))
+                                                      ("ReadAsZero" (_ "Read as Zero"))) 0
+
+
+;; Check file can be opened
+(defun fileopensp (fname)
+  (cond
+    ((not (setf fstream (open fname)))
+        (throw 'err (format nil (_ "Error~%~
+                        '~a' could not be opened.~%~
+                        Check that file exists.")
+                        fname)))
+    ; File opened OK, so check for normal ASCII, then close it and return 'true'
+    (t  (do ((j 0 (1+ j))(b (read-byte fstream)(read-byte fstream)))
+            ((or (> j 100000)(not b)))
+          (when (> b 127)
+            (throw 'err (format nil (_ "Error:~%~
+              The file must contain only plain ASCII text.~%~
+              (Invalid byte '~a' at byte number: ~a)") b (1+ j) ))))
+        (close fstream)
+        t)))
+
+;; ':new' creates a new class 'streamreader'
+;; 'filestream' and 'channel' are its instance variables.
+;; (every object of class 'streamreader' has its own
+;; copy of these variables)
+(setq streamreader
+  (send class :new '(filestream channel)))
+
+;; Initialize class 'streamreader'
+(send streamreader :answer :isnew '(stream ch) '(
+    (setq filestream stream)
+    (setq channel ch)))
+
+;; Create ':next' method.
+;; Snd-fromobject calls this method to obtain the
+;; next sound sample until it receives 'nil'
+(send streamreader :answer :next '() '(
+    (case channel
+      (0  ;mono
+        (read-and-verify filestream))
+      (1  ;left channel
+        ;Note, we still need to verify data even if skipping it.
+        (let ((val (read-and-verify filestream)))
+          (read-and-verify filestream) ;skip right channel sample
+          val))
+      (t  ;right channel
+        (read-and-verify filestream) ;skip left channel sample
+        (read-and-verify filestream)))))
+
+(defun read-and-verify (stream)
+"snd-fromobject requires float values, nil to terminate"
+  (let ((val (read stream)))
+    (cond
+      ((not val) nil) ;end of file
+      ((numberp val)  (float val)) ;valid.
+      ((= BAD-DATA 0) ;invalid. Throw error and quit
+          (throw 'err (format nil (_ "Error~%~
+              Data must be numbers in plain ASCII text.~%~
+              '~a' is not a numeric value.") val)))
+      (t  0.0)))) ;invalid. Replace with zero.
+
+;; Instantiate a new sound object
+(defun make-sound-object (stream chan)
+  (send streamreader :new stream chan))
+
+(defun sound-from-file ()
+  ;; Set path. fileopenp should return 'true'
+  (if (not (fileopensp FILENAME))
+      (throw 'err (format nil (_ "Error.~%Unable to open file"))))
+  ; Note: we can't use (arrayp *track*) because
+  ; *track* is nil in generate type plug-ins.
+  (cond 
+    ((= (get '*track*  'channels) 2)
+        (let ((left-snd (get-sound FILENAME 1))
+              (right-snd (get-sound FILENAME 2)))
+          (vector left-snd right-snd)))
+    (t  ;; Mono track
+        (get-sound FILENAME 0))))
+
+(defun get-sound (fname chan)
+  (let* ((stream (open fname :direction :input))
+         (left (make-sound-object stream chan)))
+    (setf audio-out (snd-fromobject 0 *sound-srate* left))
+    (snd-play audio-out) ;force samples to be calculated now.
+    (close stream)
+    audio-out))
+
+(catch 'err (sound-from-file))
diff --git a/Release/plug-ins/spectral-delete.ny b/Release/plug-ins/spectral-delete.ny
new file mode 100644
index 0000000000000000000000000000000000000000..befe4af254a283c348098cdf297daf2214ed93cf
--- /dev/null
+++ b/Release/plug-ins/spectral-delete.ny
@@ -0,0 +1,136 @@
+$nyquist plug-in
+$version 4
+$type process spectral
+$name (_ "Spectral Delete")
+$author (_ "Steve Daulton")
+$release 3.0.4-1
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+(defun sinc (x fc)
+  ;; http://www.dspguide.com/ch16/1.htm
+  ;; Note that fc is a fraction of the sample rate.
+  (if (= x 0)
+      (* 2 pi fc)
+      (/ (sin (* 2 pi fc x)) x)))
+
+(defun blackman (i M)
+  ;; Where: 0 <= i <= M
+  (+ 0.42
+     (* -0.5 (cos (/ (* 2.0 pi i) M)))
+     (* 0.08 (cos (/ (* 4 pi i) M)))))
+
+(defun calc-kernel (size fc)
+  ;; Generate windowed sinc kernel impulse
+  (when (oddp size)
+    (error "Size of sinc filter must be even"))
+  (let ((ar (make-array (1+ size)))
+        (norm 0)  ;Normalization factor
+        val)
+    (do ((i 0 (1+ i))
+         (j size (1- j))
+         (x (- halfk) (1+ x)))
+        ((> i j))
+      (setf val (* (sinc x fc)(blackman i size)))
+      (setf norm (+ norm val))
+      (setf (aref ar i) val)
+      (setf (aref ar j) val))
+    ;; norm is sum of all samples, but don't count middle value twice.
+    (setf norm (- (* norm 2)(aref ar halfk)))
+    (dotimes (i size ar)
+      (setf (aref ar i)(/ (aref ar i) norm)))))
+
+(defun get-kernel (size fc type)
+  ;; type: 0 (low pass) or 1 (highpass)
+  ;; Returns filter kernel as a sound.
+  (let ((kernel (calc-kernel size fc)))
+    (when (= type 1)
+      ;; Convert kernel to high pass
+      ;; https://tomroelandts.com/articles/how-to-create-a-simple-high-pass-filter
+      (dotimes (i size kernel)
+        (setf (aref kernel i)(* -1 (aref kernel i))))
+      (incf (aref kernel halfk)))
+    (snd-from-array 0 *sound-srate* kernel)))
+
+(defun sinc-filter (sig start end impulse)
+  (extract-abs start end (convolve sig impulse)))
+
+(defmacro validate-low-hz (hz fmin fmax)
+  ;; Discard if out of valid range.
+  ;; Do NOT coerce into range if too high - if multiple tracks with
+  ;; different sample rates, that could cause very unexpected results.
+  `(if (or (not ,hz) (< ,hz fmin) (> ,hz fmax))
+       (setf ,hz nil)))
+
+(defmacro validate-high-hz (hz fmin fmax)
+  ;; Discard if too high. Coerce into range if too low.
+  `(if (or (not ,hz) (>= ,hz fmax))
+       (setf ,hz nil)
+       (setf ,hz (max ,hz fmin))))
+
+(defun dofilter (cf bw type)
+  ;; type: 0 (low pass) or 1 (highpass)
+  ;; Calculate kernel length (must be even)
+  ;; http://www.dspguide.com/ch16/2.htm
+  (setf klength (/ 4.0 bw))
+  (setf halfk (round (/ klength 2)))
+  (setf klength (* 2 halfk))
+  (let ((imp (get-kernel klength cf type))
+        (start (/ halfk *sound-srate*))
+        (dur (get-duration 1)))
+    (multichan-expand #'sinc-filter *track* start (+ start dur) imp)))
+
+(defun bandwidth (hz)
+  ;; Set bandwidth ratio of each filter as 1% of filter frequency.
+  (* hz 0.01))
+
+(defun bw-ratio (hz)
+  ;; Bandwidth ratio is required as a fraction of the sampling rate
+  (/ (bandwidth hz) *sound-srate*))
+
+(defun filter ()
+  (when (< *sound-srate* 100)
+    (throw 'err (_ "Error.~%Track sample rate below 100 Hz is not supported.")))
+  (let* ((f0 (get '*selection* 'low-hz))
+         (f1 (get '*selection* 'high-hz))
+         (fc (get '*selection* 'center-hz))
+         ; If frequency too low, filter length is too large.
+         (fmin (* 0.002 *sound-srate*))
+         (fmax (* 0.498 *sound-srate*))
+         (tn (truncate len))
+         (transition (truncate (* 0.01 *sound-srate*))) ; 10 ms
+         (t1 (min transition (/ tn 2)))        ; fade in length (samples)
+         (t2 (max (- tn transition) (/ tn 2))) ; length before fade out (samples)
+         (breakpoints (list t1 1.0 t2 1.0 tn))
+         (env (snd-pwl 0.0 *sound-srate* breakpoints)))
+    (validate-low-hz f0 fmin fmax)
+    (validate-high-hz f1 fmin fmax)
+    ;; Handle very narrow selections.
+    ;; This may cause f0 or f1 to 'slightly' exceed fmin fmax.
+    (when (and f0 f1 (< (- f1 f0) (* fc 0.02)))
+      (setf f0 (* fc 0.99))
+      (setf f1 (* fc 1.01)))
+    (when f0
+      (setf lp-width (bw-ratio f0))
+      (setf f0 (/ f0 *sound-srate*)))
+    (when f1
+      (setf hp-width (bw-ratio f1))
+      (setf f1 (/ f1 *sound-srate*)))
+    ;(format t "Low: ~a    High: ~a" (if f0 (* f0 *sound-srate*) nil) (if f1 (* f1 *sound-srate*) nil))
+    (if (not (or f0 f1))
+        ""  ;may occur if multiple tracks with different sample rates
+        (sim
+          (mult env
+              (if f0 (dofilter f0 lp-width 0) 0))
+          (mult env
+              (if f1 (dofilter f1 hp-width 1) 0))
+          (mult (diff 1.0 env) *track*)))))
+
+
+(catch 'err (filter))
diff --git a/Release/plug-ins/tremolo.ny b/Release/plug-ins/tremolo.ny
new file mode 100644
index 0000000000000000000000000000000000000000..d0ee70e2522da8d8b974e24e0e374c8326f8f80c
--- /dev/null
+++ b/Release/plug-ins/tremolo.ny
@@ -0,0 +1,57 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview linear
+$name (_ "Tremolo")
+$debugbutton disabled
+$author (_ "Steve Daulton")
+$release 2.4.0
+$copyright (_ "GNU General Public License v2.0 or later")
+
+;; tremolo.ny by Steve Daulton (www.easyspacepro.com) July 2012.
+;; Based on Tremolo by Dominic Mazzoni and David R. Sky."
+
+;; License: GPL v2+
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control WAVE (_ "Waveform type") choice ((_ "Sine")
+                                          (_ "Triangle" "waveform")
+                                          (_ "Sawtooth")
+                                          ("InverseSawtooth" (_ "Inverse Sawtooth"))
+                                          (_ "Square")) 0
+
+$control PHASE (_ "Starting phase (degrees)") int "" 0 -180 180
+$control WET (_ "Wet level (percent)") int "" 40 1 100
+$control LFO (_ "Frequency (Hz)") float-text "" 4 0.001 1000
+
+
+; set tremolo waveform 
+(setf waveform
+  (abs-env
+    (case WAVE
+      (0 *sine-table*)
+      (1 *tri-table*)
+      ; sawtooth
+      (2 (maketable (pwlv -1 0.995 1 1 -1)))
+      ; inverse sawtooth
+      (3 (maketable (pwlv -1 0.005 1 1 -1)))
+      ; square
+      (4 (maketable (pwlv -1 0.005 1 0.5 1 0.505 -1 1 -1))))))
+
+
+;;; Generate modulation wave
+(defun mod-wave (level)
+  ; *sine-table* is 90 degrees rotated compared to other tables.
+  (if (= WAVE 0)
+      (setf phase-shift (- PHASE 90))
+      (setf phase-shift PHASE))
+  (sum (- 1 level) 
+       (mult level 
+             (osc (hz-to-step LFO) 1.0 waveform phase-shift))))
+
+
+(mult *track* (mod-wave (/ WET 200.0)))
diff --git a/Release/plug-ins/vocoder.ny b/Release/plug-ins/vocoder.ny
new file mode 100644
index 0000000000000000000000000000000000000000..6dac03651c05b923499b39b0f512b05efde9d5dd
--- /dev/null
+++ b/Release/plug-ins/vocoder.ny
@@ -0,0 +1,117 @@
+$nyquist plug-in
+$version 4
+$type process
+$preview enabled
+$name (_ "Vocoder")
+$debugbutton false
+$author (_ "Edgar-RFT and Steve Daulton")
+$release 3.1.2-1
+$copyright (_ "GNU General Public License v2.0")
+
+
+;; If selected track is mono, the vocoder uses sine waves as the modulation
+;; carrier, mixed with noise and radar needles according to slider settings.
+;; If selected track is stereo, the right channel is used as the carrier wave.
+
+;; License: GPL v2
+;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+;;
+;; For information about writing and modifying Nyquist plug-ins:
+;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
+
+
+$control DST (_ "Distance: (1 to 120, default = 20)") float "" 20 1 120
+$control MST (_ "Output choice") choice (("BothChannels" (_ "Both Channels"))
+                                         ("RightOnly" (_ "Right Only"))) 0
+$control BANDS (_ "Number of vocoder bands") int "" 40 10 240
+$control TRACK-VL (_ "Amplitude of carrier wave (percent)") float "" 100 0 100
+$control NOISE-VL (_ "Amplitude of white noise (percent)") float "" 0 0 100
+$control RADAR-VL (_ "Amplitude of Radar Needles (percent)") float "" 0 0 100
+$control RADAR-F (_ "Frequency of Radar Needles (Hz)") float "" 30 1 100
+
+
+;; Return log to base 2 of x.
+(defun log2 (x)
+  (/ (log (float x)) (log 2.0)))
+
+
+;; Global constants.
+;; Scale slider values for better control.
+(setf TRACK-VOL (sqrt (/ TRACK-VL 100.0)))
+(setf NOISE-VOL (expt (/ NOISE-VL 100.0) 2.0))
+(setf RADAR-VOL (sqrt (/ RADAR-VL 100.0)))
+
+;; number of octaves from 20 Hz.
+;; Maximum number of octaves is: log2(high-hz / low-hz)
+;; "2.205" is for compatibility with older versions of vocoder effect.
+(setf OCTAVES (log2 (/ (/ *sound-srate* 2.205) 20)))
+
+;; interval - number of semitones per vocoder band
+(setf INTERVAL (/ (* OCTAVES 12.0) BANDS))
+
+
+(defun make-radar-table (hz)
+  (let ((one (/ *sound-srate*)) ;one sample period
+        radar-table)
+    (setf radar-table
+        (stretch-abs 1 (sim (snd-const 1 one *sound-srate* one)
+                            (s-rest (/ 1.0 hz)))))
+    (list radar-table (hz-to-step hz) T)))
+
+
+;;; The Mixer
+(defun mix-noise (sig)
+  (sum (cond ((= TRACK-VOL 0) 0)
+             ((< TRACK-VOL 1) (mult TRACK-VOL sig))
+             (t sig))
+       (if (> RADAR-VL 0)
+           (let ((r-table (make-radar-table RADAR-F)))
+             (mult RADAR-VOL
+                   (osc (hz-to-step RADAR-F) 1 r-table)))
+           0)
+       (if (> NOISE-VL 0)
+           (mult NOISE-VOL (noise 1))
+           0)))
+
+
+;; Raise 'hz' by 'INTERVAL' semitones.
+(defmacro next-hz (hz INTERVAL)
+  `(let* ((prev-step (hz-to-step ,hz))
+          (next-step (+ prev-step ,INTERVAL)))
+    (step-to-hz next-step)))
+
+
+(defmacro sumto (x y)
+  `(setf ,x (sum ,x ,y)))
+
+
+;;; Stereo Vocoder - returns mono sound.
+(defun vocoder (sig is-mono-track)
+  (let (mod-envelope
+        band
+        (result 0))
+    (do ((i 0 (1+ i))
+         (q (/ (sqrt 2.0) (/ OCTAVES BANDS)))  ; quick approximation of q
+         (f (next-hz 20 (/ INTERVAL 2.0))
+            (next-hz f INTERVAL)))
+        ((= i BANDS) result)
+      (when is-mono-track
+        (sumto (aref sig 1) (mult 0.5 (/ TRACK-VOL BANDS) (hzosc f))))
+      (setf band (bandpass2 sig f q)) ; intermediate results (2 channels)
+      (setf mod-envelope (lowpass8 (s-abs (aref band 0)) (/ f DST)))
+      (sumto result (bandpass2 (mult mod-envelope (aref band 1)) f q)))))
+
+
+;;; The Program
+(if (= (+ TRACK-VOL NOISE-VOL RADAR-VOL) 0)
+    (format nil (_ "Error.~%No modulation carrier."))
+    (progn
+      (if (arrayp *track*)
+          (setf sig (vector (aref *track* 0) (mix-noise (aref *track* 1))))
+          (setf sig (vector *track* (mix-noise (s-rest 0)))))
+      (setf sig (vocoder sig (soundp *track*)))
+      ;; Normalize *track* to 0 db peak based on first 10 million samples.
+      (setf sig (scale (/ (peak sig 10000000)) sig))
+      (if (or MST (soundp *track*))
+          sig
+          (vector (aref *track* 0) sig))))