BehringerXTouchExtender 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package BehringerXTouchExtender --version 1.0.0                
NuGet\Install-Package BehringerXTouchExtender -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BehringerXTouchExtender" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add BehringerXTouchExtender --version 1.0.0                
#r "nuget: BehringerXTouchExtender, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install BehringerXTouchExtender as a Cake Addin
#addin nuget:?package=BehringerXTouchExtender&version=1.0.0

// Install BehringerXTouchExtender as a Cake Tool
#tool nuget:?package=BehringerXTouchExtender&version=1.0.0                

Nuget GitHub Workflow Status Testspace Coveralls

Send and receive events with a Behringer X-Touch Extender DAW MIDI control surface over USB.

X-Touch Extender

Quick Start

dotnet new console
dotnet add package BehringerXTouchExtender
// Program.cs
using BehringerXTouchExtender;

using var device = BehringerXTouchExtenderFactory.CreateWithRelativeMode();
device.Open();

var scribbleStrip = device.GetScribbleStrip(0);
scribbleStrip.TopText.Connect("Hello");
scribbleStrip.BottomText.Connect("World");
scribbleStrip.TopTextColor.Connect(ScribbleStripTextColor.Light);
scribbleStrip.BottomTextColor.Connect(ScribbleStripTextColor.Dark);
scribbleStrip.BackgroundColor.Connect(ScribbleStripBackgroundColor.Magenta);
dotnet run

Hello World

Prerequisites

MIDI control mode

You must manually set the device to absolute or relative MIDI control mode. The other two modes, HUI and MC, are not supported by this library.

  1. Turn on the device while holding the track 1 select button
  2. Turn the track 1 rotary encoder knob until the track 1 LCD shows Ctrl (absolute MIDI control mode) or CtrlRel (relative MIDI control mode)
  3. Press the track 1 select button
  4. Remember which mode you chose when you connect to the device

Firmware upgrade

If your computer has an AMD Zen2 (Ryzen 3000) or later CPU, then you must install X-Touch Extender firmware 1.21 or later to fix the broken USB connection.

  1. Download the firmware
  2. Extract the .syx file from the .zip file
  3. Turn on the device while holding the track 8 record button
  4. Download and run MIDI-OX on an unaffected (e.g. Intel) computer connected to the device over USB
  5. Highlight the X-Touch-Ext entries in Options › MIDI Devices
  6. Select the downloaded .syx file using Actions › Send › SysEx File
  7. Wait for the upgrade to finish
  8. Reboot the device using the power button

Installation

You can install this library into your project from NuGet Gallery:

  • dotnet add package BehringerXTouchExtender
  • Install-Package BehringerXTouchExtender
  • Go to Project › Manage NuGet Packages in Visual Studio and search for BehringerXTouchExtender

Connection

  1. Use BehringerXTouchExtenderFactory to create a device instance you can use.
    • If you set your X-Touch Extender to Ctrl mode:
      using BehringerXTouchExtender;
      
      using var device = BehringerXTouchExtenderFactory.CreateWithAbsoluteMode();
      
    • If you set your X-Touch Extender to CtrlRel mode:
      using BehringerXTouchExtender;
      
      using var device = BehringerXTouchExtenderFactory.CreateWithRelativeMode();
      
  2. Once the X-Touch Extender is powered on and connected over USB, open the connection.
    device.Open();
    

Track identifiers

The X-Touch Extender is divided into eight vertical banks of controls, called tracks or channels. In this library, they are numbered from 0 on the left to 7 on the right (0-indexed).

On the physical device, the painted legends are 1-indexed, but 0-indexing is easier and less confusing to use in software.

Any time this library takes or provides a track ID, it's 0-indexed.

In most of the code examples below, the trackId argument used is 0.

Reactive data

This library handles data using KoKo Property objects. These encapsulate values that can change, including automatically in response to other Properties, and they automatically fire change events.

If you have your own Property that you want to drive a control on the X-Touch Extender, you can connect it to this library.

using KoKo;

var scribbleStrip = device.GetScribbleStrip(0);
var greeting = new StoredProperty<string>("Hello");
scribbleStrip.TopText.Connect(greeting);
// The device will show "Hello"

greeting.Value = "Hola";
// The device will automatically update to show "Hola"

KoKo Properties are used by all values on all controls in this library, not just scribble strips. This is useful if the desired value actually depends on other values, because they can all be automatically calculated without any manual change event firing or listening.

Property<CultureInfo> cultureInfo;
var greeting = DerivedProperty.Create<string>(cultureInfo, culture => culture.Name switch {
    "es" => "Hola",
    _ => "Hello"
});
scribbleStrip.TopText.Connect(greeting);
// The device will automatically update the greeting shown whenever the cultureInfo property changes

These Properties are used for reading changing data as well as writing it. You can read the immediate value, as well as registering for events whenever the Property changes in the future.

var button = device.GetRecordButton(0);

