@@ -180,6 +180,7 @@ public: | |||
float getValue() const noexcept; | |||
void setValue(float value, bool sendCallback = false) noexcept; | |||
void setDefault(float def) noexcept; | |||
void setStartPos(const Point<int>& startPos) noexcept; | |||
void setStartPos(int x, int y) noexcept; | |||
@@ -203,7 +204,9 @@ private: | |||
float fMaximum; | |||
float fStep; | |||
float fValue; | |||
float fValueDef; | |||
float fValueTmp; | |||
bool fUsingDefault; | |||
bool fDragging; | |||
bool fInverted; | |||
@@ -24,9 +24,11 @@ | |||
// ----------------------------------------------------------------------- | |||
// Forward class names | |||
#ifdef DISTRHO_DEFINES_H_INCLUDED | |||
START_NAMESPACE_DISTRHO | |||
class UI; | |||
END_NAMESPACE_DISTRHO | |||
#endif | |||
START_NAMESPACE_DGL | |||
@@ -175,6 +177,22 @@ public: | |||
oldSize(0, 0) {} | |||
}; | |||
/** | |||
Widget position changed event. | |||
@a pos The new absolute position of the widget. | |||
@a oldPos The previous absolute position of the widget. | |||
@see onPositionChanged | |||
*/ | |||
struct PositionChangedEvent { | |||
Point<int> pos; | |||
Point<int> oldPos; | |||
/** Constuctor */ | |||
PositionChangedEvent() noexcept | |||
: pos(0, 0), | |||
oldPos(0, 0) {} | |||
}; | |||
/** | |||
Constructor. | |||
*/ | |||
@@ -362,6 +380,11 @@ protected: | |||
*/ | |||
virtual void onResize(const ResizeEvent&); | |||
/** | |||
A function called when the widget's absolute position is changed. | |||
*/ | |||
virtual void onPositionChanged(const PositionChangedEvent&); | |||
private: | |||
struct PrivateData; | |||
PrivateData* const pData; | |||
@@ -373,7 +396,9 @@ private: | |||
friend class NanoWidget; | |||
friend class Window; | |||
friend class StandaloneWindow; | |||
#ifdef DISTRHO_DEFINES_H_INCLUDED | |||
friend class DISTRHO_NAMESPACE::UI; | |||
#endif | |||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Widget) | |||
}; | |||
@@ -19,9 +19,11 @@ | |||
#include "Geometry.hpp" | |||
#ifdef DISTRHO_DEFINES_H_INCLUDED | |||
START_NAMESPACE_DISTRHO | |||
class UIExporter; | |||
END_NAMESPACE_DISTRHO | |||
#endif | |||
START_NAMESPACE_DGL | |||
@@ -129,7 +131,9 @@ private: | |||
friend class Application; | |||
friend class Widget; | |||
friend class StandaloneWindow; | |||
#ifdef DISTRHO_DEFINES_H_INCLUDED | |||
friend class DISTRHO_NAMESPACE::UIExporter; | |||
#endif | |||
virtual void _addWidget(Widget* const widget); | |||
virtual void _removeWidget(Widget* const widget); | |||
@@ -649,7 +649,9 @@ ImageSlider::ImageSlider(Window& parent, const Image& image) noexcept | |||
fMaximum(1.0f), | |||
fStep(0.0f), | |||
fValue(0.5f), | |||
fValueDef(fValue), | |||
fValueTmp(fValue), | |||
fUsingDefault(false), | |||
fDragging(false), | |||
fInverted(false), | |||
fValueIsSet(false), | |||
@@ -670,7 +672,9 @@ ImageSlider::ImageSlider(Widget* widget, const Image& image) noexcept | |||
fMaximum(1.0f), | |||
fStep(0.0f), | |||
fValue(0.5f), | |||
fValueDef(fValue), | |||
fValueTmp(fValue), | |||
fUsingDefault(false), | |||
fDragging(false), | |||
fInverted(false), | |||
fValueIsSet(false), | |||
@@ -743,6 +747,12 @@ void ImageSlider::setInverted(bool inverted) noexcept | |||
repaint(); | |||
} | |||
void ImageSlider::setDefault(float value) noexcept | |||
{ | |||
fValueDef = value; | |||
fUsingDefault = true; | |||
} | |||
void ImageSlider::setRange(float min, float max) noexcept | |||
{ | |||
fMinimum = min; | |||
@@ -830,6 +840,13 @@ bool ImageSlider::onMouse(const MouseEvent& ev) | |||
if (! fSliderArea.contains(ev.pos)) | |||
return false; | |||
if ((ev.mod & kModifierShift) != 0 && fUsingDefault) | |||
{ | |||
setValue(fValueDef, true); | |||
fValueTmp = fValue; | |||
return true; | |||
} | |||
float vper; | |||
const int x = ev.pos.getX(); | |||
const int y = ev.pos.getY(); | |||
@@ -151,20 +151,12 @@ const Point<int>& Widget::getAbsolutePos() const noexcept | |||
void Widget::setAbsoluteX(int x) noexcept | |||
{ | |||
if (pData->absolutePos.getX() == x) | |||
return; | |||
pData->absolutePos.setX(x); | |||
pData->parent.repaint(); | |||
setAbsolutePos(Point<int>(x, getAbsoluteY())); | |||
} | |||
void Widget::setAbsoluteY(int y) noexcept | |||
{ | |||
if (pData->absolutePos.getY() == y) | |||
return; | |||
pData->absolutePos.setY(y); | |||
pData->parent.repaint(); | |||
setAbsolutePos(Point<int>(getAbsoluteX(), y)); | |||
} | |||
void Widget::setAbsolutePos(int x, int y) noexcept | |||
@@ -177,7 +169,13 @@ void Widget::setAbsolutePos(const Point<int>& pos) noexcept | |||
if (pData->absolutePos == pos) | |||
return; | |||
PositionChangedEvent ev; | |||
ev.oldPos = pData->absolutePos; | |||
ev.pos = pos; | |||
pData->absolutePos = pos; | |||
onPositionChanged(ev); | |||
pData->parent.repaint(); | |||
} | |||
@@ -245,6 +243,10 @@ void Widget::onResize(const ResizeEvent&) | |||
{ | |||
} | |||
void Widget::onPositionChanged(const PositionChangedEvent&) | |||
{ | |||
} | |||
// ----------------------------------------------------------------------- | |||
END_NAMESPACE_DGL |
@@ -32,6 +32,8 @@ | |||
#if defined(DISTRHO_OS_WINDOWS) | |||
# include "pugl/pugl_win.cpp" | |||
# undef max | |||
# undef min | |||
#elif defined(DISTRHO_OS_MAC) | |||
# define PuglWindow DISTRHO_JOIN_MACRO(PuglWindow, DGL_NAMESPACE) | |||
# define PuglOpenGLView DISTRHO_JOIN_MACRO(PuglOpenGLView, DGL_NAMESPACE) | |||
@@ -573,7 +575,7 @@ struct Window::PrivateData { | |||
#if defined(DISTRHO_OS_WINDOWS) | |||
const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (fResizable ? WS_SIZEBOX : 0x0); | |||
RECT wr = { 0, 0, static_cast<long>(width), static_cast<long>(height) }; | |||
RECT wr = { 0, 0, static_cast<LONG>(width), static_cast<LONG>(height) }; | |||
AdjustWindowRectEx(&wr, fUsingEmbed ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); | |||
SetWindowPos(hwnd, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top, | |||
@@ -111,7 +111,11 @@ puglCreateWindow(PuglView* view, const char* title) | |||
static int wc_count = 0; | |||
char classNameBuf[256]; | |||
std::srand((std::time(NULL))); | |||
#ifdef __WINE__ | |||
std::snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); | |||
#else | |||
_snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); | |||
#endif | |||
classNameBuf[sizeof(classNameBuf)-1] = '\0'; | |||
impl->wc.style = CS_OWNDC; | |||
@@ -26,13 +26,14 @@ | |||
START_NAMESPACE_DISTRHO | |||
#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
static const writeMidiFunc writeMidiCallback = nullptr; | |||
#endif | |||
#if DISTRHO_PLUGIN_HAS_UI | |||
// ----------------------------------------------------------------------- | |||
// Carla UI | |||
#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
static const writeMidiFunc writeMidiCallback = nullptr; | |||
#endif | |||
#if ! DISTRHO_PLUGIN_WANT_STATE | |||
static const setStateFunc setStateCallback = nullptr; | |||
#endif | |||
@@ -71,18 +71,21 @@ static const writeMidiFunc writeMidiCallback = nullptr; | |||
void strncpy(char* const dst, const char* const src, const size_t size) | |||
{ | |||
std::strncpy(dst, src, size-1); | |||
DISTRHO_SAFE_ASSERT_RETURN(size > 0,); | |||
std::memcpy(dst, src, std::min(std::strlen(src), size-1)); | |||
dst[size-1] = '\0'; | |||
} | |||
void snprintf_param(char* const dst, const float value, const size_t size) | |||
{ | |||
DISTRHO_SAFE_ASSERT_RETURN(size > 0,); | |||
std::snprintf(dst, size-1, "%f", value); | |||
dst[size-1] = '\0'; | |||
} | |||
void snprintf_iparam(char* const dst, const int32_t value, const size_t size) | |||
{ | |||
DISTRHO_SAFE_ASSERT_RETURN(size > 0,); | |||
std::snprintf(dst, size-1, "%d", value); | |||
dst[size-1] = '\0'; | |||
} | |||
@@ -23,12 +23,14 @@ START_NAMESPACE_DISTRHO | |||
DistrhoPluginKars::DistrhoPluginKars() | |||
: Plugin(paramCount, 0, 0), // 0 programs, 0 states | |||
fSustain(false), | |||
fRelease(0.01), | |||
fVolume(75.0f), | |||
fSampleRate(getSampleRate()), | |||
fBlockStart(0) | |||
{ | |||
for (int i=kMaxNotes; --i >= 0;) | |||
{ | |||
fNotes[i].index = i; | |||
fNotes[i].voice = i; | |||
fNotes[i].setSampleRate(fSampleRate); | |||
} | |||
} | |||
@@ -38,15 +40,35 @@ DistrhoPluginKars::DistrhoPluginKars() | |||
void DistrhoPluginKars::initParameter(uint32_t index, Parameter& parameter) | |||
{ | |||
if (index != 0) | |||
return; | |||
parameter.hints = kParameterIsAutomable|kParameterIsBoolean; | |||
parameter.name = "Sustain"; | |||
parameter.symbol = "sustain"; | |||
parameter.ranges.def = 0.0f; | |||
parameter.ranges.min = 0.0f; | |||
parameter.ranges.max = 1.0f; | |||
switch (index) | |||
{ | |||
case paramSustain: | |||
parameter.hints = kParameterIsAutomable|kParameterIsBoolean; | |||
parameter.name = "Sustain"; | |||
parameter.symbol = "sustain"; | |||
parameter.ranges.def = 0.0f; | |||
parameter.ranges.min = 0.0f; | |||
parameter.ranges.max = 1.0f; | |||
break; | |||
case paramRelease: | |||
parameter.hints = kParameterIsAutomable; | |||
parameter.name = "Release"; | |||
parameter.symbol = "release"; | |||
parameter.unit = "s"; | |||
parameter.ranges.def = 0.01f; | |||
parameter.ranges.min = 0.0f; | |||
parameter.ranges.max = 5.0f; | |||
break; | |||
case paramVolume: | |||
parameter.hints = kParameterIsAutomable; | |||
parameter.name = "Volume"; | |||
parameter.symbol = "volume"; | |||
parameter.unit = "%"; | |||
parameter.ranges.def = 75.0f; | |||
parameter.ranges.min = 0.0f; | |||
parameter.ranges.max = 100.0f; | |||
break; | |||
} | |||
} | |||
// ----------------------------------------------------------------------- | |||
@@ -54,18 +76,30 @@ void DistrhoPluginKars::initParameter(uint32_t index, Parameter& parameter) | |||
float DistrhoPluginKars::getParameterValue(uint32_t index) const | |||
{ | |||
if (index != 0) | |||
return 0.0f; | |||
switch (index) | |||
{ | |||
case paramSustain: return fSustain ? 1.0f : 0.0f; | |||
case paramRelease: return fRelease; | |||
case paramVolume: return fVolume; | |||
} | |||
return fSustain ? 1.0f : 0.0f; | |||
return 0.0f; | |||
} | |||
void DistrhoPluginKars::setParameterValue(uint32_t index, float value) | |||
{ | |||
if (index != 0) | |||
return; | |||
fSustain = value > 0.5f; | |||
switch (index) | |||
{ | |||
case paramSustain: | |||
fSustain = value > 0.5f; | |||
break; | |||
case paramRelease: | |||
fRelease = value; | |||
break; | |||
case paramVolume: | |||
fVolume = value; | |||
break; | |||
} | |||
} | |||
// ----------------------------------------------------------------------- | |||
@@ -83,19 +117,112 @@ void DistrhoPluginKars::activate() | |||
} | |||
} | |||
/** | |||
Handy class to help keep audio buffer in sync with incoming MIDI events. | |||
To use it, create a local variable (on the stack) and call nextEvent() until it returns false. | |||
@code | |||
for (AudioMidiSyncHelper amsh(outputs, frames, midiEvents, midiEventCount); amsh.nextEvent();) | |||
{ | |||
float* const outL = amsh.outputs[0]; | |||
float* const outR = amsh.outputs[1]; | |||
for (uint32_t i=0; i<amsh.midiEventCount; ++i) | |||
{ | |||
const MidiEvent& ev(amsh.midiEvents[i]); | |||
// ... do something with the midi event | |||
} | |||
renderSynth(outL, outR, amsh.frames); | |||
} | |||
@endcode | |||
Some important notes when using this class: | |||
1. MidiEvent::frame retains its original value, but it is useless, do not use it. | |||
2. The class variables names are be the same as the default ones in the run function. | |||
Keep that in mind and try to avoid typos. :) | |||
*/ | |||
class AudioMidiSyncHelper { | |||
public: | |||
/** Parameters from the run function, adjusted for event sync */ | |||
float** outputs; | |||
uint32_t frames; | |||
const MidiEvent* midiEvents; | |||
uint32_t midiEventCount; | |||
/** | |||
Constructor, using values from the run function. | |||
*/ | |||
AudioMidiSyncHelper(float** const o, uint32_t f, const MidiEvent* m, uint32_t mc) | |||
: outputs(o), | |||
frames(0), | |||
midiEvents(m), | |||
midiEventCount(0), | |||
remainingFrames(f), | |||
remainingMidiEventCount(mc) {} | |||
/** | |||
Process a batch of events untill no more are available. | |||
You must not read any more values from this class after this function returns false. | |||
*/ | |||
bool nextEvent() | |||
{ | |||
if (remainingMidiEventCount == 0) | |||
{ | |||
if (remainingFrames == 0) | |||
return false; | |||
for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
outputs[i] += frames; | |||
if (midiEventCount != 0) | |||
midiEvents += midiEventCount; | |||
frames = remainingFrames; | |||
midiEvents = nullptr; | |||
midiEventCount = 0; | |||
remainingFrames = 0; | |||
return true; | |||
} | |||
const uint32_t eventFrame = midiEvents[0].frame; | |||
DISTRHO_SAFE_ASSERT_RETURN(eventFrame < remainingFrames, false); | |||
midiEventCount = 1; | |||
for (uint32_t i=1; i<remainingMidiEventCount; ++i) | |||
{ | |||
if (midiEvents[i].frame != eventFrame) | |||
{ | |||
midiEventCount = i; | |||
break; | |||
} | |||
} | |||
frames = remainingFrames - eventFrame; | |||
remainingFrames -= frames; | |||
remainingMidiEventCount -= midiEventCount; | |||
return true; | |||
} | |||
private: | |||
/** @internal */ | |||
uint32_t remainingFrames; | |||
uint32_t remainingMidiEventCount; | |||
}; | |||
void DistrhoPluginKars::run(const float**, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount) | |||
{ | |||
uint8_t note, velo; | |||
float* out = outputs[0]; | |||
std::memset(outputs[0], 0, sizeof(float)*frames); | |||
for (uint32_t count, pos=0, curEventIndex=0; pos<frames;) | |||
for (AudioMidiSyncHelper amsh(outputs, frames, midiEvents, midiEventCount); amsh.nextEvent();) | |||
{ | |||
for (;curEventIndex < midiEventCount && pos >= midiEvents[curEventIndex].frame; ++curEventIndex) | |||
for (uint32_t i=0; i<amsh.midiEventCount; ++i) | |||
{ | |||
if (midiEvents[curEventIndex].size > MidiEvent::kDataSize) | |||
if (amsh.midiEvents[i].size > MidiEvent::kDataSize) | |||
continue; | |||
const uint8_t* data = midiEvents[curEventIndex].data; | |||
const uint8_t* data = amsh.midiEvents[i].data; | |||
const uint8_t status = data[0] & 0xF0; | |||
switch (status) | |||
@@ -106,7 +233,7 @@ void DistrhoPluginKars::run(const float**, float** outputs, uint32_t frames, con | |||
DISTRHO_SAFE_ASSERT_BREAK(note < 128); // kMaxNotes | |||
if (velo > 0) | |||
{ | |||
fNotes[note].on = fBlockStart + midiEvents[curEventIndex].frame; | |||
fNotes[note].on = fBlockStart + amsh.midiEvents[i].frame; | |||
fNotes[note].off = kNoteNull; | |||
fNotes[note].velocity = velo; | |||
break; | |||
@@ -115,35 +242,26 @@ void DistrhoPluginKars::run(const float**, float** outputs, uint32_t frames, con | |||
case 0x80: | |||
note = data[1]; | |||
DISTRHO_SAFE_ASSERT_BREAK(note < 128); // kMaxNotes | |||
fNotes[note].off = fBlockStart + midiEvents[curEventIndex].frame; | |||
fNotes[note].off = fBlockStart + amsh.midiEvents[i].frame; | |||
break; | |||
} | |||
} | |||
if (curEventIndex < midiEventCount && midiEvents[curEventIndex].frame < frames) | |||
count = midiEvents[curEventIndex].frame - pos; | |||
else | |||
count = frames - pos; | |||
std::memset(out+pos, 0, sizeof(float)*count); | |||
//for (uint32_t i=0; i<count; ++i) | |||
// out[pos + i] = 0.0f; | |||
float* const out = amsh.outputs[0]; | |||
for (int i=kMaxNotes; --i >= 0;) | |||
{ | |||
if (fNotes[i].on != kNoteNull) | |||
addSamples(out, i, pos, count); | |||
addSamples(out, i, amsh.frames); | |||
} | |||
pos += count; | |||
} | |||
fBlockStart += frames; | |||
} | |||
void DistrhoPluginKars::addSamples(float* out, int voice, uint32_t offset, uint32_t count) | |||
void DistrhoPluginKars::addSamples(float* out, int voice, uint32_t frames) | |||
{ | |||
const uint32_t start = fBlockStart + offset; | |||
const uint32_t start = fBlockStart; | |||
Note& note(fNotes[voice]); | |||
@@ -162,7 +280,7 @@ void DistrhoPluginKars::addSamples(float* out, int voice, uint32_t offset, uint3 | |||
float gain, sample; | |||
uint32_t index, size; | |||
for (uint32_t i=0, s=start-note.on; i<count; ++i, ++s) | |||
for (uint32_t i=0, s=start-note.on; i<frames; ++i, ++s) | |||
{ | |||
gain = vgain; | |||
@@ -170,8 +288,8 @@ void DistrhoPluginKars::addSamples(float* out, int voice, uint32_t offset, uint3 | |||
{ | |||
// reuse index and size to save some performance. | |||
// actual values are release and dist | |||
index = 1 + uint32_t(0.01 * fSampleRate); // release, not index | |||
size = i + start - note.off; // dist, not size | |||
index = 1 + uint32_t(fRelease * fSampleRate); // release, not index | |||
size = i + start - note.off; // dist, not size | |||
if (size > index) | |||
{ | |||
@@ -198,7 +316,7 @@ void DistrhoPluginKars::addSamples(float* out, int voice, uint32_t offset, uint3 | |||
note.wavetable[index] = sample/2; | |||
} | |||
out[offset+i] += gain * sample; | |||
out[i] += gain * sample * (fVolume / 100.0f); | |||
} | |||
} | |||
@@ -32,6 +32,8 @@ public: | |||
enum Parameters | |||
{ | |||
paramSustain = 0, | |||
paramRelease, | |||
paramVolume, | |||
paramCount | |||
}; | |||
@@ -97,6 +99,8 @@ protected: | |||
private: | |||
bool fSustain; | |||
float fRelease; | |||
float fVolume; | |||
double fSampleRate; | |||
uint32_t fBlockStart; | |||
@@ -104,16 +108,16 @@ private: | |||
uint32_t on; | |||
uint32_t off; | |||
uint8_t velocity; | |||
float index; | |||
float size; | |||
int sizei; | |||
float* wavetable; | |||
float voice; | |||
float size; | |||
int sizei; | |||
float* wavetable; | |||
Note() noexcept | |||
: on(kNoteNull), | |||
off(kNoteNull), | |||
velocity(0), | |||
index(0.0f), | |||
voice(0.0f), | |||
size(0.0f), | |||
wavetable(nullptr) {} | |||
@@ -131,7 +135,7 @@ private: | |||
if (wavetable != nullptr) | |||
delete[] wavetable; | |||
const float frequency = 440.0f * std::pow(2.0f, (index - 69.0f) / 12.0f); | |||
const float frequency = 440.0f * std::pow(2.0f, (voice - 69.0f) / 12.0f); | |||
size = sampleRate / frequency; | |||
sizei = int(size)+1; | |||
wavetable = new float[sizei]; | |||
@@ -140,7 +144,7 @@ private: | |||
} fNotes[kMaxNotes]; | |||
void addSamples(float* out, int voice, uint32_t offset, uint32_t count); | |||
void addSamples(float* out, int voice, uint32_t frames); | |||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DistrhoPluginKars) | |||
}; | |||
@@ -23,6 +23,12 @@ FILES_UI = \ | |||
include ../../dpf/Makefile.plugins.mk | |||
# -------------------------------------------------------------- | |||
# Extra flags | |||
BASE_FLAGS += $(shell pkg-config --cflags libprojectM) | |||
LINK_FLAGS += $(shell pkg-config --libs libprojectM) -lpthread | |||
# -------------------------------------------------------------- | |||
# Enable all possible plugin types | |||