This project has moved. For the latest updates, please go here.

Code to Load/Save Banks

Nov 21, 2011 at 12:12 PM

I been looking through various piceces of Code on the net to try and find an example of loading a bank of programs, modifying one or more programs and then persisting.

From what I gather the persistence can sometimes be handled by the plugin itself, if that is the case how do you 'save bank as'.

If anyonne has some examples of loading bank and persting I would be very greatful.

For the load so far I have, can anyone help ?:

Regards T

  PluginContext.PluginCommandStub.MainsChanged(false);
            OpenFileDialog o = new OpenFileDialog();
            if (o.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
              
                System.IO.BinaryReader b = new BinaryReader( new System.IO.FileStream(o.FileName, FileMode.Open));
                byte[] buff =
                b.ReadBytes((int)b.BaseStream.Length);

          
               var d= PluginContext.PluginCommandStub.BeginLoadBank(new VstPatchChunkInfo(1, PluginContext.PluginInfo.PluginID, PluginContext.PluginInfo.PluginVersion, PluginContext.PluginInfo.ProgramCount));
               PluginContext.PluginCommandStub.SetProgram(0);
                PluginContext.PluginCommandStub.SetChunk(buff,false);
            }

Coordinator
Nov 21, 2011 at 1:44 PM

First: there are two ways to save/load plugin data. Not all plugin's support chunks (check VstPluginInfo.Flags). The host must iterate all programs and all parameter (values) for those plugins that dont support chunks. The other way is using chunks where the plugin passes (binary) data to the host to save or the host passes (binary) data to the plugin to load.

The idea of the VstPatchChunkInfo is to let the plugin know what version and what plugin (Id) wrote the data when the bank was saved. So its the host's job to capture that info and save it with the chunk the plugin is providing. Then when that 'file' is loaded, the host parses out that info and presents it to the plugin. The plugin that actually loads the data doesn't have to be the same plugin that wrote the data (that plugin ID is there to match files to plugins. Dont allow plugin data to be loaded of a different plugin). A newer version of the plugin could be installed in the meantime. The plugin indicates if it is capable of loading the data with the return value of BeginLoadBank/Program methods. If it responds with NO it cannot load the data (for whatever reason). Only when the plugin answers with Yes (or dont know) then the host calls the SetChunk method with the data. If the SetChunk method returns zero, interpret that as failed.

Hope it helps,
Marc 

Nov 21, 2011 at 3:39 PM

Ok thanks that gives me a bit more to go on

Regatds T

Nov 21, 2011 at 3:56 PM

So to check chunk support :

 bool usingChunks = (PluginContext.PluginInfo.Flags & VstPluginFlags.ProgramChunks) == VstPluginFlags.ProgramChunks;

Marc does this mean if we are loading data using chuck I must read the start of the data into VstPatchChunkInfo

this is then fed into PluginContext.PluginCommandStub.BeginLoadBank() ?

Then I call   PluginContext.PluginCommandStub.SetChunk(buff,false);

Should buff contain VstPatchChunkInfo as well or should it have been removed

Regards T

Coordinator
Nov 22, 2011 at 5:31 AM

Starting with the last question: What you pass to SetChunk MUST be exactly the same as what you received from GetChunk, otherwise the plugin cannot know what to expect. 

And yes, the host would save a header containing the VstPathChunkInfo data. When the header is read by the host, it determines what plugin owns this data (PluginID) and proceed with calling the plugin after it has found (or created) and instance. First call the BeginLoadBakn/Program with the VstPathChunkInfo header data, then - if return value was not 'No'- call SetChunk with the 'blob'.

You can store additional information in the file header you might need for your host application.

Hope it helps,
Marc 

Nov 22, 2011 at 8:38 AM

Thanks for you quick and very early reply. So for the benefit of me and others

I would do something like : ( am I any closer ?)

System.IO.BinaryReader b = new BinaryReader( new System.IO.FileStream(o.FileName, FileMode.Open));

 int elelementCount  =b.ReadInt32();

 int pluginId =b.ReadInt32();

 int pluginVersion =b.ReadInt32();

 int version =b.ReadInt32();

