| @@ -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 | |||