Arrays handling

Hi!
I am working on a sequencer for CsoundUnity using arrays.
Each array has 3 dimensions: steps, notes, note data.
Each step can have multiple notes (0 to n), each note has 3 “slots” for now: note number, volume and duration.
I have 8 arrays in total for 4 tracks. I have a slider to be able to choose, for each track, between the two arrays, a “basic” version and a “tense” version.
All works great, and I am also able to switch between different arrays during performance.
But I have some clicks when switching when I use longer arrays, with lots of notes.

Is there any technique I could follow to avoid such clicks?

Maybe I should create all the arrays in advance? I want to be able to let the user load different sets, so they might not be known in advance.

Basically I’m updating 8 arrays all at once, each goes from 8 to 64 steps (different tracks can have different lengths, but the same track needs to have the same length) with a max of two notes per step (but this could increase). So to simplify things, each new array I’m writing has the same length as the one I’m replacing (but I’d like to overcome this limitation if possible - I tried and indeed I can use different lengths, so I thought they were the cause of the clicks, but they weren’t).

I was wondering if for example updating each cell instead of the whole array could be beneficial.

This is the behaviour, for each of the 8 channels:

  • I update a string channel from Unity , with the array content as a string (with separators)
  • I parse this string to create a 3D array (and I believe this is the culprit)
  • I replace the existing array with this newly created one

This is the code involved in the parsing:

/*
    Parse a 2D Note Array using ASCII separators from a string generated by an external software
    A note is a struct with a note number, a volume and a duration
*/
opcode Parse2DNoteArray, i[][], Sii
    String, iSepA, iSepB xin

    //prints "\nParse2DNoteArray: %s", String
    iLen = StrayLen(String, iSepA)
    iCounter = 0
    
    iArr[][] init iLen, 3
    
    while iCounter < iLen do
        SNote = StrayGetEl(String, iCounter, iSepA)
        ;prints SNote 
        
        iNoteNum = strtod(StrayGetEl(SNote, 0, iSepB))
        iVol = strtod(StrayGetEl(SNote, 1, iSepB))
        iDur = strtod(StrayGetEl(SNote, 2, iSepB))
        iRow[] = fillarray(iNoteNum, iVol, iDur)
        iArr = setrow(iRow, iCounter)
        ; "\n %d: %d, %d, %d", iCounter, iNoteNum, iVol, iDur
        iCounter += 1
    od

    ;printarray iArr, "%d", "\n2D Array: "
    
    xout iArr
endop


/*
    Parse a 3D Note Array using ASCII separators from a string generated by an external software
    The array contains a list of Steps, each step can contain one or more notes.
    A note is a struct with a note number, a volume and a duration
*/
opcode Parse3DNoteArray, i[][][], Siii

    String, iSepA, iSepB, iSepC xin
    iCounter = 0
    iLen = StrayLen(String, iSepA)
    ;prints "\n\n3D Array Length: %d\n", iLen   
    
    ; we have to overcome the possible difference in length of the 2D arrays: they could have a different element count
    ; but this is not supported in Csound, where subarrays of multidimensional arrays must have the same length
    ; so first parse 2D arrays and keep track of the biggest size
    iMaxSize init 0
    while iCounter < iLen do
    
        Step = StrayGetEl(String, iCounter, iSepA)
        ;prints "\nStep[%d]: %s\n", iCounter, Step
        iSize = StrayLen(Step, iSepB)
        ;prints "\nStep[%d] size: %d", iSize
        
        if iSize > iMaxSize then
            iMaxSize = iSize
        endif
        
        iCounter += 1
    od
    
    ; init the 3D array with the found max size on the 2nd dimension
    iArr[][][] init iLen, iMaxSize, 3
    
    ; now it's time to copy 2D arrays in the 3D array
    iCount1 = 0
    while iCount1 < iLen do
        
        Step = StrayGetEl(String, iCount1, iSepA)
        ;prints "\n\nStep[%d]: %s\n", iCount1, Step
        iStep[][] Parse2DNoteArray Step, iSepB, iSepC
        
        iCount2 = 0
        while iCount2 < iMaxSize do
            iCount3 = 0
            
            ; we still need to skip in case the step array has not enough elements
            ; the skipped elements will remain unset
            iSize lenarray iStep, 1
            ;prints "\ncurrent step [%d][%d] size: %d", iCount1, iCount2, iSize
            if iCount2 < iSize then
                while iCount3 < 3 do
                    iVal = iStep[iCount2][iCount3]
                    ;prints "\n\tsetting value [%d][%d][%d]: : %f", iCount1, iCount2, iCount3, iVal
                    iArr[iCount1][iCount2][iCount3] = iVal
                    iCount3 += 1
                od
            else
                ;prints "\n\tnothing to set"
            endif
            iCount2 += 1
        od
        iCount1 += 1
    od
    xout iArr
endop

It uses the wonderful opcodes by Joachim to split strings, StrayGetEl and StrayLen

I am using the opcode above like this:

iRhythm[][][] Parse3DNoteArray gSRhythm, 95, 58, 44 ; ASCII Codes Separators in string: 95 --> underscore '_', 58 --> colon ':', 44 --> comma ','

where gSRhythm is a string channel defined like this:

gSRhythm                chnexport "rhythm", 1

and this is an example string I could pass to that string channel:

1,1,1:_0,0,0:_0,0,0:_0,0,0:_1,1,1:3,1,1_0,0,0:_0,0,0:_0,0,0:_

Where ‘_’ is the separator between steps, and ‘:’ is the separator between notes (here 8 steps with instr 1 playing in pos 0, and instr 1 and 3 playing in pos 4).

I had lots of difficulties at the beginning with the note subarray possibly being of different lengths, and I found that apparently this cannot work on Csound, but I hope to be wrong. So I had to find a workaround, seen in the above UDO. I think that another source of the issue could be the fact that I’m looking for the biggest array size, and those could be different between the two sequences, so again, clicks?.
I hope there is an easier way of passing such data from an external software. Any suggestion is super welcome.

To clarify things, this is the C# code that describes my sequences:

public class Sequence : ScriptableObject
{
    public string channel;
    public TicksDivision ticks = TicksDivision.Sixteenth;
    public List<Step> steps;
}

[Serializable]
public class Step
{
    public List<Note> notes;
}

[Serializable]
public class Note
{
    public int number;
    public float volume;
    public float duration; // relative to speed
}

Sorry for this very long post, I just tried to explain things in detail.

Wow thanks for reading! :pray: