developing a set of data filters which could be used for an application such as audio processing in java

  • The objective of this project is to practice producing class hierarchies, by developing a set of data filters which could be used for an application such as audio processing. This project will involve using abstract classes and inheritance to create several related classes which share functionality.

    Overview:

    1. Create the abstract class DataSource, which allows data to be read one floating point number at a time.
    2. Create three subclasses of DataSource, namely ArraySource which draws its input from an array; SineSource which produces a sine-wave output; and DataFilter, which draws its input from another DataSource.
    3. Create subclass of DataFilter called ScaleFilter, which scales (multiplies) the input by a constant; and SineScaleFilter, which scales the input by a sine wave.
    4. Create a subclass of DataFilter called BufferedFilter, which uses a circular buffer to remember a range of previous input.
    5. Create a subclass of BufferedFilter called EchoFilter which echoes the input with a certain delay.

    Audio processing typically involves taking streams of digital data – streams of discrete, oscillating numbers – and performing various mathematical operations on them. However, there are a number of different operations you can use to process an audio stream – from the simple, like amplitude scaling (increasing and decreasing the volume), to more complicated, like filters which isolate certain audio frequencies, to specialized ones which produce different audio effects. In this project, we will set up a framework for audio filtering, and produce a few basic filters.Rules

    1. You may not import any extra functionality besides the default (i.e. System, Math)
    2. The main method will not be tested; you may use it any way you want.
    3. All fields must be declared private or protected. This will be verified manually.
    4. You may write your own helper methods, but any methods which are specifically asked for must match exactly (i.e. capitalization of the method name; number and order of the parameters).

    DataSource: ( 10p)
    public abstract class DataSource implements java.util.Iterator
    We can begin with a class that represents a generic source of data. The data we will be dealing with is a stream of Double data (remember that Double is just the reference version of the double primitive type). The data from the stream is to be read one floating point number at a time, for as long as there is data left on the stream. This class is abstract – our data-reading methods are left there as a placeholder for future classes to override. The class should implement the following:

    • public DataSource() we don’t need to do anything at this point, but it’s still nice to have a constructor as a placeholder.
    • @Override public abstract Double next(); this would pull the next unit of data from the stream; not implemented yet.
    • @Override public abstract boolean hasNext(); this would tell us if there is more data left in the stream; not implemented yet.
    • public void display() while hasNext() indicates that there is more data left in this source, println the result of toString() (hint: there’s nothing fancy about this method, and it can be written in as little as one line of code). The result will be a visual depiction of the data stream.
    • @Override public String toString() this pulls a value from the source using next() and uses it to produce a text string containing a single asterisk surrounded by whitespace. Use the following code: @Override public String toString() {
      double d = next();
      if (d < -5 || d > 5) return "";
      int i = (int)(7*d + 40);
      return String.format("%" + i + "s", "*");
      }

    Notice how the class declares that it implements some kind of iterator? That’s something which we gained for free, just by ensuring that we gave our methods the right names.ArraySource: (10p)
    public class ArraySource extends DataSourceWe already have data sources which have no data! Let’s do something about that. This basic data source will allow you to enter an array of Double values to use as input. Calls to next() will begin with the first value in the array, and continue in order until there are no more values in the array, at which point hasNext() will begin to return false. The only new method in this class will be the constructor, although next() and hasNext() must be overriden so that the class is no longer abstract:

    • public ArraySource(Double[] a) initializes the source using the array a (assume that it’s not null.
    • @Override public Double next().
    • @Override public boolean hasNext().

    SineSource: (10p)
    public class SineSource extends DataSourceThe SineSource is another source of data, but this time it generates its own data. As you may be able to guess from the name, it generates a sine wave. A sine wave has several parameters: the amplitude a represents how large it is; the frequency f represents how quickly it oscillates; and the offset d represents the moment it begins. Together, they form an equation like the following:a×sin(f×t + d)
    The variable t above represents the time step: the first time you call next() corresponds to zero, the next time to 1, etc. Assume that the angle is in radians, to be consistent with Java’s built in Math functionality. We want the input to be finite so our constructor will include a maximum number of time steps before the sine wave runs out. You will implement:

    • public SineSource(Double a, Double f, Double d, int steps) initializes the source using the given parameters; a, f, and d correspond to parameters in the sine equation, while steps is the total number of steps for which the SineSource will have data.
    • @Override public Double next() Retrieves the next unit of data from the sine wave.
    • @Override public boolean hasNext() return false once the SineSource has exceeded all of its allotted steps.

    Once you’ve created this class and you think it works, you can try the following as a simple test to see what happens:new SineSource(3.0, Math.PI/20, 0.0, 100).display();
    DataFilter: (10p)
    public class DataFilter extends DataSourceThis class takes another DataSource as input, and sends it directly through to the next() method. This will not be useful in and of itself, but we will derive classes later which modify this functionality in order to be able to do something interesting with the data. Like with the other sources, you have the following to implement:

    • public DataFilter(DataSource src) initializes so that the output of src is being used to send data to the output of this filter.
    • @Override public Double next() initializes so that the output of src is being used to send data from input to output.
    • @Override public boolean hasNext() this should depend on whether the input filter object still has data in it or not.

    ScaleFilter: (10p)
    public class ScaleFilter extends DataFilterThis filter doesn’t just pass the input to the output: it also multiplies it by a scaling constant before doing so. So if it has a scaling constant of c=5.0 and it recieves an input of 0.2, then the output it produces with next() will be 1.0. This filter will implement the following:

    • public ScaleFilter(DataSource src, Double c) initializes the filter with the given data source and scaling constant c.
    • @Override public Double next().

    SineScaleFilter: (10p)
    public class SineScaleFilter extends DataFilterThis filter is similar to the ScaleFilter, except that instead of multiplying by a constant at every step, it multiplies by a sine wave (see SineSource) at every step. This filter will implement the following:

    • public SineScaleFilter(DataSource src, Double a, Double f, Double d) initializes the filter with the given data source and sine wave parameters a for amplitude, f for frequency, and d for offset.
    • @Override public Double next().

    Implementation note: although you must derive this class from DataFilter, you are not required to derive directly from DataFilter. You can either leave the declaration as-is, or use a variant (for example, derive SineScaleFilter from ScaleFilter, or derive both of them from some intermediate class).BufferedFilter: (20p)
    public class BufferedFilter extends DataFilterWhile you’re reading data through the filter, it’s possible to use a buffer (in this case an array of Double values) to remember input which you’ve already seen. For example, if you have an array of 20 Double values, then you can remember the past 20 input values which have passed through your filter. If you want to remember which data you’ve seen 5 time units ago, all you need to do is look back 5 places in your array.There’s a catch, though: when you begin, you only have a fixed-size buffer, but your input can go on for a long time. The input may be so long, that you don’t even want to keep a buffer large enough to capture all of it. Maybe your input is 100,000 time units long, but you only care about remembering the past 20 time steps. A solution is to use a circular buffer. The buffer fills up, but when it gets to the end, it starts back at the beginning and starts overwriting what’s already there. Basically, the buffer has a moving head and a moving tail.So if our buffer is length 20, at the beginning, our head is at position 0, our tail is at position 1, and the data for the previous time step is at position 19, and five time steps ago is at position 15. We read in a bit of data, and it gets stored at position 0. Now, the head is at position 1, the tail is at position 2, the previous time step (the one you’ve just read in) is at position 0, and five time steps ago is at position 16. Etc…For this class you implement a circular buffer which will store input values as you read them from the source, after which you forward them out as-is through the filter. You will implement the following:

    • public BufferedFilter(DataSource src, int bufSize) initializes the filter with the given data source and a buffer of length bufSize. Hint: it’s expected that your array will be all zeros at the start, but if you declare a list of Double values, their initial value would be null instead of zero.
    • public Double getLast(int t) retrieve the memory of the input from t time units ago. If t is zero, then it refers to the most recently read input. Assume that t is within the range of the buffer size, otherwise the result is undefined.
    • public Double inputNext() this retrieves the next input value from the input DataSource, stores it in the circular buffer (updating anything you need to update in the process), and returns the value you’ve just read in.
    • @Override public Double next() uses inputNext() to send on the next input from the source as-is.

    EchoFilter: (10p)
    public class EchoFilter extends BufferedFilterThis filter uses the memory of previous inputs to produce an echo effect. At every step, it will output a value which is the sum of the current input and an input from some n steps ago. To do this, the following needs to be implemented:

    • public EchoFilter(DataSource src, int echo) initializes the filter with the given data source and an echo delay of echo. To produce an echo which is delayed by a certain number of steps, Think about how big of a buffer we need: if we want a delay of 1 step, for example, then our memory would need to store both the current step and the previous step, so two steps. What about if we want an echo delay of 5 steps? We’d need the previous five steps plus the current step, so a 6 step memory. So if we wanted a delay of length echo, how big of a buffer do we need to ask for?
    • @Override public Double next() produces an output which is the result of adding the current input to the input from echo steps ago

"Is this question part of your assignment? We can help"

ORDER NOW