Telecom programs 3 and 4

Due: June 29

You are to write the following two programs, again involving manipulation of .wav files. Both involve loading the sound file into a big array, eg short[] samples, and then applying the "convolution" operation of setting result[i] to be a suitable "moving average" of values in samples[].

Specifically, convolution of an array samples[] by a "kernel" K, a shorter array of double of length len, means to set result[n] as follows:

	double sum = 0;
	for (int i = 0; i<len; i++) sum += samples[i+n]*K[len-i-1];
	result[n] = (short) sum;
Purists do the reversed indexing of K, as above, but if you replace K[len-i-1] with K[i], all you have to do is make sure that K itself is created in the reversed order. If K is symmetrical, you won't even have to do that.

You can do two things when you reach the upper end of the array samples[]: quit at n = samples.length - len, or else use 0.0 wherever you need a value samples[n] for n ≥ samples.length.

Program 3: echo

Add back to the original a delayed and slightly reduced copy of itself. The delay and attenuation should be specified by constants declared at the beginning of your program, or else read in or provided on the command line.

You can do this directly: result[i] = samples[i]+A*samples[i-delay]. Or you can convolve by a kernel of the following form:

1 0 0 0 0 0 ... (length depends on delay) ... 0 0 A
(That kernel is of the "backwards" orientation).

Program 4: bandpass

Create a kernel to pass only frequencies in the range between f1 and f2, where f1 and f2 are given at the beginning of your program. The length of the kernel will also be given.

The simplest approach is to create two separate kernels: a lowpass filter to reject all frequencies > f2, and a highpass filter to then reject all frequencies < f1. However, it's best to combine these into a single kernel, by convolving them together. If you do this, you must handle the "tails" properly. Consider each kernel as extended by 0's in each direction, and allow for any possible degree of overlap. If n is the length of each separate kernel, then their convolution will have length 2*n-1.

The lowpass filter uses the sinc function, y = sin(x)/x. Specifically, let f_low = f2/samplerate (do the division as a double; note f_low is the higher of f1 and f2), and let n be the number of components to be used on either side of the center. The entire kernel will thus have length 2*n+1. Ideally n will be a multiple of the wavelength 1.0/f_low.

	L[i] = A*Math.sin(2*pi*f_low*(i-n)) / (i-n);

as i goes from 0 to 2*n, except for i=n. At i=n (where the denominator is 0), set L[i] = A*2*pi*f_low (This is the corrected value; before I had just L[n]=A; that was wrong).

The above kernel ends abruptly at the ends; to taper it more gradually, the following tweak (the "Blackman window") is recommended:

    for (int i = 0; i<=n; i++) {
        double factor = 0.42 - 0.5*Math.cos(pi*i/n) + 0.08*Math.cos(2*pi*i/n);
        L[i] *= factor;
        L[2*n-i] *= factor;
    }
This "cosine" wave rises and falls just once in the entire range, versus many wavelengths for the sin part above.

After you create your basic filter, without A, "normalize" it by adding up the sum of all the coefficients and then multiplying every coefficient by A = 1.0/sum. This will ensure that the sum of the coefficients is now 1.0.

To create a highpass filter H, just subtract the lowpass filter. In other words,

	H[i] = -L[i], i ≠ n
	H[n] = 1.0 - L[n];
I implemented this as a method invert() in my SincFilter class; I would create the two filters as follows:
    SincFilter Hi = new SincFilter((double)f1/samplerate, n);
    Hi.invert();
Note that the high- and low-pass filters will have different fundamental frequencies.

Demo

Use your mixer to mix three distinct tones (eg the chord with frequencies 262 (C), 330 (E), 392 (G)), and then use your bandpass filter to pick out the middle tone only.