diff --git a/.gitignore b/.gitignore index 9a8170c..8b7dd71 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ cmake-build-debug cmake-build-release release-scripts build-release +BUILDROOT/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index a5fdfff..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "JUCE"] - path = JUCE - url = https://github.com/juce-framework/JUCE.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 0104d4d..664c507 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,11 +17,26 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "Minimum OS X deployment version" FORCE) -project(CRYPT_SYNTH_PLUGIN VERSION 2.1.0) +project(Crypt2 VERSION 2.1.0) -add_subdirectory(JUCE) +# Enable compile commands export for IDEs +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -juce_add_plugin(Crypt2SynthPlugin +include(cmake/CPM.cmake) +CPMAddPackage("gh:/juce-framework/JUCE#8.0.11") +CPMAddPackage("gh:/free-audio/clap-juce-extensions#main") +# +# Copy plugins after build option +option(CRYPT_COPY_PLUGIN_AFTER_BUILD "Copy JUCE Plugins after built" ON) + +# ==================== Add Plugin ======================= +# Build LV2 only on Linux +set(JUCE_FORMATS AU VST3 Standalone) +if(UNIX AND NOT APPLE) + list(APPEND JUCE_FORMATS LV2) +endif() + +juce_add_plugin(${PROJECT_NAME} VERSION 2.1.0 # Set this if the plugin version is different to the project version ICON_BIG ${CMAKE_CURRENT_SOURCE_DIR}/resources/crypt-icon.png # ICON_* arguments specify a path to an image file to use as an icon for the Standalone ICON_SMALL ${CMAKE_CURRENT_SOURCE_DIR}/resources/crypt-icon.png @@ -31,24 +46,31 @@ juce_add_plugin(Crypt2SynthPlugin NEEDS_MIDI_OUTPUT FALSE # Does the plugin need midi output? IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect? EDITOR_WANTS_KEYBOARD_FOCUS FALSE # Does the editor need keyboard focus? - COPY_PLUGIN_AFTER_BUILD TRUE # Should the plugin be installed to a default location after building? + COPY_PLUGIN_AFTER_BUILD ${CRYPT_COPY_PLUGIN_AFTER_BUILD} # Should the plugin be installed to a default location after building? PLUGIN_MANUFACTURER_CODE Vitl # A four-character manufacturer id with at least one upper-case character PLUGIN_CODE Crp2 # A unique four-character plugin id with at least one upper-case character DESCRIPTION "Hyper-Unison Synthesiser from Bow Church/Vitling" VST3_CATEGORIES "Instrument Synth Stereo" AU_MAIN_TYPE "kAudioUnitType_MusicDevice" - FORMATS VST3 AU Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 + FORMATS ${JUCE_FORMATS} BUNDLE_ID "xyz.vitling.plugins.crypt2" HARDENED_RUNTIME_ENABLED TRUE - PRODUCT_NAME "Crypt2") # The name of the final executable, which can differ from the target name + LV2URI "https://github.com/vitling/crypt" + PRODUCT_NAME ${PROJECT_NAME}) # The name of the final executable, which can differ from the target name -juce_generate_juce_header(Crypt2SynthPlugin) +# ==================== CLAP ======================= +clap_juce_extensions_plugin(TARGET ${PROJECT_NAME} + CLAP_ID "xyz.vitling.plugins.crypt2" + CLAP_FEATURES instrument "virtual analog" +) -target_sources(Crypt2SynthPlugin PRIVATE +juce_generate_juce_header(${PROJECT_NAME}) + +target_sources(${PROJECT_NAME} PRIVATE src/CryptPlugin.cpp) -target_compile_definitions(Crypt2SynthPlugin +target_compile_definitions(${PROJECT_NAME} PUBLIC JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 @@ -56,13 +78,35 @@ target_compile_definitions(Crypt2SynthPlugin # We don't have to display the splash screen since we're using JUCE # under the GPL - JUCE_DISPLAY_SPLASH_SCREEN=0) + # JUCE_DISPLAY_SPLASH_SCREEN=0 +) -juce_add_binary_data(Crypt2SynthPluginData SOURCES resources/Gothica-Book.ttf resources/bg.jpg resources/presets.xml resources/keyboard-icon.png) -set_target_properties(Crypt2SynthPluginData PROPERTIES POSITION_INDEPENDENT_CODE ON) +juce_add_binary_data(${PROJECT_NAME}Data SOURCES resources/Gothica-Book.ttf resources/bg.jpg resources/presets.xml resources/keyboard-icon.png) +set_target_properties(${PROJECT_NAME}Data PROPERTIES POSITION_INDEPENDENT_CODE ON) -target_link_libraries(Crypt2SynthPlugin PRIVATE - Crypt2SynthPluginData +target_link_libraries(${PROJECT_NAME} PRIVATE + ${PROJECT_NAME}Data juce::juce_audio_utils juce::juce_dsp) - \ No newline at end of file + + +# ==================== Installation ======================= +include(GNUInstallDirs) + +# Install the standalone application +if(TARGET ${PROJECT_NAME}_Standalone) + install(TARGETS ${PROJECT_NAME}_Standalone DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +# Install the native Juce plugins +foreach(format VST3 LV2) + if(TARGET ${PROJECT_NAME}_${format}) + get_target_property(output ${PROJECT_NAME}_${format} JUCE_PLUGIN_ARTEFACT_FILE) + install(DIRECTORY ${output} DESTINATION ${CMAKE_INSTALL_LIBDIR}/$) + endif() +endforeach() + +# Install the CLAP plugin +if(TARGET ${PROJECT_NAME}_CLAP) + install(TARGETS ${PROJECT_NAME}_CLAP DESTINATION ${CMAKE_INSTALL_LIBDIR}/clap) +endif() diff --git a/JUCE b/JUCE deleted file mode 160000 index 4f43011..0000000 --- a/JUCE +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4f43011b96eb0636104cb3e433894cda98243626 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a2c616d --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +NAME = Crypt2 +BUILD_DIR = build + BUILD_TYPE ?= Release +SETUP_OPTIONS = -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCRYPT_COPY_PLUGIN_AFTER_BUILD=OFF +BUILD_OPTIONS = --config ${BUILD_TYPE} ${TARGET} +BUILDROOT = $(shell pwd)/BUILDROOT + +all: build + +clean: + @echo "Cleaning build directory : ${BUILD_DIR}" + @rm -rf ${BUILD_DIR} +.PHONY: clean + +setup: + cmake -B ${BUILD_DIR} ${SETUP_OPTIONS} +.PHONY: setup + +build: setup + cmake --build ${BUILD_DIR} ${BUILD_OPTIONS} +.PHONY: build + +install: build + @echo "Installing ${NAME} to ${BUILDROOT}" + rm -rf ${BUILDROOT} + mkdir -p ${BUILDROOT} + cmake --install ${BUILD_DIR} --prefix ${BUILDROOT}/usr/ +.PHONY: install + +run-stanalone: + @echo "Running standalone" + ${BUILDROOT}/usr/bin/${NAME} +.PHONY: run-stanalone diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..8474873 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.42.0) +set(CPM_HASH_SUM "2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/src/CryptAudioProcessorEditor.hpp b/src/CryptAudioProcessorEditor.hpp index 0bdb30f..b68187d 100644 --- a/src/CryptAudioProcessorEditor.hpp +++ b/src/CryptAudioProcessorEditor.hpp @@ -17,26 +17,14 @@ #pragma once #include +#include #include "CryptAudioProcessor.hpp" const Colour CRYPT_BLUE = Colour::fromString("ff60a5ca"); -class EmbeddedFonts { -private: - Font gothicaBook; - -public: - EmbeddedFonts() { - gothicaBook = Font(Typeface::createSystemTypefaceFor(BinaryData::GothicaBook_ttf, BinaryData::GothicaBook_ttfSize)); - } - const Font& getGothicaBook() const { - return gothicaBook; - } -}; - -EmbeddedFonts& getFonts() +juce::Font& getFonts() { - static EmbeddedFonts fonts; + static Font fonts = juce::Font(FontOptions(Typeface::createSystemTypefaceFor(BinaryData::GothicaBook_ttf, BinaryData::GothicaBook_ttfSize))); return fonts; } @@ -63,8 +51,8 @@ class CryptLookAndFeel: public LookAndFeel_V4 { this->setColour(MidiKeyboardComponent::ColourIds::textLabelColourId, CRYPT_BLUE); this->setColour(MidiKeyboardComponent::ColourIds::shadowColourId, Colours::transparentWhite); - this->setDefaultSansSerifTypeface(fonts.getGothicaBook().getTypefacePtr()); - + this->setDefaultSansSerifTypeface(fonts.getTypefacePtr()); + } // After numerous attempts to remove the box from the value label with conventional methods I just gave up and overrid it here @@ -159,9 +147,9 @@ class LabelledDial: public Component { slider.setSliderStyle(Slider::SliderStyle::RotaryHorizontalVerticalDrag); if (suffix.isNotEmpty()) { slider.setTextValueSuffix(suffix); - + } - + slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 20); if (labelText.isEmpty()) { @@ -170,7 +158,7 @@ class LabelledDial: public Component { label.setText(labelText,NotificationType::dontSendNotification); } label.setJustificationType(Justification::centred); - label.setFont(fonts.getGothicaBook().withHeight(20)); + label.setFont(fonts.withHeight(20)); addAndMakeVisible(slider); addAndMakeVisible(label); } @@ -200,13 +188,13 @@ class ControlGroup: public GroupComponent { if (visualiser) { addAndMakeVisible(*visualiser); } - + setText(title); } void resized() override { auto contentsBounds = getLocalBounds().reduced(20); - + int width = contentsBounds.getWidth(); int height = 80; @@ -216,14 +204,14 @@ class ControlGroup: public GroupComponent { if (visualiser) { visualiser->setBounds(contentsBounds); } - + float controlWidth = width / nControls; for (int i = 0 ; i < nControls; i++) { auto &c = controls[i]; c->setBounds({controlBounds.getX() + i * (int)controlWidth, controlBounds.getY(), (int)controlWidth, height}); } } - + }; class ADSREditor: public GroupComponent { @@ -289,8 +277,8 @@ class ADSREditor: public GroupComponent { graphics.setColour(CRYPT_BLUE); - - Point start(0, yScale+5), + + Point start(0, yScale+5), peak(xScale * attack, 5), sus1(xScale * (attack + decay), yScale * (1-sustain)+5), sus2(xScale * (attack + decay + 1.0), yScale * (1-sustain)+5), @@ -299,13 +287,13 @@ class ADSREditor: public GroupComponent { float curve = 0; Path path; path.startNewSubPath({0.0f, yScale+5}); - + path.cubicTo(start.translated(curve,0), peak.translated(-curve, 0), peak); path.cubicTo(peak.translated(curve,0), sus1.translated(-curve,0), sus1); path.cubicTo(sus1.translated(curve,0), sus2.translated(-curve,0), sus2); path.cubicTo(sus2.translated(curve,0), end.translated(-curve,0), end); - + PathStrokeType s(2); @@ -385,7 +373,7 @@ class DelayDisplay: public Component, public AudioProcessorValueTreeState::Liste delayFeedback = newValue; } else if (parameterID == CryptParameters::DelayMix) { delayMix = newValue; - } + } triggerAsyncUpdate(); } void paint(Graphics &g) override { @@ -490,11 +478,11 @@ class WaveformDisplay: public Component, Timer { WaveformDisplay(SharedBuffer & buffer): buffer(buffer), img(Image::PixelFormat::ARGB, 200,500,true) { i.setImage(img); - + addAndMakeVisible(i); startTimerHz(30); } - + void resized() override { i.setBounds(getLocalBounds()); @@ -512,12 +500,12 @@ class WaveformDisplay: public Component, Timer { void update() { img.clear(img.getBounds(), Colours::transparentBlack); Graphics g(i.getImage()); - + buffer.read(); auto & displayBuffer = buffer.get(); - + auto area = g.getClipBounds(); - + Path waveformPath; waveformPath.startNewSubPath(area.getCentreX(), 0); @@ -527,7 +515,7 @@ class WaveformDisplay: public Component, Timer { max = abs(displayBuffer[i]); } } - + float scaleFactor = 0.7f / max; float dbSize = displayBuffer.size(); @@ -543,7 +531,7 @@ class WaveformDisplay: public Component, Timer { g.setColour (Colours::white); g.strokePath(waveformPath, PathStrokeType(2.0f)); - + const MessageManagerLock mml; if (mml.lockWasGained()) { repaint(); @@ -560,7 +548,7 @@ class KeyboardToggleButton: public Button { protected: void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) { auto mainColor = CRYPT_BLUE; - + g.setColour(CRYPT_BLUE.withAlpha((isEnabled ? 0.8f : 0.4f) + (shouldDrawButtonAsHighlighted ? 0.1f : 0.0f) + (shouldDrawButtonAsDown ? 0.1f : 0.0f))); g.drawImage(keyboardIcon, getLocalBounds().toFloat().reduced(10), RectanglePlacement::stretchToFit, true); @@ -671,7 +659,7 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis auto& fonts = getFonts(); pluginTitle.setText("CRYPT",NotificationType::dontSendNotification); - pluginTitle.setFont(fonts.getGothicaBook().withHeight(40)); + pluginTitle.setFont(fonts.withHeight(40)); pluginTitle.setJustificationType(Justification::horizontallyCentred); setSize(1000,625); @@ -707,10 +695,10 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis bowchurch.setURL(URL{"https://www.vitling.xyz/ext/crypt/bowchurch"}); vitling.setURL(URL{"https://www.vitling.xyz/ext/crypt/vitling"}); donate.setURL(URL{"https://www.vitling.xyz/ext/crypt/donate"}); - - bowchurch.setFont(fonts.getGothicaBook().withHeight(24.0f), false); - vitling.setFont(fonts.getGothicaBook().withHeight(24.0f), false); - donate.setFont(fonts.getGothicaBook().withHeight(24.0f), false); + + bowchurch.setFont(fonts.withHeight(24.0f), false); + vitling.setFont(fonts.withHeight(24.0f), false); + donate.setFont(fonts.withHeight(24.0f), false); bowchurch.setTooltip(""); vitling.setTooltip(""); @@ -722,7 +710,7 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis save.setButtonText("Save"); load.setButtonText("Load"); - + addAndMakeVisible(save); addAndMakeVisible(load); @@ -730,7 +718,7 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis load.addListener(this); keyboardButton.addListener(this); - + addAndMakeVisible(keyboardButton); addAndMakeVisible(keyboard); keyboard.setVisible(keyboardIsVisible); @@ -788,7 +776,7 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis void openSaveDialog() { fileChooser = std::make_unique("Save preset", File::getSpecialLocation(File::userHomeDirectory), "*.crypt"); auto flags = FileBrowserComponent::saveMode; - + fileChooser->launchAsync(flags, [this] (const FileChooser& chooser) { File file (chooser.getResult()); if (file.getFileName().isEmpty()) { @@ -817,7 +805,7 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis auto keyboardBounds = totalBounds.removeFromBottom(keyboardHeight); keyboard.setBounds(keyboardBounds); } - + Rectangle titleText = {titleBar.getCentreX()-102, titleBar.getY(), 200, titleBar.getHeight()}; pluginTitle.setBounds(titleText); @@ -832,8 +820,8 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis save.setBounds(Rectangle{270,0,70,50}.reduced(10)); load.setBounds(Rectangle{200,0,70,50}.reduced(10)); - - + + auto leftBounds = totalBounds.removeFromLeft(400); auto rightBounds = totalBounds.removeFromRight(400); @@ -848,7 +836,7 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis leftLayout.setItemLayout(1, 200,200,200); leftLayout.setItemLayout(2, 125,125,125); leftLayout.layOutComponents(leftComponents, 3, leftBounds.getX(), leftBounds.getY(), leftBounds.getWidth(), leftBounds.getHeight(), true, true); - + auto envBounds = ampEnv.getBounds(); auto ampEnvBounds = envBounds.removeFromLeft(envBounds.getWidth() / 2); ampEnv.setBounds(ampEnvBounds); @@ -871,4 +859,4 @@ class CryptAudioProcessorEditor: public AudioProcessorEditor, public Button::Lis auto image = ImageCache::getFromMemory(BinaryData::bg_jpg, BinaryData::bg_jpgSize); graphics.drawImageWithin(image, 0, 0, getWidth(), getHeight(), RectanglePlacement::fillDestination); } -}; \ No newline at end of file +};