diff --git a/extras/audio plugins/wrapper/AU/juce_AU_Wrapper.mm b/extras/audio plugins/wrapper/AU/juce_AU_Wrapper.mm index f099e3f702..95dafdfc95 100644 --- a/extras/audio plugins/wrapper/AU/juce_AU_Wrapper.mm +++ b/extras/audio plugins/wrapper/AU/juce_AU_Wrapper.mm @@ -794,7 +794,7 @@ public: while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) { - jassert (((unsigned int) midiEventPosition) < (unsigned int) numSamples); + jassert (isPositiveAndBelow (midiEventPosition, numSamples)); diff --git a/extras/audio plugins/wrapper/VST/juce_VST_Wrapper.cpp b/extras/audio plugins/wrapper/VST/juce_VST_Wrapper.cpp index 7c912b4bcc..ac25e9cbe7 100644 --- a/extras/audio plugins/wrapper/VST/juce_VST_Wrapper.cpp +++ b/extras/audio plugins/wrapper/VST/juce_VST_Wrapper.cpp @@ -825,7 +825,7 @@ public: bool getProgramNameIndexed (VstInt32 /*category*/, VstInt32 index, char* text) { - if (filter != 0 && ((unsigned int) index) < (unsigned int) filter->getNumPrograms()) + if (filter != 0 && isPositiveAndBelow (index, filter->getNumPrograms())) { filter->getProgramName (index).copyToCString (text, 24); return true; @@ -840,7 +840,7 @@ public: if (filter == 0) return 0.0f; - jassert (((unsigned int) index) < (unsigned int) filter->getNumParameters()); + jassert (isPositiveAndBelow (index, filter->getNumParameters())); return filter->getParameter (index); } @@ -848,7 +848,7 @@ public: { if (filter != 0) { - jassert (((unsigned int) index) < (unsigned int) filter->getNumParameters()); + jassert (isPositiveAndBelow (index, filter->getNumParameters())); filter->setParameter (index, value); } } @@ -857,7 +857,7 @@ public: { if (filter != 0) { - jassert (((unsigned int) index) < (unsigned int) filter->getNumParameters()); + jassert (isPositiveAndBelow (index, filter->getNumParameters())); filter->getParameterText (index).copyToCString (text, 24); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. } } @@ -866,7 +866,7 @@ public: { if (filter != 0) { - jassert (((unsigned int) index) < (unsigned int) filter->getNumParameters()); + jassert (isPositiveAndBelow (index, filter->getNumParameters())); filter->getParameterName (index).copyToCString (text, 16); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. } } @@ -1278,8 +1278,8 @@ public: public AsyncUpdater { public: - EditorCompWrapper (JuceVSTWrapper& wrapper_, AudioProcessorEditor* editor_) - : wrapper (wrapper_), editor (editor_) + EditorCompWrapper (JuceVSTWrapper& wrapper_, AudioProcessorEditor* editor) + : wrapper (wrapper_) { setOpaque (true); editor->setOpaque (true); @@ -1296,8 +1296,8 @@ public: ~EditorCompWrapper() { - jassert (isParentOf (editor)); // you mustn't remove your editor from its parent! - editor = 0; + deleteAllChildren(); // note that we can't use a ScopedPointer because the editor may + // have been transferred to another parent which takes over ownership. } void paint (Graphics&) {} @@ -1323,11 +1323,13 @@ public: AudioProcessorEditor* getEditorComp() const { - return editor; + return dynamic_cast (getChildComponent (0)); } void resized() { + Component* const editor = getChildComponent(0); + if (editor != 0) editor->setBounds (getLocalBounds()); } @@ -1377,7 +1379,6 @@ public: private: //============================================================================== JuceVSTWrapper& wrapper; - ScopedPointer editor; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper); }; diff --git a/extras/juce demo/Source/demos/AudioDemoPlaybackPage.cpp b/extras/juce demo/Source/demos/AudioDemoPlaybackPage.cpp index 50dcd12f61..f90034f32f 100644 --- a/extras/juce demo/Source/demos/AudioDemoPlaybackPage.cpp +++ b/extras/juce demo/Source/demos/AudioDemoPlaybackPage.cpp @@ -82,20 +82,14 @@ public: if (thumbnail.getTotalLength() > 0) { - int heightPerChannel = (getHeight() - 4) / thumbnail.getNumChannels(); - - for (int i = 0; i < thumbnail.getNumChannels(); ++i) - { - thumbnail.drawChannel (g, 2, 2 + heightPerChannel * i, - getWidth() - 4, heightPerChannel, - startTime, endTime, - i, 1.0f); - } + thumbnail.drawChannels (g, getLocalBounds().reduced (2, 2), + startTime, endTime, 1.0f); } else { g.setFont (14.0f); - g.drawFittedText ("(No audio file selected)", 0, 0, getWidth(), getHeight(), Justification::centred, 2); + g.drawFittedText ("(No audio file selected)", 0, 0, getWidth(), getHeight(), + Justification::centred, 2); } } diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 370b9cd90e..89274645ce 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -4580,13 +4580,13 @@ bool NamedValueSet::remove (const Identifier& name) const Identifier NamedValueSet::getName (const int index) const { - jassert (((unsigned int) index) < (unsigned int) values.size()); + jassert (isPositiveAndBelow (index, values.size())); return values [index].name; } const var NamedValueSet::getValueAt (const int index) const { - jassert (((unsigned int) index) < (unsigned int) values.size()); + jassert (isPositiveAndBelow (index, values.size())); return values [index].value; } @@ -12983,7 +12983,7 @@ const String String::replaceCharacters (const String& charactersToReplace, { const int index = charactersToReplace.indexOfChar (*t); - if (((unsigned int) index) < (unsigned int) len2) + if (isPositiveAndBelow (index, len2)) *t = charactersToInsertInstead [index]; ++t; @@ -13051,7 +13051,7 @@ const String String::toLowerCase() const juce_wchar& String::operator[] (const int index) { - jassert (((unsigned int) index) <= (unsigned int) length()); + jassert (isPositiveAndNotGreaterThan (index, length())); text = StringHolder::makeUnique (text); return text [index]; } @@ -14249,7 +14249,7 @@ void StringArray::clear() const String& StringArray::operator[] (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) strings.size()) + if (isPositiveAndBelow (index, strings.size())) return strings.getReference (index); return String::empty; @@ -14257,7 +14257,7 @@ const String& StringArray::operator[] (const int index) const throw() String& StringArray::getReference (const int index) throw() { - jassert (((unsigned int) index) < (unsigned int) strings.size()); + jassert (isPositiveAndBelow (index, strings.size())); return strings.getReference (index); } @@ -18376,10 +18376,10 @@ void ValueTree::SharedObject::removeAllChildren (UndoManager* const undoManager) void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { // The source index must be a valid index! - jassert (((unsigned int) currentIndex) < (unsigned int) children.size()); + jassert (isPositiveAndBelow (currentIndex, children.size())); if (currentIndex != newIndex - && ((unsigned int) currentIndex) < (unsigned int) children.size()) + && isPositiveAndBelow (currentIndex, children.size())) { if (undoManager == 0) { @@ -18388,7 +18388,7 @@ void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoMan } else { - if (((unsigned int) newIndex) >= (unsigned int) children.size()) + if (! isPositiveAndBelow (newIndex, children.size())) newIndex = children.size() - 1; undoManager->perform (new MoveChildAction (this, currentIndex, newIndex)); @@ -21312,22 +21312,6 @@ bool AudioFormatReader::read (int* const* destSamples, return true; } -static void findAudioBufferMaxMin (const float* const buffer, const int num, float& maxVal, float& minVal) throw() -{ - float mn = buffer[0]; - float mx = mn; - - for (int i = 1; i < num; ++i) - { - const float s = buffer[i]; - if (s > mx) mx = s; - if (s < mn) mn = s; - } - - maxVal = mx; - minVal = mn; -} - void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, float& lowestLeft, float& highestLeft, @@ -21365,16 +21349,16 @@ void AudioFormatReader::readMaxLevels (int64 startSampleInFile, numSamples -= numToDo; startSampleInFile += numToDo; - float bufmin, bufmax; - findAudioBufferMaxMin (reinterpret_cast (tempBuffer[0]), numToDo, bufmax, bufmin); - lmin = jmin (lmin, bufmin); - lmax = jmax (lmax, bufmax); + float bufMin, bufMax; + findMinAndMax (reinterpret_cast (tempBuffer[0]), numToDo, bufMin, bufMax); + lmin = jmin (lmin, bufMin); + lmax = jmax (lmax, bufMax); if (numChannels > 1) { - findAudioBufferMaxMin (reinterpret_cast (tempBuffer[1]), numToDo, bufmax, bufmin); - rmin = jmin (rmin, bufmin); - rmax = jmax (rmax, bufmax); + findMinAndMax (reinterpret_cast (tempBuffer[1]), numToDo, bufMin, bufMax); + rmin = jmin (rmin, bufMin); + rmax = jmax (rmax, bufMax); } } @@ -21406,21 +21390,8 @@ void AudioFormatReader::readMaxLevels (int64 startSampleInFile, for (int j = numChannels; --j >= 0;) { - int bufMax = std::numeric_limits::min(); - int bufMin = std::numeric_limits::max(); - - const int* const b = tempBuffer[j]; - - for (int i = 0; i < numToDo; ++i) - { - const int samp = b[i]; - - if (samp < bufMin) - bufMin = samp; - - if (samp > bufMax) - bufMax = samp; - } + int bufMin, bufMax; + findMinAndMax (tempBuffer[j], numToDo, bufMin, bufMax); if (j == 0) { @@ -21724,7 +21695,10 @@ public: : AbstractFifo (bufferSize), buffer (numChannels, bufferSize), timeSliceThread (timeSliceThread_), - writer (writer_), isRunning (true) + writer (writer_), + thumbnailToUpdate (0), + samplesWritten (0), + isRunning (true) { timeSliceThread.addTimeSliceClient (this); } @@ -21774,17 +21748,43 @@ public: writer->writeFromAudioSampleBuffer (buffer, start1, size1); + const ScopedLock sl (thumbnailLock); + if (thumbnailToUpdate != 0) + thumbnailToUpdate->addBlock (samplesWritten, buffer, start1, size1); + + samplesWritten += size1; + if (size2 > 0) + { writer->writeFromAudioSampleBuffer (buffer, start2, size2); + if (thumbnailToUpdate != 0) + thumbnailToUpdate->addBlock (samplesWritten, buffer, start2, size2); + + samplesWritten += size2; + } + finishedRead (size1 + size2); return true; } + void setThumbnail (AudioThumbnail* thumb) + { + if (thumb != 0) + thumb->reset (buffer.getNumChannels(), writer->sampleRate); + + const ScopedLock sl (thumbnailLock); + thumbnailToUpdate = thumb; + samplesWritten = 0; + } + private: AudioSampleBuffer buffer; TimeSliceThread& timeSliceThread; ScopedPointer writer; + CriticalSection thumbnailLock; + AudioThumbnail* thumbnailToUpdate; + int64 samplesWritten; volatile bool isRunning; JUCE_DECLARE_NON_COPYABLE (Buffer); @@ -21804,6 +21804,11 @@ bool AudioFormatWriter::ThreadedWriter::write (const float** data, int numSample return buffer->write (data, numSamples); } +void AudioFormatWriter::ThreadedWriter::setThumbnailToUpdate (AudioThumbnail* thumb) +{ + buffer->setThumbnail (thumb); +} + END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioFormatWriter.cpp ***/ @@ -22064,482 +22069,677 @@ END_JUCE_NAMESPACE /*** Start of inlined file: juce_AudioThumbnail.cpp ***/ BEGIN_JUCE_NAMESPACE -AudioThumbnail::AudioThumbnail (const int orginalSamplesPerThumbnailSample_, +struct AudioThumbnail::MinMaxValue +{ + char minValue; + char maxValue; + + MinMaxValue() : minValue (0), maxValue (0) + { + } + + inline void set (const char newMin, const char newMax) throw() + { + minValue = newMin; + maxValue = newMax; + } + + inline void setFloat (const float newMin, const float newMax) throw() + { + minValue = (char) jlimit (-128, 127, roundFloatToInt (newMin * 127.0f)); + maxValue = (char) jlimit (-128, 127, roundFloatToInt (newMax * 127.0f)); + + if (maxValue == minValue) + maxValue = (char) jmin (127, maxValue + 1); + } + + inline bool isNonZero() const throw() + { + return maxValue > minValue; + } + + inline void read (InputStream& input) + { + minValue = input.readByte(); + maxValue = input.readByte(); + } + + inline void write (OutputStream& output) + { + output.writeByte (minValue); + output.writeByte (maxValue); + } +}; + +class AudioThumbnail::LevelDataSource : public TimeSliceClient, + public Timer +{ +public: + LevelDataSource (AudioThumbnail& owner_, AudioFormatReader* newReader, int64 hash) + : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0), + hashCode (hash), owner (owner_), reader (newReader) + { + } + + LevelDataSource (AudioThumbnail& owner_, InputSource* source_) + : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0), + hashCode (source_->hashCode()), owner (owner_), source (source_) + { + } + + ~LevelDataSource() + { + owner.cache.removeTimeSliceClient (this); + } + + enum { timeBeforeDeletingReader = 2000 }; + + void initialise (int64 numSamplesFinished_) + { + const ScopedLock sl (readerLock); + + numSamplesFinished = numSamplesFinished_; + + createReader(); + + if (reader != 0) + { + lengthInSamples = reader->lengthInSamples; + numChannels = reader->numChannels; + sampleRate = reader->sampleRate; + + if (lengthInSamples <= 0) + reader = 0; + else if (! isFullyLoaded()) + owner.cache.addTimeSliceClient (this); + } + } + + void getLevels (int64 startSample, int numSamples, Array& levels) + { + const ScopedLock sl (readerLock); + createReader(); + + if (reader != 0) + { + float l[4] = { 0 }; + reader->readMaxLevels (startSample, numSamples, l[0], l[1], l[2], l[3]); + + levels.clearQuick(); + levels.addArray ((const float*) l, 4); + } + } + + void releaseResources() + { + const ScopedLock sl (readerLock); + reader = 0; + } + + bool useTimeSlice() + { + if (isFullyLoaded()) + { + if (reader != 0 && source != 0) + startTimer (timeBeforeDeletingReader); + + owner.cache.removeTimeSliceClient (this); + return false; + } + + stopTimer(); + + bool justFinished = false; + + { + const ScopedLock sl (readerLock); + + createReader(); + + if (reader != 0) + { + if (! readNextBlock()) + return true; + + justFinished = true; + } + } + + if (justFinished) + owner.cache.storeThumb (owner, hashCode); + + return false; + } + + void timerCallback() + { + stopTimer(); + releaseResources(); + } + + bool isFullyLoaded() const throw() + { + return numSamplesFinished >= lengthInSamples; + } + + inline int sampleToThumbSample (const int64 originalSample) const throw() + { + return (int) (originalSample / owner.samplesPerThumbSample); + } + + int64 lengthInSamples, numSamplesFinished; + double sampleRate; + int numChannels; + int64 hashCode; + +private: + AudioThumbnail& owner; + ScopedPointer source; + ScopedPointer reader; + CriticalSection readerLock; + + void createReader() + { + if (reader == 0 && source != 0) + { + InputStream* audioFileStream = source->createInputStream(); + + if (audioFileStream != 0) + reader = owner.formatManagerToUse.createReaderFor (audioFileStream); + } + } + + bool readNextBlock() + { + jassert (reader != 0); + + if (! isFullyLoaded()) + { + const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished); + + if (numToDo > 0) + { + int64 startSample = numSamplesFinished; + + const int firstThumbIndex = sampleToThumbSample (startSample); + const int lastThumbIndex = sampleToThumbSample (startSample + numToDo); + const int numThumbSamps = lastThumbIndex - firstThumbIndex; + + HeapBlock levelData (numThumbSamps * 2); + MinMaxValue* levels[2] = { levelData, levelData + numThumbSamps }; + + for (int i = 0; i < numThumbSamps; ++i) + { + float lowestLeft, highestLeft, lowestRight, highestRight; + + reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample, owner.samplesPerThumbSample, + lowestLeft, highestLeft, lowestRight, highestRight); + + levels[0][i].setFloat (lowestLeft, highestLeft); + levels[1][i].setFloat (lowestRight, highestRight); + } + + { + const ScopedUnlock su (readerLock); + owner.setLevels (levels, firstThumbIndex, 2, numThumbSamps); + } + + numSamplesFinished += numToDo; + } + } + + return isFullyLoaded(); + } +}; + +class AudioThumbnail::ThumbData +{ +public: + ThumbData (const int numThumbSamples) + { + ensureSize (numThumbSamples); + } + + inline MinMaxValue* getData (const int thumbSampleIndex) throw() + { + jassert (thumbSampleIndex < data.size()); + return data.getRawDataPointer() + thumbSampleIndex; + } + + int getSize() const throw() + { + return data.size(); + } + + void getMinMax (int startSample, int endSample, MinMaxValue& result) throw() + { + if (startSample >= 0) + { + endSample = jmin (endSample, data.size() - 1); + + char mx = -128; + char mn = 127; + + while (startSample <= endSample) + { + const MinMaxValue& v = data.getReference (startSample); + + if (v.minValue < mn) mn = v.minValue; + if (v.maxValue > mx) mx = v.maxValue; + + ++startSample; + } + + if (mn <= mx) + { + result.set (mn, mx); + return; + } + } + + result.set (1, 0); + } + + void write (const MinMaxValue* const source, const int startIndex, const int numValues) + { + if (startIndex + numValues > data.size()) + ensureSize (startIndex + numValues); + + MinMaxValue* const dest = getData (startIndex); + + for (int i = 0; i < numValues; ++i) + dest[i] = source[i]; + } + +private: + Array data; + + void ensureSize (const int thumbSamples) + { + const int extraNeeded = thumbSamples - data.size(); + if (extraNeeded > 0) + data.insertMultiple (-1, MinMaxValue(), extraNeeded); + } +}; + +class AudioThumbnail::CachedWindow +{ +public: + CachedWindow() + : cachedStart (0), cachedTimePerPixel (0), + numChannelsCached (0), numSamplesCached (0), + cacheNeedsRefilling (true) + { + } + + void invalidate() + { + cacheNeedsRefilling = true; + } + + void drawChannel (Graphics& g, const Rectangle& area, + const double startTime, const double endTime, + const int channelNum, const float verticalZoomFactor, + const double sampleRate, const int numChannels, const int samplesPerThumbSample, + LevelDataSource* levelData, const OwnedArray& channels) + { + refillCache (area.getWidth(), startTime, (endTime - startTime) / area.getWidth(), + sampleRate, numChannels, samplesPerThumbSample, levelData, channels); + + if (isPositiveAndBelow (channelNum, numChannelsCached)) + { + const Rectangle clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())))); + + if (! clip.isEmpty()) + { + const float topY = (float) area.getY(); + const float bottomY = (float) area.getBottom(); + const float midY = (topY + bottomY) * 0.5f; + const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f; + + const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX()); + + int x = clip.getX(); + for (int w = clip.getWidth(); --w >= 0;) + { + if (cacheData->isNonZero()) + g.drawVerticalLine (x++, jmax (midY - cacheData->maxValue * vscale - 0.3f, topY), + jmin (midY - cacheData->minValue * vscale + 0.3f, bottomY)); + + ++cacheData; + } + } + } + } + +private: + Array data; + double cachedStart, cachedTimePerPixel; + int numChannelsCached, numSamplesCached; + bool cacheNeedsRefilling; + + void refillCache (const int numSamples, double startTime, const double timePerPixel, + const double sampleRate, const int numChannels, const int samplesPerThumbSample, + LevelDataSource* levelData, const OwnedArray& channels) + { + if (numSamples <= 0 || timePerPixel <= 0.0 || sampleRate <= 0) + { + invalidate(); + return; + } + + if (numSamples == numSamplesCached + && numChannelsCached == numChannels + && startTime == cachedStart + && timePerPixel == cachedTimePerPixel + && ! cacheNeedsRefilling) + { + return; + } + + numSamplesCached = numSamples; + numChannelsCached = numChannels; + cachedStart = startTime; + cachedTimePerPixel = timePerPixel; + cacheNeedsRefilling = false; + + ensureSize (numSamples); + + if (timePerPixel * sampleRate <= samplesPerThumbSample && levelData != 0) + { + int sample = roundToInt (startTime * sampleRate); + Array levels; + + int i; + for (i = 0; i < numSamples; ++i) + { + const int nextSample = roundToInt ((startTime + timePerPixel) * sampleRate); + + if (sample >= 0) + { + if (sample >= levelData->lengthInSamples) + break; + + levelData->getLevels (sample, jmax (1, nextSample - sample), levels); + + const int numChans = jmin (levels.size() / 2, numChannelsCached); + + for (int chan = 0; chan < numChans; ++chan) + getData (chan, i)->setFloat (levels.getUnchecked (chan * 2), + levels.getUnchecked (chan * 2 + 1)); + } + + startTime += timePerPixel; + sample = nextSample; + } + + numSamplesCached = i; + } + else + { + jassert (channels.size() == numChannelsCached); + + for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum) + { + ThumbData* channelData = channels.getUnchecked (channelNum); + MinMaxValue* cacheData = getData (channelNum, 0); + + const double timeToThumbSampleFactor = sampleRate / (double) samplesPerThumbSample; + + startTime = cachedStart; + int sample = roundToInt (startTime * timeToThumbSampleFactor); + + for (int i = numSamples; --i >= 0;) + { + const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor); + + channelData->getMinMax (sample, nextSample, *cacheData); + + ++cacheData; + startTime += timePerPixel; + sample = nextSample; + } + } + } + } + + MinMaxValue* getData (const int channelNum, const int cacheIndex) throw() + { + jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size())); + + return data.getRawDataPointer() + channelNum * numSamplesCached + + cacheIndex; + } + + void ensureSize (const int numSamples) + { + const int itemsRequired = numSamples * numChannelsCached; + + if (data.size() < itemsRequired) + data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size()); + } +}; + +AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample, AudioFormatManager& formatManagerToUse_, AudioThumbnailCache& cacheToUse) : formatManagerToUse (formatManagerToUse_), cache (cacheToUse), - orginalSamplesPerThumbnailSample (orginalSamplesPerThumbnailSample_), - timeBeforeDeletingReader (2000) + window (new CachedWindow()), + samplesPerThumbSample (originalSamplesPerThumbnailSample), + totalSamples (0), + numChannels (0), + sampleRate (0) { - clear(); } AudioThumbnail::~AudioThumbnail() { - cache.removeThumbnail (this); - - const ScopedLock sl (readerLock); - reader = 0; -} - -AudioThumbnail::DataFormat* AudioThumbnail::getData() const throw() -{ - jassert (data.getData() != 0); - return static_cast (data.getData()); -} - -void AudioThumbnail::setSource (InputSource* const newSource) -{ - cache.removeThumbnail (this); - timerCallback(); // stops the timer and deletes the reader - - source = newSource; clear(); - - if (newSource != 0 - && ! (cache.loadThumb (*this, newSource->hashCode()) - && isFullyLoaded())) - { - { - const ScopedLock sl (readerLock); - reader = createReader(); - } - - if (reader != 0) - { - initialiseFromAudioFile (*reader); - cache.addThumbnail (this); - } - } - - sendChangeMessage(); -} - -bool AudioThumbnail::useTimeSlice() -{ - const ScopedLock sl (readerLock); - - if (isFullyLoaded()) - { - if (reader != 0) - startTimer (timeBeforeDeletingReader); - - cache.removeThumbnail (this); - return false; - } - - if (reader == 0) - reader = createReader(); - - if (reader != 0) - { - readNextBlockFromAudioFile (*reader); - stopTimer(); - - sendChangeMessage(); - - const bool justFinished = isFullyLoaded(); - - if (justFinished) - cache.storeThumb (*this, source->hashCode()); - - return ! justFinished; - } - - return false; -} - -AudioFormatReader* AudioThumbnail::createReader() const -{ - if (source != 0) - { - InputStream* const audioFileStream = source->createInputStream(); - - if (audioFileStream != 0) - return formatManagerToUse.createReaderFor (audioFileStream); - } - - return 0; -} - -void AudioThumbnail::timerCallback() -{ - stopTimer(); - - const ScopedLock sl (readerLock); - reader = 0; } void AudioThumbnail::clear() { - data.setSize (sizeof (DataFormat) + 3); + source = 0; - DataFormat* const d = getData(); + const ScopedLock sl (lock); + window->invalidate(); + channels.clear(); + totalSamples = numSamplesFinished = 0; + numChannels = 0; + sampleRate = 0; - d->thumbnailMagic[0] = 'j'; - d->thumbnailMagic[1] = 'a'; - d->thumbnailMagic[2] = 't'; - d->thumbnailMagic[3] = 'm'; + sendChangeMessage(); +} - d->samplesPerThumbSample = orginalSamplesPerThumbnailSample; - d->totalSamples = 0; - d->numFinishedSamples = 0; - d->numThumbnailSamples = 0; - d->numChannels = 0; - d->sampleRate = 0; +void AudioThumbnail::reset (int newNumChannels, double newSampleRate) +{ + clear(); - numSamplesCached = 0; - cacheNeedsRefilling = true; + numChannels = newNumChannels; + sampleRate = newSampleRate; + + createChannels (0); +} + +void AudioThumbnail::createChannels (const int length) +{ + while (channels.size() < numChannels) + channels.add (new ThumbData (length)); } void AudioThumbnail::loadFrom (InputStream& input) { - const ScopedLock sl (readerLock); + clear(); - data.setSize (0); - input.readIntoMemoryBlock (data); + if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm') + return; - DataFormat* const d = getData(); - d->flipEndiannessIfBigEndian(); + samplesPerThumbSample = input.readInt(); + totalSamples = input.readInt64(); // Total number of source samples. + numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail. + int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data. + numChannels = input.readInt(); // Number of audio channels. + sampleRate = input.readInt(); // Source sample rate. + input.skipNextBytes (16); // reserved area - if (! (d->thumbnailMagic[0] == 'j' - && d->thumbnailMagic[1] == 'a' - && d->thumbnailMagic[2] == 't' - && d->thumbnailMagic[3] == 'm')) - { - clear(); - } + createChannels (numThumbnailSamples); - numSamplesCached = 0; - cacheNeedsRefilling = true; + for (int i = 0; i < numThumbnailSamples; ++i) + for (int chan = 0; chan < numChannels; ++chan) + channels.getUnchecked(chan)->getData(i)->read (input); } void AudioThumbnail::saveTo (OutputStream& output) const { - const ScopedLock sl (readerLock); + const ScopedLock sl (lock); - DataFormat* const d = getData(); - d->flipEndiannessIfBigEndian(); - output.write (d, (int) data.getSize()); - d->flipEndiannessIfBigEndian(); + const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize(); + + output.write ("jatm", 4); + output.writeInt (samplesPerThumbSample); + output.writeInt64 (totalSamples); + output.writeInt64 (numSamplesFinished); + output.writeInt (numThumbnailSamples); + output.writeInt (numChannels); + output.writeInt ((int) sampleRate); + output.writeInt64 (0); + output.writeInt64 (0); + + for (int i = 0; i < numThumbnailSamples; ++i) + for (int chan = 0; chan < numChannels; ++chan) + channels.getUnchecked(chan)->getData(i)->write (output); } -bool AudioThumbnail::initialiseFromAudioFile (AudioFormatReader& fileReader) +void AudioThumbnail::setDataSource (LevelDataSource* newSource) { - DataFormat* d = getData(); + numSamplesFinished = 0; - d->totalSamples = fileReader.lengthInSamples; - d->numChannels = jmin ((uint32) 2, fileReader.numChannels); - d->numFinishedSamples = 0; - d->sampleRate = roundToInt (fileReader.sampleRate); - d->numThumbnailSamples = (int) (d->totalSamples / d->samplesPerThumbSample) + 1; - - data.setSize (sizeof (DataFormat) + 3 + d->numThumbnailSamples * d->numChannels * 2); - - d = getData(); - zeromem (d->data, d->numThumbnailSamples * d->numChannels * 2); - - return d->totalSamples > 0; -} - -bool AudioThumbnail::readNextBlockFromAudioFile (AudioFormatReader& fileReader) -{ - DataFormat* const d = getData(); - - if (d->numFinishedSamples < d->totalSamples) + if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded()) { - const int numToDo = (int) jmin ((int64) 65536, d->totalSamples - d->numFinishedSamples); + source = newSource; // (make sure this isn't done before loadThumb is called) - generateSection (fileReader, - d->numFinishedSamples, - numToDo); - - d->numFinishedSamples += numToDo; + source->lengthInSamples = totalSamples; + source->sampleRate = sampleRate; + source->numChannels = numChannels; + source->numSamplesFinished = numSamplesFinished; } + else + { + source = newSource; // (make sure this isn't done before loadThumb is called) - cacheNeedsRefilling = true; - return d->numFinishedSamples < d->totalSamples; + const ScopedLock sl (lock); + source->initialise (numSamplesFinished); + + totalSamples = source->lengthInSamples; + sampleRate = source->sampleRate; + numChannels = source->numChannels; + + createChannels (1 + (int) (totalSamples / samplesPerThumbSample)); + } +} + +void AudioThumbnail::setSource (InputSource* const newSource) +{ + clear(); + + if (newSource != 0) + setDataSource (new LevelDataSource (*this, newSource)); +} + +void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash) +{ + clear(); + + if (newReader != 0) + setDataSource (new LevelDataSource (*this, newReader, hash)); +} + +void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming, + int startOffsetInBuffer, int numSamples) +{ + jassert (startSample >= 0); + + const int firstThumbIndex = (int) (startSample / samplesPerThumbSample); + const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample); + const int numToDo = lastThumbIndex - firstThumbIndex; + + if (numToDo > 0) + { + const int numChans = jmin (channels.size(), incoming.getNumChannels()); + + const HeapBlock thumbData (numToDo * numChans); + const HeapBlock thumbChannels (numChans); + + for (int chan = 0; chan < numChans; ++chan) + { + const float* const source = incoming.getSampleData (chan, startOffsetInBuffer); + MinMaxValue* const dest = thumbData + numToDo * chan; + thumbChannels [chan] = dest; + + for (int i = 0; i < numToDo; ++i) + { + float low, high; + const int start = i * samplesPerThumbSample; + findMinAndMax (source + start, jmin (samplesPerThumbSample, numSamples - start), low, high); + dest[i].setFloat (low, high); + } + } + + setLevels (thumbChannels, firstThumbIndex, numChans, numToDo); + } +} + +void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues) +{ + const ScopedLock sl (lock); + + for (int i = jmin (numChans, channels.size()); --i >= 0;) + channels.getUnchecked(i)->write (values[i], thumbIndex, numValues); + + numSamplesFinished = jmax (numSamplesFinished, (thumbIndex + numValues) * (int64) samplesPerThumbSample); + totalSamples = jmax (numSamplesFinished, totalSamples); + window->invalidate(); + sendChangeMessage(); } int AudioThumbnail::getNumChannels() const throw() { - return getData()->numChannels; + return numChannels; } double AudioThumbnail::getTotalLength() const throw() { - const DataFormat* const d = getData(); - - if (d->sampleRate > 0) - return d->totalSamples / (double) d->sampleRate; - else - return 0.0; -} - -void AudioThumbnail::generateSection (AudioFormatReader& fileReader, - int64 startSample, - int numSamples) -{ - DataFormat* const d = getData(); - - const int firstDataPos = (int) (startSample / d->samplesPerThumbSample); - const int lastDataPos = (int) ((startSample + numSamples) / d->samplesPerThumbSample); - - char* const l = getChannelData (0); - char* const r = getChannelData (1); - - for (int i = firstDataPos; i < lastDataPos; ++i) - { - const int sourceStart = i * d->samplesPerThumbSample; - const int sourceEnd = sourceStart + d->samplesPerThumbSample; - - float lowestLeft, highestLeft, lowestRight, highestRight; - - fileReader.readMaxLevels (sourceStart, - sourceEnd - sourceStart, - lowestLeft, - highestLeft, - lowestRight, - highestRight); - - int n = i * 2; - - if (r != 0) - { - l [n] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f); - r [n++] = (char) jlimit (-128.0f, 127.0f, lowestRight * 127.0f); - l [n] = (char) jlimit (-128.0f, 127.0f, highestLeft * 127.0f); - r [n++] = (char) jlimit (-128.0f, 127.0f, highestRight * 127.0f); - } - else - { - l [n++] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f); - l [n++] = (char) jlimit (-128.0f, 127.0f, highestLeft * 127.0f); - } - } -} - -char* AudioThumbnail::getChannelData (int channel) const -{ - DataFormat* const d = getData(); - - if (channel >= 0 && channel < d->numChannels) - return d->data + (channel * 2 * d->numThumbnailSamples); - - return 0; + return totalSamples / sampleRate; } bool AudioThumbnail::isFullyLoaded() const throw() { - const DataFormat* const d = getData(); - return d->numFinishedSamples >= d->totalSamples; + return numSamplesFinished >= totalSamples; } -void AudioThumbnail::refillCache (const int numSamples, - double startTime, - const double timePerPixel) +void AudioThumbnail::drawChannel (Graphics& g, const Rectangle& area, double startTime, + double endTime, int channelNum, float verticalZoomFactor) { - const DataFormat* const d = getData(); + const ScopedLock sl (lock); - if (numSamples <= 0 - || timePerPixel <= 0.0 - || d->sampleRate <= 0) - { - numSamplesCached = 0; - cacheNeedsRefilling = true; - return; - } - - if (numSamples == numSamplesCached - && numChannelsCached == d->numChannels - && startTime == cachedStart - && timePerPixel == cachedTimePerPixel - && ! cacheNeedsRefilling) - { - return; - } - - numSamplesCached = numSamples; - numChannelsCached = d->numChannels; - cachedStart = startTime; - cachedTimePerPixel = timePerPixel; - - cachedLevels.ensureSize (2 * numChannelsCached * numSamples); - - const bool needExtraDetail = (timePerPixel * d->sampleRate <= d->samplesPerThumbSample); - - const ScopedLock sl (readerLock); - - cacheNeedsRefilling = false; - - if (needExtraDetail && reader == 0) - reader = createReader(); - - if (reader != 0 && timePerPixel * d->sampleRate <= d->samplesPerThumbSample) - { - startTimer (timeBeforeDeletingReader); - - char* cacheData = static_cast (cachedLevels.getData()); - int sample = roundToInt (startTime * d->sampleRate); - - for (int i = numSamples; --i >= 0;) - { - const int nextSample = roundToInt ((startTime + timePerPixel) * d->sampleRate); - - if (sample >= 0) - { - if (sample >= reader->lengthInSamples) - break; - - float lmin, lmax, rmin, rmax; - - reader->readMaxLevels (sample, - jmax (1, nextSample - sample), - lmin, lmax, rmin, rmax); - - cacheData[0] = (char) jlimit (-128, 127, roundFloatToInt (lmin * 127.0f)); - cacheData[1] = (char) jlimit (-128, 127, roundFloatToInt (lmax * 127.0f)); - - if (numChannelsCached > 1) - { - cacheData[2] = (char) jlimit (-128, 127, roundFloatToInt (rmin * 127.0f)); - cacheData[3] = (char) jlimit (-128, 127, roundFloatToInt (rmax * 127.0f)); - } - - cacheData += 2 * numChannelsCached; - } - - startTime += timePerPixel; - sample = nextSample; - } - } - else - { - for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum) - { - char* const channelData = getChannelData (channelNum); - char* cacheData = static_cast (cachedLevels.getData()) + channelNum * 2; - - const double timeToThumbSampleFactor = d->sampleRate / (double) d->samplesPerThumbSample; - - startTime = cachedStart; - int sample = roundToInt (startTime * timeToThumbSampleFactor); - const int numFinished = (int) (d->numFinishedSamples / d->samplesPerThumbSample); - - for (int i = numSamples; --i >= 0;) - { - const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor); - - if (sample >= 0 && channelData != 0) - { - char mx = -128; - char mn = 127; - - while (sample <= nextSample) - { - if (sample >= numFinished) - break; - - const int n = sample << 1; - const char sampMin = channelData [n]; - const char sampMax = channelData [n + 1]; - - if (sampMin < mn) - mn = sampMin; - - if (sampMax > mx) - mx = sampMax; - - ++sample; - } - - if (mn <= mx) - { - cacheData[0] = mn; - cacheData[1] = mx; - } - else - { - cacheData[0] = 1; - cacheData[1] = 0; - } - } - else - { - cacheData[0] = 1; - cacheData[1] = 0; - } - - cacheData += numChannelsCached * 2; - startTime += timePerPixel; - sample = nextSample; - } - } - } + window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor, + sampleRate, numChannels, samplesPerThumbSample, source, channels); } -void AudioThumbnail::drawChannel (Graphics& g, - int x, int y, int w, int h, - double startTime, - double endTime, - int channelNum, - const float verticalZoomFactor) +void AudioThumbnail::drawChannels (Graphics& g, const Rectangle& area, double startTimeSeconds, + double endTimeSeconds, float verticalZoomFactor) { - refillCache (w, startTime, (endTime - startTime) / w); - - if (numSamplesCached >= w - && channelNum >= 0 - && channelNum < numChannelsCached) + for (int i = 0; i < numChannels; ++i) { - const float topY = (float) y; - const float bottomY = topY + h; - const float midY = topY + h * 0.5f; - const float vscale = verticalZoomFactor * h / 256.0f; + const int y1 = roundToInt ((i * area.getHeight()) / numChannels); + const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels); - const Rectangle clip (g.getClipBounds()); - const int skipLeft = jlimit (0, w, clip.getX() - x); - w -= skipLeft; - x += skipLeft; - - const char* cacheData = static_cast (cachedLevels.getData()) - + (channelNum << 1) - + skipLeft * (numChannelsCached << 1); - - while (--w >= 0) - { - const char mn = cacheData[0]; - const char mx = cacheData[1]; - cacheData += numChannelsCached << 1; - - if (mn <= mx) // if the wrong way round, signifies that the sample's not yet known - g.drawVerticalLine (x, jmax (midY - mx * vscale - 0.3f, topY), - jmin (midY - mn * vscale + 0.3f, bottomY)); - - if (++x >= clip.getRight()) - break; - } + drawChannel (g, Rectangle (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1), + startTimeSeconds, endTimeSeconds, i, verticalZoomFactor); } } -void AudioThumbnail::DataFormat::flipEndiannessIfBigEndian() throw() -{ - #if JUCE_BIG_ENDIAN - struct Flipper - { - static void flip (int32& n) { n = (int32) ByteOrder::swap ((uint32) n); } - static void flip (int64& n) { n = (int64) ByteOrder::swap ((uint64) n); } - }; - - Flipper::flip (samplesPerThumbSample); - Flipper::flip (totalSamples); - Flipper::flip (numFinishedSamples); - Flipper::flip (numThumbnailSamples); - Flipper::flip (numChannels); - Flipper::flip (sampleRate); - #endif -} - END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioThumbnail.cpp ***/ @@ -22567,18 +22767,26 @@ AudioThumbnailCache::~AudioThumbnailCache() { } -bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) +ThumbnailCacheEntry* AudioThumbnailCache::findThumbFor (const int64 hash) const { for (int i = thumbs.size(); --i >= 0;) - { - if (thumbs[i]->hash == hashCode) - { - MemoryInputStream in (thumbs[i]->data, false); - thumb.loadFrom (in); + if (thumbs.getUnchecked(i)->hash == hash) + return thumbs.getUnchecked(i); - thumbs[i]->lastUsed = Time::getMillisecondCounter(); - return true; - } + return 0; +} + +bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) +{ + ThumbnailCacheEntry* te = findThumbFor (hashCode); + + if (te != 0) + { + te->lastUsed = Time::getMillisecondCounter(); + + MemoryInputStream in (te->data, false); + thumb.loadFrom (in); + return true; } return false; @@ -22587,19 +22795,7 @@ bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode void AudioThumbnailCache::storeThumb (const AudioThumbnail& thumb, const int64 hashCode) { - MemoryOutputStream out; - thumb.saveTo (out); - - ThumbnailCacheEntry* te = 0; - - for (int i = thumbs.size(); --i >= 0;) - { - if (thumbs[i]->hash == hashCode) - { - te = thumbs[i]; - break; - } - } + ThumbnailCacheEntry* te = findThumbFor (hashCode); if (te == 0) { @@ -22613,20 +22809,25 @@ void AudioThumbnailCache::storeThumb (const AudioThumbnail& thumb, else { int oldest = 0; - unsigned int oldestTime = Time::getMillisecondCounter() + 1; + uint32 oldestTime = Time::getMillisecondCounter() + 1; - int i; - for (i = thumbs.size(); --i >= 0;) - if (thumbs[i]->lastUsed < oldestTime) + for (int i = thumbs.size(); --i >= 0;) + { + if (thumbs.getUnchecked(i)->lastUsed < oldestTime) + { oldest = i; + oldestTime = thumbs.getUnchecked(i)->lastUsed; + } + } - thumbs.set (i, te); + thumbs.set (oldest, te); } } te->lastUsed = Time::getMillisecondCounter(); - te->data.setSize (0); - te->data.append (out.getData(), out.getDataSize()); + + MemoryOutputStream out (te->data, false); + thumb.saveTo (out); } void AudioThumbnailCache::clear() @@ -22634,16 +22835,6 @@ void AudioThumbnailCache::clear() thumbs.clear(); } -void AudioThumbnailCache::addThumbnail (AudioThumbnail* const thumb) -{ - addTimeSliceClient (thumb); -} - -void AudioThumbnailCache::removeThumbnail (AudioThumbnail* const thumb) -{ - removeTimeSliceClient (thumb); -} - END_JUCE_NAMESPACE /*** End of inlined file: juce_AudioThumbnailCache.cpp ***/ @@ -27177,7 +27368,7 @@ void AudioSampleBuffer::clear (const int channel, const int startSample, const int numSamples) throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); zeromem (channels [channel] + startSample, numSamples * sizeof (float)); @@ -27188,7 +27379,7 @@ void AudioSampleBuffer::applyGain (const int channel, int numSamples, const float gain) throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); if (gain != 1.0f) @@ -27219,7 +27410,7 @@ void AudioSampleBuffer::applyGainRamp (const int channel, } else { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); const float increment = (endGain - startGain) / numSamples; @@ -27250,9 +27441,9 @@ void AudioSampleBuffer::addFrom (const int destChannel, const float gain) throw() { jassert (&source != this || sourceChannel != destChannel); - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (((unsigned int) sourceChannel) < (unsigned int) source.numChannels); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (gain != 0.0f && numSamples > 0) @@ -27279,7 +27470,7 @@ void AudioSampleBuffer::addFrom (const int destChannel, int numSamples, const float gain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -27307,7 +27498,7 @@ void AudioSampleBuffer::addFromWithRamp (const int destChannel, float startGain, const float endGain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -27343,9 +27534,9 @@ void AudioSampleBuffer::copyFrom (const int destChannel, int numSamples) throw() { jassert (&source != this || sourceChannel != destChannel); - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (((unsigned int) sourceChannel) < (unsigned int) source.numChannels); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (numSamples > 0) @@ -27361,7 +27552,7 @@ void AudioSampleBuffer::copyFrom (const int destChannel, const float* source, int numSamples) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -27379,7 +27570,7 @@ void AudioSampleBuffer::copyFrom (const int destChannel, int numSamples, const float gain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -27413,7 +27604,7 @@ void AudioSampleBuffer::copyFromWithRamp (const int destChannel, float startGain, float endGain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -27447,42 +27638,17 @@ void AudioSampleBuffer::findMinMax (const int channel, float& minVal, float& maxVal) const throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); - if (numSamples <= 0) - { - minVal = 0.0f; - maxVal = 0.0f; - } - else - { - const float* d = channels [channel] + startSample; - - float mn = *d++; - float mx = mn; - - while (--numSamples > 0) // (> 0 rather than >= 0 because we've already taken the first sample) - { - const float samp = *d++; - - if (samp > mx) - mx = samp; - - if (samp < mn) - mn = samp; - } - - maxVal = mx; - minVal = mn; - } + findMinAndMax (channels [channel] + startSample, numSamples, minVal, maxVal); } float AudioSampleBuffer::getMagnitude (const int channel, const int startSample, const int numSamples) const throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); float mn, mx; @@ -27506,7 +27672,7 @@ float AudioSampleBuffer::getRMSLevel (const int channel, const int startSample, const int numSamples) const throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples <= 0 || channel < 0 || channel >= numChannels) @@ -28542,24 +28708,24 @@ bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const thro { jassert (midiChannel >= 0 && midiChannel <= 16); - return ((unsigned int) n) < 128 + return isPositiveAndBelow (n, (int) 128) && (noteStates[n] & (1 << (midiChannel - 1))) != 0; } bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const throw() { - return ((unsigned int) n) < 128 + return isPositiveAndBelow (n, (int) 128) && (noteStates[n] & midiChannelMask) != 0; } void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { jassert (midiChannel >= 0 && midiChannel <= 16); - jassert (((unsigned int) midiNoteNumber) < 128); + jassert (isPositiveAndBelow (midiNoteNumber, (int) 128)); const ScopedLock sl (lock); - if (((unsigned int) midiNoteNumber) < 128) + if (isPositiveAndBelow (midiNoteNumber, (int) 128)) { const int timeNow = (int) Time::getMillisecondCounter(); eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); @@ -28571,7 +28737,7 @@ void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) { - if (((unsigned int) midiNoteNumber) < 128) + if (isPositiveAndBelow (midiNoteNumber, (int) 128)) { noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); @@ -29026,8 +29192,8 @@ const MidiMessage MidiMessage::aftertouchChange (const int channel, const int aftertouchValue) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (((unsigned int) noteNum) <= 127); - jassert (((unsigned int) aftertouchValue) <= 127); + jassert (isPositiveAndBelow (noteNum, (int) 128)); + jassert (isPositiveAndBelow (aftertouchValue, (int) 128)); return MidiMessage (0xa0 | jlimit (0, 15, channel - 1), noteNum & 0x7f, @@ -29050,7 +29216,7 @@ const MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (((unsigned int) pressure) <= 127); + jassert (isPositiveAndBelow (pressure, (int) 128)); return MidiMessage (0xd0 | jlimit (0, 15, channel - 1), pressure & 0x7f); @@ -29089,7 +29255,7 @@ const MidiMessage MidiMessage::pitchWheel (const int channel, const int position) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (((unsigned int) position) <= 0x3fff); + jassert (isPositiveAndBelow (position, (int) 0x4000)); return MidiMessage (0xe0 | jlimit (0, 15, channel - 1), position & 127, @@ -29139,7 +29305,7 @@ const MidiMessage MidiMessage::noteOn (const int channel, const uint8 velocity) throw() { jassert (channel > 0 && channel <= 16); - jassert (((unsigned int) noteNumber) <= 127); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (0x90 | jlimit (0, 15, channel - 1), noteNumber & 127, @@ -29150,7 +29316,7 @@ const MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) throw() { jassert (channel > 0 && channel <= 16); - jassert (((unsigned int) noteNumber) <= 127); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (0x80 | jlimit (0, 15, channel - 1), noteNumber & 127, 0); } @@ -29635,7 +29801,7 @@ const String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includ static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; - if (((unsigned int) note) < 128) + if (isPositiveAndBelow (note, (int) 128)) { String s (useSharps ? sharpNoteNames [note % 12] : flatNoteNames [note % 12]); @@ -29684,7 +29850,7 @@ const String MidiMessage::getGMInstrumentName (const int n) "Applause", "Gunshot" }; - return (((unsigned int) n) < 128) ? names[n] : (const char*) 0; + return isPositiveAndBelow (n, (int) 128) ? names[n] : (const char*) 0; } const String MidiMessage::getGMInstrumentBankName (const int n) @@ -29697,7 +29863,7 @@ const String MidiMessage::getGMInstrumentBankName (const int n) "Synth Effects", "Ethnic", "Percussive", "Sound Effects" }; - return (((unsigned int) n) <= 15) ? names[n] : (const char*) 0; + return isPositiveAndBelow (n, (int) 16) ? names[n] : (const char*) 0; } const String MidiMessage::getRhythmInstrumentName (const int n) @@ -29746,7 +29912,7 @@ const String MidiMessage::getControllerName (const int n) "Poly Operation" }; - return (((unsigned int) n) < 128) ? names[n] : (const char*) 0; + return isPositiveAndBelow (n, (int) 128) ? names[n] : (const char*) 0; } END_JUCE_NAMESPACE @@ -29985,7 +30151,7 @@ double MidiMessageSequence::getEndTime() const double MidiMessageSequence::getEventTime (const int index) const { - if (((unsigned int) index) < (unsigned int) list.size()) + if (isPositiveAndBelow (index, list.size())) return list.getUnchecked (index)->message.getTimeStamp(); return 0.0; @@ -30010,7 +30176,7 @@ void MidiMessageSequence::addEvent (const MidiMessage& newMessage, void MidiMessageSequence::deleteEvent (const int index, const bool deleteMatchingNoteUp) { - if (((unsigned int) index) < (unsigned int) list.size()) + if (isPositiveAndBelow (index, list.size())) { if (deleteMatchingNoteUp) deleteEvent (getIndexOfMatchingKeyUp (index), false); @@ -30762,7 +30928,7 @@ int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const { const int i = menuResultCode - menuIdBase; - return (((unsigned int) i) < (unsigned int) types.size()) ? i : -1; + return isPositiveAndBelow (i, types.size()) ? i : -1; } END_JUCE_NAMESPACE @@ -32393,7 +32559,7 @@ float AudioUnitPluginInstance::getParameter (int index) Float32 value = 0.0f; - if (audioUnit != 0 && ((unsigned int) index) < (unsigned int) parameterIds.size()) + if (audioUnit != 0 && isPositiveAndBelow (index, parameterIds.size())) { AudioUnitGetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), @@ -32408,7 +32574,7 @@ void AudioUnitPluginInstance::setParameter (int index, float newValue) { const ScopedLock sl (lock); - if (audioUnit != 0 && ((unsigned int) index) < (unsigned int) parameterIds.size()) + if (audioUnit != 0 && isPositiveAndBelow (index, parameterIds.size())) { AudioUnitSetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), @@ -32539,7 +32705,7 @@ void AudioUnitPluginInstance::changeProgramName (int index, const String& newNam const String AudioUnitPluginInstance::getInputChannelName (int index) const { - if (((unsigned int) index) < (unsigned int) getNumInputChannels()) + if (isPositiveAndBelow (index, getNumInputChannels())) return "Input " + String (index + 1); return String::empty; @@ -32547,7 +32713,7 @@ const String AudioUnitPluginInstance::getInputChannelName (int index) const bool AudioUnitPluginInstance::isInputChannelStereoPair (int index) const { - if (((unsigned int) index) >= (unsigned int) getNumInputChannels()) + if (! isPositiveAndBelow (index, getNumInputChannels())) return false; return true; @@ -32555,7 +32721,7 @@ bool AudioUnitPluginInstance::isInputChannelStereoPair (int index) const const String AudioUnitPluginInstance::getOutputChannelName (int index) const { - if (((unsigned int) index) < (unsigned int) getNumOutputChannels()) + if (isPositiveAndBelow (index, getNumOutputChannels())) return "Output " + String (index + 1); return String::empty; @@ -32563,7 +32729,7 @@ const String AudioUnitPluginInstance::getOutputChannelName (int index) const bool AudioUnitPluginInstance::isOutputChannelStereoPair (int index) const { - if (((unsigned int) index) >= (unsigned int) getNumOutputChannels()) + if (! isPositiveAndBelow (index, getNumOutputChannels())) return false; return true; @@ -35292,7 +35458,7 @@ const String VSTPluginInstance::getCategory() const float VSTPluginInstance::getParameter (int index) { - if (effect != 0 && ((unsigned int) index) < (unsigned int) effect->numParams) + if (effect != 0 && isPositiveAndBelow (index, (int) effect->numParams)) { try { @@ -35309,7 +35475,7 @@ float VSTPluginInstance::getParameter (int index) void VSTPluginInstance::setParameter (int index, float newValue) { - if (effect != 0 && ((unsigned int) index) < (unsigned int) effect->numParams) + if (effect != 0 && isPositiveAndBelow (index, (int) effect->numParams)) { try { @@ -35872,7 +36038,7 @@ void AudioProcessor::setParameterNotifyingHost (const int parameterIndex, void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex, const float newValue) { - jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); + jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); for (int i = listeners.size(); --i >= 0;) { @@ -35890,7 +36056,7 @@ void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex void AudioProcessor::beginParameterChangeGesture (int parameterIndex) { - jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); + jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); #if JUCE_DEBUG // This means you've called beginParameterChangeGesture twice in succession without a matching @@ -35915,7 +36081,7 @@ void AudioProcessor::beginParameterChangeGesture (int parameterIndex) void AudioProcessor::endParameterChangeGesture (int parameterIndex) { - jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); + jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); #if JUCE_DEBUG // This means you've called endParameterChangeGesture without having previously called @@ -36394,11 +36560,11 @@ bool AudioProcessorGraph::removeIllegalConnections() if (source == 0 || dest == 0 || (c->sourceChannelIndex != midiChannelIndex - && (((unsigned int) c->sourceChannelIndex) >= (unsigned int) source->processor->getNumOutputChannels())) + && ! isPositiveAndBelow (c->sourceChannelIndex, source->processor->getNumOutputChannels())) || (c->sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi()) || (c->destChannelIndex != midiChannelIndex - && (((unsigned int) c->destChannelIndex) >= (unsigned int) dest->processor->getNumInputChannels())) + && ! isPositiveAndBelow (c->destChannelIndex, dest->processor->getNumInputChannels())) || (c->destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) { @@ -39876,8 +40042,8 @@ public: static inline bool hitTest (Component& comp, const Point& localPoint) { - return ((unsigned int) localPoint.getX()) < (unsigned int) comp.getWidth() - && ((unsigned int) localPoint.getY()) < (unsigned int) comp.getHeight() + return isPositiveAndBelow (localPoint.getX(), comp.getWidth()) + && isPositiveAndBelow (localPoint.getY(), comp.getHeight()) && comp.hitTest (localPoint.getX(), localPoint.getY()); } @@ -46759,7 +46925,7 @@ void CodeEditorComponent::setColourForTokenType (const int tokenType, const Colo const Colour CodeEditorComponent::getColourForTokenType (const int tokenType) const { - if (((unsigned int) tokenType) >= (unsigned int) coloursForTokenCategories.size()) + if (! isPositiveAndBelow (tokenType, coloursForTokenCategories.size())) return findColour (CodeEditorComponent::defaultTextColourId); return coloursForTokenCategories.getReference (tokenType); @@ -48878,7 +49044,7 @@ void ListBox::selectRowInternal (const int row, if ((! isRowSelected (row)) || (deselectOthersFirst && getNumSelectedRows() > 1)) { - if (((unsigned int) row) < (unsigned int) totalItems) + if (isPositiveAndBelow (row, totalItems)) { if (deselectOthersFirst) selected.clear(); @@ -48999,7 +49165,7 @@ int ListBox::getNumSelectedRows() const int ListBox::getSelectedRow (const int index) const { - return (((unsigned int) index) < (unsigned int) selected.size()) + return (isPositiveAndBelow (index, selected.size())) ? selected [index] : -1; } @@ -49015,11 +49181,11 @@ int ListBox::getLastRowSelected() const int ListBox::getRowContainingPosition (const int x, const int y) const throw() { - if (((unsigned int) x) < (unsigned int) getWidth()) + if (isPositiveAndBelow (x, getWidth())) { const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight; - if (((unsigned int) row) < (unsigned int) totalItems) + if (isPositiveAndBelow (row, totalItems)) return row; } @@ -49028,7 +49194,7 @@ int ListBox::getRowContainingPosition (const int x, const int y) const throw() int ListBox::getInsertionIndexForPosition (const int x, const int y) const throw() { - if (((unsigned int) x) < (unsigned int) getWidth()) + if (isPositiveAndBelow (x, getWidth())) { const int row = (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight; return jlimit (0, totalItems, row); @@ -51001,7 +51167,7 @@ void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidt { const int index = getIndexOfColumnId (columnId, true) + 1; - if (((unsigned int) index) < (unsigned int) numColumns) + if (isPositiveAndBelow (index, numColumns)) { const int x = getColumnPosition (index).getX(); @@ -51670,7 +51836,7 @@ void TableHeaderComponent::handleAsyncUpdate() int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const { - if (((unsigned int) mouseX) < (unsigned int) getWidth()) + if (isPositiveAndBelow (mouseX, getWidth())) { const int draggableDistance = 3; int x = 0; @@ -57026,7 +57192,7 @@ void TreeViewItem::removeSubItem (const int index, const bool deleteItem) { const ScopedLock sl (ownerView->nodeAlterationLock); - if (((unsigned int) index) < (unsigned int) subItems.size()) + if (isPositiveAndBelow (index, subItems.size())) { subItems.remove (index, deleteItem); treeHasChanged(); @@ -57421,7 +57587,7 @@ TreeViewItem* TreeViewItem::getItemOnRow (int index) throw() TreeViewItem* TreeViewItem::findItemRecursively (int targetY) throw() { - if (((unsigned int) targetY) < (unsigned int) totalHeight) + if (isPositiveAndBelow (targetY, totalHeight)) { const int h = itemHeight; @@ -59322,7 +59488,7 @@ void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, void FileSearchPathListComponent::deleteKeyPressed (int row) { - if (((unsigned int) row) < (unsigned int) path.getNumPaths()) + if (isPositiveAndBelow (row, path.getNumPaths())) { path.remove (row); changed(); @@ -62957,18 +63123,18 @@ void ScrollBar::mouseDown (const MouseEvent& e) void ScrollBar::mouseDrag (const MouseEvent& e) { - if (isDraggingThumb) + const int mousePos = vertical ? e.y : e.x; + + if (isDraggingThumb && lastMousePos != mousePos) { - const int deltaPixels = ((vertical) ? e.y : e.x) - dragStartMousePos; + const int deltaPixels = mousePos - dragStartMousePos; setCurrentRangeStart (dragStartRange + deltaPixels * (totalRange.getLength() - visibleRange.getLength()) / (thumbAreaSize - thumbSize)); } - else - { - lastMousePos = (vertical) ? e.y : e.x; - } + + lastMousePos = mousePos; } void ScrollBar::mouseUp (const MouseEvent&) @@ -63584,7 +63750,7 @@ bool TabBarButton::hitTest (int mx, int my) if (owner.getOrientation() == TabbedButtonBar::TabsAtLeft || owner.getOrientation() == TabbedButtonBar::TabsAtRight) { - if (((unsigned int) mx) < (unsigned int) getWidth() + if (isPositiveAndBelow (mx, getWidth()) && my >= area.getY() + overlapPixels && my < area.getBottom() - overlapPixels) return true; @@ -63592,7 +63758,7 @@ bool TabBarButton::hitTest (int mx, int my) else { if (mx >= area.getX() + overlapPixels && mx < area.getRight() - overlapPixels - && ((unsigned int) my) < (unsigned int) getHeight()) + && isPositiveAndBelow (my, getHeight())) return true; } @@ -63709,7 +63875,7 @@ void TabbedButtonBar::addTab (const String& tabName, if (tabName.isNotEmpty()) { - if (((unsigned int) insertIndex) > (unsigned int) tabs.size()) + if (! isPositiveAndBelow (insertIndex, tabs.size())) insertIndex = tabs.size(); TabInfo* newTab = new TabInfo(); @@ -63787,7 +63953,7 @@ void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMes { if (currentTabIndex != newIndex) { - if (((unsigned int) newIndex) >= (unsigned int) tabs.size()) + if (! isPositiveAndBelow (newIndex, tabs.size())) newIndex = -1; currentTabIndex = newIndex; @@ -68345,7 +68511,7 @@ int MenuBarComponent::getItemAt (const int x, const int y) void MenuBarComponent::repaintMenuItem (int index) { - if (((unsigned int) index) < (unsigned int) xPositions.size()) + if (isPositiveAndBelow (index, xPositions.size())) { const int x1 = xPositions [index]; const int x2 = xPositions [index + 1]; @@ -68910,7 +69076,7 @@ public: { const int y = target.getY() - windowPos.getY(); ensureItemIsVisible (itemIdThatMustBeVisible, - (((unsigned int) y) < (unsigned int) windowPos.getHeight()) ? y : -1); + isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1); } resizeToBestWindowPos(); @@ -69171,7 +69337,7 @@ public: bool overScrollArea = false; if (isScrolling() - && (isOver || (isDown && ((unsigned int) localMousePos.getX()) < (unsigned int) getWidth())) + && (isOver || (isDown && isPositiveAndBelow (localMousePos.getX(), getWidth()))) && ((isScrollZoneActive (false) && localMousePos.getY() < PopupMenuSettings::scrollZone) || (isScrollZoneActive (true) && localMousePos.getY() > getHeight() - PopupMenuSettings::scrollZone))) { @@ -72564,7 +72730,7 @@ public: int width, int height, bool rowIsSelected) { - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId) @@ -72635,7 +72801,7 @@ private: void flipEnablement (const int row) { - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { const String item (items [row]); deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); @@ -73164,7 +73330,7 @@ public: int width, int height, bool rowIsSelected) { - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId) @@ -73253,7 +73419,7 @@ public: { jassert (type == audioInputType || type == audioOutputType); - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { AudioDeviceManager::AudioDeviceSetup config; setup.manager->getAudioDeviceSetup (config); @@ -74776,8 +74942,8 @@ public: bool contains (const Point& position, bool) const { - return ((unsigned int) position.getX()) < (unsigned int) magnifierComp->getWidth() - && ((unsigned int) position.getY()) < (unsigned int) magnifierComp->getHeight(); + return isPositiveAndBelow (position.getX(), magnifierComp->getWidth()) + && isPositiveAndBelow (position.getY(), magnifierComp->getHeight()); } void repaint (const Rectangle& area) @@ -80504,7 +80670,7 @@ int ColourGradient::getNumColours() const throw() double ColourGradient::getColourPosition (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) colours.size()) + if (isPositiveAndBelow (index, colours.size())) return colours.getReference (index).position; return 0; @@ -80512,7 +80678,7 @@ double ColourGradient::getColourPosition (const int index) const throw() const Colour ColourGradient::getColour (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) colours.size()) + if (isPositiveAndBelow (index, colours.size())) return colours.getReference (index).colour; return Colour(); @@ -80520,7 +80686,7 @@ const Colour ColourGradient::getColour (const int index) const throw() void ColourGradient::setColour (int index, const Colour& newColour) throw() { - if (((unsigned int) index) < (unsigned int) colours.size()) + if (isPositiveAndBelow (index, colours.size())) colours.getReference (index).colour = newColour; } @@ -81388,7 +81554,7 @@ void EdgeTable::intersectWithEdgeTableLine (const int y, const int* otherLine) lastX = nextX; const int nextLevel = (level1 * (level2 + 1)) >> 8; - jassert (((unsigned int) nextLevel) < (unsigned int) 256); + jassert (isPositiveAndBelow (nextLevel, (int) 256)); if (nextLevel != lastLevel) { @@ -83618,9 +83784,9 @@ private: if (betterQuality) { - if (((unsigned int) loResX) < (unsigned int) maxX) + if (isPositiveAndBelow (loResX, maxX)) { - if (((unsigned int) loResY) < (unsigned int) maxY) + if (isPositiveAndBelow (loResY, maxY)) { // In the centre of the image.. render4PixelAverage (dest, this->srcData.getPixelPointer (loResX, loResY), @@ -83645,7 +83811,7 @@ private: } else { - if (((unsigned int) loResY) < (unsigned int) maxY) + if (isPositiveAndBelow (loResY, maxY)) { // At a left or right hand edge.. if (! repeatPattern) @@ -89762,7 +89928,7 @@ void GlyphArrangement::clear() PositionedGlyph& GlyphArrangement::getGlyph (const int index) const { - jassert (((unsigned int) index) < (unsigned int) glyphs.size()); + jassert (isPositiveAndBelow (index, glyphs.size())); return *glyphs [index]; } @@ -90880,7 +91046,7 @@ void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, con // Check that you're not trying to add the same character twice.. jassert (findGlyph (character, false) == 0); - if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable)) + if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable))) lookupTable [character] = (short) glyphs.size(); glyphs.add (new GlyphInfo (character, path, width)); @@ -90900,7 +91066,7 @@ void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar ch CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) throw() { - if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable) && lookupTable [character] > 0) + if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable)) && lookupTable [character] > 0) return glyphs [(int) lookupTable [(int) character]]; for (int i = 0; i < glyphs.size(); ++i) @@ -94363,7 +94529,7 @@ void RectangleList::clear() const Rectangle RectangleList::getRectangle (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) rects.size()) + if (isPositiveAndBelow (index, rects.size())) return rects.getReference (index); return Rectangle(); @@ -95078,7 +95244,7 @@ Image::BitmapData::~BitmapData() const Colour Image::BitmapData::getPixelColour (const int x, const int y) const throw() { - jassert (((unsigned int) x) < (unsigned int) width && ((unsigned int) y) < (unsigned int) height); + jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height)); const uint8* const pixel = getPixelPointer (x, y); @@ -95106,7 +95272,7 @@ const Colour Image::BitmapData::getPixelColour (const int x, const int y) const void Image::BitmapData::setPixelColour (const int x, const int y, const Colour& colour) const throw() { - jassert (((unsigned int) x) < (unsigned int) width && ((unsigned int) y) < (unsigned int) height); + jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height)); uint8* const pixel = getPixelPointer (x, y); const PixelARGB col (colour.getPixelARGB()); @@ -95185,8 +95351,7 @@ void Image::clear (const Rectangle& area, const Colour& colourToClearTo) const Colour Image::getPixelAt (const int x, const int y) const { - if (((unsigned int) x) < (unsigned int) getWidth() - && ((unsigned int) y) < (unsigned int) getHeight()) + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())) { const BitmapData srcData (*this, x, y, 1, 1); return srcData.getPixelColour (0, 0); @@ -95197,8 +95362,7 @@ const Colour Image::getPixelAt (const int x, const int y) const void Image::setPixelAt (const int x, const int y, const Colour& colour) { - if (((unsigned int) x) < (unsigned int) getWidth() - && ((unsigned int) y) < (unsigned int) getHeight()) + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())) { const BitmapData destData (*this, x, y, 1, 1, true); destData.setPixelColour (0, 0, colour); @@ -95207,8 +95371,7 @@ void Image::setPixelAt (const int x, const int y, const Colour& colour) void Image::multiplyAlphaAt (const int x, const int y, const float multiplier) { - if (((unsigned int) x) < (unsigned int) getWidth() - && ((unsigned int) y) < (unsigned int) getHeight() + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight()) && hasAlphaChannel()) { const BitmapData destData (*this, x, y, 1, 1, true); @@ -95578,22 +95741,16 @@ ImageConvolutionKernel::~ImageConvolutionKernel() float ImageConvolutionKernel::getKernelValue (const int x, const int y) const throw() { - if (((unsigned int) x) < (unsigned int) size - && ((unsigned int) y) < (unsigned int) size) - { + if (isPositiveAndBelow (x, size) && isPositiveAndBelow (y, size)) return values [x + y * size]; - } - else - { - jassertfalse; - return 0; - } + + jassertfalse; + return 0; } void ImageConvolutionKernel::setKernelValue (const int x, const int y, const float value) throw() { - if (((unsigned int) x) < (unsigned int) size - && ((unsigned int) y) < (unsigned int) size) + if (isPositiveAndBelow (x, size) && isPositiveAndBelow (y, size)) { values [x + y * size] = value; } @@ -241894,8 +242051,8 @@ public: bool contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) component->getWidth() - || ((unsigned int) position.getY()) >= (unsigned int) component->getHeight()) + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) return false; RECT r; @@ -247688,7 +247845,7 @@ AudioCDReader* AudioCDReader::createReaderForCD (const int deviceIndex) CDDeviceInfo list[8]; const int num = FindCDDevices (list, 8); - if (((unsigned int) deviceIndex) < (unsigned int) num) + if (isPositiveAndBelow (deviceIndex, num)) { CDDeviceHandle* const handle = openHandle (&(list[deviceIndex])); @@ -258325,8 +258482,7 @@ public: bool contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) ww - || ((unsigned int) position.getY()) >= (unsigned int) wh) + if (! (isPositiveAndBelow (position.getX(), ww) && isPositiveAndBelow (position.getY(), wh))) return false; for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;) @@ -258584,67 +258740,56 @@ public: { case 2: // 'KeyPress' { - ScopedXLock xlock; - XKeyEvent* const keyEvent = (XKeyEvent*) &event->xkey; - updateKeyStates (keyEvent->keycode, true); - - char utf8 [64]; - zeromem (utf8, sizeof (utf8)); + char utf8 [64] = { 0 }; + juce_wchar unicodeChar = 0; + int keyCode = 0; + bool keyDownChange = false; KeySym sym; { + ScopedXLock xlock; + XKeyEvent* const keyEvent = (XKeyEvent*) &event->xkey; + updateKeyStates (keyEvent->keycode, true); + const char* oldLocale = ::setlocale (LC_ALL, 0); ::setlocale (LC_ALL, ""); XLookupString (keyEvent, utf8, sizeof (utf8), &sym, 0); ::setlocale (LC_ALL, oldLocale); + + unicodeChar = String::fromUTF8 (utf8, sizeof (utf8) - 1) [0]; + keyCode = (int) unicodeChar; + + if (keyCode < 0x20) + keyCode = XKeycodeToKeysym (display, keyEvent->keycode, currentModifiers.isShiftDown() ? 1 : 0); + + keyDownChange = (sym != NoSymbol) && ! updateKeyModifiersFromSym (sym, true); } - const juce_wchar unicodeChar = String::fromUTF8 (utf8, sizeof (utf8) - 1) [0]; - int keyCode = (int) unicodeChar; - - if (keyCode < 0x20) - keyCode = XKeycodeToKeysym (display, keyEvent->keycode, currentModifiers.isShiftDown() ? 1 : 0); - const ModifierKeys oldMods (currentModifiers); bool keyPressed = false; - const bool keyDownChange = (sym != NoSymbol) && ! updateKeyModifiersFromSym (sym, true); - if ((sym & 0xff00) == 0xff00) { - // Translate keypad - if (sym == XK_KP_Divide) - keyCode = XK_slash; - else if (sym == XK_KP_Multiply) - keyCode = XK_asterisk; - else if (sym == XK_KP_Subtract) - keyCode = XK_hyphen; - else if (sym == XK_KP_Add) - keyCode = XK_plus; - else if (sym == XK_KP_Enter) - keyCode = XK_Return; - else if (sym == XK_KP_Decimal) - keyCode = Keys::numLock ? XK_period : XK_Delete; - else if (sym == XK_KP_0) - keyCode = Keys::numLock ? XK_0 : XK_Insert; - else if (sym == XK_KP_1) - keyCode = Keys::numLock ? XK_1 : XK_End; - else if (sym == XK_KP_2) - keyCode = Keys::numLock ? XK_2 : XK_Down; - else if (sym == XK_KP_3) - keyCode = Keys::numLock ? XK_3 : XK_Page_Down; - else if (sym == XK_KP_4) - keyCode = Keys::numLock ? XK_4 : XK_Left; - else if (sym == XK_KP_5) - keyCode = XK_5; - else if (sym == XK_KP_6) - keyCode = Keys::numLock ? XK_6 : XK_Right; - else if (sym == XK_KP_7) - keyCode = Keys::numLock ? XK_7 : XK_Home; - else if (sym == XK_KP_8) - keyCode = Keys::numLock ? XK_8 : XK_Up; - else if (sym == XK_KP_9) - keyCode = Keys::numLock ? XK_9 : XK_Page_Up; + switch (sym) // Translate keypad + { + case XK_KP_Divide: keyCode = XK_slash; break; + case XK_KP_Multiply: keyCode = XK_asterisk; break; + case XK_KP_Subtract: keyCode = XK_hyphen; break; + case XK_KP_Add: keyCode = XK_plus; break; + case XK_KP_Enter: keyCode = XK_Return; break; + case XK_KP_Decimal: keyCode = Keys::numLock ? XK_period : XK_Delete; break; + case XK_KP_0: keyCode = Keys::numLock ? XK_0 : XK_Insert; break; + case XK_KP_1: keyCode = Keys::numLock ? XK_1 : XK_End; break; + case XK_KP_2: keyCode = Keys::numLock ? XK_2 : XK_Down; break; + case XK_KP_3: keyCode = Keys::numLock ? XK_3 : XK_Page_Down; break; + case XK_KP_4: keyCode = Keys::numLock ? XK_4 : XK_Left; break; + case XK_KP_5: keyCode = XK_5; break; + case XK_KP_6: keyCode = Keys::numLock ? XK_6 : XK_Right; break; + case XK_KP_7: keyCode = Keys::numLock ? XK_7 : XK_Home; break; + case XK_KP_8: keyCode = Keys::numLock ? XK_8 : XK_Up; break; + case XK_KP_9: keyCode = Keys::numLock ? XK_9 : XK_Page_Up; break; + default: break; + } switch (sym) { @@ -266331,7 +266476,7 @@ private: #endif } - JUCE_LEAK_DETECTOR (CoreGraphicsImage); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImage); }; Image::SharedImage* Image::SharedImage::createNativeImage (PixelFormat format, int width, int height, bool clearImage) @@ -267678,8 +267823,8 @@ void UIViewComponentPeer::displayRotated() bool UIViewComponentPeer::contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) component->getWidth() - || ((unsigned int) position.getY()) >= (unsigned int) component->getHeight()) + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) return false; UIView* v = [view hitTest: CGPointMake ((float) position.getX(), (float) position.getY()) @@ -270209,7 +270354,7 @@ MidiOutput* MidiOutput::openDevice (int index) { MidiOutput* mo = 0; - if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations()) + if (isPositiveAndBelow (index, (int) MIDIGetNumberOfDestinations())) { MIDIEndpointRef endPoint = MIDIGetDestination (index); @@ -270346,7 +270491,7 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) using namespace CoreMidiHelpers; MidiInput* newInput = 0; - if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources()) + if (isPositiveAndBelow (index, (int) MIDIGetNumberOfSources())) { MIDIEndpointRef endPoint = MIDIGetSource (index); @@ -271061,7 +271206,7 @@ private: #endif } - JUCE_LEAK_DETECTOR (CoreGraphicsImage); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImage); }; Image::SharedImage* Image::SharedImage::createNativeImage (PixelFormat format, int width, int height, bool clearImage) @@ -272901,8 +273046,8 @@ bool NSViewComponentPeer::isFullScreen() const bool NSViewComponentPeer::contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) component->getWidth() - || ((unsigned int) position.getY()) >= (unsigned int) component->getHeight()) + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) return false; NSPoint p; @@ -277533,7 +277678,7 @@ public: HeapBlock types; const int num = getAllDataSourcesForDevice (deviceID, types); - if (((unsigned int) index) < (unsigned int) num) + if (isPositiveAndBelow (index, num)) { AudioObjectPropertyAddress pa; pa.mSelector = kAudioDevicePropertyDataSource; @@ -278719,7 +278864,7 @@ MidiOutput* MidiOutput::openDevice (int index) { MidiOutput* mo = 0; - if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations()) + if (isPositiveAndBelow (index, (int) MIDIGetNumberOfDestinations())) { MIDIEndpointRef endPoint = MIDIGetDestination (index); @@ -278856,7 +279001,7 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) using namespace CoreMidiHelpers; MidiInput* newInput = 0; - if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources()) + if (isPositiveAndBelow (index, (int) MIDIGetNumberOfSources())) { MIDIEndpointRef endPoint = MIDIGetSource (index); diff --git a/juce_amalgamated.h b/juce_amalgamated.h index e7344d6db7..dd4c2223d2 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 98 +#define JUCE_BUILDNUMBER 99 /** Current Juce version number. @@ -1101,6 +1101,69 @@ inline Type jmin (const Type a, const Type b, const Type c) { return (b < a) template inline Type jmin (const Type a, const Type b, const Type c, const Type d) { return jmin (a, jmin (b, c, d)); } +/** Scans an array of values, returning the minimum value that it contains. */ +template +const Type findMinimum (const Type* data, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*data++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *data++; + if (v < result) result = v; + } + + return result; +} + +/** Scans an array of values, returning the minimum value that it contains. */ +template +const Type findMaximum (const Type* values, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*values++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + if (result > v) result = v; + } + + return result; +} + +/** Scans an array of values, returning the minimum and maximum values that it contains. */ +template +void findMinAndMax (const Type* values, int numValues, Type& lowest, Type& highest) +{ + if (numValues <= 0) + { + lowest = Type(); + highest = Type(); + } + else + { + Type mn (*values++); + Type mx (mn); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + + if (mx < v) mx = v; + if (v < mn) mn = v; + } + + lowest = mn; + highest = mx; + } +} + /** Constrains a value to keep it within a given range. This will check that the specified value lies between the lower and upper bounds @@ -1129,6 +1192,44 @@ inline Type jlimit (const Type lowerLimit, : valueToConstrain); } +/** Returns true if a value is at least zero, and also below a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest < upperLimit + @endcode +*/ +template +inline bool isPositiveAndBelow (Type valueToTest, Type upperLimit) throw() +{ + jassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest < upperLimit; +} + +template <> +inline bool isPositiveAndBelow (const int valueToTest, const int upperLimit) throw() +{ + jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) < static_cast (upperLimit); +} + +/** Returns true if a value is at least zero, and also less than or equal to a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest <= upperLimit + @endcode +*/ +template +inline bool isPositiveAndNotGreaterThan (Type valueToTest, Type upperLimit) throw() +{ + jassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest <= upperLimit; +} + +template <> +inline bool isPositiveAndNotGreaterThan (const int valueToTest, const int upperLimit) throw() +{ + jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) <= static_cast (upperLimit); +} + /** Handy function to swap two values over. */ template @@ -2035,7 +2136,7 @@ public: No checks are made to see if the index is within a valid range, so be careful! */ - inline const juce_wchar& operator[] (int index) const throw() { jassert (((unsigned int) index) <= (unsigned int) length()); return text [index]; } + inline const juce_wchar& operator[] (int index) const throw() { jassert (isPositiveAndNotGreaterThan (index, length())); return text [index]; } /** Returns a character from the string such that it can also be altered. @@ -4299,8 +4400,8 @@ public: inline ElementType operator[] (const int index) const { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : ElementType(); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : ElementType(); } /** Returns one of the elements in the array, without checking the index passed in. @@ -4315,7 +4416,7 @@ public: inline const ElementType getUnchecked (const int index) const { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -4331,7 +4432,7 @@ public: inline ElementType& getReference (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -4442,7 +4543,7 @@ public: const ScopedLockType lock (getLock()); data.ensureAllocatedSize (numUsed + 1); - if (((unsigned int) indexToInsertAt) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToInsertAt, numUsed)) { ElementType* const insertPos = data.elements + indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; @@ -4480,7 +4581,7 @@ public: data.ensureAllocatedSize (numUsed + numberOfTimesToInsertIt); ElementType* insertPos; - if (((unsigned int) indexToInsertAt) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToInsertAt, numUsed)) { insertPos = data.elements + indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; @@ -4520,7 +4621,7 @@ public: data.ensureAllocatedSize (numUsed + numberOfElements); ElementType* insertPos; - if (((unsigned int) indexToInsertAt) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToInsertAt, numUsed)) { insertPos = data.elements + indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; @@ -4568,7 +4669,7 @@ public: jassert (indexToChange >= 0); const ScopedLockType lock (getLock()); - if (((unsigned int) indexToChange) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToChange, numUsed)) { data.elements [indexToChange] = newValue; } @@ -4591,7 +4692,7 @@ public: void setUnchecked (const int indexToChange, ParameterType newValue) { const ScopedLockType lock (getLock()); - jassert (((unsigned int) indexToChange) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (indexToChange, numUsed)); data.elements [indexToChange] = newValue; } @@ -4757,7 +4858,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { --numUsed; @@ -4930,8 +5031,8 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) index1) < (unsigned int) numUsed - && ((unsigned int) index2) < (unsigned int) numUsed) + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) { swapVariables (data.elements [index1], data.elements [index2]); @@ -4958,9 +5059,9 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) currentIndex) < (unsigned int) numUsed) + if (isPositiveAndBelow (currentIndex, numUsed)) { - if (((unsigned int) newIndex) >= (unsigned int) numUsed) + if (! isPositiveAndBelow (newIndex, numUsed)) newIndex = numUsed - 1; char tempCopy [sizeof (ElementType)]; @@ -7255,8 +7356,8 @@ public: inline ObjectClass* operator[] (const int index) const throw() { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : static_cast (0); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : static_cast (0); } /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. @@ -7267,7 +7368,7 @@ public: inline ObjectClass* getUnchecked (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -7472,6 +7573,11 @@ public: // object has a private destructor, both OwnedArray and // ScopedPointer would need to be friend classes.. } + else + { + jassertfalse; // you're trying to set an object at a negative index, which doesn't have + // any effect - but since the object is not being added, it may be leaking.. + } } /** Adds elements from another array to the end of this array. @@ -7635,7 +7741,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { ObjectClass** const e = data.elements + indexToRemove; @@ -7672,7 +7778,7 @@ public: ObjectClass* removedItem = 0; const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { ObjectClass** const e = data.elements + indexToRemove; removedItem = *e; @@ -7791,8 +7897,8 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) index1) < (unsigned int) numUsed - && ((unsigned int) index2) < (unsigned int) numUsed) + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) { swapVariables (data.elements [index1], data.elements [index2]); @@ -7819,9 +7925,9 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) currentIndex) < (unsigned int) numUsed) + if (isPositiveAndBelow (currentIndex, numUsed)) { - if (((unsigned int) newIndex) >= (unsigned int) numUsed) + if (! isPositiveAndBelow (newIndex, numUsed)) newIndex = numUsed - 1; ObjectClass* const value = data.elements [currentIndex]; @@ -10756,7 +10862,7 @@ public: /** Returns a range with the same length as this one, but moved to have the given start position. */ const Range movedToStartAt (const ValueType newStart) const throw() { - return Range (newStart, newStart + getLength()); + return Range (newStart, end + (newStart - start)); } /** Changes the end position of the range, leaving the start unchanged. @@ -10782,7 +10888,7 @@ public: /** Returns a range with the same length as this one, but moved to have the given start position. */ const Range movedToEndAt (const ValueType newEnd) const throw() { - return Range (newEnd - getLength(), newEnd); + return Range (start + (newEnd - end), newEnd); } /** Changes the length of the range. @@ -11003,8 +11109,8 @@ public: inline const ReferenceCountedObjectPtr operator[] (const int index) const throw() { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : static_cast (0); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : static_cast (0); } /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. @@ -11015,7 +11121,7 @@ public: inline const ReferenceCountedObjectPtr getUnchecked (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -11290,7 +11396,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { ObjectClass** const e = data.elements + indexToRemove; @@ -11402,8 +11508,8 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) index1) < (unsigned int) numUsed - && ((unsigned int) index2) < (unsigned int) numUsed) + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) { swapVariables (data.elements [index1], data.elements [index2]); @@ -11430,9 +11536,9 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) currentIndex) < (unsigned int) numUsed) + if (isPositiveAndBelow (currentIndex, numUsed)) { - if (((unsigned int) newIndex) >= (unsigned int) numUsed) + if (! isPositiveAndBelow (newIndex, numUsed)) newIndex = numUsed - 1; ObjectClass* const value = data.elements [currentIndex]; @@ -11729,8 +11835,8 @@ public: inline ElementType operator[] (const int index) const throw() { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : ElementType(); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : ElementType(); } /** Returns one of the elements in the set, without checking the index passed in. @@ -11744,7 +11850,7 @@ public: inline ElementType getUnchecked (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -11952,7 +12058,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { --numUsed; @@ -12203,7 +12309,7 @@ public: */ const Range getRange (const int rangeIndex) const { - if (((unsigned int) rangeIndex) < (unsigned int) getNumRanges()) + if (isPositiveAndBelow (rangeIndex, getNumRanges())) return Range (values.getUnchecked (rangeIndex << 1), values.getUnchecked ((rangeIndex << 1) + 1)); else @@ -31085,7 +31191,7 @@ public: */ float* getSampleData (const int channelNumber) const throw() { - jassert (((unsigned int) channelNumber) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channelNumber, numChannels)); return channels [channelNumber]; } @@ -31097,8 +31203,8 @@ public: float* getSampleData (const int channelNumber, const int sampleOffset) const throw() { - jassert (((unsigned int) channelNumber) < (unsigned int) numChannels); - jassert (((unsigned int) sampleOffset) < (unsigned int) size); + jassert (isPositiveAndBelow (channelNumber, numChannels)); + jassert (isPositiveAndBelow (sampleOffset, size)); return channels [channelNumber] + sampleOffset; } @@ -31532,6 +31638,8 @@ public: #endif // __JUCE_AUDIOSOURCE_JUCEHEADER__ /*** End of inlined file: juce_AudioSource.h ***/ +class AudioThumbnail; + /** Writes samples to an audio file stream. @@ -31677,6 +31785,13 @@ public: */ bool write (const float** data, int numSamples); + /** Allows you to specify a thumbnail that this writer should update with the + incoming data. + The thumbnail will be cleared and will the writer will begin adding data to + it as it arrives. Pass a null pointer to stop the writer updating any thumbnails. + */ + void setThumbnailToUpdate (AudioThumbnail* thumbnailToUpdate); + /** @internal */ class Buffer; // (only public for VC6 compatibility) @@ -32444,9 +32559,7 @@ class AudioThumbnailCache; @see AudioThumbnailCache */ -class JUCE_API AudioThumbnail : public ChangeBroadcaster, - public TimeSliceClient, - private Timer +class JUCE_API AudioThumbnail : public ChangeBroadcaster { public: @@ -32468,6 +32581,9 @@ public: /** Destructor. */ ~AudioThumbnail(); + /** Clears and resets the thumbnail. */ + void clear(); + /** Specifies the file or stream that contains the audio file. For a file, just call @@ -32482,29 +32598,46 @@ public: */ void setSource (InputSource* newSource); + /** Gives the thumbnail an AudioFormatReader to use directly. + This will start parsing the audio in a background thread (unless the hash code + can be looked-up successfully in the thumbnail cache). + */ + void setReader (AudioFormatReader* newReader, int64 hashCode); + + /** Resets the thumbnail, ready for adding data with the specified format. + If you're going to generate a thumbnail yourself, call this before using addBlock() + to add the data. + */ + void reset (int numChannels, double sampleRate); + + /** Adds a block of level data to the thumbnail. + Call reset() before using this, to tell the thumbnail about the data format. + */ + void addBlock (int64 sampleNumberInSource, const AudioSampleBuffer& newData, + int startOffsetInBuffer, int numSamples); + /** Reloads the low res thumbnail data from an input stream. - The thumb will automatically attempt to reload itself from its - AudioThumbnailCache. + This is not an audio file stream! It takes a stream of thumbnail data that would + previously have been created by the saveTo() method. + @see saveTo */ void loadFrom (InputStream& input); /** Saves the low res thumbnail data to an output stream. - The thumb will automatically attempt to save itself to its - AudioThumbnailCache after it finishes scanning the wave file. + The data that is written can later be reloaded using loadFrom(). + @see loadFrom */ void saveTo (OutputStream& output) const; - /** Returns the number of channels in the file. - */ + /** Returns the number of channels in the file. */ int getNumChannels() const throw(); - /** Returns the length of the audio file, in seconds. - */ + /** Returns the length of the audio file, in seconds. */ double getTotalLength() const throw(); - /** Renders the waveform shape for a channel. + /** Draws the waveform for a channel. The waveform will be drawn within the specified rectangle, where startTime and endTime specify the times within the audio file that should be positioned @@ -32515,66 +32648,60 @@ public: with the verticalZoomFactor parameter. */ void drawChannel (Graphics& g, - int x, int y, int w, int h, + const Rectangle& area, double startTimeSeconds, double endTimeSeconds, int channelNum, float verticalZoomFactor); + /** Draws the waveforms for all channels in the thumbnail. + + This will call drawChannel() to render each of the thumbnail's channels, stacked + above each other within the specified area. + + @see drawChannel + */ + void drawChannels (Graphics& g, + const Rectangle& area, + double startTimeSeconds, + double endTimeSeconds, + float verticalZoomFactor); + /** Returns true if the low res preview is fully generated. */ bool isFullyLoaded() const throw(); - /** The binary data format that is stored in the thumbnail file. */ - struct DataFormat - { - char thumbnailMagic[4]; /**< Should be 'jatm'. */ - int32 samplesPerThumbSample; /**< Number of source samples per thumbnail sample. */ - int64 totalSamples; /**< Total number of source samples. */ - int64 numFinishedSamples; /**< Number of valid source samples that have been read into the thumbnail. */ - int32 numThumbnailSamples; /**< Number of samples in the thumbnail data. */ - int32 numChannels; /**< Number of audio channels. */ - int32 sampleRate; /**< Source sample rate. */ - char future[16]; /**< Reserved for future use. */ - char data[1]; /**< The raw thumbnail samples, two signed bytes per - sample (min and max values). Channels are interleaved. */ - - /** Flips the endianness of the values in this data structure if the CPU is big-endian. */ - void flipEndiannessIfBigEndian() throw(); - }; - - /** @internal */ - bool useTimeSlice(); - /** @internal */ - void timerCallback(); + // (this is only public to avoid a VC6 bug) + class LevelDataSource; private: AudioFormatManager& formatManagerToUse; AudioThumbnailCache& cache; - ScopedPointer source; - CriticalSection readerLock; - ScopedPointer reader; - MemoryBlock data, cachedLevels; - int orginalSamplesPerThumbnailSample; - int numChannelsCached, numSamplesCached; - double cachedStart, cachedTimePerPixel; - bool cacheNeedsRefilling; - const int timeBeforeDeletingReader; - friend class AudioThumbnailCache; + struct MinMaxValue; + class ThumbData; + class CachedWindow; - void clear(); - AudioFormatReader* createReader() const; - void generateSection (AudioFormatReader& reader, int64 startSample, int numSamples); - char* getChannelData (int channel) const; - void refillCache (int numSamples, double startTime, double timePerPixel); - DataFormat* getData() const throw(); + friend class LevelDataSource; + friend class ScopedPointer; + friend class ThumbData; + friend class OwnedArray; + friend class CachedWindow; + friend class ScopedPointer; - // true if it needs more callbacks from the readNextBlockFromAudioFile() method - bool initialiseFromAudioFile (AudioFormatReader& reader); + ScopedPointer source; + ScopedPointer window; + OwnedArray channels; - // returns true if more needs to be read - bool readNextBlockFromAudioFile (AudioFormatReader& reader); + int32 samplesPerThumbSample; + int64 totalSamples, numSamplesFinished; + int32 numChannels; + double sampleRate; + CriticalSection lock; + + void setDataSource (LevelDataSource* newSource); + void setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues); + void createChannels (int length); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnail); }; @@ -32639,9 +32766,7 @@ private: OwnedArray thumbs; int maxNumThumbsToStore; - friend class AudioThumbnail; - void addThumbnail (AudioThumbnail* thumb); - void removeThumbnail (AudioThumbnail* thumb); + ThumbnailCacheEntry* findThumbFor (int64 hash) const; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnailCache); }; @@ -59759,7 +59884,7 @@ public: while (--numPoints >= 0) { const int level = *++line; - jassert (((unsigned int) level) < (unsigned int) 256); + jassert (isPositiveAndBelow (level, (int) 256)); const int endX = *++line; jassert (endX >= x); const int endOfRun = (endX >> 8); diff --git a/src/audio/audio_file_formats/juce_AudioFormatReader.cpp b/src/audio/audio_file_formats/juce_AudioFormatReader.cpp index b4b36eda0b..22e837e0b2 100644 --- a/src/audio/audio_file_formats/juce_AudioFormatReader.cpp +++ b/src/audio/audio_file_formats/juce_AudioFormatReader.cpp @@ -111,22 +111,6 @@ bool AudioFormatReader::read (int* const* destSamples, return true; } -static void findAudioBufferMaxMin (const float* const buffer, const int num, float& maxVal, float& minVal) throw() -{ - float mn = buffer[0]; - float mx = mn; - - for (int i = 1; i < num; ++i) - { - const float s = buffer[i]; - if (s > mx) mx = s; - if (s < mn) mn = s; - } - - maxVal = mx; - minVal = mn; -} - void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, float& lowestLeft, float& highestLeft, @@ -164,16 +148,16 @@ void AudioFormatReader::readMaxLevels (int64 startSampleInFile, numSamples -= numToDo; startSampleInFile += numToDo; - float bufmin, bufmax; - findAudioBufferMaxMin (reinterpret_cast (tempBuffer[0]), numToDo, bufmax, bufmin); - lmin = jmin (lmin, bufmin); - lmax = jmax (lmax, bufmax); + float bufMin, bufMax; + findMinAndMax (reinterpret_cast (tempBuffer[0]), numToDo, bufMin, bufMax); + lmin = jmin (lmin, bufMin); + lmax = jmax (lmax, bufMax); if (numChannels > 1) { - findAudioBufferMaxMin (reinterpret_cast (tempBuffer[1]), numToDo, bufmax, bufmin); - rmin = jmin (rmin, bufmin); - rmax = jmax (rmax, bufmax); + findMinAndMax (reinterpret_cast (tempBuffer[1]), numToDo, bufMin, bufMax); + rmin = jmin (rmin, bufMin); + rmax = jmax (rmax, bufMax); } } @@ -205,21 +189,8 @@ void AudioFormatReader::readMaxLevels (int64 startSampleInFile, for (int j = numChannels; --j >= 0;) { - int bufMax = std::numeric_limits::min(); - int bufMin = std::numeric_limits::max(); - - const int* const b = tempBuffer[j]; - - for (int i = 0; i < numToDo; ++i) - { - const int samp = b[i]; - - if (samp < bufMin) - bufMin = samp; - - if (samp > bufMax) - bufMax = samp; - } + int bufMin, bufMax; + findMinAndMax (tempBuffer[j], numToDo, bufMin, bufMax); if (j == 0) { diff --git a/src/audio/audio_file_formats/juce_AudioFormatWriter.cpp b/src/audio/audio_file_formats/juce_AudioFormatWriter.cpp index 96c3aeba89..110b7e3d9c 100644 --- a/src/audio/audio_file_formats/juce_AudioFormatWriter.cpp +++ b/src/audio/audio_file_formats/juce_AudioFormatWriter.cpp @@ -30,6 +30,7 @@ BEGIN_JUCE_NAMESPACE #include "juce_AudioFormat.h" #include "../dsp/juce_AudioSampleBuffer.h" #include "../../containers/juce_AbstractFifo.h" +#include "juce_AudioThumbnail.h" //============================================================================== @@ -187,7 +188,10 @@ public: : AbstractFifo (bufferSize), buffer (numChannels, bufferSize), timeSliceThread (timeSliceThread_), - writer (writer_), isRunning (true) + writer (writer_), + thumbnailToUpdate (0), + samplesWritten (0), + isRunning (true) { timeSliceThread.addTimeSliceClient (this); } @@ -237,17 +241,43 @@ public: writer->writeFromAudioSampleBuffer (buffer, start1, size1); + const ScopedLock sl (thumbnailLock); + if (thumbnailToUpdate != 0) + thumbnailToUpdate->addBlock (samplesWritten, buffer, start1, size1); + + samplesWritten += size1; + if (size2 > 0) + { writer->writeFromAudioSampleBuffer (buffer, start2, size2); + if (thumbnailToUpdate != 0) + thumbnailToUpdate->addBlock (samplesWritten, buffer, start2, size2); + + samplesWritten += size2; + } + finishedRead (size1 + size2); return true; } + void setThumbnail (AudioThumbnail* thumb) + { + if (thumb != 0) + thumb->reset (buffer.getNumChannels(), writer->sampleRate); + + const ScopedLock sl (thumbnailLock); + thumbnailToUpdate = thumb; + samplesWritten = 0; + } + private: AudioSampleBuffer buffer; TimeSliceThread& timeSliceThread; ScopedPointer writer; + CriticalSection thumbnailLock; + AudioThumbnail* thumbnailToUpdate; + int64 samplesWritten; volatile bool isRunning; JUCE_DECLARE_NON_COPYABLE (Buffer); @@ -267,5 +297,9 @@ bool AudioFormatWriter::ThreadedWriter::write (const float** data, int numSample return buffer->write (data, numSamples); } +void AudioFormatWriter::ThreadedWriter::setThumbnailToUpdate (AudioThumbnail* thumb) +{ + buffer->setThumbnail (thumb); +} END_JUCE_NAMESPACE diff --git a/src/audio/audio_file_formats/juce_AudioFormatWriter.h b/src/audio/audio_file_formats/juce_AudioFormatWriter.h index 96bc3d2ca3..f9c5555343 100644 --- a/src/audio/audio_file_formats/juce_AudioFormatWriter.h +++ b/src/audio/audio_file_formats/juce_AudioFormatWriter.h @@ -30,6 +30,7 @@ #include "juce_AudioFormatReader.h" #include "../audio_sources/juce_AudioSource.h" #include "../../threads/juce_TimeSliceThread.h" +class AudioThumbnail; //============================================================================== @@ -184,6 +185,13 @@ public: */ bool write (const float** data, int numSamples); + /** Allows you to specify a thumbnail that this writer should update with the + incoming data. + The thumbnail will be cleared and will the writer will begin adding data to + it as it arrives. Pass a null pointer to stop the writer updating any thumbnails. + */ + void setThumbnailToUpdate (AudioThumbnail* thumbnailToUpdate); + /** @internal */ class Buffer; // (only public for VC6 compatibility) diff --git a/src/audio/audio_file_formats/juce_AudioThumbnail.cpp b/src/audio/audio_file_formats/juce_AudioThumbnail.cpp index 5176d05d9b..685f3884ff 100644 --- a/src/audio/audio_file_formats/juce_AudioThumbnail.cpp +++ b/src/audio/audio_file_formats/juce_AudioThumbnail.cpp @@ -32,482 +32,683 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -AudioThumbnail::AudioThumbnail (const int orginalSamplesPerThumbnailSample_, +struct AudioThumbnail::MinMaxValue +{ + char minValue; + char maxValue; + + MinMaxValue() : minValue (0), maxValue (0) + { + } + + inline void set (const char newMin, const char newMax) throw() + { + minValue = newMin; + maxValue = newMax; + } + + inline void setFloat (const float newMin, const float newMax) throw() + { + minValue = (char) jlimit (-128, 127, roundFloatToInt (newMin * 127.0f)); + maxValue = (char) jlimit (-128, 127, roundFloatToInt (newMax * 127.0f)); + + if (maxValue == minValue) + maxValue = (char) jmin (127, maxValue + 1); + } + + inline bool isNonZero() const throw() + { + return maxValue > minValue; + } + + inline void read (InputStream& input) + { + minValue = input.readByte(); + maxValue = input.readByte(); + } + + inline void write (OutputStream& output) + { + output.writeByte (minValue); + output.writeByte (maxValue); + } +}; + +//============================================================================== +class AudioThumbnail::LevelDataSource : public TimeSliceClient, + public Timer +{ +public: + LevelDataSource (AudioThumbnail& owner_, AudioFormatReader* newReader, int64 hash) + : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0), + hashCode (hash), owner (owner_), reader (newReader) + { + } + + LevelDataSource (AudioThumbnail& owner_, InputSource* source_) + : lengthInSamples (0), numSamplesFinished (0), sampleRate (0), numChannels (0), + hashCode (source_->hashCode()), owner (owner_), source (source_) + { + } + + ~LevelDataSource() + { + owner.cache.removeTimeSliceClient (this); + } + + enum { timeBeforeDeletingReader = 2000 }; + + void initialise (int64 numSamplesFinished_) + { + const ScopedLock sl (readerLock); + + numSamplesFinished = numSamplesFinished_; + + createReader(); + + if (reader != 0) + { + lengthInSamples = reader->lengthInSamples; + numChannels = reader->numChannels; + sampleRate = reader->sampleRate; + + if (lengthInSamples <= 0) + reader = 0; + else if (! isFullyLoaded()) + owner.cache.addTimeSliceClient (this); + } + } + + void getLevels (int64 startSample, int numSamples, Array& levels) + { + const ScopedLock sl (readerLock); + createReader(); + + if (reader != 0) + { + float l[4] = { 0 }; + reader->readMaxLevels (startSample, numSamples, l[0], l[1], l[2], l[3]); + + levels.clearQuick(); + levels.addArray ((const float*) l, 4); + } + } + + void releaseResources() + { + const ScopedLock sl (readerLock); + reader = 0; + } + + bool useTimeSlice() + { + if (isFullyLoaded()) + { + if (reader != 0 && source != 0) + startTimer (timeBeforeDeletingReader); + + owner.cache.removeTimeSliceClient (this); + return false; + } + + stopTimer(); + + bool justFinished = false; + + { + const ScopedLock sl (readerLock); + + createReader(); + + if (reader != 0) + { + if (! readNextBlock()) + return true; + + justFinished = true; + } + } + + if (justFinished) + owner.cache.storeThumb (owner, hashCode); + + return false; + } + + void timerCallback() + { + stopTimer(); + releaseResources(); + } + + bool isFullyLoaded() const throw() + { + return numSamplesFinished >= lengthInSamples; + } + + inline int sampleToThumbSample (const int64 originalSample) const throw() + { + return (int) (originalSample / owner.samplesPerThumbSample); + } + + int64 lengthInSamples, numSamplesFinished; + double sampleRate; + int numChannels; + int64 hashCode; + +private: + AudioThumbnail& owner; + ScopedPointer source; + ScopedPointer reader; + CriticalSection readerLock; + + void createReader() + { + if (reader == 0 && source != 0) + { + InputStream* audioFileStream = source->createInputStream(); + + if (audioFileStream != 0) + reader = owner.formatManagerToUse.createReaderFor (audioFileStream); + } + } + + bool readNextBlock() + { + jassert (reader != 0); + + if (! isFullyLoaded()) + { + const int numToDo = (int) jmin (256 * (int64) owner.samplesPerThumbSample, lengthInSamples - numSamplesFinished); + + if (numToDo > 0) + { + int64 startSample = numSamplesFinished; + + const int firstThumbIndex = sampleToThumbSample (startSample); + const int lastThumbIndex = sampleToThumbSample (startSample + numToDo); + const int numThumbSamps = lastThumbIndex - firstThumbIndex; + + HeapBlock levelData (numThumbSamps * 2); + MinMaxValue* levels[2] = { levelData, levelData + numThumbSamps }; + + for (int i = 0; i < numThumbSamps; ++i) + { + float lowestLeft, highestLeft, lowestRight, highestRight; + + reader->readMaxLevels ((firstThumbIndex + i) * owner.samplesPerThumbSample, owner.samplesPerThumbSample, + lowestLeft, highestLeft, lowestRight, highestRight); + + levels[0][i].setFloat (lowestLeft, highestLeft); + levels[1][i].setFloat (lowestRight, highestRight); + } + + { + const ScopedUnlock su (readerLock); + owner.setLevels (levels, firstThumbIndex, 2, numThumbSamps); + } + + numSamplesFinished += numToDo; + } + } + + return isFullyLoaded(); + } +}; + +//============================================================================== +class AudioThumbnail::ThumbData +{ +public: + ThumbData (const int numThumbSamples) + { + ensureSize (numThumbSamples); + } + + inline MinMaxValue* getData (const int thumbSampleIndex) throw() + { + jassert (thumbSampleIndex < data.size()); + return data.getRawDataPointer() + thumbSampleIndex; + } + + int getSize() const throw() + { + return data.size(); + } + + void getMinMax (int startSample, int endSample, MinMaxValue& result) throw() + { + if (startSample >= 0) + { + endSample = jmin (endSample, data.size() - 1); + + char mx = -128; + char mn = 127; + + while (startSample <= endSample) + { + const MinMaxValue& v = data.getReference (startSample); + + if (v.minValue < mn) mn = v.minValue; + if (v.maxValue > mx) mx = v.maxValue; + + ++startSample; + } + + if (mn <= mx) + { + result.set (mn, mx); + return; + } + } + + result.set (1, 0); + } + + void write (const MinMaxValue* const source, const int startIndex, const int numValues) + { + if (startIndex + numValues > data.size()) + ensureSize (startIndex + numValues); + + MinMaxValue* const dest = getData (startIndex); + + for (int i = 0; i < numValues; ++i) + dest[i] = source[i]; + } + +private: + Array data; + + void ensureSize (const int thumbSamples) + { + const int extraNeeded = thumbSamples - data.size(); + if (extraNeeded > 0) + data.insertMultiple (-1, MinMaxValue(), extraNeeded); + } +}; + +//============================================================================== +class AudioThumbnail::CachedWindow +{ +public: + CachedWindow() + : cachedStart (0), cachedTimePerPixel (0), + numChannelsCached (0), numSamplesCached (0), + cacheNeedsRefilling (true) + { + } + + void invalidate() + { + cacheNeedsRefilling = true; + } + + void drawChannel (Graphics& g, const Rectangle& area, + const double startTime, const double endTime, + const int channelNum, const float verticalZoomFactor, + const double sampleRate, const int numChannels, const int samplesPerThumbSample, + LevelDataSource* levelData, const OwnedArray& channels) + { + refillCache (area.getWidth(), startTime, (endTime - startTime) / area.getWidth(), + sampleRate, numChannels, samplesPerThumbSample, levelData, channels); + + if (isPositiveAndBelow (channelNum, numChannelsCached)) + { + const Rectangle clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth())))); + + if (! clip.isEmpty()) + { + const float topY = (float) area.getY(); + const float bottomY = (float) area.getBottom(); + const float midY = (topY + bottomY) * 0.5f; + const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f; + + const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX()); + + int x = clip.getX(); + for (int w = clip.getWidth(); --w >= 0;) + { + if (cacheData->isNonZero()) + g.drawVerticalLine (x++, jmax (midY - cacheData->maxValue * vscale - 0.3f, topY), + jmin (midY - cacheData->minValue * vscale + 0.3f, bottomY)); + + ++cacheData; + } + } + } + } + +private: + Array data; + double cachedStart, cachedTimePerPixel; + int numChannelsCached, numSamplesCached; + bool cacheNeedsRefilling; + + void refillCache (const int numSamples, double startTime, const double timePerPixel, + const double sampleRate, const int numChannels, const int samplesPerThumbSample, + LevelDataSource* levelData, const OwnedArray& channels) + { + if (numSamples <= 0 || timePerPixel <= 0.0 || sampleRate <= 0) + { + invalidate(); + return; + } + + if (numSamples == numSamplesCached + && numChannelsCached == numChannels + && startTime == cachedStart + && timePerPixel == cachedTimePerPixel + && ! cacheNeedsRefilling) + { + return; + } + + numSamplesCached = numSamples; + numChannelsCached = numChannels; + cachedStart = startTime; + cachedTimePerPixel = timePerPixel; + cacheNeedsRefilling = false; + + ensureSize (numSamples); + + if (timePerPixel * sampleRate <= samplesPerThumbSample && levelData != 0) + { + int sample = roundToInt (startTime * sampleRate); + Array levels; + + int i; + for (i = 0; i < numSamples; ++i) + { + const int nextSample = roundToInt ((startTime + timePerPixel) * sampleRate); + + if (sample >= 0) + { + if (sample >= levelData->lengthInSamples) + break; + + levelData->getLevels (sample, jmax (1, nextSample - sample), levels); + + const int numChans = jmin (levels.size() / 2, numChannelsCached); + + for (int chan = 0; chan < numChans; ++chan) + getData (chan, i)->setFloat (levels.getUnchecked (chan * 2), + levels.getUnchecked (chan * 2 + 1)); + } + + startTime += timePerPixel; + sample = nextSample; + } + + numSamplesCached = i; + } + else + { + jassert (channels.size() == numChannelsCached); + + for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum) + { + ThumbData* channelData = channels.getUnchecked (channelNum); + MinMaxValue* cacheData = getData (channelNum, 0); + + const double timeToThumbSampleFactor = sampleRate / (double) samplesPerThumbSample; + + startTime = cachedStart; + int sample = roundToInt (startTime * timeToThumbSampleFactor); + + for (int i = numSamples; --i >= 0;) + { + const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor); + + channelData->getMinMax (sample, nextSample, *cacheData); + + ++cacheData; + startTime += timePerPixel; + sample = nextSample; + } + } + } + } + + MinMaxValue* getData (const int channelNum, const int cacheIndex) throw() + { + jassert (isPositiveAndBelow (channelNum, numChannelsCached) && isPositiveAndBelow (cacheIndex, data.size())); + + return data.getRawDataPointer() + channelNum * numSamplesCached + + cacheIndex; + } + + void ensureSize (const int numSamples) + { + const int itemsRequired = numSamples * numChannelsCached; + + if (data.size() < itemsRequired) + data.insertMultiple (-1, MinMaxValue(), itemsRequired - data.size()); + } +}; + +//============================================================================== +AudioThumbnail::AudioThumbnail (const int originalSamplesPerThumbnailSample, AudioFormatManager& formatManagerToUse_, AudioThumbnailCache& cacheToUse) : formatManagerToUse (formatManagerToUse_), cache (cacheToUse), - orginalSamplesPerThumbnailSample (orginalSamplesPerThumbnailSample_), - timeBeforeDeletingReader (2000) + window (new CachedWindow()), + samplesPerThumbSample (originalSamplesPerThumbnailSample), + totalSamples (0), + numChannels (0), + sampleRate (0) { - clear(); } AudioThumbnail::~AudioThumbnail() { - cache.removeThumbnail (this); - - const ScopedLock sl (readerLock); - reader = 0; -} - -AudioThumbnail::DataFormat* AudioThumbnail::getData() const throw() -{ - jassert (data.getData() != 0); - return static_cast (data.getData()); -} - -void AudioThumbnail::setSource (InputSource* const newSource) -{ - cache.removeThumbnail (this); - timerCallback(); // stops the timer and deletes the reader - - source = newSource; clear(); - - if (newSource != 0 - && ! (cache.loadThumb (*this, newSource->hashCode()) - && isFullyLoaded())) - { - { - const ScopedLock sl (readerLock); - reader = createReader(); - } - - if (reader != 0) - { - initialiseFromAudioFile (*reader); - cache.addThumbnail (this); - } - } - - sendChangeMessage(); -} - -bool AudioThumbnail::useTimeSlice() -{ - const ScopedLock sl (readerLock); - - if (isFullyLoaded()) - { - if (reader != 0) - startTimer (timeBeforeDeletingReader); - - cache.removeThumbnail (this); - return false; - } - - if (reader == 0) - reader = createReader(); - - if (reader != 0) - { - readNextBlockFromAudioFile (*reader); - stopTimer(); - - sendChangeMessage(); - - const bool justFinished = isFullyLoaded(); - - if (justFinished) - cache.storeThumb (*this, source->hashCode()); - - return ! justFinished; - } - - return false; -} - -AudioFormatReader* AudioThumbnail::createReader() const -{ - if (source != 0) - { - InputStream* const audioFileStream = source->createInputStream(); - - if (audioFileStream != 0) - return formatManagerToUse.createReaderFor (audioFileStream); - } - - return 0; -} - -void AudioThumbnail::timerCallback() -{ - stopTimer(); - - const ScopedLock sl (readerLock); - reader = 0; } void AudioThumbnail::clear() { - data.setSize (sizeof (DataFormat) + 3); + source = 0; - DataFormat* const d = getData(); + const ScopedLock sl (lock); + window->invalidate(); + channels.clear(); + totalSamples = numSamplesFinished = 0; + numChannels = 0; + sampleRate = 0; - d->thumbnailMagic[0] = 'j'; - d->thumbnailMagic[1] = 'a'; - d->thumbnailMagic[2] = 't'; - d->thumbnailMagic[3] = 'm'; - - d->samplesPerThumbSample = orginalSamplesPerThumbnailSample; - d->totalSamples = 0; - d->numFinishedSamples = 0; - d->numThumbnailSamples = 0; - d->numChannels = 0; - d->sampleRate = 0; - - numSamplesCached = 0; - cacheNeedsRefilling = true; + sendChangeMessage(); } +void AudioThumbnail::reset (int newNumChannels, double newSampleRate) +{ + clear(); + + numChannels = newNumChannels; + sampleRate = newSampleRate; + + createChannels (0); +} + +void AudioThumbnail::createChannels (const int length) +{ + while (channels.size() < numChannels) + channels.add (new ThumbData (length)); +} + +//============================================================================== void AudioThumbnail::loadFrom (InputStream& input) { - const ScopedLock sl (readerLock); + clear(); - data.setSize (0); - input.readIntoMemoryBlock (data); + if (input.readByte() != 'j' || input.readByte() != 'a' || input.readByte() != 't' || input.readByte() != 'm') + return; - DataFormat* const d = getData(); - d->flipEndiannessIfBigEndian(); + samplesPerThumbSample = input.readInt(); + totalSamples = input.readInt64(); // Total number of source samples. + numSamplesFinished = input.readInt64(); // Number of valid source samples that have been read into the thumbnail. + int32 numThumbnailSamples = input.readInt(); // Number of samples in the thumbnail data. + numChannels = input.readInt(); // Number of audio channels. + sampleRate = input.readInt(); // Source sample rate. + input.skipNextBytes (16); // reserved area - if (! (d->thumbnailMagic[0] == 'j' - && d->thumbnailMagic[1] == 'a' - && d->thumbnailMagic[2] == 't' - && d->thumbnailMagic[3] == 'm')) - { - clear(); - } + createChannels (numThumbnailSamples); - numSamplesCached = 0; - cacheNeedsRefilling = true; + for (int i = 0; i < numThumbnailSamples; ++i) + for (int chan = 0; chan < numChannels; ++chan) + channels.getUnchecked(chan)->getData(i)->read (input); } void AudioThumbnail::saveTo (OutputStream& output) const { - const ScopedLock sl (readerLock); + const ScopedLock sl (lock); - DataFormat* const d = getData(); - d->flipEndiannessIfBigEndian(); - output.write (d, (int) data.getSize()); - d->flipEndiannessIfBigEndian(); + const int numThumbnailSamples = channels.size() == 0 ? 0 : channels.getUnchecked(0)->getSize(); + + output.write ("jatm", 4); + output.writeInt (samplesPerThumbSample); + output.writeInt64 (totalSamples); + output.writeInt64 (numSamplesFinished); + output.writeInt (numThumbnailSamples); + output.writeInt (numChannels); + output.writeInt ((int) sampleRate); + output.writeInt64 (0); + output.writeInt64 (0); + + for (int i = 0; i < numThumbnailSamples; ++i) + for (int chan = 0; chan < numChannels; ++chan) + channels.getUnchecked(chan)->getData(i)->write (output); } -bool AudioThumbnail::initialiseFromAudioFile (AudioFormatReader& fileReader) +//============================================================================== +void AudioThumbnail::setDataSource (LevelDataSource* newSource) { - DataFormat* d = getData(); + numSamplesFinished = 0; - d->totalSamples = fileReader.lengthInSamples; - d->numChannels = jmin ((uint32) 2, fileReader.numChannels); - d->numFinishedSamples = 0; - d->sampleRate = roundToInt (fileReader.sampleRate); - d->numThumbnailSamples = (int) (d->totalSamples / d->samplesPerThumbSample) + 1; - - data.setSize (sizeof (DataFormat) + 3 + d->numThumbnailSamples * d->numChannels * 2); - - d = getData(); - zeromem (d->data, d->numThumbnailSamples * d->numChannels * 2); - - return d->totalSamples > 0; -} - -bool AudioThumbnail::readNextBlockFromAudioFile (AudioFormatReader& fileReader) -{ - DataFormat* const d = getData(); - - if (d->numFinishedSamples < d->totalSamples) + if (cache.loadThumb (*this, newSource->hashCode) && isFullyLoaded()) { - const int numToDo = (int) jmin ((int64) 65536, d->totalSamples - d->numFinishedSamples); + source = newSource; // (make sure this isn't done before loadThumb is called) - generateSection (fileReader, - d->numFinishedSamples, - numToDo); - - d->numFinishedSamples += numToDo; + source->lengthInSamples = totalSamples; + source->sampleRate = sampleRate; + source->numChannels = numChannels; + source->numSamplesFinished = numSamplesFinished; } + else + { + source = newSource; // (make sure this isn't done before loadThumb is called) - cacheNeedsRefilling = true; - return d->numFinishedSamples < d->totalSamples; + const ScopedLock sl (lock); + source->initialise (numSamplesFinished); + + totalSamples = source->lengthInSamples; + sampleRate = source->sampleRate; + numChannels = source->numChannels; + + createChannels (1 + (int) (totalSamples / samplesPerThumbSample)); + } } +void AudioThumbnail::setSource (InputSource* const newSource) +{ + clear(); + + if (newSource != 0) + setDataSource (new LevelDataSource (*this, newSource)); +} + +void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash) +{ + clear(); + + if (newReader != 0) + setDataSource (new LevelDataSource (*this, newReader, hash)); +} + +void AudioThumbnail::addBlock (const int64 startSample, const AudioSampleBuffer& incoming, + int startOffsetInBuffer, int numSamples) +{ + jassert (startSample >= 0); + + const int firstThumbIndex = (int) (startSample / samplesPerThumbSample); + const int lastThumbIndex = (int) ((startSample + numSamples + (samplesPerThumbSample - 1)) / samplesPerThumbSample); + const int numToDo = lastThumbIndex - firstThumbIndex; + + if (numToDo > 0) + { + const int numChans = jmin (channels.size(), incoming.getNumChannels()); + + const HeapBlock thumbData (numToDo * numChans); + const HeapBlock thumbChannels (numChans); + + for (int chan = 0; chan < numChans; ++chan) + { + const float* const source = incoming.getSampleData (chan, startOffsetInBuffer); + MinMaxValue* const dest = thumbData + numToDo * chan; + thumbChannels [chan] = dest; + + for (int i = 0; i < numToDo; ++i) + { + float low, high; + const int start = i * samplesPerThumbSample; + findMinAndMax (source + start, jmin (samplesPerThumbSample, numSamples - start), low, high); + dest[i].setFloat (low, high); + } + } + + setLevels (thumbChannels, firstThumbIndex, numChans, numToDo); + } +} + +void AudioThumbnail::setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues) +{ + const ScopedLock sl (lock); + + for (int i = jmin (numChans, channels.size()); --i >= 0;) + channels.getUnchecked(i)->write (values[i], thumbIndex, numValues); + + numSamplesFinished = jmax (numSamplesFinished, (thumbIndex + numValues) * (int64) samplesPerThumbSample); + totalSamples = jmax (numSamplesFinished, totalSamples); + window->invalidate(); + sendChangeMessage(); +} + +//============================================================================== int AudioThumbnail::getNumChannels() const throw() { - return getData()->numChannels; + return numChannels; } double AudioThumbnail::getTotalLength() const throw() { - const DataFormat* const d = getData(); - - if (d->sampleRate > 0) - return d->totalSamples / (double) d->sampleRate; - else - return 0.0; -} - -void AudioThumbnail::generateSection (AudioFormatReader& fileReader, - int64 startSample, - int numSamples) -{ - DataFormat* const d = getData(); - - const int firstDataPos = (int) (startSample / d->samplesPerThumbSample); - const int lastDataPos = (int) ((startSample + numSamples) / d->samplesPerThumbSample); - - char* const l = getChannelData (0); - char* const r = getChannelData (1); - - for (int i = firstDataPos; i < lastDataPos; ++i) - { - const int sourceStart = i * d->samplesPerThumbSample; - const int sourceEnd = sourceStart + d->samplesPerThumbSample; - - float lowestLeft, highestLeft, lowestRight, highestRight; - - fileReader.readMaxLevels (sourceStart, - sourceEnd - sourceStart, - lowestLeft, - highestLeft, - lowestRight, - highestRight); - - int n = i * 2; - - if (r != 0) - { - l [n] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f); - r [n++] = (char) jlimit (-128.0f, 127.0f, lowestRight * 127.0f); - l [n] = (char) jlimit (-128.0f, 127.0f, highestLeft * 127.0f); - r [n++] = (char) jlimit (-128.0f, 127.0f, highestRight * 127.0f); - } - else - { - l [n++] = (char) jlimit (-128.0f, 127.0f, lowestLeft * 127.0f); - l [n++] = (char) jlimit (-128.0f, 127.0f, highestLeft * 127.0f); - } - } -} - -char* AudioThumbnail::getChannelData (int channel) const -{ - DataFormat* const d = getData(); - - if (channel >= 0 && channel < d->numChannels) - return d->data + (channel * 2 * d->numThumbnailSamples); - - return 0; + return totalSamples / sampleRate; } bool AudioThumbnail::isFullyLoaded() const throw() { - const DataFormat* const d = getData(); - return d->numFinishedSamples >= d->totalSamples; + return numSamplesFinished >= totalSamples; } -void AudioThumbnail::refillCache (const int numSamples, - double startTime, - const double timePerPixel) +void AudioThumbnail::drawChannel (Graphics& g, const Rectangle& area, double startTime, + double endTime, int channelNum, float verticalZoomFactor) { - const DataFormat* const d = getData(); + const ScopedLock sl (lock); - if (numSamples <= 0 - || timePerPixel <= 0.0 - || d->sampleRate <= 0) - { - numSamplesCached = 0; - cacheNeedsRefilling = true; - return; - } - - if (numSamples == numSamplesCached - && numChannelsCached == d->numChannels - && startTime == cachedStart - && timePerPixel == cachedTimePerPixel - && ! cacheNeedsRefilling) - { - return; - } - - numSamplesCached = numSamples; - numChannelsCached = d->numChannels; - cachedStart = startTime; - cachedTimePerPixel = timePerPixel; - - cachedLevels.ensureSize (2 * numChannelsCached * numSamples); - - const bool needExtraDetail = (timePerPixel * d->sampleRate <= d->samplesPerThumbSample); - - const ScopedLock sl (readerLock); - - cacheNeedsRefilling = false; - - if (needExtraDetail && reader == 0) - reader = createReader(); - - if (reader != 0 && timePerPixel * d->sampleRate <= d->samplesPerThumbSample) - { - startTimer (timeBeforeDeletingReader); - - char* cacheData = static_cast (cachedLevels.getData()); - int sample = roundToInt (startTime * d->sampleRate); - - for (int i = numSamples; --i >= 0;) - { - const int nextSample = roundToInt ((startTime + timePerPixel) * d->sampleRate); - - if (sample >= 0) - { - if (sample >= reader->lengthInSamples) - break; - - float lmin, lmax, rmin, rmax; - - reader->readMaxLevels (sample, - jmax (1, nextSample - sample), - lmin, lmax, rmin, rmax); - - cacheData[0] = (char) jlimit (-128, 127, roundFloatToInt (lmin * 127.0f)); - cacheData[1] = (char) jlimit (-128, 127, roundFloatToInt (lmax * 127.0f)); - - if (numChannelsCached > 1) - { - cacheData[2] = (char) jlimit (-128, 127, roundFloatToInt (rmin * 127.0f)); - cacheData[3] = (char) jlimit (-128, 127, roundFloatToInt (rmax * 127.0f)); - } - - cacheData += 2 * numChannelsCached; - } - - startTime += timePerPixel; - sample = nextSample; - } - } - else - { - for (int channelNum = 0; channelNum < numChannelsCached; ++channelNum) - { - char* const channelData = getChannelData (channelNum); - char* cacheData = static_cast (cachedLevels.getData()) + channelNum * 2; - - const double timeToThumbSampleFactor = d->sampleRate / (double) d->samplesPerThumbSample; - - startTime = cachedStart; - int sample = roundToInt (startTime * timeToThumbSampleFactor); - const int numFinished = (int) (d->numFinishedSamples / d->samplesPerThumbSample); - - for (int i = numSamples; --i >= 0;) - { - const int nextSample = roundToInt ((startTime + timePerPixel) * timeToThumbSampleFactor); - - if (sample >= 0 && channelData != 0) - { - char mx = -128; - char mn = 127; - - while (sample <= nextSample) - { - if (sample >= numFinished) - break; - - const int n = sample << 1; - const char sampMin = channelData [n]; - const char sampMax = channelData [n + 1]; - - if (sampMin < mn) - mn = sampMin; - - if (sampMax > mx) - mx = sampMax; - - ++sample; - } - - if (mn <= mx) - { - cacheData[0] = mn; - cacheData[1] = mx; - } - else - { - cacheData[0] = 1; - cacheData[1] = 0; - } - } - else - { - cacheData[0] = 1; - cacheData[1] = 0; - } - - cacheData += numChannelsCached * 2; - startTime += timePerPixel; - sample = nextSample; - } - } - } + window->drawChannel (g, area, startTime, endTime, channelNum, verticalZoomFactor, + sampleRate, numChannels, samplesPerThumbSample, source, channels); } -void AudioThumbnail::drawChannel (Graphics& g, - int x, int y, int w, int h, - double startTime, - double endTime, - int channelNum, - const float verticalZoomFactor) +void AudioThumbnail::drawChannels (Graphics& g, const Rectangle& area, double startTimeSeconds, + double endTimeSeconds, float verticalZoomFactor) { - refillCache (w, startTime, (endTime - startTime) / w); - - if (numSamplesCached >= w - && channelNum >= 0 - && channelNum < numChannelsCached) + for (int i = 0; i < numChannels; ++i) { - const float topY = (float) y; - const float bottomY = topY + h; - const float midY = topY + h * 0.5f; - const float vscale = verticalZoomFactor * h / 256.0f; + const int y1 = roundToInt ((i * area.getHeight()) / numChannels); + const int y2 = roundToInt (((i + 1) * area.getHeight()) / numChannels); - const Rectangle clip (g.getClipBounds()); - const int skipLeft = jlimit (0, w, clip.getX() - x); - w -= skipLeft; - x += skipLeft; - - const char* cacheData = static_cast (cachedLevels.getData()) - + (channelNum << 1) - + skipLeft * (numChannelsCached << 1); - - while (--w >= 0) - { - const char mn = cacheData[0]; - const char mx = cacheData[1]; - cacheData += numChannelsCached << 1; - - if (mn <= mx) // if the wrong way round, signifies that the sample's not yet known - g.drawVerticalLine (x, jmax (midY - mx * vscale - 0.3f, topY), - jmin (midY - mn * vscale + 0.3f, bottomY)); - - if (++x >= clip.getRight()) - break; - } + drawChannel (g, Rectangle (area.getX(), area.getY() + y1, area.getWidth(), y2 - y1), + startTimeSeconds, endTimeSeconds, i, verticalZoomFactor); } } -//============================================================================== -void AudioThumbnail::DataFormat::flipEndiannessIfBigEndian() throw() -{ - #if JUCE_BIG_ENDIAN - struct Flipper - { - static void flip (int32& n) { n = (int32) ByteOrder::swap ((uint32) n); } - static void flip (int64& n) { n = (int64) ByteOrder::swap ((uint64) n); } - }; - - Flipper::flip (samplesPerThumbSample); - Flipper::flip (totalSamples); - Flipper::flip (numFinishedSamples); - Flipper::flip (numThumbnailSamples); - Flipper::flip (numChannels); - Flipper::flip (sampleRate); - #endif -} - END_JUCE_NAMESPACE diff --git a/src/audio/audio_file_formats/juce_AudioThumbnail.h b/src/audio/audio_file_formats/juce_AudioThumbnail.h index 8d288a0ef3..5e3aa1e8cf 100644 --- a/src/audio/audio_file_formats/juce_AudioThumbnail.h +++ b/src/audio/audio_file_formats/juce_AudioThumbnail.h @@ -57,9 +57,7 @@ class AudioThumbnailCache; @see AudioThumbnailCache */ -class JUCE_API AudioThumbnail : public ChangeBroadcaster, - public TimeSliceClient, - private Timer +class JUCE_API AudioThumbnail : public ChangeBroadcaster { public: //============================================================================== @@ -82,6 +80,9 @@ public: ~AudioThumbnail(); //============================================================================== + /** Clears and resets the thumbnail. */ + void clear(); + /** Specifies the file or stream that contains the audio file. For a file, just call @@ -96,30 +97,48 @@ public: */ void setSource (InputSource* newSource); + /** Gives the thumbnail an AudioFormatReader to use directly. + This will start parsing the audio in a background thread (unless the hash code + can be looked-up successfully in the thumbnail cache). + */ + void setReader (AudioFormatReader* newReader, int64 hashCode); + + /** Resets the thumbnail, ready for adding data with the specified format. + If you're going to generate a thumbnail yourself, call this before using addBlock() + to add the data. + */ + void reset (int numChannels, double sampleRate); + + /** Adds a block of level data to the thumbnail. + Call reset() before using this, to tell the thumbnail about the data format. + */ + void addBlock (int64 sampleNumberInSource, const AudioSampleBuffer& newData, + int startOffsetInBuffer, int numSamples); + + //============================================================================== /** Reloads the low res thumbnail data from an input stream. - The thumb will automatically attempt to reload itself from its - AudioThumbnailCache. + This is not an audio file stream! It takes a stream of thumbnail data that would + previously have been created by the saveTo() method. + @see saveTo */ void loadFrom (InputStream& input); /** Saves the low res thumbnail data to an output stream. - The thumb will automatically attempt to save itself to its - AudioThumbnailCache after it finishes scanning the wave file. + The data that is written can later be reloaded using loadFrom(). + @see loadFrom */ void saveTo (OutputStream& output) const; //============================================================================== - /** Returns the number of channels in the file. - */ + /** Returns the number of channels in the file. */ int getNumChannels() const throw(); - /** Returns the length of the audio file, in seconds. - */ + /** Returns the length of the audio file, in seconds. */ double getTotalLength() const throw(); - /** Renders the waveform shape for a channel. + /** Draws the waveform for a channel. The waveform will be drawn within the specified rectangle, where startTime and endTime specify the times within the audio file that should be positioned @@ -130,68 +149,61 @@ public: with the verticalZoomFactor parameter. */ void drawChannel (Graphics& g, - int x, int y, int w, int h, + const Rectangle& area, double startTimeSeconds, double endTimeSeconds, int channelNum, float verticalZoomFactor); + /** Draws the waveforms for all channels in the thumbnail. + + This will call drawChannel() to render each of the thumbnail's channels, stacked + above each other within the specified area. + + @see drawChannel + */ + void drawChannels (Graphics& g, + const Rectangle& area, + double startTimeSeconds, + double endTimeSeconds, + float verticalZoomFactor); + /** Returns true if the low res preview is fully generated. */ bool isFullyLoaded() const throw(); - //============================================================================== - /** The binary data format that is stored in the thumbnail file. */ - struct DataFormat - { - char thumbnailMagic[4]; /**< Should be 'jatm'. */ - int32 samplesPerThumbSample; /**< Number of source samples per thumbnail sample. */ - int64 totalSamples; /**< Total number of source samples. */ - int64 numFinishedSamples; /**< Number of valid source samples that have been read into the thumbnail. */ - int32 numThumbnailSamples; /**< Number of samples in the thumbnail data. */ - int32 numChannels; /**< Number of audio channels. */ - int32 sampleRate; /**< Source sample rate. */ - char future[16]; /**< Reserved for future use. */ - char data[1]; /**< The raw thumbnail samples, two signed bytes per - sample (min and max values). Channels are interleaved. */ - /** Flips the endianness of the values in this data structure if the CPU is big-endian. */ - void flipEndiannessIfBigEndian() throw(); - }; - - //============================================================================== - /** @internal */ - bool useTimeSlice(); - /** @internal */ - void timerCallback(); + // (this is only public to avoid a VC6 bug) + class LevelDataSource; private: //============================================================================== AudioFormatManager& formatManagerToUse; AudioThumbnailCache& cache; - ScopedPointer source; - CriticalSection readerLock; - ScopedPointer reader; - MemoryBlock data, cachedLevels; - int orginalSamplesPerThumbnailSample; - int numChannelsCached, numSamplesCached; - double cachedStart, cachedTimePerPixel; - bool cacheNeedsRefilling; - const int timeBeforeDeletingReader; - friend class AudioThumbnailCache; + struct MinMaxValue; + class ThumbData; + class CachedWindow; - void clear(); - AudioFormatReader* createReader() const; - void generateSection (AudioFormatReader& reader, int64 startSample, int numSamples); - char* getChannelData (int channel) const; - void refillCache (int numSamples, double startTime, double timePerPixel); - DataFormat* getData() const throw(); + friend class LevelDataSource; + friend class ScopedPointer; + friend class ThumbData; + friend class OwnedArray; + friend class CachedWindow; + friend class ScopedPointer; - // true if it needs more callbacks from the readNextBlockFromAudioFile() method - bool initialiseFromAudioFile (AudioFormatReader& reader); + ScopedPointer source; + ScopedPointer window; + OwnedArray channels; - // returns true if more needs to be read - bool readNextBlockFromAudioFile (AudioFormatReader& reader); + int32 samplesPerThumbSample; + int64 totalSamples, numSamplesFinished; + int32 numChannels; + double sampleRate; + CriticalSection lock; + + void setDataSource (LevelDataSource* newSource); + void setLevels (const MinMaxValue* const* values, int thumbIndex, int numChans, int numValues); + void createChannels (int length); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnail); }; diff --git a/src/audio/audio_file_formats/juce_AudioThumbnailCache.cpp b/src/audio/audio_file_formats/juce_AudioThumbnailCache.cpp index 040bceb6b7..7258fd9681 100644 --- a/src/audio/audio_file_formats/juce_AudioThumbnailCache.cpp +++ b/src/audio/audio_file_formats/juce_AudioThumbnailCache.cpp @@ -54,18 +54,26 @@ AudioThumbnailCache::~AudioThumbnailCache() { } -bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) +ThumbnailCacheEntry* AudioThumbnailCache::findThumbFor (const int64 hash) const { for (int i = thumbs.size(); --i >= 0;) - { - if (thumbs[i]->hash == hashCode) - { - MemoryInputStream in (thumbs[i]->data, false); - thumb.loadFrom (in); + if (thumbs.getUnchecked(i)->hash == hash) + return thumbs.getUnchecked(i); - thumbs[i]->lastUsed = Time::getMillisecondCounter(); - return true; - } + return 0; +} + +bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode) +{ + ThumbnailCacheEntry* te = findThumbFor (hashCode); + + if (te != 0) + { + te->lastUsed = Time::getMillisecondCounter(); + + MemoryInputStream in (te->data, false); + thumb.loadFrom (in); + return true; } return false; @@ -74,19 +82,7 @@ bool AudioThumbnailCache::loadThumb (AudioThumbnail& thumb, const int64 hashCode void AudioThumbnailCache::storeThumb (const AudioThumbnail& thumb, const int64 hashCode) { - MemoryOutputStream out; - thumb.saveTo (out); - - ThumbnailCacheEntry* te = 0; - - for (int i = thumbs.size(); --i >= 0;) - { - if (thumbs[i]->hash == hashCode) - { - te = thumbs[i]; - break; - } - } + ThumbnailCacheEntry* te = findThumbFor (hashCode); if (te == 0) { @@ -100,20 +96,25 @@ void AudioThumbnailCache::storeThumb (const AudioThumbnail& thumb, else { int oldest = 0; - unsigned int oldestTime = Time::getMillisecondCounter() + 1; + uint32 oldestTime = Time::getMillisecondCounter() + 1; - int i; - for (i = thumbs.size(); --i >= 0;) - if (thumbs[i]->lastUsed < oldestTime) + for (int i = thumbs.size(); --i >= 0;) + { + if (thumbs.getUnchecked(i)->lastUsed < oldestTime) + { oldest = i; + oldestTime = thumbs.getUnchecked(i)->lastUsed; + } + } - thumbs.set (i, te); + thumbs.set (oldest, te); } } te->lastUsed = Time::getMillisecondCounter(); - te->data.setSize (0); - te->data.append (out.getData(), out.getDataSize()); + + MemoryOutputStream out (te->data, false); + thumb.saveTo (out); } void AudioThumbnailCache::clear() @@ -121,16 +122,5 @@ void AudioThumbnailCache::clear() thumbs.clear(); } -//============================================================================== -void AudioThumbnailCache::addThumbnail (AudioThumbnail* const thumb) -{ - addTimeSliceClient (thumb); -} - -void AudioThumbnailCache::removeThumbnail (AudioThumbnail* const thumb) -{ - removeTimeSliceClient (thumb); -} - END_JUCE_NAMESPACE diff --git a/src/audio/audio_file_formats/juce_AudioThumbnailCache.h b/src/audio/audio_file_formats/juce_AudioThumbnailCache.h index 0519475fef..401cf419dd 100644 --- a/src/audio/audio_file_formats/juce_AudioThumbnailCache.h +++ b/src/audio/audio_file_formats/juce_AudioThumbnailCache.h @@ -80,9 +80,7 @@ private: OwnedArray thumbs; int maxNumThumbsToStore; - friend class AudioThumbnail; - void addThumbnail (AudioThumbnail* thumb); - void removeThumbnail (AudioThumbnail* thumb); + ThumbnailCacheEntry* findThumbFor (int64 hash) const; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioThumbnailCache); }; diff --git a/src/audio/dsp/juce_AudioSampleBuffer.cpp b/src/audio/dsp/juce_AudioSampleBuffer.cpp index 6f17a6707f..9815af2e52 100644 --- a/src/audio/dsp/juce_AudioSampleBuffer.cpp +++ b/src/audio/dsp/juce_AudioSampleBuffer.cpp @@ -222,7 +222,7 @@ void AudioSampleBuffer::clear (const int channel, const int startSample, const int numSamples) throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); zeromem (channels [channel] + startSample, numSamples * sizeof (float)); @@ -233,7 +233,7 @@ void AudioSampleBuffer::applyGain (const int channel, int numSamples, const float gain) throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); if (gain != 1.0f) @@ -264,7 +264,7 @@ void AudioSampleBuffer::applyGainRamp (const int channel, } else { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); const float increment = (endGain - startGain) / numSamples; @@ -295,9 +295,9 @@ void AudioSampleBuffer::addFrom (const int destChannel, const float gain) throw() { jassert (&source != this || sourceChannel != destChannel); - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (((unsigned int) sourceChannel) < (unsigned int) source.numChannels); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (gain != 0.0f && numSamples > 0) @@ -324,7 +324,7 @@ void AudioSampleBuffer::addFrom (const int destChannel, int numSamples, const float gain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -352,7 +352,7 @@ void AudioSampleBuffer::addFromWithRamp (const int destChannel, float startGain, const float endGain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -388,9 +388,9 @@ void AudioSampleBuffer::copyFrom (const int destChannel, int numSamples) throw() { jassert (&source != this || sourceChannel != destChannel); - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (((unsigned int) sourceChannel) < (unsigned int) source.numChannels); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); if (numSamples > 0) @@ -406,7 +406,7 @@ void AudioSampleBuffer::copyFrom (const int destChannel, const float* source, int numSamples) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -424,7 +424,7 @@ void AudioSampleBuffer::copyFrom (const int destChannel, int numSamples, const float gain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -458,7 +458,7 @@ void AudioSampleBuffer::copyFromWithRamp (const int destChannel, float startGain, float endGain) throw() { - jassert (((unsigned int) destChannel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (destChannel, numChannels)); jassert (destStartSample >= 0 && destStartSample + numSamples <= size); jassert (source != 0); @@ -492,42 +492,17 @@ void AudioSampleBuffer::findMinMax (const int channel, float& minVal, float& maxVal) const throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); - if (numSamples <= 0) - { - minVal = 0.0f; - maxVal = 0.0f; - } - else - { - const float* d = channels [channel] + startSample; - - float mn = *d++; - float mx = mn; - - while (--numSamples > 0) // (> 0 rather than >= 0 because we've already taken the first sample) - { - const float samp = *d++; - - if (samp > mx) - mx = samp; - - if (samp < mn) - mn = samp; - } - - maxVal = mx; - minVal = mn; - } + findMinAndMax (channels [channel] + startSample, numSamples, minVal, maxVal); } float AudioSampleBuffer::getMagnitude (const int channel, const int startSample, const int numSamples) const throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); float mn, mx; @@ -551,7 +526,7 @@ float AudioSampleBuffer::getRMSLevel (const int channel, const int startSample, const int numSamples) const throw() { - jassert (((unsigned int) channel) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && startSample + numSamples <= size); if (numSamples <= 0 || channel < 0 || channel >= numChannels) diff --git a/src/audio/dsp/juce_AudioSampleBuffer.h b/src/audio/dsp/juce_AudioSampleBuffer.h index 22f109239d..5885f67338 100644 --- a/src/audio/dsp/juce_AudioSampleBuffer.h +++ b/src/audio/dsp/juce_AudioSampleBuffer.h @@ -110,7 +110,7 @@ public: */ float* getSampleData (const int channelNumber) const throw() { - jassert (((unsigned int) channelNumber) < (unsigned int) numChannels); + jassert (isPositiveAndBelow (channelNumber, numChannels)); return channels [channelNumber]; } @@ -122,8 +122,8 @@ public: float* getSampleData (const int channelNumber, const int sampleOffset) const throw() { - jassert (((unsigned int) channelNumber) < (unsigned int) numChannels); - jassert (((unsigned int) sampleOffset) < (unsigned int) size); + jassert (isPositiveAndBelow (channelNumber, numChannels)); + jassert (isPositiveAndBelow (sampleOffset, size)); return channels [channelNumber] + sampleOffset; } diff --git a/src/audio/midi/juce_MidiKeyboardState.cpp b/src/audio/midi/juce_MidiKeyboardState.cpp index d663e6d8a7..71c5356cab 100644 --- a/src/audio/midi/juce_MidiKeyboardState.cpp +++ b/src/audio/midi/juce_MidiKeyboardState.cpp @@ -53,24 +53,24 @@ bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const thro { jassert (midiChannel >= 0 && midiChannel <= 16); - return ((unsigned int) n) < 128 + return isPositiveAndBelow (n, (int) 128) && (noteStates[n] & (1 << (midiChannel - 1))) != 0; } bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const throw() { - return ((unsigned int) n) < 128 + return isPositiveAndBelow (n, (int) 128) && (noteStates[n] & midiChannelMask) != 0; } void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { jassert (midiChannel >= 0 && midiChannel <= 16); - jassert (((unsigned int) midiNoteNumber) < 128); + jassert (isPositiveAndBelow (midiNoteNumber, (int) 128)); const ScopedLock sl (lock); - if (((unsigned int) midiNoteNumber) < 128) + if (isPositiveAndBelow (midiNoteNumber, (int) 128)) { const int timeNow = (int) Time::getMillisecondCounter(); eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); @@ -82,7 +82,7 @@ void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) { - if (((unsigned int) midiNoteNumber) < 128) + if (isPositiveAndBelow (midiNoteNumber, (int) 128)) { noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); diff --git a/src/audio/midi/juce_MidiMessage.cpp b/src/audio/midi/juce_MidiMessage.cpp index 437d527cd3..55df492b3c 100644 --- a/src/audio/midi/juce_MidiMessage.cpp +++ b/src/audio/midi/juce_MidiMessage.cpp @@ -372,8 +372,8 @@ const MidiMessage MidiMessage::aftertouchChange (const int channel, const int aftertouchValue) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (((unsigned int) noteNum) <= 127); - jassert (((unsigned int) aftertouchValue) <= 127); + jassert (isPositiveAndBelow (noteNum, (int) 128)); + jassert (isPositiveAndBelow (aftertouchValue, (int) 128)); return MidiMessage (0xa0 | jlimit (0, 15, channel - 1), noteNum & 0x7f, @@ -396,7 +396,7 @@ const MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (((unsigned int) pressure) <= 127); + jassert (isPositiveAndBelow (pressure, (int) 128)); return MidiMessage (0xd0 | jlimit (0, 15, channel - 1), pressure & 0x7f); @@ -435,7 +435,7 @@ const MidiMessage MidiMessage::pitchWheel (const int channel, const int position) throw() { jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (((unsigned int) position) <= 0x3fff); + jassert (isPositiveAndBelow (position, (int) 0x4000)); return MidiMessage (0xe0 | jlimit (0, 15, channel - 1), position & 127, @@ -485,7 +485,7 @@ const MidiMessage MidiMessage::noteOn (const int channel, const uint8 velocity) throw() { jassert (channel > 0 && channel <= 16); - jassert (((unsigned int) noteNumber) <= 127); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (0x90 | jlimit (0, 15, channel - 1), noteNumber & 127, @@ -496,7 +496,7 @@ const MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) throw() { jassert (channel > 0 && channel <= 16); - jassert (((unsigned int) noteNumber) <= 127); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); return MidiMessage (0x80 | jlimit (0, 15, channel - 1), noteNumber & 127, 0); } @@ -986,7 +986,7 @@ const String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includ static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; - if (((unsigned int) note) < 128) + if (isPositiveAndBelow (note, (int) 128)) { String s (useSharps ? sharpNoteNames [note % 12] : flatNoteNames [note % 12]); @@ -1035,7 +1035,7 @@ const String MidiMessage::getGMInstrumentName (const int n) "Applause", "Gunshot" }; - return (((unsigned int) n) < 128) ? names[n] : (const char*) 0; + return isPositiveAndBelow (n, (int) 128) ? names[n] : (const char*) 0; } const String MidiMessage::getGMInstrumentBankName (const int n) @@ -1048,7 +1048,7 @@ const String MidiMessage::getGMInstrumentBankName (const int n) "Synth Effects", "Ethnic", "Percussive", "Sound Effects" }; - return (((unsigned int) n) <= 15) ? names[n] : (const char*) 0; + return isPositiveAndBelow (n, (int) 16) ? names[n] : (const char*) 0; } const String MidiMessage::getRhythmInstrumentName (const int n) @@ -1097,7 +1097,7 @@ const String MidiMessage::getControllerName (const int n) "Poly Operation" }; - return (((unsigned int) n) < 128) ? names[n] : (const char*) 0; + return isPositiveAndBelow (n, (int) 128) ? names[n] : (const char*) 0; } END_JUCE_NAMESPACE diff --git a/src/audio/midi/juce_MidiMessageSequence.cpp b/src/audio/midi/juce_MidiMessageSequence.cpp index 0e77274a99..47aea09ded 100644 --- a/src/audio/midi/juce_MidiMessageSequence.cpp +++ b/src/audio/midi/juce_MidiMessageSequence.cpp @@ -128,7 +128,7 @@ double MidiMessageSequence::getEndTime() const double MidiMessageSequence::getEventTime (const int index) const { - if (((unsigned int) index) < (unsigned int) list.size()) + if (isPositiveAndBelow (index, list.size())) return list.getUnchecked (index)->message.getTimeStamp(); return 0.0; @@ -154,7 +154,7 @@ void MidiMessageSequence::addEvent (const MidiMessage& newMessage, void MidiMessageSequence::deleteEvent (const int index, const bool deleteMatchingNoteUp) { - if (((unsigned int) index) < (unsigned int) list.size()) + if (isPositiveAndBelow (index, list.size())) { if (deleteMatchingNoteUp) deleteEvent (getIndexOfMatchingKeyUp (index), false); diff --git a/src/audio/plugins/formats/juce_AudioUnitPluginFormat.mm b/src/audio/plugins/formats/juce_AudioUnitPluginFormat.mm index 6e8853631f..976d850da6 100644 --- a/src/audio/plugins/formats/juce_AudioUnitPluginFormat.mm +++ b/src/audio/plugins/formats/juce_AudioUnitPluginFormat.mm @@ -1202,7 +1202,7 @@ float AudioUnitPluginInstance::getParameter (int index) Float32 value = 0.0f; - if (audioUnit != 0 && ((unsigned int) index) < (unsigned int) parameterIds.size()) + if (audioUnit != 0 && isPositiveAndBelow (index, parameterIds.size())) { AudioUnitGetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), @@ -1217,7 +1217,7 @@ void AudioUnitPluginInstance::setParameter (int index, float newValue) { const ScopedLock sl (lock); - if (audioUnit != 0 && ((unsigned int) index) < (unsigned int) parameterIds.size()) + if (audioUnit != 0 && isPositiveAndBelow (index, parameterIds.size())) { AudioUnitSetParameter (audioUnit, (UInt32) parameterIds.getUnchecked (index), @@ -1350,7 +1350,7 @@ void AudioUnitPluginInstance::changeProgramName (int index, const String& newNam //============================================================================== const String AudioUnitPluginInstance::getInputChannelName (int index) const { - if (((unsigned int) index) < (unsigned int) getNumInputChannels()) + if (isPositiveAndBelow (index, getNumInputChannels())) return "Input " + String (index + 1); return String::empty; @@ -1358,7 +1358,7 @@ const String AudioUnitPluginInstance::getInputChannelName (int index) const bool AudioUnitPluginInstance::isInputChannelStereoPair (int index) const { - if (((unsigned int) index) >= (unsigned int) getNumInputChannels()) + if (! isPositiveAndBelow (index, getNumInputChannels())) return false; @@ -1367,7 +1367,7 @@ bool AudioUnitPluginInstance::isInputChannelStereoPair (int index) const const String AudioUnitPluginInstance::getOutputChannelName (int index) const { - if (((unsigned int) index) < (unsigned int) getNumOutputChannels()) + if (isPositiveAndBelow (index, getNumOutputChannels())) return "Output " + String (index + 1); return String::empty; @@ -1375,7 +1375,7 @@ const String AudioUnitPluginInstance::getOutputChannelName (int index) const bool AudioUnitPluginInstance::isOutputChannelStereoPair (int index) const { - if (((unsigned int) index) >= (unsigned int) getNumOutputChannels()) + if (! isPositiveAndBelow (index, getNumOutputChannels())) return false; return true; diff --git a/src/audio/plugins/formats/juce_VSTPluginFormat.cpp b/src/audio/plugins/formats/juce_VSTPluginFormat.cpp index 9a18f79a53..37671c2f2c 100644 --- a/src/audio/plugins/formats/juce_VSTPluginFormat.cpp +++ b/src/audio/plugins/formats/juce_VSTPluginFormat.cpp @@ -2456,7 +2456,7 @@ const String VSTPluginInstance::getCategory() const //============================================================================== float VSTPluginInstance::getParameter (int index) { - if (effect != 0 && ((unsigned int) index) < (unsigned int) effect->numParams) + if (effect != 0 && isPositiveAndBelow (index, (int) effect->numParams)) { try { @@ -2473,7 +2473,7 @@ float VSTPluginInstance::getParameter (int index) void VSTPluginInstance::setParameter (int index, float newValue) { - if (effect != 0 && ((unsigned int) index) < (unsigned int) effect->numParams) + if (effect != 0 && isPositiveAndBelow (index, (int) effect->numParams)) { try { diff --git a/src/audio/plugins/juce_KnownPluginList.cpp b/src/audio/plugins/juce_KnownPluginList.cpp index c0eb3c46e0..5d8ebb0a6c 100644 --- a/src/audio/plugins/juce_KnownPluginList.cpp +++ b/src/audio/plugins/juce_KnownPluginList.cpp @@ -450,7 +450,7 @@ int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const { const int i = menuResultCode - menuIdBase; - return (((unsigned int) i) < (unsigned int) types.size()) ? i : -1; + return isPositiveAndBelow (i, types.size()) ? i : -1; } END_JUCE_NAMESPACE diff --git a/src/audio/processors/juce_AudioProcessor.cpp b/src/audio/processors/juce_AudioProcessor.cpp index 7be1abc5ce..dd2b4863d4 100644 --- a/src/audio/processors/juce_AudioProcessor.cpp +++ b/src/audio/processors/juce_AudioProcessor.cpp @@ -110,7 +110,7 @@ void AudioProcessor::setParameterNotifyingHost (const int parameterIndex, void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex, const float newValue) { - jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); + jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); for (int i = listeners.size(); --i >= 0;) { @@ -128,7 +128,7 @@ void AudioProcessor::sendParamChangeMessageToListeners (const int parameterIndex void AudioProcessor::beginParameterChangeGesture (int parameterIndex) { - jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); + jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); #if JUCE_DEBUG // This means you've called beginParameterChangeGesture twice in succession without a matching @@ -153,7 +153,7 @@ void AudioProcessor::beginParameterChangeGesture (int parameterIndex) void AudioProcessor::endParameterChangeGesture (int parameterIndex) { - jassert (((unsigned int) parameterIndex) < (unsigned int) getNumParameters()); + jassert (isPositiveAndBelow (parameterIndex, getNumParameters())); #if JUCE_DEBUG // This means you've called endParameterChangeGesture without having previously called diff --git a/src/audio/processors/juce_AudioProcessorGraph.cpp b/src/audio/processors/juce_AudioProcessorGraph.cpp index 78824f20fd..a29855bab7 100644 --- a/src/audio/processors/juce_AudioProcessorGraph.cpp +++ b/src/audio/processors/juce_AudioProcessorGraph.cpp @@ -320,11 +320,11 @@ bool AudioProcessorGraph::removeIllegalConnections() if (source == 0 || dest == 0 || (c->sourceChannelIndex != midiChannelIndex - && (((unsigned int) c->sourceChannelIndex) >= (unsigned int) source->processor->getNumOutputChannels())) + && ! isPositiveAndBelow (c->sourceChannelIndex, source->processor->getNumOutputChannels())) || (c->sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi()) || (c->destChannelIndex != midiChannelIndex - && (((unsigned int) c->destChannelIndex) >= (unsigned int) dest->processor->getNumInputChannels())) + && ! isPositiveAndBelow (c->destChannelIndex, dest->processor->getNumInputChannels())) || (c->destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) { diff --git a/src/containers/juce_Array.h b/src/containers/juce_Array.h index b208878991..017406d03f 100644 --- a/src/containers/juce_Array.h +++ b/src/containers/juce_Array.h @@ -221,8 +221,8 @@ public: inline ElementType operator[] (const int index) const { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : ElementType(); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : ElementType(); } /** Returns one of the elements in the array, without checking the index passed in. @@ -237,7 +237,7 @@ public: inline const ElementType getUnchecked (const int index) const { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -253,7 +253,7 @@ public: inline ElementType& getReference (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -366,7 +366,7 @@ public: const ScopedLockType lock (getLock()); data.ensureAllocatedSize (numUsed + 1); - if (((unsigned int) indexToInsertAt) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToInsertAt, numUsed)) { ElementType* const insertPos = data.elements + indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; @@ -404,7 +404,7 @@ public: data.ensureAllocatedSize (numUsed + numberOfTimesToInsertIt); ElementType* insertPos; - if (((unsigned int) indexToInsertAt) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToInsertAt, numUsed)) { insertPos = data.elements + indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; @@ -444,7 +444,7 @@ public: data.ensureAllocatedSize (numUsed + numberOfElements); ElementType* insertPos; - if (((unsigned int) indexToInsertAt) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToInsertAt, numUsed)) { insertPos = data.elements + indexToInsertAt; const int numberToMove = numUsed - indexToInsertAt; @@ -492,7 +492,7 @@ public: jassert (indexToChange >= 0); const ScopedLockType lock (getLock()); - if (((unsigned int) indexToChange) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToChange, numUsed)) { data.elements [indexToChange] = newValue; } @@ -515,7 +515,7 @@ public: void setUnchecked (const int indexToChange, ParameterType newValue) { const ScopedLockType lock (getLock()); - jassert (((unsigned int) indexToChange) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (indexToChange, numUsed)); data.elements [indexToChange] = newValue; } @@ -682,7 +682,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { --numUsed; @@ -855,8 +855,8 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) index1) < (unsigned int) numUsed - && ((unsigned int) index2) < (unsigned int) numUsed) + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) { swapVariables (data.elements [index1], data.elements [index2]); @@ -883,9 +883,9 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) currentIndex) < (unsigned int) numUsed) + if (isPositiveAndBelow (currentIndex, numUsed)) { - if (((unsigned int) newIndex) >= (unsigned int) numUsed) + if (! isPositiveAndBelow (newIndex, numUsed)) newIndex = numUsed - 1; char tempCopy [sizeof (ElementType)]; diff --git a/src/containers/juce_NamedValueSet.cpp b/src/containers/juce_NamedValueSet.cpp index e12f432241..57f568a012 100644 --- a/src/containers/juce_NamedValueSet.cpp +++ b/src/containers/juce_NamedValueSet.cpp @@ -153,13 +153,13 @@ bool NamedValueSet::remove (const Identifier& name) const Identifier NamedValueSet::getName (const int index) const { - jassert (((unsigned int) index) < (unsigned int) values.size()); + jassert (isPositiveAndBelow (index, values.size())); return values [index].name; } const var NamedValueSet::getValueAt (const int index) const { - jassert (((unsigned int) index) < (unsigned int) values.size()); + jassert (isPositiveAndBelow (index, values.size())); return values [index].value; } diff --git a/src/containers/juce_OwnedArray.h b/src/containers/juce_OwnedArray.h index 27a2f5db6a..4735f3d881 100644 --- a/src/containers/juce_OwnedArray.h +++ b/src/containers/juce_OwnedArray.h @@ -109,8 +109,8 @@ public: inline ObjectClass* operator[] (const int index) const throw() { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : static_cast (0); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : static_cast (0); } /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. @@ -121,7 +121,7 @@ public: inline ObjectClass* getUnchecked (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -328,6 +328,11 @@ public: // object has a private destructor, both OwnedArray and // ScopedPointer would need to be friend classes.. } + else + { + jassertfalse; // you're trying to set an object at a negative index, which doesn't have + // any effect - but since the object is not being added, it may be leaking.. + } } /** Adds elements from another array to the end of this array. @@ -492,7 +497,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { ObjectClass** const e = data.elements + indexToRemove; @@ -529,7 +534,7 @@ public: ObjectClass* removedItem = 0; const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { ObjectClass** const e = data.elements + indexToRemove; removedItem = *e; @@ -648,8 +653,8 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) index1) < (unsigned int) numUsed - && ((unsigned int) index2) < (unsigned int) numUsed) + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) { swapVariables (data.elements [index1], data.elements [index2]); @@ -676,9 +681,9 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) currentIndex) < (unsigned int) numUsed) + if (isPositiveAndBelow (currentIndex, numUsed)) { - if (((unsigned int) newIndex) >= (unsigned int) numUsed) + if (! isPositiveAndBelow (newIndex, numUsed)) newIndex = numUsed - 1; ObjectClass* const value = data.elements [currentIndex]; diff --git a/src/containers/juce_Range.h b/src/containers/juce_Range.h index 1d13bfa1b2..e45154534b 100644 --- a/src/containers/juce_Range.h +++ b/src/containers/juce_Range.h @@ -120,7 +120,7 @@ public: /** Returns a range with the same length as this one, but moved to have the given start position. */ const Range movedToStartAt (const ValueType newStart) const throw() { - return Range (newStart, newStart + getLength()); + return Range (newStart, end + (newStart - start)); } /** Changes the end position of the range, leaving the start unchanged. @@ -146,7 +146,7 @@ public: /** Returns a range with the same length as this one, but moved to have the given start position. */ const Range movedToEndAt (const ValueType newEnd) const throw() { - return Range (newEnd - getLength(), newEnd); + return Range (start + (newEnd - end), newEnd); } /** Changes the length of the range. diff --git a/src/containers/juce_ReferenceCountedArray.h b/src/containers/juce_ReferenceCountedArray.h index b4196748aa..bbf1df12b6 100644 --- a/src/containers/juce_ReferenceCountedArray.h +++ b/src/containers/juce_ReferenceCountedArray.h @@ -129,8 +129,8 @@ public: inline const ReferenceCountedObjectPtr operator[] (const int index) const throw() { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : static_cast (0); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : static_cast (0); } /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. @@ -141,7 +141,7 @@ public: inline const ReferenceCountedObjectPtr getUnchecked (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -418,7 +418,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { ObjectClass** const e = data.elements + indexToRemove; @@ -530,8 +530,8 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) index1) < (unsigned int) numUsed - && ((unsigned int) index2) < (unsigned int) numUsed) + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) { swapVariables (data.elements [index1], data.elements [index2]); @@ -558,9 +558,9 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) currentIndex) < (unsigned int) numUsed) + if (isPositiveAndBelow (currentIndex, numUsed)) { - if (((unsigned int) newIndex) >= (unsigned int) numUsed) + if (! isPositiveAndBelow (newIndex, numUsed)) newIndex = numUsed - 1; ObjectClass* const value = data.elements [currentIndex]; diff --git a/src/containers/juce_SortedSet.h b/src/containers/juce_SortedSet.h index 4e5cf56e35..b986722f7b 100644 --- a/src/containers/juce_SortedSet.h +++ b/src/containers/juce_SortedSet.h @@ -185,8 +185,8 @@ public: inline ElementType operator[] (const int index) const throw() { const ScopedLockType lock (getLock()); - return (((unsigned int) index) < (unsigned int) numUsed) ? data.elements [index] - : ElementType(); + return isPositiveAndBelow (index, numUsed) ? data.elements [index] + : ElementType(); } /** Returns one of the elements in the set, without checking the index passed in. @@ -200,7 +200,7 @@ public: inline ElementType getUnchecked (const int index) const throw() { const ScopedLockType lock (getLock()); - jassert (((unsigned int) index) < (unsigned int) numUsed); + jassert (isPositiveAndBelow (index, numUsed)); return data.elements [index]; } @@ -412,7 +412,7 @@ public: { const ScopedLockType lock (getLock()); - if (((unsigned int) indexToRemove) < (unsigned int) numUsed) + if (isPositiveAndBelow (indexToRemove, numUsed)) { --numUsed; diff --git a/src/containers/juce_SparseSet.h b/src/containers/juce_SparseSet.h index bc7334e035..1925e62ee3 100644 --- a/src/containers/juce_SparseSet.h +++ b/src/containers/juce_SparseSet.h @@ -142,7 +142,7 @@ public: */ const Range getRange (const int rangeIndex) const { - if (((unsigned int) rangeIndex) < (unsigned int) getNumRanges()) + if (isPositiveAndBelow (rangeIndex, getNumRanges())) return Range (values.getUnchecked (rangeIndex << 1), values.getUnchecked ((rangeIndex << 1) + 1)); else diff --git a/src/containers/juce_ValueTree.cpp b/src/containers/juce_ValueTree.cpp index 8d02f7783b..30c4cdeaed 100644 --- a/src/containers/juce_ValueTree.cpp +++ b/src/containers/juce_ValueTree.cpp @@ -489,10 +489,10 @@ void ValueTree::SharedObject::removeAllChildren (UndoManager* const undoManager) void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { // The source index must be a valid index! - jassert (((unsigned int) currentIndex) < (unsigned int) children.size()); + jassert (isPositiveAndBelow (currentIndex, children.size())); if (currentIndex != newIndex - && ((unsigned int) currentIndex) < (unsigned int) children.size()) + && isPositiveAndBelow (currentIndex, children.size())) { if (undoManager == 0) { @@ -501,7 +501,7 @@ void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoMan } else { - if (((unsigned int) newIndex) >= (unsigned int) children.size()) + if (! isPositiveAndBelow (newIndex, children.size())) newIndex = children.size() - 1; undoManager->perform (new MoveChildAction (this, currentIndex, newIndex)); diff --git a/src/core/juce_MathsFunctions.h b/src/core/juce_MathsFunctions.h index 394d46d467..5c7c2218db 100644 --- a/src/core/juce_MathsFunctions.h +++ b/src/core/juce_MathsFunctions.h @@ -122,6 +122,69 @@ inline Type jmin (const Type a, const Type b, const Type c) template inline Type jmin (const Type a, const Type b, const Type c, const Type d) { return jmin (a, jmin (b, c, d)); } +/** Scans an array of values, returning the minimum value that it contains. */ +template +const Type findMinimum (const Type* data, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*data++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *data++; + if (v < result) result = v; + } + + return result; +} + +/** Scans an array of values, returning the minimum value that it contains. */ +template +const Type findMaximum (const Type* values, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*values++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + if (result > v) result = v; + } + + return result; +} + +/** Scans an array of values, returning the minimum and maximum values that it contains. */ +template +void findMinAndMax (const Type* values, int numValues, Type& lowest, Type& highest) +{ + if (numValues <= 0) + { + lowest = Type(); + highest = Type(); + } + else + { + Type mn (*values++); + Type mx (mn); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + + if (mx < v) mx = v; + if (v < mn) mn = v; + } + + lowest = mn; + highest = mx; + } +} + //============================================================================== /** Constrains a value to keep it within a given range. @@ -152,6 +215,44 @@ inline Type jlimit (const Type lowerLimit, : valueToConstrain); } +/** Returns true if a value is at least zero, and also below a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest < upperLimit + @endcode +*/ +template +inline bool isPositiveAndBelow (Type valueToTest, Type upperLimit) throw() +{ + jassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest < upperLimit; +} + +template <> +inline bool isPositiveAndBelow (const int valueToTest, const int upperLimit) throw() +{ + jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) < static_cast (upperLimit); +} + +/** Returns true if a value is at least zero, and also less than or equal to a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest <= upperLimit + @endcode +*/ +template +inline bool isPositiveAndNotGreaterThan (Type valueToTest, Type upperLimit) throw() +{ + jassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest <= upperLimit; +} + +template <> +inline bool isPositiveAndNotGreaterThan (const int valueToTest, const int upperLimit) throw() +{ + jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) <= static_cast (upperLimit); +} + //============================================================================== /** Handy function to swap two values over. */ diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index 62ad18e892..e239d6e351 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 98 +#define JUCE_BUILDNUMBER 99 /** Current Juce version number. diff --git a/src/gui/components/code_editor/juce_CodeEditorComponent.cpp b/src/gui/components/code_editor/juce_CodeEditorComponent.cpp index 01c14ee42b..8296f43690 100644 --- a/src/gui/components/code_editor/juce_CodeEditorComponent.cpp +++ b/src/gui/components/code_editor/juce_CodeEditorComponent.cpp @@ -1187,7 +1187,7 @@ void CodeEditorComponent::setColourForTokenType (const int tokenType, const Colo const Colour CodeEditorComponent::getColourForTokenType (const int tokenType) const { - if (((unsigned int) tokenType) >= (unsigned int) coloursForTokenCategories.size()) + if (! isPositiveAndBelow (tokenType, coloursForTokenCategories.size())) return findColour (CodeEditorComponent::defaultTextColourId); return coloursForTokenCategories.getReference (tokenType); diff --git a/src/gui/components/controls/juce_ListBox.cpp b/src/gui/components/controls/juce_ListBox.cpp index 0f33fee59a..87344e12fe 100644 --- a/src/gui/components/controls/juce_ListBox.cpp +++ b/src/gui/components/controls/juce_ListBox.cpp @@ -473,7 +473,7 @@ void ListBox::selectRowInternal (const int row, if ((! isRowSelected (row)) || (deselectOthersFirst && getNumSelectedRows() > 1)) { - if (((unsigned int) row) < (unsigned int) totalItems) + if (isPositiveAndBelow (row, totalItems)) { if (deselectOthersFirst) selected.clear(); @@ -594,7 +594,7 @@ int ListBox::getNumSelectedRows() const int ListBox::getSelectedRow (const int index) const { - return (((unsigned int) index) < (unsigned int) selected.size()) + return (isPositiveAndBelow (index, selected.size())) ? selected [index] : -1; } @@ -611,11 +611,11 @@ int ListBox::getLastRowSelected() const //============================================================================== int ListBox::getRowContainingPosition (const int x, const int y) const throw() { - if (((unsigned int) x) < (unsigned int) getWidth()) + if (isPositiveAndBelow (x, getWidth())) { const int row = (viewport->getViewPositionY() + y - viewport->getY()) / rowHeight; - if (((unsigned int) row) < (unsigned int) totalItems) + if (isPositiveAndBelow (row, totalItems)) return row; } @@ -624,7 +624,7 @@ int ListBox::getRowContainingPosition (const int x, const int y) const throw() int ListBox::getInsertionIndexForPosition (const int x, const int y) const throw() { - if (((unsigned int) x) < (unsigned int) getWidth()) + if (isPositiveAndBelow (x, getWidth())) { const int row = (viewport->getViewPositionY() + y + rowHeight / 2 - viewport->getY()) / rowHeight; return jlimit (0, totalItems, row); diff --git a/src/gui/components/controls/juce_TableHeaderComponent.cpp b/src/gui/components/controls/juce_TableHeaderComponent.cpp index dfd011efdb..c8e939a8b2 100644 --- a/src/gui/components/controls/juce_TableHeaderComponent.cpp +++ b/src/gui/components/controls/juce_TableHeaderComponent.cpp @@ -204,7 +204,7 @@ void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidt { const int index = getIndexOfColumnId (columnId, true) + 1; - if (((unsigned int) index) < (unsigned int) numColumns) + if (isPositiveAndBelow (index, numColumns)) { const int x = getColumnPosition (index).getX(); @@ -879,7 +879,7 @@ void TableHeaderComponent::handleAsyncUpdate() int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const { - if (((unsigned int) mouseX) < (unsigned int) getWidth()) + if (isPositiveAndBelow (mouseX, getWidth())) { const int draggableDistance = 3; int x = 0; diff --git a/src/gui/components/controls/juce_TreeView.cpp b/src/gui/components/controls/juce_TreeView.cpp index 110f18396a..12d94f2de3 100644 --- a/src/gui/components/controls/juce_TreeView.cpp +++ b/src/gui/components/controls/juce_TreeView.cpp @@ -1201,7 +1201,7 @@ void TreeViewItem::removeSubItem (const int index, const bool deleteItem) { const ScopedLock sl (ownerView->nodeAlterationLock); - if (((unsigned int) index) < (unsigned int) subItems.size()) + if (isPositiveAndBelow (index, subItems.size())) { subItems.remove (index, deleteItem); treeHasChanged(); @@ -1597,7 +1597,7 @@ TreeViewItem* TreeViewItem::getItemOnRow (int index) throw() TreeViewItem* TreeViewItem::findItemRecursively (int targetY) throw() { - if (((unsigned int) targetY) < (unsigned int) totalHeight) + if (isPositiveAndBelow (targetY, totalHeight)) { const int h = itemHeight; diff --git a/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp b/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp index 57b2a5ea5c..d78e119153 100644 --- a/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp @@ -147,7 +147,7 @@ void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, void FileSearchPathListComponent::deleteKeyPressed (int row) { - if (((unsigned int) row) < (unsigned int) path.getNumPaths()) + if (isPositiveAndBelow (row, path.getNumPaths())) { path.remove (row); changed(); diff --git a/src/gui/components/juce_Component.cpp b/src/gui/components/juce_Component.cpp index 3c31cce1a9..c783fd04e7 100644 --- a/src/gui/components/juce_Component.cpp +++ b/src/gui/components/juce_Component.cpp @@ -216,8 +216,8 @@ public: //============================================================================== static inline bool hitTest (Component& comp, const Point& localPoint) { - return ((unsigned int) localPoint.getX()) < (unsigned int) comp.getWidth() - && ((unsigned int) localPoint.getY()) < (unsigned int) comp.getHeight() + return isPositiveAndBelow (localPoint.getX(), comp.getWidth()) + && isPositiveAndBelow (localPoint.getY(), comp.getHeight()) && comp.hitTest (localPoint.getX(), localPoint.getY()); } diff --git a/src/gui/components/layout/juce_ScrollBar.cpp b/src/gui/components/layout/juce_ScrollBar.cpp index 273d86c0bd..4ec7ec152e 100644 --- a/src/gui/components/layout/juce_ScrollBar.cpp +++ b/src/gui/components/layout/juce_ScrollBar.cpp @@ -369,18 +369,18 @@ void ScrollBar::mouseDown (const MouseEvent& e) void ScrollBar::mouseDrag (const MouseEvent& e) { - if (isDraggingThumb) + const int mousePos = vertical ? e.y : e.x; + + if (isDraggingThumb && lastMousePos != mousePos) { - const int deltaPixels = ((vertical) ? e.y : e.x) - dragStartMousePos; + const int deltaPixels = mousePos - dragStartMousePos; setCurrentRangeStart (dragStartRange + deltaPixels * (totalRange.getLength() - visibleRange.getLength()) / (thumbAreaSize - thumbSize)); } - else - { - lastMousePos = (vertical) ? e.y : e.x; - } + + lastMousePos = mousePos; } void ScrollBar::mouseUp (const MouseEvent&) diff --git a/src/gui/components/layout/juce_TabbedButtonBar.cpp b/src/gui/components/layout/juce_TabbedButtonBar.cpp index c2c8f4492b..81190331de 100644 --- a/src/gui/components/layout/juce_TabbedButtonBar.cpp +++ b/src/gui/components/layout/juce_TabbedButtonBar.cpp @@ -83,7 +83,7 @@ bool TabBarButton::hitTest (int mx, int my) if (owner.getOrientation() == TabbedButtonBar::TabsAtLeft || owner.getOrientation() == TabbedButtonBar::TabsAtRight) { - if (((unsigned int) mx) < (unsigned int) getWidth() + if (isPositiveAndBelow (mx, getWidth()) && my >= area.getY() + overlapPixels && my < area.getBottom() - overlapPixels) return true; @@ -91,7 +91,7 @@ bool TabBarButton::hitTest (int mx, int my) else { if (mx >= area.getX() + overlapPixels && mx < area.getRight() - overlapPixels - && ((unsigned int) my) < (unsigned int) getHeight()) + && isPositiveAndBelow (my, getHeight())) return true; } @@ -214,7 +214,7 @@ void TabbedButtonBar::addTab (const String& tabName, if (tabName.isNotEmpty()) { - if (((unsigned int) insertIndex) > (unsigned int) tabs.size()) + if (! isPositiveAndBelow (insertIndex, tabs.size())) insertIndex = tabs.size(); TabInfo* newTab = new TabInfo(); @@ -292,7 +292,7 @@ void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMes { if (currentTabIndex != newIndex) { - if (((unsigned int) newIndex) >= (unsigned int) tabs.size()) + if (! isPositiveAndBelow (newIndex, tabs.size())) newIndex = -1; currentTabIndex = newIndex; diff --git a/src/gui/components/menus/juce_MenuBarComponent.cpp b/src/gui/components/menus/juce_MenuBarComponent.cpp index e924a4b2f0..8ad11151ab 100644 --- a/src/gui/components/menus/juce_MenuBarComponent.cpp +++ b/src/gui/components/menus/juce_MenuBarComponent.cpp @@ -134,7 +134,7 @@ int MenuBarComponent::getItemAt (const int x, const int y) void MenuBarComponent::repaintMenuItem (int index) { - if (((unsigned int) index) < (unsigned int) xPositions.size()) + if (isPositiveAndBelow (index, xPositions.size())) { const int x1 = xPositions [index]; const int x2 = xPositions [index + 1]; diff --git a/src/gui/components/menus/juce_PopupMenu.cpp b/src/gui/components/menus/juce_PopupMenu.cpp index e662431d62..0f731f6cfb 100644 --- a/src/gui/components/menus/juce_PopupMenu.cpp +++ b/src/gui/components/menus/juce_PopupMenu.cpp @@ -302,7 +302,7 @@ public: { const int y = target.getY() - windowPos.getY(); ensureItemIsVisible (itemIdThatMustBeVisible, - (((unsigned int) y) < (unsigned int) windowPos.getHeight()) ? y : -1); + isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1); } resizeToBestWindowPos(); @@ -568,7 +568,7 @@ public: bool overScrollArea = false; if (isScrolling() - && (isOver || (isDown && ((unsigned int) localMousePos.getX()) < (unsigned int) getWidth())) + && (isOver || (isDown && isPositiveAndBelow (localMousePos.getX(), getWidth()))) && ((isScrollZoneActive (false) && localMousePos.getY() < PopupMenuSettings::scrollZone) || (isScrollZoneActive (true) && localMousePos.getY() > getHeight() - PopupMenuSettings::scrollZone))) { diff --git a/src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp b/src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp index 153f347e1f..48763a1841 100644 --- a/src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp +++ b/src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp @@ -114,7 +114,7 @@ public: int width, int height, bool rowIsSelected) { - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId) @@ -185,7 +185,7 @@ private: void flipEnablement (const int row) { - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { const String item (items [row]); deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); @@ -717,7 +717,7 @@ public: int width, int height, bool rowIsSelected) { - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId) @@ -806,7 +806,7 @@ public: { jassert (type == audioInputType || type == audioOutputType); - if (((unsigned int) row) < (unsigned int) items.size()) + if (isPositiveAndBelow (row, items.size())) { AudioDeviceManager::AudioDeviceSetup config; setup.manager->getAudioDeviceSetup (config); diff --git a/src/gui/components/special/juce_MagnifierComponent.cpp b/src/gui/components/special/juce_MagnifierComponent.cpp index df79587b85..afe2cf7739 100644 --- a/src/gui/components/special/juce_MagnifierComponent.cpp +++ b/src/gui/components/special/juce_MagnifierComponent.cpp @@ -114,8 +114,8 @@ public: bool contains (const Point& position, bool) const { - return ((unsigned int) position.getX()) < (unsigned int) magnifierComp->getWidth() - && ((unsigned int) position.getY()) < (unsigned int) magnifierComp->getHeight(); + return isPositiveAndBelow (position.getX(), magnifierComp->getWidth()) + && isPositiveAndBelow (position.getY(), magnifierComp->getHeight()); } void repaint (const Rectangle& area) diff --git a/src/gui/graphics/colour/juce_ColourGradient.cpp b/src/gui/graphics/colour/juce_ColourGradient.cpp index dc05e5c04e..3c279605d8 100644 --- a/src/gui/graphics/colour/juce_ColourGradient.cpp +++ b/src/gui/graphics/colour/juce_ColourGradient.cpp @@ -110,7 +110,7 @@ int ColourGradient::getNumColours() const throw() double ColourGradient::getColourPosition (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) colours.size()) + if (isPositiveAndBelow (index, colours.size())) return colours.getReference (index).position; return 0; @@ -118,7 +118,7 @@ double ColourGradient::getColourPosition (const int index) const throw() const Colour ColourGradient::getColour (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) colours.size()) + if (isPositiveAndBelow (index, colours.size())) return colours.getReference (index).colour; return Colour(); @@ -126,7 +126,7 @@ const Colour ColourGradient::getColour (const int index) const throw() void ColourGradient::setColour (int index, const Colour& newColour) throw() { - if (((unsigned int) index) < (unsigned int) colours.size()) + if (isPositiveAndBelow (index, colours.size())) colours.getReference (index).colour = newColour; } diff --git a/src/gui/graphics/contexts/juce_EdgeTable.cpp b/src/gui/graphics/contexts/juce_EdgeTable.cpp index aac399eb1a..36977e8cbb 100644 --- a/src/gui/graphics/contexts/juce_EdgeTable.cpp +++ b/src/gui/graphics/contexts/juce_EdgeTable.cpp @@ -500,7 +500,7 @@ void EdgeTable::intersectWithEdgeTableLine (const int y, const int* otherLine) lastX = nextX; const int nextLevel = (level1 * (level2 + 1)) >> 8; - jassert (((unsigned int) nextLevel) < (unsigned int) 256); + jassert (isPositiveAndBelow (nextLevel, (int) 256)); if (nextLevel != lastLevel) { diff --git a/src/gui/graphics/contexts/juce_EdgeTable.h b/src/gui/graphics/contexts/juce_EdgeTable.h index dedadd7edb..982ff06528 100644 --- a/src/gui/graphics/contexts/juce_EdgeTable.h +++ b/src/gui/graphics/contexts/juce_EdgeTable.h @@ -133,7 +133,7 @@ public: while (--numPoints >= 0) { const int level = *++line; - jassert (((unsigned int) level) < (unsigned int) 256); + jassert (isPositiveAndBelow (level, (int) 256)); const int endX = *++line; jassert (endX >= x); const int endOfRun = (endX >> 8); diff --git a/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp b/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp index 55ec9d1f57..b742845942 100644 --- a/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp +++ b/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp @@ -680,9 +680,9 @@ private: if (betterQuality) { - if (((unsigned int) loResX) < (unsigned int) maxX) + if (isPositiveAndBelow (loResX, maxX)) { - if (((unsigned int) loResY) < (unsigned int) maxY) + if (isPositiveAndBelow (loResY, maxY)) { // In the centre of the image.. render4PixelAverage (dest, this->srcData.getPixelPointer (loResX, loResY), @@ -707,7 +707,7 @@ private: } else { - if (((unsigned int) loResY) < (unsigned int) maxY) + if (isPositiveAndBelow (loResY, maxY)) { // At a left or right hand edge.. if (! repeatPattern) diff --git a/src/gui/graphics/fonts/juce_GlyphArrangement.cpp b/src/gui/graphics/fonts/juce_GlyphArrangement.cpp index 490287610f..8e46df4519 100644 --- a/src/gui/graphics/fonts/juce_GlyphArrangement.cpp +++ b/src/gui/graphics/fonts/juce_GlyphArrangement.cpp @@ -156,7 +156,7 @@ void GlyphArrangement::clear() PositionedGlyph& GlyphArrangement::getGlyph (const int index) const { - jassert (((unsigned int) index) < (unsigned int) glyphs.size()); + jassert (isPositiveAndBelow (index, glyphs.size())); return *glyphs [index]; } diff --git a/src/gui/graphics/fonts/juce_Typeface.cpp b/src/gui/graphics/fonts/juce_Typeface.cpp index b836332997..eb1c0c1e66 100644 --- a/src/gui/graphics/fonts/juce_Typeface.cpp +++ b/src/gui/graphics/fonts/juce_Typeface.cpp @@ -170,7 +170,7 @@ void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, con // Check that you're not trying to add the same character twice.. jassert (findGlyph (character, false) == 0); - if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable)) + if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable))) lookupTable [character] = (short) glyphs.size(); glyphs.add (new GlyphInfo (character, path, width)); @@ -190,7 +190,7 @@ void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar ch CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) throw() { - if (((unsigned int) character) < (unsigned int) numElementsInArray (lookupTable) && lookupTable [character] > 0) + if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable)) && lookupTable [character] > 0) return glyphs [(int) lookupTable [(int) character]]; for (int i = 0; i < glyphs.size(); ++i) diff --git a/src/gui/graphics/geometry/juce_RectangleList.cpp b/src/gui/graphics/geometry/juce_RectangleList.cpp index da9962afd6..9aedb08cb8 100644 --- a/src/gui/graphics/geometry/juce_RectangleList.cpp +++ b/src/gui/graphics/geometry/juce_RectangleList.cpp @@ -64,7 +64,7 @@ void RectangleList::clear() const Rectangle RectangleList::getRectangle (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) rects.size()) + if (isPositiveAndBelow (index, rects.size())) return rects.getReference (index); return Rectangle(); diff --git a/src/gui/graphics/imaging/juce_Image.cpp b/src/gui/graphics/imaging/juce_Image.cpp index be779c8b0f..869138054f 100644 --- a/src/gui/graphics/imaging/juce_Image.cpp +++ b/src/gui/graphics/imaging/juce_Image.cpp @@ -292,7 +292,7 @@ Image::BitmapData::~BitmapData() const Colour Image::BitmapData::getPixelColour (const int x, const int y) const throw() { - jassert (((unsigned int) x) < (unsigned int) width && ((unsigned int) y) < (unsigned int) height); + jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height)); const uint8* const pixel = getPixelPointer (x, y); @@ -320,7 +320,7 @@ const Colour Image::BitmapData::getPixelColour (const int x, const int y) const void Image::BitmapData::setPixelColour (const int x, const int y, const Colour& colour) const throw() { - jassert (((unsigned int) x) < (unsigned int) width && ((unsigned int) y) < (unsigned int) height); + jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height)); uint8* const pixel = getPixelPointer (x, y); const PixelARGB col (colour.getPixelARGB()); @@ -401,8 +401,7 @@ void Image::clear (const Rectangle& area, const Colour& colourToClearTo) //============================================================================== const Colour Image::getPixelAt (const int x, const int y) const { - if (((unsigned int) x) < (unsigned int) getWidth() - && ((unsigned int) y) < (unsigned int) getHeight()) + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())) { const BitmapData srcData (*this, x, y, 1, 1); return srcData.getPixelColour (0, 0); @@ -413,8 +412,7 @@ const Colour Image::getPixelAt (const int x, const int y) const void Image::setPixelAt (const int x, const int y, const Colour& colour) { - if (((unsigned int) x) < (unsigned int) getWidth() - && ((unsigned int) y) < (unsigned int) getHeight()) + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())) { const BitmapData destData (*this, x, y, 1, 1, true); destData.setPixelColour (0, 0, colour); @@ -423,8 +421,7 @@ void Image::setPixelAt (const int x, const int y, const Colour& colour) void Image::multiplyAlphaAt (const int x, const int y, const float multiplier) { - if (((unsigned int) x) < (unsigned int) getWidth() - && ((unsigned int) y) < (unsigned int) getHeight() + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight()) && hasAlphaChannel()) { const BitmapData destData (*this, x, y, 1, 1, true); diff --git a/src/gui/graphics/imaging/juce_ImageConvolutionKernel.cpp b/src/gui/graphics/imaging/juce_ImageConvolutionKernel.cpp index 0d1bf6866c..3ac7267d5b 100644 --- a/src/gui/graphics/imaging/juce_ImageConvolutionKernel.cpp +++ b/src/gui/graphics/imaging/juce_ImageConvolutionKernel.cpp @@ -45,22 +45,16 @@ ImageConvolutionKernel::~ImageConvolutionKernel() //============================================================================== float ImageConvolutionKernel::getKernelValue (const int x, const int y) const throw() { - if (((unsigned int) x) < (unsigned int) size - && ((unsigned int) y) < (unsigned int) size) - { + if (isPositiveAndBelow (x, size) && isPositiveAndBelow (y, size)) return values [x + y * size]; - } - else - { - jassertfalse; - return 0; - } + + jassertfalse; + return 0; } void ImageConvolutionKernel::setKernelValue (const int x, const int y, const float value) throw() { - if (((unsigned int) x) < (unsigned int) size - && ((unsigned int) y) < (unsigned int) size) + if (isPositiveAndBelow (x, size) && isPositiveAndBelow (y, size)) { values [x + y * size] = value; } diff --git a/src/native/linux/juce_linux_Windowing.cpp b/src/native/linux/juce_linux_Windowing.cpp index f263f6fc4f..77013bc9cd 100644 --- a/src/native/linux/juce_linux_Windowing.cpp +++ b/src/native/linux/juce_linux_Windowing.cpp @@ -946,8 +946,7 @@ public: bool contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) ww - || ((unsigned int) position.getY()) >= (unsigned int) wh) + if (! (isPositiveAndBelow (position.getX(), ww) && isPositiveAndBelow (position.getY(), wh))) return false; for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;) @@ -1206,67 +1205,56 @@ public: { case 2: // 'KeyPress' { - ScopedXLock xlock; - XKeyEvent* const keyEvent = (XKeyEvent*) &event->xkey; - updateKeyStates (keyEvent->keycode, true); - - char utf8 [64]; - zeromem (utf8, sizeof (utf8)); + char utf8 [64] = { 0 }; + juce_wchar unicodeChar = 0; + int keyCode = 0; + bool keyDownChange = false; KeySym sym; { + ScopedXLock xlock; + XKeyEvent* const keyEvent = (XKeyEvent*) &event->xkey; + updateKeyStates (keyEvent->keycode, true); + const char* oldLocale = ::setlocale (LC_ALL, 0); ::setlocale (LC_ALL, ""); XLookupString (keyEvent, utf8, sizeof (utf8), &sym, 0); ::setlocale (LC_ALL, oldLocale); + + unicodeChar = String::fromUTF8 (utf8, sizeof (utf8) - 1) [0]; + keyCode = (int) unicodeChar; + + if (keyCode < 0x20) + keyCode = XKeycodeToKeysym (display, keyEvent->keycode, currentModifiers.isShiftDown() ? 1 : 0); + + keyDownChange = (sym != NoSymbol) && ! updateKeyModifiersFromSym (sym, true); } - const juce_wchar unicodeChar = String::fromUTF8 (utf8, sizeof (utf8) - 1) [0]; - int keyCode = (int) unicodeChar; - - if (keyCode < 0x20) - keyCode = XKeycodeToKeysym (display, keyEvent->keycode, currentModifiers.isShiftDown() ? 1 : 0); - const ModifierKeys oldMods (currentModifiers); bool keyPressed = false; - const bool keyDownChange = (sym != NoSymbol) && ! updateKeyModifiersFromSym (sym, true); - if ((sym & 0xff00) == 0xff00) { - // Translate keypad - if (sym == XK_KP_Divide) - keyCode = XK_slash; - else if (sym == XK_KP_Multiply) - keyCode = XK_asterisk; - else if (sym == XK_KP_Subtract) - keyCode = XK_hyphen; - else if (sym == XK_KP_Add) - keyCode = XK_plus; - else if (sym == XK_KP_Enter) - keyCode = XK_Return; - else if (sym == XK_KP_Decimal) - keyCode = Keys::numLock ? XK_period : XK_Delete; - else if (sym == XK_KP_0) - keyCode = Keys::numLock ? XK_0 : XK_Insert; - else if (sym == XK_KP_1) - keyCode = Keys::numLock ? XK_1 : XK_End; - else if (sym == XK_KP_2) - keyCode = Keys::numLock ? XK_2 : XK_Down; - else if (sym == XK_KP_3) - keyCode = Keys::numLock ? XK_3 : XK_Page_Down; - else if (sym == XK_KP_4) - keyCode = Keys::numLock ? XK_4 : XK_Left; - else if (sym == XK_KP_5) - keyCode = XK_5; - else if (sym == XK_KP_6) - keyCode = Keys::numLock ? XK_6 : XK_Right; - else if (sym == XK_KP_7) - keyCode = Keys::numLock ? XK_7 : XK_Home; - else if (sym == XK_KP_8) - keyCode = Keys::numLock ? XK_8 : XK_Up; - else if (sym == XK_KP_9) - keyCode = Keys::numLock ? XK_9 : XK_Page_Up; + switch (sym) // Translate keypad + { + case XK_KP_Divide: keyCode = XK_slash; break; + case XK_KP_Multiply: keyCode = XK_asterisk; break; + case XK_KP_Subtract: keyCode = XK_hyphen; break; + case XK_KP_Add: keyCode = XK_plus; break; + case XK_KP_Enter: keyCode = XK_Return; break; + case XK_KP_Decimal: keyCode = Keys::numLock ? XK_period : XK_Delete; break; + case XK_KP_0: keyCode = Keys::numLock ? XK_0 : XK_Insert; break; + case XK_KP_1: keyCode = Keys::numLock ? XK_1 : XK_End; break; + case XK_KP_2: keyCode = Keys::numLock ? XK_2 : XK_Down; break; + case XK_KP_3: keyCode = Keys::numLock ? XK_3 : XK_Page_Down; break; + case XK_KP_4: keyCode = Keys::numLock ? XK_4 : XK_Left; break; + case XK_KP_5: keyCode = XK_5; break; + case XK_KP_6: keyCode = Keys::numLock ? XK_6 : XK_Right; break; + case XK_KP_7: keyCode = Keys::numLock ? XK_7 : XK_Home; break; + case XK_KP_8: keyCode = Keys::numLock ? XK_8 : XK_Up; break; + case XK_KP_9: keyCode = Keys::numLock ? XK_9 : XK_Page_Up; break; + default: break; + } switch (sym) { diff --git a/src/native/mac/juce_iphone_UIViewComponentPeer.mm b/src/native/mac/juce_iphone_UIViewComponentPeer.mm index e370afbc48..f937ce9ff0 100644 --- a/src/native/mac/juce_iphone_UIViewComponentPeer.mm +++ b/src/native/mac/juce_iphone_UIViewComponentPeer.mm @@ -667,8 +667,8 @@ void UIViewComponentPeer::displayRotated() bool UIViewComponentPeer::contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) component->getWidth() - || ((unsigned int) position.getY()) >= (unsigned int) component->getHeight()) + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) return false; UIView* v = [view hitTest: CGPointMake ((float) position.getX(), (float) position.getY()) diff --git a/src/native/mac/juce_mac_CoreAudio.cpp b/src/native/mac/juce_mac_CoreAudio.cpp index 93dcaa769b..2c186b49b6 100644 --- a/src/native/mac/juce_mac_CoreAudio.cpp +++ b/src/native/mac/juce_mac_CoreAudio.cpp @@ -403,7 +403,7 @@ public: HeapBlock types; const int num = getAllDataSourcesForDevice (deviceID, types); - if (((unsigned int) index) < (unsigned int) num) + if (isPositiveAndBelow (index, num)) { AudioObjectPropertyAddress pa; pa.mSelector = kAudioDevicePropertyDataSource; diff --git a/src/native/mac/juce_mac_CoreGraphicsContext.mm b/src/native/mac/juce_mac_CoreGraphicsContext.mm index 5ef7243428..af395b2584 100644 --- a/src/native/mac/juce_mac_CoreGraphicsContext.mm +++ b/src/native/mac/juce_mac_CoreGraphicsContext.mm @@ -127,7 +127,7 @@ private: #endif } - JUCE_LEAK_DETECTOR (CoreGraphicsImage); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImage); }; Image::SharedImage* Image::SharedImage::createNativeImage (PixelFormat format, int width, int height, bool clearImage) diff --git a/src/native/mac/juce_mac_CoreMidi.cpp b/src/native/mac/juce_mac_CoreMidi.cpp index 7c29041ac3..1ed74d7a4c 100644 --- a/src/native/mac/juce_mac_CoreMidi.cpp +++ b/src/native/mac/juce_mac_CoreMidi.cpp @@ -323,7 +323,7 @@ MidiOutput* MidiOutput::openDevice (int index) { MidiOutput* mo = 0; - if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations()) + if (isPositiveAndBelow (index, (int) MIDIGetNumberOfDestinations())) { MIDIEndpointRef endPoint = MIDIGetDestination (index); @@ -461,7 +461,7 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) using namespace CoreMidiHelpers; MidiInput* newInput = 0; - if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources()) + if (isPositiveAndBelow (index, (int) MIDIGetNumberOfSources())) { MIDIEndpointRef endPoint = MIDIGetSource (index); diff --git a/src/native/mac/juce_mac_NSViewComponentPeer.mm b/src/native/mac/juce_mac_NSViewComponentPeer.mm index 868bf6acdd..6c791d8527 100644 --- a/src/native/mac/juce_mac_NSViewComponentPeer.mm +++ b/src/native/mac/juce_mac_NSViewComponentPeer.mm @@ -1162,8 +1162,8 @@ bool NSViewComponentPeer::isFullScreen() const bool NSViewComponentPeer::contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) component->getWidth() - || ((unsigned int) position.getY()) >= (unsigned int) component->getHeight()) + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) return false; NSPoint p; diff --git a/src/native/windows/juce_win32_AudioCDReader.cpp b/src/native/windows/juce_win32_AudioCDReader.cpp index 24fc949cf9..a159d97b98 100644 --- a/src/native/windows/juce_win32_AudioCDReader.cpp +++ b/src/native/windows/juce_win32_AudioCDReader.cpp @@ -1671,7 +1671,7 @@ AudioCDReader* AudioCDReader::createReaderForCD (const int deviceIndex) CDDeviceInfo list[8]; const int num = FindCDDevices (list, 8); - if (((unsigned int) deviceIndex) < (unsigned int) num) + if (isPositiveAndBelow (deviceIndex, num)) { CDDeviceHandle* const handle = openHandle (&(list[deviceIndex])); diff --git a/src/native/windows/juce_win32_Windowing.cpp b/src/native/windows/juce_win32_Windowing.cpp index dea8140dd3..54378e26d7 100644 --- a/src/native/windows/juce_win32_Windowing.cpp +++ b/src/native/windows/juce_win32_Windowing.cpp @@ -747,8 +747,8 @@ public: bool contains (const Point& position, bool trueIfInAChildWindow) const { - if (((unsigned int) position.getX()) >= (unsigned int) component->getWidth() - || ((unsigned int) position.getY()) >= (unsigned int) component->getHeight()) + if (! (isPositiveAndBelow (position.getX(), component->getWidth()) + && isPositiveAndBelow (position.getY(), component->getHeight()))) return false; RECT r; diff --git a/src/text/juce_String.cpp b/src/text/juce_String.cpp index d9c2f3a025..7613696001 100644 --- a/src/text/juce_String.cpp +++ b/src/text/juce_String.cpp @@ -1262,7 +1262,7 @@ const String String::replaceCharacters (const String& charactersToReplace, { const int index = charactersToReplace.indexOfChar (*t); - if (((unsigned int) index) < (unsigned int) len2) + if (isPositiveAndBelow (index, len2)) *t = charactersToInsertInstead [index]; ++t; @@ -1333,7 +1333,7 @@ const String String::toLowerCase() const //============================================================================== juce_wchar& String::operator[] (const int index) { - jassert (((unsigned int) index) <= (unsigned int) length()); + jassert (isPositiveAndNotGreaterThan (index, length())); text = StringHolder::makeUnique (text); return text [index]; } diff --git a/src/text/juce_String.h b/src/text/juce_String.h index 9a44fef4ef..2e876d5b49 100644 --- a/src/text/juce_String.h +++ b/src/text/juce_String.h @@ -442,7 +442,7 @@ public: No checks are made to see if the index is within a valid range, so be careful! */ - inline const juce_wchar& operator[] (int index) const throw() { jassert (((unsigned int) index) <= (unsigned int) length()); return text [index]; } + inline const juce_wchar& operator[] (int index) const throw() { jassert (isPositiveAndNotGreaterThan (index, length())); return text [index]; } /** Returns a character from the string such that it can also be altered. diff --git a/src/text/juce_StringArray.cpp b/src/text/juce_StringArray.cpp index 045f4f470e..21a6480a31 100644 --- a/src/text/juce_StringArray.cpp +++ b/src/text/juce_StringArray.cpp @@ -109,7 +109,7 @@ void StringArray::clear() const String& StringArray::operator[] (const int index) const throw() { - if (((unsigned int) index) < (unsigned int) strings.size()) + if (isPositiveAndBelow (index, strings.size())) return strings.getReference (index); return String::empty; @@ -117,7 +117,7 @@ const String& StringArray::operator[] (const int index) const throw() String& StringArray::getReference (const int index) throw() { - jassert (((unsigned int) index) < (unsigned int) strings.size()); + jassert (isPositiveAndBelow (index, strings.size())); return strings.getReference (index); }