audacia/plug-ins/sample-data-import.ny

109 lines
3.9 KiB
Common Lisp

$nyquist plug-in
$version 4
$type tool generate
$name (_ "Sample Data Import")
$manpage "Sample_Data_Import"
$action (_ "Reading and rendering samples...")
$author (_ "Steve Daulton")
$release 2.3.0
$copyright (_ "Released under terms of the GNU General Public License version 2")
$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
;; Released under terms of the GNU General Public License version 2:
;; 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
;; 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 'chanel' are its instance variables.
;; (every object of class 'streamreader' has its own
;; copy of these variables)
(setq streamreader
(send class :new '(filestream chanel)))
;; 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 (filename)
;; 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 filename))