Cabbage Logo
Back to Cabbage Site

CsoundUnity Package (UPM) development

Ha, thanks!! That does it. I’m going to try calling into some node buffers from this now and see if it works…

So I have this as my callback, in CsoundUnity, where I dynamically add some node components:

SetYieldCallback(() => {
    foreach (CsoundUnityNode node in csoundUnityNodes)
    {
        foreach (string channelName in node.GetChannels())
        {
            node.FillNodeBuffer(GetAudioChannel(channelName));
        }
    }
});

Right now CsoundUnityNode.FillNodeBuffer(); looks like this:

public void FillNodeBuffer(MYFLT[] samples)
{
    channelSamples = samples;
    ksmpsIndex = 0;
}

And in CsoundUnityNode::ProcessBlock/OnAudioFilterRead() looks like this:

   public void ProcessBlock(float[] samples, int numChannels)
    {
        //print(samples.Length);
        for (int i = 0; i < samples.Length; i += numChannels, ksmpsIndex++)
        {
            for (uint channel = 0; channel < numChannels; channel++)
            {
                if (ksmpsIndex >= ksmps)
                {
                    ksmpsIndex = 0;
                }
                //Debug.Log("NODE - ksmpsIndex" + ksmpsIndex.ToString());
                samples[i + channel] = (float)(channelSamples[ksmpsIndex] / zerodbfs);

            }
        }
    }

The problem is that even though we are updating channelSamples[] every 32 samples, those updates are not making their way through the this method until it has finished its own processing cycle. I guess this is to ensure it is thread safe.

To check I simply remeove the resetting of ksmpsIndex and see what it gets to before the FillNodeBuffer method resets it. It goes to 1024, which is half of 2048, the number of samples in Unity’s audio buffers. So once the OnAudioFilterRead is doing it’s thing, it’s not possible to dynamically update any of the data it is reading. And for good reason I suspect. So next idea? :thinking:

I understand. It should be cause events and OnAudioFilterRead happen on different threads maybe. We should find a way to sync them…

Could we dynamically generate audio clips in CsoundUnity and pass them to other game objects?

yes with AudioClip.SetData, but I think it would be very slow

Yes, it does seem like needless overhead. :persevere:

I’m insanely thinking of using Marshal.Copy to update the array, but I don’t know if it will really solve the sync issue.

I’m all for insanity!! Go for it :rofl:

1 Like

Me too, especially when I don’t understand the subject totally :smiley:

1 Like

Woke up thinking about updating the nodes in the OnAudioFilterRead of the master node, instead of using the YieldCallback :thinking:
And also try to make the list of nodes static. (even if this would limit to just one master csound instance, since static fields are shared across instances of the same class)
Then I’ll look again to the SenseEventCallback, but I think it would have the same problem of the YieldCallback

I tried this yesterday. Same problem. Each OnAudioFilterRead() run through 2048 samples, meaning the node buffers only get updated in the nodes every 2048 samples, and not 32 ksmps samples as we would like.

Worth a shot. We can have a limit on the number of nodes, and declare them all when the game starts. At least for now it might provide some answers.

I am not sure there is any need. It will suffer the same problem. Also, the YieldCallback is the better way to do this because Csound will not continue running until it returns true. So there is no way Csound can start writing audio again until we have gotten ours from it.

We could also explore the use of named pipes. The idea being that we write the audio to a temp file and pick it up in our node object. Audio applications have been doing this for years, but I’m not sure how it would work within Unity…

Also AudioSource.Audioclip.SetData could be worth a try, maybe it’s not too heavy if the AudioClip is not created/destroyed everytime, but just once
You could do this in the YieldCallback, since it would happen outside of the OnAudioFilterRead

FWIW, I’m attaching the simple CsoundUnityNode test and a simple .csd file that should help you discover if this works or not. Note that I have to manually add an AudioSource to my node component because

[RequireComponent(typeof(AudioSource))]

doesn’t seem to work.

CsoundUnityNode.cs (2.3 KB)
SimpleSynth.csd (1.7 KB)

You’re right, we should at least rule it out. it’s probably the best place to start this morning!

[edit] I have to look into something else for moment, but I can have a go at this a little later on, if you don’t already have it finished!

Ok I hope I’ll make it work before you come back! I am free this morning!

Keep me updated. I have to do some work for that VR game I’m involved in. Not much more to do with it so hopefully I can put more time into CsoundUnity when it’s done.

it seems it’s possible to change the dsp buffer length of Unity audio, never heard of it!
I’m trying this now!

https://docs.unity3d.com/ScriptReference/AudioConfiguration.html

https://docs.unity3d.com/ScriptReference/AudioSettings.Reset.html

I thought of that, but then figured it would put game under unnecessary CPU strain…?

If it’s done at start, then we are sure dsp size and ksmps are the same size, then maybe the YieldCallback will work!
Btw, I don’t understand where you’re setting the string[] csoundChannels of CsoundUnityNode, I need a SetAudioChannel somewhere?

This is true, but running every single AudioSource in a game with ksmps of 32 sounds like an awful waste of resources :laughing:

You don’t need to call SetAudioChannel, Csound creates the channel in the .csd file, see instr 2.

I set it to oscil in the editor. It’s a public field. Note that my node script is just a quick hack to get things moving!