Writing nodes using C#

Writing your own nodes for VL using C# requires no VL specific knowledge or preparation. Essentially you’re writing plain C# code that VL then turns into nodes. Here is a step by step guide to get you started:

Start from a Template

In Visual Studio 2017 create a new project by choosing either of those two templates:

As a rule of thumb: Use .NET Standard as long as you can, use .NET Framework if you need. For more confusing information on this, read this Stackoverflow discussion.

vl libraries writingNodes vs2017.PNG
Visual Studio Template Chooser.
Note
If you are using Visual Studio 2015 choose Visual C# ▸ Class Library.

This will create a .sln, a .csproj and a Class1.cs file which looks like this:

vl libraries writingNodes vs2017 emptyClass.PNG
Empty generated Class1.cs.

The Namespace you specify here will turn into the nodes category in VL. Nested namespaces (using dot syntax) will be translated to nested categories accordingly.

Now any public static operation and every member operation of a public class you write will turn into a VL node. The most simple node you can write is therefore:

namespace MyCustomNodes
{
    public static class MyStaticNodes
    {
        public static float MyAddition(float input, float input2)
        {
            return input + input2;
        }
    }
}

This is basically it. Now build the project and you’ll get a managed .dll containing your nodes. From here you continue with Importing Nodes to use those operations as nodes in VL.

vl libraries writingNodes MyAdditionNode.png
Resulting Node in VL.

More Details

Here are some simple examples and a few more details that will help you create your own nodes. Those are also available via: https://github.com/vvvv/VL.DemoLib

For more general considerations also see: Library Design Guidelines

Vectors and Matrices

In order to create pins of type Vector/Matrix that are compatible with the ones in VL you have to use the types coming with the SharpDX.Mathematics nuget.

Pin Names

For better readability in VL, an operation’s arguments are separated at camelCasing. So "firstInput" in c# turns into "First Input" in VL. The default “return” value is called "Output" in VL.

public static float PinNames(float firstInput, float secondInput)
{
    return firstInput + secondInput;
}
vl libraries writingNodes PinNames.png
Tooltip shows pin name.

Default Values

Simply use the c# notation for defaults to define defaults for inputs in VL.

public static float Defaults(float firstInput=44f, float secondInput=0.44f)
{
    return firstInput + secondInput;
}
vl libraries writingNodes Defaults.png
Default on Input.

Multiple Outputs

Instead of returning a single value you can also use one or even multiple out parameters that will show up as output pins on the VL node:

public static void MultipleOutputs(float firstInput, float secondInput, out float added, out float multiplied)
{
    added = firstInput + secondInput;
    multiplied = firstInput * secondInput;
}
vl libraries writingNodes MultipleOutputs.png
A node with multiple outputs.

Function Overloading

You can write multiple operations with the same name that only differ in the number of input parameters:

public static float MyAddition(float input, float input2)
{
    return input + input2;
}

public static float MyAddition(float input, float input2, float input3)
{
    return input + input2 + input3;
}

Choosing the respective node in the NodeBrowser will then ask you for a further choice to specify the which version you want to use.

Image:NodeBrowser shows two nodes

Using Enums

You can use custom c# enums as input or output types to operations:

public enum DemoEnum { Foo, Bar };

public static string StaticEnumDemo(DemoEnum e)
{
    return e.ToString();
}
vl libraries writingNodes StaticEnum.png
Enum IOBox in VL patch.

For an example of a dynamic enum (ie, one whose entries change during runtime), see below.

Using Generics

VL embraces generics, so of course you can write generic nodes easily:

public static string Generic<T>(T input)
{
    return input.ToString();
}
vl libraries writingNodes Generic.png
Generic pin out of node.

Operating on Spreads

The c# IEnumerable<> appears as Sequence<> in VL:

public static IEnumerable<float> ReverseSequence(IEnumerable<float> input)
{
    return input.Reverse();
}

Documentation

Use XML documentation in C# to provide some information about your nodes:

  • Summary: A one-liner info about the node

  • Remarks: Some additional remarks, like usage instructions, warnings,.. can be multi-line

  • Param name: Short info for each Input

  • Returns: Short info about the result of the node