if(button.IsPressed.Value){
    Console.WriteLine("Button is pressed");
}

button.IsPressed.PropertyChanged += (sender, args) => {
    if(args.NewValue){
        Console.WriteLine("Button was pressed");
    }
};

If you don't want to create reactive properties, you can connect the device's properties to constant values whenever you want them to change.

scribbleStrip.BottomText.Connect("World");

Usage

Rotary encoders

IRotaryEncoder rotaryEncoder = device.GetRotaryEncoder(0);
Illuminating lights

There are thirteen orange lights on each rotary encoder. Exactly one of them is illuminated at any given time. You can't turn them all off at the same time. Set the LightPosition property to change which light is illuminated.

They are numbered from 0 (most counter-clockwise) to 12 (most clockwise). Values outside this range are clipped to stay within [0, 12].

The number of lights 13 is available programmatically in the IRotaryEncoder.LightCount property.

rotaryEncoder.LightPosition.Connect(0);
// Most counter-clockwise light illuminates
Detecting presses

You can click in on the knob and the rotary encoder's IsPressed Property will change to true (pressed) or false (not pressed).

Console.WriteLine($"Rotary encoder is currently {(rotaryEncoder.IsPressed.Value ? "pressed" : "not pressed")}");

rotaryEncoder.IsPressed.PropertyChanged += (sender, args) => 
        Console.WriteLine($"Rotary encoder was {(args.NewValue ? "pressed" : "released")}");
Detecting rotation

The available Properties and their values for a rotary encoder depend on whether you created your IBehringerXTouchExtender instance using either BehringerXTouchExtenderFactory.CreateWithAbsoluteMode() or .CreateWithRelativeMode().

This control mode must match the configured mode on the physical X-Touch Extender (Ctrl or CtrlRel, respectively).

Absolute control mode

Available when you set the X-Touch Extender's control mode to Ctrl and called BehringerXTouchExtenderFactory.CreateWithAbsoluteMode().

When the knob is rotated, the rotary encoder will update its RotationPosition Property with the new position of the knob, from 0.0 (most counter-clockwise) to 1.0 (most clockwise).

If you keep turning the knob while it is at one of its numeric limits, the RotationPosition value will stay in the range [0.0, 1.0].

using IAbsoluteBehringerXTouchExtender device = BehringerXTouchExtenderFactory.CreateWithAbsoluteMode();
device.Open();
IAbsoluteRotaryEncoder rotaryEncoder = device.GetRotaryEncoder(0);

rotaryEncoder.RotationPosition.PropertyChanged += (sender, args) => 
        Console.WriteLine($"Knob was turned to {args.NewValue:P0}");
Relative control mode

Available when you set the X-Touch Extender's control mode to CtrlRel and called BehringerXTouchExtenderFactory.CreateWithRelativeMode().

When the knob is rotated, the rotary encoder will emit a Rotated event that tells you in which direction it was rotated. It does not tell you how far it was rotated, instead, it sends more Rotated events as you continue to turn the knob.

Each Rotated event corresponds to turning the knob until you feel the next physical detent. One complete 360° rotation is 24 detents, with 15° between each one.

using IRelativeBehringerXTouchExtender device = BehringerXTouchExtenderFactory.CreateWithRelativeMode();
device.Open();
IRelativeRotaryEncoder rotaryEncoder = device.GetRotaryEncoder(0);

rotaryEncoder.Rotated += (sender, args) =>
        Console.WriteLine($"Knob was turned 15° {(args.IsClockwise ? "clockwise" : "counter-clockwise")}");

Scribble strips

These are the LCD screens at the top of each track column. They are so named because they are digital replacements for putting a strip of tape on your analog mixer and scribbling the channel name on them with a marker.

The eight X-Touch Extender scribble strips can each show two lines of seven ASCII characters each. The background color can be Black, Red, Green, Yellow, Blue, Magenta, Cyan, or White. The text color can be light or dark, with the negative space inverted, and can be set independently for both rows.

I recommend not using a Black background, because it's completely illegible and the LCD appears to be off or broken, even with Light text. Instead, to show white text on a black background, you should set the background color to White and the text color to Light.

IScribbleStrip scribbleStrip = device.GetScribbleStrip(0);
scribbleStrip.TopText.Connect("Hello");
scribbleStrip.BottomText.Connect("World");
scribbleStrip.TopTextColor.Connect(ScribbleStripTextColor.Light);
scribbleStrip.BottomTextColor.Connect(ScribbleStripTextColor.Dark);
scribbleStrip.BackgroundColor.Connect(ScribbleStripBackgroundColor.Magenta);

For low-level protocol details of this control, see the Scribble strip RAW MIDI usage.

VU meters

There are eight lights on each VU meter, at most one of which can be illuminated at any time. They can also all be turned off.

The lights are numbered from 1 at the bottom to 8 at the top, with 0 representing all lights being turned off. You can change which one is illuminated by setting the LightPosition Property.

