Cabbage Logo
Back to Cabbage Site

Internal Presets

I decided to create a new thread, as I didn’t want to confuse the other topics/conversations, though they’re all related IMO: this is in reference to various other threads in the forum…

  • About presets…
    Solution for user-made presets?
  • Sliders ‘meta’ control (and in my personal case, using sliders to create ‘monome-like’ grids)…
    Mouse Grid
  • “State” save/set/write opcodes (and their antipodes) –– and possibly other JSON-related threads(?)

I followed the latter example from the Docs and merged all these ideas together into a patch/plugin that will save multiple states as discrete presets automatically, you can get an idea about it in the following animation…

IP2

And you can check out the plugin + csd file here:

InternalPresets.zip (2.5 MB)

So, many questions / things to report / ideas…

  • In the Docs example for “Channel State Save,” what’s the purpose of the k0k variable right in front of this opcodes (or how does it work)?

  • I noticed what might potentially be a bug in my grid patch: as soon as Cabbage runs the csd, the grid automatically updates with the hover of the mouse (it doesn’t respect the ‘if mouse == down’ instruction until a preset has been recalled). However, this doesn’t happen with the plugin inside the DAW environment.

  • Making changes to each preset is well remembered by the plugin/DAW – except for the first preset: if P1 is the last thing on screen, yes it will be remembered OK next time the session is opened; but if the plugin is left on some other preset, the 1st preset ‘forgets’ the last-saved state (not sure if this is a problem at init time and if it’s got something to do with my patch as instruments are called etc.).

  • As to the ‘managing user preset’ thread(s), it would be great to have the ability to update/save a specific preset (as opposed to appending and removing –– e.g., I’d like to update, say, ‘preset 3’ and only 3 and leave the others alone; presently, that seemed to require some cumbersome coding when working with the standard combobox+file button preset protocol. Also, notice that in this example, I’m attempting to ‘autosave’ the presets, so the user just has to go to any one preset, make changes and voilá.

  • Currently, these multiple widget/channel states are saved each into its own preset file (which is fine when working with a small number of presets ‘embedded’ in the plugin itself, as shown in the example). However, it would be great if one could save all of that data into a single JSON file (that would be part of the DAW’s standard preset mechanism or that can be easily archived/shared/etc.).

  • Following the last point –– this might already be implemented so apologies if it’s ignorance on my part –– it would be great if one could easily update not only a single preset (with any number of channel states), but if one could parse (read/write) specific ‘keys’ or channels within a preset. I noticed there were some articles about this (“Get/Set State Value”), but I couldn’t get these to work and anyhow, had a hard time understanding how they worked? Any insights or pointers will be greatly appreciated!

  • With all that said/to summarize: if there’s already a (more efficient) way to do: 1) what the example attempts to showcase: to have a few presets internal to the plugin but which can be updated by the user and remembered by the DAW/session; and 2) to save these into one ‘giant’ preset file (which would become the actual ‘plugin preset’ and one that can be archived/shared), well (almost) all my dreams will come true and we’ll have a whole veggie garden in heaven!

I found some mistakes in my code (re: cabbageCreate vsliders), which when sorted out these problems went away. Here’s the new version…

<Cabbage>
form caption("Internal Presets 2") size(660, 620), guiMode("queue"), colour(240, 240, 240), pluginId("inp2")

hslider bounds(20, 180, 120, 20), channel("presets"), range(0, 5, 0, 1, 1), filmstrip("hGridSlider6.png", 6);, popupText(0)

</Cabbage>

<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>

///////////////////////////////////////////////////////////////////////////// GLOBAL
 
ksmps       =   32
nchnls      =   2
0dbfs       =   1

massign     0, "guiCtl"

;---------------------------------------------------------------------------- GRID
     
giG1Size    =   20
giG1Cols    =   6
giG1Rows    =   7
giG1Left    =   20
giG1Top     =   20
giG1Id      =   1

///////////////////////////////////////////////////////////////////////////// UDOS

