Deriving New Class from Existing Audio Class

Status
Not open for further replies.

gfvalvo

Well-known member
Hi all.
I'm thinking of a custom Audio analysis class that I want to create based on one of the existing FFT classes. So, I'd like this class to just inherit from AudioAnalyzeFFT1024. It would provide some functions of its own as well as calling functions from the base class. Will the inheritance structure shown in the code below work? I've only provided one (admittedly contrived) new method just for illustration. The real class would be more substantial.
Code:
#include "Audio.h"

class myAnalysis : public AudioAnalyzeFFT1024 {
  public:
    myAnalysis() : AudioAnalyzeFFT1024() {}

    bool myAvailable() {
      bool ready = false;
      bool b = AudioAnalyzeFFT1024::available();
      // Do other stuff
      // ready = someFunctionOf(otherStuff, b);
      return ready;
    }
};

AudioInputI2S            i2s1;
AudioMixer4              mixer1;
myAnalysis               analysis1;
AudioConnection          patchCord1(i2s1, 0, mixer1, 0);
AudioConnection          patchCord2(i2s1, 1, mixer1, 1);
AudioConnection          patchCord3(mixer1, analysis1);

void setup() {}

void loop() {}

Next question: What if I wanted to derive a new class from an Audio Library class that also has an output that can be connected to other objects (such as a filter)? Is there any way to "intercept" data from the 'transmit(block)' function of the base class, processes it further in the derived class and then 'transmit' it for real to the other objects via the defined AudioConnection?

Thanks.
 
Is there any way to "intercept" data from the 'transmit(block)' function of the base class, processes it further in the derived class and then 'transmit' it for real to the other objects via the defined AudioConnection?

IMO, that looks like a audio objects that receives data say from the FFT object, does some processing, and transmits it to the next audio object.
This can easily be done by creating a own proper audio object. To me, this seems easier than to augment existing objects with new functionality.
 
Is there any way to "intercept" data from the 'transmit(block)' function of the base class, processes it further in the derived class and then 'transmit' it for real to the other objects via the defined AudioConnection?

I want to clearly communicate that the AudioStream base class was not designed to provide this sort of functionality, while at the same time stopping short of saying there isn't *any* way.

AudioStream was designed to be a very lightweight buffer management layer. It's also meant to "feel" like you're plugging arbitrary audio processing components together with patch cords when using the design tool. Just to keep some healthy perspective, this system was designed to run on ~100 MHz microcontrollers and yet allow using many dozens of audio components, each processing 44100 samples per second! This isn't PC class hardware with many CPU cores running at multiple GHz. The code is designed to give you quite a lot of capability, but it's also not packed with much extra capability, because we're running on low-power hardware!

There's no support for a tiered hierarchy of audio processing. The many audio processing objects are managed by just a simple list. So if you want to add more processing, and you're not going to change the AudioStream base class, then you have 2 options.

1: You can add another object (as far as AudioStream is concerned) and have both be treated separately from the base class, but maybe present them to the user in a carefully crafted illusion of being a single component.

2: Or you can modify the FFT object so it's still 1 instance as far as the update list in the base class is concerned. To specifically answer your question "processes it further in the derived class and then 'transmit' it for real", after you've called AudioStream transmit(), but before you return from your update() function, you still have a pointer to the actual buffer. You can still do pretty much anything you want do that buffer. Before calling release(), the buffer is technically still your's. Even after release(), you still have the pointer... though writing to it might be considered bad form.

However, once you return from your update() function, that buffer will be reused by whatever other objects receive your transmission. If you connect the your output to the inputs of multiple other objects, AudioStream uses a shared copy-on-write scheme. That's why there are 2 different ways to receive... one where you promise not to modify the buffer, which allows AudioStream to avoid making copies when audio is connected to multiple destinations.

Not sure if this really answers the question, but hopefully a little insight into the overall design idea of the system can help guide your approach.
 
Thanks Paul. I understand what you’re saying. But, I want to clarify that I was asking two different, but similar, questions. I’ll expand on the second one first:

For Audio classes that take in one or more input streams, do processing, then send out one or more output streams (like filters, effects, amplifiers, etc), it makes sense to just use “patch cords” to connect to my own Audio class objects for further processing. Hence, no need to derive a class from (say) the filter base class, “intercept”, re-processes, then “re-transmit”. Case closed on that question.

But, analysis classes (like the various FFTs) don’t produce an output stream. They just process their input(s) and make the result(s) available to the main program via various member functions. That means I can’t use “patch cords” to connect these results to another Audio class object for further analysis during the Audio library’s update() process.

So, if I want to make my own analysis class based on (say) AudioAnalyzeFFT1024, I see three options:

1. Copy the entire AudioAnalyzeFFT1024 code into my new class then modify / add to it to implement my new analysis features.

2. Create my new Audio class containing a private member of the AudioAnalyzeFFT1024 class and use its results in the update() of my containing class.

3. Derive my new class from AudioAnalyzeFFT1024 using inheritance.

Option #1 seems inelegant. For Option #2, I’m not sure how to “patch cord” the private member AudioAnalyzeFFT1024 object into the update() structure.

That leaves Option #3 with the skeleton code I originally posted. I’ve tried to expand on that code below to further explain what I mean (but it’s still contrived and meant to be illustrative only). I’d appreciate hearing your thoughts on it.

Thanks again.

PS:
The code below compiles and I'm pretty sure it's correct in the C++ / OOP sense. But, what I'm not sure about is will the base AudioAnalyzeFFT1024 object properly receive the input signal which is "patch corded" to the derived object in the global declarations.


Code:
#include "Audio.h"

class myAnalysis : public AudioAnalyzeFFT1024 {
  public:
    myAnalysis() : AudioAnalyzeFFT1024() {}

    virtual void update(void) {
      AudioAnalyzeFFT1024::update();
      if (AudioAnalyzeFFT1024::available()) {
        // Get data from FFT using AudioAnalyzeFFT1024::read()
        // Process FFT data using additional algorithms
        // Save results of processing in results[] array
        newData = true;
      } else {
        newData = false;
      }
    }

    bool myAvailable() {
      return newData;
    }

    float * myGetData() {
      newData = false;
      return results;
    }

  private:
    bool newData=false;
    float results[10];
};

AudioInputI2S            i2s1;
AudioMixer4              mixer1;
myAnalysis               analysis1;
AudioConnection          patchCord1(i2s1, 0, mixer1, 0);
AudioConnection          patchCord2(i2s1, 1, mixer1, 1);
AudioConnection          patchCord3(mixer1, analysis1);

void setup() {}
// Do required Audio system setup (memory, etc) here

void loop() {
  float *theData;
  if (analysis1.myAvailable()) {
    theData = analysis1.myGetData();
    // Do stuff here with the data
  }
}
 
Last edited:
Who really decides what is elegant anyways?
In this case, since I’m writing the code, I do.

If this were an important project with a fast-approaching hard deadline, then I’d probably go with Option #1 and move on with things.

But, it’s really more of a learning experience to expand my C++ skills. Option #3 is certainly more in the spirit of proper OOP. So, if there’s no definitive answer and no one else has tried it yet, I’ll go ahead write the code and see what happens. Will report back with results.
 
Status
Not open for further replies.
Back
Top