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!