Writing a script action to overcome Bitwig's project "Save" behaviour.
-
- KVRist
- 39 posts since 4 Oct, 2007
It turns out that BW only retrieves the current state of a plugin from the plugin during a project Save when it has detected that there has been a change in the plugin state.
In the case of a network connected plugin (in the case of Vienna Ensemble Pro server hosted plugin) changing a parameter in such a remote plugin GUI, there is no automatic update of the plugin data in the DAW, as there would be in a normal locally hosted plugin instance.
This is where BW is different from other hosts (I've tested). Hosts such as Reaper and Ableton will retrieve the state of plugins regardless. This means that plugins hosted via VEP always have their current state saved with the DAW project. Not so with BW. Generally changes made in remote plugin will get lost after a saved project is reloaded.
I exchanged a thread of emails with BW support back in 2020 over this issue and they confirmed this situation is as I describe and that VEP is a unique case that they were unwilling to support. To them, the prospect of transferring every parameter regardless of whether it has changed was too much.
However, I discovered a workaround: it turns out that if a parameter on the VEP instance plugin (the VEP plugin hosted by the DAW that communicates Audio and control data with the remote server), that if such a parameter is touched, then all the data of that plugin is queried by BW and saved with the project file.
So I am coding a special "Save with data" action that will traverse the project and change and restore a single parameter of each VEP plugin instance before invoking "Save".
My first question would be: is there an API callback that is triggered as the result of a Save command?
In the case of a network connected plugin (in the case of Vienna Ensemble Pro server hosted plugin) changing a parameter in such a remote plugin GUI, there is no automatic update of the plugin data in the DAW, as there would be in a normal locally hosted plugin instance.
This is where BW is different from other hosts (I've tested). Hosts such as Reaper and Ableton will retrieve the state of plugins regardless. This means that plugins hosted via VEP always have their current state saved with the DAW project. Not so with BW. Generally changes made in remote plugin will get lost after a saved project is reloaded.
I exchanged a thread of emails with BW support back in 2020 over this issue and they confirmed this situation is as I describe and that VEP is a unique case that they were unwilling to support. To them, the prospect of transferring every parameter regardless of whether it has changed was too much.
However, I discovered a workaround: it turns out that if a parameter on the VEP instance plugin (the VEP plugin hosted by the DAW that communicates Audio and control data with the remote server), that if such a parameter is touched, then all the data of that plugin is queried by BW and saved with the project file.
So I am coding a special "Save with data" action that will traverse the project and change and restore a single parameter of each VEP plugin instance before invoking "Save".
My first question would be: is there an API callback that is triggered as the result of a Save command?
- KVRist
- 393 posts since 12 Apr, 2020
I don't think there is a callback on save...dungle wrote: Mon Jan 20, 2025 3:58 am It turns out that BW only retrieves the current state of a plugin from the plugin during a project Save when it has detected that there has been a change in the plugin state.
In the case of a network connected plugin (in the case of Vienna Ensemble Pro server hosted plugin) changing a parameter in such a remote plugin GUI, there is no automatic update of the plugin data in the DAW, as there would be in a normal locally hosted plugin instance.
This is where BW is different from other hosts (I've tested). Hosts such as Reaper and Ableton will retrieve the state of plugins regardless. This means that plugins hosted via VEP always have their current state saved with the DAW project. Not so with BW. Generally changes made in remote plugin will get lost after a saved project is reloaded.
I exchanged a thread of emails with BW support back in 2020 over this issue and they confirmed this situation is as I describe and that VEP is a unique case that they were unwilling to support. To them, the prospect of transferring every parameter regardless of whether it has changed was too much.
However, I discovered a workaround: it turns out that if a parameter on the VEP instance plugin (the VEP plugin hosted by the DAW that communicates Audio and control data with the remote server), that if such a parameter is touched, then all the data of that plugin is queried by BW and saved with the project file.
So I am coding a special "Save with data" action that will traverse the project and change and restore a single parameter of each VEP plugin instance before invoking "Save".
My first question would be: is there an API callback that is triggered as the result of a Save command?
your best bet would be to make a signal setting in the document or some midi based event. then trigger both your parameter saving technique and a bitwig save moments later
----------------------------------------------------------------------
/CTRL → http://slashctrl.io
Music & mixes → http://soundcloud.com/kirkwoodwest
/CTRL → http://slashctrl.io
Music & mixes → http://soundcloud.com/kirkwoodwest
-
- KVRist
- Topic Starter
- 39 posts since 4 Oct, 2007
I'm triggering my script via midi, which then does as you suggest, and nudge and return the first parameter of every VEP plugin instance, and then execute a regular Save. It works perfectly well. Just as well too, because I spent a lot of time getting it going. I won't continue describing the intricacies but if anyone wants to use VEP with BW let me know and I can describe what I did.
- KVRist
- 393 posts since 12 Apr, 2020
That’s dope! U put in the time and got the results. Well done!
----------------------------------------------------------------------
/CTRL → http://slashctrl.io
Music & mixes → http://soundcloud.com/kirkwoodwest
/CTRL → http://slashctrl.io
Music & mixes → http://soundcloud.com/kirkwoodwest
-
- KVRist
- 226 posts since 25 Sep, 2022
Hello, I'm interested in your solution, as I have problems with VE Pro parameters in Bitwig, too. And, in the same way, they have pushed the problem away from them. Do you have to map parameters in VE Pro? How do you access them from Bitwig?
Seems like another problem that they are the only ones to meet... I give up with their pathetic (paid!) support.
Seems like another problem that they are the only ones to meet... I give up with their pathetic (paid!) support.
-
- KVRist
- Topic Starter
- 39 posts since 4 Oct, 2007
The quickest and by far simplest and easiest solution is to:
- use a spare knob on a midi controller set it to some obscure CC like 0x7D and if you can, limit its range to 0x62 to 0x64.
- on each VEP track plugin (BW native device panel), right click on "Param 1" and select "Map to Controller or Key ..." .Now wiggle the knob.
Once you done this on every instance in the project, simply wiggle the knob, then hit "Save".
This causes every instance data to be included in the save. Remember to wiggle and save after changing any parameter on a remote plugin sometime before you quit the session.
The global midi mapping generally persists. You have to remember to do the midi learn every time you make a new VEP instance in a project.
There are programmatic ways of doing this that avoid having to remember to set each instance, but developing java extensions for bitwig is a relatively huge learning curve and time investment.
- use a spare knob on a midi controller set it to some obscure CC like 0x7D and if you can, limit its range to 0x62 to 0x64.
- on each VEP track plugin (BW native device panel), right click on "Param 1" and select "Map to Controller or Key ..." .Now wiggle the knob.
Once you done this on every instance in the project, simply wiggle the knob, then hit "Save".
This causes every instance data to be included in the save. Remember to wiggle and save after changing any parameter on a remote plugin sometime before you quit the session.
The global midi mapping generally persists. You have to remember to do the midi learn every time you make a new VEP instance in a project.
There are programmatic ways of doing this that avoid having to remember to set each instance, but developing java extensions for bitwig is a relatively huge learning curve and time investment.
-
- KVRist
- 226 posts since 25 Sep, 2022
For me to understand, this means that you have to expose this parameter to DAW through the automation panel in VEP? https://cdn2.vsl.co.at/manuals/vienna_e ... odD96.webpdungle wrote: Mon Jan 20, 2025 3:58 am ... if a parameter on the VEP instance plugin (the VEP plugin hosted by the DAW that communicates Audio and control data with the remote server), that if such a parameter is touched ...
Because on my side if I do that, Bitwig becomes unusable since they introduced their lazy implementation of VST undo feature. Or does it mean that I have just to wiggle a param on VEP VST device in BW not matter if its linked to something in VEP?
-
- KVRist
- 226 posts since 25 Sep, 2022
AI analysis of Bitwig API:
I have generated a little DisplayFusion script that sends a random MIDI CC and CTRL+S or CTRL+SHIT+S to Bitwig. Tools:
- https://www.displayfusion.com/ (not sure that you can add custom functions in Free version, but this app is so useful that I think $34 is worth it). See launchers in DF functions dialog (picture is just an example of launcher in title bar).
- https://github.com/gbevin/SendMIDI
- https://www.tobias-erichsen.de/software/loopmidi.html or any other virtual MIDI cable
Function flow
Function code:
Set in --- CONFIGURATION --- section for user settings
Start from an exiting Github project.
The Bitwig API provides access to project-related information and actions via the com.bitwig.extension.controller.api.Project interface. However, there are no direct save or load project functions exposed in the API for extensions.
Project-related functions available:
getProject() (returns the current project object)
projectName() (gets the project name)
nextProject(), previousProject() (navigate between projects)
isModified() (checks if the project has unsaved changes)
Scene and track group management: createScene(), createSceneFromPlayingLauncherClips(), getRootTrackGroup(), etc.
No API methods for saving or loading the project file directly are exposed. Project save/load is managed by Bitwig Studio itself, not by the extension API.
If you need to detect when a project is modified or get its name, you can use:
isModified() (returns a BooleanValue)
projectName() (returns a StringValue)
I have generated a little DisplayFusion script that sends a random MIDI CC and CTRL+S or CTRL+SHIT+S to Bitwig. Tools:
- https://www.displayfusion.com/ (not sure that you can add custom functions in Free version, but this app is so useful that I think $34 is worth it). See launchers in DF functions dialog (picture is just an example of launcher in title bar).
- https://github.com/gbevin/SendMIDI
- https://www.tobias-erichsen.de/software/loopmidi.html or any other virtual MIDI cable
Function flow
Code: Select all
User triggers DisplayFusion function
v
DisplayFusionFunction.Run(windowHandle)
v
Get all visible windows
v
Autoselect first window whose title starts with "Bitwig Studio"
v
Create ContextMenuStrip with "Save" and "Save as..." items
v
Show menu at mouse position
v
User clicks "Save" or "Save as..."
v
-------------------------------
| For selected menu item: |
|-----------------------------|
| 1. Generate random CC value |
| 2. Build sendmidi.exe args |
| 3. Start sendmidi.exe |
| 4. Wait for process exit |
| 5. Wait timeoutMs |
| 6. Focus Bitwig window |-----> Bitwig window activated
| 7. Wait 100ms |
| 8. Send Ctrl+S or Ctrl+Shift+S |-----> Bitwig receives key combo (Save/Save As)
-------------------------------
v
Menu closes, function ends
Function code:
Set in --- CONFIGURATION --- section for user settings
Code: Select all
using System;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms;
using System.Threading;
// The 'windowHandle' parameter will contain the window handle for the:
// - Active window when run by hotkey
// - Trigger target when run by a Trigger rule
// - TitleBar Button owner when run by a TitleBar Button
// - Jump List owner when run from a Taskbar Jump List
// - Currently focused window if none of these match
public static class DisplayFusionFunction
{
public static void Run(IntPtr windowHandle)
{
// --- CONFIGURATION ---
string sendmidiPath = @"...\\sendmidi.exe"; // Path to sendmidi.exe
string midiDevice = "loopMIDI Port"; // MIDI output device name
int midiChannel = 1; // MIDI channel (1-16)
int ccNumber = 20; // CC number (0-127)
int ccMin = 0; // Minimum CC value
int ccMax = 127; // Maximum CC value
int timeoutMs = 1000; // Wait time after MIDI send (ms)
Random ccRnd = new Random(); // Random generator for CC value
// --- WINDOW SELECTION ---
// Get all visible and minimized windows
IntPtr[] windowHandles = BFS.Window.GetVisibleAndMinimizedWindowHandles();
IntPtr bitwigHandle = IntPtr.Zero;
for (int i = 0; i < windowHandles.Length; i++)
{
string title = BFS.Window.GetText(windowHandles[i]);
// Autoselect first window whose title starts with "Bitwig Studio"
if (!string.IsNullOrEmpty(title) && title.StartsWith("Bitwig Studio"))
{
bitwigHandle = windowHandles[i];
break;
}
}
if (bitwigHandle == IntPtr.Zero)
{
MessageBox.Show("No Bitwig Studio window found.");
return;
}
// --- MENU CREATION ---
using (ContextMenuStrip menu = new ContextMenuStrip())
{
// Remove check and image margins for cleaner look
menu.ShowCheckMargin = false;
menu.ShowImageMargin = false;
// Add "Save" menu item
var saveItem = new ToolStripMenuItem("Save");
saveItem.Click += (s, e) =>
{
// Generate random CC value for MIDI message
int ccValue = ccRnd.Next(ccMin, ccMax + 1);
var args = $"dev \"{midiDevice}\" ch {midiChannel} cc {ccNumber} {ccValue}";
try
{
// Send MIDI CC message using sendmidi.exe
using (var proc = Process.Start(new ProcessStartInfo {
FileName = sendmidiPath,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true
}))
{
if (proc != null)
{
bool finished = proc.WaitForExit(timeoutMs);
if (!finished)
MessageBox.Show("sendmidi timed out after " + (timeoutMs/1000) + " seconds.");
}
}
}
catch (Exception ex) { MessageBox.Show("sendmidi failed: " + ex.Message); }
// Wait for MIDI to be processed
Thread.Sleep(timeoutMs);
// Focus Bitwig window and send Ctrl+S
BFS.Window.Focus(bitwigHandle);
Thread.Sleep(100); // Ensure focus
SendCtrlS();
};
menu.Items.Add(saveItem);
// Add "Save as..." menu item
var saveAsItem = new ToolStripMenuItem("Save as...");
saveAsItem.Click += (s, e) =>
{
int ccValue = ccRnd.Next(ccMin, ccMax + 1);
var args = $"dev \"{midiDevice}\" ch {midiChannel} cc {ccNumber} {ccValue}";
try
{
using (var proc = Process.Start(new ProcessStartInfo {
FileName = sendmidiPath,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true
}))
{
if (proc != null)
{
bool finished = proc.WaitForExit(timeoutMs);
if (!finished)
MessageBox.Show("sendmidi timed out after " + (timeoutMs/1000) + " seconds.");
}
}
}
catch (Exception ex) { MessageBox.Show("sendmidi failed: " + ex.Message); }
Thread.Sleep(timeoutMs);
BFS.Window.Focus(bitwigHandle);
Thread.Sleep(100);
SendCtrlShiftS();
};
menu.Items.Add(saveAsItem);
// Show menu at mouse position
Point pt = new Point(Cursor.Position.X, Cursor.Position.Y);
menu.Show(pt);
BFS.Window.Focus(menu.Handle);
// Wait for menu to close
while (menu.Visible) Application.DoEvents();
// Refocus Bitwig window after menu closes
BFS.Window.Focus(bitwigHandle);
}
}
private static void FocusBitwig(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero) return;
try { BFS.Window.Focus(hWnd); } catch { }
}
private static void SendCtrlS()
{
SendKeys.SendWait("^s"); // Ctrl+S
}
private static void SendCtrlShiftS()
{
SendKeys.SendWait("^+s"); // Ctrl+Shift+S
}
private static class NativeMethods
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
}
}
With AI things have considerably changed. I was able to do very serious and advanced work without much diving into API specs.dungle wrote: Wed Sep 24, 2025 3:50 am There are programmatic ways of doing this that avoid having to remember to set each instance, but developing java extensions for bitwig is a relatively huge learning curve and time investment.
You do not have the required permissions to view the files attached to this post.
-
- KVRist
- Topic Starter
- 39 posts since 4 Oct, 2007
No, don't map anything in VEP. Like I tried to say you only need to nudge a parameter: those exposed by default in the BW native device panel for VEP plugin. I always do the first one "Param 1". You can midi learn many of these (ie one for each VEP instance) in your project to the same knob.monolithx wrote: Wed Sep 24, 2025 2:51 pm
For me to understand, this means that you have to expose this parameter to DAW through the automation panel in VEP?
AI is a real game changer for any kind of coding these days, but there are other things such as setting up the environment, and generally learning to understand what you're dealing with that are real "time sinks" that need to be balanced with how you want to be spending your time IMO.
The BW API (java) does allow for the triggering of menu actions such as "Save".
