Merge pull request #497 from jpcima/editor-improvement
Update the piano
This commit is contained in:
commit
cc9832507f
12 changed files with 410 additions and 239 deletions
|
|
@ -34,6 +34,8 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL
|
||||||
src/editor/EditorController.h
|
src/editor/EditorController.h
|
||||||
src/editor/GUIComponents.h
|
src/editor/GUIComponents.h
|
||||||
src/editor/GUIComponents.cpp
|
src/editor/GUIComponents.cpp
|
||||||
|
src/editor/GUIPiano.h
|
||||||
|
src/editor/GUIPiano.cpp
|
||||||
src/editor/NativeHelpers.h
|
src/editor/NativeHelpers.h
|
||||||
src/editor/NativeHelpers.cpp
|
src/editor/NativeHelpers.cpp
|
||||||
src/editor/layout/main.hpp
|
src/editor/layout/main.hpp
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
Fl_Box sfzFileLabel_ {
|
Fl_Box sfzFileLabel_ {
|
||||||
label {DefaultInstrument.sfz}
|
label {DefaultInstrument.sfz}
|
||||||
comment {tag=kTagLoadSfzFile} selected
|
comment {tag=kTagLoadSfzFile}
|
||||||
xywh {195 11 250 31} labelsize 20 align 20
|
xywh {195 11 250 31} labelsize 20 align 20
|
||||||
class ClickableLabel
|
class ClickableLabel
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Group {subPanels_[kPanelGeneral]} {
|
Fl_Group {subPanels_[kPanelGeneral]} {
|
||||||
xywh {5 110 791 285} hide
|
xywh {5 110 791 285}
|
||||||
class LogicalGroup
|
class LogicalGroup
|
||||||
} {
|
} {
|
||||||
Fl_Group {} {open
|
Fl_Group {} {open
|
||||||
|
|
@ -212,8 +212,8 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Group {subPanels_[kPanelSettings]} {
|
Fl_Group {subPanels_[kPanelSettings]} {open
|
||||||
xywh {5 109 790 286}
|
xywh {5 109 790 286} hide
|
||||||
class LogicalGroup
|
class LogicalGroup
|
||||||
} {
|
} {
|
||||||
Fl_Group {} {
|
Fl_Group {} {
|
||||||
|
|
@ -305,8 +305,8 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box piano_ {selected
|
||||||
xywh {5 400 790 70}
|
xywh {5 400 790 70} labelsize 12
|
||||||
class Piano
|
class Piano
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "EditorController.h"
|
#include "EditorController.h"
|
||||||
#include "EditIds.h"
|
#include "EditIds.h"
|
||||||
#include "GUIComponents.h"
|
#include "GUIComponents.h"
|
||||||
|
#include "GUIPiano.h"
|
||||||
#include "NativeHelpers.h"
|
#include "NativeHelpers.h"
|
||||||
#include <absl/strings/string_view.h>
|
#include <absl/strings/string_view.h>
|
||||||
#include <absl/strings/match.h>
|
#include <absl/strings/match.h>
|
||||||
|
|
@ -96,6 +97,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener {
|
||||||
|
|
||||||
SActionMenu* fileOperationsMenu_ = nullptr;
|
SActionMenu* fileOperationsMenu_ = nullptr;
|
||||||
|
|
||||||
|
SPiano* piano_ = nullptr;
|
||||||
|
|
||||||
void uiReceiveValue(EditId id, const EditValue& v) override;
|
void uiReceiveValue(EditId id, const EditValue& v) override;
|
||||||
|
|
||||||
void createFrameContents();
|
void createFrameContents();
|
||||||
|
|
@ -554,8 +557,10 @@ void Editor::Impl::createFrameContents()
|
||||||
auto createNextFileButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) {
|
auto createNextFileButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) {
|
||||||
return createGlyphButton(u8"\ue0da", bounds, tag, fontsize);
|
return createGlyphButton(u8"\ue0da", bounds, tag, fontsize);
|
||||||
};
|
};
|
||||||
auto createPiano = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
|
auto createPiano = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int fontsize) {
|
||||||
SPiano* piano = new SPiano(bounds);
|
SPiano* piano = new SPiano(bounds);
|
||||||
|
auto font = owned(new CFontDesc("Roboto", fontsize));
|
||||||
|
piano->setFont(font);
|
||||||
return piano;
|
return piano;
|
||||||
};
|
};
|
||||||
auto createChevronDropDown = [this, &theme](const CRect& bounds, int, const char*, CHoriTxtAlign, int fontsize) {
|
auto createChevronDropDown = [this, &theme](const CRect& bounds, int, const char*, CHoriTxtAlign, int fontsize) {
|
||||||
|
|
@ -693,6 +698,23 @@ void Editor::Impl::createFrameContents()
|
||||||
menu->addEntry("Edit file", kTagEditSfzFile);
|
menu->addEntry("Edit file", kTagEditSfzFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SPiano* piano = piano_) {
|
||||||
|
piano->onKeyPressed = [this](unsigned key, float vel) {
|
||||||
|
uint8_t msg[3];
|
||||||
|
msg[0] = 0x90;
|
||||||
|
msg[1] = static_cast<uint8_t>(key);
|
||||||
|
msg[2] = static_cast<uint8_t>(std::max(1, static_cast<int>(vel * 127)));
|
||||||
|
ctrl_->uiSendMIDI(msg, sizeof(msg));
|
||||||
|
};
|
||||||
|
piano->onKeyReleased = [this](unsigned key, float vel) {
|
||||||
|
uint8_t msg[3];
|
||||||
|
msg[0] = 0x80;
|
||||||
|
msg[1] = static_cast<uint8_t>(key);
|
||||||
|
msg[2] = static_cast<uint8_t>(vel * 127);
|
||||||
|
ctrl_->uiSendMIDI(msg, sizeof(msg));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
CViewContainer* panel;
|
CViewContainer* panel;
|
||||||
activePanel_ = 0;
|
activePanel_ = 0;
|
||||||
|
|
|
||||||
|
|
@ -154,201 +154,6 @@ bool SFileDropTarget::isFileDrop(IDataPackage* package)
|
||||||
package->getDataType(0) == IDataPackage::kFilePath;
|
package->getDataType(0) == IDataPackage::kFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
SPiano::SPiano(const CRect& bounds)
|
|
||||||
: CView(bounds), font_(kNormalFont)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPiano::setFont(CFontRef font)
|
|
||||||
{
|
|
||||||
font_ = font;
|
|
||||||
invalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPiano::clearKeyRanges()
|
|
||||||
{
|
|
||||||
keyInRange_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPiano::addKeyRange(int start, int end)
|
|
||||||
{
|
|
||||||
start = std::min(127, std::max(0, start));
|
|
||||||
end = std::min(127, std::max(0, end));
|
|
||||||
|
|
||||||
for (int x = start; x <= end; ++x)
|
|
||||||
keyInRange_.set(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
CCoord SPiano::getKeyWidth()
|
|
||||||
{
|
|
||||||
return 6.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCoord SPiano::getKeySwitchesHeight()
|
|
||||||
{
|
|
||||||
return 20.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCoord SPiano::getKeyRangesHeight()
|
|
||||||
{
|
|
||||||
return 11.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCoord SPiano::getKeysHeight() const
|
|
||||||
{
|
|
||||||
return getHeight() -
|
|
||||||
(getKeySwitchesHeight() + getKeyRangesHeight() + getOctavesHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
CCoord SPiano::getOctavesHeight() const
|
|
||||||
{
|
|
||||||
return font_->getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPiano::getZoneDimensions(
|
|
||||||
CRect* pKeySwitches,
|
|
||||||
CRect* pKeyboard,
|
|
||||||
CRect* pKeyRanges,
|
|
||||||
CRect* pOctaves)
|
|
||||||
{
|
|
||||||
CRect bounds = getViewSize();
|
|
||||||
|
|
||||||
CRect keySwitches(bounds);
|
|
||||||
keySwitches.setHeight(getKeySwitchesHeight());
|
|
||||||
|
|
||||||
CRect keyboard(bounds);
|
|
||||||
keyboard.top = keySwitches.bottom;
|
|
||||||
keyboard.setHeight(getKeysHeight());
|
|
||||||
|
|
||||||
CRect keyRanges(bounds);
|
|
||||||
keyRanges.top = keyboard.bottom;
|
|
||||||
keyRanges.setHeight(getKeyRangesHeight());
|
|
||||||
|
|
||||||
CRect octaves(bounds);
|
|
||||||
octaves.top = keyRanges.bottom;
|
|
||||||
octaves.setHeight(getOctavesHeight());
|
|
||||||
|
|
||||||
// apply some paddings
|
|
||||||
keySwitches.extend(-2.0, -2.0);
|
|
||||||
keyboard.extend(-2.0, -2.0);
|
|
||||||
keyRanges.extend(-2.0, -4.0);
|
|
||||||
octaves.extend(-2.0, -2.0);
|
|
||||||
|
|
||||||
// offsets for centered keyboard
|
|
||||||
CCoord keyWidth = getKeyWidth();
|
|
||||||
CCoord offset = std::round((keyboard.getWidth() - (128.0 * keyWidth)) * 0.5);
|
|
||||||
if (offset > 0) {
|
|
||||||
keySwitches.extend(-offset, 0.0);
|
|
||||||
keyboard.extend(-offset, 0.0);
|
|
||||||
keyRanges.extend(-offset, 0.0);
|
|
||||||
octaves.extend(-offset, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
if (pKeySwitches)
|
|
||||||
*pKeySwitches = keySwitches;
|
|
||||||
if (pKeyboard)
|
|
||||||
*pKeyboard = keyboard;
|
|
||||||
if (pKeyRanges)
|
|
||||||
*pKeyRanges = keyRanges;
|
|
||||||
if (pOctaves)
|
|
||||||
*pOctaves = octaves;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPiano::draw(CDrawContext* dc)
|
|
||||||
{
|
|
||||||
CRect bounds = getViewSize();
|
|
||||||
|
|
||||||
dc->setDrawMode(kAntiAliasing);
|
|
||||||
|
|
||||||
SharedPointer<CGraphicsPath> path;
|
|
||||||
|
|
||||||
path = owned(dc->createGraphicsPath());
|
|
||||||
path->addRoundRect(bounds, 5.0);
|
|
||||||
dc->setFillColor(CColor(0xca, 0xca, 0xca));
|
|
||||||
dc->drawGraphicsPath(path, CDrawContext::kPathFilled);
|
|
||||||
|
|
||||||
//
|
|
||||||
CRect rectKeySwitches;
|
|
||||||
CRect rectKeyboard;
|
|
||||||
CRect rectKeyRanges;
|
|
||||||
CRect rectOctaves;
|
|
||||||
getZoneDimensions(&rectKeySwitches, &rectKeyboard, &rectKeyRanges, &rectOctaves);
|
|
||||||
|
|
||||||
//
|
|
||||||
path = owned(dc->createGraphicsPath());
|
|
||||||
path->addRoundRect(rectKeyboard, 1.0);
|
|
||||||
dc->setFillColor(CColor(0xff, 0xff, 0xff));
|
|
||||||
dc->drawGraphicsPath(path, CDrawContext::kPathFilled);
|
|
||||||
|
|
||||||
CCoord keyWidth = getKeyWidth();
|
|
||||||
for (int key = 0; key < 128; ++key) {
|
|
||||||
CCoord keyX = rectKeyboard.left + key * keyWidth;
|
|
||||||
int key12 = key % 12;
|
|
||||||
if (key12 == 1 || key12 == 3 ||
|
|
||||||
key12 == 6 || key12 == 8 || key12 == 10)
|
|
||||||
{
|
|
||||||
CRect blackRect(keyX, rectKeyboard.top + 2, keyX + keyWidth, rectKeyboard.bottom - 2);
|
|
||||||
path = owned(dc->createGraphicsPath());
|
|
||||||
path->addRoundRect(blackRect, 1.0);
|
|
||||||
dc->setFillColor(CColor(0x02, 0x02, 0x02));
|
|
||||||
dc->drawGraphicsPath(path, CDrawContext::kPathFilled);
|
|
||||||
}
|
|
||||||
if (key != 0 && key12 == 0) {
|
|
||||||
dc->setLineWidth(1.5);
|
|
||||||
dc->setFrameColor(CColor(0x63, 0x63, 0x63));
|
|
||||||
dc->drawLine(CPoint(keyX, rectKeyboard.top), CPoint(keyX, rectKeyboard.bottom));
|
|
||||||
}
|
|
||||||
if (key12 == 5) {
|
|
||||||
CCoord pad = rectKeyboard.getHeight() * 0.4;
|
|
||||||
dc->setLineWidth(1.0);
|
|
||||||
dc->setFrameColor(CColor(0x63, 0x63, 0x63));
|
|
||||||
dc->drawLine(CPoint(keyX, rectKeyboard.top + pad), CPoint(keyX, rectKeyboard.bottom - pad));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
for (int rangeStart = 0; rangeStart < 128;)
|
|
||||||
{
|
|
||||||
if (!keyInRange_[rangeStart]) {
|
|
||||||
++rangeStart;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int rangeEnd = rangeStart;
|
|
||||||
while (rangeEnd + 1 < 128 && keyInRange_[rangeEnd + 1])
|
|
||||||
++rangeEnd;
|
|
||||||
|
|
||||||
CCoord rangeStartX = rectKeyRanges.left + rangeStart * keyWidth;
|
|
||||||
CCoord rangeEndX = rectKeyRanges.left + (rangeEnd + 1.0) * keyWidth;
|
|
||||||
CRect rectRange(rangeStartX, rectKeyRanges.top, rangeEndX, rectKeyRanges.bottom);
|
|
||||||
|
|
||||||
path = owned(dc->createGraphicsPath());
|
|
||||||
path->addRoundRect(rectRange, 2.0);
|
|
||||||
dc->setFillColor(CColor(0x0f, 0x0f, 0x0f));
|
|
||||||
dc->drawGraphicsPath(path, CDrawContext::kPathFilled);
|
|
||||||
|
|
||||||
rangeStart = rangeEnd + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
for (int key = 0; key < 128; ++key) {
|
|
||||||
CCoord keyX = rectOctaves.left + key * keyWidth;
|
|
||||||
int key12 = key % 12;
|
|
||||||
if (key12 == 0) {
|
|
||||||
CRect textRect(keyX, rectOctaves.top, keyX + 12 * keyWidth, rectOctaves.bottom);
|
|
||||||
dc->setFont(font_);
|
|
||||||
dc->setFontColor(CColor(0x63, 0x63, 0x63));
|
|
||||||
dc->drawString(std::to_string(key / 12 - 1).c_str(), textRect, kLeftText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
SValueMenu::SValueMenu(const CRect& bounds, IControlListener* listener, int32_t tag)
|
SValueMenu::SValueMenu(const CRect& bounds, IControlListener* listener, int32_t tag)
|
||||||
: CParamDisplay(bounds), menuListener_(owned(new MenuListener(*this)))
|
: CParamDisplay(bounds), menuListener_(owned(new MenuListener(*this)))
|
||||||
|
|
|
||||||
|
|
@ -82,36 +82,6 @@ private:
|
||||||
FileDropFunction dropFunction_;
|
FileDropFunction dropFunction_;
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
|
||||||
class SPiano : public CView {
|
|
||||||
public:
|
|
||||||
explicit SPiano(const CRect& bounds);
|
|
||||||
CFontRef getFont() const { return font_; }
|
|
||||||
void setFont(CFontRef font);
|
|
||||||
|
|
||||||
void clearKeyRanges();
|
|
||||||
void addKeyRange(int start, int end);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static CCoord getKeyWidth();
|
|
||||||
static CCoord getKeySwitchesHeight();
|
|
||||||
static CCoord getKeyRangesHeight();
|
|
||||||
CCoord getKeysHeight() const;
|
|
||||||
CCoord getOctavesHeight() const;
|
|
||||||
|
|
||||||
void getZoneDimensions(
|
|
||||||
CRect* pKeySwitches,
|
|
||||||
CRect* pKeyboard,
|
|
||||||
CRect* pKeyRanges,
|
|
||||||
CRect* pOctaves);
|
|
||||||
|
|
||||||
void draw(CDrawContext* dc) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
SharedPointer<CFontDesc> font_;
|
|
||||||
std::bitset<128> keyInRange_;
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
///
|
||||||
class SValueMenu : public CParamDisplay {
|
class SValueMenu : public CParamDisplay {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
238
editor/src/editor/GUIPiano.cpp
Normal file
238
editor/src/editor/GUIPiano.cpp
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||||
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
|
#include "GUIPiano.h"
|
||||||
|
#include "utility/vstgui_before.h"
|
||||||
|
#include "vstgui/lib/cdrawcontext.h"
|
||||||
|
#include "vstgui/lib/cgraphicspath.h"
|
||||||
|
#include "utility/vstgui_after.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
static constexpr CCoord keyoffs[12] = {0, 0.6, 1, 1.8, 2, 3,
|
||||||
|
3.55, 4, 4.7, 5, 5.85, 6};
|
||||||
|
static constexpr bool black[12] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0};
|
||||||
|
|
||||||
|
SPiano::SPiano(CRect bounds)
|
||||||
|
: CView(bounds)
|
||||||
|
{
|
||||||
|
setNumOctaves(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPiano::setFont(CFontRef font)
|
||||||
|
{
|
||||||
|
font_ = font;
|
||||||
|
getDimensions(true);
|
||||||
|
invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPiano::setNumOctaves(unsigned octs)
|
||||||
|
{
|
||||||
|
keyval_.resize(octs * 12);
|
||||||
|
octs_ = std::max(1u, octs);
|
||||||
|
getDimensions(true);
|
||||||
|
invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPiano::draw(CDrawContext* dc)
|
||||||
|
{
|
||||||
|
const Dimensions dim = getDimensions(false);
|
||||||
|
const unsigned octs = octs_;
|
||||||
|
const unsigned keyCount = octs * 12;
|
||||||
|
|
||||||
|
dc->setDrawMode(kAntiAliasing);
|
||||||
|
|
||||||
|
if (backgroundFill_.alpha > 0) {
|
||||||
|
SharedPointer<CGraphicsPath> path;
|
||||||
|
path = owned(dc->createGraphicsPath());
|
||||||
|
path->addRoundRect(dim.bounds, backgroundRadius_);
|
||||||
|
dc->setFillColor(CColor(0xca, 0xca, 0xca));
|
||||||
|
dc->drawGraphicsPath(path, CDrawContext::kPathFilled);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned key = 0; key < keyCount; ++key) {
|
||||||
|
if (!black[key % 12]) {
|
||||||
|
CRect rect = keyRect(key);
|
||||||
|
CColor keycolor = whiteFill_;
|
||||||
|
if (keyval_[key])
|
||||||
|
keycolor = pressedFill_;
|
||||||
|
dc->setFillColor(keycolor);
|
||||||
|
dc->drawRect(rect, kDrawFilled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dc->setFrameColor(outline_);
|
||||||
|
dc->drawLine(dim.keyBounds.getTopLeft(), dim.keyBounds.getBottomLeft());
|
||||||
|
for (unsigned key = 0; key < keyCount; ++key) {
|
||||||
|
if (!black[key % 12]) {
|
||||||
|
CRect rect = keyRect(key);
|
||||||
|
dc->drawLine(rect.getTopRight(), rect.getBottomRight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned key = 0; key < keyCount; ++key) {
|
||||||
|
if (black[key % 12]) {
|
||||||
|
CRect rect = keyRect(key);
|
||||||
|
CColor keycolor = blackFill_;
|
||||||
|
if (keyval_[key])
|
||||||
|
keycolor = pressedFill_;
|
||||||
|
dc->setFillColor(keycolor);
|
||||||
|
dc->drawRect(rect, kDrawFilled);
|
||||||
|
dc->setFrameColor(outline_);
|
||||||
|
dc->drawRect(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const CFontRef& font = font_) {
|
||||||
|
for (unsigned o = 0; o < octs; ++o) {
|
||||||
|
CRect rect = keyRect(o * 12);
|
||||||
|
CRect textRect(
|
||||||
|
rect.left, dim.labelBounds.top,
|
||||||
|
rect.right, dim.labelBounds.bottom);
|
||||||
|
dc->setFont(font_);
|
||||||
|
dc->setFontColor(labelStroke_);
|
||||||
|
std::string text = std::to_string(static_cast<int>(o) - 1);
|
||||||
|
dc->drawString(text.c_str(), textRect, kCenterText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dc->setFrameColor(outline_);
|
||||||
|
dc->drawLine(dim.keyBounds.getTopLeft(), dim.keyBounds.getTopRight());
|
||||||
|
dc->setFrameColor(shadeOutline_);
|
||||||
|
dc->drawLine(dim.keyBounds.getBottomLeft(), dim.keyBounds.getBottomRight());
|
||||||
|
}
|
||||||
|
|
||||||
|
dc->setFrameColor(outline_);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMouseEventResult SPiano::onMouseDown(CPoint& where, const CButtonState& buttons)
|
||||||
|
{
|
||||||
|
unsigned key = keyAtPos(where);
|
||||||
|
if (key != ~0u) {
|
||||||
|
keyval_[key] = 1;
|
||||||
|
mousePressedKey_ = key;
|
||||||
|
if (onKeyPressed)
|
||||||
|
onKeyPressed(key, mousePressVelocity(key, where.y));
|
||||||
|
invalid();
|
||||||
|
return kMouseEventHandled;
|
||||||
|
}
|
||||||
|
return CView::onMouseDown(where, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMouseEventResult SPiano::onMouseUp(CPoint& where, const CButtonState& buttons)
|
||||||
|
{
|
||||||
|
unsigned key = mousePressedKey_;
|
||||||
|
if (key != ~0u) {
|
||||||
|
keyval_[key] = 0;
|
||||||
|
if (onKeyReleased)
|
||||||
|
onKeyReleased(key, mousePressVelocity(key, where.y));
|
||||||
|
mousePressedKey_ = ~0u;
|
||||||
|
invalid();
|
||||||
|
return kMouseEventHandled;
|
||||||
|
}
|
||||||
|
return CView::onMouseUp(where, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMouseEventResult SPiano::onMouseMoved(CPoint& where, const CButtonState& buttons)
|
||||||
|
{
|
||||||
|
if (mousePressedKey_ != ~0u) {
|
||||||
|
unsigned key = keyAtPos(where);
|
||||||
|
if (mousePressedKey_ != key) {
|
||||||
|
keyval_[mousePressedKey_] = 0;
|
||||||
|
if (onKeyReleased)
|
||||||
|
onKeyReleased(mousePressedKey_, mousePressVelocity(key, where.y));
|
||||||
|
// mousePressedKey_ = ~0u;
|
||||||
|
if (key != ~0u) {
|
||||||
|
keyval_[key] = 1;
|
||||||
|
mousePressedKey_ = key;
|
||||||
|
if (onKeyPressed)
|
||||||
|
onKeyPressed(key, mousePressVelocity(key, where.y));
|
||||||
|
}
|
||||||
|
invalid();
|
||||||
|
}
|
||||||
|
return kMouseEventHandled;
|
||||||
|
}
|
||||||
|
return CView::onMouseMoved(where, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SPiano::Dimensions& SPiano::getDimensions(bool forceUpdate) const
|
||||||
|
{
|
||||||
|
if (!forceUpdate && dim_.bounds == getViewSize())
|
||||||
|
return dim_;
|
||||||
|
|
||||||
|
Dimensions dim;
|
||||||
|
dim.bounds = getViewSize();
|
||||||
|
dim.paddedBounds = CRect(dim.bounds)
|
||||||
|
.extend(-2 * innerPaddingX_, -2 * innerPaddingY_);
|
||||||
|
CCoord keyHeight = std::floor(dim.paddedBounds.getHeight());
|
||||||
|
CCoord fontHeight = font_ ? font_->getSize() : 0.0;
|
||||||
|
keyHeight -= spacingY_ + fontHeight;
|
||||||
|
dim.keyBounds = CRect(dim.paddedBounds)
|
||||||
|
.setHeight(keyHeight);
|
||||||
|
dim.keyWidth = static_cast<unsigned>(
|
||||||
|
dim.paddedBounds.getWidth() / octs_ / 7.0);
|
||||||
|
dim.keyBounds.setWidth(dim.keyWidth * octs_ * 7.0);
|
||||||
|
dim.keyBounds.offset(
|
||||||
|
std::floor(0.5 * (dim.paddedBounds.getWidth() - dim.keyBounds.getWidth())), 0.0);
|
||||||
|
|
||||||
|
if (!font_)
|
||||||
|
dim.labelBounds = CRect();
|
||||||
|
else
|
||||||
|
dim.labelBounds = CRect(
|
||||||
|
dim.keyBounds.left, dim.keyBounds.bottom + spacingY_,
|
||||||
|
dim.keyBounds.right, dim.keyBounds.bottom + spacingY_ + fontHeight);
|
||||||
|
|
||||||
|
dim_ = dim;
|
||||||
|
return dim_;
|
||||||
|
}
|
||||||
|
|
||||||
|
CRect SPiano::keyRect(const Dimensions& dim, unsigned key)
|
||||||
|
{
|
||||||
|
unsigned oct = key / 12;
|
||||||
|
unsigned note = key % 12;
|
||||||
|
unsigned keyw = dim.keyWidth;
|
||||||
|
unsigned keyh = static_cast<unsigned>(dim.keyBounds.getHeight());
|
||||||
|
CCoord octwidth = (keyoffs[11] + 1.0) * keyw;
|
||||||
|
CCoord octx = octwidth * oct;
|
||||||
|
CCoord notex = octx + keyoffs[note] * keyw;
|
||||||
|
CCoord notew = black[note] ? (0.6 * keyw) : keyw;
|
||||||
|
CCoord noteh = black[note] ? (0.6 * keyh) : keyh;
|
||||||
|
return CRect(notex, 0.0, notex + notew, noteh).offset(dim.keyBounds.getTopLeft());
|
||||||
|
}
|
||||||
|
|
||||||
|
CRect SPiano::keyRect(unsigned key) const
|
||||||
|
{
|
||||||
|
return keyRect(getDimensions(false), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned SPiano::keyAtPos(CPoint pos) const
|
||||||
|
{
|
||||||
|
const unsigned octs = octs_;
|
||||||
|
|
||||||
|
for (unsigned key = 0; key < octs * 12; ++key) {
|
||||||
|
if (black[key % 12]) {
|
||||||
|
if (keyRect(key).pointInside(pos))
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned key = 0; key < octs * 12; ++key) {
|
||||||
|
if (!black[key % 12]) {
|
||||||
|
if (keyRect(key).pointInside(pos))
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ~0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SPiano::mousePressVelocity(unsigned key, CCoord posY)
|
||||||
|
{
|
||||||
|
const CRect rect = keyRect(key);
|
||||||
|
CCoord value = (posY - rect.top) / rect.getHeight();
|
||||||
|
return std::max(0.0f, std::min(1.0f, static_cast<float>(value)));
|
||||||
|
}
|
||||||
72
editor/src/editor/GUIPiano.h
Normal file
72
editor/src/editor/GUIPiano.h
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||||
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "utility/vstgui_before.h"
|
||||||
|
#include "vstgui/lib/cview.h"
|
||||||
|
#include "vstgui/lib/ccolor.h"
|
||||||
|
#include "utility/vstgui_after.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace VSTGUI;
|
||||||
|
|
||||||
|
class SPiano : public CView {
|
||||||
|
public:
|
||||||
|
explicit SPiano(CRect bounds);
|
||||||
|
|
||||||
|
CFontRef getFont() const { return font_; }
|
||||||
|
void setFont(CFontRef font);
|
||||||
|
|
||||||
|
unsigned getNumOctaves() const { return octs_; }
|
||||||
|
void setNumOctaves(unsigned octs);
|
||||||
|
|
||||||
|
std::function<void(unsigned, float)> onKeyPressed;
|
||||||
|
std::function<void(unsigned, float)> onKeyReleased;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void draw(CDrawContext* dc) override;
|
||||||
|
CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons) override;
|
||||||
|
CMouseEventResult onMouseUp(CPoint& where, const CButtonState& buttons) override;
|
||||||
|
CMouseEventResult onMouseMoved(CPoint& where, const CButtonState& buttons) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Dimensions {
|
||||||
|
CRect bounds {};
|
||||||
|
CRect paddedBounds {};
|
||||||
|
CRect keyBounds {};
|
||||||
|
unsigned keyWidth {};
|
||||||
|
CRect labelBounds {};
|
||||||
|
};
|
||||||
|
const Dimensions& getDimensions(bool forceUpdate) const;
|
||||||
|
|
||||||
|
static CRect keyRect(const Dimensions& dim, unsigned key);
|
||||||
|
CRect keyRect(unsigned key) const;
|
||||||
|
unsigned keyAtPos(CPoint pos) const;
|
||||||
|
|
||||||
|
float mousePressVelocity(unsigned key, CCoord posY);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned octs_ {};
|
||||||
|
std::vector<unsigned> keyval_;
|
||||||
|
unsigned mousePressedKey_ = ~0u;
|
||||||
|
|
||||||
|
CCoord innerPaddingX_ = 4.0;
|
||||||
|
CCoord innerPaddingY_ = 4.0;
|
||||||
|
CCoord spacingY_ = 4.0;
|
||||||
|
|
||||||
|
CColor backgroundFill_ { 0xca, 0xca, 0xca, 0xff };
|
||||||
|
float backgroundRadius_ = 5.0;
|
||||||
|
CColor whiteFill_ { 0xee, 0xee, 0xec, 0xff };
|
||||||
|
CColor blackFill_ { 0x2e, 0x34, 0x36, 0xff };
|
||||||
|
CColor pressedFill_ { 0xa0, 0xa0, 0xa0, 0xff };
|
||||||
|
CColor outline_ { 0x00, 0x00, 0x00, 0xff };
|
||||||
|
CColor shadeOutline_ { 0x80, 0x80, 0x80, 0xff };
|
||||||
|
CColor labelStroke_ { 0x63, 0x63, 0x63, 0xff };
|
||||||
|
|
||||||
|
mutable Dimensions dim_;
|
||||||
|
SharedPointer<CFontDesc> font_;
|
||||||
|
};
|
||||||
|
|
@ -69,7 +69,6 @@ enterTheme(defaultTheme);
|
||||||
LogicalGroup* const view__28 = createLogicalGroup(CRect(5, 110, 796, 395), -1, "", kCenterText, 14);
|
LogicalGroup* const view__28 = createLogicalGroup(CRect(5, 110, 796, 395), -1, "", kCenterText, 14);
|
||||||
subPanels_[kPanelGeneral] = view__28;
|
subPanels_[kPanelGeneral] = view__28;
|
||||||
view__0->addView(view__28);
|
view__0->addView(view__28);
|
||||||
view__28->setVisible(false);
|
|
||||||
RoundedGroup* const view__29 = createRoundedGroup(CRect(0, 0, 175, 280), -1, "", kCenterText, 14);
|
RoundedGroup* const view__29 = createRoundedGroup(CRect(0, 0, 175, 280), -1, "", kCenterText, 14);
|
||||||
view__28->addView(view__29);
|
view__28->addView(view__29);
|
||||||
Label* const view__30 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14);
|
Label* const view__30 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14);
|
||||||
|
|
@ -108,6 +107,7 @@ view__41->addView(view__42);
|
||||||
LogicalGroup* const view__43 = createLogicalGroup(CRect(5, 109, 795, 395), -1, "", kCenterText, 14);
|
LogicalGroup* const view__43 = createLogicalGroup(CRect(5, 109, 795, 395), -1, "", kCenterText, 14);
|
||||||
subPanels_[kPanelSettings] = view__43;
|
subPanels_[kPanelSettings] = view__43;
|
||||||
view__0->addView(view__43);
|
view__0->addView(view__43);
|
||||||
|
view__43->setVisible(false);
|
||||||
TitleGroup* const view__44 = createTitleGroup(CRect(255, 26, 535, 126), -1, "Engine", kCenterText, 12);
|
TitleGroup* const view__44 = createTitleGroup(CRect(255, 26, 535, 126), -1, "Engine", kCenterText, 12);
|
||||||
view__43->addView(view__44);
|
view__43->addView(view__44);
|
||||||
ValueMenu* const view__45 = createValueMenu(CRect(25, 60, 85, 85), kTagSetNumVoices, "", kCenterText, 12);
|
ValueMenu* const view__45 = createValueMenu(CRect(25, 60, 85, 85), kTagSetNumVoices, "", kCenterText, 12);
|
||||||
|
|
@ -150,5 +150,6 @@ view__51->addView(view__59);
|
||||||
ValueMenu* const view__60 = createValueMenu(CRect(170, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12);
|
ValueMenu* const view__60 = createValueMenu(CRect(170, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12);
|
||||||
scalaRootOctaveSlider_ = view__60;
|
scalaRootOctaveSlider_ = view__60;
|
||||||
view__51->addView(view__60);
|
view__51->addView(view__60);
|
||||||
Piano* const view__61 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 14);
|
Piano* const view__61 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12);
|
||||||
|
piano_ = view__61;
|
||||||
view__0->addView(view__61);
|
view__0->addView(view__61);
|
||||||
|
|
|
||||||
|
|
@ -176,10 +176,20 @@ void SfizzVstEditor::uiEndSend(EditId id)
|
||||||
getController()->endEdit(pid);
|
getController()->endEdit(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SfizzVstEditor::uiSendMIDI(const uint8_t* msg, uint32_t len)
|
void SfizzVstEditor::uiSendMIDI(const uint8_t* data, uint32_t len)
|
||||||
{
|
{
|
||||||
// TODO send MIDI...
|
SfizzVstController* ctl = getController();
|
||||||
|
|
||||||
|
Steinberg::OPtr<Vst::IMessage> msg { ctl->allocateMessage() };
|
||||||
|
if (!msg) {
|
||||||
|
fprintf(stderr, "[Sfizz] UI could not allocate message\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->setMessageID("MidiMessage");
|
||||||
|
Vst::IAttributeList* attr = msg->getAttributes();
|
||||||
|
attr->setBinary("Data", data, len);
|
||||||
|
ctl->sendMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ protected:
|
||||||
void uiSendValue(EditId id, const EditValue& v) override;
|
void uiSendValue(EditId id, const EditValue& v) override;
|
||||||
void uiBeginSend(EditId id) override;
|
void uiBeginSend(EditId id) override;
|
||||||
void uiEndSend(EditId id) override;
|
void uiEndSend(EditId id) override;
|
||||||
void uiSendMIDI(const uint8_t* msg, uint32_t len) override;
|
void uiSendMIDI(const uint8_t* data, uint32_t len) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadSfzFile(const std::string& filePath);
|
void loadSfzFile(const std::string& filePath);
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ static const char defaultSfzText[] =
|
||||||
"<region>sample=*sine" "\n"
|
"<region>sample=*sine" "\n"
|
||||||
"ampeg_attack=0.02 ampeg_release=0.1" "\n";
|
"ampeg_attack=0.02 ampeg_release=0.1" "\n";
|
||||||
|
|
||||||
|
enum { kMidiEventMaximumSize = 4 };
|
||||||
|
|
||||||
SfizzVstProcessor::SfizzVstProcessor()
|
SfizzVstProcessor::SfizzVstProcessor()
|
||||||
: _fifoToWorker(64 * 1024)
|
: _fifoToWorker(64 * 1024), _fifoMidiFromUi(64 * 1024)
|
||||||
{
|
{
|
||||||
setControllerClass(SfizzVstController::cid);
|
setControllerClass(SfizzVstController::cid);
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +183,8 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
|
||||||
else
|
else
|
||||||
synth.disableFreeWheeling();
|
synth.disableFreeWheeling();
|
||||||
|
|
||||||
|
processMidiFromUi();
|
||||||
|
|
||||||
if (Vst::IParameterChanges* pc = data.inputParameterChanges)
|
if (Vst::IParameterChanges* pc = data.inputParameterChanges)
|
||||||
processControllerChanges(*pc);
|
processControllerChanges(*pc);
|
||||||
|
|
||||||
|
|
@ -373,6 +377,40 @@ void SfizzVstProcessor::processEvents(Vst::IEventList& events)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SfizzVstProcessor::processMidiFromUi()
|
||||||
|
{
|
||||||
|
sfz::Sfizz& synth = *_synth;
|
||||||
|
|
||||||
|
for (uint32 size = 0; _fifoMidiFromUi.peek(size) &&
|
||||||
|
_fifoMidiFromUi.size_used() >= sizeof(size) + size; ) {
|
||||||
|
_fifoMidiFromUi.discard(sizeof(size));
|
||||||
|
|
||||||
|
if (size > kMidiEventMaximumSize) {
|
||||||
|
_fifoMidiFromUi.discard(size);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data[kMidiEventMaximumSize] = {};
|
||||||
|
_fifoMidiFromUi.get(data, size);
|
||||||
|
|
||||||
|
// interpret the MIDI message
|
||||||
|
switch (data[0] & 0xf0) {
|
||||||
|
case 0x80:
|
||||||
|
synth.noteOff(0, data[1] & 0x7f, data[2] & 0x7f);
|
||||||
|
break;
|
||||||
|
case 0x90:
|
||||||
|
synth.noteOn(0, data[1] & 0x7f, data[2] & 0x7f);
|
||||||
|
break;
|
||||||
|
case 0xb0:
|
||||||
|
synth.cc(0, data[1] & 0x7f, data[2] & 0x7f);
|
||||||
|
break;
|
||||||
|
case 0xe0:
|
||||||
|
synth.pitchWheel(0, (data[2] << 7) + data[1] - 8192);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int SfizzVstProcessor::convertVelocityFromFloat(float x)
|
int SfizzVstProcessor::convertVelocityFromFloat(float x)
|
||||||
{
|
{
|
||||||
return std::min(127, std::max(0, (int)(x * 127.0f)));
|
return std::min(127, std::max(0, (int)(x * 127.0f)));
|
||||||
|
|
@ -425,6 +463,17 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
|
||||||
reply->getAttributes()->setBinary("File", _state.scalaFile.data(), _state.scalaFile.size());
|
reply->getAttributes()->setBinary("File", _state.scalaFile.data(), _state.scalaFile.size());
|
||||||
sendMessage(reply);
|
sendMessage(reply);
|
||||||
}
|
}
|
||||||
|
else if (!std::strcmp(id, "MidiMessage")) {
|
||||||
|
const void* data = nullptr;
|
||||||
|
uint32 size = 0;
|
||||||
|
result = attr->getBinary("Data", data, size);
|
||||||
|
if (size < kMidiEventMaximumSize) {
|
||||||
|
if (_fifoMidiFromUi.size_free() >= sizeof(size) + size) {
|
||||||
|
_fifoMidiFromUi.put(size);
|
||||||
|
_fifoMidiFromUi.put(reinterpret_cast<const uint8_t*>(data), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public:
|
||||||
void processParameterChanges(Vst::IParameterChanges& pc);
|
void processParameterChanges(Vst::IParameterChanges& pc);
|
||||||
void processControllerChanges(Vst::IParameterChanges& pc);
|
void processControllerChanges(Vst::IParameterChanges& pc);
|
||||||
void processEvents(Vst::IEventList& events);
|
void processEvents(Vst::IEventList& events);
|
||||||
|
void processMidiFromUi();
|
||||||
static int convertVelocityFromFloat(float x);
|
static int convertVelocityFromFloat(float x);
|
||||||
|
|
||||||
tresult PLUGIN_API notify(Vst::IMessage* message) override;
|
tresult PLUGIN_API notify(Vst::IMessage* message) override;
|
||||||
|
|
@ -58,6 +59,7 @@ private:
|
||||||
volatile bool _workRunning = false;
|
volatile bool _workRunning = false;
|
||||||
Ring_Buffer _fifoToWorker;
|
Ring_Buffer _fifoToWorker;
|
||||||
RTSemaphore _semaToWorker;
|
RTSemaphore _semaToWorker;
|
||||||
|
Ring_Buffer _fifoMidiFromUi;
|
||||||
std::mutex _processMutex;
|
std::mutex _processMutex;
|
||||||
|
|
||||||
// file modification periodic checker
|
// file modification periodic checker
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue