mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-04 16:14:00 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			218 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
;nyquist plug-in
 | 
						|
;version 4
 | 
						|
;type generate
 | 
						|
;name "Sample Data Import..."
 | 
						|
;manpage "Sample_Data_Import"
 | 
						|
;action "Reading and rendering samples..."
 | 
						|
;author "Steve Daulton"
 | 
						|
;copyright "Released under terms of the GNU General Public License version 2"
 | 
						|
 | 
						|
;; sample-data-import.ny by Steve Daulton November 2016.
 | 
						|
;; Released under terms of the GNU General Public License version 2:
 | 
						|
;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 | 
						|
 | 
						|
 | 
						|
;control filename "File name" string "" "sample-data.txt"
 | 
						|
;control path "File location (path to file)" string "" "Home directory"
 | 
						|
;control bad-data "Invalid data handling" choice "Throw error,Read as zero" 0
 | 
						|
 | 
						|
 | 
						|
;; Documentation.
 | 
						|
;;
 | 
						|
;; Introduction:
 | 
						|
;; This plug-in reads numeric values from the specified
 | 
						|
;; file and creates a PCM sample for each numeric value
 | 
						|
;; read. The values are read as 32-bit float numbers,
 | 
						|
;; where a range of +/- 1.0 represents 0 dB (full scale).
 | 
						|
;; The file MUST be plain ASCII text, and values should
 | 
						|
;; be separated by spaces, tabs or line-breaks.
 | 
						|
;; The file name must have a '.txt' file extension.
 | 
						|
;;
 | 
						|
;; *** ATTENTION ***
 | 
						|
;; The file to be imported must contain
 | 
						|
;; plain ASCII text only.
 | 
						|
;; Files other than plain ASCII text are NOT SUPPORTED.
 | 
						|
;; ***************
 | 
						|
;; 
 | 
						|
;; Depending on the computer file system, the file name
 | 
						|
;; may be case sensitive. The named file must be located
 | 
						|
;; in the specified directory (the "File location").
 | 
						|
;; The directory may be one of:
 | 
						|
;; 1) "Home Directory" (without quotes. This is the default).
 | 
						|
;;    The "Home Directory" is normally:
 | 
						|
;;    C:\Users\<username>  (Windows)
 | 
						|
;;    /Users/<username>    (Mac OS X)
 | 
						|
;;    /home/<username>     (Linux)
 | 
						|
;; 2) The fully qualified path, for example:
 | 
						|
;;    C:\Users\(User_Name)\Documents
 | 
						|
;; 3) Linux & Mac only:
 | 
						|
;;    The fully qualified path using "~/" (tilde slash)
 | 
						|
;;    as an abbreviation for the Home folder.
 | 
						|
;; If the "File location" is not defined (empty), the
 | 
						|
;; plug-in will look in the "home" folder for the named
 | 
						|
;; file.
 | 
						|
;; If the file cannot be found, the plug-in will abort
 | 
						|
;; and an error will be shown.
 | 
						|
;; 
 | 
						|
;; Number of channels:
 | 
						|
;; If a stereo track is selected, then the data is assumed
 | 
						|
;; to be stereo, otherwise the data will be treated as
 | 
						|
;; mono.
 | 
						|
;; Stereo data must be interleaved (alternate left/right
 | 
						|
;; sample values).
 | 
						|
;; 
 | 
						|
;; Data format:
 | 
						|
;; Sample data must be plain ASCII text only and should
 | 
						|
;; be numeric values only.
 | 
						|
;; 
 | 
						|
;; Values must be separated by one of:
 | 
						|
;; 1) One or more spaces.
 | 
						|
;; 2) One or more tabs.
 | 
						|
;; 3) One or more line breaks.
 | 
						|
;; 4) Any combination of the above.
 | 
						|
;; 
 | 
						|
;; Data comments:
 | 
						|
;; Comments may be included in the file by preceding
 | 
						|
;; the comment with a semicolon. All text between a
 | 
						|
;; semicolon and the end of the line is ignored.
 | 
						|
;;
 | 
						|
;; Invalid data handling:
 | 
						|
;; There are two options for handling invalid data:
 | 
						|
;; 1) Throw error (default):
 | 
						|
;;    Any text that cannot be read as a numeric value
 | 
						|
;;    will abort the effect and display an error message.
 | 
						|
;;    The error message will attempt to display the
 | 
						|
;;    invalid text.
 | 
						|
;; 2) Read as zero:
 | 
						|
;;    Any text that cannot be read as a numeric value
 | 
						|
;;    will produce a zero (silent) sample value.
 | 
						|
;; 
 | 
						|
;; Further information:
 | 
						|
;; http://manual.audacityteam.org/man/sample_data_import.html
 | 
						|
 | 
						|
 | 
						|
;; home directory
 | 
						|
(defun home ()
 | 
						|
  (if (windowsp)
 | 
						|
      (get-env "UserProfile") ;Windows
 | 
						|
      (get-env "HOME")));Mac / Linux
 | 
						|
 | 
						|
;; Check if OS is Windows
 | 
						|
(defun windowsp ()
 | 
						|
  (char= #\\ *file-separator*))
 | 
						|
 | 
						|
;; Check file can be opened
 | 
						|
;; As Nyquist plug-ins do not (yet) have a file browser,
 | 
						|
;; we need to be quite rigorous with error checking here.
 | 
						|
(defun fileopensp (path fname)
 | 
						|
  (let ((path (string-trim " " path)))
 | 
						|
    (if (string-equal fname "")
 | 
						|
        (throw 'err "Error\nNo file name."))
 | 
						|
    (if (string-not-equal fname ".txt" :start1 (- (length fname) 4))
 | 
						|
        (throw 'err "Error\nThe file must be a plain ASCII text file\nwith '.txt' file extension."))
 | 
						|
    ;; Handle special 'path' formats:
 | 
						|
    (cond
 | 
						|
      ; "~" without "/" is not recommended (or documented)
 | 
						|
      ; but more user friendly to allow it.
 | 
						|
      ((string= path "~")
 | 
						|
          (if (windowsp)
 | 
						|
              "Error\n'~/' is not valid on Windows"
 | 
						|
              (setq path (home))))
 | 
						|
      ;; replace "~/" on Linux/Mac
 | 
						|
      ((and (>= (length path) 2) (string= path "~/" :end1 2))
 | 
						|
          (if (windowsp)
 | 
						|
              "Error\n'~/' is not valid on Windows"
 | 
						|
              (setq path (strcat (home)(subseq path 1)))))
 | 
						|
      ((string-equal path "Home directory")
 | 
						|
          (setf path (home)))
 | 
						|
      ;; If empty, use 'Home'
 | 
						|
      ((string-equal path "")
 | 
						|
          (setf path (home))))
 | 
						|
    ;; Now check that the file can be opened:
 | 
						|
    (cond
 | 
						|
      ((not (setdir path))
 | 
						|
          (throw 'err (format nil "Error~%~
 | 
						|
                          Directory '~a' could not be opened." path)))
 | 
						|
      ((not (setf fstream (open fname)))
 | 
						|
          (throw 'err (format nil "Error~%~
 | 
						|
                          '~a~a~a' could not be opened.~%~
 | 
						|
                          Check that file exists."
 | 
						|
                          path *file-separator* 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 objet of class 'streamreader' has it's 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 path filename))
 | 
						|
      (throw 'err "Error.\nUnable 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))
 |