Skip to content

Commit

Permalink
VST3 Fixes for automation and midi (#769)
Browse files Browse the repository at this point in the history
This commit implements the core non-MPE VST3 features which were missing
on mac and windows.

* Parameter names are long names in VST3 and were swprintfed as such
  even though they are non-wchar in surge internals
* Parameter changes in VST3 flow to the UI and adjust widgets
* Bound parameters in the DAW are named correctly
* Midi controllers and pitch bend are captured and sent to surge
* Unicode support is corrected for systems where wchar_t != char16
  (like mac) while still working on Windows, albeit in a somewhat
  clumsy fashion
* Handle oddities around call type allow the plugin to also work
  32 and 64 bit windows (Although demand for 32 bit VST3 seems
  more a formality than practicality, it does work)

Closes #766 VST3 names not correct when learning
Closes #752 VST3 Automation in Bitwig
Closes #26 VST3-Win MIDI Control Issues
  • Loading branch information
baconpaul committed Mar 11, 2019
1 parent 687982c commit 97d1366
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 61 deletions.
3 changes: 3 additions & 0 deletions premake5.lua
Expand Up @@ -133,6 +133,9 @@ elseif (os.istarget("windows")) then

filter "platforms:x86"
targetsuffix "_x86"
defines {
"WIN_X86=1"
}
filter "platforms:x64"
targetsuffix ""
filter {}
Expand Down
2 changes: 1 addition & 1 deletion scripts/win/build-win.ps1
Expand Up @@ -88,7 +88,7 @@ function Install-Surge
New-Item -ItemType Directory -Force -Path $vst2Dir
}
Copy-Item "target\vst2\Release\Surge.dll" -Destination $vst2Dir -Force
Copy-Item "target\vst2\Release\Surge32.dll" -Destination $vst2Dir -Force
Copy-Item "target\vst2\Release\Surge_x86.dll" -Destination $vst2Dir -Force

Write-Host "Start-Process -Verb runAs -WorkingDirectory $PSScriptRoot powershell -argumentlist install-vst3.ps1"
Start-Process -Verb runAs "powershell" -argumentlist "$PSScriptRoot\install-vst3.ps1"
Expand Down
2 changes: 1 addition & 1 deletion scripts/win/install-vst3.ps1
Expand Up @@ -7,4 +7,4 @@ If(!(Test-Path $vstDir))
}

Copy-Item "$PSScriptRoot\..\..\target\vst3\Release\Surge.vst3" -Destination $vstDir -Force
Copy-Item "$PSScriptRoot\..\..\target\vst3\Release\Surge32.vst3" -Destination $vstDir -Force
Copy-Item "$PSScriptRoot\..\..\target\vst3\Release\Surge_x86.vst3" -Destination $vstDir -Force
11 changes: 6 additions & 5 deletions src/common/SurgeSynthesizer.cpp
Expand Up @@ -1913,7 +1913,8 @@ void SurgeSynthesizer::getParameterNameW(long index, wchar_t* ptr)
{
if ((index >= 0) && (index < storage.getPatch().param_ptr.size()))
{
swprintf(ptr, 128, L"%S", storage.getPatch().param_ptr[index]->get_full_name());
// the input is not wide so don't use %S
swprintf(ptr, 128, L"%s", storage.getPatch().param_ptr[index]->get_full_name());
}
else if (index >= metaparam_offset)
{
Expand All @@ -1924,7 +1925,7 @@ void SurgeSynthesizer::getParameterNameW(long index, wchar_t* ptr)
}
else
{
swprintf(ptr, 128, L"C%i:%S", c + 1, storage.getPatch().CustomControllerLabel[c]);
swprintf(ptr, 128, L"C%i:%s", c + 1, storage.getPatch().CustomControllerLabel[c]);
}
}
else
Expand All @@ -1937,7 +1938,7 @@ void SurgeSynthesizer::getParameterShortNameW(long index, wchar_t* ptr)
{
if ((index >= 0) && (index < storage.getPatch().param_ptr.size()))
{
swprintf(ptr, 128, L"%S", storage.getPatch().param_ptr[index]->get_name());
swprintf(ptr, 128, L"%s", storage.getPatch().param_ptr[index]->get_name());
}
else if (index >= metaparam_offset)
{
Expand All @@ -1948,7 +1949,7 @@ void SurgeSynthesizer::getParameterShortNameW(long index, wchar_t* ptr)
}
else
{
swprintf(ptr, 128, L"C%i:%S", c + 1, storage.getPatch().CustomControllerLabel[c]);
swprintf(ptr, 128, L"C%i:%s", c + 1, storage.getPatch().CustomControllerLabel[c]);
}
}
else
Expand Down Expand Up @@ -1976,7 +1977,7 @@ void SurgeSynthesizer::getParameterStringW(long index, float value, wchar_t* ptr
char text[128];
storage.getPatch().param_ptr[index]->get_display(text);

swprintf(ptr, 128, L"%S", text);
swprintf(ptr, 128, L"%s", text);
}
else if (index >= metaparam_offset)
{
Expand Down
6 changes: 5 additions & 1 deletion src/common/gui/SurgeGUIEditor.cpp
Expand Up @@ -40,7 +40,6 @@ const int yofs = 10;
using namespace VSTGUI;
using namespace std;


CFontRef displayFont = NULL;
CFontRef patchNameFont = NULL;
CFontRef lfoTypeFont = NULL;
Expand Down Expand Up @@ -2450,6 +2449,11 @@ long SurgeGUIEditor::applyParameterOffset(long id)
return id-start_paramtags;
}

