;;; -*- syntax: common-lisp; package: clm; base: 10; mode:lisp -*- ;;; ;;; Granular synthesis instrument ;;; Fernando Lopez-Lezcano ;;; ;;; original grani.ins instrument written for the 220a Course by ;;; Fernando Lopez-Lezcano & Juan Pampin, November 6 1996 ;;; ;;; 3/21/97 Working with hop and grain-dur envelopes ;;; 3/22/97 Working with src envelope (grain wise)& src spread ;;; v7 nando, started work on 1/26/1998 ;;; Nov 7 1998: input soundfile duration calculation wrong ;;; Nov 10 1998: bug in in-samples (thanks to Kristopher D. Giesing for this one) ;;;----------------------------------------------------------------------------- ;;; Auxilliary functions ;;; calculate a random spread around a center of 0 (defmacro random-spread (spread) `(if (/= ,spread 0) (- (random ,spread) (/ ,spread 2)) 0)) ;;; convert a time in seconds to a number of samples (defmacro to-samples (time srate) `(floor (* ,time ,srate))) ;;; create a constant envelope if argument is a number (defun envelope-or-number (in) (if (numberp in)(list 0 in 1 in) in)) ;;; create an array from an envelope (defun make-gr-env (env &optional (length 512)) (let* ((env-arr (make-short-float-array length)) (length-1 (1- length))) (loop for i from 0 below length do (setf (aref env-arr i)(make-short-float (eref env (/ i length-1))))) env-arr)) ;;;----------------------------------------------------------------------------- ;;; Grain envelopes (defun raised-cosine (&key (duty-cycle 100) (length 128)) (let* ((a (make-short-float-array length)) (active (* length duty-cycle 0.01)) (incr (/ pi (1- active))) (start (/ (- length active) 2)) (end (/ (+ length active) 2))) (loop with s = 0 for i from 0 below length for sine = (if (and (>= i start)(< i end)) (prog1 (sin (* s incr)) (incf s)) 0) do (setf (aref a i)(make-short-float (* sine sine)))) a)) ;;;============================================================================= ;;; Granular synthesis instrument ;;;============================================================================= ;;; amp-envelope: ;;; amplitude envelope for the note ;;; grain-envelope: ;;; grain-envelope-end: ;;; envelopes for each individual grain. The envelope applied in the result ;;; of interpolating both envelopes. The interpolation is controlled by ;;; grain-envelope-trasition. If "grain-envelope-end" is nil interpolation ;;; is turned off and only grain-envelope is applied to the grains. ;;; grain-envelope-trasition: ;;; an enveloper that controls the interpolation between the two grain envelopes ;;; 0 -> selects "grain-envelope" ;;; 1 -> selects "grain-envelope-end" ;;; grain-duration: ;;; envelope that controls grain duration in seconds ;;; srate-linear: ;;; t -> sample rate envelope is linear ;;; nil -> sample rate envelope is exponential ;;; srate: ;;; envelope that controls sample rate conversion. The envelope is an ;;; exponential envelope, the base and error bound of the conversion ;;; are controlled by "srate-base" and "srate-error". ;;; srate-spread: ;;; random spread of sample rate conversion around "srate" ;;; srate-base: ;;; base for the exponential conversion ;;; for example: base = (expt 2 (/ 12)) converts a semitone envelope ;;; srate-error: ;;; error bound for the exponential conversion. ;;; grain-start: ;;; envelope that determines the starting point of the current grain in ;;; the input file. "y"->0 starts the grain at the beginning of the input ;;; file. "y"->1 starts the grain at the end of the input file. ;;; grain-start-spread: ;;; random spread around the value of "grain-start" ;;; grain-density: ;;; envelope that controls the number of grains per second generated in the output file ;;; grain-density-spread: ;;; envelope that controls a random variation of density (defparameter grani-grains 0) (defparameter grani-amp-envelope '(0 0 0.3 1 0.7 1 1 0)) (defparameter grani-grain-envelope '(0 0 0.3 1 0.7 1 1 0)) (defparameter grani-grain-envelope-end nil) (defparameter grani-grain-envelope-transition '(0 0 1 1)) (defparameter grani-grain-duration 0.1) (defparameter grani-grain-duration-spread 0) (defparameter grani-grain-duration-limit 0.002) (defparameter grani-srate 0.0) (defparameter grani-srate-spread 0.0) (defparameter grani-srate-linear nil) (defparameter grani-srate-base (expt 2 (/ 12))) (defparameter grani-srate-error 0.01) (defparameter grani-grain-start '(0 0 1 1)) (defparameter grani-grain-start-spread 0) (defparameter grani-grain-density 10) (defparameter grani-grain-density-spread 0) (defparameter grani-reverb-amount 0.01) (defparameter grani-reverse nil) (defparameter grani-where-to 0) (defparameter grani-grain-distance 1) (defparameter grani-grain-distance-spread 0) (defparameter grani-grain-degree 0.5) (defparameter grani-grain-degree-spread 0) (defparameter grani-where-bins '()) (defconstant grani-to-locsig 0) (defconstant grani-to-grain-length 1) (defconstant grani-to-grain-start 2) (defconstant grani-to-grain-sample-rate 3) (defconstant grani-to-grain-random 4) (definstrument grani (start-time duration amplitude file &key (grains grani-grains) (amp-envelope grani-amp-envelope) (grain-envelope grani-grain-envelope) (grain-envelope-end grani-grain-envelope-end) (grain-envelope-transition grani-grain-envelope-transition) (grain-duration grani-grain-duration) (grain-duration-spread grani-grain-duration-spread) (grain-duration-limit grani-grain-duration-limit) (srate grani-srate) (srate-spread grani-srate-spread) (srate-linear grani-srate-linear) (srate-base grani-srate-base) (srate-error grani-srate-error) (grain-start grani-grain-start) (grain-start-spread grani-grain-start-spread) (grain-density grani-grain-density) (grain-density-spread grani-grain-density-spread) (reverb-amount grani-reverb-amount) (reverse grani-reverse) (where-to grani-where-to) (where-bins grani-where-bins) (grain-distance grani-grain-distance) (grain-distance-spread grani-grain-distance-spread) (grain-degree grani-grain-degree) (grain-degree-spread grani-grain-degree-spread)) (multiple-value-bind (beg end) (get-beg-end start-time duration) (let* ((in-file (open-input file)) (in-file-sr (snd-srate in-file)) (in-file-dur (/ (snd-frames in-file) in-file-sr)) (last-in-sample (floor (* in-file-dur in-file-sr))) ;; ratio between input and output sampling rates (srate-ratio (/ in-file-sr sampling-rate)) ;; sample rate converter for input samples (in-file-reader (make-src :file in-file :start-time 0 :srate 1.0)) ;; sample rate conversion envelope (sr-linear (if srate-linear 1 0)) (sr-env (make-env :envelope (if srate-linear (envelope-or-number srate) (exp-env (envelope-or-number srate) :base srate-base :error srate-error)) :scaler srate-ratio :start-time start-time :duration duration)) ;; sample rate conversion random spread (sr-spread-env (make-env :envelope (envelope-or-number srate-spread) :start-time start-time :duration duration)) ;; amplitude envelope for the note (amp-env (make-env :envelope amp-envelope :scaler amplitude :start-time start-time :duration duration)) ;; grain duration envelope (gr-dur (make-env :envelope (envelope-or-number grain-duration) :start-time start-time :duration duration)) (gr-dur-spread (make-env :envelope (envelope-or-number grain-duration-spread) :start-time start-time :duration duration)) ;; position in the input file where the grain starts (gr-start-env (make-env :envelope (envelope-or-number grain-start) :start-time start-time :duration duration)) ;; random variation in the position in the input file (gr-start-spread (make-env :envelope (envelope-or-number grain-start-spread) :start-time start-time :duration duration)) ;; density envelope in grains per second (gr-dens-env (make-env :envelope (envelope-or-number grain-density) :start-time start-time :duration duration)) ;; density spread envelope in grains per second (gr-dens-spread-env (make-env :envelope (envelope-or-number grain-density-spread) :start-time start-time :duration duration)) ;; grain envelope (gr-env (make-table-lookup :frequency 1.0 :initial-phase 0.0 :wave (if (arrayp grain-envelope) grain-envelope (make-gr-env grain-envelope)))) ;; grain envelope (gr-env-end (make-table-lookup :frequency 1.0 :initial-phase 0.0 :wave (if grain-envelope-end (if (arrayp grain-envelope-end) grain-envelope-end (make-gr-env grain-envelope-end)) (make-table)))) ;; envelope for transition between grain envelopes (gr-int-env (make-env :envelope (envelope-or-number grain-envelope-transition) :start-time start-time :duration duration)) (interp-gr-envs (not (null grain-envelope-end))) ;; envelope for distance of grains (for using in locsig) (gr-dist (make-env :envelope (envelope-or-number grain-distance) :start-time start-time :duration duration)) (gr-dist-spread (make-env :envelope (envelope-or-number grain-distance-spread) :start-time start-time :duration duration)) ;; envelopes for angular location and spread of grain in the stereo field (gr-degree (make-env :envelope (envelope-or-number grain-degree) :start-time start-time :duration duration)) (gr-degree-spread (make-env :envelope (envelope-or-number grain-degree-spread) :start-time start-time :duration duration)) ;; signal locator in the stereo image (loc (make-locsig :degree 45 :distance 1)) ;; array of condition bins (bins (make-array (length where-bins) :initial-contents where-bins)) ;; variables used and initialized inside the run loop (in-samples 0) (gr-start beg) (gr-from-beg 0) (in-start 0) (in-start-percent 0) (gr-duration 0) (gr-samples 0) (gr-offset (1+ gr-samples)) (gr-dens 0) (gr-dens-spread 0) (gr-srate 0) (grain-counter 0) (samples 0) (first-grain t) (gr-where 0) (where) (deg 0) (dist 0) (dist-scl 0)) (if reverse (read-backward in-file-reader)) (run (loop for i from beg do (if (< gr-offset gr-samples) ;; ;; send sample to output ;; (progn (if interp-gr-envs (setf gr-where (env gr-int-env))) (locsig loc (floor (+ gr-start gr-offset)) (* (if interp-gr-envs (+ (* (- 1 gr-where) (table-lookup gr-env)) (* gr-where (table-lookup gr-env-end))) (table-lookup gr-env)) (env amp-env) (src in-file-reader))) ;; increment pointer in grain (incf gr-offset)) (progn ;; ;; start of a new grain ;; ;; now, why do we zero this sample? If we don't then some grains have ;; a missing tail and we get clicks in the output. Putting a call to ;; locsig does _not_ get rid of the clicks, only doing explicit outs. ;; If this sample is not zeroed then we would not call outn during ;; one iteration of the loop. Maybe there's a connection between that ;; and the cause of the problem. (loop with now = (floor (+ gr-start gr-offset)) for chan from 0 below (channels *current-output-file*) do (out-any now 0 chan)) (if first-grain ;; first grain always start at 0 (setf first-grain nil gr-start beg) (progn ;; start grain in output file using increments from previous grain (setf gr-start (+ gr-start (to-samples (/ (+ gr-dens gr-dens-spread)) sampling-rate))) ;; finish if start of grain falls outside of note bounds or number of grains exceeded (if (or (> gr-start end) (if (/= grains 0) (>= grain-counter grains) nil)) (progn (clm-print "; grains: ~d, sample ratio: ~f~%" grain-counter (/ samples (- end beg))) (clm::loop-finish))))) (setf ;; back to the beginning of the grain gr-offset 0 ;; start of grain in samples from beginning of note gr-from-beg (floor (- gr-start beg)) ;; reset out-time dependent envelopes to current time (read-position amp-env) gr-from-beg (read-position gr-dur) gr-from-beg (read-position gr-dur-spread) gr-from-beg (read-position sr-env) gr-from-beg (read-position sr-spread-env) gr-from-beg (read-position gr-start-env) gr-from-beg (read-position gr-start-spread) gr-from-beg (read-position gr-dens-env) gr-from-beg (read-position gr-dens-spread-env) gr-from-beg ;; start of grain in input file in-start-percent (+ (env gr-start-env) (random-spread (env gr-start-spread))) in-start (to-samples (* in-file-dur in-start-percent) in-file-sr) ;; duration in seconds of the grain gr-duration (max grain-duration-limit (+ (env gr-dur) (random-spread (env gr-dur-spread)))) ;; number of samples in the grain gr-samples (to-samples gr-duration sampling-rate) ;; new sample rate for grain gr-srate (if (= sr-linear 0) (* (env sr-env) (expt srate-base (random-spread (env sr-spread-env)))) (+ (env sr-env) (random-spread (env sr-spread-env)))) ;; set new sampling rate conversion factor (sr-incr in-file-reader) gr-srate ;; number of samples in input in-samples (floor gr-samples (/ 1 srate-ratio)) ;; restart grain envelopes (tbl-phase gr-env) 0.0 (tbl-phase gr-env-end) 0.0 ;; reset grain envelope durations (frequency gr-env)(/ 1 gr-duration) (frequency gr-env-end)(/ 1 gr-duration) ;; ;; move position in output file for next grain ;; gr-dens (env gr-dens-env) ;; increment spread in output file for next grain gr-dens-spread (random-spread (env gr-dens-spread-env)) ;; gather some statistics samples (+ samples gr-samples) grain-counter (+ grain-counter 1)) ;; decide where this grain is going to go (setf where (cond (;; use length of grains as delimiter (= where-to grani-to-grain-length) gr-duration) (;; use start in input file as delimiter (= where-to grani-to-grain-start) in-start-percent) (;; use sampling rate as delimiter (= where-to grani-to-grain-sample-rate) gr-srate) (;; use a random number as delimiter (= where-to grani-to-grain-random)(random 1.0)))) (if where (loop for c from 0 below (- (length bins) 1) do (setf (aref (locs-outn loc) c) (if (< (aref bins c) where (aref bins (+ c 1))) 1.0 0.0))) ;; cero or unknown: use normal n-channel locsig (setf (read-position gr-dist) gr-from-beg (read-position gr-dist-spread) gr-from-beg (read-position gr-degree) gr-from-beg (read-position gr-degree-spread) gr-from-beg ;; set locsig parameters, for now only understands stereo deg (+ (env gr-degree)(random-spread (env gr-degree-spread))) dist (+ (env gr-dist)(random-spread (env gr-dist-spread))) dist-scl (/ 1.0 dist) (aref (locs-outn loc) 0)(* (- 1.0 deg) dist-scl) (aref (locs-outn loc) 1)(* deg dist-scl) (aref (locs-revn loc) 0)(* reverb-amount (sqrt dist-scl)))) ;; check for out of bounds condition in in-file pointers (if (> (+ in-start in-samples) last-in-sample) (setf in-start (- last-in-sample in-samples)) (if (< in-start 0) (setf in-start 0))) ;; reset position of input file reader (setf (read-position in-file-reader) in-start))))) ;; close file (close-input in-file))))