Browse Source

Finish basic implementation of SVG support + MidiKeyboard example

pull/141/head
Patrick Desaulniers 6 years ago
parent
commit
23cc7da9a6
9 changed files with 546 additions and 74 deletions
  1. +3
    -3
      Makefile
  2. +10
    -7
      dgl/SVG.hpp
  3. +3
    -2
      dgl/src/Image.cpp
  4. +22
    -21
      dgl/src/SVG.cpp
  5. +1
    -2
      examples/MidiKeyboard/DistrhoPluginInfo.h
  6. +467
    -10
      examples/MidiKeyboard/KeyboardWidget.hpp
  7. +8
    -1
      examples/MidiKeyboard/Makefile
  8. +32
    -20
      examples/MidiKeyboard/MidiKeyboardExampleUI.cpp
  9. +0
    -8
      examples/MidiKeyboard/resources/MidiKeyboardResources.hpp

+ 3
- 3
Makefile View File

@@ -22,9 +22,9 @@ examples: dgl
$(MAKE) all -C examples/States
$(MAKE) all -C examples/MidiKeyboard

#ifeq ($(HAVE_CAIRO),true)
# $(MAKE) all -C examples/CairoUI
#endif
ifeq ($(HAVE_CAIRO),true)
$(MAKE) all -C examples/CairoUI
endif

ifneq ($(MACOS_OR_WINDOWS),true)
# ExternalUI is WIP


+ 10
- 7
dgl/SVG.hpp View File

@@ -36,16 +36,19 @@ public:
*/
SVG();

/**
Constructor using raw SVG data.
*/
SVG(const char* const data, const uint width, const uint height, const float scaling = 1.0f);

/**
Destructor.
*/
~SVG();
/**
Load SVG data from memory.
@note @a rawData must remain valid for the lifetime of this SVG.
*/
void loadFromMemory(const char* const rawData,
const uint dataSize,
const float scaling = 1.0f) noexcept;

/**
Check if this SVG is valid.
*/
@@ -63,9 +66,9 @@ public:

private:
Size<uint> fSize;
NSVGrasterizer* fRasterizer;
NSVGimage* fImage;
unsigned char* fRGBAData;

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SVG)
};

END_NAMESPACE_DGL


+ 3
- 2
dgl/src/Image.cpp View File

@@ -94,6 +94,8 @@ void Image::loadFromMemory(const char* const rawData,

void Image::loadFromSVG(const SVG& svg) noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(svg.isValid(),)

loadFromMemory((const char*)svg.getRGBAData(), svg.getSize(), GL_RGBA);
}

@@ -119,8 +121,7 @@ Image& Image::operator=(const Image& image) noexcept

void Image::_drawAt(const Point<int>& pos)
{
if (fTextureId == 0 || ! isValid())
return;
DISTRHO_SAFE_ASSERT_RETURN(fTextureId != 0 && isValid(), )

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, fTextureId);


+ 22
- 21
dgl/src/SVG.cpp View File

@@ -27,45 +27,46 @@ START_NAMESPACE_DGL

SVG::SVG()
: fSize(),
fRasterizer(nullptr),
fImage(nullptr),
fRGBAData(nullptr)
{

}

SVG::SVG(const char* const data, const uint width, const uint height, const float scaling)
: fSize(width * scaling, height * scaling)
void SVG::loadFromMemory(const char* const rawData, const uint dataSize, const float scaling) noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(data != nullptr,)
DISTRHO_SAFE_ASSERT_RETURN(fSize.isValid(),)

fRasterizer = nsvgCreateRasterizer();
DISTRHO_SAFE_ASSERT_RETURN(rawData != nullptr, )
DISTRHO_SAFE_ASSERT_RETURN(scaling > 0.0f, )

const size_t bufferSize = width * height * 4;
NSVGrasterizer* rasterizer = nsvgCreateRasterizer();

// nsvgParse modifies the input data, so we must use a temporary buffer
const size_t bufferSize = dataSize * 4;
char tmpBuffer[bufferSize];
strncpy(tmpBuffer, data, bufferSize);
strncpy(tmpBuffer, rawData, bufferSize);
const float dpi = 96 * scaling;
fImage = nsvgParse(tmpBuffer, "px", dpi);
const float dpi = 96;

NSVGimage* image = nsvgParse(tmpBuffer, "px", dpi);