opcode GridDraw, 0, iiiiii
    iSize, iCols, iRows, iLeft, iTop, iId    xin
    
    iCount  init    0

    while iCount < iCols do
        SCode   sprintf "bounds(%d, %d, %d, %d) channel(\"g%dc%d\") range(0, %d, 0, 1, 1), filmstrip(\"vGridSlider%d.png\", %d, valueTextBox(\"0\"), popup(\"0\")", iLeft+(iSize*iCount), iTop, iSize, iSize*iRows, iId, iCount, iRows-1, iRows, iRows
        cabbageCreate "vslider", SCode
        iCount+=1
    od
    
    SCode = sprintf("bounds(%d, %d, %d, %d), channel(\"overlay%d\"), colour(0, 0, 0, 0)", iLeft, iTop, iSize*iCols, iSize*iRows, iId) 
    cabbageCreate "image", SCode
endop

;---------------------------------------------------------------------------- GRID CONTROL

opcode GridCtl, 0, iiii
    kms         chnget  "MOUSE_DOWN_LEFT"
    kmx         chnget  "MOUSE_X"
    kmy         chnget  "MOUSE_Y"
    
    iSize, iCols, iRows, iId    xin
   
    iOverlay[]  cabbageGet (sprintf("overlay%d", iId)), "bounds" 
    iLeft       = iOverlay[0]
    iTop        = iOverlay[1]
    iWidth      = iOverlay[2]
    iHeight     = iOverlay[3] 
   
    if kms == 1 then
        if (kmx > iLeft && kmx < iLeft+iWidth && kmy > iTop && kmy < iTop + iHeight) then
            kColumn     = int((kmx - iLeft) / iSize)
            kVal        = int(((kmy - iTop) * -iRows / iHeight) + iRows)

            SChannel    = sprintfk("g%dc%d", iId, kColumn)
            cabbageSetValue SChannel, kVal
        endif
    endif
endop

///////////////////////////////////////////////////////////////////////////// INIT

instr guiDraw
    GridDraw    giG1Size, giG1Cols, giG1Rows, giG1Left, giG1Top, giG1Id
endin

instr guiCtl
    GridCtl giG1Size, giG1Cols, giG1Rows, giG1Id

    kPreset, kTrig cabbageGetValue "presets"
    
    if kTrig == 1 then
        
        SIgnore[] init 1
        SIgnore[0] = "presets"
        SFile sprintfk "Preset%d.pre", kPreset
        kOk = cabbageChannelStateRecall:k(SFile, SIgnore)

        cabbageSetValue "g1c0", cabbageGetValue:k("g1c0")
        cabbageSetValue "g1c1", cabbageGetValue:k("g1c1")
        cabbageSetValue "g1c2", cabbageGetValue:k("g1c2")
        cabbageSetValue "g1c3", cabbageGetValue:k("g1c3")
        cabbageSetValue "g1c4", cabbageGetValue:k("g1c4")
        cabbageSetValue "g1c5", cabbageGetValue:k("g1c5")

   endif

   if changed:k(chnget:k("g1c0")) == 1 || changed:k(chnget:k("g1c1")) == 1 || changed:k(chnget:k("g1c2")) == 1 || changed:k(chnget:k("g1c3")) == 1 || changed:k(chnget:k("g1c4")) == 1 || changed:k(chnget:k("g1c5")) == 1 then
        SPath = chnget:S("CSD_PATH")
        SFile sprintfk "Preset%d.pre", kPreset
      
        kOk = cabbageChannelStateSave:k(SFile)
   endif
endin

instr Presets

endin

</CsInstruments>
<CsScore>
f 0 z
i "guiDraw" 0 1
i "guiCtl"  0 z
</CsScore>
</CsoundSynthesizer>

As to saving a single preset file, it occurred to me just as I wrote my last post (of course it would happen then), that I could create dummy sliders for each column x presets (6 x 6 in this case), so as to save all the channels at once (and have the “presets” slider just ‘gate’ the grid to control a particular set of dummy sliders); not the most elegant solution, but at any rate these internal presets would be to control a few elements only (say a couple of grids or something like that). Then, all the channels could be saved into a single file and standard buttons/comboboxes (open/save/preset list) could also be implemented.

Will work on this soon and post when I’ve found some useful solution!

Nice. You can also just use straight up native presets which means you don’t have to write any code to manage them. They also give users the chance to name their presets.

