\ dsp.fs -- dsp.scm|rb --> dsp.fs \ Author: Michael Scholz \ Created: 05/12/30 04:52:13 \ Changed: 18/04/14 17:17:37 \ \ @(#)dsp.fs 1.53 4/14/18 \ src-duration ( en -- dur ) \ src-fit-envelope ( e1 target-dur -- e2 ) \ dolph ( n gamma -- im ) \ dolph-1 (n gamma -- im ) \ down-oct ( n :optional snd chn -- vct ) \ stretch-sound-via-dft ( factor :optional snd chn -- ) \ compute-uniform-circular-string ( size x0 x1 x2 mass xspring damp -- ) \ compute-string ( size x0 x1 x2 ms xs esprs damps haptics -- ) \ freqdiv ( n :optional snd chn -- ) \ adsat ( size :optional beg dur snd chn -- res ) \ spike ( :optional snd chn -- res ) \ spot-freq ( samp :optional snd chn -- ) \ chorus ( -- proc; inval self -- val ) \ chordalize ( -- proc; x self -- val ) \ zero-phase ( :optional snd chn -- vct ) \ rotate-phase ( func :optional snd chn -- vct) \ make-asyfm ( :key freq initial-phase ratio r index -- gen ) \ asyfm-J ( gen input -- val ) \ asyfm-I ( gen input -- val ) \ make-cosine-summation ( :key frequency initial-phase -- gen ) \ cosine-summation ( gen r -- val ) \ make-kosine-summation ( :key frequency initial-phase -- gen ) \ kosine-summation ( gen r k -- val ) \ fejer-sum ( angle n -- val ) \ legendre-sum ( angle n -- val ) \ sum-of-n-sines ( angle n -- val ) \ sum-of-n-odd-sines ( angle n -- val ) \ sum-of-n-odd-cosines ( angle n -- val ) \ band-limited-sawtooth ( x a N fi -- val ) \ band-limited-square-wave ( theta n -- val ) \ brighten-slightly ( amount :optional snd chn -- ) \ brighten-slightly-1 ( coeffs :optional snd chn -- ) \ spectrum->coeffs ( order spectr -- vct ) \ fltit-1 ( order spectr -- proc; y self -- val ) \ \ make-hilbert-transform ( :optional len -- gen ) \ make-highpass ( fc :optional len -- gen ) \ make-lowpass ( fc :optional len -- gen ) \ make-bandpass ( flo fhi :optional len -- gen ) \ make-bandstop ( flo fhi :optional len -- gen ) \ make-differentiator ( :optional len -- gen ) \ make-butter-high-pass ( freq -- flt ) \ make-butter-low-pass ( freq -- flt ) \ make-butter-band-pass ( freq band -- flt ) \ make-butter-band-reject ( freq band -- flt ) \ make-biquad ( a0 a1 a2 b1 b2 -- gen ) \ make-iir-low-pass-2 ( fc :optional d -- gen ) \ make-iir-high-pass-2 ( fc :optional d -- gen ) \ make-iir-band-pass-2 ( f1 f2 -- gen ) \ make-iir-band-stop-2 ( f1 f2 -- gen ) \ make-eliminate-hum ( :optional hum-frq hum-harm bw -- ) \ \ make-peaking-2 ( f1 f2 m -- prc; y self -- val ) \ cascade->canonical ( A -- A' ) \ make-butter-lp ( M fc -- flt ) \ make-butter-hp ( M fc -- flt ) \ make-butter-bp ( M f1 f2 -- flt ) \ make-butter-bs ( M f1 f2 -- flt ) \ \ make-notch-frequency-response ( cur-sr freqs :optional notchw -- fresp ) \ notch-channel ( freqs :optional ... ) \ notch-sound ( freqs :optional filtero snd chn notchw -- f ) \ notch-selection ( freqs :optional filtero notchw -- f ) \ fractional-fourier-transform ( real imaginary n angle -- hr hi ) \ z-transform ( data n z -- ) \ dht ( data -- vct ) \ find-sine ( freq beg dur -- amp ph ) \ goertzel ( freq :optional beg dur -- amp ) \ \ make-spencer-filter ( -- gen ) \ any-random ( amount :optional en -- r ) \ gaussian-distribution ( s -- en ) \ pareto-distribution ( a -- en ) \ inverse-integrate ( dist :optional data-size e-size -- vct ) \ gaussian-envelope ( s -- en ) \ \ channel-mean ( :optional snd chn -- val ) \ channel-total-energy ( :optional snd chn -- val ) \ channel-average-power ( :optional snd chn -- val ) \ channel-rms ( :optional snd chn -- val ) \ channel-variance ( :optional snd chn -- val ) \ channel-norm ( :optional snd chn -- val ) \ channel-lp ( p :optional snd chn -- val ) \ channel-lp-inf ( :optional snd chn -- val ) \ channel2-inner-product ( s1 c1 s2 c2 -- val ) \ channel2-angle ( s1 c1 s2 c2 -- val ) \ channel2-orthogonal? ( s1 c1 s2 c2 -- f ) \ channel2-coefficient-of-projection ( s1 c1 s2 c2 -- val ) \ channel-distance ( s1 c1 s2 c2 -- val ) \ periodogram ( N -- ) \ \ shift-channel-pitch ( frq :optional ord beg dur snd chn ep -- val ) \ ssb-bank ( ofreq nfreq pairs :optional ... ) \ ssb-bank-env ( ofreq nfreq freq-env pairs :optional ... ) \ make-transposer ( ofreq nfreq pairs :optional ord bw -- gen ) \ transpose ( gen input -- val ) \ make-fdelay ( len pitch scaler -- prc; y self -- val ) \ fdelay ( gen input -- val ) \ transposed-echo ( pitch scaler secs -- val ) \ \ vct-polynomial ( v coeffs -- vct ) \ channel-polynomial ( coeffs :optional snd chn -- vct ) \ spectral-polynomial ( coeffs :optional snd chn -- vct ) \ \ scentroid ( file :key beg dur db-floor rf ffts -- vals ) \ invert-filter ( fcoeffs -- res ) \ make-volterra-filter ( acoeffs bcoeffs -- gen ) \ volterra-filter ( flt y -- val ) \ \ make-moving-max ( :optional size -- gen ) \ moving-max ( gen y -- scl ) \ make-moving-sum ( :optional size -- gen ) \ moving-sum ( gen y -- val ) \ make-moving-rms ( :optional size -- gen ) \ moving-rms ( gen y -- val ) \ make-moving-length ( :optional size -- gen ) \ moving-length ( gen y -- val ) \ make-smoothing-filter ( size -- gen ) \ smoothing-filter ( gen sig -- r ) \ make-exponentially-weighted-moving-average ( order -- r ) \ make-weighted-moving-average ( order -- r ) \ weighted-moving-average ( gen sig -- r ) \ harmonicizer ( freq coeffs pairs :optional ... ) \ linear-src-channel ( sr :optional snd chn -- file ) \ display-bark-fft ( :optional off -- ) \ undisplay-bark-fft ( -- ) \ lpc-coeffs ( data n m -- val ) \ lpc-predict ( data n coeffs m nf :optional clipped -- val ) \ unclip-channel ( :optional snd chn -- assoc ) \ unclip-sound ( :optional snd -- assoc ) require clm require env require examp \ ;;; -------- src-duration (see src-channel in extsnd.html) : src-duration ( en -- dur ) doc" Return new duration of a sound after using ENVELOPE \ for time-varying sampling-rate conversion." { en } en 0 object-ref { ex0 } en -2 object-ref { ex1 } ex1 ex0 f- { all-x } 0.0 ( dur ) en object-length 3 - 0 ?do en i object-ref { x0 } en i 1 + object-ref { xy0 } en i 2 + object-ref { x1 } en i 3 + object-ref { xy1 } xy0 f0= if 1.0 else xy0 1/f then { y0 } xy1 f0= if 1.0 else xy1 1/f then { y1 } xy0 xy1 f- fabs 0.0001 f< if x1 x0 f- all-x f/ y0 f* else y1 flog y0 flog f- xy0 xy1 f- f/ x1 x0 f- all-x f/ f* then ( area ) fabs ( dur ) f+ 2 +loop ( dur ) ; : src-fit-envelope { e1 target-dur -- e2 } e1 e1 src-duration target-dur f/ scale-envelope ; 'complex provided? [if] \ ;;; -------- 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) : dolph ( n gamma -- im ) doc" Produce Dolph-Chebyshev FFT data window \ of N points using GAMMA as the window parameter." { n gamma } 10.0 gamma f** cacosh n c/ ccosh { alpha } alpha cacosh n c* ccosh 1/c { den } n 0.0 make-vct { rl } n 0.0 make-vct { im } pi n f/ { freq } 0.0 { phase } n 0 ?do phase ccos alpha c* cacos n c* ccos den c* { val } rl i val real-ref vct-set! drop im i val imag-ref vct-set! drop phase freq f+ to phase loop rl im -1 fft ( rl ) dup vct-peak 1/f vct-scale! ( rl ) n 2/ cycle-start! n 0 ?do im i rl cycle-ref vct-set! drop loop 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, vcts etc) : dolph-1 ( n gamma -- im ) { n gamma } 10.0 gamma f** cacosh n c/ ccosh { alpha } alpha cacosh n c* ccosh 1/c { den } pi n f/ { freq } half-pi fnegate { phase } -1.0 { mult } n make-array map! phase ccos alpha c* cacos n c* ccos den c* mult c* ( x ) mult fnegate to mult phase freq f+ to phase ( x ) end-map { vals } \ now take the DFT 0.0 { pk } n make-array map! 0.0 ( sum ) vals each ( val ) 0+1.0i two-pi c* j c* i c* n c/ cexp c* c+ ( sum++ ) end-each cabs { sum } sum pk f> if sum to pk then sum end-map ( w ) map! *key* pk f/ end-map ( w ) ; [then] \ ;;; ------- move sound down by n (a power of 2) --- : down-oct <{ n :optional snd #f chn #f -- vct }> doc" Move sound down by power of 2 n." snd chn #f framples { len } 2.0 len flog 2.0 flog f/ fceil ( pow2 ) f** f>s { fftlen } fftlen 1/f { fftscale } 0 fftlen snd chn #f channel->vct { rl1 } fftlen 0.0 make-vct { im1 } rl1 im1 1 fft ( rl1 ) fftscale vct-scale! drop im1 fftscale vct-scale! drop fftlen n * 0.0 make-vct { rl2 } fftlen n * 0.0 make-vct { im2 } fftlen 1- { kdx } fftlen n * 1- { jdx } fftlen 2/ 0 ?do rl2 i rl1 i vct-ref vct-set! drop rl2 jdx rl1 kdx vct-ref vct-set! drop im2 i im1 i vct-ref vct-set! drop im2 jdx im1 kdx vct-ref vct-set! drop jdx 1- to jdx kdx 1- to kdx loop rl2 im2 -1 fft ( rl2 ) 0 n len * snd chn #f "%s %s" #( n get-func-name ) string-format vct->channel ; 'complex provided? [if] : stretch-sound-via-dft <{ factor :optional snd #f chn #f -- }> doc" Make 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." snd chn #f framples { n } n f2/ floor f>s { n2 } n factor f* fround->s { out-n } 0 n snd chn #f channel->vct { in-data } out-n :initial-element 0.0 make-array { fr } two-pi n f/ { freq } n 0 ?do i n2 < if fr i freq 0.0-1.0i c* i c* else fr out-n n - 1- i + freq 0.0-1.0i c* i c* then in-data edot-product array-set! loop two-pi out-n f/ { freq } out-n 0.0 make-vct map! freq 0.0+1.0i c* i c* fr edot-product n c/ real-ref end-map ( out-data ) 0 out-n snd chn #f "%s %s" #( factor get-func-name ) string-format vct->channel drop ; [then] \ ;;; -------- compute-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). : compute-uniform-circular-string ( size x0 x1 x2 mass xspring damp -- ) { size x0 x1 x2 mass xspring damp } damp mass f/ { dm } xspring mass f/ { km } 1.0 dm f+ { denom } dm km f2* f- 2.0 f+ denom f/ { p1 } km denom f/ { p2 } -1.0 denom f/ { p3 } x0 map! x1 i vct-ref p1 f* x1 i 1- object-ref x1 i 1+ size = if 0 else i 1+ then object-ref f+ p2 f* f+ x2 i vct-ref p3 f* f+ end-map to x0 x2 0.0 vct-fill! drop x2 x1 vct-add! drop x1 0.0 vct-fill! drop x1 x2 vct-add! drop ; : compute-string ( size x0 x1 x2 masses xsprings esprings damps haptics -- ) { size x0 x1 x2 masses xsprings esprings damps haptics } x0 map! damps i vct-ref masses i vct-ref f/ { dm } xsprings i vct-ref masses i vct-ref f/ { km } esprings i vct-ref masses i vct-ref f/ { cm } 1.0 dm cm f+ f+ { denom } dm km f2* f- 2.0 f+ denom f/ { p1 } km denom f/ { p2 } -1.0 denom f/ { p3 } haptics i vct-ref masses i vct-ref denom f* f/ { p4 } x1 i vct-ref p1 f* x1 i 1- object-ref x1 i 1+ size = if 0 else i 1+ then vct-ref f+ p2 f* f+ x2 i vct-ref p3 f* f+ p4 f+ end-map to x0 size 0 ?do x2 i x1 i vct-ref vct-set! drop x1 i x0 i vct-ref vct-set! drop loop ; \ ;;; -------- "frequency division" -- an effect from sed_sed@my-dejanews.com hide : freqdiv-cb { div n curval -- prc; y self -- val } 1 proc-create ( prc ) div , n , curval , does> ( y self -- val ) { y self } self @ { div } self cell+ @ { n } div 0= if y self 2 cells + ! ( curval ) then 1 self +! ( div++ ) div n = if 0 self ! then self 2 cells + @ ( curval ) ; set-current : freqdiv <{ n :optional snd #f chn #f -- val }> doc" Repeat each nth sample N times (clobbering the \ intermediate samples): 8 freqdiv." 0 n 0.0 freqdiv-cb 0 #f snd chn #f "%s %s" #( n get-func-name ) string-format map-channel ; previous \ ;;; -------- "adaptive saturation" -- an effect from sed_sed@my-dejanews.com hide : adsat-cb { mn mx n vals -- prc; inval self -- res } 1 proc-create ( prc ) vals , n , mx , mn , does> ( inval self -- res ) { inval self } self @ { vals } self 1 cells + @ { n } self 2 cells + @ { mx } self 3 cells + @ { mn } vals length n = if vals each { x } vals i x f0>= if mx else mn then vct-set! drop end-each 0 self 1 cells + ! ( n ) 0.0 self 2 cells + ! ( mx ) 0.0 self 3 cells + ! ( mn ) vals else vals n inval vct-set! drop 1 self 1 cells + +! ( n++ ) inval mx fmax self 2 cells + ! ( mx ) inval mn fmin self 3 cells + ! ( mn ) #f then ; set-current : adsat <{ size :optional beg 0 dur #f snd #f chn #f -- val }> doc" An 'adaptive saturation' sound effect." 0.0 0.0 0 size 0.0 make-vct adsat-cb beg dur snd chn #f "%s %s %s %s" #( size beg dur get-func-name ) string-format map-channel ; previous \ ;;; -------- spike --- \ ;;; \ ;;; makes sound more spikey -- sometimes a nice effect hide : spike-cb ( snd chn -- prc; x0 self -- res ) { snd chn } snd chn #f maxamp { amp } 1 proc-create ( prc ) 0.0 ( x1 ) , 0.0 ( x2 ) , amp , does> ( x0 self -- res ) { x0 self } self @ { x1 } self 1 cells + @ { x2 } self 2 cells + @ { amp } x0 amp amp f* f/ x2 fabs f* x1 fabs f* { res } x1 self cell+ ! ( x2 ) x0 self ! ( x1 ) res ; set-current : spike <{ :optional snd #f chn #f -- val }> doc" Multiply successive samples together to make a sound more spikey." snd chn spike-cb 0 #f snd chn #f get-func-name map-channel ; previous \ ;;; -------- easily-fooled autocorrelation-based pitch tracker : spot-freq <{ samp :optional snd #f chn #f -- }> doc" Try to determine the current pitch: left-sample spot-freq." snd srate s>f { sr } 2.0 sr 20.0 f/ flog 2.0 flog f/ fceil f** fround->s { fftlen } samp fftlen snd chn #f channel->vct autocorrelate { data } data vct-peak { cor-peak } cor-peak f2* { cor-peak2 } 0.0 ( ret ) fftlen 2 - 1 ?do data i vct-ref data i 1 + vct-ref f< data i 1 + vct-ref data i 2 + vct-ref f> && if drop ( old ret ) data i vct-ref cor-peak f+ cor-peak2 f/ flog10 { logla } data i 1 + vct-ref cor-peak f+ cor-peak2 f/ flog10 { logca } data i 2 + vct-ref cor-peak f+ cor-peak2 f/ flog10 { logra } logla logra f- f2/ logla logra f+ logca -2.0 f* f+ f/ { offset } sr offset i f+ 1.0 f+ f2* f/ ( new ret ) leave then loop ; 0 [if] \ Left sample: graph-hook lambda: <{ snd chn y0 y1 -- f }> "freq: %.3f" #( snd chn LEFT-SAMPLE snd chn spot-freq ) string-format snd status-report drop #f ; add-hook! \ At cursor position: mouse-click-hook lambda: <{ snd chn button state x y axis -- }> axis time-graph = if "freq: %.3f" #( snd chn #f CURSOR snd chn spot-freq ) string-format snd status-report else #f then ; add-hook! [then] \ ;;; -------- chorus (doesn't always work and needs speedup) 5 value chorus-size 0.05 value chorus-time 20.0 value chorus-amount 10.0 value chorus-speed hide : make-flanger ( -- ri gen ) :frequency chorus-speed :amplitude chorus-amount make-rand-interp { ri } chorus-time 3.0 f* #f srate f* random floor f>s { len } len :max-size len chorus-amount f>s + 1+ make-delay { gen } #( gen ri ) ; : flanger ( dly inval -- val ) { dly inval } dly 0 array-ref inval dly 1 array-ref 0.0 rand-interp delay inval f+ ; set-current : chorus ( -- prc; inval self -- val ) doc" Try to produce the chorus sound effect." chorus-size make-array map! make-flanger end-map { dlys } 1 proc-create ( prc ) dlys , does> { inval self -- val } self @ { dlys } 0.0 ( sum ) dlys each ( dly ) inval flanger f+ ( sum++ ) end-each ; previous \ ;;; -------- chordalize (comb filters to make a chord using \ ;;; chordalize-amount and chordalize-base) 0.95 value chordalize-amount 100 value chordalize-base 'ratio provided? [if] #( 1 3/4 5/4 ) [else] <'> f* alias q* <'> f>s alias q>s #( 1 3 4 f/ 5 4 f/ ) [then] value chordalize-chord : chordalize ( -- prc; x self -- val ) doc" Use harmonically-related comb-filters to bring \ out a chord in a sound. \ Global variable CHORDALIZE-CHORD is an array of members \ of chord such as #( 1 5/4 3/2 )." chordalize-chord map :scaler chordalize-amount :size chordalize-base *key* q* q>s make-comb end-map { combs } chordalize-chord length 0.5 f/ { scaler } 1 proc-create ( prc ) combs , scaler , does> { x self -- val } self @ { combs } self cell+ @ { scaler } 0.0 ( val ) combs each ( gen ) x 0.0 comb f+ ( val += ... ) end-each scaler f* ; \ ;;; -------- zero-phase, rotate-phase \ ;;; fft games (from the "phazor" package of Scott McNab) : zero-phase <{ :optional snd #f chn #f -- vct }> doc" Call fft, set all phases to 0, and un-fft." snd chn #f framples { len } 2.0 len flog 2.0 flog f/ fceil ( pow2 ) f** fround->s { fftlen } fftlen 1/f { fftscale } 0 fftlen snd chn #f channel->vct { rl } rl vct-peak { old-pk } fftlen 0.0 make-vct { im } rl im 1 fft ( rl ) im rectangular->polar drop rl fftscale vct-scale! drop im 0.0 vct-scale! drop rl im -1 fft ( rl ) vct-peak { pk } rl old-pk pk f/ vct-scale! 0 len snd chn #f get-func-name vct->channel ; : rotate-phase <{ func :optional snd #f chn #f -- vct }> doc" Call fft, apply FUNC, a proc or xt, to each phase, then un-fft." func word? func 1 "a proc or xt" assert-type snd chn #f framples { len } 2.0 len flog 2.0 flog f/ fceil ( pow2 ) f** fround->s { fftlen } fftlen 2/ { fftlen2 } fftlen 1/f { fftscale } 0 fftlen snd chn #f channel->vct { rl } rl vct-peak { old-pk } fftlen 0.0 make-vct { im } \ fft returns result in rl rl im 1 fft ( rl ) im rectangular->polar drop rl fftscale vct-scale! drop im 0 0.0 vct-set! drop func xt? if func 1 make-proc to func then fftlen2 1 ?do im i func #( im i object-ref ) run-proc object-set! \ handles negative index im i negate im i negate object-ref fnegate object-set! loop rl im -1 fft ( rl ) vct-peak { pk } func proc-name { pn } pn length 0= if "noname" to pn then "<'> %s %s" #( pn get-func-name ) string-format { origin } rl old-pk pk f/ vct-scale! 0 len snd chn #f origin vct->channel ; \ XXX: origin in rotate-phase [ms] \ If FUNC isn't a proc or xt with a name (eg: 'lambda:' has no name), \ the origin will not be useful. \ \ The next two lines will deliver a useful origin: \ : rot-zero-ph ( y -- 0.0 ) drop 0.0 ; \ <'> rot-zero-ph rotate-phase \ \ while this won't: \ lambda: { y -- 0.0 } 0.0 ; rotate-phase \ \ Here are the examples from dsp.scm: \ lambda: { y -- r } 0.0 ; rotate-phase \ is the same as zero-phase \ lambda: { y -- r } pi random ; rotate-phase \ randomizes phases \ lambda: { y -- r } y ; rotate-phase \ returns original \ lambda: { y -- r } y fnegate ; rotate-phase \ reverses original \ lambda: { y -- r } y f2* ; rotate-phase \ reverb-effect (best with voice) \ lambda: { y -- r } y 12.0 f* ; rotate-phase \ "bruise blood" effect \ ;;; -------- asymmetric FM (bes-i0 case) #( "asyfm-freq" "asyfm-phase" "asyfm-ratio" "asyfm-r" "asyfm-index" ) create-struct make-asymmetric-struct : make-asyfm <{ :key frequency *clm-default-frequency* initial-phase 0.0 ratio 1.0 r 1.0 index 1.0 -- gen }> make-asymmetric-struct { gen } gen frequency hz->radians asyfm-freq! gen initial-phase asyfm-phase! gen ratio asyfm-ratio! gen r asyfm-r! gen index asyfm-index! gen ; : asyfm-J ( gen input -- val ) doc" ;; This is the same as the CLM asymmetric-fm generator, \ set r != 1.0 to get the asymmetric spectra.\n\ :frequency 2000 :ratio 0.1 make-asyfm value gen\n\ lambda: <{ n -- r }> gen 0.0 asyfm-J ; map-channel." { gen input } gen asyfm-phase@ { phase } gen asyfm-ratio@ { ratio } gen asyfm-r@ { r } gen asyfm-index@ { index } r 1/f { r1 } ratio phase f* { modphase } r f0< r -1.0 f> && r 1.0 f> || if -1.0 else 1.0 then { one } modphase fcos one f+ r r1 f- f* index f* 0.5 f* fexp modphase fsin r r1 f+ f* index f* 0.5 f* phase f+ fcos f* ( val ) gen phase input f+ gen asyfm-freq@ f+ asyfm-phase! ( val ) ; \ Example from old dsp.scm (now in clm23.scm): \ :frequency 2000 :ratio 0.1 make-asyfm value gen \ lambda: <{ n -- r }> gen 0.0 asyfm-J ; map-channel \ \ Examples from generators.scm: 0 [if] lambda: ( -- ) :frequency 2000 :ratio 0.1 make-asyfm { gen } 10000 0 do i gen 0.0 asyfm-J f2/ *output* outa drop loop ; :clipped #f :statistics #t :play #t with-sound lambda: ( -- ) :frequency 2000 :ratio 0.1 :index 1 make-asyfm { gen } '( 0 -4 1 -1 ) :length 20000 make-env { r-env } 20000 0 do gen r-env env asyfm-r! i gen 0.0 asyfm-J *output* outa drop loop ; :clipped #f :statistics #t :play #t with-sound [then] : asyfm-I { gen input -- val } gen asyfm-phase@ { phase } gen asyfm-ratio@ { ratio } gen asyfm-r@ { r } gen asyfm-index@ { index } r 1/f { r1 } ratio phase f* { modphase } modphase fcos r r1 f+ f* index f* 0.5 f* r r1 f+ index f* bes-i0 flog 0.5 f* f- fexp modphase fsin r r1 f- f* index f* 0.5 f* phase f+ fsin f* ( val ) gen phase input f+ gen asyfm-freq@ f+ asyfm-phase! ( val ) ; \ ;;; -------- cosine-summation (a simpler version of sine-summation) \ ;;; \ ;;; from Andrews, Askey, Roy "Special Functions" 5.1.16 : cosine-summation ( gen r -- val ) doc" A variant of the CLM sine-summation generator; \ R controls successive sinusoid amplitudes." { gen r } 1.0 r r f* f- 1.0 r r f* f+ r f2* gen 0.0 0.0 oscil f* f- f/ 1.0 f- 1.0 r r f* f- 1.0 r r f* f+ r f2* f* f/ f* ; <'> make-oscil alias make-cosine-summation \ 100.0 make-cosine-summation value gen \ lambda: <{ y }> gen 0.5 cosine-summation 0.2 f* ; map-channel \ ;;; -------- kosine-summation \ ;;; \ ;;; from Askey "Ramanujan and Hypergeometric Series" in Berndt and \ ;;; Rankin "Ramanujan: Essays and Surveys" \ ;;; \ ;;; this gives a sum of cosines of decreasing amp where the "k" parameter \ ;;; determines the "index" (in FM nomenclature) -- higher k = more cosines; \ ;;; the actual amount of the nth cos involves hypergeometric series \ ;;; (looks like r^n/n! (~=e^n?) with a million other terms). : kosine-summation ( gen r k -- val ) doc" It's a variant of sum-of-cosines; \ R controls successive sinusoid amplitude; \ K controls how many sinusoids are produced." { gen r k } 1.0 r r f* f+ r f2* gen 0.0 0.0 oscil f* f- k fnegate f** 1.0 r r f* f+ r f2* f- k f** f* ; <'> make-oscil alias make-kosine-summation \ 100.0 make-kosine-summation value gen \ lambda: <{ y }> gen 0.5 5.0 kosine-summation 0.2 f* ; map-channel \ ;;; -------- legendre, fejer : fejer-sum ( angle n -- val ) doc" Produce band-limited pulse train." \ ;; from "Trigonometric Series" Zygmund p88 { angle n } angle f0= if 1.0 else n 1.0 f+ angle f* f2/ fsin angle f2/ fsin f2* f/ ( val ) dup f* n 1.0 f+ f/ f2* then ; \ 0.0 value angle \ lambda: <{ y -- val }> \ angle 3.0 fejer-sum 0.1 f* ( val ) \ angle 0.1 f+ to angle \ ; map-channel : legendre-sum ( angle n -- val ) doc" Produce band-limited pulse train." \ ;; from Andrews, Askey, Roy "Special Functions" p 314 { angle n } angle f0= if 1.0 else n 0.5 f+ angle f* fsin angle f2/ fsin f/ dup f* then ; \ 0.0 value angle \ lambda: <{ y -- val }> \ angle 3.0 legendre-sum 0.1 f* ( val ) \ angle 0.1 f+ to angle \ ; map-channel \ ;;; -------- variations on sum-of-cosines \ ;;; from "Trigonometric Delights" by Eli Maor : sum-of-n-sines ( angle n -- val ) doc" Produce the sum of N sines." { angle n } angle f2/ { a2 } a2 fsin { den } den f0= if 0.0 else n a2 f* fsin n 1.0 f+ a2 f* fsin f* den f/ then ; \ 0.0 value angle \ lambda: <{ y -- val }> \ angle 3.0 sum-of-n-sines 0.1 f* ( val ) \ angle 0.1 f+ to angle \ ; map-channel : sum-of-n-odd-sines ( angle n -- val ) doc" Produce the sum of N odd-numbered sines." { angle n } angle fsin { den } angle n f* fsin dup { na2 } den f0= if 0.0 else na2 den f/ then ; : sum-of-n-odd-cosines ( angle n -- val ) doc" Produce the sum of N odd-numbered cosines." { angle n } angle fsin f2* { den } den f0= if n else angle n f* f2* den f/ then ; : band-limited-sawtooth ( x a N fi -- val ) doc" Produce a band-limited sawtooth; \ X is the current phase, \ A is the amp (more or less), \ N is 1..10 or thereabouts, \ FI is the phase increment." { x a N fi } a dup f* x fcos a f* -2.0 f* f+ 1.0 f+ { s4 } s4 f0= if 0.0 else a N 1.0 f- f** N 1.0 f- x f* fi f+ fsin f* { s1 } a N f** N x f* fi f+ fsin f* { s2 } x fi f+ fsin a f+ { s3 } fi fsin s3 fnegate f+ s2 fnegate f+ s1 f+ s4 f/ then ; \ 0.0 value angle \ lambda: <{ y -- val }> \ angle 0.5 8.0 0.2 band-limited-sawtooth ( val ) \ angle 0.2 f+ to angle \ ; map-channel : band-limited-square-wave ( theta n -- val ) doc" Produce square-wave; N sets how squared-off it is, \ THETA is instantaneous phase." swap fsin f* ftanh ; \ 0.0 value angle \ lambda: <{ y -- val }> \ angle 10.0 band-limited-square-wave ( val ) \ angle 0.2 f+ to angle \ ; map-channel \ ;;; -------- brighten-slightly hide : bs-cb { brt mx -- prc; y self -- val } 1 proc-create ( prc ) mx , brt , does> { y self -- val } self @ { mx } self cell+ @ { brt } brt y f* fsin mx f* ; set-current : brighten-slightly <{ amount :optional snd #f chn #f -- val }> doc" It's a form of contrast-enhancement (AMOUNT between ca 0.1 and 1)." snd chn #f maxamp { mx } two-pi amount f* mx f/ { brt } brt mx bs-cb 0 #f snd chn #f "%s %s" #( amount get-func-name ) string-format map-channel ; previous hide : brighten-slightly-1-cb { pcoeffs mx -- prc; y self -- val } 1 proc-create ( prc ) mx , pcoeffs , does> { y self -- val } self @ { mx } self cell+ @ { pcoeffs } pcoeffs y mx f/ polynomial mx f* ; set-current : brighten-slightly-1 <{ coeffs :optional snd #f chn #f -- val }> doc" It's a form of contrast-enhancement (AMOUNT between ca 0.1 and 1)." snd chn #f maxamp { mx } coeffs mus-chebyshev-first-kind partials->polynomial { pcoeffs } pcoeffs mx brighten-slightly-1-cb 0 #f snd chn #f "%s %s" #( coeffs get-func-name ) string-format map-channel ; \ #( 1 0.5 3 1 ) brighten-slightly-1 previous \ ;;; -------- FIR filters : spectrum->coeffs ( order spectr -- vct ) doc" Return FIR filter coefficients given the filter order and \ desired spectral envelope." { order spectr } order 0.0 make-vct { coeffs } order { n } n 1+ f2/ { am } am floor f>s { m } two-pi n f/ { q } n 1- { jj } m 0 ?do spectr 0 vct-ref f2/ ( xt ) m 1 ?do spectr i vct-ref q i am j 1.0 f- f- f* f* fcos f* f+ ( xt += ... ) loop ( xt ) n f/ f2* { coeff } coeffs i coeff vct-set! drop coeffs jj coeff vct-set! drop -1 +to jj loop coeffs ; : fltit-1 ( order spectr -- prc; y self -- val ) doc" Create FIR filter from spectrum and order and \ return a closure that calls it:\n\ 10 vct( 0 1.0 0 0 0 0 0 0 1.0 0 ) fltit-1 map-channel." { order spectr } :order order :xcoeffs order spectr spectrum->coeffs make-fir-filter { gen } 1 proc-create ( prc ) gen , does> { y self -- val } self @ ( gen ) y fir-filter ; \ 10 vct( 0 1.0 0 0 0 0 0 0 1.0 0 ) fltit-1 map-channel \ ;;; -------- Hilbert transform : make-hilbert-transform <{ :optional len 30 -- gen }> doc" Make Hilbert transform filter." len 2* 1+ { arrlen } arrlen 0.0 make-vct { arr } len even? if len else len 1+ then { lim } lim len negate ?do i len + { kk } i pi f* { denom } 1.0 denom fcos f- { num } num f0<> i 0<> || if arr kk num denom f/ denom len f/ fcos 0.46 f* 0.54 f+ f* vct-set! drop then loop :order arrlen :xcoeffs arr make-fir-filter ; <'> fir-filter alias hilbert-transform <'> hilbert-transform "( gen :optional input 0.0 -- val ) \ The generator corresponding to make-hilbert-transform." help-set! \ 15 make-hilbert-transform value h \ lambda: <{ y -- val }> h y hilbert-transform ; map-channel \ ;;; -------- highpass filter : make-highpass <{ fc :optional len 30 -- gen }> doc" Make FIR highpass filter." len 2* 1+ { arrlen } arrlen 0.0 make-vct { arr } len len negate ?do pi i f* { denom } fc i f* fsin fnegate { num } arr i len + i 0= if 1.0 fc pi f/ f- else num denom f/ pi i f* len f/ fcos 0.46 f* 0.54 f+ f* then vct-set! drop loop :order arrlen :xcoeffs arr make-fir-filter ; <'> fir-filter alias highpass <'> highpass "( gen :optional input 0.0 -- val ) \ The generator corresponding to make-highpass." help-set! \ pi 0.1 f* make-highpass value hp \ lambda: <{ y -- val }> hp y highpass ; map-channel \ ;;; -------- lowpass filter : make-lowpass <{ fc :optional len 30 -- gen }> doc" Make FIR lowpass filter." len 2* 1+ { arrlen } arrlen 0.0 make-vct { arr } len len negate ?do pi i f* { denom } fc i f* fsin { num } arr i len + i 0= if fc pi f/ else num denom f/ pi i f* len f/ fcos 0.46 f* 0.54 f+ f* then vct-set! drop loop :order arrlen :xcoeffs arr make-fir-filter ; <'> fir-filter alias lowpass <'> lowpass "( gen :optional input 0.0 -- val ) \ The generator corresponding to make-lowpass." help-set! \ pi 0.2 f* make-lowpass value lp \ lambda: <{ y -- val }> lp y lowpass ; map-channel \ ;;; -------- bandpass filter : make-bandpass <{ flo fhi :optional len 30 -- gen }> doc" Make FIR bandpass filter." len 2* 1+ { arrlen } arrlen 0.0 make-vct { arr } len len negate ?do pi i f* { denom } fhi i f* fsin flo i f* fsin f- { num } arr i len + i 0= if fhi flo f- pi f/ else num denom f/ pi i f* len f/ fcos 0.46 f* 0.54 f+ f* then vct-set! drop loop :order arrlen :xcoeffs arr make-fir-filter ; <'> fir-filter alias bandpass <'> bandpass "( gen :optional input 0.0 -- val ) \ The generator corresponding to make-bandpass." help-set! \ pi 0.1 f* pi 0.2 f* make-bandpass value bp \ lambda: <{ y -- val }> bp y bandpass ; map-channel \ ;;; -------- bandstop filter : make-bandstop <{ flo fhi :optional len 30 -- gen }> doc" Make FIR bandstop (notch) filter." len 2* 1+ { arrlen } arrlen 0.0 make-vct { arr } len len negate ?do pi i f* { denom } fhi i f* fsin flo i f* fsin f- { num } arr i len + i 0= if 1.0 fhi flo f- pi f/ f- else num denom f/ pi i f* len f/ fcos 0.46 f* 0.54 f+ f* then vct-set! drop loop :order arrlen :xcoeffs arr make-fir-filter ; <'> fir-filter alias bandstop <'> bandstop "( gen :optional input 0.0 -- val ) \ The generator corresponding to make-bandstop." help-set! \ pi 0.1 f* pi 0.3 f* make-bandstop value bs \ lambda: <{ y -- val }> bs y bandstop ; map-channel \ ;;; -------- differentiator : make-differentiator <{ :optional len 30 -- gen }> doc" Make FIR differentiator (highpass) filter." len 2* 1+ { arrlen } arrlen 0.0 make-vct { arr } len len negate ?do i 0<> if pi i f* { pi*i } arr i len + pi*i fcos i f/ pi*i fsin pi*i i f* f/ f- pi*i len f/ fcos 0.46 f* 0.54 f+ f* vct-set! drop then loop :order arrlen :xcoeffs arr make-fir-filter ; <'> fir-filter alias differentiator <'> differentiator "( gen :optional input 0.0 -- val ) \ The generator corresponding to make-differentiator." help-set! \ make-differentiator value dt \ lambda: <{ y -- val }> dt y differentiator ; map-channel \ ;;; -------- 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. : make-butter-high-pass ( freq -- flt ) doc" Make Butterworth filter with high pass cutoff at FREQ." { freq } pi freq f* #f srate f/ ftan { r } r r f* { r2 } r 2.0 fsqrt r2 f* f* 1/f { c1 } -2.0 c1 f* { c2 } c1 { c3 } r2 1.0 f- c1 f* f2* { c4 } 1.0 2.0 fsqrt r f* f- r2 f+ c1 f* { c5 } 3 vct( c1 c2 c3 ) vct( 0.0 c4 c5 ) make-filter ; : make-butter-low-pass ( freq -- flt ) doc" Make Butterworth filter with low pass cutoff at FREQ. \ The result can be used directly: 500.0 make-butter-low-pass filter-sound, \ or via the 'butter' generator." { freq } pi freq f* #f srate f/ ftan 1/f { r } r r f* { r2 } r 2.0 fsqrt f* 1.0 r2 f* f* 1/f { c1 } c1 f2* { c2 } c1 { c3 } 1.0 r2 f- c1 f* f2* { c4 } 1.0 2.0 fsqrt r f* f- r2 f+ c1 f* { c5 } 3 vct( c1 c2 c3 ) vct( 0.0 c4 c5 ) make-filter ; : make-butter-band-pass ( freq bw -- flt ) doc" Make bandpass Butterworth filter with \ low edge at FREQ and width BAND." { freq bw } #f srate { sr } 2.0 pi freq f* f* sr f/ fcos f2* { d } pi bw f* sr f/ ftan 1/f { c } 1.0 c f+ 1/f { c1 } 0.0 { c2 } c1 fnegate { c3 } c fnegate d c1 f* f* { c4 } c 1.0 f- c1 f* { c5 } 3 vct( c1 c2 c3 ) vct( 0.0 c4 c5 ) make-filter ; : make-butter-band-reject ( freq bw -- flt ) doc" Make band-reject Butterworth filter with low edge at FREQ \ and width BAND." { freq bw } #f srate { sr } 2.0 pi freq f* f* sr f/ fcos f2* { d } pi bw f* sr f/ ftan { c } 1.0 c f+ 1/f { c1 } d fnegate c1 f* { c2 } c1 { c3 } c2 { c4 } 1.0 c f- c1 f* { c5 } 3 vct( c1 c2 c3 ) vct( 0.0 c4 c5 ) make-filter ; <'> filter alias butter <'> butter "( gen :optional input 0.0 -- val ) \ The generator side for the various make-butter procedures." help-set! \ 500.0 make-butter-low-pass filter-sound \ ;;; from "DSP Filter Cookbook" by Lane et al, Prompt Pubs, 2001 \ ;;; \ ;;; use with the filter generator \ ;;; 1000 make-iir-high-pass-2 value gen \ ;;; gen 1.0 filter \ ;;; etc : make-biquad ( a0 a1 a2 b1 b2 -- gen ) doc" Return biquad filter (use with the CLM filter gen)." { a0 a1 a2 b1 b2 } 3 vct( a0 a1 a2 ) vct( 0.0 b1 b2 ) make-filter ; : make-iir-low-pass-2 ( fc :optional d -- gen ) #( 2.0 fsqrt ) 1 get-optargs { fc d } two-pi fc f* mus-srate f/ { theta } 1.0 d f2/ theta fsin f* f- 1.0 d f2/ theta fsin f* f+ f/ f2/ { beta } beta 0.5 f+ theta fcos f* { gamma } beta 0.5 f+ gamma fnegate f+ f2/ { alpha } 3 vct( alpha alpha f2* alpha ) vct( 0.0 gamma -2.0 f* beta f2* ) make-filter ; : make-iir-high-pass-2 ( fc :optional d -- gen ) #( 2.0 fsqrt ) 1 get-optargs { fc d } two-pi fc f* mus-srate f/ { theta } 1.0 d f2/ theta fsin f* f- 1.0 d f2/ theta fsin f* f+ f/ f2/ { beta } beta 0.5 f+ theta fcos f* { gamma } beta 0.5 f+ gamma f+ f2/ { alpha } 3 vct( alpha alpha -2.0 f* alpha ) vct( 0.0 gamma -2.0 f* beta f2* ) make-filter ; : make-iir-band-pass-2 ( f1 f2 -- gen ) { f1 f2 } two-pi f1 f2 f* fsqrt mus-srate f/ { theta } f1 f2 f* fsqrt f2 f1 f- f/ { Q } theta Q f2* f/ ftan { t2 } 1.0 t2 f- 1.0 t2 f+ f/ f2/ { beta } beta 0.5 f+ theta fcos f* { gamma } 0.5 beta f- { alpha } 3 vct( alpha 0.0 alpha fnegate ) vct( 0.0 gamma -2.0 f* beta f2* ) make-filter ; : make-iir-band-stop-2 ( f1 f2 -- gen ) { f1 f2 } two-pi f1 f2 f* fsqrt mus-srate f/ { theta } f1 f2 f* fsqrt f2 f1 f- f/ { Q } theta Q f2* f/ ftan { t2 } 1.0 t2 f- 1.0 t2 f+ f/ f2/ { beta } beta 0.5 f+ theta fcos f* { gamma } 0.5 beta f+ { alpha } 3 vct( alpha gamma -2.0 f* alpha fnegate ) vct( 0.0 gamma -2.0 f* beta f2* ) make-filter ; : make-eliminate-hum <{ :optional hum-freq 60.0 hum-harmonics 5 bandwith 10 -- gen }> hum-harmonics make-array map! hum-freq i 1.0 f+ f* { center } bandwith f2/ { b2 } center b2 f- center b2 f+ make-iir-band-stop-2 end-map ( gen ) ; : eliminate-hum ( gens x0 -- val ) { gens x0 } gens each ( gen ) x0 filter to x0 end-each x0 ; \ make-eliminate-hum value hummer \ lambda: <{ y -- val }> hummer y eliminate-hum ; map-channel \ ;; bandpass, m is gain at center of peak \ ;; use map-channel with this one (not clm-channel or filter) : make-peaking-2 ( f1 f2 m -- prc; y self -- val ) { f1 f2 m } f1 f2 f* fsqrt two-pi f* mus-srate f/ { theta } f1 f2 f* fsqrt f2 f1 f- f/ { Q } 4.0 m 1.0 f+ f/ theta Q f2* f/ ftan f* { t2 } 1.0 t2 f- 1.0 t2 f+ f/ f2/ { beta } 0.5 beta f+ theta fcos f* { gamma } 0.5 beta f- { alpha } 3 vct( alpha 0.0 alpha fnegate ) vct( 0.0 -2.0 gamma f* beta f2* ) make-filter { flt } 1 proc-create ( prc ) flt , m 1.0 f- , does> { y self -- val } self @ ( flt ) y filter self cell+ @ ( m ) f* y f+ ; : cascade->canonical ( A -- A' ) doc" Convert array of cascade coeffs (vcts with 3 entries) \ to canonical form." { A } A length { K } K 2* 1+ 0.0 make-vct { d } K 2* 1+ 0.0 make-vct { a1 } a1 0 1.0 vct-set! drop A each { h } i 2* 3 + 0 ?do d i 0.0 vct-set! drop 2 i min 0 ?do d j h i vct-ref a1 j i - vct-ref f* object-set+! loop loop i 2* 3 + 0 ?do a1 j d j vct-ref vct-set! drop loop end-each a1 ; : make-butter-lp ( M fc -- flt ) doc" Return butterworth low-pass filter; \ its order is M * 2, FC is the cutoff frequency in Hz." { M fc } #() { xcoeffs } #() { ycoeffs } two-pi fc f* mus-srate f/ { theta } theta fsin { st } theta fcos { ct } M 0 ?do i 1+ 2* 1- pi f* 4 M * f/ fsin f2* { d } 1.0 d st f* f2/ f- 1.0 d st f* f2/ f/ f2/ { beta } beta 0.5 f+ ct f* { gamma } gamma fnegate beta f+ 0.5 f+ 0.25 f* { alpha } xcoeffs vct( alpha f2* alpha 4.0 f* alpha f2* ) array-push drop ycoeffs vct( 1.0 -2.0 gamma f* beta f2* ) array-push drop loop M 2* 1+ xcoeffs cascade->canonical ycoeffs cascade->canonical make-filter ; : make-butter-hp ( M fc -- flt ) doc" Return butterworth high-pass filter; \ its order is M * 2, FC is the cutoff frequency in Hz." { M fc } #() { xcoeffs } #() { ycoeffs } two-pi fc f* mus-srate f/ { theta } theta fsin { st } theta fcos { ct } M 0 ?do i 1+ 2* 1- pi f* 4 M * f/ fsin f2* { d } 1.0 d st f* f2/ f- 1.0 d st f* f2/ f/ f2/ { beta } beta 0.5 f+ ct f* { gamma } gamma beta f+ 0.5 f+ 0.25 f* { alpha } xcoeffs vct( alpha f2* alpha -4.0 f* alpha f2* ) array-push drop ycoeffs vct( 1.0 -2.0 gamma f* beta f2* ) array-push drop loop M 2* 1+ xcoeffs cascade->canonical ycoeffs cascade->canonical make-filter ; : make-butter-bp ( M f1 f2 -- flt ) doc" Return butterworth band-pass filter; \ its order is M * 2, F1 and F2 are the band edge frequencies in Hz." { M f1 f2 } #() { xcoeffs } #() { ycoeffs } f1 f2 f* fsqrt { f0 } f0 f2 f1 f- f/ { Q } two-pi f0 f* mus-srate f/ { theta0 } theta0 Q f2* f/ theta0 fsin f* f2* { de } de f2/ { de2 } theta0 f2/ ftan { tn0 } 1 1 { jj kk } M 0 ?do kk 2* 1- pi f* M 2* f/ fsin f2* { Dk } de2 de2 f* 1.0 f+ Dk de2 f* f/ { Ak } de Dk f* Ak Ak f* 1.0 f- fsqrt Ak f+ f/ fsqrt { dk1 } Dk dk1 f/ de2 f* { Bk } Bk Bk f* 1.0 f- fsqrt Bk f+ { Wk } jj 1 = if tn0 Wk f/ else tn0 Wk f* then fatan f2* { thetajk } 1.0 0.5 dk1 f* thetajk fsin f* f- 1.0 0.5 dk1 f* thetajk fsin f* f+ f/ f2/ { betajk } 0.5 betajk f+ thetajk fcos f* { gammajk } Wk Wk 1/f f- dk1 f/ { wk2 } 0.5 betajk f- wk2 wk2 f* 1.0 f+ fsqrt f* { alphajk } xcoeffs vct( alphajk f2* 0.0 -2.0 alphajk f* ) array-push drop ycoeffs vct( 1.0 -2.0 gammajk f* betajk f2* ) array-push drop jj 1 = if 2 to jj else 1 +to kk 1 to jj then loop M 2* 1+ xcoeffs cascade->canonical ycoeffs cascade->canonical make-filter ; : make-butter-bs ( M f1 f2 -- flt ) doc" Return butterworth band-stop filter; \ its order is M * 2, F1 and F2 are the band edge frequencies in Hz." { M f1 f2 } #() { xcoeffs } #() { ycoeffs } f1 f2 f* fsqrt { f0 } f0 f2 f1 f- f/ { Q } two-pi f0 f* mus-srate f/ { theta0 } theta0 Q f2* f/ theta0 fsin f* f2* { de } de f2/ { de2 } theta0 fcos { ct } theta0 f2/ ftan { tn0 } 1 1 { jj kk } M 0 ?do kk 2* 1- pi f* M 2* f/ fsin f2* { Dk } de2 de2 f* 1.0 f+ Dk de2 f* f/ { Ak } de Dk f* Ak Ak f* 1.0 f- fsqrt Ak f+ f/ fsqrt { dk1 } Dk dk1 f/ de2 f* { Bk } Bk Bk f* 1.0 f- fsqrt Bk f+ { Wk } jj 1 = if tn0 Wk f/ else tn0 Wk f* then fatan f2* { thetajk } 1.0 0.5 dk1 f* thetajk fsin f* f- 1.0 0.5 dk1 f* thetajk fsin f* f+ f/ f2/ { betajk } 0.5 betajk f+ thetajk fcos f* { gammajk } 0.5 betajk f+ 1.0 thetajk fcos f- 1.0 ct f- f/ f* f2/ { alphajk } xcoeffs vct( alphajk f2* ct alphajk f* -4.0 f* alphajk f2* ) array-push drop ycoeffs vct( 1.0 -2.0 gammajk f* betajk f2* ) array-push drop jj 1 = if 2 to jj else 1 +to kk 1 to jj then loop M 2* 1+ xcoeffs cascade->canonical ycoeffs cascade->canonical make-filter ; \ ;;; -------- notch filters : make-notch-frequency-response <{ cur-srate freqs :optional notch-width 2 -- fresp }> #( 0.0 1.0 ) ( freq-response ) freqs each { f } \ ; left upper y hz f notch-width f- f2* cur-srate f/ array-push \ ; left upper y resp 1.0 array-push \ ; left bottom y hz f notch-width f2/ f- f2* cur-srate f/ array-push \ ; left bottom y resp 0.0 array-push \ ; right bottom y hz f notch-width f2/ f+ f2* cur-srate f/ array-push \ ; right bottom y resp 0.0 array-push \ ; right upper y hz f notch-width f+ f2* cur-srate f/ array-push \ ; right upper y resp 1.0 array-push end-each #( 1.0 1.0 ) array-append ( freq-response ) ; : notch-channel <{ freqs :optional filter-order #f beg 0 dur #f snd #f chn #f edpos #f truncate #t notch-width 2 -- f }> doc" Return notch filter removing freqs." snd srate s>f freqs notch-width make-notch-frequency-response { nf } filter-order 2.0 snd srate notch-width f/ flog 2.0 flog f/ fceil f** fround->s || { order } "%s %s %s %s %s" #( freqs filter-order beg dur get-func-name ) string-format { origin } nf order beg dur snd chn edpos truncate origin filter-channel ; : notch-sound <{ freqs :optional filter-order #f snd #f chn #f notch-width 2 -- f }> doc" Return notch filter removing freqs." snd srate s>f freqs notch-width make-notch-frequency-response { nf } filter-order 2.0 snd srate notch-width f/ flog 2.0 flog f/ fceil f** fround->s || { order } "%s %s 0 #f notch-channel " #( freqs filter-order ) string-format { origin } nf order snd chn #f origin filter-sound ; : notch-selection <{ freqs :optional filter-order #f notch-width 2 -- f }> doc" Return notch filter removing freqs." undef selection? if selection-srate s>f freqs notch-width make-notch-frequency-response ( nf ) filter-order 2.0 selection-srate notch-width f/ flog 2.0 flog f/ fceil f** fround->s || ( order ) #t ( truncate ) filter-selection then ; \ ;;; -------- fractional Fourier Transform, z transform \ ;;; \ ;;; translated from the fxt package of Joerg Arndt : fractional-fourier-transform ( real imaginary n angle -- hr hi ) doc" Perform fractional Fourier transform on data; \ if angle=1.0, you get a normal Fourier transform." { fr fi n v } v two-pi f* n f/ { ph0 } n 0.0 make-vct { hi } n 0.0 make-vct map! 0.0 0.0 { sr si } fr each { x } ph0 i f* j f* { phase } phase fcos { c } phase fsin { s } phase fcos { c } fi i vct-ref { y } x c f* y s f* f- { r } y c f* x s f* f+ { i } sr r f+ to sr si i f+ to si end-each hi i si vct-set! drop sr end-map ( hr ) hi ; 'complex provided? [if] : z-transform ( data n z -- ) doc" Perform Z transform on DATA; \ if z=e^2*pi*j/n you get a Fourier transform; \ complex results in returned vector." { f n z } n make-array map! 0.0 ( sum ) 1.0 { t } z i f** { m } n 0 ?do f i vct-ref t c* c+ ( sum += ... ) t m c* to t loop ( sum ) end-map ; \ data n 0.0 2.0 n f/ pi f* make-rectangular cexp z-transform [then] \ ;;; -------- slow Hartley transform : dht ( data -- vct ) doc" Return the Hartley transform of DATA." { data } data length { len } two-pi len f/ { w } len 0.0 make-vct { arr } len 0 do len 0 do arr j data i vct-ref i j w f* f* fcos i j w f* f* fsin f+ f* object-set+! loop loop arr ; 'complex provided? [if] : find-sine ( freq beg dur -- amp ph ) doc" Return the amplitude and initial-phase (for sin) at FREQ." { freq beg dur } freq hz->radians { incr } 0.0 0.0 { sw cw } beg #f #f 1 #f make-sampler { reader } dur 0 do reader next-sample { samp } i incr c* csin samp c* sw c+ to sw i incr c* ccos samp c* cw c+ to cw loop sw dup c* cw dup c* c+ csqrt dur c/ 2.0 c* ( amp ) cw sw catan2 ( ph ) ; hide : goert-cb { y0 y1 y2 cs -- prc; y self -- f } 1 proc-create ( prc ) y0 , y1 , y2 , cs , does> { y self -- f } self 1 cells + @ self 2 cells + ! ( y2 = y1 ) self @ self 1 cells + ! ( y1 = y0 ) self 1 cells + @ { y1 } self 2 cells + @ { y2 } self 3 cells + @ { cs } y1 cs c* y2 c- y c+ self ! ( y0 = ... ) #f ; set-current : goertzel <{ freq :optional beg 0 dur #f -- amp }> doc" Return the amplitude of the FREQ spectral component." #f srate { sr } 0.0 0.0 0.0 { y0 y1 y2 } two-pi freq f* sr f/ { rfreq } rfreq fcos f2* { cs } dur unless #f #f #f framples to dur then y0 y1 y2 cs goert-cb beg dur #f #f #f scan-channel drop 0.0 rfreq fnegate make-rectangular cexp y1 c* y0 c+ magnitude ; previous [then] : make-spencer-filter ( -- gen ) doc" It's a version of make-fir-filter; \ it returns one of the standard smoothing filters \ from the era when computers were human beings." #( -3 -6 -5 3 21 46 67 74 67 46 21 3 -5 -6 -3 ) { lst } :xcoeffs lst 0.0 make-vct map! *key* 320.0 f/ end-map :order 15 make-fir-filter ; \ ;;; -------- any-random \ ;;; \ ;;; arbitrary random number distributions via the "rejection method" : any-random <{ amount :optional en #f -- r }> amount f0= if 0.0 else en if en envelope-last-x { x1 } 0.0 0.0 { x y } begin x1 random to x 1.0 random to y y x en 1.0 envelope-interp f<= until x else amount random then then ; : gaussian-distribution ( s -- en ) { s } #() { en } 2.0 s s f* f* { den } 0.0 -4.0 { x y } 21 0 do en x array-push y y f* den f/ fexp array-push to en x 0.05 f+ to x y 0.40 f* to y loop en ; : pareto-distribution ( a -- en ) { a } #() { en } 1.0 a 1.0 f+ f** a f/ { scl } 0.0 1.0 { x y } 21 0 do en x array-push a y a 1.0 f+ f** f/ scl f* array-push to en x 0.05 f+ to x y 0.20 f* to y loop en ; \ uniform distribution \ lambda: <{ y -- val }> 1.0 #( 0 1 1 1 ) any-random ; map-channel \ mostly toward 1.0 \ lambda: <{ y -- val }> 1.0 #( 0 0 0.95 0.1 1 1 ) any-random ; map-channel \ 1.0 gaussian-distribution value g \ lambda: <{ y -- val }> 1.0 g any-random ; map-channel \ 1.0 pareto-distribution value g \ lambda: <{ y -- val }> 1.0 g any-random ; map-channel \ ;;; this is the inverse integration function used by CLM to turn a \ ;;; distribution function into a weighting function : inverse-integrate <{ dist :optional data-size 512 e-size 50 -- vct }> #() { en } dist 1 array-ref exact->inexact dup { sum first-sum } dist 0 array-ref { x0 } dist envelope-last-x { x1 } x1 x0 f- e-size f/ { xincr } x0 { x } e-size 0 ?do en sum array-push x array-push to en x dist 1.0 envelope-interp sum f+ to sum x xincr f+ to x loop en -2 array-ref first-sum f- data-size 1- f/ { incr } first-sum to x data-size 0.0 make-vct map! x en 1.0 envelope-interp ( val ) x incr f+ to x end-map ( data ) ; : gaussian-envelope ( s -- en ) { s } #() { en } 2.0 s s f* f* { den } -1.0 -4.0 { x y } 21 0 do en x array-push y y f* den f/ fexp array-push to en x 0.1 f+ to x y 0.4 f* to y loop en ; \ :envelope 1.0 gaussian-envelope make-rand \ ;;; ---------------- Julius Smith stuff ---------------- \ ;;; \ ;;; these are from "Mathematics of the DFT", W3K Pubs hide : chm-cb { sum -- prc; y self -- f } 1 proc-create ( prc ) sum , does> { y self -- f } y self +! ( sum += y ) #f ; set-current : channel-mean <{ :optional snd #f chn #f -- val }> \ / n doc" Return the average of the samples in the given channel: /n." 0.0 { sum } snd chn #f framples { len } sum chm-cb 0 len snd chn #f scan-channel drop sum len f/ ; previous hide : chte-cb { sum -- prc; y self -- f } 1 proc-create ( prc ) sum , does> { y self -- f } y dup f* self +! ( sum += y ) #f ; set-current : channel-total-energy <{ :optional snd #f chn #f -- val }> \ doc" Return the sum of the squares of all the samples \ in the given channel: ." 0.0 { sum } snd chn #f framples { len } sum chte-cb 0 len snd chn #f scan-channel drop sum ; previous : channel-average-power <{ :optional snd #f chn #f -- val }> \ / n doc" Return the average power in the given channel: /n." snd chn channel-total-energy snd chn #f framples f/ ; : channel-rms <{ :optional snd #f chn #f -- val }> \ sqrt( / n) doc" Return the RMS value of the samples \ in the given channel: sqrt(/n)." snd chn channel-average-power fsqrt ; \ - ( / n) ^ 2 with quibbles : channel-variance <{ :optional snd #f chn #f -- val }> doc" Return the sample variance in the \ given channel: -((/ n)^2." snd chn #f framples { len } len len 1- f/ snd chn channel-mean f* { mu } snd chn channel-total-energy { P } P mu dup f* f- ; : channel-norm <{ :optional snd #f chn #f -- val }> \ sqrt() doc" Return the norm of the samples in the given channel: sqrt()." snd chn channel-total-energy fsqrt ; hide : chlp-cb { sum p -- prc; y self -- f } 1 proc-create ( prc ) sum , p , does> { y self -- f } y fabs self cell+ @ ( p ) f** self +! ( sum += y ** p ) ; set-current : channel-lp <{ p :optional snd #f chn #f -- val }> doc" Return the Lp norm of the samples in the given channel." 0.0 { sum } snd chn #f framples { len } sum p chlp-cb 0 len snd chn #f scan-channel drop sum p 1/f f** ; previous hide : chlpinf-cb { mx -- prc; y self -- f } 1 proc-create ( prc ) mx , does> { y self -- f } y fabs self @ fmax self ! ; set-current : channel-lp-inf <{ :optional snd #f chn #f -- val }> doc" Return the maxamp in the given channel (the name is \ just math jargon for maxamp)." 0.0 { mx } snd chn #f framples { len } mx chlpinf-cb 0 len snd chn #f scan-channel drop mx ; previous : channel2-inner-product ( s1 c1 s2 c2 -- val ) \ doc" Return the inner-product of the two channels: ." { s1 c1 s2 c2 } 0.0 { sum } 0 s1 c1 1 #f make-sampler { r1 } 0 s2 c2 1 #f make-sampler { r2 } 0.0 ( sum ) s1 c1 #f framples 0 ?do r1 next-sample r2 next-sample f* f+ ( sum += ... ) loop ( sum ) ; \ acos( / (sqrt() * sqrt())) : channel2-angle ( s1 c1 s2 c2 -- val ) doc" Treat the two channels as vectors, \ returning the 'angle' between them: acos(/(sqrt()*sqrt()))." { s1 c1 s2 c2 } s1 c1 s2 c2 channel2-inner-product { inprod } s1 c1 channel-norm { norm1 } s2 c2 channel-norm { norm2 } inprod norm1 norm2 f* f/ facos ; : channel2-orthogonal? ( s1 c1 s2 c2 -- f ) \ == 0 doc" Return #t if the two channels' inner-product is 0: ==0." { s1 c1 s2 c2 } s1 c1 s2 c2 channel2-inner-product fzero? ; \ s1,c1 = x, s2,c2 = y, / : channel2-coefficient-of-projection ( s1 c1 s2 c2 -- val ) doc" Return /." { s1 c1 s2 c2 } s1 c1 s2 c2 channel2-inner-product s1 c1 channel-total-energy f/ ; \ ;;; -------- end of JOS stuff -------- : channel-distance ( s1 c1 s2 c2 -- val ) doc" Return the euclidean distance between the two channels: \ sqrt()." { s1 c1 s2 c2 } 0 s1 c1 1 #f make-sampler { r1 } 0 s2 c2 1 #f make-sampler { r2 } 0.0 ( sum ) s1 c1 #f framples s2 c2 #f framples min 0 ?do r1 next-sample r2 next-sample f- dup f* f+ ( sum += diff^2 ) loop ( sum ) fsqrt ; : periodogram ( N -- ) doc" Display an 'N' point Bartlett periodogram \ of the samples in the current channel." { N } #f #f #f framples { len } 0 #f #f 1 #f make-sampler { rd } N 2* { N2 } N2 0.0 make-vct { rl } N 0 ?do rl i rd next-sample vct-set! drop loop N2 0.0 make-vct { im } rl im N2 1 mus-fft drop N 0.0 make-vct map! rl i vct-ref dup f* im i vct-ref dup f* f+ end-map ( average-data ) len N f/ fceil 1/f vct-scale! "average-data" 0.0 1.0 #f #f #f #f #t #t graph ; \ ;;; -------- ssb-am friends hide : scp-cb { gen -- prc; y self -- val } 1 proc-create ( prc ) gen , does> { y self -- val } self @ y 0.0 ssb-am ; set-current : shift-channel-pitch <{ freq :optional order 40 beg 0 dur #f snd #f chn #f edpos #f -- val }> :frequency freq :order order make-ssb-am { gen } "%s %s %s %s %s" #( freq order beg dur get-func-name ) string-format { origin } gen scp-cb beg dur snd chn edpos origin map-channel ; previous : hz->2pi ( freq -- r ) two-pi f* #f srate f/ ; hide : ssbmc-cb { nmx ssbs bands -- prc; y self -- val } 1 proc-create ( prc ) nmx , ssbs , bands , does> { y self -- val } self @ { nmx } self 1 cells + @ { ssbs } self 2 cells + @ { bands } 0.0 ssbs each ( gen ) bands i array-ref y bandpass 0.0 ssb-am f+ ( sum+=... ) end-each ( sum ) dup fabs nmx fmax self ! ( to nmx ) ; : ssbaoe-cb { mx ssbs bands beg dur snd chn edpos -- prc; self -- val } 0.0 { nmx } nmx ssbs bands ssbmc-cb { proc } 0 proc-create ( prc ) proc , mx , nmx , beg , dur , snd , chn , edpos , does> { self -- val } self @ { proc } self 1 cells + @ { mx } self 2 cells + @ { nmx } self 3 cells + @ { beg } self 4 cells + @ { dur } self 5 cells + @ { snd } self 6 cells + @ { chn } self 7 cells + @ { edpos } proc beg dur snd chn edpos #f map-channel drop mx nmx f/ beg dur snd chn #f scale-channel ; set-current : ssb-bank <{ old-freq new-freq pairs :optional order 40 bw 50.0 beg 0 dur #f snd #f chn #f edpos #f -- val }> pairs make-array { ssbs } pairs make-array { bands } new-freq old-freq f- old-freq f/ { factor } snd chn #f maxamp { mx } pairs 0 ?do i 1.0 f+ { idx } old-freq idx f* { aff } idx pairs f2* f/ 1.0 f+ bw f* { bwf } ssbs i :frequency idx factor f* old-freq f* :order 40 make-ssb-am array-set! bands i aff bwf f- hz->2pi aff bwf f+ hz->2pi order make-bandpass array-set! loop "%s %s %s %s %s %s %s %s" #( old-freq new-freq pairs order bw beg dur get-func-name ) string-format { origin } mx ssbs bands beg dur snd chn edpos ssbaoe-cb origin as-one-edit ; previous hide : ssbemc-cb { nmx ssbs bands frenvs -- prc; y self -- val } 1 proc-create ( prc ) nmx , ssbs , bands , frenvs , does> { y self -- val } self @ { nmx } self 1 cells + @ { ssbs } self 2 cells + @ { bands } self 3 cells + @ { frenvs } 0.0 ssbs each ( gen ) bands i array-ref y bandpass frenvs i array-ref env ssb-am f+ ( sum+=... ) end-each ( sum ) dup fabs nmx fmax self ! ( to nmx ) ( sum ) ; : ssbeaoe-cb { mx ssbs bands frenvs beg dur snd chn edpos -- prc; self -- val } 0.0 { nmx } nmx ssbs bands frenvs ssbemc-cb { proc } 0 proc-create ( prc ) proc , mx , nmx , beg , dur , snd , chn , edpos , does> { self -- val } self @ { proc } self 1 cells + @ { mx } self 2 cells + @ { nmx } self 3 cells + @ { beg } self 4 cells + @ { dur } self 5 cells + @ { snd } self 6 cells + @ { chn } self 7 cells + @ { edpos } proc beg dur snd chn edpos #f map-channel drop mx nmx f/ beg dur snd chn #f scale-channel ; set-current : ssb-bank-env <{ old-freq new-freq freq-env pairs :optional order 40 bw 50.0 beg 0 dur #f snd #f chn #f edpos #f -- val }> pairs make-array { ssbs } pairs make-array { bands } pairs make-array { frenvs } new-freq old-freq f- old-freq f/ { factor } snd chn #f maxamp { mx } snd chn #f framples 1- { len } pairs 0 ?do i 1.0 f+ { idx } old-freq idx f* { aff } idx pairs f2* f/ 1.0 f+ bw f* { bwf } ssbs i :frequency idx factor f* old-freq f* :order 40 make-ssb-am array-set! bands i aff bwf f- hz->2pi aff bwf f+ hz->2pi order make-bandpass array-set! :envelope freq-env :scaler idx hz->radians :length len make-env { en } frenvs i en array-set! loop "%s %s %s %s %s %s %s %s %s" #( old-freq new-freq freq-env pairs order bw beg dur get-func-name ) string-format { origin } mx ssbs bands frenvs beg dur snd chn edpos ssbeaoe-cb origin as-one-edit ; previous \ ;; echoes with each echo at a new pitch via ssb-am etc : make-transposer <{ old-freq new-freq pairs :optional order 40 bw 50.0 -- g }> new-freq old-freq f- old-freq f/ { factor } pairs make-array map! i 1+ factor f* old-freq f* make-ssb-am end-map { ssbs } pairs make-array map! i 1.0 f+ { idx } idx old-freq f* { aff } idx pairs f2* f/ 1.0 f+ bw f* { bwf } aff bwf f- hz->radians aff bwf f+ hz->radians order make-bandpass end-map { bands } #( ssbs bands ) ; : transpose ( gen input -- val ) { gen input } gen 0 array-ref { ssbs } gen 1 array-ref { bands } 0.0 ( sum ) ssbs each { g } g bands i array-ref input bandpass 0.0 ssb-am f+ ( sum += ) end-each ( sum ) ; : make-fdelay ( len pitch scaler -- prc; y self -- val ) { len pitch scaler } len make-delay { dly } 440.0 440.0 pitch f* 10 make-transposer { ssb } 1 proc-create ( prc ) dly , ssb , scaler , does> { y self -- val } self @ { dly } self cell+ @ { ssb } self 2 cells + @ { scaler } dly ssb dly 0.0 tap transpose scaler f* y f+ 0.0 delay ; : fdelay ( gen input -- val ) { gen input } gen #( input ) run-proc ; : transposed-echo ( pitch scaler secs -- val ) { pitch scaler secs } #f srate secs f* fround->s pitch scaler make-fdelay 0 #f #f #f #f #f map-channel ; \ ;;; vct|channel|spectral-polynomial : vct-polynomial ( v coeffs -- vct ) { v coeffs } v vct-length coeffs last-ref make-vct { new-v } coeffs vct-length 2- 0 ?do new-v v vct-multiply! coeffs i vct-ref vct-offset! drop -1 +loop new-v ; : channel-polynomial <{ coeffs :optional snd #f chn #f -- vct }> snd chn #f framples { len } "%S %s" #( coeffs get-func-name ) string-format { origin } 0 len snd chn #f channel->vct coeffs vct-polynomial 0 len snd chn #f origin vct->channel ; \ vct( 0.0 0.5 ) channel-polynomial == x*0.5 \ vct( 0.0 1.0 1.0 1.0 ) channel-polynomial == x*x*x + x*x + x \ ;;; convolution -> * in freq : spectral-polynomial <{ coeffs :optional snd #f chn #f -- vct }> snd chn #f framples { len } 0 len snd chn #f channel->vct { sound } coeffs vct-length { num-coeffs } num-coeffs 2 < if len else 2.0 num-coeffs 1 f- len f* flog 2.0 flog f/ fceil f** fround->s then { fft-len } fft-len 0.0 make-vct { rl1 } fft-len 0.0 make-vct { rl2 } fft-len 0.0 make-vct { new-sound } coeffs 0 vct-ref f0> if coeffs 0 vct-ref { dither } new-sound map! dither mus-random end-map drop then num-coeffs 1 > if new-sound sound vct-copy coeffs 1 vct-ref vct-scale! vct-add! drop num-coeffs 2 > if snd chn #f maxamp { peak } rl1 0.0 vct-scale! sound vct-add! drop 0.0 { pk } num-coeffs 2 ?do rl1 rl2 0.0 vct-scale! sound vct-add! fft-len convolution drop rl1 vct-peak to pk new-sound rl1 vct-copy coeffs i vct-ref peak f* pk f/ vct-scale! vct-add! drop loop new-sound vct-peak to pk new-sound peak pk f/ vct-scale! drop then then "%S %s" #( coeffs get-func-name ) string-format { origin } new-sound 0 num-coeffs 1- len * len max snd chn #f origin vct->channel ; \ ;;; ---------------- \ ;;; 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. : scentroid <{ file :key beg 0.0 dur #f db-floor -40.0 rfreq 100.0 fftsize 4094 -- vals }> doc" Return 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." file find-file to file file unless 'no-such-file #( "%s: %s" get-func-name file ) fth-throw then file mus-sound-srate { fsr } fsr rfreq f/ fround->s { incrsamps } beg fsr f* fround->s { start } dur if dur fsr f* else file mus-sound-framples beg d- then { end } fftsize 0.0 make-vct { fdr } fftsize 0.0 make-vct { fdi } end start d- incrsamps d/ 1 d+ { windows } windows 0.0 make-vct { results } fftsize 2/ { fft2 } fsr fftsize f/ fround->s { binwidth } file make-readin { rd } 0 { loc } end start ?do rd i set-mus-location drop 0.0 { sum-of-squares } fdr map! rd readin { val } val dup f* sum-of-squares f+ to sum-of-squares val end-map sum-of-squares fftsize f/ fsqrt linear->db db-floor f>= if 0.0 0.0 { numsum densum } fdi 0.0 vct-fill! drop fdr fdi fftsize 1 mus-fft ( fdr ) fdi rectangular->polar drop fft2 0 ?do fdr i vct-ref binwidth f* i f* numsum f+ to numsum fdr i vct-ref densum f+ to densum loop results loc numsum densum f/ vct-set! drop then loc 1+ to loc incrsamps +loop results ; \ ;;; ---------------- \ ;;; \ ;;; invert-filter inverts an FIR filter \ ;;; \ ;;; say we previously filtered a sound via \ ;;; (filter-channel (vct .5 .25 .125)) \ ;;; and we want to undo it without using (undo): \ ;;; (filter-channel (invert-filter (vct .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). : invert-filter ( fcoeffs -- res ) doc" Try to return an inverse filter to undo \ the effect of the FIR filter coeffs." { fcoeffs } fcoeffs length { flen } 32 flen + 0.0 make-vct { coeffs } coeffs length { order } flen 0 do coeffs i fcoeffs i vct-ref vct-set! drop loop order 0.0 make-vct { nfilt } nfilt 0 coeffs 0 vct-ref 1/f vct-set! drop order 1 ?do i { kk } 0.0 ( sum ) i 0 ?do nfilt i vct-ref coeffs kk vct-ref f* f+ ( sum += ... ) kk 1- to kk loop ( sum ) coeffs 0 vct-ref fnegate f/ nfilt i -rot vct-set! drop loop 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 : make-volterra-filter ( acoeffs bcoeffs -- gen ) doc" Return array for use with volterra-filter, \ producing one of the standard non-linear filters." { acoeffs bcoeffs } #( acoeffs bcoeffs acoeffs length bcoeffs length max 0.0 make-vct ) ; : volterra-filter ( flt y -- val ) doc" Take FLT, an array returned by make-volterra-filter, \ and an input X, and returns the (non-linear filtered) result." { flt y } flt 0 array-ref { as } flt 1 array-ref { bs } flt 2 array-ref { xs } as length { x1len } bs length { x2len } xs length { xlen } xs xlen 1- xlen 2- #t vct-move! drop xs 0 y vct-set! drop as xs x1len dot-product ( sum ) x2len 0 ?do x2len 0 ?do bs i vct-ref xs j vct-ref f* xs i vct-ref f* f+ ( sum += ... ) loop loop ( sum ) ; \ vct( 0.5 0.1 ) vct( 0.3 0.2 0.1 ) make-volterra-filter value flt \ lambda: <{ y -- val }> flt y volterra-filter ; map-channel \ ;;; ---------------- \ ;;; \ ;;; moving-max generator (the max norm, or uniform norm, infinity-norm) : make-moving-max <{ :optional size 128 -- gen }> doc" Return moving-max generator. \ The generator keeps a running window of the last SIZE inputs, \ returning the maxamp in that window." save-stack { s } size make-delay { gen } s restore-stack gen 0.0 set-mus-scaler drop gen ; : moving-max ( gen y -- scl ) doc" Return the maxamp in a moving window over the last few inputs." { gen y } y fabs { absy } gen absy 0.0 delay { mx } absy gen mus-scaler f>= if gen absy set-mus-scaler else mx gen mus-scaler f>= if gen gen mus-data vct-peak set-mus-scaler else gen mus-scaler then then ; \ ;;; ---------------- \ ;;; \ ;;; moving-sum generator (the sum norm or 1-norm) : make-moving-sum <{ :optional size 128 -- gen }> doc" Return moving-sum generator. \ The generator keeps a running window of the last SIZE inputs, \ returning the sum of the absolute values of the samples in that window." size make-moving-average { gen } gen 1.0 set-mus-increment drop gen ; : moving-sum ( gen y -- val ) doc" Return the sum of the absolute values in a moving window over the \ last few inputs." ( gen y ) fabs moving-average ; \ ;;; ---------------- \ ;;; \ ;;; moving-rms generator : make-moving-rms <{ :optional size 128 -- gen }> doc" Return moving-rms generator. \ The generator keeps a running window of the last SIZE inputs, \ returning the rms of the samples in that window." size make-moving-average ; : moving-rms ( gen y -- val ) doc" Return the rms of the values in a window over the last few inputs." ( gen y ) dup f* moving-average fsqrt ; \ ;;; ---------------- \ ;;; \ ;;; moving-length generator (euclidean norm or 2-norm) : make-moving-length <{ :optional size 128 -- gen }> doc" Return moving-length generator. \ The generator keeps a running window of the last SIZE inputs, \ returning the euclidean length of the vector in that window." size make-moving-average { gen } gen 1.0 set-mus-increment drop gen ; : moving-length ( gen y -- val ) doc" Return the length of the values in a \ window over the last few inputs." ( gen y ) dup f* moving-average fsqrt ; \ ;;; ---------------- \ ;;; \ ;;; more moving-average cases \ \ ;;; a simple modification is to round-off the edges: <'> make-moving-average alias make-smoothing-filter : smoothing-filter ( gen sig -- r ) { gen sig } gen sig moving-average { val } gen tap { old-sig } gen mus-order { n } n n 1- f/ val gen mus-increment 0.5 f* sig old-sig f+ f* f- f* ; \ ;;; exponential weights : make-exponentially-weighted-moving-average ( order -- r ) { order } order 1/f order fnegate 1.0 order f+ f/ make-one-pole ; <'> one-pole alias exponentially-weighted-moving-average \ ;;; arithmetic (1/n) weights : make-weighted-moving-average ( order -- r ) { order } order make-moving-average { gen } gen 1.0 set-mus-increment drop #( gen 0.0 0.0 ) ; : weighted-moving-average ( gen sig -- r ) { gen-ary sig } gen-ary 0 array-ref { gen } gen-ary 1 array-ref { num } gen-ary 2 array-ref { sum } gen mus-order { n } n 1+ n * 2/ { den } n sig f* num f+ sum f- to num gen sig moving-average to sum gen-ary num 1 array-set! gen-ary sum 2 array-set! num den f/ ; \ ;;; ---------------- \ ;;; \ ;;; 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 hide : harm-mc-cb { bands avgs peaks pcoeffs flt new-mx -- prc; y self -- val } 1 proc-create ( prc ) 40 ( ctr ) , bands , avgs , peaks , pcoeffs , flt , new-mx , does> { y self -- val } self @ { ctr } self cell+ @ { bands } self 2 cells + @ { avgs } self 3 cells + @ { peaks } self 4 cells + @ { pcoeffs } self 5 cells + @ { flt } self 6 cells + @ { new-mx } 0.0 ( sum ) bands each { bd } bd y bandpass { sig } peaks i array-ref sig moving-max { mx } avgs i array-ref mx f0> if 100.0 mx 1/f fmin else 0.0 then moving-average { amp } amp f0> if pcoeffs amp sig f* polynomial mx f* f+ ( sum += ... ) then end-each flt swap ( sum ) filter { val } val fabs new-mx fmax self 6 cells + ! ( to new-mx ) ctr 0= if val else -1 self +! ( ctr-- ) 0.0 then ; : harm-aoe-cb ( bands avgs peaks pcoeffs flt old-mx beg dur snd chn edpos -- prc; self -- val ) { bands avgs peaks pcoeffs flt old-mx beg dur snd chn edpos } 0 proc-create ( prc ) 0.0 ( new-mx ) , bands , avgs , peaks , pcoeffs , flt , old-mx , beg , dur , snd , chn , edpos , does> { self -- val } self @ { new-mx } self cell+ @ { bands } self 2 cells + @ { avgs } self 3 cells + @ { peaks } self 4 cells + @ { pcoeffs } self 5 cells + @ { flt } self 6 cells + @ { old-mx } self 7 cells + @ { beg } self 8 cells + @ { dur } self 9 cells + @ { snd } self 10 cells + @ { chn } self 11 cells + @ { edpos } bands avgs peaks pcoeffs flt new-mx harm-mc-cb beg dur snd chn edpos map-channel { retval } new-mx f0> if old-mx new-mx f/ beg dur snd chn #f scale-channel drop then { retval } ; set-current : harmonicizer <{ freq coeffs pairs :optional order 40 bw 50.0 beg 0 dur #f snd #f chn #f edpos #f -- val }> doc" Split out each harmonic and replaces it \ with the spectrum given in coeffs." pairs make-array map! i 1.0 f+ { idx } idx freq f/ { aff } idx pairs f2* f/ 1.0 f+ bw f* { bwf } aff bwf f- hz->2pi aff bwf f+ hz->2pi order make-bandpass end-map { bands } pairs make-array map! 128 make-moving-average end-map { avgs } pairs make-array map! 128 make-moving-max end-map { peaks } coeffs mus-chebyshev-first-kind partials->polynomial { pcoeffs } 2 vct( 1 -1 ) vct( 0 -0.9 ) make-filter { flt } snd chn #f maxamp { old-mx } bands avgs peaks pcoeffs flt old-mx beg dur snd chn edpos harm-aoe-cb get-func-name as-one-edit ; previous \ ;;; ---------------- \ ;;; \ ;;; linear sampling rate conversion hide : lsc-ws-cb { rd sr -- prc; self -- f } 0 proc-create ( prc ) rd next-sample , rd next-sample , 0.0 , rd , sr , does> { self -- f } self @ { last } self cell+ @ { next } self 2 cells + @ { intrp } self 3 cells + @ { rd } self 4 cells + @ { sr } 0 { samp } begin intrp { pos } pos 1.0 f>= if pos fround->s { num } num 0 ?do next to last rd next-sample to next loop pos num f- to pos then pos sr f+ to intrp samp next last f- pos f* last f+ 0 *output* out-any drop samp 1+ to samp rd sampler-at-end? until #f ; set-current : linear-src-channel <{ sr :optional snd #f chn #f -- file }> doc" Perform sampling rate conversion using linear interpolation." 0 snd chn 1 #f make-sampler { rd } rd sr lsc-ws-cb :output snd-tempnam :srate snd srate with-sound :output ws-ref { tempfile } tempfile mus-sound-framples { len } 0 len 1- tempfile snd chn #t ( trunc ) get-func-name 0 #f ( edpos ) #t ( autodel ) set-samples ; previous \ ;;; -------- spectrum displayed in various frequency scales 'snd-nogui provided? not [if] hide 0 value bark-fft-size 0 value bark-tick-function : bark ( r1 -- r2 ) { f } f 7500.0 f/ { f2 } f2 f2 f* fatan 3.5 f* 0.00076 f f* fatan 13.5 f* f+ ; : mel ( r1 -- r2 ) { f } f 700.0 f/ 1.0 f+ flog 1127.0 f* ; : erb ( r1 -- r2 ) { f } f 312.0 f+ f 14675.0 f+ f/ flog 11.17 f* 43.0 f+ ; : display-bark-fft-cb <{ snd chn -- f }> snd chn left-sample { ls } snd chn right-sample { rs } 2.0 rs ls - 1+ flog 2.0 flog f/ fceil f** fround->s { fftlen } fftlen 0<= if #f exit then ls fftlen snd chn #f channel->vct { data } snd chn transform-normalization dont-normalize <> { normalized } #t { linear } data vct? unless #f exit then data snd chn fft-window fftlen linear snd chn fft-window-beta #f normalized snd-spectrum { fft } fft vct? unless #f exit then snd srate { sr } fft vct-peak { mx } fft length { data-len } \ bark settings 20.0 bark floor { bark-low } sr f2/ bark fceil { bark-high } data-len bark-high bark-low f- f/ { bark-frqscl } data-len 0.0 make-vct { bark-data } \ mel settings 20.0 mel floor { mel-low } sr f2/ bark fceil { mel-high } data-len mel-high mel-low f- f/ { mel-frqscl } data-len 0.0 make-vct { mel-data } \ erb settings 20.0 erb floor { erb-low } sr f2/ bark fceil { erb-high } data-len erb-high erb-low f- f/ { erb-frqscl } data-len 0.0 make-vct { erb-data } fftlen to bark-fft-size fft each { val } i fftlen f/ sr f* { frq } frq bark bark-low f- bark-frqscl f* fround->s { b-bin } frq mel mel-low f- mel-frqscl f* fround->s { mel-bin } frq erb erb-low f- erb-frqscl f* fround->s { erb-bin } b-bin 0>= b-bin data-len < && if bark-data b-bin val object-set+! then mel-bin 0>= mel-bin data-len < && if mel-data mel-bin val object-set+! then erb-bin 0>= erb-bin data-len < && if erb-data erb-bin val object-set+! then end-each normalized if bark-data vct-peak { bmx } mel-data vct-peak { mmx } erb-data vct-peak { emx } mx bmx f- fabs 0.01 f> if bark-data mx bmx f/ vct-scale! drop then mx mmx f- fabs 0.01 f> if mel-data mx mmx f/ vct-scale! drop then mx emx f- fabs 0.01 f> if erb-data mx emx f/ vct-scale! drop then then #( bark-data mel-data erb-data ) "ignored" 20.0 sr f2/ 0.0 normalized if 1.0 else data-len snd chn y-zoom-slider * then snd chn #f show-bare-x-axis graph drop #f ; : scale-pos { axis-x0 axis-x1 sr2 f scale -- n } 20.0 scale execute { b20 } axis-x1 axis-x0 f- f scale execute b20 f- f* sr2 scale execute b20 f- f/ axis-x0 f+ fround->s ; : draw-bark-ticks { axis-x0 axis-x1 axis-y0 major-y0 minor-y0 tick-y0 sr2 bark-function cro snd chn } 2 snd-font ?dup-if snd chn copy-context set-current-font drop then axis-x0 tick-y0 axis-x0 major-y0 snd chn copy-context cro draw-line drop axis-x0 axis-x1 sr2 1000.0 bark-function scale-pos { i1000 } axis-x0 axis-x1 sr2 10000.0 bark-function scale-pos { i10000 } i1000 tick-y0 i1000 major-y0 snd chn copy-context cro draw-line drop i10000 tick-y0 i10000 major-y0 snd chn copy-context cro draw-line drop "20" axis-x0 major-y0 snd chn copy-context cro draw-string drop "1000" i1000 12 - major-y0 snd chn copy-context cro draw-string drop "10000" i10000 24 - major-y0 snd chn copy-context cro draw-string drop "fft size: %d" #( bark-fft-size ) string-format axis-x0 10 + axis-y0 snd chn copy-context cro draw-string drop 1000 100 do axis-x0 axis-x1 sr2 i bark-function scale-pos { i100 } i100 tick-y0 i100 minor-y0 snd chn copy-context cro draw-line drop 100 +loop 10000 2000 do axis-x0 axis-x1 sr2 i bark-function scale-pos { i1000 } i1000 tick-y0 i1000 minor-y0 snd chn copy-context cro draw-line drop 1000 +loop ; : make-bark-labels <{ snd chn -- f }> snd chn copy-context foreground-color { old-foreground-color } snd chn lisp-graph axis-info { axinfo } axinfo 10 array-ref { axis-x0 } axinfo 12 array-ref { axis-x1 } axinfo 13 array-ref { axis-y0 } axinfo 11 array-ref { axis-y1 } 15 { label-height } 8 { char-width } snd srate 2/ { sr2 } 6 { minor-tick-len } 12 { major-tick-len } axis-y1 { tick-y0 } axis-y1 minor-tick-len + { minor-y0 } axis-y1 major-tick-len + { major-y0 } 3 snd-font { bark-label-font } axis-x1 axis-x0 f- 0.45 f* axis-x0 f+ fround->s { label-pos } snd chn channel-widgets 17 list-ref { cro } \ bark label/ticks bark-tick-function 0= if axis-x0 axis-x1 axis-y0 major-y0 minor-y0 tick-y0 sr2 <'> bark cro snd chn draw-bark-ticks then bark-label-font ?dup-if snd chn copy-context set-current-font drop then "bark," label-pos axis-y1 label-height + snd chn copy-context cro draw-string drop \ mel label/ticks 2 snd-color snd chn copy-context set-foreground-color drop bark-tick-function 1 = if axis-x0 axis-x1 axis-y0 major-y0 minor-y0 tick-y0 sr2 <'> mel cro snd chn draw-bark-ticks then bark-label-font ?dup-if snd chn copy-context set-current-font drop then "mel," label-pos char-width 6 * + axis-y1 label-height + snd chn copy-context cro draw-string drop \ erb label/ticks 4 snd-color snd chn copy-context set-foreground-color drop bark-tick-function 2 = if axis-x0 axis-x1 axis-y0 major-y0 minor-y0 tick-y0 sr2 <'> erb cro snd chn draw-bark-ticks then bark-label-font ?dup-if snd chn copy-context set-current-font drop then "erb" label-pos char-width 11 * + axis-y1 label-height + snd chn copy-context cro draw-string drop old-foreground-color snd chn copy-context set-foreground-color ; : choose-bark-ticks <{ snd chn button state x y axis -- f }> axis lisp-graph = if bark-tick-function 1+ to bark-tick-function bark-tick-function 2 > if 0 to bark-tick-function then snd chn update-lisp-graph else #f then ; set-current : display-bark-fft <{ :optional off #f -- }> off unless lisp-graph-hook <'> display-bark-fft-cb add-hook! after-lisp-graph-hook <'> make-bark-labels add-hook! mouse-click-hook <'> choose-bark-ticks add-hook! sounds each { snd } snd channels 0 ?do snd i ( chn ) update-lisp-graph drop loop end-each else lisp-graph-hook <'> display-bark-fft-cb remove-hook! after-lisp-graph-hook <'> make-bark-labels remove-hook! mouse-click-hook <'> choose-bark-ticks remove-hook! sounds each { snd } snd channels 0 ?do #f snd i ( chn ) set-lisp-graph? drop loop end-each then ; previous : undisplay-bark-fft ( -- ) #t display-bark-fft ; [then] \ ;;; -------- lpc-coeffs, lpc-predict : lpc-coeffs ( data n m -- val ) doc" Return M LPC coeffients (in a vector) given N data \ points in the vct DATA." { data n m } m :initial-element 0.0 make-array { d } n :initial-element 0.0 make-array { wk1 } n :initial-element 0.0 make-array { wk2 } n :initial-element 0.0 make-array { wkm } wk1 0 data 0 vct-ref array-set! wk2 n 2- data n 1- vct-ref array-set! n 1- 1 ?do wk1 i data i vct-ref array-set! wk2 i 1- data i vct-ref array-set! loop m 0 ?do 0.0 0.0 { num denom } n i - 1- 0 ?do wk1 i array-ref wk2 i array-ref f* num f+ to num wk1 i array-ref wk2 i array-ref f* dup f* denom f+ to denom loop denom f0<> if d i num f2* denom f/ array-set! then i 0 ?do d i wkm i array-ref d j array-ref wkm j i - 1- array-ref f* f- array-set! loop i m 1- < if i 1+ 0 ?do wkm i d i array-ref array-set! loop n i - 2- 0 ?do wk1 i wk1 i array-ref wkm j array-ref wk2 i array-ref f* f- array-set! wk2 i wk2 i 1+ array-ref wkm j array-ref wk1 i 1+ array-ref f* f- array-set! loop then loop d ; : lpc-predict <{ data n coeffs m nf :optional clipped #f -- val }> doc" Take the output of lpc-coeffs (COEFFS, a vector) and \ the length thereof (M), N data points of DATA (a vct), \ and produces NF new data points (in a vct) as its prediction. \ If CLIPPED is #t, the new data is assumed to be outside -1.0 to 1.0." n 1- { jj } m 0.0 make-vct map! data jj vct-ref ( val ) jj 1- to jj end-map { reg } nf 0.0 make-vct map! 0.0 ( sum ) reg each ( val ) coeffs i array-ref f* f+ ( sum += ... ) end-each { sum } 0 m 1- do reg i reg i 1- vct-ref vct-set! drop -1 +loop clipped if sum f0> if sum 1.0 f< if 1.0 to sum then else sum -1.0 f> if -1.0 to sum then then then reg 0 sum vct-set! drop sum end-map ( future ) ; \ ;;; -------- unclip-channel hide : uncchn-sc-cb { clip-data unclipped-max -- prc; y self -- f } 1 proc-create ( prc ) clip-data , unclipped-max , 0 ( clip-beg ) , 0 ( samp ) , #f ( in-clip ) , does> { y self -- f } self @ { clip-data } self 1 cells + @ { unclipped-max } self 2 cells + @ { clip-beg } self 3 cells + @ { samp } self 4 cells + @ { in-clip } y fabs { absy } absy 0.9999 f> if in-clip unless #t to in-clip samp self 2 cells + ! ( to clip-beg ) then else absy unclipped-max fmax self 2 cells + ! ( to unclipped-max ) in-clip if #f self 4 cells + ! ( in-clip = #f ) clip-data #( clip-beg samp 1- ) array-push to clip-data then then 1 self 3 cells + +! ( samp++ ) #f ; : uncchn-aoe-cb { clip-data max-len snd chn len -- prc; self -- val } 0 proc-create ( prc ) clip-data , max-len , snd , chn , does> { self -- val } self @ { clip-data } self cell+ @ { max-len } self 2 cells + @ { snd } self 3 cells + @ { chn } self 4 cells + @ { len } 32 { min-data-len } clip-data each { lst } lst 0 array-ref { clip-beg } lst 1 array-ref { clip-end } clip-end clip-beg = 1+ { clip-len } min-data-len clip-len 4 * max { data-len } clip-len max-len > if clip-len to max-len then data-len { forward-data-len } data-len { backward-data-len } i 0= if 0 else clip-data i 1- array-ref 1 array-ref then { previous-end } i clip-data length 3 - < if clip-data i 1+ array-ref 0 array-ref else len then { next-beg } clip-beg data-len - previous-end < if clip-beg previous-end - 4 max to forward-data-len then clip-len data-len + next-beg > if next-beg clip-end - 4 max to backward-data-len then clip-len forward-data-len f2/ fround->s max { forward-predict-len } clip-len backward-data-len f2/ fround->s max { backward-predict-len } clip-beg forward-data-len - forward-data-len snd chn #f channel->vct { data } data forward-data-len data forward-data-len forward-predict-len lpc-coeffs forward-predict-len clip-len #f lpc-predict { future } clip-end 1+ backward-data-len snd chn #f channel->vct vct-reverse! { rdata } rdata backward-data-len rdata backward-data-len backward-predict-len lpc-coeffs backward-predict-len clip-len #f lpc-predict { past } clip-len 0.0 make-vct { new-data } clip-len 1 > if clip-len 1- { jj } clip-len 1- { clen } new-data map! i clen f/ pi f* fcos 1.0 f+ f2/ { sn } sn future i vct-ref f* 1.0 sn f- past jj vct-ref f* f+ jj 1- to jj end-map else new-data 0 future 0 vct-ref past 0 vct-ref future 0 vct-ref f0> if fmax else fmin then vct-set! drop then new-data clip-beg clip-len snd chn #f get-func-name vct->channel drop end-each #f ; set-current : unclip-channel <{ :optional snd #f chn #f -- assoc }> doc" Look for clipped portions and try to \ reconstruct the original using LPC." #() { clip-data } \ #( #( beg end ) #( ... ) ... ) 0.0 { unclipped-max } snd chn #f framples { len } clip-data unclipped-max uncchn-sc-cb 0 len snd chn #f scan-channel drop clip-data length 0> if 0 { max-len } clip-data max-len snd chn len uncchn-aoe-cb get-func-name as-one-edit drop unclipped-max 0.95 f> if 0.999 to unclipped-max then unclipped-max snd chn #f maxamp f/ 0 len snd chn #f scale-channel drop #a( :max unclipped-max :clips clip-data length 2/ :max-len max-len ) else #() then ; previous : unclip-sound <{ :optional snd #f -- assoc }> doc" Apply unclip-channel to each channel of SND." snd snd-snd to snd snd sound? if snd channels make-array map! snd i unclip-channel end-map ( res ) #() ( assoc ) snd channels 0 ?do ( assoc ) snd i unclip-channel assoc loop ( assoc ) else 'no-such-sound #( "%s: %s" get-func-name snd ) fth-throw then ; \ dsp.fs ends here