; ********************************************************************
|
; 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)
|
|
|
;;; variables =======================
|
|
(defparameter *gnuplot-path* "/usr/local/bin/gnuplot") ; should probably be in the preferences file. For now no specific option for this "/usr/local/bin/gnuplot"
|
|
(defvar *gnuplot-terminal-menu* '(
|
("png" "png")
|
("aqua" "aqua")
|
;("ascii" "dumb")
|
("jpeg" "jpeg")
|
("gif" "gif")
|
("pdf" "pdf")
|
("eps" "eps")
|
("eps-landscape" "eps-landscape")
|
))
|
|
(defparameter *gnuplot-terminal-commands* '(
|
("aqua" "set term aqua")
|
("pdf" "set term pdf")
|
("eps" "set term postscript eps color enhanced rounded font \"Times\" 12")
|
("png" "set term pngcairo")
|
("jpeg" "set term jpeg")
|
("gif" "set term gif")
|
))
|
|
|
(defun gnuplot-not-found ()
|
;(om-message-abort (format nil "SoX exectuable not found. Path set in preferences?")
|
(om-beep-msg (format nil "gnuplot not found. Is it in /usr/local/bin ?"))
|
)
|
|
#|
|
(if (probe-file *gnuplot-path*)
|
()
|
(gnuplot-not-found))
|
|
(environment-variable "PATH")
|
(setf (environment-variable "PATH")
|
(concatenate 'string (environment-variable "PATH")
|
":/usr/local/bin"))
|
|#
|
|
;;; sox-plot ===============================
|
|
; Perhaps I should be able to connect other things to my sox-plot function.
|
; For example, to draw a waveform or to draw the dftlist
|
; So that when it receives a list of values it can plot them (in 2D or 3D)
|
; this function needs to be able to distinguish list of numbers from biquad effects, from fir effects
|
; so 3 methods?
|
|
;perhaps introduce a 3rd dimension (z)
|
(defmethod! sox-plot ((commands string) &key output (terminal "png") (samplerate "48k") (numsamples 250) title x-label y-label x-range y-range (x-scale "logarithmic") (y-scale "deciBel"))
|
:icon 07
|
:initvals '(nil nil "aqua" nil nil nil "" "" "" nil nil nil nil) ;(nil "" "aqua" nil nil nil "" "" "" nil nil nil nil)
|
:menuins (list
|
(list 2 *gnuplot-terminal-menu*)
|
(list 3 *sox-samplerates*)
|
(list 10 '(("linear" "linear") ("logarithmic" "logarithmic")))
|
(list 11 '(("linear" "linear") ("deciBel" "deciBel"))))
|
|
(if (probe-file *sox-path*)
|
(if (probe-file *gnuplot-path*)
|
|
(let ((outfile (create-path () output "plt")))
|
(sox-print "terminal:" terminal)
|
(sox-print "outfile:" outfile)
|
(setf str
|
(format nil "~s -r~a --plot gnuplot -n -n "
|
(namestring *sox-path*)
|
samplerate
|
))
|
|
(setf str (string+ str commands " >"))
|
(setf str (sox-concat outfile str))
|
(om-cmd-line str *sys-console*)
|
(let ((mypltfile (probe-file outfile)))
|
|
;modif of textfile
|
(let* ((loaded-textfile (objfromobjs outfile (make-instance 'textfile :eval-mode "text")))
|
(xscale (if (equal x-scale "logarithmic") "set logscale x" "#set logscale x"))
|
(samples (format nil "set samples ~D" numsamples))
|
(plot-title (format nil "set title '~a'" title))
|
(x-range (or x-range (list 10 "Fs/2")))
|
(y-range (or y-range (if (equal y-scale "deciBel") (list -40 12) (list 0 2))))
|
(xlabel (if x-label ; maybe this if-statement is not needed, as X is always Frequency and Y always Amplitude
|
(if (equal x-label "") "#set xlabel <not set>" (format nil "set xlabel '~a'" x-label))
|
(if (equal x-scale "logarithmic") "set xlabel 'Frequency (Hz) (log.)'" "set xlabel 'Frequency (Hz)'")))
|
(ylabel (if y-label
|
(if (equal y-label "") "#set ylabel <not set>" (format nil "set ylabel '~a'" y-label))
|
(if (equal y-scale "deciBel") "set ylabel 'Amplitude (dBFS)'" "set ylabel 'Amplitude (linear)'")))
|
(plot-text (if (equal y-scale "deciBel")
|
(format nil "plot [f=~D:~a] [~D:~D] 20*log10(H(f))" (first x-range) (second x-range) (first y-range) (second y-range))
|
(format nil "plot [f=~D:~a] [~D:~D] (H(f))" (first x-range) (second x-range) (first y-range) (second y-range))))
|
(plot-fir-text (if numsamples (format nil "plot '-' with lines") (format nil "plot '-'")))
|
(manipulated-text (if (find (read-from-string commands) '(fir hilbert sinc))
|
(if title
|
(insert-in-list (subs-posn (butlast (butlast (cdr (exp-list loaded-textfile))))
|
'(0 1 2 5) (list plot-title xlabel ylabel plot-fir-text)) xscale 3)
|
(insert-in-list (subs-posn (butlast (butlast (cdr (exp-list loaded-textfile))))
|
'(1 2 5) (list xlabel ylabel plot-fir-text)) xscale 3))
|
(if title
|
(subs-posn (butlast (butlast (cdr (exp-list loaded-textfile)))) '(0 1 2 7 8 11) (list plot-title xlabel ylabel xscale samples plot-text))
|
(subs-posn (butlast (butlast (cdr (exp-list loaded-textfile)))) '(1 2 7 8 11) (list xlabel ylabel xscale samples plot-text)))))
|
|
(mypicfile (create-path () output terminal))
|
(new-text (x-append *sox-gnuplot-header*
|
(second (assoc terminal *gnuplot-terminal-commands* :test #'equalp))
|
(format nil "set output ~s" (namestring mypicfile))
|
manipulated-text))
|
(new-textfile (make-instance 'textfile :eval-mode "text" )))
|
(setf (exp-list new-textfile) new-text)
|
(save-data new-textfile mypltfile)
|
(probe-file mypltfile)
|
(om-cmd-line (format nil "~s ~s " *gnuplot-path* (namestring mypltfile)) *sys-console*)
|
(let ((myoutfile (probe-file mypicfile))
|
(mypict (make-instance 'picture)))
|
(setf (background mypict) myoutfile)
|
|
;optional removal of temporary files
|
(add-tmp-file mypltfile)
|
(add-tmp-file myoutfile)
|
(when *delete-inter-file* (clean-tmp-files))
|
mypict)
|
);)
|
)
|
)
|
(gnuplot-not-found))
|
(sox-not-found)
|
)
|
)
|
|
|
(defmethod! sox-plot ((data list) &key output (terminal "png") (samplerate "48k") (numsamples 250) title x-label y-label x-range y-range x-scale y-scale)
|
|
(if (probe-file *sox-path*)
|
(if (probe-file *gnuplot-path*)
|
|
(let* ((mypltfile (print (create-path () output "plt")))
|
(default-text *default-gnuplot-text*)
|
(dimensions (length (car data)))
|
(datalist (if (eq dimensions 3)
|
(loop for item in data collect
|
(format nil "~d ~d ~d" (first item) (if (equal y-scale "deciBel") (lin->dB (abs (second item))) (second item)) (third item) #\newline)
|
)
|
(loop for item in data collect
|
(format nil "~d ~d" (first item) (if (equal y-scale "deciBel") (lin->dB (abs (second item))) (second item)) #\newline)
|
)))
|
;isn't that (datalist) like this? (reduce #'(lambda (s1 s2) (format nil "~d ~d " s1 s2)) coefficients) -> and like 'concat-strings'?
|
(xscale (if (equal x-scale "logarithmic") "set logscale x" "#set logscale x"))
|
(samples (format nil "set samples ~D" numsamples))
|
(plot-title (format nil "set title '~a'" title))
|
|
;ranges have no effect at the moment. Don't know how to set it for gnuplot. Would need a band-filter on the data list.
|
|
(xlabel (if x-label (format nil "set xlabel '~a'" x-label) "#set xlabel <not set>"))
|
(ylabel (if y-label (format nil "set ylabel '~a'" y-label) "#set ylabel <not set>"))
|
(plot-list-text (if (eq dimensions 3)
|
(if numsamples (format nil "splot '-' with lines") (format nil "splot '-'"))
|
(if numsamples (format nil "plot '-' with lines") (format nil "plot '-'"))))
|
(manipulated-text (if title (subs-posn default-text '(0 1 2 3 6) (list plot-title xlabel ylabel xscale plot-list-text))
|
(subs-posn default-text '(1 2 3 6) (list xlabel ylabel xscale plot-list-text))))
|
(mypicfile (create-path () output terminal))
|
(new-text (x-append *sox-gnuplot-header*
|
(second (assoc terminal *gnuplot-terminal-commands* :test #'equalp))
|
(format nil "set output ~s" (namestring mypicfile))
|
manipulated-text
|
datalist))
|
(new-textfile (make-instance 'textfile :eval-mode "text" )))
|
(setf (exp-list new-textfile) new-text)
|
(save-data new-textfile mypltfile)
|
(probe-file mypltfile)
|
(om-cmd-line (format nil "~s ~s " *gnuplot-path* (namestring mypltfile)) *sys-console*)
|
(let ((myoutfile (probe-file mypicfile))
|
(mypict (make-instance 'picture)))
|
(setf (background mypict) myoutfile)
|
|
;optional removal of temporary files
|
(add-tmp-file mypltfile)
|
(add-tmp-file myoutfile)
|
(when *delete-inter-file* (clean-tmp-files))
|
mypict)
|
)
|
(gnuplot-not-found))
|
(sox-not-found)
|
)
|
)
|
|
#|
|
(defmethod sox-coeffs-to-string ((coeffs list))
|
(if (eq (length (car coeffs)) 2)
|
(reduce #'(lambda (s1 s2) (format nil "~d ~d " s1 s2 #\newline)) coeffs)
|
(reduce #'(lambda (s1 s2 s3) (format nil "~d ~d ~d " s1 s2 s3 #\newline)) coeffs)
|
)) ;doesn't seem to work -- WHY?
|
|
(setf test-list '(("Aqua" "Bingo") ("Delta" "nothing")))
|
(assoc "Aqua" test-list :test #'equalp)
|
(second (find "Aqua" test-list :test (lambda (item arg)
|
(string-equal item (car arg)))))
|
|#
|
|
(defmethod! sox-plot ((data bpf) &key output (terminal "png") (samplerate "48k") (numsamples 250) biquad-cascade title x-label y-label x-range y-range x-scale y-scale)
|
(sox-plot (mat-trans (list (x-points data) (y-points data))) :output output :terminal terminal :samplerate samplerate :numsamples numsamples :biquad-cascade biquad-cascade
|
:title title :x-label x-label :y-label y-label :x-range x-range :y-range y-range :x-scale x-scale :y-scale y-scale))
|
|
(defmethod! sox-plot ((data 3dc) &key output (terminal "png") (samplerate "48k") (numsamples 250) biquad-cascade title x-label y-label x-range y-range x-scale y-scale)
|
(sox-plot (mat-trans (list (x-points data) (y-points data) (z-points data))) :output output :terminal terminal :samplerate samplerate :numsamples numsamples :biquad-cascade biquad-cascade
|
:title title :x-label x-label :y-label y-label :x-range x-range :y-range y-range :x-scale x-scale :y-scale y-scale))
|
|
(defparameter *exp-list* '("# gnuplot file" "set title 'SoX effect: bass gain=12 frequency=100 Q=10 (rate=48000)'" "set xlabel 'Frequency (Hz)'" "set ylabel 'Amplitude Response (dB)'" "Fs=48000" "b0=1.000524909583901e+00; b1=-1.998859954575080e+00; b2=9.986767718845346e-01; a1=-1.998987899064422e+00; a2=9.990737369790924e-01" "o=2*pi/Fs" "H(f)=sqrt((b0*b0+b1*b1+b2*b2+2.*(b0*b1+b1*b2)*cos(f*o)+2.*(b0*b2)*cos(2.*f*o))/(1.+a1*a1+a2*a2+2.*(a1+a1*a2)*cos(f*o)+2.*a2*cos(2.*f*o)))" "#set logscale x" "set samples 250" "#set grid xtics ytics" "#set key off" "plot [f=10:Fs/2] [-35:55] H(f) #20*log10(H(f))" "pause -1 'Hit return to continue'" ""))
|
|
|
;-> perhaps add this to the sox-plot function: Meaning, that you can make a list of the individual fx, then supply them to "biquad" cascade!!
|
|
#|
|
(defmethod! sox-multiplot (&rest biquad-fx &key output (terminal "png") (samplerate "48k") (numsamples 250) biquad-cascade title x-label y-label x-range y-range x-scale y-scale)
|
; this function is like sox-plot but allows to connect multiple effects which are then combined in the visualization (new inlet per new biquad effect))
|
:icon 07
|
; :initvals (list (repeat-n nil (length biquad-effects)) nil "" "aqua" nil nil nil "" "" "" nil nil nil nil) ;hmmm, not sure what to do with the initvals
|
; :menuins (list
|
; (list 2 *gnuplot-terminal-menu*)
|
; (list 3 *sox-samplerates*)
|
; (list 10 '(("linear" "linear") ("logarithmic" "logarithmic")))
|
; (list 11 '(("linear" "linear") ("deciBel" "deciBel"))))
|
; :doc '("sox-multiplot allows connecting multiple effects which are then combined and plotted (new inlet per sox-effect)
|
|
;Plots the combined amplitude response of biquad-based effects. Biquad-based effects are:
|
;highpass, lowpass, bandpass, bandreject, allpass, bass, treble, equalizer, band, deemph, riaa, biquad.")
|
|
(if (probe-file *sox-path*)
|
(if (probe-file *gnuplot-path*)
|
|
(let* ((default-text *def-pearl-txt*)
|
(datalist (sox-reduce-fx biquad-fx))
|
(xscale (if (equal x-scale "logarithmic") "set logscale x" "#set logscale x"))
|
(samples (format nil "set samples ~D" numsamples))
|
(plot-title (or title (format nil "set title 'SoX effects: $desc (rate=$rate)'")))
|
(xlabel (if x-label ; maybe this if-statement is not needed, as X is always Frequency and Y always Amplitude
|
(if (equal x-label "") "#set xlabel <not set>" (format nil "set xlabel '~a'" x-label))
|
(if (equal x-scale "logarithmic") "set xlabel 'Frequency (Hz) (log.)'" "set xlabel 'Frequency (Hz)'")))
|
(ylabel (if y-label
|
(if (equal y-label "") "#set ylabel <not set>" (format nil "set ylabel '~a'" y-label))
|
(if (equal y-scale "deciBel") "set ylabel 'Amplitude (dBFS)'" "set ylabel 'Amplitude (linear)'")))
|
(sox-gnuplot-call "$_ = `~a --plot gnuplot --rate $rate -n -n $_ | sed -n -e '6 p'`" *sox-path*)
|
(plot-text (if (equal y-scale "deciBel")
|
(format nil "plot [f=~D:~a] [~D:~D] 20*log10(H(f))" (first x-range) (second x-range) (first y-range) (second y-range))
|
(format nil "plot [f=~D:~a] [~D:~D] (H(f))" (first x-range) (second x-range) (first y-range) (second y-range))))
|
|
(manipulated-text (if title (subs-posn default-text '(0 1 2 3 6) (list plot-title xlabel ylabel xscale plot-list-text))
|
(subs-posn default-text '(1 2 3 6) (list xlabel ylabel xscale plot-list-text))))
|
(mypicfile (create-path () output terminal))
|
(new-text (x-append *sox-gnuplot-header*
|
(second (assoc terminal *gnuplot-terminal-commands* :test #'equalp))
|
(format nil "set output ~s" (namestring mypicfile))
|
manipulated-text
|
datalist))
|
(new-textfile (make-instance 'textfile :eval-mode "text" )))
|
(setf (exp-list new-textfile) new-text)
|
(save-data new-textfile mypearlfile)
|
(probe-file mypearlfile)
|
(om-cmd-line (format nil "~s ~s " mypearlfile (namestring mypltfile)) *sys-console*)
|
datalist
|
(let ((myoutfile (probe-file mypicfile))
|
(mypict (make-instance 'picture)))
|
(setf (background mypict) myoutfile)
|
|
;optional removal of temporary files
|
(add-tmp-file mypltfile)
|
(add-tmp-file myoutfile)
|
(when *delete-inter-file* (clean-tmp-files))
|
mypict)
|
)
|
(gnuplot-not-found))
|
(sox-not-found)
|
)
|
|#
|
|
|
(defparameter *sox-gnuplot-header* '("#This file was produced for gnuplot by OM-SoX v1.0."))
|
(defparameter *default-gnuplot-text* (list
|
"#set title <no title>"
|
"set xlabel <not set>"
|
"set ylabel <not set>"
|
"set scale <not set>"
|
"set grid xtics ytics"
|
"set key off"
|
"plot '-' with lines"))
|
|
(defparameter *def-pearl-txt* '((*sox-gnuplot-header*
|
"# Perl Script by Ulrich Klauer"
|
""
|
"my $rate = 48000;"
|
"$rate = shift if $ARGV[0] =~ /^\d+$/ && $ARGV[0] != 0;"
|
"#usage() if $#ARGV == -1;"
|
"my $desc = join(" ", @ARGV);"
|
"print <<END;"
|
"# gnuplot file"
|
"set title 'SoX effects: $desc (rate=$rate)'"
|
"set terminal 'png'"
|
"set output <not set>"
|
"#set xlabel <not set>"
|
"#set ylabel <not set>"
|
"Fs=$rate"
|
"o=2*pi/Fs"
|
"#set logscale x"
|
"set grid xtics ytics"
|
"set key off"
|
"END"
|
"my $l = 0;"
|
"foreach (@ARGV) {"
|
" $l++;"
|
" $_ = `sox --plot gnuplot --rate $rate -n -n $_ | sed -n -e '6 p'`;"
|
" s/([ab][012])/$1_$l/g;"
|
" print;"
|
" print \"H_$l(f)=sqrt((b0_$l*b0_$l+b1_$l*b1_$l+b2_$l*b2_$l+2.*(b0_$l*b1_$l+b1_$l*b2_$l)*cos(f*o)+2.*(b0_$l*b2_$l)*cos(2.*f*o))/(1.+a1_$l*a1_$l+a2_$l*a2_$l+2.*(a1_$l+a1_$l*a2_$l)*cos(f*o)+2.*a2_$l*cos(2.*f*o)))\n\";"
|
"}"
|
"my $prod = join(\"*\", map { \"H_$_(f)\" } (1..$l));"
|
"print \"H(f)=$prod\n\";"
|
"print <<END;"
|
"plot [f=10:Fs/2] [-35:25] 20*log10(H(f))" ;this gets replaced by my function
|
"END")
|
))
|
|
|
;(defun string+ (&rest strings) (eval `(concatenate 'string ,.strings)))
|
|
(defun sox-reduce-effects (effects)
|
(reduce #'(lambda (old new) (format nil " ~a \"~a\" " old new))
|
effects :initial-value "")
|
)
|
|
(defun sox-reduce-fx (&rest effects)
|
(reduce #'(lambda (old new) (format nil " ~a \"~a\" " old new))
|
effects :initial-value "")
|
)
|
|
|
; the perl script:
|
; gotta use this together with arguments <file> args ... (which are the commands!)
|
|
#|
|
"#!/usr/bin/perl -w
|
|
sub usage() {
|
print STDERR "Usage: $0 [sampling_rate] \"effect1 effargs1\" " .
|
"\"effect2 effargs2\" ...\n";
|
print STDERR "Plots the combined amplitude response of biquad-based " .
|
"effects.\n";
|
print STDERR "Biquad-based effects are: highpass, lowpass, " .
|
"bandpass, bandreject, allpass,\n" . "bass, treble, " .
|
"equalizer, band, deemph, riaa, biquad.\n";
|
exit(1);
|
}
|
|
usage() if $#ARGV == -1;
|
|
|
;--------this above is not needed-------
|
|
;maybe don't change this title (or title (format nil "set title 'SoX effects: $desc (rate=$rate)'"))"
|
|
|
"set title 'SoX effects: $desc (rate=$rate)'
|
"#set xlabel <not set>"
|
"#set ylabel <not set>"
|
"Fs=$rate"
|
"o=2*pi/Fs"
|
"#set logscale x"
|
"set grid xtics ytics"
|
"set key off"
|
"END"
|
"my $l = 0;"
|
"foreach (@ARGV) {"
|
" $l++;"
|
"$_ = `sox --plot gnuplot --rate $rate -n -n $_ | sed -n -e '6 p'`;"
|
" s/([ab][012])/$1_$l/g;"
|
" print;"
|
" print \"H_$l(f)=sqrt((b0_$l*b0_$l+b1_$l*b1_$l+b2_$l*b2_$l+2.*(b0_$l*b1_$l+b1_$l*b2_$l)*cos(f*o)+2.*(b0_$l*b2_$l)*cos(2.*f*o))/(1.+a1_$l*a1_$l+a2_$l*a2_$l+2.*(a1_$l+a1_$l*a2_$l)*cos(f*o)+2.*a2_$l*cos(2.*f*o)))\n\";"
|
"}"
|
"my $prod = join(\"*\", map { \"H_$_(f)\" } (1..$l));"
|
"print \"H(f)=$prod\n\";"
|
|
"print <<END;"
|
"plot [f=10:Fs/2] [-35:25] 20*log10(H(f))" ;this gets replaced by my function
|
"END")
|
|
(format nil "$_ = `~a --plot gnuplot --rate $rate -n -n $_ | sed -n -e '6 p'`" *sox-path*)
|
|
|#
|
|
; plotting directly in OM would be the most flexible!
|
(defun biquad-equation (f b0 b1 b2 a1 a2 samplerate)
|
(let* ((o (/ (* 2 pi) samplerate)))
|
; this creates a complex number for some reason... where? probably a negative number
|
; what does this mean (from the scripts): o=2*pi/Fs
|
(sqrt (/ (+ (* b0 b0)
|
(* b1 b1)
|
(* b2 b2)
|
(* 2 (+ (* b0 b1) (* b1 b2)) (cos (* f o)))
|
(* 2 (* b0 b2) (cos (* 2 f o))))
|
(+ 1 (* a1 a1) (* a2 a2)
|
(* 2 (+ a1 (* a1 a2))
|
(cos (* f o)))
|
(* 2 (* a2 (cos (* 2 f o)))))))
|
))
|