diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5ac1aba8..aa9f750d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,46 +16,47 @@ add_subdirectory(common)
add_subdirectory(lib/vst3sdk)
smtg_enable_vst3_sdk()
-add_subdirectory(AccumulativeRingMod)
-add_subdirectory(BasicLimiter)
-add_subdirectory(BasicLimiterAutoMake)
+# add_subdirectory(AccumulativeRingMod)
+# add_subdirectory(BasicLimiter)
+# add_subdirectory(BasicLimiterAutoMake)
add_subdirectory(ClangCymbal)
-add_subdirectory(ClangSynth)
-add_subdirectory(CollidingCombSynth)
-add_subdirectory(CombDistortion)
-add_subdirectory(CubicPadSynth)
-add_subdirectory(EnvelopedSine)
-add_subdirectory(EsPhaser)
-add_subdirectory(FDN64Reverb)
-add_subdirectory(FDNCymbal)
-add_subdirectory(FeedbackPhaser)
-add_subdirectory(FoldShaper)
-add_subdirectory(GenericDrum)
-add_subdirectory(IterativeSinCluster)
-add_subdirectory(L3Reverb)
-add_subdirectory(L4Reverb)
-add_subdirectory(LatticeReverb)
-add_subdirectory(LightPadSynth)
-add_subdirectory(LongPhaser)
-add_subdirectory(MatrixShifter)
-add_subdirectory(MaybeSnare)
-add_subdirectory(MembraneSynth)
-add_subdirectory(MiniCliffEQ)
-add_subdirectory(ModuloShaper)
-add_subdirectory(NarrowingDelay)
-add_subdirectory(OddPowShaper)
-add_subdirectory(OrdinaryPhaser)
-add_subdirectory(ParallelComb)
-add_subdirectory(ParallelDetune)
-add_subdirectory(PitchShiftDelay)
-add_subdirectory(RingModSpacer)
-add_subdirectory(SevenDelay)
-add_subdirectory(SoftClipper)
-add_subdirectory(SyncSawSynth)
-add_subdirectory(TrapezoidSynth)
-add_subdirectory(UltraSynth)
-add_subdirectory(UltrasonicRingMod)
-add_subdirectory(WaveCymbal)
+
+# add_subdirectory(ClangSynth)
+# add_subdirectory(CollidingCombSynth)
+# add_subdirectory(CombDistortion)
+# add_subdirectory(CubicPadSynth)
+# add_subdirectory(EnvelopedSine)
+# add_subdirectory(EsPhaser)
+# add_subdirectory(FDN64Reverb)
+# add_subdirectory(FDNCymbal)
+# add_subdirectory(FeedbackPhaser)
+# add_subdirectory(FoldShaper)
+# add_subdirectory(GenericDrum)
+# add_subdirectory(IterativeSinCluster)
+# add_subdirectory(L3Reverb)
+# add_subdirectory(L4Reverb)
+# add_subdirectory(LatticeReverb)
+# add_subdirectory(LightPadSynth)
+# add_subdirectory(LongPhaser)
+# add_subdirectory(MatrixShifter)
+# add_subdirectory(MaybeSnare)
+# add_subdirectory(MembraneSynth)
+# add_subdirectory(MiniCliffEQ)
+# add_subdirectory(ModuloShaper)
+# add_subdirectory(NarrowingDelay)
+# add_subdirectory(OddPowShaper)
+# add_subdirectory(OrdinaryPhaser)
+# add_subdirectory(ParallelComb)
+# add_subdirectory(ParallelDetune)
+# add_subdirectory(PitchShiftDelay)
+# add_subdirectory(RingModSpacer)
+# add_subdirectory(SevenDelay)
+# add_subdirectory(SoftClipper)
+# add_subdirectory(SyncSawSynth)
+# add_subdirectory(TrapezoidSynth)
+# add_subdirectory(UltraSynth)
+# add_subdirectory(UltrasonicRingMod)
+# add_subdirectory(WaveCymbal)
# ## Below are prototype plugins. Breaking changes will be introduced.
# add_subdirectory(TestBedSynth)
diff --git a/ClangCymbal/source/editor.cpp b/ClangCymbal/source/editor.cpp
index 631a3e5d..c86da35c 100644
--- a/ClangCymbal/source/editor.cpp
+++ b/ClangCymbal/source/editor.cpp
@@ -16,6 +16,7 @@
// along with ClangCymbal. If not, see .
#include "editor.hpp"
+#include "gui/randomizebutton.hpp"
#include "version.hpp"
#include
@@ -188,8 +189,18 @@ bool Editor::prepareUI()
miscLeft1, miscTop3, labelWidth, labelHeight, uiTextSize, ID::slideType,
slideTypeItems);
addCheckbox(
- miscLeft0 + int(labelWidth / 2), miscTop4, labelWidth, labelHeight, uiTextSize,
- "2x Sampling", ID::overSampling);
+ miscLeft1, miscTop4, labelWidth, labelHeight, uiTextSize, "2x Sampling",
+ ID::overSampling);
+
+ // Randomize button.
+ const auto randomButtonTop = miscTop4;
+ const auto randomButtonLeft = miscLeft0;
+ auto panicButton = new RandomizeButton(
+ CRect(
+ randomButtonLeft, randomButtonTop, randomButtonLeft + labelWidth - 2 * margin,
+ randomButtonTop + labelHeight),
+ this, 0, "Random", getFont(uiTextSize), palette, this);
+ frame->addView(panicButton);
// Oscillator.
constexpr auto oscLeft0 = gainLeft0 + 2 * labelWidth + 4 * margin;
diff --git a/ClangCymbal/source/gui/randomizebutton.hpp b/ClangCymbal/source/gui/randomizebutton.hpp
new file mode 100644
index 00000000..c552b309
--- /dev/null
+++ b/ClangCymbal/source/gui/randomizebutton.hpp
@@ -0,0 +1,311 @@
+// (c) 2023 Takamitsu Endo
+//
+// This file is part of ClangCymbal.
+//
+// ClangCymbal is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// ClangCymbal is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with ClangCymbal. If not, see .
+
+#pragma once
+
+#include "public.sdk/source/vst/vsteditcontroller.h"
+#include "vstgui/vstgui.h"
+
+#include "../../../common/gui/plugeditor.hpp"
+#include "../../../common/gui/style.hpp"
+#include "../parameter.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace VSTGUI {
+
+class RandomizeButton : public CControl {
+public:
+ std::string label;
+
+ RandomizeButton(
+ const CRect &size,
+ IControlListener *listener,
+ int32_t tag,
+ std::string label,
+ const SharedPointer &fontId,
+ Uhhyou::Palette &palette,
+ Steinberg::Vst::PlugEditor *editor)
+ : CControl(size, listener, tag)
+ , label(label)
+ , fontId(fontId)
+ , pal(palette)
+ , editor(editor)
+ {
+ if (editor) editor->remember();
+ }
+
+ ~RandomizeButton()
+ {
+ if (editor) editor->forget();
+ }
+
+ void draw(CDrawContext *pContext) override
+ {
+ pContext->setDrawMode(CDrawMode(CDrawModeFlags::kAntiAliasing));
+ CDrawContext::Transform t(
+ *pContext, CGraphicsTransform().translate(getViewSize().getTopLeft()));
+
+ // Border.
+ const double borderW = isMouseEntered ? 2 * borderWidth : borderWidth;
+ const double halfBorderWidth = int(borderW / 2.0);
+ pContext->setFillColor(isPressed ? pal.highlightButton() : pal.boxBackground());
+ pContext->setFrameColor(
+ isMouseEntered && !isPressed ? pal.highlightButton() : pal.border());
+ pContext->setLineWidth(borderW);
+ pContext->drawRect(
+ CRect(
+ halfBorderWidth, halfBorderWidth, getWidth() - halfBorderWidth,
+ getHeight() - halfBorderWidth),
+ kDrawFilledAndStroked);
+
+ // Text
+ pContext->setFont(fontId);
+ pContext->setFontColor(pal.foreground());
+ pContext->drawString(
+ label.c_str(), CRect(0, 0, getWidth(), getHeight()), kCenterText);
+ }
+
+ void onMouseEnterEvent(MouseEnterEvent &event) override
+ {
+ isMouseEntered = true;
+ invalid();
+ event.consumed = true;
+ }
+
+ void onMouseExitEvent(MouseExitEvent &event) override
+ {
+ if (value == 1.0f) {
+ value = 0.0f;
+ }
+ isPressed = false;
+ isMouseEntered = false;
+ invalid();
+ event.consumed = true;
+ }
+
+ void onMouseDownEvent(MouseDownEvent &event) override
+ {
+ using ID = Steinberg::Synth::ParameterID::ID;
+ using Pv = Steinberg::Vst::ParamValue;
+
+ if (!event.buttonState.isLeft()) return;
+ isPressed = true;
+ value = 1.0f;
+
+ if (editor) {
+ using Rng = std::mt19937_64;
+
+ std::random_device source;
+ std::random_device::result_type
+ random_data[(Rng::state_size - 1) / sizeof(source()) + 1];
+ std::generate(std::begin(random_data), std::end(random_data), std::ref(source));
+ std::seed_seq seeds(std::begin(random_data), std::end(random_data));
+ Rng rng(seeds);
+
+ std::uniform_real_distribution uniform{Pv(0), Pv(1)};
+ std::uniform_real_distribution uniformUpperHalf{Pv(0.5), Pv(1)};
+ std::uniform_real_distribution uniformLowerHalf{Pv(0), Pv(0.5)};
+ setParam(ID::impulseGain, uniform(rng));
+ // setParam(ID::oscGain, uniform(rng));
+ setParam(ID::oscNoisePulseRatio, uniform(rng));
+ setParam(ID::oscAttack, uniformLowerHalf(rng));
+ setParam(ID::oscDecay, uniformUpperHalf(rng));
+ setParam(ID::oscDensityHz, uniformUpperHalf(rng));
+ setParam(ID::oscDensityKeyFollow, uniform(rng));
+ setParam(ID::oscNoiseDecay, uniform(rng));
+ setParam(ID::oscBounce, uniform(rng));
+ setParam(ID::oscBounceCurve, uniform(rng));
+ setParam(ID::oscJitter, uniform(rng));
+ setParam(ID::oscPulseAmpRandomness, uniform(rng));
+ // setParam(ID::oscLowpassCutoffSemi, uniform(rng));
+ setParam(ID::oscLowpassQ, uniform(rng));
+ setParam(ID::oscLowpassKeyFollow, uniform(rng));
+
+ setParam(ID::fdnMatrixIdentityAmount, uniform(rng));
+ setParam(ID::fdnFeedback, uniform(rng));
+ setParam(ID::fdnOvertoneAdd, uniform(rng));
+ setParam(ID::fdnOvertoneMul, uniform(rng));
+ setParam(ID::fdnOvertoneOffset, uniform(rng));
+ setParam(ID::fdnOvertoneModulo, uniform(rng));
+ // setParam(ID::fdnOvertoneRandomness, uniform(rng));
+ setParam(ID::fdnInterpRate, uniform(rng));
+ setParam(ID::fdnInterpLowpassSecond, uniform(rng));
+ setParam(ID::fdnSeed, uniform(rng));
+ // setParam(ID::fdnRandomizeRatio, uniform(rng));
+
+ auto lpCut = generateFilterTable(rng);
+ auto lpQ = generateFilterTable(rng);
+ auto hpCut = generateFilterTable(rng);
+ auto hpQ = generateFilterTable(rng);
+ for (int idx = 0; idx < fdnMatrixSize; ++idx) {
+ setParam(ID::fdnLowpassCutoffSemiOffset0 + idx, lpCut[idx]);
+ setParam(ID::fdnLowpassQOffset0 + idx, lpQ[idx]);
+ setParam(ID::fdnHighpassCutoffSemiOffset0 + idx, hpCut[idx]);
+ setParam(ID::fdnHighpassQOffset0 + idx, hpQ[idx]);
+ }
+
+ auto lpSemi = uniform(rng);
+ auto hpSemi = uniform(rng);
+ if (lpSemi < hpSemi) std::swap(lpSemi, hpSemi);
+
+ setParam(ID::fdnLowpassCutoffSemi, lpSemi);
+ setParam(ID::fdnLowpassCutoffSlope, uniform(rng));
+ setParam(ID::fdnLowpassQ, uniform(rng));
+ setParam(ID::fdnLowpassQSlope, uniform(rng));
+ // setParam(ID::fdnLowpassKeyFollow, uniform(rng));
+ setParam(ID::fdnHighpassCutoffSemi, hpSemi);
+ setParam(ID::fdnHighpassCutoffSlope, uniform(rng));
+ setParam(ID::fdnHighpassQ, uniform(rng));
+ setParam(ID::fdnHighpassQSlope, uniform(rng));
+ // setParam(ID::fdnHighpassKeyFollow, uniform(rng));
+
+ auto table = generateEnvTable(rng);
+ for (int idx = 0; idx < nModEnvelopeWavetable; ++idx) {
+ setParam(ID::modEnvelopeWavetable0 + idx, table[idx]);
+ }
+ setParam(ID::modEnvelopeTime, uniform(rng));
+ setParam(ID::modEnvelopeToFdnLowpassCutoff, uniform(rng));
+ setParam(ID::modEnvelopeToFdnHighpassCutoff, uniform(rng));
+ setParam(ID::modEnvelopeToFdnPitch, uniform(rng));
+ setParam(ID::modEnvelopeToFdnOvertoneAdd, uniform(rng));
+ setParam(ID::modEnvelopeToOscJitter, uniform(rng));
+ setParam(ID::modEnvelopeToOscNoisePulseRatio, uniform(rng));
+
+ // setParam(ID::tremoloMix, uniform(rng));
+ setParam(ID::tremoloDepth, uniform(rng));
+ setParam(ID::tremoloDelayTime, uniform(rng));
+ setParam(ID::tremoloModulationToDelayTimeOffset, uniform(rng));
+ setParam(ID::tremoloModulationRateHz, uniform(rng));
+ }
+
+ invalid();
+ event.consumed = true;
+ }
+
+ void onMouseUpEvent(MouseUpEvent &event) override
+ {
+ if (isPressed) {
+ isPressed = false;
+ value = 0.0f;
+ invalid();
+ }
+ event.consumed = true;
+ }
+
+ void onMouseCancelEvent(MouseCancelEvent &event) override
+ {
+ if (isPressed) {
+ isPressed = false;
+ value = 0;
+ invalid();
+ }
+ isMouseEntered = false;
+ event.consumed = true;
+ }
+
+ void setBorderWidth(CCoord width) { borderWidth = width < 0 ? 0 : width; }
+
+ CLASS_METHODS(RandomizeButton, CControl);
+
+private:
+ // Before calling check if `editor` is not nullptr.
+ inline void setParam(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue value)
+ {
+ editor->valueChanged(id, value);
+ editor->updateUI(id, value);
+ }
+
+ template inline auto generateFilterTable(Rng &rng)
+ {
+ using Pv = Steinberg::Vst::ParamValue;
+ constexpr int length = int(fdnMatrixSize);
+
+ std::array table{};
+
+ std::uniform_real_distribution uniform{Pv(-1), Pv(1)};
+ std::generate(table.begin(), table.end(), [&]() { return uniform(rng); });
+
+ // Bidirectional filtering.
+ Pv v1 = 0;
+ for (int idx = 0; idx < length; ++idx) {
+ v1 += Pv(0.3) * (table[idx] - v1);
+ table[idx] = v1;
+ }
+ v1 = 0;
+ for (int idx = length - 1; idx >= 0; --idx) {
+ v1 += Pv(0.3) * (table[idx] - v1);
+ table[idx] = v1;
+ }
+
+ // Random scaling.
+ Pv max = 0;
+ for (int i = 0; i < length; ++i) max = std::max(max, std::abs(table[i]));
+ std::uniform_real_distribution uniform01{Pv(0), Pv(1)};
+ if (max != 0) {
+ auto scaler = uniform01(rng) / max;
+ for (int i = 0; i < length; ++i) table[i] *= scaler;
+ }
+
+ // Normalze range from [-1, 1] to [0, 1].
+ for (int i = 0; i < length; ++i) table[i] = Pv(0.5) * (Pv(1) + table[i]);
+
+ return table;
+ }
+
+ template inline auto generateEnvTable(Rng &rng)
+ {
+ using Pv = Steinberg::Vst::ParamValue;
+
+ std::array table{};
+
+ std::uniform_real_distribution uniform{Pv(0), Pv(1)};
+ std::generate(table.begin(), table.end(), [&]() { return uniform(rng); });
+
+ Pv v1 = 0;
+ Pv v2 = 0;
+ for (int idx = nModEnvelopeWavetable - 1; idx >= 0; --idx) {
+ v1 += Pv(0.3) * (table[idx] - v1);
+ v2 += Pv(0.3) * (v1 - v2);
+ table[idx] = v2;
+ }
+
+ const auto iter = std::max_element(table.begin(), table.end());
+ if (iter == table.end()) return table;
+ auto max = *iter;
+ if (max == 0) return table;
+ for (int idx = 0; idx < nModEnvelopeWavetable; ++idx) table[idx] /= max;
+
+ return table;
+ }
+
+ Steinberg::Vst::PlugEditor *editor = nullptr;
+
+ SharedPointer fontId;
+ Uhhyou::Palette &pal;
+
+ CCoord borderWidth = 1.0;
+
+ bool isPressed = false;
+ bool isMouseEntered = false;
+};
+
+} // namespace VSTGUI
diff --git a/ClangCymbal/source/version.hpp b/ClangCymbal/source/version.hpp
index d73a8b9f..6606f4e3 100644
--- a/ClangCymbal/source/version.hpp
+++ b/ClangCymbal/source/version.hpp
@@ -29,11 +29,11 @@
#define SUB_VERSION_STR "1"
#define SUB_VERSION_INT 1
-#define RELEASE_NUMBER_STR "8"
-#define RELEASE_NUMBER_INT 8
+#define RELEASE_NUMBER_STR "9"
+#define RELEASE_NUMBER_INT 9
-#define BUILD_NUMBER_STR "11"
-#define BUILD_NUMBER_INT 11
+#define BUILD_NUMBER_STR "12"
+#define BUILD_NUMBER_INT 12
#define FULL_VERSION_STR \
MAJOR_VERSION_STR "." SUB_VERSION_STR "." RELEASE_NUMBER_STR "." BUILD_NUMBER_STR