///<summary>Multiplies input by two</summary>
///<remarks>Some additional remarks</remarks>
///<param name="a">The A Parameter</param>
///<returns>Returns 2 times a</returns>
public static int HTMLDocuTest(int a)
{
    return a*2;
}
vl libraries writingNodes Documentation.png
Documentation shows up in NodeBrowser and Tooltip.
Note
Don’t forget to enable "XML Documentation File" in the c# projects properties to make sure the .xml file holding the documentation is generated. This file will then always need to be next to the .dll, therfore always move those two files together!

C# Ref Paramters

You can use C# ref parameters, but beware: Assigning the parameter leads to undefined behavior in VL (for now), so never write to but only read from ref parameters!

public static int RefParams(ref int firstInput)
{
    return firstInput + 4444;
}
vl libraries writingNodes RefParam.png
A node with a ref parameter as an Input.

Datatypes

Any datatype that you define as class or strcut in c# can be used in VL:

  • Any constructor will be available as a Create node

  • Any get-property will show up as a node returning the properties value

  • Any set-property will show up as a node called Set.. allowing you to set the properties value

  • Any public member operation will be available as a node in VL. Private or Protected operations will be ignored.

public class MyDataType
{
    private float FX;

    public MyDataType(float x)
    {
        FX = x;
    }

    public float AddValue(float value)
    {
        var lastFX = FX;
        FX += value;

        return FX;
    }
}
vl libraries writingNodes Datatypes.png
Corresponding nodes.

Events/Observables

VL translates .net events that conform to the .NET Core Event Pattern to Observables automatically. So you can simply use events in your code and then access them in VL via the Observable pattern.

Here is an example of c# events without and with event arguments:

public class MyDataType
{
    public event EventHandler OnValueChanged;
    public event EventHandler<MyGenericEventArgs<float>> OnValueExceeded;
    ...
}

public class MyGenericEventArgs<T> : EventArgs
{
    public readonly T Value;

    public MyGenericEventArgs(T value)
    {
        Value = value;
    }
}

In your code those could be called like this:

public float AddValue(float value)
{
    if (value != 0)
    {
        FX += value;
        OnValueChanged?.Invoke(this, EventArgs.Empty);
    }

    if (FX > FThreshold)
        OnValueExceeded?.Invoke(this, new MyGenericEventArgs<float>(FX));

    return FX;
}

In VL those events are available as nodes of the same name that return an Observable<EventPattern<>>:

vl libraries writingNodes Observables.png
How this looks in VL: a) Member Operations, b) ValueChanged event without any arguments, c) ValueExceeded event with an argument.
  • In case your event does not have any arguments (section 'b' on the image above), but simply sends a bang when something happens, use the On Data output of the HoldLatest [Reactive] node to be informed of the event.

  • If your event does have arguments (section 'c' on the image above) you’ll receive an Observable<EventPattern<MyGenericEventArgs<>>> which you’ll have to unpack using the EventArgs [Reactive.EventPattern] node, which is available via the VL.DevLib package. The node then gives you access to the Sender and Value of the EventArgs.

vl libraries writingNodes Observables EventArgs.png
Unpacking using EventArgs.

For general information on workig with Observables see the chapter about Reactive Programming.

Dynamic Enums

Dynamic enums are useful in cases where you want to offer users a list of items to choose from, where the entries of that list may change during runtime. A typical example are nodes that give access to hardware devices that can be plugged in and removed anytime.

Consider a normal enum in c#:

enum MyEnum = { Foo, Bar }

Here MyEnum is what we call the type and { Foo, Bar } makes its definition.

And the way we want to use such an enum in our code is to have it as the type of an input parameter to one of our operations, like this:

public static string EnumDemo(MyEnum e)
{
    return e.ToString();
}

Implementing dynamic Enums for VL

Now in order to create a dynamic enum for VL we also need those two elements, the type and the definition. Both need to be implemented as classes in c#:

  • The type needs to implement IDynamicEnum

  • The definition needs to implement IDynamicEnumDefinition

both of which come with the VL.Core nuget.

Note
For now the VL.Core nuget is only available as pre-release package!

To make their use easier there are also baseclass implementations available:

  • VL.Lib.Collections.DynamicEnumBase<T, U>

  • VL.Lib.Collections.DynamicEnumDefinitionBase<U>

  • VL.Lib.Collections.ManualDynamicEnumDefinitionBase<U>

