Sound Processing Techniques - part 1 of 2:
Soundfile Manipulation

by Nicky Hind

List of Topics


Introduction

Techniques for processing digitally sampled sound have been existence for many years, and were inherited from approaches commonly used in the analogue domain in relation to "Musique Concrete" or "Tape Music". These techniques include splicing and copying sections of sound, reversing, enveloping, looping, and changing the speed (and pitch) of the original sound, all of which (and many more) are easily achieved in the digital domain.

Before going into a discussion of how to implement various techniques in CLM, it should be pointed out that there are a variety of graphical user interface (GUI) applications which offer sound editing capabilities, and often implement various sound processing techniques. These can often be a lot more intuitive to work with, but on the other hand, implementation is usually hidden from the user. CLM provides a more explicit means of dealing with these readily available techniques.


Enveloping: Example 1:
Simple Readin Instrument with Envelope Control

All this instrument does is to read in a specified soundfile, envelope it, and write out the new enveloped version as another soundfile. It will nevertheless serve to introduce some of the CLM functions and syntax in relation to such operations.

(definstrument simple-readin (start-time file &key
		;; the arguments are as follows:
		;; DURATION is the amount of the soundfile (in seconds)
		;; to read in (description of why it is set to -1.0 follows)
		;; ONSET is where (in seconds) in the soundfile to begin reading from
		;; AMPSCL is a scaler on the amplitude of the soundfile
		;; AMP-ENV-SHAPE is the envleope shape to apply to the soundfile
					      (duration -1.0) (onset 0.0) (ampscl 1.0)
					      (amp-env-shape '(0 0  50 1  100 1)))
		;; the input soundfile is assigned to the variable, f.
       (let ((f (open-input file)))
		;; UNWIND-PROTECT (as it implies) makes sure that everything is 
		;; cleaned up properly if you happen to interrupt computation.
	    (unwind-protect
	     (let* ((beg (floor (* start-time sampling-rate)))
		;; Setting the default duration to -1.0 is a programming ploy (`hack')
		;; to facilitate the specification that the duration will be the actual
		;; duration fo the soundfile. So if DURATION is given some
		;; positive value, then this is taken as the desired duration,
		;; otherwise, the duration will be the duration of the soundfile -
		;; the function CLM-GET-DURATION provides an easy way to find this out.
		;; It is also necessary to take into account any onset time, as this
		;; will obviously shorten the overall duration of the output soundfile,
		;; and must be subtracted from the given duration.
		        (real-duration (if (plusp duration) 
				  			      (- duration onset)
				                 (- (clm-get-duration f) onset)))
		        (end (+ beg (floor (* sampling-rate real-duration))))
		;; the variable TWO-CHANS is another programming ploy, this time
		;; to ascertain whether or not both input and ouput soundfiles are
		;; stereo. If both are, then two-chans will get the value T (true),
		;; otherwise its value will be NIL.
		        (two-chans (and (stereo f) (stereo *current-output-file*)))
		;; Here, the READIN structure is defined and given 
		;; appropriate argument values.
		        (readin-a (make-readin :file f :start-time onset :channel :A))
		;; Notice that if the value of TWO-CHANS is NIL, the READIN-B
		;; structure will not be made.
		        (readin-b (when two-chans 
				     (make-readin :file f :start-time onset
					          :channel :B)))
		;; Envelope structure. As seen before.
		        (amp-env (make-env :envelope amp-env-shape
				       :scaler ampscl 
				       :start-time start-time
				;; NB duration argument now REAL-DURATION
				       :duration real-duration))
		;; Some variables that will be need for the RUN loop.
		        (out-sig-a 0.0) (out-sig-b 0.0) (env-val 0.0)
			    (cnt 1))

	     (Run
		   (loop for i from beg to end do
		;; Since the RUN loop has to cater for the possibility of precessing
		;; either a stereo or mono file, we must be careful that if the file
		;; _is not_ stereo (ie. TWO-CHANS is NIL) that the READIN-A structure
		;; is only called once per iteration, and that if it _is_ stereo, 
		;; (ie. TWO-CHANS is T) that only one envelope value is obtained
		;; per iteration.
			  (setf env-val (env amp-env))
		      (setf out-sig-a (* env-val (readin readin-a)))
		      (when two-chans
			    (setf out-sig-b (* env-val (readin readin-b))))
		      (outa i out-sig-a)
		;; if processing in stereo, send the processed READIN-B to ouput
		;; channel B, otherwise send it the same as channel A
		      (if two-chans (outb i out-sig-b) (outb i out-sig-a))
		;; here's a handy way to keep track of what is going on when the
		;; instrument is called with WITH-SOUND. Every time a whole seconds'
		;; worth of sound has been computed that number of seconds will be 
		;; printed out. This way you can know for example that CLM is not 
		;; just sitting doing nothing, or has hit an infinite loop.
             (when (= i (* sampling-rate cnt)) (print cnt) (incf cnt))))
		;; CLOSE-INPUT ensures that the soundfile gets wrapped up and put
		;; safely back in its box! 
		;; NB. This is outside the RUN loop.
	       (close-input f)))))


Some Calls to the simple-readin Instrument

1. With the default triangular envelope shape:

	(with-sound () (simple-readin 0 "<pathname of your soundfile>"))

2. With a more crazy envelope shape:

	(with-sound () (simple-readin 0 "<pathname of your soundfile>"
		:amp-env-shape '(0.000 1.000 4.818 0.005 14.161 0.998 23.212 0.012 40.876 1.000 53.285 0.000 60.584 1.000 71.387 0.002 78.102 1.000 92.555 0.000 100.146 0.941)))


Reversing: Example 2:
Simple Readin Reverse Instrument with Envelope Control

Functionally, this instrument is exactly the same as the last one, other than that the soundfile is reversed.

(definstrument reverse-readin (start-time file 
	       &key (duration -1.0) (onset 0.0) (ampscl 1.0)
		;; the envelope shape defaults to a flat line at 1
		;; thus leaving the envelope of the soundfile unmodified 
		    (amp-env-shape '(0 1  100 1)))
  (let ((f (open-input file)))
    (unwind-protect
      (let* ((beg (floor (* start-time sampling-rate)))
	     (real-duration (if (plusp duration) 
		  	      (- duration onset)
			      (- (clm-get-duration f) onset)))
	     (end (+ beg (floor (* sampling-rate real-duration))))
	     (two-chans (and (stereo f) (stereo *current-output-file*)))
 	;; since the sound is being reversed, the position to start
	;; reading the soundfile from, is the not the beginning (as above),
	;; but the end. Consequently the START-TIME argument is given the
	;; value of REAL-DURATION.
	;; Everything else is as the previous instrument.
	     (reverse-a (make-reverse :file f :start-time real-duration 
		  			     :channel :A))
	     (reverse-b (when two-chans (make-reverse :file f 
			                    :start-time real-duration 
					   :channel :B)))
	     (amp-env (make-env :envelope amp-env-shape
				  :scaler ampscl 
				  :start-time start-time 
				  :duration real-duration))
	     (out-sig-a 0.0) (out-sig-b 0.0) (env-val 0.0)
	     (cnt 1))

	  (Run
	   (loop for i from beg to end do
		 (setf env-val (env amp-env))
		 (setf out-sig-a (* env-val (readin-reverse reverse-a)))
		 (when two-chans
		   (setf out-sig-b (* env-val (readin-reverse reverse-b))))
		 (outa i out-sig-a)
		 (if two-chans (outb i out-sig-b) (outb i out-sig-a))
		 (when (= i (* sampling-rate cnt)) (print cnt) (incf cnt))))
	  (close-input f)))))


Some Calls to the reverse-readin Instrument

1. Default values:

	(with-sound () (reverse-readin 0 "<pathname of your soundfile>"))

2. Starting 1 second in from the end of the soundfile:

	(with-sound () (reverse-readin 0 "<pathname of your soundfile>" :onset 1))

3. Overlaying a `reversed' envelope over the reversed sound:

	(with-sound () (reverse-readin 0 "<pathname of your soundfile>" 
			:amp-env-shape '(0 0  75 1  100 0)))


Sampling Rate Conversion: Example 3a:
Basic Sampling Rate Conversion

Sampling rate conversion can be used to alter the pitch of a sound. It is analogous in tape music to speeding up or slowing down the tape speed, and likewise, a side effect is to modify the duration of the soundfile in proortion to the ratio of the sampling rate conversion (ie. change of speed). CLM offers two structures for doing sampling rate conversion: RESAMPLE which performs the task using linear interpolation, and SRC which convolves the input file with a sinc function.

Here are examples of instruments using both methods. Firstly, with RESAMPLE

(definstrument basic-sr-conversion (start-time file src-ratio 
	       &key (duration -1.0) (onset 0.0) (ampscl 1.0)
		    (amp-env-shape '(0 1  100 1)))
  (let ((f (open-input file)))
    (unwind-protect
      (let* ((beg (floor (* start-time sampling-rate)))
	;; As well as taking into account any given onset time
	;; DURATION is made to be inversely proportional to the SRC-RATIO.
	;; Otherwise the structures in this instrument 
	;; are all similar to those seen so far
	     (real-duration (if (plusp duration) 
		  	     (/ (- duration onset) src-ratio)
			     (/ (- (clm-get-duration f) onset) src-ratio)))
	     (end (+ beg (floor (* sampling-rate real-duration))))
	     (two-chans (and (stereo f) (stereo *current-output-file*)))
	     (sr-convert-a (make-resample :file f :srate src-ratio
					 :start-time onset :channel :A))
	     (sr-convert-b 
	       (when two-chans 
		 (make-resample :file f :srate src-ratio
			        :start-time onset  :channel :B)))
	     (amp-env (make-env :envelope amp-env-shape
			        :scaler ampscl 
			        :start-time start-time 
			        :duration real-duration))
	     (out-sig-a 0.0) (out-sig-b 0.0) (env-val 0.0)
	     (cnt 1))

	 (Run
	   (loop for i from beg to end do
		 (setf env-val (env amp-env))
		 (setf out-sig-a (* env-val (resample sr-convert-a)))
		 (when two-chans
		   (setf out-sig-b (* env-val (resample sr-convert-b))))
		 (outa i out-sig-a)
		 (if two-chans (outb i out-sig-b) (outb i out-sig-a))
		 (when (= i (* sampling-rate cnt)) (print cnt) (incf cnt))))
	  (close-input f)))))


Some Calls to the basic-sr-convert Instrument

1. Transposition up one octave:

	(with-sound (:channels 2) (basic-sr-conversion 0
					 "<pathname of your soundfile>" 2.0))

2. Transposition down one octave:

	(with-sound (:channels 2) (basic-sr-conversion 0
					"<pathname of your soundfile>" 0.5))


Sampling Rate Conversion: Example 3b:
`Upmarket' Sampling Rate Conversion with Enveloping

Here is an instrument using SRC (ie. with sinc function convolution), and with one other feature: the sampling rate conversion ratio is not constant, but is enveloped during the course of the soundfile.

(definstrument sr-convert-env (start-time file src-ratio 
               &key (duration -1.0) (onset 0.0) (ampscl 1.0)
                    (amp-env-shape '(0 1  100 1))
		;; additional envelope shape for src-ratio
                    (src-env '(0 1  100 1)))
  (let ((f (open-input file)))
    (unwind-protect
	(let* ((beg (floor (* start-time sampling-rate)))
	       (real-duration (if (plusp duration) 
		  	     (/ (- duration onset) src-ratio)
			     (/ (- (clm-get-duration f) onset) src-ratio)))
	       (end (+ beg (floor (* sampling-rate real-duration))))
	       (two-chans (and (stereo f) (stereo *current-output-file*)))
	       (sr-convert-a (make-src :file f :srate src-ratio
				       :start-time onset :channel :A))
	       (sr-convert-b (when two-chans 
                                (make-src :file f :srate src-ratio
                                          :start-time onset  :channel :B)))
	;; The envelope structure for the SRC-RATIO
	       (src-ratio-env (make-env :envelope src-env
				       	:scaler 1.0 :offset 0.0
					:start-time start-time
					:duration real-duration))
	       (amp-env (make-env :envelope amp-env-shape
				  :scaler ampscl 
				  :start-time start-time 
				  :duration real-duration))
	       (out-sig-a 0.0) (out-sig-b 0.0)
	       (amp-env-val 0.0) (src-env-val 0.0)
	       (cnt 1))

	  (Run
	   (loop for i from beg to end do
		 (setf amp-env-val (env amp-env))
		 (setf src-env-val (env src-ratio-env))
		 (setf out-sig-a (* amp-env-val (src sr-convert-a src-env-val)))
		 (when two-chans
		   (setf out-sig-b (* amp-env-val (src sr-convert-b src-env-val))))
		 (outa i out-sig-a)
		 (if two-chans (outb i out-sig-b) (outb i out-sig-a))
		 (when (= i (* sampling-rate cnt)) (print cnt) (incf cnt))))
	  (close-input f)))))


Some Calls to the sr-convert-env Instrument

1. Enveloping the transposition such that the sound starts off one octave up, and finishes one octave down. (Notice that the range of the envelope's "y-axis" extends to 2 -- illegal, of course for amplitude envelopes, but OK here).

	(with-sound () (sr-convert-env 0 "<pathname of your soundfile>" 1.0
			       :src-env '(0 2  100 0.5)))
2. The opposite of the previous example:

	(with-sound () (sr-convert-env 0 "<pathname of your soundfile>" 0.5
			       :src-env '(0 0.5  100 2.0)))


Granulation: Example 4:
Time Expansion without Changing Pitch

Expansion and contraction without altering pitch can be achieved in CLM using the structure, EXPAND. Here is Bill Schottstaedt's description of the function.

It is the poor man's way to change the speed at which things happen in a recorded sound without changing the pitches. It works by slicing the input file into short pieces, then overlapping these slices to lengthen (or shorten) the result, sometimes known as granular synthesis, and similar to the "freeze" function.

If this is the "poor man's way", then the rich man's way would probably be to use the phase vocoder, but nevertheless, EXPAND can produce some nice results in many of cases.

(definstrument expand-sound (start-time file  
	       &key (duration -1.0) 
		    (onset 0.0) 
		    (expand-amt 1.0))
  (let ((f (open-input file)))
    (unwind-protect
      (let* ((beg (floor (* start-time sampling-rate)))
	;; duration is computed to take account of the expansion factor
	     (real-duration (if (plusp duration) 
			      (* (- duration onset) expand-amt)
			      (* (- (clm-get-duration f) onset) expand-amt)))
	     (end (+ beg (floor (* real-duration sampling-rate))))
	     (two-chans (and (stereo f) (stereo *current-output-file*)))
	     (expand-a (make-expand f :start-time onset 
		                     :expansion-amount expand-amt))
	     (expand-b (if two-chans 
			  (make-expand f :start-time onset
					 :expansion-amount expand-amt
					 :channel :B)))
	     (out-sig-a 0.0) (out-sig-b 0.0)
	     (cnt 1))

	(Run
	   (loop for i from beg to end do
		 (setf out-sig-a (expand expand-a))
		 (when two-chans
		   (setf out-sig-b (expand expand-b)))
		 (outa i out-sig-a)
		 (if two-chans (outb i out-sig-b) (outb i out-sig-a))
		 (when (= i (* sampling-rate cnt)) (print cnt) (incf cnt))))
	  (close-input f)))))

Here is a description of the various parameters and their defaults for MAKE-EXPAND (from "ins.lisp"). Parameters with defaults are shown in parentheses with their default values.

MAKE-EXPAND
file-nameinput file name
&key
start-timestart-time (secs) in the input file
startsame as above, but in samples
(channel :A)which channel this expand gen reads
(segment-length . 15)length of file slices that are overlapped
(segment-scaler .6) amplitude scaler on slices (to avoid overflows)
(expansion-amount 1.0) how much to lengthen or compress the file
(output-hop .05)speed at which slices are repeated in output
(ramp-time .4)amount of slice-time spent ramping up/down


Some Calls to the expand-sound Instrument

1. Expansion by a factor of 1.5:

	(with-sound () (expand-sound 0 "/dist/220/sounds/my-voice.snd" 
				:expand-amt 1.5))

2. Compression by a factor of 0.5:

	(with-sound () (expand-sound 0 "/dist/220/sounds/my-voice.snd" 
				:expand-amt 0.5))


Table of Contents
The Basics | Additive Synthesis (1/2) | Additive Synthesis (2/2) | Frequency Modulation Synthesis (1/2)
Frequency Modulation Synthesis (2/2) | Sound Processing (1/2) | Sound Processing (2/2) | Physical Modelling