Cabbage Logo
Back to Cabbage Site

Internal Presets

I don’t think you can combine the two systems. One saves the presets in a single file, while the other saves presets in multiple files. Ok, you could write a JSON parser in Csound that would update a particular preset in one JSON file with the contents of a channelSave file. It might be a bit of work though.

I get the feeling that we’re still not on the same page here? :rofl:

Getting closer anyhow I think :wink: I’ll need a little time to try out an example to show what I mean (if I’m successful with it, of course).

Re: parsing to a JSON file, it makes sense and yes, I can see that it’d probably take some work to make it happen…will burn that bridge if/when the need arises.

Will post as soon as I’ve made some progress!

If you do need to do some file reading/writing, you might find these opcodes useful: strToFile and fileToStr. They are actually included with Cabbage but undocumented. I should prepend their names with cabbage to make it clear they are not part of canonical Csound.

Ah, great! Will try these. I haven’t yet tackled my idea/example yet (might have to wait until Monday I’m afraid).

I just tried the following code to get a snaps file outside of the plugin (sorry I’m being so slow in understanding how native presets are meant to be just internal! But perhaps this will be useful still)…

<Cabbage>
form caption("Presets Named 2") size(370, 280), guiMode("queue"), colour(58, 110, 182), pluginId("pn02")
keyboard bounds(10, 90, 345, 95)
rslider bounds(12, 8, 85, 79), channel("att"), range(0, 1, 0.01), text("Att.")
rslider bounds(98, 8, 85, 79), channel("dec"), range(0, 1, 0.4), text("Dec.")
rslider bounds(184, 8, 85, 79), channel("sus"), range(0, 1, 0.7), text("Sus.")
rslider bounds(270, 8, 85, 79), channel("rel"), range(0, 1, 0.8), text("Rel.")
combobox bounds(74, 190, 100, 25), populate("*.snaps"), channelType("string")
filebutton bounds(12, 190, 60, 25), channel("savep"), text("Preset"), populate("*.snaps"), mode("named snapshot")
button bounds(200, 190, 60, 25), channel("savef"), text("File")
filebutton bounds(12, 220, 60, 25), channel("open"), text("Open"), populate("*.snaps"), mode("file")
filebutton bounds(12, 250, 60, 25), channel("remove"), text("Remove"), populate("*.snaps", "test"), mode("remove preset")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>

ksmps = 32
nchnls = 2
0dbfs = 1

instr 1
kbut, ktrig cabbageGetValue "savef"
    if ktrig == 1 then
        cabbageCopyFile "/Users/flaviogaete/Desktop", "PN02.snaps"
    endif
endin

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

Was wondering if there was a way that the user specify the path (and hopefully also the filename?) where to save (copy) to. I’ll check out strToFile and fileToStr in case that’s the way (unless you were meaning some other custom JSON file and then I’m still not on the same page? :thinking:).

Sorry, I’m on my phone here, but you can use the filebutton widget in you need to get a file and path from the user. Set it to save mode and it will prompt the user for a file. The full path and name will be returned to the channel.

1 Like

Thanks! I myself got distracted from my computer. Will look at all this soon.

Ok, so I managed to save the native presets to an external file. However, I’m having trouble getting to read a file, not sure if it’s possible. Here’s the code for both (save / open)…

<Cabbage>
form caption("Presets Named 2") size(340, 240), guiMode("queue"), colour(58, 110, 182), pluginId("pn02")

rslider bounds(20, 20, 60, 60), channel("att"), range(0, 1, 0.01), text("Att.")
rslider bounds(100, 20, 60, 60), channel("dec"), range(0, 1, 0.4), text("Dec.")
rslider bounds(180, 20, 60, 60), channel("sus"), range(0, 1, 0.7), text("Sus.")
rslider bounds(260, 20, 60, 60), channel("rel"), range(0, 1, 0.8), text("Rel.")

combobox bounds(100, 120, 100, 20), channel("list"), populate("*.snaps"), channelType("string")

filebutton bounds(20, 120, 60, 20), channel("savep"), text("Preset"), populate("*.snaps"), mode("named snapshot")
filebutton bounds(220, 120, 60, 20), channel("savef"), text("Save"), mode("save")
filebutton bounds(20, 160, 60, 20), channel("open"), text("Open"), mode("file")
filebutton bounds(20, 200, 60, 20), channel("remove"), text("Remove"), populate("*.snaps", "test"), mode("remove preset")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>

ksmps = 32
nchnls = 2
0dbfs = 1

instr 1
    gSSave, kts cabbageGetValue "savef"
    gSOpen, kto cabbageGetValue "open"
    
    if kts == 1 then
        event "i", "Save", 0, 1
    endif
        
    if kto == 1 then
        event "i", "Open", 0, 1
    endif
