Cabbage Logo
Back to Cabbage Site

Coming over from Kontakt/KSP, Group type situation in cabbage?

Hey all. I’m diving in HARD with cabbage and so far I’m loving it.

I have an instrument I’ve built in Kontakt that I’m really proud of and I’m excited to try and make a VST/AU version for the heck of it…I tried some iterations in projucer, but cabbage just clicks way better for me.

My question is:

In my Kontakt instrument, I utilized groups as a pairing mechanism. So for example,
there are 3 instances of a sample playing any time a midi note is triggered. In Kontakt, that would be Group 1, Group 21, Group 41. (incremented for other banks - e.g. Group 2, 22, 42)

Then I implemented a 3 state switch that automated the volume of those groups depending on the state of the switch, so only one group was audible.
The user-end functionality is kind of like a pedal chain, with

position 0 of switch Group 1 audible, Group 21/41 volume = 0
position 1 of switch Group 21 audible, Group 1/41 volume = 0
position 2 of switch Group 41 audible, Group 1/21 volume = 0

Does anyone have any tips on how to best implement this sort of logic in cSound? I’m playing around with some ideas on my own but I’m sure someone with more experience than me has a better idea on how to achieve this!

Welcome to the forum!

Does this code help?

<Cabbage>
form caption("Group") size(400, 300), guiMode("queue") pluginId("def1")
rslider bounds(8, 10, 60, 60) channel("rslider1") text("Slider 1"), range(0, 1, 0.4, 1, 0.001)
rslider bounds(68, 10, 60, 60) channel("rslider2") text("Slider 2"), range(0, 1, 0.2, 1, 0.001)
rslider bounds(128, 10, 60, 60) channel("rslider3") text("Slider 3"), range(0, 1, 0.8, 1, 0.001)
rslider bounds(188, 10, 60, 60) channel("rslider4") text("Slider 4"), range(0, 1, 0.1, 1, 0.001)
button bounds(270, 10, 101, 25) channel("button1"), text("Group 1")
button bounds(270, 40, 101, 25) channel("button2"), text("Group 1")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1

    kGroup1, kGroup1Trig cabbageGetValue "button1"
    kGroup2, kGroup2Trig cabbageGetValue "button2"

    ;when user presses button1, mute slider 1 + 2
    cabbageSetValue "rslider1", 0, kGroup1Trig
    cabbageSetValue "rslider2", 0, kGroup1Trig

    ;when user presses button2, mute sliders 3 + 4    
    cabbageSetValue "rslider3", 0, kGroup2Trig
    cabbageSetValue "rslider4", 0, kGroup2Trig

endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

test.csd (1.3 KB)

Group button1 controls the volume of sliders 1 and 2, while group button2 controls the volume of sliders 3 and 4. Any widget can control another, or multiple other widgets. Just give the design of it some thought as things can get messy pretty quickly :rofl:

Hi Rory! Thanks for your suggestion.

I was able to achieve the functionality by making bank ranges that all have their own volume params. eg banks 0-x have a volume control, banks x-30 have a volume control, etc. So depending on a switch position, its allowing a certain range of the banks to be at 100% volume while the other ranges switch to 0%. So far it seems to be working as intended.

My initial explanation was not very clear now that I’ve read it back.

I do have a question about sample-loop points! I’m using disk2 opcode to set filepaths and call samples.

From what i’ve read this is the ideal opcode for fetching SMPL loop points, however the samples are just looping from start to end, ignoring the loop-points set in the SMPL chunk. I’ve verified everything at this point in regards to the source files- being properly configured, called, etc.

Do you have any idea what the issue may be?

I didn’t think diskin could handle smpl points at all, but I could be wrong. In fact, I’m not sure if any opcodes are set up to read these values. The lposcil opcodes let users set the start and nd of loop points, but you’d need to get that info from the sample chunk data. Leave it with me I’ll do some rsearch. It should be relatively simple to get this data, but it would need to be done at the opcode level.

Btw, I’m glad you found a solution to the other issue. I also wasn’t quite sure I understood the problem :rofl:

