;;; SNDWARP (provide 'snd-sndwarp.scm) (if (not (provided? 'snd-env.scm)) (load "env.scm")) ; normalize-envelope ;;; ;;; CLM 3 implementation of Richard Karpen's SNDWARP Csound Ugen. ;;; By Bret Battey. http://www.BatHatMedia.com ;;; translated to Scheme by Bill S Feb-05 ;;; changes for the optimizer 24-Oct-06 ;;; ;;; Except as noted below, the parameters are modeled directly after ;;; the Csound version of sndwarp. ;;; ISSUES ;;; ;;; Output in this new CLM version is seeming quite noisy/clipped (?) ;;; Varying stereo/mono input/output has not been tested in clm2 alterations. ;;; Hasn't been tested with differing input and output file sampling rates. ;;; Uses half-sine envelope only; doesn't support alternative windowing envs. ;;; Csound-style attack doesn't strictly match Csound results ;;; SNDWARP KEY PARAMETERS ;;; ;;; amp = Amplitude ;;; [number] ;;; ;;; amp-env = Amplitude envelope ;;; [envelope] ;;; ;;; stretch = Stretch value or time pointer envelope (see 'time-ptr') ;;; [number or envelope expressed in either stretch values ;;; (for stretch mode) or in seconds (in time-ptr mode)] ;;; ;;; srate = Resampling scalar (1 = same pitch, .5 = 1 octave lower, etc.) ;;; A negative srate will read backwards into the soundfile from ;;; the start of each read window (not available in Csound version). ;;; [number or envelope] ;;; ;;; inputbeg = Source file input offset. In 'stretch' mode (see 'time-ptr'), ;;; soundfile read will begin at inputbeg. In 'time-ptr' mode, ;;; inputbeg will be added to the time pointer. ;;; [number, in seconds] ;;; ;;; wsize = Size of the sndwarp windows. ;;; [number, in seconds] ;;; ;;; randw = Range of random values to be added to wsize ;;; [number, in seconds] ;;; ;;; overlaps = number of window overlaps ;;; [number per second] ;;; ;;; time-ptr = Flag to determine whether stretching or time-pointer mode ;;; is to be used in interpreting the 'stretch' parameter. ;;; In stretch mode, the value of 'stretch' will scale the time ;;; of the sound. For example, a value of 2 will stretch the sound ;;; by 2 times. Note that stretch values of or near 0 are not ;;; viable since window advance times are determined by dividing ;;; by the stretch value. ;;; In time-ptr mode, the value(s) of stretch are readin pointers ;;; into the soundfile. For example, to read through a file ;;; backwards from 2 seconds at half speed, one would use a ;;; stretch envelope like [0 2 1 0] with a 4 second note duration. ;;; [NIL = stretch mode, T = time-ptr mode] ;;; ;;; scale-time-ptr = Flag to determine whether the time-ptr envelope will be ;;; interpreted in absolute seconds or rescaled to fit the ;;; duration of the input sound file. ;;; {not part of csound implementation} ;;; [NIL = absolute, T = rescale] ;;; ;;; zero-start-time-ptr = Flag to determine when in time-ptr mode whether ;;; the first section of windows will start at ;;; time-ptr = 0. ;;; The csound sndwarp makes this assumption, so you ;;; always get a bit of the attack of the sound even ;;; if you try to run the time pointer starting in ;;; the middle or end. ;;; [NIL = first section starts according to time-ptr, ;;; T = first section always starts at time-ptr = 0] ;;; ;;; sndwarp-window-offset = Flag to determine how the windows are offset ;;; in time. T = Csound sndwarp style, windows ;;; in different layers line up. ;;; NIL = spread evenly. ;;; ;;; loc = Stereo panning position, where 0 = left and 1 = right. ;;; Uses simple sqrt method. ;;; [number or envelope] ;;; ;;; rev = Scalar for reverb sending to a CLM reverb instrument. ;;; [number or envelope] ;;; ;;; status = Flag to control whether SNDWARP prints a window %-complete count ;;; while working. ;;; [NIL = no status printing, T = status printing] ;;; ;;; srcwidth = width of the sinc function used in the interpolation function of ;;; CLM's "src" -- which provides the resampling in sndwarp. Defaults to ;;; 5. If you hear high-frequency artifacts in the output sound, try ;;; increasing this number. ;;; ;;; SNDWARP DEFAULTS (define sndwarp-amp 1.0) (define sndwarp-amp-env '(0 1 100 1)) (define sndwarp-stretch 1.0) (define sndwarp-srate 1.0) (define sndwarp-inputbeg 0.0) (define sndwarp-wsize 0.1) ; csound manual recommended start = .1 (define sndwarp-randw 0.02) ; csound manual recommended start = .02 (define sndwarp-overlaps 15) ; csound manual recommended start = 15 (define sndwarp-time-ptr #f) (define sndwarp-scale-time-ptr #f) (define sndwarp-zero-start-time-ptr #f) ; #t to match csound (define sndwarp-window-offset #f) ; #t to match csound (define sndwarp-loc 0.5) (define sndwarp-rev 0.1) (define sndwarp-srcwidth 5) ;;; UTILITY FUNCTIONS (define clmsw-2pi (* 2 pi)) ;;; SNDWARP (define* (sndwarp begtime dur file (amp sndwarp-amp) (amp-env sndwarp-amp-env) (stretch sndwarp-stretch) (srate sndwarp-srate) (inputbeg sndwarp-inputbeg) (wsize sndwarp-wsize) (randw sndwarp-randw) (overlaps sndwarp-overlaps) (time-ptr sndwarp-time-ptr) (scale-time-ptr sndwarp-scale-time-ptr) (zero-start-time-ptr sndwarp-zero-start-time-ptr) (window-offset sndwarp-window-offset) (loc sndwarp-loc) (rev sndwarp-rev) (srcwidth sndwarp-srcwidth)) (define (clmsw-envelope-or-number in) (if (number? in) (list 0 in 1 in) in)) (let* ((stereo-i (= (mus-sound-chans file) 2)) (f-a (make-readin file :channel 0)) (f-b (and stereo-i (make-readin file :channel 1)))) (let ((beg (seconds->samples begtime)) (fsr (mus-sound-srate file)) (rdA (make-src :input (lambda (dir) (readin f-a)) :srate 0.0 :width srcwidth)) (rdB (and stereo-i (make-src :input (lambda (dir) (readin f-b)) :srate 0.0 :width srcwidth))) (windf (make-oscil)) (wsizef (make-env (clmsw-envelope-or-number wsize) :duration dur)) (ampf (make-env amp-env :scaler amp :duration dur)) (sratef (make-env (clmsw-envelope-or-number srate) :duration dur)) (timef (let ((time-env (clmsw-envelope-or-number stretch)) (fdur (mus-sound-duration file))) (make-env (if (and time-ptr scale-time-ptr) (normalize-envelope time-env (- fdur inputbeg)) time-env) :duration dur))) (locf (make-env (clmsw-envelope-or-number loc) :duration dur))) (let ((end (+ beg (seconds->samples dur))) (stereo-o #f) (writestart 0) (readstart (round (* fsr inputbeg))) (eow-flag #f) (overlap-ratio 0.0000) (overlap-ratio-compl 0.0000) (outa-val 0.0000) (outb-val 0.0000)) (do ((overlap 0 (+ 1 overlap))) ((or eow-flag (= overlap overlaps))) (set! overlap-ratio (/ overlap overlaps)) (set! overlap-ratio-compl (- 1 overlap-ratio)) (set! eow-flag #f) (set! writestart beg) (set! (mus-location ampf) beg) (set! (mus-location locf) beg) (do ((section 0 (+ 1 section))) ((or eow-flag (= overlap overlaps))) (set! (mus-location timef) writestart) (set! (mus-location sratef) writestart) (set! (mus-location wsizef) writestart) (set! wsize (env wsizef)) (let* ((winlen (if (= overlap 0 section) ; first section of first overlap isn't randomized wsize (+ wsize (random randw)))) (winsamps (seconds->samples winlen)) (srate-val (env sratef))) (let ((time-val (env timef))) ;; Even for the first section's truncated envelopes, the frequency of the envelope must be as if the envelope were full duration. (set! (mus-frequency windf) (* .5 (/ fsr winsamps))) ;; Set windowing oscillator to starting phase and appropriate frequency to provide half-sine envelope over window. ;; Phase must be altered for first envelope of each overlap stream. (set! (mus-phase windf) (if (and (= section 0) (not (= overlap 0))) (* .5 clmsw-2pi overlap-ratio-compl) 0.0)) ;; Either use the absolute time pointer or a scaled increment. ;; If first section in scaled mode, must initialize section readstart to beginning plus first overlap position. ;; In both cases, need to alter readstart and length of first section's windows based on phase of overlap (if time-ptr ;; TIME-PTR mode (if (= section 0) ;; initial section (let ((overlap-start (if (and window-offset (not (= overlap 0))) ;; Csound style - start each overlap series further into the soundfile (round (* winlen overlap-ratio-compl)) ;; Alternative style - start each overlap series at 0 0)) ;; To match csound version, first section must start reading at 0. Using zero-start-time-ptr ;; flag = #f, however, allows first section to start as determined by time-ptr instead. (adj-time-val (if zero-start-time-ptr 0.0 time-val))) (set! readstart (round (* fsr (+ inputbeg overlap-start adj-time-val)))) (if (not (= overlap 0)) (set! winsamps (floor (* winsamps overlap-ratio))))) ;; remaining sections (set! readstart (round (* fsr (+ inputbeg time-val))))) ;; STRETCH mode (if (= section 0) ;; initial section (let ((init-read-start (if (and window-offset (not (= overlap 0))) ;; Csound style - start each overlap series further into the soundfile (round (* winlen overlap-ratio-compl)) ;; Alternative style - start each overlap series at 0 0))) (set! readstart (round (* fsr (+ inputbeg init-read-start)))) (if (not (= overlap 0)) (set! winsamps (floor (* winsamps overlap-ratio))))) ;; remaining sections (set! readstart (round (+ readstart (* fsr (/ winlen time-val)))))))) ;; Set readin position and sampling rate (set! (mus-location f-a) readstart) (set! (mus-increment rdA) srate-val) (mus-reset rdA) (if stereo-i (begin (set! (mus-location f-b) readstart) (set! (mus-increment rdB) srate-val) (mus-reset rdB))) ;; Write window out (do ((k 0 (+ 1 k)) (i writestart (+ i 1))) ((or eow-flag (= k winsamps))) (if (> i end) (begin (set! eow-flag #t) (set! overlap (+ 1 overlaps))) (let* ((amp-val (env ampf)) (loc-val (env locf)) (win-val (oscil windf)) (sampa (* (src rdA) win-val)) (sampb (if stereo-i (* (src rdB) win-val)))) ;; channel panning (if stereo-o (let ((apan (sqrt loc-val)) (bpan (sqrt (- 1 loc-val)))) (set! outa-val (* amp-val apan sampa)) (set! outb-val (* amp-val bpan (if stereo-i sampb sampa)))) ;; stereo in, mono out (set! outa-val (* amp-val (if stereo-i (* (+ sampa sampb) .75) ;; mono in, mono out sampa)))) ;; output (outa i outa-val) (if stereo-o (begin (outb i outb-val) (if *reverb* (outa i (* rev outa-val) *reverb*))))))) (if (and (not eow-flag) ;; For first section, have to backup readstart (= section 0) (> overlap 0) (not time-ptr)) (set! readstart (- readstart (round (* fsr winlen overlap-ratio-compl))))) (set! writestart (+ writestart winsamps)))))))))