endin

instr Save
    SFromPreset fileToStr   "PN02.snaps"
    iRes        strToFile   SFromPreset, gSSave, 0
endin

instr Open
    SToPreset   fileToStr   gSOpen
    iRes        strToFile   SToPreset, "PN02.snaps", 0
    turnoff2    1, 0, 0
    event       "i", 1, 0, 1
endin
;
</CsInstruments>
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer>

I’m getting some running messages in the Cabbage console as soon as I choose an item in the combobox, so I’m sure there’s something I’m missing. Also, (haven’t tested this yet to confirm), I think I can pass the file back to the local path of the .csd file but I can’t manage to have the patch recognize it unless I stop and start the instrument again?

I’ll be in and out today so my responses will continue to be sporadic, but will get to the forum as soon as I get a chance!

Thanks very much for your time Rory :slight_smile:

My first thoughts about this were to send a populate() identifier to the combobox once you have copied the file over. It didn’t work, but does now. However, we have another issue. cabbageGetValue with a filebutton will only trigger an update IF the file name has changed. So if you want to keep copying the same file it will not work. Leave it with me. I’ll come up with a solution. And when I do I’ll push the populate() update too.

1 Like

This is a little hacky, but it works. In order to be able to pick up a change on a filebutton IF one always tries to use the same file we need to keep setting it’s file identifier to identifier("") once a file has been accessed. The following update to your code works.

<Cabbage>
form caption("Presets Named 2") size(340, 240), guiMode("queue"), colour(58, 110, 182), pluginId("pn02")

rslider bounds(20, 20, 60, 60), channel("att"), range(0, 1, 0.01, .2), text("Att.")
rslider bounds(100, 20, 60, 60), channel("dec"), range(0, 1, 0.4), text("Dec.")
rslider bounds(180, 20, 60, 60), channel("sus"), range(0, 1, 0.7), text("Sus.")
rslider bounds(260, 20, 60, 60), channel("rel"), range(0, 1, 0.8), text("Rel.")

combobox bounds(100, 120, 100, 20), channel("list"), populate("*.snaps"), channelType("string")

filebutton bounds(20, 120, 60, 20), channel("savep"), text("Preset"), populate("*.snaps"), mode("named snapshot")
filebutton bounds(220, 120, 60, 20), channel("savef"), text("Save"), mode("file")
filebutton bounds(20, 160, 60, 20), channel("open"), text("Open"), mode("file")
filebutton bounds(20, 200, 60, 20), channel("remove"), text("Remove"), populate("*.snaps", "test"), mode("remove preset")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>

ksmps = 32
nchnls = 2
0dbfs = 1

instr 1
    gSSave, kts cabbageGetValue "savef"
    gSOpen, kto cabbageGetValue "open"
    
    if kts == 1 then
        event "i", "Save", 0, 1
    endif
        
    if kto == 1 then
        event "i", "Open", 0, 1
    endif
endin

instr Save
    SFromPreset fileToStr   "PN02.snaps"
    iRes        strToFile   SFromPreset, gSSave, 0
    cabbageSet "savef", "file(\"\")"
endin

instr Open
    SToPreset   fileToStr   gSOpen
    iRes        strToFile   SToPreset, "PN02.snaps", 0
    cabbageSet "list", "populate(\"*.snaps\")" 
    cabbageSet "open", "file(\"\")"
endin
;
</CsInstruments>
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer> 

Note that it might take some work to get the combobox to display an item when you refresh it. If you know the name of a preset you can use cabbageSet "list", "text", SPresetName but you will need to parse the presets I guess.

Hi Rory,

First, thanks very much for taking the time to find a solution!

I tried the following example at the end of the main instrument

cabbageSet "list", "text", "p3"

…assuming a predefined name and in hopes to call a specific value for the combobox, but was not successful. I also tried creating a separate instrument that I can call with a slight delay just to refresh the value (not sure if that would make a difference with re: the widgets’ default value?). And I also tried setting the channelType to “float” and calling the combobox via cabbageSetValue (e.g. “list”, 2), but it didn’t work either. I’m sure there’s something I’m just not understanding about the combobox and how it works? At any rate, it’d be great if one could both get and set float values independently of the preset names etc. so one can, for example call specific presets with a MIDI note or cc value, etc.

Also wanted to send a few styling requests (though I realize this might not be a priority and that some of these might be complicated to implement, so: OK if you can’t get to it at this moment, but I throw it out there if it’s any helpful feedback): I’d love it if we could control the following on the combobox widget:

  • The corners (the ability to make them zero as well)
  • Hide/show triangle
  • Hide/show checkbox –– and instead have say a ‘selected item’ color (either font or background)
  • Menu background color (or it could be the same as the combobox background color).

