;********************************************************************* ; 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, J. Bresson (in-package :om) (print "loading OM-SoX player for OM version >= 6.7") ; player specific controls ; I need a 'record' button - including a choice of channels I guess ; Might have a 'spread' numbox later ; Where are things like selection (region) and loop etc. set? (defmethod make-player-specific-controls ((self (eql :soxplayer)) control-view) (let ((snd (object (editor control-view)))) ; ------------------------------------------ (list (om-make-dialog-item 'om-static-text (om-make-point 405 8) (om-make-point 40 20) "Gain" ;level :font *om-default-font1*) (om-make-dialog-item 'edit-numBox (om-make-point 360 8) (om-make-point 45 18) (format nil " ~4f" (om-round (sox-vol-convert (vol snd)) 3)) :min-val -76 :max-val 12 ;:incr 0.01 :font *om-default-font1* :bg-color *om-white-color* :value (sox-vol-convert (vol snd)) :afterfun #'(lambda (item) (setf (vol snd) (sox-convert-vol (value item))) (om-set-dialog-item-text item (format () " ~4f" (om-round (value item) 3))) ; not sure this is needed )) (om-make-dialog-item 'om-static-text (om-make-point 480 8) (om-make-point 70 20) "Panning" :font *om-default-font1*) ;PAN (om-make-dialog-item 'edit-numBox (om-make-point 440 8) (om-make-point 40 20) (format nil " ~4f" (sox-pan-convert (pan snd) (tracknum snd))) :font *om-default-font1* ;:incr 0.1 :min-val 0.0 :max-val 64.0 ; the minvalue is '0' which means "no panning" :fg-color (if (>= (tracknum snd) 1.0) *om-black-color* *om-gray-color*) :bg-color *om-white-color* :value (sox-pan-convert (pan snd) (tracknum snd)) :afterfun #'(lambda (item) (om-set-dialog-item-text item (format () " ~4f" (value item))) (setf (pan snd) (second (sox-convert-pan (value item)))) (setf (tracknum snd) (first (sox-convert-pan (value item)))) ) ) #| (om-make-dialog-item 'numbox (om-make-point 70 pos) (om-make-point 36 18) (format nil " ~2f" (nth (micid self) (micexps (mic-array self)))) :value (nth (micid self) (micexps (mic-array self))) :font *om-default-font1* :incr 0.01 :min-val 0.0 :max-val 2.0 :afterfun #'(lambda (box) (setf (nth (micid self) (micexps (mic-array self))) (value box)) (setf (bpcs (mic-array self)) (gen-bpcs (mic-array self))) (report-modifications (om-view-container self))) ) |# (om-make-dialog-item 'om-check-box (om-make-point 100 8) (om-make-point 100 20) "Sonagram" :font *om-default-font1* :di-action #'(lambda (item) (let* ((editor (editor control-view)) (thesound (object editor))) (when (om-checked-p item) (unless (pict-spectre thesound) (setf (pict-spectre thesound) (sox-spectrogram thesound nil nil :mode "negativ-monochrome" :legend nil)))) (setf (pict-spectre? thesound) (om-checked-p item)) (om-invalidate-view (panel editor)))) :checked-p (pict-spectre? (object (editor control-view))) ) )) ) ; needs a menu for the 'mode' of the sonagram in the player panel settings perhaps ;(defmethod player-special-action ((self (eql :soxplayer))) (launch-multiplayer-app)) ;(defmethod player-type ((player (eql :soxplayer))) :UDP) ;(defun init-soxplayer-app () ;; enable soxplayer-engine for sound class: ; (pushnew :soxplayer *enabled-players*) ; (add-player-for-object sound :soxplayer)) ;;(init-soxplayer-app) ;(om-add-init-func 'init-soxplayer-app) (enable-player :soxplayer) (defmethod player-name ((self (eql :soxplayer))) "OM-SoX player") (defmethod player-desc ((self (eql :soxplayer))) "external SoX player, requires OM-SoX library.") ;(defmethod player-special-action ((self (eql :multiplayer))) (launch-multiplayer-app)) ;(defmethod player-type ((player (eql :multiplayer))) :UDP) (add-player-for-object 'sound :soxplayer) ;================== ; APP ;================== (defvar *sox-play-list* nil) (defvar *sox-play-temp* "sox-play.aiff") (defvar *sox-temp-recorded-file* nil) (defun init-soxplayer-app () (unless (probe-file *sox-path*) (sox-not-found))) (om-add-init-func 'init-soxplayer-app) #| (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!") )) |# ;================ ; PROTOCOL ;================ ;(defvar *multiplayer-file-to-play* (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)) (print "resetting player") (when (probe-file (outfile *sox-play-temp*)) (delete-file (outfile *sox-play-temp*))) (setf *sox-play-list* nil) t) ; (defmethod InitPlayingSeq ((player (eql 'multiplayer)) dur &key (port nil)) t) ; (defmethod FinalizePlayingSeq ((player (eql 'multiplayer)) dur &key (port nil)) t) ; sequence is prepare-to-play and then player-start (defmethod prepare-to-play ((engine (eql :soxplayer)) (player omplayer) object at interval) (call-next-method) ) ; (declare (ignore approx)) ; (push (list object at interval) *sox-play-list*)) (defmethod player-start ((engine (eql :soxplayer)) &optional play-list) (call-next-method)) (defmethod player-play-object ((engine (eql :soxplayer)) (object sound) &key interval) ; (print "soxplayer play") (sox-play object)) (defmethod player-stop ((engine (eql :soxplayer)) &optional play-list) ;(print "soxplayer stop") (declare (ignore view)) (sox-kill nil)) #| ; currently no scheme for player-pause and player-continue (defmethod player-pause ((engine (eql :soxplayer)) &optional play-list) (sox-kill nil)) (defmethod player-continue ((engine (eql :soxplayer)) &optional play-list) nil) |# #| (defmethod Reset-Player ((self (eql 'soxplayer)) &optional view) (declare (ignore view)) (print "resetting player") (when (probe-file (outfile *sox-play-temp*)) (delete-file (outfile *sox-play-temp*))) (setf *sox-play-list* nil) t) |# #| (defmethod player-record ((engine (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*))) ;;; must return the recorded object (defmethod player-record-stop ((engine (eql :soxplayer))) (sox-kill nil) (probe-file *sox-temp-recorded-file*)) (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*)) |# #| (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*)) |# ; this needs to be redefined #| ;;;;; ;;; SPECIFIES SOMETHING TO BE PLAYED ATHER A GIVEN DELAY () PAST THE CALL TO PLAYER-START ;;; THE DEFAULT BEHAVIOUR IS TO SCHEDULE 'player-play' AT DELAY (defmethod prepare-to-play ((engine t) (player omplayer) object at interval) (schedule-task player #'(lambda () ;(print (list object engine at interval)) (player-play-object engine object :interval interval)) at)) ;;; PLAY (NOW) (defmethod player-play-object ((engine t) object &key interval) ;(print (format nil "~A : play ~A - ~A" engine object interval)) t) ;;; START (PLAY WHAT IS SCHEDULED) (defmethod player-start ((engine t) &optional play-list) ;(print (format nil "~A : start" engine)) t) ;;; PAUSE (all) (defmethod player-pause ((engine t) &optional play-list) ;(print (format nil "~A : pause" engine)) t) ;;; CONTINUE (all) (defmethod player-continue ((engine t) &optional play-list) ;(print (format nil "~A : continue" engine)) t) ;;; STOP (all) (defmethod player-stop ((engine t) &optional play-list) ;(print (format nil "~A : stop" engine)) t) ;;; SET LOOP (called before play) (defmethod player-set-loop ((engine t) &optional start end) ;(print (format nil "~A : set loop" engine)) t) (defmethod player-loop ((engine t) &optional play-list) ;(print (format nil "~A : loop" engine)) t) (defmethod player-record ((engine t)) ;(print (format nil "~A : record" engine)) t) ;;; must return the recorded object (defmethod player-record-stop ((engine t)) ;(print (format nil "~A : record stop" engine)) nil) |# ;######## METHODS FOR SOX-PLAY ########### (defvar *sox-running-processes* nil "keeps a list of process-ids of currently running sox-processes") ; Q What is it that makes sox-play back only a subsection of a sound (selection) ... it's not 'clipping' ; This way we could implement pause & continue methods. (defmethod! sox-play ((self string) &key clipping samplerate bitdepth gain pan) :icon 07 :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)) :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. ; basically, when connecting 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 07 :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) |# #+(or macosx linux) (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) (sox-play-print (string+ "auto remove " (number-to-string localpid))) (delete localpid *sox-running-processes*))