diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 73aac9c..21739c1 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -4,7 +4,10 @@ AirPlayPluginEditor::AirPlayPluginEditor(AirPlayPluginProcessor& p) : AudioProcessorEditor(&p), audioProcessor(p) { - setSize(500, 500); + // Set a reasonable size that works for both standalone and plugin contexts + setSize(600, 500); + setResizable(true, true); + setResizeLimits(400, 400, 800, 600); titleLabel.setText("FreeCaster", juce::dontSendNotification); titleLabel.setFont(juce::Font(24.0f, juce::Font::bold)); @@ -56,6 +59,13 @@ AirPlayPluginEditor::AirPlayPluginEditor(AirPlayPluginProcessor& p) outputMeterLabel.setFont(juce::Font(11.0f)); addAndMakeVisible(outputMeterLabel); + // Test audio button + testAudioButton.setButtonText("Test Meters"); + testAudioButton.onClick = [this] { + testLevel = (testLevel > 0.0f) ? 0.0f : 0.75f; + }; + addAndMakeVisible(testAudioButton); + // Set up error callbacks audioProcessor.getAirPlayManager().onError = [this](const juce::String& error) { @@ -107,9 +117,11 @@ void AirPlayPluginEditor::resized() area.removeFromTop(10); auto buttonArea = area.removeFromTop(40); - connectButton.setBounds(buttonArea.removeFromLeft(165)); - buttonArea.removeFromLeft(10); - disconnectButton.setBounds(buttonArea); + connectButton.setBounds(buttonArea.removeFromLeft(110)); + buttonArea.removeFromLeft(5); + disconnectButton.setBounds(buttonArea.removeFromLeft(110)); + buttonArea.removeFromLeft(5); + testAudioButton.setBounds(buttonArea); // Layout meters vertically on the right side metersArea.removeFromTop(40); // Align with title @@ -119,10 +131,10 @@ void AirPlayPluginEditor::resized() auto outputMeterArea = metersArea.removeFromLeft(40); inputMeterLabel.setBounds(inputMeterArea.removeFromTop(20)); - inputMeter.setBounds(inputMeterArea.removeFromTop(340)); + inputMeter.setBounds(inputMeterArea); // Use remaining height outputMeterLabel.setBounds(outputMeterArea.removeFromTop(20)); - outputMeter.setBounds(outputMeterArea.removeFromTop(340)); + outputMeter.setBounds(outputMeterArea); // Use remaining height } void AirPlayPluginEditor::timerCallback() @@ -131,8 +143,11 @@ void AirPlayPluginEditor::timerCallback() updateBufferHealth(); // Update level meters - inputMeter.setLevel(audioProcessor.getInputLevel()); - outputMeter.setLevel(audioProcessor.getOutputLevel()); + float inputLevel = (testLevel > 0.0f) ? testLevel : audioProcessor.getInputLevel(); + float outputLevel = (testLevel > 0.0f) ? testLevel : audioProcessor.getOutputLevel(); + + inputMeter.setLevel(inputLevel); + outputMeter.setLevel(outputLevel); } void AirPlayPluginEditor::updateStatusDisplay() diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index cd5761f..bb61704 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -10,52 +10,50 @@ class LevelMeter : public juce::Component public: LevelMeter() { - setOpaque(false); + setOpaque(true); + setVisible(true); } - + void paint(juce::Graphics& g) override { auto bounds = getLocalBounds().toFloat(); - + // Draw background g.setColour(juce::Colour(0xFF1a1a1a)); g.fillRoundedRectangle(bounds, 4.0f); - + // Draw scale markings g.setColour(juce::Colour(0xFF404040)); g.setFont(9.0f); - const float dbMarks[] = { 0.0f, -6.0f, -12.0f, -24.0f, -48.0f }; for (auto db : dbMarks) { float yPos = dbToY(db, bounds.getHeight()); g.drawLine(bounds.getX(), yPos, bounds.getX() + 3, yPos, 1.0f); - g.drawText(juce::String((int)db), bounds.getX() + bounds.getWidth() + 2, yPos - 6, 30, 12, + g.drawText(juce::String((int)db), bounds.getX() + bounds.getWidth() + 2, yPos - 6, 30, 12, juce::Justification::left, false); } - + // Calculate meter height float meterHeight = bounds.getHeight() * currentLevel; float peakHeight = bounds.getHeight() * peakLevel; - + if (meterHeight > 0.0f) { auto meterBounds = bounds.withTop(bounds.getBottom() - meterHeight).reduced(2.0f); - // Color gradient based on level juce::ColourGradient gradient( juce::Colour(0xFF00ff00), meterBounds.getX(), meterBounds.getBottom(), juce::Colour(0xFFffff00), meterBounds.getX(), meterBounds.getY(), false); - + gradient.addColour(0.7, juce::Colour(0xFF00ff00)); // Green gradient.addColour(0.85, juce::Colour(0xFFffff00)); // Yellow gradient.addColour(1.0, juce::Colour(0xFFff0000)); // Red - + g.setGradientFill(gradient); g.fillRoundedRectangle(meterBounds, 2.0f); } - // Draw peak hold indicator if (peakLevel > 0.0f) { @@ -63,17 +61,16 @@ class LevelMeter : public juce::Component g.setColour(peakLevel > 0.9f ? juce::Colours::red : juce::Colours::white); g.fillRect(bounds.getX() + 1, peakY - 1, bounds.getWidth() - 2, 2.0f); } - + // Draw border g.setColour(juce::Colour(0xFF505050)); g.drawRoundedRectangle(bounds, 4.0f, 1.0f); } - void setLevel(float newLevel) { // Convert to normalized 0-1 range (assuming input is in 0-1 linear amplitude) currentLevel = juce::jlimit(0.0f, 1.0f, newLevel); - + // Update peak hold if (currentLevel > peakLevel) { @@ -91,10 +88,9 @@ class LevelMeter : public juce::Component if (peakLevel < 0.001f) peakLevel = 0.0f; } - + repaint(); } - void reset() { currentLevel = 0.0f; @@ -102,7 +98,6 @@ class LevelMeter : public juce::Component peakHoldCounter = 0; repaint(); } - private: float dbToY(float db, float height) const { @@ -110,11 +105,10 @@ class LevelMeter : public juce::Component float normalized = (db + 60.0f) / 60.0f; // -60dB to 0dB range return height * (1.0f - juce::jlimit(0.0f, 1.0f, normalized)); } - + float currentLevel = 0.0f; float peakLevel = 0.0f; int peakHoldCounter = 0; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LevelMeter) }; @@ -126,15 +120,15 @@ class AirPlayPluginEditor : public juce::AudioProcessorEditor, public: AirPlayPluginEditor(AirPlayPluginProcessor&); ~AirPlayPluginEditor() override; - + void paint(juce::Graphics&) override; void resized() override; - + private: void timerCallback() override; void deviceFound(const AirPlayDevice& device) override; void deviceLost(const AirPlayDevice& device) override; - + void updateDeviceList(); void connectButtonClicked(); void disconnectButtonClicked(); @@ -142,45 +136,46 @@ class AirPlayPluginEditor : public juce::AudioProcessorEditor, void updateBufferHealth(); void showError(const juce::String& error); void showStatus(const juce::String& status); - + AirPlayPluginProcessor& audioProcessor; - + juce::ListBox deviceListBox; juce::StringArray deviceNames; juce::Array devices; - + juce::TextButton connectButton; juce::TextButton disconnectButton; juce::Label statusLabel; juce::Label titleLabel; juce::Label errorLabel; juce::Label bufferHealthLabel; - + // Level meters LevelMeter inputMeter; LevelMeter outputMeter; juce::Label inputMeterLabel; juce::Label outputMeterLabel; - + juce::TextButton testAudioButton; + float testLevel = 0.0f; class DeviceListModel : public juce::ListBoxModel { public: DeviceListModel(AirPlayPluginEditor& e) : editor(e) {} - + int getNumRows() override { return editor.deviceNames.size(); } - + void paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) override { if (rowIsSelected) g.fillAll(juce::Colours::lightblue); - + g.setColour(juce::Colours::black); g.setFont(14.0f); - + if (rowNumber < editor.deviceNames.size()) { g.drawText(editor.deviceNames[rowNumber], @@ -188,12 +183,12 @@ class AirPlayPluginEditor : public juce::AudioProcessorEditor, juce::Justification::centredLeft, true); } } - + private: AirPlayPluginEditor& editor; }; - + std::unique_ptr listModel; - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AirPlayPluginEditor) }; diff --git a/test_plugin.py b/test_plugin.py new file mode 100644 index 0000000..32b1f1b --- /dev/null +++ b/test_plugin.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +Simple test script to verify FreeCaster plugin loads correctly +""" +import subprocess +import sys +import os + +def test_plugin(): + print("Testing FreeCaster Plugin...") + + # Check if VST3 plugin exists + vst3_path = "/Users/edahl/Library/Audio/Plug-Ins/VST3/FreeCaster.vst3" + if os.path.exists(vst3_path): + print("✅ VST3 plugin found") + else: + print("❌ VST3 plugin not found") + return False + + # Check if AU plugin exists + au_path = "/Users/edahl/Library/Audio/Plug-Ins/Components/FreeCaster.component" + if os.path.exists(au_path): + print("✅ AU plugin found") + else: + print("❌ AU plugin not found") + return False + + # Check if standalone app exists + standalone_path = "/Users/edahl/FreeCaster/build/FreeCaster_artefacts/Standalone/FreeCaster.app" + if os.path.exists(standalone_path): + print("✅ Standalone app found") + else: + print("❌ Standalone app not found") + return False + + print("\n🎵 Plugin Installation Summary:") + print(" • VST3: Available for Ableton Live, Logic Pro, etc.") + print(" • AU: Available for Logic Pro, GarageBand, etc.") + print(" • Standalone: Available for direct testing") + + print("\n📋 Testing Instructions for Ableton Live:") + print(" 1. Open Ableton Live 11") + print(" 2. Create a new audio track") + print(" 3. Add FreeCaster as an Audio Effect") + print(" 4. Look for two vertical meters on the right side") + print(" 5. Click 'Test Meters' button to verify functionality") + + return True + +if __name__ == "__main__": + success = test_plugin() + sys.exit(0 if success else 1) +