;********************************************************************* ; OM-SoX, (c) 2011-2014 Marlon Schumacher (CIRMMT/McGill University) * ; http://sourceforge.net/projects/omsox/ * ; * ; Multichannel Audio Manipulation and Functional Batch Processing. * ; DSP based on SoX - (c) C.Bagwell and Contributors * ; http://sox.sourceforge.net/ * ;********************************************************************* ; ;This program is free software; you can redistribute it and/or ;modify it under the terms of the GNU General Public License ;as published by the Free Software Foundation; either version 2 ;of the License, or (at your option) any later version. ; ;See file LICENSE for further informations on licensing terms. ; ;This program is distributed in the hope that it will be useful, ;but WITHOUT ANY WARRANTY; without even the implied warranty of ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;GNU General Public License for more details. ; ;You should have received a copy of the GNU General Public License ;along with this program; if not, write to the Free Software ;Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,10 USA. ; ;Authors: M. Schumacher (in-package :om) (print "loading OM-SoX player for OM version < 6.7") ;######## SOX-PLAY ########### ; Logic: ; the keywords gain, pan, in-channels are ignored when sox-input classes are used. ; For sound objects the keywords override the ones given by the sound obj's internal parameters (defmethod! sox-play ((self string) &key clipping samplerate bitdepth gain pan) :icon 08 :initvals '(nil nil nil nil nil nil) :doc "Play back a sound using the OM-SoX library" :numouts 1 (sox-play-print self) (if (probe-file *sox-path*) (let* ((inpath self)) (setf thestring (format nil "~s ~a ~s -q" (namestring *sox-path*) *sox-options* (namestring inpath))) (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*)) (when clipping (setf thestring (string+ thestring (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping)))))) ; pan means that a sound is going to be panned between the two adjacent channels determined by the scalar. ; E.g. a value of 4.5 means between ch4 and ch5 (producing a 5-channel file) --> like the mix-console (if pan (progn (sox-print "sox-play: Panning a sound") (sox-play (sox-pan->sox-remix self gain pan) ;if both pan and gain it should go into the function directly, otherwise only gain :clipping clipping :samplerate samplerate :bitdepth bitdepth) ) (progn (when gain (setf thestring (string+ thestring (format nil " gain ~d " gain)))) ;(when *normalize* (setf thestring (string+ thestring " norm " (makestring *normalize-level*)))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) ;(print localpid) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (om-run-process "remove-terminated-pid" sox-remove-pid (if clipping (- (cadr clipping) (car clipping)) (sox-sound-duration self)) localpid) ))) (sox-not-found)) self ) (defmethod! sox-play ((self pathname) &key clipping samplerate bitdepth gain pan) (sox-play (namestring self) :clipping clipping :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan) ) (defmethod! sox-play ((self list) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (mapcar (lambda (thesound) (sox-play thesound :clipping clipping :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan)) self)) ; (sox-play thesound :clipping clipping :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan)) self)) (defmethod! sox-play ((self sound) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (sound-path self) (let ((gain (or gain (sox-vol-convert (vol self))))) (if (equal (tracknum self) 0) (sox-play (pathname (sound-path self)) :clipping clipping ;(or clipping (markers self)) :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan) (sox-play (make-instance 'sox-pan :sound self :gains gain :panning (or pan (+ (tracknum self) (* 0.01 (pan self)))) :clipping clipping :samplerate samplerate :bitdepth bitdepth) ) ) ) (om-beep-msg (format nil "OM-SoX: Cannot play sound with path \"~s\"" (sound-path self))) ) ) ; ###### sox-mix ##### (defmethod! sox-play ((self sox-mix) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (probe-file *sox-path*) (let* ((pathlist (sound self)) (gainlist (db->lin (gains self)))) (setf thestring (format nil "~s ~a -m " (namestring *sox-path*) *sox-options*)) (loop for path in pathlist for gain in gainlist do (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path))))) ; could also be a pipe (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*)) (when clipping (setf thestring (string+ thestring (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping)))))) ; (when *normalize* (setf thestring (string+ thestring " norm " (makestring *normalize-level*)))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) ;(print localpid) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (om-run-process "remove-terminated-pid" sox-remove-pid (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self)))) localpid) ) (sox-not-found)) self ) ; ###### sox-merge ##### (defmethod! sox-play ((self sox-merge) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (probe-file *sox-path*) (let* ((pathlist (sound self)) (gainlist (db->lin (gains self)))) (setf thestring (format nil "~s ~a -M " (namestring *sox-path*) *sox-options*)) (loop for path in pathlist for gain in gainlist do (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path))))) ; could also be a pipe (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*)) (when clipping (setf thestring (string+ thestring (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping)))))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) ;(print localpid) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (om-run-process "remove-terminated-pid" sox-remove-pid (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self)))) localpid) ) (sox-not-found)) self ) ; ###### sox-concatenate ##### (defmethod! sox-play ((self sox-concatenate) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (probe-file *sox-path*) (let* ((pathlist (sound self)) (gainlist (db->lin (gains self)))) (setf thestring (format nil "~s ~a " (namestring *sox-path*) *sox-options*)) (loop for path in pathlist for gain in gainlist do (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path))))) (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*)) (when clipping (setf thestring (string+ thestring (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping)))))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) (print localpid) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (om-run-process "remove-terminated-pid" sox-remove-pid (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self)))) localpid) ) (sox-not-found)) self ) ; ###### sox-splice ##### (defmethod! sox-play ((self sox-splice) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (probe-file *sox-path*) (let* ((pathlist (sound self)) (gainlist (db->lin (gains self)))) (setf thestring (format nil "~s ~a " (namestring *sox-path*) *sox-options*)) (loop for path in pathlist for gain in gainlist do (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path))))) (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*)) (when clipping (setf thestring (string+ thestring (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping)))))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) (print localpid) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (om-run-process "remove-terminated-pid" sox-remove-pid (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self)))) localpid) ) (sox-not-found)) self ) ; ###### sox-pan ##### (defmethod! sox-play ((self sox-pan) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (sox-play (sox-pan->sox-remix (sound self) (car (list! (gains self))) (car (list! (panning self))) (numchannels self)) ;(car (list! (gains self))) ;the (car (list! (panning self))) should be taken care of at initialize-instance... but maybe the editors need lists... :clipping clipping :samplerate samplerate :bitdepth bitdepth )) ; ###### sox-remix ##### (defmethod! sox-play ((self sox-remix) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (probe-file *sox-path*) (let* ((inpath (sound self))) (setf thestring (format nil "~s ~a ~s -q" (namestring *sox-path*) *sox-options* (namestring inpath))) (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*)) (setf thestring (string+ thestring (format nil " remix ~a" (sox-remixconc-no-norm (gain-matrix self) (channel-matrix self)) ))) (when clipping (setf thestring (string+ thestring (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping)))))) ; (when *normalize* (setf thestring (string+ thestring " norm " (makestring *normalize-level*)))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) ;(print localpid) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (om-run-process "remove-terminated-pid" sox-remove-pid (if clipping (- (cadr clipping) (car clipping)) (sox-sound-duration (sound self))) localpid) ) (sox-not-found)) self ) ; ###### sox-mix-console ##### (defmethod! sox-play ((self sox-mix-console) &key clipping samplerate bitdepth gain pan) (sox-play-print self) (if (probe-file *sox-path*) (sox-play (make-instance 'sox-mix :sound (sox-process (sox-pan->sox-remix (sound self) (gains self) (panning self)) ;(numchannels self)) "" :output "pipe") ) :clipping clipping :samplerate samplerate :bitdepth bitdepth) (sox-not-found)) self ) ;##### helper functions ################## -> probably better in utilities. ; it should be possible to connect a sox-record to a sox-input class it should allow for doing this. ;; recording function (defmethod! sox-rec (filepath &key duration filetype samplerate bitdepth gain channel) ;better call it fileformat? or audioformat? :icon 08 :initvals '(nil nil nil nil) :menuins '((2 (("aif" "aif") ("wav" "wav") ("flac" "flac") ("ogg" "ogg")))) :doc "records incoming audio through sox into a file" :numouts 1 (if (probe-file *sox-path*) (let* ((outpath (or filepath (om-choose-new-file-dialog :prompt "Choose a name and location for the recorded audio file" :directory (outfile nil))))) (when outpath (setf outpath (om-make-pathname :directory (pathname-directory outpath) :name (pathname-name outpath) :type (or filetype (pathname-type outpath))));) (setf thestring (format nil "~s -d" (namestring *sox-path*))) (setf thestring (sox-samplebits thestring bitdepth samplerate outpath)) (when duration (setf thestring (string+ thestring (format nil " trim 0 ~d" duration)))) (when gain (setf thestring (string+ thestring (format nil " gain ~d " gain)))) (when channel (setf thestring (string+ thestring (format nil " remix ~a" channel)))) (setf localpid (om-cmd-line thestring nil nil)) ;(print thestring) (setf *sox-running-processes* (x-append *sox-running-processes* localpid)) (when duration (om-run-process "remove-terminated-pid" sox-remove-pid duration localpid)) ; NB the soundobject throws an error if the soundfile is not there yet outpath )) (sox-not-found)) ) ; ######### command-line functions ########## ; sox-kill terminates running sox play/rec processes (defmethod! sox-kill (process-ids) :icon 01 :initvals '(nil) :numouts 1 #+win32(sys::call-system "taskkill /F /IM \"sox.exe\"") #| some experiments to retrieve the pid from Windows DOS console (sys::call-system "tasklist /v | find \"sox.exe\"") (sys::call-system "tasklist /v | find \"sox.exe\" | taskkill /F /IM") (om-cmd-line "tasklist /F /IM \"sox.exe\"" nil nil) |# #+macosx(let ((pid-list (list! (or process-ids *sox-running-processes*)))) (loop for pid in pid-list do (om-cmd-line (format nil "kill ~d" pid) *sys-console*)) (setf *sox-running-processes* nil) ;(print (format nil "killed pids ~s" pid-list)) (when (om-find-process "remove-terminated-pid") (om-kill-process (om-find-process "remove-terminated-pid"))) ) ) (defun sox-remove-pid (wait-time localpid) (sleep wait-time) (print (string+ "auto remove " (number-to-string localpid))) (delete localpid *sox-running-processes*)) ;;;=========================================== ;;; INTERFACE OM : USE SOX AS A SOUND PLAYER ;;;=========================================== (unless (find :soxplayer *audio-players*) (pushr :soxplayer *audio-players*)) (defmethod audio-player-name ((self (eql :soxplayer))) "Sox") ; should be spelled "SoX" (defmethod audio-player-desc ((self (eql :soxplayer))) "Requires OM-SoX library") (add-assoc-player *general-player* 'soxplayer) (defvar *sox-play-list* nil) (defvar *sox-play-temp* "sox-play.aiff") (defmethod InitPlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) (setf *sox-play-list* nil) (unless (probe-file *sox-path*) (om-beep-msg "SoX-executable not found!") )) ;This is BEFORE playplayer (defmethod FinalizePlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) (when *sox-play-list* ;*sox-play-list* = sound, offset, interval (setf *sox-play-list* (if (= 1 (length *sox-play-list*)) (let ((sound (first (car *sox-play-list*))) (offset (ms->sec (second (car *sox-play-list*)))) (interval (ms->sec (third (car *sox-play-list*))))) (sox-print "FinalizePlayingSeq: single case") (if (zerop offset) ;(sox-print (list sound interval)) ; = sox, interval, pan is accounted for in PlayPlayer (via the sound object) (sox-print (list (sox-process sound "" :output "pipe") interval)) (progn (sox-print "this is with offset") (sox-print (list (sox-process sound (string+ (sox-pad offset)) :output "pipe" ) interval)) ;what's this doing? converting into a path yes? ) ) ) (progn ;this looks like the Maquette case (sox-print "FinalizePlayingSeq: list case") (list (sox-process (make-instance 'sox-mix :sound (mapcar #'(lambda (pl-item) (let* ((sound (car pl-item))) (sox-process (if (equal (tracknum sound) 0) sound (make-instance 'sox-pan :sound sound)) ;sound (if (zerop (cadr pl-item)) "" (sox-pad (ms->sec (cadr pl-item)))) ;offset :samplerate *audio-sr* :output "pipe")) ) *sox-play-list*) ) "" :output *sox-play-temp*) (ms->sec (third (car *sox-play-list*))))) ;interval ))) (sox-print (string+ "Finalize END " (makestring *sox-play-list*)))) (defmethod* PrepareToPlay ((player (eql 'soxplayer)) (self sound) at &key approx port interval voice) (declare (ignore approx)) (push (list self at interval) *sox-play-list*)) ; ========== = = = = = (defmethod Play-player ((self (eql 'soxplayer))) (when (sox-print (string+ "Play Player BEGIN " (makestring *sox-play-list*))) (cond ((subtypep (type-of (car *sox-play-list*)) 'sound) (let* ((sound (car (sox-print *sox-play-list*))) (clipping (cadr *sox-play-list*))) (sox-print "Play-player: Playing a sound object") (if (equal (tracknum (car *sox-play-list*)) 0) (sox-play sound :clipping clipping) (sox-play (make-instance 'sox-pan :sound sound) :clipping clipping)) ) ) (t (progn (sox-print "Play-player: Playing not a sound object") (sox-play (car (sox-print *sox-play-list*)) :clipping (cadr *sox-play-list*)) )))) ) (defmethod Stop-Player ((self (eql 'soxplayer)) &optional view) (declare (ignore view)) (sox-kill nil)) (defmethod Pause-Player ((self (eql 'soxplayer))) (om-beep)) ;there's currently no good method for Pause-Player (defmethod Continue-Player ((self (eql 'soxplayer))) nil) (defmethod Reset-Player ((self (eql 'soxplayer)) &optional view) (declare (ignore view)) (sox-print "resetting player") (when (and *delete-inter-file* (probe-file (outfile *sox-play-temp*))) (delete-file (outfile *sox-play-temp*))) (setf *sox-play-list* nil) t) (defmethod audio-record-start ((self (eql 'soxplayer))) (setf *sox-temp-recorded-file* (om-choose-new-file-dialog :prompt "Choose a name and location for the recorded audio file." :directory (outfile nil))) (when *sox-temp-recorded-file* (sox-rec *sox-temp-recorded-file*))) (defmethod audio-record-stop ((self (eql 'soxplayer))) (sox-kill nil) (probe-file *sox-temp-recorded-file*)) #| ;;;================================= ;;; PLAYBACK FOR SOX-INPUT CLASSES ;;;================================= (defmethod play-obj? ((self sox-input)) t) (defmethod* PrepareToPlay ((player (eql 'soxplayer)) (self sound) at &key approx port interval voice) (declare (ignore approx)) (push (list self at interval) *sox-play-list*)) ;the new player method for the sox-input classes could just have a new method for 'sox-input' (defmethod* PrepareToPlay ((player t) (self sox-input) at &key approx port interval voice) (declare (ignore approx)) ;(print "bingo") (push (list self at interval) *sox-play-list*)) ;(sox-play self)) (defmethod InitPlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) t) (defmethod FinalizePlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) t) (defmethod Play-player ((self (eql 'sox-input))) (print "Playing") (sox-play *sox-play-list*)) |# ; SPREAD FUNCTION ; Macro for multichannel files. ; Where a 'spread' parameter determines how 'spread' the channels are - similar to vbap. #| (om-round (om+ 3.75 ;panning (arithm-ser (om* 150 ; spread -0.005) (om* 150 ;spread 0.005) (om/ (om- (om+ 3.75 (om* 150 ; spread -0.005)) (om+ 3.75 (om* 150 ; spread 0.005))) 2))) 2) |# ;;; collect-ressources function from OM 6.7 (defun collect-resources (path) (loop for item in (om-directory path) append (cond ((directoryp item) (collect-resources item)) ((om-persistant-p item) (let ((fileres (get-resources item))) (loop for restype in fileres append (loop for path in (cdr restype) collect (let ((*relative-path-reference* item) (corrected-path (if (and (stringp path) (pathnamep (read-from-string path))) (read-from-string path) path))) (list (car restype) (restore-path corrected-path)) ))))) (t nil) )))