diff --git a/README.md b/README.md index d155591e..70add6c8 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ The sfizz library also uses in some subprojects: - [cxxopts] by Jarryd Beck, licensed under the MIT license - [fmidi] by Jean Pierre Cimalando, licensed under the Boost Software License 1.0 - [libsamplerate], licensed under the BSD 2-Clause license +- [GLSL-Color-Spaces] by tobspr, licensed under the MIT license [Abseil]: https://abseil.io/ [atomic_queue]: https://github.com/max0x7ba/atomic_queue @@ -74,6 +75,7 @@ The sfizz library also uses in some subprojects: [libsamplerate]: http://www.mega-nerd.com/SRC/ [libsndfile]: http://www.mega-nerd.com/libsndfile/ [LV2]: https://lv2plug.in/ +[GLSL-Color-Spaces]: https://github.com/tobspr/GLSL-Color-Spaces [our website]: https://sfz.tools/sfizz [releases]: https://github.com/sfztools/sfizz/releases [Carla]: https://kx.studio/Applications:Carla diff --git a/plugins/editor/CMakeLists.txt b/plugins/editor/CMakeLists.txt index 1c10dab7..d2153d32 100644 --- a/plugins/editor/CMakeLists.txt +++ b/plugins/editor/CMakeLists.txt @@ -36,6 +36,8 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL src/editor/GUIComponents.cpp src/editor/GUIPiano.h src/editor/GUIPiano.cpp + src/editor/ColorHelpers.h + src/editor/ColorHelpers.cpp src/editor/NativeHelpers.h src/editor/NativeHelpers.cpp src/editor/layout/main.hpp @@ -59,6 +61,10 @@ if(APPLE) endif() # dependencies +add_library(sfizz_colorspaces INTERFACE) +add_library(sfizz::colorspaces ALIAS sfizz_colorspaces) +target_include_directories(sfizz_colorspaces INTERFACE "external/color-spaces") + if(WIN32) # elseif(APPLE) @@ -69,7 +75,7 @@ else() target_include_directories(sfizz_editor PRIVATE ${sfizz-gio_INCLUDE_DIRS}) target_link_libraries(sfizz_editor PRIVATE ${sfizz-gio_LIBRARIES}) endif() -target_link_libraries(sfizz_editor PRIVATE sfizz::bit_array sfizz::filesystem) +target_link_libraries(sfizz_editor PRIVATE sfizz::colorspaces sfizz::bit_array sfizz::filesystem) # layout tool if(NOT CMAKE_CROSSCOMPILING) diff --git a/plugins/editor/external/color-spaces/ColorSpaces.h b/plugins/editor/external/color-spaces/ColorSpaces.h new file mode 100644 index 00000000..06af231a --- /dev/null +++ b/plugins/editor/external/color-spaces/ColorSpaces.h @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +/* +GLSL Color Space Utility Functions +(c) 2015 tobspr + +Porting a subset to C++ +(c) 2020 Jean Pierre Cimalando + +------------------------------------------------------------------------------- + +The MIT License (MIT) + +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------- + +Most formulars / matrices are from: +https://en.wikipedia.org/wiki/SRGB + +Some are from: +http://www.chilliant.com/rgb2hsv.html +https://www.fourcc.org/fccyvrgb.php +*/ + +#pragma once +#include +#include +#include + +namespace ColorSpaces { + +template using vec = std::array; +using vec3 = vec<3>; +using vec4 = vec<4>; + +template +T clamp(T x, T lo, T hi) +{ + return std::max(lo, std::min(hi, x)); +} + +template +vec saturate(vec x) +{ + for (std::size_t i = 0; i < N; ++i) + x[i] = clamp(x[i], 0.0f, 1.0f); + return x; +} + +float dot(vec3 a, vec3 b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +// Constants +static constexpr float HCV_EPSILON = 1e-10; +static constexpr float HCY_EPSILON = 1e-10; + +// Converts a value from linear RGB to HCV (Hue, Chroma, Value) +vec3 rgb_to_hcv(vec3 rgb) +{ + // Based on work by Sam Hocevar and Emil Persson + vec4 P = (rgb[1] < rgb[2]) ? vec4{{rgb[2], rgb[1], -1.0, 2.0/3.0}} : vec4{{rgb[1], rgb[2], 0.0, -1.0/3.0}}; + vec4 Q = (rgb[0] < P[0]) ? vec4{{P[0], P[1], P[3], rgb[0]}} : vec4{{rgb[0], P[1], P[2], P[0]}}; + float C = Q[0] - std::min(Q[3], Q[1]); + float H = std::abs((Q[3] - Q[1]) / (6 * C + HCV_EPSILON) + Q[2]); + return vec3{{H, C, Q[0]}}; +} + +// Converts from pure Hue to linear RGB +vec3 hue_to_rgb(float hue) +{ + float R = std::fabs(hue * 6 - 3) - 1; + float G = 2 - std::fabs(hue * 6 - 2); + float B = 2 - std::fabs(hue * 6 - 4); + return saturate(vec3{{R,G,B}}); +} + +// Converts from HCY to linear RGB +vec3 hcy_to_rgb(vec3 hcy) +{ + const vec3 HCYwts{{0.299, 0.587, 0.114}}; + vec3 RGB = hue_to_rgb(hcy[0]); + float Z = dot(RGB, HCYwts); + if (hcy[2] < Z) { + hcy[1] *= hcy[2] / Z; + } else if (Z < 1) { + hcy[1] *= (1 - hcy[2]) / (1 - Z); + } + return vec3{{(RGB[0] - Z) * hcy[1] + hcy[2], + (RGB[1] - Z) * hcy[1] + hcy[2], + (RGB[2] - Z) * hcy[1] + hcy[2]}}; +} + +// Converts from rgb to hcy (Hue, Chroma, Luminance) +vec3 rgb_to_hcy(vec3 rgb) +{ + const vec3 HCYwts = vec3{{0.299, 0.587, 0.114}}; + // Corrected by David Schaeffer + vec3 HCV = rgb_to_hcv(rgb); + float Y = dot(rgb, HCYwts); + float Z = dot(hue_to_rgb(HCV[0]), HCYwts); + if (Y < Z) { + HCV[1] *= Z / (HCY_EPSILON + Y); + } else { + HCV[1] *= (1 - Z) / (HCY_EPSILON + 1 - Y); + } + return vec3{{HCV[0], HCV[1], Y}}; +} + +} // namespace ColorSpaces diff --git a/plugins/editor/external/color-spaces/LICENSE b/plugins/editor/external/color-spaces/LICENSE new file mode 100644 index 00000000..20efd1b3 --- /dev/null +++ b/plugins/editor/external/color-spaces/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/plugins/editor/src/editor/ColorHelpers.cpp b/plugins/editor/src/editor/ColorHelpers.cpp new file mode 100644 index 00000000..0d92ce27 --- /dev/null +++ b/plugins/editor/src/editor/ColorHelpers.cpp @@ -0,0 +1,46 @@ +// 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 "ColorHelpers.h" +#include + +SColorRGB::SColorRGB(const CColor &cc) +{ + r = cc.normRed(); + g = cc.normGreen(); + b = cc.normBlue(); + a = cc.normAlpha(); +} + +SColorRGB::SColorRGB(const SColorHCY &hcy) +{ + ColorSpaces::vec3 vhcy{{hcy.h, hcy.c, hcy.y}}; + ColorSpaces::vec3 vrgb = ColorSpaces::hcy_to_rgb(vhcy); + r = vrgb[0]; + g = vrgb[1]; + b = vrgb[2]; + a = hcy.a; +} + +CColor SColorRGB::toColor() const +{ + CColor cc; + cc.setNormRed(r); + cc.setNormGreen(g); + cc.setNormBlue(b); + cc.setNormAlpha(a); + return cc; +} + +SColorHCY::SColorHCY(const SColorRGB &rgb) +{ + ColorSpaces::vec3 vrgb{{rgb.r, rgb.g, rgb.b}}; + ColorSpaces::vec3 vhcy = ColorSpaces::rgb_to_hcy(vrgb); + h = vhcy[0]; + c = vhcy[1]; + y = vhcy[2]; + a = rgb.a; +} diff --git a/plugins/editor/src/editor/ColorHelpers.h b/plugins/editor/src/editor/ColorHelpers.h new file mode 100644 index 00000000..fd484799 --- /dev/null +++ b/plugins/editor/src/editor/ColorHelpers.h @@ -0,0 +1,33 @@ +// 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 "vstgui/lib/ccolor.h" + +using namespace VSTGUI; + +struct SColorRGB; +struct SColorHCY; + +struct SColorRGB { + SColorRGB() = default; + explicit SColorRGB(const CColor &cc); + explicit SColorRGB(const SColorHCY &hcy); + SColorRGB(float r, float g, float b, float a = 1.0) : r(r), g(g), b(b), a(a) {} + CColor toColor() const; + + float r {}, g {}, b {}, a { 1.0 }; +}; + +struct SColorHCY { + SColorHCY() = default; + explicit SColorHCY(const CColor &cc) : SColorHCY(SColorRGB(cc)) {} + explicit SColorHCY(const SColorRGB &rgb); + SColorHCY(float h, float c, float y, float a = 1.0) : h(h), c(c), y(y), a(a) {} + CColor toColor() const { return SColorRGB(*this).toColor(); } + + float h {}, c {}, y {}, a { 1.0 }; +}; diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 14987bf1..173d7166 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -164,6 +164,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { void updateTuningFrequencyLabel(float tuningFrequency); void updateStretchedTuningLabel(float stretchedTuning); + void updateKeyUsed(unsigned key, bool used); void updateCCUsed(unsigned cc, bool used); void updateCCValue(unsigned cc, float value); void updateCCDefaultValue(unsigned cc, float value); @@ -394,7 +395,12 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi unsigned indices[8]; if (Messages::matchOSC("/key/slots", path, indices) && !strcmp(sig, "b")) { - // TODO(jpc) key ranges + size_t numBits = 8 * args[0].b->size; + ConstBitSpan bits { args[0].b->data, numBits }; + for (unsigned key = 0; key < 128; ++key) { + bool used = key < numBits && bits.test(key); + updateKeyUsed(key, used); + } } else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b")) { size_t numBits = 8 * args[0].b->size; @@ -1274,6 +1280,12 @@ void Editor::Impl::updateStretchedTuningLabel(float stretchedTuning) label->setText(text); } +void Editor::Impl::updateKeyUsed(unsigned key, bool used) +{ + if (SPiano* piano = piano_) + piano->setKeyUsed(key, used); +} + void Editor::Impl::updateCCUsed(unsigned cc, bool used) { if (SControlsPanel* panel = controlsPanel_) diff --git a/plugins/editor/src/editor/GUIPiano.cpp b/plugins/editor/src/editor/GUIPiano.cpp index 2c17ddc4..22ec4681 100644 --- a/plugins/editor/src/editor/GUIPiano.cpp +++ b/plugins/editor/src/editor/GUIPiano.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "GUIPiano.h" +#include "ColorHelpers.h" #include "utility/vstgui_before.h" #include "vstgui/lib/cdrawcontext.h" #include "vstgui/lib/cgraphicspath.h" @@ -37,11 +38,24 @@ void SPiano::setNumOctaves(unsigned octs) invalid(); } +void SPiano::setKeyUsed(unsigned key, bool used) +{ + if (key >= 128) + return; + + if (keyUsed_.test(key) == used) + return; + + keyUsed_.set(key, used); + invalid(); +} + void SPiano::draw(CDrawContext* dc) { const Dimensions dim = getDimensions(false); const unsigned octs = octs_; const unsigned keyCount = octs * 12; + const bool allKeysUsed = keyUsed_.all(); dc->setDrawMode(kAntiAliasing); @@ -56,9 +70,17 @@ void SPiano::draw(CDrawContext* dc) for (unsigned key = 0; key < keyCount; ++key) { if (!black[key % 12]) { CRect rect = keyRect(key); - CColor keycolor = whiteFill_; + + SColorHCY hcy(keyUsedHue_, 1.0, whiteKeyLuma_); + if (!keyUsed_[key] || allKeysUsed) { + hcy.y = 1.0; + if (keyval_[key]) + hcy.c = 0.0; + } if (keyval_[key]) - keycolor = pressedFill_; + hcy.y = std::max(0.0f, hcy.y - keyLumaPressDelta_); + + CColor keycolor = hcy.toColor(); dc->setFillColor(keycolor); dc->drawRect(rect, kDrawFilled); } @@ -76,9 +98,14 @@ void SPiano::draw(CDrawContext* dc) for (unsigned key = 0; key < keyCount; ++key) { if (black[key % 12]) { CRect rect = keyRect(key); - CColor keycolor = blackFill_; + + SColorHCY hcy(keyUsedHue_, 1.0, blackKeyLuma_); + if (!keyUsed_[key] || allKeysUsed) + hcy.c = 0.0; if (keyval_[key]) - keycolor = pressedFill_; + hcy.y = std::max(0.0f, hcy.y - keyLumaPressDelta_); + + CColor keycolor = hcy.toColor(); dc->setFillColor(keycolor); dc->drawRect(rect, kDrawFilled); dc->setFrameColor(outline_); diff --git a/plugins/editor/src/editor/GUIPiano.h b/plugins/editor/src/editor/GUIPiano.h index 6927b06f..c35f8741 100644 --- a/plugins/editor/src/editor/GUIPiano.h +++ b/plugins/editor/src/editor/GUIPiano.h @@ -11,6 +11,7 @@ #include "utility/vstgui_after.h" #include #include +#include using namespace VSTGUI; @@ -24,6 +25,8 @@ public: unsigned getNumOctaves() const { return octs_; } void setNumOctaves(unsigned octs); + void setKeyUsed(unsigned key, bool used); + std::function onKeyPressed; std::function onKeyReleased; @@ -52,6 +55,7 @@ private: private: unsigned octs_ {}; std::vector keyval_; + std::bitset<128> keyUsed_; unsigned mousePressedKey_ = ~0u; CCoord innerPaddingX_ = 4.0; @@ -60,9 +64,12 @@ private: 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 }; + + float keyUsedHue_ = 0.55; + float whiteKeyLuma_ = 0.9; + float blackKeyLuma_ = 0.5; + float keyLumaPressDelta_ = 0.2; + CColor outline_ { 0x00, 0x00, 0x00, 0xff }; CColor shadeOutline_ { 0x80, 0x80, 0x80, 0xff }; CColor labelStroke_ { 0x63, 0x63, 0x63, 0xff };