@@ -192,10 +192,10 @@ private: | |||
@code | |||
const char* txt = "Text me up."; | |||
textBounds(vg, x,y, txt, NULL, bounds); | |||
beginPath(vg); | |||
roundedRect(vg, bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); | |||
fill(vg); | |||
vg.textBounds(x,y, txt, NULL, bounds); | |||
vg.beginPath(); | |||
vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); | |||
vg.fill(); | |||
@endcode | |||
Note: currently only solid color fill is supported for text. | |||
@@ -111,7 +111,7 @@ public: | |||
/** | |||
Mouse event. | |||
@a button The button number (1 = left, 2 = middle, 3 = right). | |||
@a press True if the key was pressed, false if released. | |||
@a press True if the button was pressed, false if released. | |||
@a pos The widget-relative coordinates of the pointer. | |||
@see onMouse | |||
*/ | |||
@@ -133,6 +133,29 @@ struct AudioPort { | |||
symbol() {} | |||
}; | |||
/** | |||
Parameter designation.@n | |||
Allows a parameter to be specially designated for a task, like bypass. | |||
Each designation is unique, there must be only one parameter that uses it.@n | |||
The use of designated parameters is completely optional. | |||
@note Designated parameters have strict ranges. | |||
@see ParameterRanges::adjustForDesignation() | |||
*/ | |||
enum ParameterDesignation { | |||
/** | |||
Null or unset designation. | |||
*/ | |||
kParameterDesignationNull = 0, | |||
/** | |||
Bypass designation.@n | |||
When on (> 0.5f), it means the plugin must run in a bypassed state. | |||
*/ | |||
kParameterDesignationBypass = 1 | |||
}; | |||
/** | |||
Parameter ranges.@n | |||
This is used to set the default, minimum and maximum values of a parameter. | |||
@@ -289,6 +312,11 @@ struct Parameter { | |||
*/ | |||
ParameterRanges ranges; | |||
/** | |||
Designation for this parameter. | |||
*/ | |||
ParameterDesignation designation; | |||
/** | |||
MIDI CC to use by default on this parameter.@n | |||
A value of 0 or 32 (bank change) is considered invalid.@n | |||
@@ -306,6 +334,7 @@ struct Parameter { | |||
symbol(), | |||
unit(), | |||
ranges(), | |||
designation(kParameterDesignationNull), | |||
midiCC(0) {} | |||
/** | |||
@@ -317,7 +346,32 @@ struct Parameter { | |||
symbol(s), | |||
unit(u), | |||
ranges(def, min, max), | |||
designation(kParameterDesignationNull), | |||
midiCC(0) {} | |||
/** | |||
Initialize a parameter for a specific designation. | |||
*/ | |||
void initDesignation(ParameterDesignation d) noexcept | |||
{ | |||
designation = d; | |||
switch (d) | |||
{ | |||
case kParameterDesignationNull: | |||
break; | |||
case kParameterDesignationBypass: | |||
hints = kParameterIsAutomable|kParameterIsBoolean|kParameterIsInteger; | |||
name = "Bypass"; | |||
symbol = "dpf_bypass"; | |||
unit = ""; | |||
midiCC = 0; | |||
ranges.def = 0.0f; | |||
ranges.min = 0.0f; | |||
ranges.max = 1.0f; | |||
break; | |||
} | |||
} | |||
}; | |||
/** | |||
@@ -541,7 +595,7 @@ public: | |||
Returns false when the host buffer is full, in which case do not call this again until the next run(). | |||
@note This function is not implemented yet!@n | |||
It's here so that developers can prepare MIDI plugins in advance.@n | |||
If you plan to use this, please report to DPF authos so it can be implemented. | |||
If you plan to use this, please report to DPF authors so it can be implemented. | |||
*/ | |||
bool writeMidiEvent(const MidiEvent& midiEvent) noexcept; | |||
#endif | |||
@@ -661,7 +715,7 @@ protected: | |||
/** | |||
Get the value of an internal state.@n | |||
The host may call this function from any non-realtime context.@n | |||
Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_PROGRAMS or DISTRHO_PLUGIN_WANT_FULL_STATE is enabled. | |||
Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_FULL_STATE is enabled. | |||
@note The use of this function breaks compatibility with the DSSI format. | |||
*/ | |||
virtual String getState(const char* key) const = 0; | |||
@@ -290,6 +290,13 @@ public: | |||
return fData->parameters[index].hints; | |||
} | |||
ParameterDesignation getParameterDesignation(const uint32_t index) const noexcept | |||
{ | |||
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, kParameterDesignationNull); | |||
return fData->parameters[index].designation; | |||
} | |||
bool isParameterOutput(const uint32_t index) const noexcept | |||
{ | |||
return (getParameterHints(index) & kParameterIsOutput); | |||
@@ -97,28 +97,27 @@ public: | |||
#endif | |||
fClient(client) | |||
{ | |||
#if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
char strBuf[0xff+1]; | |||
strBuf[0xff] = '\0'; | |||
#if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
# if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) | |||
{ | |||
std::snprintf(strBuf, 0xff, "in%i", i+1); | |||
fPortAudioIns[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); | |||
} | |||
#endif | |||
#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
# endif | |||
# if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
{ | |||
std::snprintf(strBuf, 0xff, "out%i", i+1); | |||
fPortAudioOuts[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); | |||
} | |||
# endif | |||
#endif | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
fPortMidiIn = jack_port_register(fClient, "midi-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); | |||
#endif | |||
fPortEventsIn = jack_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); | |||
#if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
if (fPlugin.getProgramCount() > 0) | |||
@@ -128,11 +127,18 @@ public: | |||
fUI.programLoaded(0); | |||
# endif | |||
} | |||
# if DISTRHO_PLUGIN_HAS_UI | |||
fProgramChanged = -1; | |||
# endif | |||
#endif | |||
if (const uint32_t count = fPlugin.getParameterCount()) | |||
{ | |||
fLastOutputValues = new float[count]; | |||
#if DISTRHO_PLUGIN_HAS_UI | |||
fParametersChanged = new bool[count]; | |||
std::memset(fParametersChanged, 0, sizeof(bool)*count); | |||
#endif | |||
for (uint32_t i=0; i < count; ++i) | |||
{ | |||
@@ -143,15 +149,18 @@ public: | |||
else | |||
{ | |||
fLastOutputValues[i] = 0.0f; | |||
# if DISTRHO_PLUGIN_HAS_UI | |||
#if DISTRHO_PLUGIN_HAS_UI | |||
fUI.parameterChanged(i, fPlugin.getParameterValue(i)); | |||
# endif | |||
#endif | |||
} | |||
} | |||
} | |||
else | |||
{ | |||
fLastOutputValues = nullptr; | |||
#if DISTRHO_PLUGIN_HAS_UI | |||
fParametersChanged = nullptr; | |||
#endif | |||
} | |||
jack_set_buffer_size_callback(fClient, jackBufferSizeCallback, this); | |||
@@ -192,10 +201,8 @@ public: | |||
if (fClient == nullptr) | |||
return; | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
jack_port_unregister(fClient, fPortMidiIn); | |||
fPortMidiIn = nullptr; | |||
#endif | |||
jack_port_unregister(fClient, fPortEventsIn); | |||
fPortEventsIn = nullptr; | |||
#if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) | |||
@@ -225,20 +232,31 @@ protected: | |||
if (gCloseSignalReceived) | |||
return fUI.quit(); | |||
float value; | |||
# if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
if (fProgramChanged >= 0) | |||
{ | |||
fUI.programLoaded(fProgramChanged); | |||
fProgramChanged = -1; | |||
} | |||
# endif | |||
for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) | |||
{ | |||
if (! fPlugin.isParameterOutput(i)) | |||
continue; | |||
value = fPlugin.getParameterValue(i); | |||
if (fPlugin.isParameterOutput(i)) | |||
{ | |||
const float value = fPlugin.getParameterValue(i); | |||
if (fLastOutputValues[i] == value) | |||
continue; | |||
if (d_isEqual(fLastOutputValues[i], value)) | |||
continue; | |||
fLastOutputValues[i] = value; | |||
fUI.parameterChanged(i, value); | |||
fLastOutputValues[i] = value; | |||
fUI.parameterChanged(i, value); | |||
} | |||
else if (fParametersChanged[i]) | |||
{ | |||
fParametersChanged[i] = false; | |||
fUI.parameterChanged(i, fPlugin.getParameterValue(i)); | |||
} | |||
} | |||
fUI.exec_idle(); | |||
@@ -310,14 +328,14 @@ protected: | |||
fPlugin.setTimePosition(fTimePosition); | |||
#endif | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
void* const midiBuf = jack_port_get_buffer(fPortMidiIn, nframes); | |||
void* const midiBuf = jack_port_get_buffer(fPortEventsIn, nframes); | |||
if (const uint32_t eventCount = jack_midi_get_event_count(midiBuf)) | |||
{ | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
uint32_t midiEventCount = 0; | |||
MidiEvent midiEvents[eventCount]; | |||
#endif | |||
jack_midi_event_t jevent; | |||
for (uint32_t i=0; i < eventCount; ++i) | |||
@@ -325,6 +343,47 @@ protected: | |||
if (jack_midi_event_get(&jevent, midiBuf, i) != 0) | |||
break; | |||
// Check if message is control change on channel 1 | |||
if (jevent.buffer[0] == 0xB0 && jevent.size == 3) | |||
{ | |||
const uint8_t control = jevent.buffer[1]; | |||
const uint8_t value = jevent.buffer[2]; | |||
/* NOTE: This is not optimal, we're iterating all parameters on every CC message. | |||
Since the JACK standalone is more of a test tool, this will do for now. */ | |||
for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j) | |||
{ | |||
if (fPlugin.isParameterOutput(j)) | |||
continue; | |||
if (fPlugin.getParameterMidiCC(j) != control) | |||
continue; | |||
const float scaled = static_cast<float>(value)/127.0f; | |||
const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled); | |||
fPlugin.setParameterValue(j, fvalue); | |||
#if DISTRHO_PLUGIN_HAS_UI | |||
fParametersChanged[j] = true; | |||
#endif | |||
break; | |||
} | |||
} | |||
#if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
// Check if message is program change on channel 1 | |||
else if (jevent.buffer[0] == 0xC0 && jevent.size == 2) | |||
{ | |||
const uint8_t program = jevent.buffer[1]; | |||
if (program < fPlugin.getProgramCount()) | |||
{ | |||
fPlugin.loadProgram(program); | |||
# if DISTRHO_PLUGIN_HAS_UI | |||
fProgramChanged = program; | |||
# endif | |||
} | |||
} | |||
#endif | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
MidiEvent& midiEvent(midiEvents[midiEventCount++]); | |||
midiEvent.frame = jevent.time; | |||
@@ -334,10 +393,14 @@ protected: | |||
midiEvent.dataExt = jevent.buffer; | |||
else | |||
std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size); | |||
#endif | |||
} | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount); | |||
#endif | |||
} | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
else | |||
{ | |||
fPlugin.run(audioIns, audioOuts, nframes, nullptr, 0); | |||
@@ -393,9 +456,7 @@ private: | |||
#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; | |||
#endif | |||
#if DISTRHO_PLUGIN_IS_SYNTH | |||
jack_port_t* fPortMidiIn; | |||
#endif | |||
jack_port_t* fPortEventsIn; | |||
#if DISTRHO_PLUGIN_WANT_TIMEPOS | |||
TimePosition fTimePosition; | |||
#endif | |||
@@ -403,6 +464,14 @@ private: | |||
// Temporary data | |||
float* fLastOutputValues; | |||
#if DISTRHO_PLUGIN_HAS_UI | |||
// Store DSP changes to send to UI | |||
bool* fParametersChanged; | |||
# if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
int fProgramChanged; | |||
# endif | |||
#endif | |||
// ------------------------------------------------------------------- | |||
// Callbacks | |||
@@ -512,6 +512,12 @@ public: | |||
if (fLastControlValues[i] != curValue && ! fPlugin.isParameterOutput(i)) | |||
{ | |||
fLastControlValues[i] = curValue; | |||
if (fPlugin.getParameterDesignation(i) == kParameterDesignationBypass) | |||
{ | |||
curValue = 1.0f - curValue; | |||
} | |||
fPlugin.setParameterValue(i, curValue); | |||
} | |||
} | |||
@@ -589,10 +595,14 @@ public: | |||
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI | |||
const uint32_t capacity = fPortEventsOut->atom.size; | |||
bool needsInit = true; | |||
uint32_t size, offset = 0; | |||
LV2_Atom_Event* aev; | |||
fPortEventsOut->atom.size = sizeof(LV2_Atom_Sequence_Body); | |||
fPortEventsOut->atom.type = fURIDs.atomSequence; | |||
fPortEventsOut->body.unit = 0; | |||
fPortEventsOut->body.pad = 0; | |||
// TODO - MIDI Output | |||
for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) | |||
@@ -617,15 +627,6 @@ public: | |||
if (sizeof(LV2_Atom_Event) + msgSize > capacity - offset) | |||
break; | |||
if (needsInit) | |||
{ | |||
fPortEventsOut->atom.size = 0; | |||
fPortEventsOut->atom.type = fURIDs.atomSequence; | |||
fPortEventsOut->body.unit = 0; | |||
fPortEventsOut->body.pad = 0; | |||
needsInit = false; | |||
} | |||
// reserve msg space | |||
char msgBuf[msgSize]; | |||
std::memset(msgBuf, 0, msgSize); | |||
@@ -377,10 +377,34 @@ void lv2_generate_ttl(const char* const basename) | |||
pluginString += " a lv2:InputPort, lv2:ControlPort ;\n"; | |||
pluginString += " lv2:index " + String(portIndex) + " ;\n"; | |||
pluginString += " lv2:name \"" + plugin.getParameterName(i) + "\" ;\n"; | |||
// symbol | |||
bool designated = false; | |||
// designation | |||
if (! plugin.isParameterOutput(i)) | |||
{ | |||
switch (plugin.getParameterDesignation(i)) | |||
{ | |||
case kParameterDesignationNull: | |||
break; | |||
case kParameterDesignationBypass: | |||
designated = true; | |||
pluginString += " lv2:name \"Enabled\" ;\n"; | |||
pluginString += " lv2:symbol \"lv2_enabled\" ;\n"; | |||
pluginString += " lv2:default 1 ;\n"; | |||
pluginString += " lv2:minimum 0 ;\n"; | |||
pluginString += " lv2:maximum 1 ;\n"; | |||
pluginString += " lv2:portProperty lv2:toggled , lv2:integer ;\n"; | |||
pluginString += " lv2:designation lv2:enabled ;\n"; | |||
break; | |||
} | |||
} | |||
// name and symbol | |||
if (! designated) | |||
{ | |||
pluginString += " lv2:name \"" + plugin.getParameterName(i) + "\" ;\n"; | |||
String symbol(plugin.getParameterSymbol(i)); | |||
if (symbol.isEmpty()) | |||
@@ -390,24 +414,28 @@ void lv2_generate_ttl(const char* const basename) | |||
} | |||
// ranges | |||
if (! designated) | |||
{ | |||
const ParameterRanges& ranges(plugin.getParameterRanges(i)); | |||
if (plugin.getParameterHints(i) & kParameterIsInteger) | |||
{ | |||
pluginString += " lv2:default " + String(int(plugin.getParameterValue(i))) + " ;\n"; | |||
if (! plugin.isParameterOutput(i)) | |||
pluginString += " lv2:default " + String(int(plugin.getParameterValue(i))) + " ;\n"; | |||
pluginString += " lv2:minimum " + String(int(ranges.min)) + " ;\n"; | |||
pluginString += " lv2:maximum " + String(int(ranges.max)) + " ;\n"; | |||
} | |||
else | |||
{ | |||
pluginString += " lv2:default " + String(plugin.getParameterValue(i)) + " ;\n"; | |||
if (! plugin.isParameterOutput(i)) | |||
pluginString += " lv2:default " + String(plugin.getParameterValue(i)) + " ;\n"; | |||
pluginString += " lv2:minimum " + String(ranges.min) + " ;\n"; | |||
pluginString += " lv2:maximum " + String(ranges.max) + " ;\n"; | |||
} | |||
} | |||
// unit | |||
if (! designated) | |||
{ | |||
const String& unit(plugin.getParameterUnit(i)); | |||
@@ -453,6 +481,7 @@ void lv2_generate_ttl(const char* const basename) | |||
} | |||
// hints | |||
if (! designated) | |||
{ | |||
const uint32_t hints(plugin.getParameterHints(i)); | |||
@@ -239,7 +239,7 @@ protected: | |||
const size_t msgSize(tmpStr.length()+1); | |||
// reserve atom space | |||
const size_t atomSize(lv2_atom_pad_size(sizeof(LV2_Atom) + msgSize)); | |||
const size_t atomSize(sizeof(LV2_Atom) + msgSize); | |||
char atomBuf[atomSize]; | |||
std::memset(atomBuf, 0, atomSize); | |||
@@ -135,6 +135,7 @@ DistrhoPluginNekobi::DistrhoPluginNekobi() | |||
fParams.decay = 75.0f; | |||
fParams.accent = 25.0f; | |||
fParams.volume = 75.0f; | |||
fParams.bypass = false; | |||
// Internal stuff | |||
fSynth.waveform = 0.0f; | |||
@@ -232,6 +233,9 @@ void DistrhoPluginNekobi::initParameter(uint32_t index, Parameter& parameter) | |||
parameter.ranges.min = 0.0f; | |||
parameter.ranges.max = 100.0f; | |||
break; | |||
case paramBypass: | |||
parameter.initDesignation(kParameterDesignationBypass); | |||
break; | |||
} | |||
} | |||
@@ -258,6 +262,8 @@ float DistrhoPluginNekobi::getParameterValue(uint32_t index) const | |||
return fParams.accent; | |||
case paramVolume: | |||
return fParams.volume; | |||
case paramBypass: | |||
return fParams.bypass ? 1.0f : 0.0f; | |||
} | |||
return 0.0f; | |||
@@ -307,6 +313,14 @@ void DistrhoPluginNekobi::setParameterValue(uint32_t index, float value) | |||
fSynth.volume = value/100.0f; | |||
DISTRHO_SAFE_ASSERT(fSynth.volume >= 0.0f && fSynth.volume <= 1.0f); | |||
break; | |||
case paramBypass: { | |||
const bool bypass = (value > 0.5f); | |||
if (fParams.bypass != bypass) | |||
{ | |||
fParams.bypass = bypass; | |||
nekobee_synth_all_voices_off(&fSynth); | |||
} | |||
} break; | |||
} | |||
} | |||
@@ -342,6 +356,10 @@ void DistrhoPluginNekobi::run(const float**, float** outputs, uint32_t frames, c | |||
return; | |||
} | |||
// ignore midi input if bypassed | |||
if (fParams.bypass) | |||
midiEventCount = 0; | |||
while (framesDone < frames) | |||
{ | |||
if (fSynth.nugget_remains == 0) | |||
@@ -42,6 +42,7 @@ public: | |||
paramDecay, | |||
paramAccent, | |||
paramVolume, | |||
paramBypass, | |||
paramCount | |||
}; | |||
@@ -79,7 +80,7 @@ protected: | |||
uint32_t getVersion() const noexcept override | |||
{ | |||
return d_version(1, 0, 0); | |||
return d_version(1, 1, 0); | |||
} | |||
int64_t getUniqueId() const noexcept override | |||
@@ -117,6 +118,7 @@ private: | |||
float decay; | |||
float accent; | |||
float volume; | |||
bool bypass; | |||
} fParams; | |||
nekobee_synth_t fSynth; | |||