;;; -*- syntax: common-lisp; package: clm; base: 10; mode:lisp -*- ;;; ;;; HRTF localization instrument ;;; Fernando Lopez-Lezcano ;;; ;;; April 13 1998 ... April 21 1998 ;;; Feb 2001: translated to clm-2 [nando] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; HRTF data loading (for the MIT kemar hrtf's) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defparameter hrtf-fir-file "/usr/ccrma/snd/nando/hrtf/fir-coeffs.lisp") (defparameter hrtf-fir-data '(()())) (defparameter hrtf-dir "/usr/ccrma/snd/nando/hrtf/diffuse/") (defparameter hrtf-size 128) (defparameter hrtf-data nil) (defvar hrtf-impulses (make-array (* 2 400 hrtf-size) :element-type 'short-float :adjustable t)) (defun load-fir-data (&key (file hrtf-fir-file) (verbose)) (if (not verbose) (format t "; loading fir filter coefficients...~%")) (load file)) (defun load-hrtf-data (&key (dir hrtf-dir) (size hrtf-size) (verbose)) (if (not verbose) (format t "; loading hrtf data set...~%")) (loop with data with offset = 0 for elevation-dir in (directory dir) do ;; parse the elevation value [elevXXX.DAT] (multiple-value-bind (elevation index) (parse-integer (file-namestring elevation-dir) :start 4 :junk-allowed t) ;; each elevation is a directory, process the files inside (loop with current with azimut with name with impulse-file with impulse-in-a with impulse-in-b for azimut-file in (directory (concatenate 'string (namestring elevation-dir) "/")) do (setf name (file-namestring azimut-file)) ;; parse the filename's elevation and azimut [hXXXeYYYa.DAT] (multiple-value-bind (elevation index) (parse-integer name :start 1 :junk-allowed t) (setf azimut (parse-integer name :start (+ index 1) :junk-allowed t)) (if verbose (format t "reading ~a --> [~s:~s]...~%" name elevation azimut)) (setf impulse-file (open-input azimut-file) impulse-in-a (make-readin impulse-file 0) impulse-in-b (make-readin impulse-file 1)) ;; read in the impulse response for the given elevation and azimut (loop for i from 0 below size do (setf (aref hrtf-impulses (+ offset i))(readin impulse-in-a) (aref hrtf-impulses (+ offset size i))(readin impulse-in-b))) (close-input impulse-file) ;; merge each azimut list into the current elevation (setf current (if (not current) (list (list azimut offset (+ offset size))) (lisp:merge 'list current (list (list azimut offset (+ offset size))) #'< :key #'car))) (incf offset (* 2 size))) finally ;; merge each elevation into the dataset (setf data (if (not data) (list (list elevation current)) (lisp:merge 'list data (list (list elevation current)) #'< :key #'car))))) finally ;; set the global data, maybe we should just return the list (if verbose (format t "~s hrtf sets loaded~%" (/ offset 2 hrtf-size))) (setf hrtf-data data))) (definstrument hrtf (start-time amplitude file &key (elevation 0) (azimut 0) (reverb-amount 0.1) (headphones nil)) (let* (e-min e-max e-pos e-pos-gt a-pos a-pos-gt e a impulse-l impulse-r) ;; load all impulse responses if necessary (if (not hrtf-data) (load-hrtf-data)) ;; load the crosstalk cancellation filter coefficients if necessary (if (not (first hrtf-fir-data)) (load-fir-data)) ;; sanity checks for elevation data (setf e-min (first (first hrtf-data)) e-max (first (first (last hrtf-data)))) (if (< elevation e-min) (format t "; Warning: elevation [~s] less than minimum ~s~%" elevation e-min) (if (> elevation e-max) (format t "; Warning: elevation [~s] more than maximum ~s~%" elevation e-max))) ;; find the closest elevation impulse response set (setf e-pos (position-if #'(lambda (x) (<= (first x) elevation)) hrtf-data :from-end t)) (if (null e-pos) (setf e-pos 0)) (setf e-pos-gt (+ e-pos 1)) (if (nth e-pos-gt hrtf-data) (if (< (abs (- elevation (first (nth e-pos hrtf-data)))) (abs (- (first (nth e-pos-gt hrtf-data)) elevation))) (setf e (nth e-pos hrtf-data)) (setf e (nth e-pos-gt hrtf-data))) (setf e (nth e-pos hrtf-data))) ;; find the closest azimut impulse response (setf azimut (mod azimut 360)) (if (> azimut 180) (setf azimut (- azimut 360))) (setf a-pos (position-if #'(lambda (x) (<= (first x)(abs azimut))) (second e) :from-end t)) (if (null a-pos) (setf a-pos 0)) (setf a-pos-gt (+ a-pos 1)) (if (nth a-pos-gt (second e)) (if (< (abs (- elevation (first (nth a-pos (second e))))) (abs (- (first (nth a-pos-gt (second e))) elevation))) (setf a (nth a-pos (second e))) (setf a (nth a-pos-gt (second e)))) (setf a (nth a-pos (second e)))) (setf impulse-l (second a) impulse-r (third a)) (format t "at [~s:~s] ~s:~s~%" elevation azimut (first e) (first a)) ;; create unit generators (let* ((beg (floor (* start-time *srate*))) (in-file (open-input file)) (in-file-dur (sound-duration in-file)) (end (+ beg (floor (* in-file-dur *srate*)))) (filter-l (make-double-float-array hrtf-size)) (filter-r (make-double-float-array hrtf-size)) ;; create the convolvers (conv-l (make-convolve :input in-file :fft-size hrtf-size :filter (loop for j from 0 below hrtf-size for k from (if (< azimut 0) impulse-l impulse-r) do (setf (aref filter-l j) (double-float (aref hrtf-impulses k))) finally (return filter-l)))) (conv-r (make-convolve :input in-file :fft-size hrtf-size :filter (loop for j from 0 below hrtf-size for k from (if (< azimut 0) impulse-r impulse-l) do (setf (aref filter-r j) (double-float (aref hrtf-impulses k))) finally (return filter-r)))) ;; create the fir filters for crosstalk cancellation (fir-l (make-fir-filter (length (first hrtf-fir-data)) (first hrtf-fir-data))) (fir-r (make-fir-filter (length (second hrtf-fir-data)) (second hrtf-fir-data)))) (run (loop for i from beg to end do (let* ((left (* amplitude (convolve conv-l))) (right (* amplitude (convolve conv-r))) (firl (fir-filter fir-l (+ left right))) (firr (fir-filter fir-r (- left right))) (outl (+ firl firr)) (outr (- firl firr))) (outa i (if headphones left outl)) (outb i (if headphones right outr)) (if *reverb* (out-any i (* (/ (+ (if headphones left outl *reverb*) (if headphones right outr)) 2) reverb-amount)))))) (close-input in-file)))) #| (with-sound(:channels 2 :statistics t) (loop for time from 0 by 0.3 for az from 0 by 5 below 181 do (hrtf time 0.25 "/usr/ccrma/web/html/courses/220b/lectures/5/examples/sounds/knife-11.snd" :azimut az :headphones t))) |#