Work-in-progress repo for ambisonics extensions for OM-SoX
Alexander Nguyen
18 hours ago ff1d9f1b689ebdea536530dfca6fbed5f0068f34
commit | author | age
92c40d 1 ;*********************************************************************
AN 2 ; OM-SoX, (c) 2011-2014 Marlon Schumacher (CIRMMT/McGill University) *
3 ;             http://sourceforge.net/projects/omsox/                 *
4 ;                                                                    *
5 ;  Multichannel Audio Manipulation and Functional Batch Processing.  *
6 ;        DSP based on SoX - (c) C.Bagwell and Contributors           *
7 ;                  http://sox.sourceforge.net/                       *
8 ;*********************************************************************
9 ;
10 ;This program is free software; you can redistribute it and/or
11 ;modify it under the terms of the GNU General Public License
12 ;as published by the Free Software Foundation; either version 2
13 ;of the License, or (at your option) any later version.
14 ;
15 ;See file LICENSE for further informations on licensing terms.
16 ;
17 ;This program is distributed in the hope that it will be useful,
18 ;but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;GNU General Public License for more details.
21 ;
22 ;You should have received a copy of the GNU General Public License
23 ;along with this program; if not, write to the Free Software
24 ;Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,10 USA.
25 ;
26 ;Authors: M. Schumacher
27
28 (in-package :om)
29
30 (print "loading OM-SoX player for OM version < 6.7")   
31
32 ;######## SOX-PLAY ###########
33
34 ; Logic:
35 ; the keywords gain, pan, in-channels are ignored when sox-input classes are used. 
36 ; For sound objects the keywords override the ones given by the sound obj's internal parameters
37
38
39 (defmethod! sox-play ((self string) &key clipping samplerate bitdepth gain pan)
40             :icon 08
41             :initvals '(nil nil nil nil nil nil)
42             :doc "Play back a sound using the OM-SoX library"
43             :numouts 1
44             (sox-play-print self)
45             (if (probe-file *sox-path*)
46                 (let* ((inpath self))             
47                   (setf thestring (format nil "~s ~a ~s -q" (namestring *sox-path*) *sox-options* (namestring inpath)))
48                   (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*))
49                   (when clipping (setf thestring (string+ thestring 
50                                                           (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping))))))                    
51                   
52                   ; pan means that a sound is going to be panned between the two adjacent channels determined by the scalar. 
53                   ; E.g. a value of 4.5 means between ch4 and ch5 (producing a 5-channel file) --> like the mix-console
54
55                   (if pan
56                       (progn  
57                         (sox-print "sox-play: Panning a sound")
58                         (sox-play (sox-pan->sox-remix self gain pan) ;if both pan and gain it should go into the function directly, otherwise only gain
59                                   :clipping clipping :samplerate samplerate :bitdepth bitdepth)
60                         )
61
62                     (progn
63                       (when gain (setf thestring (string+ thestring (format nil " gain ~d " gain))))
64                       ;(when *normalize* (setf thestring (string+ thestring " norm " (makestring *normalize-level*))))
65                       
66                       (setf localpid (om-cmd-line thestring nil nil))     
67                   ;(print thestring)
68                   ;(print localpid)
69                       (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
70                       (om-run-process "remove-terminated-pid" sox-remove-pid 
71                                       (if clipping (- (cadr clipping) (car clipping)) (sox-sound-duration self))
72                                       localpid)
73                       )))
74                     (sox-not-found))
75                   self
76             )
77
78 (defmethod! sox-play ((self pathname) &key clipping samplerate bitdepth gain pan)
79             (sox-play (namestring self) :clipping clipping :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan)
80             )
81
82 (defmethod! sox-play ((self list) &key clipping samplerate bitdepth gain pan)
83             (sox-play-print self)
84             (mapcar (lambda (thesound) 
85                       (sox-play thesound :clipping clipping :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan)) self))
86                      ; (sox-play thesound :clipping clipping :samplerate samplerate :bitdepth bitdepth :gain gain :pan pan)) self))
87
88 (defmethod! sox-play ((self sound) &key clipping samplerate bitdepth gain pan)
89             (sox-play-print self)
90             (if (sound-path self)
91                 (let ((gain (or gain (sox-vol-convert (vol self)))))
92                   (if (equal (tracknum self) 0)
93                       (sox-play (pathname (sound-path self)) 
94                                 :clipping clipping ;(or clipping (markers self)) 
95                                 :samplerate samplerate 
96                                 :bitdepth bitdepth 
97                                 :gain gain
98                                 :pan pan)
99                     (sox-play (make-instance 'sox-pan
100                                              :sound self
101                                              :gains gain
102                                              :panning (or pan (+ (tracknum self) (* 0.01 (pan self))))
103                                              :clipping clipping
104                                              :samplerate samplerate
105                                              :bitdepth bitdepth)
106                               )
107                     )
108                   )
109               (om-beep-msg (format nil "OM-SoX: Cannot play sound with path \"~s\"" (sound-path self)))
110               )
111             )
112
113 ; ###### sox-mix #####
114 (defmethod! sox-play ((self sox-mix) &key clipping samplerate bitdepth gain pan)          
115             (sox-play-print self)
116             (if (probe-file *sox-path*)
117                 (let* ((pathlist (sound self))
118                        (gainlist (db->lin (gains self))))             
119                   (setf thestring (format nil "~s ~a -m " (namestring *sox-path*) *sox-options*))
120                   (loop for path in pathlist 
121                         for gain in gainlist do
122                         (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path))))) ; could also be a pipe
123                   (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*))
124                   (when clipping (setf thestring (string+ thestring 
125                                                           (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping))))))
126                  ; (when *normalize* (setf thestring (string+ thestring " norm " (makestring *normalize-level*))))
127                   (setf localpid (om-cmd-line thestring nil nil))     
128                   ;(print thestring)
129                   ;(print localpid)
130                   (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
131                   (om-run-process "remove-terminated-pid" sox-remove-pid 
132                                   (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self))))
133                                   localpid)
134                   )
135               (sox-not-found))
136             self
137             )
138
139 ; ###### sox-merge #####
140 (defmethod! sox-play ((self sox-merge) &key clipping samplerate bitdepth gain pan)          
141             (sox-play-print self)
142             (if (probe-file *sox-path*)
143                 (let* ((pathlist (sound self))
144                        (gainlist (db->lin (gains self))))             
145                   (setf thestring (format nil "~s ~a -M " (namestring *sox-path*) *sox-options*))
146                   (loop for path in pathlist 
147                         for gain in gainlist do
148                         (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path))))) ; could also be a pipe
149                   (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*))
150                   (when clipping (setf thestring (string+ thestring 
151                                                           (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping))))))                    
152                   (setf localpid (om-cmd-line thestring nil nil))  
153                   ;(print thestring)
154                   ;(print localpid)
155                   (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
156                   (om-run-process "remove-terminated-pid" sox-remove-pid 
157                                   (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self))))
158                                   localpid)
159                   )
160               (sox-not-found))
161             self
162             )
163
164 ; ###### sox-concatenate #####
165 (defmethod! sox-play ((self sox-concatenate) &key clipping samplerate bitdepth gain pan)          
166             (sox-play-print self)
167             (if (probe-file *sox-path*)
168                 (let* ((pathlist (sound self))
169                        (gainlist (db->lin (gains self))))             
170                   (setf thestring (format nil "~s ~a " (namestring *sox-path*) *sox-options*))
171                   (loop for path in pathlist 
172                         for gain in gainlist do
173                         (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path)))))
174                   (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*))
175                   (when clipping (setf thestring (string+ thestring 
176                                                           (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping))))))                    
177                   (setf localpid (om-cmd-line thestring nil nil))     
178                   ;(print thestring)
179                   (print localpid)
180                   (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
181                   (om-run-process "remove-terminated-pid" sox-remove-pid 
182                                   (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self))))
183                                   localpid)
184                   )
185               (sox-not-found))
186             self
187             )
188
189 ; ###### sox-splice #####
190 (defmethod! sox-play ((self sox-splice) &key clipping samplerate bitdepth gain pan)          
191             (sox-play-print self)
192             (if (probe-file *sox-path*)
193                 (let* ((pathlist (sound self))
194                        (gainlist (db->lin (gains self))))             
195                   (setf thestring (format nil "~s ~a " (namestring *sox-path*) *sox-options*))
196                   (loop for path in pathlist 
197                         for gain in gainlist do
198                         (setf thestring (string+ thestring (format nil " -v~d ~s" gain (namestring path)))))
199                   (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*))
200                   (when clipping (setf thestring (string+ thestring 
201                                                           (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping))))))                    
202                   (setf localpid (om-cmd-line thestring nil nil))     
203                   ;(print thestring)
204                   (print localpid)
205                   (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
206                   (om-run-process "remove-terminated-pid" sox-remove-pid 
207                                   (if clipping (- (cadr clipping) (car clipping)) (list-max (sox-sound-duration (sound self))))
208                                   localpid)
209                   )
210               (sox-not-found))
211             self
212             )
213
214 ; ###### sox-pan #####
215 (defmethod! sox-play ((self sox-pan) &key clipping samplerate bitdepth gain pan)          
216             (sox-play-print self)   
217             (sox-play (sox-pan->sox-remix (sound self) (car (list! (gains self))) (car (list! (panning self))) (numchannels self)) ;(car (list! (gains self))) 
218             ;the (car (list! (panning self))) should be taken care of at initialize-instance... but maybe the editors need lists...
219                       :clipping clipping
220                       :samplerate samplerate
221                       :bitdepth bitdepth                   
222                       ))
223
224 ; ###### sox-remix #####
225 (defmethod! sox-play ((self sox-remix) &key clipping samplerate bitdepth gain pan)              
226             (sox-play-print self)
227             (if (probe-file *sox-path*)
228                 (let* ((inpath (sound self)))             
229                   (setf thestring (format nil "~s ~a ~s -q" (namestring *sox-path*) *sox-options* (namestring inpath)))
230                   (setf thestring (sox-samplebits thestring bitdepth samplerate *sox-audio-device*))
231                   (setf thestring (string+ thestring (format nil " remix ~a" 
232                                                    (sox-remixconc-no-norm (gain-matrix self) (channel-matrix self))
233                                                    )))
234
235                   (when clipping (setf thestring (string+ thestring 
236                                                           (format nil " trim ~d ~d " (first clipping) (- (second clipping) (first clipping))))))                    
237                  ; (when *normalize* (setf thestring (string+ thestring " norm " (makestring *normalize-level*))))
238
239                   (setf localpid (om-cmd-line thestring nil nil))     
240                   ;(print thestring)
241                   ;(print localpid)
242                   (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
243                   (om-run-process "remove-terminated-pid" sox-remove-pid 
244                                    (if clipping (- (cadr clipping) (car clipping)) (sox-sound-duration (sound self))) 
245                                   localpid)
246                   )
247               (sox-not-found))
248             self
249             )
250
251 ; ###### sox-mix-console #####
252 (defmethod! sox-play ((self sox-mix-console) &key clipping samplerate bitdepth gain pan)          
253             (sox-play-print self)
254             (if (probe-file *sox-path*)                 
255                   (sox-play (make-instance 'sox-mix
256                                            :sound (sox-process (sox-pan->sox-remix (sound self) (gains self) (panning self)) ;(numchannels self))
257                                                                ""
258                                                                :output "pipe")
259                                            )
260                             :clipping clipping :samplerate samplerate :bitdepth bitdepth)
261               (sox-not-found))
262             self
263             )
264
265
266 ;##### helper functions ################## -> probably better in utilities.
267
268 ; it should be possible to connect a sox-record to a sox-input class it should allow for doing this.
269
270 ;; recording function
271 (defmethod! sox-rec (filepath &key duration filetype samplerate bitdepth gain channel) ;better call it fileformat? or audioformat?
272             :icon 08
273             :initvals '(nil nil nil nil)
274             :menuins '((2 (("aif" "aif") ("wav" "wav") ("flac" "flac") ("ogg" "ogg")))) 
275             :doc "records incoming audio through sox into a file"
276             :numouts 1
277             (if (probe-file *sox-path*)
278                 (let* ((outpath (or filepath (om-choose-new-file-dialog :prompt "Choose a name and location for the recorded audio file"
279                                                                         :directory (outfile nil)))))
280                   (when outpath
281                     (setf outpath (om-make-pathname :directory (pathname-directory outpath) 
282                                                   :name (pathname-name outpath) 
283                                                   :type (or filetype (pathname-type outpath))));)
284                   (setf thestring (format nil "~s -d" (namestring *sox-path*)))                    
285                   (setf thestring (sox-samplebits thestring bitdepth samplerate outpath)) 
286
287                   (when duration (setf thestring (string+ thestring (format nil " trim 0 ~d" duration))))                  
288                   (when gain (setf thestring (string+ thestring (format nil " gain ~d " gain))))
289                   (when channel (setf thestring (string+ thestring (format nil " remix ~a" channel))))
290
291                   (setf localpid (om-cmd-line thestring nil nil))
292                   ;(print thestring)
293                   (setf *sox-running-processes* (x-append *sox-running-processes* localpid))
294                   (when duration (om-run-process "remove-terminated-pid" sox-remove-pid duration localpid))                    
295                   ; NB the soundobject throws an error if the soundfile is not there yet
296                   outpath
297                   ))
298               (sox-not-found))
299             )
300
301 ; ######### command-line functions ##########
302 ; sox-kill terminates running sox play/rec processes
303 (defmethod! sox-kill (process-ids)       
304             :icon 01
305             :initvals '(nil)
306             :numouts 1
307             #+win32(sys::call-system "taskkill /F /IM \"sox.exe\"")
308             #|      some experiments to retrieve the pid from Windows DOS console
309                     (sys::call-system "tasklist /v | find \"sox.exe\"")
310                     (sys::call-system "tasklist /v | find \"sox.exe\" | taskkill /F /IM")
311                     (om-cmd-line "tasklist /F /IM \"sox.exe\"" nil nil)
312             |#
313             #+macosx(let ((pid-list (list! (or process-ids *sox-running-processes*))))
314                       (loop for pid in pid-list do (om-cmd-line (format nil "kill ~d" pid) *sys-console*))
315                       (setf *sox-running-processes* nil)
316                       ;(print (format nil "killed pids ~s" pid-list))
317                       (when (om-find-process "remove-terminated-pid") (om-kill-process (om-find-process "remove-terminated-pid")))
318                       )
319             )
320
321 (defun sox-remove-pid (wait-time localpid)
322   (sleep wait-time)
323   (print (string+ "auto remove " (number-to-string localpid)))
324   (delete localpid *sox-running-processes*))
325
326
327 ;;;===========================================
328 ;;; INTERFACE OM : USE SOX AS A SOUND PLAYER
329 ;;;===========================================
330
331 (unless (find :soxplayer *audio-players*) (pushr :soxplayer *audio-players*))
332 (defmethod audio-player-name ((self (eql :soxplayer))) "Sox") ; should be spelled "SoX"
333 (defmethod audio-player-desc ((self (eql :soxplayer))) "Requires OM-SoX library")
334
335 (add-assoc-player *general-player* 'soxplayer)
336
337 (defvar *sox-play-list* nil)
338 (defvar *sox-play-temp* "sox-play.aiff")
339
340 (defmethod InitPlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) 
341   (setf *sox-play-list* nil)
342   (unless (probe-file *sox-path*) 
343     (om-beep-msg "SoX-executable not found!")
344     ))
345
346
347 ;This is BEFORE playplayer
348 (defmethod FinalizePlayingSeq ((player (eql 'soxplayer)) dur &key (port nil))
349   (when *sox-play-list* ;*sox-play-list* = sound, offset, interval
350     (setf *sox-play-list*
351           (if (= 1 (length *sox-play-list*))
352               (let ((sound (first (car *sox-play-list*)))
353                     (offset (ms->sec (second (car *sox-play-list*))))
354                     (interval (ms->sec (third (car *sox-play-list*)))))
355                 (sox-print "FinalizePlayingSeq: single case")
356                 (if (zerop offset)
357                     ;(sox-print (list sound interval)) ; = sox, interval, pan is accounted for in PlayPlayer (via the sound object)
358                     (sox-print (list (sox-process sound "" :output "pipe") interval))
359                     (progn 
360                     (sox-print "this is with offset")
361                     (sox-print (list (sox-process sound (string+ (sox-pad offset)) :output "pipe" ) interval)) ;what's this doing? converting into a path yes?
362                     )
363                   )
364                 )
365             (progn ;this looks like the Maquette case
366                (sox-print "FinalizePlayingSeq: list case")
367                  (list 
368                   (sox-process 
369                    (make-instance 'sox-mix
370                                   :sound (mapcar #'(lambda (pl-item)
371                                                      (let* ((sound (car pl-item)))
372                                                        (sox-process 
373                                                         (if (equal (tracknum sound) 0) sound (make-instance 'sox-pan :sound sound)) ;sound
374                                                         (if (zerop (cadr pl-item)) "" (sox-pad (ms->sec (cadr pl-item)))) ;offset
375                                                         :samplerate *audio-sr* 
376                                                         :output "pipe"))
377                                                      )
378                                                  *sox-play-list*)
379                                   )
380                    ""
381                    :output *sox-play-temp*)
382                   (ms->sec (third (car *sox-play-list*))))) ;interval
383             )))
384   (sox-print (string+ "Finalize END " (makestring *sox-play-list*))))
385                                                                 
386
387 (defmethod* PrepareToPlay ((player (eql 'soxplayer)) (self sound) at &key approx port interval voice)
388    (declare (ignore approx))
389    (push (list self at interval) *sox-play-list*))
390
391 ; ========== = = = = =
392
393 (defmethod Play-player ((self (eql 'soxplayer)))
394   (when (sox-print (string+ "Play Player BEGIN " (makestring *sox-play-list*)))
395     (cond ((subtypep (type-of (car *sox-play-list*)) 'sound)
396              (let* ((sound (car (sox-print *sox-play-list*)))
397                     (clipping (cadr *sox-play-list*)))
398                (sox-print "Play-player: Playing a sound object")
399                (if (equal (tracknum (car *sox-play-list*)) 0)
400                    (sox-play sound :clipping clipping)
401                  (sox-play (make-instance 'sox-pan :sound sound) :clipping clipping))
402                )
403              )
404           (t 
405            (progn 
406              (sox-print "Play-player: Playing not a sound object")
407              (sox-play (car (sox-print *sox-play-list*)) :clipping (cadr *sox-play-list*))
408              ))))
409   )
410
411 (defmethod Stop-Player ((self (eql 'soxplayer)) &optional view)
412    (declare (ignore view))
413    (sox-kill nil))
414
415 (defmethod Pause-Player ((self (eql 'soxplayer))) (om-beep)) ;there's currently no good method for Pause-Player
416 (defmethod Continue-Player ((self (eql 'soxplayer))) nil)
417
418 (defmethod Reset-Player ((self (eql 'soxplayer)) &optional view)
419   (declare (ignore view))
420   (sox-print "resetting player")
421   (when (and *delete-inter-file* (probe-file (outfile *sox-play-temp*))) (delete-file (outfile *sox-play-temp*)))
422   (setf *sox-play-list* nil)
423   t)
424
425 (defmethod audio-record-start ((self (eql 'soxplayer)))
426   (setf *sox-temp-recorded-file* 
427         (om-choose-new-file-dialog :prompt "Choose a name and location for the recorded audio file."
428                                    :directory (outfile nil)))
429   (when *sox-temp-recorded-file*
430     (sox-rec *sox-temp-recorded-file*)))
431
432 (defmethod audio-record-stop ((self (eql 'soxplayer)))
433   (sox-kill nil)
434   (probe-file *sox-temp-recorded-file*))
435
436 #|
437 ;;;=================================
438 ;;; PLAYBACK FOR SOX-INPUT CLASSES
439 ;;;=================================
440
441 (defmethod play-obj? ((self sox-input)) t)
442
443 (defmethod* PrepareToPlay ((player (eql 'soxplayer)) (self sound) at &key approx port interval voice)
444    (declare (ignore approx))
445    (push (list self at interval) *sox-play-list*))
446
447 ;the new player method for the sox-input classes could just have a new method for 'sox-input'
448 (defmethod* PrepareToPlay ((player t) (self sox-input) at &key approx port interval voice)
449    (declare (ignore approx))
450    ;(print "bingo")
451    (push (list self at interval) *sox-play-list*))
452    ;(sox-play self))
453
454 (defmethod InitPlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) t)
455     
456 (defmethod FinalizePlayingSeq ((player (eql 'soxplayer)) dur &key (port nil)) t)
457
458 (defmethod Play-player ((self (eql 'sox-input)))
459   (print "Playing")
460   (sox-play *sox-play-list*))
461 |#
462
463 ; SPREAD FUNCTION
464 ; Macro for multichannel files. 
465 ; Where a 'spread' parameter determines how 'spread' the channels are - similar to vbap.
466 #|
467 (om-round (om+ 3.75 ;panning
468                
469                (arithm-ser 
470                 (om* 150 ; spread   
471                      -0.005) 
472                 (om* 150  ;spread
473                      0.005) 
474
475                 (om/ (om- (om+ 3.75 (om* 150
476                            ; spread 
477                            -0.005))
478                           (om+ 3.75 (om* 150
479                            ; spread 
480                            0.005))) 
481                      2))) 
482           2)
483 |#      
484
485 ;;; collect-ressources function from OM 6.7
486 (defun collect-resources (path)
487  (loop for item in (om-directory path) append
488        (cond ((directoryp item) (collect-resources item))
489              ((om-persistant-p item) 
490               (let ((fileres (get-resources item)))
491                 (loop for restype in fileres append
492                       (loop for path in (cdr restype) collect
493                             (let ((*relative-path-reference* item)
494                                   (corrected-path (if (and (stringp path) (pathnamep (read-from-string path)))
495                                                       (read-from-string path)
496                                                     path)))
497                               (list (car restype) (restore-path corrected-path))
498                               )))))
499              (t nil)
500              )))