diff --git a/CMakeLists.txt b/CMakeLists.txt index 658a95a..a75bb67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,8 @@ juce_add_binary_data(ChromaFlockBinaryData NAMESPACE BinaryData SOURCES bg.png - newlogo.png + logo.png + metalknob.png ) if(APPLE) diff --git a/README.md b/README.md index 142fba9..f89be86 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ChromaFlock -![ChromaFlock](newlogo.png) +![ChromaFlock](logo.png) A subtractive synthesizer plugin built with [JUCE](https://juce.com). macOS (VST3, AU, Standalone) and Linux (VST3, Standalone). diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 7bbb3ec..7032e18 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1,6 +1,11 @@ #include "PluginEditor.h" #include +KnobLookAndFeel::KnobLookAndFeel() { + knobOverlay = juce::ImageCache::getFromMemory(BinaryData::metalknob_png, + BinaryData::metalknob_pngSize); +} + void KnobLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) { @@ -25,23 +30,33 @@ void KnobLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int widt auto bevelPath = juce::Path(); bevelPath.addEllipse(rx, ry, rw, rw); + // Metal knob overlay (clipped to circle, behind gradient) + if (knobOverlay.isValid()) { + juce::Graphics::ScopedSaveState state(g); + juce::Path circlePath; + circlePath.addEllipse(rx, ry, rw, rw); + g.reduceClipRegion(circlePath); + g.setOpacity(0.55f); + g.drawImageWithin(knobOverlay, (int)rx, (int)ry, (int)rw, (int)rw, juce::RectanglePlacement::stretchToFit); + g.setOpacity(1.0f); + } + // Knob body gradient — light top-left to dark bottom-right for 3D bulge - juce::ColourGradient bodyGrad(juce::Colour(0xb3404040), rx, ry, - juce::Colour(0xb31a1a1a), rx + rw, ry + rw, true); + juce::ColourGradient bodyGrad(juce::Colour(0xa3191919), rx, ry, + juce::Colour(0xd31f1f1f), rx + rw, ry + rw, true); g.setGradientFill(bodyGrad); g.fillEllipse(rx, ry, rw, rw); // Soft inner shadow ring (bottom-right dark edge, subtle) - g.setColour(juce::Colour(0x18000000)); - auto shadowArc = juce::Path(); - float innerR = radius - 2.0f; - shadowArc.addCentredArc(centreX, centreY, innerR, innerR, 0.0f, - 0.5f, 2.7f, true); - g.strokePath(shadowArc, juce::PathStrokeType(1.5f)); + // g.setColour(juce::Colour(0x18000000)); + // auto shadowArc = juce::Path(); + // float innerR = radius - 2.0f; + // shadowArc.addCentredArc(centreX, centreY, innerR, innerR, 0.0f, 0.5f, 2.7f, true); + // g.strokePath(shadowArc, juce::PathStrokeType(1.5f)); // Outer rim - g.setColour(juce::Colour(0xff555555)); - g.drawEllipse(rx, ry, rw, rw, 1.5f); + // g.setColour(juce::Colour(0xff555555)); + // g.drawEllipse(rx, ry, rw, rw, 1.5f); // Pointer line with glow — green (left) → yellow (mid) → red (right) auto indicatorColour = juce::Colour::fromHSV(0.33f * (1.0f - sliderPos), 0.85f, 0.9f, 1.0f); @@ -931,7 +946,7 @@ void MainContentComponent::paint(juce::Graphics& g) { // Load logo if (!logoLoaded) { auto img = juce::ImageFileFormat::loadFrom( - BinaryData::newlogo_png, BinaryData::newlogo_pngSize); + BinaryData::logo_png, BinaryData::logo_pngSize); if (img.isValid()) { juce::Image::BitmapData bd(img, juce::Image::BitmapData::readOnly); int minX = bd.width, maxX = 0, minY = bd.height, maxY = 0; @@ -954,15 +969,15 @@ void MainContentComponent::paint(juce::Graphics& g) { logoLoaded = true; } if (logoImage.isValid()) { - int logoW = 440; + int logoW = 352; int logoH = logoImage.getWidth() > 0 ? static_cast(logoW * static_cast(logoImage.getHeight()) / logoImage.getWidth()) : logoW; int logoX = (getWidth() - logoW) / 2; - int logoY = (124 - logoH) / 2 + 32; + int logoY = (80 - logoH) / 2 - 2; auto logoArea = juce::Rectangle(logoX, logoY, logoW, logoH); juce::Graphics::ScopedSaveState saved(g); - g.setOpacity(0.8f); + g.setOpacity(1.0f); g.drawImage(logoImage, logoArea.toFloat(), juce::RectanglePlacement::centred); } @@ -983,13 +998,13 @@ void MainContentComponent::paint(juce::Graphics& g) { g.drawRoundedRectangle(rect, 9.0f, 1.0f); }; - drawSection(10, 128, 498, 200); - drawSection(518, 128, 624, 200); - drawSection(1152, 128, 498, 200); - drawSection(10, 338, 563, 160); - drawSection(583, 338, 502, 160); - drawSection(1095, 338, 555, 160); - drawSection(10, 956, 1640, 136); + drawSection(10, 80, 498, 200); + drawSection(518, 80, 624, 200); + drawSection(1152, 80, 498, 200); + drawSection(10, 290, 563, 160); + drawSection(583, 290, 502, 160); + drawSection(1095, 290, 555, 160); + drawSection(10, 884, 1640, 136); // FX sub-sections auto drawSubSection = [&](int x, int y, int w, int h, const juce::String& title, int titlePadY = 2) { @@ -1009,22 +1024,22 @@ void MainContentComponent::paint(juce::Graphics& g) { g.drawText(title, x + 6, y + titlePadY, w - 12, 14, juce::Justification::centred); }; - drawSubSection(10, 508, 510, 140, "DISTORTION", 6); - drawSubSection(528, 508, 468, 140, "COMPRESSION", 6); - drawSubSection(1004, 508, 268, 140, "PANNING", 6); - drawSubSection(1280, 508, 370, 140, "LIMITER", 6); + drawSubSection(10, 460, 510, 115, "DISTORTION", 6); + drawSubSection(528, 460, 468, 115, "COMPRESSION", 6); + drawSubSection(1004, 460, 268, 115, "PANNING", 6); + drawSubSection(1280, 460, 370, 115, "LIMITER", 6); // LFO sub-sections - drawSubSection(10, 658, 540, 150, "LFO 1", 4); - drawSubSection(560, 658, 530, 150, "LFO 2", 4); + drawSubSection(10, 586, 540, 150, "LFO 1", 4); + drawSubSection(560, 586, 530, 150, "LFO 2", 4); // FX2 sub-sections - drawSubSection(1015, 658, 375, 150, "DELAY", 4); - drawSubSection(1395, 658, 255, 150, "REVERB", 4); + drawSubSection(1015, 586, 375, 150, "DELAY", 4); + drawSubSection(1395, 586, 255, 150, "REVERB", 4); } void MainContentComponent::resized() { - auto sectionY = 132; + auto sectionY = 84; auto knobSize = 112; auto knobSpacing = 10; auto labelH = 18; @@ -1055,33 +1070,33 @@ void MainContentComponent::resized() { filterEnvAmtKnob.setBounds(1162 + (knobSize + knobSpacing) * 2, knobYF, knobSize, knobSize); keyTrackKnob.setBounds(1162 + (knobSize + knobSpacing) * 3, knobYF, knobSize, knobSize); - envLabel.setBounds(10, 342, 563, labelH); + envLabel.setBounds(10, 294, 563, labelH); { int rowW = 4 * knobSize + 3 * knobSpacing; int sx = 10 + (563 - rowW) / 2; - int knobYA = 352 + labelH + 6; + int knobYA = 304 + labelH + 6; envAttackKnob.setBounds(sx, knobYA, knobSize, knobSize); envDecayKnob.setBounds(sx + (knobSize + knobSpacing), knobYA, knobSize, knobSize); envSustainKnob.setBounds(sx + (knobSize + knobSpacing) * 2, knobYA, knobSize, knobSize); envReleaseKnob.setBounds(sx + (knobSize + knobSpacing) * 3, knobYA, knobSize, knobSize); } - fEnvLabel.setBounds(583, 342, 502, labelH); + fEnvLabel.setBounds(583, 294, 502, labelH); { int rowW = 4 * knobSize + 3 * knobSpacing; int sx = 583 + (502 - rowW) / 2; - int knobYFE = 352 + labelH + 6; + int knobYFE = 304 + labelH + 6; fEnvAttackKnob.setBounds(sx, knobYFE, knobSize, knobSize); fEnvDecayKnob.setBounds(sx + (knobSize + knobSpacing), knobYFE, knobSize, knobSize); fEnvSustainKnob.setBounds(sx + (knobSize + knobSpacing) * 2, knobYFE, knobSize, knobSize); fEnvReleaseKnob.setBounds(sx + (knobSize + knobSpacing) * 3, knobYFE, knobSize, knobSize); } - globalLabel.setBounds(1095, 342, 555, labelH); + globalLabel.setBounds(1095, 294, 555, labelH); { int rowW = 4 * knobSize + 3 * knobSpacing; int sx = 1095 + (555 - rowW) / 2; - int knobYM = 352 + labelH + 6; + int knobYM = 304 + labelH + 6; panKnob.setBounds(sx, knobYM, knobSize, knobSize); driveKnob.setBounds(sx + (knobSize + knobSpacing), knobYM, knobSize, knobSize); masterKnob.setBounds(sx + (knobSize + knobSpacing) * 2, knobYM, knobSize, knobSize); @@ -1089,7 +1104,7 @@ void MainContentComponent::resized() { } // --- FX SECTION --- - int fxY = 480; + int fxY = 432; int fxKnobSize = 80; int fxLabelH = 16; int fxKnobY = fxY + 54; // sub-section y(476) + titlePad(6) + titleH(14) + bottomPad(6) + 4 @@ -1138,9 +1153,9 @@ void MainContentComponent::resized() { } // --- LFO SECTION --- - int lfoY = 638; + int lfoY = 566; int lfoKnobSize = 80; - int lfoKnobY = 658 + 4 + 14 + 6; // subSectionY + titlePad + titleH + bottomPad + int lfoKnobY = 586 + 4 + 14 + 6; // subSectionY + titlePad + titleH + bottomPad lfoLabel.setBounds(10, lfoY, 1080, fxLabelH); fx2Label.setBounds(1015, lfoY, 635, fxLabelH); @@ -1180,7 +1195,7 @@ void MainContentComponent::resized() { } // --- PRESET + SCALE (vertically centered within top section, y: 0..128) --- - int headerH = 128; + int headerH = 80; int btnH = 28, btnGap = 8; int presetGroupH = btnH * 2 + btnGap; int presetStartY = (headerH - presetGroupH) / 2; @@ -1209,14 +1224,14 @@ void MainContentComponent::resized() { uiScaleBox.setBounds(1560, comboY2, 80, comboH2); // Visualizer area - int vizY = 818; + int vizY = 746; int vizH = 128; vuMeter.setBounds(10, vizY, 40, vizH); waveformDisplay.setBounds(60, vizY, 790, vizH); spectrumAnalyzer.setBounds(860, vizY, 790, vizH); // Piano roll - pianoRoll.setBounds(10, 956, 1640, 136); + pianoRoll.setBounds(10, 884, 1640, 136); } // ===== PianoRollComponent ===== diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 8844127..320790b 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -7,9 +7,12 @@ class KnobLookAndFeel : public juce::LookAndFeel_V4 { public: + KnobLookAndFeel(); void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) override; +private: + juce::Image knobOverlay; }; class ComboBoxLookAndFeel : public juce::LookAndFeel_V4 { @@ -249,7 +252,7 @@ private: MainContentComponent content; float currentScale = 1.0f; static constexpr int baseWidth = 1660; - static constexpr int baseHeight = 1100; + static constexpr int baseHeight = 1028; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChromaFlockEditor) }; diff --git a/logo.png b/logo.png index c32b928..4c49de2 100644 Binary files a/logo.png and b/logo.png differ diff --git a/newlogo.png b/newlogo.png deleted file mode 100644 index 12e7293..0000000 Binary files a/newlogo.png and /dev/null differ