| @@ -0,0 +1,14 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| Build options for juce_audio_basics static library | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED | |||||
| #define CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED | |||||
| #include "../juce_core/AppConfig.h" | |||||
| #endif // CARLA_JUCE_AUDIO_BASICS_APPCONFIG_H_INCLUDED | |||||
| @@ -0,0 +1,88 @@ | |||||
| #!/usr/bin/make -f | |||||
| # Makefile for juce_audio_basics # | |||||
| # ------------------------------ # | |||||
| # Created by falkTX | |||||
| # | |||||
| include ../../Makefile.mk | |||||
| # -------------------------------------------------------------- | |||||
| BUILD_CXX_FLAGS += -I. | |||||
| ifeq ($(MACOS),true) | |||||
| BUILD_CXX_FLAGS += -objc++ | |||||
| OBJS = juce_audio_basics.mm.o | |||||
| OBJS_posix32 = juce_audio_basics.mm.posix32.o | |||||
| OBJS_posix64 = juce_audio_basics.mm.posix64.o | |||||
| else | |||||
| OBJS = juce_audio_basics.cpp.o | |||||
| OBJS_posix32 = juce_audio_basics.cpp.posix32.o | |||||
| OBJS_posix64 = juce_audio_basics.cpp.posix64.o | |||||
| endif | |||||
| OBJS_win32 = juce_audio_basics.cpp.win32.o | |||||
| OBJS_win64 = juce_audio_basics.cpp.win64.o | |||||
| # -------------------------------------------------------------- | |||||
| all: ../juce_audio_basics.a | |||||
| posix32: ../juce_audio_basics.posix32.a | |||||
| posix64: ../juce_audio_basics.posix64.a | |||||
| win32: ../juce_audio_basics.win32.a | |||||
| win64: ../juce_audio_basics.win64.a | |||||
| # -------------------------------------------------------------- | |||||
| ../juce_audio_basics.a: $(OBJS) | |||||
| $(AR) rs $@ $^ | |||||
| ../juce_audio_basics.posix32.a: $(OBJS_posix32) | |||||
| $(AR) rs $@ $^ | |||||
| ../juce_audio_basics.posix64.a: $(OBJS_posix64) | |||||
| $(AR) rs $@ $^ | |||||
| ../juce_audio_basics.win32.a: $(OBJS_win32) | |||||
| $(AR) rs $@ $^ | |||||
| ../juce_audio_basics.win64.a: $(OBJS_win64) | |||||
| $(AR) rs $@ $^ | |||||
| ../juce_audio_basics.dll: $(OBJS) | |||||
| $(CXX) $^ -shared $(LINK_FLAGS) -o $@ | |||||
| ../juce_audio_basics.dylib: $(OBJS) | |||||
| $(CXX) $^ -dynamiclib $(LINK_FLAGS) -o $@ | |||||
| ../juce_audio_basics.so: $(OBJS) | |||||
| $(CXX) $^ -shared $(LINK_FLAGS) -o $@ | |||||
| # -------------------------------------------------------------- | |||||
| %.cpp.o: %.cpp | |||||
| $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
| %.mm.o: %.mm | |||||
| $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
| %.posix32.o: % | |||||
| $(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
| %.posix64.o: % | |||||
| $(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
| %.win32.o: % | |||||
| $(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
| %.win64.o: % | |||||
| $(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
| # -------------------------------------------------------------- | |||||
| clean: | |||||
| rm -f *.o ../juce_audio_basics.* | |||||
| debug: | |||||
| $(MAKE) DEBUG=true | |||||
| @@ -0,0 +1,600 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fff; | |||||
| char* intData = static_cast <char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint16*) intData = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fff; | |||||
| char* intData = static_cast <char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint16*) intData = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffff; | |||||
| char* intData = static_cast <char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffff; | |||||
| char* intData = static_cast <char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffffff; | |||||
| char* intData = static_cast <char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint32*)intData = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| const double maxVal = (double) 0x7fffffff; | |||||
| char* intData = static_cast <char*> (dest); | |||||
| if (dest != (void*) source || destBytesPerSample <= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += destBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= destBytesPerSample; | |||||
| *(uint32*)intData = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
| char* d = static_cast <char*> (dest); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(float*) d = source[i]; | |||||
| #if JUCE_BIG_ENDIAN | |||||
| *(uint32*) d = ByteOrder::swap (*(uint32*) d); | |||||
| #endif | |||||
| d += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) | |||||
| { | |||||
| jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! | |||||
| char* d = static_cast <char*> (dest); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| *(float*) d = source[i]; | |||||
| #if JUCE_LITTLE_ENDIAN | |||||
| *(uint32*) d = ByteOrder::swap (*(uint32*) d); | |||||
| #endif | |||||
| d += destBytesPerSample; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fff; | |||||
| const char* intData = static_cast <const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*(uint16*)intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fff; | |||||
| const char* intData = static_cast <const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*(uint16*)intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffff; | |||||
| const char* intData = static_cast <const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffff; | |||||
| const char* intData = static_cast <const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffffff; | |||||
| const char* intData = static_cast <const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*(uint32*) intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const float scale = 1.0f / 0x7fffffff; | |||||
| const char* intData = static_cast <const char*> (source); | |||||
| if (source != (void*) dest || srcBytesPerSample >= 4) | |||||
| { | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); | |||||
| intData += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| intData += srcBytesPerSample * numSamples; | |||||
| for (int i = numSamples; --i >= 0;) | |||||
| { | |||||
| intData -= srcBytesPerSample; | |||||
| dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*(uint32*) intData); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const char* s = static_cast <const char*> (source); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = *(float*)s; | |||||
| #if JUCE_BIG_ENDIAN | |||||
| uint32* const d = (uint32*) (dest + i); | |||||
| *d = ByteOrder::swap (*d); | |||||
| #endif | |||||
| s += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) | |||||
| { | |||||
| const char* s = static_cast <const char*> (source); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| dest[i] = *(float*)s; | |||||
| #if JUCE_LITTLE_ENDIAN | |||||
| uint32* const d = (uint32*) (dest + i); | |||||
| *d = ByteOrder::swap (*d); | |||||
| #endif | |||||
| s += srcBytesPerSample; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, | |||||
| const float* const source, | |||||
| void* const dest, | |||||
| const int numSamples) | |||||
| { | |||||
| switch (destFormat) | |||||
| { | |||||
| case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; | |||||
| case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; | |||||
| case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; | |||||
| case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; | |||||
| case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; | |||||
| case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; | |||||
| case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; | |||||
| case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; | |||||
| default: jassertfalse; break; | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::convertFormatToFloat (const DataFormat sourceFormat, | |||||
| const void* const source, | |||||
| float* const dest, | |||||
| const int numSamples) | |||||
| { | |||||
| switch (sourceFormat) | |||||
| { | |||||
| case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; | |||||
| case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; | |||||
| case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; | |||||
| case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; | |||||
| case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; | |||||
| case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; | |||||
| case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; | |||||
| case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; | |||||
| default: jassertfalse; break; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioDataConverters::interleaveSamples (const float** const source, | |||||
| float* const dest, | |||||
| const int numSamples, | |||||
| const int numChannels) | |||||
| { | |||||
| for (int chan = 0; chan < numChannels; ++chan) | |||||
| { | |||||
| int i = chan; | |||||
| const float* src = source [chan]; | |||||
| for (int j = 0; j < numSamples; ++j) | |||||
| { | |||||
| dest [i] = src [j]; | |||||
| i += numChannels; | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioDataConverters::deinterleaveSamples (const float* const source, | |||||
| float** const dest, | |||||
| const int numSamples, | |||||
| const int numChannels) | |||||
| { | |||||
| for (int chan = 0; chan < numChannels; ++chan) | |||||
| { | |||||
| int i = chan; | |||||
| float* dst = dest [chan]; | |||||
| for (int j = 0; j < numSamples; ++j) | |||||
| { | |||||
| dst [j] = source [i]; | |||||
| i += numChannels; | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| #if JUCE_UNIT_TESTS | |||||
| class AudioConversionTests : public UnitTest | |||||
| { | |||||
| public: | |||||
| AudioConversionTests() : UnitTest ("Audio data conversion") {} | |||||
| template <class F1, class E1, class F2, class E2> | |||||
| struct Test5 | |||||
| { | |||||
| static void test (UnitTest& unitTest) | |||||
| { | |||||
| test (unitTest, false); | |||||
| test (unitTest, true); | |||||
| } | |||||
| static void test (UnitTest& unitTest, bool inPlace) | |||||
| { | |||||
| const int numSamples = 2048; | |||||
| int32 original [numSamples], converted [numSamples], reversed [numSamples]; | |||||
| Random r; | |||||
| { | |||||
| AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> d (original); | |||||
| bool clippingFailed = false; | |||||
| for (int i = 0; i < numSamples / 2; ++i) | |||||
| { | |||||
| d.setAsFloat (r.nextFloat() * 2.2f - 1.1f); | |||||
| if (! d.isFloatingPoint()) | |||||
| clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; | |||||
| ++d; | |||||
| d.setAsInt32 (r.nextInt()); | |||||
| ++d; | |||||
| } | |||||
| unitTest.expect (! clippingFailed); | |||||
| } | |||||
| // convert data from the source to dest format.. | |||||
| ScopedPointer<AudioData::Converter> conv (new AudioData::ConverterInstance <AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>, | |||||
| AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::NonConst> >()); | |||||
| conv->convertSamples (inPlace ? reversed : converted, original, numSamples); | |||||
| // ..and back again.. | |||||
| conv = new AudioData::ConverterInstance <AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>, | |||||
| AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::NonConst> >(); | |||||
| if (! inPlace) | |||||
| zeromem (reversed, sizeof (reversed)); | |||||
| conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); | |||||
| { | |||||
| int biggestDiff = 0; | |||||
| AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d1 (original); | |||||
| AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const> d2 (reversed); | |||||
| const int errorMargin = 2 * AudioData::Pointer<F1, E1, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution() | |||||
| + AudioData::Pointer<F2, E2, AudioData::NonInterleaved, AudioData::Const>::get32BitResolution(); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); | |||||
| ++d1; | |||||
| ++d2; | |||||
| } | |||||
| unitTest.expect (biggestDiff <= errorMargin); | |||||
| } | |||||
| } | |||||
| }; | |||||
| template <class F1, class E1, class FormatType> | |||||
| struct Test3 | |||||
| { | |||||
| static void test (UnitTest& unitTest) | |||||
| { | |||||
| Test5 <F1, E1, FormatType, AudioData::BigEndian>::test (unitTest); | |||||
| Test5 <F1, E1, FormatType, AudioData::LittleEndian>::test (unitTest); | |||||
| } | |||||
| }; | |||||
| template <class FormatType, class Endianness> | |||||
| struct Test2 | |||||
| { | |||||
| static void test (UnitTest& unitTest) | |||||
| { | |||||
| Test3 <FormatType, Endianness, AudioData::Int8>::test (unitTest); | |||||
| Test3 <FormatType, Endianness, AudioData::UInt8>::test (unitTest); | |||||
| Test3 <FormatType, Endianness, AudioData::Int16>::test (unitTest); | |||||
| Test3 <FormatType, Endianness, AudioData::Int24>::test (unitTest); | |||||
| Test3 <FormatType, Endianness, AudioData::Int32>::test (unitTest); | |||||
| Test3 <FormatType, Endianness, AudioData::Float32>::test (unitTest); | |||||
| } | |||||
| }; | |||||
| template <class FormatType> | |||||
| struct Test1 | |||||
| { | |||||
| static void test (UnitTest& unitTest) | |||||
| { | |||||
| Test2 <FormatType, AudioData::BigEndian>::test (unitTest); | |||||
| Test2 <FormatType, AudioData::LittleEndian>::test (unitTest); | |||||
| } | |||||
| }; | |||||
| void runTest() | |||||
| { | |||||
| beginTest ("Round-trip conversion: Int8"); | |||||
| Test1 <AudioData::Int8>::test (*this); | |||||
| beginTest ("Round-trip conversion: Int16"); | |||||
| Test1 <AudioData::Int16>::test (*this); | |||||
| beginTest ("Round-trip conversion: Int24"); | |||||
| Test1 <AudioData::Int24>::test (*this); | |||||
| beginTest ("Round-trip conversion: Int32"); | |||||
| Test1 <AudioData::Int32>::test (*this); | |||||
| beginTest ("Round-trip conversion: Float32"); | |||||
| Test1 <AudioData::Float32>::test (*this); | |||||
| } | |||||
| }; | |||||
| static AudioConversionTests audioConversionUnitTests; | |||||
| #endif | |||||
| @@ -0,0 +1,691 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_AUDIODATACONVERTERS_H_INCLUDED | |||||
| #define JUCE_AUDIODATACONVERTERS_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| This class a container which holds all the classes pertaining to the AudioData::Pointer | |||||
| audio sample format class. | |||||
| @see AudioData::Pointer. | |||||
| */ | |||||
| class JUCE_API AudioData | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| // These types can be used as the SampleFormat template parameter for the AudioData::Pointer class. | |||||
| class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */ | |||||
| class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */ | |||||
| class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */ | |||||
| class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */ | |||||
| class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */ | |||||
| class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */ | |||||
| //============================================================================== | |||||
| // These types can be used as the Endianness template parameter for the AudioData::Pointer class. | |||||
| class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */ | |||||
| class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */ | |||||
| class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */ | |||||
| //============================================================================== | |||||
| // These types can be used as the InterleavingType template parameter for the AudioData::Pointer class. | |||||
| class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */ | |||||
| class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */ | |||||
| //============================================================================== | |||||
| // These types can be used as the Constness template parameter for the AudioData::Pointer class. | |||||
| class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */ | |||||
| class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */ | |||||
| #ifndef DOXYGEN | |||||
| //============================================================================== | |||||
| class BigEndian | |||||
| { | |||||
| public: | |||||
| template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } | |||||
| template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } | |||||
| template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } | |||||
| template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } | |||||
| template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } | |||||
| enum { isBigEndian = 1 }; | |||||
| }; | |||||
| class LittleEndian | |||||
| { | |||||
| public: | |||||
| template <class SampleFormatType> static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } | |||||
| template <class SampleFormatType> static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } | |||||
| template <class SampleFormatType> static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } | |||||
| template <class SampleFormatType> static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } | |||||
| template <class SourceType, class DestType> static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); } | |||||
| enum { isBigEndian = 0 }; | |||||
| }; | |||||
| #if JUCE_BIG_ENDIAN | |||||
| class NativeEndian : public BigEndian {}; | |||||
| #else | |||||
| class NativeEndian : public LittleEndian {}; | |||||
| #endif | |||||
| //============================================================================== | |||||
| class Int8 | |||||
| { | |||||
| public: | |||||
| inline Int8 (void* d) noexcept : data (static_cast <int8*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + maxValue))); } | |||||
| inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int) (*data << 24); } | |||||
| inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
| inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); } | |||||
| inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; } | |||||
| int8* data; | |||||
| enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||||
| }; | |||||
| class UInt8 | |||||
| { | |||||
| public: | |||||
| inline UInt8 (void* d) noexcept : data (static_cast <uint8*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + maxValue))); } | |||||
| inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + maxValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int) ((*data - 128) << 24); } | |||||
| inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } | |||||
| inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); } | |||||
| inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } | |||||
| inline void clear() noexcept { *data = 128; } | |||||
| inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; } | |||||
| uint8* data; | |||||
| enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; | |||||
| }; | |||||
| class Int16 | |||||
| { | |||||
| public: | |||||
| inline Int16 (void* d) noexcept : data (static_cast <uint16*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue)))); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; } | |||||
| uint16* data; | |||||
| enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 }; | |||||
| }; | |||||
| class Int24 | |||||
| { | |||||
| public: | |||||
| inline Int24 (void* d) noexcept : data (static_cast <char*> (d)) {} | |||||
| inline void advance() noexcept { data += 3; } | |||||
| inline void skip (int numSamples) noexcept { data += 3 * numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + maxValue))); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + maxValue))), data); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::littleEndian24Bit (data) << 8; } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::bigEndian24Bit (data) << 8; } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); } | |||||
| inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; } | |||||
| char* data; | |||||
| enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; | |||||
| }; | |||||
| class Int32 | |||||
| { | |||||
| public: | |||||
| inline Int32 (void* d) noexcept : data (static_cast <uint32*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } | |||||
| inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (maxValue * jlimit (-1.0, 1.0, (double) newValue))); } | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } | |||||
| inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } | |||||
| uint32* data; | |||||
| enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; | |||||
| }; | |||||
| class Float32 | |||||
| { | |||||
| public: | |||||
| inline Float32 (void* d) noexcept : data (static_cast <float*> (d)) {} | |||||
| inline void advance() noexcept { ++data; } | |||||
| inline void skip (int numSamples) noexcept { data += numSamples; } | |||||
| #if JUCE_BIG_ENDIAN | |||||
| inline float getAsFloatBE() const noexcept { return *data; } | |||||
| inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } | |||||
| inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
| inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
| #else | |||||
| inline float getAsFloatLE() const noexcept { return *data; } | |||||
| inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } | |||||
| inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } | |||||
| inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } | |||||
| #endif | |||||
| inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } | |||||
| inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } | |||||
| inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
| inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + maxValue)))); } | |||||
| inline void clear() noexcept { *data = 0; } | |||||
| inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} | |||||
| template <class SourceType> inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } | |||||
| template <class SourceType> inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } | |||||
| inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } | |||||
| float* data; | |||||
| enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; | |||||
| }; | |||||
| //============================================================================== | |||||
| class NonInterleaved | |||||
| { | |||||
| public: | |||||
| inline NonInterleaved() noexcept {} | |||||
| inline NonInterleaved (const NonInterleaved&) noexcept {} | |||||
| inline NonInterleaved (const int) noexcept {} | |||||
| inline void copyFrom (const NonInterleaved&) noexcept {} | |||||
| template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } | |||||
| template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } | |||||
| template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } | |||||
| template <class SampleFormatType> inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } | |||||
| enum { isInterleavedType = 0, numInterleavedChannels = 1 }; | |||||
| }; | |||||
| class Interleaved | |||||
| { | |||||
| public: | |||||
| inline Interleaved() noexcept : numInterleavedChannels (1) {} | |||||
| inline Interleaved (const Interleaved& other) noexcept : numInterleavedChannels (other.numInterleavedChannels) {} | |||||
| inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} | |||||
| inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } | |||||
| template <class SampleFormatType> inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } | |||||
| template <class SampleFormatType> inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } | |||||
| template <class SampleFormatType> inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } | |||||
| template <class SampleFormatType> inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } | |||||
| int numInterleavedChannels; | |||||
| enum { isInterleavedType = 1 }; | |||||
| }; | |||||
| //============================================================================== | |||||
| class NonConst | |||||
| { | |||||
| public: | |||||
| typedef void VoidType; | |||||
| static inline void* toVoidPtr (VoidType* v) noexcept { return v; } | |||||
| enum { isConst = 0 }; | |||||
| }; | |||||
| class Const | |||||
| { | |||||
| public: | |||||
| typedef const void VoidType; | |||||
| static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast <void*> (v); } | |||||
| enum { isConst = 1 }; | |||||
| }; | |||||
| #endif | |||||
| //============================================================================== | |||||
| /** | |||||
| A pointer to a block of audio data with a particular encoding. | |||||
| This object can be used to read and write from blocks of encoded audio samples. To create one, you specify | |||||
| the audio format as a series of template parameters, e.g. | |||||
| @code | |||||
| // this creates a pointer for reading from a const array of 16-bit little-endian packed samples. | |||||
| AudioData::Pointer <AudioData::Int16, | |||||
| AudioData::LittleEndian, | |||||
| AudioData::NonInterleaved, | |||||
| AudioData::Const> pointer (someRawAudioData); | |||||
| // These methods read the sample that is being pointed to | |||||
| float firstSampleAsFloat = pointer.getAsFloat(); | |||||
| int32 firstSampleAsInt = pointer.getAsInt32(); | |||||
| ++pointer; // moves the pointer to the next sample. | |||||
| pointer += 3; // skips the next 3 samples. | |||||
| @endcode | |||||
| The convertSamples() method lets you copy a range of samples from one format to another, automatically | |||||
| converting its format. | |||||
| @see AudioData::Converter | |||||
| */ | |||||
| template <typename SampleFormat, | |||||
| typename Endianness, | |||||
| typename InterleavingType, | |||||
| typename Constness> | |||||
| class Pointer : private InterleavingType // (inherited for EBCO) | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a non-interleaved pointer from some raw data in the appropriate format. | |||||
| This constructor is only used if you've specified the AudioData::NonInterleaved option - | |||||
| for interleaved formats, use the constructor that also takes a number of channels. | |||||
| */ | |||||
| Pointer (typename Constness::VoidType* sourceData) noexcept | |||||
| : data (Constness::toVoidPtr (sourceData)) | |||||
| { | |||||
| // If you're using interleaved data, call the other constructor! If you're using non-interleaved data, | |||||
| // you should pass NonInterleaved as the template parameter for the interleaving type! | |||||
| static_jassert (InterleavingType::isInterleavedType == 0); | |||||
| } | |||||
| /** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels. | |||||
| For non-interleaved data, use the other constructor. | |||||
| */ | |||||
| Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept | |||||
| : InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData)) | |||||
| { | |||||
| } | |||||
| /** Creates a copy of another pointer. */ | |||||
| Pointer (const Pointer& other) noexcept | |||||
| : InterleavingType (other), data (other.data) | |||||
| { | |||||
| } | |||||
| Pointer& operator= (const Pointer& other) noexcept | |||||
| { | |||||
| InterleavingType::operator= (other); | |||||
| data = other.data; | |||||
| return *this; | |||||
| } | |||||
| //============================================================================== | |||||
| /** Returns the value of the first sample as a floating point value. | |||||
| The value will be in the range -1.0 to 1.0 for integer formats. For floating point | |||||
| formats, the value could be outside that range, although -1 to 1 is the standard range. | |||||
| */ | |||||
| inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); } | |||||
| /** Sets the value of the first sample as a floating point value. | |||||
| (This method can only be used if the AudioData::NonConst option was used). | |||||
| The value should be in the range -1.0 to 1.0 - for integer formats, values outside that | |||||
| range will be clipped. For floating point formats, any value passed in here will be | |||||
| written directly, although -1 to 1 is the standard range. | |||||
| */ | |||||
| inline void setAsFloat (float newValue) noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| Endianness::setAsFloat (data, newValue); | |||||
| } | |||||
| /** Returns the value of the first sample as a 32-bit integer. | |||||
| The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be | |||||
| shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up | |||||
| by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will | |||||
| be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff. | |||||
| */ | |||||
| inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); } | |||||
| /** Sets the value of the first sample as a 32-bit integer. | |||||
| This will be mapped to the range of the format that is being written - see getAsInt32(). | |||||
| */ | |||||
| inline void setAsInt32 (int32 newValue) noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| Endianness::setAsInt32 (data, newValue); | |||||
| } | |||||
| /** Moves the pointer along to the next sample. */ | |||||
| inline Pointer& operator++() noexcept { advance(); return *this; } | |||||
| /** Moves the pointer back to the previous sample. */ | |||||
| inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; } | |||||
| /** Adds a number of samples to the pointer's position. */ | |||||
| Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; } | |||||
| /** Writes a stream of samples into this pointer from another pointer. | |||||
| This will copy the specified number of samples, converting between formats appropriately. | |||||
| */ | |||||
| void convertSamples (Pointer source, int numSamples) const noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| for (Pointer dest (*this); --numSamples >= 0;) | |||||
| { | |||||
| dest.data.copyFromSameType (source.data); | |||||
| dest.advance(); | |||||
| source.advance(); | |||||
| } | |||||
| } | |||||
| /** Writes a stream of samples into this pointer from another pointer. | |||||
| This will copy the specified number of samples, converting between formats appropriately. | |||||
| */ | |||||
| template <class OtherPointerType> | |||||
| void convertSamples (OtherPointerType source, int numSamples) const noexcept | |||||
| { | |||||
| static_jassert (Constness::isConst == 0); // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! | |||||
| Pointer dest (*this); | |||||
| if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples()) | |||||
| { | |||||
| while (--numSamples >= 0) | |||||
| { | |||||
| Endianness::copyFrom (dest.data, source); | |||||
| dest.advance(); | |||||
| ++source; | |||||
| } | |||||
| } | |||||
| else // copy backwards if we're increasing the sample width.. | |||||
| { | |||||
| dest += numSamples; | |||||
| source += numSamples; | |||||
| while (--numSamples >= 0) | |||||
| Endianness::copyFrom ((--dest).data, --source); | |||||
| } | |||||
| } | |||||
| /** Sets a number of samples to zero. */ | |||||
| void clearSamples (int numSamples) const noexcept | |||||
| { | |||||
| Pointer dest (*this); | |||||
| dest.clear (dest.data, numSamples); | |||||
| } | |||||
| /** Scans a block of data, returning the lowest and highest levels as floats */ | |||||
| void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept | |||||
| { | |||||
| if (numSamples == 0) | |||||
| { | |||||
| minValue = maxValue = 0; | |||||
| return; | |||||
| } | |||||
| Pointer dest (*this); | |||||
| if (isFloatingPoint()) | |||||
| { | |||||
| float mn = dest.getAsFloat(); | |||||
| dest.advance(); | |||||
| float mx = mn; | |||||
| while (--numSamples > 0) | |||||
| { | |||||
| const float v = dest.getAsFloat(); | |||||
| dest.advance(); | |||||
| if (mx < v) mx = v; | |||||
| if (v < mn) mn = v; | |||||
| } | |||||
| minValue = mn; | |||||
| maxValue = mx; | |||||
| } | |||||
| else | |||||
| { | |||||
| int32 mn = dest.getAsInt32(); | |||||
| dest.advance(); | |||||
| int32 mx = mn; | |||||
| while (--numSamples > 0) | |||||
| { | |||||
| const int v = dest.getAsInt32(); | |||||
| dest.advance(); | |||||
| if (mx < v) mx = v; | |||||
| if (v < mn) mn = v; | |||||
| } | |||||
| minValue = mn * (float) (1.0 / (1.0 + Int32::maxValue)); | |||||
| maxValue = mx * (float) (1.0 / (1.0 + Int32::maxValue)); | |||||
| } | |||||
| } | |||||
| /** Returns true if the pointer is using a floating-point format. */ | |||||
| static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } | |||||
| /** Returns true if the format is big-endian. */ | |||||
| static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } | |||||
| /** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ | |||||
| static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } | |||||
| /** Returns the number of interleaved channels in the format. */ | |||||
| int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } | |||||
| /** Returns the number of bytes between the start address of each sample. */ | |||||
| int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } | |||||
| /** Returns the accuracy of this format when represented as a 32-bit integer. | |||||
| This is the smallest number above 0 that can be represented in the sample format, converted to | |||||
| a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, | |||||
| its resolution is 0x100. | |||||
| */ | |||||
| static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } | |||||
| /** Returns a pointer to the underlying data. */ | |||||
| const void* getRawData() const noexcept { return data.data; } | |||||
| private: | |||||
| //============================================================================== | |||||
| SampleFormat data; | |||||
| inline void advance() noexcept { this->advanceData (data); } | |||||
| Pointer operator++ (int); // private to force you to use the more efficient pre-increment! | |||||
| Pointer operator-- (int); | |||||
| }; | |||||
| //============================================================================== | |||||
| /** A base class for objects that are used to convert between two different sample formats. | |||||
| The AudioData::ConverterInstance implements this base class and can be templated, so | |||||
| you can create an instance that converts between two particular formats, and then | |||||
| store this in the abstract base class. | |||||
| @see AudioData::ConverterInstance | |||||
| */ | |||||
| class Converter | |||||
| { | |||||
| public: | |||||
| virtual ~Converter() {} | |||||
| /** Converts a sequence of samples from the converter's source format into the dest format. */ | |||||
| virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; | |||||
| /** Converts a sequence of samples from the converter's source format into the dest format. | |||||
| This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a | |||||
| particular sub-channel of the data to be used. | |||||
| */ | |||||
| virtual void convertSamples (void* destSamples, int destSubChannel, | |||||
| const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| A class that converts between two templated AudioData::Pointer types, and which | |||||
| implements the AudioData::Converter interface. | |||||
| This can be used as a concrete instance of the AudioData::Converter abstract class. | |||||
| @see AudioData::Converter | |||||
| */ | |||||
| template <class SourceSampleType, class DestSampleType> | |||||
| class ConverterInstance : public Converter | |||||
| { | |||||
| public: | |||||
| ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) | |||||
| : sourceChannels (numSourceChannels), destChannels (numDestChannels) | |||||
| {} | |||||
| ~ConverterInstance() {} | |||||
| void convertSamples (void* dest, const void* source, int numSamples) const | |||||
| { | |||||
| SourceSampleType s (source, sourceChannels); | |||||
| DestSampleType d (dest, destChannels); | |||||
| d.convertSamples (s, numSamples); | |||||
| } | |||||
| void convertSamples (void* dest, int destSubChannel, | |||||
| const void* source, int sourceSubChannel, int numSamples) const | |||||
| { | |||||
| jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); | |||||
| SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); | |||||
| DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); | |||||
| d.convertSamples (s, numSamples); | |||||
| } | |||||
| private: | |||||
| JUCE_DECLARE_NON_COPYABLE (ConverterInstance) | |||||
| const int sourceChannels, destChannels; | |||||
| }; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| A set of routines to convert buffers of 32-bit floating point data to and from | |||||
| various integer formats. | |||||
| Note that these functions are deprecated - the AudioData class provides a much more | |||||
| flexible set of conversion classes now. | |||||
| */ | |||||
| class JUCE_API AudioDataConverters | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
| static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); | |||||
| static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
| static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); | |||||
| static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); | |||||
| //============================================================================== | |||||
| static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
| static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); | |||||
| static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
| static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); | |||||
| static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); | |||||
| //============================================================================== | |||||
| enum DataFormat | |||||
| { | |||||
| int16LE, | |||||
| int16BE, | |||||
| int24LE, | |||||
| int24BE, | |||||
| int32LE, | |||||
| int32BE, | |||||
| float32LE, | |||||
| float32BE, | |||||
| }; | |||||
| static void convertFloatToFormat (DataFormat destFormat, | |||||
| const float* source, void* dest, int numSamples); | |||||
| static void convertFormatToFloat (DataFormat sourceFormat, | |||||
| const void* source, float* dest, int numSamples); | |||||
| //============================================================================== | |||||
| static void interleaveSamples (const float** source, float* dest, | |||||
| int numSamples, int numChannels); | |||||
| static void deinterleaveSamples (const float* source, float** dest, | |||||
| int numSamples, int numChannels); | |||||
| private: | |||||
| AudioDataConverters(); | |||||
| JUCE_DECLARE_NON_COPYABLE (AudioDataConverters) | |||||
| }; | |||||
| #endif // JUCE_AUDIODATACONVERTERS_H_INCLUDED | |||||
| @@ -0,0 +1,542 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| AudioSampleBuffer::AudioSampleBuffer (const int numChans, | |||||
| const int numSamples) noexcept | |||||
| : numChannels (numChans), | |||||
| size (numSamples) | |||||
| { | |||||
| jassert (numSamples >= 0); | |||||
| jassert (numChans > 0); | |||||
| allocateData(); | |||||
| } | |||||
| AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept | |||||
| : numChannels (other.numChannels), | |||||
| size (other.size), | |||||
| allocatedBytes (other.allocatedBytes) | |||||
| { | |||||
| if (allocatedBytes == 0) | |||||
| { | |||||
| allocateChannels (other.channels, 0); | |||||
| } | |||||
| else | |||||
| { | |||||
| allocateData(); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::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 <float**> (allocatedData.getData()); | |||||
| float* chan = (float*) (allocatedData + channelListSize); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| { | |||||
| channels[i] = chan; | |||||
| chan += size; | |||||
| } | |||||
| channels [numChannels] = nullptr; | |||||
| } | |||||
| AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||||
| const int numChans, | |||||
| const int numSamples) noexcept | |||||
| : numChannels (numChans), | |||||
| size (numSamples), | |||||
| allocatedBytes (0) | |||||
| { | |||||
| jassert (numChans > 0); | |||||
| allocateChannels (dataToReferTo, 0); | |||||
| } | |||||
| AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||||
| const int numChans, | |||||
| const int startSample, | |||||
| const int numSamples) noexcept | |||||
| : numChannels (numChans), | |||||
| size (numSamples), | |||||
| allocatedBytes (0) | |||||
| { | |||||
| jassert (numChans > 0); | |||||
| allocateChannels (dataToReferTo, startSample); | |||||
| } | |||||
| void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, | |||||
| const int newNumChannels, | |||||
| const int newNumSamples) noexcept | |||||
| { | |||||
| jassert (newNumChannels > 0); | |||||
| allocatedBytes = 0; | |||||
| allocatedData.free(); | |||||
| numChannels = newNumChannels; | |||||
| size = newNumSamples; | |||||
| allocateChannels (dataToReferTo, 0); | |||||
| } | |||||
| void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset) | |||||
| { | |||||
| // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) | |||||
| if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) | |||||
| { | |||||
| channels = static_cast <float**> (preallocatedChannelSpace); | |||||
| } | |||||
| else | |||||
| { | |||||
| allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*)); | |||||
| channels = reinterpret_cast <float**> (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; | |||||
| } | |||||
| AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept | |||||
| { | |||||
| if (this != &other) | |||||
| { | |||||
| setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||||
| } | |||||
| return *this; | |||||
| } | |||||
| AudioSampleBuffer::~AudioSampleBuffer() noexcept | |||||
| { | |||||
| } | |||||
| void AudioSampleBuffer::setSize (const int newNumChannels, | |||||
| const int newNumSamples, | |||||
| const bool keepExistingContent, | |||||
| const bool clearExtraSpace, | |||||
| const bool avoidReallocating) noexcept | |||||
| { | |||||
| jassert (newNumChannels > 0); | |||||
| jassert (newNumSamples >= 0); | |||||
| if (newNumSamples != size || newNumChannels != numChannels) | |||||
| { | |||||
| const size_t allocatedSamplesPerChannel = (newNumSamples + 3) & ~3; | |||||
| const size_t channelListSize = ((sizeof (float*) * (size_t) (newNumChannels + 1)) + 15) & ~15; | |||||
| const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (float)) | |||||
| + channelListSize + 32; | |||||
| if (keepExistingContent) | |||||
| { | |||||
| HeapBlock <char, true> newData; | |||||
| newData.allocate (newTotalBytes, clearExtraSpace); | |||||
| const size_t numSamplesToCopy = jmin (newNumSamples, size); | |||||
| float** const newChannels = reinterpret_cast <float**> (newData.getData()); | |||||
| float* newChan = reinterpret_cast <float*> (newData + channelListSize); | |||||
| for (int j = 0; j < newNumChannels; ++j) | |||||
| { | |||||
| newChannels[j] = newChan; | |||||
| newChan += allocatedSamplesPerChannel; | |||||
| } | |||||
| const int numChansToCopy = jmin (numChannels, newNumChannels); | |||||
| for (int i = 0; i < numChansToCopy; ++i) | |||||
| FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); | |||||
| allocatedData.swapWith (newData); | |||||
| allocatedBytes = newTotalBytes; | |||||
| channels = newChannels; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (avoidReallocating && allocatedBytes >= newTotalBytes) | |||||
| { | |||||
| if (clearExtraSpace) | |||||
| allocatedData.clear (newTotalBytes); | |||||
| } | |||||
| else | |||||
| { | |||||
| allocatedBytes = newTotalBytes; | |||||
| allocatedData.allocate (newTotalBytes, clearExtraSpace); | |||||
| channels = reinterpret_cast <float**> (allocatedData.getData()); | |||||
| } | |||||
| float* chan = reinterpret_cast <float*> (allocatedData + channelListSize); | |||||
| for (int i = 0; i < newNumChannels; ++i) | |||||
| { | |||||
| channels[i] = chan; | |||||
| chan += allocatedSamplesPerChannel; | |||||
| } | |||||
| } | |||||
| channels [newNumChannels] = 0; | |||||
| size = newNumSamples; | |||||
| numChannels = newNumChannels; | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::clear() noexcept | |||||
| { | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| FloatVectorOperations::clear (channels[i], size); | |||||
| } | |||||
| void AudioSampleBuffer::clear (const int startSample, | |||||
| const int numSamples) noexcept | |||||
| { | |||||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| FloatVectorOperations::clear (channels[i] + startSample, numSamples); | |||||
| } | |||||
| void AudioSampleBuffer::clear (const int channel, | |||||
| const int startSample, | |||||
| const int numSamples) noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
| FloatVectorOperations::clear (channels [channel] + startSample, numSamples); | |||||
| } | |||||
| void AudioSampleBuffer::applyGain (const int channel, | |||||
| const int startSample, | |||||
| int numSamples, | |||||
| const float gain) noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
| if (gain != 1.0f) | |||||
| { | |||||
| float* const d = channels [channel] + startSample; | |||||
| if (gain == 0.0f) | |||||
| FloatVectorOperations::clear (d, numSamples); | |||||
| else | |||||
| FloatVectorOperations::multiply (d, gain, numSamples); | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::applyGainRamp (const int channel, | |||||
| const int startSample, | |||||
| int numSamples, | |||||
| float startGain, | |||||
| float endGain) noexcept | |||||
| { | |||||
| 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; | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::applyGain (const int startSample, | |||||
| const int numSamples, | |||||
| const float gain) noexcept | |||||
| { | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| applyGain (i, startSample, numSamples, gain); | |||||
| } | |||||
| void AudioSampleBuffer::applyGain (const float gain) noexcept | |||||
| { | |||||
| applyGain (0, size, gain); | |||||
| } | |||||
| void AudioSampleBuffer::applyGainRamp (const int startSample, | |||||
| const int numSamples, | |||||
| const float startGain, | |||||
| const float endGain) noexcept | |||||
| { | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| applyGainRamp (i, startSample, numSamples, startGain, endGain); | |||||
| } | |||||
| void AudioSampleBuffer::addFrom (const int destChannel, | |||||
| const int destStartSample, | |||||
| const AudioSampleBuffer& source, | |||||
| const int sourceChannel, | |||||
| const int sourceStartSample, | |||||
| int numSamples, | |||||
| const float gain) 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 (gain != 0.0f && numSamples > 0) | |||||
| { | |||||
| float* const d = channels [destChannel] + destStartSample; | |||||
| const float* const s = source.channels [sourceChannel] + sourceStartSample; | |||||
| if (gain != 1.0f) | |||||
| FloatVectorOperations::addWithMultiply (d, s, gain, numSamples); | |||||
| else | |||||
| FloatVectorOperations::add (d, s, numSamples); | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::addFrom (const int destChannel, | |||||
| const int destStartSample, | |||||
| const float* source, | |||||
| int numSamples, | |||||
| const float gain) noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
| jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
| jassert (source != nullptr); | |||||
| if (gain != 0.0f && numSamples > 0) | |||||
| { | |||||
| float* const d = channels [destChannel] + destStartSample; | |||||
| if (gain != 1.0f) | |||||
| FloatVectorOperations::addWithMultiply (d, source, gain, numSamples); | |||||
| else | |||||
| FloatVectorOperations::add (d, source, numSamples); | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::addFromWithRamp (const int destChannel, | |||||
| const int destStartSample, | |||||
| const float* source, | |||||
| int numSamples, | |||||
| float startGain, | |||||
| const 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)) | |||||
| { | |||||
| const float increment = (endGain - startGain) / numSamples; | |||||
| float* d = channels [destChannel] + destStartSample; | |||||
| while (--numSamples >= 0) | |||||
| { | |||||
| *d++ += startGain * *source++; | |||||
| startGain += increment; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::copyFrom (const int destChannel, | |||||
| const int destStartSample, | |||||
| const AudioSampleBuffer& source, | |||||
| const int sourceChannel, | |||||
| const 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) | |||||
| { | |||||
| FloatVectorOperations::copy (channels [destChannel] + destStartSample, | |||||
| source.channels [sourceChannel] + sourceStartSample, | |||||
| numSamples); | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::copyFrom (const int destChannel, | |||||
| const int destStartSample, | |||||
| const float* source, | |||||
| int numSamples) noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
| jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
| jassert (source != nullptr); | |||||
| if (numSamples > 0) | |||||
| { | |||||
| FloatVectorOperations::copy (channels [destChannel] + destStartSample, | |||||
| source, | |||||
| numSamples); | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::copyFrom (const int destChannel, | |||||
| const int destStartSample, | |||||
| const float* source, | |||||
| int numSamples, | |||||
| const float gain) noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (destChannel, numChannels)); | |||||
| jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||||
| jassert (source != nullptr); | |||||
| if (numSamples > 0) | |||||
| { | |||||
| float* d = channels [destChannel] + destStartSample; | |||||
| if (gain != 1.0f) | |||||
| { | |||||
| if (gain == 0) | |||||
| FloatVectorOperations::clear (d, numSamples); | |||||
| else | |||||
| FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||||
| } | |||||
| else | |||||
| { | |||||
| FloatVectorOperations::copy (d, source, numSamples); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::copyFromWithRamp (const int destChannel, | |||||
| const 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)) | |||||
| { | |||||
| const float increment = (endGain - startGain) / numSamples; | |||||
| float* d = channels [destChannel] + destStartSample; | |||||
| while (--numSamples >= 0) | |||||
| { | |||||
| *d++ = startGain * *source++; | |||||
| startGain += increment; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioSampleBuffer::findMinMax (const int channel, | |||||
| const int startSample, | |||||
| int numSamples, | |||||
| float& minVal, | |||||
| float& maxVal) const noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
| FloatVectorOperations::findMinAndMax (channels [channel] + startSample, | |||||
| numSamples, minVal, maxVal); | |||||
| } | |||||
| float AudioSampleBuffer::getMagnitude (const int channel, | |||||
| const int startSample, | |||||
| const int numSamples) const noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
| float mn, mx; | |||||
| findMinMax (channel, startSample, numSamples, mn, mx); | |||||
| return jmax (mn, -mn, mx, -mx); | |||||
| } | |||||
| float AudioSampleBuffer::getMagnitude (const int startSample, | |||||
| const int numSamples) const noexcept | |||||
| { | |||||
| float mag = 0.0f; | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| mag = jmax (mag, getMagnitude (i, startSample, numSamples)); | |||||
| return mag; | |||||
| } | |||||
| float AudioSampleBuffer::getRMSLevel (const int channel, | |||||
| const int startSample, | |||||
| const int numSamples) const noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||||
| if (numSamples <= 0 || channel < 0 || channel >= numChannels) | |||||
| 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); | |||||
| } | |||||
| @@ -0,0 +1,444 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_AUDIOSAMPLEBUFFER_H_INCLUDED | |||||
| #define JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| A multi-channel buffer of 32-bit floating point audio samples. | |||||
| */ | |||||
| class JUCE_API AudioSampleBuffer | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** 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 numChannels, | |||||
| int numSamples) noexcept; | |||||
| /** 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 numChannels 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 numChannels, | |||||
| int numSamples) noexcept; | |||||
| /** 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 numChannels 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 numChannels, | |||||
| int startSample, | |||||
| int numSamples) noexcept; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** Destructor. | |||||
| This will free any memory allocated by the buffer. | |||||
| */ | |||||
| ~AudioSampleBuffer() noexcept; | |||||
| //============================================================================== | |||||
| /** 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 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! | |||||
| */ | |||||
| float* getSampleData (const int channelNumber) const noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||||
| return channels [channelNumber]; | |||||
| } | |||||
| /** Returns a pointer to a sample in one of the buffer's channels. | |||||
| For speed, this doesn't check whether the channel and sample number | |||||
| are out-of-range, so be careful when using it! | |||||
| */ | |||||
| float* getSampleData (const int channelNumber, | |||||
| const int sampleOffset) const noexcept | |||||
| { | |||||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||||
| jassert (isPositiveAndBelow (sampleOffset, size)); | |||||
| return channels [channelNumber] + sampleOffset; | |||||
| } | |||||
| /** 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** getArrayOfChannels() const noexcept { 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; | |||||
| /** 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 numChannels 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 | |||||
| */ | |||||
| void setDataToReferTo (float** dataToReferTo, | |||||
| int numChannels, | |||||
| int numSamples) noexcept; | |||||
| //============================================================================== | |||||
| /** Clears all the samples in all channels. */ | |||||
| void clear() noexcept; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** Applies a gain multiple to all the audio data. */ | |||||
| void applyGain (float gain) noexcept; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** 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 = 1.0f) noexcept; | |||||
| /** 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 = 1.0f) noexcept; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** 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; | |||||
| /** Finds the highest and lowest sample values in a given range. | |||||
| @param channel the channel to read from | |||||
| @param startSample the start sample within the channel | |||||
| @param numSamples the number of samples to check | |||||
| @param minVal on return, the lowest value that was found | |||||
| @param maxVal on return, the highest value that was found | |||||
| */ | |||||
| void findMinMax (int channel, | |||||
| int startSample, | |||||
| int numSamples, | |||||
| float& minVal, | |||||
| float& maxVal) const noexcept; | |||||
| /** Finds the highest absolute sample value within a region of a channel. | |||||
| */ | |||||
| float getMagnitude (int channel, | |||||
| int startSample, | |||||
| int numSamples) const noexcept; | |||||
| /** Finds the highest absolute sample value within a region on all channels. | |||||
| */ | |||||
| float getMagnitude (int startSample, | |||||
| int numSamples) const noexcept; | |||||
| /** Returns the root mean squared level for a region of a channel. | |||||
| */ | |||||
| float getRMSLevel (int channel, | |||||
| int startSample, | |||||
| int numSamples) const noexcept; | |||||
| private: | |||||
| //============================================================================== | |||||
| int numChannels, size; | |||||
| size_t allocatedBytes; | |||||
| float** channels; | |||||
| HeapBlock <char, true> allocatedData; | |||||
| float* preallocatedChannelSpace [32]; | |||||
| void allocateData(); | |||||
| void allocateChannels (float* const* dataToReferTo, int offset); | |||||
| JUCE_LEAK_DETECTOR (AudioSampleBuffer) | |||||
| }; | |||||
| #endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED | |||||
| @@ -0,0 +1,344 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| namespace FloatVectorHelpers | |||||
| { | |||||
| static bool sse2Present = false; | |||||
| static bool isSSE2Available() noexcept | |||||
| { | |||||
| if (sse2Present) | |||||
| return true; | |||||
| sse2Present = SystemStats::hasSSE2(); | |||||
| return sse2Present; | |||||
| } | |||||
| inline static bool isAligned (const void* p) noexcept | |||||
| { | |||||
| return (((pointer_sized_int) p) & 15) == 0; | |||||
| } | |||||
| inline static void mmEmpty() noexcept | |||||
| { | |||||
| #if ! JUCE_64BIT | |||||
| _mm_empty(); | |||||
| #endif | |||||
| } | |||||
| static inline float findMinimumOrMaximum (const float* src, int num, const bool isMinimum) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const int numLongOps = num / 4; | |||||
| if (numLongOps > 1 && FloatVectorHelpers::isSSE2Available()) | |||||
| { | |||||
| __m128 val; | |||||
| #define JUCE_MINIMUMMAXIMUM_SSE_LOOP(loadOp, minMaxOp) \ | |||||
| val = loadOp (src); \ | |||||
| src += 4; \ | |||||
| for (int i = 1; i < numLongOps; ++i) \ | |||||
| { \ | |||||
| const __m128 s = loadOp (src); \ | |||||
| val = minMaxOp (val, s); \ | |||||
| src += 4; \ | |||||
| } | |||||
| if (isMinimum) | |||||
| { | |||||
| if (FloatVectorHelpers::isAligned (src)) { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_load_ps, _mm_min_ps) } | |||||
| else { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_loadu_ps, _mm_min_ps) } | |||||
| } | |||||
| else | |||||
| { | |||||
| if (FloatVectorHelpers::isAligned (src)) { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_load_ps, _mm_max_ps) } | |||||
| else { JUCE_MINIMUMMAXIMUM_SSE_LOOP (_mm_loadu_ps,_mm_max_ps) } | |||||
| } | |||||
| float localVal; | |||||
| { | |||||
| float vals[4]; | |||||
| _mm_storeu_ps (vals, val); | |||||
| FloatVectorHelpers::mmEmpty(); | |||||
| localVal = isMinimum ? jmin (vals[0], vals[1], vals[2], vals[3]) | |||||
| : jmax (vals[0], vals[1], vals[2], vals[3]); | |||||
| } | |||||
| num &= 3; | |||||
| for (int i = 0; i < num; ++i) | |||||
| localVal = isMinimum ? jmin (localVal, src[i]) | |||||
| : jmax (localVal, src[i]); | |||||
| return localVal; | |||||
| } | |||||
| #endif | |||||
| return isMinimum ? juce::findMinimum (src, num) | |||||
| : juce::findMaximum (src, num); | |||||
| } | |||||
| } | |||||
| #define JUCE_BEGIN_SSE_OP \ | |||||
| if (FloatVectorHelpers::isSSE2Available()) \ | |||||
| { \ | |||||
| const int numLongOps = num / 4; | |||||
| #define JUCE_FINISH_SSE_OP(normalOp) \ | |||||
| FloatVectorHelpers::mmEmpty(); \ | |||||
| num &= 3; \ | |||||
| if (num == 0) return; \ | |||||
| } \ | |||||
| for (int i = 0; i < num; ++i) normalOp; | |||||
| #define JUCE_SSE_LOOP(sseOp, srcLoad, dstLoad, dstStore, locals, increment) \ | |||||
| for (int i = 0; i < numLongOps; ++i) \ | |||||
| { \ | |||||
| locals (srcLoad, dstLoad); \ | |||||
| dstStore (dest, sseOp); \ | |||||
| increment; \ | |||||
| } | |||||
| #define JUCE_INCREMENT_SRC_DEST dest += 4; src += 4; | |||||
| #define JUCE_INCREMENT_DEST dest += 4; | |||||
| #define JUCE_LOAD_NONE(srcLoad, dstLoad) | |||||
| #define JUCE_LOAD_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); | |||||
| #define JUCE_LOAD_SRC(srcLoad, dstLoad) const __m128 s = srcLoad (src); | |||||
| #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const __m128 d = dstLoad (dest); const __m128 s = srcLoad (src); | |||||
| #define JUCE_PERFORM_SSE_OP_DEST(normalOp, sseOp, locals) \ | |||||
| JUCE_BEGIN_SSE_OP \ | |||||
| if (FloatVectorHelpers::isAligned (dest)) JUCE_SSE_LOOP (sseOp, dummy, _mm_load_ps, _mm_store_ps, locals, JUCE_INCREMENT_DEST) \ | |||||
| else JUCE_SSE_LOOP (sseOp, dummy, _mm_loadu_ps, _mm_storeu_ps, locals, JUCE_INCREMENT_DEST) \ | |||||
| JUCE_FINISH_SSE_OP (normalOp) | |||||
| #define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) \ | |||||
| JUCE_BEGIN_SSE_OP \ | |||||
| if (FloatVectorHelpers::isAligned (dest)) \ | |||||
| { \ | |||||
| if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_load_ps, _mm_store_ps, locals, increment) \ | |||||
| else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_load_ps, _mm_store_ps, locals, increment) \ | |||||
| }\ | |||||
| else \ | |||||
| { \ | |||||
| if (FloatVectorHelpers::isAligned (src)) JUCE_SSE_LOOP (sseOp, _mm_load_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \ | |||||
| else JUCE_SSE_LOOP (sseOp, _mm_loadu_ps, _mm_loadu_ps, _mm_storeu_ps, locals, increment) \ | |||||
| } \ | |||||
| JUCE_FINISH_SSE_OP (normalOp) | |||||
| #else | |||||
| #define JUCE_PERFORM_SSE_OP_DEST(normalOp, unused1, unused2) for (int i = 0; i < num; ++i) normalOp; | |||||
| #define JUCE_PERFORM_SSE_OP_SRC_DEST(normalOp, sseOp, locals, increment) for (int i = 0; i < num; ++i) normalOp; | |||||
| #endif | |||||
| void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| vDSP_vclr (dest, 1, num); | |||||
| #else | |||||
| zeromem (dest, num * sizeof (float)); | |||||
| #endif | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::fill (float* dest, float valueToFill, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| vDSP_vfill (&valueToFill, dest, 1, num); | |||||
| #else | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const __m128 val = _mm_load1_ps (&valueToFill); | |||||
| #endif | |||||
| JUCE_PERFORM_SSE_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE) | |||||
| #endif | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::copy (float* dest, const float* src, int num) noexcept | |||||
| { | |||||
| memcpy (dest, src, num * sizeof (float)); | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| vDSP_vsmul (src, 1, &multiplier, dest, 1, num); | |||||
| #else | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const __m128 mult = _mm_load1_ps (&multiplier); | |||||
| #endif | |||||
| JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier, | |||||
| _mm_mul_ps (mult, s), | |||||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST) | |||||
| #endif | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| vDSP_vadd (src, 1, dest, 1, dest, 1, num); | |||||
| #else | |||||
| JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i], | |||||
| _mm_add_ps (d, s), | |||||
| JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) | |||||
| #endif | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const __m128 amountToAdd = _mm_load1_ps (&amount); | |||||
| #endif | |||||
| JUCE_PERFORM_SSE_OP_DEST (dest[i] += amount, | |||||
| _mm_add_ps (d, amountToAdd), | |||||
| JUCE_LOAD_DEST) | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const __m128 mult = _mm_load1_ps (&multiplier); | |||||
| #endif | |||||
| JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] += src[i] * multiplier, | |||||
| _mm_add_ps (d, _mm_mul_ps (mult, s)), | |||||
| JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| vDSP_vmul (src, 1, dest, 1, dest, 1, num); | |||||
| #else | |||||
| JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] *= src[i], | |||||
| _mm_mul_ps (d, s), | |||||
| JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST) | |||||
| #endif | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, float multiplier, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| vDSP_vsmul (dest, 1, &multiplier, dest, 1, num); | |||||
| #else | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const __m128 mult = _mm_load1_ps (&multiplier); | |||||
| #endif | |||||
| JUCE_PERFORM_SSE_OP_DEST (dest[i] *= multiplier, | |||||
| _mm_mul_ps (d, mult), | |||||
| JUCE_LOAD_DEST) | |||||
| #endif | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const __m128 mult = _mm_load1_ps (&multiplier); | |||||
| #endif | |||||
| JUCE_PERFORM_SSE_OP_SRC_DEST (dest[i] = src[i] * multiplier, | |||||
| _mm_mul_ps (mult, _mm_cvtepi32_ps (_mm_loadu_si128 ((const __m128i*) src))), | |||||
| JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST) | |||||
| } | |||||
| void JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int num, float& minResult, float& maxResult) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| const int numLongOps = num / 4; | |||||
| if (numLongOps > 1 && FloatVectorHelpers::isSSE2Available()) | |||||
| { | |||||
| __m128 mn, mx; | |||||
| #define JUCE_MINMAX_SSE_LOOP(loadOp) \ | |||||
| mn = loadOp (src); \ | |||||
| mx = mn; \ | |||||
| src += 4; \ | |||||
| for (int i = 1; i < numLongOps; ++i) \ | |||||
| { \ | |||||
| const __m128 s = loadOp (src); \ | |||||
| mn = _mm_min_ps (mn, s); \ | |||||
| mx = _mm_max_ps (mx, s); \ | |||||
| src += 4; \ | |||||
| } | |||||
| if (FloatVectorHelpers::isAligned (src)) { JUCE_MINMAX_SSE_LOOP (_mm_load_ps) } | |||||
| else { JUCE_MINMAX_SSE_LOOP (_mm_loadu_ps) } | |||||
| float localMin, localMax; | |||||
| { | |||||
| float mns[4], mxs[4]; | |||||
| _mm_storeu_ps (mns, mn); | |||||
| _mm_storeu_ps (mxs, mx); | |||||
| FloatVectorHelpers::mmEmpty(); | |||||
| localMin = jmin (mns[0], mns[1], mns[2], mns[3]); | |||||
| localMax = jmax (mxs[0], mxs[1], mxs[2], mxs[3]); | |||||
| } | |||||
| num &= 3; | |||||
| for (int i = 0; i < num; ++i) | |||||
| { | |||||
| const float s = src[i]; | |||||
| localMin = jmin (localMin, s); | |||||
| localMax = jmax (localMax, s); | |||||
| } | |||||
| minResult = localMin; | |||||
| maxResult = localMax; | |||||
| return; | |||||
| } | |||||
| #endif | |||||
| juce::findMinAndMax (src, num, minResult, maxResult); | |||||
| } | |||||
| float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| return FloatVectorHelpers::findMinimumOrMaximum (src, num, true); | |||||
| #else | |||||
| return juce::findMinimum (src, num); | |||||
| #endif | |||||
| } | |||||
| float JUCE_CALLTYPE FloatVectorOperations::findMaximum (const float* src, int num) noexcept | |||||
| { | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| return FloatVectorHelpers::findMinimumOrMaximum (src, num, false); | |||||
| #else | |||||
| return juce::findMaximum (src, num); | |||||
| #endif | |||||
| } | |||||
| @@ -0,0 +1,79 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_FLOATVECTOROPERATIONS_H_INCLUDED | |||||
| #define JUCE_FLOATVECTOROPERATIONS_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| A collection of simple vector operations on arrays of floats, accelerated with | |||||
| SIMD instructions where possible. | |||||
| */ | |||||
| class JUCE_API FloatVectorOperations | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Clears a vector of floats. */ | |||||
| static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept; | |||||
| /** Copies a repeated value into a vector of floats. */ | |||||
| static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept; | |||||
| /** Copies a vector of floats. */ | |||||
| static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; | |||||
| /** Copies a vector of floats, multiplying each value by a given multiplier */ | |||||
| static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||||
| /** Adds the source values to the destination values. */ | |||||
| static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||||
| /** Adds a fixed value to the destination values. */ | |||||
| static void JUCE_CALLTYPE add (float* dest, float amount, int numValues) noexcept; | |||||
| /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||||
| static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||||
| /** Multiplies the destination values by the source values. */ | |||||
| static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept; | |||||
| /** Multiplies each of the destination values by a fixed multiplier. */ | |||||
| static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; | |||||
| /** Converts a stream of integers to floats, multiplying each one by the given multiplier. */ | |||||
| static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept; | |||||
| /** Finds the miniumum and maximum values in the given array. */ | |||||
| static void JUCE_CALLTYPE findMinAndMax (const float* src, int numValues, float& minResult, float& maxResult) noexcept; | |||||
| /** Finds the miniumum value in the given array. */ | |||||
| static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; | |||||
| /** Finds the maximum value in the given array. */ | |||||
| static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; | |||||
| }; | |||||
| #endif // JUCE_FLOATVECTOROPERATIONS_H_INCLUDED | |||||
| @@ -0,0 +1,103 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_DECIBELS_H_INCLUDED | |||||
| #define JUCE_DECIBELS_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| This class contains some helpful static methods for dealing with decibel values. | |||||
| */ | |||||
| class Decibels | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Converts a dBFS value to its equivalent gain level. | |||||
| A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any | |||||
| decibel value lower than minusInfinityDb will return a gain of 0. | |||||
| */ | |||||
| template <typename Type> | |||||
| static Type decibelsToGain (const Type decibels, | |||||
| const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
| { | |||||
| return decibels > minusInfinityDb ? powf ((Type) 10.0, decibels * (Type) 0.05) | |||||
| : Type(); | |||||
| } | |||||
| /** Converts a gain level into a dBFS value. | |||||
| A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. | |||||
| If the gain is 0 (or negative), then the method will return the value | |||||
| provided as minusInfinityDb. | |||||
| */ | |||||
| template <typename Type> | |||||
| static Type gainToDecibels (const Type gain, | |||||
| const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
| { | |||||
| return gain > Type() ? jmax (minusInfinityDb, (Type) std::log10 (gain) * (Type) 20.0) | |||||
| : minusInfinityDb; | |||||
| } | |||||
| //============================================================================== | |||||
| /** Converts a decibel reading to a string, with the 'dB' suffix. | |||||
| If the decibel value is lower than minusInfinityDb, the return value will | |||||
| be "-INF dB". | |||||
| */ | |||||
| template <typename Type> | |||||
| static String toString (const Type decibels, | |||||
| const int decimalPlaces = 2, | |||||
| const Type minusInfinityDb = (Type) defaultMinusInfinitydB) | |||||
| { | |||||
| String s; | |||||
| if (decibels <= minusInfinityDb) | |||||
| { | |||||
| s = "-INF dB"; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (decibels >= Type()) | |||||
| s << '+'; | |||||
| s << String (decibels, decimalPlaces) << " dB"; | |||||
| } | |||||
| return s; | |||||
| } | |||||
| private: | |||||
| //============================================================================== | |||||
| enum | |||||
| { | |||||
| defaultMinusInfinitydB = -100 | |||||
| }; | |||||
| Decibels(); // This class can't be instantiated, it's just a holder for static methods.. | |||||
| JUCE_DECLARE_NON_COPYABLE (Decibels) | |||||
| }; | |||||
| #endif // JUCE_DECIBELS_H_INCLUDED | |||||
| @@ -0,0 +1,245 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if JUCE_INTEL | |||||
| #define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8 || n > 1.0e-8)) n = 0; | |||||
| #else | |||||
| #define JUCE_SNAP_TO_ZERO(n) | |||||
| #endif | |||||
| //============================================================================== | |||||
| IIRCoefficients::IIRCoefficients() noexcept | |||||
| { | |||||
| zeromem (c, sizeof (c)); | |||||
| } | |||||
| IIRCoefficients::~IIRCoefficients() noexcept {} | |||||
| IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept | |||||
| { | |||||
| memcpy (c, other.c, sizeof (c)); | |||||
| } | |||||
| IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept | |||||
| { | |||||
| memcpy (c, other.c, sizeof (c)); | |||||
| return *this; | |||||
| } | |||||
| IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||||
| double c4, double c5, double c6) noexcept | |||||
| { | |||||
| const double a = 1.0 / c4; | |||||
| c[0] = (float) (c1 * a); | |||||
| c[1] = (float) (c2 * a); | |||||
| c[2] = (float) (c3 * a); | |||||
| c[3] = (float) (c5 * a); | |||||
| c[4] = (float) (c6 * a); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||||
| const double frequency) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0); | |||||
| const double n = 1.0 / tan (double_Pi * frequency / sampleRate); | |||||
| const double nSquared = n * n; | |||||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
| return IIRCoefficients (c1, | |||||
| c1 * 2.0f, | |||||
| c1, | |||||
| 1.0, | |||||
| c1 * 2.0 * (1.0 - nSquared), | |||||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||||
| const double frequency) noexcept | |||||
| { | |||||
| const double n = tan (double_Pi * frequency / sampleRate); | |||||
| const double nSquared = n * n; | |||||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
| return IIRCoefficients (c1, | |||||
| c1 * -2.0f, | |||||
| c1, | |||||
| 1.0, | |||||
| c1 * 2.0 * (nSquared - 1.0), | |||||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||||
| const double cutOffFrequency, | |||||
| const double Q, | |||||
| const float gainFactor) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0); | |||||
| jassert (Q > 0); | |||||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||||
| const double aminus1 = A - 1.0; | |||||
| const double aplus1 = A + 1.0; | |||||
| const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; | |||||
| const double coso = std::cos (omega); | |||||
| const double beta = std::sin (omega) * std::sqrt (A) / Q; | |||||
| const double aminus1TimesCoso = aminus1 * coso; | |||||
| return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta), | |||||
| A * 2.0 * (aminus1 - aplus1 * coso), | |||||
| A * (aplus1 - aminus1TimesCoso - beta), | |||||
| aplus1 + aminus1TimesCoso + beta, | |||||
| -2.0 * (aminus1 + aplus1 * coso), | |||||
| aplus1 + aminus1TimesCoso - beta); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||||
| const double cutOffFrequency, | |||||
| const double Q, | |||||
| const float gainFactor) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0); | |||||
| jassert (Q > 0); | |||||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||||
| const double aminus1 = A - 1.0; | |||||
| const double aplus1 = A + 1.0; | |||||
| const double omega = (double_Pi * 2.0 * jmax (cutOffFrequency, 2.0)) / sampleRate; | |||||
| const double coso = std::cos (omega); | |||||
| const double beta = std::sin (omega) * std::sqrt (A) / Q; | |||||
| const double aminus1TimesCoso = aminus1 * coso; | |||||
| return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta), | |||||
| A * -2.0 * (aminus1 + aplus1 * coso), | |||||
| A * (aplus1 + aminus1TimesCoso - beta), | |||||
| aplus1 - aminus1TimesCoso + beta, | |||||
| 2.0 * (aminus1 - aplus1 * coso), | |||||
| aplus1 - aminus1TimesCoso - beta); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||||
| const double centreFrequency, | |||||
| const double Q, | |||||
| const float gainFactor) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0); | |||||
| jassert (Q > 0); | |||||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||||
| const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; | |||||
| const double alpha = 0.5 * std::sin (omega) / Q; | |||||
| const double c2 = -2.0 * std::cos (omega); | |||||
| const double alphaTimesA = alpha * A; | |||||
| const double alphaOverA = alpha / A; | |||||
| return IIRCoefficients (1.0 + alphaTimesA, | |||||
| c2, | |||||
| 1.0 - alphaTimesA, | |||||
| 1.0 + alphaOverA, | |||||
| c2, | |||||
| 1.0 - alphaOverA); | |||||
| } | |||||
| //============================================================================== | |||||
| IIRFilter::IIRFilter() noexcept | |||||
| : v1 (0), v2 (0), active (false) | |||||
| { | |||||
| } | |||||
| IIRFilter::IIRFilter (const IIRFilter& other) noexcept | |||||
| : v1 (0), v2 (0), active (other.active) | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (other.processLock); | |||||
| coefficients = other.coefficients; | |||||
| } | |||||
| IIRFilter::~IIRFilter() noexcept | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| void IIRFilter::makeInactive() noexcept | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (processLock); | |||||
| active = false; | |||||
| } | |||||
| void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (processLock); | |||||
| coefficients = newCoefficients; | |||||
| active = true; | |||||
| } | |||||
| //============================================================================== | |||||
| void IIRFilter::reset() noexcept | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (processLock); | |||||
| v1 = v2 = 0; | |||||
| } | |||||
| float IIRFilter::processSingleSampleRaw (const float in) noexcept | |||||
| { | |||||
| float out = coefficients.c[0] * in + v1; | |||||
| JUCE_SNAP_TO_ZERO (out); | |||||
| v1 = coefficients.c[1] * in - coefficients.c[3] * out + v2; | |||||
| v2 = coefficients.c[2] * in - coefficients.c[4] * out; | |||||
| return out; | |||||
| } | |||||
| void IIRFilter::processSamples (float* const samples, | |||||
| const int numSamples) noexcept | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (processLock); | |||||
| if (active) | |||||
| { | |||||
| const float c0 = coefficients.c[0]; | |||||
| const float c1 = coefficients.c[1]; | |||||
| const float c2 = coefficients.c[2]; | |||||
| const float c3 = coefficients.c[3]; | |||||
| const float c4 = coefficients.c[4]; | |||||
| float lv1 = v1, lv2 = v2; | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| const float in = samples[i]; | |||||
| const float out = c0 * in + lv1; | |||||
| samples[i] = out; | |||||
| lv1 = c1 * in - c3 * out + lv2; | |||||
| lv2 = c2 * in - c4 * out; | |||||
| } | |||||
| JUCE_SNAP_TO_ZERO (lv1); v1 = lv1; | |||||
| JUCE_SNAP_TO_ZERO (lv2); v2 = lv2; | |||||
| } | |||||
| } | |||||
| #undef JUCE_SNAP_TO_ZERO | |||||
| @@ -0,0 +1,172 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_IIRFILTER_H_INCLUDED | |||||
| #define JUCE_IIRFILTER_H_INCLUDED | |||||
| class IIRFilter; | |||||
| //============================================================================== | |||||
| /** | |||||
| A set of coefficients for use in an IIRFilter object. | |||||
| @see IIRFilter | |||||
| */ | |||||
| class JUCE_API IIRCoefficients | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a null set of coefficients (which will produce silence). */ | |||||
| IIRCoefficients() noexcept; | |||||
| /** Directly constructs an object from the raw coefficients. | |||||
| Most people will want to use the static methods instead of this, but | |||||
| the constructor is public to allow tinkerers to create their own custom | |||||
| filters! | |||||
| */ | |||||
| IIRCoefficients (double c1, double c2, double c3, | |||||
| double c4, double c5, double c6) noexcept; | |||||
| /** Creates a copy of another filter. */ | |||||
| IIRCoefficients (const IIRCoefficients&) noexcept; | |||||
| /** Creates a copy of another filter. */ | |||||
| IIRCoefficients& operator= (const IIRCoefficients&) noexcept; | |||||
| /** Destructor. */ | |||||
| ~IIRCoefficients() noexcept; | |||||
| /** Returns the coefficients for a low-pass filter. */ | |||||
| static IIRCoefficients makeLowPass (double sampleRate, | |||||
| double frequency) noexcept; | |||||
| /** Returns the coefficients for a high-pass filter. */ | |||||
| static IIRCoefficients makeHighPass (double sampleRate, | |||||
| double frequency) noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | |||||
| The gain is a scale factor that the low frequencies are multiplied by, so values | |||||
| greater than 1.0 will boost the low frequencies, values less than 1.0 will | |||||
| attenuate them. | |||||
| */ | |||||
| static IIRCoefficients makeLowShelf (double sampleRate, | |||||
| double cutOffFrequency, | |||||
| double Q, | |||||
| float gainFactor) noexcept; | |||||
| /** Returns the coefficients for a high-pass shelf filter with variable Q and gain. | |||||
| The gain is a scale factor that the high frequencies are multiplied by, so values | |||||
| greater than 1.0 will boost the high frequencies, values less than 1.0 will | |||||
| attenuate them. | |||||
| */ | |||||
| static IIRCoefficients makeHighShelf (double sampleRate, | |||||
| double cutOffFrequency, | |||||
| double Q, | |||||
| float gainFactor) noexcept; | |||||
| /** Returns the coefficients for a peak filter centred around a | |||||
| given frequency, with a variable Q and gain. | |||||
| The gain is a scale factor that the centre frequencies are multiplied by, so | |||||
| values greater than 1.0 will boost the centre frequencies, values less than | |||||
| 1.0 will attenuate them. | |||||
| */ | |||||
| static IIRCoefficients makePeakFilter (double sampleRate, | |||||
| double centreFrequency, | |||||
| double Q, | |||||
| float gainFactor) noexcept; | |||||
| private: | |||||
| friend class IIRFilter; | |||||
| float c[5]; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| An IIR filter that can perform low, high, or band-pass filtering on an | |||||
| audio signal. | |||||
| @see IIRCoefficient, IIRFilterAudioSource | |||||
| */ | |||||
| class JUCE_API IIRFilter | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a filter. | |||||
| Initially the filter is inactive, so will have no effect on samples that | |||||
| you process with it. Use the setCoefficients() method to turn it into the | |||||
| type of filter needed. | |||||
| */ | |||||
| IIRFilter() noexcept; | |||||
| /** Creates a copy of another filter. */ | |||||
| IIRFilter (const IIRFilter&) noexcept; | |||||
| /** Destructor. */ | |||||
| ~IIRFilter() noexcept; | |||||
| //============================================================================== | |||||
| /** Clears the filter so that any incoming data passes through unchanged. */ | |||||
| void makeInactive() noexcept; | |||||
| /** Applies a set of coefficients to this filter. */ | |||||
| void setCoefficients (const IIRCoefficients& newCoefficients) noexcept; | |||||
| /** Returns the coefficients that this filter is using. */ | |||||
| IIRCoefficients getCoefficients() const noexcept { return coefficients; } | |||||
| //============================================================================== | |||||
| /** Resets the filter's processing pipeline, ready to start a new stream of data. | |||||
| Note that this clears the processing state, but the type of filter and | |||||
| its coefficients aren't changed. To put a filter into an inactive state, use | |||||
| the makeInactive() method. | |||||
| */ | |||||
| void reset() noexcept; | |||||
| /** Performs the filter operation on the given set of samples. */ | |||||
| void processSamples (float* samples, int numSamples) noexcept; | |||||
| /** Processes a single sample, without any locking or checking. | |||||
| Use this if you need fast processing of a single value, but be aware that | |||||
| this isn't thread-safe in the way that processSamples() is. | |||||
| */ | |||||
| float processSingleSampleRaw (float sample) noexcept; | |||||
| protected: | |||||
| //============================================================================== | |||||
| SpinLock processLock; | |||||
| IIRCoefficients coefficients; | |||||
| float v1, v2; | |||||
| bool active; | |||||
| IIRFilter& operator= (const IIRFilter&); | |||||
| JUCE_LEAK_DETECTOR (IIRFilter) | |||||
| }; | |||||
| #endif // JUCE_IIRFILTER_H_INCLUDED | |||||
| @@ -0,0 +1,200 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace LagrangeHelpers | |||||
| { | |||||
| template <int k> | |||||
| struct ResampleHelper | |||||
| { | |||||
| static forcedinline void calc (float& a, float b) { a *= b * (1.0f / k); } | |||||
| }; | |||||
| template<> | |||||
| struct ResampleHelper <0> | |||||
| { | |||||
| static forcedinline void calc (float&, float) {} | |||||
| }; | |||||
| template <int k> | |||||
| static forcedinline float calcCoefficient (float input, const float offset) noexcept | |||||
| { | |||||
| ResampleHelper <0 - k>::calc (input, -2.0f - offset); | |||||
| ResampleHelper <1 - k>::calc (input, -1.0f - offset); | |||||
| ResampleHelper <2 - k>::calc (input, 0.0f - offset); | |||||
| ResampleHelper <3 - k>::calc (input, 1.0f - offset); | |||||
| ResampleHelper <4 - k>::calc (input, 2.0f - offset); | |||||
| return input; | |||||
| } | |||||
| static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | |||||
| { | |||||
| return calcCoefficient<0> (inputs[4], offset) | |||||
| + calcCoefficient<1> (inputs[3], offset) | |||||
| + calcCoefficient<2> (inputs[2], offset) | |||||
| + calcCoefficient<3> (inputs[1], offset) | |||||
| + calcCoefficient<4> (inputs[0], offset); | |||||
| } | |||||
| static forcedinline void push (float* inputs, const float newValue) noexcept | |||||
| { | |||||
| inputs[4] = inputs[3]; | |||||
| inputs[3] = inputs[2]; | |||||
| inputs[2] = inputs[1]; | |||||
| inputs[1] = inputs[0]; | |||||
| inputs[0] = newValue; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| LagrangeInterpolator::LagrangeInterpolator() { reset(); } | |||||
| LagrangeInterpolator::~LagrangeInterpolator() {} | |||||
| void LagrangeInterpolator::reset() noexcept | |||||
| { | |||||
| subSamplePos = 1.0; | |||||
| for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) | |||||
| lastInputSamples[i] = 0; | |||||
| } | |||||
| int LagrangeInterpolator::process (const double actualRatio, const float* in, | |||||
| float* out, const int numOut) noexcept | |||||
| { | |||||
| if (actualRatio == 1.0) | |||||
| { | |||||
| memcpy (out, in, numOut * sizeof (float)); | |||||
| if (numOut >= 4) | |||||
| { | |||||
| memcpy (lastInputSamples, in + (numOut - 4), 4 * sizeof (float)); | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = 0; i < numOut; ++i) | |||||
| LagrangeHelpers::push (lastInputSamples, in[i]); | |||||
| } | |||||
| return numOut; | |||||
| } | |||||
| const float* const originalIn = in; | |||||
| double pos = subSamplePos; | |||||
| if (actualRatio < 1.0) | |||||
| { | |||||
| for (int i = numOut; --i >= 0;) | |||||
| { | |||||
| if (pos >= 1.0) | |||||
| { | |||||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||||
| pos -= 1.0; | |||||
| } | |||||
| *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); | |||||
| pos += actualRatio; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = numOut; --i >= 0;) | |||||
| { | |||||
| while (pos < actualRatio) | |||||
| { | |||||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||||
| pos += 1.0; | |||||
| } | |||||
| pos -= actualRatio; | |||||
| *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, 1.0f - (float) pos); | |||||
| } | |||||
| } | |||||
| subSamplePos = pos; | |||||
| return (int) (in - originalIn); | |||||
| } | |||||
| int LagrangeInterpolator::processAdding (const double actualRatio, const float* in, | |||||
| float* out, const int numOut, const float gain) noexcept | |||||
| { | |||||
| if (actualRatio == 1.0) | |||||
| { | |||||
| if (gain != 1.0f) | |||||
| { | |||||
| for (int i = 0; i < numOut; ++i) | |||||
| out[i] += in[i] * gain; | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = 0; i < numOut; ++i) | |||||
| out[i] += in[i]; | |||||
| } | |||||
| if (numOut >= 4) | |||||
| { | |||||
| memcpy (lastInputSamples, in + (numOut - 4), 4 * sizeof (float)); | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = 0; i < numOut; ++i) | |||||
| LagrangeHelpers::push (lastInputSamples, in[i]); | |||||
| } | |||||
| return numOut; | |||||
| } | |||||
| const float* const originalIn = in; | |||||
| double pos = subSamplePos; | |||||
| if (actualRatio < 1.0) | |||||
| { | |||||
| for (int i = numOut; --i >= 0;) | |||||
| { | |||||
| if (pos >= 1.0) | |||||
| { | |||||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||||
| pos -= 1.0; | |||||
| } | |||||
| *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); | |||||
| pos += actualRatio; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = numOut; --i >= 0;) | |||||
| { | |||||
| while (pos < actualRatio) | |||||
| { | |||||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||||
| pos += 1.0; | |||||
| } | |||||
| pos -= actualRatio; | |||||
| *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); | |||||
| } | |||||
| } | |||||
| subSamplePos = pos; | |||||
| return (int) (in - originalIn); | |||||
| } | |||||
| @@ -0,0 +1,93 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_LAGRANGEINTERPOLATOR_H_INCLUDED | |||||
| #define JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | |||||
| Note that the resampler is stateful, so when there's a break in the continuity | |||||
| of the input stream you're feeding it, you should call reset() before feeding | |||||
| it any new data. And like with any other stateful filter, if you're resampling | |||||
| multiple channels, make sure each one uses its own LagrangeInterpolator | |||||
| object. | |||||
| */ | |||||
| class JUCE_API LagrangeInterpolator | |||||
| { | |||||
| public: | |||||
| LagrangeInterpolator(); | |||||
| ~LagrangeInterpolator(); | |||||
| /** Resets the state of the interpolator. | |||||
| Call this when there's a break in the continuity of the input data stream. | |||||
| */ | |||||
| void reset() noexcept; | |||||
| /** Resamples a stream of samples. | |||||
| @param speedRatio the number of input samples to use for each output sample | |||||
| @param inputSamples the source data to read from. This must contain at | |||||
| least (speedRatio * numOutputSamplesToProduce) samples. | |||||
| @param outputSamples the buffer to write the results into | |||||
| @param numOutputSamplesToProduce the number of output samples that should be created | |||||
| @returns the actual number of input samples that were used | |||||
| */ | |||||
| int process (double speedRatio, | |||||
| const float* inputSamples, | |||||
| float* outputSamples, | |||||
| int numOutputSamplesToProduce) noexcept; | |||||
| /** Resamples a stream of samples, adding the results to the output data | |||||
| with a gain. | |||||
| @param speedRatio the number of input samples to use for each output sample | |||||
| @param inputSamples the source data to read from. This must contain at | |||||
| least (speedRatio * numOutputSamplesToProduce) samples. | |||||
| @param outputSamples the buffer to write the results to - the result values will be added | |||||
| to any pre-existing data in this buffer after being multiplied by | |||||
| the gain factor | |||||
| @param numOutputSamplesToProduce the number of output samples that should be created | |||||
| @param gain a gain factor to multiply the resulting samples by before | |||||
| adding them to the destination buffer | |||||
| @returns the actual number of input samples that were used | |||||
| */ | |||||
| int processAdding (double speedRatio, | |||||
| const float* inputSamples, | |||||
| float* outputSamples, | |||||
| int numOutputSamplesToProduce, | |||||
| float gain) noexcept; | |||||
| private: | |||||
| float lastInputSamples[5]; | |||||
| double subSamplePos; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator) | |||||
| }; | |||||
| #endif // JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED | |||||
| @@ -0,0 +1,323 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_REVERB_H_INCLUDED | |||||
| #define JUCE_REVERB_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| Performs a simple reverb effect on a stream of audio data. | |||||
| This is a simple stereo reverb, based on the technique and tunings used in FreeVerb. | |||||
| Use setSampleRate() to prepare it, and then call processStereo() or processMono() to | |||||
| apply the reverb to your audio data. | |||||
| @see ReverbAudioSource | |||||
| */ | |||||
| class Reverb | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| Reverb() | |||||
| { | |||||
| setParameters (Parameters()); | |||||
| setSampleRate (44100.0); | |||||
| } | |||||
| //============================================================================== | |||||
| /** Holds the parameters being used by a Reverb object. */ | |||||
| struct Parameters | |||||
| { | |||||
| Parameters() noexcept | |||||
| : roomSize (0.5f), | |||||
| damping (0.5f), | |||||
| wetLevel (0.33f), | |||||
| dryLevel (0.4f), | |||||
| width (1.0f), | |||||
| freezeMode (0) | |||||
| {} | |||||
| float roomSize; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ | |||||
| float damping; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ | |||||
| float wetLevel; /**< Wet level, 0 to 1.0 */ | |||||
| float dryLevel; /**< Dry level, 0 to 1.0 */ | |||||
| float width; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ | |||||
| float freezeMode; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 | |||||
| put the reverb into a continuous feedback loop. */ | |||||
| }; | |||||
| //============================================================================== | |||||
| /** Returns the reverb's current parameters. */ | |||||
| const Parameters& getParameters() const noexcept { return parameters; } | |||||
| /** Applies a new set of parameters to the reverb. | |||||
| Note that this doesn't attempt to lock the reverb, so if you call this in parallel with | |||||
| the process method, you may get artifacts. | |||||
| */ | |||||
| void setParameters (const Parameters& newParams) | |||||
| { | |||||
| const float wetScaleFactor = 3.0f; | |||||
| const float dryScaleFactor = 2.0f; | |||||
| const float wet = newParams.wetLevel * wetScaleFactor; | |||||
| wet1 = wet * (newParams.width * 0.5f + 0.5f); | |||||
| wet2 = wet * (1.0f - newParams.width) * 0.5f; | |||||
| dry = newParams.dryLevel * dryScaleFactor; | |||||
| gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; | |||||
| parameters = newParams; | |||||
| shouldUpdateDamping = true; | |||||
| } | |||||
| //============================================================================== | |||||
| /** Sets the sample rate that will be used for the reverb. | |||||
| You must call this before the process methods, in order to tell it the correct sample rate. | |||||
| */ | |||||
| void setSampleRate (const double sampleRate) | |||||
| { | |||||
| jassert (sampleRate > 0); | |||||
| static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz) | |||||
| static const short allPassTunings[] = { 556, 441, 341, 225 }; | |||||
| const int stereoSpread = 23; | |||||
| const int intSampleRate = (int) sampleRate; | |||||
| for (int i = 0; i < numCombs; ++i) | |||||
| { | |||||
| comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100); | |||||
| comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100); | |||||
| } | |||||
| for (int i = 0; i < numAllPasses; ++i) | |||||
| { | |||||
| allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100); | |||||
| allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100); | |||||
| } | |||||
| shouldUpdateDamping = true; | |||||
| } | |||||
| /** Clears the reverb's buffers. */ | |||||
| void reset() | |||||
| { | |||||
| for (int j = 0; j < numChannels; ++j) | |||||
| { | |||||
| for (int i = 0; i < numCombs; ++i) | |||||
| comb[j][i].clear(); | |||||
| for (int i = 0; i < numAllPasses; ++i) | |||||
| allPass[j][i].clear(); | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| /** Applies the reverb to two stereo channels of audio data. */ | |||||
| void processStereo (float* const left, float* const right, const int numSamples) noexcept | |||||
| { | |||||
| jassert (left != nullptr && right != nullptr); | |||||
| if (shouldUpdateDamping) | |||||
| updateDamping(); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| const float input = (left[i] + right[i]) * gain; | |||||
| float outL = 0, outR = 0; | |||||
| for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel | |||||
| { | |||||
| outL += comb[0][j].process (input); | |||||
| outR += comb[1][j].process (input); | |||||
| } | |||||
| for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series | |||||
| { | |||||
| outL = allPass[0][j].process (outL); | |||||
| outR = allPass[1][j].process (outR); | |||||
| } | |||||
| left[i] = outL * wet1 + outR * wet2 + left[i] * dry; | |||||
| right[i] = outR * wet1 + outL * wet2 + right[i] * dry; | |||||
| } | |||||
| } | |||||
| /** Applies the reverb to a single mono channel of audio data. */ | |||||
| void processMono (float* const samples, const int numSamples) noexcept | |||||
| { | |||||
| jassert (samples != nullptr); | |||||
| if (shouldUpdateDamping) | |||||
| updateDamping(); | |||||
| for (int i = 0; i < numSamples; ++i) | |||||
| { | |||||
| const float input = samples[i] * gain; | |||||
| float output = 0; | |||||
| for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel | |||||
| output += comb[0][j].process (input); | |||||
| for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series | |||||
| output = allPass[0][j].process (output); | |||||
| samples[i] = output * wet1 + input * dry; | |||||
| } | |||||
| } | |||||
| private: | |||||
| //============================================================================== | |||||
| Parameters parameters; | |||||
| volatile bool shouldUpdateDamping; | |||||
| float gain, wet1, wet2, dry; | |||||
| inline static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; } | |||||
| void updateDamping() noexcept | |||||
| { | |||||
| const float roomScaleFactor = 0.28f; | |||||
| const float roomOffset = 0.7f; | |||||
| const float dampScaleFactor = 0.4f; | |||||
| shouldUpdateDamping = false; | |||||
| if (isFrozen (parameters.freezeMode)) | |||||
| setDamping (0.0f, 1.0f); | |||||
| else | |||||
| setDamping (parameters.damping * dampScaleFactor, | |||||
| parameters.roomSize * roomScaleFactor + roomOffset); | |||||
| } | |||||
| void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept | |||||
| { | |||||
| for (int j = 0; j < numChannels; ++j) | |||||
| for (int i = numCombs; --i >= 0;) | |||||
| comb[j][i].setFeedbackAndDamp (roomSizeToUse, dampingToUse); | |||||
| } | |||||
| //============================================================================== | |||||
| class CombFilter | |||||
| { | |||||
| public: | |||||
| CombFilter() noexcept | |||||
| : bufferSize (0), bufferIndex (0), | |||||
| feedback (0), last (0), damp1 (0), damp2 (0) | |||||
| {} | |||||
| void setSize (const int size) | |||||
| { | |||||
| if (size != bufferSize) | |||||
| { | |||||
| bufferIndex = 0; | |||||
| buffer.malloc ((size_t) size); | |||||
| bufferSize = size; | |||||
| } | |||||
| clear(); | |||||
| } | |||||
| void clear() noexcept | |||||
| { | |||||
| last = 0; | |||||
| buffer.clear ((size_t) bufferSize); | |||||
| } | |||||
| void setFeedbackAndDamp (const float f, const float d) noexcept | |||||
| { | |||||
| damp1 = d; | |||||
| damp2 = 1.0f - d; | |||||
| feedback = f; | |||||
| } | |||||
| inline float process (const float input) noexcept | |||||
| { | |||||
| const float output = buffer [bufferIndex]; | |||||
| last = (output * damp2) + (last * damp1); | |||||
| JUCE_UNDENORMALISE (last); | |||||
| float temp = input + (last * feedback); | |||||
| JUCE_UNDENORMALISE (temp); | |||||
| buffer [bufferIndex] = temp; | |||||
| bufferIndex = (bufferIndex + 1) % bufferSize; | |||||
| return output; | |||||
| } | |||||
| private: | |||||
| HeapBlock<float> buffer; | |||||
| int bufferSize, bufferIndex; | |||||
| float feedback, last, damp1, damp2; | |||||
| JUCE_DECLARE_NON_COPYABLE (CombFilter) | |||||
| }; | |||||
| //============================================================================== | |||||
| class AllPassFilter | |||||
| { | |||||
| public: | |||||
| AllPassFilter() noexcept : bufferSize (0), bufferIndex (0) {} | |||||
| void setSize (const int size) | |||||
| { | |||||
| if (size != bufferSize) | |||||
| { | |||||
| bufferIndex = 0; | |||||
| buffer.malloc ((size_t) size); | |||||
| bufferSize = size; | |||||
| } | |||||
| clear(); | |||||
| } | |||||
| void clear() noexcept | |||||
| { | |||||
| buffer.clear ((size_t) bufferSize); | |||||
| } | |||||
| inline float process (const float input) noexcept | |||||
| { | |||||
| const float bufferedValue = buffer [bufferIndex]; | |||||
| float temp = input + (bufferedValue * 0.5f); | |||||
| JUCE_UNDENORMALISE (temp); | |||||
| buffer [bufferIndex] = temp; | |||||
| bufferIndex = (bufferIndex + 1) % bufferSize; | |||||
| return bufferedValue - input; | |||||
| } | |||||
| private: | |||||
| HeapBlock<float> buffer; | |||||
| int bufferSize, bufferIndex; | |||||
| JUCE_DECLARE_NON_COPYABLE (AllPassFilter) | |||||
| }; | |||||
| enum { numCombs = 8, numAllPasses = 4, numChannels = 2 }; | |||||
| CombFilter comb [numChannels][numCombs]; | |||||
| AllPassFilter allPass [numChannels][numAllPasses]; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) | |||||
| }; | |||||
| #endif // JUCE_REVERB_H_INCLUDED | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if defined (JUCE_AUDIO_BASICS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||||
| /* When you add this cpp file to your project, you mustn't include it in a file where you've | |||||
| already included any other headers - just put it inside a file on its own, possibly with your config | |||||
| flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||||
| header files that the compiler may be using. | |||||
| */ | |||||
| #error "Incorrect use of JUCE cpp file" | |||||
| #endif | |||||
| // Your project must contain an AppConfig.h file with your project-specific settings in it, | |||||
| // and your header search path must make it accessible to the module's files. | |||||
| #include "AppConfig.h" | |||||
| #include "juce_audio_basics.h" | |||||
| #ifndef JUCE_USE_SSE_INTRINSICS | |||||
| #define JUCE_USE_SSE_INTRINSICS 1 | |||||
| #endif | |||||
| #if ! JUCE_INTEL | |||||
| #undef JUCE_USE_SSE_INTRINSICS | |||||
| #endif | |||||
| #if JUCE_USE_SSE_INTRINSICS | |||||
| #include <emmintrin.h> | |||||
| #endif | |||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #define JUCE_USE_VDSP_FRAMEWORK 1 | |||||
| #include <Accelerate/Accelerate.h> | |||||
| #endif | |||||
| namespace juce | |||||
| { | |||||
| // START_AUTOINCLUDE buffers/*.cpp, effects/*.cpp, midi/*.cpp, sources/*.cpp, synthesisers/*.cpp | |||||
| #include "buffers/juce_AudioDataConverters.cpp" | |||||
| #include "buffers/juce_AudioSampleBuffer.cpp" | |||||
| #include "buffers/juce_FloatVectorOperations.cpp" | |||||
| #include "effects/juce_IIRFilter.cpp" | |||||
| #include "effects/juce_LagrangeInterpolator.cpp" | |||||
| #include "midi/juce_MidiBuffer.cpp" | |||||
| #include "midi/juce_MidiFile.cpp" | |||||
| #include "midi/juce_MidiKeyboardState.cpp" | |||||
| #include "midi/juce_MidiMessage.cpp" | |||||
| #include "midi/juce_MidiMessageSequence.cpp" | |||||
| #include "sources/juce_BufferingAudioSource.cpp" | |||||
| #include "sources/juce_ChannelRemappingAudioSource.cpp" | |||||
| #include "sources/juce_IIRFilterAudioSource.cpp" | |||||
| #include "sources/juce_MixerAudioSource.cpp" | |||||
| #include "sources/juce_ResamplingAudioSource.cpp" | |||||
| #include "sources/juce_ReverbAudioSource.cpp" | |||||
| #include "sources/juce_ToneGeneratorAudioSource.cpp" | |||||
| #include "synthesisers/juce_Synthesiser.cpp" | |||||
| // END_AUTOINCLUDE | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_AUDIO_BASICS_H_INCLUDED | |||||
| #define JUCE_AUDIO_BASICS_H_INCLUDED | |||||
| #include "../juce_core/juce_core.h" | |||||
| //============================================================================= | |||||
| namespace juce | |||||
| { | |||||
| // START_AUTOINCLUDE buffers, effects, midi, sources, synthesisers | |||||
| #include "buffers/juce_AudioDataConverters.h" | |||||
| #include "buffers/juce_AudioSampleBuffer.h" | |||||
| #include "buffers/juce_FloatVectorOperations.h" | |||||
| #include "effects/juce_Decibels.h" | |||||
| #include "effects/juce_IIRFilter.h" | |||||
| #include "effects/juce_LagrangeInterpolator.h" | |||||
| #include "effects/juce_Reverb.h" | |||||
| #include "midi/juce_MidiBuffer.h" | |||||
| #include "midi/juce_MidiFile.h" | |||||
| #include "midi/juce_MidiKeyboardState.h" | |||||
| #include "midi/juce_MidiMessage.h" | |||||
| #include "midi/juce_MidiMessageSequence.h" | |||||
| #include "sources/juce_AudioSource.h" | |||||
| #include "sources/juce_BufferingAudioSource.h" | |||||
| #include "sources/juce_ChannelRemappingAudioSource.h" | |||||
| #include "sources/juce_IIRFilterAudioSource.h" | |||||
| #include "sources/juce_MixerAudioSource.h" | |||||
| #include "sources/juce_PositionableAudioSource.h" | |||||
| #include "sources/juce_ResamplingAudioSource.h" | |||||
| #include "sources/juce_ReverbAudioSource.h" | |||||
| #include "sources/juce_ToneGeneratorAudioSource.h" | |||||
| #include "synthesisers/juce_Synthesiser.h" | |||||
| // END_AUTOINCLUDE | |||||
| } | |||||
| #endif // JUCE_AUDIO_BASICS_H_INCLUDED | |||||
| @@ -0,0 +1,25 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_audio_basics.cpp" | |||||
| @@ -0,0 +1,24 @@ | |||||
| { | |||||
| "id": "juce_audio_basics", | |||||
| "name": "JUCE audio and midi data classes", | |||||
| "version": "2.1.2", | |||||
| "description": "Classes for audio buffer manipulation, midi message handling, synthesis, etc", | |||||
| "website": "http://www.juce.com/juce", | |||||
| "license": "GPL/Commercial", | |||||
| "dependencies": [ { "id": "juce_core", "version": "matching" } ], | |||||
| "include": "juce_audio_basics.h", | |||||
| "compile": [ { "file": "juce_audio_basics.cpp", "target": "! xcode" }, | |||||
| { "file": "juce_audio_basics.mm", "target": "xcode" } ], | |||||
| "browse": [ "buffers/*", | |||||
| "midi/*", | |||||
| "effects/*", | |||||
| "sources/*", | |||||
| "synthesisers/*" ], | |||||
| "OSXFrameworks": "Accelerate", | |||||
| "iOSFrameworks": "Accelerate" | |||||
| } | |||||
| @@ -0,0 +1,289 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace MidiBufferHelpers | |||||
| { | |||||
| inline int getEventTime (const void* const d) noexcept | |||||
| { | |||||
| return *static_cast <const int*> (d); | |||||
| } | |||||
| inline uint16 getEventDataSize (const void* const d) noexcept | |||||
| { | |||||
| return *reinterpret_cast <const uint16*> (static_cast <const char*> (d) + sizeof (int)); | |||||
| } | |||||
| inline uint16 getEventTotalSize (const void* const d) noexcept | |||||
| { | |||||
| return getEventDataSize (d) + sizeof (int) + 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; | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| MidiBuffer::MidiBuffer() noexcept | |||||
| : bytesUsed (0) | |||||
| { | |||||
| } | |||||
| MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept | |||||
| : bytesUsed (0) | |||||
| { | |||||
| addEvent (message, 0); | |||||
| } | |||||
| MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept | |||||
| : data (other.data), | |||||
| bytesUsed (other.bytesUsed) | |||||
| { | |||||
| } | |||||
| MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept | |||||
| { | |||||
| bytesUsed = other.bytesUsed; | |||||
| data = other.data; | |||||
| return *this; | |||||
| } | |||||
| void MidiBuffer::swapWith (MidiBuffer& other) noexcept | |||||
| { | |||||
| data.swapWith (other.data); | |||||
| std::swap (bytesUsed, other.bytesUsed); | |||||
| } | |||||
| MidiBuffer::~MidiBuffer() | |||||
| { | |||||
| } | |||||
| inline uint8* MidiBuffer::getData() const noexcept | |||||
| { | |||||
| return static_cast <uint8*> (data.getData()); | |||||
| } | |||||
| void MidiBuffer::clear() noexcept | |||||
| { | |||||
| bytesUsed = 0; | |||||
| } | |||||
| void MidiBuffer::clear (const int startSample, const int numSamples) | |||||
| { | |||||
| uint8* const start = findEventAfter (getData(), startSample - 1); | |||||
| uint8* const end = findEventAfter (start, startSample + numSamples - 1); | |||||
| if (end > start) | |||||
| { | |||||
| const int bytesToMove = bytesUsed - (int) (end - getData()); | |||||
| if (bytesToMove > 0) | |||||
| memmove (start, end, (size_t) bytesToMove); | |||||
| bytesUsed -= (int) (end - start); | |||||
| } | |||||
| } | |||||
| 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 <const uint8*> (newData), maxBytes); | |||||
| if (numBytes > 0) | |||||
| { | |||||
| size_t spaceNeeded = (size_t) bytesUsed + (size_t) numBytes + sizeof (int) + sizeof (uint16); | |||||
| data.ensureSize ((spaceNeeded + spaceNeeded / 2 + 8) & ~(size_t) 7); | |||||
| uint8* d = findEventAfter (getData(), sampleNumber); | |||||
| const int bytesToMove = bytesUsed - (int) (d - getData()); | |||||
| if (bytesToMove > 0) | |||||
| memmove (d + numBytes + sizeof (int) + sizeof (uint16), d, (size_t) bytesToMove); | |||||
| *reinterpret_cast <int*> (d) = sampleNumber; | |||||
| d += sizeof (int); | |||||
| *reinterpret_cast <uint16*> (d) = (uint16) numBytes; | |||||
| d += sizeof (uint16); | |||||
| memcpy (d, newData, (size_t) numBytes); | |||||
| bytesUsed += sizeof (int) + sizeof (uint16) + (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); | |||||
| } | |||||
| } | |||||
| void MidiBuffer::ensureSize (size_t minimumNumBytes) | |||||
| { | |||||
| data.ensureSize (minimumNumBytes); | |||||
| } | |||||
| bool MidiBuffer::isEmpty() const noexcept | |||||
| { | |||||
| return bytesUsed == 0; | |||||
| } | |||||
| int MidiBuffer::getNumEvents() const noexcept | |||||
| { | |||||
| int n = 0; | |||||
| const uint8* d = getData(); | |||||
| const uint8* const end = d + bytesUsed; | |||||
| while (d < end) | |||||
| { | |||||
| d += MidiBufferHelpers::getEventTotalSize (d); | |||||
| ++n; | |||||
| } | |||||
| return n; | |||||
| } | |||||
| int MidiBuffer::getFirstEventTime() const noexcept | |||||
| { | |||||
| return bytesUsed > 0 ? MidiBufferHelpers::getEventTime (data.getData()) : 0; | |||||
| } | |||||
| int MidiBuffer::getLastEventTime() const noexcept | |||||
| { | |||||
| if (bytesUsed == 0) | |||||
| return 0; | |||||
| const uint8* d = getData(); | |||||
| const uint8* const endData = d + bytesUsed; | |||||
| for (;;) | |||||
| { | |||||
| const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d); | |||||
| if (nextOne >= endData) | |||||
| return MidiBufferHelpers::getEventTime (d); | |||||
| d = nextOne; | |||||
| } | |||||
| } | |||||
| uint8* MidiBuffer::findEventAfter (uint8* d, const int samplePosition) const noexcept | |||||
| { | |||||
| const uint8* const endData = getData() + bytesUsed; | |||||
| while (d < endData && MidiBufferHelpers::getEventTime (d) <= samplePosition) | |||||
| d += MidiBufferHelpers::getEventTotalSize (d); | |||||
| return d; | |||||
| } | |||||
| //============================================================================== | |||||
| MidiBuffer::Iterator::Iterator (const MidiBuffer& buffer_) noexcept | |||||
| : buffer (buffer_), | |||||
| data (buffer_.getData()) | |||||
| { | |||||
| } | |||||
| MidiBuffer::Iterator::~Iterator() noexcept | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept | |||||
| { | |||||
| data = buffer.getData(); | |||||
| const uint8* dataEnd = data + buffer.bytesUsed; | |||||
| 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.getData() + buffer.bytesUsed) | |||||
| return false; | |||||
| samplePosition = MidiBufferHelpers::getEventTime (data); | |||||
| numBytes = MidiBufferHelpers::getEventDataSize (data); | |||||
| data += sizeof (int) + sizeof (uint16); | |||||
| midiData = data; | |||||
| data += numBytes; | |||||
| return true; | |||||
| } | |||||
| bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept | |||||
| { | |||||
| if (data >= buffer.getData() + buffer.bytesUsed) | |||||
| return false; | |||||
| samplePosition = MidiBufferHelpers::getEventTime (data); | |||||
| const int numBytes = MidiBufferHelpers::getEventDataSize (data); | |||||
| data += sizeof (int) + sizeof (uint16); | |||||
| result = MidiMessage (data, numBytes, samplePosition); | |||||
| data += numBytes; | |||||
| return true; | |||||
| } | |||||
| @@ -0,0 +1,240 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_MIDIBUFFER_H_INCLUDED | |||||
| #define JUCE_MIDIBUFFER_H_INCLUDED | |||||
| #include "juce_MidiMessage.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| 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& other) noexcept; | |||||
| /** Makes a copy of another MidiBuffer. */ | |||||
| MidiBuffer& operator= (const MidiBuffer& other) 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& other) 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& buffer) 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 not set) | |||||
| @param samplePosition on return, this will be the position of the event | |||||
| @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 | |||||
| @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) | |||||
| }; | |||||
| private: | |||||
| //============================================================================== | |||||
| friend class MidiBuffer::Iterator; | |||||
| MemoryBlock data; | |||||
| int bytesUsed; | |||||
| uint8* getData() const noexcept; | |||||
| uint8* findEventAfter (uint8*, int samplePosition) const noexcept; | |||||
| JUCE_LEAK_DETECTOR (MidiBuffer) | |||||
| }; | |||||
| #endif // JUCE_MIDIBUFFER_H_INCLUDED | |||||
| @@ -0,0 +1,424 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace MidiFileHelpers | |||||
| { | |||||
| static void writeVariableLengthInt (OutputStream& out, unsigned int v) | |||||
| { | |||||
| unsigned int buffer = v & 0x7f; | |||||
| while ((v >>= 7) != 0) | |||||
| { | |||||
| buffer <<= 8; | |||||
| buffer |= ((v & 0x7f) | 0x80); | |||||
| } | |||||
| for (;;) | |||||
| { | |||||
| out.writeByte ((char) buffer); | |||||
| if (buffer & 0x80) | |||||
| buffer >>= 8; | |||||
| else | |||||
| break; | |||||
| } | |||||
| } | |||||
| static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept | |||||
| { | |||||
| unsigned int ch = ByteOrder::bigEndianInt (data); | |||||
| data += 4; | |||||
| if (ch != ByteOrder::bigEndianInt ("MThd")) | |||||
| { | |||||
| bool ok = false; | |||||
| if (ch == ByteOrder::bigEndianInt ("RIFF")) | |||||
| { | |||||
| for (int i = 0; i < 8; ++i) | |||||
| { | |||||
| ch = ByteOrder::bigEndianInt (data); | |||||
| data += 4; | |||||
| if (ch == ByteOrder::bigEndianInt ("MThd")) | |||||
| { | |||||
| ok = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (! ok) | |||||
| return false; | |||||
| } | |||||
| unsigned int bytesRemaining = ByteOrder::bigEndianInt (data); | |||||
| data += 4; | |||||
| fileType = (short) ByteOrder::bigEndianShort (data); | |||||
| data += 2; | |||||
| numberOfTracks = (short) ByteOrder::bigEndianShort (data); | |||||
| data += 2; | |||||
| timeFormat = (short) ByteOrder::bigEndianShort (data); | |||||
| data += 2; | |||||
| bytesRemaining -= 6; | |||||
| data += bytesRemaining; | |||||
| return true; | |||||
| } | |||||
| static double convertTicksToSeconds (const double time, | |||||
| const MidiMessageSequence& tempoEvents, | |||||
| const int timeFormat) | |||||
| { | |||||
| if (timeFormat < 0) | |||||
| return time / (-(timeFormat >> 8) * (timeFormat & 0xff)); | |||||
| double lastTime = 0.0, correctedTime = 0.0; | |||||
| const double tickLen = 1.0 / (timeFormat & 0x7fff); | |||||
| double secsPerTick = 0.5 * tickLen; | |||||
| const int numEvents = tempoEvents.getNumEvents(); | |||||
| for (int i = 0; i < numEvents; ++i) | |||||
| { | |||||
| const MidiMessage& m = tempoEvents.getEventPointer(i)->message; | |||||
| const double eventTime = m.getTimeStamp(); | |||||
| if (eventTime >= time) | |||||
| break; | |||||
| correctedTime += (eventTime - lastTime) * secsPerTick; | |||||
| lastTime = eventTime; | |||||
| if (m.isTempoMetaEvent()) | |||||
| secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); | |||||
| while (i + 1 < numEvents) | |||||
| { | |||||
| const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message; | |||||
| if (m2.getTimeStamp() != eventTime) | |||||
| break; | |||||
| if (m2.isTempoMetaEvent()) | |||||
| secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); | |||||
| ++i; | |||||
| } | |||||
| } | |||||
| return correctedTime + (time - lastTime) * secsPerTick; | |||||
| } | |||||
| // a comparator that puts all the note-offs before note-ons that have the same time | |||||
| struct Sorter | |||||
| { | |||||
| static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, | |||||
| const MidiMessageSequence::MidiEventHolder* const second) noexcept | |||||
| { | |||||
| const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp()); | |||||
| if (diff > 0) return 1; | |||||
| if (diff < 0) return -1; | |||||
| if (first->message.isNoteOff() && second->message.isNoteOn()) return -1; | |||||
| if (first->message.isNoteOn() && second->message.isNoteOff()) return 1; | |||||
| return 0; | |||||
| } | |||||
| }; | |||||
| } | |||||
| //============================================================================== | |||||
| MidiFile::MidiFile() | |||||
| : timeFormat ((short) (unsigned short) 0xe728) | |||||
| { | |||||
| } | |||||
| MidiFile::~MidiFile() | |||||
| { | |||||
| } | |||||
| void MidiFile::clear() | |||||
| { | |||||
| tracks.clear(); | |||||
| } | |||||
| //============================================================================== | |||||
| int MidiFile::getNumTracks() const noexcept | |||||
| { | |||||
| return tracks.size(); | |||||
| } | |||||
| const MidiMessageSequence* MidiFile::getTrack (const int index) const noexcept | |||||
| { | |||||
| return tracks [index]; | |||||
| } | |||||
| void MidiFile::addTrack (const MidiMessageSequence& trackSequence) | |||||
| { | |||||
| tracks.add (new MidiMessageSequence (trackSequence)); | |||||
| } | |||||
| //============================================================================== | |||||
| short MidiFile::getTimeFormat() const noexcept | |||||
| { | |||||
| return timeFormat; | |||||
| } | |||||
| void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept | |||||
| { | |||||
| timeFormat = (short) ticks; | |||||
| } | |||||
| void MidiFile::setSmpteTimeFormat (const int framesPerSecond, | |||||
| const int subframeResolution) noexcept | |||||
| { | |||||
| timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiFile::findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const | |||||
| { | |||||
| for (int i = tracks.size(); --i >= 0;) | |||||
| { | |||||
| const int numEvents = tracks.getUnchecked(i)->getNumEvents(); | |||||
| for (int j = 0; j < numEvents; ++j) | |||||
| { | |||||
| const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; | |||||
| if (m.isTempoMetaEvent()) | |||||
| tempoChangeEvents.addEvent (m); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MidiFile::findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const | |||||
| { | |||||
| for (int i = tracks.size(); --i >= 0;) | |||||
| { | |||||
| const int numEvents = tracks.getUnchecked(i)->getNumEvents(); | |||||
| for (int j = 0; j < numEvents; ++j) | |||||
| { | |||||
| const MidiMessage& m = tracks.getUnchecked(i)->getEventPointer (j)->message; | |||||
| if (m.isTimeSignatureMetaEvent()) | |||||
| timeSigEvents.addEvent (m); | |||||
| } | |||||
| } | |||||
| } | |||||
| double MidiFile::getLastTimestamp() const | |||||
| { | |||||
| double t = 0.0; | |||||
| for (int i = tracks.size(); --i >= 0;) | |||||
| t = jmax (t, tracks.getUnchecked(i)->getEndTime()); | |||||
| return t; | |||||
| } | |||||
| //============================================================================== | |||||
| bool MidiFile::readFrom (InputStream& sourceStream) | |||||
| { | |||||
| clear(); | |||||
| MemoryBlock data; | |||||
| const int maxSensibleMidiFileSize = 2 * 1024 * 1024; | |||||
| // (put a sanity-check on the file size, as midi files are generally small) | |||||
| if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||||
| { | |||||
| size_t size = data.getSize(); | |||||
| const uint8* d = static_cast <const uint8*> (data.getData()); | |||||
| short fileType, expectedTracks; | |||||
| if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) | |||||
| { | |||||
| size -= (size_t) (d - static_cast <const uint8*> (data.getData())); | |||||
| int track = 0; | |||||
| while (size > 0 && track < expectedTracks) | |||||
| { | |||||
| const int chunkType = (int) ByteOrder::bigEndianInt (d); | |||||
| d += 4; | |||||
| const int chunkSize = (int) ByteOrder::bigEndianInt (d); | |||||
| d += 4; | |||||
| if (chunkSize <= 0) | |||||
| break; | |||||
| if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) | |||||
| readNextTrack (d, chunkSize); | |||||
| size -= (size_t) chunkSize + 8; | |||||
| d += chunkSize; | |||||
| ++track; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void MidiFile::readNextTrack (const uint8* data, int size) | |||||
| { | |||||
| double time = 0; | |||||
| uint8 lastStatusByte = 0; | |||||
| MidiMessageSequence result; | |||||
| while (size > 0) | |||||
| { | |||||
| int bytesUsed; | |||||
| const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed); | |||||
| data += bytesUsed; | |||||
| size -= bytesUsed; | |||||
| time += delay; | |||||
| int messSize = 0; | |||||
| const MidiMessage mm (data, size, messSize, lastStatusByte, time); | |||||
| if (messSize <= 0) | |||||
| break; | |||||
| size -= messSize; | |||||
| data += messSize; | |||||
| result.addEvent (mm); | |||||
| const uint8 firstByte = *(mm.getRawData()); | |||||
| if ((firstByte & 0xf0) != 0xf0) | |||||
| lastStatusByte = firstByte; | |||||
| } | |||||
| // use a sort that puts all the note-offs before note-ons that have the same time | |||||
| MidiFileHelpers::Sorter sorter; | |||||
| result.list.sort (sorter, true); | |||||
| addTrack (result); | |||||
| tracks.getLast()->updateMatchedPairs(); | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiFile::convertTimestampTicksToSeconds() | |||||
| { | |||||
| MidiMessageSequence tempoEvents; | |||||
| findAllTempoEvents (tempoEvents); | |||||
| findAllTimeSigEvents (tempoEvents); | |||||
| if (timeFormat != 0) | |||||
| { | |||||
| for (int i = 0; i < tracks.size(); ++i) | |||||
| { | |||||
| const MidiMessageSequence& ms = *tracks.getUnchecked(i); | |||||
| for (int j = ms.getNumEvents(); --j >= 0;) | |||||
| { | |||||
| MidiMessage& m = ms.getEventPointer(j)->message; | |||||
| m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), | |||||
| tempoEvents, | |||||
| timeFormat)); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| bool MidiFile::writeTo (OutputStream& out) | |||||
| { | |||||
| out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); | |||||
| out.writeIntBigEndian (6); | |||||
| out.writeShortBigEndian (1); // type | |||||
| out.writeShortBigEndian ((short) tracks.size()); | |||||
| out.writeShortBigEndian (timeFormat); | |||||
| for (int i = 0; i < tracks.size(); ++i) | |||||
| writeTrack (out, i); | |||||
| out.flush(); | |||||
| return true; | |||||
| } | |||||
| void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum) | |||||
| { | |||||
| MemoryOutputStream out; | |||||
| const MidiMessageSequence& ms = *tracks.getUnchecked (trackNum); | |||||
| int lastTick = 0; | |||||
| uint8 lastStatusByte = 0; | |||||
| for (int i = 0; i < ms.getNumEvents(); ++i) | |||||
| { | |||||
| const MidiMessage& mm = ms.getEventPointer(i)->message; | |||||
| if (! mm.isEndOfTrackMetaEvent()) | |||||
| { | |||||
| const int tick = roundToInt (mm.getTimeStamp()); | |||||
| const int delta = jmax (0, tick - lastTick); | |||||
| MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta); | |||||
| lastTick = tick; | |||||
| const uint8* data = mm.getRawData(); | |||||
| int dataSize = mm.getRawDataSize(); | |||||
| const uint8 statusByte = data[0]; | |||||
| if (statusByte == lastStatusByte | |||||
| && (statusByte & 0xf0) != 0xf0 | |||||
| && dataSize > 1 | |||||
| && i > 0) | |||||
| { | |||||
| ++data; | |||||
| --dataSize; | |||||
| } | |||||
| else if (statusByte == 0xf0) // Write sysex message with length bytes. | |||||
| { | |||||
| out.writeByte ((char) statusByte); | |||||
| ++data; | |||||
| --dataSize; | |||||
| MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize); | |||||
| } | |||||
| out.write (data, (size_t) dataSize); | |||||
| lastStatusByte = statusByte; | |||||
| } | |||||
| } | |||||
| { | |||||
| out.writeByte (0); // (tick delta) | |||||
| const MidiMessage m (MidiMessage::endOfTrack()); | |||||
| out.write (m.getRawData(), (size_t) m.getRawDataSize()); | |||||
| } | |||||
| mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk")); | |||||
| mainOut.writeIntBigEndian ((int) out.getDataSize()); | |||||
| mainOut << out; | |||||
| } | |||||
| @@ -0,0 +1,186 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_MIDIFILE_H_INCLUDED | |||||
| #define JUCE_MIDIFILE_H_INCLUDED | |||||
| #include "juce_MidiMessageSequence.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| Reads/writes standard midi format files. | |||||
| To read a midi file, create a MidiFile object and call its readFrom() method. You | |||||
| can then get the individual midi tracks from it using the getTrack() method. | |||||
| To write a file, create a MidiFile object, add some MidiMessageSequence objects | |||||
| to it using the addTrack() method, and then call its writeTo() method to stream | |||||
| it out. | |||||
| @see MidiMessageSequence | |||||
| */ | |||||
| class JUCE_API MidiFile | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates an empty MidiFile object. | |||||
| */ | |||||
| MidiFile(); | |||||
| /** Destructor. */ | |||||
| ~MidiFile(); | |||||
| //============================================================================== | |||||
| /** Returns the number of tracks in the file. | |||||
| @see getTrack, addTrack | |||||
| */ | |||||
| int getNumTracks() const noexcept; | |||||
| /** Returns a pointer to one of the tracks in the file. | |||||
| @returns a pointer to the track, or nullptr if the index is out-of-range | |||||
| @see getNumTracks, addTrack | |||||
| */ | |||||
| const MidiMessageSequence* getTrack (int index) const noexcept; | |||||
| /** Adds a midi track to the file. | |||||
| This will make its own internal copy of the sequence that is passed-in. | |||||
| @see getNumTracks, getTrack | |||||
| */ | |||||
| void addTrack (const MidiMessageSequence& trackSequence); | |||||
| /** Removes all midi tracks from the file. | |||||
| @see getNumTracks | |||||
| */ | |||||
| void clear(); | |||||
| /** Returns the raw time format code that will be written to a stream. | |||||
| After reading a midi file, this method will return the time-format that | |||||
| was read from the file's header. It can be changed using the setTicksPerQuarterNote() | |||||
| or setSmpteTimeFormat() methods. | |||||
| If the value returned is positive, it indicates the number of midi ticks | |||||
| per quarter-note - see setTicksPerQuarterNote(). | |||||
| It it's negative, the upper byte indicates the frames-per-second (but negative), and | |||||
| the lower byte is the number of ticks per frame - see setSmpteTimeFormat(). | |||||
| */ | |||||
| short getTimeFormat() const noexcept; | |||||
| /** Sets the time format to use when this file is written to a stream. | |||||
| If this is called, the file will be written as bars/beats using the | |||||
| specified resolution, rather than SMPTE absolute times, as would be | |||||
| used if setSmpteTimeFormat() had been called instead. | |||||
| @param ticksPerQuarterNote e.g. 96, 960 | |||||
| @see setSmpteTimeFormat | |||||
| */ | |||||
| void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept; | |||||
| /** Sets the time format to use when this file is written to a stream. | |||||
| If this is called, the file will be written using absolute times, rather | |||||
| than bars/beats as would be the case if setTicksPerBeat() had been called | |||||
| instead. | |||||
| @param framesPerSecond must be 24, 25, 29 or 30 | |||||
| @param subframeResolution the sub-second resolution, e.g. 4 (midi time code), | |||||
| 8, 10, 80 (SMPTE bit resolution), or 100. For millisecond | |||||
| timing, setSmpteTimeFormat (25, 40) | |||||
| @see setTicksPerBeat | |||||
| */ | |||||
| void setSmpteTimeFormat (int framesPerSecond, | |||||
| int subframeResolution) noexcept; | |||||
| //============================================================================== | |||||
| /** Makes a list of all the tempo-change meta-events from all tracks in the midi file. | |||||
| Useful for finding the positions of all the tempo changes in a file. | |||||
| @param tempoChangeEvents a list to which all the events will be added | |||||
| */ | |||||
| void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const; | |||||
| /** Makes a list of all the time-signature meta-events from all tracks in the midi file. | |||||
| Useful for finding the positions of all the tempo changes in a file. | |||||
| @param timeSigEvents a list to which all the events will be added | |||||
| */ | |||||
| void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; | |||||
| /** Returns the latest timestamp in any of the tracks. | |||||
| (Useful for finding the length of the file). | |||||
| */ | |||||
| double getLastTimestamp() const; | |||||
| //============================================================================== | |||||
| /** Reads a midi file format stream. | |||||
| After calling this, you can get the tracks that were read from the file by using the | |||||
| getNumTracks() and getTrack() methods. | |||||
| The timestamps of the midi events in the tracks will represent their positions in | |||||
| terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() | |||||
| method. | |||||
| @returns true if the stream was read successfully | |||||
| */ | |||||
| bool readFrom (InputStream& sourceStream); | |||||
| /** Writes the midi tracks as a standard midi file. | |||||
| @returns true if the operation succeeded. | |||||
| */ | |||||
| bool writeTo (OutputStream& destStream); | |||||
| /** Converts the timestamp of all the midi events from midi ticks to seconds. | |||||
| This will use the midi time format and tempo/time signature info in the | |||||
| tracks to convert all the timestamps to absolute values in seconds. | |||||
| */ | |||||
| void convertTimestampTicksToSeconds(); | |||||
| private: | |||||
| //============================================================================== | |||||
| OwnedArray <MidiMessageSequence> tracks; | |||||
| short timeFormat; | |||||
| void readNextTrack (const uint8* data, int size); | |||||
| void writeTrack (OutputStream& mainOut, int trackNum); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiFile) | |||||
| }; | |||||
| #endif // JUCE_MIDIFILE_H_INCLUDED | |||||
| @@ -0,0 +1,183 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| MidiKeyboardState::MidiKeyboardState() | |||||
| { | |||||
| zerostruct (noteStates); | |||||
| } | |||||
| MidiKeyboardState::~MidiKeyboardState() | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiKeyboardState::reset() | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| zerostruct (noteStates); | |||||
| eventsToAdd.clear(); | |||||
| } | |||||
| bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept | |||||
| { | |||||
| jassert (midiChannel >= 0 && midiChannel <= 16); | |||||
| return isPositiveAndBelow (n, (int) 128) | |||||
| && (noteStates[n] & (1 << (midiChannel - 1))) != 0; | |||||
| } | |||||
| bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept | |||||
| { | |||||
| return isPositiveAndBelow (n, (int) 128) | |||||
| && (noteStates[n] & midiChannelMask) != 0; | |||||
| } | |||||
| void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) | |||||
| { | |||||
| jassert (midiChannel >= 0 && midiChannel <= 16); | |||||
| jassert (isPositiveAndBelow (midiNoteNumber, (int) 128)); | |||||
| const ScopedLock sl (lock); | |||||
| if (isPositiveAndBelow (midiNoteNumber, (int) 128)) | |||||
| { | |||||
| const int timeNow = (int) Time::getMillisecondCounter(); | |||||
| eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); | |||||
| eventsToAdd.clear (0, timeNow - 500); | |||||
| noteOnInternal (midiChannel, midiNoteNumber, velocity); | |||||
| } | |||||
| } | |||||
| void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) | |||||
| { | |||||
| if (isPositiveAndBelow (midiNoteNumber, (int) 128)) | |||||
| { | |||||
| noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); | |||||
| for (int i = listeners.size(); --i >= 0;) | |||||
| listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity); | |||||
| } | |||||
| } | |||||
| void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| if (isNoteOn (midiChannel, midiNoteNumber)) | |||||
| { | |||||
| const int timeNow = (int) Time::getMillisecondCounter(); | |||||
| eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow); | |||||
| eventsToAdd.clear (0, timeNow - 500); | |||||
| noteOffInternal (midiChannel, midiNoteNumber); | |||||
| } | |||||
| } | |||||
| void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber) | |||||
| { | |||||
| if (isNoteOn (midiChannel, midiNoteNumber)) | |||||
| { | |||||
| noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1)); | |||||
| for (int i = listeners.size(); --i >= 0;) | |||||
| listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber); | |||||
| } | |||||
| } | |||||
| void MidiKeyboardState::allNotesOff (const int midiChannel) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| if (midiChannel <= 0) | |||||
| { | |||||
| for (int i = 1; i <= 16; ++i) | |||||
| allNotesOff (i); | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = 0; i < 128; ++i) | |||||
| noteOff (midiChannel, i); | |||||
| } | |||||
| } | |||||
| void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message) | |||||
| { | |||||
| if (message.isNoteOn()) | |||||
| { | |||||
| noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); | |||||
| } | |||||
| else if (message.isNoteOff()) | |||||
| { | |||||
| noteOffInternal (message.getChannel(), message.getNoteNumber()); | |||||
| } | |||||
| else if (message.isAllNotesOff()) | |||||
| { | |||||
| for (int i = 0; i < 128; ++i) | |||||
| noteOffInternal (message.getChannel(), i); | |||||
| } | |||||
| } | |||||
| void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, | |||||
| const int startSample, | |||||
| const int numSamples, | |||||
| const bool injectIndirectEvents) | |||||
| { | |||||
| MidiBuffer::Iterator i (buffer); | |||||
| MidiMessage message (0xf4, 0.0); | |||||
| int time; | |||||
| const ScopedLock sl (lock); | |||||
| while (i.getNextEvent (message, time)) | |||||
| processNextMidiEvent (message); | |||||
| if (injectIndirectEvents) | |||||
| { | |||||
| MidiBuffer::Iterator i2 (eventsToAdd); | |||||
| const int firstEventToAdd = eventsToAdd.getFirstEventTime(); | |||||
| const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); | |||||
| while (i2.getNextEvent (message, time)) | |||||
| { | |||||
| const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor)); | |||||
| buffer.addEvent (message, startSample + pos); | |||||
| } | |||||
| } | |||||
| eventsToAdd.clear(); | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| listeners.addIfNotAlreadyThere (listener); | |||||
| } | |||||
| void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| listeners.removeFirstMatchingValue (listener); | |||||
| } | |||||
| @@ -0,0 +1,206 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_MIDIKEYBOARDSTATE_H_INCLUDED | |||||
| #define JUCE_MIDIKEYBOARDSTATE_H_INCLUDED | |||||
| #include "juce_MidiBuffer.h" | |||||
| class MidiKeyboardState; | |||||
| //============================================================================== | |||||
| /** | |||||
| Receives events from a MidiKeyboardState object. | |||||
| @see MidiKeyboardState | |||||
| */ | |||||
| class JUCE_API MidiKeyboardStateListener | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| MidiKeyboardStateListener() noexcept {} | |||||
| virtual ~MidiKeyboardStateListener() {} | |||||
| //============================================================================== | |||||
| /** Called when one of the MidiKeyboardState's keys is pressed. | |||||
| This will be called synchronously when the state is either processing a | |||||
| buffer in its MidiKeyboardState::processNextMidiBuffer() method, or | |||||
| when a note is being played with its MidiKeyboardState::noteOn() method. | |||||
| Note that this callback could happen from an audio callback thread, so be | |||||
| careful not to block, and avoid any UI activity in the callback. | |||||
| */ | |||||
| virtual void handleNoteOn (MidiKeyboardState* source, | |||||
| int midiChannel, int midiNoteNumber, float velocity) = 0; | |||||
| /** Called when one of the MidiKeyboardState's keys is released. | |||||
| This will be called synchronously when the state is either processing a | |||||
| buffer in its MidiKeyboardState::processNextMidiBuffer() method, or | |||||
| when a note is being played with its MidiKeyboardState::noteOff() method. | |||||
| Note that this callback could happen from an audio callback thread, so be | |||||
| careful not to block, and avoid any UI activity in the callback. | |||||
| */ | |||||
| virtual void handleNoteOff (MidiKeyboardState* source, | |||||
| int midiChannel, int midiNoteNumber) = 0; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Represents a piano keyboard, keeping track of which keys are currently pressed. | |||||
| This object can parse a stream of midi events, using them to update its idea | |||||
| of which keys are pressed for each individiual midi channel. | |||||
| When keys go up or down, it can broadcast these events to listener objects. | |||||
| It also allows key up/down events to be triggered with its noteOn() and noteOff() | |||||
| methods, and midi messages for these events will be merged into the | |||||
| midi stream that gets processed by processNextMidiBuffer(). | |||||
| */ | |||||
| class JUCE_API MidiKeyboardState | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| MidiKeyboardState(); | |||||
| ~MidiKeyboardState(); | |||||
| //============================================================================== | |||||
| /** Resets the state of the object. | |||||
| All internal data for all the channels is reset, but no events are sent as a | |||||
| result. | |||||
| If you want to release any keys that are currently down, and to send out note-up | |||||
| midi messages for this, use the allNotesOff() method instead. | |||||
| */ | |||||
| void reset(); | |||||
| /** Returns true if the given midi key is currently held down for the given midi channel. | |||||
| The channel number must be between 1 and 16. If you want to see if any notes are | |||||
| on for a range of channels, use the isNoteOnForChannels() method. | |||||
| */ | |||||
| bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept; | |||||
| /** Returns true if the given midi key is currently held down on any of a set of midi channels. | |||||
| The channel mask has a bit set for each midi channel you want to test for - bit | |||||
| 0 = midi channel 1, bit 1 = midi channel 2, etc. | |||||
| If a note is on for at least one of the specified channels, this returns true. | |||||
| */ | |||||
| bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept; | |||||
| /** Turns a specified note on. | |||||
| This will cause a suitable midi note-on event to be injected into the midi buffer during the | |||||
| next call to processNextMidiBuffer(). | |||||
| It will also trigger a synchronous callback to the listeners to tell them that the key has | |||||
| gone down. | |||||
| */ | |||||
| void noteOn (int midiChannel, int midiNoteNumber, float velocity); | |||||
| /** Turns a specified note off. | |||||
| This will cause a suitable midi note-off event to be injected into the midi buffer during the | |||||
| next call to processNextMidiBuffer(). | |||||
| It will also trigger a synchronous callback to the listeners to tell them that the key has | |||||
| gone up. | |||||
| But if the note isn't acutally down for the given channel, this method will in fact do nothing. | |||||
| */ | |||||
| void noteOff (int midiChannel, int midiNoteNumber); | |||||
| /** This will turn off any currently-down notes for the given midi channel. | |||||
| If you pass 0 for the midi channel, it will in fact turn off all notes on all channels. | |||||
| Calling this method will make calls to noteOff(), so can trigger synchronous callbacks | |||||
| and events being added to the midi stream. | |||||
| */ | |||||
| void allNotesOff (int midiChannel); | |||||
| //============================================================================== | |||||
| /** Looks at a key-up/down event and uses it to update the state of this object. | |||||
| To process a buffer full of midi messages, use the processNextMidiBuffer() method | |||||
| instead. | |||||
| */ | |||||
| void processNextMidiEvent (const MidiMessage& message); | |||||
| /** Scans a midi stream for up/down events and adds its own events to it. | |||||
| This will look for any up/down events and use them to update the internal state, | |||||
| synchronously making suitable callbacks to the listeners. | |||||
| If injectIndirectEvents is true, then midi events to produce the recent noteOn() | |||||
| and noteOff() calls will be added into the buffer. | |||||
| Only the section of the buffer whose timestamps are between startSample and | |||||
| (startSample + numSamples) will be affected, and any events added will be placed | |||||
| between these times. | |||||
| If you're going to use this method, you'll need to keep calling it regularly for | |||||
| it to work satisfactorily. | |||||
| To process a single midi event at a time, use the processNextMidiEvent() method | |||||
| instead. | |||||
| */ | |||||
| void processNextMidiBuffer (MidiBuffer& buffer, | |||||
| int startSample, | |||||
| int numSamples, | |||||
| bool injectIndirectEvents); | |||||
| //============================================================================== | |||||
| /** Registers a listener for callbacks when keys go up or down. | |||||
| @see removeListener | |||||
| */ | |||||
| void addListener (MidiKeyboardStateListener* listener); | |||||
| /** Deregisters a listener. | |||||
| @see addListener | |||||
| */ | |||||
| void removeListener (MidiKeyboardStateListener* listener); | |||||
| private: | |||||
| //============================================================================== | |||||
| CriticalSection lock; | |||||
| uint16 noteStates [128]; | |||||
| MidiBuffer eventsToAdd; | |||||
| Array <MidiKeyboardStateListener*> listeners; | |||||
| void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity); | |||||
| void noteOffInternal (int midiChannel, int midiNoteNumber); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState) | |||||
| }; | |||||
| #endif // JUCE_MIDIKEYBOARDSTATE_H_INCLUDED | |||||
| @@ -0,0 +1,939 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_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 | |||||
| */ | |||||
| MidiMessage (const void* data, int maxBytesToUse, | |||||
| int& numBytesUsed, uint8 lastStatusByte, | |||||
| double timeStamp = 0); | |||||
| /** 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& other); | |||||
| /** Creates a copy of another midi message, with a different timestamp. */ | |||||
| MidiMessage (const MidiMessage& other, double newTimeStamp); | |||||
| /** Destructor. */ | |||||
| ~MidiMessage(); | |||||
| /** Copies this message from another one. */ | |||||
| MidiMessage& operator= (const MidiMessage& other); | |||||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
| MidiMessage (MidiMessage&& other) noexcept; | |||||
| MidiMessage& operator= (MidiMessage&& other) noexcept; | |||||
| #endif | |||||
| //============================================================================== | |||||
| /** Returns a pointer to the raw midi data. | |||||
| @see getRawDataSize | |||||
| */ | |||||
| const uint8* getRawData() const noexcept { return data; } | |||||
| /** Returns the number of bytes of data in the message. | |||||
| @see getRawData | |||||
| */ | |||||
| int getRawDataSize() const noexcept { return size; } | |||||
| //============================================================================== | |||||
| /** 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 127 | |||||
| @see isNoteOff | |||||
| */ | |||||
| static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity = 0) 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 will be | |||||
| nonsense. | |||||
| @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; | |||||
| //============================================================================== | |||||
| /** 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 | |||||
| */ | |||||
| bool isKeySignatureMetaEvent() const noexcept; | |||||
| /** Returns the key from a key-signature meta-event. | |||||
| @see isKeySignatureMetaEvent | |||||
| */ | |||||
| int getKeySignatureNumberOfSharpsOrFlats() const noexcept; | |||||
| //============================================================================== | |||||
| /** 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 (const 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, const double frequencyOfA = 440.0) noexcept; | |||||
| /** Returns the standard name of a GM instrument. | |||||
| @param midiInstrumentNumber the program number 0 to 127 | |||||
| @see getProgramChangeNumber | |||||
| */ | |||||
| static String getGMInstrumentName (int midiInstrumentNumber); | |||||
| /** Returns the name of a bank of GM instruments. | |||||
| @param midiBankNumber the bank, 0 to 15 | |||||
| */ | |||||
| static String getGMInstrumentBankName (int midiBankNumber); | |||||
| /** Returns the standard name of a channel 10 percussion sound. | |||||
| @param midiNoteNumber the key number, 35 to 81 | |||||
| */ | |||||
| static String getRhythmInstrumentName (int midiNoteNumber); | |||||
| /** Returns the name of a controller type number. | |||||
| @see getControllerNumber | |||||
| */ | |||||
| static String getControllerName (int controllerNumber); | |||||
| private: | |||||
| //============================================================================== | |||||
| double timeStamp; | |||||
| uint8* data; | |||||
| int size; | |||||
| #ifndef DOXYGEN | |||||
| union | |||||
| { | |||||
| uint8 asBytes[4]; | |||||
| uint32 asInt32; | |||||
| } preallocatedData; | |||||
| #endif | |||||
| void freeData() noexcept; | |||||
| void setToUseInternalData() noexcept; | |||||
| bool usesAllocatedData() const noexcept; | |||||
| }; | |||||
| #endif // JUCE_MIDIMESSAGE_H_INCLUDED | |||||
| @@ -0,0 +1,334 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| MidiMessageSequence::MidiMessageSequence() | |||||
| { | |||||
| } | |||||
| MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) | |||||
| { | |||||
| list.ensureStorageAllocated (other.list.size()); | |||||
| for (int i = 0; i < other.list.size(); ++i) | |||||
| list.add (new MidiEventHolder (other.list.getUnchecked(i)->message)); | |||||
| } | |||||
| MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) | |||||
| { | |||||
| MidiMessageSequence otherCopy (other); | |||||
| swapWith (otherCopy); | |||||
| return *this; | |||||
| } | |||||
| void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept | |||||
| { | |||||
| list.swapWith (other.list); | |||||
| } | |||||
| MidiMessageSequence::~MidiMessageSequence() | |||||
| { | |||||
| } | |||||
| void MidiMessageSequence::clear() | |||||
| { | |||||
| list.clear(); | |||||
| } | |||||
| int MidiMessageSequence::getNumEvents() const | |||||
| { | |||||
| return list.size(); | |||||
| } | |||||
| MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const | |||||
| { | |||||
| return list [index]; | |||||
| } | |||||
| double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const | |||||
| { | |||||
| if (const MidiEventHolder* const meh = list [index]) | |||||
| if (meh->noteOffObject != nullptr) | |||||
| return meh->noteOffObject->message.getTimeStamp(); | |||||
| return 0.0; | |||||
| } | |||||
| int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const | |||||
| { | |||||
| if (const MidiEventHolder* const meh = list [index]) | |||||
| return list.indexOf (meh->noteOffObject); | |||||
| return -1; | |||||
| } | |||||
| int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const | |||||
| { | |||||
| return list.indexOf (event); | |||||
| } | |||||
| int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const | |||||
| { | |||||
| const int numEvents = list.size(); | |||||
| int i; | |||||
| for (i = 0; i < numEvents; ++i) | |||||
| if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) | |||||
| break; | |||||
| return i; | |||||
| } | |||||
| //============================================================================== | |||||
| double MidiMessageSequence::getStartTime() const | |||||
| { | |||||
| return getEventTime (0); | |||||
| } | |||||
| double MidiMessageSequence::getEndTime() const | |||||
| { | |||||
| return getEventTime (list.size() - 1); | |||||
| } | |||||
| double MidiMessageSequence::getEventTime (const int index) const | |||||
| { | |||||
| if (const MidiEventHolder* const meh = list [index]) | |||||
| return meh->message.getTimeStamp(); | |||||
| return 0.0; | |||||
| } | |||||
| //============================================================================== | |||||
| MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, | |||||
| double timeAdjustment) | |||||
| { | |||||
| MidiEventHolder* const newOne = new MidiEventHolder (newMessage); | |||||
| timeAdjustment += newMessage.getTimeStamp(); | |||||
| newOne->message.setTimeStamp (timeAdjustment); | |||||
| int i; | |||||
| for (i = list.size(); --i >= 0;) | |||||
| if (list.getUnchecked(i)->message.getTimeStamp() <= timeAdjustment) | |||||
| break; | |||||
| list.insert (i + 1, newOne); | |||||
| return newOne; | |||||
| } | |||||
| void MidiMessageSequence::deleteEvent (const int index, | |||||
| const bool deleteMatchingNoteUp) | |||||
| { | |||||
| if (isPositiveAndBelow (index, list.size())) | |||||
| { | |||||
| if (deleteMatchingNoteUp) | |||||
| deleteEvent (getIndexOfMatchingKeyUp (index), false); | |||||
| list.remove (index); | |||||
| } | |||||
| } | |||||
| struct MidiMessageSequenceSorter | |||||
| { | |||||
| static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, | |||||
| const MidiMessageSequence::MidiEventHolder* const second) noexcept | |||||
| { | |||||
| const double diff = first->message.getTimeStamp() - second->message.getTimeStamp(); | |||||
| return (diff > 0) - (diff < 0); | |||||
| } | |||||
| }; | |||||
| void MidiMessageSequence::addSequence (const MidiMessageSequence& other, | |||||
| double timeAdjustment, | |||||
| double firstAllowableTime, | |||||
| double endOfAllowableDestTimes) | |||||
| { | |||||
| firstAllowableTime -= timeAdjustment; | |||||
| endOfAllowableDestTimes -= timeAdjustment; | |||||
| for (int i = 0; i < other.list.size(); ++i) | |||||
| { | |||||
| const MidiMessage& m = other.list.getUnchecked(i)->message; | |||||
| const double t = m.getTimeStamp(); | |||||
| if (t >= firstAllowableTime && t < endOfAllowableDestTimes) | |||||
| { | |||||
| MidiEventHolder* const newOne = new MidiEventHolder (m); | |||||
| newOne->message.setTimeStamp (timeAdjustment + t); | |||||
| list.add (newOne); | |||||
| } | |||||
| } | |||||
| sort(); | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiMessageSequence::sort() | |||||
| { | |||||
| MidiMessageSequenceSorter sorter; | |||||
| list.sort (sorter, true); | |||||
| } | |||||
| void MidiMessageSequence::updateMatchedPairs() | |||||
| { | |||||
| for (int i = 0; i < list.size(); ++i) | |||||
| { | |||||
| MidiEventHolder* const meh = list.getUnchecked(i); | |||||
| const MidiMessage& m1 = meh->message; | |||||
| if (m1.isNoteOn()) | |||||
| { | |||||
| meh->noteOffObject = nullptr; | |||||
| const int note = m1.getNoteNumber(); | |||||
| const int chan = m1.getChannel(); | |||||
| const int len = list.size(); | |||||
| for (int j = i + 1; j < len; ++j) | |||||
| { | |||||
| const MidiMessage& m = list.getUnchecked(j)->message; | |||||
| if (m.getNoteNumber() == note && m.getChannel() == chan) | |||||
| { | |||||
| if (m.isNoteOff()) | |||||
| { | |||||
| meh->noteOffObject = list[j]; | |||||
| break; | |||||
| } | |||||
| else if (m.isNoteOn()) | |||||
| { | |||||
| MidiEventHolder* const newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note)); | |||||
| list.insert (j, newEvent); | |||||
| newEvent->message.setTimeStamp (m.getTimeStamp()); | |||||
| meh->noteOffObject = newEvent; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| void MidiMessageSequence::addTimeToMessages (const double delta) | |||||
| { | |||||
| for (int i = list.size(); --i >= 0;) | |||||
| { | |||||
| MidiMessage& mm = list.getUnchecked(i)->message; | |||||
| mm.setTimeStamp (mm.getTimeStamp() + delta); | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, | |||||
| MidiMessageSequence& destSequence, | |||||
| const bool alsoIncludeMetaEvents) const | |||||
| { | |||||
| for (int i = 0; i < list.size(); ++i) | |||||
| { | |||||
| const MidiMessage& mm = list.getUnchecked(i)->message; | |||||
| if (mm.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && mm.isMetaEvent())) | |||||
| destSequence.addEvent (mm); | |||||
| } | |||||
| } | |||||
| void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const | |||||
| { | |||||
| for (int i = 0; i < list.size(); ++i) | |||||
| { | |||||
| const MidiMessage& mm = list.getUnchecked(i)->message; | |||||
| if (mm.isSysEx()) | |||||
| destSequence.addEvent (mm); | |||||
| } | |||||
| } | |||||
| void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) | |||||
| { | |||||
| for (int i = list.size(); --i >= 0;) | |||||
| if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) | |||||
| list.remove(i); | |||||
| } | |||||
| void MidiMessageSequence::deleteSysExMessages() | |||||
| { | |||||
| for (int i = list.size(); --i >= 0;) | |||||
| if (list.getUnchecked(i)->message.isSysEx()) | |||||
| list.remove(i); | |||||
| } | |||||
| //============================================================================== | |||||
| void MidiMessageSequence::createControllerUpdatesForTime (const int channelNumber, | |||||
| const double time, | |||||
| OwnedArray<MidiMessage>& dest) | |||||
| { | |||||
| bool doneProg = false; | |||||
| bool donePitchWheel = false; | |||||
| Array <int> doneControllers; | |||||
| doneControllers.ensureStorageAllocated (32); | |||||
| for (int i = list.size(); --i >= 0;) | |||||
| { | |||||
| const MidiMessage& mm = list.getUnchecked(i)->message; | |||||
| if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) | |||||
| { | |||||
| if (mm.isProgramChange()) | |||||
| { | |||||
| if (! doneProg) | |||||
| { | |||||
| dest.add (new MidiMessage (mm, 0.0)); | |||||
| doneProg = true; | |||||
| } | |||||
| } | |||||
| else if (mm.isController()) | |||||
| { | |||||
| if (! doneControllers.contains (mm.getControllerNumber())) | |||||
| { | |||||
| dest.add (new MidiMessage (mm, 0.0)); | |||||
| doneControllers.add (mm.getControllerNumber()); | |||||
| } | |||||
| } | |||||
| else if (mm.isPitchWheel()) | |||||
| { | |||||
| if (! donePitchWheel) | |||||
| { | |||||
| dest.add (new MidiMessage (mm, 0.0)); | |||||
| donePitchWheel = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) | |||||
| : message (mm), | |||||
| noteOffObject (nullptr) | |||||
| { | |||||
| } | |||||
| MidiMessageSequence::MidiEventHolder::~MidiEventHolder() | |||||
| { | |||||
| } | |||||
| @@ -0,0 +1,280 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_MIDIMESSAGESEQUENCE_H_INCLUDED | |||||
| #define JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED | |||||
| #include "juce_MidiMessage.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| A sequence of timestamped midi messages. | |||||
| This allows the sequence to be manipulated, and also to be read from and | |||||
| written to a standard midi file. | |||||
| @see MidiMessage, MidiFile | |||||
| */ | |||||
| class JUCE_API MidiMessageSequence | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates an empty midi sequence object. */ | |||||
| MidiMessageSequence(); | |||||
| /** Creates a copy of another sequence. */ | |||||
| MidiMessageSequence (const MidiMessageSequence& other); | |||||
| /** Replaces this sequence with another one. */ | |||||
| MidiMessageSequence& operator= (const MidiMessageSequence& other); | |||||
| /** Destructor. */ | |||||
| ~MidiMessageSequence(); | |||||
| //============================================================================== | |||||
| /** Structure used to hold midi events in the sequence. | |||||
| These structures act as 'handles' on the events as they are moved about in | |||||
| the list, and make it quick to find the matching note-offs for note-on events. | |||||
| @see MidiMessageSequence::getEventPointer | |||||
| */ | |||||
| class MidiEventHolder | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Destructor. */ | |||||
| ~MidiEventHolder(); | |||||
| /** The message itself, whose timestamp is used to specify the event's time. | |||||
| */ | |||||
| MidiMessage message; | |||||
| /** The matching note-off event (if this is a note-on event). | |||||
| If this isn't a note-on, this pointer will be null. | |||||
| Use the MidiMessageSequence::updateMatchedPairs() method to keep these | |||||
| note-offs up-to-date after events have been moved around in the sequence | |||||
| or deleted. | |||||
| */ | |||||
| MidiEventHolder* noteOffObject; | |||||
| private: | |||||
| //============================================================================== | |||||
| friend class MidiMessageSequence; | |||||
| MidiEventHolder (const MidiMessage& message); | |||||
| JUCE_LEAK_DETECTOR (MidiEventHolder) | |||||
| }; | |||||
| //============================================================================== | |||||
| /** Clears the sequence. */ | |||||
| void clear(); | |||||
| /** Returns the number of events in the sequence. */ | |||||
| int getNumEvents() const; | |||||
| /** Returns a pointer to one of the events. */ | |||||
| MidiEventHolder* getEventPointer (int index) const; | |||||
| /** Returns the time of the note-up that matches the note-on at this index. | |||||
| If the event at this index isn't a note-on, it'll just return 0. | |||||
| @see MidiMessageSequence::MidiEventHolder::noteOffObject | |||||
| */ | |||||
| double getTimeOfMatchingKeyUp (int index) const; | |||||
| /** Returns the index of the note-up that matches the note-on at this index. | |||||
| If the event at this index isn't a note-on, it'll just return -1. | |||||
| @see MidiMessageSequence::MidiEventHolder::noteOffObject | |||||
| */ | |||||
| int getIndexOfMatchingKeyUp (int index) const; | |||||
| /** Returns the index of an event. */ | |||||
| int getIndexOf (MidiEventHolder* event) const; | |||||
| /** Returns the index of the first event on or after the given timestamp. | |||||
| If the time is beyond the end of the sequence, this will return the | |||||
| number of events. | |||||
| */ | |||||
| int getNextIndexAtTime (double timeStamp) const; | |||||
| //============================================================================== | |||||
| /** Returns the timestamp of the first event in the sequence. | |||||
| @see getEndTime | |||||
| */ | |||||
| double getStartTime() const; | |||||
| /** Returns the timestamp of the last event in the sequence. | |||||
| @see getStartTime | |||||
| */ | |||||
| double getEndTime() const; | |||||
| /** Returns the timestamp of the event at a given index. | |||||
| If the index is out-of-range, this will return 0.0 | |||||
| */ | |||||
| double getEventTime (int index) const; | |||||
| //============================================================================== | |||||
| /** Inserts a midi message into the sequence. | |||||
| The index at which the new message gets inserted will depend on its timestamp, | |||||
| because the sequence is kept sorted. | |||||
| Remember to call updateMatchedPairs() after adding note-on events. | |||||
| @param newMessage the new message to add (an internal copy will be made) | |||||
| @param timeAdjustment an optional value to add to the timestamp of the message | |||||
| that will be inserted | |||||
| @see updateMatchedPairs | |||||
| */ | |||||
| MidiEventHolder* addEvent (const MidiMessage& newMessage, | |||||
| double timeAdjustment = 0); | |||||
| /** Deletes one of the events in the sequence. | |||||
| Remember to call updateMatchedPairs() after removing events. | |||||
| @param index the index of the event to delete | |||||
| @param deleteMatchingNoteUp whether to also remove the matching note-off | |||||
| if the event you're removing is a note-on | |||||
| */ | |||||
| void deleteEvent (int index, bool deleteMatchingNoteUp); | |||||
| /** Merges another sequence into this one. | |||||
| Remember to call updateMatchedPairs() after using this method. | |||||
| @param other the sequence to add from | |||||
| @param timeAdjustmentDelta an amount to add to the timestamps of the midi events | |||||
| as they are read from the other sequence | |||||
| @param firstAllowableDestTime events will not be added if their time is earlier | |||||
| than this time. (This is after their time has been adjusted | |||||
| by the timeAdjustmentDelta) | |||||
| @param endOfAllowableDestTimes events will not be added if their time is equal to | |||||
| or greater than this time. (This is after their time has | |||||
| been adjusted by the timeAdjustmentDelta) | |||||
| */ | |||||
| void addSequence (const MidiMessageSequence& other, | |||||
| double timeAdjustmentDelta, | |||||
| double firstAllowableDestTime, | |||||
| double endOfAllowableDestTimes); | |||||
| //============================================================================== | |||||
| /** Makes sure all the note-on and note-off pairs are up-to-date. | |||||
| Call this after moving messages about or deleting/adding messages, and it | |||||
| will scan the list and make sure all the note-offs in the MidiEventHolder | |||||
| structures are pointing at the correct ones. | |||||
| */ | |||||
| void updateMatchedPairs(); | |||||
| /** Forces a sort of the sequence. | |||||
| You may need to call this if you've manually modified the timestamps of some | |||||
| events such that the overall order now needs updating. | |||||
| */ | |||||
| void sort(); | |||||
| //============================================================================== | |||||
| /** Copies all the messages for a particular midi channel to another sequence. | |||||
| @param channelNumberToExtract the midi channel to look for, in the range 1 to 16 | |||||
| @param destSequence the sequence that the chosen events should be copied to | |||||
| @param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific | |||||
| channel) will also be copied across. | |||||
| @see extractSysExMessages | |||||
| */ | |||||
| void extractMidiChannelMessages (int channelNumberToExtract, | |||||
| MidiMessageSequence& destSequence, | |||||
| bool alsoIncludeMetaEvents) const; | |||||
| /** Copies all midi sys-ex messages to another sequence. | |||||
| @param destSequence this is the sequence to which any sys-exes in this sequence | |||||
| will be added | |||||
| @see extractMidiChannelMessages | |||||
| */ | |||||
| void extractSysExMessages (MidiMessageSequence& destSequence) const; | |||||
| /** Removes any messages in this sequence that have a specific midi channel. | |||||
| @param channelNumberToRemove the midi channel to look for, in the range 1 to 16 | |||||
| */ | |||||
| void deleteMidiChannelMessages (int channelNumberToRemove); | |||||
| /** Removes any sys-ex messages from this sequence. | |||||
| */ | |||||
| void deleteSysExMessages(); | |||||
| /** Adds an offset to the timestamps of all events in the sequence. | |||||
| @param deltaTime the amount to add to each timestamp. | |||||
| */ | |||||
| void addTimeToMessages (double deltaTime); | |||||
| //============================================================================== | |||||
| /** Scans through the sequence to determine the state of any midi controllers at | |||||
| a given time. | |||||
| This will create a sequence of midi controller changes that can be | |||||
| used to set all midi controllers to the state they would be in at the | |||||
| specified time within this sequence. | |||||
| As well as controllers, it will also recreate the midi program number | |||||
| and pitch bend position. | |||||
| @param channelNumber the midi channel to look for, in the range 1 to 16. Controllers | |||||
| for other channels will be ignored. | |||||
| @param time the time at which you want to find out the state - there are | |||||
| no explicit units for this time measurement, it's the same units | |||||
| as used for the timestamps of the messages | |||||
| @param resultMessages an array to which midi controller-change messages will be added. This | |||||
| will be the minimum number of controller changes to recreate the | |||||
| state at the required time. | |||||
| */ | |||||
| void createControllerUpdatesForTime (int channelNumber, double time, | |||||
| OwnedArray<MidiMessage>& resultMessages); | |||||
| //============================================================================== | |||||
| /** Swaps this sequence with another one. */ | |||||
| void swapWith (MidiMessageSequence& other) noexcept; | |||||
| private: | |||||
| //============================================================================== | |||||
| friend class MidiFile; | |||||
| OwnedArray <MidiEventHolder> list; | |||||
| JUCE_LEAK_DETECTOR (MidiMessageSequence) | |||||
| }; | |||||
| #endif // JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED | |||||
| @@ -0,0 +1,183 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_AUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_AUDIOSOURCE_H_INCLUDED | |||||
| #include "../buffers/juce_AudioSampleBuffer.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| Used by AudioSource::getNextAudioBlock(). | |||||
| */ | |||||
| struct JUCE_API AudioSourceChannelInfo | |||||
| { | |||||
| /** Creates an uninitialised AudioSourceChannelInfo. */ | |||||
| AudioSourceChannelInfo() noexcept | |||||
| { | |||||
| } | |||||
| /** Creates an AudioSourceChannelInfo. */ | |||||
| AudioSourceChannelInfo (AudioSampleBuffer* bufferToUse, | |||||
| int startSampleOffset, int numSamplesToUse) noexcept | |||||
| : buffer (bufferToUse), | |||||
| startSample (startSampleOffset), | |||||
| numSamples (numSamplesToUse) | |||||
| { | |||||
| } | |||||
| /** Creates an AudioSourceChannelInfo that uses the whole of a buffer. | |||||
| Note that the buffer provided must not be deleted while the | |||||
| AudioSourceChannelInfo is still using it. | |||||
| */ | |||||
| explicit AudioSourceChannelInfo (AudioSampleBuffer& bufferToUse) noexcept | |||||
| : buffer (&bufferToUse), | |||||
| startSample (0), | |||||
| numSamples (bufferToUse.getNumSamples()) | |||||
| { | |||||
| } | |||||
| /** The destination buffer to fill with audio data. | |||||
| When the AudioSource::getNextAudioBlock() method is called, the active section | |||||
| of this buffer should be filled with whatever output the source produces. | |||||
| Only the samples specified by the startSample and numSamples members of this structure | |||||
| should be affected by the call. | |||||
| The contents of the buffer when it is passed to the the AudioSource::getNextAudioBlock() | |||||
| method can be treated as the input if the source is performing some kind of filter operation, | |||||
| but should be cleared if this is not the case - the clearActiveBufferRegion() is | |||||
| a handy way of doing this. | |||||
| The number of channels in the buffer could be anything, so the AudioSource | |||||
| must cope with this in whatever way is appropriate for its function. | |||||
| */ | |||||
| AudioSampleBuffer* buffer; | |||||
| /** The first sample in the buffer from which the callback is expected | |||||
| to write data. */ | |||||
| int startSample; | |||||
| /** The number of samples in the buffer which the callback is expected to | |||||
| fill with data. */ | |||||
| int numSamples; | |||||
| /** Convenient method to clear the buffer if the source is not producing any data. */ | |||||
| void clearActiveBufferRegion() const | |||||
| { | |||||
| if (buffer != nullptr) | |||||
| buffer->clear (startSample, numSamples); | |||||
| } | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Base class for objects that can produce a continuous stream of audio. | |||||
| An AudioSource has two states: 'prepared' and 'unprepared'. | |||||
| When a source needs to be played, it is first put into a 'prepared' state by a call to | |||||
| prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to | |||||
| process the audio data. | |||||
| Once playback has finished, the releaseResources() method is called to put the stream | |||||
| back into an 'unprepared' state. | |||||
| @see AudioFormatReaderSource, ResamplingAudioSource | |||||
| */ | |||||
| class JUCE_API AudioSource | |||||
| { | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** Creates an AudioSource. */ | |||||
| AudioSource() noexcept {} | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~AudioSource() {} | |||||
| //============================================================================== | |||||
| /** Tells the source to prepare for playing. | |||||
| An AudioSource has two states: prepared and unprepared. | |||||
| The prepareToPlay() method is guaranteed to be called at least once on an 'unpreprared' | |||||
| source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). | |||||
| This callback allows the source to initialise any resources it might need when playing. | |||||
| Once playback has finished, the releaseResources() method is called to put the stream | |||||
| back into an 'unprepared' state. | |||||
| Note that this method could be called more than once in succession without | |||||
| a matching call to releaseResources(), so make sure your code is robust and | |||||
| can handle that kind of situation. | |||||
| @param samplesPerBlockExpected the number of samples that the source | |||||
| will be expected to supply each time its | |||||
| getNextAudioBlock() method is called. This | |||||
| number may vary slightly, because it will be dependent | |||||
| on audio hardware callbacks, and these aren't | |||||
| guaranteed to always use a constant block size, so | |||||
| the source should be able to cope with small variations. | |||||
| @param sampleRate the sample rate that the output will be used at - this | |||||
| is needed by sources such as tone generators. | |||||
| @see releaseResources, getNextAudioBlock | |||||
| */ | |||||
| virtual void prepareToPlay (int samplesPerBlockExpected, | |||||
| double sampleRate) = 0; | |||||
| /** Allows the source to release anything it no longer needs after playback has stopped. | |||||
| This will be called when the source is no longer going to have its getNextAudioBlock() | |||||
| method called, so it should release any spare memory, etc. that it might have | |||||
| allocated during the prepareToPlay() call. | |||||
| Note that there's no guarantee that prepareToPlay() will actually have been called before | |||||
| releaseResources(), and it may be called more than once in succession, so make sure your | |||||
| code is robust and doesn't make any assumptions about when it will be called. | |||||
| @see prepareToPlay, getNextAudioBlock | |||||
| */ | |||||
| virtual void releaseResources() = 0; | |||||
| /** Called repeatedly to fetch subsequent blocks of audio data. | |||||
| After calling the prepareToPlay() method, this callback will be made each | |||||
| time the audio playback hardware (or whatever other destination the audio | |||||
| data is going to) needs another block of data. | |||||
| It will generally be called on a high-priority system thread, or possibly even | |||||
| an interrupt, so be careful not to do too much work here, as that will cause | |||||
| audio glitches! | |||||
| @see AudioSourceChannelInfo, prepareToPlay, releaseResources | |||||
| */ | |||||
| virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0; | |||||
| }; | |||||
| #endif // JUCE_AUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,260 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* source_, | |||||
| TimeSliceThread& backgroundThread_, | |||||
| const bool deleteSourceWhenDeleted, | |||||
| const int numberOfSamplesToBuffer_, | |||||
| const int numberOfChannels_) | |||||
| : source (source_, deleteSourceWhenDeleted), | |||||
| backgroundThread (backgroundThread_), | |||||
| numberOfSamplesToBuffer (jmax (1024, numberOfSamplesToBuffer_)), | |||||
| numberOfChannels (numberOfChannels_), | |||||
| buffer (numberOfChannels_, 0), | |||||
| bufferValidStart (0), | |||||
| bufferValidEnd (0), | |||||
| nextPlayPos (0), | |||||
| sampleRate (0), | |||||
| wasSourceLooping (false), | |||||
| isPrepared (false) | |||||
| { | |||||
| jassert (source_ != nullptr); | |||||
| jassert (numberOfSamplesToBuffer_ > 1024); // not much point using this class if you're | |||||
| // not using a larger buffer.. | |||||
| } | |||||
| BufferingAudioSource::~BufferingAudioSource() | |||||
| { | |||||
| releaseResources(); | |||||
| } | |||||
| //============================================================================== | |||||
| void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate_) | |||||
| { | |||||
| const int bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); | |||||
| if (sampleRate_ != sampleRate | |||||
| || bufferSizeNeeded != buffer.getNumSamples() | |||||
| || ! isPrepared) | |||||
| { | |||||
| backgroundThread.removeTimeSliceClient (this); | |||||
| isPrepared = true; | |||||
| sampleRate = sampleRate_; | |||||
| source->prepareToPlay (samplesPerBlockExpected, sampleRate_); | |||||
| buffer.setSize (numberOfChannels, bufferSizeNeeded); | |||||
| buffer.clear(); | |||||
| bufferValidStart = 0; | |||||
| bufferValidEnd = 0; | |||||
| backgroundThread.addTimeSliceClient (this); | |||||
| while (bufferValidEnd - bufferValidStart < jmin (((int) sampleRate_) / 4, | |||||
| buffer.getNumSamples() / 2)) | |||||
| { | |||||
| backgroundThread.moveToFrontOfQueue (this); | |||||
| Thread::sleep (5); | |||||
| } | |||||
| } | |||||
| } | |||||
| void BufferingAudioSource::releaseResources() | |||||
| { | |||||
| isPrepared = false; | |||||
| backgroundThread.removeTimeSliceClient (this); | |||||
| buffer.setSize (numberOfChannels, 0); | |||||
| source->releaseResources(); | |||||
| } | |||||
| void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
| { | |||||
| const ScopedLock sl (bufferStartPosLock); | |||||
| const int validStart = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||||
| const int validEnd = (int) (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||||
| if (validStart == validEnd) | |||||
| { | |||||
| // total cache miss | |||||
| info.clearActiveBufferRegion(); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (validStart > 0) | |||||
| info.buffer->clear (info.startSample, validStart); // partial cache miss at start | |||||
| if (validEnd < info.numSamples) | |||||
| info.buffer->clear (info.startSample + validEnd, | |||||
| info.numSamples - validEnd); // partial cache miss at end | |||||
| if (validStart < validEnd) | |||||
| { | |||||
| for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) | |||||
| { | |||||
| jassert (buffer.getNumSamples() > 0); | |||||
| const int startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); | |||||
| const int endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); | |||||
| if (startBufferIndex < endBufferIndex) | |||||
| { | |||||
| info.buffer->copyFrom (chan, info.startSample + validStart, | |||||
| buffer, | |||||
| chan, startBufferIndex, | |||||
| validEnd - validStart); | |||||
| } | |||||
| else | |||||
| { | |||||
| const int initialSize = buffer.getNumSamples() - startBufferIndex; | |||||
| info.buffer->copyFrom (chan, info.startSample + validStart, | |||||
| buffer, | |||||
| chan, startBufferIndex, | |||||
| initialSize); | |||||
| info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, | |||||
| buffer, | |||||
| chan, 0, | |||||
| (validEnd - validStart) - initialSize); | |||||
| } | |||||
| } | |||||
| } | |||||
| nextPlayPos += info.numSamples; | |||||
| } | |||||
| } | |||||
| int64 BufferingAudioSource::getNextReadPosition() const | |||||
| { | |||||
| jassert (source->getTotalLength() > 0); | |||||
| return (source->isLooping() && nextPlayPos > 0) | |||||
| ? nextPlayPos % source->getTotalLength() | |||||
| : nextPlayPos; | |||||
| } | |||||
| void BufferingAudioSource::setNextReadPosition (int64 newPosition) | |||||
| { | |||||
| const ScopedLock sl (bufferStartPosLock); | |||||
| nextPlayPos = newPosition; | |||||
| backgroundThread.moveToFrontOfQueue (this); | |||||
| } | |||||
| bool BufferingAudioSource::readNextBufferChunk() | |||||
| { | |||||
| int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; | |||||
| { | |||||
| const ScopedLock sl (bufferStartPosLock); | |||||
| if (wasSourceLooping != isLooping()) | |||||
| { | |||||
| wasSourceLooping = isLooping(); | |||||
| bufferValidStart = 0; | |||||
| bufferValidEnd = 0; | |||||
| } | |||||
| newBVS = jmax ((int64) 0, nextPlayPos); | |||||
| newBVE = newBVS + buffer.getNumSamples() - 4; | |||||
| sectionToReadStart = 0; | |||||
| sectionToReadEnd = 0; | |||||
| const int maxChunkSize = 2048; | |||||
| if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) | |||||
| { | |||||
| newBVE = jmin (newBVE, newBVS + maxChunkSize); | |||||
| sectionToReadStart = newBVS; | |||||
| sectionToReadEnd = newBVE; | |||||
| bufferValidStart = 0; | |||||
| bufferValidEnd = 0; | |||||
| } | |||||
| else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 | |||||
| || std::abs ((int) (newBVE - bufferValidEnd)) > 512) | |||||
| { | |||||
| newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); | |||||
| sectionToReadStart = bufferValidEnd; | |||||
| sectionToReadEnd = newBVE; | |||||
| bufferValidStart = newBVS; | |||||
| bufferValidEnd = jmin (bufferValidEnd, newBVE); | |||||
| } | |||||
| } | |||||
| if (sectionToReadStart == sectionToReadEnd) | |||||
| return false; | |||||
| jassert (buffer.getNumSamples() > 0); | |||||
| const int bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); | |||||
| const int bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); | |||||
| if (bufferIndexStart < bufferIndexEnd) | |||||
| { | |||||
| readBufferSection (sectionToReadStart, | |||||
| (int) (sectionToReadEnd - sectionToReadStart), | |||||
| bufferIndexStart); | |||||
| } | |||||
| else | |||||
| { | |||||
| const int initialSize = buffer.getNumSamples() - bufferIndexStart; | |||||
| readBufferSection (sectionToReadStart, | |||||
| initialSize, | |||||
| bufferIndexStart); | |||||
| readBufferSection (sectionToReadStart + initialSize, | |||||
| (int) (sectionToReadEnd - sectionToReadStart) - initialSize, | |||||
| 0); | |||||
| } | |||||
| { | |||||
| const ScopedLock sl2 (bufferStartPosLock); | |||||
| bufferValidStart = newBVS; | |||||
| bufferValidEnd = newBVE; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void BufferingAudioSource::readBufferSection (const int64 start, const int length, const int bufferOffset) | |||||
| { | |||||
| if (source->getNextReadPosition() != start) | |||||
| source->setNextReadPosition (start); | |||||
| AudioSourceChannelInfo info (&buffer, bufferOffset, length); | |||||
| source->getNextAudioBlock (info); | |||||
| } | |||||
| int BufferingAudioSource::useTimeSlice() | |||||
| { | |||||
| return readNextBufferChunk() ? 1 : 100; | |||||
| } | |||||
| @@ -0,0 +1,113 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_BUFFERINGAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_BUFFERINGAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_PositionableAudioSource.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| An AudioSource which takes another source as input, and buffers it using a thread. | |||||
| Create this as a wrapper around another thread, and it will read-ahead with | |||||
| a background thread to smooth out playback. You can either create one of these | |||||
| directly, or use it indirectly using an AudioTransportSource. | |||||
| @see PositionableAudioSource, AudioTransportSource | |||||
| */ | |||||
| class JUCE_API BufferingAudioSource : public PositionableAudioSource, | |||||
| private TimeSliceClient | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a BufferingAudioSource. | |||||
| @param source the input source to read from | |||||
| @param backgroundThread a background thread that will be used for the | |||||
| background read-ahead. This object must not be deleted | |||||
| until after any BufferedAudioSources that are using it | |||||
| have been deleted! | |||||
| @param deleteSourceWhenDeleted if true, then the input source object will | |||||
| be deleted when this object is deleted | |||||
| @param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||||
| @param numberOfChannels the number of channels that will be played | |||||
| */ | |||||
| BufferingAudioSource (PositionableAudioSource* source, | |||||
| TimeSliceThread& backgroundThread, | |||||
| bool deleteSourceWhenDeleted, | |||||
| int numberOfSamplesToBuffer, | |||||
| int numberOfChannels = 2); | |||||
| /** Destructor. | |||||
| The input source may be deleted depending on whether the deleteSourceWhenDeleted | |||||
| flag was set in the constructor. | |||||
| */ | |||||
| ~BufferingAudioSource(); | |||||
| //============================================================================== | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void releaseResources() override; | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override; | |||||
| //============================================================================== | |||||
| /** Implements the PositionableAudioSource method. */ | |||||
| void setNextReadPosition (int64 newPosition) override; | |||||
| /** Implements the PositionableAudioSource method. */ | |||||
| int64 getNextReadPosition() const override; | |||||
| /** Implements the PositionableAudioSource method. */ | |||||
| int64 getTotalLength() const override { return source->getTotalLength(); } | |||||
| /** Implements the PositionableAudioSource method. */ | |||||
| bool isLooping() const override { return source->isLooping(); } | |||||
| private: | |||||
| //============================================================================== | |||||
| OptionalScopedPointer<PositionableAudioSource> source; | |||||
| TimeSliceThread& backgroundThread; | |||||
| int numberOfSamplesToBuffer, numberOfChannels; | |||||
| AudioSampleBuffer buffer; | |||||
| CriticalSection bufferStartPosLock; | |||||
| int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||||
| double volatile sampleRate; | |||||
| bool wasSourceLooping, isPrepared; | |||||
| bool readNextBufferChunk(); | |||||
| void readBufferSection (int64 start, int length, int bufferOffset); | |||||
| int useTimeSlice(); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) | |||||
| }; | |||||
| #endif // JUCE_BUFFERINGAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,185 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, | |||||
| const bool deleteSourceWhenDeleted) | |||||
| : source (source_, deleteSourceWhenDeleted), | |||||
| requiredNumberOfChannels (2), | |||||
| buffer (2, 16) | |||||
| { | |||||
| remappedInfo.buffer = &buffer; | |||||
| remappedInfo.startSample = 0; | |||||
| } | |||||
| ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {} | |||||
| //============================================================================== | |||||
| void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| requiredNumberOfChannels = requiredNumberOfChannels_; | |||||
| } | |||||
| void ChannelRemappingAudioSource::clearAllMappings() | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| remappedInputs.clear(); | |||||
| remappedOutputs.clear(); | |||||
| } | |||||
| void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| while (remappedInputs.size() < destIndex) | |||||
| remappedInputs.add (-1); | |||||
| remappedInputs.set (destIndex, sourceIndex); | |||||
| } | |||||
| void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| while (remappedOutputs.size() < sourceIndex) | |||||
| remappedOutputs.add (-1); | |||||
| remappedOutputs.set (sourceIndex, destIndex); | |||||
| } | |||||
| int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) | |||||
| return remappedInputs.getUnchecked (inputChannelIndex); | |||||
| return -1; | |||||
| } | |||||
| int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) | |||||
| return remappedOutputs .getUnchecked (outputChannelIndex); | |||||
| return -1; | |||||
| } | |||||
| //============================================================================== | |||||
| void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
| { | |||||
| source->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
| } | |||||
| void ChannelRemappingAudioSource::releaseResources() | |||||
| { | |||||
| source->releaseResources(); | |||||
| } | |||||
| void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); | |||||
| const int numChans = bufferToFill.buffer->getNumChannels(); | |||||
| for (int i = 0; i < buffer.getNumChannels(); ++i) | |||||
| { | |||||
| const int remappedChan = getRemappedInputChannel (i); | |||||
| if (remappedChan >= 0 && remappedChan < numChans) | |||||
| { | |||||
| buffer.copyFrom (i, 0, *bufferToFill.buffer, | |||||
| remappedChan, | |||||
| bufferToFill.startSample, | |||||
| bufferToFill.numSamples); | |||||
| } | |||||
| else | |||||
| { | |||||
| buffer.clear (i, 0, bufferToFill.numSamples); | |||||
| } | |||||
| } | |||||
| remappedInfo.numSamples = bufferToFill.numSamples; | |||||
| source->getNextAudioBlock (remappedInfo); | |||||
| bufferToFill.clearActiveBufferRegion(); | |||||
| for (int i = 0; i < requiredNumberOfChannels; ++i) | |||||
| { | |||||
| const int remappedChan = getRemappedOutputChannel (i); | |||||
| if (remappedChan >= 0 && remappedChan < numChans) | |||||
| { | |||||
| bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, | |||||
| buffer, i, 0, bufferToFill.numSamples); | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| XmlElement* ChannelRemappingAudioSource::createXml() const | |||||
| { | |||||
| XmlElement* e = new XmlElement ("MAPPINGS"); | |||||
| String ins, outs; | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = 0; i < remappedInputs.size(); ++i) | |||||
| ins << remappedInputs.getUnchecked(i) << ' '; | |||||
| for (int i = 0; i < remappedOutputs.size(); ++i) | |||||
| outs << remappedOutputs.getUnchecked(i) << ' '; | |||||
| e->setAttribute ("inputs", ins.trimEnd()); | |||||
| e->setAttribute ("outputs", outs.trimEnd()); | |||||
| return e; | |||||
| } | |||||
| void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) | |||||
| { | |||||
| if (e.hasTagName ("MAPPINGS")) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| clearAllMappings(); | |||||
| StringArray ins, outs; | |||||
| ins.addTokens (e.getStringAttribute ("inputs"), false); | |||||
| outs.addTokens (e.getStringAttribute ("outputs"), false); | |||||
| for (int i = 0; i < ins.size(); ++i) | |||||
| remappedInputs.add (ins[i].getIntValue()); | |||||
| for (int i = 0; i < outs.size(); ++i) | |||||
| remappedOutputs.add (outs[i].getIntValue()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,148 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_CHANNELREMAPPINGAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_CHANNELREMAPPINGAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| An AudioSource that takes the audio from another source, and re-maps its | |||||
| input and output channels to a different arrangement. | |||||
| You can use this to increase or decrease the number of channels that an | |||||
| audio source uses, or to re-order those channels. | |||||
| Call the reset() method before using it to set up a default mapping, and then | |||||
| the setInputChannelMapping() and setOutputChannelMapping() methods to | |||||
| create an appropriate mapping, otherwise no channels will be connected and | |||||
| it'll produce silence. | |||||
| @see AudioSource | |||||
| */ | |||||
| class ChannelRemappingAudioSource : public AudioSource | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a remapping source that will pass on audio from the given input. | |||||
| @param source the input source to use. Make sure that this doesn't | |||||
| get deleted before the ChannelRemappingAudioSource object | |||||
| @param deleteSourceWhenDeleted if true, the input source will be deleted | |||||
| when this object is deleted, if false, the caller is | |||||
| responsible for its deletion | |||||
| */ | |||||
| ChannelRemappingAudioSource (AudioSource* source, | |||||
| bool deleteSourceWhenDeleted); | |||||
| /** Destructor. */ | |||||
| ~ChannelRemappingAudioSource(); | |||||
| //============================================================================== | |||||
| /** Specifies a number of channels that this audio source must produce from its | |||||
| getNextAudioBlock() callback. | |||||
| */ | |||||
| void setNumberOfChannelsToProduce (int requiredNumberOfChannels); | |||||
| /** Clears any mapped channels. | |||||
| After this, no channels are mapped, so this object will produce silence. Create | |||||
| some mappings with setInputChannelMapping() and setOutputChannelMapping(). | |||||
| */ | |||||
| void clearAllMappings(); | |||||
| /** Creates an input channel mapping. | |||||
| When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming | |||||
| data will be sent to destChannelIndex of our input source. | |||||
| @param destChannelIndex the index of an input channel in our input audio source (i.e. the | |||||
| source specified when this object was created). | |||||
| @param sourceChannelIndex the index of the input channel in the incoming audio data buffer | |||||
| during our getNextAudioBlock() callback | |||||
| */ | |||||
| void setInputChannelMapping (int destChannelIndex, | |||||
| int sourceChannelIndex); | |||||
| /** Creates an output channel mapping. | |||||
| When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by | |||||
| our input audio source will be copied to channel destChannelIndex of the final buffer. | |||||
| @param sourceChannelIndex the index of an output channel coming from our input audio source | |||||
| (i.e. the source specified when this object was created). | |||||
| @param destChannelIndex the index of the output channel in the incoming audio data buffer | |||||
| during our getNextAudioBlock() callback | |||||
| */ | |||||
| void setOutputChannelMapping (int sourceChannelIndex, | |||||
| int destChannelIndex); | |||||
| /** Returns the channel from our input that will be sent to channel inputChannelIndex of | |||||
| our input audio source. | |||||
| */ | |||||
| int getRemappedInputChannel (int inputChannelIndex) const; | |||||
| /** Returns the output channel to which channel outputChannelIndex of our input audio | |||||
| source will be sent to. | |||||
| */ | |||||
| int getRemappedOutputChannel (int outputChannelIndex) const; | |||||
| //============================================================================== | |||||
| /** Returns an XML object to encapsulate the state of the mappings. | |||||
| @see restoreFromXml | |||||
| */ | |||||
| XmlElement* createXml() const; | |||||
| /** Restores the mappings from an XML object created by createXML(). | |||||
| @see createXml | |||||
| */ | |||||
| void restoreFromXml (const XmlElement& e); | |||||
| //============================================================================== | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
| void releaseResources(); | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
| private: | |||||
| //============================================================================== | |||||
| OptionalScopedPointer<AudioSource> source; | |||||
| Array <int> remappedInputs, remappedOutputs; | |||||
| int requiredNumberOfChannels; | |||||
| AudioSampleBuffer buffer; | |||||
| AudioSourceChannelInfo remappedInfo; | |||||
| CriticalSection lock; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource) | |||||
| }; | |||||
| #endif // JUCE_CHANNELREMAPPINGAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,77 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource, | |||||
| const bool deleteInputWhenDeleted) | |||||
| : input (inputSource, deleteInputWhenDeleted) | |||||
| { | |||||
| jassert (inputSource != nullptr); | |||||
| for (int i = 2; --i >= 0;) | |||||
| iirFilters.add (new IIRFilter()); | |||||
| } | |||||
| IIRFilterAudioSource::~IIRFilterAudioSource() {} | |||||
| //============================================================================== | |||||
| void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients) | |||||
| { | |||||
| for (int i = iirFilters.size(); --i >= 0;) | |||||
| iirFilters.getUnchecked(i)->setCoefficients (newCoefficients); | |||||
| } | |||||
| void IIRFilterAudioSource::makeInactive() | |||||
| { | |||||
| for (int i = iirFilters.size(); --i >= 0;) | |||||
| iirFilters.getUnchecked(i)->makeInactive(); | |||||
| } | |||||
| //============================================================================== | |||||
| void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
| { | |||||
| input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
| for (int i = iirFilters.size(); --i >= 0;) | |||||
| iirFilters.getUnchecked(i)->reset(); | |||||
| } | |||||
| void IIRFilterAudioSource::releaseResources() | |||||
| { | |||||
| input->releaseResources(); | |||||
| } | |||||
| void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
| { | |||||
| input->getNextAudioBlock (bufferToFill); | |||||
| const int numChannels = bufferToFill.buffer->getNumChannels(); | |||||
| while (numChannels > iirFilters.size()) | |||||
| iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| iirFilters.getUnchecked(i) | |||||
| ->processSamples (bufferToFill.buffer->getSampleData (i, bufferToFill.startSample), | |||||
| bufferToFill.numSamples); | |||||
| } | |||||
| @@ -0,0 +1,73 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_IIRFILTERAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_IIRFILTERAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| #include "../effects/juce_IIRFilter.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| An AudioSource that performs an IIR filter on another source. | |||||
| */ | |||||
| class JUCE_API IIRFilterAudioSource : public AudioSource | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a IIRFilterAudioSource for a given input source. | |||||
| @param inputSource the input source to read from - this must not be null | |||||
| @param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
| this object is deleted | |||||
| */ | |||||
| IIRFilterAudioSource (AudioSource* inputSource, | |||||
| bool deleteInputWhenDeleted); | |||||
| /** Destructor. */ | |||||
| ~IIRFilterAudioSource(); | |||||
| //============================================================================== | |||||
| /** Changes the filter to use the same parameters as the one being passed in. */ | |||||
| void setCoefficients (const IIRCoefficients& newCoefficients); | |||||
| /** Calls IIRFilter::makeInactive() on all the filters being used internally. */ | |||||
| void makeInactive(); | |||||
| //============================================================================== | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
| void releaseResources(); | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
| private: | |||||
| //============================================================================== | |||||
| OptionalScopedPointer<AudioSource> input; | |||||
| OwnedArray <IIRFilter> iirFilters; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource) | |||||
| }; | |||||
| #endif // JUCE_IIRFILTERAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,157 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| MixerAudioSource::MixerAudioSource() | |||||
| : tempBuffer (2, 0), | |||||
| currentSampleRate (0.0), | |||||
| bufferSizeExpected (0) | |||||
| { | |||||
| } | |||||
| MixerAudioSource::~MixerAudioSource() | |||||
| { | |||||
| removeAllInputs(); | |||||
| } | |||||
| //============================================================================== | |||||
| void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) | |||||
| { | |||||
| if (input != nullptr && ! inputs.contains (input)) | |||||
| { | |||||
| double localRate; | |||||
| int localBufferSize; | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| localRate = currentSampleRate; | |||||
| localBufferSize = bufferSizeExpected; | |||||
| } | |||||
| if (localRate > 0.0) | |||||
| input->prepareToPlay (localBufferSize, localRate); | |||||
| const ScopedLock sl (lock); | |||||
| inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); | |||||
| inputs.add (input); | |||||
| } | |||||
| } | |||||
| void MixerAudioSource::removeInputSource (AudioSource* const input) | |||||
| { | |||||
| if (input != nullptr) | |||||
| { | |||||
| ScopedPointer<AudioSource> toDelete; | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| const int index = inputs.indexOf (input); | |||||
| if (index < 0) | |||||
| return; | |||||
| if (inputsToDelete [index]) | |||||
| toDelete = input; | |||||
| inputsToDelete.shiftBits (-1, index); | |||||
| inputs.remove (index); | |||||
| } | |||||
| input->releaseResources(); | |||||
| } | |||||
| } | |||||
| void MixerAudioSource::removeAllInputs() | |||||
| { | |||||
| OwnedArray<AudioSource> toDelete; | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = inputs.size(); --i >= 0;) | |||||
| if (inputsToDelete[i]) | |||||
| toDelete.add (inputs.getUnchecked(i)); | |||||
| inputs.clear(); | |||||
| } | |||||
| for (int i = toDelete.size(); --i >= 0;) | |||||
| toDelete.getUnchecked(i)->releaseResources(); | |||||
| } | |||||
| void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
| { | |||||
| tempBuffer.setSize (2, samplesPerBlockExpected); | |||||
| const ScopedLock sl (lock); | |||||
| currentSampleRate = sampleRate; | |||||
| bufferSizeExpected = samplesPerBlockExpected; | |||||
| for (int i = inputs.size(); --i >= 0;) | |||||
| inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
| } | |||||
| void MixerAudioSource::releaseResources() | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = inputs.size(); --i >= 0;) | |||||
| inputs.getUnchecked(i)->releaseResources(); | |||||
| tempBuffer.setSize (2, 0); | |||||
| currentSampleRate = 0; | |||||
| bufferSizeExpected = 0; | |||||
| } | |||||
| void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| if (inputs.size() > 0) | |||||
| { | |||||
| inputs.getUnchecked(0)->getNextAudioBlock (info); | |||||
| if (inputs.size() > 1) | |||||
| { | |||||
| tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), | |||||
| info.buffer->getNumSamples()); | |||||
| AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples); | |||||
| for (int i = 1; i < inputs.size(); ++i) | |||||
| { | |||||
| inputs.getUnchecked(i)->getNextAudioBlock (info2); | |||||
| for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) | |||||
| info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); | |||||
| } | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| info.clearActiveBufferRegion(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,103 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_MIXERAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_MIXERAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| An AudioSource that mixes together the output of a set of other AudioSources. | |||||
| Input sources can be added and removed while the mixer is running as long as their | |||||
| prepareToPlay() and releaseResources() methods are called before and after adding | |||||
| them to the mixer. | |||||
| */ | |||||
| class JUCE_API MixerAudioSource : public AudioSource | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a MixerAudioSource. */ | |||||
| MixerAudioSource(); | |||||
| /** Destructor. */ | |||||
| ~MixerAudioSource(); | |||||
| //============================================================================== | |||||
| /** Adds an input source to the mixer. | |||||
| If the mixer is running you'll need to make sure that the input source | |||||
| is ready to play by calling its prepareToPlay() method before adding it. | |||||
| If the mixer is stopped, then its input sources will be automatically | |||||
| prepared when the mixer's prepareToPlay() method is called. | |||||
| @param newInput the source to add to the mixer | |||||
| @param deleteWhenRemoved if true, then this source will be deleted when | |||||
| no longer needed by the mixer. | |||||
| */ | |||||
| void addInputSource (AudioSource* newInput, bool deleteWhenRemoved); | |||||
| /** Removes an input source. | |||||
| If the source was added by calling addInputSource() with the deleteWhenRemoved | |||||
| flag set, it will be deleted by this method. | |||||
| */ | |||||
| void removeInputSource (AudioSource* input); | |||||
| /** Removes all the input sources. | |||||
| Any sources which were added by calling addInputSource() with the deleteWhenRemoved | |||||
| flag set will be deleted by this method. | |||||
| */ | |||||
| void removeAllInputs(); | |||||
| //============================================================================== | |||||
| /** Implementation of the AudioSource method. | |||||
| This will call prepareToPlay() on all its input sources. | |||||
| */ | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
| /** Implementation of the AudioSource method. | |||||
| This will call releaseResources() on all its input sources. | |||||
| */ | |||||
| void releaseResources(); | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
| private: | |||||
| //============================================================================== | |||||
| Array <AudioSource*> inputs; | |||||
| BigInteger inputsToDelete; | |||||
| CriticalSection lock; | |||||
| AudioSampleBuffer tempBuffer; | |||||
| double currentSampleRate; | |||||
| int bufferSizeExpected; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource) | |||||
| }; | |||||
| #endif // JUCE_MIXERAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_POSITIONABLEAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_POSITIONABLEAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| A type of AudioSource which can be repositioned. | |||||
| The basic AudioSource just streams continuously with no idea of a current | |||||
| time or length, so the PositionableAudioSource is used for a finite stream | |||||
| that has a current read position. | |||||
| @see AudioSource, AudioTransportSource | |||||
| */ | |||||
| class JUCE_API PositionableAudioSource : public AudioSource | |||||
| { | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** Creates the PositionableAudioSource. */ | |||||
| PositionableAudioSource() noexcept {} | |||||
| public: | |||||
| /** Destructor */ | |||||
| ~PositionableAudioSource() {} | |||||
| //============================================================================== | |||||
| /** Tells the stream to move to a new position. | |||||
| Calling this indicates that the next call to AudioSource::getNextAudioBlock() | |||||
| should return samples from this position. | |||||
| Note that this may be called on a different thread to getNextAudioBlock(), | |||||
| so the subclass should make sure it's synchronised. | |||||
| */ | |||||
| virtual void setNextReadPosition (int64 newPosition) = 0; | |||||
| /** Returns the position from which the next block will be returned. | |||||
| @see setNextReadPosition | |||||
| */ | |||||
| virtual int64 getNextReadPosition() const = 0; | |||||
| /** Returns the total length of the stream (in samples). */ | |||||
| virtual int64 getTotalLength() const = 0; | |||||
| /** Returns true if this source is actually playing in a loop. */ | |||||
| virtual bool isLooping() const = 0; | |||||
| /** Tells the source whether you'd like it to play in a loop. */ | |||||
| virtual void setLooping (bool shouldLoop) { (void) shouldLoop; } | |||||
| }; | |||||
| #endif // JUCE_POSITIONABLEAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,257 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, | |||||
| const bool deleteInputWhenDeleted, | |||||
| const int numChannels_) | |||||
| : input (inputSource, deleteInputWhenDeleted), | |||||
| ratio (1.0), | |||||
| lastRatio (1.0), | |||||
| buffer (numChannels_, 0), | |||||
| bufferPos (0), | |||||
| sampsInBuffer (0), | |||||
| subSampleOffset (0), | |||||
| numChannels (numChannels_) | |||||
| { | |||||
| jassert (input != nullptr); | |||||
| zeromem (coefficients, sizeof (coefficients)); | |||||
| } | |||||
| ResamplingAudioSource::~ResamplingAudioSource() {} | |||||
| void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) | |||||
| { | |||||
| jassert (samplesInPerOutputSample > 0); | |||||
| const SpinLock::ScopedLockType sl (ratioLock); | |||||
| ratio = jmax (0.0, samplesInPerOutputSample); | |||||
| } | |||||
| void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, | |||||
| double sampleRate) | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (ratioLock); | |||||
| input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
| buffer.setSize (numChannels, roundToInt (samplesPerBlockExpected * ratio) + 32); | |||||
| buffer.clear(); | |||||
| sampsInBuffer = 0; | |||||
| bufferPos = 0; | |||||
| subSampleOffset = 0.0; | |||||
| filterStates.calloc ((size_t) numChannels); | |||||
| srcBuffers.calloc ((size_t) numChannels); | |||||
| destBuffers.calloc ((size_t) numChannels); | |||||
| createLowPass (ratio); | |||||
| resetFilters(); | |||||
| } | |||||
| void ResamplingAudioSource::releaseResources() | |||||
| { | |||||
| input->releaseResources(); | |||||
| buffer.setSize (numChannels, 0); | |||||
| } | |||||
| void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
| { | |||||
| double localRatio; | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (ratioLock); | |||||
| localRatio = ratio; | |||||
| } | |||||
| if (lastRatio != localRatio) | |||||
| { | |||||
| createLowPass (localRatio); | |||||
| lastRatio = localRatio; | |||||
| } | |||||
| const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 2; | |||||
| int bufferSize = buffer.getNumSamples(); | |||||
| if (bufferSize < sampsNeeded + 8) | |||||
| { | |||||
| bufferPos %= bufferSize; | |||||
| bufferSize = sampsNeeded + 32; | |||||
| buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); | |||||
| } | |||||
| bufferPos %= bufferSize; | |||||
| int endOfBufferPos = bufferPos + sampsInBuffer; | |||||
| const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); | |||||
| while (sampsNeeded > sampsInBuffer) | |||||
| { | |||||
| endOfBufferPos %= bufferSize; | |||||
| int numToDo = jmin (sampsNeeded - sampsInBuffer, | |||||
| bufferSize - endOfBufferPos); | |||||
| AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo); | |||||
| input->getNextAudioBlock (readInfo); | |||||
| if (localRatio > 1.0001) | |||||
| { | |||||
| // for down-sampling, pre-apply the filter.. | |||||
| for (int i = channelsToProcess; --i >= 0;) | |||||
| applyFilter (buffer.getSampleData (i, endOfBufferPos), numToDo, filterStates[i]); | |||||
| } | |||||
| sampsInBuffer += numToDo; | |||||
| endOfBufferPos += numToDo; | |||||
| } | |||||
| for (int channel = 0; channel < channelsToProcess; ++channel) | |||||
| { | |||||
| destBuffers[channel] = info.buffer->getSampleData (channel, info.startSample); | |||||
| srcBuffers[channel] = buffer.getSampleData (channel, 0); | |||||
| } | |||||
| int nextPos = (bufferPos + 1) % bufferSize; | |||||
| for (int m = info.numSamples; --m >= 0;) | |||||
| { | |||||
| const float alpha = (float) subSampleOffset; | |||||
| for (int channel = 0; channel < channelsToProcess; ++channel) | |||||
| *destBuffers[channel]++ = srcBuffers[channel][bufferPos] | |||||
| + alpha * (srcBuffers[channel][nextPos] - srcBuffers[channel][bufferPos]); | |||||
| subSampleOffset += localRatio; | |||||
| jassert (sampsInBuffer > 0); | |||||
| while (subSampleOffset >= 1.0) | |||||
| { | |||||
| if (++bufferPos >= bufferSize) | |||||
| bufferPos = 0; | |||||
| --sampsInBuffer; | |||||
| nextPos = (bufferPos + 1) % bufferSize; | |||||
| subSampleOffset -= 1.0; | |||||
| } | |||||
| } | |||||
| if (localRatio < 0.9999) | |||||
| { | |||||
| // for up-sampling, apply the filter after transposing.. | |||||
| for (int i = channelsToProcess; --i >= 0;) | |||||
| applyFilter (info.buffer->getSampleData (i, info.startSample), info.numSamples, filterStates[i]); | |||||
| } | |||||
| else if (localRatio <= 1.0001 && info.numSamples > 0) | |||||
| { | |||||
| // if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities | |||||
| for (int i = channelsToProcess; --i >= 0;) | |||||
| { | |||||
| const float* const endOfBuffer = info.buffer->getSampleData (i, info.startSample + info.numSamples - 1); | |||||
| FilterState& fs = filterStates[i]; | |||||
| if (info.numSamples > 1) | |||||
| { | |||||
| fs.y2 = fs.x2 = *(endOfBuffer - 1); | |||||
| } | |||||
| else | |||||
| { | |||||
| fs.y2 = fs.y1; | |||||
| fs.x2 = fs.x1; | |||||
| } | |||||
| fs.y1 = fs.x1 = *endOfBuffer; | |||||
| } | |||||
| } | |||||
| jassert (sampsInBuffer >= 0); | |||||
| } | |||||
| void ResamplingAudioSource::createLowPass (const double frequencyRatio) | |||||
| { | |||||
| const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio | |||||
| : 0.5 * frequencyRatio; | |||||
| const double n = 1.0 / std::tan (double_Pi * jmax (0.001, proportionalRate)); | |||||
| const double nSquared = n * n; | |||||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
| setFilterCoefficients (c1, | |||||
| c1 * 2.0f, | |||||
| c1, | |||||
| 1.0, | |||||
| c1 * 2.0 * (1.0 - nSquared), | |||||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
| } | |||||
| void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) | |||||
| { | |||||
| const double a = 1.0 / c4; | |||||
| c1 *= a; | |||||
| c2 *= a; | |||||
| c3 *= a; | |||||
| c5 *= a; | |||||
| c6 *= a; | |||||
| coefficients[0] = c1; | |||||
| coefficients[1] = c2; | |||||
| coefficients[2] = c3; | |||||
| coefficients[3] = c4; | |||||
| coefficients[4] = c5; | |||||
| coefficients[5] = c6; | |||||
| } | |||||
| void ResamplingAudioSource::resetFilters() | |||||
| { | |||||
| filterStates.clear ((size_t) numChannels); | |||||
| } | |||||
| void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) | |||||
| { | |||||
| while (--num >= 0) | |||||
| { | |||||
| const double in = *samples; | |||||
| double out = coefficients[0] * in | |||||
| + coefficients[1] * fs.x1 | |||||
| + coefficients[2] * fs.x2 | |||||
| - coefficients[4] * fs.y1 | |||||
| - coefficients[5] * fs.y2; | |||||
| #if JUCE_INTEL | |||||
| if (! (out < -1.0e-8 || out > 1.0e-8)) | |||||
| out = 0; | |||||
| #endif | |||||
| fs.x2 = fs.x1; | |||||
| fs.x1 = in; | |||||
| fs.y2 = fs.y1; | |||||
| fs.y1 = out; | |||||
| *samples++ = (float) out; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,105 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_RESAMPLINGAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_RESAMPLINGAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| A type of AudioSource that takes an input source and changes its sample rate. | |||||
| @see AudioSource | |||||
| */ | |||||
| class JUCE_API ResamplingAudioSource : public AudioSource | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a ResamplingAudioSource for a given input source. | |||||
| @param inputSource the input source to read from | |||||
| @param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
| this object is deleted | |||||
| @param numChannels the number of channels to process | |||||
| */ | |||||
| ResamplingAudioSource (AudioSource* inputSource, | |||||
| bool deleteInputWhenDeleted, | |||||
| int numChannels = 2); | |||||
| /** Destructor. */ | |||||
| ~ResamplingAudioSource(); | |||||
| /** Changes the resampling ratio. | |||||
| (This value can be changed at any time, even while the source is running). | |||||
| @param samplesInPerOutputSample if set to 1.0, the input is passed through; higher | |||||
| values will speed it up; lower values will slow it | |||||
| down. The ratio must be greater than 0 | |||||
| */ | |||||
| void setResamplingRatio (double samplesInPerOutputSample); | |||||
| /** Returns the current resampling ratio. | |||||
| This is the value that was set by setResamplingRatio(). | |||||
| */ | |||||
| double getResamplingRatio() const noexcept { return ratio; } | |||||
| //============================================================================== | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
| void releaseResources(); | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
| private: | |||||
| //============================================================================== | |||||
| OptionalScopedPointer<AudioSource> input; | |||||
| double ratio, lastRatio; | |||||
| AudioSampleBuffer buffer; | |||||
| int bufferPos, sampsInBuffer; | |||||
| double subSampleOffset; | |||||
| double coefficients[6]; | |||||
| SpinLock ratioLock; | |||||
| const int numChannels; | |||||
| HeapBlock<float*> destBuffers, srcBuffers; | |||||
| void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6); | |||||
| void createLowPass (double proportionalRate); | |||||
| struct FilterState | |||||
| { | |||||
| double x1, x2, y1, y2; | |||||
| }; | |||||
| HeapBlock<FilterState> filterStates; | |||||
| void resetFilters(); | |||||
| void applyFilter (float* samples, int num, FilterState& fs); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource) | |||||
| }; | |||||
| #endif // JUCE_RESAMPLINGAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) | |||||
| : input (inputSource, deleteInputWhenDeleted), | |||||
| bypass (false) | |||||
| { | |||||
| jassert (inputSource != nullptr); | |||||
| } | |||||
| ReverbAudioSource::~ReverbAudioSource() {} | |||||
| void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| input->prepareToPlay (samplesPerBlockExpected, sampleRate); | |||||
| reverb.setSampleRate (sampleRate); | |||||
| } | |||||
| void ReverbAudioSource::releaseResources() {} | |||||
| void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| input->getNextAudioBlock (bufferToFill); | |||||
| if (! bypass) | |||||
| { | |||||
| float* const firstChannel = bufferToFill.buffer->getSampleData (0, bufferToFill.startSample); | |||||
| if (bufferToFill.buffer->getNumChannels() > 1) | |||||
| { | |||||
| reverb.processStereo (firstChannel, | |||||
| bufferToFill.buffer->getSampleData (1, bufferToFill.startSample), | |||||
| bufferToFill.numSamples); | |||||
| } | |||||
| else | |||||
| { | |||||
| reverb.processMono (firstChannel, bufferToFill.numSamples); | |||||
| } | |||||
| } | |||||
| } | |||||
| void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| reverb.setParameters (newParams); | |||||
| } | |||||
| void ReverbAudioSource::setBypassed (bool b) noexcept | |||||
| { | |||||
| if (bypass != b) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| bypass = b; | |||||
| reverb.reset(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,79 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_REVERBAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_REVERBAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| #include "../effects/juce_Reverb.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| An AudioSource that uses the Reverb class to apply a reverb to another AudioSource. | |||||
| @see Reverb | |||||
| */ | |||||
| class JUCE_API ReverbAudioSource : public AudioSource | |||||
| { | |||||
| public: | |||||
| /** Creates a ReverbAudioSource to process a given input source. | |||||
| @param inputSource the input source to read from - this must not be null | |||||
| @param deleteInputWhenDeleted if true, the input source will be deleted when | |||||
| this object is deleted | |||||
| */ | |||||
| ReverbAudioSource (AudioSource* inputSource, | |||||
| bool deleteInputWhenDeleted); | |||||
| /** Destructor. */ | |||||
| ~ReverbAudioSource(); | |||||
| //============================================================================== | |||||
| /** Returns the parameters from the reverb. */ | |||||
| const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); } | |||||
| /** Changes the reverb's parameters. */ | |||||
| void setParameters (const Reverb::Parameters& newParams); | |||||
| void setBypassed (bool isBypassed) noexcept; | |||||
| bool isBypassed() const noexcept { return bypass; } | |||||
| //============================================================================== | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
| void releaseResources(); | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
| private: | |||||
| //============================================================================== | |||||
| CriticalSection lock; | |||||
| OptionalScopedPointer<AudioSource> input; | |||||
| Reverb reverb; | |||||
| volatile bool bypass; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource) | |||||
| }; | |||||
| #endif // JUCE_REVERBAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,76 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| ToneGeneratorAudioSource::ToneGeneratorAudioSource() | |||||
| : frequency (1000.0), | |||||
| sampleRate (44100.0), | |||||
| currentPhase (0.0), | |||||
| phasePerSample (0.0), | |||||
| amplitude (0.5f) | |||||
| { | |||||
| } | |||||
| ToneGeneratorAudioSource::~ToneGeneratorAudioSource() | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) | |||||
| { | |||||
| amplitude = newAmplitude; | |||||
| } | |||||
| void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) | |||||
| { | |||||
| frequency = newFrequencyHz; | |||||
| phasePerSample = 0.0; | |||||
| } | |||||
| //============================================================================== | |||||
| void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, | |||||
| double sampleRate_) | |||||
| { | |||||
| currentPhase = 0.0; | |||||
| phasePerSample = 0.0; | |||||
| sampleRate = sampleRate_; | |||||
| } | |||||
| void ToneGeneratorAudioSource::releaseResources() | |||||
| { | |||||
| } | |||||
| void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||||
| { | |||||
| if (phasePerSample == 0.0) | |||||
| phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
| for (int i = 0; i < info.numSamples; ++i) | |||||
| { | |||||
| const float sample = amplitude * (float) std::sin (currentPhase); | |||||
| currentPhase += phasePerSample; | |||||
| for (int j = info.buffer->getNumChannels(); --j >= 0;) | |||||
| *info.buffer->getSampleData (j, info.startSample + i) = sample; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,75 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_TONEGENERATORAUDIOSOURCE_H_INCLUDED | |||||
| #define JUCE_TONEGENERATORAUDIOSOURCE_H_INCLUDED | |||||
| #include "juce_AudioSource.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| A simple AudioSource that generates a sine wave. | |||||
| */ | |||||
| class JUCE_API ToneGeneratorAudioSource : public AudioSource | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a ToneGeneratorAudioSource. */ | |||||
| ToneGeneratorAudioSource(); | |||||
| /** Destructor. */ | |||||
| ~ToneGeneratorAudioSource(); | |||||
| //============================================================================== | |||||
| /** Sets the signal's amplitude. */ | |||||
| void setAmplitude (float newAmplitude); | |||||
| /** Sets the signal's frequency. */ | |||||
| void setFrequency (double newFrequencyHz); | |||||
| //============================================================================== | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void prepareToPlay (int samplesPerBlockExpected, double sampleRate); | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void releaseResources(); | |||||
| /** Implementation of the AudioSource method. */ | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill); | |||||
| private: | |||||
| //============================================================================== | |||||
| double frequency, sampleRate; | |||||
| double currentPhase, phasePerSample; | |||||
| float amplitude; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource) | |||||
| }; | |||||
| #endif // JUCE_TONEGENERATORAUDIOSOURCE_H_INCLUDED | |||||
| @@ -0,0 +1,432 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| SynthesiserSound::SynthesiserSound() | |||||
| { | |||||
| } | |||||
| SynthesiserSound::~SynthesiserSound() | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| SynthesiserVoice::SynthesiserVoice() | |||||
| : currentSampleRate (44100.0), | |||||
| currentlyPlayingNote (-1), | |||||
| noteOnTime (0), | |||||
| keyIsDown (false), | |||||
| sostenutoPedalDown (false) | |||||
| { | |||||
| } | |||||
| SynthesiserVoice::~SynthesiserVoice() | |||||
| { | |||||
| } | |||||
| bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const | |||||
| { | |||||
| return currentlyPlayingSound != nullptr | |||||
| && currentlyPlayingSound->appliesToChannel (midiChannel); | |||||
| } | |||||
| void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) | |||||
| { | |||||
| currentSampleRate = newRate; | |||||
| } | |||||
| void SynthesiserVoice::clearCurrentNote() | |||||
| { | |||||
| currentlyPlayingNote = -1; | |||||
| currentlyPlayingSound = nullptr; | |||||
| } | |||||
| //============================================================================== | |||||
| Synthesiser::Synthesiser() | |||||
| : sampleRate (0), | |||||
| lastNoteOnCounter (0), | |||||
| shouldStealNotes (true) | |||||
| { | |||||
| for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||||
| lastPitchWheelValues[i] = 0x2000; | |||||
| } | |||||
| Synthesiser::~Synthesiser() | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| SynthesiserVoice* Synthesiser::getVoice (const int index) const | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| return voices [index]; | |||||
| } | |||||
| void Synthesiser::clearVoices() | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| voices.clear(); | |||||
| } | |||||
| void Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| voices.add (newVoice); | |||||
| } | |||||
| void Synthesiser::removeVoice (const int index) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| voices.remove (index); | |||||
| } | |||||
| void Synthesiser::clearSounds() | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| sounds.clear(); | |||||
| } | |||||
| void Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| sounds.add (newSound); | |||||
| } | |||||
| void Synthesiser::removeSound (const int index) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| sounds.remove (index); | |||||
| } | |||||
| void Synthesiser::setNoteStealingEnabled (const bool shouldStealNotes_) | |||||
| { | |||||
| shouldStealNotes = shouldStealNotes_; | |||||
| } | |||||
| //============================================================================== | |||||
| void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||||
| { | |||||
| if (sampleRate != newRate) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| allNotesOff (0, false); | |||||
| sampleRate = newRate; | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate); | |||||
| } | |||||
| } | |||||
| void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, | |||||
| const MidiBuffer& midiData, | |||||
| int startSample, | |||||
| int numSamples) | |||||
| { | |||||
| // must set the sample rate before using this! | |||||
| jassert (sampleRate != 0); | |||||
| const ScopedLock sl (lock); | |||||
| MidiBuffer::Iterator midiIterator (midiData); | |||||
| midiIterator.setNextSamplePosition (startSample); | |||||
| MidiMessage m (0xf4, 0.0); | |||||
| while (numSamples > 0) | |||||
| { | |||||
| int midiEventPos; | |||||
| const bool useEvent = midiIterator.getNextEvent (m, midiEventPos) | |||||
| && midiEventPos < startSample + numSamples; | |||||
| const int numThisTime = useEvent ? midiEventPos - startSample | |||||
| : numSamples; | |||||
| if (numThisTime > 0) | |||||
| { | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| voices.getUnchecked (i)->renderNextBlock (outputBuffer, startSample, numThisTime); | |||||
| } | |||||
| if (useEvent) | |||||
| handleMidiEvent (m); | |||||
| startSample += numThisTime; | |||||
| numSamples -= numThisTime; | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||||
| { | |||||
| if (m.isNoteOn()) | |||||
| { | |||||
| noteOn (m.getChannel(), | |||||
| m.getNoteNumber(), | |||||
| m.getFloatVelocity()); | |||||
| } | |||||
| else if (m.isNoteOff()) | |||||
| { | |||||
| noteOff (m.getChannel(), | |||||
| m.getNoteNumber(), | |||||
| true); | |||||
| } | |||||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||||
| { | |||||
| allNotesOff (m.getChannel(), true); | |||||
| } | |||||
| else if (m.isPitchWheel()) | |||||
| { | |||||
| const int channel = m.getChannel(); | |||||
| const int wheelPos = m.getPitchWheelValue(); | |||||
| lastPitchWheelValues [channel - 1] = wheelPos; | |||||
| handlePitchWheel (channel, wheelPos); | |||||
| } | |||||
| else if (m.isController()) | |||||
| { | |||||
| handleController (m.getChannel(), | |||||
| m.getControllerNumber(), | |||||
| m.getControllerValue()); | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void Synthesiser::noteOn (const int midiChannel, | |||||
| const int midiNoteNumber, | |||||
| const float velocity) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = sounds.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserSound* const sound = sounds.getUnchecked(i); | |||||
| if (sound->appliesToNote (midiNoteNumber) | |||||
| && sound->appliesToChannel (midiChannel)) | |||||
| { | |||||
| // If hitting a note that's still ringing, stop it first (it could be | |||||
| // still playing because of the sustain or sostenuto pedal). | |||||
| for (int j = voices.size(); --j >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (j); | |||||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
| && voice->isPlayingChannel (midiChannel)) | |||||
| stopVoice (voice, true); | |||||
| } | |||||
| startVoice (findFreeVoice (sound, shouldStealNotes), | |||||
| sound, midiChannel, midiNoteNumber, velocity); | |||||
| } | |||||
| } | |||||
| } | |||||
| void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||||
| SynthesiserSound* const sound, | |||||
| const int midiChannel, | |||||
| const int midiNoteNumber, | |||||
| const float velocity) | |||||
| { | |||||
| if (voice != nullptr && sound != nullptr) | |||||
| { | |||||
| if (voice->currentlyPlayingSound != nullptr) | |||||
| voice->stopNote (false); | |||||
| voice->startNote (midiNoteNumber, velocity, sound, | |||||
| lastPitchWheelValues [midiChannel - 1]); | |||||
| voice->currentlyPlayingNote = midiNoteNumber; | |||||
| voice->noteOnTime = ++lastNoteOnCounter; | |||||
| voice->currentlyPlayingSound = sound; | |||||
| voice->keyIsDown = true; | |||||
| voice->sostenutoPedalDown = false; | |||||
| } | |||||
| } | |||||
| void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) | |||||
| { | |||||
| jassert (voice != nullptr); | |||||
| voice->stopNote (allowTailOff); | |||||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||||
| } | |||||
| void Synthesiser::noteOff (const int midiChannel, | |||||
| const int midiNoteNumber, | |||||
| const bool allowTailOff) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||||
| { | |||||
| if (SynthesiserSound* const sound = voice->getCurrentlyPlayingSound()) | |||||
| { | |||||
| if (sound->appliesToNote (midiNoteNumber) | |||||
| && sound->appliesToChannel (midiChannel)) | |||||
| { | |||||
| voice->keyIsDown = false; | |||||
| if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||||
| stopVoice (voice, allowTailOff); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->stopNote (allowTailOff); | |||||
| } | |||||
| sustainPedalsDown.clear(); | |||||
| } | |||||
| void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->pitchWheelMoved (wheelValue); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleController (const int midiChannel, | |||||
| const int controllerNumber, | |||||
| const int controllerValue) | |||||
| { | |||||
| switch (controllerNumber) | |||||
| { | |||||
| case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; | |||||
| case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; | |||||
| case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; | |||||
| default: break; | |||||
| } | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||||
| voice->controllerMoved (controllerNumber, controllerValue); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||||
| { | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| const ScopedLock sl (lock); | |||||
| if (isDown) | |||||
| { | |||||
| sustainPedalsDown.setBit (midiChannel); | |||||
| } | |||||
| else | |||||
| { | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||||
| stopVoice (voice, true); | |||||
| } | |||||
| sustainPedalsDown.clearBit (midiChannel); | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||||
| { | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->isPlayingChannel (midiChannel)) | |||||
| { | |||||
| if (isDown) | |||||
| voice->sostenutoPedalDown = true; | |||||
| else if (voice->sostenutoPedalDown) | |||||
| stopVoice (voice, true); | |||||
| } | |||||
| } | |||||
| } | |||||
| void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||||
| { | |||||
| (void) midiChannel; | |||||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||||
| } | |||||
| //============================================================================== | |||||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||||
| const bool stealIfNoneAvailable) const | |||||
| { | |||||
| const ScopedLock sl (lock); | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| if (voices.getUnchecked (i)->getCurrentlyPlayingNote() < 0 | |||||
| && voices.getUnchecked (i)->canPlaySound (soundToPlay)) | |||||
| return voices.getUnchecked (i); | |||||
| if (stealIfNoneAvailable) | |||||
| { | |||||
| // currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||||
| SynthesiserVoice* oldest = nullptr; | |||||
| for (int i = voices.size(); --i >= 0;) | |||||
| { | |||||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
| if (voice->canPlaySound (soundToPlay) | |||||
| && (oldest == nullptr || oldest->noteOnTime > voice->noteOnTime)) | |||||
| oldest = voice; | |||||
| } | |||||
| jassert (oldest != nullptr); | |||||
| return oldest; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| @@ -0,0 +1,495 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2013 - Raw Material Software 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_SYNTHESISER_H_INCLUDED | |||||
| #define JUCE_SYNTHESISER_H_INCLUDED | |||||
| #include "../buffers/juce_AudioSampleBuffer.h" | |||||
| #include "../midi/juce_MidiBuffer.h" | |||||
| //============================================================================== | |||||
| /** | |||||
| Describes one of the sounds that a Synthesiser can play. | |||||
| A synthesiser can contain one or more sounds, and a sound can choose which | |||||
| midi notes and channels can trigger it. | |||||
| The SynthesiserSound is a passive class that just describes what the sound is - | |||||
| the actual audio rendering for a sound is done by a SynthesiserVoice. This allows | |||||
| more than one SynthesiserVoice to play the same sound at the same time. | |||||
| @see Synthesiser, SynthesiserVoice | |||||
| */ | |||||
| class JUCE_API SynthesiserSound : public ReferenceCountedObject | |||||
| { | |||||
| protected: | |||||
| //============================================================================== | |||||
| SynthesiserSound(); | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~SynthesiserSound(); | |||||
| //============================================================================== | |||||
| /** Returns true if this sound should be played when a given midi note is pressed. | |||||
| The Synthesiser will use this information when deciding which sounds to trigger | |||||
| for a given note. | |||||
| */ | |||||
| virtual bool appliesToNote (const int midiNoteNumber) = 0; | |||||
| /** Returns true if the sound should be triggered by midi events on a given channel. | |||||
| The Synthesiser will use this information when deciding which sounds to trigger | |||||
| for a given note. | |||||
| */ | |||||
| virtual bool appliesToChannel (const int midiChannel) = 0; | |||||
| /** | |||||
| */ | |||||
| typedef ReferenceCountedObjectPtr <SynthesiserSound> Ptr; | |||||
| private: | |||||
| //============================================================================== | |||||
| JUCE_LEAK_DETECTOR (SynthesiserSound) | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Represents a voice that a Synthesiser can use to play a SynthesiserSound. | |||||
| A voice plays a single sound at a time, and a synthesiser holds an array of | |||||
| voices so that it can play polyphonically. | |||||
| @see Synthesiser, SynthesiserSound | |||||
| */ | |||||
| class JUCE_API SynthesiserVoice | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a voice. */ | |||||
| SynthesiserVoice(); | |||||
| /** Destructor. */ | |||||
| virtual ~SynthesiserVoice(); | |||||
| //============================================================================== | |||||
| /** Returns the midi note that this voice is currently playing. | |||||
| Returns a value less than 0 if no note is playing. | |||||
| */ | |||||
| int getCurrentlyPlayingNote() const { return currentlyPlayingNote; } | |||||
| /** Returns the sound that this voice is currently playing. | |||||
| Returns nullptr if it's not playing. | |||||
| */ | |||||
| SynthesiserSound::Ptr getCurrentlyPlayingSound() const { return currentlyPlayingSound; } | |||||
| /** Must return true if this voice object is capable of playing the given sound. | |||||
| If there are different classes of sound, and different classes of voice, a voice can | |||||
| choose which ones it wants to take on. | |||||
| A typical implementation of this method may just return true if there's only one type | |||||
| of voice and sound, or it might check the type of the sound object passed-in and | |||||
| see if it's one that it understands. | |||||
| */ | |||||
| virtual bool canPlaySound (SynthesiserSound* sound) = 0; | |||||
| /** Called to start a new note. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void startNote (const int midiNoteNumber, | |||||
| const float velocity, | |||||
| SynthesiserSound* sound, | |||||
| const int currentPitchWheelPosition) = 0; | |||||
| /** Called to stop a note. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all | |||||
| sound immediately, and must call clearCurrentNote() to reset the state of this voice | |||||
| and allow the synth to reassign it another sound. | |||||
| If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to | |||||
| begin fading out its sound, and it can stop playing until it's finished. As soon as it | |||||
| finishes playing (during the rendering callback), it must make sure that it calls | |||||
| clearCurrentNote(). | |||||
| */ | |||||
| virtual void stopNote (const bool allowTailOff) = 0; | |||||
| /** Called to let the voice know that the pitch wheel has been moved. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void pitchWheelMoved (const int newValue) = 0; | |||||
| /** Called to let the voice know that a midi controller has been moved. | |||||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||||
| */ | |||||
| virtual void controllerMoved (const int controllerNumber, | |||||
| const int newValue) = 0; | |||||
| //============================================================================== | |||||
| /** Renders the next block of data for this voice. | |||||
| The output audio data must be added to the current contents of the buffer provided. | |||||
| Only the region of the buffer between startSample and (startSample + numSamples) | |||||
| should be altered by this method. | |||||
| If the voice is currently silent, it should just return without doing anything. | |||||
| If the sound that the voice is playing finishes during the course of this rendered | |||||
| block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. | |||||
| The size of the blocks that are rendered can change each time it is called, and may | |||||
| involve rendering as little as 1 sample at a time. In between rendering callbacks, | |||||
| the voice's methods will be called to tell it about note and controller events. | |||||
| */ | |||||
| virtual void renderNextBlock (AudioSampleBuffer& outputBuffer, | |||||
| int startSample, | |||||
| int numSamples) = 0; | |||||
| /** Returns true if the voice is currently playing a sound which is mapped to the given | |||||
| midi channel. | |||||
| If it's not currently playing, this will return false. | |||||
| */ | |||||
| bool isPlayingChannel (int midiChannel) const; | |||||
| /** Changes the voice's reference sample rate. | |||||
| The rate is set so that subclasses know the output rate and can set their pitch | |||||
| accordingly. | |||||
| This method is called by the synth, and subclasses can access the current rate with | |||||
| the currentSampleRate member. | |||||
| */ | |||||
| void setCurrentPlaybackSampleRate (double newRate); | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** Returns the current target sample rate at which rendering is being done. | |||||
| This is available for subclasses so they can pitch things correctly. | |||||
| */ | |||||
| double getSampleRate() const { return currentSampleRate; } | |||||
| /** Resets the state of this voice after a sound has finished playing. | |||||
| The subclass must call this when it finishes playing a note and becomes available | |||||
| to play new ones. | |||||
| It must either call it in the stopNote() method, or if the voice is tailing off, | |||||
| then it should call it later during the renderNextBlock method, as soon as it | |||||
| finishes its tail-off. | |||||
| It can also be called at any time during the render callback if the sound happens | |||||
| to have finished, e.g. if it's playing a sample and the sample finishes. | |||||
| */ | |||||
| void clearCurrentNote(); | |||||
| private: | |||||
| //============================================================================== | |||||
| friend class Synthesiser; | |||||
| double currentSampleRate; | |||||
| int currentlyPlayingNote; | |||||
| uint32 noteOnTime; | |||||
| SynthesiserSound::Ptr currentlyPlayingSound; | |||||
| bool keyIsDown; // the voice may still be playing when the key is not down (i.e. sustain pedal) | |||||
| bool sostenutoPedalDown; | |||||
| JUCE_LEAK_DETECTOR (SynthesiserVoice) | |||||
| }; | |||||
| //============================================================================== | |||||
| /** | |||||
| Base class for a musical device that can play sounds. | |||||
| To create a synthesiser, you'll need to create a subclass of SynthesiserSound | |||||
| to describe each sound available to your synth, and a subclass of SynthesiserVoice | |||||
| which can play back one of these sounds. | |||||
| Then you can use the addVoice() and addSound() methods to give the synthesiser a | |||||
| set of sounds, and a set of voices it can use to play them. If you only give it | |||||
| one voice it will be monophonic - the more voices it has, the more polyphony it'll | |||||
| have available. | |||||
| Then repeatedly call the renderNextBlock() method to produce the audio. Any midi | |||||
| events that go in will be scanned for note on/off messages, and these are used to | |||||
| start and stop the voices playing the appropriate sounds. | |||||
| While it's playing, you can also cause notes to be triggered by calling the noteOn(), | |||||
| noteOff() and other controller methods. | |||||
| Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it | |||||
| what the target playback rate is. This value is passed on to the voices so that | |||||
| they can pitch their output correctly. | |||||
| */ | |||||
| class JUCE_API Synthesiser | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a new synthesiser. | |||||
| You'll need to add some sounds and voices before it'll make any sound.. | |||||
| */ | |||||
| Synthesiser(); | |||||
| /** Destructor. */ | |||||
| virtual ~Synthesiser(); | |||||
| //============================================================================== | |||||
| /** Deletes all voices. */ | |||||
| void clearVoices(); | |||||
| /** Returns the number of voices that have been added. */ | |||||
| int getNumVoices() const { return voices.size(); } | |||||
| /** Returns one of the voices that have been added. */ | |||||
| SynthesiserVoice* getVoice (int index) const; | |||||
| /** Adds a new voice to the synth. | |||||
| All the voices should be the same class of object and are treated equally. | |||||
| The object passed in will be managed by the synthesiser, which will delete | |||||
| it later on when no longer needed. The caller should not retain a pointer to the | |||||
| voice. | |||||
| */ | |||||
| void addVoice (SynthesiserVoice* newVoice); | |||||
| /** Deletes one of the voices. */ | |||||
| void removeVoice (int index); | |||||
| //============================================================================== | |||||
| /** Deletes all sounds. */ | |||||
| void clearSounds(); | |||||
| /** Returns the number of sounds that have been added to the synth. */ | |||||
| int getNumSounds() const { return sounds.size(); } | |||||
| /** Returns one of the sounds. */ | |||||
| SynthesiserSound* getSound (int index) const { return sounds [index]; } | |||||
| /** Adds a new sound to the synthesiser. | |||||
| The object passed in is reference counted, so will be deleted when it is removed | |||||
| from the synthesiser, and when no voices are still using it. | |||||
| */ | |||||
| void addSound (const SynthesiserSound::Ptr& newSound); | |||||
| /** Removes and deletes one of the sounds. */ | |||||
| void removeSound (int index); | |||||
| //============================================================================== | |||||
| /** If set to true, then the synth will try to take over an existing voice if | |||||
| it runs out and needs to play another note. | |||||
| The value of this boolean is passed into findFreeVoice(), so the result will | |||||
| depend on the implementation of this method. | |||||
| */ | |||||
| void setNoteStealingEnabled (bool shouldStealNotes); | |||||
| /** Returns true if note-stealing is enabled. | |||||
| @see setNoteStealingEnabled | |||||
| */ | |||||
| bool isNoteStealingEnabled() const { return shouldStealNotes; } | |||||
| //============================================================================== | |||||
| /** Triggers a note-on event. | |||||
| The default method here will find all the sounds that want to be triggered by | |||||
| this note/channel. For each sound, it'll try to find a free voice, and use the | |||||
| voice to start playing the sound. | |||||
| Subclasses might want to override this if they need a more complex algorithm. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
| */ | |||||
| virtual void noteOn (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| float velocity); | |||||
| /** Triggers a note-off event. | |||||
| This will turn off any voices that are playing a sound for the given note/channel. | |||||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||||
| */ | |||||
| virtual void noteOff (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| bool allowTailOff); | |||||
| /** Turns off all notes. | |||||
| This will turn off any voices that are playing a sound on the given midi channel. | |||||
| If midiChannel is 0 or less, then all voices will be turned off, regardless of | |||||
| which channel they're playing. Otherwise it represents a valid midi channel, from | |||||
| 1 to 16 inclusive. | |||||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| */ | |||||
| virtual void allNotesOff (int midiChannel, | |||||
| bool allowTailOff); | |||||
| /** Sends a pitch-wheel message. | |||||
| This will send a pitch-wheel message to any voices that are playing sounds on | |||||
| the given midi channel. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||||
| @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() | |||||
| */ | |||||
| virtual void handlePitchWheel (int midiChannel, | |||||
| int wheelValue); | |||||
| /** Sends a midi controller message. | |||||
| This will send a midi controller message to any voices that are playing sounds on | |||||
| the given midi channel. | |||||
| This method will be called automatically according to the midi data passed into | |||||
| renderNextBlock(), but may be called explicitly too. | |||||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||||
| @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() | |||||
| @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() | |||||
| */ | |||||
| virtual void handleController (int midiChannel, | |||||
| int controllerNumber, | |||||
| int controllerValue); | |||||
| virtual void handleSustainPedal (int midiChannel, bool isDown); | |||||
| virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||||
| virtual void handleSoftPedal (int midiChannel, bool isDown); | |||||
| //============================================================================== | |||||
| /** Tells the synthesiser what the sample rate is for the audio it's being used to | |||||
| render. | |||||
| This value is propagated to the voices so that they can use it to render the correct | |||||
| pitches. | |||||
| */ | |||||
| void setCurrentPlaybackSampleRate (double sampleRate); | |||||
| /** Creates the next block of audio output. | |||||
| This will process the next numSamples of data from all the voices, and add that output | |||||
| to the audio block supplied, starting from the offset specified. Note that the | |||||
| data will be added to the current contents of the buffer, so you should clear it | |||||
| before calling this method if necessary. | |||||
| The midi events in the inputMidi buffer are parsed for note and controller events, | |||||
| and these are used to trigger the voices. Note that the startSample offset applies | |||||
| both to the audio output buffer and the midi input buffer, so any midi events | |||||
| with timestamps outside the specified region will be ignored. | |||||
| */ | |||||
| void renderNextBlock (AudioSampleBuffer& outputAudio, | |||||
| const MidiBuffer& inputMidi, | |||||
| int startSample, | |||||
| int numSamples); | |||||
| protected: | |||||
| //============================================================================== | |||||
| /** This is used to control access to the rendering callback and the note trigger methods. */ | |||||
| CriticalSection lock; | |||||
| OwnedArray <SynthesiserVoice> voices; | |||||
| ReferenceCountedArray <SynthesiserSound> sounds; | |||||
| /** The last pitch-wheel values for each midi channel. */ | |||||
| int lastPitchWheelValues [16]; | |||||
| /** Searches through the voices to find one that's not currently playing, and which | |||||
| can play the given sound. | |||||
| Returns nullptr if all voices are busy and stealing isn't enabled. | |||||
| This can be overridden to implement custom voice-stealing algorithms. | |||||
| */ | |||||
| virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||||
| const bool stealIfNoneAvailable) const; | |||||
| /** Starts a specified voice playing a particular sound. | |||||
| You'll probably never need to call this, it's used internally by noteOn(), but | |||||
| may be needed by subclasses for custom behaviours. | |||||
| */ | |||||
| void startVoice (SynthesiserVoice* voice, | |||||
| SynthesiserSound* sound, | |||||
| int midiChannel, | |||||
| int midiNoteNumber, | |||||
| float velocity); | |||||
| /** Can be overridden to do custom handling of incoming midi events. */ | |||||
| virtual void handleMidiEvent (const MidiMessage&); | |||||
| private: | |||||
| //============================================================================== | |||||
| double sampleRate; | |||||
| uint32 lastNoteOnCounter; | |||||
| bool shouldStealNotes; | |||||
| BigInteger sustainPedalsDown; | |||||
| void stopVoice (SynthesiserVoice*, bool allowTailOff); | |||||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
| // Note the new parameters for this method. | |||||
| virtual int findFreeVoice (const bool) const { return 0; } | |||||
| #endif | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) | |||||
| }; | |||||
| #endif // JUCE_SYNTHESISER_H_INCLUDED | |||||