diff --git a/include/BasePedalComponent.h b/include/BasePedalComponent.h index ed61d3f..1d03656 100644 --- a/include/BasePedalComponent.h +++ b/include/BasePedalComponent.h @@ -7,7 +7,7 @@ * @brief Base class for pedal components. * This class provides a common interface and functionality for all pedal components. */ -class BasePedalComponent : public EffectComponent +class BasePedalComponent : public EffectComponent, public DragAndDropTarget { public: /** @@ -32,6 +32,25 @@ class BasePedalComponent : public EffectComponent */ void resized() override; + /** + * @brief Handles mouse down events. + * @param event The mouse event that occurred. + */ + void mouseDown(const juce::MouseEvent& event) override; + + /** + * + * @param dragSourceDetails The details of the drag source. + * @return A boolean indicating whether the component is interested in the drag source. + */ + bool isInterestedInDragSource(const SourceDetails& dragSourceDetails) override; + + /** + * @brief Handles the drop of a component onto this pedal component. + * @param dragSourceDetails The details of the drag source. + */ + void itemDropped(const SourceDetails& dragSourceDetails) override; + protected: /** * @brief The default width of the component. diff --git a/include/ChromaticTuner.h b/include/ChromaticTuner.h new file mode 100644 index 0000000..73f0e21 --- /dev/null +++ b/include/ChromaticTuner.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class ChromaticTuner +{ +public: + ChromaticTuner(int fftOrder, double sampleRate); + ~ChromaticTuner(); + std::optional getMainFrequencyFromAudioBlock(const dsp::AudioBlock& block); + int getFFTSize() const; + +private: + juce::dsp::FFT fft; + int fftSize; + double sampleRate; + + juce::HeapBlock fftBuffer; + juce::dsp::WindowingFunction window; +}; \ No newline at end of file diff --git a/include/ChromaticTunerComponent.h b/include/ChromaticTunerComponent.h new file mode 100644 index 0000000..63e37c3 --- /dev/null +++ b/include/ChromaticTunerComponent.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "ChromaticTuner.h" + +struct ChromaticTuningResult +{ + std::string noteName; + float frequency; + float deviation; + bool isInTune; +}; + +class ChromaticTunerComponent : public juce::Component +{ +public: + explicit ChromaticTunerComponent(int sampleRate = 44100, int fftOrder = 10); + ~ChromaticTunerComponent() override; + + void paint(juce::Graphics &g) override; + void resized() override; + void prepareToPlay(int samplesPerBlockExpected, double sampleRate); + void tune(const AudioSourceChannelInfo &bufferToFill); + bool isTuning(); +private: + ChromaticTuner* tuner; + bool isTunerActive = false; + const std::vector notesNames; + float baseFrequency; + std::string currentNote; + float currentTuneCents; + bool isInTune; + float smoothedFrequency = 0.0f; + float smoothingCoeff = 0.2f; // Ajustez selon la réactivité voulue + + ChromaticTuningResult detectTuning(float frequency); + std::string getNoteName(int midiNote); + void updateTuningDisplay(const ChromaticTuningResult& result); +}; \ No newline at end of file diff --git a/include/DistortionEffect.h b/include/DistortionEffect.h index 6f68921..aba0d2b 100644 --- a/include/DistortionEffect.h +++ b/include/DistortionEffect.h @@ -63,14 +63,7 @@ class DistortionEffect : public AbstractEffect { } } - 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; - } + bool operator==(const AbstractEffect* other); private: float level = 1.0f; diff --git a/include/MainComponent.h b/include/MainComponent.h index 647c1db..369c0e3 100644 --- a/include/MainComponent.h +++ b/include/MainComponent.h @@ -20,10 +20,15 @@ * 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 { +class MainComponent final : public AudioAppComponent, public DragAndDropContainer { public: explicit MainComponent(const Manager &manager); + /** + * Destructor of the MainComponent. + */ + ~MainComponent() override; + /** * Determines how the component is displayed. */ @@ -100,5 +105,7 @@ class MainComponent final : public AudioAppComponent { */ bool isSoundMuted = false; + std::function tuningFunction; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent) }; diff --git a/include/ModalOverlayComponent.h b/include/ModalOverlayComponent.h index ea25545..7bb0d0a 100644 --- a/include/ModalOverlayComponent.h +++ b/include/ModalOverlayComponent.h @@ -4,7 +4,7 @@ class ModalOverlayComponent : public juce::Component { public: - ModalOverlayComponent(std::string viewName, juce::Component* modalContent); + ModalOverlayComponent(std::string viewName, juce::Component* modalContent, std::function onCloseCallback); ~ModalOverlayComponent() override = default; void resized() override; void paint(Graphics& g) override; @@ -13,6 +13,7 @@ class ModalOverlayComponent : public juce::Component juce::Label viewNameLabel; juce::ImageButton closeOverlayButton; juce::Component* modalComponent = nullptr; + std::function onCloseCallback; void onCloseOverlayButtonClicked(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModalOverlayComponent) diff --git a/include/Pedal.h b/include/Pedal.h deleted file mode 100644 index 33af80c..0000000 --- a/include/Pedal.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -class Pedal { - -}; diff --git a/include/Pedalboard.h b/include/Pedalboard.h index 366ee95..7fca58c 100644 --- a/include/Pedalboard.h +++ b/include/Pedalboard.h @@ -66,7 +66,7 @@ class Pedalboard : public AbstractEffect { * @brief Gets the collection of effects contained in the pedalboard. * @return The collection of effects contained in the pedalboard. */ - std::vector getEffects(); + std::vector& getEffects(); /** * @brief Gets the type name of the effect for serialization purposes. diff --git a/include/PedalboardComponent.h b/include/PedalboardComponent.h index 2b35414..d89c6e6 100644 --- a/include/PedalboardComponent.h +++ b/include/PedalboardComponent.h @@ -7,7 +7,7 @@ /** * @brief Represents a graphical component that contains and displays a pedalboard. */ -class PedalboardComponent : public EffectComponent { +class PedalboardComponent : public EffectComponent, public DragAndDropContainer { public: /** * @brief Initializes a new instance of the PedalboardComponent class. @@ -48,6 +48,21 @@ class PedalboardComponent : public EffectComponent { * @return The required height for the pedalboard. */ int getRequiredHeight(const int boardWidth) const; + + /** + * @brief Handles the drag and drop of a Component onto the pedalboard. + * @param target The target Component where the dragged component is dropped. + * @param dragged The Component that is being dragged. + */ + void onPedalDropped(Component* target, Component* dragged); + + /** + * @brief Handles the drag and drop of an EffectComponent onto the pedalboard. + * @param target The target EffectComponent where the dragged component is dropped. + * @param dragged The EffectComponent that is being dragged. + */ + void onPedalDropped(EffectComponent* target, EffectComponent* dragged); + private: /** @@ -64,4 +79,14 @@ class PedalboardComponent : public EffectComponent { * @brief The flexbox that arranges layout for the effect components in the pedalboard. */ juce::FlexBox flexBox; + + /** + * @brief Indicates whether something is being dragged over the pedalboard. + */ + bool somethingIsBeingDraggedOver; + + /** + * @brief Refreshes the flexbox layout of the pedalboard. + */ + void refreshFlexBox(); }; \ No newline at end of file diff --git a/include/TopMenuBarComponent.h b/include/TopMenuBarComponent.h index 5778bbe..97041df 100644 --- a/include/TopMenuBarComponent.h +++ b/include/TopMenuBarComponent.h @@ -6,6 +6,7 @@ #include "ModalOverlayComponent.h" #include "SettingsComponent.h" #include "AccountComponent.h" +#include "ChromaticTunerComponent.h" /** * @brief Represents a graphical component that contains and displays the top menu bar. @@ -15,7 +16,7 @@ class TopMenuBarComponent : public juce::Component { /** * @brief Initializes a new instance of the TopMenuBarComponent class. */ - explicit TopMenuBarComponent(juce::AudioDeviceManager& deviceManager, bool* isMuted = nullptr); + explicit TopMenuBarComponent(juce::AudioDeviceManager& deviceManager, bool* isMuted = nullptr, std::function* tuningFunction = nullptr); /** * @brief Destroys the instance of the TopMenuBarComponent class. @@ -39,7 +40,7 @@ class TopMenuBarComponent : public juce::Component { */ juce::FlexBox flexBox; - ModalOverlayComponent* modalOverlay; + std::unique_ptr modalOverlay; /** * @brief The image button to open settings. @@ -50,12 +51,18 @@ class TopMenuBarComponent : public juce::Component { juce::ImageButton accountButton; AccountComponent* accountComponent; + juce::ImageButton tunerButton; + ChromaticTunerComponent* tunerComponent; + std::function* tuningFunction; + bool* isSoundMuted = nullptr; juce::ImageButton muteButton; void openSettingsPopup(juce::AudioDeviceManager& deviceManager); void openAccountPopup(); + void openTunerPopup(); void toggleMute(); + void closePopup(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopMenuBarComponent) }; \ No newline at end of file diff --git a/resources/icons/close.png b/resources/icons/close.png new file mode 100644 index 0000000..9d5f761 Binary files /dev/null and b/resources/icons/close.png differ diff --git a/resources/icons/tuner.png b/resources/icons/tuner.png new file mode 100644 index 0000000..67482f0 Binary files /dev/null and b/resources/icons/tuner.png differ diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index 1d3a185..5e8045f 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -2,11 +2,8 @@ #include "SettingsComponent.h" #include "ResourceManager.h" - - - MainComponent::MainComponent(const Manager &manager): pedalboardComponent(manager.getPedalboard()), - topMenuBarComponent(this->deviceManager, &isSoundMuted), manager(manager) + topMenuBarComponent(this->deviceManager, &isSoundMuted, &tuningFunction), manager(manager) { setAudioChannels(2, 2); @@ -28,6 +25,11 @@ MainComponent::MainComponent(const Manager &manager): pedalboardComponent(manage addAndMakeVisible(connectionComponent); } +MainComponent::~MainComponent() +{ + releaseResources(); +} + //============================================================================== void MainComponent::paint (juce::Graphics& g) { @@ -76,9 +78,13 @@ void MainComponent::getNextAudioBlock(const AudioSourceChannelInfo &bufferToFill { bufferToFill.clearActiveBufferRegion(); } + if (tuningFunction != nullptr) { + tuningFunction(bufferToFill); + } } void MainComponent::releaseResources() { + shutdownAudio(); } diff --git a/src/Pedalboard.cpp b/src/Pedalboard.cpp index 076b7e9..2e8ba7e 100644 --- a/src/Pedalboard.cpp +++ b/src/Pedalboard.cpp @@ -6,7 +6,11 @@ Pedalboard::Pedalboard() { }; -Pedalboard::~Pedalboard() = default; +Pedalboard::~Pedalboard() { + for (AbstractEffect* effect : effects) { + delete effect; + } +}; void Pedalboard::apply(const AudioSourceChannelInfo &bufferToFill) { for (AbstractEffect* effect : effects) { @@ -33,7 +37,7 @@ void Pedalboard::remove(const AbstractEffect* effect) { effects.erase(std::remove(effects.begin(), effects.end(), effect), effects.end()); } -std::vector Pedalboard::getEffects() { +std::vector& Pedalboard::getEffects() { return effects; } diff --git a/src/components/ChromaticTunerComponent.cpp b/src/components/ChromaticTunerComponent.cpp new file mode 100644 index 0000000..726b8d0 --- /dev/null +++ b/src/components/ChromaticTunerComponent.cpp @@ -0,0 +1,117 @@ +#include "ChromaticTunerComponent.h" + +ChromaticTunerComponent::ChromaticTunerComponent(int sampleRate, int fftOrder) + : notesNames({"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}), + baseFrequency(440.0f), currentTuneCents(0), isInTune(false), currentNote("No Signal"), isTunerActive(false) +{ + tuner = new ChromaticTuner(fftOrder, sampleRate); +} + +ChromaticTunerComponent::~ChromaticTunerComponent() = default; + +void ChromaticTunerComponent::paint(juce::Graphics &g) { + auto area = getLocalBounds(); + int numBars = 11; + int barWidth = 20; + int barHeight = 30; + float textHeight = 50.0f; + int centerX = area.getCentreX(); + + // Drawing the note name. + g.setColour(isInTune ? juce::Colours::green : juce::Colours::orange); + g.setFont(60.0f); + g.drawText(currentNote, 0, getHeight() / 2 - textHeight, area.getWidth(), textHeight, juce::Justification::centred); + + // Drawing the tuning bars. + float normalizedCents = juce::jlimit(-50.0f, 50.0f, currentTuneCents); + int highlightIndex = static_cast(std::round(normalizedCents / 10.0f)) + 5; + + for (int i = 0; i < numBars; ++i) { + int x = centerX + (i - 5) * barWidth; + int y = getHeight() / 2; + + bool isActive = (i == highlightIndex); + juce::Colour color = isActive ? (isInTune ? juce::Colours::green : juce::Colours::orange) + : juce::Colours::grey; + + g.setColour(color); + g.fillRect(x, y, barWidth - 2, barHeight); + } +} + +void ChromaticTunerComponent::resized() { +} + +void ChromaticTunerComponent::prepareToPlay(int samplesPerBlockExpected, double sampleRate) { +} + +void ChromaticTunerComponent::tune(const AudioSourceChannelInfo &buffer) { + if (!isTunerActive) { + isTunerActive = true; + } + if (buffer.buffer == nullptr || buffer.buffer->getNumChannels() == 0) { + return; + } + dsp::AudioBlock audioBlock(*buffer.buffer); + if (audioBlock.getNumSamples() == 0) { + return; + } + dsp::AudioBlock subBlock = audioBlock.getSingleChannelBlock((size_t)0); + std::optional frequency = tuner->getMainFrequencyFromAudioBlock(subBlock); + if (frequency.has_value()) { + float newFreq = frequency.value(); + if (smoothedFrequency == 0.0f) + smoothedFrequency = newFreq; + else + smoothedFrequency = smoothingCoeff * newFreq + (1.0f - smoothingCoeff) * smoothedFrequency; + ChromaticTuningResult result = detectTuning(frequency.value()); + updateTuningDisplay(result); + } else { + currentNote = "No Signal"; + currentTuneCents = 0.0f; + isInTune = false; + repaint(); + } +} + +ChromaticTuningResult ChromaticTunerComponent::detectTuning(float frequency) { + ChromaticTuningResult result; + + if (frequency <= 0.0f) return result; + + float midiFloat = 69 + 12 * std::log2(frequency / 440.0f); + int midiNote = std::round(midiFloat); + float exactFrequency = 440.0f * std::pow(2.0f, (midiNote - 69) / 12.0f); + float deviation = 1200.0f * std::log2(frequency / exactFrequency); + + result.frequency = frequency; + result.noteName = getNoteName(midiNote); + result.deviation = deviation; + result.isInTune = std::abs(deviation) < 5.0f; + + return result; +} + +std::string ChromaticTunerComponent::getNoteName(int midiNote) { + if (midiNote < 0) { + return "Unknown"; + } + return notesNames[midiNote % notesNames.size()]; +} + +void ChromaticTunerComponent::updateTuningDisplay(const ChromaticTuningResult& result) { + if (result.frequency < 20.0f) { + currentNote = "No Signal"; + currentTuneCents = 0.0f; + isInTune = false; + } else { + currentNote = result.noteName; + currentTuneCents = result.deviation; // Doit être l'écart en cents, pas la fréquence + isInTune = result.isInTune; + } + repaint(); +} + +bool ChromaticTunerComponent::isTuning() { + return isTunerActive; +} \ No newline at end of file diff --git a/src/components/PedalboardComponent.cpp b/src/components/PedalboardComponent.cpp index 6804424..33310e7 100644 --- a/src/components/PedalboardComponent.cpp +++ b/src/components/PedalboardComponent.cpp @@ -1,7 +1,13 @@ #include "PedalboardComponent.h" #include "EffectComponentFactory.h" -PedalboardComponent::~PedalboardComponent() = default; +PedalboardComponent::~PedalboardComponent() +{ + for (Component* effectComponent : effectsComponents) { + removeChildComponent(effectComponent); + delete effectComponent; + } +} PedalboardComponent::PedalboardComponent(AbstractEffect* pedalboard) : EffectComponent(pedalboard) { if (Pedalboard* pedalboard = dynamic_cast(this->effect)) { @@ -65,4 +71,49 @@ int PedalboardComponent::getRequiredHeight(const int boardWidth) const { totalHeight += maxHeightInRow; return totalHeight; +} + +void PedalboardComponent::onPedalDropped(Component* target, Component* dragged) +{ + if (target == dragged) return; + if (static_cast(target) != nullptr && static_cast(dragged) != nullptr) { + onPedalDropped(static_cast(target), static_cast(dragged)); + } +} + +void PedalboardComponent::onPedalDropped(EffectComponent* target, EffectComponent* dragged) { + if (target == dragged) return; + + // Finds the dragged and target components in the effectsComponents vector. + auto itDragged = std::find(effectsComponents.begin(), effectsComponents.end(), dragged); + auto itTarget = std::find(effectsComponents.begin(), effectsComponents.end(), target); + if (itDragged == effectsComponents.end() || itTarget == effectsComponents.end()) return; + + // Reorders the components in the PedalboardComponent. + auto draggedPtr = *itDragged; + effectsComponents.erase(itDragged); + effectsComponents.insert(itTarget, draggedPtr); + + // Reorders the effects in the Pedalboard. + if (auto* pedalboard = dynamic_cast(effect)) { + auto& effects = pedalboard->getEffects(); + auto itEffDragged = std::find(effects.begin(), effects.end(), dragged->getEffect()); + auto itEffTarget = std::find(effects.begin(), effects.end(), target->getEffect()); + if (itEffDragged != effects.end() && itEffTarget != effects.end()) { + auto effPtr = *itEffDragged; + effects.erase(itEffDragged); + effects.insert(itEffTarget, effPtr); + } + } + + refreshFlexBox(); +} + +void PedalboardComponent::refreshFlexBox() { + flexBox.items.clear(); + for (auto &effectComponent : effectsComponents) { + flexBox.items.add(juce::FlexItem(*effectComponent).withWidth(effectComponent->getWidth()).withHeight(effectComponent->getHeight()).withMargin(PEDALS_MARGIN)); + } + resized(); + repaint(); } \ No newline at end of file diff --git a/src/components/TopMenuBarComponent.cpp b/src/components/TopMenuBarComponent.cpp index 970fe4e..2b5257c 100644 --- a/src/components/TopMenuBarComponent.cpp +++ b/src/components/TopMenuBarComponent.cpp @@ -7,11 +7,10 @@ #include "ResourceManager.h" -TopMenuBarComponent::TopMenuBarComponent(juce::AudioDeviceManager& deviceManager, bool* isSoundMuted) +TopMenuBarComponent::TopMenuBarComponent(juce::AudioDeviceManager& deviceManager, bool* isSoundMuted, std::function* tuningFunction) { this->isSoundMuted = isSoundMuted; - - + this->tuningFunction = tuningFunction; juce::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, {}); @@ -39,18 +38,32 @@ TopMenuBarComponent::TopMenuBarComponent(juce::AudioDeviceManager& deviceManager DBG("Erreur : image mute.png introuvable ou invalide."); } + juce::Image tunerImage = ResourceManager::loadImage("resources/icons/tuner.png"); + if (tunerImage.isValid()) { + tunerButton.setImages(true, true, true, tunerImage, 1.0f, {}, tunerImage, 1.0f, {}, tunerImage, 1.0f, {}); + tunerButton.setSize(tunerImage.getWidth(), tunerImage.getHeight()); + addAndMakeVisible(tunerButton); + } else { + DBG("Erreur : image tuner.png introuvable ou invalide."); + } + #if !JUCE_IOS settingsButton.onClick = [this, &deviceManager]() { openSettingsPopup(deviceManager); }; #endif accountButton.onClick = [this]() { openAccountPopup(); }; muteButton.onClick = [this]() { toggleMute(); }; + tunerButton.onClick = [this]() { openTunerPopup(); }; settingsButton.setBounds(0, 0, 100, 50); accountButton.setBounds(0, 0, 100, 50); muteButton.setBounds(0, 0, 100, 50); + tunerButton.setBounds(0, 0, 100, 50); + flexBox.justifyContent = juce::FlexBox::JustifyContent::flexEnd; flexBox.alignItems = juce::FlexBox::AlignItems::center; + flexBox.items.add( + juce::FlexItem(tunerButton).withWidth(tunerButton.getWidth()).withHeight(tunerButton.getHeight())); flexBox.items.add( juce::FlexItem(muteButton).withWidth(muteButton.getWidth()).withHeight(muteButton.getHeight())); flexBox.items.add( @@ -83,6 +96,10 @@ void TopMenuBarComponent::resized() { accountComponent->setBounds(mainWindow->getLocalBounds()); } + if (tunerComponent != nullptr) + { + tunerComponent->setBounds(mainWindow->getLocalBounds()); + } flexBox.performLayout(getLocalBounds()); } @@ -93,8 +110,11 @@ void TopMenuBarComponent::openSettingsPopup(juce::AudioDeviceManager& deviceMana if (mainWindow == nullptr) return; - modalOverlay = new ModalOverlayComponent("Audio settings", settingsComponent); - mainWindow->addAndMakeVisible(modalOverlay); + modalOverlay = std::make_unique("Audio settings", settingsComponent, [this]() { + settingsComponent = nullptr; + modalOverlay = nullptr; + }); + mainWindow->addAndMakeVisible(modalOverlay.get()); modalOverlay->setBounds(mainWindow->getLocalBounds()); } @@ -105,8 +125,11 @@ void TopMenuBarComponent::openAccountPopup() if (mainWindow == nullptr) return; - modalOverlay = new ModalOverlayComponent("Account", accountComponent); - mainWindow->addAndMakeVisible(modalOverlay); + modalOverlay = std::make_unique("Account", accountComponent, [this]() { + accountComponent = nullptr; + modalOverlay = nullptr; + }); + mainWindow->addAndMakeVisible(modalOverlay.get()); modalOverlay->setBounds(mainWindow->getLocalBounds()); } @@ -134,3 +157,23 @@ void TopMenuBarComponent::toggleMute() } *(this->isSoundMuted) = !(*(this->isSoundMuted)); } + +void TopMenuBarComponent::openTunerPopup() +{ + tunerComponent = new ChromaticTunerComponent(44100, 9); // Sample rate and FFT order can be adjusted as needed + auto* mainWindow = getTopLevelComponent(); + if (mainWindow == nullptr) + return; + + *this->tuningFunction = [&](const juce::AudioSourceChannelInfo& buffer) { + if (tunerComponent != nullptr) + tunerComponent->tune(buffer); + }; + + modalOverlay = std::make_unique("Chromatic Tuner", tunerComponent, [this]() {; + tunerComponent = nullptr; + modalOverlay = nullptr; // Clear the tuning function when the tuner is closed + }); + mainWindow->addAndMakeVisible(modalOverlay.get()); + modalOverlay->setBounds(mainWindow->getLocalBounds()); +} diff --git a/src/components/effects/BasePedalComponent.cpp b/src/components/effects/BasePedalComponent.cpp index c72108a..1d7498b 100644 --- a/src/components/effects/BasePedalComponent.cpp +++ b/src/components/effects/BasePedalComponent.cpp @@ -1,9 +1,13 @@ #include "BasePedalComponent.h" + +#include "PedalboardComponent.h" #include "ResourceManager.h" BasePedalComponent::BasePedalComponent(AbstractEffect* effect) : EffectComponent(effect) { isEnabled = effect->isEnabled; + setInterceptsMouseClicks(true, false); + addMouseListener(this, true); } BasePedalComponent::~BasePedalComponent() = default; @@ -41,7 +45,7 @@ void BasePedalComponent::initializePedal() { }; addAndMakeVisible(enablePedalButton); } else { - DBG("Erreur : image power.png introuvable ou invalide."); + DBG("Erreur true: image power.png introuvable ou invalide."); } addAndMakeVisible(*pedalLabel); @@ -65,4 +69,25 @@ void BasePedalComponent::initializePedal() { juce::GridItem(*isEnabledIndicator), juce::GridItem(*pedalLabel), }; +} + +void BasePedalComponent::mouseDown(const juce::MouseEvent& event) +{ + if (dynamic_cast(event.eventComponent) || dynamic_cast(event.eventComponent)) + { + if (auto* parent = getParentComponent()) + if (auto* dnd = dynamic_cast(parent)) + dnd->startDragging("Pedal", this); + } +} + +bool BasePedalComponent::isInterestedInDragSource(const juce::DragAndDropTarget::SourceDetails& details) +{ + return details.description == "Pedal"; +} + +void BasePedalComponent::itemDropped(const juce::DragAndDropTarget::SourceDetails& details) +{ + if (auto* parent = dynamic_cast(getParentComponent())) + parent->onPedalDropped(this, details.sourceComponent.get()); } \ No newline at end of file diff --git a/src/effects/ChromaticTuner.cpp b/src/effects/ChromaticTuner.cpp new file mode 100644 index 0000000..4f56dc8 --- /dev/null +++ b/src/effects/ChromaticTuner.cpp @@ -0,0 +1,74 @@ +#include "ChromaticTuner.h" + +ChromaticTuner::ChromaticTuner(int fftOrder, double sampleRate) : fft(fftOrder), + fftSize(1 << fftOrder), + window(fftSize, juce::dsp::WindowingFunction::hann), + sampleRate(sampleRate) +{ + fftBuffer.calloc(fftSize * 2); +} + +ChromaticTuner::~ChromaticTuner() = default; + +std::optional ChromaticTuner::getMainFrequencyFromAudioBlock(const dsp::AudioBlock& block) +{ + if (block.getNumChannels() == 0) + return std::nullopt; + + if (block.getNumSamples() != fftSize) + { + fftSize = block.getNumSamples(); + fftBuffer.calloc(fftSize * 2); + } + + // 1. Copier un bloc mono (moyenne stéréo si besoin) + for (size_t i = 0; i < fftSize; ++i) + { + float sample = 0.0f; + for (size_t ch = 0; ch < block.getNumChannels(); ++ch) + sample += block.getSample(ch, i); + fftBuffer[i] = sample / static_cast(block.getNumChannels()); + } + + // 2. Appliquer fenêtre de Hann + window.multiplyWithWindowingTable(fftBuffer.get(), fftSize); + + // 3. Appliquer la FFT + std::fill(fftBuffer.get() + fftSize, fftBuffer.get() + 2 * fftSize, 0.0f); // imag = 0 + fft.performRealOnlyForwardTransform(fftBuffer.get()); + + // 4. Trouver le pic de magnitude dans la plage utile + int maxIndex = 1; + float maxMag = 0.0f; + + for (int bin = 1; bin < fftSize / 2; ++bin) + { + float real = fftBuffer[2 * bin]; + float imag = fftBuffer[2 * bin + 1]; + float magnitude = std::sqrt(real * real + imag * imag); + + if (magnitude > maxMag) + { + maxMag = magnitude; + maxIndex = bin; + } + } + + // 5. Interpolation parabolique pour précision sub-bin + float alpha = fftBuffer[2 * (maxIndex - 1)]; + float beta = fftBuffer[2 * maxIndex]; + float gamma = fftBuffer[2 * (maxIndex + 1)]; + float denom = alpha - 2 * beta + gamma; + + float binShift = (denom == 0.0f) ? 0.0f : 0.5f * (alpha - gamma) / denom; + float interpolatedBin = static_cast(maxIndex) + binShift; + + // 6. Convertir en fréquence + float frequency = (interpolatedBin * sampleRate) / fftSize; + return frequency; +} + +int ChromaticTuner::getFFTSize() const +{ + return fftSize; +} \ No newline at end of file diff --git a/src/effects/DistortionEffect.cpp b/src/effects/DistortionEffect.cpp index c54b1e1..cfd8675 100644 --- a/src/effects/DistortionEffect.cpp +++ b/src/effects/DistortionEffect.cpp @@ -15,7 +15,6 @@ 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(); } @@ -28,8 +27,6 @@ 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); @@ -38,8 +35,6 @@ void DistortionEffect::apply(const juce::AudioSourceChannelInfo& bufferToFill) { } } -// --- Paramètres --- - void DistortionEffect::setLevel(float value) { level = juce::jlimit(0.0f, 1.0f, value); processorChain.get<3>().setGainLinear(level); @@ -63,17 +58,12 @@ 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 + float freq = 2000.0f + tone * (turbo ? 8000.0f : 4000.0f); *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) @@ -83,8 +73,12 @@ float DistortionEffect::waveshaperFunc(float x) { } void DistortionEffect::updateWaveshaper() { - float drive = 1.0f + dist * (turbo ? 100.0f : 50.0f); // Turbo = plus de gain + float drive = 1.0f + dist * (turbo ? 100.0f : 50.0f); gDrive = drive; gTurbo = turbo; processorChain.get<1>().functionToUse = &DistortionEffect::waveshaperFunc; +} + +bool DistortionEffect::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 deleted file mode 100644 index 52e2996..0000000 --- a/src/models/Pedal.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by Ema on 14/05/2025. -// - -#include "Pedal.h" diff --git a/src/utils/ModalOverlayComponent.cpp b/src/utils/ModalOverlayComponent.cpp index c4c7302..2234307 100644 --- a/src/utils/ModalOverlayComponent.cpp +++ b/src/utils/ModalOverlayComponent.cpp @@ -2,8 +2,9 @@ #include "ResourceManager.h" -ModalOverlayComponent::ModalOverlayComponent(std::string viewName, juce::Component* modalContent) +ModalOverlayComponent::ModalOverlayComponent(std::string viewName, juce::Component* modalContent, std::function onCloseCallback) { + this->onCloseCallback = onCloseCallback; addAndMakeVisible(modalComponent = modalContent); this->setInterceptsMouseClicks(true, false); @@ -51,6 +52,6 @@ void ModalOverlayComponent::onCloseOverlayButtonClicked() if (mainWindow == nullptr) return; mainWindow->removeChildComponent(this); - delete this; + onCloseCallback(); } }