Passing arguments to AudioCallback()

John, I understand what you’re saying. Just like we can’t see in the existing code any calls to StartAudio with a parameter, we can’t make a direct call to StartAudioCustom with an additional parameter, only because the new Custom functions were created.

It looks like StartAudio gets its parameter values in audio.cpp.

If we wanted StartAudioCustom to work with the additional parameter, it would have to also be in audio.cpp, when the buffer is first read.

If the new parameter is something defined later in the process in mywidgeteffect.cpp, there is nothing that can be done to inject it into StartAudioCustom.

If the new parameter is known at Start, then the new custom code could be called. Maybe… I don’t know what happens before Start in audio.cpp… if anything at all. Would this work?

You can definitely extend all of the code to use a new Callback format with more parameters but this seems like a lot of work to solve the original problem - global variables initialized in main (prior to calling StartAudio) and accessed in the AudioCallback code.

A much easier solution is an object wrapper:

#include "audio.h"

class MyClass
{
public:
   MyClass(int newvar);

   AudioHandle::InterleavingAudioCallback MyCallback(InterleavingInputBuffer in,
                   InterleavingOutputBuffer out,
                   size_t size);

private:
   int newvar;
};

MyClass::MyClass(int newvar)
        : newvar(newvar)
{}        

AudioHandle::InterleavingAudioCallback MyClass::MyCallback(InterleavingInputBuffer in,
                                   InterleavingOutputBuffer out,
                                                      size_t size)
{
        // Callback code, with access to newvar
}

int main(void)
{
  //  existing code

  MyClass myObj(23); // Construct object with initial value for newvar
  hw.StartAudio(myObj.MyCallback);

  // More code
}

As long as the “main” method does not exit before the hw object is finished calling the callback code, something like this should work - otherwise you’ll have to take extra steps to make sure the myObj object persists for the life of the callback. You can add as many variables with as many different types as you like to MyClass, initialize them in the constructor (or add an explicit initializer method) and access the values in the callback code.

Regards,
John

I will just add - global variables are not necessarily always evil. For something like a Daisy application with a relatively small code base and few coders (or even just one), the convenience of global variables can justify their use, IMHO.
Regards,
John

Nice code !

Wondering about passing variables to a function… doesn’t that require constant stack dynamic memory allocation? Wouldn’t a global variable be at least nominally more efficient?

Yeah. I think I was over-prioritizing not using them but their use is justified here.

Yeah, you were trying to solve your problem with what programmers call an “anti-pattern.” I think what might be useful is to step back and think about how you are managing state on a broader level. The Daisy code is designed around a callback model, and if you try to work with that code in a non-callback fashion you are likely going to encounter anti-pattern problems.

Think of a callback function kind of like an alarm clock, and your code is a narcoleptic dude who can only stay awake long enough to finish a simple job. The purpose of the call back function is to kick the dude in the butt every so often and say, “HEY! WAKE UP! TIME TO DO YOUR JOB!” The Daisy library is the clock where you set the alarm. It will trigger the alarm regularly to remind you it’s time to wake up, but it has no idea why you want to wake up or what you need to do. The purpose of the Daisy code is to let you know when it is time to do whatever it is you need to do. It does this by running your callback code.

The Daisy code doesn’t know anything about what you are trying to do. It doesn’t know if you are trying to play a synthesizer oscillator, or access a delay buffer, or turn on a servo motor to flip the waffles in your breakfast pan.

It is the sleepy dude who needs to remember what he is supposed to do and how he is supposed to do it, so that sleepy dude is where you need to maintain all of your state. The sleepy dude is your main code file – the one that has both the main function and the audio callback function code. Those are the tools that you have to maintain state and remember what you are trying to do every time the alarm clock goes off.

This is where you can start thinking of strategies for maintaining state. The main problem for you to solve is how to share information between your “main” function and your callback function. One way to do this is to put your callback function inside an object that exists in the main function. I think that is what JCRiggs is showing you how to do.

Another way to do this is to create a state holder object at the global level. You can initialize it in main, and the access it in your callback. This second approach is is what most of the Daisy library examples do:

#include "daisy_seed.h"
#include "daisysp.h"
#include "MyKillerSynthesizer.h"  // <--- That is a file you create for your synth. You also create the cpp file.
 
using namespace daisy;
using namespace daisysp;
 
DaisySeed hw; 
// Here is the object you create that manages state and also knows how to do synth stuff
 MyKillerSynthesizer theSynth;
 
// Here is how the Daisy tells your synth dude to WAKE UP!!!!
void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
    // This is the function you create where synth dude does something amazing:
    theSynth.Process(in, out, size);
}  // <--- Now synth dude goes back to sleep until Daisy wakes him up again.
 
int main(void)
{
	hw.Init();
	hw.SetAudioBlockSize(48); // number of samples handled per callback
	hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);

	theSynth.Init(hw.AudioSampleRate()); // <-- This is a function you make that tells how to set up the synth.
 
        hw.StartAdc();
	hw.StartAudio(AudioCallback);

	while(1)
    {
        // This infinite loop prevents the program from exiting so that the Daisy library can keep calling your callback and telling sleepyhead to WAKE UP over and over. How rude!
    }
}

That takes us to the next step, which is creating MyKillerSynthesizer.h and MyKillerSynthesizer.cpp…

2 Likes

MyKillerSynthesizer.h is the header file that defines what your synthesizer knows and what it does. This is where your state goes. Finally, right?!?!?

#include "daisy_seed.h"
#include "daisysp.h"

using namespace daisy;
using namespace daisysp;
 
class MyKillerSynthesizer
{
    private:
        Oscillator osc;

    public:
        int myIntVar;  // Here it is at long last. Your variable!!!!
        float myFloatVar; // Your other variable!
 
        // This will be the init function that you called in main().
        void Init(float sampleRate);
        // This is what was called in the callback function. This is what you do when you WAKE UP!
        void Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size);
};
1 Like

Now, finally, in MyKillerSynthesizer.cpp you flesh out Init and Process. Where the header (.h) file holds your state, this file is where you explain what to do with it.

#include "MyKillerSynthesizer.h"

void MyKillerSynthesizer::Init(float sampleRate)
{
    // Get that oscillator ready!
    osc.Init(sampleRate);
    osc.SetWaveform(Oscillator::WAVE_TRI);
    myIntVar = 440;
    myFloatVar = 0.3f;
}

void MyKillerSynthesizer::Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
    // Use that state!
    osc.SetFreq((float)myIntVar);
    osc.SetAmp(myFloatVar);
        
        // and play!!!
	for (size_t i = 0; i < size; i++)
	{
                float result = osc.Process();
		out[0][i] = result;
		out[1][i] = result;
	}
}

So now we finally go all the way back to your original question in the very beginning. How do we pass a variable to the audio callback function? Now I can finally give you an answer. In your audio callback function (or in that infinite main loop) you can access theSynth.myIntVar and theSynth.myFloatVar. You can read them and you can change them. When you do that, the pitch and volume will change due to the first two lines that get called every time the alarm goes off, sleepyhead WAKES UP, and the Process function gets called.

2 Likes