It’s not used, but it will be 1 if the writing of the channel data was Ok. You can use this to check for read/write errors.

Fixed and in GIT. Will be available through Azure shortly.

A lot of the concerns you raise in the later comments can be handled and dealt with using the native preset system. For example, they are all saved into a single JSON file. And you can save and overwrite presets quite quickly, without having to write any Csound code. Autosaving it not that simple however.

1 Like

Sorry, I didn’t see your last post before I posted the one above. Ok, so it looks like you are back on track? I still think the native presets might be useful.

1 Like

Great! Will take a look :wink: Thanks!

Ah yes, but can you with the native presets (even if with a button and not automatically) save/replace a particular preset slot (the examples only show that you can append presets but I didn’t see a way to go to some previous preset and update it?)

Check out the namedPresets.csd file in the examples. It lets you overwrite existing slots.

Ok, will do :slight_smile: Thanks!

Is there a way to bypass the dialog box when replacing a specific preset? I think I’m not fully understanding how the system works (how to parse a specific preset). I tried the following code PN01.csd (1.4 KB) (to no avail) –– the combo box is populated with “p1, p2, p3” etc.:

combobox bounds(74, 190, 100, 25), channel("list"), populate("*.snaps"), channelType("string")

filebutton bounds(12, 190, 60, 25), channel("save"), text("Save As"), populate("*.snaps", "test"), mode("named preset")
filebutton bounds(12, 220, 60, 25), channel("replace"), text("Replace"), populate("p1.snaps"), mode("preset")
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
    SFile, kTrig cabbageGetValue "list"

    if kTrig == 1 then
        SFile2 sprintfk "%s.snaps", SFile
        cabbageSet 1, "replace", "populate", SFile2
    endif
endin

The native preset system saves the state of every widget when you save a preset, and and recalls them when you you select a preset. You don’t parse the presets. They just automatically cause the widgets to update. As you can in the named preset example, there is no Csound code relating to the presets.

This modified example will print the preset name when each time you save or change the preset:

<Cabbage>
form caption("Presets") size(370, 280), guiMode("queue"), colour(58, 110, 182), pluginId("MPre")
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"), channel("presetName")
filebutton bounds(12, 190, 60, 25), text("Save"), populate("*.snaps", "test"), mode("named preset")
filebutton bounds(12, 220, 60, 25), 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>
; Initialize the global variables. 
;sr is set by the host
ksmps = 32
nchnls = 2
0dbfs = 1

;instrument will be triggered by keyboard widget
instr 1

endin

instr 1000
    SCombo, kTrig cabbageGetValue "presetName"
    printf SCombo, kTrig 
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
i1 0 z
i1000 0 z
</CsScore>
</CsoundSynthesizer>

Btw, if you do use the native preset system you can’t programmatically take snapshots from the Csound code. Perhaps this in itself is enough of a reason for you to persist with the other methods…

Mmm, I think I understand (BTW, I was thinking of creating dummy sliders for each grid so as to have only one ChannelSetSave instruction/file, which then I was hoping to save using the native preset system if that makes sense).

But, if I may, let’s go back to my last question first? According to the Docs/Native Presets:

You can also set the name of the custom preset file. Instead of passing “*.snaps” as the file type, pass the full file name, i.e, “myPresets.snaps”.

…that’s what I was attempting to do (or what I meant by ‘parsing a preset’): I’d like to update/replace a specific preset without having to go through a dialog box (and having to type the name of the preset to be replaced), is that possible?

I’ll continue working on the ChannelSetSave example in the meantime (perhaps there’s a way to do everything with this opcode?: 1) save to a default file using a slider/combobox/etc (as in the original example on this post) and 2) have a side button to save to an external location?).

Thanks either way :slight_smile:

No, that is not possible.

Why have a side button to save to an external location? If it is to give users a chance to ‘save as’ to a different location, you can use the cabbageCopyFile opcode.

Ok, thanks!

I was thinking of the file button “save” mode, but I think I misunderstood the native presets, which you explained here…

But if I can combine my ‘internal presets’ with the PresetsNamed, I think that should meet all the basic needs :wink: (if not, will look into cabbageCopyFile, if that can apply to a custom JSON file?)

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