Cabbage Logo
Back to Cabbage Site

Routing csound output channels to unity gameobjects

Is there any way to grab the output of a csoundUnity channel and read it using OnAudioFilerRead on a separate gameobject? I would like to avoid the overhead of having multiple csoundUnity instances and wanted to know if this was even plausible before moving forward.

The develop branch has this feature implemented, it’s called CsoundUnityChild, it simply reads audio channels from the other CsoundUnity objects present in scene.

2 Likes

Sorry to resurrect this, was about to do a new post then saw this!
So does each CsoundUnityChild essentially have a ProcessBlock on a different thread, that then routes the audio to the mixer channel on that game object? Reason I ask is I’m running into ~some form of~ bottleneck with CsoundUnity, and we have like 8 CsoundUnityChild instances, all with their own ProcessBlock. What I’m wondering is, is there potential for a performance boost if I can route the audio to different AudioSources from an overload of CsoundUnity itself’s process block? Something along these lines:

public void ProcessAudioBlock(float[] samples, int numChannels)
{
    //Debug.Log($"Num Channels: {numChannels}, NumDestinations: {destinations.Count}");
    if (destinations.Count != numChannels)
    {
        return;
    }
    List<float[]> destinationBuffers = new List<float[]>();
    for (int i = 0; i < destinations.Count; i++)
    {
        destinationBuffers.Add(new float[samples.Length / numChannels]);
        Debug.LogError($"Destination buffer size: {destinationBuffers[i].Length}");
    }
    for (int i = 0; i < samples.Length; i += numChannels, ksmpsIndex++)
    {
        for (uint channel = 0; channel < numChannels; channel++)
        {
            if (ksmpsIndex >= GetKsmps() && GetKsmps() > 0)
            {
                var res = PerformKsmps();
                performanceFinished = res == 1;
                ksmpsIndex = 0;
                foreach(var channelName in availableAudioChannels)
                {
                    if (!namedAudioChannelTempBufferDict.ContainsKey(channelName)) continue;
                    namedAudioChannelTempBufferDict[channelName] = GetAudioChannel(channelName);
                }
            }
            var outputSampleChannel = channel < GetNchnls() ? channel : GetNchnls() - 1;
            destinationBuffers[(int)channel][i % 1024] = (float)GetOutputSample((int)ksmpsIndex, (int)outputSampleChannel) / zerdbfs;
        }
    }
    for(int i = 0; i < destinations.Count; i++)
    {
        destinations[i].clip.SetData(destinationBuffers[i], 0);
    }
    destinationBuffers.Clear();
}

The above doesn’t work because you can’t call GetClip() on anything other than the main thread (shout out unity for making EVERYTHING single threaded lol), and I guess that’s working on the assumption that more ProcessBlock() instances there are, the higher the DSP Load is going to be!
Wanted to get you guys’ thoughts on it anyway, I’ll keep trying to get it going and let you know what happens

I am not sure if each AudioSource gets a processBlock() on a new thread, of if they all share one audio thread. To be honest, I don’t know enough about Unity’s audio sub-system. But I can tell you the having one Csound instance that sends data to other audio sources is far easier on the CPU than multiple Csound sources.

I think our hands were somewhat tied in the implementation of this. @giovannibedetti might be able to give you more details…

1 Like

With no setting clips, I had a pretty dramatic reduction in CPU usage (~40%), but yknow, that could just be because I’m not making the mixer do any work.
Currently working through this to try and make it so I can set the clip data from the CsoundUnity class, I’ll let you know if I get it working what the performance is like, and can then tidy up my code and upload my CsoundUnity package here if you guys want it assuming this does anything!
(Edit, have access to unity api functions from the thread, trying to work out what to do with said samples now they’re in the main thread, was thinking I could just write them to an audio clip and then PlayOneShot() but dont like that, is there an elegant way of streaming that isn’t OnFilterRead?)

How are you getting on with this? You’re basically trying to avoid needing to use OnAudioFilterRead() in your child objects. I think this was the first approach we took, i.e, sending channel data to clips, but we hit the same problems you did.

Just about to get going again today, as far as I can tell i’ve the data outside of the thread CsoundUnity is on, just puzzling out where to put it, would it have hurt them to have AudioMixerGroup.AddSample() lol
(Edit:) Convoluted as all hell but I guess I COULD try and make a custom filter with the Native Plugin SDK that takes n inputs and outputs to the right places?
(Edit 2.0): Really seems like they took every precaution NOT to let you do this I’m out of ideas for it :frowning:

Hey Syl, I’m not sure I’m following totally what you’re trying to achieve.
We added the CsoundUnityChild thinking of it as a simple “sink” that would be filled by its parent CsoundUnity instance, one audio (mono or stereo) channel for child.
What we can do is sending more channels, for example 4, but I’m not sure if this can help.
I recently did a test with 10 children, and the cpu was using around 10-15% on the audio thread, but main thread was almost at 0%.

EDIT:
Some stats of 10 playing tables, routed to 10 audio channels

    instr 1

    kgain = 4
    ;read tables, they all have the same length
    iLen = ftlen(900)
    ;so the reading phasor can be the same for all
    aPhs phasor (sr/iLen)
    ;use the phasor to scan the tables
    afile1 table aPhs, 900, 1
    afile2 table aPhs, 901, 1
    afile3 table aPhs, 902, 1
    afile4 table aPhs, 903, 1
    afile5 table aPhs, 904, 1
    afile6 table aPhs, 905, 1
    afile7 table aPhs, 906, 1 
    afile8 table aPhs, 907, 1
    afile9 table aPhs, 908, 1
    afile10 table aPhs, 909, 1

    chnset afile1*kgain, "file 1"
    chnset afile2*kgain, "file 2"
    chnset afile3*kgain, "file 3"
    chnset afile4*kgain, "file 4"
    chnset afile5*kgain, "file 5"
    chnset afile6*kgain, "file 6"
    chnset afile7*kgain, "file 7"
    chnset afile8*kgain, "file 8"
    chnset afile9*kgain, "file 9"
    chnset afile10*kgain, "file 10"
    
    endin

10 children stat 2020-11-26 194540 10 children stat 2020-11-26 195123

Oh that’s interesting, I guess that actually isn’t the bottleneck then!
What I was trying to do was to have a single ProcessBlock() function which would then route the audio to different audio sources, to try and cut down on the amount of OnFilterReads, but it’s probably not even going to help much even if I CAN get that working!

I think there’s no other easy way, setting data to the clip (https://docs.unity3d.com/ScriptReference/AudioClip.SetData.html) doesn’t seem to be the way to go (and it is for sure slower than using OnAudioFilterRead).
Probably this could be done creating a native plugin, but it doesn’t look like a quick task…