var d= PluginContext.PluginCommandStub.BeginLoadBank(new VstPatchChunkInfo(version,pluginId,pluginVersion,elelementCount );

if (d != VstCanDoResult.No)

{

  byte[] buff = b.ReadInt32((int)b.BaseStream.Length - (int)b.BaseStream.Position);

  PluginContext.PluginCommandStub.SetChunk(buff,false);

}

Regards T  (thanks in advance)

Nov 22, 2011 at 1:03 PM

for all those who wish to know I found this code on the net

struct fxSet {
    long    chunkMagic;         // 'CcnK'
    long    byteSize;           // of this chunk, excl. magic + byteSize

    long    fxMagic;            // 'FxBk'
    long    version;
    long    fxID;               // fx unique id
    long    fxVersion;

    long    numPrograms;
    char    future[128];

    fxProgram programs[1];      // variable no. of programs
};
for us in the world of c# these would be int32 so to read the chunkMagic
                
System.IO.BinaryReader b = new BinaryReader(new System.IO.FileStream(o.FileName, FileMode.Open));

var chunkMagic=b.Chars(4);



http://www.koders.com/cpp/fidE4B273B325D02CA1FEBA15D6ED9EDE666BE85DA9.aspx?s=file%3Aaeffect.h
Nov 22, 2011 at 1:39 PM

Stop the press although the stucture above is correct, note that the order of the bytes of the int are reversed so you will need to

 var bytes = b.ReadBytes(4);

                if (BitConverter.IsLittleEndian)
                    Array.Reverse(bytes);

                var pluginId =BitConverter.ToInt32(bytes, 0);

Nov 23, 2011 at 10:04 AM

Marc I wonder if you could offer any asisstance, I appear to be closer but no cigar.

I can see by opening up the fxb file in fex editor that i have loaded the correct data into VstPatchChunkInfo but

PluginContext.PluginCommandStub.SetChunk always returns 0

regards Tony

    private void button2_Click(object sender, EventArgs e)
        {

            PluginContext.PluginCommandStub.MainsChanged(false);
            bool usingChunks = (PluginContext.PluginInfo.Flags & VstPluginFlags.ProgramChunks) == VstPluginFlags.ProgramChunks;
            OpenFileDialog o = new OpenFileDialog();
            if (o.ShowDialog() == System.Windows.Forms.DialogResult.OK)
         {
//                struct fxSet {
//    long    chunkMagic;         // 'CcnK'
//    long    byteSize;           // of this chunk, excl. magic + byteSize

//    long    fxMagic;            // 'FxBk'
//    long    version;
//    long    fxID;               // fx unique id
//    long    fxVersion;

//    long    numPrograms;
//    char    future[128];

//    fxProgram programs[1];      // variable no. of programs
//};
                System.IO.BinaryReader b = new BinaryReader(new System.IO.FileStream(o.FileName, FileMode.Open));
               var chs = b.ReadChars(4); //chunkMagic
                b.ReadBytes(4); //byteSize
                b.ReadBytes(4); //fxMagic

                //version
                var bytes = b.ReadBytes(4);
                Array.Reverse(bytes);
                int version = BitConverter.ToInt32(bytes, 0);

                 // fx unique id
                 Array.Clear(bytes,0,bytes.Length);
                 bytes = b.ReadBytes(4);
                 Array.Reverse(bytes);
                var pluginId =BitConverter.ToInt32(bytes, 0); 

                //fxVersion
                Array.Clear(bytes, 0, bytes.Length);
                bytes = b.ReadBytes(4);
                Array.Reverse(bytes);
                int pluginVersion =BitConverter.ToInt32(bytes, 0);


                //numPrograms
                Array.Clear(bytes, 0, bytes.Length);
                bytes = b.ReadBytes(4);
                Array.Reverse(bytes);
                int elelementCount =BitConverter.ToInt32(bytes, 0);

                b.ReadBytes(128);
                

                
                Console.WriteLine("read Id " + pluginId);
                Console.WriteLine(PluginContext.PluginInfo.PluginID);
                var d = PluginContext.PluginCommandStub.BeginLoadBank(new VstPatchChunkInfo(version, pluginId, pluginVersion, elelementCount));

                if (d != VstCanDoResult.No)
                {

                    byte[] buff = b.ReadBytes((int)b.BaseStream.Length);
                    PluginContext.PluginCommandStub.SetProgram(0);

                    //X always returns 0 why ?
                    var x = PluginContext.PluginCommandStub.SetChunk(buff, true);

                }

            }
        }

Nov 23, 2011 at 12:16 PM

Hi Marc I think I may have discovered a small bug.  I got the above code to work following your advice about GetChuck and SetChuck being the same.  So I called GetChuck and had a look at the byte pattern and then compared it with some fxb files for the same VST.  It seems the data required by GetChunk starts at position 160 in the fxb. I think the offset must be caused by

fxProgram programs[1];
What ever thats for !

However my call to
//X always returns 0 why ?
var x = PluginContext.PluginCommandStub.SetChunk(buff, true);

Still returns 0 ! It does however load the bank sucessfully. According to the doc' it should return the number of bytes read.

Thanks once again to everyone who has made VST.net possible. I'm currently working on an C# sequencer workstation with VST support using C# Midi toolkit.
Thanks to yourself and portAudio I will soon be able to intergate a vstHost that will use ASIO drivers and play my wav metronome all with low Latency and C# support

Well Done.

P.S when I get the sequencer workstation to a stable state I will make it open source
Regard Tony
Coordinator
Nov 24, 2011 at 9:16 AM
Edited Nov 24, 2011 at 9:17 AM

Hi T,

Thanx for the kind words.

The "fxProgram programs[1]" is a (lame) trick in C++ to sort of have dynamic structures. It still involves a lot of pointer magic but that is what it is meant for.

My advice is to define your own header and dont try to include the plugin chunk in that. You could include the chunk byte size, especially when you store more plugin chunks in one file.
Then you just open the file Stream, read you header, call the plugin etc and read the rest of the stream into a byte[] buffer to pass to SetChunk.

As for returning zero from the SetChunk method... Well, some plugins respect the docs better than others. It would not be the first time you had to ignore the specs in order to make it work. ;-)
Perhaps when you try a different plugin it will report the bytes read..? 