long SurgeGUIEditor::unapplyParameterOffset(long id)
{
return id + start_paramtags;
}

void SurgeGUIEditor::setZoomFactor(int zf)
{
if (zf < minimumZoom)
Expand Down
5 changes: 3 additions & 2 deletions src/common/gui/SurgeGUIEditor.h
Expand Up @@ -49,8 +49,9 @@ class SurgeGUIEditor : public EditorType, public VSTGUI::IControlListener, publi
virtual void beginEdit(long index);
virtual void endEdit(long index);

virtual long applyParameterOffset(long index);

static long applyParameterOffset(long index);
static long unapplyParameterOffset(long index);

#if !TARGET_VST3
bool open(void* parent) override;
void close() override;
Expand Down
6 changes: 4 additions & 2 deletions src/vst2/Vst2PluginInstance.cpp
Expand Up @@ -530,12 +530,14 @@ VstInt32 Vst2PluginInstance::setChunk(void* data, VstInt32 byteSize, bool isPres

bool Vst2PluginInstance::beginEdit( VstInt32 index )
{
return AudioEffectX::beginEdit( ((SurgeGUIEditor *)editor)->applyParameterOffset( _instance->remapExternalApiToInternalId(index ) ) );
return AudioEffectX::beginEdit(
SurgeGUIEditor::applyParameterOffset(_instance->remapExternalApiToInternalId(index)));
}

bool Vst2PluginInstance::endEdit( VstInt32 index )
{
return AudioEffectX::endEdit( ((SurgeGUIEditor *)editor)->applyParameterOffset( _instance->remapExternalApiToInternalId(index)));
return AudioEffectX::endEdit(
SurgeGUIEditor::applyParameterOffset(_instance->remapExternalApiToInternalId(index)));
}

bool Vst2PluginInstance::tryInit()
Expand Down
187 changes: 141 additions & 46 deletions src/vst3/SurgeVst3Processor.cpp
Expand Up @@ -13,6 +13,12 @@
#include "CScalableBitmap.h"

#include <algorithm>
#include <cwchar>
#include <codecvt>
#include <string.h>

#define MIDI_CONTROLLER_0 20000
#define MIDI_CONTROLLER_MAX 24096

using namespace Steinberg::Vst;

Expand Down Expand Up @@ -52,7 +58,7 @@ tresult PLUGIN_API SurgeVst3Processor::initialize(FUnknown* context)
addAudioOutput(STR16("Stereo Out"), SpeakerArr::kStereo);

//---create Event In/Out busses (1 bus with 16 channels)------
addEventInput(STR16("Note In"));
addEventInput(USTRING("MIDI In"));

// addUnit(new Unit(USTRING ("Macro Parameters"), 1));

Expand Down Expand Up @@ -232,47 +238,68 @@ void SurgeVst3Processor::processParameterChanges(int sampleOffset,
if (paramQueue)
{
int32 offsetSamples;
double value;
double value = 0;
;
int32 numPoints = paramQueue->getPointCount();
/*switch (paramQueue->getParameterId ())
{

}*/
int id = paramQueue->getParameterId();

if (id >= MIDI_CONTROLLER_0 && id <= MIDI_CONTROLLER_MAX)
{
int chancont = id - MIDI_CONTROLLER_0;
int channel = chancont & 0xF;
int cont = chancont >> 4;

for (int i = 0; i < numPoints; ++i)
{
paramQueue->getPoint(0, offsetSamples, value);
if (cont < 128)
{
if (cont == ControllerNumbers::kCtrlAllSoundsOff ||
cont == ControllerNumbers::kCtrlAllNotesOff)
{
surgeInstance->allNotesOff();
}
else
{
surgeInstance->channelController(channel, cont, (int)(value * 128));
}
}
else
switch (cont)
{
case kAfterTouch:
surgeInstance->channelAftertouch(channel, (int)(value * 127.f));
break;
case kPitchBend:
surgeInstance->pitchBend(channel, (int)(value * 8192.f));
break;
case kCtrlProgramChange:
break;
case kCtrlPolyPressure:
break;
default:
break;
}
}
}
else
{
int id = paramQueue->getParameterId();

if (numPoints == 1)
{
paramQueue->getPoint(0, offsetSamples, value);
surgeInstance->setParameter01(id, value, true);
}
else
{
// Unclear what to do here
}
}
}
}
}
/*int32 numParamsChanged = paramChanges->getParameterCount ();
// for each parameter which are some changes in this audio block:
for (int32 i = 0; i < numParamsChanged; i++)
{
IParamValueQueue* paramQueue = paramChanges->getParameterData (i);
if (paramQueue)
{
int32 offsetSamples;
double value;
int32 numPoints = paramQueue->getPointCount ();
switch (paramQueue->getParameterId ())
{
case kGainId:
// we use in this example only the last point of the queue.
// in some wanted case for specific kind of parameter it makes sense to retrieve all
points
// and process the whole audio block in small blocks.
if (paramQueue->getPoint (numPoints - 1, offsetSamples, value) == kResultTrue)
{
fGain = (float)value;
}
break;
case kBypassId:
if (paramQueue->getPoint (numPoints - 1, offsetSamples, value) == kResultTrue)
{
bBypass = (value > 0.5f);
}
break;
}
}
}*/
}

