diff --git a/Documentation/DexedTheme.md b/Documentation/DexedTheme.md index 427dd102..fe4d9f34 100644 --- a/Documentation/DexedTheme.md +++ b/Documentation/DexedTheme.md @@ -1,11 +1,14 @@ Dexed Theme =========== +Last update: 10 July 2024 + Dexed UI can be themed if you want a different colour combination or background images. -You need need to create a file named "DexedTheme.xml" and it must be placed in the same directory where "Dexed.xml" is created. On Windows, this is C:\Users\\\AppData\Roaming\DigitalSuburban" and on Mac it is "~/Library/Application Support/DigitalSuburban" +You need need to create a file named "DexedTheme.xml" and it must be placed in the same directory where "Dexed.xml" is created. On Windows, this is "C:\Users\<your user name>\AppData\Roaming\DigitalSuburban\Dexed" and on Mac it is "~/Library/Application Support/DigitalSuburban" (or maybe "~/Library/Application Support/DigitalSuburban/Dexed" ? FIXME!), and in Linux it is "~/.local/share/DigitalSuburban/Dexed". Colour id are analogous to JUCE color ID; see [DXLookNFeel() class](../Source/DXLookNFeel.cpp) for a complete list a defined colours id. Colour value is the hexa decimal values (from 0 to 0xFF) for ALPHA RED BLUE GREEN. +For ALPHA, 0x00 is completely transparent, 0xFF is opaque. Known colour keys; simply override those you want different. @@ -28,18 +31,47 @@ PopupMenu::backgroundColourId PopupMenu::textColourId PopupMenu::highlightedTextColourId PopupMenu::highlightedBackgroundColourId +TreeView::backgroundColourId +DirectoryContentsDisplayComponent::highlightColourId +DirectoryContentsDisplayComponent::textColourId +ScrollBar::thumbColourId Dexed::backgroundId Dexed::fillColourId ``` Image id are the file name defined in [ui folder](../Resources/ui). If it cannot find the file, the image will no longer be rendered. The image path is relative to the path where "DexedTheme.xml" is defined. +Note that sizes in pixels and structures of the images should be same as original ones, because the layout of GUI based on these hardcoded fix sizes. Recently, the following image ids could be specified: + +Knob_68x68.png +Switch_96x52.png +SwitchLighted_48x26.png +Switch_64x64.png +ButtonUnlabeled_50x30.png +Slider_52x52.png +Scaling_36_26.png +Light_28x28.png +LFO_36_26.png +OperatorEditor_574x436.png +GlobalEditor_1728x288.png + -Example configuration ---------------------- +Example ``DexedTheme.xml`` configuration +---------------------------------------- ```xml - - + + + + + + -``` \ No newline at end of file +``` + +As the result: +- the colors of the background of popup menus, the text of normal menu items, and the text and background of highlighted menu items are changed to opaque blue, white, yellow, and cyan, +- the color of thumbnail of scrollbars is changed to opaque lightgray, +- the color of bright LEDs are changed to green from red (if there is a folder named ``myTheme`` within the subdirectory where your ``Dexed.xml`` and ``DexedTheme.xml`` files are located, and there is the file ``GreenLight_28x28.png`` containing the image of a red LED inside that ``myTheme`` subdirectory). + +Note that ``DexedTheme.xml``are loaded only once during the initialization when Dexed (either the Standalone or the plugin version) is launched. \ No newline at end of file diff --git a/Documentation/GreenLight_28x28.png b/Documentation/GreenLight_28x28.png new file mode 100644 index 00000000..07d305b6 Binary files /dev/null and b/Documentation/GreenLight_28x28.png differ diff --git a/Source/DXLookNFeel.cpp b/Source/DXLookNFeel.cpp index 85d70bd2..51a9bc2b 100644 --- a/Source/DXLookNFeel.cpp +++ b/Source/DXLookNFeel.cpp @@ -17,6 +17,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ + +#include +#include #include "DXLookNFeel.h" #include "DXComponents.h" @@ -29,8 +32,12 @@ Image findImage(String path) { Image img; if ( path.length() <= 3 ) return img; - File imgFile(path); + //File imgFile(path); // it might cause an assertion in juce_File.cpp + File imgFile = File::getCurrentWorkingDirectory().getChildFile(path); img = ImageCache::getFromFile(imgFile); + if (img.isNull()) { + TRACE("img.isNull() == true, path=%s", path.toRawUTF8()); + } return img; } @@ -40,6 +47,8 @@ DXLookNFeel::DXLookNFeel() { DexedAudioProcessor::dexedAppDir.setAsCurrentWorkingDirectory(); ctrlBackground = Colour(20,18,18); + // WARNING! If you modify the colour IDs here, please actualize the file ''DexedTheme.md'' + // in the subdirectory ``~/dexed/Documentation/`` REG_COLOUR(TextButton::buttonColourId,Colour(0xFF0FC00F)); REG_COLOUR(TextButton::textColourOnId, Colours::white); REG_COLOUR(TextButton::textColourOffId, Colours::white); @@ -62,6 +71,11 @@ DXLookNFeel::DXLookNFeel() { REG_COLOUR(DirectoryContentsDisplayComponent::highlightColourId, fillColour); REG_COLOUR(DirectoryContentsDisplayComponent::textColourId, Colours::white); + // Register ``Scrollbar::thumbColourId`` to allow its redefinion in ``DexedTheme.xml``. + REG_COLOUR(ScrollBar::thumbColourId, background.darker()); + + // WARNING! If you modify the images here, please actualize the file ''DexedTheme.md'' + // in the subdirectory ``~/dexed/Documentation/`` imageKnob = ImageCache::getFromMemory(BinaryData::Knob_68x68_png, BinaryData::Knob_68x68_pngSize); // 2x imageSwitch = ImageCache::getFromMemory(BinaryData::Switch_96x52_png, BinaryData::Switch_96x52_pngSize); // 2x imageSwitchLighted = ImageCache::getFromMemory(BinaryData::SwitchLighted_48x26_png, BinaryData::SwitchLighted_48x26_pngSize); @@ -74,14 +88,28 @@ DXLookNFeel::DXLookNFeel() { imageOperator = ImageCache::getFromMemory(BinaryData::OperatorEditor_574x436_png, BinaryData::OperatorEditor_574x436_pngSize); // 2x imageGlobal = ImageCache::getFromMemory (BinaryData::GlobalEditor_1728x288_png, BinaryData::GlobalEditor_1728x288_pngSize); // 2x + //--- + // load and parse the file ``DexedTheme.xml`` + //--- + File dexedTheme = DexedAudioProcessor::dexedAppDir.getChildFile("DexedTheme.xml"); - if ( ! dexedTheme.existsAsFile() ) + if ( ! dexedTheme.existsAsFile() ) { + TRACE("no DexedTheme.xml found at %s", dexedTheme.getFullPathName().toRawUTF8()); return; + } std::unique_ptr root = XmlDocument::parse(dexedTheme); if ( root == NULL ) + { + TRACE("DXLookNFeel(): ERROR: XmlDocument::parse(): failed"); return; + } + + //--- + // get custom colors from ``DexedTheme.xml`` + // specified by the ``colour id`` / ``value`` pairs + //--- forEachXmlChildElementWithTagName(*root, colour, "colour") { String name = colour->getStringAttribute("id", ""); @@ -90,32 +118,51 @@ DXLookNFeel::DXLookNFeel() { String value = colour->getStringAttribute("value", ""); if ( value == "" ) continue; - if ( value.length() < 8 ) + if ( value.length() < 8 ) { + TRACE("ERROR: illegal value=\"%s\" at color id=\"%s\"; skipped", value.toRawUTF8(), name.toRawUTF8()); continue; - int conv = strtol(value.toRawUTF8(), NULL, 16); + } + //int conv = strtol(value.toRawUTF8(), NULL, 16); // as alpha (MSB) could be above 0x7F, hence ``strtol()`` is inappropiate to convert values exceeding ``0x7FFFFFFF`` + char* endptr = NULL; + uint64_t conv = strtoull(value.toRawUTF8(), &endptr, 16); + TRACE("color id=\"%s\" value=\"%s\": conv=0x%" PRIx64 "", name.toRawUTF8(), value.toRawUTF8(), conv); + if (endptr != nullptr && *endptr != '\0') { + TRACE("ERROR: illegal char #%d in value=\"%s\" at color id=\"%s\"; skipped", (int)(*endptr), value.toRawUTF8(), name.toRawUTF8()); + continue; + } + if (conv > 0xFFFFFFFFULL) { + TRACE("ERROR: value 0x%" PRIx64 " exceeded the limit at color id=\"%s\"; skipped", conv, name.toRawUTF8()); + continue; + } if ( colourMap.contains(name) ) { - setColour(colourMap[name], Colour(conv)); + setColour(colourMap[name], Colour((uint32_t)conv)); } else { if ( name == "Dexed::backgroundId" ) { - background = Colour(conv); + background = Colour((uint32_t)conv); continue; } - if ( name == "Dexed::fillColourId" ) { - fillColour = Colour(conv); + else if ( name == "Dexed::fillColourId" ) { + fillColour = Colour((uint32_t)conv); continue; } + TRACE("ERROR: color id=\"%s\" not found in colourMap; skipped.", name.toRawUTF8()); } } - // TODO: THIS IS DEAD CODE. NOBODY IS USING THIS. + //--- + // get custom images from ``DexedTheme.xml`` + // specified by the ``image id`` / ``path`` pairs + //--- + forEachXmlChildElementWithTagName(*root, image, "image") { String name = image->getStringAttribute("id", ""); String path = image->getStringAttribute("path", ""); - if ( name == "Knob_34x34.png" ) { + //TRACE("image id=\'%s\' path=\'%s\'", name.toRawUTF8(), path.toRawUTF8()); + if ( name == /*"Knob_34x34.png"*/"Knob_68x68.png" ) { // 2x imageKnob = findImage(path); continue; } - if ( name == "Switch_48x26.png" ) { + if ( name == /*"Switch_48x26.png"*/ "Switch_96x52.png" ) { // 2x imageSwitch = findImage(path); continue; } @@ -123,7 +170,7 @@ DXLookNFeel::DXLookNFeel() { imageSwitchLighted = findImage(path); continue; } - if ( name == "Switch_32x64.png" ) { + if ( name == /*"Switch_32x64.png"*/ "Switch_64x64.png" ) { // 2x imageSwitchOperator = findImage(path); continue; } @@ -131,7 +178,7 @@ DXLookNFeel::DXLookNFeel() { imageButton = findImage(path); continue; } - if ( name == "Slider_26x26.png" ) { + if ( name == /*"Slider_26x26.png"*/ "Slider_52x52.png" ) { // 2x imageSlider = findImage(path); continue; } @@ -139,7 +186,7 @@ DXLookNFeel::DXLookNFeel() { imageScaling = findImage(path); continue; } - if ( name == "Light_14x14.png" ) { + if ( name == /*"Light_14x14.png"*/ "Light_28x28.png" ) { // 2x imageLight = findImage(path); continue; } @@ -147,14 +194,15 @@ DXLookNFeel::DXLookNFeel() { imageLFO = findImage(path); continue; } - if ( name == "OperatorEditor_287x218.png" ) { + if ( name == /*"OperatorEditor_287x218.png"*/ "OperatorEditor_574x436_png" ) { // 2x imageOperator = findImage(path); continue; } - if ( name == "GlobalEditor_864x144.png" ) { + if ( name == /*"GlobalEditor_864x144.png"*/ "GlobalEditor_1728x288_png" ) { // 2x imageGlobal = findImage(path); continue; } + TRACE("ERROR: unknown image id=\"%s\"; skipped", name.toRawUTF8()); } } @@ -173,7 +221,8 @@ void DXLookNFeel::drawRotarySlider( Graphics &g, int x, int y, int width, int he const int nFrames = imageKnob.getHeight()/imageKnob.getWidth(); // number of frames for vertical film strip const int frameIdx = (int)ceil(fractRotation * ((double)nFrames-1.0) ); // current index from 0 --> nFrames-1 - const float radius = jmin (width / 2.0f, height / 2.0f) ; + //const float radius = jmin (width / 2.0f, height / 2.0f) ; + const float radius = jmin(width * 0.5f, height * 0.5f); const float centreX = x + width * 0.5f; const float centreY = y + height * 0.5f; const float rx = centreX - radius - 1.0f; @@ -254,4 +303,3 @@ Colour DXLookNFeel::fillColour = Colour(77,159,151); Colour DXLookNFeel::lightBackground = Colour(78,72,63); Colour DXLookNFeel::background = Colour(60,50,47); Colour DXLookNFeel::roundBackground = Colour(58,52,48); - diff --git a/Source/GlobalEditor.cpp b/Source/GlobalEditor.cpp index 15dd94e3..11da45a4 100644 --- a/Source/GlobalEditor.cpp +++ b/Source/GlobalEditor.cpp @@ -31,14 +31,25 @@ * Ugly but useful midi monitor to know if you are really sending/receiving something from the DX7 * If the midi is not configured this component wont show up * + */ +#ifdef IMPLEMENT_MidiMonitor class MidiMonitor : public Component { SysexComm *midi; Image light; + int imageHeight; + int imageHeight2; + int imageWidth; + SharedResourcePointer lookAndFeel; public: MidiMonitor(SysexComm *sysexComm) { midi = sysexComm; - light = DXLookNFeel::getLookAndFeel()->imageLight; + light = lookAndFeel->imageLight; + imageHeight = light.getHeight(); + imageHeight2 = imageHeight / 2; + imageWidth = light.getWidth(); + + TRACE("WARNING! This functionality is a candidate for deprecation/obsolescence!"); } void paint(Graphics &g) { @@ -47,24 +58,25 @@ class MidiMonitor : public Component { g.setColour(Colours::white); if ( midi->isInputActive() ) { - g.drawSingleLineText("DX7 IN", 17,14); - g.drawImage(light, 0, 3, 14, 14, 0, midi->inActivity ? 14 : 0, 14, 14); + g.drawSingleLineText("DX7 IN", 24, 18); + g.drawImage(light, 0, 0, imageWidth, imageHeight2, 0, midi->inActivity ? imageHeight2 : 0, imageWidth, imageHeight2); midi->inActivity = false; } if ( midi->isOutputActive() ) { - g.drawSingleLineText("DX7 OUT", 17, 28); - g.drawImage(light, 0, 17, 14, 14, 0, midi->outActivity ? 14 : 0, 14, 14); + g.drawSingleLineText("DX7 OUT", 24, 36); + g.drawImage(light, 0, 18, imageWidth, imageHeight2, 0, midi->outActivity ? imageHeight2 : 0, imageWidth, imageHeight2); midi->outActivity = false; } } -};*/ +}; +#endif //IMPLEMENT_MidiMonitor class AboutBox : public DialogWindow { public: Image logo_png; std::unique_ptr dexed; // changed to std::unique_ptr from juce::ScopedPointer - std::unique_ptr surge; // changed to std__unique_ptr from juce::ScopedPointer + std::unique_ptr surge; // changed to std::unique_ptr from juce::ScopedPointer AboutBox(Component *parent) : DialogWindow("About", Colour(0xFF000000), true), dexed(std::make_unique("https://asb2m10.github.io/dexed/", URL("https://asb2m10.github.io/dexed/"))), @@ -706,9 +718,11 @@ void GlobalEditor::bind(DexedAudioProcessorEditor *edit) { editor = edit; - //midiMonitor = new MidiMonitor(&(processor->sysexComm)); - //addAndMakeVisible(midiMonitor); - //midiMonitor->setBounds(155, 21, 80, 45); +#ifdef IMPLEMENT_MidiMonitor + midiMonitor = std::make_unique(&(processor->sysexComm)); + addAndMakeVisible(*midiMonitor); + midiMonitor->setBounds(110, 10, 80, 45); //midiMonitor->setBounds(155, 21, 80, 45); +#endif //IMPLEMENT_MidiMonitor repaint(); } @@ -734,7 +748,9 @@ void GlobalEditor::updatePitchPos(int pos) { void GlobalEditor::updateVu(float f) { vuOutput->v = f; vuOutput->repaint(); - //midiMonitor->repaint(); +#ifdef IMPLEMENT_MidiMonitor + midiMonitor->repaint(); +#endif //IMPLEMENT_MidiMonitor } void GlobalEditor::setMonoState(bool state) { diff --git a/Source/GlobalEditor.h b/Source/GlobalEditor.h index e892a1d6..65b59b43 100644 --- a/Source/GlobalEditor.h +++ b/Source/GlobalEditor.h @@ -25,6 +25,10 @@ #include "DXComponents.h" #include "AlgoDisplay.h" +#ifdef IMPLEMENT_MidiMonitor +#include "SysexComm.h" +#endif // IMPLEMENT_MidiMonitor + class DexedAudioProcessorEditor; //[/Headers] @@ -59,7 +63,10 @@ class GlobalEditor : public Component, void setMonoState(bool state); ProgramSelector *programs; - //std::unique_ptr midiMonitor; + +#ifdef IMPLEMENT_MidiMonitor + std::unique_ptr midiMonitor; +#endif //IMPLEMENT_MidiMonitor void mouseDown(const MouseEvent& e) override; //[/UserMethods] diff --git a/Source/PluginFx.cpp b/Source/PluginFx.cpp index e1b5171a..43989bdf 100644 --- a/Source/PluginFx.cpp +++ b/Source/PluginFx.cpp @@ -101,7 +101,12 @@ inline float PluginFx::NR(float sample, float g) { } void PluginFx::process(float *work, int sampleSize) { + // very basic DC filter + // see descriptions at: https://www.musicdsp.org/en/latest/Filters/135-dc-filter.html + // https://www.degruyter.com/document/doi/10.1515/freq-2020-0177/html?lang=en + // https://www.dsprelated.com/showarticle/58.php + float t_fd = work[0]; work[0] = work[0] - dc_id + dc_r * dc_od; dc_id = t_fd; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 1264ad7c..f02889ed 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -599,7 +599,9 @@ void DexedAudioProcessor::handleIncomingMidiMessage(MidiInput* source, const Mid if ( message.isActiveSense() ) return; - sysexComm.inActivity = true; +#ifdef IMPLEMENT_MidiMonitor + sysexComm.inActivity = true; // indicate for MidiMonitor, that a MIDI messages (other than Active Sense) received +#endif //IMPLEMENT_MidiMonitor const uint8 *buf = message.getRawData(); int sz = message.getRawDataSize(); diff --git a/Source/SysexComm.cpp b/Source/SysexComm.cpp index c4e70aee..43aba0b6 100644 --- a/Source/SysexComm.cpp +++ b/Source/SysexComm.cpp @@ -31,6 +31,11 @@ SysexComm::SysexComm() { input = NULL; output = NULL; inputOutput = false; + +#ifdef IMPLEMENT_MidiMonitor + inActivity = false; + outActivity = false; +#endif //IMPLEMENT_MidiMonitor } String SysexComm::getInput() { @@ -38,8 +43,10 @@ String SysexComm::getInput() { } bool SysexComm::setInput(String target) { +#ifndef IMPLEMENT_MidiMonitor if ( JUCEApplication::isStandaloneApp() ) return true; +#endif if ( input != NULL ) { input->stop(); @@ -132,7 +139,11 @@ void SysexComm::setChl(int chl) { int SysexComm::send(const MidiMessage &message) { if ( output == NULL ) return 2; + +#ifdef IMPLEMENT_MidiMonitor outActivity = true; +#endif // IMPLEMENT_MidiMonitor + output->sendMessageNow(message); return 0; } diff --git a/Source/SysexComm.h b/Source/SysexComm.h index 871266b3..abd9b10b 100644 --- a/Source/SysexComm.h +++ b/Source/SysexComm.h @@ -23,6 +23,14 @@ #include "../JuceLibraryCode/JuceHeader.h" +// If the macro ''IMPLEMENT_MidiMonitor'' is defined, +// then the class ''MidiMonitor'' and its related variables +// and invocations occurring in other classes are implemented. +// If the macro is not defined the related source snippets are excluded. +// WARNING: this class and related variables and invocations are very likely candidates for deprecation / obsolescence, +// so it is NOT RECOMMENDED to define this macro! +//#define IMPLEMENT_MidiMonitor + class SysexComm { std::unique_ptr input; std::unique_ptr output; @@ -35,9 +43,10 @@ class SysexComm { MidiBuffer noteOutput; public : MidiInputCallback *listener; +#ifdef IMPLEMENT_MidiMonitor bool inActivity; bool outActivity; - +#endif //IMPLEMENT_MidiMonitor SysexComm(); bool setInput(String name); diff --git a/Source/msfa/aligned_buf.h b/Source/msfa/aligned_buf.h index 8f7c5b68..28c29bba 100644 --- a/Source/msfa/aligned_buf.h +++ b/Source/msfa/aligned_buf.h @@ -17,12 +17,17 @@ // A convenient wrapper for buffers with alignment constraints // Note that if we were on C++11, we'd use aligned_storage or somesuch. +// +// Define ``IMPLEMENT_ORIGINAL_ALIGNED_BUF`` to use the original version, +// or DO NOT define it to allow alignment mechanism provided by the C++ compilers. #ifndef __ALIGNED_BUF_H #define __ALIGNED_BUF_H #include +#ifdef IMPLEMENT_ORIGINAL_ALIGNED_BUF + template class AlignedBuf { public: @@ -33,4 +38,24 @@ class AlignedBuf { unsigned char storage_[size * sizeof(T) + alignment]; }; +#else //IMPLEMENT_ORIGINAL_ALIGNED_BUF + +template +class AlignedBuf { +public: + inline T* get() { + return (T*)storage_; + } +private: +#ifdef __GNUC__ + // GCC specific code + T storage_[size] __attribute__((aligned(alignment))); +#else // __GNUC__ + // MSC specific code + alignas(alignment) T storage_[size]; +#endif // __GNUC__ +}; + +#endif //IMPLEMENT_ORIGINAL_ALIGNED_BUF + #endif // __ALIGNED_BUF_H