Hope it helps,
Marc 

PS: you might want to take a look at how RIFF (IFF) files are structured. That might give you some context and even inspiration how to deal with these chunks in your file.

Nov 27, 2011 at 8:06 PM
Edited Aug 18, 2012 at 3:38 PM

Hi,

I am in no way a professional developer, so be warned.

Anyway I wanted to create a vst plugin command line processor, I.e. a command line tool that get's a vst as one input parameter and a wav file to be processed as the other paramater, and then output the resulting audio as wavefile or play the audio. It also supports saving and loading fxp and fxb (i.e. steinberg preset or bank formats).

I have a working version now (full of non-optimal code) but it can give the people looking for such features some ideas.

The code can be found at https://github.com/perivar/Wave2ZebraSynth/tree/master/ProcessVSTPlugin

 

UPDATE: I have made a new version that also supports offline processing (without playing and much faster)

Can be found at https://github.com/perivar/AudioVSTToolbox/tree/master/ProcessVSTPlugin2

The download can be found at https://github.com/perivar/AudioVSTToolbox/downloads

 

Yes it's a part of a project that I never seem to be able to complete, a tool than can sample incoming audio and create u-he zebra 2 presets.

But the ProcessVSTPlugin will work by itself. I use SharpDevelop myself.

Thanks,

Per Ivar

Nov 28, 2011 at 1:52 PM

Thank you perivarnerseth, much appreciated

Nov 30, 2011 at 2:58 PM

Quick question for perivarnerseth or obiwanjacobi

Why does the data put into setchuck not look as the data obtained from getchunk.

for example why are the length of buff and buff2 not the same infact buff is almost  2X bigger

byte[] buff = b.ReadBytes((int)b.BaseStream.Length);
var x = pluginContext.PluginCommandStub.SetChunk(buff, false);
var buff2 = pluginContext.PluginCommandStub.GetChunk(false);


Portions of the data in buff2 look like so : 

INIT(Copy 29)                   

 buff looks like

I N I T ( C o p y 2 9 )  

in buff every other byte is ascii 0 

I suspect this something like going frm a BSTR   to byte array

Any clues



Thanks Regards T        
                 

Coordinator
Dec 1, 2011 at 7:27 AM

Could be a character code-page/unicode issue..?

Unicode needs two bytes for each character. Ascii only uses one byte (7 bits to be exact).
Look like buf2 is in Ascii and buff is in unicode.

If you have the source code to the plugin (and it looks like you do) you can look for this conversion. It does not have to be a problem in the Host. Could also be that the GetChunk of the plugin converts to unicode.
Take a look at the default encodings of any XxxxWriter (from System.IO) class you might use...

Hope it helps,
Marc 

Dec 1, 2011 at 8:12 AM

obiwanjacobi I think your correct it is a unicode issue, however I think I posted prematurely . Despite  the difference in file size and the unicode issue it seems to make no difference to the plugin, bank saves and loads do work.  I have also checked the fxb from other plugins and the strings are not unicode so as you might expect the character encoding is down to the plugin.

Thanks again.