tresult PLUGIN_API SurgeVst3Processor::process(ProcessData& data)
Expand Down Expand Up @@ -414,6 +441,27 @@ IPlugView* PLUGIN_API SurgeVst3Processor::createView(const char* name)
return nullptr;
}

tresult SurgeVst3Processor::beginEdit(ParamID id)
{
int mappedId =
SurgeGUIEditor::applyParameterOffset(surgeInstance->remapExternalApiToInternalId(id));
return Steinberg::Vst::SingleComponentEffect::beginEdit(mappedId);
}

tresult SurgeVst3Processor::performEdit(ParamID id, Steinberg::Vst::ParamValue valueNormalized)
{
int mappedId =
SurgeGUIEditor::applyParameterOffset(surgeInstance->remapExternalApiToInternalId(id));
return Steinberg::Vst::SingleComponentEffect::performEdit(mappedId, valueNormalized);
}

tresult SurgeVst3Processor::endEdit(ParamID id)
{
int mappedId =
SurgeGUIEditor::applyParameterOffset(surgeInstance->remapExternalApiToInternalId(id));
return Steinberg::Vst::SingleComponentEffect::endEdit(mappedId);
}

void SurgeVst3Processor::editorAttached(EditorView* editor)
{
SurgeGUIEditor* view = dynamic_cast<SurgeGUIEditor*>(editor);
Expand All @@ -433,10 +481,14 @@ void SurgeVst3Processor::editorRemoved(EditorView* editor)
}