Ok, Iooks like I was way off. The loscil family of opcodes can do this. :+1:

Thanks Rory! Loscil is working. Functionality is good when a note is held down…but if i trigger a note quickly with a long release time set (longer than the sample length), the sample just cuts off abruptly, so I see the looping function of locsil does not work unless a note is held. Again, I’m coming over from Kontakt where these sorts of things are all hard coded on the backend and you never have to think about them - so I’m a bit confused as to approach this issue… do you have any suggestions?

Can you try adding an xtratim to the instrument? That should (hopefully) extend the release time and allow the sample to end on its own terms.

Unfortunately xtratim won’t do the trick. This is because as soon as loscil understands that it is in the release stage (which includes when xtratim or ‘r’-type envelopes are used) it stops looping and simply proceeds to the end of the stored sound file.
If you examine the internal phase pointer output you can see what is happening. In the output below, the key was released at 5 seconds and even though an 8-second xtratim was added, the pointer just moved to the end of the file (phase=1) in just over a second (and sound ceases).


This behaviour could be regarded as useful if the note release includes a distinctive articulation and might work well with things like trumpets or oboes which don’t simply fade out at the end of the note. It is a little unfortunate that other behavioural options are not offered by loscil and could merit a feature request.

The workaround I came up with as shown below:

<Cabbage>
form caption("Untitled") size(400, 300), guiMode("queue"), pluginId("def1")
keyboard bounds(8, 158, 381, 95)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -dm0 -+rtmidi=NULL -M0
</CsOptions>
<CsInstruments>

ksmps = 32
nchnls = 2
0dbfs = 1


gi1 ftgen  1, 0, 0, -1, "Alto-Flute-sus-A#3-PB-loop.wav",0,0,0

gkEnvs[] init 128 ; array that holds envelopes for each note

instr 1
iNum  notnum
p1    +=      iNum * 0.001                        ; create a unique fractional p1 for this note
iRto  =       cpsmidinn(iNum)/cpsoct(8)           ; playback speed ratio
iFn   =       1                                   ; function table containing the sample
gkEnvs[int(iNum)] =  transegr:k(1, 5, -8, 0)      ; write envelope into array at index location corresponding to this note. Release time is 5 seconds. 
if timeinstk()==1 then
      turnoff2 2 + iNum * 0.001, 4, 1                        ; turnoff any existing instances of this note
      event "i", 2 + iNum * 0.001, 0, 36000, iNum, iRto, iFn ; trigger held note in order to sustain looping
endif
endin

instr 2
if active:k(1+p4*0.001)==0 then ; if p1 instrument that triggered this p2 instance has completed its release envelope...
 turnoff2 p1,4,1                ; force instrument to turnoff (better not to have silent instruments lingering)
endif
a1     loscil  1,p5,p6,1
a1     *=      lineto:k(gkEnvs[int(p4)],0.05)  ; read envelope for this note
a1     *=      linsegr:a(1,0.05,0)             ; anticlick (in case the note is stopped by turnoff2 in instr 1)
       outall  a1
endin

</CsInstruments>
<CsScore>
f 0 z
</CsScore>
</CsoundSynthesizer>

I’ve also attached the csd and the sound file I used here:
LoscilLoopTest.zip (199.2 KB)

We have to trick loscil into believing that it is being performed by a held note and that is done by triggering it from instr 1 with a very long note. Envelopes and release stages are created in instr 1 and shared via an array. Elsewhere there is a rats’ nest of turnoffs, actives and fractional instrument numbers but it seems to work okay and it should be easy enough to build a more elaborate sampler around it with multiple samples, keygroups, velocity groups and so on.

The alternatives are a feature request for loscil to allow optional looping in release stages as I mentioned or you could also look at flooper/flooper2. They will loop sound files with a crossfade at the loop point but they won’t read loop points from the sound file metadata; you have to tell them the loop points.

Ah thanks for this Iain. It’s a shame it doesn’t work as is, but thank you for presenting a solution. A feature request might be a good idea. It sounds like something the community would benefit from.