DISTRHO_SAFE_ASSERT_RETURN(fImage != nullptr,)
DISTRHO_SAFE_ASSERT_RETURN(image != nullptr, )

const uint scaledWidth = width * scaling;
const uint scaledHeight = height * scaling;
const uint scaledWidth = image->width * scaling;
const uint scaledHeight = image->height * scaling;

fRGBAData = (unsigned char *)malloc(scaledWidth * scaledHeight * 4);
DISTRHO_SAFE_ASSERT_RETURN(scaledWidth > 0 && scaledHeight > 0, )

nsvgRasterize(fRasterizer, fImage, 0, 0, 1, fRGBAData, scaledWidth, scaledHeight, scaledWidth * 4);
fRGBAData = (unsigned char*)malloc(scaledWidth * scaledHeight * 4);

nsvgRasterize(rasterizer, image, 0, 0, scaling, fRGBAData, scaledWidth, scaledHeight, scaledWidth * 4);

fSize.setSize(scaledWidth, scaledHeight);

nsvgDelete(image);
nsvgDeleteRasterizer(rasterizer);
}

SVG::~SVG()
{
nsvgDelete(fImage);
nsvgDeleteRasterizer(fRasterizer);

free(fRGBAData);
}


+ 1
- 2
examples/MidiKeyboard/DistrhoPluginInfo.h View File

@@ -28,8 +28,7 @@
#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1
#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 1

#define DISTRHO_UI_USE_NANOVG 1
#define DISTRHO_UI_USER_RESIZABLE 1
#define DISTRHO_UI_USER_RESIZABLE 1

#define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:MIDIPlugin"



+ 467
- 10
examples/MidiKeyboard/KeyboardWidget.hpp View File

@@ -1,28 +1,485 @@
#include "Widget.hpp"

#include "Image.hpp"
#include "SVG.hpp"
#include "resources/MidiKeyboardResources.hpp"

START_NAMESPACE_DISTRHO

class PianoKey
{
public:
PianoKey()
: fPressed(false),
fImageNormal(),
fImageDown(),
fIndex(-1)
{

}

/**
Set the images that will be used when drawing the key.
*/
void setImages(const Image& imageNormal, const Image& imageDown)
{
fImageNormal = imageNormal;
fImageDown = imageDown;

fBoundingBox.setSize(imageNormal.getWidth(), imageNormal.getHeight());
}

/**
Set the state of the key.
*/
void setPressed(bool pressed) noexcept
{
fPressed = pressed;
}

/**
Indicate if the key is currently down.
*/
bool isPressed() noexcept
{
return fPressed;
}

/**
Set the note index of the key.
*/
void setIndex(const int index)
{
fIndex = index;
}

/**
Get the note index of the key.
*/
int getIndex()
{
return fIndex;
}

/**
Determine if a point intersects with the key's bounding box.
*/
bool contains(Point<int> point) noexcept
{
return fBoundingBox.contains(point);
}

/**
Set the position of the key, relative to its parent KeyboardWidget.
*/
void setPosition(const int x, const int y) noexcept
{
fBoundingBox.setPos(x, y);
}

/**
Get the width of the key.
*/
uint getWidth() noexcept
{
return fBoundingBox.getWidth();
}

/**
Draw the key at its bounding box's position.
*/
void draw()
{
Image* img;

if (isPressed())
{
img = &fImageDown;
}
else
{
img = &fImageNormal;
}

img->drawAt(fBoundingBox.getPos());
}

private:
Rectangle<int> fBoundingBox;
bool fPressed;
Image fImageNormal;
Image fImageDown;
int fIndex;
};