Thanks again!
Flavio

Try value() instead. I’ve offset the updating of the item until I am confident that the items have been refreshed. It seems that sending a value(0) will do the trick, but keep in mind that preset comboboxes were not designed to be used in this way. Although I have to say you’re getting a fair amount of mileage from them!

instr Open
    SToPreset   fileToStr   gSOpen
    iRes        strToFile   SToPreset, "PN02.snaps", 0
    cabbageSet "list", "populate(\"*.snaps\")" 
    cabbageSet "open", "file(\"\")"
    event "i", "UpdateItem", 1, 1
endin

instr UpdateItem
    cabbageSet "list", "value(0)"
endin

Mmm, I’ve tried the following (assume I added an nslider with channel(“num”) for testing), on the main instr (which is always running):

knum, ktrig cabbageGetValue "num"
SPreset sprintfk "value(%d)\n", knum

if ktrig == 1 then
    cabbageSet "list", SPreset
endif

I couldn’t get the nslider to control the combobox. I understand I’m really stretching its use here. At any rate, I actually wouldn’t need the combobox…

[Note: sorry, I just realized I might be confusing messages here –– the styling requests were meant for other more normal uses of the combobox (e.g. either a straightforward preset manager, or to choose samples, etc.)].

…it’s more a matter of calling widget states (including the grid + grid.presets) from a single file that can be archived/shared. For example…

Save/Open buttons: when clicked…

  • Write/read the following widget states:
    • Grid columns x presets…
      • preset1 {“gCol1, gCol2, gCol3, gCol4, gCol5, gCol6”}
      • preset2 {“gCol1, gCol2, gCol3, gCol4, gCol5, gCol6”}
      • preset3 {“gCol1, gCol2, gCol3, gCol4, gCol5, gCol6”}
      • preset4 {“gCol1, gCol2, gCol3, gCol4, gCol5, gCol6”}
      • preset5 {“gCol1, gCol2, gCol3, gCol4, gCol5, gCol6”}
      • preset6 {“gCol1, gCol2, gCol3, gCol4, gCol5, gCol6”}
    • “att”
    • “dec”
    • “sus”
    • “rel”

…I’m finally working on the aforementioned example which would take care of this; even though it’s not be the most elegant solution probably, it might just be the simpler way to go, which is to create 6x6 dummy widgets and then save all the desired channels with channelStateSave.

At any rate, it’d be great to know if it’s possible to control the combobox from other sources (mostly MIDI, but perhaps also another widget’s state, etc.).

I do hope I’m not wasting your time here and that these questions might be ultimately useful for others! Anyway, thanks!! Will post an example as soon as I have something ready to show :wink:

This will call the i-time version of cabbageSetValue as S-args are basically i-rate. Instead, just do this:

cabbageSetValue ktrig, "list", sprintfk("value(%d)", knum)

I’ll have a look through the combobox stuff and see if I can that styling.

Thanks very much Rory!

I tried the following examples (including your last suggestion), just in case I should’ve followed the order stated in the Cabbage manual:

 cabbageSetValue ktrig, "list", sprintfk("value(%d)", knum)
 cabbageSetValue "list", sprintfk("value(%d)", knum), ktrig
 cabbageSetValue "list", knum, ktrig

Still, I got an error when trying to pass an S variable:

error: Unable to find opcode entry for 'cabbageSetValue' with matching argument types:
Found: (null) cabbageSetValue kSS
Line: 41

error: Unable to find opcode entry for 'cabbageSetValue' with matching argument types:
Found: (null) cabbageSetValue SSk
Line: 43

When I wrote it in the traditional order (3rd line), I got a ‘non-response’ as before. Here’s the entire file just in case: PN02r.csd (2.2 KB) PN02r.snaps (700 Bytes)

I’ll continue working on my example in the meantime :wink:

Sorry that was my typo, I meant cabageSet not cabageSetValue. But even with that it still doesn’t work as you would like. Let me look at the comboboox code. I’m afraid to make any crazy changes there in case it messes up the current preset system… I’ll let you know if I can find something that works,

I just took a look now and if I try to allow Cabbage to update the preset combobox items it breaks the current preset system. I just don’t see how these can be mixed and matched without some serious reconstruction. And anyone reading this thread who uses the current preset system is now screaming ‘don’t do it Rory!!’. Let’s see if we can find another way.

You can control regular comboboxes from Csound. For example, the following will let you toggle through all the files founds in the current directory:

