From 098abe4a2f69ff990fb966fae580b8baca4dedea Mon Sep 17 00:00:00 2001 From: Julian Storer Date: Fri, 17 Dec 2010 12:35:56 +0000 Subject: [PATCH] Fixed a very very subtle component bug. Added peak level detection to AudioThumbnail. Tidied up some old demo plugin UI code. --- .../demo/Source/PluginEditor.cpp | 71 +++++++------- .../audio plugins/demo/Source/PluginEditor.h | 10 +- juce_amalgamated.cpp | 98 ++++++++++++------- juce_amalgamated.h | 27 +++-- .../juce_AudioThumbnail.cpp | 40 ++++++++ .../audio_file_formats/juce_AudioThumbnail.h | 6 ++ .../processors/juce_AudioProcessorGraph.cpp | 12 +-- src/core/juce_StandardHeader.h | 2 +- src/events/juce_Timer.h | 15 +-- src/gui/components/juce_Component.cpp | 46 +++------ src/gui/components/juce_Component.h | 4 +- 11 files changed, 199 insertions(+), 132 deletions(-) diff --git a/extras/audio plugins/demo/Source/PluginEditor.cpp b/extras/audio plugins/demo/Source/PluginEditor.cpp index a998410907..b8b7e5c432 100644 --- a/extras/audio plugins/demo/Source/PluginEditor.cpp +++ b/extras/audio plugins/demo/Source/PluginEditor.cpp @@ -13,32 +13,38 @@ //============================================================================== JuceDemoPluginAudioProcessorEditor::JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor* ownerFilter) - : AudioProcessorEditor (ownerFilter) + : AudioProcessorEditor (ownerFilter), + midiKeyboard (ownerFilter->keyboardState, MidiKeyboardComponent::horizontalKeyboard), + infoLabel (String::empty), + gainLabel ("", "Throughput level:"), + delayLabel ("", "Delay:"), + gainSlider ("gain"), + delaySlider ("delay") { - addAndMakeVisible (gainSlider = new Slider ("gain")); - gainSlider->setSliderStyle (Slider::Rotary); - gainSlider->addListener (this); - gainSlider->setRange (0.0, 1.0, 0.01); - Label* l = new Label ("", "Throughput level:"); - l->attachToComponent (gainSlider, false); - l->setFont (Font (11.0f)); + // add some sliders.. + addAndMakeVisible (&gainSlider); + gainSlider.setSliderStyle (Slider::Rotary); + gainSlider.addListener (this); + gainSlider.setRange (0.0, 1.0, 0.01); - addAndMakeVisible (delaySlider = new Slider ("delay")); - delaySlider->setSliderStyle (Slider::Rotary); - delaySlider->addListener (this); - delaySlider->setRange (0.0, 1.0, 0.01); - l = new Label ("", "Delay:"); - l->attachToComponent (delaySlider, false); - l->setFont (Font (11.0f)); + addAndMakeVisible (&delaySlider); + delaySlider.setSliderStyle (Slider::Rotary); + delaySlider.addListener (this); + delaySlider.setRange (0.0, 1.0, 0.01); - // create and add the midi keyboard component.. - addAndMakeVisible (midiKeyboard - = new MidiKeyboardComponent (ownerFilter->keyboardState, - MidiKeyboardComponent::horizontalKeyboard)); + // add some labels for the sliders.. + gainLabel.attachToComponent (&gainSlider, false); + gainLabel.setFont (Font (11.0f)); + + delayLabel.attachToComponent (&delaySlider, false); + delayLabel.setFont (Font (11.0f)); + + // add the midi keyboard component.. + addAndMakeVisible (&midiKeyboard); // add a label that will display the current timecode and status.. - addAndMakeVisible (infoLabel = new Label (String::empty, String::empty)); - infoLabel->setColour (Label::textColourId, Colours::blue); + addAndMakeVisible (&infoLabel); + infoLabel.setColour (Label::textColourId, Colours::blue); // add the triangular resizer component for the bottom-right of the UI addAndMakeVisible (resizer = new ResizableCornerComponent (this, &resizeLimits)); @@ -53,7 +59,6 @@ JuceDemoPluginAudioProcessorEditor::JuceDemoPluginAudioProcessorEditor (JuceDemo JuceDemoPluginAudioProcessorEditor::~JuceDemoPluginAudioProcessorEditor() { - deleteAllChildren(); } //============================================================================== @@ -65,12 +70,12 @@ void JuceDemoPluginAudioProcessorEditor::paint (Graphics& g) void JuceDemoPluginAudioProcessorEditor::resized() { - infoLabel->setBounds (10, 4, 400, 25); - gainSlider->setBounds (20, 60, 150, 40); - delaySlider->setBounds (200, 60, 150, 40); + infoLabel.setBounds (10, 4, 400, 25); + gainSlider.setBounds (20, 60, 150, 40); + delaySlider.setBounds (200, 60, 150, 40); const int keyboardHeight = 70; - midiKeyboard->setBounds (4, getHeight() - keyboardHeight - 4, getWidth() - 8, keyboardHeight); + midiKeyboard.setBounds (4, getHeight() - keyboardHeight - 4, getWidth() - 8, keyboardHeight); resizer->setBounds (getWidth() - 16, getHeight() - 16, 16, 16); @@ -89,25 +94,25 @@ void JuceDemoPluginAudioProcessorEditor::timerCallback() if (lastDisplayedPosition != newPos) displayPositionInfo (newPos); - gainSlider->setValue (ourProcessor->gain, false); - delaySlider->setValue (ourProcessor->delay, false); + gainSlider.setValue (ourProcessor->gain, false); + delaySlider.setValue (ourProcessor->delay, false); } // This is our Slider::Listener callback, when the user drags a slider. void JuceDemoPluginAudioProcessorEditor::sliderValueChanged (Slider* slider) { - if (slider == gainSlider) + if (slider == &gainSlider) { // It's vital to use setParameterNotifyingHost to change any parameters that are automatable // by the host, rather than just modifying them directly, otherwise the host won't know // that they've changed. getProcessor()->setParameterNotifyingHost (JuceDemoPluginAudioProcessor::gainParam, - (float) gainSlider->getValue()); + (float) gainSlider.getValue()); } - else if (slider == delaySlider) + else if (slider == &delaySlider) { getProcessor()->setParameterNotifyingHost (JuceDemoPluginAudioProcessor::delayParam, - (float) delaySlider->getValue()); + (float) delaySlider.getValue()); } } @@ -169,5 +174,5 @@ void JuceDemoPluginAudioProcessorEditor::displayPositionInfo (const AudioPlayHea else if (pos.isPlaying) displayText << " (playing)"; - infoLabel->setText (displayText, false); + infoLabel.setText (displayText, false); } diff --git a/extras/audio plugins/demo/Source/PluginEditor.h b/extras/audio plugins/demo/Source/PluginEditor.h index 3e260c0f98..ebaac3219f 100644 --- a/extras/audio plugins/demo/Source/PluginEditor.h +++ b/extras/audio plugins/demo/Source/PluginEditor.h @@ -34,11 +34,11 @@ public: void sliderValueChanged (Slider*); private: - MidiKeyboardComponent* midiKeyboard; - Label* infoLabel; - Slider* gainSlider; - Slider* delaySlider; - ResizableCornerComponent* resizer; + MidiKeyboardComponent midiKeyboard; + Label infoLabel, gainLabel, delayLabel; + Slider gainSlider; + Slider delaySlider; + ScopedPointer resizer; ComponentBoundsConstrainer resizeLimits; AudioPlayHead::CurrentPositionInfo lastDisplayedPosition; diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index abc3d95609..92c9f3b113 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -21988,6 +21988,12 @@ struct AudioThumbnail::MinMaxValue return maxValue > minValue; } + inline int getPeak() const throw() + { + return jmax (std::abs ((int) minValue), + std::abs ((int) maxValue)); + } + inline void read (InputStream& input) { minValue = input.readByte(); @@ -22186,6 +22192,7 @@ class AudioThumbnail::ThumbData { public: ThumbData (const int numThumbSamples) + : peakLevel (-1) { ensureSize (numThumbSamples); } @@ -22232,6 +22239,8 @@ public: void write (const MinMaxValue* const source, const int startIndex, const int numValues) { + resetPeak(); + if (startIndex + numValues > data.size()) ensureSize (startIndex + numValues); @@ -22241,8 +22250,29 @@ public: dest[i] = source[i]; } + void resetPeak() + { + peakLevel = -1; + } + + int getPeak() + { + if (peakLevel < 0) + { + for (int i = 0; i < data.size(); ++i) + { + const int peak = data[i].getPeak(); + if (peak > peakLevel) + peakLevel = peak; + } + } + + return peakLevel; + } + private: Array data; + int peakLevel; void ensureSize (const int thumbSamples) { @@ -22619,6 +22649,16 @@ bool AudioThumbnail::isFullyLoaded() const throw() return numSamplesFinished >= totalSamples - samplesPerThumbSample; } +float AudioThumbnail::getApproximatePeak() const +{ + int peak = 0; + + for (int i = channels.size(); --i >= 0;) + peak = jmax (peak, channels.getUnchecked(i)->getPeak()); + + return jlimit (0, 127, peak) / 127.0f; +} + void AudioThumbnail::drawChannel (Graphics& g, const Rectangle& area, double startTime, double endTime, int channelNum, float verticalZoomFactor) { @@ -36682,10 +36722,10 @@ public: : graph (graph_), orderedNodes (orderedNodes_) { - nodeIds.add (zeroNodeID); // first buffer is read-only zeros + nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros channels.add (0); - midiNodeIds.add (zeroNodeID); + midiNodeIds.add ((uint32) zeroNodeID); for (int i = 0; i < orderedNodes.size(); ++i) { @@ -36969,7 +37009,7 @@ private: if (midiNodeIds.getUnchecked(i) == freeNodeID) return i; - midiNodeIds.add (freeNodeID); + midiNodeIds.add ((uint32) freeNodeID); return midiNodeIds.size() - 1; } else @@ -36978,7 +37018,7 @@ private: if (nodeIds.getUnchecked(i) == freeNodeID) return i; - nodeIds.add (freeNodeID); + nodeIds.add ((uint32) freeNodeID); channels.add (0); return nodeIds.size() - 1; } @@ -37018,7 +37058,7 @@ private: nodeIds.getUnchecked(i), channels.getUnchecked(i))) { - nodeIds.set (i, freeNodeID); + nodeIds.set (i, (uint32) freeNodeID); } } @@ -37029,7 +37069,7 @@ private: midiNodeIds.getUnchecked(i), AudioProcessorGraph::midiChannelIndex)) { - midiNodeIds.set (i, freeNodeID); + midiNodeIds.set (i, (uint32) freeNodeID); } } } @@ -40198,8 +40238,7 @@ void Component::setVisible (bool shouldBeVisible) if (! shouldBeVisible) { - if (currentlyFocusedComponent == this - || isParentOf (currentlyFocusedComponent)) + if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) { if (parentComponent_ != 0) parentComponent_->grabKeyboardFocus(); @@ -41074,32 +41113,18 @@ Component* Component::removeChildComponent (const int index) childComponentList_.remove (index); child->parentComponent_ = 0; - if (childShowing) + // (NB: there are obscure situations where a childShowing = false, but it still has the focus) + if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) { - JUCE_TRY - { - if ((currentlyFocusedComponent == child) - || child->isParentOf (currentlyFocusedComponent)) - { - // get rid first to force the grabKeyboardFocus to change to us. - giveAwayFocus(); - grabKeyboardFocus(); - } - } - #if JUCE_CATCH_UNHANDLED_EXCEPTIONS - catch (const std::exception& e) - { - currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); - JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); - } - catch (...) - { - currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); - JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__); - } - #endif + SafePointer thisPointer (this); + + giveAwayFocus(); + + if (thisPointer == 0) + return child; + + if (childShowing) + grabKeyboardFocus(); } child->internalHierarchyChanged(); @@ -42545,14 +42570,13 @@ Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() throw() void Component::giveAwayFocus() { - // use a copy so we can clear the value before the call - SafePointer componentLosingFocus (currentlyFocusedComponent); - + Component* const componentLosingFocus = currentlyFocusedComponent; currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); if (componentLosingFocus != 0) componentLosingFocus->internalFocusLoss (focusChangedDirectly); + + Desktop::getInstance().triggerFocusCallback(); } bool Component::isMouseOver (const bool includeChildren) const diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 6030ab24f3..70deb5a7d0 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -64,7 +64,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 105 +#define JUCE_BUILDNUMBER 106 /** Current Juce version number. @@ -28178,9 +28178,9 @@ private: bool isDisabledFlag : 1; bool childCompFocusedFlag : 1; bool dontClipGraphicsFlag : 1; -#if JUCE_DEBUG + #if JUCE_DEBUG bool isInsidePaintCall : 1; -#endif + #endif }; union @@ -29126,18 +29126,19 @@ private: class InternalTimerThread; /** - Repeatedly calls a user-defined method at a specified time interval. + Makes repeated callbacks to a virtual method at a specified time interval. A Timer's timerCallback() method will be repeatedly called at a given - interval. Initially when a Timer object is created, they will do nothing - until the startTimer() method is called, then the message thread will - start calling it back until stopTimer() is called. + interval. When you create a Timer object, it will do nothing until the + startTimer() method is called, which will cause the message thread to + start making callbacks at the specified interval, until stopTimer() is called + or the object is deleted. The time interval isn't guaranteed to be precise to any more than maybe 10-20ms, and the intervals may end up being much longer than requested if the - system is busy. Because it's the message thread that is doing the callbacks, - any messages that take a significant amount of time to process will block - all the timers for that period. + system is busy. Because the callbacks are made by the main message thread, + anything that blocks the message queue for a period of time will also prevent + any timers from running until it can carry on. If you need to have a single callback that is shared by multiple timers with different frequencies, then the MultiTimer class allows you to do that - its @@ -32741,6 +32742,12 @@ public: /** Returns true if the low res preview is fully generated. */ bool isFullyLoaded() const throw(); + /** Returns the highest level in the thumbnail. + Note that because the thumb only stores low-resolution data, this isn't + an accurate representation of the highest value, it's only a rough approximation. + */ + float getApproximatePeak() const; + /** Returns the hash code that was set by setSource() or setReader(). */ int64 getHashCode() const; diff --git a/src/audio/audio_file_formats/juce_AudioThumbnail.cpp b/src/audio/audio_file_formats/juce_AudioThumbnail.cpp index cf9b6e49ac..6176ba72d8 100644 --- a/src/audio/audio_file_formats/juce_AudioThumbnail.cpp +++ b/src/audio/audio_file_formats/juce_AudioThumbnail.cpp @@ -62,6 +62,12 @@ struct AudioThumbnail::MinMaxValue return maxValue > minValue; } + inline int getPeak() const throw() + { + return jmax (std::abs ((int) minValue), + std::abs ((int) maxValue)); + } + inline void read (InputStream& input) { minValue = input.readByte(); @@ -262,6 +268,7 @@ class AudioThumbnail::ThumbData { public: ThumbData (const int numThumbSamples) + : peakLevel (-1) { ensureSize (numThumbSamples); } @@ -308,6 +315,8 @@ public: void write (const MinMaxValue* const source, const int startIndex, const int numValues) { + resetPeak(); + if (startIndex + numValues > data.size()) ensureSize (startIndex + numValues); @@ -317,8 +326,29 @@ public: dest[i] = source[i]; } + void resetPeak() + { + peakLevel = -1; + } + + int getPeak() + { + if (peakLevel < 0) + { + for (int i = 0; i < data.size(); ++i) + { + const int peak = data[i].getPeak(); + if (peak > peakLevel) + peakLevel = peak; + } + } + + return peakLevel; + } + private: Array data; + int peakLevel; void ensureSize (const int thumbSamples) { @@ -700,6 +730,16 @@ bool AudioThumbnail::isFullyLoaded() const throw() return numSamplesFinished >= totalSamples - samplesPerThumbSample; } +float AudioThumbnail::getApproximatePeak() const +{ + int peak = 0; + + for (int i = channels.size(); --i >= 0;) + peak = jmax (peak, channels.getUnchecked(i)->getPeak()); + + return jlimit (0, 127, peak) / 127.0f; +} + void AudioThumbnail::drawChannel (Graphics& g, const Rectangle& area, double startTime, double endTime, int channelNum, float verticalZoomFactor) { diff --git a/src/audio/audio_file_formats/juce_AudioThumbnail.h b/src/audio/audio_file_formats/juce_AudioThumbnail.h index 4a18a4b690..95fe52c203 100644 --- a/src/audio/audio_file_formats/juce_AudioThumbnail.h +++ b/src/audio/audio_file_formats/juce_AudioThumbnail.h @@ -177,6 +177,12 @@ public: /** Returns true if the low res preview is fully generated. */ bool isFullyLoaded() const throw(); + /** Returns the highest level in the thumbnail. + Note that because the thumb only stores low-resolution data, this isn't + an accurate representation of the highest value, it's only a rough approximation. + */ + float getApproximatePeak() const; + /** Returns the hash code that was set by setSource() or setReader(). */ int64 getHashCode() const; diff --git a/src/audio/processors/juce_AudioProcessorGraph.cpp b/src/audio/processors/juce_AudioProcessorGraph.cpp index e300c22836..2199190cb8 100644 --- a/src/audio/processors/juce_AudioProcessorGraph.cpp +++ b/src/audio/processors/juce_AudioProcessorGraph.cpp @@ -546,10 +546,10 @@ public: : graph (graph_), orderedNodes (orderedNodes_) { - nodeIds.add (zeroNodeID); // first buffer is read-only zeros + nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros channels.add (0); - midiNodeIds.add (zeroNodeID); + midiNodeIds.add ((uint32) zeroNodeID); for (int i = 0; i < orderedNodes.size(); ++i) { @@ -835,7 +835,7 @@ private: if (midiNodeIds.getUnchecked(i) == freeNodeID) return i; - midiNodeIds.add (freeNodeID); + midiNodeIds.add ((uint32) freeNodeID); return midiNodeIds.size() - 1; } else @@ -844,7 +844,7 @@ private: if (nodeIds.getUnchecked(i) == freeNodeID) return i; - nodeIds.add (freeNodeID); + nodeIds.add ((uint32) freeNodeID); channels.add (0); return nodeIds.size() - 1; } @@ -884,7 +884,7 @@ private: nodeIds.getUnchecked(i), channels.getUnchecked(i))) { - nodeIds.set (i, freeNodeID); + nodeIds.set (i, (uint32) freeNodeID); } } @@ -895,7 +895,7 @@ private: midiNodeIds.getUnchecked(i), AudioProcessorGraph::midiChannelIndex)) { - midiNodeIds.set (i, freeNodeID); + midiNodeIds.set (i, (uint32) freeNodeID); } } } diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index a4ef6a2334..4ab825c2cc 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 105 +#define JUCE_BUILDNUMBER 106 /** Current Juce version number. diff --git a/src/events/juce_Timer.h b/src/events/juce_Timer.h index 9cc1a08a3f..be3c9822fb 100644 --- a/src/events/juce_Timer.h +++ b/src/events/juce_Timer.h @@ -31,18 +31,19 @@ class InternalTimerThread; //============================================================================== /** - Repeatedly calls a user-defined method at a specified time interval. + Makes repeated callbacks to a virtual method at a specified time interval. A Timer's timerCallback() method will be repeatedly called at a given - interval. Initially when a Timer object is created, they will do nothing - until the startTimer() method is called, then the message thread will - start calling it back until stopTimer() is called. + interval. When you create a Timer object, it will do nothing until the + startTimer() method is called, which will cause the message thread to + start making callbacks at the specified interval, until stopTimer() is called + or the object is deleted. The time interval isn't guaranteed to be precise to any more than maybe 10-20ms, and the intervals may end up being much longer than requested if the - system is busy. Because it's the message thread that is doing the callbacks, - any messages that take a significant amount of time to process will block - all the timers for that period. + system is busy. Because the callbacks are made by the main message thread, + anything that blocks the message queue for a period of time will also prevent + any timers from running until it can carry on. If you need to have a single callback that is shared by multiple timers with different frequencies, then the MultiTimer class allows you to do that - its diff --git a/src/gui/components/juce_Component.cpp b/src/gui/components/juce_Component.cpp index 82c347d736..7dbf09d0e3 100644 --- a/src/gui/components/juce_Component.cpp +++ b/src/gui/components/juce_Component.cpp @@ -470,8 +470,7 @@ void Component::setVisible (bool shouldBeVisible) if (! shouldBeVisible) { - if (currentlyFocusedComponent == this - || isParentOf (currentlyFocusedComponent)) + if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) { if (parentComponent_ != 0) parentComponent_->grabKeyboardFocus(); @@ -1358,32 +1357,18 @@ Component* Component::removeChildComponent (const int index) childComponentList_.remove (index); child->parentComponent_ = 0; - if (childShowing) + // (NB: there are obscure situations where a childShowing = false, but it still has the focus) + if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) { - JUCE_TRY - { - if ((currentlyFocusedComponent == child) - || child->isParentOf (currentlyFocusedComponent)) - { - // get rid first to force the grabKeyboardFocus to change to us. - giveAwayFocus(); - grabKeyboardFocus(); - } - } - #if JUCE_CATCH_UNHANDLED_EXCEPTIONS - catch (const std::exception& e) - { - currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); - JUCEApplication::sendUnhandledException (&e, __FILE__, __LINE__); - } - catch (...) - { - currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); - JUCEApplication::sendUnhandledException (0, __FILE__, __LINE__); - } - #endif + SafePointer thisPointer (this); + + giveAwayFocus(); + + if (thisPointer == 0) + return child; + + if (childShowing) + grabKeyboardFocus(); } child->internalHierarchyChanged(); @@ -2854,14 +2839,13 @@ Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() throw() void Component::giveAwayFocus() { - // use a copy so we can clear the value before the call - SafePointer componentLosingFocus (currentlyFocusedComponent); - + Component* const componentLosingFocus = currentlyFocusedComponent; currentlyFocusedComponent = 0; - Desktop::getInstance().triggerFocusCallback(); if (componentLosingFocus != 0) componentLosingFocus->internalFocusLoss (focusChangedDirectly); + + Desktop::getInstance().triggerFocusCallback(); } //============================================================================== diff --git a/src/gui/components/juce_Component.h b/src/gui/components/juce_Component.h index 4e35ce66c0..62c4f1eb60 100644 --- a/src/gui/components/juce_Component.h +++ b/src/gui/components/juce_Component.h @@ -2171,9 +2171,9 @@ private: bool isDisabledFlag : 1; bool childCompFocusedFlag : 1; bool dontClipGraphicsFlag : 1; -#if JUCE_DEBUG + #if JUCE_DEBUG bool isInsidePaintCall : 1; -#endif + #endif }; union