|
|
@@ -0,0 +1,801 @@ |
|
|
|
/* |
|
|
|
* Carla SFZero Plugin |
|
|
|
* Copyright (C) 2018 Filipe Coelho <falktx@falktx.com> |
|
|
|
* |
|
|
|
* This program is free software; you can redistribute it and/or |
|
|
|
* modify it under the terms of the GNU General Public License as |
|
|
|
* published by the Free Software Foundation; either version 2 of |
|
|
|
* the License, or any later version. |
|
|
|
* |
|
|
|
* This program is distributed in the hope that it will be useful, |
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
|
* GNU General Public License for more details. |
|
|
|
* |
|
|
|
* For a full copy of the GNU General Public License see the doc/GPL.txt file. |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "CarlaPluginInternal.hpp" |
|
|
|
#include "CarlaEngine.hpp" |
|
|
|
|
|
|
|
#include "sfzero/SFZero.h" |
|
|
|
|
|
|
|
#include "water/audioformat/AudioFormatManager.h" |
|
|
|
#include "water/buffers/AudioSampleBuffer.h" |
|
|
|
#include "water/files/File.h" |
|
|
|
#include "water/memory/SharedResourcePointer.h" |
|
|
|
#include "water/midi/MidiMessage.h" |
|
|
|
|
|
|
|
using water::AudioFormatManager; |
|
|
|
using water::AudioSampleBuffer; |
|
|
|
using water::File; |
|
|
|
using water::MidiMessage; |
|
|
|
using water::SharedResourcePointer; |
|
|
|
using water::String; |
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
CARLA_BACKEND_START_NAMESPACE |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------- |
|
|
|
// Fallback data |
|
|
|
|
|
|
|
static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 }; |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
class AutoAudioFormatManager : public AudioFormatManager |
|
|
|
{ |
|
|
|
public: |
|
|
|
AutoAudioFormatManager() |
|
|
|
: AudioFormatManager() |
|
|
|
{ |
|
|
|
registerBasicFormats(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
class CarlaPluginSFZero : public CarlaPlugin |
|
|
|
{ |
|
|
|
public: |
|
|
|
CarlaPluginSFZero(CarlaEngine* const engine, const uint id) |
|
|
|
: CarlaPlugin(engine, id), |
|
|
|
fSynth(), |
|
|
|
fNumVoices(0.0f), |
|
|
|
fLabel(nullptr), |
|
|
|
fRealName(nullptr) |
|
|
|
{ |
|
|
|
carla_debug("CarlaPluginSFZero::CarlaPluginSFZero(%p, %i)", engine, id); |
|
|
|
} |
|
|
|
|
|
|
|
~CarlaPluginSFZero() override |
|
|
|
{ |
|
|
|
carla_debug("CarlaPluginSFZero::~CarlaPluginSFZero()"); |
|
|
|
|
|
|
|
pData->singleMutex.lock(); |
|
|
|
pData->masterMutex.lock(); |
|
|
|
|
|
|
|
if (pData->client != nullptr && pData->client->isActive()) |
|
|
|
pData->client->deactivate(); |
|
|
|
|
|
|
|
if (pData->active) |
|
|
|
{ |
|
|
|
deactivate(); |
|
|
|
pData->active = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (fLabel != nullptr) |
|
|
|
{ |
|
|
|
delete[] fLabel; |
|
|
|
fLabel = nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
if (fRealName != nullptr) |
|
|
|
{ |
|
|
|
delete[] fRealName; |
|
|
|
fRealName = nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
clearBuffers(); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Information (base) |
|
|
|
|
|
|
|
PluginType getType() const noexcept override |
|
|
|
{ |
|
|
|
return PLUGIN_SFZ; |
|
|
|
} |
|
|
|
|
|
|
|
PluginCategory getCategory() const noexcept override |
|
|
|
{ |
|
|
|
return PLUGIN_CATEGORY_SYNTH; |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Information (count) |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Information (current data) |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Information (per-plugin data) |
|
|
|
|
|
|
|
uint getOptionsAvailable() const noexcept override |
|
|
|
{ |
|
|
|
uint options = 0x0; |
|
|
|
|
|
|
|
options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; |
|
|
|
options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; |
|
|
|
options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; |
|
|
|
options |= PLUGIN_OPTION_SEND_PITCHBEND; |
|
|
|
options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; |
|
|
|
|
|
|
|
return options; |
|
|
|
} |
|
|
|
|
|
|
|
float getParameterValue(const uint32_t parameterId) const noexcept override |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(parameterId == 0, 0.0f); |
|
|
|
|
|
|
|
return fNumVoices; |
|
|
|
} |
|
|
|
|
|
|
|
void getLabel(char* const strBuf) const noexcept override |
|
|
|
{ |
|
|
|
if (fLabel != nullptr) |
|
|
|
{ |
|
|
|
std::strncpy(strBuf, fLabel, STR_MAX); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
CarlaPlugin::getLabel(strBuf); |
|
|
|
} |
|
|
|
|
|
|
|
void getMaker(char* const strBuf) const noexcept override |
|
|
|
{ |
|
|
|
std::strncpy(strBuf, "SFZero engine", STR_MAX); |
|
|
|
} |
|
|
|
|
|
|
|
void getCopyright(char* const strBuf) const noexcept override |
|
|
|
{ |
|
|
|
std::strncpy(strBuf, "ISC", STR_MAX); |
|
|
|
} |
|
|
|
|
|
|
|
void getRealName(char* const strBuf) const noexcept override |
|
|
|
{ |
|
|
|
if (fRealName != nullptr) |
|
|
|
{ |
|
|
|
std::strncpy(strBuf, fRealName, STR_MAX); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
CarlaPlugin::getRealName(strBuf); |
|
|
|
} |
|
|
|
|
|
|
|
void getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(parameterId == 0,); |
|
|
|
|
|
|
|
std::strncpy(strBuf, "Voice Count", STR_MAX); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Set data (state) |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Set data (internal stuff) |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Set data (plugin-specific stuff) |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Set ui stuff |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Plugin state |
|
|
|
|
|
|
|
void reload() override |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); |
|
|
|
carla_debug("CarlaPluginSFZero::reload() - start"); |
|
|
|
|
|
|
|
const EngineProcessMode processMode(pData->engine->getProccessMode()); |
|
|
|
|
|
|
|
// Safely disable plugin for reload |
|
|
|
const ScopedDisabler sd(this); |
|
|
|
|
|
|
|
if (pData->active) |
|
|
|
deactivate(); |
|
|
|
|
|
|
|
clearBuffers(); |
|
|
|
|
|
|
|
pData->audioOut.createNew(2); |
|
|
|
pData->param.createNew(1, false); |
|
|
|
|
|
|
|
const uint portNameSize(pData->engine->getMaxPortNameSize()); |
|
|
|
CarlaString portName; |
|
|
|
|
|
|
|
// --------------------------------------- |
|
|
|
// Audio Outputs |
|
|
|
|
|
|
|
// out-left |
|
|
|
portName.clear(); |
|
|
|
|
|
|
|
if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) |
|
|
|
{ |
|
|
|
portName = pData->name; |
|
|
|
portName += ":"; |
|
|
|
} |
|
|
|
|
|
|
|
portName += "out-left"; |
|
|
|
portName.truncate(portNameSize); |
|
|
|
|
|
|
|
pData->audioOut.ports[0].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, 0); |
|
|
|
pData->audioOut.ports[0].rindex = 0; |
|
|
|
|
|
|
|
// out-right |
|
|
|
portName.clear(); |
|
|
|
|
|
|
|
if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) |
|
|
|
{ |
|
|
|
portName = pData->name; |
|
|
|
portName += ":"; |
|
|
|
} |
|
|
|
|
|
|
|
portName += "out-right"; |
|
|
|
portName.truncate(portNameSize); |
|
|
|
|
|
|
|
pData->audioOut.ports[1].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, 1); |
|
|
|
pData->audioOut.ports[1].rindex = 1; |
|
|
|
|
|
|
|
// --------------------------------------- |
|
|
|
// Event Input |
|
|
|
|
|
|
|
portName.clear(); |
|
|
|
|
|
|
|
if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) |
|
|
|
{ |
|
|
|
portName = pData->name; |
|
|
|
portName += ":"; |
|
|
|
} |
|
|
|
|
|
|
|
portName += "events-in"; |
|
|
|
portName.truncate(portNameSize); |
|
|
|
|
|
|
|
pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0); |
|
|
|
|
|
|
|
// --------------------------------------- |
|
|
|
// Parameters |
|
|
|
|
|
|
|
pData->param.data[0].type = PARAMETER_OUTPUT; |
|
|
|
pData->param.data[0].hints = PARAMETER_IS_ENABLED | PARAMETER_IS_AUTOMABLE | PARAMETER_IS_INTEGER; |
|
|
|
pData->param.data[0].index = 0; |
|
|
|
pData->param.data[0].rindex = 0; |
|
|
|
pData->param.ranges[0].min = 0.0f; |
|
|
|
pData->param.ranges[0].max = 128; |
|
|
|
pData->param.ranges[0].def = 0.0f; |
|
|
|
pData->param.ranges[0].step = 1.0f; |
|
|
|
pData->param.ranges[0].stepSmall = 1.0f; |
|
|
|
pData->param.ranges[0].stepLarge = 1.0f; |
|
|
|
|
|
|
|
// --------------------------------------- |
|
|
|
|
|
|
|
// plugin hints |
|
|
|
pData->hints = 0x0; |
|
|
|
pData->hints |= PLUGIN_IS_SYNTH; |
|
|
|
pData->hints |= PLUGIN_CAN_VOLUME; |
|
|
|
pData->hints |= PLUGIN_CAN_BALANCE; |
|
|
|
|
|
|
|
// extra plugin hints |
|
|
|
pData->extraHints = 0x0; |
|
|
|
pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_IN; |
|
|
|
pData->extraHints |= PLUGIN_EXTRA_HINT_CAN_RUN_RACK; |
|
|
|
|
|
|
|
bufferSizeChanged(pData->engine->getBufferSize()); |
|
|
|
reloadPrograms(true); |
|
|
|
|
|
|
|
if (pData->active) |
|
|
|
activate(); |
|
|
|
|
|
|
|
carla_debug("CarlaPluginSFZero::reload() - end"); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Plugin processing |
|
|
|
|
|
|
|
void process(const float** const, float** const audioOut, const float** const, float** const, const uint32_t frames) override |
|
|
|
{ |
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Check if active |
|
|
|
|
|
|
|
if (! pData->active) |
|
|
|
{ |
|
|
|
// disable any output sound |
|
|
|
for (uint32_t i=0; i < pData->audioOut.count; ++i) |
|
|
|
carla_zeroFloats(audioOut[i], frames); |
|
|
|
|
|
|
|
fNumVoices = 0.0f; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Check if needs reset |
|
|
|
|
|
|
|
if (pData->needsReset) |
|
|
|
{ |
|
|
|
fSynth.allNotesOff(0, false); |
|
|
|
pData->needsReset = false; |
|
|
|
} |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Event Input and Processing |
|
|
|
|
|
|
|
{ |
|
|
|
// ---------------------------------------------------------------------------------------------------- |
|
|
|
// Setup audio buffer |
|
|
|
|
|
|
|
AudioSampleBuffer audioOutBuffer(audioOut, 2, frames); |
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------- |
|
|
|
// MIDI Input (External) |
|
|
|
|
|
|
|
if (pData->extNotes.mutex.tryLock()) |
|
|
|
{ |
|
|
|
for (RtLinkedList<ExternalMidiNote>::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next()) |
|
|
|
{ |
|
|
|
const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback)); |
|
|
|
CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS); |
|
|
|
|
|
|
|
if (note.velo > 0) |
|
|
|
fSynth.noteOn(note.channel+1, note.note, static_cast<float>(note.velo)/127.0f); |
|
|
|
else |
|
|
|
fSynth.noteOff(note.channel+1, note.note, static_cast<float>(note.velo)/127.0f, true); |
|
|
|
} |
|
|
|
|
|
|
|
pData->extNotes.data.clear(); |
|
|
|
pData->extNotes.mutex.unlock(); |
|
|
|
|
|
|
|
} // End of MIDI Input (External) |
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------- |
|
|
|
// Event Input (System) |
|
|
|
|
|
|
|
#ifndef BUILD_BRIDGE |
|
|
|
bool allNotesOffSent = false; |
|
|
|
#endif |
|
|
|
uint32_t timeOffset = 0; |
|
|
|
|
|
|
|
for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i) |
|
|
|
{ |
|
|
|
const EngineEvent& event(pData->event.portIn->getEvent(i)); |
|
|
|
|
|
|
|
uint32_t eventTime = event.time; |
|
|
|
CARLA_SAFE_ASSERT_CONTINUE(eventTime < frames); |
|
|
|
|
|
|
|
if (eventTime < timeOffset) |
|
|
|
{ |
|
|
|
carla_stderr2("Timing error, eventTime:%u < timeOffset:%u for '%s'", |
|
|
|
eventTime, timeOffset, pData->name); |
|
|
|
eventTime = timeOffset; |
|
|
|
} |
|
|
|
else if (eventTime > timeOffset) |
|
|
|
{ |
|
|
|
if (processSingle(audioOutBuffer, eventTime - timeOffset, timeOffset)) |
|
|
|
timeOffset = eventTime; |
|
|
|
} |
|
|
|
|
|
|
|
// Control change |
|
|
|
switch (event.type) |
|
|
|
{ |
|
|
|
case kEngineEventTypeNull: |
|
|
|
break; |
|
|
|
|
|
|
|
case kEngineEventTypeControl: |
|
|
|
{ |
|
|
|
const EngineControlEvent& ctrlEvent = event.ctrl; |
|
|
|
|
|
|
|
switch (ctrlEvent.type) |
|
|
|
{ |
|
|
|
case kEngineControlEventTypeNull: |
|
|
|
break; |
|
|
|
|
|
|
|
case kEngineControlEventTypeParameter: |
|
|
|
{ |
|
|
|
#ifndef BUILD_BRIDGE |
|
|
|
// Control backend stuff |
|
|
|
if (event.channel == pData->ctrlChannel) |
|
|
|
{ |
|
|
|
float value; |
|
|
|
|
|
|
|
if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0) |
|
|
|
{ |
|
|
|
value = ctrlEvent.value; |
|
|
|
setDryWetRT(value); |
|
|
|
} |
|
|
|
|
|
|
|
if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0) |
|
|
|
{ |
|
|
|
value = ctrlEvent.value*127.0f/100.0f; |
|
|
|
setVolumeRT(value); |
|
|
|
} |
|
|
|
|
|
|
|
if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0) |
|
|
|
{ |
|
|
|
float left, right; |
|
|
|
value = ctrlEvent.value/0.5f - 1.0f; |
|
|
|
|
|
|
|
if (value < 0.0f) |
|
|
|
{ |
|
|
|
left = -1.0f; |
|
|
|
right = (value*2.0f)+1.0f; |
|
|
|
} |
|
|
|
else if (value > 0.0f) |
|
|
|
{ |
|
|
|
left = (value*2.0f)-1.0f; |
|
|
|
right = 1.0f; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
left = -1.0f; |
|
|
|
right = 1.0f; |
|
|
|
} |
|
|
|
|
|
|
|
setBalanceLeftRT(left); |
|
|
|
setBalanceRightRT(right); |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
// Control plugin parameters |
|
|
|
for (uint32_t k=0; k < pData->param.count; ++k) |
|
|
|
{ |
|
|
|
if (pData->param.data[k].midiChannel != event.channel) |
|
|
|
continue; |
|
|
|
if (pData->param.data[k].midiCC != ctrlEvent.param) |
|
|
|
continue; |
|
|
|
if (pData->param.data[k].hints != PARAMETER_INPUT) |
|
|
|
continue; |
|
|
|
if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0) |
|
|
|
continue; |
|
|
|
|
|
|
|
float value; |
|
|
|
|
|
|
|
if (pData->param.data[k].hints & PARAMETER_IS_BOOLEAN) |
|
|
|
{ |
|
|
|
value = (ctrlEvent.value < 0.5f) ? pData->param.ranges[k].min : pData->param.ranges[k].max; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
value = pData->param.ranges[k].getUnnormalizedValue(ctrlEvent.value); |
|
|
|
|
|
|
|
if (pData->param.data[k].hints & PARAMETER_IS_INTEGER) |
|
|
|
value = std::rint(value); |
|
|
|
} |
|
|
|
|
|
|
|
setParameterValueRT(k, value); |
|
|
|
} |
|
|
|
|
|
|
|
if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_CONTROL) |
|
|
|
{ |
|
|
|
fSynth.handleController(event.channel+1, ctrlEvent.param, int(ctrlEvent.value*127.0f)); |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
case kEngineControlEventTypeMidiBank: |
|
|
|
case kEngineControlEventTypeMidiProgram: |
|
|
|
case kEngineControlEventTypeAllSoundOff: |
|
|
|
break; |
|
|
|
|
|
|
|
case kEngineControlEventTypeAllNotesOff: |
|
|
|
if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) |
|
|
|
{ |
|
|
|
#ifndef BUILD_BRIDGE |
|
|
|
if (event.channel == pData->ctrlChannel && ! allNotesOffSent) |
|
|
|
{ |
|
|
|
allNotesOffSent = true; |
|
|
|
sendMidiAllNotesOffToCallback(); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
fSynth.allNotesOff(event.channel+1, true); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
case kEngineEventTypeMidi: { |
|
|
|
const EngineMidiEvent& midiEvent(event.midi); |
|
|
|
|
|
|
|
const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data); |
|
|
|
|
|
|
|
uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData)); |
|
|
|
|
|
|
|
if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0) |
|
|
|
continue; |
|
|
|
if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0) |
|
|
|
continue; |
|
|
|
if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) |
|
|
|
continue; |
|
|
|
if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0) |
|
|
|
continue; |
|
|
|
|
|
|
|
// Fix bad note-off |
|
|
|
if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0) |
|
|
|
status = MIDI_STATUS_NOTE_OFF; |
|
|
|
|
|
|
|
// put back channel in data |
|
|
|
uint8_t midiData2[midiEvent.size]; |
|
|
|
midiData2[0] = uint8_t(status | (event.channel & MIDI_CHANNEL_BIT)); |
|
|
|
std::memcpy(midiData2+1, midiData+1, static_cast<std::size_t>(midiEvent.size-1)); |
|
|
|
|
|
|
|
const MidiMessage midiMessage(midiData2, static_cast<int>(midiEvent.size), 0.0); |
|
|
|
|
|
|
|
fSynth.handleMidiEvent(midiMessage); |
|
|
|
|
|
|
|
if (status == MIDI_STATUS_NOTE_ON) |
|
|
|
pData->postponeRtEvent(kPluginPostRtEventNoteOn, event.channel, midiData[1], midiData[2]); |
|
|
|
else if (status == MIDI_STATUS_NOTE_OFF) |
|
|
|
pData->postponeRtEvent(kPluginPostRtEventNoteOff, event.channel, midiData[1], 0.0f); |
|
|
|
} break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pData->postRtEvents.trySplice(); |
|
|
|
|
|
|
|
if (frames > timeOffset) |
|
|
|
processSingle(audioOutBuffer, frames - timeOffset, timeOffset); |
|
|
|
|
|
|
|
} // End of Event Input and Processing |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Parameter outputs |
|
|
|
|
|
|
|
fNumVoices = fSynth.numVoicesUsed(); |
|
|
|
} |
|
|
|
|
|
|
|
bool processSingle(AudioSampleBuffer& audioOutBuffer, const uint32_t frames, const uint32_t timeOffset) |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(frames > 0, false); |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Try lock, silence otherwise |
|
|
|
|
|
|
|
#ifndef STOAT_TEST_BUILD |
|
|
|
if (pData->engine->isOffline()) |
|
|
|
{ |
|
|
|
pData->singleMutex.lock(); |
|
|
|
} |
|
|
|
else |
|
|
|
#endif |
|
|
|
if (! pData->singleMutex.tryLock()) |
|
|
|
{ |
|
|
|
audioOutBuffer.clear(timeOffset, frames); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Run plugin |
|
|
|
|
|
|
|
fSynth.renderVoices(audioOutBuffer, timeOffset, frames); |
|
|
|
|
|
|
|
#if 0 // ndef BUILD_BRIDGE |
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
// Post-processing (dry/wet, volume and balance) |
|
|
|
|
|
|
|
{ |
|
|
|
const bool doVolume = carla_isNotEqual(pData->postProc.volume, 1.0f); |
|
|
|
// const bool doBalance = carla_isNotEqual(pData->postProc.balanceLeft, -1.0f) || carla_isNotEqual(pData->postProc.balanceRight, 1.0f); |
|
|
|
|
|
|
|
float* outBufferL = audioOutBuffer.getWritePointer(0, timeOffset); |
|
|
|
float* outBufferR = audioOutBuffer.getWritePointer(1, timeOffset); |
|
|
|
|
|
|
|
#if 0 |
|
|
|
if (doBalance) |
|
|
|
{ |
|
|
|
float oldBufLeft[frames]; |
|
|
|
|
|
|
|
// there was a loop here |
|
|
|
{ |
|
|
|
if (i % 2 == 0) |
|
|
|
carla_copyFloats(oldBufLeft, outBuffer[i], frames); |
|
|
|
|
|
|
|
float balRangeL = (pData->postProc.balanceLeft + 1.0f)/2.0f; |
|
|
|
float balRangeR = (pData->postProc.balanceRight + 1.0f)/2.0f; |
|
|
|
|
|
|
|
for (uint32_t k=0; k < frames; ++k) |
|
|
|
{ |
|
|
|
if (i % 2 == 0) |
|
|
|
{ |
|
|
|
// left |
|
|
|
outBuffer[i][k] = oldBufLeft[k] * (1.0f - balRangeL); |
|
|
|
outBuffer[i][k] += outBuffer[i+1][k] * (1.0f - balRangeR); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// right |
|
|
|
outBuffer[i][k] = outBuffer[i][k] * balRangeR; |
|
|
|
outBuffer[i][k] += oldBufLeft[k] * balRangeL; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
if (doVolume) |
|
|
|
{ |
|
|
|
for (uint32_t k=0; k < frames; ++k) |
|
|
|
{ |
|
|
|
*outBufferL++ *= pData->postProc.volume; |
|
|
|
*outBufferR++ *= pData->postProc.volume; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} // End of Post-processing |
|
|
|
#endif |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
pData->singleMutex.unlock(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
void sampleRateChanged(const double newSampleRate) override |
|
|
|
{ |
|
|
|
fSynth.setCurrentPlaybackSampleRate(newSampleRate); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Plugin buffers |
|
|
|
|
|
|
|
// nothing |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
|
|
bool init(const char* const filename, const char* const name, const char* const label, const uint options) |
|
|
|
{ |
|
|
|
CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false); |
|
|
|
|
|
|
|
// --------------------------------------------------------------- |
|
|
|
// first checks |
|
|
|
|
|
|
|
if (pData->client != nullptr) |
|
|
|
{ |
|
|
|
pData->engine->setLastError("Plugin client is already registered"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (filename == nullptr || filename[0] == '\0') |
|
|
|
{ |
|
|
|
pData->engine->setLastError("null filename"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
for (int i = 128; --i >=0;) |
|
|
|
fSynth.addVoice(new sfzero::Voice()); |
|
|
|
|
|
|
|
// --------------------------------------------------------------- |
|
|
|
// Init SFZero stuff |
|
|
|
|
|
|
|
fSynth.setCurrentPlaybackSampleRate(pData->engine->getSampleRate()); |
|
|
|
|
|
|
|
File file(filename); |
|
|
|
sfzero::Sound* const sound = new sfzero::Sound(file); |
|
|
|
|
|
|
|
sound->loadRegions(); |
|
|
|
sound->loadSamples(sAudioFormatManager.getPointer(), nullptr, nullptr); |
|
|
|
|
|
|
|
if (fSynth.addSound(sound) == nullptr) |
|
|
|
{ |
|
|
|
pData->engine->setLastError("Failed to allocate SFZ sounds in memory"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// --------------------------------------------------------------- |
|
|
|
|
|
|
|
const String basename(File(filename).getFileNameWithoutExtension()); |
|
|
|
|
|
|
|
CarlaString label2(label != nullptr ? label : basename.toRawUTF8()); |
|
|
|
|
|
|
|
fLabel = label2.dup(); |
|
|
|
fRealName = carla_strdup(basename.toRawUTF8()); |
|
|
|
|
|
|
|
pData->filename = carla_strdup(filename); |
|
|
|
|
|
|
|
if (name != nullptr && name[0] != '\0') |
|
|
|
pData->name = pData->engine->getUniquePluginName(name); |
|
|
|
else if (fRealName[0] != '\0') |
|
|
|
pData->name = pData->engine->getUniquePluginName(fRealName); |
|
|
|
else |
|
|
|
pData->name = pData->engine->getUniquePluginName(fLabel); |
|
|
|
|
|
|
|
// --------------------------------------------------------------- |
|
|
|
// register client |
|
|
|
|
|
|
|
pData->client = pData->engine->addClient(this); |
|
|
|
|
|
|
|
if (pData->client == nullptr || ! pData->client->isOk()) |
|
|
|
{ |
|
|
|
pData->engine->setLastError("Failed to register plugin client"); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// --------------------------------------------------------------- |
|
|
|
// set default options |
|
|
|
|
|
|
|
pData->options = 0x0; |
|
|
|
pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; |
|
|
|
pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; |
|
|
|
pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; |
|
|
|
pData->options |= PLUGIN_OPTION_SEND_PITCHBEND; |
|
|
|
pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
(void)options; |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
|
|
private: |
|
|
|
sfzero::Synth fSynth; |
|
|
|
float fNumVoices; |
|
|
|
|
|
|
|
const char* fLabel; |
|
|
|
const char* fRealName; |
|
|
|
|
|
|
|
SharedResourcePointer<AutoAudioFormatManager> sAudioFormatManager; |
|
|
|
|
|
|
|
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginSFZero) |
|
|
|
}; |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
CarlaPlugin* CarlaPlugin::newSFZero(const Initializer& init) |
|
|
|
{ |
|
|
|
carla_debug("CarlaPluginSFZero::newLinuxSampler({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})", |
|
|
|
init.engine, init.filename, init.name, init.label, init.uniqueId); |
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
// Check if file exists |
|
|
|
|
|
|
|
if (! File(init.filename).existsAsFile()) |
|
|
|
{ |
|
|
|
init.engine->setLastError("Requested file is not valid or does not exist"); |
|
|
|
return nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
CarlaPluginSFZero* const plugin(new CarlaPluginSFZero(init.engine, init.id)); |
|
|
|
|
|
|
|
if (! plugin->init(init.filename, init.name, init.label, init.options)) |
|
|
|
{ |
|
|
|
delete plugin; |
|
|
|
return nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
return plugin; |
|
|
|
} |
|
|
|
|
|
|
|
CarlaPlugin* CarlaPlugin::newFileSFZ(const Initializer& init) |
|
|
|
{ |
|
|
|
return newSFZero(init); |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
CARLA_BACKEND_END_NAMESPACE |