class KeyboardWidget : public Widget
{
public:
KeyboardWidget(Window& parent) : Widget(parent)
class Callback
{
public:
virtual ~Callback() {}
virtual void keyboardKeyPressed(const uint keyIndex) = 0;
virtual void keyboardKeyReleased(const uint keyIndex) = 0;
};

KeyboardWidget(Window& parent)
: Widget(parent),
fCallback(nullptr),
fMouseDown(false),
fHeldKey(nullptr)
{
fSVGs[kWhiteKeyResourceIndex].loadFromMemory(MidiKeyboardResources::white_keyData,
MidiKeyboardResources::white_keyDataSize,
1.0f);

fSVGs[kWhiteKeyPressedResourceIndex].loadFromMemory(MidiKeyboardResources::white_key_pressedData,
MidiKeyboardResources::white_key_pressedDataSize,
1.0f);

fSVGs[kBlackKeyResourceIndex].loadFromMemory(MidiKeyboardResources::black_keyData,
MidiKeyboardResources::black_keyDataSize,
1.0f);

fSVGs[kBlackKeyPressedResourceIndex].loadFromMemory(MidiKeyboardResources::black_key_pressedData,
MidiKeyboardResources::black_key_pressedDataSize,
1.0f);

for (int i = 0; i < kResourcesCount; ++i)
{
fImages[i].loadFromSVG(fSVGs[i]);
}

const int whiteKeysTotalSpacing = kWhiteKeySpacing * kWhiteKeysCount;

const uint width = fImages[kWhiteKeyResourceIndex].getWidth() * kWhiteKeysCount + whiteKeysTotalSpacing;
const uint height = fImages[kWhiteKeyResourceIndex].getHeight();

setSize(width, height);

setupKeyLookupTable();
setKeyImages();
positionKeys();
}

/**
Set the 'pressed' state of a key in the keyboard.
*/
void setKeyPressed(const uint keyIndex, const bool pressed, const bool sendCallback = false)
{
DISTRHO_SAFE_ASSERT_RETURN(keyIndex < kKeyCount, )

PianoKey& key = *fKeysLookup[keyIndex];

if (key.isPressed() == pressed)
return;

key.setPressed(pressed);

if (fCallback != nullptr && sendCallback)
{
if (pressed)
{
fCallback->keyboardKeyPressed(keyIndex);
}
else
{
fCallback->keyboardKeyReleased(keyIndex);
}
}

repaint();
}

void setCallback(Callback* callback)
{
fCallback = callback;
}

/**
Draw the piano keys.
*/
void onDisplay() override
{
const SVG &svg = SVG(MidiKeyboardResources::black_keyData,
MidiKeyboardResources::black_keyWidth,
MidiKeyboardResources::black_keyHeight,
1.0f);
// Draw the white keys.
for (int i = 0; i < kWhiteKeysCount; ++i)
{
fWhiteKeys[i].draw();
}

// Draw the black keys, on top of the white keys.
for (int i = 0; i < kBlackKeysCount; ++i)
{
fBlackKeys[i].draw();
}
}

/**
Get the key that is under the specified point.
Return nullptr if the point is not hovering any key.
*/
PianoKey* tryGetHoveredKey(const Point<int>& point)
{
// Since the black keys are on top of the white keys, we check for a mouse event on the black ones first
for (int i = 0; i < kBlackKeysCount; ++i)
{
if (fBlackKeys[i].contains(point))
{
return &fBlackKeys[i];
}
}

// Check for mouse event on white keys
for (int i = 0; i < kWhiteKeysCount; ++i)
{
if (fWhiteKeys[i].contains(point))
{
return &fWhiteKeys[i];
}
}

return nullptr;
}

/**
Handle mouse events.
*/
bool onMouse(const MouseEvent& ev) override
{
// We only care about left mouse button events.
if (ev.button != 1)
{
return false;
}

fMouseDown = ev.press;

if (!fMouseDown)
{
if (fHeldKey != nullptr)
{
setKeyPressed(fHeldKey->getIndex(), false, true);
fHeldKey = nullptr;

return true;
}
}

if (!contains(ev.pos))
{
return false;
}

PianoKey* key = tryGetHoveredKey(ev.pos);

if (key != nullptr)
{
setKeyPressed(key->getIndex(), ev.press, true);
fHeldKey = key;

return true;
}

return false;
}

/**
Handle mouse motion.
*/
bool onMotion(const MotionEvent& ev) override
{
if (!fMouseDown)
{
return false;
}

PianoKey* key = tryGetHoveredKey(ev.pos);

if (key != fHeldKey)
{
if (fHeldKey != nullptr)
{
setKeyPressed(fHeldKey->getIndex(), false, true);
}

if (key != nullptr)
{
setKeyPressed(key->getIndex(), true, true);
}

fHeldKey = key;

repaint();
}

return true;
}

private:
void setupKeyLookupTable()
{
int whiteKeysCounter = 0;
int blackKeysCounter = 0;

for (int i = 0; i < kKeyCount; ++i)
{
if (isBlackKey(i))
{
fKeysLookup[i] = &fBlackKeys[blackKeysCounter++];
}
else
{
fKeysLookup[i] = &fWhiteKeys[whiteKeysCounter++];
}
}
}

void setKeyImages()
{
for (int i = 0; i < kWhiteKeysCount; ++i)
{
fWhiteKeys[i].setImages(fImages[kWhiteKeyResourceIndex], fImages[kWhiteKeyPressedResourceIndex]);
}

for (int i = 0; i < kBlackKeysCount; ++i)
{
fBlackKeys[i].setImages(fImages[kBlackKeyResourceIndex], fImages[kBlackKeyPressedResourceIndex]);
}
}

/**
Put the keys at their proper position in the keyboard.
*/
void positionKeys()
{
const int whiteKeyWidth = fImages[kWhiteKeyResourceIndex].getWidth();
const int blackKeyWidth = fImages[kBlackKeyResourceIndex].getWidth();

int whiteKeysCounter = 0;
int blackKeysCounter = 0;
Image img = Image();
img.loadFromSVG(svg);
for (int i = 0; i < kKeyCount; ++i)
{
const int totalSpacing = kWhiteKeySpacing * whiteKeysCounter;

PianoKey* key;
int xPos;

if (isBlackKey(i))
{
key = &fBlackKeys[blackKeysCounter];
xPos = whiteKeysCounter * whiteKeyWidth + totalSpacing - blackKeyWidth / 2;

blackKeysCounter++;
}
else
{
key = &fWhiteKeys[whiteKeysCounter];
xPos = whiteKeysCounter * whiteKeyWidth + totalSpacing;

whiteKeysCounter++;
}

key->setPosition(xPos, 0);
key->setIndex(i);
}
}

/**
* Determine if a note at a certain index is associated with a white key.
*/
bool isWhiteKey(const uint noteIndex)
{
return !isBlackKey(noteIndex);
}

/**
* Determine if a note at a certain index is associated with a black key.
*/
bool isBlackKey(const uint noteIndex)
{
// Bring the index down to the first octave
const uint adjustedIndex = noteIndex % 12;

img.drawAt(0,0);
return adjustedIndex == 1
|| adjustedIndex == 3
|| adjustedIndex == 6
|| adjustedIndex == 8
|| adjustedIndex == 10;
}

/**
* Identifiers used for accessing the graphical resources of the widget.
*/
enum Resources
{
kWhiteKeyResourceIndex = 0,
kWhiteKeyPressedResourceIndex = 1,
kBlackKeyResourceIndex = 2,
kBlackKeyPressedResourceIndex = 3,
kResourcesCount
};

/**
The number of octaves displayed in the keyboard.
*/
static constexpr int kOctaves = 2;

/**
The number of white keys displayed in the keyboard.
*/
static constexpr int kWhiteKeysCount = 7 * kOctaves + 1;

/**
The spacing in pixels between the white keys.
*/
static constexpr int kWhiteKeySpacing = 1;

/**
The number of black keys in the keyboard.
*/
static constexpr int kBlackKeysCount = 5 * kOctaves;

/**
The number of keys in the keyboard.
*/
static constexpr int kKeyCount = kWhiteKeysCount + kBlackKeysCount;

/**
The keyboard's white keys.
*/
PianoKey fWhiteKeys[kWhiteKeysCount];

/**
The keyboard's black keys.
*/
PianoKey fBlackKeys[kBlackKeysCount];

/**
Zero-indexed lookup table that maps notes to piano keys.
In this example, 0 is equal to C4.
*/
PianoKey* fKeysLookup[kKeyCount];

/**
Graphical resources.
*/
SVG fSVGs[kResourcesCount];
Image fImages[kResourcesCount];

/**
The callback pointer, used for notifying the UI of piano key presses and releases.
*/
Callback* fCallback;

/**
Whether or not the left mouse button is currently pressed.
*/
bool fMouseDown;

/**
The piano key that is currently pressed with the mouse.
*/
PianoKey* fHeldKey;
};