<Cabbage>
form caption("Untitled") size(400, 300), guiMode("queue") pluginId("def1")
rslider bounds(30, 80, 100, 100), channel("items"), range(0, 1, 0, 1, 0.001), text("Items")
combobox bounds(31, 31, 143, 20) channel("combo1"), channelType("string") populate("*", ".")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1


instr 1
kItem, kTrig cabbageGetValue "items"
SFiles[] cabbageFindFiles ".", "files"
kIndex = int(kItem*lenarray:i(SFiles))
cabbageSet kTrig, "combo1", sprintfk("value(\"%s\")", SFiles[kIndex])
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>

Ok, no worries if it ends up opening a Pandora’s box, don’t mean to mess everyone’s modus operandi here either! Will check the last bit of code now and continue working on my other solution (getting there! just being distracted away from my computer, hence the responses being somewhat tardy).

Ok so, I think I’ve found a solution…

IP5

EDIT: I now tested this on an AU plugin and I’m very happy to report that this preliminary patch only demanded 1-2% on a 2015 Mac laptop (dualCore i5), and saving presets inside Live & Logic was a breeze! I’m hopeful that if one were to finesse the DAW/widget interaction (have even less automatable parameters etc. –– when not needed, of course), the dent on having so many additional widgets could be minimal (I’m sure Rory’s “queue” mode and who-knows-what-other-beautiful-magic has something to do with this :wink: …also, I’ve yet to explore complete synths from the community to learn more about the GUI/CPU ratio…will do in due time!).

Below some notes/questions:

  • If one wanted to have such an ‘internal preset’ system with say 2-3 grids, the amount of widgets will be exponentially higher (in the hundreds easily), however, if they can remain inactive or internal to the Csound script and the DAW didn’t have to deal with them AND if so they’re not a burden on the system, well I can live with that.

  • With this example, we’re doing away with the internal presets (so no internal management is possible as originally intended with the native presets). On the other hand, we do get an internal memory for the grid and the ability to export/import all the desired channels/widgets’ states (personally, that’s sufficient –– though it’d be nice to be able to save as part of the DAWs presets: not sure how that’d work and if it’s going to vary dramatically depending on the DAW being used).

  • The other alternative is to have the combobox itself be a ‘dummy widget’ and call it. However, as @rorywalsh pointed out, it might be a bit tricky to save / update presets automatically (I’m not losing hope on this idea just yet, as one may be able to pass a predefined preset name, I think? This way we can save on having so many widgets! I’ll give this idea some thought (though first I hope to try out my example with a ‘synth engine’ and see if it runs OK).

  • But having all those values at the ready, I have to say makes for pretty efficient reading.

  • Question: making all those dummy widgets inactive and non-automatable, does that help on CPU efficiency? Is there some other trick I might not know about to help minimize widget activity when they’re not being called etc. (besides using kTrigs on the cabbageGetValue)? On this note, Rory (or any other kind veteran out there) –– if you have the time –– could you quickly scan through the code (I’m pasting it at the end of this post), just to get your expert eye to possibly spot any boo-boos I might be making in terms of efficiency?

Mm, I think that’s it for now. Here are the files for anyone who might be curious to implement:

IP5.csd (10.3 KB)
IP5 Presets.zip (3.6 KB)

Great. That’s what we like to hear :muscle:

If you don’t want them to be a burden to the system mark then as automatable(0). This will cause the host to ignore them and avoid users having to wade through 100s of native sliders in their DAW. Btw, 100s of widgets shouldn’t be much of a drain, if you treat them nicely :rofl:

If each of the sliders is automatable you should be able to save the session or state via your DAW. When you save a session with a Cabbage plugin, the plugin writes the states of each automatable widget to the session file.

It certainly does. But the proof is in the pudding. I’ve no doubt there will be a hint of trial and error. The main things to remember is only update UI elements when they need updating.

I just took a look now. The only things that jumps out is this:

SP1 sprintfk "g1n%d0", kPreset

I think you can use an i-time sprintf. Also, I wonder if this opcode might not of some use to you.

Finally, I’m not 100% sure why you need all those dummy widgets? If you are saving each preset in its own file, I don’t understand the need for the dummy widgets? To be honest, it’s hard to keep up sometimes with all the different threads going on here :rofl:

1 Like

Thanks for the answers, Rory :slight_smile:

I’ll take a look at these, yes the getWidgets might come in handy if I can manage to filter just what I want.

Well, that’s what sparked the idea behind this last example. Using individual ‘saved state’ files will make all the ‘inactive grids’ not recognizable by the DAW, plus their saved state becomes the plugin’s default state, so you can’t really have various ‘DAW presets’ as they’ll all be updated to the last saved state (I had tried this beforehand). Also, having all the implied channels in a single file provides that extra freedom of saving/loading it externally and independently.

1 Like