The bottom four lights (14) are green, the next three (57) are orange, and the top light (8) is red.

The maximum value 8 is available programmatically in the IVuMeter.LightCount property.

IVuMeter vuMeter = device.GetVuMeter(0);
vuMeter.LightPosition.Connect(vuMeter.LightCount);
// Top red light illuminates

Record, solo, mute, and select buttons

IIlluminatedButton recordButton = device.GetRecordButton(0);
IIlluminatedButton soloButton   = device.GetSoloButton(0);
IIlluminatedButton muteButton   = device.GetMuteButton(0);
IIlluminatedButton selectButton = device.GetSelectButton(0);
Illuminating lights

The buttons lights up different colors depending on what kind of button it is when you set their IlluminationState Properties to On. You can also use the Blinking value to make them blink with a pattern of 0.5 seconds on, 0.5 seconds off. The Off value turns off the lights.

The record and mute buttons light up red, the solo buttons light up yellow, and the select buttons light up green. When they are off, the buttons are all gray.

button.IlluminationState.Connect(IlluminatedButtonState.On);
// Light up whenever button is pressed
button.IlluminationState.Connect(DerivedProperty<IlluminatedButtonState>.Create(button.IsPressed, isPressed => 
        isPressed ? IlluminatedButtonState.On : IlluminatedButtonState.Off));
Detecting presses

You can press the buttons and their IsPressed Properties will change to true (pressed) or false (not pressed).

Faders

Faders are the vertical sliders on each track. They are motorized, so you can move them programmatically to any given position. You can also move them manually with your fingers and detect where they went.

The finger rest knobs have capacitive touch sensors, so the device can sense when you are touching the fader knob, which is useful for preventing the device from moving the fader out from under you when you don't expect it and possibly hurting your hand.

IFader fader = device.GetFader(0);
Detecting presses

You can touch the fader knob and its IsPressed Property will change to true (touching) or false (not touching).

fader.IsPressed.PropertyChanged += (sender, args) => 
        Console.WriteLine($"{(args.NewValue ? "Touching" : "Not touching")} fader");
Requesting movement

The range of motion for each fader is from 0.0 at the bottom to 1.0 at the top. If you try to move the fader to a value outside this range, it will be clipped to stay within the range [0.0, 1.0].

Move a fader by changing the value of its DesiredPosition Property.

The printed legends on the physical device go from -∞ at the bottom to 10 at the top, with the 0 legend corresponding to a logical value of about 0.75 in this library.

When you read the value of this Property, it returns the most recent position to which you programmatically requested the fader to move, which may be out-of-date, rather than its current position. To get its current position instead, use ActualPosition (see Detecting movement).

fader.DesiredPosition.Connect(1.0);
// Fader moves all the way to the top of its travel distance.

If the fader is being pressed when you change the DesiredPosition value, the change will be automatically ignored so that the knob moving doesn't surprise or hurt the user.

Detecting movement

There is a separate ActualPosition Property that shows where the fader is currently located, which is different from the DesiredPosition Property that you use to actuate the motor. These are two separate Properties instead of one in order to prevent infinite event loops, and to make it possible to subscribe to only the changes events that you want.

This property will change in response to both manual (finger) and automatic (motor) movement, so it will always have an up-to-date value for the fader's position.

fader.ActualPosition.PropertyChanged += (sender, args) =>
        Console.WriteLine($"Fader moved to {args.NewValue:P0}");

Disposal

When you're done using an IBehringerXTouchExtender<> instance, you should dispose of it to cleanly close the MIDI connection to the device. You can do this explicitly by calling IBehringerXTouchExtender.Dispose(), implicitly with a using statement or declaration, or implicitly with a dependency injection framework that manages the lifecycle of your components.

public void ExplicitlyDispose() {
    var device = BehringerXTouchExtenderFactory.CreateWithRelativeMode();
    device.Open();
    // use device here
    device.Dispose();
}
public void ImplicitlyDisposeWithUsingDeclaration() {
    using var device = BehringerXTouchExtenderFactory.CreateWithRelativeMode();
    device.Open();
    // use device here
    // when control exits the Main method, device will be disposed
}
public void ImplicitlyDisposeWithUsingStatement() {
    using (var device = BehringerXTouchExtenderFactory.CreateWithRelativeMode()) {
        device.Open();
        // use device here
        // when control exits the using block, device will be disposed
    }
}

References

Acknowledgements

  • Maxim Dobroselsky for the DryWetMIDI library that controls MIDI devices from .NET
  • The person on the now-deleted MusicTribe forums who correctly answered a question about the 5th byte of the scribble strip SysEx message, the value of which must be the message length 0x15 instead of the incorrectly documented device ID 0x42
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.1 134 4/25/2024
1.0.0 386 11/12/2022
0.0.1-SNAPSHOT2 167 10/25/2022
0.0.1-SNAPSHOT1 150 10/8/2022