END_NAMESPACE_DISTRHO
END_NAMESPACE_DISTRHO

+ 8
- 1
examples/MidiKeyboard/Makefile View File

@@ -33,11 +33,18 @@ BASE_FLAGS += -I../../dgl/src/nanovg
# --------------------------------------------------------------
# Enable all possible plugin types

ifeq ($(LINUX),true)
ifeq ($(HAVE_JACK),true)
ifeq ($(HAVE_OPENGL),true)
TARGETS += jack
endif
endif

ifeq ($(HAVE_OPENGL),true)
TARGETS += lv2_sep
else
TARGETS += lv2_dsp
endif

TARGETS += vst

all: $(TARGETS)


+ 32
- 20
examples/MidiKeyboard/MidiKeyboardExampleUI.cpp View File

@@ -20,21 +20,24 @@

START_NAMESPACE_DISTRHO

/**
We need the rectangle class from DGL.
*/
using DGL::Rectangle;

// -----------------------------------------------------------------------------------------------------------

class MidiKeyboardExampleUI : public UI
class MidiKeyboardExampleUI : public UI,
public KeyboardWidget::Callback
{
public:
/* constructor */
MidiKeyboardExampleUI()
: UI(512, 512)
: UI(800, 132),
fKeyboardWidget(getParentWindow())
{
//getParentWindow().focus();
const uint keyboardDeltaWidth = getWidth() - fKeyboardWidget.getWidth();

fKeyboardWidget.setAbsoluteX(keyboardDeltaWidth / 2);
fKeyboardWidget.setAbsoluteY(getHeight() - fKeyboardWidget.getHeight());
fKeyboardWidget.setCallback(this);

// Add a min-size constraint to the window, to make sure that it can't become too small
setGeometryConstraints(getWidth(), getHeight(), true, true);
}
@@ -46,8 +49,9 @@ class MidiKeyboardExampleUI : public UI
/**
A parameter has changed on the plugin side.
This is called by the host to inform the UI about parameter changes.
This plugin does not have any parameters, so we can leave this blank.
*/
void parameterChanged(uint32_t index, float value) override
void parameterChanged(uint32_t, float) override
{
}

@@ -56,33 +60,41 @@ class MidiKeyboardExampleUI : public UI

/**
The OpenGL drawing function.
Here, we set a custom background color.
*/
void onNanoDisplay() override
void onDisplay() override
{
const uint width = getWidth();
const uint height = getHeight();
glClearColor(17.f / 255.f,
17.f / 255.f,
17.f / 255.f,
17.f / 255.f);

//KeyboardRenderer().draw(getContext());
glClear(GL_COLOR_BUFFER_BIT);
}

/**
Mouse press event.
*/
bool onMouse(const MouseEvent &ev) override
Called when a note is pressed on the piano.
*/
void keyboardKeyPressed(const uint keyIndex) override
{
return false;
const uint C4 = 60;
sendNote(0, C4 + keyIndex, 127);
}

bool onKeyboard(const KeyboardEvent &ev) override
/**
Called when a note is released on the piano.
*/
void keyboardKeyReleased(const uint keyIndex) override
{
fprintf(stderr, "%u\n", ev.key);
repaint();
return false;
const uint C4 = 60;
sendNote(0, C4 + keyIndex, 0);
}

// -------------------------------------------------------------------------------------------------------

private:
KeyboardWidget fKeyboardWidget;

/**
Set our UI class as non-copyable and add a leak detector just in case.
*/


+ 0
- 8
examples/MidiKeyboard/resources/MidiKeyboardResources.hpp View File

@@ -7,23 +7,15 @@ namespace MidiKeyboardResources
{
extern const char* black_keyData;
const unsigned int black_keyDataSize = 72329;
const unsigned int black_keyWidth = 32;
const unsigned int black_keyHeight = 72;

extern const char* black_key_pressedData;
const unsigned int black_key_pressedDataSize = 72336;
const unsigned int black_key_pressedWidth = 32;
const unsigned int black_key_pressedHeight = 72;

extern const char* white_keyData;
const unsigned int white_keyDataSize = 72329;
const unsigned int white_keyWidth = 46;
const unsigned int white_keyHeight = 112;

extern const char* white_key_pressedData;
const unsigned int white_key_pressedDataSize = 72338;
const unsigned int white_key_pressedWidth = 46;
const unsigned int white_key_pressedHeight = 112;
}

#endif // BINARY_MIDIKEYBOARDRESOURCES_HPP


Loading…
Cancel
Save