There are three elements of this step and two questions you must answer. The first programming task requires you to configure a simple circular queue delay and parameterize it to experiment with audio delays. You'll modify the AudioProcess program you worked on recently. This step will focus on short-time delays and delays on the output (feedback). You'll be adding a dialog box to configure you program.
The second task requires you to test and validate several filters.
The third task has you creating a reson filter, a powerful filter for digital audio.
The programming elements are due 10-8-09, the written elements are due 10-12-09.
1. I want to build a filter that eliminates the frequency 1000Hz, assuming a sample rate of 44,100 samples per second. I don't care what this filter does to other frequencies, only that 1000Hz is eliminated. Write a transfer equation for this filter.
2. A common device in broadcasting is the profanity delay. When you press "dump", some of the delay is discarded so we can skip over the occasional utterance. Then the system starts to build the delay back up again. Describe how you would implement this device. Include how you will implement the delay, how you will implement the dump, and how you will implement the catch-up feature. Be sure your catch-up solution will not cause pitch changes in the audio or any "dead air". There are many possible ways to accomplish this.
I have created a page on using a circular queue as an audio delay. You want to use a circular queue because you want to be efficient. In this step you will create a circular queue that can tap your delay in multiple places, adding each location with a different weight. You'll need an audio frequency sweep file to get an idea of what some of this is doing. The AudioProcess application can generate that sweep file for you. Also, run this on some of the longer example audio. The delay points for this step are to be given in samples, not time.
We describe many types of editing operations using transfer equations. These are equations that express an output sample as a function of the current and past input samples and, potentially, past output samples. Here is an example transfer equation:
yt = xt
This is simply saying that the output at time t is the same as the input at time t for our simple filter. This if functionally identical to our "copy" operation. Here's another example:
yt = xt + xt-44100
This equation means that we are going to mix (add) the current input to the input 44,100 samples earlier. This is going back in time 1 second, assuming a 44,100Hz sample rate.
We'll commonly weigh the terms of a transfer equation:
yt = 0.5 xt + 0.5 xt-44100
Here, I'm adding the current sample and the sample one second ago, each with a weight of 0.5.
Notice: The delay is expressed as samples, not time. If I want what happened one sample earlier, that would be xt-1. The actual time is not a factor here. In the page describing how to create a circular queue, I assumed you wanted to look back in time a certain amount (in seconds). So, you do this calculation:
int delaylength = int( (DELAY * SampleRate() + 0.5)) * NumChannels();However, if we are talking about delays in samples, delaylength is simply the amount of sample we want to delay multipled by the number of channels:
int delaylength = delayinsample * NumChannels();For this step, just assume everything is stereo, so you would have two channels.
Given our previous examples of transfer equations, what do you think this will do:
yt = xt + 0.5 * yt-44100
Note that I have a y term on the right side. This is feedback, the use of past outputs of a filter as future inputs. You implement feedback by simply having two circular queues, one for the input and one for the output. Your program will do the following steps:
You have to be careful with feedback. What do you think the following equation will do?
We'll need to specific transfer equations for our program. We'll also need to describe them in our class. Here's a file format we'll use for transfer equations:
3 0 0.5 1 0.23 100 -0.5 2 1 0.01 6 -0.55
The first number is the number of x terms in the transfer equation. In this case we will have three. Those terms will be expressed as three pairs, each consisting of an integer delay amount followed by a weight. After we have listed the x terms, we have a single integer telling how many y terms we will have. In this case there are 2. These are described that same as the x terms. The transfer equation that this file describes is:
yt = 0.5 xt + 0.23 xt-1 - 0.5 xt-100 + 0.01 yt-1 - 0.55 yt-6
This file format has been chosen because it is VERY easy to read in. The extension we'll use for the files will be .tran. Save the above file, you're going to need it soon. Describing this filter in memory is easy to do as well. Simple create a struct (as a private member of your document class) like this:
struct FTerm {
int m_delay;
double m_weight;
};
Then, just use lists to list the terms for the x and y values:
std::list<FTerm> m_xterms;
std::list<FTerm> m_yterms;
You need to load the transfer function file into the member variables you just created. Create a menu option for this task and add the following code at the beginning of the menu handler function:
static WCHAR BASED_CODE szFilter[] = L"Filter Transfer Equation Files (*.tran)|*.tran|All Files (*.*)|*.*||";
CFileDialog dlg(TRUE, L".tran", NULL, 0, szFilter, NULL);
if(dlg.DoModal() != IDOK)
return;
ifstream filt(dlg.GetPathName());
if(filt.fail())
{
AfxMessageBox(L"Failed to open filter transfer equation file");
return;
}
|
I'm going to remind you here that you have to #include <fstream> to use this and put "using namespace std;" at the front of the module. In the future, I will no longer remind you of these issues for standard template library elements. You should know how to do them yourself already and I shouldn't have to tell you |
This puts up a File Open dialog box for the file and gets it open. You should add the code to make this routine read in the file format and store it in m_xterms and m_yterms.
Now, create a filter function that performs the filtering using your transfer equation. Remember, you need two delay queues. Make your queue's large enough to accommodate just over one second of stereo audio. Use floating point for your intermediate values and range check the result.
Note: If you think about it, you can see that the write location is the same for both the x and y queue, so you can use the same variable. Be very careful to test that your program works right for this transfer equation:
Here's the original test audio to compare against:
![]()
Try each of the following. What do they do? Try them with both audio and then try to figure out a way that you can quantify the response of the filter.
|
|
|
A problem with some of these filters is that they increase the
audio gain causing a lot of clipping. Here's the last filter with the gain
dropped by 0.2 so we don't get any clipping: ![]()
A very powerful filter for audio processing is the reson filter. This is a very simple filter that emphasizes one specific frequency range. Note that that range may be emphasized quite a lot, resulting in a very loud output. You may want to add a gain factor to decrease the amplitude of this filter.
To design a reson filter, you need to decide a frequency to emphasize and the bandwidth of the filter. The frequency to emphasize is a number between 0 and 0.5, because it's a normalized frequency. Try emphasizing something like 500Hz. Assuming a 44,100 Hz sample rate, this will be a normalized frequency of 0.01134. Next, decide a bandwidth. This is a number that defines how selective the filter is. A narrow bandwidth (small values) makes the filter more selective. A wide bandwidth makes it less selective. Let f be the resonant filter and b be the bandwidth. Note that b is in the range 0 to 1, though you can't use zero, as this makes the filter infinitely emphasize one frequency and it becomes unstable. Example values that work are: f=0.01134 and B=0.01.
Given these values, the equation for a reson is as follows. Note the intermediate values we compute.
R = 1 - B/2
cos q = (2Rcos (2pf)) / (1 + R2)
A = (1 - R2) sin q
yt = Axt + (2Rcos q)yt-1 - R2yt-2
If you are using Firefox, the symbols may not be correct in this equation. Here is a link a version as an image.
This equation may appear confusing at first, but it is correct. It is a more general version than in the class notes. The value of f is the resonant frequency, but is not actually the pole angle theta, because the second pole does pull the frequency bump a bit. The second equation computes the cosine of theta directly and yes, it does get multiplied by 2R again. So, where do you get sinq? Well, you can just to cos-1(cosq). A better choice is to recognize that (cosq)2 + (sinq)2 = 1, so...
Create the Reson filter in your program and create a dialog box that allows you to enter the frequency and bandwidth of the filter.
Example with f=0.01134 and b=0.01 ![]()
Example with f=0.045351 and b=0.01
![]()
The equations here are designed to keep the amplitude of the frequencies that
are passed that same as that of the input. Since we are keep some content
the same, but dropping a lot of other content, the amplitude of the result goes
down quite a bit. If you want to make the amplitude greater, simply multiply A
in the equations above by a constant gain. It should be obvious why this
works. Here's the example with f=0.045351 and b=0.01 and a gain of 10.
![]()