diff --git a/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h b/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h index 209f242d78..c59074475d 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h +++ b/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h @@ -436,6 +436,9 @@ public: /** Adds a number of samples to the pointer's position. */ Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; } + /** Returns a new pointer with the specified offset from this pointer's position. */ + Pointer operator+ (int samplesToJump) const { return Pointer { *this } += samplesToJump; } + /** Writes a stream of samples into this pointer from another pointer. This will copy the specified number of samples, converting between formats appropriately. */ diff --git a/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp b/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp index 4f0e5a2d1d..377b70edcd 100644 --- a/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp +++ b/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp @@ -83,6 +83,66 @@ private: int8 values[2]; }; + +//============================================================================== +template +class AudioBufferReader : public AudioFormatReader +{ +public: + AudioBufferReader (const AudioBuffer* bufferIn, double rate) + : AudioFormatReader (nullptr, "AudioBuffer"), buffer (bufferIn) + { + sampleRate = rate; + bitsPerSample = 32; + lengthInSamples = buffer->getNumSamples(); + numChannels = (unsigned int) buffer->getNumChannels(); + usesFloatingPointData = std::is_floating_point_v; + } + + bool readSamples (int* const* destChannels, + int numDestChannels, + int startOffsetInDestBuffer, + int64 startSampleInFile, + int numSamples) override + { + clearSamplesBeyondAvailableLength (destChannels, numDestChannels, startOffsetInDestBuffer, + startSampleInFile, numSamples, lengthInSamples); + + const auto numAvailableSamples = (int) ((int64) buffer->getNumSamples() - startSampleInFile); + const auto numSamplesToCopy = std::clamp (numAvailableSamples, 0, numSamples); + + if (numSamplesToCopy == 0) + return true; + + for (int i = 0; i < numDestChannels; ++i) + { + if (void* targetChannel = destChannels[i]) + { + const auto dest = DestType (targetChannel) + startOffsetInDestBuffer; + + if (i < buffer->getNumChannels()) + dest.convertSamples (SourceType (buffer->getReadPointer (i) + startSampleInFile), numSamplesToCopy); + else + dest.clearSamples (numSamples); + } + } + + return true; + } + +private: + using SourceNumericalType = + std::conditional_t, AudioData::Int32, + std::conditional_t, AudioData::Float32, void>>; + + using DestinationNumericalType = std::conditional_t, AudioData::Float32, AudioData::Int32>; + + using DestType = AudioData::Pointer; + using SourceType = AudioData::Pointer; + + const AudioBuffer* buffer; +}; + //============================================================================== class AudioThumbnail::LevelDataSource : public TimeSliceClient { @@ -687,6 +747,16 @@ void AudioThumbnail::setReader (AudioFormatReader* newReader, int64 hash) setDataSource (new LevelDataSource (*this, newReader, hash)); } +void AudioThumbnail::setSource (const AudioBuffer* newSource, double rate, int64 hash) +{ + setReader (new AudioBufferReader (newSource, rate), hash); +} + +void AudioThumbnail::setSource (const AudioBuffer* newSource, double rate, int64 hash) +{ + setReader (new AudioBufferReader (newSource, rate), hash); +} + int64 AudioThumbnail::getHashCode() const { return source == nullptr ? 0 : source->hashCode; diff --git a/modules/juce_audio_utils/gui/juce_AudioThumbnail.h b/modules/juce_audio_utils/gui/juce_AudioThumbnail.h index ca912c8924..156aeaa09a 100644 --- a/modules/juce_audio_utils/gui/juce_AudioThumbnail.h +++ b/modules/juce_audio_utils/gui/juce_AudioThumbnail.h @@ -99,6 +99,18 @@ public: */ void setReader (AudioFormatReader* newReader, int64 hashCode) override; + /** Sets an AudioBuffer as the source for the thumbnail. + + The buffer contents aren't copied and you must ensure that the lifetime of the buffer is + valid for as long as the AudioThumbnail uses it as its source. Calling this function will + start reading the audio in a background thread (unless the hash code can be looked-up + successfully in the thumbnail cache). + */ + void setSource (const AudioBuffer* newSource, double sampleRate, int64 hashCode); + + /** Same as the other setSource() overload except for int data. */ + void setSource (const AudioBuffer* newSource, double sampleRate, 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.