diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..61e646b
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,83 @@
+# Google C/C++ Code Style settings
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+# Author: Kehan Xue, kehan.xue (at) gmail.com
+
+Language: Cpp
+BasedOnStyle: Google
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: None
+AlignOperands: Align
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: Never # To avoid conflict, set this "Never" and each "if statement" should include brace when coding
+AllowShortLambdasOnASingleLine: Inline
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterStruct: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeColon
+BreakInheritanceList: BeforeColon
+ColumnLimit: 80
+CompactNamespaces: false
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false # Make sure the * or & align on the left
+EmptyLineBeforeAccessModifier: LogicalBlock
+FixNamespaceComments: true
+IncludeBlocks: Preserve
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+KeepEmptyLinesAtTheStartOfBlocks: true
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PointerAlignment: Left
+ReflowComments: false
+# SeparateDefinitionBlocks: Always # Only support since clang-format 14
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: c++11
+TabWidth: 4
+UseTab: Always
\ No newline at end of file
diff --git a/.github/workflows/cmake-linux-platform.yml b/.github/workflows/cmake-linux-platform.yml
new file mode 100644
index 0000000..b3845ec
--- /dev/null
+++ b/.github/workflows/cmake-linux-platform.yml
@@ -0,0 +1,41 @@
+# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage.
+# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml
+name: CMake on a single platform (GNU/Linux)
+
+on:
+ push:
+ branches: [ "dev" ]
+ pull_request:
+ branches: [ "dev" ]
+
+env:
+ BUILD_TYPE: Release
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false # Don't fetch submodules
+ fetch-depth: 1
+
+ - name: Get JUCE submodule
+ run: |
+ git submodule init JUCE
+ git submodule update --depth 1 JUCE
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update -y --no-install-recommends
+ sudo apt-get install -y --no-install-recommends libasound2-dev libjack-jackd2-dev ladspa-sdk libcurl4-openssl-dev libfreetype-dev libfontconfig1-dev libx11-dev libxcomposite-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev libwebkit2gtk-4.1-dev libglu1-mesa-dev mesa-common-dev
+
+ - name: Configure CMake
+ run: |
+ mkdir build/
+ cd build/
+ cmake .. -G "Unix Makefiles"
+
+ - name: Build
+ run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
diff --git a/.gitignore b/.gitignore
index 4a5b5e3..c3678a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,13 +2,19 @@
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
+.idea/editor.xml
# Compiled sources
-cmake-build-debug
-build-ios
+cmake-build-debug/
+build-ios/
+build/
+
+# JUCE dependencies
+JUCE/
# macOS specific
.DS_Store
# Windows specific
Thumbs.db
+/.idea/vcs.xml
diff --git a/.gitmodules b/.gitmodules
index 8851e64..cb168bc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "docs"]
- path = docs
- url = git@github.com:k147-studio/docs.git
[submodule "JUCE"]
path = JUCE
- url = https://github.com/juce-framework/JUCE.git
+ url = https://github.com/k147-studio/JUCE.git
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/editor.xml b/.idea/editor.xml
deleted file mode 100644
index e62c182..0000000
--- a/.idea/editor.xml
+++ /dev/null
@@ -1,583 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 79b3c94..2bc1fda 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,6 @@
-
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 154f836..480f52b 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,7 +1,8 @@
-
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dfc0798..6160d8a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,15 +31,11 @@ juce_add_gui_app(kAmp
# DOCUMENT_EXTENSIONS ... # Specify file extensions that should be associated with this app
BUNDLE_ID = "eu.k-147.kAmp"
COMPANY_NAME "K-147"
- PRODUCT_NAME "kAmp")
+ PRODUCT_NAME "kAmp"
+ ICON_BIG "resources/images/kamp_logo_with_background.png"
+ ICON_SMALL "resources/images/kamp_logo_with_background.png")
-# `juce_generate_juce_header` will create a JuceHeader.h for a given target, which will be generated
-# into your build tree. This should be included with `#include `. The include path for
-# this header will be automatically added to the target. The main function of the JuceHeader is to
-# include all your JUCE module headers; if you're happy to include module headers directly, you
-# probably don't need to call this.
-
-# juce_generate_juce_header(kAmp)
+juce_generate_juce_header(kAmp)
# `target_sources` adds source files to a target. We pass the target that needs the sources as the
# first argument, then a visibility parameter for the sources which should normally be PRIVATE.
@@ -53,6 +49,25 @@ file(
src/*.cpp
)
+if(APPLE)
+ # Sur macOS, copie dans le bundle app (Contents/Resources)
+ add_custom_command(TARGET kAmp POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ ${CMAKE_SOURCE_DIR}/resources
+ $/../Resources/resources
+ )
+else()
+ # Sur Linux/Windows, copie dans le dossier de build à côté de l'exe
+ add_custom_command(TARGET kAmp POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ ${CMAKE_SOURCE_DIR}/resources
+ $/resources
+ )
+endif()
+
+
+
+
target_sources(kAmp
PRIVATE
${SOURCES})
@@ -73,7 +88,7 @@ target_compile_definitions(kAmp
# JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them.
JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_gui_app` call
JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_gui_app` call
- JUCE_IOS=1
+ # JUCE_IOS=1
JUCE_APPLICATION_NAME_STRING="$"
JUCE_APPLICATION_VERSION_STRING="$")
@@ -95,7 +110,10 @@ target_compile_definitions(kAmp
target_link_libraries(kAmp
PRIVATE
# GuiAppData # If we'd created a binary data target, we'd link to it here
+ juce::juce_audio_basics
+ juce::juce_audio_utils
juce::juce_gui_extra
+ juce::juce_dsp
PUBLIC
juce::juce_recommended_config_flags
juce::juce_recommended_lto_flags
diff --git a/JUCE b/JUCE
index 51a8a6d..d6181bd 160000
--- a/JUCE
+++ b/JUCE
@@ -1 +1 @@
-Subproject commit 51a8a6d7aeae7326956d747737ccf1575e61e209
+Subproject commit d6181bde38d858c283c3b7bf699ce6340c050b5d
diff --git a/README.md b/README.md
index b7dfba9..f39e55b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# kAmp
+[](https://app.codacy.com/gh/k147-studio/kAmp/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
+
## How to build
### Prerequisites
diff --git a/docs b/docs
deleted file mode 160000
index 80463c5..0000000
--- a/docs
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 80463c5e6fd68b4b4966701ce6b1dfa24e52da98
diff --git a/include/AbstractEffect.h b/include/AbstractEffect.h
new file mode 100644
index 0000000..03437a6
--- /dev/null
+++ b/include/AbstractEffect.h
@@ -0,0 +1,123 @@
+#pragma once
+#include
+#include "IEffect.h"
+#include
+
+/**
+ * @brief Abstract class that represents an effect by determining
+ * the minimum caracteristics an effect should have.
+ * Implements the IEffect interface.
+ */
+class AbstractEffect : public IEffect {
+ public:
+ /**
+ * @brief The name of the effect.
+ */
+ std::string effectName;
+
+ /**
+ * Tells if the effect is enabled or not.
+ */
+ bool* isEnabled = new bool(true);
+
+ /**
+ * @brief Initializes a new instance of the AbstractEffect class.
+ */
+ AbstractEffect() = default;
+
+ /**
+ * @brief Destroys the instance of the AbstractEffect class.
+ */
+ ~AbstractEffect() override = default;
+
+ /**
+ * @brief Gets the id of the effect.
+ * @return The id of the effect.
+ */
+ int getId();
+
+ /**
+ * @brief Gets the name of the author of the effect.
+ * @return The name of the author of the effect.
+ */
+ std::string getAuthor();
+
+ /**
+ * @brief Gets the description of the effect.
+ * @return The description of the effect.
+ */
+ std::string getDescription();
+
+ /**
+ * @brief Gets the version of the effect.
+ * @return The version of the effect.
+ */
+ std::string getVersion();
+
+ /**
+ * @brief Compares the effect with another given effect.
+ * @param effect The effect to compare with.
+ * @return True if the effect is equal to the given effect, false otherwise.
+ */
+ virtual bool operator==(const AbstractEffect* effect) = 0;
+
+ /**
+ * @brief Applies the effect to the given audio buffer.
+ * @param bufferToFill The audio buffer to apply the effect to.
+ */
+ void apply(const juce::AudioSourceChannelInfo &bufferToFill) override = 0;
+
+ /**
+ * @brief Serializes the effect to a JSON object.
+ * @return JSON object containing serialized effect data.
+ */
+ [[nodiscard]] virtual var toJSON() const {
+ const auto obj = new DynamicObject();
+ obj->setProperty("id", id);
+ obj->setProperty("author", var(author));
+ obj->setProperty("description", var(description));
+ obj->setProperty("version", var(version));
+ obj->setProperty("type", getEffectType());
+ return obj;
+ }
+
+ /**
+ * @brief Deserializes the effect from a JSON object.
+ * @param json JSON object containing serialized effect data.
+ */
+ virtual void fromJSON(const var &json) {
+ if (const auto *obj = json.getDynamicObject()) {
+ id = obj->getProperty("id");
+ author = obj->getProperty("author").toString().toStdString();
+ description = obj->getProperty("description").toString().toStdString();
+ version = obj->getProperty("version").toString().toStdString();
+ }
+ }
+
+ /**
+ * @brief Gets the type name of the effect for serialization purposes.
+ * @return A string representing the effect type.
+ */
+ [[nodiscard]] virtual String getEffectType() const = 0;
+
+ private:
+ /**
+ * @brief The id of the effect.
+ */
+ int id;
+
+ /**
+ * @brief The name of the author of the effect.
+ */
+ std::string author;
+
+ /**
+ * @brief The description of the effect.
+ */
+ std::string description;
+
+ /**
+ * @brief The version of the effect.
+ */
+ std::string version;
+};
\ No newline at end of file
diff --git a/include/AccountComponent.h b/include/AccountComponent.h
new file mode 100644
index 0000000..bd2756a
--- /dev/null
+++ b/include/AccountComponent.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include
+
+class AccountComponent : public Component {
+public:
+ AccountComponent();
+ void paint(Graphics&) override;
+ void resized() override;
+
+private:
+ void apiResponseReceived(const String& content);
+ void saveSettings();
+ void importSettings();
+
+ void setupLabels();
+ void setupButtons();
+ void setupGrid();
+
+ Label titleLabel{"titleLabel", "Mon compte"};
+ Label responseLabel{"responseLabel", "En attente de réponse..."};
+
+ Label emailLabel{"emailLabel", "Adresse mail :"};
+ Label usernameLabel{"usernameLabel", "Nom d'utilisateur :"};
+ Label emailValueLabel{"emailValueLabel", "utilisateur@example.com"};
+ Label usernameValueLabel{"usernameValueLabel", "NomUtilisateur"};
+
+ TextButton saveButton{"Sauvegarder les réglages"};
+ TextButton importButton{"Importer les réglages"};
+
+ Grid grid;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AccountComponent)
+};
\ No newline at end of file
diff --git a/include/ApiClient.h b/include/ApiClient.h
new file mode 100644
index 0000000..d3c7f0a
--- /dev/null
+++ b/include/ApiClient.h
@@ -0,0 +1,8 @@
+#pragma once
+
+class ApiClient {
+public:
+ using ReplyFunc = std::function;
+
+ static void runHTTP(const URL &url, const ReplyFunc &replyFunc);
+};
diff --git a/include/AudioBuffer.h b/include/AudioBuffer.h
new file mode 100644
index 0000000..554d259
--- /dev/null
+++ b/include/AudioBuffer.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include
+
+class AudioBuffer {
+ public:
+ AudioBuffer();
+ ~AudioBuffer();
+ juce::AudioSourceChannelInfo getBuffer();
+ private:
+ juce::AudioSourceChannelInfo bufferToFill;
+};
\ No newline at end of file
diff --git a/include/BasePedalComponent.h b/include/BasePedalComponent.h
new file mode 100644
index 0000000..3997706
--- /dev/null
+++ b/include/BasePedalComponent.h
@@ -0,0 +1,94 @@
+#pragma once
+#include "EffectComponent.h"
+#include "PedalPowerIndicatorComponent.h"
+#include "PedalSettingsLayoutComponent.h"
+
+/**
+ * @brief Base class for pedal components.
+ * This class provides a common interface and functionality for all pedal components.
+ */
+class BasePedalComponent : public EffectComponent {
+public:
+ /**
+ * @brief Initializes a new instance of the BasePedalComponent class.
+ * @param effect The effect associated with this pedal component.
+ */
+ BasePedalComponent(AbstractEffect* effect);
+
+ /**
+ * @brief Destroys the instance of the BasePedalComponent class.
+ */
+ ~BasePedalComponent() override;
+
+ /**
+ * @brief Paints the component.
+ * @param g The JUCE graphics context that paints the component.
+ */
+ void paint(Graphics& g) override;
+
+ /**
+ * @brief Resizes the component.
+ */
+ void resized() override;
+
+protected:
+ /**
+ * @brief The default width of the component.
+ */
+ const float DEFAULT_WIDTH = 200.0f;
+
+ /**
+ * @brief The default height of the component.
+ */
+ const float DEFAULT_HEIGHT = 300.0f;
+
+ /**
+ * @brief The main layout for the pedal component.
+ */
+ Grid pedalLayout;
+
+ /**
+ * @brief The primary color of the pedal component. Used as background color.
+ */
+ Colour primaryColor;
+
+ /**
+ * @brief The secondary color of the pedal component. Used as background color.
+ */
+ Colour secondaryColor;
+
+ /**
+ * @brief The layout for the pedal settings.
+ */
+ PedalSettingsLayoutComponent* settingsLayout;
+
+ /**
+ * @brief The label for the pedal name.
+ */
+ Label* pedalLabel;
+
+ /**
+ * @brief The button to enable/disable the pedal.
+ */
+ ImageButton enablePedalButton;
+
+ /**
+ * @brief The indicator for the pedal power status.
+ */
+ PedalPowerIndicatorComponent* isEnabledIndicator;
+
+ /**
+ * @brief Tells if the pedal is enabled or not.
+ */
+ bool* isEnabled;
+
+ /**
+ * @brief Called when the enable button is clicked.
+ */
+ void onEnableButtonClicked();
+
+ /**
+ * @brief Initializes the pedal layout to display it.
+ */
+ void initializePedal();
+};
diff --git a/include/BottomMenuBarComponent.h b/include/BottomMenuBarComponent.h
new file mode 100644
index 0000000..c8edfcb
--- /dev/null
+++ b/include/BottomMenuBarComponent.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include
+
+/**
+ * @brief Represents a graphical component that contains and displays the bottom menu bar.
+ */
+class BottomMenuBarComponent : public juce::Component {
+ public:
+ /**
+ * @brief Initializes a new instance of the BottomMenuBarComponent class.
+ */
+ BottomMenuBarComponent();
+
+ /**
+ * @brief Destroys the instance of the BottomMenuBarComponent class.
+ */
+ ~BottomMenuBarComponent() override;
+
+ /**
+ * @brief Determines how to display the component.
+ * @param g The JUCE graphics context that paints the component.
+ */
+ void paint(juce::Graphics &g) override;
+
+ /**
+ * @brief Determines what to do when the component is resized.
+ */
+ void resized() override;
+ private:
+ /**
+ * @brief The menu bar component that contains the menu items.
+ */
+ juce::MenuBarComponent menuBarComponent;
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BottomMenuBarComponent)
+};
\ No newline at end of file
diff --git a/include/ChorusEffect.h b/include/ChorusEffect.h
new file mode 100644
index 0000000..3382ba3
--- /dev/null
+++ b/include/ChorusEffect.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include
+#include "AbstractEffect.h"
+#include
+
+class ChorusEffect : public AbstractEffect {
+public:
+ ChorusEffect();
+ ~ChorusEffect() override = default;
+
+ void apply(const juce::AudioSourceChannelInfo &bufferToFill) override;
+ bool operator==(const AbstractEffect* effect) override;
+
+ [[nodiscard]] juce::String getEffectType() const override { return "ChorusEffect"; }
+
+ [[nodiscard]] juce::var toJSON() const override;
+ void fromJSON(const juce::var &json) override;
+
+ float getDepth() const;
+ float getRate() const;
+ float getMix() const;
+
+ void setRate(float newRate);
+ void setDepth(float newDepth);
+ void setCentreDelay(float newCentreDelay);
+ void setFeedback(float newFeedback);
+ void setMix(float newMix);
+ void prepare(double sampleRate, int samplesPerBlock, int numChannels);
+
+
+private:
+ juce::dsp::Chorus chorus;
+
+ float rate = 1.5f;
+ float depth = 0.5f;
+ float centreDelay = 7.0f;
+ float feedback = 0.2f;
+ float mix = 0.5f;
+
+ void updateParameters();
+};
diff --git a/include/ChorusEffectComponent.h b/include/ChorusEffectComponent.h
new file mode 100644
index 0000000..07dcd38
--- /dev/null
+++ b/include/ChorusEffectComponent.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "BasePedalComponent.h"
+#include "ChorusEffect.h"
+#include
+
+class ChorusEffectComponent : public BasePedalComponent
+{
+public:
+ explicit ChorusEffectComponent(AbstractEffect* e);
+
+
+private:
+ ChorusEffect* chorusEffect;
+
+ juce::Slider depthSlider, rateSlider, mixSlider;
+ juce::Label depthLabel, rateLabel, mixLabel;
+
+ juce::Grid grid;
+};
diff --git a/include/DelayEffect.h b/include/DelayEffect.h
new file mode 100644
index 0000000..06d00bd
--- /dev/null
+++ b/include/DelayEffect.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include
+#include "AbstractEffect.h"
+
+/**
+ * @brief Represents a delay effect that applies a delay to the audio stream.
+ * Inherits from the AbstractEffect class.
+ */
+class DelayEffect : public AbstractEffect {
+public:
+ /**
+ * @brief Initializes a new instance of the DelayEffect class.
+ */
+ DelayEffect();
+
+ /**
+ * @brief Destroys the instance of the DelayEffect class.
+ */
+ ~DelayEffect() override;
+
+ /**
+ * @brief Applies the delay effect to the given audio buffer.
+ * @param bufferToFill The audio buffer to apply the effect to.
+ */
+ void apply(const AudioSourceChannelInfo& bufferToFill) override;
+
+ /**
+ * @brief Sets the rate of the delay effect.
+ * @param rate The rate of the delay effect.
+ */
+ void setRate(float rate);
+
+ /**
+ * @brief Sets the delay of the effect.
+ * @param delay The delay of the effect.
+ */
+ void setDelay(float delay);
+
+ /**
+ * @brief Compares the effect with another given effect.
+ * @param effect The effect to compare with.
+ * @return True if the effect is equal to the given effect, false otherwise.
+ */
+ bool operator==(const AbstractEffect* effect) override;
+
+ /**
+ * @brief Gets the type name of the effect for serialization purposes.
+ * @return A string representing the effect type.
+ */
+ [[nodiscard]] String getEffectType() const override {
+ return "DelayEffect";
+ }
+
+ /**
+ * @brief Serializes the delay effect to a JSON object.
+ * @return JSON object containing serialized effect data.
+ */
+ [[nodiscard]] var toJSON() const override {
+ auto obj = AbstractEffect::toJSON();
+ if (auto* dynamicObj = obj.getDynamicObject()) {
+ dynamicObj->setProperty("rate", rate);
+ dynamicObj->setProperty("delay", delay);
+ }
+ return obj;
+ }
+
+ /**
+ * @brief Deserializes the delay effect from a JSON object.
+ * @param json JSON object containing serialized effect data.
+ */
+ void fromJSON(const var& json) override {
+ AbstractEffect::fromJSON(json);
+
+ if (const auto* obj = json.getDynamicObject()) {
+ rate = static_cast(obj->getProperty("rate"));
+ delay = static_cast(obj->getProperty("delay"));
+ }
+ }
+
+private:
+ /**
+ * @brief The rate of the delay.
+ */
+ float rate;
+
+ /**
+ * @brief The delay of the effect.
+ */
+ float delay;
+
+ std::vector circularBuffer;
+ int writePosition = 0;
+};
diff --git a/include/DelayEffectComponent.h b/include/DelayEffectComponent.h
new file mode 100644
index 0000000..627008a
--- /dev/null
+++ b/include/DelayEffectComponent.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include
+
+#include "BasePedalComponent.h"
+#include "DelayEffect.h"
+
+/**
+ * @brief Represents a graphical component that contains and displays a delay effect.
+ */
+class DelayEffectComponent : public BasePedalComponent {
+ public:
+ /**
+ * @brief Initializes a new instance of the DelayComponent class.
+ */
+ DelayEffectComponent();
+
+ /**
+ * @brief Initializes a new instance of the DelayComponent class with the given AbstractEffect.
+ * @param effect The effect to initialize the component with.
+ */
+ explicit DelayEffectComponent(AbstractEffect* effect);
+
+ /**
+ * @brief Destroys the instance of the DelayComponent class.
+ */
+ ~DelayEffectComponent() override;
+
+ private:
+ /**
+ * @brief The grid that contains the subcomponents of the delay effect
+ * and determines their layout.
+ */
+ juce::Grid grid;
+
+ /**
+ * @brief The slider that controls the rate of the delay effect.
+ */
+ juce::Slider rateSlider;
+
+ /**
+ * @brief The label of the rate slider.
+ */
+ juce::Label rateLabel;
+
+ /**
+ * @brief The slider that controls the delay of the delay effect.
+ */
+ juce::Slider delaySlider;
+
+ /**
+ * @brief The label of the delay slider.
+ */
+ juce::Label delayLabel;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DelayEffectComponent)
+};
\ No newline at end of file
diff --git a/include/DistortionEffect.h b/include/DistortionEffect.h
new file mode 100644
index 0000000..6f68921
--- /dev/null
+++ b/include/DistortionEffect.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include
+#include
+#include "AbstractEffect.h"
+
+class DistortionEffect : public AbstractEffect {
+public:
+ DistortionEffect();
+ ~DistortionEffect() override;
+
+ void apply(const AudioSourceChannelInfo &bufferToFill) override;
+ void prepare(const juce::dsp::ProcessSpec& spec);
+ void reset() noexcept;
+
+ // Paramètres Boss DS-2
+ void setLevel(float value); // 0.0 à 1.0
+ void setTone(float value); // 0.0 à 1.0
+ void setDist(float value); // 0.0 à 1.0
+ void setTurbo(bool enabled);
+
+ float getLevel() const;
+ float getTone() const;
+ float getDist() const;
+ bool isTurbo() const;
+
+ // (Sérialisation, etc.)
+ /**
+ * @brief Gets the type name of the effect for serialization purposes.
+ * @return A string representing the effect type.
+ */
+ [[nodiscard]] String getEffectType() const override { return "DistortionEffect"; }
+
+ /**
+ * @brief Serializes the delay effect to a JSON object.
+ * @return JSON object containing serialized effect data.
+ */
+ [[nodiscard]] var toJSON() const override {
+ auto obj = AbstractEffect::toJSON();
+ if (auto *dynamicObj = obj.getDynamicObject()) {
+ dynamicObj->setProperty("level", level);
+ dynamicObj->setProperty("tone", tone);
+ dynamicObj->setProperty("dist", dist);
+ dynamicObj->setProperty("turbo", turbo);
+ }
+ return obj;
+ }
+
+ /**
+ * @brief Deserializes the delay effect from a JSON object.
+ * @param json JSON object containing serialized effect data.
+ */
+ void fromJSON(const var &json) override {
+ AbstractEffect::fromJSON(json);
+ if (const auto *obj = json.getDynamicObject()) {
+ if (obj->hasProperty("level")) level = static_cast(obj->getProperty("level"));
+ if (obj->hasProperty("tone")) tone = static_cast(obj->getProperty("tone"));
+ if (obj->hasProperty("dist")) dist = static_cast(obj->getProperty("dist"));
+ if (obj->hasProperty("turbo")) turbo = static_cast(obj->getProperty("turbo"));
+ updateWaveshaper();
+ updateTone();
+ processorChain.get<3>().setGainLinear(level);
+ }
+ }
+
+ bool operator==(const AbstractEffect* other)
+ {
+ return dynamic_cast(other) &&
+ level == static_cast(other)->level &&
+ tone == static_cast(other)->tone &&
+ dist == static_cast(other)->dist &&
+ turbo == static_cast(other)->turbo;
+ }
+
+private:
+ float level = 1.0f;
+ float tone = 0.5f;
+ float dist = 0.5f;
+ bool turbo = false;
+ float currentDrive = 1.0f;
+ bool currentTurbo = false;
+
+ juce::dsp::ProcessorChain<
+ juce::dsp::IIR::Filter, // High-pass
+ juce::dsp::WaveShaper, // Distorsion
+ juce::dsp::IIR::Filter, // Tone (low-pass)
+ juce::dsp::Gain // Level
+ > processorChain;
+
+ void updateTone();
+
+ static float waveshaperFunc(float x);
+ void updateWaveshaper();
+};
\ No newline at end of file
diff --git a/include/DistortionEffectComponent.h b/include/DistortionEffectComponent.h
new file mode 100644
index 0000000..450abc9
--- /dev/null
+++ b/include/DistortionEffectComponent.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include
+#include "BasePedalComponent.h"
+#include "ToggleButtonComponent.h"
+
+class DistortionEffectComponent : public BasePedalComponent {
+public:
+ DistortionEffectComponent();
+ explicit DistortionEffectComponent(AbstractEffect* effect);
+ ~DistortionEffectComponent() override;
+
+private:
+ juce::Grid grid;
+ juce::Slider levelSlider, toneSlider, distSlider;
+ juce::Label levelLabel, toneLabel, distLabel;
+ ToggleButtonComponent turboButton;
+ juce::Label turboLabel;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DistortionEffectComponent)
+};
\ No newline at end of file
diff --git a/include/EffectComponent.h b/include/EffectComponent.h
new file mode 100644
index 0000000..b57b7fe
--- /dev/null
+++ b/include/EffectComponent.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include
+#include
+#include "AbstractEffect.h"
+
+/**
+ * @brief Abstract class that represents what an effect component should have.
+ */
+class EffectComponent : public juce::Component
+{
+ public:
+ /**
+ * @brief Gets the AbstractEffect instance of the component.
+ * @return The AbstractEffect instance of the component.
+ */
+ AbstractEffect* getEffect();
+ protected:
+ /**
+ * @brief Initializes a new instance of the EffectComponent class.
+ * @param effect The effect to initialize the component with.
+ */
+ explicit EffectComponent(AbstractEffect* effect);
+
+ /**
+ * @brief Destroys the instance of the EffectComponent class.
+ */
+ ~EffectComponent() override = default;
+
+ /**
+ * @brief The effect that the component represents.
+ */
+ AbstractEffect* effect;
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectComponent)
+};
\ No newline at end of file
diff --git a/include/EffectComponentFactory.h b/include/EffectComponentFactory.h
new file mode 100644
index 0000000..6418829
--- /dev/null
+++ b/include/EffectComponentFactory.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "EffectComponent.h"
+#include "AbstractEffect.h"
+
+/**
+ * @brief Factory class that creates graphical effect components.
+ */
+class EffectComponentFactory
+{
+ public:
+ /**
+ * @brief Creates an effect component for the given AbstractEffect.
+ * @param effect The effect to create the component for.
+ * @return The created effect component.
+ */
+ static EffectComponent* CreateEffectComponent(AbstractEffect* effect);
+};
\ No newline at end of file
diff --git a/include/EffectsFactory.h b/include/EffectsFactory.h
new file mode 100644
index 0000000..fba24a3
--- /dev/null
+++ b/include/EffectsFactory.h
@@ -0,0 +1,15 @@
+#pragma once
+#include "AbstractEffect.h"
+
+/**
+ * @brief A factory class for creating instances of various effects.
+ */
+class EffectsFactory
+{
+public:
+ /**
+ * @brief Creates one instance of each effect type.
+ * @return The vector containing pointers to the created effect instances.
+ */
+ static std::vector createAllEffects();
+};
diff --git a/include/EqualizerEffect.h b/include/EqualizerEffect.h
new file mode 100644
index 0000000..37334aa
--- /dev/null
+++ b/include/EqualizerEffect.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+#include "AbstractEffect.h"
+#include
+
+
+class EqualizerEffect : public AbstractEffect {
+public:
+ EqualizerEffect();
+ ~EqualizerEffect() override = default;
+
+ void apply(const AudioSourceChannelInfo &bufferToFill) override;
+ void setGain(int bandIndex, float gain);
+ float getGain(int bandIndex) const;
+ bool operator==(const AbstractEffect* effect) override;
+
+ [[nodiscard]] String getEffectType() const override { return "EqualizerEffect"; }
+
+ [[nodiscard]] var toJSON() const override;
+ void fromJSON(const var &json) override;
+
+private:
+ static constexpr int numBands = 10;
+ float bandGains[numBands]; // Gain en dB pour chaque bande
+ std::vector> filters;
+
+ void updateFilters();
+};
diff --git a/include/EqualizerEffectComponent.h b/include/EqualizerEffectComponent.h
new file mode 100644
index 0000000..32ecbe1
--- /dev/null
+++ b/include/EqualizerEffectComponent.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+#include "BasePedalComponent.h"
+#include "EqualizerEffect.h"
+
+class EqualizerEffectComponent : public BasePedalComponent {
+public:
+ explicit EqualizerEffectComponent(AbstractEffect* effect);
+ ~EqualizerEffectComponent() override = default;
+
+private:
+ EqualizerEffect* eqEffect;
+ Slider sliders[10];
+ Label labels[10];
+
+ Grid grid;
+
+ void sliderValueChanged(Slider* slider);
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EqualizerEffectComponent)
+};
diff --git a/include/IEffect.h b/include/IEffect.h
new file mode 100644
index 0000000..1162255
--- /dev/null
+++ b/include/IEffect.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+
+/**
+ * @brief Interface that declares the actions an effect should be able to do.
+ */
+class IEffect {
+ public:
+ /**
+ * @brief Applies the effect to the given audio buffer.
+ * @param bufferToFill The audio buffer to apply the effects to.
+ */
+ virtual void apply(const AudioSourceChannelInfo &bufferToFill) = 0;
+
+ /**
+ * @brief Destroys the current instance of IEffect.
+ */
+ virtual ~IEffect() = default;
+};
\ No newline at end of file
diff --git a/include/LoginComponent.h b/include/LoginComponent.h
new file mode 100644
index 0000000..2d545cb
--- /dev/null
+++ b/include/LoginComponent.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include
+
+class LoginComponent final : public Component {
+public:
+ LoginComponent();
+ ~LoginComponent() override;
+
+ void paint(Graphics&) override;
+ void resized() override;
+
+private:
+ void setupFields();
+ void setupButtons();
+ void layoutComponents();
+ void skipButtonClicked();
+
+ TextEditor usernameField;
+ TextEditor passwordField;
+ TextButton loginButton{"Login"};
+ TextButton skipButton{"Skip"};
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LoginComponent)
+};
\ No newline at end of file
diff --git a/include/MainComponent.h b/include/MainComponent.h
index 7bde5e1..645f75f 100644
--- a/include/MainComponent.h
+++ b/include/MainComponent.h
@@ -1,29 +1,104 @@
#pragma once
+#include "BottomMenuBarComponent.h"
+#include "LoginComponent.h"
+#include "Manager.h"
+#include "PedalboardComponent.h"
+#include "TopMenuBarComponent.h"
+
// CMake builds don't use an AppConfig.h, so it's safe to include juce module headers
// directly. If you need to remain compatible with Projucer-generated builds, and
// have called `juce_generate_juce_header()` in your CMakeLists.txt,
// you could `#include ` here instead, to make all your module headers visible.
-#include
-
-//==============================================================================
-/*
- This component lives inside our window, and this is where you should put all
- your controls and content.
-*/
-class MainComponent final : public juce::Component
-{
+#include
+#include
+#include
+
+
+/**
+ * @brief The main component of the application that contains all the main UI.
+ * It is responsible for the layout of the components and the audio processing.
+ * It contains the pedalboard, the top menu bar, the bottom menu bar and the connection component.
+ */
+class MainComponent final : public AudioAppComponent {
public:
- //==============================================================================
- MainComponent();
+ explicit MainComponent(const Manager &manager);
+
+ /**
+ * Determines how the component is displayed.
+ */
+ void paint(Graphics &) override;
- //==============================================================================
- void paint (juce::Graphics&) override;
+ /**
+ * Determines what to do when the component is resized.
+ * Sets the sizes of the subcomponents.
+ */
void resized() override;
+ /**
+ * @brief Prepares the application to process audio.
+ * @param samplesPerBlockExpected The number of samples per processed block expected.
+ * @param sampleRate The expected sample rate.
+ */
+ void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override;
+
+ /**
+ * Releases the audio sources used when the application is closed.
+ * It avoids audio glitches on the operating system that can happen
+ * when some resources are not released.
+ */
+ void releaseResources() override;
+
+ /**
+ * @brief Gets the next audio block to process.
+ * @param bufferToFill The audio buffer to apply the effects to.
+ */
+ void getNextAudioBlock(const AudioSourceChannelInfo &bufferToFill) override;
+
private:
- //==============================================================================
- // Your private member variables go here...
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
+ ImageComponent backgroundImage;
+
+ /**
+ * The grid that contains the main components on the main page.
+ */
+ Grid grid;
+
+ /**
+ * The component that allows the user to login.
+ */
+ LoginComponent connectionComponent;
+
+ /**
+ * Component that contains the pedalboard and that automatically adjust rendering so it becomes
+ * scrollable if necessary.
+ */
+ juce::Viewport pedalboardContainer;
+
+ /**
+ * The component that displays the pedalboard.
+ */
+ PedalboardComponent pedalboardComponent;
+
+ /**
+ * The menu bar at the top of the application.
+ */
+ TopMenuBarComponent topMenuBarComponent;
+
+ /**
+ * The bottom menu bar of the application.
+ */
+ BottomMenuBarComponent bottomMenuBarComponent;
+
+ /**
+ * The manager that processes the audio stream and effects.
+ */
+ Manager manager;
+
+ /**
+ * Tells if the sound is muted or not.
+ */
+ bool isSoundMuted = false;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};
diff --git a/include/Manager.h b/include/Manager.h
new file mode 100644
index 0000000..3f290dc
--- /dev/null
+++ b/include/Manager.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "Pedalboard.h"
+
+/**
+ * @brief Manages the effects in currently used in the pedalboard.
+ */
+class Manager {
+public:
+ explicit Manager(AbstractEffect *pedalboard);
+
+ /**
+ * @brief Applies the effects from the pedalboard to the given audio buffer.
+ * @param bufferToFill The audio buffer to apply the effects to.
+ */
+ void apply(const AudioSourceChannelInfo &bufferToFill) const;
+
+ /**
+ * @brief Imports a pedalboard.
+ */
+ void importF() const;
+
+ /**
+ * @brief Adds the given effect at the end of the pedalboard.
+ * @param effect The effect to append in the pedalboard.
+ */
+ void append(AbstractEffect *effect) const;
+
+ /**
+ * @brief Exports all the pedalboard.
+ */
+ void exportAll() const;
+
+ /**
+ * Exports a given list of effects from the pedalboard.
+ */
+ void exportSelection() const;
+
+ /**
+ * @brief Gets the pedalboard.
+ * @return The pedalboard.
+ */
+ [[nodiscard]]
+ AbstractEffect *getPedalboard() const;
+
+private:
+ /**
+ * @brief The pedalboard that contains all the currently used effects.
+ */
+ AbstractEffect *pedalboard;
+};
diff --git a/include/ModalOverlayComponent.h b/include/ModalOverlayComponent.h
new file mode 100644
index 0000000..ea25545
--- /dev/null
+++ b/include/ModalOverlayComponent.h
@@ -0,0 +1,20 @@
+#pragma once
+#include
+
+class ModalOverlayComponent : public juce::Component
+{
+public:
+ ModalOverlayComponent(std::string viewName, juce::Component* modalContent);
+ ~ModalOverlayComponent() override = default;
+ void resized() override;
+ void paint(Graphics& g) override;
+
+private:
+ juce::Label viewNameLabel;
+ juce::ImageButton closeOverlayButton;
+ juce::Component* modalComponent = nullptr;
+
+ void onCloseOverlayButtonClicked();
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModalOverlayComponent)
+};
+
diff --git a/include/NoiseGateEffect.h b/include/NoiseGateEffect.h
new file mode 100644
index 0000000..7501c14
--- /dev/null
+++ b/include/NoiseGateEffect.h
@@ -0,0 +1,100 @@
+#pragma once
+#include "AbstractEffect.h"
+
+/**
+ * @brief Represents a noise gate effect that applies a noise gate to the audio stream.
+ * Inherits from the AbstractEffect class.
+ */
+class NoiseGateEffect: public AbstractEffect
+{
+public:
+ /**
+ * @brief Initializes a new instance of the NoiseGateEffect class.
+ */
+ NoiseGateEffect();
+
+ /**
+ * @brief Destroys the instance of the NoiseGateEffect class.
+ */
+ ~NoiseGateEffect() override;
+
+ /**
+ * @brief Applies the noise gate effect to the given audio buffer.
+ * @param bufferToFill The audio buffer to apply the effect to.
+ */
+ void apply(const AudioSourceChannelInfo &bufferToFill) override;
+
+ /**
+ * @brief Sets the threshold of the noise gate effect.
+ * @param threshold The threshold of the noise gate effect.
+ */
+ void setThreshold(float threshold);
+
+ /**
+ * @brief Sets the attack time of the noise gate effect.
+ * @param attack The attack time of the noise gate effect.
+ */
+ void setAttack(float attack);
+
+ /**
+ * @brief Sets the release time of the noise gate effect.
+ * @param release The release time of the noise gate effect.
+ */
+ void setRelease(float release);
+
+ /**
+ * @brief Compares the effect with another given effect.
+ * @param effect The effect to compare with.
+ * @return True if the effect is equal to the given effect, false otherwise.
+ */
+ bool operator==(const AbstractEffect* effect) override;
+
+ /**
+ * @brief Gets the type name of the effect for serialization purposes.
+ * @return A string representing the effect type.
+ */
+ [[nodiscard]] String getEffectType() const override { return "NoiseGateEffect"; }
+
+ /**
+ * @brief Serializes the delay effect to a JSON object.
+ * @return JSON object containing serialized effect data.
+ */
+ [[nodiscard]] var toJSON() const override {
+ auto obj = AbstractEffect::toJSON();
+ if (auto *dynamicObj = obj.getDynamicObject()) {
+ dynamicObj->setProperty("threshold", threshold);
+ dynamicObj->setProperty("attack", attack);
+ dynamicObj->setProperty("release", release);
+ }
+ return obj;
+ }
+
+ /**
+ * @brief Deserializes the delay effect from a JSON object.
+ * @param json JSON object containing serialized effect data.
+ */
+ void fromJSON(const var &json) override {
+ AbstractEffect::fromJSON(json);
+
+ if (const auto *obj = json.getDynamicObject()) {
+ threshold = static_cast(obj->getProperty("threshold"));
+ attack = static_cast(obj->getProperty("attack"));
+ release = static_cast(obj->getProperty("release"));
+ }
+ }
+private:
+ /**
+ * @brief The threshold of the noise gate effect.
+ */
+ float threshold;
+
+ /**
+ * @brief The attack time of the noise gate effect.
+ */
+ float attack;
+
+ /**
+ * @brief The release time of the noise gate effect.
+ */
+ float release;
+};
diff --git a/include/NoiseGateEffectComponent.h b/include/NoiseGateEffectComponent.h
new file mode 100644
index 0000000..e1e9849
--- /dev/null
+++ b/include/NoiseGateEffectComponent.h
@@ -0,0 +1,75 @@
+#pragma once
+#include "BasePedalComponent.h"
+#include "NoiseGateEffect.h"
+#include
+
+/**
+ * @brief Represents a noise gate effect component that applies a noise gate to the audio stream.
+ * Inherits from the BasePedalComponent class.
+ */
+class NoiseGateEffectComponent: public BasePedalComponent
+{
+public:
+ /**
+ * @brief Initializes a new instance of the NoiseGateEffectComponent class.
+ */
+ explicit NoiseGateEffectComponent(AbstractEffect* effect);
+
+ /**
+ * @brief Destroys the instance of the NoiseGateEffectComponent class.
+ */
+ ~NoiseGateEffectComponent() override;
+private:
+ /**
+ * @brief The Grid component that contains the menu items.
+ */
+ juce::Grid grid;
+
+ /**
+ * @brief The slider that manages the threshold value.
+ */
+ juce::Slider thresholdSlider;
+
+ /**
+ * @brief The label that is linked to the threshold slider.
+ */
+ juce::Label thresholdLabel;
+
+ /**
+ * @brief The slider that manages the attack value.
+ */
+ juce::Slider attackSlider;
+
+ /**
+ * @brief The label that is linked to the attack slider.
+ */
+ juce::Label attackLabel;
+
+ /**
+ * @brief The slider that manages the release value.
+ */
+ juce::Slider releaseSlider;
+
+ /**
+ * @brief The label that is linked to the release slider.
+ */
+ juce::Label releaseLabel;
+
+ /**
+ * @brief The function that is called when the threshold value changes.
+ * @param value The new value of the threshold slider.
+ */
+ void onThreholdValueChanged(NoiseGateEffect* effect, double value);
+
+ /**
+ * @brief The function that is called when the release value changes.
+ * @param value The new value of the release slider.
+ */
+ void onAttackValueChanged(NoiseGateEffect* effect, double value);
+
+ /**
+ * @brief The function that is called when the release value changes.
+ * @param value The new value of the release slider.
+ */
+ void onReleaseValueChanged(NoiseGateEffect* effect, double value);
+};
\ No newline at end of file
diff --git a/include/Pedal.h b/include/Pedal.h
new file mode 100644
index 0000000..33af80c
--- /dev/null
+++ b/include/Pedal.h
@@ -0,0 +1,5 @@
+#pragma once
+
+class Pedal {
+
+};
diff --git a/include/PedalPowerIndicatorComponent.h b/include/PedalPowerIndicatorComponent.h
new file mode 100644
index 0000000..903508f
--- /dev/null
+++ b/include/PedalPowerIndicatorComponent.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+
+/**
+ * @brief Represents a graphical component that indicates the power status of a pedal.
+ */
+class PedalPowerIndicatorComponent: public juce::Component
+{
+public:
+ /**
+ * @brief Initializes a new instance of the PedalPowerIndicatorComponent class.
+ * @param isEnabled Indicates whether the pedal is enabled or not.
+ */
+ explicit PedalPowerIndicatorComponent(bool isEnabled = true);
+
+ /**
+ * @brief Destroys the instance of the PedalPowerIndicatorComponent class.
+ */
+ ~PedalPowerIndicatorComponent() override;
+
+ /**
+ * @brief Paints the component.
+ * @param g The JUCE graphics context that paints the component.
+ */
+ void paint(juce::Graphics &g) override;
+
+ /**
+ * @brief Toggles the power status of the pedal.
+ * @param isEnabled Indicates whether the pedal is enabled or not.
+ */
+ void togglePower(bool isEnabled);
+private:
+ /**
+ * @brief Indicates whether the pedal is enabled or not.
+ */
+ bool isEnabled;
+};
\ No newline at end of file
diff --git a/include/PedalSettingsLayoutComponent.h b/include/PedalSettingsLayoutComponent.h
new file mode 100644
index 0000000..655b6fa
--- /dev/null
+++ b/include/PedalSettingsLayoutComponent.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+
+class PedalSettingsLayoutComponent: public juce::Component
+{
+public:
+ PedalSettingsLayoutComponent(juce::Grid* layout);
+ ~PedalSettingsLayoutComponent() override;
+ void paint(juce::Graphics& g) override;
+ void resized() override;
+private:
+ juce::Grid* layout;
+};
diff --git a/include/Pedalboard.h b/include/Pedalboard.h
new file mode 100644
index 0000000..366ee95
--- /dev/null
+++ b/include/Pedalboard.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include "AbstractEffect.h"
+
+/**
+ * @brief Represents a pedalboard that contains all the effects and apply them on the audio stream.
+ * It inherits from the AbstractEffect class so we can include a pedalboard in another.
+ */
+class Pedalboard : public AbstractEffect {
+public:
+ /**
+ * @brief Initializes a new instance of the Pedalboard class.
+ */
+ Pedalboard();
+
+ /**
+ * @brief Initializes a new instance of the Pedalboard class with the given effects.
+ * @param effects The effects collection to initialize the pedalboard with.
+ */
+ explicit Pedalboard(const std::vector &effects);
+
+ /**
+ * @brief Destroys the instance of the Pedalboard class.
+ */
+ ~Pedalboard() override;
+
+ /**
+ * @brief Applies all the effects in the pedalboard to the given audio buffer.
+ * @param bufferToFill The audio buffer to apply the effects to.
+ */
+ void apply(const AudioSourceChannelInfo &bufferToFill) override;
+
+ /**
+ * @brief Compares the pedalboard with the given effect.
+ * @param effect The effect to compare with.
+ * @return True if the pedalboard is equal to the given effect, false otherwise.
+ */
+ bool operator==(const AbstractEffect *effect) override;
+
+ /**
+ * @brief Adds an effect at the end of the pedalboard.
+ * @param effect The effect to add to the pedalboard.
+ */
+ void append(AbstractEffect *effect);
+
+ /**
+ * @brief Adds all the effects from the given vector to the pedalboard.
+ * @param effects The vector of effects to add to the pedalboard.
+ */
+ void appendAll(std::vector effects);
+
+ /**
+ * @brief Inserts an effect at the given index in the pedalboard.
+ * @param effect The effect to insert into the pedalboard.
+ * @param index The index where to insert the effect.
+ */
+ void insert(AbstractEffect *effect, int index);
+
+ /**
+ * @brief Removes the given effect from the pedalboard.
+ * @param effect The effect to remove from the pedalboard.
+ */
+ void remove(const AbstractEffect *effect);
+
+ /**
+ * @brief Gets the collection of effects contained in the pedalboard.
+ * @return The collection of effects contained in the pedalboard.
+ */
+ std::vector getEffects();
+
+ /**
+ * @brief Gets the type name of the effect for serialization purposes.
+ * @return A string representing the effect type.
+ */
+ [[nodiscard]] String getEffectType() const override { return "DelayEffect"; }
+
+ /**
+ * @brief Serializes the delay effect to a JSON object.
+ * @return JSON object containing serialized effect data.
+ */
+ [[nodiscard]] var toJSON() const override {
+ auto obj = AbstractEffect::toJSON();
+ return obj;
+ }
+
+ /**
+ * @brief Deserializes the delay effect from a JSON object.
+ * @param json JSON object containing serialized effect data.
+ */
+ void fromJSON(const var &json) override {
+ AbstractEffect::fromJSON(json);
+ }
+
+private:
+ /**
+ * @brief The collection of effects contained in the pedalboard.
+ */
+ std::vector effects;
+};
diff --git a/include/PedalboardComponent.h b/include/PedalboardComponent.h
new file mode 100644
index 0000000..2b35414
--- /dev/null
+++ b/include/PedalboardComponent.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include
+#include "Pedalboard.h"
+#include "EffectComponent.h"
+
+/**
+ * @brief Represents a graphical component that contains and displays a pedalboard.
+ */
+class PedalboardComponent : public EffectComponent {
+ public:
+ /**
+ * @brief Initializes a new instance of the PedalboardComponent class.
+ * @param pedalboard The pedalboard to initialize the component with.
+ */
+ PedalboardComponent(AbstractEffect* pedalboard);
+
+ /**
+ * @brief Destroys the instance of the PedalboardComponent class.
+ */
+ ~PedalboardComponent() override;
+
+ /**
+ * @brief Adds an EffectComponent to the pedalboard.
+ * @param effect The EffectComponent to add to the pedalboard.
+ */
+ void addEffect(EffectComponent* effect);
+
+ /**
+ * @brief Determines what to do when the component is resized.
+ */
+ void resized() override;
+
+ /**
+ * @brief Determines how the component is displayed.
+ * @param g The JUCE graphics context that paints the component.
+ */
+ void paint(juce::Graphics &g) override;
+
+ /**
+ * @brief Gets the required width for the pedalboard based on its effects.
+ * @return The required width for the pedalboard.
+ */
+ int getRequiredWidth() const;
+
+ /**
+ * @brief Gets the required height for the pedalboard based on its effects.
+ * @return The required height for the pedalboard.
+ */
+ int getRequiredHeight(const int boardWidth) const;
+ private:
+
+ /**
+ * @brief The default margin between pedals.
+ */
+ const int PEDALS_MARGIN = 20;
+
+ /**
+ * @brief The collection of the effect components in the pedalboard.
+ */
+ std::vector effectsComponents;
+
+ /**
+ * @brief The flexbox that arranges layout for the effect components in the pedalboard.
+ */
+ juce::FlexBox flexBox;
+};
\ No newline at end of file
diff --git a/include/PopupContentComponent.h b/include/PopupContentComponent.h
new file mode 100644
index 0000000..38308a1
--- /dev/null
+++ b/include/PopupContentComponent.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include
+
+class PopupContentComponent : public juce::Component
+{
+public:
+ PopupContentComponent()
+ {
+ setSize(400, 300);
+
+ addAndMakeVisible(closeButton);
+ closeButton.setButtonText("Fermer");
+ closeButton.onClick = [this]() {
+ if (auto* dw = findParentComponentOfClass())
+ dw->exitModalState(0);
+ };
+ }
+
+ void resized() override
+ {
+ closeButton.setBounds(getLocalBounds().reduced(20));
+ }
+
+private:
+ juce::TextButton closeButton;
+};
diff --git a/include/ResourceManager.h b/include/ResourceManager.h
new file mode 100644
index 0000000..01d0670
--- /dev/null
+++ b/include/ResourceManager.h
@@ -0,0 +1,11 @@
+#pragma once
+#include
+
+class ResourceManager
+{
+public:
+ ResourceManager();
+ ~ResourceManager();
+
+ static Image loadImage(const juce::String& relativePath);
+};
diff --git a/include/SettingsComponent.h b/include/SettingsComponent.h
new file mode 100644
index 0000000..8705784
--- /dev/null
+++ b/include/SettingsComponent.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+#include
+#include
+
+class SettingsComponent : public juce::Component {
+public:
+ explicit SettingsComponent(juce::AudioDeviceManager& deviceManager);
+
+ void resized() override;
+
+private:
+ juce::AudioDeviceSelectorComponent deviceSelector;
+};
diff --git a/include/ToggleButtonComponent.h b/include/ToggleButtonComponent.h
new file mode 100644
index 0000000..6b2e9b3
--- /dev/null
+++ b/include/ToggleButtonComponent.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include
+
+class ToggleButtonComponent : public ToggleButton {
+public:
+ ToggleButtonComponent();
+ ~ToggleButtonComponent() override;
+ void paint(Graphics& g) override;
+};
diff --git a/include/TopMenuBarComponent.h b/include/TopMenuBarComponent.h
new file mode 100644
index 0000000..ad810ad
--- /dev/null
+++ b/include/TopMenuBarComponent.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+#include "AccountComponent.h"
+#include "ModalOverlayComponent.h"
+#include "SettingsComponent.h"
+
+class TopMenuBarComponent : public Component {
+public:
+ explicit TopMenuBarComponent(AudioDeviceManager& deviceManager,
+ bool* isMuted = nullptr);
+ ~TopMenuBarComponent() override;
+
+ void paint(Graphics& g) override;
+ void resized() override;
+
+private:
+ int buttonSize = 32;
+ float gap = 16;
+
+ FlexBox flexBox;
+
+ ImageButton accountButton;
+ ImageButton muteButton;
+ ImageButton settingsButton;
+
+ AccountComponent* accountComponent;
+ ModalOverlayComponent* modalOverlay;
+ SettingsComponent* settingsComponent;
+
+ bool* isSoundMuted = nullptr;
+
+ void openSettingsPopup(AudioDeviceManager& deviceManager);
+ void openAccountPopup();
+ void toggleMute();
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopMenuBarComponent)
+};
diff --git a/resources/icons/account.png b/resources/icons/account.png
new file mode 100644
index 0000000..fe445ac
Binary files /dev/null and b/resources/icons/account.png differ
diff --git a/resources/icons/export.png b/resources/icons/export.png
new file mode 100644
index 0000000..69f11e2
Binary files /dev/null and b/resources/icons/export.png differ
diff --git a/resources/icons/mute.png b/resources/icons/mute.png
new file mode 100644
index 0000000..88dc4e0
Binary files /dev/null and b/resources/icons/mute.png differ
diff --git a/resources/icons/power.png b/resources/icons/power.png
new file mode 100644
index 0000000..dcfe593
Binary files /dev/null and b/resources/icons/power.png differ
diff --git a/resources/icons/settings.png b/resources/icons/settings.png
new file mode 100644
index 0000000..2071f27
Binary files /dev/null and b/resources/icons/settings.png differ
diff --git a/resources/icons/sync.png b/resources/icons/sync.png
new file mode 100644
index 0000000..cba8294
Binary files /dev/null and b/resources/icons/sync.png differ
diff --git a/resources/icons/unmute.png b/resources/icons/unmute.png
new file mode 100644
index 0000000..d1e4d79
Binary files /dev/null and b/resources/icons/unmute.png differ
diff --git a/resources/icons/xmark.png b/resources/icons/xmark.png
new file mode 100644
index 0000000..4937ee7
Binary files /dev/null and b/resources/icons/xmark.png differ
diff --git a/resources/images/background.png b/resources/images/background.png
new file mode 100644
index 0000000..07a4d50
Binary files /dev/null and b/resources/images/background.png differ
diff --git a/resources/images/kamp_logo_with_background.png b/resources/images/kamp_logo_with_background.png
new file mode 100644
index 0000000..0ca379d
Binary files /dev/null and b/resources/images/kamp_logo_with_background.png differ
diff --git a/src/ApiClient.cpp b/src/ApiClient.cpp
new file mode 100644
index 0000000..a057c75
--- /dev/null
+++ b/src/ApiClient.cpp
@@ -0,0 +1,22 @@
+#include
+#include "ApiClient.h"
+
+using ReplyFunc = std::function;
+
+void ApiClient::runHTTP(const URL &url, const ReplyFunc &replyFunc) {
+ auto req = [url, replyFunc] {
+ const auto options = URL::InputStreamOptions(URL::ParameterHandling::inAddress);
+ if (const auto stream = url.createInputStream(options); stream != nullptr) {
+ auto content = stream->readString();
+ MessageManager::callAsync([replyFunc, content] {
+ replyFunc(content);
+ });
+ } else {
+ MessageManager::callAsync([replyFunc] {
+ replyFunc("Error connecting to server");
+ });
+ }
+ };
+
+ Thread::launch(req);
+}
diff --git a/src/AudioBuffer.cpp b/src/AudioBuffer.cpp
new file mode 100644
index 0000000..291454e
--- /dev/null
+++ b/src/AudioBuffer.cpp
@@ -0,0 +1,9 @@
+#include "AudioBuffer.h"
+
+AudioBuffer::AudioBuffer() {}
+
+AudioBuffer::~AudioBuffer() {}
+
+juce::AudioSourceChannelInfo AudioBuffer::getBuffer() {
+ return bufferToFill;
+}
\ No newline at end of file
diff --git a/src/EffectComponentFactory.cpp b/src/EffectComponentFactory.cpp
new file mode 100644
index 0000000..a9a9df0
--- /dev/null
+++ b/src/EffectComponentFactory.cpp
@@ -0,0 +1,32 @@
+#include "AbstractEffect.h"
+#include "DelayEffectComponent.h"
+#include "DistortionEffect.h"
+#include "DistortionEffectComponent.h"
+#include "EffectComponentFactory.h"
+#include "EqualizerEffect.h"
+#include "EqualizerEffectComponent.h"
+#include "NoiseGateEffect.h"
+#include "NoiseGateEffectComponent.h"
+#include "ChorusEffect.h"
+#include "ChorusEffectComponent.h"
+
+EffectComponent* EffectComponentFactory::CreateEffectComponent(AbstractEffect* effect) {
+ if (dynamic_cast(effect) != nullptr) {
+ return new DelayEffectComponent(effect);
+ }
+ if (dynamic_cast(effect) != nullptr) {
+ return new DistortionEffectComponent(effect);
+ }
+ if (dynamic_cast(effect) != nullptr)
+ {
+ return new EqualizerEffectComponent(effect);
+ }
+ if (dynamic_cast(effect) != nullptr) {
+ return new NoiseGateEffectComponent(effect);
+ }
+ if (dynamic_cast(effect) != nullptr)
+ {
+ return new ChorusEffectComponent(effect);
+ }
+ return nullptr;
+}
diff --git a/src/Main.cpp b/src/Main.cpp
index 22bbaad..895ebdb 100644
--- a/src/Main.cpp
+++ b/src/Main.cpp
@@ -1,101 +1,81 @@
+#include
+#include "EffectsFactory.h"
#include "MainComponent.h"
-//==============================================================================
-class GuiAppApplication final : public juce::JUCEApplication
-{
+class GuiAppApplication final : public JUCEApplication {
public:
- //==============================================================================
- GuiAppApplication() {}
-
- // We inject these as compile definitions from the CMakeLists.txt
- // If you've enabled the juce header with `juce_generate_juce_header()`
- // you could `#include ` and use `ProjectInfo::projectName` etc. instead.
- const juce::String getApplicationName() override { return JUCE_APPLICATION_NAME_STRING; }
- const juce::String getApplicationVersion() override { return JUCE_APPLICATION_VERSION_STRING; }
- bool moreThanOneInstanceAllowed() override { return true; }
-
- //==============================================================================
- void initialise (const juce::String& commandLine) override
- {
- // This method is where you should put your application's initialisation code..
- juce::ignoreUnused (commandLine);
-
- mainWindow.reset (new MainWindow (getApplicationName()));
- }
-
- void shutdown() override
- {
- // Add your application's shutdown code here..
-
- mainWindow = nullptr; // (deletes our window)
- }
-
- //==============================================================================
- void systemRequestedQuit() override
- {
- // This is called when the app is being asked to quit: you can ignore this
- // request and let the app carry on running, or call quit() to allow the app to close.
- quit();
- }
-
- void anotherInstanceStarted (const juce::String& commandLine) override
- {
- // When another instance of the app is launched while this one is running,
- // this method is invoked, and the commandLine parameter tells you what
- // the other instance's command-line arguments were.
- juce::ignoreUnused (commandLine);
- }
-
- //==============================================================================
- /*
- This class implements the desktop window that contains an instance of
- our MainComponent class.
- */
- class MainWindow final : public juce::DocumentWindow
- {
- public:
- explicit MainWindow (juce::String name)
- : DocumentWindow (name,
- juce::Desktop::getInstance().getDefaultLookAndFeel()
- .findColour (backgroundColourId),
- allButtons)
- {
- setUsingNativeTitleBar (true);
- setContentOwned (new MainComponent(), true);
-
- #if JUCE_IOS || JUCE_ANDROID
+ GuiAppApplication() = default;
+
+ const String getApplicationName() override {
+ return JUCE_APPLICATION_NAME_STRING;
+ }
+
+ const String getApplicationVersion() override {
+ return JUCE_APPLICATION_VERSION_STRING;
+ }
+
+ bool moreThanOneInstanceAllowed() override { return false; }
+
+ void initialise(const String& commandLine) override {
+ // This method is where you should put your application's initialisation code..
+ ignoreUnused(commandLine);
+
+ mainWindow = std::make_unique(getApplicationName());
+ }
+
+ void shutdown() override {
+ mainWindow = nullptr; // (deletes our window)
+ }
+
+ void systemRequestedQuit() override {
+ // This is called when the app is being asked to quit: you can ignore this
+ // request and let the app carry on running, or call quit() to allow the app to close.
+ quit();
+ }
+
+ void anotherInstanceStarted(const String& commandLine) override {
+ // When another instance of the app is launched while this one is running,
+ // this method is invoked, and the commandLine parameter tells you what
+ // the other instance's command-line arguments were.
+ ignoreUnused(commandLine);
+ }
+
+ class MainWindow final : public DocumentWindow {
+ public:
+ explicit MainWindow(const String& name) :
+ DocumentWindow(
+ name, Desktop::getInstance().getDefaultLookAndFeel().findColour(
+ backgroundColourId), allButtons) {
+ setUsingNativeTitleBar(true);
+ Pedalboard* pedalboard = new Pedalboard();
+ pedalboard->appendAll(EffectsFactory::createAllEffects());
+ Manager* manager = new Manager(pedalboard);
+ setContentOwned(new MainComponent(*manager), true);
+
+#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
- #else
- setResizable (true, true);
- centreWithSize (getWidth(), getHeight());
- #endif
-
- setVisible (true);
- }
-
- void closeButtonPressed() override
- {
- // This is called when the user tries to close this window. Here, we'll just
- // ask the app to quit when this happens, but you can change this to do
- // whatever you need.
- getInstance()->systemRequestedQuit();
- }
-
- /* Note: Be careful if you override any DocumentWindow methods - the base
- class uses a lot of them, so by overriding you might break its functionality.
- It's best to do all your work in your content component instead, but if
- you really have to override any DocumentWindow methods, make sure your
- subclass also calls the superclass's method.
- */
-
- private:
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
- };
+#else
+ setResizable(true, true);
+ setSize(1280, 854);
+ centreWithSize(1280, 854);
+#endif
+ Component::setVisible(true);
+ }
+
+ void closeButtonPressed() override {
+ // This is called when the user tries to close this window. Here, we'll just
+ // ask the app to quit when this happens, but you can change this to do
+ // whatever you need.
+ getInstance()->systemRequestedQuit();
+ }
+
+ private:
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
+ };
private:
- std::unique_ptr mainWindow;
+ std::unique_ptr mainWindow;
};
-//==============================================================================
// This macro generates the main() routine that launches the app.
-START_JUCE_APPLICATION (GuiAppApplication)
+START_JUCE_APPLICATION(GuiAppApplication)
diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp
index c7dcd2f..275967a 100644
--- a/src/MainComponent.cpp
+++ b/src/MainComponent.cpp
@@ -1,25 +1,71 @@
#include "MainComponent.h"
+#include "ResourceManager.h"
-//==============================================================================
-MainComponent::MainComponent()
-{
- setSize (900, 700);
+MainComponent::MainComponent(const Manager& manager):
+ pedalboardComponent(manager.getPedalboard()),
+ topMenuBarComponent(this->deviceManager, &isSoundMuted), manager(manager) {
+ setAudioChannels(2, 2);
+
+ const Image background = ResourceManager::loadImage(
+ "resources/images/background.png");
+ if (background.isValid()) {
+ backgroundImage.setImage(background);
+ backgroundImage.setImagePlacement(RectanglePlacement::stretchToFit);
+ addAndMakeVisible(backgroundImage);
+ } else {
+ DBG("Error : resources/images/background.png not found.");
+ }
+
+ pedalboardContainer.setViewedComponent(&pedalboardComponent, true);
+ pedalboardContainer.setScrollBarsShown(true, false);
+
+ addAndMakeVisible(pedalboardContainer);
+ addAndMakeVisible(topMenuBarComponent);
+ addAndMakeVisible(bottomMenuBarComponent);
+ addAndMakeVisible(connectionComponent);
}
//==============================================================================
-void MainComponent::paint (juce::Graphics& g)
-{
- // (Our component is opaque, so we must completely fill the background with a solid colour)
- g.fillAll ( juce::Colours::black);
- g.setFont (juce::FontOptions (16.0f));
- g.setColour (juce::Colours::white);
- g.drawText ("Hello kAmp!", getLocalBounds(), juce::Justification::centred, true);
+void MainComponent::paint(Graphics& g) {}
+
+void MainComponent::resized() {
+ backgroundImage.setBounds(getLocalBounds());
+ connectionComponent.setBounds(getLocalBounds());
+ int pedalboardWidth = getWidth();
+ int pedalboardHeight = pedalboardComponent.getRequiredHeight(getWidth());
+ pedalboardComponent.setSize(pedalboardWidth, pedalboardHeight);
+
+ using Track = Grid::TrackInfo;
+ using Px = Grid::Px;
+ using Fr = Grid::Fr;
+ grid.templateRows = {Track(Px(50)), Track(Fr(1))};
+ grid.templateColumns = {Track(Fr(1))};
+ grid.items = {
+ GridItem(topMenuBarComponent),
+ GridItem(pedalboardContainer)
+ };
+ grid.performLayout(getLocalBounds());
}
-void MainComponent::resized()
-{
- // This is called when the MainComponent is resized.
- // If you add any child components, this is where you should
- // update their positions.
+void MainComponent::prepareToPlay(int samplesPerBlockExpected,
+ double sampleRate) {}
+
+void MainComponent::getNextAudioBlock(
+ const AudioSourceChannelInfo& bufferToFill) {
+ if (!this->isSoundMuted) {
+ if (&bufferToFill == nullptr) {
+ return;
+ }
+ if (bufferToFill.buffer == nullptr) {
+ return;
+ }
+ this->manager.apply(bufferToFill);
+ } else {
+ bufferToFill.clearActiveBufferRegion();
+ }
}
+
+void MainComponent::releaseResources() {}
+
+
diff --git a/src/Manager.cpp b/src/Manager.cpp
new file mode 100644
index 0000000..860ba67
--- /dev/null
+++ b/src/Manager.cpp
@@ -0,0 +1,30 @@
+#include "Manager.h"
+
+Manager::Manager(AbstractEffect *pedalboard) : pedalboard(pedalboard) {
+}
+
+void Manager::apply(const AudioSourceChannelInfo &bufferToFill) const {
+ if (bufferToFill.buffer == nullptr) {
+ return;
+ }
+ pedalboard->apply(bufferToFill);
+}
+
+void Manager::importF() const {
+}
+
+void Manager::append(AbstractEffect* effect) const {
+ if (Pedalboard* pedalboard = dynamic_cast(this->pedalboard)) {
+ pedalboard->append(effect);
+ }
+}
+
+void Manager::exportAll() const {
+}
+
+void Manager::exportSelection() const {
+}
+
+AbstractEffect *Manager::getPedalboard() const {
+ return pedalboard;
+}
diff --git a/src/Pedalboard.cpp b/src/Pedalboard.cpp
new file mode 100644
index 0000000..076b7e9
--- /dev/null
+++ b/src/Pedalboard.cpp
@@ -0,0 +1,46 @@
+#include "DelayEffect.h"
+#include "DistortionEffect.h"
+#include "Pedalboard.h"
+#include "EqualizerEffect.h"
+#include "NoiseGateEffect.h"
+
+Pedalboard::Pedalboard() { };
+
+Pedalboard::~Pedalboard() = default;
+
+void Pedalboard::apply(const AudioSourceChannelInfo &bufferToFill) {
+ for (AbstractEffect* effect : effects) {
+ if (*(effect->isEnabled))
+ {
+ effect->apply(bufferToFill);
+ }
+ }
+}
+
+void Pedalboard::append(AbstractEffect* effect) {
+ effects.push_back(effect);
+}
+
+void Pedalboard::appendAll(std::vector effects) {
+ this->effects.insert(this->effects.end(), effects.begin(), effects.end());
+}
+
+void Pedalboard::insert(AbstractEffect* effect, int index) {
+ effects.insert(effects.begin() + index, effect);
+}
+
+void Pedalboard::remove(const AbstractEffect* effect) {
+ effects.erase(std::remove(effects.begin(), effects.end(), effect), effects.end());
+}
+
+std::vector Pedalboard::getEffects() {
+ return effects;
+}
+
+Pedalboard::Pedalboard(const std::vector &effects) {
+ this->effects = effects;
+}
+
+bool Pedalboard::operator==(const AbstractEffect* effect) {
+ return true;
+}
\ No newline at end of file
diff --git a/src/components/AccountComponent.cpp b/src/components/AccountComponent.cpp
new file mode 100644
index 0000000..09662e2
--- /dev/null
+++ b/src/components/AccountComponent.cpp
@@ -0,0 +1,104 @@
+#include "AccountComponent.h"
+#include "ApiClient.h"
+
+namespace {
+constexpr float titleFontSize = 24.0f;
+constexpr float labelFontSize = 16.0f;
+constexpr float responseFontSize = 14.0f;
+}
+
+AccountComponent::AccountComponent() {
+ setupLabels();
+ setupButtons();
+ setupGrid();
+
+ // Dummy API call
+ auto replyFunc = [this](const String& content) {
+ apiResponseReceived(content);
+ };
+ ApiClient::runHTTP({"https://dummyjson.com/test"}, replyFunc);
+}
+
+void AccountComponent::setupLabels() {
+ titleLabel.setFont(FontOptions(titleFontSize, Font::bold));
+ titleLabel.setJustificationType(Justification::centred);
+ addAndMakeVisible(titleLabel);
+
+ for (auto* label : {&emailLabel, &usernameLabel}) {
+ label->setFont(FontOptions(labelFontSize));
+ label->setJustificationType(Justification::centredLeft);
+ addAndMakeVisible(*label);
+ }
+
+ for (auto* val : {&emailValueLabel, &usernameValueLabel}) {
+ val->setFont(FontOptions(labelFontSize));
+ val->setJustificationType(Justification::centredLeft);
+ val->setColour(Label::textColourId, Colours::black);
+ addAndMakeVisible(*val);
+ }
+
+ responseLabel.setFont(FontOptions(responseFontSize));
+ responseLabel.setJustificationType(Justification::centred);
+ responseLabel.setColour(Label::textColourId, Colours::darkgrey);
+ addAndMakeVisible(responseLabel);
+}
+
+void AccountComponent::setupButtons() {
+ saveButton.onClick = [this] { saveSettings(); };
+ importButton.onClick = [this] { importSettings(); };
+
+ for (auto* button : {&saveButton, &importButton})
+ addAndMakeVisible(*button);
+}
+
+void AccountComponent::setupGrid() {
+ using namespace juce;
+ grid.templateRows = {
+ Grid::TrackInfo(40_px), // Title
+ Grid::TrackInfo(30_px), // Email label
+ Grid::TrackInfo(30_px), // Email value
+ Grid::TrackInfo(30_px), // Username label
+ Grid::TrackInfo(30_px), // Username value
+ Grid::TrackInfo(20_px), // Spacer
+ Grid::TrackInfo(40_px), // Change password button
+ Grid::TrackInfo(40_px), // Save button
+ Grid::TrackInfo(40_px), // Import button
+ Grid::TrackInfo(30_px) // Response label
+ };
+
+ grid.templateColumns = {Grid::TrackInfo(1_fr)};
+
+ grid.items = {
+ GridItem(titleLabel),
+ GridItem(emailLabel),
+ GridItem(emailValueLabel),
+ GridItem(usernameLabel),
+ GridItem(usernameValueLabel),
+ GridItem().withArea(6, 1), // Spacer
+ GridItem(saveButton),
+ GridItem(importButton),
+ GridItem(responseLabel)
+ };
+}
+
+void AccountComponent::paint(Graphics& g) {
+ g.fillAll(Colours::lightgrey);
+}
+
+void AccountComponent::resized() {
+ grid.performLayout(getLocalBounds().reduced(40));
+}
+
+void AccountComponent::apiResponseReceived(const String& content) {
+ responseLabel.setText("Réponse API : " + content, dontSendNotification);
+}
+
+void AccountComponent::saveSettings() {
+ responseLabel.setText("✅ Réglages sauvegardés !", dontSendNotification);
+}
+
+void AccountComponent::importSettings() {
+ emailValueLabel.setText("import@example.com", dontSendNotification);
+ usernameValueLabel.setText("UtilisateurImporté", dontSendNotification);
+ responseLabel.setText("📥 Réglages importés", dontSendNotification);
+}
diff --git a/src/components/BottomMenuBarComponent.cpp b/src/components/BottomMenuBarComponent.cpp
new file mode 100644
index 0000000..ec40f5a
--- /dev/null
+++ b/src/components/BottomMenuBarComponent.cpp
@@ -0,0 +1,15 @@
+#include "BottomMenuBarComponent.h"
+
+BottomMenuBarComponent::BottomMenuBarComponent() {
+ addAndMakeVisible(menuBarComponent);
+}
+
+BottomMenuBarComponent::~BottomMenuBarComponent() = default;
+
+void BottomMenuBarComponent::paint(juce::Graphics &g) {
+ g.fillAll(juce::Colours::white);
+}
+
+void BottomMenuBarComponent::resized() {
+ menuBarComponent.setBounds(getLocalBounds());
+}
\ No newline at end of file
diff --git a/src/components/ChorusEffectComponent.cpp b/src/components/ChorusEffectComponent.cpp
new file mode 100644
index 0000000..fad9ba8
--- /dev/null
+++ b/src/components/ChorusEffectComponent.cpp
@@ -0,0 +1,98 @@
+#include "ChorusEffectComponent.h"
+#include
+
+ChorusEffectComponent::ChorusEffectComponent(AbstractEffect* e)
+ : BasePedalComponent(e), chorusEffect(dynamic_cast(e))
+{
+ jassert(chorusEffect != nullptr);
+
+ primaryColor = juce::Colours::cornflowerblue;
+
+ using Track = juce::Grid::TrackInfo;
+ using Fr = juce::Grid::Fr;
+
+ grid.templateRows = { Track(Fr(1)), Track(Fr(2)) };
+ grid.templateColumns = { Track(Fr(1)), Track(Fr(1)), Track(Fr(1)) };
+
+ // ----- Sliders -----
+ // Depth
+ depthSlider.setSliderStyle(juce::Slider::Rotary);
+ depthSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20);
+ depthSlider.setRange(0.0, 1.0, 0.01);
+ depthSlider.setValue(chorusEffect->getDepth());
+ depthSlider.onValueChange = [this]() {
+ chorusEffect->setDepth(depthSlider.getValue());
+ };
+ depthLabel.setText("Depth", juce::dontSendNotification);
+ depthLabel.setJustificationType(juce::Justification::centred);
+
+ // Rate
+ rateSlider.setSliderStyle(juce::Slider::Rotary);
+ rateSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20);
+ rateSlider.setRange(0.0, 10.0, 0.01);
+ rateSlider.setValue(chorusEffect->getRate());
+ rateSlider.onValueChange = [this]() {
+ chorusEffect->setRate(rateSlider.getValue());
+ };
+ rateLabel.setText("Rate", juce::dontSendNotification);
+ rateLabel.setJustificationType(juce::Justification::centred);
+
+ // Mix
+ mixSlider.setSliderStyle(juce::Slider::Rotary);
+ mixSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20);
+ mixSlider.setRange(0.0, 1.0, 0.01);
+ mixSlider.setValue(chorusEffect->getMix());
+ mixSlider.onValueChange = [this]() {
+ chorusEffect->setMix(mixSlider.getValue());
+ };
+ mixLabel.setText("Mix", juce::dontSendNotification);
+ mixLabel.setJustificationType(juce::Justification::centred);
+
+ // ----- Ajouter au Grid -----
+ grid.items.addArray({
+ juce::GridItem(depthLabel), juce::GridItem(rateLabel), juce::GridItem(mixLabel),
+ juce::GridItem(depthSlider), juce::GridItem(rateSlider), juce::GridItem(mixSlider),
+ });
+
+ settingsLayout = new PedalSettingsLayoutComponent(&grid);
+
+ addAndMakeVisible(settingsLayout); // <<== Ajout crucial pour afficher le layout
+
+ if (chorusEffect != nullptr)
+ {
+ double defaultSampleRate = 44100.0;
+ int defaultBlockSize = 512;
+ int defaultNumChannels = 2;
+
+ chorusEffect->prepare(defaultSampleRate, defaultBlockSize, defaultNumChannels);
+ }
+
+ // Ajout visible
+ addAndMakeVisible(depthSlider);
+ addAndMakeVisible(depthLabel);
+ addAndMakeVisible(rateSlider);
+ addAndMakeVisible(rateLabel);
+ addAndMakeVisible(mixSlider);
+ addAndMakeVisible(mixLabel);
+
+ // Positionnement manuel (à ajuster en fonction de ta taille)
+ auto area = getLocalBounds();
+ int sliderWidth = area.getWidth() / 3;
+ int sliderHeight = area.getHeight() / 2;
+
+ depthSlider.setBounds(0, sliderHeight, sliderWidth, sliderHeight);
+ depthLabel.setBounds(0, 0, sliderWidth, sliderHeight / 2);
+
+ rateSlider.setBounds(sliderWidth, sliderHeight, sliderWidth, sliderHeight);
+ rateLabel.setBounds(sliderWidth, 0, sliderWidth, sliderHeight / 2);
+
+ mixSlider.setBounds(sliderWidth * 2, sliderHeight, sliderWidth, sliderHeight);
+ mixLabel.setBounds(sliderWidth * 2, 0, sliderWidth, sliderHeight / 2);
+
+
+ this->initializePedal();
+ setSize(300, 300);
+}
+
+
+
diff --git a/src/components/EffectComponent.cpp b/src/components/EffectComponent.cpp
new file mode 100644
index 0000000..fbcaa23
--- /dev/null
+++ b/src/components/EffectComponent.cpp
@@ -0,0 +1,10 @@
+#include "EffectComponent.h"
+#include "AbstractEffect.h"
+
+EffectComponent::EffectComponent(AbstractEffect* effect) : effect(effect) {
+ this->effect = effect;
+}
+
+AbstractEffect* EffectComponent::getEffect() {
+ return effect;
+}
\ No newline at end of file
diff --git a/src/components/LoginComponent.cpp b/src/components/LoginComponent.cpp
new file mode 100644
index 0000000..77dbaaa
--- /dev/null
+++ b/src/components/LoginComponent.cpp
@@ -0,0 +1,49 @@
+#include "LoginComponent.h"
+
+LoginComponent::LoginComponent() {
+ setupFields();
+ setupButtons();
+ layoutComponents();
+}
+
+LoginComponent::~LoginComponent() = default;
+
+void LoginComponent::setupFields() {
+ usernameField.setTextToShowWhenEmpty("Username", Colours::grey);
+ passwordField.setTextToShowWhenEmpty("Password", Colours::grey);
+ passwordField.setPasswordCharacter('*');
+ addAndMakeVisible(usernameField);
+ addAndMakeVisible(passwordField);
+}
+
+void LoginComponent::setupButtons() {
+ loginButton.onClick = [] {
+ // TODO: Implement login logic
+ };
+ skipButton.onClick = [this] { skipButtonClicked(); };
+ addAndMakeVisible(loginButton);
+ addAndMakeVisible(skipButton);
+}
+
+void LoginComponent::paint(Graphics& g) {
+ g.fillAll(Colours::white);
+}
+
+void LoginComponent::resized() {
+ layoutComponents();
+}
+
+void LoginComponent::layoutComponents() {
+ auto area = getLocalBounds().reduced(50);
+ auto halfWidth = area.getWidth() / 2;
+ auto halfHeight = area.getHeight() / 2;
+
+ usernameField.setBounds(halfWidth - 100, halfHeight - 80, 300, 40);
+ passwordField.setBounds(halfWidth - 100, halfHeight - 30, 300, 40);
+ loginButton.setBounds(halfWidth - 100, halfHeight + 30, 300, 40);
+ skipButton.setBounds(halfWidth - 100, halfHeight + 80, 300, 40);
+}
+
+void LoginComponent::skipButtonClicked() {
+ setVisible(false);
+}
\ No newline at end of file
diff --git a/src/components/PedalPowerIndicatorComponent.cpp b/src/components/PedalPowerIndicatorComponent.cpp
new file mode 100644
index 0000000..2d3724a
--- /dev/null
+++ b/src/components/PedalPowerIndicatorComponent.cpp
@@ -0,0 +1,20 @@
+#include "PedalPowerIndicatorComponent.h"
+
+PedalPowerIndicatorComponent::PedalPowerIndicatorComponent(bool isEnabled)
+{
+ this->isEnabled = isEnabled;
+}
+
+PedalPowerIndicatorComponent::~PedalPowerIndicatorComponent() = default;
+
+void PedalPowerIndicatorComponent::paint(juce::Graphics &g)
+{
+ g.setColour(isEnabled ? juce::Colours::green : juce::Colours::red);
+ g.fillEllipse(juce::Rectangle((getWidth() / 2) - 10,(getHeight() / 2) - 10,20,20));
+}
+
+void PedalPowerIndicatorComponent::togglePower(bool isEnabled)
+{
+ this->isEnabled = isEnabled;
+ repaint();
+}
\ No newline at end of file
diff --git a/src/components/PedalSettingsLayoutComponent.cpp b/src/components/PedalSettingsLayoutComponent.cpp
new file mode 100644
index 0000000..684216d
--- /dev/null
+++ b/src/components/PedalSettingsLayoutComponent.cpp
@@ -0,0 +1,13 @@
+#include "PedalSettingsLayoutComponent.h"
+
+PedalSettingsLayoutComponent::PedalSettingsLayoutComponent(juce::Grid* layout)
+ : layout(layout) {}
+
+PedalSettingsLayoutComponent::~PedalSettingsLayoutComponent() = default;
+
+void PedalSettingsLayoutComponent::paint(juce::Graphics& g) {
+}
+
+void PedalSettingsLayoutComponent::resized() {
+ layout->performLayout(getLocalBounds());
+}
\ No newline at end of file
diff --git a/src/components/PedalboardComponent.cpp b/src/components/PedalboardComponent.cpp
new file mode 100644
index 0000000..6804424
--- /dev/null
+++ b/src/components/PedalboardComponent.cpp
@@ -0,0 +1,68 @@
+#include "PedalboardComponent.h"
+#include "EffectComponentFactory.h"
+
+PedalboardComponent::~PedalboardComponent() = default;
+
+PedalboardComponent::PedalboardComponent(AbstractEffect* pedalboard) : EffectComponent(pedalboard) {
+ if (Pedalboard* pedalboard = dynamic_cast(this->effect)) {
+ for (AbstractEffect *effect : pedalboard->getEffects()) {
+ addEffect(EffectComponentFactory::CreateEffectComponent(effect));
+ }
+ }
+ flexBox.flexDirection = juce::FlexBox::Direction::row;
+ flexBox.justifyContent = juce::FlexBox::JustifyContent::center;
+ flexBox.alignItems = juce::FlexBox::AlignItems::center;
+ flexBox.flexWrap = juce::FlexBox::Wrap::wrap;
+ for (auto &effectComponent : effectsComponents) {
+ flexBox.items.add(juce::FlexItem(*effectComponent).withWidth(effectComponent->getWidth()).withHeight(effectComponent->getHeight()).withMargin(PEDALS_MARGIN));
+ }
+}
+
+void PedalboardComponent::resized() {
+ flexBox.performLayout(getLocalBounds());
+}
+
+void PedalboardComponent::paint(juce::Graphics &g) {
+}
+
+void PedalboardComponent::addEffect(EffectComponent* effect) {
+ effectsComponents.push_back(effect);
+ addAndMakeVisible(effect);
+}
+
+
+int PedalboardComponent::getRequiredWidth() const {
+ int totalWidth = 0;
+ for (const auto& effectComponent : effectsComponents) {
+ totalWidth += effectComponent->getWidth() + PEDALS_MARGIN * 2;
+ }
+ return totalWidth;
+}
+
+int PedalboardComponent::getRequiredHeight(const int boardWidth) const {
+ if (effectsComponents.empty())
+ return 0;
+ int x = 0;
+ int maxHeightInRow = 0;
+ int totalHeight = 0;
+ const int margin = PEDALS_MARGIN;
+
+ for (const auto* effectComponent : effectsComponents) {
+ int effectWidth = effectComponent->getWidth() + margin * 2;
+ int effectHeight = effectComponent->getHeight() + margin * 2;
+
+ if (x + effectWidth > boardWidth && x > 0) {
+ totalHeight += maxHeightInRow;
+ x = 0;
+ maxHeightInRow = 0;
+ }
+
+ x += effectWidth;
+ if (effectHeight > maxHeightInRow)
+ maxHeightInRow = effectHeight;
+ }
+
+ totalHeight += maxHeightInRow;
+
+ return totalHeight;
+}
\ No newline at end of file
diff --git a/src/components/SettingsComponent.cpp b/src/components/SettingsComponent.cpp
new file mode 100644
index 0000000..6c3e3fd
--- /dev/null
+++ b/src/components/SettingsComponent.cpp
@@ -0,0 +1,12 @@
+#include "SettingsComponent.h"
+
+SettingsComponent::SettingsComponent(juce::AudioDeviceManager& deviceManager)
+ : deviceSelector(deviceManager, 0, 2, 0, 2, true, true, false, false)
+{
+ addAndMakeVisible(deviceSelector);
+}
+
+void SettingsComponent::resized()
+{
+ deviceSelector.setBounds(10, 50, getWidth() - 20, getHeight() - 60);
+}
diff --git a/src/components/TopMenuBarComponent.cpp b/src/components/TopMenuBarComponent.cpp
new file mode 100644
index 0000000..322d024
--- /dev/null
+++ b/src/components/TopMenuBarComponent.cpp
@@ -0,0 +1,148 @@
+#include "AccountComponent.h"
+#include "ModalOverlayComponent.h"
+#include "PopupContentComponent.h"
+#include "SettingsComponent.h"
+#include "TopMenuBarComponent.h"
+#include "ResourceManager.h"
+
+TopMenuBarComponent::TopMenuBarComponent(AudioDeviceManager& deviceManager,
+ bool* isMuted) {
+ this->isSoundMuted = isMuted;
+
+ Image settingsImage = ResourceManager::loadImage(
+ "resources/icons/settings.png");
+ if (settingsImage.isValid()) {
+ settingsButton.setImages(true, true, true, settingsImage, 1.0f, {},
+ settingsImage, 1.0f, {}, settingsImage, 1.0f,
+ {});
+ settingsButton.setSize(settingsImage.getWidth(),
+ settingsImage.getHeight());
+ addAndMakeVisible(settingsButton);
+ } else {
+ DBG("Erreur : image settings.png introuvable ou invalide.");
+ }
+
+ Image accountImage = ResourceManager::loadImage(
+ "resources/icons/account.png");
+ if (accountImage.isValid()) {
+ accountButton.setImages(true, true, true, accountImage, 1.0f, {},
+ accountImage, 1.0f, {}, accountImage, 1.0f, {});
+ accountButton.setSize(accountImage.getWidth(),
+ accountImage.getHeight());
+ addAndMakeVisible(accountButton);
+ } else {
+ DBG("Erreur : image account.png introuvable ou invalide.");
+ }
+
+ Image muteImage = ResourceManager::loadImage("resources/icons/unmute.png");
+ if (muteImage.isValid()) {
+ muteButton.setImages(true, true, true, muteImage, 1.0f, {}, muteImage,
+ 1.0f, {}, muteImage, 1.0f, {});
+ muteButton.setSize(muteImage.getWidth(), muteImage.getHeight());
+ addAndMakeVisible(muteButton);
+ } else {
+ DBG("Erreur : image mute.png introuvable ou invalide.");
+ }
+
+#if !JUCE_IOS
+ settingsButton.onClick = [this, &deviceManager] {
+ openSettingsPopup(deviceManager);
+ };
+#endif
+ accountButton.onClick = [this] { openAccountPopup(); };
+ muteButton.onClick = [this] { toggleMute(); };
+
+ flexBox.justifyContent = FlexBox::JustifyContent::flexEnd;
+ flexBox.alignItems = FlexBox::AlignItems::center;
+ flexBox.items.add(
+ FlexItem(muteButton).withWidth(buttonSize).withHeight(buttonSize).
+ withMargin({0, gap, 0, 0}));
+ flexBox.items.add(
+ FlexItem(settingsButton).withWidth(buttonSize).withHeight(buttonSize).
+ withMargin({0, gap, 0, 0}));
+ flexBox.items.add(
+ FlexItem(accountButton).withWidth(buttonSize).withHeight(buttonSize).
+ withMargin({0, gap, 0, 0}));
+}
+
+TopMenuBarComponent::~TopMenuBarComponent() = default;
+
+void TopMenuBarComponent::paint(Graphics& g) {
+ const ColourGradient gradient(Colours::black, 0, 0,
+ Colours::transparentBlack, 0,
+ static_cast(getHeight()), false);
+ g.setGradientFill(gradient);
+ g.fillAll();
+
+ const FontOptions font("Times New Roman", 24.0f, Font::bold | Font::italic);
+ g.setFont(font);
+ g.setColour(Colours::white);
+ const int topMargin = (getHeight() - 24) / 2;
+
+ g.drawText("kAmp", gap, topMargin, 80, 24, Justification::left);
+}
+
+
+void TopMenuBarComponent::resized() {
+ auto* mainWindow = getTopLevelComponent();
+ if (mainWindow == nullptr)
+ return;
+ if (modalOverlay != nullptr) {
+ modalOverlay->setBounds(mainWindow->getLocalBounds());
+ }
+ if (settingsComponent != nullptr) {
+ settingsComponent->setBounds(mainWindow->getLocalBounds());
+ }
+ if (accountComponent != nullptr) {
+ accountComponent->setBounds(mainWindow->getLocalBounds());
+ }
+ flexBox.performLayout(getLocalBounds());
+}
+
+void TopMenuBarComponent::openSettingsPopup(AudioDeviceManager& deviceManager) {
+ settingsComponent = new SettingsComponent(deviceManager);
+ auto* mainWindow = getTopLevelComponent();
+ if (mainWindow == nullptr)
+ return;
+
+ modalOverlay = new ModalOverlayComponent("Audio settings",
+ settingsComponent);
+ mainWindow->addAndMakeVisible(modalOverlay);
+ modalOverlay->setBounds(mainWindow->getLocalBounds());
+}
+
+void TopMenuBarComponent::openAccountPopup() {
+ accountComponent = new AccountComponent();
+ auto* mainWindow = getTopLevelComponent();
+ if (mainWindow == nullptr)
+ return;
+
+ modalOverlay = new ModalOverlayComponent("Account", accountComponent);
+ mainWindow->addAndMakeVisible(modalOverlay);
+ modalOverlay->setBounds(mainWindow->getLocalBounds());
+}
+
+void TopMenuBarComponent::toggleMute() {
+ if (*this->isSoundMuted) {
+ Image muteImage = ResourceManager::loadImage(
+ "resources/icons/unmute.png");
+ if (muteImage.isValid()) {
+ muteButton.setImages(false, true, true, muteImage, 1.0f, {},
+ muteImage, 1.0f, {}, muteImage, 1.0f, {});
+ addAndMakeVisible(muteButton);
+ } else {
+ DBG("Erreur : image mute.png introuvable ou invalide.");
+ }
+ } else {
+ Image muteImage =
+ ResourceManager::loadImage("resources/icons/mute.png");
+ if (muteImage.isValid()) {
+ muteButton.setImages(false, true, true, muteImage, 1.0f, {},
+ muteImage, 1.0f, {}, muteImage, 1.0f, {});
+ addAndMakeVisible(muteButton);
+ } else {
+ DBG("Erreur : image mute.png introuvable ou invalide.");
+ }
+ }
+ *(this->isSoundMuted) = !(*(this->isSoundMuted));
+}
diff --git a/src/components/effects/BasePedalComponent.cpp b/src/components/effects/BasePedalComponent.cpp
new file mode 100644
index 0000000..9409b25
--- /dev/null
+++ b/src/components/effects/BasePedalComponent.cpp
@@ -0,0 +1,68 @@
+#include "BasePedalComponent.h"
+#include "ResourceManager.h"
+
+BasePedalComponent::BasePedalComponent(AbstractEffect* effect) :
+ EffectComponent(effect) {
+ isEnabled = effect->isEnabled;
+}
+
+BasePedalComponent::~BasePedalComponent() = default;
+
+void BasePedalComponent::paint(Graphics& g) {
+ g.setColour(primaryColor);
+ g.fillRoundedRectangle(0, 0, getWidth(), getHeight(), 15);
+}
+
+void BasePedalComponent::resized() {
+ pedalLayout.performLayout(getLocalBounds());
+}
+
+void BasePedalComponent::onEnableButtonClicked() {
+ *isEnabled = !(*isEnabled);
+ enablePedalButton.setToggleState(*isEnabled, dontSendNotification);
+ isEnabledIndicator->togglePower(*isEnabled);
+}
+
+void BasePedalComponent::initializePedal() {
+ isEnabledIndicator = new PedalPowerIndicatorComponent(*isEnabled);
+ pedalLabel = new Label();
+ pedalLabel->setText(getEffect()->effectName, dontSendNotification);
+ pedalLabel->setJustificationType(Justification::centred);
+ pedalLabel->setFont(FontOptions(30.0f, Font::bold));
+
+ Image powerImage = ResourceManager::loadImage("resources/icons/power.png");
+ if (powerImage.isValid()) {
+ enablePedalButton.setImages(true, true, true,
+ powerImage, 1.0f, {},
+ powerImage, 1.0f, {},
+ powerImage, 1.0f, {});
+ enablePedalButton.onClick = [this] {
+ this->onEnableButtonClicked();
+ };
+ addAndMakeVisible(enablePedalButton);
+ } else {
+ DBG("Erreur : image power.png introuvable ou invalide.");
+ }
+
+ addAndMakeVisible(*pedalLabel);
+ addAndMakeVisible(*isEnabledIndicator);
+
+ using Track = Grid::TrackInfo;
+ using Fr = Grid::Fr;
+ pedalLayout.templateRows = {
+ Track(Fr(2)),
+ Track(Fr(1)),
+ Track(Fr(1)),
+ Track(Fr(1))
+ };
+ pedalLayout.templateColumns = {
+ Track(Fr(1))
+ };
+
+ pedalLayout.items = {
+ GridItem(*settingsLayout),
+ GridItem(enablePedalButton),
+ GridItem(*isEnabledIndicator),
+ GridItem(*pedalLabel),
+ };
+}
\ No newline at end of file
diff --git a/src/components/effects/DelayEffectComponent.cpp b/src/components/effects/DelayEffectComponent.cpp
new file mode 100644
index 0000000..744780c
--- /dev/null
+++ b/src/components/effects/DelayEffectComponent.cpp
@@ -0,0 +1,71 @@
+#include "DelayEffectComponent.h"
+
+DelayEffectComponent::DelayEffectComponent() : BasePedalComponent(
+ new DelayEffect()) {}
+
+DelayEffectComponent::DelayEffectComponent(AbstractEffect* effect) :
+ BasePedalComponent(effect) {
+ if (auto delayEffect = dynamic_cast(effect)) {
+ primaryColor = Colours::mediumblue;
+ using Track = Grid::TrackInfo;
+ using Fr = Grid::Fr;
+ grid.templateRows = {Track(Fr(1)), Track(Fr(3))};
+ grid.templateColumns = {Track(Fr(1)), Track(Fr(1))};
+ grid.items = {
+ GridItem(rateLabel),
+ GridItem(delayLabel),
+ GridItem(rateSlider),
+ GridItem(delaySlider),
+ };
+ rateSlider.setSliderStyle(Slider::SliderStyle::RotaryVerticalDrag);
+ rateSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20);
+ rateSlider.setColour(Slider::textBoxOutlineColourId,
+ Colours::transparentWhite);
+ rateSlider.setTextValueSuffix("%");
+ rateSlider.setTitle("Rate");
+ rateSlider.setRange(0.0, 100.0, 1);
+ rateSlider.setValue(50.0);
+ rateSlider.onValueChange = [this, delayEffect] {
+ delayEffect->setRate(rateSlider.getValue());
+ };
+
+ rateLabel.setText("Rate", dontSendNotification);
+ rateLabel.setJustificationType(Justification::centred);
+ rateLabel.attachToComponent(&rateSlider, false);
+
+ delaySlider.setSliderStyle(Slider::SliderStyle::RotaryVerticalDrag);
+ delaySlider.setTextValueSuffix("ms");
+ delaySlider.setTitle("Delay");
+ delaySlider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20);
+ delaySlider.setColour(Slider::textBoxOutlineColourId,
+ Colours::transparentWhite);
+ delaySlider.setRange(0.0, 3000.0, 10);
+ delaySlider.setValue(500.0);
+ delaySlider.onValueChange = [this, delayEffect] {
+ delayEffect->setDelay(delaySlider.getValue());
+ };
+
+ delayLabel.setText("Delay", dontSendNotification);
+ delayLabel.setJustificationType(Justification::centred);
+ delayLabel.attachToComponent(&delaySlider, false);
+
+ rateSlider.setBounds(0, 0, 200, 200);
+ delaySlider.setBounds(300, 0, 200, 200);
+ rateLabel.setBounds(rateSlider.getX(), rateSlider.getY() + 20,
+ rateSlider.getWidth(), 20);
+ delayLabel.setBounds(delaySlider.getX(), delaySlider.getY() + 20,
+ delaySlider.getWidth(), 20);
+
+ settingsLayout = new PedalSettingsLayoutComponent(&grid);
+
+ addAndMakeVisible(rateSlider);
+ addAndMakeVisible(rateLabel);
+ addAndMakeVisible(delaySlider);
+ addAndMakeVisible(delayLabel);
+
+ this->initializePedal();
+ setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+ }
+}
+
+DelayEffectComponent::~DelayEffectComponent() = default;
\ No newline at end of file
diff --git a/src/components/effects/DistortionEffectComponent.cpp b/src/components/effects/DistortionEffectComponent.cpp
new file mode 100644
index 0000000..a92b7c5
--- /dev/null
+++ b/src/components/effects/DistortionEffectComponent.cpp
@@ -0,0 +1,95 @@
+#include "DistortionEffect.h"
+#include "DistortionEffectComponent.h"
+
+DistortionEffectComponent::DistortionEffectComponent()
+ : BasePedalComponent(new DistortionEffect()) {}
+
+DistortionEffectComponent::DistortionEffectComponent(AbstractEffect* effect)
+ : BasePedalComponent(effect)
+{
+ if (auto* distEffect = dynamic_cast(effect)) {
+ primaryColor = juce::Colours::darkviolet;
+
+ // Level
+ levelSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
+ levelSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 20);
+ levelSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ levelSlider.setRange(0.0, 1.0, 0.01);
+ levelSlider.setValue(distEffect->getLevel());
+ levelSlider.onValueChange = [distEffect, this] {
+ distEffect->setLevel(static_cast(levelSlider.getValue()));
+ };
+ levelLabel.setText("Level", juce::dontSendNotification);
+ levelLabel.attachToComponent(&levelSlider, false);
+ levelLabel.setJustificationType(juce::Justification::centred);
+
+ // Tone
+ toneSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
+ toneSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 20);
+ toneSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ toneSlider.setRange(0.0, 1.0, 0.01);
+ toneSlider.setValue(distEffect->getTone());
+ toneSlider.onValueChange = [distEffect, this] {
+ distEffect->setTone(static_cast(toneSlider.getValue()));
+ };
+ toneLabel.setText("Tone", juce::dontSendNotification);
+ toneLabel.attachToComponent(&toneSlider, false);
+ toneLabel.setJustificationType(juce::Justification::centred);
+
+ // Dist
+ distSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
+ distSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 20);
+ distSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ distSlider.setRange(0.0, 10.0, 0.01);
+ distSlider.setValue(distEffect->getDist());
+ distSlider.onValueChange = [distEffect, this] {
+ distEffect->setDist(static_cast(distSlider.getValue()));
+ };
+ distLabel.setText("Dist", juce::dontSendNotification);
+ distLabel.attachToComponent(&distSlider, false);
+ distLabel.setJustificationType(juce::Justification::centred);
+
+ // Turbo
+ turboButton.setToggleState(distEffect->isTurbo(), juce::dontSendNotification);
+ turboButton.onClick = [distEffect, this] {
+ distEffect->setTurbo(turboButton.getToggleState());
+ };
+
+ turboLabel.setText("Turbo", juce::dontSendNotification);
+ turboLabel.attachToComponent(&turboButton, false);
+ turboLabel.setJustificationType(juce::Justification::centred);
+
+ // Layout
+ using Track = juce::Grid::TrackInfo;
+ using Fr = juce::Grid::Fr;
+ grid.templateRows = { Track (Fr (1)), Track (Fr (3)) };
+ grid.templateColumns = { Track (Fr (1)), Track (Fr (1)), Track (Fr (1)), Track (Fr (1)) };
+ grid.items = {
+ juce::GridItem(toneLabel),
+ juce::GridItem(distLabel),
+ juce::GridItem(levelLabel),
+ juce::GridItem(turboLabel),
+ juce::GridItem(toneSlider),
+ juce::GridItem(distSlider),
+ juce::GridItem(levelSlider),
+ juce::GridItem(turboButton)
+ };
+
+ settingsLayout = new PedalSettingsLayoutComponent(&grid);
+
+ addAndMakeVisible(levelSlider);
+ addAndMakeVisible(levelLabel);
+ addAndMakeVisible(toneSlider);
+ addAndMakeVisible(toneLabel);
+ addAndMakeVisible(distSlider);
+ addAndMakeVisible(distLabel);
+ addAndMakeVisible(turboButton);
+ addAndMakeVisible(turboLabel);
+
+ this->initializePedal();
+
+ setSize(400, DEFAULT_HEIGHT);
+ }
+}
+
+DistortionEffectComponent::~DistortionEffectComponent() = default;
\ No newline at end of file
diff --git a/src/components/effects/EqualizerEffectComponent.cpp b/src/components/effects/EqualizerEffectComponent.cpp
new file mode 100644
index 0000000..ab116e2
--- /dev/null
+++ b/src/components/effects/EqualizerEffectComponent.cpp
@@ -0,0 +1,72 @@
+#include "EqualizerEffectComponent.h"
+#include
+
+#include "EqualizerEffectComponent.h"
+
+
+EqualizerEffectComponent::EqualizerEffectComponent(AbstractEffect* effect)
+ : BasePedalComponent(effect), eqEffect(dynamic_cast(effect)) {
+
+ jassert(eqEffect != nullptr);
+
+ primaryColor = juce::Colours::orange;
+
+ const char* freqLabels[10] = { "31Hz", "62Hz", "125Hz", "250Hz", "500Hz", "1k", "2k", "4k", "8k", "16k" };
+
+ using Track = juce::Grid::TrackInfo;
+ using Fr = juce::Grid::Fr;
+
+ // 2 lignes : sliders + labels
+ grid.templateRows = { Track(Fr(1)), Track(Fr(3)) };
+ grid.templateColumns = { Track(Fr(1)), Track(Fr(1)), Track(Fr(1)), Track(Fr(1)), Track(Fr(1)),
+ Track(Fr(1)), Track(Fr(1)), Track(Fr(1)), Track(Fr(1)), Track(Fr(1)) };
+
+ for (int i = 0; i < 10; ++i) {
+ sliders[i].setSliderStyle(juce::Slider::LinearVertical);
+ sliders[i].setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20);
+ sliders[i].setRange(-24.0, 24.0, 0.1);
+ sliders[i].setValue(0.0);
+ sliders[i].setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ sliders[i].onValueChange = [this, i]
+ {
+ if (static_cast(sliders[i].getValue())) {
+ eqEffect->setGain(i, static_cast(sliders[i].getValue()));
+ }
+ };
+ sliders[i].setTitle(freqLabels[i]);
+ sliders[i].setBounds(i * (getWidth() / 10), 0, getWidth() / 10, (getHeight() / 5) * 4);
+
+ labels[i].setText(freqLabels[i], juce::dontSendNotification);
+ labels[i].setJustificationType(juce::Justification::centred);
+ labels[i].attachToComponent(&sliders[i], false);
+ labels[i].setBounds(i * (getWidth() / 10), 0, getWidth() / 10, getHeight() / 5);
+
+ addAndMakeVisible(sliders[i]);
+ addAndMakeVisible(labels[i]);
+ }
+
+ for (int i = 0; i < 10; ++i)
+ {
+ grid.items.add(GridItem(labels[i]));
+ }
+ for (int i = 0; i < 10; ++i)
+ {
+ grid.items.add(juce::GridItem(sliders[i]));
+ }
+
+ settingsLayout = new PedalSettingsLayoutComponent(&grid);
+
+ this->initializePedal();
+ setSize(400, 300);
+}
+
+
+
+void EqualizerEffectComponent::sliderValueChanged(juce::Slider* slider) {
+ for (int i = 0; i < 10; ++i) {
+ if (slider == &sliders[i]) {
+ eqEffect->setGain(i, slider->getValue());
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/effects/NoiseGateEffectComponent.cpp b/src/components/effects/NoiseGateEffectComponent.cpp
new file mode 100644
index 0000000..0389c56
--- /dev/null
+++ b/src/components/effects/NoiseGateEffectComponent.cpp
@@ -0,0 +1,109 @@
+#include "NoiseGateEffectComponent.h"
+#include "NoiseGateEffect.h"
+
+NoiseGateEffectComponent::NoiseGateEffectComponent(AbstractEffect* effect): BasePedalComponent(effect)
+{
+ if (NoiseGateEffect* noiseGateEffect = dynamic_cast(effect))
+ {
+ primaryColor = juce::Colours::darkgrey;
+ using Track = juce::Grid::TrackInfo;
+ using Fr = juce::Grid::Fr;
+
+ grid.templateRows = { Track(Fr(1)), Track(Fr(3)) };
+ grid.templateColumns = { Track(Fr(1)), Track(Fr(1)), Track(Fr(1)) };
+
+ thresholdSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
+ thresholdSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 100, 20);
+ thresholdSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ thresholdSlider.setTextValueSuffix("dB");
+ thresholdSlider.setTitle("Threshold");
+ thresholdSlider.setRange(-100.0f, 0.0f, 0.1f);
+ thresholdSlider.setValue(-50.0f);
+ thresholdSlider.onValueChange = [this, noiseGateEffect] {
+ this->onThreholdValueChanged(noiseGateEffect, thresholdSlider.getValue());
+ };
+
+ thresholdLabel.setText("Threshold", juce::dontSendNotification);
+ thresholdLabel.setJustificationType(juce::Justification::centred);
+ thresholdLabel.attachToComponent(&thresholdSlider, false);
+
+ attackSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
+ attackSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 100, 20);
+ attackSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ attackSlider.setTextValueSuffix("ms");
+ attackSlider.setTitle("Attack");
+ attackSlider.setRange(0.0f, 100.0f, 0.1f);
+ attackSlider.setValue(10.0f);
+ attackSlider.onValueChange = [this, noiseGateEffect] {
+ this->onAttackValueChanged(noiseGateEffect, attackSlider.getValue());
+ };
+
+ attackLabel.setText("Attack", juce::dontSendNotification);
+ attackLabel.setJustificationType(juce::Justification::centred);
+ attackLabel.attachToComponent(&attackSlider, false);
+
+ releaseSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
+ releaseSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 100, 20);
+ releaseSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite);
+ releaseSlider.setTextValueSuffix("ms");
+ releaseSlider.setTitle("Release");
+ releaseSlider.setRange(0.0f, 1000.0f, 1.0f);
+ releaseSlider.setValue(100.0f);
+ releaseSlider.onValueChange = [this, noiseGateEffect] {
+ this->onReleaseValueChanged(noiseGateEffect, releaseSlider.getValue());
+ };
+
+ releaseLabel.setText("Release", juce::dontSendNotification);
+ releaseLabel.setJustificationType(juce::Justification::centred);
+ releaseLabel.attachToComponent(&releaseSlider, false);
+
+ grid.items = {
+ juce::GridItem(thresholdLabel),
+ juce::GridItem(attackLabel),
+ juce::GridItem(releaseLabel),
+ juce::GridItem(thresholdSlider),
+ juce::GridItem(attackSlider),
+ juce::GridItem(releaseSlider)
+ };
+
+ this->settingsLayout = new PedalSettingsLayoutComponent(&grid);
+
+ addAndMakeVisible(thresholdSlider);
+ addAndMakeVisible(thresholdLabel);
+ addAndMakeVisible(attackSlider);
+ addAndMakeVisible(attackLabel);
+ addAndMakeVisible(releaseSlider);
+ addAndMakeVisible(releaseLabel);
+
+ this->initializePedal();
+ setSize(300, 300);
+ }
+}
+
+NoiseGateEffectComponent::~NoiseGateEffectComponent() = default;
+
+void NoiseGateEffectComponent::onThreholdValueChanged(NoiseGateEffect* effect, double value)
+{
+ if (float threshold = static_cast(value))
+ {
+ effect->setThreshold(threshold);
+ }
+}
+
+void NoiseGateEffectComponent::onAttackValueChanged(NoiseGateEffect* effect, double value)
+{
+ if (float attack = static_cast(value))
+ {
+ effect->setAttack(attack);
+ }
+}
+
+void NoiseGateEffectComponent::onReleaseValueChanged(NoiseGateEffect* effect, double value)
+{
+ if (float release = static_cast(value))
+ {
+ effect->setRelease(release);
+ }
+}
+
+
diff --git a/src/effects/ChorusEffect.cpp b/src/effects/ChorusEffect.cpp
new file mode 100644
index 0000000..c1c0cac
--- /dev/null
+++ b/src/effects/ChorusEffect.cpp
@@ -0,0 +1,93 @@
+#include "ChorusEffect.h"
+
+ChorusEffect::ChorusEffect() {
+ effectName = "Chorus";
+ updateParameters();
+}
+
+void ChorusEffect::apply(const juce::AudioSourceChannelInfo& bufferToFill) {
+ auto* buffer = bufferToFill.buffer;
+ if (buffer == nullptr || buffer->getNumSamples() == 0 || buffer->getNumChannels() == 0)
+ return;
+
+ juce::dsp::AudioBlock block(*buffer);
+ juce::dsp::ProcessContextReplacing context(block);
+
+ chorus.process(context);
+}
+
+
+void ChorusEffect::updateParameters() {
+ chorus.setRate(rate);
+ chorus.setDepth(depth);
+ chorus.setCentreDelay(centreDelay);
+ chorus.setFeedback(feedback);
+ chorus.setMix(mix);
+}
+
+float ChorusEffect::getDepth() const { return depth; }
+float ChorusEffect::getRate() const { return rate; }
+float ChorusEffect::getMix() const { return mix; }
+
+void ChorusEffect::setRate(float newRate) {
+ rate = newRate;
+ chorus.setRate(rate);
+}
+
+void ChorusEffect::setDepth(float newDepth) {
+ depth = newDepth;
+ chorus.setDepth(depth);
+}
+
+void ChorusEffect::setCentreDelay(float newCentreDelay) {
+ centreDelay = newCentreDelay;
+ chorus.setCentreDelay(centreDelay);
+}
+
+void ChorusEffect::setFeedback(float newFeedback) {
+ feedback = newFeedback;
+ chorus.setFeedback(feedback);
+}
+
+void ChorusEffect::setMix(float newMix) {
+ mix = newMix;
+ chorus.setMix(mix);
+}
+
+void ChorusEffect::prepare(double sampleRate, int samplesPerBlock, int numChannels)
+{
+ juce::dsp::ProcessSpec spec;
+ spec.sampleRate = sampleRate;
+ spec.maximumBlockSize = static_cast(samplesPerBlock);
+ spec.numChannels = static_cast(numChannels);
+
+ chorus.prepare(spec);
+}
+
+
+bool ChorusEffect::operator==(const AbstractEffect* effect) {
+ return this == effect;
+}
+
+juce::var ChorusEffect::toJSON() const {
+ auto obj = AbstractEffect::toJSON();
+ if (auto* dynamicObj = obj.getDynamicObject()) {
+ dynamicObj->setProperty("rate", rate);
+ dynamicObj->setProperty("depth", depth);
+ dynamicObj->setProperty("centreDelay", centreDelay);
+ dynamicObj->setProperty("feedback", feedback);
+ dynamicObj->setProperty("mix", mix);
+ }
+ return obj;
+}
+
+void ChorusEffect::fromJSON(const juce::var& json) {
+ AbstractEffect::fromJSON(json);
+ if (const auto* obj = json.getDynamicObject()) {
+ if (obj->hasProperty("rate")) setRate((float)obj->getProperty("rate"));
+ if (obj->hasProperty("depth")) setDepth((float)obj->getProperty("depth"));
+ if (obj->hasProperty("centreDelay")) setCentreDelay((float)obj->getProperty("centreDelay"));
+ if (obj->hasProperty("feedback")) setFeedback((float)obj->getProperty("feedback"));
+ if (obj->hasProperty("mix")) setMix((float)obj->getProperty("mix"));
+ }
+}
diff --git a/src/effects/DelayEffect.cpp b/src/effects/DelayEffect.cpp
new file mode 100644
index 0000000..fd19713
--- /dev/null
+++ b/src/effects/DelayEffect.cpp
@@ -0,0 +1,48 @@
+#include "DelayEffect.h"
+
+DelayEffect::DelayEffect() {
+ rate = 50.0;
+ delay = 500.0;
+ effectName = "Delay";
+}
+
+DelayEffect::~DelayEffect() = default;
+
+void DelayEffect::apply(const AudioSourceChannelInfo &bufferToFill) {
+ if (delay > 0 && rate > 0) {
+ const int numChannels = bufferToFill.buffer->getNumChannels();
+ const int numSamples = bufferToFill.numSamples;
+ constexpr auto sampleRate = 44100.0;
+ const auto delaySamples = static_cast(delay * sampleRate / 1000.0f);
+
+ if (circularBuffer.size() < delaySamples) {
+ circularBuffer.resize(delaySamples, 0.0f);
+ writePosition = 0;
+ }
+
+
+ for (int i = 0; i < numSamples; ++i) {
+ auto delayedSample = circularBuffer[writePosition];
+ for (int channel = 0; channel < numChannels; ++channel)
+ {
+ auto* channelBuffer = bufferToFill.buffer->getWritePointer(channel);
+ auto inputSample = channelBuffer[i];
+ channelBuffer[i] = inputSample + delayedSample * (rate / 100.0f);
+ circularBuffer[writePosition] = inputSample;
+ }
+ writePosition = (writePosition + 1) % delaySamples;
+ }
+ }
+}
+
+void DelayEffect::setRate(const float rate) {
+ this->rate = rate;
+}
+
+void DelayEffect::setDelay(const float delay) {
+ this->delay = delay;
+}
+
+bool DelayEffect::operator==(const AbstractEffect* effect) {
+ return this == effect;
+}
diff --git a/src/effects/DistortionEffect.cpp b/src/effects/DistortionEffect.cpp
new file mode 100644
index 0000000..c54b1e1
--- /dev/null
+++ b/src/effects/DistortionEffect.cpp
@@ -0,0 +1,90 @@
+#include "DistortionEffect.h"
+#include
+
+float gDrive = 1.0f;
+bool gTurbo = false;
+
+DistortionEffect::DistortionEffect() {
+ effectName = "Distortion";
+ updateWaveshaper();
+ updateTone();
+ processorChain.get<3>().setGainLinear(level);
+}
+
+DistortionEffect::~DistortionEffect() = default;
+
+void DistortionEffect::prepare(const juce::dsp::ProcessSpec& spec) {
+ processorChain.prepare(spec);
+ // High-pass à 120 Hz
+ *processorChain.get<0>().coefficients = *juce::dsp::IIR::Coefficients::makeHighPass(spec.sampleRate, 120.0f);
+ updateTone();
+}
+
+void DistortionEffect::reset() noexcept {
+ processorChain.reset();
+}
+
+void DistortionEffect::apply(const juce::AudioSourceChannelInfo& bufferToFill) {
+ if (bufferToFill.buffer == nullptr) return;
+ juce::dsp::AudioBlock block(*bufferToFill.buffer, (size_t) bufferToFill.startSample);
+ const int numChannels = block.getNumChannels();
+ // auto subBlock = block.getSubBlock(0, (size_t) bufferToFill.numSamples);
+ // juce::dsp::ProcessContextReplacing context(subBlock);
+ for (int channel = 0; channel < numChannels; ++channel)
+ {
+ auto channelBlock = block.getSingleChannelBlock((size_t)channel);
+ juce::dsp::ProcessContextReplacing context(channelBlock);
+ processorChain.process(context);
+ }
+}
+
+// --- Paramètres ---
+
+void DistortionEffect::setLevel(float value) {
+ level = juce::jlimit(0.0f, 1.0f, value);
+ processorChain.get<3>().setGainLinear(level);
+}
+void DistortionEffect::setTone(float value) {
+ tone = juce::jlimit(0.0f, 1.0f, value);
+ updateTone();
+}
+void DistortionEffect::setDist(float value) {
+ dist = juce::jlimit(0.0f, 10.0f, value);
+ updateWaveshaper();
+}
+void DistortionEffect::setTurbo(bool enabled) {
+ turbo = enabled;
+ updateWaveshaper();
+ updateTone();
+}
+
+float DistortionEffect::getLevel() const { return level; }
+float DistortionEffect::getTone() const { return tone; }
+float DistortionEffect::getDist() const { return dist; }
+bool DistortionEffect::isTurbo() const { return turbo; }
+
+// --- DSP interne ---
+
+void DistortionEffect::updateTone() {
+ // Tone = low-pass variable, Turbo = plus d'aigus
+ float freq = 2000.0f + tone * (turbo ? 8000.0f : 4000.0f); // 2kHz à 10kHz (Turbo) ou 2kHz à 6kHz
+ *processorChain.get<2>().coefficients = *juce::dsp::IIR::Coefficients::makeLowPass(44100, freq);
+}
+
+float DistortionEffect::waveshaperFunc(float x) {
+ // Utilise les membres statiques pour le drive/turbo
+ // (Attention, non thread-safe, mais simple pour un test)
+ extern float gDrive;
+ extern bool gTurbo;
+ if (gTurbo)
+ return std::tanh(gDrive * x) * 1.2f;
+ else
+ return std::tanh(gDrive * x);
+}
+
+void DistortionEffect::updateWaveshaper() {
+ float drive = 1.0f + dist * (turbo ? 100.0f : 50.0f); // Turbo = plus de gain
+ gDrive = drive;
+ gTurbo = turbo;
+ processorChain.get<1>().functionToUse = &DistortionEffect::waveshaperFunc;
+}
\ No newline at end of file
diff --git a/src/effects/EffectsFactory.cpp b/src/effects/EffectsFactory.cpp
new file mode 100644
index 0000000..c9261f3
--- /dev/null
+++ b/src/effects/EffectsFactory.cpp
@@ -0,0 +1,16 @@
+#include "EffectsFactory.h"
+#include "DelayEffect.h"
+#include "DistortionEffect.h"
+#include "EqualizerEffect.h"
+#include "NoiseGateEffect.h"
+#include "ChorusEffect.h"
+
+std::vector EffectsFactory::createAllEffects() {
+ std::vector effects;
+ effects.push_back(new DelayEffect());
+ effects.push_back(new DistortionEffect());
+ effects.push_back(new EqualizerEffect());
+ effects.push_back(new NoiseGateEffect());
+ effects.push_back(new ChorusEffect());
+ return effects;
+}
diff --git a/src/effects/EqualizerEffect.cpp b/src/effects/EqualizerEffect.cpp
new file mode 100644
index 0000000..db7d960
--- /dev/null
+++ b/src/effects/EqualizerEffect.cpp
@@ -0,0 +1,78 @@
+#include "EqualizerEffect.h"
+#include
+
+EqualizerEffect::EqualizerEffect() {
+ effectName = "Equalizer";
+ filters.resize(numBands);
+ for (int i = 0; i < numBands; ++i) bandGains[i] = 0.0f;
+ updateFilters();
+}
+
+void EqualizerEffect::apply(const juce::AudioSourceChannelInfo &bufferToFill) {
+ auto* buffer = bufferToFill.buffer;
+ const int numChannels = buffer->getNumChannels();
+ const int numSamples = bufferToFill.numSamples;
+
+ juce::dsp::AudioBlock block(*buffer);
+
+ for (int channel = 0; channel < numChannels; ++channel) {
+ auto channelBlock = block.getSingleChannelBlock((size_t)channel);
+ juce::dsp::ProcessContextReplacing context(channelBlock);
+
+ for (int i = 0; i < numBands; ++i) {
+ filters[i].process(context);
+ }
+ }
+}
+
+void EqualizerEffect::setGain(int bandIndex, float gain) {
+ if (bandIndex >= 0 && bandIndex < numBands) {
+ bandGains[bandIndex] = gain;
+ updateFilters();
+ }
+}
+
+float EqualizerEffect::getGain(int bandIndex) const {
+ return (bandIndex >= 0 && bandIndex < numBands) ? bandGains[bandIndex] : 0.0f;
+}
+
+void EqualizerEffect::updateFilters() {
+ // Fréquences fixes typiques d'un égaliseur 10 bandes
+ const float freqs[10] = {31, 62, 125, 250, 500, 1000, 2000, 4000, 8000, 16000};
+ constexpr float sampleRate = 44100.0f;
+
+ for (int i = 0; i < numBands; ++i) {
+ filters[i].reset();
+ auto coeffs = juce::dsp::IIR::Coefficients::makePeakFilter(
+ sampleRate, freqs[i], 1.0f, juce::Decibels::decibelsToGain(bandGains[i])
+ );
+ filters[i].coefficients = coeffs;
+ }
+}
+
+bool EqualizerEffect::operator==(const AbstractEffect* effect) {
+ return this == effect;
+}
+
+juce::var EqualizerEffect::toJSON() const {
+ auto obj = AbstractEffect::toJSON();
+ if (auto *dynamicObj = obj.getDynamicObject()) {
+ juce::Array gains;
+ for (float g : bandGains) gains.add(g);
+ dynamicObj->setProperty("bandGains", gains);
+ }
+ return obj;
+}
+
+void EqualizerEffect::fromJSON(const juce::var &json) {
+ AbstractEffect::fromJSON(json);
+ if (const auto* obj = json.getDynamicObject()) {
+ const auto& gains = obj->getProperty("bandGains");
+ if (auto* arr = gains.getArray()) {
+ for (int i = 0; i < juce::jmin((int)arr->size(), numBands); ++i) {
+ bandGains[i] = static_cast((*arr)[i]);
+ }
+ updateFilters();
+ }
+ }
+}
diff --git a/src/effects/NoiseGateEffect.cpp b/src/effects/NoiseGateEffect.cpp
new file mode 100644
index 0000000..6bbe2ab
--- /dev/null
+++ b/src/effects/NoiseGateEffect.cpp
@@ -0,0 +1,57 @@
+#include "NoiseGateEffect.h"
+
+NoiseGateEffect::NoiseGateEffect() {
+ threshold = 0.5f;
+ attack = 0.01f;
+ release = 0.1f;
+ effectName = "Noise Gate";
+}
+
+NoiseGateEffect::~NoiseGateEffect() = default;
+
+void NoiseGateEffect::apply(const AudioSourceChannelInfo &bufferToFill) {
+ auto *leftBuffer = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);
+ auto *rightBuffer = bufferToFill.buffer->getNumChannels() > 1
+ ? bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample)
+ : nullptr;
+ int numSamples = bufferToFill.numSamples;
+
+ float envelopeLeft = 0.0f;
+ float envelopeRight = 0.0f;
+
+ float attackCoeff = std::exp(-1.0f / (attack * numSamples));
+ float releaseCoeff = std::exp(-1.0f / (release * numSamples));
+
+ for (int i = 0; i < numSamples; ++i) {
+ float inputSampleLeft = leftBuffer[i];
+ float inputSampleRight = rightBuffer ? rightBuffer[i] : 0.0f;
+
+ envelopeLeft = std::max(std::abs(inputSampleLeft), envelopeLeft * (inputSampleLeft > envelopeLeft ? attackCoeff : releaseCoeff));
+ if (rightBuffer)
+ envelopeRight = std::max(std::abs(inputSampleRight), envelopeRight * (inputSampleRight > envelopeRight ? attackCoeff : releaseCoeff));
+
+ float gainLeft = envelopeLeft < threshold ? 0.0f : 1.0f;
+ float gainRight = rightBuffer ? (envelopeRight < threshold ? 0.0f : 1.0f) : 1.0f;
+
+ leftBuffer[i] = inputSampleLeft * gainLeft;
+ if (rightBuffer) {
+ rightBuffer[i] = inputSampleRight * gainRight;
+ }
+ }
+}
+
+void NoiseGateEffect::setThreshold(float threshold) {
+ this->threshold = threshold;
+}
+
+void NoiseGateEffect::setAttack(float attack) {
+ this->attack = attack;
+}
+
+void NoiseGateEffect::setRelease(float release) {
+ this->release = release;
+}
+
+bool NoiseGateEffect::operator==(const AbstractEffect* effect) {
+ return this == effect;
+}
\ No newline at end of file
diff --git a/src/models/Pedal.cpp b/src/models/Pedal.cpp
new file mode 100644
index 0000000..52e2996
--- /dev/null
+++ b/src/models/Pedal.cpp
@@ -0,0 +1,5 @@
+//
+// Created by Ema on 14/05/2025.
+//
+
+#include "Pedal.h"
diff --git a/src/utils/EffectSerializer.cpp b/src/utils/EffectSerializer.cpp
new file mode 100644
index 0000000..990c1a2
--- /dev/null
+++ b/src/utils/EffectSerializer.cpp
@@ -0,0 +1,80 @@
+#pragma once
+
+#include
+#include
+#include
+#include "AbstractEffect.h"
+#include "DelayEffect.h"
+
+class EffectSerializer {
+public:
+ /**
+ * @brief Saves a list of effects to a JSON file.
+ * @param effects Vector of effect pointers to serialize
+ * @param filePath Path to save the JSON file
+ * @return True if saving succeeded, false otherwise
+ */
+ static bool saveEffectsToFile(const std::vector > &effects, const File &filePath) {
+ const auto effectsArray = var(Array());
+ Array *array = effectsArray.getArray();
+
+ for (const auto &effect: effects) {
+ array->add(effect->toJSON());
+ }
+
+ DynamicObject *root = new DynamicObject();
+ root->setProperty("effects", effectsArray);
+
+ String jsonString = JSON::toString(root);
+ return filePath.replaceWithText(jsonString);
+ }
+
+ /**
+ * @brief Loads effects from a JSON file.
+ * @param filePath Path to the JSON file
+ * @return Vector of loaded effect pointers
+ */
+ static std::vector > loadEffectsFromFile(const File &filePath) {
+ std::vector > loadedEffects;
+
+ if (!filePath.existsAsFile()) {
+ return loadedEffects;
+ }
+
+ const String jsonContent = filePath.loadFileAsString();
+ const auto rootVar = JSON::parse(jsonContent);
+
+ if (const auto *root = rootVar.getDynamicObject()) {
+ const auto effectsVar = root->getProperty("effects");
+
+ if (auto *effectsArray = effectsVar.getArray()) {
+ for (const auto &effectVar: *effectsArray) {
+ if (const auto *effectObj = effectVar.getDynamicObject()) {
+ String type = effectObj->getProperty("type").toString();
+
+ if (std::shared_ptr effect = createEffectFromType(type)) {
+ effect->fromJSON(effectVar);
+ loadedEffects.push_back(effect);
+ }
+ }
+ }
+ }
+ }
+
+ return loadedEffects;
+ }
+
+private:
+ /**
+ * @brief Creates an effect instance based on its type string.
+ * @param type The type name of the effect
+ * @return Shared pointer to the created effect, or nullptr if type is unknown
+ */
+ static std::shared_ptr createEffectFromType(const String &type) {
+ if (type == "DelayEffect")
+ return std::make_shared();
+ // Add more effect types here as needed
+
+ return nullptr;
+ }
+};
diff --git a/src/utils/ModalOverlayComponent.cpp b/src/utils/ModalOverlayComponent.cpp
new file mode 100644
index 0000000..936a1a5
--- /dev/null
+++ b/src/utils/ModalOverlayComponent.cpp
@@ -0,0 +1,56 @@
+#include "ModalOverlayComponent.h"
+
+#include "ResourceManager.h"
+
+ModalOverlayComponent::ModalOverlayComponent(std::string viewName, juce::Component* modalContent)
+{
+ addAndMakeVisible(modalComponent = modalContent);
+ this->setInterceptsMouseClicks(true, false);
+
+ modalComponent->setInterceptsMouseClicks(true, true);
+ modalComponent->setCentreRelative(0.5f, 0.5f);
+
+ addAndMakeVisible(viewNameLabel);
+ viewNameLabel.setText(viewName, juce::dontSendNotification);
+ viewNameLabel.setFont(juce::FontOptions(32.0f, juce::Font::bold));
+ viewNameLabel.setJustificationType(juce::Justification::centred);
+
+ juce::Image closeImage = ResourceManager::loadImage("resources/icons/xmark.png");
+ if (closeImage.isValid()) {
+ closeOverlayButton.setImages(true, true, true,closeImage, 1.0f, {}, closeImage, 1.0f, {},closeImage, 1.0f, {});
+ closeOverlayButton.setSize(closeImage.getWidth(), closeImage.getHeight());
+ addAndMakeVisible(closeOverlayButton);
+ } else {
+ DBG("Erreur : image xmark.png introuvable ou invalide.");
+ }
+ closeOverlayButton.onClick = [this]() { this->onCloseOverlayButtonClicked(); };
+}
+
+void ModalOverlayComponent::resized()
+{
+ viewNameLabel.setBounds(0, 0, getWidth(), 50);
+ closeOverlayButton.setBounds(getWidth() - 100, 10, 100, 50);
+ if (modalComponent != nullptr)
+ {
+ modalComponent->setBounds(getLocalBounds());
+ }
+}
+
+void ModalOverlayComponent::paint(juce::Graphics& g)
+{
+ g.fillAll(juce::Colours::black.withAlpha(0.8f));
+}
+
+void ModalOverlayComponent::onCloseOverlayButtonClicked()
+{
+ if (modalComponent != nullptr)
+ {
+ removeAllChildren();
+ delete modalComponent;
+ auto* mainWindow = getTopLevelComponent();
+ if (mainWindow == nullptr)
+ return;
+ mainWindow->removeChildComponent(this);
+ delete this;
+ }
+}
diff --git a/src/utils/ResourceManager.cpp b/src/utils/ResourceManager.cpp
new file mode 100644
index 0000000..e6ef989
--- /dev/null
+++ b/src/utils/ResourceManager.cpp
@@ -0,0 +1,28 @@
+#include "ResourceManager.h"
+
+ResourceManager::ResourceManager() = default;
+ResourceManager::~ResourceManager() = default;
+
+Image ResourceManager::loadImage(const String& relativePath)
+{
+ File imageFile;
+
+#if JUCE_MAC
+ imageFile = File::getSpecialLocation(File::currentApplicationFile)
+ .getParentDirectory()
+ .getParentDirectory()
+ .getParentDirectory()
+ .getChildFile(relativePath);
+#else
+ imageFile = juce::File::getCurrentWorkingDirectory().getChildFile(relativePath);
+#endif
+
+ Image image = ImageFileFormat::loadFrom(imageFile);
+
+ if (image.isNull())
+ {
+ DBG("Erreur : impossible de charger l'image à partir de " + imageFile.getFullPathName());
+ }
+
+ return image; // retourne un objet juce::Image valide ou invalide (mais jamais nullptr)
+}
diff --git a/src/utils/ToggleButtonComponent.cpp b/src/utils/ToggleButtonComponent.cpp
new file mode 100644
index 0000000..07b3baf
--- /dev/null
+++ b/src/utils/ToggleButtonComponent.cpp
@@ -0,0 +1,42 @@
+#include "ToggleButtonComponent.h"
+
+namespace {
+constexpr int buttonWidth = 100;
+constexpr int buttonHeight = 50;
+constexpr float toggleWidth = 50.0f;
+constexpr float toggleHeight = 25.0f;
+constexpr float toggleRadius = 10.0f;
+constexpr float ellipseDiameter = 19.0f;
+constexpr float ellipseYOffset = 3.0f;
+constexpr float ellipseXOffsetOn = 0.0f;
+constexpr float ellipseXOffsetOff = -22.0f;
+}
+
+ToggleButtonComponent::ToggleButtonComponent() {
+ setMouseCursor(MouseCursor::PointingHandCursor);
+ setSize(buttonWidth, buttonHeight);
+}
+
+ToggleButtonComponent::~ToggleButtonComponent() = default;
+
+void ToggleButtonComponent::paint(Graphics& g) {
+ const auto centerX = static_cast(getWidth()) / 2.0f;
+ const auto centerY = static_cast(getHeight()) / 2.0f;
+
+ g.setColour(Colours::darkgrey);
+ g.fillRoundedRectangle(centerX - toggleWidth / 2.0f,
+ centerY - toggleHeight / 2.0f,
+ toggleWidth, toggleHeight, toggleRadius);
+
+ if (getToggleState()) {
+ g.setColour(Colours::green);
+ g.fillEllipse(centerX + ellipseXOffsetOn,
+ centerY - toggleHeight / 2.0f + ellipseYOffset,
+ ellipseDiameter, ellipseDiameter);
+ } else {
+ g.setColour(Colours::red);
+ g.fillEllipse(centerX + ellipseXOffsetOff,
+ centerY - toggleHeight / 2.0f + ellipseYOffset,
+ ellipseDiameter, ellipseDiameter);
+ }
+}
\ No newline at end of file