;;; a DSP-related grabbag (provide 'snd-dsp.scm) (if (provided? 'snd) (require snd-ws.scm) (require sndlib-ws.scm)) (require snd-env.scm) (when (provided? 'pure-s7) (define (make-polar mag ang) (if (and (real? mag) (real? ang)) (complex (* mag (cos ang)) (* mag (sin ang))) (error 'wrong-type-arg "make-polar args should be real")))) (define binomial (let ((documentation "(binomial n k) computes the binomial coefficient C(N,K)")) (lambda (n k) (let ((mn (min k (- n k)))) (if (< mn 0) 0 (if (= mn 0) 1 (let ((mx (max k (- n k)))) (do ((cnk (+ 1 mx)) (i 2 (+ 1 i))) ((> i mn) cnk) (set! cnk (/ (* cnk (+ mx i)) i)))))))))) (define log10 (let ((documentation "(log10 a) returns the log base 10 of 'a'")) (lambda (a) (log a 10)))) ;;; src-duration (see src-channel in extsnd.html) (define src-duration (let ((documentation "(src-duration envelope) returns the new duration of a sound after using 'envelope' for time-varying sampling-rate conversion")) (lambda (e) (let ((len (- (length e) 2))) (do ((all-x (- (e len) (e 0))) ; last x - first x (dur 0.0) (i 0 (+ i 2))) ((>= i len) dur) (let ((area (let ((x0 (e i)) (x1 (e (+ i 2))) (y0 (e (+ i 1))) ; 1/x x points (y1 (e (+ i 3)))) (if (< (abs (real-part (- y0 y1))) .0001) (/ (- x1 x0) (* y0 all-x)) (/ (* (- (log y1) (log y0)) (- x1 x0)) (* (- y1 y0) all-x)))))) ;; or (* (/ (- (log y1) (log y0)) (- y1 y0)) ;; or (/ (log (/ y1 y0)) (- y1 y0)) ;; (/ (- x1 x0) all-x) (set! dur (+ dur (abs area))))))))) ;;; :(src-duration '(0 1 1 2)) ;;; 0.69314718055995 ;;; :(src-duration #(0 1 1 2)) ;;; 0.69314718055995 (define (src-fit-envelope e target-dur) (scale-envelope e (/ (src-duration e) target-dur))) ; scale-envelope is in env.scm ;;; -------- Dolph-Chebyshev window ;;; ;;; formula taken from Richard Lyons, "Understanding DSP" ;;; see clm.c for C version (using either GSL's or GCC's complex trig functions) (define dolph (let ((documentation "(dolph n gamma) produces a Dolph-Chebyshev FFT data window of 'n' points using 'gamma' as the window parameter.")) (lambda (N gamma) (let ((rl (make-float-vector N)) (im (make-float-vector N))) (let ((alpha (cosh (/ (acosh (expt 10.0 gamma)) N)))) (do ((den (/ 1.0 (cosh (* N (acosh alpha))))) (freq (/ pi N)) (i 0 (+ i 1)) (phase 0.0 (+ phase freq))) ((= i N)) (let ((val (* den (cos (* N (acos (* alpha (cos phase)))))))) (set! (rl i) (real-part val)) (set! (im i) (imag-part val))))) ;this is always essentially 0.0 (fft rl im -1) ;direction could also be 1 (float-vector-scale! rl (/ 1.0 (float-vector-peak rl))) (do ((i 0 (+ i 1)) (j (/ N 2))) ((= i N)) (set! (im i) (rl j)) (set! j (+ j 1)) (if (= j N) (set! j 0))) im)))) ;;; this version taken from Julius Smith's "Spectral Audio..." with three changes ;;; it does the DFT by hand, and is independent of anything from Snd (fft, float-vectors etc) (define dolph-1 (let ((documentation "(dolph-1 n gamma) produces a Dolph-Chebyshev FFT data window of 'n' points using 'gamma' as the window parameter.")) (lambda (N gamma) (let ((vals (make-vector N))) (let ((alpha (cosh (/ (acosh (expt 10.0 gamma)) N)))) (do ((den (/ 1.0 (cosh (* N (acosh alpha))))) (freq (/ pi N)) (mult -1 (- mult)) (i 0 (+ i 1)) (phase (* -0.5 pi) (+ phase freq))) ((= i N)) (set! (vals i) (* mult den (cos (* N (acos (* alpha (cos phase))))))))) ;; now take the DFT (let ((pk 0.0) (w (make-vector N))) (do ((i 0 (+ i 1)) (sum 0.0 0.0)) ((= i N)) (do ((k 0 (+ k 1))) ((= k N)) (set! sum (+ sum (* (vals k) (exp (/ (* 2.0 0+1.0i pi k i) N)))))) (set! (w i) (magnitude sum)) (set! pk (max pk (w i)))) ;; scale to 1.0 (it's usually pretty close already, that is pk is close to 1.0) (do ((i 0 (+ i 1))) ((= i N)) (set! (w i) (/ (w i) pk))) w))))) ;;; -------- move sound down by n (a power of 2) (define down-oct (let ((documentation "(down-n n) moves a sound down by n-1 octaves")) (lambda* (n snd chn) ;; I think this is "stretch" in DSP jargon -- to interpolate in the time domain we're squeezing the frequency domain ;; the power-of-2 limitation is based on the underlying fft function's insistence on power-of-2 data sizes ;; see stretch-sound-via-dft below for a general version (let* ((len (framples snd chn)) (fftlen (floor (expt 2 (ceiling (log len 2)))))) (let ((fftlen2 (/ fftlen 2)) (fft-1 (- (* n fftlen) 1)) (fftscale (/ 1.0 fftlen)) (rl1 (channel->float-vector 0 fftlen snd chn)) (im1 (make-float-vector fftlen))) (fft rl1 im1 1) (float-vector-scale! rl1 fftscale) (float-vector-scale! im1 fftscale) (let ((rl2 (make-float-vector (* n fftlen))) (im2 (make-float-vector (* n fftlen)))) (copy rl1 rl2 0 fftlen2) (copy im1 im2 0 fftlen2) (do ((k (- fftlen 1) (- k 1)) (j fft-1 (- j 1))) ((= k fftlen2)) (float-vector-set! rl2 j (float-vector-ref rl1 k)) (float-vector-set! im2 j (float-vector-ref im1 k))) (fft rl2 im2 -1) (float-vector->channel rl2 0 (* n len) snd chn #f (format #f "down-oct ~A" n)))))))) (define stretch-sound-via-dft (let ((documentation "(stretch-sound-via-dft factor snd chn) makes the given channel longer ('factor' should be > 1.0) by \ squeezing in the frequency domain, then using the inverse DFT to get the time domain result.")) (lambda* (factor snd chn) ;; this is very slow! factor>1.0 (let* ((n (framples snd chn)) (out-n (round (* n factor)))) (let ((out-data (make-float-vector out-n)) (fr (make-vector out-n 0.0)) (freq (/ (* 2 pi) n))) (do ((in-data (channel->float-vector 0 n snd chn)) (n2 (floor (/ n 2.0))) (i 0 (+ i 1))) ((= i n)) ;; DFT + split (set! (fr (if (< i n2) i (- (+ i out-n) n 1))) (edot-product (* freq 0.0-1.0i i) in-data))) (set! freq (/ (* 2 pi) out-n)) (do ((i 0 (+ i 1))) ((= i out-n)) ;; inverse DFT (set! (out-data i) (real-part (/ (edot-product (* freq 0.0+1.0i i) fr) n)))) (float-vector->channel out-data 0 out-n snd chn #f (format #f "stretch-sound-via-dft ~A" factor))))))) ;;; -------- vibrating-uniform-circular-string ;;; ;;; this is a simplification of the underlying table-filling routine for "scanned synthesis". ;;; To watch the wave, open some sound (so Snd has some place to put the graph), turn off ;;; the time domain display (to give our graph all the window -- to do this in a much more ;;; elegant manner, see snd-motif.scm under scanned-synthesis). #| (define old-vibrating-uniform-circular-string (lambda (size x0 x1 x2 mass xspring damp) (define circle-ref (lambda (v i) (if (< i 0) (v (+ size i)) (if (>= i size) (v (- i size)) (v i))))) (let* ((dm (/ damp mass)) (km (/ xspring mass)) (denom (+ 1.0 dm)) (p1 (/ (+ 2.0 (- dm (* 2.0 km))) denom)) (p2 (/ km denom)) (p3 (/ -1.0 denom))) (do ((i 0 (+ i 1))) ((= i size)) (set! (x0 i) (+ (* p1 (x1 i)) (* p2 (+ (circle-ref x1 (- i 1)) (circle-ref x1 (+ i 1)))) (* p3 (x2 i))))) (copy x1 x2) (copy x0 x1)))) ;;; (vibrating-uniform-circular-string 100 (make-float-vector 100) (make-float-vector 100) (make-float-vector 100) .5 .5 .5) |# (define (vibrating-uniform-circular-string size x0 x1 x2 mass xspring damp) (let ((dm (/ damp mass)) (km (/ xspring mass))) (let ((denom (+ 1.0 dm))) (let ((p2 (/ km denom)) (s1 (- size 1))) (float-vector-scale! x2 (/ -1.0 denom)) (copy x1 x0) (float-vector-scale! x0 (/ (- (+ 2.0 dm) (* 2.0 km)) denom)) (float-vector-add! x0 x2) (set! (x0 0) (+ (x0 0) (* p2 (+ (x1 s1) (x1 1))))) (do ((i 1 (+ i 1))) ((= i s1)) (float-vector-set! x0 i (+ (float-vector-ref x0 i) (* p2 (+ (float-vector-ref x1 (- i 1)) (float-vector-ref x1 (+ i 1))))))) (set! (x0 s1) (+ (x0 s1) (* p2 (+ (x1 (- s1 1)) (x1 0)))))))) (copy x1 x2) (copy x0 x1)) (define (vibrating-string size x0 x1 x2 masses xsprings esprings damps haptics) ;; this is the more general form (do ((i 0 (+ i 1))) ((= i size)) (let ((mass (masses i))) (let ((dm (/ (damps i) mass)) (km (/ (xsprings i) mass)) (cm (/ (esprings i) mass))) (let ((denom (+ 1.0 dm cm))) (let ((p1 (/ (- (+ 2.0 dm) (* 2.0 km)) denom)) (p2 (/ km denom)) (p3 (/ -1.0 denom)) (p4 (/ (haptics i) (* mass denom))) (j (if (= i 0) size (- i 1))) (k (if (= i (- size 1)) 0 (+ i 1)))) (set! (x0 i) (+ (* p1 (x1 i)) (* p2 (+ (x1 j) (x1 k))) (* p3 (x2 i)) p4))))))) (copy x1 x2) (copy x0 x1)) ;;; -------- "frequency division" -- an effect from sed_sed@my-dejanews.com (define freqdiv (let ((documentation "(freqdiv n snd chn) repeats each nth sample n times (clobbering the intermediate samples): (freqdiv 8)")) (lambda* (n snd chn) (let* ((data (channel->float-vector 0 #f snd chn)) (len (length data))) (if (> n 1) (do ((i 0 (+ i n))) ((>= i len) (float-vector->channel data 0 len snd chn)) (let ((val (data i)) (stop (min len (+ i n)))) (do ((k (+ i 1) (+ k 1))) ((= k stop)) (float-vector-set! data k val))))))))) ;;; -------- "adaptive saturation" -- an effect from sed_sed@my-dejanews.com ;;; ;;; a more extreme effect is "saturation": ;;; (map-channel (lambda (val) (if (< (abs val) .1) val (if (>= val 0.0) 0.25 -0.25)))) (define adsat (let ((documentation "(adsat size beg dur snd chn) is an 'adaptive saturation' sound effect")) (lambda* (size (beg 0) dur snd chn) (let* ((len (if (number? dur) dur (- (framples snd chn) beg))) (data (make-float-vector (* size (ceiling (/ len size)))))) (let ((reader (make-sampler beg snd chn)) (mn 0.0) (mx 0.0) (vals (make-float-vector size))) (do ((i 0 (+ i size))) ((>= i len)) (do ((k 0 (+ k 1))) ((= k size)) (float-vector-set! vals k (next-sample reader))) (set! mn (float-vector-min vals)) (set! mx (float-vector-max vals)) (do ((k 0 (+ k 1))) ((= k size)) (float-vector-set! data (+ i k) (if (negative? (float-vector-ref vals k)) mn mx)))) (float-vector->channel data beg len snd chn current-edit-position (format #f "adsat ~A ~A ~A" size beg dur))))))) ;;; -------- spike ;;; ;;; makes sound more spikey -- sometimes a nice effect #| (define spike (let ((documentation "(spike snd chn) multiplies successive samples together to make a sound more spikey")) (lambda* (snd chn) (let* ((len (framples snd chn)) (data (make-float-vector len)) (amp (maxamp snd chn))) ; keep resultant peak at maxamp (let ((reader (make-sampler 0 snd chn)) (x1 0.0) (x2 0.0) (amp1 (/ 1.0 (* amp amp)))) (do ((i 0 (+ i 1))) ((= i len)) (let ((x0 (next-sample reader))) (float-vector-set! data i (* x0 x1 x2)) (set! x2 x1) (set! x1 (abs x0)))) (float-vector->channel (float-vector-scale! data amp1) 0 len snd chn current-edit-position "spike")))))) |# (define spike (let ((documentation "(spike snd chn) multiplies successive samples together to make a sound more spikey")) (lambda* (snd chn) (let ((len (framples snd chn))) (let ((data (channel->float-vector 0 (+ len 2) snd chn)) (amp (maxamp snd chn)) ; keep resultant peak at maxamp ;; multiply x[0]*x[1]*x[2] (data1 (make-float-vector (+ len 1)))) (copy data data1 1) (float-vector-abs! (float-vector-multiply! data1 data)) (float-vector-multiply! data (make-shared-vector data1 (list len) 1)) (let ((amp1 (/ amp (float-vector-peak data)))) (float-vector->channel (float-vector-scale! data amp1) 0 len snd chn current-edit-position "spike"))))))) ;;; the more successive samples we include in the product, the more we ;;; limit the output to pulses placed at (just after) wave peaks ;;; -------- easily-fooled autocorrelation-based pitch tracker (define spot-freq (let ((documentation "(spot-freq samp snd chn) tries to determine the current pitch: (spot-freq (left-sample))")) (lambda* (s0 snd chn) (let* ((fftlen (floor (expt 2 (ceiling (log (/ (srate snd) 20.0) 2))))) (data (autocorrelate (channel->float-vector s0 fftlen snd chn))) (cor-peak (float-vector-peak data))) (if (= cor-peak 0.0) 0.0 (call-with-exit (lambda (return) (do ((i 1 (+ i 1))) ((= i (- fftlen 2)) 0) (if (and (< (data i) (data (+ i 1))) (> (data (+ i 1)) (data (+ i 2)))) (let ((offset (let ((logla (log10 (/ (+ cor-peak (data i)) (* 2 cor-peak)))) (logca (log10 (/ (+ cor-peak (data (+ i 1))) (* 2 cor-peak)))) (logra (log10 (/ (+ cor-peak (data (+ i 2))) (* 2 cor-peak))))) (/ (* 0.5 (- logla logra)) (+ logla logra (* -2.0 logca)))))) (return (/ (srate snd) (* 2 (+ i 1 offset)))))))))))))) ;(hook-push graph-hook ; (lambda (hook) ; (status-report (format #f "~A" (spot-freq (left-sample)))))) ;;; -------- chorus (doesn't always work and needs speedup) (define chorus-size 5) (define chorus (let ((documentation "(chorus) tries to produce the chorus sound effect")) (lambda () (let ((make-flanger (let ((chorus-time .05) (chorus-amount 20.0) (chorus-speed 10.0)) (lambda () (let ((ri (make-rand-interp :frequency chorus-speed :amplitude chorus-amount)) (len (floor (random (* 3.0 chorus-time (srate)))))) (list (make-delay len :max-size (+ len chorus-amount 1)) ri))))) (flanger (lambda (dly inval) (+ inval (delay (car dly) inval (rand-interp (cadr dly)))))) (dlys (make-vector chorus-size))) (do ((i 0 (+ i 1))) ((= i chorus-size)) (set! (dlys i) (make-flanger))) (lambda (inval) (do ((sum 0.0) (i 0 (+ i 1))) ((= i chorus-size) (* .25 sum)) (set! sum (+ sum (flanger (dlys i) inval))))))))) ;;; -------- chordalize (comb filters to make a chord using chordalize-amount and chordalize-base) (define chordalize-amount .95) (define chordalize-base 100) (define chordalize-chord '(1 3/4 5/4)) (define chordalize (let ((documentation "(chordalize) uses harmonically-related comb-filters to bring out a chord in a sound")) (lambda () ;; chord is a list of members of chord such as '(1 5/4 3/2) (let ((combs (make-comb-bank (apply vector (map (lambda (interval) (make-comb chordalize-amount (floor (* chordalize-base interval)))) chordalize-chord)))) (scaler (/ 0.5 (length chordalize-chord)))) ; just a guess -- maybe this should rescale to old maxamp (lambda (x) (* scaler (comb-bank combs x))))))) ;;; -------- zero-phase, rotate-phase ;;; fft games (from the "phazor" package of Scott McNab) (define zero-phase (let ((documentation "(zero-phase snd chn) calls fft, sets all phases to 0, and un-ffts")) (lambda* (snd chn) (let* ((len (framples snd chn)) (fftlen (floor (expt 2 (ceiling (log len 2))))) (rl (channel->float-vector 0 fftlen snd chn))) (let ((fftscale (/ 1.0 fftlen)) (old-pk (float-vector-peak rl)) (im (make-float-vector fftlen))) (if (> old-pk 0.0) (begin (fft rl im 1) (rectangular->magnitudes rl im) (float-vector-scale! rl fftscale) (float-vector-scale! im 0.0) (fft rl im -1) (let ((pk (float-vector-peak rl))) (float-vector->channel (float-vector-scale! rl (/ old-pk pk)) 0 len snd chn #f "zero-phase"))))))))) (define rotate-phase (let ((documentation "(rotate-phase func snd chn) calls fft, applies func to each phase, then un-ffts")) (lambda* (func snd chn) (let* ((len (framples snd chn)) (fftlen (floor (expt 2 (ceiling (log len 2))))) (rl (channel->float-vector 0 fftlen snd chn))) (let ((fftlen2 (floor (/ fftlen 2))) (fftscale (/ 1.0 fftlen)) (old-pk (float-vector-peak rl)) (im (make-float-vector fftlen))) (if (> old-pk 0.0) (begin (fft rl im 1) (rectangular->magnitudes rl im) (float-vector-scale! rl fftscale) (set! (im 0) 0.0) (do ((i 1 (+ i 1)) (j (- fftlen 1) (- j 1))) ((= i fftlen2)) ;; rotate the fft vector by func, keeping imaginary part complex conjgate of real (set! (im i) (func (im i))) (set! (im j) (- (im i)))) (polar->rectangular rl im) (fft rl im -1) (let ((pk (float-vector-peak rl))) (float-vector->channel (float-vector-scale! rl (/ old-pk pk)) 0 len snd chn #f (format #f "rotate-phase ~A" (procedure-source func))))))))))) #| (rotate-phase (lambda (x) 0.0)) is the same as (zero-phase) (rotate-phase (lambda (x) (random pi))) randomizes phases (rotate-phase (lambda (x) x)) returns original (rotate-phase (lambda (x) (- x))) reverses original (might want to write fftlen samps here) (rotate-phase (lambda (x) (* x 2))) reverb-effect (best with voice) (rotate-phase (lambda (x) (* x 12)) "bruise blood" effect |# (define (signum n) ;; in CL this returns 1.0 if n is float (if (positive? n) 1 (if (zero? n) 0 -1))) ;;; -------- brighten-slightly (define brighten-slightly (let ((documentation "(brighten-slightly amount snd chn) is a form of contrast-enhancement ('amount' between ca .1 and 1)")) (lambda* (amount snd chn) (let ((len (framples snd chn))) (let ((mx (maxamp)) (brt (* 2 pi amount)) (data (make-float-vector len)) (reader (make-sampler 0 snd chn))) (do ((i 0 (+ i 1))) ((= i len)) (float-vector-set! data i (sin (* (next-sample reader) brt)))) (float-vector->channel (float-vector-scale! data (/ mx (float-vector-peak data))) 0 len snd chn current-edit-position (format #f "brighten-slightly ~A" amount))))))) (define brighten-slightly-1 (let ((documentation "(brighten-slightly-1 coeffs) is a form of contrast-enhancement: (brighten-slightly-1 '(1 .5 3 1))")) (lambda (coeffs) (let ((pcoeffs (partials->polynomial coeffs)) (mx (maxamp)) (len (framples))) (let ((data (make-float-vector len)) (reader (make-sampler 0))) (do ((i 0 (+ i 1))) ((= i len)) (float-vector-set! data i (polynomial pcoeffs (next-sample reader)))) (float-vector->channel (float-vector-scale! data (/ mx (float-vector-peak data))))))))) ;;; -------- FIR filters ;;; Snd's (very simple) spectrum->coefficients procedure is: (define spectrum->coeffs (let ((documentation "(spectrum->coeffs order spectr) returns FIR filter coefficients given the filter order and desired spectral envelope (a float-vector)")) (lambda (order spectr) (let ((coeffs (make-float-vector order)) (m (floor (/ (+ order 1) 2))) (am (* 0.5 (+ order 1))) (q (/ (* pi 2.0) order))) (if (not (float-vector? spectr)) (error "spectrum->coeffs spectrum argument should be a float-vector")) (do ((j 0 (+ j 1)) (jj (- order 1) (- jj 1))) ((= j m) coeffs) (let ((xt (* 0.5 (spectr 0)))) (do ((i 1 (+ i 1))) ((= i m)) (set! xt (+ xt (* (spectr i) (cos (* q i (- am j 1))))))) (let ((coeff (* 2.0 (/ xt order)))) (set! (coeffs j) coeff) (set! (coeffs jj) coeff)))))))) ;; (filter-channel et al reflect around the midpoint, so to match exactly you need to take ;; the env passed and flip it backwards for the back portion -- that is to say, this function ;; needs a wrapper to make it act like anyone would expect) (define fltit-1 (let ((documentation "(fltit-1 order spectrum) creates an FIR filter from spectrum and order and returns a closure that calls it: \ (map-channel (fltit-1 10 (float-vector 0 1.0 0 0 0 0 0 0 1.0 0)))")) (lambda (order spectr) (let ((flt (make-fir-filter order (spectrum->coeffs order spectr)))) (lambda (x) (fir-filter flt x)))))) ;(map-channel (fltit-1 10 (float-vector 0 1.0 0 0 0 0 0 0 1.0 0))) ; ;(let ((notched-spectr (make-float-vector 40))) ; (set! (notched-spectr 2) 1.0) ; (set! (notched-spectr 37) 1.0) ; (map-channel (fltit-1 40 notched-spectr))) ; ;;; -------- Hilbert transform (define make-hilbert-transform (let ((documentation "(make-hilbert-transform (len 30)) makes a Hilbert transform filter")) (lambda* ((len 30)) (let ((arrlen (+ 1 (* 2 len)))) (do ((arr (make-float-vector arrlen)) (lim (if (even? len) len (+ 1 len))) (i (- len) (+ i 1))) ((= i lim) (make-fir-filter arrlen arr)) (let ((k (+ i len)) (denom (* pi i)) (num (- 1.0 (cos (* pi i))))) (set! (arr k) (if (or (= num 0.0) (= i 0)) 0.0 ;; this is the "ideal" -- rectangular window -- version: ;; (set! (arr k) (/ num denom)) ;; this is the Hamming window version: (* (/ num denom) (+ .54 (* .46 (cos (/ (* i pi) len))))))))))))) ; window (define hilbert-transform fir-filter) #| (let ((h (make-hilbert-transform 15))) (map-channel (lambda (y) (hilbert-transform h y)))) ;;; this comes from R Lyons: (define* (sound->amp-env snd chn) (let ((hlb (make-hilbert-transform 40)) (d (make-delay 40))) (map-channel (lambda (y) (let ((hy (hilbert-transform hlb y)) (dy (delay d y))) (sqrt (+ (* hy hy) (* dy dy))))) 0 #f snd chn #f "sound->amp-env"))) (define* (hilbert-transform-via-fft snd chn) ;; same as FIR version but use FFT and change phases by hand ;; see snd-test.scm test 18 for a faster version (let* ((size (framples snd chn)) (len (expt 2 (ceiling (log size 2.0)))) (rl (make-float-vector len)) (im (make-float-vector len)) (rd (make-sampler 0 snd chn)) (pi2 (* 0.5 pi))) (do ((i 0 (+ i 1))) ((= i size)) (set! (rl i) (rd))) (mus-fft rl im len) (do ((i 0 (+ i 1))) ((= i len)) (let* ((c (complex (rl i) (im i))) (ph (angle c)) (mag (magnitude c))) (if (< i (/ len 2)) (set! ph (+ ph pi2)) (set! ph (- ph pi2))) (set! c (make-polar mag ph)) (set! (rl i) (real-part c)) (set! (im i) (imag-part c)))) (mus-fft rl im len -1) (float-vector-scale! rl (/ 1.0 len)) (float-vector->channel rl 0 len snd chn #f "hilbert-transform-via-fft"))) |# ;;; -------- highpass filter (define make-highpass (let ((documentation "(make-highpass fc (len 30)) makes an FIR highpass filter")) (lambda* (fc (len 30)) (let ((arrlen (+ 1 (* 2 len)))) (do ((arr (make-float-vector arrlen)) (i (- len) (+ i 1))) ((= i len) (make-fir-filter arrlen arr)) (let ((k (+ i len)) (denom (* pi i)) (num (- (sin (* fc i))))) (set! (arr k) (if (= i 0) (- 1.0 (/ fc pi)) (* (/ num denom) (+ .54 (* .46 (cos (/ (* i pi) len))))))))))))) (define highpass fir-filter) #| (let ((hp (make-highpass (* .1 pi)))) (map-channel (lambda (y) (highpass hp y)))) |# ;;; -------- lowpass filter (define make-lowpass (let ((documentation "(make-lowpass fc (len 30)) makes an FIR lowpass filter")) (lambda* (fc (len 30)) (let ((arrlen (+ 1 (* 2 len)))) (do ((arr (make-float-vector arrlen)) (i (- len) (+ i 1))) ((= i len) (make-fir-filter arrlen arr)) (let ((k (+ i len)) (denom (* pi i)) (num (sin (* fc i)))) (set! (arr k) (if (= i 0) (/ fc pi) (* (/ num denom) (+ .54 (* .46 (cos (/ (* i pi) len))))))))))))) (define lowpass fir-filter) #| (let ((hp (make-lowpass (* .2 pi)))) (map-channel (lambda (y) (lowpass hp y)))) |# ;;; -------- bandpass filter (define make-bandpass (let ((documentation "(make-bandpass flo fhi (len 30)) makes an FIR bandpass filter")) (lambda* (flo fhi (len 30)) (let ((arrlen (+ 1 (* 2 len)))) (do ((arr (make-float-vector arrlen)) (i (- len) (+ i 1))) ((= i len) (make-fir-filter arrlen arr)) (let ((k (+ i len)) (denom (* pi i)) (num (- (sin (* fhi i)) (sin (* flo i))))) (set! (arr k) (if (= i 0) (/ (- fhi flo) pi) (* (/ num denom) (+ .54 (* .46 (cos (/ (* i pi) len))))))))))))) (define bandpass fir-filter) #| (let ((hp (make-bandpass (* .1 pi) (* .2 pi)))) (map-channel (lambda (y) (bandpass hp y)))) ;; for more bands, you can add the coeffs: (define* (make-bandpass-2 flo1 fhi1 flo2 fhi2 (len 30)) (let* ((f1 (make-bandpass flo1 fhi1 len)) (f2 (make-bandpass flo2 fhi2 len))) (float-vector-add! (mus-xcoeffs f1) (mus-xcoeffs f2)) f1)) (let ((ind (new-sound "test.snd"))) (map-channel (lambda (y) (mus-random 1.0)) 0 10000) (let ((f2 (make-bandpass-2 (* .12 pi) (* .15 pi) (* .22 pi) (* .25 pi) 100))) (map-channel (lambda (y) (fir-filter f2 y))) )) |# ;;; -------- bandstop filter (define make-bandstop (let ((documentation "(make-bandstop flo fhi (len 30)) makes an FIR bandstop (notch) filter")) (lambda* (flo fhi (len 30)) (let ((arrlen (+ 1 (* 2 len)))) (do ((arr (make-float-vector arrlen)) (i (- len) (+ i 1))) ((= i len) (make-fir-filter arrlen arr)) (let ((k (+ i len)) (denom (* pi i)) (num (- (sin (* flo i)) (sin (* fhi i))))) (set! (arr k) (if (= i 0) (- 1.0 (/ (- fhi flo) pi)) (* (/ num denom) (+ .54 (* .46 (cos (/ (* i pi) len))))))))))))) (define bandstop fir-filter) #| (let ((hp (make-bandstop (* .1 pi) (* .3 pi)))) (map-channel (lambda (y) (bandstop hp y)))) |# ;;; -------- differentiator (define make-differentiator (let ((documentation "(make-differentiator (len 30)) makes an FIR differentiator (highpass) filter")) (lambda* ((len 30)) (let ((arrlen (+ 1 (* 2 len)))) (do ((arr (make-float-vector arrlen)) (i (- len) (+ i 1))) ((= i len) (make-fir-filter arrlen arr)) (let ((k (+ i len))) (set! (arr k) (if (= i 0) 0.0 (* (/ (cos (* pi i)) i) (+ .54 (* .46 (cos (/ (* i pi) len))))))))))))) (define differentiator fir-filter) #| (let ((hp (make-differentiator))) (map-channel (lambda (y) (differentiator hp y)))) |# ;;; -------- IIR filters ;;; see analog-filter.scm for the usual suspects ;;; -------- Butterworth filters (see also further below -- make-butter-lp et al) ;;; ;; translated from CLM butterworth.cl: ;; ;; Sam Heisz, January 1998 ;; inspired by some unit generators written for Csound by Paris Smaragdis ;; who based his work on formulas from ;; Charles Dodge, Computer music: synthesis, composition, and performance. (define butter filter) (define make-butter-high-pass (let ((documentation "(make-butter-high-pass freq) makes a Butterworth filter with high pass cutoff at 'freq'")) (lambda (fq) ;; this is the same as iir-low-pass-2 below with 'din' set to (sqrt 2.0) -- similarly with the others (let* ((r (tan (/ (* pi fq) (srate)))) (r2 (* r r)) (c1 (/ 1.0 (+ 1.0 (* r (sqrt 2.0)) r2)))) (make-filter 3 (float-vector c1 (* -2.0 c1) c1) (float-vector 0.0 (* 2.0 (- r2 1.0) c1) (* (- (+ 1.0 r2) (* r (sqrt 2.0))) c1))))))) (define make-butter-low-pass (let ((documentation "(make-butter-low-pass freq) makes a Butterworth filter with low pass cutoff at 'freq'. The result \ can be used directly: (filter-sound (make-butter-low-pass 500.0)), or via the 'butter' generator")) (lambda (fq) (let* ((r (/ 1.0 (tan (/ (* pi fq) (srate))))) (r2 (* r r)) (c1 (/ 1.0 (+ 1.0 (* r (sqrt 2.0)) r2)))) (make-filter 3 (float-vector c1 (* 2.0 c1) c1) (float-vector 0.0 (* 2.0 (- 1.0 r2) c1) (* (- (+ 1.0 r2) (* r (sqrt 2.0))) c1))))))) (define make-butter-band-pass (let ((documentation "(make-butter-band-pass freq band) makes a bandpass Butterworth filter with low edge at 'freq' and width 'band'")) (lambda (fq bw) (let ((c (/ 1.0 (tan (/ (* pi bw) (srate)))))) (let ((d (* 2.0 (cos (/ (* 2.0 pi fq) (srate))))) (c1 (/ 1.0 (+ 1.0 c)))) (make-filter 3 (float-vector c1 0.0 (- c1)) (float-vector 0.0 (* (- c) d c1) (* (- c 1.0) c1)))))))) (define make-butter-band-reject (let ((documentation "(make-butter-band-reject freq band) makes a band-reject Butterworth filter with low edge at 'freq' and width 'band'")) (lambda (fq bw) (let* ((c (tan (/ (* pi bw) (srate)))) (c1 (/ 1.0 (+ 1.0 c))) (c2 (* c1 -2.0 (cos (/ (* 2.0 pi fq) (srate)))))) (make-filter 3 (float-vector c1 c2 c1) (float-vector 0.0 c2 (* (- 1.0 c) c1))))))) ;;; simplest use is (filter-sound (make-butter-low-pass 500.0)) ;;; see also effects.scm ;;; from "DSP Filter Cookbook" by Lane et al, Prompt Pubs, 2001 ;;; ;;; use with the filter generator ;;; (define gen (make-iir-high-pass-2 1000)) ;;; (filter gen 1.0) ;;; etc (define make-biquad (let ((documentation "(make-biquad a0 a1 a2 b1 b2) returns a biquad filter (use with the CLM filter gen)")) (lambda (a0 a1 a2 b1 b2) (make-filter 3 (float-vector a0 a1 a2) (float-vector 0.0 b1 b2))))) (define (make-local-biquad a0 a1 a2 gamma beta) (make-biquad a0 a1 a2 (* -2.0 gamma) (* 2.0 beta))) (define* (make-iir-low-pass-2 fc din) ; din=(sqrt 2.0) for example (suggested range 0.2.. 10) (let* ((theta (/ (* 2 pi fc) *clm-srate*)) (beta (let ((d (* (sin theta) (/ (or din (sqrt 2.0)) 2)))) (* 0.5 (/ (- 1.0 d) (+ 1.0 d))))) (gamma (* (+ 0.5 beta) (cos theta))) (alpha (* 0.5 (- (+ 0.5 beta) gamma)))) (make-local-biquad alpha (* 2.0 alpha) alpha gamma beta))) (define* (make-iir-high-pass-2 fc din) (let* ((theta (/ (* 2 pi fc) *clm-srate*)) (beta (let ((d (* (sin theta) (/ (or din (sqrt 2.0)) 2)))) (* 0.5 (/ (- 1.0 d) (+ 1.0 d))))) (gamma (* (+ 0.5 beta) (cos theta))) (alpha (* 0.5 (+ 0.5 beta gamma)))) (make-local-biquad alpha (* -2.0 alpha) alpha gamma beta))) (define (make-iir-band-pass-2 f1 f2) (let* ((theta (/ (* 2 pi (sqrt (* f1 f2))) *clm-srate*)) (beta (let ((t2 (tan (/ theta (* 2 (/ (sqrt (* f1 f2)) (- f2 f1))))))) (* 0.5 (/ (- 1.0 t2) (+ 1.0 t2)))))) (let ((gamma (* (+ 0.5 beta) (cos theta))) (alpha (- 0.5 beta))) (make-local-biquad alpha 0.0 (- alpha) gamma beta)))) (define (make-iir-band-stop-2 f1 f2) (let* ((theta (/ (* 2 pi (sqrt (* f1 f2))) *clm-srate*)) (beta (let ((t2 (tan (/ theta (* 2 (/ (sqrt (* f1 f2)) (- f2 f1))))))) (* 0.5 (/ (- 1.0 t2) (+ 1.0 t2)))))) (let ((gamma (* (+ 0.5 beta) (cos theta))) (alpha (+ 0.5 beta))) (make-local-biquad alpha (* -2.0 gamma) alpha gamma beta)))) #| (define* (old-make-eliminate-hum (hum-freq 60.0) (hum-harmonics 5) (bandwidth 10)) (let ((gen (make-vector hum-harmonics))) (do ((i 0 (+ i 1))) ((= i hum-harmonics)) (let ((center (* (+ i 1.0) hum-freq)) (b2 (* 0.5 bandwidth))) (set! (gen i) (make-iir-band-stop-2 (- center b2) (+ center b2))))) gen)) (define (old-eliminate-hum gen x0) (do ((i 0 (+ i 1))) ((= i (length gen)) x0) (set! x0 (filter (vector-ref gen i) x0)))) ; "cascade" n filters ;;; (let ((hummer (old-make-eliminate-hum))) (map-channel (lambda (x) (old-eliminate-hum hummer x)))) |# ;;; the new form is creating a function/closure of the form: ;;; (let ((g0 (make-iir-band-stop-2 c00 c01)) (g1 (make-iir-band-stop-2 c10 c11))) (lambda (y) (filter g1 (filter g0 y)))) (define-macro* (make-eliminate-hum (hum-freq 60.0) (hum-harmonics 5) (bandwidth 10)) `(let ((body 'y) (closure ())) (do ((i 0 (+ i 1))) ((= i ,hum-harmonics)) (let ((filt (string->symbol (format #f "g~D" i))) (center (* (+ i 1.0) ,hum-freq)) (b2 (* 0.5 ,bandwidth))) (set! body (list 'filter filt body)) (set! closure (cons (list filt (list 'make-iir-band-stop-2 (- center b2) (+ center b2))) closure)))) (apply let closure `((lambda (y) ,body))))) ;;; (map-channel (make-eliminate-hum)) (define (make-peaking-2 f1 f2 m) ;; bandpass, m is gain at center of peak ;; use map-channel with this one (not clm-channel or filter) (let ((flt (let* ((theta (/ (* 2 pi (sqrt (* f1 f2))) *clm-srate*)) (beta (let ((t2 (* (/ 4.0 (+ m 1)) (tan (/ theta (* 2 (/ (sqrt (* f1 f2)) (- f2 f1)))))))) (* 0.5 (/ (- 1.0 t2) (+ 1.0 t2)))))) (let ((gamma (* (+ 0.5 beta) (cos theta))) (alpha (- 0.5 beta))) (make-local-biquad alpha 0.0 (- alpha) gamma beta)))) (m1 (- m 1.0))) (lambda (x) (+ x (* m1 (filter flt x)))))) (define cascade->canonical (let ((documentation "(cascade->canonical A) converts a list of cascade coeffs (float-vectors with 3 entries) to canonical form")) ;; from Orfanidis "Introduction to Signal Processing" (lambda (A) (define (conv M h L x y) ;; x * h -> y (do ((n 0 (+ n 1))) ((= n (+ L M))) (let ((sum 0.0) (start (max 0 (- n L 1))) (end (min n M))) (do ((m start (+ m 1))) ((> m end)) (set! sum (+ sum (* (h m) (x (- n m)))))) (set! (y n) sum)))) (let ((K (length A))) (let ((d (make-float-vector (+ 1 (* 2 K)))) (a1 (make-float-vector (+ 1 (* 2 K))))) (set! (a1 0) 1.0) (do ((i 0 (+ i 1))) ((= i K)) (conv 2 (A i) (+ 1 (* 2 i)) a1 d) (copy d a1 0 (+ 3 (* 2 i)))) a1))))) (define make-butter-lp (let ((documentation "(make-butter-lp M fc) returns a butterworth low-pass filter; its order is 'M' * 2, 'fc' is the cutoff frequency in Hz")) (lambda (M fc) (let ((theta (/ (* 2 pi fc) *clm-srate*))) (do ((xcoeffs ()) (ycoeffs ()) (st (sin theta)) (ct (cos theta)) (k 1 (+ k 1))) ((> k M) (make-filter (+ 1 (* 2 M)) (cascade->canonical xcoeffs) (cascade->canonical ycoeffs))) (let* ((beta (let ((d (* st (sin (/ (* pi (- (* 2 k) 1)) (* 4 M)))))) (* 0.5 (/ (- 1.0 d) (+ 1.0 d))))) (gamma (* ct (+ 0.5 beta))) (alpha (* 0.25 (- (+ 0.5 beta) gamma)))) (set! xcoeffs (cons (float-vector (* 2 alpha) (* 4 alpha) (* 2 alpha)) xcoeffs)) (set! ycoeffs (cons (float-vector 1.0 (* -2.0 gamma) (* 2.0 beta)) ycoeffs)))))))) (define make-butter-hp (let ((documentation "(make-butter-hp M fc) returns a butterworth high-pass filter; its order is 'M' * 2, 'fc' is the cutoff frequency in Hz")) (lambda (M fc) (let ((theta (/ (* 2 pi fc) *clm-srate*))) (do ((xcoeffs ()) (ycoeffs ()) (st (sin theta)) (ct (cos theta)) (k 1 (+ k 1))) ((> k M) (make-filter (+ 1 (* 2 M)) (cascade->canonical xcoeffs) (cascade->canonical ycoeffs))) (let* ((beta (let ((d (* st (sin (/ (* pi (- (* 2 k) 1)) (* 4 M)))))) (* 0.5 (/ (- 1.0 d) (+ 1.0 d))))) (gamma (* ct (+ 0.5 beta))) (alpha (* 0.25 (+ 0.5 beta gamma)))) (set! xcoeffs (cons (float-vector (* 2 alpha) (* -4 alpha) (* 2 alpha)) xcoeffs)) (set! ycoeffs (cons (float-vector 1.0 (* -2.0 gamma) (* 2.0 beta)) ycoeffs)))))))) (define make-butter-bp (let ((documentation "(make-butter-bp M f1 f2) returns a butterworth band-pass filter; its order is 'M' * 2, 'f1' and 'f2' are the band edge frequencies in Hz")) (lambda (M f1 f2) (let* ((f0 (sqrt (* f1 f2))) (theta0 (/ (* 2 pi f0) *clm-srate*)) (de (let ((Q (/ f0 (- f2 f1)))) (/ (* 2 (tan (/ theta0 (* 2 Q)))) (sin theta0))))) (do ((xcoeffs ()) (ycoeffs ()) (de2 (/ de 2)) (tn0 (tan (* theta0 0.5))) (i 1 (+ i 1)) (k 1) (j 1)) ((> i M) (make-filter (+ 1 (* 2 M)) (cascade->canonical xcoeffs) (cascade->canonical ycoeffs))) (let* ((Dk (* 2 (sin (/ (* pi (- (* 2 k) 1)) (* 2 M))))) (dk1 (let ((Ak (/ (+ 1 (* de2 de2)) (* Dk de2)))) (sqrt (/ (* de Dk) (+ Ak (sqrt (- (* Ak Ak) 1))))))) (Wk (let ((Bk (* de2 (/ Dk dk1)))) (real-part (+ Bk (sqrt (- (* Bk Bk) 1.0)))))) ; fp inaccuracies causing tiny (presumably bogus) imaginary part here (thetajk (* 2 (if (= j 1) (atan tn0 Wk) (atan (* tn0 Wk))))) (betajk (* 0.5 (/ (- 1.0 (* 0.5 dk1 (sin thetajk))) (+ 1.0 (* 0.5 dk1 (sin thetajk))))))) (let ((gammajk (* (+ 0.5 betajk) (cos thetajk))) (alphajk (let ((wk2 (/ (- Wk (/ 1.0 Wk)) dk1))) (* 0.5 (- 0.5 betajk) (sqrt (+ 1.0 (* wk2 wk2))))))) (set! xcoeffs (cons (float-vector (* 2 alphajk) 0.0 (* -2 alphajk)) xcoeffs)) (set! ycoeffs (cons (float-vector 1.0 (* -2.0 gammajk) (* 2.0 betajk)) ycoeffs)))) (if (= j 1) (set! j 2) (begin (set! k (+ k 1)) (set! j 1)))))))) (define make-butter-bs (let ((documentation "(make-butter-bs M f1 f2) returns a butterworth band-stop filter; its order is 'M' * 2, 'f1' and 'f2' are the band edge frequencies in Hz")) (lambda (M f1 f2) (let* ((f0 (sqrt (* f1 f2))) (theta0 (/ (* 2 pi f0) *clm-srate*)) (de (let ((Q (/ f0 (- f2 f1)))) (/ (* 2 (tan (/ theta0 (* 2 Q)))) (sin theta0))))) (do ((xcoeffs ()) (ycoeffs ()) (de2 (/ de 2)) (ct (cos theta0)) (tn0 (tan (* theta0 0.5))) (i 1 (+ i 1)) (k 1) (j 1)) ((> i M) (make-filter (+ 1 (* 2 M)) (cascade->canonical xcoeffs) (cascade->canonical ycoeffs))) (let* ((Dk (* 2 (sin (/ (* pi (- (* 2 k) 1)) (* 2 M))))) (dk1 (let ((Ak (/ (+ 1 (* de2 de2)) (* Dk de2)))) (sqrt (/ (* de Dk) (+ Ak (sqrt (- (* Ak Ak) 1))))))) (thetajk (let ((Wk (let ((Bk (* de2 (/ Dk dk1)))) (real-part (+ Bk (sqrt (- (* Bk Bk) 1.0))))))) (* 2 (if (= j 1) (atan tn0 Wk) (atan (* tn0 Wk)))))) (betajk (* 0.5 (/ (- 1.0 (* 0.5 dk1 (sin thetajk))) (+ 1.0 (* 0.5 dk1 (sin thetajk))))))) (let ((gammajk (* (+ 0.5 betajk) (cos thetajk))) (alphajk (/ (* 0.5 (+ 0.5 betajk) (- 1.0 (cos thetajk))) (- 1.0 ct)))) (set! xcoeffs (cons (float-vector (* 2 alphajk) (* -4 ct alphajk) (* 2 alphajk)) xcoeffs)) (set! ycoeffs (cons (float-vector 1.0 (* -2.0 gammajk) (* 2.0 betajk)) ycoeffs)))) (if (= j 1) (set! j 2) (begin (set! k (+ k 1)) (set! j 1)))))))) ;;; -------- notch filters (define* (make-notch-frequency-response cur-srate freqs (notch-width 2)) (let ((freq-response (list 1.0 0.0))) (for-each (lambda (i) (set! freq-response (cons 1.0 (cons (/ (* 2 (- i notch-width)) cur-srate) freq-response))) ; left upper y resp hz (set! freq-response (cons 0.0 (cons (/ (* 2 (- i (/ notch-width 2))) cur-srate) freq-response))) ; left bottom y resp hz (set! freq-response (cons 0.0 (cons (/ (* 2 (+ i (/ notch-width 2))) cur-srate) freq-response))) ; right bottom y resp hz (set! freq-response (cons 1.0 (cons (/ (* 2 (+ i notch-width)) cur-srate) freq-response)))) ; right upper y resp hz freqs) (set! freq-response (cons 1.0 (cons 1.0 freq-response))) (reverse freq-response))) (define notch-channel (let ((documentation "(notch-channel freqs filter-order beg dur snd chn edpos (truncate #t) (notch-width 2)) -> notch filter removing freqs")) (lambda* (freqs filter-order beg dur snd chn edpos (truncate #t) (notch-width 2)) (filter-channel (make-notch-frequency-response (* 1.0 (srate snd)) freqs notch-width) (or filter-order (min (framples snd chn) (expt 2 (floor (log (/ (srate snd) notch-width) 2))))) beg dur snd chn edpos truncate (format #f "notch-channel '~A ~A ~A ~A" freqs filter-order beg dur))))) (define notch-sound (let ((documentation "(notch-sound freqs filter-order snd chn (notch-width 2)) -> notch filter removing freqs")) (lambda* (freqs filter-order snd chn (notch-width 2)) (filter-sound (make-notch-frequency-response (* 1.0 (srate snd)) freqs notch-width) (or filter-order (min (framples snd chn) (expt 2 (floor (log (/ (srate snd) notch-width) 2))))) snd chn #f (format #f "notch-channel '~A ~A 0 #f" freqs filter-order))))) (define notch-selection (let ((documentation "(notch-selection freqs filter-order (notch-width 2)) -> notch filter removing freqs")) (lambda* (freqs filter-order (notch-width 2)) (if (selection?) (filter-selection (make-notch-frequency-response (* 1.0 (selection-srate)) freqs notch-width) (or filter-order (min (selection-framples) (expt 2 (floor (log (/ (selection-srate) notch-width) 2)))))))))) ;; apparently I'm using powers of 2 here so that mus_make_fir_coeffs, called from get_filter_coeffs, can use an fft ;; the others use the fft internally for the fir filter, but not filter-selection ;;; -------- fractional Fourier Transform, z transform ;;; ;;; translated from the fxt package of Joerg Arndt (define fractional-fourier-transform (let ((documentation "(fractional-fourier-transform real imaginary n angle) performs a fractional Fourier transform on data; if angle=1.0, you get a normal Fourier transform")) (lambda (fr fi n v) ;; this is the slow (dft) form ;; v=1 -> normal fourier transform (do ((hr (make-float-vector n)) (hi (make-float-vector n)) (ph0 (/ (* v 2 pi) n)) (w 0 (+ 1 w)) (sr 0.0 0.0) (si 0.0 0.0)) ((= w n) (list hr hi)) (do ((k 0 (+ k 1))) ((= k n)) (let ((phase (* ph0 k w))) (let ((c (cos phase)) (s (sin phase)) (x (fr k)) (y (fi k))) (let ((r (- (* x c) (* y s))) (i (+ (* y c) (* x s)))) (set! sr (+ sr r)) (set! si (+ si i)))))) (set! (hr w) sr) (set! (hi w) si))))) (define z-transform (let ((documentation "(z-transform data n z) performs a Z transform on data; if z=e^2*pi*j/n you get a Fourier transform; complex results in returned vector")) (lambda (f n z) ;; using vector to allow complex sums (z=e^2*pi*i/n -> fourier transform) ;; (z-transform data n (exp (complex 0.0 (* (/ 2.0 n) pi)))) (let ((res ((if (float-vector? f) make-float-vector make-vector) n))) (do ((w 0 (+ 1 w))) ((= w n)) (do ((sum 0.0) (t 1.0) (m (expt z w)) ;; -w?? there seems to be confusion here -- slowzt.cc in the fxt package uses +w (k 0 (+ k 1))) ((= k n) (set! (res w) sum)) (set! sum (+ sum (* (f k) t))) (set! t (* t m)))) res)))) ;;; -------- slow Hartley transform (define dht (let ((documentation "(dht data) returns the Hartley transform of 'data'.")) (lambda (data) ;; taken from Perry Cook's SignalProcessor.m (the slow version of the Hartley transform) (let ((len (length data)) ) (do ((arr (make-float-vector len)) (w (/ (* 2.0 pi) len)) (i 0 (+ i 1))) ((= i len) arr) (do ((j 0 (+ j 1))) ((= j len)) (set! (arr i) (+ (arr i) (* (data j) (+ (cos (* i j w)) (sin (* i j w)))))))))))) (define find-sine (let ((documentation "(find-sine freq beg dur snd) returns the amplitude and initial-phase (for sin) at freq")) (lambda* (freq beg dur snd) (let ((incr (/ (* freq 2 pi) (srate snd))) (sw 0.0) (cw 0.0) (reader (make-sampler beg snd)) (samp 0.0)) (do ((i 0 (+ i 1)) ; this could also use edot-product (x 0.0 (+ x incr))) ((= i dur)) (set! samp (next-sample reader)) (set! sw (+ sw (* samp (sin x)))) (set! cw (+ cw (* samp (cos x))))) (list (* 2 (/ (sqrt (+ (* sw sw) (* cw cw))) dur)) (atan cw sw)))))) ;;; this is a faster version of find-sine using the "Goertzel algorithm" taken from R Lyons "Understanding DSP" p 529 ;;; it returns the same result as find-sine above if you take (* 2 (/ (goertzel...) dur)) -- see snd-test.scm examples (define goertzel (let ((documentation "(goertzel freq beg dur snd) returns the amplitude of the 'freq' spectral component")) (lambda* (freq (beg 0) dur snd) (let* ((rfreq (/ (* 2.0 pi freq) (srate snd))) (cs (* 2.0 (cos rfreq)))) (let ((reader (make-sampler beg snd 0)) (len (- (if (number? dur) dur (- (framples snd 0) beg)) 2)) (flt (make-two-pole 1.0 (- cs) 1.0))) (do ((i 0 (+ i 1))) ((= i len)) (two-pole flt (next-sample reader))) (let ((y1 (two-pole flt (next-sample reader))) (y0 (two-pole flt (next-sample reader)))) (magnitude (- y0 (* y1 (exp (complex 0.0 (- rfreq)))))))))))) #| ;; old version: (let ((y2 0.0) (y1 0.0) (y0 0.0) (reader (make-sampler beg snd 0)) (len (if (number? dur) dur (- (framples snd 0) beg)))) (do ((i 0 (+ i 1))) ((= i len)) (set! y2 y1) (set! y1 y0) (set! y0 (+ (- (* y1 cs) y2) (next-sample reader)))) |# (define make-spencer-filter (let ((documentation "(make-spencer-filter) is a version of make-fir-filter; it returns one of the standard smoothing filters from \ the era when computers were human beings")) (lambda () (make-fir-filter 15 (apply float-vector (map (lambda (n) (/ n 320.0)) '(-3 -6 -5 3 21 46 67 74 67 46 21 3 -5 -6 -3))))))) ;;; -------- any-random ;;; ;;; arbitrary random number distributions via the "rejection method" (define* (any-random amount e) (if (= amount 0.0) 0.0 (if (not e) (random amount) (let next-random () (let ((x (random (* 1.0 (e (- (length e) 2))))) (y (random 1.0))) (if (<= y (envelope-interp x e)) x (next-random))))))) (define (gaussian-distribution s) (do ((e ()) (den (* 2.0 s s)) (i 0 (+ i 1)) (x 0.0 (+ x .05)) (y -4.0 (+ y .4))) ((= i 21) (reverse e)) (set! e (cons (exp (- (/ (* y y) den))) (cons x e))))) (define (pareto-distribution a) (do ((e ()) (scl (/ (expt 1.0 (+ a 1.0)) a)) (i 0 (+ i 1)) (x 0.0 (+ x .05)) (y 1.0 (+ y .2))) ((= i 21) (reverse e)) (set! e (cons (* scl (/ a (expt y (+ a 1.0)))) (cons x e))))) ;(map-channel (lambda (y) (any-random 1.0 '(0 1 1 1)))) ; uniform distribution ;(map-channel (lambda (y) (any-random 1.0 '(0 0 0.95 0.1 1 1)))) ; mostly toward 1.0 ;(let ((g (gaussian-distribution 1.0))) (map-channel (lambda (y) (any-random 1.0 g)))) ;(let ((g (pareto-distribution 1.0))) (map-channel (lambda (y) (any-random 1.0 g)))) ;;; this is the inverse integration function used by CLM to turn a distribution function into a weighting function (define* (inverse-integrate dist (data-size 512) (e-size 50)) (let ((sum (cadr dist)) (x0 (car dist))) (let ((first-sum sum) (data (make-float-vector data-size)) (e ()) (xincr (/ (- (dist (- (length dist) 2)) x0) e-size))) (do ((i 0 (+ i 1)) (x x0 (+ x xincr))) ((> i e-size)) (set! e (cons x (cons sum e))) (set! sum (+ sum (envelope-interp x dist)))) (let ((incr (/ (- (cadr e) first-sum) (- data-size 1)))) (set! e (reverse e)) (do ((i 0 (+ i 1)) (x first-sum (+ x incr))) ((= i data-size)) (set! (data i) (envelope-interp x e))) data)))) (define (gaussian-envelope s) (do ((e ()) (den (* 2.0 s s)) (i 0 (+ i 1)) (x -1.0 (+ x .1)) (y -4.0 (+ y .4))) ((= i 21) (reverse e)) (set! e (cons (exp (- (/ (* y y) den))) (cons x e))))) ;;; (make-rand :envelope (gaussian-envelope 1.0)) ;;; ---------------- Julius Smith stuff ---------------- ;;; ;;; these are from "Mathematics of the DFT", W3K Pubs (define channel-mean ; / n (let ((documentation "(channel-mean snd chn) returns the average of the samples in the given channel: /n")) (lambda* (snd chn) (let ((N (framples snd chn)) (reader (make-sampler 0 snd chn)) (incr (make-one-pole 1.0 -1.0))) (do ((i 0 (+ i 1))) ((= i N)) (one-pole incr (next-sample reader))) (/ (one-pole incr 0.0) N))))) (define channel-total-energy ; (let ((documentation "(channel-total-energy snd chn) returns the sum of the squares of all the samples in the given channel: ")) (lambda* (snd chn) (let ((data (samples 0 (framples snd chn) snd chn))) (dot-product data data))))) #| (let ((sum 0.0) (N (framples snd chn)) (reader (make-sampler 0 snd chn))) (do ((i 0 (+ i 1))) ((= i N)) (let ((y (next-sample reader))) (set! sum (+ sum (* y y))))) sum)) |# (define channel-average-power ; / n (let ((documentation "(channel-average-power snd chn) returns the average power in the given channel: /n")) (lambda* (snd chn) (/ (channel-total-energy snd chn) (framples snd chn))))) (define channel-rms ; sqrt( / n) (let ((documentation "(channel-rms snd chn) returns the RMS value of the samples in the given channel: sqrt(/n)")) (lambda* (snd chn) (sqrt (channel-average-power snd chn))))) (define channel-variance ; "sample-variance" might be better, - ( / n) ^ 2 with quibbles (let ((documentation "(channel-variance snd chn) returns the sample variance in the given channel: -((/ n)^2")) (lambda* (snd chn) (let ((mu (let ((N (framples snd chn))) (* (/ N (- N 1)) (channel-mean snd chn))))) ; avoid bias sez JOS (- (channel-total-energy snd chn) (* mu mu)))))) (define channel-norm ; sqrt() (let ((documentation "(channel-norm snd chn) returns the norm of the samples in the given channel: sqrt()")) (lambda* (snd chn) (sqrt (channel-total-energy snd chn))))) (define channel-lp (let ((documentation "(channel-lp p snd chn) returns the Lp norm of the samples in the given channel")) (lambda* (p snd chn) (let ((incr (make-one-pole 1.0 -1.0)) (N (framples snd chn)) (reader (make-sampler 0 snd chn))) (do ((i 0 (+ i 1))) ((= i N)) (one-pole incr (expt (abs (next-sample reader)) p))) (expt (one-pole incr 0.0) (/ 1.0 p)))))) (define channel-lp-inf maxamp) #| "(channel-lp-inf snd chn) returns the maxamp in the given channel (the name is just math jargon for maxamp)" (let ((mx 0.0) (N (framples snd chn)) (reader (make-sampler 0 snd chn))) (do ((i 0 (+ i 1))) ((= i N)) (set! mx (max mx (abs (next-sample reader))))) mx)) |# (define channel2-inner-product ; (let ((documentation "(channel2-inner-product s1 c1 s2 c2) returns the inner-product of the two channels: ")) (lambda (s1 c1 s2 c2) (dot-product (samples 0 (framples s1 c1) s1 c1) (samples 0 (framples s1 c1) s2 c2))))) #| (let ((N (framples s1 c1)) (sum 0.0) (r1 (make-sampler 0 s1 c1)) (r2 (make-sampler 0 s2 c2))) (do ((i 0 (+ i 1))) ((= i N)) (set! sum (+ sum (* (r1) (r2))))) sum)) |# (define channel2-angle ; acos( / (sqrt() * sqrt())) (let ((documentation "(channel2-angle s1 c1 s2 c2) treats the two channels as vectors, returning the 'angle' between them: acos(/(sqrt()*sqrt()))")) (lambda (s1 c1 s2 c2) (let ((inprod (channel2-inner-product s1 c1 s2 c2)) (norm1 (channel-norm s1 c1)) (norm2 (channel-norm s2 c2))) (acos (/ inprod (* norm1 norm2))))))) (define channel2-orthogonal? ; == 0 (let ((documentation "(channel2-orthogonal? s1 c1 s2 c2) returns #t if the two channels' inner-product is 0: ==0")) (lambda (s1 c1 s2 c2) (= (channel2-inner-product s1 c1 s2 c2) 0.0)))) (define channel2-coefficient-of-projection ; s1,c1 = x, s2,c2 = y, / (let ((documentation "(channel2-coefficient-of-projection s1 c1 s2 c2) returns /")) (lambda (s1 c1 s2 c2) (/ (channel2-inner-product s1 c1 s2 c2) (channel-total-energy s1 c1))))) ;;; -------- end of JOS stuff -------- #| (define channel-distance-1 ; sqrt() (let ((documentation "(channel-distance s1 c1 s2 c2) returns the euclidean distance between the two channels: sqrt()")) (lambda* ((s1 0) (c1 0) (s2 1) (c2 0)) (let ((r1 (make-sampler 0 s1 c1)) (r2 (make-sampler 0 s2 c2)) (sum 0.0) (N (min (framples s1 c1) (framples s2 c2))) (diff 0.0)) (do ((i 0 (+ i 1))) ((= i N)) (set! diff (- (r1) (r2))) (set! sum (+ sum (* diff diff)))) (sqrt sum))))) |# (define channel-distance ; sqrt() (let ((documentation "(channel-distance s1 c1 s2 c2) returns the euclidean distance between the two channels: sqrt()")) (lambda* ((s1 0) (c1 0) (s2 1) (c2 0)) (let ((N (min (framples s1 c1) (framples s2 c2)))) (let ((data1 (samples 0 N s1 c1)) (data2 (samples 0 N s2 c2))) (float-vector-subtract! data1 data2) (sqrt (dot-product data1 data1))))))) (define periodogram (let ((documentation "(periodogram N) displays an 'N' point Bartlett periodogram of the samples in the current channel")) (lambda (N) (let ((N2 (* 2 N))) (let ((len (framples)) (average-data (make-float-vector N)) (rd (make-sampler 0)) (rl (make-float-vector N2)) (im (make-float-vector N2))) (do ((i 0 (+ i N))) ((>= i len)) (float-vector-scale! rl 0.0) (float-vector-scale! im 0.0) (do ((k 0 (+ k 1))) ((= k N)) (float-vector-set! rl k (read-sample rd))) (mus-fft rl im) (float-vector-multiply! rl rl) (float-vector-multiply! im im) (float-vector-add! average-data (float-vector-add! rl im))) ;; or add snd-spectrum results (graph (float-vector-scale! average-data (/ 1.0 (ceiling (/ len N)))))))))) ;;; -------- ssb-am friends (define shift-channel-pitch (let ((documentation "(shift-channel-pitch freq (order 40) (beg 0) dur snd chn edpos) uses the ssb-am CLM generator to \ shift the given channel in pitch without changing its length. The higher 'order', the better usually.")) (lambda* (freq (order 40) (beg 0) dur snd chn edpos) ;; higher order = better cancellation (let ((gen (make-ssb-am freq order))) (map-channel (lambda (y) (ssb-am gen y)) beg dur snd chn edpos (format #f "shift-channel-pitch ~A ~A ~A ~A" freq order beg dur)))))) (define hz->2pi (let ((documentation "(hz->2pi freq) is like hz->radians but uses the current sound's srate, not mus-srate")) (lambda (freq) (/ (* 2 pi freq) (srate))))) (define* (ssb-bank old-freq new-freq pairs (order 40) (bw 50.0) (beg 0) dur snd chn edpos) (let ((ssbs (make-vector pairs)) (bands (make-vector pairs)) (factor (/ (- new-freq old-freq) old-freq))) (do ((i 1 (+ i 1))) ((> i pairs)) (let ((aff (* i old-freq)) (bwf (* bw (+ 1.0 (/ i (* 2 pairs)))))) (set! (ssbs (- i 1)) (make-ssb-am (* i factor old-freq))) (set! (bands (- i 1)) (make-bandpass (hz->2pi (- aff bwf)) (hz->2pi (+ aff bwf)) order)))) (let ((data (channel->float-vector beg dur snd chn edpos))) (let ((len (length data)) (mx (float-vector-peak data))) (let ((summer (make-float-vector len))) (set! *output* summer) (do ((i 0 (+ i 1))) ((= i pairs)) (do ((gen (vector-ref ssbs i)) (filt (vector-ref bands i)) (k 0 (+ k 1))) ((= k len)) (outa k (ssb-am gen (bandpass filt (ina k data)))))) ; outa adds, (ina i v) is the same as (float-vector-ref v i) (set! *output* #f) (float-vector-scale! summer (/ mx (float-vector-peak summer))) (float-vector->channel summer beg len snd chn current-edit-position (format #f "ssb-bank ~A ~A ~A ~A ~A ~A ~A" old-freq new-freq pairs order bw beg dur))))))) ;;; (let ((ind (open-sound "oboe.snd"))) (ssb-bank 550.0 660.0 10)) (define* (ssb-bank-env old-freq new-freq freq-env pairs (order 40) (bw 50.0) (beg 0) dur snd chn edpos) ;; this version adds a frequency envelope ;; (ssb-bank-env 557 880 '(0 0 1 100.0) 7) (let ((ssbs (make-vector pairs)) (bands (make-vector pairs)) (factor (/ (- new-freq old-freq) old-freq)) (frenvs (make-vector pairs))) (do ((i 1 (+ i 1))) ((> i pairs)) (let ((aff (* i old-freq)) (bwf (* bw (+ 1.0 (/ i (* 2 pairs)))))) (set! (ssbs (- i 1)) (make-ssb-am (* i factor old-freq))) (set! (bands (- i 1)) (make-bandpass (hz->2pi (- aff bwf)) (hz->2pi (+ aff bwf)) order)) (set! (frenvs (- i 1)) (make-env freq-env :scaler (hz->radians i) :length (framples))))) (let ((data (channel->float-vector beg dur snd chn edpos))) (let ((len (length data)) (mx (float-vector-peak data))) (let ((summer (make-float-vector len))) (set! *output* summer) (do ((i 0 (+ i 1))) ((= i pairs)) (do ((gen (vector-ref ssbs i)) (filt (vector-ref bands i)) (e (vector-ref frenvs i)) (k 0 (+ k 1))) ((= k len)) (outa k (ssb-am gen (bandpass filt (ina k data)) (env e))))) (set! *output* #f) (float-vector-scale! summer (/ mx (float-vector-peak summer))) (float-vector->channel summer beg len snd chn current-edit-position (format #f "ssb-bank-env ~A ~A '~A ~A ~A ~A ~A ~A" old-freq new-freq freq-env pairs order bw beg dur))))))) ;;; (let ((ind (open-sound "oboe.snd"))) (ssb-bank-env 550 600 '(0 1 1 2) 10)) #| ;;; a "bump function" (Stein and Shakarchi) (define (bumpy) (let* ((x 0.0) (xi (/ 1.0 (framples))) (start 0) (end 1) (scl (exp (/ 4.0 (- end start))))) ; normalize it (map-channel (lambda (y) (let ((val (if (or (<= x start) ; don't divide by zero (>= x end)) 0.0 (* (exp (/ -1.0 (- x start))) (exp (/ -1.0 (- end x))))))) (set! x (+ x xi)) (* scl val)))))) |# ;;; float-vector|channel|spectral-polynomial (define (float-vector-polynomial v coeffs) ;; Horner's rule applied to entire float-vector (let* ((num-coeffs (length coeffs)) (new-v (make-float-vector (length v) (coeffs (- num-coeffs 1))))) (do ((i (- num-coeffs 2) (- i 1))) ((< i 0)) (float-vector-offset! (float-vector-multiply! new-v v) (coeffs i))) new-v)) (define* (channel-polynomial coeffs snd chn) (let ((len (framples snd chn))) (float-vector->channel (float-vector-polynomial (channel->float-vector 0 len snd chn) coeffs) 0 len snd chn #f (format #f "channel-polynomial ~A" (float-vector->string coeffs))))) ;;; (channel-polynomial (float-vector 0.0 .5)) = x*.5 ;;; (channel-polynomial (float-vector 0.0 1.0 1.0 1.0)) = x*x*x + x*x + x ;;; convolution -> * in freq (define* (spectral-polynomial coeffs snd chn) (let* ((len (framples snd chn)) (num-coeffs (length coeffs)) (fft-len (if (< num-coeffs 2) len (expt 2 (ceiling (log (* (- num-coeffs 1) len) 2)))))) (let ((sound (channel->float-vector 0 len snd chn)) (rl1 (make-float-vector fft-len)) (rl2 (make-float-vector fft-len)) (newv (make-float-vector fft-len))) (if (> (coeffs 0) 0.0) (let ((dither (coeffs 0))) (do ((i 0 (+ i 1))) ((= i fft-len)) (float-vector-set! newv i (mus-random dither))))) (if (> num-coeffs 1) (begin (float-vector-add! newv (float-vector-scale! (copy sound) (coeffs 1))) (if (> num-coeffs 2) (let ((peak (maxamp snd chn))) (copy sound rl1) (do ((i 2 (+ i 1))) ((= i num-coeffs)) (copy sound rl2) (convolution rl1 rl2 fft-len) (float-vector-add! newv (float-vector-scale! (copy rl1) (/ (* (coeffs i) peak) (float-vector-peak rl1))))) (float-vector-scale! newv (/ peak (float-vector-peak newv))))))) (float-vector->channel newv 0 (max len (* len (- num-coeffs 1))) snd chn #f (format #f "spectral-polynomial ~A" (float-vector->string coeffs)))))) ;;; ---------------- ;;; SCENTROID ;;; ;;; by Bret Battey ;;; Version 1.0 July 13, 2002 ;;; translated to Snd/Scheme Bill S 19-Jan-05 ;;; ;;; Returns the continuous spectral centroid envelope of a sound. ;;; The spectral centroid is the "center of gravity" of the spectrum, and it ;;; has a rough correlation to our sense of "brightness" of a sound. ;;; ;;; [Beauchamp, J., "Synthesis by spectral amplitude and 'brightness' matching ;;; analyzed musical sounds". Journal of Audio Engineering Society 30(6), 396-406] ;;; ;;; The formula used is: ;;; C = [SUMF(n)A(n)] / [SUMA(n)] ;;; Where j is the number of bins in the analysis, ;;; F(n) is the frequency of a given bin, ;;; A(n) is the magnitude of the given bin. ;;; ;;; If a pitch envelope for the analyzed sound is available, the results ;;; of SCENTROID can be used with the function NORMALIZE-CENTROID, below, ;;; to provide a "normalized spectral centroid". ;;; ;;; DB-FLOOR -- Frames below this decibel level (0 dB = max) will be discarded ;;; and returned with spectral centroid = 0 ;;; ;;; RFREQ -- Rendering frequency. Number of measurements per second. ;;; ;;; FFTSIZE -- FFT window size. Must be a power of 2. 4096 is recommended. (define scentroid (let ((documentation "(scentroid file (beg 0.0) dur (db-floor -40.0) (rfreq 100.0) (fftsize 4096)) returns the spectral centroid envelope of a sound; 'rfreq' is \ the rendering frequency, the number of measurements per second; 'db-floor' is the level below which data will be ignored")) (lambda* (file (beg 0.0) dur (db-floor -40.0) (rfreq 100.0) (fftsize 4096)) (let* ((fsr (srate file)) (start (floor (* beg fsr)))) (let ((incrsamps (floor (/ fsr rfreq))) (end (+ start (if dur (floor (* dur fsr)) (- (framples file) beg))))) (let ((fdr (make-float-vector fftsize)) (fdi (make-float-vector fftsize)) (scl (make-float-vector (/ fftsize 2))) (ones (make-float-vector (/ fftsize 2) 1.0)) (results (make-float-vector (+ 1 (floor (/ (- end start) incrsamps))))) (fft2 (floor (/ fftsize 2))) (binwidth (* 1.0 (/ fsr fftsize))) (rd (make-readin file))) (do ((k 0 (+ k 1))) ((= k fft2)) (float-vector-set! scl k (* k binwidth))) (do ((i start (+ i incrsamps)) (loc 0 (+ 1 loc))) ((>= i end)) (set! (mus-location rd) i) (do ((j 0 (+ j 1))) ((= j fftsize)) (float-vector-set! fdr j (readin rd))) (if (>= (linear->db (sqrt (/ (dot-product fdr fdr) fftsize))) db-floor) (begin (fill! fdi 0.0) (mus-fft fdr fdi fftsize) (rectangular->magnitudes fdr fdi) (set! (results loc) (/ (dot-product scl fdr fft2) (dot-product ones fdr fft2)))))) results)))))) ;;; ---------------- ;;; ;;; invert-filter inverts an FIR filter ;;; ;;; say we previously filtered a sound via (filter-channel (float-vector .5 .25 .125)) ;;; and we want to undo it without using (undo): ;;; (filter-channel (invert-filter (float-vector .5 .25 .125))) ;;; ;;; there are a million gotchas here. The primary one is that the inverse filter ;;; can "explode" -- the coefficients can grow without bound. For example, any ;;; filter returned by spectrum->coeffs above will be a problem (it always returns ;;; a "linear phase" filter). Could this be used to remove reverb? (define invert-filter (let ((documentation "(invert-filter coeffs) tries to return an inverse filter to undo the effect of the FIR filter coeffs.")) (lambda (fcoeffs) (let* ((flen (length fcoeffs)) (coeffs (make-float-vector (+ 32 flen))) ; add room for coeffs to die away (order (length coeffs))) (do ((i 0 (+ i 1))) ((= i flen)) (set! (coeffs i) (fcoeffs i))) (let ((nfilt (make-float-vector order))) (set! (nfilt 0) (/ 1.0 (coeffs 0))) (do ((i 1 (+ i 1))) ((= i order)) (do ((sum 0.0) (j 0 (+ j 1)) (k i (- k 1))) ((= j i) (set! (nfilt i) (/ sum (- (coeffs 0))))) (set! sum (+ sum (* (nfilt j) (coeffs k)))))) nfilt))))) ;;; ---------------- ;;; ;;; Volterra filter ;;; ;;; one of the standard non-linear filters ;;; this version is taken from Monson Hayes "Statistical DSP and Modeling" ;;; it is a slight specialization of the form mentioned by J O Smith and others (define make-volterra-filter (let ((documentation "(make-volterra-filter acoeffs bcoeffs) returns a list for use with volterra-filter, producing one of the standard non-linear filters")) (lambda (acoeffs bcoeffs) (list acoeffs bcoeffs (make-float-vector (max (length acoeffs) (length bcoeffs))))))) (define volterra-filter (let ((documentation "(volterra-filter flt x) takes 'flt', a list returned by make-volterra-filter, and an input 'x', and returns the (non-linear filtered) result")) (lambda (flt x) (let ((as (car flt)) (bs (cadr flt)) (xs (caddr flt))) (let ((x1len (length as)) (x2len (length bs)) (sum 0.0)) (let ((xlen (length xs))) (float-vector-move! xs (- xlen 1) (- xlen 2) #t)) (set! (xs 0) x) (set! sum (dot-product as xs x1len)) (do ((i 0 (+ i 1))) ((= i x2len)) (do ((j i (+ j 1))) ((= j x2len)) (set! sum (+ sum (* (bs j) (xs i) (xs j)))))) sum))))) ;;; (define flt (make-volterra-filter (float-vector .5 .1) (float-vector .3 .2 .1))) ;;; (map-channel (lambda (x) (volterra-filter flt x))) ;;; ---------------- ;;; ;;; harmonicizer (each harmonic is split into a set of harmonics via Chebyshev polynomials) ;;; obviously very similar to ssb-bank above, but splits harmonics individually, rather than pitch-shifting them (define harmonicizer (let ((documentation "(harmonicizer freq coeffs pairs (order 40) (bw 50.0) (beg 0) dur snd chn edpos) splits out each harmonic \ and replaces it with the spectrum given in coeffs")) (lambda* (freq coeffs pairs (order 40) (bw 50.0) (beg 0) dur snd chn edpos) (let ((bands (make-vector pairs)) (pcoeffs (partials->polynomial coeffs)) (peaks (make-vector pairs)) (peaks2 (make-vector pairs)) (flt (make-filter 2 (float-vector 1 -1) (float-vector 0 -0.9))) (old-mx (maxamp)) (startup 40) (len (- (or dur (framples snd chn edpos)) beg))) (let ((summer (make-float-vector len)) (indata (channel->float-vector beg len snd chn edpos))) (do ((i 0 (+ i 1))) ((= i pairs)) (let ((aff (* (+ i 1) freq)) (bwf (* bw (+ 1.0 (/ (+ i 1) (* 2 pairs)))))) (set! (peaks i) (make-moving-max 128)) (set! (peaks2 i) (make-moving-norm 128)) (set! (bands i) (make-bandpass (hz->2pi (- aff bwf)) (hz->2pi (+ aff bwf)) order)))) ;; ignore startup (do ((k 0 (+ k 1))) ((= k startup)) (do ((sum 0.0) (i 0 (+ i 1))) ((= i pairs) (filter flt sum)) (let ((sig (bandpass (vector-ref bands i) (float-vector-ref indata k)))) (set! sum (+ sum (* (moving-max (vector-ref peaks i) sig) (polynomial pcoeffs (* sig (moving-norm (vector-ref peaks2 i) sig))))))))) (set! *output* summer) (do ((pair 0 (+ pair 1))) ((= pair pairs)) (do ((bp (vector-ref bands pair)) (pk (vector-ref peaks pair)) (pk2 (vector-ref peaks2 pair)) (k startup (+ k 1))) ((= k len)) (let ((x (bandpass bp (float-vector-ref indata k)))) (outa k (* (moving-max pk x) (polynomial pcoeffs (* x (moving-norm pk2 x)))))))) ;; we're normalizing the polynomial input so its waveshaping index is more-or-less 1.0 ;; this might work better with len=256, max .1 -- we're assuming a well-behaved signal (set! *output* #f) (do ((k startup (+ k 1))) ((= k len)) (float-vector-set! summer k (filter flt (float-vector-ref summer k)))) (let ((nmx (float-vector-peak summer))) (if (> nmx 0.0) (float-vector-scale! summer (/ old-mx nmx)))) (float-vector->channel summer beg len snd chn)))))) ;;; (harmonicizer 550.0 (list 1 .5 2 .3 3 .2) 10) ;;; ---------------- ;;; ;;; linear sampling rate conversion (define linear-src-channel (let ((documentation "(linear-src-channel sr snd chn) performs sampling rate conversion using linear interpolation.")) (lambda* (sr snd chn) (let ((tempfile (with-sound (:output (snd-tempnam) :srate (srate snd) :to-snd #f) (let ((rd (make-sampler 0 snd chn))) (do ((intrp 0.0) (last (rd)) (next (rd)) (samp 0 (+ samp 1))) ((sampler-at-end? rd)) (outa samp (let ((pos intrp)) (if (>= pos 1.0) (do ((num (floor pos)) (i 0 (+ i 1))) ((= i num) (set! pos (- pos num))) (set! last next) (set! next (read-sample rd)))) (set! intrp (+ pos sr)) (+ last (* pos (- next last)))))))))) (set-samples 0 (- (framples tempfile) 1) tempfile snd chn #t "linear-src" 0 #f #t) ;; first #t=truncate to new length, #f=at current edpos, #t=auto delete temp file )))) ;;; -------- spectrum displayed in various frequency scales (define display-bark-fft ;; click in lisp-graph to change the tick placement choice (let ((snd-color-1 (lambda args (and (defined? 'snd-color) (apply snd-color args))))) (let ((bark-fft-size 0) (bark-tick-function 0) (color1 (snd-color-1 8)) ; selected-data-color (color2 (snd-color-1 2)) ; red (color3 (snd-color-1 4))) ; blue (define (bark f) (let ((f2 (/ f 7500))) (+ (* 13.5 (atan (* .00076 f))) (* 3.5 (atan (* f2 f2)))))) (define (mel f) (* 1127 (log (+ 1.0 (/ f 700.0))))) (define (erb f) (+ 43.0 (* 11.17 (log (/ (+ f 312) (+ f 14675)))))) (define (display-bark-fft-1 hook) (let* ((snd (hook 'snd)) (chn (hook 'chn)) (ls (left-sample snd chn)) (fftlen (floor (expt 2 (ceiling (log (- (+ (right-sample snd chn) 1) ls) 2)))))) (when (> fftlen 0) (let ((data (channel->float-vector ls fftlen snd chn)) (normalized (not (= (transform-normalization snd chn) dont-normalize))) (linear #t)) ; can't currently show lisp graph in dB ;; snd-axis make_axes: WITH_LOG_Y_AXIS, but LINEAR currently in snd-chn.c 3250 (when (float-vector? data) (let ((fftdata (snd-spectrum data ; returns fftlen / 2 data points (fft-window snd chn) fftlen linear (fft-window-beta snd chn) #f normalized))) (when (float-vector? fftdata) (let ((sr (srate snd)) (data-len (length fftdata)) (bark-low (floor (bark 20.0))) (mel-low (floor (mel 20.0))) (erb-low (floor (erb 20.0)))) (let ((mx (float-vector-peak fftdata)) ;; bark settings (bark-frqscl (/ data-len (- (ceiling (bark (* 0.5 sr))) bark-low))) (bark-data (make-float-vector data-len)) ;; mel settings (mel-frqscl (/ data-len (- (ceiling (mel (* 0.5 sr))) mel-low))) (mel-data (make-float-vector data-len)) ;; erb settings (erb-frqscl (/ data-len (- (ceiling (erb (* 0.5 sr))) erb-low))) (erb-data (make-float-vector data-len))) (set! bark-fft-size fftlen) (do ((i 0 (+ i 1))) ((= i data-len)) (let ((val (fftdata i)) (frq (* sr (/ i fftlen)))) (let ((bark-bin (round (* bark-frqscl (- (bark frq) bark-low)))) (mel-bin (round (* mel-frqscl (- (mel frq) mel-low)))) (erb-bin (round (* erb-frqscl (- (erb frq) erb-low))))) (if (and (>= bark-bin 0) (< bark-bin data-len)) (set! (bark-data bark-bin) (+ val (bark-data bark-bin)))) (if (and (>= mel-bin 0) (< mel-bin data-len)) (set! (mel-data mel-bin) (+ val (mel-data mel-bin)))) (if (and (>= erb-bin 0) (< erb-bin data-len)) (set! (erb-data erb-bin) (+ val (erb-data erb-bin))))))) (if normalized (let ((bmx (float-vector-peak bark-data)) (mmx (float-vector-peak mel-data)) (emx (float-vector-peak erb-data))) (if (> (abs (- mx bmx)) .01) (float-vector-scale! bark-data (/ mx bmx))) (if (> (abs (- mx mmx)) .01) (float-vector-scale! mel-data (/ mx mmx))) (if (> (abs (- mx emx)) .01) (float-vector-scale! erb-data (/ mx emx))))) (graph (list bark-data mel-data erb-data) "ignored" 20.0 (* 0.5 sr) 0.0 (if normalized 1.0 (* data-len (y-zoom-slider snd chn))) snd chn #f show-bare-x-axis)))))))) (list color1 color2 color3))) ; tell lisp graph display what colors to use (define (make-bark-labels hook) ;; at this point the x axis has no markings, but there is room for labels and ticks (let* ((snd (hook 'snd)) (chn (hook 'chn)) (old-foreground-color (foreground-color snd chn copy-context)) (axinfo (axis-info snd chn lisp-graph)) (axis-x0 (axinfo 10)) (axis-x1 (axinfo 12)) (axis-y1 (axinfo 11)) (bark-label-font (snd-font 3)) (cr (make-cairo (car (channel-widgets snd chn)))) (scale-position (let ((sr2 (* 0.5 (srate snd)))) (lambda (scale f) (let ((b20 (scale 20.0))) (round (+ axis-x0 (/ (* (- axis-x1 axis-x0) (- (scale f) b20)) (- (scale sr2) b20)))))))) (bark-position (lambda (f) (scale-position bark f))) (mel-position (lambda (f) (scale-position mel f))) (erb-position (lambda (f) (scale-position erb f))) (draw-bark-ticks (let ((minor-tick-len 6) (major-tick-len 12)) (let (;; (tick-y0 axis-y1) (minor-y0 (+ axis-y1 minor-tick-len)) (major-y0 (+ axis-y1 major-tick-len)) (axis-y0 (axinfo 13)) (bark-numbers-font (snd-font 2))) (lambda (bark-function) (if bark-numbers-font (set! (current-font snd chn copy-context) bark-numbers-font)) (draw-line axis-x0 axis-y1 axis-x0 major-y0 snd chn copy-context cr) (let ((i1000 (scale-position bark-function 1000.0)) (i10000 (scale-position bark-function 10000.0))) (draw-line i1000 axis-y1 i1000 major-y0 snd chn copy-context cr) (draw-line i10000 axis-y1 i10000 major-y0 snd chn copy-context cr) (draw-string "20" axis-x0 major-y0 snd chn copy-context cr) (draw-string "1000" (- i1000 12) major-y0 snd chn copy-context cr) (draw-string "10000" (- i10000 24) major-y0 snd chn copy-context cr) (draw-string (format #f "fft size: ~D" bark-fft-size) (+ axis-x0 10) axis-y0 snd chn copy-context cr) (do ((i 100 (+ i 100))) ((= i 1000)) (let ((i100 (scale-position bark-function i))) (draw-line i100 axis-y1 i100 minor-y0 snd chn copy-context cr))) (do ((i 2000 (+ i 1000))) ((= i 10000)) (let ((i1000 (scale-position bark-function i))) (draw-line i1000 axis-y1 i1000 minor-y0 snd chn copy-context cr))))))))) (let ((label-pos (round (+ axis-x0 (* .45 (- axis-x1 axis-x0))))) (label-height 15) (char-width 8)) ;; bark label/ticks (set! (foreground-color snd chn copy-context) color1) (if (= bark-tick-function 0) (draw-bark-ticks bark-position)) (if bark-label-font (set! (current-font snd chn copy-context) bark-label-font)) (draw-string "bark," label-pos (+ axis-y1 label-height) snd chn copy-context cr) ;; mel label/ticks (set! (foreground-color snd chn copy-context) color2) (if (= bark-tick-function 1) (draw-bark-ticks mel-position)) (if bark-label-font (set! (current-font snd chn copy-context) bark-label-font)) (draw-string "mel," (+ (* char-width 6) label-pos) (+ axis-y1 label-height) snd chn copy-context cr) ;; erb label/ticks (set! (foreground-color snd chn copy-context) color3) (if (= bark-tick-function 2) (draw-bark-ticks erb-position)) (if bark-label-font (set! (current-font snd chn copy-context) bark-label-font)) (draw-string "erb" (+ (* char-width 11) label-pos) (+ axis-y1 label-height) snd chn copy-context cr) (free-cairo cr)) (set! (foreground-color snd chn copy-context) old-foreground-color))) ;; mouse click = move to next scale's ticks (define (choose-bark-ticks hook) (if (= (hook 'axis) lisp-graph) (begin (set! bark-tick-function (+ bark-tick-function 1)) (if (> bark-tick-function 2) (set! bark-tick-function 0)) (update-lisp-graph (hook 'snd) (hook 'chn))))) ;; user's view of display-bark-fft function (lambda* (off col1 col2 col3) (if col1 (set! color1 col1)) (if col2 (set! color2 col2)) (if col3 (set! color3 col3)) (if (not off) (begin (hook-push lisp-graph-hook display-bark-fft-1) (hook-push after-lisp-graph-hook make-bark-labels) (hook-push mouse-click-hook choose-bark-ticks) (for-each (lambda (snd) (do ((c 0 (+ 1 c))) ((= c (channels snd))) (update-lisp-graph snd c))) (sounds))) (begin (hook-remove lisp-graph-hook display-bark-fft-1) (hook-remove after-lisp-graph-hook make-bark-labels) (hook-remove mouse-click-hook choose-bark-ticks) (for-each (lambda (snd) (do ((c 0 (+ 1 c))) ((= c (channels snd))) (set! (lisp-graph? snd c) #f))) (sounds)))))))) (define (undisplay-bark-fft) (display-bark-fft #t)) ;;; -------- lpc-coeffs, lpc-predict (define lpc-coeffs (let ((documentation "(lpc-coeffs data n m) returns 'm' LPC coeffients (in a vector) given 'n' data points in the float-vector 'data'")) (lambda (data n m) ;; translated and changed to use 0-based arrays from memcof of NRinC (let ((d (make-float-vector m)) (wk1 (make-float-vector n)) (wk2 (make-float-vector n)) (wkm (make-float-vector n))) (copy data wk1) (do ((j 1 (+ j 1)) (k 0 (+ k 1))) ((= j n)) (float-vector-set! wk2 k (float-vector-ref data j))) (do ((k 0 (+ k 1))) ((= k m) d) (let ((end (- n k 1))) (let ((num (dot-product wk1 wk2 end)) (denom (+ (dot-product wk1 wk1 end) (dot-product wk2 wk2 end)))) (if (not (= denom 0.0)) (set! (d k) (/ (* 2.0 num) denom)))) (let ((d-k (d k))) (do ((i 0 (+ i 1)) (k1 (- k 1) (- k1 1))) ((= i k)) ; first time is skipped presumably (float-vector-set! d i (- (float-vector-ref wkm i) (* d-k (float-vector-ref wkm k1)))))) (if (< k (- m 1)) (let ((end (- n k 2))) (copy d wkm 0 (+ k 1)) (let ((wkm-k (float-vector-ref wkm k)) (old-wk1 (copy wk1))) (do ((j 0 (+ j 1))) ((= j end)) (float-vector-set! wk1 j (- (float-vector-ref wk1 j) (* wkm-k (float-vector-ref wk2 j))))) (do ((j 0 (+ j 1)) (j1 1 (+ j1 1))) ((= j end)) (float-vector-set! wk2 j (- (float-vector-ref wk2 j1) (* wkm-k (float-vector-ref old-wk1 j1)))))))))))))) (define lpc-predict ;; translated and changed to use 0-based arrays from predic of NRinC ;; incoming coeffs are assumed to be in a vector (from lpc-coeffs) (let ((documentation "(lpc-predict data n coeffs m nf clipped) takes the output of lpc-coeffs ('coeffs', a float-vector) and the length thereof ('m'), \ 'n' data points of 'data' (a float-vector), and produces 'nf' new data points (in a float-vector) as its prediction. If 'clipped' is #t, the new data \ is assumed to be outside -1.0 to 1.0.")) (lambda* (data n coeffs m nf clipped) (let ((future (make-float-vector nf)) (reg (make-float-vector m))) (do ((i 0 (+ i 1)) (j (- n 1) (- j 1))) ((= i m)) (set! (reg i) (data j))) (do ((j 0 (+ j 1))) ((= j nf) future) (let ((sum (dot-product coeffs reg m))) (do ((k (- m 1) (- k 1))) ((= k 0)) (set! (reg k) (reg (- k 1)))) ;; added this block (if clipped (set! sum (if (> sum 0.0) (max sum 1.0) (min sum -1.0)))) (set! (reg 0) sum) (set! (future j) sum))))))) ;;; -------- unclip-channel (define unclip-channel (let ((documentation "(unclip-channel snd chn) looks for clipped portions and tries to reconstruct the original using LPC")) (lambda* (snd chn) (let ((clips 0) ; number of clipped portions * 2 (unclipped-max 0.0) (len (framples snd chn)) (data (channel->float-vector 0 #f snd chn)) (clip-size 1000) (clip-data (make-vector 1000 0))) ;; count clipped portions (let ((in-clip #f) (clip-beg 0)) (float-vector-abs! data) (do ((i 0 (+ i 1))) ((= i len)) (if (> (float-vector-ref data i) .9999) ; this sample is clipped (if (not in-clip) (begin (set! in-clip #t) (set! clip-beg i))) (begin ; not clipped (set! unclipped-max (max unclipped-max (float-vector-ref data i))) ; this is not the same as (float-vector-peak ...) (if in-clip (begin (set! in-clip #f) (set! (clip-data clips) clip-beg) (set! (clip-data (+ 1 clips)) (- i 1)) (set! clips (+ clips 2)) (if (> clips clip-size) (let ((new-clip-data (make-vector (* 2 clip-size) 0))) (copy clip-data new-clip-data) (set! clip-data new-clip-data) (set! clip-size (* 2 clip-size)))))))))) (if (<= clips 0) 'no-clips ;; try to restore clipped portions (let ((max-len 0)) (as-one-edit (lambda () (do ((clip 0 (+ clip 2))) ; so go through all... ((>= clip clips)) (let* ((clip-beg (clip-data clip)) ; clip-beg to clip-end inclusive are clipped (clip-end (clip-data (+ 1 clip))) (clip-len (- (+ clip-end 1) clip-beg)) (data-len (max 32 (* clip-len 4)))) (set! max-len (max max-len clip-len)) (let ((forward-data-len data-len) (backward-data-len data-len) (previous-end (if (= clip 0) 0 (clip-data (- clip 1)))) (next-beg (if (< clip (- clips 3)) (clip-data (+ clip 2)) (framples snd chn)))) (if (< (- clip-beg data-len) previous-end) ; current beg - data collides with previous (set! forward-data-len (max 4 (- clip-beg previous-end)))) (if (> (+ clip-end data-len) next-beg) ; current end + data collides with next (set! backward-data-len (max 4 (- next-beg clip-end)))) (let ((forward-predict-len (min (max clip-len (floor (/ forward-data-len 2))) forward-data-len)) (backward-predict-len (min (max clip-len (floor (/ backward-data-len 2))) backward-data-len))) ;; use LPC to reconstruct going both forwards and backwards (let ((future (let ((data (channel->float-vector (- clip-beg forward-data-len) forward-data-len snd chn))) (lpc-predict data forward-data-len (lpc-coeffs data forward-data-len forward-predict-len) forward-predict-len clip-len #f))) (past (let ((rdata (reverse! (channel->float-vector (+ 1 clip-end) backward-data-len snd chn)))) (lpc-predict rdata backward-data-len (lpc-coeffs rdata backward-data-len backward-predict-len) backward-predict-len clip-len #f))) (new-data (make-float-vector clip-len))) (if (> clip-len 1) (do ((i 0 (+ i 1)) (j (- clip-len 1) (- j 1))) ((= i clip-len)) (let ((sn (* 0.5 (+ 1.0 (cos (* pi (/ i (- clip-len 1)))))))) (set! (new-data i) (+ (* sn (future i)) (* (- 1.0 sn) (past j)))))) ;; todo perhaps move this mix dependent on data-lens? ;; todo perhaps special case for 2 samps (what if both 1.0 for example?) ;; todo perhaps if multichannel and channels are correlated and one is not clipped -- use ;; its data to help reconstruct clipped case? (set! (new-data 0) ((if (> (future 0) 0.0) max min) (future 0) (past 0)))) ;; write reconstruction (float-vector->channel new-data clip-beg clip-len snd chn)))))))) (if (> unclipped-max .95) (set! unclipped-max .999)) (scale-channel (/ unclipped-max (maxamp snd chn)) 0 (framples snd chn) snd chn) (list 'max unclipped-max 'clips (/ clips 2) 'max-len max-len))))))) (define unclip-sound (let ((documentation "(unclip-sound snd) applies unclip-channel to each channel of 'snd'.")) (lambda* (snd) (let ((index (or snd (selected-sound) (car (sounds))))) (if (not (sound? index)) (error 'no-such-sound (list "unclip-sound" snd)) (do ((chns (channels index)) (chn 0 (+ 1 chn))) ((= chn chns)) (unclip-channel index chn))))))) (define* (kalman-filter-channel (Q 1.0e-5)) ;; translated from http://www.scipy.org/Cookbook/KalmanFiltering by Andrew Straw (but "R" here is a signal) (let ((size (framples)) (mx (maxamp)) (data (channel->float-vector 0)) (xhat 0.0) (P 1.0) ; any non-zero value ok here (R 0.01) ; first guess (Pminus 0.0) (frm (make-formant :radius (- 1.0 (/ 2000.0 (srate))) :frequency 1000)) (del (make-moving-average 256)) (K 0.0)) (do ((k 1 (+ k 1))) ((= k size)) (let ((datum (data k)) (xhatminus xhat)) (let ((avg (moving-average del (abs (formant frm datum))))) (set! R (/ .000001 (+ avg .001)))) ;; K now goes between say .5 if avg large to almost 0 if avg 0 (R is inverse essentially) ;; so filter lp effect increases as apparent true signal decreases ;; "truth" here is based on vocal resonances (set! (data k) xhatminus) ; filter output (set! Pminus (+ P Q)) (set! K (/ Pminus (+ Pminus R))) (set! xhat (+ xhatminus (* K (- datum xhatminus)))) (set! P (* (- 1.0 K) Pminus)))) (float-vector-scale! data (/ mx (float-vector-peak data))) (float-vector->channel data))) ;;; -------- Savitzky-Golay filter coefficients (FIR filter -- returns float-vector of coeffs centered at float-vector midpoint) ;;; ;;; based on Numerical Recipes in C p 652 (define invert-matrix (let ((documentation "(invert-matrix matrix b (zero 1.0e-7)) inverts 'matrix'")) (lambda* (matrix b (zero 1.0e-7)) ;; translated from Numerical Recipes (gaussj) ;(format () "~%~%invert-matrix n: ~D, ~S, b: ~A, ~S~%" (length matrix) matrix (and b (length b)) b) (call-with-exit (lambda (return) (let ((n (car (vector-dimensions matrix)))) (let ((cols (make-vector n 0)) (rows (make-vector n 0)) (pivots (make-vector n 0))) (do ((i 0 (+ i 1)) (col 0 0) (row 0 0)) ((= i n)) (do ((biggest 0.0) (j 0 (+ j 1))) ((= j n) (if (< biggest zero) (return #f))) ; this can be fooled (floats...): (invert-matrix (make-share-vector (float-vector 1 2 3 3 2 1 4 5 6) (list 3 3))) (if (not (= (pivots j) 1)) (do ((k 0 (+ k 1))) ((= k n)) (if (= (pivots k) 0) (let ((val (abs (matrix j k)))) (if (> val biggest) (begin (set! col k) (set! row j) (set! biggest val)))) (if (> (pivots k) 1) (return #f)))))) (set! (pivots col) (+ (pivots col) 1)) (if (not (= row col)) (let ((temp (if (sequence? b) (b row) 0.0))) (if (sequence? b) (begin (set! (b row) (b col)) (set! (b col) temp))) (do ((k 0 (+ k 1))) ((= k n)) (set! temp (matrix row k)) (set! (matrix row k) (matrix col k)) (set! (matrix col k) temp)))) (set! (cols i) col) (set! (rows i) row) ;; round-off troubles here (if (< (abs (matrix col col)) zero) (return #f)) (let ((inverse-pivot (/ 1.0 (matrix col col)))) (set! (matrix col col) 1.0) (do ((k 0 (+ k 1))) ((= k n)) (set! (matrix col k) (* inverse-pivot (matrix col k)))) (if b (set! (b col) (* inverse-pivot (b col))))) (do ((k 0 (+ k 1))) ((= k n)) (if (not (= k col)) (let ((scl (matrix k col))) (set! (matrix k col) 0.0) (do ((m 0 (+ 1 m))) ((= m n)) (set! (matrix k m) (- (matrix k m) (* scl (matrix col m))))) (if b (set! (b k) (- (b k) (* scl (b col))))))))) (do ((i (- n 1) (- i 1))) ((< i 0)) (if (not (= (rows i) (cols i))) (do ((k 0 (+ k 1))) ((= k n)) (let ((temp (matrix k (rows i)))) (set! (matrix k (rows i)) (matrix k (cols i))) (set! (matrix k (cols i)) temp))))) (list matrix b)))))))) (define (matrix-solve A b) (cond ((invert-matrix A b) => cadr) (else #f))) (define* (make-savitzky-golay-filter size (order 2)) ;assuming symmetric filter (left = right) (if (even? size) (set! size (+ size 1))) (let ((n (/ (- size 1) 2)) (a (make-float-vector (list (+ 1 order) (+ 1 order))))) (do ((i 0 (+ i 1))) ((> i (* order 2))) (let ((sum (if (= i 0) 1.0 0.0))) (if (even? i) (do ((k 1 (+ k 1))) ((> k n)) ;(set! sum (+ sum (expt k i) (expt (- k) i))) ; kinda nuts (set! sum (+ sum (* 2 (expt k i)))))) (let ((m i)) (if (> i (- (* 2 order) i)) (set! m (- (* 2 order) i))) (do ((k (- m) (+ k 2))) ((> k m)) (set! (a (/ (+ i k) 2) (/ (- i k) 2)) sum))))) (let ((b (matrix-solve a (let ((f (make-float-vector (+ order 1)))) (set! (f 0) 1.0) ; set others instead for derivative f))) (result (make-float-vector size))) (do ((k (- n) (+ k 1)) (i 0 (+ i 1))) ((> k n)) (let ((sum (b 0)) (fac 1.0)) (do ((m 1 (+ 1 m))) ((> m order)) (set! fac (* fac k)) (set! sum (+ sum (* (b m) fac)))) (set! (result i) sum))) (make-fir-filter :order size :xcoeffs result)))) (define savitzky-golay-filter fir-filter) #| ;; NRinC examples (2nd ed, p651) :(make-savitzky-golay-filter 5 2) # :(make-savitzky-golay-filter 11 2) # :(make-savitzky-golay-filter 11 4) # :(make-savitzky-golay-filter 25 2) # |# ;;; -------- hard and soft clipping ;;; ;;; from Julius Smith's http://ccrma.stanford.edu/~jos/pasp/Cubic_Soft_Clipper.html (define (hard-clipped x) (max -1.0 (min 1.0 x))) (define (soft-clipped x) (max -0.6667 (min 0.6667 (- x (* 0.3333 x x x))))) ;;; -------- parallel FM spectrum calculator ;(fm-parallel-component 200 2000.0 (list 2000.0 200.0) (list 0.5 1.0) () () #t) (define fm-parallel-component (let ((documentation "(fm-parallel-component freq carrier modfreqs indices () () with-sines) returns the amplitude of \"freq\" in \ the multi-modulator FM case described by the list of modulator frequencies and indices")) (lambda (freq-we-want wc wms inds ns bs using-sine) (if (pair? wms) (let ((index (car inds))) (let ((sum 0.0) (mx (ceiling (* 7 index))) (wm (car wms))) (do ((k (- mx) (+ k 1))) ((>= k mx) sum) (set! sum (+ sum (fm-parallel-component freq-we-want (+ wc (* k wm)) (cdr wms) (cdr inds) (append ns (list k)) (append bs (list index)) using-sine)))))) (if (>= (abs (- freq-we-want (abs wc))) .1) 0.0 (let ((bmult 1.0)) (for-each (lambda (n index) (set! bmult (* bmult (bes-jn n index)))) ns bs) (if (and using-sine (< wc 0.0)) (set! bmult (- bmult))) bmult)))))) ;;; this returns the component in FM with complex index (using-sine ignored for now) ;;; this needs the Bessel functions (gsl or snd-test.scm) (define (fm-complex-component freq-we-want wc wm a b interp using-sine) (let ((sum 0.0) (mxa (ceiling (* 7 a))) (mxb (ceiling (* 7 b)))) (do ((k (- mxa) (+ k 1))) ((>= k mxa)) (do ((j (- mxb) (+ j 1))) ((>= j mxb)) (if (< (abs (- freq-we-want wc (* k wm) (* j wm))) 0.1) (let ((curJI (* (bes-jn k a) (bes-in (abs j) b) (expt 0.0+1.0i j)))) (set! sum (+ sum curJI)) (if (> (magnitude curJI) 0.001) (format () ";fm-complex-component add ~,3f from J~D(~A) = ~,3f and I~D(~A) = ~,3f~%" curJI k a (bes-jn k a) j b (bes-in (abs j) b))))))) (list sum (+ (* (- 1.0 interp) (real-part sum)) (* interp (imag-part sum)))))) ;(fm-complex-component 1200 1000 100 1.0 3.0 0.0 #f) ;(fm-complex-component 1200 1000 100 1.0 4.0 0.0 #f) hits the commentary (define (fm-cascade-component freq-we-want wc wm1 a wm2 b) (let ((sum 0.0) (mxa (ceiling (* 7 a))) (mxb (ceiling (* 7 b)))) (do ((k (- mxa) (+ k 1))) ((>= k mxa)) (do ((j (- mxb) (+ j 1))) ((>= j mxb)) (if (< (abs (- freq-we-want wc (* k wm1) (* j wm2))) 0.1) (let ((curJJ (* (bes-jn k a) (bes-jn j (* k b))))) (set! sum (+ sum curJJ)) (if (> (magnitude curJJ) 0.001) (format () ";fm-cascade-component add ~,3f from J~D(~A) = ~,3f and J~D(~A) = ~,3f~%" curJJ k a (bes-jn k a) j b (bes-jn j (* k b)))))))) sum)) ;(fm-cascade-component 2000 2000 500 1.5 50 1.0) ;;; waveshaping harmonic amplitude at a given index (define (cheby-hka k a coeffs) ; (coeff 0 = DC) (do ((sum 0.0) (n (length coeffs)) (j 0 (+ j 1))) ((= j n) sum) (do ((dsum 0.0) (p (+ k (* 2 j))) (i 0 (+ i 1))) ((>= (+ p (* 2 i)) n) (set! sum (+ sum (* dsum (expt a p) (binomial p j))))) (set! dsum (+ dsum (* (expt -1 i) (coeffs (+ p (* 2 i))) (+ (binomial (+ p i) i) (binomial (+ p i -1) (- i 1))))))))) #| (with-sound () (let ((gen (make-polyshape 1000.0 :partials (list 1 .5 2 .25 3 .125 4 .125)))) (do ((i 0 (+ i 1))) ((= i 88200)) (outa i (* .5 (polyshape gen 0.25)))))) (cheby-hka 1 0.25 (float-vector 0 .5 .25 .125 .125)) |# ;;; find not-so-spikey amps for waveshaping (define* (flatten-partials any-partials (tries 32)) (define (cos-fft-to-max n cur-amps) (let ((size 1024)) (do ((fft-rl (make-float-vector size)) (fft-im (make-float-vector size)) (i 0 (+ i 1)) (bin 2 (+ bin 2))) ((= i n) (float-vector-peak (mus-fft fft-rl fft-im size -1))) (set! (fft-rl bin) (cur-amps i))))) (let* ((partials (if (list? any-partials) (apply float-vector any-partials) any-partials)) (len (length partials)) (topk 0) (DC 0.0) (original-sum (do ((sum 0.0) (i 0 (+ i 2))) ((>= i len) sum) (let ((hnum (partials i)) (amp (partials (+ i 1)))) (if (= hnum 0) (set! DC amp) (begin (set! topk (max topk hnum)) (set! sum (+ sum amp))))))) (min-sum original-sum) (original-partials (do ((v (make-float-vector topk)) (i 0 (+ i 2))) ((>= i len) v) (let ((hnum (partials i))) (if (not (= hnum 0)) (set! (v (- hnum 1)) (partials (+ i 1))))))) (min-partials (copy original-partials))) (if (<= topk (log tries 2)) (set! tries (floor (expt 2 (- topk 1))))) (do ((try 0 (+ 1 try))) ((= try tries)) (let ((new-partials (copy original-partials))) (do ((k 0 (+ k 1))) ((= k topk)) (if (> (random 1.0) 0.5) (set! (new-partials k) (- (new-partials k))))) (let ((new-sum (cos-fft-to-max topk new-partials))) (if (< new-sum min-sum) (begin (set! min-partials (copy new-partials)) (set! min-sum new-sum)))))) (let ((new-amps (float-vector-scale! min-partials (/ original-sum min-sum))) (new-partials (copy partials))) (do ((i 0 (+ i 2))) ((>= i len)) (let ((hnum (new-partials i))) (set! (new-partials (+ i 1)) (if (= hnum 0) DC (new-amps (- hnum 1)))))) new-partials))) #| (with-sound (:clipped #f :statistics #t :channels 2) (let* ((amps (normalize-partials (list 1 .25 2 .5 3 .25))) (gen1 (make-polywave 400.0 amps)) (gen2 (make-polywave 400.0 (flatten-partials amps)))) (do ((i 0 (+ i 1))) ((= i 44100)) (outa i (polywave gen1)) (outb i (polywave gen2))))) |# #| ;;; these are standard FFTs for s7 (define* (fft! rl im n (dir 1)) (if (not im) (let ((clear (copy rl))) (fill! clear 0.0) (set! im clear))) (if (not n) (set! n (length rl))) (do ((i 0 (+ i 1)) (j 0)) ((= i n)) (if (> j i) (let ((tempr (rl j)) (tempi (im j))) (set! (rl j) (rl i)) (set! (im j) (im i)) (set! (rl i) tempr) (set! (im i) tempi))) (let ((m (/ n 2))) (do () ((or (< m 2) (< j m))) (set! j (- j m)) (set! m (/ m 2))) (set! j (+ j m)))) (let ((ipow (floor (log n 2))) (prev 1)) (do ((lg 0 (+ lg 1)) (mmax 2 (* mmax 2)) (pow (/ n 2) (/ pow 2)) (theta (* pi dir) (* theta 0.5))) ((= lg ipow)) (let ((wpr (cos theta)) (wpi (sin theta)) (wr 1.0) (wi 0.0)) (do ((ii 0 (+ ii 1))) ((= ii prev)) (do ((jj 0 (+ jj 1)) (i ii (+ i mmax)) (j (+ ii prev) (+ j mmax))) ((>= jj pow)) (let ((tempr (- (* wr (rl j)) (* wi (im j)))) (tempi (+ (* wr (im j)) (* wi (rl j))))) (set! (rl j) (- (rl i) tempr)) (set! (rl i) (+ (rl i) tempr)) (set! (im j) (- (im i) tempi)) (set! (im i) (+ (im i) tempi)))) (let ((wtemp wr)) (set! wr (- (* wr wpr) (* wi wpi))) (set! wi (+ (* wi wpr) (* wtemp wpi))))) (set! prev mmax)))) rl) (define* (cfft! data n (dir 1)) (if (not n) (set! n (length data))) (do ((i 0 (+ i 1)) (j 0)) ((= i n)) (if (> j i) (let ((temp (data j))) (set! (data j) (data i)) (set! (data i) temp))) (let ((m (/ n 2))) (do () ((or (< m 2) (< j m))) (set! j (- j m)) (set! m (/ m 2))) (set! j (+ j m)))) (let ((ipow (floor (log n 2))) (prev 1)) (do ((lg 0 (+ lg 1)) (mmax 2 (* mmax 2)) (pow (/ n 2) (/ pow 2)) (theta (complex 0.0 (* pi dir)) (* theta 0.5))) ((= lg ipow)) (let ((wpc (exp theta)) (wc 1.0)) (do ((ii 0 (+ ii 1))) ((= ii prev)) (do ((jj 0 (+ jj 1)) (i ii (+ i mmax)) (j (+ ii prev) (+ j mmax))) ((>= jj pow)) (let ((tc (* wc (data j)))) (set! (data j) (- (data i) tc)) (set! (data i) (+ (data i) tc)))) (set! wc (* wc wpc))) (set! prev mmax)))) data) ;;; > (cfft! (list 0.0 1+i 0.0 0.0)) ;;; (1+1i -1+1i -1-1i 1-1i) ;;; ;;; > (cfft! (vector 0.0 1+i 0.0 0.0)) ;;; #(1+1i -1+1i -1-1i 1-1i) ;;; ;;; ;; check against built-in FFT ;;; > (let ((rl (float-vector 0.0 1.0 0.0 0.0)) ;;; (im (float-vector 0.0 1.0 0.0 0.0))) ;;; (mus-fft rl im) ;;; (map complex rl im)) ;;; (1+1i -1+1i -1-1i 1-1i) |#