Note that the definition base classes are Singleton, meaning that its implementation takes care that always only one instance exists of it globally. We want this because it is important that any node that is referring to a specific enum definition always gets exactly the same entries!

Using the above two baseclasses, an implementation of your own dynamic enum could look like this:

1. Create an enum type

First derive from the DynamicEnumBase to create your own enum type.

[Serializable]
public class MyEnum: DynamicEnumBase<MyEnum, MyEnumDefinition>
{
    public MyEnum(string value) : base(value)
    {
    }

    //this method needs to be imported in VL to set the default
    public static MyEnum CreateDefault()
    {
        //use method of base class if nothing special required
        return CreateDefaultBase();
    }
}

The code above most likely doesn’t need many changes for your own implementation except:

  • Give it a proper name instead of "MyEnum", something like e.g. "MidiInputDevice". Note the singular in the naming: This type represents one entry in the enumeration.

  • Note the second type parameter MyEnumDefinition which connects your enum to its definition and should similarly be called "MidiInputDeviceDefinition"

2. Provide available entries

Derive from DynamicEnumDefinitionBase to implement the class that provides the available entries of your enum to the system. Here you only have to override two functions: One that can return a list of current enum-entries as strings and another one that tells the system when your enum-entries have changed.

public class MyEnumDefinition : DynamicEnumDefinitionBase<MyEnumDefinition>
{
    //return the current enum entries
    protected override IReadOnlyDictionary<string, object> GetEntries()
    {
    }

    //inform the system that the enum has changed
    protected override IObservable<object> GetEntriesChangedObservable()
    {
    }

    //optionally disable alphabetic sorting
    protected override bool AutoSortAlphabetically => false; //true is the default
}

Implementations here will vary depending on your usecase. A simple example could look like this:

public class ComPortDefinition : DynamicEnumDefinitionBase<ComPortDefinition>
{
    protected override IObservable<object> GetEntriesChangedObservable()
    {
        return HardwareChangedEvents.HardwareChanged;
    }

    protected override IReadOnlyDictionary<string, object> GetEntries()
    {
        Dictionary<string, object> portNames = new Dictionary<string, object>();

        foreach(var portName in NetSerialPort.GetPortNames()
            .Where(n => n.StartsWith("com", StringComparison.InvariantCultureIgnoreCase)))
        {
            //the return dictionary holds the names of the entries as key with an optional "tag"
            //here the tag is null but you can provide any object that you want to associate with the entry
            portNames[portName] = null;
        }

        return portNames;
    }
}

In VL you can then access the tag of the selected enum entry using the Tag [Collections.DynamicEnum] node.

The ManualDynamicEnumDefinitionBase can be used if you need to add and remove entries in a VL patch. It comes with nodes like AddEntry, RemoveEntry and Clear:

public class MyEnumDefinition : ManualDynamicEnumDefinitionBase<MyEnumDefinition>
{
    //this is optional an can be used if any initialization before the call to GetEntries is needed
    protected override void Initialize()
    {
        //add two default entries on initialization
        AddEntry("abara", null);
        AddEntry("kadabara", null);
    }

    //add this to get a node that can access the singleton instance from everywhere
    public static MyEnumDefinition Instance => ManualDynamicEnumDefinitionBase<MyEnumDefinition>.Instance;
}
Note
For the Observable type you need to install the 'System.Reactive' nuget.
3. Introduce the enum to VL

Lastly there is one thing you’ll have to do in VL to get your dynamic enum working: So far when you create a node in VL that has an input typed with your MyEnum, the inputs tooltip will show "null". This is because the VL type system does not know how to deal with the type yet. What we want, is that the type-system automatically creates an instance of MyEnum whenever it encounters it.

For this to happen we have to introduce the enum to VL which has to be done using a Typeforward:

  1. In the .vl document where you have set a reference to the .dll that holds your enum open the Solution Explorer (Ctrl+Shift+J).

  2. Unfolding it, you should see the MyEnum type which you can drag-drop onto the document patch. This makes the enum available for the VL type system and allows it to initialize the type, whenever it encounters it.

vl libraries writingNodes DynamicEnums CreateDefault.png
Creating a TypeForward for MyEnum.

Now you will see a valid instance in any pin that uses MyEnum and you can create an IOBox to control it.

vl libraries writingNodes DynamicEnums.png
How this looks in VL.

results matching ""

    No results matching ""