Added VST SDK support.
This commit is contained in:
parent
a1482ec110
commit
8e09f58944
25 changed files with 850 additions and 23 deletions
|
|
@ -12,3 +12,8 @@ listening acceptance. It never starts a backend or plays audio.
|
|||
|
||||
`projects/midi-demo.xtc` is an original media/plugin-free MIDI example installed
|
||||
under `share/xtc/examples`. See projects/README.md for opening and playback.
|
||||
|
||||
[Steinberg SDK examples](steinberg/README.md) build an official-SDK VST3 gain
|
||||
and MIDI synth, qualify them with Steinberg's validator and the xtc C host, and
|
||||
render a WAV offline. An optional Windows ASIO device diagnostic is also provided;
|
||||
it is not yet a qualified audio backend.
|
||||
|
|
|
|||
52
examples/steinberg/CMakeLists.txt
Normal file
52
examples/steinberg/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
cmake_minimum_required(VERSION 3.25)
|
||||
project(xtc_steinberg_examples VERSION 1.0.0 LANGUAGES C CXX)
|
||||
set(XTC_VST3_SDK_ROOT "" CACHE PATH "External official VST3 SDK checkout (with submodules)")
|
||||
option(XTC_BUILD_ASIO_EXAMPLE "Build Windows ASIO device diagnostics" OFF)
|
||||
set(XTC_ASIO_SDK_ROOT "" CACHE PATH "External ASIO SDK root")
|
||||
|
||||
if(XTC_VST3_SDK_ROOT)
|
||||
if(NOT EXISTS "${XTC_VST3_SDK_ROOT}/public.sdk/source/vst/vstaudioeffect.h")
|
||||
message(FATAL_ERROR "XTC_VST3_SDK_ROOT needs the official SDK and its submodules")
|
||||
endif()
|
||||
# Never install/link example plugins into the user's plugin directories.
|
||||
set(SMTG_CREATE_PLUGIN_LINK OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_RUN_VST_VALIDATOR OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_ENABLE_VSTGUI_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_ENABLE_VST3_PLUGIN_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_ENABLE_VST3_HOSTING_EXAMPLES ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory("${XTC_VST3_SDK_ROOT}" sdk)
|
||||
set(public_sdk_SOURCE_DIR "${XTC_VST3_SDK_ROOT}/public.sdk")
|
||||
smtg_add_vst3plugin(xtc-sdk-examples sdk_plugins.cpp)
|
||||
target_link_libraries(xtc-sdk-examples PRIVATE sdk)
|
||||
target_compile_features(xtc-sdk-examples PRIVATE cxx_std_17)
|
||||
target_include_directories(xtc-sdk-examples PRIVATE "${XTC_VST3_SDK_ROOT}")
|
||||
if(MSVC)
|
||||
target_compile_options(xtc-sdk-examples PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(xtc-sdk-examples PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(XTC_BUILD_ASIO_EXAMPLE)
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "The ASIO example needs Windows and an installed ASIO driver; Linux uses JACK/PipeWire")
|
||||
endif()
|
||||
foreach(source common/asio.cpp host/asiodrivers.cpp host/pc/asiolist.cpp)
|
||||
if(NOT EXISTS "${XTC_ASIO_SDK_ROOT}/${source}")
|
||||
message(FATAL_ERROR "XTC_ASIO_SDK_ROOT is missing ${source}")
|
||||
endif()
|
||||
endforeach()
|
||||
add_executable(xtc-asio-devices asio_devices.cpp
|
||||
"${XTC_ASIO_SDK_ROOT}/common/asio.cpp"
|
||||
"${XTC_ASIO_SDK_ROOT}/host/asiodrivers.cpp"
|
||||
"${XTC_ASIO_SDK_ROOT}/host/pc/asiolist.cpp")
|
||||
target_include_directories(xtc-asio-devices PRIVATE
|
||||
"${XTC_ASIO_SDK_ROOT}/common" "${XTC_ASIO_SDK_ROOT}/host"
|
||||
"${XTC_ASIO_SDK_ROOT}/host/pc")
|
||||
target_compile_features(xtc-asio-devices PRIVATE cxx_std_17)
|
||||
target_link_libraries(xtc-asio-devices PRIVATE ole32 advapi32 uuid user32)
|
||||
endif()
|
||||
|
||||
if(NOT XTC_VST3_SDK_ROOT AND NOT XTC_BUILD_ASIO_EXAMPLE)
|
||||
message(FATAL_ERROR "Set XTC_VST3_SDK_ROOT or enable XTC_BUILD_ASIO_EXAMPLE with XTC_ASIO_SDK_ROOT")
|
||||
endif()
|
||||
91
examples/steinberg/README.md
Normal file
91
examples/steinberg/README.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Official Steinberg SDK examples
|
||||
|
||||
These are optional integration examples. Normal `make` / `make test` uses the
|
||||
existing pinned C ABI headers and never downloads SDKs. Original example source
|
||||
is MIT-licensed like xtc. SDK code is compiled from an external checkout, not
|
||||
vendored or installed by these targets. C++ is confined to the SDK examples;
|
||||
`render.c` and the xtc host remain C99.
|
||||
|
||||
## VST3 effect and instrument (Linux)
|
||||
|
||||
Requirements: the normal C toolchain, C++17, CMake >= 3.25, and an official VST3
|
||||
SDK checkout with its submodules. SDK hosting utilities can require additional
|
||||
platform development libraries (the inspected SDK discovers X11/GTK). Dependencies
|
||||
are never installed automatically. Obtain the SDK explicitly, for example:
|
||||
|
||||
```sh
|
||||
git clone --recursive https://github.com/steinbergmedia/vst3sdk.git /your/path/vst3sdk
|
||||
```
|
||||
|
||||
Qualified SDK revision: `3cdf9ca5d1f5b1b21e0a86832aa4abe55607bd96`
|
||||
(3.8.1; MIT license). To reproduce this revision, checkout it and run
|
||||
`git submodule update --init --recursive`. Review the license of the actual SDK
|
||||
revision used; this example neither bundles SDK sources nor grants plugin or
|
||||
trademark distribution rights.
|
||||
|
||||
From the xtc repository root:
|
||||
|
||||
```sh
|
||||
make VST3_SDK_ROOT=/your/path/vst3sdk test-steinberg-sdk
|
||||
```
|
||||
|
||||
This builds the original `xtc-sdk-examples.vst3` bundle using Steinberg's factory,
|
||||
AudioEffect, controller and platform-entry implementations; runs the official
|
||||
validator; and runs xtc's gain/MIDI interoperability checks. Build output stays
|
||||
under `build/steinberg-sdk`; automatic plugin-directory links and installation
|
||||
are disabled. SDK source may emit its own compiler warnings.
|
||||
|
||||
The bundle contains:
|
||||
|
||||
| Factory index | Class | Behavior |
|
||||
| --- | --- | --- |
|
||||
| 0 | xtc SDK Half Gain | mono float32 input multiplied by 0.5 |
|
||||
| 1 | xtc SDK Sine | mono MIDI synth; 16 channels; note velocity; note-on/off offsets |
|
||||
| 2 | xtc SDK Controller | non-audio class; no editable parameters |
|
||||
|
||||
Both audio classes are zero-latency, stateless, default-configuration examples.
|
||||
These make good host compatibility checks; they are not production instruments.
|
||||
The sine example uses immediate note release and has no envelope or antialiasing.
|
||||
|
||||
Create a two-second WAV (one second of A4 followed by silence) through the actual
|
||||
xtc synth -> effect host path:
|
||||
|
||||
```sh
|
||||
./build/steinberg-render \
|
||||
"$PWD/build/steinberg-sdk/VST3/Release/xtc-sdk-examples.vst3" \
|
||||
/absolute/path/to/new-sdk-demo.wav
|
||||
```
|
||||
|
||||
The destination must not exist. This executable is built by `test-steinberg-sdk`.
|
||||
To use a different xtc build directory, pass `BUILD_DIR=...`; the SDK build then
|
||||
lives under that directory unless `STEINBERG_BUILD_DIR=...` is explicitly set.
|
||||
|
||||
The main app's existing plugin loader can use the bundle path through its C API;
|
||||
its file chooser may require selecting the inner `.so`. The effect loads as a
|
||||
track insert; the synth is available to the instrument descriptor workflow.
|
||||
See [the hosting contract](../../docs/STEINBERG.md) for remaining limitations.
|
||||
|
||||
## ASIO driver diagnostics (Windows, pending qualification)
|
||||
|
||||
Supply the official ASIO SDK separately. The source layout targets the inspected
|
||||
ASIO 2.3.3 SDK (`common/asio.cpp`, `host/asiodrivers.cpp`, `host/pc/asiolist.cpp`).
|
||||
Its Steinberg SDK license differs from the VST3 revision above; follow the
|
||||
license accompanying your SDK before redistribution. SDK source or binaries are
|
||||
not included in xtc packages. A current SDK may require adapting its source layout.
|
||||
|
||||
On native Windows with a matching MSVC toolchain and an installed ASIO driver:
|
||||
|
||||
```powershell
|
||||
cmake -S examples/steinberg -B build/asio-sdk -DXTC_BUILD_ASIO_EXAMPLE=ON -DXTC_ASIO_SDK_ROOT=C:/SDKs/asiosdk
|
||||
cmake --build build/asio-sdk --config Release --target xtc-asio-devices
|
||||
./build/asio-sdk/Release/xtc-asio-devices.exe
|
||||
./build/asio-sdk/Release/xtc-asio-devices.exe --driver "Exact installed driver name"
|
||||
```
|
||||
|
||||
The first invocation lists drivers registered for the executable's architecture.
|
||||
The second opens only the selected driver, queries its existing channel formats,
|
||||
sample rate, buffer constraints and latencies, then exits. It never calls
|
||||
ASIOStart, ASIOCreateBuffers, ASIOSetSampleRate or the driver's control panel.
|
||||
Linux configuration with this option deliberately fails with a platform message.
|
||||
Windows compilation and real driver execution have **not** been verified here.
|
||||
This is a diagnostic example, not audio playback/capture or an app backend.
|
||||
81
examples/steinberg/asio_devices.cpp
Normal file
81
examples/steinberg/asio_devices.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Original MIT-licensed diagnostics; build against a separately supplied ASIO SDK.
|
||||
// This example inspects an explicitly selected driver. It never starts audio,
|
||||
// changes sample rate, opens a control panel, or installs/registers a driver.
|
||||
#include "asiosys.h"
|
||||
#include "asio.h"
|
||||
#include "asiodrivers.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
// The SDK dispatch layer (including ASIOExit) uses this SDK-owned pointer.
|
||||
extern AsioDrivers* asioDrivers;
|
||||
|
||||
namespace {
|
||||
struct Driver {
|
||||
AsioDrivers drivers;
|
||||
bool initialized = false;
|
||||
Driver() { asioDrivers = &drivers; }
|
||||
~Driver() {
|
||||
if (initialized) ASIOExit();
|
||||
drivers.removeCurrentDriver();
|
||||
asioDrivers = nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 1 && (argc != 3 || std::strcmp(argv[1], "--driver"))) {
|
||||
std::fprintf(stderr, "Usage: xtc-asio-devices [--driver \"installed driver name\"]\n");
|
||||
return 2;
|
||||
}
|
||||
Driver session;
|
||||
if (argc == 1) {
|
||||
const long count = session.drivers.asioGetNumDev();
|
||||
if (count < 0) return 1;
|
||||
for (long i = 0; i < count; ++i) {
|
||||
std::array<char, MAXDRVNAMELEN> name{};
|
||||
if (session.drivers.asioGetDriverName(static_cast<int>(i), name.data(),
|
||||
static_cast<int>(name.size())) != 0) return 1;
|
||||
std::puts(name.data());
|
||||
}
|
||||
if (!count) std::puts("No ASIO drivers registered for this executable's architecture.");
|
||||
return 0;
|
||||
}
|
||||
// ASIO SDK uses char*, but does not retain or modify this argument.
|
||||
if (!session.drivers.loadDriver(argv[2])) {
|
||||
std::fprintf(stderr, "Cannot load the named ASIO driver.\n"); return 1;
|
||||
}
|
||||
ASIODriverInfo info{};
|
||||
info.asioVersion = 2;
|
||||
info.sysRef = GetDesktopWindow();
|
||||
if (ASIOInit(&info) != ASE_OK) {
|
||||
std::fprintf(stderr, "ASIO initialization failed: %.124s\n", info.errorMessage); return 1;
|
||||
}
|
||||
session.initialized = true;
|
||||
long inputs = 0, outputs = 0, minimum = 0, maximum = 0, preferred = 0, granularity = 0;
|
||||
long inputLatency = 0, outputLatency = 0;
|
||||
ASIOSampleRate rate = 0;
|
||||
if (ASIOGetChannels(&inputs, &outputs) != ASE_OK ||
|
||||
ASIOGetSampleRate(&rate) != ASE_OK ||
|
||||
ASIOGetBufferSize(&minimum, &maximum, &preferred, &granularity) != ASE_OK ||
|
||||
ASIOGetLatencies(&inputLatency, &outputLatency) != ASE_OK) {
|
||||
std::fprintf(stderr, "ASIO format/status query failed.\n"); return 1;
|
||||
}
|
||||
std::printf("%.32s | version %ld\nrate %.0f Hz | %ld inputs / %ld outputs\n"
|
||||
"buffer %ld..%ld frames, preferred %ld, granularity %ld\n"
|
||||
"latency input %ld / output %ld frames\n",
|
||||
info.name, info.driverVersion, rate, inputs, outputs,
|
||||
minimum, maximum, preferred, granularity, inputLatency, outputLatency);
|
||||
if (inputs < 0 || outputs < 0 || inputs > 1024 || outputs > 1024) return 1;
|
||||
for (long direction = 0; direction < 2; ++direction) {
|
||||
for (long i = 0; i < (direction ? outputs : inputs); ++i) {
|
||||
ASIOChannelInfo channel{};
|
||||
channel.channel = i;
|
||||
channel.isInput = direction ? ASIOFalse : ASIOTrue;
|
||||
if (ASIOGetChannelInfo(&channel) != ASE_OK) return 1;
|
||||
std::printf("%s %ld: %.32s | ASIO sample type %ld\n",
|
||||
direction ? "output" : "input", i, channel.name, channel.type);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
43
examples/steinberg/render.c
Normal file
43
examples/steinberg/render.c
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* Render the SDK sine -> half-gain examples without an audio server. */
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "xtc/xtc_audio.h"
|
||||
#include <stdio.h>
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
xtc_plugin_spec spec;
|
||||
xtc_plugin *synth = NULL, *gain = NULL;
|
||||
xtc_recorder *writer = NULL;
|
||||
xtc_result result = XTC_ERR_INVALID_ARGUMENT;
|
||||
size_t position;
|
||||
float dry[256], wet[256];
|
||||
if (argc != 3) {
|
||||
fprintf(stderr,"Usage: steinberg-render /absolute/xtc-sdk-examples.vst3 /absolute/new.wav\n");
|
||||
return 2;
|
||||
}
|
||||
result = xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[1],1,48000,&spec);
|
||||
if (result == XTC_OK) result = xtc_plugin_create(&spec,48000,256,&synth);
|
||||
if (result == XTC_OK) result = xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[1],0,48000,&spec);
|
||||
if (result == XTC_OK) result = xtc_plugin_create(&spec,48000,256,&gain);
|
||||
if (result == XTC_OK) result = xtc_recorder_create(argv[2],48000,512,&writer);
|
||||
for (position = 0; result == XTC_OK && position < 96000; position += 256) {
|
||||
size_t frames = 96000-position < 256 ? 96000-position : 256;
|
||||
xtc_midi_event event = {0};
|
||||
size_t count = 0;
|
||||
event.channel = 0; event.pitch = 69; event.velocity = 100;
|
||||
if (!position) { event.type = XTC_MIDI_NOTE_ON; count = 1; }
|
||||
if (position <= 48000 && position+frames > 48000) {
|
||||
event.type = XTC_MIDI_NOTE_OFF; event.offset = (unsigned int)(48000-position); count = 1;
|
||||
}
|
||||
result = xtc_plugin_process_midi_at(synth,&event,count,dry,frames,position);
|
||||
if (result == XTC_OK) result = xtc_plugin_process_at(gain,dry,wet,frames,position);
|
||||
if (result == XTC_OK) {
|
||||
xtc_recorder_push(writer,wet,frames);
|
||||
result = xtc_recorder_drain(writer);
|
||||
}
|
||||
}
|
||||
if (result == XTC_OK) result = xtc_recorder_finish(writer);
|
||||
xtc_recorder_destroy(writer); xtc_plugin_destroy(gain); xtc_plugin_destroy(synth);
|
||||
if (result != XTC_OK) { fprintf(stderr,"SDK example render failed (%d).\n",(int)result); return 1; }
|
||||
puts("Rendered 2 seconds at 48 kHz: A4 for 1 second, then silence.");
|
||||
return 0;
|
||||
}
|
||||
132
examples/steinberg/sdk_plugins.cpp
Normal file
132
examples/steinberg/sdk_plugins.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// Original MIT-licensed xtc examples using an external official Steinberg SDK.
|
||||
// C++ is confined to this optional SDK example, never linked into libxtc.
|
||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||
#include "public.sdk/source/vst/vsteditcontroller.h"
|
||||
#include "public.sdk/source/main/pluginfactory.h"
|
||||
#include "pluginterfaces/base/ibstream.h"
|
||||
#include "pluginterfaces/vst/ivstevents.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
using namespace Steinberg;
|
||||
using namespace Steinberg::Vst;
|
||||
|
||||
const FUID controllerID(0x58544343,0x4F4E5431,0x98561234,0xABCD0003);
|
||||
class Controller final : public EditController {
|
||||
public:
|
||||
static FUnknown* create(void*) { return static_cast<IEditController*>(new Controller); }
|
||||
tresult PLUGIN_API setComponentState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
};
|
||||
|
||||
class Gain final : public AudioEffect {
|
||||
public:
|
||||
Gain() { setControllerClass(controllerID); }
|
||||
static FUnknown* create(void*) { return static_cast<IAudioProcessor*>(new Gain); }
|
||||
tresult PLUGIN_API initialize(FUnknown* host) override {
|
||||
const auto result = AudioEffect::initialize(host);
|
||||
if (result != kResultOk) return result;
|
||||
addAudioInput(STR16("Input"), SpeakerArr::kMono);
|
||||
addAudioOutput(STR16("Output"), SpeakerArr::kMono);
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API setBusArrangements(SpeakerArrangement* in, int32 ni,
|
||||
SpeakerArrangement* out, int32 no) override {
|
||||
if (ni != 1 || no != 1 || !in || !out || in[0] != SpeakerArr::kMono || out[0] != SpeakerArr::kMono)
|
||||
return kResultFalse;
|
||||
return AudioEffect::setBusArrangements(in, ni, out, no);
|
||||
}
|
||||
tresult PLUGIN_API canProcessSampleSize(int32 size) override {
|
||||
return size == kSample32 ? kResultOk : kResultFalse;
|
||||
}
|
||||
tresult PLUGIN_API setProcessing(TBool) override { return kResultOk; }
|
||||
tresult PLUGIN_API process(ProcessData& data) override {
|
||||
if (data.symbolicSampleSize != kSample32 || data.numSamples < 0) return kInvalidArgument;
|
||||
if (!data.numSamples) return kResultOk;
|
||||
if (data.numInputs != 1 || data.numOutputs != 1 || !data.inputs || !data.outputs ||
|
||||
data.inputs[0].numChannels != 1 || data.outputs[0].numChannels != 1 ||
|
||||
!data.inputs[0].channelBuffers32 || !data.outputs[0].channelBuffers32 ||
|
||||
!data.inputs[0].channelBuffers32[0] || !data.outputs[0].channelBuffers32[0]) return kInvalidArgument;
|
||||
for (int32 i = 0; i < data.numSamples; ++i)
|
||||
data.outputs[0].channelBuffers32[0][i] = data.inputs[0].channelBuffers32[0][i] * .5f;
|
||||
data.outputs[0].silenceFlags = data.inputs[0].silenceFlags;
|
||||
return kResultOk;
|
||||
}
|
||||
// Stateless examples still implement the SDK's stream contract.
|
||||
tresult PLUGIN_API setState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
tresult PLUGIN_API getState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
};
|
||||
|
||||
class Sine final : public AudioEffect {
|
||||
struct Voice { double phase = 0; float velocity = 0; };
|
||||
std::array<std::array<Voice, 128>, 16> voices{};
|
||||
void event(const Event& e) {
|
||||
if (e.type == Event::kNoteOnEvent && e.noteOn.channel >= 0 && e.noteOn.channel < 16 &&
|
||||
e.noteOn.pitch >= 0 && e.noteOn.pitch < 128) {
|
||||
auto& v = voices[e.noteOn.channel][e.noteOn.pitch];
|
||||
v.phase = 0; v.velocity = e.noteOn.velocity;
|
||||
} else if (e.type == Event::kNoteOffEvent && e.noteOff.channel >= 0 && e.noteOff.channel < 16 &&
|
||||
e.noteOff.pitch >= 0 && e.noteOff.pitch < 128)
|
||||
voices[e.noteOff.channel][e.noteOff.pitch].velocity = 0;
|
||||
}
|
||||
public:
|
||||
Sine() { setControllerClass(controllerID); }
|
||||
static FUnknown* create(void*) { return static_cast<IAudioProcessor*>(new Sine); }
|
||||
tresult PLUGIN_API initialize(FUnknown* host) override {
|
||||
const auto result = AudioEffect::initialize(host);
|
||||
if (result != kResultOk) return result;
|
||||
addEventInput(STR16("Notes"), 16);
|
||||
addAudioOutput(STR16("Output"), SpeakerArr::kMono);
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API setBusArrangements(SpeakerArrangement*, int32 ni,
|
||||
SpeakerArrangement* out, int32 no) override {
|
||||
if (ni != 0 || no != 1 || !out || out[0] != SpeakerArr::kMono) return kResultFalse;
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API canProcessSampleSize(int32 size) override { return size == kSample32 ? kResultOk : kResultFalse; }
|
||||
tresult PLUGIN_API setProcessing(TBool) override { voices = {}; return kResultOk; }
|
||||
tresult PLUGIN_API process(ProcessData& data) override {
|
||||
if (data.symbolicSampleSize != kSample32 || data.numSamples < 0) return kInvalidArgument;
|
||||
if (!data.numSamples) return kResultOk;
|
||||
if (data.numOutputs != 1 || !data.outputs || data.outputs[0].numChannels != 1 ||
|
||||
!data.outputs[0].channelBuffers32 || !data.outputs[0].channelBuffers32[0]) return kInvalidArgument;
|
||||
int32 index = 0;
|
||||
const int32 count = data.inputEvents ? data.inputEvents->getEventCount() : 0;
|
||||
Event next{};
|
||||
bool pending = index < count && data.inputEvents->getEvent(index, next) == kResultOk;
|
||||
bool silent = true;
|
||||
for (int32 i = 0; i < data.numSamples; ++i) {
|
||||
while (pending && next.sampleOffset <= i) {
|
||||
event(next); ++index;
|
||||
pending = index < count && data.inputEvents->getEvent(index, next) == kResultOk;
|
||||
}
|
||||
double value = 0;
|
||||
for (auto& channel : voices) for (size_t key = 0; key < channel.size(); ++key) {
|
||||
auto& v = channel[key];
|
||||
if (v.velocity == 0) continue;
|
||||
value += std::sin(v.phase) * v.velocity * .1;
|
||||
v.phase += 6.283185307179586 * 440.0 * std::pow(2.0, (static_cast<double>(key)-69.0)/12.0) / processSetup.sampleRate;
|
||||
v.phase = std::fmod(v.phase, 6.283185307179586);
|
||||
}
|
||||
data.outputs[0].channelBuffers32[0][i] = static_cast<float>(std::tanh(value));
|
||||
if (value != 0) silent = false;
|
||||
}
|
||||
data.outputs[0].silenceFlags = silent ? 1 : 0;
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API setState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
tresult PLUGIN_API getState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
};
|
||||
}
|
||||
BEGIN_FACTORY("xtc", "", "", 0)
|
||||
DEF_CLASS2(INLINE_UID(0x58544347,0x41494E31,0x98561234,0xABCD0001),
|
||||
PClassInfo::kManyInstances, kVstAudioEffectClass, "xtc SDK Half Gain", 0,
|
||||
"Fx", "1.0.0", kVstVersionString, Gain::create)
|
||||
DEF_CLASS2(INLINE_UID(0x58544353,0x494E4531,0x98561234,0xABCD0002),
|
||||
PClassInfo::kManyInstances, kVstAudioEffectClass, "xtc SDK Sine", 0,
|
||||
"Instrument|Synth", "1.0.0", kVstVersionString, Sine::create)
|
||||
DEF_CLASS2(INLINE_UID(0x58544343,0x4F4E5431,0x98561234,0xABCD0003),
|
||||
PClassInfo::kManyInstances, kVstComponentControllerClass, "xtc SDK Controller", 0,
|
||||
"", "1.0.0", kVstVersionString, Controller::create)
|
||||
END_FACTORY
|
||||
Loading…
Add table
Add a link
Reference in a new issue