From 898121c8e4c6670fc0f1e7a58ddbdc9a933c8bae Mon Sep 17 00:00:00 2001 From: falkTX Date: Tue, 31 Oct 2017 13:34:07 +0100 Subject: [PATCH] Try to get a minimal version of juce v4.x in, for patchbay mode --- source/modules/juce_audio_graph/Makefile | 122 + .../buffers/juce_AudioSampleBuffer.h | 1100 +++++++++ .../juce_audio_graph/containers/juce_Array.h | 1155 ++++++++++ .../containers/juce_ArrayAllocationBase.h | 140 ++ .../containers/juce_ElementComparator.h | 197 ++ .../containers/juce_NamedValueSet.cpp | 255 +++ .../containers/juce_NamedValueSet.h | 188 ++ .../containers/juce_OwnedArray.h | 841 +++++++ .../containers/juce_Variant.cpp | 799 +++++++ .../containers/juce_Variant.h | 353 +++ .../juce_audio_graph/juce_audio_graph.cpp | 75 + .../juce_audio_graph/juce_audio_graph.h | 167 ++ .../juce_audio_graph/juce_audio_graph.mm | 19 + .../maths/juce_MathsFunctions.h | 554 +++++ .../juce_audio_graph/memory/juce_Atomic.h | 249 +++ .../juce_audio_graph/memory/juce_ByteOrder.h | 241 ++ .../juce_audio_graph/memory/juce_HeapBlock.h | 314 +++ .../juce_audio_graph/memory/juce_Memory.h | 120 + .../memory/juce_MemoryBlock.cpp | 418 ++++ .../memory/juce_MemoryBlock.h | 263 +++ .../juce_audio_graph/midi/juce_MidiBuffer.cpp | 235 ++ .../juce_audio_graph/midi/juce_MidiBuffer.h | 241 ++ .../midi/juce_MidiMessage.cpp | 1150 ++++++++++ .../juce_audio_graph/midi/juce_MidiMessage.h | 940 ++++++++ .../processors/juce_AudioPlayHead.h | 144 ++ .../processors/juce_AudioProcessor.cpp | 124 ++ .../processors/juce_AudioProcessor.h | 381 ++++ .../processors/juce_AudioProcessorGraph.cpp | 1630 ++++++++++++++ .../processors/juce_AudioProcessorGraph.h | 387 ++++ .../streams/juce_InputStream.cpp | 238 ++ .../streams/juce_InputStream.h | 268 +++ .../streams/juce_MemoryOutputStream.cpp | 216 ++ .../streams/juce_MemoryOutputStream.h | 141 ++ .../streams/juce_OutputStream.cpp | 353 +++ .../streams/juce_OutputStream.h | 279 +++ .../text/juce_CharPointer_UTF8.h | 572 +++++ .../text/juce_CharacterFunctions.cpp | 171 ++ .../text/juce_CharacterFunctions.h | 620 ++++++ .../juce_audio_graph/text/juce_NewLine.h | 90 + .../juce_audio_graph/text/juce_String.cpp | 1972 +++++++++++++++++ .../juce_audio_graph/text/juce_String.h | 1181 ++++++++++ .../juce_audio_graph/text/juce_StringRef.h | 136 ++ 42 files changed, 19039 insertions(+) create mode 100644 source/modules/juce_audio_graph/Makefile create mode 100644 source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h create mode 100644 source/modules/juce_audio_graph/containers/juce_Array.h create mode 100644 source/modules/juce_audio_graph/containers/juce_ArrayAllocationBase.h create mode 100644 source/modules/juce_audio_graph/containers/juce_ElementComparator.h create mode 100644 source/modules/juce_audio_graph/containers/juce_NamedValueSet.cpp create mode 100644 source/modules/juce_audio_graph/containers/juce_NamedValueSet.h create mode 100644 source/modules/juce_audio_graph/containers/juce_OwnedArray.h create mode 100644 source/modules/juce_audio_graph/containers/juce_Variant.cpp create mode 100644 source/modules/juce_audio_graph/containers/juce_Variant.h create mode 100644 source/modules/juce_audio_graph/juce_audio_graph.cpp create mode 100644 source/modules/juce_audio_graph/juce_audio_graph.h create mode 100644 source/modules/juce_audio_graph/juce_audio_graph.mm create mode 100644 source/modules/juce_audio_graph/maths/juce_MathsFunctions.h create mode 100644 source/modules/juce_audio_graph/memory/juce_Atomic.h create mode 100644 source/modules/juce_audio_graph/memory/juce_ByteOrder.h create mode 100644 source/modules/juce_audio_graph/memory/juce_HeapBlock.h create mode 100644 source/modules/juce_audio_graph/memory/juce_Memory.h create mode 100644 source/modules/juce_audio_graph/memory/juce_MemoryBlock.cpp create mode 100644 source/modules/juce_audio_graph/memory/juce_MemoryBlock.h create mode 100644 source/modules/juce_audio_graph/midi/juce_MidiBuffer.cpp create mode 100644 source/modules/juce_audio_graph/midi/juce_MidiBuffer.h create mode 100644 source/modules/juce_audio_graph/midi/juce_MidiMessage.cpp create mode 100644 source/modules/juce_audio_graph/midi/juce_MidiMessage.h create mode 100644 source/modules/juce_audio_graph/processors/juce_AudioPlayHead.h create mode 100644 source/modules/juce_audio_graph/processors/juce_AudioProcessor.cpp create mode 100644 source/modules/juce_audio_graph/processors/juce_AudioProcessor.h create mode 100644 source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.cpp create mode 100644 source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h create mode 100644 source/modules/juce_audio_graph/streams/juce_InputStream.cpp create mode 100644 source/modules/juce_audio_graph/streams/juce_InputStream.h create mode 100644 source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.cpp create mode 100644 source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.h create mode 100644 source/modules/juce_audio_graph/streams/juce_OutputStream.cpp create mode 100644 source/modules/juce_audio_graph/streams/juce_OutputStream.h create mode 100644 source/modules/juce_audio_graph/text/juce_CharPointer_UTF8.h create mode 100644 source/modules/juce_audio_graph/text/juce_CharacterFunctions.cpp create mode 100644 source/modules/juce_audio_graph/text/juce_CharacterFunctions.h create mode 100644 source/modules/juce_audio_graph/text/juce_NewLine.h create mode 100644 source/modules/juce_audio_graph/text/juce_String.cpp create mode 100644 source/modules/juce_audio_graph/text/juce_String.h create mode 100644 source/modules/juce_audio_graph/text/juce_StringRef.h diff --git a/source/modules/juce_audio_graph/Makefile b/source/modules/juce_audio_graph/Makefile new file mode 100644 index 000000000..fd01163a0 --- /dev/null +++ b/source/modules/juce_audio_graph/Makefile @@ -0,0 +1,122 @@ +#!/usr/bin/make -f +# Makefile for juce_audio_graph # +# ----------------------------- # +# Created by falkTX +# + +CWD=../.. +MODULENAME=juce_audio_graph +include ../Makefile.mk + +# ---------------------------------------------------------------------------------------------------------------------------- + +BUILD_CXX_FLAGS += -I.. + +# ---------------------------------------------------------------------------------------------------------------------------- + +ifeq ($(MACOS),true) +OBJS = $(OBJDIR)/$(MODULENAME).mm.o +OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o +OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o +else +OBJS = $(OBJDIR)/$(MODULENAME).cpp.o +OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o +OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o +endif +OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o +OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o + +# ---------------------------------------------------------------------------------------------------------------------------- + +all: $(MODULEDIR)/$(MODULENAME).a +posix32: $(MODULEDIR)/$(MODULENAME).posix32.a +posix64: $(MODULEDIR)/$(MODULENAME).posix64.a +win32: $(MODULEDIR)/$(MODULENAME).win32.a +win64: $(MODULEDIR)/$(MODULENAME).win64.a + +# ---------------------------------------------------------------------------------------------------------------------------- + +clean: + rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a + +debug: + $(MAKE) DEBUG=true + +test: main.cpp $(MODULEDIR)/$(MODULENAME).a + @$(CXX) $< $(MODULEDIR)/$(MODULENAME).a $(BUILD_CXX_FLAGS) $(LINK_FLAGS) -lpthread -o $@ + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(MODULEDIR)/$(MODULENAME).a: $(OBJS) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).posix32.a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).posix64.a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).win32.a" + @rm -f $@ + @$(AR) crs $@ $^ + +$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) + -@mkdir -p $(MODULEDIR) + @echo "Creating $(MODULENAME).win64.a" + @rm -f $@ + @$(AR) crs $@ $^ + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ + +$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (32bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ + +$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (64bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ + +# ---------------------------------------------------------------------------------------------------------------------------- + +$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $<" + @$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ + +$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (32bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ + +$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling $< (64bit)" + @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ + +# ---------------------------------------------------------------------------------------------------------------------------- + +-include $(OBJS:%.o=%.d) +-include $(OBJS_posix32:%.o=%.d) +-include $(OBJS_posix64:%.o=%.d) +-include $(OBJS_win32:%.o=%.d) +-include $(OBJS_win64:%.o=%.d) + +# ---------------------------------------------------------------------------------------------------------------------------- diff --git a/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h b/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h new file mode 100644 index 000000000..0c6f9d214 --- /dev/null +++ b/source/modules/juce_audio_graph/buffers/juce_AudioSampleBuffer.h @@ -0,0 +1,1100 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED +#define JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED + +#include "CarlaMathUtils.hpp" + +//============================================================================== +/** + A multi-channel buffer of floating point audio samples. + + @see AudioSampleBuffer +*/ +class AudioSampleBuffer +{ +public: + //============================================================================== + /** Creates an empty buffer with 0 channels and 0 length. */ + AudioSampleBuffer() noexcept + : numChannels (0), size (0), allocatedBytes (0), + channels (static_cast (preallocatedChannelSpace)), + isClear (false) + { + } + + //============================================================================== + /** Creates a buffer with a specified number of channels and samples. + + The contents of the buffer will initially be undefined, so use clear() to + set all the samples to zero. + + The buffer will allocate its memory internally, and this will be released + when the buffer is deleted. If the memory can't be allocated, this will + throw a std::bad_alloc exception. + */ + AudioSampleBuffer (int numChannelsToAllocate, + int numSamplesToAllocate) noexcept + : numChannels (numChannelsToAllocate), + size (numSamplesToAllocate) + { + jassert (size >= 0); + jassert (numChannels >= 0); + + allocateData(); + } + + /** Creates a buffer using a pre-allocated block of memory. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param numChannelsToUse the number of channels to use - this must correspond to the + number of elements in the array passed in + @param numSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + AudioSampleBuffer (float* const* dataToReferTo, + int numChannelsToUse, + int numSamples) noexcept + : numChannels (numChannelsToUse), + size (numSamples), + allocatedBytes (0) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, 0); + } + + /** Creates a buffer using a pre-allocated block of memory. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param numChannelsToUse the number of channels to use - this must correspond to the + number of elements in the array passed in + @param startSample the offset within the arrays at which the data begins + @param numSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + AudioSampleBuffer (float* const* dataToReferTo, + int numChannelsToUse, + int startSample, + int numSamples) noexcept + : numChannels (numChannelsToUse), + size (numSamples), + allocatedBytes (0), + isClear (false) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && startSample >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, startSample); + } + + /** Copies another buffer. + + This buffer will make its own copy of the other's data, unless the buffer was created + using an external data buffer, in which case boths buffers will just point to the same + shared block of data. + */ + AudioSampleBuffer (const AudioSampleBuffer& other) noexcept + : numChannels (other.numChannels), + size (other.size), + allocatedBytes (other.allocatedBytes) + { + if (allocatedBytes == 0) + { + allocateChannels (other.channels, 0); + } + else + { + allocateData(); + + if (other.isClear) + { + clear(); + } + else + { + for (int i = 0; i < numChannels; ++i) + carla_copyFloats (channels[i], other.channels[i], size); + } + } + } + + /** Copies another buffer onto this one. + This buffer's size will be changed to that of the other buffer. + */ + AudioSampleBuffer& operator= (const AudioSampleBuffer& other) noexcept + { + if (this != &other) + { + setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); + + if (other.isClear) + { + clear(); + } + else + { + isClear = false; + + for (int i = 0; i < numChannels; ++i) + carla_copyFloats (channels[i], other.channels[i], size); + } + } + + return *this; + } + + /** Destructor. + This will free any memory allocated by the buffer. + */ + ~AudioSampleBuffer() noexcept {} + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Move constructor */ + AudioSampleBuffer (AudioSampleBuffer&& other) noexcept + : numChannels (other.numChannels), + size (other.size), + allocatedBytes (other.allocatedBytes), + channels (other.channels), + allocatedData (static_cast&&> (other.allocatedData)), + isClear (other.isClear) + { + memcpy (preallocatedChannelSpace, other.preallocatedChannelSpace, sizeof (preallocatedChannelSpace)); + other.numChannels = 0; + other.size = 0; + other.allocatedBytes = 0; + } + + /** Move assignment */ + AudioSampleBuffer& operator= (AudioSampleBuffer&& other) noexcept + { + numChannels = other.numChannels; + size = other.size; + allocatedBytes = other.allocatedBytes; + channels = other.channels; + allocatedData = static_cast&&> (other.allocatedData); + isClear = other.isClear; + memcpy (preallocatedChannelSpace, other.preallocatedChannelSpace, sizeof (preallocatedChannelSpace)); + other.numChannels = 0; + other.size = 0; + other.allocatedBytes = 0; + return *this; + } + #endif + + //============================================================================== + /** Returns the number of channels of audio data that this buffer contains. + @see getSampleData + */ + int getNumChannels() const noexcept { return numChannels; } + + /** Returns the number of samples allocated in each of the buffer's channels. + @see getSampleData + */ + int getNumSamples() const noexcept { return size; } + + /** Returns a pointer to an array of read-only samples in one of the buffer's channels. + For speed, this doesn't check whether the channel number is out of range, + so be careful when using it! + If you need to write to the data, do NOT call this method and const_cast the + result! Instead, you must call getWritePointer so that the buffer knows you're + planning on modifying the data. + */ + const float* getReadPointer (int channelNumber) const noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + return channels [channelNumber]; + } + + /** Returns a pointer to an array of read-only samples in one of the buffer's channels. + For speed, this doesn't check whether the channel number or index are out of range, + so be careful when using it! + If you need to write to the data, do NOT call this method and const_cast the + result! Instead, you must call getWritePointer so that the buffer knows you're + planning on modifying the data. + */ + const float* getReadPointer (int channelNumber, int sampleIndex) const noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + return channels [channelNumber] + sampleIndex; + } + + /** Returns a writeable pointer to one of the buffer's channels. + For speed, this doesn't check whether the channel number is out of range, + so be careful when using it! + Note that if you're not planning on writing to the data, you should always + use getReadPointer instead. + */ + float* getWritePointer (int channelNumber) noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + isClear = false; + return channels [channelNumber]; + } + + /** Returns a writeable pointer to one of the buffer's channels. + For speed, this doesn't check whether the channel number or index are out of range, + so be careful when using it! + Note that if you're not planning on writing to the data, you should + use getReadPointer instead. + */ + float* getWritePointer (int channelNumber, int sampleIndex) noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + isClear = false; + return channels [channelNumber] + sampleIndex; + } + + /** Returns an array of pointers to the channels in the buffer. + + Don't modify any of the pointers that are returned, and bear in mind that + these will become invalid if the buffer is resized. + */ + const float** getArrayOfReadPointers() const noexcept { return const_cast (channels); } + + /** Returns an array of pointers to the channels in the buffer. + + Don't modify any of the pointers that are returned, and bear in mind that + these will become invalid if the buffer is resized. + */ + float** getArrayOfWritePointers() noexcept { isClear = false; return channels; } + + //============================================================================== + /** Changes the buffer's size or number of channels. + + This can expand or contract the buffer's length, and add or remove channels. + + If keepExistingContent is true, it will try to preserve as much of the + old data as it can in the new buffer. + + If clearExtraSpace is true, then any extra channels or space that is + allocated will be also be cleared. If false, then this space is left + uninitialised. + + If avoidReallocating is true, then changing the buffer's size won't reduce the + amount of memory that is currently allocated (but it will still increase it if + the new size is bigger than the amount it currently has). If this is false, then + a new allocation will be done so that the buffer uses takes up the minimum amount + of memory that it needs. + + If the required memory can't be allocated, this will throw a std::bad_alloc exception. + */ + void setSize (int newNumChannels, + int newNumSamples, + bool keepExistingContent = false, + bool clearExtraSpace = false, + bool avoidReallocating = false) noexcept + { + jassert (newNumChannels >= 0); + jassert (newNumSamples >= 0); + + if (newNumSamples != size || newNumChannels != numChannels) + { + const size_t allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; + const size_t channelListSize = ((sizeof (float*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; + const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (float)) + + channelListSize + 32; + + if (keepExistingContent) + { + HeapBlock newData; + newData.allocate (newTotalBytes, clearExtraSpace || isClear); + + const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size); + + float** const newChannels = reinterpret_cast (newData.getData()); + float* newChan = reinterpret_cast (newData + channelListSize); + + for (int j = 0; j < newNumChannels; ++j) + { + newChannels[j] = newChan; + newChan += allocatedSamplesPerChannel; + } + + if (! isClear) + { + const int numChansToCopy = jmin (numChannels, newNumChannels); + for (int i = 0; i < numChansToCopy; ++i) + carla_copyFloats (newChannels[i], channels[i], (int) numSamplesToCopy); + } + + allocatedData.swapWith (newData); + allocatedBytes = newTotalBytes; + channels = newChannels; + } + else + { + if (avoidReallocating && allocatedBytes >= newTotalBytes) + { + if (clearExtraSpace || isClear) + allocatedData.clear (newTotalBytes); + } + else + { + allocatedBytes = newTotalBytes; + allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); + channels = reinterpret_cast (allocatedData.getData()); + } + + float* chan = reinterpret_cast (allocatedData + channelListSize); + for (int i = 0; i < newNumChannels; ++i) + { + channels[i] = chan; + chan += allocatedSamplesPerChannel; + } + } + + channels [newNumChannels] = 0; + size = newNumSamples; + numChannels = newNumChannels; + } + } + + /** Makes this buffer point to a pre-allocated set of channel data arrays. + + There's also a constructor that lets you specify arrays like this, but this + lets you change the channels dynamically. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param newNumChannels the number of channels to use - this must correspond to the + number of elements in the array passed in + @param newNumSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + void setDataToReferTo (float** dataToReferTo, + const int newNumChannels, + const int newNumSamples) noexcept + { + jassert (dataToReferTo != nullptr); + jassert (newNumChannels >= 0 && newNumSamples >= 0); + + if (allocatedBytes != 0) + { + allocatedBytes = 0; + allocatedData.free(); + } + + numChannels = newNumChannels; + size = newNumSamples; + + allocateChannels (dataToReferTo, 0); + jassert (! isClear); + } + + /** Resizes this buffer to match the given one, and copies all of its content across. + The source buffer can contain a different floating point type, so this can be used to + convert between 32 and 64 bit float buffer types. + */ + void makeCopyOf (const AudioSampleBuffer& other, bool avoidReallocating = false) + { + setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); + + if (other.hasBeenCleared()) + { + clear(); + } + else + { + for (int chan = 0; chan < numChannels; ++chan) + { + float* const dest = channels[chan]; + const float* const src = other.getReadPointer (chan); + + for (int i = 0; i < size; ++i) + dest[i] = static_cast (src[i]); + } + } + } + + //============================================================================== + /** Clears all the samples in all channels. */ + void clear() noexcept + { + if (! isClear) + { + for (int i = 0; i < numChannels; ++i) + carla_zeroFloats (channels[i], size); + + isClear = true; + } + } + + /** Clears a specified region of all the channels. + + For speed, this doesn't check whether the channel and sample number + are in-range, so be careful! + */ + void clear (int startSample, + int numSamples) noexcept + { + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + { + if (startSample == 0 && numSamples == size) + isClear = true; + + for (int i = 0; i < numChannels; ++i) + carla_zeroFloats (channels[i] + startSample, numSamples); + } + } + + /** Clears a specified region of just one channel. + + For speed, this doesn't check whether the channel and sample number + are in-range, so be careful! + */ + void clear (int channel, + int startSample, + int numSamples) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + carla_zeroFloats (channels [channel] + startSample, numSamples); + } + + /** Returns true if the buffer has been entirely cleared. + Note that this does not actually measure the contents of the buffer - it simply + returns a flag that is set when the buffer is cleared, and which is reset whenever + functions like getWritePointer() are invoked. That means the method does not take + any time, but it may return false negatives when in fact the buffer is still empty. + */ + bool hasBeenCleared() const noexcept { return isClear; } + + //============================================================================== + /** Returns a sample from the buffer. + The channel and index are not checked - they are expected to be in-range. If not, + an assertion will be thrown, but in a release build, you're into 'undefined behaviour' + territory. + */ + float getSample (int channel, int sampleIndex) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + return *(channels [channel] + sampleIndex); + } + + /** Sets a sample in the buffer. + The channel and index are not checked - they are expected to be in-range. If not, + an assertion will be thrown, but in a release build, you're into 'undefined behaviour' + territory. + */ + void setSample (int destChannel, int destSample, float newValue) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels [destChannel] + destSample) = newValue; + isClear = false; + } + + /** Adds a value to a sample in the buffer. + The channel and index are not checked - they are expected to be in-range. If not, + an assertion will be thrown, but in a release build, you're into 'undefined behaviour' + territory. + */ + void addSample (int destChannel, int destSample, float valueToAdd) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels [destChannel] + destSample) += valueToAdd; + isClear = false; + } + + /** Applies a gain multiple to a region of one channel. + + For speed, this doesn't check whether the channel and sample number + are in-range, so be careful! + */ + void applyGain (int channel, + int startSample, + int numSamples, + float gain) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (gain != 1.0f && ! isClear) + { + float* const d = channels [channel] + startSample; + + if (gain == 0.0f) + carla_zeroFloats (d, numSamples); + else + carla_multiply (d, gain, numSamples); + } + } + + /** Applies a gain multiple to a region of all the channels. + + For speed, this doesn't check whether the sample numbers + are in-range, so be careful! + */ + void applyGain (int startSample, + int numSamples, + float gain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGain (i, startSample, numSamples, gain); + } + + /** Applies a gain multiple to all the audio data. */ + void applyGain (float gain) noexcept + { + applyGain (0, size, gain); + } + + /** Applies a range of gains to a region of a channel. + + The gain that is applied to each sample will vary from + startGain on the first sample to endGain on the last Sample, + so it can be used to do basic fades. + + For speed, this doesn't check whether the sample numbers + are in-range, so be careful! + */ + void applyGainRamp (int channel, + int startSample, + int numSamples, + float startGain, + float endGain) noexcept + { + if (! isClear) + { + if (startGain == endGain) + { + applyGain (channel, startSample, numSamples, startGain); + } + else + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + const float increment = (endGain - startGain) / numSamples; + float* d = channels [channel] + startSample; + + while (--numSamples >= 0) + { + *d++ *= startGain; + startGain += increment; + } + } + } + } + + /** Applies a range of gains to a region of all channels. + + The gain that is applied to each sample will vary from + startGain on the first sample to endGain on the last Sample, + so it can be used to do basic fades. + + For speed, this doesn't check whether the sample numbers + are in-range, so be careful! + */ + void applyGainRamp (int startSample, + int numSamples, + float startGain, + float endGain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGainRamp (i, startSample, numSamples, startGain, endGain); + } + + /** Adds samples from another buffer to this one. + + @param destChannel the channel within this buffer to add the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to add from + @param sourceChannel the channel within the source buffer to read from + @param sourceStartSample the offset within the source buffer's channel to start reading samples from + @param numSamples the number of samples to process + @param gainToApplyToSource an optional gain to apply to the source samples before they are + added to this buffer's samples + + @see copyFrom + */ + void addFrom (int destChannel, + int destStartSample, + const AudioSampleBuffer& source, + int sourceChannel, + int sourceStartSample, + int numSamples, + float gainToApplyToSource = (float) 1) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (gainToApplyToSource != 0.0f && numSamples > 0 && ! source.isClear) + { + float* const d = channels [destChannel] + destStartSample; + const float* const s = source.channels [sourceChannel] + sourceStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != 1.0f) + carla_copyWithMultiply (d, s, gainToApplyToSource, numSamples); + else + carla_copyFloats (d, s, numSamples); + } + else + { + if (gainToApplyToSource != 1.0f) + carla_addWithMultiply (d, s, gainToApplyToSource, numSamples); + else + carla_add (d, s, numSamples); + } + } + } + + + /** Adds samples from an array of floats to one of the channels. + + @param destChannel the channel within this buffer to add the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source data to use + @param numSamples the number of samples to process + @param gainToApplyToSource an optional gain to apply to the source samples before they are + added to this buffer's samples + + @see copyFrom + */ + void addFrom (int destChannel, + int destStartSample, + const float* source, + int numSamples, + float gainToApplyToSource = (float) 1) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (gainToApplyToSource != 0.0f && numSamples > 0) + { + float* const d = channels [destChannel] + destStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != 1.0f) + carla_copyWithMultiply (d, source, gainToApplyToSource, numSamples); + else + carla_copyFloats (d, source, numSamples); + } + else + { + if (gainToApplyToSource != 1.0f) + carla_addWithMultiply (d, source, gainToApplyToSource, numSamples); + else + carla_add (d, source, numSamples); + } + } + } + + + /** Adds samples from an array of floats, applying a gain ramp to them. + + @param destChannel the channel within this buffer to add the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source data to use + @param numSamples the number of samples to process + @param startGain the gain to apply to the first sample (this is multiplied with + the source samples before they are added to this buffer) + @param endGain the gain to apply to the final sample. The gain is linearly + interpolated between the first and last samples. + */ + void addFromWithRamp (int destChannel, + int destStartSample, + const float* source, + int numSamples, + float startGain, + float endGain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (startGain == endGain) + { + addFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) + { + isClear = false; + const float increment = (endGain - startGain) / numSamples; + float* d = channels [destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ += startGain * *source++; + startGain += increment; + } + } + } + } + + /** Copies samples from another buffer to this one. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param sourceChannel the channel within the source buffer to read from + @param sourceStartSample the offset within the source buffer's channel to start reading samples from + @param numSamples the number of samples to process + + @see addFrom + */ + void copyFrom (int destChannel, + int destStartSample, + const AudioSampleBuffer& source, + int sourceChannel, + int sourceStartSample, + int numSamples) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (numSamples > 0) + { + if (source.isClear) + { + if (! isClear) + carla_zeroFloats (channels [destChannel] + destStartSample, numSamples); + } + else + { + isClear = false; + carla_copyFloats (channels [destChannel] + destStartSample, + source.channels [sourceChannel] + sourceStartSample, + numSamples); + } + } + } + + /** Copies samples from an array of floats into one of the channels. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param numSamples the number of samples to process + + @see addFrom + */ + void copyFrom (int destChannel, + int destStartSample, + const float* source, + int numSamples) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + isClear = false; + carla_copyFloats (channels [destChannel] + destStartSample, source, numSamples); + } + } + + /** Copies samples from an array of floats into one of the channels, applying a gain to it. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param numSamples the number of samples to process + @param gain the gain to apply + + @see addFrom + */ + void copyFrom (int destChannel, + int destStartSample, + const float* source, + int numSamples, + float gain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + float* const d = channels [destChannel] + destStartSample; + + if (gain != 1.0f) + { + if (gain == 0) + { + if (! isClear) + carla_zeroFloats (d, numSamples); + } + else + { + isClear = false; + carla_copyWithMultiply (d, source, gain, numSamples); + } + } + else + { + isClear = false; + carla_copyFloats (d, source, numSamples); + } + } + } + + /** Copies samples from an array of floats into one of the channels, applying a gain ramp. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param numSamples the number of samples to process + @param startGain the gain to apply to the first sample (this is multiplied with + the source samples before they are copied to this buffer) + @param endGain the gain to apply to the final sample. The gain is linearly + interpolated between the first and last samples. + + @see addFrom + */ + void copyFromWithRamp (int destChannel, + int destStartSample, + const float* source, + int numSamples, + float startGain, + float endGain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (startGain == endGain) + { + copyFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) + { + isClear = false; + const float increment = (endGain - startGain) / numSamples; + float* d = channels [destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ = startGain * *source++; + startGain += increment; + } + } + } + } + +#if 0 + /** Returns a Range indicating the lowest and highest sample values in a given section. + + @param channel the channel to read from + @param startSample the start sample within the channel + @param numSamples the number of samples to check + */ + Range findMinMax (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (isClear) + return Range(); + + return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); + } + + + /** Finds the highest absolute sample value within a region of a channel. */ + float getMagnitude (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (isClear) + return 0.0f; + + const Range r (findMinMax (channel, startSample, numSamples)); + + return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); + } + + /** Finds the highest absolute sample value within a region on all channels. */ + float getMagnitude (int startSample, + int numSamples) const noexcept + { + float mag = 0.0f; + + if (! isClear) + for (int i = 0; i < numChannels; ++i) + mag = jmax (mag, getMagnitude (i, startSample, numSamples)); + + return mag; + } + + /** Returns the root mean squared level for a region of a channel. */ + float getRMSLevel (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) + return 0.0f; + + const float* const data = channels [channel] + startSample; + double sum = 0.0; + + for (int i = 0; i < numSamples; ++i) + { + const float sample = data [i]; + sum += sample * sample; + } + + return (float) std::sqrt (sum / numSamples); + } + + /** Reverses a part of a channel. */ + void reverse (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + std::reverse (channels[channel] + startSample, + channels[channel] + startSample + numSamples); + } + + /** Reverses a part of the buffer. */ + void reverse (int startSample, int numSamples) const noexcept + { + for (int i = 0; i < numChannels; ++i) + reverse (i, startSample, numSamples); + } +#endif + +private: + //============================================================================== + int numChannels, size; + size_t allocatedBytes; + float** channels; + HeapBlock allocatedData; + float* preallocatedChannelSpace [32]; + bool isClear; + + void allocateData() + { + const size_t channelListSize = sizeof (float*) * (size_t) (numChannels + 1); + allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (float) + channelListSize + 32; + allocatedData.malloc (allocatedBytes); + channels = reinterpret_cast (allocatedData.getData()); + + float* chan = (float*) (allocatedData + channelListSize); + for (int i = 0; i < numChannels; ++i) + { + channels[i] = chan; + chan += size; + } + + channels [numChannels] = nullptr; + isClear = false; + } + + void allocateChannels (float* const* const dataToReferTo, int offset) + { + jassert (offset >= 0); + + // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) + if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) + { + channels = static_cast (preallocatedChannelSpace); + } + else + { + allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*)); + channels = reinterpret_cast (allocatedData.getData()); + } + + for (int i = 0; i < numChannels; ++i) + { + // you have to pass in the same number of valid pointers as numChannels + jassert (dataToReferTo[i] != nullptr); + + channels[i] = dataToReferTo[i] + offset; + } + + channels [numChannels] = nullptr; + isClear = false; + } + + JUCE_LEAK_DETECTOR (AudioSampleBuffer) +}; + + +#endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_Array.h b/source/modules/juce_audio_graph/containers/juce_Array.h new file mode 100644 index 000000000..5abebd6c8 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_Array.h @@ -0,0 +1,1155 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ARRAY_H_INCLUDED +#define JUCE_ARRAY_H_INCLUDED + + +//============================================================================== +/** + Holds a resizable array of primitive or copy-by-value objects. + + Examples of arrays are: Array, Array or Array + + The Array class can be used to hold simple, non-polymorphic objects as well as primitive types - to + do so, the class must fulfil these requirements: + - it must have a copy constructor and assignment operator + - it must be able to be relocated in memory by a memcpy without this causing any problems - so + objects whose functionality relies on external pointers or references to themselves can not be used. + + You can of course have an array of pointers to any kind of object, e.g. Array, but if + you do this, the array doesn't take any ownership of the objects - see the OwnedArray class or the + ReferenceCountedArray class for more powerful ways of holding lists of objects. + + For holding lists of strings, you can use Array\, but it's usually better to use the + specialised class StringArray, which provides more useful functions. + + @see OwnedArray, ReferenceCountedArray, StringArray, CriticalSection +*/ +template +class Array +{ +private: + typedef PARAMETER_TYPE (ElementType) ParameterType; + +public: + //============================================================================== + /** Creates an empty array. */ + Array() noexcept : numUsed (0) + { + } + + /** Creates a copy of another array. + @param other the array to copy + */ + Array (const Array& other) + { + numUsed = other.numUsed; + data.setAllocatedSize (other.numUsed); + + for (int i = 0; i < numUsed; ++i) + new (data.elements + i) ElementType (other.data.elements[i]); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Array (Array&& other) noexcept + : data (static_cast&&> (other.data)), + numUsed (other.numUsed) + { + other.numUsed = 0; + } + #endif + + /** Initalises from a null-terminated C array of values. + + @param values the array to copy from + */ + template + explicit Array (const TypeToCreateFrom* values) : numUsed (0) + { + while (*values != TypeToCreateFrom()) + add (*values++); + } + + /** Initalises from a C array of values. + + @param values the array to copy from + @param numValues the number of values in the array + */ + template + Array (const TypeToCreateFrom* values, int numValues) : numUsed (numValues) + { + data.setAllocatedSize (numValues); + + for (int i = 0; i < numValues; ++i) + new (data.elements + i) ElementType (values[i]); + } + + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + template + Array (const std::initializer_list& items) : numUsed (0) + { + addArray (items); + } + #endif + + /** Destructor. */ + ~Array() + { + deleteAllElements(); + } + + /** Copies another array. + @param other the array to copy + */ + Array& operator= (const Array& other) + { + if (this != &other) + { + Array otherCopy (other); + swapWith (otherCopy); + } + + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Array& operator= (Array&& other) noexcept + { + deleteAllElements(); + data = static_cast&&> (other.data); + numUsed = other.numUsed; + other.numUsed = 0; + return *this; + } + #endif + + //============================================================================== + /** Compares this array to another one. + Two arrays are considered equal if they both contain the same set of + elements, in the same order. + @param other the other array to compare with + */ + template + bool operator== (const OtherArrayType& other) const + { + if (numUsed != other.numUsed) + return false; + + for (int i = numUsed; --i >= 0;) + if (! (data.elements [i] == other.data.elements [i])) + return false; + + return true; + } + + /** Compares this array to another one. + Two arrays are considered equal if they both contain the same set of + elements, in the same order. + @param other the other array to compare with + */ + template + bool operator!= (const OtherArrayType& other) const + { + return ! operator== (other); + } + + //============================================================================== + /** Removes all elements from the array. + This will remove all the elements, and free any storage that the array is + using. To clear the array without freeing the storage, use the clearQuick() + method instead. + + @see clearQuick + */ + void clear() + { + deleteAllElements(); + data.setAllocatedSize (0); + numUsed = 0; + } + + /** Removes all elements from the array without freeing the array's allocated storage. + @see clear + */ + void clearQuick() + { + deleteAllElements(); + numUsed = 0; + } + + //============================================================================== + /** Returns the current number of elements in the array. */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns true if the array is empty, false otherwise. */ + inline bool isEmpty() const noexcept + { + return size() == 0; + } + + /** Returns one of the elements in the array. + If the index passed in is beyond the range of valid elements, this + will return a default value. + + If you're certain that the index will always be a valid element, you + can call getUnchecked() instead, which is faster. + + @param index the index of the element being requested (0 is the first element in the array) + @see getUnchecked, getFirst, getLast + */ + ElementType operator[] (const int index) const + { + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return ElementType(); + } + + /** Returns one of the elements in the array, without checking the index passed in. + + Unlike the operator[] method, this will try to return an element without + checking that the index is within the bounds of the array, so should only + be used when you're confident that it will always be a valid index. + + @param index the index of the element being requested (0 is the first element in the array) + @see operator[], getFirst, getLast + */ + inline ElementType getUnchecked (const int index) const + { + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns a direct reference to one of the elements in the array, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + @see operator[], getFirst, getLast + */ + inline ElementType& getReference (const int index) const noexcept + { + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns the first element in the array, or a default value if the array is empty. + + @see operator[], getUnchecked, getLast + */ + inline ElementType getFirst() const + { + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements[0]; + } + + return ElementType(); + } + + /** Returns the last element in the array, or a default value if the array is empty. + + @see operator[], getUnchecked, getFirst + */ + inline ElementType getLast() const + { + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements[numUsed - 1]; + } + + return ElementType(); + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ElementType* getRawDataPointer() noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* end() const noexcept + { + #if JUCE_DEBUG + if (data.elements == nullptr || numUsed <= 0) // (to keep static analysers happy) + return data.elements; + #endif + + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of the first element which matches the value passed in. + + This will search the array for the given object, and return the index + of its first occurrence. If the object isn't found, the method will return -1. + + @param elementToLookFor the value or object to look for + @returns the index of the object, or -1 if it's not found + */ + int indexOf (ParameterType elementToLookFor) const + { + const ElementType* e = data.elements.getData(); + const ElementType* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (elementToLookFor == *e) + return static_cast (e - data.elements.getData()); + + return -1; + } + + /** Returns true if the array contains at least one occurrence of an object. + + @param elementToLookFor the value or object to look for + @returns true if the item is found + */ + bool contains (ParameterType elementToLookFor) const + { + const ElementType* e = data.elements.getData(); + const ElementType* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (elementToLookFor == *e) + return true; + + return false; + } + + //============================================================================== + /** Appends a new element at the end of the array. + + @param newElement the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addUsingDefaultSort, addArray + */ + void add (const ElementType& newElement) + { + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (newElement); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Appends a new element at the end of the array. + + @param newElement the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addUsingDefaultSort, addArray + */ + void add (ElementType&& newElement) + { + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (static_cast (newElement)); + } + #endif + + /** Inserts a new element into the array at a given position. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the new element should be + inserted (pass in -1 to add it to the end) + @param newElement the new object to add to the array + @see add, addSorted, addUsingDefaultSort, set + */ + void insert (int indexToInsertAt, ParameterType newElement) + { + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + ElementType* const insertPos = data.elements + indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + + if (numberToMove > 0) + memmove (insertPos + 1, insertPos, ((size_t) numberToMove) * sizeof (ElementType)); + + new (insertPos) ElementType (newElement); + ++numUsed; + } + else + { + new (data.elements + numUsed++) ElementType (newElement); + } + } + + /** Inserts multiple copies of an element into the array at a given position. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the new element should be inserted + @param newElement the new object to add to the array + @param numberOfTimesToInsertIt how many copies of the value to insert + @see insert, add, addSorted, set + */ + void insertMultiple (int indexToInsertAt, ParameterType newElement, + int numberOfTimesToInsertIt) + { + if (numberOfTimesToInsertIt > 0) + { + data.ensureAllocatedSize (numUsed + numberOfTimesToInsertIt); + ElementType* insertPos; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos = data.elements + indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + memmove (insertPos + numberOfTimesToInsertIt, insertPos, ((size_t) numberToMove) * sizeof (ElementType)); + } + else + { + insertPos = data.elements + numUsed; + } + + numUsed += numberOfTimesToInsertIt; + + while (--numberOfTimesToInsertIt >= 0) + { + new (insertPos) ElementType (newElement); + ++insertPos; // NB: this increment is done separately from the + // new statement to avoid a compiler bug in VS2014 + } + } + } + + /** Inserts an array of values into this array at a given position. + + If the index is less than 0 or greater than the size of the array, the + new elements will be added to the end of the array. + Otherwise, they will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the first new element should be inserted + @param newElements the new values to add to the array + @param numberOfElements how many items are in the array + @see insert, add, addSorted, set + */ + void insertArray (int indexToInsertAt, + const ElementType* newElements, + int numberOfElements) + { + if (numberOfElements > 0) + { + data.ensureAllocatedSize (numUsed + numberOfElements); + ElementType* insertPos = data.elements; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos += indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + memmove (insertPos + numberOfElements, insertPos, (size_t) numberToMove * sizeof (ElementType)); + } + else + { + insertPos += numUsed; + } + + numUsed += numberOfElements; + + while (--numberOfElements >= 0) + new (insertPos++) ElementType (*newElements++); + } + } + + /** Appends a new element at the end of the array as long as the array doesn't + already contain it. + + If the array already contains an element that matches the one passed in, nothing + will be done. + + @param newElement the new object to add to the array + @return true if the element was added to the array; false otherwise. + */ + bool addIfNotAlreadyThere (ParameterType newElement) + { + if (contains (newElement)) + return false; + + add (newElement); + return true; + } + + /** Replaces an element with a new value. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the item is added to the end of the array. + + @param indexToChange the index whose value you want to change + @param newValue the new value to set for this index. + @see add, insert + */ + void set (const int indexToChange, ParameterType newValue) + { + jassert (indexToChange >= 0); + + if (isPositiveAndBelow (indexToChange, numUsed)) + { + jassert (data.elements != nullptr); + data.elements [indexToChange] = newValue; + } + else if (indexToChange >= 0) + { + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (newValue); + } + } + + /** Replaces an element with a new value without doing any bounds-checking. + + This just sets a value directly in the array's internal storage, so you'd + better make sure it's in range! + + @param indexToChange the index whose value you want to change + @param newValue the new value to set for this index. + @see set, getUnchecked + */ + void setUnchecked (const int indexToChange, ParameterType newValue) + { + jassert (isPositiveAndBelow (indexToChange, numUsed)); + data.elements [indexToChange] = newValue; + } + + /** Adds elements from an array to the end of this array. + + @param elementsToAdd an array of some kind of object from which elements + can be constructed. + @param numElementsToAdd how many elements are in this other array + @see add + */ + template + void addArray (const Type* elementsToAdd, int numElementsToAdd) + { + if (numElementsToAdd > 0) + { + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + { + new (data.elements + numUsed) ElementType (*elementsToAdd++); + ++numUsed; + } + } + } + + #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS + template + void addArray (const std::initializer_list& items) + { + data.ensureAllocatedSize (numUsed + (int) items.size()); + + for (auto& item : items) + { + new (data.elements + numUsed) ElementType (item); + ++numUsed; + } + } + #endif + + /** Adds elements from a null-terminated array of pointers to the end of this array. + + @param elementsToAdd an array of pointers to some kind of object from which elements + can be constructed. This array must be terminated by a nullptr + @see addArray + */ + template + void addNullTerminatedArray (const Type* const* elementsToAdd) + { + int num = 0; + for (const Type* const* e = elementsToAdd; *e != nullptr; ++e) + ++num; + + addArray (elementsToAdd, num); + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addArray (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + while (--numElementsToAdd >= 0) + add (arrayToAddFrom.getUnchecked (startIndex++)); + } + + /** This will enlarge or shrink the array to the given number of elements, by adding + or removing items from its end. + + If the array is smaller than the given target size, empty elements will be appended + until its size is as specified. If its size is larger than the target, items will be + removed from its end to shorten it. + */ + void resize (const int targetNumItems) + { + jassert (targetNumItems >= 0); + + const int numToAdd = targetNumItems - numUsed; + if (numToAdd > 0) + insertMultiple (numUsed, ElementType(), numToAdd); + else if (numToAdd < 0) + removeRange (targetNumItems, -numToAdd); + } + + /** Inserts a new element into the array, assuming that the array is sorted. + + This will use a comparator to find the position at which the new element + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param newElement the new element to insert to the array + @returns the index at which the new item was added + @see addUsingDefaultSort, add, sort + */ + template + int addSorted (ElementComparator& comparator, ParameterType newElement) + { + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newElement, 0, numUsed); + insert (index, newElement); + return index; + } + + /** Inserts a new element into the array, assuming that the array is sorted. + + This will use the DefaultElementComparator class for sorting, so your ElementType + must be suitable for use with that class. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param newElement the new element to insert to the array + @see addSorted, sort + */ + void addUsingDefaultSort (ParameterType newElement) + { + DefaultElementComparator comparator; + addSorted (comparator, newElement); + } + + /** Finds the index of an element in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param elementToLookFor the element to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, TargetValueType elementToLookFor) const + { + ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + for (int s = 0, e = numUsed;;) + { + if (s >= e) + return -1; + + if (comparator.compareElements (elementToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + return -1; + + if (comparator.compareElements (elementToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + } + + //============================================================================== + /** Removes an element from the array. + + This will remove the element at a given index, and move back + all the subsequent elements to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @see removeAndReturn, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + void remove (int indexToRemove) + { + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + jassert (data.elements != nullptr); + removeInternal (indexToRemove); + } + } + + /** Removes an element from the array. + + This will remove the element at a given index, and move back + all the subsequent elements to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @returns the element that has been removed + @see removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + ElementType removeAndReturn (const int indexToRemove) + { + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + jassert (data.elements != nullptr); + ElementType removed (data.elements[indexToRemove]); + removeInternal (indexToRemove); + return removed; + } + + return ElementType(); + } + + /** Removes an element from the array. + + This will remove the element pointed to by the given iterator, + and move back all the subsequent elements to close the gap. + If the iterator passed in does not point to an element within the + array, behaviour is undefined. + + @param elementToRemove a pointer to the element to remove + @see removeFirstMatchingValue, removeAllInstancesOf, removeRange, removeIf + */ + void remove (const ElementType* elementToRemove) + { + jassert (elementToRemove != nullptr); + jassert (data.elements != nullptr); + const int indexToRemove = int (elementToRemove - data.elements); + + if (! isPositiveAndBelow (indexToRemove, numUsed)) + { + jassertfalse; + return; + } + + removeInternal (indexToRemove); + } + + /** Removes an item from the array. + + This will remove the first occurrence of the given element from the array. + If the item isn't found, no action is taken. + + @param valueToRemove the object to try to remove + @see remove, removeRange, removeIf + */ + void removeFirstMatchingValue (ParameterType valueToRemove) + { + ElementType* const e = data.elements; + + for (int i = 0; i < numUsed; ++i) + { + if (valueToRemove == e[i]) + { + removeInternal (i); + break; + } + } + } + + /** Removes items from the array. + + This will remove all occurrences of the given element from the array. + If no such items are found, no action is taken. + + @param valueToRemove the object to try to remove + @return how many objects were removed. + @see remove, removeRange, removeIf + */ + int removeAllInstancesOf (ParameterType valueToRemove) + { + int numRemoved = 0; + + for (int i = numUsed; --i >= 0;) + { + if (valueToRemove == data.elements[i]) + { + removeInternal (i); + ++numRemoved; + } + } + + return numRemoved; + } + + /** Removes items from the array. + + This will remove all objects from the array that match a condition. + If no such items are found, no action is taken. + + @param predicate the condition when to remove an item. Must be a callable + type that takes an ElementType and returns a bool + + @return how many objects were removed. + @see remove, removeRange, removeAllInstancesOf + */ + template + int removeIf (PredicateType predicate) + { + int numRemoved = 0; + + for (int i = numUsed; --i >= 0;) + { + if (predicate (data.elements[i]) == true) + { + removeInternal (i); + ++numRemoved; + } + } + + return numRemoved; + } + + /** Removes a range of elements from the array. + + This will remove a set of elements, starting from the given index, + and move subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first element to remove + @param numberToRemove how many elements should be removed + @see remove, removeFirstMatchingValue, removeAllInstancesOf, removeIf + */ + void removeRange (int startIndex, int numberToRemove) + { + const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); + startIndex = jlimit (0, numUsed, startIndex); + + if (endIndex > startIndex) + { + ElementType* const e = data.elements + startIndex; + + numberToRemove = endIndex - startIndex; + for (int i = 0; i < numberToRemove; ++i) + e[i].~ElementType(); + + const int numToShift = numUsed - endIndex; + if (numToShift > 0) + memmove (e, e + numberToRemove, ((size_t) numToShift) * sizeof (ElementType)); + + numUsed -= numberToRemove; + minimiseStorageAfterRemoval(); + } + } + + /** Removes the last n elements from the array. + + @param howManyToRemove how many elements to remove from the end of the array + @see remove, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + void removeLast (int howManyToRemove = 1) + { + if (howManyToRemove > numUsed) + howManyToRemove = numUsed; + + for (int i = 1; i <= howManyToRemove; ++i) + data.elements [numUsed - i].~ElementType(); + + numUsed -= howManyToRemove; + minimiseStorageAfterRemoval(); + } + + /** Removes any elements which are also in another array. + + @param otherArray the other array in which to look for elements to remove + @see removeValuesNotIn, remove, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + template + void removeValuesIn (const OtherArrayType& otherArray) + { + if (this == &otherArray) + { + clear(); + } + else + { + if (otherArray.size() > 0) + { + for (int i = numUsed; --i >= 0;) + if (otherArray.contains (data.elements [i])) + removeInternal (i); + } + } + } + + /** Removes any elements which are not found in another array. + + Only elements which occur in this other array will be retained. + + @param otherArray the array in which to look for elements NOT to remove + @see removeValuesIn, remove, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + template + void removeValuesNotIn (const OtherArrayType& otherArray) + { + if (this != &otherArray) + { + if (otherArray.size() <= 0) + { + clear(); + } + else + { + for (int i = numUsed; --i >= 0;) + if (! otherArray.contains (data.elements [i])) + removeInternal (i); + } + } + } + + /** Swaps over two elements in the array. + + This swaps over the elements found at the two indexes passed in. + If either index is out-of-range, this method will do nothing. + + @param index1 index of one of the elements to swap + @param index2 index of the other element to swap + */ + void swap (const int index1, + const int index2) + { + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the values to a different position. + + This will move the value to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the value to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this value to end up. If this + is less than zero, the value will be moved to the end + of the array + */ + void move (const int currentIndex, int newIndex) noexcept + { + if (currentIndex != newIndex) + { + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + char tempCopy [sizeof (ElementType)]; + memcpy (tempCopy, data.elements + currentIndex, sizeof (ElementType)); + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ElementType) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ElementType) * (size_t) (currentIndex - newIndex)); + } + + memcpy (data.elements + newIndex, tempCopy, sizeof (ElementType)); + } + } + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() + { + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Sorts the array using a default comparison operation. + If the type of your elements isn't supported by the DefaultElementComparator class + then you may need to use the other version of sort, which takes a custom comparator. + */ + void sort() + { + DefaultElementComparator comparator; + sort (comparator); + } + + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + + @see addSorted, indexOfSorted, sortArray + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) + { + ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void removeInternal (const int indexToRemove) + { + --numUsed; + ElementType* const e = data.elements + indexToRemove; + e->~ElementType(); + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, ((size_t) numberToShift) * sizeof (ElementType)); + + minimiseStorageAfterRemoval(); + } + + inline void deleteAllElements() noexcept + { + for (int i = 0; i < numUsed; ++i) + data.elements[i].~ElementType(); + } + + void minimiseStorageAfterRemoval() + { + if (data.numAllocated > jmax (minimumAllocatedSize, numUsed * 2)) + data.shrinkToNoMoreThan (jmax (numUsed, jmax (minimumAllocatedSize, 64 / (int) sizeof (ElementType)))); + } +}; + + +#endif // JUCE_ARRAY_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_ArrayAllocationBase.h b/source/modules/juce_audio_graph/containers/juce_ArrayAllocationBase.h new file mode 100644 index 000000000..8bf244c9d --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_ArrayAllocationBase.h @@ -0,0 +1,140 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ARRAYALLOCATIONBASE_H_INCLUDED +#define JUCE_ARRAYALLOCATIONBASE_H_INCLUDED + + +//============================================================================== +/** + Implements some basic array storage allocation functions. + + This class isn't really for public use - it's used by the other + array classes, but might come in handy for some purposes. + + It inherits from a critical section class to allow the arrays to use + the "empty base class optimisation" pattern to reduce their footprint. + + @see Array, OwnedArray, ReferenceCountedArray +*/ +template +class ArrayAllocationBase +{ +public: + //============================================================================== + /** Creates an empty array. */ + ArrayAllocationBase() noexcept + : numAllocated (0) + { + } + + /** Destructor. */ + ~ArrayAllocationBase() noexcept + { + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + ArrayAllocationBase (ArrayAllocationBase&& other) noexcept + : elements (static_cast&&> (other.elements)), + numAllocated (other.numAllocated) + { + } + + ArrayAllocationBase& operator= (ArrayAllocationBase&& other) noexcept + { + elements = static_cast&&> (other.elements); + numAllocated = other.numAllocated; + return *this; + } + #endif + + //============================================================================== + /** Changes the amount of storage allocated. + + This will retain any data currently held in the array, and either add or + remove extra space at the end. + + @param numElements the number of elements that are needed + */ + void setAllocatedSize (const int numElements) + { + if (numAllocated != numElements) + { + if (numElements > 0) + elements.realloc ((size_t) numElements); + else + elements.free(); + + numAllocated = numElements; + } + } + + /** Increases the amount of storage allocated if it is less than a given amount. + + This will retain any data currently held in the array, but will add + extra space at the end to make sure there it's at least as big as the size + passed in. If it's already bigger, no action is taken. + + @param minNumElements the minimum number of elements that are needed + */ + void ensureAllocatedSize (const int minNumElements) + { + if (minNumElements > numAllocated) + setAllocatedSize ((minNumElements + minNumElements / 2 + 8) & ~7); + + jassert (numAllocated <= 0 || elements != nullptr); + } + + /** Minimises the amount of storage allocated so that it's no more than + the given number of elements. + */ + void shrinkToNoMoreThan (const int maxNumElements) + { + if (maxNumElements < numAllocated) + setAllocatedSize (maxNumElements); + } + + /** Swap the contents of two objects. */ + void swapWith (ArrayAllocationBase & other) noexcept + { + elements.swapWith (other.elements); + std::swap (numAllocated, other.numAllocated); + } + + //============================================================================== + HeapBlock elements; + int numAllocated; + +private: + JUCE_DECLARE_NON_COPYABLE (ArrayAllocationBase) +}; + + +#endif // JUCE_ARRAYALLOCATIONBASE_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_ElementComparator.h b/source/modules/juce_audio_graph/containers/juce_ElementComparator.h new file mode 100644 index 000000000..5cfd1f028 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_ElementComparator.h @@ -0,0 +1,197 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ELEMENTCOMPARATOR_H_INCLUDED +#define JUCE_ELEMENTCOMPARATOR_H_INCLUDED + +#ifndef DOXYGEN + +/** This is an internal helper class which converts a juce ElementComparator style + class (using a "compareElements" method) into a class that's compatible with + std::sort (i.e. using an operator() to compare the elements) +*/ +template +struct SortFunctionConverter +{ + SortFunctionConverter (ElementComparator& e) : comparator (e) {} + + template + bool operator() (Type a, Type b) { return comparator.compareElements (a, b) < 0; } + +private: + ElementComparator& comparator; + SortFunctionConverter& operator= (const SortFunctionConverter&) JUCE_DELETED_FUNCTION; +}; + +#endif + + +//============================================================================== +/** + Sorts a range of elements in an array. + + The comparator object that is passed-in must define a public method with the following + signature: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator an object which defines a compareElements() method + @param array the array to sort + @param firstElement the index of the first element of the range to be sorted + @param lastElement the index of the last element in the range that needs + sorting (this is inclusive) + @param retainOrderOfEquivalentItems if true, the order of items that the + comparator deems the same will be maintained - this will be + a slower algorithm than if they are allowed to be moved around. + + @see sortArrayRetainingOrder +*/ +template +static void sortArray (ElementComparator& comparator, + ElementType* const array, + int firstElement, + int lastElement, + const bool retainOrderOfEquivalentItems) +{ + SortFunctionConverter converter (comparator); + + if (retainOrderOfEquivalentItems) + std::stable_sort (array + firstElement, array + lastElement + 1, converter); + else + std::sort (array + firstElement, array + lastElement + 1, converter); +} + + +//============================================================================== +/** + Searches a sorted array of elements, looking for the index at which a specified value + should be inserted for it to be in the correct order. + + The comparator object that is passed-in must define a public method with the following + signature: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator an object which defines a compareElements() method + @param array the array to search + @param newElement the value that is going to be inserted + @param firstElement the index of the first element to search + @param lastElement the index of the last element in the range (this is non-inclusive) +*/ +template +static int findInsertIndexInSortedArray (ElementComparator& comparator, + ElementType* const array, + const ElementType newElement, + int firstElement, + int lastElement) +{ + jassert (firstElement <= lastElement); + + ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + while (firstElement < lastElement) + { + if (comparator.compareElements (newElement, array [firstElement]) == 0) + { + ++firstElement; + break; + } + else + { + const int halfway = (firstElement + lastElement) >> 1; + + if (halfway == firstElement) + { + if (comparator.compareElements (newElement, array [halfway]) >= 0) + ++firstElement; + + break; + } + else if (comparator.compareElements (newElement, array [halfway]) >= 0) + { + firstElement = halfway; + } + else + { + lastElement = halfway; + } + } + } + + return firstElement; +} + +//============================================================================== +/** + A simple ElementComparator class that can be used to sort an array of + objects that support the '<' operator. + + This will work for primitive types and objects that implement operator<(). + + Example: @code + Array myArray; + DefaultElementComparator sorter; + myArray.sort (sorter); + @endcode + + @see ElementComparator +*/ +template +class DefaultElementComparator +{ +private: + typedef PARAMETER_TYPE (ElementType) ParameterType; + +public: + static int compareElements (ParameterType first, ParameterType second) + { + return (first < second) ? -1 : ((second < first) ? 1 : 0); + } +}; + + +#endif // JUCE_ELEMENTCOMPARATOR_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_NamedValueSet.cpp b/source/modules/juce_audio_graph/containers/juce_NamedValueSet.cpp new file mode 100644 index 000000000..e53943793 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_NamedValueSet.cpp @@ -0,0 +1,255 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +//============================================================================== +NamedValueSet::NamedValueSet() noexcept +{ +} + +NamedValueSet::NamedValueSet (const NamedValueSet& other) + : values (other.values) +{ +} + +NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) +{ + clear(); + values = other.values; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept + : values (static_cast&&> (other.values)) +{ +} + +NamedValueSet& NamedValueSet::operator= (NamedValueSet&& other) noexcept +{ + other.values.swapWith (values); + return *this; +} +#endif + +NamedValueSet::~NamedValueSet() noexcept +{ +} + +void NamedValueSet::clear() +{ + values.clear(); +} + +bool NamedValueSet::operator== (const NamedValueSet& other) const +{ + return values == other.values; +} + +bool NamedValueSet::operator!= (const NamedValueSet& other) const +{ + return ! operator== (other); +} + +int NamedValueSet::size() const noexcept +{ + return values.size(); +} + +bool NamedValueSet::isEmpty() const noexcept +{ + return values.isEmpty(); +} + +static const var& getNullVarRef() noexcept +{ + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + return var::null; + #else + static var nullVar; + return nullVar; + #endif +} + +const var& NamedValueSet::operator[] (const Identifier& name) const noexcept +{ + if (const var* v = getVarPointer (name)) + return *v; + + return getNullVarRef(); +} + +var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const +{ + if (const var* const v = getVarPointer (name)) + return *v; + + return defaultReturnValue; +} + +var* NamedValueSet::getVarPointer (const Identifier& name) const noexcept +{ + for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) + if (i->name == name) + return &(i->value); + + return nullptr; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +bool NamedValueSet::set (const Identifier& name, var&& newValue) +{ + if (var* const v = getVarPointer (name)) + { + if (v->equalsWithSameType (newValue)) + return false; + + *v = static_cast (newValue); + return true; + } + + values.add (NamedValue (name, static_cast (newValue))); + return true; +} +#endif + +bool NamedValueSet::set (const Identifier& name, const var& newValue) +{ + if (var* const v = getVarPointer (name)) + { + if (v->equalsWithSameType (newValue)) + return false; + + *v = newValue; + return true; + } + + values.add (NamedValue (name, newValue)); + return true; +} + +bool NamedValueSet::contains (const Identifier& name) const noexcept +{ + return getVarPointer (name) != nullptr; +} + +int NamedValueSet::indexOf (const Identifier& name) const noexcept +{ + const int numValues = values.size(); + + for (int i = 0; i < numValues; ++i) + if (values.getReference(i).name == name) + return i; + + return -1; +} + +bool NamedValueSet::remove (const Identifier& name) +{ + const int numValues = values.size(); + + for (int i = 0; i < numValues; ++i) + { + if (values.getReference(i).name == name) + { + values.remove (i); + return true; + } + } + + return false; +} + +Identifier NamedValueSet::getName (const int index) const noexcept +{ + if (isPositiveAndBelow (index, values.size())) + return values.getReference (index).name; + + jassertfalse; + return Identifier(); +} + +const var& NamedValueSet::getValueAt (const int index) const noexcept +{ + if (isPositiveAndBelow (index, values.size())) + return values.getReference (index).value; + + jassertfalse; + return getNullVarRef(); +} + +var* NamedValueSet::getVarPointerAt (int index) const noexcept +{ + if (isPositiveAndBelow (index, values.size())) + return &(values.getReference (index).value); + + return nullptr; +} + +void NamedValueSet::setFromXmlAttributes (const XmlElement& xml) +{ + values.clearQuick(); + + for (const XmlElement::XmlAttributeNode* att = xml.attributes; att != nullptr; att = att->nextListItem) + { + if (att->name.toString().startsWith ("base64:")) + { + MemoryBlock mb; + + if (mb.fromBase64Encoding (att->value)) + { + values.add (NamedValue (att->name.toString().substring (7), var (mb))); + continue; + } + } + + values.add (NamedValue (att->name, var (att->value))); + } +} + +void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const +{ + for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) + { + if (const MemoryBlock* mb = i->value.getBinaryData()) + { + xml.setAttribute ("base64:" + i->name.toString(), mb->toBase64Encoding()); + } + else + { + // These types can't be stored as XML! + jassert (! i->value.isObject()); + jassert (! i->value.isMethod()); + jassert (! i->value.isArray()); + + xml.setAttribute (i->name.toString(), + i->value.toString()); + } + } +} diff --git a/source/modules/juce_audio_graph/containers/juce_NamedValueSet.h b/source/modules/juce_audio_graph/containers/juce_NamedValueSet.h new file mode 100644 index 000000000..87dec87a4 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_NamedValueSet.h @@ -0,0 +1,188 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_NAMEDVALUESET_H_INCLUDED +#define JUCE_NAMEDVALUESET_H_INCLUDED + + +//============================================================================== +/** Holds a set of named var objects. + + This can be used as a basic structure to hold a set of var object, which can + be retrieved by using their identifier. +*/ +class JUCE_API NamedValueSet +{ +public: + /** Creates an empty set. */ + NamedValueSet() noexcept; + + /** Creates a copy of another set. */ + NamedValueSet (const NamedValueSet&); + + /** Replaces this set with a copy of another set. */ + NamedValueSet& operator= (const NamedValueSet&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValueSet (NamedValueSet&&) noexcept; + NamedValueSet& operator= (NamedValueSet&&) noexcept; + #endif + + /** Destructor. */ + ~NamedValueSet() noexcept; + + bool operator== (const NamedValueSet&) const; + bool operator!= (const NamedValueSet&) const; + + //============================================================================== + struct NamedValue + { + NamedValue() noexcept {} + NamedValue (const Identifier& n, const var& v) : name (n), value (v) {} + NamedValue (const NamedValue& other) : name (other.name), value (other.value) {} + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValue (NamedValue&& other) noexcept + : name (static_cast (other.name)), + value (static_cast (other.value)) + { + } + + NamedValue (Identifier&& n, var&& v) noexcept + : name (static_cast (n)), + value (static_cast (v)) + { + } + + NamedValue& operator= (NamedValue&& other) noexcept + { + name = static_cast (other.name); + value = static_cast (other.value); + return *this; + } + #endif + + bool operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; } + bool operator!= (const NamedValue& other) const noexcept { return ! operator== (other); } + + Identifier name; + var value; + }; + + NamedValueSet::NamedValue* begin() { return values.begin(); } + NamedValueSet::NamedValue* end() { return values.end(); } + + //============================================================================== + + /** Returns the total number of values that the set contains. */ + int size() const noexcept; + + /** Returns true if the set is empty. */ + bool isEmpty() const noexcept; + + /** Returns the value of a named item. + If the name isn't found, this will return a void variant. + @see getProperty + */ + const var& operator[] (const Identifier& name) const noexcept; + + /** Tries to return the named value, but if no such value is found, this will + instead return the supplied default value. + */ + var getWithDefault (const Identifier& name, const var& defaultReturnValue) const; + + /** Changes or adds a named value. + @returns true if a value was changed or added; false if the + value was already set the value passed-in. + */ + bool set (const Identifier& name, const var& newValue); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Changes or adds a named value. + @returns true if a value was changed or added; false if the + value was already set the value passed-in. + */ + bool set (const Identifier& name, var&& newValue); + #endif + + /** Returns true if the set contains an item with the specified name. */ + bool contains (const Identifier& name) const noexcept; + + /** Removes a value from the set. + @returns true if a value was removed; false if there was no value + with the name that was given. + */ + bool remove (const Identifier& name); + + /** Returns the name of the value at a given index. + The index must be between 0 and size() - 1. + */ + Identifier getName (int index) const noexcept; + + /** Returns a pointer to the var that holds a named value, or null if there is + no value with this name. + + Do not use this method unless you really need access to the internal var object + for some reason - for normal reading and writing always prefer operator[]() and set(). + */ + var* getVarPointer (const Identifier& name) const noexcept; + + /** Returns the value of the item at a given index. + The index must be between 0 and size() - 1. + */ + const var& getValueAt (int index) const noexcept; + + /** Returns the value of the item at a given index. + The index must be between 0 and size() - 1, or this will return a nullptr + */ + var* getVarPointerAt (int index) const noexcept; + + /** Returns the index of the given name, or -1 if it's not found. */ + int indexOf (const Identifier& name) const noexcept; + + /** Removes all values. */ + void clear(); + + //============================================================================== + /** Sets properties to the values of all of an XML element's attributes. */ + void setFromXmlAttributes (const XmlElement& xml); + + /** Sets attributes in an XML element corresponding to each of this object's + properties. + */ + void copyToXmlAttributes (XmlElement& xml) const; + +private: + //============================================================================== + Array values; +}; + + +#endif // JUCE_NAMEDVALUESET_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_OwnedArray.h b/source/modules/juce_audio_graph/containers/juce_OwnedArray.h new file mode 100644 index 000000000..8259d6f7c --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_OwnedArray.h @@ -0,0 +1,841 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_OWNEDARRAY_H_INCLUDED +#define JUCE_OWNEDARRAY_H_INCLUDED + + +//============================================================================== +/** An array designed for holding objects. + + This holds a list of pointers to objects, and will automatically + delete the objects when they are removed from the array, or when the + array is itself deleted. + + Declare it in the form: OwnedArray + + ..and then add new objects, e.g. myOwnedArray.add (new MyObjectClass()); + + After adding objects, they are 'owned' by the array and will be deleted when + removed or replaced. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, ReferenceCountedArray, StringArray, CriticalSection +*/ +template + +class OwnedArray +{ +public: + //============================================================================== + /** Creates an empty array. */ + OwnedArray() noexcept + : numUsed (0) + { + } + + /** Deletes the array and also deletes any objects inside it. + + To get rid of the array without deleting its objects, use its + clear (false) method before deleting it. + */ + ~OwnedArray() + { + deleteAllObjects(); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + OwnedArray (OwnedArray&& other) noexcept + : data (static_cast&&> (other.data)), + numUsed (other.numUsed) + { + other.numUsed = 0; + } + + OwnedArray& operator= (OwnedArray&& other) noexcept + { + deleteAllObjects(); + + data = static_cast&&> (other.data); + numUsed = other.numUsed; + other.numUsed = 0; + return *this; + } + #endif + + //============================================================================== + /** Clears the array, optionally deleting the objects inside it first. */ + void clear (bool deleteObjects = true) + { + if (deleteObjects) + deleteAllObjects(); + + data.setAllocatedSize (0); + numUsed = 0; + } + + //============================================================================== + /** Clears the array, optionally deleting the objects inside it first. */ + void clearQuick (bool deleteObjects) + { + if (deleteObjects) + deleteAllObjects(); + + numUsed = 0; + } + + //============================================================================== + /** Returns the number of items currently in the array. + @see operator[] + */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns true if the array is empty, false otherwise. */ + inline bool isEmpty() const noexcept + { + return size() == 0; + } + + /** Returns a pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClass* operator[] (const int index) const noexcept + { + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return nullptr; + } + + /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. + + This is a faster and less safe version of operator[] which doesn't check the index passed in, so + it can be used when you're sure the index is always going to be legal. + */ + inline ObjectClass* getUnchecked (const int index) const noexcept + { + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns a pointer to the first object in the array. + + This will return a null pointer if the array's empty. + @see getLast + */ + inline ObjectClass* getFirst() const noexcept + { + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements [0]; + } + + return nullptr; + } + + /** Returns a pointer to the last object in the array. + + This will return a null pointer if the array's empty. + @see getFirst + */ + inline ObjectClass* getLast() const noexcept + { + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements [numUsed - 1]; + } + + return nullptr; + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ObjectClass** getRawDataPointer() noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** end() const noexcept + { + #if JUCE_DEBUG + if (data.elements == nullptr || numUsed <= 0) // (to keep static analysers happy) + return data.elements; + #endif + + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of an object which might be in the array. + + @param objectToLookFor the object to look for + @returns the index at which the object was found, or -1 if it's not found + */ + int indexOf (const ObjectClass* objectToLookFor) const noexcept + { + ObjectClass* const* e = data.elements.getData(); + ObjectClass* const* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (objectToLookFor == *e) + return static_cast (e - data.elements.getData()); + + return -1; + } + + /** Returns true if the array contains a specified object. + + @param objectToLookFor the object to look for + @returns true if the object is in the array + */ + bool contains (const ObjectClass* objectToLookFor) const noexcept + { + ObjectClass* const* e = data.elements.getData(); + ObjectClass* const* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (objectToLookFor == *e) + return true; + + return false; + } + + //============================================================================== + /** Appends a new object to the end of the array. + + Note that the this object will be deleted by the OwnedArray when it + is removed, so be careful not to delete it somewhere else. + + Also be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param newObject the new object to add to the array + @returns the new object that was added + @see set, insert, addIfNotAlreadyThere, addSorted + */ + ObjectClass* add (ObjectClass* newObject) noexcept + { + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + data.elements [numUsed++] = newObject; + return newObject; + } + + /** Inserts a new object into the array at the given index. + + Note that the this object will be deleted by the OwnedArray when it + is removed, so be careful not to delete it somewhere else. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + Be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param indexToInsertAt the index at which the new element should be inserted + @param newObject the new object to add to the array + @returns the new object that was added + @see add, addSorted, addIfNotAlreadyThere, set + */ + ObjectClass* insert (int indexToInsertAt, ObjectClass* newObject) noexcept + { + if (indexToInsertAt < 0) + return add (newObject); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + ObjectClass** const e = data.elements + indexToInsertAt; + const int numToMove = numUsed - indexToInsertAt; + + if (numToMove > 0) + memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); + + *e = newObject; + ++numUsed; + return newObject; + } + + /** Inserts an array of values into this array at a given position. + + If the index is less than 0 or greater than the size of the array, the + new elements will be added to the end of the array. + Otherwise, they will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the first new element should be inserted + @param newObjects the new values to add to the array + @param numberOfElements how many items are in the array + @see insert, add, addSorted, set + */ + void insertArray (int indexToInsertAt, + ObjectClass* const* newObjects, + int numberOfElements) + { + if (numberOfElements > 0) + { + data.ensureAllocatedSize (numUsed + numberOfElements); + ObjectClass** insertPos = data.elements; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos += indexToInsertAt; + const size_t numberToMove = (size_t) (numUsed - indexToInsertAt); + memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ObjectClass*)); + } + else + { + insertPos += numUsed; + } + + numUsed += numberOfElements; + + while (--numberOfElements >= 0) + *insertPos++ = *newObjects++; + } + } + + /** Appends a new object at the end of the array as long as the array doesn't + already contain it. + + If the array already contains a matching object, nothing will be done. + + @param newObject the new object to add to the array + @returns true if the new object was added, false otherwise + */ + bool addIfNotAlreadyThere (ObjectClass* newObject) noexcept + { + if (contains (newObject)) + return false; + + add (newObject); + return true; + } + + /** Replaces an object in the array with a different one. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the new object is added to the end of the array. + + Be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param indexToChange the index whose value you want to change + @param newObject the new value to set for this index. + @param deleteOldElement whether to delete the object that's being replaced with the new one + @see add, insert, remove + */ + ObjectClass* set (int indexToChange, ObjectClass* newObject, bool deleteOldElement = true) + { + if (indexToChange >= 0) + { + ScopedPointer toDelete; + + { + if (indexToChange < numUsed) + { + if (deleteOldElement) + { + toDelete = data.elements [indexToChange]; + + if (toDelete == newObject) + toDelete.release(); + } + + data.elements [indexToChange] = newObject; + } + else + { + data.ensureAllocatedSize (numUsed + 1); + data.elements [numUsed++] = newObject; + } + } + } + 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.. + } + + return newObject; + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addArray (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + data.ensureAllocatedSize (numUsed + numElementsToAdd); + jassert (numElementsToAdd <= 0 || data.elements != nullptr); + + while (--numElementsToAdd >= 0) + { + data.elements [numUsed] = arrayToAddFrom.getUnchecked (startIndex++); + ++numUsed; + } + } + + /** Adds copies of the elements in another array to the end of this array. + + The other array must be either an OwnedArray of a compatible type of object, or an Array + containing pointers to the same kind of object. The objects involved must provide + a copy constructor, and this will be used to create new copies of each element, and + add them to this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addCopiesOf (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + data.ensureAllocatedSize (numUsed + numElementsToAdd); + jassert (numElementsToAdd <= 0 || data.elements != nullptr); + + while (--numElementsToAdd >= 0) + data.elements [numUsed++] = createCopyIfNotNull (arrayToAddFrom.getUnchecked (startIndex++)); + } + + /** Inserts a new object into the array assuming that the array is sorted. + + This will use a comparator to find the position at which the new object + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort method + for details about this object's structure + @param newObject the new object to insert to the array + @returns the index at which the new object was added + @see add, sort, indexOfSorted + */ + template + int addSorted (ElementComparator& comparator, ObjectClass* const newObject) noexcept + { + ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + insert (index, newObject); + return index; + } + + /** Finds the index of an object in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param objectToLookFor the object to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, const ObjectClass* const objectToLookFor) const noexcept + { + ignoreUnused (comparator); + int s = 0, e = numUsed; + + while (s < e) + { + if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + break; + + if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + + return -1; + } + + //============================================================================== + /** Removes an object from the array. + + This will remove the object at a given index (optionally also + deleting it) and move back all the subsequent objects to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @param deleteObject whether to delete the object that is removed + @see removeObject, removeRange + */ + void remove (int indexToRemove, bool deleteObject = true) + { + ScopedPointer toDelete; + + { + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (deleteObject) + toDelete = *e; + + --numUsed; + const int numToShift = numUsed - indexToRemove; + + if (numToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); + } + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + /** Removes and returns an object from the array without deleting it. + + This will remove the object at a given index and return it, moving back all + the subsequent objects to close the gap. If the index passed in is out-of-range, + nothing will happen. + + @param indexToRemove the index of the element to remove + @see remove, removeObject, removeRange + */ + ObjectClass* removeAndReturn (int indexToRemove) + { + ObjectClass* removedItem = nullptr; + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + removedItem = *e; + + --numUsed; + const int numToShift = numUsed - indexToRemove; + + if (numToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + return removedItem; + } + + /** Removes a specified object from the array. + + If the item isn't found, no action is taken. + + @param objectToRemove the object to try to remove + @param deleteObject whether to delete the object (if it's found) + @see remove, removeRange + */ + void removeObject (const ObjectClass* objectToRemove, bool deleteObject = true) + { + ObjectClass** const e = data.elements.getData(); + + for (int i = 0; i < numUsed; ++i) + { + if (objectToRemove == e[i]) + { + remove (i, deleteObject); + break; + } + } + } + + /** Removes a range of objects from the array. + + This will remove a set of objects, starting from the given index, + and move any subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first object to remove + @param numberToRemove how many objects should be removed + @param deleteObjects whether to delete the objects that get removed + @see remove, removeObject + */ + void removeRange (int startIndex, int numberToRemove, bool deleteObjects = true) + { + const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); + startIndex = jlimit (0, numUsed, startIndex); + + if (endIndex > startIndex) + { + if (deleteObjects) + { + for (int i = startIndex; i < endIndex; ++i) + { + delete data.elements [i]; + data.elements [i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) + } + } + + const int rangeSize = endIndex - startIndex; + ObjectClass** e = data.elements + startIndex; + int numToShift = numUsed - endIndex; + numUsed -= rangeSize; + + while (--numToShift >= 0) + { + *e = e [rangeSize]; + ++e; + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes the last n objects from the array. + + @param howManyToRemove how many objects to remove from the end of the array + @param deleteObjects whether to also delete the objects that are removed + @see remove, removeObject, removeRange + */ + void removeLast (int howManyToRemove = 1, + bool deleteObjects = true) + { + if (howManyToRemove >= numUsed) + clear (deleteObjects); + else + removeRange (numUsed - howManyToRemove, howManyToRemove, deleteObjects); + } + + /** Swaps a pair of objects in the array. + + If either of the indexes passed in is out-of-range, nothing will happen, + otherwise the two objects at these positions will be exchanged. + */ + void swap (int index1, + int index2) noexcept + { + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the objects to a different position. + + This will move the object to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the object to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this object to end up. If this + is less than zero, it will be moved to the end of the array + */ + void move (int currentIndex, int newIndex) noexcept + { + if (currentIndex != newIndex) + { + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + ObjectClass* const value = data.elements [currentIndex]; + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); + } + + data.elements [newIndex] = value; + } + } + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) noexcept + { + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType* first, ElementType* second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + @see sortArray, indexOfSorted + */ + template + void sort (ElementComparator& comparator, + bool retainOrderOfEquivalentItems = false) const noexcept + { + ignoreUnused (comparator); // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void deleteAllObjects() + { + while (numUsed > 0) + delete data.elements [--numUsed]; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OwnedArray) +}; + + +#endif // JUCE_OWNEDARRAY_H_INCLUDED diff --git a/source/modules/juce_audio_graph/containers/juce_Variant.cpp b/source/modules/juce_audio_graph/containers/juce_Variant.cpp new file mode 100644 index 000000000..04a82f8c4 --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_Variant.cpp @@ -0,0 +1,799 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +enum VariantStreamMarkers +{ + varMarker_Int = 1, + varMarker_BoolTrue = 2, + varMarker_BoolFalse = 3, + varMarker_Double = 4, + varMarker_String = 5, + varMarker_Int64 = 6, + varMarker_Array = 7, + varMarker_Binary = 8, + varMarker_Undefined = 9 +}; + +//============================================================================== +class var::VariantType +{ +public: + VariantType() noexcept {} + virtual ~VariantType() noexcept {} + + virtual int toInt (const ValueUnion&) const noexcept { return 0; } + virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; } + virtual double toDouble (const ValueUnion&) const noexcept { return 0; } + virtual String toString (const ValueUnion&) const { return String(); } + virtual bool toBool (const ValueUnion&) const noexcept { return false; } + virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } + virtual Array* toArray (const ValueUnion&) const noexcept { return nullptr; } + virtual MemoryBlock* toBinary (const ValueUnion&) const noexcept { return nullptr; } + virtual var clone (const var& original) const { return original; } + + virtual bool isVoid() const noexcept { return false; } + virtual bool isUndefined() const noexcept { return false; } + virtual bool isInt() const noexcept { return false; } + virtual bool isInt64() const noexcept { return false; } + virtual bool isBool() const noexcept { return false; } + virtual bool isDouble() const noexcept { return false; } + virtual bool isString() const noexcept { return false; } + virtual bool isObject() const noexcept { return false; } + virtual bool isArray() const noexcept { return false; } + virtual bool isBinary() const noexcept { return false; } + virtual bool isMethod() const noexcept { return false; } + + virtual void cleanUp (ValueUnion&) const noexcept {} + virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } + virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept = 0; + virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; +}; + +//============================================================================== +class var::VariantType_Void : public var::VariantType +{ +public: + VariantType_Void() noexcept {} + static const VariantType_Void instance; + + bool isVoid() const noexcept override { return true; } + bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } + void writeToStream (const ValueUnion&, OutputStream& output) const override { output.writeCompressedInt (0); } +}; + +//============================================================================== +class var::VariantType_Undefined : public var::VariantType +{ +public: + VariantType_Undefined() noexcept {} + static const VariantType_Undefined instance; + + bool isUndefined() const noexcept override { return true; } + String toString (const ValueUnion&) const override { return "undefined"; } + bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } + + void writeToStream (const ValueUnion&, OutputStream& output) const override + { + output.writeCompressedInt (1); + output.writeByte (varMarker_Undefined); + } +}; + +//============================================================================== +class var::VariantType_Int : public var::VariantType +{ +public: + VariantType_Int() noexcept {} + static const VariantType_Int instance; + + int toInt (const ValueUnion& data) const noexcept override { return data.intValue; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.intValue; }; + double toDouble (const ValueUnion& data) const noexcept override { return (double) data.intValue; } + String toString (const ValueUnion& data) const override { return String (data.intValue); } + bool toBool (const ValueUnion& data) const noexcept override { return data.intValue != 0; } + bool isInt() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + if (otherType.isDouble() || otherType.isInt64() || otherType.isString()) + return otherType.equals (otherData, data, *this); + + return otherType.toInt (otherData) == data.intValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (5); + output.writeByte (varMarker_Int); + output.writeInt (data.intValue); + } +}; + +//============================================================================== +class var::VariantType_Int64 : public var::VariantType +{ +public: + VariantType_Int64() noexcept {} + static const VariantType_Int64 instance; + + int toInt (const ValueUnion& data) const noexcept override { return (int) data.int64Value; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return data.int64Value; }; + double toDouble (const ValueUnion& data) const noexcept override { return (double) data.int64Value; } + String toString (const ValueUnion& data) const override { return String (data.int64Value); } + bool toBool (const ValueUnion& data) const noexcept override { return data.int64Value != 0; } + bool isInt64() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + if (otherType.isDouble() || otherType.isString()) + return otherType.equals (otherData, data, *this); + + return otherType.toInt64 (otherData) == data.int64Value; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (9); + output.writeByte (varMarker_Int64); + output.writeInt64 (data.int64Value); + } +}; + +//============================================================================== +class var::VariantType_Double : public var::VariantType +{ +public: + VariantType_Double() noexcept {} + static const VariantType_Double instance; + + int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.doubleValue; }; + double toDouble (const ValueUnion& data) const noexcept override { return data.doubleValue; } + String toString (const ValueUnion& data) const override { return String (data.doubleValue, 20); } + bool toBool (const ValueUnion& data) const noexcept override { return data.doubleValue != 0; } + bool isDouble() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return std::abs (otherType.toDouble (otherData) - data.doubleValue) < std::numeric_limits::epsilon(); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (9); + output.writeByte (varMarker_Double); + output.writeDouble (data.doubleValue); + } +}; + +//============================================================================== +class var::VariantType_Bool : public var::VariantType +{ +public: + VariantType_Bool() noexcept {} + static const VariantType_Bool instance; + + int toInt (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; }; + double toDouble (const ValueUnion& data) const noexcept override { return data.boolValue ? 1.0 : 0.0; } + String toString (const ValueUnion& data) const override { return String::charToString (data.boolValue ? (juce_wchar) '1' : (juce_wchar) '0'); } + bool toBool (const ValueUnion& data) const noexcept override { return data.boolValue; } + bool isBool() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.toBool (otherData) == data.boolValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (1); + output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse); + } +}; + +//============================================================================== +class var::VariantType_String : public var::VariantType +{ +public: + VariantType_String() noexcept {} + static const VariantType_String instance; + + void cleanUp (ValueUnion& data) const noexcept override { getString (data)-> ~String(); } + void createCopy (ValueUnion& dest, const ValueUnion& source) const override { new (dest.stringValue) String (*getString (source)); } + + bool isString() const noexcept override { return true; } + int toInt (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue(); }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return getString (data)->getLargeIntValue(); }; + double toDouble (const ValueUnion& data) const noexcept override { return getString (data)->getDoubleValue(); } + String toString (const ValueUnion& data) const override { return *getString (data); } + bool toBool (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue() != 0 + || getString (data)->trim().equalsIgnoreCase ("true") + || getString (data)->trim().equalsIgnoreCase ("yes"); } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.toString (otherData) == *getString (data); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + const String* const s = getString (data); + const size_t len = s->getNumBytesAsUTF8() + 1; + HeapBlock temp (len); + s->copyToUTF8 (temp, len); + output.writeCompressedInt ((int) (len + 1)); + output.writeByte (varMarker_String); + output.write (temp, len); + } + +private: + static inline const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } + static inline String* getString (ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } +}; + +//============================================================================== +class var::VariantType_Object : public var::VariantType +{ +public: + VariantType_Object() noexcept {} + static const VariantType_Object instance; + + void cleanUp (ValueUnion& data) const noexcept override { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } + + void createCopy (ValueUnion& dest, const ValueUnion& source) const override + { + dest.objectValue = source.objectValue; + if (dest.objectValue != nullptr) + dest.objectValue->incReferenceCount(); + } + + String toString (const ValueUnion& data) const override { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } + bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != nullptr; } + ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept override { return data.objectValue; } + bool isObject() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.toObject (otherData) == data.objectValue; + } + + var clone (const var& original) const override + { + if (DynamicObject* d = original.getDynamicObject()) + return d->clone().get(); + + jassertfalse; // can only clone DynamicObjects! + return var(); + } + + void writeToStream (const ValueUnion&, OutputStream& output) const override + { + jassertfalse; // Can't write an object to a stream! + output.writeCompressedInt (0); + } +}; + +//============================================================================== +class var::VariantType_Array : public var::VariantType_Object +{ +public: + VariantType_Array() noexcept {} + static const VariantType_Array instance; + + String toString (const ValueUnion&) const override { return "[Array]"; } + ReferenceCountedObject* toObject (const ValueUnion&) const noexcept override { return nullptr; } + bool isArray() const noexcept override { return true; } + + Array* toArray (const ValueUnion& data) const noexcept override + { + if (RefCountedArray* a = dynamic_cast (data.objectValue)) + return &(a->array); + + return nullptr; + } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + const Array* const thisArray = toArray (data); + const Array* const otherArray = otherType.toArray (otherData); + return thisArray == otherArray || (thisArray != nullptr && otherArray != nullptr && *otherArray == *thisArray); + } + + var clone (const var& original) const override + { + Array arrayCopy; + + if (const Array* array = toArray (original.value)) + for (int i = 0; i < array->size(); ++i) + arrayCopy.add (array->getReference(i).clone()); + + return var (arrayCopy); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + if (const Array* array = toArray (data)) + { + MemoryOutputStream buffer (512); + const int numItems = array->size(); + buffer.writeCompressedInt (numItems); + + for (int i = 0; i < numItems; ++i) + array->getReference(i).writeToStream (buffer); + + output.writeCompressedInt (1 + (int) buffer.getDataSize()); + output.writeByte (varMarker_Array); + output << buffer; + } + } + + struct RefCountedArray : public ReferenceCountedObject + { + RefCountedArray (const Array& a) : array (a) { incReferenceCount(); } + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + RefCountedArray (Array&& a) : array (static_cast&&> (a)) { incReferenceCount(); } + #endif + Array array; + }; +}; + +//============================================================================== +class var::VariantType_Binary : public var::VariantType +{ +public: + VariantType_Binary() noexcept {} + + static const VariantType_Binary instance; + + void cleanUp (ValueUnion& data) const noexcept override { delete data.binaryValue; } + void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.binaryValue = new MemoryBlock (*source.binaryValue); } + + String toString (const ValueUnion& data) const override { return data.binaryValue->toBase64Encoding(); } + bool isBinary() const noexcept override { return true; } + MemoryBlock* toBinary (const ValueUnion& data) const noexcept override { return data.binaryValue; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + const MemoryBlock* const otherBlock = otherType.toBinary (otherData); + return otherBlock != nullptr && *otherBlock == *data.binaryValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (1 + (int) data.binaryValue->getSize()); + output.writeByte (varMarker_Binary); + output << *data.binaryValue; + } +}; + +//============================================================================== +class var::VariantType_Method : public var::VariantType +{ +public: + VariantType_Method() noexcept {} + static const VariantType_Method instance; + + void cleanUp (ValueUnion& data) const noexcept override { if (data.methodValue != nullptr ) delete data.methodValue; } + void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.methodValue = new NativeFunction (*source.methodValue); } + + String toString (const ValueUnion&) const override { return "Method"; } + bool toBool (const ValueUnion& data) const noexcept override { return data.methodValue != nullptr; } + bool isMethod() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.isMethod() && otherData.methodValue == data.methodValue; + } + + void writeToStream (const ValueUnion&, OutputStream& output) const override + { + jassertfalse; // Can't write a method to a stream! + output.writeCompressedInt (0); + } +}; + +//============================================================================== +const var::VariantType_Void var::VariantType_Void::instance; +const var::VariantType_Undefined var::VariantType_Undefined::instance; +const var::VariantType_Int var::VariantType_Int::instance; +const var::VariantType_Int64 var::VariantType_Int64::instance; +const var::VariantType_Bool var::VariantType_Bool::instance; +const var::VariantType_Double var::VariantType_Double::instance; +const var::VariantType_String var::VariantType_String::instance; +const var::VariantType_Object var::VariantType_Object::instance; +const var::VariantType_Array var::VariantType_Array::instance; +const var::VariantType_Binary var::VariantType_Binary::instance; +const var::VariantType_Method var::VariantType_Method::instance; + + +//============================================================================== +var::var() noexcept : type (&VariantType_Void::instance) {} +var::var (const VariantType& t) noexcept : type (&t) {} +var::~var() noexcept { type->cleanUp (value); } + +#if JUCE_ALLOW_STATIC_NULL_VARIABLES +const var var::null; +#endif + +//============================================================================== +var::var (const var& valueToCopy) : type (valueToCopy.type) +{ + type->createCopy (value, valueToCopy.value); +} + +var::var (const int v) noexcept : type (&VariantType_Int::instance) { value.intValue = v; } +var::var (const int64 v) noexcept : type (&VariantType_Int64::instance) { value.int64Value = v; } +var::var (const bool v) noexcept : type (&VariantType_Bool::instance) { value.boolValue = v; } +var::var (const double v) noexcept : type (&VariantType_Double::instance) { value.doubleValue = v; } +var::var (NativeFunction m) noexcept : type (&VariantType_Method::instance) { value.methodValue = new NativeFunction (m); } +var::var (const Array& v) : type (&VariantType_Array::instance) { value.objectValue = new VariantType_Array::RefCountedArray(v); } +var::var (const String& v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const char* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const wchar_t* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const void* v, size_t sz) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v, sz); } +var::var (const MemoryBlock& v) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v); } + +var::var (const StringArray& v) : type (&VariantType_Array::instance) +{ + Array strings; + + const int n = v.size(); + for (int i = 0; i < n; ++i) + strings.add (var (v[i])); + + value.objectValue = new VariantType_Array::RefCountedArray(strings); +} + +var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) +{ + value.objectValue = object; + + if (object != nullptr) + object->incReferenceCount(); +} + +var var::undefined() noexcept { return var (VariantType_Undefined::instance); } + +//============================================================================== +bool var::isVoid() const noexcept { return type->isVoid(); } +bool var::isUndefined() const noexcept { return type->isUndefined(); } +bool var::isInt() const noexcept { return type->isInt(); } +bool var::isInt64() const noexcept { return type->isInt64(); } +bool var::isBool() const noexcept { return type->isBool(); } +bool var::isDouble() const noexcept { return type->isDouble(); } +bool var::isString() const noexcept { return type->isString(); } +bool var::isObject() const noexcept { return type->isObject(); } +bool var::isArray() const noexcept { return type->isArray(); } +bool var::isBinaryData() const noexcept { return type->isBinary(); } +bool var::isMethod() const noexcept { return type->isMethod(); } + +var::operator int() const noexcept { return type->toInt (value); } +var::operator int64() const noexcept { return type->toInt64 (value); } +var::operator bool() const noexcept { return type->toBool (value); } +var::operator float() const noexcept { return (float) type->toDouble (value); } +var::operator double() const noexcept { return type->toDouble (value); } +String var::toString() const { return type->toString (value); } +var::operator String() const { return type->toString (value); } +ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); } +Array* var::getArray() const noexcept { return type->toArray (value); } +MemoryBlock* var::getBinaryData() const noexcept { return type->toBinary (value); } +DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast (getObject()); } + +//============================================================================== +void var::swapWith (var& other) noexcept +{ + std::swap (type, other.type); + std::swap (value, other.value); +} + +var& var::operator= (const var& v) { type->cleanUp (value); type = v.type; type->createCopy (value, v.value); return *this; } +var& var::operator= (const int v) { type->cleanUp (value); type = &VariantType_Int::instance; value.intValue = v; return *this; } +var& var::operator= (const int64 v) { type->cleanUp (value); type = &VariantType_Int64::instance; value.int64Value = v; return *this; } +var& var::operator= (const bool v) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = v; return *this; } +var& var::operator= (const double v) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = v; return *this; } +var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &VariantType_Binary::instance; value.binaryValue = new MemoryBlock (v); return *this; } +var& var::operator= (const Array& v) { var v2 (v); swapWith (v2); return *this; } +var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } +var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; } + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +var::var (var&& other) noexcept + : type (other.type), + value (other.value) +{ + other.type = &VariantType_Void::instance; +} + +var& var::operator= (var&& other) noexcept +{ + swapWith (other); + return *this; +} + +var::var (String&& v) : type (&VariantType_String::instance) +{ + new (value.stringValue) String (static_cast (v)); +} + +var::var (MemoryBlock&& v) : type (&VariantType_Binary::instance) +{ + value.binaryValue = new MemoryBlock (static_cast (v)); +} + +var::var (Array&& v) : type (&VariantType_Array::instance) +{ + value.objectValue = new VariantType_Array::RefCountedArray (static_cast&&> (v)); +} + +var& var::operator= (String&& v) +{ + type->cleanUp (value); + type = &VariantType_String::instance; + new (value.stringValue) String (static_cast (v)); + return *this; +} +#endif + +//============================================================================== +bool var::equals (const var& other) const noexcept +{ + return type->equals (value, other.value, *other.type); +} + +bool var::equalsWithSameType (const var& other) const noexcept +{ + return type == other.type && equals (other); +} + +bool var::hasSameTypeAs (const var& other) const noexcept +{ + return type == other.type; +} + +bool operator== (const var& v1, const var& v2) noexcept { return v1.equals (v2); } +bool operator!= (const var& v1, const var& v2) noexcept { return ! v1.equals (v2); } +bool operator== (const var& v1, const String& v2) { return v1.toString() == v2; } +bool operator!= (const var& v1, const String& v2) { return v1.toString() != v2; } +bool operator== (const var& v1, const char* const v2) { return v1.toString() == v2; } +bool operator!= (const var& v1, const char* const v2) { return v1.toString() != v2; } + +//============================================================================== +var var::clone() const noexcept +{ + return type->clone (*this); +} + +//============================================================================== +const var& var::operator[] (const Identifier& propertyName) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->getProperty (propertyName); + + return getNullVarRef(); +} + +const var& var::operator[] (const char* const propertyName) const +{ + return operator[] (Identifier (propertyName)); +} + +var var::getProperty (const Identifier& propertyName, const var& defaultReturnValue) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->getProperties().getWithDefault (propertyName, defaultReturnValue); + + return defaultReturnValue; +} + +var::NativeFunction var::getNativeFunction() const +{ + return isMethod() && (value.methodValue != nullptr) ? *value.methodValue : nullptr; +} + +var var::invoke (const Identifier& method, const var* arguments, int numArguments) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->invokeMethod (method, var::NativeFunctionArgs (*this, arguments, numArguments)); + + return var(); +} + +var var::call (const Identifier& method) const +{ + return invoke (method, nullptr, 0); +} + +var var::call (const Identifier& method, const var& arg1) const +{ + return invoke (method, &arg1, 1); +} + +var var::call (const Identifier& method, const var& arg1, const var& arg2) const +{ + var args[] = { arg1, arg2 }; + return invoke (method, args, 2); +} + +var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3) +{ + var args[] = { arg1, arg2, arg3 }; + return invoke (method, args, 3); +} + +var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const +{ + var args[] = { arg1, arg2, arg3, arg4 }; + return invoke (method, args, 4); +} + +var var::call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const +{ + var args[] = { arg1, arg2, arg3, arg4, arg5 }; + return invoke (method, args, 5); +} + +//============================================================================== +int var::size() const +{ + if (const Array* const array = getArray()) + return array->size(); + + return 0; +} + +const var& var::operator[] (int arrayIndex) const +{ + const Array* const array = getArray(); + + // When using this method, the var must actually be an array, and the index + // must be in-range! + jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); + + return array->getReference (arrayIndex); +} + +var& var::operator[] (int arrayIndex) +{ + const Array* const array = getArray(); + + // When using this method, the var must actually be an array, and the index + // must be in-range! + jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); + + return array->getReference (arrayIndex); +} + +Array* var::convertToArray() +{ + if (Array* array = getArray()) + return array; + + Array tempVar; + if (! isVoid()) + tempVar.add (*this); + + *this = tempVar; + return getArray(); +} + +void var::append (const var& n) +{ + convertToArray()->add (n); +} + +void var::remove (const int index) +{ + if (Array* const array = getArray()) + array->remove (index); +} + +void var::insert (const int index, const var& n) +{ + convertToArray()->insert (index, n); +} + +void var::resize (const int numArrayElementsWanted) +{ + convertToArray()->resize (numArrayElementsWanted); +} + +int var::indexOf (const var& n) const +{ + if (const Array* const array = getArray()) + return array->indexOf (n); + + return -1; +} + +//============================================================================== +void var::writeToStream (OutputStream& output) const +{ + type->writeToStream (value, output); +} + +var var::readFromStream (InputStream& input) +{ + const int numBytes = input.readCompressedInt(); + + if (numBytes > 0) + { + switch (input.readByte()) + { + case varMarker_Int: return var (input.readInt()); + case varMarker_Int64: return var (input.readInt64()); + case varMarker_BoolTrue: return var (true); + case varMarker_BoolFalse: return var (false); + case varMarker_Double: return var (input.readDouble()); + case varMarker_String: + { + MemoryOutputStream mo; + mo.writeFromInputStream (input, numBytes - 1); + return var (mo.toUTF8()); + } + + case varMarker_Binary: + { + MemoryBlock mb ((size_t) numBytes - 1); + + if (numBytes > 1) + { + const int numRead = input.read (mb.getData(), numBytes - 1); + mb.setSize ((size_t) numRead); + } + + return var (mb); + } + + case varMarker_Array: + { + var v; + Array* const destArray = v.convertToArray(); + + for (int i = input.readCompressedInt(); --i >= 0;) + destArray->add (readFromStream (input)); + + return v; + } + + default: + input.skipNextBytes (numBytes - 1); break; + } + } + + return var(); +} + +var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int numArgs) noexcept + : thisObject (t), arguments (args), numArguments (numArgs) +{} diff --git a/source/modules/juce_audio_graph/containers/juce_Variant.h b/source/modules/juce_audio_graph/containers/juce_Variant.h new file mode 100644 index 000000000..071f0a45a --- /dev/null +++ b/source/modules/juce_audio_graph/containers/juce_Variant.h @@ -0,0 +1,353 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_VARIANT_H_INCLUDED +#define JUCE_VARIANT_H_INCLUDED + + +//============================================================================== +/** + A variant class, that can be used to hold a range of primitive values. + + A var object can hold a range of simple primitive values, strings, or + any kind of ReferenceCountedObject. The var class is intended to act like + the kind of values used in dynamic scripting languages. + + You can save/load var objects either in a small, proprietary binary format + using writeToStream()/readFromStream(), or as JSON by using the JSON class. + + @see JSON, DynamicObject +*/ +class JUCE_API var +{ +public: + //============================================================================== + /** This structure is passed to a NativeFunction callback, and contains invocation + details about the function's arguments and context. + */ + struct NativeFunctionArgs + { + NativeFunctionArgs (const var& thisObject, const var* args, int numArgs) noexcept; + + const var& thisObject; + const var* arguments; + int numArguments; + + JUCE_DECLARE_NON_COPYABLE (NativeFunctionArgs) + }; + + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + using NativeFunction = std::function; + #else + typedef var (*NativeFunction) (const NativeFunctionArgs&); + #endif + + //============================================================================== + /** Creates a void variant. */ + var() noexcept; + + /** Destructor. */ + ~var() noexcept; + + #if JUCE_ALLOW_STATIC_NULL_VARIABLES + /** A static var object that can be used where you need an empty variant object. */ + static const var null; + #endif + + var (const var& valueToCopy); + var (int value) noexcept; + var (int64 value) noexcept; + var (bool value) noexcept; + var (double value) noexcept; + var (const char* value); + var (const wchar_t* value); + var (const String& value); + var (const Array& value); + var (const StringArray& value); + var (ReferenceCountedObject* object); + var (NativeFunction method) noexcept; + var (const void* binaryData, size_t dataSize); + var (const MemoryBlock& binaryData); + + var& operator= (const var& valueToCopy); + var& operator= (int value); + var& operator= (int64 value); + var& operator= (bool value); + var& operator= (double value); + var& operator= (const char* value); + var& operator= (const wchar_t* value); + var& operator= (const String& value); + var& operator= (const MemoryBlock& value); + var& operator= (const Array& value); + var& operator= (ReferenceCountedObject* object); + var& operator= (NativeFunction method); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + var (var&&) noexcept; + var (String&&); + var (MemoryBlock&&); + var (Array&&); + var& operator= (var&&) noexcept; + var& operator= (String&&); + #endif + + void swapWith (var& other) noexcept; + + /** Returns a var object that can be used where you need the javascript "undefined" value. */ + static var undefined() noexcept; + + //============================================================================== + operator int() const noexcept; + operator int64() const noexcept; + operator bool() const noexcept; + operator float() const noexcept; + operator double() const noexcept; + operator String() const; + String toString() const; + + /** If this variant holds an array, this provides access to it. + NOTE: Beware when you use this - the array pointer is only valid for the lifetime + of the variant that returned it, so be very careful not to call this method on temporary + var objects that are the return-value of a function, and which may go out of scope before + you use the array! + */ + Array* getArray() const noexcept; + + /** If this variant holds a memory block, this provides access to it. + NOTE: Beware when you use this - the MemoryBlock pointer is only valid for the lifetime + of the variant that returned it, so be very careful not to call this method on temporary + var objects that are the return-value of a function, and which may go out of scope before + you use the MemoryBlock! + */ + MemoryBlock* getBinaryData() const noexcept; + + ReferenceCountedObject* getObject() const noexcept; + DynamicObject* getDynamicObject() const noexcept; + + //============================================================================== + bool isVoid() const noexcept; + bool isUndefined() const noexcept; + bool isInt() const noexcept; + bool isInt64() const noexcept; + bool isBool() const noexcept; + bool isDouble() const noexcept; + bool isString() const noexcept; + bool isObject() const noexcept; + bool isArray() const noexcept; + bool isBinaryData() const noexcept; + bool isMethod() const noexcept; + + /** Returns true if this var has the same value as the one supplied. + Note that this ignores the type, so a string var "123" and an integer var with the + value 123 are considered to be equal. + @see equalsWithSameType + */ + bool equals (const var& other) const noexcept; + + /** Returns true if this var has the same value and type as the one supplied. + This differs from equals() because e.g. "123" and 123 will be considered different. + @see equals + */ + bool equalsWithSameType (const var& other) const noexcept; + + /** Returns true if this var has the same type as the one supplied. */ + bool hasSameTypeAs (const var& other) const noexcept; + + /** Returns a deep copy of this object. + For simple types this just returns a copy, but if the object contains any arrays + or DynamicObjects, they will be cloned (recursively). + */ + var clone() const noexcept; + + //============================================================================== + /** If the var is an array, this returns the number of elements. + If the var isn't actually an array, this will return 0. + */ + int size() const; + + /** If the var is an array, this can be used to return one of its elements. + To call this method, you must make sure that the var is actually an array, and + that the index is a valid number. If these conditions aren't met, behaviour is + undefined. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + const var& operator[] (int arrayIndex) const; + + /** If the var is an array, this can be used to return one of its elements. + To call this method, you must make sure that the var is actually an array, and + that the index is a valid number. If these conditions aren't met, behaviour is + undefined. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + var& operator[] (int arrayIndex); + + /** Appends an element to the var, converting it to an array if it isn't already one. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array. The parameter value + will then be appended to it. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void append (const var& valueToAppend); + + /** Inserts an element to the var, converting it to an array if it isn't already one. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array. The parameter value + will then be inserted into it. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void insert (int index, const var& value); + + /** If the var is an array, this removes one of its elements. + If the index is out-of-range or the var isn't an array, nothing will be done. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void remove (int index); + + /** Treating the var as an array, this resizes it to contain the specified number of elements. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array before resizing. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void resize (int numArrayElementsWanted); + + /** If the var is an array, this searches it for the first occurrence of the specified value, + and returns its index. + If the var isn't an array, or if the value isn't found, this returns -1. + */ + int indexOf (const var& value) const; + + //============================================================================== + /** If this variant is an object, this returns one of its properties. */ + const var& operator[] (const Identifier& propertyName) const; + /** If this variant is an object, this returns one of its properties. */ + const var& operator[] (const char* propertyName) const; + /** If this variant is an object, this returns one of its properties, or a default + fallback value if the property is not set. */ + var getProperty (const Identifier& propertyName, const var& defaultReturnValue) const; + + /** Invokes a named method call with no arguments. */ + var call (const Identifier& method) const; + /** Invokes a named method call with one argument. */ + var call (const Identifier& method, const var& arg1) const; + /** Invokes a named method call with 2 arguments. */ + var call (const Identifier& method, const var& arg1, const var& arg2) const; + /** Invokes a named method call with 3 arguments. */ + var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3); + /** Invokes a named method call with 4 arguments. */ + var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const; + /** Invokes a named method call with 5 arguments. */ + var call (const Identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const; + /** Invokes a named method call with a list of arguments. */ + var invoke (const Identifier& method, const var* arguments, int numArguments) const; + /** If this object is a method, this returns the function pointer. */ + NativeFunction getNativeFunction() const; + + //============================================================================== + /** Writes a binary representation of this value to a stream. + The data can be read back later using readFromStream(). + @see JSON + */ + void writeToStream (OutputStream& output) const; + + /** Reads back a stored binary representation of a value. + The data in the stream must have been written using writeToStream(), or this + will have unpredictable results. + @see JSON + */ + static var readFromStream (InputStream& input); + +private: + //============================================================================== + class VariantType; friend class VariantType; + class VariantType_Void; friend class VariantType_Void; + class VariantType_Undefined; friend class VariantType_Undefined; + class VariantType_Int; friend class VariantType_Int; + class VariantType_Int64; friend class VariantType_Int64; + class VariantType_Double; friend class VariantType_Double; + class VariantType_Bool; friend class VariantType_Bool; + class VariantType_String; friend class VariantType_String; + class VariantType_Object; friend class VariantType_Object; + class VariantType_Array; friend class VariantType_Array; + class VariantType_Binary; friend class VariantType_Binary; + class VariantType_Method; friend class VariantType_Method; + + union ValueUnion + { + int intValue; + int64 int64Value; + bool boolValue; + double doubleValue; + char stringValue [sizeof (String)]; + ReferenceCountedObject* objectValue; + MemoryBlock* binaryValue; + NativeFunction* methodValue; + }; + + const VariantType* type; + ValueUnion value; + + Array* convertToArray(); + var (const VariantType&) noexcept; +}; + +/** Compares the values of two var objects, using the var::equals() comparison. */ +JUCE_API bool operator== (const var&, const var&) noexcept; +/** Compares the values of two var objects, using the var::equals() comparison. */ +JUCE_API bool operator!= (const var&, const var&) noexcept; +JUCE_API bool operator== (const var&, const String&); +JUCE_API bool operator!= (const var&, const String&); +JUCE_API bool operator== (const var&, const char*); +JUCE_API bool operator!= (const var&, const char*); + +//============================================================================== +/** This template-overloaded class can be used to convert between var and custom types. */ +template +struct VariantConverter +{ + static Type fromVar (const var& v) { return static_cast (v); } + static var toVar (const Type& t) { return t; } +}; + +/** This template-overloaded class can be used to convert between var and custom types. */ +template <> +struct VariantConverter +{ + static String fromVar (const var& v) { return v.toString(); } + static var toVar (const String& s) { return s; } +}; + + +#endif // JUCE_VARIANT_H_INCLUDED diff --git a/source/modules/juce_audio_graph/juce_audio_graph.cpp b/source/modules/juce_audio_graph/juce_audio_graph.cpp new file mode 100644 index 000000000..ddd1252a5 --- /dev/null +++ b/source/modules/juce_audio_graph/juce_audio_graph.cpp @@ -0,0 +1,75 @@ +/* + * Standalone Juce AudioProcessorGraph + * Copyright (C) 2015 ROLI Ltd. + * Copyright (C) 2017 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the doc/GPL.txt file. + */ + +#include "juce_audio_graph.h" + +// #include "AppConfig.h" + +// #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 + +// #include "juce_audio_processors.h" +// #include + +#include +#include +#include +#include + +// #include + +//============================================================================== +namespace juce +{ + +#include "memory/juce_MemoryBlock.cpp" + +#include "text/juce_CharacterFunctions.cpp" +#include "text/juce_String.cpp" +// #include "streams/juce_InputStream.cpp" +// #include "streams/juce_OutputStream.cpp" + +// static inline bool arrayContainsPlugin (const OwnedArray& list, +// const PluginDescription& desc) +// { +// for (int i = list.size(); --i >= 0;) +// if (list.getUnchecked(i)->isDuplicateOf (desc)) +// return true; +// +// return false; +// } + +#include "midi/juce_MidiBuffer.cpp" +#include "midi/juce_MidiMessage.cpp" + +// #include "format/juce_AudioPluginFormat.cpp" +// #include "format/juce_AudioPluginFormatManager.cpp" +#include "processors/juce_AudioProcessor.cpp" +#include "processors/juce_AudioProcessorGraph.cpp" +// #include "processors/juce_GenericAudioProcessorEditor.cpp" +// #include "processors/juce_PluginDescription.cpp" +// #include "format_types/juce_LADSPAPluginFormat.cpp" +// #include "format_types/juce_VSTPluginFormat.cpp" +// #include "format_types/juce_VST3PluginFormat.cpp" +// #include "format_types/juce_AudioUnitPluginFormat.mm" +// #include "scanning/juce_KnownPluginList.cpp" +// #include "scanning/juce_PluginDirectoryScanner.cpp" +// #include "scanning/juce_PluginListComponent.cpp" +// #include "utilities/juce_AudioProcessorValueTreeState.cpp" +// #include "utilities/juce_AudioProcessorParameters.cpp" + +} diff --git a/source/modules/juce_audio_graph/juce_audio_graph.h b/source/modules/juce_audio_graph/juce_audio_graph.h new file mode 100644 index 000000000..1e1db136f --- /dev/null +++ b/source/modules/juce_audio_graph/juce_audio_graph.h @@ -0,0 +1,167 @@ +/* + * Standalone Juce AudioProcessorGraph + * Copyright (C) 2015 ROLI Ltd. + * Copyright (C) 2017 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the doc/GPL.txt file. + */ + +#ifndef JUCE_AUDIO_GRAPH_H_INCLUDED +#define JUCE_AUDIO_GRAPH_H_INCLUDED + +#include "CarlaJuceUtils.hpp" +#include "CarlaMutex.hpp" +#include "distrho/extra/ScopedPointer.hpp" + +#include + +//============================================================================== + +#define JUCE_API +#define JUCE_CALLTYPE + +#define jassertfalse carla_safe_assert("jassertfalse triggered", __FILE__, __LINE__); +#define jassert(expression) CARLA_SAFE_ASSERT(expression) + +#define static_jassert(expression) static_assert(expression, #expression); + +#if defined (__LP64__) || defined (_LP64) || defined (__arm64__) || defined(__MINGW64__) + #define JUCE_64BIT 1 +#else + #define JUCE_32BIT 1 +#endif + +#ifdef __LITTLE_ENDIAN__ + #define JUCE_LITTLE_ENDIAN 1 +#else + #define JUCE_BIG_ENDIAN 1 +#endif + +#define JUCE_ALIGN(bytes) __attribute__ ((aligned (bytes))) + +#if (__cplusplus >= 201103L || defined (__GXX_EXPERIMENTAL_CXX0X__)) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 + #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + #define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 + #define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_DELETED_FUNCTION) + #define JUCE_DELETED_FUNCTION = delete + #endif + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && ! defined (JUCE_COMPILER_SUPPORTS_LAMBDAS) + #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 + #endif +#endif + +#ifndef JUCE_DELETED_FUNCTION + /** This macro can be placed after a method declaration to allow the use of + the C++11 feature "= delete" on all compilers. + On newer compilers that support it, it does the C++11 "= delete", but on + older ones it's just an empty definition. + */ + #define JUCE_DELETED_FUNCTION +#endif + +#if ! JUCE_COMPILER_SUPPORTS_NOEXCEPT + #ifdef noexcept + #undef noexcept + #endif + #define noexcept throw() +#endif + +#if ! JUCE_COMPILER_SUPPORTS_NULLPTR + #ifdef nullptr + #undef nullptr + #endif + #define nullptr (0) +#endif + +#if ! JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL + #undef override + #define override +#endif + +#define JUCE_DECLARE_NON_COPYABLE(className) CARLA_DECLARE_NON_COPY_CLASS(className) +#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className) \ + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className) +#define JUCE_LEAK_DETECTOR(className) CARLA_LEAK_DETECTOR(className) +#define JUCE_PREVENT_HEAP_ALLOCATION CARLA_PREVENT_HEAP_ALLOCATION + +#define NEEDS_TRANS(x) (x) + +//============================================================================== +namespace juce +{ + +class File; +class MidiMessage; +class MemoryBlock; +class OutputStream; +class StringRef; + +#include "memory/juce_Memory.h" +#include "maths/juce_MathsFunctions.h" +#include "memory/juce_ByteOrder.h" +#include "memory/juce_Atomic.h" +#include "text/juce_CharacterFunctions.h" +#include "text/juce_CharPointer_UTF8.h" + +#include "text/juce_String.h" +#include "text/juce_StringRef.h" + +#include "memory/juce_HeapBlock.h" +#include "memory/juce_MemoryBlock.h" +#include "text/juce_NewLine.h" + +#include "containers/juce_ElementComparator.h" +#include "containers/juce_ArrayAllocationBase.h" +#include "containers/juce_Array.h" +#include "containers/juce_OwnedArray.h" +// #include "containers/juce_Variant.h" +// #include "containers/juce_NamedValueSet.h" + +#include "streams/juce_InputStream.h" +#include "streams/juce_OutputStream.h" +#include "streams/juce_MemoryOutputStream.h" + +#include "buffers/juce_AudioSampleBuffer.h" +#include "midi/juce_MidiBuffer.h" +#include "midi/juce_MidiMessage.h" + +class AudioProcessor; +#include "processors/juce_AudioPlayHead.h" +// #include "processors/juce_AudioProcessorListener.h" +// #include "processors/juce_AudioProcessorParameter.h" +#include "processors/juce_AudioProcessor.h" +// #include "processors/juce_PluginDescription.h" +// #include "processors/juce_AudioPluginInstance.h" +#include "processors/juce_AudioProcessorGraph.h" +// #include "format/juce_AudioPluginFormat.h" +// #include "format/juce_AudioPluginFormatManager.h" +// #include "scanning/juce_KnownPluginList.h" +// #include "utilities/juce_AudioProcessorValueTreeState.h" +// #include "utilities/juce_AudioProcessorParameterWithID.h" +// #include "utilities/juce_AudioParameterFloat.h" +// #include "utilities/juce_AudioParameterInt.h" +// #include "utilities/juce_AudioParameterBool.h" +// #include "utilities/juce_AudioParameterChoice.h" + +} + +#endif // JUCE_AUDIO_PROCESSORS_H_INCLUDED diff --git a/source/modules/juce_audio_graph/juce_audio_graph.mm b/source/modules/juce_audio_graph/juce_audio_graph.mm new file mode 100644 index 000000000..d7d16310c --- /dev/null +++ b/source/modules/juce_audio_graph/juce_audio_graph.mm @@ -0,0 +1,19 @@ +/* + * Standalone Juce AudioProcessorGraph + * Copyright (C) 2015 ROLI Ltd. + * Copyright (C) 2017 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the doc/GPL.txt file. + */ + +#include "juce_audio_graph.cpp" diff --git a/source/modules/juce_audio_graph/maths/juce_MathsFunctions.h b/source/modules/juce_audio_graph/maths/juce_MathsFunctions.h new file mode 100644 index 000000000..4772aaf26 --- /dev/null +++ b/source/modules/juce_audio_graph/maths/juce_MathsFunctions.h @@ -0,0 +1,554 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MATHSFUNCTIONS_H_INCLUDED +#define JUCE_MATHSFUNCTIONS_H_INCLUDED + +//============================================================================== +/* + This file sets up some handy mathematical typdefs and functions. +*/ + +//============================================================================== +// Definitions for the int8, int16, int32, int64 and pointer_sized_int types. + +/** A platform-independent 8-bit signed integer type. */ +typedef signed char int8; +/** A platform-independent 8-bit unsigned integer type. */ +typedef unsigned char uint8; +/** A platform-independent 16-bit signed integer type. */ +typedef signed short int16; +/** A platform-independent 16-bit unsigned integer type. */ +typedef unsigned short uint16; +/** A platform-independent 32-bit signed integer type. */ +typedef signed int int32; +/** A platform-independent 32-bit unsigned integer type. */ +typedef unsigned int uint32; +/** A platform-independent 64-bit integer type. */ +typedef long long int64; +/** A platform-independent 64-bit unsigned integer type. */ +typedef unsigned long long uint64; + +#if JUCE_64BIT + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef int64 pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef uint64 pointer_sized_uint; +#else + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef int pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef unsigned int pointer_sized_uint; +#endif + +//============================================================================== +// Some indispensable min/max functions + +/** Returns the larger of two values. */ +template +Type jmax (const Type a, const Type b) { return (a < b) ? b : a; } + +/** Returns the larger of three values. */ +template +Type jmax (const Type a, const Type b, const Type c) { return (a < b) ? ((b < c) ? c : b) : ((a < c) ? c : a); } + +/** Returns the larger of four values. */ +template +Type jmax (const Type a, const Type b, const Type c, const Type d) { return jmax (a, jmax (b, c, d)); } + +/** Returns the smaller of two values. */ +template +Type jmin (const Type a, const Type b) { return (b < a) ? b : a; } + +/** Returns the smaller of three values. */ +template +Type jmin (const Type a, const Type b, const Type c) { return (b < a) ? ((c < b) ? c : b) : ((c < a) ? c : a); } + +/** Returns the smaller of four values. */ +template +Type jmin (const Type a, const Type b, const Type c, const Type d) { return jmin (a, jmin (b, c, d)); } + +/** Remaps a normalised value (between 0 and 1) to a target range. + This effectively returns (targetRangeMin + value0To1 * (targetRangeMax - targetRangeMin)). +*/ +template +Type jmap (Type value0To1, Type targetRangeMin, Type targetRangeMax) +{ + return targetRangeMin + value0To1 * (targetRangeMax - targetRangeMin); +} + +/** Remaps a value from a source range to a target range. */ +template +Type jmap (Type sourceValue, Type sourceRangeMin, Type sourceRangeMax, Type targetRangeMin, Type targetRangeMax) +{ + jassert (sourceRangeMax != sourceRangeMin); // mapping from a range of zero will produce NaN! + return targetRangeMin + ((targetRangeMax - targetRangeMin) * (sourceValue - sourceRangeMin)) / (sourceRangeMax - sourceRangeMin); +} + +/** Scans an array of values, returning the minimum value that it contains. */ +template +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 maximum value that it contains. */ +template +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 + specified, and if not, will return the nearest value that would be in-range. Effectively, + it's like calling jmax (lowerLimit, jmin (upperLimit, value)). + + Note that it expects that lowerLimit <= upperLimit. If this isn't true, + the results will be unpredictable. + + @param lowerLimit the minimum value to return + @param upperLimit the maximum value to return + @param valueToConstrain the value to try to return + @returns the closest value to valueToConstrain which lies between lowerLimit + and upperLimit (inclusive) + @see jmin, jmax, jmap +*/ +template +Type jlimit (const Type lowerLimit, + const Type upperLimit, + const Type valueToConstrain) noexcept +{ + jassert (lowerLimit <= upperLimit); // if these are in the wrong order, results are unpredictable.. + + return (valueToConstrain < lowerLimit) ? lowerLimit + : ((upperLimit < valueToConstrain) ? upperLimit + : 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 +bool isPositiveAndBelow (Type valueToTest, Type upperLimit) noexcept +{ + 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) noexcept +{ + 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 +bool isPositiveAndNotGreaterThan (Type valueToTest, Type upperLimit) noexcept +{ + 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) noexcept +{ + 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. */ +template +void swapVariables (Type& variable1, Type& variable2) +{ + std::swap (variable1, variable2); +} + +/** Handy function for avoiding unused variables warning. */ +template +void ignoreUnused (const Type1&) noexcept {} + +template +void ignoreUnused (const Type1&, const Type2&) noexcept {} + +template +void ignoreUnused (const Type1&, const Type2&, const Type3&) noexcept {} + +template +void ignoreUnused (const Type1&, const Type2&, const Type3&, const Type4&) noexcept {} + +/** Handy function for getting the number of elements in a simple const C array. + E.g. + @code + static int myArray[] = { 1, 2, 3 }; + + int numElements = numElementsInArray (myArray) // returns 3 + @endcode +*/ +template +int numElementsInArray (Type (&array)[N]) +{ + ignoreUnused (array); + (void) sizeof (0[array]); // This line should cause an error if you pass an object with a user-defined subscript operator + return N; +} + +//============================================================================== +// Some useful maths functions that aren't always present with all compilers and build settings. + +/** Using juce_hypot is easier than dealing with the different types of hypot function + that are provided by the various platforms and compilers. */ +template +Type juce_hypot (Type a, Type b) noexcept +{ + return static_cast (hypot (a, b)); +} + +template <> +inline float juce_hypot (float a, float b) noexcept +{ + return hypotf (a, b); +} + +/** 64-bit abs function. */ +inline int64 abs64 (const int64 n) noexcept +{ + return (n >= 0) ? n : -n; +} + +//============================================================================== +/** A predefined value for Pi, at double-precision. + @see float_Pi +*/ +const double double_Pi = 3.1415926535897932384626433832795; + +/** A predefined value for Pi, at single-precision. + @see double_Pi +*/ +const float float_Pi = 3.14159265358979323846f; + + +/** Converts an angle in degrees to radians. */ +inline float degreesToRadians (float degrees) noexcept { return degrees * (float_Pi / 180.0f); } + +/** Converts an angle in degrees to radians. */ +inline double degreesToRadians (double degrees) noexcept { return degrees * (double_Pi / 180.0); } + +/** Converts an angle in radians to degrees. */ +inline float radiansToDegrees (float radians) noexcept { return radians * (180.0f / float_Pi); } + +/** Converts an angle in radians to degrees. */ +inline double radiansToDegrees (double radians) noexcept { return radians * (180.0 / double_Pi); } + + +//============================================================================== +/** The isfinite() method seems to vary between platforms, so this is a + platform-independent function for it. +*/ +template +bool juce_isfinite (NumericType) noexcept +{ + return true; // Integer types are always finite +} + +template <> +inline bool juce_isfinite (float value) noexcept +{ + return std::isfinite (value); +} + +template <> +inline bool juce_isfinite (double value) noexcept +{ + return std::isfinite (value); +} + +//============================================================================== +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a float to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. +*/ +template +int roundToInt (const FloatType value) noexcept +{ + union { int asInt[2]; double asDouble; } n; + n.asDouble = ((double) value) + 6755399441055744.0; + + #if JUCE_BIG_ENDIAN + return n.asInt [1]; + #else + return n.asInt [0]; + #endif +} + +inline int roundToInt (int value) noexcept +{ + return value; +} + +/** Fast floating-point-to-integer conversion. + + This is a slightly slower and slightly more accurate version of roundDoubleToInt(). It works + fine for values above zero, but negative numbers are rounded the wrong way. +*/ +inline int roundToIntAccurate (double value) noexcept +{ + return roundToInt (value + 1.5e-8); +} + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a double to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. For a more accurate conversion, + see roundDoubleToIntAccurate(). +*/ +inline int roundDoubleToInt (double value) noexcept +{ + return roundToInt (value); +} + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a float to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. +*/ +inline int roundFloatToInt (float value) noexcept +{ + return roundToInt (value); +} + +//============================================================================== +/** Returns true if the specified integer is a power-of-two. */ +template +bool isPowerOfTwo (IntegerType value) +{ + return (value & (value - 1)) == 0; +} + +/** Returns the smallest power-of-two which is equal to or greater than the given integer. */ +inline int nextPowerOfTwo (int n) noexcept +{ + --n; + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return n + 1; +} + +/** Returns the index of the highest set bit in a (non-zero) number. + So for n=3 this would return 1, for n=7 it returns 2, etc. + An input value of 0 is illegal! +*/ +int findHighestSetBit (uint32 n) noexcept; + +/** Returns the number of bits in a 32-bit integer. */ +inline int countNumberOfBits (uint32 n) noexcept +{ + n -= ((n >> 1) & 0x55555555); + n = (((n >> 2) & 0x33333333) + (n & 0x33333333)); + n = (((n >> 4) + n) & 0x0f0f0f0f); + n += (n >> 8); + n += (n >> 16); + return (int) (n & 0x3f); +} + +/** Returns the number of bits in a 64-bit integer. */ +inline int countNumberOfBits (uint64 n) noexcept +{ + return countNumberOfBits ((uint32) n) + countNumberOfBits ((uint32) (n >> 32)); +} + +/** Performs a modulo operation, but can cope with the dividend being negative. + The divisor must be greater than zero. +*/ +template +IntegerType negativeAwareModulo (IntegerType dividend, const IntegerType divisor) noexcept +{ + jassert (divisor > 0); + dividend %= divisor; + return (dividend < 0) ? (dividend + divisor) : dividend; +} + +/** Returns the square of its argument. */ +template +NumericType square (NumericType n) noexcept +{ + return n * n; +} + +//============================================================================== +/** Writes a number of bits into a memory buffer at a given bit index. + The buffer is treated as a sequence of 8-bit bytes, and the value is encoded in little-endian order, + so for example if startBit = 10, and numBits = 11 then the lower 6 bits of the value would be written + into bits 2-8 of targetBuffer[1], and the upper 5 bits of value into bits 0-5 of targetBuffer[2]. + + @see readLittleEndianBitsInBuffer +*/ +void writeLittleEndianBitsInBuffer (void* targetBuffer, uint32 startBit, uint32 numBits, uint32 value) noexcept; + +/** Reads a number of bits from a buffer at a given bit index. + The buffer is treated as a sequence of 8-bit bytes, and the value is encoded in little-endian order, + so for example if startBit = 10, and numBits = 11 then the lower 6 bits of the result would be read + from bits 2-8 of sourceBuffer[1], and the upper 5 bits of the result from bits 0-5 of sourceBuffer[2]. + + @see writeLittleEndianBitsInBuffer +*/ +uint32 readLittleEndianBitsInBuffer (const void* sourceBuffer, uint32 startBit, uint32 numBits) noexcept; + + +//============================================================================== +/** This namespace contains a few template classes for helping work out class type variations. +*/ +namespace TypeHelpers +{ + /** The ParameterType struct is used to find the best type to use when passing some kind + of object as a parameter. + + Of course, this is only likely to be useful in certain esoteric template situations. + + Because "typename TypeHelpers::ParameterType::type" is a bit of a mouthful, there's + a PARAMETER_TYPE(SomeClass) macro that you can use to get the same effect. + + E.g. "myFunction (PARAMETER_TYPE (int), PARAMETER_TYPE (MyObject))" + would evaluate to "myfunction (int, const MyObject&)", keeping any primitive types as + pass-by-value, but passing objects as a const reference, to avoid copying. + */ + template struct ParameterType { typedef const Type& type; }; + template struct ParameterType { typedef Type& type; }; + template struct ParameterType { typedef Type* type; }; + template <> struct ParameterType { typedef char type; }; + template <> struct ParameterType { typedef unsigned char type; }; + template <> struct ParameterType { typedef short type; }; + template <> struct ParameterType { typedef unsigned short type; }; + template <> struct ParameterType { typedef int type; }; + template <> struct ParameterType { typedef unsigned int type; }; + template <> struct ParameterType { typedef long type; }; + template <> struct ParameterType { typedef unsigned long type; }; + template <> struct ParameterType { typedef int64 type; }; + template <> struct ParameterType { typedef uint64 type; }; + template <> struct ParameterType { typedef bool type; }; + template <> struct ParameterType { typedef float type; }; + template <> struct ParameterType { typedef double type; }; + + /** A helpful macro to simplify the use of the ParameterType template. + @see ParameterType + */ + #define PARAMETER_TYPE(a) typename TypeHelpers::ParameterType::type + + + /** These templates are designed to take a type, and if it's a double, they return a double + type; for anything else, they return a float type. + */ + template struct SmallestFloatType { typedef float type; }; + template <> struct SmallestFloatType { typedef double type; }; +} + + +//============================================================================== + +#endif // JUCE_MATHSFUNCTIONS_H_INCLUDED diff --git a/source/modules/juce_audio_graph/memory/juce_Atomic.h b/source/modules/juce_audio_graph/memory/juce_Atomic.h new file mode 100644 index 000000000..a08cf17a4 --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_Atomic.h @@ -0,0 +1,249 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ATOMIC_H_INCLUDED +#define JUCE_ATOMIC_H_INCLUDED + + +//============================================================================== +/** + Simple class to hold a primitive value and perform atomic operations on it. + + The type used must be a 32 or 64 bit primitive, like an int, pointer, etc. + There are methods to perform most of the basic atomic operations. +*/ +template +class Atomic +{ +public: + /** Creates a new value, initialised to zero. */ + inline Atomic() noexcept + : value (0) + { + } + + /** Creates a new value, with a given initial value. */ + inline explicit Atomic (const Type initialValue) noexcept + : value (initialValue) + { + } + + /** Copies another value (atomically). */ + inline Atomic (const Atomic& other) noexcept + : value (other.get()) + { + } + + /** Destructor. */ + inline ~Atomic() noexcept + { + // This class can only be used for types which are 32 or 64 bits in size. + static_jassert (sizeof (Type) == 4 || sizeof (Type) == 8); + } + + /** Atomically reads and returns the current value. */ + Type get() const noexcept; + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Atomic& other) noexcept { exchange (other.get()); return *this; } + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Type newValue) noexcept { exchange (newValue); return *this; } + + /** Atomically sets the current value. */ + void set (Type newValue) noexcept { exchange (newValue); } + + /** Atomically sets the current value, returning the value that was replaced. */ + Type exchange (Type value) noexcept; + + /** Atomically adds a number to this value, returning the new value. */ + Type operator+= (Type amountToAdd) noexcept; + + /** Atomically subtracts a number from this value, returning the new value. */ + Type operator-= (Type amountToSubtract) noexcept; + + /** Atomically increments this value, returning the new value. */ + Type operator++() noexcept; + + /** Atomically decrements this value, returning the new value. */ + Type operator--() noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + bool compareAndSetBool (Type newValue, Type valueToCompare) + { + if (get() == valueToCompare) + { + set (newValue); + return true; + } + + return false; + } + @endcode + + @returns true if the comparison was true and the value was replaced; false if + the comparison failed and the value was left unchanged. + @see compareAndSetValue + */ + bool compareAndSetBool (Type newValue, Type valueToCompare) noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + Type compareAndSetValue (Type newValue, Type valueToCompare) + { + Type oldValue = get(); + if (oldValue == valueToCompare) + set (newValue); + + return oldValue; + } + @endcode + + @returns the old value before it was changed. + @see compareAndSetBool + */ + Type compareAndSetValue (Type newValue, Type valueToCompare) noexcept; + + /** Implements a memory read/write barrier. */ + static void memoryBarrier() noexcept; + + //============================================================================== + #if JUCE_64BIT + JUCE_ALIGN (8) + #else + JUCE_ALIGN (4) + #endif + + /** The raw value that this class operates on. + This is exposed publicly in case you need to manipulate it directly + for performance reasons. + */ + volatile Type value; + +private: + template + static inline Dest castTo (Source value) noexcept { union { Dest d; Source s; } u; u.s = value; return u.d; } + + static inline Type castFrom32Bit (int32 value) noexcept { return castTo (value); } + static inline Type castFrom64Bit (int64 value) noexcept { return castTo (value); } + static inline int32 castTo32Bit (Type value) noexcept { return castTo (value); } + static inline int64 castTo64Bit (Type value) noexcept { return castTo (value); } + + Type operator++ (int); // better to just use pre-increment with atomics.. + Type operator-- (int); + + /** This templated negate function will negate pointers as well as integers */ + template + inline ValueType negateValue (ValueType n) noexcept + { + return sizeof (ValueType) == 1 ? (ValueType) -(signed char) n + : (sizeof (ValueType) == 2 ? (ValueType) -(short) n + : (sizeof (ValueType) == 4 ? (ValueType) -(int) n + : ((ValueType) -(int64) n))); + } + + /** This templated negate function will negate pointers as well as integers */ + template + inline PointerType* negateValue (PointerType* n) noexcept + { + return reinterpret_cast (-reinterpret_cast (n)); + } +}; + +//============================================================================== +template +inline Type Atomic::get() const noexcept +{ + return sizeof (Type) == 4 ? castFrom32Bit ((int32) __sync_add_and_fetch ((volatile int32*) &value, 0)) + : castFrom64Bit ((int64) __sync_add_and_fetch ((volatile int64*) &value, 0)); +} + +template +inline Type Atomic::exchange (const Type newValue) noexcept +{ + Type currentVal = value; + while (! compareAndSetBool (newValue, currentVal)) { currentVal = value; } + return currentVal; +} + +template +inline Type Atomic::operator+= (const Type amountToAdd) noexcept +{ + return (Type) __sync_add_and_fetch (&value, amountToAdd); +} + +template +inline Type Atomic::operator-= (const Type amountToSubtract) noexcept +{ + return operator+= (negateValue (amountToSubtract)); +} + +template +inline Type Atomic::operator++() noexcept +{ + return sizeof (Type) == 4 ? (Type) __sync_add_and_fetch (&value, (Type) 1) + : (Type) __sync_add_and_fetch ((int64_t*) &value, 1); +} + +template +inline Type Atomic::operator--() noexcept +{ + return sizeof (Type) == 4 ? (Type) __sync_add_and_fetch (&value, (Type) -1) + : (Type) __sync_add_and_fetch ((int64_t*) &value, -1); +} + +template +inline bool Atomic::compareAndSetBool (const Type newValue, const Type valueToCompare) noexcept +{ + return sizeof (Type) == 4 ? __sync_bool_compare_and_swap ((volatile int32*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue)) + : __sync_bool_compare_and_swap ((volatile int64*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue)); +} + +template +inline Type Atomic::compareAndSetValue (const Type newValue, const Type valueToCompare) noexcept +{ + return sizeof (Type) == 4 ? castFrom32Bit ((int32) __sync_val_compare_and_swap ((volatile int32*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue))) + : castFrom64Bit ((int64) __sync_val_compare_and_swap ((volatile int64*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue))); +} + +template +inline void Atomic::memoryBarrier() noexcept +{ + __sync_synchronize(); +} + +#endif // JUCE_ATOMIC_H_INCLUDED diff --git a/source/modules/juce_audio_graph/memory/juce_ByteOrder.h b/source/modules/juce_audio_graph/memory/juce_ByteOrder.h new file mode 100644 index 000000000..0440ad2f5 --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_ByteOrder.h @@ -0,0 +1,241 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_BYTEORDER_H_INCLUDED +#define JUCE_BYTEORDER_H_INCLUDED + + +//============================================================================== +/** Contains static methods for converting the byte order between different + endiannesses. +*/ +class JUCE_API ByteOrder +{ +public: + //============================================================================== + /** Swaps the upper and lower bytes of a 16-bit integer. */ + static uint16 swap (uint16 value) noexcept; + + /** Reverses the order of the 4 bytes in a 32-bit integer. */ + static uint32 swap (uint32 value) noexcept; + + /** Reverses the order of the 8 bytes in a 64-bit integer. */ + static uint64 swap (uint64 value) noexcept; + + //============================================================================== + /** Swaps the byte order of a 16-bit unsigned int if the CPU is big-endian */ + static uint16 swapIfBigEndian (uint16 value) noexcept; + + /** Swaps the byte order of a 32-bit unsigned int if the CPU is big-endian */ + static uint32 swapIfBigEndian (uint32 value) noexcept; + + /** Swaps the byte order of a 64-bit unsigned int if the CPU is big-endian */ + static uint64 swapIfBigEndian (uint64 value) noexcept; + + /** Swaps the byte order of a 16-bit signed int if the CPU is big-endian */ + static int16 swapIfBigEndian (int16 value) noexcept; + + /** Swaps the byte order of a 32-bit signed int if the CPU is big-endian */ + static int32 swapIfBigEndian (int32 value) noexcept; + + /** Swaps the byte order of a 64-bit signed int if the CPU is big-endian */ + static int64 swapIfBigEndian (int64 value) noexcept; + + /** Swaps the byte order of a 32-bit float if the CPU is big-endian */ + static float swapIfBigEndian (float value) noexcept; + + /** Swaps the byte order of a 64-bit float if the CPU is big-endian */ + static double swapIfBigEndian (double value) noexcept; + + /** Swaps the byte order of a 16-bit unsigned int if the CPU is little-endian */ + static uint16 swapIfLittleEndian (uint16 value) noexcept; + + /** Swaps the byte order of a 32-bit unsigned int if the CPU is little-endian */ + static uint32 swapIfLittleEndian (uint32 value) noexcept; + + /** Swaps the byte order of a 64-bit unsigned int if the CPU is little-endian */ + static uint64 swapIfLittleEndian (uint64 value) noexcept; + + /** Swaps the byte order of a 16-bit signed int if the CPU is little-endian */ + static int16 swapIfLittleEndian (int16 value) noexcept; + + /** Swaps the byte order of a 32-bit signed int if the CPU is little-endian */ + static int32 swapIfLittleEndian (int32 value) noexcept; + + /** Swaps the byte order of a 64-bit signed int if the CPU is little-endian */ + static int64 swapIfLittleEndian (int64 value) noexcept; + + /** Swaps the byte order of a 32-bit float if the CPU is little-endian */ + static float swapIfLittleEndian (float value) noexcept; + + /** Swaps the byte order of a 64-bit float if the CPU is little-endian */ + static double swapIfLittleEndian (double value) noexcept; + + //============================================================================== + /** Turns 4 bytes into a little-endian integer. */ + static uint32 littleEndianInt (const void* bytes) noexcept; + + /** Turns 8 bytes into a little-endian integer. */ + static uint64 littleEndianInt64 (const void* bytes) noexcept; + + /** Turns 2 bytes into a little-endian integer. */ + static uint16 littleEndianShort (const void* bytes) noexcept; + + /** Turns 4 bytes into a big-endian integer. */ + static uint32 bigEndianInt (const void* bytes) noexcept; + + /** Turns 8 bytes into a big-endian integer. */ + static uint64 bigEndianInt64 (const void* bytes) noexcept; + + /** Turns 2 bytes into a big-endian integer. */ + static uint16 bigEndianShort (const void* bytes) noexcept; + + //============================================================================== + /** Converts 3 little-endian bytes into a signed 24-bit value (which is sign-extended to 32 bits). */ + static int littleEndian24Bit (const void* bytes) noexcept; + + /** Converts 3 big-endian bytes into a signed 24-bit value (which is sign-extended to 32 bits). */ + static int bigEndian24Bit (const void* bytes) noexcept; + + /** Copies a 24-bit number to 3 little-endian bytes. */ + static void littleEndian24BitToChars (int value, void* destBytes) noexcept; + + /** Copies a 24-bit number to 3 big-endian bytes. */ + static void bigEndian24BitToChars (int value, void* destBytes) noexcept; + + //============================================================================== + /** Returns true if the current CPU is big-endian. */ + static bool isBigEndian() noexcept; + +private: + ByteOrder() JUCE_DELETED_FUNCTION; + + JUCE_DECLARE_NON_COPYABLE (ByteOrder) +}; + + +//============================================================================== +#if JUCE_MSVC && ! defined (__INTEL_COMPILER) + #pragma intrinsic (_byteswap_ulong) +#endif + +inline uint16 ByteOrder::swap (uint16 n) noexcept +{ + return static_cast ((n << 8) | (n >> 8)); +} + +inline uint32 ByteOrder::swap (uint32 n) noexcept +{ + #if JUCE_MAC || JUCE_IOS + return OSSwapInt32 (n); + #elif (JUCE_GCC || JUCE_CLANG) && JUCE_INTEL && ! JUCE_NO_INLINE_ASM + asm("bswap %%eax" : "=a"(n) : "a"(n)); + return n; + #elif JUCE_MSVC + return _byteswap_ulong (n); + #elif JUCE_ANDROID + return bswap_32 (n); + #else + return (n << 24) | (n >> 24) | ((n & 0xff00) << 8) | ((n & 0xff0000) >> 8); + #endif +} + +inline uint64 ByteOrder::swap (uint64 value) noexcept +{ + #if JUCE_MAC || JUCE_IOS + return OSSwapInt64 (value); + #elif JUCE_MSVC + return _byteswap_uint64 (value); + #else + return (((uint64) swap ((uint32) value)) << 32) | swap ((uint32) (value >> 32)); + #endif +} + +#if JUCE_LITTLE_ENDIAN + inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return v; } + inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return v; } + inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return v; } + inline int16 ByteOrder::swapIfBigEndian (const int16 v) noexcept { return v; } + inline int32 ByteOrder::swapIfBigEndian (const int32 v) noexcept { return v; } + inline int64 ByteOrder::swapIfBigEndian (const int64 v) noexcept { return v; } + inline float ByteOrder::swapIfBigEndian (const float v) noexcept { return v; } + inline double ByteOrder::swapIfBigEndian (const double v) noexcept { return v; } + + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return swap (v); } + inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return swap (v); } + inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return swap (v); } + inline int16 ByteOrder::swapIfLittleEndian (const int16 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int32 ByteOrder::swapIfLittleEndian (const int32 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int64 ByteOrder::swapIfLittleEndian (const int64 v) noexcept { return static_cast (swap (static_cast (v))); } +inline float ByteOrder::swapIfLittleEndian (const float v) noexcept { union { uint32 asUInt; float asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + inline double ByteOrder::swapIfLittleEndian (const double v) noexcept { union { uint64 asUInt; double asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint32 ByteOrder::bigEndianInt (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint64 ByteOrder::bigEndianInt64 (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint16 ByteOrder::bigEndianShort (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline bool ByteOrder::isBigEndian() noexcept { return false; } +#else + inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return swap (v); } + inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return swap (v); } + inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return swap (v); } + inline int16 ByteOrder::swapIfBigEndian (const int16 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int32 ByteOrder::swapIfBigEndian (const int32 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int64 ByteOrder::swapIfBigEndian (const int64 v) noexcept { return static_cast (swap (static_cast (v))); } + inline float ByteOrder::swapIfBigEndian (const float v) noexcept { union { uint32 asUInt; float asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + inline double ByteOrder::swapIfBigEndian (const double v) noexcept { union { uint64 asUInt; double asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return v; } + inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return v; } + inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return v; } + inline int16 ByteOrder::swapIfLittleEndian (const int16 v) noexcept { return v; } + inline int32 ByteOrder::swapIfLittleEndian (const int32 v) noexcept { return v; } + inline int64 ByteOrder::swapIfLittleEndian (const int64 v) noexcept { return v; } + inline float ByteOrder::swapIfLittleEndian (const float v) noexcept { return v; } + inline double ByteOrder::swapIfLittleEndian (const double v) noexcept { return v; } + + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint32 ByteOrder::bigEndianInt (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint64 ByteOrder::bigEndianInt64 (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint16 ByteOrder::bigEndianShort (const void* const bytes) noexcept { return *static_cast (bytes); } + inline bool ByteOrder::isBigEndian() noexcept { return true; } +#endif + +inline int ByteOrder::littleEndian24Bit (const void* const bytes) noexcept { return (((int) static_cast (bytes)[2]) << 16) | (((int) static_cast (bytes)[1]) << 8) | ((int) static_cast (bytes)[0]); } +inline int ByteOrder::bigEndian24Bit (const void* const bytes) noexcept { return (((int) static_cast (bytes)[0]) << 16) | (((int) static_cast (bytes)[1]) << 8) | ((int) static_cast (bytes)[2]); } +inline void ByteOrder::littleEndian24BitToChars (const int value, void* const destBytes) noexcept { static_cast (destBytes)[0] = (uint8) value; static_cast (destBytes)[1] = (uint8) (value >> 8); static_cast (destBytes)[2] = (uint8) (value >> 16); } +inline void ByteOrder::bigEndian24BitToChars (const int value, void* const destBytes) noexcept { static_cast (destBytes)[0] = (uint8) (value >> 16); static_cast (destBytes)[1] = (uint8) (value >> 8); static_cast (destBytes)[2] = (uint8) value; } + + +#endif // JUCE_BYTEORDER_H_INCLUDED diff --git a/source/modules/juce_audio_graph/memory/juce_HeapBlock.h b/source/modules/juce_audio_graph/memory/juce_HeapBlock.h new file mode 100644 index 000000000..5f3a423a8 --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_HeapBlock.h @@ -0,0 +1,314 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_HEAPBLOCK_H_INCLUDED +#define JUCE_HEAPBLOCK_H_INCLUDED + +#if ! (defined (DOXYGEN) || JUCE_EXCEPTIONS_DISABLED) +namespace HeapBlockHelper +{ + template + struct ThrowOnFail { static void checkPointer (void*) {} }; + + template<> + struct ThrowOnFail { static void checkPointer (void* data) { if (data == nullptr) throw std::bad_alloc(); } }; +} +#endif + +//============================================================================== +/** + Very simple container class to hold a pointer to some data on the heap. + + When you need to allocate some heap storage for something, always try to use + this class instead of allocating the memory directly using malloc/free. + + A HeapBlock object can be treated in pretty much exactly the same way + as an char*, but as long as you allocate it on the stack or as a class member, + it's almost impossible for it to leak memory. + + It also makes your code much more concise and readable than doing the same thing + using direct allocations, + + E.g. instead of this: + @code + int* temp = (int*) malloc (1024 * sizeof (int)); + memcpy (temp, xyz, 1024 * sizeof (int)); + free (temp); + temp = (int*) calloc (2048 * sizeof (int)); + temp[0] = 1234; + memcpy (foobar, temp, 2048 * sizeof (int)); + free (temp); + @endcode + + ..you could just write this: + @code + HeapBlock temp (1024); + memcpy (temp, xyz, 1024 * sizeof (int)); + temp.calloc (2048); + temp[0] = 1234; + memcpy (foobar, temp, 2048 * sizeof (int)); + @endcode + + The class is extremely lightweight, containing only a pointer to the + data, and exposes malloc/realloc/calloc/free methods that do the same jobs + as their less object-oriented counterparts. Despite adding safety, you probably + won't sacrifice any performance by using this in place of normal pointers. + + The throwOnFailure template parameter can be set to true if you'd like the class + to throw a std::bad_alloc exception when an allocation fails. If this is false, + then a failed allocation will just leave the heapblock with a null pointer (assuming + that the system's malloc() function doesn't throw). + + @see Array, OwnedArray, MemoryBlock +*/ +template +class HeapBlock +{ +public: + //============================================================================== + /** Creates a HeapBlock which is initially just a null pointer. + + After creation, you can resize the array using the malloc(), calloc(), + or realloc() methods. + */ + HeapBlock() noexcept : data (nullptr) + { + } + + /** Creates a HeapBlock containing a number of elements. + + The contents of the block are undefined, as it will have been created by a + malloc call. + + If you want an array of zero values, you can use the calloc() method or the + other constructor that takes an InitialisationState parameter. + */ + explicit HeapBlock (const size_t numElements) + : data (static_cast (std::malloc (numElements * sizeof (ElementType)))) + { + throwOnAllocationFailure(); + } + + /** Creates a HeapBlock containing a number of elements. + + The initialiseToZero parameter determines whether the new memory should be cleared, + or left uninitialised. + */ + HeapBlock (const size_t numElements, const bool initialiseToZero) + : data (static_cast (initialiseToZero + ? std::calloc (numElements, sizeof (ElementType)) + : std::malloc (numElements * sizeof (ElementType)))) + { + throwOnAllocationFailure(); + } + + /** Destructor. + This will free the data, if any has been allocated. + */ + ~HeapBlock() + { + std::free (data); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + HeapBlock (HeapBlock&& other) noexcept + : data (other.data) + { + other.data = nullptr; + } + + HeapBlock& operator= (HeapBlock&& other) noexcept + { + std::swap (data, other.data); + return *this; + } + #endif + + //============================================================================== + /** Returns a raw pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator ElementType*() const noexcept { return data; } + + /** Returns a raw pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline ElementType* getData() const noexcept { return data; } + + /** Returns a void pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator void*() const noexcept { return static_cast (data); } + + /** Returns a void pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator const void*() const noexcept { return static_cast (data); } + + /** Lets you use indirect calls to the first element in the array. + Obviously this will cause problems if the array hasn't been initialised, because it'll + be referencing a null pointer. + */ + inline ElementType* operator->() const noexcept { return data; } + + /** Returns a reference to one of the data elements. + Obviously there's no bounds-checking here, as this object is just a dumb pointer and + has no idea of the size it currently has allocated. + */ + template + inline ElementType& operator[] (IndexType index) const noexcept { return data [index]; } + + /** Returns a pointer to a data element at an offset from the start of the array. + This is the same as doing pointer arithmetic on the raw pointer itself. + */ + template + inline ElementType* operator+ (IndexType index) const noexcept { return data + index; } + + //============================================================================== + /** Compares the pointer with another pointer. + This can be handy for checking whether this is a null pointer. + */ + inline bool operator== (const ElementType* const otherPointer) const noexcept { return otherPointer == data; } + + /** Compares the pointer with another pointer. + This can be handy for checking whether this is a null pointer. + */ + inline bool operator!= (const ElementType* const otherPointer) const noexcept { return otherPointer != data; } + + //============================================================================== + /** Allocates a specified amount of memory. + + This uses the normal malloc to allocate an amount of memory for this object. + Any previously allocated memory will be freed by this method. + + The number of bytes allocated will be (newNumElements * elementSize). Normally + you wouldn't need to specify the second parameter, but it can be handy if you need + to allocate a size in bytes rather than in terms of the number of elements. + + The data that is allocated will be freed when this object is deleted, or when you + call free() or any of the allocation methods. + */ + void malloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + std::free (data); + data = static_cast (std::malloc (newNumElements * elementSize)); + throwOnAllocationFailure(); + } + + /** Allocates a specified amount of memory and clears it. + This does the same job as the malloc() method, but clears the memory that it allocates. + */ + void calloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + std::free (data); + data = static_cast (std::calloc (newNumElements, elementSize)); + throwOnAllocationFailure(); + } + + /** Allocates a specified amount of memory and optionally clears it. + This does the same job as either malloc() or calloc(), depending on the + initialiseToZero parameter. + */ + void allocate (const size_t newNumElements, bool initialiseToZero) + { + std::free (data); + data = static_cast (initialiseToZero + ? std::calloc (newNumElements, sizeof (ElementType)) + : std::malloc (newNumElements * sizeof (ElementType))); + throwOnAllocationFailure(); + } + + /** Re-allocates a specified amount of memory. + + The semantics of this method are the same as malloc() and calloc(), but it + uses realloc() to keep as much of the existing data as possible. + */ + void realloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + data = static_cast (data == nullptr ? std::malloc (newNumElements * elementSize) + : std::realloc (data, newNumElements * elementSize)); + throwOnAllocationFailure(); + } + + /** Frees any currently-allocated data. + This will free the data and reset this object to be a null pointer. + */ + void free() noexcept + { + std::free (data); + data = nullptr; + } + + /** Swaps this object's data with the data of another HeapBlock. + The two objects simply exchange their data pointers. + */ + template + void swapWith (HeapBlock& other) noexcept + { + std::swap (data, other.data); + } + + /** This fills the block with zeros, up to the number of elements specified. + Since the block has no way of knowing its own size, you must make sure that the number of + elements you specify doesn't exceed the allocated size. + */ + void clear (size_t numElements) noexcept + { + zeromem (data, sizeof (ElementType) * numElements); + } + + /** This typedef can be used to get the type of the heapblock's elements. */ + typedef ElementType Type; + +private: + //============================================================================== + ElementType* data; + + void throwOnAllocationFailure() const + { + #if JUCE_EXCEPTIONS_DISABLED + jassert (data != nullptr); // without exceptions, you'll need to find a better way to handle this failure case. + #else + HeapBlockHelper::ThrowOnFail::checkPointer (data); + #endif + } + + #if ! (defined (JUCE_DLL) || defined (JUCE_DLL_BUILD)) + JUCE_DECLARE_NON_COPYABLE (HeapBlock) + JUCE_PREVENT_HEAP_ALLOCATION // Creating a 'new HeapBlock' would be missing the point! + #endif +}; + + +#endif // JUCE_HEAPBLOCK_H_INCLUDED diff --git a/source/modules/juce_audio_graph/memory/juce_Memory.h b/source/modules/juce_audio_graph/memory/juce_Memory.h new file mode 100644 index 000000000..9b8e8070d --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_Memory.h @@ -0,0 +1,120 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MEMORY_H_INCLUDED +#define JUCE_MEMORY_H_INCLUDED + +//============================================================================== +/** Fills a block of memory with zeros. */ +inline void zeromem (void* memory, size_t numBytes) noexcept { memset (memory, 0, numBytes); } + +/** Overwrites a structure or object with zeros. */ +template +inline void zerostruct (Type& structure) noexcept { memset (&structure, 0, sizeof (structure)); } + +/** Delete an object pointer, and sets the pointer to null. + + Remember that it's not good c++ practice to use delete directly - always try to use a ScopedPointer + or other automatic lifetime-management system rather than resorting to deleting raw pointers! +*/ +template +inline void deleteAndZero (Type& pointer) { delete pointer; pointer = nullptr; } + +/** A handy function which adds a number of bytes to any type of pointer and returns the result. + This can be useful to avoid casting pointers to a char* and back when you want to move them by + a specific number of bytes, +*/ +template +inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept { return (Type*) (((char*) basePointer) + bytes); } + +/** A handy function which returns the difference between any two pointers, in bytes. + The address of the second pointer is subtracted from the first, and the difference in bytes is returned. +*/ +template +inline int getAddressDifference (Type1* pointer1, Type2* pointer2) noexcept { return (int) (((const char*) pointer1) - (const char*) pointer2); } + +/** If a pointer is non-null, this returns a new copy of the object that it points to, or safely returns + nullptr if the pointer is null. +*/ +template +inline Type* createCopyIfNotNull (const Type* objectToCopy) { return objectToCopy != nullptr ? new Type (*objectToCopy) : nullptr; } + +//============================================================================== +/** A handy function to read un-aligned memory without a performance penalty or bus-error. */ +template +inline Type readUnaligned (const void* srcPtr) noexcept +{ + Type value; + memcpy (&value, srcPtr, sizeof (Type)); + + return value; +} + +/** A handy function to write un-aligned memory without a performance penalty or bus-error. */ +template +inline void writeUnaligned (void* dstPtr, Type value) noexcept +{ + memcpy (dstPtr, &value, sizeof(Type)); +} + +//============================================================================== +#if JUCE_MAC || JUCE_IOS || DOXYGEN + + /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. + You should use the JUCE_AUTORELEASEPOOL macro to create a local auto-release pool on the stack. + */ + class JUCE_API ScopedAutoReleasePool + { + public: + ScopedAutoReleasePool(); + ~ScopedAutoReleasePool(); + + private: + void* pool; + + JUCE_DECLARE_NON_COPYABLE (ScopedAutoReleasePool) + }; + + /** A macro that can be used to easily declare a local ScopedAutoReleasePool + object for RAII-based obj-C autoreleasing. + Because this may use the \@autoreleasepool syntax, you must follow the macro with + a set of braces to mark the scope of the pool. + */ +#if (JUCE_COMPILER_SUPPORTS_ARC && defined (__OBJC__)) || DOXYGEN + #define JUCE_AUTORELEASEPOOL @autoreleasepool +#else + #define JUCE_AUTORELEASEPOOL const juce::ScopedAutoReleasePool JUCE_JOIN_MACRO (autoReleasePool_, __LINE__); +#endif + +#else + #define JUCE_AUTORELEASEPOOL +#endif + +#endif // JUCE_MEMORY_H_INCLUDED diff --git a/source/modules/juce_audio_graph/memory/juce_MemoryBlock.cpp b/source/modules/juce_audio_graph/memory/juce_MemoryBlock.cpp new file mode 100644 index 000000000..93b025585 --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_MemoryBlock.cpp @@ -0,0 +1,418 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +MemoryBlock::MemoryBlock() noexcept + : size (0) +{ +} + +MemoryBlock::MemoryBlock (const size_t initialSize, const bool initialiseToZero) +{ + if (initialSize > 0) + { + size = initialSize; + data.allocate (initialSize, initialiseToZero); + } + else + { + size = 0; + } +} + +MemoryBlock::MemoryBlock (const MemoryBlock& other) + : size (other.size) +{ + if (size > 0) + { + jassert (other.data != nullptr); + data.malloc (size); + memcpy (data, other.data, size); + } +} + +MemoryBlock::MemoryBlock (const void* const dataToInitialiseFrom, const size_t sizeInBytes) + : size (sizeInBytes) +{ + jassert (((ssize_t) sizeInBytes) >= 0); + + if (size > 0) + { + jassert (dataToInitialiseFrom != nullptr); // non-zero size, but a zero pointer passed-in? + + data.malloc (size); + + if (dataToInitialiseFrom != nullptr) + memcpy (data, dataToInitialiseFrom, size); + } +} + +MemoryBlock::~MemoryBlock() noexcept +{ +} + +MemoryBlock& MemoryBlock::operator= (const MemoryBlock& other) +{ + if (this != &other) + { + setSize (other.size, false); + memcpy (data, other.data, size); + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +MemoryBlock::MemoryBlock (MemoryBlock&& other) noexcept + : data (static_cast&&> (other.data)), + size (other.size) +{ +} + +MemoryBlock& MemoryBlock::operator= (MemoryBlock&& other) noexcept +{ + data = static_cast&&> (other.data); + size = other.size; + return *this; +} +#endif + + +//============================================================================== +bool MemoryBlock::operator== (const MemoryBlock& other) const noexcept +{ + return matches (other.data, other.size); +} + +bool MemoryBlock::operator!= (const MemoryBlock& other) const noexcept +{ + return ! operator== (other); +} + +bool MemoryBlock::matches (const void* dataToCompare, size_t dataSize) const noexcept +{ + return size == dataSize + && memcmp (data, dataToCompare, size) == 0; +} + +//============================================================================== +// this will resize the block to this size +void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) +{ + if (size != newSize) + { + if (newSize <= 0) + { + reset(); + } + else + { + if (data != nullptr) + { + data.realloc (newSize); + + if (initialiseToZero && (newSize > size)) + zeromem (data + size, newSize - size); + } + else + { + data.allocate (newSize, initialiseToZero); + } + + size = newSize; + } + } +} + +void MemoryBlock::reset() +{ + data.free(); + size = 0; +} + +void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) +{ + if (size < minimumSize) + setSize (minimumSize, initialiseToZero); +} + +void MemoryBlock::swapWith (MemoryBlock& other) noexcept +{ + std::swap (size, other.size); + data.swapWith (other.data); +} + +//============================================================================== +void MemoryBlock::fillWith (const uint8 value) noexcept +{ + memset (data, (int) value, size); +} + +void MemoryBlock::append (const void* const srcData, const size_t numBytes) +{ + if (numBytes > 0) + { + jassert (srcData != nullptr); // this must not be null! + const size_t oldSize = size; + setSize (size + numBytes); + memcpy (data + oldSize, srcData, numBytes); + } +} + +void MemoryBlock::replaceWith (const void* const srcData, const size_t numBytes) +{ + if (numBytes > 0) + { + jassert (srcData != nullptr); // this must not be null! + setSize (numBytes); + memcpy (data, srcData, numBytes); + } +} + +void MemoryBlock::insert (const void* const srcData, const size_t numBytes, size_t insertPosition) +{ + if (numBytes > 0) + { + jassert (srcData != nullptr); // this must not be null! + insertPosition = jmin (size, insertPosition); + const size_t trailingDataSize = size - insertPosition; + setSize (size + numBytes, false); + + if (trailingDataSize > 0) + memmove (data + insertPosition + numBytes, + data + insertPosition, + trailingDataSize); + + memcpy (data + insertPosition, srcData, numBytes); + } +} + +void MemoryBlock::removeSection (const size_t startByte, const size_t numBytesToRemove) +{ + if (startByte + numBytesToRemove >= size) + { + setSize (startByte); + } + else if (numBytesToRemove > 0) + { + memmove (data + startByte, + data + startByte + numBytesToRemove, + size - (startByte + numBytesToRemove)); + + setSize (size - numBytesToRemove); + } +} + +void MemoryBlock::copyFrom (const void* const src, int offset, size_t num) noexcept +{ + const char* d = static_cast (src); + + if (offset < 0) + { + d -= offset; + num += (size_t) -offset; + offset = 0; + } + + if ((size_t) offset + num > size) + num = size - (size_t) offset; + + if (num > 0) + memcpy (data + offset, d, num); +} + +void MemoryBlock::copyTo (void* const dst, int offset, size_t num) const noexcept +{ + char* d = static_cast (dst); + + if (offset < 0) + { + zeromem (d, (size_t) -offset); + d -= offset; + num -= (size_t) -offset; + offset = 0; + } + + if ((size_t) offset + num > size) + { + const size_t newNum = (size_t) size - (size_t) offset; + zeromem (d + newNum, num - newNum); + num = newNum; + } + + if (num > 0) + memcpy (d, data + offset, num); +} + +String MemoryBlock::toString() const +{ + return String::fromUTF8 (data, (int) size); +} + +//============================================================================== +int MemoryBlock::getBitRange (const size_t bitRangeStart, size_t numBits) const noexcept +{ + int res = 0; + + size_t byte = bitRangeStart >> 3; + size_t offsetInByte = bitRangeStart & 7; + size_t bitsSoFar = 0; + + while (numBits > 0 && (size_t) byte < size) + { + const size_t bitsThisTime = jmin (numBits, 8 - offsetInByte); + const int mask = (0xff >> (8 - bitsThisTime)) << offsetInByte; + + res |= (((data[byte] & mask) >> offsetInByte) << bitsSoFar); + + bitsSoFar += bitsThisTime; + numBits -= bitsThisTime; + ++byte; + offsetInByte = 0; + } + + return res; +} + +void MemoryBlock::setBitRange (const size_t bitRangeStart, size_t numBits, int bitsToSet) noexcept +{ + size_t byte = bitRangeStart >> 3; + size_t offsetInByte = bitRangeStart & 7; + uint32 mask = ~((((uint32) 0xffffffff) << (32 - numBits)) >> (32 - numBits)); + + while (numBits > 0 && (size_t) byte < size) + { + const size_t bitsThisTime = jmin (numBits, 8 - offsetInByte); + + const uint32 tempMask = (mask << offsetInByte) | ~((((uint32) 0xffffffff) >> offsetInByte) << offsetInByte); + const uint32 tempBits = (uint32) bitsToSet << offsetInByte; + + data[byte] = (char) (((uint32) data[byte] & tempMask) | tempBits); + + ++byte; + numBits -= bitsThisTime; + bitsToSet >>= bitsThisTime; + mask >>= bitsThisTime; + offsetInByte = 0; + } +} + +//============================================================================== +void MemoryBlock::loadFromHexString (StringRef hex) +{ + ensureSize ((size_t) hex.length() >> 1); + char* dest = data; + String::CharPointerType t (hex.text); + + for (;;) + { + int byte = 0; + + for (int loop = 2; --loop >= 0;) + { + byte <<= 4; + + for (;;) + { + const juce_wchar c = t.getAndAdvance(); + + if (c >= '0' && c <= '9') { byte |= c - '0'; break; } + if (c >= 'a' && c <= 'z') { byte |= c - ('a' - 10); break; } + if (c >= 'A' && c <= 'Z') { byte |= c - ('A' - 10); break; } + + if (c == 0) + { + setSize (static_cast (dest - data)); + return; + } + } + } + + *dest++ = (char) byte; + } +} + +//============================================================================== +static const char base64EncodingTable[] = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; + +String MemoryBlock::toBase64Encoding() const +{ + const size_t numChars = ((size << 3) + 5) / 6; + + String destString ((unsigned int) size); // store the length, followed by a '.', and then the data. + const int initialLen = destString.length(); + destString.preallocateBytes (sizeof (String::CharPointerType::CharType) * (size_t) initialLen + 2 + numChars); + + String::CharPointerType d (destString.getCharPointer()); + d += initialLen; + d.write ('.'); + + for (size_t i = 0; i < numChars; ++i) + d.write ((juce_wchar) (uint8) base64EncodingTable [getBitRange (i * 6, 6)]); + + d.writeNull(); + return destString; +} + +static const char base64DecodingTable[] = +{ + 63, 0, 0, 0, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 0, 0, 0, 0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 +}; + +bool MemoryBlock::fromBase64Encoding (StringRef s) +{ + String::CharPointerType dot (CharacterFunctions::find (s.text, (juce_wchar) '.')); + + if (dot.isEmpty()) + return false; + + const int numBytesNeeded = String (s.text, dot).getIntValue(); + + setSize ((size_t) numBytesNeeded, true); + + String::CharPointerType srcChars (dot + 1); + int pos = 0; + + for (;;) + { + int c = (int) srcChars.getAndAdvance(); + + if (c == 0) + return true; + + c -= 43; + if (isPositiveAndBelow (c, numElementsInArray (base64DecodingTable))) + { + setBitRange ((size_t) pos, 6, base64DecodingTable [c]); + pos += 6; + } + } +} diff --git a/source/modules/juce_audio_graph/memory/juce_MemoryBlock.h b/source/modules/juce_audio_graph/memory/juce_MemoryBlock.h new file mode 100644 index 000000000..73426569f --- /dev/null +++ b/source/modules/juce_audio_graph/memory/juce_MemoryBlock.h @@ -0,0 +1,263 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYBLOCK_H_INCLUDED +#define JUCE_MEMORYBLOCK_H_INCLUDED + + +//============================================================================== +/** + A class to hold a resizable block of raw data. + +*/ +class JUCE_API MemoryBlock +{ +public: + //============================================================================== + /** Create an uninitialised block with 0 size. */ + MemoryBlock() noexcept; + + /** Creates a memory block with a given initial size. + + @param initialSize the size of block to create + @param initialiseToZero whether to clear the memory or just leave it uninitialised + */ + MemoryBlock (const size_t initialSize, + bool initialiseToZero = false); + + /** Creates a copy of another memory block. */ + MemoryBlock (const MemoryBlock&); + + /** Creates a memory block using a copy of a block of data. + + @param dataToInitialiseFrom some data to copy into this block + @param sizeInBytes how much space to use + */ + MemoryBlock (const void* dataToInitialiseFrom, size_t sizeInBytes); + + /** Destructor. */ + ~MemoryBlock() noexcept; + + /** Copies another memory block onto this one. + This block will be resized and copied to exactly match the other one. + */ + MemoryBlock& operator= (const MemoryBlock&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + MemoryBlock (MemoryBlock&&) noexcept; + MemoryBlock& operator= (MemoryBlock&&) noexcept; + #endif + + //============================================================================== + /** Compares two memory blocks. + @returns true only if the two blocks are the same size and have identical contents. + */ + bool operator== (const MemoryBlock& other) const noexcept; + + /** Compares two memory blocks. + @returns true if the two blocks are different sizes or have different contents. + */ + bool operator!= (const MemoryBlock& other) const noexcept; + + /** Returns true if the data in this MemoryBlock matches the raw bytes passed-in. */ + bool matches (const void* data, size_t dataSize) const noexcept; + + //============================================================================== + /** Returns a void pointer to the data. + + Note that the pointer returned will probably become invalid when the + block is resized. + */ + void* getData() const noexcept { return data; } + + /** Returns a byte from the memory block. + This returns a reference, so you can also use it to set a byte. + */ + template + char& operator[] (const Type offset) const noexcept { return data [offset]; } + + + //============================================================================== + /** Returns the block's current allocated size, in bytes. */ + size_t getSize() const noexcept { return size; } + + /** Resizes the memory block. + + Any data that is present in both the old and new sizes will be retained. + When enlarging the block, the new space that is allocated at the end can either be + cleared, or left uninitialised. + + @param newSize the new desired size for the block + @param initialiseNewSpaceToZero if the block gets enlarged, this determines + whether to clear the new section or just leave it + uninitialised + @see ensureSize + */ + void setSize (const size_t newSize, + bool initialiseNewSpaceToZero = false); + + /** Increases the block's size only if it's smaller than a given size. + + @param minimumSize if the block is already bigger than this size, no action + will be taken; otherwise it will be increased to this size + @param initialiseNewSpaceToZero if the block gets enlarged, this determines + whether to clear the new section or just leave it + uninitialised + @see setSize + */ + void ensureSize (const size_t minimumSize, + bool initialiseNewSpaceToZero = false); + + /** Frees all the blocks data, setting its size to 0. */ + void reset(); + + //============================================================================== + /** Fills the entire memory block with a repeated byte value. + This is handy for clearing a block of memory to zero. + */ + void fillWith (uint8 valueToUse) noexcept; + + /** Adds another block of data to the end of this one. + The data pointer must not be null. This block's size will be increased accordingly. + */ + void append (const void* data, size_t numBytes); + + /** Resizes this block to the given size and fills its contents from the supplied buffer. + The data pointer must not be null. + */ + void replaceWith (const void* data, size_t numBytes); + + /** Inserts some data into the block. + The dataToInsert pointer must not be null. This block's size will be increased accordingly. + If the insert position lies outside the valid range of the block, it will be clipped to + within the range before being used. + */ + void insert (const void* dataToInsert, size_t numBytesToInsert, size_t insertPosition); + + /** Chops out a section of the block. + + This will remove a section of the memory block and close the gap around it, + shifting any subsequent data downwards and reducing the size of the block. + + If the range specified goes beyond the size of the block, it will be clipped. + */ + void removeSection (size_t startByte, size_t numBytesToRemove); + + //============================================================================== + /** Copies data into this MemoryBlock from a memory address. + + @param srcData the memory location of the data to copy into this block + @param destinationOffset the offset in this block at which the data being copied should begin + @param numBytes how much to copy in (if this goes beyond the size of the memory block, + it will be clipped so not to do anything nasty) + */ + void copyFrom (const void* srcData, + int destinationOffset, + size_t numBytes) noexcept; + + /** Copies data from this MemoryBlock to a memory address. + + @param destData the memory location to write to + @param sourceOffset the offset within this block from which the copied data will be read + @param numBytes how much to copy (if this extends beyond the limits of the memory block, + zeros will be used for that portion of the data) + */ + void copyTo (void* destData, + int sourceOffset, + size_t numBytes) const noexcept; + + //============================================================================== + /** Exchanges the contents of this and another memory block. + No actual copying is required for this, so it's very fast. + */ + void swapWith (MemoryBlock& other) noexcept; + + //============================================================================== + /** Attempts to parse the contents of the block as a zero-terminated UTF8 string. */ + String toString() const; + + //============================================================================== + /** Parses a string of hexadecimal numbers and writes this data into the memory block. + + The block will be resized to the number of valid bytes read from the string. + Non-hex characters in the string will be ignored. + + @see String::toHexString() + */ + void loadFromHexString (StringRef sourceHexString); + + //============================================================================== + /** Sets a number of bits in the memory block, treating it as a long binary sequence. */ + void setBitRange (size_t bitRangeStart, + size_t numBits, + int binaryNumberToApply) noexcept; + + /** Reads a number of bits from the memory block, treating it as one long binary sequence */ + int getBitRange (size_t bitRangeStart, + size_t numBitsToRead) const noexcept; + + //============================================================================== + /** Returns a string of characters in a JUCE-specific text encoding that represents the + binary contents of this block. + + This uses a JUCE-specific (i.e. not standard!) 64-bit encoding system to convert binary + data into a string of ASCII characters for purposes like storage in XML. + Note that this proprietary format is mainly kept here for backwards-compatibility, and + you may prefer to use the Base64::toBase64() method if you want to use the standard + base-64 encoding. + + @see fromBase64Encoding, Base64::toBase64, Base64::convertToBase64 + */ + String toBase64Encoding() const; + + /** Takes a string created by MemoryBlock::toBase64Encoding() and extracts the original data. + + The string passed in must have been created by to64BitEncoding(), and this + block will be resized to recreate the original data block. + + Note that these methods use a JUCE-specific (i.e. not standard!) 64-bit encoding system. + You may prefer to use the Base64::convertFromBase64() method if you want to use the + standard base-64 encoding. + + @see toBase64Encoding, Base64::convertFromBase64 + */ + bool fromBase64Encoding (StringRef encodedString); + + +private: + //============================================================================== + HeapBlock data; + size_t size; + + JUCE_LEAK_DETECTOR (MemoryBlock) +}; + + +#endif // JUCE_MEMORYBLOCK_H_INCLUDED diff --git a/source/modules/juce_audio_graph/midi/juce_MidiBuffer.cpp b/source/modules/juce_audio_graph/midi/juce_MidiBuffer.cpp new file mode 100644 index 000000000..b1ce34473 --- /dev/null +++ b/source/modules/juce_audio_graph/midi/juce_MidiBuffer.cpp @@ -0,0 +1,235 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +namespace MidiBufferHelpers +{ + inline int getEventTime (const void* const d) noexcept + { + return readUnaligned (d); + } + + inline uint16 getEventDataSize (const void* const d) noexcept + { + return readUnaligned (static_cast (d) + sizeof (int32)); + } + + inline uint16 getEventTotalSize (const void* const d) noexcept + { + return getEventDataSize (d) + sizeof (int32) + sizeof (uint16); + } + + static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept + { + unsigned int byte = (unsigned int) *data; + int size = 0; + + if (byte == 0xf0 || byte == 0xf7) + { + const uint8* d = data + 1; + + while (d < data + maxBytes) + if (*d++ == 0xf7) + break; + + size = (int) (d - data); + } + else if (byte == 0xff) + { + int n; + const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); + size = jmin (maxBytes, n + 2 + bytesLeft); + } + else if (byte >= 0x80) + { + size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); + } + + return size; + } + + static uint8* findEventAfter (uint8* d, uint8* endData, const int samplePosition) noexcept + { + while (d < endData && getEventTime (d) <= samplePosition) + d += getEventTotalSize (d); + + return d; + } +} + +//============================================================================== +MidiBuffer::MidiBuffer() noexcept {} +MidiBuffer::~MidiBuffer() {} + +MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {} + +MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept +{ + data = other.data; + return *this; +} + +MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept +{ + addEvent (message, 0); +} + +void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); } +void MidiBuffer::clear() noexcept { data.clearQuick(); } +void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } +bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } + +void MidiBuffer::clear (const int startSample, const int numSamples) +{ + uint8* const start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); + uint8* const end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); + + data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); +} + +void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber) +{ + addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); +} + +void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber) +{ + const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast (newData), maxBytes); + + if (numBytes > 0) + { + const size_t newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); + const int offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); + + data.insertMultiple (offset, 0, (int) newItemSize); + + uint8* const d = data.begin() + offset; + writeUnaligned (d, sampleNumber); + writeUnaligned (d + 4, static_cast (numBytes)); + memcpy (d + 6, newData, (size_t) numBytes); + } +} + +void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, + const int startSample, + const int numSamples, + const int sampleDeltaToAdd) +{ + Iterator i (otherBuffer); + i.setNextSamplePosition (startSample); + + const uint8* eventData; + int eventSize, position; + + while (i.getNextEvent (eventData, eventSize, position) + && (position < startSample + numSamples || numSamples < 0)) + { + addEvent (eventData, eventSize, position + sampleDeltaToAdd); + } +} + +int MidiBuffer::getNumEvents() const noexcept +{ + int n = 0; + const uint8* const end = data.end(); + + for (const uint8* d = data.begin(); d < end; ++n) + d += MidiBufferHelpers::getEventTotalSize (d); + + return n; +} + +int MidiBuffer::getFirstEventTime() const noexcept +{ + return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0; +} + +int MidiBuffer::getLastEventTime() const noexcept +{ + if (data.size() == 0) + return 0; + + const uint8* const endData = data.end(); + + for (const uint8* d = data.begin();;) + { + const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d); + + if (nextOne >= endData) + return MidiBufferHelpers::getEventTime (d); + + d = nextOne; + } +} + +//============================================================================== +MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept + : buffer (b), data (b.data.begin()) +{ +} + +MidiBuffer::Iterator::~Iterator() noexcept +{ +} + +void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept +{ + data = buffer.data.begin(); + const uint8* const dataEnd = buffer.data.end(); + + while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) + data += MidiBufferHelpers::getEventTotalSize (data); +} + +bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept +{ + if (data >= buffer.data.end()) + return false; + + samplePosition = MidiBufferHelpers::getEventTime (data); + const int itemSize = MidiBufferHelpers::getEventDataSize (data); + numBytes = itemSize; + midiData = data + sizeof (int32) + sizeof (uint16); + data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; + + return true; +} + +bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept +{ + if (data >= buffer.data.end()) + return false; + + samplePosition = MidiBufferHelpers::getEventTime (data); + const int itemSize = MidiBufferHelpers::getEventDataSize (data); + result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition); + data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; + + return true; +} diff --git a/source/modules/juce_audio_graph/midi/juce_MidiBuffer.h b/source/modules/juce_audio_graph/midi/juce_MidiBuffer.h new file mode 100644 index 000000000..36dde82f6 --- /dev/null +++ b/source/modules/juce_audio_graph/midi/juce_MidiBuffer.h @@ -0,0 +1,241 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MIDIBUFFER_H_INCLUDED +#define JUCE_MIDIBUFFER_H_INCLUDED + + +//============================================================================== +/** + Holds a sequence of time-stamped midi events. + + Analogous to the AudioSampleBuffer, this holds a set of midi events with + integer time-stamps. The buffer is kept sorted in order of the time-stamps. + + If you're working with a sequence of midi events that may need to be manipulated + or read/written to a midi file, then MidiMessageSequence is probably a more + appropriate container. MidiBuffer is designed for lower-level streams of raw + midi data. + + @see MidiMessage +*/ +class JUCE_API MidiBuffer +{ +public: + //============================================================================== + /** Creates an empty MidiBuffer. */ + MidiBuffer() noexcept; + + /** Creates a MidiBuffer containing a single midi message. */ + explicit MidiBuffer (const MidiMessage& message) noexcept; + + /** Creates a copy of another MidiBuffer. */ + MidiBuffer (const MidiBuffer&) noexcept; + + /** Makes a copy of another MidiBuffer. */ + MidiBuffer& operator= (const MidiBuffer&) noexcept; + + /** Destructor */ + ~MidiBuffer(); + + //============================================================================== + /** Removes all events from the buffer. */ + void clear() noexcept; + + /** Removes all events between two times from the buffer. + + All events for which (start <= event position < start + numSamples) will + be removed. + */ + void clear (int start, int numSamples); + + /** Returns true if the buffer is empty. + To actually retrieve the events, use a MidiBuffer::Iterator object + */ + bool isEmpty() const noexcept; + + /** Counts the number of events in the buffer. + + This is actually quite a slow operation, as it has to iterate through all + the events, so you might prefer to call isEmpty() if that's all you need + to know. + */ + int getNumEvents() const noexcept; + + /** Adds an event to the buffer. + + The sample number will be used to determine the position of the event in + the buffer, which is always kept sorted. The MidiMessage's timestamp is + ignored. + + If an event is added whose sample position is the same as one or more events + already in the buffer, the new event will be placed after the existing ones. + + To retrieve events, use a MidiBuffer::Iterator object + */ + void addEvent (const MidiMessage& midiMessage, int sampleNumber); + + /** Adds an event to the buffer from raw midi data. + + The sample number will be used to determine the position of the event in + the buffer, which is always kept sorted. + + If an event is added whose sample position is the same as one or more events + already in the buffer, the new event will be placed after the existing ones. + + The event data will be inspected to calculate the number of bytes in length that + the midi event really takes up, so maxBytesOfMidiData may be longer than the data + that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, + it'll actually only store 3 bytes. If the midi data is invalid, it might not + add an event at all. + + To retrieve events, use a MidiBuffer::Iterator object + */ + void addEvent (const void* rawMidiData, + int maxBytesOfMidiData, + int sampleNumber); + + /** Adds some events from another buffer to this one. + + @param otherBuffer the buffer containing the events you want to add + @param startSample the lowest sample number in the source buffer for which + events should be added. Any source events whose timestamp is + less than this will be ignored + @param numSamples the valid range of samples from the source buffer for which + events should be added - i.e. events in the source buffer whose + timestamp is greater than or equal to (startSample + numSamples) + will be ignored. If this value is less than 0, all events after + startSample will be taken. + @param sampleDeltaToAdd a value which will be added to the source timestamps of the events + that are added to this buffer + */ + void addEvents (const MidiBuffer& otherBuffer, + int startSample, + int numSamples, + int sampleDeltaToAdd); + + /** Returns the sample number of the first event in the buffer. + If the buffer's empty, this will just return 0. + */ + int getFirstEventTime() const noexcept; + + /** Returns the sample number of the last event in the buffer. + If the buffer's empty, this will just return 0. + */ + int getLastEventTime() const noexcept; + + //============================================================================== + /** Exchanges the contents of this buffer with another one. + + This is a quick operation, because no memory allocating or copying is done, it + just swaps the internal state of the two buffers. + */ + void swapWith (MidiBuffer&) noexcept; + + /** Preallocates some memory for the buffer to use. + This helps to avoid needing to reallocate space when the buffer has messages + added to it. + */ + void ensureSize (size_t minimumNumBytes); + + //============================================================================== + /** + Used to iterate through the events in a MidiBuffer. + + Note that altering the buffer while an iterator is using it isn't a + safe operation. + + @see MidiBuffer + */ + class JUCE_API Iterator + { + public: + //============================================================================== + /** Creates an Iterator for this MidiBuffer. */ + Iterator (const MidiBuffer&) noexcept; + + /** Destructor. */ + ~Iterator() noexcept; + + //============================================================================== + /** Repositions the iterator so that the next event retrieved will be the first + one whose sample position is at greater than or equal to the given position. + */ + void setNextSamplePosition (int samplePosition) noexcept; + + /** Retrieves a copy of the next event from the buffer. + + @param result on return, this will be the message. The MidiMessage's timestamp + is set to the same value as samplePosition. + @param samplePosition on return, this will be the position of the event, as a + sample index in the buffer + @returns true if an event was found, or false if the iterator has reached + the end of the buffer + */ + bool getNextEvent (MidiMessage& result, + int& samplePosition) noexcept; + + /** Retrieves the next event from the buffer. + + @param midiData on return, this pointer will be set to a block of data containing + the midi message. Note that to make it fast, this is a pointer + directly into the MidiBuffer's internal data, so is only valid + temporarily until the MidiBuffer is altered. + @param numBytesOfMidiData on return, this is the number of bytes of data used by the + midi message + @param samplePosition on return, this will be the position of the event, as a + sample index in the buffer + @returns true if an event was found, or false if the iterator has reached + the end of the buffer + */ + bool getNextEvent (const uint8* &midiData, + int& numBytesOfMidiData, + int& samplePosition) noexcept; + + private: + //============================================================================== + const MidiBuffer& buffer; + const uint8* data; + + JUCE_DECLARE_NON_COPYABLE (Iterator) + }; + + /** The raw data holding this buffer. + Obviously access to this data is provided at your own risk. Its internal format could + change in future, so don't write code that relies on it! + */ + Array data; + +private: + JUCE_LEAK_DETECTOR (MidiBuffer) +}; + + +#endif // JUCE_MIDIBUFFER_H_INCLUDED diff --git a/source/modules/juce_audio_graph/midi/juce_MidiMessage.cpp b/source/modules/juce_audio_graph/midi/juce_MidiMessage.cpp new file mode 100644 index 000000000..2c683516c --- /dev/null +++ b/source/modules/juce_audio_graph/midi/juce_MidiMessage.cpp @@ -0,0 +1,1150 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +namespace MidiHelpers +{ + inline uint8 initialByte (const int type, const int channel) noexcept + { + return (uint8) (type | jlimit (0, 15, channel - 1)); + } + + inline uint8 validVelocity (const int v) noexcept + { + return (uint8) jlimit (0, 127, v); + } +} + +//============================================================================== +uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept +{ + return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); +} + +uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, + const float pitchbendRange) noexcept +{ + // can't translate a pitchbend value that is outside of the given range! + jassert (std::abs (pitchbend) <= pitchbendRange); + + return static_cast (pitchbend > 0.0f + ? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) + : jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); +} + +//============================================================================== +int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept +{ + numBytesUsed = 0; + int v = 0, i; + + do + { + i = (int) *data++; + + if (++numBytesUsed > 6) + break; + + v = (v << 7) + (i & 0x7f); + + } while (i & 0x80); + + return v; +} + +int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept +{ + // this method only works for valid starting bytes of a short midi message + jassert (firstByte >= 0x80 && firstByte != 0xf0 && firstByte != 0xf7); + + static const char messageLengths[] = + { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + return messageLengths [firstByte & 0x7f]; +} + +//============================================================================== +MidiMessage::MidiMessage() noexcept + : timeStamp (0), size (2) +{ + packedData.asBytes[0] = 0xf0; + packedData.asBytes[1] = 0xf7; +} + +MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) + : timeStamp (t), size (dataSize) +{ + jassert (dataSize > 0); + // this checks that the length matches the data.. + jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); + + memcpy (allocateSpace (dataSize), d, (size_t) dataSize); +} + +MidiMessage::MidiMessage (const int byte1, const double t) noexcept + : timeStamp (t), size (1) +{ + packedData.asBytes[0] = (uint8) byte1; + + // check that the length matches the data.. + jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); +} + +MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept + : timeStamp (t), size (2) +{ + packedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[1] = (uint8) byte2; + + // check that the length matches the data.. + jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); +} + +MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept + : timeStamp (t), size (3) +{ + packedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[1] = (uint8) byte2; + packedData.asBytes[2] = (uint8) byte3; + + // check that the length matches the data.. + jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); +} + +MidiMessage::MidiMessage (const MidiMessage& other) + : timeStamp (other.timeStamp), size (other.size) +{ + if (isHeapAllocated()) + memcpy (allocateSpace (size), other.getData(), (size_t) size); + else + packedData.allocatedData = other.packedData.allocatedData; +} + +MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) + : timeStamp (newTimeStamp), size (other.size) +{ + if (isHeapAllocated()) + memcpy (allocateSpace (size), other.getData(), (size_t) size); + else + packedData.allocatedData = other.packedData.allocatedData; +} + +MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, + double t, bool sysexHasEmbeddedLength) + : timeStamp (t) +{ + const uint8* src = static_cast (srcData); + unsigned int byte = (unsigned int) *src; + + if (byte < 0x80) + { + byte = (unsigned int) (uint8) lastStatusByte; + numBytesUsed = -1; + } + else + { + numBytesUsed = 0; + --sz; + ++src; + } + + if (byte >= 0x80) + { + if (byte == 0xf0) + { + const uint8* d = src; + bool haveReadAllLengthBytes = ! sysexHasEmbeddedLength; + int numVariableLengthSysexBytes = 0; + + while (d < src + sz) + { + if (*d >= 0x80) + { + if (*d == 0xf7) + { + ++d; // include the trailing 0xf7 when we hit it + break; + } + + if (haveReadAllLengthBytes) // if we see a 0x80 bit set after the initial data length + break; // bytes, assume it's the end of the sysex + + ++numVariableLengthSysexBytes; + } + else if (! haveReadAllLengthBytes) + { + haveReadAllLengthBytes = true; + ++numVariableLengthSysexBytes; + } + + ++d; + } + + src += numVariableLengthSysexBytes; + size = 1 + (int) (d - src); + + uint8* dest = allocateSpace (size); + *dest = (uint8) byte; + memcpy (dest + 1, src, (size_t) (size - 1)); + + numBytesUsed += numVariableLengthSysexBytes; // (these aren't counted in the size) + } + else if (byte == 0xff) + { + int n; + const int bytesLeft = readVariableLengthVal (src + 1, n); + size = jmin (sz + 1, n + 2 + bytesLeft); + + uint8* dest = allocateSpace (size); + *dest = (uint8) byte; + memcpy (dest + 1, src, (size_t) size - 1); + } + else + { + size = getMessageLengthFromFirstByte ((uint8) byte); + packedData.asBytes[0] = (uint8) byte; + + if (size > 1) + { + packedData.asBytes[1] = src[0]; + + if (size > 2) + packedData.asBytes[2] = src[1]; + } + } + + numBytesUsed += size; + } + else + { + packedData.allocatedData = nullptr; + size = 0; + } +} + +MidiMessage& MidiMessage::operator= (const MidiMessage& other) +{ + if (this != &other) + { + if (other.isHeapAllocated()) + { + if (isHeapAllocated()) + packedData.allocatedData = static_cast (std::realloc (packedData.allocatedData, (size_t) other.size)); + else + packedData.allocatedData = static_cast (std::malloc ((size_t) other.size)); + + memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); + } + else + { + if (isHeapAllocated()) + std::free (packedData.allocatedData); + + packedData.allocatedData = other.packedData.allocatedData; + } + + timeStamp = other.timeStamp; + size = other.size; + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +MidiMessage::MidiMessage (MidiMessage&& other) noexcept + : timeStamp (other.timeStamp), size (other.size) +{ + packedData.allocatedData = other.packedData.allocatedData; + other.size = 0; +} + +MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept +{ + packedData.allocatedData = other.packedData.allocatedData; + timeStamp = other.timeStamp; + size = other.size; + other.size = 0; + return *this; +} +#endif + +MidiMessage::~MidiMessage() noexcept +{ + if (isHeapAllocated()) + std::free (packedData.allocatedData); +} + +uint8* MidiMessage::allocateSpace (int bytes) +{ + if (bytes > (int) sizeof (packedData)) + { + uint8* d = static_cast (std::malloc ((size_t) bytes)); + packedData.allocatedData = d; + return d; + } + + return packedData.asBytes; +} + +String MidiMessage::getDescription() const +{ + if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); + if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); + if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); + if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); + if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); + if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); + if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); + if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); + if (isMetaEvent()) return "Meta event"; + + if (isController()) + { + String name (MidiMessage::getControllerName (getControllerNumber())); + + if (name.isEmpty()) + name = String (getControllerNumber()); + + return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); + } + + return String::toHexString (getRawData(), getRawDataSize()); +} + +int MidiMessage::getChannel() const noexcept +{ + const uint8* const data = getRawData(); + + if ((data[0] & 0xf0) != 0xf0) + return (data[0] & 0xf) + 1; + + return 0; +} + +bool MidiMessage::isForChannel (const int channel) const noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + + const uint8* const data = getRawData(); + + return ((data[0] & 0xf) == channel - 1) + && ((data[0] & 0xf0) != 0xf0); +} + +void MidiMessage::setChannel (const int channel) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + + uint8* const data = getData(); + + if ((data[0] & 0xf0) != (uint8) 0xf0) + data[0] = (uint8) ((data[0] & (uint8) 0xf0) + | (uint8)(channel - 1)); +} + +bool MidiMessage::isNoteOn (const bool returnTrueForVelocity0) const noexcept +{ + const uint8* const data = getRawData(); + + return ((data[0] & 0xf0) == 0x90) + && (returnTrueForVelocity0 || data[2] != 0); +} + +bool MidiMessage::isNoteOff (const bool returnTrueForNoteOnVelocity0) const noexcept +{ + const uint8* const data = getRawData(); + + return ((data[0] & 0xf0) == 0x80) + || (returnTrueForNoteOnVelocity0 && (data[2] == 0) && ((data[0] & 0xf0) == 0x90)); +} + +bool MidiMessage::isNoteOnOrOff() const noexcept +{ + const uint8* const data = getRawData(); + + const int d = data[0] & 0xf0; + return (d == 0x90) || (d == 0x80); +} + +int MidiMessage::getNoteNumber() const noexcept +{ + return getRawData()[1]; +} + +void MidiMessage::setNoteNumber (const int newNoteNumber) noexcept +{ + if (isNoteOnOrOff() || isAftertouch()) + getData()[1] = (uint8) (newNoteNumber & 127); +} + +uint8 MidiMessage::getVelocity() const noexcept +{ + if (isNoteOnOrOff()) + return getRawData()[2]; + + return 0; +} + +float MidiMessage::getFloatVelocity() const noexcept +{ + return getVelocity() * (1.0f / 127.0f); +} + +void MidiMessage::setVelocity (const float newVelocity) noexcept +{ + if (isNoteOnOrOff()) + getData()[2] = floatValueToMidiByte (newVelocity); +} + +void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept +{ + if (isNoteOnOrOff()) + { + uint8* const data = getData(); + data[2] = MidiHelpers::validVelocity (roundToInt (scaleFactor * data[2])); + } +} + +bool MidiMessage::isAftertouch() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xa0; +} + +int MidiMessage::getAfterTouchValue() const noexcept +{ + jassert (isAftertouch()); + return getRawData()[2]; +} + +MidiMessage MidiMessage::aftertouchChange (const int channel, + const int noteNum, + const int aftertouchValue) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + jassert (isPositiveAndBelow (noteNum, (int) 128)); + jassert (isPositiveAndBelow (aftertouchValue, (int) 128)); + + return MidiMessage (MidiHelpers::initialByte (0xa0, channel), + noteNum & 0x7f, + aftertouchValue & 0x7f); +} + +bool MidiMessage::isChannelPressure() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xd0; +} + +int MidiMessage::getChannelPressureValue() const noexcept +{ + jassert (isChannelPressure()); + return getRawData()[1]; +} + +MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + jassert (isPositiveAndBelow (pressure, (int) 128)); + + return MidiMessage (MidiHelpers::initialByte (0xd0, channel), pressure & 0x7f); +} + +bool MidiMessage::isSustainPedalOn() const noexcept { return isControllerOfType (0x40) && getRawData()[2] >= 64; } +bool MidiMessage::isSustainPedalOff() const noexcept { return isControllerOfType (0x40) && getRawData()[2] < 64; } + +bool MidiMessage::isSostenutoPedalOn() const noexcept { return isControllerOfType (0x42) && getRawData()[2] >= 64; } +bool MidiMessage::isSostenutoPedalOff() const noexcept { return isControllerOfType (0x42) && getRawData()[2] < 64; } + +bool MidiMessage::isSoftPedalOn() const noexcept { return isControllerOfType (0x43) && getRawData()[2] >= 64; } +bool MidiMessage::isSoftPedalOff() const noexcept { return isControllerOfType (0x43) && getRawData()[2] < 64; } + + +bool MidiMessage::isProgramChange() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xc0; +} + +int MidiMessage::getProgramChangeNumber() const noexcept +{ + jassert (isProgramChange()); + return getRawData()[1]; +} + +MidiMessage MidiMessage::programChange (const int channel, const int programNumber) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + + return MidiMessage (MidiHelpers::initialByte (0xc0, channel), programNumber & 0x7f); +} + +bool MidiMessage::isPitchWheel() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xe0; +} + +int MidiMessage::getPitchWheelValue() const noexcept +{ + jassert (isPitchWheel()); + const uint8* const data = getRawData(); + return data[1] | (data[2] << 7); +} + +MidiMessage MidiMessage::pitchWheel (const int channel, const int position) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + jassert (isPositiveAndBelow (position, (int) 0x4000)); + + return MidiMessage (MidiHelpers::initialByte (0xe0, channel), + position & 127, (position >> 7) & 127); +} + +bool MidiMessage::isController() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xb0; +} + +bool MidiMessage::isControllerOfType (const int controllerType) const noexcept +{ + const uint8* const data = getRawData(); + return (data[0] & 0xf0) == 0xb0 && data[1] == controllerType; +} + +int MidiMessage::getControllerNumber() const noexcept +{ + jassert (isController()); + return getRawData()[1]; +} + +int MidiMessage::getControllerValue() const noexcept +{ + jassert (isController()); + return getRawData()[2]; +} + +MidiMessage MidiMessage::controllerEvent (const int channel, const int controllerType, const int value) noexcept +{ + // the channel must be between 1 and 16 inclusive + jassert (channel > 0 && channel <= 16); + + return MidiMessage (MidiHelpers::initialByte (0xb0, channel), + controllerType & 127, value & 127); +} + +MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const uint8 velocity) noexcept +{ + jassert (channel > 0 && channel <= 16); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); + + return MidiMessage (MidiHelpers::initialByte (0x90, channel), + noteNumber & 127, MidiHelpers::validVelocity (velocity)); +} + +MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept +{ + return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); +} + +MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept +{ + jassert (channel > 0 && channel <= 16); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); + + return MidiMessage (MidiHelpers::initialByte (0x80, channel), + noteNumber & 127, MidiHelpers::validVelocity (velocity)); +} + +MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept +{ + return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); +} + +MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept +{ + jassert (channel > 0 && channel <= 16); + jassert (isPositiveAndBelow (noteNumber, (int) 128)); + + return MidiMessage (MidiHelpers::initialByte (0x80, channel), noteNumber & 127, 0); +} + +MidiMessage MidiMessage::allNotesOff (const int channel) noexcept +{ + return controllerEvent (channel, 123, 0); +} + +bool MidiMessage::isAllNotesOff() const noexcept +{ + const uint8* const data = getRawData(); + return (data[0] & 0xf0) == 0xb0 && data[1] == 123; +} + +MidiMessage MidiMessage::allSoundOff (const int channel) noexcept +{ + return controllerEvent (channel, 120, 0); +} + +bool MidiMessage::isAllSoundOff() const noexcept +{ + const uint8* const data = getRawData(); + return (data[0] & 0xf0) == 0xb0 && data[1] == 120; +} + +MidiMessage MidiMessage::allControllersOff (const int channel) noexcept +{ + return controllerEvent (channel, 121, 0); +} + +MidiMessage MidiMessage::masterVolume (const float volume) +{ + const int vol = jlimit (0, 0x3fff, roundToInt (volume * 0x4000)); + + const uint8 buf[] = { 0xf0, 0x7f, 0x7f, 0x04, 0x01, + (uint8) (vol & 0x7f), + (uint8) (vol >> 7), + 0xf7 }; + + return MidiMessage (buf, 8); +} + +//============================================================================== +bool MidiMessage::isSysEx() const noexcept +{ + return *getRawData() == 0xf0; +} + +MidiMessage MidiMessage::createSysExMessage (const void* sysexData, const int dataSize) +{ + HeapBlock m ((size_t) dataSize + 2); + + m[0] = 0xf0; + memcpy (m + 1, sysexData, (size_t) dataSize); + m[dataSize + 1] = 0xf7; + + return MidiMessage (m, dataSize + 2); +} + +const uint8* MidiMessage::getSysExData() const noexcept +{ + return isSysEx() ? getRawData() + 1 : nullptr; +} + +int MidiMessage::getSysExDataSize() const noexcept +{ + return isSysEx() ? size - 2 : 0; +} + +//============================================================================== +bool MidiMessage::isMetaEvent() const noexcept { return *getRawData() == 0xff; } +bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0xfe; } + +int MidiMessage::getMetaEventType() const noexcept +{ + const uint8* const data = getRawData(); + return *data != 0xff ? -1 : data[1]; +} + +int MidiMessage::getMetaEventLength() const noexcept +{ + const uint8* const data = getRawData(); + if (*data == 0xff) + { + int n; + return jmin (size - 2, readVariableLengthVal (data + 2, n)); + } + + return 0; +} + +const uint8* MidiMessage::getMetaEventData() const noexcept +{ + jassert (isMetaEvent()); + + int n; + const uint8* d = getRawData() + 2; + readVariableLengthVal (d, n); + return d + n; +} + +bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } +bool MidiMessage::isEndOfTrackMetaEvent() const noexcept { return getMetaEventType() == 47; } + +bool MidiMessage::isTextMetaEvent() const noexcept +{ + const int t = getMetaEventType(); + return t > 0 && t < 16; +} + +String MidiMessage::getTextFromTextMetaEvent() const +{ + const char* const textData = reinterpret_cast (getMetaEventData()); + return String (CharPointer_UTF8 (textData), + CharPointer_UTF8 (textData + getMetaEventLength())); +} + +MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) +{ + jassert (type > 0 && type < 16); + + MidiMessage result; + + const size_t textSize = text.text.sizeInBytes() - 1; + + uint8 header[8]; + size_t n = sizeof (header); + + header[--n] = (uint8) (textSize & 0x7f); + + for (size_t i = textSize; (i >>= 7) != 0;) + header[--n] = (uint8) ((i & 0x7f) | 0x80); + + header[--n] = (uint8) type; + header[--n] = 0xff; + + const size_t headerLen = sizeof (header) - n; + const int totalSize = (int) (headerLen + textSize); + + uint8* const dest = result.allocateSpace (totalSize); + result.size = totalSize; + + memcpy (dest, header + n, headerLen); + memcpy (dest + headerLen, text.text.getAddress(), textSize); + + return result; +} + +bool MidiMessage::isTrackNameEvent() const noexcept { const uint8* data = getRawData(); return (data[1] == 3) && (*data == 0xff); } +bool MidiMessage::isTempoMetaEvent() const noexcept { const uint8* data = getRawData(); return (data[1] == 81) && (*data == 0xff); } +bool MidiMessage::isMidiChannelMetaEvent() const noexcept { const uint8* data = getRawData(); return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } + +int MidiMessage::getMidiChannelMetaEventChannel() const noexcept +{ + jassert (isMidiChannelMetaEvent()); + return getRawData()[3] + 1; +} + +double MidiMessage::getTempoSecondsPerQuarterNote() const noexcept +{ + if (! isTempoMetaEvent()) + return 0.0; + + const uint8* const d = getMetaEventData(); + + return (((unsigned int) d[0] << 16) + | ((unsigned int) d[1] << 8) + | d[2]) + / 1000000.0; +} + +double MidiMessage::getTempoMetaEventTickLength (const short timeFormat) const noexcept +{ + if (timeFormat > 0) + { + if (! isTempoMetaEvent()) + return 0.5 / timeFormat; + + return getTempoSecondsPerQuarterNote() / timeFormat; + } + else + { + const int frameCode = (-timeFormat) >> 8; + double framesPerSecond; + + switch (frameCode) + { + case 24: framesPerSecond = 24.0; break; + case 25: framesPerSecond = 25.0; break; + case 29: framesPerSecond = 29.97; break; + case 30: framesPerSecond = 30.0; break; + default: framesPerSecond = 30.0; break; + } + + return (1.0 / framesPerSecond) / (timeFormat & 0xff); + } +} + +MidiMessage MidiMessage::tempoMetaEvent (int microsecondsPerQuarterNote) noexcept +{ + const uint8 d[] = { 0xff, 81, 3, + (uint8) (microsecondsPerQuarterNote >> 16), + (uint8) (microsecondsPerQuarterNote >> 8), + (uint8) microsecondsPerQuarterNote }; + + return MidiMessage (d, 6, 0.0); +} + +bool MidiMessage::isTimeSignatureMetaEvent() const noexcept +{ + const uint8* const data = getRawData(); + return (data[1] == 0x58) && (*data == (uint8) 0xff); +} + +void MidiMessage::getTimeSignatureInfo (int& numerator, int& denominator) const noexcept +{ + if (isTimeSignatureMetaEvent()) + { + const uint8* const d = getMetaEventData(); + numerator = d[0]; + denominator = 1 << d[1]; + } + else + { + numerator = 4; + denominator = 4; + } +} + +MidiMessage MidiMessage::timeSignatureMetaEvent (const int numerator, const int denominator) +{ + int n = 1; + int powerOfTwo = 0; + + while (n < denominator) + { + n <<= 1; + ++powerOfTwo; + } + + const uint8 d[] = { 0xff, 0x58, 0x04, (uint8) numerator, (uint8) powerOfTwo, 1, 96 }; + return MidiMessage (d, 7, 0.0); +} + +MidiMessage MidiMessage::midiChannelMetaEvent (const int channel) noexcept +{ + const uint8 d[] = { 0xff, 0x20, 0x01, (uint8) jlimit (0, 0xff, channel - 1) }; + return MidiMessage (d, 4, 0.0); +} + +bool MidiMessage::isKeySignatureMetaEvent() const noexcept +{ + return getMetaEventType() == 0x59; +} + +int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept +{ + return (int) (int8) getMetaEventData()[0]; +} + +bool MidiMessage::isKeySignatureMajorKey() const noexcept +{ + return getMetaEventData()[1] == 0; +} + +MidiMessage MidiMessage::keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey) +{ + jassert (numberOfSharpsOrFlats >= -7 && numberOfSharpsOrFlats <= 7); + + const uint8 d[] = { 0xff, 0x59, 0x02, (uint8) numberOfSharpsOrFlats, isMinorKey ? (uint8) 1 : (uint8) 0 }; + return MidiMessage (d, 5, 0.0); +} + +MidiMessage MidiMessage::endOfTrack() noexcept +{ + return MidiMessage (0xff, 0x2f, 0, 0.0); +} + +//============================================================================== +bool MidiMessage::isSongPositionPointer() const noexcept { return *getRawData() == 0xf2; } +int MidiMessage::getSongPositionPointerMidiBeat() const noexcept { const uint8* data = getRawData(); return data[1] | (data[2] << 7); } + +MidiMessage MidiMessage::songPositionPointer (const int positionInMidiBeats) noexcept +{ + return MidiMessage (0xf2, + positionInMidiBeats & 127, + (positionInMidiBeats >> 7) & 127); +} + +bool MidiMessage::isMidiStart() const noexcept { return *getRawData() == 0xfa; } +MidiMessage MidiMessage::midiStart() noexcept { return MidiMessage (0xfa); } + +bool MidiMessage::isMidiContinue() const noexcept { return *getRawData() == 0xfb; } +MidiMessage MidiMessage::midiContinue() noexcept { return MidiMessage (0xfb); } + +bool MidiMessage::isMidiStop() const noexcept { return *getRawData() == 0xfc; } +MidiMessage MidiMessage::midiStop() noexcept { return MidiMessage (0xfc); } + +bool MidiMessage::isMidiClock() const noexcept { return *getRawData() == 0xf8; } +MidiMessage MidiMessage::midiClock() noexcept { return MidiMessage (0xf8); } + +bool MidiMessage::isQuarterFrame() const noexcept { return *getRawData() == 0xf1; } +int MidiMessage::getQuarterFrameSequenceNumber() const noexcept { return ((int) getRawData()[1]) >> 4; } +int MidiMessage::getQuarterFrameValue() const noexcept { return ((int) getRawData()[1]) & 0x0f; } + +MidiMessage MidiMessage::quarterFrame (const int sequenceNumber, const int value) noexcept +{ + return MidiMessage (0xf1, (sequenceNumber << 4) | value); +} + +bool MidiMessage::isFullFrame() const noexcept +{ + const uint8* const data = getRawData(); + + return data[0] == 0xf0 + && data[1] == 0x7f + && size >= 10 + && data[3] == 0x01 + && data[4] == 0x01; +} + +void MidiMessage::getFullFrameParameters (int& hours, int& minutes, int& seconds, int& frames, + MidiMessage::SmpteTimecodeType& timecodeType) const noexcept +{ + jassert (isFullFrame()); + + const uint8* const data = getRawData(); + timecodeType = (SmpteTimecodeType) (data[5] >> 5); + hours = data[5] & 0x1f; + minutes = data[6]; + seconds = data[7]; + frames = data[8]; +} + +MidiMessage MidiMessage::fullFrame (const int hours, const int minutes, + const int seconds, const int frames, + MidiMessage::SmpteTimecodeType timecodeType) +{ + const uint8 d[] = { 0xf0, 0x7f, 0x7f, 0x01, 0x01, + (uint8) ((hours & 0x01f) | (timecodeType << 5)), + (uint8) minutes, + (uint8) seconds, + (uint8) frames, + 0xf7 }; + + return MidiMessage (d, 10, 0.0); +} + +bool MidiMessage::isMidiMachineControlMessage() const noexcept +{ + const uint8* const data = getRawData(); + return data[0] == 0xf0 + && data[1] == 0x7f + && data[3] == 0x06 + && size > 5; +} + +MidiMessage::MidiMachineControlCommand MidiMessage::getMidiMachineControlCommand() const noexcept +{ + jassert (isMidiMachineControlMessage()); + + return (MidiMachineControlCommand) getRawData()[4]; +} + +MidiMessage MidiMessage::midiMachineControlCommand (MidiMessage::MidiMachineControlCommand command) +{ + const uint8 d[] = { 0xf0, 0x7f, 0, 6, (uint8) command, 0xf7 }; + + return MidiMessage (d, 6, 0.0); +} + +//============================================================================== +bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& seconds, int& frames) const noexcept +{ + const uint8* const data = getRawData(); + if (size >= 12 + && data[0] == 0xf0 + && data[1] == 0x7f + && data[3] == 0x06 + && data[4] == 0x44 + && data[5] == 0x06 + && data[6] == 0x01) + { + hours = data[7] % 24; // (that some machines send out hours > 24) + minutes = data[8]; + seconds = data[9]; + frames = data[10]; + + return true; + } + + return false; +} + +MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int seconds, int frames) +{ + const uint8 d[] = { 0xf0, 0x7f, 0, 6, 0x44, 6, 1, + (uint8) hours, + (uint8) minutes, + (uint8) seconds, + (uint8) frames, + 0xf7 }; + + return MidiMessage (d, 12, 0.0); +} + +//============================================================================== +String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC) +{ + 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 (isPositiveAndBelow (note, (int) 128)) + { + String s (useSharps ? sharpNoteNames [note % 12] + : flatNoteNames [note % 12]); + + if (includeOctaveNumber) + s << (note / 12 + (octaveNumForMiddleC - 5)); + + return s; + } + + return String(); +} + +double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept +{ + return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); +} + +bool MidiMessage::isMidiNoteBlack (int noteNumber) noexcept +{ + return ((1 << (noteNumber % 12)) & 0x054a) != 0; +} + +const char* MidiMessage::getGMInstrumentName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Acoustic Grand Piano"), NEEDS_TRANS("Bright Acoustic Piano"), NEEDS_TRANS("Electric Grand Piano"), NEEDS_TRANS("Honky-tonk Piano"), + NEEDS_TRANS("Electric Piano 1"), NEEDS_TRANS("Electric Piano 2"), NEEDS_TRANS("Harpsichord"), NEEDS_TRANS("Clavinet"), + NEEDS_TRANS("Celesta"), NEEDS_TRANS("Glockenspiel"), NEEDS_TRANS("Music Box"), NEEDS_TRANS("Vibraphone"), + NEEDS_TRANS("Marimba"), NEEDS_TRANS("Xylophone"), NEEDS_TRANS("Tubular Bells"), NEEDS_TRANS("Dulcimer"), + NEEDS_TRANS("Drawbar Organ"), NEEDS_TRANS("Percussive Organ"), NEEDS_TRANS("Rock Organ"), NEEDS_TRANS("Church Organ"), + NEEDS_TRANS("Reed Organ"), NEEDS_TRANS("Accordion"), NEEDS_TRANS("Harmonica"), NEEDS_TRANS("Tango Accordion"), + NEEDS_TRANS("Acoustic Guitar (nylon)"), NEEDS_TRANS("Acoustic Guitar (steel)"), NEEDS_TRANS("Electric Guitar (jazz)"), NEEDS_TRANS("Electric Guitar (clean)"), + NEEDS_TRANS("Electric Guitar (mute)"), NEEDS_TRANS("Overdriven Guitar"), NEEDS_TRANS("Distortion Guitar"), NEEDS_TRANS("Guitar Harmonics"), + NEEDS_TRANS("Acoustic Bass"), NEEDS_TRANS("Electric Bass (finger)"), NEEDS_TRANS("Electric Bass (pick)"), NEEDS_TRANS("Fretless Bass"), + NEEDS_TRANS("Slap Bass 1"), NEEDS_TRANS("Slap Bass 2"), NEEDS_TRANS("Synth Bass 1"), NEEDS_TRANS("Synth Bass 2"), + NEEDS_TRANS("Violin"), NEEDS_TRANS("Viola"), NEEDS_TRANS("Cello"), NEEDS_TRANS("Contrabass"), + NEEDS_TRANS("Tremolo Strings"), NEEDS_TRANS("Pizzicato Strings"), NEEDS_TRANS("Orchestral Harp"), NEEDS_TRANS("Timpani"), + NEEDS_TRANS("String Ensemble 1"), NEEDS_TRANS("String Ensemble 2"), NEEDS_TRANS("SynthStrings 1"), NEEDS_TRANS("SynthStrings 2"), + NEEDS_TRANS("Choir Aahs"), NEEDS_TRANS("Voice Oohs"), NEEDS_TRANS("Synth Voice"), NEEDS_TRANS("Orchestra Hit"), + NEEDS_TRANS("Trumpet"), NEEDS_TRANS("Trombone"), NEEDS_TRANS("Tuba"), NEEDS_TRANS("Muted Trumpet"), + NEEDS_TRANS("French Horn"), NEEDS_TRANS("Brass Section"), NEEDS_TRANS("SynthBrass 1"), NEEDS_TRANS("SynthBrass 2"), + NEEDS_TRANS("Soprano Sax"), NEEDS_TRANS("Alto Sax"), NEEDS_TRANS("Tenor Sax"), NEEDS_TRANS("Baritone Sax"), + NEEDS_TRANS("Oboe"), NEEDS_TRANS("English Horn"), NEEDS_TRANS("Bassoon"), NEEDS_TRANS("Clarinet"), + NEEDS_TRANS("Piccolo"), NEEDS_TRANS("Flute"), NEEDS_TRANS("Recorder"), NEEDS_TRANS("Pan Flute"), + NEEDS_TRANS("Blown Bottle"), NEEDS_TRANS("Shakuhachi"), NEEDS_TRANS("Whistle"), NEEDS_TRANS("Ocarina"), + NEEDS_TRANS("Lead 1 (square)"), NEEDS_TRANS("Lead 2 (sawtooth)"), NEEDS_TRANS("Lead 3 (calliope)"), NEEDS_TRANS("Lead 4 (chiff)"), + NEEDS_TRANS("Lead 5 (charang)"), NEEDS_TRANS("Lead 6 (voice)"), NEEDS_TRANS("Lead 7 (fifths)"), NEEDS_TRANS("Lead 8 (bass+lead)"), + NEEDS_TRANS("Pad 1 (new age)"), NEEDS_TRANS("Pad 2 (warm)"), NEEDS_TRANS("Pad 3 (polysynth)"), NEEDS_TRANS("Pad 4 (choir)"), + NEEDS_TRANS("Pad 5 (bowed)"), NEEDS_TRANS("Pad 6 (metallic)"), NEEDS_TRANS("Pad 7 (halo)"), NEEDS_TRANS("Pad 8 (sweep)"), + NEEDS_TRANS("FX 1 (rain)"), NEEDS_TRANS("FX 2 (soundtrack)"), NEEDS_TRANS("FX 3 (crystal)"), NEEDS_TRANS("FX 4 (atmosphere)"), + NEEDS_TRANS("FX 5 (brightness)"), NEEDS_TRANS("FX 6 (goblins)"), NEEDS_TRANS("FX 7 (echoes)"), NEEDS_TRANS("FX 8 (sci-fi)"), + NEEDS_TRANS("Sitar"), NEEDS_TRANS("Banjo"), NEEDS_TRANS("Shamisen"), NEEDS_TRANS("Koto"), + NEEDS_TRANS("Kalimba"), NEEDS_TRANS("Bag pipe"), NEEDS_TRANS("Fiddle"), NEEDS_TRANS("Shanai"), + NEEDS_TRANS("Tinkle Bell"), NEEDS_TRANS("Agogo"), NEEDS_TRANS("Steel Drums"), NEEDS_TRANS("Woodblock"), + NEEDS_TRANS("Taiko Drum"), NEEDS_TRANS("Melodic Tom"), NEEDS_TRANS("Synth Drum"), NEEDS_TRANS("Reverse Cymbal"), + NEEDS_TRANS("Guitar Fret Noise"), NEEDS_TRANS("Breath Noise"), NEEDS_TRANS("Seashore"), NEEDS_TRANS("Bird Tweet"), + NEEDS_TRANS("Telephone Ring"), NEEDS_TRANS("Helicopter"), NEEDS_TRANS("Applause"), NEEDS_TRANS("Gunshot") + }; + + return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; +} + +const char* MidiMessage::getGMInstrumentBankName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Piano"), NEEDS_TRANS("Chromatic Percussion"), NEEDS_TRANS("Organ"), NEEDS_TRANS("Guitar"), + NEEDS_TRANS("Bass"), NEEDS_TRANS("Strings"), NEEDS_TRANS("Ensemble"), NEEDS_TRANS("Brass"), + NEEDS_TRANS("Reed"), NEEDS_TRANS("Pipe"), NEEDS_TRANS("Synth Lead"), NEEDS_TRANS("Synth Pad"), + NEEDS_TRANS("Synth Effects"), NEEDS_TRANS("Ethnic"), NEEDS_TRANS("Percussive"), NEEDS_TRANS("Sound Effects") + }; + + return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; +} + +const char* MidiMessage::getRhythmInstrumentName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Acoustic Bass Drum"), NEEDS_TRANS("Bass Drum 1"), NEEDS_TRANS("Side Stick"), NEEDS_TRANS("Acoustic Snare"), + NEEDS_TRANS("Hand Clap"), NEEDS_TRANS("Electric Snare"), NEEDS_TRANS("Low Floor Tom"), NEEDS_TRANS("Closed Hi-Hat"), + NEEDS_TRANS("High Floor Tom"), NEEDS_TRANS("Pedal Hi-Hat"), NEEDS_TRANS("Low Tom"), NEEDS_TRANS("Open Hi-Hat"), + NEEDS_TRANS("Low-Mid Tom"), NEEDS_TRANS("Hi-Mid Tom"), NEEDS_TRANS("Crash Cymbal 1"), NEEDS_TRANS("High Tom"), + NEEDS_TRANS("Ride Cymbal 1"), NEEDS_TRANS("Chinese Cymbal"), NEEDS_TRANS("Ride Bell"), NEEDS_TRANS("Tambourine"), + NEEDS_TRANS("Splash Cymbal"), NEEDS_TRANS("Cowbell"), NEEDS_TRANS("Crash Cymbal 2"), NEEDS_TRANS("Vibraslap"), + NEEDS_TRANS("Ride Cymbal 2"), NEEDS_TRANS("Hi Bongo"), NEEDS_TRANS("Low Bongo"), NEEDS_TRANS("Mute Hi Conga"), + NEEDS_TRANS("Open Hi Conga"), NEEDS_TRANS("Low Conga"), NEEDS_TRANS("High Timbale"), NEEDS_TRANS("Low Timbale"), + NEEDS_TRANS("High Agogo"), NEEDS_TRANS("Low Agogo"), NEEDS_TRANS("Cabasa"), NEEDS_TRANS("Maracas"), + NEEDS_TRANS("Short Whistle"), NEEDS_TRANS("Long Whistle"), NEEDS_TRANS("Short Guiro"), NEEDS_TRANS("Long Guiro"), + NEEDS_TRANS("Claves"), NEEDS_TRANS("Hi Wood Block"), NEEDS_TRANS("Low Wood Block"), NEEDS_TRANS("Mute Cuica"), + NEEDS_TRANS("Open Cuica"), NEEDS_TRANS("Mute Triangle"), NEEDS_TRANS("Open Triangle") + }; + + return (n >= 35 && n <= 81) ? names [n - 35] : nullptr; +} + +const char* MidiMessage::getControllerName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Bank Select"), NEEDS_TRANS("Modulation Wheel (coarse)"), NEEDS_TRANS("Breath controller (coarse)"), + nullptr, + NEEDS_TRANS("Foot Pedal (coarse)"), NEEDS_TRANS("Portamento Time (coarse)"), NEEDS_TRANS("Data Entry (coarse)"), + NEEDS_TRANS("Volume (coarse)"), NEEDS_TRANS("Balance (coarse)"), + nullptr, + NEEDS_TRANS("Pan position (coarse)"), NEEDS_TRANS("Expression (coarse)"), NEEDS_TRANS("Effect Control 1 (coarse)"), + NEEDS_TRANS("Effect Control 2 (coarse)"), + nullptr, nullptr, + NEEDS_TRANS("General Purpose Slider 1"), NEEDS_TRANS("General Purpose Slider 2"), + NEEDS_TRANS("General Purpose Slider 3"), NEEDS_TRANS("General Purpose Slider 4"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Bank Select (fine)"), NEEDS_TRANS("Modulation Wheel (fine)"), NEEDS_TRANS("Breath controller (fine)"), + nullptr, + NEEDS_TRANS("Foot Pedal (fine)"), NEEDS_TRANS("Portamento Time (fine)"), NEEDS_TRANS("Data Entry (fine)"), NEEDS_TRANS("Volume (fine)"), + NEEDS_TRANS("Balance (fine)"), nullptr, NEEDS_TRANS("Pan position (fine)"), NEEDS_TRANS("Expression (fine)"), + NEEDS_TRANS("Effect Control 1 (fine)"), NEEDS_TRANS("Effect Control 2 (fine)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Hold Pedal (on/off)"), NEEDS_TRANS("Portamento (on/off)"), NEEDS_TRANS("Sustenuto Pedal (on/off)"), NEEDS_TRANS("Soft Pedal (on/off)"), + NEEDS_TRANS("Legato Pedal (on/off)"), NEEDS_TRANS("Hold 2 Pedal (on/off)"), NEEDS_TRANS("Sound Variation"), NEEDS_TRANS("Sound Timbre"), + NEEDS_TRANS("Sound Release Time"), NEEDS_TRANS("Sound Attack Time"), NEEDS_TRANS("Sound Brightness"), NEEDS_TRANS("Sound Control 6"), + NEEDS_TRANS("Sound Control 7"), NEEDS_TRANS("Sound Control 8"), NEEDS_TRANS("Sound Control 9"), NEEDS_TRANS("Sound Control 10"), + NEEDS_TRANS("General Purpose Button 1 (on/off)"), NEEDS_TRANS("General Purpose Button 2 (on/off)"), + NEEDS_TRANS("General Purpose Button 3 (on/off)"), NEEDS_TRANS("General Purpose Button 4 (on/off)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Reverb Level"), NEEDS_TRANS("Tremolo Level"), NEEDS_TRANS("Chorus Level"), NEEDS_TRANS("Celeste Level"), + NEEDS_TRANS("Phaser Level"), NEEDS_TRANS("Data Button increment"), NEEDS_TRANS("Data Button decrement"), NEEDS_TRANS("Non-registered Parameter (fine)"), + NEEDS_TRANS("Non-registered Parameter (coarse)"), NEEDS_TRANS("Registered Parameter (fine)"), NEEDS_TRANS("Registered Parameter (coarse)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("All Sound Off"), NEEDS_TRANS("All Controllers Off"), NEEDS_TRANS("Local Keyboard (on/off)"), NEEDS_TRANS("All Notes Off"), + NEEDS_TRANS("Omni Mode Off"), NEEDS_TRANS("Omni Mode On"), NEEDS_TRANS("Mono Operation"), NEEDS_TRANS("Poly Operation") + }; + + return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; +} diff --git a/source/modules/juce_audio_graph/midi/juce_MidiMessage.h b/source/modules/juce_audio_graph/midi/juce_MidiMessage.h new file mode 100644 index 000000000..d09115412 --- /dev/null +++ b/source/modules/juce_audio_graph/midi/juce_MidiMessage.h @@ -0,0 +1,940 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MIDIMESSAGE_H_INCLUDED +#define JUCE_MIDIMESSAGE_H_INCLUDED + + +//============================================================================== +/** + Encapsulates a MIDI message. + + @see MidiMessageSequence, MidiOutput, MidiInput +*/ +class JUCE_API MidiMessage +{ +public: + //============================================================================== + /** Creates a 3-byte short midi message. + + @param byte1 message byte 1 + @param byte2 message byte 2 + @param byte3 message byte 3 + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + */ + MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept; + + /** Creates a 2-byte short midi message. + + @param byte1 message byte 1 + @param byte2 message byte 2 + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + */ + MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept; + + /** Creates a 1-byte short midi message. + + @param byte1 message byte 1 + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + */ + MidiMessage (int byte1, double timeStamp = 0) noexcept; + + /** Creates a midi message from a block of data. */ + MidiMessage (const void* data, int numBytes, double timeStamp = 0); + + /** Reads the next midi message from some data. + + This will read as many bytes from a data stream as it needs to make a + complete message, and will return the number of bytes it used. This lets + you read a sequence of midi messages from a file or stream. + + @param data the data to read from + @param maxBytesToUse the maximum number of bytes it's allowed to read + @param numBytesUsed returns the number of bytes that were actually needed + @param lastStatusByte in a sequence of midi messages, the initial byte + can be dropped from a message if it's the same as the + first byte of the previous message, so this lets you + supply the byte to use if the first byte of the message + has in fact been dropped. + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + @param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether + to expect the data to begin with a variable-length field + indicating its size + */ + MidiMessage (const void* data, int maxBytesToUse, + int& numBytesUsed, uint8 lastStatusByte, + double timeStamp = 0, + bool sysexHasEmbeddedLength = true); + + /** Creates an active-sense message. + Since the MidiMessage has to contain a valid message, this default constructor + just initialises it with an empty sysex message. + */ + MidiMessage() noexcept; + + /** Creates a copy of another midi message. */ + MidiMessage (const MidiMessage&); + + /** Creates a copy of another midi message, with a different timestamp. */ + MidiMessage (const MidiMessage&, double newTimeStamp); + + /** Destructor. */ + ~MidiMessage() noexcept; + + /** Copies this message from another one. */ + MidiMessage& operator= (const MidiMessage& other); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + MidiMessage (MidiMessage&&) noexcept; + MidiMessage& operator= (MidiMessage&&) noexcept; + #endif + + //============================================================================== + /** Returns a pointer to the raw midi data. + @see getRawDataSize + */ + const uint8* getRawData() const noexcept { return getData(); } + + /** Returns the number of bytes of data in the message. + @see getRawData + */ + int getRawDataSize() const noexcept { return size; } + + //============================================================================== + /** Returns a human-readable description of the midi message as a string, + for example "Note On C#3 Velocity 120 Channel 1". + */ + String getDescription() const; + + //============================================================================== + /** Returns the timestamp associated with this message. + + The exact meaning of this time and its units will vary, as messages are used in + a variety of different contexts. + + If you're getting the message from a midi file, this could be a time in seconds, or + a number of ticks - see MidiFile::convertTimestampTicksToSeconds(). + + If the message is being used in a MidiBuffer, it might indicate the number of + audio samples from the start of the buffer. + + If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage() + for details of the way that it initialises this value. + + @see setTimeStamp, addToTimeStamp + */ + double getTimeStamp() const noexcept { return timeStamp; } + + /** Changes the message's associated timestamp. + The units for the timestamp will be application-specific - see the notes for getTimeStamp(). + @see addToTimeStamp, getTimeStamp + */ + void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; } + + /** Adds a value to the message's timestamp. + The units for the timestamp will be application-specific. + */ + void addToTimeStamp (double delta) noexcept { timeStamp += delta; } + + //============================================================================== + /** Returns the midi channel associated with the message. + + @returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g. + if it's a sysex) + @see isForChannel, setChannel + */ + int getChannel() const noexcept; + + /** Returns true if the message applies to the given midi channel. + + @param channelNumber the channel number to look for, in the range 1 to 16 + @see getChannel, setChannel + */ + bool isForChannel (int channelNumber) const noexcept; + + /** Changes the message's midi channel. + This won't do anything for non-channel messages like sysexes. + @param newChannelNumber the channel number to change it to, in the range 1 to 16 + */ + void setChannel (int newChannelNumber) noexcept; + + //============================================================================== + /** Returns true if this is a system-exclusive message. + */ + bool isSysEx() const noexcept; + + /** Returns a pointer to the sysex data inside the message. + If this event isn't a sysex event, it'll return 0. + @see getSysExDataSize + */ + const uint8* getSysExData() const noexcept; + + /** Returns the size of the sysex data. + This value excludes the 0xf0 header byte and the 0xf7 at the end. + @see getSysExData + */ + int getSysExDataSize() const noexcept; + + //============================================================================== + /** Returns true if this message is a 'key-down' event. + + @param returnTrueForVelocity0 if true, then if this event is a note-on with + velocity 0, it will still be considered to be a note-on and the + method will return true. If returnTrueForVelocity0 is false, then + if this is a note-on event with velocity 0, it'll be regarded as + a note-off, and the method will return false + + @see isNoteOff, getNoteNumber, getVelocity, noteOn + */ + bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept; + + /** Creates a key-down message (using a floating-point velocity). + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 1.0 + @see isNoteOn + */ + static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept; + + /** Creates a key-down message (using an integer velocity). + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 127 + @see isNoteOn + */ + static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept; + + /** Returns true if this message is a 'key-up' event. + + If returnTrueForNoteOnVelocity0 is true, then his will also return true + for a note-on event with a velocity of 0. + + @see isNoteOn, getNoteNumber, getVelocity, noteOff + */ + bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept; + + /** Creates a key-up message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 1.0 + @see isNoteOff + */ + static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept; + + /** Creates a key-up message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 127 + @see isNoteOff + */ + static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept; + + /** Creates a key-up message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @see isNoteOff + */ + static MidiMessage noteOff (int channel, int noteNumber) noexcept; + + /** Returns true if this message is a 'key-down' or 'key-up' event. + + @see isNoteOn, isNoteOff + */ + bool isNoteOnOrOff() const noexcept; + + /** Returns the midi note number for note-on and note-off messages. + If the message isn't a note-on or off, the value returned is undefined. + @see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber + */ + int getNoteNumber() const noexcept; + + /** Changes the midi note number of a note-on or note-off message. + If the message isn't a note on or off, this will do nothing. + */ + void setNoteNumber (int newNoteNumber) noexcept; + + //============================================================================== + /** Returns the velocity of a note-on or note-off message. + + The value returned will be in the range 0 to 127. + If the message isn't a note-on or off event, it will return 0. + + @see getFloatVelocity + */ + uint8 getVelocity() const noexcept; + + /** Returns the velocity of a note-on or note-off message. + + The value returned will be in the range 0 to 1.0 + If the message isn't a note-on or off event, it will return 0. + + @see getVelocity, setVelocity + */ + float getFloatVelocity() const noexcept; + + /** Changes the velocity of a note-on or note-off message. + + If the message isn't a note on or off, this will do nothing. + + @param newVelocity the new velocity, in the range 0 to 1.0 + @see getFloatVelocity, multiplyVelocity + */ + void setVelocity (float newVelocity) noexcept; + + /** Multiplies the velocity of a note-on or note-off message by a given amount. + + If the message isn't a note on or off, this will do nothing. + + @param scaleFactor the value by which to multiply the velocity + @see setVelocity + */ + void multiplyVelocity (float scaleFactor) noexcept; + + //============================================================================== + /** Returns true if this message is a 'sustain pedal down' controller message. */ + bool isSustainPedalOn() const noexcept; + /** Returns true if this message is a 'sustain pedal up' controller message. */ + bool isSustainPedalOff() const noexcept; + + /** Returns true if this message is a 'sostenuto pedal down' controller message. */ + bool isSostenutoPedalOn() const noexcept; + /** Returns true if this message is a 'sostenuto pedal up' controller message. */ + bool isSostenutoPedalOff() const noexcept; + + /** Returns true if this message is a 'soft pedal down' controller message. */ + bool isSoftPedalOn() const noexcept; + /** Returns true if this message is a 'soft pedal up' controller message. */ + bool isSoftPedalOff() const noexcept; + + //============================================================================== + /** Returns true if the message is a program (patch) change message. + @see getProgramChangeNumber, getGMInstrumentName + */ + bool isProgramChange() const noexcept; + + /** Returns the new program number of a program change message. + If the message isn't a program change, the value returned is undefined. + @see isProgramChange, getGMInstrumentName + */ + int getProgramChangeNumber() const noexcept; + + /** Creates a program-change message. + + @param channel the midi channel, in the range 1 to 16 + @param programNumber the midi program number, 0 to 127 + @see isProgramChange, getGMInstrumentName + */ + static MidiMessage programChange (int channel, int programNumber) noexcept; + + //============================================================================== + /** Returns true if the message is a pitch-wheel move. + @see getPitchWheelValue, pitchWheel + */ + bool isPitchWheel() const noexcept; + + /** Returns the pitch wheel position from a pitch-wheel move message. + + The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. + If called for messages which aren't pitch wheel events, the number returned will be + nonsense. + + @see isPitchWheel + */ + int getPitchWheelValue() const noexcept; + + /** Creates a pitch-wheel move message. + + @param channel the midi channel, in the range 1 to 16 + @param position the wheel position, in the range 0 to 16383 + @see isPitchWheel + */ + static MidiMessage pitchWheel (int channel, int position) noexcept; + + //============================================================================== + /** Returns true if the message is an aftertouch event. + + For aftertouch events, use the getNoteNumber() method to find out the key + that it applies to, and getAftertouchValue() to find out the amount. Use + getChannel() to find out the channel. + + @see getAftertouchValue, getNoteNumber + */ + bool isAftertouch() const noexcept; + + /** Returns the amount of aftertouch from an aftertouch messages. + + The value returned is in the range 0 to 127, and will be nonsense for messages + other than aftertouch messages. + + @see isAftertouch + */ + int getAfterTouchValue() const noexcept; + + /** Creates an aftertouch message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param aftertouchAmount the amount of aftertouch, 0 to 127 + @see isAftertouch + */ + static MidiMessage aftertouchChange (int channel, + int noteNumber, + int aftertouchAmount) noexcept; + + /** Returns true if the message is a channel-pressure change event. + + This is like aftertouch, but common to the whole channel rather than a specific + note. Use getChannelPressureValue() to find out the pressure, and getChannel() + to find out the channel. + + @see channelPressureChange + */ + bool isChannelPressure() const noexcept; + + /** Returns the pressure from a channel pressure change message. + + @returns the pressure, in the range 0 to 127 + @see isChannelPressure, channelPressureChange + */ + int getChannelPressureValue() const noexcept; + + /** Creates a channel-pressure change event. + + @param channel the midi channel: 1 to 16 + @param pressure the pressure, 0 to 127 + @see isChannelPressure + */ + static MidiMessage channelPressureChange (int channel, int pressure) noexcept; + + //============================================================================== + /** Returns true if this is a midi controller message. + + @see getControllerNumber, getControllerValue, controllerEvent + */ + bool isController() const noexcept; + + /** Returns the controller number of a controller message. + + The name of the controller can be looked up using the getControllerName() method. + Note that the value returned is invalid for messages that aren't controller changes. + + @see isController, getControllerName, getControllerValue + */ + int getControllerNumber() const noexcept; + + /** Returns the controller value from a controller message. + + A value 0 to 127 is returned to indicate the new controller position. + Note that the value returned is invalid for messages that aren't controller changes. + + @see isController, getControllerNumber + */ + int getControllerValue() const noexcept; + + /** Returns true if this message is a controller message and if it has the specified + controller type. + */ + bool isControllerOfType (int controllerType) const noexcept; + + /** Creates a controller message. + + @param channel the midi channel, in the range 1 to 16 + @param controllerType the type of controller + @param value the controller value + @see isController + */ + static MidiMessage controllerEvent (int channel, + int controllerType, + int value) noexcept; + + /** Checks whether this message is an all-notes-off message. + @see allNotesOff + */ + bool isAllNotesOff() const noexcept; + + /** Checks whether this message is an all-sound-off message. + @see allSoundOff + */ + bool isAllSoundOff() const noexcept; + + /** Creates an all-notes-off message. + + @param channel the midi channel, in the range 1 to 16 + @see isAllNotesOff + */ + static MidiMessage allNotesOff (int channel) noexcept; + + /** Creates an all-sound-off message. + + @param channel the midi channel, in the range 1 to 16 + @see isAllSoundOff + */ + static MidiMessage allSoundOff (int channel) noexcept; + + /** Creates an all-controllers-off message. + + @param channel the midi channel, in the range 1 to 16 + */ + static MidiMessage allControllersOff (int channel) noexcept; + + //============================================================================== + /** Returns true if this event is a meta-event. + + Meta-events are things like tempo changes, track names, etc. + + @see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, + isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, + isKeySignatureMetaEvent, isMidiChannelMetaEvent + */ + bool isMetaEvent() const noexcept; + + /** Returns a meta-event's type number. + + If the message isn't a meta-event, this will return -1. + + @see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, + isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, + isKeySignatureMetaEvent, isMidiChannelMetaEvent + */ + int getMetaEventType() const noexcept; + + /** Returns a pointer to the data in a meta-event. + @see isMetaEvent, getMetaEventLength + */ + const uint8* getMetaEventData() const noexcept; + + /** Returns the length of the data for a meta-event. + @see isMetaEvent, getMetaEventData + */ + int getMetaEventLength() const noexcept; + + //============================================================================== + /** Returns true if this is a 'track' meta-event. */ + bool isTrackMetaEvent() const noexcept; + + /** Returns true if this is an 'end-of-track' meta-event. */ + bool isEndOfTrackMetaEvent() const noexcept; + + /** Creates an end-of-track meta-event. + @see isEndOfTrackMetaEvent + */ + static MidiMessage endOfTrack() noexcept; + + /** Returns true if this is an 'track name' meta-event. + You can use the getTextFromTextMetaEvent() method to get the track's name. + */ + bool isTrackNameEvent() const noexcept; + + /** Returns true if this is a 'text' meta-event. + @see getTextFromTextMetaEvent + */ + bool isTextMetaEvent() const noexcept; + + /** Returns the text from a text meta-event. + @see isTextMetaEvent + */ + String getTextFromTextMetaEvent() const; + + /** Creates a text meta-event. */ + static MidiMessage textMetaEvent (int type, StringRef text); + + //============================================================================== + /** Returns true if this is a 'tempo' meta-event. + @see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote + */ + bool isTempoMetaEvent() const noexcept; + + /** Returns the tick length from a tempo meta-event. + + @param timeFormat the 16-bit time format value from the midi file's header. + @returns the tick length (in seconds). + @see isTempoMetaEvent + */ + double getTempoMetaEventTickLength (short timeFormat) const noexcept; + + /** Calculates the seconds-per-quarter-note from a tempo meta-event. + @see isTempoMetaEvent, getTempoMetaEventTickLength + */ + double getTempoSecondsPerQuarterNote() const noexcept; + + /** Creates a tempo meta-event. + @see isTempoMetaEvent + */ + static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; + + //============================================================================== + /** Returns true if this is a 'time-signature' meta-event. + @see getTimeSignatureInfo + */ + bool isTimeSignatureMetaEvent() const noexcept; + + /** Returns the time-signature values from a time-signature meta-event. + @see isTimeSignatureMetaEvent + */ + void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; + + /** Creates a time-signature meta-event. + @see isTimeSignatureMetaEvent + */ + static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); + + //============================================================================== + /** Returns true if this is a 'key-signature' meta-event. + @see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey + */ + bool isKeySignatureMetaEvent() const noexcept; + + /** Returns the key from a key-signature meta-event. + This method must only be called if isKeySignatureMetaEvent() is true. + A positive number here indicates the number of sharps in the key signature, + and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#, + -2 = Bb + Eb + @see isKeySignatureMetaEvent, isKeySignatureMajorKey + */ + int getKeySignatureNumberOfSharpsOrFlats() const noexcept; + + /** Returns true if this key-signature event is major, or false if it's minor. + This method must only be called if isKeySignatureMetaEvent() is true. + */ + bool isKeySignatureMajorKey() const noexcept; + + /** Creates a key-signature meta-event. + @param numberOfSharpsOrFlats if positive, this indicates the number of sharps + in the key; if negative, the number of flats + @param isMinorKey if true, the key is minor; if false, it is major + @see isKeySignatureMetaEvent + */ + static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey); + + //============================================================================== + /** Returns true if this is a 'channel' meta-event. + + A channel meta-event specifies the midi channel that should be used + for subsequent meta-events. + + @see getMidiChannelMetaEventChannel + */ + bool isMidiChannelMetaEvent() const noexcept; + + /** Returns the channel number from a channel meta-event. + + @returns the channel, in the range 1 to 16. + @see isMidiChannelMetaEvent + */ + int getMidiChannelMetaEventChannel() const noexcept; + + /** Creates a midi channel meta-event. + + @param channel the midi channel, in the range 1 to 16 + @see isMidiChannelMetaEvent + */ + static MidiMessage midiChannelMetaEvent (int channel) noexcept; + + //============================================================================== + /** Returns true if this is an active-sense message. */ + bool isActiveSense() const noexcept; + + //============================================================================== + /** Returns true if this is a midi start event. + @see midiStart + */ + bool isMidiStart() const noexcept; + + /** Creates a midi start event. */ + static MidiMessage midiStart() noexcept; + + /** Returns true if this is a midi continue event. + @see midiContinue + */ + bool isMidiContinue() const noexcept; + + /** Creates a midi continue event. */ + static MidiMessage midiContinue() noexcept; + + /** Returns true if this is a midi stop event. + @see midiStop + */ + bool isMidiStop() const noexcept; + + /** Creates a midi stop event. */ + static MidiMessage midiStop() noexcept; + + /** Returns true if this is a midi clock event. + @see midiClock, songPositionPointer + */ + bool isMidiClock() const noexcept; + + /** Creates a midi clock event. */ + static MidiMessage midiClock() noexcept; + + /** Returns true if this is a song-position-pointer message. + @see getSongPositionPointerMidiBeat, songPositionPointer + */ + bool isSongPositionPointer() const noexcept; + + /** Returns the midi beat-number of a song-position-pointer message. + @see isSongPositionPointer, songPositionPointer + */ + int getSongPositionPointerMidiBeat() const noexcept; + + /** Creates a song-position-pointer message. + + The position is a number of midi beats from the start of the song, where 1 midi + beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there + are 4 midi beats in a quarter-note. + + @see isSongPositionPointer, getSongPositionPointerMidiBeat + */ + static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; + + //============================================================================== + /** Returns true if this is a quarter-frame midi timecode message. + @see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue + */ + bool isQuarterFrame() const noexcept; + + /** Returns the sequence number of a quarter-frame midi timecode message. + This will be a value between 0 and 7. + @see isQuarterFrame, getQuarterFrameValue, quarterFrame + */ + int getQuarterFrameSequenceNumber() const noexcept; + + /** Returns the value from a quarter-frame message. + This will be the lower nybble of the message's data-byte, a value between 0 and 15 + */ + int getQuarterFrameValue() const noexcept; + + /** Creates a quarter-frame MTC message. + + @param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte + @param value a value 0 to 15 for the lower nybble of the message's data byte + */ + static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; + + /** SMPTE timecode types. + Used by the getFullFrameParameters() and fullFrame() methods. + */ + enum SmpteTimecodeType + { + fps24 = 0, + fps25 = 1, + fps30drop = 2, + fps30 = 3 + }; + + /** Returns true if this is a full-frame midi timecode message. */ + bool isFullFrame() const noexcept; + + /** Extracts the timecode information from a full-frame midi timecode message. + + You should only call this on messages where you've used isFullFrame() to + check that they're the right kind. + */ + void getFullFrameParameters (int& hours, + int& minutes, + int& seconds, + int& frames, + SmpteTimecodeType& timecodeType) const noexcept; + + /** Creates a full-frame MTC message. */ + static MidiMessage fullFrame (int hours, + int minutes, + int seconds, + int frames, + SmpteTimecodeType timecodeType); + + //============================================================================== + /** Types of MMC command. + + @see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand + */ + enum MidiMachineControlCommand + { + mmc_stop = 1, + mmc_play = 2, + mmc_deferredplay = 3, + mmc_fastforward = 4, + mmc_rewind = 5, + mmc_recordStart = 6, + mmc_recordStop = 7, + mmc_pause = 9 + }; + + /** Checks whether this is an MMC message. + If it is, you can use the getMidiMachineControlCommand() to find out its type. + */ + bool isMidiMachineControlMessage() const noexcept; + + /** For an MMC message, this returns its type. + + Make sure it's actually an MMC message with isMidiMachineControlMessage() before + calling this method. + */ + MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; + + /** Creates an MMC message. */ + static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); + + /** Checks whether this is an MMC "goto" message. + If it is, the parameters passed-in are set to the time that the message contains. + @see midiMachineControlGoto + */ + bool isMidiMachineControlGoto (int& hours, + int& minutes, + int& seconds, + int& frames) const noexcept; + + /** Creates an MMC "goto" message. + This messages tells the device to go to a specific frame. + @see isMidiMachineControlGoto + */ + static MidiMessage midiMachineControlGoto (int hours, + int minutes, + int seconds, + int frames); + + //============================================================================== + /** Creates a master-volume change message. + @param volume the volume, 0 to 1.0 + */ + static MidiMessage masterVolume (float volume); + + //============================================================================== + /** Creates a system-exclusive message. + The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. + */ + static MidiMessage createSysExMessage (const void* sysexData, + int dataSize); + + + //============================================================================== + /** Reads a midi variable-length integer. + + @param data the data to read the number from + @param numBytesUsed on return, this will be set to the number of bytes that were read + */ + static int readVariableLengthVal (const uint8* data, + int& numBytesUsed) noexcept; + + /** Based on the first byte of a short midi message, this uses a lookup table + to return the message length (either 1, 2, or 3 bytes). + + The value passed in must be 0x80 or higher. + */ + static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; + + //============================================================================== + /** Returns the name of a midi note number. + + E.g "C", "D#", etc. + + @param noteNumber the midi note number, 0 to 127 + @param useSharps if true, sharpened notes are used, e.g. "C#", otherwise + they'll be flattened, e.g. "Db" + @param includeOctaveNumber if true, the octave number will be appended to the string, + e.g. "C#4" + @param octaveNumForMiddleC if an octave number is being appended, this indicates the + number that will be used for middle C's octave + + @see getMidiNoteInHertz + */ + static String getMidiNoteName (int noteNumber, + bool useSharps, + bool includeOctaveNumber, + int octaveNumForMiddleC); + + /** Returns the frequency of a midi note number. + + The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. + @see getMidiNoteName + */ + static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; + + /** Returns true if the given midi note number is a black key. */ + static bool isMidiNoteBlack (int noteNumber) noexcept; + + /** Returns the standard name of a GM instrument, or nullptr if unknown for this index. + + @param midiInstrumentNumber the program number 0 to 127 + @see getProgramChangeNumber + */ + static const char* getGMInstrumentName (int midiInstrumentNumber); + + /** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number. + @param midiBankNumber the bank, 0 to 15 + */ + static const char* getGMInstrumentBankName (int midiBankNumber); + + /** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number. + @param midiNoteNumber the key number, 35 to 81 + */ + static const char* getRhythmInstrumentName (int midiNoteNumber); + + /** Returns the name of a controller type number, or nullptr if unknown for this controller number. + @see getControllerNumber + */ + static const char* getControllerName (int controllerNumber); + + /** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ + static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; + + /** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ + static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, + float pitchbendRangeInSemitones) noexcept; + +private: + //============================================================================== + #ifndef DOXYGEN + union PackedData + { + uint8* allocatedData; + uint8 asBytes[sizeof (uint8*)]; + }; + + PackedData packedData; + double timeStamp; + int size; + #endif + + inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } + inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } + uint8* allocateSpace (int); +}; + +#endif // JUCE_MIDIMESSAGE_H_INCLUDED diff --git a/source/modules/juce_audio_graph/processors/juce_AudioPlayHead.h b/source/modules/juce_audio_graph/processors/juce_AudioPlayHead.h new file mode 100644 index 000000000..c54e7c35d --- /dev/null +++ b/source/modules/juce_audio_graph/processors/juce_AudioPlayHead.h @@ -0,0 +1,144 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_AUDIOPLAYHEAD_H_INCLUDED +#define JUCE_AUDIOPLAYHEAD_H_INCLUDED + + +//============================================================================== +/** + A subclass of AudioPlayHead can supply information about the position and + status of a moving play head during audio playback. + + One of these can be supplied to an AudioProcessor object so that it can find + out about the position of the audio that it is rendering. + + @see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead +*/ +class JUCE_API AudioPlayHead +{ +protected: + //============================================================================== + AudioPlayHead() {} + +public: + virtual ~AudioPlayHead() {} + + //============================================================================== + /** Frame rate types. */ + enum FrameRateType + { + fps24 = 0, + fps25 = 1, + fps2997 = 2, + fps30 = 3, + fps2997drop = 4, + fps30drop = 5, + fpsUnknown = 99 + }; + + //============================================================================== + /** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. + */ + struct JUCE_API CurrentPositionInfo + { + /** The tempo in BPM */ + double bpm; + + /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ + int timeSigNumerator; + /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ + int timeSigDenominator; + + /** The current play position, in samples from the start of the edit. */ + int64 timeInSamples; + /** The current play position, in seconds from the start of the edit. */ + double timeInSeconds; + + /** For timecode, the position of the start of the edit, in seconds from 00:00:00:00. */ + double editOriginTime; + + /** The current play position, in pulses-per-quarter-note. */ + double ppqPosition; + + /** The position of the start of the last bar, in pulses-per-quarter-note. + + This is the time from the start of the edit to the start of the current + bar, in ppq units. + + Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If + it's not available, the value will be 0. + */ + double ppqPositionOfLastBarStart; + + /** The video frame rate, if applicable. */ + FrameRateType frameRate; + + /** True if the transport is currently playing. */ + bool isPlaying; + + /** True if the transport is currently recording. + + (When isRecording is true, then isPlaying will also be true). + */ + bool isRecording; + + /** The current cycle start position in pulses-per-quarter-note. + Note that not all hosts or plugin formats may provide this value. + @see isLooping + */ + double ppqLoopStart; + + /** The current cycle end position in pulses-per-quarter-note. + Note that not all hosts or plugin formats may provide this value. + @see isLooping + */ + double ppqLoopEnd; + + /** True if the transport is currently looping. */ + bool isLooping; + + //============================================================================== + bool operator== (const CurrentPositionInfo& other) const noexcept; + bool operator!= (const CurrentPositionInfo& other) const noexcept; + + void resetToDefault(); + }; + + //============================================================================== + /** Fills-in the given structure with details about the transport's + position at the start of the current processing block. If this method returns + false then the current play head position is not available and the given + structure will be undefined. + + You can ONLY call this from your processBlock() method! Calling it at other + times will produce undefined behaviour, as the host may not have any context + in which a time would make sense, and some hosts will almost certainly have + multithreading issues if it's not called on the audio thread. + */ + virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; +}; + + +#endif // JUCE_AUDIOPLAYHEAD_H_INCLUDED diff --git a/source/modules/juce_audio_graph/processors/juce_AudioProcessor.cpp b/source/modules/juce_audio_graph/processors/juce_AudioProcessor.cpp new file mode 100644 index 000000000..761ada41c --- /dev/null +++ b/source/modules/juce_audio_graph/processors/juce_AudioProcessor.cpp @@ -0,0 +1,124 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +// #include "juce_AudioProcessor.h" + +AudioProcessor::AudioProcessor() +{ + cachedTotalIns = 0; + cachedTotalOuts = 0; + + playHead = nullptr; + currentSampleRate = 0; + blockSize = 0; + latencySamples = 0; + + suspended = false; + nonRealtime = false; +} + +AudioProcessor::~AudioProcessor() +{ +} + +//============================================================================== +void AudioProcessor::setPlayHead (AudioPlayHead* const newPlayHead) +{ + playHead = newPlayHead; +} + +void AudioProcessor::setPlayConfigDetails (const int newNumIns, + const int newNumOuts, + const double newSampleRate, + const int newBlockSize) +{ + cachedTotalIns = newNumIns; + cachedTotalOuts = newNumOuts; + setRateAndBufferSizeDetails (newSampleRate, newBlockSize); +} + +void AudioProcessor::setRateAndBufferSizeDetails (double newSampleRate, int newBlockSize) noexcept +{ + currentSampleRate = newSampleRate; + blockSize = newBlockSize; +} + +//============================================================================== +void AudioProcessor::setNonRealtime (const bool newNonRealtime) noexcept +{ + nonRealtime = newNonRealtime; +} + +void AudioProcessor::setLatencySamples (const int newLatency) +{ + if (latencySamples != newLatency) + latencySamples = newLatency; +} + +void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) +{ + const CarlaRecursiveMutexLocker cml (callbackLock); + suspended = shouldBeSuspended; +} + +void AudioProcessor::reset() {} + +void AudioProcessor::processBypassed (AudioSampleBuffer& buffer, MidiBuffer&) +{ + for (int ch = getTotalNumInputChannels(); ch < getTotalNumOutputChannels(); ++ch) + buffer.clear (ch, 0, buffer.getNumSamples()); +} + +void AudioProcessor::processBlockBypassed (AudioSampleBuffer& buffer, MidiBuffer& midi) { processBypassed (buffer, midi); } + +//============================================================================== +bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const noexcept +{ + return timeInSamples == other.timeInSamples + && ppqPosition == other.ppqPosition + && editOriginTime == other.editOriginTime + && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart + && frameRate == other.frameRate + && isPlaying == other.isPlaying + && isRecording == other.isRecording + && bpm == other.bpm + && timeSigNumerator == other.timeSigNumerator + && timeSigDenominator == other.timeSigDenominator + && ppqLoopStart == other.ppqLoopStart + && ppqLoopEnd == other.ppqLoopEnd + && isLooping == other.isLooping; +} + +bool AudioPlayHead::CurrentPositionInfo::operator!= (const CurrentPositionInfo& other) const noexcept +{ + return ! operator== (other); +} + +void AudioPlayHead::CurrentPositionInfo::resetToDefault() +{ + zerostruct (*this); + timeSigNumerator = 4; + timeSigDenominator = 4; + bpm = 120; +} diff --git a/source/modules/juce_audio_graph/processors/juce_AudioProcessor.h b/source/modules/juce_audio_graph/processors/juce_AudioProcessor.h new file mode 100644 index 000000000..f619127c8 --- /dev/null +++ b/source/modules/juce_audio_graph/processors/juce_AudioProcessor.h @@ -0,0 +1,381 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED +#define JUCE_AUDIOPROCESSOR_H_INCLUDED + +// #include "juce_AudioPlayHead.h" + +//============================================================================== +/** + Base class for audio processing filters or plugins. + + This is intended to act as a base class of audio filter that is general enough to + be wrapped as a VST, AU, RTAS, etc, or used internally. + + It is also used by the plugin hosting code as the wrapper around an instance + of a loaded plugin. + + Derive your filter class from this base class, and if you're building a plugin, + you should implement a global function called createPluginFilter() which creates + and returns a new instance of your subclass. +*/ +class JUCE_API AudioProcessor +{ +protected: + //============================================================================== + /** Constructor. + + This constructor will create a main input and output bus which are diabled + by default. If you need more fine grain control then use the other + constructors. + */ + AudioProcessor(); + +public: + //============================================================================== + /** Destructor. */ + virtual ~AudioProcessor(); + + //============================================================================== + /** Returns the name of this processor. */ + virtual const String getName() const = 0; + + //============================================================================== + /** Called before playback starts, to let the filter prepare itself. + + The sample rate is the target sample rate, and will remain constant until + playback stops. + + You can call getTotalNumInputChannels and getTotalNumOutputChannels + or query the busLayout member variable to find out the number of + channels your processBlock callback must process. + + The maximumExpectedSamplesPerBlock value is a strong hint about the maximum + number of samples that will be provided in each block. You may want to use + this value to resize internal buffers. You should program defensively in + case a buggy host exceeds this value. The actual block sizes that the host + uses may be different each time the callback happens: completely variable + block sizes can be expected from some hosts. + + @see busLayout, getTotalNumInputChannels, getTotalNumOutputChannels + */ + virtual void prepareToPlay (double sampleRate, + int maximumExpectedSamplesPerBlock) = 0; + + /** Called after playback has stopped, to let the filter free up any resources it + no longer needs. + */ + virtual void releaseResources() = 0; + + /** Renders the next block. + + When this method is called, the buffer contains a number of channels which is + at least as great as the maximum number of input and output channels that + this filter is using. It will be filled with the filter's input data and + should be replaced with the filter's output. + + So for example if your filter has a total of 2 input channels and 4 output + channels, then the buffer will contain 4 channels, the first two being filled + with the input data. Your filter should read these, do its processing, and + replace the contents of all 4 channels with its output. + + Or if your filter has a total of 5 inputs and 2 outputs, the buffer will have 5 + channels, all filled with data, and your filter should overwrite the first 2 of + these with its output. But be VERY careful not to write anything to the last 3 + channels, as these might be mapped to memory that the host assumes is read-only! + + If your plug-in has more than one input or output buses then the buffer passed + to the processBlock methods will contain a bundle of all channels of each bus. + Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a + particular bus. + + Note that if you have more outputs than inputs, then only those channels that + correspond to an input channel are guaranteed to contain sensible data - e.g. + in the case of 2 inputs and 4 outputs, the first two channels contain the input, + but the last two channels may contain garbage, so you should be careful not to + let this pass through without being overwritten or cleared. + + Also note that the buffer may have more channels than are strictly necessary, + but you should only read/write from the ones that your filter is supposed to + be using. + + The number of samples in these buffers is NOT guaranteed to be the same for every + callback, and may be more or less than the estimated value given to prepareToPlay(). + Your code must be able to cope with variable-sized blocks, or you're going to get + clicks and crashes! + + Also note that some hosts will occasionally decide to pass a buffer containing + zero samples, so make sure that your algorithm can deal with that! + + If the filter is receiving a midi input, then the midiMessages array will be filled + with the midi messages for this block. Each message's timestamp will indicate the + message's time, as a number of samples from the start of the block. + + Any messages left in the midi buffer when this method has finished are assumed to + be the filter's midi output. This means that your filter should be careful to + clear any incoming messages from the array if it doesn't want them to be passed-on. + + Be very careful about what you do in this callback - it's going to be called by + the audio thread, so any kind of interaction with the UI is absolutely + out of the question. If you change a parameter in here and need to tell your UI to + update itself, the best way is probably to inherit from a ChangeBroadcaster, let + the UI components register as listeners, and then call sendChangeMessage() inside the + processBlock() method to send out an asynchronous message. You could also use + the AsyncUpdater class in a similar way. + + @see AudiobusLayout::getBusBuffer + */ + + virtual void processBlock (AudioSampleBuffer& buffer, + MidiBuffer& midiMessages) = 0; + + /** Renders the next block when the processor is being bypassed. + + The default implementation of this method will pass-through any incoming audio, but + you may override this method e.g. to add latency compensation to the data to match + the processor's latency characteristics. This will avoid situations where bypassing + will shift the signal forward in time, possibly creating pre-echo effects and odd timings. + Another use for this method would be to cross-fade or morph between the wet (not bypassed) + and dry (bypassed) signals. + */ + virtual void processBlockBypassed (AudioSampleBuffer& buffer, + MidiBuffer& midiMessages); + + //============================================================================== + /** Returns the current AudioPlayHead object that should be used to find + out the state and position of the playhead. + + You can ONLY call this from your processBlock() method! Calling it at other + times will produce undefined behaviour, as the host may not have any context + in which a time would make sense, and some hosts will almost certainly have + multithreading issues if it's not called on the audio thread. + + The AudioPlayHead object that is returned can be used to get the details about + the time of the start of the block currently being processed. But do not + store this pointer or use it outside of the current audio callback, because + the host may delete or re-use it. + + If the host can't or won't provide any time info, this will return nullptr. + */ + AudioPlayHead* getPlayHead() const noexcept { return playHead; } + + //============================================================================== + /** Returns the total number of input channels. + + This method will return the total number of input channels by accumulating + the number of channels on each input bus. The number of channels of the + buffer passed to your processBlock callback will be equivalent to either + getTotalNumInputChannels or getTotalNumOutputChannels - which ever + is greater. + + Note that getTotalNumInputChannels is equivalent to + getMainBusNumInputChannels if your processor does not have any sidechains + or aux buses. + */ + int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } + + /** Returns the total number of output channels. + + This method will return the total number of output channels by accumulating + the number of channels on each output bus. The number of channels of the + buffer passed to your processBlock callback will be equivalent to either + getTotalNumInputChannels or getTotalNumOutputChannels - which ever + is greater. + + Note that getTotalNumOutputChannels is equivalent to + getMainBusNumOutputChannels if your processor does not have any sidechains + or aux buses. + */ + int getTotalNumOutputChannels() const noexcept { return cachedTotalOuts; } + + //============================================================================== + /** Returns the current sample rate. + + This can be called from your processBlock() method - it's not guaranteed + to be valid at any other time, and may return 0 if it's unknown. + */ + double getSampleRate() const noexcept { return currentSampleRate; } + + /** Returns the current typical block size that is being used. + + This can be called from your processBlock() method - it's not guaranteed + to be valid at any other time. + + Remember it's not the ONLY block size that may be used when calling + processBlock, it's just the normal one. The actual block sizes used may be + larger or smaller than this, and will vary between successive calls. + */ + int getBlockSize() const noexcept { return blockSize; } + + //============================================================================== + + /** This returns the number of samples delay that the filter imposes on the audio + passing through it. + + The host will call this to find the latency - the filter itself should set this value + by calling setLatencySamples() as soon as it can during its initialisation. + */ + int getLatencySamples() const noexcept { return latencySamples; } + + /** The filter should call this to set the number of samples delay that it introduces. + + The filter should call this as soon as it can during initialisation, and can call it + later if the value changes. + */ + void setLatencySamples (int newLatency); + + /** Returns the length of the filter's tail, in seconds. */ + virtual double getTailLengthSeconds() const = 0; + + /** Returns true if the processor wants midi messages. */ + virtual bool acceptsMidi() const = 0; + + /** Returns true if the processor produces midi messages. */ + virtual bool producesMidi() const = 0; + + /** Returns true if the processor supports MPE. */ + virtual bool supportsMPE() const { return false; } + + /** Returns true if this is a midi effect plug-in and does no audio processing. */ + virtual bool isMidiEffect() const { return false; } + + //============================================================================== + /** This returns a critical section that will automatically be locked while the host + is calling the processBlock() method. + + Use it from your UI or other threads to lock access to variables that are used + by the process callback, but obviously be careful not to keep it locked for + too long, because that could cause stuttering playback. If you need to do something + that'll take a long time and need the processing to stop while it happens, use the + suspendProcessing() method instead. + + @see suspendProcessing + */ + const CarlaRecursiveMutex& getCallbackLock() const noexcept { return callbackLock; } + + /** Enables and disables the processing callback. + + If you need to do something time-consuming on a thread and would like to make sure + the audio processing callback doesn't happen until you've finished, use this + to disable the callback and re-enable it again afterwards. + + E.g. + @code + void loadNewPatch() + { + suspendProcessing (true); + + ..do something that takes ages.. + + suspendProcessing (false); + } + @endcode + + If the host tries to make an audio callback while processing is suspended, the + filter will return an empty buffer, but won't block the audio thread like it would + do if you use the getCallbackLock() critical section to synchronise access. + + Any code that calls processBlock() should call isSuspended() before doing so, and + if the processor is suspended, it should avoid the call and emit silence or + whatever is appropriate. + + @see getCallbackLock + */ + void suspendProcessing (bool shouldBeSuspended); + + /** Returns true if processing is currently suspended. + @see suspendProcessing + */ + bool isSuspended() const noexcept { return suspended; } + + /** A plugin can override this to be told when it should reset any playing voices. + + The default implementation does nothing, but a host may call this to tell the + plugin that it should stop any tails or sounds that have been left running. + */ + virtual void reset(); + + //============================================================================== + /** Returns true if the processor is being run in an offline mode for rendering. + + If the processor is being run live on realtime signals, this returns false. + If the mode is unknown, this will assume it's realtime and return false. + + This value may be unreliable until the prepareToPlay() method has been called, + and could change each time prepareToPlay() is called. + + @see setNonRealtime() + */ + bool isNonRealtime() const noexcept { return nonRealtime; } + + /** Called by the host to tell this processor whether it's being used in a non-realtime + capacity for offline rendering or bouncing. + */ + virtual void setNonRealtime (bool isNonRealtime) noexcept; + + //============================================================================== + /** Tells the processor to use this playhead object. + The processor will not take ownership of the object, so the caller must delete it when + it is no longer being used. + */ + virtual void setPlayHead (AudioPlayHead* newPlayHead); + + //============================================================================== + /** This is called by the processor to specify its details before being played. Use this + version of the function if you are not interested in any sidechain and/or aux buses + and do not care about the layout of channels. Otherwise use setRateAndBufferSizeDetails.*/ + void setPlayConfigDetails (int numIns, int numOuts, double sampleRate, int blockSize); + + /** This is called by the processor to specify its details before being played. You + should call this function after having informed the processor about the channel + and bus layouts via setBusesLayout. + + @see setBusesLayout + */ + void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; + +private: + //============================================================================== + /** @internal */ + AudioPlayHead* playHead; + + //============================================================================== + double currentSampleRate; + int blockSize, latencySamples; + bool suspended, nonRealtime; + CarlaRecursiveMutex callbackLock; + + String cachedInputSpeakerArrString; + String cachedOutputSpeakerArrString; + + int cachedTotalIns, cachedTotalOuts; + + void processBypassed (AudioSampleBuffer&, MidiBuffer&); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessor) +}; + + +#endif // JUCE_AUDIOPROCESSOR_H_INCLUDED diff --git a/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.cpp b/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.cpp new file mode 100644 index 000000000..636aefeb2 --- /dev/null +++ b/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.cpp @@ -0,0 +1,1630 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#include "juce_AudioProcessorGraph.h" + +const int AudioProcessorGraph::midiChannelIndex = 0x1000; + +#if 0 +//============================================================================== +template struct FloatDoubleUtil {}; +template struct FloatDoubleType {}; + +template +struct FloatAndDoubleComposition +{ + typedef typename FloatDoubleType::Type FloatType; + typedef typename FloatDoubleType::Type DoubleType; + + template + inline typename FloatDoubleType::Type& get() noexcept + { + return FloatDoubleUtil >::get (*this); + } + + FloatType floatVersion; + DoubleType doubleVersion; +}; + +template struct FloatDoubleUtil { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } }; +template struct FloatDoubleUtil { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } }; + +struct FloatPlaceholder; + +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef AudioBuffer Type; }; +template struct FloatDoubleType*, FloatingType> { typedef AudioBuffer* Type; }; +#endif + +//============================================================================== +namespace GraphRenderingOps +{ + +struct AudioGraphRenderingOpBase +{ + AudioGraphRenderingOpBase() noexcept {} + virtual ~AudioGraphRenderingOpBase() {} + + virtual void perform (AudioSampleBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) = 0; + + JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase) +}; + +// use CRTP +template +struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase +{ + void perform (AudioSampleBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } +}; + +//============================================================================== +struct ClearChannelOp : public AudioGraphRenderingOp +{ + ClearChannelOp (const int channel) noexcept : channelNum (channel) {} + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + sharedBufferChans.clear (channelNum, 0, numSamples); + } + + const int channelNum; + + JUCE_DECLARE_NON_COPYABLE (ClearChannelOp) +}; + +//============================================================================== +struct CopyChannelOp : public AudioGraphRenderingOp +{ + CopyChannelOp (const int srcChan, const int dstChan) noexcept + : srcChannelNum (srcChan), dstChannelNum (dstChan) + {} + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); + } + + const int srcChannelNum, dstChannelNum; + + JUCE_DECLARE_NON_COPYABLE (CopyChannelOp) +}; + +//============================================================================== +struct AddChannelOp : public AudioGraphRenderingOp +{ + AddChannelOp (const int srcChan, const int dstChan) noexcept + : srcChannelNum (srcChan), dstChannelNum (dstChan) + {} + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); + } + + const int srcChannelNum, dstChannelNum; + + JUCE_DECLARE_NON_COPYABLE (AddChannelOp) +}; + +//============================================================================== +struct ClearMidiBufferOp : public AudioGraphRenderingOp +{ + ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} + + void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) + { + sharedMidiBuffers.getUnchecked (bufferNum)->clear(); + } + + const int bufferNum; + + JUCE_DECLARE_NON_COPYABLE (ClearMidiBufferOp) +}; + +//============================================================================== +struct CopyMidiBufferOp : public AudioGraphRenderingOp +{ + CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept + : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) + {} + + void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) + { + *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); + } + + const int srcBufferNum, dstBufferNum; + + JUCE_DECLARE_NON_COPYABLE (CopyMidiBufferOp) +}; + +//============================================================================== +struct AddMidiBufferOp : public AudioGraphRenderingOp +{ + AddMidiBufferOp (const int srcBuffer, const int dstBuffer) + : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) + {} + + void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) + { + sharedMidiBuffers.getUnchecked (dstBufferNum) + ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); + } + + const int srcBufferNum, dstBufferNum; + + JUCE_DECLARE_NON_COPYABLE (AddMidiBufferOp) +}; + +//============================================================================== +struct DelayChannelOp : public AudioGraphRenderingOp +{ + DelayChannelOp (const int chan, const int delaySize) + : channel (chan), + bufferSize (delaySize + 1), + readIndex (0), writeIndex (delaySize) + { + buffer.calloc ((size_t) bufferSize); + } + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + { + float* data = sharedBufferChans.getWritePointer (channel, 0); + HeapBlock& block = buffer; + + for (int i = numSamples; --i >= 0;) + { + block [writeIndex] = *data; + *data++ = block [readIndex]; + + if (++readIndex >= bufferSize) readIndex = 0; + if (++writeIndex >= bufferSize) writeIndex = 0; + } + } + +private: + HeapBlock buffer; + const int channel, bufferSize; + int readIndex, writeIndex; + + JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) +}; + +#if 0 +//============================================================================== +struct ProcessBufferOp : public AudioGraphRenderingOp +{ + ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, + const Array& audioChannelsUsed, + const int totalNumChans, + const int midiBuffer) + : node (n), + processor (n->getProcessor()), + audioChannelsToUse (audioChannelsUsed), + totalChans (jmax (1, totalNumChans)), + midiBufferToUse (midiBuffer) + { + audioChannels.calloc ((size_t) totalChans); + + while (audioChannelsToUse.size() < totalChans) + audioChannelsToUse.add (0); + } + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) + { + HeapBlock& channels = audioChannels(); + + for (int i = totalChans; --i >= 0;) + channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); + + AudioSampleBuffer buffer (channels, totalChans, numSamples); + + if (processor->isSuspended()) + { + buffer.clear(); + } + else + { + const CarlaRecursiveMutexLocker cml (processor->getCallbackLock()); + + callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + } + } + + void callProcess (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) + { + processor->processBlock (buffer, midiMessages); + } + + const AudioProcessorGraph::Node::Ptr node; + AudioProcessor* const processor; + +private: + Array audioChannelsToUse; + FloatAndDoubleComposition > audioChannels; + AudioSampleBuffer tempBuffer; + const int totalChans; + const int midiBufferToUse; + + JUCE_DECLARE_NON_COPYABLE (ProcessBufferOp) +}; +#endif + +//============================================================================== +/** Used to calculate the correct sequence of rendering ops needed, based on + the best re-use of shared buffers at each stage. +*/ +struct RenderingOpSequenceCalculator +{ + RenderingOpSequenceCalculator (AudioProcessorGraph& g, + const Array& nodes, + Array& renderingOps) + : graph (g), + orderedNodes (nodes), + totalLatency (0) + { + nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros + channels.add (0); + + midiNodeIds.add ((uint32) zeroNodeID); + + for (int i = 0; i < orderedNodes.size(); ++i) + { + createRenderingOpsForNode (*orderedNodes.getUnchecked(i), renderingOps, i); + markAnyUnusedBuffersAsFree (i); + } + +#if 0 + graph.setLatencySamples (totalLatency); +#endif + } + + int getNumBuffersNeeded() const noexcept { return nodeIds.size(); } + int getNumMidiBuffersNeeded() const noexcept { return midiNodeIds.size(); } + +private: + //============================================================================== + AudioProcessorGraph& graph; + const Array& orderedNodes; + Array channels; + Array nodeIds, midiNodeIds; + + enum { freeNodeID = 0xffffffff, zeroNodeID = 0xfffffffe }; + + static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID && nodeID != zeroNodeID; } + + Array nodeDelayIDs; + Array nodeDelays; + int totalLatency; + + int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } + + void setNodeDelay (const uint32 nodeID, const int latency) + { + const int index = nodeDelayIDs.indexOf (nodeID); + + if (index >= 0) + { + nodeDelays.set (index, latency); + } + else + { + nodeDelayIDs.add (nodeID); + nodeDelays.add (latency); + } + } + + int getInputLatencyForNode (const uint32 nodeID) const + { + int maxLatency = 0; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == nodeID) + maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); + } + + return maxLatency; + } + + //============================================================================== + void createRenderingOpsForNode (AudioProcessorGraph::Node& node, + Array& renderingOps, + const int ourRenderingIndex) + { +#if 0 + AudioProcessor& processor = *node.getProcessor(); + const int numIns = processor.getTotalNumInputChannels(); + const int numOuts = processor.getTotalNumOutputChannels(); + const int totalChans = jmax (numIns, numOuts); +#else + const int numIns = 0; + const int numOuts = 0; +#endif + + Array audioChannelsToUse; + int midiBufferToUse = -1; + + int maxLatency = getInputLatencyForNode (node.nodeId); + + for (int inputChan = 0; inputChan < numIns; ++inputChan) + { + // get a list of all the inputs to this node + Array sourceNodes; + Array sourceOutputChans; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == node.nodeId && c->destChannelIndex == inputChan) + { + sourceNodes.add (c->sourceNodeId); + sourceOutputChans.add (c->sourceChannelIndex); + } + } + + int bufIndex = -1; + + if (sourceNodes.size() == 0) + { + // unconnected input channel + + if (inputChan >= numOuts) + { + bufIndex = getReadOnlyEmptyBuffer(); + jassert (bufIndex >= 0); + } + else + { + bufIndex = getFreeBuffer (false); + renderingOps.add (new ClearChannelOp (bufIndex)); + } + } + else if (sourceNodes.size() == 1) + { + // channel with a straightforward single input.. + const uint32 srcNode = sourceNodes.getUnchecked(0); + const int srcChan = sourceOutputChans.getUnchecked(0); + + bufIndex = getBufferContaining (srcNode, srcChan); + + if (bufIndex < 0) + { + // if not found, this is probably a feedback loop + bufIndex = getReadOnlyEmptyBuffer(); + jassert (bufIndex >= 0); + } + + if (inputChan < numOuts + && isBufferNeededLater (ourRenderingIndex, + inputChan, + srcNode, srcChan)) + { + // can't mess up this channel because it's needed later by another node, so we + // need to use a copy of it.. + const int newFreeBuffer = getFreeBuffer (false); + + renderingOps.add (new CopyChannelOp (bufIndex, newFreeBuffer)); + + bufIndex = newFreeBuffer; + } + + const int nodeDelay = getNodeDelay (srcNode); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); + } + else + { + // channel with a mix of several inputs.. + + // try to find a re-usable channel from our inputs.. + int reusableInputIndex = -1; + + for (int i = 0; i < sourceNodes.size(); ++i) + { + const int sourceBufIndex = getBufferContaining (sourceNodes.getUnchecked(i), + sourceOutputChans.getUnchecked(i)); + + if (sourceBufIndex >= 0 + && ! isBufferNeededLater (ourRenderingIndex, + inputChan, + sourceNodes.getUnchecked(i), + sourceOutputChans.getUnchecked(i))) + { + // we've found one of our input chans that can be re-used.. + reusableInputIndex = i; + bufIndex = sourceBufIndex; + + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay)); + + break; + } + } + + if (reusableInputIndex < 0) + { + // can't re-use any of our input chans, so get a new one and copy everything into it.. + bufIndex = getFreeBuffer (false); + jassert (bufIndex != 0); + + const int srcIndex = getBufferContaining (sourceNodes.getUnchecked (0), + sourceOutputChans.getUnchecked (0)); + if (srcIndex < 0) + { + // if not found, this is probably a feedback loop + renderingOps.add (new ClearChannelOp (bufIndex)); + } + else + { + renderingOps.add (new CopyChannelOp (srcIndex, bufIndex)); + } + + reusableInputIndex = 0; + const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); + } + + for (int j = 0; j < sourceNodes.size(); ++j) + { + if (j != reusableInputIndex) + { + int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), + sourceOutputChans.getUnchecked(j)); + if (srcIndex >= 0) + { + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); + + if (nodeDelay < maxLatency) + { + if (! isBufferNeededLater (ourRenderingIndex, inputChan, + sourceNodes.getUnchecked(j), + sourceOutputChans.getUnchecked(j))) + { + renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay)); + } + else // buffer is reused elsewhere, can't be delayed + { + const int bufferToDelay = getFreeBuffer (false); + renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay)); + renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay)); + srcIndex = bufferToDelay; + } + } + + renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + } + } + } + } + + jassert (bufIndex >= 0); + audioChannelsToUse.add (bufIndex); + + if (inputChan < numOuts) + markBufferAsContaining (bufIndex, node.nodeId, inputChan); + } + + for (int outputChan = numIns; outputChan < numOuts; ++outputChan) + { + const int bufIndex = getFreeBuffer (false); + jassert (bufIndex != 0); + audioChannelsToUse.add (bufIndex); + + markBufferAsContaining (bufIndex, node.nodeId, outputChan); + } + + // Now the same thing for midi.. + Array midiSourceNodes; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == node.nodeId && c->destChannelIndex == AudioProcessorGraph::midiChannelIndex) + midiSourceNodes.add (c->sourceNodeId); + } + + if (midiSourceNodes.size() == 0) + { + // No midi inputs.. + midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + +#if 0 + if (processor.acceptsMidi() || processor.producesMidi()) + renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); +#endif + } + else if (midiSourceNodes.size() == 1) + { + // One midi input.. + midiBufferToUse = getBufferContaining (midiSourceNodes.getUnchecked(0), + AudioProcessorGraph::midiChannelIndex); + + if (midiBufferToUse >= 0) + { + if (isBufferNeededLater (ourRenderingIndex, + AudioProcessorGraph::midiChannelIndex, + midiSourceNodes.getUnchecked(0), + AudioProcessorGraph::midiChannelIndex)) + { + // can't mess up this channel because it's needed later by another node, so we + // need to use a copy of it.. + const int newFreeBuffer = getFreeBuffer (true); + renderingOps.add (new CopyMidiBufferOp (midiBufferToUse, newFreeBuffer)); + midiBufferToUse = newFreeBuffer; + } + } + else + { + // probably a feedback loop, so just use an empty one.. + midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + } + } + else + { + // More than one midi input being mixed.. + int reusableInputIndex = -1; + + for (int i = 0; i < midiSourceNodes.size(); ++i) + { + const int sourceBufIndex = getBufferContaining (midiSourceNodes.getUnchecked(i), + AudioProcessorGraph::midiChannelIndex); + + if (sourceBufIndex >= 0 + && ! isBufferNeededLater (ourRenderingIndex, + AudioProcessorGraph::midiChannelIndex, + midiSourceNodes.getUnchecked(i), + AudioProcessorGraph::midiChannelIndex)) + { + // we've found one of our input buffers that can be re-used.. + reusableInputIndex = i; + midiBufferToUse = sourceBufIndex; + break; + } + } + + if (reusableInputIndex < 0) + { + // can't re-use any of our input buffers, so get a new one and copy everything into it.. + midiBufferToUse = getFreeBuffer (true); + jassert (midiBufferToUse >= 0); + + const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(0), + AudioProcessorGraph::midiChannelIndex); + if (srcIndex >= 0) + renderingOps.add (new CopyMidiBufferOp (srcIndex, midiBufferToUse)); + else + renderingOps.add (new ClearMidiBufferOp (midiBufferToUse)); + + reusableInputIndex = 0; + } + + for (int j = 0; j < midiSourceNodes.size(); ++j) + { + if (j != reusableInputIndex) + { + const int srcIndex = getBufferContaining (midiSourceNodes.getUnchecked(j), + AudioProcessorGraph::midiChannelIndex); + if (srcIndex >= 0) + renderingOps.add (new AddMidiBufferOp (srcIndex, midiBufferToUse)); + } + } + } + +#if 0 + if (processor.producesMidi()) + markBufferAsContaining (midiBufferToUse, node.nodeId, + AudioProcessorGraph::midiChannelIndex); + + setNodeDelay (node.nodeId, maxLatency + processor.getLatencySamples()); + + if (numOuts == 0) + totalLatency = maxLatency; + + renderingOps.add (new ProcessBufferOp (&node, audioChannelsToUse, + totalChans, midiBufferToUse)); +#endif + } + + //============================================================================== + int getFreeBuffer (const bool forMidi) + { + if (forMidi) + { + for (int i = 1; i < midiNodeIds.size(); ++i) + if (midiNodeIds.getUnchecked(i) == freeNodeID) + return i; + + midiNodeIds.add ((uint32) freeNodeID); + return midiNodeIds.size() - 1; + } + else + { + for (int i = 1; i < nodeIds.size(); ++i) + if (nodeIds.getUnchecked(i) == freeNodeID) + return i; + + nodeIds.add ((uint32) freeNodeID); + channels.add (0); + return nodeIds.size() - 1; + } + } + + int getReadOnlyEmptyBuffer() const noexcept + { + return 0; + } + + int getBufferContaining (const uint32 nodeId, const int outputChannel) const noexcept + { + if (outputChannel == AudioProcessorGraph::midiChannelIndex) + { + for (int i = midiNodeIds.size(); --i >= 0;) + if (midiNodeIds.getUnchecked(i) == nodeId) + return i; + } + else + { + for (int i = nodeIds.size(); --i >= 0;) + if (nodeIds.getUnchecked(i) == nodeId + && channels.getUnchecked(i) == outputChannel) + return i; + } + + return -1; + } + + void markAnyUnusedBuffersAsFree (const int stepIndex) + { + for (int i = 0; i < nodeIds.size(); ++i) + { + if (isNodeBusy (nodeIds.getUnchecked(i)) + && ! isBufferNeededLater (stepIndex, -1, + nodeIds.getUnchecked(i), + channels.getUnchecked(i))) + { + nodeIds.set (i, (uint32) freeNodeID); + } + } + + for (int i = 0; i < midiNodeIds.size(); ++i) + { + if (isNodeBusy (midiNodeIds.getUnchecked(i)) + && ! isBufferNeededLater (stepIndex, -1, + midiNodeIds.getUnchecked(i), + AudioProcessorGraph::midiChannelIndex)) + { + midiNodeIds.set (i, (uint32) freeNodeID); + } + } + } + + bool isBufferNeededLater (int stepIndexToSearchFrom, + int inputChannelOfIndexToIgnore, + const uint32 nodeId, + const int outputChanIndex) const + { + while (stepIndexToSearchFrom < orderedNodes.size()) + { + const AudioProcessorGraph::Node* const node = (const AudioProcessorGraph::Node*) orderedNodes.getUnchecked (stepIndexToSearchFrom); + + if (outputChanIndex == AudioProcessorGraph::midiChannelIndex) + { + if (inputChannelOfIndexToIgnore != AudioProcessorGraph::midiChannelIndex + && graph.getConnectionBetween (nodeId, AudioProcessorGraph::midiChannelIndex, + node->nodeId, AudioProcessorGraph::midiChannelIndex) != nullptr) + return true; + } + else + { +#if 0 + for (int i = 0; i < node->getProcessor()->getTotalNumInputChannels(); ++i) + if (i != inputChannelOfIndexToIgnore + && graph.getConnectionBetween (nodeId, outputChanIndex, + node->nodeId, i) != nullptr) + return true; +#endif + } + + inputChannelOfIndexToIgnore = -1; + ++stepIndexToSearchFrom; + } + + return false; + } + + void markBufferAsContaining (int bufferNum, uint32 nodeId, int outputIndex) + { + if (outputIndex == AudioProcessorGraph::midiChannelIndex) + { + jassert (bufferNum > 0 && bufferNum < midiNodeIds.size()); + + midiNodeIds.set (bufferNum, nodeId); + } + else + { + jassert (bufferNum >= 0 && bufferNum < nodeIds.size()); + + nodeIds.set (bufferNum, nodeId); + channels.set (bufferNum, outputIndex); + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RenderingOpSequenceCalculator) +}; + +//============================================================================== +// Holds a fast lookup table for checking which nodes are inputs to others. +class ConnectionLookupTable +{ +public: + explicit ConnectionLookupTable (const OwnedArray& connections) + { + for (int i = 0; i < connections.size(); ++i) + { + const AudioProcessorGraph::Connection* const c = connections.getUnchecked(i); + + int index; + Entry* entry = findEntry (c->destNodeId, index); + + if (entry == nullptr) + { + entry = new Entry (c->destNodeId); + entries.insert (index, entry); + } + +#if 0 + entry->srcNodes.add (c->sourceNodeId); +#endif + } + } + + bool isAnInputTo (const uint32 possibleInputId, + const uint32 possibleDestinationId) const noexcept + { + return isAnInputToRecursive (possibleInputId, possibleDestinationId, entries.size()); + } + +private: + //============================================================================== + struct Entry + { + explicit Entry (const uint32 destNodeId_) noexcept : destNodeId (destNodeId_) {} + + const uint32 destNodeId; +#if 0 + SortedSet srcNodes; +#endif + + JUCE_DECLARE_NON_COPYABLE (Entry) + }; + + OwnedArray entries; + + bool isAnInputToRecursive (const uint32 possibleInputId, + const uint32 possibleDestinationId, + int recursionCheck) const noexcept + { + int index; + + if (const Entry* const entry = findEntry (possibleDestinationId, index)) + { +#if 0 + const SortedSet& srcNodes = entry->srcNodes; + + if (srcNodes.contains (possibleInputId)) + return true; + + if (--recursionCheck >= 0) + { + for (int i = 0; i < srcNodes.size(); ++i) + if (isAnInputToRecursive (possibleInputId, srcNodes.getUnchecked(i), recursionCheck)) + return true; + } +#else + (void)possibleInputId; + (void)recursionCheck; +#endif + } + + return false; + } + + Entry* findEntry (const uint32 destNodeId, int& insertIndex) const noexcept + { + Entry* result = nullptr; + + int start = 0; + int end = entries.size(); + + for (;;) + { + if (start >= end) + { + break; + } + else if (destNodeId == entries.getUnchecked (start)->destNodeId) + { + result = entries.getUnchecked (start); + break; + } + else + { + const int halfway = (start + end) / 2; + + if (halfway == start) + { + if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) + ++start; + + break; + } + else if (destNodeId >= entries.getUnchecked (halfway)->destNodeId) + start = halfway; + else + end = halfway; + } + } + + insertIndex = start; + return result; + } + + JUCE_DECLARE_NON_COPYABLE (ConnectionLookupTable) +}; + +//============================================================================== +struct ConnectionSorter +{ + static int compareElements (const AudioProcessorGraph::Connection* const first, + const AudioProcessorGraph::Connection* const second) noexcept + { + if (first->sourceNodeId < second->sourceNodeId) return -1; + if (first->sourceNodeId > second->sourceNodeId) return 1; + if (first->destNodeId < second->destNodeId) return -1; + if (first->destNodeId > second->destNodeId) return 1; + if (first->sourceChannelIndex < second->sourceChannelIndex) return -1; + if (first->sourceChannelIndex > second->sourceChannelIndex) return 1; + if (first->destChannelIndex < second->destChannelIndex) return -1; + if (first->destChannelIndex > second->destChannelIndex) return 1; + + return 0; + } +}; + +} + +//============================================================================== +AudioProcessorGraph::Connection::Connection (const uint32 sourceID, const int sourceChannel, + const uint32 destID, const int destChannel) noexcept + : sourceNodeId (sourceID), sourceChannelIndex (sourceChannel), + destNodeId (destID), destChannelIndex (destChannel) +{ +} + +//============================================================================== +AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) noexcept + : nodeId (nodeID), processor (p), isPrepared (false) +{ + jassert (processor != nullptr); +} + +void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, + AudioProcessorGraph* const graph) +{ + if (! isPrepared) + { + isPrepared = true; + setParentGraph (graph); + + processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); + processor->prepareToPlay (newSampleRate, newBlockSize); + } +} + +void AudioProcessorGraph::Node::unprepare() +{ + if (isPrepared) + { + isPrepared = false; + processor->releaseResources(); + } +} + +void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph) const +{ + if (AudioProcessorGraph::AudioGraphIOProcessor* const ioProc + = dynamic_cast (processor.get())) + ioProc->setParentGraph (graph); +} + +//============================================================================== +struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers +{ + AudioProcessorGraphBufferHelpers() + { + currentAudioInputBuffer = nullptr; + } + + void setRenderingBufferSize (int newNumChannels, int newNumSamples) + { + renderingBuffers.setSize (newNumChannels, newNumSamples); + renderingBuffers.clear(); + } + + void release() + { + renderingBuffers.setSize (1, 1); + currentAudioInputBuffer = nullptr; + currentAudioOutputBuffer.setSize (1, 1); + } + + void prepareInOutBuffers(int newNumChannels, int newNumSamples) + { + currentAudioInputBuffer = nullptr; + currentAudioOutputBuffer.setSize (newNumChannels, newNumSamples); + } + + AudioSampleBuffer renderingBuffers; + AudioSampleBuffer* currentAudioInputBuffer; + AudioSampleBuffer currentAudioOutputBuffer; +}; + +//============================================================================== +AudioProcessorGraph::AudioProcessorGraph() + : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), + currentMidiInputBuffer (nullptr), isPrepared (false) +{ +} + +AudioProcessorGraph::~AudioProcessorGraph() +{ + clearRenderingSequence(); + clear(); +} + +const String AudioProcessorGraph::getName() const +{ + return "Audio Graph"; +} + +//============================================================================== +void AudioProcessorGraph::clear() +{ +#if 0 + nodes.clear(); +#endif + connections.clear(); +#if 0 + triggerAsyncUpdate(); +#endif +} + +#if 0 +AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeId) const +{ + for (int i = nodes.size(); --i >= 0;) + if (nodes.getUnchecked(i)->nodeId == nodeId) + return nodes.getUnchecked(i); + + return nullptr; +} + +AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId) +{ + if (newProcessor == nullptr || newProcessor == this) + { + jassertfalse; + return nullptr; + } + + for (int i = nodes.size(); --i >= 0;) + { + if (nodes.getUnchecked(i)->getProcessor() == newProcessor) + { + jassertfalse; // Cannot add the same object to the graph twice! + return nullptr; + } + } + + if (nodeId == 0) + { + nodeId = ++lastNodeId; + } + else + { + // you can't add a node with an id that already exists in the graph.. + jassert (getNodeForId (nodeId) == nullptr); + removeNode (nodeId); + + if (nodeId > lastNodeId) + lastNodeId = nodeId; + } + + newProcessor->setPlayHead (getPlayHead()); + + Node* const n = new Node (nodeId, newProcessor); + nodes.add (n); + + if (isPrepared) + triggerAsyncUpdate(); + + n->setParentGraph (this); + return n; +} + +bool AudioProcessorGraph::removeNode (const uint32 nodeId) +{ + disconnectNode (nodeId); + + for (int i = nodes.size(); --i >= 0;) + { + if (nodes.getUnchecked(i)->nodeId == nodeId) + { + nodes.remove (i); + + if (isPrepared) + triggerAsyncUpdate(); + + return true; + } + } + + return false; +} + +bool AudioProcessorGraph::removeNode (Node* node) +{ + if (node != nullptr) + return removeNode (node->nodeId); + + jassertfalse; + return false; +} +#endif + +//============================================================================== +const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, + const int sourceChannelIndex, + const uint32 destNodeId, + const int destChannelIndex) const +{ + const Connection c (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex); + GraphRenderingOps::ConnectionSorter sorter; + return connections [connections.indexOfSorted (sorter, &c)]; +} + +bool AudioProcessorGraph::isConnected (const uint32 possibleSourceNodeId, + const uint32 possibleDestNodeId) const +{ + for (int i = connections.size(); --i >= 0;) + { + const Connection* const c = connections.getUnchecked(i); + + if (c->sourceNodeId == possibleSourceNodeId + && c->destNodeId == possibleDestNodeId) + { + return true; + } + } + + return false; +} + +#if 0 +bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, + const int sourceChannelIndex, + const uint32 destNodeId, + const int destChannelIndex) const +{ + if (sourceChannelIndex < 0 + || destChannelIndex < 0 + || sourceNodeId == destNodeId + || (destChannelIndex == midiChannelIndex) != (sourceChannelIndex == midiChannelIndex)) + return false; + + const Node* const source = getNodeForId (sourceNodeId); + + if (source == nullptr + || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) + || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) + return false; + + const Node* const dest = getNodeForId (destNodeId); + + if (dest == nullptr + || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) + || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) + return false; + + return getConnectionBetween (sourceNodeId, sourceChannelIndex, + destNodeId, destChannelIndex) == nullptr; +} +#endif + +bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, + const int sourceChannelIndex, + const uint32 destNodeId, + const int destChannelIndex) +{ + if (! canConnect (sourceNodeId, sourceChannelIndex, destNodeId, destChannelIndex)) + return false; + + GraphRenderingOps::ConnectionSorter sorter; + connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, + destNodeId, destChannelIndex)); + +#if 0 + if (isPrepared) + triggerAsyncUpdate(); +#endif + + return true; +} + +void AudioProcessorGraph::removeConnection (const int index) +{ + connections.remove (index); + +#if 0 + if (isPrepared) + triggerAsyncUpdate(); +#endif +} + +bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, + const uint32 destNodeId, const int destChannelIndex) +{ + bool doneAnything = false; + + for (int i = connections.size(); --i >= 0;) + { + const Connection* const c = connections.getUnchecked(i); + + if (c->sourceNodeId == sourceNodeId + && c->destNodeId == destNodeId + && c->sourceChannelIndex == sourceChannelIndex + && c->destChannelIndex == destChannelIndex) + { + removeConnection (i); + doneAnything = true; + } + } + + return doneAnything; +} + +bool AudioProcessorGraph::disconnectNode (const uint32 nodeId) +{ + bool doneAnything = false; + + for (int i = connections.size(); --i >= 0;) + { + const Connection* const c = connections.getUnchecked(i); + + if (c->sourceNodeId == nodeId || c->destNodeId == nodeId) + { + removeConnection (i); + doneAnything = true; + } + } + + return doneAnything; +} + +#if 0 +bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const +{ + jassert (c != nullptr); + + const Node* const source = getNodeForId (c->sourceNodeId); + const Node* const dest = getNodeForId (c->destNodeId); + + return source != nullptr + && dest != nullptr + && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) + : source->processor->producesMidi()) + && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) + : dest->processor->acceptsMidi()); +} +#endif + +bool AudioProcessorGraph::removeIllegalConnections() +{ + bool doneAnything = false; + + for (int i = connections.size(); --i >= 0;) + { + if (! isConnectionLegal (connections.getUnchecked(i))) + { + removeConnection (i); + doneAnything = true; + } + } + + return doneAnything; +} + +//============================================================================== +static void deleteRenderOpArray (Array& ops) +{ + for (int i = ops.size(); --i >= 0;) + delete static_cast (ops.getUnchecked(i)); +} + +void AudioProcessorGraph::clearRenderingSequence() +{ + Array oldOps; + + { + const CarlaRecursiveMutexLocker cml (getCallbackLock()); + renderingOps.swapWith (oldOps); + } + + deleteRenderOpArray (oldOps); +} + +#if 0 +bool AudioProcessorGraph::isAnInputTo (const uint32 possibleInputId, + const uint32 possibleDestinationId, + const int recursionCheck) const +{ + if (recursionCheck > 0) + { + for (int i = connections.size(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = connections.getUnchecked (i); + + if (c->destNodeId == possibleDestinationId + && (c->sourceNodeId == possibleInputId + || isAnInputTo (possibleInputId, c->sourceNodeId, recursionCheck - 1))) + return true; + } + } + + return false; +} +#endif + +void AudioProcessorGraph::buildRenderingSequence() +{ + Array newRenderingOps; + int numRenderingBuffersNeeded = 2; + int numMidiBuffersNeeded = 1; + + { +#if 0 + MessageManagerLock mml; +#endif + + Array orderedNodes; + + { + const GraphRenderingOps::ConnectionLookupTable table (connections); + +#if 0 + for (int i = 0; i < nodes.size(); ++i) + { + Node* const node = nodes.getUnchecked(i); + + node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); + + int j = 0; + for (; j < orderedNodes.size(); ++j) + if (table.isAnInputTo (node->nodeId, ((Node*) orderedNodes.getUnchecked(j))->nodeId)) + break; + + orderedNodes.insert (j, node); + } +#endif + } + + GraphRenderingOps::RenderingOpSequenceCalculator calculator (*this, orderedNodes, newRenderingOps); + + numRenderingBuffersNeeded = calculator.getNumBuffersNeeded(); + numMidiBuffersNeeded = calculator.getNumMidiBuffersNeeded(); + } + + { + // swap over to the new rendering sequence.. + const CarlaRecursiveMutexLocker cml (getCallbackLock()); + + audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); + + for (int i = midiBuffers.size(); --i >= 0;) + midiBuffers.getUnchecked(i)->clear(); + + while (midiBuffers.size() < numMidiBuffersNeeded) + midiBuffers.add (new MidiBuffer()); + + renderingOps.swapWith (newRenderingOps); + } + + // delete the old ones.. + deleteRenderOpArray (newRenderingOps); +} + +#if 0 +void AudioProcessorGraph::handleAsyncUpdate() +{ + buildRenderingSequence(); +} +#endif + +//============================================================================== +void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) +{ + audioBuffers->prepareInOutBuffers (jmax (1, getTotalNumOutputChannels()), estimatedSamplesPerBlock); + + currentMidiInputBuffer = nullptr; + currentMidiOutputBuffer.clear(); + + clearRenderingSequence(); + buildRenderingSequence(); + + isPrepared = true; +} + +void AudioProcessorGraph::releaseResources() +{ + isPrepared = false; + +#if 0 + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->unprepare(); +#endif + + audioBuffers->release(); + midiBuffers.clear(); + + currentMidiInputBuffer = nullptr; + currentMidiOutputBuffer.clear(); +} + +void AudioProcessorGraph::reset() +{ + const CarlaRecursiveMutexLocker cml (getCallbackLock()); + +#if 0 + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->getProcessor()->reset(); +#endif +} + +void AudioProcessorGraph::setNonRealtime (bool isProcessingNonRealtime) noexcept +{ + const CarlaRecursiveMutexLocker cml (getCallbackLock()); + + AudioProcessor::setNonRealtime (isProcessingNonRealtime); + +#if 0 + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->getProcessor()->setNonRealtime (isProcessingNonRealtime); +#endif +} + +void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) +{ + const CarlaRecursiveMutexLocker cml (getCallbackLock()); + + AudioProcessor::setPlayHead (audioPlayHead); + +#if 0 + for (int i = 0; i < nodes.size(); ++i) + nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); +#endif +} + +void AudioProcessorGraph::processAudio (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) +{ + AudioSampleBuffer& renderingBuffers = audioBuffers->renderingBuffers; + AudioSampleBuffer*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer; + AudioSampleBuffer& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer; + + const int numSamples = buffer.getNumSamples(); + + currentAudioInputBuffer = &buffer; + currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); + currentAudioOutputBuffer.clear(); + currentMidiInputBuffer = &midiMessages; + currentMidiOutputBuffer.clear(); + + for (int i = 0; i < renderingOps.size(); ++i) + { + GraphRenderingOps::AudioGraphRenderingOpBase* const op + = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); + + op->perform (renderingBuffers, midiBuffers, numSamples); + } + + for (int i = 0; i < buffer.getNumChannels(); ++i) + buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); + + midiMessages.clear(); + midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); +} + +double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } +bool AudioProcessorGraph::acceptsMidi() const { return true; } +bool AudioProcessorGraph::producesMidi() const { return true; } + +void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +//============================================================================== +AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) + : type (deviceType), graph (nullptr) +{ +} + +AudioProcessorGraph::AudioGraphIOProcessor::~AudioGraphIOProcessor() +{ +} + +const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const +{ + switch (type) + { + case audioOutputNode: return "Audio Output"; + case audioInputNode: return "Audio Input"; + case midiOutputNode: return "Midi Output"; + case midiInputNode: return "Midi Input"; + default: break; + } + + return String(); +} + +#if 0 +void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (PluginDescription& d) const +{ + d.name = getName(); + d.uid = d.name.hashCode(); + d.category = "I/O devices"; + d.pluginFormatName = "Internal"; + d.manufacturerName = "ROLI Ltd."; + d.version = "1.0"; + d.isInstrument = false; + + d.numInputChannels = getTotalNumInputChannels(); + if (type == audioOutputNode && graph != nullptr) + d.numInputChannels = graph->getTotalNumInputChannels(); + + d.numOutputChannels = getTotalNumOutputChannels(); + if (type == audioInputNode && graph != nullptr) + d.numOutputChannels = graph->getTotalNumOutputChannels(); +} +#endif + +void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) +{ + jassert (graph != nullptr); +} + +void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() +{ +} + +void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioSampleBuffer& buffer, + MidiBuffer& midiMessages) +{ + AudioSampleBuffer*& currentAudioInputBuffer = + graph->audioBuffers->currentAudioInputBuffer; + + AudioSampleBuffer& currentAudioOutputBuffer = + graph->audioBuffers->currentAudioOutputBuffer; + + jassert (graph != nullptr); + + switch (type) + { + case audioOutputNode: + { + for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), + buffer.getNumChannels()); --i >= 0;) + { + currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); + } + + break; + } + + case audioInputNode: + { + for (int i = jmin (currentAudioInputBuffer->getNumChannels(), + buffer.getNumChannels()); --i >= 0;) + { + buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); + } + + break; + } + + case midiOutputNode: + graph->currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); + break; + + case midiInputNode: + midiMessages.addEvents (*graph->currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); + break; + + default: + break; + } +} + +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const +{ + return 0; +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::acceptsMidi() const +{ + return type == midiOutputNode; +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const +{ + return type == midiInputNode; +} + +bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; } +bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; } + +void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorGraph* const newGraph) +{ + graph = newGraph; + + if (graph != nullptr) + { + setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, + type == audioInputNode ? graph->getTotalNumInputChannels() : 0, + getSampleRate(), + getBlockSize()); + } +} diff --git a/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h b/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h new file mode 100644 index 000000000..faca741c0 --- /dev/null +++ b/source/modules/juce_audio_graph/processors/juce_AudioProcessorGraph.h @@ -0,0 +1,387 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2015 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED +#define JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED + +#include "juce_AudioProcessor.h" + +//============================================================================== +/** + A type of AudioProcessor which plays back a graph of other AudioProcessors. + + Use one of these objects if you want to wire-up a set of AudioProcessors + and play back the result. + + Processors can be added to the graph as "nodes" using addNode(), and once + added, you can connect any of their input or output channels to other + nodes using addConnection(). + + To play back a graph through an audio device, you might want to use an + AudioProcessorPlayer object. +*/ +class JUCE_API AudioProcessorGraph : public AudioProcessor + /* private AsyncUpdater*/ +{ +public: + //============================================================================== + /** Creates an empty graph. */ + AudioProcessorGraph(); + + /** Destructor. + Any processor objects that have been added to the graph will also be deleted. + */ + ~AudioProcessorGraph(); + + //============================================================================== + /** Represents one of the nodes, or processors, in an AudioProcessorGraph. + + To create a node, call AudioProcessorGraph::addNode(). + */ + class JUCE_API Node /*: public ReferenceCountedObject*/ + { + public: + //============================================================================== + /** The ID number assigned to this node. + This is assigned by the graph that owns it, and can't be changed. + */ + const uint32 nodeId; + + /** The actual processor object that this node represents. */ + AudioProcessor* getProcessor() const noexcept { return processor; } + +#if 0 + /** A set of user-definable properties that are associated with this node. + + This can be used to attach values to the node for whatever purpose seems + useful. For example, you might store an x and y position if your application + is displaying the nodes on-screen. + */ + NamedValueSet properties; + + //============================================================================== + /** A convenient typedef for referring to a pointer to a node object. */ + typedef ReferenceCountedObjectPtr Ptr; +#endif + + private: + //============================================================================== + friend class AudioProcessorGraph; + + const ScopedPointer processor; + bool isPrepared; + + Node (uint32 nodeId, AudioProcessor*) noexcept; + + void setParentGraph (AudioProcessorGraph*) const; + void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*); + void unprepare(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) + }; + + //============================================================================== + /** Represents a connection between two channels of two nodes in an AudioProcessorGraph. + + To create a connection, use AudioProcessorGraph::addConnection(). + */ + struct JUCE_API Connection + { + public: + //============================================================================== + Connection (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex) noexcept; + + //============================================================================== + /** The ID number of the node which is the input source for this connection. + @see AudioProcessorGraph::getNodeForId + */ + uint32 sourceNodeId; + + /** The index of the output channel of the source node from which this + connection takes its data. + + If this value is the special number AudioProcessorGraph::midiChannelIndex, then + it is referring to the source node's midi output. Otherwise, it is the zero-based + index of an audio output channel in the source node. + */ + int sourceChannelIndex; + + /** The ID number of the node which is the destination for this connection. + @see AudioProcessorGraph::getNodeForId + */ + uint32 destNodeId; + + /** The index of the input channel of the destination node to which this + connection delivers its data. + + If this value is the special number AudioProcessorGraph::midiChannelIndex, then + it is referring to the destination node's midi input. Otherwise, it is the zero-based + index of an audio input channel in the destination node. + */ + int destChannelIndex; + + private: + //============================================================================== + JUCE_LEAK_DETECTOR (Connection) + }; + + //============================================================================== + /** Deletes all nodes and connections from this graph. + Any processor objects in the graph will be deleted. + */ + void clear(); + +#if 0 + /** Returns the number of nodes in the graph. */ + int getNumNodes() const noexcept { return nodes.size(); } + + /** Returns a pointer to one of the nodes in the graph. + This will return nullptr if the index is out of range. + @see getNodeForId + */ + Node* getNode (const int index) const noexcept { return nodes [index]; } + + /** Searches the graph for a node with the given ID number and returns it. + If no such node was found, this returns nullptr. + @see getNode + */ + Node* getNodeForId (const uint32 nodeId) const; + + /** Adds a node to the graph. + + This creates a new node in the graph, for the specified processor. Once you have + added a processor to the graph, the graph owns it and will delete it later when + it is no longer needed. + + The optional nodeId parameter lets you specify an ID to use for the node, but + if the value is already in use, this new node will overwrite the old one. + + If this succeeds, it returns a pointer to the newly-created node. + */ + Node* addNode (AudioProcessor* newProcessor, uint32 nodeId = 0); + + /** Deletes a node within the graph which has the specified ID. + + This will also delete any connections that are attached to this node. + */ + bool removeNode (uint32 nodeId); + + /** Deletes a node within the graph which has the specified ID. + + This will also delete any connections that are attached to this node. + */ + bool removeNode (Node* node); +#endif + + //============================================================================== + /** Returns the number of connections in the graph. */ + int getNumConnections() const { return connections.size(); } + + /** Returns a pointer to one of the connections in the graph. */ + const Connection* getConnection (int index) const { return connections [index]; } + + /** Searches for a connection between some specified channels. + If no such connection is found, this returns nullptr. + */ + const Connection* getConnectionBetween (uint32 sourceNodeId, + int sourceChannelIndex, + uint32 destNodeId, + int destChannelIndex) const; + + /** Returns true if there is a connection between any of the channels of + two specified nodes. + */ + bool isConnected (uint32 possibleSourceNodeId, + uint32 possibleDestNodeId) const; + + /** Returns true if it would be legal to connect the specified points. */ + bool canConnect (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex) const; + + /** Attempts to connect two specified channels of two nodes. + + If this isn't allowed (e.g. because you're trying to connect a midi channel + to an audio one or other such nonsense), then it'll return false. + */ + bool addConnection (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex); + + /** Deletes the connection with the specified index. */ + void removeConnection (int index); + + /** Deletes any connection between two specified points. + Returns true if a connection was actually deleted. + */ + bool removeConnection (uint32 sourceNodeId, int sourceChannelIndex, + uint32 destNodeId, int destChannelIndex); + + /** Removes all connections from the specified node. */ + bool disconnectNode (uint32 nodeId); + + /** Returns true if the given connection's channel numbers map on to valid + channels at each end. + Even if a connection is valid when created, its status could change if + a node changes its channel config. + */ + bool isConnectionLegal (const Connection* connection) const; + + /** Performs a sanity checks of all the connections. + + This might be useful if some of the processors are doing things like changing + their channel counts, which could render some connections obsolete. + */ + bool removeIllegalConnections(); + + //============================================================================== + /** A special number that represents the midi channel of a node. + + This is used as a channel index value if you want to refer to the midi input + or output instead of an audio channel. + */ + static const int midiChannelIndex; + + + //============================================================================== + /** A special type of AudioProcessor that can live inside an AudioProcessorGraph + in order to use the audio that comes into and out of the graph itself. + + If you create an AudioGraphIOProcessor in "input" mode, it will act as a + node in the graph which delivers the audio that is coming into the parent + graph. This allows you to stream the data to other nodes and process the + incoming audio. + + Likewise, one of these in "output" mode can be sent data which it will add to + the sum of data being sent to the graph's output. + + @see AudioProcessorGraph + */ + class JUCE_API AudioGraphIOProcessor : public AudioProcessor + { + public: + /** Specifies the mode in which this processor will operate. + */ + enum IODeviceType + { + audioInputNode, /**< In this mode, the processor has output channels + representing all the audio input channels that are + coming into its parent audio graph. */ + audioOutputNode, /**< In this mode, the processor has input channels + representing all the audio output channels that are + going out of its parent audio graph. */ + midiInputNode, /**< In this mode, the processor has a midi output which + delivers the same midi data that is arriving at its + parent graph. */ + midiOutputNode /**< In this mode, the processor has a midi input and + any data sent to it will be passed out of the parent + graph. */ + }; + + //============================================================================== + /** Returns the mode of this processor. */ + IODeviceType getType() const noexcept { return type; } + + /** Returns the parent graph to which this processor belongs, or nullptr if it + hasn't yet been added to one. */ + AudioProcessorGraph* getParentGraph() const noexcept { return graph; } + + /** True if this is an audio or midi input. */ + bool isInput() const noexcept; + /** True if this is an audio or midi output. */ + bool isOutput() const noexcept; + + //============================================================================== + AudioGraphIOProcessor (const IODeviceType type); + ~AudioGraphIOProcessor(); + + const String getName() const override; +#if 0 + void fillInPluginDescription (PluginDescription&) const override; +#endif + void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; + void releaseResources() override; + void processBlock (AudioSampleBuffer&, MidiBuffer&) override; + + double getTailLengthSeconds() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + + /** @internal */ + void setParentGraph (AudioProcessorGraph*); + + private: + const IODeviceType type; + AudioProcessorGraph* graph; + + //============================================================================== + void processAudio (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) + }; + + //============================================================================== + const String getName() const override; + void prepareToPlay (double, int) override; + void releaseResources() override; + void processBlock (AudioSampleBuffer&, MidiBuffer&) override; + + void reset() override; + void setNonRealtime (bool) noexcept override; + void setPlayHead (AudioPlayHead*) override; + + double getTailLengthSeconds() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + +private: + //============================================================================== + void processAudio (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); + + //============================================================================== +#if 0 + ReferenceCountedArray nodes; +#endif + OwnedArray connections; + uint32 lastNodeId; + OwnedArray midiBuffers; + Array renderingOps; + + friend class AudioGraphIOProcessor; + struct AudioProcessorGraphBufferHelpers; + ScopedPointer audioBuffers; + + MidiBuffer* currentMidiInputBuffer; + MidiBuffer currentMidiOutputBuffer; + + bool isPrepared; + + void clearRenderingSequence(); + void buildRenderingSequence(); + bool isAnInputTo (uint32 possibleInputId, uint32 possibleDestinationId, int recursionCheck) const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorGraph) +}; + + +#endif // JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED diff --git a/source/modules/juce_audio_graph/streams/juce_InputStream.cpp b/source/modules/juce_audio_graph/streams/juce_InputStream.cpp new file mode 100644 index 000000000..f0b8390c3 --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_InputStream.cpp @@ -0,0 +1,238 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +int64 InputStream::getNumBytesRemaining() +{ + int64 len = getTotalLength(); + + if (len >= 0) + len -= getPosition(); + + return len; +} + +char InputStream::readByte() +{ + char temp = 0; + read (&temp, 1); + return temp; +} + +bool InputStream::readBool() +{ + return readByte() != 0; +} + +short InputStream::readShort() +{ + char temp[2]; + + if (read (temp, 2) == 2) + return (short) ByteOrder::littleEndianShort (temp); + + return 0; +} + +short InputStream::readShortBigEndian() +{ + char temp[2]; + + if (read (temp, 2) == 2) + return (short) ByteOrder::bigEndianShort (temp); + + return 0; +} + +int InputStream::readInt() +{ + char temp[4]; + + if (read (temp, 4) == 4) + return (int) ByteOrder::littleEndianInt (temp); + + return 0; +} + +int InputStream::readIntBigEndian() +{ + char temp[4]; + + if (read (temp, 4) == 4) + return (int) ByteOrder::bigEndianInt (temp); + + return 0; +} + +int InputStream::readCompressedInt() +{ + const uint8 sizeByte = (uint8) readByte(); + if (sizeByte == 0) + return 0; + + const int numBytes = (sizeByte & 0x7f); + if (numBytes > 4) + { + jassertfalse; // trying to read corrupt data - this method must only be used + // to read data that was written by OutputStream::writeCompressedInt() + return 0; + } + + char bytes[4] = { 0, 0, 0, 0 }; + if (read (bytes, numBytes) != numBytes) + return 0; + + const int num = (int) ByteOrder::littleEndianInt (bytes); + return (sizeByte >> 7) ? -num : num; +} + +int64 InputStream::readInt64() +{ + union { uint8 asBytes[8]; uint64 asInt64; } n; + + if (read (n.asBytes, 8) == 8) + return (int64) ByteOrder::swapIfBigEndian (n.asInt64); + + return 0; +} + +int64 InputStream::readInt64BigEndian() +{ + union { uint8 asBytes[8]; uint64 asInt64; } n; + + if (read (n.asBytes, 8) == 8) + return (int64) ByteOrder::swapIfLittleEndian (n.asInt64); + + return 0; +} + +float InputStream::readFloat() +{ + // the union below relies on these types being the same size... + static_jassert (sizeof (int32) == sizeof (float)); + union { int32 asInt; float asFloat; } n; + n.asInt = (int32) readInt(); + return n.asFloat; +} + +float InputStream::readFloatBigEndian() +{ + union { int32 asInt; float asFloat; } n; + n.asInt = (int32) readIntBigEndian(); + return n.asFloat; +} + +double InputStream::readDouble() +{ + union { int64 asInt; double asDouble; } n; + n.asInt = readInt64(); + return n.asDouble; +} + +double InputStream::readDoubleBigEndian() +{ + union { int64 asInt; double asDouble; } n; + n.asInt = readInt64BigEndian(); + return n.asDouble; +} + +String InputStream::readString() +{ + MemoryBlock buffer (256); + char* data = static_cast (buffer.getData()); + size_t i = 0; + + while ((data[i] = readByte()) != 0) + { + if (++i >= buffer.getSize()) + { + buffer.setSize (buffer.getSize() + 512); + data = static_cast (buffer.getData()); + } + } + + return String::fromUTF8 (data, (int) i); +} + +String InputStream::readNextLine() +{ + MemoryBlock buffer (256); + char* data = static_cast (buffer.getData()); + size_t i = 0; + + while ((data[i] = readByte()) != 0) + { + if (data[i] == '\n') + break; + + if (data[i] == '\r') + { + const int64 lastPos = getPosition(); + + if (readByte() != '\n') + setPosition (lastPos); + + break; + } + + if (++i >= buffer.getSize()) + { + buffer.setSize (buffer.getSize() + 512); + data = static_cast (buffer.getData()); + } + } + + return String::fromUTF8 (data, (int) i); +} + +size_t InputStream::readIntoMemoryBlock (MemoryBlock& block, ssize_t numBytes) +{ + MemoryOutputStream mo (block, true); + return (size_t) mo.writeFromInputStream (*this, numBytes); +} + +String InputStream::readEntireStreamAsString() +{ + MemoryOutputStream mo; + mo << *this; + return mo.toString(); +} + +//============================================================================== +void InputStream::skipNextBytes (int64 numBytesToSkip) +{ + if (numBytesToSkip > 0) + { + const int skipBufferSize = (int) jmin (numBytesToSkip, (int64) 16384); + HeapBlock temp ((size_t) skipBufferSize); + + while (numBytesToSkip > 0 && ! isExhausted()) + numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize)); + } +} diff --git a/source/modules/juce_audio_graph/streams/juce_InputStream.h b/source/modules/juce_audio_graph/streams/juce_InputStream.h new file mode 100644 index 000000000..fcce077e9 --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_InputStream.h @@ -0,0 +1,268 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_INPUTSTREAM_H_INCLUDED +#define JUCE_INPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** The base class for streams that read data. + + Input and output streams are used throughout the library - subclasses can override + some or all of the virtual functions to implement their behaviour. + + @see OutputStream, MemoryInputStream, BufferedInputStream, FileInputStream +*/ +class JUCE_API InputStream +{ +public: + /** Destructor. */ + virtual ~InputStream() {} + + //============================================================================== + /** Returns the total number of bytes available for reading in this stream. + + Note that this is the number of bytes available from the start of the + stream, not from the current position. + + If the size of the stream isn't actually known, this will return -1. + + @see getNumBytesRemaining + */ + virtual int64 getTotalLength() = 0; + + /** Returns the number of bytes available for reading, or a negative value if + the remaining length is not known. + @see getTotalLength + */ + int64 getNumBytesRemaining(); + + /** Returns true if the stream has no more data to read. */ + virtual bool isExhausted() = 0; + + //============================================================================== + /** Reads some data from the stream into a memory buffer. + + This is the only read method that subclasses actually need to implement, as the + InputStream base class implements the other read methods in terms of this one (although + it's often more efficient for subclasses to implement them directly). + + @param destBuffer the destination buffer for the data. This must not be null. + @param maxBytesToRead the maximum number of bytes to read - make sure the + memory block passed in is big enough to contain this + many bytes. This value must not be negative. + + @returns the actual number of bytes that were read, which may be less than + maxBytesToRead if the stream is exhausted before it gets that far + */ + virtual int read (void* destBuffer, int maxBytesToRead) = 0; + + /** Reads a byte from the stream. + If the stream is exhausted, this will return zero. + @see OutputStream::writeByte + */ + virtual char readByte(); + + /** Reads a boolean from the stream. + The bool is encoded as a single byte - non-zero for true, 0 for false. + If the stream is exhausted, this will return false. + @see OutputStream::writeBool + */ + virtual bool readBool(); + + /** Reads two bytes from the stream as a little-endian 16-bit value. + If the next two bytes read are byte1 and byte2, this returns (byte1 | (byte2 << 8)). + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeShort, readShortBigEndian + */ + virtual short readShort(); + + /** Reads two bytes from the stream as a little-endian 16-bit value. + If the next two bytes read are byte1 and byte2, this returns (byte2 | (byte1 << 8)). + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeShortBigEndian, readShort + */ + virtual short readShortBigEndian(); + + /** Reads four bytes from the stream as a little-endian 32-bit value. + + If the next four bytes are byte1 to byte4, this returns + (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt, readIntBigEndian + */ + virtual int readInt(); + + /** Reads four bytes from the stream as a big-endian 32-bit value. + + If the next four bytes are byte1 to byte4, this returns + (byte4 | (byte3 << 8) | (byte2 << 16) | (byte1 << 24)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeIntBigEndian, readInt + */ + virtual int readIntBigEndian(); + + /** Reads eight bytes from the stream as a little-endian 64-bit value. + + If the next eight bytes are byte1 to byte8, this returns + (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24) | (byte5 << 32) | (byte6 << 40) | (byte7 << 48) | (byte8 << 56)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt64, readInt64BigEndian + */ + virtual int64 readInt64(); + + /** Reads eight bytes from the stream as a big-endian 64-bit value. + + If the next eight bytes are byte1 to byte8, this returns + (byte8 | (byte7 << 8) | (byte6 << 16) | (byte5 << 24) | (byte4 << 32) | (byte3 << 40) | (byte2 << 48) | (byte1 << 56)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt64BigEndian, readInt64 + */ + virtual int64 readInt64BigEndian(); + + /** Reads four bytes as a 32-bit floating point value. + The raw 32-bit encoding of the float is read from the stream as a little-endian int. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeFloat, readDouble + */ + virtual float readFloat(); + + /** Reads four bytes as a 32-bit floating point value. + The raw 32-bit encoding of the float is read from the stream as a big-endian int. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeFloatBigEndian, readDoubleBigEndian + */ + virtual float readFloatBigEndian(); + + /** Reads eight bytes as a 64-bit floating point value. + The raw 64-bit encoding of the double is read from the stream as a little-endian int64. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeDouble, readFloat + */ + virtual double readDouble(); + + /** Reads eight bytes as a 64-bit floating point value. + The raw 64-bit encoding of the double is read from the stream as a big-endian int64. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeDoubleBigEndian, readFloatBigEndian + */ + virtual double readDoubleBigEndian(); + + /** Reads an encoded 32-bit number from the stream using a space-saving compressed format. + For small values, this is more space-efficient than using readInt() and OutputStream::writeInt() + The format used is: number of significant bytes + up to 4 bytes in little-endian order. + @see OutputStream::writeCompressedInt() + */ + virtual int readCompressedInt(); + + //============================================================================== + /** Reads a UTF-8 string from the stream, up to the next linefeed or carriage return. + + This will read up to the next "\n" or "\r\n" or end-of-stream. + + After this call, the stream's position will be left pointing to the next character + following the line-feed, but the linefeeds aren't included in the string that + is returned. + */ + virtual String readNextLine(); + + /** Reads a zero-terminated UTF-8 string from the stream. + + This will read characters from the stream until it hits a null character + or end-of-stream. + + @see OutputStream::writeString, readEntireStreamAsString + */ + virtual String readString(); + + /** Tries to read the whole stream and turn it into a string. + + This will read from the stream's current position until the end-of-stream. + It can read from UTF-8 data, or UTF-16 if it detects suitable header-bytes. + */ + virtual String readEntireStreamAsString(); + + /** Reads from the stream and appends the data to a MemoryBlock. + + @param destBlock the block to append the data onto + @param maxNumBytesToRead if this is a positive value, it sets a limit to the number + of bytes that will be read - if it's negative, data + will be read until the stream is exhausted. + @returns the number of bytes that were added to the memory block + */ + virtual size_t readIntoMemoryBlock (MemoryBlock& destBlock, + ssize_t maxNumBytesToRead = -1); + + //============================================================================== + /** Returns the offset of the next byte that will be read from the stream. + @see setPosition + */ + virtual int64 getPosition() = 0; + + /** Tries to move the current read position of the stream. + + The position is an absolute number of bytes from the stream's start. + + Some streams might not be able to do this, in which case they should do + nothing and return false. Others might be able to manage it by resetting + themselves and skipping to the correct position, although this is + obviously a bit slow. + + @returns true if the stream manages to reposition itself correctly + @see getPosition + */ + virtual bool setPosition (int64 newPosition) = 0; + + /** Reads and discards a number of bytes from the stream. + + Some input streams might implement this efficiently, but the base + class will just keep reading data until the requisite number of bytes + have been done. + */ + virtual void skipNextBytes (int64 numBytesToSkip); + + +protected: + //============================================================================== + InputStream() noexcept {} + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputStream) +}; + +#endif // JUCE_INPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.cpp b/source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.cpp new file mode 100644 index 000000000..cb1b47a34 --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.cpp @@ -0,0 +1,216 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +MemoryOutputStream::MemoryOutputStream (const size_t initialSize) + : blockToUse (&internalBlock), externalData (nullptr), + position (0), size (0), availableSize (0) +{ + internalBlock.setSize (initialSize, false); +} + +MemoryOutputStream::MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + const bool appendToExistingBlockContent) + : blockToUse (&memoryBlockToWriteTo), externalData (nullptr), + position (0), size (0), availableSize (0) +{ + if (appendToExistingBlockContent) + position = size = memoryBlockToWriteTo.getSize(); +} + +MemoryOutputStream::MemoryOutputStream (void* destBuffer, size_t destBufferSize) + : blockToUse (nullptr), externalData (destBuffer), + position (0), size (0), availableSize (destBufferSize) +{ + jassert (externalData != nullptr); // This must be a valid pointer. +} + +MemoryOutputStream::~MemoryOutputStream() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::flush() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::trimExternalBlockSize() +{ + if (blockToUse != &internalBlock && blockToUse != nullptr) + blockToUse->setSize (size, false); +} + +void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) +{ + if (blockToUse != nullptr) + blockToUse->ensureSize (bytesToPreallocate + 1); +} + +void MemoryOutputStream::reset() noexcept +{ + position = 0; + size = 0; +} + +char* MemoryOutputStream::prepareToWrite (size_t numBytes) +{ + jassert ((ssize_t) numBytes >= 0); + size_t storageNeeded = position + numBytes; + + char* data; + + if (blockToUse != nullptr) + { + if (storageNeeded >= blockToUse->getSize()) + blockToUse->ensureSize ((storageNeeded + jmin (storageNeeded / 2, (size_t) (1024 * 1024)) + 32) & ~31u); + + data = static_cast (blockToUse->getData()); + } + else + { + if (storageNeeded > availableSize) + return nullptr; + + data = static_cast (externalData); + } + + char* const writePointer = data + position; + position += numBytes; + size = jmax (size, position); + return writePointer; +} + +bool MemoryOutputStream::write (const void* const buffer, size_t howMany) +{ + jassert (buffer != nullptr); + + if (howMany == 0) + return true; + + if (char* dest = prepareToWrite (howMany)) + { + memcpy (dest, buffer, howMany); + return true; + } + + return false; +} + +bool MemoryOutputStream::writeRepeatedByte (uint8 byte, size_t howMany) +{ + if (howMany == 0) + return true; + + if (char* dest = prepareToWrite (howMany)) + { + memset (dest, byte, howMany); + return true; + } + + return false; +} + +bool MemoryOutputStream::appendUTF8Char (juce_wchar c) +{ + if (char* dest = prepareToWrite (CharPointer_UTF8::getBytesRequiredFor (c))) + { + CharPointer_UTF8 (dest).write (c); + return true; + } + + return false; +} + +MemoryBlock MemoryOutputStream::getMemoryBlock() const +{ + return MemoryBlock (getData(), getDataSize()); +} + +const void* MemoryOutputStream::getData() const noexcept +{ + if (blockToUse == nullptr) + return externalData; + + if (blockToUse->getSize() > size) + static_cast (blockToUse->getData()) [size] = 0; + + return blockToUse->getData(); +} + +bool MemoryOutputStream::setPosition (int64 newPosition) +{ + if (newPosition <= (int64) size) + { + // ok to seek backwards + position = jlimit ((size_t) 0, size, (size_t) newPosition); + return true; + } + + // can't move beyond the end of the stream.. + return false; +} + +int64 MemoryOutputStream::writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite) +{ + // before writing from an input, see if we can preallocate to make it more efficient.. + int64 availableData = source.getTotalLength() - source.getPosition(); + + if (availableData > 0) + { + if (maxNumBytesToWrite > availableData || maxNumBytesToWrite < 0) + maxNumBytesToWrite = availableData; + + if (blockToUse != nullptr) + preallocate (blockToUse->getSize() + (size_t) maxNumBytesToWrite); + } + + return OutputStream::writeFromInputStream (source, maxNumBytesToWrite); +} + +String MemoryOutputStream::toUTF8() const +{ + const char* const d = static_cast (getData()); + return String (CharPointer_UTF8 (d), CharPointer_UTF8 (d + getDataSize())); +} + +String MemoryOutputStream::toString() const +{ + return String::createStringFromData (getData(), (int) getDataSize()); +} + +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead) +{ + const size_t dataSize = streamToRead.getDataSize(); + + if (dataSize > 0) + stream.write (streamToRead.getData(), dataSize); + + return stream; +} diff --git a/source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.h b/source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.h new file mode 100644 index 000000000..595d6035e --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_MemoryOutputStream.h @@ -0,0 +1,141 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED +#define JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + Writes data to an internal memory buffer, which grows as required. + + The data that was written into the stream can then be accessed later as + a contiguous block of memory. +*/ +class JUCE_API MemoryOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates an empty memory stream, ready to be written into. + @param initialSize the intial amount of capacity to allocate for writing into + */ + MemoryOutputStream (size_t initialSize = 256); + + /** Creates a memory stream for writing into into a pre-existing MemoryBlock object. + + Note that the destination block will always be larger than the amount of data + that has been written to the stream, because the MemoryOutputStream keeps some + spare capactity at its end. To trim the block's size down to fit the actual + data, call flush(), or delete the MemoryOutputStream. + + @param memoryBlockToWriteTo the block into which new data will be written. + @param appendToExistingBlockContent if this is true, the contents of the block will be + kept, and new data will be appended to it. If false, + the block will be cleared before use + */ + MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + bool appendToExistingBlockContent); + + /** Creates a MemoryOutputStream that will write into a user-supplied, fixed-size + block of memory. + When using this mode, the stream will write directly into this memory area until + it's full, at which point write operations will fail. + */ + MemoryOutputStream (void* destBuffer, size_t destBufferSize); + + /** Destructor. + This will free any data that was written to it. + */ + ~MemoryOutputStream(); + + //============================================================================== + /** Returns a pointer to the data that has been written to the stream. + @see getDataSize + */ + const void* getData() const noexcept; + + /** Returns the number of bytes of data that have been written to the stream. + @see getData + */ + size_t getDataSize() const noexcept { return size; } + + /** Resets the stream, clearing any data that has been written to it so far. */ + void reset() noexcept; + + /** Increases the internal storage capacity to be able to contain at least the specified + amount of data without needing to be resized. + */ + void preallocate (size_t bytesToPreallocate); + + /** Appends the utf-8 bytes for a unicode character */ + bool appendUTF8Char (juce_wchar character); + + /** Returns a String created from the (UTF8) data that has been written to the stream. */ + String toUTF8() const; + + /** Attempts to detect the encoding of the data and convert it to a string. + @see String::createStringFromData + */ + String toString() const; + + /** Returns a copy of the stream's data as a memory block. */ + MemoryBlock getMemoryBlock() const; + + //============================================================================== + /** If the stream is writing to a user-supplied MemoryBlock, this will trim any excess + capacity off the block, so that its length matches the amount of actual data that + has been written so far. + */ + void flush() override; + + bool write (const void*, size_t) override; + int64 getPosition() override { return (int64) position; } + bool setPosition (int64) override; + int64 writeFromInputStream (InputStream&, int64 maxNumBytesToWrite) override; + bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) override; + +private: + //============================================================================== + MemoryBlock* const blockToUse; + MemoryBlock internalBlock; + void* externalData; + size_t position, size, availableSize; + + void trimExternalBlockSize(); + char* prepareToWrite (size_t); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryOutputStream) +}; + +/** Copies all the data that has been written to a MemoryOutputStream into another stream. */ +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead); + + +#endif // JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_audio_graph/streams/juce_OutputStream.cpp b/source/modules/juce_audio_graph/streams/juce_OutputStream.cpp new file mode 100644 index 000000000..7db6532fe --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_OutputStream.cpp @@ -0,0 +1,353 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_DEBUG + +struct DanglingStreamChecker +{ + DanglingStreamChecker() {} + + ~DanglingStreamChecker() + { + /* + It's always a bad idea to leak any object, but if you're leaking output + streams, then there's a good chance that you're failing to flush a file + to disk properly, which could result in corrupted data and other similar + nastiness.. + */ + jassert (activeStreams.size() == 0); + } + + Array activeStreams; +}; + +static DanglingStreamChecker danglingStreamChecker; +#endif + +//============================================================================== +OutputStream::OutputStream() + : newLineString (NewLine::getDefault()) +{ + #if JUCE_DEBUG + danglingStreamChecker.activeStreams.add (this); + #endif +} + +OutputStream::~OutputStream() +{ + #if JUCE_DEBUG + danglingStreamChecker.activeStreams.removeFirstMatchingValue (this); + #endif +} + +//============================================================================== +bool OutputStream::writeBool (const bool b) +{ + return writeByte (b ? (char) 1 + : (char) 0); +} + +bool OutputStream::writeByte (char byte) +{ + return write (&byte, 1); +} + +bool OutputStream::writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) +{ + for (size_t i = 0; i < numTimesToRepeat; ++i) + if (! writeByte ((char) byte)) + return false; + + return true; +} + +bool OutputStream::writeShort (short value) +{ + const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); + return write (&v, 2); +} + +bool OutputStream::writeShortBigEndian (short value) +{ + const unsigned short v = ByteOrder::swapIfLittleEndian ((unsigned short) value); + return write (&v, 2); +} + +bool OutputStream::writeInt (int value) +{ + const unsigned int v = ByteOrder::swapIfBigEndian ((unsigned int) value); + return write (&v, 4); +} + +bool OutputStream::writeIntBigEndian (int value) +{ + const unsigned int v = ByteOrder::swapIfLittleEndian ((unsigned int) value); + return write (&v, 4); +} + +bool OutputStream::writeCompressedInt (int value) +{ + unsigned int un = (value < 0) ? (unsigned int) -value + : (unsigned int) value; + + uint8 data[5]; + int num = 0; + + while (un > 0) + { + data[++num] = (uint8) un; + un >>= 8; + } + + data[0] = (uint8) num; + + if (value < 0) + data[0] |= 0x80; + + return write (data, (size_t) num + 1); +} + +bool OutputStream::writeInt64 (int64 value) +{ + const uint64 v = ByteOrder::swapIfBigEndian ((uint64) value); + return write (&v, 8); +} + +bool OutputStream::writeInt64BigEndian (int64 value) +{ + const uint64 v = ByteOrder::swapIfLittleEndian ((uint64) value); + return write (&v, 8); +} + +bool OutputStream::writeFloat (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + return writeInt (n.asInt); +} + +bool OutputStream::writeFloatBigEndian (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + return writeIntBigEndian (n.asInt); +} + +bool OutputStream::writeDouble (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + return writeInt64 (n.asInt); +} + +bool OutputStream::writeDoubleBigEndian (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + return writeInt64BigEndian (n.asInt); +} + +bool OutputStream::writeString (const String& text) +{ + #if (JUCE_STRING_UTF_TYPE == 8) + return write (text.toRawUTF8(), text.getNumBytesAsUTF8() + 1); + #else + // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind + // if lots of large, persistent strings were to be written to streams). + const size_t numBytes = text.getNumBytesAsUTF8() + 1; + HeapBlock temp (numBytes); + text.copyToUTF8 (temp, numBytes); + return write (temp, numBytes); + #endif +} + +bool OutputStream::writeText (const String& text, const bool asUTF16, + const bool writeUTF16ByteOrderMark) +{ + if (asUTF16) + { + if (writeUTF16ByteOrderMark) + write ("\x0ff\x0fe", 2); + + String::CharPointerType src (text.getCharPointer()); + bool lastCharWasReturn = false; + + for (;;) + { + const juce_wchar c = src.getAndAdvance(); + + if (c == 0) + break; + + if (c == '\n' && ! lastCharWasReturn) + writeShort ((short) '\r'); + + lastCharWasReturn = (c == L'\r'); + + if (! writeShort ((short) c)) + return false; + } + } + else + { + const char* src = text.toUTF8(); + const char* t = src; + + for (;;) + { + if (*t == '\n') + { + if (t > src) + if (! write (src, (size_t) (t - src))) + return false; + + if (! write ("\r\n", 2)) + return false; + + src = t + 1; + } + else if (*t == '\r') + { + if (t[1] == '\n') + ++t; + } + else if (*t == 0) + { + if (t > src) + if (! write (src, (size_t) (t - src))) + return false; + + break; + } + + ++t; + } + } + + return true; +} + +int64 OutputStream::writeFromInputStream (InputStream& source, int64 numBytesToWrite) +{ + if (numBytesToWrite < 0) + numBytesToWrite = std::numeric_limits::max(); + + int64 numWritten = 0; + + while (numBytesToWrite > 0) + { + char buffer [8192]; + const int num = source.read (buffer, (int) jmin (numBytesToWrite, (int64) sizeof (buffer))); + + if (num <= 0) + break; + + write (buffer, (size_t) num); + + numBytesToWrite -= num; + numWritten += num; + } + + return numWritten; +} + +//============================================================================== +void OutputStream::setNewLineString (const String& newLineString_) +{ + newLineString = newLineString_; +} + +//============================================================================== +template +static void writeIntToStream (OutputStream& stream, IntegerType number) +{ + char buffer [NumberToStringConverters::charsNeededForInt]; + char* end = buffer + numElementsInArray (buffer); + const char* start = NumberToStringConverters::numberToString (end, number); + stream.write (start, (size_t) (end - start - 1)); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int number) +{ + writeIntToStream (stream, number); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int64 number) +{ + writeIntToStream (stream, number); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const double number) +{ + return stream << String (number); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char character) +{ + stream.writeByte (character); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* const text) +{ + stream.write (text, strlen (text)); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data) +{ + if (data.getSize() > 0) + stream.write (data.getData(), data.getSize()); + + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) +{ + FileInputStream in (fileToRead); + + if (in.openedOk()) + return stream << in; + + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead) +{ + stream.writeFromInputStream (streamToRead, -1); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&) +{ + return stream << stream.getNewLineString(); +} diff --git a/source/modules/juce_audio_graph/streams/juce_OutputStream.h b/source/modules/juce_audio_graph/streams/juce_OutputStream.h new file mode 100644 index 000000000..bfcb6d0f3 --- /dev/null +++ b/source/modules/juce_audio_graph/streams/juce_OutputStream.h @@ -0,0 +1,279 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_OUTPUTSTREAM_H_INCLUDED +#define JUCE_OUTPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + The base class for streams that write data to some kind of destination. + + Input and output streams are used throughout the library - subclasses can override + some or all of the virtual functions to implement their behaviour. + + @see InputStream, MemoryOutputStream, FileOutputStream +*/ +class JUCE_API OutputStream +{ +protected: + //============================================================================== + OutputStream(); + +public: + /** Destructor. + + Some subclasses might want to do things like call flush() during their + destructors. + */ + virtual ~OutputStream(); + + //============================================================================== + /** If the stream is using a buffer, this will ensure it gets written + out to the destination. */ + virtual void flush() = 0; + + /** Tries to move the stream's output position. + + Not all streams will be able to seek to a new position - this will return + false if it fails to work. + + @see getPosition + */ + virtual bool setPosition (int64 newPosition) = 0; + + /** Returns the stream's current position. + + @see setPosition + */ + virtual int64 getPosition() = 0; + + //============================================================================== + /** Writes a block of data to the stream. + + When creating a subclass of OutputStream, this is the only write method + that needs to be overloaded - the base class has methods for writing other + types of data which use this to do the work. + + @param dataToWrite the target buffer to receive the data. This must not be null. + @param numberOfBytes the number of bytes to write. + @returns false if the write operation fails for some reason + */ + virtual bool write (const void* dataToWrite, + size_t numberOfBytes) = 0; + + //============================================================================== + /** Writes a single byte to the stream. + @returns false if the write operation fails for some reason + @see InputStream::readByte + */ + virtual bool writeByte (char byte); + + /** Writes a boolean to the stream as a single byte. + This is encoded as a binary byte (not as text) with a value of 1 or 0. + @returns false if the write operation fails for some reason + @see InputStream::readBool + */ + virtual bool writeBool (bool boolValue); + + /** Writes a 16-bit integer to the stream in a little-endian byte order. + This will write two bytes to the stream: (value & 0xff), then (value >> 8). + @returns false if the write operation fails for some reason + @see InputStream::readShort + */ + virtual bool writeShort (short value); + + /** Writes a 16-bit integer to the stream in a big-endian byte order. + This will write two bytes to the stream: (value >> 8), then (value & 0xff). + @returns false if the write operation fails for some reason + @see InputStream::readShortBigEndian + */ + virtual bool writeShortBigEndian (short value); + + /** Writes a 32-bit integer to the stream in a little-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt + */ + virtual bool writeInt (int value); + + /** Writes a 32-bit integer to the stream in a big-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readIntBigEndian + */ + virtual bool writeIntBigEndian (int value); + + /** Writes a 64-bit integer to the stream in a little-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt64 + */ + virtual bool writeInt64 (int64 value); + + /** Writes a 64-bit integer to the stream in a big-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt64BigEndian + */ + virtual bool writeInt64BigEndian (int64 value); + + /** Writes a 32-bit floating point value to the stream in a binary format. + The binary 32-bit encoding of the float is written as a little-endian int. + @returns false if the write operation fails for some reason + @see InputStream::readFloat + */ + virtual bool writeFloat (float value); + + /** Writes a 32-bit floating point value to the stream in a binary format. + The binary 32-bit encoding of the float is written as a big-endian int. + @returns false if the write operation fails for some reason + @see InputStream::readFloatBigEndian + */ + virtual bool writeFloatBigEndian (float value); + + /** Writes a 64-bit floating point value to the stream in a binary format. + The eight raw bytes of the double value are written out as a little-endian 64-bit int. + @returns false if the write operation fails for some reason + @see InputStream::readDouble + */ + virtual bool writeDouble (double value); + + /** Writes a 64-bit floating point value to the stream in a binary format. + The eight raw bytes of the double value are written out as a big-endian 64-bit int. + @see InputStream::readDoubleBigEndian + @returns false if the write operation fails for some reason + */ + virtual bool writeDoubleBigEndian (double value); + + /** Writes a byte to the output stream a given number of times. + @returns false if the write operation fails for some reason + */ + virtual bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat); + + /** Writes a condensed binary encoding of a 32-bit integer. + + If you're storing a lot of integers which are unlikely to have very large values, + this can save a lot of space, because values under 0xff will only take up 2 bytes, + under 0xffff only 3 bytes, etc. + + The format used is: number of significant bytes + up to 4 bytes in little-endian order. + + @returns false if the write operation fails for some reason + @see InputStream::readCompressedInt + */ + virtual bool writeCompressedInt (int value); + + /** Stores a string in the stream in a binary format. + + This isn't the method to use if you're trying to append text to the end of a + text-file! It's intended for storing a string so that it can be retrieved later + by InputStream::readString(). + + It writes the string to the stream as UTF8, including the null termination character. + + For appending text to a file, instead use writeText, or operator<< + + @returns false if the write operation fails for some reason + @see InputStream::readString, writeText, operator<< + */ + virtual bool writeString (const String& text); + + /** Writes a string of text to the stream. + + It can either write the text as UTF-8 or UTF-16, and can also add the UTF-16 byte-order-mark + bytes (0xff, 0xfe) to indicate the endianness (these should only be used at the start + of a file). + + The method also replaces '\\n' characters in the text with '\\r\\n'. + @returns false if the write operation fails for some reason + */ + virtual bool writeText (const String& text, + bool asUTF16, + bool writeUTF16ByteOrderMark); + + /** Reads data from an input stream and writes it to this stream. + + @param source the stream to read from + @param maxNumBytesToWrite the number of bytes to read from the stream (if this is + less than zero, it will keep reading until the input + is exhausted) + @returns the number of bytes written + */ + virtual int64 writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite); + + //============================================================================== + /** Sets the string to write to the stream when a new line is written. + By default this will be set the value of NewLine::getDefault(). + */ + void setNewLineString (const String& newLineString); + + /** Returns the current new-line string that was set by setNewLineString(). */ + const String& getNewLineString() const noexcept { return newLineString; } + +private: + //============================================================================== + String newLineString; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OutputStream) +}; + +//============================================================================== +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, int number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, int64 number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, double number); + +/** Writes a character to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, char character); + +/** Writes a null-terminated text string to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* text); + +/** Writes a block of data from a MemoryBlock to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data); + +/** Writes the contents of a file to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead); + +/** Writes the complete contents of an input stream to an output stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead); + +/** Writes a new-line to a stream. + You can use the predefined symbol 'newLine' to invoke this, e.g. + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode + @see OutputStream::setNewLineString +*/ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&); + + +#endif // JUCE_OUTPUTSTREAM_H_INCLUDED diff --git a/source/modules/juce_audio_graph/text/juce_CharPointer_UTF8.h b/source/modules/juce_audio_graph/text/juce_CharPointer_UTF8.h new file mode 100644 index 000000000..357010735 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_CharPointer_UTF8.h @@ -0,0 +1,572 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF8_H_INCLUDED +#define JUCE_CHARPOINTER_UTF8_H_INCLUDED + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-8 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF16, CharPointer_UTF32 +*/ +class CharPointer_UTF8 +{ +public: + typedef char CharType; + + inline explicit CharPointer_UTF8 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF8 (const CharPointer_UTF8& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF8 operator= (CharPointer_UTF8 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF8 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF8 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF8 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF8 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF8 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF8 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF8 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + juce_wchar operator*() const noexcept + { + const signed char byte = (signed char) *data; + + if (byte >= 0) + return (juce_wchar) (uint8) byte; + + uint32 n = (uint32) (uint8) byte; + uint32 mask = 0x7f; + uint32 bit = 0x40; + int numExtraValues = 0; + + while ((n & bit) != 0 && bit > 0x8) + { + mask >>= 1; + ++numExtraValues; + bit >>= 1; + } + + n &= mask; + + for (int i = 1; i <= numExtraValues; ++i) + { + const uint32 nextByte = (uint32) (uint8) data[i]; + + if ((nextByte & 0xc0) != 0x80) + break; + + n <<= 6; + n |= (nextByte & 0x3f); + } + + return (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF8& operator++() noexcept + { + jassert (*data != 0); // trying to advance past the end of the string? + const signed char n = (signed char) *data++; + + if (n < 0) + { + juce_wchar bit = 0x40; + + while ((n & bit) != 0 && bit > 0x8) + { + ++data; + bit >>= 1; + } + } + + return *this; + } + + /** Moves this pointer back to the previous character in the string. */ + CharPointer_UTF8 operator--() noexcept + { + int count = 0; + + while ((*--data & 0xc0) == 0x80 && ++count < 4) + {} + + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + juce_wchar getAndAdvance() noexcept + { + const signed char byte = (signed char) *data++; + + if (byte >= 0) + return (juce_wchar) (uint8) byte; + + uint32 n = (uint32) (uint8) byte; + uint32 mask = 0x7f; + uint32 bit = 0x40; + int numExtraValues = 0; + + while ((n & bit) != 0 && bit > 0x8) + { + mask >>= 1; + ++numExtraValues; + bit >>= 1; + } + + n &= mask; + + while (--numExtraValues >= 0) + { + const uint32 nextByte = (uint32) (uint8) *data; + + if ((nextByte & 0xc0) != 0x80) + break; + + ++data; + n <<= 6; + n |= (nextByte & 0x3f); + } + + return (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF8 operator++ (int) noexcept + { + CharPointer_UTF8 temp (*this); + ++*this; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + void operator+= (int numToSkip) noexcept + { + if (numToSkip < 0) + { + while (++numToSkip <= 0) + --*this; + } + else + { + while (--numToSkip >= 0) + ++*this; + } + } + + /** Moves this pointer backwards by the specified number of characters. */ + void operator-= (int numToSkip) noexcept + { + operator+= (-numToSkip); + } + + /** Returns the character at a given character index from the start of the string. */ + juce_wchar operator[] (int characterIndex) const noexcept + { + CharPointer_UTF8 p (*this); + p += characterIndex; + return *p; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF8 operator+ (int numToSkip) const noexcept + { + CharPointer_UTF8 p (*this); + p += numToSkip; + return p; + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF8 operator- (int numToSkip) const noexcept + { + CharPointer_UTF8 p (*this); + p += -numToSkip; + return p; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + const CharType* d = data; + size_t count = 0; + + for (;;) + { + const uint32 n = (uint32) (uint8) *d++; + + if ((n & 0x80) != 0) + { + while ((*d & 0xc0) == 0x80) + ++d; + } + else if (n == 0) + break; + + ++count; + } + + return count; + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF8 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + jassert (data != nullptr); + return strlen (data) + 1; + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static size_t getBytesRequiredFor (const juce_wchar charToWrite) noexcept + { + size_t num = 1; + const uint32 c = (uint32) charToWrite; + + if (c >= 0x80) + { + ++num; + if (c >= 0x800) + { + ++num; + if (c >= 0x10000) + ++num; + } + } + + return num; + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (CharPointer text) noexcept + { + size_t count = 0; + + while (juce_wchar n = text.getAndAdvance()) + count += getBytesRequiredFor (n); + + return count; + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF8 findTerminatingNull() const noexcept + { + return CharPointer_UTF8 (data + strlen (data)); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + void write (const juce_wchar charToWrite) noexcept + { + const uint32 c = (uint32) charToWrite; + + if (c >= 0x80) + { + int numExtraBytes = 1; + if (c >= 0x800) + { + ++numExtraBytes; + if (c >= 0x10000) + ++numExtraBytes; + } + + *data++ = (CharType) ((uint32) (0xff << (7 - numExtraBytes)) | (c >> (numExtraBytes * 6))); + + while (--numExtraBytes >= 0) + *data++ = (CharType) (0x80 | (0x3f & (c >> (numExtraBytes * 6)))); + } + else + { + *data++ = (CharType) c; + } + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF8 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const noexcept + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one. */ + int compareIgnoreCase (const CharPointer_UTF8 other) const noexcept + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind) const noexcept + { + return CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const noexcept { const CharType c = *data; return c == ' ' || (c <= 13 && c >= 9); } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const noexcept { const CharType c = *data; return c >= '0' && c <= '9'; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return atoi (data); } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if JUCE_LINUX || JUCE_ANDROID || JUCE_MINGW + return atoll (data); + #elif JUCE_WINDOWS + return _atoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF8 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (juce_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + while (--maxBytesToRead >= 0 && *dataToTest != 0) + { + const signed char byte = (signed char) *dataToTest++; + + if (byte < 0) + { + int bit = 0x40; + int numExtraValues = 0; + + while ((byte & bit) != 0) + { + if (bit < 8) + return false; + + ++numExtraValues; + bit >>= 1; + + if (bit == 8 && (numExtraValues > maxBytesToRead + || *CharPointer_UTF8 (dataToTest - 1) > 0x10ffff)) + return false; + } + + if (numExtraValues == 0) + return false; + + maxBytesToRead -= numExtraValues; + if (maxBytesToRead < 0) + return false; + + while (--numExtraValues >= 0) + if ((*dataToTest++ & 0xc0) != 0x80) + return false; + } + } + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF8 atomicSwap (const CharPointer_UTF8 newValue) + { + return CharPointer_UTF8 (reinterpret_cast&> (data).exchange (newValue.data)); + } + + /** These values are the byte-order mark (BOM) values for a UTF-8 stream. */ + enum + { + byteOrderMark1 = 0xef, + byteOrderMark2 = 0xbb, + byteOrderMark3 = 0xbf + }; + + /** Returns true if the first three bytes in this pointer are the UTF8 byte-order mark (BOM). + The pointer must not be null, and must point to at least 3 valid bytes. + */ + static bool isByteOrderMark (const void* possibleByteOrder) noexcept + { + jassert (possibleByteOrder != nullptr); + const uint8* const c = static_cast (possibleByteOrder); + + return c[0] == (uint8) byteOrderMark1 + && c[1] == (uint8) byteOrderMark2 + && c[2] == (uint8) byteOrderMark3; + } + +private: + CharType* data; +}; + +#endif // JUCE_CHARPOINTER_UTF8_H_INCLUDED diff --git a/source/modules/juce_audio_graph/text/juce_CharacterFunctions.cpp b/source/modules/juce_audio_graph/text/juce_CharacterFunctions.cpp new file mode 100644 index 000000000..b6f610d18 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_CharacterFunctions.cpp @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +//============================================================================== + +juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) noexcept +{ + return (juce_wchar) towupper ((wint_t) character); +} + +juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) noexcept +{ + return (juce_wchar) towlower ((wint_t) character); +} + +bool CharacterFunctions::isUpperCase (const juce_wchar character) noexcept +{ + #if JUCE_WINDOWS + return iswupper ((wint_t) character) != 0; + #else + return toLowerCase (character) != character; + #endif +} + +bool CharacterFunctions::isLowerCase (const juce_wchar character) noexcept +{ + #if JUCE_WINDOWS + return iswlower ((wint_t) character) != 0; + #else + return toUpperCase (character) != character; + #endif +} + +//============================================================================== +bool CharacterFunctions::isWhitespace (const char character) noexcept +{ + return character == ' ' || (character <= 13 && character >= 9); +} + +bool CharacterFunctions::isWhitespace (const juce_wchar character) noexcept +{ + return iswspace ((wint_t) character) != 0; +} + +bool CharacterFunctions::isDigit (const char character) noexcept +{ + return (character >= '0' && character <= '9'); +} + +bool CharacterFunctions::isDigit (const juce_wchar character) noexcept +{ + return iswdigit ((wint_t) character) != 0; +} + +bool CharacterFunctions::isLetter (const char character) noexcept +{ + return (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z'); +} + +bool CharacterFunctions::isLetter (const juce_wchar character) noexcept +{ + return iswalpha ((wint_t) character) != 0; +} + +bool CharacterFunctions::isLetterOrDigit (const char character) noexcept +{ + return (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9'); +} + +bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept +{ + return iswalnum ((wint_t) character) != 0; +} + +bool CharacterFunctions::isPrintable (const char character) noexcept +{ + return (character >= ' ' && character <= '~'); +} + +bool CharacterFunctions::isPrintable (const juce_wchar character) noexcept +{ + return iswprint ((wint_t) character) != 0; +} + +int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept +{ + unsigned int d = (unsigned int) digit - '0'; + if (d < (unsigned int) 10) + return (int) d; + + d += (unsigned int) ('0' - 'a'); + if (d < (unsigned int) 6) + return (int) d + 10; + + d += (unsigned int) ('a' - 'A'); + if (d < (unsigned int) 6) + return (int) d + 10; + + return -1; +} + +double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept +{ + if (exponent == 0) + return value; + + if (value == 0) + return 0; + + const bool negative = (exponent < 0); + if (negative) + exponent = -exponent; + + double result = 1.0, power = 10.0; + for (int bit = 1; exponent != 0; bit <<= 1) + { + if ((exponent & bit) != 0) + { + exponent ^= bit; + result *= power; + if (exponent == 0) + break; + } + power *= power; + } + + return negative ? (value / result) : (value * result); +} + +juce_wchar CharacterFunctions::getUnicodeCharFromWindows1252Codepage (const uint8 c) noexcept +{ + if (c < 0x80 || c >= 0xa0) + return (juce_wchar) c; + + static const uint16 lookup[] = { 0x20AC, 0x0007, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0007, 0x017D, 0x0007, + 0x0007, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0007, 0x017E, 0x0178 }; + + return (juce_wchar) lookup[c - 0x80]; +} diff --git a/source/modules/juce_audio_graph/text/juce_CharacterFunctions.h b/source/modules/juce_audio_graph/text/juce_CharacterFunctions.h new file mode 100644 index 000000000..9ab51f613 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_CharacterFunctions.h @@ -0,0 +1,620 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CHARACTERFUNCTIONS_H_INCLUDED +#define JUCE_CHARACTERFUNCTIONS_H_INCLUDED + +/** This macro will be set to 1 if the compiler's native wchar_t is an 8-bit type. */ +#define JUCE_NATIVE_WCHAR_IS_UTF8 1 + +/** A platform-independent 32-bit unicode character type. */ +typedef uint32 juce_wchar; + +//============================================================================== +/** + A collection of functions for manipulating characters and character strings. + + Most of these methods are designed for internal use by the String and CharPointer + classes, but some of them may be useful to call directly. + + @see String, CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32 +*/ +class JUCE_API CharacterFunctions +{ +public: + //============================================================================== + /** Converts a character to upper-case. */ + static juce_wchar toUpperCase (juce_wchar character) noexcept; + /** Converts a character to lower-case. */ + static juce_wchar toLowerCase (juce_wchar character) noexcept; + + /** Checks whether a unicode character is upper-case. */ + static bool isUpperCase (juce_wchar character) noexcept; + /** Checks whether a unicode character is lower-case. */ + static bool isLowerCase (juce_wchar character) noexcept; + + /** Checks whether a character is whitespace. */ + static bool isWhitespace (char character) noexcept; + /** Checks whether a character is whitespace. */ + static bool isWhitespace (juce_wchar character) noexcept; + + /** Checks whether a character is a digit. */ + static bool isDigit (char character) noexcept; + /** Checks whether a character is a digit. */ + static bool isDigit (juce_wchar character) noexcept; + + /** Checks whether a character is alphabetic. */ + static bool isLetter (char character) noexcept; + /** Checks whether a character is alphabetic. */ + static bool isLetter (juce_wchar character) noexcept; + + /** Checks whether a character is alphabetic or numeric. */ + static bool isLetterOrDigit (char character) noexcept; + /** Checks whether a character is alphabetic or numeric. */ + static bool isLetterOrDigit (juce_wchar character) noexcept; + + /** Checks whether a character is a printable character, i.e. alphabetic, numeric, + a punctuation character or a space. + */ + static bool isPrintable (char character) noexcept; + + /** Checks whether a character is a printable character, i.e. alphabetic, numeric, + a punctuation character or a space. + */ + static bool isPrintable (juce_wchar character) noexcept; + + /** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ + static int getHexDigitValue (juce_wchar digit) noexcept; + + /** Converts a byte of Windows 1252 codepage to unicode. */ + static juce_wchar getUnicodeCharFromWindows1252Codepage (uint8 windows1252Char) noexcept; + + //============================================================================== + /** Parses a character string to read a floating-point number. + Note that this will advance the pointer that is passed in, leaving it at + the end of the number. + */ + template + static double readDoubleValue (CharPointerType& text) noexcept + { + double result[3] = { 0 }, accumulator[2] = { 0 }; + int exponentAdjustment[2] = { 0 }, exponentAccumulator[2] = { -1, -1 }; + int exponent = 0, decPointIndex = 0, digit = 0; + int lastDigit = 0, numSignificantDigits = 0; + bool isNegative = false, digitsFound = false; + const int maxSignificantDigits = 15 + 2; + + text = text.findEndOfWhitespace(); + juce_wchar c = *text; + + switch (c) + { + case '-': isNegative = true; // fall-through.. + case '+': c = *++text; + } + + switch (c) + { + case 'n': + case 'N': + if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N')) + return std::numeric_limits::quiet_NaN(); + break; + + case 'i': + case 'I': + if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F')) + return std::numeric_limits::infinity(); + break; + } + + for (;;) + { + if (text.isDigit()) + { + lastDigit = digit; + digit = (int) text.getAndAdvance() - '0'; + digitsFound = true; + + if (decPointIndex != 0) + exponentAdjustment[1]++; + + if (numSignificantDigits == 0 && digit == 0) + continue; + + if (++numSignificantDigits > maxSignificantDigits) + { + if (digit > 5) + ++accumulator [decPointIndex]; + else if (digit == 5 && (lastDigit & 1) != 0) + ++accumulator [decPointIndex]; + + if (decPointIndex > 0) + exponentAdjustment[1]--; + else + exponentAdjustment[0]++; + + while (text.isDigit()) + { + ++text; + if (decPointIndex == 0) + exponentAdjustment[0]++; + } + } + else + { + const double maxAccumulatorValue = (double) ((std::numeric_limits::max() - 9) / 10); + if (accumulator [decPointIndex] > maxAccumulatorValue) + { + result [decPointIndex] = mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex]) + + accumulator [decPointIndex]; + accumulator [decPointIndex] = 0; + exponentAccumulator [decPointIndex] = 0; + } + + accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit; + exponentAccumulator [decPointIndex]++; + } + } + else if (decPointIndex == 0 && *text == '.') + { + ++text; + decPointIndex = 1; + + if (numSignificantDigits > maxSignificantDigits) + { + while (text.isDigit()) + ++text; + break; + } + } + else + { + break; + } + } + + result[0] = mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0]; + + if (decPointIndex != 0) + result[1] = mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1]; + + c = *text; + if ((c == 'e' || c == 'E') && digitsFound) + { + bool negativeExponent = false; + + switch (*++text) + { + case '-': negativeExponent = true; // fall-through.. + case '+': ++text; + } + + while (text.isDigit()) + exponent = (exponent * 10) + ((int) text.getAndAdvance() - '0'); + + if (negativeExponent) + exponent = -exponent; + } + + double r = mulexp10 (result[0], exponent + exponentAdjustment[0]); + if (decPointIndex != 0) + r += mulexp10 (result[1], exponent - exponentAdjustment[1]); + + return isNegative ? -r : r; + } + + /** Parses a character string, to read a floating-point value. */ + template + static double getDoubleValue (CharPointerType text) noexcept + { + return readDoubleValue (text); + } + + //============================================================================== + /** Parses a character string, to read an integer value. */ + template + static IntType getIntValue (const CharPointerType text) noexcept + { + IntType v = 0; + CharPointerType s (text.findEndOfWhitespace()); + + const bool isNeg = *s == '-'; + if (isNeg) + ++s; + + for (;;) + { + const juce_wchar c = s.getAndAdvance(); + + if (c >= '0' && c <= '9') + v = v * 10 + (IntType) (c - '0'); + else + break; + } + + return isNeg ? -v : v; + } + + template + struct HexParser + { + template + static ResultType parse (CharPointerType t) noexcept + { + ResultType result = 0; + + while (! t.isEmpty()) + { + const int hexValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); + + if (hexValue >= 0) + result = (result << 4) | hexValue; + } + + return result; + } + }; + + //============================================================================== + /** Counts the number of characters in a given string, stopping if the count exceeds + a specified limit. */ + template + static size_t lengthUpTo (CharPointerType text, const size_t maxCharsToCount) noexcept + { + size_t len = 0; + + while (len < maxCharsToCount && text.getAndAdvance() != 0) + ++len; + + return len; + } + + /** Counts the number of characters in a given string, stopping if the count exceeds + a specified end-pointer. */ + template + static size_t lengthUpTo (CharPointerType start, const CharPointerType end) noexcept + { + size_t len = 0; + + while (start < end && start.getAndAdvance() != 0) + ++len; + + return len; + } + + /** Copies null-terminated characters from one string to another. */ + template + static void copyAll (DestCharPointerType& dest, SrcCharPointerType src) noexcept + { + while (juce_wchar c = src.getAndAdvance()) + dest.write (c); + + dest.writeNull(); + } + + /** Copies characters from one string to another, up to a null terminator + or a given byte size limit. */ + template + static size_t copyWithDestByteLimit (DestCharPointerType& dest, SrcCharPointerType src, size_t maxBytesToWrite) noexcept + { + typename DestCharPointerType::CharType const* const startAddress = dest.getAddress(); + ssize_t maxBytes = (ssize_t) maxBytesToWrite; + maxBytes -= sizeof (typename DestCharPointerType::CharType); // (allow for a terminating null) + + for (;;) + { + const juce_wchar c = src.getAndAdvance(); + const size_t bytesNeeded = DestCharPointerType::getBytesRequiredFor (c); + + maxBytes -= bytesNeeded; + if (c == 0 || maxBytes < 0) + break; + + dest.write (c); + } + + dest.writeNull(); + + return (size_t) getAddressDifference (dest.getAddress(), startAddress) + + sizeof (typename DestCharPointerType::CharType); + } + + /** Copies characters from one string to another, up to a null terminator + or a given maximum number of characters. */ + template + static void copyWithCharLimit (DestCharPointerType& dest, SrcCharPointerType src, int maxChars) noexcept + { + while (--maxChars > 0) + { + const juce_wchar c = src.getAndAdvance(); + if (c == 0) + break; + + dest.write (c); + } + + dest.writeNull(); + } + + /** Compares two characters. */ + static inline int compare (juce_wchar char1, juce_wchar char2) noexcept + { + if (int diff = static_cast (char1) - static_cast (char2)) + return diff < 0 ? -1 : 1; + + return 0; + } + + /** Compares two null-terminated character strings. */ + template + static int compare (CharPointerType1 s1, CharPointerType2 s2) noexcept + { + for (;;) + { + const juce_wchar c1 = s1.getAndAdvance(); + + if (int diff = compare (c1, s2.getAndAdvance())) + return diff; + + if (c1 == 0) + break; + } + + return 0; + } + + /** Compares two null-terminated character strings, up to a given number of characters. */ + template + static int compareUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept + { + while (--maxChars >= 0) + { + const juce_wchar c1 = s1.getAndAdvance(); + + if (int diff = compare (c1, s2.getAndAdvance())) + return diff; + + if (c1 == 0) + break; + } + + return 0; + } + + /** Compares two characters, using a case-independant match. */ + static inline int compareIgnoreCase (juce_wchar char1, juce_wchar char2) noexcept + { + return char1 != char2 ? compare (toUpperCase (char1), toUpperCase (char2)) : 0; + } + + /** Compares two null-terminated character strings, using a case-independant match. */ + template + static int compareIgnoreCase (CharPointerType1 s1, CharPointerType2 s2) noexcept + { + for (;;) + { + const juce_wchar c1 = s1.getAndAdvance(); + + if (int diff = compareIgnoreCase (c1, s2.getAndAdvance())) + return diff; + + if (c1 == 0) + break; + } + + return 0; + } + + /** Compares two null-terminated character strings, using a case-independent match. */ + template + static int compareIgnoreCaseUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept + { + while (--maxChars >= 0) + { + const juce_wchar c1 = s1.getAndAdvance(); + + if (int diff = compareIgnoreCase (c1, s2.getAndAdvance())) + return diff; + + if (c1 == 0) + break; + } + + return 0; + } + + /** Finds the character index of a given substring in another string. + Returns -1 if the substring is not found. + */ + template + static int indexOf (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept + { + int index = 0; + const int substringLength = (int) substringToLookFor.length(); + + for (;;) + { + if (textToSearch.compareUpTo (substringToLookFor, substringLength) == 0) + return index; + + if (textToSearch.getAndAdvance() == 0) + return -1; + + ++index; + } + } + + /** Returns a pointer to the first occurrence of a substring in a string. + If the substring is not found, this will return a pointer to the string's + null terminator. + */ + template + static CharPointerType1 find (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept + { + const int substringLength = (int) substringToLookFor.length(); + + while (textToSearch.compareUpTo (substringToLookFor, substringLength) != 0 + && ! textToSearch.isEmpty()) + ++textToSearch; + + return textToSearch; + } + + /** Returns a pointer to the first occurrence of a substring in a string. + If the substring is not found, this will return a pointer to the string's + null terminator. + */ + template + static CharPointerType find (CharPointerType textToSearch, const juce_wchar charToLookFor) noexcept + { + for (;; ++textToSearch) + { + const juce_wchar c = *textToSearch; + + if (c == charToLookFor || c == 0) + break; + } + + return textToSearch; + } + + /** Finds the character index of a given substring in another string, using + a case-independent match. + Returns -1 if the substring is not found. + */ + template + static int indexOfIgnoreCase (CharPointerType1 haystack, const CharPointerType2 needle) noexcept + { + int index = 0; + const int needleLength = (int) needle.length(); + + for (;;) + { + if (haystack.compareIgnoreCaseUpTo (needle, needleLength) == 0) + return index; + + if (haystack.getAndAdvance() == 0) + return -1; + + ++index; + } + } + + /** Finds the character index of a given character in another string. + Returns -1 if the character is not found. + */ + template + static int indexOfChar (Type text, const juce_wchar charToFind) noexcept + { + int i = 0; + + while (! text.isEmpty()) + { + if (text.getAndAdvance() == charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Finds the character index of a given character in another string, using + a case-independent match. + Returns -1 if the character is not found. + */ + template + static int indexOfCharIgnoreCase (Type text, juce_wchar charToFind) noexcept + { + charToFind = CharacterFunctions::toLowerCase (charToFind); + int i = 0; + + while (! text.isEmpty()) + { + if (text.toLowerCase() == charToFind) + return i; + + ++text; + ++i; + } + + return -1; + } + + /** Returns a pointer to the first non-whitespace character in a string. + If the string contains only whitespace, this will return a pointer + to its null terminator. + */ + template + static Type findEndOfWhitespace (Type text) noexcept + { + while (text.isWhitespace()) + ++text; + + return text; + } + + /** Returns a pointer to the first character in the string which is found in + the breakCharacters string. + */ + template + static Type findEndOfToken (Type text, const BreakType breakCharacters, const Type quoteCharacters) + { + juce_wchar currentQuoteChar = 0; + + while (! text.isEmpty()) + { + const juce_wchar c = text.getAndAdvance(); + + if (currentQuoteChar == 0 && breakCharacters.indexOf (c) >= 0) + { + --text; + break; + } + + if (quoteCharacters.indexOf (c) >= 0) + { + if (currentQuoteChar == 0) + currentQuoteChar = c; + else if (currentQuoteChar == c) + currentQuoteChar = 0; + } + } + + return text; + } + +private: + static double mulexp10 (const double value, int exponent) noexcept; +}; + + +#endif // JUCE_CHARACTERFUNCTIONS_H_INCLUDED diff --git a/source/modules/juce_audio_graph/text/juce_NewLine.h b/source/modules/juce_audio_graph/text/juce_NewLine.h new file mode 100644 index 000000000..fef928344 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_NewLine.h @@ -0,0 +1,90 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_NEWLINE_H_INCLUDED +#define JUCE_NEWLINE_H_INCLUDED + + +//============================================================================== +/** This class is used for represent a new-line character sequence. + + To write a new-line to a stream, you can use the predefined 'newLine' variable, e.g. + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode + + The exact character sequence that will be used for the new-line can be set and + retrieved with OutputStream::setNewLineString() and OutputStream::getNewLineString(). +*/ +class JUCE_API NewLine +{ +public: + /** Returns the default new-line sequence that the library uses. + @see OutputStream::setNewLineString() + */ + static const char* getDefault() noexcept { return "\r\n"; } + + /** Returns the default new-line sequence that the library uses. + @see getDefault() + */ + operator String() const { return getDefault(); } + + /** Returns the default new-line sequence that the library uses. + @see OutputStream::setNewLineString() + */ + operator StringRef() const noexcept { return getDefault(); } +}; + +//============================================================================== +/** A predefined object representing a new-line, which can be written to a string or stream. + + To write a new-line to a stream, you can use the predefined 'newLine' variable like this: + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode +*/ +extern NewLine newLine; + +//============================================================================== +/** Writes a new-line sequence to a string. + You can use the predefined object 'newLine' to invoke this, e.g. + @code + myString << "Hello World" << newLine << newLine; + @endcode +*/ +inline String& operator<< (String& string1, const NewLine&) { return string1 += NewLine::getDefault(); } +inline String& operator+= (String& s1, const NewLine&) { return s1 += NewLine::getDefault(); } + +inline String operator+ (const NewLine&, const NewLine&) { return String (NewLine::getDefault()) + NewLine::getDefault(); } +inline String operator+ (String s1, const NewLine&) { return s1 += NewLine::getDefault(); } +inline String operator+ (const NewLine&, const char* s2) { return String (NewLine::getDefault()) + s2; } + + +#endif // JUCE_NEWLINE_H_INCLUDED diff --git a/source/modules/juce_audio_graph/text/juce_String.cpp b/source/modules/juce_audio_graph/text/juce_String.cpp new file mode 100644 index 000000000..edc105f4e --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_String.cpp @@ -0,0 +1,1972 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +NewLine newLine; + +//============================================================================== +// (Mirrors the structure of StringHolder, but without the atomic member, so can be statically constructed) +struct EmptyString +{ + int refCount; + size_t allocatedBytes; + String::CharPointerType::CharType text; +}; + +static const EmptyString emptyString = { 0x3fffffff, sizeof (String::CharPointerType::CharType), 0 }; + +//============================================================================== +class StringHolder +{ +public: + StringHolder() JUCE_DELETED_FUNCTION; + + typedef String::CharPointerType CharPointerType; + typedef String::CharPointerType::CharType CharType; + + //============================================================================== + static CharPointerType createUninitialisedBytes (size_t numBytes) + { + numBytes = (numBytes + 3) & ~(size_t) 3; + StringHolder* const s = reinterpret_cast (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); + s->refCount.value = 0; + s->allocatedNumBytes = numBytes; + return CharPointerType (s->text); + } + + template + static CharPointerType createFromCharPointer (const CharPointer text) + { + if (text.getAddress() == nullptr || text.isEmpty()) + return CharPointerType (&(emptyString.text)); + + const size_t bytesNeeded = sizeof (CharType) + CharPointerType::getBytesRequiredFor (text); + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeAll (text); + return dest; + } + + template + static CharPointerType createFromCharPointer (const CharPointer text, size_t maxChars) + { + if (text.getAddress() == nullptr || text.isEmpty() || maxChars == 0) + return CharPointerType (&(emptyString.text)); + + CharPointer end (text); + size_t numChars = 0; + size_t bytesNeeded = sizeof (CharType); + + while (numChars < maxChars && ! end.isEmpty()) + { + bytesNeeded += CharPointerType::getBytesRequiredFor (end.getAndAdvance()); + ++numChars; + } + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeWithCharLimit (text, (int) numChars + 1); + return dest; + } + + template + static CharPointerType createFromCharPointer (const CharPointer start, const CharPointer end) + { + if (start.getAddress() == nullptr || start.isEmpty()) + return CharPointerType (&(emptyString.text)); + + CharPointer e (start); + int numChars = 0; + size_t bytesNeeded = sizeof (CharType); + + while (e < end && ! e.isEmpty()) + { + bytesNeeded += CharPointerType::getBytesRequiredFor (e.getAndAdvance()); + ++numChars; + } + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeWithCharLimit (start, numChars + 1); + return dest; + } + + static CharPointerType createFromCharPointer (const CharPointerType start, const CharPointerType end) + { + if (start.getAddress() == nullptr || start.isEmpty()) + return CharPointerType (&(emptyString.text)); + + const size_t numBytes = (size_t) (reinterpret_cast (end.getAddress()) + - reinterpret_cast (start.getAddress())); + const CharPointerType dest (createUninitialisedBytes (numBytes + sizeof (CharType))); + memcpy (dest.getAddress(), start, numBytes); + dest.getAddress()[numBytes / sizeof (CharType)] = 0; + return dest; + } + + static CharPointerType createFromFixedLength (const char* const src, const size_t numChars) + { + const CharPointerType dest (createUninitialisedBytes (numChars * sizeof (CharType) + sizeof (CharType))); + CharPointerType (dest).writeWithCharLimit (CharPointer_UTF8 (src), (int) (numChars + 1)); + return dest; + } + + //============================================================================== + static void retain (const CharPointerType text) noexcept + { + StringHolder* const b = bufferFromText (text); + + if (b != (StringHolder*) &emptyString) + ++(b->refCount); + } + + static inline void release (StringHolder* const b) noexcept + { + if (b != (StringHolder*) &emptyString) + if (--(b->refCount) == -1) + delete[] reinterpret_cast (b); + } + + static void release (const CharPointerType text) noexcept + { + release (bufferFromText (text)); + } + + static inline int getReferenceCount (const CharPointerType text) noexcept + { + return bufferFromText (text)->refCount.get() + 1; + } + + //============================================================================== + static CharPointerType makeUniqueWithByteSize (const CharPointerType text, size_t numBytes) + { + StringHolder* const b = bufferFromText (text); + + if (b == (StringHolder*) &emptyString) + { + CharPointerType newText (createUninitialisedBytes (numBytes)); + newText.writeNull(); + return newText; + } + + if (b->allocatedNumBytes >= numBytes && b->refCount.get() <= 0) + return text; + + CharPointerType newText (createUninitialisedBytes (jmax (b->allocatedNumBytes, numBytes))); + memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes); + release (b); + + return newText; + } + + static size_t getAllocatedNumBytes (const CharPointerType text) noexcept + { + return bufferFromText (text)->allocatedNumBytes; + } + + //============================================================================== + Atomic refCount; + size_t allocatedNumBytes; + CharType text[1]; + +private: + static inline StringHolder* bufferFromText (const CharPointerType text) noexcept + { + // (Can't use offsetof() here because of warnings about this not being a POD) + return reinterpret_cast (reinterpret_cast (text.getAddress()) + - (reinterpret_cast (reinterpret_cast (1)->text) - 1)); + } +}; + +//============================================================================== +String::String() noexcept : text (&(emptyString.text)) +{ +} + +String::~String() noexcept +{ + StringHolder::release (text); +} + +String::String (const String& other) noexcept : text (other.text) +{ + StringHolder::retain (text); +} + +void String::swapWith (String& other) noexcept +{ + std::swap (text, other.text); +} + +void String::clear() noexcept +{ + StringHolder::release (text); + text = &(emptyString.text); +} + +String& String::operator= (const String& other) noexcept +{ + StringHolder::retain (other.text); + StringHolder::release (text.atomicSwap (other.text)); + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +String::String (String&& other) noexcept : text (other.text) +{ + other.text = &(emptyString.text); +} + +String& String::operator= (String&& other) noexcept +{ + std::swap (text, other.text); + return *this; +} +#endif + +inline String::PreallocationBytes::PreallocationBytes (const size_t num) noexcept : numBytes (num) {} + +String::String (const PreallocationBytes& preallocationSize) + : text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType))) +{ +} + +void String::preallocateBytes (const size_t numBytesNeeded) +{ + text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType)); +} + +int String::getReferenceCount() const noexcept +{ + return StringHolder::getReferenceCount (text); +} + +//============================================================================== +String::String (const char* const t) + : text (StringHolder::createFromCharPointer (CharPointer_UTF8 (t))) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + + Note that the Projucer has a handy string literal generator utility that will convert + any unicode string to a valid C++ string literal, creating ascii escape sequences that will + work in any compiler. + */ + jassert (t == nullptr || CharPointer_UTF8::isValidString (t, std::numeric_limits::max())); +} + +String::String (const char* const t, const size_t maxChars) + : text (StringHolder::createFromCharPointer (CharPointer_UTF8 (t), maxChars)) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + + Note that the Projucer has a handy string literal generator utility that will convert + any unicode string to a valid C++ string literal, creating ascii escape sequences that will + work in any compiler. + */ + jassert (t == nullptr || CharPointer_UTF8::isValidString (t, (int) maxChars)); +} + +String::String (const CharPointer_UTF8 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_UTF8 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const CharPointer_UTF8 start, const CharPointer_UTF8 end) : text (StringHolder::createFromCharPointer (start, end)) {} + +String::String (const std::string& s) : text (StringHolder::createFromFixedLength (s.data(), s.size())) {} +String::String (StringRef s) : text (StringHolder::createFromCharPointer (s.text)) {} + +String String::charToString (const juce_wchar character) +{ + String result (PreallocationBytes (CharPointerType::getBytesRequiredFor (character))); + CharPointerType t (result.text); + t.write (character); + t.writeNull(); + return result; +} + +//============================================================================== +namespace NumberToStringConverters +{ + enum + { + charsNeededForInt = 32, + charsNeededForDouble = 48 + }; + + template + static char* printDigits (char* t, Type v) noexcept + { + *--t = 0; + + do + { + *--t = '0' + (char) (v % 10); + v /= 10; + + } while (v > 0); + + return t; + } + + // pass in a pointer to the END of a buffer.. + static char* numberToString (char* t, const int64 n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + // NB: this needs to be careful not to call -std::numeric_limits::min(), + // which has undefined behaviour + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, uint64 v) noexcept + { + return printDigits (t, v); + } + + static char* numberToString (char* t, const int n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + // NB: this needs to be careful not to call -std::numeric_limits::min(), + // which has undefined behaviour + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, const unsigned int v) noexcept + { + return printDigits (t, v); + } + + static char* numberToString (char* t, const long n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, const unsigned long v) noexcept + { + return printDigits (t, v); + } + + struct StackArrayStream : public std::basic_streambuf > + { + explicit StackArrayStream (char* d) + { + static const std::locale classicLocale (std::locale::classic()); + imbue (classicLocale); + setp (d, d + charsNeededForDouble); + } + + size_t writeDouble (double n, int numDecPlaces) + { + { + std::ostream o (this); + + if (numDecPlaces > 0) + o.precision ((std::streamsize) numDecPlaces); + + o << n; + } + + return (size_t) (pptr() - pbase()); + } + }; + + static char* doubleToString (char* buffer, const int numChars, double n, int numDecPlaces, size_t& len) noexcept + { + if (numDecPlaces > 0 && numDecPlaces < 7 && n > -1.0e20 && n < 1.0e20) + { + char* const end = buffer + numChars; + char* t = end; + int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5); + *--t = (char) 0; + + while (numDecPlaces >= 0 || v > 0) + { + if (numDecPlaces == 0) + *--t = '.'; + + *--t = (char) ('0' + (v % 10)); + + v /= 10; + --numDecPlaces; + } + + if (n < 0) + *--t = '-'; + + len = (size_t) (end - t - 1); + return t; + } + + StackArrayStream strm (buffer); + len = strm.writeDouble (n, numDecPlaces); + jassert (len <= charsNeededForDouble); + return buffer; + } + + template + static String::CharPointerType createFromInteger (const IntegerType number) + { + char buffer [charsNeededForInt]; + char* const end = buffer + numElementsInArray (buffer); + char* const start = numberToString (end, number); + return StringHolder::createFromFixedLength (start, (size_t) (end - start - 1)); + } + + static String::CharPointerType createFromDouble (const double number, const int numberOfDecimalPlaces) + { + char buffer [charsNeededForDouble]; + size_t len; + char* const start = doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len); + return StringHolder::createFromFixedLength (start, len); + } +} + +//============================================================================== +String::String (const int number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const unsigned int number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const short number) : text (NumberToStringConverters::createFromInteger ((int) number)) {} +String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} +String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const long number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const unsigned long number) : text (NumberToStringConverters::createFromInteger (number)) {} + +String::String (const float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {} +String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} +String::String (const float number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble ((double) number, numberOfDecimalPlaces)) {} +String::String (const double number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble (number, numberOfDecimalPlaces)) {} + +//============================================================================== +int String::length() const noexcept +{ + return (int) text.length(); +} + +static size_t findByteOffsetOfEnd (String::CharPointerType text) noexcept +{ + return (size_t) (((char*) text.findTerminatingNull().getAddress()) - (char*) text.getAddress()); +} + +size_t String::getByteOffsetOfEnd() const noexcept +{ + return findByteOffsetOfEnd (text); +} + +juce_wchar String::operator[] (int index) const noexcept +{ + jassert (index == 0 || (index > 0 && index <= (int) text.lengthUpTo ((size_t) index + 1))); + return text [index]; +} + +template +struct HashGenerator +{ + template + static Type calculate (CharPointer t) noexcept + { + Type result = Type(); + + while (! t.isEmpty()) + result = ((Type) multiplier) * result + (Type) t.getAndAdvance(); + + return result; + } + + enum { multiplier = sizeof (Type) > 4 ? 101 : 31 }; +}; + +int String::hashCode() const noexcept { return HashGenerator ::calculate (text); } +int64 String::hashCode64() const noexcept { return HashGenerator ::calculate (text); } +size_t String::hash() const noexcept { return HashGenerator ::calculate (text); } + +//============================================================================== +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const String& s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const char* s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const char* s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, StringRef s2) noexcept { return s1.getCharPointer().compare (s2.text) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, StringRef s2) noexcept { return s1.getCharPointer().compare (s2.text) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator> (const String& s1, const String& s2) noexcept { return s1.compare (s2) > 0; } +JUCE_API bool JUCE_CALLTYPE operator< (const String& s1, const String& s2) noexcept { return s1.compare (s2) < 0; } +JUCE_API bool JUCE_CALLTYPE operator>= (const String& s1, const String& s2) noexcept { return s1.compare (s2) >= 0; } +JUCE_API bool JUCE_CALLTYPE operator<= (const String& s1, const String& s2) noexcept { return s1.compare (s2) <= 0; } + +bool String::equalsIgnoreCase (const char* const t) const noexcept +{ + return t != nullptr ? text.compareIgnoreCase (CharPointer_UTF8 (t)) == 0 + : isEmpty(); +} + +bool String::equalsIgnoreCase (StringRef t) const noexcept +{ + return text.compareIgnoreCase (t.text) == 0; +} + +bool String::equalsIgnoreCase (const String& other) const noexcept +{ + return text == other.text + || text.compareIgnoreCase (other.text) == 0; +} + +int String::compare (const String& other) const noexcept { return (text == other.text) ? 0 : text.compare (other.text); } +int String::compare (const char* const other) const noexcept { return text.compare (CharPointer_UTF8 (other)); } +int String::compareIgnoreCase (const String& other) const noexcept { return (text == other.text) ? 0 : text.compareIgnoreCase (other.text); } + +static int stringCompareRight (String::CharPointerType s1, String::CharPointerType s2) noexcept +{ + for (int bias = 0;;) + { + const juce_wchar c1 = s1.getAndAdvance(); + const bool isDigit1 = CharacterFunctions::isDigit (c1); + + const juce_wchar c2 = s2.getAndAdvance(); + const bool isDigit2 = CharacterFunctions::isDigit (c2); + + if (! (isDigit1 || isDigit2)) return bias; + if (! isDigit1) return -1; + if (! isDigit2) return 1; + + if (c1 != c2 && bias == 0) + bias = c1 < c2 ? -1 : 1; + + jassert (c1 != 0 && c2 != 0); + } +} + +static int stringCompareLeft (String::CharPointerType s1, String::CharPointerType s2) noexcept +{ + for (;;) + { + const juce_wchar c1 = s1.getAndAdvance(); + const bool isDigit1 = CharacterFunctions::isDigit (c1); + + const juce_wchar c2 = s2.getAndAdvance(); + const bool isDigit2 = CharacterFunctions::isDigit (c2); + + if (! (isDigit1 || isDigit2)) return 0; + if (! isDigit1) return -1; + if (! isDigit2) return 1; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + } +} + +static int naturalStringCompare (String::CharPointerType s1, String::CharPointerType s2, bool isCaseSensitive) noexcept +{ + bool firstLoop = true; + + for (;;) + { + const bool hasSpace1 = s1.isWhitespace(); + const bool hasSpace2 = s2.isWhitespace(); + + if ((! firstLoop) && (hasSpace1 ^ hasSpace2)) + return hasSpace2 ? 1 : -1; + + firstLoop = false; + + if (hasSpace1) s1 = s1.findEndOfWhitespace(); + if (hasSpace2) s2 = s2.findEndOfWhitespace(); + + if (s1.isDigit() && s2.isDigit()) + { + const int result = (*s1 == '0' || *s2 == '0') ? stringCompareLeft (s1, s2) + : stringCompareRight (s1, s2); + + if (result != 0) + return result; + } + + juce_wchar c1 = s1.getAndAdvance(); + juce_wchar c2 = s2.getAndAdvance(); + + if (c1 != c2 && ! isCaseSensitive) + { + c1 = CharacterFunctions::toUpperCase (c1); + c2 = CharacterFunctions::toUpperCase (c2); + } + + if (c1 == c2) + { + if (c1 == 0) + return 0; + } + else + { + const bool isAlphaNum1 = CharacterFunctions::isLetterOrDigit (c1); + const bool isAlphaNum2 = CharacterFunctions::isLetterOrDigit (c2); + + if (isAlphaNum2 && ! isAlphaNum1) return -1; + if (isAlphaNum1 && ! isAlphaNum2) return 1; + + return c1 < c2 ? -1 : 1; + } + + jassert (c1 != 0 && c2 != 0); + } +} + +int String::compareNatural (StringRef other, bool isCaseSensitive) const noexcept +{ + return naturalStringCompare (getCharPointer(), other.text, isCaseSensitive); +} + +//============================================================================== +void String::append (const String& textToAppend, size_t maxCharsToTake) +{ + appendCharPointer (this == &textToAppend ? String (textToAppend).text + : textToAppend.text, maxCharsToTake); +} + +void String::appendCharPointer (const CharPointerType textToAppend) +{ + appendCharPointer (textToAppend, textToAppend.findTerminatingNull()); +} + +void String::appendCharPointer (const CharPointerType startOfTextToAppend, + const CharPointerType endOfTextToAppend) +{ + jassert (startOfTextToAppend.getAddress() != nullptr && endOfTextToAppend.getAddress() != nullptr); + + const int extraBytesNeeded = getAddressDifference (endOfTextToAppend.getAddress(), + startOfTextToAppend.getAddress()); + jassert (extraBytesNeeded >= 0); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + preallocateBytes (byteOffsetOfNull + (size_t) extraBytesNeeded); + + CharPointerType::CharType* const newStringStart = addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull); + memcpy (newStringStart, startOfTextToAppend.getAddress(), (size_t) extraBytesNeeded); + CharPointerType (addBytesToPointer (newStringStart, extraBytesNeeded)).writeNull(); + } +} + +String& String::operator+= (const char* const t) +{ + appendCharPointer (CharPointer_UTF8 (t)); // (using UTF8 here triggers a faster code-path than ascii) + return *this; +} + +String& String::operator+= (const String& other) +{ + if (isEmpty()) + return operator= (other); + + if (this == &other) + return operator+= (String (*this)); + + appendCharPointer (other.text); + return *this; +} + +String& String::operator+= (StringRef other) +{ + return operator+= (String (other)); +} + +String& String::operator+= (const char ch) +{ + const char asString[] = { ch, 0 }; + return operator+= (asString); +} + +namespace StringHelpers +{ + template + inline String& operationAddAssign (String& str, const T number) + { + char buffer [(sizeof(T) * 8) / 2]; + char* end = buffer + numElementsInArray (buffer); + char* start = NumberToStringConverters::numberToString (end, number); + + str.appendCharPointer (String::CharPointerType (start), String::CharPointerType (end)); + return str; + } +} + +String& String::operator+= (const int number) { return StringHelpers::operationAddAssign (*this, number); } +String& String::operator+= (const int64 number) { return StringHelpers::operationAddAssign (*this, number); } +String& String::operator+= (const uint64 number) { return StringHelpers::operationAddAssign (*this, number); } + +//============================================================================== +JUCE_API String JUCE_CALLTYPE operator+ (const char* const s1, const String& s2) { String s (s1); return s += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (const char s1, const String& s2) { return String::charToString ((juce_wchar) (uint8) s1) + s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const String& s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char* const s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char s2) { return s1 += s2; } + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char* const s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const String& s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, StringRef s2) { return s1 += s2; } + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const unsigned short number) { return s1 += (uint64) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const unsigned long number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int64 number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const uint64 number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& text) +{ + return operator<< (stream, StringRef (text)); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, StringRef text) +{ + const size_t numBytes = CharPointer_UTF8::getBytesRequiredFor (text.text); + + stream.write (text.text.getAddress(), numBytes); + return stream; +} + +//============================================================================== +int String::indexOfChar (const juce_wchar character) const noexcept +{ + return text.indexOf (character); +} + +int String::indexOfChar (const int startIndex, const juce_wchar character) const noexcept +{ + CharPointerType t (text); + + for (int i = 0; ! t.isEmpty(); ++i) + { + if (i >= startIndex) + { + if (t.getAndAdvance() == character) + return i; + } + else + { + ++t; + } + } + + return -1; +} + +int String::lastIndexOfChar (const juce_wchar character) const noexcept +{ + CharPointerType t (text); + int last = -1; + + for (int i = 0; ! t.isEmpty(); ++i) + if (t.getAndAdvance() == character) + last = i; + + return last; +} + +int String::indexOfAnyOf (StringRef charactersToLookFor, const int startIndex, const bool ignoreCase) const noexcept +{ + CharPointerType t (text); + + for (int i = 0; ! t.isEmpty(); ++i) + { + if (i >= startIndex) + { + if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) + return i; + } + else + { + ++t; + } + } + + return -1; +} + +int String::indexOf (StringRef other) const noexcept +{ + return other.isEmpty() ? 0 : text.indexOf (other.text); +} + +int String::indexOfIgnoreCase (StringRef other) const noexcept +{ + return other.isEmpty() ? 0 : CharacterFunctions::indexOfIgnoreCase (text, other.text); +} + +int String::indexOf (const int startIndex, StringRef other) const noexcept +{ + if (other.isEmpty()) + return -1; + + CharPointerType t (text); + + for (int i = startIndex; --i >= 0;) + { + if (t.isEmpty()) + return -1; + + ++t; + } + + int found = t.indexOf (other.text); + if (found >= 0) + found += startIndex; + return found; +} + +int String::indexOfIgnoreCase (const int startIndex, StringRef other) const noexcept +{ + if (other.isEmpty()) + return -1; + + CharPointerType t (text); + + for (int i = startIndex; --i >= 0;) + { + if (t.isEmpty()) + return -1; + + ++t; + } + + int found = CharacterFunctions::indexOfIgnoreCase (t, other.text); + if (found >= 0) + found += startIndex; + return found; +} + +int String::lastIndexOf (StringRef other) const noexcept +{ + if (other.isNotEmpty()) + { + const int len = other.length(); + int i = length() - len; + + if (i >= 0) + { + for (CharPointerType n (text + i); i >= 0; --i) + { + if (n.compareUpTo (other.text, len) == 0) + return i; + + --n; + } + } + } + + return -1; +} + +int String::lastIndexOfIgnoreCase (StringRef other) const noexcept +{ + if (other.isNotEmpty()) + { + const int len = other.length(); + int i = length() - len; + + if (i >= 0) + { + for (CharPointerType n (text + i); i >= 0; --i) + { + if (n.compareIgnoreCaseUpTo (other.text, len) == 0) + return i; + + --n; + } + } + } + + return -1; +} + +int String::lastIndexOfAnyOf (StringRef charactersToLookFor, const bool ignoreCase) const noexcept +{ + CharPointerType t (text); + int last = -1; + + for (int i = 0; ! t.isEmpty(); ++i) + if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) + last = i; + + return last; +} + +bool String::contains (StringRef other) const noexcept +{ + return indexOf (other) >= 0; +} + +bool String::containsChar (const juce_wchar character) const noexcept +{ + return text.indexOf (character) >= 0; +} + +bool String::containsIgnoreCase (StringRef t) const noexcept +{ + return indexOfIgnoreCase (t) >= 0; +} + +int String::indexOfWholeWord (StringRef word) const noexcept +{ + if (word.isNotEmpty()) + { + CharPointerType t (text); + const int wordLen = word.length(); + const int end = (int) t.length() - wordLen; + + for (int i = 0; i <= end; ++i) + { + if (t.compareUpTo (word.text, wordLen) == 0 + && (i == 0 || ! (t - 1).isLetterOrDigit()) + && ! (t + wordLen).isLetterOrDigit()) + return i; + + ++t; + } + } + + return -1; +} + +int String::indexOfWholeWordIgnoreCase (StringRef word) const noexcept +{ + if (word.isNotEmpty()) + { + CharPointerType t (text); + const int wordLen = word.length(); + const int end = (int) t.length() - wordLen; + + for (int i = 0; i <= end; ++i) + { + if (t.compareIgnoreCaseUpTo (word.text, wordLen) == 0 + && (i == 0 || ! (t - 1).isLetterOrDigit()) + && ! (t + wordLen).isLetterOrDigit()) + return i; + + ++t; + } + } + + return -1; +} + +bool String::containsWholeWord (StringRef wordToLookFor) const noexcept +{ + return indexOfWholeWord (wordToLookFor) >= 0; +} + +bool String::containsWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept +{ + return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0; +} + +//============================================================================== +template +struct WildCardMatcher +{ + static bool matches (CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept + { + for (;;) + { + const juce_wchar wc = wildcard.getAndAdvance(); + + if (wc == '*') + return wildcard.isEmpty() || matchesAnywhere (wildcard, test, ignoreCase); + + if (! characterMatches (wc, test.getAndAdvance(), ignoreCase)) + return false; + + if (wc == 0) + return true; + } + } + + static bool characterMatches (const juce_wchar wc, const juce_wchar tc, const bool ignoreCase) noexcept + { + return (wc == tc) || (wc == '?' && tc != 0) + || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (tc)); + } + + static bool matchesAnywhere (const CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept + { + for (; ! test.isEmpty(); ++test) + if (matches (wildcard, test, ignoreCase)) + return true; + + return false; + } +}; + +bool String::matchesWildcard (StringRef wildcard, const bool ignoreCase) const noexcept +{ + return WildCardMatcher::matches (wildcard.text, text, ignoreCase); +} + +//============================================================================== +String String::repeatedString (StringRef stringToRepeat, int numberOfTimesToRepeat) +{ + if (numberOfTimesToRepeat <= 0) + return String(); + + String result (PreallocationBytes (findByteOffsetOfEnd (stringToRepeat) * (size_t) numberOfTimesToRepeat)); + CharPointerType n (result.text); + + while (--numberOfTimesToRepeat >= 0) + n.writeAll (stringToRepeat.text); + + return result; +} + +String String::paddedLeft (const juce_wchar padCharacter, int minimumLength) const +{ + jassert (padCharacter != 0); + + int extraChars = minimumLength; + CharPointerType end (text); + + while (! end.isEmpty()) + { + --extraChars; + ++end; + } + + if (extraChars <= 0 || padCharacter == 0) + return *this; + + const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress()); + String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); + CharPointerType n (result.text); + + while (--extraChars >= 0) + n.write (padCharacter); + + n.writeAll (text); + return result; +} + +String String::paddedRight (const juce_wchar padCharacter, int minimumLength) const +{ + jassert (padCharacter != 0); + + int extraChars = minimumLength; + CharPointerType end (text); + + while (! end.isEmpty()) + { + --extraChars; + ++end; + } + + if (extraChars <= 0 || padCharacter == 0) + return *this; + + const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress()); + String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); + CharPointerType n (result.text); + + n.writeAll (text); + + while (--extraChars >= 0) + n.write (padCharacter); + + n.writeNull(); + return result; +} + +//============================================================================== +String String::replaceSection (int index, int numCharsToReplace, StringRef stringToInsert) const +{ + if (index < 0) + { + // a negative index to replace from? + jassertfalse; + index = 0; + } + + if (numCharsToReplace < 0) + { + // replacing a negative number of characters? + numCharsToReplace = 0; + jassertfalse; + } + + CharPointerType insertPoint (text); + + for (int i = 0; i < index; ++i) + { + if (insertPoint.isEmpty()) + { + // replacing beyond the end of the string? + jassertfalse; + return *this + stringToInsert; + } + + ++insertPoint; + } + + CharPointerType startOfRemainder (insertPoint); + + for (int i = 0; i < numCharsToReplace && ! startOfRemainder.isEmpty(); ++i) + ++startOfRemainder; + + if (insertPoint == text && startOfRemainder.isEmpty()) + return stringToInsert.text; + + const size_t initialBytes = (size_t) (((char*) insertPoint.getAddress()) - (char*) text.getAddress()); + const size_t newStringBytes = findByteOffsetOfEnd (stringToInsert); + const size_t remainderBytes = (size_t) (((char*) startOfRemainder.findTerminatingNull().getAddress()) - (char*) startOfRemainder.getAddress()); + + const size_t newTotalBytes = initialBytes + newStringBytes + remainderBytes; + if (newTotalBytes <= 0) + return String(); + + String result (PreallocationBytes ((size_t) newTotalBytes)); + + char* dest = (char*) result.text.getAddress(); + memcpy (dest, text.getAddress(), initialBytes); + dest += initialBytes; + memcpy (dest, stringToInsert.text.getAddress(), newStringBytes); + dest += newStringBytes; + memcpy (dest, startOfRemainder.getAddress(), remainderBytes); + dest += remainderBytes; + CharPointerType ((CharPointerType::CharType*) dest).writeNull(); + + return result; +} + +String String::replace (StringRef stringToReplace, StringRef stringToInsert, const bool ignoreCase) const +{ + const int stringToReplaceLen = stringToReplace.length(); + const int stringToInsertLen = stringToInsert.length(); + + int i = 0; + String result (*this); + + while ((i = (ignoreCase ? result.indexOfIgnoreCase (i, stringToReplace) + : result.indexOf (i, stringToReplace))) >= 0) + { + result = result.replaceSection (i, stringToReplaceLen, stringToInsert); + i += stringToInsertLen; + } + + return result; +} + +class StringCreationHelper +{ +public: + StringCreationHelper (const size_t initialBytes) + : source (nullptr), dest (nullptr), allocatedBytes (initialBytes), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + StringCreationHelper (const String::CharPointerType s) + : source (s), dest (nullptr), allocatedBytes (StringHolder::getAllocatedNumBytes (s)), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + void write (juce_wchar c) + { + bytesWritten += String::CharPointerType::getBytesRequiredFor (c); + + if (bytesWritten > allocatedBytes) + { + allocatedBytes += jmax ((size_t) 8, allocatedBytes / 16); + const size_t destOffset = (size_t) (((char*) dest.getAddress()) - (char*) result.getCharPointer().getAddress()); + result.preallocateBytes (allocatedBytes); + dest = addBytesToPointer (result.getCharPointer().getAddress(), (int) destOffset); + } + + dest.write (c); + } + + String result; + String::CharPointerType source; + +private: + String::CharPointerType dest; + size_t allocatedBytes, bytesWritten; +}; + +String String::replaceCharacter (const juce_wchar charToReplace, const juce_wchar charToInsert) const +{ + if (! containsChar (charToReplace)) + return *this; + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + if (c == charToReplace) + c = charToInsert; + + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::replaceCharacters (StringRef charactersToReplace, StringRef charactersToInsertInstead) const +{ + // Each character in the first string must have a matching one in the + // second, so the two strings must be the same length. + jassert (charactersToReplace.length() == charactersToInsertInstead.length()); + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + const int index = charactersToReplace.text.indexOf (c); + if (index >= 0) + c = charactersToInsertInstead [index]; + + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +//============================================================================== +bool String::startsWith (StringRef other) const noexcept +{ + return text.compareUpTo (other.text, other.length()) == 0; +} + +bool String::startsWithIgnoreCase (StringRef other) const noexcept +{ + return text.compareIgnoreCaseUpTo (other.text, other.length()) == 0; +} + +bool String::startsWithChar (const juce_wchar character) const noexcept +{ + jassert (character != 0); // strings can't contain a null character! + + return *text == character; +} + +bool String::endsWithChar (const juce_wchar character) const noexcept +{ + jassert (character != 0); // strings can't contain a null character! + + if (text.isEmpty()) + return false; + + CharPointerType t (text.findTerminatingNull()); + return *--t == character; +} + +bool String::endsWith (StringRef other) const noexcept +{ + CharPointerType end (text.findTerminatingNull()); + CharPointerType otherEnd (other.text.findTerminatingNull()); + + while (end > text && otherEnd > other.text) + { + --end; + --otherEnd; + + if (*end != *otherEnd) + return false; + } + + return otherEnd == other.text; +} + +bool String::endsWithIgnoreCase (StringRef other) const noexcept +{ + CharPointerType end (text.findTerminatingNull()); + CharPointerType otherEnd (other.text.findTerminatingNull()); + + while (end > text && otherEnd > other.text) + { + --end; + --otherEnd; + + if (end.toLowerCase() != otherEnd.toLowerCase()) + return false; + } + + return otherEnd == other.text; +} + +//============================================================================== +String String::toUpperCase() const +{ + StringCreationHelper builder (text); + + for (;;) + { + const juce_wchar c = builder.source.toUpperCase(); + builder.write (c); + + if (c == 0) + break; + + ++(builder.source); + } + + return builder.result; +} + +String String::toLowerCase() const +{ + StringCreationHelper builder (text); + + for (;;) + { + const juce_wchar c = builder.source.toLowerCase(); + builder.write (c); + + if (c == 0) + break; + + ++(builder.source); + } + + return builder.result; +} + +//============================================================================== +juce_wchar String::getLastCharacter() const noexcept +{ + return isEmpty() ? juce_wchar() : text [length() - 1]; +} + +String String::substring (int start, const int end) const +{ + if (start < 0) + start = 0; + + if (end <= start) + return String(); + + int i = 0; + CharPointerType t1 (text); + + while (i < start) + { + if (t1.isEmpty()) + return String(); + + ++i; + ++t1; + } + + CharPointerType t2 (t1); + while (i < end) + { + if (t2.isEmpty()) + { + if (start == 0) + return *this; + + break; + } + + ++i; + ++t2; + } + + return String (t1, t2); +} + +String String::substring (int start) const +{ + if (start <= 0) + return *this; + + CharPointerType t (text); + + while (--start >= 0) + { + if (t.isEmpty()) + return String(); + + ++t; + } + + return String (t); +} + +String String::dropLastCharacters (const int numberToDrop) const +{ + return String (text, (size_t) jmax (0, length() - numberToDrop)); +} + +String String::getLastCharacters (const int numCharacters) const +{ + return String (text + jmax (0, length() - jmax (0, numCharacters))); +} + +String String::fromFirstOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? indexOfIgnoreCase (sub) + : indexOf (sub); + if (i < 0) + return String(); + + return substring (includeSubString ? i : i + sub.length()); +} + +String String::fromLastOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) + : lastIndexOf (sub); + if (i < 0) + return *this; + + return substring (includeSubString ? i : i + sub.length()); +} + +String String::upToFirstOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? indexOfIgnoreCase (sub) + : indexOf (sub); + if (i < 0) + return *this; + + return substring (0, includeSubString ? i + sub.length() : i); +} + +String String::upToLastOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) + : lastIndexOf (sub); + if (i < 0) + return *this; + + return substring (0, includeSubString ? i + sub.length() : i); +} + +bool String::isQuotedString() const +{ + const juce_wchar trimmedStart = trimStart()[0]; + + return trimmedStart == '"' + || trimmedStart == '\''; +} + +String String::unquoted() const +{ + const int len = length(); + + if (len == 0) + return String(); + + const juce_wchar lastChar = text [len - 1]; + const int dropAtStart = (*text == '"' || *text == '\'') ? 1 : 0; + const int dropAtEnd = (lastChar == '"' || lastChar == '\'') ? 1 : 0; + + return substring (dropAtStart, len - dropAtEnd); +} + +//============================================================================== +static String::CharPointerType findTrimmedEnd (const String::CharPointerType start, + String::CharPointerType end) +{ + while (end > start) + { + if (! (--end).isWhitespace()) + { + ++end; + break; + } + } + + return end; +} + +String String::trim() const +{ + if (isNotEmpty()) + { + CharPointerType start (text.findEndOfWhitespace()); + + const CharPointerType end (start.findTerminatingNull()); + CharPointerType trimmedEnd (findTrimmedEnd (start, end)); + + if (trimmedEnd <= start) + return String(); + + if (text < start || trimmedEnd < end) + return String (start, trimmedEnd); + } + + return *this; +} + +String String::trimStart() const +{ + if (isNotEmpty()) + { + const CharPointerType t (text.findEndOfWhitespace()); + + if (t != text) + return String (t); + } + + return *this; +} + +String String::trimEnd() const +{ + if (isNotEmpty()) + { + const CharPointerType end (text.findTerminatingNull()); + CharPointerType trimmedEnd (findTrimmedEnd (text, end)); + + if (trimmedEnd < end) + return String (text, trimmedEnd); + } + + return *this; +} + +String String::trimCharactersAtStart (StringRef charactersToTrim) const +{ + CharPointerType t (text); + + while (charactersToTrim.text.indexOf (*t) >= 0) + ++t; + + return t == text ? *this : String (t); +} + +String String::trimCharactersAtEnd (StringRef charactersToTrim) const +{ + if (isNotEmpty()) + { + const CharPointerType end (text.findTerminatingNull()); + CharPointerType trimmedEnd (end); + + while (trimmedEnd > text) + { + if (charactersToTrim.text.indexOf (*--trimmedEnd) < 0) + { + ++trimmedEnd; + break; + } + } + + if (trimmedEnd < end) + return String (text, trimmedEnd); + } + + return *this; +} + +//============================================================================== +String String::retainCharacters (StringRef charactersToRetain) const +{ + if (isEmpty()) + return String(); + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + if (charactersToRetain.text.indexOf (c) >= 0) + builder.write (c); + + if (c == 0) + break; + } + + builder.write (0); + return builder.result; +} + +String String::removeCharacters (StringRef charactersToRemove) const +{ + if (isEmpty()) + return String(); + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + if (charactersToRemove.text.indexOf (c) < 0) + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::initialSectionContainingOnly (StringRef permittedCharacters) const +{ + for (CharPointerType t (text); ! t.isEmpty(); ++t) + if (permittedCharacters.text.indexOf (*t) < 0) + return String (text, t); + + return *this; +} + +String String::initialSectionNotContaining (StringRef charactersToStopAt) const +{ + for (CharPointerType t (text); ! t.isEmpty(); ++t) + if (charactersToStopAt.text.indexOf (*t) >= 0) + return String (text, t); + + return *this; +} + +bool String::containsOnly (StringRef chars) const noexcept +{ + for (CharPointerType t (text); ! t.isEmpty();) + if (chars.text.indexOf (t.getAndAdvance()) < 0) + return false; + + return true; +} + +bool String::containsAnyOf (StringRef chars) const noexcept +{ + for (CharPointerType t (text); ! t.isEmpty();) + if (chars.text.indexOf (t.getAndAdvance()) >= 0) + return true; + + return false; +} + +bool String::containsNonWhitespaceChars() const noexcept +{ + for (CharPointerType t (text); ! t.isEmpty(); ++t) + if (! t.isWhitespace()) + return true; + + return false; +} + +// Note! The format parameter here MUST NOT be a reference, otherwise MS's va_start macro fails to work (but still compiles). +String String::formatted (const String pf, ... ) +{ + size_t bufferSize = 256; + + for (;;) + { + va_list args; + va_start (args, pf); + + HeapBlock temp (bufferSize); + const int num = vsnprintf (temp.getData(), bufferSize - 1, pf.toRawUTF8(), args); + + va_end (args); + + if (num > 0) + return String (temp); + + bufferSize += 256; + + if (num == 0 || bufferSize > 65536) // the upper limit is a sanity check to avoid situations where vsnprintf repeatedly + break; // returns -1 because of an error rather than because it needs more space. + } + + return String(); +} + +//============================================================================== +int String::getIntValue() const noexcept { return text.getIntValue32(); } +int64 String::getLargeIntValue() const noexcept { return text.getIntValue64(); } +float String::getFloatValue() const noexcept { return (float) getDoubleValue(); } +double String::getDoubleValue() const noexcept { return text.getDoubleValue(); } + +int String::getTrailingIntValue() const noexcept +{ + int n = 0; + int mult = 1; + CharPointerType t (text.findTerminatingNull()); + + while (--t >= text) + { + if (! t.isDigit()) + { + if (*t == '-') + n = -n; + + break; + } + + n += mult * (*t - '0'); + mult *= 10; + } + + return n; +} + +static const char hexDigits[] = "0123456789abcdef"; + +template +static String hexToString (Type v) +{ + String::CharPointerType::CharType buffer[32]; + String::CharPointerType::CharType* const end = buffer + numElementsInArray (buffer) - 1; + String::CharPointerType::CharType* t = end; + *t = 0; + + do + { + *--t = hexDigits [(int) (v & 15)]; + v >>= 4; + + } while (v != 0); + + return String (String::CharPointerType (t), + String::CharPointerType (end)); +} + +String String::toHexString (int number) { return hexToString ((unsigned int) number); } +String String::toHexString (int64 number) { return hexToString ((uint64) number); } +String String::toHexString (short number) { return toHexString ((int) (unsigned short) number); } + +String String::toHexString (const void* const d, const int size, const int groupSize) +{ + if (size <= 0) + return String(); + + int numChars = (size * 2) + 2; + if (groupSize > 0) + numChars += size / groupSize; + + String s (PreallocationBytes (sizeof (CharPointerType::CharType) * (size_t) numChars)); + + const unsigned char* data = static_cast (d); + CharPointerType dest (s.text); + + for (int i = 0; i < size; ++i) + { + const unsigned char nextByte = *data++; + dest.write ((juce_wchar) hexDigits [nextByte >> 4]); + dest.write ((juce_wchar) hexDigits [nextByte & 0xf]); + + if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) + dest.write ((juce_wchar) ' '); + } + + dest.writeNull(); + return s; +} + +int String::getHexValue32() const noexcept { return CharacterFunctions::HexParser ::parse (text); } +int64 String::getHexValue64() const noexcept { return CharacterFunctions::HexParser::parse (text); } + +//============================================================================== +static const juce_wchar emptyChar = 0; + +template +struct StringEncodingConverter +{ + static CharPointerType_Dest convert (const String& s) + { + String& source = const_cast (s); + + typedef typename CharPointerType_Dest::CharType DestChar; + + if (source.isEmpty()) + return CharPointerType_Dest (reinterpret_cast (&emptyChar)); + + CharPointerType_Src text (source.getCharPointer()); + const size_t extraBytesNeeded = CharPointerType_Dest::getBytesRequiredFor (text) + sizeof (typename CharPointerType_Dest::CharType); + const size_t endOffset = (text.sizeInBytes() + 3) & ~3u; // the new string must be word-aligned or many Windows + // functions will fail to read it correctly! + source.preallocateBytes (endOffset + extraBytesNeeded); + text = source.getCharPointer(); + + void* const newSpace = addBytesToPointer (text.getAddress(), (int) endOffset); + const CharPointerType_Dest extraSpace (static_cast (newSpace)); + + #if JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) + const size_t bytesToClear = (size_t) jmin ((int) extraBytesNeeded, 4); + zeromem (addBytesToPointer (newSpace, extraBytesNeeded - bytesToClear), bytesToClear); + #endif + + CharPointerType_Dest (extraSpace).writeAll (text); + return extraSpace; + } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 ((CharPointer_UTF8::CharType*) source.getCharPointer().getAddress()); } +}; + +CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter::convert (*this); } + +const char* String::toRawUTF8() const +{ + return toUTF8().getAddress(); +} + +std::string String::toStdString() const +{ + return std::string (toRawUTF8()); +} + +//============================================================================== +template +struct StringCopier +{ + static size_t copyToBuffer (const CharPointerType_Src source, typename CharPointerType_Dest::CharType* const buffer, const size_t maxBufferSizeBytes) + { + jassert (((ssize_t) maxBufferSizeBytes) >= 0); // keep this value positive! + + if (buffer == nullptr) + return CharPointerType_Dest::getBytesRequiredFor (source) + sizeof (typename CharPointerType_Dest::CharType); + + return CharPointerType_Dest (buffer).writeWithDestByteLimit (source, maxBufferSizeBytes); + } +}; + +size_t String::copyToUTF8 (CharPointer_UTF8::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +//============================================================================== +size_t String::getNumBytesAsUTF8() const noexcept +{ + return CharPointer_UTF8::getBytesRequiredFor (text); +} + +String String::fromUTF8 (const char* const buffer, int bufferSizeBytes) +{ + if (buffer != nullptr) + { + if (bufferSizeBytes < 0) + return String (CharPointer_UTF8 (buffer)); + + if (bufferSizeBytes > 0) + { + jassert (CharPointer_UTF8::isValidString (buffer, bufferSizeBytes)); + return String (CharPointer_UTF8 (buffer), CharPointer_UTF8 (buffer + bufferSizeBytes)); + } + } + + return String(); +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +StringRef::StringRef() noexcept : text ((const String::CharPointerType::CharType*) "\0\0\0") +{ +} + +StringRef::StringRef (const char* stringLiteral) noexcept + : text (stringLiteral) +{ + jassert (stringLiteral != nullptr); // This must be a valid string literal, not a null pointer!! + + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the StringRef class - so for example if your source data is actually UTF-8, + you'd call StringRef (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + */ + jassert (CharPointer_UTF8::isValidString (stringLiteral, std::numeric_limits::max())); +} + +StringRef::StringRef (String::CharPointerType stringLiteral) noexcept : text (stringLiteral) +{ + jassert (stringLiteral.getAddress() != nullptr); // This must be a valid string literal, not a null pointer!! +} + +StringRef::StringRef (const String& string) noexcept : text (string.getCharPointer()) {} + + +//============================================================================== diff --git a/source/modules/juce_audio_graph/text/juce_String.h b/source/modules/juce_audio_graph/text/juce_String.h new file mode 100644 index 000000000..6452940b4 --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_String.h @@ -0,0 +1,1181 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_STRING_H_INCLUDED +#define JUCE_STRING_H_INCLUDED + + +//============================================================================== +/** + The JUCE String class! + + Using a reference-counted internal representation, these strings are fast + and efficient, and there are methods to do just about any operation you'll ever + dream of. + + @see StringArray, StringPairArray +*/ +class JUCE_API String +{ +public: + //============================================================================== + /** Creates an empty string. + @see empty + */ + String() noexcept; + + /** Creates a copy of another string. */ + String (const String& other) noexcept; + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + String (String&& other) noexcept; + #endif + + /** Creates a string from a zero-terminated ascii text string. + + The string passed-in must not contain any characters with a value above 127, because + these can't be converted to unicode without knowing the original encoding that was + used to create the string. If you attempt to pass-in values above 127, you'll get an + assertion. + + To create strings with extended characters from UTF-8, you should explicitly call + String (CharPointer_UTF8 ("my utf8 string..")). It's *highly* recommended that you + use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent unicode strings in a way that isn't dependent + on the compiler, source code editor and platform. + */ + String (const char* text); + + /** Creates a string from a string of 8-bit ascii characters. + + The string passed-in must not contain any characters with a value above 127, because + these can't be converted to unicode without knowing the original encoding that was + used to create the string. If you attempt to pass-in values above 127, you'll get an + assertion. + + To create strings with extended characters from UTF-8, you should explicitly call + String (CharPointer_UTF8 ("my utf8 string..")). It's *highly* recommended that you + use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent unicode strings in a way that isn't dependent + on the compiler, source code editor and platform. + + This will use up to the first maxChars characters of the string (or less if the string + is actually shorter). + */ + String (const char* text, size_t maxChars); + + //============================================================================== + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 text); + + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 text, size_t maxChars); + + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 start, const CharPointer_UTF8 end); + + //============================================================================== + /** Creates a string from a UTF-8 encoded std::string. */ + String (const std::string&); + + /** Creates a string from a StringRef */ + String (StringRef); + + //============================================================================== + /** Creates a string from a single character. */ + static String charToString (juce_wchar character); + + /** Destructor. */ + ~String() noexcept; + + /** This is the character encoding type used internally to store the string. + + By setting the value of JUCE_STRING_UTF_TYPE to 8, 16, or 32, you can change the + internal storage format of the String class. UTF-8 uses the least space (if your strings + contain few extended characters), but call operator[] involves iterating the string to find + the required index. UTF-32 provides instant random access to its characters, but uses 4 bytes + per character to store them. UTF-16 uses more space than UTF-8 and is also slow to index, + but is the native wchar_t format used in Windows. + + It doesn't matter too much which format you pick, because the toUTF8(), toUTF16() and + toUTF32() methods let you access the string's content in any of the other formats. + */ + typedef CharPointer_UTF8 CharPointerType; + + //============================================================================== + /** Generates a probably-unique 32-bit hashcode from this string. */ + int hashCode() const noexcept; + + /** Generates a probably-unique 64-bit hashcode from this string. */ + int64 hashCode64() const noexcept; + + /** Generates a probably-unique hashcode from this string. */ + size_t hash() const noexcept; + + /** Returns the number of characters in the string. */ + int length() const noexcept; + + //============================================================================== + // Assignment and concatenation operators.. + + /** Replaces this string's contents with another string. */ + String& operator= (const String& other) noexcept; + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + String& operator= (String&& other) noexcept; + #endif + + /** Appends another string at the end of this one. */ + String& operator+= (const String& stringToAppend); + /** Appends another string at the end of this one. */ + String& operator+= (const char* textToAppend); + /** Appends another string at the end of this one. */ + String& operator+= (StringRef textToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (int numberToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (long numberToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (int64 numberToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (uint64 numberToAppend); + /** Appends a character at the end of this string. */ + String& operator+= (char characterToAppend); + + /** Appends a string to the end of this one. + + @param textToAppend the string to add + @param maxCharsToTake the maximum number of characters to take from the string passed in + */ + void append (const String& textToAppend, size_t maxCharsToTake); + + /** Appends a string to the end of this one. + + @param startOfTextToAppend the start of the string to add. This must not be a nullptr + @param endOfTextToAppend the end of the string to add. This must not be a nullptr + */ + void appendCharPointer (const CharPointerType startOfTextToAppend, + const CharPointerType endOfTextToAppend); + + /** Appends a string to the end of this one. + + @param startOfTextToAppend the start of the string to add. This must not be a nullptr + @param endOfTextToAppend the end of the string to add. This must not be a nullptr + */ + template + void appendCharPointer (const CharPointer startOfTextToAppend, + const CharPointer endOfTextToAppend) + { + jassert (startOfTextToAppend.getAddress() != nullptr && endOfTextToAppend.getAddress() != nullptr); + + size_t extraBytesNeeded = 0, numChars = 1; + + for (CharPointer t (startOfTextToAppend); t != endOfTextToAppend && ! t.isEmpty(); ++numChars) + extraBytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + + preallocateBytes (byteOffsetOfNull + extraBytesNeeded); + CharPointerType (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)) + .writeWithCharLimit (startOfTextToAppend, (int) numChars); + } + } + + /** Appends a string to the end of this one. */ + void appendCharPointer (const CharPointerType textToAppend); + + /** Appends a string to the end of this one. + + @param textToAppend the string to add + @param maxCharsToTake the maximum number of characters to take from the string passed in + */ + template + void appendCharPointer (const CharPointer textToAppend, size_t maxCharsToTake) + { + if (textToAppend.getAddress() != nullptr) + { + size_t extraBytesNeeded = 0, numChars = 1; + + for (CharPointer t (textToAppend); numChars <= maxCharsToTake && ! t.isEmpty(); ++numChars) + extraBytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + + preallocateBytes (byteOffsetOfNull + extraBytesNeeded); + CharPointerType (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)) + .writeWithCharLimit (textToAppend, (int) numChars); + } + } + } + + /** Appends a string to the end of this one. */ + template + void appendCharPointer (const CharPointer textToAppend) + { + appendCharPointer (textToAppend, std::numeric_limits::max()); + } + + //============================================================================== + // Comparison methods.. + + /** Returns true if the string contains no characters. + Note that there's also an isNotEmpty() method to help write readable code. + @see containsNonWhitespaceChars() + */ + inline bool isEmpty() const noexcept { return text.isEmpty(); } + + /** Returns true if the string contains at least one character. + Note that there's also an isEmpty() method to help write readable code. + @see containsNonWhitespaceChars() + */ + inline bool isNotEmpty() const noexcept { return ! text.isEmpty(); } + + /** Resets this string to be empty. */ + void clear() noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const String& other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (StringRef other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const char* other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const String& other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const char* other) const noexcept; + + /** Case-insensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compareIgnoreCase (const String& other) const noexcept; + + /** Compares two strings, taking into account textual characteristics like numbers and spaces. + + This comparison is case-insensitive and can detect words and embedded numbers in the + strings, making it good for sorting human-readable lists of things like filenames. + + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compareNatural (StringRef other, bool isCaseSensitive = false) const noexcept; + + /** Tests whether the string begins with another string. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool startsWith (StringRef text) const noexcept; + + /** Tests whether the string begins with a particular character. + If the character is 0, this will always return false. + Uses a case-sensitive comparison. + */ + bool startsWithChar (juce_wchar character) const noexcept; + + /** Tests whether the string begins with another string. + If the parameter is an empty string, this will always return true. + Uses a case-insensitive comparison. + */ + bool startsWithIgnoreCase (StringRef text) const noexcept; + + /** Tests whether the string ends with another string. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool endsWith (StringRef text) const noexcept; + + /** Tests whether the string ends with a particular character. + If the character is 0, this will always return false. + Uses a case-sensitive comparison. + */ + bool endsWithChar (juce_wchar character) const noexcept; + + /** Tests whether the string ends with another string. + If the parameter is an empty string, this will always return true. + Uses a case-insensitive comparison. + */ + bool endsWithIgnoreCase (StringRef text) const noexcept; + + /** Tests whether the string contains another substring. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool contains (StringRef text) const noexcept; + + /** Tests whether the string contains a particular character. + Uses a case-sensitive comparison. + */ + bool containsChar (juce_wchar character) const noexcept; + + /** Tests whether the string contains another substring. + Uses a case-insensitive comparison. + */ + bool containsIgnoreCase (StringRef text) const noexcept; + + /** Tests whether the string contains another substring as a distinct word. + + @returns true if the string contains this word, surrounded by + non-alphanumeric characters + @see indexOfWholeWord, containsWholeWordIgnoreCase + */ + bool containsWholeWord (StringRef wordToLookFor) const noexcept; + + /** Tests whether the string contains another substring as a distinct word. + + @returns true if the string contains this word, surrounded by + non-alphanumeric characters + @see indexOfWholeWordIgnoreCase, containsWholeWord + */ + bool containsWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept; + + /** Finds an instance of another substring if it exists as a distinct word. + + @returns if the string contains this word, surrounded by non-alphanumeric characters, + then this will return the index of the start of the substring. If it isn't + found, then it will return -1 + @see indexOfWholeWordIgnoreCase, containsWholeWord + */ + int indexOfWholeWord (StringRef wordToLookFor) const noexcept; + + /** Finds an instance of another substring if it exists as a distinct word. + + @returns if the string contains this word, surrounded by non-alphanumeric characters, + then this will return the index of the start of the substring. If it isn't + found, then it will return -1 + @see indexOfWholeWord, containsWholeWordIgnoreCase + */ + int indexOfWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept; + + /** Looks for any of a set of characters in the string. + Uses a case-sensitive comparison. + + @returns true if the string contains any of the characters from + the string that is passed in. + */ + bool containsAnyOf (StringRef charactersItMightContain) const noexcept; + + /** Looks for a set of characters in the string. + Uses a case-sensitive comparison. + + @returns Returns false if any of the characters in this string do not occur in + the parameter string. If this string is empty, the return value will + always be true. + */ + bool containsOnly (StringRef charactersItMightContain) const noexcept; + + /** Returns true if this string contains any non-whitespace characters. + + This will return false if the string contains only whitespace characters, or + if it's empty. + + It is equivalent to calling "myString.trim().isNotEmpty()". + */ + bool containsNonWhitespaceChars() const noexcept; + + /** Returns true if the string matches this simple wildcard expression. + + So for example String ("abcdef").matchesWildcard ("*DEF", true) would return true. + + This isn't a full-blown regex though! The only wildcard characters supported + are "*" and "?". It's mainly intended for filename pattern matching. + */ + bool matchesWildcard (StringRef wildcard, bool ignoreCase) const noexcept; + + //============================================================================== + // Substring location methods.. + + /** Searches for a character inside this string. + Uses a case-sensitive comparison. + @returns the index of the first occurrence of the character in this + string, or -1 if it's not found. + */ + int indexOfChar (juce_wchar characterToLookFor) const noexcept; + + /** Searches for a character inside this string. + Uses a case-sensitive comparison. + @param startIndex the index from which the search should proceed + @param characterToLookFor the character to look for + @returns the index of the first occurrence of the character in this + string, or -1 if it's not found. + */ + int indexOfChar (int startIndex, juce_wchar characterToLookFor) const noexcept; + + /** Returns the index of the first character that matches one of the characters + passed-in to this method. + + This scans the string, beginning from the startIndex supplied, and if it finds + a character that appears in the string charactersToLookFor, it returns its index. + + If none of these characters are found, it returns -1. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see indexOfChar, lastIndexOfAnyOf + */ + int indexOfAnyOf (StringRef charactersToLookFor, + int startIndex = 0, + bool ignoreCase = false) const noexcept; + + /** Searches for a substring within this string. + Uses a case-sensitive comparison. + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return 0. + */ + int indexOf (StringRef textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-sensitive comparison. + @param startIndex the index from which the search should proceed + @param textToLookFor the string to search for + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return -1. + */ + int indexOf (int startIndex, StringRef textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-insensitive comparison. + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return 0. + */ + int indexOfIgnoreCase (StringRef textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-insensitive comparison. + @param startIndex the index from which the search should proceed + @param textToLookFor the string to search for + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return -1. + */ + int indexOfIgnoreCase (int startIndex, StringRef textToLookFor) const noexcept; + + /** Searches for a character inside this string (working backwards from the end of the string). + Uses a case-sensitive comparison. + @returns the index of the last occurrence of the character in this string, or -1 if it's not found. + */ + int lastIndexOfChar (juce_wchar character) const noexcept; + + /** Searches for a substring inside this string (working backwards from the end of the string). + Uses a case-sensitive comparison. + @returns the index of the start of the last occurrence of the substring within this string, + or -1 if it's not found. If textToLookFor is an empty string, this will always return -1. + */ + int lastIndexOf (StringRef textToLookFor) const noexcept; + + /** Searches for a substring inside this string (working backwards from the end of the string). + Uses a case-insensitive comparison. + @returns the index of the start of the last occurrence of the substring within this string, or -1 + if it's not found. If textToLookFor is an empty string, this will always return -1. + */ + int lastIndexOfIgnoreCase (StringRef textToLookFor) const noexcept; + + /** Returns the index of the last character in this string that matches one of the + characters passed-in to this method. + + This scans the string backwards, starting from its end, and if it finds + a character that appears in the string charactersToLookFor, it returns its index. + + If none of these characters are found, it returns -1. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see lastIndexOf, indexOfAnyOf + */ + int lastIndexOfAnyOf (StringRef charactersToLookFor, + bool ignoreCase = false) const noexcept; + + + //============================================================================== + // Substring extraction and manipulation methods.. + + /** Returns the character at this index in the string. + In a release build, no checks are made to see if the index is within a valid range, so be + careful! In a debug build, the index is checked and an assertion fires if it's out-of-range. + + Also beware that depending on the encoding format that the string is using internally, this + method may execute in either O(1) or O(n) time, so be careful when using it in your algorithms. + If you're scanning through a string to inspect its characters, you should never use this operator + for random access, it's far more efficient to call getCharPointer() to return a pointer, and + then to use that to iterate the string. + @see getCharPointer + */ + juce_wchar operator[] (int index) const noexcept; + + /** Returns the final character of the string. + If the string is empty this will return 0. + */ + juce_wchar getLastCharacter() const noexcept; + + //============================================================================== + /** Returns a subsection of the string. + + If the range specified is beyond the limits of the string, as much as + possible is returned. + + @param startIndex the index of the start of the substring needed + @param endIndex all characters from startIndex up to (but not including) + this index are returned + @see fromFirstOccurrenceOf, dropLastCharacters, getLastCharacters, upToFirstOccurrenceOf + */ + String substring (int startIndex, int endIndex) const; + + /** Returns a section of the string, starting from a given position. + + @param startIndex the first character to include. If this is beyond the end + of the string, an empty string is returned. If it is zero or + less, the whole string is returned. + @returns the substring from startIndex up to the end of the string + @see dropLastCharacters, getLastCharacters, fromFirstOccurrenceOf, upToFirstOccurrenceOf, fromLastOccurrenceOf + */ + String substring (int startIndex) const; + + /** Returns a version of this string with a number of characters removed + from the end. + + @param numberToDrop the number of characters to drop from the end of the + string. If this is greater than the length of the string, + an empty string will be returned. If zero or less, the + original string will be returned. + @see substring, fromFirstOccurrenceOf, upToFirstOccurrenceOf, fromLastOccurrenceOf, getLastCharacter + */ + String dropLastCharacters (int numberToDrop) const; + + /** Returns a number of characters from the end of the string. + + This returns the last numCharacters characters from the end of the string. If the + string is shorter than numCharacters, the whole string is returned. + + @see substring, dropLastCharacters, getLastCharacter + */ + String getLastCharacters (int numCharacters) const; + + //============================================================================== + /** Returns a section of the string starting from a given substring. + + This will search for the first occurrence of the given substring, and + return the section of the string starting from the point where this is + found (optionally not including the substring itself). + + e.g. for the string "123456", fromFirstOccurrenceOf ("34", true) would return "3456", and + fromFirstOccurrenceOf ("34", false) would return "56". + + If the substring isn't found, the method will return an empty string. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see upToFirstOccurrenceOf, fromLastOccurrenceOf + */ + String fromFirstOccurrenceOf (StringRef substringToStartFrom, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns a section of the string starting from the last occurrence of a given substring. + + Similar to fromFirstOccurrenceOf(), but using the last occurrence of the substring, and + unlike fromFirstOccurrenceOf(), if the substring isn't found, this method will + return the whole of the original string. + + @see fromFirstOccurrenceOf, upToLastOccurrenceOf + */ + String fromLastOccurrenceOf (StringRef substringToFind, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns the start of this string, up to the first occurrence of a substring. + + This will search for the first occurrence of a given substring, and then + return a copy of the string, up to the position of this substring, + optionally including or excluding the substring itself in the result. + + e.g. for the string "123456", upTo ("34", false) would return "12", and + upTo ("34", true) would return "1234". + + If the substring isn't found, this will return the whole of the original string. + + @see upToLastOccurrenceOf, fromFirstOccurrenceOf + */ + String upToFirstOccurrenceOf (StringRef substringToEndWith, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns the start of this string, up to the last occurrence of a substring. + + Similar to upToFirstOccurrenceOf(), but this finds the last occurrence rather than the first. + If the substring isn't found, this will return the whole of the original string. + + @see upToFirstOccurrenceOf, fromFirstOccurrenceOf + */ + String upToLastOccurrenceOf (StringRef substringToFind, + bool includeSubStringInResult, + bool ignoreCase) const; + + //============================================================================== + /** Returns a copy of this string with any whitespace characters removed from the start and end. */ + String trim() const; + + /** Returns a copy of this string with any whitespace characters removed from the start. */ + String trimStart() const; + + /** Returns a copy of this string with any whitespace characters removed from the end. */ + String trimEnd() const; + + /** Returns a copy of this string, having removed a specified set of characters from its start. + Characters are removed from the start of the string until it finds one that is not in the + specified set, and then it stops. + @param charactersToTrim the set of characters to remove. + @see trim, trimStart, trimCharactersAtEnd + */ + String trimCharactersAtStart (StringRef charactersToTrim) const; + + /** Returns a copy of this string, having removed a specified set of characters from its end. + Characters are removed from the end of the string until it finds one that is not in the + specified set, and then it stops. + @param charactersToTrim the set of characters to remove. + @see trim, trimEnd, trimCharactersAtStart + */ + String trimCharactersAtEnd (StringRef charactersToTrim) const; + + //============================================================================== + /** Returns an upper-case version of this string. */ + String toUpperCase() const; + + /** Returns an lower-case version of this string. */ + String toLowerCase() const; + + //============================================================================== + /** Replaces a sub-section of the string with another string. + + This will return a copy of this string, with a set of characters + from startIndex to startIndex + numCharsToReplace removed, and with + a new string inserted in their place. + + Note that this is a const method, and won't alter the string itself. + + @param startIndex the first character to remove. If this is beyond the bounds of the string, + it will be constrained to a valid range. + @param numCharactersToReplace the number of characters to remove. If zero or less, no + characters will be taken out. + @param stringToInsert the new string to insert at startIndex after the characters have been + removed. + */ + String replaceSection (int startIndex, + int numCharactersToReplace, + StringRef stringToInsert) const; + + /** Replaces all occurrences of a substring with another string. + + Returns a copy of this string, with any occurrences of stringToReplace + swapped for stringToInsertInstead. + + Note that this is a const method, and won't alter the string itself. + */ + String replace (StringRef stringToReplace, + StringRef stringToInsertInstead, + bool ignoreCase = false) const; + + /** Returns a string with all occurrences of a character replaced with a different one. */ + String replaceCharacter (juce_wchar characterToReplace, + juce_wchar characterToInsertInstead) const; + + /** Replaces a set of characters with another set. + + Returns a string in which each character from charactersToReplace has been replaced + by the character at the equivalent position in newCharacters (so the two strings + passed in must be the same length). + + e.g. replaceCharacters ("abc", "def") replaces 'a' with 'd', 'b' with 'e', etc. + + Note that this is a const method, and won't affect the string itself. + */ + String replaceCharacters (StringRef charactersToReplace, + StringRef charactersToInsertInstead) const; + + /** Returns a version of this string that only retains a fixed set of characters. + + This will return a copy of this string, omitting any characters which are not + found in the string passed-in. + + e.g. for "1122334455", retainCharacters ("432") would return "223344" + + Note that this is a const method, and won't alter the string itself. + */ + String retainCharacters (StringRef charactersToRetain) const; + + /** Returns a version of this string with a set of characters removed. + + This will return a copy of this string, omitting any characters which are + found in the string passed-in. + + e.g. for "1122334455", removeCharacters ("432") would return "1155" + + Note that this is a const method, and won't alter the string itself. + */ + String removeCharacters (StringRef charactersToRemove) const; + + /** Returns a section from the start of the string that only contains a certain set of characters. + + This returns the leftmost section of the string, up to (and not including) the + first character that doesn't appear in the string passed in. + */ + String initialSectionContainingOnly (StringRef permittedCharacters) const; + + /** Returns a section from the start of the string that only contains a certain set of characters. + + This returns the leftmost section of the string, up to (and not including) the + first character that occurs in the string passed in. (If none of the specified + characters are found in the string, the return value will just be the original string). + */ + String initialSectionNotContaining (StringRef charactersToStopAt) const; + + //============================================================================== + /** Checks whether the string might be in quotation marks. + + @returns true if the string begins with a quote character (either a double or single quote). + It is also true if there is whitespace before the quote, but it doesn't check the end of the string. + @see unquoted, quoted + */ + bool isQuotedString() const; + + /** Removes quotation marks from around the string, (if there are any). + + Returns a copy of this string with any quotes removed from its ends. Quotes that aren't + at the ends of the string are not affected. If there aren't any quotes, the original string + is returned. + + Note that this is a const method, and won't alter the string itself. + + @see isQuotedString, quoted + */ + String unquoted() const; + + //============================================================================== + /** Creates a string which is a version of a string repeated and joined together. + + @param stringToRepeat the string to repeat + @param numberOfTimesToRepeat how many times to repeat it + */ + static String repeatedString (StringRef stringToRepeat, + int numberOfTimesToRepeat); + + /** Returns a copy of this string with the specified character repeatedly added to its + beginning until the total length is at least the minimum length specified. + */ + String paddedLeft (juce_wchar padCharacter, int minimumLength) const; + + /** Returns a copy of this string with the specified character repeatedly added to its + end until the total length is at least the minimum length specified. + */ + String paddedRight (juce_wchar padCharacter, int minimumLength) const; + + /** Creates a String from a printf-style parameter list. + + I don't like this method. I don't use it myself, and I recommend avoiding it and + using the operator<< methods or pretty much anything else instead. It's only provided + here because of the popular unrest that was stirred-up when I tried to remove it... + + If you're really determined to use it, at least make sure that you never, ever, + pass any String objects to it as parameters. And bear in mind that internally, depending + on the platform, it may be using wchar_t or char character types, so that even string + literals can't be safely used as parameters if you're writing portable code. + */ + static String formatted (const String formatString, ... ); + + //============================================================================== + // Numeric conversions.. + + /** Creates a string containing this signed 32-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (int decimalInteger); + + /** Creates a string containing this unsigned 32-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned int decimalInteger); + + /** Creates a string containing this signed 16-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (short decimalInteger); + + /** Creates a string containing this unsigned 16-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned short decimalInteger); + + /** Creates a string containing this signed 64-bit integer as a decimal number. + @see getLargeIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (int64 largeIntegerValue); + + /** Creates a string containing this unsigned 64-bit integer as a decimal number. + @see getLargeIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (uint64 largeIntegerValue); + + /** Creates a string containing this signed long integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (long decimalInteger); + + /** Creates a string containing this unsigned long integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned long decimalInteger); + + /** Creates a string representing this floating-point number. + @param floatValue the value to convert to a string + @see getDoubleValue, getIntValue + */ + explicit String (float floatValue); + + /** Creates a string representing this floating-point number. + @param doubleValue the value to convert to a string + @see getFloatValue, getIntValue + */ + explicit String (double doubleValue); + + /** Creates a string representing this floating-point number. + @param floatValue the value to convert to a string + @param numberOfDecimalPlaces if this is > 0, it will format the number using that many + decimal places, and will not use exponent notation. If 0 or + less, it will use exponent notation if necessary. + @see getDoubleValue, getIntValue + */ + String (float floatValue, int numberOfDecimalPlaces); + + /** Creates a string representing this floating-point number. + @param doubleValue the value to convert to a string + @param numberOfDecimalPlaces if this is > 0, it will format the number using that many + decimal places, and will not use exponent notation. If 0 or + less, it will use exponent notation if necessary. + @see getFloatValue, getIntValue + */ + String (double doubleValue, int numberOfDecimalPlaces); + + /** Reads the value of the string as a decimal number (up to 32 bits in size). + + @returns the value of the string as a 32 bit signed base-10 integer. + @see getTrailingIntValue, getHexValue32, getHexValue64 + */ + int getIntValue() const noexcept; + + /** Reads the value of the string as a decimal number (up to 64 bits in size). + @returns the value of the string as a 64 bit signed base-10 integer. + */ + int64 getLargeIntValue() const noexcept; + + /** Parses a decimal number from the end of the string. + + This will look for a value at the end of the string. + e.g. for "321 xyz654" it will return 654; for "2 3 4" it'll return 4. + + Negative numbers are not handled, so "xyz-5" returns 5. + + @see getIntValue + */ + int getTrailingIntValue() const noexcept; + + /** Parses this string as a floating point number. + + @returns the value of the string as a 32-bit floating point value. + @see getDoubleValue + */ + float getFloatValue() const noexcept; + + /** Parses this string as a floating point number. + + @returns the value of the string as a 64-bit floating point value. + @see getFloatValue + */ + double getDoubleValue() const noexcept; + + /** Parses the string as a hexadecimal number. + + Non-hexadecimal characters in the string are ignored. + + If the string contains too many characters, then the lowest significant + digits are returned, e.g. "ffff12345678" would produce 0x12345678. + + @returns a 32-bit number which is the value of the string in hex. + */ + int getHexValue32() const noexcept; + + /** Parses the string as a hexadecimal number. + + Non-hexadecimal characters in the string are ignored. + + If the string contains too many characters, then the lowest significant + digits are returned, e.g. "ffff1234567812345678" would produce 0x1234567812345678. + + @returns a 64-bit number which is the value of the string in hex. + */ + int64 getHexValue64() const noexcept; + + /** Creates a string representing this 32-bit value in hexadecimal. */ + static String toHexString (int number); + + /** Creates a string representing this 64-bit value in hexadecimal. */ + static String toHexString (int64 number); + + /** Creates a string representing this 16-bit value in hexadecimal. */ + static String toHexString (short number); + + /** Creates a string containing a hex dump of a block of binary data. + + @param data the binary data to use as input + @param size how many bytes of data to use + @param groupSize how many bytes are grouped together before inserting a + space into the output. e.g. group size 0 has no spaces, + group size 1 looks like: "be a1 c2 ff", group size 2 looks + like "bea1 c2ff". + */ + static String toHexString (const void* data, int size, int groupSize = 1); + + //============================================================================== + /** Returns the character pointer currently being used to store this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + */ + inline CharPointerType getCharPointer() const noexcept { return text; } + + /** Returns a pointer to a UTF-8 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @see toRawUTF8, getCharPointer, toUTF16, toUTF32 + */ + CharPointer_UTF8 toUTF8() const; + + /** Returns a pointer to a UTF-8 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @see getCharPointer, toUTF8, toUTF16, toUTF32 + */ + const char* toRawUTF8() const; + + /** */ + std::string toStdString() const; + + //============================================================================== + /** Creates a String from a UTF-8 encoded buffer. + If the size is < 0, it'll keep reading until it hits a zero. + */ + static String fromUTF8 (const char* utf8buffer, int bufferSizeBytes = -1); + + /** Returns the number of bytes required to represent this string as UTF8. + The number returned does NOT include the trailing zero. + @see toUTF8, copyToUTF8 + */ + size_t getNumBytesAsUTF8() const noexcept; + + //============================================================================== + /** Copies the string to a buffer as UTF-8 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF8::writeWithDestByteLimit + */ + size_t copyToUTF8 (CharPointer_UTF8::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + //============================================================================== + /** Increases the string's internally allocated storage. + + Although the string's contents won't be affected by this call, it will + increase the amount of memory allocated internally for the string to grow into. + + If you're about to make a large number of calls to methods such + as += or <<, it's more efficient to preallocate enough extra space + beforehand, so that these methods won't have to keep resizing the string + to append the extra characters. + + @param numBytesNeeded the number of bytes to allocate storage for. If this + value is less than the currently allocated size, it will + have no effect. + */ + void preallocateBytes (size_t numBytesNeeded); + + /** Swaps the contents of this string with another one. + This is a very fast operation, as no allocation or copying needs to be done. + */ + void swapWith (String& other) noexcept; + + //============================================================================== + #if JUCE_MAC || JUCE_IOS || DOXYGEN + /** OSX ONLY - Creates a String from an OSX CFString. */ + static String fromCFString (CFStringRef cfString); + + /** OSX ONLY - Converts this string to a CFString. + Remember that you must use CFRelease() to free the returned string when you're + finished with it. + */ + CFStringRef toCFString() const; + + /** OSX ONLY - Returns a copy of this string in which any decomposed unicode characters have + been converted to their precomposed equivalents. */ + String convertToPrecomposedUnicode() const; + #endif + + /** Returns the number of String objects which are currently sharing the same internal + data as this one. + */ + int getReferenceCount() const noexcept; + +private: + //============================================================================== + CharPointerType text; + + //============================================================================== + struct PreallocationBytes + { + explicit PreallocationBytes (size_t) noexcept; + size_t numBytes; + }; + + explicit String (const PreallocationBytes&); // This constructor preallocates a certain amount of memory + size_t getByteOffsetOfEnd() const noexcept; +}; + +//============================================================================== +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (const char* string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (char string1, const String& string2); + +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const char* string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, char characterToAppend); + +//============================================================================== +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, char characterToAppend); + +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const char* string2); +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const String& string2); +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, StringRef string2); + +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, short number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, long number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int64 number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, uint64 number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, float number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, double number); + +//============================================================================== +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF8 string2) noexcept; + +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF8 string2) noexcept; + +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator> (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator< (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator>= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator<= (const String& string1, const String& string2) noexcept; + +//============================================================================== +/** This operator allows you to write a juce String directly to std output streams. + This is handy for writing strings to std::cout, std::cerr, etc. +*/ +template +std::basic_ostream & JUCE_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toRawUTF8(); +} + +/** Writes a string to an OutputStream as UTF8. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& stringToWrite); + +/** Writes a string to an OutputStream as UTF8. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, StringRef stringToWrite); + + +#endif // JUCE_STRING_H_INCLUDED diff --git a/source/modules/juce_audio_graph/text/juce_StringRef.h b/source/modules/juce_audio_graph/text/juce_StringRef.h new file mode 100644 index 000000000..a3ad6bc7d --- /dev/null +++ b/source/modules/juce_audio_graph/text/juce_StringRef.h @@ -0,0 +1,136 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ----------------------------------------------------------------------------- + + To release a closed-source product which uses other parts of JUCE not + licensed under the ISC terms, commercial licenses are available: visit + www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_STRINGREF_H_INCLUDED +#define JUCE_STRINGREF_H_INCLUDED + +//============================================================================== +/** + A simple class for holding temporary references to a string literal or String. + + Unlike a real String object, the StringRef does not allocate any memory or + take ownership of the strings you give to it - it simply holds a reference to + a string that has been allocated elsewhere. + The main purpose of the class is to be used instead of a const String& as the type + of function arguments where the caller may pass either a string literal or a String + object. This means that when the called uses a string literal, there's no need + for an temporary String object to be allocated, and this cuts down overheads + substantially. + + Because the class is simply a wrapper around a pointer, you should always pass + it by value, not by reference. + + @code + void myStringFunction1 (const String&); + void myStringFunction2 (StringRef); + + myStringFunction1 ("abc"); // Implicitly allocates a temporary String object. + myStringFunction2 ("abc"); // Much faster, as no local allocations are needed. + @endcode + + For examples of it in use, see the XmlElement or StringArray classes. + + Bear in mind that there are still many cases where it's better to use an argument + which is a const String&. For example if the function stores the string or needs + to internally create a String from the argument, then it's better for the original + argument to already be a String. + + @see String +*/ +class JUCE_API StringRef +{ +public: + /** Creates a StringRef from a raw string literal. + The StringRef object does NOT take ownership or copy this data, so you must + ensure that the data does not change during the lifetime of the StringRef. + Note that this pointer not be null! + */ + StringRef (const char* stringLiteral) noexcept; + + /** Creates a StringRef from a raw char pointer. + The StringRef object does NOT take ownership or copy this data, so you must + ensure that the data does not change during the lifetime of the StringRef. + */ + StringRef (String::CharPointerType stringLiteral) noexcept; + + /** Creates a StringRef from a String. + The StringRef object does NOT take ownership or copy the data from the String, + so you must ensure that the String is not modified or deleted during the lifetime + of the StringRef. + */ + StringRef (const String& string) noexcept; + + /** Creates a StringRef pointer to an empty string. */ + StringRef() noexcept; + + //============================================================================== + /** Returns a raw pointer to the underlying string data. */ + operator const String::CharPointerType::CharType*() const noexcept { return text.getAddress(); } + /** Returns a pointer to the underlying string data as a char pointer object. */ + operator String::CharPointerType() const noexcept { return text; } + + /** Returns true if the string is empty. */ + bool isEmpty() const noexcept { return text.isEmpty(); } + /** Returns true if the string is not empty. */ + bool isNotEmpty() const noexcept { return ! text.isEmpty(); } + /** Returns the number of characters in the string. */ + int length() const noexcept { return (int) text.length(); } + + /** Retrieves a character by index. */ + juce_wchar operator[] (int index) const noexcept { return text[index]; } + + /** Compares this StringRef with a String. */ + bool operator== (const String& s) const noexcept { return text.compare (s.getCharPointer()) == 0; } + /** Compares this StringRef with a String. */ + bool operator!= (const String& s) const noexcept { return text.compare (s.getCharPointer()) != 0; } + + /** Case-sensitive comparison of two StringRefs. */ + bool operator== (StringRef s) const noexcept { return text.compare (s.text) == 0; } + /** Case-sensitive comparison of two StringRefs. */ + bool operator!= (StringRef s) const noexcept { return text.compare (s.text) != 0; } + + //============================================================================== + /** The text that is referenced. */ + String::CharPointerType text; +}; + +//============================================================================== +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, StringRef string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, StringRef string2) noexcept; + +inline String operator+ (String s1, StringRef s2) { return s1 += String (s2.text); } +inline String operator+ (StringRef s1, const String& s2) { return String (s1.text) + s2; } +inline String operator+ (const char* s1, StringRef s2) { return String (s1) + String (s2.text); } +inline String operator+ (StringRef s1, const char* s2) { return String (s1.text) + String (s2); } + + +#endif // JUCE_STRINGREF_H_INCLUDED