void SurgeVst3Processor::addDependentView(SurgeGUIEditor* view)
{}
{
viewsSet.insert(view);
}

void SurgeVst3Processor::removeDependentView(SurgeGUIEditor* view)
{}
{
viewsSet.erase(view);
}

int32 PLUGIN_API SurgeVst3Processor::getParameterCount()
{
Expand Down Expand Up @@ -474,9 +526,35 @@ tresult PLUGIN_API SurgeVst3Processor::getParameterInfo(int32 paramIndex, Parame

info.id = id;

surgeInstance->getParameterNameW(id, reinterpret_cast<wchar_t *>(info.title));
surgeInstance->getParameterShortNameW(id, reinterpret_cast<wchar_t *>(info.shortTitle));
surgeInstance->getParameterUnitW(id, reinterpret_cast<wchar_t *>(info.units));
/*
** String128 is a TChar[128] is a char16[128]. On mac, wchar is a char32 so
** the original reinrpret cast didn't work well.
**
** I thought a lot about using std::wstring_convert<std::codecvt_utf8<wchar_t>> here
** but in the end decided to just copy the bytes
*/
wchar_t tmpwchar[512];
surgeInstance->getParameterNameW(id, tmpwchar);
#if MAC
std::copy(tmpwchar, tmpwchar + 128, info.title);
#else
swprintf(reinterpret_cast<wchar_t *>(info.title), 128, L"%S", tmpwchar);
#endif

surgeInstance->getParameterShortNameW(id, tmpwchar);
#if MAC
std::copy(tmpwchar, tmpwchar + 128, info.shortTitle);
#else
swprintf(reinterpret_cast<wchar_t *>(info.shortTitle), 128, L"%S", tmpwchar);
#endif

surgeInstance->getParameterUnitW(id, tmpwchar);
#if MAC
std::copy(tmpwchar, tmpwchar + 128, info.units);
#else
swprintf(reinterpret_cast<wchar_t *>(info.units), 128, L"%S", tmpwchar);
#endif

info.stepCount = 0; // 1 = toggle,
info.defaultNormalizedValue = meta.fdefault;
info.unitId = 0; // meta.clump;
Expand All @@ -496,7 +574,14 @@ tresult PLUGIN_API SurgeVst3Processor::getParamStringByValue(ParamID tag,
return kInvalidArgument;
}

surgeInstance->getParameterStringW(tag, valueNormalized, reinterpret_cast<wchar_t *>(string));

wchar_t tmpwchar[ 512 ];
surgeInstance->getParameterStringW(tag, valueNormalized, tmpwchar);
#if MAC
std::copy(string, string+128, tmpwchar);
#else
swprintf(reinterpret_cast<wchar_t *>(string), 128, L"%S", tmpwchar);
#endif

return kResultOk;
}
Expand Down Expand Up @@ -578,7 +663,14 @@ tresult PLUGIN_API SurgeVst3Processor::getMidiControllerAssignment(int32 busInde
CtrlNumber midiControllerNumber,
ParamID& id /*out*/)
{
return kResultFalse;
/*
** Alrighty dighty. What VST3 wants us to do here is, for the controller number,
** tell it a parameter to map to. We alas don't have a parameter to map to because
** that's not how surge works. But... we can map to parameter id of MIDI_CONTROLLER_0 + id
** and test that elsewhere
*/
id = MIDI_CONTROLLER_0 + midiControllerNumber * 16 + channel;
return kResultTrue;
}

bool SurgeVst3Processor::exportAllMidiControllers()
Expand All @@ -602,9 +694,12 @@ void SurgeVst3Processor::updateDisplay()
// setDirty(true);
}

void SurgeVst3Processor::setParameterAutomated(int externalparam, float value)
void SurgeVst3Processor::setParameterAutomated(int inputParam, float value)
{
beginEdit(externalparam); // TODO
int externalparam = SurgeGUIEditor::unapplyParameterOffset(
surgeInstance->remapExternalApiToInternalId(inputParam));

beginEdit(externalparam);
performEdit(externalparam, value);
endEdit(externalparam);
}
Expand Down

0 comments on commit 97d1366

Please sign in to comment.